From 00e18af8fd6dd13d8c7d79875445d185b2cc9bfe Mon Sep 17 00:00:00 2001 From: Brian Wiborg Date: Wed, 1 Oct 2025 21:55:01 +0200 Subject: [PATCH] =?UTF-8?q?=F0=9F=93=9D=F0=9F=8E=A8=20More=20inline=20docs?= =?UTF-8?q?;=20small=20adjustments?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/ohmyapi/core/runtime.py | 61 ++++++++++++++++++++++++------------- 1 file changed, 40 insertions(+), 21 deletions(-) diff --git a/src/ohmyapi/core/runtime.py b/src/ohmyapi/core/runtime.py index 103853c..1cc1773 100644 --- a/src/ohmyapi/core/runtime.py +++ b/src/ohmyapi/core/runtime.py @@ -4,6 +4,7 @@ import importlib.util import json import pkgutil import sys +from http import HTTPStatus from pathlib import Path from types import ModuleType from typing import Any, Dict, Generator, List, Optional, Type @@ -21,8 +22,9 @@ class Project: """ Project runtime loader + Tortoise/Aerich integration. - - injects builtin apps as ohmyapi_ - - builds unified tortoise config for runtime + - aliases builtin apps as ohmyapi_ + - loads all INSTALLED_APPS into scope + - builds unified tortoise config for ORM runtime - provides makemigrations/migrate methods using Aerich Command API """ @@ -34,8 +36,8 @@ class Project: if str(self.project_path) not in sys.path: sys.path.insert(0, str(self.project_path)) - # Pre-register builtin apps as ohmyapi_. - # This makes all builtin apps easily loadable via f"ohmyapi_{app_name}". + # Alias builtin apps as ohmyapi_. + # We need this, because Tortoise app-names may not include dots `.`. spec = importlib.util.find_spec("ohmyapi.builtin") if spec and spec.submodule_search_locations: for _, modname, _ in pkgutil.iter_modules(spec.submodule_search_locations): @@ -200,7 +202,8 @@ class App: self.project = project self.name = name - # Reference to this app's models modules. + # Reference to this app's models modules. Tortoise needs to know the + # modules where to lookup models for this app. self._models: Dict[str, ModuleType] = {} # Reference to this app's routes modules. @@ -219,14 +222,20 @@ class App: return self.__repr__() def __load_models(self, mod_name: str): + """ + Recursively scan through a module and collect all models. + If the module is a package, iterate through its submodules. + """ + + # An app may come without any models. try: importlib.import_module(mod_name) except ModuleNotFoundError: print(f"no models detected: {mod_name}") return + # Acoid duplicates. visited: set[str] = set() - out: Dict[str, ModuleType] = {} def walk(mod_name: str): mod = importlib.import_module(mod_name) @@ -240,7 +249,7 @@ class App: and issubclass(value, Model) and not name == Model.__name__ ): - out[mod_name] = out.get(mod_name, []) + [value] + self._models[mod_name] = self._models.get(mod_name, []) + [value] # if it's a package, recurse into submodules if hasattr(mod, "__path__"): @@ -249,18 +258,24 @@ class App: ): walk(subname) + # Walk the walk. walk(mod_name) - self._models = out def __load_routes(self, mod_name: str): + """ + Recursively scan through a module and collect all APIRouters. + If the module is a package, iterate through all its submodules. + """ + + # An app may come without any routes. try: importlib.import_module(mod_name) except ModuleNotFound: print(f"no routes detected: {mod_name}") return + # Avoid duplicates. visited: set[str] = set() - out: Dict[str, ModuleType] = {} def walk(mod_name: str): mod = importlib.import_module(mod_name) @@ -270,7 +285,7 @@ class App: for name, value in vars(mod).copy().items(): if isinstance(value, APIRouter) and not name == APIRouter.__name__: - out[mod_name] = out.get(mod_name, []) + [value] + self._routers[mod_name] = self._routers.get(mod_name, []) + [value] # if it's a package, recurse into submodules if hasattr(mod, "__path__"): @@ -280,22 +295,17 @@ class App: submod = importlib.import_module(subname) walk(submod) + # Walk the walk. walk(mod_name) - self._routers = out def __serialize_route(self, route): - """Convert APIRoute to JSON-serializable dict.""" + """ + Convert APIRoute to JSON-serializable dict. + """ return { "path": route.path, - "name": route.name, - "methods": list(route.methods), - "endpoint": route.endpoint.__name__, # just the function name - "response_model": ( - getattr(route, "response_model", None).__name__ - if getattr(route, "response_model", None) - else None - ), - "tags": getattr(route, "tags", None), + "method": list(route.methods)[0], + "endpoint": f"{route.endpoint.__module__}.{route.endpoint.__name__}", } def __serialize_router(self): @@ -303,6 +313,9 @@ class App: @property def models(self) -> List[ModuleType]: + """ + Return a list of all loaded models. + """ out = [] for module in self._models: for model in self._models[module]: @@ -313,6 +326,9 @@ class App: @property def routes(self): + """ + Return an APIRouter with all loaded routes. + """ router = APIRouter() for routes_mod in self._routers: for r in self._routers[routes_mod]: @@ -320,6 +336,9 @@ class App: return router.routes def dict(self) -> Dict[str, Any]: + """ + Convenience method for serializing the runtime data. + """ return { "models": [ f"{self.name}.{m.__name__}" for m in self.models[f"{self.name}.models"]