-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathpyproject.toml
More file actions
263 lines (241 loc) · 10.3 KB
/
Copy pathpyproject.toml
File metadata and controls
263 lines (241 loc) · 10.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
[project]
name = "gw2analytics-root"
version = "0.16.0"
description = "GW2 WvW combat analytics platform (monorepo root)"
readme = "README.md"
requires-python = ">=3.12"
# v0.15.2: SPDX LicenseRef form (PEP 639 bare-string) replaces the
# previously-deprecated ``{ text = ... }`` dict form. The full legal
# text is at the root ``LICENSE`` file; this metadata field is only
# read by PyPI / uv / pip at package-publish time.
license = "LicenseRef-Proprietary"
# PEP 639 compliance: declare which file holds the full license
# text so uv / pip / PyPI know where to find it. Required for any
# non-SPDX-list ``LicenseRef-*`` expression.
license-files = ["LICENSE"]
# Pure virtual project: declares dev-tool groups only.
# Each workspace member keeps its own production deps in its own [project].
[dependency-groups]
dev = [
# v0.10.5 dep-cycle: bump to 0.15.21 (matches PR #8 + CI) so
# local auto-fix produces the AST that the bumping CI validates.
"ruff>=0.16.2,<0.17",
"mypy>=2.2.0",
"pytest>=8.3",
"pytest-asyncio>=0.24",
"pre-commit>=4.6.2",
"httpx>=0.27", # for tests
"pytest-env>=1.6.0",
"respx>=0.21",
"time-machine>=3.4.0",
"pytest-cov>=6.0",
"pip-audit>=2.7", # v0.10.26-pre followup-5: the load-test harness in tests/load/locust/
# imports `locust`; locust ships with PEP 561 type info since 2.x
# (py.typed stub at top-level), so pin a modern >= for clean mypy
# + ruff resolution. The mypy override below mirrors the fastapi_mcp
# one for the /tests/load/locust/locustfile.py HttpUser subclass.
# TODO: bump <3.0 -> <4.0 when locust 3.0 ships GA (Q1 2026 estimate).
"locust>=2.46.2,<3.0",
# v0.10.26-pre followup-6: mypy.ini declares
# ``plugins = pydantic.mypy`` but uv only resolves the ROOT
# [dependency-groups].dev for ``uv run mypy`` invocations --
# workspace members' production deps (pydantic lives in
# apps/api + libs/gw2_core + libs/gw2_skills pyprojects) are
# NOT visible at the root level, so any root-level tool
# (mypy plugin, ruff plugin, CLI) needs its own pin here.
# Workspace members all require ``>=2.9``; pin to match + cap
# <3.0 until workspace members migrate to pydantic 3.x. # TODO: bump <3 -> <4 when pydantic 3.0 ships GA + workspace
# members require it (Q1 2026 estimate).
"pydantic>=2.9,<3",
# v0.10.26-pre followup-7: mypy . cascading-error closure (root-cause).
# uv only resolves ROOT [dependency-groups].dev for ``uv run mypy .``
# invocations; workspace-member production deps below are NOT visible
# at the root level, which is the same gap closed in followup-6 for
# pydantic: 143 import-not-found + 27 untyped-decorator + 12 no-any-
# return + 6 unused-ignore + 3 misc + 1 import-untyped errors cascade
# when mypy strict runs against apps/api/src (slight breakdown-vs-total
# variance is fine -- some errors carry multiple rule tags per stderr
# line).
# Pinned here so the root venv matches what the workspace members
# themselves require (mirrors the locust + pydantic followups-5+6
# pattern). Each upper bound caps the next major so a future bump
# breaks loud. Workspace references (gw2_core etc.) are resolved
# via [tool.uv.sources] workspace = true, not as dev-deps.
# TODO: bump each <N -> <N+1 once a workspace member migrates that far.
"fastapi-mcp>=0.4,<1",
"fastapi>=0.140.13,<1",
"uvicorn[standard]>=0.52.1,<1",
"pydantic-settings>=2.15.0,<3",
"sqlalchemy>=2.0,<3",
"psycopg[binary]>=3.3.4,<4",
"alembic>=1.19.1,<2",
"minio>=7.2,<8",
"python-multipart>=0.0.20,<1",
"cryptography>=50,<51", # v0.10.26 CI: pip-audit → crypt>=48; v0.15.x: CVE-2026-69247/8/9 → >=50
"arq>=0.25,<1",
"redis>=8.1.0,<9",
"prometheus_client>=0.26.0,<1",
]
[tool.uv.workspace]
members = [
"libs/gw2_core",
"libs/gw2_evtc_parser",
"libs/gw2_analytics",
"libs/gw2_api_client",
"apps/api",
]
[tool.uv.sources]
gw2_core = { workspace = true }
gw2_evtc_parser = { workspace = true }
gw2_analytics = { workspace = true }
gw2_api_client = { workspace = true }
[tool.uv]
package = false
# ---------------------------------------------------------------------------
# Ruff: project-wide lint config (applies to every workspace member).
# v0.10.5 dep-cycle consolidates the previous dual-config:
# - ruff.toml (strict, took precedence on local runs)
# - [tool.ruff] in pyproject.toml (lower-precedence; ignored on
# local runs when ruff.toml existed)
# The dual config produced 200+ stale errors after a dependabot
# chain started bumping ruff over multiple patch versions. This
# consolidated block is the SINGLE SOURCE OF TRUTH; ruff.toml has
# been removed (git archeology preserves it).
# ---------------------------------------------------------------------------
[tool.ruff]
line-length = 100
target-version = "py312"
extend-exclude = [
".venv",
"node_modules",
"web",
"build",
"dist",
"apps/api/alembic/versions", # autogenerated — ignore ALL (see below)
# Config files are not Python source: ruff should not produce E999
# syntax errors if someone runs ``ruff check <ini-file>``
# explicitly. The ``*.ini`` glob keeps the build clean.
"*.ini",
".pre-commit-config.yaml",
# BMAD framework files (third-party) - exclude from linting
".agents",
"_bmad",
]
[tool.ruff.lint]
select = [
"E", # pycodestyle errors
"W", # pycodestyle warnings
"F", # pyflakes
"I", # isort
"N", # pep8-naming
"UP", # pyupgrade
"B", # bugbear
"C4", # comprehensions
"SIM", # simplify
"PIE", # flake8-pie
"RET", # return-style
"RUF", # ruff-specific
"PL", # pylint (curated)
"TID", # tidy imports
"ARG", # unused-arguments
"PTH", # pathlib
"G", # logging
"S", # bandit (security)
"ASYNC",
]
ignore = [
"COM812", # conflict with formatter
"ISC001", # conflict with formatter
"PLR0913", # too many args (Pydantic models need many)
"PLR0917", # too many positional args (companion to PLR0913; ruff 0.16+)
"PLR2004", # magic values (tests / fixtures use them)
"D", # pydocstyle — handled by griffe / mkdocs later
]
[tool.ruff.lint.per-file-ignores]
# Tests: relaxed on naming + bandit (random data, asserts, unused
# fixture args, etc.). Union of the previous two configs' carve-outs.
"**/tests/**/*.py" = ["S101", "S105", "S311", "N801", "ARG", "ANN", "SLF001", "PLC0415"]
# Alembic migrations are autogenerated — ignore everything there.
"apps/api/alembic/versions/**" = ["ALL"]
# Empty __init__.py re-exports are fine.
"**/__init__.py" = ["F401"]
[tool.ruff.lint.isort]
known-first-party = [
"gw2analytics_api",
"gw2_core",
"gw2_evtc_parser",
"gw2_analytics",
"gw2_api_client",
]
[tool.ruff.format]
quote-style = "double"
indent-style = "space"
# ---------------------------------------------------------------------------
# pytest-env: inject docker-compose dev credentials into os.environ at
# session startup, so the test suite never depends on a hand-rolled
# .env file. Values mirror the credentials declared in docker-compose.yml
# for the postgres + minio services.
# ---------------------------------------------------------------------------
[tool.pytest_env]
DATABASE_URL = "postgresql+psycopg://gw2analytics:gw2analytics@localhost:5432/gw2analytics"
S3_ENDPOINT = "localhost:9000"
S3_ACCESS_KEY = "gw2analytics"
S3_SECRET_KEY = "gw2analytics-secret"
S3_BUCKET = "gw2analytics"
# v0.10.0 plan 031: webhook secret at rest encryption (Fernet envelope).
# 44 chars = 32 random bytes base64-url encoded (Fernet key format).
# Deterministic test KEK = base64.urlsafe_b64encode(b"a"*32); re-runs
# always decrypt the same plaintext regardless of Fernet timestamp.
# NEVER use this KEK in production; generate via
# `python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())"`.
SECRETS_KEK = "YWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWE="
# ---------------------------------------------------------------------------
# Mypy: project-wide type-check config. Per-package overrides live in
# each member's pyproject.toml.
#
# v0.10.5 dep-cycle note: ``mypy.ini`` is the SINGLE SOURCE OF TRUTH
# for mypy config (mypy's precedence picks mypy.ini over this
# ``[tool.mypy]`` block when both exist, which would make any
# exclusion added here silently dead config). The conftest.py
# carve-out lives in mypy.ini only — do NOT duplicate it here.
# ---------------------------------------------------------------------------
[tool.mypy]
python_version = "3.12"
strict_optional = true
warn_unused_ignores = true
warn_redundant_casts = true
no_implicit_optional = true
check_untyped_defs = true
# fastapi_mcp is imported in main.py and used at runtime
# (FastApiMCP(app).mount()) but ships no type stubs. Silence the
# import-untyped warning so the project's own mypy run stays clean.
# The pre-commit mypy hook reads this same pyproject.toml.
[[tool.mypy.overrides]]
module = ["fastapi_mcp"]
ignore_missing_imports = true
[[tool.mypy.overrides]]
module = ["slowapi", "slowapi.util", "slowapi.middleware"]
ignore_missing_imports = true
# v0.10.26-pre followup-5: the Locust load-test harness subclasses
# ``HttpUser`` and decorates ``@task`` methods. Locust since 2.x ships
# its own .pyi stubs but the @task decorator's return-type annotation
# is still ``Any`` in the upstream stubs, which trips
# ``untyped-decorator`` on the browse_fights hook. Pinning ``locust``
# to ``>=2.40,<3.0`` in [dependency-groups].dev + this override
# closes the 3 errors in tests/load/locust/locustfile.py without
# needing a real .pyi (locust's stub provides everything else).
[[tool.mypy.overrides]]
module = ["locust"]
ignore_missing_imports = true
# ---------------------------------------------------------------------------
# pytest: strict asyncio mode. Async tests need an explicit
# ``@pytest.mark.asyncio`` decorator (keeps the existing fast / sync
# test suites from being silently rewired onto the asyncio loop).
# ---------------------------------------------------------------------------
[tool.pytest.ini_options]
asyncio_mode = "strict"
# Coverage gate: enforce a 90% line-coverage floor across the
# Python source surface. The flags are applied to every pytest
# invocation (local + CI) so regressions are caught early.
addopts = "--cov=apps/api/src --cov=libs --cov-report=term-missing --cov-fail-under=90"
filterwarnings = []