diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 00000000..846f35db --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,137 @@ +# WorldEngine Development Guide + +## Project Overview + +WorldEngine is a procedural world generator that simulates realistic terrain through plate tectonics, erosion, climate systems, and biomes. It generates both world data files (protobuf/hdf5) and visualization images (PNG). The project emphasizes scientific simulation over gaming shortcuts. + +## Architecture + +### Core Data Flow (Pipeline Pattern) + +World generation follows a strict sequential pipeline in `worldengine/generation.py`: + +1. **Plates** (`plates.py` + PyPlatec C extension) → generates elevation and plate boundaries +2. **Temperature** → based on latitude and elevation +3. **Precipitation** → considering rain shadow effects and ocean proximity +4. **Erosion** → modifies elevation based on water flow +5. **Hydrology** → watermap and river systems via recursive droplet simulation +6. **Humidity** → combines precipitation, irrigation, and distance to water +7. **Permeability** → soil absorption rates +8. **Biome** → Holdridge life zones model classification +9. **Icecap** → polar ice coverage + +Each simulation in `worldengine/simulations/` is a class with `is_applicable()` and `execute(world, seed)` methods. Simulations modify the World object's layers in-place. + +### World Model (`worldengine/model/world.py`) + +The `World` class is the central data structure: +- **Layers** stored as numpy arrays in `world.layers['name']` dict (elevation, ocean, precipitation, temperature, humidity, biome, etc.) +- **LayerWithThresholds** for categorical data (e.g., elevation thresholds: sea/plain/hill/mountain) +- **LayerWithQuantiles** for distribution-based data (e.g., humidity quantiles) +- **Generation metadata**: seed, n_plates, ocean_level, step, temps/humids thresholds + +Access patterns: +- Direct: `world.layers['elevation'].data[y, x]` (numpy array indexing) +- Helper: `world.elevation_at((x, y))` (tuple coordinates) +- Boolean checks: `world.is_ocean((x, y))`, `world.has_biome()` + +### Step System (`worldengine/step.py`) + +Controls generation depth via `Step` enum: +- `plates`: Only plate simulation +- `precipitations`: Through precipitation/temperature +- `full`: Complete pipeline including biomes + +Check flags: `step.include_precipitations`, `step.include_erosion`, `step.include_biome` + +### Biomes (`worldengine/biome.py`) + +Metaclass-based registry pattern: +- Each biome is a class inheriting from `Biome` (e.g., `class TropicalRainForest(Biome)`) +- Auto-registration via `_BiomeMetaclass` converts CamelCase to "tropical rain forest" +- Access: `Biome.by_name("boreal forest")`, `Biome.all_names()` +- Stored as strings in world.layers['biome'], converted to indices for protobuf serialization + +## Key Development Patterns + +### NumPy-First Operations + +Always prefer vectorized NumPy operations over Python loops: + +```python +# Good: Vectorized ocean detection +ocean = numpy.zeros(elevation.shape, dtype=bool) + +# Avoid: Cell-by-cell iteration unless simulating physical processes +for y in range(height): + for x in range(width): # Only when simulating droplets, adjacency, etc. +``` + +### Seed Management + +Deterministic generation requires careful seed handling: +- Main seed → numpy RNG → 100 sub-seeds (one per simulation) +- See `seed_dict` in `generate_world()` for allocation +- Never use global random state in simulations + +### Coordinate Systems + +Two conventions coexist: +- **Tuple style**: `(x, y)` for method parameters (e.g., `world.elevation_at((x, y))`) +- **NumPy style**: `[y, x]` for array indexing (e.g., `world.layers['elevation'].data[y, x]`) + +### Serialization + +Two formats supported: +- **Protobuf** (default): `World.proto` → `World_pb2.py` (regenerate with `protoc`) +- **HDF5**: `worldengine/hdf5_serialization.py` (requires h5py, optional dependency) + +Loading worlds: `world = World.from_pickle_file(filename)` or `load_world_from_hdf5()` + +## Testing + +### Running Tests + +```bash +# All tests +nosetests tests -v + +# Specific test module +nosetests tests/biome_test.py -v + +# With coverage +coverage run --source worldengine --branch $(which nosetests) tests -v +coverage report --omit=worldengine/tests/* --show-missing +``` + +### Test Structure + +- Tests in `tests/*_test.py` use unittest framework +- `TestBase` in `tests/draw_test.py` provides common fixtures +- Visual regression via `tests/blessed_images/` (compare generated images) +- No mocking of NumPy/PyPlatec; tests use actual generation + +## CLI Entry Point + +`worldengine/cli/main.py` provides commands: +- `worldengine world -s SEED -n NAME` → generate world +- `worldengine ancient_map -w FILE` → render ancient-style map +- `worldengine info -w FILE` → display world metadata + +All CLI commands route through functions that call `world_gen()` or draw operations. + +## Critical Dependencies + +- **PyPlatec**: C extension for plate tectonics (fails gracefully if unavailable) +- **NumPy**: All data is numpy arrays; version pinned for reproducibility +- **protobuf 3.0.0a3**: Exact version required for compatibility +- **pypng**: Image output (not PIL/Pillow) +- **noise**: Perlin/Simplex noise (package `noise`, function `snoise2`) + +## Common Pitfalls + +1. **Don't modify world.layers['X'].data shape** - all layers must stay (height, width) +2. **Ocean detection**: Use `world.is_ocean()` not `elevation <= sea_level` (accounts for threshold) +3. **Verbose output**: Check `get_verbose()` before expensive debug operations +4. **Border effects**: `place_oceans_at_map_borders()` intentionally lowers edges +5. **Anti-aliasing**: Applied to some layers (watermap) via `anti_alias()` to smooth artifacts diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..087388ff --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,166 @@ +name: CI + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + workflow_dispatch: + +jobs: + test: + name: Test Python ${{ matrix.python-version }} on ${{ matrix.os }} + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + python-version: ['3.9', '3.10', '3.11', '3.12', '3.13', '3.14'] + + steps: + - uses: actions/checkout@v4 + with: + path: worldengine + + - name: Checkout worldengine-data + uses: actions/checkout@v4 + with: + repository: Mindwerks/worldengine-data + path: worldengine-data + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + allow-prereleases: true + + - name: Install uv + uses: astral-sh/setup-uv@v4 + with: + enable-cache: true + + - name: Install dependencies + working-directory: worldengine + run: | + uv pip install --system -e ".[hdf5,dev]" + + - name: Run tests + working-directory: worldengine + run: | + pytest -v tests --tb=short + + - name: Test worldengine command + if: runner.os != 'Windows' + working-directory: worldengine + run: | + worldengine --help + worldengine world -s 42 -n test_world -x 512 -y 512 + + - name: Test worldengine command (Windows - no world generation) + if: runner.os == 'Windows' + working-directory: worldengine + run: | + worldengine --help + + lint: + name: Lint and Type Check + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + with: + path: worldengine + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.11' + + - name: Install uv + uses: astral-sh/setup-uv@v4 + + - name: Install dependencies + working-directory: worldengine + run: | + uv pip install --system -e ".[dev]" + + - name: Run pre-commit + working-directory: worldengine + run: | + pre-commit run --all-files + + - name: Run ruff check + working-directory: worldengine + run: | + ruff check worldengine/ + + - name: Run mypy + working-directory: worldengine + run: | + mypy worldengine/ --ignore-missing-imports + continue-on-error: true + + build-wheels: + name: Build wheels on ${{ matrix.os }} + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + + steps: + - uses: actions/checkout@v4 + with: + path: worldengine + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.11' + + - name: Install build tools + run: | + python -m pip install --upgrade pip build + + - name: Build sdist (Ubuntu only) + if: matrix.os == 'ubuntu-latest' + working-directory: worldengine + run: | + python -m build --sdist + + - name: Build wheel + working-directory: worldengine + run: | + python -m build --wheel + + - name: Upload artifacts + uses: actions/upload-artifact@v4 + with: + name: wheels-${{ matrix.os }} + path: worldengine/dist/* + + docker: + name: Test Docker build + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + with: + path: worldengine + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Build Docker image + uses: docker/build-push-action@v6 + with: + context: ./worldengine + push: false + tags: worldengine:test + cache-from: type=gha + cache-to: type=gha,mode=max + load: true + + - name: Test Docker image + run: | + docker run worldengine:test worldengine --help + docker run worldengine:test worldengine world -s 123 -n docker_test -x 256 -y 256 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 00000000..cc65fcd3 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,108 @@ +name: Build and Release + +on: + release: + types: [published] + workflow_dispatch: + +jobs: + build-sdist: + name: Build source distribution + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.11' + + - name: Install build tools + run: | + python -m pip install --upgrade pip build + + - name: Build sdist + run: | + python -m build --sdist + + - name: Upload sdist + uses: actions/upload-artifact@v4 + with: + name: sdist + path: dist/*.tar.gz + + build-wheels: + name: Build wheels on ${{ matrix.os }} + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + + steps: + - uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.11' + + - name: Install build tools + run: | + python -m pip install --upgrade pip build + + - name: Build wheel + run: | + python -m build --wheel + + - name: Upload wheels + uses: actions/upload-artifact@v4 + with: + name: wheels-${{ matrix.os }} + path: dist/*.whl + + publish: + name: Publish to PyPI + needs: [build-sdist, build-wheels] + runs-on: ubuntu-latest + if: github.event_name == 'release' && github.event.action == 'published' + environment: + name: pypi + url: https://pypi.org/p/worldengine + permissions: + id-token: write + + steps: + - name: Download all artifacts + uses: actions/download-artifact@v4 + with: + path: dist + merge-multiple: true + + - name: List distributions + run: ls -lh dist/ + + - name: Publish to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 + with: + skip-existing: true + + create-github-release-assets: + name: Upload release assets + needs: [build-sdist, build-wheels] + runs-on: ubuntu-latest + if: github.event_name == 'release' && github.event.action == 'published' + permissions: + contents: write + + steps: + - name: Download all artifacts + uses: actions/download-artifact@v4 + with: + path: dist + merge-multiple: true + + - name: Upload to GitHub Release + uses: softprops/action-gh-release@v2 + with: + files: dist/* diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 00000000..d8b721d9 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,23 @@ +# See https://pre-commit.com for more information +# See https://pre-commit.com/hooks.html for more hooks +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v5.0.0 + hooks: + - id: trailing-whitespace + - id: end-of-file-fixer + - id: check-yaml + - id: check-added-large-files + - id: check-merge-conflict + - id: check-toml + - id: check-json + - id: mixed-line-ending + + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.8.4 + hooks: + # Run the linter + - id: ruff + args: [--fix, --exit-non-zero-on-fix] + # Run the formatter + - id: ruff-format diff --git a/.python-version b/.python-version new file mode 100644 index 00000000..e9ca0eaf --- /dev/null +++ b/.python-version @@ -0,0 +1,6 @@ +3.14.0 +3.13.7 +3.12.11 +3.11.13 +3.10.16 +3.9.21 diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 76a64140..00000000 --- a/.travis.yml +++ /dev/null @@ -1,52 +0,0 @@ -language: python -python: 2.7 -os: - - linux - - osx - -dist: trusty -sudo: required -cache: - - pip - - apt - -env: - - TOX_ENV=py27 - - TOX_ENV=py34 - - TOX_ENV=pypy - - TOX_ENV=pyflakes - - TOX_ENV=manifest - -matrix: - fast_finish: true - allow_failures: - - os: osx - - env: TOX_ENV=pypy - -install: - - dpkg -l - - sudo rm -fr /opt/python/pypy-2.6.1 /opt/python/pypy3-2.4.0 /usr/local/pypy/bin - - echo "yes" | sudo apt-add-repository ppa:psi29a/trusty - - echo "yes" | sudo apt-add-repository ppa:pypy/ppa - - sudo apt-get update - - sudo apt-get remove -y libgdal1h postgresql-9.1-postgis-2.1 postgresql-9.2-postgis-2.1 postgresql-9.3-postgis-2.1 liblwgeom-2.1.8 - - sudo apt-get install -y libhdf5-dev pypy pypy-dev libgdal1-dev gdal-bin - - echo "libgdal version `gdal-config --version`" - - echo "GCC version `gcc --version` @ `which gcc`" - - echo "Python version `python --version` @ `which python`" - - echo "PyPy version `pypy --version` @ `which pypy`" - - pip install pip --upgrade - - pip install tox codecov coveralls - - git clone https://github.com/Mindwerks/worldengine-data.git ../worldengine-data - - dpkg -l - -# # command to run tests -script: - - tox -e $TOX_ENV - -notifications: - email: false - -after_success: - - coveralls - - codecov diff --git a/Dockerfile b/Dockerfile index 8c8e8d31..7363f3b1 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,18 +1,22 @@ -FROM debian:stretch +FROM python:3.11-slim +# Install system dependencies for PyPlatec and h5py RUN apt-get update && \ - apt-get -y install git \ - procps \ - python-dev \ - python-pip \ - curl \ - vim - -RUN pip install --upgrade pip setuptools + apt-get -y install --no-install-recommends \ + gcc \ + g++ \ + libhdf5-dev \ + pkg-config && \ + rm -rf /var/lib/apt/lists/* WORKDIR /app -ADD . /app -RUN pip install -r /app/requirements-dev.txt +# Copy all project files +COPY . /app/ +# Install worldengine with dependencies +RUN pip install --no-cache-dir --upgrade pip setuptools wheel && \ + pip install --no-cache-dir -e ".[hdf5,dev]" +# Default command +CMD ["worldengine", "--help"] diff --git a/LICENSE.txt b/LICENSE.txt index 34633bf3..300e155d 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -18,4 +18,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. \ No newline at end of file +SOFTWARE. diff --git a/README.md b/README.md index 2d4bfbab..ed4fb718 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,8 @@ WorldEngine - a world generator ========================= -[![Build Status](https://travis-ci.org/Mindwerks/worldengine.svg?branch=master)](https://travis-ci.org/Mindwerks/worldengine) [![Build status](https://ci.appveyor.com/api/projects/status/io4ljim2ra83df23?svg=true)](https://ci.appveyor.com/project/ftomassetti/worldengine) [![Coverage Status](https://coveralls.io/repos/Mindwerks/worldengine/badge.svg?branch=master&service=github)](https://coveralls.io/github/Mindwerks/worldengine?branch=master) [![Code Health](https://landscape.io/github/Mindwerks/worldengine/master/landscape.png)](https://landscape.io/github/Mindwerks/worldengine/master) [![codecov.io Coverage Status](https://codecov.io/github/Mindwerks/worldengine/coverage.svg?branch=master)](https://codecov.io/github/Mindwerks/worldengine?branch=master) [![Coverage Status](https://coveralls.io/repos/Mindwerks/worldengine/badge.svg?branch=master&service=github)](https://coveralls.io/github/Mindwerks/worldengine?branch=master) [![Requirements Status](https://requires.io/github/Mindwerks/worldengine/requirements.svg?branch=master)](https://requires.io/github/Mindwerks/worldengine/requirements/?branch=master) +[![CI](https://github.com/Mindwerks/worldengine/actions/workflows/ci.yml/badge.svg)](https://github.com/Mindwerks/worldengine/actions/workflows/ci.yml) +[![Python 3.9+](https://img.shields.io/badge/python-3.9+-blue.svg)](https://www.python.org/downloads/) _The current stable version is 0.19.0_ @@ -53,30 +54,57 @@ An experimental (and limited!) GUI is available as a separate project: [https:// Install ======= +**Python 3.9+ required**. WorldEngine supports Python 3.9 through 3.14. + ### Using Docker First, git clone or download the code, then: -``` +```bash bin/run_in_docker.sh ``` ### Using pip -``` +```bash # Currently not yet released on pypi, you may want to still use Lands or WorldSynth # or alternatively download the source pip install worldengine ``` -### From source code +### Using uv (Recommended - Fast!) + +[uv](https://github.com/astral-sh/uv) is a fast Python package installer (10-100x faster than pip): +```bash +# Install uv if you haven't already +curl -LsSf https://astral.sh/uv/install.sh | sh + +# Create environment and install +uv venv +uv pip install -e ".[hdf5,dev]" ``` -git clone or download the code -# for unit-testing: also clone worldengine-data -git clone git@github.com:Mindwerks/worldengine-data.git ../worldengine-data -nosetest tests +### From source code + +```bash +git clone https://github.com/Mindwerks/worldengine.git +cd worldengine + +# Quick setup with provided script (requires pyenv) +./setup_venv.sh +source venv/bin/activate + +# Or manual setup +python -m venv venv +source venv/bin/activate # On Windows: venv\Scripts\activate +pip install -e ".[dev]" + +# Run tests +pytest -v tests + +# Or test across all Python versions (requires tox) +tox ``` ### _On Windows_ @@ -90,7 +118,11 @@ Note: you need also a copy of the worldengine src directory in the same folder a Dependencies ============ -The gui is based on QT, so you will need to install them +Core dependencies are managed via `pyproject.toml`. Optional dependencies: +- `hdf5`: For HDF5 world format support (requires h5py) +- `dev`: Development tools (pytest, tox, etc.) + +The GUI (separate project) is based on Qt Output ====== @@ -188,28 +220,41 @@ The world generation algorithm goes through different phases: Development =========== -Using virtualenv you can create a sandbox in which to develop. +**Python 3.9-3.14 supported**. Using the provided setup script or virtualenv you can create a sandbox in which to develop. -Python 2 --------- +## Quick Setup with uv (Fast!) ```bash -virtualenv venv -source venv/bin/activate -pip install --upgrade pip setuptools -pip install -r requirements-dev.txt -python worldengine +# Install uv if needed +curl -LsSf https://astral.sh/uv/install.sh | sh + +# Setup environment +uv venv +uv pip install -e ".[hdf5,dev]" + +# Setup pre-commit hooks (recommended) +pre-commit install + +# Run tests +pytest -v tests + +# Test across all Python versions (blazing fast with tox-uv) +tox ``` -Python 3 --------- +## Traditional Setup ```bash -virtualenv venv -p /usr/bin/python3 +# Using the provided setup script (requires pyenv) +./setup_venv.sh +source venv/bin/activate + +# Or manually +python -m venv venv source venv/bin/activate pip install --upgrade pip setuptools -pip install -r requirements-dev.txt -python worldengine +pip install -e ".[dev]" +pytest -v tests ``` Distribution @@ -244,14 +289,14 @@ Remember, be consistent if you are either win32 or win64 and everything you down and install is either one or the other, but not both. You'll want to install msysgit: https://msysgit.github.io/ which will get you -a Linux like environment. After that, clone the repo and install Python 2.7 for -windows: https://www.python.org/downloads/windows/ This will get you also pip -which is required for the rest. You'll first need to pip install virtualenv. +a Linux like environment. After that, clone the repo and install Python 3.14+ for +Windows: https://www.python.org/downloads/windows/ This will get you pip as well, +which is required for the rest. The layout is a bit different than in Linux. ```bash -virtualenv venv -venv/Scripts/pip install -r requirements.txt +python -m venv venv +venv\Scripts\pip install -e . ``` Numpy install will fail, so you'll need download a pre-compiled wheel file and diff --git a/appveyor.yml b/appveyor.yml deleted file mode 100644 index c0130f74..00000000 --- a/appveyor.yml +++ /dev/null @@ -1,70 +0,0 @@ -# Taken from: https://packaging.python.org/en/latest/appveyor.html -# For now considering only Python 2.7, looking to support soon Python 2.6 -# and Python 3 in the future - -environment: - - global: - # SDK v7.0 MSVC Express 2008's SetEnv.cmd script will fail if the - # /E:ON and /V:ON options are not enabled in the batch script intepreter - # See: http://stackoverflow.com/a/13751649/163740 - WITH_COMPILER: "cmd /E:ON /V:ON /C .\\appveyor\\run_with_compiler.cmd" - - matrix: - - PYTHON: "C:\\Python27" - PYTHON_VERSION: "2.7.8" - PYTHON_ARCH: "32" - - # - PYTHON: "C:\\Python33" - # PYTHON_VERSION: "3.3.5" - # PYTHON_ARCH: "32" - - # - PYTHON: "C:\\Python34" - # PYTHON_VERSION: "3.4.1" - # PYTHON_ARCH: "32" - - # - PYTHON: "C:\\Python27-x64" - # PYTHON_VERSION: "2.7.8" - # PYTHON_ARCH: "64" - # WINDOWS_SDK_VERSION: "v7.0" - - # - PYTHON: "C:\\Python33-x64" - # PYTHON_VERSION: "3.3.5" - # PYTHON_ARCH: "64" - # WINDOWS_SDK_VERSION: "v7.1" - - # - PYTHON: "C:\\Python34-x64" - # PYTHON_VERSION: "3.4.1" - # PYTHON_ARCH: "64" - # WINDOWS_SDK_VERSION: "v7.1" - -init: - - "ECHO %PYTHON% %PYTHON_VERSION% %PYTHON_ARCH%" - - echo %path% - -install: - - "powershell appveyor\\install_hdf5.ps1" - - "powershell appveyor\\install.ps1" - - "%PYTHON%\\Scripts\\pip.exe install -r requirements-dev.txt" - - mkdir %systemdrive%\temp - - cd %systemdrive%\temp - - appveyor DownloadFile http://download.gisinternals.com/sdk/downloads/release-1600-gdal-1-11-mapserver-6-4/gdal-111-1600-core.msi - - - msiexec /i gdal-111-1600-core.msi /qn - - appveyor DownloadFile http://download.gisinternals.com/sdk/downloads/release-1600-gdal-1-11-mapserver-6-4/GDAL-1.11.4.win32-py2.7.msi - - msiexec /i GDAL-1.11.4.win32-py2.7.msi /qn - - cd %APPVEYOR_BUILD_FOLDER% - -build: off - -test_script: - - "%WITH_COMPILER% %PYTHON%/python setup.py test" - -after_test: - - "%WITH_COMPILER% %PYTHON%/python setup.py bdist_wheel" - -artifacts: - - path: dist\* - -#on_success: -# - TODO: upload the content of dist/*.whl to a public wheelhouse diff --git a/appveyor/install.ps1 b/appveyor/install.ps1 deleted file mode 100644 index 30d651fb..00000000 --- a/appveyor/install.ps1 +++ /dev/null @@ -1,84 +0,0 @@ -# Authors: Olivier Grisel, Kyle Kastner, Bret Curtis -# License: CC0 1.0 Universal: http://creativecommons.org/publicdomain/zero/1.0/ - -$BASE_URL = "https://www.python.org/ftp/python/" -$GET_PIP_URL = "https://bootstrap.pypa.io/get-pip.py" -$GET_PIP_PATH = "C:\get-pip.py" - - -function DownloadPython ($python_version, $platform_suffix) { - $webclient = New-Object System.Net.WebClient - $filename = "python-" + $python_version + $platform_suffix + ".msi" - $url = $BASE_URL + $python_version + "/" + $filename - - $basedir = $pwd.Path + "\" - $filepath = $basedir + $filename - if (Test-Path $filename) { - Write-Host "Reusing" $filepath - return $filepath - } - - # Download and retry up to 5 times in case of network transient errors. - Write-Host "Downloading" $filename "from" $url - $retry_attempts = 3 - for($i=0; $i -lt $retry_attempts; $i++){ - try { - $webclient.DownloadFile($url, $filepath) - break - } - Catch [Exception]{ - Start-Sleep 1 - } - } - Write-Host "File saved at" $filepath - return $filepath -} - - -function InstallPython ($python_version, $architecture, $python_home) { - Write-Host "Installing Python" $python_version "for" $architecture "bit architecture to" $python_home - if (Test-Path $python_home) { - Write-Host $python_home "already exists, skipping." - return $false - } - if ($architecture -eq "32") { - $platform_suffix = "" - } else { - $platform_suffix = ".amd64" - } - $filepath = DownloadPython $python_version $platform_suffix - Write-Host "Installing" $filepath "to" $python_home - $args = "/qn /i $filepath TARGETDIR=$python_home" - Write-Host "msiexec.exe" $args - Start-Process -FilePath "msiexec.exe" -ArgumentList $args -Wait -Passthru - Write-Host "Python $python_version ($architecture) installation complete" - return $true -} - -function InstallPip ($python_home) { - $pip_path = $python_home + "/Scripts/pip.exe" - $python_path = $python_home + "/python.exe" - if (-not(Test-Path $pip_path)) { - Write-Host "Installing pip..." - $webclient = New-Object System.Net.WebClient - $webclient.DownloadFile($GET_PIP_URL, $GET_PIP_PATH) - Write-Host "Executing:" $python_path $GET_PIP_PATH - Start-Process -FilePath "$python_path" -ArgumentList "$GET_PIP_PATH" -Wait -Passthru - } else { - Write-Host "pip already installed." -# Start-Process -FilePath "$pip_path" -ArgumentList { "install", "wheel"} -Wait -Passthru - } -} - -function InstallPackage ($python_home, $pkg) { - $pip_path = $python_home + "/Scripts/pip.exe" - & $pip_path install $pkg -} - -function main () { - InstallPython $env:PYTHON_VERSION $env:PYTHON_ARCH $env:PYTHON - InstallPip $env:PYTHON - InstallPackage $env:PYTHON wheel -} - -main diff --git a/appveyor/install_hdf5.ps1 b/appveyor/install_hdf5.ps1 deleted file mode 100644 index 65e1eafe..00000000 --- a/appveyor/install_hdf5.ps1 +++ /dev/null @@ -1,24 +0,0 @@ -$URL = http://www.hdfgroup.org/ftp/HDF5/current/bin/windows/hdf5-1.8.15-patch1-win32-vs2013-shared.zip - -function main () { - $basedir = $pwd.Path + "\" - $filename = "hdf5.zip" - $filepath = $basedir + $filename - Write-Host "Downloading" $filename "from" $URL - $retry_attempts = 3 - for($i=0; $i -lt $retry_attempts; $i++){ - try { - $webclient.DownloadFile($URL, $filepath) - break - } - Catch [Exception]{ - Start-Sleep 1 - } - } - $outpath = $basedir + "\hdf5_unzipped" - [System.IO.Compression.ZipFile]::ExtractToDirectory($filepath, $outpath) - $msipath = $outpath + "\HDF5-1.8.15-win64.msi" - Invoke-Command -ScriptBlock { & cmd /c "msiexec.exe /i $msipath" /qn ADVANCED_OPTIONS=1 CHANNEL=100} -} - -main \ No newline at end of file diff --git a/appveyor/run_with_compiler.cmd b/appveyor/run_with_compiler.cmd deleted file mode 100644 index 3a472bc8..00000000 --- a/appveyor/run_with_compiler.cmd +++ /dev/null @@ -1,47 +0,0 @@ -:: To build extensions for 64 bit Python 3, we need to configure environment -:: variables to use the MSVC 2010 C++ compilers from GRMSDKX_EN_DVD.iso of: -:: MS Windows SDK for Windows 7 and .NET Framework 4 (SDK v7.1) -:: -:: To build extensions for 64 bit Python 2, we need to configure environment -:: variables to use the MSVC 2008 C++ compilers from GRMSDKX_EN_DVD.iso of: -:: MS Windows SDK for Windows 7 and .NET Framework 3.5 (SDK v7.0) -:: -:: 32 bit builds do not require specific environment configurations. -:: -:: Note: this script needs to be run with the /E:ON and /V:ON flags for the -:: cmd interpreter, at least for (SDK v7.0) -:: -:: More details at: -:: https://github.com/cython/cython/wiki/64BitCythonExtensionsOnWindows -:: http://stackoverflow.com/a/13751649/163740 -:: -:: Author: Olivier Grisel -:: License: CC0 1.0 Universal: http://creativecommons.org/publicdomain/zero/1.0/ -@ECHO OFF - -SET COMMAND_TO_RUN=%* -SET WIN_SDK_ROOT=C:\Program Files\Microsoft SDKs\Windows - -SET MAJOR_PYTHON_VERSION="%PYTHON_VERSION:~0,1%" -IF %MAJOR_PYTHON_VERSION% == "2" ( - SET WINDOWS_SDK_VERSION="v7.0" -) ELSE IF %MAJOR_PYTHON_VERSION% == "3" ( - SET WINDOWS_SDK_VERSION="v7.1" -) ELSE ( - ECHO Unsupported Python version: "%MAJOR_PYTHON_VERSION%" - EXIT 1 -) - -IF "%PYTHON_ARCH%"=="64" ( - ECHO Configuring Windows SDK %WINDOWS_SDK_VERSION% for Python %MAJOR_PYTHON_VERSION% on a 64 bit architecture - SET DISTUTILS_USE_SDK=1 - SET MSSdk=1 - "%WIN_SDK_ROOT%\%WINDOWS_SDK_VERSION%\Setup\WindowsSdkVer.exe" -q -version:%WINDOWS_SDK_VERSION% - "%WIN_SDK_ROOT%\%WINDOWS_SDK_VERSION%\Bin\SetEnv.cmd" /x64 /release - ECHO Executing: %COMMAND_TO_RUN% - call %COMMAND_TO_RUN% || EXIT 1 -) ELSE ( - ECHO Using default MSVC build environment for 32 bit architecture - ECHO Executing: %COMMAND_TO_RUN% - call %COMMAND_TO_RUN% || EXIT 1 -) diff --git a/biomes.scm b/biomes.scm index 2286d81d..8742d10c 100644 --- a/biomes.scm +++ b/biomes.scm @@ -2,7 +2,7 @@ base) (gimp-context-set-sample-threshold-int 0) (gimp-palette-set-foreground "#000000") - (let* ( + (let* ( (polar-ice-layer base) (subpolar-dry-tundra-layer base) (subpolar-moist-tundra-layer base) @@ -307,7 +307,7 @@ (gimp-item-set-name tundra-group "Ice Group") (gimp-image-reorder-item img polar-desert-layer tundra-group 0) (gimp-image-reorder-item img polar-ice-layer tundra-group 0) - + (set! tropical-rain-forest-layer2 (car (gimp-layer-new img 4096 2048 1 "Tropical Rain Forest" 100 0))) (gimp-image-add-layer img tropical-rain-forest-layer2 0) (gimp-image-select-color img 2 base "#40F090") @@ -317,20 +317,20 @@ (gimp-image-select-color img 0 base "#20FFA0") (gimp-palette-set-foreground "#1cb242") (gimp-edit-fill tropical-rain-forest-layer2 0) - + (set! tropical-seasonal-forest-layer2 (car (gimp-layer-new img 4096 2048 1 "Tropical Seasonal Forest" 100 0))) (gimp-image-add-layer img tropical-seasonal-forest-layer2 0) (gimp-image-select-color img 2 base "#60F080") (gimp-palette-set-foreground "#25e750") (gimp-edit-fill tropical-seasonal-forest-layer2 0) - + (set! tropical-dry-forest-layer2 (car (gimp-layer-new img 4096 2048 1 "Tropical Dry Forest" 100 0))) (gimp-image-add-layer img tropical-dry-forest-layer2 0) (gimp-image-select-color img 2 base "#80F080") (gimp-image-select-color img 0 base "#80FF80") (gimp-palette-set-foreground "#4dff4d") (gimp-edit-fill tropical-dry-forest-layer2 0) - + (set! savanna-layer (car (gimp-layer-new img 4096 2048 1 "Savanna" 100 0))) (gimp-image-add-layer img savanna-layer 0) (gimp-image-select-color img 2 base "#B0F080") @@ -338,7 +338,7 @@ (gimp-image-select-color img 0 base "#A0FF80") (gimp-palette-set-foreground "#90ff4d") (gimp-edit-fill savanna-layer 0) - + (set! hot-desert-layer (car (gimp-layer-new img 4096 2048 1 "Hot Desert" 100 0))) (gimp-image-add-layer img hot-desert-layer 0) (gimp-image-select-color img 2 base "#E0E080") @@ -349,14 +349,14 @@ (gimp-image-select-color img 0 base "#E0FF80") (gimp-palette-set-foreground "#d4ff4d") (gimp-edit-fill hot-desert-layer 0) - + (set! chapparal-layer (car (gimp-layer-new img 4096 2048 1 "Chapparal" 100 0))) (gimp-image-add-layer img chapparal-layer 0) (gimp-image-select-color img 2 base "#A0E080") (gimp-image-select-color img 0 base "#80E080") (gimp-palette-set-foreground "#52cd52") (gimp-edit-fill chapparal-layer 0) - + (set! deciduous-forest-layer (car (gimp-layer-new img 4096 2048 1 "Deciduous Forest" 100 0))) (gimp-image-add-layer img deciduous-forest-layer 0) (gimp-image-select-color img 2 base "#60E080") @@ -364,20 +364,20 @@ (gimp-image-select-color img 0 base "#20E0C0") (gimp-palette-set-foreground "#00cd7b") (gimp-edit-fill deciduous-forest-layer 0) - + (set! cool-desert-layer (car (gimp-layer-new img 4096 2048 1 "Cool Desert" 100 0))) (gimp-image-add-layer img cool-desert-layer 0) (gimp-image-select-color img 2 base "#C0C080") (gimp-image-select-color img 0 base "#A0C080") (gimp-palette-set-foreground "#aac066") (gimp-edit-fill cool-desert-layer 0) - + (set! steppe-layer (car (gimp-layer-new img 4096 2048 1 "Steppes" 100 0))) (gimp-image-add-layer img steppe-layer 0) (gimp-image-select-color img 2 base "#80C080") (gimp-palette-set-foreground "#529a52") (gimp-edit-fill steppe-layer 0) - + (set! mixed-forest-layer (car (gimp-layer-new img 4096 2048 1 "Mixed Forest" 100 0))) (gimp-image-add-layer img mixed-forest-layer 0) (gimp-image-select-color img 2 base "#60C080") @@ -385,7 +385,7 @@ (gimp-image-select-color img 0 base "#20C0C0") (gimp-palette-set-foreground "#0a9a76") (gimp-edit-fill mixed-forest-layer 0) - + (set! coniferous-forest-layer (car (gimp-layer-new img 4096 2048 1 "Coniferous Forest" 100 0))) (gimp-image-add-layer img coniferous-forest-layer 0) (gimp-image-select-color img 2 base "#60A080") @@ -393,7 +393,7 @@ (gimp-image-select-color img 0 base "#20A0C0") (gimp-palette-set-foreground "#1d6060") (gimp-edit-fill coniferous-forest-layer 0) - + (set! cold-parklands-layer (car (gimp-layer-new img 4096 2048 1 "Cold Parklands" 100 0))) (gimp-image-add-layer img cold-parklands-layer 0) (gimp-image-select-color img 2 base "#808080") @@ -401,7 +401,7 @@ (gimp-image-select-color img 0 base "#80A080") (gimp-palette-set-foreground "#90907a") (gimp-edit-fill cold-parklands-layer 0) - + (set! tundra-layer (car (gimp-layer-new img 4096 2048 1 "Tundra" 100 0))) (gimp-image-add-layer img tundra-layer 0) (gimp-image-select-color img 2 base "#608080") @@ -409,7 +409,7 @@ (gimp-image-select-color img 0 base "#2080C0") (gimp-palette-set-foreground "#0f3b3b") (gimp-edit-fill tundra-layer 0) - + (set! ice-layer (car (gimp-layer-new img 4096 2048 1 "Ice" 100 0))) (gimp-image-add-layer img ice-layer 0) (gimp-image-select-color img 2 base "#FFFFFF") @@ -429,7 +429,7 @@ "copyright 2014, Evan Sampson" ;copyright notice "October 24, 2014" ;date created "" ;image type that the script works on - SF-IMAGE "Image" 0 - SF-DRAWABLE "Base layer" 0 + SF-IMAGE "Image" 0 + SF-DRAWABLE "Base layer" 0 ) -(script-fu-menu-register "script-fu-biomes-and-vegetation" "/Filters/Biomes") \ No newline at end of file +(script-fu-menu-register "script-fu-biomes-and-vegetation" "/Filters/Biomes") diff --git a/docs/Biomes.html b/docs/Biomes.html index 8750fb39..9cba27a9 100644 --- a/docs/Biomes.html +++ b/docs/Biomes.html @@ -2,7 +2,7 @@ - + @@ -10,14 +10,14 @@ - + - + diff --git a/manual/biomes.rst b/manual/biomes.rst index dec6628a..be2e52b2 100644 --- a/manual/biomes.rst +++ b/manual/biomes.rst @@ -14,7 +14,7 @@ Worldengine implements a version of the `Holdridge life zones v documentation". -#html_title = None +# html_title = None # A shorter title for the navigation bar. Default is the same as html_title. -#html_short_title = None +# html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. -#html_logo = None +# html_logo = None # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. -#html_favicon = None +# html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] +html_static_path = ["_static"] # Add any extra paths that contain custom files (such as robots.txt or # .htaccess) here, relative to this directory. These files are copied # directly to the root of the documentation. -#html_extra_path = [] +# html_extra_path = [] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. -#html_last_updated_fmt = '%b %d, %Y' +# html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. -#html_use_smartypants = True +# html_use_smartypants = True # Custom sidebar templates, maps document names to template names. -#html_sidebars = {} +# html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. -#html_additional_pages = {} +# html_additional_pages = {} # If false, no module index is generated. -#html_domain_indices = True +# html_domain_indices = True # If false, no index is generated. -#html_use_index = True +# html_use_index = True # If true, the index is split into individual pages for each letter. -#html_split_index = False +# html_split_index = False # If true, links to the reST sources are added to the pages. -#html_show_sourcelink = True +# html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. -#html_show_sphinx = True +# html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. -#html_show_copyright = True +# html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. -#html_use_opensearch = '' +# html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). -#html_file_suffix = None +# html_file_suffix = None # Language to be used for generating the HTML full-text search index. # Sphinx supports the following languages: # 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja' # 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr' -#html_search_language = 'en' +# html_search_language = 'en' # A dictionary with options for the search language support, empty by default. # Now only 'ja' uses this config value -#html_search_options = {'type': 'default'} +# html_search_options = {'type': 'default'} # The name of a javascript file (relative to the configuration directory) that # implements a search results scorer. If empty, the default will be used. -#html_search_scorer = 'scorer.js' +# html_search_scorer = 'scorer.js' # Output file base name for HTML help builder. -htmlhelp_basename = 'WorldEnginedoc' +htmlhelp_basename = "WorldEnginedoc" # -- Options for LaTeX output --------------------------------------------- latex_elements = { -# The paper size ('letterpaper' or 'a4paper'). -#'papersize': 'letterpaper', - -# The font size ('10pt', '11pt' or '12pt'). -#'pointsize': '10pt', - -# Additional stuff for the LaTeX preamble. -#'preamble': '', - -# Latex figure (float) alignment -#'figure_align': 'htbp', + # The paper size ('letterpaper' or 'a4paper'). + #'papersize': 'letterpaper', + # The font size ('10pt', '11pt' or '12pt'). + #'pointsize': '10pt', + # Additional stuff for the LaTeX preamble. + #'preamble': '', + # Latex figure (float) alignment + #'figure_align': 'htbp', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ - (master_doc, 'WorldEngine.tex', u'WorldEngine Documentation', - u'Bret Curtis and Federico Tomassetti', 'manual'), + (master_doc, "WorldEngine.tex", "WorldEngine Documentation", "Bret Curtis and Federico Tomassetti", "manual"), ] # The name of an image file (relative to this directory) to place at the top of # the title page. -#latex_logo = None +# latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. -#latex_use_parts = False +# latex_use_parts = False # If true, show page references after internal links. -#latex_show_pagerefs = False +# latex_show_pagerefs = False # If true, show URL addresses after external links. -#latex_show_urls = False +# latex_show_urls = False # Documents to append as an appendix to all manuals. -#latex_appendices = [] +# latex_appendices = [] # If false, no module index is generated. -#latex_domain_indices = True +# latex_domain_indices = True # -- Options for manual page output --------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). -man_pages = [ - (master_doc, 'worldengine', u'WorldEngine Documentation', - [author], 1) -] +man_pages = [(master_doc, "worldengine", "WorldEngine Documentation", [author], 1)] # If true, show URL addresses after external links. -#man_show_urls = False +# man_show_urls = False # -- Options for Texinfo output ------------------------------------------- @@ -268,19 +258,25 @@ # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ - (master_doc, 'WorldEngine', u'WorldEngine Documentation', - author, 'WorldEngine', 'One line description of project.', - 'Miscellaneous'), + ( + master_doc, + "WorldEngine", + "WorldEngine Documentation", + author, + "WorldEngine", + "One line description of project.", + "Miscellaneous", + ), ] # Documents to append as an appendix to all manuals. -#texinfo_appendices = [] +# texinfo_appendices = [] # If false, no module index is generated. -#texinfo_domain_indices = True +# texinfo_domain_indices = True # How to display URL addresses: 'footnote', 'no', or 'inline'. -#texinfo_show_urls = 'footnote' +# texinfo_show_urls = 'footnote' # If true, do not generate a @detailmenu in the "Top" node's menu. -#texinfo_no_detailmenu = False +# texinfo_no_detailmenu = False diff --git a/manual/contributing.rst b/manual/contributing.rst index 56be9e8c..3923227e 100644 --- a/manual/contributing.rst +++ b/manual/contributing.rst @@ -1,4 +1,4 @@ Contributing ====================== -We are always very happy to receive new contributions and we are ready to help you working on your first patches. \ No newline at end of file +We are always very happy to receive new contributions and we are ready to help you working on your first patches. diff --git a/manual/gui.rst b/manual/gui.rst index b1feb9d6..f649bb06 100644 --- a/manual/gui.rst +++ b/manual/gui.rst @@ -1,4 +1,4 @@ GUI ====================== -Ok, you prefer graphical interfaces: we got you covered. \ No newline at end of file +Ok, you prefer graphical interfaces: we got you covered. diff --git a/manual/install.rst b/manual/install.rst index e019baec..04255581 100644 --- a/manual/install.rst +++ b/manual/install.rst @@ -1,4 +1,4 @@ Install ====================== -WorldEngine will not be any good for you if you do not manage to install it first. Let's see how to do that. \ No newline at end of file +WorldEngine will not be any good for you if you do not manage to install it first. Let's see how to do that. diff --git a/manual/scenarios.rst b/manual/scenarios.rst index 05019b7d..1d830e4e 100644 --- a/manual/scenarios.rst +++ b/manual/scenarios.rst @@ -1,4 +1,4 @@ Usage scenarios ====================== -In this section we present typical usage scenarios. They could help you figure out what WorldEngine can be used for. \ No newline at end of file +In this section we present typical usage scenarios. They could help you figure out what WorldEngine can be used for. diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 00000000..44b1b3d5 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,135 @@ +[build-system] +requires = ["setuptools>=61.0", "wheel"] +build-backend = "setuptools.build_meta" + +[project] +name = "worldengine" +dynamic = ["version"] +description = "World generator simulating plate tectonics, erosion, etc." +readme = "README.md" +authors = [ + {name = "Federico Tomassetti", email = "f.tomassetti@gmail.com"}, + {name = "Bret Curtis", email = "psi29a@gmail.com"} +] +license = {text = "MIT License"} +requires-python = ">=3.9" +classifiers = [ + "Development Status :: 4 - Beta", + "Intended Audience :: Developers", + "Intended Audience :: Science/Research", + "License :: OSI Approved :: MIT License", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", + "Topic :: Games/Entertainment :: Simulation", + "Topic :: Scientific/Engineering", +] +dependencies = [ + "PyPlatec==1.4.2", + "pypng>=0.20220715.0", + "numpy>=1.24.0", + "noise==1.2.2", + "protobuf>=4.21.0", +] + +[project.optional-dependencies] +hdf5 = [ + "Cython>=3.0.0", + "h5py>=3.10.0", +] +gdal = [ + # GDAL is optional for heightmap export in various formats + # Note: Requires system GDAL libraries (libgdal-dev on Ubuntu, gdal on macOS via brew) + # Installation: apt-get install libgdal-dev (Ubuntu) or brew install gdal (macOS) + "GDAL>=3.0.0", +] +dev = [ + # Include hdf5 for testing + "Cython>=3.0.0", + "h5py>=3.10.0", + # Testing + "pytest>=7.4.0", + "pytest-cov>=4.1.0", + # Code quality + "ruff>=0.1.0", + "mypy>=1.7.0", + "pre-commit>=3.5.0", + # Documentation + "sphinx>=7.2.0", + "sphinx-rtd-theme>=2.0.0", + # Multi-version testing + "tox>=4.0.0", + "tox-uv>=1.0.0", +] + +[project.urls] +Homepage = "http://github.com/Mindwerks/worldengine" +Download = "https://github.com/Mindwerks/worldengine/releases" +Documentation = "https://worldengine.readthedocs.io" +Repository = "https://github.com/Mindwerks/worldengine" +Issues = "https://github.com/Mindwerks/worldengine/issues" + +[project.scripts] +worldengine = "worldengine.cli.main:main" + +[tool.setuptools.dynamic] +version = {attr = "worldengine.version.__version__"} + +[tool.setuptools.packages.find] +where = ["."] +include = ["worldengine*"] + +[tool.pytest.ini_options] +testpaths = ["tests"] +python_files = ["*_test.py"] +python_classes = ["Test*"] +python_functions = ["test_*"] + +[tool.ruff] +line-length = 120 +target-version = "py39" + +[tool.ruff.lint] +select = ["E", "F", "W", "I", "N", "UP"] +ignore = [ + "E501", # Line too long (handled by formatter) + "N802", # Function name should be lowercase (some scientific methods use camelCase) + "N804", # First argument of class method should be named 'cls' (mcs is conventional for metaclass) + "N806", # Variable in function should be lowercase (scientific notation) + "N807", # Function name should not start/end with __ + "N999", # Invalid module name + "UP031", # Use format specifiers (too many legacy % formats to change at once) +] + +[tool.ruff.lint.per-file-ignores] +"setup.py" = ["F821"] # __version__ defined via exec() + +[dependency-groups] +dev = [ + # Include hdf5 for testing + "Cython>=3.0.0", + "h5py>=3.10.0", + # Testing + "pytest>=7.4.0", + "pytest-cov>=4.1.0", + # Code quality + "ruff>=0.1.0", + "mypy>=1.7.0", + "pre-commit>=3.5.0", + # Documentation + "sphinx>=7.2.0", + "sphinx-rtd-theme>=2.0.0", + # Multi-version testing + "tox>=4.0.0", + "tox-uv>=1.0.0", +] + +[tool.mypy] +python_version = "3.9" +warn_return_any = true +warn_unused_configs = true +disallow_untyped_defs = false diff --git a/requirements-dev.txt b/requirements-dev.txt deleted file mode 100644 index 87442540..00000000 --- a/requirements-dev.txt +++ /dev/null @@ -1,7 +0,0 @@ --r requirements.txt -nose -pytest -pdbpp -# for the documentation -sphinx -sphinx-autobuild \ No newline at end of file diff --git a/requirements-gdal.txt b/requirements-gdal.txt deleted file mode 100644 index 667de3fc..00000000 --- a/requirements-gdal.txt +++ /dev/null @@ -1,3 +0,0 @@ -# For export support, you'll need GDAL. --r requirements.txt -pygdal==1.11.3.3 \ No newline at end of file diff --git a/requirements-hdf5.txt b/requirements-hdf5.txt deleted file mode 100644 index feb430a4..00000000 --- a/requirements-hdf5.txt +++ /dev/null @@ -1,4 +0,0 @@ -# For export support, you'll need HDF5 --r requirements.txt -Cython -h5py==2.5.0 diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index 47f81745..00000000 --- a/requirements.txt +++ /dev/null @@ -1,7 +0,0 @@ -argparse==1.2.1 -noise==1.2.2 -numpy==1.9.2 -pypng==0.0.18 -PyPlatec==1.4.0 -protobuf==3.0.0a3 -six==1.10.0 diff --git a/setup.cfg b/setup.cfg index 5e409001..b9cb9c3f 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,2 +1,3 @@ [wheel] -universal = 1 +# Python 3 only - no universal wheel +universal = 0 diff --git a/setup.py b/setup.py index 054aa1d7..28a148b9 100644 --- a/setup.py +++ b/setup.py @@ -1,4 +1,5 @@ from setuptools import setup + # from pip.req import parse_requirements # import pip.download @@ -10,24 +11,45 @@ # e.g. ['django==1.5.1', 'mezzanine==1.4.6'] # reqs = [str(ir.req) for ir in install_reqs] -exec(open('worldengine/version.py').read()) +with open("worldengine/version.py") as f: + exec(f.read()) # noqa: S102 - defines __version__ config = { - 'name': 'worldengine', - 'description': 'World generator simulating plate tectonics, erosion, etc.', - 'author': 'Federico Tomassetti, Bret Curtis', - 'author_email': 'f.tomassetti@gmail.com, psi29a@gmail.com', - 'url': 'http://github.com/Mindwerks/worldengine', - 'download_url': 'https://github.com/Mindwerks/worldengine/releases', - 'version': __version__, - 'packages': ['worldengine', 'worldengine.cli', 'worldengine.simulations', - 'worldengine.protobuf', 'worldengine.imex'], - 'entry_points': { - 'console_scripts': ['worldengine=worldengine.cli.main:main'], + "name": "worldengine", + "description": "World generator simulating plate tectonics, erosion, etc.", + "author": "Federico Tomassetti, Bret Curtis", + "author_email": "f.tomassetti@gmail.com, psi29a@gmail.com", + "url": "http://github.com/Mindwerks/worldengine", + "download_url": "https://github.com/Mindwerks/worldengine/releases", + "version": __version__, + "packages": [ + "worldengine", + "worldengine.cli", + "worldengine.simulations", + "worldengine.protobuf", + "worldengine.imex", + ], + "entry_points": { + "console_scripts": ["worldengine=worldengine.cli.main:main"], }, - 'install_requires': ['PyPlatec==1.4.0', 'pypng>=0.0.18', 'numpy>=1.22.2', - 'argparse==1.2.1', 'noise==1.2.2', 'protobuf==3.0.0a3'], - 'license': 'MIT License' + "install_requires": ["PyPlatec==1.4.2", "pypng>=0.20220715.0", "numpy>=1.24.0", "noise==1.2.2", "protobuf>=4.21.0"], + "python_requires": ">=3.9", + "classifiers": [ + "Development Status :: 4 - Beta", + "Intended Audience :: Developers", + "Intended Audience :: Science/Research", + "License :: OSI Approved :: MIT License", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", + "Topic :: Games/Entertainment :: Simulation", + "Topic :: Scientific/Engineering", + ], + "license": "MIT License", } setup(**config) diff --git a/setup_venv.sh b/setup_venv.sh new file mode 100755 index 00000000..40348d82 --- /dev/null +++ b/setup_venv.sh @@ -0,0 +1,171 @@ +#!/usr/bin/env bash + +# WorldEngine Development Environment Setup Script +# Compatible with Linux and macOS +# Requires: pyenv + +set -e + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' # No Color + +# Configuration +PYTHON_VERSION="3.14" +VENV_DIR="venv" + +echo -e "${GREEN}=== WorldEngine Development Environment Setup ===${NC}" + +# Check if pyenv is installed +if ! command -v pyenv &> /dev/null; then + echo -e "${RED}Error: pyenv is not installed.${NC}" + echo "Please install pyenv first:" + echo " macOS: brew install pyenv" + echo " Linux: curl https://pyenv.run | bash" + echo "" + echo "Then add to your shell configuration (~/.bashrc, ~/.zshrc):" + echo ' export PYENV_ROOT="$HOME/.pyenv"' + echo ' export PATH="$PYENV_ROOT/bin:$PATH"' + echo ' eval "$(pyenv init --path)"' + echo ' eval "$(pyenv init -)"' + exit 1 +fi + +echo -e "${GREEN}✓ pyenv found${NC}" + +# Check if protoc is installed +if ! command -v protoc &> /dev/null; then + echo -e "${YELLOW}Warning: protoc (Protocol Buffer Compiler) is not installed.${NC}" + echo "protoc is required to regenerate protobuf files." + echo "" + if [[ "$OSTYPE" == "darwin"* ]]; then + if command -v brew &> /dev/null; then + echo -e "${YELLOW}Installing protobuf via Homebrew...${NC}" + brew install protobuf + echo -e "${GREEN}✓ protobuf installed${NC}" + else + echo "Please install protobuf:" + echo " brew install protobuf" + echo "or download from: https://github.com/protocolbuffers/protobuf/releases" + fi + elif [[ "$OSTYPE" == "linux-gnu"* ]]; then + echo "Please install protobuf compiler:" + echo " Debian/Ubuntu: sudo apt-get install -y protobuf-compiler" + echo " Fedora/RHEL: sudo dnf install protobuf-compiler" + echo " or download from: https://github.com/protocolbuffers/protobuf/releases" + read -p "Press Enter to continue or Ctrl+C to cancel..." + fi +else + PROTOC_VERSION=$(protoc --version | awk '{print $2}') + echo -e "${GREEN}✓ protoc found (version ${PROTOC_VERSION})${NC}" +fi + +# Check if Python 3.14 is available in pyenv +AVAILABLE_VERSIONS=$(pyenv install --list | grep -E "^\s*${PYTHON_VERSION}" | head -1 | xargs) + +if [ -z "$AVAILABLE_VERSIONS" ]; then + echo -e "${YELLOW}Warning: Python ${PYTHON_VERSION} not yet available in pyenv.${NC}" + echo "Attempting to find the latest Python 3.14 release..." + AVAILABLE_VERSIONS=$(pyenv install --list | grep -E "^\s*3\.14\.[0-9]+" | tail -1 | xargs) + + if [ -z "$AVAILABLE_VERSIONS" ]; then + echo -e "${RED}Error: Python 3.14.x not available in pyenv.${NC}" + echo "Please update pyenv: cd \$(pyenv root) && git pull" + exit 1 + fi + PYTHON_VERSION="$AVAILABLE_VERSIONS" +fi + +echo -e "${GREEN}Found Python version: ${PYTHON_VERSION}${NC}" + +# Check if Python version is already installed +if ! pyenv versions --bare | grep -q "^${PYTHON_VERSION}$"; then + echo -e "${YELLOW}Installing Python ${PYTHON_VERSION}...${NC}" + echo "This may take a few minutes..." + + # Install dependencies based on OS + if [[ "$OSTYPE" == "darwin"* ]]; then + echo "Detected macOS" + if command -v brew &> /dev/null; then + echo "Ensuring build dependencies are available..." + brew install openssl readline sqlite3 xz zlib 2>/dev/null || true + fi + elif [[ "$OSTYPE" == "linux-gnu"* ]]; then + echo "Detected Linux" + echo "Make sure you have build dependencies installed:" + echo " Debian/Ubuntu: sudo apt-get install -y make build-essential libssl-dev zlib1g-dev \\" + echo " libbz2-dev libreadline-dev libsqlite3-dev wget curl llvm libncurses5-dev \\" + echo " libncursesw5-dev xz-utils tk-dev libffi-dev liblzma-dev python3-openssl git" + echo "" + read -p "Press Enter to continue or Ctrl+C to cancel..." + fi + + pyenv install ${PYTHON_VERSION} + echo -e "${GREEN}✓ Python ${PYTHON_VERSION} installed${NC}" +else + echo -e "${GREEN}✓ Python ${PYTHON_VERSION} already installed${NC}" +fi + +# Set local Python version +echo -e "${YELLOW}Setting local Python version to ${PYTHON_VERSION}...${NC}" +pyenv local ${PYTHON_VERSION} + +# Verify Python version +CURRENT_VERSION=$(python --version 2>&1 | awk '{print $2}') +echo -e "${GREEN}✓ Active Python version: ${CURRENT_VERSION}${NC}" + +# Remove existing venv if it exists +if [ -d "$VENV_DIR" ]; then + echo -e "${YELLOW}Removing existing virtual environment...${NC}" + rm -rf "$VENV_DIR" +fi + +# Create virtual environment +echo -e "${YELLOW}Creating virtual environment...${NC}" +python -m venv "$VENV_DIR" +echo -e "${GREEN}✓ Virtual environment created${NC}" + +# Activate virtual environment +source "$VENV_DIR/bin/activate" + +# Upgrade pip, setuptools, and wheel +echo -e "${YELLOW}Upgrading pip, setuptools, and wheel...${NC}" +pip install --upgrade pip setuptools wheel + +# Install requirements +echo -e "${YELLOW}Installing dependencies...${NC}" + +# Install package in development mode with all dependencies +if [ -f "pyproject.toml" ]; then + echo "Installing worldengine with dependencies from pyproject.toml..." + pip install -e ".[hdf5,dev]" +fi + +# Regenerate protobuf files if protoc is available +if command -v protoc &> /dev/null; then + echo -e "${YELLOW}Regenerating protobuf files...${NC}" + cd worldengine && protoc World.proto --python_out=protobuf && cd .. + echo -e "${GREEN}✓ Protobuf files regenerated${NC}" +else + echo -e "${YELLOW}Skipping protobuf regeneration (protoc not available)${NC}" +fi + +echo "" +echo -e "${GREEN}=== Setup Complete! ===${NC}" +echo "" +echo "Virtual environment created in: $VENV_DIR" +echo "Python version: $CURRENT_VERSION" +echo "" +echo "To activate the virtual environment, run:" +echo -e "${YELLOW} source $VENV_DIR/bin/activate${NC}" +echo "" +echo "To deactivate when done:" +echo -e "${YELLOW} deactivate${NC}" +echo "" +echo "To run tests:" +echo -e "${YELLOW} pytest tests/${NC}" +echo "" +echo "To start development:" +echo -e "${YELLOW} worldengine --help${NC}" diff --git a/tests/astar_test.py b/tests/astar_test.py index 3d0639cc..5e9b6d0a 100644 --- a/tests/astar_test.py +++ b/tests/astar_test.py @@ -1,6 +1,9 @@ """Tests for the A* Implementation.""" + import unittest + import numpy + from worldengine import astar from worldengine.common import _equal @@ -16,13 +19,50 @@ def test_traversal(self): test_map[10, :] = line test_map[10, 18] = 0.0 path_data = numpy.array( - [[0, 1], [0, 2], [0, 3], [0, 4], [0, 5], [0, 6], [0, 7], [0, 8], [0, 9], - [1, 9], [2, 9], [3, 9], [4, 9], [5, 9], [6, 9], [7, 9], [8, 9], [9, 9], - [10, 9], [11, 9], [12, 9], [13, 9], [14, 9], [15, 9], [16, 9], [17, 9], - [18, 9], [18, 10], [18, 11], [18, 12], [18, 13], [18, 14], [18, 15], - [18, 16], [18, 17], [18, 18], [18, 19], [19, 19]]) + [ + [0, 1], + [0, 2], + [0, 3], + [0, 4], + [0, 5], + [0, 6], + [0, 7], + [0, 8], + [0, 9], + [1, 9], + [2, 9], + [3, 9], + [4, 9], + [5, 9], + [6, 9], + [7, 9], + [8, 9], + [9, 9], + [10, 9], + [11, 9], + [12, 9], + [13, 9], + [14, 9], + [15, 9], + [16, 9], + [17, 9], + [18, 9], + [18, 10], + [18, 11], + [18, 12], + [18, 13], + [18, 14], + [18, 15], + [18, 16], + [18, 17], + [18, 18], + [18, 19], + [19, 19], + ] + ) shortest_path = astar.PathFinder().find(test_map, [0, 0], [19, 19]) self.assertTrue(_equal(path_data, numpy.array(shortest_path))) -if __name__ == '__main__': + +if __name__ == "__main__": unittest.main() diff --git a/tests/basic_map_operations_test.py b/tests/basic_map_operations_test.py index 490f8d99..1375890c 100644 --- a/tests/basic_map_operations_test.py +++ b/tests/basic_map_operations_test.py @@ -1,9 +1,9 @@ import unittest + from worldengine.basic_map_operations import distance, index_of_nearest class TestBasicMapOperations(unittest.TestCase): - def setUp(self): pass @@ -15,14 +15,11 @@ def test_distance(self): def test_index_of_nearest(self): self.assertTrue(index_of_nearest((0, 0), []) is None) - self.assertEqual(0, index_of_nearest( - (0, 0), [(0, 0), (10, 10), (7, 7), (-5, -5), (-2, 7)])) - self.assertEqual(3, index_of_nearest( - (-4, -4), [(0, 0), (10, 10), (7, 7), (-5, -5), (-2, 7)])) - self.assertEqual(3, index_of_nearest( - (-100, -100), [(0, 0), (10, 10), (7, 7), (-5, -5), (-2, 7)])) - self.assertEqual(3, index_of_nearest( - (-100.0, -100.0), [(0, 0), (10, 10), (7, 7), (-5, -5), (-2, 7)])) + self.assertEqual(0, index_of_nearest((0, 0), [(0, 0), (10, 10), (7, 7), (-5, -5), (-2, 7)])) + self.assertEqual(3, index_of_nearest((-4, -4), [(0, 0), (10, 10), (7, 7), (-5, -5), (-2, 7)])) + self.assertEqual(3, index_of_nearest((-100, -100), [(0, 0), (10, 10), (7, 7), (-5, -5), (-2, 7)])) + self.assertEqual(3, index_of_nearest((-100.0, -100.0), [(0, 0), (10, 10), (7, 7), (-5, -5), (-2, 7)])) + -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() diff --git a/tests/biome_test.py b/tests/biome_test.py index 1dcaa3e4..4254c026 100644 --- a/tests/biome_test.py +++ b/tests/biome_test.py @@ -1,18 +1,23 @@ -import unittest import os +import unittest -from worldengine.biome import Biome, Ocean, PolarDesert, SubpolarDryTundra, \ - CoolTemperateMoistForest, biome_name_to_index, biome_index_to_name -from worldengine.simulations.biome import BiomeSimulation +from worldengine.biome import ( + Biome, + CoolTemperateMoistForest, + Ocean, + PolarDesert, + SubpolarDryTundra, + biome_index_to_name, + biome_name_to_index, +) from worldengine.model.world import World +from worldengine.simulations.biome import BiomeSimulation class TestBiome(unittest.TestCase): - def setUp(self): tests_dir = os.path.dirname(os.path.realpath(__file__)) - self.tests_data_dir = os.path.abspath(os.path.join( - tests_dir, "../../worldengine-data/tests/data")) + self.tests_data_dir = os.path.abspath(os.path.join(tests_dir, "../../worldengine-data/tests/data")) def test_biome_by_name(self): self.assertRaises(Exception, Biome.by_name, "unexisting biome") @@ -29,47 +34,47 @@ def test_biome_name_to_index(self): # We do not want these values to change in the future, otherwise the world saved # will be not loaded correctly - self.assertEqual(0, biome_name_to_index('boreal desert')) - self.assertEqual(1, biome_name_to_index('boreal dry scrub')) - self.assertEqual(2, biome_name_to_index('boreal moist forest')) - self.assertEqual(3, biome_name_to_index('boreal rain forest')) - self.assertEqual(4, biome_name_to_index('boreal wet forest')) - self.assertEqual(5, biome_name_to_index('cool temperate desert')) - self.assertEqual(6, biome_name_to_index('cool temperate desert scrub')) - self.assertEqual(7, biome_name_to_index('cool temperate moist forest')) - self.assertEqual(8, biome_name_to_index('cool temperate rain forest')) - self.assertEqual(9, biome_name_to_index('cool temperate steppe')) - self.assertEqual(10, biome_name_to_index('cool temperate wet forest')) - self.assertEqual(11, biome_name_to_index('ice')) - self.assertEqual(12, biome_name_to_index('ocean')) - self.assertEqual(13, biome_name_to_index('polar desert')) - self.assertEqual(14, biome_name_to_index('sea')) - self.assertEqual(15, biome_name_to_index('subpolar dry tundra')) - self.assertEqual(16, biome_name_to_index('subpolar moist tundra')) - self.assertEqual(17, biome_name_to_index('subpolar rain tundra')) - self.assertEqual(18, biome_name_to_index('subpolar wet tundra')) - self.assertEqual(19, biome_name_to_index('subtropical desert')) - self.assertEqual(20, biome_name_to_index('subtropical desert scrub')) - self.assertEqual(21, biome_name_to_index('subtropical dry forest')) - self.assertEqual(22, biome_name_to_index('subtropical moist forest')) - self.assertEqual(23, biome_name_to_index('subtropical rain forest')) - self.assertEqual(24, biome_name_to_index('subtropical thorn woodland')) - self.assertEqual(25, biome_name_to_index('subtropical wet forest')) - self.assertEqual(26, biome_name_to_index('tropical desert')) - self.assertEqual(27, biome_name_to_index('tropical desert scrub')) - self.assertEqual(28, biome_name_to_index('tropical dry forest')) - self.assertEqual(29, biome_name_to_index('tropical moist forest')) - self.assertEqual(30, biome_name_to_index('tropical rain forest')) - self.assertEqual(31, biome_name_to_index('tropical thorn woodland')) - self.assertEqual(32, biome_name_to_index('tropical very dry forest')) - self.assertEqual(33, biome_name_to_index('tropical wet forest')) - self.assertEqual(34, biome_name_to_index('warm temperate desert')) - self.assertEqual(35, biome_name_to_index('warm temperate desert scrub')) - self.assertEqual(36, biome_name_to_index('warm temperate dry forest')) - self.assertEqual(37, biome_name_to_index('warm temperate moist forest')) - self.assertEqual(38, biome_name_to_index('warm temperate rain forest')) - self.assertEqual(39, biome_name_to_index('warm temperate thorn scrub')) - self.assertEqual(40, biome_name_to_index('warm temperate wet forest')) + self.assertEqual(0, biome_name_to_index("boreal desert")) + self.assertEqual(1, biome_name_to_index("boreal dry scrub")) + self.assertEqual(2, biome_name_to_index("boreal moist forest")) + self.assertEqual(3, biome_name_to_index("boreal rain forest")) + self.assertEqual(4, biome_name_to_index("boreal wet forest")) + self.assertEqual(5, biome_name_to_index("cool temperate desert")) + self.assertEqual(6, biome_name_to_index("cool temperate desert scrub")) + self.assertEqual(7, biome_name_to_index("cool temperate moist forest")) + self.assertEqual(8, biome_name_to_index("cool temperate rain forest")) + self.assertEqual(9, biome_name_to_index("cool temperate steppe")) + self.assertEqual(10, biome_name_to_index("cool temperate wet forest")) + self.assertEqual(11, biome_name_to_index("ice")) + self.assertEqual(12, biome_name_to_index("ocean")) + self.assertEqual(13, biome_name_to_index("polar desert")) + self.assertEqual(14, biome_name_to_index("sea")) + self.assertEqual(15, biome_name_to_index("subpolar dry tundra")) + self.assertEqual(16, biome_name_to_index("subpolar moist tundra")) + self.assertEqual(17, biome_name_to_index("subpolar rain tundra")) + self.assertEqual(18, biome_name_to_index("subpolar wet tundra")) + self.assertEqual(19, biome_name_to_index("subtropical desert")) + self.assertEqual(20, biome_name_to_index("subtropical desert scrub")) + self.assertEqual(21, biome_name_to_index("subtropical dry forest")) + self.assertEqual(22, biome_name_to_index("subtropical moist forest")) + self.assertEqual(23, biome_name_to_index("subtropical rain forest")) + self.assertEqual(24, biome_name_to_index("subtropical thorn woodland")) + self.assertEqual(25, biome_name_to_index("subtropical wet forest")) + self.assertEqual(26, biome_name_to_index("tropical desert")) + self.assertEqual(27, biome_name_to_index("tropical desert scrub")) + self.assertEqual(28, biome_name_to_index("tropical dry forest")) + self.assertEqual(29, biome_name_to_index("tropical moist forest")) + self.assertEqual(30, biome_name_to_index("tropical rain forest")) + self.assertEqual(31, biome_name_to_index("tropical thorn woodland")) + self.assertEqual(32, biome_name_to_index("tropical very dry forest")) + self.assertEqual(33, biome_name_to_index("tropical wet forest")) + self.assertEqual(34, biome_name_to_index("warm temperate desert")) + self.assertEqual(35, biome_name_to_index("warm temperate desert scrub")) + self.assertEqual(36, biome_name_to_index("warm temperate dry forest")) + self.assertEqual(37, biome_name_to_index("warm temperate moist forest")) + self.assertEqual(38, biome_name_to_index("warm temperate rain forest")) + self.assertEqual(39, biome_name_to_index("warm temperate thorn scrub")) + self.assertEqual(40, biome_name_to_index("warm temperate wet forest")) def test_biome_index_to_name(self): self.assertRaises(Exception, biome_index_to_name, -1) @@ -78,57 +83,57 @@ def test_biome_index_to_name(self): # We do not want these values to change in the future, otherwise the world saved # will be not loaded correctly - self.assertEqual('boreal desert', biome_index_to_name(0)) - self.assertEqual('boreal dry scrub', biome_index_to_name(1)) - self.assertEqual('boreal moist forest', biome_index_to_name(2)) - self.assertEqual('boreal rain forest', biome_index_to_name(3)) - self.assertEqual('boreal wet forest', biome_index_to_name(4)) - self.assertEqual('cool temperate desert', biome_index_to_name(5)) - self.assertEqual('cool temperate desert scrub', biome_index_to_name(6)) - self.assertEqual('cool temperate moist forest', biome_index_to_name(7)) - self.assertEqual('cool temperate rain forest', biome_index_to_name(8)) - self.assertEqual('cool temperate steppe', biome_index_to_name(9)) - self.assertEqual('cool temperate wet forest', biome_index_to_name(10)) - self.assertEqual('ice', biome_index_to_name(11)) - self.assertEqual('ocean', biome_index_to_name(12)) - self.assertEqual('polar desert', biome_index_to_name(13)) - self.assertEqual('sea', biome_index_to_name(14)) - self.assertEqual('subpolar dry tundra', biome_index_to_name(15)) - self.assertEqual('subpolar moist tundra', biome_index_to_name(16)) - self.assertEqual('subpolar rain tundra', biome_index_to_name(17)) - self.assertEqual('subpolar wet tundra', biome_index_to_name(18)) - self.assertEqual('subtropical desert', biome_index_to_name(19)) - self.assertEqual('subtropical desert scrub', biome_index_to_name(20)) - self.assertEqual('subtropical dry forest', biome_index_to_name(21)) - self.assertEqual('subtropical moist forest', biome_index_to_name(22)) - self.assertEqual('subtropical rain forest', biome_index_to_name(23)) - self.assertEqual('subtropical thorn woodland', biome_index_to_name(24)) - self.assertEqual('subtropical wet forest', biome_index_to_name(25)) - self.assertEqual('tropical desert', biome_index_to_name(26)) - self.assertEqual('tropical desert scrub', biome_index_to_name(27)) - self.assertEqual('tropical dry forest', biome_index_to_name(28)) - self.assertEqual('tropical moist forest', biome_index_to_name(29)) - self.assertEqual('tropical rain forest', biome_index_to_name(30)) - self.assertEqual('tropical thorn woodland', biome_index_to_name(31)) - self.assertEqual('tropical very dry forest', biome_index_to_name(32)) - self.assertEqual('tropical wet forest', biome_index_to_name(33)) - self.assertEqual('warm temperate desert', biome_index_to_name(34)) - self.assertEqual('warm temperate desert scrub', biome_index_to_name(35)) - self.assertEqual('warm temperate dry forest', biome_index_to_name(36)) - self.assertEqual('warm temperate moist forest', biome_index_to_name(37)) - self.assertEqual('warm temperate rain forest', biome_index_to_name(38)) - self.assertEqual('warm temperate thorn scrub', biome_index_to_name(39)) - self.assertEqual('warm temperate wet forest', biome_index_to_name(40)) + self.assertEqual("boreal desert", biome_index_to_name(0)) + self.assertEqual("boreal dry scrub", biome_index_to_name(1)) + self.assertEqual("boreal moist forest", biome_index_to_name(2)) + self.assertEqual("boreal rain forest", biome_index_to_name(3)) + self.assertEqual("boreal wet forest", biome_index_to_name(4)) + self.assertEqual("cool temperate desert", biome_index_to_name(5)) + self.assertEqual("cool temperate desert scrub", biome_index_to_name(6)) + self.assertEqual("cool temperate moist forest", biome_index_to_name(7)) + self.assertEqual("cool temperate rain forest", biome_index_to_name(8)) + self.assertEqual("cool temperate steppe", biome_index_to_name(9)) + self.assertEqual("cool temperate wet forest", biome_index_to_name(10)) + self.assertEqual("ice", biome_index_to_name(11)) + self.assertEqual("ocean", biome_index_to_name(12)) + self.assertEqual("polar desert", biome_index_to_name(13)) + self.assertEqual("sea", biome_index_to_name(14)) + self.assertEqual("subpolar dry tundra", biome_index_to_name(15)) + self.assertEqual("subpolar moist tundra", biome_index_to_name(16)) + self.assertEqual("subpolar rain tundra", biome_index_to_name(17)) + self.assertEqual("subpolar wet tundra", biome_index_to_name(18)) + self.assertEqual("subtropical desert", biome_index_to_name(19)) + self.assertEqual("subtropical desert scrub", biome_index_to_name(20)) + self.assertEqual("subtropical dry forest", biome_index_to_name(21)) + self.assertEqual("subtropical moist forest", biome_index_to_name(22)) + self.assertEqual("subtropical rain forest", biome_index_to_name(23)) + self.assertEqual("subtropical thorn woodland", biome_index_to_name(24)) + self.assertEqual("subtropical wet forest", biome_index_to_name(25)) + self.assertEqual("tropical desert", biome_index_to_name(26)) + self.assertEqual("tropical desert scrub", biome_index_to_name(27)) + self.assertEqual("tropical dry forest", biome_index_to_name(28)) + self.assertEqual("tropical moist forest", biome_index_to_name(29)) + self.assertEqual("tropical rain forest", biome_index_to_name(30)) + self.assertEqual("tropical thorn woodland", biome_index_to_name(31)) + self.assertEqual("tropical very dry forest", biome_index_to_name(32)) + self.assertEqual("tropical wet forest", biome_index_to_name(33)) + self.assertEqual("warm temperate desert", biome_index_to_name(34)) + self.assertEqual("warm temperate desert scrub", biome_index_to_name(35)) + self.assertEqual("warm temperate dry forest", biome_index_to_name(36)) + self.assertEqual("warm temperate moist forest", biome_index_to_name(37)) + self.assertEqual("warm temperate rain forest", biome_index_to_name(38)) + self.assertEqual("warm temperate thorn scrub", biome_index_to_name(39)) + self.assertEqual("warm temperate wet forest", biome_index_to_name(40)) def test_locate_biomes(self): w = World.open_protobuf("%s/seed_28070.world" % self.tests_data_dir) - #TODO: do something to the result + # TODO: do something to the result cm, biome_cm = BiomeSimulation().execute(w, 28070) @staticmethod def name(): - return 'cool temperate moist forest' + return "cool temperate moist forest" -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() diff --git a/tests/blessed_images/generated_blessed_images.py b/tests/blessed_images/generated_blessed_images.py index fa53ec24..033e27b9 100644 --- a/tests/blessed_images/generated_blessed_images.py +++ b/tests/blessed_images/generated_blessed_images.py @@ -9,15 +9,28 @@ import os -from worldengine.model.world import * -from worldengine.draw import * +from worldengine.draw import ( + draw_ancientmap_on_file, + draw_biome_on_file, + draw_elevation_on_file, + draw_grayscale_heightmap_on_file, + draw_ocean_on_file, + draw_precipitation_on_file, + draw_rivers_on_image, + draw_riversmap_on_file, + draw_satellite_on_file, + draw_scatter_plot_on_file, + draw_simple_elevation_on_file, + draw_temperature_levels_on_file, + draw_world_on_file, +) from worldengine.image_io import PNGWriter +from worldengine.model.world import World def main(blessed_images_dir, tests_data_dir): w = World.open_protobuf("%s/seed_28070.world" % tests_data_dir) - draw_simple_elevation_on_file(w, "%s/simple_elevation_28070.png" - % blessed_images_dir, w.sea_level()) + draw_simple_elevation_on_file(w, "%s/simple_elevation_28070.png" % blessed_images_dir, w.sea_level()) draw_elevation_on_file(w, "%s/elevation_28070_shadow.png" % blessed_images_dir, shadow=True) draw_elevation_on_file(w, "%s/elevation_28070_no_shadow.png" % blessed_images_dir, shadow=False) draw_riversmap_on_file(w, "%s/riversmap_28070.png" % blessed_images_dir) @@ -29,17 +42,16 @@ def main(blessed_images_dir, tests_data_dir): draw_biome_on_file(w, "%s/biome_28070.png" % blessed_images_dir) draw_scatter_plot_on_file(w, "%s/scatter_28070.png" % blessed_images_dir) draw_satellite_on_file(w, "%s/satellite_28070.png" % blessed_images_dir) - draw_ancientmap_on_file( - w, "%s/ancientmap_28070_factor3.png" % blessed_images_dir, resize_factor=3) + draw_ancientmap_on_file(w, "%s/ancientmap_28070_factor3.png" % blessed_images_dir, resize_factor=3) - img = PNGWriter.rgba_from_dimensions( - w.width * 2, w.height * 2, "%s/rivers_28070_factor2.png" % blessed_images_dir) + img = PNGWriter.rgba_from_dimensions(w.width * 2, w.height * 2, "%s/rivers_28070_factor2.png" % blessed_images_dir) draw_rivers_on_image(w, img, factor=2) img.complete() -if __name__ == '__main__': + +if __name__ == "__main__": BLESSED_IMAGES_DIR = os.path.abspath( - os.path.join( - os.path.dirname(os.path.realpath(__file__)), "../../../worldengine-data/tests/images")) - TESTS_DATA_DIR = os.path.abspath(os.path.join(BLESSED_IMAGES_DIR, '../data')) + os.path.join(os.path.dirname(os.path.realpath(__file__)), "../../../worldengine-data/tests/images") + ) + TESTS_DATA_DIR = os.path.abspath(os.path.join(BLESSED_IMAGES_DIR, "../data")) main(BLESSED_IMAGES_DIR, TESTS_DATA_DIR) diff --git a/tests/cli_test.py b/tests/cli_test.py index 59652c11..72ffd45d 100644 --- a/tests/cli_test.py +++ b/tests/cli_test.py @@ -1,22 +1,23 @@ import os import sys import unittest + +import pytest + from tests.draw_test import TestBase from worldengine import __main__ from worldengine.cli.main import main class TestCLI(TestBase): - def setUp(self): - super(TestCLI, self).setUp() + super().setUp() self.world = "%s/seed_28070.world" % self.tests_data_dir def test__main__(self): assert __main__ def test_options(self): - backup_argv = sys.argv sys.argv = ["python", "--help"] self.assertRaises(SystemExit, main) sys.argv = ["python", "--version"] @@ -39,11 +40,19 @@ def test_options(self): sys.argv = ["python", "plates", "--number-of-plates", "101"] self.assertRaises(SystemExit, main) + @pytest.mark.skipif(sys.platform == "win32", reason="PyPlatec has known stability issues on Windows") def test_warnings(self): - backup_argv = sys.argv - sys.argv = ["python", "--width", "16", "--height", "16", - "--temps", "1.1/.235/.406/.561/.634/-.1", "--humidity", - "1.1/.222/.493/.764/.927/.986/-.1"] + sys.argv = [ + "python", + "--width", + "16", + "--height", + "16", + "--temps", + "1.1/.235/.406/.561/.634/-.1", + "--humidity", + "1.1/.222/.493/.764/.927/.986/-.1", + ] try: main() except Exception as e: @@ -59,15 +68,29 @@ def test_smoke_info(self): # TODO: fill in the rest of the options and their possibilities sys.argv = backup_argv + @pytest.mark.skipif(sys.platform == "win32", reason="PyPlatec has known stability issues on Windows") def test_smoke_full(self): # the big smoke test, can we go through # everything without it exploding? - backup_argv = sys.argv - sys.argv = ["python", "--width", "16", "--height", "16", - "-r", "-v", "--gs", "--scatter", "--temps", - ".126/.235/.406/.561/.634/.876", "--humidity", - ".059/.222/.493/.764/.927/.986/.998", "-go", ".2", - "-gv", "1.25"] + sys.argv = [ + "python", + "--width", + "16", + "--height", + "16", + "-r", + "-v", + "--gs", + "--scatter", + "--temps", + ".126/.235/.406/.561/.634/.876", + "--humidity", + ".059/.222/.493/.764/.927/.986/.998", + "-go", + ".2", + "-gv", + "1.25", + ] try: main() except Exception as e: @@ -82,10 +105,10 @@ def test_smoke_ancient(self): raise e sys.argv = backup_argv + @pytest.mark.skipif(sys.platform == "win32", reason="PyPlatec has known stability issues on Windows") def test_smoke_plates(self): backup_argv = sys.argv - sys.argv = ["python", "plates", "--width", "16", - "--height", "16", "--number-of-plates", "2"] + sys.argv = ["python", "plates", "--width", "16", "--height", "16", "--number-of-plates", "2"] try: main() except Exception as e: @@ -93,5 +116,5 @@ def test_smoke_plates(self): sys.argv = backup_argv -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() diff --git a/tests/common_test.py b/tests/common_test.py index d66af25f..1c85cd3f 100644 --- a/tests/common_test.py +++ b/tests/common_test.py @@ -1,10 +1,11 @@ import unittest + import numpy -from worldengine.common import Counter, anti_alias, get_verbose, set_verbose, _equal +from worldengine.common import Counter, _equal, anti_alias, get_verbose, set_verbose -class TestCommon(unittest.TestCase): +class TestCommon(unittest.TestCase): def setUp(self): pass @@ -27,9 +28,7 @@ def test_counter(self): self.assertEqual("a : 1\nb : 3\n", c.to_str()) def test_antialias(self): - original = numpy.array([[0.5, 0.12, 0.7, 0.15, 0.0], - [0.0, 0.12, 0.7, 0.7, 8.0], - [0.2, 0.12, 0.7, 0.7, 4.0]]) + original = numpy.array([[0.5, 0.12, 0.7, 0.15, 0.0], [0.0, 0.12, 0.7, 0.7, 8.0], [0.2, 0.12, 0.7, 0.7, 4.0]]) antialiased = anti_alias(original, 1) self.assertAlmostEqual(1.2781818181818183, antialiased[0][0]) self.assertAlmostEqual(0.4918181818181818, antialiased[1][2]) @@ -42,13 +41,14 @@ def test_dictionary_equality(self): a = {} b = {} - a['foo'] = 'bar' + a["foo"] = "bar" self.assertFalse(_equal(a, b)) - b['foo'] = 'bar' + b["foo"] = "bar" self.assertTrue(_equal(a, b)) -if __name__ == '__main__': + +if __name__ == "__main__": unittest.main() diff --git a/tests/data/data_generator.py b/tests/data/data_generator.py index 2f14847d..5149ff48 100644 --- a/tests/data/data_generator.py +++ b/tests/data/data_generator.py @@ -1,4 +1,4 @@ -""" This program generates some of the data used by tests other data samples could have been +"""This program generates some of the data used by tests other data samples could have been obtained simply by running the program. The data lives in the worldengine-data repo: https://github.com/Mindwerks/worldengine-data @@ -8,7 +8,9 @@ """ import os + import numpy + from worldengine.plates import world_gen @@ -18,7 +20,7 @@ def main(tests_data_dir): w.protobuf_to_file("%s/seed_28070.world" % tests_data_dir) -if __name__ == '__main__': +if __name__ == "__main__": BLESSED_IMAGES_DIR = os.path.dirname(os.path.realpath(__file__)) - TESTS_DATA_DIR = os.path.abspath(os.path.join(BLESSED_IMAGES_DIR, '../data')) + TESTS_DATA_DIR = os.path.abspath(os.path.join(BLESSED_IMAGES_DIR, "../data")) main(TESTS_DATA_DIR) diff --git a/tests/draw_test.py b/tests/draw_test.py index 7804310f..ce3c0d0e 100644 --- a/tests/draw_test.py +++ b/tests/draw_test.py @@ -1,26 +1,35 @@ -import unittest import os +import unittest + import numpy -from worldengine.draw import _biome_colors, draw_simple_elevation, elevation_color, \ - draw_elevation, draw_riversmap, draw_ocean, draw_precipitation, \ - draw_world, draw_temperature_levels, draw_biome, draw_scatter_plot, draw_satellite from worldengine.biome import Biome +from worldengine.draw import ( + _biome_colors, + draw_biome, + draw_elevation, + draw_ocean, + draw_precipitation, + draw_riversmap, + draw_satellite, + draw_scatter_plot, + draw_simple_elevation, + draw_temperature_levels, + draw_world, + elevation_color, +) +from worldengine.image_io import PNGReader, PNGWriter from worldengine.model.world import World -from worldengine.image_io import PNGWriter, PNGReader class TestBase(unittest.TestCase): - def setUp(self): tests_dir = os.path.dirname(os.path.realpath(__file__)) - self.tests_data_dir = os.path.abspath(os.path.join( - tests_dir, "../../worldengine-data/tests/data")) - self.tests_blessed_images_dir = os.path.abspath( - os.path.join(tests_dir, "../../worldengine-data/tests/images")) + self.tests_data_dir = os.path.abspath(os.path.join(tests_dir, "../../worldengine-data/tests/data")) + self.tests_blessed_images_dir = os.path.abspath(os.path.join(tests_dir, "../../worldengine-data/tests/images")) self.assertTrue( - os.path.isdir(self.tests_data_dir), - "worldengine-data doesn't exist, please clone it before continuing.") + os.path.isdir(self.tests_data_dir), "worldengine-data doesn't exist, please clone it before continuing." + ) def _assert_is_valid_color(self, color, color_name): r, g, b = color @@ -39,10 +48,12 @@ def _assert_img_equal(self, blessed_image_name, drawn_image): blessed_img = PNGReader("%s/%s.png" % (self.tests_blessed_images_dir, blessed_image_name)) # check shapes (i.e. (height, width, channels)-tuple) - self.assertTrue(blessed_img.array.shape == drawn_image.array.shape, - "Blessed and drawn images differ in height, width " + - "and/or amount of channels. Blessed %s, drawn %s" - % (str(blessed_img.array.shape), str(drawn_image.array.shape))) + self.assertTrue( + blessed_img.array.shape == drawn_image.array.shape, + "Blessed and drawn images differ in height, width " + + "and/or amount of channels. Blessed %s, drawn %s" + % (str(blessed_img.array.shape), str(drawn_image.array.shape)), + ) # compare images; # cmp_array will be an array of booleans in case of equal shapes @@ -54,14 +65,17 @@ def _assert_img_equal(self, blessed_image_name, drawn_image): diff = numpy.transpose(numpy.nonzero(cmp_array)) # list of tuples of differing indices self.assertTrue( False, - "Pixels at %i, %i are different. Blessed %s, drawn %s" % ( - diff[0][0], diff[0][1], + "Pixels at %i, %i are different. Blessed %s, drawn %s" + % ( + diff[0][0], + diff[0][1], blessed_img.array[diff[0][0], diff[0][1]], - drawn_image.array[diff[0][0], diff[0][1]])) + drawn_image.array[diff[0][0], diff[0][1]], + ), + ) class TestDraw(TestBase): - def test_biome_colors(self): self.assertEqual(Biome.all_names(), sorted(_biome_colors.keys())) @@ -83,12 +97,12 @@ def test_elevation_color(self): # ra, ga, ba = c # rb, gb, bb = c_low # rc, gc, bc = c_high - #self.assertAlmostEqual(ra, rb, 5, "value %f, red, low, from %f to %f" % (v, ra, rb)) - #self.assertAlmostEqual(ra, rc, 5, "value %f, red, high, from %f to %f" % (v, ra, rc)) - #self.assertAlmostEqual(ga, gb, 5, "value %f, green, low, from %f to %f" % (v, ga, gb)) - #self.assertAlmostEqual(ga, gc, 5, "value %f, green, high, from %f to %f" % (v, ga, gc)) - #self.assertAlmostEqual(ba, bb, 5, "value %f, blue, low, from %f to %f" % (v, ba, bb)) - #self.assertAlmostEqual(ba, bc, 5, "value %f, blue, high, from %f to %f" % (v, ba, bc)) + # self.assertAlmostEqual(ra, rb, 5, "value %f, red, low, from %f to %f" % (v, ra, rb)) + # self.assertAlmostEqual(ra, rc, 5, "value %f, red, high, from %f to %f" % (v, ra, rc)) + # self.assertAlmostEqual(ga, gb, 5, "value %f, green, low, from %f to %f" % (v, ga, gb)) + # self.assertAlmostEqual(ga, gc, 5, "value %f, green, high, from %f to %f" % (v, ga, gc)) + # self.assertAlmostEqual(ba, bb, 5, "value %f, blue, low, from %f to %f" % (v, ba, bb)) + # self.assertAlmostEqual(ba, bc, 5, "value %f, blue, high, from %f to %f" % (v, ba, bc)) def test_draw_simple_elevation(self): w = World.open_protobuf("%s/seed_28070.world" % self.tests_data_dir) @@ -116,13 +130,13 @@ def test_draw_river_map(self): def test_draw_grayscale_heightmap(self): w = World.open_protobuf("%s/seed_28070.world" % self.tests_data_dir) - target = PNGWriter.grayscale_from_array(w.layers['elevation'].data, scale_to_range=True) + target = PNGWriter.grayscale_from_array(w.layers["elevation"].data, scale_to_range=True) self._assert_img_equal("grayscale_heightmap_28070", target) def test_draw_ocean(self): w = World.open_protobuf("%s/seed_28070.world" % self.tests_data_dir) target = PNGWriter.rgba_from_dimensions(w.width, w.height) - draw_ocean(w.layers['ocean'].data, target) + draw_ocean(w.layers["ocean"].data, target) self._assert_img_equal("ocean_28070", target) def test_draw_precipitation(self): @@ -161,5 +175,6 @@ def test_draw_satellite(self): draw_satellite(w, target) self._assert_img_equal("satellite_28070", target) -if __name__ == '__main__': + +if __name__ == "__main__": unittest.main() diff --git a/tests/drawing_functions_test.py b/tests/drawing_functions_test.py index 6aea0436..3c861db7 100644 --- a/tests/drawing_functions_test.py +++ b/tests/drawing_functions_test.py @@ -1,15 +1,14 @@ import unittest -from worldengine.drawing_functions import draw_ancientmap, gradient, draw_rivers_on_image -from worldengine.model.world import World -from worldengine.image_io import PNGWriter from tests.draw_test import TestBase +from worldengine.drawing_functions import draw_ancientmap, draw_rivers_on_image, gradient +from worldengine.image_io import PNGWriter +from worldengine.model.world import World class TestDrawingFunctions(TestBase): - def setUp(self): - super(TestDrawingFunctions, self).setUp() + super().setUp() self.w = World.open_protobuf("%s/seed_28070.world" % self.tests_data_dir) def test_draw_ancient_map(self): @@ -23,19 +22,16 @@ def test_draw_ancient_map_outer_borders(self): target = PNGWriter.rgba_from_dimensions(self.w.width * 3, self.w.height * 3) draw_ancientmap(self.w, target, resize_factor=3, draw_outer_land_border=True) - def test_gradient(self): - self._assert_are_colors_equal((10, 20, 40), - gradient(0.0, 0.0, 1.0, (10, 20, 40), (0, 128, 240))) - self._assert_are_colors_equal((0, 128, 240), - gradient(1.0, 0.0, 1.0, (10, 20, 40), (0, 128, 240))) - self._assert_are_colors_equal((5, 74, 140), - gradient(0.5, 0.0, 1.0, (10, 20, 40), (0, 128, 240))) + self._assert_are_colors_equal((10, 20, 40), gradient(0.0, 0.0, 1.0, (10, 20, 40), (0, 128, 240))) + self._assert_are_colors_equal((0, 128, 240), gradient(1.0, 0.0, 1.0, (10, 20, 40), (0, 128, 240))) + self._assert_are_colors_equal((5, 74, 140), gradient(0.5, 0.0, 1.0, (10, 20, 40), (0, 128, 240))) def test_draw_rivers_on_image(self): target = PNGWriter.rgba_from_dimensions(self.w.width * 2, self.w.height * 2) draw_rivers_on_image(self.w, target, factor=2) self._assert_img_equal("rivers_28070_factor2", target) -if __name__ == '__main__': + +if __name__ == "__main__": unittest.main() diff --git a/tests/generation_test.py b/tests/generation_test.py index 2e206572..c9d78d51 100644 --- a/tests/generation_test.py +++ b/tests/generation_test.py @@ -1,16 +1,18 @@ +import sys import unittest + import numpy +import pytest from tests.draw_test import TestBase - -from worldengine.plates import Step, center_land, world_gen -from worldengine.model.world import World, Size, GenerationParameters -from worldengine.generation import sea_depth from worldengine.common import anti_alias +from worldengine.generation import sea_depth +from worldengine.model.world import GenerationParameters, Size, World +from worldengine.plates import Step, center_land, world_gen class TestGeneration(TestBase): - + @pytest.mark.skipif(sys.platform == "win32", reason="PyPlatec has known issues on Windows") def test_world_gen_does_not_explode_badly(self): # FIXME: remove me when proper tests are in place # Very stupid test that just verify nothing explode badly @@ -41,8 +43,7 @@ def test_center_land(self): def test_sea_depth(self): ocean_level = 1.0 extent = 11 - w = World( - "sea_depth", Size(extent, extent), 0, GenerationParameters(0, ocean_level, 0), None) + w = World("sea_depth", Size(extent, extent), 0, GenerationParameters(0, ocean_level, 0), None) ocean = numpy.full([extent, extent], True) ocean[5, 5] = False @@ -55,17 +56,131 @@ def test_sea_depth(self): w.elevation = (elevation, t) w.ocean = ocean - desired_result = numpy.asarray([0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, \ - 0.9, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.9, \ - 0.9, 0.7, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.7, 0.9, \ - 0.9, 0.7, 0.5, 0.3, 0.3, 0.3, 0.3, 0.3, 0.5, 0.7, 0.9, \ - 0.9, 0.7, 0.5, 0.3, 0.0, 0.0, 0.0, 0.3, 0.5, 0.7, 0.9, \ - 0.9, 0.7, 0.5, 0.3, 0.0, -1.0, 0.0, 0.3, 0.5, 0.7, 0.9, \ - 0.9, 0.7, 0.5, 0.3, 0.0, 0.0, 0.0, 0.3, 0.5, 0.7, 0.9, \ - 0.9, 0.7, 0.5, 0.3, 0.3, 0.3, 0.3, 0.3, 0.5, 0.7, 0.9, \ - 0.9, 0.7, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.7, 0.9, \ - 0.9, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.9, \ - 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9]) + desired_result = numpy.asarray( + [ + 0.9, + 0.9, + 0.9, + 0.9, + 0.9, + 0.9, + 0.9, + 0.9, + 0.9, + 0.9, + 0.9, + 0.9, + 0.7, + 0.7, + 0.7, + 0.7, + 0.7, + 0.7, + 0.7, + 0.7, + 0.7, + 0.9, + 0.9, + 0.7, + 0.5, + 0.5, + 0.5, + 0.5, + 0.5, + 0.5, + 0.5, + 0.7, + 0.9, + 0.9, + 0.7, + 0.5, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.5, + 0.7, + 0.9, + 0.9, + 0.7, + 0.5, + 0.3, + 0.0, + 0.0, + 0.0, + 0.3, + 0.5, + 0.7, + 0.9, + 0.9, + 0.7, + 0.5, + 0.3, + 0.0, + -1.0, + 0.0, + 0.3, + 0.5, + 0.7, + 0.9, + 0.9, + 0.7, + 0.5, + 0.3, + 0.0, + 0.0, + 0.0, + 0.3, + 0.5, + 0.7, + 0.9, + 0.9, + 0.7, + 0.5, + 0.3, + 0.3, + 0.3, + 0.3, + 0.3, + 0.5, + 0.7, + 0.9, + 0.9, + 0.7, + 0.5, + 0.5, + 0.5, + 0.5, + 0.5, + 0.5, + 0.5, + 0.7, + 0.9, + 0.9, + 0.7, + 0.7, + 0.7, + 0.7, + 0.7, + 0.7, + 0.7, + 0.7, + 0.7, + 0.9, + 0.9, + 0.9, + 0.9, + 0.9, + 0.9, + 0.9, + 0.9, + 0.9, + 0.9, + 0.9, + 0.9, + ] + ) desired_result = desired_result.reshape([extent, extent]) @@ -86,5 +201,5 @@ def test_sea_depth(self): self.assertAlmostEqual(desired_result[y, x], result[y, x]) -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() diff --git a/tests/serialization_test.py b/tests/serialization_test.py index 38ea9965..89ab6273 100644 --- a/tests/serialization_test.py +++ b/tests/serialization_test.py @@ -1,37 +1,36 @@ -import unittest -import tempfile import os +import sys +import tempfile +import unittest -from worldengine.plates import Step, world_gen -from worldengine.model.world import World -from worldengine.common import _equal -from worldengine.hdf5_serialization import save_world_to_hdf5, load_world_to_hdf5 +import pytest -try: # are we python3? - set -except NameError: # apparently not. - from sets import Set as set +from worldengine.common import _equal +from worldengine.hdf5_serialization import load_world_to_hdf5, save_world_to_hdf5 +from worldengine.model.world import World +from worldengine.plates import Step, world_gen class TestSerialization(unittest.TestCase): - def setUp(self): self.maxDiff = None + @pytest.mark.skipif(sys.platform == "win32", reason="PyPlatec has known issues on Windows") def test_protobuf_serialize_unserialize(self): w = world_gen("Dummy", 32, 16, 1, step=Step.get_by_name("full")) serialized = w.protobuf_serialize() unserialized = World.protobuf_unserialize(serialized) self.assertEqual(set(w.layers.keys()), set(unserialized.layers.keys())) - for l in w.layers.keys(): - self.assertEqual(w.layers[l], unserialized.layers[l], "Comparing %s" % l) - self.assertTrue(_equal(w.ocean_level, unserialized.ocean_level)) - self.assertEquals(w.seed, unserialized.seed) - self.assertEquals(w.n_plates, unserialized.n_plates) - self.assertEquals(w.step, unserialized.step) - self.assertEqual(sorted(dir(w)), sorted(dir(unserialized))) + for layer in w.layers.keys(): + self.assertEqual(w.layers[layer], unserialized.layers[layer], "Comparing %s" % layer) + self.assertTrue(_equal(w.ocean_level, unserialized.ocean_level)) + self.assertEqual(w.seed, unserialized.seed) + self.assertEqual(w.n_plates, unserialized.n_plates) + self.assertEqual(w.step, unserialized.step) + self.assertEqual(sorted(dir(w)), sorted(dir(unserialized))) self.assertEqual(w, unserialized) + @pytest.mark.skipif(sys.platform == "win32", reason="PyPlatec has known issues on Windows") def test_hdf5_serialize_unserialize(self): filename = None try: @@ -42,20 +41,19 @@ def test_hdf5_serialize_unserialize(self): save_world_to_hdf5(w, filename) unserialized = load_world_to_hdf5(filename) self.assertEqual(set(w.layers.keys()), set(unserialized.layers.keys())) - self.assertEqual( - w.layers['humidity'].quantiles, - unserialized.layers['humidity'].quantiles) - for l in w.layers.keys(): - self.assertEqual(w.layers[l], unserialized.layers[l], "Comparing %s" % l) - self.assertTrue(_equal(w.ocean_level, unserialized.ocean_level)) - self.assertEquals(w.seed, unserialized.seed) - self.assertEquals(w.n_plates, unserialized.n_plates) - self.assertEquals(w.step, unserialized.step) - self.assertEqual(sorted(dir(w)), sorted(dir(unserialized))) - #self.assertEqual(w, unserialized) + self.assertEqual(w.layers["humidity"].quantiles, unserialized.layers["humidity"].quantiles) + for layer in w.layers.keys(): + self.assertEqual(w.layers[layer], unserialized.layers[layer], "Comparing %s" % layer) + self.assertTrue(_equal(w.ocean_level, unserialized.ocean_level)) + self.assertEqual(w.seed, unserialized.seed) + self.assertEqual(w.n_plates, unserialized.n_plates) + self.assertEqual(w.step, unserialized.step) + self.assertEqual(sorted(dir(w)), sorted(dir(unserialized))) + # self.assertEqual(w, unserialized) finally: if filename: os.remove(filename) -if __name__ == '__main__': + +if __name__ == "__main__": unittest.main() diff --git a/tests/simulation_test.py b/tests/simulation_test.py index b4d08ef4..2d1c5a27 100644 --- a/tests/simulation_test.py +++ b/tests/simulation_test.py @@ -2,8 +2,9 @@ import numpy +from worldengine.model.world import GenerationParameters, Size, World from worldengine.simulations.hydrology import WatermapSimulation -from worldengine.model.world import World, Size, GenerationParameters + class TestSimulation(unittest.TestCase): # This does only test that watermap leaves the rng in the state that the @@ -28,7 +29,7 @@ def test_watermap_rng_stabilty(self): percipitation = numpy.ones((size.height, size.width)) - elevation = numpy.fromfunction(lambda y, x: y*x, (size.height, size.width)) + elevation = numpy.fromfunction(lambda y, x: y * x, (size.height, size.width)) t = numpy.zeros(5) @@ -48,7 +49,6 @@ def test_watermap_rng_stabilty(self): d = numpy.random.randint(0, 100) self.assertEqual(d, 59) - def test_watermap_does_not_break_with_no_land(self): seed = 12345 numpy.random.seed(seed) @@ -62,7 +62,6 @@ def test_watermap_does_not_break_with_no_land(self): WatermapSimulation._watermap(w, 200) - def test_random_land_returns_only_land(self): size = Size(100, 90) @@ -75,9 +74,9 @@ def test_random_land_returns_only_land(self): land_indices = w.random_land(num_samples) - for i in range(0, num_samples*2, 2): - self.assertFalse(ocean[land_indices[i+1], land_indices[i]]) - + for i in range(0, num_samples * 2, 2): + self.assertFalse(ocean[land_indices[i + 1], land_indices[i]]) + -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() diff --git a/tox.ini b/tox.ini index 03505481..f8eb2d49 100644 --- a/tox.ini +++ b/tox.ini @@ -1,53 +1,46 @@ [tox] -envlist = py27, py34, pypy, pyflakes, manifest -skip_install = True -skipsdist = True - -[base] -deps = - PyPlatec - noise - nose - six - pypng +envlist = py39, py310, py311, py312, py313, py314 +requires = + tox>=4 + tox-uv>=1.0 [testenv] +package = wheel +wheel_build_env = .pkg +uv_seed = true deps = - coverage - Cython - numpy==1.9.2 - pygdal==1.11.3.3 - protobuf==3.0.0a3 - h5py - {[base]deps} + Cython>=3.0.0 + numpy>=1.24.0 + PyPlatec==1.4.2 + protobuf>=4.21.0 + pypng>=0.20220715.0 + noise==1.2.2 + pytest>=7.4.0 + h5py>=3.10.0 + +setenv = + PYTHONPATH = {toxinidir} commands = - {envpython} --version - nosetests --version - coverage run --source worldengine --branch {envdir}/bin/nosetests tests -v - coverage report --omit=worldengine/tests/* --show-missing + python -c "import sys; print(sys.version)" + python --version + python -c "import worldengine; print(worldengine.__version__)" + pytest -v tests -[testenv:pypy] -install_command = pip install {opts} {packages} --no-deps +[testenv:py39] +basepython = python3.9 -deps = - cffi - git+https://bitbucket.org/pypy/numpy.git - protobuf==3.0.0a3 - {[base]deps} +[testenv:py310] +basepython = python3.10 -commands = - {envpython} --version - nosetests --version - {envdir}/bin/nosetests tests -v +[testenv:py311] +basepython = python3.11 -[testenv:pyflakes] -deps = pyflakes -commands = pyflakes worldengine +[testenv:py312] +basepython = python3.12 +[testenv:py313] +basepython = python3.13 -[testenv:manifest] -deps = - check-manifest -commands = - check-manifest +[testenv:py314] +basepython = python3.14 diff --git a/uv.lock b/uv.lock new file mode 100644 index 00000000..152163a1 --- /dev/null +++ b/uv.lock @@ -0,0 +1,1933 @@ +version = 1 +revision = 3 +requires-python = ">=3.9" +resolution-markers = [ + "python_full_version >= '3.11'", + "python_full_version == '3.10.*'", + "python_full_version < '3.10'", +] + +[[package]] +name = "alabaster" +version = "0.7.16" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.10'", +] +sdist = { url = "https://files.pythonhosted.org/packages/c9/3e/13dd8e5ed9094e734ac430b5d0eb4f2bb001708a8b7856cbf8e084e001ba/alabaster-0.7.16.tar.gz", hash = "sha256:75a8b99c28a5dad50dd7f8ccdd447a121ddb3892da9e53d1ca5cca3106d58d65", size = 23776, upload-time = "2024-01-10T00:56:10.189Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/32/34/d4e1c02d3bee589efb5dfa17f88ea08bdb3e3eac12bc475462aec52ed223/alabaster-0.7.16-py3-none-any.whl", hash = "sha256:b46733c07dce03ae4e150330b975c75737fa60f0a7c591b6c8bf4928a28e2c92", size = 13511, upload-time = "2024-01-10T00:56:08.388Z" }, +] + +[[package]] +name = "alabaster" +version = "1.0.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.11'", + "python_full_version == '3.10.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/a6/f8/d9c74d0daf3f742840fd818d69cfae176fa332022fd44e3469487d5a9420/alabaster-1.0.0.tar.gz", hash = "sha256:c00dca57bca26fa62a6d7d0a9fcce65f3e026e9bfe33e9c538fd3fbb2144fd9e", size = 24210, upload-time = "2024-07-26T18:15:03.762Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/b3/6b4067be973ae96ba0d615946e314c5ae35f9f993eca561b356540bb0c2b/alabaster-1.0.0-py3-none-any.whl", hash = "sha256:fc6786402dc3fcb2de3cabd5fe455a2db534b371124f1f21de8731783dec828b", size = 13929, upload-time = "2024-07-26T18:15:02.05Z" }, +] + +[[package]] +name = "babel" +version = "2.17.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7d/6b/d52e42361e1aa00709585ecc30b3f9684b3ab62530771402248b1b1d6240/babel-2.17.0.tar.gz", hash = "sha256:0c54cffb19f690cdcc52a3b50bcbf71e07a808d1c80d549f2459b9d2cf0afb9d", size = 9951852, upload-time = "2025-02-01T15:17:41.026Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/b8/3fe70c75fe32afc4bb507f75563d39bc5642255d1d94f1f23604725780bf/babel-2.17.0-py3-none-any.whl", hash = "sha256:4d0b53093fdfb4b21c92b5213dba5a1b23885afa8383709427046b21c366e5f2", size = 10182537, upload-time = "2025-02-01T15:17:37.39Z" }, +] + +[[package]] +name = "cachetools" +version = "6.2.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fb/44/ca1675be2a83aeee1886ab745b28cda92093066590233cc501890eb8417a/cachetools-6.2.2.tar.gz", hash = "sha256:8e6d266b25e539df852251cfd6f990b4bc3a141db73b939058d809ebd2590fc6", size = 31571, upload-time = "2025-11-13T17:42:51.465Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e6/46/eb6eca305c77a4489affe1c5d8f4cae82f285d9addd8de4ec084a7184221/cachetools-6.2.2-py3-none-any.whl", hash = "sha256:6c09c98183bf58560c97b2abfcedcbaf6a896a490f534b031b661d3723b45ace", size = 11503, upload-time = "2025-11-13T17:42:50.232Z" }, +] + +[[package]] +name = "certifi" +version = "2025.11.12" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/8c/58f469717fa48465e4a50c014a0400602d3c437d7c0c468e17ada824da3a/certifi-2025.11.12.tar.gz", hash = "sha256:d8ab5478f2ecd78af242878415affce761ca6bc54a22a27e026d7c25357c3316", size = 160538, upload-time = "2025-11-12T02:54:51.517Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/70/7d/9bc192684cea499815ff478dfcdc13835ddf401365057044fb721ec6bddb/certifi-2025.11.12-py3-none-any.whl", hash = "sha256:97de8790030bbd5c2d96b7ec782fc2f7820ef8dba6db909ccf95449f2d062d4b", size = 159438, upload-time = "2025-11-12T02:54:49.735Z" }, +] + +[[package]] +name = "cfgv" +version = "3.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/11/74/539e56497d9bd1d484fd863dd69cbbfa653cd2aa27abfe35653494d85e94/cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560", size = 7114, upload-time = "2023-08-12T20:38:17.776Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c5/55/51844dd50c4fc7a33b653bfaba4c2456f06955289ca770a5dbd5fd267374/cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9", size = 7249, upload-time = "2023-08-12T20:38:16.269Z" }, +] + +[[package]] +name = "chardet" +version = "5.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f3/0d/f7b6ab21ec75897ed80c17d79b15951a719226b9fababf1e40ea74d69079/chardet-5.2.0.tar.gz", hash = "sha256:1b3b6ff479a8c414bc3fa2c0852995695c4a026dcd6d0633b2dd092ca39c1cf7", size = 2069618, upload-time = "2023-08-01T19:23:02.662Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/38/6f/f5fbc992a329ee4e0f288c1fe0e2ad9485ed064cac731ed2fe47dcc38cbf/chardet-5.2.0-py3-none-any.whl", hash = "sha256:e1cf59446890a00105fe7b7912492ea04b6e6f06d4b742b2c788469e34c82970", size = 199385, upload-time = "2023-08-01T19:23:00.661Z" }, +] + +[[package]] +name = "charset-normalizer" +version = "3.4.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/13/69/33ddede1939fdd074bce5434295f38fae7136463422fe4fd3e0e89b98062/charset_normalizer-3.4.4.tar.gz", hash = "sha256:94537985111c35f28720e43603b8e7b43a6ecfb2ce1d3058bbe955b73404e21a", size = 129418, upload-time = "2025-10-14T04:42:32.879Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1f/b8/6d51fc1d52cbd52cd4ccedd5b5b2f0f6a11bbf6765c782298b0f3e808541/charset_normalizer-3.4.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e824f1492727fa856dd6eda4f7cee25f8518a12f3c4a56a74e8095695089cf6d", size = 209709, upload-time = "2025-10-14T04:40:11.385Z" }, + { url = "https://files.pythonhosted.org/packages/5c/af/1f9d7f7faafe2ddfb6f72a2e07a548a629c61ad510fe60f9630309908fef/charset_normalizer-3.4.4-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4bd5d4137d500351a30687c2d3971758aac9a19208fc110ccb9d7188fbe709e8", size = 148814, upload-time = "2025-10-14T04:40:13.135Z" }, + { url = "https://files.pythonhosted.org/packages/79/3d/f2e3ac2bbc056ca0c204298ea4e3d9db9b4afe437812638759db2c976b5f/charset_normalizer-3.4.4-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:027f6de494925c0ab2a55eab46ae5129951638a49a34d87f4c3eda90f696b4ad", size = 144467, upload-time = "2025-10-14T04:40:14.728Z" }, + { url = "https://files.pythonhosted.org/packages/ec/85/1bf997003815e60d57de7bd972c57dc6950446a3e4ccac43bc3070721856/charset_normalizer-3.4.4-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f820802628d2694cb7e56db99213f930856014862f3fd943d290ea8438d07ca8", size = 162280, upload-time = "2025-10-14T04:40:16.14Z" }, + { url = "https://files.pythonhosted.org/packages/3e/8e/6aa1952f56b192f54921c436b87f2aaf7c7a7c3d0d1a765547d64fd83c13/charset_normalizer-3.4.4-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:798d75d81754988d2565bff1b97ba5a44411867c0cf32b77a7e8f8d84796b10d", size = 159454, upload-time = "2025-10-14T04:40:17.567Z" }, + { url = "https://files.pythonhosted.org/packages/36/3b/60cbd1f8e93aa25d1c669c649b7a655b0b5fb4c571858910ea9332678558/charset_normalizer-3.4.4-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d1bb833febdff5c8927f922386db610b49db6e0d4f4ee29601d71e7c2694313", size = 153609, upload-time = "2025-10-14T04:40:19.08Z" }, + { url = "https://files.pythonhosted.org/packages/64/91/6a13396948b8fd3c4b4fd5bc74d045f5637d78c9675585e8e9fbe5636554/charset_normalizer-3.4.4-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9cd98cdc06614a2f768d2b7286d66805f94c48cde050acdbbb7db2600ab3197e", size = 151849, upload-time = "2025-10-14T04:40:20.607Z" }, + { url = "https://files.pythonhosted.org/packages/b7/7a/59482e28b9981d105691e968c544cc0df3b7d6133152fb3dcdc8f135da7a/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:077fbb858e903c73f6c9db43374fd213b0b6a778106bc7032446a8e8b5b38b93", size = 151586, upload-time = "2025-10-14T04:40:21.719Z" }, + { url = "https://files.pythonhosted.org/packages/92/59/f64ef6a1c4bdd2baf892b04cd78792ed8684fbc48d4c2afe467d96b4df57/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:244bfb999c71b35de57821b8ea746b24e863398194a4014e4c76adc2bbdfeff0", size = 145290, upload-time = "2025-10-14T04:40:23.069Z" }, + { url = "https://files.pythonhosted.org/packages/6b/63/3bf9f279ddfa641ffa1962b0db6a57a9c294361cc2f5fcac997049a00e9c/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:64b55f9dce520635f018f907ff1b0df1fdc31f2795a922fb49dd14fbcdf48c84", size = 163663, upload-time = "2025-10-14T04:40:24.17Z" }, + { url = "https://files.pythonhosted.org/packages/ed/09/c9e38fc8fa9e0849b172b581fd9803bdf6e694041127933934184e19f8c3/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:faa3a41b2b66b6e50f84ae4a68c64fcd0c44355741c6374813a800cd6695db9e", size = 151964, upload-time = "2025-10-14T04:40:25.368Z" }, + { url = "https://files.pythonhosted.org/packages/d2/d1/d28b747e512d0da79d8b6a1ac18b7ab2ecfd81b2944c4c710e166d8dd09c/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:6515f3182dbe4ea06ced2d9e8666d97b46ef4c75e326b79bb624110f122551db", size = 161064, upload-time = "2025-10-14T04:40:26.806Z" }, + { url = "https://files.pythonhosted.org/packages/bb/9a/31d62b611d901c3b9e5500c36aab0ff5eb442043fb3a1c254200d3d397d9/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:cc00f04ed596e9dc0da42ed17ac5e596c6ccba999ba6bd92b0e0aef2f170f2d6", size = 155015, upload-time = "2025-10-14T04:40:28.284Z" }, + { url = "https://files.pythonhosted.org/packages/1f/f3/107e008fa2bff0c8b9319584174418e5e5285fef32f79d8ee6a430d0039c/charset_normalizer-3.4.4-cp310-cp310-win32.whl", hash = "sha256:f34be2938726fc13801220747472850852fe6b1ea75869a048d6f896838c896f", size = 99792, upload-time = "2025-10-14T04:40:29.613Z" }, + { url = "https://files.pythonhosted.org/packages/eb/66/e396e8a408843337d7315bab30dbf106c38966f1819f123257f5520f8a96/charset_normalizer-3.4.4-cp310-cp310-win_amd64.whl", hash = "sha256:a61900df84c667873b292c3de315a786dd8dac506704dea57bc957bd31e22c7d", size = 107198, upload-time = "2025-10-14T04:40:30.644Z" }, + { url = "https://files.pythonhosted.org/packages/b5/58/01b4f815bf0312704c267f2ccb6e5d42bcc7752340cd487bc9f8c3710597/charset_normalizer-3.4.4-cp310-cp310-win_arm64.whl", hash = "sha256:cead0978fc57397645f12578bfd2d5ea9138ea0fac82b2f63f7f7c6877986a69", size = 100262, upload-time = "2025-10-14T04:40:32.108Z" }, + { url = "https://files.pythonhosted.org/packages/ed/27/c6491ff4954e58a10f69ad90aca8a1b6fe9c5d3c6f380907af3c37435b59/charset_normalizer-3.4.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6e1fcf0720908f200cd21aa4e6750a48ff6ce4afe7ff5a79a90d5ed8a08296f8", size = 206988, upload-time = "2025-10-14T04:40:33.79Z" }, + { url = "https://files.pythonhosted.org/packages/94/59/2e87300fe67ab820b5428580a53cad894272dbb97f38a7a814a2a1ac1011/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f819d5fe9234f9f82d75bdfa9aef3a3d72c4d24a6e57aeaebba32a704553aa0", size = 147324, upload-time = "2025-10-14T04:40:34.961Z" }, + { url = "https://files.pythonhosted.org/packages/07/fb/0cf61dc84b2b088391830f6274cb57c82e4da8bbc2efeac8c025edb88772/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a59cb51917aa591b1c4e6a43c132f0cdc3c76dbad6155df4e28ee626cc77a0a3", size = 142742, upload-time = "2025-10-14T04:40:36.105Z" }, + { url = "https://files.pythonhosted.org/packages/62/8b/171935adf2312cd745d290ed93cf16cf0dfe320863ab7cbeeae1dcd6535f/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8ef3c867360f88ac904fd3f5e1f902f13307af9052646963ee08ff4f131adafc", size = 160863, upload-time = "2025-10-14T04:40:37.188Z" }, + { url = "https://files.pythonhosted.org/packages/09/73/ad875b192bda14f2173bfc1bc9a55e009808484a4b256748d931b6948442/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d9e45d7faa48ee908174d8fe84854479ef838fc6a705c9315372eacbc2f02897", size = 157837, upload-time = "2025-10-14T04:40:38.435Z" }, + { url = "https://files.pythonhosted.org/packages/6d/fc/de9cce525b2c5b94b47c70a4b4fb19f871b24995c728e957ee68ab1671ea/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:840c25fb618a231545cbab0564a799f101b63b9901f2569faecd6b222ac72381", size = 151550, upload-time = "2025-10-14T04:40:40.053Z" }, + { url = "https://files.pythonhosted.org/packages/55/c2/43edd615fdfba8c6f2dfbd459b25a6b3b551f24ea21981e23fb768503ce1/charset_normalizer-3.4.4-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ca5862d5b3928c4940729dacc329aa9102900382fea192fc5e52eb69d6093815", size = 149162, upload-time = "2025-10-14T04:40:41.163Z" }, + { url = "https://files.pythonhosted.org/packages/03/86/bde4ad8b4d0e9429a4e82c1e8f5c659993a9a863ad62c7df05cf7b678d75/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d9c7f57c3d666a53421049053eaacdd14bbd0a528e2186fcb2e672effd053bb0", size = 150019, upload-time = "2025-10-14T04:40:42.276Z" }, + { url = "https://files.pythonhosted.org/packages/1f/86/a151eb2af293a7e7bac3a739b81072585ce36ccfb4493039f49f1d3cae8c/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:277e970e750505ed74c832b4bf75dac7476262ee2a013f5574dd49075879e161", size = 143310, upload-time = "2025-10-14T04:40:43.439Z" }, + { url = "https://files.pythonhosted.org/packages/b5/fe/43dae6144a7e07b87478fdfc4dbe9efd5defb0e7ec29f5f58a55aeef7bf7/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:31fd66405eaf47bb62e8cd575dc621c56c668f27d46a61d975a249930dd5e2a4", size = 162022, upload-time = "2025-10-14T04:40:44.547Z" }, + { url = "https://files.pythonhosted.org/packages/80/e6/7aab83774f5d2bca81f42ac58d04caf44f0cc2b65fc6db2b3b2e8a05f3b3/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:0d3d8f15c07f86e9ff82319b3d9ef6f4bf907608f53fe9d92b28ea9ae3d1fd89", size = 149383, upload-time = "2025-10-14T04:40:46.018Z" }, + { url = "https://files.pythonhosted.org/packages/4f/e8/b289173b4edae05c0dde07f69f8db476a0b511eac556dfe0d6bda3c43384/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:9f7fcd74d410a36883701fafa2482a6af2ff5ba96b9a620e9e0721e28ead5569", size = 159098, upload-time = "2025-10-14T04:40:47.081Z" }, + { url = "https://files.pythonhosted.org/packages/d8/df/fe699727754cae3f8478493c7f45f777b17c3ef0600e28abfec8619eb49c/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ebf3e58c7ec8a8bed6d66a75d7fb37b55e5015b03ceae72a8e7c74495551e224", size = 152991, upload-time = "2025-10-14T04:40:48.246Z" }, + { url = "https://files.pythonhosted.org/packages/1a/86/584869fe4ddb6ffa3bd9f491b87a01568797fb9bd8933f557dba9771beaf/charset_normalizer-3.4.4-cp311-cp311-win32.whl", hash = "sha256:eecbc200c7fd5ddb9a7f16c7decb07b566c29fa2161a16cf67b8d068bd21690a", size = 99456, upload-time = "2025-10-14T04:40:49.376Z" }, + { url = "https://files.pythonhosted.org/packages/65/f6/62fdd5feb60530f50f7e38b4f6a1d5203f4d16ff4f9f0952962c044e919a/charset_normalizer-3.4.4-cp311-cp311-win_amd64.whl", hash = "sha256:5ae497466c7901d54b639cf42d5b8c1b6a4fead55215500d2f486d34db48d016", size = 106978, upload-time = "2025-10-14T04:40:50.844Z" }, + { url = "https://files.pythonhosted.org/packages/7a/9d/0710916e6c82948b3be62d9d398cb4fcf4e97b56d6a6aeccd66c4b2f2bd5/charset_normalizer-3.4.4-cp311-cp311-win_arm64.whl", hash = "sha256:65e2befcd84bc6f37095f5961e68a6f077bf44946771354a28ad434c2cce0ae1", size = 99969, upload-time = "2025-10-14T04:40:52.272Z" }, + { url = "https://files.pythonhosted.org/packages/f3/85/1637cd4af66fa687396e757dec650f28025f2a2f5a5531a3208dc0ec43f2/charset_normalizer-3.4.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0a98e6759f854bd25a58a73fa88833fba3b7c491169f86ce1180c948ab3fd394", size = 208425, upload-time = "2025-10-14T04:40:53.353Z" }, + { url = "https://files.pythonhosted.org/packages/9d/6a/04130023fef2a0d9c62d0bae2649b69f7b7d8d24ea5536feef50551029df/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b5b290ccc2a263e8d185130284f8501e3e36c5e02750fc6b6bdeb2e9e96f1e25", size = 148162, upload-time = "2025-10-14T04:40:54.558Z" }, + { url = "https://files.pythonhosted.org/packages/78/29/62328d79aa60da22c9e0b9a66539feae06ca0f5a4171ac4f7dc285b83688/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74bb723680f9f7a6234dcf67aea57e708ec1fbdf5699fb91dfd6f511b0a320ef", size = 144558, upload-time = "2025-10-14T04:40:55.677Z" }, + { url = "https://files.pythonhosted.org/packages/86/bb/b32194a4bf15b88403537c2e120b817c61cd4ecffa9b6876e941c3ee38fe/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f1e34719c6ed0b92f418c7c780480b26b5d9c50349e9a9af7d76bf757530350d", size = 161497, upload-time = "2025-10-14T04:40:57.217Z" }, + { url = "https://files.pythonhosted.org/packages/19/89/a54c82b253d5b9b111dc74aca196ba5ccfcca8242d0fb64146d4d3183ff1/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2437418e20515acec67d86e12bf70056a33abdacb5cb1655042f6538d6b085a8", size = 159240, upload-time = "2025-10-14T04:40:58.358Z" }, + { url = "https://files.pythonhosted.org/packages/c0/10/d20b513afe03acc89ec33948320a5544d31f21b05368436d580dec4e234d/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:11d694519d7f29d6cd09f6ac70028dba10f92f6cdd059096db198c283794ac86", size = 153471, upload-time = "2025-10-14T04:40:59.468Z" }, + { url = "https://files.pythonhosted.org/packages/61/fa/fbf177b55bdd727010f9c0a3c49eefa1d10f960e5f09d1d887bf93c2e698/charset_normalizer-3.4.4-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ac1c4a689edcc530fc9d9aa11f5774b9e2f33f9a0c6a57864e90908f5208d30a", size = 150864, upload-time = "2025-10-14T04:41:00.623Z" }, + { url = "https://files.pythonhosted.org/packages/05/12/9fbc6a4d39c0198adeebbde20b619790e9236557ca59fc40e0e3cebe6f40/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:21d142cc6c0ec30d2efee5068ca36c128a30b0f2c53c1c07bd78cb6bc1d3be5f", size = 150647, upload-time = "2025-10-14T04:41:01.754Z" }, + { url = "https://files.pythonhosted.org/packages/ad/1f/6a9a593d52e3e8c5d2b167daf8c6b968808efb57ef4c210acb907c365bc4/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:5dbe56a36425d26d6cfb40ce79c314a2e4dd6211d51d6d2191c00bed34f354cc", size = 145110, upload-time = "2025-10-14T04:41:03.231Z" }, + { url = "https://files.pythonhosted.org/packages/30/42/9a52c609e72471b0fc54386dc63c3781a387bb4fe61c20231a4ebcd58bdd/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:5bfbb1b9acf3334612667b61bd3002196fe2a1eb4dd74d247e0f2a4d50ec9bbf", size = 162839, upload-time = "2025-10-14T04:41:04.715Z" }, + { url = "https://files.pythonhosted.org/packages/c4/5b/c0682bbf9f11597073052628ddd38344a3d673fda35a36773f7d19344b23/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:d055ec1e26e441f6187acf818b73564e6e6282709e9bcb5b63f5b23068356a15", size = 150667, upload-time = "2025-10-14T04:41:05.827Z" }, + { url = "https://files.pythonhosted.org/packages/e4/24/a41afeab6f990cf2daf6cb8c67419b63b48cf518e4f56022230840c9bfb2/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:af2d8c67d8e573d6de5bc30cdb27e9b95e49115cd9baad5ddbd1a6207aaa82a9", size = 160535, upload-time = "2025-10-14T04:41:06.938Z" }, + { url = "https://files.pythonhosted.org/packages/2a/e5/6a4ce77ed243c4a50a1fecca6aaaab419628c818a49434be428fe24c9957/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:780236ac706e66881f3b7f2f32dfe90507a09e67d1d454c762cf642e6e1586e0", size = 154816, upload-time = "2025-10-14T04:41:08.101Z" }, + { url = "https://files.pythonhosted.org/packages/a8/ef/89297262b8092b312d29cdb2517cb1237e51db8ecef2e9af5edbe7b683b1/charset_normalizer-3.4.4-cp312-cp312-win32.whl", hash = "sha256:5833d2c39d8896e4e19b689ffc198f08ea58116bee26dea51e362ecc7cd3ed26", size = 99694, upload-time = "2025-10-14T04:41:09.23Z" }, + { url = "https://files.pythonhosted.org/packages/3d/2d/1e5ed9dd3b3803994c155cd9aacb60c82c331bad84daf75bcb9c91b3295e/charset_normalizer-3.4.4-cp312-cp312-win_amd64.whl", hash = "sha256:a79cfe37875f822425b89a82333404539ae63dbdddf97f84dcbc3d339aae9525", size = 107131, upload-time = "2025-10-14T04:41:10.467Z" }, + { url = "https://files.pythonhosted.org/packages/d0/d9/0ed4c7098a861482a7b6a95603edce4c0d9db2311af23da1fb2b75ec26fc/charset_normalizer-3.4.4-cp312-cp312-win_arm64.whl", hash = "sha256:376bec83a63b8021bb5c8ea75e21c4ccb86e7e45ca4eb81146091b56599b80c3", size = 100390, upload-time = "2025-10-14T04:41:11.915Z" }, + { url = "https://files.pythonhosted.org/packages/97/45/4b3a1239bbacd321068ea6e7ac28875b03ab8bc0aa0966452db17cd36714/charset_normalizer-3.4.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:e1f185f86a6f3403aa2420e815904c67b2f9ebc443f045edd0de921108345794", size = 208091, upload-time = "2025-10-14T04:41:13.346Z" }, + { url = "https://files.pythonhosted.org/packages/7d/62/73a6d7450829655a35bb88a88fca7d736f9882a27eacdca2c6d505b57e2e/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b39f987ae8ccdf0d2642338faf2abb1862340facc796048b604ef14919e55ed", size = 147936, upload-time = "2025-10-14T04:41:14.461Z" }, + { url = "https://files.pythonhosted.org/packages/89/c5/adb8c8b3d6625bef6d88b251bbb0d95f8205831b987631ab0c8bb5d937c2/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3162d5d8ce1bb98dd51af660f2121c55d0fa541b46dff7bb9b9f86ea1d87de72", size = 144180, upload-time = "2025-10-14T04:41:15.588Z" }, + { url = "https://files.pythonhosted.org/packages/91/ed/9706e4070682d1cc219050b6048bfd293ccf67b3d4f5a4f39207453d4b99/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:81d5eb2a312700f4ecaa977a8235b634ce853200e828fbadf3a9c50bab278328", size = 161346, upload-time = "2025-10-14T04:41:16.738Z" }, + { url = "https://files.pythonhosted.org/packages/d5/0d/031f0d95e4972901a2f6f09ef055751805ff541511dc1252ba3ca1f80cf5/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5bd2293095d766545ec1a8f612559f6b40abc0eb18bb2f5d1171872d34036ede", size = 158874, upload-time = "2025-10-14T04:41:17.923Z" }, + { url = "https://files.pythonhosted.org/packages/f5/83/6ab5883f57c9c801ce5e5677242328aa45592be8a00644310a008d04f922/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a8a8b89589086a25749f471e6a900d3f662d1d3b6e2e59dcecf787b1cc3a1894", size = 153076, upload-time = "2025-10-14T04:41:19.106Z" }, + { url = "https://files.pythonhosted.org/packages/75/1e/5ff781ddf5260e387d6419959ee89ef13878229732732ee73cdae01800f2/charset_normalizer-3.4.4-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc7637e2f80d8530ee4a78e878bce464f70087ce73cf7c1caf142416923b98f1", size = 150601, upload-time = "2025-10-14T04:41:20.245Z" }, + { url = "https://files.pythonhosted.org/packages/d7/57/71be810965493d3510a6ca79b90c19e48696fb1ff964da319334b12677f0/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f8bf04158c6b607d747e93949aa60618b61312fe647a6369f88ce2ff16043490", size = 150376, upload-time = "2025-10-14T04:41:21.398Z" }, + { url = "https://files.pythonhosted.org/packages/e5/d5/c3d057a78c181d007014feb7e9f2e65905a6c4ef182c0ddf0de2924edd65/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:554af85e960429cf30784dd47447d5125aaa3b99a6f0683589dbd27e2f45da44", size = 144825, upload-time = "2025-10-14T04:41:22.583Z" }, + { url = "https://files.pythonhosted.org/packages/e6/8c/d0406294828d4976f275ffbe66f00266c4b3136b7506941d87c00cab5272/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:74018750915ee7ad843a774364e13a3db91682f26142baddf775342c3f5b1133", size = 162583, upload-time = "2025-10-14T04:41:23.754Z" }, + { url = "https://files.pythonhosted.org/packages/d7/24/e2aa1f18c8f15c4c0e932d9287b8609dd30ad56dbe41d926bd846e22fb8d/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:c0463276121fdee9c49b98908b3a89c39be45d86d1dbaa22957e38f6321d4ce3", size = 150366, upload-time = "2025-10-14T04:41:25.27Z" }, + { url = "https://files.pythonhosted.org/packages/e4/5b/1e6160c7739aad1e2df054300cc618b06bf784a7a164b0f238360721ab86/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:362d61fd13843997c1c446760ef36f240cf81d3ebf74ac62652aebaf7838561e", size = 160300, upload-time = "2025-10-14T04:41:26.725Z" }, + { url = "https://files.pythonhosted.org/packages/7a/10/f882167cd207fbdd743e55534d5d9620e095089d176d55cb22d5322f2afd/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9a26f18905b8dd5d685d6d07b0cdf98a79f3c7a918906af7cc143ea2e164c8bc", size = 154465, upload-time = "2025-10-14T04:41:28.322Z" }, + { url = "https://files.pythonhosted.org/packages/89/66/c7a9e1b7429be72123441bfdbaf2bc13faab3f90b933f664db506dea5915/charset_normalizer-3.4.4-cp313-cp313-win32.whl", hash = "sha256:9b35f4c90079ff2e2edc5b26c0c77925e5d2d255c42c74fdb70fb49b172726ac", size = 99404, upload-time = "2025-10-14T04:41:29.95Z" }, + { url = "https://files.pythonhosted.org/packages/c4/26/b9924fa27db384bdcd97ab83b4f0a8058d96ad9626ead570674d5e737d90/charset_normalizer-3.4.4-cp313-cp313-win_amd64.whl", hash = "sha256:b435cba5f4f750aa6c0a0d92c541fb79f69a387c91e61f1795227e4ed9cece14", size = 107092, upload-time = "2025-10-14T04:41:31.188Z" }, + { url = "https://files.pythonhosted.org/packages/af/8f/3ed4bfa0c0c72a7ca17f0380cd9e4dd842b09f664e780c13cff1dcf2ef1b/charset_normalizer-3.4.4-cp313-cp313-win_arm64.whl", hash = "sha256:542d2cee80be6f80247095cc36c418f7bddd14f4a6de45af91dfad36d817bba2", size = 100408, upload-time = "2025-10-14T04:41:32.624Z" }, + { url = "https://files.pythonhosted.org/packages/2a/35/7051599bd493e62411d6ede36fd5af83a38f37c4767b92884df7301db25d/charset_normalizer-3.4.4-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:da3326d9e65ef63a817ecbcc0df6e94463713b754fe293eaa03da99befb9a5bd", size = 207746, upload-time = "2025-10-14T04:41:33.773Z" }, + { url = "https://files.pythonhosted.org/packages/10/9a/97c8d48ef10d6cd4fcead2415523221624bf58bcf68a802721a6bc807c8f/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8af65f14dc14a79b924524b1e7fffe304517b2bff5a58bf64f30b98bbc5079eb", size = 147889, upload-time = "2025-10-14T04:41:34.897Z" }, + { url = "https://files.pythonhosted.org/packages/10/bf/979224a919a1b606c82bd2c5fa49b5c6d5727aa47b4312bb27b1734f53cd/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74664978bb272435107de04e36db5a9735e78232b85b77d45cfb38f758efd33e", size = 143641, upload-time = "2025-10-14T04:41:36.116Z" }, + { url = "https://files.pythonhosted.org/packages/ba/33/0ad65587441fc730dc7bd90e9716b30b4702dc7b617e6ba4997dc8651495/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:752944c7ffbfdd10c074dc58ec2d5a8a4cd9493b314d367c14d24c17684ddd14", size = 160779, upload-time = "2025-10-14T04:41:37.229Z" }, + { url = "https://files.pythonhosted.org/packages/67/ed/331d6b249259ee71ddea93f6f2f0a56cfebd46938bde6fcc6f7b9a3d0e09/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d1f13550535ad8cff21b8d757a3257963e951d96e20ec82ab44bc64aeb62a191", size = 159035, upload-time = "2025-10-14T04:41:38.368Z" }, + { url = "https://files.pythonhosted.org/packages/67/ff/f6b948ca32e4f2a4576aa129d8bed61f2e0543bf9f5f2b7fc3758ed005c9/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ecaae4149d99b1c9e7b88bb03e3221956f68fd6d50be2ef061b2381b61d20838", size = 152542, upload-time = "2025-10-14T04:41:39.862Z" }, + { url = "https://files.pythonhosted.org/packages/16/85/276033dcbcc369eb176594de22728541a925b2632f9716428c851b149e83/charset_normalizer-3.4.4-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cb6254dc36b47a990e59e1068afacdcd02958bdcce30bb50cc1700a8b9d624a6", size = 149524, upload-time = "2025-10-14T04:41:41.319Z" }, + { url = "https://files.pythonhosted.org/packages/9e/f2/6a2a1f722b6aba37050e626530a46a68f74e63683947a8acff92569f979a/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c8ae8a0f02f57a6e61203a31428fa1d677cbe50c93622b4149d5c0f319c1d19e", size = 150395, upload-time = "2025-10-14T04:41:42.539Z" }, + { url = "https://files.pythonhosted.org/packages/60/bb/2186cb2f2bbaea6338cad15ce23a67f9b0672929744381e28b0592676824/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:47cc91b2f4dd2833fddaedd2893006b0106129d4b94fdb6af1f4ce5a9965577c", size = 143680, upload-time = "2025-10-14T04:41:43.661Z" }, + { url = "https://files.pythonhosted.org/packages/7d/a5/bf6f13b772fbb2a90360eb620d52ed8f796f3c5caee8398c3b2eb7b1c60d/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:82004af6c302b5d3ab2cfc4cc5f29db16123b1a8417f2e25f9066f91d4411090", size = 162045, upload-time = "2025-10-14T04:41:44.821Z" }, + { url = "https://files.pythonhosted.org/packages/df/c5/d1be898bf0dc3ef9030c3825e5d3b83f2c528d207d246cbabe245966808d/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:2b7d8f6c26245217bd2ad053761201e9f9680f8ce52f0fcd8d0755aeae5b2152", size = 149687, upload-time = "2025-10-14T04:41:46.442Z" }, + { url = "https://files.pythonhosted.org/packages/a5/42/90c1f7b9341eef50c8a1cb3f098ac43b0508413f33affd762855f67a410e/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:799a7a5e4fb2d5898c60b640fd4981d6a25f1c11790935a44ce38c54e985f828", size = 160014, upload-time = "2025-10-14T04:41:47.631Z" }, + { url = "https://files.pythonhosted.org/packages/76/be/4d3ee471e8145d12795ab655ece37baed0929462a86e72372fd25859047c/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:99ae2cffebb06e6c22bdc25801d7b30f503cc87dbd283479e7b606f70aff57ec", size = 154044, upload-time = "2025-10-14T04:41:48.81Z" }, + { url = "https://files.pythonhosted.org/packages/b0/6f/8f7af07237c34a1defe7defc565a9bc1807762f672c0fde711a4b22bf9c0/charset_normalizer-3.4.4-cp314-cp314-win32.whl", hash = "sha256:f9d332f8c2a2fcbffe1378594431458ddbef721c1769d78e2cbc06280d8155f9", size = 99940, upload-time = "2025-10-14T04:41:49.946Z" }, + { url = "https://files.pythonhosted.org/packages/4b/51/8ade005e5ca5b0d80fb4aff72a3775b325bdc3d27408c8113811a7cbe640/charset_normalizer-3.4.4-cp314-cp314-win_amd64.whl", hash = "sha256:8a6562c3700cce886c5be75ade4a5db4214fda19fede41d9792d100288d8f94c", size = 107104, upload-time = "2025-10-14T04:41:51.051Z" }, + { url = "https://files.pythonhosted.org/packages/da/5f/6b8f83a55bb8278772c5ae54a577f3099025f9ade59d0136ac24a0df4bde/charset_normalizer-3.4.4-cp314-cp314-win_arm64.whl", hash = "sha256:de00632ca48df9daf77a2c65a484531649261ec9f25489917f09e455cb09ddb2", size = 100743, upload-time = "2025-10-14T04:41:52.122Z" }, + { url = "https://files.pythonhosted.org/packages/46/7c/0c4760bccf082737ca7ab84a4c2034fcc06b1f21cf3032ea98bd6feb1725/charset_normalizer-3.4.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:a9768c477b9d7bd54bc0c86dbaebdec6f03306675526c9927c0e8a04e8f94af9", size = 209609, upload-time = "2025-10-14T04:42:10.922Z" }, + { url = "https://files.pythonhosted.org/packages/bb/a4/69719daef2f3d7f1819de60c9a6be981b8eeead7542d5ec4440f3c80e111/charset_normalizer-3.4.4-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1bee1e43c28aa63cb16e5c14e582580546b08e535299b8b6158a7c9c768a1f3d", size = 149029, upload-time = "2025-10-14T04:42:12.38Z" }, + { url = "https://files.pythonhosted.org/packages/e6/21/8d4e1d6c1e6070d3672908b8e4533a71b5b53e71d16828cc24d0efec564c/charset_normalizer-3.4.4-cp39-cp39-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:fd44c878ea55ba351104cb93cc85e74916eb8fa440ca7903e57575e97394f608", size = 144580, upload-time = "2025-10-14T04:42:13.549Z" }, + { url = "https://files.pythonhosted.org/packages/a7/0a/a616d001b3f25647a9068e0b9199f697ce507ec898cacb06a0d5a1617c99/charset_normalizer-3.4.4-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:0f04b14ffe5fdc8c4933862d8306109a2c51e0704acfa35d51598eb45a1e89fc", size = 162340, upload-time = "2025-10-14T04:42:14.892Z" }, + { url = "https://files.pythonhosted.org/packages/85/93/060b52deb249a5450460e0585c88a904a83aec474ab8e7aba787f45e79f2/charset_normalizer-3.4.4-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:cd09d08005f958f370f539f186d10aec3377d55b9eeb0d796025d4886119d76e", size = 159619, upload-time = "2025-10-14T04:42:16.676Z" }, + { url = "https://files.pythonhosted.org/packages/dd/21/0274deb1cc0632cd587a9a0ec6b4674d9108e461cb4cd40d457adaeb0564/charset_normalizer-3.4.4-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4fe7859a4e3e8457458e2ff592f15ccb02f3da787fcd31e0183879c3ad4692a1", size = 153980, upload-time = "2025-10-14T04:42:17.917Z" }, + { url = "https://files.pythonhosted.org/packages/28/2b/e3d7d982858dccc11b31906976323d790dded2017a0572f093ff982d692f/charset_normalizer-3.4.4-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:fa09f53c465e532f4d3db095e0c55b615f010ad81803d383195b6b5ca6cbf5f3", size = 152174, upload-time = "2025-10-14T04:42:19.018Z" }, + { url = "https://files.pythonhosted.org/packages/6e/ff/4a269f8e35f1e58b2df52c131a1fa019acb7ef3f8697b7d464b07e9b492d/charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:7fa17817dc5625de8a027cb8b26d9fefa3ea28c8253929b8d6649e705d2835b6", size = 151666, upload-time = "2025-10-14T04:42:20.171Z" }, + { url = "https://files.pythonhosted.org/packages/da/c9/ec39870f0b330d58486001dd8e532c6b9a905f5765f58a6f8204926b4a93/charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:5947809c8a2417be3267efc979c47d76a079758166f7d43ef5ae8e9f92751f88", size = 145550, upload-time = "2025-10-14T04:42:21.324Z" }, + { url = "https://files.pythonhosted.org/packages/75/8f/d186ab99e40e0ed9f82f033d6e49001701c81244d01905dd4a6924191a30/charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:4902828217069c3c5c71094537a8e623f5d097858ac6ca8252f7b4d10b7560f1", size = 163721, upload-time = "2025-10-14T04:42:22.46Z" }, + { url = "https://files.pythonhosted.org/packages/96/b1/6047663b9744df26a7e479ac1e77af7134b1fcf9026243bb48ee2d18810f/charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:7c308f7e26e4363d79df40ca5b2be1c6ba9f02bdbccfed5abddb7859a6ce72cf", size = 152127, upload-time = "2025-10-14T04:42:23.712Z" }, + { url = "https://files.pythonhosted.org/packages/59/78/e5a6eac9179f24f704d1be67d08704c3c6ab9f00963963524be27c18ed87/charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:2c9d3c380143a1fedbff95a312aa798578371eb29da42106a29019368a475318", size = 161175, upload-time = "2025-10-14T04:42:24.87Z" }, + { url = "https://files.pythonhosted.org/packages/e5/43/0e626e42d54dd2f8dd6fc5e1c5ff00f05fbca17cb699bedead2cae69c62f/charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:cb01158d8b88ee68f15949894ccc6712278243d95f344770fa7593fa2d94410c", size = 155375, upload-time = "2025-10-14T04:42:27.246Z" }, + { url = "https://files.pythonhosted.org/packages/e9/91/d9615bf2e06f35e4997616ff31248c3657ed649c5ab9d35ea12fce54e380/charset_normalizer-3.4.4-cp39-cp39-win32.whl", hash = "sha256:2677acec1a2f8ef614c6888b5b4ae4060cc184174a938ed4e8ef690e15d3e505", size = 99692, upload-time = "2025-10-14T04:42:28.425Z" }, + { url = "https://files.pythonhosted.org/packages/d1/a9/6c040053909d9d1ef4fcab45fddec083aedc9052c10078339b47c8573ea8/charset_normalizer-3.4.4-cp39-cp39-win_amd64.whl", hash = "sha256:f8e160feb2aed042cd657a72acc0b481212ed28b1b9a95c0cee1621b524e1966", size = 107192, upload-time = "2025-10-14T04:42:29.482Z" }, + { url = "https://files.pythonhosted.org/packages/f0/c6/4fa536b2c0cd3edfb7ccf8469fa0f363ea67b7213a842b90909ca33dd851/charset_normalizer-3.4.4-cp39-cp39-win_arm64.whl", hash = "sha256:b5d84d37db046c5ca74ee7bb47dd6cbc13f80665fdde3e8040bdd3fb015ecb50", size = 100220, upload-time = "2025-10-14T04:42:30.632Z" }, + { url = "https://files.pythonhosted.org/packages/0a/4c/925909008ed5a988ccbb72dcc897407e5d6d3bd72410d69e051fc0c14647/charset_normalizer-3.4.4-py3-none-any.whl", hash = "sha256:7a32c560861a02ff789ad905a2fe94e3f840803362c84fecf1851cb4cf3dc37f", size = 53402, upload-time = "2025-10-14T04:42:31.76Z" }, +] + +[[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 = "coverage" +version = "7.10.7" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.10'", +] +sdist = { url = "https://files.pythonhosted.org/packages/51/26/d22c300112504f5f9a9fd2297ce33c35f3d353e4aeb987c8419453b2a7c2/coverage-7.10.7.tar.gz", hash = "sha256:f4ab143ab113be368a3e9b795f9cd7906c5ef407d6173fe9675a902e1fffc239", size = 827704, upload-time = "2025-09-21T20:03:56.815Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e5/6c/3a3f7a46888e69d18abe3ccc6fe4cb16cccb1e6a2f99698931dafca489e6/coverage-7.10.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:fc04cc7a3db33664e0c2d10eb8990ff6b3536f6842c9590ae8da4c614b9ed05a", size = 217987, upload-time = "2025-09-21T20:00:57.218Z" }, + { url = "https://files.pythonhosted.org/packages/03/94/952d30f180b1a916c11a56f5c22d3535e943aa22430e9e3322447e520e1c/coverage-7.10.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e201e015644e207139f7e2351980feb7040e6f4b2c2978892f3e3789d1c125e5", size = 218388, upload-time = "2025-09-21T20:01:00.081Z" }, + { url = "https://files.pythonhosted.org/packages/50/2b/9e0cf8ded1e114bcd8b2fd42792b57f1c4e9e4ea1824cde2af93a67305be/coverage-7.10.7-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:240af60539987ced2c399809bd34f7c78e8abe0736af91c3d7d0e795df633d17", size = 245148, upload-time = "2025-09-21T20:01:01.768Z" }, + { url = "https://files.pythonhosted.org/packages/19/20/d0384ac06a6f908783d9b6aa6135e41b093971499ec488e47279f5b846e6/coverage-7.10.7-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:8421e088bc051361b01c4b3a50fd39a4b9133079a2229978d9d30511fd05231b", size = 246958, upload-time = "2025-09-21T20:01:03.355Z" }, + { url = "https://files.pythonhosted.org/packages/60/83/5c283cff3d41285f8eab897651585db908a909c572bdc014bcfaf8a8b6ae/coverage-7.10.7-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6be8ed3039ae7f7ac5ce058c308484787c86e8437e72b30bf5e88b8ea10f3c87", size = 248819, upload-time = "2025-09-21T20:01:04.968Z" }, + { url = "https://files.pythonhosted.org/packages/60/22/02eb98fdc5ff79f423e990d877693e5310ae1eab6cb20ae0b0b9ac45b23b/coverage-7.10.7-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e28299d9f2e889e6d51b1f043f58d5f997c373cc12e6403b90df95b8b047c13e", size = 245754, upload-time = "2025-09-21T20:01:06.321Z" }, + { url = "https://files.pythonhosted.org/packages/b4/bc/25c83bcf3ad141b32cd7dc45485ef3c01a776ca3aa8ef0a93e77e8b5bc43/coverage-7.10.7-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:c4e16bd7761c5e454f4efd36f345286d6f7c5fa111623c355691e2755cae3b9e", size = 246860, upload-time = "2025-09-21T20:01:07.605Z" }, + { url = "https://files.pythonhosted.org/packages/3c/b7/95574702888b58c0928a6e982038c596f9c34d52c5e5107f1eef729399b5/coverage-7.10.7-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:b1c81d0e5e160651879755c9c675b974276f135558cf4ba79fee7b8413a515df", size = 244877, upload-time = "2025-09-21T20:01:08.829Z" }, + { url = "https://files.pythonhosted.org/packages/47/b6/40095c185f235e085df0e0b158f6bd68cc6e1d80ba6c7721dc81d97ec318/coverage-7.10.7-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:606cc265adc9aaedcc84f1f064f0e8736bc45814f15a357e30fca7ecc01504e0", size = 245108, upload-time = "2025-09-21T20:01:10.527Z" }, + { url = "https://files.pythonhosted.org/packages/c8/50/4aea0556da7a4b93ec9168420d170b55e2eb50ae21b25062513d020c6861/coverage-7.10.7-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:10b24412692df990dbc34f8fb1b6b13d236ace9dfdd68df5b28c2e39cafbba13", size = 245752, upload-time = "2025-09-21T20:01:11.857Z" }, + { url = "https://files.pythonhosted.org/packages/6a/28/ea1a84a60828177ae3b100cb6723838523369a44ec5742313ed7db3da160/coverage-7.10.7-cp310-cp310-win32.whl", hash = "sha256:b51dcd060f18c19290d9b8a9dd1e0181538df2ce0717f562fff6cf74d9fc0b5b", size = 220497, upload-time = "2025-09-21T20:01:13.459Z" }, + { url = "https://files.pythonhosted.org/packages/fc/1a/a81d46bbeb3c3fd97b9602ebaa411e076219a150489bcc2c025f151bd52d/coverage-7.10.7-cp310-cp310-win_amd64.whl", hash = "sha256:3a622ac801b17198020f09af3eaf45666b344a0d69fc2a6ffe2ea83aeef1d807", size = 221392, upload-time = "2025-09-21T20:01:14.722Z" }, + { url = "https://files.pythonhosted.org/packages/d2/5d/c1a17867b0456f2e9ce2d8d4708a4c3a089947d0bec9c66cdf60c9e7739f/coverage-7.10.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a609f9c93113be646f44c2a0256d6ea375ad047005d7f57a5c15f614dc1b2f59", size = 218102, upload-time = "2025-09-21T20:01:16.089Z" }, + { url = "https://files.pythonhosted.org/packages/54/f0/514dcf4b4e3698b9a9077f084429681bf3aad2b4a72578f89d7f643eb506/coverage-7.10.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:65646bb0359386e07639c367a22cf9b5bf6304e8630b565d0626e2bdf329227a", size = 218505, upload-time = "2025-09-21T20:01:17.788Z" }, + { url = "https://files.pythonhosted.org/packages/20/f6/9626b81d17e2a4b25c63ac1b425ff307ecdeef03d67c9a147673ae40dc36/coverage-7.10.7-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:5f33166f0dfcce728191f520bd2692914ec70fac2713f6bf3ce59c3deacb4699", size = 248898, upload-time = "2025-09-21T20:01:19.488Z" }, + { url = "https://files.pythonhosted.org/packages/b0/ef/bd8e719c2f7417ba03239052e099b76ea1130ac0cbb183ee1fcaa58aaff3/coverage-7.10.7-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:35f5e3f9e455bb17831876048355dca0f758b6df22f49258cb5a91da23ef437d", size = 250831, upload-time = "2025-09-21T20:01:20.817Z" }, + { url = "https://files.pythonhosted.org/packages/a5/b6/bf054de41ec948b151ae2b79a55c107f5760979538f5fb80c195f2517718/coverage-7.10.7-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4da86b6d62a496e908ac2898243920c7992499c1712ff7c2b6d837cc69d9467e", size = 252937, upload-time = "2025-09-21T20:01:22.171Z" }, + { url = "https://files.pythonhosted.org/packages/0f/e5/3860756aa6f9318227443c6ce4ed7bf9e70bb7f1447a0353f45ac5c7974b/coverage-7.10.7-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6b8b09c1fad947c84bbbc95eca841350fad9cbfa5a2d7ca88ac9f8d836c92e23", size = 249021, upload-time = "2025-09-21T20:01:23.907Z" }, + { url = "https://files.pythonhosted.org/packages/26/0f/bd08bd042854f7fd07b45808927ebcce99a7ed0f2f412d11629883517ac2/coverage-7.10.7-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:4376538f36b533b46f8971d3a3e63464f2c7905c9800db97361c43a2b14792ab", size = 250626, upload-time = "2025-09-21T20:01:25.721Z" }, + { url = "https://files.pythonhosted.org/packages/8e/a7/4777b14de4abcc2e80c6b1d430f5d51eb18ed1d75fca56cbce5f2db9b36e/coverage-7.10.7-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:121da30abb574f6ce6ae09840dae322bef734480ceafe410117627aa54f76d82", size = 248682, upload-time = "2025-09-21T20:01:27.105Z" }, + { url = "https://files.pythonhosted.org/packages/34/72/17d082b00b53cd45679bad682fac058b87f011fd8b9fe31d77f5f8d3a4e4/coverage-7.10.7-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:88127d40df529336a9836870436fc2751c339fbaed3a836d42c93f3e4bd1d0a2", size = 248402, upload-time = "2025-09-21T20:01:28.629Z" }, + { url = "https://files.pythonhosted.org/packages/81/7a/92367572eb5bdd6a84bfa278cc7e97db192f9f45b28c94a9ca1a921c3577/coverage-7.10.7-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ba58bbcd1b72f136080c0bccc2400d66cc6115f3f906c499013d065ac33a4b61", size = 249320, upload-time = "2025-09-21T20:01:30.004Z" }, + { url = "https://files.pythonhosted.org/packages/2f/88/a23cc185f6a805dfc4fdf14a94016835eeb85e22ac3a0e66d5e89acd6462/coverage-7.10.7-cp311-cp311-win32.whl", hash = "sha256:972b9e3a4094b053a4e46832b4bc829fc8a8d347160eb39d03f1690316a99c14", size = 220536, upload-time = "2025-09-21T20:01:32.184Z" }, + { url = "https://files.pythonhosted.org/packages/fe/ef/0b510a399dfca17cec7bc2f05ad8bd78cf55f15c8bc9a73ab20c5c913c2e/coverage-7.10.7-cp311-cp311-win_amd64.whl", hash = "sha256:a7b55a944a7f43892e28ad4bc0561dfd5f0d73e605d1aa5c3c976b52aea121d2", size = 221425, upload-time = "2025-09-21T20:01:33.557Z" }, + { url = "https://files.pythonhosted.org/packages/51/7f/023657f301a276e4ba1850f82749bc136f5a7e8768060c2e5d9744a22951/coverage-7.10.7-cp311-cp311-win_arm64.whl", hash = "sha256:736f227fb490f03c6488f9b6d45855f8e0fd749c007f9303ad30efab0e73c05a", size = 220103, upload-time = "2025-09-21T20:01:34.929Z" }, + { url = "https://files.pythonhosted.org/packages/13/e4/eb12450f71b542a53972d19117ea5a5cea1cab3ac9e31b0b5d498df1bd5a/coverage-7.10.7-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7bb3b9ddb87ef7725056572368040c32775036472d5a033679d1fa6c8dc08417", size = 218290, upload-time = "2025-09-21T20:01:36.455Z" }, + { url = "https://files.pythonhosted.org/packages/37/66/593f9be12fc19fb36711f19a5371af79a718537204d16ea1d36f16bd78d2/coverage-7.10.7-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:18afb24843cbc175687225cab1138c95d262337f5473512010e46831aa0c2973", size = 218515, upload-time = "2025-09-21T20:01:37.982Z" }, + { url = "https://files.pythonhosted.org/packages/66/80/4c49f7ae09cafdacc73fbc30949ffe77359635c168f4e9ff33c9ebb07838/coverage-7.10.7-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:399a0b6347bcd3822be369392932884b8216d0944049ae22925631a9b3d4ba4c", size = 250020, upload-time = "2025-09-21T20:01:39.617Z" }, + { url = "https://files.pythonhosted.org/packages/a6/90/a64aaacab3b37a17aaedd83e8000142561a29eb262cede42d94a67f7556b/coverage-7.10.7-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:314f2c326ded3f4b09be11bc282eb2fc861184bc95748ae67b360ac962770be7", size = 252769, upload-time = "2025-09-21T20:01:41.341Z" }, + { url = "https://files.pythonhosted.org/packages/98/2e/2dda59afd6103b342e096f246ebc5f87a3363b5412609946c120f4e7750d/coverage-7.10.7-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c41e71c9cfb854789dee6fc51e46743a6d138b1803fab6cb860af43265b42ea6", size = 253901, upload-time = "2025-09-21T20:01:43.042Z" }, + { url = "https://files.pythonhosted.org/packages/53/dc/8d8119c9051d50f3119bb4a75f29f1e4a6ab9415cd1fa8bf22fcc3fb3b5f/coverage-7.10.7-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc01f57ca26269c2c706e838f6422e2a8788e41b3e3c65e2f41148212e57cd59", size = 250413, upload-time = "2025-09-21T20:01:44.469Z" }, + { url = "https://files.pythonhosted.org/packages/98/b3/edaff9c5d79ee4d4b6d3fe046f2b1d799850425695b789d491a64225d493/coverage-7.10.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a6442c59a8ac8b85812ce33bc4d05bde3fb22321fa8294e2a5b487c3505f611b", size = 251820, upload-time = "2025-09-21T20:01:45.915Z" }, + { url = "https://files.pythonhosted.org/packages/11/25/9a0728564bb05863f7e513e5a594fe5ffef091b325437f5430e8cfb0d530/coverage-7.10.7-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:78a384e49f46b80fb4c901d52d92abe098e78768ed829c673fbb53c498bef73a", size = 249941, upload-time = "2025-09-21T20:01:47.296Z" }, + { url = "https://files.pythonhosted.org/packages/e0/fd/ca2650443bfbef5b0e74373aac4df67b08180d2f184b482c41499668e258/coverage-7.10.7-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:5e1e9802121405ede4b0133aa4340ad8186a1d2526de5b7c3eca519db7bb89fb", size = 249519, upload-time = "2025-09-21T20:01:48.73Z" }, + { url = "https://files.pythonhosted.org/packages/24/79/f692f125fb4299b6f963b0745124998ebb8e73ecdfce4ceceb06a8c6bec5/coverage-7.10.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:d41213ea25a86f69efd1575073d34ea11aabe075604ddf3d148ecfec9e1e96a1", size = 251375, upload-time = "2025-09-21T20:01:50.529Z" }, + { url = "https://files.pythonhosted.org/packages/5e/75/61b9bbd6c7d24d896bfeec57acba78e0f8deac68e6baf2d4804f7aae1f88/coverage-7.10.7-cp312-cp312-win32.whl", hash = "sha256:77eb4c747061a6af8d0f7bdb31f1e108d172762ef579166ec84542f711d90256", size = 220699, upload-time = "2025-09-21T20:01:51.941Z" }, + { url = "https://files.pythonhosted.org/packages/ca/f3/3bf7905288b45b075918d372498f1cf845b5b579b723c8fd17168018d5f5/coverage-7.10.7-cp312-cp312-win_amd64.whl", hash = "sha256:f51328ffe987aecf6d09f3cd9d979face89a617eacdaea43e7b3080777f647ba", size = 221512, upload-time = "2025-09-21T20:01:53.481Z" }, + { url = "https://files.pythonhosted.org/packages/5c/44/3e32dbe933979d05cf2dac5e697c8599cfe038aaf51223ab901e208d5a62/coverage-7.10.7-cp312-cp312-win_arm64.whl", hash = "sha256:bda5e34f8a75721c96085903c6f2197dc398c20ffd98df33f866a9c8fd95f4bf", size = 220147, upload-time = "2025-09-21T20:01:55.2Z" }, + { url = "https://files.pythonhosted.org/packages/9a/94/b765c1abcb613d103b64fcf10395f54d69b0ef8be6a0dd9c524384892cc7/coverage-7.10.7-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:981a651f543f2854abd3b5fcb3263aac581b18209be49863ba575de6edf4c14d", size = 218320, upload-time = "2025-09-21T20:01:56.629Z" }, + { url = "https://files.pythonhosted.org/packages/72/4f/732fff31c119bb73b35236dd333030f32c4bfe909f445b423e6c7594f9a2/coverage-7.10.7-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:73ab1601f84dc804f7812dc297e93cd99381162da39c47040a827d4e8dafe63b", size = 218575, upload-time = "2025-09-21T20:01:58.203Z" }, + { url = "https://files.pythonhosted.org/packages/87/02/ae7e0af4b674be47566707777db1aa375474f02a1d64b9323e5813a6cdd5/coverage-7.10.7-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:a8b6f03672aa6734e700bbcd65ff050fd19cddfec4b031cc8cf1c6967de5a68e", size = 249568, upload-time = "2025-09-21T20:01:59.748Z" }, + { url = "https://files.pythonhosted.org/packages/a2/77/8c6d22bf61921a59bce5471c2f1f7ac30cd4ac50aadde72b8c48d5727902/coverage-7.10.7-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:10b6ba00ab1132a0ce4428ff68cf50a25efd6840a42cdf4239c9b99aad83be8b", size = 252174, upload-time = "2025-09-21T20:02:01.192Z" }, + { url = "https://files.pythonhosted.org/packages/b1/20/b6ea4f69bbb52dac0aebd62157ba6a9dddbfe664f5af8122dac296c3ee15/coverage-7.10.7-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c79124f70465a150e89340de5963f936ee97097d2ef76c869708c4248c63ca49", size = 253447, upload-time = "2025-09-21T20:02:02.701Z" }, + { url = "https://files.pythonhosted.org/packages/f9/28/4831523ba483a7f90f7b259d2018fef02cb4d5b90bc7c1505d6e5a84883c/coverage-7.10.7-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:69212fbccdbd5b0e39eac4067e20a4a5256609e209547d86f740d68ad4f04911", size = 249779, upload-time = "2025-09-21T20:02:04.185Z" }, + { url = "https://files.pythonhosted.org/packages/a7/9f/4331142bc98c10ca6436d2d620c3e165f31e6c58d43479985afce6f3191c/coverage-7.10.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7ea7c6c9d0d286d04ed3541747e6597cbe4971f22648b68248f7ddcd329207f0", size = 251604, upload-time = "2025-09-21T20:02:06.034Z" }, + { url = "https://files.pythonhosted.org/packages/ce/60/bda83b96602036b77ecf34e6393a3836365481b69f7ed7079ab85048202b/coverage-7.10.7-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b9be91986841a75042b3e3243d0b3cb0b2434252b977baaf0cd56e960fe1e46f", size = 249497, upload-time = "2025-09-21T20:02:07.619Z" }, + { url = "https://files.pythonhosted.org/packages/5f/af/152633ff35b2af63977edd835d8e6430f0caef27d171edf2fc76c270ef31/coverage-7.10.7-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:b281d5eca50189325cfe1f365fafade89b14b4a78d9b40b05ddd1fc7d2a10a9c", size = 249350, upload-time = "2025-09-21T20:02:10.34Z" }, + { url = "https://files.pythonhosted.org/packages/9d/71/d92105d122bd21cebba877228990e1646d862e34a98bb3374d3fece5a794/coverage-7.10.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:99e4aa63097ab1118e75a848a28e40d68b08a5e19ce587891ab7fd04475e780f", size = 251111, upload-time = "2025-09-21T20:02:12.122Z" }, + { url = "https://files.pythonhosted.org/packages/a2/9e/9fdb08f4bf476c912f0c3ca292e019aab6712c93c9344a1653986c3fd305/coverage-7.10.7-cp313-cp313-win32.whl", hash = "sha256:dc7c389dce432500273eaf48f410b37886be9208b2dd5710aaf7c57fd442c698", size = 220746, upload-time = "2025-09-21T20:02:13.919Z" }, + { url = "https://files.pythonhosted.org/packages/b1/b1/a75fd25df44eab52d1931e89980d1ada46824c7a3210be0d3c88a44aaa99/coverage-7.10.7-cp313-cp313-win_amd64.whl", hash = "sha256:cac0fdca17b036af3881a9d2729a850b76553f3f716ccb0360ad4dbc06b3b843", size = 221541, upload-time = "2025-09-21T20:02:15.57Z" }, + { url = "https://files.pythonhosted.org/packages/14/3a/d720d7c989562a6e9a14b2c9f5f2876bdb38e9367126d118495b89c99c37/coverage-7.10.7-cp313-cp313-win_arm64.whl", hash = "sha256:4b6f236edf6e2f9ae8fcd1332da4e791c1b6ba0dc16a2dc94590ceccb482e546", size = 220170, upload-time = "2025-09-21T20:02:17.395Z" }, + { url = "https://files.pythonhosted.org/packages/bb/22/e04514bf2a735d8b0add31d2b4ab636fc02370730787c576bb995390d2d5/coverage-7.10.7-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:a0ec07fd264d0745ee396b666d47cef20875f4ff2375d7c4f58235886cc1ef0c", size = 219029, upload-time = "2025-09-21T20:02:18.936Z" }, + { url = "https://files.pythonhosted.org/packages/11/0b/91128e099035ece15da3445d9015e4b4153a6059403452d324cbb0a575fa/coverage-7.10.7-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:dd5e856ebb7bfb7672b0086846db5afb4567a7b9714b8a0ebafd211ec7ce6a15", size = 219259, upload-time = "2025-09-21T20:02:20.44Z" }, + { url = "https://files.pythonhosted.org/packages/8b/51/66420081e72801536a091a0c8f8c1f88a5c4bf7b9b1bdc6222c7afe6dc9b/coverage-7.10.7-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:f57b2a3c8353d3e04acf75b3fed57ba41f5c0646bbf1d10c7c282291c97936b4", size = 260592, upload-time = "2025-09-21T20:02:22.313Z" }, + { url = "https://files.pythonhosted.org/packages/5d/22/9b8d458c2881b22df3db5bb3e7369e63d527d986decb6c11a591ba2364f7/coverage-7.10.7-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:1ef2319dd15a0b009667301a3f84452a4dc6fddfd06b0c5c53ea472d3989fbf0", size = 262768, upload-time = "2025-09-21T20:02:24.287Z" }, + { url = "https://files.pythonhosted.org/packages/f7/08/16bee2c433e60913c610ea200b276e8eeef084b0d200bdcff69920bd5828/coverage-7.10.7-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:83082a57783239717ceb0ad584de3c69cf581b2a95ed6bf81ea66034f00401c0", size = 264995, upload-time = "2025-09-21T20:02:26.133Z" }, + { url = "https://files.pythonhosted.org/packages/20/9d/e53eb9771d154859b084b90201e5221bca7674ba449a17c101a5031d4054/coverage-7.10.7-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:50aa94fb1fb9a397eaa19c0d5ec15a5edd03a47bf1a3a6111a16b36e190cff65", size = 259546, upload-time = "2025-09-21T20:02:27.716Z" }, + { url = "https://files.pythonhosted.org/packages/ad/b0/69bc7050f8d4e56a89fb550a1577d5d0d1db2278106f6f626464067b3817/coverage-7.10.7-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:2120043f147bebb41c85b97ac45dd173595ff14f2a584f2963891cbcc3091541", size = 262544, upload-time = "2025-09-21T20:02:29.216Z" }, + { url = "https://files.pythonhosted.org/packages/ef/4b/2514b060dbd1bc0aaf23b852c14bb5818f244c664cb16517feff6bb3a5ab/coverage-7.10.7-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:2fafd773231dd0378fdba66d339f84904a8e57a262f583530f4f156ab83863e6", size = 260308, upload-time = "2025-09-21T20:02:31.226Z" }, + { url = "https://files.pythonhosted.org/packages/54/78/7ba2175007c246d75e496f64c06e94122bdb914790a1285d627a918bd271/coverage-7.10.7-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:0b944ee8459f515f28b851728ad224fa2d068f1513ef6b7ff1efafeb2185f999", size = 258920, upload-time = "2025-09-21T20:02:32.823Z" }, + { url = "https://files.pythonhosted.org/packages/c0/b3/fac9f7abbc841409b9a410309d73bfa6cfb2e51c3fada738cb607ce174f8/coverage-7.10.7-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4b583b97ab2e3efe1b3e75248a9b333bd3f8b0b1b8e5b45578e05e5850dfb2c2", size = 261434, upload-time = "2025-09-21T20:02:34.86Z" }, + { url = "https://files.pythonhosted.org/packages/ee/51/a03bec00d37faaa891b3ff7387192cef20f01604e5283a5fabc95346befa/coverage-7.10.7-cp313-cp313t-win32.whl", hash = "sha256:2a78cd46550081a7909b3329e2266204d584866e8d97b898cd7fb5ac8d888b1a", size = 221403, upload-time = "2025-09-21T20:02:37.034Z" }, + { url = "https://files.pythonhosted.org/packages/53/22/3cf25d614e64bf6d8e59c7c669b20d6d940bb337bdee5900b9ca41c820bb/coverage-7.10.7-cp313-cp313t-win_amd64.whl", hash = "sha256:33a5e6396ab684cb43dc7befa386258acb2d7fae7f67330ebb85ba4ea27938eb", size = 222469, upload-time = "2025-09-21T20:02:39.011Z" }, + { url = "https://files.pythonhosted.org/packages/49/a1/00164f6d30d8a01c3c9c48418a7a5be394de5349b421b9ee019f380df2a0/coverage-7.10.7-cp313-cp313t-win_arm64.whl", hash = "sha256:86b0e7308289ddde73d863b7683f596d8d21c7d8664ce1dee061d0bcf3fbb4bb", size = 220731, upload-time = "2025-09-21T20:02:40.939Z" }, + { url = "https://files.pythonhosted.org/packages/23/9c/5844ab4ca6a4dd97a1850e030a15ec7d292b5c5cb93082979225126e35dd/coverage-7.10.7-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:b06f260b16ead11643a5a9f955bd4b5fd76c1a4c6796aeade8520095b75de520", size = 218302, upload-time = "2025-09-21T20:02:42.527Z" }, + { url = "https://files.pythonhosted.org/packages/f0/89/673f6514b0961d1f0e20ddc242e9342f6da21eaba3489901b565c0689f34/coverage-7.10.7-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:212f8f2e0612778f09c55dd4872cb1f64a1f2b074393d139278ce902064d5b32", size = 218578, upload-time = "2025-09-21T20:02:44.468Z" }, + { url = "https://files.pythonhosted.org/packages/05/e8/261cae479e85232828fb17ad536765c88dd818c8470aca690b0ac6feeaa3/coverage-7.10.7-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3445258bcded7d4aa630ab8296dea4d3f15a255588dd535f980c193ab6b95f3f", size = 249629, upload-time = "2025-09-21T20:02:46.503Z" }, + { url = "https://files.pythonhosted.org/packages/82/62/14ed6546d0207e6eda876434e3e8475a3e9adbe32110ce896c9e0c06bb9a/coverage-7.10.7-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:bb45474711ba385c46a0bfe696c695a929ae69ac636cda8f532be9e8c93d720a", size = 252162, upload-time = "2025-09-21T20:02:48.689Z" }, + { url = "https://files.pythonhosted.org/packages/ff/49/07f00db9ac6478e4358165a08fb41b469a1b053212e8a00cb02f0d27a05f/coverage-7.10.7-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:813922f35bd800dca9994c5971883cbc0d291128a5de6b167c7aa697fcf59360", size = 253517, upload-time = "2025-09-21T20:02:50.31Z" }, + { url = "https://files.pythonhosted.org/packages/a2/59/c5201c62dbf165dfbc91460f6dbbaa85a8b82cfa6131ac45d6c1bfb52deb/coverage-7.10.7-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:93c1b03552081b2a4423091d6fb3787265b8f86af404cff98d1b5342713bdd69", size = 249632, upload-time = "2025-09-21T20:02:51.971Z" }, + { url = "https://files.pythonhosted.org/packages/07/ae/5920097195291a51fb00b3a70b9bbd2edbfe3c84876a1762bd1ef1565ebc/coverage-7.10.7-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:cc87dd1b6eaf0b848eebb1c86469b9f72a1891cb42ac7adcfbce75eadb13dd14", size = 251520, upload-time = "2025-09-21T20:02:53.858Z" }, + { url = "https://files.pythonhosted.org/packages/b9/3c/a815dde77a2981f5743a60b63df31cb322c944843e57dbd579326625a413/coverage-7.10.7-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:39508ffda4f343c35f3236fe8d1a6634a51f4581226a1262769d7f970e73bffe", size = 249455, upload-time = "2025-09-21T20:02:55.807Z" }, + { url = "https://files.pythonhosted.org/packages/aa/99/f5cdd8421ea656abefb6c0ce92556709db2265c41e8f9fc6c8ae0f7824c9/coverage-7.10.7-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:925a1edf3d810537c5a3abe78ec5530160c5f9a26b1f4270b40e62cc79304a1e", size = 249287, upload-time = "2025-09-21T20:02:57.784Z" }, + { url = "https://files.pythonhosted.org/packages/c3/7a/e9a2da6a1fc5d007dd51fca083a663ab930a8c4d149c087732a5dbaa0029/coverage-7.10.7-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2c8b9a0636f94c43cd3576811e05b89aa9bc2d0a85137affc544ae5cb0e4bfbd", size = 250946, upload-time = "2025-09-21T20:02:59.431Z" }, + { url = "https://files.pythonhosted.org/packages/ef/5b/0b5799aa30380a949005a353715095d6d1da81927d6dbed5def2200a4e25/coverage-7.10.7-cp314-cp314-win32.whl", hash = "sha256:b7b8288eb7cdd268b0304632da8cb0bb93fadcfec2fe5712f7b9cc8f4d487be2", size = 221009, upload-time = "2025-09-21T20:03:01.324Z" }, + { url = "https://files.pythonhosted.org/packages/da/b0/e802fbb6eb746de006490abc9bb554b708918b6774b722bb3a0e6aa1b7de/coverage-7.10.7-cp314-cp314-win_amd64.whl", hash = "sha256:1ca6db7c8807fb9e755d0379ccc39017ce0a84dcd26d14b5a03b78563776f681", size = 221804, upload-time = "2025-09-21T20:03:03.4Z" }, + { url = "https://files.pythonhosted.org/packages/9e/e8/71d0c8e374e31f39e3389bb0bd19e527d46f00ea8571ec7ec8fd261d8b44/coverage-7.10.7-cp314-cp314-win_arm64.whl", hash = "sha256:097c1591f5af4496226d5783d036bf6fd6cd0cbc132e071b33861de756efb880", size = 220384, upload-time = "2025-09-21T20:03:05.111Z" }, + { url = "https://files.pythonhosted.org/packages/62/09/9a5608d319fa3eba7a2019addeacb8c746fb50872b57a724c9f79f146969/coverage-7.10.7-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:a62c6ef0d50e6de320c270ff91d9dd0a05e7250cac2a800b7784bae474506e63", size = 219047, upload-time = "2025-09-21T20:03:06.795Z" }, + { url = "https://files.pythonhosted.org/packages/f5/6f/f58d46f33db9f2e3647b2d0764704548c184e6f5e014bef528b7f979ef84/coverage-7.10.7-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:9fa6e4dd51fe15d8738708a973470f67a855ca50002294852e9571cdbd9433f2", size = 219266, upload-time = "2025-09-21T20:03:08.495Z" }, + { url = "https://files.pythonhosted.org/packages/74/5c/183ffc817ba68e0b443b8c934c8795553eb0c14573813415bd59941ee165/coverage-7.10.7-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:8fb190658865565c549b6b4706856d6a7b09302c797eb2cf8e7fe9dabb043f0d", size = 260767, upload-time = "2025-09-21T20:03:10.172Z" }, + { url = "https://files.pythonhosted.org/packages/0f/48/71a8abe9c1ad7e97548835e3cc1adbf361e743e9d60310c5f75c9e7bf847/coverage-7.10.7-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:affef7c76a9ef259187ef31599a9260330e0335a3011732c4b9effa01e1cd6e0", size = 262931, upload-time = "2025-09-21T20:03:11.861Z" }, + { url = "https://files.pythonhosted.org/packages/84/fd/193a8fb132acfc0a901f72020e54be5e48021e1575bb327d8ee1097a28fd/coverage-7.10.7-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6e16e07d85ca0cf8bafe5f5d23a0b850064e8e945d5677492b06bbe6f09cc699", size = 265186, upload-time = "2025-09-21T20:03:13.539Z" }, + { url = "https://files.pythonhosted.org/packages/b1/8f/74ecc30607dd95ad50e3034221113ccb1c6d4e8085cc761134782995daae/coverage-7.10.7-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:03ffc58aacdf65d2a82bbeb1ffe4d01ead4017a21bfd0454983b88ca73af94b9", size = 259470, upload-time = "2025-09-21T20:03:15.584Z" }, + { url = "https://files.pythonhosted.org/packages/0f/55/79ff53a769f20d71b07023ea115c9167c0bb56f281320520cf64c5298a96/coverage-7.10.7-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:1b4fd784344d4e52647fd7857b2af5b3fbe6c239b0b5fa63e94eb67320770e0f", size = 262626, upload-time = "2025-09-21T20:03:17.673Z" }, + { url = "https://files.pythonhosted.org/packages/88/e2/dac66c140009b61ac3fc13af673a574b00c16efdf04f9b5c740703e953c0/coverage-7.10.7-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:0ebbaddb2c19b71912c6f2518e791aa8b9f054985a0769bdb3a53ebbc765c6a1", size = 260386, upload-time = "2025-09-21T20:03:19.36Z" }, + { url = "https://files.pythonhosted.org/packages/a2/f1/f48f645e3f33bb9ca8a496bc4a9671b52f2f353146233ebd7c1df6160440/coverage-7.10.7-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:a2d9a3b260cc1d1dbdb1c582e63ddcf5363426a1a68faa0f5da28d8ee3c722a0", size = 258852, upload-time = "2025-09-21T20:03:21.007Z" }, + { url = "https://files.pythonhosted.org/packages/bb/3b/8442618972c51a7affeead957995cfa8323c0c9bcf8fa5a027421f720ff4/coverage-7.10.7-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:a3cc8638b2480865eaa3926d192e64ce6c51e3d29c849e09d5b4ad95efae5399", size = 261534, upload-time = "2025-09-21T20:03:23.12Z" }, + { url = "https://files.pythonhosted.org/packages/b2/dc/101f3fa3a45146db0cb03f5b4376e24c0aac818309da23e2de0c75295a91/coverage-7.10.7-cp314-cp314t-win32.whl", hash = "sha256:67f8c5cbcd3deb7a60b3345dffc89a961a484ed0af1f6f73de91705cc6e31235", size = 221784, upload-time = "2025-09-21T20:03:24.769Z" }, + { url = "https://files.pythonhosted.org/packages/4c/a1/74c51803fc70a8a40d7346660379e144be772bab4ac7bb6e6b905152345c/coverage-7.10.7-cp314-cp314t-win_amd64.whl", hash = "sha256:e1ed71194ef6dea7ed2d5cb5f7243d4bcd334bfb63e59878519be558078f848d", size = 222905, upload-time = "2025-09-21T20:03:26.93Z" }, + { url = "https://files.pythonhosted.org/packages/12/65/f116a6d2127df30bcafbceef0302d8a64ba87488bf6f73a6d8eebf060873/coverage-7.10.7-cp314-cp314t-win_arm64.whl", hash = "sha256:7fe650342addd8524ca63d77b2362b02345e5f1a093266787d210c70a50b471a", size = 220922, upload-time = "2025-09-21T20:03:28.672Z" }, + { url = "https://files.pythonhosted.org/packages/a3/ad/d1c25053764b4c42eb294aae92ab617d2e4f803397f9c7c8295caa77a260/coverage-7.10.7-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:fff7b9c3f19957020cac546c70025331113d2e61537f6e2441bc7657913de7d3", size = 217978, upload-time = "2025-09-21T20:03:30.362Z" }, + { url = "https://files.pythonhosted.org/packages/52/2f/b9f9daa39b80ece0b9548bbb723381e29bc664822d9a12c2135f8922c22b/coverage-7.10.7-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:bc91b314cef27742da486d6839b677b3f2793dfe52b51bbbb7cf736d5c29281c", size = 218370, upload-time = "2025-09-21T20:03:32.147Z" }, + { url = "https://files.pythonhosted.org/packages/dd/6e/30d006c3b469e58449650642383dddf1c8fb63d44fdf92994bfd46570695/coverage-7.10.7-cp39-cp39-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:567f5c155eda8df1d3d439d40a45a6a5f029b429b06648235f1e7e51b522b396", size = 244802, upload-time = "2025-09-21T20:03:33.919Z" }, + { url = "https://files.pythonhosted.org/packages/b0/49/8a070782ce7e6b94ff6a0b6d7c65ba6bc3091d92a92cef4cd4eb0767965c/coverage-7.10.7-cp39-cp39-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2af88deffcc8a4d5974cf2d502251bc3b2db8461f0b66d80a449c33757aa9f40", size = 246625, upload-time = "2025-09-21T20:03:36.09Z" }, + { url = "https://files.pythonhosted.org/packages/6a/92/1c1c5a9e8677ce56d42b97bdaca337b2d4d9ebe703d8c174ede52dbabd5f/coverage-7.10.7-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c7315339eae3b24c2d2fa1ed7d7a38654cba34a13ef19fbcb9425da46d3dc594", size = 248399, upload-time = "2025-09-21T20:03:38.342Z" }, + { url = "https://files.pythonhosted.org/packages/c0/54/b140edee7257e815de7426d5d9846b58505dffc29795fff2dfb7f8a1c5a0/coverage-7.10.7-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:912e6ebc7a6e4adfdbb1aec371ad04c68854cd3bf3608b3514e7ff9062931d8a", size = 245142, upload-time = "2025-09-21T20:03:40.591Z" }, + { url = "https://files.pythonhosted.org/packages/e4/9e/6d6b8295940b118e8b7083b29226c71f6154f7ff41e9ca431f03de2eac0d/coverage-7.10.7-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:f49a05acd3dfe1ce9715b657e28d138578bc40126760efb962322c56e9ca344b", size = 246284, upload-time = "2025-09-21T20:03:42.355Z" }, + { url = "https://files.pythonhosted.org/packages/db/e5/5e957ca747d43dbe4d9714358375c7546cb3cb533007b6813fc20fce37ad/coverage-7.10.7-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:cce2109b6219f22ece99db7644b9622f54a4e915dad65660ec435e89a3ea7cc3", size = 244353, upload-time = "2025-09-21T20:03:44.218Z" }, + { url = "https://files.pythonhosted.org/packages/9a/45/540fc5cc92536a1b783b7ef99450bd55a4b3af234aae35a18a339973ce30/coverage-7.10.7-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:f3c887f96407cea3916294046fc7dab611c2552beadbed4ea901cbc6a40cc7a0", size = 244430, upload-time = "2025-09-21T20:03:46.065Z" }, + { url = "https://files.pythonhosted.org/packages/75/0b/8287b2e5b38c8fe15d7e3398849bb58d382aedc0864ea0fa1820e8630491/coverage-7.10.7-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:635adb9a4507c9fd2ed65f39693fa31c9a3ee3a8e6dc64df033e8fdf52a7003f", size = 245311, upload-time = "2025-09-21T20:03:48.19Z" }, + { url = "https://files.pythonhosted.org/packages/0c/1d/29724999984740f0c86d03e6420b942439bf5bd7f54d4382cae386a9d1e9/coverage-7.10.7-cp39-cp39-win32.whl", hash = "sha256:5a02d5a850e2979b0a014c412573953995174743a3f7fa4ea5a6e9a3c5617431", size = 220500, upload-time = "2025-09-21T20:03:50.024Z" }, + { url = "https://files.pythonhosted.org/packages/43/11/4b1e6b129943f905ca54c339f343877b55b365ae2558806c1be4f7476ed5/coverage-7.10.7-cp39-cp39-win_amd64.whl", hash = "sha256:c134869d5ffe34547d14e174c866fd8fe2254918cc0a95e99052903bc1543e07", size = 221408, upload-time = "2025-09-21T20:03:51.803Z" }, + { url = "https://files.pythonhosted.org/packages/ec/16/114df1c291c22cac3b0c127a73e0af5c12ed7bbb6558d310429a0ae24023/coverage-7.10.7-py3-none-any.whl", hash = "sha256:f7941f6f2fe6dd6807a1208737b8a0cbcf1cc6d7b07d24998ad2d63590868260", size = 209952, upload-time = "2025-09-21T20:03:53.918Z" }, +] + +[package.optional-dependencies] +toml = [ + { name = "tomli", marker = "python_full_version < '3.10'" }, +] + +[[package]] +name = "coverage" +version = "7.11.3" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.11'", + "python_full_version == '3.10.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/d2/59/9698d57a3b11704c7b89b21d69e9d23ecf80d538cabb536c8b63f4a12322/coverage-7.11.3.tar.gz", hash = "sha256:0f59387f5e6edbbffec2281affb71cdc85e0776c1745150a3ab9b6c1d016106b", size = 815210, upload-time = "2025-11-10T00:13:17.18Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fd/68/b53157115ef76d50d1d916d6240e5cd5b3c14dba8ba1b984632b8221fc2e/coverage-7.11.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0c986537abca9b064510f3fd104ba33e98d3036608c7f2f5537f869bc10e1ee5", size = 216377, upload-time = "2025-11-10T00:10:27.317Z" }, + { url = "https://files.pythonhosted.org/packages/14/c1/d2f9d8e37123fe6e7ab8afcaab8195f13bc84a8b2f449a533fd4812ac724/coverage-7.11.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:28c5251b3ab1d23e66f1130ca0c419747edfbcb4690de19467cd616861507af7", size = 216892, upload-time = "2025-11-10T00:10:30.624Z" }, + { url = "https://files.pythonhosted.org/packages/83/73/18f05d8010149b650ed97ee5c9f7e4ae68c05c7d913391523281e41c2495/coverage-7.11.3-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:4f2bb4ee8dd40f9b2a80bb4adb2aecece9480ba1fa60d9382e8c8e0bd558e2eb", size = 243650, upload-time = "2025-11-10T00:10:32.392Z" }, + { url = "https://files.pythonhosted.org/packages/63/3c/c0cbb296c0ecc6dcbd70f4b473fcd7fe4517bbef8b09f4326d78f38adb87/coverage-7.11.3-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:e5f4bfac975a2138215a38bda599ef00162e4143541cf7dd186da10a7f8e69f1", size = 245478, upload-time = "2025-11-10T00:10:34.157Z" }, + { url = "https://files.pythonhosted.org/packages/b9/9a/dad288cf9faa142a14e75e39dc646d968b93d74e15c83e9b13fd628f2cb3/coverage-7.11.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8f4cbfff5cf01fa07464439a8510affc9df281535f41a1f5312fbd2b59b4ab5c", size = 247337, upload-time = "2025-11-10T00:10:35.655Z" }, + { url = "https://files.pythonhosted.org/packages/e3/ba/f6148ebf5547b3502013175e41bf3107a4e34b7dd19f9793a6ce0e1cd61f/coverage-7.11.3-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:31663572f20bf3406d7ac00d6981c7bbbcec302539d26b5ac596ca499664de31", size = 244328, upload-time = "2025-11-10T00:10:37.459Z" }, + { url = "https://files.pythonhosted.org/packages/e6/4d/b93784d0b593c5df89a0d48cbbd2d0963e0ca089eaf877405849792e46d3/coverage-7.11.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:9799bd6a910961cb666196b8583ed0ee125fa225c6fdee2cbf00232b861f29d2", size = 245381, upload-time = "2025-11-10T00:10:39.229Z" }, + { url = "https://files.pythonhosted.org/packages/3a/8d/6735bfd4f0f736d457642ee056a570d704c9d57fdcd5c91ea5d6b15c944e/coverage-7.11.3-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:097acc18bedf2c6e3144eaf09b5f6034926c3c9bb9e10574ffd0942717232507", size = 243390, upload-time = "2025-11-10T00:10:40.984Z" }, + { url = "https://files.pythonhosted.org/packages/db/3d/7ba68ed52d1873d450aefd8d2f5a353e67b421915cb6c174e4222c7b918c/coverage-7.11.3-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:6f033dec603eea88204589175782290a038b436105a8f3637a81c4359df27832", size = 243654, upload-time = "2025-11-10T00:10:42.496Z" }, + { url = "https://files.pythonhosted.org/packages/14/26/be2720c4c7bf73c6591ae4ab503a7b5a31c7a60ced6dba855cfcb4a5af7e/coverage-7.11.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:dd9ca2d44ed8018c90efb72f237a2a140325a4c3339971364d758e78b175f58e", size = 244272, upload-time = "2025-11-10T00:10:44.39Z" }, + { url = "https://files.pythonhosted.org/packages/90/20/086f5697780df146dbc0df4ae9b6db2b23ddf5aa550f977b2825137728e9/coverage-7.11.3-cp310-cp310-win32.whl", hash = "sha256:900580bc99c145e2561ea91a2d207e639171870d8a18756eb57db944a017d4bb", size = 218969, upload-time = "2025-11-10T00:10:45.863Z" }, + { url = "https://files.pythonhosted.org/packages/98/5c/cc6faba945ede5088156da7770e30d06c38b8591785ac99bcfb2074f9ef6/coverage-7.11.3-cp310-cp310-win_amd64.whl", hash = "sha256:c8be5bfcdc7832011b2652db29ed7672ce9d353dd19bce5272ca33dbcf60aaa8", size = 219903, upload-time = "2025-11-10T00:10:47.676Z" }, + { url = "https://files.pythonhosted.org/packages/92/92/43a961c0f57b666d01c92bcd960c7f93677de5e4ee7ca722564ad6dee0fa/coverage-7.11.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:200bb89fd2a8a07780eafcdff6463104dec459f3c838d980455cfa84f5e5e6e1", size = 216504, upload-time = "2025-11-10T00:10:49.524Z" }, + { url = "https://files.pythonhosted.org/packages/5d/5c/dbfc73329726aef26dbf7fefef81b8a2afd1789343a579ea6d99bf15d26e/coverage-7.11.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8d264402fc179776d43e557e1ca4a7d953020d3ee95f7ec19cc2c9d769277f06", size = 217006, upload-time = "2025-11-10T00:10:51.32Z" }, + { url = "https://files.pythonhosted.org/packages/a5/e0/878c84fb6661964bc435beb1e28c050650aa30e4c1cdc12341e298700bda/coverage-7.11.3-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:385977d94fc155f8731c895accdfcc3dd0d9dd9ef90d102969df95d3c637ab80", size = 247415, upload-time = "2025-11-10T00:10:52.805Z" }, + { url = "https://files.pythonhosted.org/packages/56/9e/0677e78b1e6a13527f39c4b39c767b351e256b333050539861c63f98bd61/coverage-7.11.3-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:0542ddf6107adbd2592f29da9f59f5d9cff7947b5bb4f734805085c327dcffaa", size = 249332, upload-time = "2025-11-10T00:10:54.35Z" }, + { url = "https://files.pythonhosted.org/packages/54/90/25fc343e4ce35514262451456de0953bcae5b37dda248aed50ee51234cee/coverage-7.11.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d60bf4d7f886989ddf80e121a7f4d140d9eac91f1d2385ce8eb6bda93d563297", size = 251443, upload-time = "2025-11-10T00:10:55.832Z" }, + { url = "https://files.pythonhosted.org/packages/13/56/bc02bbc890fd8b155a64285c93e2ab38647486701ac9c980d457cdae857a/coverage-7.11.3-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c0a3b6e32457535df0d41d2d895da46434706dd85dbaf53fbc0d3bd7d914b362", size = 247554, upload-time = "2025-11-10T00:10:57.829Z" }, + { url = "https://files.pythonhosted.org/packages/0f/ab/0318888d091d799a82d788c1e8d8bd280f1d5c41662bbb6e11187efe33e8/coverage-7.11.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:876a3ee7fd2613eb79602e4cdb39deb6b28c186e76124c3f29e580099ec21a87", size = 249139, upload-time = "2025-11-10T00:10:59.465Z" }, + { url = "https://files.pythonhosted.org/packages/79/d8/3ee50929c4cd36fcfcc0f45d753337001001116c8a5b8dd18d27ea645737/coverage-7.11.3-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:a730cd0824e8083989f304e97b3f884189efb48e2151e07f57e9e138ab104200", size = 247209, upload-time = "2025-11-10T00:11:01.432Z" }, + { url = "https://files.pythonhosted.org/packages/94/7c/3cf06e327401c293e60c962b4b8a2ceb7167c1a428a02be3adbd1d7c7e4c/coverage-7.11.3-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:b5cd111d3ab7390be0c07ad839235d5ad54d2ca497b5f5db86896098a77180a4", size = 246936, upload-time = "2025-11-10T00:11:02.964Z" }, + { url = "https://files.pythonhosted.org/packages/99/0b/ffc03dc8f4083817900fd367110015ef4dd227b37284104a5eb5edc9c106/coverage-7.11.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:074e6a5cd38e06671580b4d872c1a67955d4e69639e4b04e87fc03b494c1f060", size = 247835, upload-time = "2025-11-10T00:11:04.405Z" }, + { url = "https://files.pythonhosted.org/packages/17/4d/dbe54609ee066553d0bcdcdf108b177c78dab836292bee43f96d6a5674d1/coverage-7.11.3-cp311-cp311-win32.whl", hash = "sha256:86d27d2dd7c7c5a44710565933c7dc9cd70e65ef97142e260d16d555667deef7", size = 218994, upload-time = "2025-11-10T00:11:05.966Z" }, + { url = "https://files.pythonhosted.org/packages/94/11/8e7155df53f99553ad8114054806c01a2c0b08f303ea7e38b9831652d83d/coverage-7.11.3-cp311-cp311-win_amd64.whl", hash = "sha256:ca90ef33a152205fb6f2f0c1f3e55c50df4ef049bb0940ebba666edd4cdebc55", size = 219926, upload-time = "2025-11-10T00:11:07.936Z" }, + { url = "https://files.pythonhosted.org/packages/1f/93/bea91b6a9e35d89c89a1cd5824bc72e45151a9c2a9ca0b50d9e9a85e3ae3/coverage-7.11.3-cp311-cp311-win_arm64.whl", hash = "sha256:56f909a40d68947ef726ce6a34eb38f0ed241ffbe55c5007c64e616663bcbafc", size = 218599, upload-time = "2025-11-10T00:11:09.578Z" }, + { url = "https://files.pythonhosted.org/packages/c2/39/af056ec7a27c487e25c7f6b6e51d2ee9821dba1863173ddf4dc2eebef4f7/coverage-7.11.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:5b771b59ac0dfb7f139f70c85b42717ef400a6790abb6475ebac1ecee8de782f", size = 216676, upload-time = "2025-11-10T00:11:11.566Z" }, + { url = "https://files.pythonhosted.org/packages/3c/f8/21126d34b174d037b5d01bea39077725cbb9a0da94a95c5f96929c695433/coverage-7.11.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:603c4414125fc9ae9000f17912dcfd3d3eb677d4e360b85206539240c96ea76e", size = 217034, upload-time = "2025-11-10T00:11:13.12Z" }, + { url = "https://files.pythonhosted.org/packages/d5/3f/0fd35f35658cdd11f7686303214bd5908225838f374db47f9e457c8d6df8/coverage-7.11.3-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:77ffb3b7704eb7b9b3298a01fe4509cef70117a52d50bcba29cffc5f53dd326a", size = 248531, upload-time = "2025-11-10T00:11:15.023Z" }, + { url = "https://files.pythonhosted.org/packages/8f/59/0bfc5900fc15ce4fd186e092451de776bef244565c840c9c026fd50857e1/coverage-7.11.3-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:4d4ca49f5ba432b0755ebb0fc3a56be944a19a16bb33802264bbc7311622c0d1", size = 251290, upload-time = "2025-11-10T00:11:16.628Z" }, + { url = "https://files.pythonhosted.org/packages/71/88/d5c184001fa2ac82edf1b8f2cd91894d2230d7c309e937c54c796176e35b/coverage-7.11.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:05fd3fb6edff0c98874d752013588836f458261e5eba587afe4c547bba544afd", size = 252375, upload-time = "2025-11-10T00:11:18.249Z" }, + { url = "https://files.pythonhosted.org/packages/5c/29/f60af9f823bf62c7a00ce1ac88441b9a9a467e499493e5cc65028c8b8dd2/coverage-7.11.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0e920567f8c3a3ce68ae5a42cf7c2dc4bb6cc389f18bff2235dd8c03fa405de5", size = 248946, upload-time = "2025-11-10T00:11:20.202Z" }, + { url = "https://files.pythonhosted.org/packages/67/16/4662790f3b1e03fce5280cad93fd18711c35980beb3c6f28dca41b5230c6/coverage-7.11.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4bec8c7160688bd5a34e65c82984b25409563134d63285d8943d0599efbc448e", size = 250310, upload-time = "2025-11-10T00:11:21.689Z" }, + { url = "https://files.pythonhosted.org/packages/8f/75/dd6c2e28308a83e5fc1ee602f8204bd3aa5af685c104cb54499230cf56db/coverage-7.11.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:adb9b7b42c802bd8cb3927de8c1c26368ce50c8fdaa83a9d8551384d77537044", size = 248461, upload-time = "2025-11-10T00:11:23.384Z" }, + { url = "https://files.pythonhosted.org/packages/16/fe/b71af12be9f59dc9eb060688fa19a95bf3223f56c5af1e9861dfa2275d2c/coverage-7.11.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:c8f563b245b4ddb591e99f28e3cd140b85f114b38b7f95b2e42542f0603eb7d7", size = 248039, upload-time = "2025-11-10T00:11:25.07Z" }, + { url = "https://files.pythonhosted.org/packages/11/b8/023b2003a2cd96bdf607afe03d9b96c763cab6d76e024abe4473707c4eb8/coverage-7.11.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e2a96fdc7643c9517a317553aca13b5cae9bad9a5f32f4654ce247ae4d321405", size = 249903, upload-time = "2025-11-10T00:11:26.992Z" }, + { url = "https://files.pythonhosted.org/packages/d6/ee/5f1076311aa67b1fa4687a724cc044346380e90ce7d94fec09fd384aa5fd/coverage-7.11.3-cp312-cp312-win32.whl", hash = "sha256:e8feeb5e8705835f0622af0fe7ff8d5cb388948454647086494d6c41ec142c2e", size = 219201, upload-time = "2025-11-10T00:11:28.619Z" }, + { url = "https://files.pythonhosted.org/packages/4f/24/d21688f48fe9fcc778956680fd5aaf69f4e23b245b7c7a4755cbd421d25b/coverage-7.11.3-cp312-cp312-win_amd64.whl", hash = "sha256:abb903ffe46bd319d99979cdba350ae7016759bb69f47882242f7b93f3356055", size = 220012, upload-time = "2025-11-10T00:11:30.234Z" }, + { url = "https://files.pythonhosted.org/packages/4f/9e/d5eb508065f291456378aa9b16698b8417d87cb084c2b597f3beb00a8084/coverage-7.11.3-cp312-cp312-win_arm64.whl", hash = "sha256:1451464fd855d9bd000c19b71bb7dafea9ab815741fb0bd9e813d9b671462d6f", size = 218652, upload-time = "2025-11-10T00:11:32.165Z" }, + { url = "https://files.pythonhosted.org/packages/6d/f6/d8572c058211c7d976f24dab71999a565501fb5b3cdcb59cf782f19c4acb/coverage-7.11.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:84b892e968164b7a0498ddc5746cdf4e985700b902128421bb5cec1080a6ee36", size = 216694, upload-time = "2025-11-10T00:11:34.296Z" }, + { url = "https://files.pythonhosted.org/packages/4a/f6/b6f9764d90c0ce1bce8d995649fa307fff21f4727b8d950fa2843b7b0de5/coverage-7.11.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f761dbcf45e9416ec4698e1a7649248005f0064ce3523a47402d1bff4af2779e", size = 217065, upload-time = "2025-11-10T00:11:36.281Z" }, + { url = "https://files.pythonhosted.org/packages/a5/8d/a12cb424063019fd077b5be474258a0ed8369b92b6d0058e673f0a945982/coverage-7.11.3-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:1410bac9e98afd9623f53876fae7d8a5db9f5a0ac1c9e7c5188463cb4b3212e2", size = 248062, upload-time = "2025-11-10T00:11:37.903Z" }, + { url = "https://files.pythonhosted.org/packages/7f/9c/dab1a4e8e75ce053d14259d3d7485d68528a662e286e184685ea49e71156/coverage-7.11.3-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:004cdcea3457c0ea3233622cd3464c1e32ebba9b41578421097402bee6461b63", size = 250657, upload-time = "2025-11-10T00:11:39.509Z" }, + { url = "https://files.pythonhosted.org/packages/3f/89/a14f256438324f33bae36f9a1a7137729bf26b0a43f5eda60b147ec7c8c7/coverage-7.11.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8f067ada2c333609b52835ca4d4868645d3b63ac04fb2b9a658c55bba7f667d3", size = 251900, upload-time = "2025-11-10T00:11:41.372Z" }, + { url = "https://files.pythonhosted.org/packages/04/07/75b0d476eb349f1296486b1418b44f2d8780cc8db47493de3755e5340076/coverage-7.11.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:07bc7745c945a6d95676953e86ba7cebb9f11de7773951c387f4c07dc76d03f5", size = 248254, upload-time = "2025-11-10T00:11:43.27Z" }, + { url = "https://files.pythonhosted.org/packages/5a/4b/0c486581fa72873489ca092c52792d008a17954aa352809a7cbe6cf0bf07/coverage-7.11.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:8bba7e4743e37484ae17d5c3b8eb1ce78b564cb91b7ace2e2182b25f0f764cb5", size = 250041, upload-time = "2025-11-10T00:11:45.274Z" }, + { url = "https://files.pythonhosted.org/packages/af/a3/0059dafb240ae3e3291f81b8de00e9c511d3dd41d687a227dd4b529be591/coverage-7.11.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:fbffc22d80d86fbe456af9abb17f7a7766e7b2101f7edaacc3535501691563f7", size = 248004, upload-time = "2025-11-10T00:11:46.93Z" }, + { url = "https://files.pythonhosted.org/packages/83/93/967d9662b1eb8c7c46917dcc7e4c1875724ac3e73c3cb78e86d7a0ac719d/coverage-7.11.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:0dba4da36730e384669e05b765a2c49f39514dd3012fcc0398dd66fba8d746d5", size = 247828, upload-time = "2025-11-10T00:11:48.563Z" }, + { url = "https://files.pythonhosted.org/packages/4c/1c/5077493c03215701e212767e470b794548d817dfc6247a4718832cc71fac/coverage-7.11.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ae12fe90b00b71a71b69f513773310782ce01d5f58d2ceb2b7c595ab9d222094", size = 249588, upload-time = "2025-11-10T00:11:50.581Z" }, + { url = "https://files.pythonhosted.org/packages/7f/a5/77f64de461016e7da3e05d7d07975c89756fe672753e4cf74417fc9b9052/coverage-7.11.3-cp313-cp313-win32.whl", hash = "sha256:12d821de7408292530b0d241468b698bce18dd12ecaf45316149f53877885f8c", size = 219223, upload-time = "2025-11-10T00:11:52.184Z" }, + { url = "https://files.pythonhosted.org/packages/ed/1c/ec51a3c1a59d225b44bdd3a4d463135b3159a535c2686fac965b698524f4/coverage-7.11.3-cp313-cp313-win_amd64.whl", hash = "sha256:6bb599052a974bb6cedfa114f9778fedfad66854107cf81397ec87cb9b8fbcf2", size = 220033, upload-time = "2025-11-10T00:11:53.871Z" }, + { url = "https://files.pythonhosted.org/packages/01/ec/e0ce39746ed558564c16f2cc25fa95ce6fc9fa8bfb3b9e62855d4386b886/coverage-7.11.3-cp313-cp313-win_arm64.whl", hash = "sha256:bb9d7efdb063903b3fdf77caec7b77c3066885068bdc0d44bc1b0c171033f944", size = 218661, upload-time = "2025-11-10T00:11:55.597Z" }, + { url = "https://files.pythonhosted.org/packages/46/cb/483f130bc56cbbad2638248915d97b185374d58b19e3cc3107359715949f/coverage-7.11.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:fb58da65e3339b3dbe266b607bb936efb983d86b00b03eb04c4ad5b442c58428", size = 217389, upload-time = "2025-11-10T00:11:57.59Z" }, + { url = "https://files.pythonhosted.org/packages/cb/ae/81f89bae3afef75553cf10e62feb57551535d16fd5859b9ee5a2a97ddd27/coverage-7.11.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:8d16bbe566e16a71d123cd66382c1315fcd520c7573652a8074a8fe281b38c6a", size = 217742, upload-time = "2025-11-10T00:11:59.519Z" }, + { url = "https://files.pythonhosted.org/packages/db/6e/a0fb897041949888191a49c36afd5c6f5d9f5fd757e0b0cd99ec198a324b/coverage-7.11.3-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:a8258f10059b5ac837232c589a350a2df4a96406d6d5f2a09ec587cbdd539655", size = 259049, upload-time = "2025-11-10T00:12:01.592Z" }, + { url = "https://files.pythonhosted.org/packages/d9/b6/d13acc67eb402d91eb94b9bd60593411799aed09ce176ee8d8c0e39c94ca/coverage-7.11.3-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:4c5627429f7fbff4f4131cfdd6abd530734ef7761116811a707b88b7e205afd7", size = 261113, upload-time = "2025-11-10T00:12:03.639Z" }, + { url = "https://files.pythonhosted.org/packages/ea/07/a6868893c48191d60406df4356aa7f0f74e6de34ef1f03af0d49183e0fa1/coverage-7.11.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:465695268414e149bab754c54b0c45c8ceda73dd4a5c3ba255500da13984b16d", size = 263546, upload-time = "2025-11-10T00:12:05.485Z" }, + { url = "https://files.pythonhosted.org/packages/24/e5/28598f70b2c1098332bac47925806353b3313511d984841111e6e760c016/coverage-7.11.3-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4ebcddfcdfb4c614233cff6e9a3967a09484114a8b2e4f2c7a62dc83676ba13f", size = 258260, upload-time = "2025-11-10T00:12:07.137Z" }, + { url = "https://files.pythonhosted.org/packages/0e/58/58e2d9e6455a4ed746a480c4b9cf96dc3cb2a6b8f3efbee5efd33ae24b06/coverage-7.11.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:13b2066303a1c1833c654d2af0455bb009b6e1727b3883c9964bc5c2f643c1d0", size = 261121, upload-time = "2025-11-10T00:12:09.138Z" }, + { url = "https://files.pythonhosted.org/packages/17/57/38803eefb9b0409934cbc5a14e3978f0c85cb251d2b6f6a369067a7105a0/coverage-7.11.3-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:d8750dd20362a1b80e3cf84f58013d4672f89663aee457ea59336df50fab6739", size = 258736, upload-time = "2025-11-10T00:12:11.195Z" }, + { url = "https://files.pythonhosted.org/packages/a8/f3/f94683167156e93677b3442be1d4ca70cb33718df32a2eea44a5898f04f6/coverage-7.11.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:ab6212e62ea0e1006531a2234e209607f360d98d18d532c2fa8e403c1afbdd71", size = 257625, upload-time = "2025-11-10T00:12:12.843Z" }, + { url = "https://files.pythonhosted.org/packages/87/ed/42d0bf1bc6bfa7d65f52299a31daaa866b4c11000855d753857fe78260ac/coverage-7.11.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:a6b17c2b5e0b9bb7702449200f93e2d04cb04b1414c41424c08aa1e5d352da76", size = 259827, upload-time = "2025-11-10T00:12:15.128Z" }, + { url = "https://files.pythonhosted.org/packages/d3/76/5682719f5d5fbedb0c624c9851ef847407cae23362deb941f185f489c54e/coverage-7.11.3-cp313-cp313t-win32.whl", hash = "sha256:426559f105f644b69290ea414e154a0d320c3ad8a2bb75e62884731f69cf8e2c", size = 219897, upload-time = "2025-11-10T00:12:17.274Z" }, + { url = "https://files.pythonhosted.org/packages/10/e0/1da511d0ac3d39e6676fa6cc5ec35320bbf1cebb9b24e9ee7548ee4e931a/coverage-7.11.3-cp313-cp313t-win_amd64.whl", hash = "sha256:90a96fcd824564eae6137ec2563bd061d49a32944858d4bdbae5c00fb10e76ac", size = 220959, upload-time = "2025-11-10T00:12:19.292Z" }, + { url = "https://files.pythonhosted.org/packages/e5/9d/e255da6a04e9ec5f7b633c54c0fdfa221a9e03550b67a9c83217de12e96c/coverage-7.11.3-cp313-cp313t-win_arm64.whl", hash = "sha256:1e33d0bebf895c7a0905fcfaff2b07ab900885fc78bba2a12291a2cfbab014cc", size = 219234, upload-time = "2025-11-10T00:12:21.251Z" }, + { url = "https://files.pythonhosted.org/packages/84/d6/634ec396e45aded1772dccf6c236e3e7c9604bc47b816e928f32ce7987d1/coverage-7.11.3-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:fdc5255eb4815babcdf236fa1a806ccb546724c8a9b129fd1ea4a5448a0bf07c", size = 216746, upload-time = "2025-11-10T00:12:23.089Z" }, + { url = "https://files.pythonhosted.org/packages/28/76/1079547f9d46f9c7c7d0dad35b6873c98bc5aa721eeabceafabd722cd5e7/coverage-7.11.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:fe3425dc6021f906c6325d3c415e048e7cdb955505a94f1eb774dafc779ba203", size = 217077, upload-time = "2025-11-10T00:12:24.863Z" }, + { url = "https://files.pythonhosted.org/packages/2d/71/6ad80d6ae0d7cb743b9a98df8bb88b1ff3dc54491508a4a97549c2b83400/coverage-7.11.3-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:4ca5f876bf41b24378ee67c41d688155f0e54cdc720de8ef9ad6544005899240", size = 248122, upload-time = "2025-11-10T00:12:26.553Z" }, + { url = "https://files.pythonhosted.org/packages/20/1d/784b87270784b0b88e4beec9d028e8d58f73ae248032579c63ad2ac6f69a/coverage-7.11.3-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9061a3e3c92b27fd8036dafa26f25d95695b6aa2e4514ab16a254f297e664f83", size = 250638, upload-time = "2025-11-10T00:12:28.555Z" }, + { url = "https://files.pythonhosted.org/packages/f5/26/b6dd31e23e004e9de84d1a8672cd3d73e50f5dae65dbd0f03fa2cdde6100/coverage-7.11.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:abcea3b5f0dc44e1d01c27090bc32ce6ffb7aa665f884f1890710454113ea902", size = 251972, upload-time = "2025-11-10T00:12:30.246Z" }, + { url = "https://files.pythonhosted.org/packages/c9/ef/f9c64d76faac56b82daa036b34d4fe9ab55eb37f22062e68e9470583e688/coverage-7.11.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:68c4eb92997dbaaf839ea13527be463178ac0ddd37a7ac636b8bc11a51af2428", size = 248147, upload-time = "2025-11-10T00:12:32.195Z" }, + { url = "https://files.pythonhosted.org/packages/b6/eb/5b666f90a8f8053bd264a1ce693d2edef2368e518afe70680070fca13ecd/coverage-7.11.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:149eccc85d48c8f06547534068c41d69a1a35322deaa4d69ba1561e2e9127e75", size = 249995, upload-time = "2025-11-10T00:12:33.969Z" }, + { url = "https://files.pythonhosted.org/packages/eb/7b/871e991ffb5d067f8e67ffb635dabba65b231d6e0eb724a4a558f4a702a5/coverage-7.11.3-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:08c0bcf932e47795c49f0406054824b9d45671362dfc4269e0bc6e4bff010704", size = 247948, upload-time = "2025-11-10T00:12:36.341Z" }, + { url = "https://files.pythonhosted.org/packages/0a/8b/ce454f0af9609431b06dbe5485fc9d1c35ddc387e32ae8e374f49005748b/coverage-7.11.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:39764c6167c82d68a2d8c97c33dba45ec0ad9172570860e12191416f4f8e6e1b", size = 247770, upload-time = "2025-11-10T00:12:38.167Z" }, + { url = "https://files.pythonhosted.org/packages/61/8f/79002cb58a61dfbd2085de7d0a46311ef2476823e7938db80284cedd2428/coverage-7.11.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:3224c7baf34e923ffc78cb45e793925539d640d42c96646db62dbd61bbcfa131", size = 249431, upload-time = "2025-11-10T00:12:40.354Z" }, + { url = "https://files.pythonhosted.org/packages/58/cc/d06685dae97468ed22999440f2f2f5060940ab0e7952a7295f236d98cce7/coverage-7.11.3-cp314-cp314-win32.whl", hash = "sha256:c713c1c528284d636cd37723b0b4c35c11190da6f932794e145fc40f8210a14a", size = 219508, upload-time = "2025-11-10T00:12:42.231Z" }, + { url = "https://files.pythonhosted.org/packages/5f/ed/770cd07706a3598c545f62d75adf2e5bd3791bffccdcf708ec383ad42559/coverage-7.11.3-cp314-cp314-win_amd64.whl", hash = "sha256:c381a252317f63ca0179d2c7918e83b99a4ff3101e1b24849b999a00f9cd4f86", size = 220325, upload-time = "2025-11-10T00:12:44.065Z" }, + { url = "https://files.pythonhosted.org/packages/ee/ac/6a1c507899b6fb1b9a56069954365f655956bcc648e150ce64c2b0ecbed8/coverage-7.11.3-cp314-cp314-win_arm64.whl", hash = "sha256:3e33a968672be1394eded257ec10d4acbb9af2ae263ba05a99ff901bb863557e", size = 218899, upload-time = "2025-11-10T00:12:46.18Z" }, + { url = "https://files.pythonhosted.org/packages/9a/58/142cd838d960cd740654d094f7b0300d7b81534bb7304437d2439fb685fb/coverage-7.11.3-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:f9c96a29c6d65bd36a91f5634fef800212dff69dacdb44345c4c9783943ab0df", size = 217471, upload-time = "2025-11-10T00:12:48.392Z" }, + { url = "https://files.pythonhosted.org/packages/bc/2c/2f44d39eb33e41ab3aba80571daad32e0f67076afcf27cb443f9e5b5a3ee/coverage-7.11.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:2ec27a7a991d229213c8070d31e3ecf44d005d96a9edc30c78eaeafaa421c001", size = 217742, upload-time = "2025-11-10T00:12:50.182Z" }, + { url = "https://files.pythonhosted.org/packages/32/76/8ebc66c3c699f4de3174a43424c34c086323cd93c4930ab0f835731c443a/coverage-7.11.3-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:72c8b494bd20ae1c58528b97c4a67d5cfeafcb3845c73542875ecd43924296de", size = 259120, upload-time = "2025-11-10T00:12:52.451Z" }, + { url = "https://files.pythonhosted.org/packages/19/89/78a3302b9595f331b86e4f12dfbd9252c8e93d97b8631500888f9a3a2af7/coverage-7.11.3-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:60ca149a446da255d56c2a7a813b51a80d9497a62250532598d249b3cdb1a926", size = 261229, upload-time = "2025-11-10T00:12:54.667Z" }, + { url = "https://files.pythonhosted.org/packages/07/59/1a9c0844dadef2a6efac07316d9781e6c5a3f3ea7e5e701411e99d619bfd/coverage-7.11.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:eb5069074db19a534de3859c43eec78e962d6d119f637c41c8e028c5ab3f59dd", size = 263642, upload-time = "2025-11-10T00:12:56.841Z" }, + { url = "https://files.pythonhosted.org/packages/37/86/66c15d190a8e82eee777793cabde730640f555db3c020a179625a2ad5320/coverage-7.11.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ac5d5329c9c942bbe6295f4251b135d860ed9f86acd912d418dce186de7c19ac", size = 258193, upload-time = "2025-11-10T00:12:58.687Z" }, + { url = "https://files.pythonhosted.org/packages/c7/c7/4a4aeb25cb6f83c3ec4763e5f7cc78da1c6d4ef9e22128562204b7f39390/coverage-7.11.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e22539b676fafba17f0a90ac725f029a309eb6e483f364c86dcadee060429d46", size = 261107, upload-time = "2025-11-10T00:13:00.502Z" }, + { url = "https://files.pythonhosted.org/packages/ed/91/b986b5035f23cf0272446298967ecdd2c3c0105ee31f66f7e6b6948fd7f8/coverage-7.11.3-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:2376e8a9c889016f25472c452389e98bc6e54a19570b107e27cde9d47f387b64", size = 258717, upload-time = "2025-11-10T00:13:02.747Z" }, + { url = "https://files.pythonhosted.org/packages/f0/c7/6c084997f5a04d050c513545d3344bfa17bd3b67f143f388b5757d762b0b/coverage-7.11.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:4234914b8c67238a3c4af2bba648dc716aa029ca44d01f3d51536d44ac16854f", size = 257541, upload-time = "2025-11-10T00:13:04.689Z" }, + { url = "https://files.pythonhosted.org/packages/3b/c5/38e642917e406930cb67941210a366ccffa767365c8f8d9ec0f465a8b218/coverage-7.11.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:f0b4101e2b3c6c352ff1f70b3a6fcc7c17c1ab1a91ccb7a33013cb0782af9820", size = 259872, upload-time = "2025-11-10T00:13:06.559Z" }, + { url = "https://files.pythonhosted.org/packages/b7/67/5e812979d20c167f81dbf9374048e0193ebe64c59a3d93d7d947b07865fa/coverage-7.11.3-cp314-cp314t-win32.whl", hash = "sha256:305716afb19133762e8cf62745c46c4853ad6f9eeba54a593e373289e24ea237", size = 220289, upload-time = "2025-11-10T00:13:08.635Z" }, + { url = "https://files.pythonhosted.org/packages/24/3a/b72573802672b680703e0df071faadfab7dcd4d659aaaffc4626bc8bbde8/coverage-7.11.3-cp314-cp314t-win_amd64.whl", hash = "sha256:9245bd392572b9f799261c4c9e7216bafc9405537d0f4ce3ad93afe081a12dc9", size = 221398, upload-time = "2025-11-10T00:13:10.734Z" }, + { url = "https://files.pythonhosted.org/packages/f8/4e/649628f28d38bad81e4e8eb3f78759d20ac173e3c456ac629123815feb40/coverage-7.11.3-cp314-cp314t-win_arm64.whl", hash = "sha256:9a1d577c20b4334e5e814c3d5fe07fa4a8c3ae42a601945e8d7940bab811d0bd", size = 219435, upload-time = "2025-11-10T00:13:12.712Z" }, + { url = "https://files.pythonhosted.org/packages/19/8f/92bdd27b067204b99f396a1414d6342122f3e2663459baf787108a6b8b84/coverage-7.11.3-py3-none-any.whl", hash = "sha256:351511ae28e2509c8d8cae5311577ea7dd511ab8e746ffc8814a0896c3d33fbe", size = 208478, upload-time = "2025-11-10T00:13:14.908Z" }, +] + +[package.optional-dependencies] +toml = [ + { name = "tomli", marker = "python_full_version >= '3.10' and python_full_version <= '3.11'" }, +] + +[[package]] +name = "cython" +version = "3.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/83/36/cce2972e13e83ffe58bc73bfd9d37340b5e5113e8243841a57511c7ae1c2/cython-3.2.1.tar.gz", hash = "sha256:2be1e4d0cbdf7f4cd4d9b8284a034e1989b59fd060f6bd4d24bf3729394d2ed8", size = 3270455, upload-time = "2025-11-12T19:02:59.847Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/87/74/f9fe9e7034f24aef407e7816880c012d8e863bedaa6b42b9ff33e79ea139/cython-3.2.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f1d10b3731171a33563ba81fdcba39c229e45087269dfbe07a1c00e7dcb2537f", size = 2957374, upload-time = "2025-11-12T19:03:10.132Z" }, + { url = "https://files.pythonhosted.org/packages/65/47/f9dd519117f520aaf4d723c88fd9e9139262a0379edc01e71a1e9825e082/cython-3.2.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:92b814b6066d178a5057b557d372e2a03854e947e41cb9dec21db732fbd14c3c", size = 3366838, upload-time = "2025-11-12T19:03:11.742Z" }, + { url = "https://files.pythonhosted.org/packages/5d/3e/d967acfafef00056c3ba832692b9bb358ede2919f641e4a2d24828adacc6/cython-3.2.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c9fc6abd0532007827d8c6143b2bfedf80c7cb89a3c1c12f058336663489ed2e", size = 3535901, upload-time = "2025-11-12T19:03:13.545Z" }, + { url = "https://files.pythonhosted.org/packages/68/79/bc46e714ecb010f80a8aa7f7eaf412c53cbabbe7489590d6aba5f4478ba5/cython-3.2.1-cp310-cp310-win_amd64.whl", hash = "sha256:14f1ed135347587cfddcd3c3219667cac4f0ea0b66aa1c4c0187d50a1b92c222", size = 2764043, upload-time = "2025-11-12T19:03:15.584Z" }, + { url = "https://files.pythonhosted.org/packages/48/d4/ba7b9f341ec168de78bd659600e04bb7de3b2d069bf98b2178a135e88ea4/cython-3.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3cb32c650e7f4476941d1f735cae75a2067d5e3279576273bb8802e8ea907222", size = 2949720, upload-time = "2025-11-12T19:03:17.492Z" }, + { url = "https://files.pythonhosted.org/packages/ad/47/c42417f424c0b928361f48d7dd0ae72716ee21f647b73ceb16f66b98663e/cython-3.2.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8a2b306813d7f28aa0a2c3e4e63ada1427a8109917532df942cd5429db228252", size = 3242127, upload-time = "2025-11-12T19:03:19.227Z" }, + { url = "https://files.pythonhosted.org/packages/e6/fc/1040460889129551649ec35be45e05169871fbcf71bd8e13c533e86f9468/cython-3.2.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0959d9a36d4f004ce63acc1474b3c606745af98b65e8ae709efd0c10988e9d6b", size = 3377094, upload-time = "2025-11-12T19:03:21.25Z" }, + { url = "https://files.pythonhosted.org/packages/f8/f2/8c754298eefa40e21af0ae3592837c6e71254900d5aea1c8859e96b11de5/cython-3.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:60c62e734421365135cc2842013d883136054a26c617c001be494235edfc447a", size = 2767824, upload-time = "2025-11-12T19:03:23.317Z" }, + { url = "https://files.pythonhosted.org/packages/ee/0e/19d5041b87f98ed19c94c388607cd27c1f7458078c3bad5de2dead55b2e1/cython-3.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ea5097d97afd2ab14e98637b7033eba5146de29a5dedf89f5e946076396ab891", size = 2966736, upload-time = "2025-11-12T19:03:25.064Z" }, + { url = "https://files.pythonhosted.org/packages/84/b8/bcc36d9d2464348106984956608a52a42a01ab44ea64031207dffdebc078/cython-3.2.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a4bf12de0475bb6a21e2336a4a04dc4a2b4dd0507a2a3c703e045f3484266605", size = 3221633, upload-time = "2025-11-12T19:03:26.754Z" }, + { url = "https://files.pythonhosted.org/packages/79/20/7d4807fe4ebcef9f20f2e5f93312d0f5d02f9f76524fd4e37706d04e83f7/cython-3.2.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:18c64a0f69a1b8164de70ec7efc72250c589fec21519170de21582300f6aaed9", size = 3389542, upload-time = "2025-11-12T19:03:28.656Z" }, + { url = "https://files.pythonhosted.org/packages/2a/92/b06ba6721299293bc41e89732070132c453bdbaaeabb8f8cc76851b75345/cython-3.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:5ba14907d5826d8010e82306ce279a0d3650f5b50a4813c80836a17b2213c520", size = 2755307, upload-time = "2025-11-12T19:03:30.684Z" }, + { url = "https://files.pythonhosted.org/packages/40/28/c6e36c214baeb27ae45b518552e74457536c7c964b1a55b5900b047fa467/cython-3.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:b4e850fc7a2f72d19679dd083fe4d20bf66860fceabb4f3207112f240249d708", size = 2957307, upload-time = "2025-11-12T19:03:32.471Z" }, + { url = "https://files.pythonhosted.org/packages/c8/c8/b0b9ba64f81f2875c42aab5c0979d6454cd1ac6b3c1e2373ad552701565d/cython-3.2.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3d20ca4afe993f7dccad3aeddbf4c3536cb0fd3ad6dc7a225935a666a5655af2", size = 3210919, upload-time = "2025-11-12T19:03:34.274Z" }, + { url = "https://files.pythonhosted.org/packages/f9/33/5d9ca6abba0e77e1851b843dd1b3c4095fbc6373166935e83c4414f80e88/cython-3.2.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f5a54a757d01ca6a260b02ce5baf17d9db1c2253566ab5844ee4966ff2a69c19", size = 3373350, upload-time = "2025-11-12T19:03:35.927Z" }, + { url = "https://files.pythonhosted.org/packages/e4/29/4408c3486ff380a2d6ae0d4b71da5195efcef3c4360017113ee7d1cb7335/cython-3.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:1b81e56584727a328e00d91c164f8f0f2c59b02bf6857c3f000cd830fa571453", size = 2753425, upload-time = "2025-11-12T19:03:38.157Z" }, + { url = "https://files.pythonhosted.org/packages/f0/32/c1aa03ccadda89487ff31b90d8651c3706ce2744bf4f2c2ae213147e89bd/cython-3.2.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:d7af6ad01c0fe1965d1d3badaeb6df53c1f37383ebae1ccb405b73f628f87713", size = 2967833, upload-time = "2025-11-12T19:03:40.233Z" }, + { url = "https://files.pythonhosted.org/packages/ff/dc/3488d3ade0635408a2ebb05561a3009e2f54616bfefd1f107088dfeb2c4c/cython-3.2.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e3ea7cd085b62acb67c0fbde5cd17a7d9e47992c965e81ec977cf9ea7c59cd65", size = 3256237, upload-time = "2025-11-12T19:03:42.005Z" }, + { url = "https://files.pythonhosted.org/packages/7b/ba/f3d35d3803c9a424fa8812893847114deb9e2440c1bc67a31ab9ec4b9355/cython-3.2.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:986aea38fdf231e78d73745f83271c5654852c822dc5141a1d3fba64429a6aa6", size = 3383100, upload-time = "2025-11-12T19:03:43.675Z" }, + { url = "https://files.pythonhosted.org/packages/86/dc/d72dbb2f8e7ca95d2d18fd86f32b2e385996576230e7ecddd7d250786825/cython-3.2.1-cp314-cp314-win_amd64.whl", hash = "sha256:4960e26cd34c1385f21646339f2e0361fcdd2ed3c01cdb50fe734add577ec56a", size = 2790322, upload-time = "2025-11-12T19:03:45.373Z" }, + { url = "https://files.pythonhosted.org/packages/5a/7e/1194f4ba98b981bbdca945a292e4f49e87ea09d69516b24445409e7cf611/cython-3.2.1-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:4e9167316bf6ecfea33dcca62f074605648fb93cc053ef46b5deb3e5d12fc0d3", size = 2872858, upload-time = "2025-11-12T19:03:55.074Z" }, + { url = "https://files.pythonhosted.org/packages/6b/1a/393ca8ffec7ad3f02b8e4bffaba3dba4fb62c4a1c4c0b6dbf3b80e709fe3/cython-3.2.1-cp39-abi3-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3095df6cd470064742f428c937bed7200c5123b9e19ee04aa09ec61281e565a3", size = 3209664, upload-time = "2025-11-12T19:03:56.771Z" }, + { url = "https://files.pythonhosted.org/packages/37/57/f209f64c609d3d8fac60a572e56da2f621dc1789e399c58db61d5645a31f/cython-3.2.1-cp39-abi3-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:db3f53b2d9afb206075a2605f1150aa019f0733c7795a38eccc6119c2e9c3f7b", size = 2854607, upload-time = "2025-11-12T19:03:59.413Z" }, + { url = "https://files.pythonhosted.org/packages/fc/af/1e5c73fe52423f40776130b0be914fd9f9f8dc26c4f6ea4c2ed04772d558/cython-3.2.1-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:0fc5e7687ac8f8e2b2fb95648f43e9e074ebaa72fd5cb3d8e20e5f1e8b8e02d9", size = 2991567, upload-time = "2025-11-12T19:04:02.209Z" }, + { url = "https://files.pythonhosted.org/packages/39/2c/3ea175b6b1fdfb429f9e9c395240d894155b3c0615caced05fef43264cba/cython-3.2.1-cp39-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:bbb3bc152bc0de82b031c8d355418fa4890a92424209d59366c2c0bc9e6cf53c", size = 2889178, upload-time = "2025-11-12T19:04:05.272Z" }, + { url = "https://files.pythonhosted.org/packages/f1/88/b2ab22a3a3feac78c62354a823c5c0c33659909e9918f53aa05904532b4b/cython-3.2.1-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:a2022bc48ad0c2c0e0485bf0b54902913a3d81086b7d435f4437620c667799f6", size = 3223755, upload-time = "2025-11-12T19:04:07.262Z" }, + { url = "https://files.pythonhosted.org/packages/0b/56/9ba58629a03cbffb5965a3c65ccd91fa683d95d588c21a875da72fdc249b/cython-3.2.1-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:99fdd4ffc2dcb513f4be9ce71c6fedd895b96b1f814655b6bbab196df497b090", size = 3113456, upload-time = "2025-11-12T19:04:09.175Z" }, + { url = "https://files.pythonhosted.org/packages/56/5b/148c1a7ea5aebe460a70cad716a77e5fd0205be2de9fc5250491eb13ad8c/cython-3.2.1-cp39-abi3-win32.whl", hash = "sha256:06071f85bd5ce040464d43b2f9f287742a79f905e81b709fe904567230f1ed51", size = 2434223, upload-time = "2025-11-12T19:04:11.294Z" }, + { url = "https://files.pythonhosted.org/packages/7a/54/bb9b0c9db2a92a5e93747ca3027cfc645741411f8f1c6af2fb2a7b82df5d/cython-3.2.1-cp39-abi3-win_arm64.whl", hash = "sha256:e87c131d59480aee1ebac622b64f287c0e1d665ad1a1b7d498ac48accdb36c6b", size = 2439268, upload-time = "2025-11-12T19:04:12.931Z" }, + { url = "https://files.pythonhosted.org/packages/eb/60/883eee29296a8b91e150fc4f18bb3c89ed94857bb0226bf2218cfdef223a/cython-3.2.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:eb3c59a137aead5aecdda87922aa96ff5541147b5dc84850955717b539753014", size = 2966821, upload-time = "2025-11-12T19:04:14.948Z" }, + { url = "https://files.pythonhosted.org/packages/6a/9a/f918933f1a62a3b9effa8d5bc2d269edcadc23d7d888e1f69ba957ea8248/cython-3.2.1-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3e2d11f770259884b337481df78547727e92d042afbbe29e0f21f7246f1f9252", size = 3372892, upload-time = "2025-11-12T19:04:17.149Z" }, + { url = "https://files.pythonhosted.org/packages/f3/8b/ca36f689c4ab959625b0ffa4c01e0a977bca11d5a355eebd85de256f7638/cython-3.2.1-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d7ccb0e16071fac3f115c083b2c5c25fa6c519c20c662b6c20f2c26fe6d98877", size = 3541441, upload-time = "2025-11-12T19:04:19.535Z" }, + { url = "https://files.pythonhosted.org/packages/46/42/22b0c49a66c59f204cddbeb017698e7b803dcfc41c9b21566ae3c854e55d/cython-3.2.1-cp39-cp39-win_amd64.whl", hash = "sha256:c0ced1c077412b38594c1e3b97ef8db5432e51bf35046678cfe5828e00252c96", size = 2769743, upload-time = "2025-11-12T19:04:21.423Z" }, + { url = "https://files.pythonhosted.org/packages/aa/30/373775b8d933d781d055c1dd0f110f275a101f320dab724c8c63a7c1b945/cython-3.2.1-py3-none-any.whl", hash = "sha256:cd72c46e7bffe8250c52d400e72c8d5d3086437b6aeec5b0eca99ccd337f5834", size = 1254219, upload-time = "2025-11-12T19:02:56.14Z" }, +] + +[[package]] +name = "distlib" +version = "0.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/96/8e/709914eb2b5749865801041647dc7f4e6d00b549cfe88b65ca192995f07c/distlib-0.4.0.tar.gz", hash = "sha256:feec40075be03a04501a973d81f633735b4b69f98b05450592310c0f401a4e0d", size = 614605, upload-time = "2025-07-17T16:52:00.465Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl", hash = "sha256:9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16", size = 469047, upload-time = "2025-07-17T16:51:58.613Z" }, +] + +[[package]] +name = "docutils" +version = "0.21.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ae/ed/aefcc8cd0ba62a0560c3c18c33925362d46c6075480bfa4df87b28e169a9/docutils-0.21.2.tar.gz", hash = "sha256:3a6b18732edf182daa3cd12775bbb338cf5691468f91eeeb109deff6ebfa986f", size = 2204444, upload-time = "2024-04-23T18:57:18.24Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8f/d7/9322c609343d929e75e7e5e6255e614fcc67572cfd083959cdef3b7aad79/docutils-0.21.2-py3-none-any.whl", hash = "sha256:dafca5b9e384f0e419294eb4d2ff9fa826435bf15f15b7bd45723e8ad76811b2", size = 587408, upload-time = "2024-04-23T18:57:14.835Z" }, +] + +[[package]] +name = "exceptiongroup" +version = "1.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0b/9f/a65090624ecf468cdca03533906e7c69ed7588582240cfe7cc9e770b50eb/exceptiongroup-1.3.0.tar.gz", hash = "sha256:b241f5885f560bc56a59ee63ca4c6a8bfa46ae4ad651af316d4e81817bb9fd88", size = 29749, upload-time = "2025-05-10T17:42:51.123Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/36/f4/c6e662dade71f56cd2f3735141b265c3c79293c109549c1e6933b0651ffc/exceptiongroup-1.3.0-py3-none-any.whl", hash = "sha256:4d111e6e0c13d0644cad6ddaa7ed0261a0b36971f6d23e7ec9b4b9097da78a10", size = 16674, upload-time = "2025-05-10T17:42:49.33Z" }, +] + +[[package]] +name = "filelock" +version = "3.19.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.10'", +] +sdist = { url = "https://files.pythonhosted.org/packages/40/bb/0ab3e58d22305b6f5440629d20683af28959bf793d98d11950e305c1c326/filelock-3.19.1.tar.gz", hash = "sha256:66eda1888b0171c998b35be2bcc0f6d75c388a7ce20c3f3f37aa8e96c2dddf58", size = 17687, upload-time = "2025-08-14T16:56:03.016Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/42/14/42b2651a2f46b022ccd948bca9f2d5af0fd8929c4eec235b8d6d844fbe67/filelock-3.19.1-py3-none-any.whl", hash = "sha256:d38e30481def20772f5baf097c122c3babc4fcdb7e14e57049eb9d88c6dc017d", size = 15988, upload-time = "2025-08-14T16:56:01.633Z" }, +] + +[[package]] +name = "filelock" +version = "3.20.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.11'", + "python_full_version == '3.10.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/58/46/0028a82567109b5ef6e4d2a1f04a583fb513e6cf9527fcdd09afd817deeb/filelock-3.20.0.tar.gz", hash = "sha256:711e943b4ec6be42e1d4e6690b48dc175c822967466bb31c0c293f34334c13f4", size = 18922, upload-time = "2025-10-08T18:03:50.056Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/76/91/7216b27286936c16f5b4d0c530087e4a54eead683e6b0b73dd0c64844af6/filelock-3.20.0-py3-none-any.whl", hash = "sha256:339b4732ffda5cd79b13f4e2711a31b0365ce445d95d243bb996273d072546a2", size = 16054, upload-time = "2025-10-08T18:03:48.35Z" }, +] + +[[package]] +name = "gdal" +version = "3.12.0.post1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/95/fc/9d187c4d280122b497c2d4c25a1c3b1ba50e5e1836924e49323685999c43/gdal-3.12.0.post1.tar.gz", hash = "sha256:d2b68361c7cf2ef7caa8cbb8549a315080301564e3f025d1842df71a4e406236", size = 902365, upload-time = "2025-11-07T23:40:07.734Z" } + +[[package]] +name = "h5py" +version = "3.14.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.10'", +] +dependencies = [ + { name = "numpy", version = "2.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5d/57/dfb3c5c3f1bf5f5ef2e59a22dec4ff1f3d7408b55bfcefcfb0ea69ef21c6/h5py-3.14.0.tar.gz", hash = "sha256:2372116b2e0d5d3e5e705b7f663f7c8d96fa79a4052d250484ef91d24d6a08f4", size = 424323, upload-time = "2025-06-06T14:06:15.01Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/52/89/06cbb421e01dea2e338b3154326523c05d9698f89a01f9d9b65e1ec3fb18/h5py-3.14.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:24df6b2622f426857bda88683b16630014588a0e4155cba44e872eb011c4eaed", size = 3332522, upload-time = "2025-06-06T14:04:13.775Z" }, + { url = "https://files.pythonhosted.org/packages/c3/e7/6c860b002329e408348735bfd0459e7b12f712c83d357abeef3ef404eaa9/h5py-3.14.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6ff2389961ee5872de697054dd5a033b04284afc3fb52dc51d94561ece2c10c6", size = 2831051, upload-time = "2025-06-06T14:04:18.206Z" }, + { url = "https://files.pythonhosted.org/packages/fa/cd/3dd38cdb7cc9266dc4d85f27f0261680cb62f553f1523167ad7454e32b11/h5py-3.14.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:016e89d3be4c44f8d5e115fab60548e518ecd9efe9fa5c5324505a90773e6f03", size = 4324677, upload-time = "2025-06-06T14:04:23.438Z" }, + { url = "https://files.pythonhosted.org/packages/b1/45/e1a754dc7cd465ba35e438e28557119221ac89b20aaebef48282654e3dc7/h5py-3.14.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1223b902ef0b5d90bcc8a4778218d6d6cd0f5561861611eda59fa6c52b922f4d", size = 4557272, upload-time = "2025-06-06T14:04:28.863Z" }, + { url = "https://files.pythonhosted.org/packages/5c/06/f9506c1531645829d302c420851b78bb717af808dde11212c113585fae42/h5py-3.14.0-cp310-cp310-win_amd64.whl", hash = "sha256:852b81f71df4bb9e27d407b43071d1da330d6a7094a588efa50ef02553fa7ce4", size = 2866734, upload-time = "2025-06-06T14:04:33.5Z" }, + { url = "https://files.pythonhosted.org/packages/61/1b/ad24a8ce846cf0519695c10491e99969d9d203b9632c4fcd5004b1641c2e/h5py-3.14.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f30dbc58f2a0efeec6c8836c97f6c94afd769023f44e2bb0ed7b17a16ec46088", size = 3352382, upload-time = "2025-06-06T14:04:37.95Z" }, + { url = "https://files.pythonhosted.org/packages/36/5b/a066e459ca48b47cc73a5c668e9924d9619da9e3c500d9fb9c29c03858ec/h5py-3.14.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:543877d7f3d8f8a9828ed5df6a0b78ca3d8846244b9702e99ed0d53610b583a8", size = 2852492, upload-time = "2025-06-06T14:04:42.092Z" }, + { url = "https://files.pythonhosted.org/packages/08/0c/5e6aaf221557314bc15ba0e0da92e40b24af97ab162076c8ae009320a42b/h5py-3.14.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c497600c0496548810047257e36360ff551df8b59156d3a4181072eed47d8ad", size = 4298002, upload-time = "2025-06-06T14:04:47.106Z" }, + { url = "https://files.pythonhosted.org/packages/21/d4/d461649cafd5137088fb7f8e78fdc6621bb0c4ff2c090a389f68e8edc136/h5py-3.14.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:723a40ee6505bd354bfd26385f2dae7bbfa87655f4e61bab175a49d72ebfc06b", size = 4516618, upload-time = "2025-06-06T14:04:52.467Z" }, + { url = "https://files.pythonhosted.org/packages/db/0c/6c3f879a0f8e891625817637fad902da6e764e36919ed091dc77529004ac/h5py-3.14.0-cp311-cp311-win_amd64.whl", hash = "sha256:d2744b520440a996f2dae97f901caa8a953afc055db4673a993f2d87d7f38713", size = 2874888, upload-time = "2025-06-06T14:04:56.95Z" }, + { url = "https://files.pythonhosted.org/packages/3e/77/8f651053c1843391e38a189ccf50df7e261ef8cd8bfd8baba0cbe694f7c3/h5py-3.14.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:e0045115d83272090b0717c555a31398c2c089b87d212ceba800d3dc5d952e23", size = 3312740, upload-time = "2025-06-06T14:05:01.193Z" }, + { url = "https://files.pythonhosted.org/packages/ff/10/20436a6cf419b31124e59fefc78d74cb061ccb22213226a583928a65d715/h5py-3.14.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6da62509b7e1d71a7d110478aa25d245dd32c8d9a1daee9d2a42dba8717b047a", size = 2829207, upload-time = "2025-06-06T14:05:05.061Z" }, + { url = "https://files.pythonhosted.org/packages/3f/19/c8bfe8543bfdd7ccfafd46d8cfd96fce53d6c33e9c7921f375530ee1d39a/h5py-3.14.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:554ef0ced3571366d4d383427c00c966c360e178b5fb5ee5bb31a435c424db0c", size = 4708455, upload-time = "2025-06-06T14:05:11.528Z" }, + { url = "https://files.pythonhosted.org/packages/86/f9/f00de11c82c88bfc1ef22633557bfba9e271e0cb3189ad704183fc4a2644/h5py-3.14.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0cbd41f4e3761f150aa5b662df991868ca533872c95467216f2bec5fcad84882", size = 4929422, upload-time = "2025-06-06T14:05:18.399Z" }, + { url = "https://files.pythonhosted.org/packages/7a/6d/6426d5d456f593c94b96fa942a9b3988ce4d65ebaf57d7273e452a7222e8/h5py-3.14.0-cp312-cp312-win_amd64.whl", hash = "sha256:bf4897d67e613ecf5bdfbdab39a1158a64df105827da70ea1d90243d796d367f", size = 2862845, upload-time = "2025-06-06T14:05:23.699Z" }, + { url = "https://files.pythonhosted.org/packages/6c/c2/7efe82d09ca10afd77cd7c286e42342d520c049a8c43650194928bcc635c/h5py-3.14.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:aa4b7bbce683379b7bf80aaba68e17e23396100336a8d500206520052be2f812", size = 3289245, upload-time = "2025-06-06T14:05:28.24Z" }, + { url = "https://files.pythonhosted.org/packages/4f/31/f570fab1239b0d9441024b92b6ad03bb414ffa69101a985e4c83d37608bd/h5py-3.14.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ef9603a501a04fcd0ba28dd8f0995303d26a77a980a1f9474b3417543d4c6174", size = 2807335, upload-time = "2025-06-06T14:05:31.997Z" }, + { url = "https://files.pythonhosted.org/packages/0d/ce/3a21d87896bc7e3e9255e0ad5583ae31ae9e6b4b00e0bcb2a67e2b6acdbc/h5py-3.14.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8cbaf6910fa3983c46172666b0b8da7b7bd90d764399ca983236f2400436eeb", size = 4700675, upload-time = "2025-06-06T14:05:37.38Z" }, + { url = "https://files.pythonhosted.org/packages/e7/ec/86f59025306dcc6deee5fda54d980d077075b8d9889aac80f158bd585f1b/h5py-3.14.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d90e6445ab7c146d7f7981b11895d70bc1dd91278a4f9f9028bc0c95e4a53f13", size = 4921632, upload-time = "2025-06-06T14:05:43.464Z" }, + { url = "https://files.pythonhosted.org/packages/3f/6d/0084ed0b78d4fd3e7530c32491f2884140d9b06365dac8a08de726421d4a/h5py-3.14.0-cp313-cp313-win_amd64.whl", hash = "sha256:ae18e3de237a7a830adb76aaa68ad438d85fe6e19e0d99944a3ce46b772c69b3", size = 2852929, upload-time = "2025-06-06T14:05:47.659Z" }, + { url = "https://files.pythonhosted.org/packages/ec/ac/9ea82488c8790ee5b6ad1a807cd7dc3b9dadfece1cd0e0e369f68a7a8937/h5py-3.14.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f5cc1601e78027cedfec6dd50efb4802f018551754191aeb58d948bd3ec3bd7a", size = 3345097, upload-time = "2025-06-06T14:05:51.984Z" }, + { url = "https://files.pythonhosted.org/packages/6c/bc/a172ecaaf287e3af2f837f23b470b0a2229c79555a0da9ac8b5cc5bed078/h5py-3.14.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5e59d2136a8b302afd25acdf7a89b634e0eb7c66b1a211ef2d0457853768a2ef", size = 2843320, upload-time = "2025-06-06T14:05:55.754Z" }, + { url = "https://files.pythonhosted.org/packages/66/40/b423b57696514e05aa7bb06150ef96667d0e0006cc6de7ab52c71734ab51/h5py-3.14.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:573c33ad056ac7c1ab6d567b6db9df3ffc401045e3f605736218f96c1e0490c6", size = 4326368, upload-time = "2025-06-06T14:06:00.782Z" }, + { url = "https://files.pythonhosted.org/packages/f7/07/e088f89f04fdbe57ddf9de377f857158d3daa38cf5d0fb20ef9bd489e313/h5py-3.14.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ccbe17dc187c0c64178f1a10aa274ed3a57d055117588942b8a08793cc448216", size = 4559686, upload-time = "2025-06-06T14:06:07.416Z" }, + { url = "https://files.pythonhosted.org/packages/b4/e4/fb8032d0e5480b1db9b419b5b50737b61bb3c7187c49d809975d62129fb0/h5py-3.14.0-cp39-cp39-win_amd64.whl", hash = "sha256:4f025cf30ae738c4c4e38c7439a761a71ccfcce04c2b87b2a2ac64e8c5171d43", size = 2877166, upload-time = "2025-06-06T14:06:13.05Z" }, +] + +[[package]] +name = "h5py" +version = "3.15.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.11'", + "python_full_version == '3.10.*'", +] +dependencies = [ + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" }, + { name = "numpy", version = "2.3.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/4d/6a/0d79de0b025aa85dc8864de8e97659c94cf3d23148394a954dc5ca52f8c8/h5py-3.15.1.tar.gz", hash = "sha256:c86e3ed45c4473564de55aa83b6fc9e5ead86578773dfbd93047380042e26b69", size = 426236, upload-time = "2025-10-16T10:35:27.404Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/86/30/8fa61698b438dd751fa46a359792e801191dadab560d0a5f1c709443ef8e/h5py-3.15.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:67e59f6c2f19a32973a40f43d9a088ae324fe228c8366e25ebc57ceebf093a6b", size = 3414477, upload-time = "2025-10-16T10:33:24.201Z" }, + { url = "https://files.pythonhosted.org/packages/16/16/db2f63302937337c4e9e51d97a5984b769bdb7488e3d37632a6ac297f8ef/h5py-3.15.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0e2f471688402c3404fa4e13466e373e622fd4b74b47b56cfdff7cc688209422", size = 2850298, upload-time = "2025-10-16T10:33:27.747Z" }, + { url = "https://files.pythonhosted.org/packages/fc/2e/f1bb7de9b05112bfd14d5206090f0f92f1e75bbb412fbec5d4653c3d44dd/h5py-3.15.1-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4c45802bcb711e128a6839cb6c01e9ac648dc55df045c9542a675c771f15c8d5", size = 4523605, upload-time = "2025-10-16T10:33:31.168Z" }, + { url = "https://files.pythonhosted.org/packages/05/8a/63f4b08f3628171ce8da1a04681a65ee7ac338fde3cb3e9e3c9f7818e4da/h5py-3.15.1-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:64ce3f6470adb87c06e3a8dd1b90e973699f1759ad79bfa70c230939bff356c9", size = 4735346, upload-time = "2025-10-16T10:33:34.759Z" }, + { url = "https://files.pythonhosted.org/packages/74/48/f16d12d9de22277605bcc11c0dcab5e35f06a54be4798faa2636b5d44b3c/h5py-3.15.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:4411c1867b9899a25e983fff56d820a66f52ac326bbe10c7cdf7d832c9dcd883", size = 4175305, upload-time = "2025-10-16T10:33:38.83Z" }, + { url = "https://files.pythonhosted.org/packages/d6/2f/47cdbff65b2ce53c27458c6df63a232d7bb1644b97df37b2342442342c84/h5py-3.15.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:2cbc4104d3d4aca9d6db8c0c694555e255805bfeacf9eb1349bda871e26cacbe", size = 4653602, upload-time = "2025-10-16T10:33:42.188Z" }, + { url = "https://files.pythonhosted.org/packages/c3/28/dc08de359c2f43a67baa529cb70d7f9599848750031975eed92d6ae78e1d/h5py-3.15.1-cp310-cp310-win_amd64.whl", hash = "sha256:01f55111ca516f5568ae7a7fc8247dfce607de331b4467ee8a9a6ed14e5422c7", size = 2873601, upload-time = "2025-10-16T10:33:45.323Z" }, + { url = "https://files.pythonhosted.org/packages/41/fd/8349b48b15b47768042cff06ad6e1c229f0a4bd89225bf6b6894fea27e6d/h5py-3.15.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5aaa330bcbf2830150c50897ea5dcbed30b5b6d56897289846ac5b9e529ec243", size = 3434135, upload-time = "2025-10-16T10:33:47.954Z" }, + { url = "https://files.pythonhosted.org/packages/c1/b0/1c628e26a0b95858f54aba17e1599e7f6cd241727596cc2580b72cb0a9bf/h5py-3.15.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c970fb80001fffabb0109eaf95116c8e7c0d3ca2de854e0901e8a04c1f098509", size = 2870958, upload-time = "2025-10-16T10:33:50.907Z" }, + { url = "https://files.pythonhosted.org/packages/f9/e3/c255cafc9b85e6ea04e2ad1bba1416baa1d7f57fc98a214be1144087690c/h5py-3.15.1-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:80e5bb5b9508d5d9da09f81fd00abbb3f85da8143e56b1585d59bc8ceb1dba8b", size = 4504770, upload-time = "2025-10-16T10:33:54.357Z" }, + { url = "https://files.pythonhosted.org/packages/8b/23/4ab1108e87851ccc69694b03b817d92e142966a6c4abd99e17db77f2c066/h5py-3.15.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5b849ba619a066196169763c33f9f0f02e381156d61c03e000bb0100f9950faf", size = 4700329, upload-time = "2025-10-16T10:33:57.616Z" }, + { url = "https://files.pythonhosted.org/packages/a4/e4/932a3a8516e4e475b90969bf250b1924dbe3612a02b897e426613aed68f4/h5py-3.15.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:e7f6c841efd4e6e5b7e82222eaf90819927b6d256ab0f3aca29675601f654f3c", size = 4152456, upload-time = "2025-10-16T10:34:00.843Z" }, + { url = "https://files.pythonhosted.org/packages/2a/0a/f74d589883b13737021b2049ac796328f188dbb60c2ed35b101f5b95a3fc/h5py-3.15.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ca8a3a22458956ee7b40d8e39c9a9dc01f82933e4c030c964f8b875592f4d831", size = 4617295, upload-time = "2025-10-16T10:34:04.154Z" }, + { url = "https://files.pythonhosted.org/packages/23/95/499b4e56452ef8b6c95a271af0dde08dac4ddb70515a75f346d4f400579b/h5py-3.15.1-cp311-cp311-win_amd64.whl", hash = "sha256:550e51131376889656feec4aff2170efc054a7fe79eb1da3bb92e1625d1ac878", size = 2882129, upload-time = "2025-10-16T10:34:06.886Z" }, + { url = "https://files.pythonhosted.org/packages/ce/bb/cfcc70b8a42222ba3ad4478bcef1791181ea908e2adbd7d53c66395edad5/h5py-3.15.1-cp311-cp311-win_arm64.whl", hash = "sha256:b39239947cb36a819147fc19e86b618dcb0953d1cd969f5ed71fc0de60392427", size = 2477121, upload-time = "2025-10-16T10:34:09.579Z" }, + { url = "https://files.pythonhosted.org/packages/62/b8/c0d9aa013ecfa8b7057946c080c0c07f6fa41e231d2e9bd306a2f8110bdc/h5py-3.15.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:316dd0f119734f324ca7ed10b5627a2de4ea42cc4dfbcedbee026aaa361c238c", size = 3399089, upload-time = "2025-10-16T10:34:12.135Z" }, + { url = "https://files.pythonhosted.org/packages/a4/5e/3c6f6e0430813c7aefe784d00c6711166f46225f5d229546eb53032c3707/h5py-3.15.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b51469890e58e85d5242e43aab29f5e9c7e526b951caab354f3ded4ac88e7b76", size = 2847803, upload-time = "2025-10-16T10:34:14.564Z" }, + { url = "https://files.pythonhosted.org/packages/00/69/ba36273b888a4a48d78f9268d2aee05787e4438557450a8442946ab8f3ec/h5py-3.15.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8a33bfd5dfcea037196f7778534b1ff7e36a7f40a89e648c8f2967292eb6898e", size = 4914884, upload-time = "2025-10-16T10:34:18.452Z" }, + { url = "https://files.pythonhosted.org/packages/3a/30/d1c94066343a98bb2cea40120873193a4fed68c4ad7f8935c11caf74c681/h5py-3.15.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:25c8843fec43b2cc368aa15afa1cdf83fc5e17b1c4e10cd3771ef6c39b72e5ce", size = 5109965, upload-time = "2025-10-16T10:34:21.853Z" }, + { url = "https://files.pythonhosted.org/packages/81/3d/d28172116eafc3bc9f5991b3cb3fd2c8a95f5984f50880adfdf991de9087/h5py-3.15.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a308fd8681a864c04423c0324527237a0484e2611e3441f8089fd00ed56a8171", size = 4561870, upload-time = "2025-10-16T10:34:26.69Z" }, + { url = "https://files.pythonhosted.org/packages/a5/83/393a7226024238b0f51965a7156004eaae1fcf84aa4bfecf7e582676271b/h5py-3.15.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f4a016df3f4a8a14d573b496e4d1964deb380e26031fc85fb40e417e9131888a", size = 5037161, upload-time = "2025-10-16T10:34:30.383Z" }, + { url = "https://files.pythonhosted.org/packages/cf/51/329e7436bf87ca6b0fe06dd0a3795c34bebe4ed8d6c44450a20565d57832/h5py-3.15.1-cp312-cp312-win_amd64.whl", hash = "sha256:59b25cf02411bf12e14f803fef0b80886444c7fe21a5ad17c6a28d3f08098a1e", size = 2874165, upload-time = "2025-10-16T10:34:33.461Z" }, + { url = "https://files.pythonhosted.org/packages/09/a8/2d02b10a66747c54446e932171dd89b8b4126c0111b440e6bc05a7c852ec/h5py-3.15.1-cp312-cp312-win_arm64.whl", hash = "sha256:61d5a58a9851e01ee61c932bbbb1c98fe20aba0a5674776600fb9a361c0aa652", size = 2458214, upload-time = "2025-10-16T10:34:35.733Z" }, + { url = "https://files.pythonhosted.org/packages/88/b3/40207e0192415cbff7ea1d37b9f24b33f6d38a5a2f5d18a678de78f967ae/h5py-3.15.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c8440fd8bee9500c235ecb7aa1917a0389a2adb80c209fa1cc485bd70e0d94a5", size = 3376511, upload-time = "2025-10-16T10:34:38.596Z" }, + { url = "https://files.pythonhosted.org/packages/31/96/ba99a003c763998035b0de4c299598125df5fc6c9ccf834f152ddd60e0fb/h5py-3.15.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ab2219dbc6fcdb6932f76b548e2b16f34a1f52b7666e998157a4dfc02e2c4123", size = 2826143, upload-time = "2025-10-16T10:34:41.342Z" }, + { url = "https://files.pythonhosted.org/packages/6a/c2/fc6375d07ea3962df7afad7d863fe4bde18bb88530678c20d4c90c18de1d/h5py-3.15.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d8cb02c3a96255149ed3ac811eeea25b655d959c6dd5ce702c9a95ff11859eb5", size = 4908316, upload-time = "2025-10-16T10:34:44.619Z" }, + { url = "https://files.pythonhosted.org/packages/d9/69/4402ea66272dacc10b298cca18ed73e1c0791ff2ae9ed218d3859f9698ac/h5py-3.15.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:121b2b7a4c1915d63737483b7bff14ef253020f617c2fb2811f67a4bed9ac5e8", size = 5103710, upload-time = "2025-10-16T10:34:48.639Z" }, + { url = "https://files.pythonhosted.org/packages/e0/f6/11f1e2432d57d71322c02a97a5567829a75f223a8c821764a0e71a65cde8/h5py-3.15.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:59b0d63b318bf3cc06687def2b45afd75926bbc006f7b8cd2b1a231299fc8599", size = 4556042, upload-time = "2025-10-16T10:34:51.841Z" }, + { url = "https://files.pythonhosted.org/packages/18/88/3eda3ef16bfe7a7dbc3d8d6836bbaa7986feb5ff091395e140dc13927bcc/h5py-3.15.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e02fe77a03f652500d8bff288cbf3675f742fc0411f5a628fa37116507dc7cc0", size = 5030639, upload-time = "2025-10-16T10:34:55.257Z" }, + { url = "https://files.pythonhosted.org/packages/e5/ea/fbb258a98863f99befb10ed727152b4ae659f322e1d9c0576f8a62754e81/h5py-3.15.1-cp313-cp313-win_amd64.whl", hash = "sha256:dea78b092fd80a083563ed79a3171258d4a4d307492e7cf8b2313d464c82ba52", size = 2864363, upload-time = "2025-10-16T10:34:58.099Z" }, + { url = "https://files.pythonhosted.org/packages/5d/c9/35021cc9cd2b2915a7da3026e3d77a05bed1144a414ff840953b33937fb9/h5py-3.15.1-cp313-cp313-win_arm64.whl", hash = "sha256:c256254a8a81e2bddc0d376e23e2a6d2dc8a1e8a2261835ed8c1281a0744cd97", size = 2449570, upload-time = "2025-10-16T10:35:00.473Z" }, + { url = "https://files.pythonhosted.org/packages/a0/2c/926eba1514e4d2e47d0e9eb16c784e717d8b066398ccfca9b283917b1bfb/h5py-3.15.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:5f4fb0567eb8517c3ecd6b3c02c4f4e9da220c8932604960fd04e24ee1254763", size = 3380368, upload-time = "2025-10-16T10:35:03.117Z" }, + { url = "https://files.pythonhosted.org/packages/65/4b/d715ed454d3baa5f6ae1d30b7eca4c7a1c1084f6a2edead9e801a1541d62/h5py-3.15.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:954e480433e82d3872503104f9b285d369048c3a788b2b1a00e53d1c47c98dd2", size = 2833793, upload-time = "2025-10-16T10:35:05.623Z" }, + { url = "https://files.pythonhosted.org/packages/ef/d4/ef386c28e4579314610a8bffebbee3b69295b0237bc967340b7c653c6c10/h5py-3.15.1-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fd125c131889ebbef0849f4a0e29cf363b48aba42f228d08b4079913b576bb3a", size = 4903199, upload-time = "2025-10-16T10:35:08.972Z" }, + { url = "https://files.pythonhosted.org/packages/33/5d/65c619e195e0b5e54ea5a95c1bb600c8ff8715e0d09676e4cce56d89f492/h5py-3.15.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:28a20e1a4082a479b3d7db2169f3a5034af010b90842e75ebbf2e9e49eb4183e", size = 5097224, upload-time = "2025-10-16T10:35:12.808Z" }, + { url = "https://files.pythonhosted.org/packages/30/30/5273218400bf2da01609e1292f562c94b461fcb73c7a9e27fdadd43abc0a/h5py-3.15.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:fa8df5267f545b4946df8ca0d93d23382191018e4cda2deda4c2cedf9a010e13", size = 4551207, upload-time = "2025-10-16T10:35:16.24Z" }, + { url = "https://files.pythonhosted.org/packages/d3/39/a7ef948ddf4d1c556b0b2b9559534777bccc318543b3f5a1efdf6b556c9c/h5py-3.15.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:99d374a21f7321a4c6ab327c4ab23bd925ad69821aeb53a1e75dd809d19f67fa", size = 5025426, upload-time = "2025-10-16T10:35:19.831Z" }, + { url = "https://files.pythonhosted.org/packages/b6/d8/7368679b8df6925b8415f9dcc9ab1dab01ddc384d2b2c24aac9191bd9ceb/h5py-3.15.1-cp314-cp314-win_amd64.whl", hash = "sha256:9c73d1d7cdb97d5b17ae385153472ce118bed607e43be11e9a9deefaa54e0734", size = 2865704, upload-time = "2025-10-16T10:35:22.658Z" }, + { url = "https://files.pythonhosted.org/packages/d3/b7/4a806f85d62c20157e62e58e03b27513dc9c55499768530acc4f4c5ce4be/h5py-3.15.1-cp314-cp314-win_arm64.whl", hash = "sha256:a6d8c5a05a76aca9a494b4c53ce8a9c29023b7f64f625c6ce1841e92a362ccdf", size = 2465544, upload-time = "2025-10-16T10:35:25.695Z" }, +] + +[[package]] +name = "identify" +version = "2.6.15" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ff/e7/685de97986c916a6d93b3876139e00eef26ad5bbbd61925d670ae8013449/identify-2.6.15.tar.gz", hash = "sha256:e4f4864b96c6557ef2a1e1c951771838f4edc9df3a72ec7118b338801b11c7bf", size = 99311, upload-time = "2025-10-02T17:43:40.631Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0f/1c/e5fd8f973d4f375adb21565739498e2e9a1e54c858a97b9a8ccfdc81da9b/identify-2.6.15-py2.py3-none-any.whl", hash = "sha256:1181ef7608e00704db228516541eb83a88a9f94433a8c80bb9b5bd54b1d81757", size = 99183, upload-time = "2025-10-02T17:43:39.137Z" }, +] + +[[package]] +name = "idna" +version = "3.11" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582, upload-time = "2025-10-12T14:55:20.501Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" }, +] + +[[package]] +name = "imagesize" +version = "1.4.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a7/84/62473fb57d61e31fef6e36d64a179c8781605429fd927b5dd608c997be31/imagesize-1.4.1.tar.gz", hash = "sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a", size = 1280026, upload-time = "2022-07-01T12:21:05.687Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ff/62/85c4c919272577931d407be5ba5d71c20f0b616d31a0befe0ae45bb79abd/imagesize-1.4.1-py2.py3-none-any.whl", hash = "sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b", size = 8769, upload-time = "2022-07-01T12:21:02.467Z" }, +] + +[[package]] +name = "importlib-metadata" +version = "8.7.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "zipp", marker = "python_full_version < '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/76/66/650a33bd90f786193e4de4b3ad86ea60b53c89b669a5c7be931fac31cdb0/importlib_metadata-8.7.0.tar.gz", hash = "sha256:d13b81ad223b890aa16c5471f2ac3056cf76c5f10f82d6f9292f0b415f389000", size = 56641, upload-time = "2025-04-27T15:29:01.736Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/20/b0/36bd937216ec521246249be3bf9855081de4c5e06a0c9b4219dbeda50373/importlib_metadata-8.7.0-py3-none-any.whl", hash = "sha256:e5dd1551894c77868a30651cef00984d50e1002d06942a7101d34870c5f02afd", size = 27656, upload-time = "2025-04-27T15:29:00.214Z" }, +] + +[[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.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.11'", + "python_full_version == '3.10.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, +] + +[[package]] +name = "jinja2" +version = "3.1.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115, upload-time = "2025-03-05T20:05:02.478Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" }, +] + +[[package]] +name = "markupsafe" +version = "3.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7e/99/7690b6d4034fffd95959cbe0c02de8deb3098cc577c67bb6a24fe5d7caa7/markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698", size = 80313, upload-time = "2025-09-27T18:37:40.426Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e8/4b/3541d44f3937ba468b75da9eebcae497dcf67adb65caa16760b0a6807ebb/markupsafe-3.0.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2f981d352f04553a7171b8e44369f2af4055f888dfb147d55e42d29e29e74559", size = 11631, upload-time = "2025-09-27T18:36:05.558Z" }, + { url = "https://files.pythonhosted.org/packages/98/1b/fbd8eed11021cabd9226c37342fa6ca4e8a98d8188a8d9b66740494960e4/markupsafe-3.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e1c1493fb6e50ab01d20a22826e57520f1284df32f2d8601fdd90b6304601419", size = 12057, upload-time = "2025-09-27T18:36:07.165Z" }, + { url = "https://files.pythonhosted.org/packages/40/01/e560d658dc0bb8ab762670ece35281dec7b6c1b33f5fbc09ebb57a185519/markupsafe-3.0.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1ba88449deb3de88bd40044603fafffb7bc2b055d626a330323a9ed736661695", size = 22050, upload-time = "2025-09-27T18:36:08.005Z" }, + { url = "https://files.pythonhosted.org/packages/af/cd/ce6e848bbf2c32314c9b237839119c5a564a59725b53157c856e90937b7a/markupsafe-3.0.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f42d0984e947b8adf7dd6dde396e720934d12c506ce84eea8476409563607591", size = 20681, upload-time = "2025-09-27T18:36:08.881Z" }, + { url = "https://files.pythonhosted.org/packages/c9/2a/b5c12c809f1c3045c4d580b035a743d12fcde53cf685dbc44660826308da/markupsafe-3.0.3-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c0c0b3ade1c0b13b936d7970b1d37a57acde9199dc2aecc4c336773e1d86049c", size = 20705, upload-time = "2025-09-27T18:36:10.131Z" }, + { url = "https://files.pythonhosted.org/packages/cf/e3/9427a68c82728d0a88c50f890d0fc072a1484de2f3ac1ad0bfc1a7214fd5/markupsafe-3.0.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:0303439a41979d9e74d18ff5e2dd8c43ed6c6001fd40e5bf2e43f7bd9bbc523f", size = 21524, upload-time = "2025-09-27T18:36:11.324Z" }, + { url = "https://files.pythonhosted.org/packages/bc/36/23578f29e9e582a4d0278e009b38081dbe363c5e7165113fad546918a232/markupsafe-3.0.3-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:d2ee202e79d8ed691ceebae8e0486bd9a2cd4794cec4824e1c99b6f5009502f6", size = 20282, upload-time = "2025-09-27T18:36:12.573Z" }, + { url = "https://files.pythonhosted.org/packages/56/21/dca11354e756ebd03e036bd8ad58d6d7168c80ce1fe5e75218e4945cbab7/markupsafe-3.0.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:177b5253b2834fe3678cb4a5f0059808258584c559193998be2601324fdeafb1", size = 20745, upload-time = "2025-09-27T18:36:13.504Z" }, + { url = "https://files.pythonhosted.org/packages/87/99/faba9369a7ad6e4d10b6a5fbf71fa2a188fe4a593b15f0963b73859a1bbd/markupsafe-3.0.3-cp310-cp310-win32.whl", hash = "sha256:2a15a08b17dd94c53a1da0438822d70ebcd13f8c3a95abe3a9ef9f11a94830aa", size = 14571, upload-time = "2025-09-27T18:36:14.779Z" }, + { url = "https://files.pythonhosted.org/packages/d6/25/55dc3ab959917602c96985cb1253efaa4ff42f71194bddeb61eb7278b8be/markupsafe-3.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:c4ffb7ebf07cfe8931028e3e4c85f0357459a3f9f9490886198848f4fa002ec8", size = 15056, upload-time = "2025-09-27T18:36:16.125Z" }, + { url = "https://files.pythonhosted.org/packages/d0/9e/0a02226640c255d1da0b8d12e24ac2aa6734da68bff14c05dd53b94a0fc3/markupsafe-3.0.3-cp310-cp310-win_arm64.whl", hash = "sha256:e2103a929dfa2fcaf9bb4e7c091983a49c9ac3b19c9061b6d5427dd7d14d81a1", size = 13932, upload-time = "2025-09-27T18:36:17.311Z" }, + { url = "https://files.pythonhosted.org/packages/08/db/fefacb2136439fc8dd20e797950e749aa1f4997ed584c62cfb8ef7c2be0e/markupsafe-3.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1cc7ea17a6824959616c525620e387f6dd30fec8cb44f649e31712db02123dad", size = 11631, upload-time = "2025-09-27T18:36:18.185Z" }, + { url = "https://files.pythonhosted.org/packages/e1/2e/5898933336b61975ce9dc04decbc0a7f2fee78c30353c5efba7f2d6ff27a/markupsafe-3.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4bd4cd07944443f5a265608cc6aab442e4f74dff8088b0dfc8238647b8f6ae9a", size = 12058, upload-time = "2025-09-27T18:36:19.444Z" }, + { url = "https://files.pythonhosted.org/packages/1d/09/adf2df3699d87d1d8184038df46a9c80d78c0148492323f4693df54e17bb/markupsafe-3.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b5420a1d9450023228968e7e6a9ce57f65d148ab56d2313fcd589eee96a7a50", size = 24287, upload-time = "2025-09-27T18:36:20.768Z" }, + { url = "https://files.pythonhosted.org/packages/30/ac/0273f6fcb5f42e314c6d8cd99effae6a5354604d461b8d392b5ec9530a54/markupsafe-3.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0bf2a864d67e76e5c9a34dc26ec616a66b9888e25e7b9460e1c76d3293bd9dbf", size = 22940, upload-time = "2025-09-27T18:36:22.249Z" }, + { url = "https://files.pythonhosted.org/packages/19/ae/31c1be199ef767124c042c6c3e904da327a2f7f0cd63a0337e1eca2967a8/markupsafe-3.0.3-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc51efed119bc9cfdf792cdeaa4d67e8f6fcccab66ed4bfdd6bde3e59bfcbb2f", size = 21887, upload-time = "2025-09-27T18:36:23.535Z" }, + { url = "https://files.pythonhosted.org/packages/b2/76/7edcab99d5349a4532a459e1fe64f0b0467a3365056ae550d3bcf3f79e1e/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:068f375c472b3e7acbe2d5318dea141359e6900156b5b2ba06a30b169086b91a", size = 23692, upload-time = "2025-09-27T18:36:24.823Z" }, + { url = "https://files.pythonhosted.org/packages/a4/28/6e74cdd26d7514849143d69f0bf2399f929c37dc2b31e6829fd2045b2765/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:7be7b61bb172e1ed687f1754f8e7484f1c8019780f6f6b0786e76bb01c2ae115", size = 21471, upload-time = "2025-09-27T18:36:25.95Z" }, + { url = "https://files.pythonhosted.org/packages/62/7e/a145f36a5c2945673e590850a6f8014318d5577ed7e5920a4b3448e0865d/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f9e130248f4462aaa8e2552d547f36ddadbeaa573879158d721bbd33dfe4743a", size = 22923, upload-time = "2025-09-27T18:36:27.109Z" }, + { url = "https://files.pythonhosted.org/packages/0f/62/d9c46a7f5c9adbeeeda52f5b8d802e1094e9717705a645efc71b0913a0a8/markupsafe-3.0.3-cp311-cp311-win32.whl", hash = "sha256:0db14f5dafddbb6d9208827849fad01f1a2609380add406671a26386cdf15a19", size = 14572, upload-time = "2025-09-27T18:36:28.045Z" }, + { url = "https://files.pythonhosted.org/packages/83/8a/4414c03d3f891739326e1783338e48fb49781cc915b2e0ee052aa490d586/markupsafe-3.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:de8a88e63464af587c950061a5e6a67d3632e36df62b986892331d4620a35c01", size = 15077, upload-time = "2025-09-27T18:36:29.025Z" }, + { url = "https://files.pythonhosted.org/packages/35/73/893072b42e6862f319b5207adc9ae06070f095b358655f077f69a35601f0/markupsafe-3.0.3-cp311-cp311-win_arm64.whl", hash = "sha256:3b562dd9e9ea93f13d53989d23a7e775fdfd1066c33494ff43f5418bc8c58a5c", size = 13876, upload-time = "2025-09-27T18:36:29.954Z" }, + { url = "https://files.pythonhosted.org/packages/5a/72/147da192e38635ada20e0a2e1a51cf8823d2119ce8883f7053879c2199b5/markupsafe-3.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d53197da72cc091b024dd97249dfc7794d6a56530370992a5e1a08983ad9230e", size = 11615, upload-time = "2025-09-27T18:36:30.854Z" }, + { url = "https://files.pythonhosted.org/packages/9a/81/7e4e08678a1f98521201c3079f77db69fb552acd56067661f8c2f534a718/markupsafe-3.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1872df69a4de6aead3491198eaf13810b565bdbeec3ae2dc8780f14458ec73ce", size = 12020, upload-time = "2025-09-27T18:36:31.971Z" }, + { url = "https://files.pythonhosted.org/packages/1e/2c/799f4742efc39633a1b54a92eec4082e4f815314869865d876824c257c1e/markupsafe-3.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a7e8ae81ae39e62a41ec302f972ba6ae23a5c5396c8e60113e9066ef893da0d", size = 24332, upload-time = "2025-09-27T18:36:32.813Z" }, + { url = "https://files.pythonhosted.org/packages/3c/2e/8d0c2ab90a8c1d9a24f0399058ab8519a3279d1bd4289511d74e909f060e/markupsafe-3.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d6dd0be5b5b189d31db7cda48b91d7e0a9795f31430b7f271219ab30f1d3ac9d", size = 22947, upload-time = "2025-09-27T18:36:33.86Z" }, + { url = "https://files.pythonhosted.org/packages/2c/54/887f3092a85238093a0b2154bd629c89444f395618842e8b0c41783898ea/markupsafe-3.0.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:94c6f0bb423f739146aec64595853541634bde58b2135f27f61c1ffd1cd4d16a", size = 21962, upload-time = "2025-09-27T18:36:35.099Z" }, + { url = "https://files.pythonhosted.org/packages/c9/2f/336b8c7b6f4a4d95e91119dc8521402461b74a485558d8f238a68312f11c/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:be8813b57049a7dc738189df53d69395eba14fb99345e0a5994914a3864c8a4b", size = 23760, upload-time = "2025-09-27T18:36:36.001Z" }, + { url = "https://files.pythonhosted.org/packages/32/43/67935f2b7e4982ffb50a4d169b724d74b62a3964bc1a9a527f5ac4f1ee2b/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:83891d0e9fb81a825d9a6d61e3f07550ca70a076484292a70fde82c4b807286f", size = 21529, upload-time = "2025-09-27T18:36:36.906Z" }, + { url = "https://files.pythonhosted.org/packages/89/e0/4486f11e51bbba8b0c041098859e869e304d1c261e59244baa3d295d47b7/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:77f0643abe7495da77fb436f50f8dab76dbc6e5fd25d39589a0f1fe6548bfa2b", size = 23015, upload-time = "2025-09-27T18:36:37.868Z" }, + { url = "https://files.pythonhosted.org/packages/2f/e1/78ee7a023dac597a5825441ebd17170785a9dab23de95d2c7508ade94e0e/markupsafe-3.0.3-cp312-cp312-win32.whl", hash = "sha256:d88b440e37a16e651bda4c7c2b930eb586fd15ca7406cb39e211fcff3bf3017d", size = 14540, upload-time = "2025-09-27T18:36:38.761Z" }, + { url = "https://files.pythonhosted.org/packages/aa/5b/bec5aa9bbbb2c946ca2733ef9c4ca91c91b6a24580193e891b5f7dbe8e1e/markupsafe-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:26a5784ded40c9e318cfc2bdb30fe164bdb8665ded9cd64d500a34fb42067b1c", size = 15105, upload-time = "2025-09-27T18:36:39.701Z" }, + { url = "https://files.pythonhosted.org/packages/e5/f1/216fc1bbfd74011693a4fd837e7026152e89c4bcf3e77b6692fba9923123/markupsafe-3.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:35add3b638a5d900e807944a078b51922212fb3dedb01633a8defc4b01a3c85f", size = 13906, upload-time = "2025-09-27T18:36:40.689Z" }, + { url = "https://files.pythonhosted.org/packages/38/2f/907b9c7bbba283e68f20259574b13d005c121a0fa4c175f9bed27c4597ff/markupsafe-3.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795", size = 11622, upload-time = "2025-09-27T18:36:41.777Z" }, + { url = "https://files.pythonhosted.org/packages/9c/d9/5f7756922cdd676869eca1c4e3c0cd0df60ed30199ffd775e319089cb3ed/markupsafe-3.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219", size = 12029, upload-time = "2025-09-27T18:36:43.257Z" }, + { url = "https://files.pythonhosted.org/packages/00/07/575a68c754943058c78f30db02ee03a64b3c638586fba6a6dd56830b30a3/markupsafe-3.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:133a43e73a802c5562be9bbcd03d090aa5a1fe899db609c29e8c8d815c5f6de6", size = 24374, upload-time = "2025-09-27T18:36:44.508Z" }, + { url = "https://files.pythonhosted.org/packages/a9/21/9b05698b46f218fc0e118e1f8168395c65c8a2c750ae2bab54fc4bd4e0e8/markupsafe-3.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676", size = 22980, upload-time = "2025-09-27T18:36:45.385Z" }, + { url = "https://files.pythonhosted.org/packages/7f/71/544260864f893f18b6827315b988c146b559391e6e7e8f7252839b1b846a/markupsafe-3.0.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:509fa21c6deb7a7a273d629cf5ec029bc209d1a51178615ddf718f5918992ab9", size = 21990, upload-time = "2025-09-27T18:36:46.916Z" }, + { url = "https://files.pythonhosted.org/packages/c2/28/b50fc2f74d1ad761af2f5dcce7492648b983d00a65b8c0e0cb457c82ebbe/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4afe79fb3de0b7097d81da19090f4df4f8d3a2b3adaa8764138aac2e44f3af1", size = 23784, upload-time = "2025-09-27T18:36:47.884Z" }, + { url = "https://files.pythonhosted.org/packages/ed/76/104b2aa106a208da8b17a2fb72e033a5a9d7073c68f7e508b94916ed47a9/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:795e7751525cae078558e679d646ae45574b47ed6e7771863fcc079a6171a0fc", size = 21588, upload-time = "2025-09-27T18:36:48.82Z" }, + { url = "https://files.pythonhosted.org/packages/b5/99/16a5eb2d140087ebd97180d95249b00a03aa87e29cc224056274f2e45fd6/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8485f406a96febb5140bfeca44a73e3ce5116b2501ac54fe953e488fb1d03b12", size = 23041, upload-time = "2025-09-27T18:36:49.797Z" }, + { url = "https://files.pythonhosted.org/packages/19/bc/e7140ed90c5d61d77cea142eed9f9c303f4c4806f60a1044c13e3f1471d0/markupsafe-3.0.3-cp313-cp313-win32.whl", hash = "sha256:bdd37121970bfd8be76c5fb069c7751683bdf373db1ed6c010162b2a130248ed", size = 14543, upload-time = "2025-09-27T18:36:51.584Z" }, + { url = "https://files.pythonhosted.org/packages/05/73/c4abe620b841b6b791f2edc248f556900667a5a1cf023a6646967ae98335/markupsafe-3.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5", size = 15113, upload-time = "2025-09-27T18:36:52.537Z" }, + { url = "https://files.pythonhosted.org/packages/f0/3a/fa34a0f7cfef23cf9500d68cb7c32dd64ffd58a12b09225fb03dd37d5b80/markupsafe-3.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:7e68f88e5b8799aa49c85cd116c932a1ac15caaa3f5db09087854d218359e485", size = 13911, upload-time = "2025-09-27T18:36:53.513Z" }, + { url = "https://files.pythonhosted.org/packages/e4/d7/e05cd7efe43a88a17a37b3ae96e79a19e846f3f456fe79c57ca61356ef01/markupsafe-3.0.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:218551f6df4868a8d527e3062d0fb968682fe92054e89978594c28e642c43a73", size = 11658, upload-time = "2025-09-27T18:36:54.819Z" }, + { url = "https://files.pythonhosted.org/packages/99/9e/e412117548182ce2148bdeacdda3bb494260c0b0184360fe0d56389b523b/markupsafe-3.0.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3524b778fe5cfb3452a09d31e7b5adefeea8c5be1d43c4f810ba09f2ceb29d37", size = 12066, upload-time = "2025-09-27T18:36:55.714Z" }, + { url = "https://files.pythonhosted.org/packages/bc/e6/fa0ffcda717ef64a5108eaa7b4f5ed28d56122c9a6d70ab8b72f9f715c80/markupsafe-3.0.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e885a3d1efa2eadc93c894a21770e4bc67899e3543680313b09f139e149ab19", size = 25639, upload-time = "2025-09-27T18:36:56.908Z" }, + { url = "https://files.pythonhosted.org/packages/96/ec/2102e881fe9d25fc16cb4b25d5f5cde50970967ffa5dddafdb771237062d/markupsafe-3.0.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8709b08f4a89aa7586de0aadc8da56180242ee0ada3999749b183aa23df95025", size = 23569, upload-time = "2025-09-27T18:36:57.913Z" }, + { url = "https://files.pythonhosted.org/packages/4b/30/6f2fce1f1f205fc9323255b216ca8a235b15860c34b6798f810f05828e32/markupsafe-3.0.3-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b8512a91625c9b3da6f127803b166b629725e68af71f8184ae7e7d54686a56d6", size = 23284, upload-time = "2025-09-27T18:36:58.833Z" }, + { url = "https://files.pythonhosted.org/packages/58/47/4a0ccea4ab9f5dcb6f79c0236d954acb382202721e704223a8aafa38b5c8/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9b79b7a16f7fedff2495d684f2b59b0457c3b493778c9eed31111be64d58279f", size = 24801, upload-time = "2025-09-27T18:36:59.739Z" }, + { url = "https://files.pythonhosted.org/packages/6a/70/3780e9b72180b6fecb83a4814d84c3bf4b4ae4bf0b19c27196104149734c/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:12c63dfb4a98206f045aa9563db46507995f7ef6d83b2f68eda65c307c6829eb", size = 22769, upload-time = "2025-09-27T18:37:00.719Z" }, + { url = "https://files.pythonhosted.org/packages/98/c5/c03c7f4125180fc215220c035beac6b9cb684bc7a067c84fc69414d315f5/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8f71bc33915be5186016f675cd83a1e08523649b0e33efdb898db577ef5bb009", size = 23642, upload-time = "2025-09-27T18:37:01.673Z" }, + { url = "https://files.pythonhosted.org/packages/80/d6/2d1b89f6ca4bff1036499b1e29a1d02d282259f3681540e16563f27ebc23/markupsafe-3.0.3-cp313-cp313t-win32.whl", hash = "sha256:69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354", size = 14612, upload-time = "2025-09-27T18:37:02.639Z" }, + { url = "https://files.pythonhosted.org/packages/2b/98/e48a4bfba0a0ffcf9925fe2d69240bfaa19c6f7507b8cd09c70684a53c1e/markupsafe-3.0.3-cp313-cp313t-win_amd64.whl", hash = "sha256:1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218", size = 15200, upload-time = "2025-09-27T18:37:03.582Z" }, + { url = "https://files.pythonhosted.org/packages/0e/72/e3cc540f351f316e9ed0f092757459afbc595824ca724cbc5a5d4263713f/markupsafe-3.0.3-cp313-cp313t-win_arm64.whl", hash = "sha256:ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287", size = 13973, upload-time = "2025-09-27T18:37:04.929Z" }, + { url = "https://files.pythonhosted.org/packages/33/8a/8e42d4838cd89b7dde187011e97fe6c3af66d8c044997d2183fbd6d31352/markupsafe-3.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:eaa9599de571d72e2daf60164784109f19978b327a3910d3e9de8c97b5b70cfe", size = 11619, upload-time = "2025-09-27T18:37:06.342Z" }, + { url = "https://files.pythonhosted.org/packages/b5/64/7660f8a4a8e53c924d0fa05dc3a55c9cee10bbd82b11c5afb27d44b096ce/markupsafe-3.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c47a551199eb8eb2121d4f0f15ae0f923d31350ab9280078d1e5f12b249e0026", size = 12029, upload-time = "2025-09-27T18:37:07.213Z" }, + { url = "https://files.pythonhosted.org/packages/da/ef/e648bfd021127bef5fa12e1720ffed0c6cbb8310c8d9bea7266337ff06de/markupsafe-3.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f34c41761022dd093b4b6896d4810782ffbabe30f2d443ff5f083e0cbbb8c737", size = 24408, upload-time = "2025-09-27T18:37:09.572Z" }, + { url = "https://files.pythonhosted.org/packages/41/3c/a36c2450754618e62008bf7435ccb0f88053e07592e6028a34776213d877/markupsafe-3.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:457a69a9577064c05a97c41f4e65148652db078a3a509039e64d3467b9e7ef97", size = 23005, upload-time = "2025-09-27T18:37:10.58Z" }, + { url = "https://files.pythonhosted.org/packages/bc/20/b7fdf89a8456b099837cd1dc21974632a02a999ec9bf7ca3e490aacd98e7/markupsafe-3.0.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e8afc3f2ccfa24215f8cb28dcf43f0113ac3c37c2f0f0806d8c70e4228c5cf4d", size = 22048, upload-time = "2025-09-27T18:37:11.547Z" }, + { url = "https://files.pythonhosted.org/packages/9a/a7/591f592afdc734f47db08a75793a55d7fbcc6902a723ae4cfbab61010cc5/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ec15a59cf5af7be74194f7ab02d0f59a62bdcf1a537677ce67a2537c9b87fcda", size = 23821, upload-time = "2025-09-27T18:37:12.48Z" }, + { url = "https://files.pythonhosted.org/packages/7d/33/45b24e4f44195b26521bc6f1a82197118f74df348556594bd2262bda1038/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:0eb9ff8191e8498cca014656ae6b8d61f39da5f95b488805da4bb029cccbfbaf", size = 21606, upload-time = "2025-09-27T18:37:13.485Z" }, + { url = "https://files.pythonhosted.org/packages/ff/0e/53dfaca23a69fbfbbf17a4b64072090e70717344c52eaaaa9c5ddff1e5f0/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2713baf880df847f2bece4230d4d094280f4e67b1e813eec43b4c0e144a34ffe", size = 23043, upload-time = "2025-09-27T18:37:14.408Z" }, + { url = "https://files.pythonhosted.org/packages/46/11/f333a06fc16236d5238bfe74daccbca41459dcd8d1fa952e8fbd5dccfb70/markupsafe-3.0.3-cp314-cp314-win32.whl", hash = "sha256:729586769a26dbceff69f7a7dbbf59ab6572b99d94576a5592625d5b411576b9", size = 14747, upload-time = "2025-09-27T18:37:15.36Z" }, + { url = "https://files.pythonhosted.org/packages/28/52/182836104b33b444e400b14f797212f720cbc9ed6ba34c800639d154e821/markupsafe-3.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:bdc919ead48f234740ad807933cdf545180bfbe9342c2bb451556db2ed958581", size = 15341, upload-time = "2025-09-27T18:37:16.496Z" }, + { url = "https://files.pythonhosted.org/packages/6f/18/acf23e91bd94fd7b3031558b1f013adfa21a8e407a3fdb32745538730382/markupsafe-3.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:5a7d5dc5140555cf21a6fefbdbf8723f06fcd2f63ef108f2854de715e4422cb4", size = 14073, upload-time = "2025-09-27T18:37:17.476Z" }, + { url = "https://files.pythonhosted.org/packages/3c/f0/57689aa4076e1b43b15fdfa646b04653969d50cf30c32a102762be2485da/markupsafe-3.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:1353ef0c1b138e1907ae78e2f6c63ff67501122006b0f9abad68fda5f4ffc6ab", size = 11661, upload-time = "2025-09-27T18:37:18.453Z" }, + { url = "https://files.pythonhosted.org/packages/89/c3/2e67a7ca217c6912985ec766c6393b636fb0c2344443ff9d91404dc4c79f/markupsafe-3.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1085e7fbddd3be5f89cc898938f42c0b3c711fdcb37d75221de2666af647c175", size = 12069, upload-time = "2025-09-27T18:37:19.332Z" }, + { url = "https://files.pythonhosted.org/packages/f0/00/be561dce4e6ca66b15276e184ce4b8aec61fe83662cce2f7d72bd3249d28/markupsafe-3.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b52b4fb9df4eb9ae465f8d0c228a00624de2334f216f178a995ccdcf82c4634", size = 25670, upload-time = "2025-09-27T18:37:20.245Z" }, + { url = "https://files.pythonhosted.org/packages/50/09/c419f6f5a92e5fadde27efd190eca90f05e1261b10dbd8cbcb39cd8ea1dc/markupsafe-3.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50", size = 23598, upload-time = "2025-09-27T18:37:21.177Z" }, + { url = "https://files.pythonhosted.org/packages/22/44/a0681611106e0b2921b3033fc19bc53323e0b50bc70cffdd19f7d679bb66/markupsafe-3.0.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f190daf01f13c72eac4efd5c430a8de82489d9cff23c364c3ea822545032993e", size = 23261, upload-time = "2025-09-27T18:37:22.167Z" }, + { url = "https://files.pythonhosted.org/packages/5f/57/1b0b3f100259dc9fffe780cfb60d4be71375510e435efec3d116b6436d43/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e56b7d45a839a697b5eb268c82a71bd8c7f6c94d6fd50c3d577fa39a9f1409f5", size = 24835, upload-time = "2025-09-27T18:37:23.296Z" }, + { url = "https://files.pythonhosted.org/packages/26/6a/4bf6d0c97c4920f1597cc14dd720705eca0bf7c787aebc6bb4d1bead5388/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:f3e98bb3798ead92273dc0e5fd0f31ade220f59a266ffd8a4f6065e0a3ce0523", size = 22733, upload-time = "2025-09-27T18:37:24.237Z" }, + { url = "https://files.pythonhosted.org/packages/14/c7/ca723101509b518797fedc2fdf79ba57f886b4aca8a7d31857ba3ee8281f/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5678211cb9333a6468fb8d8be0305520aa073f50d17f089b5b4b477ea6e67fdc", size = 23672, upload-time = "2025-09-27T18:37:25.271Z" }, + { url = "https://files.pythonhosted.org/packages/fb/df/5bd7a48c256faecd1d36edc13133e51397e41b73bb77e1a69deab746ebac/markupsafe-3.0.3-cp314-cp314t-win32.whl", hash = "sha256:915c04ba3851909ce68ccc2b8e2cd691618c4dc4c4232fb7982bca3f41fd8c3d", size = 14819, upload-time = "2025-09-27T18:37:26.285Z" }, + { url = "https://files.pythonhosted.org/packages/1a/8a/0402ba61a2f16038b48b39bccca271134be00c5c9f0f623208399333c448/markupsafe-3.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4faffd047e07c38848ce017e8725090413cd80cbc23d86e55c587bf979e579c9", size = 15426, upload-time = "2025-09-27T18:37:27.316Z" }, + { url = "https://files.pythonhosted.org/packages/70/bc/6f1c2f612465f5fa89b95bead1f44dcb607670fd42891d8fdcd5d039f4f4/markupsafe-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa", size = 14146, upload-time = "2025-09-27T18:37:28.327Z" }, + { url = "https://files.pythonhosted.org/packages/56/23/0d8c13a44bde9154821586520840643467aee574d8ce79a17da539ee7fed/markupsafe-3.0.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:15d939a21d546304880945ca1ecb8a039db6b4dc49b2c5a400387cdae6a62e26", size = 11623, upload-time = "2025-09-27T18:37:29.296Z" }, + { url = "https://files.pythonhosted.org/packages/fd/23/07a2cb9a8045d5f3f0890a8c3bc0859d7a47bfd9a560b563899bec7b72ed/markupsafe-3.0.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f71a396b3bf33ecaa1626c255855702aca4d3d9fea5e051b41ac59a9c1c41edc", size = 12049, upload-time = "2025-09-27T18:37:30.234Z" }, + { url = "https://files.pythonhosted.org/packages/bc/e4/6be85eb81503f8e11b61c0b6369b6e077dcf0a74adbd9ebf6b349937b4e9/markupsafe-3.0.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0f4b68347f8c5eab4a13419215bdfd7f8c9b19f2b25520968adfad23eb0ce60c", size = 21923, upload-time = "2025-09-27T18:37:31.177Z" }, + { url = "https://files.pythonhosted.org/packages/6f/bc/4dc914ead3fe6ddaef035341fee0fc956949bbd27335b611829292b89ee2/markupsafe-3.0.3-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e8fc20152abba6b83724d7ff268c249fa196d8259ff481f3b1476383f8f24e42", size = 20543, upload-time = "2025-09-27T18:37:32.168Z" }, + { url = "https://files.pythonhosted.org/packages/89/6e/5fe81fbcfba4aef4093d5f856e5c774ec2057946052d18d168219b7bd9f9/markupsafe-3.0.3-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:949b8d66bc381ee8b007cd945914c721d9aba8e27f71959d750a46f7c282b20b", size = 20585, upload-time = "2025-09-27T18:37:33.166Z" }, + { url = "https://files.pythonhosted.org/packages/f6/f6/e0e5a3d3ae9c4020f696cd055f940ef86b64fe88de26f3a0308b9d3d048c/markupsafe-3.0.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:3537e01efc9d4dccdf77221fb1cb3b8e1a38d5428920e0657ce299b20324d758", size = 21387, upload-time = "2025-09-27T18:37:34.185Z" }, + { url = "https://files.pythonhosted.org/packages/c8/25/651753ef4dea08ea790f4fbb65146a9a44a014986996ca40102e237aa49a/markupsafe-3.0.3-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:591ae9f2a647529ca990bc681daebdd52c8791ff06c2bfa05b65163e28102ef2", size = 20133, upload-time = "2025-09-27T18:37:35.138Z" }, + { url = "https://files.pythonhosted.org/packages/dc/0a/c3cf2b4fef5f0426e8a6d7fce3cb966a17817c568ce59d76b92a233fdbec/markupsafe-3.0.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:a320721ab5a1aba0a233739394eb907f8c8da5c98c9181d1161e77a0c8e36f2d", size = 20588, upload-time = "2025-09-27T18:37:36.096Z" }, + { url = "https://files.pythonhosted.org/packages/cd/1b/a7782984844bd519ad4ffdbebbba2671ec5d0ebbeac34736c15fb86399e8/markupsafe-3.0.3-cp39-cp39-win32.whl", hash = "sha256:df2449253ef108a379b8b5d6b43f4b1a8e81a061d6537becd5582fba5f9196d7", size = 14566, upload-time = "2025-09-27T18:37:37.09Z" }, + { url = "https://files.pythonhosted.org/packages/18/1f/8d9c20e1c9440e215a44be5ab64359e207fcb4f675543f1cf9a2a7f648d0/markupsafe-3.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:7c3fb7d25180895632e5d3148dbdc29ea38ccb7fd210aa27acbd1201a1902c6e", size = 15053, upload-time = "2025-09-27T18:37:38.054Z" }, + { url = "https://files.pythonhosted.org/packages/4e/d3/fe08482b5cd995033556d45041a4f4e76e7f0521112a9c9991d40d39825f/markupsafe-3.0.3-cp39-cp39-win_arm64.whl", hash = "sha256:38664109c14ffc9e7437e86b4dceb442b0096dfe3541d7864d9cbe1da4cf36c8", size = 13928, upload-time = "2025-09-27T18:37:39.037Z" }, +] + +[[package]] +name = "mypy" +version = "1.18.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mypy-extensions" }, + { name = "pathspec" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c0/77/8f0d0001ffad290cef2f7f216f96c814866248a0b92a722365ed54648e7e/mypy-1.18.2.tar.gz", hash = "sha256:06a398102a5f203d7477b2923dda3634c36727fa5c237d8f859ef90c42a9924b", size = 3448846, upload-time = "2025-09-19T00:11:10.519Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/03/6f/657961a0743cff32e6c0611b63ff1c1970a0b482ace35b069203bf705187/mypy-1.18.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c1eab0cf6294dafe397c261a75f96dc2c31bffe3b944faa24db5def4e2b0f77c", size = 12807973, upload-time = "2025-09-19T00:10:35.282Z" }, + { url = "https://files.pythonhosted.org/packages/10/e9/420822d4f661f13ca8900f5fa239b40ee3be8b62b32f3357df9a3045a08b/mypy-1.18.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:7a780ca61fc239e4865968ebc5240bb3bf610ef59ac398de9a7421b54e4a207e", size = 11896527, upload-time = "2025-09-19T00:10:55.791Z" }, + { url = "https://files.pythonhosted.org/packages/aa/73/a05b2bbaa7005f4642fcfe40fb73f2b4fb6bb44229bd585b5878e9a87ef8/mypy-1.18.2-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:448acd386266989ef11662ce3c8011fd2a7b632e0ec7d61a98edd8e27472225b", size = 12507004, upload-time = "2025-09-19T00:11:05.411Z" }, + { url = "https://files.pythonhosted.org/packages/4f/01/f6e4b9f0d031c11ccbd6f17da26564f3a0f3c4155af344006434b0a05a9d/mypy-1.18.2-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f9e171c465ad3901dc652643ee4bffa8e9fef4d7d0eece23b428908c77a76a66", size = 13245947, upload-time = "2025-09-19T00:10:46.923Z" }, + { url = "https://files.pythonhosted.org/packages/d7/97/19727e7499bfa1ae0773d06afd30ac66a58ed7437d940c70548634b24185/mypy-1.18.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:592ec214750bc00741af1f80cbf96b5013d81486b7bb24cb052382c19e40b428", size = 13499217, upload-time = "2025-09-19T00:09:39.472Z" }, + { url = "https://files.pythonhosted.org/packages/9f/4f/90dc8c15c1441bf31cf0f9918bb077e452618708199e530f4cbd5cede6ff/mypy-1.18.2-cp310-cp310-win_amd64.whl", hash = "sha256:7fb95f97199ea11769ebe3638c29b550b5221e997c63b14ef93d2e971606ebed", size = 9766753, upload-time = "2025-09-19T00:10:49.161Z" }, + { url = "https://files.pythonhosted.org/packages/88/87/cafd3ae563f88f94eec33f35ff722d043e09832ea8530ef149ec1efbaf08/mypy-1.18.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:807d9315ab9d464125aa9fcf6d84fde6e1dc67da0b6f80e7405506b8ac72bc7f", size = 12731198, upload-time = "2025-09-19T00:09:44.857Z" }, + { url = "https://files.pythonhosted.org/packages/0f/e0/1e96c3d4266a06d4b0197ace5356d67d937d8358e2ee3ffac71faa843724/mypy-1.18.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:776bb00de1778caf4db739c6e83919c1d85a448f71979b6a0edd774ea8399341", size = 11817879, upload-time = "2025-09-19T00:09:47.131Z" }, + { url = "https://files.pythonhosted.org/packages/72/ef/0c9ba89eb03453e76bdac5a78b08260a848c7bfc5d6603634774d9cd9525/mypy-1.18.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1379451880512ffce14505493bd9fe469e0697543717298242574882cf8cdb8d", size = 12427292, upload-time = "2025-09-19T00:10:22.472Z" }, + { url = "https://files.pythonhosted.org/packages/1a/52/ec4a061dd599eb8179d5411d99775bec2a20542505988f40fc2fee781068/mypy-1.18.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1331eb7fd110d60c24999893320967594ff84c38ac6d19e0a76c5fd809a84c86", size = 13163750, upload-time = "2025-09-19T00:09:51.472Z" }, + { url = "https://files.pythonhosted.org/packages/c4/5f/2cf2ceb3b36372d51568f2208c021870fe7834cf3186b653ac6446511839/mypy-1.18.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3ca30b50a51e7ba93b00422e486cbb124f1c56a535e20eff7b2d6ab72b3b2e37", size = 13351827, upload-time = "2025-09-19T00:09:58.311Z" }, + { url = "https://files.pythonhosted.org/packages/c8/7d/2697b930179e7277529eaaec1513f8de622818696857f689e4a5432e5e27/mypy-1.18.2-cp311-cp311-win_amd64.whl", hash = "sha256:664dc726e67fa54e14536f6e1224bcfce1d9e5ac02426d2326e2bb4e081d1ce8", size = 9757983, upload-time = "2025-09-19T00:10:09.071Z" }, + { url = "https://files.pythonhosted.org/packages/07/06/dfdd2bc60c66611dd8335f463818514733bc763e4760dee289dcc33df709/mypy-1.18.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:33eca32dd124b29400c31d7cf784e795b050ace0e1f91b8dc035672725617e34", size = 12908273, upload-time = "2025-09-19T00:10:58.321Z" }, + { url = "https://files.pythonhosted.org/packages/81/14/6a9de6d13a122d5608e1a04130724caf9170333ac5a924e10f670687d3eb/mypy-1.18.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a3c47adf30d65e89b2dcd2fa32f3aeb5e94ca970d2c15fcb25e297871c8e4764", size = 11920910, upload-time = "2025-09-19T00:10:20.043Z" }, + { url = "https://files.pythonhosted.org/packages/5f/a9/b29de53e42f18e8cc547e38daa9dfa132ffdc64f7250e353f5c8cdd44bee/mypy-1.18.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5d6c838e831a062f5f29d11c9057c6009f60cb294fea33a98422688181fe2893", size = 12465585, upload-time = "2025-09-19T00:10:33.005Z" }, + { url = "https://files.pythonhosted.org/packages/77/ae/6c3d2c7c61ff21f2bee938c917616c92ebf852f015fb55917fd6e2811db2/mypy-1.18.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01199871b6110a2ce984bde85acd481232d17413868c9807e95c1b0739a58914", size = 13348562, upload-time = "2025-09-19T00:10:11.51Z" }, + { url = "https://files.pythonhosted.org/packages/4d/31/aec68ab3b4aebdf8f36d191b0685d99faa899ab990753ca0fee60fb99511/mypy-1.18.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a2afc0fa0b0e91b4599ddfe0f91e2c26c2b5a5ab263737e998d6817874c5f7c8", size = 13533296, upload-time = "2025-09-19T00:10:06.568Z" }, + { url = "https://files.pythonhosted.org/packages/9f/83/abcb3ad9478fca3ebeb6a5358bb0b22c95ea42b43b7789c7fb1297ca44f4/mypy-1.18.2-cp312-cp312-win_amd64.whl", hash = "sha256:d8068d0afe682c7c4897c0f7ce84ea77f6de953262b12d07038f4d296d547074", size = 9828828, upload-time = "2025-09-19T00:10:28.203Z" }, + { url = "https://files.pythonhosted.org/packages/5f/04/7f462e6fbba87a72bc8097b93f6842499c428a6ff0c81dd46948d175afe8/mypy-1.18.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:07b8b0f580ca6d289e69209ec9d3911b4a26e5abfde32228a288eb79df129fcc", size = 12898728, upload-time = "2025-09-19T00:10:01.33Z" }, + { url = "https://files.pythonhosted.org/packages/99/5b/61ed4efb64f1871b41fd0b82d29a64640f3516078f6c7905b68ab1ad8b13/mypy-1.18.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ed4482847168439651d3feee5833ccedbf6657e964572706a2adb1f7fa4dfe2e", size = 11910758, upload-time = "2025-09-19T00:10:42.607Z" }, + { url = "https://files.pythonhosted.org/packages/3c/46/d297d4b683cc89a6e4108c4250a6a6b717f5fa96e1a30a7944a6da44da35/mypy-1.18.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c3ad2afadd1e9fea5cf99a45a822346971ede8685cc581ed9cd4d42eaf940986", size = 12475342, upload-time = "2025-09-19T00:11:00.371Z" }, + { url = "https://files.pythonhosted.org/packages/83/45/4798f4d00df13eae3bfdf726c9244bcb495ab5bd588c0eed93a2f2dd67f3/mypy-1.18.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a431a6f1ef14cf8c144c6b14793a23ec4eae3db28277c358136e79d7d062f62d", size = 13338709, upload-time = "2025-09-19T00:11:03.358Z" }, + { url = "https://files.pythonhosted.org/packages/d7/09/479f7358d9625172521a87a9271ddd2441e1dab16a09708f056e97007207/mypy-1.18.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7ab28cc197f1dd77a67e1c6f35cd1f8e8b73ed2217e4fc005f9e6a504e46e7ba", size = 13529806, upload-time = "2025-09-19T00:10:26.073Z" }, + { url = "https://files.pythonhosted.org/packages/71/cf/ac0f2c7e9d0ea3c75cd99dff7aec1c9df4a1376537cb90e4c882267ee7e9/mypy-1.18.2-cp313-cp313-win_amd64.whl", hash = "sha256:0e2785a84b34a72ba55fb5daf079a1003a34c05b22238da94fcae2bbe46f3544", size = 9833262, upload-time = "2025-09-19T00:10:40.035Z" }, + { url = "https://files.pythonhosted.org/packages/5a/0c/7d5300883da16f0063ae53996358758b2a2df2a09c72a5061fa79a1f5006/mypy-1.18.2-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:62f0e1e988ad41c2a110edde6c398383a889d95b36b3e60bcf155f5164c4fdce", size = 12893775, upload-time = "2025-09-19T00:10:03.814Z" }, + { url = "https://files.pythonhosted.org/packages/50/df/2cffbf25737bdb236f60c973edf62e3e7b4ee1c25b6878629e88e2cde967/mypy-1.18.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:8795a039bab805ff0c1dfdb8cd3344642c2b99b8e439d057aba30850b8d3423d", size = 11936852, upload-time = "2025-09-19T00:10:51.631Z" }, + { url = "https://files.pythonhosted.org/packages/be/50/34059de13dd269227fb4a03be1faee6e2a4b04a2051c82ac0a0b5a773c9a/mypy-1.18.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6ca1e64b24a700ab5ce10133f7ccd956a04715463d30498e64ea8715236f9c9c", size = 12480242, upload-time = "2025-09-19T00:11:07.955Z" }, + { url = "https://files.pythonhosted.org/packages/5b/11/040983fad5132d85914c874a2836252bbc57832065548885b5bb5b0d4359/mypy-1.18.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d924eef3795cc89fecf6bedc6ed32b33ac13e8321344f6ddbf8ee89f706c05cb", size = 13326683, upload-time = "2025-09-19T00:09:55.572Z" }, + { url = "https://files.pythonhosted.org/packages/e9/ba/89b2901dd77414dd7a8c8729985832a5735053be15b744c18e4586e506ef/mypy-1.18.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:20c02215a080e3a2be3aa50506c67242df1c151eaba0dcbc1e4e557922a26075", size = 13514749, upload-time = "2025-09-19T00:10:44.827Z" }, + { url = "https://files.pythonhosted.org/packages/25/bc/cc98767cffd6b2928ba680f3e5bc969c4152bf7c2d83f92f5a504b92b0eb/mypy-1.18.2-cp314-cp314-win_amd64.whl", hash = "sha256:749b5f83198f1ca64345603118a6f01a4e99ad4bf9d103ddc5a3200cc4614adf", size = 9982959, upload-time = "2025-09-19T00:10:37.344Z" }, + { url = "https://files.pythonhosted.org/packages/3f/a6/490ff491d8ecddf8ab91762d4f67635040202f76a44171420bcbe38ceee5/mypy-1.18.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:25a9c8fb67b00599f839cf472713f54249a62efd53a54b565eb61956a7e3296b", size = 12807230, upload-time = "2025-09-19T00:09:49.471Z" }, + { url = "https://files.pythonhosted.org/packages/eb/2e/60076fc829645d167ece9e80db9e8375648d210dab44cc98beb5b322a826/mypy-1.18.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c2b9c7e284ee20e7598d6f42e13ca40b4928e6957ed6813d1ab6348aa3f47133", size = 11895666, upload-time = "2025-09-19T00:10:53.678Z" }, + { url = "https://files.pythonhosted.org/packages/97/4a/1e2880a2a5dda4dc8d9ecd1a7e7606bc0b0e14813637eeda40c38624e037/mypy-1.18.2-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d6985ed057513e344e43a26cc1cd815c7a94602fb6a3130a34798625bc2f07b6", size = 12499608, upload-time = "2025-09-19T00:09:36.204Z" }, + { url = "https://files.pythonhosted.org/packages/00/81/a117f1b73a3015b076b20246b1f341c34a578ebd9662848c6b80ad5c4138/mypy-1.18.2-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:22f27105f1525ec024b5c630c0b9f36d5c1cc4d447d61fe51ff4bd60633f47ac", size = 13244551, upload-time = "2025-09-19T00:10:17.531Z" }, + { url = "https://files.pythonhosted.org/packages/9b/61/b9f48e1714ce87c7bf0358eb93f60663740ebb08f9ea886ffc670cea7933/mypy-1.18.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:030c52d0ea8144e721e49b1f68391e39553d7451f0c3f8a7565b59e19fcb608b", size = 13491552, upload-time = "2025-09-19T00:10:13.753Z" }, + { url = "https://files.pythonhosted.org/packages/c9/66/b2c0af3b684fa80d1b27501a8bdd3d2daa467ea3992a8aa612f5ca17c2db/mypy-1.18.2-cp39-cp39-win_amd64.whl", hash = "sha256:aa5e07ac1a60a253445797e42b8b2963c9675563a94f11291ab40718b016a7a0", size = 9765635, upload-time = "2025-09-19T00:10:30.993Z" }, + { url = "https://files.pythonhosted.org/packages/87/e3/be76d87158ebafa0309946c4a73831974d4d6ab4f4ef40c3b53a385a66fd/mypy-1.18.2-py3-none-any.whl", hash = "sha256:22a1748707dd62b58d2ae53562ffc4d7f8bcc727e8ac7cbc69c053ddc874d47e", size = 2352367, upload-time = "2025-09-19T00:10:15.489Z" }, +] + +[[package]] +name = "mypy-extensions" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/6e/371856a3fb9d31ca8dac321cda606860fa4548858c0cc45d9d1d4ca2628b/mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558", size = 6343, upload-time = "2025-04-22T14:54:24.164Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963, upload-time = "2025-04-22T14:54:22.983Z" }, +] + +[[package]] +name = "nodeenv" +version = "1.9.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/43/16/fc88b08840de0e0a72a2f9d8c6bae36be573e475a6326ae854bcc549fc45/nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f", size = 47437, upload-time = "2024-06-04T18:44:11.171Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d2/1d/1b658dbd2b9fa9c4c9f32accbfc0205d532c8c6194dc0f2a4c0428e7128a/nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9", size = 22314, upload-time = "2024-06-04T18:44:08.352Z" }, +] + +[[package]] +name = "noise" +version = "1.2.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/18/29/bb830ee6d934311e17a7a4fa1368faf3e73fbb09c0d80fc44e41828df177/noise-1.2.2.tar.gz", hash = "sha256:57a2797436574391ff63a111e852e53a4164ecd81ad23639641743cd1a209b65", size = 125615, upload-time = "2015-03-29T02:57:45.115Z" } + +[[package]] +name = "numpy" +version = "2.0.2" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.10'", +] +sdist = { url = "https://files.pythonhosted.org/packages/a9/75/10dd1f8116a8b796cb2c737b674e02d02e80454bda953fa7e65d8c12b016/numpy-2.0.2.tar.gz", hash = "sha256:883c987dee1880e2a864ab0dc9892292582510604156762362d9326444636e78", size = 18902015, upload-time = "2024-08-26T20:19:40.945Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/21/91/3495b3237510f79f5d81f2508f9f13fea78ebfdf07538fc7444badda173d/numpy-2.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:51129a29dbe56f9ca83438b706e2e69a39892b5eda6cedcb6b0c9fdc9b0d3ece", size = 21165245, upload-time = "2024-08-26T20:04:14.625Z" }, + { url = "https://files.pythonhosted.org/packages/05/33/26178c7d437a87082d11019292dce6d3fe6f0e9026b7b2309cbf3e489b1d/numpy-2.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f15975dfec0cf2239224d80e32c3170b1d168335eaedee69da84fbe9f1f9cd04", size = 13738540, upload-time = "2024-08-26T20:04:36.784Z" }, + { url = "https://files.pythonhosted.org/packages/ec/31/cc46e13bf07644efc7a4bf68df2df5fb2a1a88d0cd0da9ddc84dc0033e51/numpy-2.0.2-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:8c5713284ce4e282544c68d1c3b2c7161d38c256d2eefc93c1d683cf47683e66", size = 5300623, upload-time = "2024-08-26T20:04:46.491Z" }, + { url = "https://files.pythonhosted.org/packages/6e/16/7bfcebf27bb4f9d7ec67332ffebee4d1bf085c84246552d52dbb548600e7/numpy-2.0.2-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:becfae3ddd30736fe1889a37f1f580e245ba79a5855bff5f2a29cb3ccc22dd7b", size = 6901774, upload-time = "2024-08-26T20:04:58.173Z" }, + { url = "https://files.pythonhosted.org/packages/f9/a3/561c531c0e8bf082c5bef509d00d56f82e0ea7e1e3e3a7fc8fa78742a6e5/numpy-2.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2da5960c3cf0df7eafefd806d4e612c5e19358de82cb3c343631188991566ccd", size = 13907081, upload-time = "2024-08-26T20:05:19.098Z" }, + { url = "https://files.pythonhosted.org/packages/fa/66/f7177ab331876200ac7563a580140643d1179c8b4b6a6b0fc9838de2a9b8/numpy-2.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:496f71341824ed9f3d2fd36cf3ac57ae2e0165c143b55c3a035ee219413f3318", size = 19523451, upload-time = "2024-08-26T20:05:47.479Z" }, + { url = "https://files.pythonhosted.org/packages/25/7f/0b209498009ad6453e4efc2c65bcdf0ae08a182b2b7877d7ab38a92dc542/numpy-2.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a61ec659f68ae254e4d237816e33171497e978140353c0c2038d46e63282d0c8", size = 19927572, upload-time = "2024-08-26T20:06:17.137Z" }, + { url = "https://files.pythonhosted.org/packages/3e/df/2619393b1e1b565cd2d4c4403bdd979621e2c4dea1f8532754b2598ed63b/numpy-2.0.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d731a1c6116ba289c1e9ee714b08a8ff882944d4ad631fd411106a30f083c326", size = 14400722, upload-time = "2024-08-26T20:06:39.16Z" }, + { url = "https://files.pythonhosted.org/packages/22/ad/77e921b9f256d5da36424ffb711ae79ca3f451ff8489eeca544d0701d74a/numpy-2.0.2-cp310-cp310-win32.whl", hash = "sha256:984d96121c9f9616cd33fbd0618b7f08e0cfc9600a7ee1d6fd9b239186d19d97", size = 6472170, upload-time = "2024-08-26T20:06:50.361Z" }, + { url = "https://files.pythonhosted.org/packages/10/05/3442317535028bc29cf0c0dd4c191a4481e8376e9f0db6bcf29703cadae6/numpy-2.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:c7b0be4ef08607dd04da4092faee0b86607f111d5ae68036f16cc787e250a131", size = 15905558, upload-time = "2024-08-26T20:07:13.881Z" }, + { url = "https://files.pythonhosted.org/packages/8b/cf/034500fb83041aa0286e0fb16e7c76e5c8b67c0711bb6e9e9737a717d5fe/numpy-2.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:49ca4decb342d66018b01932139c0961a8f9ddc7589611158cb3c27cbcf76448", size = 21169137, upload-time = "2024-08-26T20:07:45.345Z" }, + { url = "https://files.pythonhosted.org/packages/4a/d9/32de45561811a4b87fbdee23b5797394e3d1504b4a7cf40c10199848893e/numpy-2.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:11a76c372d1d37437857280aa142086476136a8c0f373b2e648ab2c8f18fb195", size = 13703552, upload-time = "2024-08-26T20:08:06.666Z" }, + { url = "https://files.pythonhosted.org/packages/c1/ca/2f384720020c7b244d22508cb7ab23d95f179fcfff33c31a6eeba8d6c512/numpy-2.0.2-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:807ec44583fd708a21d4a11d94aedf2f4f3c3719035c76a2bbe1fe8e217bdc57", size = 5298957, upload-time = "2024-08-26T20:08:15.83Z" }, + { url = "https://files.pythonhosted.org/packages/0e/78/a3e4f9fb6aa4e6fdca0c5428e8ba039408514388cf62d89651aade838269/numpy-2.0.2-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:8cafab480740e22f8d833acefed5cc87ce276f4ece12fdaa2e8903db2f82897a", size = 6905573, upload-time = "2024-08-26T20:08:27.185Z" }, + { url = "https://files.pythonhosted.org/packages/a0/72/cfc3a1beb2caf4efc9d0b38a15fe34025230da27e1c08cc2eb9bfb1c7231/numpy-2.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a15f476a45e6e5a3a79d8a14e62161d27ad897381fecfa4a09ed5322f2085669", size = 13914330, upload-time = "2024-08-26T20:08:48.058Z" }, + { url = "https://files.pythonhosted.org/packages/ba/a8/c17acf65a931ce551fee11b72e8de63bf7e8a6f0e21add4c937c83563538/numpy-2.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:13e689d772146140a252c3a28501da66dfecd77490b498b168b501835041f951", size = 19534895, upload-time = "2024-08-26T20:09:16.536Z" }, + { url = "https://files.pythonhosted.org/packages/ba/86/8767f3d54f6ae0165749f84648da9dcc8cd78ab65d415494962c86fac80f/numpy-2.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9ea91dfb7c3d1c56a0e55657c0afb38cf1eeae4544c208dc465c3c9f3a7c09f9", size = 19937253, upload-time = "2024-08-26T20:09:46.263Z" }, + { url = "https://files.pythonhosted.org/packages/df/87/f76450e6e1c14e5bb1eae6836478b1028e096fd02e85c1c37674606ab752/numpy-2.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c1c9307701fec8f3f7a1e6711f9089c06e6284b3afbbcd259f7791282d660a15", size = 14414074, upload-time = "2024-08-26T20:10:08.483Z" }, + { url = "https://files.pythonhosted.org/packages/5c/ca/0f0f328e1e59f73754f06e1adfb909de43726d4f24c6a3f8805f34f2b0fa/numpy-2.0.2-cp311-cp311-win32.whl", hash = "sha256:a392a68bd329eafac5817e5aefeb39038c48b671afd242710b451e76090e81f4", size = 6470640, upload-time = "2024-08-26T20:10:19.732Z" }, + { url = "https://files.pythonhosted.org/packages/eb/57/3a3f14d3a759dcf9bf6e9eda905794726b758819df4663f217d658a58695/numpy-2.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:286cd40ce2b7d652a6f22efdfc6d1edf879440e53e76a75955bc0c826c7e64dc", size = 15910230, upload-time = "2024-08-26T20:10:43.413Z" }, + { url = "https://files.pythonhosted.org/packages/45/40/2e117be60ec50d98fa08c2f8c48e09b3edea93cfcabd5a9ff6925d54b1c2/numpy-2.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:df55d490dea7934f330006d0f81e8551ba6010a5bf035a249ef61a94f21c500b", size = 20895803, upload-time = "2024-08-26T20:11:13.916Z" }, + { url = "https://files.pythonhosted.org/packages/46/92/1b8b8dee833f53cef3e0a3f69b2374467789e0bb7399689582314df02651/numpy-2.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8df823f570d9adf0978347d1f926b2a867d5608f434a7cff7f7908c6570dcf5e", size = 13471835, upload-time = "2024-08-26T20:11:34.779Z" }, + { url = "https://files.pythonhosted.org/packages/7f/19/e2793bde475f1edaea6945be141aef6c8b4c669b90c90a300a8954d08f0a/numpy-2.0.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:9a92ae5c14811e390f3767053ff54eaee3bf84576d99a2456391401323f4ec2c", size = 5038499, upload-time = "2024-08-26T20:11:43.902Z" }, + { url = "https://files.pythonhosted.org/packages/e3/ff/ddf6dac2ff0dd50a7327bcdba45cb0264d0e96bb44d33324853f781a8f3c/numpy-2.0.2-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:a842d573724391493a97a62ebbb8e731f8a5dcc5d285dfc99141ca15a3302d0c", size = 6633497, upload-time = "2024-08-26T20:11:55.09Z" }, + { url = "https://files.pythonhosted.org/packages/72/21/67f36eac8e2d2cd652a2e69595a54128297cdcb1ff3931cfc87838874bd4/numpy-2.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c05e238064fc0610c840d1cf6a13bf63d7e391717d247f1bf0318172e759e692", size = 13621158, upload-time = "2024-08-26T20:12:14.95Z" }, + { url = "https://files.pythonhosted.org/packages/39/68/e9f1126d757653496dbc096cb429014347a36b228f5a991dae2c6b6cfd40/numpy-2.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0123ffdaa88fa4ab64835dcbde75dcdf89c453c922f18dced6e27c90d1d0ec5a", size = 19236173, upload-time = "2024-08-26T20:12:44.049Z" }, + { url = "https://files.pythonhosted.org/packages/d1/e9/1f5333281e4ebf483ba1c888b1d61ba7e78d7e910fdd8e6499667041cc35/numpy-2.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:96a55f64139912d61de9137f11bf39a55ec8faec288c75a54f93dfd39f7eb40c", size = 19634174, upload-time = "2024-08-26T20:13:13.634Z" }, + { url = "https://files.pythonhosted.org/packages/71/af/a469674070c8d8408384e3012e064299f7a2de540738a8e414dcfd639996/numpy-2.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ec9852fb39354b5a45a80bdab5ac02dd02b15f44b3804e9f00c556bf24b4bded", size = 14099701, upload-time = "2024-08-26T20:13:34.851Z" }, + { url = "https://files.pythonhosted.org/packages/d0/3d/08ea9f239d0e0e939b6ca52ad403c84a2bce1bde301a8eb4888c1c1543f1/numpy-2.0.2-cp312-cp312-win32.whl", hash = "sha256:671bec6496f83202ed2d3c8fdc486a8fc86942f2e69ff0e986140339a63bcbe5", size = 6174313, upload-time = "2024-08-26T20:13:45.653Z" }, + { url = "https://files.pythonhosted.org/packages/b2/b5/4ac39baebf1fdb2e72585c8352c56d063b6126be9fc95bd2bb5ef5770c20/numpy-2.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:cfd41e13fdc257aa5778496b8caa5e856dc4896d4ccf01841daee1d96465467a", size = 15606179, upload-time = "2024-08-26T20:14:08.786Z" }, + { url = "https://files.pythonhosted.org/packages/43/c1/41c8f6df3162b0c6ffd4437d729115704bd43363de0090c7f913cfbc2d89/numpy-2.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9059e10581ce4093f735ed23f3b9d283b9d517ff46009ddd485f1747eb22653c", size = 21169942, upload-time = "2024-08-26T20:14:40.108Z" }, + { url = "https://files.pythonhosted.org/packages/39/bc/fd298f308dcd232b56a4031fd6ddf11c43f9917fbc937e53762f7b5a3bb1/numpy-2.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:423e89b23490805d2a5a96fe40ec507407b8ee786d66f7328be214f9679df6dd", size = 13711512, upload-time = "2024-08-26T20:15:00.985Z" }, + { url = "https://files.pythonhosted.org/packages/96/ff/06d1aa3eeb1c614eda245c1ba4fb88c483bee6520d361641331872ac4b82/numpy-2.0.2-cp39-cp39-macosx_14_0_arm64.whl", hash = "sha256:2b2955fa6f11907cf7a70dab0d0755159bca87755e831e47932367fc8f2f2d0b", size = 5306976, upload-time = "2024-08-26T20:15:10.876Z" }, + { url = "https://files.pythonhosted.org/packages/2d/98/121996dcfb10a6087a05e54453e28e58694a7db62c5a5a29cee14c6e047b/numpy-2.0.2-cp39-cp39-macosx_14_0_x86_64.whl", hash = "sha256:97032a27bd9d8988b9a97a8c4d2c9f2c15a81f61e2f21404d7e8ef00cb5be729", size = 6906494, upload-time = "2024-08-26T20:15:22.055Z" }, + { url = "https://files.pythonhosted.org/packages/15/31/9dffc70da6b9bbf7968f6551967fc21156207366272c2a40b4ed6008dc9b/numpy-2.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1e795a8be3ddbac43274f18588329c72939870a16cae810c2b73461c40718ab1", size = 13912596, upload-time = "2024-08-26T20:15:42.452Z" }, + { url = "https://files.pythonhosted.org/packages/b9/14/78635daab4b07c0930c919d451b8bf8c164774e6a3413aed04a6d95758ce/numpy-2.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f26b258c385842546006213344c50655ff1555a9338e2e5e02a0756dc3e803dd", size = 19526099, upload-time = "2024-08-26T20:16:11.048Z" }, + { url = "https://files.pythonhosted.org/packages/26/4c/0eeca4614003077f68bfe7aac8b7496f04221865b3a5e7cb230c9d055afd/numpy-2.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5fec9451a7789926bcf7c2b8d187292c9f93ea30284802a0ab3f5be8ab36865d", size = 19932823, upload-time = "2024-08-26T20:16:40.171Z" }, + { url = "https://files.pythonhosted.org/packages/f1/46/ea25b98b13dccaebddf1a803f8c748680d972e00507cd9bc6dcdb5aa2ac1/numpy-2.0.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:9189427407d88ff25ecf8f12469d4d39d35bee1db5d39fc5c168c6f088a6956d", size = 14404424, upload-time = "2024-08-26T20:17:02.604Z" }, + { url = "https://files.pythonhosted.org/packages/c8/a6/177dd88d95ecf07e722d21008b1b40e681a929eb9e329684d449c36586b2/numpy-2.0.2-cp39-cp39-win32.whl", hash = "sha256:905d16e0c60200656500c95b6b8dca5d109e23cb24abc701d41c02d74c6b3afa", size = 6476809, upload-time = "2024-08-26T20:17:13.553Z" }, + { url = "https://files.pythonhosted.org/packages/ea/2b/7fc9f4e7ae5b507c1a3a21f0f15ed03e794c1242ea8a242ac158beb56034/numpy-2.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:a3f4ab0caa7f053f6797fcd4e1e25caee367db3112ef2b6ef82d749530768c73", size = 15911314, upload-time = "2024-08-26T20:17:36.72Z" }, + { url = "https://files.pythonhosted.org/packages/8f/3b/df5a870ac6a3be3a86856ce195ef42eec7ae50d2a202be1f5a4b3b340e14/numpy-2.0.2-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:7f0a0c6f12e07fa94133c8a67404322845220c06a9e80e85999afe727f7438b8", size = 21025288, upload-time = "2024-08-26T20:18:07.732Z" }, + { url = "https://files.pythonhosted.org/packages/2c/97/51af92f18d6f6f2d9ad8b482a99fb74e142d71372da5d834b3a2747a446e/numpy-2.0.2-pp39-pypy39_pp73-macosx_14_0_x86_64.whl", hash = "sha256:312950fdd060354350ed123c0e25a71327d3711584beaef30cdaa93320c392d4", size = 6762793, upload-time = "2024-08-26T20:18:19.125Z" }, + { url = "https://files.pythonhosted.org/packages/12/46/de1fbd0c1b5ccaa7f9a005b66761533e2f6a3e560096682683a223631fe9/numpy-2.0.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:26df23238872200f63518dd2aa984cfca675d82469535dc7162dc2ee52d9dd5c", size = 19334885, upload-time = "2024-08-26T20:18:47.237Z" }, + { url = "https://files.pythonhosted.org/packages/cc/dc/d330a6faefd92b446ec0f0dfea4c3207bb1fef3c4771d19cf4543efd2c78/numpy-2.0.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:a46288ec55ebbd58947d31d72be2c63cbf839f0a63b49cb755022310792a3385", size = 15828784, upload-time = "2024-08-26T20:19:11.19Z" }, +] + +[[package]] +name = "numpy" +version = "2.2.6" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.10.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/76/21/7d2a95e4bba9dc13d043ee156a356c0a8f0c6309dff6b21b4d71a073b8a8/numpy-2.2.6.tar.gz", hash = "sha256:e29554e2bef54a90aa5cc07da6ce955accb83f21ab5de01a62c8478897b264fd", size = 20276440, upload-time = "2025-05-17T22:38:04.611Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9a/3e/ed6db5be21ce87955c0cbd3009f2803f59fa08df21b5df06862e2d8e2bdd/numpy-2.2.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b412caa66f72040e6d268491a59f2c43bf03eb6c96dd8f0307829feb7fa2b6fb", size = 21165245, upload-time = "2025-05-17T21:27:58.555Z" }, + { url = "https://files.pythonhosted.org/packages/22/c2/4b9221495b2a132cc9d2eb862e21d42a009f5a60e45fc44b00118c174bff/numpy-2.2.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8e41fd67c52b86603a91c1a505ebaef50b3314de0213461c7a6e99c9a3beff90", size = 14360048, upload-time = "2025-05-17T21:28:21.406Z" }, + { url = "https://files.pythonhosted.org/packages/fd/77/dc2fcfc66943c6410e2bf598062f5959372735ffda175b39906d54f02349/numpy-2.2.6-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:37e990a01ae6ec7fe7fa1c26c55ecb672dd98b19c3d0e1d1f326fa13cb38d163", size = 5340542, upload-time = "2025-05-17T21:28:30.931Z" }, + { url = "https://files.pythonhosted.org/packages/7a/4f/1cb5fdc353a5f5cc7feb692db9b8ec2c3d6405453f982435efc52561df58/numpy-2.2.6-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:5a6429d4be8ca66d889b7cf70f536a397dc45ba6faeb5f8c5427935d9592e9cf", size = 6878301, upload-time = "2025-05-17T21:28:41.613Z" }, + { url = "https://files.pythonhosted.org/packages/eb/17/96a3acd228cec142fcb8723bd3cc39c2a474f7dcf0a5d16731980bcafa95/numpy-2.2.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:efd28d4e9cd7d7a8d39074a4d44c63eda73401580c5c76acda2ce969e0a38e83", size = 14297320, upload-time = "2025-05-17T21:29:02.78Z" }, + { url = "https://files.pythonhosted.org/packages/b4/63/3de6a34ad7ad6646ac7d2f55ebc6ad439dbbf9c4370017c50cf403fb19b5/numpy-2.2.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc7b73d02efb0e18c000e9ad8b83480dfcd5dfd11065997ed4c6747470ae8915", size = 16801050, upload-time = "2025-05-17T21:29:27.675Z" }, + { url = "https://files.pythonhosted.org/packages/07/b6/89d837eddef52b3d0cec5c6ba0456c1bf1b9ef6a6672fc2b7873c3ec4e2e/numpy-2.2.6-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:74d4531beb257d2c3f4b261bfb0fc09e0f9ebb8842d82a7b4209415896adc680", size = 15807034, upload-time = "2025-05-17T21:29:51.102Z" }, + { url = "https://files.pythonhosted.org/packages/01/c8/dc6ae86e3c61cfec1f178e5c9f7858584049b6093f843bca541f94120920/numpy-2.2.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8fc377d995680230e83241d8a96def29f204b5782f371c532579b4f20607a289", size = 18614185, upload-time = "2025-05-17T21:30:18.703Z" }, + { url = "https://files.pythonhosted.org/packages/5b/c5/0064b1b7e7c89137b471ccec1fd2282fceaae0ab3a9550f2568782d80357/numpy-2.2.6-cp310-cp310-win32.whl", hash = "sha256:b093dd74e50a8cba3e873868d9e93a85b78e0daf2e98c6797566ad8044e8363d", size = 6527149, upload-time = "2025-05-17T21:30:29.788Z" }, + { url = "https://files.pythonhosted.org/packages/a3/dd/4b822569d6b96c39d1215dbae0582fd99954dcbcf0c1a13c61783feaca3f/numpy-2.2.6-cp310-cp310-win_amd64.whl", hash = "sha256:f0fd6321b839904e15c46e0d257fdd101dd7f530fe03fd6359c1ea63738703f3", size = 12904620, upload-time = "2025-05-17T21:30:48.994Z" }, + { url = "https://files.pythonhosted.org/packages/da/a8/4f83e2aa666a9fbf56d6118faaaf5f1974d456b1823fda0a176eff722839/numpy-2.2.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f9f1adb22318e121c5c69a09142811a201ef17ab257a1e66ca3025065b7f53ae", size = 21176963, upload-time = "2025-05-17T21:31:19.36Z" }, + { url = "https://files.pythonhosted.org/packages/b3/2b/64e1affc7972decb74c9e29e5649fac940514910960ba25cd9af4488b66c/numpy-2.2.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c820a93b0255bc360f53eca31a0e676fd1101f673dda8da93454a12e23fc5f7a", size = 14406743, upload-time = "2025-05-17T21:31:41.087Z" }, + { url = "https://files.pythonhosted.org/packages/4a/9f/0121e375000b5e50ffdd8b25bf78d8e1a5aa4cca3f185d41265198c7b834/numpy-2.2.6-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:3d70692235e759f260c3d837193090014aebdf026dfd167834bcba43e30c2a42", size = 5352616, upload-time = "2025-05-17T21:31:50.072Z" }, + { url = "https://files.pythonhosted.org/packages/31/0d/b48c405c91693635fbe2dcd7bc84a33a602add5f63286e024d3b6741411c/numpy-2.2.6-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:481b49095335f8eed42e39e8041327c05b0f6f4780488f61286ed3c01368d491", size = 6889579, upload-time = "2025-05-17T21:32:01.712Z" }, + { url = "https://files.pythonhosted.org/packages/52/b8/7f0554d49b565d0171eab6e99001846882000883998e7b7d9f0d98b1f934/numpy-2.2.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b64d8d4d17135e00c8e346e0a738deb17e754230d7e0810ac5012750bbd85a5a", size = 14312005, upload-time = "2025-05-17T21:32:23.332Z" }, + { url = "https://files.pythonhosted.org/packages/b3/dd/2238b898e51bd6d389b7389ffb20d7f4c10066d80351187ec8e303a5a475/numpy-2.2.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba10f8411898fc418a521833e014a77d3ca01c15b0c6cdcce6a0d2897e6dbbdf", size = 16821570, upload-time = "2025-05-17T21:32:47.991Z" }, + { url = "https://files.pythonhosted.org/packages/83/6c/44d0325722cf644f191042bf47eedad61c1e6df2432ed65cbe28509d404e/numpy-2.2.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:bd48227a919f1bafbdda0583705e547892342c26fb127219d60a5c36882609d1", size = 15818548, upload-time = "2025-05-17T21:33:11.728Z" }, + { url = "https://files.pythonhosted.org/packages/ae/9d/81e8216030ce66be25279098789b665d49ff19eef08bfa8cb96d4957f422/numpy-2.2.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9551a499bf125c1d4f9e250377c1ee2eddd02e01eac6644c080162c0c51778ab", size = 18620521, upload-time = "2025-05-17T21:33:39.139Z" }, + { url = "https://files.pythonhosted.org/packages/6a/fd/e19617b9530b031db51b0926eed5345ce8ddc669bb3bc0044b23e275ebe8/numpy-2.2.6-cp311-cp311-win32.whl", hash = "sha256:0678000bb9ac1475cd454c6b8c799206af8107e310843532b04d49649c717a47", size = 6525866, upload-time = "2025-05-17T21:33:50.273Z" }, + { url = "https://files.pythonhosted.org/packages/31/0a/f354fb7176b81747d870f7991dc763e157a934c717b67b58456bc63da3df/numpy-2.2.6-cp311-cp311-win_amd64.whl", hash = "sha256:e8213002e427c69c45a52bbd94163084025f533a55a59d6f9c5b820774ef3303", size = 12907455, upload-time = "2025-05-17T21:34:09.135Z" }, + { url = "https://files.pythonhosted.org/packages/82/5d/c00588b6cf18e1da539b45d3598d3557084990dcc4331960c15ee776ee41/numpy-2.2.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:41c5a21f4a04fa86436124d388f6ed60a9343a6f767fced1a8a71c3fbca038ff", size = 20875348, upload-time = "2025-05-17T21:34:39.648Z" }, + { url = "https://files.pythonhosted.org/packages/66/ee/560deadcdde6c2f90200450d5938f63a34b37e27ebff162810f716f6a230/numpy-2.2.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:de749064336d37e340f640b05f24e9e3dd678c57318c7289d222a8a2f543e90c", size = 14119362, upload-time = "2025-05-17T21:35:01.241Z" }, + { url = "https://files.pythonhosted.org/packages/3c/65/4baa99f1c53b30adf0acd9a5519078871ddde8d2339dc5a7fde80d9d87da/numpy-2.2.6-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:894b3a42502226a1cac872f840030665f33326fc3dac8e57c607905773cdcde3", size = 5084103, upload-time = "2025-05-17T21:35:10.622Z" }, + { url = "https://files.pythonhosted.org/packages/cc/89/e5a34c071a0570cc40c9a54eb472d113eea6d002e9ae12bb3a8407fb912e/numpy-2.2.6-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:71594f7c51a18e728451bb50cc60a3ce4e6538822731b2933209a1f3614e9282", size = 6625382, upload-time = "2025-05-17T21:35:21.414Z" }, + { url = "https://files.pythonhosted.org/packages/f8/35/8c80729f1ff76b3921d5c9487c7ac3de9b2a103b1cd05e905b3090513510/numpy-2.2.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f2618db89be1b4e05f7a1a847a9c1c0abd63e63a1607d892dd54668dd92faf87", size = 14018462, upload-time = "2025-05-17T21:35:42.174Z" }, + { url = "https://files.pythonhosted.org/packages/8c/3d/1e1db36cfd41f895d266b103df00ca5b3cbe965184df824dec5c08c6b803/numpy-2.2.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd83c01228a688733f1ded5201c678f0c53ecc1006ffbc404db9f7a899ac6249", size = 16527618, upload-time = "2025-05-17T21:36:06.711Z" }, + { url = "https://files.pythonhosted.org/packages/61/c6/03ed30992602c85aa3cd95b9070a514f8b3c33e31124694438d88809ae36/numpy-2.2.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:37c0ca431f82cd5fa716eca9506aefcabc247fb27ba69c5062a6d3ade8cf8f49", size = 15505511, upload-time = "2025-05-17T21:36:29.965Z" }, + { url = "https://files.pythonhosted.org/packages/b7/25/5761d832a81df431e260719ec45de696414266613c9ee268394dd5ad8236/numpy-2.2.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fe27749d33bb772c80dcd84ae7e8df2adc920ae8297400dabec45f0dedb3f6de", size = 18313783, upload-time = "2025-05-17T21:36:56.883Z" }, + { url = "https://files.pythonhosted.org/packages/57/0a/72d5a3527c5ebffcd47bde9162c39fae1f90138c961e5296491ce778e682/numpy-2.2.6-cp312-cp312-win32.whl", hash = "sha256:4eeaae00d789f66c7a25ac5f34b71a7035bb474e679f410e5e1a94deb24cf2d4", size = 6246506, upload-time = "2025-05-17T21:37:07.368Z" }, + { url = "https://files.pythonhosted.org/packages/36/fa/8c9210162ca1b88529ab76b41ba02d433fd54fecaf6feb70ef9f124683f1/numpy-2.2.6-cp312-cp312-win_amd64.whl", hash = "sha256:c1f9540be57940698ed329904db803cf7a402f3fc200bfe599334c9bd84a40b2", size = 12614190, upload-time = "2025-05-17T21:37:26.213Z" }, + { url = "https://files.pythonhosted.org/packages/f9/5c/6657823f4f594f72b5471f1db1ab12e26e890bb2e41897522d134d2a3e81/numpy-2.2.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0811bb762109d9708cca4d0b13c4f67146e3c3b7cf8d34018c722adb2d957c84", size = 20867828, upload-time = "2025-05-17T21:37:56.699Z" }, + { url = "https://files.pythonhosted.org/packages/dc/9e/14520dc3dadf3c803473bd07e9b2bd1b69bc583cb2497b47000fed2fa92f/numpy-2.2.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:287cc3162b6f01463ccd86be154f284d0893d2b3ed7292439ea97eafa8170e0b", size = 14143006, upload-time = "2025-05-17T21:38:18.291Z" }, + { url = "https://files.pythonhosted.org/packages/4f/06/7e96c57d90bebdce9918412087fc22ca9851cceaf5567a45c1f404480e9e/numpy-2.2.6-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:f1372f041402e37e5e633e586f62aa53de2eac8d98cbfb822806ce4bbefcb74d", size = 5076765, upload-time = "2025-05-17T21:38:27.319Z" }, + { url = "https://files.pythonhosted.org/packages/73/ed/63d920c23b4289fdac96ddbdd6132e9427790977d5457cd132f18e76eae0/numpy-2.2.6-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:55a4d33fa519660d69614a9fad433be87e5252f4b03850642f88993f7b2ca566", size = 6617736, upload-time = "2025-05-17T21:38:38.141Z" }, + { url = "https://files.pythonhosted.org/packages/85/c5/e19c8f99d83fd377ec8c7e0cf627a8049746da54afc24ef0a0cb73d5dfb5/numpy-2.2.6-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f92729c95468a2f4f15e9bb94c432a9229d0d50de67304399627a943201baa2f", size = 14010719, upload-time = "2025-05-17T21:38:58.433Z" }, + { url = "https://files.pythonhosted.org/packages/19/49/4df9123aafa7b539317bf6d342cb6d227e49f7a35b99c287a6109b13dd93/numpy-2.2.6-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1bc23a79bfabc5d056d106f9befb8d50c31ced2fbc70eedb8155aec74a45798f", size = 16526072, upload-time = "2025-05-17T21:39:22.638Z" }, + { url = "https://files.pythonhosted.org/packages/b2/6c/04b5f47f4f32f7c2b0e7260442a8cbcf8168b0e1a41ff1495da42f42a14f/numpy-2.2.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e3143e4451880bed956e706a3220b4e5cf6172ef05fcc397f6f36a550b1dd868", size = 15503213, upload-time = "2025-05-17T21:39:45.865Z" }, + { url = "https://files.pythonhosted.org/packages/17/0a/5cd92e352c1307640d5b6fec1b2ffb06cd0dabe7d7b8227f97933d378422/numpy-2.2.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b4f13750ce79751586ae2eb824ba7e1e8dba64784086c98cdbbcc6a42112ce0d", size = 18316632, upload-time = "2025-05-17T21:40:13.331Z" }, + { url = "https://files.pythonhosted.org/packages/f0/3b/5cba2b1d88760ef86596ad0f3d484b1cbff7c115ae2429678465057c5155/numpy-2.2.6-cp313-cp313-win32.whl", hash = "sha256:5beb72339d9d4fa36522fc63802f469b13cdbe4fdab4a288f0c441b74272ebfd", size = 6244532, upload-time = "2025-05-17T21:43:46.099Z" }, + { url = "https://files.pythonhosted.org/packages/cb/3b/d58c12eafcb298d4e6d0d40216866ab15f59e55d148a5658bb3132311fcf/numpy-2.2.6-cp313-cp313-win_amd64.whl", hash = "sha256:b0544343a702fa80c95ad5d3d608ea3599dd54d4632df855e4c8d24eb6ecfa1c", size = 12610885, upload-time = "2025-05-17T21:44:05.145Z" }, + { url = "https://files.pythonhosted.org/packages/6b/9e/4bf918b818e516322db999ac25d00c75788ddfd2d2ade4fa66f1f38097e1/numpy-2.2.6-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0bca768cd85ae743b2affdc762d617eddf3bcf8724435498a1e80132d04879e6", size = 20963467, upload-time = "2025-05-17T21:40:44Z" }, + { url = "https://files.pythonhosted.org/packages/61/66/d2de6b291507517ff2e438e13ff7b1e2cdbdb7cb40b3ed475377aece69f9/numpy-2.2.6-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:fc0c5673685c508a142ca65209b4e79ed6740a4ed6b2267dbba90f34b0b3cfda", size = 14225144, upload-time = "2025-05-17T21:41:05.695Z" }, + { url = "https://files.pythonhosted.org/packages/e4/25/480387655407ead912e28ba3a820bc69af9adf13bcbe40b299d454ec011f/numpy-2.2.6-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:5bd4fc3ac8926b3819797a7c0e2631eb889b4118a9898c84f585a54d475b7e40", size = 5200217, upload-time = "2025-05-17T21:41:15.903Z" }, + { url = "https://files.pythonhosted.org/packages/aa/4a/6e313b5108f53dcbf3aca0c0f3e9c92f4c10ce57a0a721851f9785872895/numpy-2.2.6-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:fee4236c876c4e8369388054d02d0e9bb84821feb1a64dd59e137e6511a551f8", size = 6712014, upload-time = "2025-05-17T21:41:27.321Z" }, + { url = "https://files.pythonhosted.org/packages/b7/30/172c2d5c4be71fdf476e9de553443cf8e25feddbe185e0bd88b096915bcc/numpy-2.2.6-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e1dda9c7e08dc141e0247a5b8f49cf05984955246a327d4c48bda16821947b2f", size = 14077935, upload-time = "2025-05-17T21:41:49.738Z" }, + { url = "https://files.pythonhosted.org/packages/12/fb/9e743f8d4e4d3c710902cf87af3512082ae3d43b945d5d16563f26ec251d/numpy-2.2.6-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f447e6acb680fd307f40d3da4852208af94afdfab89cf850986c3ca00562f4fa", size = 16600122, upload-time = "2025-05-17T21:42:14.046Z" }, + { url = "https://files.pythonhosted.org/packages/12/75/ee20da0e58d3a66f204f38916757e01e33a9737d0b22373b3eb5a27358f9/numpy-2.2.6-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:389d771b1623ec92636b0786bc4ae56abafad4a4c513d36a55dce14bd9ce8571", size = 15586143, upload-time = "2025-05-17T21:42:37.464Z" }, + { url = "https://files.pythonhosted.org/packages/76/95/bef5b37f29fc5e739947e9ce5179ad402875633308504a52d188302319c8/numpy-2.2.6-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8e9ace4a37db23421249ed236fdcdd457d671e25146786dfc96835cd951aa7c1", size = 18385260, upload-time = "2025-05-17T21:43:05.189Z" }, + { url = "https://files.pythonhosted.org/packages/09/04/f2f83279d287407cf36a7a8053a5abe7be3622a4363337338f2585e4afda/numpy-2.2.6-cp313-cp313t-win32.whl", hash = "sha256:038613e9fb8c72b0a41f025a7e4c3f0b7a1b5d768ece4796b674c8f3fe13efff", size = 6377225, upload-time = "2025-05-17T21:43:16.254Z" }, + { url = "https://files.pythonhosted.org/packages/67/0e/35082d13c09c02c011cf21570543d202ad929d961c02a147493cb0c2bdf5/numpy-2.2.6-cp313-cp313t-win_amd64.whl", hash = "sha256:6031dd6dfecc0cf9f668681a37648373bddd6421fff6c66ec1624eed0180ee06", size = 12771374, upload-time = "2025-05-17T21:43:35.479Z" }, + { url = "https://files.pythonhosted.org/packages/9e/3b/d94a75f4dbf1ef5d321523ecac21ef23a3cd2ac8b78ae2aac40873590229/numpy-2.2.6-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:0b605b275d7bd0c640cad4e5d30fa701a8d59302e127e5f79138ad62762c3e3d", size = 21040391, upload-time = "2025-05-17T21:44:35.948Z" }, + { url = "https://files.pythonhosted.org/packages/17/f4/09b2fa1b58f0fb4f7c7963a1649c64c4d315752240377ed74d9cd878f7b5/numpy-2.2.6-pp310-pypy310_pp73-macosx_14_0_x86_64.whl", hash = "sha256:7befc596a7dc9da8a337f79802ee8adb30a552a94f792b9c9d18c840055907db", size = 6786754, upload-time = "2025-05-17T21:44:47.446Z" }, + { url = "https://files.pythonhosted.org/packages/af/30/feba75f143bdc868a1cc3f44ccfa6c4b9ec522b36458e738cd00f67b573f/numpy-2.2.6-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce47521a4754c8f4593837384bd3424880629f718d87c5d44f8ed763edd63543", size = 16643476, upload-time = "2025-05-17T21:45:11.871Z" }, + { url = "https://files.pythonhosted.org/packages/37/48/ac2a9584402fb6c0cd5b5d1a91dcf176b15760130dd386bbafdbfe3640bf/numpy-2.2.6-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d042d24c90c41b54fd506da306759e06e568864df8ec17ccc17e9e884634fd00", size = 12812666, upload-time = "2025-05-17T21:45:31.426Z" }, +] + +[[package]] +name = "numpy" +version = "2.3.5" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.11'", +] +sdist = { url = "https://files.pythonhosted.org/packages/76/65/21b3bc86aac7b8f2862db1e808f1ea22b028e30a225a34a5ede9bf8678f2/numpy-2.3.5.tar.gz", hash = "sha256:784db1dcdab56bf0517743e746dfb0f885fc68d948aba86eeec2cba234bdf1c0", size = 20584950, upload-time = "2025-11-16T22:52:42.067Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/43/77/84dd1d2e34d7e2792a236ba180b5e8fcc1e3e414e761ce0253f63d7f572e/numpy-2.3.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:de5672f4a7b200c15a4127042170a694d4df43c992948f5e1af57f0174beed10", size = 17034641, upload-time = "2025-11-16T22:49:19.336Z" }, + { url = "https://files.pythonhosted.org/packages/2a/ea/25e26fa5837106cde46ae7d0b667e20f69cbbc0efd64cba8221411ab26ae/numpy-2.3.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:acfd89508504a19ed06ef963ad544ec6664518c863436306153e13e94605c218", size = 12528324, upload-time = "2025-11-16T22:49:22.582Z" }, + { url = "https://files.pythonhosted.org/packages/4d/1a/e85f0eea4cf03d6a0228f5c0256b53f2df4bc794706e7df019fc622e47f1/numpy-2.3.5-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:ffe22d2b05504f786c867c8395de703937f934272eb67586817b46188b4ded6d", size = 5356872, upload-time = "2025-11-16T22:49:25.408Z" }, + { url = "https://files.pythonhosted.org/packages/5c/bb/35ef04afd567f4c989c2060cde39211e4ac5357155c1833bcd1166055c61/numpy-2.3.5-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:872a5cf366aec6bb1147336480fef14c9164b154aeb6542327de4970282cd2f5", size = 6893148, upload-time = "2025-11-16T22:49:27.549Z" }, + { url = "https://files.pythonhosted.org/packages/f2/2b/05bbeb06e2dff5eab512dfc678b1cc5ee94d8ac5956a0885c64b6b26252b/numpy-2.3.5-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3095bdb8dd297e5920b010e96134ed91d852d81d490e787beca7e35ae1d89cf7", size = 14557282, upload-time = "2025-11-16T22:49:30.964Z" }, + { url = "https://files.pythonhosted.org/packages/65/fb/2b23769462b34398d9326081fad5655198fcf18966fcb1f1e49db44fbf31/numpy-2.3.5-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8cba086a43d54ca804ce711b2a940b16e452807acebe7852ff327f1ecd49b0d4", size = 16897903, upload-time = "2025-11-16T22:49:34.191Z" }, + { url = "https://files.pythonhosted.org/packages/ac/14/085f4cf05fc3f1e8aa95e85404e984ffca9b2275a5dc2b1aae18a67538b8/numpy-2.3.5-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6cf9b429b21df6b99f4dee7a1218b8b7ffbbe7df8764dc0bd60ce8a0708fed1e", size = 16341672, upload-time = "2025-11-16T22:49:37.2Z" }, + { url = "https://files.pythonhosted.org/packages/6f/3b/1f73994904142b2aa290449b3bb99772477b5fd94d787093e4f24f5af763/numpy-2.3.5-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:396084a36abdb603546b119d96528c2f6263921c50df3c8fd7cb28873a237748", size = 18838896, upload-time = "2025-11-16T22:49:39.727Z" }, + { url = "https://files.pythonhosted.org/packages/cd/b9/cf6649b2124f288309ffc353070792caf42ad69047dcc60da85ee85fea58/numpy-2.3.5-cp311-cp311-win32.whl", hash = "sha256:b0c7088a73aef3d687c4deef8452a3ac7c1be4e29ed8bf3b366c8111128ac60c", size = 6563608, upload-time = "2025-11-16T22:49:42.079Z" }, + { url = "https://files.pythonhosted.org/packages/aa/44/9fe81ae1dcc29c531843852e2874080dc441338574ccc4306b39e2ff6e59/numpy-2.3.5-cp311-cp311-win_amd64.whl", hash = "sha256:a414504bef8945eae5f2d7cb7be2d4af77c5d1cb5e20b296c2c25b61dff2900c", size = 13078442, upload-time = "2025-11-16T22:49:43.99Z" }, + { url = "https://files.pythonhosted.org/packages/6d/a7/f99a41553d2da82a20a2f22e93c94f928e4490bb447c9ff3c4ff230581d3/numpy-2.3.5-cp311-cp311-win_arm64.whl", hash = "sha256:0cd00b7b36e35398fa2d16af7b907b65304ef8bb4817a550e06e5012929830fa", size = 10458555, upload-time = "2025-11-16T22:49:47.092Z" }, + { url = "https://files.pythonhosted.org/packages/44/37/e669fe6cbb2b96c62f6bbedc6a81c0f3b7362f6a59230b23caa673a85721/numpy-2.3.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:74ae7b798248fe62021dbf3c914245ad45d1a6b0cb4a29ecb4b31d0bfbc4cc3e", size = 16733873, upload-time = "2025-11-16T22:49:49.84Z" }, + { url = "https://files.pythonhosted.org/packages/c5/65/df0db6c097892c9380851ab9e44b52d4f7ba576b833996e0080181c0c439/numpy-2.3.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ee3888d9ff7c14604052b2ca5535a30216aa0a58e948cdd3eeb8d3415f638769", size = 12259838, upload-time = "2025-11-16T22:49:52.863Z" }, + { url = "https://files.pythonhosted.org/packages/5b/e1/1ee06e70eb2136797abe847d386e7c0e830b67ad1d43f364dd04fa50d338/numpy-2.3.5-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:612a95a17655e213502f60cfb9bf9408efdc9eb1d5f50535cc6eb365d11b42b5", size = 5088378, upload-time = "2025-11-16T22:49:55.055Z" }, + { url = "https://files.pythonhosted.org/packages/6d/9c/1ca85fb86708724275103b81ec4cf1ac1d08f465368acfc8da7ab545bdae/numpy-2.3.5-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:3101e5177d114a593d79dd79658650fe28b5a0d8abeb8ce6f437c0e6df5be1a4", size = 6628559, upload-time = "2025-11-16T22:49:57.371Z" }, + { url = "https://files.pythonhosted.org/packages/74/78/fcd41e5a0ce4f3f7b003da85825acddae6d7ecb60cf25194741b036ca7d6/numpy-2.3.5-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8b973c57ff8e184109db042c842423ff4f60446239bd585a5131cc47f06f789d", size = 14250702, upload-time = "2025-11-16T22:49:59.632Z" }, + { url = "https://files.pythonhosted.org/packages/b6/23/2a1b231b8ff672b4c450dac27164a8b2ca7d9b7144f9c02d2396518352eb/numpy-2.3.5-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0d8163f43acde9a73c2a33605353a4f1bc4798745a8b1d73183b28e5b435ae28", size = 16606086, upload-time = "2025-11-16T22:50:02.127Z" }, + { url = "https://files.pythonhosted.org/packages/a0/c5/5ad26fbfbe2012e190cc7d5003e4d874b88bb18861d0829edc140a713021/numpy-2.3.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:51c1e14eb1e154ebd80e860722f9e6ed6ec89714ad2db2d3aa33c31d7c12179b", size = 16025985, upload-time = "2025-11-16T22:50:04.536Z" }, + { url = "https://files.pythonhosted.org/packages/d2/fa/dd48e225c46c819288148d9d060b047fd2a6fb1eb37eae25112ee4cb4453/numpy-2.3.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b46b4ec24f7293f23adcd2d146960559aaf8020213de8ad1909dba6c013bf89c", size = 18542976, upload-time = "2025-11-16T22:50:07.557Z" }, + { url = "https://files.pythonhosted.org/packages/05/79/ccbd23a75862d95af03d28b5c6901a1b7da4803181513d52f3b86ed9446e/numpy-2.3.5-cp312-cp312-win32.whl", hash = "sha256:3997b5b3c9a771e157f9aae01dd579ee35ad7109be18db0e85dbdbe1de06e952", size = 6285274, upload-time = "2025-11-16T22:50:10.746Z" }, + { url = "https://files.pythonhosted.org/packages/2d/57/8aeaf160312f7f489dea47ab61e430b5cb051f59a98ae68b7133ce8fa06a/numpy-2.3.5-cp312-cp312-win_amd64.whl", hash = "sha256:86945f2ee6d10cdfd67bcb4069c1662dd711f7e2a4343db5cecec06b87cf31aa", size = 12782922, upload-time = "2025-11-16T22:50:12.811Z" }, + { url = "https://files.pythonhosted.org/packages/78/a6/aae5cc2ca78c45e64b9ef22f089141d661516856cf7c8a54ba434576900d/numpy-2.3.5-cp312-cp312-win_arm64.whl", hash = "sha256:f28620fe26bee16243be2b7b874da327312240a7cdc38b769a697578d2100013", size = 10194667, upload-time = "2025-11-16T22:50:16.16Z" }, + { url = "https://files.pythonhosted.org/packages/db/69/9cde09f36da4b5a505341180a3f2e6fadc352fd4d2b7096ce9778db83f1a/numpy-2.3.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d0f23b44f57077c1ede8c5f26b30f706498b4862d3ff0a7298b8411dd2f043ff", size = 16728251, upload-time = "2025-11-16T22:50:19.013Z" }, + { url = "https://files.pythonhosted.org/packages/79/fb/f505c95ceddd7027347b067689db71ca80bd5ecc926f913f1a23e65cf09b/numpy-2.3.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:aa5bc7c5d59d831d9773d1170acac7893ce3a5e130540605770ade83280e7188", size = 12254652, upload-time = "2025-11-16T22:50:21.487Z" }, + { url = "https://files.pythonhosted.org/packages/78/da/8c7738060ca9c31b30e9301ee0cf6c5ffdbf889d9593285a1cead337f9a5/numpy-2.3.5-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:ccc933afd4d20aad3c00bcef049cb40049f7f196e0397f1109dba6fed63267b0", size = 5083172, upload-time = "2025-11-16T22:50:24.562Z" }, + { url = "https://files.pythonhosted.org/packages/a4/b4/ee5bb2537fb9430fd2ef30a616c3672b991a4129bb1c7dcc42aa0abbe5d7/numpy-2.3.5-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:afaffc4393205524af9dfa400fa250143a6c3bc646c08c9f5e25a9f4b4d6a903", size = 6622990, upload-time = "2025-11-16T22:50:26.47Z" }, + { url = "https://files.pythonhosted.org/packages/95/03/dc0723a013c7d7c19de5ef29e932c3081df1c14ba582b8b86b5de9db7f0f/numpy-2.3.5-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9c75442b2209b8470d6d5d8b1c25714270686f14c749028d2199c54e29f20b4d", size = 14248902, upload-time = "2025-11-16T22:50:28.861Z" }, + { url = "https://files.pythonhosted.org/packages/f5/10/ca162f45a102738958dcec8023062dad0cbc17d1ab99d68c4e4a6c45fb2b/numpy-2.3.5-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:11e06aa0af8c0f05104d56450d6093ee639e15f24ecf62d417329d06e522e017", size = 16597430, upload-time = "2025-11-16T22:50:31.56Z" }, + { url = "https://files.pythonhosted.org/packages/2a/51/c1e29be863588db58175175f057286900b4b3327a1351e706d5e0f8dd679/numpy-2.3.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ed89927b86296067b4f81f108a2271d8926467a8868e554eaf370fc27fa3ccaf", size = 16024551, upload-time = "2025-11-16T22:50:34.242Z" }, + { url = "https://files.pythonhosted.org/packages/83/68/8236589d4dbb87253d28259d04d9b814ec0ecce7cb1c7fed29729f4c3a78/numpy-2.3.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:51c55fe3451421f3a6ef9a9c1439e82101c57a2c9eab9feb196a62b1a10b58ce", size = 18533275, upload-time = "2025-11-16T22:50:37.651Z" }, + { url = "https://files.pythonhosted.org/packages/40/56/2932d75b6f13465239e3b7b7e511be27f1b8161ca2510854f0b6e521c395/numpy-2.3.5-cp313-cp313-win32.whl", hash = "sha256:1978155dd49972084bd6ef388d66ab70f0c323ddee6f693d539376498720fb7e", size = 6277637, upload-time = "2025-11-16T22:50:40.11Z" }, + { url = "https://files.pythonhosted.org/packages/0c/88/e2eaa6cffb115b85ed7c7c87775cb8bcf0816816bc98ca8dbfa2ee33fe6e/numpy-2.3.5-cp313-cp313-win_amd64.whl", hash = "sha256:00dc4e846108a382c5869e77c6ed514394bdeb3403461d25a829711041217d5b", size = 12779090, upload-time = "2025-11-16T22:50:42.503Z" }, + { url = "https://files.pythonhosted.org/packages/8f/88/3f41e13a44ebd4034ee17baa384acac29ba6a4fcc2aca95f6f08ca0447d1/numpy-2.3.5-cp313-cp313-win_arm64.whl", hash = "sha256:0472f11f6ec23a74a906a00b48a4dcf3849209696dff7c189714511268d103ae", size = 10194710, upload-time = "2025-11-16T22:50:44.971Z" }, + { url = "https://files.pythonhosted.org/packages/13/cb/71744144e13389d577f867f745b7df2d8489463654a918eea2eeb166dfc9/numpy-2.3.5-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:414802f3b97f3c1eef41e530aaba3b3c1620649871d8cb38c6eaff034c2e16bd", size = 16827292, upload-time = "2025-11-16T22:50:47.715Z" }, + { url = "https://files.pythonhosted.org/packages/71/80/ba9dc6f2a4398e7f42b708a7fdc841bb638d353be255655498edbf9a15a8/numpy-2.3.5-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5ee6609ac3604fa7780e30a03e5e241a7956f8e2fcfe547d51e3afa5247ac47f", size = 12378897, upload-time = "2025-11-16T22:50:51.327Z" }, + { url = "https://files.pythonhosted.org/packages/2e/6d/db2151b9f64264bcceccd51741aa39b50150de9b602d98ecfe7e0c4bff39/numpy-2.3.5-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:86d835afea1eaa143012a2d7a3f45a3adce2d7adc8b4961f0b362214d800846a", size = 5207391, upload-time = "2025-11-16T22:50:54.542Z" }, + { url = "https://files.pythonhosted.org/packages/80/ae/429bacace5ccad48a14c4ae5332f6aa8ab9f69524193511d60ccdfdc65fa/numpy-2.3.5-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:30bc11310e8153ca664b14c5f1b73e94bd0503681fcf136a163de856f3a50139", size = 6721275, upload-time = "2025-11-16T22:50:56.794Z" }, + { url = "https://files.pythonhosted.org/packages/74/5b/1919abf32d8722646a38cd527bc3771eb229a32724ee6ba340ead9b92249/numpy-2.3.5-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1062fde1dcf469571705945b0f221b73928f34a20c904ffb45db101907c3454e", size = 14306855, upload-time = "2025-11-16T22:50:59.208Z" }, + { url = "https://files.pythonhosted.org/packages/a5/87/6831980559434973bebc30cd9c1f21e541a0f2b0c280d43d3afd909b66d0/numpy-2.3.5-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ce581db493ea1a96c0556360ede6607496e8bf9b3a8efa66e06477267bc831e9", size = 16657359, upload-time = "2025-11-16T22:51:01.991Z" }, + { url = "https://files.pythonhosted.org/packages/dd/91/c797f544491ee99fd00495f12ebb7802c440c1915811d72ac5b4479a3356/numpy-2.3.5-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:cc8920d2ec5fa99875b670bb86ddeb21e295cb07aa331810d9e486e0b969d946", size = 16093374, upload-time = "2025-11-16T22:51:05.291Z" }, + { url = "https://files.pythonhosted.org/packages/74/a6/54da03253afcbe7a72785ec4da9c69fb7a17710141ff9ac5fcb2e32dbe64/numpy-2.3.5-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:9ee2197ef8c4f0dfe405d835f3b6a14f5fee7782b5de51ba06fb65fc9b36e9f1", size = 18594587, upload-time = "2025-11-16T22:51:08.585Z" }, + { url = "https://files.pythonhosted.org/packages/80/e9/aff53abbdd41b0ecca94285f325aff42357c6b5abc482a3fcb4994290b18/numpy-2.3.5-cp313-cp313t-win32.whl", hash = "sha256:70b37199913c1bd300ff6e2693316c6f869c7ee16378faf10e4f5e3275b299c3", size = 6405940, upload-time = "2025-11-16T22:51:11.541Z" }, + { url = "https://files.pythonhosted.org/packages/d5/81/50613fec9d4de5480de18d4f8ef59ad7e344d497edbef3cfd80f24f98461/numpy-2.3.5-cp313-cp313t-win_amd64.whl", hash = "sha256:b501b5fa195cc9e24fe102f21ec0a44dffc231d2af79950b451e0d99cea02234", size = 12920341, upload-time = "2025-11-16T22:51:14.312Z" }, + { url = "https://files.pythonhosted.org/packages/bb/ab/08fd63b9a74303947f34f0bd7c5903b9c5532c2d287bead5bdf4c556c486/numpy-2.3.5-cp313-cp313t-win_arm64.whl", hash = "sha256:a80afd79f45f3c4a7d341f13acbe058d1ca8ac017c165d3fa0d3de6bc1a079d7", size = 10262507, upload-time = "2025-11-16T22:51:16.846Z" }, + { url = "https://files.pythonhosted.org/packages/ba/97/1a914559c19e32d6b2e233cf9a6a114e67c856d35b1d6babca571a3e880f/numpy-2.3.5-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:bf06bc2af43fa8d32d30fae16ad965663e966b1a3202ed407b84c989c3221e82", size = 16735706, upload-time = "2025-11-16T22:51:19.558Z" }, + { url = "https://files.pythonhosted.org/packages/57/d4/51233b1c1b13ecd796311216ae417796b88b0616cfd8a33ae4536330748a/numpy-2.3.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:052e8c42e0c49d2575621c158934920524f6c5da05a1d3b9bab5d8e259e045f0", size = 12264507, upload-time = "2025-11-16T22:51:22.492Z" }, + { url = "https://files.pythonhosted.org/packages/45/98/2fe46c5c2675b8306d0b4a3ec3494273e93e1226a490f766e84298576956/numpy-2.3.5-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:1ed1ec893cff7040a02c8aa1c8611b94d395590d553f6b53629a4461dc7f7b63", size = 5093049, upload-time = "2025-11-16T22:51:25.171Z" }, + { url = "https://files.pythonhosted.org/packages/ce/0e/0698378989bb0ac5f1660c81c78ab1fe5476c1a521ca9ee9d0710ce54099/numpy-2.3.5-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:2dcd0808a421a482a080f89859a18beb0b3d1e905b81e617a188bd80422d62e9", size = 6626603, upload-time = "2025-11-16T22:51:27Z" }, + { url = "https://files.pythonhosted.org/packages/5e/a6/9ca0eecc489640615642a6cbc0ca9e10df70df38c4d43f5a928ff18d8827/numpy-2.3.5-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:727fd05b57df37dc0bcf1a27767a3d9a78cbbc92822445f32cc3436ba797337b", size = 14262696, upload-time = "2025-11-16T22:51:29.402Z" }, + { url = "https://files.pythonhosted.org/packages/c8/f6/07ec185b90ec9d7217a00eeeed7383b73d7e709dae2a9a021b051542a708/numpy-2.3.5-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fffe29a1ef00883599d1dc2c51aa2e5d80afe49523c261a74933df395c15c520", size = 16597350, upload-time = "2025-11-16T22:51:32.167Z" }, + { url = "https://files.pythonhosted.org/packages/75/37/164071d1dde6a1a84c9b8e5b414fa127981bad47adf3a6b7e23917e52190/numpy-2.3.5-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:8f7f0e05112916223d3f438f293abf0727e1181b5983f413dfa2fefc4098245c", size = 16040190, upload-time = "2025-11-16T22:51:35.403Z" }, + { url = "https://files.pythonhosted.org/packages/08/3c/f18b82a406b04859eb026d204e4e1773eb41c5be58410f41ffa511d114ae/numpy-2.3.5-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2e2eb32ddb9ccb817d620ac1d8dae7c3f641c1e5f55f531a33e8ab97960a75b8", size = 18536749, upload-time = "2025-11-16T22:51:39.698Z" }, + { url = "https://files.pythonhosted.org/packages/40/79/f82f572bf44cf0023a2fe8588768e23e1592585020d638999f15158609e1/numpy-2.3.5-cp314-cp314-win32.whl", hash = "sha256:66f85ce62c70b843bab1fb14a05d5737741e74e28c7b8b5a064de10142fad248", size = 6335432, upload-time = "2025-11-16T22:51:42.476Z" }, + { url = "https://files.pythonhosted.org/packages/a3/2e/235b4d96619931192c91660805e5e49242389742a7a82c27665021db690c/numpy-2.3.5-cp314-cp314-win_amd64.whl", hash = "sha256:e6a0bc88393d65807d751a614207b7129a310ca4fe76a74e5c7da5fa5671417e", size = 12919388, upload-time = "2025-11-16T22:51:45.275Z" }, + { url = "https://files.pythonhosted.org/packages/07/2b/29fd75ce45d22a39c61aad74f3d718e7ab67ccf839ca8b60866054eb15f8/numpy-2.3.5-cp314-cp314-win_arm64.whl", hash = "sha256:aeffcab3d4b43712bb7a60b65f6044d444e75e563ff6180af8f98dd4b905dfd2", size = 10476651, upload-time = "2025-11-16T22:51:47.749Z" }, + { url = "https://files.pythonhosted.org/packages/17/e1/f6a721234ebd4d87084cfa68d081bcba2f5cfe1974f7de4e0e8b9b2a2ba1/numpy-2.3.5-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:17531366a2e3a9e30762c000f2c43a9aaa05728712e25c11ce1dbe700c53ad41", size = 16834503, upload-time = "2025-11-16T22:51:50.443Z" }, + { url = "https://files.pythonhosted.org/packages/5c/1c/baf7ffdc3af9c356e1c135e57ab7cf8d247931b9554f55c467efe2c69eff/numpy-2.3.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:d21644de1b609825ede2f48be98dfde4656aefc713654eeee280e37cadc4e0ad", size = 12381612, upload-time = "2025-11-16T22:51:53.609Z" }, + { url = "https://files.pythonhosted.org/packages/74/91/f7f0295151407ddc9ba34e699013c32c3c91944f9b35fcf9281163dc1468/numpy-2.3.5-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:c804e3a5aba5460c73955c955bdbd5c08c354954e9270a2c1565f62e866bdc39", size = 5210042, upload-time = "2025-11-16T22:51:56.213Z" }, + { url = "https://files.pythonhosted.org/packages/2e/3b/78aebf345104ec50dd50a4d06ddeb46a9ff5261c33bcc58b1c4f12f85ec2/numpy-2.3.5-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:cc0a57f895b96ec78969c34f682c602bf8da1a0270b09bc65673df2e7638ec20", size = 6724502, upload-time = "2025-11-16T22:51:58.584Z" }, + { url = "https://files.pythonhosted.org/packages/02/c6/7c34b528740512e57ef1b7c8337ab0b4f0bddf34c723b8996c675bc2bc91/numpy-2.3.5-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:900218e456384ea676e24ea6a0417f030a3b07306d29d7ad843957b40a9d8d52", size = 14308962, upload-time = "2025-11-16T22:52:01.698Z" }, + { url = "https://files.pythonhosted.org/packages/80/35/09d433c5262bc32d725bafc619e095b6a6651caf94027a03da624146f655/numpy-2.3.5-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:09a1bea522b25109bf8e6f3027bd810f7c1085c64a0c7ce050c1676ad0ba010b", size = 16655054, upload-time = "2025-11-16T22:52:04.267Z" }, + { url = "https://files.pythonhosted.org/packages/7a/ab/6a7b259703c09a88804fa2430b43d6457b692378f6b74b356155283566ac/numpy-2.3.5-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:04822c00b5fd0323c8166d66c701dc31b7fbd252c100acd708c48f763968d6a3", size = 16091613, upload-time = "2025-11-16T22:52:08.651Z" }, + { url = "https://files.pythonhosted.org/packages/c2/88/330da2071e8771e60d1038166ff9d73f29da37b01ec3eb43cb1427464e10/numpy-2.3.5-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:d6889ec4ec662a1a37eb4b4fb26b6100841804dac55bd9df579e326cdc146227", size = 18591147, upload-time = "2025-11-16T22:52:11.453Z" }, + { url = "https://files.pythonhosted.org/packages/51/41/851c4b4082402d9ea860c3626db5d5df47164a712cb23b54be028b184c1c/numpy-2.3.5-cp314-cp314t-win32.whl", hash = "sha256:93eebbcf1aafdf7e2ddd44c2923e2672e1010bddc014138b229e49725b4d6be5", size = 6479806, upload-time = "2025-11-16T22:52:14.641Z" }, + { url = "https://files.pythonhosted.org/packages/90/30/d48bde1dfd93332fa557cff1972fbc039e055a52021fbef4c2c4b1eefd17/numpy-2.3.5-cp314-cp314t-win_amd64.whl", hash = "sha256:c8a9958e88b65c3b27e22ca2a076311636850b612d6bbfb76e8d156aacde2aaf", size = 13105760, upload-time = "2025-11-16T22:52:17.975Z" }, + { url = "https://files.pythonhosted.org/packages/2d/fd/4b5eb0b3e888d86aee4d198c23acec7d214baaf17ea93c1adec94c9518b9/numpy-2.3.5-cp314-cp314t-win_arm64.whl", hash = "sha256:6203fdf9f3dc5bdaed7319ad8698e685c7a3be10819f41d32a0723e611733b42", size = 10545459, upload-time = "2025-11-16T22:52:20.55Z" }, + { url = "https://files.pythonhosted.org/packages/c6/65/f9dea8e109371ade9c782b4e4756a82edf9d3366bca495d84d79859a0b79/numpy-2.3.5-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:f0963b55cdd70fad460fa4c1341f12f976bb26cb66021a5580329bd498988310", size = 16910689, upload-time = "2025-11-16T22:52:23.247Z" }, + { url = "https://files.pythonhosted.org/packages/00/4f/edb00032a8fb92ec0a679d3830368355da91a69cab6f3e9c21b64d0bb986/numpy-2.3.5-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:f4255143f5160d0de972d28c8f9665d882b5f61309d8362fdd3e103cf7bf010c", size = 12457053, upload-time = "2025-11-16T22:52:26.367Z" }, + { url = "https://files.pythonhosted.org/packages/16/a4/e8a53b5abd500a63836a29ebe145fc1ab1f2eefe1cfe59276020373ae0aa/numpy-2.3.5-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:a4b9159734b326535f4dd01d947f919c6eefd2d9827466a696c44ced82dfbc18", size = 5285635, upload-time = "2025-11-16T22:52:29.266Z" }, + { url = "https://files.pythonhosted.org/packages/a3/2f/37eeb9014d9c8b3e9c55bc599c68263ca44fdbc12a93e45a21d1d56df737/numpy-2.3.5-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:2feae0d2c91d46e59fcd62784a3a83b3fb677fead592ce51b5a6fbb4f95965ff", size = 6801770, upload-time = "2025-11-16T22:52:31.421Z" }, + { url = "https://files.pythonhosted.org/packages/7d/e4/68d2f474df2cb671b2b6c2986a02e520671295647dad82484cde80ca427b/numpy-2.3.5-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ffac52f28a7849ad7576293c0cb7b9f08304e8f7d738a8cb8a90ec4c55a998eb", size = 14391768, upload-time = "2025-11-16T22:52:33.593Z" }, + { url = "https://files.pythonhosted.org/packages/b8/50/94ccd8a2b141cb50651fddd4f6a48874acb3c91c8f0842b08a6afc4b0b21/numpy-2.3.5-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:63c0e9e7eea69588479ebf4a8a270d5ac22763cc5854e9a7eae952a3908103f7", size = 16729263, upload-time = "2025-11-16T22:52:36.369Z" }, + { url = "https://files.pythonhosted.org/packages/2d/ee/346fa473e666fe14c52fcdd19ec2424157290a032d4c41f98127bfb31ac7/numpy-2.3.5-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:f16417ec91f12f814b10bafe79ef77e70113a2f5f7018640e7425ff979253425", size = 12967213, upload-time = "2025-11-16T22:52:39.38Z" }, +] + +[[package]] +name = "packaging" +version = "25.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" }, +] + +[[package]] +name = "pathspec" +version = "0.12.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ca/bc/f35b8446f4531a7cb215605d100cd88b7ac6f44ab3fc94870c120ab3adbf/pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712", size = 51043, upload-time = "2023-12-10T22:30:45Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", size = 31191, upload-time = "2023-12-10T22:30:43.14Z" }, +] + +[[package]] +name = "platformdirs" +version = "4.4.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.10'", +] +sdist = { url = "https://files.pythonhosted.org/packages/23/e8/21db9c9987b0e728855bd57bff6984f67952bea55d6f75e055c46b5383e8/platformdirs-4.4.0.tar.gz", hash = "sha256:ca753cf4d81dc309bc67b0ea38fd15dc97bc30ce419a7f58d13eb3bf14c4febf", size = 21634, upload-time = "2025-08-26T14:32:04.268Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/40/4b/2028861e724d3bd36227adfa20d3fd24c3fc6d52032f4a93c133be5d17ce/platformdirs-4.4.0-py3-none-any.whl", hash = "sha256:abd01743f24e5287cd7a5db3752faf1a2d65353f38ec26d98e25a6db65958c85", size = 18654, upload-time = "2025-08-26T14:32:02.735Z" }, +] + +[[package]] +name = "platformdirs" +version = "4.5.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.11'", + "python_full_version == '3.10.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/61/33/9611380c2bdb1225fdef633e2a9610622310fed35ab11dac9620972ee088/platformdirs-4.5.0.tar.gz", hash = "sha256:70ddccdd7c99fc5942e9fc25636a8b34d04c24b335100223152c2803e4063312", size = 21632, upload-time = "2025-10-08T17:44:48.791Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/73/cb/ac7874b3e5d58441674fb70742e6c374b28b0c7cb988d37d991cde47166c/platformdirs-4.5.0-py3-none-any.whl", hash = "sha256:e578a81bb873cbb89a41fcc904c7ef523cc18284b7e3b3ccf06aca1403b7ebd3", size = 18651, upload-time = "2025-10-08T17:44:47.223Z" }, +] + +[[package]] +name = "pluggy" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +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 = "pre-commit" +version = "4.3.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.10'", +] +dependencies = [ + { name = "cfgv", marker = "python_full_version < '3.10'" }, + { name = "identify", marker = "python_full_version < '3.10'" }, + { name = "nodeenv", marker = "python_full_version < '3.10'" }, + { name = "pyyaml", marker = "python_full_version < '3.10'" }, + { name = "virtualenv", marker = "python_full_version < '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ff/29/7cf5bbc236333876e4b41f56e06857a87937ce4bf91e117a6991a2dbb02a/pre_commit-4.3.0.tar.gz", hash = "sha256:499fe450cc9d42e9d58e606262795ecb64dd05438943c62b66f6a8673da30b16", size = 193792, upload-time = "2025-08-09T18:56:14.651Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5b/a5/987a405322d78a73b66e39e4a90e4ef156fd7141bf71df987e50717c321b/pre_commit-4.3.0-py2.py3-none-any.whl", hash = "sha256:2b0747ad7e6e967169136edffee14c16e148a778a54e4f967921aa1ebf2308d8", size = 220965, upload-time = "2025-08-09T18:56:13.192Z" }, +] + +[[package]] +name = "pre-commit" +version = "4.4.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.11'", + "python_full_version == '3.10.*'", +] +dependencies = [ + { name = "cfgv", marker = "python_full_version >= '3.10'" }, + { name = "identify", marker = "python_full_version >= '3.10'" }, + { name = "nodeenv", marker = "python_full_version >= '3.10'" }, + { name = "pyyaml", marker = "python_full_version >= '3.10'" }, + { name = "virtualenv", marker = "python_full_version >= '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a6/49/7845c2d7bf6474efd8e27905b51b11e6ce411708c91e829b93f324de9929/pre_commit-4.4.0.tar.gz", hash = "sha256:f0233ebab440e9f17cabbb558706eb173d19ace965c68cdce2c081042b4fab15", size = 197501, upload-time = "2025-11-08T21:12:11.607Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/27/11/574fe7d13acf30bfd0a8dd7fa1647040f2b8064f13f43e8c963b1e65093b/pre_commit-4.4.0-py2.py3-none-any.whl", hash = "sha256:b35ea52957cbf83dcc5d8ee636cbead8624e3a15fbfa61a370e42158ac8a5813", size = 226049, upload-time = "2025-11-08T21:12:10.228Z" }, +] + +[[package]] +name = "protobuf" +version = "6.33.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0a/03/a1440979a3f74f16cab3b75b0da1a1a7f922d56a8ddea96092391998edc0/protobuf-6.33.1.tar.gz", hash = "sha256:97f65757e8d09870de6fd973aeddb92f85435607235d20b2dfed93405d00c85b", size = 443432, upload-time = "2025-11-13T16:44:18.895Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/06/f1/446a9bbd2c60772ca36556bac8bfde40eceb28d9cc7838755bc41e001d8f/protobuf-6.33.1-cp310-abi3-win32.whl", hash = "sha256:f8d3fdbc966aaab1d05046d0240dd94d40f2a8c62856d41eaa141ff64a79de6b", size = 425593, upload-time = "2025-11-13T16:44:06.275Z" }, + { url = "https://files.pythonhosted.org/packages/a6/79/8780a378c650e3df849b73de8b13cf5412f521ca2ff9b78a45c247029440/protobuf-6.33.1-cp310-abi3-win_amd64.whl", hash = "sha256:923aa6d27a92bf44394f6abf7ea0500f38769d4b07f4be41cb52bd8b1123b9ed", size = 436883, upload-time = "2025-11-13T16:44:09.222Z" }, + { url = "https://files.pythonhosted.org/packages/cd/93/26213ff72b103ae55bb0d73e7fb91ea570ef407c3ab4fd2f1f27cac16044/protobuf-6.33.1-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:fe34575f2bdde76ac429ec7b570235bf0c788883e70aee90068e9981806f2490", size = 427522, upload-time = "2025-11-13T16:44:10.475Z" }, + { url = "https://files.pythonhosted.org/packages/c2/32/df4a35247923393aa6b887c3b3244a8c941c32a25681775f96e2b418f90e/protobuf-6.33.1-cp39-abi3-manylinux2014_aarch64.whl", hash = "sha256:f8adba2e44cde2d7618996b3fc02341f03f5bc3f2748be72dc7b063319276178", size = 324445, upload-time = "2025-11-13T16:44:11.869Z" }, + { url = "https://files.pythonhosted.org/packages/8e/d0/d796e419e2ec93d2f3fa44888861c3f88f722cde02b7c3488fcc6a166820/protobuf-6.33.1-cp39-abi3-manylinux2014_s390x.whl", hash = "sha256:0f4cf01222c0d959c2b399142deb526de420be8236f22c71356e2a544e153c53", size = 339161, upload-time = "2025-11-13T16:44:12.778Z" }, + { url = "https://files.pythonhosted.org/packages/1d/2a/3c5f05a4af06649547027d288747f68525755de692a26a7720dced3652c0/protobuf-6.33.1-cp39-abi3-manylinux2014_x86_64.whl", hash = "sha256:8fd7d5e0eb08cd5b87fd3df49bc193f5cfd778701f47e11d127d0afc6c39f1d1", size = 323171, upload-time = "2025-11-13T16:44:14.035Z" }, + { url = "https://files.pythonhosted.org/packages/a0/86/a801cbb316860004bd865b1ded691c53e41d4a8224e3e421f8394174aba7/protobuf-6.33.1-cp39-cp39-win32.whl", hash = "sha256:023af8449482fa884d88b4563d85e83accab54138ae098924a985bcbb734a213", size = 425689, upload-time = "2025-11-13T16:44:15.389Z" }, + { url = "https://files.pythonhosted.org/packages/a2/0f/77b5a12825d59af2596634f062eb1a472f44494965a05dcd97cb5daf3ae5/protobuf-6.33.1-cp39-cp39-win_amd64.whl", hash = "sha256:df051de4fd7e5e4371334e234c62ba43763f15ab605579e04c7008c05735cd82", size = 436877, upload-time = "2025-11-13T16:44:16.71Z" }, + { url = "https://files.pythonhosted.org/packages/08/b4/46310463b4f6ceef310f8348786f3cff181cea671578e3d9743ba61a459e/protobuf-6.33.1-py3-none-any.whl", hash = "sha256:d595a9fd694fdeb061a62fbe10eb039cc1e444df81ec9bb70c7fc59ebcb1eafa", size = 170477, upload-time = "2025-11-13T16:44:17.633Z" }, +] + +[[package]] +name = "pygments" +version = "2.19.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, +] + +[[package]] +name = "pyplatec" +version = "1.4.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/af/67/2a527aca4ca0a2047749239aa2fe0999e68b7b9a0d8cc41319e399cf5939/pyplatec-1.4.2.tar.gz", hash = "sha256:b34347a811961f568aaf9ceee4a05a072f7ecbc8a3eb34f811ee7abe97864b56", size = 54359, upload-time = "2025-11-17T20:03:03.044Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/21/5c/e02ae7cd05dca4da6028f9a99904f4e2ec95b7b0158b70af439e6b6aba1d/pyplatec-1.4.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:35d62e8f59bb5571cde3eb92c43378bb8a99919e13030aac44d819c27fb5f315", size = 130248, upload-time = "2025-11-17T20:02:27.554Z" }, + { url = "https://files.pythonhosted.org/packages/ab/6b/41b5277eda5eb73fd7b615ddd179d1f41637c960e7efe4d1896ab890aaf7/pyplatec-1.4.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d5e5646dfdf229b7d169878fcc5155fd45a2edc8ed3d4a0232ec02d6b67c6438", size = 651959, upload-time = "2025-11-17T20:02:31.005Z" }, + { url = "https://files.pythonhosted.org/packages/a3/fb/bf1cf8970b62fc15496deb4675f95a825f53c4cc3a6b3306a1f207ef3bb4/pyplatec-1.4.2-cp310-cp310-win_amd64.whl", hash = "sha256:f4d4ef4d73092195a09a85e6d54a9ae92b1e7ced7b145fc511fdda019627f244", size = 52437, upload-time = "2025-11-17T20:02:34.848Z" }, + { url = "https://files.pythonhosted.org/packages/ef/e0/660e159418bba8b3503d84a1c83ed1d1bd8af58e95c41dba05194491dc77/pyplatec-1.4.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7a11cb80d8ec5046172c2a1d48104e229b74a7509674a52f260299df58ad5a0e", size = 130272, upload-time = "2025-11-17T20:02:35.953Z" }, + { url = "https://files.pythonhosted.org/packages/73/53/89bcc953abf3733745d0608ead9c84090c605d7e3f6b121c0c18f65b6ef6/pyplatec-1.4.2-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:388017ebdbed6225e86680e2dc06b273a568aeb639f7f6e3176ff164271ad682", size = 620355, upload-time = "2025-11-17T20:02:37.88Z" }, + { url = "https://files.pythonhosted.org/packages/44/59/f129d23cd8d655cef6cfdffd743bee86e695c54dbbba98c74ac65b214167/pyplatec-1.4.2-cp311-cp311-win_amd64.whl", hash = "sha256:3f59d05c604f365a0e2869fbf8b23eb20f64b8280805370a12e5c54f4c590451", size = 52432, upload-time = "2025-11-17T20:02:38.89Z" }, + { url = "https://files.pythonhosted.org/packages/8a/71/ae8d5b56791fcbe1d34a43a55a9dcbbd932a06947ac58951aba649641402/pyplatec-1.4.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:136f630841a07c34b4f782b8d941428c6f741fc9026e0b199a0b3299f89ac88a", size = 130352, upload-time = "2025-11-17T20:02:40.383Z" }, + { url = "https://files.pythonhosted.org/packages/cc/9b/d0a82fd3b08d7f62d94c881a2fb96c6f46b336812aabb54f0ad677047da6/pyplatec-1.4.2-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1d2b5dd4316f12e9a1c5ec4eccb4e833ba745c302e503b79868ae31135c11cf4", size = 620537, upload-time = "2025-11-17T20:02:42.811Z" }, + { url = "https://files.pythonhosted.org/packages/d0/e0/2a962f5bd1fed41f9fb794820f19929fe6f62f611c8ca2e45365b77f68dd/pyplatec-1.4.2-cp312-cp312-win_amd64.whl", hash = "sha256:2169026e209c9c5b9a8ed20dbef87d7b66a5913e2dc9210b73b14ee45562f8e5", size = 52475, upload-time = "2025-11-17T20:02:44.144Z" }, + { url = "https://files.pythonhosted.org/packages/c4/86/f291e38405b66295183f87ce4a0b21ee740130f6dc9948ca417177de275b/pyplatec-1.4.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:d432b3c1759b22c6481d48b28f9fef7026dea431e4a4481fbacca752c86dc00f", size = 130362, upload-time = "2025-11-17T20:02:47.519Z" }, + { url = "https://files.pythonhosted.org/packages/30/bb/61076667bd5cf64919d22c7ecb9e37bf0fb7074455a2ee3a37248fa8eb4f/pyplatec-1.4.2-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e8c808d4d7082ab569b57bd4f70440067c9b90208f641bed6c02bf50f86851e8", size = 620463, upload-time = "2025-11-17T20:02:49.497Z" }, + { url = "https://files.pythonhosted.org/packages/23/38/ce0e36bcdc93ece0ca5c2f625c0c0369b8e0e06fad43f3229729483d10a4/pyplatec-1.4.2-cp313-cp313-win_amd64.whl", hash = "sha256:b62f7abc794c0432ca2c4d2c8f2bf6f91a6cb96a254e617e17e020d6170e42a5", size = 52471, upload-time = "2025-11-17T20:02:51.923Z" }, + { url = "https://files.pythonhosted.org/packages/fd/bd/21e34279e7efec458e4f6937efc139ea2ef089fb7a277990f3aafae169d6/pyplatec-1.4.2-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:6492a235a4946d7bd220b6a45583a647e5d38438a0e7e23286222be9d10ca328", size = 130435, upload-time = "2025-11-17T20:02:53.38Z" }, + { url = "https://files.pythonhosted.org/packages/c1/8c/44fd62d2a588e22fcb74116bfb5e59ae8bfbc673448a33bb324262c75bae/pyplatec-1.4.2-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7f6cfc2f48edf2592b8f9b40c9281c7f9f6d169912ebe14faccd7b6151edcddf", size = 620494, upload-time = "2025-11-17T20:02:55.819Z" }, + { url = "https://files.pythonhosted.org/packages/91/7f/b12cbc139dc50f48f94b3ffca2393689ce9d67964ab98b9a74db29f976b2/pyplatec-1.4.2-cp314-cp314-win_amd64.whl", hash = "sha256:c510a7227c71664baa20fcc69b95aafa248e1d841c30c794e321dde740e747c5", size = 53777, upload-time = "2025-11-17T20:02:56.902Z" }, + { url = "https://files.pythonhosted.org/packages/38/fa/42b3f3ff14a0cb1af2df92a273d77e3a698b5be0b67825f43690535c45f1/pyplatec-1.4.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e895ff8f27f26c62bfb06fbdda8235c11e32833da411600c2ebfddbcb736d9fa", size = 130248, upload-time = "2025-11-17T20:02:58.228Z" }, + { url = "https://files.pythonhosted.org/packages/09/fc/c099e1b78d9ab2ae6c14b1a85c759560bfe84f7b940afb0af694aa1030eb/pyplatec-1.4.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9623a16b6b6d4e81593c5e5860a70bb049b3395b21a42bf79fdf137a6eff9676", size = 651791, upload-time = "2025-11-17T20:02:59.77Z" }, + { url = "https://files.pythonhosted.org/packages/7b/5c/37c39ea0e24df70b5a3441659f3e485a338690f60310c0f06d1ae1e1098c/pyplatec-1.4.2-cp39-cp39-win_amd64.whl", hash = "sha256:81cd4f92264a25cb09bc4bb3ea491ad48972d1b0330e5a44fcd11279645df7a7", size = 52428, upload-time = "2025-11-17T20:03:00.912Z" }, +] + +[[package]] +name = "pypng" +version = "0.20220715.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/93/cd/112f092ec27cca83e0516de0a3368dbd9128c187fb6b52aaaa7cde39c96d/pypng-0.20220715.0.tar.gz", hash = "sha256:739c433ba96f078315de54c0db975aee537cbc3e1d0ae4ed9aab0ca1e427e2c1", size = 128992, upload-time = "2022-07-15T14:11:05.301Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3e/b9/3766cc361d93edb2ce81e2e1f87dd98f314d7d513877a342d31b30741680/pypng-0.20220715.0-py3-none-any.whl", hash = "sha256:4a43e969b8f5aaafb2a415536c1a8ec7e341cd6a3f957fd5b5f32a4cfeed902c", size = 58057, upload-time = "2022-07-15T14:11:03.713Z" }, +] + +[[package]] +name = "pyproject-api" +version = "1.9.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.10'", +] +dependencies = [ + { name = "packaging", marker = "python_full_version < '3.10'" }, + { name = "tomli", marker = "python_full_version < '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/19/fd/437901c891f58a7b9096511750247535e891d2d5a5a6eefbc9386a2b41d5/pyproject_api-1.9.1.tar.gz", hash = "sha256:43c9918f49daab37e302038fc1aed54a8c7a91a9fa935d00b9a485f37e0f5335", size = 22710, upload-time = "2025-05-12T14:41:58.025Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ef/e6/c293c06695d4a3ab0260ef124a74ebadba5f4c511ce3a4259e976902c00b/pyproject_api-1.9.1-py3-none-any.whl", hash = "sha256:7d6238d92f8962773dd75b5f0c4a6a27cce092a14b623b811dba656f3b628948", size = 13158, upload-time = "2025-05-12T14:41:56.217Z" }, +] + +[[package]] +name = "pyproject-api" +version = "1.10.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.11'", + "python_full_version == '3.10.*'", +] +dependencies = [ + { name = "packaging", marker = "python_full_version >= '3.10'" }, + { name = "tomli", marker = "python_full_version == '3.10.*'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/45/7b/c0e1333b61d41c69e59e5366e727b18c4992688caf0de1be10b3e5265f6b/pyproject_api-1.10.0.tar.gz", hash = "sha256:40c6f2d82eebdc4afee61c773ed208c04c19db4c4a60d97f8d7be3ebc0bbb330", size = 22785, upload-time = "2025-10-09T19:12:27.21Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/cc/cecf97be298bee2b2a37dd360618c819a2a7fd95251d8e480c1f0eb88f3b/pyproject_api-1.10.0-py3-none-any.whl", hash = "sha256:8757c41a79c0f4ab71b99abed52b97ecf66bd20b04fa59da43b5840bac105a09", size = 13218, upload-time = "2025-10-09T19:12:24.428Z" }, +] + +[[package]] +name = "pytest" +version = "8.4.2" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.10'", +] +dependencies = [ + { name = "colorama", marker = "python_full_version < '3.10' and sys_platform == 'win32'" }, + { name = "exceptiongroup", marker = "python_full_version < '3.10'" }, + { name = "iniconfig", version = "2.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "packaging", marker = "python_full_version < '3.10'" }, + { name = "pluggy", marker = "python_full_version < '3.10'" }, + { name = "pygments", marker = "python_full_version < '3.10'" }, + { name = "tomli", marker = "python_full_version < '3.10'" }, +] +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.11'", + "python_full_version == '3.10.*'", +] +dependencies = [ + { name = "colorama", marker = "python_full_version >= '3.10' and sys_platform == 'win32'" }, + { name = "exceptiongroup", marker = "python_full_version == '3.10.*'" }, + { name = "iniconfig", version = "2.3.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "packaging", marker = "python_full_version >= '3.10'" }, + { name = "pluggy", marker = "python_full_version >= '3.10'" }, + { name = "pygments", marker = "python_full_version >= '3.10'" }, + { name = "tomli", marker = "python_full_version == '3.10.*'" }, +] +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-cov" +version = "7.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "coverage", version = "7.10.7", source = { registry = "https://pypi.org/simple" }, extra = ["toml"], marker = "python_full_version < '3.10'" }, + { name = "coverage", version = "7.11.3", source = { registry = "https://pypi.org/simple" }, extra = ["toml"], marker = "python_full_version >= '3.10'" }, + { name = "pluggy" }, + { 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/5e/f7/c933acc76f5208b3b00089573cf6a2bc26dc80a8aece8f52bb7d6b1855ca/pytest_cov-7.0.0.tar.gz", hash = "sha256:33c97eda2e049a0c5298e91f519302a1334c26ac65c1a483d6206fd458361af1", size = 54328, upload-time = "2025-09-09T10:57:02.113Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ee/49/1377b49de7d0c1ce41292161ea0f721913fa8722c19fb9c1e3aa0367eecb/pytest_cov-7.0.0-py3-none-any.whl", hash = "sha256:3b8e9558b16cc1479da72058bdecf8073661c7f57f7d3c5f22a1c23507f2d861", size = 22424, upload-time = "2025-09-09T10:57:00.695Z" }, +] + +[[package]] +name = "pyyaml" +version = "6.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/a0/39350dd17dd6d6c6507025c0e53aef67a9293a6d37d3511f23ea510d5800/pyyaml-6.0.3-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:214ed4befebe12df36bcc8bc2b64b396ca31be9304b8f59e25c11cf94a4c033b", size = 184227, upload-time = "2025-09-25T21:31:46.04Z" }, + { url = "https://files.pythonhosted.org/packages/05/14/52d505b5c59ce73244f59c7a50ecf47093ce4765f116cdb98286a71eeca2/pyyaml-6.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:02ea2dfa234451bbb8772601d7b8e426c2bfa197136796224e50e35a78777956", size = 174019, upload-time = "2025-09-25T21:31:47.706Z" }, + { url = "https://files.pythonhosted.org/packages/43/f7/0e6a5ae5599c838c696adb4e6330a59f463265bfa1e116cfd1fbb0abaaae/pyyaml-6.0.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b30236e45cf30d2b8e7b3e85881719e98507abed1011bf463a8fa23e9c3e98a8", size = 740646, upload-time = "2025-09-25T21:31:49.21Z" }, + { url = "https://files.pythonhosted.org/packages/2f/3a/61b9db1d28f00f8fd0ae760459a5c4bf1b941baf714e207b6eb0657d2578/pyyaml-6.0.3-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:66291b10affd76d76f54fad28e22e51719ef9ba22b29e1d7d03d6777a9174198", size = 840793, upload-time = "2025-09-25T21:31:50.735Z" }, + { url = "https://files.pythonhosted.org/packages/7a/1e/7acc4f0e74c4b3d9531e24739e0ab832a5edf40e64fbae1a9c01941cabd7/pyyaml-6.0.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9c7708761fccb9397fe64bbc0395abcae8c4bf7b0eac081e12b809bf47700d0b", size = 770293, upload-time = "2025-09-25T21:31:51.828Z" }, + { url = "https://files.pythonhosted.org/packages/8b/ef/abd085f06853af0cd59fa5f913d61a8eab65d7639ff2a658d18a25d6a89d/pyyaml-6.0.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:418cf3f2111bc80e0933b2cd8cd04f286338bb88bdc7bc8e6dd775ebde60b5e0", size = 732872, upload-time = "2025-09-25T21:31:53.282Z" }, + { url = "https://files.pythonhosted.org/packages/1f/15/2bc9c8faf6450a8b3c9fc5448ed869c599c0a74ba2669772b1f3a0040180/pyyaml-6.0.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5e0b74767e5f8c593e8c9b5912019159ed0533c70051e9cce3e8b6aa699fcd69", size = 758828, upload-time = "2025-09-25T21:31:54.807Z" }, + { url = "https://files.pythonhosted.org/packages/a3/00/531e92e88c00f4333ce359e50c19b8d1de9fe8d581b1534e35ccfbc5f393/pyyaml-6.0.3-cp310-cp310-win32.whl", hash = "sha256:28c8d926f98f432f88adc23edf2e6d4921ac26fb084b028c733d01868d19007e", size = 142415, upload-time = "2025-09-25T21:31:55.885Z" }, + { url = "https://files.pythonhosted.org/packages/2a/fa/926c003379b19fca39dd4634818b00dec6c62d87faf628d1394e137354d4/pyyaml-6.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:bdb2c67c6c1390b63c6ff89f210c8fd09d9a1217a465701eac7316313c915e4c", size = 158561, upload-time = "2025-09-25T21:31:57.406Z" }, + { url = "https://files.pythonhosted.org/packages/6d/16/a95b6757765b7b031c9374925bb718d55e0a9ba8a1b6a12d25962ea44347/pyyaml-6.0.3-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:44edc647873928551a01e7a563d7452ccdebee747728c1080d881d68af7b997e", size = 185826, upload-time = "2025-09-25T21:31:58.655Z" }, + { url = "https://files.pythonhosted.org/packages/16/19/13de8e4377ed53079ee996e1ab0a9c33ec2faf808a4647b7b4c0d46dd239/pyyaml-6.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:652cb6edd41e718550aad172851962662ff2681490a8a711af6a4d288dd96824", size = 175577, upload-time = "2025-09-25T21:32:00.088Z" }, + { url = "https://files.pythonhosted.org/packages/0c/62/d2eb46264d4b157dae1275b573017abec435397aa59cbcdab6fc978a8af4/pyyaml-6.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:10892704fc220243f5305762e276552a0395f7beb4dbf9b14ec8fd43b57f126c", size = 775556, upload-time = "2025-09-25T21:32:01.31Z" }, + { url = "https://files.pythonhosted.org/packages/10/cb/16c3f2cf3266edd25aaa00d6c4350381c8b012ed6f5276675b9eba8d9ff4/pyyaml-6.0.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:850774a7879607d3a6f50d36d04f00ee69e7fc816450e5f7e58d7f17f1ae5c00", size = 882114, upload-time = "2025-09-25T21:32:03.376Z" }, + { url = "https://files.pythonhosted.org/packages/71/60/917329f640924b18ff085ab889a11c763e0b573da888e8404ff486657602/pyyaml-6.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b8bb0864c5a28024fac8a632c443c87c5aa6f215c0b126c449ae1a150412f31d", size = 806638, upload-time = "2025-09-25T21:32:04.553Z" }, + { url = "https://files.pythonhosted.org/packages/dd/6f/529b0f316a9fd167281a6c3826b5583e6192dba792dd55e3203d3f8e655a/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d37d57ad971609cf3c53ba6a7e365e40660e3be0e5175fa9f2365a379d6095a", size = 767463, upload-time = "2025-09-25T21:32:06.152Z" }, + { url = "https://files.pythonhosted.org/packages/f2/6a/b627b4e0c1dd03718543519ffb2f1deea4a1e6d42fbab8021936a4d22589/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:37503bfbfc9d2c40b344d06b2199cf0e96e97957ab1c1b546fd4f87e53e5d3e4", size = 794986, upload-time = "2025-09-25T21:32:07.367Z" }, + { url = "https://files.pythonhosted.org/packages/45/91/47a6e1c42d9ee337c4839208f30d9f09caa9f720ec7582917b264defc875/pyyaml-6.0.3-cp311-cp311-win32.whl", hash = "sha256:8098f252adfa6c80ab48096053f512f2321f0b998f98150cea9bd23d83e1467b", size = 142543, upload-time = "2025-09-25T21:32:08.95Z" }, + { url = "https://files.pythonhosted.org/packages/da/e3/ea007450a105ae919a72393cb06f122f288ef60bba2dc64b26e2646fa315/pyyaml-6.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:9f3bfb4965eb874431221a3ff3fdcddc7e74e3b07799e0e84ca4a0f867d449bf", size = 158763, upload-time = "2025-09-25T21:32:09.96Z" }, + { url = "https://files.pythonhosted.org/packages/d1/33/422b98d2195232ca1826284a76852ad5a86fe23e31b009c9886b2d0fb8b2/pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196", size = 182063, upload-time = "2025-09-25T21:32:11.445Z" }, + { url = "https://files.pythonhosted.org/packages/89/a0/6cf41a19a1f2f3feab0e9c0b74134aa2ce6849093d5517a0c550fe37a648/pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0", size = 173973, upload-time = "2025-09-25T21:32:12.492Z" }, + { url = "https://files.pythonhosted.org/packages/ed/23/7a778b6bd0b9a8039df8b1b1d80e2e2ad78aa04171592c8a5c43a56a6af4/pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28", size = 775116, upload-time = "2025-09-25T21:32:13.652Z" }, + { url = "https://files.pythonhosted.org/packages/65/30/d7353c338e12baef4ecc1b09e877c1970bd3382789c159b4f89d6a70dc09/pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c", size = 844011, upload-time = "2025-09-25T21:32:15.21Z" }, + { url = "https://files.pythonhosted.org/packages/8b/9d/b3589d3877982d4f2329302ef98a8026e7f4443c765c46cfecc8858c6b4b/pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc", size = 807870, upload-time = "2025-09-25T21:32:16.431Z" }, + { url = "https://files.pythonhosted.org/packages/05/c0/b3be26a015601b822b97d9149ff8cb5ead58c66f981e04fedf4e762f4bd4/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e", size = 761089, upload-time = "2025-09-25T21:32:17.56Z" }, + { url = "https://files.pythonhosted.org/packages/be/8e/98435a21d1d4b46590d5459a22d88128103f8da4c2d4cb8f14f2a96504e1/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea", size = 790181, upload-time = "2025-09-25T21:32:18.834Z" }, + { url = "https://files.pythonhosted.org/packages/74/93/7baea19427dcfbe1e5a372d81473250b379f04b1bd3c4c5ff825e2327202/pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5", size = 137658, upload-time = "2025-09-25T21:32:20.209Z" }, + { url = "https://files.pythonhosted.org/packages/86/bf/899e81e4cce32febab4fb42bb97dcdf66bc135272882d1987881a4b519e9/pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b", size = 154003, upload-time = "2025-09-25T21:32:21.167Z" }, + { url = "https://files.pythonhosted.org/packages/1a/08/67bd04656199bbb51dbed1439b7f27601dfb576fb864099c7ef0c3e55531/pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd", size = 140344, upload-time = "2025-09-25T21:32:22.617Z" }, + { url = "https://files.pythonhosted.org/packages/d1/11/0fd08f8192109f7169db964b5707a2f1e8b745d4e239b784a5a1dd80d1db/pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8", size = 181669, upload-time = "2025-09-25T21:32:23.673Z" }, + { url = "https://files.pythonhosted.org/packages/b1/16/95309993f1d3748cd644e02e38b75d50cbc0d9561d21f390a76242ce073f/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1", size = 173252, upload-time = "2025-09-25T21:32:25.149Z" }, + { url = "https://files.pythonhosted.org/packages/50/31/b20f376d3f810b9b2371e72ef5adb33879b25edb7a6d072cb7ca0c486398/pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c", size = 767081, upload-time = "2025-09-25T21:32:26.575Z" }, + { url = "https://files.pythonhosted.org/packages/49/1e/a55ca81e949270d5d4432fbbd19dfea5321eda7c41a849d443dc92fd1ff7/pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5", size = 841159, upload-time = "2025-09-25T21:32:27.727Z" }, + { url = "https://files.pythonhosted.org/packages/74/27/e5b8f34d02d9995b80abcef563ea1f8b56d20134d8f4e5e81733b1feceb2/pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6", size = 801626, upload-time = "2025-09-25T21:32:28.878Z" }, + { url = "https://files.pythonhosted.org/packages/f9/11/ba845c23988798f40e52ba45f34849aa8a1f2d4af4b798588010792ebad6/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6", size = 753613, upload-time = "2025-09-25T21:32:30.178Z" }, + { url = "https://files.pythonhosted.org/packages/3d/e0/7966e1a7bfc0a45bf0a7fb6b98ea03fc9b8d84fa7f2229e9659680b69ee3/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be", size = 794115, upload-time = "2025-09-25T21:32:31.353Z" }, + { url = "https://files.pythonhosted.org/packages/de/94/980b50a6531b3019e45ddeada0626d45fa85cbe22300844a7983285bed3b/pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26", size = 137427, upload-time = "2025-09-25T21:32:32.58Z" }, + { url = "https://files.pythonhosted.org/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c", size = 154090, upload-time = "2025-09-25T21:32:33.659Z" }, + { url = "https://files.pythonhosted.org/packages/73/e8/2bdf3ca2090f68bb3d75b44da7bbc71843b19c9f2b9cb9b0f4ab7a5a4329/pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb", size = 140246, upload-time = "2025-09-25T21:32:34.663Z" }, + { url = "https://files.pythonhosted.org/packages/9d/8c/f4bd7f6465179953d3ac9bc44ac1a8a3e6122cf8ada906b4f96c60172d43/pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac", size = 181814, upload-time = "2025-09-25T21:32:35.712Z" }, + { url = "https://files.pythonhosted.org/packages/bd/9c/4d95bb87eb2063d20db7b60faa3840c1b18025517ae857371c4dd55a6b3a/pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310", size = 173809, upload-time = "2025-09-25T21:32:36.789Z" }, + { url = "https://files.pythonhosted.org/packages/92/b5/47e807c2623074914e29dabd16cbbdd4bf5e9b2db9f8090fa64411fc5382/pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7", size = 766454, upload-time = "2025-09-25T21:32:37.966Z" }, + { url = "https://files.pythonhosted.org/packages/02/9e/e5e9b168be58564121efb3de6859c452fccde0ab093d8438905899a3a483/pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788", size = 836355, upload-time = "2025-09-25T21:32:39.178Z" }, + { url = "https://files.pythonhosted.org/packages/88/f9/16491d7ed2a919954993e48aa941b200f38040928474c9e85ea9e64222c3/pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5", size = 794175, upload-time = "2025-09-25T21:32:40.865Z" }, + { url = "https://files.pythonhosted.org/packages/dd/3f/5989debef34dc6397317802b527dbbafb2b4760878a53d4166579111411e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764", size = 755228, upload-time = "2025-09-25T21:32:42.084Z" }, + { url = "https://files.pythonhosted.org/packages/d7/ce/af88a49043cd2e265be63d083fc75b27b6ed062f5f9fd6cdc223ad62f03e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35", size = 789194, upload-time = "2025-09-25T21:32:43.362Z" }, + { url = "https://files.pythonhosted.org/packages/23/20/bb6982b26a40bb43951265ba29d4c246ef0ff59c9fdcdf0ed04e0687de4d/pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac", size = 156429, upload-time = "2025-09-25T21:32:57.844Z" }, + { url = "https://files.pythonhosted.org/packages/f4/f4/a4541072bb9422c8a883ab55255f918fa378ecf083f5b85e87fc2b4eda1b/pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3", size = 143912, upload-time = "2025-09-25T21:32:59.247Z" }, + { url = "https://files.pythonhosted.org/packages/7c/f9/07dd09ae774e4616edf6cda684ee78f97777bdd15847253637a6f052a62f/pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3", size = 189108, upload-time = "2025-09-25T21:32:44.377Z" }, + { url = "https://files.pythonhosted.org/packages/4e/78/8d08c9fb7ce09ad8c38ad533c1191cf27f7ae1effe5bb9400a46d9437fcf/pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba", size = 183641, upload-time = "2025-09-25T21:32:45.407Z" }, + { url = "https://files.pythonhosted.org/packages/7b/5b/3babb19104a46945cf816d047db2788bcaf8c94527a805610b0289a01c6b/pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c", size = 831901, upload-time = "2025-09-25T21:32:48.83Z" }, + { url = "https://files.pythonhosted.org/packages/8b/cc/dff0684d8dc44da4d22a13f35f073d558c268780ce3c6ba1b87055bb0b87/pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702", size = 861132, upload-time = "2025-09-25T21:32:50.149Z" }, + { url = "https://files.pythonhosted.org/packages/b1/5e/f77dc6b9036943e285ba76b49e118d9ea929885becb0a29ba8a7c75e29fe/pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c", size = 839261, upload-time = "2025-09-25T21:32:51.808Z" }, + { url = "https://files.pythonhosted.org/packages/ce/88/a9db1376aa2a228197c58b37302f284b5617f56a5d959fd1763fb1675ce6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065", size = 805272, upload-time = "2025-09-25T21:32:52.941Z" }, + { url = "https://files.pythonhosted.org/packages/da/92/1446574745d74df0c92e6aa4a7b0b3130706a4142b2d1a5869f2eaa423c6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65", size = 829923, upload-time = "2025-09-25T21:32:54.537Z" }, + { url = "https://files.pythonhosted.org/packages/f0/7a/1c7270340330e575b92f397352af856a8c06f230aa3e76f86b39d01b416a/pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9", size = 174062, upload-time = "2025-09-25T21:32:55.767Z" }, + { url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" }, + { url = "https://files.pythonhosted.org/packages/9f/62/67fc8e68a75f738c9200422bf65693fb79a4cd0dc5b23310e5202e978090/pyyaml-6.0.3-cp39-cp39-macosx_10_13_x86_64.whl", hash = "sha256:b865addae83924361678b652338317d1bd7e79b1f4596f96b96c77a5a34b34da", size = 184450, upload-time = "2025-09-25T21:33:00.618Z" }, + { url = "https://files.pythonhosted.org/packages/ae/92/861f152ce87c452b11b9d0977952259aa7df792d71c1053365cc7b09cc08/pyyaml-6.0.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c3355370a2c156cffb25e876646f149d5d68f5e0a3ce86a5084dd0b64a994917", size = 174319, upload-time = "2025-09-25T21:33:02.086Z" }, + { url = "https://files.pythonhosted.org/packages/d0/cd/f0cfc8c74f8a030017a2b9c771b7f47e5dd702c3e28e5b2071374bda2948/pyyaml-6.0.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3c5677e12444c15717b902a5798264fa7909e41153cdf9ef7ad571b704a63dd9", size = 737631, upload-time = "2025-09-25T21:33:03.25Z" }, + { url = "https://files.pythonhosted.org/packages/ef/b2/18f2bd28cd2055a79a46c9b0895c0b3d987ce40ee471cecf58a1a0199805/pyyaml-6.0.3-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5ed875a24292240029e4483f9d4a4b8a1ae08843b9c54f43fcc11e404532a8a5", size = 836795, upload-time = "2025-09-25T21:33:05.014Z" }, + { url = "https://files.pythonhosted.org/packages/73/b9/793686b2d54b531203c160ef12bec60228a0109c79bae6c1277961026770/pyyaml-6.0.3-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0150219816b6a1fa26fb4699fb7daa9caf09eb1999f3b70fb6e786805e80375a", size = 750767, upload-time = "2025-09-25T21:33:06.398Z" }, + { url = "https://files.pythonhosted.org/packages/a9/86/a137b39a611def2ed78b0e66ce2fe13ee701a07c07aebe55c340ed2a050e/pyyaml-6.0.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:fa160448684b4e94d80416c0fa4aac48967a969efe22931448d853ada8baf926", size = 727982, upload-time = "2025-09-25T21:33:08.708Z" }, + { url = "https://files.pythonhosted.org/packages/dd/62/71c27c94f457cf4418ef8ccc71735324c549f7e3ea9d34aba50874563561/pyyaml-6.0.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:27c0abcb4a5dac13684a37f76e701e054692a9b2d3064b70f5e4eb54810553d7", size = 755677, upload-time = "2025-09-25T21:33:09.876Z" }, + { url = "https://files.pythonhosted.org/packages/29/3d/6f5e0d58bd924fb0d06c3a6bad00effbdae2de5adb5cda5648006ffbd8d3/pyyaml-6.0.3-cp39-cp39-win32.whl", hash = "sha256:1ebe39cb5fc479422b83de611d14e2c0d3bb2a18bbcb01f229ab3cfbd8fee7a0", size = 142592, upload-time = "2025-09-25T21:33:10.983Z" }, + { url = "https://files.pythonhosted.org/packages/f0/0c/25113e0b5e103d7f1490c0e947e303fe4a696c10b501dea7a9f49d4e876c/pyyaml-6.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:2e71d11abed7344e42a8849600193d15b6def118602c4c176f748e4583246007", size = 158777, upload-time = "2025-09-25T21:33:15.55Z" }, +] + +[[package]] +name = "requests" +version = "2.32.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "charset-normalizer" }, + { name = "idna" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c9/74/b3ff8e6c8446842c3f5c837e9c3dfcfe2018ea6ecef224c710c85ef728f4/requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf", size = 134517, upload-time = "2025-08-18T20:46:02.573Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" }, +] + +[[package]] +name = "roman-numerals-py" +version = "3.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/30/76/48fd56d17c5bdbdf65609abbc67288728a98ed4c02919428d4f52d23b24b/roman_numerals_py-3.1.0.tar.gz", hash = "sha256:be4bf804f083a4ce001b5eb7e3c0862479d10f94c936f6c4e5f250aa5ff5bd2d", size = 9017, upload-time = "2025-02-22T07:34:54.333Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/53/97/d2cbbaa10c9b826af0e10fdf836e1bf344d9f0abb873ebc34d1f49642d3f/roman_numerals_py-3.1.0-py3-none-any.whl", hash = "sha256:9da2ad2fb670bcf24e81070ceb3be72f6c11c440d73bd579fbeca1e9f330954c", size = 7742, upload-time = "2025-02-22T07:34:52.422Z" }, +] + +[[package]] +name = "ruff" +version = "0.14.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/82/fa/fbb67a5780ae0f704876cb8ac92d6d76da41da4dc72b7ed3565ab18f2f52/ruff-0.14.5.tar.gz", hash = "sha256:8d3b48d7d8aad423d3137af7ab6c8b1e38e4de104800f0d596990f6ada1a9fc1", size = 5615944, upload-time = "2025-11-13T19:58:51.155Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/68/31/c07e9c535248d10836a94e4f4e8c5a31a1beed6f169b31405b227872d4f4/ruff-0.14.5-py3-none-linux_armv6l.whl", hash = "sha256:f3b8248123b586de44a8018bcc9fefe31d23dda57a34e6f0e1e53bd51fd63594", size = 13171630, upload-time = "2025-11-13T19:57:54.894Z" }, + { url = "https://files.pythonhosted.org/packages/8e/5c/283c62516dca697cd604c2796d1487396b7a436b2f0ecc3fd412aca470e0/ruff-0.14.5-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:f7a75236570318c7a30edd7f5491945f0169de738d945ca8784500b517163a72", size = 13413925, upload-time = "2025-11-13T19:57:59.181Z" }, + { url = "https://files.pythonhosted.org/packages/b6/f3/aa319f4afc22cb6fcba2b9cdfc0f03bbf747e59ab7a8c5e90173857a1361/ruff-0.14.5-py3-none-macosx_11_0_arm64.whl", hash = "sha256:6d146132d1ee115f8802356a2dc9a634dbf58184c51bff21f313e8cd1c74899a", size = 12574040, upload-time = "2025-11-13T19:58:02.056Z" }, + { url = "https://files.pythonhosted.org/packages/f9/7f/cb5845fcc7c7e88ed57f58670189fc2ff517fe2134c3821e77e29fd3b0c8/ruff-0.14.5-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e2380596653dcd20b057794d55681571a257a42327da8894b93bbd6111aa801f", size = 13009755, upload-time = "2025-11-13T19:58:05.172Z" }, + { url = "https://files.pythonhosted.org/packages/21/d2/bcbedbb6bcb9253085981730687ddc0cc7b2e18e8dc13cf4453de905d7a0/ruff-0.14.5-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2d1fa985a42b1f075a098fa1ab9d472b712bdb17ad87a8ec86e45e7fa6273e68", size = 12937641, upload-time = "2025-11-13T19:58:08.345Z" }, + { url = "https://files.pythonhosted.org/packages/a4/58/e25de28a572bdd60ffc6bb71fc7fd25a94ec6a076942e372437649cbb02a/ruff-0.14.5-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88f0770d42b7fa02bbefddde15d235ca3aa24e2f0137388cc15b2dcbb1f7c7a7", size = 13610854, upload-time = "2025-11-13T19:58:11.419Z" }, + { url = "https://files.pythonhosted.org/packages/7d/24/43bb3fd23ecee9861970978ea1a7a63e12a204d319248a7e8af539984280/ruff-0.14.5-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:3676cb02b9061fee7294661071c4709fa21419ea9176087cb77e64410926eb78", size = 15061088, upload-time = "2025-11-13T19:58:14.551Z" }, + { url = "https://files.pythonhosted.org/packages/23/44/a022f288d61c2f8c8645b24c364b719aee293ffc7d633a2ca4d116b9c716/ruff-0.14.5-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b595bedf6bc9cab647c4a173a61acf4f1ac5f2b545203ba82f30fcb10b0318fb", size = 14734717, upload-time = "2025-11-13T19:58:17.518Z" }, + { url = "https://files.pythonhosted.org/packages/58/81/5c6ba44de7e44c91f68073e0658109d8373b0590940efe5bd7753a2585a3/ruff-0.14.5-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f55382725ad0bdb2e8ee2babcbbfb16f124f5a59496a2f6a46f1d9d99d93e6e2", size = 14028812, upload-time = "2025-11-13T19:58:20.533Z" }, + { url = "https://files.pythonhosted.org/packages/ad/ef/41a8b60f8462cb320f68615b00299ebb12660097c952c600c762078420f8/ruff-0.14.5-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7497d19dce23976bdaca24345ae131a1d38dcfe1b0850ad8e9e6e4fa321a6e19", size = 13825656, upload-time = "2025-11-13T19:58:23.345Z" }, + { url = "https://files.pythonhosted.org/packages/7c/00/207e5de737fdb59b39eb1fac806904fe05681981b46d6a6db9468501062e/ruff-0.14.5-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:410e781f1122d6be4f446981dd479470af86537fb0b8857f27a6e872f65a38e4", size = 13959922, upload-time = "2025-11-13T19:58:26.537Z" }, + { url = "https://files.pythonhosted.org/packages/bc/7e/fa1f5c2776db4be405040293618846a2dece5c70b050874c2d1f10f24776/ruff-0.14.5-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:c01be527ef4c91a6d55e53b337bfe2c0f82af024cc1a33c44792d6844e2331e1", size = 12932501, upload-time = "2025-11-13T19:58:29.822Z" }, + { url = "https://files.pythonhosted.org/packages/67/d8/d86bf784d693a764b59479a6bbdc9515ae42c340a5dc5ab1dabef847bfaa/ruff-0.14.5-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:f66e9bb762e68d66e48550b59c74314168ebb46199886c5c5aa0b0fbcc81b151", size = 12927319, upload-time = "2025-11-13T19:58:32.923Z" }, + { url = "https://files.pythonhosted.org/packages/ac/de/ee0b304d450ae007ce0cb3e455fe24fbcaaedae4ebaad6c23831c6663651/ruff-0.14.5-py3-none-musllinux_1_2_i686.whl", hash = "sha256:d93be8f1fa01022337f1f8f3bcaa7ffee2d0b03f00922c45c2207954f351f465", size = 13206209, upload-time = "2025-11-13T19:58:35.952Z" }, + { url = "https://files.pythonhosted.org/packages/33/aa/193ca7e3a92d74f17d9d5771a765965d2cf42c86e6f0fd95b13969115723/ruff-0.14.5-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:c135d4b681f7401fe0e7312017e41aba9b3160861105726b76cfa14bc25aa367", size = 13953709, upload-time = "2025-11-13T19:58:39.002Z" }, + { url = "https://files.pythonhosted.org/packages/cc/f1/7119e42aa1d3bf036ffc9478885c2e248812b7de9abea4eae89163d2929d/ruff-0.14.5-py3-none-win32.whl", hash = "sha256:c83642e6fccfb6dea8b785eb9f456800dcd6a63f362238af5fc0c83d027dd08b", size = 12925808, upload-time = "2025-11-13T19:58:42.779Z" }, + { url = "https://files.pythonhosted.org/packages/3b/9d/7c0a255d21e0912114784e4a96bf62af0618e2190cae468cd82b13625ad2/ruff-0.14.5-py3-none-win_amd64.whl", hash = "sha256:9d55d7af7166f143c94eae1db3312f9ea8f95a4defef1979ed516dbb38c27621", size = 14331546, upload-time = "2025-11-13T19:58:45.691Z" }, + { url = "https://files.pythonhosted.org/packages/e5/80/69756670caedcf3b9be597a6e12276a6cf6197076eb62aad0c608f8efce0/ruff-0.14.5-py3-none-win_arm64.whl", hash = "sha256:4b700459d4649e2594b31f20a9de33bc7c19976d4746d8d0798ad959621d64a4", size = 13433331, upload-time = "2025-11-13T19:58:48.434Z" }, +] + +[[package]] +name = "snowballstemmer" +version = "3.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/75/a7/9810d872919697c9d01295633f5d574fb416d47e535f258272ca1f01f447/snowballstemmer-3.0.1.tar.gz", hash = "sha256:6d5eeeec8e9f84d4d56b847692bacf79bc2c8e90c7f80ca4444ff8b6f2e52895", size = 105575, upload-time = "2025-05-09T16:34:51.843Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c8/78/3565d011c61f5a43488987ee32b6f3f656e7f107ac2782dd57bdd7d91d9a/snowballstemmer-3.0.1-py3-none-any.whl", hash = "sha256:6cd7b3897da8d6c9ffb968a6781fa6532dce9c3618a4b127d920dab764a19064", size = 103274, upload-time = "2025-05-09T16:34:50.371Z" }, +] + +[[package]] +name = "sphinx" +version = "7.4.7" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.10'", +] +dependencies = [ + { name = "alabaster", version = "0.7.16", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "babel", marker = "python_full_version < '3.10'" }, + { name = "colorama", marker = "python_full_version < '3.10' and sys_platform == 'win32'" }, + { name = "docutils", marker = "python_full_version < '3.10'" }, + { name = "imagesize", marker = "python_full_version < '3.10'" }, + { name = "importlib-metadata", marker = "python_full_version < '3.10'" }, + { name = "jinja2", marker = "python_full_version < '3.10'" }, + { name = "packaging", marker = "python_full_version < '3.10'" }, + { name = "pygments", marker = "python_full_version < '3.10'" }, + { name = "requests", marker = "python_full_version < '3.10'" }, + { name = "snowballstemmer", marker = "python_full_version < '3.10'" }, + { name = "sphinxcontrib-applehelp", marker = "python_full_version < '3.10'" }, + { name = "sphinxcontrib-devhelp", marker = "python_full_version < '3.10'" }, + { name = "sphinxcontrib-htmlhelp", marker = "python_full_version < '3.10'" }, + { name = "sphinxcontrib-jsmath", marker = "python_full_version < '3.10'" }, + { name = "sphinxcontrib-qthelp", marker = "python_full_version < '3.10'" }, + { name = "sphinxcontrib-serializinghtml", marker = "python_full_version < '3.10'" }, + { name = "tomli", marker = "python_full_version < '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5b/be/50e50cb4f2eff47df05673d361095cafd95521d2a22521b920c67a372dcb/sphinx-7.4.7.tar.gz", hash = "sha256:242f92a7ea7e6c5b406fdc2615413890ba9f699114a9c09192d7dfead2ee9cfe", size = 8067911, upload-time = "2024-07-20T14:46:56.059Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0d/ef/153f6803c5d5f8917dbb7f7fcf6d34a871ede3296fa89c2c703f5f8a6c8e/sphinx-7.4.7-py3-none-any.whl", hash = "sha256:c2419e2135d11f1951cd994d6eb18a1835bd8fdd8429f9ca375dc1f3281bd239", size = 3401624, upload-time = "2024-07-20T14:46:52.142Z" }, +] + +[[package]] +name = "sphinx" +version = "8.1.3" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.10.*'", +] +dependencies = [ + { name = "alabaster", version = "1.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" }, + { name = "babel", marker = "python_full_version == '3.10.*'" }, + { name = "colorama", marker = "python_full_version == '3.10.*' and sys_platform == 'win32'" }, + { name = "docutils", marker = "python_full_version == '3.10.*'" }, + { name = "imagesize", marker = "python_full_version == '3.10.*'" }, + { name = "jinja2", marker = "python_full_version == '3.10.*'" }, + { name = "packaging", marker = "python_full_version == '3.10.*'" }, + { name = "pygments", marker = "python_full_version == '3.10.*'" }, + { name = "requests", marker = "python_full_version == '3.10.*'" }, + { name = "snowballstemmer", marker = "python_full_version == '3.10.*'" }, + { name = "sphinxcontrib-applehelp", marker = "python_full_version == '3.10.*'" }, + { name = "sphinxcontrib-devhelp", marker = "python_full_version == '3.10.*'" }, + { name = "sphinxcontrib-htmlhelp", marker = "python_full_version == '3.10.*'" }, + { name = "sphinxcontrib-jsmath", marker = "python_full_version == '3.10.*'" }, + { name = "sphinxcontrib-qthelp", marker = "python_full_version == '3.10.*'" }, + { name = "sphinxcontrib-serializinghtml", marker = "python_full_version == '3.10.*'" }, + { name = "tomli", marker = "python_full_version == '3.10.*'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/be0b61178fe2cdcb67e2a92fc9ebb488e3c51c4f74a36a7824c0adf23425/sphinx-8.1.3.tar.gz", hash = "sha256:43c1911eecb0d3e161ad78611bc905d1ad0e523e4ddc202a58a821773dc4c927", size = 8184611, upload-time = "2024-10-13T20:27:13.93Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/26/60/1ddff83a56d33aaf6f10ec8ce84b4c007d9368b21008876fceda7e7381ef/sphinx-8.1.3-py3-none-any.whl", hash = "sha256:09719015511837b76bf6e03e42eb7595ac8c2e41eeb9c29c5b755c6b677992a2", size = 3487125, upload-time = "2024-10-13T20:27:10.448Z" }, +] + +[[package]] +name = "sphinx" +version = "8.2.3" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.11'", +] +dependencies = [ + { name = "alabaster", version = "1.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "babel", marker = "python_full_version >= '3.11'" }, + { name = "colorama", marker = "python_full_version >= '3.11' and sys_platform == 'win32'" }, + { name = "docutils", marker = "python_full_version >= '3.11'" }, + { name = "imagesize", marker = "python_full_version >= '3.11'" }, + { name = "jinja2", marker = "python_full_version >= '3.11'" }, + { name = "packaging", marker = "python_full_version >= '3.11'" }, + { name = "pygments", marker = "python_full_version >= '3.11'" }, + { name = "requests", marker = "python_full_version >= '3.11'" }, + { name = "roman-numerals-py", marker = "python_full_version >= '3.11'" }, + { name = "snowballstemmer", marker = "python_full_version >= '3.11'" }, + { name = "sphinxcontrib-applehelp", marker = "python_full_version >= '3.11'" }, + { name = "sphinxcontrib-devhelp", marker = "python_full_version >= '3.11'" }, + { name = "sphinxcontrib-htmlhelp", marker = "python_full_version >= '3.11'" }, + { name = "sphinxcontrib-jsmath", marker = "python_full_version >= '3.11'" }, + { name = "sphinxcontrib-qthelp", marker = "python_full_version >= '3.11'" }, + { name = "sphinxcontrib-serializinghtml", marker = "python_full_version >= '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/38/ad/4360e50ed56cb483667b8e6dadf2d3fda62359593faabbe749a27c4eaca6/sphinx-8.2.3.tar.gz", hash = "sha256:398ad29dee7f63a75888314e9424d40f52ce5a6a87ae88e7071e80af296ec348", size = 8321876, upload-time = "2025-03-02T22:31:59.658Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/31/53/136e9eca6e0b9dc0e1962e2c908fbea2e5ac000c2a2fbd9a35797958c48b/sphinx-8.2.3-py3-none-any.whl", hash = "sha256:4405915165f13521d875a8c29c8970800a0141c14cc5416a38feca4ea5d9b9c3", size = 3589741, upload-time = "2025-03-02T22:31:56.836Z" }, +] + +[[package]] +name = "sphinx-rtd-theme" +version = "3.0.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "docutils" }, + { name = "sphinx", version = "7.4.7", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "sphinx", version = "8.1.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" }, + { name = "sphinx", version = "8.2.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "sphinxcontrib-jquery" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/91/44/c97faec644d29a5ceddd3020ae2edffa69e7d00054a8c7a6021e82f20335/sphinx_rtd_theme-3.0.2.tar.gz", hash = "sha256:b7457bc25dda723b20b086a670b9953c859eab60a2a03ee8eb2bb23e176e5f85", size = 7620463, upload-time = "2024-11-13T11:06:04.545Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/85/77/46e3bac77b82b4df5bb5b61f2de98637724f246b4966cfc34bc5895d852a/sphinx_rtd_theme-3.0.2-py2.py3-none-any.whl", hash = "sha256:422ccc750c3a3a311de4ae327e82affdaf59eb695ba4936538552f3b00f4ee13", size = 7655561, upload-time = "2024-11-13T11:06:02.094Z" }, +] + +[[package]] +name = "sphinxcontrib-applehelp" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ba/6e/b837e84a1a704953c62ef8776d45c3e8d759876b4a84fe14eba2859106fe/sphinxcontrib_applehelp-2.0.0.tar.gz", hash = "sha256:2f29ef331735ce958efa4734873f084941970894c6090408b079c61b2e1c06d1", size = 20053, upload-time = "2024-07-29T01:09:00.465Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5d/85/9ebeae2f76e9e77b952f4b274c27238156eae7979c5421fba91a28f4970d/sphinxcontrib_applehelp-2.0.0-py3-none-any.whl", hash = "sha256:4cd3f0ec4ac5dd9c17ec65e9ab272c9b867ea77425228e68ecf08d6b28ddbdb5", size = 119300, upload-time = "2024-07-29T01:08:58.99Z" }, +] + +[[package]] +name = "sphinxcontrib-devhelp" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f6/d2/5beee64d3e4e747f316bae86b55943f51e82bb86ecd325883ef65741e7da/sphinxcontrib_devhelp-2.0.0.tar.gz", hash = "sha256:411f5d96d445d1d73bb5d52133377b4248ec79db5c793ce7dbe59e074b4dd1ad", size = 12967, upload-time = "2024-07-29T01:09:23.417Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/35/7a/987e583882f985fe4d7323774889ec58049171828b58c2217e7f79cdf44e/sphinxcontrib_devhelp-2.0.0-py3-none-any.whl", hash = "sha256:aefb8b83854e4b0998877524d1029fd3e6879210422ee3780459e28a1f03a8a2", size = 82530, upload-time = "2024-07-29T01:09:21.945Z" }, +] + +[[package]] +name = "sphinxcontrib-htmlhelp" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/43/93/983afd9aa001e5201eab16b5a444ed5b9b0a7a010541e0ddfbbfd0b2470c/sphinxcontrib_htmlhelp-2.1.0.tar.gz", hash = "sha256:c9e2916ace8aad64cc13a0d233ee22317f2b9025b9cf3295249fa985cc7082e9", size = 22617, upload-time = "2024-07-29T01:09:37.889Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0a/7b/18a8c0bcec9182c05a0b3ec2a776bba4ead82750a55ff798e8d406dae604/sphinxcontrib_htmlhelp-2.1.0-py3-none-any.whl", hash = "sha256:166759820b47002d22914d64a075ce08f4c46818e17cfc9470a9786b759b19f8", size = 98705, upload-time = "2024-07-29T01:09:36.407Z" }, +] + +[[package]] +name = "sphinxcontrib-jquery" +version = "4.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "sphinx", version = "7.4.7", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "sphinx", version = "8.1.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" }, + { name = "sphinx", version = "8.2.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/de/f3/aa67467e051df70a6330fe7770894b3e4f09436dea6881ae0b4f3d87cad8/sphinxcontrib-jquery-4.1.tar.gz", hash = "sha256:1620739f04e36a2c779f1a131a2dfd49b2fd07351bf1968ced074365933abc7a", size = 122331, upload-time = "2023-03-14T15:01:01.944Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/76/85/749bd22d1a68db7291c89e2ebca53f4306c3f205853cf31e9de279034c3c/sphinxcontrib_jquery-4.1-py2.py3-none-any.whl", hash = "sha256:f936030d7d0147dd026a4f2b5a57343d233f1fc7b363f68b3d4f1cb0993878ae", size = 121104, upload-time = "2023-03-14T15:01:00.356Z" }, +] + +[[package]] +name = "sphinxcontrib-jsmath" +version = "1.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b2/e8/9ed3830aeed71f17c026a07a5097edcf44b692850ef215b161b8ad875729/sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8", size = 5787, upload-time = "2019-01-21T16:10:16.347Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c2/42/4c8646762ee83602e3fb3fbe774c2fac12f317deb0b5dbeeedd2d3ba4b77/sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178", size = 5071, upload-time = "2019-01-21T16:10:14.333Z" }, +] + +[[package]] +name = "sphinxcontrib-qthelp" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/68/bc/9104308fc285eb3e0b31b67688235db556cd5b0ef31d96f30e45f2e51cae/sphinxcontrib_qthelp-2.0.0.tar.gz", hash = "sha256:4fe7d0ac8fc171045be623aba3e2a8f613f8682731f9153bb2e40ece16b9bbab", size = 17165, upload-time = "2024-07-29T01:09:56.435Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/27/83/859ecdd180cacc13b1f7e857abf8582a64552ea7a061057a6c716e790fce/sphinxcontrib_qthelp-2.0.0-py3-none-any.whl", hash = "sha256:b18a828cdba941ccd6ee8445dbe72ffa3ef8cbe7505d8cd1fa0d42d3f2d5f3eb", size = 88743, upload-time = "2024-07-29T01:09:54.885Z" }, +] + +[[package]] +name = "sphinxcontrib-serializinghtml" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/3b/44/6716b257b0aa6bfd51a1b31665d1c205fb12cb5ad56de752dfa15657de2f/sphinxcontrib_serializinghtml-2.0.0.tar.gz", hash = "sha256:e9d912827f872c029017a53f0ef2180b327c3f7fd23c87229f7a8e8b70031d4d", size = 16080, upload-time = "2024-07-29T01:10:09.332Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/52/a7/d2782e4e3f77c8450f727ba74a8f12756d5ba823d81b941f1b04da9d033a/sphinxcontrib_serializinghtml-2.0.0-py3-none-any.whl", hash = "sha256:6e2cb0eef194e10c27ec0023bfeb25badbbb5868244cf5bc5bdc04e4464bf331", size = 92072, upload-time = "2024-07-29T01:10:08.203Z" }, +] + +[[package]] +name = "tomli" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/52/ed/3f73f72945444548f33eba9a87fc7a6e969915e7b1acc8260b30e1f76a2f/tomli-2.3.0.tar.gz", hash = "sha256:64be704a875d2a59753d80ee8a533c3fe183e3f06807ff7dc2232938ccb01549", size = 17392, upload-time = "2025-10-08T22:01:47.119Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/2e/299f62b401438d5fe1624119c723f5d877acc86a4c2492da405626665f12/tomli-2.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:88bd15eb972f3664f5ed4b57c1634a97153b4bac4479dcb6a495f41921eb7f45", size = 153236, upload-time = "2025-10-08T22:01:00.137Z" }, + { url = "https://files.pythonhosted.org/packages/86/7f/d8fffe6a7aefdb61bced88fcb5e280cfd71e08939da5894161bd71bea022/tomli-2.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:883b1c0d6398a6a9d29b508c331fa56adbcdff647f6ace4dfca0f50e90dfd0ba", size = 148084, upload-time = "2025-10-08T22:01:01.63Z" }, + { url = "https://files.pythonhosted.org/packages/47/5c/24935fb6a2ee63e86d80e4d3b58b222dafaf438c416752c8b58537c8b89a/tomli-2.3.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d1381caf13ab9f300e30dd8feadb3de072aeb86f1d34a8569453ff32a7dea4bf", size = 234832, upload-time = "2025-10-08T22:01:02.543Z" }, + { url = "https://files.pythonhosted.org/packages/89/da/75dfd804fc11e6612846758a23f13271b76d577e299592b4371a4ca4cd09/tomli-2.3.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a0e285d2649b78c0d9027570d4da3425bdb49830a6156121360b3f8511ea3441", size = 242052, upload-time = "2025-10-08T22:01:03.836Z" }, + { url = "https://files.pythonhosted.org/packages/70/8c/f48ac899f7b3ca7eb13af73bacbc93aec37f9c954df3c08ad96991c8c373/tomli-2.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0a154a9ae14bfcf5d8917a59b51ffd5a3ac1fd149b71b47a3a104ca4edcfa845", size = 239555, upload-time = "2025-10-08T22:01:04.834Z" }, + { url = "https://files.pythonhosted.org/packages/ba/28/72f8afd73f1d0e7829bfc093f4cb98ce0a40ffc0cc997009ee1ed94ba705/tomli-2.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:74bf8464ff93e413514fefd2be591c3b0b23231a77f901db1eb30d6f712fc42c", size = 245128, upload-time = "2025-10-08T22:01:05.84Z" }, + { url = "https://files.pythonhosted.org/packages/b6/eb/a7679c8ac85208706d27436e8d421dfa39d4c914dcf5fa8083a9305f58d9/tomli-2.3.0-cp311-cp311-win32.whl", hash = "sha256:00b5f5d95bbfc7d12f91ad8c593a1659b6387b43f054104cda404be6bda62456", size = 96445, upload-time = "2025-10-08T22:01:06.896Z" }, + { url = "https://files.pythonhosted.org/packages/0a/fe/3d3420c4cb1ad9cb462fb52967080575f15898da97e21cb6f1361d505383/tomli-2.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:4dc4ce8483a5d429ab602f111a93a6ab1ed425eae3122032db7e9acf449451be", size = 107165, upload-time = "2025-10-08T22:01:08.107Z" }, + { url = "https://files.pythonhosted.org/packages/ff/b7/40f36368fcabc518bb11c8f06379a0fd631985046c038aca08c6d6a43c6e/tomli-2.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d7d86942e56ded512a594786a5ba0a5e521d02529b3826e7761a05138341a2ac", size = 154891, upload-time = "2025-10-08T22:01:09.082Z" }, + { url = "https://files.pythonhosted.org/packages/f9/3f/d9dd692199e3b3aab2e4e4dd948abd0f790d9ded8cd10cbaae276a898434/tomli-2.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:73ee0b47d4dad1c5e996e3cd33b8a76a50167ae5f96a2607cbe8cc773506ab22", size = 148796, upload-time = "2025-10-08T22:01:10.266Z" }, + { url = "https://files.pythonhosted.org/packages/60/83/59bff4996c2cf9f9387a0f5a3394629c7efa5ef16142076a23a90f1955fa/tomli-2.3.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:792262b94d5d0a466afb5bc63c7daa9d75520110971ee269152083270998316f", size = 242121, upload-time = "2025-10-08T22:01:11.332Z" }, + { url = "https://files.pythonhosted.org/packages/45/e5/7c5119ff39de8693d6baab6c0b6dcb556d192c165596e9fc231ea1052041/tomli-2.3.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4f195fe57ecceac95a66a75ac24d9d5fbc98ef0962e09b2eddec5d39375aae52", size = 250070, upload-time = "2025-10-08T22:01:12.498Z" }, + { url = "https://files.pythonhosted.org/packages/45/12/ad5126d3a278f27e6701abde51d342aa78d06e27ce2bb596a01f7709a5a2/tomli-2.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e31d432427dcbf4d86958c184b9bfd1e96b5b71f8eb17e6d02531f434fd335b8", size = 245859, upload-time = "2025-10-08T22:01:13.551Z" }, + { url = "https://files.pythonhosted.org/packages/fb/a1/4d6865da6a71c603cfe6ad0e6556c73c76548557a8d658f9e3b142df245f/tomli-2.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7b0882799624980785240ab732537fcfc372601015c00f7fc367c55308c186f6", size = 250296, upload-time = "2025-10-08T22:01:14.614Z" }, + { url = "https://files.pythonhosted.org/packages/a0/b7/a7a7042715d55c9ba6e8b196d65d2cb662578b4d8cd17d882d45322b0d78/tomli-2.3.0-cp312-cp312-win32.whl", hash = "sha256:ff72b71b5d10d22ecb084d345fc26f42b5143c5533db5e2eaba7d2d335358876", size = 97124, upload-time = "2025-10-08T22:01:15.629Z" }, + { url = "https://files.pythonhosted.org/packages/06/1e/f22f100db15a68b520664eb3328fb0ae4e90530887928558112c8d1f4515/tomli-2.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:1cb4ed918939151a03f33d4242ccd0aa5f11b3547d0cf30f7c74a408a5b99878", size = 107698, upload-time = "2025-10-08T22:01:16.51Z" }, + { url = "https://files.pythonhosted.org/packages/89/48/06ee6eabe4fdd9ecd48bf488f4ac783844fd777f547b8d1b61c11939974e/tomli-2.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5192f562738228945d7b13d4930baffda67b69425a7f0da96d360b0a3888136b", size = 154819, upload-time = "2025-10-08T22:01:17.964Z" }, + { url = "https://files.pythonhosted.org/packages/f1/01/88793757d54d8937015c75dcdfb673c65471945f6be98e6a0410fba167ed/tomli-2.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:be71c93a63d738597996be9528f4abe628d1adf5e6eb11607bc8fe1a510b5dae", size = 148766, upload-time = "2025-10-08T22:01:18.959Z" }, + { url = "https://files.pythonhosted.org/packages/42/17/5e2c956f0144b812e7e107f94f1cc54af734eb17b5191c0bbfb72de5e93e/tomli-2.3.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c4665508bcbac83a31ff8ab08f424b665200c0e1e645d2bd9ab3d3e557b6185b", size = 240771, upload-time = "2025-10-08T22:01:20.106Z" }, + { url = "https://files.pythonhosted.org/packages/d5/f4/0fbd014909748706c01d16824eadb0307115f9562a15cbb012cd9b3512c5/tomli-2.3.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4021923f97266babc6ccab9f5068642a0095faa0a51a246a6a02fccbb3514eaf", size = 248586, upload-time = "2025-10-08T22:01:21.164Z" }, + { url = "https://files.pythonhosted.org/packages/30/77/fed85e114bde5e81ecf9bc5da0cc69f2914b38f4708c80ae67d0c10180c5/tomli-2.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4ea38c40145a357d513bffad0ed869f13c1773716cf71ccaa83b0fa0cc4e42f", size = 244792, upload-time = "2025-10-08T22:01:22.417Z" }, + { url = "https://files.pythonhosted.org/packages/55/92/afed3d497f7c186dc71e6ee6d4fcb0acfa5f7d0a1a2878f8beae379ae0cc/tomli-2.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ad805ea85eda330dbad64c7ea7a4556259665bdf9d2672f5dccc740eb9d3ca05", size = 248909, upload-time = "2025-10-08T22:01:23.859Z" }, + { url = "https://files.pythonhosted.org/packages/f8/84/ef50c51b5a9472e7265ce1ffc7f24cd4023d289e109f669bdb1553f6a7c2/tomli-2.3.0-cp313-cp313-win32.whl", hash = "sha256:97d5eec30149fd3294270e889b4234023f2c69747e555a27bd708828353ab606", size = 96946, upload-time = "2025-10-08T22:01:24.893Z" }, + { url = "https://files.pythonhosted.org/packages/b2/b7/718cd1da0884f281f95ccfa3a6cc572d30053cba64603f79d431d3c9b61b/tomli-2.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:0c95ca56fbe89e065c6ead5b593ee64b84a26fca063b5d71a1122bf26e533999", size = 107705, upload-time = "2025-10-08T22:01:26.153Z" }, + { url = "https://files.pythonhosted.org/packages/19/94/aeafa14a52e16163008060506fcb6aa1949d13548d13752171a755c65611/tomli-2.3.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:cebc6fe843e0733ee827a282aca4999b596241195f43b4cc371d64fc6639da9e", size = 154244, upload-time = "2025-10-08T22:01:27.06Z" }, + { url = "https://files.pythonhosted.org/packages/db/e4/1e58409aa78eefa47ccd19779fc6f36787edbe7d4cd330eeeedb33a4515b/tomli-2.3.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4c2ef0244c75aba9355561272009d934953817c49f47d768070c3c94355c2aa3", size = 148637, upload-time = "2025-10-08T22:01:28.059Z" }, + { url = "https://files.pythonhosted.org/packages/26/b6/d1eccb62f665e44359226811064596dd6a366ea1f985839c566cd61525ae/tomli-2.3.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c22a8bf253bacc0cf11f35ad9808b6cb75ada2631c2d97c971122583b129afbc", size = 241925, upload-time = "2025-10-08T22:01:29.066Z" }, + { url = "https://files.pythonhosted.org/packages/70/91/7cdab9a03e6d3d2bb11beae108da5bdc1c34bdeb06e21163482544ddcc90/tomli-2.3.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0eea8cc5c5e9f89c9b90c4896a8deefc74f518db5927d0e0e8d4a80953d774d0", size = 249045, upload-time = "2025-10-08T22:01:31.98Z" }, + { url = "https://files.pythonhosted.org/packages/15/1b/8c26874ed1f6e4f1fcfeb868db8a794cbe9f227299402db58cfcc858766c/tomli-2.3.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b74a0e59ec5d15127acdabd75ea17726ac4c5178ae51b85bfe39c4f8a278e879", size = 245835, upload-time = "2025-10-08T22:01:32.989Z" }, + { url = "https://files.pythonhosted.org/packages/fd/42/8e3c6a9a4b1a1360c1a2a39f0b972cef2cc9ebd56025168c4137192a9321/tomli-2.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b5870b50c9db823c595983571d1296a6ff3e1b88f734a4c8f6fc6188397de005", size = 253109, upload-time = "2025-10-08T22:01:34.052Z" }, + { url = "https://files.pythonhosted.org/packages/22/0c/b4da635000a71b5f80130937eeac12e686eefb376b8dee113b4a582bba42/tomli-2.3.0-cp314-cp314-win32.whl", hash = "sha256:feb0dacc61170ed7ab602d3d972a58f14ee3ee60494292d384649a3dc38ef463", size = 97930, upload-time = "2025-10-08T22:01:35.082Z" }, + { url = "https://files.pythonhosted.org/packages/b9/74/cb1abc870a418ae99cd5c9547d6bce30701a954e0e721821df483ef7223c/tomli-2.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:b273fcbd7fc64dc3600c098e39136522650c49bca95df2d11cf3b626422392c8", size = 107964, upload-time = "2025-10-08T22:01:36.057Z" }, + { url = "https://files.pythonhosted.org/packages/54/78/5c46fff6432a712af9f792944f4fcd7067d8823157949f4e40c56b8b3c83/tomli-2.3.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:940d56ee0410fa17ee1f12b817b37a4d4e4dc4d27340863cc67236c74f582e77", size = 163065, upload-time = "2025-10-08T22:01:37.27Z" }, + { url = "https://files.pythonhosted.org/packages/39/67/f85d9bd23182f45eca8939cd2bc7050e1f90c41f4a2ecbbd5963a1d1c486/tomli-2.3.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f85209946d1fe94416debbb88d00eb92ce9cd5266775424ff81bc959e001acaf", size = 159088, upload-time = "2025-10-08T22:01:38.235Z" }, + { url = "https://files.pythonhosted.org/packages/26/5a/4b546a0405b9cc0659b399f12b6adb750757baf04250b148d3c5059fc4eb/tomli-2.3.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a56212bdcce682e56b0aaf79e869ba5d15a6163f88d5451cbde388d48b13f530", size = 268193, upload-time = "2025-10-08T22:01:39.712Z" }, + { url = "https://files.pythonhosted.org/packages/42/4f/2c12a72ae22cf7b59a7fe75b3465b7aba40ea9145d026ba41cb382075b0e/tomli-2.3.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c5f3ffd1e098dfc032d4d3af5c0ac64f6d286d98bc148698356847b80fa4de1b", size = 275488, upload-time = "2025-10-08T22:01:40.773Z" }, + { url = "https://files.pythonhosted.org/packages/92/04/a038d65dbe160c3aa5a624e93ad98111090f6804027d474ba9c37c8ae186/tomli-2.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5e01decd096b1530d97d5d85cb4dff4af2d8347bd35686654a004f8dea20fc67", size = 272669, upload-time = "2025-10-08T22:01:41.824Z" }, + { url = "https://files.pythonhosted.org/packages/be/2f/8b7c60a9d1612a7cbc39ffcca4f21a73bf368a80fc25bccf8253e2563267/tomli-2.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:8a35dd0e643bb2610f156cca8db95d213a90015c11fee76c946aa62b7ae7e02f", size = 279709, upload-time = "2025-10-08T22:01:43.177Z" }, + { url = "https://files.pythonhosted.org/packages/7e/46/cc36c679f09f27ded940281c38607716c86cf8ba4a518d524e349c8b4874/tomli-2.3.0-cp314-cp314t-win32.whl", hash = "sha256:a1f7f282fe248311650081faafa5f4732bdbfef5d45fe3f2e702fbc6f2d496e0", size = 107563, upload-time = "2025-10-08T22:01:44.233Z" }, + { url = "https://files.pythonhosted.org/packages/84/ff/426ca8683cf7b753614480484f6437f568fd2fda2edbdf57a2d3d8b27a0b/tomli-2.3.0-cp314-cp314t-win_amd64.whl", hash = "sha256:70a251f8d4ba2d9ac2542eecf008b3c8a9fc5c3f9f02c56a9d7952612be2fdba", size = 119756, upload-time = "2025-10-08T22:01:45.234Z" }, + { url = "https://files.pythonhosted.org/packages/77/b8/0135fadc89e73be292b473cb820b4f5a08197779206b33191e801feeae40/tomli-2.3.0-py3-none-any.whl", hash = "sha256:e95b1af3c5b07d9e643909b5abbec77cd9f1217e6d0bca72b0234736b9fb1f1b", size = 14408, upload-time = "2025-10-08T22:01:46.04Z" }, +] + +[[package]] +name = "tox" +version = "4.30.3" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.10'", +] +dependencies = [ + { name = "cachetools", marker = "python_full_version < '3.10'" }, + { name = "chardet", marker = "python_full_version < '3.10'" }, + { name = "colorama", marker = "python_full_version < '3.10'" }, + { name = "filelock", version = "3.19.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "packaging", marker = "python_full_version < '3.10'" }, + { name = "platformdirs", version = "4.4.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "pluggy", marker = "python_full_version < '3.10'" }, + { name = "pyproject-api", version = "1.9.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "tomli", marker = "python_full_version < '3.10'" }, + { name = "typing-extensions", marker = "python_full_version < '3.10'" }, + { name = "virtualenv", marker = "python_full_version < '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/51/b2/cee55172e5e10ce030b087cd3ac06641e47d08a3dc8d76c17b157dba7558/tox-4.30.3.tar.gz", hash = "sha256:f3dd0735f1cd4e8fbea5a3661b77f517456b5f0031a6256432533900e34b90bf", size = 202799, upload-time = "2025-10-02T16:24:39.974Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e2/e4/8bb9ce952820df4165eb34610af347665d6cb436898a234db9d84d093ce6/tox-4.30.3-py3-none-any.whl", hash = "sha256:a9f17b4b2d0f74fe0d76207236925a119095011e5c2e661a133115a8061178c9", size = 175512, upload-time = "2025-10-02T16:24:38.209Z" }, +] + +[[package]] +name = "tox" +version = "4.32.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.11'", + "python_full_version == '3.10.*'", +] +dependencies = [ + { name = "cachetools", marker = "python_full_version >= '3.10'" }, + { name = "chardet", marker = "python_full_version >= '3.10'" }, + { name = "colorama", marker = "python_full_version >= '3.10'" }, + { name = "filelock", version = "3.20.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "packaging", marker = "python_full_version >= '3.10'" }, + { name = "platformdirs", version = "4.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "pluggy", marker = "python_full_version >= '3.10'" }, + { name = "pyproject-api", version = "1.10.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "tomli", marker = "python_full_version == '3.10.*'" }, + { name = "typing-extensions", marker = "python_full_version == '3.10.*'" }, + { name = "virtualenv", marker = "python_full_version >= '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/59/bf/0e4dbd42724cbae25959f0e34c95d0c730df03ab03f54d52accd9abfc614/tox-4.32.0.tar.gz", hash = "sha256:1ad476b5f4d3679455b89a992849ffc3367560bbc7e9495ee8a3963542e7c8ff", size = 203330, upload-time = "2025-10-24T18:03:38.132Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fc/cc/e09c0d663a004945f82beecd4f147053567910479314e8d01ba71e5d5dea/tox-4.32.0-py3-none-any.whl", hash = "sha256:451e81dc02ba8d1ed20efd52ee409641ae4b5d5830e008af10fe8823ef1bd551", size = 175905, upload-time = "2025-10-24T18:03:36.337Z" }, +] + +[[package]] +name = "tox-uv" +version = "1.28.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.10'", +] +dependencies = [ + { name = "packaging", marker = "python_full_version < '3.10'" }, + { name = "tomli", marker = "python_full_version < '3.10'" }, + { name = "tox", version = "4.30.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "typing-extensions", marker = "python_full_version < '3.10'" }, + { name = "uv", marker = "python_full_version < '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f2/23/5c7f9bb50f25b4e9096a3b38e4b67604d3030388fdb6e645e54226b30cb0/tox_uv-1.28.1.tar.gz", hash = "sha256:fb01a34f49496e51e198196ee73a2be19ecd9cdbdc2508d86b981314c3d1b058", size = 23518, upload-time = "2025-10-09T16:13:45.286Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f1/54/46abc86d4cf2844d34dbd8e7bd0e4ed226ed6fb6a9a9481a85d4daff28ca/tox_uv-1.28.1-py3-none-any.whl", hash = "sha256:29f64076c57bda643b0c25dcb925011a35bfa57b0a94d3aaf550607d31e9f30a", size = 17363, upload-time = "2025-10-09T16:13:43.793Z" }, +] + +[[package]] +name = "tox-uv" +version = "1.29.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.11'", + "python_full_version == '3.10.*'", +] +dependencies = [ + { name = "packaging", marker = "python_full_version >= '3.10'" }, + { name = "tomli", marker = "python_full_version == '3.10.*'" }, + { name = "tox", version = "4.32.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "uv", marker = "python_full_version >= '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/4f/90/06752775b8cfadba8856190f5beae9f552547e0f287e0246677972107375/tox_uv-1.29.0.tar.gz", hash = "sha256:30fa9e6ad507df49d3c6a2f88894256bcf90f18e240a00764da6ecab1db24895", size = 23427, upload-time = "2025-10-09T20:40:27.384Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5c/17/221d62937c4130b044bb437caac4181e7e13d5536bbede65264db1f0ac9f/tox_uv-1.29.0-py3-none-any.whl", hash = "sha256:b1d251286edeeb4bc4af1e24c8acfdd9404700143c2199ccdbb4ea195f7de6cc", size = 17254, upload-time = "2025-10-09T20:40:25.885Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.15.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, +] + +[[package]] +name = "urllib3" +version = "2.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/15/22/9ee70a2574a4f4599c47dd506532914ce044817c7752a79b6a51286319bc/urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760", size = 393185, upload-time = "2025-06-18T14:07:41.644Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc", size = 129795, upload-time = "2025-06-18T14:07:40.39Z" }, +] + +[[package]] +name = "uv" +version = "0.9.10" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c7/42/18d158032c594110e7dab012931a3cd6a09f4dec801854dc877a3c6e865a/uv-0.9.10.tar.gz", hash = "sha256:84128ca76a665fe0584bff6ef913741d5615a4187c1aaed81739d519e669cfbd", size = 3738676, upload-time = "2025-11-17T17:02:36.069Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1d/d2/68f07f3b96539b986d36a6f2b03f9f8484373f7d8f05949221be1a1f9993/uv-0.9.10-py3-none-linux_armv6l.whl", hash = "sha256:a02b27e00afe0d7908fbb9ec61ad5338508e02356138a8a718af239fe773dd2e", size = 20522030, upload-time = "2025-11-17T17:01:48.503Z" }, + { url = "https://files.pythonhosted.org/packages/27/67/763da49b0dfafb5465376e29e176f56c3f1ca79a38ef59983fe5d62dd371/uv-0.9.10-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:3c2ddc0bca2e4b7e31623a17d60ea5f8d1ee4ff3ee27ddbf884e089e57cd0c93", size = 19627118, upload-time = "2025-11-17T17:01:51.736Z" }, + { url = "https://files.pythonhosted.org/packages/b7/e8/5b5b5a719830ee5505bddf35082f6dc41569dab8fb6761626e5034c27748/uv-0.9.10-py3-none-macosx_11_0_arm64.whl", hash = "sha256:8c8969d54c9f603107445a23f36fba03a0dfa4c75b3ada2faf94ed371f26f3a4", size = 18174169, upload-time = "2025-11-17T17:01:54.034Z" }, + { url = "https://files.pythonhosted.org/packages/35/6b/4c074cab3bf483a172bec905e1696269b0222d73fd8dc890388da8b110c9/uv-0.9.10-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.musllinux_1_1_aarch64.whl", hash = "sha256:c9a326f04b670f1af2d9fa90e50af57a9a872a8dc90bb377d0c27b2030438ffc", size = 19950337, upload-time = "2025-11-17T17:01:56.35Z" }, + { url = "https://files.pythonhosted.org/packages/45/9d/a8930ce9f5247629335de32dd97f19b955f86fb7524ea268f17181c3bccc/uv-0.9.10-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:55ecddf19314f7da5b8dea6bcfcc86f5769dd7d22106c731e8e6cfbf3fa6b98d", size = 20123165, upload-time = "2025-11-17T17:01:58.668Z" }, + { url = "https://files.pythonhosted.org/packages/c5/4b/3b136c1effa53ab84bc72f66cbc3aca0013fad6581d9c03c2989635b3be1/uv-0.9.10-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:763cf33c7c5ab490323fab667b24f66412c3c3ca58e86b56e13fc0109d031a1b", size = 21024525, upload-time = "2025-11-17T17:02:01.022Z" }, + { url = "https://files.pythonhosted.org/packages/64/f8/7aab6deb6362431edd670c2885d802ced2cb045fd4f73df462c9c8b05f32/uv-0.9.10-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:5e7a86eb5edbb9064b1401522eb10c5bd25ff56c04cd0ed253d0cd088a448bef", size = 22578222, upload-time = "2025-11-17T17:02:03.333Z" }, + { url = "https://files.pythonhosted.org/packages/7d/5c/658647132d2c2396bd484b9ee42ab9a0a3f439a14b59e91c48b66b6b7403/uv-0.9.10-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3c133e34feca0823d952a1d323d1d47f2b35e13154de5ca293d188f5b8792a62", size = 22205447, upload-time = "2025-11-17T17:02:05.706Z" }, + { url = "https://files.pythonhosted.org/packages/80/22/b4c766d855aaefd3797c6e296af4b006d560b78cec5f3041902cd6d7abce/uv-0.9.10-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:92d4b58ef810eaf5edf95c607d613ff240ab903ee599af1d687f891ab64b4129", size = 21247210, upload-time = "2025-11-17T17:02:08.312Z" }, + { url = "https://files.pythonhosted.org/packages/a2/0c/4b705b613237519e8640baaa1fe4809ddef2482ad7b82ea807c71e5ddb5d/uv-0.9.10-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21981bc859802c94d4b8f026b8734d39e8146baac703f1e3eab2e2d87d65ca8c", size = 21389310, upload-time = "2025-11-17T17:02:11.117Z" }, + { url = "https://files.pythonhosted.org/packages/35/44/81df7494107d8473a754fea74e51c8727192a71c11fc4c92aa901e36a255/uv-0.9.10-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:110fd2062dccc4825f96cef0007f6bbb6f5da117af50415bed6f6dcd5e1e3603", size = 20062308, upload-time = "2025-11-17T17:02:14.437Z" }, + { url = "https://files.pythonhosted.org/packages/d5/b5/0919100138d2cdb2f4f058a0e7c6fc059aad1914b1f6637c7fcfe2e670d9/uv-0.9.10-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:b0a0c4594871de9e8b12c9e10982dc83e2414b712a34e84860fac9fbc8510c5a", size = 21182524, upload-time = "2025-11-17T17:02:16.844Z" }, + { url = "https://files.pythonhosted.org/packages/43/f5/deb8be34797f71ae72f690da26b388682cc478bda630822fff7bc90e80f3/uv-0.9.10-py3-none-musllinux_1_1_armv7l.whl", hash = "sha256:516d1c5549912696ba68376d2652661ed327199a9ec15763f7baa29bc75b35ec", size = 20084575, upload-time = "2025-11-17T17:02:19.174Z" }, + { url = "https://files.pythonhosted.org/packages/f1/da/4e51d384669103a9ba8ede1ecf1c99c932148bdb365794ac586f90e5296f/uv-0.9.10-py3-none-musllinux_1_1_i686.whl", hash = "sha256:780a7af4a7dfb0894a8d367b5c3a48f926480f465a34aa4a8633d6e4f521e517", size = 20495061, upload-time = "2025-11-17T17:02:21.978Z" }, + { url = "https://files.pythonhosted.org/packages/78/9f/c16fb7e0290744a22987ffb9700c69fe1e459e199e04f3e881ce05d032e4/uv-0.9.10-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:4e256d3cc542f73435b27a3d0bf2a6b0f9d2dd6dd5c8df1a8028834deb896819", size = 21601257, upload-time = "2025-11-17T17:02:24.646Z" }, + { url = "https://files.pythonhosted.org/packages/b1/f9/ea01841fe2660add4b8fbc2b3a34e8d4f812d9bdaeeeead5470e1142b4be/uv-0.9.10-py3-none-win32.whl", hash = "sha256:dfcc3cd5765158e82a0f52066462e378887aac01b347b588907fe3290af92356", size = 19338075, upload-time = "2025-11-17T17:02:28.007Z" }, + { url = "https://files.pythonhosted.org/packages/43/c3/72dbdf10ea0e70ca47248aaa25122df9dfa7a7e85e96ba6031cafc67cb02/uv-0.9.10-py3-none-win_amd64.whl", hash = "sha256:9ee9027eb98cf5a99d9a3d5ddab7048745e2e49d572869044f66772e17f57792", size = 21367277, upload-time = "2025-11-17T17:02:30.672Z" }, + { url = "https://files.pythonhosted.org/packages/6c/9d/b38be12b524dbd28dcdc99cfd88f6f3f792823258b96efb6d691025872cb/uv-0.9.10-py3-none-win_arm64.whl", hash = "sha256:d4903e73f5e281ce5c3def60014bef93d2d1a148901653e222470ce54987f4a1", size = 19770409, upload-time = "2025-11-17T17:02:34.057Z" }, +] + +[[package]] +name = "virtualenv" +version = "20.35.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "distlib" }, + { name = "filelock", version = "3.19.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "filelock", version = "3.20.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "platformdirs", version = "4.4.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "platformdirs", version = "4.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/20/28/e6f1a6f655d620846bd9df527390ecc26b3805a0c5989048c210e22c5ca9/virtualenv-20.35.4.tar.gz", hash = "sha256:643d3914d73d3eeb0c552cbb12d7e82adf0e504dbf86a3182f8771a153a1971c", size = 6028799, upload-time = "2025-10-29T06:57:40.511Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/79/0c/c05523fa3181fdf0c9c52a6ba91a23fbf3246cc095f26f6516f9c60e6771/virtualenv-20.35.4-py3-none-any.whl", hash = "sha256:c21c9cede36c9753eeade68ba7d523529f228a403463376cf821eaae2b650f1b", size = 6005095, upload-time = "2025-10-29T06:57:37.598Z" }, +] + +[[package]] +name = "worldengine" +source = { editable = "." } +dependencies = [ + { name = "noise" }, + { name = "numpy", version = "2.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" }, + { name = "numpy", version = "2.3.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "protobuf" }, + { name = "pyplatec" }, + { name = "pypng" }, +] + +[package.optional-dependencies] +dev = [ + { name = "cython" }, + { name = "h5py", version = "3.14.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "h5py", version = "3.15.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "mypy" }, + { name = "pre-commit", version = "4.3.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "pre-commit", version = "4.4.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { 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-cov" }, + { name = "ruff" }, + { name = "sphinx", version = "7.4.7", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "sphinx", version = "8.1.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" }, + { name = "sphinx", version = "8.2.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "sphinx-rtd-theme" }, + { name = "tox", version = "4.30.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "tox", version = "4.32.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "tox-uv", version = "1.28.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "tox-uv", version = "1.29.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, +] +gdal = [ + { name = "gdal" }, +] +hdf5 = [ + { name = "cython" }, + { name = "h5py", version = "3.14.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "h5py", version = "3.15.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, +] + +[package.dev-dependencies] +dev = [ + { name = "cython" }, + { name = "h5py", version = "3.14.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "h5py", version = "3.15.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "mypy" }, + { name = "pre-commit", version = "4.3.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "pre-commit", version = "4.4.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { 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-cov" }, + { name = "ruff" }, + { name = "sphinx", version = "7.4.7", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "sphinx", version = "8.1.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" }, + { name = "sphinx", version = "8.2.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "sphinx-rtd-theme" }, + { name = "tox", version = "4.30.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "tox", version = "4.32.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "tox-uv", version = "1.28.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "tox-uv", version = "1.29.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, +] + +[package.metadata] +requires-dist = [ + { name = "cython", marker = "extra == 'dev'", specifier = ">=3.0.0" }, + { name = "cython", marker = "extra == 'hdf5'", specifier = ">=3.0.0" }, + { name = "gdal", marker = "extra == 'gdal'", specifier = ">=3.0.0" }, + { name = "h5py", marker = "extra == 'dev'", specifier = ">=3.10.0" }, + { name = "h5py", marker = "extra == 'hdf5'", specifier = ">=3.10.0" }, + { name = "mypy", marker = "extra == 'dev'", specifier = ">=1.7.0" }, + { name = "noise", specifier = "==1.2.2" }, + { name = "numpy", specifier = ">=1.24.0" }, + { name = "pre-commit", marker = "extra == 'dev'", specifier = ">=3.5.0" }, + { name = "protobuf", specifier = ">=4.21.0" }, + { name = "pyplatec", specifier = "==1.4.2" }, + { name = "pypng", specifier = ">=0.20220715.0" }, + { name = "pytest", marker = "extra == 'dev'", specifier = ">=7.4.0" }, + { name = "pytest-cov", marker = "extra == 'dev'", specifier = ">=4.1.0" }, + { name = "ruff", marker = "extra == 'dev'", specifier = ">=0.1.0" }, + { name = "sphinx", marker = "extra == 'dev'", specifier = ">=7.2.0" }, + { name = "sphinx-rtd-theme", marker = "extra == 'dev'", specifier = ">=2.0.0" }, + { name = "tox", marker = "extra == 'dev'", specifier = ">=4.0.0" }, + { name = "tox-uv", marker = "extra == 'dev'", specifier = ">=1.0.0" }, +] +provides-extras = ["hdf5", "gdal", "dev"] + +[package.metadata.requires-dev] +dev = [ + { name = "cython", specifier = ">=3.0.0" }, + { name = "h5py", specifier = ">=3.10.0" }, + { name = "mypy", specifier = ">=1.7.0" }, + { name = "pre-commit", specifier = ">=3.5.0" }, + { name = "pytest", specifier = ">=7.4.0" }, + { name = "pytest-cov", specifier = ">=4.1.0" }, + { name = "ruff", specifier = ">=0.1.0" }, + { name = "sphinx", specifier = ">=7.2.0" }, + { name = "sphinx-rtd-theme", specifier = ">=2.0.0" }, + { name = "tox", specifier = ">=4.0.0" }, + { name = "tox-uv", specifier = ">=1.0.0" }, +] + +[[package]] +name = "zipp" +version = "3.23.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e3/02/0f2892c661036d50ede074e376733dca2ae7c6eb617489437771209d4180/zipp-3.23.0.tar.gz", hash = "sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166", size = 25547, upload-time = "2025-06-08T17:06:39.4Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2e/54/647ade08bf0db230bfea292f893923872fd20be6ac6f53b2b936ba839d75/zipp-3.23.0-py3-none-any.whl", hash = "sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e", size = 10276, upload-time = "2025-06-08T17:06:38.034Z" }, +] diff --git a/worldengine/World.proto b/worldengine/World.proto index a65a82f4..f3b4ae6c 100644 --- a/worldengine/World.proto +++ b/worldengine/World.proto @@ -25,11 +25,11 @@ message World { message BooleanMatrix { repeated BooleanRow rows = 1; - } + } message IntegerMatrix { repeated IntegerRow rows = 1; - } + } message DoubleQuantile { required int32 key = 1; @@ -114,5 +114,3 @@ message World { // Ice-caps optional DoubleMatrix icecap = 36; } - - diff --git a/worldengine/__init__.py b/worldengine/__init__.py index e69de29b..1c1f967a 100644 --- a/worldengine/__init__.py +++ b/worldengine/__init__.py @@ -0,0 +1,3 @@ +from worldengine.version import __version__ + +__all__ = ["__version__"] diff --git a/worldengine/__main__.py b/worldengine/__main__.py index 3b07b636..c9448aed 100644 --- a/worldengine/__main__.py +++ b/worldengine/__main__.py @@ -1,6 +1,8 @@ if __name__ == "__main__": - from os import path, pardir import sys + from os import pardir, path + sys.path.append(path.join(path.dirname(path.realpath(__file__)), pardir)) from cli import main + main.main() diff --git a/worldengine/astar.py b/worldengine/astar.py index 9e1ab2ff..569e9ce0 100644 --- a/worldengine/astar.py +++ b/worldengine/astar.py @@ -19,7 +19,8 @@ class Path: - """ A path object, containing the nodes and total cost.""" + """A path object, containing the nodes and total cost.""" + def __init__(self, nodes, total_cost): self.nodes = nodes self.totalCost = total_cost @@ -32,7 +33,8 @@ def get_total_movement_cost(self): class Node: - """ The basic unit/pixel/location is a Node.""" + """The basic unit/pixel/location is a Node.""" + def __init__(self, location, movement_cost, lid, parent=None): self.location = location # where is this node located self.mCost = movement_cost # total move cost to reach this node @@ -48,11 +50,12 @@ def __eq__(self, n): class AStar: - """ The "A* Star Search Algorithm" itself. + """The "A* Star Search Algorithm" itself. Have a read: https://en.wikipedia.org/wiki/A*_search_algorithm """ + def __init__(self, map_handler): self.mh = map_handler self.o = [] @@ -140,8 +143,8 @@ def __init__(self, x, y): self.x = x self.y = y - def __eq__(self, l): - if l.x == self.x and l.y == self.y: + def __eq__(self, other): + if other.x == self.x and other.y == self.y: return 1 else: return 0 @@ -199,7 +202,7 @@ def _handle_node(self, x, y, from_node, destination_x, destination_y): class PathFinder: """Using the a* algorithm we will try to find the best path between two - points. + points. """ def __init__(self): @@ -212,7 +215,7 @@ def find(height_map, source, destination): path = [] height, width = height_map.shape - graph = height_map.flatten('C') #flatten array (row-major) + graph = height_map.flatten("C") # flatten array (row-major) pathfinder = AStar(SQMapHandler(graph, width, height)) start = SQLocation(sx, sy) diff --git a/worldengine/biome.py b/worldengine/biome.py index c98816f9..b0193b6a 100644 --- a/worldengine/biome.py +++ b/worldengine/biome.py @@ -3,33 +3,30 @@ """ import re -from six import with_metaclass def _un_camelize(name): - s1 = re.sub('(.)([A-Z][a-z]+)', r'\1 \2', name) - return re.sub('([a-z0-9])([A-Z])', r'\1 \2', s1).lower() + s1 = re.sub("(.)([A-Z][a-z]+)", r"\1 \2", name) + return re.sub("([a-z0-9])([A-Z])", r"\1 \2", s1).lower() class _BiomeMetaclass(type): - def __new__(mcs, name, parents, dct): if not hasattr(_BiomeMetaclass, "biomes"): _BiomeMetaclass.biomes = {} un_camelized_name = _un_camelize(name) - created_class = super(_BiomeMetaclass, mcs).__new__(mcs, name, - parents, dct) - if object not in parents: + created_class = super().__new__(mcs, name, parents, dct) + # Don't register the base Biome class itself, only subclasses + if name != "Biome" and parents: _BiomeMetaclass.biomes[un_camelized_name] = created_class return created_class -class Biome(with_metaclass(_BiomeMetaclass, object)): - +class Biome(metaclass=_BiomeMetaclass): @classmethod def by_name(cls, name): if name not in _BiomeMetaclass.biomes: - raise Exception("No biome named '%s'" % name) + raise Exception(f"No biome named '{name}'") return _BiomeMetaclass.biomes[name]() @classmethod @@ -40,7 +37,8 @@ def all_names(cls): def name(cls): return _un_camelize(cls.__name__) -class BiomeGroup(object): + +class BiomeGroup: pass @@ -93,10 +91,11 @@ class CoolDesert(BiomeGroup): class Chaparral(BiomeGroup): - """ Chaparral is a shrubland or heathland plant community. + """Chaparral is a shrubland or heathland plant community. For details see http://en.wikipedia.org/wiki/Chaparral. """ + pass @@ -268,6 +267,7 @@ class TropicalRainForest(Biome, Jungle): # Serialization # ------------- + def biome_name_to_index(biome_name): names = sorted(_BiomeMetaclass.biomes.keys()) diff --git a/worldengine/cli/main.py b/worldengine/cli/main.py index 362d6066..51b47424 100644 --- a/worldengine/cli/main.py +++ b/worldengine/cli/main.py @@ -1,17 +1,27 @@ -import sys import os +import sys from argparse import ArgumentParser + import numpy import worldengine.generation as geo -from worldengine.common import set_verbose, print_verbose, get_verbose -from worldengine.draw import draw_ancientmap_on_file, draw_biome_on_file, draw_ocean_on_file, \ - draw_precipitation_on_file, draw_grayscale_heightmap_on_file, draw_simple_elevation_on_file, \ - draw_temperature_levels_on_file, draw_riversmap_on_file, draw_scatter_plot_on_file, \ - draw_satellite_on_file, draw_icecaps_on_file +from worldengine.common import get_verbose, print_verbose, set_verbose +from worldengine.draw import ( + draw_ancientmap_on_file, + draw_biome_on_file, + draw_grayscale_heightmap_on_file, + draw_icecaps_on_file, + draw_ocean_on_file, + draw_precipitation_on_file, + draw_riversmap_on_file, + draw_satellite_on_file, + draw_scatter_plot_on_file, + draw_simple_elevation_on_file, + draw_temperature_levels_on_file, +) from worldengine.imex import export -from worldengine.model.world import World, Size, GenerationParameters -from worldengine.plates import world_gen, generate_plates_simulation +from worldengine.model.world import GenerationParameters, Size, World +from worldengine.plates import generate_plates_simulation, world_gen from worldengine.step import Step from worldengine.version import __version__ @@ -19,92 +29,117 @@ from worldengine.hdf5_serialization import save_world_to_hdf5 HDF5_AVAILABLE = True -except: +except ImportError: HDF5_AVAILABLE = False VERSION = __version__ -OPERATIONS = 'world|plates|ancient_map|info|export' -SEA_COLORS = 'blue|brown' -STEPS = 'plates|precipitations|full' - - -def generate_world(world_name, width, height, seed, num_plates, output_dir, - step, ocean_level, temps, humids, world_format='protobuf', - gamma_curve=1.25, curve_offset=.2, fade_borders=True, - verbose=True, black_and_white=False): - w = world_gen(world_name, width, height, seed, temps, humids, num_plates, ocean_level, - step, gamma_curve=gamma_curve, curve_offset=curve_offset, - fade_borders=fade_borders, verbose=verbose) - - print('') # empty line - print('Producing ouput:') +OPERATIONS = "world|plates|ancient_map|info|export" +SEA_COLORS = "blue|brown" +STEPS = "plates|precipitations|full" + + +def generate_world( + world_name, + width, + height, + seed, + num_plates, + output_dir, + step, + ocean_level, + temps, + humids, + world_format="protobuf", + gamma_curve=1.25, + curve_offset=0.2, + fade_borders=True, + verbose=True, + black_and_white=False, +): + w = world_gen( + world_name, + width, + height, + seed, + temps, + humids, + num_plates, + ocean_level, + step, + gamma_curve=gamma_curve, + curve_offset=curve_offset, + fade_borders=fade_borders, + verbose=verbose, + ) + + print("") # empty line + print("Producing ouput:") sys.stdout.flush() # Save data - filename = "%s/%s.world" % (output_dir, world_name) - if world_format == 'protobuf': + filename = f"{output_dir}/{world_name}.world" + if world_format == "protobuf": with open(filename, "wb") as f: f.write(w.protobuf_serialize()) - elif world_format == 'hdf5': + elif world_format == "hdf5": save_world_to_hdf5(w, filename) else: - print("Unknown format '%s', not saving " % world_format) - print("* world data saved in '%s'" % filename) + print(f"Unknown format '{world_format}', not saving ") + print(f"* world data saved in '{filename}'") sys.stdout.flush() # Generate images - filename = '%s/%s_ocean.png' % (output_dir, world_name) - draw_ocean_on_file(w.layers['ocean'].data, filename) - print("* ocean image generated in '%s'" % filename) + filename = f"{output_dir}/{world_name}_ocean.png" + draw_ocean_on_file(w.layers["ocean"].data, filename) + print(f"* ocean image generated in '{filename}'") if step.include_precipitations: - filename = '%s/%s_precipitation.png' % (output_dir, world_name) + filename = f"{output_dir}/{world_name}_precipitation.png" draw_precipitation_on_file(w, filename, black_and_white) - print("* precipitation image generated in '%s'" % filename) - filename = '%s/%s_temperature.png' % (output_dir, world_name) + print(f"* precipitation image generated in '{filename}'") + filename = f"{output_dir}/{world_name}_temperature.png" draw_temperature_levels_on_file(w, filename, black_and_white) - print("* temperature image generated in '%s'" % filename) + print(f"* temperature image generated in '{filename}'") if step.include_biome: - filename = '%s/%s_biome.png' % (output_dir, world_name) + filename = f"{output_dir}/{world_name}_biome.png" draw_biome_on_file(w, filename) - print("* biome image generated in '%s'" % filename) + print(f"* biome image generated in '{filename}'") - filename = '%s/%s_elevation.png' % (output_dir, world_name) + filename = f"{output_dir}/{world_name}_elevation.png" sea_level = w.sea_level() draw_simple_elevation_on_file(w, filename, sea_level=sea_level) - print("* elevation image generated in '%s'" % filename) + print(f"* elevation image generated in '{filename}'") return w def generate_grayscale_heightmap(world, filename): draw_grayscale_heightmap_on_file(world, filename) - print("+ grayscale heightmap generated in '%s'" % filename) + print(f"+ grayscale heightmap generated in '{filename}'") def generate_rivers_map(world, filename): draw_riversmap_on_file(world, filename) - print("+ rivers map generated in '%s'" % filename) + print(f"+ rivers map generated in '{filename}'") def draw_scatter_plot(world, filename): draw_scatter_plot_on_file(world, filename) - print("+ scatter plot generated in '%s'" % filename) + print(f"+ scatter plot generated in '{filename}'") def draw_satellite_map(world, filename): draw_satellite_on_file(world, filename) - print("+ satellite map generated in '%s'" % filename) + print(f"+ satellite map generated in '{filename}'") def draw_icecaps_map(world, filename): draw_icecaps_on_file(world, filename) - print("+ icecap map generated in '%s'" % filename) + print(f"+ icecap map generated in '{filename}'") -def generate_plates(seed, world_name, output_dir, width, height, - num_plates=10): +def generate_plates(seed, world_name, output_dir, width, height, num_plates=10): """ Eventually this method should be invoked when generation is called at asked to stop at step "plates", it should not be a different operation @@ -116,22 +151,20 @@ def generate_plates(seed, world_name, output_dir, width, height, :param num_plates: :return: """ - elevation, plates = generate_plates_simulation(seed, width, height, - num_plates=num_plates) + elevation, plates = generate_plates_simulation(seed, width, height, num_plates=num_plates) - world = World(world_name, Size(width, height), seed, - GenerationParameters(num_plates, -1.0, "plates")) + world = World(world_name, Size(width, height), seed, GenerationParameters(num_plates, -1.0, "plates")) world.elevation = (numpy.array(elevation).reshape(height, width), None) world.plates = numpy.array(plates, dtype=numpy.uint16).reshape(height, width) # Generate images - filename = '%s/plates_%s.png' % (output_dir, world_name) + filename = f"{output_dir}/plates_{world_name}.png" draw_simple_elevation_on_file(world, filename, None) - print("+ plates image generated in '%s'" % filename) + print(f"+ plates image generated in '{filename}'") geo.center_land(world) - filename = '%s/centered_plates_%s.png' % (output_dir, world_name) + filename = f"{output_dir}/centered_plates_{world_name}.png" draw_simple_elevation_on_file(world, filename, None) - print("+ centered plates image generated in '%s'" % filename) + print(f"+ centered plates image generated in '{filename}'") def check_step(step_name): @@ -143,17 +176,25 @@ def check_step(step_name): return step -def operation_ancient_map(world, map_filename, resize_factor, sea_color, - draw_biome, draw_rivers, draw_mountains, - draw_outer_land_border): - draw_ancientmap_on_file(world, map_filename, resize_factor, sea_color, - draw_biome, draw_rivers, draw_mountains, - draw_outer_land_border, get_verbose()) - print("+ ancient map generated in '%s'" % map_filename) +def operation_ancient_map( + world, map_filename, resize_factor, sea_color, draw_biome, draw_rivers, draw_mountains, draw_outer_land_border +): + draw_ancientmap_on_file( + world, + map_filename, + resize_factor, + sea_color, + draw_biome, + draw_rivers, + draw_mountains, + draw_outer_land_border, + get_verbose(), + ) + print(f"+ ancient map generated in '{map_filename}'") def __get_last_byte__(filename): - with open(filename, 'rb') as input_file: + with open(filename, "rb") as input_file: data = tmp_data = input_file.read(1024 * 1024) while tmp_data: tmp_data = input_file.read(1024 * 1024) @@ -175,7 +216,7 @@ def __varint_to_value__(varint): def __get_tag__(filename): - with open(filename, 'rb') as ifile: + with open(filename, "rb") as ifile: # drop first byte, it should tell us the protobuf version and it # should be normally equal to 8 data = ifile.read(1) @@ -215,178 +256,271 @@ def load_world(world_filename): def print_world_info(world): - print(" name : %s" % world.name) + print(f" name : {world.name}") print(" width : %i" % world.width) print(" height : %i" % world.height) print(" seed : %i" % world.seed) print(" no plates : %i" % world.n_plates) - print(" ocean level : %f" % world.ocean_level) - print(" step : %s" % world.step.name) + print(f" ocean level : {world.ocean_level:f}") + print(f" step : {world.step.name}") - print(" has biome : %s" % world.has_biome()) - print(" has humidity : %s" % world.has_humidity()) - print(" has irrigation : %s" % world.has_irrigation()) - print(" has permeability : %s" % world.has_permeability()) - print(" has watermap : %s" % world.has_watermap()) - print(" has precipitations : %s" % world.has_precipitations()) - print(" has temperature : %s" % world.has_temperature()) + print(f" has biome : {world.has_biome()}") + print(f" has humidity : {world.has_humidity()}") + print(f" has irrigation : {world.has_irrigation()}") + print(f" has permeability : {world.has_permeability()}") + print(f" has watermap : {world.has_watermap()}") + print(f" has precipitations : {world.has_precipitations()}") + print(f" has temperature : {world.has_temperature()}") def main(): - parser = ArgumentParser( - usage="usage: %(prog)s [options] [" + OPERATIONS + "]") - parser.add_argument('OPERATOR', nargs='?') - parser.add_argument('FILE', nargs='?') + parser = ArgumentParser(usage="usage: %(prog)s [options] [" + OPERATIONS + "]") + parser.add_argument("OPERATOR", nargs="?") + parser.add_argument("FILE", nargs="?") parser.add_argument( - '-o', '--output-dir', dest='output_dir', + "-o", + "--output-dir", + dest="output_dir", help="generate files in DIR [default = '%(default)s']", - metavar="DIR", default='.') + metavar="DIR", + default=".", + ) parser.add_argument( - '-n', '--worldname', dest='world_name', - help="set world name to STR. output is stored in a " + - "world file with the name format 'STR.world'. If " + - "a name is not provided, then seed_N.world, " + - "where N=SEED", - metavar="STR") - parser.add_argument('--hdf5', dest='hdf5', - action="store_true", - help="Save world file using HDF5 format. " + - "Default = store using protobuf format", - default=False) - parser.add_argument('-s', '--seed', dest='seed', type=int, - help="Use seed=N to initialize the pseudo-random " + - "generation. If not provided, one will be " + - "selected for you.", - metavar="N") - parser.add_argument('-t', '--step', dest='step', - help="Use step=[" + STEPS + "] to specify how far " + - "to proceed in the world generation process. " + - "[default='%(default)s']", - metavar="STR", default="full") + "-n", + "--worldname", + dest="world_name", + help="set world name to STR. output is stored in a " + + "world file with the name format 'STR.world'. If " + + "a name is not provided, then seed_N.world, " + + "where N=SEED", + metavar="STR", + ) + parser.add_argument( + "--hdf5", + dest="hdf5", + action="store_true", + help="Save world file using HDF5 format. " + "Default = store using protobuf format", + default=False, + ) + parser.add_argument( + "-s", + "--seed", + dest="seed", + type=int, + help="Use seed=N to initialize the pseudo-random " + + "generation. If not provided, one will be " + + "selected for you.", + metavar="N", + ) + parser.add_argument( + "-t", + "--step", + dest="step", + help="Use step=[" + + STEPS + + "] to specify how far " + + "to proceed in the world generation process. " + + "[default='%(default)s']", + metavar="STR", + default="full", + ) # TODO --step appears to be duplicate of OPERATIONS. Especially if # ancient_map is added to --step - parser.add_argument('-x', '--width', dest='width', type=int, - help="N = width of the world to be generated " + - "[default=%(default)s]", - metavar="N", - default='512') - parser.add_argument('-y', '--height', dest='height', type=int, - help="N = height of the world to be generated " + - "[default=%(default)s]", - metavar="N", - default='512') - parser.add_argument('-q', '--number-of-plates', dest='number_of_plates', - type=int, - help="N = number of plates [default = %(default)s]", - metavar="N", default='10') - parser.add_argument('--recursion_limit', dest='recursion_limit', type=int, - help="Set the recursion limit [default = %(default)s]", - metavar="N", default='2000') - parser.add_argument('-v', '--verbose', dest='verbose', action="store_true", - help="Enable verbose messages", default=False) - parser.add_argument('--version', dest='version', action="store_true", - help="Display version information", default=False) - parser.add_argument('--bw', '--black-and-white', dest='black_and_white', - action="store_true", - help="generate maps in black and white", - default=False) + parser.add_argument( + "-x", + "--width", + dest="width", + type=int, + help="N = width of the world to be generated " + "[default=%(default)s]", + metavar="N", + default="512", + ) + parser.add_argument( + "-y", + "--height", + dest="height", + type=int, + help="N = height of the world to be generated " + "[default=%(default)s]", + metavar="N", + default="512", + ) + parser.add_argument( + "-q", + "--number-of-plates", + dest="number_of_plates", + type=int, + help="N = number of plates [default = %(default)s]", + metavar="N", + default="10", + ) + parser.add_argument( + "--recursion_limit", + dest="recursion_limit", + type=int, + help="Set the recursion limit [default = %(default)s]", + metavar="N", + default="2000", + ) + parser.add_argument( + "-v", "--verbose", dest="verbose", action="store_true", help="Enable verbose messages", default=False + ) + parser.add_argument( + "--version", dest="version", action="store_true", help="Display version information", default=False + ) + parser.add_argument( + "--bw", + "--black-and-white", + dest="black_and_white", + action="store_true", + help="generate maps in black and white", + default=False, + ) # ----------------------------------------------------- g_generate = parser.add_argument_group( - "Generate Options", "These options are only useful in plate and " + - "world modes") - g_generate.add_argument('-r', '--rivers', dest='rivers_map', - action="store_true", help="generate rivers map") - g_generate.add_argument('--gs', '--grayscale-heightmap', - dest='grayscale_heightmap', action="store_true", - help='produce a grayscale heightmap') - g_generate.add_argument('--ocean_level', dest='ocean_level', type=float, - help='elevation cut off for sea level " +' - '[default = %(default)s]', - metavar="N", default=1.0) - g_generate.add_argument('--temps', dest='temps', - help="Provide alternate ranges for temperatures. " + - "If not provided, the default values will be used. \n" + - "[default = .126/.235/.406/.561/.634/.876]", - metavar="#/#/#/#/#/#") - g_generate.add_argument('--humidity', dest='humids', - help="Provide alternate ranges for humidities. " + - "If not provided, the default values will be used. \n" + - "[default = .059/.222/.493/.764/.927/.986/.998]", - metavar="#/#/#/#/#/#/#") - g_generate.add_argument('-gv', '--gamma-value', dest='gv', type=float, - help="N = Gamma value for temperature/precipitation " + - "gamma correction curve. [default = %(default)s]", - metavar="N", default='1.25') - g_generate.add_argument('-go', '--gamma-offset', dest='go', type=float, - help="N = Adjustment value for temperature/precipitation " + - "gamma correction curve. [default = %(default)s]", - metavar="N", default='.2') - g_generate.add_argument('--not-fade-borders', dest='fade_borders', action="store_false", - help="Not fade borders", - default=True) - g_generate.add_argument('--scatter', dest='scatter_plot', - action="store_true", help="generate scatter plot") - g_generate.add_argument('--sat', dest='satelite_map', - action="store_true", help="generate satellite map") - g_generate.add_argument('--ice', dest='icecaps_map', - action="store_true", help="generate ice caps map") + "Generate Options", "These options are only useful in plate and " + "world modes" + ) + g_generate.add_argument("-r", "--rivers", dest="rivers_map", action="store_true", help="generate rivers map") + g_generate.add_argument( + "--gs", + "--grayscale-heightmap", + dest="grayscale_heightmap", + action="store_true", + help="produce a grayscale heightmap", + ) + g_generate.add_argument( + "--ocean_level", + dest="ocean_level", + type=float, + help='elevation cut off for sea level " +' "[default = %(default)s]", + metavar="N", + default=1.0, + ) + g_generate.add_argument( + "--temps", + dest="temps", + help="Provide alternate ranges for temperatures. " + + "If not provided, the default values will be used. \n" + + "[default = .126/.235/.406/.561/.634/.876]", + metavar="#/#/#/#/#/#", + ) + g_generate.add_argument( + "--humidity", + dest="humids", + help="Provide alternate ranges for humidities. " + + "If not provided, the default values will be used. \n" + + "[default = .059/.222/.493/.764/.927/.986/.998]", + metavar="#/#/#/#/#/#/#", + ) + g_generate.add_argument( + "-gv", + "--gamma-value", + dest="gv", + type=float, + help="N = Gamma value for temperature/precipitation " + "gamma correction curve. [default = %(default)s]", + metavar="N", + default="1.25", + ) + g_generate.add_argument( + "-go", + "--gamma-offset", + dest="go", + type=float, + help="N = Adjustment value for temperature/precipitation " + "gamma correction curve. [default = %(default)s]", + metavar="N", + default=".2", + ) + g_generate.add_argument( + "--not-fade-borders", dest="fade_borders", action="store_false", help="Not fade borders", default=True + ) + g_generate.add_argument("--scatter", dest="scatter_plot", action="store_true", help="generate scatter plot") + g_generate.add_argument("--sat", dest="satelite_map", action="store_true", help="generate satellite map") + g_generate.add_argument("--ice", dest="icecaps_map", action="store_true", help="generate ice caps map") # ----------------------------------------------------- g_ancient_map = parser.add_argument_group( - "Ancient Map Options", "These options are only useful in " + - "ancient_map mode") - g_ancient_map.add_argument('-w', '--worldfile', dest='world_file', - help="FILE to be loaded", metavar="FILE") - g_ancient_map.add_argument('-g', '--generatedfile', dest='generated_file', - help="name of the FILE", metavar="FILE") + "Ancient Map Options", "These options are only useful in " + "ancient_map mode" + ) + g_ancient_map.add_argument("-w", "--worldfile", dest="world_file", help="FILE to be loaded", metavar="FILE") + g_ancient_map.add_argument("-g", "--generatedfile", dest="generated_file", help="name of the FILE", metavar="FILE") + g_ancient_map.add_argument( + "-f", + "--resize-factor", + dest="resize_factor", + type=int, + help="resize factor (only integer values). " + + "Note this can only be used to increase " + + "the size of the map [default=%(default)s]", + metavar="N", + default="1", + ) g_ancient_map.add_argument( - '-f', '--resize-factor', dest='resize_factor', type=int, - help="resize factor (only integer values). " + - "Note this can only be used to increase " + - "the size of the map [default=%(default)s]", - metavar="N", default='1') - g_ancient_map.add_argument('--sea_color', dest='sea_color', - help="string for color [" + SEA_COLORS + "]", - metavar="S", default="brown") - - g_ancient_map.add_argument('--not-draw-biome', dest='draw_biome', - action="store_false", - help="Not draw biome", - default=True) - g_ancient_map.add_argument('--not-draw-mountains', dest='draw_mountains', - action="store_false", - help="Not draw mountains", - default=True) - g_ancient_map.add_argument('--not-draw-rivers', dest='draw_rivers', - action="store_false", - help="Not draw rivers", - default=True) - g_ancient_map.add_argument('--draw-outer-border', dest='draw_outer_border', - action="store_true", - help="Draw outer land border", - default=False) + "--sea_color", dest="sea_color", help="string for color [" + SEA_COLORS + "]", metavar="S", default="brown" + ) + + g_ancient_map.add_argument( + "--not-draw-biome", dest="draw_biome", action="store_false", help="Not draw biome", default=True + ) + g_ancient_map.add_argument( + "--not-draw-mountains", dest="draw_mountains", action="store_false", help="Not draw mountains", default=True + ) + g_ancient_map.add_argument( + "--not-draw-rivers", dest="draw_rivers", action="store_false", help="Not draw rivers", default=True + ) + g_ancient_map.add_argument( + "--draw-outer-border", + dest="draw_outer_border", + action="store_true", + help="Draw outer land border", + default=False, + ) # TODO: allow for RGB specification as [r g b], ie [0.5 0.5 0.5] for gray # ----------------------------------------------------- export_options = parser.add_argument_group( - "Export Options", "You can specify the formats you wish the generated output to be in. ") - export_options.add_argument("--export-format", dest="export_format", type=str, - help="Export to a specific format such as BMP or PNG. " + - "All possible formats: http://www.gdal.org/formats_list.html", - default="PNG", metavar="STR") - export_options.add_argument("--export-datatype", dest="export_datatype", type=str, - help="Type of stored data (e.g. uint16, int32, float32 and etc.)", - default="uint16", metavar="STR") - export_options.add_argument("--export-dimensions", dest="export_dimensions", type=int, - help="Export to desired dimensions. (e.g. 4096 4096)", default=None, - nargs=2) - export_options.add_argument("--export-normalize", dest="export_normalize", type=int, - help="Normalize the data set to between min and max. (e.g 0 255)", - nargs=2, default=None) - export_options.add_argument("--export-subset", dest="export_subset", type=int, - help="Normalize the data set to between min and max?", - nargs=4, default=None) + "Export Options", "You can specify the formats you wish the generated output to be in. " + ) + export_options.add_argument( + "--export-format", + dest="export_format", + type=str, + help="Export to a specific format such as BMP or PNG. " + + "All possible formats: http://www.gdal.org/formats_list.html", + default="PNG", + metavar="STR", + ) + export_options.add_argument( + "--export-datatype", + dest="export_datatype", + type=str, + help="Type of stored data (e.g. uint16, int32, float32 and etc.)", + default="uint16", + metavar="STR", + ) + export_options.add_argument( + "--export-dimensions", + dest="export_dimensions", + type=int, + help="Export to desired dimensions. (e.g. 4096 4096)", + default=None, + nargs=2, + ) + export_options.add_argument( + "--export-normalize", + dest="export_normalize", + type=int, + help="Normalize the data set to between min and max. (e.g 0 255)", + nargs=2, + default=None, + ) + export_options.add_argument( + "--export-subset", + dest="export_subset", + type=int, + help="Normalize the data set to between min and max?", + nargs=4, + default=None, + ) args = parser.parse_args() @@ -397,7 +531,7 @@ def main(): if not os.path.isdir(args.output_dir): raise Exception("Output dir exists but it is not a dir") else: - print('Directory does not exist, we are creating it') + print("Directory does not exist, we are creating it") os.makedirs(args.output_dir) # it needs to be increased to be able to generate very large maps @@ -419,7 +553,7 @@ def main(): else: operation = args.OPERATOR.lower() - if args.OPERATOR == 'info' or args.OPERATOR == 'export': + if args.OPERATOR == "info" or args.OPERATOR == "export": if args.FILE is None: parser.print_help() usage("For operation info only the filename should be specified") @@ -427,15 +561,12 @@ def main(): usage("The specified world file does not exist") # there is a hard limit somewhere so seeds outside the uint16 range are considered unsafe - maxseed = numpy.iinfo( - numpy.uint16).max + maxseed = numpy.iinfo(numpy.uint16).max if args.seed is not None: seed = int(args.seed) - assert 0 <= seed <= maxseed, \ - "Seed has to be in the range between 0 and %s, borders included." % maxseed + assert 0 <= seed <= maxseed, f"Seed has to be in the range between 0 and {maxseed}, borders included." else: - seed = numpy.random.randint(0, - maxseed) # first-time RNG initialization is done automatically + seed = numpy.random.randint(0, maxseed) # first-time RNG initialization is done automatically numpy.random.seed(seed) if args.world_name: @@ -445,16 +576,14 @@ def main(): step = check_step(args.step) - world_format = 'protobuf' + world_format = "protobuf" if args.hdf5: - world_format = 'hdf5' + world_format = "hdf5" - generation_operation = (operation == 'world') or (operation == 'plates') + generation_operation = (operation == "world") or (operation == "plates") if args.grayscale_heightmap and not generation_operation: - usage( - error="Grayscale heightmap can be produced only during world " + - "generation") + usage(error="Grayscale heightmap can be produced only during world " + "generation") if args.rivers_map and not generation_operation: usage(error="Rivers map can be produced only during world generation") @@ -462,7 +591,7 @@ def main(): if args.temps and not generation_operation: usage(error="temps can be assigned only during world generation") - if args.temps and len(args.temps.split('/')) != 6: + if args.temps and len(args.temps.split("/")) != 6: usage(error="temps must have exactly 6 values") if args.go >= 1 or args.go < 0: @@ -471,59 +600,59 @@ def main(): if args.gv <= 0: usage(error="Gamma value must be greater than 0") - temps = [.874, .765, .594, .439, .366, .124] + temps = [0.874, 0.765, 0.594, 0.439, 0.366, 0.124] if args.temps: - temps = args.temps.split('/') + temps = args.temps.split("/") for x in range(0, len(temps)): temps[x] = 1 - float(temps[x]) if args.humids and not generation_operation: usage(error="humidity can be assigned only during world generation") - if args.humids and len(args.humids.split('/')) != 7: + if args.humids and len(args.humids.split("/")) != 7: usage(error="humidity must have exactly 7 values") - humids = [.941, .778, .507, .236, 0.073, .014, .002] + humids = [0.941, 0.778, 0.507, 0.236, 0.073, 0.014, 0.002] if args.humids: - humids = args.humids.split('/') + humids = args.humids.split("/") for x in range(0, len(humids)): humids[x] = 1 - float(humids[x]) if args.scatter_plot and not generation_operation: usage(error="Scatter plot can be produced only during world generation") - print('Worldengine - a world generator (v. %s)' % VERSION) - print('-----------------------') + print(f"Worldengine - a world generator (v. {VERSION})") + print("-----------------------") if generation_operation: - print(' operation : %s generation' % operation) - print(' seed : %i' % seed) - print(' name : %s' % world_name) - print(' width : %i' % args.width) - print(' height : %i' % args.height) - print(' number of plates : %i' % args.number_of_plates) - print(' world format : %s' % world_format) - print(' black and white maps : %s' % args.black_and_white) - print(' step : %s' % step.name) - print(' greyscale heightmap : %s' % args.grayscale_heightmap) - print(' icecaps heightmap : %s' % args.icecaps_map) - print(' rivers map : %s' % args.rivers_map) - print(' scatter plot : %s' % args.scatter_plot) - print(' satellite map : %s' % args.satelite_map) - print(' fade borders : %s' % args.fade_borders) + print(f" operation : {operation} generation") + print(" seed : %i" % seed) + print(f" name : {world_name}") + print(" width : %i" % args.width) + print(" height : %i" % args.height) + print(" number of plates : %i" % args.number_of_plates) + print(f" world format : {world_format}") + print(f" black and white maps : {args.black_and_white}") + print(f" step : {step.name}") + print(f" greyscale heightmap : {args.grayscale_heightmap}") + print(f" icecaps heightmap : {args.icecaps_map}") + print(f" rivers map : {args.rivers_map}") + print(f" scatter plot : {args.scatter_plot}") + print(f" satellite map : {args.satelite_map}") + print(f" fade borders : {args.fade_borders}") if args.temps: - print(' temperature ranges : %s' % args.temps) + print(f" temperature ranges : {args.temps}") if args.humids: - print(' humidity ranges : %s' % args.humids) - print(' gamma value : %s' % args.gv) - print(' gamma offset : %s' % args.go) - if operation == 'ancient_map': - print(' operation : %s generation' % operation) - print(' resize factor : %i' % args.resize_factor) - print(' world file : %s' % args.world_file) - print(' sea color : %s' % args.sea_color) - print(' draw biome : %s' % args.draw_biome) - print(' draw rivers : %s' % args.draw_rivers) - print(' draw mountains : %s' % args.draw_mountains) - print(' draw land outer border : %s' % args.draw_outer_border) + print(f" humidity ranges : {args.humids}") + print(f" gamma value : {args.gv}") + print(f" gamma offset : {args.go}") + if operation == "ancient_map": + print(f" operation : {operation} generation") + print(" resize factor : %i" % args.resize_factor) + print(f" world file : {args.world_file}") + print(f" sea color : {args.sea_color}") + print(f" draw biome : {args.draw_biome}") + print(f" draw rivers : {args.draw_rivers}") + print(f" draw mountains : {args.draw_mountains}") + print(f" draw land outer border : {args.draw_outer_border}") # Warning messages warnings = [] @@ -547,94 +676,106 @@ def main(): set_verbose(args.verbose) - if operation == 'world': - print('') # empty line - print('starting (it could take a few minutes) ...') - - world = generate_world(world_name, args.width, args.height, - seed, args.number_of_plates, args.output_dir, - step, args.ocean_level, temps, humids, world_format, - gamma_curve=args.gv, curve_offset=args.go, - fade_borders=args.fade_borders, - verbose=args.verbose, black_and_white=args.black_and_white) + if operation == "world": + print("") # empty line + print("starting (it could take a few minutes) ...") + + world = generate_world( + world_name, + args.width, + args.height, + seed, + args.number_of_plates, + args.output_dir, + step, + args.ocean_level, + temps, + humids, + world_format, + gamma_curve=args.gv, + curve_offset=args.go, + fade_borders=args.fade_borders, + verbose=args.verbose, + black_and_white=args.black_and_white, + ) if args.grayscale_heightmap: - generate_grayscale_heightmap(world, - '%s/%s_grayscale.png' % (args.output_dir, world_name)) + generate_grayscale_heightmap(world, f"{args.output_dir}/{world_name}_grayscale.png") if args.rivers_map: - generate_rivers_map(world, - '%s/%s_rivers.png' % (args.output_dir, world_name)) + generate_rivers_map(world, f"{args.output_dir}/{world_name}_rivers.png") if args.scatter_plot: - draw_scatter_plot(world, - '%s/%s_scatter.png' % (args.output_dir, world_name)) + draw_scatter_plot(world, f"{args.output_dir}/{world_name}_scatter.png") if args.satelite_map: - draw_satellite_map(world, - '%s/%s_satellite.png' % (args.output_dir, world_name)) + draw_satellite_map(world, f"{args.output_dir}/{world_name}_satellite.png") if args.icecaps_map: - draw_icecaps_map(world, - '%s/%s_icecaps.png' % (args.output_dir, world_name)) + draw_icecaps_map(world, f"{args.output_dir}/{world_name}_icecaps.png") - elif operation == 'plates': - print('') # empty line - print('starting (it could take a few minutes) ...') + elif operation == "plates": + print("") # empty line + print("starting (it could take a few minutes) ...") - generate_plates(seed, world_name, args.output_dir, args.width, - args.height, num_plates=args.number_of_plates) + generate_plates(seed, world_name, args.output_dir, args.width, args.height, num_plates=args.number_of_plates) - elif operation == 'ancient_map': - print('') # empty line - print('starting (it could take a few minutes) ...') + elif operation == "ancient_map": + print("") # empty line + print("starting (it could take a few minutes) ...") # First, some error checking if args.sea_color == "blue": sea_color = (142, 162, 179, 255) elif args.sea_color == "brown": sea_color = (212, 198, 169, 255) else: - usage("Unknown sea color: " + args.sea_color + - " Select from [" + SEA_COLORS + "]") + usage("Unknown sea color: " + args.sea_color + " Select from [" + SEA_COLORS + "]") if not args.world_file: - usage( - "For generating an ancient map is necessary to specify the " + - "world to be used (-w option)") + usage("For generating an ancient map is necessary to specify the " + "world to be used (-w option)") world = load_world(args.world_file) print_verbose(" * world loaded") if not args.generated_file: - args.generated_file = "ancient_map_%s.png" % world.name - operation_ancient_map(world, args.generated_file, - args.resize_factor, sea_color, - args.draw_biome, args.draw_rivers, - args.draw_mountains, args.draw_outer_border) - elif operation == 'info': + args.generated_file = f"ancient_map_{world.name}.png" + operation_ancient_map( + world, + args.generated_file, + args.resize_factor, + sea_color, + args.draw_biome, + args.draw_rivers, + args.draw_mountains, + args.draw_outer_border, + ) + elif operation == "info": world = load_world(args.FILE) print_world_info(world) - elif operation == 'export': + elif operation == "export": world = load_world(args.FILE) print_world_info(world) - export(world, args.export_format, args.export_datatype, args.export_dimensions, - args.export_normalize, args.export_subset, - path='%s/%s_elevation' % (args.output_dir, world.name)) + export( + world, + args.export_format, + args.export_datatype, + args.export_dimensions, + args.export_normalize, + args.export_subset, + path=f"{args.output_dir}/{world.name}_elevation", + ) else: - raise Exception( - 'Unknown operation: valid operations are %s' % OPERATIONS) + raise Exception(f"Unknown operation: valid operations are {OPERATIONS}") - print('...done') + print("...done") def usage(error=None): - print( - ' -------------------------------------------------------------------') - print(' Federico Tomassetti and Bret Curtis, 2011-2017') - print(' Worldengine - a world generator (v. %s)' % VERSION) - print(' ') - print(' worldengine [operation] [options]') - print(' possible operations: %s' % OPERATIONS) - print(' use -h to see options') - print( - ' -------------------------------------------------------------------') + print(" -------------------------------------------------------------------") + print(" Federico Tomassetti and Bret Curtis, 2011-2017") + print(f" Worldengine - a world generator (v. {VERSION})") + print(" ") + print(" worldengine [operation] [options]") + print(f" possible operations: {OPERATIONS}") + print(" use -h to see options") + print(" -------------------------------------------------------------------") if error: - print("ERROR: %s" % error) - sys.exit(' ') + print(f"ERROR: {error}") + sys.exit(" ") # ------------------------------- diff --git a/worldengine/common.py b/worldengine/common.py index 4958a61d..619b4baa 100644 --- a/worldengine/common.py +++ b/worldengine/common.py @@ -1,4 +1,5 @@ import sys + import numpy # ---------------- @@ -16,7 +17,7 @@ def get_verbose(): global verbose - if 'verbose' not in globals(): + if "verbose" not in globals(): return False else: return verbose @@ -35,8 +36,7 @@ def print_verbose(msg): print(msg) -class Counter(object): - +class Counter: def __init__(self): self.c = {} @@ -67,7 +67,7 @@ def print_self(self): # current = map # # map_part = (2/11)*map -# +# # linear_filter = [[1/11, 1/11, 1/11], # [1/11, 1/11, 1/11], # [1/11, 1/11, 1/11]] @@ -80,6 +80,7 @@ def print_self(self): # Unless we want to add scipy as a dependency we only have 1D convolution at our hands from numpy. # So we take advantage of the kernel being seperable. + def anti_alias(map_in, steps): """ Execute the anti_alias operation steps times on the given map @@ -87,21 +88,20 @@ def anti_alias(map_in, steps): height, width = map_in.shape - map_part = (2.0/11.0)*map_in + map_part = (2.0 / 11.0) * map_in # notice how [-1/sqrt(3), -1/sqrt(3), -1/sqrt(3)] * [-1/sqrt(3), -1/sqrt(3), -1/sqrt(3)]^T # equals [[1/3, 1/3, 1/3], [1/3, 1/3, 1/3], [1/3, 1/3, 1/3]] # multiply that by (3/11) and we have the 2d kernel from the example above # therefore the kernel is seperable - w = -1.0/numpy.sqrt(3.0) + w = -1.0 / numpy.sqrt(3.0) kernel = [w, w, w] def _anti_alias_step(original): - # cf. comments above fo the factor # this also makes a copy which might actually be superfluous - result = original * (3.0/11.0) + result = original * (3.0 / 11.0) # we need to handle boundary conditions by hand, unfortunately # there might be a better way but this works (circular boundary) @@ -114,12 +114,12 @@ def _anti_alias_step(original): result = numpy.insert(result, [0], numpy.transpose([result[:, -2]]), 1) # with a seperable kernel we can convolve the rows first ... - for y in range(height+2): - result[y, 1:-1] = numpy.convolve(result[y, :], kernel, 'valid') + for y in range(height + 2): + result[y, 1:-1] = numpy.convolve(result[y, :], kernel, "valid") # ... and then the columns - for x in range(width+2): - result[1:-1, x] = numpy.convolve(result[:, x], kernel, 'valid') + for x in range(width + 2): + result[1:-1, x] = numpy.convolve(result[:, x], kernel, "valid") # throw away invalid values at the boundary result = result[1:-1, 1:-1] @@ -133,24 +133,25 @@ def _anti_alias_step(original): current = _anti_alias_step(current) return current + def count_neighbours(mask, radius=1): - '''Count how many neighbours of a coordinate are set to one. - This uses the same principles as anti_alias, compare comments there.''' + """Count how many neighbours of a coordinate are set to one. + This uses the same principles as anti_alias, compare comments there.""" height, width = mask.shape - f = 2.0*radius+1.0 + f = 2.0 * radius + 1.0 - w = -1.0/numpy.sqrt(f) - kernel = [w]*radius + [w] + [w]*radius + w = -1.0 / numpy.sqrt(f) + kernel = [w] * radius + [w] + [w] * radius result = mask * f for y in range(height): - result[y, :] = numpy.convolve(result[y, :], kernel, 'same') + result[y, :] = numpy.convolve(result[y, :], kernel, "same") for x in range(width): - result[:, x] = numpy.convolve(result[:, x], kernel, 'same') + result[:, x] = numpy.convolve(result[:, x], kernel, "same") return result - mask @@ -160,29 +161,31 @@ def _equal(a, b): # TODO: Remove and replace calls with specific comparisons. # recursion on subclasses of types: tuple, list, dict # specifically checks : float, ndarray - if type(a) is float and type(b) is float:#float - return(numpy.allclose(a, b)) - elif type(a) is numpy.ndarray and type(b) is numpy.ndarray:#ndarray - return(numpy.array_equiv(a, b))#alternative for float-arrays: numpy.allclose(a, b[, rtol, atol]) - elif isinstance(a, dict) and isinstance(b, dict):#dict + if type(a) is float and type(b) is float: # float + return numpy.allclose(a, b) + elif type(a) is numpy.ndarray and type(b) is numpy.ndarray: # ndarray + return numpy.array_equiv(a, b) # alternative for float-arrays: numpy.allclose(a, b[, rtol, atol]) + elif isinstance(a, dict) and isinstance(b, dict): # dict if len(a) != len(b): - return(False) + return False t = True for key, val in a.items(): if key not in b: - return(False) + return False t = _equal(val, b[key]) if not t: - return(False) - return(t) - elif (isinstance(a, list) and isinstance(b, list)) or (isinstance(a, tuple) and isinstance(b, tuple)):#list, tuples + return False + return t + elif (isinstance(a, list) and isinstance(b, list)) or ( + isinstance(a, tuple) and isinstance(b, tuple) + ): # list, tuples if len(a) != len(b): - return(False) + return False t = True for vala, valb in zip(a, b): t = _equal(vala, valb) if not t: - return(False) - return(t) - else:#fallback - return (a == b) + return False + return t + else: # fallback + return a == b diff --git a/worldengine/draw.py b/worldengine/draw.py index 355d3145..e116c250 100644 --- a/worldengine/draw.py +++ b/worldengine/draw.py @@ -1,7 +1,6 @@ import numpy -from worldengine.drawing_functions import draw_ancientmap, \ - draw_rivers_on_image +from worldengine.drawing_functions import draw_ancientmap, draw_rivers_on_image from worldengine.image_io import PNGWriter # ------------- @@ -12,20 +11,20 @@ # a random value between -NOISE_RANGE and NOISE_RANGE will be added to the rgb of each pixel NOISE_RANGE = 15 -# These are arbitrarily-chosen elevation cutoffs for 4 different height levels. +# These are arbitrarily-chosen elevation cutoffs for 4 different height levels. # Some color modifiers will be applied at each level HIGH_MOUNTAIN_ELEV = 215 -MOUNTAIN_ELEV = 175 -HIGH_HILL_ELEV = 160 -HILL_ELEV = 145 +MOUNTAIN_ELEV = 175 +HIGH_HILL_ELEV = 160 +HILL_ELEV = 145 # These are rgb color values which will be added to the noise, # if the elevation is greater than the height specified # These are not cumulative -HIGH_MOUNTAIN_NOISE_MODIFIER = (10, 6, 10) -MOUNTAIN_NOISE_MODIFIER = (-4, -12, -4) -HIGH_HILL_NOISE_MODIFIER = (-3, -10, -3) -HILL_NOISE_MODIFIER = (-2, -6, -2) +HIGH_MOUNTAIN_NOISE_MODIFIER = (10, 6, 10) +MOUNTAIN_NOISE_MODIFIER = (-4, -12, -4) +HIGH_HILL_NOISE_MODIFIER = (-3, -10, -3) +HILL_NOISE_MODIFIER = (-2, -6, -2) # This is the base "mountain color". # Elevations above this size will have their colors interpolated with this @@ -50,97 +49,97 @@ _biome_colors = { - 'ocean': (23, 94, 145), - 'sea': (23, 94, 145), - 'ice': (255, 255, 255), - 'subpolar dry tundra': (128, 128, 128), - 'subpolar moist tundra': (96, 128, 128), - 'subpolar wet tundra': (64, 128, 128), - 'subpolar rain tundra': (32, 128, 192), - 'polar desert': (192, 192, 192), - 'boreal desert': (160, 160, 128), - 'cool temperate desert': (192, 192, 128), - 'warm temperate desert': (224, 224, 128), - 'subtropical desert': (240, 240, 128), - 'tropical desert': (255, 255, 128), - 'boreal rain forest': (32, 160, 192), - 'cool temperate rain forest': (32, 192, 192), - 'warm temperate rain forest': (32, 224, 192), - 'subtropical rain forest': (32, 240, 176), - 'tropical rain forest': (32, 255, 160), - 'boreal wet forest': (64, 160, 144), - 'cool temperate wet forest': (64, 192, 144), - 'warm temperate wet forest': (64, 224, 144), - 'subtropical wet forest': (64, 240, 144), - 'tropical wet forest': (64, 255, 144), - 'boreal moist forest': (96, 160, 128), - 'cool temperate moist forest': (96, 192, 128), - 'warm temperate moist forest': (96, 224, 128), - 'subtropical moist forest': (96, 240, 128), - 'tropical moist forest': (96, 255, 128), - 'warm temperate dry forest': (128, 224, 128), - 'subtropical dry forest': (128, 240, 128), - 'tropical dry forest': (128, 255, 128), - 'boreal dry scrub': (128, 160, 128), - 'cool temperate desert scrub': (160, 192, 128), - 'warm temperate desert scrub': (192, 224, 128), - 'subtropical desert scrub': (208, 240, 128), - 'tropical desert scrub': (224, 255, 128), - 'cool temperate steppe': (128, 192, 128), - 'warm temperate thorn scrub': (160, 224, 128), - 'subtropical thorn woodland': (176, 240, 128), - 'tropical thorn woodland': (192, 255, 128), - 'tropical very dry forest': (160, 255, 128), + "ocean": (23, 94, 145), + "sea": (23, 94, 145), + "ice": (255, 255, 255), + "subpolar dry tundra": (128, 128, 128), + "subpolar moist tundra": (96, 128, 128), + "subpolar wet tundra": (64, 128, 128), + "subpolar rain tundra": (32, 128, 192), + "polar desert": (192, 192, 192), + "boreal desert": (160, 160, 128), + "cool temperate desert": (192, 192, 128), + "warm temperate desert": (224, 224, 128), + "subtropical desert": (240, 240, 128), + "tropical desert": (255, 255, 128), + "boreal rain forest": (32, 160, 192), + "cool temperate rain forest": (32, 192, 192), + "warm temperate rain forest": (32, 224, 192), + "subtropical rain forest": (32, 240, 176), + "tropical rain forest": (32, 255, 160), + "boreal wet forest": (64, 160, 144), + "cool temperate wet forest": (64, 192, 144), + "warm temperate wet forest": (64, 224, 144), + "subtropical wet forest": (64, 240, 144), + "tropical wet forest": (64, 255, 144), + "boreal moist forest": (96, 160, 128), + "cool temperate moist forest": (96, 192, 128), + "warm temperate moist forest": (96, 224, 128), + "subtropical moist forest": (96, 240, 128), + "tropical moist forest": (96, 255, 128), + "warm temperate dry forest": (128, 224, 128), + "subtropical dry forest": (128, 240, 128), + "tropical dry forest": (128, 255, 128), + "boreal dry scrub": (128, 160, 128), + "cool temperate desert scrub": (160, 192, 128), + "warm temperate desert scrub": (192, 224, 128), + "subtropical desert scrub": (208, 240, 128), + "tropical desert scrub": (224, 255, 128), + "cool temperate steppe": (128, 192, 128), + "warm temperate thorn scrub": (160, 224, 128), + "subtropical thorn woodland": (176, 240, 128), + "tropical thorn woodland": (192, 255, 128), + "tropical very dry forest": (160, 255, 128), } # These colors are used when drawing the satellite view map -# The rgb values were hand-picked from an actual high-resolution +# The rgb values were hand-picked from an actual high-resolution # satellite map of earth. However, many values are either too similar # to each other or otherwise need to be updated. It is recommended that # further research go into these values, making sure that each rgb is # actually picked from a region on earth that has the matching biome _biome_satellite_colors = { - 'ocean': (23, 94, 145), - 'sea': (23, 94, 145), - 'ice': (255, 255, 255), - 'subpolar dry tundra': (186, 199, 206), - 'subpolar moist tundra': (186, 195, 202), - 'subpolar wet tundra': (186, 195, 204), - 'subpolar rain tundra': (186, 200, 210), - 'polar desert': (182, 195, 201), - 'boreal desert': (132, 146, 143), - 'cool temperate desert': (183, 163, 126), - 'warm temperate desert': (166, 142, 104), - 'subtropical desert': (205, 181, 137), - 'tropical desert': (203, 187, 153), - 'boreal rain forest': (21, 29, 8), - 'cool temperate rain forest': (25, 34, 15), - 'warm temperate rain forest': (19, 28, 7), - 'subtropical rain forest': (48, 60, 24), - 'tropical rain forest': (21, 38, 6), - 'boreal wet forest': (6, 17, 11), - 'cool temperate wet forest': (6, 17, 11), - 'warm temperate wet forest': (44, 48, 19), - 'subtropical wet forest': (23, 36, 10), - 'tropical wet forest': (23, 36, 10), - 'boreal moist forest': (31, 39, 18), - 'cool temperate moist forest': (31, 39, 18), - 'warm temperate moist forest': (36, 42, 19), - 'subtropical moist forest': (23, 31, 10), - 'tropical moist forest': (24, 36, 11), - 'warm temperate dry forest': (52, 51, 30), - 'subtropical dry forest': (53, 56, 30), - 'tropical dry forest': (54, 60, 30), - 'boreal dry scrub': (73, 70, 61), - 'cool temperate desert scrub': (80, 58, 44), - 'warm temperate desert scrub': (92, 81, 49), - 'subtropical desert scrub': (68, 57, 35), - 'tropical desert scrub': (107, 87, 60), - 'cool temperate steppe': (95, 82, 50), - 'warm temperate thorn scrub': (77, 81, 48), - 'subtropical thorn woodland': (27, 40, 12), - 'tropical thorn woodland': (40, 62, 15), - 'tropical very dry forest': (87, 81, 49), + "ocean": (23, 94, 145), + "sea": (23, 94, 145), + "ice": (255, 255, 255), + "subpolar dry tundra": (186, 199, 206), + "subpolar moist tundra": (186, 195, 202), + "subpolar wet tundra": (186, 195, 204), + "subpolar rain tundra": (186, 200, 210), + "polar desert": (182, 195, 201), + "boreal desert": (132, 146, 143), + "cool temperate desert": (183, 163, 126), + "warm temperate desert": (166, 142, 104), + "subtropical desert": (205, 181, 137), + "tropical desert": (203, 187, 153), + "boreal rain forest": (21, 29, 8), + "cool temperate rain forest": (25, 34, 15), + "warm temperate rain forest": (19, 28, 7), + "subtropical rain forest": (48, 60, 24), + "tropical rain forest": (21, 38, 6), + "boreal wet forest": (6, 17, 11), + "cool temperate wet forest": (6, 17, 11), + "warm temperate wet forest": (44, 48, 19), + "subtropical wet forest": (23, 36, 10), + "tropical wet forest": (23, 36, 10), + "boreal moist forest": (31, 39, 18), + "cool temperate moist forest": (31, 39, 18), + "warm temperate moist forest": (36, 42, 19), + "subtropical moist forest": (23, 31, 10), + "tropical moist forest": (24, 36, 11), + "warm temperate dry forest": (52, 51, 30), + "subtropical dry forest": (53, 56, 30), + "tropical dry forest": (54, 60, 30), + "boreal dry scrub": (73, 70, 61), + "cool temperate desert scrub": (80, 58, 44), + "warm temperate desert scrub": (92, 81, 49), + "subtropical desert scrub": (68, 57, 35), + "tropical desert scrub": (107, 87, 60), + "cool temperate steppe": (95, 82, 50), + "warm temperate thorn scrub": (77, 81, 48), + "subtropical thorn woodland": (27, 40, 12), + "tropical thorn woodland": (40, 62, 15), + "tropical very dry forest": (87, 81, 49), } # ---------------- @@ -154,11 +153,11 @@ def _elevation_color(elevation, sea_level=1.0): :param elevation: :return: """ - #TODO: Write me in a way that is more speaking and uses less magic numbers. + # TODO: Write me in a way that is more speaking and uses less magic numbers. color_step = 1.5 if sea_level is None: sea_level = -1 - if elevation < sea_level/2: + if elevation < sea_level / 2: elevation /= sea_level return 0.0, 0.0, 0.75 + 0.5 * elevation elif elevation < sea_level: @@ -167,31 +166,29 @@ def _elevation_color(elevation, sea_level=1.0): else: elevation -= sea_level if elevation < 1.0 * color_step: - return (0.0, 0.5 + - 0.5 * elevation / color_step, 0.0) + return (0.0, 0.5 + 0.5 * elevation / color_step, 0.0) elif elevation < 1.5 * color_step: return 2 * (elevation - 1.0 * color_step) / color_step, 1.0, 0.0 elif elevation < 2.0 * color_step: return 1.0, 1.0 - (elevation - 1.5 * color_step) / color_step, 0 elif elevation < 3.0 * color_step: - return (1.0 - 0.5 * (elevation - 2.0 * - color_step) / color_step, - 0.5 - 0.25 * (elevation - 2.0 * - color_step) / color_step, 0) + return ( + 1.0 - 0.5 * (elevation - 2.0 * color_step) / color_step, + 0.5 - 0.25 * (elevation - 2.0 * color_step) / color_step, + 0, + ) elif elevation < 5.0 * color_step: - return (0.5 - 0.125 * (elevation - 3.0 * - color_step) / (2 * color_step), - 0.25 + 0.125 * (elevation - 3.0 * - color_step) / (2 * color_step), - 0.375 * (elevation - 3.0 * - color_step) / (2 * color_step)) + return ( + 0.5 - 0.125 * (elevation - 3.0 * color_step) / (2 * color_step), + 0.25 + 0.125 * (elevation - 3.0 * color_step) / (2 * color_step), + 0.375 * (elevation - 3.0 * color_step) / (2 * color_step), + ) elif elevation < 8.0 * color_step: - return (0.375 + 0.625 * (elevation - 5.0 * - color_step) / (3 * color_step), - 0.375 + 0.625 * (elevation - 5.0 * - color_step) / (3 * color_step), - 0.375 + 0.625 * (elevation - 5.0 * - color_step) / (3 * color_step)) + return ( + 0.375 + 0.625 * (elevation - 5.0 * color_step) / (3 * color_step), + 0.375 + 0.625 * (elevation - 5.0 * color_step) / (3 * color_step), + 0.375 + 0.625 * (elevation - 5.0 * color_step) / (3 * color_step), + ) else: elevation -= 8.0 * color_step while elevation > 2.0 * color_step: @@ -221,27 +218,28 @@ def elevation_color(elevation, sea_level=1.0): def add_colors(*args): - ''' Do some *args magic to return a tuple, which has the sums of all tuples in *args ''' + """Do some *args magic to return a tuple, which has the sums of all tuples in *args""" # Adapted from an answer here: http://stackoverflow.com/questions/14180866/sum-each-value-in-a-list-of-tuples - added = [sum(x) for x in zip(*args)] + # Convert to int to avoid overflow issues with uint8 arrays in NumPy 2.x + added = [sum(int(x) if isinstance(x, (numpy.integer, numpy.uint8)) else x for x in vals) for vals in zip(*args)] return numpy.clip(added, 0, 255) # restrict to uint8 def average_colors(c1, c2): - ''' Average the values of two colors together ''' - r = int((c1[0] + c2[0])/2) - g = int((c1[1] + c2[1])/2) - b = int((c1[2] + c2[2])/2) + """Average the values of two colors together""" + r = int((c1[0] + c2[0]) / 2) + g = int((c1[1] + c2[1]) / 2) + b = int((c1[2] + c2[2]) / 2) return (r, g, b) def get_normalized_elevation_array(world): - ''' Convert raw elevation into normalized values between 0 and 255, - and return a numpy array of these values ''' + """Convert raw elevation into normalized values between 0 and 255, + and return a numpy array of these values""" - e = world.layers['elevation'].data - ocean = world.layers['ocean'].data + e = world.layers["elevation"].data + ocean = world.layers["ocean"].data mask = numpy.ma.array(e, mask=ocean) # only land min_elev_land = mask.min() @@ -253,7 +251,7 @@ def get_normalized_elevation_array(world): max_elev_sea = mask.max() elev_delta_sea = max_elev_sea - min_elev_sea - c = numpy.empty(e.shape, dtype=numpy.float) + c = numpy.empty(e.shape, dtype=numpy.float64) c[numpy.invert(ocean)] = (e[numpy.invert(ocean)] - min_elev_land) * 127 / elev_delta_land + 128 c[ocean] = (e[ocean] - min_elev_sea) * 127 / elev_delta_sea c = numpy.rint(c).astype(dtype=numpy.int32) # proper rounding @@ -262,22 +260,22 @@ def get_normalized_elevation_array(world): def get_biome_color_based_on_elevation(world, elev, x, y, rng): - ''' This is the "business logic" for determining the base biome color in satellite view. - This includes generating some "noise" at each spot in a pixel's rgb value, potentially - modifying the noise based on elevation, and finally incorporating this with the base biome color. + """This is the "business logic" for determining the base biome color in satellite view. + This includes generating some "noise" at each spot in a pixel's rgb value, potentially + modifying the noise based on elevation, and finally incorporating this with the base biome color. - The basic rules regarding noise generation are: - - Oceans have no noise added - - land tiles start with noise somewhere inside (-NOISE_RANGE, NOISE_RANGE) for each rgb value - - land tiles with high elevations further modify the noise by set amounts (to drain some of the - color and make the map look more like mountains) + The basic rules regarding noise generation are: + - Oceans have no noise added + - land tiles start with noise somewhere inside (-NOISE_RANGE, NOISE_RANGE) for each rgb value + - land tiles with high elevations further modify the noise by set amounts (to drain some of the + color and make the map look more like mountains) - The biome's base color may be interpolated with a predefined mountain brown color if the elevation is high enough. + The biome's base color may be interpolated with a predefined mountain brown color if the elevation is high enough. - Finally, the noise plus the biome color are added and returned. + Finally, the noise plus the biome color are added and returned. - rng refers to an instance of a random number generator used to draw the random samples needed by this function. - ''' + rng refers to an instance of a random number generator used to draw the random samples needed by this function. + """ v = world.biome_at((x, y)).name() biome_color = _biome_satellite_colors[v] @@ -325,28 +323,31 @@ def get_biome_color_based_on_elevation(world, elev, x, y, rng): # Draw on generic target # ---------------------- + def draw_simple_elevation(world, sea_level, target): - """ This function can be used on a generic canvas (either an image to save - on disk or a canvas part of a GUI) + """This function can be used on a generic canvas (either an image to save + on disk or a canvas part of a GUI) """ - e = world.layers['elevation'].data - c = numpy.empty(e.shape, dtype=numpy.float) + e = world.layers["elevation"].data + c = numpy.empty(e.shape, dtype=numpy.float64) - has_ocean = not (sea_level is None or world.layers['ocean'].data is None or not world.layers['ocean'].data.any()) # or 'not any ocean' - mask_land = numpy.ma.array(e, mask=world.layers['ocean'].data if has_ocean else False) # only land + has_ocean = not ( + sea_level is None or world.layers["ocean"].data is None or not world.layers["ocean"].data.any() + ) # or 'not any ocean' + mask_land = numpy.ma.array(e, mask=world.layers["ocean"].data if has_ocean else False) # only land min_elev_land = mask_land.min() max_elev_land = mask_land.max() elev_delta_land = (max_elev_land - min_elev_land) / 11.0 if has_ocean: - land = numpy.logical_not(world.layers['ocean'].data) + land = numpy.logical_not(world.layers["ocean"].data) mask_ocean = numpy.ma.array(e, mask=land) # only ocean min_elev_sea = mask_ocean.min() max_elev_sea = mask_ocean.max() elev_delta_sea = max_elev_sea - min_elev_sea - c[world.layers['ocean'].data] = ((e[world.layers['ocean'].data] - min_elev_sea) / elev_delta_sea) + c[world.layers["ocean"].data] = (e[world.layers["ocean"].data] - min_elev_sea) / elev_delta_sea c[land] = ((e[land] - min_elev_land) / elev_delta_land) + 1 else: c = ((e - min_elev_land) / elev_delta_land) + 1 @@ -354,8 +355,7 @@ def draw_simple_elevation(world, sea_level, target): for y in range(world.height): for x in range(world.width): r, g, b = elevation_color(c[y, x], sea_level) - target.set_pixel(x, y, (int(r * 255), int(g * 255), - int(b * 255), 255)) + target.set_pixel(x, y, (int(r * 255), int(g * 255), int(b * 255), 255)) def draw_riversmap(world, target): @@ -378,13 +378,17 @@ def draw_grayscale_heightmap(world, target): def draw_satellite(world, target): - ''' This draws a "satellite map" - a view of the generated planet as it may look from space ''' + """This draws a "satellite map" - a view of the generated planet as it may look from space""" # Get an elevation mask where heights are normalized between 0 and 255 elevation_mask = get_normalized_elevation_array(world) - smooth_mask = numpy.invert(world.layers['ocean'].data) # all land shall be smoothed (other tiles can be included by setting them to True) + smooth_mask = numpy.invert( + world.layers["ocean"].data + ) # all land shall be smoothed (other tiles can be included by setting them to True) - rng = numpy.random.RandomState(world.seed) # create our own random generator; necessary for now to make the tests reproducible, even though it is a bit ugly + rng = numpy.random.RandomState( + world.seed + ) # create our own random generator; necessary for now to make the tests reproducible, even though it is a bit ugly ## The first loop sets each pixel's color based on colors defined in _biome_satellite_colors # and additional "business logic" defined in get_biome_color_based_on_elevation @@ -392,7 +396,7 @@ def draw_satellite(world, target): for x in range(world.width): # Get the normalized elevation at this pixel elev = elevation_mask[y, x] - + # Get a rgb noise value, with some logic to modify it based on the elevation of the tile r, g, b = get_biome_color_based_on_elevation(world, elev, x, y, rng) @@ -401,17 +405,19 @@ def draw_satellite(world, target): target.set_pixel(x, y, (r, g, b, 255)) # Paint frozen areas. - ice_color_variation = int(30) # 0 means perfectly white ice; must be in [0, 255]; only affects R- and G-channel + ice_color_variation = 30 # 0 means perfectly white ice; must be in [0, 255]; only affects R- and G-channel for y in range(world.height): for x in range(world.width): - if world.layers['icecap'].data[y, x] > 0.0: + if world.layers["icecap"].data[y, x] > 0.0: smooth_mask[y, x] = True # smooth the frozen areas, too variation = rng.randint(0, ice_color_variation) - target.set_pixel(x, y, (255 - ice_color_variation + variation, 255 - ice_color_variation + variation, 255, 255)) + target.set_pixel( + x, y, (255 - ice_color_variation + variation, 255 - ice_color_variation + variation, 255, 255) + ) # Loop through and average a pixel with its neighbors to smooth transitions between biomes - for y in range(1, world.height-1): - for x in range(1, world.width-1): + for y in range(1, world.height - 1): + for x in range(1, world.width - 1): ## Only smooth land tiles if smooth_mask[y, x]: # Lists to hold the separated rgb values of the neighboring pixels @@ -420,8 +426,8 @@ def draw_satellite(world, target): all_b = [] # Loop through this pixel and all neighboring pixels - for j in range(y-1, y+2): - for i in range(x-1, x+2): + for j in range(y - 1, y + 2): + for i in range(x - 1, x + 2): # Don't include ocean in the smoothing, if this tile happens to border an ocean if smooth_mask[j, i]: # Grab each rgb value and append to the list @@ -432,9 +438,10 @@ def draw_satellite(world, target): # Making sure there is at least one valid tile to be smoothed before we attempt to average the values if all_r: - avg_r = int(sum(all_r) / len(all_r)) - avg_g = int(sum(all_g) / len(all_g)) - avg_b = int(sum(all_b) / len(all_b)) + # Convert to int to avoid overflow with uint8 in NumPy 2.x + avg_r = int(sum(int(v) for v in all_r) / len(all_r)) + avg_g = int(sum(int(v) for v in all_g) / len(all_g)) + avg_b = int(sum(int(v) for v in all_b) / len(all_b)) ## Setting color of the pixel again - this will be once more modified by the shading algorithm target.set_pixel(x, y, (avg_r, avg_g, avg_b, 255)) @@ -443,34 +450,34 @@ def draw_satellite(world, target): for y in range(world.height): for x in range(world.width): ## Color rivers - if world.is_land((x, y)) and (world.layers['river_map'].data[y, x] > 0.0): + if world.is_land((x, y)) and (world.layers["river_map"].data[y, x] > 0.0): base_color = target[y, x] r, g, b = add_colors(base_color, RIVER_COLOR_CHANGE) target.set_pixel(x, y, (r, g, b, 255)) ## Color lakes - if world.is_land((x, y)) and (world.layers['lake_map'].data[y, x] != 0): + if world.is_land((x, y)) and (world.layers["lake_map"].data[y, x] != 0): base_color = target[y, x] r, g, b = add_colors(base_color, LAKE_COLOR_CHANGE) target.set_pixel(x, y, (r, g, b, 255)) # "Shade" the map by sending beams of light west to east, and increasing or decreasing value of pixel based on elevation difference - for y in range(SAT_SHADOW_SIZE-1, world.height-SAT_SHADOW_SIZE-1): - for x in range(SAT_SHADOW_SIZE-1, world.width-SAT_SHADOW_SIZE-1): + for y in range(SAT_SHADOW_SIZE - 1, world.height - SAT_SHADOW_SIZE - 1): + for x in range(SAT_SHADOW_SIZE - 1, world.width - SAT_SHADOW_SIZE - 1): if world.is_land((x, y)): r, g, b, a = target[y, x] - + # Build up list of elevations in the previous n tiles, where n is the shadow size. # This goes northwest to southeast - prev_elevs = [world.layers['elevation'].data[y-n, x-n] for n in range(1, SAT_SHADOW_SIZE+1)] + prev_elevs = [world.layers["elevation"].data[y - n, x - n] for n in range(1, SAT_SHADOW_SIZE + 1)] # Take the average of the height of the previous n tiles avg_prev_elev = int(sum(prev_elevs) / len(prev_elevs)) # Find the difference between this tile's elevation, and the average of the previous elevations - difference = int(world.layers['elevation'].data[y, x] - avg_prev_elev) + difference = int(world.layers["elevation"].data[y, x] - avg_prev_elev) # Amplify the difference adjusted_difference = difference * SAT_SHADOW_DISTANCE_MULTIPLIER @@ -478,9 +485,10 @@ def draw_satellite(world, target): # The amplified difference is now translated into the rgb of the tile. # This adds light to tiles higher that the previous average, and shadow # to tiles lower than the previous average - r = numpy.clip(adjusted_difference + r, 0, 255) # prevent under-/overflows - g = numpy.clip(adjusted_difference + g, 0, 255) - b = numpy.clip(adjusted_difference + b, 0, 255) + # Convert uint8 to int to avoid overflow in NumPy 2.x + r = numpy.clip(adjusted_difference + int(r), 0, 255) # prevent under-/overflows + g = numpy.clip(adjusted_difference + int(g), 0, 255) + b = numpy.clip(adjusted_difference + int(b), 0, 255) # Set the final color for this pixel target.set_pixel(x, y, (r, g, b, 255)) @@ -490,8 +498,8 @@ def draw_elevation(world, shadow, target): width = world.width height = world.height - data = world.layers['elevation'].data - ocean = world.layers['ocean'].data + data = world.layers["elevation"].data + ocean = world.layers["ocean"].data mask = numpy.ma.array(data, mask=ocean) @@ -509,12 +517,13 @@ def draw_elevation(world, shadow, target): if shadow and y > 2 and x > 2: if data[y - 1, x - 1] > e: c -= 15 - if data[y - 2, x - 2] > e \ - and data[y - 2, x - 2] > data[y - 1, x - 1]: + if data[y - 2, x - 2] > e and data[y - 2, x - 2] > data[y - 1, x - 1]: c -= 10 - if data[y - 3, x - 3] > e \ - and data[y - 3, x - 3] > data[y - 1, x - 1] \ - and data[y - 3, x - 3] > data[y - 2, x - 2]: + if ( + data[y - 3, x - 3] > e + and data[y - 3, x - 3] > data[y - 1, x - 1] + and data[y - 3, x - 3] > data[y - 2, x - 2] + ): c -= 5 if c < 0: c = 0 @@ -538,12 +547,12 @@ def draw_precipitation(world, target, black_and_white=False): height = world.height if black_and_white: - low = world.precipitation['data'].min() - high = world.precipitation['data'].max() + low = world.precipitation["data"].min() + high = world.precipitation["data"].max() floor = 0 ceiling = 255 # could be changed into 16 Bit grayscale easily - colors = numpy.interp(world.precipitation['data'], [low, high], [floor, ceiling]) + colors = numpy.interp(world.precipitation["data"], [low, high], [floor, ceiling]) colors = numpy.rint(colors).astype(dtype=numpy.int32) # proper rounding for y in range(height): for x in range(width): @@ -579,7 +588,7 @@ def draw_world(world, target): biome = world.biome_at((x, y)) target.set_pixel(x, y, _biome_colors[biome.name()]) else: - c = int(world.layers['sea_depth'].data[y, x] * 200 + 50) + c = int(world.layers["sea_depth"].data[y, x] * 200 + 50) target.set_pixel(x, y, (0, 0, 255 - c, 255)) @@ -593,7 +602,7 @@ def draw_temperature_levels(world, target, black_and_white=False): floor = 0 ceiling = 255 # could be changed into 16 Bit grayscale easily - colors = numpy.interp(world.temperature['data'], [low, high], [floor, ceiling]) + colors = numpy.interp(world.temperature["data"], [low, high], [floor, ceiling]) colors = numpy.rint(colors).astype(dtype=numpy.int32) # proper rounding for y in range(height): for x in range(width): @@ -622,7 +631,7 @@ def draw_biome(world, target): width = world.width height = world.height - biome = world.layers['biome'].data + biome = world.layers["biome"].data for y in range(height): for x in range(width): @@ -631,36 +640,40 @@ def draw_biome(world, target): def draw_scatter_plot(world, size, target): - """ This function can be used on a generic canvas (either an image to save - on disk or a canvas part of a GUI) + """This function can be used on a generic canvas (either an image to save + on disk or a canvas part of a GUI) """ - #Find min and max values of humidity and temperature on land so we can - #normalize temperature and humidity to the chart - humid = numpy.ma.masked_array(world.layers['humidity'].data, mask=world.layers['ocean'].data) - temp = numpy.ma.masked_array(world.layers['temperature'].data, mask=world.layers['ocean'].data) + # Find min and max values of humidity and temperature on land so we can + # normalize temperature and humidity to the chart + humid = numpy.ma.masked_array(world.layers["humidity"].data, mask=world.layers["ocean"].data) + temp = numpy.ma.masked_array(world.layers["temperature"].data, mask=world.layers["ocean"].data) min_humidity = humid.min() max_humidity = humid.max() min_temperature = temp.min() max_temperature = temp.max() temperature_delta = max_temperature - min_temperature humidity_delta = max_humidity - min_humidity - - #set all pixels white + + # set all pixels white for y in range(0, size): for x in range(0, size): target.set_pixel(x, y, (255, 255, 255, 255)) - #fill in 'bad' boxes with grey - h_values = ['62', '50', '37', '25', '12'] - t_values = [ 0, 1, 2, 3, 5 ] + # fill in 'bad' boxes with grey + h_values = ["62", "50", "37", "25", "12"] + t_values = [0, 1, 2, 3, 5] for loop in range(0, 5): - h_min = (size - 1) * ((world.layers['humidity'].quantiles[h_values[loop]] - min_humidity) / humidity_delta) + h_min = (size - 1) * ((world.layers["humidity"].quantiles[h_values[loop]] - min_humidity) / humidity_delta) if loop != 4: - h_max = (size - 1) * ((world.layers['humidity'].quantiles[h_values[loop + 1]] - min_humidity) / humidity_delta) + h_max = (size - 1) * ( + (world.layers["humidity"].quantiles[h_values[loop + 1]] - min_humidity) / humidity_delta + ) else: h_max = size - v_max = (size - 1) * ((world.layers['temperature'].thresholds[t_values[loop]][1] - min_temperature) / temperature_delta) + v_max = (size - 1) * ( + (world.layers["temperature"].thresholds[t_values[loop]][1] - min_temperature) / temperature_delta + ) if h_min < 0: h_min = 0 if h_max > size: @@ -673,37 +686,37 @@ def draw_scatter_plot(world, size, target): for y in range(int(h_min), int(h_max)): for x in range(0, int(v_max)): target.set_pixel(x, (size - 1) - y, (128, 128, 128, 255)) - - #draw lines based on thresholds + + # draw lines based on thresholds for t in range(0, 6): - v = (size - 1) * ((world.layers['temperature'].thresholds[t][1] - min_temperature) / temperature_delta) + v = (size - 1) * ((world.layers["temperature"].thresholds[t][1] - min_temperature) / temperature_delta) if 0 < v < size: for y in range(0, size): target.set_pixel(int(v), (size - 1) - y, (0, 0, 0, 255)) - ranges = ['87', '75', '62', '50', '37', '25', '12'] + ranges = ["87", "75", "62", "50", "37", "25", "12"] for p in ranges: - h = (size - 1) * ((world.layers['humidity'].quantiles[p] - min_humidity) / humidity_delta) + h = (size - 1) * ((world.layers["humidity"].quantiles[p] - min_humidity) / humidity_delta) if 0 < h < size: for x in range(0, size): target.set_pixel(x, (size - 1) - int(h), (0, 0, 0, 255)) - #draw gamma curve + # draw gamma curve curve_gamma = world.gamma_curve curve_bonus = world.curve_offset - + for x in range(0, size): y = (size - 1) * ((numpy.power((float(x) / (size - 1)), curve_gamma) * (1 - curve_bonus)) + curve_bonus) target.set_pixel(x, (size - 1) - int(y), (255, 0, 0, 255)) - #examine all cells in the map and if it is land get the temperature and - #humidity for the cell. + # examine all cells in the map and if it is land get the temperature and + # humidity for the cell. for y in range(world.height): for x in range(world.width): if world.is_land((x, y)): t = world.temperature_at((x, y)) p = world.humidity_at((x, y)) - #get red and blue values depending on temperature and humidity + # get red and blue values depending on temperature and humidity if world.is_temperature_polar((x, y)): r = 0 elif world.is_temperature_alpine((x, y)): @@ -735,12 +748,12 @@ def draw_scatter_plot(world, size, target): elif world.is_humidity_superhumid((x, y)): b = 255 - #calculate x and y position based on normalized temperature and humidity + # calculate x and y position based on normalized temperature and humidity nx = (size - 1) * ((t - min_temperature) / temperature_delta) ny = (size - 1) * ((p - min_humidity) / humidity_delta) - + target.set_pixel(int(nx), (size - 1) - int(ny), (r, 128, b, 255)) - + # ------------- # Draw on files @@ -760,7 +773,7 @@ def draw_riversmap_on_file(world, filename): def draw_grayscale_heightmap_on_file(world, filename): - img = PNGWriter.grayscale_from_array(world.layers['elevation'].data, filename, scale_to_range=True) + img = PNGWriter.grayscale_from_array(world.layers["elevation"].data, filename, scale_to_range=True) img.complete() @@ -801,14 +814,21 @@ def draw_biome_on_file(world, filename): img.complete() -def draw_ancientmap_on_file(world, filename, resize_factor=1, - sea_color=(212, 198, 169, 255), - draw_biome=True, draw_rivers=True, draw_mountains=True, - draw_outer_land_border=False, verbose=False): +def draw_ancientmap_on_file( + world, + filename, + resize_factor=1, + sea_color=(212, 198, 169, 255), + draw_biome=True, + draw_rivers=True, + draw_mountains=True, + draw_outer_land_border=False, + verbose=False, +): img = PNGWriter.rgba_from_dimensions(world.width * resize_factor, world.height * resize_factor, filename) - draw_ancientmap(world, img, resize_factor, sea_color, - draw_biome, draw_rivers, draw_mountains, draw_outer_land_border, - verbose) + draw_ancientmap( + world, img, resize_factor, sea_color, draw_biome, draw_rivers, draw_mountains, draw_outer_land_border, verbose + ) img.complete() @@ -825,5 +845,5 @@ def draw_satellite_on_file(world, filename): def draw_icecaps_on_file(world, filename): - img = PNGWriter.grayscale_from_array(world.layers['icecap'].data, filename, scale_to_range=True) + img = PNGWriter.grayscale_from_array(world.layers["icecap"].data, filename, scale_to_range=True) img.complete() diff --git a/worldengine/drawing_functions.py b/worldengine/drawing_functions.py index 0de9663b..043fbe97 100644 --- a/worldengine/drawing_functions.py +++ b/worldengine/drawing_functions.py @@ -6,11 +6,12 @@ import sys import time + import numpy -from worldengine.common import get_verbose, count_neighbours -from worldengine.common import anti_alias as anti_alias_channel -from worldengine.biome import BiomeGroup, _un_camelize +from worldengine.biome import BiomeGroup, _un_camelize +from worldengine.common import anti_alias as anti_alias_channel +from worldengine.common import count_neighbours, get_verbose # ------------------- # Reusable functions @@ -37,16 +38,15 @@ def rgba_to_rgb(rgba): def draw_rivers_on_image(world, target, factor=1): - """Draw only the rivers, it expect the background to be in place - """ + """Draw only the rivers, it expect the background to be in place""" for y in range(world.height): for x in range(world.width): - if world.is_land((x, y)) and (world.layers['river_map'].data[y, x] > 0.0): + if world.is_land((x, y)) and (world.layers["river_map"].data[y, x] > 0.0): for dx in range(factor): for dy in range(factor): target.set_pixel(x * factor + dx, y * factor + dy, (0, 0, 128, 255)) - if world.is_land((x, y)) and (world.layers['lake_map'].data[y, x] != 0): + if world.is_land((x, y)) and (world.layers["lake_map"].data[y, x] != 0): for dx in range(factor): for dy in range(factor): target.set_pixel(x * factor + dx, y * factor + dy, (0, 100, 128, 255)) @@ -56,6 +56,7 @@ def draw_rivers_on_image(world, target, factor=1): # Drawing ancient map # ------------------- + def _find_mountains_mask(world, factor): _mask = numpy.zeros((world.height, world.width), float) _mask[world.elevation > world.get_mountain_level()] = 1.0 @@ -76,7 +77,6 @@ def _find_mountains_mask(world, factor): def _build_biome_group_masks(world, factor): - biome_groups = BiomeGroup.__subclasses__() biome_masks = {} @@ -86,7 +86,7 @@ def _build_biome_group_masks(world, factor): for biome in group.__subclasses__(): group_mask[world.biome == _un_camelize(biome.__name__)] += 1.0 - + group_mask[group_mask > 0] = count_neighbours(group_mask)[group_mask > 0] group_mask[group_mask < 5.000000001] = 0.0 @@ -97,6 +97,7 @@ def _build_biome_group_masks(world, factor): return biome_masks + def _draw_shaded_pixel(pixels, x, y, r, g, b): nb = (x ** int(y / 5) + x * 23 + y * 37 + (x * y) * 13) % 75 nr = r - nb @@ -297,7 +298,7 @@ def _dynamic_draw_a_mountain(pixels, rng, x, y, w=3, h=3): max_leftborder = int(bottomness * w * 1.33) if last_leftborder is not None: max_leftborder = min(max_leftborder, last_leftborder + 1) - leftborder = int(bottomness * w) + rng.randint(-2, 2)/2 + leftborder = int(bottomness * w) + rng.randint(-2, 2) / 2 if leftborder < min_leftborder: leftborder = min_leftborder if leftborder > max_leftborder: @@ -307,11 +308,9 @@ def _dynamic_draw_a_mountain(pixels, rng, x, y, w=3, h=3): darkarea = int(bottomness * w / 2) lightarea = int(bottomness * w / 2) for itx in range(darkarea, leftborder + 1): - pixels[y + mody, x - itx] = gradient(itx, darkarea, leftborder, - (0, 0, 0), (64, 64, 64)) + pixels[y + mody, x - itx] = gradient(itx, darkarea, leftborder, (0, 0, 0), (64, 64, 64)) for itx in range(-darkarea, lightarea + 1): - pixels[y + mody, x - itx] = gradient(itx, -darkarea, lightarea, - (64, 64, 64), (128, 128, 128)) + pixels[y + mody, x - itx] = gradient(itx, -darkarea, lightarea, (64, 64, 64), (128, 128, 128)) for itx in range(lightarea, leftborder): pixels[y + mody, x - itx] = (181, 166, 127, 255) # land_color # right edge @@ -324,7 +323,7 @@ def _dynamic_draw_a_mountain(pixels, rng, x, y, w=3, h=3): max_modx = int(bottomness * w * 1.33) if last_modx is not None: max_modx = min(max_modx, last_modx + 1) - modx = int(bottomness * w) + numpy.random.randint(-2, 2)/2 + modx = int(bottomness * w) + numpy.random.randint(-2, 2) / 2 if modx < min_modx: modx = min_modx if modx > max_modx: @@ -344,11 +343,9 @@ def _draw_a_mountain(pixels, x, y, w=3, h=3): darkarea = int(bottomness * w / 2) lightarea = int(bottomness * w / 2) for itx in range(darkarea, leftborder + 1): - pixels[y + mody, x - itx] = gradient(itx, darkarea, leftborder, - (0, 0, 0), (64, 64, 64)) + pixels[y + mody, x - itx] = gradient(itx, darkarea, leftborder, (0, 0, 0), (64, 64, 64)) for itx in range(-darkarea, lightarea + 1): - pixels[y + mody, x + itx] = gradient(itx, -darkarea, lightarea, - (64, 64, 64), (128, 128, 128)) + pixels[y + mody, x + itx] = gradient(itx, -darkarea, lightarea, (64, 64, 64), (128, 128, 128)) for itx in range(lightarea, leftborder): pixels[y + mody, x + itx] = (181, 166, 127, 255) # land_color # right edge @@ -358,10 +355,17 @@ def _draw_a_mountain(pixels, x, y, w=3, h=3): pixels[y + mody, x + modx] = mcr -def draw_ancientmap(world, target, resize_factor=1, - sea_color=(212, 198, 169, 255), - draw_biome=True, draw_rivers=True, draw_mountains=True, - draw_outer_land_border=False, verbose=get_verbose()): +def draw_ancientmap( + world, + target, + resize_factor=1, + sea_color=(212, 198, 169, 255), + draw_biome=True, + draw_rivers=True, + draw_mountains=True, + draw_outer_land_border=False, + verbose=get_verbose(), +): rng = numpy.random.RandomState(world.seed) # create our own random generator if verbose: @@ -386,8 +390,7 @@ def draw_ancientmap(world, target, resize_factor=1, outer_borders = None for i in range(2): - _outer_borders = numpy.zeros( - (resize_factor * world.height, resize_factor * world.width), bool) + _outer_borders = numpy.zeros((resize_factor * world.height, resize_factor * world.width), bool) _outer_borders[count_neighbours(inner_borders) > 0] = True _outer_borders[inner_borders] = False _outer_borders[numpy.logical_not(scaled_ocean)] = False @@ -408,23 +411,21 @@ def _draw_biome(name, _func, w, h, r, _alt_func=None): for x in range(resize_factor * world.width): if biome_masks[name][y, x] > 0: if r == 0 or border_neighbours[r][y, x] <= 2: - if _alt_func is not None and rng.random_sample() > .5: + if _alt_func is not None and rng.random_sample() > 0.5: _alt_func(target, x, y, w, h) else: _func(target, x, y, w, h) - biome_masks[name][y-r:y+r+1, x-r:x+r+1] = 0.0 + biome_masks[name][y - r : y + r + 1, x - r : x + r + 1] = 0.0 if verbose: elapsed_time = time.time() - start_time print( - "...drawing_functions.draw_ancientmap: " + name + - " Elapsed time " + str(elapsed_time) + " seconds.") + "...drawing_functions.draw_ancientmap: " + name + " Elapsed time " + str(elapsed_time) + " seconds." + ) if verbose: elapsed_time = time.time() - start_time - print( - "...drawing_functions.draw_oldmap_on_pixel: init Elapsed time " + - str(elapsed_time) + " seconds.") + print("...drawing_functions.draw_oldmap_on_pixel: init Elapsed time " + str(elapsed_time) + " seconds.") sys.stdout.flush() if verbose: @@ -454,14 +455,17 @@ def _draw_biome(name, _func, w, h, r, _alt_func=None): if verbose: elapsed_time = time.time() - start_time print( - "...drawing_functions.draw_oldmap_on_pixel: color ocean " + - "Elapsed time " + str(elapsed_time) + " seconds.") + "...drawing_functions.draw_oldmap_on_pixel: color ocean " + + "Elapsed time " + + str(elapsed_time) + + " seconds." + ) if verbose: start_time = time.time() # don't anti-alias the alpha channel - for c in range(num_channels-1): + for c in range(num_channels - 1): channels[c] = anti_alias_channel(channels[c], 1) # switch from channel major storage to pixel major storage @@ -471,8 +475,8 @@ def _draw_biome(name, _func, w, h, r, _alt_func=None): if verbose: elapsed_time = time.time() - start_time print( - "...drawing_functions.draw_oldmap_on_pixel: anti alias " + - "Elapsed time " + str(elapsed_time) + " seconds.") + "...drawing_functions.draw_oldmap_on_pixel: anti alias " + "Elapsed time " + str(elapsed_time) + " seconds." + ) if draw_biome: # Draw glacier @@ -480,28 +484,29 @@ def _draw_biome(name, _func, w, h, r, _alt_func=None): start_time = time.time() for y in range(resize_factor * world.height): for x in range(resize_factor * world.width): - if not borders[y, x] and world.is_iceland( - (int(x / resize_factor), int(y / resize_factor))): + if not borders[y, x] and world.is_iceland((int(x / resize_factor), int(y / resize_factor))): _draw_glacier(target, x, y) if verbose: elapsed_time = time.time() - start_time print( - "...drawing_functions.draw_oldmap_on_pixel: draw glacier " + - "Elapsed time " + str(elapsed_time) + " seconds.") - - _draw_biome('tundra', _draw_tundra, 0, 0, 0) - _draw_biome('cold parklands', _draw_cold_parklands, 0, 0, 0) - _draw_biome('steppe', _draw_steppe, 0, 0, 0) - _draw_biome('chaparral', _draw_chaparral, 0, 0, 0) - _draw_biome('savanna', _draw_savanna, 0, 0, 0) - _draw_biome('cool desert', _draw_cool_desert, 8, 2, 9) - _draw_biome('hot desert', _draw_hot_desert, 8, 2, 9) - _draw_biome('boreal forest', _draw_boreal_forest, 4, 5, 6) - _draw_biome('cool temperate forest', _draw_temperate_forest1, 4, 5, 6, - _draw_temperate_forest2) - _draw_biome('warm temperate forest', _draw_warm_temperate_forest, 4, 5, 6) - _draw_biome('tropical dry forest group', _draw_tropical_dry_forest, 4, 5, 6) - _draw_biome('jungle', _draw_jungle, 4, 5, 6) + "...drawing_functions.draw_oldmap_on_pixel: draw glacier " + + "Elapsed time " + + str(elapsed_time) + + " seconds." + ) + + _draw_biome("tundra", _draw_tundra, 0, 0, 0) + _draw_biome("cold parklands", _draw_cold_parklands, 0, 0, 0) + _draw_biome("steppe", _draw_steppe, 0, 0, 0) + _draw_biome("chaparral", _draw_chaparral, 0, 0, 0) + _draw_biome("savanna", _draw_savanna, 0, 0, 0) + _draw_biome("cool desert", _draw_cool_desert, 8, 2, 9) + _draw_biome("hot desert", _draw_hot_desert, 8, 2, 9) + _draw_biome("boreal forest", _draw_boreal_forest, 4, 5, 6) + _draw_biome("cool temperate forest", _draw_temperate_forest1, 4, 5, 6, _draw_temperate_forest2) + _draw_biome("warm temperate forest", _draw_warm_temperate_forest, 4, 5, 6) + _draw_biome("tropical dry forest group", _draw_tropical_dry_forest, 4, 5, 6) + _draw_biome("jungle", _draw_jungle, 4, 5, 6) # TODO: there was a stub for a rock desert biome group # it should be super easy to introduce that group with the new @@ -518,8 +523,7 @@ def _draw_biome(name, _func, w, h, r, _alt_func=None): for x in range(resize_factor * world.width): if mountains_mask[y, x] > 0: w = mountains_mask[y, x] - h = 3 + int(world.level_of_mountain( - (int(x / resize_factor), int(y / resize_factor)))) + h = 3 + int(world.level_of_mountain((int(x / resize_factor), int(y / resize_factor)))) r = max(int(w / 3 * 2), h) if r not in border_neighbours: @@ -527,9 +531,12 @@ def _draw_biome(name, _func, w, h, r, _alt_func=None): if border_neighbours[r][y, x] <= 2: _draw_a_mountain(target, x, y, w=w, h=h) - mountains_mask[y-r:y+r+1, x-r:x+r+1] = 0.0 + mountains_mask[y - r : y + r + 1, x - r : x + r + 1] = 0.0 if verbose: elapsed_time = time.time() - start_time print( - "...drawing_functions.draw_oldmap_on_pixel: draw mountains " + - "Elapsed time " + str(elapsed_time) + " seconds.") + "...drawing_functions.draw_oldmap_on_pixel: draw mountains " + + "Elapsed time " + + str(elapsed_time) + + " seconds." + ) diff --git a/worldengine/generation.py b/worldengine/generation.py index d37d4de7..8cc45756 100644 --- a/worldengine/generation.py +++ b/worldengine/generation.py @@ -1,50 +1,45 @@ import numpy - from noise import snoise2 +from worldengine.common import anti_alias, get_verbose from worldengine.model.world import Step from worldengine.simulations.basic import find_threshold_f +from worldengine.simulations.biome import BiomeSimulation +from worldengine.simulations.erosion import ErosionSimulation +from worldengine.simulations.humidity import HumiditySimulation from worldengine.simulations.hydrology import WatermapSimulation +from worldengine.simulations.icecap import IcecapSimulation from worldengine.simulations.irrigation import IrrigationSimulation -from worldengine.simulations.humidity import HumiditySimulation -from worldengine.simulations.temperature import TemperatureSimulation from worldengine.simulations.permeability import PermeabilitySimulation -from worldengine.simulations.erosion import ErosionSimulation from worldengine.simulations.precipitation import PrecipitationSimulation -from worldengine.simulations.biome import BiomeSimulation -from worldengine.simulations.icecap import IcecapSimulation -from worldengine.common import anti_alias, get_verbose - +from worldengine.simulations.temperature import TemperatureSimulation # ------------------ # Initial generation # ------------------ + def center_land(world): """Translate the map horizontally and vertically to put as much ocean as - possible at the borders. It operates on elevation and plates map""" + possible at the borders. It operates on elevation and plates map""" - y_sums = world.layers['elevation'].data.sum(1) # 1 == sum along x-axis + y_sums = world.layers["elevation"].data.sum(1) # 1 == sum along x-axis y_with_min_sum = y_sums.argmin() if get_verbose(): print("geo.center_land: height complete") - x_sums = world.layers['elevation'].data.sum(0) # 0 == sum along y-axis + x_sums = world.layers["elevation"].data.sum(0) # 0 == sum along y-axis x_with_min_sum = x_sums.argmin() if get_verbose(): print("geo.center_land: width complete") latshift = 0 - world.layers['elevation'].data = numpy.roll( - numpy.roll( - world.layers['elevation'].data, - -y_with_min_sum + latshift, axis=0), - - x_with_min_sum, axis=1) - world.layers['plates'].data = numpy.roll( - numpy.roll( - world.layers['plates'].data, - -y_with_min_sum + latshift, axis=0), - - x_with_min_sum, axis=1) + world.layers["elevation"].data = numpy.roll( + numpy.roll(world.layers["elevation"].data, -y_with_min_sum + latshift, axis=0), -x_with_min_sum, axis=1 + ) + world.layers["plates"].data = numpy.roll( + numpy.roll(world.layers["plates"].data, -y_with_min_sum + latshift, axis=0), -x_with_min_sum, axis=1 + ) if get_verbose(): print("geo.center_land: width complete") @@ -57,8 +52,7 @@ def place_oceans_at_map_borders(world): ocean_border = int(min(30, max(world.width / 5, world.height / 5))) def place_ocean(x, y, i): - world.layers['elevation'].data[y, x] = \ - (world.layers['elevation'].data[y, x] * i) / ocean_border + world.layers["elevation"].data[y, x] = (world.layers["elevation"].data[y, x] * i) / ocean_border for x in range(world.width): for i in range(ocean_border): @@ -77,20 +71,20 @@ def add_noise_to_elevation(world, seed): for y in range(world.height): for x in range(world.width): n = snoise2(x / freq * 2, y / freq * 2, octaves, base=seed) - world.layers['elevation'].data[y, x] += n + world.layers["elevation"].data[y, x] += n -def fill_ocean(elevation, sea_level):#TODO: Make more use of numpy? +def fill_ocean(elevation, sea_level): # TODO: Make more use of numpy? height, width = elevation.shape ocean = numpy.zeros(elevation.shape, dtype=bool) to_expand = [] - for x in range(width):#handle top and bottom border of the map + for x in range(width): # handle top and bottom border of the map if elevation[0, x] <= sea_level: to_expand.append((x, 0)) if elevation[height - 1, x] <= sea_level: to_expand.append((x, height - 1)) - for y in range(height):#handle left- and rightmost border of the map + for y in range(height): # handle left- and rightmost border of the map if elevation[y, 0] <= sea_level: to_expand.append((0, y)) if elevation[y, width - 1] <= sea_level: @@ -113,14 +107,11 @@ def initialize_ocean_and_thresholds(world, ocean_level=1.0): :param ocean_level: the elevation representing the ocean level :return: nothing, the world will be changed """ - e = world.layers['elevation'].data + e = world.layers["elevation"].data ocean = fill_ocean(e, ocean_level) hl = find_threshold_f(e, 0.10) # the highest 10% of all (!) land are declared hills ml = find_threshold_f(e, 0.03) # the highest 3% are declared mountains - e_th = [('sea', ocean_level), - ('plain', hl), - ('hill', ml), - ('mountain', None)] + e_th = [("sea", ocean_level), ("plain", hl), ("hill", ml), ("mountain", None)] harmonize_ocean(ocean, e, ocean_level) world.ocean = ocean world.elevation = (e, e_th) @@ -144,12 +135,13 @@ def harmonize_ocean(ocean, elevation, ocean_level): deep_ocean = numpy.logical_and(elevation > midpoint, ocean_points) elevation[deep_ocean] = midpoint + ((elevation[deep_ocean] - midpoint) / 5.0) + # ---- # Misc # ---- -def sea_depth(world, sea_level): +def sea_depth(world, sea_level): # a dynamic programming approach to gather how far the next land is # from a given coordinate up to a maximum distance of max_radius # result is 0 for land coordinates and -1 for coordinates further than @@ -182,15 +174,15 @@ def next_land_dynamic(ocean, max_radius=5): # possible TODO: make this a parameter factors = [0.0, 0.3, 0.5, 0.7, 0.9] - next_land = next_land_dynamic(world.layers['ocean'].data) + next_land = next_land_dynamic(world.layers["ocean"].data) - result = sea_level - world.layers['elevation'].data + result = sea_level - world.layers["elevation"].data for y in range(world.height): for x in range(world.width): dist_to_next_land = next_land[y, x] if dist_to_next_land > 0: - result[y, x] *= factors[dist_to_next_land-1] + result[y, x] *= factors[dist_to_next_land - 1] result = anti_alias(result, 10) @@ -226,53 +218,53 @@ def generate_world(w, step): rng = numpy.random.RandomState(w.seed) # choose lowest common denominator (32 bit Windows numpy cannot handle a larger value) sub_seeds = rng.randint(0, numpy.iinfo(numpy.int32).max, size=100) - # after 0.19.0 do not ever switch out the seeds here to maximize seed-compatibility + # after 0.19.0 do not ever switch out the seeds here to maximize seed-compatibility seed_dict = { - 'PrecipitationSimulation': sub_seeds[ 0], - 'ErosionSimulation': sub_seeds[ 1], - 'WatermapSimulation': sub_seeds[ 2], - 'IrrigationSimulation': sub_seeds[ 3], - 'TemperatureSimulation': sub_seeds[ 4], - 'HumiditySimulation': sub_seeds[ 5], - 'PermeabilitySimulation': sub_seeds[ 6], - 'BiomeSimulation': sub_seeds[ 7], - 'IcecapSimulation': sub_seeds[ 8], - '': sub_seeds[99] + "PrecipitationSimulation": sub_seeds[0], + "ErosionSimulation": sub_seeds[1], + "WatermapSimulation": sub_seeds[2], + "IrrigationSimulation": sub_seeds[3], + "TemperatureSimulation": sub_seeds[4], + "HumiditySimulation": sub_seeds[5], + "PermeabilitySimulation": sub_seeds[6], + "BiomeSimulation": sub_seeds[7], + "IcecapSimulation": sub_seeds[8], + "": sub_seeds[99], } - TemperatureSimulation().execute(w, seed_dict['TemperatureSimulation']) + TemperatureSimulation().execute(w, seed_dict["TemperatureSimulation"]) # Precipitation with thresholds - PrecipitationSimulation().execute(w, seed_dict['PrecipitationSimulation']) + PrecipitationSimulation().execute(w, seed_dict["PrecipitationSimulation"]) if not step.include_erosion: return w - ErosionSimulation().execute(w, seed_dict['ErosionSimulation']) # seed not currently used + ErosionSimulation().execute(w, seed_dict["ErosionSimulation"]) # seed not currently used if get_verbose(): print("...erosion calculated") - WatermapSimulation().execute(w, seed_dict['WatermapSimulation']) # seed not currently used + WatermapSimulation().execute(w, seed_dict["WatermapSimulation"]) # seed not currently used # FIXME: create setters - IrrigationSimulation().execute(w, seed_dict['IrrigationSimulation']) # seed not currently used - HumiditySimulation().execute(w, seed_dict['HumiditySimulation']) # seed not currently used + IrrigationSimulation().execute(w, seed_dict["IrrigationSimulation"]) # seed not currently used + HumiditySimulation().execute(w, seed_dict["HumiditySimulation"]) # seed not currently used - PermeabilitySimulation().execute(w, seed_dict['PermeabilitySimulation']) + PermeabilitySimulation().execute(w, seed_dict["PermeabilitySimulation"]) - cm, biome_cm = BiomeSimulation().execute(w, seed_dict['BiomeSimulation']) # seed not currently used + cm, biome_cm = BiomeSimulation().execute(w, seed_dict["BiomeSimulation"]) # seed not currently used for cl in cm: count = cm[cl] if get_verbose(): print("%s = %i" % (str(cl), count)) if get_verbose(): - print('') # empty line - print('Biome obtained:') + print("") # empty line + print("Biome obtained:") for cl in biome_cm: count = biome_cm[cl] if get_verbose(): print(" %30s = %7i" % (str(cl), count)) - IcecapSimulation().execute(w, seed_dict['IcecapSimulation']) # makes use of temperature-map + IcecapSimulation().execute(w, seed_dict["IcecapSimulation"]) # makes use of temperature-map return w diff --git a/worldengine/hdf5_serialization.py b/worldengine/hdf5_serialization.py index 0c241392..3f0c104d 100644 --- a/worldengine/hdf5_serialization.py +++ b/worldengine/hdf5_serialization.py @@ -1,14 +1,13 @@ -import numpy - import h5py +import numpy +from worldengine.biome import biome_index_to_name, biome_name_to_index +from worldengine.model.world import GenerationParameters, Size, Step, World from worldengine.version import __version__ -from worldengine.biome import biome_name_to_index, biome_index_to_name -from worldengine.model.world import World, Step, Size, GenerationParameters def save_world_to_hdf5(world, filename): - f = h5py.File(filename, libver='latest', mode='w') + f = h5py.File(filename, libver="latest", mode="w") general_grp = f.create_group("general") general_grp["worldengine_version"] = __version__ @@ -18,94 +17,94 @@ def save_world_to_hdf5(world, filename): elevation_grp = f.create_group("elevation") elevation_ths_grp = elevation_grp.create_group("thresholds") - elevation_ths_grp["sea"] = world.layers['elevation'].thresholds[0][1] - elevation_ths_grp["plain"] = world.layers['elevation'].thresholds[1][1] - elevation_ths_grp["hill"] = world.layers['elevation'].thresholds[2][1] - elevation_data = elevation_grp.create_dataset("data", (world.height, world.width), dtype=numpy.float) - elevation_data.write_direct(world.layers['elevation'].data) + elevation_ths_grp["sea"] = world.layers["elevation"].thresholds[0][1] + elevation_ths_grp["plain"] = world.layers["elevation"].thresholds[1][1] + elevation_ths_grp["hill"] = world.layers["elevation"].thresholds[2][1] + elevation_data = elevation_grp.create_dataset("data", (world.height, world.width), dtype=numpy.float64) + elevation_data.write_direct(world.layers["elevation"].data) plates_data = f.create_dataset("plates", (world.height, world.width), dtype=numpy.uint16) - plates_data.write_direct(world.layers['plates'].data) + plates_data.write_direct(world.layers["plates"].data) - ocean_data = f.create_dataset("ocean", (world.height, world.width), dtype=numpy.bool) - ocean_data.write_direct(world.layers['ocean'].data) + ocean_data = f.create_dataset("ocean", (world.height, world.width), dtype=numpy.bool_) + ocean_data.write_direct(world.layers["ocean"].data) - sea_depth_data = f.create_dataset("sea_depth", (world.height, world.width), dtype=numpy.float) - sea_depth_data.write_direct(world.layers['sea_depth'].data) + sea_depth_data = f.create_dataset("sea_depth", (world.height, world.width), dtype=numpy.float64) + sea_depth_data.write_direct(world.layers["sea_depth"].data) if world.has_biome(): biome_data = f.create_dataset("biome", (world.height, world.width), dtype=numpy.uint16) for y in range(world.height): for x in range(world.width): - biome_data[y, x] = biome_name_to_index(world.layers['biome'].data[y][x]) + biome_data[y, x] = biome_name_to_index(world.layers["biome"].data[y][x]) if world.has_humidity(): humidity_grp = f.create_group("humidity") humidity_quantiles_grp = humidity_grp.create_group("quantiles") - for k in world.layers['humidity'].quantiles.keys(): - humidity_quantiles_grp[k] = world.layers['humidity'].quantiles[k] - humidity_data = humidity_grp.create_dataset("data", (world.height, world.width), dtype=numpy.float) - humidity_data.write_direct(world.layers['humidity'].data) + for k in world.layers["humidity"].quantiles.keys(): + humidity_quantiles_grp[k] = world.layers["humidity"].quantiles[k] + humidity_data = humidity_grp.create_dataset("data", (world.height, world.width), dtype=numpy.float64) + humidity_data.write_direct(world.layers["humidity"].data) if world.has_irrigation(): - irrigation_data = f.create_dataset("irrigation", (world.height, world.width), dtype=numpy.float) - irrigation_data.write_direct(world.layers['irrigation'].data) + irrigation_data = f.create_dataset("irrigation", (world.height, world.width), dtype=numpy.float64) + irrigation_data.write_direct(world.layers["irrigation"].data) if world.has_permeability(): permeability_grp = f.create_group("permeability") permeability_ths_grp = permeability_grp.create_group("thresholds") - permeability_ths_grp['low'] = world.layers['permeability'].thresholds[0][1] - permeability_ths_grp['med'] = world.layers['permeability'].thresholds[1][1] - permeability_data = permeability_grp.create_dataset("data", (world.height, world.width), dtype=numpy.float) - permeability_data.write_direct(world.layers['permeability'].data) + permeability_ths_grp["low"] = world.layers["permeability"].thresholds[0][1] + permeability_ths_grp["med"] = world.layers["permeability"].thresholds[1][1] + permeability_data = permeability_grp.create_dataset("data", (world.height, world.width), dtype=numpy.float64) + permeability_data.write_direct(world.layers["permeability"].data) if world.has_watermap(): watermap_grp = f.create_group("watermap") watermap_ths_grp = watermap_grp.create_group("thresholds") - watermap_ths_grp['creek'] = world.layers['watermap'].thresholds['creek'] - watermap_ths_grp['river'] = world.layers['watermap'].thresholds['river'] - watermap_ths_grp['mainriver'] = world.layers['watermap'].thresholds['main river'] - watermap_data = watermap_grp.create_dataset("data", (world.height, world.width), dtype=numpy.float) - watermap_data.write_direct(world.layers['watermap'].data) + watermap_ths_grp["creek"] = world.layers["watermap"].thresholds["creek"] + watermap_ths_grp["river"] = world.layers["watermap"].thresholds["river"] + watermap_ths_grp["mainriver"] = world.layers["watermap"].thresholds["main river"] + watermap_data = watermap_grp.create_dataset("data", (world.height, world.width), dtype=numpy.float64) + watermap_data.write_direct(world.layers["watermap"].data) if world.has_precipitations(): precipitation_grp = f.create_group("precipitation") precipitation_ths_grp = precipitation_grp.create_group("thresholds") - precipitation_ths_grp['low'] = world.layers['precipitation'].thresholds[0][1] - precipitation_ths_grp['med'] = world.layers['precipitation'].thresholds[1][1] - precipitation_data = precipitation_grp.create_dataset("data", (world.height, world.width), dtype=numpy.float) - precipitation_data.write_direct(world.layers['precipitation'].data) + precipitation_ths_grp["low"] = world.layers["precipitation"].thresholds[0][1] + precipitation_ths_grp["med"] = world.layers["precipitation"].thresholds[1][1] + precipitation_data = precipitation_grp.create_dataset("data", (world.height, world.width), dtype=numpy.float64) + precipitation_data.write_direct(world.layers["precipitation"].data) if world.has_temperature(): temperature_grp = f.create_group("temperature") temperature_ths_grp = temperature_grp.create_group("thresholds") - th = world.layers['temperature'].thresholds - temperature_ths_grp['polar'] = th[0][1] - temperature_ths_grp['alpine'] = th[1][1] - temperature_ths_grp['boreal'] = th[2][1] - temperature_ths_grp['cool'] = th[3][1] - temperature_ths_grp['warm'] = th[4][1] - temperature_ths_grp['subtropical'] = th[5][1] - temperature_data = temperature_grp.create_dataset("data", (world.height, world.width), dtype=numpy.float) - temperature_data.write_direct(world.layers['temperature'].data) + th = world.layers["temperature"].thresholds + temperature_ths_grp["polar"] = th[0][1] + temperature_ths_grp["alpine"] = th[1][1] + temperature_ths_grp["boreal"] = th[2][1] + temperature_ths_grp["cool"] = th[3][1] + temperature_ths_grp["warm"] = th[4][1] + temperature_ths_grp["subtropical"] = th[5][1] + temperature_data = temperature_grp.create_dataset("data", (world.height, world.width), dtype=numpy.float64) + temperature_data.write_direct(world.layers["temperature"].data) if world.has_icecap(): - icecap_data = f.create_dataset("icecap", (world.height, world.width), dtype=numpy.float) - icecap_data.write_direct(world.layers['icecap'].data) + icecap_data = f.create_dataset("icecap", (world.height, world.width), dtype=numpy.float64) + icecap_data.write_direct(world.layers["icecap"].data) if world.has_lakemap(): - lake_map_data = f.create_dataset("lake_map", (world.height, world.width), dtype=numpy.float) - lake_map_data.write_direct(world.layers['lake_map'].data) + lake_map_data = f.create_dataset("lake_map", (world.height, world.width), dtype=numpy.float64) + lake_map_data.write_direct(world.layers["lake_map"].data) if world.has_rivermap(): - river_map_data = f.create_dataset("river_map", (world.height, world.width), dtype=numpy.float) - river_map_data.write_direct(world.layers['river_map'].data) + river_map_data = f.create_dataset("river_map", (world.height, world.width), dtype=numpy.float64) + river_map_data.write_direct(world.layers["river_map"].data) generation_params_grp = f.create_group("generation_params") - generation_params_grp['seed'] = world.seed - generation_params_grp['n_plates'] = world.n_plates - generation_params_grp['ocean_level'] = world.ocean_level - generation_params_grp['step'] = world.step.name + generation_params_grp["seed"] = world.seed + generation_params_grp["n_plates"] = world.n_plates + generation_params_grp["ocean_level"] = world.ocean_level + generation_params_grp["step"] = world.step.name f.close() @@ -113,105 +112,115 @@ def save_world_to_hdf5(world, filename): def _from_hdf5_quantiles(p_quantiles): quantiles = {} for p_quantile in p_quantiles: - quantiles[p_quantile.title()] = p_quantiles[p_quantile].value + quantiles[p_quantile.title()] = p_quantiles[p_quantile][()] return quantiles def _from_hdf5_matrix_with_quantiles(p_matrix): - return numpy.array(p_matrix['data']), _from_hdf5_quantiles(p_matrix['quantiles']) + return numpy.array(p_matrix["data"]), _from_hdf5_quantiles(p_matrix["quantiles"]) def load_world_to_hdf5(filename): - f = h5py.File(filename, libver='latest', mode='r') - - w = World(f['general/name'].value, - Size(f['general/width'].value, f['general/height'].value), - f['generation_params/seed'].value, - GenerationParameters(f['generation_params/n_plates'].value, - f['generation_params/ocean_level'].value, - Step.get_by_name(f['generation_params/step'].value))) + f = h5py.File(filename, libver="latest", mode="r") + + w = World( + f["general/name"][()].decode() if isinstance(f["general/name"][()], bytes) else f["general/name"][()], + Size(f["general/width"][()], f["general/height"][()]), + f["generation_params/seed"][()], + GenerationParameters( + f["generation_params/n_plates"][()], + f["generation_params/ocean_level"][()], + Step.get_by_name( + f["generation_params/step"][()].decode() + if isinstance(f["generation_params/step"][()], bytes) + else f["generation_params/step"][()] + ), + ), + ) # Elevation - e = numpy.array(f['elevation/data']) - e_th = [('sea', f['elevation/thresholds/sea'].value), - ('plain', f['elevation/thresholds/plain'].value), - ('hill', f['elevation/thresholds/hill'].value), - ('mountain', None)] + e = numpy.array(f["elevation/data"]) + e_th = [ + ("sea", f["elevation/thresholds/sea"][()]), + ("plain", f["elevation/thresholds/plain"][()]), + ("hill", f["elevation/thresholds/hill"][()]), + ("mountain", None), + ] w.elevation = (e, e_th) # Plates - w.plates = numpy.array(f['plates']) + w.plates = numpy.array(f["plates"]) # Ocean - w.ocean = numpy.array(f['ocean']) - w.sea_depth = numpy.array(f['sea_depth']) + w.ocean = numpy.array(f["ocean"]) + w.sea_depth = numpy.array(f["sea_depth"]) # Biome - if 'biome' in f.keys(): + if "biome" in f.keys(): biome_data = [] for y in range(w.height): row = [] for x in range(w.width): - value = f['biome'][y, x] + value = f["biome"][y, x] row.append(biome_index_to_name(value)) biome_data.append(row) biome = numpy.array(biome_data, dtype=object) w.biome = biome - if 'humidity' in f.keys(): - data, quantiles = _from_hdf5_matrix_with_quantiles(f['humidity']) + if "humidity" in f.keys(): + data, quantiles = _from_hdf5_matrix_with_quantiles(f["humidity"]) w.humidity = (data, quantiles) - if 'irrigation' in f.keys(): - w.irrigation = numpy.array(f['irrigation']) + if "irrigation" in f.keys(): + w.irrigation = numpy.array(f["irrigation"]) - if 'permeability' in f.keys(): - p = numpy.array(f['permeability/data']) + if "permeability" in f.keys(): + p = numpy.array(f["permeability/data"]) p_th = [ - ('low', f['permeability/thresholds/low'].value), - ('med', f['permeability/thresholds/med'].value), - ('hig', None) + ("low", f["permeability/thresholds/low"][()]), + ("med", f["permeability/thresholds/med"][()]), + ("hig", None), ] w.permeability = (p, p_th) - if 'watermap' in f.keys(): - data = numpy.array(f['watermap/data']) + if "watermap" in f.keys(): + data = numpy.array(f["watermap/data"]) thresholds = {} - thresholds['creek'] = f['watermap/thresholds/creek'].value - thresholds['river'] = f['watermap/thresholds/river'].value - thresholds['main river'] = f['watermap/thresholds/mainriver'].value + thresholds["creek"] = f["watermap/thresholds/creek"][()] + thresholds["river"] = f["watermap/thresholds/river"][()] + thresholds["main river"] = f["watermap/thresholds/mainriver"][()] w.watermap = (data, thresholds) - if 'precipitation' in f.keys(): - p = numpy.array(f['precipitation/data']) + if "precipitation" in f.keys(): + p = numpy.array(f["precipitation/data"]) p_th = [ - ('low', f['precipitation/thresholds/low'].value), - ('med', f['precipitation/thresholds/med'].value), - ('hig', None) + ("low", f["precipitation/thresholds/low"][()]), + ("med", f["precipitation/thresholds/med"][()]), + ("hig", None), ] w.precipitation = (p, p_th) - if 'temperature' in f.keys(): - t = numpy.array(f['temperature/data']) + if "temperature" in f.keys(): + t = numpy.array(f["temperature/data"]) t_th = [ - ('polar', f['temperature/thresholds/polar'].value), - ('alpine', f['temperature/thresholds/alpine'].value), - ('boreal', f['temperature/thresholds/boreal'].value), - ('cool', f['temperature/thresholds/cool'].value), - ('warm', f['temperature/thresholds/warm'].value), - ('subtropical', f['temperature/thresholds/subtropical'].value), - ('tropical', None) + ("polar", f["temperature/thresholds/polar"][()]), + ("alpine", f["temperature/thresholds/alpine"][()]), + ("boreal", f["temperature/thresholds/boreal"][()]), + ("cool", f["temperature/thresholds/cool"][()]), + ("warm", f["temperature/thresholds/warm"][()]), + ("subtropical", f["temperature/thresholds/subtropical"][()]), + ("tropical", None), ] w.temperature = (t, t_th) - if 'icecap' in f.keys(): - w.icecap = numpy.array(f['icecap']) + if "icecap" in f.keys(): + w.icecap = numpy.array(f["icecap"]) - if 'lake_map' in f.keys(): - w.lakemap = numpy.array(f['lake_map']) + if "lake_map" in f.keys(): + w.lakemap = numpy.array(f["lake_map"]) - if 'river_map' in f.keys(): - w.rivermap = numpy.array(f['river_map']) + if "river_map" in f.keys(): + w.rivermap = numpy.array(f["river_map"]) f.close() diff --git a/worldengine/image_io.py b/worldengine/image_io.py index e3472ad7..f447a146 100644 --- a/worldengine/image_io.py +++ b/worldengine/image_io.py @@ -23,12 +23,13 @@ import numpy import png -#Documentation PyPNG: https://pythonhosted.org/pypng/png.html -#Documentation PurePNG: http://purepng.readthedocs.org/en/latest/ -#The latter one is a fork of the former one. It is yet to be seen which one is better. +# Documentation PyPNG: https://pythonhosted.org/pypng/png.html +# Documentation PurePNG: http://purepng.readthedocs.org/en/latest/ +# The latter one is a fork of the former one. It is yet to be seen which one is better. -class PNGWriter(object): + +class PNGWriter: """ From https://pythonhosted.org/pypng/png.html#module-png : -reads/writes PNG files with all allowable bit depths: 1/2/4/8/16/24/32/48/64 @@ -38,39 +39,58 @@ class PNGWriter(object): LA (greyscale with alpha) with 8/16 bits per channel colour mapped images (1/2/4/8 bit) """ + # convenience constructors @staticmethod def grayscale_from_dimensions(width, height, filename=None, channel_bitdepth=16): - return PNGWriter.from_dimensions(width, height, channels=1, filename=filename, - channel_bitdepth=channel_bitdepth, grayscale=True) + return PNGWriter.from_dimensions( + width, height, channels=1, filename=filename, channel_bitdepth=channel_bitdepth, grayscale=True + ) @staticmethod def rgb_from_dimensions(width, height, filename=None, channel_bitdepth=8): - return PNGWriter.from_dimensions(width, height, channels=3, filename=filename, - channel_bitdepth=channel_bitdepth) + return PNGWriter.from_dimensions( + width, height, channels=3, filename=filename, channel_bitdepth=channel_bitdepth + ) @staticmethod def rgba_from_dimensions(width, height, filename=None, channel_bitdepth=8): - return PNGWriter.from_dimensions(width, height, channels=4, filename=filename, - channel_bitdepth=channel_bitdepth, has_alpha=True) + return PNGWriter.from_dimensions( + width, height, channels=4, filename=filename, channel_bitdepth=channel_bitdepth, has_alpha=True + ) @staticmethod def grayscale_from_array(array, filename=None, channel_bitdepth=16, scale_to_range=False): - return PNGWriter.from_array(array, filename=filename, channels=1, scale_to_range=scale_to_range, - grayscale=True, channel_bitdepth=channel_bitdepth) + return PNGWriter.from_array( + array, + filename=filename, + channels=1, + scale_to_range=scale_to_range, + grayscale=True, + channel_bitdepth=channel_bitdepth, + ) @staticmethod def rgb_from_array(array, filename=None, channel_bitdepth=8, scale_to_range=False): - return PNGWriter.from_array(array, filename=filename, channels=3, scale_to_range=scale_to_range, - channel_bitdepth=channel_bitdepth) + return PNGWriter.from_array( + array, filename=filename, channels=3, scale_to_range=scale_to_range, channel_bitdepth=channel_bitdepth + ) @staticmethod def rgba_from_array(array, filename=None, channel_bitdepth=8, scale_to_range=False): - return PNGWriter.from_array(array, filename=filename, channels=4, scale_to_range=scale_to_range, - channel_bitdepth=channel_bitdepth, has_alpha=True) + return PNGWriter.from_array( + array, + filename=filename, + channels=4, + scale_to_range=scale_to_range, + channel_bitdepth=channel_bitdepth, + has_alpha=True, + ) # general constructors - def __init__(self, array, filename=None, channels=3, channel_bitdepth=8, has_alpha=False, palette=None, grayscale=False): + def __init__( + self, array, filename=None, channels=3, channel_bitdepth=8, has_alpha=False, palette=None, grayscale=False + ): """ Calling the generic constructor gives full control over the created PNG file but it is very much recommended to use the appropriate static @@ -92,29 +112,43 @@ def __init__(self, array, filename=None, channels=3, channel_bitdepth=8, has_alp self.palette = palette @classmethod - def from_dimensions(cls, width, height, channels, filename=None, - grayscale=False, channel_bitdepth=8, - has_alpha=False, palette=None): + def from_dimensions( + cls, width, height, channels, filename=None, grayscale=False, channel_bitdepth=8, has_alpha=False, palette=None + ): """ Creates an empty image according to width, height and channels. Channels must be 1 (grayscale/palette), 2 (LA), 3 (RGB) or 4 (RGBA). The image will be filled with black, transparent pixels. """ - assert 1 <= channels <= 4, "PNG only supports 1 to 4 channels per pixel. Error writing %s." % filename + assert 1 <= channels <= 4, f"PNG only supports 1 to 4 channels per pixel. Error writing {filename}." dimensions = (height, width, channels) if channels == 1: dimensions = (height, width) # keep the array 2-dimensional when possible _array = numpy.zeros(dimensions, dtype=PNGWriter.get_dtype(channel_bitdepth)) - return cls(_array, filename, - grayscale=grayscale, channel_bitdepth=channel_bitdepth, - has_alpha=has_alpha, palette=palette, channels=channels) + return cls( + _array, + filename, + grayscale=grayscale, + channel_bitdepth=channel_bitdepth, + has_alpha=has_alpha, + palette=palette, + channels=channels, + ) @classmethod - def from_array(cls, array, filename=None, channels=3, scale_to_range=False, - grayscale=False, channel_bitdepth=8, - has_alpha=False, palette=None): + def from_array( + cls, + array, + filename=None, + channels=3, + scale_to_range=False, + grayscale=False, + channel_bitdepth=8, + has_alpha=False, + palette=None, + ): """ Creates an image by using a provided array. The array may be ready to be written or still need fine-tuning via set_pixel(). @@ -128,11 +162,17 @@ def from_array(cls, array, filename=None, channels=3, scale_to_range=False, else: _array = array _array = numpy.rint(_array).astype(dtype=PNGWriter.get_dtype(channel_bitdepth)) # proper rounding - return cls(_array, filename, channels=channels, - grayscale=grayscale, channel_bitdepth=channel_bitdepth, - has_alpha=has_alpha, palette=palette) - - #the following methods should not need to be overriden + return cls( + _array, + filename, + channels=channels, + grayscale=grayscale, + channel_bitdepth=channel_bitdepth, + has_alpha=has_alpha, + palette=palette, + ) + + # the following methods should not need to be overriden def set_pixel(self, x, y, color): """ Color may be: value, tuple, list etc. @@ -179,16 +219,21 @@ def complete(self, filename=None): if filename is None: return if self.img is None: - self.img = png.Writer(width=self.width, height=self.height, - greyscale=self.grayscale, bitdepth=self.channel_bitdepth, # British spelling - alpha=self.has_alpha, palette=self.palette) - #write the image - with open(filename, 'wb') as f: + self.img = png.Writer( + width=self.width, + height=self.height, + greyscale=self.grayscale, + bitdepth=self.channel_bitdepth, # British spelling + alpha=self.has_alpha, + palette=self.palette, + ) + # write the image + with open(filename, "wb") as f: self.img.write_array(f, self.prepare_array(self.array)) @staticmethod def get_dtype(channel_bitdepth): - #PNG uses unsigned data exclusively; max. 16 Bit per channel + # PNG uses unsigned data exclusively; max. 16 Bit per channel if 8 < channel_bitdepth <= 16: return numpy.uint16 return numpy.uint8 @@ -212,7 +257,7 @@ def prepare_array(array): return: array in one of these formats ("boxed row boxed pixel" supposedly uses a lot of memory) """ - return array.flatten('C').tolist() + return array.flatten("C").tolist() def get_max_colors(self): return 2**self.channel_bitdepth - 1 @@ -224,7 +269,7 @@ def __setitem__(self, item, value): self.array[item] = value -class PNGReader(object): +class PNGReader: def __init__(self, filename): self.filename = filename @@ -238,7 +283,7 @@ def __init__(self, filename): # creates a 2-dimensional array (flat pixels) self.array = numpy.vstack(tuple(map(numpy.uint16, pngdata[2]))) - if pngdata[3]['planes'] > 1: # 'unflatten' the pixels + if pngdata[3]["planes"] > 1: # 'unflatten' the pixels # height, width, depth (-1 = automatic) self.array = self.array.reshape(self.height, self.width, -1) diff --git a/worldengine/imex/__init__.py b/worldengine/imex/__init__.py index 014bc634..8342d880 100644 --- a/worldengine/imex/__init__.py +++ b/worldengine/imex/__init__.py @@ -4,24 +4,25 @@ try: import gdal except ImportError: - print("Unable to load GDAL support, no heightmap export possible.") + gdal = None # GDAL not available - export functions will handle this import os import sys import tempfile + import numpy -''' +""" Useful CLI tools: python worldengine export seed_24106.world --export-format envi --export-datatype float32 gdal_translate -srcwin 375 384 128 128 seed_24106_elevation-32.envi test.envi gdal_translate test.envi -r cubicspline -outsize 1000% 1000% test2.envi gdal_translate test2.envi -scale -of PNG -ot Byte test.png gdal_translate test2.envi -scale -ot int32 test3.envi -''' +""" -''' +""" Whenever a GDAL short-format (http://www.gdal.org/formats_list.html) is given and a unique mapping to a file suffix exists, it is looked up in gdal_mapper. @@ -30,26 +31,33 @@ All other formats (>100) currently end up with their respective GDAL short-format converted to lower-case and might need to be renamed by the user. -''' +""" gdal_mapper = { # TODO: Find a way to make GDAL provide this mapping. - "aig" : "adf", - "bsb" : "kap", - "doq1" : "doq", - "doq2" : "doq", - "esat" : "n1", - "grib" : "grb", - "gtiff" : "tif", - "hfa" : "img", - "jdem" : "mem", - "jpeg" : "jpg", - "msgn" : "nat", + "aig": "adf", + "bsb": "kap", + "doq1": "doq", + "doq2": "doq", + "esat": "n1", + "grib": "grb", + "gtiff": "tif", + "hfa": "img", + "jdem": "mem", + "jpeg": "jpg", + "msgn": "nat", "terragen": "ter", - "usgsdem" : "dem", + "usgsdem": "dem", } -def export(world, export_filetype='GTiff', export_datatype='float32', export_dimensions=None, - export_normalize=None, export_subset=None, path='seed_output'): +def export( + world, + export_filetype="GTiff", + export_datatype="float32", + export_dimensions=None, + export_normalize=None, + export_subset=None, + path="seed_output", +): try: gdal except NameError: @@ -58,7 +66,7 @@ def export(world, export_filetype='GTiff', export_datatype='float32', export_dim final_driver = gdal.GetDriverByName(export_filetype) if final_driver is None: - print("%s driver not registered." % export_filetype) + print(f"{export_filetype} driver not registered.") sys.exit(1) # try to find the proper file-suffix @@ -72,40 +80,39 @@ def export(world, export_filetype='GTiff', export_datatype='float32', export_dim # translate export_datatype; http://www.gdal.org/gdal_8h.html#a22e22ce0a55036a96f652765793fb7a4 export_datatype = export_datatype.lower() - if export_datatype in ['gdt_byte', 'uint8', 'int8', 'byte', 'char']: + if export_datatype in ["gdt_byte", "uint8", "int8", "byte", "char"]: bpp = 8 # GDAL does not support int8 numpy_type = numpy.uint8 gdal_type = gdal.GDT_Byte - elif export_datatype in ['gdt_uint16', 'uint16']: + elif export_datatype in ["gdt_uint16", "uint16"]: bpp = 16 numpy_type = numpy.uint16 gdal_type = gdal.GDT_UInt16 - elif export_datatype in ['gdt_uint32', 'uint32']: + elif export_datatype in ["gdt_uint32", "uint32"]: bpp = 32 numpy_type = numpy.uint32 gdal_type = gdal.GDT_UInt32 - elif export_datatype in ['gdt_int16', 'int16']: + elif export_datatype in ["gdt_int16", "int16"]: bpp = 16 numpy_type = numpy.int16 gdal_type = gdal.GDT_Int16 - elif export_datatype in ['gdt_int32', 'int32', 'int']: # fallback for 'int' + elif export_datatype in ["gdt_int32", "int32", "int"]: # fallback for 'int' bpp = 32 numpy_type = numpy.int32 gdal_type = gdal.GDT_Int32 - elif export_datatype in ['gdt_float32', 'float32', 'float']: # fallback for 'float' + elif export_datatype in ["gdt_float32", "float32", "float"]: # fallback for 'float' bpp = 32 numpy_type = numpy.float32 gdal_type = gdal.GDT_Float32 - elif export_datatype in ['gdt_float64', 'float64']: + elif export_datatype in ["gdt_float64", "float64"]: bpp = 64 numpy_type = numpy.float64 gdal_type = gdal.GDT_Float64 else: - raise TypeError( - "Type of data not recognized or not supported by GDAL: %s" % export_datatype) + raise TypeError(f"Type of data not recognized or not supported by GDAL: {export_datatype}") # massage data to scale between the absolute min and max - elevation = numpy.copy(world.layers['elevation'].data) + elevation = numpy.copy(world.layers["elevation"].data) # switch to final data type; no rounding performed elevation = elevation.astype(numpy_type) @@ -141,16 +148,21 @@ def export(world, export_filetype='GTiff', export_datatype='float32', export_dim # apply changes to the dataset if export_dimensions or export_normalize: intermediate_ds = gdal.Translate( - '', intermediate_ds, format='MEM', width=width, height=height, - scaleParams=scale_param, resampleAlg=gdal.GRA_CubicSpline - #exponents=str(2) - ) + "", + intermediate_ds, + format="MEM", + width=width, + height=height, + scaleParams=scale_param, + resampleAlg=gdal.GRA_CubicSpline, + # exponents=str(2) + ) # only use a specific subset of dataset if export_subset is not None: - intermediate_ds = gdal.Translate('', intermediate_ds, format='MEM', srcWin=export_subset) + intermediate_ds = gdal.Translate("", intermediate_ds, format="MEM", srcWin=export_subset) - final_driver.CreateCopy('%s-%d.%s' % (path, bpp, export_filetype), intermediate_ds) + final_driver.CreateCopy("%s-%d.%s" % (path, bpp, export_filetype), intermediate_ds) intermediate_ds = None # dereference os.close(fh_inter_file) diff --git a/worldengine/model/__init__.py b/worldengine/model/__init__.py index 74b9d87e..4cbb7ddf 100644 --- a/worldengine/model/__init__.py +++ b/worldengine/model/__init__.py @@ -1 +1 @@ -__author__ = 'federico' +__author__ = "federico" diff --git a/worldengine/model/world.py b/worldengine/model/world.py index dc300b01..0dad5524 100644 --- a/worldengine/model/world.py +++ b/worldengine/model/world.py @@ -1,15 +1,13 @@ import numpy -from worldengine.biome import biome_name_to_index, biome_index_to_name, Biome -from worldengine.biome import Iceland import worldengine.protobuf.World_pb2 as Protobuf -from worldengine.step import Step +from worldengine.biome import Biome, Iceland, biome_index_to_name, biome_name_to_index from worldengine.common import _equal +from worldengine.step import Step from worldengine.version import __version__ -class Size(object): - +class Size: def __init__(self, width, height): self.width = width self.height = height @@ -21,8 +19,7 @@ def __eq__(self, other): return False -class GenerationParameters(object): - +class GenerationParameters: def __init__(self, n_plates, ocean_level, step): self.n_plates = n_plates self.ocean_level = ocean_level @@ -35,8 +32,7 @@ def __eq__(self, other): return False -class Layer(object): - +class Layer: def __init__(self, data): self.data = data @@ -54,42 +50,45 @@ def max(self): class LayerWithThresholds(Layer): - def __init__(self, data, thresholds): Layer.__init__(self, data) self.thresholds = thresholds def __eq__(self, other): if isinstance(other, self.__class__): - return _equal(self.data, other.data) and _equal(self.thresholds, other.thresholds) else: return False class LayerWithQuantiles(Layer): - def __init__(self, data, quantiles): Layer.__init__(self, data) self.quantiles = quantiles def __eq__(self, other): if isinstance(other, self.__class__): - return _equal(self.data, other.data) and _equal(self.quantiles, other.quantiles) else: return False -class World(object): +class World: """A world composed by name, dimensions and all the characteristics of each cell. """ - def __init__(self, name, size, seed, generation_params, - temps=[0.874, 0.765, 0.594, 0.439, 0.366, 0.124], - humids=[.941, .778, .507, .236, 0.073, .014, .002], - gamma_curve=1.25, curve_offset=.2): + def __init__( + self, + name, + size, + seed, + generation_params, + temps=[0.874, 0.765, 0.594, 0.439, 0.366, 0.124], + humids=[0.941, 0.778, 0.507, 0.236, 0.073, 0.014, 0.002], + gamma_curve=1.25, + curve_offset=0.2, + ): self.name = name self.size = size self.seed = seed @@ -122,7 +121,7 @@ def __eq__(self, other): @classmethod def from_dict(cls, in_dict): - instance = World(in_dict['name'], Size(in_dict['width'], in_dict['height'])) + instance = World(in_dict["name"], Size(in_dict["width"], in_dict["height"])) for k in in_dict: instance.__dict__[k] = in_dict[k] return instance @@ -149,7 +148,6 @@ def protobuf_unserialize(cls, serialized): @staticmethod def _to_protobuf_matrix(matrix, p_matrix, transformation=None): - m = matrix if transformation is not None: t = numpy.vectorize(transformation) @@ -157,12 +155,12 @@ def _to_protobuf_matrix(matrix, p_matrix, transformation=None): for row in m: p_row = p_matrix.rows.add() - ''' + """ When using numpy, certain primitive types are replaced with numpy-specifc versions that, even though mostly compatible, cannot be digested by protobuf. This might change at some point; for now a conversion is necessary. - ''' + """ p_row.cells.extend(row.tolist()) @staticmethod @@ -206,13 +204,12 @@ def _from_protobuf_matrix_with_quantiles(p_matrix): @staticmethod def worldengine_tag(): - return ord('W') * (256 ** 3) + ord('o') * (256 ** 2) + \ - ord('e') * (256 ** 1) + ord('n') + return ord("W") * (256**3) + ord("o") * (256**2) + ord("e") * (256**1) + ord("n") @staticmethod def __version_hashcode__(): - parts = __version__.split('.') - return int(parts[0])*(256**3) + int(parts[1])*(256**2) + int(parts[2])*(256**1) + parts = __version__.split(".") + return int(parts[0]) * (256**3) + int(parts[1]) * (256**2) + int(parts[2]) * (256**1) def _to_protobuf_world(self): p_world = Protobuf.World() @@ -230,79 +227,84 @@ def _to_protobuf_world(self): p_world.generationData.step = self.step.name # Elevation - self._to_protobuf_matrix(self.layers['elevation'].data, p_world.heightMapData) - p_world.heightMapTh_sea = self.layers['elevation'].thresholds[0][1] - p_world.heightMapTh_plain = self.layers['elevation'].thresholds[1][1] - p_world.heightMapTh_hill = self.layers['elevation'].thresholds[2][1] + self._to_protobuf_matrix(self.layers["elevation"].data, p_world.heightMapData) + p_world.heightMapTh_sea = self.layers["elevation"].thresholds[0][1] + p_world.heightMapTh_plain = self.layers["elevation"].thresholds[1][1] + p_world.heightMapTh_hill = self.layers["elevation"].thresholds[2][1] # Plates - self._to_protobuf_matrix(self.layers['plates'].data, p_world.plates) + self._to_protobuf_matrix(self.layers["plates"].data, p_world.plates) # Ocean - self._to_protobuf_matrix(self.layers['ocean'].data, p_world.ocean) - self._to_protobuf_matrix(self.layers['sea_depth'].data, p_world.sea_depth) + self._to_protobuf_matrix(self.layers["ocean"].data, p_world.ocean) + self._to_protobuf_matrix(self.layers["sea_depth"].data, p_world.sea_depth) if self.has_biome(): - self._to_protobuf_matrix(self.layers['biome'].data, p_world.biome, biome_name_to_index) + self._to_protobuf_matrix(self.layers["biome"].data, p_world.biome, biome_name_to_index) if self.has_humidity(): - self._to_protobuf_matrix_with_quantiles(self.layers['humidity'], p_world.humidity) + self._to_protobuf_matrix_with_quantiles(self.layers["humidity"], p_world.humidity) if self.has_irrigation(): - self._to_protobuf_matrix(self.layers['irrigation'].data, p_world.irrigation) + self._to_protobuf_matrix(self.layers["irrigation"].data, p_world.irrigation) if self.has_permeability(): - self._to_protobuf_matrix(self.layers['permeability'].data, - p_world.permeabilityData) - p_world.permeability_low = self.layers['permeability'].thresholds[0][1] - p_world.permeability_med = self.layers['permeability'].thresholds[1][1] + self._to_protobuf_matrix(self.layers["permeability"].data, p_world.permeabilityData) + p_world.permeability_low = self.layers["permeability"].thresholds[0][1] + p_world.permeability_med = self.layers["permeability"].thresholds[1][1] if self.has_watermap(): - self._to_protobuf_matrix(self.layers['watermap'].data, p_world.watermapData) - p_world.watermap_creek = self.layers['watermap'].thresholds['creek'] - p_world.watermap_river = self.layers['watermap'].thresholds['river'] - p_world.watermap_mainriver = self.layers['watermap'].thresholds['main river'] + self._to_protobuf_matrix(self.layers["watermap"].data, p_world.watermapData) + p_world.watermap_creek = self.layers["watermap"].thresholds["creek"] + p_world.watermap_river = self.layers["watermap"].thresholds["river"] + p_world.watermap_mainriver = self.layers["watermap"].thresholds["main river"] if self.has_lakemap(): - self._to_protobuf_matrix(self.layers['lake_map'].data, p_world.lakemap) + self._to_protobuf_matrix(self.layers["lake_map"].data, p_world.lakemap) if self.has_rivermap(): - self._to_protobuf_matrix(self.layers['river_map'].data, p_world.rivermap) + self._to_protobuf_matrix(self.layers["river_map"].data, p_world.rivermap) if self.has_precipitations(): - self._to_protobuf_matrix(self.layers['precipitation'].data, p_world.precipitationData) - p_world.precipitation_low = self.layers['precipitation'].thresholds[0][1] - p_world.precipitation_med = self.layers['precipitation'].thresholds[1][1] + self._to_protobuf_matrix(self.layers["precipitation"].data, p_world.precipitationData) + p_world.precipitation_low = self.layers["precipitation"].thresholds[0][1] + p_world.precipitation_med = self.layers["precipitation"].thresholds[1][1] if self.has_temperature(): - self._to_protobuf_matrix(self.layers['temperature'].data, p_world.temperatureData) - p_world.temperature_polar = self.layers['temperature'].thresholds[0][1] - p_world.temperature_alpine = self.layers['temperature'].thresholds[1][1] - p_world.temperature_boreal = self.layers['temperature'].thresholds[2][1] - p_world.temperature_cool = self.layers['temperature'].thresholds[3][1] - p_world.temperature_warm = self.layers['temperature'].thresholds[4][1] - p_world.temperature_subtropical = self.layers['temperature'].thresholds[5][1] + self._to_protobuf_matrix(self.layers["temperature"].data, p_world.temperatureData) + p_world.temperature_polar = self.layers["temperature"].thresholds[0][1] + p_world.temperature_alpine = self.layers["temperature"].thresholds[1][1] + p_world.temperature_boreal = self.layers["temperature"].thresholds[2][1] + p_world.temperature_cool = self.layers["temperature"].thresholds[3][1] + p_world.temperature_warm = self.layers["temperature"].thresholds[4][1] + p_world.temperature_subtropical = self.layers["temperature"].thresholds[5][1] if self.has_icecap(): - self._to_protobuf_matrix(self.layers['icecap'].data, p_world.icecap) + self._to_protobuf_matrix(self.layers["icecap"].data, p_world.icecap) return p_world @classmethod def _from_protobuf_world(cls, p_world): w = World( - p_world.name, Size(p_world.width, p_world.height), - p_world.generationData.seed, GenerationParameters( + p_world.name, + Size(p_world.width, p_world.height), + p_world.generationData.seed, + GenerationParameters( p_world.generationData.n_plates, p_world.generationData.ocean_level, - Step.get_by_name(p_world.generationData.step))) + Step.get_by_name(p_world.generationData.step), + ), + ) # Elevation e = numpy.array(World._from_protobuf_matrix(p_world.heightMapData)) - e_th = [('sea', p_world.heightMapTh_sea), - ('plain', p_world.heightMapTh_plain), - ('hill', p_world.heightMapTh_hill), - ('mountain', None)] + e_th = [ + ("sea", p_world.heightMapTh_sea), + ("plain", p_world.heightMapTh_plain), + ("hill", p_world.heightMapTh_hill), + ("mountain", None), + ] w.elevation = (e, e_th) # Plates @@ -314,9 +316,7 @@ def _from_protobuf_world(cls, p_world): # Biome if p_world.biome.rows: - w.biome = numpy.array( - World._from_protobuf_matrix(p_world.biome, biome_index_to_name), - dtype=object) + w.biome = numpy.array(World._from_protobuf_matrix(p_world.biome, biome_index_to_name), dtype=object) # Humidity if p_world.humidity.rows: @@ -327,41 +327,32 @@ def _from_protobuf_world(cls, p_world): if p_world.permeabilityData.rows: p = numpy.array(World._from_protobuf_matrix(p_world.permeabilityData)) - p_th = [ - ('low', p_world.permeability_low), - ('med', p_world.permeability_med), - ('hig', None) - ] + p_th = [("low", p_world.permeability_low), ("med", p_world.permeability_med), ("hig", None)] w.permeability = (p, p_th) if p_world.watermapData.rows: - data = numpy.array(World._from_protobuf_matrix( - p_world.watermapData)) + data = numpy.array(World._from_protobuf_matrix(p_world.watermapData)) thresholds = {} - thresholds['creek'] = p_world.watermap_creek - thresholds['river'] = p_world.watermap_river - thresholds['main river'] = p_world.watermap_mainriver + thresholds["creek"] = p_world.watermap_creek + thresholds["river"] = p_world.watermap_river + thresholds["main river"] = p_world.watermap_mainriver w.watermap = (data, thresholds) if p_world.precipitationData.rows: p = numpy.array(World._from_protobuf_matrix(p_world.precipitationData)) - p_th = [ - ('low', p_world.precipitation_low), - ('med', p_world.precipitation_med), - ('hig', None) - ] + p_th = [("low", p_world.precipitation_low), ("med", p_world.precipitation_med), ("hig", None)] w.precipitation = (p, p_th) if p_world.temperatureData.rows: t = numpy.array(World._from_protobuf_matrix(p_world.temperatureData)) t_th = [ - ('polar', p_world.temperature_polar), - ('alpine', p_world.temperature_alpine), - ('boreal', p_world.temperature_boreal), - ('cool', p_world.temperature_cool), - ('warm', p_world.temperature_warm), - ('subtropical', p_world.temperature_subtropical), - ('tropical', None) + ("polar", p_world.temperature_polar), + ("alpine", p_world.temperature_alpine), + ("boreal", p_world.temperature_boreal), + ("cool", p_world.temperature_cool), + ("warm", p_world.temperature_warm), + ("subtropical", p_world.temperature_subtropical), + ("tropical", None), ] w.temperature = (t, t_th) @@ -388,31 +379,31 @@ def contains(self, pos): # def random_land(self, num_samples=1): - if self.layers['ocean'].data.all(): + if self.layers["ocean"].data.all(): # return invalid indices if there is no land at all return None, None - land = numpy.invert(self.layers['ocean'].data) + land = numpy.invert(self.layers["ocean"].data) # returns a list of tuples/indices with land positions - land_indices = numpy.transpose(numpy.nonzero(land)) + land_indices = numpy.transpose(numpy.nonzero(land)) - result = numpy.zeros(num_samples*2, dtype=int) + result = numpy.zeros(num_samples * 2, dtype=int) - for i in range(0, num_samples*2, 2): - r_num = numpy.random.randint(0, len(land_indices)) # uses global RNG - result[i+1], result[i] = land_indices[r_num] + for i in range(0, num_samples * 2, 2): + r_num = numpy.random.randint(0, len(land_indices)) # uses global RNG + result[i + 1], result[i] = land_indices[r_num] return result def is_land(self, pos): # faster than reversing pos or transposing ocean - return not self.layers['ocean'].data[pos[1], pos[0]] + return not self.layers["ocean"].data[pos[1], pos[0]] def is_ocean(self, pos): - return self.layers['ocean'].data[pos[1], pos[0]] + return self.layers["ocean"].data[pos[1], pos[0]] def sea_level(self): - return self.layers['elevation'].thresholds[0][1] + return self.layers["elevation"].thresholds[0][1] # # Tiles around @@ -436,67 +427,66 @@ def tiles_around(self, pos, radius=1, predicate=None): # def start_mountain_th(self): - return self.layers['elevation'].thresholds[2][1] + return self.layers["elevation"].thresholds[2][1] def get_mountain_level(self): - if len(self.layers['elevation'].thresholds) == 4: + if len(self.layers["elevation"].thresholds) == 4: mi = 2 else: mi = 1 - return self.layers['elevation'].thresholds[mi][1] - + return self.layers["elevation"].thresholds[mi][1] def is_mountain(self, pos): if self.is_ocean(pos): return False x, y = pos - return self.layers['elevation'].data[y][x] > self.get_mountain_level() + return self.layers["elevation"].data[y][x] > self.get_mountain_level() def is_low_mountain(self, pos): if not self.is_mountain(pos): return False - if len(self.layers['elevation'].thresholds) == 4: + if len(self.layers["elevation"].thresholds) == 4: mi = 2 else: mi = 1 - mountain_level = self.layers['elevation'].thresholds[mi][1] + mountain_level = self.layers["elevation"].thresholds[mi][1] x, y = pos - return self.layers['elevation'].data[y, x] < mountain_level + 2.0 + return self.layers["elevation"].data[y, x] < mountain_level + 2.0 def level_of_mountain(self, pos): mountain_level = self.get_mountain_level() x, y = pos - if self.layers['elevation'].data[y, x] <= mountain_level: + if self.layers["elevation"].data[y, x] <= mountain_level: return 0 else: - return self.layers['elevation'].data[y, x] - mountain_level + return self.layers["elevation"].data[y, x] - mountain_level def is_high_mountain(self, pos): if not self.is_mountain(pos): return False - if len(self.layers['elevation'].thresholds) == 4: + if len(self.layers["elevation"].thresholds) == 4: mi = 2 else: mi = 1 - mountain_level = self.layers['elevation'].thresholds[mi][1] + mountain_level = self.layers["elevation"].thresholds[mi][1] x, y = pos - return self.layers['elevation'].data[y, x] > mountain_level + 4.0 + return self.layers["elevation"].data[y, x] > mountain_level + 4.0 def is_hill(self, pos): if self.is_ocean(pos): return False - if len(self.layers['elevation'].thresholds) == 4: + if len(self.layers["elevation"].thresholds) == 4: hi = 1 else: hi = 0 - hill_level = self.layers['elevation'].thresholds[hi][1] - mountain_level = self.layers['elevation'].thresholds[hi + 1][1] + hill_level = self.layers["elevation"].thresholds[hi][1] + mountain_level = self.layers["elevation"].thresholds[hi + 1][1] x, y = pos - return hill_level < self.layers['elevation'].data[y, x] < mountain_level + return hill_level < self.layers["elevation"].data[y, x] < mountain_level def elevation_at(self, pos): - return self.layers['elevation'].data[pos[1], pos[0]] + return self.layers["elevation"].data[pos[1], pos[0]] # # Precipitations @@ -504,68 +494,68 @@ def elevation_at(self, pos): def precipitations_at(self, pos): x, y = pos - return self.layers['precipitation'].data[y, x] + return self.layers["precipitation"].data[y, x] def precipitations_thresholds(self): - return self.layers['precipitation'].thresholds + return self.layers["precipitation"].thresholds # # Temperature # def is_temperature_polar(self, pos): - th_max = self.layers['temperature'].thresholds[0][1] + th_max = self.layers["temperature"].thresholds[0][1] x, y = pos - t = self.layers['temperature'].data[y, x] + t = self.layers["temperature"].data[y, x] return t < th_max def is_temperature_alpine(self, pos): - th_min = self.layers['temperature'].thresholds[0][1] - th_max = self.layers['temperature'].thresholds[1][1] + th_min = self.layers["temperature"].thresholds[0][1] + th_max = self.layers["temperature"].thresholds[1][1] x, y = pos - t = self.layers['temperature'].data[y, x] + t = self.layers["temperature"].data[y, x] return th_max > t >= th_min def is_temperature_boreal(self, pos): - th_min = self.layers['temperature'].thresholds[1][1] - th_max = self.layers['temperature'].thresholds[2][1] + th_min = self.layers["temperature"].thresholds[1][1] + th_max = self.layers["temperature"].thresholds[2][1] x, y = pos - t = self.layers['temperature'].data[y, x] + t = self.layers["temperature"].data[y, x] return th_max > t >= th_min def is_temperature_cool(self, pos): - th_min = self.layers['temperature'].thresholds[2][1] - th_max = self.layers['temperature'].thresholds[3][1] + th_min = self.layers["temperature"].thresholds[2][1] + th_max = self.layers["temperature"].thresholds[3][1] x, y = pos - t = self.layers['temperature'].data[y, x] + t = self.layers["temperature"].data[y, x] return th_max > t >= th_min def is_temperature_warm(self, pos): - th_min = self.layers['temperature'].thresholds[3][1] - th_max = self.layers['temperature'].thresholds[4][1] + th_min = self.layers["temperature"].thresholds[3][1] + th_max = self.layers["temperature"].thresholds[4][1] x, y = pos - t = self.layers['temperature'].data[y, x] + t = self.layers["temperature"].data[y, x] return th_max > t >= th_min def is_temperature_subtropical(self, pos): - th_min = self.layers['temperature'].thresholds[4][1] - th_max = self.layers['temperature'].thresholds[5][1] + th_min = self.layers["temperature"].thresholds[4][1] + th_max = self.layers["temperature"].thresholds[5][1] x, y = pos - t = self.layers['temperature'].data[y, x] + t = self.layers["temperature"].data[y, x] return th_max > t >= th_min def is_temperature_tropical(self, pos): - th_min = self.layers['temperature'].thresholds[5][1] + th_min = self.layers["temperature"].thresholds[5][1] x, y = pos - t = self.layers['temperature'].data[y, x] + t = self.layers["temperature"].data[y, x] return t >= th_min def temperature_at(self, pos): x, y = pos - return self.layers['temperature'].data[y, x] + return self.layers["temperature"].data[y, x] def temperature_thresholds(self): - return self.layers['temperature'].thresholds + return self.layers["temperature"].thresholds # # Humidity @@ -573,66 +563,66 @@ def temperature_thresholds(self): def humidity_at(self, pos): x, y = pos - return self.layers['humidity'].data[y, x] + return self.layers["humidity"].data[y, x] def is_humidity_above_quantile(self, pos, q): - th = self.layers['humidity'].quantiles[str(q)] + th = self.layers["humidity"].quantiles[str(q)] x, y = pos - t = self.layers['humidity'].data[y, x] + t = self.layers["humidity"].data[y, x] return t >= th def is_humidity_superarid(self, pos): - th_max = self.layers['humidity'].quantiles['87'] + th_max = self.layers["humidity"].quantiles["87"] x, y = pos - t = self.layers['humidity'].data[y, x] + t = self.layers["humidity"].data[y, x] return t < th_max def is_humidity_perarid(self, pos): - th_min = self.layers['humidity'].quantiles['87'] - th_max = self.layers['humidity'].quantiles['75'] + th_min = self.layers["humidity"].quantiles["87"] + th_max = self.layers["humidity"].quantiles["75"] x, y = pos - t = self.layers['humidity'].data[y, x] + t = self.layers["humidity"].data[y, x] return th_max > t >= th_min def is_humidity_arid(self, pos): - th_min = self.layers['humidity'].quantiles['75'] - th_max = self.layers['humidity'].quantiles['62'] + th_min = self.layers["humidity"].quantiles["75"] + th_max = self.layers["humidity"].quantiles["62"] x, y = pos - t = self.layers['humidity'].data[y, x] + t = self.layers["humidity"].data[y, x] return th_max > t >= th_min def is_humidity_semiarid(self, pos): - th_min = self.layers['humidity'].quantiles['62'] - th_max = self.layers['humidity'].quantiles['50'] + th_min = self.layers["humidity"].quantiles["62"] + th_max = self.layers["humidity"].quantiles["50"] x, y = pos - t = self.layers['humidity'].data[y, x] + t = self.layers["humidity"].data[y, x] return th_max > t >= th_min def is_humidity_subhumid(self, pos): - th_min = self.layers['humidity'].quantiles['50'] - th_max = self.layers['humidity'].quantiles['37'] + th_min = self.layers["humidity"].quantiles["50"] + th_max = self.layers["humidity"].quantiles["37"] x, y = pos - t = self.layers['humidity'].data[y, x] + t = self.layers["humidity"].data[y, x] return th_max > t >= th_min def is_humidity_humid(self, pos): - th_min = self.layers['humidity'].quantiles['37'] - th_max = self.layers['humidity'].quantiles['25'] + th_min = self.layers["humidity"].quantiles["37"] + th_max = self.layers["humidity"].quantiles["25"] x, y = pos - t = self.layers['humidity'].data[y, x] + t = self.layers["humidity"].data[y, x] return th_max > t >= th_min def is_humidity_perhumid(self, pos): - th_min = self.layers['humidity'].quantiles['25'] - th_max = self.layers['humidity'].quantiles['12'] + th_min = self.layers["humidity"].quantiles["25"] + th_max = self.layers["humidity"].quantiles["12"] x, y = pos - t = self.layers['humidity'].data[y, x] + t = self.layers["humidity"].data[y, x] return th_max > t >= th_min def is_humidity_superhumid(self, pos): - th_min = self.layers['humidity'].quantiles['12'] + th_min = self.layers["humidity"].quantiles["12"] x, y = pos - t = self.layers['humidity'].data[y, x] + t = self.layers["humidity"].data[y, x] return t >= th_min # @@ -640,29 +630,26 @@ def is_humidity_superhumid(self, pos): # def contains_stream(self, pos): - return self.contains_creek(pos) or self.contains_river( - pos) or self.contains_main_river(pos) + return self.contains_creek(pos) or self.contains_river(pos) or self.contains_main_river(pos) def contains_creek(self, pos): x, y = pos - v = self.watermap['data'][y, x] - return self.watermap['thresholds']['creek'] <= v < \ - self.watermap['thresholds']['river'] + v = self.watermap["data"][y, x] + return self.watermap["thresholds"]["creek"] <= v < self.watermap["thresholds"]["river"] def contains_river(self, pos): x, y = pos - v = self.watermap['data'][y, x] - return self.watermap['thresholds']['river'] <= v < \ - self.watermap['thresholds']['main river'] + v = self.watermap["data"][y, x] + return self.watermap["thresholds"]["river"] <= v < self.watermap["thresholds"]["main river"] def contains_main_river(self, pos): x, y = pos - v = self.watermap['data'][y, x] - return v >= self.watermap['thresholds']['main river'] + v = self.watermap["data"][y, x] + return v >= self.watermap["thresholds"]["main river"] def watermap_at(self, pos): x, y = pos - return self.watermap['data'][y, x] + return self.watermap["data"][y, x] # # Biome @@ -670,12 +657,11 @@ def watermap_at(self, pos): def biome_at(self, pos): x, y = pos - b = Biome.by_name(self.layers['biome'].data[y, x]) + b = Biome.by_name(self.layers["biome"].data[y, x]) if b is None: - raise Exception('Not found') + raise Exception("Not found") return b - def is_iceland(self, pos): for subclass in Iceland.__subclasses__(): if isinstance(self.biome_at(pos), subclass): @@ -683,13 +669,12 @@ def is_iceland(self, pos): return False - # # Plates # def n_actual_plates(self): - return self.layers['plates'].data.max() + 1 + return self.layers["plates"].data.max() + 1 # # Properties @@ -697,7 +682,7 @@ def n_actual_plates(self): @property def elevation(self): - return self.layers['elevation'].data + return self.layers["elevation"].data @elevation.setter def elevation(self, val): @@ -709,71 +694,71 @@ def elevation(self, val): if data.shape != (self.height, self.width): raise Exception( "Setting elevation map with wrong dimension. " - "Expected %d x %d, found %d x %d" % ( - self.width, self.height, data.shape[1], data.shape[0])) - self.layers['elevation'] = LayerWithThresholds(data, thresholds) + "Expected %d x %d, found %d x %d" % (self.width, self.height, data.shape[1], data.shape[0]) + ) + self.layers["elevation"] = LayerWithThresholds(data, thresholds) @property def plates(self): - return self.layers['plates'].data + return self.layers["plates"].data @plates.setter def plates(self, data): if (data.shape[0] != self.height) or (data.shape[1] != self.width): raise Exception( "Setting plates map with wrong dimension. " - "Expected %d x %d, found %d x %d" % ( - self.width, self.height, data.shape[1], data.shape[0])) - self.layers['plates'] = Layer(data) + "Expected %d x %d, found %d x %d" % (self.width, self.height, data.shape[1], data.shape[0]) + ) + self.layers["plates"] = Layer(data) @property def biome(self): - return self.layers['biome'].data + return self.layers["biome"].data @biome.setter def biome(self, biome): if biome.shape[0] != self.height: raise Exception( "Setting data with wrong height: biome has height %i while " - "the height is currently %i" % ( - biome.shape[0], self.height)) + "the height is currently %i" % (biome.shape[0], self.height) + ) if biome.shape[1] != self.width: raise Exception("Setting data with wrong width") - self.layers['biome'] = Layer(biome) + self.layers["biome"] = Layer(biome) @property def ocean(self): - return self.layers['ocean'].data + return self.layers["ocean"].data @ocean.setter def ocean(self, ocean): if (ocean.shape[0] != self.height) or (ocean.shape[1] != self.width): raise Exception( "Setting ocean map with wrong dimension. Expected %d x %d, " - "found %d x %d" % (self.width, self.height, - ocean.shape[1], ocean.shape[0])) - self.layers['ocean'] = Layer(ocean) + "found %d x %d" % (self.width, self.height, ocean.shape[1], ocean.shape[0]) + ) + self.layers["ocean"] = Layer(ocean) @property def sea_depth(self): - return self.layers['sea_depth'].data + return self.layers["sea_depth"].data @sea_depth.setter def sea_depth(self, data): if (data.shape[0] != self.height) or (data.shape[1] != self.width): raise Exception( "Setting sea depth map with wrong dimension. Expected %d x %d, " - "found %d x %d" % (self.width, self.height, - data.shape[1], data.shape[0])) - self.layers['sea_depth'] = Layer(data) + "found %d x %d" % (self.width, self.height, data.shape[1], data.shape[0]) + ) + self.layers["sea_depth"] = Layer(data) @property def precipitation(self): - return self.layers['precipitation'].data + return self.layers["precipitation"].data @precipitation.setter def precipitation(self, val): - """"Precipitation is a value in [-1,1]""" + """ "Precipitation is a value in [-1,1]""" try: data, thresholds = val except ValueError: @@ -783,11 +768,11 @@ def precipitation(self, val): raise Exception("Setting data with wrong height") if data.shape[1] != self.width: raise Exception("Setting data with wrong width") - self.layers['precipitation'] = LayerWithThresholds(data, thresholds) + self.layers["precipitation"] = LayerWithThresholds(data, thresholds) @property def humidity(self): - return self.layers['humidity'].data + return self.layers["humidity"].data @humidity.setter def humidity(self, val): @@ -801,11 +786,11 @@ def humidity(self, val): raise Exception("Setting data with wrong height") if data.shape[1] != self.width: raise Exception("Setting data with wrong width") - self.layers['humidity'] = LayerWithQuantiles(data, quantiles) + self.layers["humidity"] = LayerWithQuantiles(data, quantiles) @property def irrigation(self): - return self.layers['irrigation'].data + return self.layers["irrigation"].data @irrigation.setter def irrigation(self, data): @@ -813,11 +798,11 @@ def irrigation(self, data): raise Exception("Setting data with wrong height") if data.shape[1] != self.width: raise Exception("Setting data with wrong width") - self.layers['irrigation'] = Layer(data) + self.layers["irrigation"] = Layer(data) @property def temperature(self): - return self.layers['temperature'].data + return self.layers["temperature"].data @temperature.setter def temperature(self, val): @@ -830,11 +815,11 @@ def temperature(self, val): raise Exception("Setting data with wrong height") if data.shape[1] != self.width: raise Exception("Setting data with wrong width") - self.layers['temperature'] = LayerWithThresholds(data, thresholds) + self.layers["temperature"] = LayerWithThresholds(data, thresholds) @property def permeability(self): - return self.layers['permeability'].data + return self.layers["permeability"].data @permeability.setter def permeability(self, val): @@ -847,11 +832,11 @@ def permeability(self, val): raise Exception("Setting data with wrong height") if data.shape[1] != self.width: raise Exception("Setting data with wrong width") - self.layers['permeability'] = LayerWithThresholds(data, thresholds) + self.layers["permeability"] = LayerWithThresholds(data, thresholds) @property def watermap(self): - return self.layers['watermap'].data + return self.layers["watermap"].data @watermap.setter def watermap(self, val): @@ -864,65 +849,65 @@ def watermap(self, val): raise Exception("Setting data with wrong height") if data.shape[1] != self.width: raise Exception("Setting data with wrong width") - self.layers['watermap'] = LayerWithThresholds(data, thresholds) + self.layers["watermap"] = LayerWithThresholds(data, thresholds) @property def rivermap(self): - return self.layers['river_map'].data + return self.layers["river_map"].data @rivermap.setter def rivermap(self, river_map): - self.layers['river_map'] = Layer(river_map) + self.layers["river_map"] = Layer(river_map) @property def lakemap(self): - return self.layers['lake_map'].data + return self.layers["lake_map"].data @lakemap.setter def lakemap(self, lake_map): - self.layers['lake_map'] = Layer(lake_map) + self.layers["lake_map"] = Layer(lake_map) @property def icecap(self): - return self.layers['icecap'].data + return self.layers["icecap"].data @icecap.setter def icecap(self, icecap): - self.layers['icecap'] = Layer(icecap) + self.layers["icecap"] = Layer(icecap) # # Testers # def has_ocean(self): - return 'ocean' in self.layers + return "ocean" in self.layers def has_precipitations(self): - return 'precipitation' in self.layers + return "precipitation" in self.layers def has_watermap(self): - return 'watermap' in self.layers + return "watermap" in self.layers def has_irrigation(self): - return 'irrigation' in self.layers + return "irrigation" in self.layers def has_humidity(self): - return 'humidity' in self.layers + return "humidity" in self.layers def has_temperature(self): - return 'temperature' in self.layers + return "temperature" in self.layers def has_permeability(self): - return 'permeability' in self.layers + return "permeability" in self.layers def has_biome(self): - return 'biome' in self.layers + return "biome" in self.layers def has_rivermap(self): - return 'river_map' in self.layers + return "river_map" in self.layers def has_lakemap(self): - return 'lake_map' in self.layers + return "lake_map" in self.layers def has_icecap(self): - return 'icecap' in self.layers + return "icecap" in self.layers diff --git a/worldengine/plates.py b/worldengine/plates.py index 2fd75929..5400733a 100644 --- a/worldengine/plates.py +++ b/worldengine/plates.py @@ -2,25 +2,49 @@ # extension which is not available when using this project from jython import time -import platec -import numpy -from worldengine.generation import Step, add_noise_to_elevation, center_land, generate_world, \ - get_verbose, initialize_ocean_and_thresholds, place_oceans_at_map_borders -from worldengine.model.world import World, Size, GenerationParameters +import numpy +import platec +from worldengine.generation import ( + Step, + add_noise_to_elevation, + center_land, + generate_world, + get_verbose, + initialize_ocean_and_thresholds, + place_oceans_at_map_borders, +) +from worldengine.model.world import GenerationParameters, Size, World -def generate_plates_simulation(seed, width, height, sea_level=0.65, - erosion_period=60, folding_ratio=0.02, - aggr_overlap_abs=1000000, aggr_overlap_rel=0.33, - cycle_count=2, num_plates=10, - verbose=get_verbose()): +def generate_plates_simulation( + seed, + width, + height, + sea_level=0.65, + erosion_period=60, + folding_ratio=0.02, + aggr_overlap_abs=1000000, + aggr_overlap_rel=0.33, + cycle_count=2, + num_plates=10, + verbose=get_verbose(), +): if verbose: start_time = time.time() - p = platec.create(seed, width, height, sea_level, erosion_period, - folding_ratio, aggr_overlap_abs, aggr_overlap_rel, - cycle_count, num_plates) + p = platec.create( + seed, + width, + height, + sea_level, + erosion_period, + folding_ratio, + aggr_overlap_abs, + aggr_overlap_rel, + cycle_count, + num_plates, + ) # Note: To rescale the worlds heightmap to roughly Earths scale, multiply by 2000. while platec.is_finished(p) == 0: @@ -30,50 +54,80 @@ def generate_plates_simulation(seed, width, height, sea_level=0.65, pm = platec.get_platesmap(p) if verbose: elapsed_time = time.time() - start_time - print("...plates.generate_plates_simulation() complete. " + - "Elapsed time " + str(elapsed_time) + " seconds.") + print("...plates.generate_plates_simulation() complete. " + "Elapsed time " + str(elapsed_time) + " seconds.") return hm, pm -def _plates_simulation(name, width, height, seed, temps= - [.874, .765, .594, .439, .366, .124], humids= - [.941, .778, .507, .236, 0.073, .014, .002], gamma_curve=1.25, - curve_offset=.2, num_plates=10, ocean_level=1.0, - step=Step.full(), verbose=get_verbose()): - e_as_array, p_as_array = generate_plates_simulation(seed, width, height, - num_plates=num_plates, - verbose=verbose) +def _plates_simulation( + name, + width, + height, + seed, + temps=[0.874, 0.765, 0.594, 0.439, 0.366, 0.124], + humids=[0.941, 0.778, 0.507, 0.236, 0.073, 0.014, 0.002], + gamma_curve=1.25, + curve_offset=0.2, + num_plates=10, + ocean_level=1.0, + step=Step.full(), + verbose=get_verbose(), +): + e_as_array, p_as_array = generate_plates_simulation(seed, width, height, num_plates=num_plates, verbose=verbose) - world = World(name, Size(width, height), seed, - GenerationParameters(num_plates, ocean_level, step), - temps, humids, gamma_curve, curve_offset) + world = World( + name, + Size(width, height), + seed, + GenerationParameters(num_plates, ocean_level, step), + temps, + humids, + gamma_curve, + curve_offset, + ) world.elevation = (numpy.array(e_as_array).reshape(height, width), None) world.plates = numpy.array(p_as_array, dtype=numpy.uint16).reshape(height, width) return world -def world_gen(name, width, height, seed, temps=[.874, .765, .594, .439, .366, .124], - humids=[.941, .778, .507, .236, 0.073, .014, .002], num_plates=10, - ocean_level=1.0, step=Step.full(), gamma_curve=1.25, curve_offset=.2, - fade_borders=True, verbose=get_verbose()): +def world_gen( + name, + width, + height, + seed, + temps=[0.874, 0.765, 0.594, 0.439, 0.366, 0.124], + humids=[0.941, 0.778, 0.507, 0.236, 0.073, 0.014, 0.002], + num_plates=10, + ocean_level=1.0, + step=Step.full(), + gamma_curve=1.25, + curve_offset=0.2, + fade_borders=True, + verbose=get_verbose(), +): if verbose: start_time = time.time() - world = _plates_simulation(name, width, height, seed, temps, humids, gamma_curve, - curve_offset, num_plates, ocean_level, step, verbose) + world = _plates_simulation( + name, width, height, seed, temps, humids, gamma_curve, curve_offset, num_plates, ocean_level, step, verbose + ) center_land(world) if verbose: elapsed_time = time.time() - start_time - print("...plates.world_gen: set_elevation, set_plates, center_land " + - "complete. Elapsed time " + str(elapsed_time) + " seconds.") + print( + "...plates.world_gen: set_elevation, set_plates, center_land " + + "complete. Elapsed time " + + str(elapsed_time) + + " seconds." + ) if verbose: start_time = time.time() - add_noise_to_elevation(world, numpy.random.randint(0, 4096)) # uses the global RNG; this is the very first call to said RNG - should that change, this needs to be taken care of + add_noise_to_elevation( + world, numpy.random.randint(0, 4096) + ) # uses the global RNG; this is the very first call to said RNG - should that change, this needs to be taken care of if verbose: elapsed_time = time.time() - start_time - print("...plates.world_gen: elevation noise added. Elapsed time " + - str(elapsed_time) + " seconds.") + print("...plates.world_gen: elevation noise added. Elapsed time " + str(elapsed_time) + " seconds.") if verbose: start_time = time.time() @@ -82,7 +136,6 @@ def world_gen(name, width, height, seed, temps=[.874, .765, .594, .439, .366, .1 initialize_ocean_and_thresholds(world) if verbose: elapsed_time = time.time() - start_time - print("...plates.world_gen: oceans initialized. Elapsed time " + - str(elapsed_time) + " seconds.") + print("...plates.world_gen: oceans initialized. Elapsed time " + str(elapsed_time) + " seconds.") return generate_world(world, step) diff --git a/worldengine/protobuf/World_pb2.py b/worldengine/protobuf/World_pb2.py index e48b3371..3b6b09ba 100644 --- a/worldengine/protobuf/World_pb2.py +++ b/worldengine/protobuf/World_pb2.py @@ -1,758 +1,50 @@ # Generated by the protocol buffer compiler. DO NOT EDIT! +# NO CHECKED-IN PROTOBUF GENCODE # source: World.proto -# pylint: skip-file +# Protobuf Python Version: 6.33.1 +"""Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor -from google.protobuf import message as _message -from google.protobuf import reflection as _reflection +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import runtime_version as _runtime_version from google.protobuf import symbol_database as _symbol_database -# from google.protobuf import descriptor_pb2 +from google.protobuf.internal import builder as _builder + +_runtime_version.ValidateProtobufRuntimeVersion(_runtime_version.Domain.PUBLIC, 6, 33, 1, "", "World.proto") # @@protoc_insertion_point(imports) _sym_db = _symbol_database.Default() - - -DESCRIPTOR = _descriptor.FileDescriptor( - name='World.proto', - package='World', - syntax='proto2', - serialized_pb=b'\n\x0bWorld.proto\x12\x05World\"\xf6\r\n\x05World\x12\x17\n\x0fworldengine_tag\x18\x01 \x02(\x05\x12\x1b\n\x13worldengine_version\x18\x02 \x02(\x05\x12\x0c\n\x04name\x18\x03 \x02(\t\x12\r\n\x05width\x18\x04 \x02(\x05\x12\x0e\n\x06height\x18\x05 \x02(\x05\x12\x30\n\rheightMapData\x18\x06 \x02(\x0b\x32\x19.World.World.DoubleMatrix\x12\x17\n\x0fheightMapTh_sea\x18\x07 \x02(\x01\x12\x19\n\x11heightMapTh_plain\x18\x08 \x02(\x01\x12\x18\n\x10heightMapTh_hill\x18\t \x02(\x01\x12*\n\x06plates\x18\n \x02(\x0b\x32\x1a.World.World.IntegerMatrix\x12)\n\x05ocean\x18\x0b \x02(\x0b\x32\x1a.World.World.BooleanMatrix\x12,\n\tsea_depth\x18\x0c \x02(\x0b\x32\x19.World.World.DoubleMatrix\x12)\n\x05\x62iome\x18\r \x01(\x0b\x32\x1a.World.World.IntegerMatrix\x12\x38\n\x08humidity\x18\x0e \x01(\x0b\x32&.World.World.DoubleMatrixWithQuantiles\x12-\n\nirrigation\x18\x0f \x01(\x0b\x32\x19.World.World.DoubleMatrix\x12\x33\n\x10permeabilityData\x18\x10 \x01(\x0b\x32\x19.World.World.DoubleMatrix\x12\x18\n\x10permeability_low\x18\x11 \x01(\x01\x12\x18\n\x10permeability_med\x18\x12 \x01(\x01\x12/\n\x0cwatermapData\x18\x13 \x01(\x0b\x32\x19.World.World.DoubleMatrix\x12\x16\n\x0ewatermap_creek\x18\x14 \x01(\x01\x12\x16\n\x0ewatermap_river\x18\x15 \x01(\x01\x12\x1a\n\x12watermap_mainriver\x18\x16 \x01(\x01\x12\x34\n\x11precipitationData\x18\x17 \x01(\x0b\x32\x19.World.World.DoubleMatrix\x12\x19\n\x11precipitation_low\x18\x18 \x01(\x01\x12\x19\n\x11precipitation_med\x18\x19 \x01(\x01\x12\x32\n\x0ftemperatureData\x18\x1a \x01(\x0b\x32\x19.World.World.DoubleMatrix\x12\x19\n\x11temperature_polar\x18\x1b \x01(\x01\x12\x1a\n\x12temperature_alpine\x18\x1c \x01(\x01\x12\x1a\n\x12temperature_boreal\x18\x1d \x01(\x01\x12\x18\n\x10temperature_cool\x18\x1e \x01(\x01\x12\x18\n\x10temperature_warm\x18\x1f \x01(\x01\x12\x1f\n\x17temperature_subtropical\x18 \x01(\x01\x12\x33\n\x0egenerationData\x18! \x01(\x0b\x32\x1b.World.World.GenerationData\x12*\n\x07lakemap\x18\" \x01(\x0b\x32\x19.World.World.DoubleMatrix\x12+\n\x08rivermap\x18# \x01(\x0b\x32\x19.World.World.DoubleMatrix\x12)\n\x06icecap\x18$ \x01(\x0b\x32\x19.World.World.DoubleMatrix\x1a\x1a\n\tDoubleRow\x12\r\n\x05\x63\x65lls\x18\x01 \x03(\x01\x1a\x1b\n\nBooleanRow\x12\r\n\x05\x63\x65lls\x18\x01 \x03(\x08\x1a\x1b\n\nIntegerRow\x12\r\n\x05\x63\x65lls\x18\x01 \x03(\x05\x1a\x18\n\x07\x42yteRow\x12\r\n\x05\x63\x65lls\x18\x01 \x03(\x05\x1a\x34\n\x0c\x44oubleMatrix\x12$\n\x04rows\x18\x01 \x03(\x0b\x32\x16.World.World.DoubleRow\x1a\x36\n\rBooleanMatrix\x12%\n\x04rows\x18\x01 \x03(\x0b\x32\x17.World.World.BooleanRow\x1a\x36\n\rIntegerMatrix\x12%\n\x04rows\x18\x01 \x03(\x0b\x32\x17.World.World.IntegerRow\x1a,\n\x0e\x44oubleQuantile\x12\x0b\n\x03key\x18\x01 \x02(\x05\x12\r\n\x05value\x18\x02 \x02(\x01\x1aq\n\x19\x44oubleMatrixWithQuantiles\x12.\n\tquantiles\x18\x01 \x03(\x0b\x32\x1b.World.World.DoubleQuantile\x12$\n\x04rows\x18\x02 \x03(\x0b\x32\x16.World.World.DoubleRow\x1aS\n\x0eGenerationData\x12\x0c\n\x04seed\x18\x01 \x01(\x05\x12\x10\n\x08n_plates\x18\x02 \x01(\x05\x12\x13\n\x0bocean_level\x18\x03 \x01(\x02\x12\x0c\n\x04step\x18\x04 \x01(\t' -) -_sym_db.RegisterFileDescriptor(DESCRIPTOR) - - - - -_WORLD_DOUBLEROW = _descriptor.Descriptor( - name='DoubleRow', - full_name='World.World.DoubleRow', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='cells', full_name='World.World.DoubleRow.cells', index=0, - number=1, type=1, cpp_type=5, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - options=None, - is_extendable=False, - syntax='proto2', - extension_ranges=[], - oneofs=[ - ], - serialized_start=1283, - serialized_end=1309, -) - -_WORLD_BOOLEANROW = _descriptor.Descriptor( - name='BooleanRow', - full_name='World.World.BooleanRow', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='cells', full_name='World.World.BooleanRow.cells', index=0, - number=1, type=8, cpp_type=7, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - options=None, - is_extendable=False, - syntax='proto2', - extension_ranges=[], - oneofs=[ - ], - serialized_start=1311, - serialized_end=1338, -) - -_WORLD_INTEGERROW = _descriptor.Descriptor( - name='IntegerRow', - full_name='World.World.IntegerRow', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='cells', full_name='World.World.IntegerRow.cells', index=0, - number=1, type=5, cpp_type=1, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - options=None, - is_extendable=False, - syntax='proto2', - extension_ranges=[], - oneofs=[ - ], - serialized_start=1340, - serialized_end=1367, -) - -_WORLD_BYTEROW = _descriptor.Descriptor( - name='ByteRow', - full_name='World.World.ByteRow', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='cells', full_name='World.World.ByteRow.cells', index=0, - number=1, type=5, cpp_type=1, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - options=None, - is_extendable=False, - syntax='proto2', - extension_ranges=[], - oneofs=[ - ], - serialized_start=1369, - serialized_end=1393, -) - -_WORLD_DOUBLEMATRIX = _descriptor.Descriptor( - name='DoubleMatrix', - full_name='World.World.DoubleMatrix', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='rows', full_name='World.World.DoubleMatrix.rows', index=0, - number=1, type=11, cpp_type=10, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - options=None, - is_extendable=False, - syntax='proto2', - extension_ranges=[], - oneofs=[ - ], - serialized_start=1395, - serialized_end=1447, -) - -_WORLD_BOOLEANMATRIX = _descriptor.Descriptor( - name='BooleanMatrix', - full_name='World.World.BooleanMatrix', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='rows', full_name='World.World.BooleanMatrix.rows', index=0, - number=1, type=11, cpp_type=10, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - options=None, - is_extendable=False, - syntax='proto2', - extension_ranges=[], - oneofs=[ - ], - serialized_start=1449, - serialized_end=1503, -) - -_WORLD_INTEGERMATRIX = _descriptor.Descriptor( - name='IntegerMatrix', - full_name='World.World.IntegerMatrix', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='rows', full_name='World.World.IntegerMatrix.rows', index=0, - number=1, type=11, cpp_type=10, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - options=None, - is_extendable=False, - syntax='proto2', - extension_ranges=[], - oneofs=[ - ], - serialized_start=1505, - serialized_end=1559, -) - -_WORLD_DOUBLEQUANTILE = _descriptor.Descriptor( - name='DoubleQuantile', - full_name='World.World.DoubleQuantile', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='key', full_name='World.World.DoubleQuantile.key', index=0, - number=1, type=5, cpp_type=1, label=2, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='value', full_name='World.World.DoubleQuantile.value', index=1, - number=2, type=1, cpp_type=5, label=2, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - options=None, - is_extendable=False, - syntax='proto2', - extension_ranges=[], - oneofs=[ - ], - serialized_start=1561, - serialized_end=1605, -) - -_WORLD_DOUBLEMATRIXWITHQUANTILES = _descriptor.Descriptor( - name='DoubleMatrixWithQuantiles', - full_name='World.World.DoubleMatrixWithQuantiles', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='quantiles', full_name='World.World.DoubleMatrixWithQuantiles.quantiles', index=0, - number=1, type=11, cpp_type=10, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='rows', full_name='World.World.DoubleMatrixWithQuantiles.rows', index=1, - number=2, type=11, cpp_type=10, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - options=None, - is_extendable=False, - syntax='proto2', - extension_ranges=[], - oneofs=[ - ], - serialized_start=1607, - serialized_end=1720, -) - -_WORLD_GENERATIONDATA = _descriptor.Descriptor( - name='GenerationData', - full_name='World.World.GenerationData', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='seed', full_name='World.World.GenerationData.seed', index=0, - number=1, type=5, cpp_type=1, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='n_plates', full_name='World.World.GenerationData.n_plates', index=1, - number=2, type=5, cpp_type=1, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='ocean_level', full_name='World.World.GenerationData.ocean_level', index=2, - number=3, type=2, cpp_type=6, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='step', full_name='World.World.GenerationData.step', index=3, - number=4, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=b"".decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - options=None, - is_extendable=False, - syntax='proto2', - extension_ranges=[], - oneofs=[ - ], - serialized_start=1722, - serialized_end=1805, -) - -_WORLD = _descriptor.Descriptor( - name='World', - full_name='World.World', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='worldengine_tag', full_name='World.World.worldengine_tag', index=0, - number=1, type=5, cpp_type=1, label=2, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='worldengine_version', full_name='World.World.worldengine_version', index=1, - number=2, type=5, cpp_type=1, label=2, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='name', full_name='World.World.name', index=2, - number=3, type=9, cpp_type=9, label=2, - has_default_value=False, default_value=b"".decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='width', full_name='World.World.width', index=3, - number=4, type=5, cpp_type=1, label=2, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='height', full_name='World.World.height', index=4, - number=5, type=5, cpp_type=1, label=2, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='heightMapData', full_name='World.World.heightMapData', index=5, - number=6, type=11, cpp_type=10, label=2, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='heightMapTh_sea', full_name='World.World.heightMapTh_sea', index=6, - number=7, type=1, cpp_type=5, label=2, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='heightMapTh_plain', full_name='World.World.heightMapTh_plain', index=7, - number=8, type=1, cpp_type=5, label=2, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='heightMapTh_hill', full_name='World.World.heightMapTh_hill', index=8, - number=9, type=1, cpp_type=5, label=2, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='plates', full_name='World.World.plates', index=9, - number=10, type=11, cpp_type=10, label=2, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='ocean', full_name='World.World.ocean', index=10, - number=11, type=11, cpp_type=10, label=2, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='sea_depth', full_name='World.World.sea_depth', index=11, - number=12, type=11, cpp_type=10, label=2, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='biome', full_name='World.World.biome', index=12, - number=13, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='humidity', full_name='World.World.humidity', index=13, - number=14, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='irrigation', full_name='World.World.irrigation', index=14, - number=15, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='permeabilityData', full_name='World.World.permeabilityData', index=15, - number=16, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='permeability_low', full_name='World.World.permeability_low', index=16, - number=17, type=1, cpp_type=5, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='permeability_med', full_name='World.World.permeability_med', index=17, - number=18, type=1, cpp_type=5, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='watermapData', full_name='World.World.watermapData', index=18, - number=19, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='watermap_creek', full_name='World.World.watermap_creek', index=19, - number=20, type=1, cpp_type=5, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='watermap_river', full_name='World.World.watermap_river', index=20, - number=21, type=1, cpp_type=5, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='watermap_mainriver', full_name='World.World.watermap_mainriver', index=21, - number=22, type=1, cpp_type=5, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='precipitationData', full_name='World.World.precipitationData', index=22, - number=23, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='precipitation_low', full_name='World.World.precipitation_low', index=23, - number=24, type=1, cpp_type=5, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='precipitation_med', full_name='World.World.precipitation_med', index=24, - number=25, type=1, cpp_type=5, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='temperatureData', full_name='World.World.temperatureData', index=25, - number=26, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='temperature_polar', full_name='World.World.temperature_polar', index=26, - number=27, type=1, cpp_type=5, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='temperature_alpine', full_name='World.World.temperature_alpine', index=27, - number=28, type=1, cpp_type=5, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='temperature_boreal', full_name='World.World.temperature_boreal', index=28, - number=29, type=1, cpp_type=5, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='temperature_cool', full_name='World.World.temperature_cool', index=29, - number=30, type=1, cpp_type=5, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='temperature_warm', full_name='World.World.temperature_warm', index=30, - number=31, type=1, cpp_type=5, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='temperature_subtropical', full_name='World.World.temperature_subtropical', index=31, - number=32, type=1, cpp_type=5, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='generationData', full_name='World.World.generationData', index=32, - number=33, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='lakemap', full_name='World.World.lakemap', index=33, - number=34, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='rivermap', full_name='World.World.rivermap', index=34, - number=35, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='icecap', full_name='World.World.icecap', index=35, - number=36, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - ], - extensions=[ - ], - nested_types=[_WORLD_DOUBLEROW, _WORLD_BOOLEANROW, _WORLD_INTEGERROW, _WORLD_BYTEROW, _WORLD_DOUBLEMATRIX, _WORLD_BOOLEANMATRIX, _WORLD_INTEGERMATRIX, _WORLD_DOUBLEQUANTILE, _WORLD_DOUBLEMATRIXWITHQUANTILES, _WORLD_GENERATIONDATA, ], - enum_types=[ - ], - options=None, - is_extendable=False, - syntax='proto2', - extension_ranges=[], - oneofs=[ - ], - serialized_start=23, - serialized_end=1805, -) - -_WORLD_DOUBLEROW.containing_type = _WORLD -_WORLD_BOOLEANROW.containing_type = _WORLD -_WORLD_INTEGERROW.containing_type = _WORLD -_WORLD_BYTEROW.containing_type = _WORLD -_WORLD_DOUBLEMATRIX.fields_by_name['rows'].message_type = _WORLD_DOUBLEROW -_WORLD_DOUBLEMATRIX.containing_type = _WORLD -_WORLD_BOOLEANMATRIX.fields_by_name['rows'].message_type = _WORLD_BOOLEANROW -_WORLD_BOOLEANMATRIX.containing_type = _WORLD -_WORLD_INTEGERMATRIX.fields_by_name['rows'].message_type = _WORLD_INTEGERROW -_WORLD_INTEGERMATRIX.containing_type = _WORLD -_WORLD_DOUBLEQUANTILE.containing_type = _WORLD -_WORLD_DOUBLEMATRIXWITHQUANTILES.fields_by_name['quantiles'].message_type = _WORLD_DOUBLEQUANTILE -_WORLD_DOUBLEMATRIXWITHQUANTILES.fields_by_name['rows'].message_type = _WORLD_DOUBLEROW -_WORLD_DOUBLEMATRIXWITHQUANTILES.containing_type = _WORLD -_WORLD_GENERATIONDATA.containing_type = _WORLD -_WORLD.fields_by_name['heightMapData'].message_type = _WORLD_DOUBLEMATRIX -_WORLD.fields_by_name['plates'].message_type = _WORLD_INTEGERMATRIX -_WORLD.fields_by_name['ocean'].message_type = _WORLD_BOOLEANMATRIX -_WORLD.fields_by_name['sea_depth'].message_type = _WORLD_DOUBLEMATRIX -_WORLD.fields_by_name['biome'].message_type = _WORLD_INTEGERMATRIX -_WORLD.fields_by_name['humidity'].message_type = _WORLD_DOUBLEMATRIXWITHQUANTILES -_WORLD.fields_by_name['irrigation'].message_type = _WORLD_DOUBLEMATRIX -_WORLD.fields_by_name['permeabilityData'].message_type = _WORLD_DOUBLEMATRIX -_WORLD.fields_by_name['watermapData'].message_type = _WORLD_DOUBLEMATRIX -_WORLD.fields_by_name['precipitationData'].message_type = _WORLD_DOUBLEMATRIX -_WORLD.fields_by_name['temperatureData'].message_type = _WORLD_DOUBLEMATRIX -_WORLD.fields_by_name['generationData'].message_type = _WORLD_GENERATIONDATA -_WORLD.fields_by_name['lakemap'].message_type = _WORLD_DOUBLEMATRIX -_WORLD.fields_by_name['rivermap'].message_type = _WORLD_DOUBLEMATRIX -_WORLD.fields_by_name['icecap'].message_type = _WORLD_DOUBLEMATRIX -DESCRIPTOR.message_types_by_name['World'] = _WORLD - -World = _reflection.GeneratedProtocolMessageType('World', (_message.Message,), dict( - - DoubleRow = _reflection.GeneratedProtocolMessageType('DoubleRow', (_message.Message,), dict( - DESCRIPTOR = _WORLD_DOUBLEROW, - __module__ = 'World_pb2' - # @@protoc_insertion_point(class_scope:World.World.DoubleRow) - )) - , - - BooleanRow = _reflection.GeneratedProtocolMessageType('BooleanRow', (_message.Message,), dict( - DESCRIPTOR = _WORLD_BOOLEANROW, - __module__ = 'World_pb2' - # @@protoc_insertion_point(class_scope:World.World.BooleanRow) - )) - , - - IntegerRow = _reflection.GeneratedProtocolMessageType('IntegerRow', (_message.Message,), dict( - DESCRIPTOR = _WORLD_INTEGERROW, - __module__ = 'World_pb2' - # @@protoc_insertion_point(class_scope:World.World.IntegerRow) - )) - , - - ByteRow = _reflection.GeneratedProtocolMessageType('ByteRow', (_message.Message,), dict( - DESCRIPTOR = _WORLD_BYTEROW, - __module__ = 'World_pb2' - # @@protoc_insertion_point(class_scope:World.World.ByteRow) - )) - , - - DoubleMatrix = _reflection.GeneratedProtocolMessageType('DoubleMatrix', (_message.Message,), dict( - DESCRIPTOR = _WORLD_DOUBLEMATRIX, - __module__ = 'World_pb2' - # @@protoc_insertion_point(class_scope:World.World.DoubleMatrix) - )) - , - - BooleanMatrix = _reflection.GeneratedProtocolMessageType('BooleanMatrix', (_message.Message,), dict( - DESCRIPTOR = _WORLD_BOOLEANMATRIX, - __module__ = 'World_pb2' - # @@protoc_insertion_point(class_scope:World.World.BooleanMatrix) - )) - , - - IntegerMatrix = _reflection.GeneratedProtocolMessageType('IntegerMatrix', (_message.Message,), dict( - DESCRIPTOR = _WORLD_INTEGERMATRIX, - __module__ = 'World_pb2' - # @@protoc_insertion_point(class_scope:World.World.IntegerMatrix) - )) - , - - DoubleQuantile = _reflection.GeneratedProtocolMessageType('DoubleQuantile', (_message.Message,), dict( - DESCRIPTOR = _WORLD_DOUBLEQUANTILE, - __module__ = 'World_pb2' - # @@protoc_insertion_point(class_scope:World.World.DoubleQuantile) - )) - , - - DoubleMatrixWithQuantiles = _reflection.GeneratedProtocolMessageType('DoubleMatrixWithQuantiles', (_message.Message,), dict( - DESCRIPTOR = _WORLD_DOUBLEMATRIXWITHQUANTILES, - __module__ = 'World_pb2' - # @@protoc_insertion_point(class_scope:World.World.DoubleMatrixWithQuantiles) - )) - , - - GenerationData = _reflection.GeneratedProtocolMessageType('GenerationData', (_message.Message,), dict( - DESCRIPTOR = _WORLD_GENERATIONDATA, - __module__ = 'World_pb2' - # @@protoc_insertion_point(class_scope:World.World.GenerationData) - )) - , - DESCRIPTOR = _WORLD, - __module__ = 'World_pb2' - # @@protoc_insertion_point(class_scope:World.World) - )) -_sym_db.RegisterMessage(World) -_sym_db.RegisterMessage(World.DoubleRow) -_sym_db.RegisterMessage(World.BooleanRow) -_sym_db.RegisterMessage(World.IntegerRow) -_sym_db.RegisterMessage(World.ByteRow) -_sym_db.RegisterMessage(World.DoubleMatrix) -_sym_db.RegisterMessage(World.BooleanMatrix) -_sym_db.RegisterMessage(World.IntegerMatrix) -_sym_db.RegisterMessage(World.DoubleQuantile) -_sym_db.RegisterMessage(World.DoubleMatrixWithQuantiles) -_sym_db.RegisterMessage(World.GenerationData) - - +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile( + b'\n\x0bWorld.proto\x12\x05World"\xf6\r\n\x05World\x12\x17\n\x0fworldengine_tag\x18\x01 \x02(\x05\x12\x1b\n\x13worldengine_version\x18\x02 \x02(\x05\x12\x0c\n\x04name\x18\x03 \x02(\t\x12\r\n\x05width\x18\x04 \x02(\x05\x12\x0e\n\x06height\x18\x05 \x02(\x05\x12\x30\n\rheightMapData\x18\x06 \x02(\x0b\x32\x19.World.World.DoubleMatrix\x12\x17\n\x0fheightMapTh_sea\x18\x07 \x02(\x01\x12\x19\n\x11heightMapTh_plain\x18\x08 \x02(\x01\x12\x18\n\x10heightMapTh_hill\x18\t \x02(\x01\x12*\n\x06plates\x18\n \x02(\x0b\x32\x1a.World.World.IntegerMatrix\x12)\n\x05ocean\x18\x0b \x02(\x0b\x32\x1a.World.World.BooleanMatrix\x12,\n\tsea_depth\x18\x0c \x02(\x0b\x32\x19.World.World.DoubleMatrix\x12)\n\x05\x62iome\x18\r \x01(\x0b\x32\x1a.World.World.IntegerMatrix\x12\x38\n\x08humidity\x18\x0e \x01(\x0b\x32&.World.World.DoubleMatrixWithQuantiles\x12-\n\nirrigation\x18\x0f \x01(\x0b\x32\x19.World.World.DoubleMatrix\x12\x33\n\x10permeabilityData\x18\x10 \x01(\x0b\x32\x19.World.World.DoubleMatrix\x12\x18\n\x10permeability_low\x18\x11 \x01(\x01\x12\x18\n\x10permeability_med\x18\x12 \x01(\x01\x12/\n\x0cwatermapData\x18\x13 \x01(\x0b\x32\x19.World.World.DoubleMatrix\x12\x16\n\x0ewatermap_creek\x18\x14 \x01(\x01\x12\x16\n\x0ewatermap_river\x18\x15 \x01(\x01\x12\x1a\n\x12watermap_mainriver\x18\x16 \x01(\x01\x12\x34\n\x11precipitationData\x18\x17 \x01(\x0b\x32\x19.World.World.DoubleMatrix\x12\x19\n\x11precipitation_low\x18\x18 \x01(\x01\x12\x19\n\x11precipitation_med\x18\x19 \x01(\x01\x12\x32\n\x0ftemperatureData\x18\x1a \x01(\x0b\x32\x19.World.World.DoubleMatrix\x12\x19\n\x11temperature_polar\x18\x1b \x01(\x01\x12\x1a\n\x12temperature_alpine\x18\x1c \x01(\x01\x12\x1a\n\x12temperature_boreal\x18\x1d \x01(\x01\x12\x18\n\x10temperature_cool\x18\x1e \x01(\x01\x12\x18\n\x10temperature_warm\x18\x1f \x01(\x01\x12\x1f\n\x17temperature_subtropical\x18 \x01(\x01\x12\x33\n\x0egenerationData\x18! \x01(\x0b\x32\x1b.World.World.GenerationData\x12*\n\x07lakemap\x18" \x01(\x0b\x32\x19.World.World.DoubleMatrix\x12+\n\x08rivermap\x18# \x01(\x0b\x32\x19.World.World.DoubleMatrix\x12)\n\x06icecap\x18$ \x01(\x0b\x32\x19.World.World.DoubleMatrix\x1a\x1a\n\tDoubleRow\x12\r\n\x05\x63\x65lls\x18\x01 \x03(\x01\x1a\x1b\n\nBooleanRow\x12\r\n\x05\x63\x65lls\x18\x01 \x03(\x08\x1a\x1b\n\nIntegerRow\x12\r\n\x05\x63\x65lls\x18\x01 \x03(\x05\x1a\x18\n\x07\x42yteRow\x12\r\n\x05\x63\x65lls\x18\x01 \x03(\x05\x1a\x34\n\x0c\x44oubleMatrix\x12$\n\x04rows\x18\x01 \x03(\x0b\x32\x16.World.World.DoubleRow\x1a\x36\n\rBooleanMatrix\x12%\n\x04rows\x18\x01 \x03(\x0b\x32\x17.World.World.BooleanRow\x1a\x36\n\rIntegerMatrix\x12%\n\x04rows\x18\x01 \x03(\x0b\x32\x17.World.World.IntegerRow\x1a,\n\x0e\x44oubleQuantile\x12\x0b\n\x03key\x18\x01 \x02(\x05\x12\r\n\x05value\x18\x02 \x02(\x01\x1aq\n\x19\x44oubleMatrixWithQuantiles\x12.\n\tquantiles\x18\x01 \x03(\x0b\x32\x1b.World.World.DoubleQuantile\x12$\n\x04rows\x18\x02 \x03(\x0b\x32\x16.World.World.DoubleRow\x1aS\n\x0eGenerationData\x12\x0c\n\x04seed\x18\x01 \x01(\x05\x12\x10\n\x08n_plates\x18\x02 \x01(\x05\x12\x13\n\x0bocean_level\x18\x03 \x01(\x02\x12\x0c\n\x04step\x18\x04 \x01(\t' +) + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, "World_pb2", _globals) +if not _descriptor._USE_C_DESCRIPTORS: + DESCRIPTOR._loaded_options = None + _globals["_WORLD"]._serialized_start = 23 + _globals["_WORLD"]._serialized_end = 1805 + _globals["_WORLD_DOUBLEROW"]._serialized_start = 1283 + _globals["_WORLD_DOUBLEROW"]._serialized_end = 1309 + _globals["_WORLD_BOOLEANROW"]._serialized_start = 1311 + _globals["_WORLD_BOOLEANROW"]._serialized_end = 1338 + _globals["_WORLD_INTEGERROW"]._serialized_start = 1340 + _globals["_WORLD_INTEGERROW"]._serialized_end = 1367 + _globals["_WORLD_BYTEROW"]._serialized_start = 1369 + _globals["_WORLD_BYTEROW"]._serialized_end = 1393 + _globals["_WORLD_DOUBLEMATRIX"]._serialized_start = 1395 + _globals["_WORLD_DOUBLEMATRIX"]._serialized_end = 1447 + _globals["_WORLD_BOOLEANMATRIX"]._serialized_start = 1449 + _globals["_WORLD_BOOLEANMATRIX"]._serialized_end = 1503 + _globals["_WORLD_INTEGERMATRIX"]._serialized_start = 1505 + _globals["_WORLD_INTEGERMATRIX"]._serialized_end = 1559 + _globals["_WORLD_DOUBLEQUANTILE"]._serialized_start = 1561 + _globals["_WORLD_DOUBLEQUANTILE"]._serialized_end = 1605 + _globals["_WORLD_DOUBLEMATRIXWITHQUANTILES"]._serialized_start = 1607 + _globals["_WORLD_DOUBLEMATRIXWITHQUANTILES"]._serialized_end = 1720 + _globals["_WORLD_GENERATIONDATA"]._serialized_start = 1722 + _globals["_WORLD_GENERATIONDATA"]._serialized_end = 1805 # @@protoc_insertion_point(module_scope) diff --git a/worldengine/simulations/basic.py b/worldengine/simulations/basic.py index fdbbdda9..a6a68d60 100644 --- a/worldengine/simulations/basic.py +++ b/worldengine/simulations/basic.py @@ -1,25 +1,25 @@ import numpy -def find_threshold(map_data, land_percentage, ocean=None):#never used anywhere? +def find_threshold(map_data, land_percentage, ocean=None): # never used anywhere? height, width = map_data.shape - #maybe map was already masked when we got it; if not, this will make sure we operate on a mask + # maybe map was already masked when we got it; if not, this will make sure we operate on a mask mask = numpy.ma.array(map_data, mask=False, keep_mask=True) if ocean is not None: if ocean.shape != map_data.shape: raise Exception( - "Dimension of map and ocean do not match. " + - "Map is %d x %d, while ocean is %d x%d" % ( - width, height, ocean.shape[1], ocean.shape[0])) + "Dimension of map and ocean do not match. " + + "Map is %d x %d, while ocean is %d x%d" % (width, height, ocean.shape[1], ocean.shape[0]) + ) mask = numpy.ma.array(mask, mask=ocean, keep_mask=True) def count(e): return numpy.ma.masked_less_equal(mask, e).count() def search(a, b, desired): - if (not type(a) == int) or (not type(b) == int): + if not isinstance(a, int) or not isinstance(b, int): raise Exception("A and B should be int") if a == b: return a @@ -47,15 +47,15 @@ def search(a, b, desired): def find_threshold_f(map_data, land_perc, ocean=None, max=1000.0, mindist=0.005): height, width = map_data.shape - #maybe map was already masked when we got it; if not, this will make sure we operate on a mask + # maybe map was already masked when we got it; if not, this will make sure we operate on a mask mask = numpy.ma.array(map_data, mask=False, keep_mask=True) if ocean is not None: if ocean.shape != map_data.shape: raise Exception( - "Dimension of map_data and ocean do not match. " + - "Map is %d x %d, while ocean is %d x%d" % ( - width, height, ocean.shape[1], ocean.shape[0])) + "Dimension of map_data and ocean do not match. " + + "Map is %d x %d, while ocean is %d x%d" % (width, height, ocean.shape[1], ocean.shape[0]) + ) mask = numpy.ma.array(mask, mask=ocean, keep_mask=True) def count(e): @@ -82,4 +82,4 @@ def search(a, b, desired): all_land = mask.count() desired_land = all_land * land_perc - return search(-1*max, max, desired_land) + return search(-1 * max, max, desired_land) diff --git a/worldengine/simulations/biome.py b/worldengine/simulations/biome.py index 5cab0720..daa68137 100644 --- a/worldengine/simulations/biome.py +++ b/worldengine/simulations/biome.py @@ -1,8 +1,7 @@ import numpy -class BiomeSimulation(object): - +class BiomeSimulation: @staticmethod def is_applicable(world): return world.has_humidity() and world.has_temperature() and not world.has_biome() @@ -13,7 +12,7 @@ def execute(world, seed): w = world width = world.width height = world.height - ocean = world.layers['ocean'].data + ocean = world.layers["ocean"].data cm = {} biome_cm = {} # this is still kind of expensive memory-wise @@ -21,96 +20,96 @@ def execute(world, seed): for y in range(height): for x in range(width): if ocean[y, x]: - biome[y, x] = 'ocean' + biome[y, x] = "ocean" else: if w.is_temperature_polar((x, y)): if w.is_humidity_superarid((x, y)): - biome[y, x] = 'polar desert' + biome[y, x] = "polar desert" else: - biome[y, x] = 'ice' + biome[y, x] = "ice" elif w.is_temperature_alpine((x, y)): if w.is_humidity_superarid((x, y)): - biome[y, x] = 'subpolar dry tundra' + biome[y, x] = "subpolar dry tundra" elif w.is_humidity_perarid((x, y)): - biome[y, x] = 'subpolar moist tundra' + biome[y, x] = "subpolar moist tundra" elif w.is_humidity_arid((x, y)): - biome[y, x] = 'subpolar wet tundra' + biome[y, x] = "subpolar wet tundra" else: - biome[y, x] = 'subpolar rain tundra' + biome[y, x] = "subpolar rain tundra" elif w.is_temperature_boreal((x, y)): if w.is_humidity_superarid((x, y)): - biome[y, x] = 'boreal desert' + biome[y, x] = "boreal desert" elif w.is_humidity_perarid((x, y)): - biome[y, x] = 'boreal dry scrub' + biome[y, x] = "boreal dry scrub" elif w.is_humidity_arid((x, y)): - biome[y, x] = 'boreal moist forest' + biome[y, x] = "boreal moist forest" elif w.is_humidity_semiarid((x, y)): - biome[y, x] = 'boreal wet forest' + biome[y, x] = "boreal wet forest" else: - biome[y, x] = 'boreal rain forest' + biome[y, x] = "boreal rain forest" elif w.is_temperature_cool((x, y)): if w.is_humidity_superarid((x, y)): - biome[y, x] = 'cool temperate desert' + biome[y, x] = "cool temperate desert" elif w.is_humidity_perarid((x, y)): - biome[y, x] = 'cool temperate desert scrub' + biome[y, x] = "cool temperate desert scrub" elif w.is_humidity_arid((x, y)): - biome[y, x] = 'cool temperate steppe' + biome[y, x] = "cool temperate steppe" elif w.is_humidity_semiarid((x, y)): - biome[y, x] = 'cool temperate moist forest' + biome[y, x] = "cool temperate moist forest" elif w.is_humidity_subhumid((x, y)): - biome[y, x] = 'cool temperate wet forest' + biome[y, x] = "cool temperate wet forest" else: - biome[y, x] = 'cool temperate rain forest' + biome[y, x] = "cool temperate rain forest" elif w.is_temperature_warm((x, y)): if w.is_humidity_superarid((x, y)): - biome[y, x] = 'warm temperate desert' + biome[y, x] = "warm temperate desert" elif w.is_humidity_perarid((x, y)): - biome[y, x] = 'warm temperate desert scrub' + biome[y, x] = "warm temperate desert scrub" elif w.is_humidity_arid((x, y)): - biome[y, x] = 'warm temperate thorn scrub' + biome[y, x] = "warm temperate thorn scrub" elif w.is_humidity_semiarid((x, y)): - biome[y, x] = 'warm temperate dry forest' + biome[y, x] = "warm temperate dry forest" elif w.is_humidity_subhumid((x, y)): - biome[y, x] = 'warm temperate moist forest' + biome[y, x] = "warm temperate moist forest" elif w.is_humidity_humid((x, y)): - biome[y, x] = 'warm temperate wet forest' + biome[y, x] = "warm temperate wet forest" else: - biome[y, x] = 'warm temperate rain forest' + biome[y, x] = "warm temperate rain forest" elif w.is_temperature_subtropical((x, y)): if w.is_humidity_superarid((x, y)): - biome[y, x] = 'subtropical desert' + biome[y, x] = "subtropical desert" elif w.is_humidity_perarid((x, y)): - biome[y, x] = 'subtropical desert scrub' + biome[y, x] = "subtropical desert scrub" elif w.is_humidity_arid((x, y)): - biome[y, x] = 'subtropical thorn woodland' + biome[y, x] = "subtropical thorn woodland" elif w.is_humidity_semiarid((x, y)): - biome[y, x] = 'subtropical dry forest' + biome[y, x] = "subtropical dry forest" elif w.is_humidity_subhumid((x, y)): - biome[y, x] = 'subtropical moist forest' + biome[y, x] = "subtropical moist forest" elif w.is_humidity_humid((x, y)): - biome[y, x] = 'subtropical wet forest' + biome[y, x] = "subtropical wet forest" else: - biome[y, x] = 'subtropical rain forest' + biome[y, x] = "subtropical rain forest" elif w.is_temperature_tropical((x, y)): if w.is_humidity_superarid((x, y)): - biome[y, x] = 'tropical desert' + biome[y, x] = "tropical desert" elif w.is_humidity_perarid((x, y)): - biome[y, x] = 'tropical desert scrub' + biome[y, x] = "tropical desert scrub" elif w.is_humidity_arid((x, y)): - biome[y, x] = 'tropical thorn woodland' + biome[y, x] = "tropical thorn woodland" elif w.is_humidity_semiarid((x, y)): - biome[y, x] = 'tropical very dry forest' + biome[y, x] = "tropical very dry forest" elif w.is_humidity_subhumid((x, y)): - biome[y, x] = 'tropical dry forest' + biome[y, x] = "tropical dry forest" elif w.is_humidity_humid((x, y)): - biome[y, x] = 'tropical moist forest' + biome[y, x] = "tropical moist forest" elif w.is_humidity_perhumid((x, y)): - biome[y, x] = 'tropical wet forest' + biome[y, x] = "tropical wet forest" else: - biome[y, x] = 'tropical rain forest' + biome[y, x] = "tropical rain forest" else: - biome[y, x] = 'bare rock' - if not biome[y, x] in biome_cm: + biome[y, x] = "bare rock" + if biome[y, x] not in biome_cm: biome_cm[biome[y, x]] = 0 biome_cm[biome[y, x]] += 1 w.biome = biome diff --git a/worldengine/simulations/erosion.py b/worldengine/simulations/erosion.py index d9bbd5aa..8f80ab35 100644 --- a/worldengine/simulations/erosion.py +++ b/worldengine/simulations/erosion.py @@ -1,5 +1,7 @@ import math + import numpy + import worldengine.astar # Direction @@ -24,11 +26,11 @@ def overflow(value, max_value): def in_circle(radius, center_x, center_y, x, y): - square_dist = ((center_x - x) ** 2 + (center_y - y) ** 2) - return square_dist <= radius ** 2 + square_dist = (center_x - x) ** 2 + (center_y - y) ** 2 + return square_dist <= radius**2 -class ErosionSimulation(object): +class ErosionSimulation: def __init__(self): self.wrap = True @@ -59,7 +61,7 @@ def execute(self, world, seed): # step four: simulate erosion and updating river map for river in river_list: self.river_erosion(river, world) - self.rivermap_update(river, water_flow, river_map, world.layers['precipitation'].data) + self.rivermap_update(river, water_flow, river_map, world.layers["precipitation"].data) # step five: rivers with no paths to sea form lakes for lake in lake_list: @@ -97,7 +99,7 @@ def find_quick_path(self, river, world): # *** 1,2 *** x, y = river new_path = [] - lowest_elevation = world.layers['elevation'].data[y, x] + lowest_elevation = world.layers["elevation"].data[y, x] # lowestDirection = [0, 0] for dx, dy in DIR_NEIGHBORS: @@ -109,7 +111,7 @@ def find_quick_path(self, river, world): tx, ty = overflow(tx, world.width), overflow(ty, world.height) - elevation = world.layers['elevation'].data[ty, tx] + elevation = world.layers["elevation"].data[ty, tx] if elevation < lowest_elevation: if world.contains(temp_dir): @@ -137,7 +139,7 @@ def river_sources(world, water_flow, water_path): # above sea level are marked as 'sources'. for y in range(0, world.height - 1): for x in range(0, world.width - 1): - rain_fall = world.layers['precipitation'].data[y, x] + rain_fall = world.layers["precipitation"].data[y, x] water_flow[y, x] = rain_fall if water_path[y, x] == 0: @@ -146,10 +148,8 @@ def river_sources(world, water_flow, water_path): neighbour_seed_found = False # follow flow path to where it may lead while not neighbour_seed_found: - # have we found a seed? if world.is_mountain((cx, cy)) and water_flow[cy, cx] >= RIVER_TH: - # try not to create seeds around other seeds for seed in river_source_list: sx, sy = seed @@ -186,8 +186,7 @@ def river_flow(self, source, world, river_list, lake_list): for dx, dy in DIR_NEIGHBORS: ax, ay = x + dx, y + dy if self.wrap: - ax, ay = overflow(ax, world.width), overflow(ay, - world.height) + ax, ay = overflow(ax, world.width), overflow(ay, world.height) for river in river_list: if [ax, ay] in river: @@ -212,11 +211,11 @@ def river_flow(self, source, world, river_list, lake_list): current_location = quick_section continue # stop here and enter back into loop - is_wrapped, lower_elevation = self.findLowerElevation( - current_location, world) + is_wrapped, lower_elevation = self.findLowerElevation(current_location, world) if lower_elevation and not is_wrapped: lower_path = worldengine.astar.PathFinder().find( - world.layers['elevation'].data, current_location, lower_elevation) + world.layers["elevation"].data, current_location, lower_elevation + ) if lower_path: path += lower_path current_location = path[-1] @@ -230,9 +229,7 @@ def river_flow(self, source, world, river_list, lake_list): lx, ly = lower_elevation if x < 0 or y < 0 or x > world.width or y > world.height: - raise Exception( - "BUG: fix me... we shouldn't be here: %s %s" % ( - current_location, lower_elevation)) + raise Exception(f"BUG: fix me... we shouldn't be here: {current_location} {lower_elevation}") if not in_circle(max_radius, cx, cy, lx, cy): # are we wrapping on x axis? @@ -253,13 +250,10 @@ def river_flow(self, source, world, river_list, lake_list): ny = 0 # next step is wrapped around lx = nx = int((cx + lx) / 2) # move halfway else: - raise Exception( - "BUG: fix me... we are not in circle: %s %s" % ( - current_location, lower_elevation)) + raise Exception(f"BUG: fix me... we are not in circle: {current_location} {lower_elevation}") # find our way to the edge - edge_path = worldengine.astar.PathFinder().find( - world.layers['elevation'].data, [cx, cy], [lx, ly]) + edge_path = worldengine.astar.PathFinder().find(world.layers["elevation"].data, [cx, cy], [lx, ly]) if not edge_path: # can't find another other path, make it a lake lake_list.append(current_location) @@ -270,7 +264,8 @@ def river_flow(self, source, world, river_list, lake_list): # find our way to lowest position original found lower_path = worldengine.astar.PathFinder().find( - world.layers['elevation'].data, current_location, lower_elevation) + world.layers["elevation"].data, current_location, lower_elevation + ) path += lower_path current_location = path[-1] @@ -284,25 +279,25 @@ def river_flow(self, source, world, river_list, lake_list): return path def cleanUpFlow(self, river, world): - '''Validate that for each point in river is equal to or lower than the - last''' + """Validate that for each point in river is equal to or lower than the + last""" celevation = 1.0 for r in river: rx, ry = r - relevation = world.layers['elevation'].data[ry, rx] + relevation = world.layers["elevation"].data[ry, rx] if relevation <= celevation: celevation = relevation elif relevation > celevation: - world.layers['elevation'].data[ry, rx] = celevation + world.layers["elevation"].data[ry, rx] = celevation return river def findLowerElevation(self, source, world): - '''Try to find a lower elevation with in a range of an increasing - circle's radius and try to find the best path and return it''' + """Try to find a lower elevation with in a range of an increasing + circle's radius and try to find the best path and return it""" x, y = source currentRadius = 1 maxRadius = 40 - lowestElevation = world.layers['elevation'].data[y, x] + lowestElevation = world.layers["elevation"].data[y, x] destination = [] notFound = True isWrapped = False @@ -321,13 +316,12 @@ def findLowerElevation(self, source, world): if not in_circle(currentRadius, x, y, rx, ry): continue - rx, ry = overflow(rx, world.width), overflow(ry, - world.height) + rx, ry = overflow(rx, world.width), overflow(ry, world.height) # if utilities.outOfBounds([x+cx, y+cy], self.size): # print "Fixed:",x ,y, rx, ry - elevation = world.layers['elevation'].data[ry, rx] + elevation = world.layers["elevation"].data[ry, rx] # have we found a lower elevation? if elevation < lowestElevation: lowestElevation = elevation @@ -344,10 +338,10 @@ def findLowerElevation(self, source, world): return isWrapped, destination def river_erosion(self, river, world): - """ Simulate erosion in heightmap based on river path. - * current location must be equal to or less than previous location - * riverbed is carved out by % of volume/flow - * sides of river are also eroded to slope into riverbed. + """Simulate erosion in heightmap based on river path. + * current location must be equal to or less than previous location + * riverbed is carved out by % of volume/flow + * sides of river are also eroded to slope into riverbed. """ # erosion around river, create river valley @@ -356,8 +350,7 @@ def river_erosion(self, river, world): radius = 2 for x in range(rx - radius, rx + radius): for y in range(ry - radius, ry + radius): - if not self.wrap and not world.contains( - (x, y)): # ignore edges of map + if not self.wrap and not world.contains((x, y)): # ignore edges of map continue x, y = overflow(x, world.width), overflow(y, world.height) curve = 1.0 @@ -365,11 +358,10 @@ def river_erosion(self, river, world): continue if [x, y] in river: # ignore river itself continue - if world.layers['elevation'].data[y, x] <= world.layers['elevation'].data[ry, rx]: + if world.layers["elevation"].data[y, x] <= world.layers["elevation"].data[ry, rx]: # ignore areas lower than river itself continue - if not in_circle(radius, rx, ry, x, - y): # ignore things outside a circle + if not in_circle(radius, rx, ry, x, y): # ignore things outside a circle continue adx, ady = math.fabs(rx - x), math.fabs(ry - y) @@ -378,13 +370,12 @@ def river_erosion(self, river, world): elif adx == 2 or ady == 2: curve = 0.05 - diff = world.layers['elevation'].data[ry, rx] - world.layers['elevation'].data[y, x] - newElevation = world.layers['elevation'].data[y, x] + ( - diff * curve) - if newElevation <= world.layers['elevation'].data[ry, rx]: - print('newElevation is <= than river, fix me...') - newElevation = world.layers['elevation'].data[r, x] - world.layers['elevation'].data[y, x] = newElevation + diff = world.layers["elevation"].data[ry, rx] - world.layers["elevation"].data[y, x] + newElevation = world.layers["elevation"].data[y, x] + (diff * curve) + if newElevation <= world.layers["elevation"].data[ry, rx]: + print("newElevation is <= than river, fix me...") + newElevation = world.layers["elevation"].data[r, x] + world.layers["elevation"].data[y, x] = newElevation return def rivermap_update(self, river, water_flow, rivermap, precipitations): diff --git a/worldengine/simulations/humidity.py b/worldengine/simulations/humidity.py index 7a35f5d9..a9586d79 100644 --- a/worldengine/simulations/humidity.py +++ b/worldengine/simulations/humidity.py @@ -1,12 +1,10 @@ from worldengine.simulations.basic import find_threshold_f -import numpy -class HumiditySimulation(object): +class HumiditySimulation: @staticmethod def is_applicable(world): - return world.has_precipitations() and world.has_irrigation() and ( - not world.has_humidity()) + return world.has_precipitations() and world.has_irrigation() and (not world.has_humidity()) def execute(self, world, seed): assert seed is not None @@ -18,19 +16,21 @@ def _calculate(world): humids = world.humids precipitationWeight = 1.0 irrigationWeight = 3 - data = numpy.zeros((world.height, world.width), dtype=float) - data = (world.layers['precipitation'].data * precipitationWeight - world.layers['irrigation'].data * irrigationWeight)/(precipitationWeight + irrigationWeight) + data = ( + world.layers["precipitation"].data * precipitationWeight + - world.layers["irrigation"].data * irrigationWeight + ) / (precipitationWeight + irrigationWeight) # These were originally evenly spaced at 12.5% each but changing them # to a bell curve produced better results - ocean = world.layers['ocean'].data + ocean = world.layers["ocean"].data quantiles = {} - quantiles['12'] = find_threshold_f(data, humids[6], ocean) - quantiles['25'] = find_threshold_f(data, humids[5], ocean) - quantiles['37'] = find_threshold_f(data, humids[4], ocean) - quantiles['50'] = find_threshold_f(data, humids[3], ocean) - quantiles['62'] = find_threshold_f(data, humids[2], ocean) - quantiles['75'] = find_threshold_f(data, humids[1], ocean) - quantiles['87'] = find_threshold_f(data, humids[0], ocean) + quantiles["12"] = find_threshold_f(data, humids[6], ocean) + quantiles["25"] = find_threshold_f(data, humids[5], ocean) + quantiles["37"] = find_threshold_f(data, humids[4], ocean) + quantiles["50"] = find_threshold_f(data, humids[3], ocean) + quantiles["62"] = find_threshold_f(data, humids[2], ocean) + quantiles["75"] = find_threshold_f(data, humids[1], ocean) + quantiles["87"] = find_threshold_f(data, humids[0], ocean) return data, quantiles diff --git a/worldengine/simulations/hydrology.py b/worldengine/simulations/hydrology.py index 85acd46c..95423a11 100644 --- a/worldengine/simulations/hydrology.py +++ b/worldengine/simulations/hydrology.py @@ -1,9 +1,9 @@ -from worldengine.simulations.basic import find_threshold_f import numpy +from worldengine.simulations.basic import find_threshold_f -class WatermapSimulation(object): +class WatermapSimulation: @staticmethod def is_applicable(world): return world.has_precipitations() and (not world.has_watermap()) @@ -19,15 +19,15 @@ def droplet(world, pos, q, _watermap): if q < 0: return x, y = pos - pos_elev = world.layers['elevation'].data[y, x] + _watermap[y, x] + pos_elev = world.layers["elevation"].data[y, x] + _watermap[y, x] lowers = [] min_higher = None min_lower = None # pos_min_higher = None # TODO: no longer used? tot_lowers = 0 - for p in world.tiles_around((x, y)):#TODO: switch to numpy + for p in world.tiles_around((x, y)): # TODO: switch to numpy px, py = p - e = world.layers['elevation'].data[py, px] + _watermap[py, px] + e = world.layers["elevation"].data[py, px] + _watermap[py, px] if e < pos_elev: dq = int(pos_elev - e) << 2 if min_lower is None or e < min_lower: @@ -43,8 +43,8 @@ def droplet(world, pos, q, _watermap): # pos_min_higher = p if lowers: f = q / tot_lowers - for l in lowers: - s, p = l + for lower in lowers: + s, p = lower if not world.is_ocean(p): px, py = p ql = f * s @@ -68,13 +68,13 @@ def droplet(world, pos, q, _watermap): if land_sample[0] is not None: for i in range(n): - x, y = land_sample[2*i], land_sample[2*i+1] + x, y = land_sample[2 * i], land_sample[2 * i + 1] if world.precipitations_at((x, y)) > 0: droplet(world, (x, y), world.precipitations_at((x, y)), _watermap_data) - ocean = world.layers['ocean'].data + ocean = world.layers["ocean"].data thresholds = dict() - thresholds['creek'] = find_threshold_f(_watermap_data, 0.05, ocean=ocean) - thresholds['river'] = find_threshold_f(_watermap_data, 0.02, ocean=ocean) - thresholds['main river'] = find_threshold_f(_watermap_data, 0.007, ocean=ocean) + thresholds["creek"] = find_threshold_f(_watermap_data, 0.05, ocean=ocean) + thresholds["river"] = find_threshold_f(_watermap_data, 0.02, ocean=ocean) + thresholds["main river"] = find_threshold_f(_watermap_data, 0.007, ocean=ocean) return _watermap_data, thresholds diff --git a/worldengine/simulations/icecap.py b/worldengine/simulations/icecap.py index 7f7531d0..81bac2bb 100644 --- a/worldengine/simulations/icecap.py +++ b/worldengine/simulations/icecap.py @@ -1,7 +1,7 @@ import numpy -class IcecapSimulation(object): +class IcecapSimulation: """This class creates an "ice-map", i.e. a numpy array with positive values that describe the thickness of the ice at a certain spot of the world. Ice can appear wherever there is an ocean and the temperature is cold enough. @@ -26,8 +26,8 @@ def _calculate(world, seed): # width * height * sizeof(numpy.bool) (temporary) # constants for convenience (or performance) - ocean = world.layers['ocean'].data - temperature = world.layers['temperature'].data + ocean = world.layers["ocean"].data + temperature = world.layers["temperature"].data # primary constants (could be used as global variables at some point); # all values should be in [0, 1] @@ -45,7 +45,7 @@ def _calculate(world, seed): temp_min = temperature.min() # coldest spot in the world # upper temperature-limit for freezing effects - freeze_threshold = world.layers['temperature'].thresholds[0][1] + freeze_threshold = world.layers["temperature"].thresholds[0][1] # Cold biomes: TODO: find and pick most appropriate threshold # polar: self.temperature['thresholds'][0][1] # alpine: self.temperature['thresholds'][1][1] @@ -66,7 +66,9 @@ def _calculate(world, seed): for y in range(world.height): for x in range(world.width): - if world.is_ocean((x, y)): # or world.river_map[y, x] > 0 or world.lake_map[y, x] > 0 or world.watermap['data'][y, x] > 0: + if world.is_ocean( + (x, y) + ): # or world.river_map[y, x] > 0 or world.lake_map[y, x] > 0 or world.watermap['data'][y, x] > 0: t = temperature[y, x] if t - temp_min < freeze_threshold: # map temperature to freeze-chance (linear interpolation) @@ -76,7 +78,7 @@ def _calculate(world, seed): # count number of frozen/solid tiles around this one if 0 < x < world.width - 1 and 0 < y < world.height - 1: # exclude borders - surr_tiles = solid_map[y-1:y+2, x-1:x+2] + surr_tiles = solid_map[y - 1 : y + 2, x - 1 : x + 2] chance_mod = numpy.count_nonzero(surr_tiles) chance_mod -= 1 if solid_map[y, x] else 0 # remove center-tile (i.e. the current tile) diff --git a/worldengine/simulations/irrigation.py b/worldengine/simulations/irrigation.py index d4293dc1..4f934c3e 100644 --- a/worldengine/simulations/irrigation.py +++ b/worldengine/simulations/irrigation.py @@ -1,6 +1,7 @@ import numpy -class IrrigationSimulation(object): + +class IrrigationSimulation: @staticmethod def is_applicable(world): return world.has_watermap() and (not world.has_irrigation()) @@ -19,32 +20,34 @@ def _calculate(world): height = world.height radius = 10 - #create array of pre-calculated values -> less calculations + # create array of pre-calculated values -> less calculations d = numpy.arange(-radius, radius + 1, 1, dtype=float) - x, y = numpy.meshgrid(d, d)#x/y distances to array center - #calculate final matrix: ln(sqrt(x^2+y^2) + 1) + 1 + x, y = numpy.meshgrid(d, d) # x/y distances to array center + # calculate final matrix: ln(sqrt(x^2+y^2) + 1) + 1 logs = numpy.log1p(numpy.sqrt(numpy.square(x) + numpy.square(y))) + 1 - #create output array + # create output array values = numpy.zeros((height, width), dtype=float) - it_all = numpy.nditer(values, flags=['multi_index'], op_flags=['readonly']) + it_all = numpy.nditer(values, flags=["multi_index"], op_flags=["readonly"]) while not it_all.finished: x = it_all.multi_index[1] y = it_all.multi_index[0] if world.is_ocean((x, y)): - #coordinates used for the values-slice (tl = top-left etc.) - tl_v = (max(x - radius, 0) , max(y - radius, 0)) + # coordinates used for the values-slice (tl = top-left etc.) + tl_v = (max(x - radius, 0), max(y - radius, 0)) br_v = (min(x + radius, width - 1), min(y + radius, height - 1)) - #coordinates used for the logs-slice - tl_l = (max(radius - x, 0) , max(radius - y, 0)) + # coordinates used for the logs-slice + tl_l = (max(radius - x, 0), max(radius - y, 0)) br_l = (min(radius - x + width - 1, 2 * radius), min(radius - y + height - 1, 2 * radius)) - #extract the necessary parts of the arrays - logs_relevant = logs[tl_l[1]:br_l[1]+1, tl_l[0]:br_l[0]+1] + # extract the necessary parts of the arrays + logs_relevant = logs[tl_l[1] : br_l[1] + 1, tl_l[0] : br_l[0] + 1] - #finish calculation - values[tl_v[1]:br_v[1]+1, tl_v[0]:br_v[0]+1] += world.layers['watermap'].data[y, x] / logs_relevant + # finish calculation + values[tl_v[1] : br_v[1] + 1, tl_v[0] : br_v[0] + 1] += ( + world.layers["watermap"].data[y, x] / logs_relevant + ) it_all.iternext() diff --git a/worldengine/simulations/permeability.py b/worldengine/simulations/permeability.py index 203c3b1c..d4270653 100644 --- a/worldengine/simulations/permeability.py +++ b/worldengine/simulations/permeability.py @@ -1,21 +1,21 @@ -from worldengine.simulations.basic import find_threshold_f -from noise import snoise2 import numpy +from noise import snoise2 +from worldengine.simulations.basic import find_threshold_f -class PermeabilitySimulation(object): +class PermeabilitySimulation: @staticmethod def is_applicable(world): return not world.has_permeability() def execute(self, world, seed): perm = self._calculate(seed, world.width, world.height) - ocean = world.layers['ocean'].data + ocean = world.layers["ocean"].data perm_th = [ - ('low', find_threshold_f(perm, 0.75, ocean)), - ('med', find_threshold_f(perm, 0.25, ocean)), - ('hig', None) + ("low", find_threshold_f(perm, 0.75, ocean)), + ("med", find_threshold_f(perm, 0.25, ocean)), + ("hig", None), ] world.permeability = (perm, perm_th) @@ -29,7 +29,7 @@ def _calculate(seed, width, height): octaves = 6 freq = 64.0 * octaves - for y in range(0, height):#TODO: numpy optimization? + for y in range(0, height): # TODO: numpy optimization? # yscaled = float(y) / height # TODO: what is this? for x in range(0, width): n = snoise2(x / freq, y / freq, octaves, base=base) diff --git a/worldengine/simulations/precipitation.py b/worldengine/simulations/precipitation.py index ffe54f1e..31cd23c1 100644 --- a/worldengine/simulations/precipitation.py +++ b/worldengine/simulations/precipitation.py @@ -1,13 +1,13 @@ import time + import numpy from noise import snoise2 -from worldengine.simulations.basic import find_threshold_f from worldengine.common import get_verbose +from worldengine.simulations.basic import find_threshold_f -class PrecipitationSimulation(object): - +class PrecipitationSimulation: @staticmethod def is_applicable(world): return not world.has_precipitations() @@ -16,18 +16,16 @@ def execute(self, world, seed): if get_verbose(): start_time = time.time() pre_calculated = self._calculate(seed, world) - ocean = world.layers['ocean'].data + ocean = world.layers["ocean"].data ths = [ - ('low', find_threshold_f(pre_calculated, 0.75, ocean)), - ('med', find_threshold_f(pre_calculated, 0.3, ocean)), - ('hig', None) + ("low", find_threshold_f(pre_calculated, 0.75, ocean)), + ("med", find_threshold_f(pre_calculated, 0.3, ocean)), + ("hig", None), ] world.precipitation = (pre_calculated, ths) if get_verbose(): elapsed_time = time.time() - start_time - print( - "...precipitations calculated. Elapsed time %f seconds." - % elapsed_time) + print(f"...precipitations calculated. Elapsed time {elapsed_time:f} seconds.") @staticmethod def _calculate(seed, world): @@ -45,38 +43,39 @@ def _calculate(seed, world): octaves = 6 freq = 64.0 * octaves - n_scale = 1024 / float(height) #This is a variable I am adding. It exists - #so that worlds sharing a common seed but - #different sizes will have similar patterns + n_scale = 1024 / float(height) # This is a variable I am adding. It exists + # so that worlds sharing a common seed but + # different sizes will have similar patterns - for y in range(height):#TODO: numpy + for y in range(height): # TODO: numpy for x in range(width): n = snoise2((x * n_scale) / freq, (y * n_scale) / freq, octaves, base=base) # Added to allow noise pattern to wrap around right and left. if x < border: - n = (snoise2( (x * n_scale) / freq, (y * n_scale) / freq, octaves, - base=base) * x / border) + ( - snoise2(( (x * n_scale) + width) / freq, (y * n_scale) / freq, octaves, - base=base) * (border - x) / border) + n = (snoise2((x * n_scale) / freq, (y * n_scale) / freq, octaves, base=base) * x / border) + ( + snoise2(((x * n_scale) + width) / freq, (y * n_scale) / freq, octaves, base=base) + * (border - x) + / border + ) precipitations[y, x] = n - #find ranges + # find ranges min_precip = precipitations.min() max_precip = precipitations.max() - min_temp = world.layers['temperature'].min() - max_temp = world.layers['temperature'].max() - precip_delta = (max_precip - min_precip) - temp_delta = (max_temp - min_temp) + min_temp = world.layers["temperature"].min() + max_temp = world.layers["temperature"].max() + precip_delta = max_precip - min_precip + temp_delta = max_temp - min_temp - #normalize temperature and precipitation arrays - t = (world.layers['temperature'].data - min_temp) / temp_delta + # normalize temperature and precipitation arrays + t = (world.layers["temperature"].data - min_temp) / temp_delta p = (precipitations - min_precip) / precip_delta - #modify precipitation based on temperature + # modify precipitation based on temperature - #-------------------------------------------------------------------------------- + # -------------------------------------------------------------------------------- # # Ok, some explanation here because why the formula is doing this may be a # little confusing. We are going to generate a modified gamma curve based on @@ -96,16 +95,16 @@ def _calculate(seed, world): # raise or lower the value for f(t) at 1 it would have negligible impact after # renormalizing. # - #-------------------------------------------------------------------------------- + # -------------------------------------------------------------------------------- - curve = (numpy.power(t, curve_gamma) * (1-curve_bonus)) + curve_bonus + curve = (numpy.power(t, curve_gamma) * (1 - curve_bonus)) + curve_bonus precipitations = numpy.multiply(p, curve) # Renormalize precipitation because the precipitation # changes will probably not fully extend from -1 to 1. min_precip = precipitations.min() max_precip = precipitations.max() - precip_delta = (max_precip - min_precip) + precip_delta = max_precip - min_precip precipitations = (((precipitations - min_precip) / precip_delta) * 2) - 1 - + return precipitations diff --git a/worldengine/simulations/temperature.py b/worldengine/simulations/temperature.py index 1a49cd50..37543f21 100644 --- a/worldengine/simulations/temperature.py +++ b/worldengine/simulations/temperature.py @@ -1,28 +1,28 @@ -from worldengine.simulations.basic import find_threshold_f -from noise import snoise2 # http://nullege.com/codes/search/noise.snoise2 import numpy +from noise import snoise2 # http://nullege.com/codes/search/noise.snoise2 +from worldengine.simulations.basic import find_threshold_f -class TemperatureSimulation(object): +class TemperatureSimulation: @staticmethod def is_applicable(world): return not world.has_temperature() def execute(self, world, seed): - e = world.layers['elevation'].data + e = world.layers["elevation"].data ml = world.start_mountain_th() # returns how many percent of the world are mountains - ocean = world.layers['ocean'].data + ocean = world.layers["ocean"].data t = self._calculate(world, seed, e, ml) t_th = [ - ('polar', find_threshold_f(t, world.temps[0], ocean)), - ('alpine', find_threshold_f(t, world.temps[1], ocean)), - ('boreal', find_threshold_f(t, world.temps[2], ocean)), - ('cool', find_threshold_f(t, world.temps[3], ocean)), - ('warm', find_threshold_f(t, world.temps[4], ocean)), - ('subtropical', find_threshold_f(t, world.temps[5], ocean)), - ('tropical', None) + ("polar", find_threshold_f(t, world.temps[0], ocean)), + ("alpine", find_threshold_f(t, world.temps[1], ocean)), + ("boreal", find_threshold_f(t, world.temps[2], ocean)), + ("cool", find_threshold_f(t, world.temps[3], ocean)), + ("warm", find_threshold_f(t, world.temps[4], ocean)), + ("subtropical", find_threshold_f(t, world.temps[5], ocean)), + ("tropical", None), ] world.temperature = (t, t_th) @@ -35,7 +35,7 @@ def _calculate(world, seed, elevation, mountain_level): base = rng.randint(0, 4096) temp = numpy.zeros((height, width), dtype=float) - ''' + """ Set up variables to take care of some orbital parameters: distance_to_sun: -Earth-like planet = 1.0 -valid range between ~0.7 and ~1.3 @@ -54,11 +54,11 @@ def _calculate(world, seed, elevation, mountain_level): -a value of 0.5 here would refer to an angle of 90 degrees, Uranus-style see https://en.wikipedia.org/wiki/Uranus -this value should usually be in the range -0.15 < axial_tilt < 0.15 for a habitable planet - ''' + """ distance_to_sun_hwhm = 0.12 axial_tilt_hwhm = 0.07 - #derive parameters + # derive parameters distance_to_sun = rng.normal(loc=1.0, scale=distance_to_sun_hwhm / 1.177410023) distance_to_sun = max(0.1, distance_to_sun) # clamp value; no planets inside the star allowed distance_to_sun *= distance_to_sun # prepare for later usage; use inverse-square law @@ -76,25 +76,26 @@ def _calculate(world, seed, elevation, mountain_level): # map/linearly interpolate y_scaled to latitude measured from where the most sunlight hits the world: # 1.0 = hottest zone, 0.0 = coldest zone - latitude_factor = numpy.interp(y_scaled, [axial_tilt - 0.5, axial_tilt, axial_tilt + 0.5], - [0.0, 1.0, 0.0], left=0.0, right=0.0) + latitude_factor = numpy.interp( + y_scaled, [axial_tilt - 0.5, axial_tilt, axial_tilt + 0.5], [0.0, 1.0, 0.0], left=0.0, right=0.0 + ) for x in range(0, width): n = snoise2((x * n_scale) / freq, (y * n_scale) / freq, octaves, base=base) # Added to allow noise pattern to wrap around right and left. if x <= border: - n = (snoise2((x * n_scale) / freq, (y * n_scale) / freq, octaves, - base=base) * x / border) \ - + (snoise2(((x * n_scale) + width) / freq, (y * n_scale) / freq, octaves, - base=base) * (border - x) / border) + n = (snoise2((x * n_scale) / freq, (y * n_scale) / freq, octaves, base=base) * x / border) + ( + snoise2(((x * n_scale) + width) / freq, (y * n_scale) / freq, octaves, base=base) + * (border - x) + / border + ) t = (latitude_factor * 12 + n * 1) / 13.0 / distance_to_sun if elevation[y, x] > mountain_level: # vary temperature based on height if elevation[y, x] > (mountain_level + 29): altitude_factor = 0.033 else: - altitude_factor = 1.00 - ( - float(elevation[y, x] - mountain_level) / 30) + altitude_factor = 1.00 - (float(elevation[y, x] - mountain_level) / 30) t *= altitude_factor temp[y, x] = t diff --git a/worldengine/step.py b/worldengine/step.py index 9c9c1763..f5b799ec 100644 --- a/worldengine/step.py +++ b/worldengine/step.py @@ -1,7 +1,7 @@ -class Step(object): - """ A Step in the world generation process. - The process starts with plates simulation and go on through different - intermediate steps to reach the 'full' step. +class Step: + """A Step in the world generation process. + The process starts with plates simulation and go on through different + intermediate steps to reach the 'full' step. """ def __init__(self, name): @@ -19,7 +19,7 @@ def get_by_name(name): return Step.precipitations() elif name == "full": return Step.full() - raise Exception("Unknown step '%s'" % name) + raise Exception(f"Unknown step '{name}'") @classmethod def full(cls): @@ -50,5 +50,4 @@ def plates(cls): return cls._plates def __eq__(self, other): - return isinstance(other, self.__class__) and \ - self.__dict__ == other.__dict__ + return isinstance(other, self.__class__) and self.__dict__ == other.__dict__ diff --git a/worldengine/updating_protobuf_format.sh b/worldengine/updating_protobuf_format.sh old mode 100644 new mode 100755 index f3dbb45a..661ad2f0 --- a/worldengine/updating_protobuf_format.sh +++ b/worldengine/updating_protobuf_format.sh @@ -1 +1,22 @@ +#!/usr/bin/env bash + +# Script to regenerate Python protobuf code from World.proto +# Requires: protoc >= 3.19.0 + +set -e + +if ! command -v protoc &> /dev/null; then + echo "Error: protoc is not installed." + echo "Please install protobuf compiler:" + echo " macOS: brew install protobuf" + echo " Linux: sudo apt-get install protobuf-compiler" + echo " or download from: https://github.com/protocolbuffers/protobuf/releases" + exit 1 +fi + +PROTOC_VERSION=$(protoc --version | awk '{print $2}') +echo "Using protoc version: ${PROTOC_VERSION}" + protoc World.proto --python_out=protobuf + +echo "Successfully regenerated protobuf/World_pb2.py" diff --git a/worldengine/version.py b/worldengine/version.py index 482e4a19..11ac8e1a 100644 --- a/worldengine/version.py +++ b/worldengine/version.py @@ -1 +1 @@ -__version__ = '0.19.0' +__version__ = "0.19.0"