feat(*): initial project from templates
This commit is contained in:
commit
09ca301d51
70 files changed
+2184
No files matched your search
Whitespace-only changes.
@@ -0,0 +1,17 @@
|
||||
import uvicorn
|
||||
|
||||
from utils.env import env
|
||||
|
||||
|
||||
def main() -> None:
|
||||
uvicorn.run(
|
||||
"api.app:app",
|
||||
host=env.api.host,
|
||||
port=env.api.port,
|
||||
workers=env.api.workers,
|
||||
forwarded_allow_ips="*",
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,45 @@
|
||||
from collections.abc import AsyncGenerator
|
||||
from contextlib import asynccontextmanager
|
||||
from pathlib import Path
|
||||
|
||||
from fastapi import FastAPI
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from fastapi.responses import FileResponse
|
||||
|
||||
from api import routers
|
||||
from utils.env import env
|
||||
from utils.logging import setup_logging
|
||||
|
||||
IMMUTABLE_HEADERS = {"Cache-Control": "public, max-age=31536000, immutable"}
|
||||
NO_CACHE_HEADERS = {"Cache-Control": "no-cache"}
|
||||
|
||||
|
||||
@asynccontextmanager
|
||||
async def lifespan(_app: FastAPI) -> AsyncGenerator[None]:
|
||||
setup_logging()
|
||||
yield
|
||||
|
||||
|
||||
app = FastAPI(
|
||||
title="spotigrab API",
|
||||
lifespan=lifespan,
|
||||
openapi_url="/openapi.json" if env.api.docs else None,
|
||||
)
|
||||
|
||||
app.add_middleware(
|
||||
CORSMiddleware, allow_origins=["*"], allow_methods=["*"], allow_headers=["*"]
|
||||
)
|
||||
|
||||
app.include_router(routers.router)
|
||||
|
||||
spa_dir = Path(env.api.static_dir).resolve()
|
||||
if spa_dir.is_dir():
|
||||
|
||||
@app.get("/{spa_path:path}")
|
||||
async def serve_spa(spa_path: str) -> FileResponse:
|
||||
candidate = (spa_dir / spa_path).resolve()
|
||||
if spa_path and candidate.is_relative_to(spa_dir) and candidate.is_file():
|
||||
immutable = spa_path.startswith("_app/immutable/")
|
||||
headers = IMMUTABLE_HEADERS if immutable else NO_CACHE_HEADERS
|
||||
return FileResponse(candidate, headers=headers)
|
||||
return FileResponse(spa_dir / "index.html", headers=NO_CACHE_HEADERS)
|
||||
Whitespace-only changes.
Whitespace-only changes.
@@ -0,0 +1,6 @@
|
||||
from fastapi import APIRouter
|
||||
|
||||
from . import api
|
||||
|
||||
router = APIRouter()
|
||||
router.include_router(api.router, prefix="/api")
|
||||
@@ -0,0 +1,6 @@
|
||||
from fastapi import APIRouter
|
||||
|
||||
from . import health
|
||||
|
||||
router = APIRouter()
|
||||
router.include_router(health.router, prefix="/health")
|
||||
@@ -0,0 +1,9 @@
|
||||
from fastapi import APIRouter
|
||||
|
||||
router = APIRouter(tags=["health"])
|
||||
|
||||
|
||||
@router.get("")
|
||||
@router.get("/")
|
||||
async def health() -> dict[str, bool]:
|
||||
return {"ok": True}
|
||||
Whitespace-only changes.
@@ -0,0 +1,4 @@
|
||||
from .env import env
|
||||
from .logging import logger
|
||||
|
||||
__all__ = ["env", "logger"]
|
||||
@@ -0,0 +1,37 @@
|
||||
from pydantic import Field
|
||||
from pydantic_settings import BaseSettings, SettingsConfigDict
|
||||
|
||||
|
||||
class Section(BaseSettings):
|
||||
model_config = SettingsConfigDict(extra="ignore")
|
||||
|
||||
|
||||
class LogSettings(Section):
|
||||
level: str = "INFO"
|
||||
level_external: str = "WARNING"
|
||||
show_time: bool = False
|
||||
show_locals: bool = False
|
||||
console_width: int = 150
|
||||
|
||||
|
||||
class ApiSettings(Section):
|
||||
host: str = "0.0.0.0" # noqa: S104
|
||||
port: int = 8080
|
||||
workers: int = 1
|
||||
docs: bool = False
|
||||
static_dir: str = "static"
|
||||
|
||||
|
||||
class Settings(BaseSettings):
|
||||
log: LogSettings = Field(default_factory=LogSettings)
|
||||
api: ApiSettings = Field(default_factory=ApiSettings)
|
||||
|
||||
model_config = SettingsConfigDict(
|
||||
case_sensitive=False,
|
||||
env_file=("../.env", ".env"),
|
||||
env_nested_delimiter="__",
|
||||
extra="ignore",
|
||||
)
|
||||
|
||||
|
||||
env = Settings()
|
||||
@@ -0,0 +1,6 @@
|
||||
import asyncio
|
||||
from importlib import import_module
|
||||
|
||||
|
||||
async def preload(module: str) -> None:
|
||||
await asyncio.to_thread(import_module, module)
|
||||
@@ -0,0 +1,33 @@
|
||||
import logging
|
||||
|
||||
from rich.console import Console
|
||||
from rich.logging import RichHandler
|
||||
from rich.traceback import install
|
||||
|
||||
from .env import env
|
||||
|
||||
console = Console(width=env.log.console_width, color_system="auto", force_terminal=True)
|
||||
|
||||
|
||||
def setup_logging() -> None:
|
||||
logging.basicConfig(
|
||||
level=env.log.level_external,
|
||||
format="",
|
||||
datefmt=None,
|
||||
handlers=[
|
||||
RichHandler(
|
||||
console=console,
|
||||
markup=True,
|
||||
rich_tracebacks=True,
|
||||
enable_link_path=False,
|
||||
tracebacks_show_locals=env.log.show_locals,
|
||||
omit_repeated_times=False,
|
||||
show_time=env.log.show_time,
|
||||
)
|
||||
],
|
||||
)
|
||||
install(console=console, show_locals=env.log.show_locals)
|
||||
|
||||
|
||||
logger = logging.getLogger("spotigrab")
|
||||
logger.setLevel(env.log.level)
|
||||
Reference in new issue
Block a user