-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathpyproject.toml
More file actions
262 lines (253 loc) · 13.4 KB
/
Copy pathpyproject.toml
File metadata and controls
262 lines (253 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
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
[project]
name = "zunzun-ng"
version = "1.0.0"
description = "ZunZunNG — Django site for 2D/3D curve and surface fitting via pyeq3. Fork of zunzunsite3 (bitbucket.org/zunzuncode), modernized for Python 3.14 / Django 6.0 and cross-platform deployment."
readme = "README.md"
license = { file = "LICENSE.txt" }
requires-python = ">=3.14"
dependencies = [
# Django 6.0 is a short-term support release, end-of-life around
# December 2026. Next LTS is 6.2, expected April 2027. The site's
# code uses only long-stable APIs (re_path, render, TEMPLATES
# settings, default JSONSerializer).
"django>=6.0,<6.1",
# django-ratelimit 4.x decorator signature: @ratelimit(key='ip',
# rate='12/m', block=False). 3.x used a different API that would
# silently accept but not honor these kwargs.
"django-ratelimit>=4.0",
"pyeq3",
# Floor >=2.0 keeps the project on the modern numpy line that the rest
# of the scientific stack (scipy, matplotlib) is built against. Ceiling
# <3 guards against the next major; numpy has historically broken APIs
# at major versions (the 1.x -> 2.x transition broke the entire
# scientific-Python ecosystem for several months).
"numpy>=2.0,<3",
# Ceiling <2 guards against the next major. scipy has broken APIs at
# minor versions in the past (the scipy.odr removal in 1.19 prompted
# the pyeq3-ng fork on 2026-04-20), so a major bump warrants an
# explicit revalidation pass. No floor declared because the codebase
# calls only stable APIs (curve_fit, optimize.differential_evolution,
# interpolate.BSpline) that haven't moved within the 1.x line.
"scipy<2",
# matplotlib 3.2 removed the `normed=` kwarg in favor of `density=`.
# MatplotlibGraphs_2D.py calls density=; older matplotlib TypeErrors.
# Ceiling <4 guards against the next major. matplotlib has a track
# record of breaking minor-version changes (the normed/density swap
# being one example); major bumps are higher blast radius and worth
# an explicit revalidation gate.
"matplotlib>=3.2,<4",
# reportlab is used via the high-level platypus API (Canvas, Image,
# Paragraph, Table, Spacer, SimpleDocTemplate, Frame) in
# StatusMonitoredLongRunningProcessPage.py. Those APIs have been
# stable across the 4.x line. No floor declared because the codebase
# has no version-specific behavior coupling; uv.lock pins the exact
# tested version.
"reportlab",
# Pillow powers matplotlib.animation.PillowWriter, which is used
# directly by ScatterAnimation / SurfaceAnimation for 3D animated
# GIF output. Pillow is also a transitive matplotlib dep; declaring
# it explicitly documents the direct dependency. No floor declared
# because matplotlib's own pillow constraint guards against versions
# matplotlib doesn't support; uv.lock pins the exact tested version.
"pillow",
# psutil's APIs we use (Process(pid).memory_info, cpu_count,
# virtual_memory, the renicing helpers in platform_compat.py) have
# been stable since psutil 5.x. No floor declared because the
# codebase has no version-specific behavior coupling; uv.lock pins
# the exact tested version.
"psutil",
# beautifulsoup4's BeautifulSoup constructor + .find() API has been
# stable since bs4 4.0 (~2012). No floor declared because the
# codebase has no version-specific behavior coupling; uv.lock pins
# the exact tested version.
"beautifulsoup4",
# lxml is the parser BeautifulSoup is invoked with in CreateReportPDF
# (BeautifulSoup(html, "lxml")). bs4 itself does not install lxml; we
# declare it explicitly so PDF generation works on a fresh `uv sync`.
# No floor declared because the codebase calls zero lxml APIs
# directly (bs4 is the entire surface); uv.lock pins the exact
# tested version.
"lxml",
"waitress>=3.0",
]
[dependency-groups]
dev = [
# mypy is a dev-only static type checker; no runtime impact. No
# floor declared because the codebase has no specific syntax or
# feature requirement; uv.lock pins the exact tested version.
"mypy",
"ruff",
# Type stubs for `requests` so mypy can verify the smoke test's HTTP
# calls. Without this, mypy errors on `import requests` even under
# ignore_missing_imports (requests has an official stubs package,
# which mypy notices specifically).
"types-requests",
"pytest>=7.0",
"pytest-django>=4.5",
"requests>=2.28", # for scripts/smoke_test.py in later phases
# Note: FunkLoad (used by funkload_tests/) is intentionally NOT listed
# here. Its setup.py uses ez_setup, which was removed from setuptools
# and cannot be built on Python 3.11+. If you need to run the FunkLoad
# suite, install it manually in a separate Python 2.7 / Python 3.9 env,
# or port the tests to pytest + requests (the test logic is just HTTP
# asserts — see funkload_tests/test_Simple.py PostLongRunningProcess).
]
browser = [
# Playwright drives the headless-Chromium browser smoke
# (scripts/browser_smoke.py) that executes StatusPoll.js for real —
# the regression class pytest and the HTTP smoke structurally cannot
# catch (see the 2026-06-02 head-timing incident in BACKLOG). Kept
# out of `dev` so the default `uv sync` and the 3-OS pytest CI jobs
# stay browser-free. Install with:
# uv sync --group browser
# uv run playwright install chromium
"playwright",
]
[tool.uv]
# No [build-system] → uv treats the project as virtual (a collection of
# top-level Django files, not an importable package). Only dependencies
# are installed; zunzun-ng itself is never built as a wheel.
package = false
[tool.uv.sources]
# pyeq3 is pinned to upstream github.com/equations-project/pyeq3. The pin tracks
# three PRs authored from this project's pyeq3-ng fork:
# PR #8 (rev 3bfec02) — replaced scipy.odr (deprecated in scipy 1.17, removed
# in 1.19) with odrpack, which wraps the same ODRPACK95 Fortran backend with
# a function-style API.
# PR #9 (rev ef97b62) — relaxed upstream's stale numpy ceiling to >=1.24,<3 and
# declared the pypandoc dep that pyeq3's package __init__ imports transitively
# (via Utilities.Multifit). That landing let this project DELETE its two local
# bridges: the [tool.uv] override-dependencies that forced numpy 2.x, and the
# explicit pypandoc dependency. Both are gone.
# PR #26 (rev ed0a44c) — added the spline-rebuild method to the 2D/3D Spline
# models so CalculateModelPredictions rebuilds the (non-serializable) scipy
# spline from solvedCoefficients on demand. That let this project DELETE its
# hand-rolled spline reconstruction in EvaluateAtAPointView (the 2D BSpline
# build and the 3D bisplev .ev wrapper). FitSpline still persists the 3D
# degrees under splineDegrees, which the view now assigns to the equation's
# xOrder/yOrder for the rebuild. (Upstream PR #30 later renamed that method
# RebuildScipySpline -> BuildSplineFromSolvedCoefficients; it is invoked
# internally by CalculateModelPredictions, so this project has no call site.)
# PR #33 (rev e0c831d) — replaced the `estimatedCoefficients
# != []` check in SolverService's differential-evolution path with a
# len(...) > 0 guard, fixing the "truth value of an empty array is ambiguous"
# crash under modern numpy. That let this project DELETE the empty-list
# workaround in FittingBaseClass.TransferFormDataToDataObject (which kept
# estimatedCoefficients as a plain [] instead of numpy.array([]) to dodge the
# ambiguous comparison); the form path now always hands pyeq3 a numpy array.
# PR #35 (rev 647bbf1) — upstreamed this project's UDF eval AST sandbox as
# pyeq3/UdfSafety.py (ValidateUDFExpression / CollectAllowedNames) and wired
# it into pyeq3's own chokepoint: IModel.ParseAndCompileUserFunctionString
# now validates the expression before compiling, and the 2D eval namespace
# was hardened to {"__builtins__": None, "numpy": numpy} (matching 3D). That
# let this project DELETE its local zunzun/udf_safety.py duplicate; the
# forms.py / views.py call sites now catch pyeq3.UdfSafety.UnsafeUDFError
# from ParseAndCompileUserFunctionString directly.
# PR #39 (rev c6bb59c) — refined pyeq3's `abs` cosmetic check so fabs() is
# accepted (it was allow-listed but rejected by a substring match).
# PR #40 (rev 96e2ae9, the current pin) — made ConvertStringIntsToStringFloats
# round-trip digit-bearing function names (log10, log2, arctan2, deg2rad,
# rad2deg) so the .0 insertion no longer mangles them, and moved
# functionDictionary / constantsDictionary into UdfSafety.
# Upstream still has no release tag for this state (version is the 12.7.0a0
# alpha; latest tags are v12.6.0 / v12.6.1), so we pin the exact rev for
# reproducibility. Once upstream tags a release at or past PR #40, switch this to
# tag= (see docs/operations/quarterly-upgrade.md). The companion pyeq3-ng fork
# (github.com/kiloscheffer/pyeq3-ng) is retained as a fallback but is otherwise
# superseded now that these PRs are upstream.
pyeq3 = { git = "https://github.com/equations-project/pyeq3.git", rev = "96e2ae90d123aaee75470c1258e544b96c57974a" }
[tool.mypy]
# Permissive baseline for the 2016-era code. Strict mode would surface
# hundreds of issues in untyped Django request handlers and dynamic
# patterns that would block every commit until methodically addressed.
# This config lets mypy catch real type errors in newly-written code
# (which we DO annotate, e.g., zunzun/LongRunningProcess/_unique.py)
# while staying out of the way of the legacy codebase.
python_version = "3.14"
ignore_missing_imports = true
strict_optional = false
exclude = [
"commonproblems", # vendored upstream (BSD-2-clause attribution)
"funkload_tests", # legacy Py2.7 / pre-uv code
"temp", # runtime scratch
"session_db", # SQLite session store
"migrations", # Django-generated; e.g. empty `dependencies = []` trips var-annotated
"\\.venv",
"build",
]
files = [
"zunzun",
"tests",
"scripts",
"manage.py",
"settings.py",
"urls.py",
"wsgi.py",
]
[[tool.mypy.overrides]]
# zunzun/views.py:50-57 builds a list-of-lists with mixed element types
# (mtime float, size int, filename str) in a housekeeping loop. mypy
# infers the elements as `object` and complains on `totalReduction +=
# fileItem[1]` (operator) and `os.path.join(..., fileItem[2])`
# (call-overload). Fixing properly needs a TypedDict / NamedTuple
# annotation that would touch surrounding 2016-era code; suppress
# these two error codes for the module instead.
module = "zunzun.views"
disable_error_code = ["operator", "call-overload"]
[tool.ruff]
# Line length sized to fit the existing codebase without manufactured
# reflow. 100 chars matches the dbxignore project's choice; the 2016-era
# source has many long lines that benefit from staying readable at their
# natural width.
line-length = 100
target-version = "py314"
# Exclude vendored third-party content and runtime scratch.
exclude = [
"commonproblems", # vendored upstream content (BSD-2-clause attribution)
"funkload_tests", # legacy Py2.7 / pre-uv code (see dev deps note)
"temp", # runtime scratch (.png, .pdf, .html artifacts)
"session_db", # SQLite session store
]
[tool.ruff.lint]
# Starter ruleset — fatal / portable categories only. Stricter selections
# (UP, B, SIM, C4, N) are deferred: the 2016-era code has many places
# where those checks surface true-positives needing careful per-site fixes
# rather than autofix sweeps.
select = [
"E", # pycodestyle errors
"W", # pycodestyle warnings
"F", # pyflakes
"I", # isort (import ordering)
]
ignore = [
"E501", # line-too-long: cosmetic, handled by formatter
"E402", # module-level import not at top: legacy `import settings`
# patterns in zunzun/* deliberately import settings before
# touching Django state; refactoring that is out of scope.
"F401", # unused-import: false-positive risk. The codebase uses
# eval()-based dynamic name resolution in several places
# (StatusMonitoredLongRunningProcessPage.py uses
# eval('SessionStore(...)'), forms.py and FittingBaseClass.py
# use eval() to resolve equation classes by name from
# imported modules). Removing seemingly-unused imports
# breaks runtime. Re-enable per-file with # noqa: F401
# if/when each module's eval sites are mapped out.
"E722", # bare-except: 60 instances across the codebase, most in
# session.save() retry loops and Django request handlers
# where the intent is "catch absolutely anything and recover".
# Converting to `except Exception:` is mostly safe but each
# call site needs review; deferred.
"E711", # `== None` -> `is None`: 20 instances, stylistic only.
"E712", # `== False` / `== True`: 28 instances, but a few are subtly
# behavior-dependent (e.g., `arr == False` for numpy arrays
# has different semantics than `not arr`). Hand-audit needed.
]
[tool.ruff.lint.isort]
known-first-party = ["zunzun"]
[tool.pytest.ini_options]
DJANGO_SETTINGS_MODULE = "settings"
python_files = ["test_*.py"]
testpaths = ["tests"]
# Don't let pytest try to collect the funkload_tests directory
# (its test files need a live server, not pytest, and importing them fails)
norecursedirs = ["funkload_tests", ".venv", "temp", "session_db"]