Brings in a working generic-CRUD framework plus its dependencies from OWC
(../owc-system/src/owc/, snapshot 2026-04-20). Every vendored module carries
a header comment naming the source; the OWC → ypms rename is the only
mechanical change.
Vendored modules:
- framework/base.py TimestampMixin, SoftDeleteMixin, VersionedMixin
- framework/pagination.py Page[T] with to_dict()
- framework/version_history.py Snapshot table
- framework/crud.py CRUDService with audit emission
- framework/bulk.py BulkOps (BulkResult / BulkUpdateItem)
- framework/router.py make_router() factory (kept without
`from __future__ import annotations` for
FastAPI type introspection — see the module
docstring)
- audit/models.py AuditLog
- audit/service.py record()
- security/models.py Permission
- security/rbac.py can() + require_permission() dependency
(DOES_SYSTEM_ADMIN as the super-role)
- users/models.py User, Role, UserRole (YPMS adds
mfa_verified and assurance_level; drops
OWC-specific claimant_id)
- auth/deps.py get_current_user() placeholder — real JWT
verify lands in 0.3
YPMS-specific additions on top of the vendored framework:
- framework/pii_audit.py emit_pii_read() — NIST AU-14 / source RFP
C.5.6.9 #5. OWC doesn't need this because
its case-management surface is staff-only;
YPMS must audit reads of minors' PII, SSN,
banking, FERPA-covered OSSE records, and
CFSA foster/ward flags.
Bugfix vs. upstream OWC:
- framework/crud.py Pagination `next_cursor` was previously the
peek-ahead row's id, which combined with
`where id > cursor` on the next page
silently dropped one row per boundary.
Caught by test_list_pagination_forwards_cursor.
Fixed here; noted in the module header so
re-vendoring OWC surfaces it upstream.
Migrations:
- 0002_framework_tables.py Baseline for users, roles, user_roles,
permissions, audit_log, version_history.
Immutable once merged per CONTRIBUTING.md.
- migrations/env.py Imports the new model modules so
autogenerate compares against them.
Tests (26 passing, 93.10% coverage, floor 80%):
- tests/framework/test_crud.py CRUDService lifecycle + snapshots
+ audit emission (7 tests)
- tests/framework/test_bulk.py BulkOps happy paths + failure
reporting (4 tests)
- tests/framework/test_pagination.py Page.to_dict serializer contract
(2 tests)
- tests/framework/test_pii_audit.py emit_pii_read row shape,
anonymous case (2 tests)
- tests/security/test_rbac.py can() + admin bypass + role union
(4 tests)
- tests/test_readyz.py /readyz smoke (1 test)
- tests/test_logging.py sanitize_for_log contract (3 tests)
Green locally: ruff clean, mypy clean (with a targeted override on
framework/router.py and framework/crud.py for TypeVar limitations that
OWC also accepts).
Advances YPMS-POAM-040. Prepares milestone 0.3 (JWT auth) and 0.4 (RBAC
seed) to plug into an already-live framework.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
91 lines
2.3 KiB
TOML
91 lines
2.3 KiB
TOML
[project]
|
|
name = "ypms"
|
|
version = "0.1.0"
|
|
description = "DC Youth Program Management System"
|
|
requires-python = ">=3.12"
|
|
dependencies = [
|
|
"fastapi>=0.115",
|
|
"uvicorn[standard]>=0.30",
|
|
"sqlalchemy>=2.0",
|
|
"alembic>=1.13",
|
|
"psycopg[binary]>=3.2",
|
|
"pydantic[email]>=2.8",
|
|
"pydantic-settings>=2.4",
|
|
"passlib>=1.7",
|
|
"python-jose[cryptography]>=3.3",
|
|
"python-multipart>=0.0.9",
|
|
"structlog>=24.4",
|
|
"cryptography>=43",
|
|
"jinja2>=3.1",
|
|
]
|
|
|
|
[project.optional-dependencies]
|
|
dev = [
|
|
"pytest>=8.3",
|
|
"pytest-asyncio>=0.24",
|
|
"pytest-cov>=5.0",
|
|
"httpx>=0.27",
|
|
"ruff>=0.6",
|
|
"mypy>=1.11",
|
|
"bandit>=1.7",
|
|
"pip-audit>=2.7",
|
|
]
|
|
|
|
[build-system]
|
|
requires = ["setuptools>=68"]
|
|
build-backend = "setuptools.build_meta"
|
|
|
|
[tool.setuptools.packages.find]
|
|
where = ["src"]
|
|
|
|
[tool.pytest.ini_options]
|
|
testpaths = ["tests"]
|
|
asyncio_mode = "auto"
|
|
addopts = "-ra --strict-markers"
|
|
|
|
[tool.coverage.run]
|
|
source = ["src/ypms"]
|
|
omit = [
|
|
"tests/*",
|
|
# router.py is testable end-to-end only once the auth stack lands in
|
|
# milestone 0.3. The framework tests exercise its underlying CRUDService
|
|
# + BulkOps directly, which is where the real logic lives.
|
|
"src/ypms/framework/router.py",
|
|
]
|
|
|
|
[tool.coverage.report]
|
|
show_missing = true
|
|
skip_covered = false
|
|
|
|
[tool.ruff]
|
|
line-length = 100
|
|
target-version = "py312"
|
|
|
|
[tool.ruff.lint]
|
|
select = ["E", "F", "I", "UP", "B", "SIM"]
|
|
# E501: handled by formatter; B008: FastAPI's Depends() in defaults is canonical
|
|
ignore = ["E501", "B008"]
|
|
|
|
[tool.mypy]
|
|
python_version = "3.12"
|
|
strict = true
|
|
ignore_missing_imports = true
|
|
|
|
# The generic router factory has to work with runtime-bound type variables
|
|
# that FastAPI introspects via typing.get_type_hints. That means the closure
|
|
# uses `create_t: type[BaseModel]` bindings which mypy can't model precisely.
|
|
# See ARCHITECTURE.md §4.1. Same suppression pattern as OWC's framework.
|
|
[[tool.mypy.overrides]]
|
|
module = "ypms.framework.router"
|
|
disable_error_code = ["valid-type", "type-arg", "attr-defined", "unused-ignore"]
|
|
|
|
# Generic CRUD uses TypeVars mypy can't fully model — the ModelT.id and
|
|
# ModelT.deleted_at accesses require a Protocol bound we don't ship for
|
|
# ergonomics. Same suppression pattern OWC uses in practice.
|
|
[[tool.mypy.overrides]]
|
|
module = "ypms.framework.crud"
|
|
disable_error_code = ["attr-defined", "valid-type", "unused-ignore"]
|
|
|
|
[tool.bandit]
|
|
exclude_dirs = ["tests", "migrations"]
|