✨ Integrate logging
This commit is contained in:
parent
e848601f79
commit
67d89a94f4
4 changed files with 66 additions and 13 deletions
|
|
@ -9,6 +9,9 @@ import typer
|
||||||
import uvicorn
|
import uvicorn
|
||||||
|
|
||||||
from ohmyapi.core import runtime, scaffolding
|
from ohmyapi.core import runtime, scaffolding
|
||||||
|
from ohmyapi.core.logging import setup_logging
|
||||||
|
|
||||||
|
logger = setup_logging()
|
||||||
|
|
||||||
app = typer.Typer(
|
app = typer.Typer(
|
||||||
help="OhMyAPI — Django-flavored FastAPI scaffolding with tightly integrated TortoiseORM."
|
help="OhMyAPI — Django-flavored FastAPI scaffolding with tightly integrated TortoiseORM."
|
||||||
|
|
@ -19,18 +22,24 @@ app = typer.Typer(
|
||||||
def startproject(name: str):
|
def startproject(name: str):
|
||||||
"""Create a new OhMyAPI project in the given directory."""
|
"""Create a new OhMyAPI project in the given directory."""
|
||||||
scaffolding.startproject(name)
|
scaffolding.startproject(name)
|
||||||
|
logger.info(f"✅ Project '{name}' created successfully.")
|
||||||
|
logger.info(f"🔧 Next, configure your project in {name}/settings.py")
|
||||||
|
|
||||||
|
|
||||||
@app.command()
|
@app.command()
|
||||||
def startapp(app_name: str, root: str = "."):
|
def startapp(app_name: str, root: str = "."):
|
||||||
"""Create a new app with the given name in your OhMyAPI project."""
|
"""Create a new app with the given name in your OhMyAPI project."""
|
||||||
scaffolding.startapp(app_name, root)
|
scaffolding.startapp(app_name, root)
|
||||||
|
print(f"✅ App '{app_name}' created in project '{root}' successfully.")
|
||||||
|
print(f"🔧 Remember to add '{app_name}' to your INSTALLED_APPS!")
|
||||||
|
|
||||||
|
|
||||||
@app.command()
|
@app.command()
|
||||||
def dockerize(root: str = "."):
|
def dockerize(root: str = "."):
|
||||||
"""Create template Dockerfile and docker-compose.yml."""
|
"""Create template Dockerfile and docker-compose.yml."""
|
||||||
scaffolding.copy_static("docker", root)
|
scaffolding.copy_static("docker", root)
|
||||||
|
logger.info(f"✅ Templates created successfully.")
|
||||||
|
logger.info(f"🔧 Next, run `docker compose up -d --build`")
|
||||||
|
|
||||||
|
|
||||||
@app.command()
|
@app.command()
|
||||||
|
|
@ -42,7 +51,7 @@ def serve(root: str = ".", host="127.0.0.1", port=8000):
|
||||||
project = runtime.Project(project_path)
|
project = runtime.Project(project_path)
|
||||||
app_instance = project.app()
|
app_instance = project.app()
|
||||||
app_instance = project.configure_app(app_instance)
|
app_instance = project.configure_app(app_instance)
|
||||||
uvicorn.run(app_instance, host=host, port=int(port), reload=False)
|
uvicorn.run(app_instance, host=host, port=int(port), reload=False, log_config=None)
|
||||||
|
|
||||||
|
|
||||||
@app.command()
|
@app.command()
|
||||||
|
|
@ -107,6 +116,8 @@ def makemigrations(app: str = "*", name: str = "auto", root: str = "."):
|
||||||
asyncio.run(project.makemigrations(app_label=app, name=name))
|
asyncio.run(project.makemigrations(app_label=app, name=name))
|
||||||
else:
|
else:
|
||||||
asyncio.run(project.makemigrations(app_label=app, name=name))
|
asyncio.run(project.makemigrations(app_label=app, name=name))
|
||||||
|
logger.info(f"✅ Migrations created successfully.")
|
||||||
|
logger.info(f"🔧 To migrate the DB, run `ohmyapi migrate`, next.")
|
||||||
|
|
||||||
|
|
||||||
@app.command()
|
@app.command()
|
||||||
|
|
@ -121,6 +132,7 @@ def migrate(app: str = "*", root: str = "."):
|
||||||
asyncio.run(project.migrate(app))
|
asyncio.run(project.migrate(app))
|
||||||
else:
|
else:
|
||||||
asyncio.run(project.migrate(app))
|
asyncio.run(project.migrate(app))
|
||||||
|
logger.info(f"✅ Migrations ran successfully.")
|
||||||
|
|
||||||
|
|
||||||
@app.command()
|
@app.command()
|
||||||
|
|
|
||||||
38
src/ohmyapi/core/logging.py
Normal file
38
src/ohmyapi/core/logging.py
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
|
||||||
|
def setup_logging():
|
||||||
|
"""Configure unified logging for ohmyapi + FastAPI/Uvicorn."""
|
||||||
|
log_level = os.getenv("OHMYAPI_LOG_LEVEL", "INFO").upper()
|
||||||
|
level = getattr(logging, log_level, logging.INFO)
|
||||||
|
|
||||||
|
# Root logger (affects FastAPI, uvicorn, etc.)
|
||||||
|
logging.basicConfig(
|
||||||
|
level=level,
|
||||||
|
format="[%(asctime)s] [%(levelname)s] %(name)s: %(message)s",
|
||||||
|
datefmt="%Y-%m-%d %H:%M:%S",
|
||||||
|
handlers=[
|
||||||
|
logging.StreamHandler(sys.stdout)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
# Separate ohmyapi logger (optional)
|
||||||
|
logger = logging.getLogger("ohmyapi")
|
||||||
|
|
||||||
|
# Direct warnings/errors to stderr
|
||||||
|
class LevelFilter(logging.Filter):
|
||||||
|
def filter(self, record):
|
||||||
|
# Send warnings+ to stderr, everything else to stdout
|
||||||
|
if record.levelno >= logging.WARNING:
|
||||||
|
record.stream = sys.stderr
|
||||||
|
else:
|
||||||
|
record.stream = sys.stdout
|
||||||
|
return True
|
||||||
|
|
||||||
|
for handler in logger.handlers:
|
||||||
|
handler.addFilter(LevelFilter())
|
||||||
|
|
||||||
|
logger.setLevel(level)
|
||||||
|
return logger
|
||||||
|
|
@ -15,8 +15,11 @@ from aerich.exceptions import NotInitedError
|
||||||
from fastapi import APIRouter, FastAPI
|
from fastapi import APIRouter, FastAPI
|
||||||
from tortoise import Tortoise
|
from tortoise import Tortoise
|
||||||
|
|
||||||
|
from ohmyapi.core.logging import setup_logging
|
||||||
from ohmyapi.db.model import Model
|
from ohmyapi.db.model import Model
|
||||||
|
|
||||||
|
logger = setup_logging()
|
||||||
|
|
||||||
|
|
||||||
class Project:
|
class Project:
|
||||||
"""
|
"""
|
||||||
|
|
@ -29,6 +32,7 @@ class Project:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, project_path: str):
|
def __init__(self, project_path: str):
|
||||||
|
logger.debug(f"Loading project: {project_path}")
|
||||||
self.project_path = Path(project_path).resolve()
|
self.project_path = Path(project_path).resolve()
|
||||||
self._apps: Dict[str, App] = {}
|
self._apps: Dict[str, App] = {}
|
||||||
self.migrations_dir = self.project_path / "migrations"
|
self.migrations_dir = self.project_path / "migrations"
|
||||||
|
|
@ -238,6 +242,7 @@ class App:
|
||||||
# Import the app, so its __init__.py runs.
|
# Import the app, so its __init__.py runs.
|
||||||
mod: ModuleType = importlib.import_module(name)
|
mod: ModuleType = importlib.import_module(name)
|
||||||
|
|
||||||
|
logger.debug(f"Loading app: {self.name}")
|
||||||
self.__load_models(f"{self.name}.models")
|
self.__load_models(f"{self.name}.models")
|
||||||
self.__load_routes(f"{self.name}.routes")
|
self.__load_routes(f"{self.name}.routes")
|
||||||
self.__load_middlewares(f"{self.name}.middlewares")
|
self.__load_middlewares(f"{self.name}.middlewares")
|
||||||
|
|
@ -258,7 +263,6 @@ class App:
|
||||||
try:
|
try:
|
||||||
importlib.import_module(mod_name)
|
importlib.import_module(mod_name)
|
||||||
except ModuleNotFoundError:
|
except ModuleNotFoundError:
|
||||||
print(f"no models detected: {mod_name}")
|
|
||||||
return
|
return
|
||||||
|
|
||||||
# Acoid duplicates.
|
# Acoid duplicates.
|
||||||
|
|
@ -276,9 +280,11 @@ class App:
|
||||||
and issubclass(value, Model)
|
and issubclass(value, Model)
|
||||||
and not name == Model.__name__
|
and not name == Model.__name__
|
||||||
):
|
):
|
||||||
|
# monkey-patch __module__ to point to well-known aliases
|
||||||
value.__module__ = value.__module__.replace("ohmyapi.builtin.", "ohmyapi_")
|
value.__module__ = value.__module__.replace("ohmyapi.builtin.", "ohmyapi_")
|
||||||
if value.__module__.startswith(mod_name):
|
if value.__module__.startswith(mod_name):
|
||||||
self._models[mod_name] = self._models.get(mod_name, []) + [value]
|
self._models[mod_name] = self._models.get(mod_name, []) + [value]
|
||||||
|
logger.debug(f" - Model: {mod_name} -> {name}")
|
||||||
|
|
||||||
# if it's a package, recurse into submodules
|
# if it's a package, recurse into submodules
|
||||||
if hasattr(mod, "__path__"):
|
if hasattr(mod, "__path__"):
|
||||||
|
|
@ -300,7 +306,6 @@ class App:
|
||||||
try:
|
try:
|
||||||
importlib.import_module(mod_name)
|
importlib.import_module(mod_name)
|
||||||
except ModuleNotFound:
|
except ModuleNotFound:
|
||||||
print(f"no routes detected: {mod_name}")
|
|
||||||
return
|
return
|
||||||
|
|
||||||
# Avoid duplicates.
|
# Avoid duplicates.
|
||||||
|
|
@ -315,6 +320,7 @@ class App:
|
||||||
for name, value in vars(mod).copy().items():
|
for name, value in vars(mod).copy().items():
|
||||||
if isinstance(value, APIRouter) and not name == APIRouter.__name__:
|
if isinstance(value, APIRouter) and not name == APIRouter.__name__:
|
||||||
self._routers[mod_name] = self._routers.get(mod_name, []) + [value]
|
self._routers[mod_name] = self._routers.get(mod_name, []) + [value]
|
||||||
|
logger.debug(f" - Router: {mod_name} -> {name} -> {value.routes}")
|
||||||
|
|
||||||
# if it's a package, recurse into submodules
|
# if it's a package, recurse into submodules
|
||||||
if hasattr(mod, "__path__"):
|
if hasattr(mod, "__path__"):
|
||||||
|
|
@ -330,7 +336,6 @@ class App:
|
||||||
try:
|
try:
|
||||||
mod = importlib.import_module(mod_name)
|
mod = importlib.import_module(mod_name)
|
||||||
except ModuleNotFoundError:
|
except ModuleNotFoundError:
|
||||||
print(f"no middlewares detected: {mod_name}")
|
|
||||||
return
|
return
|
||||||
|
|
||||||
getter = getattr(mod, "get", None)
|
getter = getattr(mod, "get", None)
|
||||||
|
|
|
||||||
|
|
@ -2,12 +2,16 @@ from pathlib import Path
|
||||||
|
|
||||||
from jinja2 import Environment, FileSystemLoader
|
from jinja2 import Environment, FileSystemLoader
|
||||||
|
|
||||||
|
from ohmyapi.core.logging import setup_logging
|
||||||
|
|
||||||
import shutil
|
import shutil
|
||||||
|
|
||||||
# Base templates directory
|
# Base templates directory
|
||||||
TEMPLATE_DIR = Path(__file__).parent / "templates"
|
TEMPLATE_DIR = Path(__file__).parent / "templates"
|
||||||
env = Environment(loader=FileSystemLoader(str(TEMPLATE_DIR)))
|
env = Environment(loader=FileSystemLoader(str(TEMPLATE_DIR)))
|
||||||
|
|
||||||
|
logger = setup_logging()
|
||||||
|
|
||||||
|
|
||||||
def render_template_file(template_path: Path, context: dict, output_path: Path):
|
def render_template_file(template_path: Path, context: dict, output_path: Path):
|
||||||
"""Render a single Jinja2 template file to disk."""
|
"""Render a single Jinja2 template file to disk."""
|
||||||
|
|
@ -60,7 +64,7 @@ def copy_static(dir_name: str, target_dir: Path):
|
||||||
template_dir = TEMPLATE_DIR / dir_name
|
template_dir = TEMPLATE_DIR / dir_name
|
||||||
target_dir = Path(target_dir)
|
target_dir = Path(target_dir)
|
||||||
if not template_dir.exists():
|
if not template_dir.exists():
|
||||||
print(f"no templates found under: {dir_name}")
|
logger.error(f"no templates found under: {dir_name}")
|
||||||
return
|
return
|
||||||
|
|
||||||
for root, _, files in template_dir.walk():
|
for root, _, files in template_dir.walk():
|
||||||
|
|
@ -69,19 +73,15 @@ def copy_static(dir_name: str, target_dir: Path):
|
||||||
src = root_path / file
|
src = root_path / file
|
||||||
dst = target_dir / file
|
dst = target_dir / file
|
||||||
if dst.exists():
|
if dst.exists():
|
||||||
print(f"⛔ File exists, skipping: {dst}")
|
logger.warning(f"⛔ File exists, skipping: {dst}")
|
||||||
continue
|
continue
|
||||||
shutil.copy(src, ".")
|
shutil.copy(src, dst)
|
||||||
print(f"✅ Templates created successfully.")
|
|
||||||
print(f"🔧 Next, run `docker compose up -d --build`")
|
|
||||||
|
|
||||||
def startproject(name: str):
|
def startproject(name: str):
|
||||||
"""Create a new project: flat structure, all project templates go into <name>/"""
|
"""Create a new project: flat structure, all project templates go into <name>/"""
|
||||||
target_dir = Path(name).resolve()
|
target_dir = Path(name).resolve()
|
||||||
target_dir.mkdir(exist_ok=True)
|
target_dir.mkdir(exist_ok=True)
|
||||||
render_template_dir("project", target_dir, {"project_name": name})
|
render_template_dir("project", target_dir, {"project_name": name})
|
||||||
print(f"✅ Project '{name}' created successfully.")
|
|
||||||
print(f"🔧 Next, configure your project in {target_dir / 'settings.py'}")
|
|
||||||
|
|
||||||
|
|
||||||
def startapp(name: str, project: str):
|
def startapp(name: str, project: str):
|
||||||
|
|
@ -94,5 +94,3 @@ def startapp(name: str, project: str):
|
||||||
{"project_name": target_dir.resolve().name, "app_name": name},
|
{"project_name": target_dir.resolve().name, "app_name": name},
|
||||||
subdir_name=name,
|
subdir_name=name,
|
||||||
)
|
)
|
||||||
print(f"✅ App '{name}' created in project '{target_dir}' successfully.")
|
|
||||||
print(f"🔧 Remember to add '{name}' to your INSTALLED_APPS!")
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue