Repository navigation
Expand file tree
/
Copy pathpyproject.toml
More file actions
244 lines (228 loc) · 13.4 KB
/
Copy pathpyproject.toml
File metadata and controls
244 lines (228 loc) · 13.4 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
[project]
name = "keel-trader"
version = "0.21.0"
description = "Offline-first, broker-agnostic, rule-based spot-trading agent (halal policy by default)"
license = "Apache-2.0"
readme = "README.md"
authors = [
{ name = "CodeGate Software" }
]
requires-python = ">=3.14"
dependencies = [
"click>=8.4.2",
# pyyaml/python-dotenv are deliberately NOT listed here: nothing under `keel/` imports
# them any more (config parsing and secret loading both moved into keel-core), so they
# arrive transitively via the `keel-core` dependency below. Re-add them here only if
# `keel/` starts importing them directly again.
# ⚠️ The workspace siblings are pinned `==` to this exact version, and every workspace
# pyproject does the same. Unpinned, an ALREADY-INSTALLED older sibling satisfies the
# requirement, so installing the new `keel_trader` wheel upgrades nothing else and leaves a
# deployment running new code against old libraries -- `~/keel` ran `keel-trader 0.5.7`
# against `keel-core 0.5.5` for two releases exactly that way. These are not third-party
# dependencies with independent release cycles: they are cut from this repo, at this version,
# in one build, so `==` states a fact rather than guessing at compatibility. The cost is that
# a version bump must move these numbers too -- the bump already edits every package's
# `version`, and `tests/test_packaging.py` fails the build if a pin is left behind.
"keel-core==0.21.0",
"keel-broker-api==0.21.0",
# The default venue resolves through the keel.brokers entry points (#524), so the adapter
# is a hard runtime dependency, not a convenience: without it installed, every config --
# including one with no `broker:` section -- fails at broker construction. The CLI also
# imports the SDK directly to build the adapter's RESTClient transport
# (`keel/commands/_common.py`); moving that construction into the adapter package is
# Phase B's, with cb_client.py's deletion.
"keel-broker-coinbase==0.21.0",
]
[project.scripts]
keel = "keel.cli:main"
[build-system]
requires = ["uv_build>=0.10.4,<0.14.0"]
build-backend = "uv_build"
[tool.uv.build-backend]
module-root = ""
# The DISTRIBUTION is `keel-trader` (the name `keel` is taken on PyPI by an unrelated project),
# but the import package and the CLI command stay `keel`. Without this the backend would infer
# `keel_trader/` from the distribution name.
module-name = "keel"
# Ship the default config template inside the wheel so `keel init-config` can write it out on a
# fresh install, and (#535) the web UI's static assets so `keel serve` has something to serve
# from an installed wheel rather than a checkout.
#
# ⚠️ Measured against the pinned uv_build (`uv_build>=0.10.4,<0.14.0`, resolving to 0.11.31 as
# of #535): this key has NO observable effect. `artifacts` does not appear in uv_build's
# documented `[tool.uv.build-backend]` schema (`module-root`, `module-name`, `namespace`, `data`,
# `source-include`, `source-exclude`, `wheel-exclude`, `default-excludes`), and removing this
# line entirely, or replacing its value with garbage, still ships both the templates and
# `keel/web/static/**` in a rebuilt wheel with no warning either way -- uv_build's actual default
# is "the whole module tree except `default-excludes`" (`__pycache__`, `*.pyc`, `*.pyo`), not
# "importable source only". `tests/test_packaging.py::test_static_assets_survive_being_built_
# into_a_wheel` therefore checks the built wheel directly rather than trusting this line, and
# would catch it if a future uv_build (or a `wheel-exclude` added here later) narrowed that
# default. Left in place as a statement of intent and because it is the field this project's
# prior art used for the same purpose -- but it is not, today, load-bearing.
artifacts = ["keel/templates/*.yaml", "keel/web/static/**"]
[tool.uv.workspace]
members = ["packages/*"]
[tool.uv.sources]
keel-core = { workspace = true }
keel-broker-api = { workspace = true }
keel-broker-coinbase = { workspace = true }
keel-broker-alpaca = { workspace = true }
keel-broker-fake = { workspace = true }
keel-broker-kraken = { workspace = true }
keel-broker-robinhood = { workspace = true }
[dependency-groups]
dev = [
"mypy>=1.18.0",
"pytest>=9.1.1",
# Dev-only, like every entry below: optuna powers the RESEARCH-side parameter study
# (keel/research/tuning.py, issue #476) and is imported lazily inside run_study only,
# so the shipped wheel and every runtime path stay clean of it. It rides the dev group
# (uv sync's default) rather than its own group so the study's determinism tests
# actually run in CI; uninstalling it must never break `import keel.research.tuning`.
"optuna>=4.5.0",
# tests/test_security_scans.py parses .github/dependabot.yml directly, so the test
# suite imports pyyaml itself rather than riding keel-core's transitive copy (the
# rule the [project] comment above states for `keel/`, applied where it now applies).
"pyyaml>=6.0.2",
# Only `code-quality.yml` needs this: SonarQube reports coverage as a first-class metric and
# has nothing to show without a coverage XML to import. It is deliberately NOT wired into the
# default `pytest` invocation -- `ci.yml` and `release.yml` still run a plain `pytest -q`,
# because measuring coverage costs wall-clock time on every run to produce a number those two
# workflows do not read. The coverage run is opt-in via `--cov`, and only the Sonar job opts in.
"pytest-cov>=7.0.0",
"ruff>=0.15.21",
# Dev-only on purpose: the fake venue exists to exert design pressure on the port and to
# prove two-plugin discovery. A production engine must never have it installed.
"keel-broker-fake",
# Dev-only -- a DELIBERATE post-#524 choice, not a leftover of the pre-port world. The
# broker-port migration is finished, so a deployment that installs this package and selects
# `broker.name: robinhood` gets all the way to `_build_broker`, which refuses the name
# EXPLICITLY: the CLI has no credential wiring for its Ed25519 keys, and constructing it
# anyway would hand the engine a broker that cannot reach its venue. Runtime availability
# would therefore change nothing for an install until that wiring exists, while putting an
# Ed25519 stack (pynacl) into every install -- so it stays a dependency choice, made here,
# and flipping it is a real decision that also wants #233's capability-based venue
# visibility (an install must not silently widen what a deployment can select). Until
# then: the dev entry runs the conformance suite against it in CI, and users who want the
# venue install `keel-broker-robinhood` themselves -- entry-point discovery picks it up.
"keel-broker-robinhood",
# Dev-only for the same reason as Robinhood: Alpaca is an optional venue (the Phase 12
# equities milestone). It rides the dev group so the conformance suite runs against it
# in CI; a deployment that wants equities installs `keel-broker-alpaca` itself and
# entry-point discovery picks it up. Nothing in `keel/` imports it.
"keel-broker-alpaca",
# Dev-only like the venues above, and doubly so: Kraken is an optional venue whose
# adapter is a STUB (#313) -- port-complete, but every data/market method raises
# NotImplementedError, so it can never be selected by a deployment that expects data.
# It rides the dev group so the registry (`keel brokers list`) and the stub's own tests
# run in CI; there is no conformance suite for it yet because there is nothing to conform.
"keel-broker-kraken",
]
# Ruff config lives in ruff.toml at the repo root. A ruff.toml takes precedence over
# [tool.ruff] here, so keeping a copy in both places would leave silently-dead settings.
[tool.mypy]
python_version = "3.14"
files = ["packages", "keel", "tests"]
# Implied by `--strict`, but mypy rejects it in a per-module section ("Per-module sections
# should only specify per-module flags"), so it is stated here rather than in the broker block
# below. It is already mypy's own default, so this line changes no behaviour today -- it is
# here so the setting stays explicit if that default ever flips.
warn_redundant_casts = true
# New packages are strict from birth.
#
# ⚠️ These are the flags `--strict` bundles, listed one by one INSTEAD of `strict = true`,
# because `strict` is not actually a per-module setting: mypy applies it GLOBALLY no matter
# which `module` pattern the section carries (a section naming a module that does not exist
# turns it on for the whole run just the same). Written as `strict = true` here, this section
# silently type-checked `keel.*`, `keel_core.*` and `tests.*` under full strict mode too --
# invisible only because all three carry `ignore_errors` below and so never printed what it
# found. The bug surfaces the moment a module is ungated: dropping `keel.*` from the exempt
# list turned up 131 errors where default mode finds 45. Keep this list expanded; re-collapsing
# it to `strict = true` re-breaks the scoping. If a future mypy adds a strict-implied flag,
# add it here as well -- `tests/test_packaging.py::test_broker_strict_flags_match_mypy_strict`
# fails the build when this list and mypy's own `--strict` bundle drift apart.
[[tool.mypy.overrides]]
module = [
# `keel.web.*` is not a new package, and it is here for the same reason the new packages are:
# it was ALREADY clean under these flags. Measured rather than assumed -- `mypy --strict
# keel/web` reported two errors, both in one function, and both were the same defect: a
# three-argument `type()` call returning `Any` into a function annotated `-> KeelServer`,
# under a `type: ignore[return-value]` that did not name the error mypy was actually
# reporting. Fixing that left zero, so gating it costs nothing today and is what stops the
# next edit from costing something. It is also the layer where it matters most: everything
# under `keel/web/` is reachable from a socket.
"keel.web.*",
"keel_broker_api.*",
"keel_broker_alpaca.*",
"keel_broker_coinbase.*",
"keel_broker_fake.*",
"keel_broker_kraken.*",
"keel_broker_robinhood.*",
]
disallow_any_generics = true
disallow_subclassing_any = true
disallow_untyped_calls = true
disallow_untyped_defs = true
disallow_incomplete_defs = true
check_untyped_defs = true
disallow_untyped_decorators = true
# `warn_redundant_casts` belongs to the bundle too but is global-only; see `[tool.mypy]` above.
warn_unused_ignores = true
warn_return_any = true
no_implicit_reexport = true
strict_equality = true
extra_checks = true
# The suite is exempt until its modules move out (monorepo spec steps 4-7). `keel.*` came off
# this list once its default-mode errors were fixed: it is CHECKED but not yet `strict`, which
# is the next tightening step for it (and the reason it is not simply added to the strict block
# above). Tighten one package at a time, never all at once.
[[tool.mypy.overrides]]
module = ["tests.*"]
ignore_errors = true
# The Coinbase SDK ships no `py.typed`, so every module importing it is an `import-untyped`
# error the moment its importer is checked. `keel/commands/_common.py` and `keel/data/cb_client.py`
# import it directly (the broker-port migration has not removed those yet -- see the note on the
# `keel-broker-coinbase` dependency above). Scoped to the SDK, NOT to our modules: our own code
# stays checked, only the untyped third-party import is waved through, exactly as the broker
# adapter already has to do.
[[tool.mypy.overrides]]
module = "coinbase.*"
ignore_missing_imports = true
# keel-core moved in the previous plan but predates strict mode; it is tightened
# when its remaining consumers migrate.
[[tool.mypy.overrides]]
module = ["keel_core.*"]
ignore_errors = true
# Coverage is measured only when `--cov` is passed (see the pytest-cov note in [dependency-groups]).
# This section exists so that a bare `--cov` -- with no value -- measures the right thing instead of
# whatever happened to get imported: the shipped code under `keel/` and the workspace packages,
# and nothing else. `tests/` is excluded because a test file's own coverage is a tautology, and
# `scripts/` because those are one-shot operator tools the suite does not drive.
[tool.coverage.run]
source = ["keel", "packages"]
branch = true
# Emit repo-root-relative paths in coverage.xml. Without this, coverage.py writes filenames that
# are relative to each `source` root (`agent.py`, not `keel/agent.py`) and resolves them via
# absolute `<sources>` entries -- which Sonar can only match if the scanner happens to run from the
# identical absolute path. Relative paths make the report portable between the runner and any
# local run, which is the difference between Sonar showing 95% coverage and showing 0%.
relative_files = true
omit = [
# Generated by release.yml at build time and never present in a checkout.
"keel/_build_info.py",
]
[tool.coverage.report]
exclude_also = [
"if TYPE_CHECKING:",
"raise NotImplementedError",
"@overload",
]
# `ci.yml` and `release.yml` both run a plain `pytest -q` with no `-m` filter, so registering a
# marker here changes nothing about what CI runs -- it exists to give a contributor working
# offline, or iterating quickly, an opt OUT (`pytest -q -m "not slow"`) for the one test that
# shells out to a real `uv build` (tests/test_packaging.py) rather than exercising pure Python.
[tool.pytest.ini_options]
markers = [
"slow: shells out to a real subprocess (e.g. `uv build`) instead of pure Python; still runs in CI, skip locally with -m 'not slow'",
]