-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathpyproject.toml
More file actions
262 lines (239 loc) · 12.2 KB
/
Copy pathpyproject.toml
File metadata and controls
262 lines (239 loc) · 12.2 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
[project]
license = "MIT"
license-files = ["LICENSE"]
name = "cvx-quadprog"
version = "0.4.0"
description = "Goldfarb/Idnani dual quadratic programming solver in NumPy and SciPy"
readme = "README.md"
requires-python = ">=3.11"
dependencies = [
"numpy>=2.0.0",
"scipy>=1.11.0",
]
authors = [{name = "Thomas Schmelzer", email = "thomas.schmelzer@gmail.com"}]
keywords = [
"quadratic-programming",
"qp",
"qp-solver",
"convex-optimization",
"optimization",
"active-set",
"dual-method",
"goldfarb-idnani",
"quadprog",
"numpy",
"scipy",
"portfolio-optimization",
"mean-variance",
]
classifiers = [
"Development Status :: 4 - Beta",
"Intended Audience :: Financial and Insurance Industry",
"Intended Audience :: Science/Research",
"Topic :: Scientific/Engineering :: Mathematics",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Programming Language :: Python :: 3.14",
]
# `solve_qp(..., blas_threads=...)` is the only thing that needs threadpoolctl, and
# it is opt-in, so requiring it of everyone would tax the majority to serve the
# minority who call it. The probes that decide whether a process needs it are
# deliberately built on stdlib and NumPy alone: code aimed at a plain `pip install`
# cannot be gated behind an extra that a plain install does not have. See
# src/cvx/quadprog/_threads.py and issue #66.
[project.optional-dependencies]
threads = ["threadpoolctl>=3.0"]
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[tool.hatch.build.targets.wheel]
packages = ["src/cvx"]
# The sdist is an allowlist, not a filtered copy of the working tree. Hatchling's
# default is "everything git does not ignore", which put all 11 CI workflows, the
# whole .rhiza template toolchain and an 87 KB uv.lock into a distribution of a
# 775-line solver -- 100 files, of which four were the package.
#
# The test is "can a downstream packager (conda-forge, a distro, someone auditing
# a wheel they did not build) build this and convince themselves it works?" That
# needs the sources, the tests, and the pytest configuration those tests assume.
# It does not need this repository's development environment.
#
# pyproject.toml, README.md and LICENSE are added by hatchling from the metadata
# (`readme`, `license-files`) and need no entry here.
[tool.hatch.build.targets.sdist]
only-include = [
"src",
# Shipped so the artefact is verifiable off-line, not merely buildable --
# but know what that buys: 867 of the 982 tests live in test_against_c.py
# behind a module-level importorskip of the reference C `quadprog`, which is
# GPL-2.0 and deliberately not a runtime dependency. A downstream packager
# who does not install it runs 115 tests, not 982. That is still the whole of
# test_qr.py and test_specification.py -- the QR invariants, and the closed-form
# and KKT-certificate suite that on its own covers every line and branch of
# _solve.py -- so a broken solver still fails loudly.
"tests",
# pytest.ini carries the markers and the 60s timeout the suite is written
# against; without it a downstream `pytest` run silently means something else.
# Its `pythonpath = .rhiza/tests` entry is a documented no-op when absent.
"pytest.ini",
"CHANGELOG.md",
# The licensing record travels with the artefact. Someone auditing a tarball
# they did not build is exactly who needs it, and they may never see the repo.
"PROVENANCE.md",
]
# uv installs only `dev` by default. The test tooling lives in `test`, so name both
# here -- otherwise a bare `uv run pytest` finds no pytest at all.
[tool.rhiza-task]
# Settings for the task runner that replaced the synced make layer in rhiza v1.4.0. Read as
# layer 3 of rhiza-task's resolution order (defaults -> .rhiza/.env -> pyproject.toml ->
# RHIZA_* environment). Only entries that differ from the CLI's defaults belong here;
# `uvx rhiza-task print <setting>` shows what one resolves to.
# The deleted `Makefile` set `COVERAGE_FAIL_UNDER = 100`, raising python.mk's default of 90.
# The `test` task passes --cov-fail-under on the command line, which outranks any
# [tool.coverage.report] table, so without this line the gate would have dropped ten points
# with nothing in the tree saying otherwise. ([tool.interrogate]'s `fail-under = 100` below is
# the docstring gate, a different thing.)
coverage-fail-under = 100
# The retired python.mk had `TYPECHECKER ?= both`; the CLI defaults to `ty` alone, so a bare
# migration would have dropped `mypy --strict` from this repo's typecheck gate. `both` is also
# stricter here than it was under make: the shell `case` let mypy's exit status win, so a ty
# failure could pass, where the CLI raises on each run.
typechecker = "both"
# The retired quality.mk pinned `RHIZA_CHECKS_VERSION ?= 0.2.1` and installed
# `pytest-rhiza==$(RHIZA_CHECKS_VERSION)` from PyPI. The CLI's default names a git tag at
# v0.2.0, so a bare migration would have *downgraded* the conformance checks -- the drift the
# pin exists to prevent. Pinned exactly rather than floored: a gate that moves under you is not
# a gate. 0.2.2 is out; bumping to it is a separate upgrade decision.
pytest-rhiza = "pytest-rhiza==0.2.1"
# The deleted `Makefile` had `LICENSE_IGNORE_PACKAGES += quadprog`, and the CLI's default is
# empty, so the first push of this branch shipped without it and CI caught it: the `license`
# gate runs pip-licenses with --fail-on=GPL;LGPL;AGPL --partial-match, and the PyPI `quadprog`
# distribution this project wraps reports "GNU General Public License v2 or later (GPLv2+)".
# The exemption is about the *scan*, not about relicensing anything -- this repo is MIT and the
# GPL dependency is declared.
#
# `docutils` deliberately does not appear here: rhiza-task appends it itself when marimo is in
# the task registry, because docutils offers a choice of licences in one string that
# --partial-match cannot parse.
license-ignore-packages = ["quadprog"]
# Not set, on purpose:
# marimo-folder, source-folder -- `docs/notebooks` and `src` are what the deleted
# `.rhiza/.env` said, and both are already the defaults.
# mkdocs-extra-packages -- the deleted Makefile carried only
# `--with 'mkdocstrings[python]'`, now the default.
# ci-os-matrix -- unset, so rhiza_ci.yml selects the matrix per caller.
# layers -- `python` is detected from pyproject.toml.
[tool.uv]
default-groups = ["dev", "test"]
[dependency-groups]
dev = [
# Not a tool: stubs are input to the analysis, so they must be in the
# environment mypy *inspects*, not the one it runs from -- and
# `.rhiza/make.d/python.mk` supplies only mypy via `uv run --with mypy`.
# Without them `make typecheck` fails under --strict with
# 'Library stubs not installed for "scipy.linalg"'.
"scipy-stubs>=1.16",
# The reference C implementation, used only by tests/test_against_c.py to
# check this port against it. Not needed to use the package.
"quadprog>=0.1.13",
]
test = [
"pytest>=8.0",
# Property-based tests in tests/test_properties.py. In the `test` group and
# not injected via `uv run --with` because `make test` runs the whole suite,
# not just the `-m "hypothesis or property"` selection that
# `make hypothesis-test` takes -- so the import has to resolve in the plain
# environment too.
"hypothesis>=6.100",
# pytest.ini (template-owned) sets `timeout = 60`. Without this plugin that is
# a warning under `uv run pytest` and a hard "Unknown config option" failure in
# the make targets that do not inject it themselves. pytest-cov and the other
# plugins need no entry here: `make test` injects them with `uv run --with ...`.
"pytest-timeout>=2.0",
# Imported by benchmarks/ref_probe.py, which tests/test_probe.py executes. The
# probe is the script strangers run from issue #41, so it is worth a smoke test;
# that test cannot import it without this. Not needed to use the package.
"threadpoolctl>=3.0",
]
# No [tool.pytest.ini_options] here. pytest.ini is template-owned, already sets
# `testpaths = tests`, and takes precedence over this file whenever both exist --
# so a block here is not a second opinion, it is one pytest reports ignoring on
# every run. pytest.ini ships in the sdist (see [tool.hatch.build.targets.sdist]
# above), so downstream runs get that setting from the file that actually wins.
# threadpoolctl ships no py.typed marker, so under --strict its import in
# _threads.py is an error rather than an `Any`. Declared here rather than with a
# per-line `# type: ignore[import-untyped]`, because the code mypy emits depends on
# whether the module is installed at all -- it is optional -- and --strict also
# fails on an ignore that turned out to be unnecessary.
[[tool.mypy.overrides]]
module = ["threadpoolctl"]
ignore_missing_imports = true
[tool.coverage.run]
branch = true
# mutmut 3 takes its scope from config rather than CLI flags -- `mutmut run` no
# longer accepts --paths-to-mutate or --tests-dir. `source_paths` is the current
# name; `paths_to_mutate` still works but warns.
#
# do_not_mutate excludes the differential sweep against the GPL C reference:
# those tests are skipped entirely when `quadprog` is absent, so mutating them
# measures the oracle rather than this package.
[tool.mutmut]
source_paths = ["src/cvx/quadprog"]
do_not_mutate = ["tests/*"]
[tool.interrogate]
fail-under = 100
ignore-init-method = false
ignore-module = false
# The rhiza test-layout check expects tests/<pkg>/test_<module>.py mirroring each
# source module, and a TestFoo for each class Foo. This suite is organised by
# behaviour instead -- test_specification.py (what a solution must satisfy),
# test_structure.py (constraint-shape detection), test_qr.py (the factorisation
# updates in isolation), test_against_c.py (differential comparison against the
# GPL reference) -- because the package is three modules and one algorithm, and
# splitting the specification across test__solve.py and test__qr.py would file
# each assertion by which function happens to implement it rather than by what it
# proves.
#
# Mirroring exists to guarantee that no module goes untested. That guarantee is
# provided here by the coverage gate instead, which the root Makefile sets to 100%
# via COVERAGE_FAIL_UNDER, and which the suite meets at 100% of statements *and*
# branches across all ten modules -- a stronger check than the presence of a file
# with a matching name.
#
# No percent sign in `reason`: radon reads every table in this file through
# configparser, which treats a literal "%" as interpolation syntax and raises
# ValueError before it can analyse anything.
[tool.check_test_layout]
enforce = false
reason = "Tests are organised by behaviour (specification, structure, QR updates, differential oracle against the GPL C reference) rather than mirroring module names. Per-module reach is guaranteed by the full statement-and-branch coverage gate instead."
[project.urls]
Homepage = "https://github.com/Jebel-Quant/quadprog"
Repository = "https://github.com/Jebel-Quant/quadprog"
[tool.bumpversion]
current_version = "0.4.0"
tag = false
commit = false
allow_dirty = false
[[tool.bumpversion.files]]
filename = "pyproject.toml"
regex = true
search = '(?ms)^\[project\]((?:(?!^\[)[\s\S])*?)^version = "{current_version}"'
replace = '[project]\1version = "{new_version}"'
# uv.lock records this project's own version too. Without this entry the lock
# disagrees with pyproject.toml after a bump and every `uv sync --locked` job
# fails. Anchored to the cvx-quadprog package block so no dependency that
# happens to share the version number is rewritten.
[[tool.bumpversion.files]]
filename = "uv.lock"
regex = true
search = '(?m)^name = "cvx-quadprog"\nversion = "{current_version}"'
replace = 'name = "cvx-quadprog"\nversion = "{new_version}"'
# CITATION.cff states the version a citation refers to, so it has to move with a
# release or it silently credits the wrong one. Line-anchored: the only top-level
# `version:` key is the software's own, the reference entry's keys are indented.
[[tool.bumpversion.files]]
filename = "CITATION.cff"
regex = true
search = '(?m)^version: {current_version}$'
replace = 'version: {new_version}'