diff --git a/app/__init__.py b/app/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/app/__main__.py b/app/__main__.py deleted file mode 100644 index 0ae0fbb..0000000 --- a/app/__main__.py +++ /dev/null @@ -1,7 +0,0 @@ -if __name__ == '__main__': - try: - from main import run - except ImportError: - from .main import run - - run() diff --git a/app/config/__init__.py b/app/config/__init__.py index 1fb2e97..7829e5c 100644 --- a/app/config/__init__.py +++ b/app/config/__init__.py @@ -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] diff --git a/app/config/settings.py b/app/config/settings.py index c831957..06f75e3 100644 --- a/app/config/settings.py +++ b/app/config/settings.py @@ -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" diff --git a/app/core/router.py b/app/core/router.py new file mode 100644 index 0000000..0d1a300 --- /dev/null +++ b/app/core/router.py @@ -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) diff --git a/app/main.py b/app/main.py index eb0d6ea..22e83b4 100644 --- a/app/main.py +++ b/app/main.py @@ -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, ) diff --git a/app/services/vcs.py b/app/services/vcs.py index 8e58277..a7d83b0 100644 --- a/app/services/vcs.py +++ b/app/services/vcs.py @@ -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() diff --git a/app/util/logging.py b/app/util/logging.py new file mode 100644 index 0000000..29992b6 --- /dev/null +++ b/app/util/logging.py @@ -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()) diff --git a/config/config.yaml b/config/config.yaml index c48d6e9..6d4400f 100644 --- a/config/config.yaml +++ b/config/config.yaml @@ -3,7 +3,8 @@ app: port: 8000 reload: true git: - directory: "F:/IdeaProjects/paas/karl/.compose_repository" + path: "F:/IdeaProjects/paas/karl/.compose_repository" + branch: "main" kp: file: "config/kp.kdbx" secret: "config/secret.txt" diff --git a/run.sh b/run.sh index c65ef75..8d2e2b9 100644 --- a/run.sh +++ b/run.sh @@ -1 +1 @@ -uv run uvicorn app.main:app --reload +uvicorn app.main:app --factory --reload