pyproject.toml
Rama main · réplica de lectura del repositorio original.
[tool.uv]
no-build-package = ["growwapi"]
constraint-dependencies = ["plotly==6.9.0"]
# certifi stays on the newest release growwapi 1.5.0 accepts (certifi<2026).
# A uv override to the patched 2026 bundle does not apply to
# `pip install --require-hashes -r requirements.lock`. The resulting advisory
# is time-limited in supply-chain/pip-audit-allowlist.yml.
# Kotak v3 runtime and release baseline both follow the pinned v3.0.9
# commit in brokers.lock. Freshness compares that release, not default-branch HEAD.
build-constraint-dependencies = [
"packaging==26.3",
"setuptools==84.1.0",
"wheel==0.48.0",
]
no-build-isolation-package = ["kotakneoapi", "upstox-python-sdk"]
[tool.uv.workspace]
members = [
"packages/core/core",
"packages/core/data",
"packages/core/historical",
"packages/core/indicators",
"packages/core/ticks",
"packages/services/engine",
"packages/services/screener",
"packages/services/backtest",
"packages/services/ai",
"packages/services/ditto",
"packages/services/automation",
"packages/services/journal",
"packages/integrations/gateway",
"packages/integrations/webhooks",
]
# Dev/test toolchain — installed by `uv sync` (the default `dev` group) so the
# test runner is reproducible from the lockfile rather than ambient on PATH
# (audit BT-05). CI parallelism + randomisation plugins are picked up via their
# entry points when present (see [tool.pytest.ini_options] note below).
[dependency-groups]
dev = [
"pytest==9.2.0",
"pytest-asyncio==1.4.1",
"pytest-xdist==3.8.0",
"pytest-randomly==5.0.0",
"pytest-timeout==2.4.0", # CI runs `pytest --timeout=60 --timeout-method=thread`
# (test.yml / nightly); the plugin must be in the synced env
"pyfakefs==6.2.0", # baselines §4.4 disk-full migration test (HI24)
"ruff==0.17.3", # lint gate — CI runs `uv run ruff check` (test.yml), so it
# must be in the synced env, not merely on PATH
]
[tool.uv.sources]
upstox-python-sdk = { git = "https://github.com/upstox/upstox-python.git", rev = "d88a12ef5738a4c8d71f3b88eb7b79a359277c9d" }
kotakneoapi = { git = "https://github.com/Kotak-Neo/kotak-neo-python.git", rev = "0ba71b7f7c295652d4c3ea2b5ce5a88849db18a3" }
flinttrade-ai = { workspace = true }
flinttrade-automation = { workspace = true }
flinttrade-backtest = { workspace = true }
flinttrade-core = { workspace = true }
flinttrade-data = { workspace = true }
flinttrade-ditto = { workspace = true }
flinttrade-engine = { workspace = true }
flinttrade-gateway = { workspace = true }
flinttrade-historical = { workspace = true }
flinttrade-indicators = { workspace = true }
flinttrade-journal = { workspace = true }
flinttrade-screener = { workspace = true }
flinttrade-ticks = { workspace = true }
flinttrade-webhooks = { workspace = true }
# ----------------------------------------------------------------------------
# Pytest configuration
# ----------------------------------------------------------------------------
# Three custom markers are registered for the staged-test-execution model
# (unit on every commit, integration on PR, slow nightly). --strict-markers
# fails CI if any test uses an unregistered marker -- catches typos before
# they go unnoticed.
#
# Existing tests are NOT yet marked. Default behaviour is unchanged: an
# untagged test simply runs in every job. As tests get touched, prefer to
# tag them; CI selection clauses can then be tightened over time.
#
# pytest-xdist + pytest-randomly are picked up automatically when installed
# (they add their own opts via entry points). Local devs can run without
# them; CI installs them and uses -n auto for parallelism.
# ----------------------------------------------------------------------------
[tool.pytest.ini_options]
testpaths = ["tests"]
# Keep package-local tests.* namespaces distinct from shared tests.mocks,
# including when a focused or sharded run does not collect the root tests.
consider_namespace_packages = true
asyncio_mode = "auto"
addopts = [
"--import-mode=importlib",
"--strict-markers",
"-ra",
]
markers = [
"unit: fast, isolated unit test (default; runs on every commit)",
"integration: touches DB or mocked external services (PR gate)",
"slow: long-running test >1s (nightly or via --run-slow)",
]
filterwarnings = [
# huggingface_hub still calls its own deprecated `hf_xet.download_files()`
# internally — out of our control, will go away on upstream release.
"ignore:hf_xet.download_files.*:DeprecationWarning:huggingface_hub.*",
]
[tool.ruff]
# `target-version` is the OLDEST interpreter the code must still run on, not the
# one we develop against. flint.toml [requirements] sets python_requires
# ">=3.14" (python_target "3.14"), so py314 matches the floor. Bump this only
# when the flint.toml floor moves.
target-version = "py314"
line-length = 120
[tool.ruff.lint]
# Baseline: ruff 0.16 widened its default rule set, so the pre-0.16 defaults are
# pinned explicitly. A future ruff release that widens its defaults again cannot
# silently change this gate.
select = ["E4", "E7", "E9", "F"]
# Modern families adopted 2026-07-30 and measured on ruff 0.16.0 (the version the
# dev group resolves to). Listed one family per line, explicitly,
# rather than dropping `select` and inheriting whatever a future ruff decides is
# a default — the gate is intentional and dated, not implicit. Every family here
# is GREEN across packages/*/*/src/; anything that could not reach zero is
# either pinned in `ignore` below with its count, or listed in the deferred
# adoption list at the bottom of this section.
extend-select = [
"I", # isort — deterministic import blocks
"UP", # pyupgrade — no outdated ways of writing modern Python
"B", # flake8-bugbear — real bug classes, not style
"C4", # flake8-comprehensions
"RET", # flake8-return
"RSE", # flake8-raise
"PIE", # flake8-pie — misc anti-patterns
"PERF", # perflint
"FURB", # refurb — modernisation beyond pyupgrade
"ASYNC", # flake8-async — blocking calls inside async def
"LOG", # flake8-logging — logger misuse
"G", # flake8-logging-format
"ISC", # flake8-implicit-str-concat — catches the missing-comma bug
"Q", # flake8-quotes
"TID", # flake8-tidy-imports — no `from ..parent` across package layers
"SLOT", # flake8-slots
"INT", # flake8-gettext
"EXE", # flake8-executable — shebang/permission mismatches
"FA", # flake8-future-annotations
"NPY", # NumPy-specific rules
"YTT", # flake8-2020 — sys.version misuse
]
ignore = [
# ---- Rules inside adopted families that are NOT yet green (2026-07-30) ----
# Each line is a live debt marker: the count is the violation count at
# adoption time. Fixing one and deleting its line is always an improvement.
"UP035", # 59 — `typing.Callable` → `collections.abc`. ruff's fix is UNSAFE
# (it can break re-exports), so it is not auto-appliable.
"UP040", # 7 — PEP 695 `type X = ...` aliases; needs per-alias review.
"UP042", # 7 — `str, Enum` → `StrEnum`; changes `__str__`/format output,
# which some serialised payloads depend on. Needs review.
"UP047", # 2 — PEP 695 generic functions.
"B904", # 18 — `raise ... from err` inside except.
"B905", # 63 — `zip(strict=...)`. Turning strictness on can raise where
# today it truncates, so it is a behaviour decision per site.
"B007", # 5 — unused loop control variable.
"B027", # 3 — empty method in ABC without @abstractmethod.
"RET504", # 17 — unnecessary assignment before return (ruff fix is unsafe;
# the named temporaries are often deliberate for debugging).
"PERF401",# 43 — manual list comprehension.
"PERF403",# 3 — manual dict comprehension.
"ASYNC109",# 9 — own `timeout` parameter instead of `asyncio.timeout`; these
# are public signatures, so changing them is an API break.
"NPY002", # 2 — legacy `np.random`; the new Generator API draws a different
# stream, so switching changes reproducible test fixtures.
# PERF101 is NOT deferred debt — it is banned. Its fix is classified "safe"
# but is not: applying it to the single site it flagged in this repo
# (IndMoneyAdapter._symbol_keys, which iterates a set while adding aliases to
# it) deleted a load-bearing `list()` cast and turned three passing
# test_indmoney_adapter tests into `RuntimeError: Set changed size during
# iteration`. `list(x)` in a `for` header is the standard guard for
# mutate-while-iterating, and ruff cannot tell that from a redundant copy —
# so leaving the rule on means the next `ruff check --fix` silently
# reintroduces the same bug. Verified 2026-07-30.
"PERF101",
]
# ---- Deferred adoption list (measured 2026-07-30, ruff 0.16.0) ----------------
# Marginal cost of adding each family ON TOP of the set adopted above, measured
# over packages/*/*/src/ with `ruff check --extend-select <family> --statistics`.
# NOT enabled, because enabling them would put the only Python quality gate
# permanently red — and a red gate gets ignored, then stops gating anything.
# Work a family down, then move it into `extend-select` above and delete its
# line here.
#
# D 4205 (D413 2597, D102 767, D107 397, D401 110, D105 85, D205 44, …)
# TRY 3622 (TRY003 2990, TRY400 223, TRY301 218, TRY004 100, TRY300 70, …)
# EM 3010 (EM101 2126, EM102 884 — exception message as a bare literal)
# PL 1883 (PLR2004 538, PLR0913 342, PLC0415 261, PLR0917 209, PLR0912 171, …)
# ANN 1626 (ANN401 1572 — `Any` in signatures)
# RUF 1583 (RUF100 1240 — see the noqa note below; RUF002 217, RUF003 44, …)
# COM 1538 (COM812 missing-trailing-comma — formatter territory)
# BLE 456 (BLE001 blind-except)
# TC 394 (TC002 308, TC003 51, TC001 32 — needs runtime-import review)
# C90 333 (C901 complex-structure)
# FBT 254 (FBT003 106, FBT001 92, FBT002 56 — public API signatures)
# S 204 (S101 61, S110 49, S608 46, S311 23, S105 7, …)
# SIM 173 (SIM105 77, SIM108 33, SIM102 26, SIM114 14, SIM117 11, …)
# ARG 155 (ARG002 137, ARG001 18)
# SLF 119 (private-member-access — mostly deliberate, intra-package)
# N 104 (N806 36, N818 34, N803 26 — many mirror broker API field names)
# PTH 74 (os.path → pathlib; every fix is manual, no safe autofix exists)
# ERA 74 (commented-out code)
# DTZ 62 (naive datetimes; IST market-clock semantics must be preserved,
# so a blanket `tz=` insert is a correctness risk, not a cleanup)
# PYI 55 (PYI034 27, PYI036 22 — a stub-file family firing on .py)
# T20 35 (print in library code)
# PD 6, ICN 4, A 0, INP 0, DJ 0
#
# NOTE on RUF100 (1240): packages/*/*/src/ already carries 1376 `# noqa`
# directives, and 1240 of them name a rule that is NOT selected — 740 for
# PLC0415 and 390 for BLE001 alone. Those suppressions were written by authors
# who expected those families to be on. They are inert today: enabling PL/BLE
# would make 1130 existing noqa comments meaningful rather than adding 1130 new
# violations, which makes those two families much cheaper to adopt than their
# headline numbers suggest.
#
# The two test trees are excluded from every count above; their debt is
# enumerated in [tool.ruff.lint.per-file-ignores] below.
[tool.ruff.lint.per-file-ignores]
# The lint path was widened on 2026-07-30 to cover the test trees, which had
# never been linted at all under the CI invocation. The test trees are now gated
# on the E4/E7/E9/F baseline (the F821/F401 import-NameError class), but the
# newly adopted modern families still have measured debt there:
# tests/ 46 violations (41 safely autofixable)
# packages/*/*/tests/ 964 violations (844 safely autofixable, 524 of them
# I001 import sorting alone)
# Every rule below is listed individually rather than by family prefix, so a
# test-owning change can clear one and delete its line. `ruff check --fix` over
# the test trees clears roughly 88% of this in one pass.
"tests/**/*.py" = [
"ASYNC110", "B006", "B009", "B011", "B017", "B018", "C408", "C420",
"FURB110", "FURB157", "FURB167", "FURB171", "I001", "ISC004", "LOG001",
"PERF102", "PIE807", "PIE808", "PIE810", "Q000", "Q003", "RET501",
"RET503", "RSE102", "SLOT000", "UP012", "UP017", "UP022", "UP037", "UP041",
]
"packages/*/*/tests/**/*.py" = [
"ASYNC110", "B006", "B009", "B011", "B017", "B018", "C408", "C420",
"FURB110", "FURB157", "FURB167", "FURB171", "I001", "ISC004", "LOG001",
"PERF102", "PIE807", "PIE808", "PIE810", "Q000", "Q003", "RET501",
"RET503", "RSE102", "SLOT000", "UP012", "UP017", "UP022", "UP037", "UP041",
]
# The script test tree matches neither glob above, so widening the lint path
# picked it up for the first time. Its only debt is three expected-message
# assertions that each build ONE long string across several lines inside a
# single-element list. That is the exact shape ISC004 exists to flag, because a
# missing comma looks identical - but here the concatenation is deliberate, and
# splitting the assertions to prove it would cost more clarity than it buys.
"scripts/__tests__/**/*.py" = ["ISC004"] # 3 — deliberate multi-line expected messages
# Two modules deliberately import BELOW module-level `sys.path` manipulation, so
# their whole import section is one long `# noqa: E402` block hand-organised into
# commented sections. isort cannot express that: its fix reflows the block, which
# strands every section comment against the wrong group and drops the per-line
# `# noqa: E402` from any statement it has to split — turning one clean file into
# eleven new E402 violations. Correctness is unaffected either way, so the
# hand-ordering wins and I001 is off for these two files only.
"packages/core/core/src/flinttrade_core/app.py" = ["I001"]
"packages/services/backtest/src/flinttrade_backtest/strategies/__init__.py" = ["I001"]
# The PyO3 crate's Python shim is outside every `src/` tree, so widening the
# lint path to `packages/` picked it up for the first time. One outstanding
# violation, kept visible rather than silently excluded from the path:
"packages/core/ticks/python/tick_engine/__init__.py" = [
"I001", # 1 — a two-name reorder inside the single `from tick_engine.tick_engine
# import (...)` block. `ruff check --fix` clears it; it belongs to
# a ticks-owning change, not to the lint-config change.
]
[tool.ruff.lint.isort]
# The repo writes `from x import a, b as c` on one line. isort's DEFAULT is to
# force every aliased member onto a statement of its own, which turns one line
# into three and churns files for no readability gain. Keep the existing style.
combine-as-imports = true
# Pin first-party classification to the workspace distributions so the same
# import sorts identically no matter which package's src/ root ruff resolves.
known-first-party = [
"flinttrade_ai",
"flinttrade_automation",
"flinttrade_backtest",
"flinttrade_core",
"flinttrade_data",
"flinttrade_ditto",
"flinttrade_engine",
"flinttrade_gateway",
"flinttrade_historical",
"flinttrade_indicators",
"flinttrade_journal",
"flinttrade_screener",
"flinttrade_ticks",
"flinttrade_webhooks",
]