dc00a4f8f5
Wave 5-1 mypy adoption (D-058 relaxed-first): mypy 2.3.1, [tool.mypy] check_untyped_defs + no_implicit_optional + warn_unused_ignores + warn_redundant_casts + warn_unreachable, no overrides (every dep ships py.typed), files = ai_service + scripts/sandbox-agent.py + tests (not excluded). 215 errors → 0: ~140 fixed at source incl. genuine bugs found by the gate (LLMProvider protocol async-generator shape, SessionStore protocol sync/async mismatch, structured_reply genericity, get_args migrations, sandbox-agent Optional guard honesty, proctor test arg-order, dead ExecResult kwarg, 16 sqlmodel col() sites); 34 live suppressions scoped-coded + reasoned. Original 27 type:ignore dispositioned: 12 dead removed, 15 scoped (warn_unused_ignores self-eliminates the rest). Wired: scripts/typecheck.sh + package.json + turbo task + root ai:typecheck — pnpm typecheck now 5 tsc + mypy. Wave 5-2 recharts 2.15.4 → 3.10.1 (D-049/D-059): one breaking class (Tooltip formatter ValueType typing); 2 one-line inference fixes on the 2 chart files; no rollback needed (well inside the G-106 timebox). Deprecated direct dep resolved; uuid@9 gone from the tree. Build 248/248 with chart SSR markers verified in exported employer + dashboard pages. Wave 5-3 deprecated-transitive audit (G-109): 4 lockfile entries remain, all dev-only, none actionable within NFR budget — glob@7.2.3 + inflight + rimraf@3 via Storybook docgen chain (flat-cache@3); glob@10.5.0 via c8→test-exclude (D-052-binding coverage tool — swapping it is out of budget). Python: no EOL packages; zero-warning regime under filterwarnings=error is the machine-checked proof. Counts: 670 py / 44 web / 64 cli, zero warnings, G-104 allowlist still exactly 2. ---ci--- phase: 5 milestone: v0.7 status: execute ---/ci---
146 lines
6.3 KiB
TOML
146 lines
6.3 KiB
TOML
[build-system]
|
|
requires = ["setuptools>=68"]
|
|
build-backend = "setuptools.build_meta"
|
|
|
|
[project]
|
|
name = "nextcraft-ai-service"
|
|
version = "0.6.3"
|
|
description = "Nextcraft AI tutor service — six LLM agents behind a provider-agnostic layer"
|
|
requires-python = ">=3.11"
|
|
dependencies = [
|
|
"fastapi>=0.141,<0.142",
|
|
"uvicorn>=0.52,<0.53",
|
|
"pydantic>=2.13,<2.14",
|
|
"pydantic-settings>=2.15,<2.16",
|
|
"httpx>=0.28,<0.29",
|
|
"sse-starlette>=3.4,<3.5",
|
|
"sqlmodel>=0.0.24,<0.1",
|
|
"sqlalchemy>=2.0,<2.1",
|
|
"websockets>=13,<16",
|
|
"aiofiles>=24.1,<26",
|
|
# POST /v1/defense/{id}/answer multipart audio (REQ-3-006): FastAPI
|
|
# form/File parsing requires python-multipart at runtime.
|
|
"python-multipart>=0.0.32,<0.1",
|
|
# P4-B resume parse (REQ-6-005, D-054, A-410): PDF text extraction.
|
|
# Pure-Python, probe-verified 6.19.0 — the ONLY new v0.6 dependency.
|
|
"pypdf>=6.19,<7",
|
|
]
|
|
|
|
[project.optional-dependencies]
|
|
dev = [
|
|
"pytest>=9.1,<10",
|
|
"pytest-asyncio>=1.4,<2",
|
|
"pytest-cov>=7.1,<8",
|
|
"pytest-timeout>=2.4,<3",
|
|
"ruff>=0.14",
|
|
# P5 type hardening (D-058): mypy relaxed-first baseline. Resolved 2.3.1.
|
|
"mypy>=2.3,<2.4",
|
|
]
|
|
|
|
[tool.setuptools.packages.find]
|
|
include = ["ai_service*"]
|
|
|
|
[tool.pytest.ini_options]
|
|
asyncio_mode = "auto"
|
|
# D-056: pin the fixture loop scope — pytest-asyncio 1.4 warns at configure
|
|
# time when this is unset and a future default flip would change loop reuse
|
|
# for async fixtures. "function" is the current-effective default semantics.
|
|
asyncio_default_fixture_loop_scope = "function"
|
|
testpaths = ["tests"]
|
|
# P1 harness wiring (D-053/D-054): strict markers + a 60s global timeout.
|
|
# `timeout` also ships its own marker (`pytest.mark.timeout`); the 180s
|
|
# override for real_server/userns tests is applied by the collection hook
|
|
# in tests/conftest.py — marker-level values beat this global default.
|
|
markers = [
|
|
"fast: in-process unit test (default tier — unmarked tests are fast)",
|
|
"real_server: spins a real uvicorn server process",
|
|
"userns: enters a real unshare user namespace",
|
|
]
|
|
addopts = "--strict-markers --timeout=60 --timeout-method=signal"
|
|
# P3 zero-warning policy (D-056): base action `error` — every warning fails
|
|
# the run; new first-party warnings can never pass silently. The ONLY
|
|
# entries after "error" are the 2 known upstream import-time deprecations,
|
|
# each scoped by exact message prefix + emitting module (the module whose
|
|
# import triggers the warning, i.e. the frame the warning is attributed to
|
|
# after stacklevel adjustment — NOT the package the warning text mentions):
|
|
#
|
|
# 1. fastapi.testclient re-exports starlette's TestClient, and importing
|
|
# it emits starlette's httpx→httpx2 migration warning (stacklevel=2
|
|
# attributes it to fastapi/testclient.py's import line).
|
|
# 2. starlette.testclient resolves `anyio.abc.BlockingPortal` in a
|
|
# module-level type alias; anyio's lazy-import __getattr__ emits the
|
|
# alias-deprecation (stacklevel=3 attributes it to starlette's
|
|
# testclient.py).
|
|
#
|
|
# G-104 invariant: category-scoped ignores and ANY ignore matching
|
|
# ai_service.* are forbidden forever; new upstream deprecations may join
|
|
# this list only as module+message-scoped entries, justified in ship notes.
|
|
# Fields: action:message-regex:category:module-regex:lineno (pytest parses
|
|
# ini entries as raw regex — dots below are explicitly escaped).
|
|
filterwarnings = [
|
|
"error",
|
|
# Upstream (1/2): starlette 1.6 httpx→httpx2 migration notice, raised
|
|
# while importing fastapi.testclient (importer frame is fastapi's).
|
|
"ignore:Using `httpx` with `starlette\\.testclient` is deprecated:starlette.exceptions.StarletteDeprecationWarning:fastapi\\.testclient",
|
|
# Upstream (2/2): anyio 4.x lazy-import alias deprecation, raised while
|
|
# starlette.testclient evaluates its `_PortalFactoryType` alias.
|
|
"ignore:The anyio\\.abc\\.BlockingPortal alias is deprecated:DeprecationWarning:starlette\\.testclient",
|
|
]
|
|
|
|
[tool.coverage.run]
|
|
source = ["ai_service"] # tests/ excluded by construction — only the package is measured
|
|
branch = true
|
|
|
|
[tool.coverage.report]
|
|
# exclude_also APPENDS to coverage.py's built-ins (which already cover
|
|
# `pragma: no cover` and `if TYPE_CHECKING:`); these add the __main__
|
|
# entry-point guard (none in ai_service today — future-proof).
|
|
exclude_also = [
|
|
"if __name__ == [\"']__main__[\"']:",
|
|
"if TYPE_CHECKING:",
|
|
"pragma: no cover",
|
|
]
|
|
|
|
[tool.ruff]
|
|
line-length = 100
|
|
target-version = "py311"
|
|
|
|
[tool.ruff.lint]
|
|
select = ["E", "F", "W", "I", "UP", "B", "RUF100"]
|
|
# B008: Depends() in argument defaults is the idiomatic FastAPI DI pattern
|
|
ignore = ["B008"]
|
|
|
|
# P5 mypy relaxed-first baseline (D-058) — NOT full strict. The flags
|
|
# below are the binding set from the decision; per-module tightening via
|
|
# [[tool.mypy.overrides]] is available later (the plan's Wave-5 follow-up
|
|
# form). Scope decision (recorded per the task's pragmatic clause): ALL
|
|
# THREE trees pass — ai_service/ + scripts/sandbox-agent.py + tests/ —
|
|
# so tests are NOT excluded; every test violation was either fixed at
|
|
# source or scoped with a coded ignore + reason inline. Tests remain
|
|
# fully checked; a future tests.* per-module relaxation override stays
|
|
# available (D-058) if the annotation burden grows.
|
|
[tool.mypy]
|
|
python_version = "3.11"
|
|
files = [
|
|
"ai_service",
|
|
"scripts/sandbox-agent.py",
|
|
"tests",
|
|
]
|
|
# Untracked runtime droppings under tests/ (gitignored via
|
|
# `apps/ai-service/**/sandboxes/`) must never be type-checked.
|
|
exclude = ["tests/sandboxes"]
|
|
# D-058 relaxed-first baseline:
|
|
check_untyped_defs = true # real bugs hide in unannotated bodies too
|
|
no_implicit_optional = true # implicit Optional is a genuine bug class
|
|
warn_unused_ignores = true # stale `# type: ignore` becomes an error (self-eliminating suppressions)
|
|
warn_redundant_casts = true
|
|
warn_unreachable = true
|
|
warn_unused_configs = true
|
|
# Start strict on imports: every runtime dep checked code imports ships
|
|
# stubs (verified py.typed: fastapi/uvicorn/pydantic/pydantic-settings/
|
|
# httpx/sse-starlette/sqlmodel/sqlalchemy/websockets/python-multipart/
|
|
# pypdf/pytest/pytest-asyncio; aiofiles + pytest-timeout/pytest-cov are
|
|
# never imported by checked code). Overrides are reserved for deps that
|
|
# genuinely lack stubs — currently NONE, so no ignore_missing_imports
|
|
# overrides are listed; warn_unused_configs would flag any added no-op.
|
|
ignore_missing_imports = false |