diff --git a/.github/dependabot.yml b/.github/dependabot.yml deleted file mode 100644 index f4f4a477..00000000 --- a/.github/dependabot.yml +++ /dev/null @@ -1,17 +0,0 @@ -version: 2 -updates: - - package-ecosystem: "github-actions" - directory: "/" - schedule: - interval: "weekly" - - - package-ecosystem: "docker" - directory: "/" - schedule: - interval: "weekly" - - - package-ecosystem: "uv" - - directory: "/" - schedule: - interval: "weekly" diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 652ec96c..0b255bf9 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -1,6 +1,10 @@ name: checks on: pull_request: + paths: + - "src/**" + - "tests/**" + - "pyproject.toml" workflow_dispatch: jobs: diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml new file mode 100644 index 00000000..e2da5265 --- /dev/null +++ b/.github/workflows/pytest.yml @@ -0,0 +1,46 @@ +name: pytest +on: + workflow_dispatch: + pull_request: + paths: + - "src/**" + - "tests/**" + - "pyproject.toml" + +permissions: + contents: write + pull-requests: write + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + + - name: Set up Python + uses: actions/setup-python@v6 + with: + python-version-file: "pyproject.toml" + + - name: Install uv + uses: astral-sh/setup-uv@v7 + with: + enable-cache: true + version: 0.*.* + + - name: Install dependencies + run: uv sync --locked + + - name: Run tests (with coverage) + run: | + uv run pytest \ + --junitxml=pytest.xml \ + --cov-report=term-missing:skip-covered \ + --cov=. \ + | tee pytest-coverage.txt + + - name: Pytest coverage comment + uses: MishaKav/pytest-coverage-comment@main + with: + pytest-coverage-path: ./pytest-coverage.txt + junitxml-path: ./pytest.xml diff --git a/.gitignore b/.gitignore index 0c28182b..ef4e3fe8 100644 --- a/.gitignore +++ b/.gitignore @@ -66,4 +66,5 @@ venv.bak/ # LSP config files pyrightconfig.json - +# dev biome.js - not required +biome.json diff --git a/Brewfile b/Brewfile new file mode 100644 index 00000000..e22b7963 --- /dev/null +++ b/Brewfile @@ -0,0 +1,2 @@ +brew "go-task" +brew "uv" diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..d986f5c5 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,83 @@ +# Contributing + +Thanks for you interest in contributing to this project. + +Main tools used in this repository: + +| Tool | Description | +| ------------------------------------------------ | ---------------------------------- | +| [astral/uv](https://github.com/astral-sh/uv) | Python project and package manager | +| [go-task/task](https://github.com/go-task/task) | Task Runner | +| [j178/prek](https://github.com/j178/prek) | pre-commit hook runner | +| [astral/ruff](https://github.com/astral-sh/ruff) | Formatting/Linting/LSP | +| [astral/ty](https://github.com/astral-sh/ty) | Type Checking | + +## AI Policy + +Do NOT use AI to create, generate or draft any direct communication such as Issues, Comments, PR Bodies, etc. + +You MUST fully understand and be able to explain what your changes do and how they interact with the codebase. + +## Development Setup + +Clone the repository: + +```bash +git clone https://github.com/rtuszik/photon-docker +cd photon-docker +``` + +#### Dependencies + +The Brewfile can be used in order to install `Task` and `uv` with `Homebrew` on MacOS and Linux + +```bash +brew bundle +``` + +On Windows or for other install methods, refer to the official documentation: + +- install [Task](https://taskfile.dev/docs/installation) +- install [uv](https://docs.astral.sh/uv/getting-started/installation) + +### Install Project + +```bash +# installs python project with uv with dev dependencies and hooks +task install +``` + +## Making Changes + +1. Create a feature branch from `dev`. +2. Make your changes. +3. Test your changes by building and running the Docker image: + ```bash + task rebuild + ``` + Verify that Photon starts successfully and OpenSearch is up. +4. Run checks: + ```bash + task check + task test + ``` +5. Commit and push to your fork. +6. Open a pull request to the upstream `dev` branch. + +## Code Quality + +- All code must pass checks done through `task check`. +- All changes must be tested with Docker. +- Avoid unnecessary comments in the code. + +To list available tasks: + +```bash +task +``` + +## Pull Requests + +- Target the `dev` branch +- Provide a clear description of changes +- Ensure all checks pass before requesting review diff --git a/Dockerfile b/Dockerfile index 73b1053c..2269713e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM eclipse-temurin:21.0.9_10-jre-noble +FROM eclipse-temurin:25.0.2_10-jre-noble # install astral uv COPY --from=ghcr.io/astral-sh/uv:0.8 /uv /usr/local/bin/ diff --git a/Makefile b/Makefile deleted file mode 100644 index 4c46f478..00000000 --- a/Makefile +++ /dev/null @@ -1,45 +0,0 @@ -PHOTON_VERSION := $(shell cat .last_release | tr -d '[:space:]') - -.PHONY: help check lint format typecheck deadcode rebuild clean - -help: - @echo "Available targets:" - @echo " make check - Run all quality checks (lint, format, typecheck, deadcode)" - @echo " make lint - Run ruff linter with auto-fix" - @echo " make format - Run ruff formatter" - @echo " make typecheck - Run ty type checker" - @echo " make deadcode - Run vulture dead code checker" - @echo " make rebuild - Build and run Docker containers (with prompts)" - @echo " make clean - Stop and remove Docker containers" - -check: lint format typecheck deadcode - -lint: - uv run ruff check --fix - -format: - uv run ruff format - -typecheck: - uv run ty check - -deadcode: - uv run vulture --min-confidence 100 --exclude ".venv" . - -rebuild: - @read -p "Rebuild without cache? (y/n): " nocache; \ - read -p "Remove volumes before rebuild? (y/n): " volumes; \ - if [ "$$volumes" = "y" ]; then \ - docker compose -f docker-compose.build.yml down -v; \ - else \ - docker compose -f docker-compose.build.yml down; \ - fi; \ - if [ "$$nocache" = "y" ]; then \ - PHOTON_VERSION=$(PHOTON_VERSION) docker compose -f docker-compose.build.yml build --no-cache; \ - else \ - PHOTON_VERSION=$(PHOTON_VERSION) docker compose -f docker-compose.build.yml build; \ - fi; \ - PHOTON_VERSION=$(PHOTON_VERSION) docker compose -f docker-compose.build.yml up - -clean: - docker compose -f docker-compose.build.yml down diff --git a/README.md b/README.md index 86790e65..f6bd666e 100644 --- a/README.md +++ b/README.md @@ -16,16 +16,13 @@ enhancing data privacy and integration capabilities with services like [Dawarich ⚠️ **Warning: Large File Sizes** ⚠️ - The Photon index file is fairly large and growing steadily. - As of the beginning of 2025, around 200GB is needed for the full index, - and it is growing by 10-20GB per year. + As of the beginning of 2025, around 90GB are needed for the full index. Note that this will grow over time. - Ensure you have sufficient disk space available before running the container. - The initial download and extraction process may take a considerable amount of time. Depending on your hardware, checksum verification and decompression may take multiple hours. -♻️ **Change in Default Download Source** ♻️ - - To reduce the load on the official Photon servers, - the default `BASE_URL` for downloading the index files now points to a community-hosted mirror. + the default `BASE_URL` for downloading the index files points to a mirror hosted by my. Please see the **Community Mirrors** section for more details. ## Usage @@ -58,25 +55,26 @@ docker compose up -d The container can be configured using the following environment variables: -| Variable | Parameters | Default | Description | -| ---------------------- | -------------------------------------- | -------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `UPDATE_STRATEGY` | `PARALLEL`, `SEQUENTIAL`, `DISABLED` | `SEQUENTIAL` | Controls how index updates are handled. `PARALLEL` downloads the new index in the background then swaps with minimal downtime (requires 2x space). `SEQUENTIAL` stops Photon, deletes the existing index, downloads the new one, then restarts. `DISABLED` prevents automatic updates. | -| `UPDATE_INTERVAL` | Time string (e.g., "720h", "30d") | `30d` | How often to check for updates. To reduce server load, it is recommended to set this to a long interval (e.g., `720h` for 30 days) or disable updates altogether if you do not need the latest data. | -| `REGION` | Region name, country code, or `planet` | `planet` | Optional region for a specific dataset. Can be a continent (`europe`, `asia`), individual country/region (`germany`, `usa`, `japan`), country code (`de`, `us`, `jp`), or `planet` for worldwide data. See [Available Regions](#available-regions) section for details. | -| `LOG_LEVEL` | `DEBUG`, `INFO`, `ERROR` | `INFO` | Controls logging verbosity. | -| `FORCE_UPDATE` | `TRUE`, `FALSE` | `FALSE` | Forces an index update on container startup, regardless of `UPDATE_STRATEGY`. | -| `DOWNLOAD_MAX_RETRIES` | Number | `3` | Maximum number of retries for failed downloads. | -| `INITIAL_DOWNLOAD` | `TRUE`, `FALSE` | `TRUE` | Controls whether the container performs the initial index download when the Photon data directory is empty. Useful for manual imports. | -| `BASE_URL` | Valid URL | `https://r2.koalasec.org/public` | Custom base URL for index data downloads. Should point to the parent directory of index files. The default has been changed to a community mirror to reduce load on the GraphHopper servers. | -| `SKIP_MD5_CHECK` | `TRUE`, `FALSE` | `FALSE` | Optionally skip MD5 verification of downloaded index files. | -| `SKIP_SPACE_CHECK` | `TRUE`, `FALSE` | `FALSE` | Skip disk space verification before downloading. | -| `FILE_URL` | URL to a .tar.bz2 file | - | Set a custom URL for the index file to be downloaded (e.g., "https://download1.graphhopper.com/public/experimental/photon-db-latest.tar.bz2"). This must be a tar.bz2 format. Setting this overrides `UPDATE_STRATEGY` to `DISABLED`, and `SKIP_MD5_CHECK` to true if `MD5_URL` is not set. | -| `MD5_URL` | URL to the MD5 file to use | - | Set a custom URL for the md5 file to be downloaded (e.g., "https://download1.graphhopper.com/public/experimental/photon-db-latest.tar.bz2.md5"). | -| `PHOTON_PARAMS` | Photon executable parameters | - | See `https://github.com/komoot/photon#running-photon.` | -| `APPRISE_URLS` | Comma-separated Apprise URLs | - | Optional notification URLs for [Apprise](https://github.com/caronc/apprise) to send status updates (e.g., download completion, errors). Supports multiple services like Pushover, Slack, email, etc. Example: `pover://user@token,mailto://user:pass@gmail.com` | -| `PUID` | User ID | 9011 | The User ID for the photon process. Set this to your host user's ID (`id -u`) to prevent permission errors when using bind mounts. | -| `PGID` | Group ID | 9011 | The Group ID for the photon process. Set this to your host group's ID (`id -g`) to prevent permission errors when using bind mounts. | -| `ENABLE_METRICS` | `TRUE`, `FALSE` | `FALSE` | Enables Prometheus Metrics endpoint at /metrics | +| Variable | Parameters | Default | Description | +| ---------------------- | -------------------------------------- | -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `UPDATE_STRATEGY` | `PARALLEL`, `SEQUENTIAL`, `DISABLED` | `SEQUENTIAL` | Controls how index updates are handled. `PARALLEL` downloads the new index in the background then swaps with minimal downtime (requires 2x space). `SEQUENTIAL` stops Photon, deletes the existing index, downloads the new one, then restarts. `DISABLED` prevents automatic updates. | +| `UPDATE_INTERVAL` | Time string (e.g., "720h", "30d") | `30d` | How often to check for updates. To reduce server load, it is recommended to set this to a long interval (e.g., `720h` for 30 days) or disable updates altogether if you do not need the latest data. | +| `REGION` | Region name, country code, or `planet` | `planet` | Optional region for a specific dataset. Can be a continent (`europe`, `asia`), individual country/region (`germany`, `usa`, `japan`), country code (`de`, `us`, `jp`), or `planet` for worldwide data. See [Available Regions](#available-regions) section for details. | +| `LOG_LEVEL` | `DEBUG`, `INFO`, `ERROR` | `INFO` | Controls logging verbosity. | +| `PHOTON_LISTEN_IP` | IP Address | 0.0.0.0 | Populates `-listen-ip` parameter for photon | +| `FORCE_UPDATE` | `TRUE`, `FALSE` | `FALSE` | Forces an index update on container startup, regardless of `UPDATE_STRATEGY`. | +| `DOWNLOAD_MAX_RETRIES` | Number | `3` | Maximum number of retries for failed downloads. | +| `INITIAL_DOWNLOAD` | `TRUE`, `FALSE` | `TRUE` | Controls whether the container performs the initial index download when the Photon data directory is empty. Useful for manual imports. | +| `BASE_URL` | Valid URL | `https://r2.koalasec.org/public` | Custom base URL for index data downloads. Should point to the parent directory of index files. The default has been changed to a community mirror to reduce load on the GraphHopper servers. | +| `SKIP_MD5_CHECK` | `TRUE`, `FALSE` | `FALSE` | Optionally skip MD5 verification of downloaded index files. | +| `SKIP_SPACE_CHECK` | `TRUE`, `FALSE` | `FALSE` | Skip disk space verification before downloading. | +| `FILE_URL` | URL to a .tar.bz2 file | - | Set a custom URL for the index file to be downloaded (e.g., "https://download1.graphhopper.com/public/experimental/photon-db-latest.tar.bz2"). This must be a tar.bz2 format. Setting this overrides `UPDATE_STRATEGY` to `DISABLED`, and `SKIP_MD5_CHECK` to true if `MD5_URL` is not set. | +| `MD5_URL` | URL to the MD5 file to use | - | Set a custom URL for the md5 file to be downloaded (e.g., "https://download1.graphhopper.com/public/experimental/photon-db-latest.tar.bz2.md5"). | +| `PHOTON_PARAMS` | Photon executable parameters | - | See `https://github.com/komoot/photon#running-photon.` | +| `APPRISE_URLS` | Comma-separated Apprise URLs | - | Optional notification URLs for [Apprise](https://github.com/caronc/apprise) to send status updates (e.g., download completion, errors). Supports multiple services like Pushover, Slack, email, etc. Example: `pover://user@token,mailto://user:pass@gmail.com` | +| `PUID` | User ID | 9011 | The User ID for the photon process. Set this to your host user's ID (`id -u`) to prevent permission errors when using bind mounts. | +| `PGID` | Group ID | 9011 | The Group ID for the photon process. Set this to your host group's ID (`id -g`) to prevent permission errors when using bind mounts. | +| `ENABLE_METRICS` | `TRUE`, `FALSE` | `FALSE` | Enables Prometheus Metrics endpoint at /metrics | ## Available Regions @@ -157,6 +155,14 @@ If you are hosting a public mirror, please open an issue or pull request to have | `https://download1.graphhopper.com/public/` | [GraphHopper](https://www.graphhopper.com/) (Official) | ![GraphHopper](https://img.shields.io/website?url=https%3A%2F%2Fdownload1.graphhopper.com%2Fpublic%2Fphoton-db-planet-0.7OS-latest.tar.bz2&style=for-the-badge&label=Status) | | `https://r2.koalasec.org/public/` | [rtuszik](https://github.com/rtuszik) | ![KoalaSec](https://img.shields.io/website?url=https%3A%2F%2Fr2.koalasec.org%2Fpublic%2Fphoton-db-planet-0.7OS-latest.tar.bz2&style=for-the-badge&label=Status) | +## Metrics + +When `ENABLE_METRICS` is set to `TRUE`, Prometheus metrics are exposed through the at the `/metrics` endpoint. + +An example Grafana Dashboard is available here at [Grafana Labs](https://grafana.com/grafana/dashboards/24901-photon/). + +To see a live preview, [visit the public dashboard](https://r10k.grafana.net/public-dashboards/089de1d08d954a959e3a475af8968e1e) of the photon instance hosted by [rtuszik](https://github.com/rtuszik). + ### Use with Dawarich This docker container for photon can be used as your reverse-geocoder for the [Dawarich Location History Tracker](https://github.com/Freika/dawarich) @@ -194,7 +200,8 @@ http://localhost:2322/api?q=Harare ## Contributing -Contributions are welcome. Please submit pull requests or open issues for suggestions and improvements. +Contributions are welcome. +Please checkout the [Contribution Guidelines](CONTRIBUTING.md). ## License diff --git a/Taskfile.yml b/Taskfile.yml new file mode 100644 index 00000000..0175ab5d --- /dev/null +++ b/Taskfile.yml @@ -0,0 +1,131 @@ +version: "3" + +tasks: + default: + desc: Show available tasks + cmds: + - task --list + silent: true + + help: + desc: Show available tasks + cmds: + - task --list + silent: true + + install: + desc: Install dev dependencies and git hooks + cmds: + - | + if ! command -v uv >/dev/null 2>&1; then + echo "uv is not installed or not on PATH." + exit 1 + fi + + if ! command -v prek >/dev/null 2>&1; then + echo "prek is not installed or not on PATH." + exit 1 + fi + + prek install + uv sync --locked --group dev + + check: + desc: Run all quality checks + deps: + - lint + - format + - typecheck + - deadcode + + lint: + desc: Run ruff linter with auto-fix + cmds: + - uv run ruff check --fix + + format: + desc: Run ruff formatter + cmds: + - uv run ruff format + + typecheck: + desc: Run ty type checker + cmds: + - uv run ty check + + deadcode: + desc: Run vulture dead code checker + cmds: + - uv run vulture --min-confidence 100 --exclude ".venv" . + + test: + desc: Run pytest + cmds: + - uv run pytest + + rebuild: + desc: Build and run Docker containers + interactive: true + env: + PHOTON_VERSION: "{{.PHOTON_VERSION}}" + vars: + PHOTON_VERSION: + sh: tr -d '[:space:]' < .last_release + NOCACHE: '{{default "false" .NOCACHE}}' + VOLUMES: '{{default "false" .VOLUMES}}' + cmds: + - | + nocache="{{.NOCACHE}}" + volumes="{{.VOLUMES}}" + + if [ "{{.CLI_ARGS}}" != "" ]; then + echo "This task does not accept positional CLI args. Use Task vars like NOCACHE=true." + exit 1 + fi + + if [ ! -t 0 ]; then + if [ "$volumes" = "true" ]; then + docker compose -f docker-compose.build.yml down -v + else + docker compose -f docker-compose.build.yml down + fi + + if [ "$nocache" = "true" ]; then + docker compose -f docker-compose.build.yml build --no-cache + else + docker compose -f docker-compose.build.yml build + fi + + docker compose -f docker-compose.build.yml up + exit 0 + fi + + read -r "?Rebuild without cache? (y/n): " nocache_input + read -r "?Remove volumes before rebuild? (y/n): " volumes_input + + if [ "$nocache_input" = "y" ] || [ "$nocache_input" = "Y" ]; then + nocache="true" + fi + + if [ "$volumes_input" = "y" ] || [ "$volumes_input" = "Y" ]; then + volumes="true" + fi + + if [ "$volumes" = "true" ]; then + docker compose -f docker-compose.build.yml down -v + else + docker compose -f docker-compose.build.yml down + fi + + if [ "$nocache" = "true" ]; then + docker compose -f docker-compose.build.yml build --no-cache + else + docker compose -f docker-compose.build.yml build + fi + + docker compose -f docker-compose.build.yml up + + clean: + desc: Stop and remove Docker containers + cmds: + - docker compose -f docker-compose.build.yml down diff --git a/prek.toml b/prek.toml new file mode 100644 index 00000000..0016349f --- /dev/null +++ b/prek.toml @@ -0,0 +1,20 @@ +[[repos]] +repo = "https://github.com/pre-commit/pre-commit-hooks" +rev = "v6.0.0" +hooks = [ + { id = "check-yaml" }, + { id = "end-of-file-fixer" }, +] + +[[repos]] +repo = "https://github.com/rhysd/actionlint" +rev = "v1.7.11" +hooks = [{ id = "actionlint" }] + +[[repos]] +repo = "https://github.com/astral-sh/ruff-pre-commit" +rev = "v0.15.0" +hooks = [ + { id = "ruff-check", args = ["--fix"], types_or = ["python", "pyi"] }, + { id = "ruff-format", types_or = ["python", "pyi"] }, +] diff --git a/pyproject.toml b/pyproject.toml index f4ae653b..1a3cb21b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "photon-docker" -version = "1.2.1" +version = "2.1.0" description = "Unofficial docker image for the Photon Geocoder" requires-python = ">=3.12" dependencies = [ @@ -13,34 +13,35 @@ dependencies = [ ] [dependency-groups] -dev = ["ruff>=0.12.7", "ty>=0.0.1a16", "vulture>=2.14"] +dev = [ + "prek>=0.3.4", + "pytest>=9.0.2", + "pytest-cov>=7.0.0", + "ruff>=0.12.7", + "ty>=0.0.1a16", + "vulture>=2.14", +] [build-system] -requires = ["uv_build>=0.9.10"] +requires = ["uv_build>=0.10.9,<0.11.0"] build-backend = "uv_build" -[tool.uv.build-backend] -module-name = "src" -module-root = "" - - [tool.ruff] indent-width = 4 line-length = 120 - [tool.ruff.lint] # select = ["ALL"] ignore = [ - "ANN", # flake8-annotations - "COM", # flake8-commas - "C90", # mccabe complexity - "DJ", # django - "EXE", # flake8-executable - "BLE", # blind except - "PTH", # flake8-pathlib - "T10", # debugger - "TID", # flake8-tidy-imports + "ANN", # flake8-annotations + "COM", # flake8-commas + "C90", # mccabe complexity + "DJ", # django + "EXE", # flake8-executable + "BLE", # blind except + "PTH", # flake8-pathlib + "T10", # debugger + "TID", # flake8-tidy-imports "D100", "D101", "D102", @@ -50,36 +51,42 @@ ignore = [ "D106", "D107", "D101", - "D107", # missing docstring in public module - "D102", # missing docstring in public class - "D104", # missing docstring in public package + "D107", # missing docstring in public module + "D102", # missing docstring in public class + "D104", # missing docstring in public package "D213", "D203", "D400", "D415", "G004", "PLR2004", - "E501", # line too long + "E501", # line too long "TRY", "SIM105", # faster without contextlib ] - -extend-select = ["B", "S", "SIM", "T20", "C901"] - - +extend-select = ["B", "S", "SIM", "T20", "C901", "RUF"] fixable = ["ALL"] unfixable = [] - # Allow unused variables when underscore-prefixed. dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$" [tool.ruff.lint.mccabe] max-complexity = 15 +[tool.ruff.lint.per-file-ignores] +"tests/**/*.py" = ["S101"] [tool.ruff.format] quote-style = "double" indent-style = "space" line-ending = "auto" - docstring-code-format = false +skip-magic-trailing-comma = true + +[tool.tombi.format.rules] +indent-style = "space" +indent-width = 4 + +[tool.uv.build-backend] +module-name = "src" +module-root = "" diff --git a/renovate.json b/renovate.json index c7ed646a..aa272750 100644 --- a/renovate.json +++ b/renovate.json @@ -1,5 +1,13 @@ { "$schema": "https://docs.renovatebot.com/renovate-schema.json", + "extends": [ + "config:recommended", + "helpers:pinGitHubActionDigests", + ":configMigration", + ":pinDevDependencies", + "abandonments:recommended", + ":enablePreCommit" + ], "baseBranchPatterns": [ "dev" ] diff --git a/src/downloader.py b/src/downloader.py index 24cc01d4..ad458630 100644 --- a/src/downloader.py +++ b/src/downloader.py @@ -463,15 +463,7 @@ def _perform_download(url, destination, resume_byte_pos, mode, start_time): progress_bar = _create_progress_bar(total_size, resume_byte_pos, destination) try: - downloaded = _download_content( - response, - destination, - mode, - url, - total_size, - resume_byte_pos, - progress_bar, - ) + downloaded = _download_content(response, destination, mode, url, total_size, resume_byte_pos, progress_bar) if progress_bar: progress_bar.close() diff --git a/src/process_manager.py b/src/process_manager.py index e9fa1f5f..392beb06 100644 --- a/src/process_manager.py +++ b/src/process_manager.py @@ -79,7 +79,7 @@ def handle_shutdown(self, signum, _frame): def run_initial_setup(self): logger.info("Running initial setup...") - result = subprocess.run(["uv", "run", "-m", "src.entrypoint", "setup"], check=False, cwd="/photon") # noqa S603 + result = subprocess.run(["uv", "run", "--no-sync", "-m", "src.entrypoint", "setup"], check=False, cwd="/photon") # noqa S603 if result.returncode != 0: logger.error("Setup failed!") @@ -107,7 +107,17 @@ def start_photon(self, max_startup_retries=3): if java_params: cmd.extend(shlex.split(java_params)) - cmd.extend(["-jar", "/photon/photon.jar", "serve", "-listen-ip", "0.0.0.0", "-data-dir", config.DATA_DIR]) #noqa S104 + cmd.extend( + [ + "-jar", + "/photon/photon.jar", + "serve", + "-listen-ip", + config.PHOTON_LISTEN_IP, + "-data-dir", + config.DATA_DIR, + ] + ) if enable_metrics: cmd.extend(["-metrics-enable", "prometheus"]) @@ -208,7 +218,7 @@ def run_update(self): if config.UPDATE_STRATEGY == "SEQUENTIAL": self.stop_photon() - result = subprocess.run(["uv", "run", "-m", "src.updater"], check=False, cwd="/photon") # noqa S603 + result = subprocess.run(["uv", "run", "--no-sync", "-m", "src.updater"], check=False, cwd="/photon") # noqa S603 if result.returncode == 0: logger.info("Update process completed, verifying Photon health...") @@ -218,7 +228,7 @@ def run_update(self): if self.start_photon(): update_duration = time.time() - update_start logger.info(f"Update completed successfully - Photon healthy ({update_duration:.1f}s)") - target_node_dir = os.path.join(config.PHOTON_DATA_DIR, "node_1") + target_node_dir = os.path.join(config.PHOTON_DATA_DIR) cleanup_backup_after_verification(target_node_dir) else: update_duration = time.time() - update_start @@ -227,7 +237,7 @@ def run_update(self): if self.start_photon(): update_duration = time.time() - update_start logger.info(f"Update completed successfully - Photon healthy ({update_duration:.1f}s)") - target_node_dir = os.path.join(config.PHOTON_DATA_DIR, "node_1") + target_node_dir = os.path.join(config.PHOTON_DATA_DIR) cleanup_backup_after_verification(target_node_dir) else: update_duration = time.time() - update_start diff --git a/src/utils/config.py b/src/utils/config.py index 9271357f..2d79a205 100644 --- a/src/utils/config.py +++ b/src/utils/config.py @@ -18,6 +18,7 @@ SKIP_SPACE_CHECK = os.getenv("SKIP_SPACE_CHECK", "False").lower() in ("true", "1", "t") APPRISE_URLS = os.getenv("APPRISE_URLS") MIN_INDEX_DATE = os.getenv("MIN_INDEX_DATE", "10.02.26") +PHOTON_LISTEN_IP = os.getenv("PHOTON_LISTEN_IP", "0.0.0.0") # noqa: S104 # APP CONFIG INDEX_DB_VERSION = "1.0" diff --git a/src/utils/regions.py b/src/utils/regions.py index 0cc75f09..74e17a9b 100644 --- a/src/utils/regions.py +++ b/src/utils/regions.py @@ -1,39 +1,11 @@ REGION_MAPPING = { - "planet": { - "type": "planet", - "continent": None, - "available": True, - }, - "africa": { - "type": "continent", - "continent": "africa", - "available": True, - }, - "asia": { - "type": "continent", - "continent": "asia", - "available": True, - }, - "australia-oceania": { - "type": "continent", - "continent": "australia-oceania", - "available": True, - }, - "europe": { - "type": "continent", - "continent": "europe", - "available": True, - }, - "north-america": { - "type": "continent", - "continent": "north-america", - "available": True, - }, - "south-america": { - "type": "continent", - "continent": "south-america", - "available": True, - }, + "planet": {"type": "planet", "continent": None, "available": True}, + "africa": {"type": "continent", "continent": "africa", "available": True}, + "asia": {"type": "continent", "continent": "asia", "available": True}, + "australia-oceania": {"type": "continent", "continent": "australia-oceania", "available": True}, + "europe": {"type": "continent", "continent": "europe", "available": True}, + "north-america": {"type": "continent", "continent": "north-america", "available": True}, + "south-america": {"type": "continent", "continent": "south-america", "available": True}, "india": {"type": "sub-region", "continent": "asia", "available": True}, "japan": {"type": "sub-region", "continent": "asia", "available": True}, "andorra": {"type": "sub-region", "continent": "europe", "available": True}, diff --git a/tests/utils/test_regions.py b/tests/utils/test_regions.py new file mode 100644 index 00000000..ae428c0b --- /dev/null +++ b/tests/utils/test_regions.py @@ -0,0 +1,50 @@ +import pytest + +from src.utils.regions import get_index_url_path, get_region_info, is_valid_region, normalize_region + + +@pytest.mark.parametrize( + ("region", "expected"), + [ + ("planet", "planet"), + (" Europe ", "europe"), + ("mx", "mexico"), + ("United States", "usa"), + ("deutschland", "germany"), + ("españa", "spain"), + ("unknown", None), + ("", None), + ], +) +def test_normalize_region(region: str, expected: str | None): + assert normalize_region(region) == expected + + +@pytest.mark.parametrize( + ("region", "expected"), [("asia", True), ("mx", True), ("monaco", True), ("invalid-region", False)] +) +def test_is_valid_region(region: str, expected: bool): + assert is_valid_region(region) is expected + + +def test_get_region_info_for_alias_returns_canonical_region_metadata(): + assert get_region_info("us") == {"type": "sub-region", "continent": "north-america", "available": True} + + +@pytest.mark.parametrize( + ("region", "expected"), + [ + (None, "/photon-db-planet-1.0-latest.tar.bz2"), + ("planet", "/photon-db-planet-1.0-latest.tar.bz2"), + ("europe", "/europe/photon-db-europe-1.0-latest.tar.bz2"), + ("us", "/north-america/usa/photon-db-usa-1.0-latest.tar.bz2"), + ("United States", "/north-america/usa/photon-db-usa-1.0-latest.tar.bz2"), + ], +) +def test_get_index_url_path(region: str | None, expected: str): + assert get_index_url_path(region, "1.0", "tar.bz2") == expected + + +def test_get_index_url_path_raises_for_unknown_region(): + with pytest.raises(ValueError, match="Unknown region: atlantis"): + get_index_url_path("atlantis", "1.0", "tar.bz2") diff --git a/tests/utils/test_sanitize.py b/tests/utils/test_sanitize.py new file mode 100644 index 00000000..bb12fcd3 --- /dev/null +++ b/tests/utils/test_sanitize.py @@ -0,0 +1,18 @@ +import pytest + +from src.utils.sanitize import sanitize_url + + +@pytest.mark.parametrize( + ("url", "expected"), + [ + (None, None), + ("", ""), + ("https://example.com/data.tar.bz2", "https://example.com/data.tar.bz2"), + ("https://user@example.com/data.tar.bz2", "https://***@example.com/data.tar.bz2"), + ("https://user:secret@example.com/data.tar.bz2", "https://***@example.com/data.tar.bz2"), + ("https://user:secret@example.com:8443/data.tar.bz2", "https://***@example.com:8443/data.tar.bz2"), + ], +) +def test_sanitize_url(url: str | None, expected: str | None): + assert sanitize_url(url) == expected diff --git a/tests/utils/test_validate_config.py b/tests/utils/test_validate_config.py new file mode 100644 index 00000000..678b9fe1 --- /dev/null +++ b/tests/utils/test_validate_config.py @@ -0,0 +1,59 @@ +import pytest + +from src.utils import config +from src.utils.validate_config import validate_config + + +def _set_base_config(monkeypatch: pytest.MonkeyPatch): + monkeypatch.setattr(config, "UPDATE_STRATEGY", "SEQUENTIAL") + monkeypatch.setattr(config, "UPDATE_INTERVAL", "30d") + monkeypatch.setattr(config, "REGION", None) + + +def test_validate_config_accepts_valid_configuration(monkeypatch: pytest.MonkeyPatch): + _set_base_config(monkeypatch) + + validate_config() + + +@pytest.mark.parametrize("strategy", ["parallel", "FULL", ""]) +def test_validate_config_rejects_invalid_strategy(monkeypatch: pytest.MonkeyPatch, strategy: str): + _set_base_config(monkeypatch) + monkeypatch.setattr(config, "UPDATE_STRATEGY", strategy) + + with pytest.raises(ValueError, match=f"Invalid UPDATE_STRATEGY: '{strategy}'"): + validate_config() + + +@pytest.mark.parametrize("interval", ["30", "d30", "30w", "1D", ""]) +def test_validate_config_rejects_invalid_interval(monkeypatch: pytest.MonkeyPatch, interval: str): + _set_base_config(monkeypatch) + monkeypatch.setattr(config, "UPDATE_INTERVAL", interval) + + with pytest.raises(ValueError, match=f"Invalid UPDATE_INTERVAL format: '{interval}'"): + validate_config() + + +def test_validate_config_rejects_invalid_region(monkeypatch: pytest.MonkeyPatch): + _set_base_config(monkeypatch) + monkeypatch.setattr(config, "REGION", "atlantis") + + with pytest.raises(ValueError, match="Invalid REGION: 'atlantis'"): + validate_config() + + +def test_validate_config_reports_multiple_errors(monkeypatch: pytest.MonkeyPatch): + _set_base_config(monkeypatch) + monkeypatch.setattr(config, "UPDATE_STRATEGY", "WRONG") + monkeypatch.setattr(config, "UPDATE_INTERVAL", "hourly") + monkeypatch.setattr(config, "REGION", "atlantis") + + with pytest.raises(ValueError) as exc_info: + validate_config() + + message = str(exc_info.value) + + assert "Configuration validation failed:" in message + assert "Invalid UPDATE_STRATEGY: 'WRONG'" in message + assert "Invalid UPDATE_INTERVAL format: 'hourly'" in message + assert "Invalid REGION: 'atlantis'" in message diff --git a/uv.lock b/uv.lock index 204d5e49..884fbf5d 100644 --- a/uv.lock +++ b/uv.lock @@ -85,6 +85,90 @@ 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.13.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/24/56/95b7e30fa389756cb56630faa728da46a27b8c6eb46f9d557c68fff12b65/coverage-7.13.4.tar.gz", hash = "sha256:e5c8f6ed1e61a8b2dcdf31eb0b9bbf0130750ca79c1c49eb898e2ad86f5ccc91", size = 827239, upload-time = "2026-02-09T12:59:03.86Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/81/4ce2fdd909c5a0ed1f6dedb88aa57ab79b6d1fbd9b588c1ac7ef45659566/coverage-7.13.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:02231499b08dabbe2b96612993e5fc34217cdae907a51b906ac7fca8027a4459", size = 219449, upload-time = "2026-02-09T12:56:54.889Z" }, + { url = "https://files.pythonhosted.org/packages/5d/96/5238b1efc5922ddbdc9b0db9243152c09777804fb7c02ad1741eb18a11c0/coverage-7.13.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40aa8808140e55dc022b15d8aa7f651b6b3d68b365ea0398f1441e0b04d859c3", size = 219810, upload-time = "2026-02-09T12:56:56.33Z" }, + { url = "https://files.pythonhosted.org/packages/78/72/2f372b726d433c9c35e56377cf1d513b4c16fe51841060d826b95caacec1/coverage-7.13.4-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:5b856a8ccf749480024ff3bd7310adaef57bf31fd17e1bfc404b7940b6986634", size = 251308, upload-time = "2026-02-09T12:56:57.858Z" }, + { url = "https://files.pythonhosted.org/packages/5d/a0/2ea570925524ef4e00bb6c82649f5682a77fac5ab910a65c9284de422600/coverage-7.13.4-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2c048ea43875fbf8b45d476ad79f179809c590ec7b79e2035c662e7afa3192e3", size = 254052, upload-time = "2026-02-09T12:56:59.754Z" }, + { url = "https://files.pythonhosted.org/packages/e8/ac/45dc2e19a1939098d783c846e130b8f862fbb50d09e0af663988f2f21973/coverage-7.13.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b7b38448866e83176e28086674fe7368ab8590e4610fb662b44e345b86d63ffa", size = 255165, upload-time = "2026-02-09T12:57:01.287Z" }, + { url = "https://files.pythonhosted.org/packages/2d/4d/26d236ff35abc3b5e63540d3386e4c3b192168c1d96da5cb2f43c640970f/coverage-7.13.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:de6defc1c9badbf8b9e67ae90fd00519186d6ab64e5cc5f3d21359c2a9b2c1d3", size = 257432, upload-time = "2026-02-09T12:57:02.637Z" }, + { url = "https://files.pythonhosted.org/packages/ec/55/14a966c757d1348b2e19caf699415a2a4c4f7feaa4bbc6326a51f5c7dd1b/coverage-7.13.4-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7eda778067ad7ffccd23ecffce537dface96212576a07924cbf0d8799d2ded5a", size = 251716, upload-time = "2026-02-09T12:57:04.056Z" }, + { url = "https://files.pythonhosted.org/packages/77/33/50116647905837c66d28b2af1321b845d5f5d19be9655cb84d4a0ea806b4/coverage-7.13.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e87f6c587c3f34356c3759f0420693e35e7eb0e2e41e4c011cb6ec6ecbbf1db7", size = 253089, upload-time = "2026-02-09T12:57:05.503Z" }, + { url = "https://files.pythonhosted.org/packages/c2/b4/8efb11a46e3665d92635a56e4f2d4529de6d33f2cb38afd47d779d15fc99/coverage-7.13.4-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:8248977c2e33aecb2ced42fef99f2d319e9904a36e55a8a68b69207fb7e43edc", size = 251232, upload-time = "2026-02-09T12:57:06.879Z" }, + { url = "https://files.pythonhosted.org/packages/51/24/8cd73dd399b812cc76bb0ac260e671c4163093441847ffe058ac9fda1e32/coverage-7.13.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:25381386e80ae727608e662474db537d4df1ecd42379b5ba33c84633a2b36d47", size = 255299, upload-time = "2026-02-09T12:57:08.245Z" }, + { url = "https://files.pythonhosted.org/packages/03/94/0a4b12f1d0e029ce1ccc1c800944a9984cbe7d678e470bb6d3c6bc38a0da/coverage-7.13.4-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:ee756f00726693e5ba94d6df2bdfd64d4852d23b09bb0bc700e3b30e6f333985", size = 250796, upload-time = "2026-02-09T12:57:10.142Z" }, + { url = "https://files.pythonhosted.org/packages/73/44/6002fbf88f6698ca034360ce474c406be6d5a985b3fdb3401128031eef6b/coverage-7.13.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fdfc1e28e7c7cdce44985b3043bc13bbd9c747520f94a4d7164af8260b3d91f0", size = 252673, upload-time = "2026-02-09T12:57:12.197Z" }, + { url = "https://files.pythonhosted.org/packages/de/c6/a0279f7c00e786be75a749a5674e6fa267bcbd8209cd10c9a450c655dfa7/coverage-7.13.4-cp312-cp312-win32.whl", hash = "sha256:01d4cbc3c283a17fc1e42d614a119f7f438eabb593391283adca8dc86eff1246", size = 221990, upload-time = "2026-02-09T12:57:14.085Z" }, + { url = "https://files.pythonhosted.org/packages/77/4e/c0a25a425fcf5557d9abd18419c95b63922e897bc86c1f327f155ef234a9/coverage-7.13.4-cp312-cp312-win_amd64.whl", hash = "sha256:9401ebc7ef522f01d01d45532c68c5ac40fb27113019b6b7d8b208f6e9baa126", size = 222800, upload-time = "2026-02-09T12:57:15.944Z" }, + { url = "https://files.pythonhosted.org/packages/47/ac/92da44ad9a6f4e3a7debd178949d6f3769bedca33830ce9b1dcdab589a37/coverage-7.13.4-cp312-cp312-win_arm64.whl", hash = "sha256:b1ec7b6b6e93255f952e27ab58fbc68dcc468844b16ecbee881aeb29b6ab4d8d", size = 221415, upload-time = "2026-02-09T12:57:17.497Z" }, + { url = "https://files.pythonhosted.org/packages/db/23/aad45061a31677d68e47499197a131eea55da4875d16c1f42021ab963503/coverage-7.13.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b66a2da594b6068b48b2692f043f35d4d3693fb639d5ea8b39533c2ad9ac3ab9", size = 219474, upload-time = "2026-02-09T12:57:19.332Z" }, + { url = "https://files.pythonhosted.org/packages/a5/70/9b8b67a0945f3dfec1fd896c5cefb7c19d5a3a6d74630b99a895170999ae/coverage-7.13.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3599eb3992d814d23b35c536c28df1a882caa950f8f507cef23d1cbf334995ac", size = 219844, upload-time = "2026-02-09T12:57:20.66Z" }, + { url = "https://files.pythonhosted.org/packages/97/fd/7e859f8fab324cef6c4ad7cff156ca7c489fef9179d5749b0c8d321281c2/coverage-7.13.4-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:93550784d9281e374fb5a12bf1324cc8a963fd63b2d2f223503ef0fd4aa339ea", size = 250832, upload-time = "2026-02-09T12:57:22.007Z" }, + { url = "https://files.pythonhosted.org/packages/e4/dc/b2442d10020c2f52617828862d8b6ee337859cd8f3a1f13d607dddda9cf7/coverage-7.13.4-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b720ce6a88a2755f7c697c23268ddc47a571b88052e6b155224347389fdf6a3b", size = 253434, upload-time = "2026-02-09T12:57:23.339Z" }, + { url = "https://files.pythonhosted.org/packages/5a/88/6728a7ad17428b18d836540630487231f5470fb82454871149502f5e5aa2/coverage-7.13.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7b322db1284a2ed3aa28ffd8ebe3db91c929b7a333c0820abec3d838ef5b3525", size = 254676, upload-time = "2026-02-09T12:57:24.774Z" }, + { url = "https://files.pythonhosted.org/packages/7c/bc/21244b1b8cedf0dff0a2b53b208015fe798d5f2a8d5348dbfece04224fff/coverage-7.13.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f4594c67d8a7c89cf922d9df0438c7c7bb022ad506eddb0fdb2863359ff78242", size = 256807, upload-time = "2026-02-09T12:57:26.125Z" }, + { url = "https://files.pythonhosted.org/packages/97/a0/ddba7ed3251cff51006737a727d84e05b61517d1784a9988a846ba508877/coverage-7.13.4-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:53d133df809c743eb8bce33b24bcababb371f4441340578cd406e084d94a6148", size = 251058, upload-time = "2026-02-09T12:57:27.614Z" }, + { url = "https://files.pythonhosted.org/packages/9b/55/e289addf7ff54d3a540526f33751951bf0878f3809b47f6dfb3def69c6f7/coverage-7.13.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:76451d1978b95ba6507a039090ba076105c87cc76fc3efd5d35d72093964d49a", size = 252805, upload-time = "2026-02-09T12:57:29.066Z" }, + { url = "https://files.pythonhosted.org/packages/13/4e/cc276b1fa4a59be56d96f1dabddbdc30f4ba22e3b1cd42504c37b3313255/coverage-7.13.4-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:7f57b33491e281e962021de110b451ab8a24182589be17e12a22c79047935e23", size = 250766, upload-time = "2026-02-09T12:57:30.522Z" }, + { url = "https://files.pythonhosted.org/packages/94/44/1093b8f93018f8b41a8cf29636c9292502f05e4a113d4d107d14a3acd044/coverage-7.13.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:1731dc33dc276dafc410a885cbf5992f1ff171393e48a21453b78727d090de80", size = 254923, upload-time = "2026-02-09T12:57:31.946Z" }, + { url = "https://files.pythonhosted.org/packages/8b/55/ea2796da2d42257f37dbea1aab239ba9263b31bd91d5527cdd6db5efe174/coverage-7.13.4-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:bd60d4fe2f6fa7dff9223ca1bbc9f05d2b6697bc5961072e5d3b952d46e1b1ea", size = 250591, upload-time = "2026-02-09T12:57:33.842Z" }, + { url = "https://files.pythonhosted.org/packages/d4/fa/7c4bb72aacf8af5020675aa633e59c1fbe296d22aed191b6a5b711eb2bc7/coverage-7.13.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9181a3ccead280b828fae232df12b16652702b49d41e99d657f46cc7b1f6ec7a", size = 252364, upload-time = "2026-02-09T12:57:35.743Z" }, + { url = "https://files.pythonhosted.org/packages/5c/38/a8d2ec0146479c20bbaa7181b5b455a0c41101eed57f10dd19a78ab44c80/coverage-7.13.4-cp313-cp313-win32.whl", hash = "sha256:f53d492307962561ac7de4cd1de3e363589b000ab69617c6156a16ba7237998d", size = 222010, upload-time = "2026-02-09T12:57:37.25Z" }, + { url = "https://files.pythonhosted.org/packages/e2/0c/dbfafbe90a185943dcfbc766fe0e1909f658811492d79b741523a414a6cc/coverage-7.13.4-cp313-cp313-win_amd64.whl", hash = "sha256:e6f70dec1cc557e52df5306d051ef56003f74d56e9c4dd7ddb07e07ef32a84dd", size = 222818, upload-time = "2026-02-09T12:57:38.734Z" }, + { url = "https://files.pythonhosted.org/packages/04/d1/934918a138c932c90d78301f45f677fb05c39a3112b96fd2c8e60503cdc7/coverage-7.13.4-cp313-cp313-win_arm64.whl", hash = "sha256:fb07dc5da7e849e2ad31a5d74e9bece81f30ecf5a42909d0a695f8bd1874d6af", size = 221438, upload-time = "2026-02-09T12:57:40.223Z" }, + { url = "https://files.pythonhosted.org/packages/52/57/ee93ced533bcb3e6df961c0c6e42da2fc6addae53fb95b94a89b1e33ebd7/coverage-7.13.4-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:40d74da8e6c4b9ac18b15331c4b5ebc35a17069410cad462ad4f40dcd2d50c0d", size = 220165, upload-time = "2026-02-09T12:57:41.639Z" }, + { url = "https://files.pythonhosted.org/packages/c5/e0/969fc285a6fbdda49d91af278488d904dcd7651b2693872f0ff94e40e84a/coverage-7.13.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4223b4230a376138939a9173f1bdd6521994f2aff8047fae100d6d94d50c5a12", size = 220516, upload-time = "2026-02-09T12:57:44.215Z" }, + { url = "https://files.pythonhosted.org/packages/b1/b8/9531944e16267e2735a30a9641ff49671f07e8138ecf1ca13db9fd2560c7/coverage-7.13.4-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:1d4be36a5114c499f9f1f9195e95ebf979460dbe2d88e6816ea202010ba1c34b", size = 261804, upload-time = "2026-02-09T12:57:45.989Z" }, + { url = "https://files.pythonhosted.org/packages/8a/f3/e63df6d500314a2a60390d1989240d5f27318a7a68fa30ad3806e2a9323e/coverage-7.13.4-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:200dea7d1e8095cc6e98cdabe3fd1d21ab17d3cee6dab00cadbb2fe35d9c15b9", size = 263885, upload-time = "2026-02-09T12:57:47.42Z" }, + { url = "https://files.pythonhosted.org/packages/f3/67/7654810de580e14b37670b60a09c599fa348e48312db5b216d730857ffe6/coverage-7.13.4-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b8eb931ee8e6d8243e253e5ed7336deea6904369d2fd8ae6e43f68abbf167092", size = 266308, upload-time = "2026-02-09T12:57:49.345Z" }, + { url = "https://files.pythonhosted.org/packages/37/6f/39d41eca0eab3cc82115953ad41c4e77935286c930e8fad15eaed1389d83/coverage-7.13.4-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:75eab1ebe4f2f64d9509b984f9314d4aa788540368218b858dad56dc8f3e5eb9", size = 267452, upload-time = "2026-02-09T12:57:50.811Z" }, + { url = "https://files.pythonhosted.org/packages/50/6d/39c0fbb8fc5cd4d2090811e553c2108cf5112e882f82505ee7495349a6bf/coverage-7.13.4-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c35eb28c1d085eb7d8c9b3296567a1bebe03ce72962e932431b9a61f28facf26", size = 261057, upload-time = "2026-02-09T12:57:52.447Z" }, + { url = "https://files.pythonhosted.org/packages/a4/a2/60010c669df5fa603bb5a97fb75407e191a846510da70ac657eb696b7fce/coverage-7.13.4-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:eb88b316ec33760714a4720feb2816a3a59180fd58c1985012054fa7aebee4c2", size = 263875, upload-time = "2026-02-09T12:57:53.938Z" }, + { url = "https://files.pythonhosted.org/packages/3e/d9/63b22a6bdbd17f1f96e9ed58604c2a6b0e72a9133e37d663bef185877cf6/coverage-7.13.4-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:7d41eead3cc673cbd38a4417deb7fd0b4ca26954ff7dc6078e33f6ff97bed940", size = 261500, upload-time = "2026-02-09T12:57:56.012Z" }, + { url = "https://files.pythonhosted.org/packages/70/bf/69f86ba1ad85bc3ad240e4c0e57a2e620fbc0e1645a47b5c62f0e941ad7f/coverage-7.13.4-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:fb26a934946a6afe0e326aebe0730cdff393a8bc0bbb65a2f41e30feddca399c", size = 265212, upload-time = "2026-02-09T12:57:57.5Z" }, + { url = "https://files.pythonhosted.org/packages/ae/f2/5f65a278a8c2148731831574c73e42f57204243d33bedaaf18fa79c5958f/coverage-7.13.4-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:dae88bc0fc77edaa65c14be099bd57ee140cf507e6bfdeea7938457ab387efb0", size = 260398, upload-time = "2026-02-09T12:57:59.027Z" }, + { url = "https://files.pythonhosted.org/packages/ef/80/6e8280a350ee9fea92f14b8357448a242dcaa243cb2c72ab0ca591f66c8c/coverage-7.13.4-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:845f352911777a8e722bfce168958214951e07e47e5d5d9744109fa5fe77f79b", size = 262584, upload-time = "2026-02-09T12:58:01.129Z" }, + { url = "https://files.pythonhosted.org/packages/22/63/01ff182fc95f260b539590fb12c11ad3e21332c15f9799cb5e2386f71d9f/coverage-7.13.4-cp313-cp313t-win32.whl", hash = "sha256:2fa8d5f8de70688a28240de9e139fa16b153cc3cbb01c5f16d88d6505ebdadf9", size = 222688, upload-time = "2026-02-09T12:58:02.736Z" }, + { url = "https://files.pythonhosted.org/packages/a9/43/89de4ef5d3cd53b886afa114065f7e9d3707bdb3e5efae13535b46ae483d/coverage-7.13.4-cp313-cp313t-win_amd64.whl", hash = "sha256:9351229c8c8407645840edcc277f4a2d44814d1bc34a2128c11c2a031d45a5dd", size = 223746, upload-time = "2026-02-09T12:58:05.362Z" }, + { url = "https://files.pythonhosted.org/packages/35/39/7cf0aa9a10d470a5309b38b289b9bb07ddeac5d61af9b664fe9775a4cb3e/coverage-7.13.4-cp313-cp313t-win_arm64.whl", hash = "sha256:30b8d0512f2dc8c8747557e8fb459d6176a2c9e5731e2b74d311c03b78451997", size = 222003, upload-time = "2026-02-09T12:58:06.952Z" }, + { url = "https://files.pythonhosted.org/packages/92/11/a9cf762bb83386467737d32187756a42094927150c3e107df4cb078e8590/coverage-7.13.4-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:300deaee342f90696ed186e3a00c71b5b3d27bffe9e827677954f4ee56969601", size = 219522, upload-time = "2026-02-09T12:58:08.623Z" }, + { url = "https://files.pythonhosted.org/packages/d3/28/56e6d892b7b052236d67c95f1936b6a7cf7c3e2634bf27610b8cbd7f9c60/coverage-7.13.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:29e3220258d682b6226a9b0925bc563ed9a1ebcff3cad30f043eceea7eaf2689", size = 219855, upload-time = "2026-02-09T12:58:10.176Z" }, + { url = "https://files.pythonhosted.org/packages/e5/69/233459ee9eb0c0d10fcc2fe425a029b3fa5ce0f040c966ebce851d030c70/coverage-7.13.4-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:391ee8f19bef69210978363ca930f7328081c6a0152f1166c91f0b5fdd2a773c", size = 250887, upload-time = "2026-02-09T12:58:12.503Z" }, + { url = "https://files.pythonhosted.org/packages/06/90/2cdab0974b9b5bbc1623f7876b73603aecac11b8d95b85b5b86b32de5eab/coverage-7.13.4-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:0dd7ab8278f0d58a0128ba2fca25824321f05d059c1441800e934ff2efa52129", size = 253396, upload-time = "2026-02-09T12:58:14.615Z" }, + { url = "https://files.pythonhosted.org/packages/ac/15/ea4da0f85bf7d7b27635039e649e99deb8173fe551096ea15017f7053537/coverage-7.13.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:78cdf0d578b15148b009ccf18c686aa4f719d887e76e6b40c38ffb61d264a552", size = 254745, upload-time = "2026-02-09T12:58:16.162Z" }, + { url = "https://files.pythonhosted.org/packages/99/11/bb356e86920c655ca4d61daee4e2bbc7258f0a37de0be32d233b561134ff/coverage-7.13.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:48685fee12c2eb3b27c62f2658e7ea21e9c3239cba5a8a242801a0a3f6a8c62a", size = 257055, upload-time = "2026-02-09T12:58:17.892Z" }, + { url = "https://files.pythonhosted.org/packages/c9/0f/9ae1f8cb17029e09da06ca4e28c9e1d5c1c0a511c7074592e37e0836c915/coverage-7.13.4-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4e83efc079eb39480e6346a15a1bcb3e9b04759c5202d157e1dd4303cd619356", size = 250911, upload-time = "2026-02-09T12:58:19.495Z" }, + { url = "https://files.pythonhosted.org/packages/89/3a/adfb68558fa815cbc29747b553bc833d2150228f251b127f1ce97e48547c/coverage-7.13.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ecae9737b72408d6a950f7e525f30aca12d4bd8dd95e37342e5beb3a2a8c4f71", size = 252754, upload-time = "2026-02-09T12:58:21.064Z" }, + { url = "https://files.pythonhosted.org/packages/32/b1/540d0c27c4e748bd3cd0bd001076ee416eda993c2bae47a73b7cc9357931/coverage-7.13.4-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:ae4578f8528569d3cf303fef2ea569c7f4c4059a38c8667ccef15c6e1f118aa5", size = 250720, upload-time = "2026-02-09T12:58:22.622Z" }, + { url = "https://files.pythonhosted.org/packages/c7/95/383609462b3ffb1fe133014a7c84fc0dd01ed55ac6140fa1093b5af7ebb1/coverage-7.13.4-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:6fdef321fdfbb30a197efa02d48fcd9981f0d8ad2ae8903ac318adc653f5df98", size = 254994, upload-time = "2026-02-09T12:58:24.548Z" }, + { url = "https://files.pythonhosted.org/packages/f7/ba/1761138e86c81680bfc3c49579d66312865457f9fe405b033184e5793cb3/coverage-7.13.4-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:2b0f6ccf3dbe577170bebfce1318707d0e8c3650003cb4b3a9dd744575daa8b5", size = 250531, upload-time = "2026-02-09T12:58:26.271Z" }, + { url = "https://files.pythonhosted.org/packages/f8/8e/05900df797a9c11837ab59c4d6fe94094e029582aab75c3309a93e6fb4e3/coverage-7.13.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:75fcd519f2a5765db3f0e391eb3b7d150cce1a771bf4c9f861aeab86c767a3c0", size = 252189, upload-time = "2026-02-09T12:58:27.807Z" }, + { url = "https://files.pythonhosted.org/packages/00/bd/29c9f2db9ea4ed2738b8a9508c35626eb205d51af4ab7bf56a21a2e49926/coverage-7.13.4-cp314-cp314-win32.whl", hash = "sha256:8e798c266c378da2bd819b0677df41ab46d78065fb2a399558f3f6cae78b2fbb", size = 222258, upload-time = "2026-02-09T12:58:29.441Z" }, + { url = "https://files.pythonhosted.org/packages/a7/4d/1f8e723f6829977410efeb88f73673d794075091c8c7c18848d273dc9d73/coverage-7.13.4-cp314-cp314-win_amd64.whl", hash = "sha256:245e37f664d89861cf2329c9afa2c1fe9e6d4e1a09d872c947e70718aeeac505", size = 223073, upload-time = "2026-02-09T12:58:31.026Z" }, + { url = "https://files.pythonhosted.org/packages/51/5b/84100025be913b44e082ea32abcf1afbf4e872f5120b7a1cab1d331b1e13/coverage-7.13.4-cp314-cp314-win_arm64.whl", hash = "sha256:ad27098a189e5838900ce4c2a99f2fe42a0bf0c2093c17c69b45a71579e8d4a2", size = 221638, upload-time = "2026-02-09T12:58:32.599Z" }, + { url = "https://files.pythonhosted.org/packages/a7/e4/c884a405d6ead1370433dad1e3720216b4f9fd8ef5b64bfd984a2a60a11a/coverage-7.13.4-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:85480adfb35ffc32d40918aad81b89c69c9cc5661a9b8a81476d3e645321a056", size = 220246, upload-time = "2026-02-09T12:58:34.181Z" }, + { url = "https://files.pythonhosted.org/packages/81/5c/4d7ed8b23b233b0fffbc9dfec53c232be2e695468523242ea9fd30f97ad2/coverage-7.13.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:79be69cf7f3bf9b0deeeb062eab7ac7f36cd4cc4c4dd694bd28921ba4d8596cc", size = 220514, upload-time = "2026-02-09T12:58:35.704Z" }, + { url = "https://files.pythonhosted.org/packages/2f/6f/3284d4203fd2f28edd73034968398cd2d4cb04ab192abc8cff007ea35679/coverage-7.13.4-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:caa421e2684e382c5d8973ac55e4f36bed6821a9bad5c953494de960c74595c9", size = 261877, upload-time = "2026-02-09T12:58:37.864Z" }, + { url = "https://files.pythonhosted.org/packages/09/aa/b672a647bbe1556a85337dc95bfd40d146e9965ead9cc2fe81bde1e5cbce/coverage-7.13.4-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:14375934243ee05f56c45393fe2ce81fe5cc503c07cee2bdf1725fb8bef3ffaf", size = 264004, upload-time = "2026-02-09T12:58:39.492Z" }, + { url = "https://files.pythonhosted.org/packages/79/a1/aa384dbe9181f98bba87dd23dda436f0c6cf2e148aecbb4e50fc51c1a656/coverage-7.13.4-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:25a41c3104d08edb094d9db0d905ca54d0cd41c928bb6be3c4c799a54753af55", size = 266408, upload-time = "2026-02-09T12:58:41.852Z" }, + { url = "https://files.pythonhosted.org/packages/53/5e/5150bf17b4019bc600799f376bb9606941e55bd5a775dc1e096b6ffea952/coverage-7.13.4-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6f01afcff62bf9a08fb32b2c1d6e924236c0383c02c790732b6537269e466a72", size = 267544, upload-time = "2026-02-09T12:58:44.093Z" }, + { url = "https://files.pythonhosted.org/packages/e0/ed/f1de5c675987a4a7a672250d2c5c9d73d289dbf13410f00ed7181d8017dd/coverage-7.13.4-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:eb9078108fbf0bcdde37c3f4779303673c2fa1fe8f7956e68d447d0dd426d38a", size = 260980, upload-time = "2026-02-09T12:58:45.721Z" }, + { url = "https://files.pythonhosted.org/packages/b3/e3/fe758d01850aa172419a6743fe76ba8b92c29d181d4f676ffe2dae2ba631/coverage-7.13.4-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:0e086334e8537ddd17e5f16a344777c1ab8194986ec533711cbe6c41cde841b6", size = 263871, upload-time = "2026-02-09T12:58:47.334Z" }, + { url = "https://files.pythonhosted.org/packages/b6/76/b829869d464115e22499541def9796b25312b8cf235d3bb00b39f1675395/coverage-7.13.4-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:725d985c5ab621268b2edb8e50dfe57633dc69bda071abc470fed55a14935fd3", size = 261472, upload-time = "2026-02-09T12:58:48.995Z" }, + { url = "https://files.pythonhosted.org/packages/14/9e/caedb1679e73e2f6ad240173f55218488bfe043e38da577c4ec977489915/coverage-7.13.4-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:3c06f0f1337c667b971ca2f975523347e63ec5e500b9aa5882d91931cd3ef750", size = 265210, upload-time = "2026-02-09T12:58:51.178Z" }, + { url = "https://files.pythonhosted.org/packages/3a/10/0dd02cb009b16ede425b49ec344aba13a6ae1dc39600840ea6abcb085ac4/coverage-7.13.4-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:590c0ed4bf8e85f745e6b805b2e1c457b2e33d5255dd9729743165253bc9ad39", size = 260319, upload-time = "2026-02-09T12:58:53.081Z" }, + { url = "https://files.pythonhosted.org/packages/92/8e/234d2c927af27c6d7a5ffad5bd2cf31634c46a477b4c7adfbfa66baf7ebb/coverage-7.13.4-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:eb30bf180de3f632cd043322dad5751390e5385108b2807368997d1a92a509d0", size = 262638, upload-time = "2026-02-09T12:58:55.258Z" }, + { url = "https://files.pythonhosted.org/packages/2f/64/e5547c8ff6964e5965c35a480855911b61509cce544f4d442caa759a0702/coverage-7.13.4-cp314-cp314t-win32.whl", hash = "sha256:c4240e7eded42d131a2d2c4dec70374b781b043ddc79a9de4d55ca71f8e98aea", size = 223040, upload-time = "2026-02-09T12:58:56.936Z" }, + { url = "https://files.pythonhosted.org/packages/c7/96/38086d58a181aac86d503dfa9c47eb20715a79c3e3acbdf786e92e5c09a8/coverage-7.13.4-cp314-cp314t-win_amd64.whl", hash = "sha256:4c7d3cc01e7350f2f0f6f7036caaf5673fb56b6998889ccfe9e1c1fe75a9c932", size = 224148, upload-time = "2026-02-09T12:58:58.645Z" }, + { url = "https://files.pythonhosted.org/packages/ce/72/8d10abd3740a0beb98c305e0c3faf454366221c0f37a8bcf8f60020bb65a/coverage-7.13.4-cp314-cp314t-win_arm64.whl", hash = "sha256:23e3f687cf945070d1c90f85db66d11e3025665d8dafa831301a0e0038f3db9b", size = 222172, upload-time = "2026-02-09T12:59:00.396Z" }, + { url = "https://files.pythonhosted.org/packages/0d/4a/331fe2caf6799d591109bb9c08083080f6de90a823695d412a935622abb2/coverage-7.13.4-py3-none-any.whl", hash = "sha256:1af1641e57cf7ba1bd67d677c9abdbcd6cc2ab7da3bca7fa1e2b7e50e65f2ad0", size = 211242, upload-time = "2026-02-09T12:59:02.032Z" }, +] + [[package]] name = "idna" version = "3.10" @@ -94,6 +178,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442, upload-time = "2024-09-15T18:07:37.964Z" }, ] +[[package]] +name = "iniconfig" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +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 = "markdown" version = "3.8.2" @@ -112,9 +205,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/be/9c/92789c596b8df838baa98fa71844d84283302f7604ed565dafe5a6b5041a/oauthlib-3.3.1-py3-none-any.whl", hash = "sha256:88119c938d2b8fb88561af5f6ee0eec8cc8d552b7bb1f712743136eb7523b7a1", size = 160065, upload-time = "2025-06-19T22:48:06.508Z" }, ] +[[package]] +name = "packaging" +version = "26.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/65/ee/299d360cdc32edc7d2cf530f3accf79c4fca01e96ffc950d8a52213bd8e4/packaging-26.0.tar.gz", hash = "sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4", size = 143416, upload-time = "2026-01-21T20:50:39.064Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/b9/c538f279a4e237a006a2c98387d081e9eb060d203d8ed34467cc0f0b9b53/packaging-26.0-py3-none-any.whl", hash = "sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529", size = 74366, upload-time = "2026-01-21T20:50:37.788Z" }, +] + [[package]] name = "photon-docker" -version = "1.2.1" +version = "2.1.0" source = { editable = "." } dependencies = [ { name = "apprise" }, @@ -127,6 +229,9 @@ dependencies = [ [package.dev-dependencies] dev = [ + { name = "prek" }, + { name = "pytest" }, + { name = "pytest-cov" }, { name = "ruff" }, { name = "ty" }, { name = "vulture" }, @@ -144,11 +249,47 @@ requires-dist = [ [package.metadata.requires-dev] dev = [ + { name = "prek", specifier = ">=0.3.4" }, + { name = "pytest", specifier = ">=9.0.2" }, + { name = "pytest-cov", specifier = ">=7.0.0" }, { name = "ruff", specifier = ">=0.12.7" }, { name = "ty", specifier = ">=0.0.1a16" }, { name = "vulture", specifier = ">=2.14" }, ] +[[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 = "prek" +version = "0.3.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c6/51/2324eaad93a4b144853ca1c56da76f357d3a70c7b4fd6659e972d7bb8660/prek-0.3.4.tar.gz", hash = "sha256:56a74d02d8b7dfe3c774ecfcd8c1b4e5f1e1b84369043a8003e8e3a779fce72d", size = 356633, upload-time = "2026-02-28T03:47:13.452Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/09/20/1a964cb72582307c2f1dc7f583caab90f42810ad41551e5220592406a4c3/prek-0.3.4-py3-none-linux_armv6l.whl", hash = "sha256:c35192d6e23fe7406bd2f333d1c7dab1a4b34ab9289789f453170f33550aa74d", size = 4641915, upload-time = "2026-02-28T03:47:03.772Z" }, + { url = "https://files.pythonhosted.org/packages/c5/cb/4a21f37102bac37e415b61818344aa85de8d29a581253afa7db8c08d5a33/prek-0.3.4-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:6f784d78de72a8bbe58a5fe7bde787c364ae88f0aff5222c5c5c7287876c510a", size = 4649166, upload-time = "2026-02-28T03:47:06.164Z" }, + { url = "https://files.pythonhosted.org/packages/85/9c/a7c0d117a098d57931428bdb60fcb796e0ebc0478c59288017a2e22eca96/prek-0.3.4-py3-none-macosx_11_0_arm64.whl", hash = "sha256:50a43f522625e8c968e8c9992accf9e29017abad6c782d6d176b73145ad680b7", size = 4274422, upload-time = "2026-02-28T03:46:59.356Z" }, + { url = "https://files.pythonhosted.org/packages/59/84/81d06df1724d09266df97599a02543d82fde7dfaefd192f09d9b2ccb092f/prek-0.3.4-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.musllinux_1_1_aarch64.whl", hash = "sha256:4bbb1d3912a88935f35c6ba4466b4242732e3e3a8c608623c708e83cea85de00", size = 4629873, upload-time = "2026-02-28T03:46:56.419Z" }, + { url = "https://files.pythonhosted.org/packages/09/cd/bb0aefa25cfacd8dbced75b9a9d9945707707867fa5635fb69ae1bbc2d88/prek-0.3.4-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ca4d4134db8f6e8de3c418317becdf428957e3cab271807f475318105fd46d04", size = 4552507, upload-time = "2026-02-28T03:47:05.004Z" }, + { url = "https://files.pythonhosted.org/packages/9b/c0/578a7af4861afb64ec81c03bfdcc1bb3341bb61f2fff8a094ecf13987a56/prek-0.3.4-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7fb6395f6eb76133bb1e11fc718db8144522466cdc2e541d05e7813d1bbcae7d", size = 4865929, upload-time = "2026-02-28T03:47:09.231Z" }, + { url = "https://files.pythonhosted.org/packages/fc/48/f169406590028f7698ef2e1ff5bffd92ca05e017636c1163a2f5ef0f8275/prek-0.3.4-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aae17813239ddcb4ae7b38418de4d49afff740f48f8e0556029c96f58e350412", size = 5390286, upload-time = "2026-02-28T03:47:10.796Z" }, + { url = "https://files.pythonhosted.org/packages/05/c5/98a73fec052059c3ae06ce105bef67caca42334c56d84e9ef75df72ba152/prek-0.3.4-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10a621a690d9c127afc3d21c275030d364d1fbef3296c095068d3ae80a59546e", size = 4891028, upload-time = "2026-02-28T03:47:07.916Z" }, + { url = "https://files.pythonhosted.org/packages/a3/b4/029966e35e59b59c142be7e1d2208ad261709ac1a66aa4a3ce33c5b9f91f/prek-0.3.4-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:d978c31bc3b1f0b3d58895b7c6ac26f077e0ea846da54f46aeee4c7088b1b105", size = 4633986, upload-time = "2026-02-28T03:47:14.351Z" }, + { url = "https://files.pythonhosted.org/packages/1d/27/d122802555745b6940c99fcb41496001c192ddcdf56ec947ec10a0298e05/prek-0.3.4-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:a8e089a030f0a023c22a4bb2ec4ff3fcc153585d701cff67acbfca2f37e173ae", size = 4680722, upload-time = "2026-02-28T03:47:12.224Z" }, + { url = "https://files.pythonhosted.org/packages/34/40/92318c96b3a67b4e62ed82741016ede34d97ea9579d3cc1332b167632222/prek-0.3.4-py3-none-musllinux_1_1_armv7l.whl", hash = "sha256:8060c72b764f0b88112616763da9dd3a7c293e010f8520b74079893096160a2f", size = 4535623, upload-time = "2026-02-28T03:46:52.221Z" }, + { url = "https://files.pythonhosted.org/packages/df/f5/6b383d94e722637da4926b4f609d36fe432827bb6f035ad46ee02bde66b6/prek-0.3.4-py3-none-musllinux_1_1_i686.whl", hash = "sha256:65b23268456b5a763278d4e1ec532f2df33918f13ded85869a1ddff761eb9697", size = 4729879, upload-time = "2026-02-28T03:46:57.886Z" }, + { url = "https://files.pythonhosted.org/packages/79/f8/fdc705b807d813fd713ffa4f67f96741542ed1dafbb221206078c06f3df4/prek-0.3.4-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:3975c61139c7b3200e38dc3955e050b0f2615701d3deb9715696a902e850509e", size = 5001569, upload-time = "2026-02-28T03:47:00.892Z" }, + { url = "https://files.pythonhosted.org/packages/84/92/b007a41f58e8192a1e611a21b396ad870d51d7873b7af12068ebae7fc15f/prek-0.3.4-py3-none-win32.whl", hash = "sha256:37449ae82f4dc08b72e542401e3d7318f05d1163e87c31ab260a40f425d6516e", size = 4297057, upload-time = "2026-02-28T03:47:02.219Z" }, + { url = "https://files.pythonhosted.org/packages/bb/dc/bcb02de9b11461e8e0c7d3c8fdf8cfa15ac6efe73472a4375549ba5defd2/prek-0.3.4-py3-none-win_amd64.whl", hash = "sha256:60e9aa86ca65de963510ae28c5d94b9d7a97bcbaa6e4cdb5bf5083ed4c45dc71", size = 4655174, upload-time = "2026-02-28T03:46:53.749Z" }, + { url = "https://files.pythonhosted.org/packages/0b/86/98f5598569f4cd3de7161e266fab6a8981e65555f79d4704810c1502ad0a/prek-0.3.4-py3-none-win_arm64.whl", hash = "sha256:486bdae8f4512d3b4f6eb61b83e5b7595da2adca385af4b2b7823c0ab38d1827", size = 4367817, upload-time = "2026-02-28T03:46:55.264Z" }, +] + [[package]] name = "psutil" version = "7.2.1" @@ -177,6 +318,45 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/3e/73/2ce007f4198c80fcf2cb24c169884f833fe93fbc03d55d302627b094ee91/psutil-7.2.1-cp37-abi3-win_arm64.whl", hash = "sha256:0d67c1822c355aa6f7314d92018fb4268a76668a536f133599b91edd48759442", size = 133836, upload-time = "2025-12-29T08:26:43.086Z" }, ] +[[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 = "pytest" +version = "9.0.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d1/db/7ef3487e0fb0049ddb5ce41d3a49c235bf9ad299b6a25d5780a89f19230f/pytest-9.0.2.tar.gz", hash = "sha256:75186651a92bd89611d1d9fc20f0b4345fd827c41ccd5c299a868a05d70edf11", size = 1568901, upload-time = "2025-12-06T21:30:51.014Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl", hash = "sha256:711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b", size = 374801, upload-time = "2025-12-06T21:30:49.154Z" }, +] + +[[package]] +name = "pytest-cov" +version = "7.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "coverage" }, + { name = "pluggy" }, + { name = "pytest" }, +] +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 = "python-dateutil" version = "2.9.0.post0"