Restore Python 3.7 buildability, bump to 0.3.3 - #25
Open
Willmac16 wants to merge 28 commits into
Open
Conversation
0.3.2 cannot be installed on OctoPi 0.18 and older images (Python 3.7):
pip fails at "Installing build dependencies" with "Could not find a
version that satisfies the requirement setuptools>=77", because the
newest setuptools release that still supports Python 3.7 is 68.0.0.
Two things in 0.3.2 forced that floor, and both hard-error against
setuptools 68:
- `license` as a PEP 639 SPDX string plus a top-level `license-files`
needs setuptools >=77; older versions reject it with
"`project.license` must be valid exactly by one definition".
- `[[tool.setuptools.ext-modules]]` only landed in setuptools 74.1;
older versions reject it with "`tool.setuptools` must not contain
{'ext-modules'} properties".
So drop the build floor to setuptools>=61 (first release with full
[project] support, and satisfied by 68.0.0 on Python 3.7), move the
license back to the pre-PEP-639 table form with license-files under
[tool.setuptools], and declare the C extension in a minimal setup.py
again. The shim only calls setup(ext_modules=...) -- all metadata stays
static in pyproject.toml, and unlike the setup.py removed in c4349f3 it
does not need octoprint_setuptools in the build environment.
Verified end to end against setuptools 68.0.0 (the Python 3.7 ceiling)
and setuptools 83.0.0: the extension compiles, the wheel contains
octoprint_translatemodel/_translate*.so with no C++ source, LICENSE and
the octoprint.plugin entry point are recorded, and the extension imports
after install. Current setuptools only warns that the license spelling is
deprecated (removal announced for 2027-Feb-18), noted inline for when the
Python floor eventually rises.
Fixes #24
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BwbfqGAtMzTw1dhLusMkva
Nothing has ever built this repo outside a maintainer's machine, which is how 0.3.2 shipped a build-system requirement that no supported Python could satisfy. This adds a workflow that performs an isolated PEP 517 build of the checked-out tree -- the same thing OctoPrint's software update plugin does when it pip-installs archive/<tag>.zip -- across the full requires-python range, then installs the wheel and checks it. The matrix covers 3.8 through 3.13 via setup-python. 3.7 gets its own job in the python:3.7-bullseye image, because setup-python's manifest has no 3.7 build for ubuntu-24.04 (it stops at 22.04); bullseye rather than buster so actions/checkout's Node 20 has glibc >= 2.28. Builds install with --no-deps, so each job takes seconds and never resolves OctoPrint's dependency tree. That loses nothing: issue #24 failed while installing *build* dependencies, long before any runtime dependency was considered. check_build.py asserts the things this repo has actually broken before: the compiled extension present and still named _translate inside the package, no C++ source in the wheel, templates and static assets packaged, LICENSE recorded, and the octoprint.plugin entry point pointing at octoprint_translatemodel. It then loads the installed extension by file path and calls it -- by file rather than by import, since importing the parent package would need OctoPrint. Verified by running the workflow's step sequence locally against setuptools 68.0.0 (the 3.7 ceiling) and 83.0.0: both pass. Also verified the checks bite -- a wheel with the extension renamed back to `translate` and entry_points.txt removed exits 1 naming both faults. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BwbfqGAtMzTw1dhLusMkva
The first run showed the workflow firing twice for the same SHA on a branch with an open PR: once for push, once for pull_request. Limit the push trigger to main so branch commits are covered by the PR event alone. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BwbfqGAtMzTw1dhLusMkva
The build jobs deliberately install with --no-deps, which means they never prove OctoPrint can actually find the plugin. This job installs the wheel with its dependencies and walks the same path OctoPrint's plugin manager takes at startup: resolve the octoprint.plugin entry point, import the module behind it, call __plugin_load__(), and inspect the result -- no server boot required. It covers what check_build.py structurally cannot: an entry point that resolves to nothing importable, a compiled extension that fails to load through the normal `from . import _translate` path rather than by file, and a plugin class that stops registering as the mixins OctoPrint dispatches on. It also pins the installed version to pyproject's, so a half-finished release bump gets caught. Pinned to 3.11, the version OctoPi's bookworm image ships. Verified locally against OctoPrint 1.11.8 on 3.11: all 18 checks pass, install included takes about 20s. Both failure modes were confirmed to fail cleanly -- stripping entry_points.txt from the installed dist-info and removing the .so each exit 1 with a named FAIL rather than a traceback. Note for anyone extending these scripts: `pip wheel .` leaves an egg-info directory in the checkout, so metadata lookups run from the repo root can find that instead of the installed distribution. Both scripts drop the working directory from sys.path to avoid it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BwbfqGAtMzTw1dhLusMkva
OctoPrint 2.0 has been in rc since April 2026 and is the release that raises OctoPrint's own Python floor to 3.9, so it is worth knowing now whether the plugin still loads under it rather than finding out when users upgrade. The discovery job becomes a matrix over OctoPrint version. The 2.0 leg installs the plugin normally -- which resolves stable OctoPrint -- and then upgrades in place, because that is the order real users hit it: the plugin is already installed when OctoPrint moves to the new major. Both legs run on 3.11, which OctoPi's bookworm image ships and which both 1.11 and 2.0 support. Each leg now also prints the OctoPrint version it resolved, so a failure log says what it was actually testing. Verified locally: the plugin loads under 2.0.0rc4 with all 18 checks passing and no API drift -- every mixin still registers and the softwareupdate hook is intact. This leg is a hard gate rather than continue-on-error, so a regression in a future rc is visible instead of silently green. The trade-off is that a broken rc can turn the branch red for reasons outside this repo; if that becomes a nuisance, adding continue-on-error to the 2.0 leg is the one-line fix. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BwbfqGAtMzTw1dhLusMkva
The first CI runs warned that checkout@v4 and setup-python@v5 target Node 20 and were being force-run on Node 24. v7 is the current major for both (checkout v7.0.1, setup-python v7.0.0); both are ESM on Node 24 and want runner >= 2.327.1, which GitHub-hosted runners are well past. The one breaking change in checkout v7 is that fork PRs are no longer checked out for pull_request_target and workflow_run without opting in via allow-unsafe-pr-checkout. This workflow triggers on push, pull_request and workflow_dispatch, so it is unaffected -- and the new default is the safer one regardless. Node 24 in the 3.7 container was already proven by the previous run, where the runner force-ran the older actions on it and the job passed: bullseye ships glibc 2.31 against Node 24's 2.28 floor. Comment updated to say so, since it previously cited Node 20. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BwbfqGAtMzTw1dhLusMkva
Until now CI proved the plugin builds, installs and loads, but nothing checked the thing it exists to do. src/translate.cpp had no coverage at all, and a regression there does not crash -- it silently emits wrong coordinates and ruins a print. tests/ drives the extension directly, no OctoPrint required: the wheel is already installed by the build jobs, and conftest loads the compiled module by file path so importing the parent package (which needs OctoPrint) never happens. 20 tests cover single and multi-shift output, relative-mode moves being left alone, Z/E/F passing through untouched, G0-G3 versus G4, rounding, CRLF preservation, output file naming, the version header, layer markers, and preview mode returning gcode rather than a path. Two of those deserve calling out, because writing the tests is what surfaced them: translation only begins at the first layer-start match and ends at the stop match, so start and end gcode are copied through unshifted. That is correct and load-bearing -- shifting the priming line would send it off the bed -- and it now has explicit tests rather than being implicit in a regex. The tests run inside the existing build jobs rather than a new one, so they execute on every interpreter from 3.7 to 3.13; the extension is compiled per-Python, so per-version coverage is worth having. On 3.7 pip resolves pytest 7.4.4, the last release supporting it. Verified the suite has teeth rather than just passing: swapping shift[0] for shift[1] in the X branch of translateLine and rebuilding fails four tests. Also adds a 3.14 probe and Dependabot for actions. The probe forces the build past the requires-python cap to answer whether <3.14 can be lifted -- 3.14 shipped in October 2025 and OctoPrint 2.0 allows <3.15, so the cap will start excluding people. It is continue-on-error because a failure is a version this package already declares unsupported, unlike the OctoPrint 2.0 leg which gates. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BwbfqGAtMzTw1dhLusMkva
Probing the extension with malformed input found three ways to kill the
interpreter outright, all in translate_translate's argument handling:
- An invalid layer-start or stop pattern threw std::regex_error out of
translate() and into CPython, where an escaping C++ exception calls
std::terminate (SIGABRT). This is the reachable one: those patterns
come straight from plugin settings, so a typo in a settings field
took the whole OctoPrint process down mid-print.
- A shift with fewer than two entries read past the end of the
sequence (SIGSEGV), as did a non-numeric coordinate, because
PyNumber_Float's NULL return was passed straight to
PyFloat_AS_DOUBLE.
- A long enough shift list overflowed the stack (SIGSEGV): shifts was
a variable-length array sized directly by the caller. One million
shifts is 16MB against an 8MB stack.
So: catch std::exception around translate() and raise ValueError with
the message instead, validate each shift's length and coordinate
conversion, and move the shift array to a std::vector. An empty shift
list is now rejected too -- translate() reads shifts[0] on the
single-shift path, so it was reading off the end of a zero-length array.
The malformed-shift paths were not reachable through the plugin, which
coerces with float(shift[0]), float(shift[1]) and would raise in Python
first; they were still a segfault in the extension's public API. The
regex path had no such guard.
Also drops a stray Py_INCREF on the borrowed shift list argument, which
leaked a reference on every call, and the reference leaks in the shift
parsing loop that the rewrite subsumes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BwbfqGAtMzTw1dhLusMkva
The logging wrappers built a message with Py_BuildValue, which already returns a new reference, then took another with Py_XINCREF and released only one. They also dropped the return value of PyObject_CallMethod, which is a new reference too. Measured against the previous commit by watching None's refcount, since logger.debug() returns None: 2000 translate calls leaked 10000 references, five per call. After the fix the same measurement is flat at zero, which also rules out over-releasing. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BwbfqGAtMzTw1dhLusMkva
M555 tells a Prusa which bed region to probe for G29, and the copies a translate produces sit outside the original footprint, so the probed area has to grow to cover them. The arithmetic that does it -- new origin from the smallest shift, new size grown by the spread between smallest and largest -- had no coverage at all, and getting it wrong misconfigures probing rather than failing loudly. Eight tests over single and multiple shifts, negative shifts, shift order independence, three-way extents, lowercase arguments, the TRANSLATE-MODEL_BED_AREA marker, and gcode with no M555 at all. All pass against the current implementation. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BwbfqGAtMzTw1dhLusMkva
With more than one shift a layer is buffered and replayed once per copy, so G90/G91 state has to be reset to the layer's entry state before each replay -- otherwise the second copy inherits the first's trailing mode and its moves come out unshifted. translateLine carries a separate flag through the replay loop for exactly this reason, and nothing held it to that. Four tests: each copy starting from the layer entry state, the trailing mode still applying to the following layer, mode switching mid-layer, and the single-shift streaming path tracking the same state. All pass against the current implementation. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BwbfqGAtMzTw1dhLusMkva
__init__.py has a TODO about handling translation of an already translated file, and it is reachable: output lands beside the input, so it appears in OctoPrint's file list and can be picked again. These tests record the current baseline rather than endorse it. Shifts compound (10 -> 15 -> 20), the output name accumulates suffixes, and the header is written once per pass. The fourth test is the one that matters if this is ever changed: ;TRANSLATE-MODEL_LAYER_START does not match the layer-start pattern, so a second pass does not treat first-pass markers as new layers -- if that ever became true, copies would multiply on every pass instead of adding. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BwbfqGAtMzTw1dhLusMkva
The systematic version of the bad-input work: rather than guessing which malformed inputs reach a bad pointer, build the extension instrumented and let the sanitizers say so. It would have caught the out-of-bounds shift reads directly instead of by segfault. Two things this needed. libstdc++ has to be preloaded next to libasan -- with only libasan preloaded into an uninstrumented interpreter, asan's __cxa_throw interceptor cannot resolve the real symbol and the first C++ exception dies on a CHECK failure, which the invalid-regex tests trigger deliberately. And ubsan needs halt_on_error, or it prints diagnostics and still exits zero, so undefined behaviour would pass as green. Leak detection stays off: CPython is not instrumented, so its own allocations would bury anything real. This job covers memory errors and UB; the reference-counting side is covered by the refcount measurement in the previous commits. Verified locally: all 50 tests pass instrumented with no findings. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BwbfqGAtMzTw1dhLusMkva
Every other job builds from the checked-out tree, because that is what OctoPrint's updater installs from a GitHub archive. Nothing exercised the sdist path, so MANIFEST.in dropping src/translate.cpp would only surface after a PyPI upload -- and the 0.3.2 rename suggests PyPI is on the cards. This packs an sdist, builds the wheel from that tarball rather than the tree, then runs the same content checks and translate tests against it. Verified locally: the sdist carries src/translate.cpp and setup.py, and the wheel builds from it cleanly. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BwbfqGAtMzTw1dhLusMkva
Every user of this plugin is on a Raspberry Pi and every job so far builds x86. That gap matters for more than whether it compiles: plain `char` is unsigned by default on ARM and signed on x86, and translate.cpp walks gcode a character at a time, so a signedness assumption would produce wrong output only on the hardware nobody tests on. Runs the same build, wheel checks and translate tests inside an arm64 container via qemu. Slower than the native jobs, but it is the only place architecture-specific behaviour can show up before a user's print does. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BwbfqGAtMzTw1dhLusMkva
runnDict and finishDict were assigned without var/let/const, which makes them implicit globals: they land on window and are shared by every instance of the view model rather than being scoped to the branch that builds them. Both are used only within the block that assigns them, so const is a straight swap. Found by running eslint over the file, which is added in the next commit. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BwbfqGAtMzTw1dhLusMkva
static/js is the plugin's browser-facing surface and the part that needed XSS fixes in 0.3.1, and nothing has ever looked at it automatically. eslint's recommended rules plus eslint-plugin-no-unsanitized, with OctoPrint's injected globals declared so the job reports real problems rather than a wall of no-undef. Callback parameters are exempt from no-unused-vars, which is the usual convention and the only rule relaxation here. Worth being clear about the limit: no-unsanitized covers DOM sinks like innerHTML and insertAdjacentHTML, not jQuery's .html(). The one .html() call in this file passes a jQuery object rather than a string, so it is not a sink, but a future string argument would not be flagged by this job. package.json is dev-only tooling -- it is not published and does not reach the wheel, which is built from pyproject.toml. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BwbfqGAtMzTw1dhLusMkva
The software update plugin fetches archive/<tag>.zip and compares the tag against the installed distribution's version, so tagging 0.3.4 while pyproject.toml still says 0.3.3 ships an update that reports the wrong version once installed. The bump has historically been its own commit, which is the kind of step that gets skipped. Runs on tag pushes and compares the two, tolerating a v prefix in case tagging style ever changes. Verified both directions: a matching tag exits 0, a mismatched one exits 1 naming both versions. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BwbfqGAtMzTw1dhLusMkva
Everything so far tested the extension or the packaging; the plugin module itself had no coverage. These tests run against a bare plugin instance with small fakes for the printer, file manager and logger. Covered: the settings defaults, including a check that they are valid patterns for the C++ side (std::regex, not Python's re -- an invalid default would make every translate raise) and that they match the markers Cura and PrusaSlicer actually emit; the software update configuration pointing at this repository and reporting the running version; the declared JS asset existing in the installed package; the API command parameter lists; and the delete-after-print handler, which must fire only for tracked files, only on print-end events, and only for local origin, and must stop tracking the file so a later event cannot delete twice. on_api_command is deliberately not covered: it opens with a flask-principal permission check that needs a real request and app context, which is a bigger harness than the rest of this is worth. The tests skip themselves when OctoPrint is not importable, so the build jobs -- which install with --no-deps -- skip them and the discovery job, which has a real OctoPrint, runs them. Verified both ways: 65 pass with OctoPrint present, 50 pass and 1 skip without. conftest now drops the working directory from sys.path at import time rather than inside the extension loader, because these tests import the installed package at collection time and the repo's source directory would otherwise shadow it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BwbfqGAtMzTw1dhLusMkva
The lint job failed on npm error notarget: @eslint/js is versioned independently of eslint, and I wrote ^10.8.0 for both by assuming they track together. eslint is 10.8.0; @eslint/js is 10.0.1. Verified this time from a clean npm install against package.json, which is what CI does, rather than from packages installed ad hoc. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BwbfqGAtMzTw1dhLusMkva
The 3.14 probe passes but warns: PyImport_ImportModuleNoBlock is deprecated and removed in Python 3.15. It has been nothing but an alias for PyImport_ImportModule since Python 3.3, so this is behaviour-neutral today and the difference between building and not building on 3.15. Verified with deprecation warnings turned into errors: 65 tests pass. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BwbfqGAtMzTw1dhLusMkva
The probe answered its question over two runs: 3.14 builds, installs and passes the whole suite. With the NoBlock alias gone in the previous commit there is nothing left holding the cap at <3.14, and leaving it there refuses installation for anyone on 3.14 -- a version OctoPrint 2.0 supports. So raise requires-python to <3.15 and move 3.14 from the probe into the real build matrix, where it gates like every other version rather than being informational. The probe itself moves up to 3.15, which is in beta. It is the same arrangement: forced past the cap with --ignore-requires-python, continue-on-error because a failure there is a version this package declares unsupported. It doubles as a check on the previous commit -- 3.15 is where PyImport_ImportModuleNoBlock actually disappears, so a green 3.15 probe is the proof the swap was both needed and sufficient. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BwbfqGAtMzTw1dhLusMkva
Raising requires-python to <3.15 means a 3.14 user can install the plugin, and OctoPrint 1.11 caps at <3.14, so the only OctoPrint they can be running is 2.0. That pairing had no coverage: both discovery legs ran on 3.11. The matrix now carries a python version per leg and adds 2.0 on 3.14. Whether OctoPrint 2.0's own dependencies have 3.14 wheels yet is exactly what this will tell us, and it is worth knowing before a user finds out. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BwbfqGAtMzTw1dhLusMkva
package.json has no lockfile, so npm resolves within the caret ranges on every run and a new eslint major is only noticed when the job breaks -- which is precisely how the @eslint/js mistake surfaced. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BwbfqGAtMzTw1dhLusMkva
on_api_command is the plugin's only externally reachable entry point and every branch of it is guarded by an OctoPrint permission, which is why I left it uncovered earlier -- making those checks evaluate looked like it needed a flask request context. It does not. The plugin reads Permissions as a module attribute, so swapping in a stub exercises the real branching, and stubbing TranslateWorker keeps the tests from spawning threads or touching disk while still capturing what the plugin decided to hand it. Fourteen tests: FILES_UPLOAD gating translate and preview; the after-translate downgrade ladder, where load needs FILES_SELECT, print falls back to load without PRINT and to nothing without FILES_SELECT, and printAndDelete is treated like print; shift coercion; settings patterns reaching the worker; non-gcode rejection; preview always previewing; and the test command starting nothing. valid_file_type is stubbed as well -- the real one asks the plugin manager which extensions are registered, and which extensions count as gcode is OctoPrint's business rather than this plugin's. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BwbfqGAtMzTw1dhLusMkva
Submitting the same file with the same shifts while the first translate is still running raised UnboundLocalError instead of reporting that it was already in flight: the duplicate branch reads len(shifts), but shifts is only bound in the branch that starts a worker. The user got a 500 and the UI never received the "running" notification it was meant to show. len(data['shifts']) is the count that branch was reaching for -- the number of shifts requested, which is what the message reports. Found while writing the API permission tests in the previous commit; the two new tests cover the duplicate path and confirm that a request with different shifts is still treated as new work. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BwbfqGAtMzTw1dhLusMkva
It failed, and the reason is nothing this plugin controls: installing OctoPrint 2.0 on 3.14 pulls a PyYAML with no 3.14 wheel, so pip builds it from source and its setup.py dies on modern setuptools with AttributeError: 'build_ext' object has no attribute 'cython_sources'. The plugin itself is fine on 3.14 -- the gating py3.14 build job and the 3.15 probe both pass. So this leg becomes informational, like the version probe, and its going green later is the signal that the combination has become usable. Worth recording plainly: this means nobody can actually run OctoPrint on 3.14 today, since 1.11 caps at <3.14 and 2.0's dependencies will not install there. Raising requires-python to <3.15 does not unlock anyone yet; it just stops this plugin from being the thing in the way. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BwbfqGAtMzTw1dhLusMkva
Job-level continue-on-error keeps the workflow run green but still marks the job's check as failed, so the previous commit left a permanently red entry in the PR's checks list for a job that provides no coverage at all today. A red X nobody is expected to act on is worse than no job: it teaches people to skim past red. Instead the install steps are allowed to fail on 3.14 specifically, the discovery checks are skipped when they do, and a notice says why. The job goes green with an explanation rather than red with none, and the moment OctoPrint 2.0 installs on 3.14 the checks start running for real with no change to this file. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BwbfqGAtMzTw1dhLusMkva
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
0.3.2 cannot be installed on OctoPi 0.18 and older images (Python 3.7):
pip fails at "Installing build dependencies" with "Could not find a
version that satisfies the requirement setuptools>=77", because the
newest setuptools release that still supports Python 3.7 is 68.0.0.
Two things in 0.3.2 forced that floor, and both hard-error against
setuptools 68:
licenseas a PEP 639 SPDX string plus a top-levellicense-filesneeds setuptools >=77; older versions reject it with
"
project.licensemust be valid exactly by one definition".[[tool.setuptools.ext-modules]]only landed in setuptools 74.1;older versions reject it with "
tool.setuptoolsmust not contain{'ext-modules'} properties".
So drop the build floor to setuptools>=61 (first release with full
[project] support, and satisfied by 68.0.0 on Python 3.7), move the
license back to the pre-PEP-639 table form with license-files under
[tool.setuptools], and declare the C extension in a minimal setup.py
again. The shim only calls setup(ext_modules=...) -- all metadata stays
static in pyproject.toml, and unlike the setup.py removed in c4349f3 it
does not need octoprint_setuptools in the build environment.
Verified end to end against setuptools 68.0.0 (the Python 3.7 ceiling)
and setuptools 83.0.0: the extension compiles, the wheel contains
octoprint_translatemodel/_translate*.so with no C++ source, LICENSE and
the octoprint.plugin entry point are recorded, and the extension imports
after install. Current setuptools only warns that the license spelling is
deprecated (removal announced for 2027-Feb-18), noted inline for when the
Python floor eventually rises.
Fixes #24
Co-Authored-By: Claude Opus 5 noreply@anthropic.com
Claude-Session: https://claude.ai/code/session_01BwbfqGAtMzTw1dhLusMkva