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
27 changes: 26 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -128,4 +128,29 @@ select = ["E", "F", "I", "N", "UP", "B", "SIM"]
pythonVersion = "3.13"
typeCheckingMode = "basic"
include = ["src/flydesk_idp"]
exclude = ["tests/**", "**/__pycache__/**"]
exclude = ["tests/**", "**/__pycache__/**", "vendor/**"]

# Two execution-environment overrides silence false positives that come
# from framework-level metaprogramming that pyright can't follow:
#
# * ``web/controllers``: pyfly's ``Body[T]`` / ``Valid[T]`` / ``Header[T]`` /
# ``PathVar[T]`` are runtime ``Generic[T]`` classes the framework
# strips at injection time. Statically they look like opaque wrappers
# around T, so pyright rejects every call site that passes the
# unwrapped value through. Suppress ``reportArgumentType`` until
# pyfly publishes proper type stubs (or switches the markers to
# ``Annotated[T, …]``).
# * ``core/services/extraction/postprocess``: pydantic v2's
# ``populate_by_name=True`` lets us construct DTOs with either the
# Python field name (``fieldName=``) or the alias (``name=``).
# Pyright tracks the alias only, so it flags every construction
# that uses the Python name. The runtime accepts both; suppress
# ``reportCallIssue`` here.

[[tool.pyright.executionEnvironments]]
root = "src/flydesk_idp/web/controllers"
reportArgumentType = "none"

[[tool.pyright.executionEnvironments]]
root = "src/flydesk_idp/core/services/extraction"
reportCallIssue = "none"
9 changes: 8 additions & 1 deletion src/flydesk_idp/core/observability/outbound_log.py
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,14 @@ def _extract_usage_fields(result: Any, model: str) -> dict[str, Any]:
if cache_read:
fields["cache_read"] = cache_read
try:
from fireflyframework_agentic.observability.cost import get_cost_calculator
# ImportError-tolerant: the cost resolver is an optional surface
# in fireflyframework-agentic. Older / unreleased refs export it
# under a different module path; we keep the call lazy and
# swallow every failure mode so missing pricing data degrades
# to a zero-cost log line instead of a hard error.
from fireflyframework_agentic.observability.cost import ( # pyright: ignore[reportMissingImports]
get_cost_calculator, # pyright: ignore[reportAttributeAccessIssue]
)

calc = get_cost_calculator("auto")
cost = calc.estimate(model, input_tokens, output_tokens)
Expand Down
7 changes: 6 additions & 1 deletion src/flydesk_idp/core/services/rules/rule_engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -240,9 +240,14 @@ def _serialise_field_value(field: ExtractedField) -> Any:
return [
{
"rowName": row.fieldName,
# Nested ``fieldValueFound`` only carries a row of
# sub-fields for array-typed parents; for scalar leaves
# it is the value itself, which we skip here. The
# isinstance guard also satisfies pyright -- without it
# the iterator type is the full union (str / int / …).
"row": [
{"fieldName": sub.fieldName, "value": sub.fieldValueFound}
for sub in row.fieldValueFound
for sub in (row.fieldValueFound if isinstance(row.fieldValueFound, list) else [])
if isinstance(sub, ExtractedField)
],
}
Expand Down
19 changes: 15 additions & 4 deletions src/flydesk_idp/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,21 @@ async def _lifespan(app: Any):
bodies, response models) instead of the lazy-resolution shim that
pyfly registers with FastAPI.
"""
_pyfly._route_metadata = getattr(app.state, "pyfly_route_metadata", [])
_pyfly._docs_enabled = getattr(app.state, "pyfly_docs_enabled", False)
_pyfly._host = str(_pyfly.config.get("pyfly.web.host", "0.0.0.0"))
_pyfly._port = int(_pyfly.config.get("pyfly.server.port", 8400))
# PyFlyApplication exposes route metadata + host/port via private
# attributes that the framework's own lifespan flow writes to; we
# mirror that here so pyfly's structured-logging "server_started"
# event reports the right host/port. ``setattr`` keeps pyright
# quiet about writes to attrs that aren't declared on the class --
# the framework reads them via ``getattr(..., default)`` so missing
# attrs are always safe.
# Ruff prefers direct assignment (B010); pyright rejects it because
# the framework class doesn't declare these attrs. We suppress the
# ruff rule on each line and keep ``setattr`` -- the standard
# escape hatch for legitimate dynamic attribute writes.
setattr(_pyfly, "_route_metadata", getattr(app.state, "pyfly_route_metadata", [])) # noqa: B010
setattr(_pyfly, "_docs_enabled", getattr(app.state, "pyfly_docs_enabled", False)) # noqa: B010
setattr(_pyfly, "_host", str(_pyfly.config.get("pyfly.web.host", "0.0.0.0"))) # noqa: B010
setattr(_pyfly, "_port", int(_pyfly.config.get("pyfly.server.port", 8400))) # noqa: B010
await _pyfly.startup()
# Re-scan HealthIndicator beans now that the container has built
# every singleton (the eager scan inside ``create_app`` runs BEFORE
Expand Down
Loading