diff --git a/.github/workflows/build-and-deploy.yml b/.github/workflows/build-and-deploy.yml index 40bfbb5b..402e0bae 100755 --- a/.github/workflows/build-and-deploy.yml +++ b/.github/workflows/build-and-deploy.yml @@ -27,6 +27,9 @@ jobs: - python-version: "3.13" python-tag: "313" architecture: "AMD64" + - python-version: "3.14" + python-tag: "314" + architecture: "AMD64" uses: ./.github/workflows/build-windows-wheel.yml with: @@ -38,7 +41,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"] + python-version: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14"] uses: ./.github/workflows/build-linux-wheel.yml with: python-version: ${{ matrix.python-version }} @@ -73,6 +76,9 @@ jobs: - python-version: "3.13" python-tag: "313" architecture: "AMD64" + - python-version: "3.14" + python-tag: "314" + architecture: "AMD64" uses: ./.github/workflows/test-windows-wheel.yml with: python-version: ${{ matrix.python-version }} @@ -82,7 +88,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"] + python-version: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14"] uses: ./.github/workflows/test-linux-wheel.yml with: python-version: ${{ matrix.python-version }} @@ -118,6 +124,9 @@ jobs: - python-version: "3.13" python-tag: "313" architecture: "AMD64" + - python-version: "3.14" + python-tag: "314" + architecture: "AMD64" uses: ./.github/workflows/test-windows-wheel.yml with: @@ -130,7 +139,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"] + python-version: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14"] uses: ./.github/workflows/test-linux-wheel.yml with: python-version: ${{ matrix.python-version }} @@ -155,12 +164,10 @@ jobs: strategy: fail-fast: false matrix: - python-version: ${{ github.event_name == 'workflow_dispatch' && fromJSON(format('["{0}"]', inputs.python-version)) || fromJSON('["3.9", "3.10", "3.11", "3.12", "3.13"]') }} - env: - REQUIREMENTS_FILE: ${{ matrix.python-version == '3.9' && 'requirements.3.9.txt' || 'requirements.txt' }} - + python-version: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14"] steps: - uses: actions/checkout@v4 + - uses: astral-sh/setup-uv@v10.3.0 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v5 @@ -168,10 +175,7 @@ jobs: python-version: ${{ matrix.python-version }} - name: Install dependencies - run: | - sudo apt-get update - sudo apt-get install -y meson ninja-build - pip install -r $REQUIREMENTS_FILE + run: uv sync --locked --no-install-project - name: Generate meson files run: | @@ -181,22 +185,20 @@ jobs: - name: Configure build with tests enabled run: | - meson setup --buildtype=release -Dbuild_tests=true build/release + uv run --no-sync meson setup --buildtype=release -Dbuild_tests=true build/release - name: Build project run: | - cd build/release - ninja + uv run --no-sync ninja -C build/release - name: Run C++ tests - run: | - cd build/release - ./tests.x + run: ./scripts/run_cpp_tests.sh sdist: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + - uses: astral-sh/setup-uv@v10.3.0 - name: Generate meson files run: | python scripts/generate_meson.py ./src/dbzero/ core @@ -209,8 +211,7 @@ jobs: git config --global user.name "CI Builder" rm -f .gitignore git add . && git commit -m "Update meson files for build" - - run: python -m pip install build meson - - run: python -m build --sdist + - run: uv build --sdist - uses: actions/upload-artifact@v4 with: name: sdist @@ -248,4 +249,4 @@ jobs: TWINE_USERNAME: __token__ TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }} run: | - twine upload upload/* \ No newline at end of file + twine upload upload/* diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 105d1165..982ee064 100755 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -11,51 +11,71 @@ jobs: strategy: fail-fast: false matrix: - python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"] + python-version: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14"] uses: ./.github/workflows/build-linux-wheel.yml with: python-version: ${{ matrix.python-version }} build-windows: + strategy: + fail-fast: false + matrix: + include: + - python-version: "3.12" + python-tag: "312" + - python-version: "3.14" + python-tag: "314" uses: ./.github/workflows/build-windows-wheel.yml with: - python-version: "3.12" - python-tag: "312" + python-version: ${{ matrix.python-version }} + python-tag: ${{ matrix.python-tag }} architecture: "AMD64" build-macos: + strategy: + fail-fast: false + matrix: + python-version: ["3.12", "3.14"] uses: ./.github/workflows/build-mac-wheel.yml with: - python-version: "3.12" + python-version: ${{ matrix.python-version }} test-wheels-linux: needs: build-linux strategy: fail-fast: false matrix: - python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"] + python-version: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14"] uses: ./.github/workflows/test-linux-wheel.yml with: python-version: ${{ matrix.python-version }} test-wheels-windows: needs: build-windows + strategy: + fail-fast: false + matrix: + python-version: ["3.12", "3.14"] uses: ./.github/workflows/test-windows-wheel.yml with: - python-version: "3.12" + python-version: ${{ matrix.python-version }} test-wheels-macos: needs: build-macos + strategy: + fail-fast: false + matrix: + python-version: ["3.12", "3.14"] uses: ./.github/workflows/test-mac-wheel.yml with: - python-version: "3.12" + python-version: ${{ matrix.python-version }} stress-test-linux: needs: build-linux strategy: fail-fast: false matrix: - python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"] + python-version: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14"] uses: ./.github/workflows/test-linux-wheel.yml with: python-version: ${{ matrix.python-version }} @@ -102,12 +122,10 @@ jobs: strategy: fail-fast: false matrix: - python-version: ${{ github.event_name == 'workflow_dispatch' && fromJSON(format('["{0}"]', inputs.python-version)) || fromJSON('["3.9", "3.10", "3.11", "3.12", "3.13"]') }} - env: - REQUIREMENTS_FILE: ${{ matrix.python-version == '3.9' && 'requirements.3.9.txt' || 'requirements.txt' }} - + python-version: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14"] steps: - uses: actions/checkout@v4 + - uses: astral-sh/setup-uv@v10.3.0 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v5 @@ -115,10 +133,7 @@ jobs: python-version: ${{ matrix.python-version }} - name: Install dependencies - run: | - sudo apt-get update - sudo apt-get install -y meson ninja-build - pip install -r $REQUIREMENTS_FILE + run: uv sync --locked --no-install-project - name: Generate meson files run: | @@ -128,14 +143,11 @@ jobs: - name: Configure build with tests enabled run: | - meson setup --buildtype=release -Dbuild_tests=true build/release + uv run --no-sync meson setup --buildtype=release -Dbuild_tests=true build/release - name: Build project run: | - cd build/release - ninja + uv run --no-sync ninja -C build/release - name: Run C++ tests - run: | - cd build/release - ./tests.x + run: ./scripts/run_cpp_tests.sh diff --git a/.github/workflows/build-linux-wheel.yml b/.github/workflows/build-linux-wheel.yml index a711cd91..8d37e452 100755 --- a/.github/workflows/build-linux-wheel.yml +++ b/.github/workflows/build-linux-wheel.yml @@ -46,10 +46,10 @@ jobs: steps: - uses: actions/checkout@v4 - - name: Set up Python ${{ inputs.python-version }} + - name: Set up build Python uses: actions/setup-python@v5 with: - python-version: ${{ inputs.python-version }} + python-version: '3.12' - name: Generate meson files run: | @@ -69,14 +69,13 @@ jobs: run: echo "py_tag=$(echo ${{ inputs.python-version }} | tr -d '.')" >> $GITHUB_OUTPUT - name: Build manylinux wheels - uses: pypa/cibuildwheel@v2.21 + uses: pypa/cibuildwheel@v3.4.1 env: CIBW_BUILD: cp${{ steps.py-version.outputs.py_tag }}-manylinux_x86_64 CIBW_SKIP: pp* cp36-* *-musllinux* CIBW_ARCHS_LINUX: x86_64 CIBW_MANYLINUX_X86_64_IMAGE: manylinux_2_28 CIBW_BUILD_VERBOSITY: 1 - CIBW_PRERELEASE_PYTHONS: true - uses: actions/upload-artifact@v4 with: diff --git a/.github/workflows/build-mac-wheel.yml b/.github/workflows/build-mac-wheel.yml index b68dd25a..0e6c31c1 100755 --- a/.github/workflows/build-mac-wheel.yml +++ b/.github/workflows/build-mac-wheel.yml @@ -47,6 +47,7 @@ jobs: MACOSX_DEPLOYMENT_TARGET: '11.0' steps: - uses: actions/checkout@v4 + - uses: astral-sh/setup-uv@v10.3.0 - name: Set up Python ${{ inputs.python-version }} uses: actions/setup-python@v5 @@ -66,11 +67,8 @@ jobs: rm -f .gitignore git add . && git commit -m "Update meson files" - - name: Install build tools - run: pip install build - - name: Build wheel - run: python3 -m build ${{ inputs.build-args }} + run: uv build ${{ inputs.build-args }} env: CIBW_SKIP: pp* cp36-* *-musllinux* CIBW_ARCHS_MACOS: x86_64 arm64 diff --git a/.github/workflows/build-windows-wheel.yml b/.github/workflows/build-windows-wheel.yml index 957cf0e3..a351f35b 100755 --- a/.github/workflows/build-windows-wheel.yml +++ b/.github/workflows/build-windows-wheel.yml @@ -24,6 +24,7 @@ on: - '3.11' - '3.12' - '3.13' + - '3.14' default: '3.12' python-tag: description: 'Python tag (e.g., 310, 311, 312)' @@ -35,6 +36,7 @@ on: - '311' - '312' - '313' + - '314' default: '312' architecture: description: 'Build architecture' @@ -51,10 +53,10 @@ jobs: steps: - uses: actions/checkout@v4 - - name: Set up Python ${{ inputs.python-version }} + - name: Set up build Python uses: actions/setup-python@v5 with: - python-version: ${{ inputs.python-version }} + python-version: '3.12' - uses: bus1/cabuild/action/msdevshell@v1 with: @@ -86,13 +88,12 @@ jobs: Copy-Item ".github\files\*.pyz" "$cachePath\" shell: pwsh - - run: pip install cibuildwheel==2.23.0 + - run: pip install cibuildwheel==3.4.1 - run: python -m cibuildwheel --output-dir wheelhouse env: CIBW_BUILD: cp${{ inputs.python-tag }}-* CIBW_ARCHS_WINDOWS: ${{ inputs.architecture }} - CIBW_ENABLE: cpython-prerelease - uses: actions/upload-artifact@v4 with: diff --git a/.github/workflows/test-data-validation.yml b/.github/workflows/test-data-validation.yml index c596506c..9b147017 100755 --- a/.github/workflows/test-data-validation.yml +++ b/.github/workflows/test-data-validation.yml @@ -13,6 +13,7 @@ on: - '3.11' - '3.12' - '3.13' + - '3.14' default: '3.12' jobs: @@ -32,7 +33,7 @@ jobs: uses: ./.github/workflows/build-windows-wheel.yml with: python-version: ${{ inputs.python-version }} - python-tag: ${{ inputs.python-version == '3.9' && '39' || inputs.python-version == '3.10' && '310' || inputs.python-version == '3.11' && '311' || inputs.python-version == '3.12' && '312' || '313' }} + python-tag: ${{ inputs.python-version == '3.9' && '39' || inputs.python-version == '3.10' && '310' || inputs.python-version == '3.11' && '311' || inputs.python-version == '3.12' && '312' || inputs.python-version == '3.13' && '313' || '314' }} architecture: 'AMD64' create-test-data: diff --git a/.github/workflows/test-linux-wheel.yml b/.github/workflows/test-linux-wheel.yml index 8172534d..84f7c9c3 100755 --- a/.github/workflows/test-linux-wheel.yml +++ b/.github/workflows/test-linux-wheel.yml @@ -52,10 +52,9 @@ jobs: test-wheels-linux: runs-on: ubuntu-latest timeout-minutes: ${{ fromJSON(inputs.timeout-minutes) }} - env: - REQUIREMENTS_FILE: ${{ inputs.python-version == '3.9' && 'requirements.3.9.txt' || 'requirements.txt' }} steps: - uses: actions/checkout@v4 + - uses: astral-sh/setup-uv@v10.3.0 - name: Set up Python ${{ inputs.python-version }} uses: actions/setup-python@v5 @@ -70,10 +69,9 @@ jobs: - name: Install wheel and dependencies run: | - pip install pytest - pip install -r $REQUIREMENTS_FILE - pip install ./wheels/*.whl + uv sync --locked --no-install-project + uv pip install --python .venv/bin/python ./wheels/*.whl - name: Run tests run: | - ${{ inputs.ld-preload }} python -m pytest ${{ inputs.pytest-markers }} -c pytest.ini --capture=no -vv + ${{ inputs.ld-preload }} uv run --no-sync python -m pytest ${{ inputs.pytest-markers }} -c pytest.ini --capture=no -vv diff --git a/.github/workflows/test-mac-wheel.yml b/.github/workflows/test-mac-wheel.yml index a856a213..f36ed362 100755 --- a/.github/workflows/test-mac-wheel.yml +++ b/.github/workflows/test-mac-wheel.yml @@ -43,10 +43,9 @@ jobs: test-wheels-macos: runs-on: macos-latest timeout-minutes: ${{ fromJSON(inputs.timeout-minutes) }} - env: - REQUIREMENTS_FILE: ${{ inputs.python-version == '3.9' && 'requirements.3.9.txt' || 'requirements.txt' }} steps: - uses: actions/checkout@v4 + - uses: astral-sh/setup-uv@v10.3.0 - name: Set up Python ${{ inputs.python-version }} uses: actions/setup-python@v5 @@ -61,10 +60,9 @@ jobs: - name: Install wheel and dependencies run: | - pip install pytest - pip install -r $REQUIREMENTS_FILE - pip install ./wheels/*.whl + uv sync --locked --no-install-project + uv pip install --python .venv/bin/python ./wheels/*.whl - name: Run tests run: | - python -m pytest ${{ inputs.pytest-markers }} -c pytest.ini --capture=no -vv + uv run --no-sync python -m pytest ${{ inputs.pytest-markers }} -c pytest.ini --capture=no -vv diff --git a/.github/workflows/test-windows-wheel.yml b/.github/workflows/test-windows-wheel.yml index a4065610..56946a13 100755 --- a/.github/workflows/test-windows-wheel.yml +++ b/.github/workflows/test-windows-wheel.yml @@ -43,10 +43,9 @@ jobs: test-windows: runs-on: windows-latest timeout-minutes: ${{ fromJSON(inputs.timeout-minutes) }} - env: - REQUIREMENTS_FILE: ${{ inputs.python-version == '3.9' && 'requirements.3.9.txt' || 'requirements.txt' }} steps: - uses: actions/checkout@v4 + - uses: astral-sh/setup-uv@v10.3.0 - name: Set up Python ${{ inputs.python-version }} uses: actions/setup-python@v5 @@ -61,11 +60,10 @@ jobs: - name: Install wheel and dependencies run: | - pip install pytest - pip install -r $env:REQUIREMENTS_FILE - Get-ChildItem -Path "./wheels/*.whl" | ForEach-Object { pip install $_.FullName } + uv sync --locked --no-install-project + Get-ChildItem -Path "./wheels/*.whl" | ForEach-Object { uv pip install --python .venv/Scripts/python.exe $_.FullName } shell: powershell - name: Run tests run: | - python -m pytest ${{ inputs.pytest-markers }} -c pytest.ini --capture=no -vv + uv run --no-sync python -m pytest ${{ inputs.pytest-markers }} -c pytest.ini --capture=no -vv diff --git a/.gitignore b/.gitignore index e0996fdc..8bb469f7 100755 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ **/__pycache__/ .vscode *.db0.lock +.venv/ diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 47a9e86a..cb077169 100755 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -40,15 +40,18 @@ This project adheres to a code of conduct that all contributors are expected to ### Prerequisites - C++ compiler (GCC 13+ or Microsoft C/C++ Optimizing Compiler 19.41+) -- Meson -- Ninja build system +- uv (installs Meson and Ninja from the project lockfile) - Git - Python 3.9 or higher (for Python components and tooling) -- A virtual environment tool (venv, virtualenv, or conda) for Python dependencies ### Setting Up Your Environment -1. **Build the C++ components**: +1. **Install the locked Python dependencies**: + ```bash + uv sync --locked --no-install-project + ``` + +2. **Build the C++ components**: ```bash # Build debug version without tests ./scripts/build.sh @@ -63,33 +66,25 @@ This project adheres to a code of conduct that all contributors are expected to ./scripts/build.sh -r -t ``` - Alternatively, you can use Meson directly: - ```bash - meson setup build - meson compile -C build - ``` - -2. **Create a Python virtual environment** (for Python components): + Alternatively, you can call Meson through uv: ```bash - python -m venv venv - source venv/bin/activate # On Windows: venv\Scripts\activate + uv run --no-sync meson setup build + uv run --no-sync meson compile -C build ``` -3. **Install Python dependencies**: - ```bash - pip install -r requirements.txt # Install dependencies - ``` + Build the Python source distribution or wheel with `uv build`. Meson compiles + the C++ extension through the backend declared in `pyproject.toml`. -4. **Verify the installation**: +3. **Verify the installation**: ```bash # Run C++ tests (debug build) - ./build/debug/testsD.x + ./scripts/run_cpp_tests.sh --debug # Run C++ tests (release build) - ./build/release/tests.x + ./scripts/run_cpp_tests.sh # Or use Meson test runner - meson test -C build + uv run --no-sync meson test -C build # Run Python tests (excludes stress tests) ./scripts/run_tests.sh diff --git a/dbzero/build_package.sh b/dbzero/build_package.sh index 15e628fd..3a874487 100755 --- a/dbzero/build_package.sh +++ b/dbzero/build_package.sh @@ -18,28 +18,19 @@ while true ; do esac done -if ! python3 -c 'import setuptools' >/dev/null 2>&1; then - exit 0 -fi - package_root="$PWD" rm -rf .build trap 'rm -rf "$package_root/.build"' EXIT mkdir -p .build/dbzero find ./dbzero -maxdepth 1 -type f -exec cp {} ./.build/dbzero/ \; cp setup.py .build/setup.py +cp pyproject.toml .build/pyproject.toml cp LICENSE .build/LICENSE cp README.md .build/README.md cd .build -python3 setup.py sdist -# Get the current Python3 version -PYTHON3_VERSION=$(python3 -c 'import sys; print(f"{sys.version_info.minor}")') +uv build --sdist if [ "${INSTALL-}" ] ; then - if [ "$PYTHON3_VERSION" -ge 11 ]; then - pip3 install "$(ls ./dist/*.tar.gz | sort | tail -n 1)" --break-system-packages - else - pip3 install "$(ls ./dist/*.tar.gz | sort | tail -n 1)" - fi + uv pip install "$(ls ./dist/*.tar.gz | sort | tail -n 1)" fi cd .. diff --git a/dbzero/pyproject.toml b/dbzero/pyproject.toml new file mode 100644 index 00000000..09977b5b --- /dev/null +++ b/dbzero/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +requires = ["setuptools>=61"] +build-backend = "setuptools.build_meta" diff --git a/dbzero/setup.py b/dbzero/setup.py index 395f69b8..1e6f1794 100755 --- a/dbzero/setup.py +++ b/dbzero/setup.py @@ -10,7 +10,7 @@ setup( name='dbzero', - version='0.6.2', + version='0.7.0', description='DISTIC (Durable, Infinite, Shared, Transactional, Isolated, Composable) storage system for Python 3.x offering flexibility of a memory with durability of a database.', packages=['dbzero'], python_requires='>=3.9', diff --git a/pyproject.toml b/pyproject.toml index dea5e5f1..b690cec1 100755 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,10 +1,10 @@ [build-system] build-backend = 'mesonpy' -requires = ['meson-python'] +requires = ['meson-python==0.18.0', 'meson==1.9.1', 'ninja'] [project] name = 'dbzero' -version = '0.6.2' +version = '0.7.0' description = 'DISTIC (Durable, Infinite, Shared, Transactional, Isolated, Composable) storage system for Python 3.x offering flexibility of a memory with durability of a database.' readme = 'README.md' requires-python = '>=3.9' @@ -26,6 +26,19 @@ homepage = 'https://dbzero.io/' documentation = 'https://docs.dbzero.io/' repository = 'https://github.com/dbzero-software/dbzero' +[dependency-groups] +dev = [ + 'meson==1.9.1', + 'meson-python==0.18.0', + 'ninja', + 'psutil==7.0.0', + 'pytest==8.4.2; python_version == "3.9"', + 'pytest==9.0.1; python_version >= "3.10"', + 'pytest-asyncio==1.2.0; python_version == "3.9"', + 'pytest-asyncio==1.3.0; python_version >= "3.10"', + 'pytest-xdist==3.8.0', +] + [tool.pytest.ini_options] testpaths = ["python_tests"] diff --git a/python_tests/test_python314_import.py b/python_tests/test_python314_import.py new file mode 100644 index 00000000..05e5aa2b --- /dev/null +++ b/python_tests/test_python314_import.py @@ -0,0 +1,27 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +# Copyright (c) 2025-2026 Wojciech Sebastian Kozlowski + +import subprocess +import sys + +import pytest + + +@pytest.mark.skipif(sys.version_info < (3, 14), reason="Python 3.14 regression") +def test_registers_abstract_memo_class_without_crashing(): + result = subprocess.run( + [ + sys.executable, + "-c", + "from abc import ABC, abstractmethod\n" + "import dbzero as db0\n" + "@db0.memo\n" + "class AbstractMemo(ABC):\n" + " @abstractmethod\n" + " def value(self):\n" + " pass\n", + ], + capture_output=True, + text=True, + ) + assert result.returncode == 0, result.stderr diff --git a/scripts/build.sh b/scripts/build.sh index 53e448eb..0f06c2bf 100755 --- a/scripts/build.sh +++ b/scripts/build.sh @@ -52,6 +52,10 @@ if [ $cores -lt 1 ]; then exit 1; fi +uv sync --locked --no-install-project +export VIRTUAL_ENV="$PWD/.venv" +export PATH="$VIRTUAL_ENV/bin:$PATH" + # regenerate meson files: python3 scripts/generate_meson.py ./src/dbzero/ core diff --git a/scripts/run_cpp_tests.sh b/scripts/run_cpp_tests.sh new file mode 100755 index 00000000..b870ff1b --- /dev/null +++ b/scripts/run_cpp_tests.sh @@ -0,0 +1,29 @@ +#!/usr/bin/env bash +set -euo pipefail + +repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +build_type=release +test_binary=tests.x + +case "${1:-}" in + -d|--debug) + build_type=debug + test_binary=testsD.x + shift + ;; + -r|--release) + shift + ;; +esac + +if [[ ! -x "$repo_root/build/$build_type/$test_binary" ]]; then + echo "Build the C++ tests first with ./scripts/build.sh -r -t or -d -t." >&2 + exit 1 +fi + +cd "$repo_root" +python_site_packages="$(uv run --no-sync python -c 'import sysconfig; print(sysconfig.get_paths()["purelib"])')" +export PYTHONPATH="$python_site_packages${PYTHONPATH:+:$PYTHONPATH}" + +cd "$repo_root/build/$build_type" +exec "./$test_binary" "$@" diff --git a/scripts/run_tests.sh b/scripts/run_tests.sh index afedfae4..87192653 100755 --- a/scripts/run_tests.sh +++ b/scripts/run_tests.sh @@ -28,4 +28,4 @@ while [[ $# -gt 0 ]]; do esac done -python3 -m pytest -m 'not integration_test' -m 'not stress_test' -c pytest.ini --capture=no "${parallel_args[@]}" "${pytest_args[@]}" -vv +uv run --no-sync python -m pytest -m 'not integration_test' -m 'not stress_test' -c pytest.ini --capture=no "${parallel_args[@]}" "${pytest_args[@]}" -vv diff --git a/src/dbzero/bindings/python/Memo.cpp b/src/dbzero/bindings/python/Memo.cpp index 8fed7748..8672c09f 100755 --- a/src/dbzero/bindings/python/Memo.cpp +++ b/src/dbzero/bindings/python/Memo.cpp @@ -967,6 +967,80 @@ namespace db0::python template <> PyType_Slot *getCommonSlots() { return MemoImmutableObject_common_slots; } + + // Metaclasses with a custom tp_new, including ABCMeta, cannot be used by + // PyType_FromSpecWithBases on Python 3.14. Let the metaclass create the + // subtype, then attach the memo layout and native slots. + PyTypeObject *createMemoSubtype(PyTypeObject *base_class, PyType_Spec *spec) + { + auto native_type = Py_OWN(reinterpret_cast(PyType_FromSpec(spec))); + if (!native_type) { + return nullptr; + } + auto namespace_dict = Py_OWN(PyDict_Copy(base_class->tp_dict)); + if (!namespace_dict) { + return nullptr; + } + for (const char *key : {"__dict__", "__weakref__", "__slots__", "_abc_impl"}) { + if (PyDict_GetItemString(*namespace_dict, key)) { + PyDict_DelItemString(*namespace_dict, key); + } + } + PySafeDict_SetItemString(*namespace_dict, "__slots__", Py_OWN(PyTuple_New(0))); + auto name = Py_OWN(PyObject_GetAttrString(reinterpret_cast(base_class), "__name__")); + if (!name) { + return nullptr; + } + auto bases = Py_OWN(PySafeTuple_Pack(Py_BORROW(base_class))); + auto result = Py_OWN(reinterpret_cast(PyObject_CallFunctionObjArgs( + reinterpret_cast(Py_TYPE(base_class)), *name, *bases, *namespace_dict, nullptr))); + if (!result) { + return nullptr; + } + if (!PyType_Check(reinterpret_cast(*result)) || *result == base_class + || (*result)->tp_base != base_class) + { + PyErr_SetString(PyExc_TypeError, "Memo metaclass must create a new direct subtype"); + return nullptr; + } + + auto *target = *result; + auto *source = *native_type; + target->tp_basicsize = source->tp_basicsize; + target->tp_itemsize = source->tp_itemsize; + target->tp_new = source->tp_new; + target->tp_init = source->tp_init; + target->tp_dealloc = source->tp_dealloc; + target->tp_alloc = source->tp_alloc; + target->tp_free = source->tp_free; + target->tp_getattro = source->tp_getattro; + target->tp_setattro = source->tp_setattro; + target->tp_richcompare = source->tp_richcompare; + target->tp_hash = source->tp_hash; + target->tp_traverse = source->tp_traverse; + target->tp_clear = source->tp_clear; + target->tp_flags &= ~(Py_TPFLAGS_HAVE_GC | Py_TPFLAGS_MANAGED_DICT); + target->tp_flags |= source->tp_flags & Py_TPFLAGS_HAVE_GC; +#ifdef Py_TPFLAGS_MANAGED_WEAKREF + target->tp_flags &= ~Py_TPFLAGS_MANAGED_WEAKREF; +#endif +#ifdef Py_TPFLAGS_INLINE_VALUES + target->tp_flags &= ~Py_TPFLAGS_INLINE_VALUES; +#endif + for (const char *key : {"__setattr__", "__delattr__"}) { + auto *descriptor = reinterpret_cast(PyDict_GetItemString(source->tp_dict, key)); + PySafeDict_SetItemString(target->tp_dict, key, + Py_OWN(PyDescr_NewWrapper(target, descriptor->d_base, descriptor->d_wrapped))); + } + for (auto *method = source->tp_methods; method && method->ml_name; ++method) { + PySafeDict_SetItemString(target->tp_dict, method->ml_name, Py_OWN(PyDescr_NewMethod(target, method))); + } + for (auto *getset = source->tp_getset; getset && getset->name; ++getset) { + PySafeDict_SetItemString(target->tp_dict, getset->name, Py_OWN(PyDescr_NewGetSet(target, getset))); + } + PyType_Modified(target); + return result.steal(); + } template PyTypeObject *PyMemoType_FromSpec(PyTypeObject *base_class, const char *tp_name, bool is_singleton) @@ -999,9 +1073,6 @@ namespace db0::python // Enable GC for Python 3.10 compatibility - required for inheritance hierarchies std::uint32_t flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE; - if (base_class->tp_flags & Py_TPFLAGS_IS_ABSTRACT) { - flags |= Py_TPFLAGS_IS_ABSTRACT; - } #if PY_VERSION_HEX < 0x030B0000 // Python < 3.11 flags |= Py_TPFLAGS_HAVE_GC; #endif @@ -1015,10 +1086,28 @@ namespace db0::python }; auto bases = Py_OWN(PySafeTuple_Pack(Py_BORROW(base_class))); - auto tp_result = Py_OWN((PyTypeObject*)PyType_FromSpecWithBases(&type_spec, *bases)); - auto old_dict = Py_OWN((*tp_result)->tp_dict); - // NOTE: take only the auto-generated __setattr__, __delattr__, __dir__ and __dict__ - (*tp_result)->tp_dict = mergeDicts(base_class->tp_dict, *old_dict, { "__setattr__", "__delattr__", "__dir__", "__dict__" }); + const bool custom_metaclass = Py_TYPE(base_class)->tp_new != PyType_Type.tp_new; + TypeObjectSharedPtr tp_result = nullptr; + if (custom_metaclass) { + tp_result = Py_OWN(createMemoSubtype(base_class, &type_spec)); + } else { +#if PY_VERSION_HEX >= 0x030C0000 + tp_result = Py_OWN((PyTypeObject*)PyType_FromMetaclass(Py_TYPE(base_class), nullptr, &type_spec, *bases)); +#else + tp_result = Py_OWN((PyTypeObject*)PyType_FromSpecWithBases(&type_spec, *bases)); +#endif + } + if (!tp_result) { + return nullptr; + } + if (!custom_metaclass) { + auto old_dict = Py_OWN((*tp_result)->tp_dict); + // NOTE: take only the auto-generated __setattr__, __delattr__, __dir__ and __dict__ + (*tp_result)->tp_dict = mergeDicts(base_class->tp_dict, *old_dict, { "__setattr__", "__delattr__", "__dir__", "__dict__" }); + } + if (base_class->tp_flags & Py_TPFLAGS_IS_ABSTRACT) { + (*tp_result)->tp_flags |= Py_TPFLAGS_IS_ABSTRACT; + } // disable weak-refs (important for Python 3.11.x) (*tp_result)->tp_weaklistoffset = 0; diff --git a/uv.lock b/uv.lock new file mode 100644 index 00000000..e7e8cf61 --- /dev/null +++ b/uv.lock @@ -0,0 +1,411 @@ +version = 1 +revision = 5 +requires-python = ">=3.9" +resolution-markers = [ + "python_full_version >= '3.12'", + "python_full_version >= '3.10' and python_full_version < '3.12'", + "python_full_version < '3.10'", +] + +[[package]] +name = "backports-asyncio-runner" +version = "1.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8e/ff/70dca7d7cb1cbc0edb2c6cc0c38b65cba36cccc491eca64cabd5fe7f8670/backports_asyncio_runner-1.2.0.tar.gz", hash = "sha256:a5aa7b2b7d8f8bfcaa2b57313f70792df84e32a2a746f585213373f900b42162", size = 69893, upload-time = "2025-07-02T02:27:15.685Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/59/76ab57e3fe74484f48a53f8e337171b4a2349e506eabe136d7e01d059086/backports_asyncio_runner-1.2.0-py3-none-any.whl", hash = "sha256:0da0a936a8aeb554eccb426dc55af3ba63bcdc69fa1a600b5bb305413a4477b5", size = 12313, upload-time = "2025-07-02T02:27:14.263Z" }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, +] + +[[package]] +name = "dbzero" +version = "0.7.0" +source = { editable = "." } +dependencies = [ + { name = "fasteners" }, +] + +[package.dev-dependencies] +dev = [ + { name = "meson" }, + { name = "meson-python" }, + { name = "ninja" }, + { name = "psutil" }, + { name = "pytest", version = "8.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "pytest", version = "9.0.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "pytest-asyncio", version = "1.2.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "pytest-asyncio", version = "1.3.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "pytest-xdist" }, +] + +[package.metadata] +requires-dist = [{ name = "fasteners", specifier = "==0.19" }] + +[package.metadata.requires-dev] +dev = [ + { name = "meson", specifier = "==1.9.1" }, + { name = "meson-python", specifier = "==0.18.0" }, + { name = "ninja" }, + { name = "psutil", specifier = "==7.0.0" }, + { name = "pytest", marker = "python_full_version == '3.9.*'", specifier = "==8.4.2" }, + { name = "pytest", marker = "python_full_version >= '3.10'", specifier = "==9.0.1" }, + { name = "pytest-asyncio", marker = "python_full_version == '3.9.*'", specifier = "==1.2.0" }, + { name = "pytest-asyncio", marker = "python_full_version >= '3.10'", specifier = "==1.3.0" }, + { name = "pytest-xdist", specifier = "==3.8.0" }, +] + +[[package]] +name = "exceptiongroup" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/50/79/66800aadf48771f6b62f7eb014e352e5d06856655206165d775e675a02c9/exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219", size = 30371, upload-time = "2025-11-21T23:01:54.787Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8a/0e/97c33bf5009bdbac74fd2beace167cab3f978feb69cc36f1ef79360d6c4e/exceptiongroup-1.3.1-py3-none-any.whl", hash = "sha256:a7a39a3bd276781e98394987d3a5701d0c4edffb633bb7a5144577f82c773598", size = 16740, upload-time = "2025-11-21T23:01:53.443Z" }, +] + +[[package]] +name = "execnet" +version = "2.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/bf/89/780e11f9588d9e7128a3f87788354c7946a9cbb1401ad38a48c4db9a4f07/execnet-2.1.2.tar.gz", hash = "sha256:63d83bfdd9a23e35b9c6a3261412324f964c2ec8dcd8d3c6916ee9373e0befcd", size = 166622, upload-time = "2025-11-12T09:56:37.75Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ab/84/02fc1827e8cdded4aa65baef11296a9bbe595c474f0d6d758af082d849fd/execnet-2.1.2-py3-none-any.whl", hash = "sha256:67fba928dd5a544b783f6056f449e5e3931a5c378b128bc18501f7ea79e296ec", size = 40708, upload-time = "2025-11-12T09:56:36.333Z" }, +] + +[[package]] +name = "fasteners" +version = "0.19" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5f/d4/e834d929be54bfadb1f3e3b931c38e956aaa3b235a46a3c764c26c774902/fasteners-0.19.tar.gz", hash = "sha256:b4f37c3ac52d8a445af3a66bce57b33b5e90b97c696b7b984f530cf8f0ded09c", size = 24832, upload-time = "2023-09-19T17:11:20.228Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/61/bf/fd60001b3abc5222d8eaa4a204cd8c0ae78e75adc688f33ce4bf25b7fafa/fasteners-0.19-py3-none-any.whl", hash = "sha256:758819cb5d94cdedf4e836988b74de396ceacb8e2794d21f82d131fd9ee77237", size = 18679, upload-time = "2023-09-19T17:11:18.725Z" }, +] + +[[package]] +name = "iniconfig" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.10'", +] +sdist = { url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793, upload-time = "2025-03-19T20:09:59.721Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050, upload-time = "2025-03-19T20:10:01.071Z" }, +] + +[[package]] +name = "iniconfig" +version = "2.3.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.12'", + "python_full_version >= '3.10' and python_full_version < '3.12'", +] +sdist = { url = "https://files.pythonhosted.org/packages/01/e1/2069291243c926a2ff1cd706c7f3eeb9b62144bf60f77c9fb9ff2fb26bd3/iniconfig-2.3.1.tar.gz", hash = "sha256:67f4b9c50da0dedf52af349e7749a80a9057a5031199791b906c3bb3ae878960", size = 21209, upload-time = "2026-10-06T22:48:38.076Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/56/43/4ca9e49d27a1fcf6bece6f6aec0ea46bb9112489b93d4b688fb415457bdb/iniconfig-2.3.1-py3-none-any.whl", hash = "sha256:9121e2c1fdb355232495be3194c8dfe87ccc2d5dee45947b78e68f499790d7a7", size = 7552, upload-time = "2026-10-06T22:48:36.959Z" }, +] + +[[package]] +name = "meson" +version = "1.9.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/48/d8/6c1dd1c740b7d5fd73a6ad67900a09e3dd6642013f58fa9e22ae6faaffc7/meson-1.9.1.tar.gz", hash = "sha256:4e076606f2afff7881d195574bddcd8d89286f35a17b4977a216f535dc0c74ac", size = 5083044, upload-time = "2025-11-13T00:38:38.509Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9c/07/b48592d325cb86682829f05216e4efb2dc881762b8f1bafb48b57442307a/meson-1.9.1-py3-none-any.whl", hash = "sha256:f824ab770c041a202f532f69e114c971918ed2daff7ea56583d80642564598d0", size = 1030356, upload-time = "2025-09-22T18:39:37.458Z" }, +] + +[[package]] +name = "meson-python" +version = "0.18.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "meson" }, + { name = "packaging" }, + { name = "pyproject-metadata" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/26/bd/fdb26366443620f1a8a4d4ec7bfa37d1fbbe7bf737b257c205bbcf95ba95/meson_python-0.18.0.tar.gz", hash = "sha256:c56a99ec9df669a40662fe46960321af6e4b14106c14db228709c1628e23848d", size = 95630, upload-time = "2025-05-05T10:13:56.889Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/28/58/66db620a8a7ccb32633de9f403fe49f1b63c68ca94e5c340ec5cceeb9821/meson_python-0.18.0-py3-none-any.whl", hash = "sha256:3b0fe051551cc238f5febb873247c0949cd60ded556efa130aa57021804868e2", size = 28420, upload-time = "2025-05-05T10:13:55.218Z" }, +] + +[[package]] +name = "ninja" +version = "1.13.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ac/92/410b7917d16ab54c04b05cc32b9284803671d91cf79d33be6009c28d4ea8/ninja-1.13.2.tar.gz", hash = "sha256:525bfa3fc88aa30a4467df270fd5be6f9fcae8061d54d4df74ea1dc5abd5a975", size = 243739, upload-time = "2026-08-30T15:49:51.897Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b5/b8/90a9518f2264637084d6199bc20c2f3fb97fefc4c474d277720150c3bd6d/ninja-1.13.2-py3-none-macosx_10_9_universal2.whl", hash = "sha256:fd82e26c0706ad4ab88e5fdd26f3fab0a987a90f810160f6c322e752c6af298b", size = 306611, upload-time = "2026-08-30T15:49:28.267Z" }, + { url = "https://files.pythonhosted.org/packages/35/54/7368ce188625e39acc03ee362bb86cc9bfa6ad15e25c50889ae36e2889b3/ninja-1.13.2-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d775a5e43e9088f507a6250d57fcf5678eb31268c545feb5064ffeee33735622", size = 177180, upload-time = "2026-08-30T15:49:29.59Z" }, + { url = "https://files.pythonhosted.org/packages/80/1a/0b5601ece2a5de97253e7c7c442b70315333955593c2b55616fd17f1706f/ninja-1.13.2-py3-none-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:81d95081c0ad7c95f67bf682220361ed2a32659d7861b4766b03433c58f22516", size = 199485, upload-time = "2026-08-30T15:49:30.72Z" }, + { url = "https://files.pythonhosted.org/packages/48/23/fcbe234a66966e35928c47b86336f92a7612db4781665f4e5f5fddef9630/ninja-1.13.2-py3-none-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:227cbc3ae3e5e429692388103cae8c09451df086cd2d342dae0795af0d162547", size = 197676, upload-time = "2026-08-30T15:49:32.026Z" }, + { url = "https://files.pythonhosted.org/packages/24/eb/a6ca97ef0ff7bb8bdcb395ec65a716e65d7c1f40896c3afe0090bb3e1535/ninja-1.13.2-py3-none-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:1684c60d031c54c1d049541b64243c0c567dca5463dbd77682a8901780af293d", size = 187980, upload-time = "2026-08-30T15:49:33.372Z" }, + { url = "https://files.pythonhosted.org/packages/6e/53/ebfed7b689c338dd8ebeec9c0730c8d56821292f14e2536e5f3ef1a05744/ninja-1.13.2-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:65a24341b5ac09fcadcc37082660be40a94174e51a937fabf6e2cae26225fa2c", size = 183365, upload-time = "2026-08-30T15:49:34.53Z" }, + { url = "https://files.pythonhosted.org/packages/c7/d6/dcf06d7ab44ade992ae5aa1228feff317684b463a1bd47e8642b30ac922e/ninja-1.13.2-py3-none-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:aa3d2ae5706a2c4d1e93edc951d1c6cbb45107413c404f8fde1741239efbc9a0", size = 155089, upload-time = "2026-08-30T15:49:35.72Z" }, + { url = "https://files.pythonhosted.org/packages/e1/6b/6513c09c33382b17c05b4349b8e81437b18680d0d7ec6fb8f7edc29adda1/ninja-1.13.2-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:919572cbc3f233261ecd41fe1f3efc9d44aa02464a4588867e06a8b4f6f416ea", size = 152149, upload-time = "2026-08-30T15:49:36.971Z" }, + { url = "https://files.pythonhosted.org/packages/4d/70/d59fa4261f5ce586f43d8716de1da33d813afc116f0bf9173bf4cdbfdb2a/ninja-1.13.2-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:0e083700470c02ca154a855ae6d692d03564f5064cae52e113896f9ccc078418", size = 525392, upload-time = "2026-08-30T15:49:38.136Z" }, + { url = "https://files.pythonhosted.org/packages/37/04/c8c2dc5b2f5fee79a1691d490256b178b7e1af97d56769117422ae8a23cc/ninja-1.13.2-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:59d71c3e15b6b6f3d903eb0c27285544e0747ca59925ada7037bb1af781ad4b3", size = 466268, upload-time = "2026-08-30T15:49:39.479Z" }, + { url = "https://files.pythonhosted.org/packages/f3/6d/fce288647e53e96e0f0929a3c5b23986aae1b7101ea8889a3090237c5d13/ninja-1.13.2-py3-none-musllinux_1_2_i686.whl", hash = "sha256:f90f84affc441e219f15fe52532806c1c9dbd22fb66c3addddce88a3deaabab7", size = 605088, upload-time = "2026-08-30T15:49:40.826Z" }, + { url = "https://files.pythonhosted.org/packages/10/a2/d8eedd25d0ae80b9e874aea362013e67416877c8f732eb4d5e7c971fdb9c/ninja-1.13.2-py3-none-musllinux_1_2_ppc64le.whl", hash = "sha256:b2f687437fac460b27b7eadc99039b1163016fb4ba7276e2782a192d9f24ee0e", size = 610806, upload-time = "2026-08-30T15:49:42.168Z" }, + { url = "https://files.pythonhosted.org/packages/14/0f/696d96821fad1b5767fd311c1569dde8881a57412369bfe7b11bcbfde036/ninja-1.13.2-py3-none-musllinux_1_2_riscv64.whl", hash = "sha256:09de9ab04f7352f51570c73fd4913acb1e6c24be0a72cd8b80243d4d3ed04925", size = 533978, upload-time = "2026-08-30T15:49:43.451Z" }, + { url = "https://files.pythonhosted.org/packages/5d/69/28844ca579156776a202217a7cd66f60d06a0710a935e879bb89ce396ecc/ninja-1.13.2-py3-none-musllinux_1_2_s390x.whl", hash = "sha256:6a87bf42b123abe2f37737300185f0a303a891899da85d73a3613ee80547e578", size = 653822, upload-time = "2026-08-30T15:49:44.724Z" }, + { url = "https://files.pythonhosted.org/packages/f5/5f/c511f2952f94ab2966d60edd9c34e744ea32f2724b1184b62270bde55b3a/ninja-1.13.2-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:915bd482c4be41c75120fd67a22e0bb3f0fbb3bbc5f95b89787deadd59e27ef2", size = 544460, upload-time = "2026-08-30T15:49:46.29Z" }, + { url = "https://files.pythonhosted.org/packages/79/e7/fb0e828e89ac77ef77183a0834f17c4108e66088732fed86a3cc3c776a7a/ninja-1.13.2-py3-none-win32.whl", hash = "sha256:792cadbb9decfd1f776d4d0a6930feb46d08302eb57c176bcf26b09de5748e9f", size = 270319, upload-time = "2026-08-30T15:49:47.942Z" }, + { url = "https://files.pythonhosted.org/packages/3f/dd/3766b5f4d32e8a9b97d195496b0b01fbbe2e1a41669dab0cd6492a6ce199/ninja-1.13.2-py3-none-win_amd64.whl", hash = "sha256:1293f4078278b70d0ee4b6cc8f3a9e030656c9b2f59909970343c4fe76070118", size = 311798, upload-time = "2026-08-30T15:49:49.351Z" }, + { url = "https://files.pythonhosted.org/packages/b7/8d/59a31fa508070d042571d9d226b541a21000756817313f397da22287ad34/ninja-1.13.2-py3-none-win_arm64.whl", hash = "sha256:1db9852e528efa7702f5123969f86678663e46d57ff28ab13f5fd84d64a85fb1", size = 290620, upload-time = "2026-08-30T15:49:50.639Z" }, +] + +[[package]] +name = "packaging" +version = "26.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7d/fa/3944b40b07da9ce895c0e6303a5ab7d53da063554f534556b134a54d6093/packaging-26.3.tar.gz", hash = "sha256:94edc256424af38762eb31306eed28beb9f0efc50a8837492c9d6fd6004aed79", size = 313412, upload-time = "2026-08-04T18:15:28.737Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/63/34/ba1c580383c9eada3711951fef0795c80b829a078d72188184bcab9dd527/packaging-26.3-py3-none-any.whl", hash = "sha256:d7193f7c8e4e93f444fde0262bf90af30e16fa0ad0ad44cb553c87339b23cd1c", size = 129956, upload-time = "2026-08-04T18:15:27.159Z" }, +] + +[[package]] +name = "pluggy" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.10'", +] +sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, +] + +[[package]] +name = "pluggy" +version = "1.7.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.12'", + "python_full_version >= '3.10' and python_full_version < '3.12'", +] +sdist = { url = "https://files.pythonhosted.org/packages/bf/db/7fc19e6f2dc92a966727031389fc2e08b558f0f25eb7403c1119ad4713cd/pluggy-1.7.0.tar.gz", hash = "sha256:d1eaa46ebb595891b860ab086b4d09c8588af65ebd4361b8e8f4bb8920b90ba8", size = 123304, upload-time = "2026-10-15T09:50:58.343Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/40/9e/2b38731e0fc536806f16490e1a12d7f0dc2a1235aa8cc07bcc75416a7daa/pluggy-1.7.0-py3-none-any.whl", hash = "sha256:7dd7b0d8832ba3cb632c306926ded123429211b83641b35dc5c41ad2d34f9bec", size = 27082, upload-time = "2026-10-15T09:50:56.808Z" }, +] + +[[package]] +name = "psutil" +version = "7.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2a/80/336820c1ad9286a4ded7e845b2eccfcb27851ab8ac6abece774a6ff4d3de/psutil-7.0.0.tar.gz", hash = "sha256:7be9c3eba38beccb6495ea33afd982a44074b78f28c434a1f51cc07fd315c456", size = 497003, upload-time = "2025-02-13T21:54:07.946Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ed/e6/2d26234410f8b8abdbf891c9da62bee396583f713fb9f3325a4760875d22/psutil-7.0.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:101d71dc322e3cffd7cea0650b09b3d08b8e7c4109dd6809fe452dfd00e58b25", size = 238051, upload-time = "2025-02-13T21:54:12.36Z" }, + { url = "https://files.pythonhosted.org/packages/04/8b/30f930733afe425e3cbfc0e1468a30a18942350c1a8816acfade80c005c4/psutil-7.0.0-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:39db632f6bb862eeccf56660871433e111b6ea58f2caea825571951d4b6aa3da", size = 239535, upload-time = "2025-02-13T21:54:16.07Z" }, + { url = "https://files.pythonhosted.org/packages/2a/ed/d362e84620dd22876b55389248e522338ed1bf134a5edd3b8231d7207f6d/psutil-7.0.0-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1fcee592b4c6f146991ca55919ea3d1f8926497a713ed7faaf8225e174581e91", size = 275004, upload-time = "2025-02-13T21:54:18.662Z" }, + { url = "https://files.pythonhosted.org/packages/bf/b9/b0eb3f3cbcb734d930fdf839431606844a825b23eaf9a6ab371edac8162c/psutil-7.0.0-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b1388a4f6875d7e2aff5c4ca1cc16c545ed41dd8bb596cefea80111db353a34", size = 277986, upload-time = "2025-02-13T21:54:21.811Z" }, + { url = "https://files.pythonhosted.org/packages/eb/a2/709e0fe2f093556c17fbafda93ac032257242cabcc7ff3369e2cb76a97aa/psutil-7.0.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5f098451abc2828f7dc6b58d44b532b22f2088f4999a937557b603ce72b1993", size = 279544, upload-time = "2025-02-13T21:54:24.68Z" }, + { url = "https://files.pythonhosted.org/packages/50/e6/eecf58810b9d12e6427369784efe814a1eec0f492084ce8eb8f4d89d6d61/psutil-7.0.0-cp37-abi3-win32.whl", hash = "sha256:ba3fcef7523064a6c9da440fc4d6bd07da93ac726b5733c29027d7dc95b39d99", size = 241053, upload-time = "2025-02-13T21:54:34.31Z" }, + { url = "https://files.pythonhosted.org/packages/50/1b/6921afe68c74868b4c9fa424dad3be35b095e16687989ebbb50ce4fceb7c/psutil-7.0.0-cp37-abi3-win_amd64.whl", hash = "sha256:4cf3d4eb1aa9b348dec30105c55cd9b7d4629285735a102beb4441e38db90553", size = 244885, upload-time = "2025-02-13T21:54:37.486Z" }, +] + +[[package]] +name = "pygments" +version = "2.21.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/49/2e/ced460408999b33da6b31b0021b0f37d329e202d4169aeb164493778f25b/pygments-2.21.0.tar.gz", hash = "sha256:610ca751c9bc2492b38eb9a38a7fbc93edbbb2d7182edaf34e66ae493dee5c8c", size = 5005329, upload-time = "2026-08-17T08:02:48.824Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/71/46/17f022dd3e953bf20a04a028a21ec746d942f8d2af30fa0f124fa0e6a684/pygments-2.21.0-py3-none-any.whl", hash = "sha256:2363c69b61c4a97c838da3b130dcd6468f4848992b21a82f2a63ec34377137d9", size = 1250147, upload-time = "2026-08-17T08:02:44.912Z" }, +] + +[[package]] +name = "pyproject-metadata" +version = "0.12.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "packaging" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/4f/76/1cae539918a7b1746d624c2f01560b793c22cd8c081157505bb9bbf0e34d/pyproject_metadata-0.12.1.tar.gz", hash = "sha256:8809a4df6fe08279b39a8890669506ed3158e0617855ac9aff098fcbe772ae4c", size = 48457, upload-time = "2026-07-04T02:19:32.029Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b9/8e/d883718e872abc214ba053f60f7e6edc2abd5ffbd1544beb01a9c92ddd3c/pyproject_metadata-0.12.1-py3-none-any.whl", hash = "sha256:f7162d580a96386a8eb096da06215f981f547d1490f03055ef99e323bc2da427", size = 23452, upload-time = "2026-07-04T02:19:30.657Z" }, +] + +[[package]] +name = "pytest" +version = "8.4.2" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.10'", +] +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "exceptiongroup" }, + { name = "iniconfig", version = "2.1.0", source = { registry = "https://pypi.org/simple" } }, + { name = "packaging" }, + { name = "pluggy", version = "1.6.0", source = { registry = "https://pypi.org/simple" } }, + { name = "pygments" }, + { name = "tomli" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a3/5c/00a0e072241553e1a7496d638deababa67c5058571567b92a7eaa258397c/pytest-8.4.2.tar.gz", hash = "sha256:86c0d0b93306b961d58d62a4db4879f27fe25513d4b969df351abdddb3c30e01", size = 1519618, upload-time = "2025-09-04T14:34:22.711Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a8/a4/20da314d277121d6534b3a980b29035dcd51e6744bd79075a6ce8fa4eb8d/pytest-8.4.2-py3-none-any.whl", hash = "sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79", size = 365750, upload-time = "2025-09-04T14:34:20.226Z" }, +] + +[[package]] +name = "pytest" +version = "9.0.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.12'", + "python_full_version >= '3.10' and python_full_version < '3.12'", +] +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, + { name = "iniconfig", version = "2.3.1", source = { registry = "https://pypi.org/simple" } }, + { name = "packaging" }, + { name = "pluggy", version = "1.7.0", source = { registry = "https://pypi.org/simple" } }, + { name = "pygments" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/07/56/f013048ac4bc4c1d9be45afd4ab209ea62822fb1598f40687e6bf45dcea4/pytest-9.0.1.tar.gz", hash = "sha256:3e9c069ea73583e255c3b21cf46b8d3c56f6e3a1a8f6da94ccb0fcf57b9d73c8", size = 1564125, upload-time = "2025-11-12T13:05:09.333Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0b/8b/6300fb80f858cda1c51ffa17075df5d846757081d11ab4aa35cef9e6258b/pytest-9.0.1-py3-none-any.whl", hash = "sha256:67be0030d194df2dfa7b556f2e56fb3c3315bd5c8822c6951162b92b32ce7dad", size = 373668, upload-time = "2025-11-12T13:05:07.379Z" }, +] + +[[package]] +name = "pytest-asyncio" +version = "1.2.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.10'", +] +dependencies = [ + { name = "backports-asyncio-runner" }, + { name = "pytest", version = "8.4.2", source = { registry = "https://pypi.org/simple" } }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/42/86/9e3c5f48f7b7b638b216e4b9e645f54d199d7abbbab7a64a13b4e12ba10f/pytest_asyncio-1.2.0.tar.gz", hash = "sha256:c609a64a2a8768462d0c99811ddb8bd2583c33fd33cf7f21af1c142e824ffb57", size = 50119, upload-time = "2025-09-12T07:33:53.816Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/93/2fa34714b7a4ae72f2f8dad66ba17dd9a2c793220719e736dda28b7aec27/pytest_asyncio-1.2.0-py3-none-any.whl", hash = "sha256:8e17ae5e46d8e7efe51ab6494dd2010f4ca8dae51652aa3c8d55acf50bfb2e99", size = 15095, upload-time = "2025-09-12T07:33:52.639Z" }, +] + +[[package]] +name = "pytest-asyncio" +version = "1.3.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.12'", + "python_full_version >= '3.10' and python_full_version < '3.12'", +] +dependencies = [ + { name = "backports-asyncio-runner", marker = "python_full_version < '3.11'" }, + { name = "pytest", version = "9.0.1", source = { registry = "https://pypi.org/simple" } }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/90/2c/8af215c0f776415f3590cac4f9086ccefd6fd463befeae41cd4d3f193e5a/pytest_asyncio-1.3.0.tar.gz", hash = "sha256:d7f52f36d231b80ee124cd216ffb19369aa168fc10095013c6b014a34d3ee9e5", size = 50087, upload-time = "2025-11-10T16:07:47.256Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e5/35/f8b19922b6a25bc0880171a2f1a003eaeb93657475193ab516fd87cac9da/pytest_asyncio-1.3.0-py3-none-any.whl", hash = "sha256:611e26147c7f77640e6d0a92a38ed17c3e9848063698d5c93d5aa7aa11cebff5", size = 15075, upload-time = "2025-11-10T16:07:45.537Z" }, +] + +[[package]] +name = "pytest-xdist" +version = "3.8.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "execnet" }, + { name = "pytest", version = "8.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "pytest", version = "9.0.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/78/b4/439b179d1ff526791eb921115fca8e44e596a13efeda518b9d845a619450/pytest_xdist-3.8.0.tar.gz", hash = "sha256:7e578125ec9bc6050861aa93f2d59f1d8d085595d6551c2c90b6f4fad8d3a9f1", size = 88069, upload-time = "2025-07-01T13:30:59.346Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ca/31/d4e37e9e550c2b92a9cbc2e4d0b7420a27224968580b5a447f420847c975/pytest_xdist-3.8.0-py3-none-any.whl", hash = "sha256:202ca578cfeb7370784a8c33d6d05bc6e13b4f25b5053c30a152269fd10f0b88", size = 46396, upload-time = "2025-07-01T13:30:56.632Z" }, +] + +[[package]] +name = "tomli" +version = "2.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b0/78/9ad63712633ed3ab5cc1a648d863d7e7da371e9425e209555a0fe711b695/tomli-2.5.0.tar.gz", hash = "sha256:264507556cd8b8c8e7c6ee037cdf443a463f03f4c958e57195e3d369711b8ff6", size = 17662, upload-time = "2026-10-07T12:23:37.892Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/22/a6/ab99b60ee52acd949684febabc3005d0045d0f66bebd9cdebd67372d26dd/tomli-2.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c4dc1c1781f2f716de763d1e9a7b34c6a894e167e291c7c5d16c72f7a9538545", size = 163901, upload-time = "2026-10-07T12:22:15.601Z" }, + { url = "https://files.pythonhosted.org/packages/bc/00/ee01b7ed4579180fff07142d290257f25ba786f23f3ec6005f620933c2f5/tomli-2.5.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:eff8babca5a7999bc137acbc7482a8b7e17ffca5075ab41f5d770ab408c7bfef", size = 163756, upload-time = "2026-10-07T12:22:16.957Z" }, + { url = "https://files.pythonhosted.org/packages/72/c2/4efebf65372f6583185f79799312109dddb61102d47e5c33dcfd1a297aca/tomli-2.5.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:86665cee9c4835b7a7f1e8ec2c719b5258d4dc782887aded5a8ae7352a96843b", size = 268038, upload-time = "2026-10-07T12:22:18.135Z" }, + { url = "https://files.pythonhosted.org/packages/53/07/5850468e925d898abb36038666f9c333a94d2a223e802a8ba5b6d319d23f/tomli-2.5.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d7e369fd63331746182360977b1892bfc215476a30d61612d732425311639f56", size = 276422, upload-time = "2026-10-07T12:22:19.567Z" }, + { url = "https://files.pythonhosted.org/packages/b4/87/f293984cdcf83c054196d4fd3dad44fc68ae55b4b8c44bc76cef360c3150/tomli-2.5.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7ad1ea345759240d6463efa0ed1c704402752e49aa21476620738d74d72d8aa1", size = 272616, upload-time = "2026-10-07T12:22:20.794Z" }, + { url = "https://files.pythonhosted.org/packages/ce/ce/db582886b3c1219d3fec93ebd669332482e5aee7a91e0f7838d84f2d1759/tomli-2.5.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:96243987194634bd411066ce40c952e108f86af04db533ecd8ac3ff2a85b1885", size = 276593, upload-time = "2026-10-07T12:22:22.12Z" }, + { url = "https://files.pythonhosted.org/packages/bf/72/7619b87dea4261fc27dd7b54c4461c129c1f7d9bb7ba3aec89c797a431b8/tomli-2.5.0-cp311-cp311-win32.whl", hash = "sha256:610b27d99f28ec5f191c7064a48f3ddb179a1fe6ca73d571483ae859f57b605e", size = 101830, upload-time = "2026-10-07T12:22:23.651Z" }, + { url = "https://files.pythonhosted.org/packages/1e/74/220106da34502304b6751a2a9b8a9fbca6c3fd47e737a2e2e3da7c61c9db/tomli-2.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:c804ae44fe7b4bab5da295e4f980a1ff04670bca9d23fe0a4e887e08ebd741a8", size = 112742, upload-time = "2026-10-07T12:22:24.972Z" }, + { url = "https://files.pythonhosted.org/packages/27/99/7d9c8b41837a7773613e169504147375c157a290167aa59ad74a085f521f/tomli-2.5.0-cp311-cp311-win_arm64.whl", hash = "sha256:cfac177ebd6236003846ea339981f71457cb6eb748f23381eb257e45092e3980", size = 109332, upload-time = "2026-10-07T12:22:26.117Z" }, + { url = "https://files.pythonhosted.org/packages/52/ed/7baa86f87493646a594de388c7c1c40a39dd0461f7e9c0359cbeefc91fe8/tomli-2.5.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:1f4a40d03fb9f63424f0979855bdeaf44dd7696b8d59501822c10ed30ba532df", size = 164854, upload-time = "2026-10-07T12:22:27.444Z" }, + { url = "https://files.pythonhosted.org/packages/a5/b1/44c0341f2224397855723c7a8a39f718ea6fcbcc3dacc66e5aeca0f334e3/tomli-2.5.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9ebf8d19b17bd0daeb7b7dec81a946a439b753942fd0210d6e96c532249eea6b", size = 164074, upload-time = "2026-10-07T12:22:28.679Z" }, + { url = "https://files.pythonhosted.org/packages/23/04/e2d5b7d3fba47adedb23de616c16d428ea076c79a3d8e1d95d649ffe197e/tomli-2.5.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bf0b5e8e0f68ebb494356e577c06c139161efd8d3b9050f93b39b7c26cc54ff0", size = 274274, upload-time = "2026-10-07T12:22:29.804Z" }, + { url = "https://files.pythonhosted.org/packages/43/90/6090e706ff27a6f89f4a40578e3324b95c3cd8c4150868aabf33a8f414c3/tomli-2.5.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6cf74416bdc94ae458b14e37286c1073081850ac8459a00d0c5efef5d44294c6", size = 286435, upload-time = "2026-10-07T12:22:31.297Z" }, + { url = "https://files.pythonhosted.org/packages/0a/9e/a2c40768df16c408f22430afb0a73e9d7e5f79c950884954649d1146b74d/tomli-2.5.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:61ea1ebe1e55a34ea8199cc8dbff398d35027b82271c8ac4802fd3a1fd5b1bcc", size = 278119, upload-time = "2026-10-07T12:22:32.601Z" }, + { url = "https://files.pythonhosted.org/packages/12/25/3c0cb485b98e9cfac495629b1c93c87ccf0b72fbe9d2689fd8fe62c6d5a3/tomli-2.5.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ed53f7e89bb04f6d9e8e7799112360b0c4d5cbff067de0814c98c37c39b920f7", size = 286177, upload-time = "2026-10-07T12:22:33.745Z" }, + { url = "https://files.pythonhosted.org/packages/77/8b/0144c65f0e37e51c18d04ae15c21b19431c165002d0131fe9aa8b0b8b1e8/tomli-2.5.0-cp312-cp312-win32.whl", hash = "sha256:e7ad033e27a516a233bea839cdb77b80146facb3b4f40bf02cd0cac165cdd5c2", size = 102760, upload-time = "2026-10-07T12:22:34.887Z" }, + { url = "https://files.pythonhosted.org/packages/de/32/5d6d8f42fc9a05fce69354e00ff256484192f5f2fc9a2165718fa0de61ec/tomli-2.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:bd05de8c1698f8413dd7d869492693a0bf2211543b787ac78cd5e7536af1a6d7", size = 112722, upload-time = "2026-10-07T12:22:36.162Z" }, + { url = "https://files.pythonhosted.org/packages/30/65/df18032218db0fb9b769fb23c8039a051f15c811993995ea04c350273a32/tomli-2.5.0-cp312-cp312-win_arm64.whl", hash = "sha256:069435bd5480429b98c5e5afb02ab21c219b6f0064680671c6dc0d46817346ea", size = 109534, upload-time = "2026-10-07T12:22:37.296Z" }, + { url = "https://files.pythonhosted.org/packages/42/e5/51736d70da209350969e15aca5c5ab6e2ce1ea87a0a892a6c13aec172a86/tomli-2.5.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:943276cf269e0071948d9ff697159c1735e623c1151d88abb09b74659ef0cbea", size = 163328, upload-time = "2026-10-07T12:22:38.373Z" }, + { url = "https://files.pythonhosted.org/packages/ec/55/086f80dab4ab497602644274e6dea7ec5dd0b4e262e443a8ad3bb7edee2d/tomli-2.5.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:463b16086865b97facd8d0b3fb4cb7c544e3f58d2a69dc3113d6db9653fdb043", size = 162246, upload-time = "2026-10-07T12:22:39.673Z" }, + { url = "https://files.pythonhosted.org/packages/aa/eb/3ecc94459f3635c92321f4e7bde571323fdb2267c50e19e3188a281eae3b/tomli-2.5.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1245a6638fc4bb0a60af38a7d45413db34a13842027c77597c712c998c62fdf0", size = 272655, upload-time = "2026-10-07T12:22:41.08Z" }, + { url = "https://files.pythonhosted.org/packages/c0/d7/494fd1f0c37a621f1ad9975c2efadb523e8101f144ed6edb2e7fe64738f2/tomli-2.5.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5d8bac3d603c97e6854424e5b2b5b741bdbde387e09f162fb0446812b4a8362b", size = 283595, upload-time = "2026-10-07T12:22:42.222Z" }, + { url = "https://files.pythonhosted.org/packages/70/51/bb8d62b1317e6640866f6949b2d5855e5300f2c99d46de1cd245570bba65/tomli-2.5.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:21e4cae4114aba25aa0d4f85cdf486d290fb35c0954d7bba536248da64d43066", size = 276253, upload-time = "2026-10-07T12:22:43.625Z" }, + { url = "https://files.pythonhosted.org/packages/66/f4/f46bd7f0763cd47de2db697dca9257c6a4adfd1a93b018cc75c8190ed5a8/tomli-2.5.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:bbaefc84548d754be821bba7c4141c4787dda182f9e77f2f87b71213529efa7b", size = 283582, upload-time = "2026-10-07T12:22:44.983Z" }, + { url = "https://files.pythonhosted.org/packages/ac/03/70f2bcb2923a6db37818d917e124270a7f4cfd38ea576f5aa753a91c0ef5/tomli-2.5.0-cp313-cp313-win32.whl", hash = "sha256:abdbf6313b8d9efe157edeb7ab6eae4de064b1300ad31abf73755154b30abe68", size = 102628, upload-time = "2026-10-07T12:22:46.508Z" }, + { url = "https://files.pythonhosted.org/packages/dc/98/d52024bb5b0ff68b4f0d276d867f634c84a67319a7e9f6b7708a37742333/tomli-2.5.0-cp313-cp313-win_amd64.whl", hash = "sha256:fd4dc129784e0c5335bd4e61dfcc4487499a013419e655cf2da1d091b7e0efdc", size = 113301, upload-time = "2026-10-07T12:22:47.647Z" }, + { url = "https://files.pythonhosted.org/packages/6f/f2/540db3a70572a8c23a28aba3e9c358ce0ffffbafc990905c1343aa265b31/tomli-2.5.0-cp313-cp313-win_arm64.whl", hash = "sha256:69491c143d2fe063046e0301e62a810bed338fa4d1ce0fd870c27dc1e09b0d84", size = 109744, upload-time = "2026-10-07T12:22:48.925Z" }, + { url = "https://files.pythonhosted.org/packages/e4/49/caf6b307766eb9567664a8707e9d6be5fcc0e8903f18781c6677a60d80c7/tomli-2.5.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:d3182ee2d887e507bd67319a0a61105d1dd33facc111329559a233b772c1a105", size = 162899, upload-time = "2026-10-07T12:22:50.088Z" }, + { url = "https://files.pythonhosted.org/packages/d3/c8/68cfce773a2733a49c74f99d627fb461bd990756860099eac25617889585/tomli-2.5.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:521345fd1f19d45b8df87657aaa38b6f2ca3800059fadf428e7ebf479a383646", size = 162080, upload-time = "2026-10-07T12:22:51.558Z" }, + { url = "https://files.pythonhosted.org/packages/7e/b2/e5bb8651fdad593f670501a7d718b1a7f73f064d44dea15e04c04dfef45d/tomli-2.5.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6e95c7614e705bfe2b04b27aa124adec59752d15813df37e2156747cab3a006b", size = 273380, upload-time = "2026-10-07T12:22:52.918Z" }, + { url = "https://files.pythonhosted.org/packages/8d/d2/9e2d7f8b1dfe0e2b34c245986ebd55c4c553ea4ce6c47c443b332673253f/tomli-2.5.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7ac2027d37c3afbdf4bdd377f2676f6f1d2122a5be1f1137b49dced590b37e75", size = 283228, upload-time = "2026-10-07T12:22:54.173Z" }, + { url = "https://files.pythonhosted.org/packages/ba/df/ec7b876b7b1a2718bd74a3743c076fff565b04029ba33e8f61fac262739f/tomli-2.5.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c414be4ed9d3cac80c42e348fa5a956117d1a48227f48026e31f59cb4a7671eb", size = 277189, upload-time = "2026-10-07T12:22:55.342Z" }, + { url = "https://files.pythonhosted.org/packages/7d/7b/e192d9eed0b9cb80da799f4d77052297fb9a2c3cc9b19f571f56ea88add6/tomli-2.5.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:9b03d7dc168353b4132965bde20feceabaa470e570c6f59660dfae59b1f9eeb3", size = 283632, upload-time = "2026-10-07T12:22:56.735Z" }, + { url = "https://files.pythonhosted.org/packages/84/50/ff94454e75461d75623e47401ed323d65c10aab8fe9033242c20cd2fdf32/tomli-2.5.0-cp314-cp314-win32.whl", hash = "sha256:6f041843c4d3a37245c0c056fd955b186bf8b1fb85690cbe40b81230891dc34b", size = 103535, upload-time = "2026-10-07T12:22:58.084Z" }, + { url = "https://files.pythonhosted.org/packages/54/0b/bdacf05f963bd6026ebf6eeb0beda847d1d60e03e440725c64a4e08a0afd/tomli-2.5.0-cp314-cp314-win_amd64.whl", hash = "sha256:f4b653094e18f9031102d3a1da5c729c8f222d85225b18037dac621695e46e1a", size = 114621, upload-time = "2026-10-07T12:22:59.2Z" }, + { url = "https://files.pythonhosted.org/packages/61/99/53f438fa6ae4f9d4ed0ddde3e7242b3bdc34b48c8f9948b72b9e9b127676/tomli-2.5.0-cp314-cp314-win_arm64.whl", hash = "sha256:3f89d10c1ff6a38d992c27fc8a4816af71a909e08a40ec66934240b1e74347c3", size = 111572, upload-time = "2026-10-07T12:23:00.479Z" }, + { url = "https://files.pythonhosted.org/packages/b9/20/1f88f19427d380a40e90a770e087489eaafe4aeee070ae88ed2bbec00acd/tomli-2.5.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:e9e15b4a6c7dd6b85b5fbab29488a73f1f70de516942308daa266bf0e0aeb0d4", size = 171814, upload-time = "2026-10-07T12:23:01.914Z" }, + { url = "https://files.pythonhosted.org/packages/d0/56/cbe5079c9f9a54b9b3e27fc82f08f3cb36edee75561679f53d2380c801d6/tomli-2.5.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:e12bbcd32897272fb05929110362ae9ff4c1b9bb26bd9e971e71dcd3275b4c3d", size = 171324, upload-time = "2026-10-07T12:23:03.18Z" }, + { url = "https://files.pythonhosted.org/packages/2b/30/1d53fd3b0f1cb3ba542e345ec32c26aefdddc4e829e4f3429af8a4f27782/tomli-2.5.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:20aa36de8f2cf87237143bc1fa1aae8d6612c09118f4da21c6a684db5dd1f6f9", size = 297441, upload-time = "2026-10-07T12:23:04.345Z" }, + { url = "https://files.pythonhosted.org/packages/66/d9/0800acb6a111686f764c1b91ef15cc42a20a66a46013bb42220f1d2c61c1/tomli-2.5.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:22185fad8a1e622f064e78008018a0dd3323550dcb479cb7a1d296888d74024f", size = 307476, upload-time = "2026-10-07T12:23:05.671Z" }, + { url = "https://files.pythonhosted.org/packages/e8/63/30a8f3cd51b5bec37f04744bad0b0dc6160df84aad4f27b0e9283d66f221/tomli-2.5.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:984012f71908165449a951de2050d52f276bfe3aa5d5f570f63ddad814370374", size = 296113, upload-time = "2026-10-07T12:23:07.202Z" }, + { url = "https://files.pythonhosted.org/packages/ab/18/0b9ffc597e69c5a1e20a7823cb60d54b39a9f54e91edcb8574f022186758/tomli-2.5.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:f79203b3965b4000e91808aaa7c040206093f2b8bf86f455982f2274c9ccf442", size = 307725, upload-time = "2026-10-07T12:23:08.508Z" }, + { url = "https://files.pythonhosted.org/packages/ab/c7/18f8baae0b5607a60e8e19b4a7fedee43a8ff6458e3896dcbbadeeac9c22/tomli-2.5.0-cp314-cp314t-win32.whl", hash = "sha256:91294a9fb94a75542f6e46e4a2ae709bd8d9b51134098cae5cf3bea5478b6d03", size = 108546, upload-time = "2026-10-07T12:23:09.956Z" }, + { url = "https://files.pythonhosted.org/packages/72/34/4cca9739254130627bde87500b3f2b512154fe2f278efa7e2a5e10ad4bcb/tomli-2.5.0-cp314-cp314t-win_amd64.whl", hash = "sha256:f15e3e0b835a6d68b10c86bf80a3149780498d6911c93c3ffd1861d19f9200f1", size = 117814, upload-time = "2026-10-07T12:23:11.486Z" }, + { url = "https://files.pythonhosted.org/packages/7d/fb/afa530d47dd80a78fce43beac6bc6e00f84558eafcffbc6f37b21e80d056/tomli-2.5.0-cp314-cp314t-win_arm64.whl", hash = "sha256:6664b7ae7af7294256c53960a6103077f4914cec8ff98479c352f622c6f6b2f0", size = 115188, upload-time = "2026-10-07T12:23:12.728Z" }, + { url = "https://files.pythonhosted.org/packages/66/98/316fdc00f8c0939e6fe50461dd343c162d3ad51d1286eb25b7db54361d50/tomli-2.5.0-cp315-cp315-macosx_10_15_x86_64.whl", hash = "sha256:a525685c2f97da40762b8695eb7aa0af4c8344ca1905c73e4e29cb04d34607dc", size = 162775, upload-time = "2026-10-07T12:23:13.941Z" }, + { url = "https://files.pythonhosted.org/packages/c5/22/7b10fa5bb01c9539f53f69b619361b19350acc73657772ea7ac70ba309a8/tomli-2.5.0-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:9dbb18c1cfb2f6517942fc9314437f66aa06d94436ffb1f06102ef3572f35276", size = 161406, upload-time = "2026-10-07T12:23:15.215Z" }, + { url = "https://files.pythonhosted.org/packages/9c/e7/1a069d86dfd20f1f84f71c63faed9f83c1d890bc06c27d82dc7d888fb573/tomli-2.5.0-cp315-cp315-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:752e8b1aa6a4367ef8bf6a1a1e005540f7ed055ba36d7193796812ca5404eb52", size = 273855, upload-time = "2026-10-07T12:23:16.471Z" }, + { url = "https://files.pythonhosted.org/packages/ae/83/d1ef43d1687d092ab9c235455c76e6e709483b346b056f086095c7c263a5/tomli-2.5.0-cp315-cp315-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c47300f9bf791808f77d82747691c4bb09cb14bdf3060cca99b42cdc4361d5a7", size = 284910, upload-time = "2026-10-07T12:23:18.166Z" }, + { url = "https://files.pythonhosted.org/packages/cc/05/f4d9cf7de61822ece0c3873f30d291e324911c71a378b8bfe5ced13fd9f5/tomli-2.5.0-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:19b0dd8749f4ea2f112c5fcfb3c5248390c899d7e2e173f1d91abee1fa0ff391", size = 277723, upload-time = "2026-10-07T12:23:19.355Z" }, + { url = "https://files.pythonhosted.org/packages/42/28/78262493141fa543151cf005760c3cb01d09fc28a11f993c05109902cb8c/tomli-2.5.0-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:57b1c3b01fab802e2899bc3d168dca320e14165e2fd9fd584760fb4ca5826859", size = 285115, upload-time = "2026-10-07T12:23:20.698Z" }, + { url = "https://files.pythonhosted.org/packages/1a/b9/e1dab9a30bcb677b5cc5cee810609cfd64f24306a3055767dd3fda00b1e0/tomli-2.5.0-cp315-cp315-win32.whl", hash = "sha256:667e521b37a6c5ccaa044202c235b530f90177ffe2cd4a64ecc213c7dd535feb", size = 103475, upload-time = "2026-10-07T12:23:21.941Z" }, + { url = "https://files.pythonhosted.org/packages/4c/bd/31a3790c11d6ea95fcf5e6022ac0f8d0543c9b61120b730fc481bd43d3b4/tomli-2.5.0-cp315-cp315-win_amd64.whl", hash = "sha256:d747252933c8a65ef6bd8da0fbb7ce28a90eb6119d8cd00772cd528aa07b68d5", size = 114589, upload-time = "2026-10-07T12:23:23.098Z" }, + { url = "https://files.pythonhosted.org/packages/47/a2/4f6310fa699364f0e3af7ee3af88dddd9af066d33e716a0265bbe2b3ea84/tomli-2.5.0-cp315-cp315-win_arm64.whl", hash = "sha256:75dbcde8751b0a960aa3de173aa5e894d590755c6d7758b7e774c06f1dc3cbdd", size = 111493, upload-time = "2026-10-07T12:23:24.233Z" }, + { url = "https://files.pythonhosted.org/packages/68/14/00853f0b396d8971107ae1921bb5b322fdee1650d2f16bf06c20adb532e5/tomli-2.5.0-cp315-cp315t-macosx_10_15_x86_64.whl", hash = "sha256:2419c2a189551987b59d80e63ec355671283336f41c6b9b89462df679c7d0c57", size = 171380, upload-time = "2026-10-07T12:23:25.512Z" }, + { url = "https://files.pythonhosted.org/packages/89/ad/fa6949321dadee46b27363974fb197b94c911c3b0f7a5fd26d7dc18fc2a0/tomli-2.5.0-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:0dc598040da8d42cf20f0be588ed7004f46db12a0ac6c32e03a59dccedaaadcd", size = 170553, upload-time = "2026-10-07T12:23:26.855Z" }, + { url = "https://files.pythonhosted.org/packages/53/aa/3056c919eb3e084df3752b2cf5f865dcc04af0b27dba2f66d7b28af4633a/tomli-2.5.0-cp315-cp315t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:49096930c8d886c9bbdab62d2d0d17ce823ddeea522309a190b36245d5b49e01", size = 294428, upload-time = "2026-10-07T12:23:28.132Z" }, + { url = "https://files.pythonhosted.org/packages/96/b2/faeeb5d8769ea3832021d73e892c8391eae7b4b4f8b55a789127bd8b18a9/tomli-2.5.0-cp315-cp315t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b8ade5023067f99fe72b88accd30d0ea05a158e9e32a11f124e731ea9695313f", size = 304909, upload-time = "2026-10-07T12:23:29.381Z" }, + { url = "https://files.pythonhosted.org/packages/f6/52/f094c09e73fb654b621716d019acb5d29bdfd1be01df80c281d552bda48d/tomli-2.5.0-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:b69564772b5c8f22ea5f498dff08cfa825045b4d4c4400529000bdf818aa3b2a", size = 293220, upload-time = "2026-10-07T12:23:30.608Z" }, + { url = "https://files.pythonhosted.org/packages/86/f5/0c30541078ca4b505ce3bd76ed931facbfec524dd018535d691d1af0a6d2/tomli-2.5.0-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:8ff3a2ca028c7eee0c777f9a092038d0a594a9fa04e215f929a22c329e2cb142", size = 305705, upload-time = "2026-10-07T12:23:32.181Z" }, + { url = "https://files.pythonhosted.org/packages/05/74/590e7d19d6a118fc5cc5704ff358e21d95b8573f6b9443b1519f29ca8825/tomli-2.5.0-cp315-cp315t-win32.whl", hash = "sha256:62fc1bc8eb03e3a9cadfca713d65614ed8e09d974a283295ffe3a831976b4dc5", size = 108432, upload-time = "2026-10-07T12:23:33.496Z" }, + { url = "https://files.pythonhosted.org/packages/1c/b8/63a75cfb27a17c38550e44025d3a6e7be64516fd8608a3b75703bf37d81b/tomli-2.5.0-cp315-cp315t-win_amd64.whl", hash = "sha256:f3fcbc57b1791fa6cbe5d8434179d51de12be1a4811469529f47f6e7487a2571", size = 117281, upload-time = "2026-10-07T12:23:34.648Z" }, + { url = "https://files.pythonhosted.org/packages/72/01/e8c1debb2173973372934c68fc8e46170ab60ef23ed4592dff4dec6e8993/tomli-2.5.0-cp315-cp315t-win_arm64.whl", hash = "sha256:d2ba24db8a9376921b5e87b4762b9adb0f3f1deaea68f2b8b0bb2c11efb9c3e7", size = 115069, upload-time = "2026-10-07T12:23:35.77Z" }, + { url = "https://files.pythonhosted.org/packages/60/3f/3e3f8fd0919249b0200c80fbc4f9a1e70be19f9883da71dfb7f8b9ab8aca/tomli-2.5.0-py3-none-any.whl", hash = "sha256:32a7b79ac57a2e83670ce329ccf675798bc5a2094783a63676866b70503f2e2b", size = 14765, upload-time = "2026-10-07T12:23:36.875Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f6/cc/6253133b5bb138fc3306cebfbda2c520f545d36b5be2c7255cc528bb45d6/typing_extensions-4.16.0.tar.gz", hash = "sha256:dc983d19a509c94dba722ee6abd33940f7c05a89e243c47e907eb4db6f1a43e5", size = 113555, upload-time = "2026-07-02T08:40:05.92Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/49/d3/b8441a820a491ddfc024b0b0cf0393375b75ea13866d9c66727e54c2fc80/typing_extensions-4.16.0-py3-none-any.whl", hash = "sha256:481caa481374e813c1b176ada14e97f1f67a4539ce9cfeb3f350d78d6370c2e8", size = 45571, upload-time = "2026-07-02T08:40:04.659Z" }, +]