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 from .settings import get_settings
__all__ = [AppConfig, GitConfig, KeePassConfig, Settings, get_settings]

View file

@ -13,7 +13,8 @@ class AppConfig(BaseModel):
class GitConfig(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" branch: str = "master"
remote: str = "origin" 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 import logging
from fastapi.responses import HTMLResponse
from injectable import load_injection_container from injectable import load_injection_container
from jinja2 import Environment, FileSystemLoader, select_autoescape from fastapi import FastAPI
from app.api.v1 import router as api_v1_router
from app.config import get_settings from app.config import get_settings
from app.core.core import WebhookProcessor from app.core.core import WebhookProcessor
from app.util.logging import LoggingHandler, ExternalLoggingHandler
# Inicjalizacja Jinja2
templates_env = Environment(
loader=FileSystemLoader("app/templates"),
autoescape=select_autoescape(["html", "xml"]),
)
class KarlApplication:
def __init__(self) -> None:
self._set_logging()
app = FastAPI(title="Karl", version="0.1.0") app = FastAPI(title="Karl", version="0.1.0")
self._set_routes(app)
self._set_events(app)
self._init_services()
pass
# Rejestracja routera API pod /api/v1 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"]) app.include_router(api_v1_router, prefix="/api/v1", tags=["v1"])
# app.add_event_handler() pass
def _set_events(self, app: FastAPI):
pass
def _init_services(self):
logger = logging.getLogger(__name__)
load_injection_container() load_injection_container()
webhook_service = WebhookProcessor() webhook_service = WebhookProcessor()
print(webhook_service.health) logger.info(webhook_service.health)
# Przykładowy endpoint HTML def app():
@app.get("/", response_class=HTMLResponse) return KarlApplication()
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)
def run() -> None: if __name__ == "__main__":
import uvicorn import uvicorn
settings = get_settings() settings = get_settings()
uvicorn.run( uvicorn.run(
"app.main:app", "app.main:app",
factory=True,
host=settings.app.host, host=settings.app.host,
port=settings.app.port, port=settings.app.port,
reload=settings.app.reload, reload=settings.app.reload,
log_config=None,
) )

View file

@ -1,23 +1,35 @@
from git import Repo, Remote from git import Repo, Remote
from injectable import injectable from injectable import injectable
from app.config import get_settings from app.config import GitConfig, get_settings
@injectable(singleton=True) @injectable(singleton=True)
class GitService: class GitService:
def __init__(self): def __init__(self):
self._settings = get_settings() self._settings = get_settings()
try: # TODO: clone if not exists self._repo = self._check_preconditions(self._settings.git)
self._repo = Repo(self._settings.git.directory) 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 self._origin: Remote = self._repo.remotes.origin
except:
self._repo = None
def get_modified_compose(self) -> str | None: def get_modified_compose(self) -> str | None:
self._update() self._update()
return self._diff() 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): def _update(self):
self._origin.pull() 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())

View file

@ -3,7 +3,8 @@ app:
port: 8000 port: 8000
reload: true reload: true
git: git:
directory: "F:/IdeaProjects/paas/karl/.compose_repository" path: "F:/IdeaProjects/paas/karl/.compose_repository"
branch: "main"
kp: kp:
file: "config/kp.kdbx" file: "config/kp.kdbx"
secret: "config/secret.txt" secret: "config/secret.txt"

2
run.sh
View file

@ -1 +1 @@
uv run uvicorn app.main:app --reload uvicorn app.main:app --factory --reload