Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 5 additions & 6 deletions pkgs/standards/autoapi/autoapi/v2/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
AuthNProvider,
)


# ────────────────────────────────────────────────────────────────────
class AutoAPI:
"""High-level façade class exposed to user code."""
Expand All @@ -52,7 +53,8 @@ def __init__(
get_async_db: Callable[..., AsyncIterator[AsyncSession]] | None = None,
prefix: str = "",
authorize=None,
authn: "AuthNProvider | None" = None):
authn: "AuthNProvider | None" = None,
):
# lightweight state
self.base = base
self.include = include
Expand All @@ -79,16 +81,14 @@ def __init__(
# Store DDL creation for later execution
self._ddl_executed = False


# ---------- initialise hook subsystem ---------------------

_init_hooks(self)

# ---------- collect models, build routes, etc. -----------


# ---------------- AuthN wiring -----------------
if authn is not None: # preferred path
if authn is not None: # preferred path
self._authn = authn
self._authn_dep = Depends(authn.get_principal)
# Late‑binding of the injection hook
Expand All @@ -97,8 +97,6 @@ def __init__(
self._authn = None
self._authn_dep = Depends(lambda: None)



if self.get_db:
attach_health_and_methodz(self, get_db=self.get_db)
else:
Expand Down Expand Up @@ -156,6 +154,7 @@ def get_schema(orm_cls: type, tag: _SchemaVerb):

return get_autoapi_schema(orm_cls, tag)


# keep __all__ tidy for `from autoapi import *` users
__all__ = [
"AutoAPI",
Expand Down
3 changes: 2 additions & 1 deletion pkgs/standards/autoapi/autoapi/v2/engines/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,8 @@ def async_postgres_engine(
pool_pre_ping=True,
echo=False,
)
return eng, async_sessionmaker( eng,
return eng, async_sessionmaker(
eng,
expire_on_commit=False,
class_=HybridSession, # CHANGED ←
)
48 changes: 31 additions & 17 deletions pkgs/standards/autoapi/autoapi/v2/gateway.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,17 @@
Everything else (hooks, commit/rollback, result packing)
lives in autoapi.v2._runner._invoke.
"""

from __future__ import annotations

from fastapi import APIRouter, Body, Depends, HTTPException, Request
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.orm import Session
from typing import Any, Dict

from ._runner import _invoke # ← central lifecycle engine
from .jsonrpc_models import _RPCReq, _RPCRes, _err, _ok, _http_exc_to_rpc
from ._runner import _invoke # ← central lifecycle engine
from .jsonrpc_models import _RPCReq, _RPCRes, _err, _ok, _http_exc_to_rpc
from pydantic import ValidationError


# ────────────────────────────────────────────────────────────────────────────
Expand All @@ -34,12 +36,18 @@ def build_gateway(api) -> APIRouter:
# ───────── synchronous SQLAlchemy branch ───────────────────────────────
if api.get_db:

@r.post("/rpc", response_model=_RPCRes, tags=["rpc"], dependencies=[api._authn_dep],)
@r.post(
"/rpc",
response_model=_RPCRes,
tags=["rpc"],
dependencies=[api._authn_dep],
)
async def _gateway(
req : Request,
env : _RPCReq = Body(..., embed=False),
db : Session = Depends(api.get_db,
),
req: Request,
env: _RPCReq = Body(..., embed=False),
db: Session = Depends(
api.get_db,
),
):
ctx: Dict[str, Any] = {"request": req, "db": db, "env": env}

Expand All @@ -48,14 +56,16 @@ async def _gateway(
return _err(403, "Forbidden", env)

try:
result = await _invoke(api, env.method,
params=env.params,
ctx=ctx)
result = await _invoke(api, env.method, params=env.params, ctx=ctx)
return _ok(result, env)

except HTTPException as exc:
rpc_code, rpc_data = _http_exc_to_rpc(exc)
return _err(rpc_code, exc.detail, env, rpc_data)
rpc_code, rpc_message = _http_exc_to_rpc(exc)
return _err(rpc_code, rpc_message, env)

except ValidationError as exc:
# Handle Pydantic validation errors
return _err(-32602, str(exc), env)

except Exception as exc:
# _invoke() has already rolled back & fired ON_ERROR hook.
Expand All @@ -69,9 +79,9 @@ async def _gateway(

@r.post("/rpc", response_model=_RPCRes, tags=["rpc"])
async def _gateway(
req : Request,
env : _RPCReq = Body(..., embed=False),
db : AsyncSession = Depends(api.get_async_db),
req: Request,
env: _RPCReq = Body(..., embed=False),
db: AsyncSession = Depends(api.get_async_db),
):
ctx: Dict[str, Any] = {"request": req, "db": db, "env": env}

Expand All @@ -93,8 +103,12 @@ async def _gateway(
return _ok(result, env)

except HTTPException as exc:
rpc_code, rpc_data = _http_exc_to_rpc(exc)
return _err(rpc_code, exc.detail, env, rpc_data)
rpc_code, rpc_message = _http_exc_to_rpc(exc)
return _err(rpc_code, rpc_message, env)

except ValidationError as exc:
# Handle Pydantic validation errors
return _err(-32602, str(exc), env)

except Exception as exc:
return _err(-32000, str(exc), env)
Expand Down
2 changes: 1 addition & 1 deletion pkgs/standards/autoapi/autoapi/v2/get_schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ def get_autoapi_schema(

# -- define the four core variants ---------------------------------
def _schema(verb: str):
return AutoAPI._schema(AutoAPI, orm_cls, verb=verb)
return AutoAPI._schema(orm_cls, verb=verb)

SRead = _schema("read")
SCreate = _schema("create")
Expand Down
10 changes: 6 additions & 4 deletions pkgs/standards/autoapi/autoapi/v2/hooks.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ def _hook(
Usage: @api.hook(Phase.POST_COMMIT, model=DeployKeys, op="create")
Usage: @api.hook(Phase.POST_COMMIT) # catch-all hook
"""

def _reg(f: _Hook) -> _Hook:
async_f = (
f
Expand All @@ -62,10 +63,11 @@ def _reg(f: _Hook) -> _Hook:
if isinstance(model, str):
model_name = model
else:
# Handle object reference - get the class name
model_name = (
model.__name__ if hasattr(model, "__name__") else str(model)
)
# Handle object reference - use table name and convert to canonical form
# to match the method naming convention used by _canonical()
table_name = getattr(model, "__tablename__", model.__name__.lower())
# Convert table_name to canonical form (e.g., "items" -> "Items")
model_name = "".join(w.title() for w in table_name.split("_"))
hook_key = f"{model_name}.{op}"
elif model is not None or op is not None:
# Error: both model and op must be provided together
Expand Down
Loading