Merge branch 'develop' into di

# Conflicts:
#	app/main.py
#	app/services/vcs.py
#	config/config.yaml
This commit is contained in:
Piotr Dec 2025-10-16 00:13:38 +02:00
commit 1eab4cd6fc
Signed by: stawros
GPG key ID: 74B18A3F0F1E99C0
10 changed files with 162 additions and 44 deletions

View file

View file

@ -1,7 +0,0 @@
if __name__ == '__main__':
try:
from main import run
except ImportError:
from .main import run
run()

View file

@ -1 +1,7 @@
from .settings import AppConfig
from .settings import GitConfig
from .settings import KeePassConfig
from .settings import Settings
from .settings import get_settings
__all__ = [AppConfig, GitConfig, KeePassConfig, Settings, get_settings]

View file

@ -13,7 +13,8 @@ class AppConfig(BaseModel):
class GitConfig(BaseModel):
directory: str = "/opt/repo/sample"
path: Path = Path("/opt/repo/sample")
url: str = "ssh://git@hattori.ztsh.eu:29418/paas/heimdall.git"
branch: str = "master"
remote: str = "origin"

20
app/core/router.py Normal file
View file

@ -0,0 +1,20 @@
from fastapi import APIRouter, Request
from fastapi.responses import HTMLResponse
from jinja2 import Environment, FileSystemLoader, select_autoescape
router = APIRouter()
# Inicjalizacja Jinja2
templates_env = Environment(
loader=FileSystemLoader("app/templates"),
autoescape=select_autoescape(["html", "xml"]),
)
# Przykładowy endpoint HTML
@router.get("/", response_class=HTMLResponse)
async def index(request: Request) -> HTMLResponse:
template = templates_env.get_template("index.html")
html = template.render(title="Strona główna", request=request)
return HTMLResponse(content=html)

View file

@ -1,43 +1,69 @@
from fastapi import FastAPI, Request
from fastapi.responses import HTMLResponse
from injectable import load_injection_container
from jinja2 import Environment, FileSystemLoader, select_autoescape
import logging
from injectable import load_injection_container
from fastapi import FastAPI
from app.api.v1 import router as api_v1_router
from app.config import get_settings
from app.core.core import WebhookProcessor
# Inicjalizacja Jinja2
templates_env = Environment(
loader=FileSystemLoader("app/templates"),
autoescape=select_autoescape(["html", "xml"]),
)
app = FastAPI(title="Karl", version="0.1.0")
# Rejestracja routera API pod /api/v1
app.include_router(api_v1_router, prefix="/api/v1", tags=["v1"])
# app.add_event_handler()
load_injection_container()
webhook_service = WebhookProcessor()
print(webhook_service.health)
from app.util.logging import LoggingHandler, ExternalLoggingHandler
# Przykładowy endpoint HTML
@app.get("/", response_class=HTMLResponse)
async def index(request: Request) -> HTMLResponse:
template = templates_env.get_template("index.html")
html = template.render(title="Strona główna", request=request)
return HTMLResponse(content=html)
class KarlApplication:
def __init__(self) -> None:
self._set_logging()
app = FastAPI(title="Karl", version="0.1.0")
self._set_routes(app)
self._set_events(app)
self._init_services()
pass
def _set_logging(self):
logging.basicConfig(level=logging.INFO, handlers=[LoggingHandler()])
loggers = (
"uvicorn",
"uvicorn.access",
"uvicorn.error",
"fastapi",
"asyncio",
"starlette",
)
external_handler = ExternalLoggingHandler()
for logger_name in loggers:
logging_logger = logging.getLogger(logger_name)
logging_logger.handlers = [external_handler]
logging_logger.propagate = False
def _set_routes(self, app: FastAPI):
from app.core.router import router as core_router
app.include_router(core_router)
from app.api.v1 import router as api_v1_router
app.include_router(api_v1_router, prefix="/api/v1", tags=["v1"])
pass
def _set_events(self, app: FastAPI):
pass
def _init_services(self):
logger = logging.getLogger(__name__)
load_injection_container()
webhook_service = WebhookProcessor()
logger.info(webhook_service.health)
def run() -> None:
def app():
return KarlApplication()
if __name__ == "__main__":
import uvicorn
settings = get_settings()
uvicorn.run(
"app.main:app",
factory=True,
host=settings.app.host,
port=settings.app.port,
reload=settings.app.reload,
log_config=None,
)

View file

@ -1,23 +1,35 @@
from git import Repo, Remote
from injectable import injectable
from app.config import get_settings
from app.config import GitConfig, get_settings
@injectable(singleton=True)
class GitService:
def __init__(self):
self._settings = get_settings()
try: # TODO: clone if not exists
self._repo = Repo(self._settings.git.directory)
self._origin: Remote = self._repo.remotes.origin
except:
self._repo = None
self._repo = self._check_preconditions(self._settings.git)
if self._repo.head.ref.name != self._settings.git.branch:
self._repo.git.checkout(self._settings.git.branch)
self._origin: Remote = self._repo.remotes.origin
def get_modified_compose(self) -> str | None:
self._update()
return self._diff()
@staticmethod
def _check_preconditions(config: GitConfig) -> Repo:
def clone():
return Repo.clone_from(config.url, config.path, branch=config.branch)
import os
if not config.path.exists():
return clone()
if not (config.path / ".git").exists():
os.rmdir(config.path)
return clone()
return Repo(config.path)
def _update(self):
self._origin.pull()

59
app/util/logging.py Normal file
View file

@ -0,0 +1,59 @@
from logging import Formatter, StreamHandler
class NamingCache:
def __init__(self):
self._cache = {}
def __getitem__(self, key):
if key not in self._cache:
self._cache[key] = self.shorten(key)
return self._cache[key]
@staticmethod
def shorten(logger_name: str) -> str:
target_length = 18
if len(logger_name) > target_length:
parts = logger_name.split('.')
part = 0
while len(logger_name) > target_length:
if part == len(parts) - 1:
logger_name = f'...{logger_name[-(target_length - 3):]}'
break
parts[part] = parts[part][0]
logger_name = '.'.join(parts)
part += 1
return logger_name.ljust(target_length)
class ApplicationFormatter(Formatter):
def __init__(self, handler_prefix: str = ''):
super().__init__()
self._logger_names = NamingCache()
self._handler_prefix = handler_prefix
def format(self, record):
from datetime import datetime
timestamp = datetime.fromtimestamp(record.created).isoformat(sep=' ', timespec='milliseconds')
level = record.levelname.replace('WARNING', 'WARN').rjust(5)
thread_name = record.threadName.replace(' (', '#').replace(')', '').rjust(16)[-16:] # TODO: NamingCache?
logger_name = self._logger_names[f"{self._handler_prefix}{record.name}"]
message = record.getMessage()
formatted = f"{timestamp} {level} [{thread_name}] {logger_name} : {message}"
if record.exc_info:
formatted += "\n" + self.formatException(record.exc_info)
return formatted
class LoggingHandler(StreamHandler):
def __init__(self):
super().__init__()
self.setFormatter(ApplicationFormatter(handler_prefix='karl.'))
class ExternalLoggingHandler(StreamHandler):
def __init__(self):
super().__init__()
self.setFormatter(ApplicationFormatter())