[project] name = "rmi-backend" version = "0.1.0" description = "RMI (Rug Munch Intelligence) crypto-scam detection backend" requires-python = ">=3.11" readme = "DESIGN.md" # Pinned versions are kept in requirements.txt for the Docker build. # This pyproject.toml defines tool config (ruff/mypy/pytest) and a thin # dependency list. `pip install -e .` works in dev; production uses # `pip install -r requirements.txt`. dependencies = [ "fastapi>=0.115.0", "uvicorn[standard]>=0.32.0", "pydantic>=2.9.0", "pydantic-settings>=2.6.0", "redis[hiredis]>=5.2.0", "httpx>=0.27.0", "sqlalchemy[asyncio]>=2.0.36", "alembic>=1.14.0", "structlog>=24.4.0", "langfuse>=3.0.0", "PyJWT>=2.10.0", "python-multipart>=0.0.17", "duckdb>=0.10.0", ] [project.optional-dependencies] dev = [ "ruff>=0.8.0", "mypy>=1.13.0", "pytest>=8.3.0", "pytest-asyncio>=0.24.0", "pre-commit>=4.0.0", "gitleaks>=8.18.0", ] [build-system] requires = ["setuptools>=68", "wheel"] build-backend = "setuptools.build_meta" # Declare app/ as an installable package so `pip install -e ".[dev]"` registers # it on sys.path. Without this, pytest collection fails outside the repo cwd # (33/35 test files fail without PYTHONPATH=.). # packages.find auto-discovers every sub-package that has __init__.py. # Sub-packages without __init__.py are namespace packages and remain out of # scope for Phase 1.3 (tracked for Phase 3). [tool.setuptools.packages.find] where = ["."] include = ["app*"] exclude = ["app._archive*", "app._archive.**"] namespaces = false [tool.ruff] line-length = 100 target-version = "py311" extend-exclude = [ "_legacy_main.py", "app/legacy/", "app/_archive/", "alembic/versions/", "build/", "dist/", ] [tool.ruff.lint] # Modern 2026 default rule set. Strict but practical. select = [ "E", # pycodestyle errors "W", # pycodestyle warnings "F", # pyflakes "I", # isort "N", # pep8-naming "UP", # pyupgrade "B", # flake8-bugbear "C4", # flake8-comprehensions "SIM", # flake8-simplify "TCH", # flake8-type-checking "RUF", # ruff-specific "ASYNC",# flake8-async "S", # flake8-bandit (security) "PIE", # flake8-pie "PT", # flake8-pytest-style ] ignore = [ "E501", # line length (handled by formatter) "B008", # function calls in defaults (FastAPI Depends) "S101", # assert (we use it in dev paths) "N818", # exception naming (we use Error suffix) ] [tool.ruff.lint.per-file-ignores] # Tests are allowed to be looser "tests/**/*.py" = ["S101", "S105", "S106", "B011"] # Legacy code excluded entirely "_legacy_main.py" = ["ALL"] "app/legacy/**" = ["ALL"] [tool.ruff.lint.isort] known-first-party = ["app"] combine-as-imports = true [tool.mypy] python_version = "3.11" strict = true plugins = ["pydantic.mypy"] # Pragmatic: don't break legacy, but enforce on new code. exclude = [ "_legacy_main.py", "app/legacy/", "app/_archive/", "alembic/versions/", "tests/", ] # Tolerate missing imports for libraries without type stubs ignore_missing_imports = false warn_unused_configs = true warn_redundant_casts = true warn_unreachable = true [[tool.mypy.overrides]] # Domain layer is the strictest — no FastAPI leakage allowed. module = ["app.domains.*"] disallow_any_explicit = true disallow_any_decorated = false # Pydantic decorators need this no_implicit_optional = true warn_return_any = true [[tool.mypy.overrides]] # Core (cross-cutting) is also strict. module = ["app.core.*"] disallow_any_explicit = true no_implicit_optional = true [[tool.mypy.overrides]] # Legacy stays relaxed until migrated. module = ["_legacy_main", "app.legacy.*"] ignore_errors = true [tool.pytest.ini_options] minversion = "8.0" asyncio_mode = "auto" testpaths = ["tests"] python_files = ["test_*.py", "*_test.py"] addopts = [ "-ra", "--strict-markers", "--strict-config", "--showlocals", "--tb=short", ] markers = [ "slow: marks tests as slow (deselect with -m 'not slow')", "integration: marks tests that need docker/compose", ] # ── Custom size cap (not in pyproject.toml by default) ──────────────── # Enforced via .pre-commit-config.yaml custom check. [tool.coverage.run] source = ["app"] omit = ["_legacy_main.py", "app/legacy/*", "tests/*"] [tool.coverage.report] show_missing = true skip_covered = true fail_under = 60 # raise as migration progresses