mirror of
https://codeberg.org/Freedium-cfd/web.git
synced 2026-03-11 09:04:37 +00:00
feat(api): integrate dependency injection and streamline configuration
This commit is contained in:
parent
bed7382657
commit
fdd20d1ccc
17 changed files with 165 additions and 41 deletions
|
|
@ -1,4 +1,4 @@
|
|||
from freedium_library import __VERSION__
|
||||
from freedium_library.__init__ import __VERSION__
|
||||
|
||||
__NAME__ = "Freedium Library API"
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,28 @@
|
|||
from fastapi import FastAPI
|
||||
|
||||
from freedium_library.api.container import APIContainer
|
||||
from freedium_library.api.error import register_error_handler
|
||||
from freedium_library.api.handlers import register_router
|
||||
from freedium_library.api.lifespan import lifespan
|
||||
from freedium_library.api.middlewares import register_middlewares
|
||||
from freedium_library.api.settings import ApplicationSettings
|
||||
|
||||
container = APIContainer()
|
||||
|
||||
|
||||
def create_application() -> FastAPI:
|
||||
settings = ApplicationSettings()
|
||||
|
||||
if container.config.DISABLE_DOCS:
|
||||
settings.disable_docs()
|
||||
|
||||
app = FastAPI(**settings.to_dict(), lifespan=lifespan) # type: ignore
|
||||
|
||||
register_router(app, container.config.PREFIX_PATH)
|
||||
register_error_handler(app)
|
||||
register_middlewares(app)
|
||||
|
||||
return app
|
||||
|
||||
|
||||
app = create_application()
|
||||
|
|
@ -1,2 +1,8 @@
|
|||
class APIConfig:
|
||||
DISABLE_EXTERNAL_DOCS: bool = True
|
||||
from freedium_library.utils.meta.pydantic import BaseConfig, BaseSettingsConfigDict
|
||||
|
||||
|
||||
class APIConfig(BaseConfig):
|
||||
model_config = BaseSettingsConfigDict(env_prefix="API_")
|
||||
|
||||
DISABLE_DOCS: bool = True
|
||||
PREFIX_PATH: str = "/api"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,7 @@
|
|||
from dependency_injector import containers, providers
|
||||
|
||||
from freedium_library.api.config import APIConfig
|
||||
|
||||
|
||||
class APIContainer(containers.DeclarativeContainer):
|
||||
config: APIConfig = providers.Singleton(APIConfig) # type: ignore
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
from fastapi import FastAPI
|
||||
|
||||
|
||||
def register_error_handler(app: FastAPI) -> None:
|
||||
pass
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
from .utils import register_router
|
||||
|
||||
__all__ = ["register_router"]
|
||||
12
freedium-library/src/freedium_library/api/handlers/render.py
Normal file
12
freedium-library/src/freedium_library/api/handlers/render.py
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
from fastapi import APIRouter
|
||||
|
||||
handler_router = APIRouter()
|
||||
|
||||
|
||||
@handler_router.get("/render")
|
||||
async def render_page():
|
||||
return "render"
|
||||
|
||||
|
||||
def register_render_router(router: APIRouter) -> None:
|
||||
router.add_api_route(path="/render", endpoint=render_page, methods=["GET"])
|
||||
10
freedium-library/src/freedium_library/api/handlers/utils.py
Normal file
10
freedium-library/src/freedium_library/api/handlers/utils.py
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
from fastapi import APIRouter, FastAPI
|
||||
|
||||
from .render import register_render_router
|
||||
|
||||
|
||||
def register_router(app: FastAPI, router_prefix: str) -> None:
|
||||
router = APIRouter(prefix=router_prefix)
|
||||
register_render_router(router)
|
||||
|
||||
app.include_router(router)
|
||||
14
freedium-library/src/freedium_library/api/lifespan.py
Normal file
14
freedium-library/src/freedium_library/api/lifespan.py
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
from collections.abc import AsyncGenerator
|
||||
from contextlib import asynccontextmanager
|
||||
|
||||
from fastapi import FastAPI
|
||||
|
||||
from freedium_library.api.container import APIContainer
|
||||
|
||||
container = APIContainer()
|
||||
|
||||
|
||||
@asynccontextmanager
|
||||
async def lifespan(app: FastAPI) -> AsyncGenerator[None, None]:
|
||||
app.state.container = container
|
||||
yield
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
from fastapi import FastAPI
|
||||
|
||||
|
||||
def register_middlewares(app: FastAPI) -> None:
|
||||
pass
|
||||
29
freedium-library/src/freedium_library/api/settings.py
Normal file
29
freedium-library/src/freedium_library/api/settings.py
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
from dataclasses import dataclass
|
||||
|
||||
from freedium_library import __NAME__, __VERSION__
|
||||
from freedium_library.api.container import APIContainer
|
||||
|
||||
container = APIContainer()
|
||||
|
||||
|
||||
@dataclass
|
||||
class ApplicationSettings:
|
||||
title: str = __NAME__
|
||||
version: str = __VERSION__
|
||||
openapi_url: str | None = f"{container.config.PREFIX_PATH}/openapi.json"
|
||||
docs_url: str | None = f"{container.config.PREFIX_PATH}/docs"
|
||||
redoc_url: str | None = f"{container.config.PREFIX_PATH}/redoc"
|
||||
|
||||
def disable_docs(self) -> None:
|
||||
self.openapi_url = None
|
||||
self.docs_url = None
|
||||
self.redoc_url = None
|
||||
|
||||
def to_dict(self) -> dict[str, str | None]:
|
||||
return {
|
||||
"title": self.title,
|
||||
"version": self.version,
|
||||
"openapi_url": self.openapi_url,
|
||||
"docs_url": self.docs_url,
|
||||
"redoc_url": self.redoc_url,
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING, Optional
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from dependency_injector.wiring import Provide
|
||||
|
||||
|
|
@ -27,7 +27,6 @@ class MediumService(BaseService):
|
|||
self.request = request
|
||||
self.api_service = api_service
|
||||
self.path_validator = path_validator
|
||||
_content: Optional[str] = None
|
||||
|
||||
def _is_valid(self, path: str) -> bool:
|
||||
return self.path_validator.is_valid(path)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,4 @@
|
|||
from .client import Request
|
||||
from .config import RequestConfig
|
||||
|
||||
__all__ = ["Request", "RequestConfig"]
|
||||
|
|
@ -1,6 +1,5 @@
|
|||
import warnings
|
||||
from dataclasses import dataclass
|
||||
from typing import Any, Dict, Literal, Optional
|
||||
from typing import Any, Dict, Optional
|
||||
|
||||
from httpx import (
|
||||
AsyncClient,
|
||||
|
|
@ -11,39 +10,13 @@ from httpx import (
|
|||
Timeout,
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class RequestProxyConfig:
|
||||
type: Literal["http", "https", "socks5"]
|
||||
host: str
|
||||
port: int
|
||||
username: Optional[str] = None
|
||||
password: Optional[str] = None
|
||||
|
||||
@property
|
||||
def url(self) -> str:
|
||||
type = self.type.replace(
|
||||
"https", "http"
|
||||
) # See: https://www.python-httpx.org/advanced/proxies/
|
||||
|
||||
proxy_url = f"{type}://"
|
||||
if self.username and self.password:
|
||||
proxy_url += f"{self.username}:{self.password}@"
|
||||
|
||||
proxy_url += f"{self.host}:{self.port}"
|
||||
return proxy_url
|
||||
|
||||
|
||||
@dataclass
|
||||
class RequestConfig:
|
||||
timeout: int = 6
|
||||
retries: int = 3
|
||||
proxy: Optional[RequestProxyConfig] = None
|
||||
# backoff_factor: float = 0.1 # not possible. Default value: 0.5. https://github.com/encode/httpx/discussions/1895
|
||||
from .config import RequestConfig
|
||||
|
||||
|
||||
# https://github.com/encode/httpx/discussions/1748
|
||||
class Request:
|
||||
__slots__ = ("config", "_in_context_manager")
|
||||
|
||||
def __init__(self, config: Optional[RequestConfig] = None):
|
||||
self.config = config or RequestConfig()
|
||||
self._in_context_manager = False
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
from dataclasses import dataclass
|
||||
from typing import Literal, Optional
|
||||
|
||||
|
||||
@dataclass
|
||||
class RequestProxyConfig:
|
||||
type: Literal["http", "https", "socks5"]
|
||||
host: str
|
||||
port: int
|
||||
username: Optional[str] = None
|
||||
password: Optional[str] = None
|
||||
|
||||
@property
|
||||
def url(self) -> str:
|
||||
type = self.type.replace("https", "http")
|
||||
proxy_url = f"{type}://"
|
||||
if self.username and self.password:
|
||||
proxy_url += f"{self.username}:{self.password}@"
|
||||
proxy_url += f"{self.host}:{self.port}"
|
||||
return proxy_url
|
||||
|
||||
|
||||
@dataclass
|
||||
class RequestConfig:
|
||||
timeout: int = 6
|
||||
retries: int = 3
|
||||
proxy: Optional[RequestProxyConfig] = None
|
||||
# backoff_factor: float = 0.1 # not possible. Default value: 0.5. https://github.com/encode/httpx/discussions/1895
|
||||
|
|
@ -1,16 +1,17 @@
|
|||
from functools import partial
|
||||
from typing import cast
|
||||
|
||||
from pydantic_settings import BaseSettings, SettingsConfigDict
|
||||
from pydantic_settings import BaseSettings as _BaseSettings
|
||||
from pydantic_settings import SettingsConfigDict as _SettingsConfigDict
|
||||
|
||||
|
||||
class BaseConfig(BaseSettings): ...
|
||||
class BaseConfig(_BaseSettings): ...
|
||||
|
||||
|
||||
BaseSettingsConfigDict: SettingsConfigDict = cast(
|
||||
SettingsConfigDict,
|
||||
BaseSettingsConfigDict: _SettingsConfigDict = cast(
|
||||
_SettingsConfigDict,
|
||||
partial(
|
||||
SettingsConfigDict,
|
||||
_SettingsConfigDict,
|
||||
env_file=".env",
|
||||
extra="ignore",
|
||||
env_nested_delimiter="__",
|
||||
|
|
|
|||
Loading…
Reference in a new issue