Skip to content
Draft
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
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from ...op import OpSpec
from ...op.types import StepFn
from ...runtime.executor import _Ctx
from ...runtime.atoms.storage import to_stored as _to_stored
from .ctx import _ctx_db, _ctx_payload, _ctx_request
from .identifiers import _resolve_ident

Expand Down Expand Up @@ -140,7 +141,13 @@ async def step(ctx: Any) -> Any:
def _wrap_core(model: type, target: str) -> StepFn:
async def step(ctx: Any) -> Any:
db = _ctx_db(ctx)
payload = _ctx_payload(ctx)
assembled = getattr(ctx, "temp", {}).get("assembled_values")
if isinstance(assembled, Mapping):
ctx.payload = dict(assembled)
_to_stored.run(None, ctx)
payload = ctx.temp.get("assembled_values", {})
else:
payload = _ctx_payload(ctx)

if target == "create":
return await _core.create(model, payload, db=db)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ async def _endpoint(
"db": db,
"payload": payload,
"path_params": parent_kw,
"specs": getattr(model, "__autoapi_cols__", {}),
"env": SimpleNamespace(
method=alias, params=payload, target=target, model=model
),
Expand Down Expand Up @@ -120,6 +121,7 @@ async def _endpoint(
"db": db,
"payload": payload,
"path_params": parent_kw,
"specs": getattr(model, "__autoapi_cols__", {}),
"env": SimpleNamespace(
method=alias, params=payload, target=target, model=model
),
Expand Down Expand Up @@ -246,6 +248,7 @@ async def _endpoint(
"db": db,
"payload": payload,
"path_params": parent_kw,
"specs": getattr(model, "__autoapi_cols__", {}),
"env": SimpleNamespace(
method=exec_alias, params=payload, target=exec_target, model=model
),
Expand Down
3 changes: 3 additions & 0 deletions pkgs/standards/autoapi/autoapi/v3/bindings/rest/member.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ async def _endpoint(
"db": db,
"payload": payload,
"path_params": path_params,
"specs": getattr(model, "__autoapi_cols__", {}),
"env": SimpleNamespace(
method=alias, params=payload, target=target, model=model
),
Expand Down Expand Up @@ -145,6 +146,7 @@ async def _endpoint(
"db": db,
"payload": payload,
"path_params": path_params,
"specs": getattr(model, "__autoapi_cols__", {}),
"env": SimpleNamespace(
method=alias, params=payload, target=target, model=model
),
Expand Down Expand Up @@ -240,6 +242,7 @@ async def _endpoint(
"db": db,
"payload": payload,
"path_params": path_params,
"specs": getattr(model, "__autoapi_cols__", {}),
"env": SimpleNamespace(
method=alias, params=payload, target=target, model=model
),
Expand Down
11 changes: 6 additions & 5 deletions pkgs/standards/autoapi/autoapi/v3/config/resolver.py
Original file line number Diff line number Diff line change
Expand Up @@ -148,16 +148,17 @@ def _coerce_map(obj: Any) -> Mapping[str, Any]:
return {k: v for k, v in asdict(obj).items() if v is not None}
except Exception:
pass
# namespace-like with __dict__
d = getattr(obj, "__dict__", None)
if isinstance(d, dict):
return d
# pydantic v2 config-like
if hasattr(obj, "model_dump") and callable(getattr(obj, "model_dump")):
try:
return dict(obj.model_dump())
# Drop ``None`` values so optional fields don't override defaults.
return {k: v for k, v in obj.model_dump(exclude_none=True).items()}
except Exception:
pass
# namespace-like with __dict__
d = getattr(obj, "__dict__", None)
if isinstance(d, dict):
return d
# last resort: single 'cfg' attr if it's a mapping
cfg = getattr(obj, "cfg", None)
if isinstance(cfg, Mapping):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -155,11 +155,10 @@ def _is_paired(colspec: Any) -> bool:
continue
if getattr(obj, "_paired", None) is not None:
return True
if any(
bool(getattr(obj, name, False))
for name in ("secret_once", "paired", "paired_input", "generate_on_absent")
):
return True
for name in ("secret_once", "paired", "paired_input", "generate_on_absent"):
val = getattr(obj, name, None)
if isinstance(val, bool) and val:
return True
if any(
callable(getattr(obj, name, None))
for name in ("generator", "paired_generator", "secret_generator")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ def _render_run(
return
hints = getattr(resp_ns, "hints", None)
default_media = getattr(resp_ns, "default_media", "application/json")
envelope_default = getattr(resp_ns, "envelope_default", True)
envelope_default = getattr(resp_ns, "envelope_default", False)
resp_ns.result = _render.render(
req,
result,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ def render(
*,
hints: Optional[ResponseHints] = None,
default_media: str = "application/json",
envelope_default: bool = True,
envelope_default: bool = False,
) -> Response:
logger.debug("Rendering response with payload type %s", type(payload))
if isinstance(payload, Response):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ def run(obj: Optional[object], ctx: Any) -> None:
if "schema_out" in temp:
return

op = (getattr(ctx, "op", None) or getattr(ctx, "method", None) or "").lower() or None
fields_sorted = sorted(specs.keys())
entries: list[Dict[str, Any]] = []
by_field: Dict[str, Dict[str, Any]] = {}
Expand All @@ -75,6 +76,9 @@ def run(obj: Optional[object], ctx: Any) -> None:
f = getattr(col, "field", None)

out_enabled = _bool_attr(io, "out", "allow_out", "expose_out", default=True)
if out_enabled and op and getattr(io, "out_verbs", ()): # type: ignore[arg-type]
if op not in getattr(io, "out_verbs", ()): # type: ignore[arg-type]
out_enabled = False
if not out_enabled:
# Not exposed on output — skip entirely for outbound schema
continue
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ def run(obj: Optional[object], ctx: Any) -> None:
- We intentionally keep keys as canonical field names so out:masking can
evaluate sensitivity on real fields, while alias extras remain separate.
"""
if obj is None:
return
schema_out = _schema_out(ctx)
if not schema_out:
return
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import uuid as _uuid
import logging
from typing import Any, Callable, Dict, Mapping, MutableMapping, Optional, Tuple
import typing as _typing

from fastapi import HTTPException, status as _status

Expand Down Expand Up @@ -177,6 +178,8 @@ def _target_type(colspec: Any) -> Optional[type]:
return None
for name in ("py_type", "python_type"):
t = getattr(field, name, None)
if t is _typing.Any:
return None
if isinstance(t, type):
return t
return None
Expand Down
14 changes: 13 additions & 1 deletion pkgs/standards/autoapi/autoapi/v3/runtime/kernel.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,19 @@ def _discover_atoms() -> list[_DiscoveredAtom]:

def _wrap_atom(run: _AtomRun) -> StepFn:
async def _step(ctx: Any) -> Any:
rv = run(None, ctx)
candidate = getattr(ctx, "result", None)
if candidate is None or isinstance(candidate, (list, tuple, set, Mapping)):
obj = getattr(ctx, "obj", None)
else:
obj = candidate
if isinstance(obj, (list, tuple, set, Mapping)):
obj = None
if obj is not None:
try:
setattr(ctx, "obj", obj)
except Exception:
pass
rv = run(obj, ctx)
if hasattr(rv, "__await__"):
return await rv # type: ignore[misc]
return rv
Expand Down
12 changes: 12 additions & 0 deletions pkgs/standards/autoapi/tests/unit/test_config_pydantic_none.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
from pydantic import BaseModel

from autoapi.v3.config import resolve_cfg


class AppSpec(BaseModel):
trace: dict | None = None


def test_pydantic_none_fields_do_not_override_defaults() -> None:
cfg = resolve_cfg(appspec=AppSpec())
assert cfg.trace == {"enabled": True}
Loading