diff --git a/src/materia/app/app.py b/src/materia/app/app.py index 1e78f7f..bedf8b4 100644 --- a/src/materia/app/app.py +++ b/src/materia/app/app.py @@ -5,6 +5,7 @@ from typing import AsyncIterator, TypedDict, Self, Optional import uvicorn from fastapi import FastAPI +from fastapi.routing import APIRoute from fastapi.middleware.cors import CORSMiddleware from materia.core import ( Config, @@ -15,6 +16,7 @@ from materia.core import ( Cron, ) from materia import routers +from materia.core.misc import optional, optional_string class Context(TypedDict): @@ -57,7 +59,7 @@ class Application: await app.prepare_database() await app.prepare_cache() await app.prepare_cron() - await app.prepare_server() + app.prepare_server() except Exception as e: app.logger.error(" ".join(e.args)) sys.exit() @@ -99,7 +101,7 @@ class Application: self.config.cron.workers_count, backend_url=url, broker_url=url ) - async def prepare_server(self): + def prepare_server(self): @asynccontextmanager async def lifespan(app: FastAPI) -> AsyncIterator[Context]: yield Context( @@ -129,6 +131,13 @@ class Application: self.backend.include_router(routers.resources.router) self.backend.include_router(routers.root.router) + for route in self.backend.routes: + if isinstance(route, APIRoute): + route.operation_id = ( + optional_string(optional(route.tags.__getitem__, 0), "{}_") + + route.name + ) + async def start(self): self.logger.info(f"Spinning up cron workers [{self.config.cron.workers_count}]") self.cron.run_workers() diff --git a/src/materia/app/cli.py b/src/materia/app/cli.py index e5e83f9..0d506f8 100644 --- a/src/materia/app/cli.py +++ b/src/materia/app/cli.py @@ -5,6 +5,7 @@ from materia.core.config import Config from materia.core.logging import Logger from materia.app import Application import asyncio +import json @click.group() @@ -112,5 +113,36 @@ def config_check(path: Path): logger.info("OK.") +@cli.group() +def export(): + pass + + +@export.command("openapi", help="Export an OpenAPI specification.") +@click.option( + "--path", + "-p", + type=Path, + default=Path.cwd().joinpath("openapi.json"), + help="Path to the file.", +) +def export_openapi(path: Path): + path = path.resolve() + logger = Logger.new() + config = Config() + app = Application(config) + app.prepare_server() + + logger.info("Writing file at {}", path) + + try: + with open(path, "w") as io: + json.dump(app.backend.openapi(), io, sort_keys=False) + except Exception as e: + logger.error("{}", e) + + logger.info("All done.") + + if __name__ == "__main__": cli() diff --git a/src/materia/core/cron.py b/src/materia/core/cron.py index 756eb28..e189e64 100644 --- a/src/materia/core/cron.py +++ b/src/materia/core/cron.py @@ -34,10 +34,14 @@ class Cron: ): cron = Cron( workers_count, + # TODO: change log level + # TODO: exclude pickle + # TODO: disable startup banner Celery( "cron", backend=backend_url, broker=broker_url, + broker_connection_retry_on_startup=True, task_serializer="pickle", accept_content=["pickle", "json"], **kwargs, diff --git a/src/materia/routers/api/file.py b/src/materia/routers/api/file.py index e78e8de..dd1df10 100644 --- a/src/materia/routers/api/file.py +++ b/src/materia/routers/api/file.py @@ -68,7 +68,23 @@ class FileSizeValidator: raise HTTPException(status.HTTP_413_REQUEST_ENTITY_TOO_LARGE) -@router.post("/file") +@router.post("/file", openapi_extra={ + "requestBody" : { + "content": { + "multipart/form-data": { + "schema": { + "required": ["file", "path"], + "type": "object", + "properties": { + "file": { "type": "string", "format": "binary" }, + "path": { "type": "string", "format": "path", "example": "/"} + } + } + } + }, + "required": True + } +}) async def create( request: Request, repository: Repository = Depends(middleware.repository), diff --git a/tests/conftest.py b/tests/conftest.py index fb2ce93..958269f 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -129,27 +129,7 @@ async def api_client( app.database = database app.cache = cache app.cron = cron - await app.prepare_server() - - # logger = make_logger(api_config) - - # @asynccontextmanager - # async def lifespan(app: FastAPI) -> AsyncIterator[AppContext]: - # yield AppContext( - # config=api_config, database=database, cache=cache, logger=logger - # ) - - # app = FastAPI(lifespan=lifespan) - # app.include_router(routers.api.router) - # app.include_router(routers.resources.router) - # app.include_router(routers.root.router) - # app.add_middleware( - # CORSMiddleware, - # allow_origins=["*"], - # allow_credentials=True, - # allow_methods=["*"], - # allow_headers=["*"], - # ) + app.prepare_server() async with LifespanManager(app.backend) as manager: async with AsyncClient(