ledgerlens / apps /api /pyproject.toml
Abdr007's picture
LedgerLens — deployed tree
6741fc6
Raw
History Blame Contribute Delete
4.84 kB
[project]
name = "ledgerlens-api"
version = "1.0.0"
description = "LedgerLens — Intelligent Document Processing & Financial Anomaly Detection Platform"
requires-python = ">=3.12,<3.13"
dependencies = [
"fastapi>=0.115,<1.0",
"uvicorn[standard]>=0.32,<1.0",
"pydantic>=2.9,<3.0",
"pydantic-settings>=2.6,<3.0",
"sqlalchemy[asyncio]>=2.0.36,<3.0",
"asyncpg>=0.30,<1.0",
"greenlet>=3.1,<4.0",
"anthropic>=0.40",
"pymupdf>=1.24,<2.0",
"pandas>=2.2,<3.0",
"rapidfuzz>=3.10,<4.0",
"python-multipart>=0.0.12,<1.0",
"slowapi>=0.1.9,<1.0",
"httpx>=0.27,<1.0",
]
[project.optional-dependencies]
observability = ["langfuse>=2.53"]
docgen = ["reportlab>=4.2,<5.0", "pillow>=10.4,<12.0"]
# `scripts/deploy_space.py` only. Kept out of `dev` so CI installs neither the
# client nor its transitive tree to run a test suite that never deploys anything.
deploy = ["huggingface_hub>=0.28"]
dev = [
"pytest>=8.3,<9.0",
"pytest-asyncio>=0.24,<2.0",
# Drives the app's lifespan (start-up/shut-down) around the API tests.
"asgi-lifespan>=2.1,<3.0",
# The test client. A transitive dependency of anthropic today, but the tests
# import it directly, so it is declared directly.
"httpx>=0.27,<1.0",
"ruff>=0.8,<1.0",
"mypy>=1.13,<2.0",
"pandas-stubs>=2.2",
"reportlab>=4.2,<5.0",
"pillow>=10.4,<12.0",
]
[build-system]
requires = ["setuptools>=75"]
build-backend = "setuptools.build_meta"
[tool.setuptools.packages.find]
include = ["app*"]
# ----------------------------------------------------------------------------
# ruff — lint + format. Zero findings is a release gate (see AUDIT.md).
# ----------------------------------------------------------------------------
[tool.ruff]
line-length = 100
target-version = "py312"
src = ["."]
[tool.ruff.lint]
select = [
"E", # pycodestyle errors
"W", # pycodestyle warnings
"F", # pyflakes
"I", # isort
"N", # pep8-naming
"UP", # pyupgrade
"B", # flake8-bugbear
"A", # flake8-builtins
"C4", # comprehensions
"DTZ", # flake8-datetimez (naive datetimes are a real bug class here)
"T20", # flake8-print (no stray prints in the service)
"SIM", # flake8-simplify
"PTH", # use pathlib
"RUF", # ruff-specific
"S", # bandit security
"ASYNC",
"ARG", # unused arguments
"TID", # tidy imports
]
ignore = [
"S101", # assert — used deliberately in tests and invariant checks
]
[tool.ruff.lint.per-file-ignores]
"tests/*" = ["S105", "S106", "ARG001", "ARG002"]
# Corpus generation needs a *reproducible* PRNG, not a cryptographic one:
# a fixed seed is the point, so the run is identical every time.
"app/devtools/corpus.py" = ["S311"]
# Operator-facing CLIs: their whole job is to print a report to the terminal.
"scripts/*" = ["T201"]
[tool.ruff.lint.isort]
known-first-party = ["app"]
# ----------------------------------------------------------------------------
# mypy — strict. Zero findings is a release gate.
# ----------------------------------------------------------------------------
[tool.mypy]
python_version = "3.12"
strict = true
warn_unreachable = true
warn_unused_ignores = true
disallow_any_generics = true
no_implicit_reexport = true
show_error_codes = true
plugins = ["pydantic.mypy"]
# PyMuPDF ships partial annotations; its constructors are untyped. Excluding it
# from the untyped-call rule keeps `strict` on for every line we actually own.
untyped_calls_exclude = ["pymupdf"]
exclude = "^(\\.venv|build)/"
[[tool.mypy.overrides]]
module = [
"fitz.*",
"pymupdf.*",
"slowapi.*",
"langfuse.*",
"reportlab.*",
"PIL.*",
]
ignore_missing_imports = true
[tool.pytest.ini_options]
asyncio_mode = "auto"
# The engine and its connection pool are session-scoped, so fixtures and tests
# must share one event loop; a per-function loop would orphan pooled connections.
asyncio_default_fixture_loop_scope = "session"
asyncio_default_test_loop_scope = "session"
testpaths = ["tests"]
filterwarnings = [
# Our own code must not emit warnings — that is the "zero warnings" bar.
"error",
# PyMuPDF's SWIG bindings emit these three during module initialisation.
# Promoting them to exceptions raises *inside* the C extension's init and
# takes the interpreter down with SIGSEGV, so they are exempted by exact
# message rather than by relaxing the rule.
"ignore:builtin type swigvarlink has no __module__ attribute:DeprecationWarning",
"ignore:builtin type SwigPyPacked has no __module__ attribute:DeprecationWarning",
"ignore:builtin type SwigPyObject has no __module__ attribute:DeprecationWarning",
]
addopts = "-q --strict-markers --strict-config"
markers = [
"integration: requires a live Postgres (DATABASE_URL)",
]