From 851052225138f1ec7077bd2d3b0eac7d2d4a8f9b Mon Sep 17 00:00:00 2001 From: bartzbeielstein <32470350+bartzbeielstein@users.noreply.github.com> Date: Tue, 2 Jun 2026 02:09:57 +0200 Subject: [PATCH 1/4] ci: drop redundant test run from release pipeline Tests are already gated on PRs by ci.yml (required fast check) and nightly (full suite). Re-running the fast suite on the merge-to-main push duplicated cost without adding signal, so the release workflow now publishes and deploys docs without re-testing. Co-Authored-By: Claude Opus 4.8 (1M context) --- .github/workflows/release.yml | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a1542695..61d53a01 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,12 +1,12 @@ # ============================================================================= # .github/workflows/release.yml — Semantic Release + PyPI + Docs # ============================================================================= -# Triggers on every push to main. -# 1. Runs tests to gate the pipeline. -# 2. Semantic-release checks conventional commits — if a releasable change +# Triggers on every push to main. Tests are NOT run here — they are gated on +# PRs by ci.yml (the required fast check) and nightly (full suite). +# 1. Semantic-release checks conventional commits — if a releasable change # is found (feat:, fix:, etc.) it bumps version, publishes to PyPI, # and creates a GitHub release. -# 3. Builds and deploys quartodoc documentation to GitHub Pages. +# 2. Builds and deploys quartodoc documentation to GitHub Pages. # ============================================================================= name: Release @@ -53,13 +53,11 @@ jobs: - name: Install dependencies run: uv sync --extra dev --extra docs - # ── Tests ───────────────────────────────────────────────────────── - # Fast gate only: the full suite (incl. slow) already ran on the PR's - # required check and nightly. This is a quick pre-publish sanity check. - - name: Run tests (fast — excludes slow) - run: uv run pytest tests/ -m "not slow" -n auto --tb=short - # ── Semantic Release ────────────────────────────────────────────── + # NOTE: tests are intentionally NOT run here. The fast suite is the + # required check on every PR to main (ci.yml) and the full suite runs + # nightly, so re-running them on the merge commit only duplicated cost + # without adding signal. This pipeline publishes; it does not re-test. - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 with: node-version: 22 From 7b0c0c236c477961023105db0dd26875e8f7d141 Mon Sep 17 00:00:00 2001 From: bartzbeielstein <32470350+bartzbeielstein@users.noreply.github.com> Date: Tue, 2 Jun 2026 02:10:05 +0200 Subject: [PATCH 2/4] chore(deps): bump bart26g jupyter deps to clear Dependabot alerts All 18 open Dependabot alerts were in the bart26g/ paper subproject lockfile (none in the shipped spotoptim package). Bump vulnerable transitive deps to patched versions: idna 3.11->3.17, mistune 3.2.0->3.2.1, urllib3 2.6.3->2.7.0, jupyterlab 4.5.6->4.5.7, notebook 7.5.5->7.5.6, jupyter-server 2.17.0->2.19.0. Also restamps the main uv.lock spotoptim version (0.12.1->0.12.4) to match pyproject. Co-Authored-By: Claude Opus 4.8 (1M context) --- bart26g/uv.lock | 81 +++++++++++++++++++++++++------------------------ uv.lock | 2 +- 2 files changed, 43 insertions(+), 40 deletions(-) diff --git a/bart26g/uv.lock b/bart26g/uv.lock index dd74509d..5f127ec8 100644 --- a/bart26g/uv.lock +++ b/bart26g/uv.lock @@ -676,11 +676,11 @@ wheels = [ [[package]] name = "idna" -version = "3.11" +version = "3.17" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582, upload-time = "2025-10-12T14:55:20.501Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b9/28/99c51f664567218d824af024c0251650fb27e4ca066df188dab0769c5b91/idna-3.17.tar.gz", hash = "sha256:5eb0cb53bc467c12eadcf6de83163ad8527cec9416f44b9b61b19caedad2b87f", size = 196048, upload-time = "2026-05-28T14:32:38.55Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" }, + { url = "https://files.pythonhosted.org/packages/de/a7/f76514cc40ad6234098ecdebda08732d75964776c51a42845b7da10649e2/idna-3.17-py3-none-any.whl", hash = "sha256:466e48829084efe2548012b855df21540b96f2e20e51bd124c851536556a592c", size = 65316, upload-time = "2026-05-28T14:32:37.035Z" }, ] [[package]] @@ -988,7 +988,7 @@ wheels = [ [[package]] name = "jupyter-server" -version = "2.17.0" +version = "2.19.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, @@ -1010,9 +1010,9 @@ dependencies = [ { name = "traitlets" }, { name = "websocket-client" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/5b/ac/e040ec363d7b6b1f11304cc9f209dac4517ece5d5e01821366b924a64a50/jupyter_server-2.17.0.tar.gz", hash = "sha256:c38ea898566964c888b4772ae1ed58eca84592e88251d2cfc4d171f81f7e99d5", size = 731949, upload-time = "2025-08-21T14:42:54.042Z" } +sdist = { url = "https://files.pythonhosted.org/packages/76/a0/eb3c511f54df7b54ca5fc7bff3f4d2277d69052d6a7f521643dfed5279d6/jupyter_server-2.19.0.tar.gz", hash = "sha256:1731236bc32b680223e1ceb9d68209a845203475012ef68773a81434b46a31a7", size = 754561, upload-time = "2026-05-29T11:21:26.057Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/92/80/a24767e6ca280f5a49525d987bf3e4d7552bf67c8be07e8ccf20271f8568/jupyter_server-2.17.0-py3-none-any.whl", hash = "sha256:e8cb9c7db4251f51ed307e329b81b72ccf2056ff82d50524debde1ee1870e13f", size = 388221, upload-time = "2025-08-21T14:42:52.034Z" }, + { url = "https://files.pythonhosted.org/packages/c1/78/d2881e68894cecdcd05912a9c585cfb776ef1fb38b62c8dba98f12ab3adc/jupyter_server-2.19.0-py3-none-any.whl", hash = "sha256:cb76591b76d7093584c2ad2ae72ac3d58614a4b597507a1bb04e1f9f683cf9ea", size = 392244, upload-time = "2026-05-29T11:21:23.871Z" }, ] [[package]] @@ -1030,7 +1030,7 @@ wheels = [ [[package]] name = "jupyterlab" -version = "4.5.6" +version = "4.5.7" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "async-lru" }, @@ -1047,9 +1047,9 @@ dependencies = [ { name = "tornado" }, { name = "traitlets" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ac/d5/730628e03fff2e8a8e8ccdaedde1489ab1309f9a4fa2536248884e30b7c7/jupyterlab-4.5.6.tar.gz", hash = "sha256:642fe2cfe7f0f5922a8a558ba7a0d246c7bc133b708dfe43f7b3a826d163cf42", size = 23970670, upload-time = "2026-03-11T14:17:04.531Z" } +sdist = { url = "https://files.pythonhosted.org/packages/2b/22/8440ec827762146e7cdecf04335bd348795899d29dc6ae82238707353a2c/jupyterlab-4.5.7.tar.gz", hash = "sha256:55a9822c4754da305f41e113452c68383e214dcf96de760146af89ce5d5117b0", size = 23992763, upload-time = "2026-04-29T16:43:51.328Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e1/1b/dad6fdcc658ed7af26fdf3841e7394072c9549a8b896c381ab49dd11e2d9/jupyterlab-4.5.6-py3-none-any.whl", hash = "sha256:d6b3dac883aa4d9993348e0f8e95b24624f75099aed64eab6a4351a9cdd1e580", size = 12447124, upload-time = "2026-03-11T14:17:00.229Z" }, + { url = "https://files.pythonhosted.org/packages/3d/aa/537b8f7d80e799af19af35fb3ddfc970b951088a13c57dd9387dcfbb7f61/jupyterlab-4.5.7-py3-none-any.whl", hash = "sha256:fba4cb0e2c44a52859669d8c98b45de029d5e515f8407bf8534d2a8fc5f0964d", size = 12450123, upload-time = "2026-04-29T16:43:46.639Z" }, ] [[package]] @@ -1295,11 +1295,11 @@ wheels = [ [[package]] name = "mistune" -version = "3.2.0" +version = "3.2.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/9d/55/d01f0c4b45ade6536c51170b9043db8b2ec6ddf4a35c7ea3f5f559ac935b/mistune-3.2.0.tar.gz", hash = "sha256:708487c8a8cdd99c9d90eb3ed4c3ed961246ff78ac82f03418f5183ab70e398a", size = 95467, upload-time = "2025-12-23T11:36:34.994Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ca/84/620cc3f7e3adf6f5067e10f4dbae71295d8f9e16d5d3f9ef97c40f2f592c/mistune-3.2.1.tar.gz", hash = "sha256:7c8e5501d38bac1582e067e46c8343f17d57ea1aaa735823f3aba1fd59c88a28", size = 98003, upload-time = "2026-05-03T14:33:22.312Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/9b/f7/4a5e785ec9fbd65146a27b6b70b6cdc161a66f2024e4b04ac06a67f5578b/mistune-3.2.0-py3-none-any.whl", hash = "sha256:febdc629a3c78616b94393c6580551e0e34cc289987ec6c35ed3f4be42d0eee1", size = 53598, upload-time = "2025-12-23T11:36:33.211Z" }, + { url = "https://files.pythonhosted.org/packages/2a/7f/a946aa4f8752b37102b41e64dca18a1976ac705c3a0d1dfe74d820a02552/mistune-3.2.1-py3-none-any.whl", hash = "sha256:78cdb0ba5e938053ccf63651b352508d2efa9411dc8810bfb05f2dc5140c0048", size = 53749, upload-time = "2026-05-03T14:33:20.551Z" }, ] [[package]] @@ -1404,7 +1404,7 @@ wheels = [ [[package]] name = "notebook" -version = "7.5.5" +version = "7.5.6" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "jupyter-server" }, @@ -1413,9 +1413,9 @@ dependencies = [ { name = "notebook-shim" }, { name = "tornado" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/1f/6d/41052c48d6f6349ca0a7c4d1f6a78464de135e6d18f5829ba2510e62184c/notebook-7.5.5.tar.gz", hash = "sha256:dc0bfab0f2372c8278c457423d3256c34154ac2cc76bf20e9925260c461013c3", size = 14169167, upload-time = "2026-03-11T16:32:51.922Z" } +sdist = { url = "https://files.pythonhosted.org/packages/2a/c2/cf59bd2e6f2c8b976b52477e3e53bf6f97bc714ed046a51821afb428eaee/notebook-7.5.6.tar.gz", hash = "sha256:621174aade80108f0020b0f00738000b215f75fa3cd90771ad7aa0f24536a4e1", size = 14170814, upload-time = "2026-04-30T11:46:26.613Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f8/aa/cbd1deb9f07446241e88f8d5fecccd95b249bca0b4e5482214a4d1714c49/notebook-7.5.5-py3-none-any.whl", hash = "sha256:a7c14dbeefa6592e87f72290ca982e0c10f5bbf3786be2a600fda9da2764a2b8", size = 14578929, upload-time = "2026-03-11T16:32:48.021Z" }, + { url = "https://files.pythonhosted.org/packages/e9/d6/1fd0646b9bbd9efbb0b8ae21b2325fbef515769a5621c03e31d8eb8da587/notebook-7.5.6-py3-none-any.whl", hash = "sha256:4dde3f8fb55fa8fb7946d58c6e869ce9baf46d00fc070664f62604569d0faca0", size = 14581730, upload-time = "2026-04-30T11:46:22.342Z" }, ] [[package]] @@ -2252,7 +2252,7 @@ wheels = [ [[package]] name = "spotoptim" -version = "0.11.5" +version = "0.12.4" source = { editable = "../" } dependencies = [ { name = "black" }, @@ -2294,8 +2294,10 @@ requires-dist = [ { name = "matplotlib", specifier = ">=3.10.7" }, { name = "numpy", specifier = ">=1.24.3" }, { name = "pandas", specifier = ">=2.1.0" }, + { name = "pre-commit", marker = "extra == 'dev'", specifier = ">=3.7.0" }, { name = "pytest", marker = "extra == 'dev'", specifier = ">=7.4.0" }, { name = "pytest-cov", marker = "extra == 'dev'", specifier = ">=4.1.0" }, + { name = "pytest-timeout", marker = "extra == 'dev'", specifier = ">=2.3.0" }, { name = "pytest-xdist", marker = "extra == 'dev'", specifier = ">=3.5.0" }, { name = "quartodoc", marker = "extra == 'docs'", specifier = ">=0.11.1" }, { name = "requests", specifier = ">=2.32.3" }, @@ -2319,6 +2321,7 @@ dev = [ { name = "bandit", specifier = ">=1.8.0" }, { name = "black", specifier = ">=24.1.0" }, { name = "isort", specifier = ">=5.13.0" }, + { name = "pre-commit", specifier = ">=3.7.0" }, { name = "pytest", specifier = ">=7.4.0" }, { name = "pytest-cov", specifier = ">=4.1.0" }, { name = "pytest-timeout", specifier = ">=2.3.0" }, @@ -2511,10 +2514,10 @@ dependencies = [ { name = "typing-extensions", marker = "sys_platform == 'darwin'" }, ] wheels = [ - { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.11.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:442ec9dc78592564fdad69cf0beaa9da2f82ab810ccb4f13903869a90bf3f15d" }, - { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.11.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:cc3a195701bba2239c313ee311487f80f8aaebe9e89b9073dddbcf2f93b5a0ba" }, - { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.11.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:072a0d6e4865e8b0dc0dbfe6ebed68fae235124222835ef03e5814d414d8c012" }, - { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.11.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:23ec7789017da9d95b6d543d790814785e6f30905c5443efa8257d1490d73f79" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.11.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:442ec9dc78592564fdad69cf0beaa9da2f82ab810ccb4f13903869a90bf3f15d", upload-time = "2026-03-23T15:17:02Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.11.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:cc3a195701bba2239c313ee311487f80f8aaebe9e89b9073dddbcf2f93b5a0ba", upload-time = "2026-03-23T15:17:06Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.11.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:072a0d6e4865e8b0dc0dbfe6ebed68fae235124222835ef03e5814d414d8c012", upload-time = "2026-03-23T15:17:10Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.11.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:23ec7789017da9d95b6d543d790814785e6f30905c5443efa8257d1490d73f79", upload-time = "2026-03-23T15:17:14Z" }, ] [[package]] @@ -2539,22 +2542,22 @@ dependencies = [ { name = "typing-extensions", marker = "sys_platform != 'darwin'" }, ] wheels = [ - { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.11.0%2Bcpu-cp313-cp313-linux_s390x.whl" }, - { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.11.0%2Bcpu-cp313-cp313-manylinux_2_28_aarch64.whl" }, - { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.11.0%2Bcpu-cp313-cp313-manylinux_2_28_x86_64.whl" }, - { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.11.0%2Bcpu-cp313-cp313-win_amd64.whl" }, - { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.11.0%2Bcpu-cp313-cp313t-linux_s390x.whl" }, - { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.11.0%2Bcpu-cp313-cp313t-manylinux_2_28_aarch64.whl" }, - { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.11.0%2Bcpu-cp313-cp313t-manylinux_2_28_x86_64.whl" }, - { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.11.0%2Bcpu-cp313-cp313t-win_amd64.whl" }, - { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.11.0%2Bcpu-cp314-cp314-linux_s390x.whl" }, - { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.11.0%2Bcpu-cp314-cp314-manylinux_2_28_aarch64.whl" }, - { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.11.0%2Bcpu-cp314-cp314-manylinux_2_28_x86_64.whl" }, - { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.11.0%2Bcpu-cp314-cp314-win_amd64.whl" }, - { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.11.0%2Bcpu-cp314-cp314t-linux_s390x.whl" }, - { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.11.0%2Bcpu-cp314-cp314t-manylinux_2_28_aarch64.whl" }, - { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.11.0%2Bcpu-cp314-cp314t-manylinux_2_28_x86_64.whl" }, - { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.11.0%2Bcpu-cp314-cp314t-win_amd64.whl" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.11.0%2Bcpu-cp313-cp313-linux_s390x.whl", hash = "sha256:d1eff25ccc454faf21c9666c81bfab8e405e87c12d300708d4559620bc191a36", upload-time = "2026-04-28T00:06:42Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.11.0%2Bcpu-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:48b3e21a311445acdd0b27f13830e21d93adef70d4721e051e9f059baeb9b8f9", upload-time = "2026-04-28T00:06:51Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.11.0%2Bcpu-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:45025d7752dbc6b4c784c03afaee9c5f19730ce084b2e43fc9a2fe1677d9ff86", upload-time = "2026-04-28T00:07:02Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.11.0%2Bcpu-cp313-cp313-win_amd64.whl", hash = "sha256:ed70d4a4fc9f8b826c02fa1a9800a83820fb2fa6ae607680b53390f9ef394d85", upload-time = "2026-04-28T00:07:12Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.11.0%2Bcpu-cp313-cp313t-linux_s390x.whl", hash = "sha256:65d427a196ab0abe359b93c5bffedd76ded02df2b1b1d2d9f11a2609b69f426a", upload-time = "2026-04-28T00:07:19Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.11.0%2Bcpu-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:8f13dc7075ae04ca5f876a9f40b4e47522a04c23e30824b4409f42a3f3e57aa4", upload-time = "2026-04-28T00:07:27Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.11.0%2Bcpu-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:8713bb8679376ea0ec25742100b6cfb8447e0904c48bddefb9eb0ac1abbfa60a", upload-time = "2026-04-28T00:07:37Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.11.0%2Bcpu-cp313-cp313t-win_amd64.whl", hash = "sha256:62ec1f1694c185f601eab74eb7fc0e8e10c64c06ae82f13c3592774c231c4877", upload-time = "2026-04-28T00:07:47Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.11.0%2Bcpu-cp314-cp314-linux_s390x.whl", hash = "sha256:c9a14c367f470623b978e273a4e1915995b4ba7a0ae999178b06c273eea3536f", upload-time = "2026-04-28T00:07:54Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.11.0%2Bcpu-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:71676f6a9a84bbd385e010198b51fa1c2324fb8f3c512a32d2c81af65f68f4c9", upload-time = "2026-04-28T00:08:02Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.11.0%2Bcpu-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:f8481ea9088e4e5b81178a75aabdbb658bde8639bc1a15fd5d8f930abc966735", upload-time = "2026-04-28T00:08:11Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.11.0%2Bcpu-cp314-cp314-win_amd64.whl", hash = "sha256:7575af4c9f7f7500ed62b1dafeb069aa0ba35b368a5f09793b3976b3d50f4fe4", upload-time = "2026-04-28T00:08:20Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.11.0%2Bcpu-cp314-cp314t-linux_s390x.whl", hash = "sha256:825f1596878280a3a4c861441674888bc2d792e4ab7b045cb35feeab3f4f5dd7", upload-time = "2026-04-28T00:08:27Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.11.0%2Bcpu-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:c8a0bdfb2fd915b6c2cd27c856f63f729c366a4917772eba6b2b02aa3bce70d5", upload-time = "2026-04-28T00:08:36Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.11.0%2Bcpu-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:768f22924a25cad2adeb9c6cbac5159e71067c8d4019b1511960d7435a5ca652", upload-time = "2026-04-28T00:08:47Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.11.0%2Bcpu-cp314-cp314t-win_amd64.whl", hash = "sha256:6db45e7b2526d996fbf47c3d08737807a60a4e17996a6d91a97027fe260832c8", upload-time = "2026-04-28T00:08:57Z" }, ] [[package]] @@ -2636,11 +2639,11 @@ wheels = [ [[package]] name = "urllib3" -version = "2.6.3" +version = "2.7.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/c7/24/5f1b3bdffd70275f6661c76461e25f024d5a38a46f04aaca912426a2b1d3/urllib3-2.6.3.tar.gz", hash = "sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed", size = 435556, upload-time = "2026-01-07T16:24:43.925Z" } +sdist = { url = "https://files.pythonhosted.org/packages/53/0c/06f8b233b8fd13b9e5ee11424ef85419ba0d8ba0b3138bf360be2ff56953/urllib3-2.7.0.tar.gz", hash = "sha256:231e0ec3b63ceb14667c67be60f2f2c40a518cb38b03af60abc813da26505f4c", size = 433602, upload-time = "2026-05-07T16:13:18.596Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl", hash = "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4", size = 131584, upload-time = "2026-01-07T16:24:42.685Z" }, + { url = "https://files.pythonhosted.org/packages/7f/3e/5db95bcf282c52709639744ca2a8b149baccf648e39c8cc87553df9eae0c/urllib3-2.7.0-py3-none-any.whl", hash = "sha256:9fb4c81ebbb1ce9531cce37674bbc6f1360472bc18ca9a553ede278ef7276897", size = 131087, upload-time = "2026-05-07T16:13:17.151Z" }, ] [[package]] diff --git a/uv.lock b/uv.lock index 92c7f5cd..da9e5e45 100644 --- a/uv.lock +++ b/uv.lock @@ -2926,7 +2926,7 @@ wheels = [ [[package]] name = "spotoptim" -version = "0.12.1" +version = "0.12.4" source = { editable = "." } dependencies = [ { name = "black" }, From 63b379bcd3b427e7d1fbb7586efeb22e913c05b6 Mon Sep 17 00:00:00 2001 From: bartzbeielstein <32470350+bartzbeielstein@users.noreply.github.com> Date: Tue, 2 Jun 2026 02:16:24 +0200 Subject: [PATCH 3/4] fix(remote): bound objective_remote request timeout to prevent CI hangs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit objective_remote() called requests.post() with no timeout. When the remote server is unreachable and packets are dropped (as on GitHub runners, vs an immediate connection-refused locally) the socket hangs indefinitely — the fast test suite ran 20+ minutes instead of ~1 and had to be cancelled. Add a default (connect, read) timeout of (10, 120)s, exposed as a `timeout` parameter. Defense in depth: set pytest-timeout's global timeout=300 so no single test can hang the suite, and mark the networked test_objective_remote as slow so the fast PR gate skips it (the nightly full run still exercises it, now bounded by the timeout). Co-Authored-By: Claude Opus 4.8 (1M context) --- pyproject.toml | 3 +++ src/spotoptim/function/remote.py | 15 +++++++++++++-- tests/conftest.py | 3 +++ 3 files changed, 19 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index a7b5b29c..56114ec6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -84,6 +84,9 @@ python_classes = ["Test*"] python_functions = ["test_*"] # Keep slow-test timings visible on every run; the registry lives in tests/conftest.py. addopts = ["--durations=15"] +# Safety net: no single test may hang the suite (e.g. a stuck network call). +# Provided by pytest-timeout (dev dependency). +timeout = 300 markers = [ "slow: heavy end-to-end test; deselect with -m \"not slow\". Runs in nightly/full CI.", ] diff --git a/src/spotoptim/function/remote.py b/src/spotoptim/function/remote.py index e1c7a535..5bc633cd 100644 --- a/src/spotoptim/function/remote.py +++ b/src/spotoptim/function/remote.py @@ -7,10 +7,17 @@ # Default configuration for the server endpoint DEFAULT_SERVER_URL = "http://139.6.66.164:8000/compute/" +# Default (connect, read) timeout in seconds. A bounded connect timeout keeps +# the call from hanging indefinitely when the server is unreachable — e.g. in +# CI, where egress to the server may be silently dropped rather than refused. +DEFAULT_TIMEOUT: tuple[float, float] = (10.0, 120.0) def objective_remote( - X: np.ndarray, url: str = DEFAULT_SERVER_URL, **kwargs + X: np.ndarray, + url: str = DEFAULT_SERVER_URL, + timeout: float | tuple[float, float] = DEFAULT_TIMEOUT, + **kwargs, ) -> np.ndarray: """ Evaluates an objective function remotely via an HTTP POST request. @@ -19,6 +26,10 @@ def objective_remote( X (np.ndarray): Input data of shape (n_samples, n_features). url (str, optional): The URL of the remote computation server. Defaults to "http://139.6.66.164:8000/compute/". + timeout (float | tuple[float, float], optional): Request timeout in + seconds as a single value or a ``(connect, read)`` tuple. Defaults + to ``(10, 120)``; the bounded connect time prevents the call from + hanging when the server is unreachable. **kwargs (Any): Additional arguments to include in the request payload (optional). Returns: @@ -43,7 +54,7 @@ def objective_remote( payload.update(kwargs) # Perform the request - response = requests.post(url, json=payload) + response = requests.post(url, json=payload, timeout=timeout) response.raise_for_status() # Parse the response diff --git a/tests/conftest.py b/tests/conftest.py index 14f2796e..903fe04e 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -40,6 +40,9 @@ "tests/test_transformations.py::TestTransformationOptimization", "tests/test_reproducibility_comprehensive.py::TestSpotOptimReproducibility", "tests/test_optimize_refactored_methods.py::TestOptimizeIntegration", + # --- networked integration test: skip from the fast gate (the request + # timeout in remote.py bounds it; the nightly full run still exercises it) --- + "tests/test_objective_remote.py::test_objective_remote", # --- individual heavy tests --- "tests/test_early_stopping.py::test_max_restarts_does_not_trigger_with_improvement", "tests/test_parallel_optimization.py::TestParallelOptimization::test_parallel_execution_basic", From 50ef62235b5fa56782bf1d0d20c59945823aea30 Mon Sep 17 00:00:00 2001 From: bartzbeielstein <32470350+bartzbeielstein@users.noreply.github.com> Date: Tue, 2 Jun 2026 03:15:11 +0200 Subject: [PATCH 4/4] ci: pin BLAS/OpenMP threads to 1 to stop xdist oversubscription MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The fast suite took ~52 min on CI while running ~48s locally. Cause: pytest-xdist (-n auto) starts one worker per core, and each worker's numpy/scipy/torch BLAS pool also spawned threads-per-core, so tiny linear-algebra tests thrashed (67-83s for tests that are <1s locally — a ~65x systemic slowdown). Pin OMP/OPENBLAS/MKL/NUMEXPR/VECLIB thread pools to 1: as a top-level env block in ci.yml (guaranteed before any process starts) and via os.environ.setdefault at the top of tests/conftest.py (so local runs and the pre-push hook benefit too, before numpy is imported). This does not affect SpotOptim's own n_jobs/thread-pool parallelism, only native BLAS threads. Co-Authored-By: Claude Opus 4.8 (1M context) --- .github/workflows/ci.yml | 10 ++++++++++ tests/conftest.py | 18 +++++++++++++++++- 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8058efc4..dfa8d14f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,6 +23,16 @@ concurrency: group: ci-${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true +# Pin native BLAS/OpenMP thread pools to 1. pytest-xdist (-n auto) already runs +# one worker per core; without this each worker's numpy/scipy/torch pool spawns +# more threads and they thrash, which made the suite ~50x slower on CI. +env: + OMP_NUM_THREADS: "1" + OPENBLAS_NUM_THREADS: "1" + MKL_NUM_THREADS: "1" + NUMEXPR_NUM_THREADS: "1" + VECLIB_MAXIMUM_THREADS: "1" + jobs: # ── Fast required gate: everything except @pytest.mark.slow ──────────────── test: diff --git a/tests/conftest.py b/tests/conftest.py index 903fe04e..49287118 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -23,7 +23,23 @@ New slow tests may instead be decorated directly with ``@pytest.mark.slow``. """ -import pytest +import os + +# Pin native thread pools to 1 thread per worker BEFORE numpy/scipy/torch are +# imported. pytest-xdist runs one worker per core; without this, each worker's +# BLAS/OpenMP pool spawns more threads and they thrash — on CI this made small +# linear-algebra tests run ~50-80x slower. setdefault keeps any value the +# environment already set (e.g. the CI workflow exports the same vars). +for _thread_var in ( + "OMP_NUM_THREADS", + "OPENBLAS_NUM_THREADS", + "MKL_NUM_THREADS", + "NUMEXPR_NUM_THREADS", + "VECLIB_MAXIMUM_THREADS", +): + os.environ.setdefault(_thread_var, "1") + +import pytest # noqa: E402 — must come after the thread-var setup above # Heaviest tests (>~8 s each), grouped by file. Class/file entries also cover # their faster sibling integration tests, which belong in the full suite too.