From 29e365b7e0df29f1b47e302b02ef8bfd6ece6dbe Mon Sep 17 00:00:00 2001 From: FlyH010 Date: Thu, 1 Oct 2026 18:27:29 +0800 Subject: [PATCH 1/3] Fix:1.truncate Windows notifications to prevent Plyer length errors 2.remove MLflow schema validation warning for Pandera-typed inputs --- confs/training.yaml | 3 +++ src/bikes/io/registries.py | 10 +++++++--- src/bikes/io/services.py | 17 ++++++++++++++--- tests/io/test_registries.py | 29 +++++++++++++++++++++++++++++ tests/io/test_services.py | 15 +++++++++++++++ 5 files changed, 68 insertions(+), 6 deletions(-) diff --git a/confs/training.yaml b/confs/training.yaml index e037286..b110b2d 100644 --- a/confs/training.yaml +++ b/confs/training.yaml @@ -6,3 +6,6 @@ job: targets: KIND: ParquetReader path: data/targets_train.parquet + model: + KIND: BaselineSklearnModel + max_depth: 7 diff --git a/src/bikes/io/registries.py b/src/bikes/io/registries.py index 1c8ab72..86f1646 100644 --- a/src/bikes/io/registries.py +++ b/src/bikes/io/registries.py @@ -6,6 +6,7 @@ import typing as T import mlflow +import pandas as pd import pydantic as pdt from mlflow.pyfunc import PyFuncModel, PythonModel, PythonModelContext @@ -124,20 +125,23 @@ def __init__(self, model: models.Model): def predict( self, context: PythonModelContext, # noqa: ARG002 # required by mlflow PythonModel.predict - model_input: schemas.Inputs, + model_input: pd.DataFrame, params: dict[str, T.Any] | None = None, # noqa: ARG002 # required by mlflow PythonModel.predict ) -> schemas.Outputs: """Generate predictions with a custom model for the given inputs. Args: context (mlflow.PythonModelContext): mlflow context. - model_input (schemas.Inputs): inputs for the mlflow model. + model_input (pd.DataFrame): inputs to validate for the mlflow model. params (dict[str, T.Any] | None): additional parameters. Returns: schemas.Outputs: validated outputs of the project model. """ - return self.model.predict(inputs=model_input) + # MLflow recognizes pandas annotations, but cannot infer a Pandera schema. + # Keep the typed, validated project inputs behind this boundary. + inputs = schemas.InputsSchema.check(data=model_input) + return self.model.predict(inputs=inputs) @T.override def save( diff --git a/src/bikes/io/services.py b/src/bikes/io/services.py index cfcb378..0a1845a 100644 --- a/src/bikes/io/services.py +++ b/src/bikes/io/services.py @@ -101,6 +101,10 @@ class AlertsService(Service): app_name: str = "Bikes" timeout: int | None = None + _MAX_APP_NAME_LENGTH: T.ClassVar[int] = 127 + _MAX_TITLE_LENGTH: T.ClassVar[int] = 63 + _MAX_MESSAGE_LENGTH: T.ClassVar[int] = 255 + @T.override def start(self) -> None: pass @@ -115,9 +119,9 @@ def notify(self, title: str, message: str) -> None: if self.enable: try: notification.notify( - title=title, - message=message, - app_name=self.app_name, + title=self._truncate(title, self._MAX_TITLE_LENGTH), + message=self._truncate(message, self._MAX_MESSAGE_LENGTH), + app_name=self._truncate(self.app_name, self._MAX_APP_NAME_LENGTH), timeout=self.timeout, ) except NotImplementedError: @@ -126,6 +130,13 @@ def notify(self, title: str, message: str) -> None: else: self._print(title=title, message=message) + @staticmethod + def _truncate(value: str, max_length: int) -> str: + """Truncate a notification field to a platform-safe length.""" + if len(value) <= max_length: + return value + return f"{value[: max_length - 1]}\N{HORIZONTAL ELLIPSIS}" + def _print(self, title: str, message: str) -> None: """Print a notification to the system. diff --git a/tests/io/test_registries.py b/tests/io/test_registries.py index 5b1e86a..cb00537 100644 --- a/tests/io/test_registries.py +++ b/tests/io/test_registries.py @@ -1,5 +1,12 @@ # %% IMPORTS +import runpy +import warnings + +import pandas as pd +import pandera.errors as pe +import pytest + from bikes.core import models, schemas from bikes.io import registries, services from bikes.utils import signers @@ -45,6 +52,28 @@ def test_uri_for_model_alias_or_version() -> None: # %% SAVERS/LOADERS/REGISTERS +def test_custom_saver_import_without_type_hint_warning() -> None: + # MLflow inspects predict when the adapter class is defined, before logging. + with warnings.catch_warnings(record=True) as caught: + warnings.simplefilter("always") + runpy.run_path(registries.__file__) + assert not any("Type hint used in the model" in str(warning.message) for warning in caught) + + +def test_custom_saver_adapter_predict(model: models.Model, inputs: schemas.Inputs) -> None: + adapter = registries.CustomSaver.Adapter(model=model) + outputs = adapter.predict(context=None, model_input=pd.DataFrame(inputs)) + pd.testing.assert_frame_equal(outputs, model.predict(inputs=inputs)) + + +def test_custom_saver_adapter_rejects_invalid_inputs(model: models.Model, inputs: schemas.Inputs) -> None: + adapter = registries.CustomSaver.Adapter(model=model) + invalid_inputs = pd.DataFrame(inputs).copy() + invalid_inputs["hr"] = 24 + with pytest.raises(pe.SchemaError, match="less_than_or_equal_to"): + adapter.predict(context=None, model_input=invalid_inputs) + + def test_custom_pipeline( model: models.Model, inputs: schemas.Inputs, diff --git a/tests/io/test_services.py b/tests/io/test_services.py index b211da6..3fc543d 100644 --- a/tests/io/test_services.py +++ b/tests/io/test_services.py @@ -59,6 +59,21 @@ def notify_not_supported(*args, **kwargs): assert "Notifications are not supported on this system." in capsys.readouterr().out +def test_alerts_service__truncates_long_fields(mocker: pm.MockerFixture) -> None: + # given + service = services.AlertsService(enable=True, app_name="a" * 128) + notify = mocker.patch(target="plyer.notification.notify") + # when + service.notify(title="t" * 64, message="m" * 376) + # then + notify.assert_called_once_with( + title=f"{'t' * 62}\N{HORIZONTAL ELLIPSIS}", + message=f"{'m' * 254}\N{HORIZONTAL ELLIPSIS}", + app_name=f"{'a' * 126}\N{HORIZONTAL ELLIPSIS}", + timeout=None, + ) + + def test_mlflow_service(mlflow_service: services.MlflowService) -> None: # given service = mlflow_service From 3445412ff71f29e9df2421ffc5459da95ca61047 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A9d=C3=A9ric=20Hurier=20=28Fmind=29?= Date: Sat, 3 Oct 2026 06:35:43 +0200 Subject: [PATCH 2/3] fix: preserve notification content and validate MLflow inputs --- confs/training.yaml | 3 --- src/bikes/io/registries.py | 4 +-- src/bikes/io/services.py | 20 +++++++++----- tests/io/test_registries.py | 5 ++++ tests/io/test_services.py | 54 ++++++++++++++++++++++++++++++++----- 5 files changed, 69 insertions(+), 17 deletions(-) diff --git a/confs/training.yaml b/confs/training.yaml index b110b2d..e037286 100644 --- a/confs/training.yaml +++ b/confs/training.yaml @@ -6,6 +6,3 @@ job: targets: KIND: ParquetReader path: data/targets_train.parquet - model: - KIND: BaselineSklearnModel - max_depth: 7 diff --git a/src/bikes/io/registries.py b/src/bikes/io/registries.py index 86f1646..08e6857 100644 --- a/src/bikes/io/registries.py +++ b/src/bikes/io/registries.py @@ -138,8 +138,8 @@ def predict( Returns: schemas.Outputs: validated outputs of the project model. """ - # MLflow recognizes pandas annotations, but cannot infer a Pandera schema. - # Keep the typed, validated project inputs behind this boundary. + # MLflow accepts pandas here and validates the logged signature; + # Pandera enforces the project's constraints before the typed model call. inputs = schemas.InputsSchema.check(data=model_input) return self.model.predict(inputs=inputs) diff --git a/src/bikes/io/services.py b/src/bikes/io/services.py index 0a1845a..550f79a 100644 --- a/src/bikes/io/services.py +++ b/src/bikes/io/services.py @@ -101,6 +101,7 @@ class AlertsService(Service): app_name: str = "Bikes" timeout: int | None = None + # Plyer's NOTIFYICONDATAW buffers reserve one UTF-16 unit for the terminator. _MAX_APP_NAME_LENGTH: T.ClassVar[int] = 127 _MAX_TITLE_LENGTH: T.ClassVar[int] = 63 _MAX_MESSAGE_LENGTH: T.ClassVar[int] = 255 @@ -117,11 +118,16 @@ def notify(self, title: str, message: str) -> None: message (str): message of the notification. """ if self.enable: + notify_title, notify_message, app_name = title, message, self.app_name + if sys.platform == "win32": + notify_title = self._truncate(title, self._MAX_TITLE_LENGTH) + notify_message = self._truncate(message, self._MAX_MESSAGE_LENGTH) + app_name = self._truncate(app_name, self._MAX_APP_NAME_LENGTH) try: notification.notify( - title=self._truncate(title, self._MAX_TITLE_LENGTH), - message=self._truncate(message, self._MAX_MESSAGE_LENGTH), - app_name=self._truncate(self.app_name, self._MAX_APP_NAME_LENGTH), + title=notify_title, + message=notify_message, + app_name=app_name, timeout=self.timeout, ) except NotImplementedError: @@ -132,10 +138,12 @@ def notify(self, title: str, message: str) -> None: @staticmethod def _truncate(value: str, max_length: int) -> str: - """Truncate a notification field to a platform-safe length.""" - if len(value) <= max_length: + """Fit a Windows field in UTF-16 units without splitting a surrogate pair.""" + encoded = value.encode("utf-16-le") + if len(encoded) <= max_length * 2: return value - return f"{value[: max_length - 1]}\N{HORIZONTAL ELLIPSIS}" + prefix = encoded[: (max_length - 1) * 2].decode("utf-16-le", errors="ignore") + return f"{prefix}\N{HORIZONTAL ELLIPSIS}" def _print(self, title: str, message: str) -> None: """Print a notification to the system. diff --git a/tests/io/test_registries.py b/tests/io/test_registries.py index cb00537..7b53b01 100644 --- a/tests/io/test_registries.py +++ b/tests/io/test_registries.py @@ -116,6 +116,11 @@ def test_custom_pipeline( ) # - output assert schemas.OutputsSchema.check(outputs) is not None, "Outputs should be valid!" + # The serialized PyFunc must retain Pandera constraints beyond MLflow's column types. + invalid_inputs = inputs.copy() + invalid_inputs.loc[:, "hr"] = 24 + with pytest.raises(pe.SchemaError, match="less_than_or_equal_to"): + adapter.predict(inputs=invalid_inputs) def test_builtin_pipeline( diff --git a/tests/io/test_services.py b/tests/io/test_services.py index 3fc543d..beaa8fa 100644 --- a/tests/io/test_services.py +++ b/tests/io/test_services.py @@ -59,19 +59,61 @@ def notify_not_supported(*args, **kwargs): assert "Notifications are not supported on this system." in capsys.readouterr().out -def test_alerts_service__truncates_long_fields(mocker: pm.MockerFixture) -> None: +@pytest.mark.parametrize( + ("title", "message", "app_name", "expected_title", "expected_message", "expected_app_name"), + [ + ("test", "hello", "Bikes", "test", "hello", "Bikes"), + ("t" * 63, "m" * 255, "a" * 127, "t" * 63, "m" * 255, "a" * 127), + ("t" * 64, "m" * 376, "a" * 128, "t" * 62 + "…", "m" * 254 + "…", "a" * 126 + "…"), + ("😀" * 32, "😀" * 128, "😀" * 64, "😀" * 31 + "…", "😀" * 127 + "…", "😀" * 63 + "…"), + ("t" + "😀" * 32, "m" * 376, "a" * 128, "t" + "😀" * 30 + "…", "m" * 254 + "…", "a" * 126 + "…"), + ], +) +def test_alerts_service__windows_limits( + mocker: pm.MockerFixture, + title: str, + message: str, + app_name: str, + expected_title: str, + expected_message: str, + expected_app_name: str, +) -> None: # given - service = services.AlertsService(enable=True, app_name="a" * 128) + mocker.patch("bikes.io.services.sys.platform", "win32") + service = services.AlertsService(enable=True, app_name=app_name) notify = mocker.patch(target="plyer.notification.notify") # when - service.notify(title="t" * 64, message="m" * 376) + service.notify(title=title, message=message) # then notify.assert_called_once_with( - title=f"{'t' * 62}\N{HORIZONTAL ELLIPSIS}", - message=f"{'m' * 254}\N{HORIZONTAL ELLIPSIS}", - app_name=f"{'a' * 126}\N{HORIZONTAL ELLIPSIS}", + title=expected_title, + message=expected_message, + app_name=expected_app_name, timeout=None, ) + for field, limit in [(expected_title, 63), (expected_message, 255), (expected_app_name, 127)]: + assert len(field.encode("utf-16-le")) // 2 <= limit + + +@pytest.mark.parametrize("platform", ["linux", "darwin"]) +def test_alerts_service__long_fields_on_other_platforms(platform: str, mocker: pm.MockerFixture) -> None: + mocker.patch("bikes.io.services.sys.platform", platform) + service = services.AlertsService(app_name="a" * 128) + notify = mocker.patch("plyer.notification.notify") + service.notify(title="t" * 64, message="m" * 376) + notify.assert_called_once_with(title="t" * 64, message="m" * 376, app_name="a" * 128, timeout=None) + + +@pytest.mark.parametrize("enable", [True, False]) +def test_alerts_service__windows_fallback_keeps_long_fields( + enable: bool, mocker: pm.MockerFixture, capsys: pc.CaptureFixture[str] +) -> None: + mocker.patch("bikes.io.services.sys.platform", "win32") + notify = mocker.patch("plyer.notification.notify", side_effect=NotImplementedError) + service = services.AlertsService(enable=enable, app_name="a" * 128) + service.notify(title="t" * 64, message="m" * 376) + assert f"[{'a' * 128}] {'t' * 64}: {'m' * 376}\n" in capsys.readouterr().out + assert notify.call_count == int(enable) def test_mlflow_service(mlflow_service: services.MlflowService) -> None: From 5867c51208412c78fd5ddfacb8494e29c6b946a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A9d=C3=A9ric=20Hurier=20=28Fmind=29?= Date: Sat, 3 Oct 2026 06:36:17 +0200 Subject: [PATCH 3/3] chore(deps): refresh audited dependencies and pin the toolchain --- .python-version | 2 +- Dockerfile | 2 +- mise.lock | 139 ++++++++++++++++++++++++++++++++++++++++++++++-- mise.toml | 18 ++++--- pyproject.toml | 13 +---- uv.lock | 66 +++++++++++------------ 6 files changed, 181 insertions(+), 59 deletions(-) diff --git a/.python-version b/.python-version index 6324d40..9b8fe2f 100644 --- a/.python-version +++ b/.python-version @@ -1 +1 @@ -3.14 +3.14.8 diff --git a/Dockerfile b/Dockerfile index df48cd7..1309c66 100644 --- a/Dockerfile +++ b/Dockerfile @@ -6,7 +6,7 @@ ENV UV_COMPILE_BYTECODE=1 ENV UV_LINK_MODE=copy WORKDIR /app # Pinned, not `:latest` — a floating tag is invisible to Dependabot's docker ecosystem. -COPY --from=ghcr.io/astral-sh/uv:0.12.3 /uv /uvx /bin/ +COPY --from=ghcr.io/astral-sh/uv:0.12.22 /uv /uvx /bin/ RUN --mount=type=cache,target=/root/.cache/uv \ --mount=type=bind,source=uv.lock,target=uv.lock \ --mount=type=bind,source=pyproject.toml,target=pyproject.toml \ diff --git a/mise.lock b/mise.lock index f50223d..8497b60 100644 --- a/mise.lock +++ b/mise.lock @@ -1,5 +1,7 @@ # @generated - this file is auto-generated by `mise lock` https://mise.jdx.dev/dev-tools/mise-lock.html +lockfile_version = 3 + [[tools.actionlint]] version = "1.7.12" backend = "aqua:rhysd/actionlint" @@ -14,6 +16,126 @@ checksum = "sha256:aba9ced2dee8d27fecca3dc7feb1a7f9a52caefa1eb46f3271ea66b6e0e69 url = "https://github.com/rhysd/actionlint/releases/download/v1.7.12/actionlint_1.7.12_darwin_arm64.tar.gz" url_api = "https://api.github.com/repos/rhysd/actionlint/releases/assets/384924893" +[[tools."aqua:aquasecurity/trivy"]] +version = "0.75.0" +backend = "aqua:aquasecurity/trivy" +specifiers = ["0.75.0"] + +[tools."aqua:aquasecurity/trivy"."platforms.linux-x64"] +checksum = "sha256:c6e65abddb348e25f10549df887045629cf28cc72453cd1c63acb717316b3f3f" +url = "https://github.com/aquasecurity/trivy/releases/download/v0.75.0/trivy_0.75.0_Linux-64bit.tar.gz" +url_api = "https://api.github.com/repos/aquasecurity/trivy/releases/assets/603335432" + +[tools."aqua:aquasecurity/trivy"."platforms.macos-arm64"] +checksum = "sha256:4a77108cccf8e55c8d6823e1e759939a622277e66cd0daa3c1fc621ed69e4568" +url = "https://github.com/aquasecurity/trivy/releases/download/v0.75.0/trivy_0.75.0_macOS-ARM64.tar.gz" +url_api = "https://api.github.com/repos/aquasecurity/trivy/releases/assets/603335435" + +[[tools."aqua:astral-sh/uv"]] +version = "0.12.22" +backend = "aqua:astral-sh/uv" +specifiers = ["0.12.22"] + +[tools."aqua:astral-sh/uv"."platforms.linux-x64"] +checksum = "sha256:b9980552309f09c15172b8be828555e375097f16deb459795ce7bfd200380f0b" +url = "https://github.com/astral-sh/uv/releases/download/0.12.22/uv-x86_64-unknown-linux-gnu.tar.gz" +url_api = "https://api.github.com/repos/astral-sh/uv/releases/assets/604418465" + +[tools."aqua:astral-sh/uv"."platforms.macos-arm64"] +checksum = "sha256:5d714de09501a59393ceca78f4bc232a50478729640d251907160299b2a93ddd" +url = "https://github.com/astral-sh/uv/releases/download/0.12.22/uv-aarch64-apple-darwin.tar.gz" +url_api = "https://api.github.com/repos/astral-sh/uv/releases/assets/604418308" + +[[tools."aqua:dprint/dprint"]] +version = "0.58.0" +backend = "aqua:dprint/dprint" +specifiers = ["0.58.0"] + +[tools."aqua:dprint/dprint"."platforms.linux-x64"] +checksum = "sha256:3047dcabe684fe32868d1959f7fad4ccbc2996a393d639bbb6634668fa08dc49" +url = "https://github.com/dprint/dprint/releases/download/0.58.0/dprint-x86_64-unknown-linux-gnu.zip" +url_api = "https://api.github.com/repos/dprint/dprint/releases/assets/598660522" + +[tools."aqua:dprint/dprint"."platforms.macos-arm64"] +checksum = "sha256:06a7ea017e4d7da296f8a44fabb2f3d6ffafa91bbd5fddd4830bdc418bacb46d" +url = "https://github.com/dprint/dprint/releases/download/0.58.0/dprint-aarch64-apple-darwin.zip" +url_api = "https://api.github.com/repos/dprint/dprint/releases/assets/598660526" + +[[tools."aqua:gitleaks/gitleaks"]] +version = "8.30.1" +backend = "aqua:gitleaks/gitleaks" +specifiers = ["8.30.1"] + +[tools."aqua:gitleaks/gitleaks"."platforms.linux-x64"] +checksum = "sha256:551f6fc83ea457d62a0d98237cbad105af8d557003051f41f3e7ca7b3f2470eb" +url = "https://github.com/gitleaks/gitleaks/releases/download/v8.30.1/gitleaks_8.30.1_linux_x64.tar.gz" +url_api = "https://api.github.com/repos/gitleaks/gitleaks/releases/assets/378332058" + +[tools."aqua:gitleaks/gitleaks"."platforms.macos-arm64"] +checksum = "sha256:b40ab0ae55c505963e365f271a8d3846efbc170aa17f2607f13df610a9aeb6a5" +url = "https://github.com/gitleaks/gitleaks/releases/download/v8.30.1/gitleaks_8.30.1_darwin_arm64.tar.gz" +url_api = "https://api.github.com/repos/gitleaks/gitleaks/releases/assets/378332059" + +[[tools."aqua:hadolint/hadolint"]] +version = "2.15.1" +backend = "aqua:hadolint/hadolint" +specifiers = ["2.15.1"] + +[tools."aqua:hadolint/hadolint"."platforms.linux-x64"] +checksum = "sha256:c7187db94eeeeca956519a6af171adc31453941a1e777961f6e680f697c8c507" +url = "https://github.com/hadolint/hadolint/releases/download/v2.15.1/hadolint-linux-x86_64" +url_api = "https://api.github.com/repos/hadolint/hadolint/releases/assets/496546506" + +[tools."aqua:hadolint/hadolint"."platforms.macos-arm64"] +checksum = "sha256:5c09f3213f8e40406abe048233d985eebef336d4a6a20021be47fadb6cf480a2" +url = "https://github.com/hadolint/hadolint/releases/download/v2.15.1/hadolint-macos-arm64" +url_api = "https://api.github.com/repos/hadolint/hadolint/releases/assets/496546505" + +[[tools."aqua:orhun/git-cliff"]] +version = "2.14.2" +backend = "aqua:orhun/git-cliff" +specifiers = ["2.14.2"] + +[tools."aqua:orhun/git-cliff"."platforms.linux-x64"] +checksum = "sha256:24f397c733add5390fdceee3a2088588ab0d5f944ce00d34cb7029b888cf2db4" +url = "https://github.com/orhun/git-cliff/releases/download/v2.14.2/git-cliff-2.14.2-x86_64-unknown-linux-gnu.tar.gz" +url_api = "https://api.github.com/repos/orhun/git-cliff/releases/assets/573483509" + +[tools."aqua:orhun/git-cliff"."platforms.macos-arm64"] +checksum = "sha256:a0be97e237d440d34c5e508ec21d070b2c85cbb8be48a46a56f9a4e8bd247f05" +url = "https://github.com/orhun/git-cliff/releases/download/v2.14.2/git-cliff-2.14.2-aarch64-apple-darwin.tar.gz" +url_api = "https://api.github.com/repos/orhun/git-cliff/releases/assets/573487203" + +[[tools."aqua:rhysd/actionlint"]] +version = "1.7.12" +backend = "aqua:rhysd/actionlint" +specifiers = ["1.7.12"] + +[tools."aqua:rhysd/actionlint"."platforms.linux-x64"] +checksum = "sha256:8aca8db96f1b94770f1b0d72b6dddcb1ebb8123cb3712530b08cc387b349a3d8" +url = "https://github.com/rhysd/actionlint/releases/download/v1.7.12/actionlint_1.7.12_linux_amd64.tar.gz" +url_api = "https://api.github.com/repos/rhysd/actionlint/releases/assets/384924896" + +[tools."aqua:rhysd/actionlint"."platforms.macos-arm64"] +checksum = "sha256:aba9ced2dee8d27fecca3dc7feb1a7f9a52caefa1eb46f3271ea66b6e0e6953f" +url = "https://github.com/rhysd/actionlint/releases/download/v1.7.12/actionlint_1.7.12_darwin_arm64.tar.gz" +url_api = "https://api.github.com/repos/rhysd/actionlint/releases/assets/384924893" + +[[tools."aqua:zizmorcore/zizmor"]] +version = "1.30.1" +backend = "aqua:zizmorcore/zizmor" +specifiers = ["1.30.1"] + +[tools."aqua:zizmorcore/zizmor"."platforms.linux-x64"] +checksum = "sha256:e65324f4430c2717591937edcec90ccbefaf14c174f8ec9415e03ca875b46e1a" +url = "https://github.com/zizmorcore/zizmor/releases/download/v1.30.1/zizmor-x86_64-unknown-linux-gnu.tar.gz" +url_api = "https://api.github.com/repos/zizmorcore/zizmor/releases/assets/552067642" + +[tools."aqua:zizmorcore/zizmor"."platforms.macos-arm64"] +checksum = "sha256:e28d22b087f9ebb8d99da6e740d348c930f559961c7c3f12badda54f882195a2" +url = "https://github.com/zizmorcore/zizmor/releases/download/v1.30.1/zizmor-aarch64-apple-darwin.tar.gz" +url_api = "https://api.github.com/repos/zizmorcore/zizmor/releases/assets/552067640" + [[tools.dprint]] version = "0.55.2" backend = "aqua:dprint/dprint" @@ -70,6 +192,19 @@ checksum = "sha256:5c09f3213f8e40406abe048233d985eebef336d4a6a20021be47fadb6cf48 url = "https://github.com/hadolint/hadolint/releases/download/v2.15.1/hadolint-macos-arm64" url_api = "https://api.github.com/repos/hadolint/hadolint/releases/assets/496546505" +[[tools.python]] +version = "3.14.8" +backend = "core:python" +specifiers = ["3.14.8"] + +[tools.python."platforms.linux-x64"] +checksum = "sha256:b373a4a4e4e70fc05f368c9b53d7738bf37637682b650d96c742805d2da26c32" +url = "https://github.com/astral-sh/python-build-standalone/releases/download/20261001/cpython-3.14.8+20261001-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz" + +[tools.python."platforms.macos-arm64"] +checksum = "sha256:69e48cd7f54261df5b6abbd374f69fe6e496fa781d271592aeef49cd5ffbea6c" +url = "https://github.com/astral-sh/python-build-standalone/releases/download/20261001/cpython-3.14.8+20261001-aarch64-apple-darwin-install_only_stripped.tar.gz" + [[tools.trivy]] version = "0.73.0" backend = "aqua:aquasecurity/trivy" @@ -84,10 +219,6 @@ checksum = "sha256:80cc25faaf6378e37701202d0b4f9f43d9e413d198d594ba60fdf559fe44a url = "https://github.com/aquasecurity/trivy/releases/download/v0.73.0/trivy_0.73.0_macOS-ARM64.tar.gz" url_api = "https://api.github.com/repos/aquasecurity/trivy/releases/assets/499939985" -[[tools.uv]] -version = "0.12.3" -backend = "asdf:uv" - [[tools.zizmor]] version = "1.29.0" backend = "aqua:zizmorcore/zizmor" diff --git a/mise.toml b/mise.toml index 00f8c76..2a5a6ed 100644 --- a/mise.toml +++ b/mise.toml @@ -2,20 +2,22 @@ # Canonical task vocabulary shared by git hooks (lefthook) and CI (GitHub Actions). [env] +UV_LOCKED = "1" _.source = ".env" [settings.task] run_auto_install = false [tools] -actionlint = "latest" -dprint = "latest" -git-cliff = "latest" -gitleaks = "latest" -hadolint = "latest" -trivy = "latest" -uv = "latest" -zizmor = "latest" +python = "3.14.8" +"aqua:rhysd/actionlint" = "1.7.12" +"aqua:dprint/dprint" = "0.58.0" +"aqua:orhun/git-cliff" = "2.14.2" +"aqua:gitleaks/gitleaks" = "8.30.1" +"aqua:hadolint/hadolint" = "2.15.1" +"aqua:aquasecurity/trivy" = "0.75.0" +"aqua:astral-sh/uv" = "0.12.22" +"aqua:zizmorcore/zizmor" = "1.30.1" # ALL diff --git a/pyproject.toml b/pyproject.toml index b4253d2..9d35878 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -16,13 +16,13 @@ requires-python = ">=3.14" dependencies = [ "loguru>=0.7.3", "matplotlib>=3.11.0", - "mlflow>=3.14.0", + "mlflow>=3.16.1", # numba/numpy/pyarrow/shap are ABI-coupled: keep permissive floors so uv's universal # (all-platform) resolution stays satisfiable; the lockfile pins the latest tested versions. "numba>=0.61.0", "numpy>=2.1.3", "omegaconf>=2.3.1", - "pandas>=2.3.3", # MLflow 3.x pins pandas<3; pandas 3.0 is held back by MLflow compat + "pandas>=2.3.3", "pandera>=0.32.1", "plotly>=6.8.0", "plyer>=2.1.0", @@ -69,15 +69,6 @@ dev = [ ] notebook = ["ipykernel>=6.29.5", "nbformat>=5.10.4"] -# OVERRIDES - -[tool.uv] -# MLflow 3.15 still declares `cryptography<50`, but the fix for PYSEC-2026-3552 -# (PKCS#7 Bleichenbacher oracle) only ships in 50.0.0. Overriding the stale upper -# bound installs the patched library; the test suite is what proves MLflow still -# works with it. Drop this override once MLflow relaxes the constraint upstream. -override-dependencies = ["cryptography>=50"] - # TOOLS [tool.ruff] diff --git a/uv.lock b/uv.lock index ffc9894..8f5998a 100644 --- a/uv.lock +++ b/uv.lock @@ -1,14 +1,11 @@ version = 1 -revision = 3 +revision = 5 requires-python = ">=3.14" resolution-markers = [ "platform_machine == 'x86_64' and sys_platform == 'darwin'", "platform_machine != 'x86_64' or sys_platform != 'darwin'", ] -[manifest] -overrides = [{ name = "cryptography", specifier = ">=50" }] - [[package]] name = "aiohappyeyeballs" version = "2.7.1" @@ -213,7 +210,7 @@ notebook = [ requires-dist = [ { name = "loguru", specifier = ">=0.7.3" }, { name = "matplotlib", specifier = ">=3.11.0" }, - { name = "mlflow", specifier = ">=3.14.0" }, + { name = "mlflow", specifier = ">=3.16.1" }, { name = "numba", specifier = ">=0.61.0" }, { name = "numpy", specifier = ">=2.1.3" }, { name = "omegaconf", specifier = ">=2.3.1" }, @@ -825,14 +822,14 @@ wheels = [ [[package]] name = "gitpython" -version = "3.1.59" +version = "3.1.60" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "gitdb" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ca/dc/126b28e76b24a9268ba931ad3e012f71ebdadf62fd9f17758f7074bb0b20/gitpython-3.1.59.tar.gz", hash = "sha256:0a1475cfdc38a5bfba1a3e9a4a9da52a39749ecec322b772915c019f94e5b7e4", size = 230445, upload-time = "2026-08-10T12:03:20.271Z" } +sdist = { url = "https://files.pythonhosted.org/packages/84/14/e6b1a48d831755a53c2029351fcef82e70db4a08f338daefe29d8d0cf31c/gitpython-3.1.60.tar.gz", hash = "sha256:e936431879fa85581b4311fa63492ea52251909e2d655b6529c704c904ddcc24", size = 230793, upload-time = "2026-08-25T18:33:46.102Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ef/ed/ae57eb7d344f43f87b74b3a281ead6ec7d6394eef72a7b1dcb28dd089550/gitpython-3.1.59-py3-none-any.whl", hash = "sha256:67a82f537384578643624c8b2c531938a9b82be431663e575dcf638526631d4c", size = 220996, upload-time = "2026-08-10T12:03:18.804Z" }, + { url = "https://files.pythonhosted.org/packages/71/63/ba28697918b7c190af9f3f21940d03e8814e25dd4ddd39d6929f3a553995/gitpython-3.1.60-py3-none-any.whl", hash = "sha256:39548bffb8fa0f3a548133348868bb4838e79d73283052207dc97781a569b6b4", size = 221893, upload-time = "2026-08-25T18:33:44.75Z" }, ] [[package]] @@ -1350,7 +1347,7 @@ wheels = [ [[package]] name = "mlflow" -version = "3.15.1" +version = "3.16.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "aiohttp" }, @@ -1374,16 +1371,17 @@ dependencies = [ { name = "sqlalchemy" }, { name = "waitress", marker = "sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/51/01/aff6c6e1ee571769688e926d2cf55ae9ddd300f95541905a0a82ea1599c7/mlflow-3.15.1.tar.gz", hash = "sha256:6177b02c442d7d6ab3ad752daa826db9cf993e255450fb4b00ea8b7b5539c01d", size = 10396741, upload-time = "2026-08-03T09:29:20.073Z" } +sdist = { url = "https://files.pythonhosted.org/packages/65/30/a7105a50760f31aa5a6514e18d32e9087011c1bcb4ad56e0cfcc1e356f8c/mlflow-3.16.1.tar.gz", hash = "sha256:044bd17d49ed216d3e38a537436622154daa5197ea41c1f3dedf155cbd8f40c0", size = 10830822, upload-time = "2026-09-16T23:14:20.369Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5a/ff/24e530a328b3b2c9d004e94cdd59ea5ec9c052c4935799f675d7bb1f1d2c/mlflow-3.15.1-py3-none-any.whl", hash = "sha256:c91f78d304d8ef825869ea07e638c73a2850660f0d9f098993bacecc359f8a75", size = 11189154, upload-time = "2026-08-03T09:29:17.718Z" }, + { url = "https://files.pythonhosted.org/packages/2a/37/5a0bffe04625c906be3c3482915ba0b9d824c48c53f8963cfdcfb9251547/mlflow-3.16.1-py3-none-any.whl", hash = "sha256:e4dfe69ceae31dfc7b8480e76ffe6a7f36752bdfac30e058e6430ade1fb5920b", size = 11650889, upload-time = "2026-09-16T23:14:16.557Z" }, ] [[package]] name = "mlflow-skinny" -version = "3.15.1" +version = "3.16.1" source = { registry = "https://pypi.org/simple" } dependencies = [ + { name = "anyio" }, { name = "cachetools" }, { name = "click" }, { name = "cloudpickle" }, @@ -1405,14 +1403,14 @@ dependencies = [ { name = "typing-extensions" }, { name = "uvicorn" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/7e/d7/f71b2607ef378b8481a1cea08463a6025b9ca831b4607afc16c75df57872/mlflow_skinny-3.15.1.tar.gz", hash = "sha256:8c53c85bf0fe6e9ba8aa72f6b8675c774d1ff55b00735a5cd33a510803905237", size = 3035284, upload-time = "2026-08-03T09:29:35.266Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f6/21/5b8e556d3fc3030affe5d6b3ecf58d9dcc972c644ebf941d77bf8b4a5769/mlflow_skinny-3.16.1.tar.gz", hash = "sha256:7298b47942b1b0e826e02191653b8c203c3bb242fb699f8beec98e928dbec115", size = 3193879, upload-time = "2026-09-16T22:08:20.298Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f3/9e/4e8138583321f6dfbaa35360f46056b4ecc06d505d130c03a5d81aec5620/mlflow_skinny-3.15.1-py3-none-any.whl", hash = "sha256:b62056806d0afc425e8948233a3646b0b3af504702ff3b334b6395f48b22b832", size = 3613028, upload-time = "2026-08-03T09:29:33.54Z" }, + { url = "https://files.pythonhosted.org/packages/8a/f1/89f8a4386ddde208806b57f6dc8e89138e0b92ae667c0dfc5fc2d8a4d418/mlflow_skinny-3.16.1-py3-none-any.whl", hash = "sha256:41ec1697710b91d001f8951bdc2d00391a277065ed82268c81706238fa2b918e", size = 3797284, upload-time = "2026-09-16T22:08:18.176Z" }, ] [[package]] name = "mlflow-tracing" -version = "3.15.1" +version = "3.16.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cachetools" }, @@ -1424,9 +1422,9 @@ dependencies = [ { name = "protobuf" }, { name = "pydantic" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a5/a6/76964d8f265be984838a13b77656b047686c492b61b3f46ca2ef29ef7325/mlflow_tracing-3.15.1.tar.gz", hash = "sha256:ba1c4d873151c0ceeab37f53daf0997feea18a1c092358995072dc53e1944a29", size = 1501188, upload-time = "2026-08-03T09:26:15.541Z" } +sdist = { url = "https://files.pythonhosted.org/packages/88/62/64068c0d17a7ba479f4dadfa78b3e48921cde8d20878414e7a4a929e66c0/mlflow_tracing-3.16.1.tar.gz", hash = "sha256:315ba1ace00d43498da2ad2b2bbb751f3198d6f9cd328168dfeb49911df41925", size = 1525331, upload-time = "2026-09-16T22:40:58.167Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f1/c7/13db54bd1b6525a0f09d880260d9dcf8072ae89155dae7a74c9e75a1ba03/mlflow_tracing-3.15.1-py3-none-any.whl", hash = "sha256:3cdef1f1675fe2c7c9f409e132439d65b063e37455b338027bfb2c0f986bebca", size = 1785591, upload-time = "2026-08-03T09:26:13.511Z" }, + { url = "https://files.pythonhosted.org/packages/00/8c/f40c01433254f49f3b18ef88a4eba481a73a29494f27c072fb7a53812a76/mlflow_tracing-3.16.1-py3-none-any.whl", hash = "sha256:b3ab97c7b4f919704e0cbe0653e6e9b89cf1f8a790f8bb1b4cf7f236df258e9e", size = 1812478, upload-time = "2026-09-16T22:40:56.332Z" }, ] [[package]] @@ -2686,11 +2684,11 @@ wheels = [ [[package]] name = "sqlparse" -version = "0.5.5" +version = "0.6.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/90/76/437d71068094df0726366574cf3432a4ed754217b436eb7429415cf2d480/sqlparse-0.5.5.tar.gz", hash = "sha256:e20d4a9b0b8585fdf63b10d30066c7c94c5d7a7ec47c889a2d83a3caa93ff28e", size = 120815, upload-time = "2025-12-19T07:17:45.073Z" } +sdist = { url = "https://files.pythonhosted.org/packages/5f/d3/3f06a1006f2261d1342aefb3c71eed02f5d4ca5bdbecd86ebc12ad38306e/sqlparse-0.6.0.tar.gz", hash = "sha256:113c35c75365ab9cc9c7231d68c6428fb11c085fc8e9eb1ad659b7ddbf6cd2b9", size = 178477, upload-time = "2026-08-13T19:16:06.396Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/49/4b/359f28a903c13438ef59ebeee215fb25da53066db67b305c125f1c6d2a25/sqlparse-0.5.5-py3-none-any.whl", hash = "sha256:12a08b3bf3eec877c519589833aed092e2444e68240a3577e8e26148acc7b1ba", size = 46138, upload-time = "2025-12-19T07:17:46.573Z" }, + { url = "https://files.pythonhosted.org/packages/d9/50/f00935da0ec7cbf325f8dc4f772ae46fbc7b672dd62876e73f0a94adda57/sqlparse-0.6.0-py3-none-any.whl", hash = "sha256:b861c0288ce2fa56209a9a6412d2e066ac664b3873b89c26c9d8415e8e32996f", size = 50070, upload-time = "2026-08-13T19:16:04.062Z" }, ] [[package]] @@ -2766,19 +2764,19 @@ wheels = [ [[package]] name = "tornado" -version = "6.5.8" +version = "6.5.10" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/10/d3/343e5bb989d6515b1646cf3d40135d73f3d5e45339bded401b56cdac24dd/tornado-6.5.8.tar.gz", hash = "sha256:9452e1b208a8bd771e2cb1f2ff564985b9b214bdebbe622793e1799e0a6bd23f", size = 520493, upload-time = "2026-08-07T02:12:42.971Z" } +sdist = { url = "https://files.pythonhosted.org/packages/06/61/53d562a57b28c08eda40b258c0f975e360541943ad7c7bef897a40caafda/tornado-6.5.10.tar.gz", hash = "sha256:a6b1ccd08c04b4a06fb5aeb381be99de5ad1e5375c1785e31d78c880feb57687", size = 537910, upload-time = "2026-09-15T13:47:48.73Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f2/d5/007086fd8df5489338e204f65adce33fd4f21a4999dbb2b9cff2f897b5f4/tornado-6.5.8-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:cc6aa787d7cfab7c3d35189dc7a56fbd2399a569624c730c6b55b3d6531d0403", size = 449487, upload-time = "2026-08-07T02:12:28.682Z" }, - { url = "https://files.pythonhosted.org/packages/70/c8/5a24a99495903f594f6a199dd7beead1cbc0a13e2cb9102727bcaaf2a997/tornado-6.5.8-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:9715b5eb79735b2bcd454ce216a9275b7c0470e64ea1bf5742f78b2f72b26eeb", size = 447649, upload-time = "2026-08-07T02:12:30.306Z" }, - { url = "https://files.pythonhosted.org/packages/6e/de/f2e733f386b85962d1b1dc82cd63d169b5b4580062b35397eac9244a41fe/tornado-6.5.8-cp39-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:547d63f450d570c14fe0e8db2cfb14c9bbd1c2503b4a6612586267955aa47b58", size = 450707, upload-time = "2026-08-07T02:12:31.95Z" }, - { url = "https://files.pythonhosted.org/packages/0b/94/20efeee9a01c141e9ac47c397f81679dfda24b32768fc4fff24e76d36c2c/tornado-6.5.8-cp39-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7e2360a0ffbe145eca8af0b19cb7203d79b1a98dd4cccdd6b368f6f49c2e3808", size = 451677, upload-time = "2026-08-07T02:12:33.512Z" }, - { url = "https://files.pythonhosted.org/packages/42/ec/a96ccb8ccf0de2b7bc2c5fa1608a4803735018242e90c4882365a9fd418f/tornado-6.5.8-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:5d242290bdf7ab3151bc1065fdd75c0dcc21cbc7b49f22a4c56329c2d6566d22", size = 451510, upload-time = "2026-08-07T02:12:35.346Z" }, - { url = "https://files.pythonhosted.org/packages/29/b5/93185859245ad3f00e62175f29607346788b696369347f0146e0421286bb/tornado-6.5.8-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:7b94ff0e128fe0542f3bd331fb44d06260fc4ac16881545159f34ef08aad4195", size = 450917, upload-time = "2026-08-07T02:12:36.963Z" }, - { url = "https://files.pythonhosted.org/packages/97/cf/fe33cf062834487d34d1559746a4a12521033c22645b6d74d4bca702e018/tornado-6.5.8-cp39-abi3-win32.whl", hash = "sha256:67832909c4779c64942380cb5f044a5c6163d00831472d80e25e115de9917836", size = 451952, upload-time = "2026-08-07T02:12:38.512Z" }, - { url = "https://files.pythonhosted.org/packages/cb/e1/468ad54333e92ccb62627e62cb88e5fc14a2171daa67ed47b1b8542d5b86/tornado-6.5.8-cp39-abi3-win_amd64.whl", hash = "sha256:11881db6b7c168494be2c2d12e65931451bdf7ee718535418ae1d8855dd5a0ee", size = 452391, upload-time = "2026-08-07T02:12:39.971Z" }, - { url = "https://files.pythonhosted.org/packages/ad/3e/cd5e4f06e34cde33b8ef66cf36aa2b5ad46354cc1af7d2136bbe365fee1d/tornado-6.5.8-cp39-abi3-win_arm64.whl", hash = "sha256:68a7468c7e289f8514d7d664101753903217eff1bb6822c6b5994a0b5f5bcb26", size = 451411, upload-time = "2026-08-07T02:12:41.469Z" }, + { url = "https://files.pythonhosted.org/packages/cd/5b/ff5fc58fa2427c30dea74c90053f4fc5eda1e7f3833ed3ecc7147fe2b311/tornado-6.5.10-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:9261783640e23258694a9ff0795df430a5a7b0a651d3dd53dd0969ad6be16da7", size = 465883, upload-time = "2026-09-15T13:47:35.463Z" }, + { url = "https://files.pythonhosted.org/packages/ad/f5/cd7be26c34a3315532f3aef5f092465da8f59c334dd439d3c14aaef16461/tornado-6.5.10-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:83e6cf438b106c6b3852d70960967bb1b70c87438050dca0981e4b9aa751a4c1", size = 464046, upload-time = "2026-09-15T13:47:37.178Z" }, + { url = "https://files.pythonhosted.org/packages/60/33/df6d7d04854a58619f8349a51e3edb138324130a7562b0bb21f115bb940f/tornado-6.5.10-cp39-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:bdf942448169e5336451d0494d7e3d81cfa726d5aa312affdc4682dd62a62f6d", size = 467096, upload-time = "2026-09-15T13:47:38.559Z" }, + { url = "https://files.pythonhosted.org/packages/29/17/cc35dff68272d685cffd8600ffafbd8067e7d05e7348d9f80caddffbbd5f/tornado-6.5.10-cp39-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:69acca6501eed74582b76dbbceee2a91613f54728e3e418346000d7103101676", size = 468067, upload-time = "2026-09-15T13:47:40.085Z" }, + { url = "https://files.pythonhosted.org/packages/c3/01/6e5349b4e1a53a4b4972a6716785e1fe7407f312063c3972690af8ff301b/tornado-6.5.10-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:66aaa3f57d30c6e6becee83ff28055d5930ac724214bde99393eefda83d5e015", size = 467901, upload-time = "2026-09-15T13:47:41.576Z" }, + { url = "https://files.pythonhosted.org/packages/28/5e/b4facf94370dba006819c8d304376f8b9fbec6b935b5e51bf45823a9790b/tornado-6.5.10-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:4bd192b959f9128fb99b8898148070ba4574c9589b78bce42d1851131fe85828", size = 467308, upload-time = "2026-09-15T13:47:43.145Z" }, + { url = "https://files.pythonhosted.org/packages/56/ae/047938e828cafc8eca4c908fafb6588fee944e3af39a0af9d7b602499ae5/tornado-6.5.10-cp39-abi3-win32.whl", hash = "sha256:302eb1e0e3e159314eb591920529fdea80acca92df5510a2cec5bbd4f099ec72", size = 468387, upload-time = "2026-09-15T13:47:44.556Z" }, + { url = "https://files.pythonhosted.org/packages/d8/d4/5901517f05affd752490f6a654ba31b7474664e8dd80bd045a00c220bd88/tornado-6.5.10-cp39-abi3-win_amd64.whl", hash = "sha256:37ae8f150cecfdbf747fc4e12f5e9a97ecd8cf1d4cdb3f119e2de84b11196918", size = 468828, upload-time = "2026-09-15T13:47:45.961Z" }, + { url = "https://files.pythonhosted.org/packages/f3/1a/fd497f3a7f7b74bb04f4b94536b5c9f80742b5d50501fd27977652ddec16/tornado-6.5.10-cp39-abi3-win_arm64.whl", hash = "sha256:ce045d3c298fddd30e89a2777f97039d1b641eb9518ac7b26a4721903539c694", size = 467847, upload-time = "2026-09-15T13:47:47.283Z" }, ] [[package]] @@ -2884,11 +2882,11 @@ wheels = [ [[package]] name = "urllib3" -version = "2.7.0" +version = "2.8.0" source = { registry = "https://pypi.org/simple" } -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" } +sdist = { url = "https://files.pythonhosted.org/packages/e3/05/b17359e1cefb4f909b5e40b1b90a496d987258916dbbf88e842c729f510e/urllib3-2.8.0.tar.gz", hash = "sha256:63bf2ead4c879426ebf22ef2a781eeb4aa3b4ae798a0435506f8687fd5bb9b63", size = 458972, upload-time = "2026-09-15T19:29:36.253Z" } wheels = [ - { 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" }, + { url = "https://files.pythonhosted.org/packages/92/9d/c4e665119135114480843e7ab388fa94d8480650450e6f8e26b70d323a4c/urllib3-2.8.0-py3-none-any.whl", hash = "sha256:0cf3cae568d36aa9576b28dfb35f11328f1cb974ca7647d9475ebb86c75ac6e3", size = 135717, upload-time = "2026-09-15T19:29:34.577Z" }, ] [[package]]