diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ad3fe64a2..07ada9b80 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -6,12 +6,15 @@ on: - main - develop - 'release-**' + pull_request: + paths: + - Dockerfile jobs: build: strategy: matrix: - device: [maixpy_m5stickv, maixpy_amigo, maixpy_dock, maixpy_yahboom, maixpy_cube, maixpy_wonder_mv, maixpy_tzt, maixpy_embed_fire, maixpy_wonder_k] + device: ${{ fromJSON(github.event_name == 'pull_request' && '["maixpy_m5stickv"]' || '["maixpy_m5stickv","maixpy_amigo","maixpy_dock","maixpy_yahboom","maixpy_cube","maixpy_wonder_mv","maixpy_tzt","maixpy_embed_fire","maixpy_wonder_k"]') }} runs-on: ubuntu-latest services: registry: @@ -26,15 +29,15 @@ jobs: sudo rm -rf "/usr/local/share/boost" sudo rm -rf "$AGENT_TOOLSDIRECTORY" - name: Checkout - uses: actions/checkout@v7 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 with: submodules: recursive - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v4 + uses: docker/setup-buildx-action@f87e5991a6d7451dcb8d9637bfbc97413f497069 # v4 with: driver-opts: network=host - name: Cache Docker layers - uses: actions/cache@v6 + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6 with: path: /tmp/.buildx-cache key: ${{ runner.os }}-buildx-${{ github.sha }} @@ -43,7 +46,7 @@ jobs: - name: Build for M5StickV if: matrix.device == 'maixpy_m5stickv' - uses: docker/build-push-action@v7 + uses: docker/build-push-action@c3c9e263c25d99ce0380d002d59b67737d91b0dc # v7 with: context: . build-args: DEVICE=maixpy_m5stickv @@ -54,20 +57,20 @@ jobs: - name: Extract firmware for M5StickV id: extract-m5stickv if: matrix.device == 'maixpy_m5stickv' - uses: shrink/actions-docker-extract@v4 + uses: shrink/actions-docker-extract@f1ef61065b78731fe3310b4e84e511f6a927a77e # v4 with: image: localhost:5000/selfcustody/krux-builder-m5stickv:latest path: /src/firmware/Kboot/build/. - name: Upload firmware for M5StickV if: matrix.device == 'maixpy_m5stickv' - uses: actions/upload-artifact@v7 + uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7 with: path: ${{ steps.extract-m5stickv.outputs.destination }} name: build-m5stickv - name: Build for Amigo if: matrix.device == 'maixpy_amigo' - uses: docker/build-push-action@v7 + uses: docker/build-push-action@c3c9e263c25d99ce0380d002d59b67737d91b0dc # v7 with: context: . build-args: DEVICE=maixpy_amigo @@ -78,20 +81,20 @@ jobs: - name: Extract firmware for Amigo id: extract-amigo if: matrix.device == 'maixpy_amigo' - uses: shrink/actions-docker-extract@v4 + uses: shrink/actions-docker-extract@f1ef61065b78731fe3310b4e84e511f6a927a77e # v4 with: image: localhost:5000/selfcustody/krux-builder-amigo:latest path: /src/firmware/Kboot/build/. - name: Upload firmware for Amigo if: matrix.device == 'maixpy_amigo' - uses: actions/upload-artifact@v7 + uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7 with: path: ${{ steps.extract-amigo.outputs.destination }} name: build-amigo - name: Build for Dock if: matrix.device == 'maixpy_dock' - uses: docker/build-push-action@v7 + uses: docker/build-push-action@c3c9e263c25d99ce0380d002d59b67737d91b0dc # v7 with: context: . build-args: DEVICE=maixpy_dock @@ -102,20 +105,20 @@ jobs: - name: Extract firmware for Dock id: extract-dock if: matrix.device == 'maixpy_dock' - uses: shrink/actions-docker-extract@v4 + uses: shrink/actions-docker-extract@f1ef61065b78731fe3310b4e84e511f6a927a77e # v4 with: image: localhost:5000/selfcustody/krux-builder-dock:latest path: /src/firmware/Kboot/build/. - name: Upload firmware for Dock if: matrix.device == 'maixpy_dock' - uses: actions/upload-artifact@v7 + uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7 with: path: ${{ steps.extract-dock.outputs.destination }} name: build-dock - name: Build for Yahboom if: matrix.device == 'maixpy_yahboom' - uses: docker/build-push-action@v7 + uses: docker/build-push-action@c3c9e263c25d99ce0380d002d59b67737d91b0dc # v7 with: context: . build-args: DEVICE=maixpy_yahboom @@ -126,20 +129,20 @@ jobs: - name: Extract firmware for Yahboom id: extract-yahboom if: matrix.device == 'maixpy_yahboom' - uses: shrink/actions-docker-extract@v4 + uses: shrink/actions-docker-extract@f1ef61065b78731fe3310b4e84e511f6a927a77e # v4 with: image: localhost:5000/selfcustody/krux-builder-yahboom:latest path: /src/firmware/Kboot/build/. - name: Upload firmware for Yahboom if: matrix.device == 'maixpy_yahboom' - uses: actions/upload-artifact@v7 + uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7 with: path: ${{ steps.extract-yahboom.outputs.destination }} name: build-yahboom - name: Build for Cube if: matrix.device == 'maixpy_cube' - uses: docker/build-push-action@v7 + uses: docker/build-push-action@c3c9e263c25d99ce0380d002d59b67737d91b0dc # v7 with: context: . build-args: DEVICE=maixpy_cube @@ -150,20 +153,20 @@ jobs: - name: Extract firmware for Cube id: extract-cube if: matrix.device == 'maixpy_cube' - uses: shrink/actions-docker-extract@v4 + uses: shrink/actions-docker-extract@f1ef61065b78731fe3310b4e84e511f6a927a77e # v4 with: image: localhost:5000/selfcustody/krux-builder-cube:latest path: /src/firmware/Kboot/build/. - name: Upload firmware for Cube if: matrix.device == 'maixpy_cube' - uses: actions/upload-artifact@v7 + uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7 with: path: ${{ steps.extract-cube.outputs.destination }} name: build-cube - name: Build for WonderMV if: matrix.device == 'maixpy_wonder_mv' - uses: docker/build-push-action@v7 + uses: docker/build-push-action@c3c9e263c25d99ce0380d002d59b67737d91b0dc # v7 with: context: . build-args: DEVICE=maixpy_wonder_mv @@ -174,20 +177,20 @@ jobs: - name: Extract firmware for WonderMV id: extract-wonder-mv if: matrix.device == 'maixpy_wonder_mv' - uses: shrink/actions-docker-extract@v4 + uses: shrink/actions-docker-extract@f1ef61065b78731fe3310b4e84e511f6a927a77e # v4 with: image: localhost:5000/selfcustody/krux-builder-wonder-mv:latest path: /src/firmware/Kboot/build/. - name: Upload firmware for WonderMV if: matrix.device == 'maixpy_wonder_mv' - uses: actions/upload-artifact@v7 + uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7 with: path: ${{ steps.extract-wonder-mv.outputs.destination }} name: build-wonder-mv - name: Build for TZT if: matrix.device == 'maixpy_tzt' - uses: docker/build-push-action@v7 + uses: docker/build-push-action@c3c9e263c25d99ce0380d002d59b67737d91b0dc # v7 with: context: . build-args: DEVICE=maixpy_tzt @@ -198,20 +201,20 @@ jobs: - name: Extract firmware for TZT id: extract-tzt if: matrix.device == 'maixpy_tzt' - uses: shrink/actions-docker-extract@v4 + uses: shrink/actions-docker-extract@f1ef61065b78731fe3310b4e84e511f6a927a77e # v4 with: image: localhost:5000/selfcustody/krux-builder-tzt:latest path: /src/firmware/Kboot/build/. - name: Upload firmware for TZT if: matrix.device == 'maixpy_tzt' - uses: actions/upload-artifact@v7 + uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7 with: path: ${{ steps.extract-tzt.outputs.destination }} name: build-tzt - name: Build for Embed Fire if: matrix.device == 'maixpy_embed_fire' - uses: docker/build-push-action@v7 + uses: docker/build-push-action@c3c9e263c25d99ce0380d002d59b67737d91b0dc # v7 with: context: . build-args: DEVICE=maixpy_embed_fire @@ -222,20 +225,20 @@ jobs: - name: Extract firmware for Embed Fire id: extract-embed-fire if: matrix.device == 'maixpy_embed_fire' - uses: shrink/actions-docker-extract@v4 + uses: shrink/actions-docker-extract@f1ef61065b78731fe3310b4e84e511f6a927a77e # v4 with: image: localhost:5000/selfcustody/krux-builder-embed-fire:latest path: /src/firmware/Kboot/build/. - name: Upload firmware for Embed Fire if: matrix.device == 'maixpy_embed_fire' - uses: actions/upload-artifact@v7 + uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7 with: path: ${{ steps.extract-embed-fire.outputs.destination }} name: build-embed-fire - name: Build for WonderK if: matrix.device == 'maixpy_wonder_k' - uses: docker/build-push-action@v7 + uses: docker/build-push-action@c3c9e263c25d99ce0380d002d59b67737d91b0dc # v7 with: context: . build-args: DEVICE=maixpy_wonder_k @@ -246,13 +249,13 @@ jobs: - name: Extract firmware for WonderK id: extract-wonder-k if: matrix.device == 'maixpy_wonder_k' - uses: shrink/actions-docker-extract@v4 + uses: shrink/actions-docker-extract@f1ef61065b78731fe3310b4e84e511f6a927a77e # v4 with: image: localhost:5000/selfcustody/krux-builder-wonder-k:latest path: /src/firmware/Kboot/build/. - name: Upload firmware for WonderK if: matrix.device == 'maixpy_wonder_k' - uses: actions/upload-artifact@v7 + uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7 with: path: ${{ steps.extract-wonder-k.outputs.destination }} name: build-wonder-k diff --git a/.github/workflows/conventional-commits.yml b/.github/workflows/conventional-commits.yml index fc43c0e60..94d7ee070 100644 --- a/.github/workflows/conventional-commits.yml +++ b/.github/workflows/conventional-commits.yml @@ -7,7 +7,7 @@ jobs: conventional-commits: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v7 - - uses: webiny/action-conventional-commits@v1.4.2 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 + - uses: webiny/action-conventional-commits@7f91b1595ca1951cdb671ddc9f07a49081ec5b69 # v1.4.2 with: allowed-commit-types: "feat,fix,docs,style,refactor,perf,test,i18n,ci,chore,git" diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 02a3b5f07..080ad8c2b 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -9,21 +9,22 @@ jobs: build-gh-pages: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v7 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 with: submodules: recursive - name: Install uv - uses: astral-sh/setup-uv@v9.0.0 + uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0 with: + version: ">=0.13.6" enable-cache: true - name: Set up Python - run: uv python install 3.12 + run: uv python install 3.12.15 - name: Install docs dependencies - run: uv sync --frozen --python 3.12 --extra docs + run: uv sync --frozen --python 3.12.15 --extra docs - name: Build docs - run: uv run --python 3.12 mkdocs build + run: uv run --python 3.12.15 mkdocs build - name: Deploy - uses: peaceiris/actions-gh-pages@v4 + uses: peaceiris/actions-gh-pages@84c30a85c19949d7eee79c4ff27748b70285e453 # v4 with: github_token: ${{ secrets.GITHUB_TOKEN }} publish_dir: ./public diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 46f23a34d..dd39716d1 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -13,50 +13,40 @@ jobs: matrix: # minimum tested python version # maximum python version - py-version: [ "3.11.5", "3.12.13"] + py-version: [ "3.11.17", "3.12.15"] runs-on: ubuntu-latest steps: - - uses: actions/checkout@v7 - - name: Setup Python - uses: actions/setup-python@v7 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 with: - python-version: ${{ matrix.py-version }} - - uses: psf/black@stable - with: - options: "--check --verbose" - src: "./firmware/font" - - uses: psf/black@stable - with: - options: "--check --verbose" - src: "./firmware/scripts" - - uses: psf/black@stable - with: - options: "--check --verbose" - src: "./i18n" - - uses: psf/black@stable - with: - options: "--check --verbose" - src: "./src" - - uses: psf/black@stable + submodules: recursive + - name: Install uv + uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0 with: - options: "--check --verbose" - src: "./tests" - + version: ">=0.13.6" + enable-cache: true + - name: Set up Python + run: uv python install ${{ matrix.py-version }} + - name: Sync dependencies + run: uv sync --frozen --python ${{ matrix.py-version }} + - name: Check formatting + run: uv run --python ${{ matrix.py-version }} black --check --verbose firmware/font firmware/scripts i18n src tests + lint-pylint: strategy: matrix: # minimum tested python version # maximum python version - py-version: [ "3.11.5", "3.12.13"] + py-version: [ "3.11.17", "3.12.15"] runs-on: ubuntu-latest steps: - - uses: actions/checkout@v7 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 with: submodules: recursive - name: Install uv - uses: astral-sh/setup-uv@v9.0.0 + uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0 with: + version: ">=0.13.6" enable-cache: true - name: Set up Python run: uv python install ${{ matrix.py-version }} @@ -70,15 +60,16 @@ jobs: matrix: # minimum tested python version # maximum python version - py-version: [ "3.11.5", "3.12.13"] + py-version: [ "3.11.17", "3.12.15"] runs-on: ubuntu-latest steps: - - uses: actions/checkout@v7 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 with: submodules: recursive - name: Install uv - uses: astral-sh/setup-uv@v9.0.0 + uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0 with: + version: ">=0.13.6" enable-cache: true - name: Set up Python run: uv python install ${{ matrix.py-version }} @@ -92,15 +83,16 @@ jobs: matrix: # minimum tested python version # maximum python version - py-version: [ "3.11.5", "3.12.13"] + py-version: [ "3.11.17", "3.12.15"] runs-on: ubuntu-latest steps: - - uses: actions/checkout@v7 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 with: submodules: recursive - name: Install uv - uses: astral-sh/setup-uv@v9.0.0 + uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0 with: + version: ">=0.13.6" enable-cache: true - name: Set up Python run: uv python install ${{ matrix.py-version }} @@ -116,25 +108,26 @@ jobs: coverage: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v7 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 with: submodules: recursive - name: Install uv - uses: astral-sh/setup-uv@v9.0.0 + uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0 with: + version: ">=0.13.6" enable-cache: true - name: Set up Python # no need to use many coverages - run: uv python install 3.12.13 + run: uv python install 3.12.15 - name: Sync dependencies - run: uv sync --frozen --python 3.12.13 + run: uv sync --frozen --python 3.12.15 - name: Build libsecp256k1 - run: uv run --python 3.12.13 poe secp256k1-build + run: uv run --python 3.12.15 poe secp256k1-build - name: Check embit uses the C secp256k1 - run: uv run --python 3.12.13 poe secp256k1-check + run: uv run --python 3.12.15 poe secp256k1-check - name: Build coverage file - run: uv run --python 3.12.13 pytest --cache-clear --cov src/krux --cov-report xml tests + run: uv run --python 3.12.15 pytest --cache-clear --cov src/krux --cov-report xml tests - name: Upload coverage reports to Codecov with GitHub Action - uses: codecov/codecov-action@v7 + uses: codecov/codecov-action@303a32d7a59b442fa8d48b6a1cc6825c09c847a5 # v7 with: - token: ${{ secrets.CODECOV_TOKEN }} + token: ${{ secrets.CODECOV_TOKEN }} \ No newline at end of file diff --git a/.python-version b/.python-version new file mode 100644 index 000000000..bb4470a22 --- /dev/null +++ b/.python-version @@ -0,0 +1 @@ +3.12.15 diff --git a/Dockerfile b/Dockerfile index dd2eab666..550ee3038 100644 --- a/Dockerfile +++ b/Dockerfile @@ -28,6 +28,13 @@ ############ FROM gcc:12-bookworm@sha256:112aacdc53e949b9d2ccefb9ed64930a7fda5e10e007430f244be27e0263220b AS build-base +ARG PIP_VERSION="26.2.2" +ARG PIP_SHA256="fd7f4dfa8190e014013b9f08f3e973f4e4c26ffd179a204895533a7254813a70" +ARG ASTOR_VERSION="0.8.1" +ARG ASTOR_SHA256="070a54e890cefb5b3739d19f30f5a5ec840ffc9c50ffa7d23cc9fc1a38ebbfc5" +ARG PYSERIAL_VERSION="3.4" +ARG PYSERIAL_SHA256="e0770fadba80c31013896c7e6ef703f72e7834965954a78e71a3049488d4d7d8" + RUN apt-get update -y && \ apt-get install --no-install-recommends -y -q \ wget \ @@ -54,6 +61,7 @@ RUN apt-get update -y && \ zlib1g-dev \ libexpat-dev \ libisl-dev \ + ca-certificates \ python3 \ python3-pip \ python3-setuptools @@ -82,8 +90,20 @@ RUN wget https://github.com/Kitware/CMake/releases/download/v3.21.0/cmake-3.21.0 RUN apt-get update && apt-get install python3-venv -y RUN python3 -m venv /kruxenv -RUN /kruxenv/bin/pip install astor -RUN /kruxenv/bin/pip install pyserial==3.4 +RUN wget -O /tmp/pip-${PIP_VERSION}-py3-none-any.whl \ + "https://files.pythonhosted.org/packages/3f/06/3e4420302363fbb71be3c7f35ce042f54bfca330e3e66b3a2e3c4cca579f/pip-${PIP_VERSION}-py3-none-any.whl" && \ + wget -O /tmp/astor-${ASTOR_VERSION}-py2.py3-none-any.whl \ + "https://files.pythonhosted.org/packages/c3/88/97eef84f48fa04fbd6750e62dcceafba6c63c81b7ac1420856c8dcc0a3f9/astor-${ASTOR_VERSION}-py2.py3-none-any.whl" && \ + wget -O /tmp/pyserial-${PYSERIAL_VERSION}-py2.py3-none-any.whl \ + "https://files.pythonhosted.org/packages/0d/e4/2a744dd9e3be04a0c0907414e2a01a7c88bb3915cbe3c8cc06e209f59c30/pyserial-${PYSERIAL_VERSION}-py2.py3-none-any.whl" && \ + echo "${PIP_SHA256} /tmp/pip-${PIP_VERSION}-py3-none-any.whl" | sha256sum -c && \ + echo "${ASTOR_SHA256} /tmp/astor-${ASTOR_VERSION}-py2.py3-none-any.whl" | sha256sum -c && \ + echo "${PYSERIAL_SHA256} /tmp/pyserial-${PYSERIAL_VERSION}-py2.py3-none-any.whl" | sha256sum -c && \ + /kruxenv/bin/python -m pip install --no-index /tmp/pip-${PIP_VERSION}-py3-none-any.whl && \ + /kruxenv/bin/python -m pip install --no-index \ + /tmp/astor-${ASTOR_VERSION}-py2.py3-none-any.whl \ + /tmp/pyserial-${PYSERIAL_VERSION}-py2.py3-none-any.whl && \ + rm /tmp/*.whl ############ diff --git a/README.md b/README.md index e633950d0..649b91bed 100644 --- a/README.md +++ b/README.md @@ -68,7 +68,7 @@ To build and flash the firmware: The first time, the build can take around an hour or so to complete. Subsequent builds should take only a few minutes. If all goes well, you should see a new `build` folder containing `firmware.bin` and `kboot.kfpkg` files when the build completes. ## Install Krux and dev tools -Krux uses [uv](https://docs.astral.sh/uv/) for Python packaging and environment management. Install uv by following its [installation guide](https://docs.astral.sh/uv/getting-started/installation/), then sync the project to install runtime deps ([embit](https://github.com/diybitcoinhardware/embit) and [uUR](https://github.com/selfcustody/cUR), the native UR module compiled from the same sources the devices run) along with the `dev` group ([pytest](https://docs.pytest.org), [pylint](https://pypi.org/project/pylint/), [black](https://github.com/psf/black) and i18n helpers): +Krux uses [uv](https://docs.astral.sh/uv/) 0.13.6 or newer for Python packaging and environment management. The project supports Python 3.11.5 through 3.12, CI uses the latest security patch releases in that range, and `.python-version` pins the default development environment to Python 3.12.15. Install uv by following its [installation guide](https://docs.astral.sh/uv/getting-started/installation/), then sync the project to install runtime deps ([embit](https://github.com/diybitcoinhardware/embit) and [uUR](https://github.com/selfcustody/cUR), the native UR module compiled from the same sources the devices run) along with the `dev` group ([pytest](https://docs.pytest.org), [pylint](https://pypi.org/project/pylint/), [black](https://github.com/psf/black) and i18n helpers): ```bash uv sync --frozen ``` diff --git a/pyproject.toml b/pyproject.toml index 587ab12d6..65574bae1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -72,6 +72,8 @@ dev = [ # krux is firmware, not a distributable Python package [tool.uv] package = false +required-version = ">=0.13.6" +constraint-dependencies = ["certifi>=2026.10.23"] [tool.uv.sources] embit = { path = "vendor/embit", editable = true } diff --git a/uv.lock b/uv.lock index 64bc1ce06..2418b3754 100644 --- a/uv.lock +++ b/uv.lock @@ -1,11 +1,14 @@ version = 1 -revision = 3 +revision = 5 requires-python = ">=3.11.5, <3.13" resolution-markers = [ "python_full_version >= '3.12'", "python_full_version < '3.12'", ] +[manifest] +constraints = [{ name = "certifi", specifier = ">=2026.10.23" }] + [[package]] name = "astroid" version = "4.0.4" @@ -64,11 +67,11 @@ wheels = [ [[package]] name = "certifi" -version = "2026.5.20" +version = "2026.10.23" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f3/ce/ee2ecad540810a79593028e88299baeae54d346cc7a0d94b6199988b89b1/certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d", size = 135422, upload-time = "2026-05-20T11:46:50.073Z" } +sdist = { url = "https://files.pythonhosted.org/packages/fd/11/b524cbd9bbdaca58608e4cf4053fd463417549e1959fa8d3aa18319f9555/certifi-2026.10.23.tar.gz", hash = "sha256:a9895e6e2758b371e0545e321a3a8fd1fcfd9829ada8824a936dfb16aaaf36cd", size = 136225, upload-time = "2026-10-23T11:36:16.478Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/59/8c/57e832b7af6d7c5abe66eb3fbe3a3a32f4d11ea23a1aa7131371035be991/certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897", size = 134134, upload-time = "2026-05-20T11:46:48.578Z" }, + { url = "https://files.pythonhosted.org/packages/32/d1/a3652fe2c19134d0d15922177821d863cbd7e842d53a21bbe7e8e3a1b5ac/certifi-2026.10.23-py3-none-any.whl", hash = "sha256:e9b54de76e530bb6cf0ccd036787103acb58fd42ba930ea8bf948ddbaa2f61d1", size = 135082, upload-time = "2026-10-23T11:36:14.763Z" }, ] [[package]]