From c4f76d9fda59ff1dd20ef21a0cccbc3ee16845d4 Mon Sep 17 00:00:00 2001 From: Cian Hughes Date: Tue, 11 Aug 2026 14:21:17 +0100 Subject: [PATCH] Significant overhaul of tooling --- .gitignore | 225 +++++++ cli/__init__.py | 0 cli/__main__.py | 80 +++ cli/bitwarden.py | 134 ++++ cli/bootstrap.py | 35 + cli/config.py | 67 ++ env/invenio.env => cli/data/env.template | 10 +- cli/data/s3_cors.json | 8 + cli/deploy.py | 261 ++++++++ cli/dev.py | 118 ++++ cli/env.py | 24 + cli/s3.py | 110 ++++ cli/utils.py | 83 +++ devenv.nix | 11 + devenv.yaml | 14 +- env.sh | 20 - env/development/caddy/Caddyfile | 18 - env/invenio_dev.env | 6 - env/invenio_prod.env | 6 - env/production/caddy/Caddyfile | 8 - i-form-data-repository/README.md | 7 +- .../app_data/vocabularies.yaml | 12 +- .../app_data/vocabularies/languages.yaml | 96 +-- .../app_data/vocabularies/names.yaml | 20 +- .../overridableRegistry/mapping.js | 2 +- i-form-data-repository/docker-services.yml | 35 +- .../docker/nginx/.gitignore | 2 + .../docker/pgadmin/servers.json | 20 +- .../patch_js_translations.py | 110 ++-- .../site/i_form_data_repository/views.py | 1 + i-form-data-repository/translations/ga.json | 42 +- index.html | 443 ------------- justfile | 161 ----- pyproject.toml | 48 +- uv.lock | 607 +++++++++++++++++- 35 files changed, 2017 insertions(+), 827 deletions(-) create mode 100644 cli/__init__.py create mode 100644 cli/__main__.py create mode 100644 cli/bitwarden.py create mode 100644 cli/bootstrap.py create mode 100644 cli/config.py rename env/invenio.env => cli/data/env.template (93%) create mode 100644 cli/data/s3_cors.json create mode 100644 cli/deploy.py create mode 100644 cli/dev.py create mode 100644 cli/env.py create mode 100644 cli/s3.py create mode 100644 cli/utils.py delete mode 100755 env.sh delete mode 100644 env/development/caddy/Caddyfile delete mode 100644 env/invenio_dev.env delete mode 100644 env/invenio_prod.env delete mode 100644 env/production/caddy/Caddyfile create mode 100644 i-form-data-repository/docker/nginx/.gitignore delete mode 100644 index.html delete mode 100644 justfile diff --git a/.gitignore b/.gitignore index d606c63..4202238 100644 --- a/.gitignore +++ b/.gitignore @@ -22,6 +22,229 @@ package-lock.json /.pnp .pnp.js +# python +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[codz] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py.cover +*.lcov +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +# Pipfile.lock + +# UV +# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# uv.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +# poetry.lock +# poetry.toml + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +# pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python. +# https://pdm-project.org/en/latest/usage/project/#working-with-version-control +# pdm.lock +# pdm.toml +.pdm-python +.pdm-build/ + +# pixi +# Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control. +# pixi.lock +# Pixi creates a virtual environment in the .pixi directory, just like venv module creates one +# in the .venv directory. It is recommended not to include this directory in version control. +.pixi/* +!.pixi/config.toml + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule* +celerybeat.pid + +# Redis +*.rdb +*.aof +*.pid + +# RabbitMQ +mnesia/ +rabbitmq/ +rabbitmq-data/ + +# ActiveMQ +activemq-data/ + +# SageMath parsed files +*.sage.py + +# Environments +.env +.envrc +.bw_session +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +# .idea/ + +# Abstra +# Abstra is an AI-powered process automation framework. +# Ignore directories containing user credentials, local state, and settings. +# Learn more at https://abstra.io/docs +.abstra/ + +# Visual Studio Code +# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore +# that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore +# and can be added to the global gitignore or merged into this file. However, if you prefer, +# you could uncomment the following to ignore the entire vscode folder +# .vscode/ +# Temporary file for partial code execution +tempCodeRunnerFile.py + +# Ruff stuff: +.ruff_cache/ + +# PyPI configuration file +.pypirc + +# Marimo +marimo/_static/ +marimo/_lsp/ +__marimo__/ + +# Streamlit +.streamlit/secrets.toml + # testing /coverage @@ -34,6 +257,7 @@ package-lock.json .env.development.local .env.test.local .env.production.local + # work-in-progress assets /assets @@ -48,3 +272,4 @@ versions secrets.env .env agents +.aws diff --git a/cli/__init__.py b/cli/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/cli/__main__.py b/cli/__main__.py new file mode 100644 index 0000000..fa3fdbc --- /dev/null +++ b/cli/__main__.py @@ -0,0 +1,80 @@ +import typer +from loguru import logger +from plumbum import FG, local +from plumbum.cmd import docker as docker_cmd + +from cli.bitwarden import app as bw_app +from cli.bootstrap import app as bootstrap_app +from cli.config import config +from cli.deploy import app as deploy_app +from cli.dev import app as dev_app +from cli.env import app as env_app +from cli.s3 import app as s3_app +from cli.utils import docker_compose, get_repo_dir + +app = typer.Typer(help="Unified Repository Automation CLI") + +app.add_typer(dev_app, name="dev") +app.add_typer(bw_app, name="bw") +app.add_typer(s3_app, name="s3") +app.add_typer(deploy_app, name="deploy") + +app.add_typer(env_app, name="env") +app.add_typer(bootstrap_app, name="bootstrap") + + +@app.command( + context_settings={"allow_extra_args": True, "ignore_unknown_options": True} +) +def compose(ctx: typer.Context): + """Passthrough to docker-compose inside the repository directory.""" + with local.cwd(get_repo_dir()): + docker_compose(*ctx.args) & FG + + +@app.command( + context_settings={"allow_extra_args": True, "ignore_unknown_options": True} +) +def invenio(ctx: typer.Context): + """Passthrough to invenio inside the running worker container.""" + with local.cwd(get_repo_dir()): + docker_compose("exec", "worker", "invenio", *ctx.args) & FG + + +@app.command( + context_settings={"allow_extra_args": True, "ignore_unknown_options": True} +) +def docker(ctx: typer.Context): + """Passthrough to docker inside the repository directory.""" + with local.cwd(get_repo_dir()): + docker_cmd[*ctx.args] & FG + + +@app.command( + name="invenio-cli", + context_settings={"allow_extra_args": True, "ignore_unknown_options": True}, +) +def invenio_cli_cmd(ctx: typer.Context): + """Passthrough to invenio-cli inside the repository directory.""" + from plumbum.cmd import invenio_cli + + with local.cwd(get_repo_dir()): + invenio_cli[*ctx.args] & FG + + +@app.command( + context_settings={"allow_extra_args": True, "ignore_unknown_options": True} +) +def aws(ctx: typer.Context): + """Passthrough to aws-cli with configured endpoint-url and credentials.""" + try: + from plumbum.cmd import aws as aws_cmd + except ImportError: + logger.error("AWS CLI not found in PATH.") + raise typer.Exit(1) + + aws_cmd["--endpoint-url", str(config.s3_endpoint_url), *ctx.args] & FG + + +if __name__ == "__main__": + app() diff --git a/cli/bitwarden.py b/cli/bitwarden.py new file mode 100644 index 0000000..c76bc3f --- /dev/null +++ b/cli/bitwarden.py @@ -0,0 +1,134 @@ +import json + +import typer +from loguru import logger +from plumbum import FG, ProcessExecutionError, local + +from cli.config import config +from cli.utils import get_project_root + +app = typer.Typer(help="Bitwarden integration workflows.") + +BW_SESSION_FILE = get_project_root() / ".bw_session" +ENV_FILE = get_project_root() / ".env" + + +def _get_bw(): + try: + from plumbum.cmd import bw + + return bw + except ImportError: + logger.error( + "Bitwarden CLI ('bw') not found. Please ensure it is installed or " + "run 'direnv allow' if you just updated devenv.nix." + ) + raise typer.Exit(1) + + +@app.command() +def login() -> bool: + """Log in to Bitwarden and cache the session token. Returns True if a new session was created.""" + bw = _get_bw() + + if config.bitwarden_server_url: + logger.info(f"Configuring Bitwarden server: {config.bitwarden_server_url}") + bw("config", "server", config.bitwarden_server_url) + + logger.info("Checking login status...") + try: + status_json = bw("status") + status = json.loads(status_json) + if status.get("status") == "unlocked": + logger.info("Vault is already unlocked. Using existing session.") + return False + + if status.get("status") == "unauthenticated": + logger.info("Not logged in. Initiating login...") + bw["login"] & FG + elif status.get("status") == "locked": + logger.info("Vault is locked. Initiating unlock...") + except ProcessExecutionError: + logger.warning("Failed to check status. Initiating login...") + bw["login"] & FG + + logger.info("Unlocking vault and saving session...") + try: + # Prompt for master password and output just the raw session key + session_key = bw("unlock", "--raw").strip() + BW_SESSION_FILE.write_text(session_key) + BW_SESSION_FILE.chmod(0o600) + logger.success("Session unlocked and cached securely in .bw_session") + return True + except ProcessExecutionError as e: + logger.error(f"Failed to unlock vault: {e}") + raise typer.Exit(1) + + +@app.command() +def pull(): + """Pull secrets from Bitwarden Secure Note and inject into .env""" + bw = _get_bw() + + if not BW_SESSION_FILE.exists(): + logger.error( + "No active Bitwarden session found. Please run `uv run cli bw login` first." + ) + raise typer.Exit(1) + + session_key = BW_SESSION_FILE.read_text().strip() + with local.env(BW_SESSION=session_key): + logger.info("Syncing vault...") + try: + bw("sync") + except ProcessExecutionError as e: + logger.warning(f"Failed to sync vault (continuing anyway): {e}") + + logger.info(f"Fetching item: {config.bitwarden_item_name}...") + try: + item_json = bw("get", "item", config.bitwarden_item_name) + item = json.loads(item_json) + except ProcessExecutionError as e: + logger.error( + f"Failed to fetch item '{config.bitwarden_item_name}'. Make sure the name is exact." + ) + logger.debug(f"Details: {e}") + raise typer.Exit(1) + + notes = item.get("notes") + if not notes: + logger.error( + f"The item '{config.bitwarden_item_name}' does not contain any notes." + ) + raise typer.Exit(1) + + try: + secrets = json.loads(notes) + except json.JSONDecodeError: + logger.error( + f"The notes in '{config.bitwarden_item_name}' are not valid JSON." + ) + raise typer.Exit(1) + + logger.info(f"Successfully fetched {len(secrets)} secrets from secure note.") + return secrets + + +@app.command() +def lock(): + """Lock the Bitwarden vault and remove the local session token.""" + bw = _get_bw() + + if BW_SESSION_FILE.exists(): + session_key = BW_SESSION_FILE.read_text().strip() + with local.env(BW_SESSION=session_key): + try: + bw("lock") + logger.success("Bitwarden vault locked successfully.") + except ProcessExecutionError as e: + logger.warning(f"Failed to lock vault cleanly: {e}") + + BW_SESSION_FILE.unlink() + logger.success("Local session token removed.") + else: + logger.info("No local session token found.") diff --git a/cli/bootstrap.py b/cli/bootstrap.py new file mode 100644 index 0000000..2bd28f8 --- /dev/null +++ b/cli/bootstrap.py @@ -0,0 +1,35 @@ +import typer +from loguru import logger +from plumbum.cmd import docker + +app = typer.Typer(help="Server Bootstrap Tool") + + +@app.command() +def server(): + """Bootstrap a fresh production server environment.""" + logger.info("Starting fresh server bootstrap process...") + + try: + docker_version = docker["--version"]().strip() + logger.success(f"Found {docker_version}") + except Exception: + logger.error("Docker is not installed or not in PATH!") + raise typer.Exit(1) + + try: + docker("compose", "version") + logger.success("Found docker compose plugin") + except Exception: + logger.error("Docker Compose plugin is not installed!") + raise typer.Exit(1) + + from cli.env import template as env_template + + logger.info("Generating .env template for configuration...") + env_template() + + logger.success("Bootstrap successful!") + logger.warning( + "Please fill out the generated .env.template and rename it to .env before running `uv run cli deploy prod-deploy`." + ) diff --git a/cli/config.py b/cli/config.py new file mode 100644 index 0000000..d1265e7 --- /dev/null +++ b/cli/config.py @@ -0,0 +1,67 @@ +from pydantic import AliasChoices, Field, SecretStr +from pydantic_settings import BaseSettings, SettingsConfigDict + +from cli.utils import get_project_root + + +class Config(BaseSettings): + """Centralized configuration values for the scripts.""" + + model_config = SettingsConfigDict( + env_file=get_project_root() / ".env", env_file_encoding="utf-8", extra="ignore" + ) + + s3_endpoint_url: str = Field( + default="https://eu-west-1.storage.impossibleapi.net", + validation_alias=AliasChoices( + "invenio_s3_endpoint_url", "s3_endpoint_url", "aws_endpoint_url" + ), + ) + s3_access_key_id: str | None = Field( + default=None, + validation_alias=AliasChoices( + "invenio_s3_access_key_id", "s3_access_key_id", "aws_access_key_id" + ), + ) + s3_secret_access_key: SecretStr | None = Field( + default=None, + validation_alias=AliasChoices( + "invenio_s3_secret_access_key", + "s3_secret_access_key", + "aws_secret_access_key", + ), + ) + s3_region: str = Field( + default="eu-west-1", + validation_alias=AliasChoices( + "invenio_s3_region_name", + "invenio_s3_region", + "s3_region", + "aws_default_region", + ), + ) + s3_bucket_name: str = Field( + default="iform-invenio", + validation_alias=AliasChoices( + "invenio_s3_bucket_name", "s3_bucket_name", "aws_s3_bucket" + ), + ) + + # Project Settings + repo_dir_name: str = Field(default="i-form-data-repository") + docker_compose_file: str = Field(default="docker-compose.full.yml") + docker_image_name: str = Field(default="i-form-data-repository:latest") + + # Dev Settings + theme_dir_name: str = Field(default="invenio-theme-iform") + config_dir_name: str = Field(default="invenio-config-iform") + local_wheels_dir: str = Field(default="local_wheels") + ssl_cert_name: str = Field(default="nginx.crt") + ssl_key_name: str = Field(default="nginx.key") + + # Bitwarden Settings + bitwarden_item_name: str = Field(default="I-Form Invenio S3 Keys") + bitwarden_server_url: str | None = Field(default=None) + + +config = Config() diff --git a/env/invenio.env b/cli/data/env.template similarity index 93% rename from env/invenio.env rename to cli/data/env.template index 1c0dbce..0204c9f 100644 --- a/env/invenio.env +++ b/cli/data/env.template @@ -1,3 +1,8 @@ +# Invenio-S3 Storage Backend Credentials +INVENIO_S3_ENDPOINT_URL=https://eu-west-1.storage.impossibleapi.net +INVENIO_S3_REGION_NAME=eu-west-1 +INVENIO_S3_BUCKET_NAME=iform-invenio + # Invenio-Theme INVENIO_THEME_LOGO=custom_assets/I-Form_logo.webp INVENIO_THEME_FRONTPAGE_TITLE="I-Form Invenio Data Repository" @@ -58,11 +63,6 @@ INVENIO_OAISERVER_ID_PREFIX=invenio-rdm # Invenio-Files-REST INVENIO_FILES_REST_STORAGE_FACTORY=invenio_s3.s3fs_storage_factory -# Invenio-S3 -INVENIO_S3_ENDPOINT_URL=https://s3.eu-west-1.amazonaws.com -INVENIO_S3_REGION_NAME=eu-west-1 -INVENIO_S3_BUCKET_NAME=i-form-invenio - # Invenio-Search INVENIO_SEARCH_HOSTS=search:9200 INVENIO_SEARCH_INDEX_PREFIX=invenio-rdm- diff --git a/cli/data/s3_cors.json b/cli/data/s3_cors.json new file mode 100644 index 0000000..d97aac8 --- /dev/null +++ b/cli/data/s3_cors.json @@ -0,0 +1,8 @@ +[ + { + "AllowedHeaders": [], + "AllowedMethods": ["GET"], + "AllowedOrigins": ["*"], + "ExposeHeaders": [] + } +] diff --git a/cli/deploy.py b/cli/deploy.py new file mode 100644 index 0000000..7eedb39 --- /dev/null +++ b/cli/deploy.py @@ -0,0 +1,261 @@ +import typer +from loguru import logger +from plumbum import local +from plumbum.cmd import docker, git, uv + +from cli.utils import docker_compose, get_project_root, get_repo_dir + +app = typer.Typer(help="Deployment Management Tool") + +ROOT_DIR = get_project_root() +REPO_DIR = get_repo_dir() +VERSIONS_DIR = ROOT_DIR / "versions" + + +import time + +import requests + + +def healthcheck(): + """Poll the localhost health endpoint to verify the stack is up.""" + logger.info("Running healthcheck...") + for _ in range(3): + try: + response = requests.get("http://localhost/health", timeout=5) + if response.status_code == 200: + logger.success("Healthcheck passed.") + return + except requests.RequestException: + pass + logger.info("Waiting 5 seconds before retrying...") + time.sleep(5) + + logger.error("Healthcheck failed.") + raise typer.Exit(1) + + +@app.command() +def tag_version(): + """Tag current version using bumpver and save docker images state.""" + VERSIONS_DIR.mkdir(exist_ok=True) + + logger.info("Bumping version with bumpver...") + try: + uv("run", "bumpver", "update") + except Exception as e: + logger.error(f"Bumpver failed: {e}") + + current_tag = git("describe", "--tags", "--abbrev=0").strip() + logger.info(f"Current tag is {current_tag}") + + with local.cwd(REPO_DIR): + images_output = docker_compose("images") + + version_file = VERSIONS_DIR / f"{current_tag}.txt" + + with open(version_file, "w") as f: + for line in images_output.splitlines(): + if "REPOSITORY" not in line: + f.write(line + "\n") + + logger.success(f"Saved image states to {version_file}") + + +@app.command() +def rollback(version: str = typer.Option(None, help="Specific tag to rollback to")): + """Rollback to a specific version or latest.""" + current_branch = git("branch", "--show-current").strip() + + if not version: + tags = git("tag", "--sort=-v:refname").splitlines() + if not tags: + logger.error("No tags found.") + raise typer.Exit(1) + version = tags[0] + + version_file = VERSIONS_DIR / f"{version}.txt" + + if not version_file.exists(): + logger.error(f"No version file found for {version} at {version_file}") + raise typer.Exit(1) + + logger.info(f"Pulling old images for {version}...") + with open(version_file, "r") as f: + for line in f: + parts = line.split() + if len(parts) >= 2: + repo, tag = parts[0], parts[1] + logger.info(f"Pulling {repo}:{tag}") + docker("pull", f"{repo}:{tag}") + + logger.info(f"Rolling back to {version}...") + git("checkout", version) + + try: + with local.cwd(REPO_DIR): + docker_compose("down") + docker_compose("build", "--no-cache") + docker_compose("up", "-d") + finally: + if current_branch: + git("switch", current_branch) + logger.success( + f"Rollback to {version} complete. Returned git to {current_branch}." + ) + else: + logger.success( + f"Rollback to {version} complete. Remained in detached HEAD." + ) + + +@app.command() +def cleanup_versions(): + """Keep only the latest 5 version tags and delete older ones.""" + tags = git("tag", "--sort=-v:refname").splitlines() + tags_to_delete = tags[5:] + + for tag in tags_to_delete: + if not tag: + continue + git("tag", "-d", tag) + version_file = VERSIONS_DIR / f"{tag}.txt" + if version_file.exists(): + version_file.unlink() + logger.info(f"Cleaned up {tag}") + + +@app.command() +def prod_deploy(): + """First-time deployment to production.""" + + logger.info("Deploying to production (first-time)...") + git("switch", "prod") + git("pull", "origin", "prod") + + with local.cwd(REPO_DIR): + docker( + "build", + "-t", + "i-form-data-repository:latest", + "--no-cache", + "--build-arg", + "INSTALL_LOCAL_WHEELS=false", + ".", + ) + docker_compose("up", "-d", "--wait") + + setup_cmd = "invenio db init && invenio db create && invenio alembic upgrade head && invenio collect -v && invenio index init" + docker_compose("exec", "worker", "bash", "-c", setup_cmd) + + logger.success("Production deployment complete!") + + +@app.command() +def prod_update( + auto_rollback: bool = typer.Option( + False, help="Automatically rollback if healthcheck fails" + ), +): + """Update the application stack, with optional auto-rollback.""" + lock_file = ROOT_DIR / "update.lock" + + if lock_file.exists(): + logger.error("Update already in progress (update.lock exists).") + raise typer.Exit(1) + + logger.info("Updating production deployment...") + + try: + lock_file.touch() + + logger.info("Tagging current version for potential rollback...") + tag_version() + + git("switch", "prod") + git("pull", "origin", "prod") + with local.cwd(REPO_DIR): + docker_compose("pull") + docker( + "build", + "-t", + "i-form-data-repository:latest", + "--no-cache", + "--build-arg", + "INSTALL_LOCAL_WHEELS=false", + ".", + ) + docker_compose("up", "-d", "--wait") + + update_cmd = "invenio alembic upgrade head && invenio collect -v" + docker_compose("exec", "worker", "bash", "-c", update_cmd) + + try: + healthcheck() + except typer.Exit: + if auto_rollback: + logger.warning("Healthcheck failed, triggering auto-rollback...") + rollback(None) + else: + logger.error( + "Healthcheck failed. Consider running 'uv run cli deploy rollback' manually." + ) + raise + + cleanup_versions() + logger.success("Production update complete!") + + finally: + if lock_file.exists(): + lock_file.unlink() + + +@app.command() +def merge_and_push_prod(): + """Merge main into prod and push all branches.""" + logger.info("Merging main into prod...") + current = git("branch", "--show-current").strip() + + try: + git("switch", "prod") + git("merge", "main") + git("switch", "main") + logger.info("Pushing all branches...") + git("push", "--all") + logger.success("Merged and pushed to production.") + finally: + if current: + git("switch", current) + + +@app.command() +def prod_clean(): + """Clean all production build artifacts.""" + logger.info("Cleaning production build artifacts...") + from plumbum.cmd import find, rm + + root = get_project_root() + with local.cwd(root): + rm("-rf", ".venv", "build", "dist") + for egg in local.path(".") // "*.egg-info": + rm("-rf", egg) + + try: + find( + ".", + "-type", + "d", + "-name", + "__pycache__", + "-exec", + "rm", + "-r", + "{}", + "+", + ) + except Exception: + pass + + rm("-rf", "static", "node_modules") + + logger.success("Clean complete. Re-create environment before next deploy.") diff --git a/cli/dev.py b/cli/dev.py new file mode 100644 index 0000000..a528c79 --- /dev/null +++ b/cli/dev.py @@ -0,0 +1,118 @@ +import trustme +import typer +from loguru import logger + +from cli.config import config +from cli.utils import docker_compose, get_project_root, get_repo_dir + +app = typer.Typer(help="Tool for generating development SSL certificates.") + + +@app.command() +def generate(): + """Generates a self-signed localhost certificate using trustme.""" + repo_dir = get_repo_dir() + nginx_dir = repo_dir / "docker" / "nginx" + + cert_path = repo_dir / config.ssl_cert_name + key_path = repo_dir / config.ssl_key_name + + if cert_path.exists() and key_path.exists(): + logger.info(f"Certificates already exist at {nginx_dir}. Skipping generation.") + return + + logger.info("Generating self-signed dev certificates using trustme...") + + ca = trustme.CA() + server_cert = ca.issue_cert("localhost", "127.0.0.1", "::1") + + server_cert.private_key_pem.write_to_path(key_path) + server_cert.cert_chain_pems[0].write_to_path(cert_path) + + logger.success(f"Successfully generated {cert_path.name} and {key_path.name}.") + + +@app.command() +def fmt(): + """Format codebase using prettier via npx.""" + logger.info("Running prettier via npx...") + from plumbum.cmd import npx + + try: + npx( + "prettier", + "--write", + "**/*.{js,jsx,ts,tsx,html,css,scss,sass,svelte,yaml,json,markdown}", + ) + logger.success("Formatting complete.") + except Exception as e: + logger.error(f"Formatting failed: {e}") + raise typer.Exit(1) + + +@app.command() +def build_wheels(): + """Build and copy wheels for local packages (theme and config).""" + from plumbum import local + from plumbum.cmd import cp, direnv, rm + + root = get_project_root() + theme_dir = root.parent / config.theme_dir_name + config_dir = root.parent / config.config_dir_name + repo_dir = get_repo_dir() + + logger.info("Building theme wheel...") + with local.cwd(theme_dir): + rm("-rf", "dist") + direnv("exec", ".", "uv", "build", "--package", config.theme_dir_name) + + logger.info("Building config wheel...") + with local.cwd(config_dir): + rm("-rf", "dist") + direnv("exec", ".", "uv", "build", "--package", config.config_dir_name) + + logger.info("Copying wheels to repository...") + wheels_dir = repo_dir / config.local_wheels_dir + wheels_dir.mkdir(exist_ok=True) + rm("-f", local.path(str(wheels_dir)) // "*.whl") + + for whl in local.path(str(theme_dir / "dist")) // "*.whl": + cp(whl, wheels_dir) + for whl in local.path(str(config_dir / "dist")) // "*.whl": + cp(whl, wheels_dir) + + logger.success("Successfully built and copied fresh wheels.") + + +@app.command() +def test_local(): + """Rebuild and restart local docker stack with fresh wheels.""" + from plumbum import local + from plumbum.cmd import curl, docker + + build_wheels() + + logger.info("Rebuilding and restarting local docker stack...") + repo_dir = get_repo_dir() + + with local.cwd(repo_dir): + docker_compose("down") + docker( + "build", + "-t", + config.docker_image_name, + "--no-cache", + "--build-arg", + "INSTALL_LOCAL_WHEELS=true", + ".", + ) + docker_compose("up", "-d", "--wait") + + setup_cmd = "invenio db init || true; invenio db create || true; invenio alembic upgrade || true; invenio index init || true" + docker_compose("exec", "worker", "bash", "-c", setup_cmd) + + try: + curl("-skI", "https://127.0.0.1:8443/") + logger.success("HTTPS verification successful.") + except Exception: + logger.warning("HTTPS verification failed.") diff --git a/cli/env.py b/cli/env.py new file mode 100644 index 0000000..ae54573 --- /dev/null +++ b/cli/env.py @@ -0,0 +1,24 @@ +import typer +from loguru import logger + +from cli.utils import get_project_root + +app = typer.Typer(help="Environment validation and templating.") + +import shutil + + +@app.command() +def template(): + """Generate a .env.template file.""" + logger.info("Generating .env.template...") + + source = get_project_root() / "cli" / "data" / "env.template" + target = get_project_root() / ".env.template" + + if not source.exists(): + logger.error(f"Template file not found at {source}") + raise typer.Exit(1) + + shutil.copy2(source, target) + logger.success(f"Generated {target.name}") diff --git a/cli/s3.py b/cli/s3.py new file mode 100644 index 0000000..c2931d9 --- /dev/null +++ b/cli/s3.py @@ -0,0 +1,110 @@ +import json +from pathlib import Path + +import boto3 +import typer +from botocore.client import Config +from loguru import logger + +from cli.config import config +from cli.utils import get_project_root + +app = typer.Typer(help="Impossible Cloud S3 Management Tool") + + +def get_s3_client(): + if not config.s3_access_key_id or not config.s3_secret_access_key: + logger.warning( + "S3_ACCESS_KEY_ID or S3_SECRET_ACCESS_KEY not found in environment. Boto3 will attempt to use default credential provider chain." + ) + + kwargs = { + "service_name": "s3", + "endpoint_url": str(config.s3_endpoint_url), + "region_name": config.s3_region, + "config": Config(signature_version="s3v4"), + } + + if config.s3_access_key_id and config.s3_secret_access_key: + kwargs["aws_access_key_id"] = config.s3_access_key_id + kwargs["aws_secret_access_key"] = config.s3_secret_access_key.get_secret_value() + else: + logger.info("Using default AWS credential provider chain...") + + return boto3.client(**kwargs) + + +@app.command() +def list_buckets(): + """List all buckets in the S3 account.""" + client = get_s3_client() + response = client.list_buckets() + logger.info("Buckets:") + for bucket in response.get("Buckets", []): + logger.info(f" - {bucket['Name']}") + + +@app.command() +def create_bucket(bucket_name: str): + """Create a new bucket.""" + client = get_s3_client() + try: + if config.s3_region == "us-east-1": + client.create_bucket(Bucket=bucket_name) + else: + client.create_bucket( + Bucket=bucket_name, + CreateBucketConfiguration={"LocationConstraint": config.s3_region}, + ) + logger.success(f"Successfully created bucket: {bucket_name}") + except Exception as e: + logger.error(f"Error creating bucket {bucket_name}: {e}") + raise typer.Exit(1) + + +@app.command() +def upload_file( + file_path: Path = typer.Argument(..., help="Path to local file"), + bucket_name: str = typer.Argument(..., help="Target bucket name"), + object_name: str | None = typer.Option( + None, "--object-name", help="S3 object name (defaults to file name)" + ), +): + """Upload a local file to a bucket.""" + client = get_s3_client() + if object_name is None: + object_name = file_path.name + try: + client.upload_file(str(file_path), bucket_name, object_name) + logger.success(f"Uploaded {file_path} to {bucket_name}/{object_name}") + except Exception as e: + logger.error(f"Error uploading file: {e}") + raise typer.Exit(1) + + +@app.command() +def apply_cors( + bucket_name: str = typer.Argument(config.s3_bucket_name, help="Target bucket name"), + cors_file: Path = typer.Option( + get_project_root() / "cli" / "data" / "s3_cors.json", + help="Path to CORS JSON file", + ), +): + """Apply a CORS configuration from a JSON file to a bucket.""" + client = get_s3_client() + + if not cors_file.exists(): + logger.error(f"CORS file not found: {cors_file}") + raise typer.Exit(1) + + try: + with open(cors_file, "r") as f: + cors_rules = json.load(f) + + client.put_bucket_cors( + Bucket=bucket_name, CORSConfiguration={"CORSRules": cors_rules} + ) + logger.success(f"Successfully applied CORS configuration to {bucket_name}") + except Exception as e: + logger.error(f"Failed to apply CORS config: {e}") + raise typer.Exit(1) diff --git a/cli/utils.py b/cli/utils.py new file mode 100644 index 0000000..086768f --- /dev/null +++ b/cli/utils.py @@ -0,0 +1,83 @@ +import subprocess +from pathlib import Path + +from loguru import logger + + +def get_project_root() -> Path: + """Dynamically determine the project root using git.""" + try: + result = subprocess.run( + ["git", "rev-parse", "--show-toplevel"], + capture_output=True, + text=True, + check=True, + ) + return Path(result.stdout.strip()) + except subprocess.CalledProcessError: + logger.warning( + "Not inside a git repository, falling back to script path resolution." + ) + return Path(__file__).resolve().parent.parent + + +def get_repo_dir() -> Path: + """Return the repository directory path.""" + from cli.config import config + + return get_project_root() / config.repo_dir_name + + +def get_dynamic_s3_credentials() -> dict: + import atexit + + import boto3 + + from cli.bitwarden import lock as bw_lock + from cli.bitwarden import login as bw_login + from cli.bitwarden import pull as bw_pull + + logger.info("Checking for native AWS credentials...") + session = boto3.Session() + creds = session.get_credentials() + if creds and creds.access_key and creds.secret_key: + logger.success("Found existing AWS credentials natively. Skipping Bitwarden.") + return { + "INVENIO_S3_ACCESS_KEY_ID": creds.access_key, + "INVENIO_S3_SECRET_ACCESS_KEY": creds.secret_key, + } + + logger.info("No native AWS credentials found. Falling back to Bitwarden...") + if bw_login(): + atexit.register(bw_lock) + + return bw_pull() + + +_deploy_env_vars = None + + +def docker_compose(*args): + global _deploy_env_vars + import typer + from plumbum import local + from plumbum.cmd import docker + + from cli.config import config + + env_file = get_project_root() / ".env" + + if not env_file.exists(): + logger.error( + "The .env file does not exist! Please run `uv run cli bootstrap server` first to generate and configure it." + ) + raise typer.Exit(1) + + if _deploy_env_vars is None: + _deploy_env_vars = get_dynamic_s3_credentials() + + compose = docker[ + "compose", "-f", config.docker_compose_file, "--env-file", str(env_file) + ] + with local.env(**_deploy_env_vars): + return compose(*args) diff --git a/devenv.nix b/devenv.nix index 71770f4..c4092cd 100644 --- a/devenv.nix +++ b/devenv.nix @@ -7,6 +7,7 @@ }: { packages = with pkgs; [ awscli2 + bitwarden-cli cairo cairomm git @@ -18,6 +19,13 @@ dotenv.enable = true; + enterShell = '' + # Only set the exit trap if we are NOT in a direnv subshell + if [ -z "$DIRENV_DIR" ]; then + trap 'uv run cli bw lock' EXIT + fi + ''; + languages = { python = { enable = true; @@ -36,5 +44,8 @@ cairomm ]); NIX_LD = lib.fileContents "${pkgs.stdenv.cc}/nix-support/dynamic-linker"; + AWS_SHARED_CREDENTIALS_FILE = "${toString ./.}/.aws/credentials"; + AWS_CONFIG_FILE = "${toString ./.}/.aws/config"; + AWS_ENDPOINT_URL = "https://eu-west-1.storage.impossibleapi.net"; }; } diff --git a/devenv.yaml b/devenv.yaml index 184b866..d75e0e0 100644 --- a/devenv.yaml +++ b/devenv.yaml @@ -1,8 +1,8 @@ inputs: - nixpkgs: - url: github:cachix/devenv-nixpkgs/rolling - nixpkgs-python: - url: github:cachix/nixpkgs-python - inputs: - nixpkgs: - follows: nixpkgs + nixpkgs: + url: github:cachix/devenv-nixpkgs/rolling + nixpkgs-python: + url: github:cachix/nixpkgs-python + inputs: + nixpkgs: + follows: nixpkgs diff --git a/env.sh b/env.sh deleted file mode 100755 index ae803a0..0000000 --- a/env.sh +++ /dev/null @@ -1,20 +0,0 @@ -#!/usr/bin/env bash -if [[ "$*" == *--dev* ]]; then - echo "Configuring env in development mode" - ENV_FILE="invenio_dev.env" -else - echo "Configuring env in production mode" - ENV_FILE="invenio_prod.env" -fi - -{ - echo "# Secrets fetched from AWS Secrets Manager" - aws secretsmanager get-secret-value --secret-id Invenio | \ - jq -r '.SecretString | fromjson | to_entries | .[] | .key + "=\"" + .value + "\""' - echo "" -} > secrets.env - -cat secrets.env ./env/invenio.env "./env/$ENV_FILE" > .env -rm secrets.env - -echo "Environment set up using $ENV_FILE" diff --git a/env/development/caddy/Caddyfile b/env/development/caddy/Caddyfile deleted file mode 100644 index bc48e0f..0000000 --- a/env/development/caddy/Caddyfile +++ /dev/null @@ -1,18 +0,0 @@ -{ - debug -} - -localhost { - reverse_proxy invenio-rdm:5000 { - header_down Cache-Control "no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0" - header_down Pragma "no-cache" - header_down Expires "0" - } - - encode gzip - - log { - format console - level DEBUG - } -} diff --git a/env/invenio_dev.env b/env/invenio_dev.env deleted file mode 100644 index 8f06fb1..0000000 --- a/env/invenio_dev.env +++ /dev/null @@ -1,6 +0,0 @@ -# Development environment settings -INVENIO_APP_ALLOWED_HOSTS=invenio.i-form.ie,localhost,localhost:8443,127.0.0.1,127.0.0.1:8443,0.0.0.0,0.0.0.0:8443 -INVENIO_SITE_UI_URL=https://localhost -INVENIO_SITE_API_URL=https://localhost -COMPOSE_PROFILES=development -FLASK_DEBUG=true diff --git a/env/invenio_prod.env b/env/invenio_prod.env deleted file mode 100644 index 41ce9f5..0000000 --- a/env/invenio_prod.env +++ /dev/null @@ -1,6 +0,0 @@ -# Production environment settings -INVENIO_APP_ALLOWED_HOSTS=invenio.i-form.ie -INVENIO_SITE_UI_URL=https://invenio.i-form.ie -INVENIO_SITE_API_URL=https://invenio.i-form.ie/api -COMPOSE_PROFILES=production -FLASK_DEBUG=false diff --git a/env/production/caddy/Caddyfile b/env/production/caddy/Caddyfile deleted file mode 100644 index 1f8c574..0000000 --- a/env/production/caddy/Caddyfile +++ /dev/null @@ -1,8 +0,0 @@ -{ - admin off -} - -invenio.i-form.ie { - reverse_proxy invenio-rdm:5000 - encode gzip -} diff --git a/i-form-data-repository/README.md b/i-form-data-repository/README.md index f177096..eaf937d 100644 --- a/i-form-data-repository/README.md +++ b/i-form-data-repository/README.md @@ -18,8 +18,11 @@ process. Once running, visit https://127.0.0.1 in your browser. -**Note**: The server is using a self-signed SSL certificate, so your browser -will issue a warning that you will have to by-pass. +**Note**: The server uses a self-signed SSL certificate. You must generate it first by running: +```console +just generate-dev-certs +``` +Your browser will issue a warning that you will have to by-pass. ## Overview diff --git a/i-form-data-repository/app_data/vocabularies.yaml b/i-form-data-repository/app_data/vocabularies.yaml index 57d6604..bb064a6 100644 --- a/i-form-data-repository/app_data/vocabularies.yaml +++ b/i-form-data-repository/app_data/vocabularies.yaml @@ -1,10 +1,10 @@ subjects: - pid-type: sub - schemes: - - id: FOS - name: Fields of Science and Technology - uri: "http://www.oecd.org/science/inno/38235147.pdf" - data-file: vocabularies/subjects_oecd_fos.yaml + pid-type: sub + schemes: + - id: FOS + name: Fields of Science and Technology + uri: "http://www.oecd.org/science/inno/38235147.pdf" + data-file: vocabularies/subjects_oecd_fos.yaml # Minimal example vocabularies # can be used for quick testing, or a basis for building your own customized vocabularies. #names: diff --git a/i-form-data-repository/app_data/vocabularies/languages.yaml b/i-form-data-repository/app_data/vocabularies/languages.yaml index c2ec22a..6cdc995 100644 --- a/i-form-data-repository/app_data/vocabularies/languages.yaml +++ b/i-form-data-repository/app_data/vocabularies/languages.yaml @@ -1,96 +1,96 @@ - id: ara props: - alpha_2: ar + alpha_2: ar tags: - - macrolanguage - - living + - macrolanguage + - living title: - en: Arabic + en: Arabic - id: cym props: - alpha_2: cy + alpha_2: cy tags: - - individual - - living + - individual + - living title: - en: Welsh + en: Welsh - id: deu props: - alpha_2: de + alpha_2: de tags: - - individual - - living + - individual + - living title: - en: German + en: German - id: eng props: - alpha_2: en + alpha_2: en tags: - - individual - - living + - individual + - living title: - en: English + en: English - id: fra props: - alpha_2: fr + alpha_2: fr tags: - - individual - - living + - individual + - living title: - en: French + en: French - id: ita props: - alpha_2: it + alpha_2: it tags: - - individual - - living + - individual + - living title: - en: Italian + en: Italian - id: nor props: - alpha_2: 'no' + alpha_2: "no" tags: - - macrolanguage - - living + - macrolanguage + - living title: - en: Norwegian + en: Norwegian - id: roh props: - alpha_2: rm + alpha_2: rm tags: - - individual - - living + - individual + - living title: - en: Romansh + en: Romansh - id: spa props: - alpha_2: es + alpha_2: es tags: - - individual - - living + - individual + - living title: - en: Spanish + en: Spanish - id: tur props: - alpha_2: tr + alpha_2: tr tags: - - individual - - living + - individual + - living title: - en: Turkish + en: Turkish - id: aag props: - alpha_2: '' + alpha_2: "" tags: - - individual - - living + - individual + - living title: - en: Ambrak + en: Ambrak - id: aah props: - alpha_2: '' + alpha_2: "" tags: - - individual - - living + - individual + - living title: - en: Abu' Arapesh + en: Abu' Arapesh diff --git a/i-form-data-repository/app_data/vocabularies/names.yaml b/i-form-data-repository/app_data/vocabularies/names.yaml index d4e3161..96b2020 100644 --- a/i-form-data-repository/app_data/vocabularies/names.yaml +++ b/i-form-data-repository/app_data/vocabularies/names.yaml @@ -1,20 +1,20 @@ - affiliations: - - name: University of Zurich - - name: Humboldt University of Berlin - - name: Kaiser Wilhelm Institute for Physics + - name: University of Zurich + - name: Humboldt University of Berlin + - name: Kaiser Wilhelm Institute for Physics family_name: Einstein given_name: Albert id: gnd:118529579 identifiers: - - identifier: gnd:118529579 - scheme: gnd + - identifier: gnd:118529579 + scheme: gnd - affiliations: - - name: University of Cambridge - - name: California Institute of Technology - - name: University of Oxford + - name: University of Cambridge + - name: California Institute of Technology + - name: University of Oxford family_name: Hawking given_name: Stephen id: 0000-0002-9079-593X identifiers: - - identifier: https://orcid.org/0000-0002-9079-593X - scheme: orcid + - identifier: https://orcid.org/0000-0002-9079-593X + scheme: orcid diff --git a/i-form-data-repository/assets/js/invenio_app_rdm/overridableRegistry/mapping.js b/i-form-data-repository/assets/js/invenio_app_rdm/overridableRegistry/mapping.js index 0d15027..d403dbb 100644 --- a/i-form-data-repository/assets/js/invenio_app_rdm/overridableRegistry/mapping.js +++ b/i-form-data-repository/assets/js/invenio_app_rdm/overridableRegistry/mapping.js @@ -8,4 +8,4 @@ * Add here all the overridden components of your app. */ -export const overriddenComponents = {} \ No newline at end of file +export const overriddenComponents = {}; diff --git a/i-form-data-repository/docker-services.yml b/i-form-data-repository/docker-services.yml index 161707e..038e53d 100644 --- a/i-form-data-repository/docker-services.yml +++ b/i-form-data-repository/docker-services.yml @@ -17,8 +17,15 @@ services: - "INVENIO_SEARCH_HOSTS=['search:9200']" - "INVENIO_SECRET_KEY=CHANGE_ME" - "INVENIO_SQLALCHEMY_DATABASE_URI=postgresql+psycopg2://i-form-data-repository:i-form-data-repository@db/i-form-data-repository" - - "INVENIO_WSGI_PROXIES=2" + - "INVENIO_WSGI_PROXIES=1" - "INVENIO_RATELIMIT_STORAGE_URL=redis://cache:6379/3" + - "INVENIO_S3_ACCESS_KEY_ID=${INVENIO_S3_ACCESS_KEY_ID}" + - "INVENIO_S3_SECRET_ACCESS_KEY=${INVENIO_S3_SECRET_ACCESS_KEY}" + logging: + driver: "json-file" + options: + max-size: "50m" + max-file: "3" frontend: build: context: ./docker/nginx/ @@ -28,6 +35,11 @@ services: ports: - "${DOCKER_SERVICES_IP_BIND:-127.0.0.1}:8080:80" - "${DOCKER_SERVICES_IP_BIND:-127.0.0.1}:8443:443" + logging: + driver: "json-file" + options: + max-size: "50m" + max-file: "3" cache: image: docker.io/library/redis:7 restart: "unless-stopped" @@ -35,14 +47,26 @@ services: ports: - "${DOCKER_SERVICES_IP_BIND:-127.0.0.1}:6379:6379" db: - image: docker.io/library/postgres:14.13 + image: registry.developers.crunchydata.com/crunchydata/crunchy-postgres:ubi8-14.11-0 restart: "unless-stopped" environment: - "POSTGRES_USER=i-form-data-repository" - "POSTGRES_PASSWORD=i-form-data-repository" - "POSTGRES_DB=i-form-data-repository" + - "PGBACKREST_REPO1_TYPE=s3" + - "PGBACKREST_REPO1_S3_BUCKET=${INVENIO_S3_BUCKET_NAME}" + - "PGBACKREST_REPO1_S3_ENDPOINT=${INVENIO_S3_ENDPOINT_URL}" + - "PGBACKREST_REPO1_S3_KEY=${INVENIO_S3_ACCESS_KEY_ID}" + - "PGBACKREST_REPO1_S3_KEY_SECRET=${INVENIO_S3_SECRET_ACCESS_KEY}" + - "PGBACKREST_REPO1_S3_REGION=${INVENIO_S3_REGION_NAME}" + - "PGBACKREST_REPO1_PATH=/backup/meta_db" ports: - "${DOCKER_SERVICES_IP_BIND:-127.0.0.1}:5432:5432" + logging: + driver: "json-file" + options: + max-size: "50m" + max-file: "3" pgadmin: image: docker.io/dpage/pgadmin4:6 restart: "unless-stopped" @@ -66,7 +90,7 @@ services: # settings only for development. DO NOT use in production! - bootstrap.memory_lock=true - "OPENSEARCH_JAVA_OPTS=-Xms512m -Xmx512m" - - "OPENSEARCH_INITIAL_ADMIN_PASSWORD=guekxe3mvqieke1!%&ieIADE" + - "OPENSEARCH_INITIAL_ADMIN_PASSWORD=${OPENSEARCH_PASSWORD:-guekxe3mvqieke1!%&ieIADE}" - "DISABLE_INSTALL_DEMO_CONFIG=true" - "DISABLE_SECURITY_PLUGIN=true" - "discovery.type=single-node" @@ -82,6 +106,11 @@ services: ports: - "${DOCKER_SERVICES_IP_BIND:-127.0.0.1}:9200:9200" - "${DOCKER_SERVICES_IP_BIND:-127.0.0.1}:9600:9600" + logging: + driver: "json-file" + options: + max-size: "50m" + max-file: "3" opensearch-dashboards: image: docker.io/opensearchproject/opensearch-dashboards:2.17.1 ports: diff --git a/i-form-data-repository/docker/nginx/.gitignore b/i-form-data-repository/docker/nginx/.gitignore new file mode 100644 index 0000000..1b01e0e --- /dev/null +++ b/i-form-data-repository/docker/nginx/.gitignore @@ -0,0 +1,2 @@ +test.crt +test.key diff --git a/i-form-data-repository/docker/pgadmin/servers.json b/i-form-data-repository/docker/pgadmin/servers.json index be36484..bebf881 100644 --- a/i-form-data-repository/docker/pgadmin/servers.json +++ b/i-form-data-repository/docker/pgadmin/servers.json @@ -1,13 +1,13 @@ { - "Servers": { - "1": { - "Name": "i-form-data-repository-db", - "Group": "Servers", - "Host": "i-form-data-repository-db-1", - "Port": 5432, - "MaintenanceDB": "postgres", - "Username": "i-form-data-repository", - "SSLMode": "prefer" + "Servers": { + "1": { + "Name": "i-form-data-repository-db", + "Group": "Servers", + "Host": "i-form-data-repository-db-1", + "Port": 5432, + "MaintenanceDB": "postgres", + "Username": "i-form-data-repository", + "SSLMode": "prefer" + } } - } } diff --git a/i-form-data-repository/patch_js_translations.py b/i-form-data-repository/patch_js_translations.py index 4a84061..f9f798c 100644 --- a/i-form-data-repository/patch_js_translations.py +++ b/i-form-data-repository/patch_js_translations.py @@ -1,45 +1,46 @@ import glob +import json import os import shutil -import json + def main(): # 1. Ensure ga directories exist and have translations.json packages = [ - 'invenio_communities', - 'invenio_search_ui', - 'invenio_app_rdm', - 'invenio_requests', - 'invenio_jobs', - 'invenio_rdm_records', - 'invenio_administration' + "invenio_communities", + "invenio_search_ui", + "invenio_app_rdm", + "invenio_requests", + "invenio_jobs", + "invenio_rdm_records", + "invenio_administration", ] bases = [ - '/opt/invenio/src/.venv/lib/python3.12/site-packages', - '/opt/invenio/var/instance/assets/translations' + "/opt/invenio/src/.venv/lib/python3.12/site-packages", + "/opt/invenio/var/instance/assets/translations", ] for pkg in packages: for base in bases: for root, dirs, files_in_dir in os.walk(base): - if root.endswith('messages') and pkg in root: - ga_dir = os.path.join(root, 'ga') + if root.endswith("messages") and pkg in root: + ga_dir = os.path.join(root, "ga") os.makedirs(ga_dir, exist_ok=True) - json_path = os.path.join(ga_dir, 'translations.json') + json_path = os.path.join(ga_dir, "translations.json") if not os.path.exists(json_path) or os.path.getsize(json_path) <= 2: - with open(json_path, 'w') as fp: - fp.write('{}') + with open(json_path, "w") as fp: + fp.write("{}") print(f"Created empty translations.json: {json_path}") # 2. Copy the actual compiled translations if they exist copies = [ ( - '/opt/invenio/src/.venv/lib/python3.12/site-packages/invenio_communities/assets/semantic-ui/translations/invenio_communities/messages/ga/translations.json', - '/opt/invenio/var/instance/assets/translations/invenio_communities/messages/ga/translations.json' + "/opt/invenio/src/.venv/lib/python3.12/site-packages/invenio_communities/assets/semantic-ui/translations/invenio_communities/messages/ga/translations.json", + "/opt/invenio/var/instance/assets/translations/invenio_communities/messages/ga/translations.json", ), ( - '/opt/invenio/src/.venv/lib/python3.12/site-packages/invenio_search_ui/assets/semantic-ui/translations/invenio_search_ui/messages/ga/translations.json', - '/opt/invenio/var/instance/assets/translations/invenio_search_ui/messages/ga/translations.json' - ) + "/opt/invenio/src/.venv/lib/python3.12/site-packages/invenio_search_ui/assets/semantic-ui/translations/invenio_search_ui/messages/ga/translations.json", + "/opt/invenio/var/instance/assets/translations/invenio_search_ui/messages/ga/translations.json", + ), ] for src, dst in copies: if os.path.exists(src): @@ -48,57 +49,68 @@ def main(): print(f"Copied compiled translations: {src} -> {dst}") # 3. Extract app_rdm translations from global ga.json - global_ga = '/opt/invenio/var/instance/translations/ga.json' - app_rdm_dst = '/opt/invenio/var/instance/assets/translations/invenio_app_rdm/messages/ga/translations.json' + global_ga = "/opt/invenio/var/instance/translations/ga.json" + app_rdm_dst = "/opt/invenio/var/instance/assets/translations/invenio_app_rdm/messages/ga/translations.json" if os.path.exists(global_ga): try: - with open(global_ga, 'r') as fp: + with open(global_ga, "r") as fp: ga_data = json.load(fp) - app_rdm_data = ga_data.get('invenio_app_rdm', {}) + app_rdm_data = ga_data.get("invenio_app_rdm", {}) os.makedirs(os.path.dirname(app_rdm_dst), exist_ok=True) - with open(app_rdm_dst, 'w') as fp: + with open(app_rdm_dst, "w") as fp: json.dump(app_rdm_data, fp, indent=2) print(f"Extracted invenio_app_rdm translations to {app_rdm_dst}") - + # Also extract search_ui and communities if they were not copied - search_ui_dst = '/opt/invenio/var/instance/assets/translations/invenio_search_ui/messages/ga/translations.json' - if 'invenio_search_ui' in ga_data: + search_ui_dst = "/opt/invenio/var/instance/assets/translations/invenio_search_ui/messages/ga/translations.json" + if "invenio_search_ui" in ga_data: os.makedirs(os.path.dirname(search_ui_dst), exist_ok=True) - with open(search_ui_dst, 'w') as fp: - json.dump(ga_data['invenio_search_ui'], fp, indent=2) + with open(search_ui_dst, "w") as fp: + json.dump(ga_data["invenio_search_ui"], fp, indent=2) print(f"Extracted invenio_search_ui translations to {search_ui_dst}") - - communities_dst = '/opt/invenio/var/instance/assets/translations/invenio_communities/messages/ga/translations.json' - if 'invenio_communities' in ga_data: + + communities_dst = "/opt/invenio/var/instance/assets/translations/invenio_communities/messages/ga/translations.json" + if "invenio_communities" in ga_data: os.makedirs(os.path.dirname(communities_dst), exist_ok=True) - with open(communities_dst, 'w') as fp: - json.dump(ga_data['invenio_communities'], fp, indent=2) - print(f"Extracted invenio_communities translations to {communities_dst}") + with open(communities_dst, "w") as fp: + json.dump(ga_data["invenio_communities"], fp, indent=2) + print( + f"Extracted invenio_communities translations to {communities_dst}" + ) except Exception as e: print(f"Error extracting translations: {e}") # 4. Patch all _generatedTranslations.js files - files = glob.glob('/opt/invenio/src/.venv/lib/python3.12/site-packages/**/_generatedTranslations.js', recursive=True) + glob.glob('/opt/invenio/var/instance/assets/**/_generatedTranslations.js', recursive=True) + files = glob.glob( + "/opt/invenio/src/.venv/lib/python3.12/site-packages/**/_generatedTranslations.js", + recursive=True, + ) + glob.glob( + "/opt/invenio/var/instance/assets/**/_generatedTranslations.js", recursive=True + ) for f in files: - with open(f, 'r') as fp: + with open(f, "r") as fp: content = fp.read() - if 'TRANSLATE_GA' not in content: - lines = content.split('\n') + if "TRANSLATE_GA" not in content: + lines = content.split("\n") last_import = -1 for i, line in enumerate(lines): - if line.startswith('import '): + if line.startswith("import "): last_import = i if last_import != -1: - lines.insert(last_import + 1, 'import TRANSLATE_GA from "./ga/translations.json";') - content = '\n'.join(lines) - if 'export const translations = {' in content: - content = content.replace( - 'export const translations = {', - 'export const translations = {\n ga: { translation: TRANSLATE_GA },' + lines.insert( + last_import + 1, + 'import TRANSLATE_GA from "./ga/translations.json";', ) - with open(f, 'w') as fp: + content = "\n".join(lines) + if "export const translations = {" in content: + content = content.replace( + "export const translations = {", + "export const translations = {\n ga: { translation: TRANSLATE_GA },", + ) + with open(f, "w") as fp: fp.write(content) print(f"Patched _generatedTranslations.js: {f}") -if __name__ == '__main__': + +if __name__ == "__main__": main() diff --git a/i-form-data-repository/site/i_form_data_repository/views.py b/i-form-data-repository/site/i_form_data_repository/views.py index c355ac8..338992e 100644 --- a/i-form-data-repository/site/i_form_data_repository/views.py +++ b/i-form-data-repository/site/i_form_data_repository/views.py @@ -2,6 +2,7 @@ from flask import Blueprint + # # Registration # diff --git a/i-form-data-repository/translations/ga.json b/i-form-data-repository/translations/ga.json index a6878ba..35b0b4b 100644 --- a/i-form-data-repository/translations/ga.json +++ b/i-form-data-repository/translations/ga.json @@ -1,23 +1,23 @@ { - "invenio_search_ui": { - "invenio_search_ui:Search records...": "Cuardaigh taifid..." - }, - "invenio_communities": { - "invenio_communities:You are not a member of any community.": "Níl tú mar bhall d'aon phobal.", - "invenio_communities:There are no new communities.": "Níl aon phobail nua ann.", - "invenio_communities:My Communities": "Mo Phobail", - "invenio_communities:New Communities": "Pobail Nua", - "invenio_communities:Search communities...": "Cuardaigh pobail...", - "invenio_communities:Organize, curate and share your research. Ideal for research projects, departments or temporary working groups.": "Eagraigh, coimeád agus comhroinn do chuid taighde. Ideal do thionscadail taighde, ranna nó grúpaí oibre sealadacha." - }, - "invenio_app_rdm": { - "invenio_app_rdm:Search records...": "Cuardaigh taifid...", - "invenio_app_rdm:Search in my communities": "Cuardaigh i mo phobail", - "invenio_app_rdm:Search in my communities...": "Cuardaigh i mo phobail...", - "invenio_app_rdm:My communities": "Mo phobail", - "invenio_app_rdm:Search in my uploads...": "Cuardaigh i mo chuid uaslódálacha...", - "invenio_app_rdm:My uploads": "Mo chuid uaslódálacha", - "invenio_app_rdm:Overview": "Foramharc", - "invenio_app_rdm:Uploads": "Uaslódálacha" - } + "invenio_search_ui": { + "invenio_search_ui:Search records...": "Cuardaigh taifid..." + }, + "invenio_communities": { + "invenio_communities:You are not a member of any community.": "Níl tú mar bhall d'aon phobal.", + "invenio_communities:There are no new communities.": "Níl aon phobail nua ann.", + "invenio_communities:My Communities": "Mo Phobail", + "invenio_communities:New Communities": "Pobail Nua", + "invenio_communities:Search communities...": "Cuardaigh pobail...", + "invenio_communities:Organize, curate and share your research. Ideal for research projects, departments or temporary working groups.": "Eagraigh, coimeád agus comhroinn do chuid taighde. Ideal do thionscadail taighde, ranna nó grúpaí oibre sealadacha." + }, + "invenio_app_rdm": { + "invenio_app_rdm:Search records...": "Cuardaigh taifid...", + "invenio_app_rdm:Search in my communities": "Cuardaigh i mo phobail", + "invenio_app_rdm:Search in my communities...": "Cuardaigh i mo phobail...", + "invenio_app_rdm:My communities": "Mo phobail", + "invenio_app_rdm:Search in my uploads...": "Cuardaigh i mo chuid uaslódálacha...", + "invenio_app_rdm:My uploads": "Mo chuid uaslódálacha", + "invenio_app_rdm:Overview": "Foramharc", + "invenio_app_rdm:Uploads": "Uaslódálacha" + } } diff --git a/index.html b/index.html deleted file mode 100644 index a511de8..0000000 --- a/index.html +++ /dev/null @@ -1,443 +0,0 @@ - - - - - - - - - I-Form Data Repository - - - - - - - - - - - -
-

Test Instance

-

This is the test instance of the I-Form Repository.

-
- - -
-
- - - - - - - -
-
-
- - -
- - -
-
-
-
-

Research Results

- -
Frontpage: Description for research results
- - -
-
-
-
-

Publications

- -
Frontpage: Description for publications
- - -
-
-
-
-

Educational Resources

- -
Frontpage: Description for open educational resources
- - -
-
-
-
- - - -
-

Recent uploads

- - -
-

There are no public records to show.

-
- - - -
- - -
- - -
-

Need help?

-

We can help with:

- -
    -
  • Uploading your research results, software, preprints, etc.
  • -
  • Increasing upload limit beyond our default policy of 10GB.
  • -
  • Establishing contact with data stewards.
  • -
  • Finding individual solutions.
  • -
-
- - -
-

Good reasons to use the I-Form Repository

- -
    -
  • Safe — Your research results are permanently available.
  • -
  • - Trusted — A service from I-Form, based on software developed by - CERN. -
  • -
  • - Citeable — Every upload is assigned a Digital Object Identifier - (DOI). -
  • -
  • - No waiting time — Uploads are made available online as soon as - you hit publish. Your DOI is registered within seconds. -
  • -
  • - Open or closed — You determine the visibility of your uploads. A - restricted access mode is possible. -
  • -
  • - Versioning — Easily update your dataset with the versioning - feature. -
  • -
  • - Usage statistics — All uploads display standards compliant usage - statistics. (More) -
  • -
-
-
-
-
- - - - - - - - - - - - - - - - -
- - - - - - \ No newline at end of file diff --git a/justfile b/justfile deleted file mode 100644 index cdaee06..0000000 --- a/justfile +++ /dev/null @@ -1,161 +0,0 @@ -update auto-rollback="false": - #!/usr/bin/env bash - if [ ! -f update.lock ]; then - touch update.lock - just tag-version - cd i-form-data-repository && docker compose -f docker-compose.full.yml --env-file ../.env down - git pull - cd i-form-data-repository && docker compose -f docker-compose.full.yml --env-file ../.env pull - cd i-form-data-repository && docker compose -f docker-compose.full.yml --env-file ../.env build - cd i-form-data-repository && docker compose -f docker-compose.full.yml --env-file ../.env up -d --wait - rm update.lock - if [ "{{auto-rollback}}" = "true" ]; then - just healthcheck || just rollback - else - just healthcheck || echo "Healthcheck failed. Consider rolling back by running \"just rollback\" manually if needed." - fi - else - echo "Update already in progress" - fi - -merge-and-push-prod: - #!/usr/bin/env bash - git switch prod - git merge main - git switch main - git push --all - -tag-version: - #!/usr/bin/env bash - git tag backup-$(date +%Y%m%d-%H%M%S) - mkdir -p versions - cd i-form-data-repository && docker compose -f docker-compose.full.yml --env-file ../.env images | grep -v "REPOSITORY" > ../versions/$(date +%Y%m%d-%H%M%S).txt - -healthcheck: - #!/usr/bin/env bash - for i in 1 2 3; do - curl -f http://localhost/health && exit 0 - sleep 5 - done - exit 1 - -rollback version="": - #!/usr/bin/env bash - current_branch=$(git branch --show-current) - if [ -z "{{version}}" ]; then # Get last backup tag if no version specified - version=$(git tag | grep backup | sort -r | head -n1) - else # Or get the specified version - version="backup-{{version}}" - fi - - # Find corresponding version file - version_date=${version#backup-} # Remove 'backup-' prefix - version_file="versions/$version_date.txt" - - if [ ! -f "$version_file" ]; then - echo "No version file found for $version" - exit 1 - fi - - # Read the old image versions and pull them specifically - while read -r repo tag image_id _; do - if [ ! -z "$repo" ]; then - docker pull "$repo:$tag" - fi - done < "$version_file" - - # Then check out the version and update - git checkout $version - cd i-form-data-repository && docker compose -f docker-compose.full.yml --env-file ../.env down - cd i-form-data-repository && docker compose -f docker-compose.full.yml --env-file ../.env build --no-cache - cd i-form-data-repository && docker compose -f docker-compose.full.yml --env-file ../.env up -d - git switch $current_branch # Return to original branch - -cleanup-versions: - #!/usr/bin/env bash - # Keep last 5 backup tags - for tag in $(git tag | grep backup | sort -r | tail -n +6); do - git tag -d $tag - rm -f "versions/${tag#backup-}.txt" - done - -deploy *args: - ./env.sh {{args}} - cd i-form-data-repository && docker compose -f docker-compose.full.yml --env-file ../.env up -d --wait - cd i-form-data-repository && docker compose -f docker-compose.full.yml --env-file ../.env exec worker bash -c "invenio db init || true; invenio db create || true; invenio alembic upgrade || true; invenio index init || true" - -fmt: - bun run prettier --write "**/*.{js,jsx,ts,tsx,html,css,scss,sass,svelte,yaml,json,markdown}" - -[working-directory("../invenio-theme-iform/")] -build-theme-wheel: - rm -rf dist - direnv exec . uv build --package invenio-theme-iform - -[working-directory("../invenio-config-iform/")] -build-config-wheel: - rm -rf dist - direnv exec . uv build --package invenio-config-iform - -build-wheels: build-theme-wheel build-config-wheel - echo "Building fresh wheels for local packages..." - mkdir -p i-form-data-repository/local_wheels - rm -f i-form-data-repository/local_wheels/*.whl - cp ../invenio-theme-iform/dist/*.whl i-form-data-repository/local_wheels/ - cp ../invenio-config-iform/dist/*.whl i-form-data-repository/local_wheels/ - -test-local: build-wheels - #!/usr/bin/env bash - echo "Rebuilding and restarting local docker stack..." - cd i-form-data-repository - docker compose -f docker-compose.full.yml --env-file ../.env down - docker build -t i-form-data-repository:latest --no-cache --build-arg INSTALL_LOCAL_WHEELS=true . - docker compose -f docker-compose.full.yml --env-file ../.env up -d --wait - docker compose -f docker-compose.full.yml --env-file ../.env exec worker bash -c "invenio db init || true; invenio db create || true; invenio alembic upgrade || true; invenio index init || true" - curl -skI https://127.0.0.1:8443/ || echo "HTTPS verification failed" -# Production deployment commands using Docker/Podman - -# Load AWS secrets (adjust path as needed) -load-aws-secrets: - @if [ -f aws_secrets.sh ]; then . ./aws_secrets.sh; else echo "AWS secrets file not found"; exit 1; fi - -# First-time deployment to production -prod-deploy: load-aws-secrets build-wheels - #!/usr/bin/env bash - echo "Deploying to production (first-time)..." - git switch prod - git pull origin prod - # Build and start containers - cd i-form-data-repository - docker build -t i-form-data-repository:latest --no-cache --build-arg INSTALL_LOCAL_WHEELS=true . - docker compose -f docker-compose.full.yml --env-file ../.env up -d --wait - # Run initial database setup, migrations, and collect static assets - docker compose -f docker-compose.full.yml --env-file ../.env exec worker bash -c " \ - invenio db init && \ - invenio db create && \ - invenio alembic upgrade head && \ - invenio collect -v && \ - invenio index init \ - " - -# Update production without full redeploy (pull + migrations + static) -prod-update: load-aws-secrets build-wheels - #!/usr/bin/env bash - echo "Updating production deployment..." - git switch prod - git pull origin prod - cd i-form-data-repository - docker build -t i-form-data-repository:latest --no-cache --build-arg INSTALL_LOCAL_WHEELS=true . - docker compose -f docker-compose.full.yml --env-file ../.env up -d --wait - docker compose -f docker-compose.full.yml --env-file ../.env exec worker bash -c " \ - invenio alembic upgrade head && \ - invenio collect -v \ - " - -# Clean all build artefacts and environment -prod-clean: - @echo "Cleaning production build artefacts..." - rm -rf .venv build dist *.egg-info - find . -type d -name "__pycache__" -exec rm -r {} + || true - rm -rf static node_modules - echo "Clean complete. Re‑create environment before next deploy." diff --git a/pyproject.toml b/pyproject.toml index 8ecea8b..a526965 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,4 +4,50 @@ version = "0.1.0" description = "The source code for the I-Form Data Repository" readme = "README.md" requires-python = ">=3.12" -dependencies = ["invenio-cli>=1.9.2"] +dependencies = [ + "boto3>=1.43.68", + "bumpver>=2026.1132", + "click-repl>=0.3.0", + "invenio-cli>=1.9.2", + "loguru>=0.7.3", + "plumbum>=2.0.2", + "pydantic-settings>=2.15.0", + "python-dotenv>=1.2.2", + "requests>=2.32.5", + "trustme>=1.2.1", + "typer>=0.27.1", +] + +[project.scripts] +cli = "cli.__main__:app" + +[dependency-groups] +dev = [ + "pre-commit>=3.5.0", + "ruff>=0.16.2", + "ty>=0.0.70", +] + +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[tool.ruff.lint] +ignore = ["BLE001", "B008", "S110", "C408"] + +[tool.hatch.build.targets.wheel] +packages = ["cli"] + +[tool.bumpver] +current_version = "2026.8.1" +version_pattern = "YYYY.MM.INC0" +commit_message = "bump version {old_version} -> {new_version}" +commit = true +tag = true +push = false + +[tool.bumpver.file_patterns] +"pyproject.toml" = [ + 'version = "{version}"', +] + diff --git a/uv.lock b/uv.lock index 99bfa95..740e52e 100644 --- a/uv.lock +++ b/uv.lock @@ -2,6 +2,24 @@ version = 1 revision = 3 requires-python = ">=3.12" +[[package]] +name = "annotated-doc" +version = "0.0.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5a/8e/38aa427ed5402449e226975b649c5dc73ccadfefeb95e6aecb8f8ea4b6b6/annotated_doc-0.0.5.tar.gz", hash = "sha256:c7e58ce09192557605d8bbd92836d7e1d520ac9580096042c0bfd197efacf1bb", size = 10758, upload-time = "2026-07-28T13:50:58.129Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3e/30/e900b21425a860e195f32e37657aa1f7c7f2b1bfb26f03ca209b90933c06/annotated_doc-0.0.5-py3-none-any.whl", hash = "sha256:117bac03a25ede5df5440e855b32d556049ca169ead221505badf432fed4b101", size = 5302, upload-time = "2026-07-28T13:50:57.239Z" }, +] + +[[package]] +name = "annotated-types" +version = "0.8.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5f/56/a8120250d128bed162cd73c76d45f6ef9991f3e068f62a8ee060afa3104a/annotated_types-0.8.0.tar.gz", hash = "sha256:13b2beaad985e05e2d6407ee4c4f35590b11f8d693a258a561055cac8f64cab7", size = 15893, upload-time = "2026-07-23T20:16:13.995Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/99/91/8acff4f5e50511b911bbccb72b8628a49c68ce14148cd9f6431094859a90/annotated_types-0.8.0-py3-none-any.whl", hash = "sha256:f072f4d804ea359e4eaf198b1af7a8b0943881a87f31bb764f8bf219bb9419e0", size = 13427, upload-time = "2026-07-23T20:16:12.938Z" }, +] + [[package]] name = "arrow" version = "1.4.0" @@ -36,6 +54,49 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/24/7e/f7b6f453e6481d1e233540262ccbfcf89adcd43606f44a028d7f5fae5eb2/binaryornot-0.4.4-py2.py3-none-any.whl", hash = "sha256:b8b71173c917bddcd2c16070412e369c3ed7f0528926f70cac18a6c97fd563e4", size = 9006, upload-time = "2017-08-03T15:55:31.23Z" }, ] +[[package]] +name = "boto3" +version = "1.43.68" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "botocore" }, + { name = "jmespath" }, + { name = "s3transfer" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/35/40/95db6388539e6194b7d8863e4228263e7a778fff0164b17d0e4530d44ce2/boto3-1.43.68.tar.gz", hash = "sha256:4be7531c45fbf8eb145ecd0f385c7b8f9d66ba2fd0c256522717260e67fca89d", size = 112664, upload-time = "2026-08-10T19:22:07.864Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/96/bd/36dd3f5718160ea7f8ef5a81b461cfb9d0f0b289bb31ed416ae6a1cbfee3/boto3-1.43.68-py3-none-any.whl", hash = "sha256:4be071482312d05ea345ae8a2ea307637d3e2f4fdce44b8ebeb0192929aa2644", size = 140027, upload-time = "2026-08-10T19:22:05.592Z" }, +] + +[[package]] +name = "botocore" +version = "1.43.68" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jmespath" }, + { name = "python-dateutil" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e1/dc/ff7e35ecb25e2584e22ce8d5a13c5f991687597fd77d7cc388f2e4768cf3/botocore-1.43.68.tar.gz", hash = "sha256:a6c7ac88724c96f2ad5a7657f87d545d8218a74a1b219ba4397e3d37481941cd", size = 15894825, upload-time = "2026-08-10T19:22:02.501Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/32/48/18dc095a908da94e2389d8e4127f4438b185dfd2dd9190fe6f946b9f41ac/botocore-1.43.68-py3-none-any.whl", hash = "sha256:9985c6eb9b7896f88bd89c07d543accd6985ea772af9d796087012ad40f78420", size = 15579433, upload-time = "2026-08-10T19:21:58.157Z" }, +] + +[[package]] +name = "bumpver" +version = "2026.1132" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "colorama" }, + { name = "lexid" }, + { name = "toml" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/25/22/c0c9bf9e7ad6877e4c7c3dedd464e390d9fadb8927c61d6d77547eb17539/bumpver-2026.1132.tar.gz", hash = "sha256:80b223c23fca9bc9dd569b7a44680949d34bee23a738860d9a9b36f1abe3b0e0", size = 116784, upload-time = "2026-05-22T18:40:32.333Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ab/d6/45c1bf6bcf7b3f174ec0c7fe2d7dce773cbc853cfd8fde06c3153104ffff/bumpver-2026.1132-py2.py3-none-any.whl", hash = "sha256:b060170e7ff5d9ad7043a9d3d6288c7175f9c126744d08a2d6ef9b8377f6bd60", size = 65963, upload-time = "2026-05-22T18:40:30.515Z" }, +] + [[package]] name = "certifi" version = "2025.11.12" @@ -45,6 +106,100 @@ 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 = "cffi" +version = "2.1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pycparser", marker = "implementation_name != 'PyPy'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9e/ef/008a1939e372c06329a3fce4279c02f328488f3526744906eeec3da7ad5f/cffi-2.1.1.tar.gz", hash = "sha256:dd31f52ea1086513bb9df30f8fcee9b8918323ae067a3d5b78bc826a000712be", size = 530807, upload-time = "2026-08-03T21:21:18.939Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/10/69/43965eccfdead3b9220015fd1320e117be8c6ed01a62ffab76eeb752f5d5/cffi-2.1.1-cp312-cp312-macosx_10_15_x86_64.whl", hash = "sha256:c8c69575568085ba0b1b10c0249d779a214aea6f6522e949a0fc9fb0fcb449d0", size = 184821, upload-time = "2026-08-03T21:19:44.887Z" }, + { url = "https://files.pythonhosted.org/packages/54/7d/16e5a096677b5e313ca80cd5e5170efa3ea44624a82bb111925522da64b1/cffi-2.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f81b3b8f3d4e343550fa4baa0e479bba9f2d29ce9c2e9b51d1ce1718d7442fcf", size = 184719, upload-time = "2026-08-03T21:19:46.129Z" }, + { url = "https://files.pythonhosted.org/packages/56/e6/8941622732edec876dd17d0453dce07317ae96db34f2ec1436c9d3785986/cffi-2.1.1-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:811bd1e21d32de12efca32393a0ab3f5133b54fce9bd44b8bd77ab07da14bf6a", size = 214799, upload-time = "2026-08-03T21:19:47.218Z" }, + { url = "https://files.pythonhosted.org/packages/44/de/f98430906df1545ffde0d543dd124a7a439bc2cd32b36b9c53f805df7333/cffi-2.1.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:68e62fe11f30d5ca8289242866f0a5291402d8529ca2178ab8afc5c9694ae890", size = 222389, upload-time = "2026-08-03T21:19:48.331Z" }, + { url = "https://files.pythonhosted.org/packages/6a/5b/717f1526b9957b34456313c31645c5b82b8fb5c3fe9e4752999be7128bfc/cffi-2.1.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:4a7c934f7360e8cd64fe9efadcbd10c7c6364f531e432b9a4bf5ccbc9e0e8b50", size = 210249, upload-time = "2026-08-03T21:19:49.543Z" }, + { url = "https://files.pythonhosted.org/packages/64/b3/f8aa4f3e34986c7e4ec45072d1b1b9dd295b6b18007b45518d79726dd725/cffi-2.1.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:3143d81e29e1e20a9ce10901ec369012947876596f75a222235965f2b7ae832e", size = 208775, upload-time = "2026-08-03T21:19:50.918Z" }, + { url = "https://files.pythonhosted.org/packages/b1/db/dceb9dd5b231e1da801793f8acc9f3c52a7e1afe40bb1aae37e02b0faad5/cffi-2.1.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c1453022f490d2459a11819d83ad1d586e9ff65a12ac3e705ffebd46d3685dcf", size = 221822, upload-time = "2026-08-03T21:19:52.054Z" }, + { url = "https://files.pythonhosted.org/packages/a0/d2/6cd24ae3be000a634109c247d1475d62e5616d0dc78c82770942ec384248/cffi-2.1.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:208f941bb9d18e768138677f0a6d2ce01f590df56043dda1df1535ac57c88517", size = 225232, upload-time = "2026-08-03T21:19:53.109Z" }, + { url = "https://files.pythonhosted.org/packages/cb/52/3fa190537004dd7f0ab860a6dc7c0175b8667f68d1e618a46f5498d30250/cffi-2.1.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:210019b6c7cf07f081b4c54635c8cf744377001350e29cc0f81c4377b4797735", size = 223597, upload-time = "2026-08-03T21:19:54.515Z" }, + { url = "https://files.pythonhosted.org/packages/80/fb/0bb75b7039588c074b37ae99f40d9bfddf990ecb2fbc346ebccd2e56b9be/cffi-2.1.1-cp312-cp312-win32.whl", hash = "sha256:046bfc24911b37851ee1b51aab8bffe713d89c68c6a057b09484ce9fd5f69b4e", size = 175292, upload-time = "2026-08-03T21:19:55.566Z" }, + { url = "https://files.pythonhosted.org/packages/d9/79/615cc094e2fb508cade7de88d3b4f6c4ec2bab695c97bce9153dc65aadf5/cffi-2.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:f53e442b08449d42821fa4a4fba000095af9f62742a500f978a9f557ec44339a", size = 185919, upload-time = "2026-08-03T21:19:56.89Z" }, + { url = "https://files.pythonhosted.org/packages/70/c6/d0ea84713fe46b243a436a18fcd47d639732747e21635c8a27191b06dc30/cffi-2.1.1-cp312-cp312-win_arm64.whl", hash = "sha256:7bde5e4cc5c10140859842b9d383af292b22639a4dffb725314baf45968cef80", size = 180093, upload-time = "2026-08-03T21:19:58.155Z" }, + { url = "https://files.pythonhosted.org/packages/9d/f4/035513d4117049066b4779dc3b7c0c0fdad175fa13731c9f4003f1cd1478/cffi-2.1.1-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:b5bdfd1c873d4e093aabc0ca84c4ca6dbc4f752afb5c86f146d9742580c9da2e", size = 194248, upload-time = "2026-08-03T21:19:59.399Z" }, + { url = "https://files.pythonhosted.org/packages/76/af/2aeb4dbb5fc41a04161ae9ff1518de7cec08e164f44a8ce6a4cf7fd2cd1d/cffi-2.1.1-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:31348097ff5bbe827ccc41795d4dd099d9f0625e7def00ee653c137a490c2a6c", size = 196908, upload-time = "2026-08-03T21:20:00.746Z" }, + { url = "https://files.pythonhosted.org/packages/a7/46/2e5fdde8555706dd98139a910ca11be02809f3f605ce956f655d0214e100/cffi-2.1.1-cp313-cp313-macosx_10_15_x86_64.whl", hash = "sha256:9d2055050ea716bd38b7f7f1579c275386646b4894c155a3e2f3cd62ed41b7c6", size = 184805, upload-time = "2026-08-03T21:20:02.02Z" }, + { url = "https://files.pythonhosted.org/packages/55/41/4c7042f317b9217502988f0873af87e16ad606dc20f84e546e3e6ce9764c/cffi-2.1.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:19ee6127ee34de7d83ce3d371ebc5ed91addbdcc39f9ab15ce4eb35a4e534971", size = 184764, upload-time = "2026-08-03T21:20:03.141Z" }, + { url = "https://files.pythonhosted.org/packages/43/1f/1c3d90d91811c8f86ced9ed637956c54bfe5b79ca98fe976d7f8c8979f6b/cffi-2.1.1-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:6a8dddef476fab96d066d578fc88526767b836ab5ab21754e1d5bf3879c31c7c", size = 214722, upload-time = "2026-08-03T21:20:04.377Z" }, + { url = "https://files.pythonhosted.org/packages/37/6f/3b5ce4c3b2192d250f04908f2bfd91ef34552ec8f7716a5d4abdb8d67bb2/cffi-2.1.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f16c709686a78c727bbbf059f92b0bf41c6fc60deec706d2dc19f529175a6125", size = 222369, upload-time = "2026-08-03T21:20:05.544Z" }, + { url = "https://files.pythonhosted.org/packages/02/10/4b3c75dde3d9663c9e02ba05c2668b954f671d4bbe346413ca8c696b295a/cffi-2.1.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:fcd22650c908d7b7da162bbfaab594a1227a15d1643a98c68b122ac642fa2264", size = 210175, upload-time = "2026-08-03T21:20:06.75Z" }, + { url = "https://files.pythonhosted.org/packages/df/62/14f74b9543e605d17701dc797b815958b8bb70b7624ce1b832ddad48ed6c/cffi-2.1.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:aa9511c62d14da7aacc9b4bf51f3f697a621e83b2d6919008243c3aad168eea3", size = 208670, upload-time = "2026-08-03T21:20:08.04Z" }, + { url = "https://files.pythonhosted.org/packages/95/95/86342356ff5953b3fb06f7ef7c5bee212d45e770abc7218d451b9148313c/cffi-2.1.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a931079504ecc49efed7744c476a5c343a92fabf66dec2db95edb1b2fdc770e2", size = 221824, upload-time = "2026-08-03T21:20:09.274Z" }, + { url = "https://files.pythonhosted.org/packages/eb/ff/7b3429ff53aafe931ed8a5fc69f481bbef7ba6de87ddcbb63d08f483f613/cffi-2.1.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a2d7755bef5a12ed488f4ef1f1b69ee9191d7396083b755a5d2295f6edb4768b", size = 225148, upload-time = "2026-08-03T21:20:10.7Z" }, + { url = "https://files.pythonhosted.org/packages/34/34/a95870b9221e09cf4f2ce3178b1a210abdfe63a1bd357da940418d7b8d15/cffi-2.1.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e0bcb7e0f677f543555d2adff3bf19c05f66cdb4796e5ff602442ab2fe3c4ef7", size = 223564, upload-time = "2026-08-03T21:20:12.165Z" }, + { url = "https://files.pythonhosted.org/packages/70/ea/839b50531021a647fb5e929f72cf97bc1ff702b5472166164b5b6e76b851/cffi-2.1.1-cp313-cp313-win32.whl", hash = "sha256:334644fbac4eff73d985a17a91226df55d0f394160c4cfb880e084c8f7161cac", size = 175263, upload-time = "2026-08-03T21:20:13.559Z" }, + { url = "https://files.pythonhosted.org/packages/60/a6/8b149b2c3f2e11aaa1618ef64500b45f50f22c57a977a4dff1aff1f91042/cffi-2.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:1aa5645c30469b09530c4ebca77ebf8f17618293c58f8549cb1a543a50236e7d", size = 185688, upload-time = "2026-08-03T21:20:14.69Z" }, + { url = "https://files.pythonhosted.org/packages/01/9a/11f687cb39d6a3504060d5242f04f48c735afb4d3d533958a20594890cb2/cffi-2.1.1-cp313-cp313-win_arm64.whl", hash = "sha256:63bbfd5ded17c4840ac07cd8f1c21ba9d9708141f840b324f422f41b207e3973", size = 180078, upload-time = "2026-08-03T21:20:15.917Z" }, + { url = "https://files.pythonhosted.org/packages/d3/7b/d6bbf82b8b96e7391438898c42f5bd96dd02030fd5b64937d248220003e2/cffi-2.1.1-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:7dbb61fe3a7699468030f71bbe5f8a0e326a151daa91beb11a6fc1f980c55e1c", size = 194064, upload-time = "2026-08-03T21:20:17.148Z" }, + { url = "https://files.pythonhosted.org/packages/94/e6/bcc91b283be94735e268487a054004f0aa19947b6348fa367db53230abc8/cffi-2.1.1-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:f24fb43132a4c6b4cb4eb029492919b2db645be6808d738f244fd146c03c32cb", size = 196720, upload-time = "2026-08-03T21:20:18.268Z" }, + { url = "https://files.pythonhosted.org/packages/d9/99/c4b0c17cacdc9c3b8f280026286a9826d6a208c0f047591a3c3ce99b91fd/cffi-2.1.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:d28630f5854ab07ab1fd4aba756de52326c82e6be15d414b12793f1975048b54", size = 184964, upload-time = "2026-08-03T21:20:19.708Z" }, + { url = "https://files.pythonhosted.org/packages/b3/a9/9db617d05d7367c1ad0ab00b3aa6e6f9281edd689b4ee9ea0e5a84e89c97/cffi-2.1.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:661c298b4821edebead0c91edd2b00374d67ad7c5a1f7a91d4442633b79d6a72", size = 184962, upload-time = "2026-08-03T21:20:20.833Z" }, + { url = "https://files.pythonhosted.org/packages/67/b8/b42132ca113dc567d37684437b46ca1dafc885902b02a110a02d5b511857/cffi-2.1.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:58acb8ab8e295e6c5ea12f888cbb13cf21511ef2a3303a23f4325c29d17fe5c1", size = 222328, upload-time = "2026-08-03T21:20:22.118Z" }, + { url = "https://files.pythonhosted.org/packages/80/10/c5c0cbf0a657aecf59ef511409734230bf556f05a0d6c9eed7aa5c0a0166/cffi-2.1.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:456a61fa52d579ebf9df2e9552ead5129855dbaff6c1e5a9b1bc408809bdc062", size = 209985, upload-time = "2026-08-03T21:20:23.401Z" }, + { url = "https://files.pythonhosted.org/packages/d5/6c/bfa0b87b03b9238148beca990292843c9396ba069b54496596594173de7b/cffi-2.1.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:a4f00aa42f75d6e4595e8866e748cc1705adc0cddfeb2ca86d0d03993d63ba03", size = 208530, upload-time = "2026-08-03T21:20:24.628Z" }, + { url = "https://files.pythonhosted.org/packages/e9/02/4e7d553a7ac4b4238b38b3c1b80d486e9d4436f8d2acbf87a0997fe3f402/cffi-2.1.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b0431303acaea1089ad4b3e9ce4e6518193def1118d4073ca848635ee4ea2e96", size = 221525, upload-time = "2026-08-03T21:20:25.758Z" }, + { url = "https://files.pythonhosted.org/packages/82/1d/a4aaf9babd75acb4d5f223bff71533bee748dd770a382619a798960ee9ba/cffi-2.1.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:64faea20f4e2613363a1a9b9c7dd73058f3ecd00133a511e72ad7c511658f527", size = 225053, upload-time = "2026-08-03T21:20:26.985Z" }, + { url = "https://files.pythonhosted.org/packages/81/10/5dc0e7bdd18e22107054288283380fc97a06ae3f1656a106908d666a3c88/cffi-2.1.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5c58fe613dc5e5336357eff555824a314d8e43282600435c8d1cb6a7a2fedd13", size = 223213, upload-time = "2026-08-03T21:20:28.277Z" }, + { url = "https://files.pythonhosted.org/packages/0b/e9/d0061c364cde06ee43168a0d076ac1da512cbc380d44767b844ba34fe2b6/cffi-2.1.1-cp314-cp314-win32.whl", hash = "sha256:1a18a57b58cfb21fc28d72e876acf10eaed67a1ed96226f92af4df681d571c4c", size = 177682, upload-time = "2026-08-03T21:20:44.288Z" }, + { url = "https://files.pythonhosted.org/packages/a7/06/1c3e01e3ba14c39f6d10bfbac52753b7e22259e38088e5cfe1d704918690/cffi-2.1.1-cp314-cp314-win_amd64.whl", hash = "sha256:3222ba5d678f80a030e6afbcc33dc1ae5cb45facabb61cee2c7016b8432fde48", size = 187949, upload-time = "2026-08-03T21:20:45.623Z" }, + { url = "https://files.pythonhosted.org/packages/87/5b/da4e39efe18eeb89cf580ea9cfc66b6a7c3eadb808fc0cc1d3a295cb5a5d/cffi-2.1.1-cp314-cp314-win_arm64.whl", hash = "sha256:ab36d55f9ed2d067327667c2fea18dda018eb628dd6347aa01dda6cf1f5d3836", size = 182947, upload-time = "2026-08-03T21:20:46.955Z" }, + { url = "https://files.pythonhosted.org/packages/23/59/40338bf421c5accea1d45158170c87006ef1cd371b05c077e76476949728/cffi-2.1.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:7750c6449dff7864bb9bb27ddfb0267756189201a3afc911d82b3caacd70dfc3", size = 188504, upload-time = "2026-08-03T21:20:29.495Z" }, + { url = "https://files.pythonhosted.org/packages/7d/47/5ecf1023850036e674c77ec4de86182d309ae344e39e7cba984b7df5d647/cffi-2.1.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:0beceaabe56af686895136a2de78db54ecd8e4046b236b8fd6d6cb61389e9bf2", size = 188259, upload-time = "2026-08-03T21:20:31.291Z" }, + { url = "https://files.pythonhosted.org/packages/2a/9c/92934c3bea9f785b23eba304538c0b4d37a2a96d2431eb3a1bc87a11aa19/cffi-2.1.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:49cbc70e6542d4ccccb936558d1064a8012541e78f821f955cff24e357776c94", size = 223864, upload-time = "2026-08-03T21:20:32.571Z" }, + { url = "https://files.pythonhosted.org/packages/4d/45/ba4c93527bc38616a8bd36488acb69a2212d60486794f0c1f318949bbb76/cffi-2.1.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:e2d65b31f36619cda3999b78b2aa9632e76b78448e7a56fc4240824200e7c4fc", size = 211538, upload-time = "2026-08-03T21:20:33.808Z" }, + { url = "https://files.pythonhosted.org/packages/80/e9/b6ef565e452acb932fb0cb5443f44a78efbd1233e566f02b5a83855e9115/cffi-2.1.1-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:28907ab9bfb6aa13184cfc17c6b8e1023c5ab6fd7076d8c20a35e59fe04f8f29", size = 210688, upload-time = "2026-08-03T21:20:34.974Z" }, + { url = "https://files.pythonhosted.org/packages/9a/95/eff5f0cee78d2eabc7eebffec40d3fc1876b5f3c95582e018bb4b99601f2/cffi-2.1.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:51b31d1c98274844cfd7838ce00bfc27c7423a4dc00fc0772fc3331c2cc90676", size = 223803, upload-time = "2026-08-03T21:20:36.564Z" }, + { url = "https://files.pythonhosted.org/packages/fa/01/579d39fb8bef00a335a23d83757b44feb24cd6345a2c451b64cb67b9c362/cffi-2.1.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5e7cecbaadb83884793e05828cee59b210b24583b9c7425d0ba6a754fe22eb4e", size = 226763, upload-time = "2026-08-03T21:20:37.816Z" }, + { url = "https://files.pythonhosted.org/packages/8d/b0/0b44f47c60b01b57b6e2bbd92343f13a85a1d93bc46ccf6e47e244acd99c/cffi-2.1.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:25792eac27877609e7bb06d42ff88278a6624fff2ba9bbb523c09616b117e80f", size = 225688, upload-time = "2026-08-03T21:20:38.959Z" }, + { url = "https://files.pythonhosted.org/packages/eb/d2/3b7176cb570a1d3e27faf67b72f591af508036e0d8b2be2ef9af9e8c84bb/cffi-2.1.1-cp314-cp314t-win32.whl", hash = "sha256:8ef53b2de9bcb9197d31854256575d59dbac0cba72ac627bb291ef5eceb74be4", size = 182868, upload-time = "2026-08-03T21:20:40.388Z" }, + { url = "https://files.pythonhosted.org/packages/56/78/31f00c1bcd97c9bbf55f1bfdf5bc809a5de8887473e90bb9960dca825e80/cffi-2.1.1-cp314-cp314t-win_amd64.whl", hash = "sha256:616f097f2fe415bc92a247f02e11f634e1f9e9a83d327e3c915c15089c87869e", size = 194104, upload-time = "2026-08-03T21:20:41.725Z" }, + { url = "https://files.pythonhosted.org/packages/7b/1b/58496f2ed0a35de575250c02a43ab3cc2c04d494a88fed31c1cabc0fd176/cffi-2.1.1-cp314-cp314t-win_arm64.whl", hash = "sha256:ad2c86c495b899d862ea0f4b42891b8713a3bd45dd4105c7fd51c2a72f39f3a5", size = 186402, upload-time = "2026-08-03T21:20:43.042Z" }, + { url = "https://files.pythonhosted.org/packages/c1/8f/9ebe220eab48a093d1a5a5e339ab0dc7316eef3bb04d63c42f0251b61f50/cffi-2.1.1-cp315-cp315-ios_13_0_arm64_iphoneos.whl", hash = "sha256:dddad92b554513a31f272570678ba307fb9f618f05e3d4a5eacafff9eae03e1d", size = 194043, upload-time = "2026-08-03T21:20:48.179Z" }, + { url = "https://files.pythonhosted.org/packages/ff/69/844bad3ece306c4782c2ecb93597035b6690d48704b803914c199da1e8b3/cffi-2.1.1-cp315-cp315-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:da0e573f9f97159390c89d9f1a9e41908b66d408cc5b58d08cf3847d844c531b", size = 196737, upload-time = "2026-08-03T21:20:49.457Z" }, + { url = "https://files.pythonhosted.org/packages/1b/8a/af668013284634733f02d683458a0728739c7d6ddb5e14cb0c20832266fe/cffi-2.1.1-cp315-cp315-macosx_10_15_x86_64.whl", hash = "sha256:fb92203a88b3d3053034db775110081c49d28be6551923805e039924093761e4", size = 184933, upload-time = "2026-08-03T21:20:50.639Z" }, + { url = "https://files.pythonhosted.org/packages/0c/75/2f5207ff6d1a613133b23a5203cc0c2a628313b5eb3974d7956ae3c57950/cffi-2.1.1-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:2ae64be792b8966f2c69538199728b290e34726562896df1e5dc8ffd8d8188e8", size = 185002, upload-time = "2026-08-03T21:20:52.173Z" }, + { url = "https://files.pythonhosted.org/packages/e2/31/9e1313b0a6e30e91b3b3d3fff51ae99c857c07738e3afcce1f7334e1b7ab/cffi-2.1.1-cp315-cp315-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:507a24c282e0f42f8ed737cf048572cbf580468da5555764a8331735e9c736b6", size = 222271, upload-time = "2026-08-03T21:20:53.462Z" }, + { url = "https://files.pythonhosted.org/packages/50/e3/f6234a833e6e08c7007003074723c406559eecf9b48dfc97471e5a8eb7a0/cffi-2.1.1-cp315-cp315-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:246fa40ce8645a614ff682e0b70f37134e460eaf93a775e0cbe3cca585a67a80", size = 209919, upload-time = "2026-08-03T21:20:54.783Z" }, + { url = "https://files.pythonhosted.org/packages/0d/fc/5f74e293fced6edb51af3a46c4ccf6c23c9943774ecb375ddbd522c76add/cffi-2.1.1-cp315-cp315-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:471cee653ae88de62096552e6d24ccb4a5adb8c8c9f10b5054d0122c15bf2779", size = 208529, upload-time = "2026-08-03T21:20:56.066Z" }, + { url = "https://files.pythonhosted.org/packages/44/16/29e6d01b388bef055ecd6ca8244b3f4d336bd09e92d5d892187b9601084e/cffi-2.1.1-cp315-cp315-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:aeae0e330c9f6acd681f647d46cefd30c29f93e3392882e792e82080c9691399", size = 221630, upload-time = "2026-08-03T21:20:57.336Z" }, + { url = "https://files.pythonhosted.org/packages/a4/18/fa7f1f6857d5eb88a4ca99ffcbfb7c387a287ccc154c64a73e86314745d7/cffi-2.1.1-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:42a494cee34437f05546455144f2b5d9ac09b1face62bcfce597d2e521066688", size = 225134, upload-time = "2026-08-03T21:20:58.675Z" }, + { url = "https://files.pythonhosted.org/packages/e0/9f/e8e3dfa04a1b4c241f8c91faacad872b4d4efd051d49764ad4e2fd4b9fea/cffi-2.1.1-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:cc572dace3f60ef98d7b12ff411d20f5362feb31a0439eab0085bbfd349982d7", size = 223197, upload-time = "2026-08-03T21:20:59.968Z" }, + { url = "https://files.pythonhosted.org/packages/f8/7e/8debeb04f1ab9fe2a6963964cd6f1aaf7192627b83926586a6a4e089c9fa/cffi-2.1.1-cp315-cp315-win32.whl", hash = "sha256:4f42141fc14250de6dde5ee7ea4432be017252d91f19c5ad043c084cea629cac", size = 177683, upload-time = "2026-08-03T21:21:14.901Z" }, + { url = "https://files.pythonhosted.org/packages/e0/31/5158704cc474ab65c1647932e88be78dc0873f47130e253be38bcaf13d01/cffi-2.1.1-cp315-cp315-win_amd64.whl", hash = "sha256:e6e8cff14d6fb0be70a09c0bdc58096f501952d04624ebf867e0e56da2df8960", size = 187897, upload-time = "2026-08-03T21:21:16.108Z" }, + { url = "https://files.pythonhosted.org/packages/cc/4b/b3a2da8570c704ffc0f9762cdc3ec0f02c8573798e0b5cf7f11c82bbb70f/cffi-2.1.1-cp315-cp315-win_arm64.whl", hash = "sha256:27350daa11d4f10c540e6e89dada4c54feb7256ad03e9a4dc075ebad7ba360d1", size = 182935, upload-time = "2026-08-03T21:21:17.271Z" }, + { url = "https://files.pythonhosted.org/packages/d0/ef/5443574510a1207e6f6bc38ba6e1f1de36cb48fef07b2728bb896a21f430/cffi-2.1.1-cp315-cp315t-macosx_10_15_x86_64.whl", hash = "sha256:c26608d2222fb1e94487e4a387d85f13eb55d5ed725cb25a0c589ac4ee60e7bc", size = 188464, upload-time = "2026-08-03T21:21:01.163Z" }, + { url = "https://files.pythonhosted.org/packages/7e/ae/a56fa8c4686ad50e148fcbc8d3ae0d03915ff5c30d795058988c24118cef/cffi-2.1.1-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:4be96343e422f2dfcd12ab5c9f5aebe03f82f737c6bffeca6830b3875cb44aab", size = 188262, upload-time = "2026-08-03T21:21:02.382Z" }, + { url = "https://files.pythonhosted.org/packages/53/b2/6187f46f2912276a3ae284076109cc5c8680482f11f766ccf26db4a86427/cffi-2.1.1-cp315-cp315t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:937c0052c05a31ca1daf18de3158eed4dbfcb9cc107adbea227728d647be701e", size = 223779, upload-time = "2026-08-03T21:21:03.553Z" }, + { url = "https://files.pythonhosted.org/packages/8a/f6/c3ad28bd19f77047a03084424fbd4cbe997303267c14423737324be0385d/cffi-2.1.1-cp315-cp315t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:df423d40ee8654634421812bc3b196da3f9bd7d32929da813f8394c4348a5358", size = 211520, upload-time = "2026-08-03T21:21:04.863Z" }, + { url = "https://files.pythonhosted.org/packages/a0/cd/ccac9013a5bd9fd764de118674ab9c805b5ca10c19270d90ee273f8b2240/cffi-2.1.1-cp315-cp315t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:a730a083190634c65cca36ba5f489531576ebd79bcd5c8e172130f6453127231", size = 210673, upload-time = "2026-08-03T21:21:06.223Z" }, + { url = "https://files.pythonhosted.org/packages/52/86/2976131c639aead931c5bee5aba67e4b09fbeb8018b6f282f70803f923a7/cffi-2.1.1-cp315-cp315t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:363e05fa78e15116c3c32c210ee36884fd6b9afa6d440e47112c3bd511d64cb6", size = 223835, upload-time = "2026-08-03T21:21:07.539Z" }, + { url = "https://files.pythonhosted.org/packages/ac/0c/33a7aeab2f9c76918c52e084beb39c570db3588133412929e8ec06fab90b/cffi-2.1.1-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:770de9db11e84213beec501cfcaa013b019820ca881e03344dea5844f7876d94", size = 226705, upload-time = "2026-08-03T21:21:08.774Z" }, + { url = "https://files.pythonhosted.org/packages/e3/26/2cde30fdde421130bfc18f70395731a6e6b2053c6a1978a5258ff04e72fa/cffi-2.1.1-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:7da0c5eff80f0197f3b3d1232ec5a682a9325f4ae9016a78f5f5ca35f9ced1f5", size = 225539, upload-time = "2026-08-03T21:21:09.911Z" }, + { url = "https://files.pythonhosted.org/packages/6d/cd/a361394c94b2129d604bb846f624a8e88255a3ee33129c434a00d715e64f/cffi-2.1.1-cp315-cp315t-win32.whl", hash = "sha256:06c72bb76605a4b0cd0aad6930b69d4baf7dd5d806cfc409b824191099700e66", size = 182707, upload-time = "2026-08-03T21:21:11.226Z" }, + { url = "https://files.pythonhosted.org/packages/9b/b5/ba2b299993c26577d529b6ae29841f9e15b9fcf004d65f423f4fcf94ade9/cffi-2.1.1-cp315-cp315t-win_amd64.whl", hash = "sha256:d9c275eaacd24aa73f94ffd6de08fc3f932424d8b6c376f4bed7cde376fe7bc3", size = 193772, upload-time = "2026-08-03T21:21:12.39Z" }, + { url = "https://files.pythonhosted.org/packages/aa/29/35e016098c814cd93de9cd320c66b5bfba14dc6ecedd3cb518fa7c408c69/cffi-2.1.1-cp315-cp315t-win_arm64.whl", hash = "sha256:d18e5ac0f2f03f4f518d3e23db0f0cad7faa1da8620e9c09461d443bbf6e6692", size = 186360, upload-time = "2026-08-03T21:21:13.636Z" }, +] + +[[package]] +name = "cfgv" +version = "3.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4e/b5/721b8799b04bf9afe054a3899c6cf4e880fcf8563cc71c15610242490a0c/cfgv-3.5.0.tar.gz", hash = "sha256:d5b1034354820651caa73ede66a6294d6e95c1b00acc5e9b098e917404669132", size = 7334, upload-time = "2025-11-19T20:55:51.612Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl", hash = "sha256:a8dc6b26ad22ff227d2634a65cb388215ce6cc96bbcc5cfde7641ae87e8dacc0", size = 7445, upload-time = "2025-11-19T20:55:50.744Z" }, +] + [[package]] name = "chardet" version = "5.2.0" @@ -135,6 +290,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/2c/1a/aff8bb287a4b1400f69e09a53bd65de96aa5cee5691925b38731c67fc695/click_default_group-1.2.4-py2.py3-none-any.whl", hash = "sha256:9b60486923720e7fc61731bdb32b617039aba820e22e1c88766b1125592eaa5f", size = 4123, upload-time = "2023-08-04T07:54:56.875Z" }, ] +[[package]] +name = "click-repl" +version = "0.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "prompt-toolkit" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cb/a2/57f4ac79838cfae6912f997b4d1a64a858fb0c86d7fcaae6f7b58d267fca/click-repl-0.3.0.tar.gz", hash = "sha256:17849c23dba3d667247dc4defe1757fff98694e90fe37474f3feebb69ced26a9", size = 10449, upload-time = "2023-06-15T12:43:51.141Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/52/40/9d857001228658f0d59e97ebd4c346fe73e138c6de1bce61dc568a57c7f8/click_repl-0.3.0-py3-none-any.whl", hash = "sha256:fb7e06deb8da8de86180a33a9da97ac316751c094c6899382da7feeeeb51b812", size = 10289, upload-time = "2023-06-15T12:43:48.626Z" }, +] + [[package]] name = "colorama" version = "0.4.6" @@ -163,6 +331,56 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b6/d9/0137658a353168ffa9d0fc14b812d3834772040858ddd1cb6eeaf09f7a44/cookiecutter-2.6.0-py3-none-any.whl", hash = "sha256:a54a8e37995e4ed963b3e82831072d1ad4b005af736bb17b99c2cbd9d41b6e2d", size = 39177, upload-time = "2024-02-21T18:02:39.569Z" }, ] +[[package]] +name = "cryptography" +version = "50.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/de/41/6cbdcf9142d00fe82836fbb51e503e58088575cf7a0fe1dbff6695bf0840/cryptography-50.0.0.tar.gz", hash = "sha256:eeac2acb5a20ed25e0ad6d1df9891a520b78b404266b6d11778f25d5d691a6c9", size = 880201, upload-time = "2026-07-31T14:25:10.11Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c5/5c/59086b4aac5e879d38ddbcf74e4be7ade89cebc3eb199a55da998c3bb46a/cryptography-50.0.0-cp311-abi3-macosx_11_0_arm64.whl", hash = "sha256:031e2d5dd4bb9caa3ca9c82e5a197fd8ae680232cee62603d1a813f3f07e3d03", size = 4001252, upload-time = "2026-07-31T14:23:33.331Z" }, + { url = "https://files.pythonhosted.org/packages/57/ef/8f2df13c7216bcad3e1c74e07f6e193d93e998e114f524a53877c9af27ad/cryptography-50.0.0-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:fd9192b7b70c573d7f214eb1ae35e00d359f6f5e4b27c7e21e30de1fc6204645", size = 4719554, upload-time = "2026-07-31T14:23:35.611Z" }, + { url = "https://files.pythonhosted.org/packages/d9/41/029086c34d91052fc3b88bcc8056f709a7c915c7a23b235a54eb800b1c97/cryptography-50.0.0-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:06a32a980526a6ab9a4b9bf8f7385800791e2bb960903cb6b530e4817509a3b7", size = 4702130, upload-time = "2026-07-31T14:23:37.635Z" }, + { url = "https://files.pythonhosted.org/packages/7d/ff/b6ce0954962e7f7b969f850a883744197bb3910bdfd7b6da162eab7d9f68/cryptography-50.0.0-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:a1b30560f2acc95aa8b2e06e716a13dbfc97314747b80d9707e307f77b40d6b3", size = 4725244, upload-time = "2026-07-31T14:23:39.471Z" }, + { url = "https://files.pythonhosted.org/packages/06/1e/63a1027cb7fec360a182208e1b7767d5aa1fe57be3d6aa856e69a321edc0/cryptography-50.0.0-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:8d89f3976b10b4ce31118de72329025f70d2c6ead14a8217c5514dd2c6d5a78f", size = 5342265, upload-time = "2026-07-31T14:23:41.286Z" }, + { url = "https://files.pythonhosted.org/packages/6b/72/a1116d683a6d7ece94590013882515de087edf9ef0e6292aae615a44df73/cryptography-50.0.0-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:b42a28c1844fd9de8f3f7d540e36b66f3a9c83fceac7170ebc7a6a19edd9dcae", size = 4734609, upload-time = "2026-07-31T14:23:43.139Z" }, + { url = "https://files.pythonhosted.org/packages/15/37/36a9c479bbe49acea2636c7fd3360d20f7b7e079c300352011c44850b181/cryptography-50.0.0-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:900131fafd8aead39ac7dd3a7e833be754c17a95cfd91221636949fe4eb0aa8a", size = 4356517, upload-time = "2026-07-31T14:23:44.939Z" }, + { url = "https://files.pythonhosted.org/packages/32/98/8a151d64367204cbc63ec65d37502f1d9c53cf4bfc6ec3c532614dbec60d/cryptography-50.0.0-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:07949c449a1abcf60d1ee6e88956d89404c7df3c8258f46589e912988e551987", size = 4724529, upload-time = "2026-07-31T14:23:46.93Z" }, + { url = "https://files.pythonhosted.org/packages/22/f6/ec13b470172126464a86bf54d2294a46d29837fc51ba3e45d4047946fb5e/cryptography-50.0.0-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:f89831ef99dd7dd169ab06d63a831adb9e20a87aac6d380266bbda5823349169", size = 5299852, upload-time = "2026-07-31T14:23:48.851Z" }, + { url = "https://files.pythonhosted.org/packages/da/3a/f05e32c99d440c9bb891ea0e36c9091891e36be5a9a87ab2ee6ea20729f6/cryptography-50.0.0-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:82148ec5bddac30b51a5b3c1945075f896fa022cb93f8e4a01e9f6ee95292c5f", size = 4734462, upload-time = "2026-07-31T14:23:50.861Z" }, + { url = "https://files.pythonhosted.org/packages/ca/dc/bd72b26be8953f80625f63151efd38eee71c76ca6cf591c08ff34615a79e/cryptography-50.0.0-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:1489e263a8048bb8b6a8bac662eb2d402ea5d2b7b4699b72f385f1e2772db105", size = 4852708, upload-time = "2026-07-31T14:23:52.715Z" }, + { url = "https://files.pythonhosted.org/packages/27/20/c930314a2ab476d15dec966ec87e2e9637bb02b06106b12c0396c57bb603/cryptography-50.0.0-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:7cec5b856506da6defb290f30c9ee687d5f5e8cb0bd3f6459dde43b0b4fa40ef", size = 5004179, upload-time = "2026-07-31T14:23:54.887Z" }, + { url = "https://files.pythonhosted.org/packages/32/2e/c9db68a0c4bfa28e310707527c0ee3a2bd254104d2e02e68f368e197aa4c/cryptography-50.0.0-cp311-abi3-win_amd64.whl", hash = "sha256:bd1c592e4d5974f0d08d4888e432157adba757c66da0246918e43677fafa2d30", size = 3840395, upload-time = "2026-07-31T14:23:56.677Z" }, + { url = "https://files.pythonhosted.org/packages/c3/fb/951032a3bf22a5697c83183fb6294a4843772947a70e616c57b3ff5f522e/cryptography-50.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:49e7d93abdbd2990caced757e5fade25302f719c3c8fb6e6fff2dde98999fc41", size = 3989258, upload-time = "2026-07-31T14:23:58.881Z" }, + { url = "https://files.pythonhosted.org/packages/d4/67/91eb047e69c5e845f2f14b8a2e4a1aab0f283cb885531e9e22c8adb176bc/cryptography-50.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:19736989797678c6af1e55cd49055cdbcb55d8f6b5583ac5335f933aba9101dc", size = 4700648, upload-time = "2026-07-31T14:24:00.702Z" }, + { url = "https://files.pythonhosted.org/packages/30/82/85f0f7425c856b9f96459411eb12e74ef72df9caf6f8f15bf23a33ff131f/cryptography-50.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:80b63928fa35083b33966ce1efb70e5b9607181e49dcd1c22c8c005e319f667f", size = 4682442, upload-time = "2026-07-31T14:24:02.538Z" }, + { url = "https://files.pythonhosted.org/packages/1a/28/b555a365adff1cca2fbe7b9e487d68a40de6bc67ff2cb587473eb43de0e7/cryptography-50.0.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:d58c3db7cd6eed54e6c06744db55456b65ebd7492ddeae9c1e93cfca7aa857d3", size = 4707596, upload-time = "2026-07-31T14:24:04.394Z" }, + { url = "https://files.pythonhosted.org/packages/72/d8/f52538140cc719df62a01cf87d1c7142318d235817109d6f4054d7c352d6/cryptography-50.0.0-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:df2a58a472f332225671c35b0a830208b86d004f82baa8530fa3782c85646533", size = 5314552, upload-time = "2026-07-31T14:24:06.31Z" }, + { url = "https://files.pythonhosted.org/packages/38/14/6120e5bd7c5aa022ad15424ba4d5c5269d0d9448ed4d55e492ea91e3c1c4/cryptography-50.0.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:11b74db56cdbe3cdee6e3f6982ecb70334fa10dce99ed58bf7894aaaa3b2a037", size = 4717113, upload-time = "2026-07-31T14:24:08.349Z" }, + { url = "https://files.pythonhosted.org/packages/fa/71/190bf38c3ee2e0f8efc9860ae100c9df4169742eef274b91e7aa1cb133b9/cryptography-50.0.0-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:f59e38625469987d7ef6d495323c55e7db6c212eaf6112267e0d3b565a2e9c9f", size = 4338580, upload-time = "2026-07-31T14:24:10.227Z" }, + { url = "https://files.pythonhosted.org/packages/3a/63/504ccfbbe61fd8aa983f7f146399cdf034c72c2fc55f5b2dfdcdcdb20c99/cryptography-50.0.0-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:ecfed7367f965a0328cfbdd70da860f15441f002f613185668c6e6ebf5a0ac11", size = 4707038, upload-time = "2026-07-31T14:24:12.169Z" }, + { url = "https://files.pythonhosted.org/packages/01/77/2cf79bbfc4d12ca106437a6e170d6aaa01a373e93093118aaaef0e801bd4/cryptography-50.0.0-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:9aa87839c383bdbab6ef865787a1fb877af8dd03464c4400322726feaaadfc6d", size = 5273110, upload-time = "2026-07-31T14:24:14.38Z" }, + { url = "https://files.pythonhosted.org/packages/e5/45/8aae2972c520145377ea3559a605a899bebe227bf070b33cdb445929a9b9/cryptography-50.0.0-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:6ba6a53445bd3cfa809ef3ef5f1589aa6ba08784a1d962bf47d0940e871dab1c", size = 4716439, upload-time = "2026-07-31T14:24:16.415Z" }, + { url = "https://files.pythonhosted.org/packages/7b/20/4fe50b619a48c2525cc46e2dbc1ac490708d704be5d467bdaac6dc955682/cryptography-50.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:3f5735ffe4996d28b809371756219f5354864902a3b9e7c0b9ee87041209fc9c", size = 4837383, upload-time = "2026-07-31T14:24:18.553Z" }, + { url = "https://files.pythonhosted.org/packages/92/91/3a31366e183343d3703f8995c095f5734676bd6938118047e50fcf279eb4/cryptography-50.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:1b4a266766514614f8aa60416e71f2fc6e575d36e7bdc90f644fadb2f4b75b95", size = 4985772, upload-time = "2026-07-31T14:24:20.385Z" }, + { url = "https://files.pythonhosted.org/packages/74/9a/02ffe35b2853d121689871eb5dce862092562b3a1ed5cc98f1aaed441506/cryptography-50.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:12b9c6996425c76ea6c457ace4f3073e715b8c545add07cd1a8f3a4f90691269", size = 3816291, upload-time = "2026-07-31T14:24:22.125Z" }, + { url = "https://files.pythonhosted.org/packages/03/37/73d005be173aff344af30e9fd2a576575cb2391a7101d9cd3842e1fa8cce/cryptography-50.0.0-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:ccdc4a71a4dabae05de219404f9f4abc38e3b58422177ff93d0da05967dafa07", size = 4036009, upload-time = "2026-07-31T14:24:24.122Z" }, + { url = "https://files.pythonhosted.org/packages/ff/c6/7a6202a534e32103a285b7834a120869557fe198d51d7cfe59754c8bda9c/cryptography-50.0.0-cp39-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:910e1d2668e7de9648f2bcee30e180db2a6b15c30f887d7c4c93ddf96e3992e3", size = 4745252, upload-time = "2026-07-31T14:24:26.118Z" }, + { url = "https://files.pythonhosted.org/packages/85/4f/0fa8c2f4428198f15d9ff8d63400e27afbf94ce833f6108da1eb3753f945/cryptography-50.0.0-cp39-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a91296cb61e8df6f86d0c19cc4068228da256bf59bf86049fbd821084565327f", size = 4728939, upload-time = "2026-07-31T14:24:27.994Z" }, + { url = "https://files.pythonhosted.org/packages/d1/63/54dd723490ba2dc09b299682c10b38db38f159728bcaae8c591b8af2f22d/cryptography-50.0.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:e722f16708d854fe924790e051061f6704a472c3bac347b6fd88033ea8dd0dc5", size = 4748483, upload-time = "2026-07-31T14:24:30.254Z" }, + { url = "https://files.pythonhosted.org/packages/1d/dd/7c77d26285cc7f6991efce64a0f5b4f9383bfa5dd8c5033003eaf7db4cdb/cryptography-50.0.0-cp39-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:d764dcf130c428ef66786f866dd750f53182bc608813489915e9fc106bb0c82f", size = 5367599, upload-time = "2026-07-31T14:24:32.457Z" }, + { url = "https://files.pythonhosted.org/packages/46/c9/f60aed34c013f317f92817b6c171c2d22a78270fa41109bd4b08af26b194/cryptography-50.0.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:105110f43a471dbd0060b9c9516cb8a6a79233631a04cc2ba16f28323ac6e025", size = 4762647, upload-time = "2026-07-31T14:24:34.599Z" }, + { url = "https://files.pythonhosted.org/packages/be/f3/f9a0173b139372c3a48ed98154b45cc6b9de17c789d5ab552e621c293609/cryptography-50.0.0-cp39-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:828743d939e9629bc267b8e2d08d8bb67cd4319c771a33d4b18b22dd8fb7440a", size = 4385197, upload-time = "2026-07-31T14:24:36.647Z" }, + { url = "https://files.pythonhosted.org/packages/d8/36/83bb81f6e569bc38e1e4a7bc80f29b46bb9601920bc455fc8e888f5d5742/cryptography-50.0.0-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:2a8183b489dc1f7f80f135780fadc1108f14b31b8a40411c7a5b17425f65f28b", size = 4748095, upload-time = "2026-07-31T14:24:39.493Z" }, + { url = "https://files.pythonhosted.org/packages/6b/16/d3008eff98c764979865834c3d386d4fd041b5f52e7f34fc29ac1a5eb515/cryptography-50.0.0-cp39-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:6e7d61120573a7f2cd94cc095f9e81f6967c61ccdf194285aa143ecec8e0b708", size = 5325948, upload-time = "2026-07-31T14:24:41.556Z" }, + { url = "https://files.pythonhosted.org/packages/9c/f8/d97f9603efda3888187bfdb893f26c41be4735c10631d05d284ee6b047c4/cryptography-50.0.0-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:37fdb0d0111f1e2ff07139dfb79f1b49531f8e213c46f1163dd7642979b58c47", size = 4762400, upload-time = "2026-07-31T14:24:43.636Z" }, + { url = "https://files.pythonhosted.org/packages/64/a2/4615c8f7d81a00b1d6e6afe19f694e1543582349fb5f4076f6cb5dc36485/cryptography-50.0.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:c87f62a3d3b9888ed0fdde100ec06aa61ca9cd44bad9057d1dff9a516b5f5bb9", size = 4878208, upload-time = "2026-07-31T14:24:45.522Z" }, + { url = "https://files.pythonhosted.org/packages/d2/1a/efcfb02f91407149a0dacffffab791f7e19bf6385f63b3666dc8b5e5c9c8/cryptography-50.0.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:65c2c3add92b45fd0709db8594536aea39c2a67af0e27ffcf049c498501140b7", size = 5037050, upload-time = "2026-07-31T14:24:47.697Z" }, + { url = "https://files.pythonhosted.org/packages/57/30/4a22984d4f1bdfb8c054f07a92bc176b97a3134cc1d6c4b3bffb1f3688b4/cryptography-50.0.0-cp39-abi3-win_amd64.whl", hash = "sha256:d24fead1d4d076e1bfb006dcec392074a3cd8d7b4fc8a595aa64073b2b7a96ba", size = 3874135, upload-time = "2026-07-31T14:24:50.085Z" }, +] + [[package]] name = "distlib" version = "0.4.0" @@ -195,6 +413,15 @@ 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 = "identify" +version = "2.6.19" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/52/63/51723b5f116cc04b061cb6f5a561790abf249d25931d515cd375e063e0f4/identify-2.6.19.tar.gz", hash = "sha256:6be5020c38fcb07da56c53733538a3081ea5aa70d36a156f83044bfbf9173842", size = 99567, upload-time = "2026-04-17T18:39:50.265Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/94/84/d9273cd09688070a6523c4aee4663a8538721b2b755c4962aafae0011e72/identify-2.6.19-py2.py3-none-any.whl", hash = "sha256:20e6a87f786f768c092a721ad107fc9df0eb89347be9396cadf3f4abbd1fb78a", size = 99397, upload-time = "2026-04-17T18:39:49.221Z" }, +] + [[package]] name = "idna" version = "3.11" @@ -207,13 +434,49 @@ wheels = [ [[package]] name = "iform-invenio" version = "0.1.0" -source = { virtual = "." } +source = { editable = "." } dependencies = [ + { name = "boto3" }, + { name = "bumpver" }, + { name = "click-repl" }, { name = "invenio-cli" }, + { name = "loguru" }, + { name = "plumbum" }, + { name = "pydantic-settings" }, + { name = "python-dotenv" }, + { name = "requests" }, + { name = "trustme" }, + { name = "typer" }, +] + +[package.dev-dependencies] +dev = [ + { name = "pre-commit" }, + { name = "ruff" }, + { name = "ty" }, ] [package.metadata] -requires-dist = [{ name = "invenio-cli", specifier = ">=1.9.2" }] +requires-dist = [ + { name = "boto3", specifier = ">=1.43.68" }, + { name = "bumpver", specifier = ">=2026.1132" }, + { name = "click-repl", specifier = ">=0.3.0" }, + { name = "invenio-cli", specifier = ">=1.9.2" }, + { name = "loguru", specifier = ">=0.7.3" }, + { name = "plumbum", specifier = ">=2.0.2" }, + { name = "pydantic-settings", specifier = ">=2.15.0" }, + { name = "python-dotenv", specifier = ">=1.2.2" }, + { name = "requests", specifier = ">=2.32.5" }, + { name = "trustme", specifier = ">=1.2.1" }, + { name = "typer", specifier = ">=0.27.1" }, +] + +[package.metadata.requires-dev] +dev = [ + { name = "pre-commit", specifier = ">=3.5.0" }, + { name = "ruff", specifier = ">=0.16.2" }, + { name = "ty", specifier = ">=0.0.70" }, +] [[package]] name = "invenio-cli" @@ -248,6 +511,37 @@ 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 = "jmespath" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d3/59/322338183ecda247fb5d1763a6cbe46eff7222eaeebafd9fa65d4bf5cb11/jmespath-1.1.0.tar.gz", hash = "sha256:472c87d80f36026ae83c6ddd0f1d05d4e510134ed462851fd5f754c8c3cbb88d", size = 27377, upload-time = "2026-01-22T16:35:26.279Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/14/2f/967ba146e6d58cf6a652da73885f52fc68001525b4197effc174321d70b4/jmespath-1.1.0-py3-none-any.whl", hash = "sha256:a5663118de4908c91729bea0acadca56526eb2698e83de10cd116ae0f4e97c64", size = 20419, upload-time = "2026-01-22T16:35:24.919Z" }, +] + +[[package]] +name = "lexid" +version = "2021.1006" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/60/0b/28a3f9abc75abbf1fa996eb2dd77e1e33a5d1aac62566e3f60a8ec8b8a22/lexid-2021.1006.tar.gz", hash = "sha256:509a3a4cc926d3dbf22b203b18a4c66c25e6473fb7c0e0d30374533ac28bafe5", size = 11525, upload-time = "2021-04-02T20:18:34.668Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cf/e3/35764404a4b7e2021be1f88f42264c2e92e0c4720273559a62461ce64a47/lexid-2021.1006-py2.py3-none-any.whl", hash = "sha256:5526bb5606fd74c7add23320da5f02805bddd7c77916f2dc1943e6bada8605ed", size = 7587, upload-time = "2021-04-02T20:18:33.129Z" }, +] + +[[package]] +name = "loguru" +version = "0.7.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "win32-setctime", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3a/05/a1dae3dffd1116099471c643b8924f5aa6524411dc6c63fdae648c4f1aca/loguru-0.7.3.tar.gz", hash = "sha256:19480589e77d47b8d85b2c827ad95d49bf31b0dcde16593892eb51dd18706eb6", size = 63559, upload-time = "2024-12-06T11:20:56.608Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0c/29/0348de65b8cc732daa3e33e67806420b2ae89bdce2b04af740289c5c6c8c/loguru-0.7.3-py3-none-any.whl", hash = "sha256:31a33c10c8e1e10422bfd431aeb5d351c7cf7fa671e3c4df004162264b28220c", size = 61595, upload-time = "2024-12-06T11:20:54.538Z" }, +] + [[package]] name = "markdown-it-py" version = "4.0.0" @@ -332,6 +626,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" }, ] +[[package]] +name = "nodeenv" +version = "1.10.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/24/bf/d1bda4f6168e0b2e9e5958945e01910052158313224ada5ce1fb2e1113b8/nodeenv-1.10.0.tar.gz", hash = "sha256:996c191ad80897d076bdfba80a41994c2b47c68e224c542b48feba42ba00f8bb", size = 55611, upload-time = "2025-12-20T14:08:54.006Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/b2/d0896bdcdc8d28a7fc5717c305f1a861c26e18c05047949fb371034d98bd/nodeenv-1.10.0-py2.py3-none-any.whl", hash = "sha256:5bb13e3eed2923615535339b3c620e76779af4cb4c6a90deccc9e36b274d3827", size = 23438, upload-time = "2025-12-20T14:08:52.782Z" }, +] + [[package]] name = "packaging" version = "25.0" @@ -374,6 +677,159 @@ 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 = "plumbum" +version = "2.0.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6c/d2/578712a979a50e9aba08ea173a38e7598461130d3c9899f176373bc03280/plumbum-2.0.2.tar.gz", hash = "sha256:233751d7819c9e6743ec1c2405927eb4fa52a284c7b894bd10e28106a9309a92", size = 390799, upload-time = "2026-07-22T04:13:12.157Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b5/55/fdd01894807d20fb6181f13590ae247585b10e909b42e6ada833379fe520/plumbum-2.0.2-py3-none-any.whl", hash = "sha256:a865771826c1d2cce0ca8b2a2c48aa0447ed7a518859e389a95f158e9c369547", size = 168804, upload-time = "2026-07-22T04:13:10.76Z" }, +] + +[[package]] +name = "pre-commit" +version = "4.6.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cfgv" }, + { name = "identify" }, + { name = "nodeenv" }, + { name = "pyyaml" }, + { name = "virtualenv" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/74/89/1f3e8e1fc3e97de0fa963495832f581f025f29471602a309e48808244292/pre_commit-4.6.2.tar.gz", hash = "sha256:8f5d7bfb021ecdbcd9d49d89847082dd24172ccde534390081a679ad046e2441", size = 198670, upload-time = "2026-08-10T22:07:18.421Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/45/e2/bbb7129c9e7999a6b8ee9cca3b66486c25c423ab5a75f34071798b74ce94/pre_commit-4.6.2-py2.py3-none-any.whl", hash = "sha256:e2dde9a75d3bce11bd3831c26d134df00a2803c1d818be6a0383c3dcda25dc4e", size = 226202, upload-time = "2026-08-10T22:07:16.942Z" }, +] + +[[package]] +name = "prompt-toolkit" +version = "3.0.53" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "wcwidth" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7d/ea/39b988c938f75cb75d7045b5c69f8bfed47ee2152c8837fb403de29d6fb8/prompt_toolkit-3.0.53.tar.gz", hash = "sha256:9ec8a0ad96d5c56148b3f914aa79c1564c3fde5d2e6b876e7bc327e353cf8fa6", size = 435492, upload-time = "2026-07-26T20:56:14.758Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/6f/84908cad2d6aa5144abcf7b42709fe4fdb459bc640ec7ac5786e7693dabc/prompt_toolkit-3.0.53-py3-none-any.whl", hash = "sha256:01c0891d7f9237d5e339f7d3e42cdae80b7534abb1c7c0e3352efba6231492f2", size = 392288, upload-time = "2026-07-26T20:56:12.512Z" }, +] + +[[package]] +name = "pycparser" +version = "3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1b/7d/92392ff7815c21062bea51aa7b87d45576f649f16458d78b7cf94b9ab2e6/pycparser-3.0.tar.gz", hash = "sha256:600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29", size = 103492, upload-time = "2026-01-21T14:26:51.89Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl", hash = "sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992", size = 48172, upload-time = "2026-01-21T14:26:50.693Z" }, +] + +[[package]] +name = "pydantic" +version = "2.13.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "annotated-types" }, + { name = "pydantic-core" }, + { name = "typing-extensions" }, + { name = "typing-inspection" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/18/a5/b60d21ac674192f8ab0ba4e9fd860690f9b4a6e51ca5df118733b487d8d6/pydantic-2.13.4.tar.gz", hash = "sha256:c40756b57adaa8b1efeeced5c196f3f3b7c435f90e84ea7f443901bec8099ef6", size = 844775, upload-time = "2026-05-06T13:43:05.343Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fd/7b/122376b1fd3c62c1ed9dc80c931ace4844b3c55407b6fb2d199377c9736f/pydantic-2.13.4-py3-none-any.whl", hash = "sha256:45a282cde31d808236fd7ea9d919b128653c8b38b393d1c4ab335c62924d9aba", size = 472262, upload-time = "2026-05-06T13:43:02.641Z" }, +] + +[[package]] +name = "pydantic-core" +version = "2.46.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9d/56/921726b776ace8d8f5db44c4ef961006580d91dc52b803c489fafd1aa249/pydantic_core-2.46.4.tar.gz", hash = "sha256:62f875393d7f270851f20523dd2e29f082bcc82292d66db2b64ea71f64b6e1c1", size = 471464, upload-time = "2026-05-06T13:37:06.98Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ce/8c/af022f0af448d7747c5154288d46b5f2bc5f17366eaa0e23e9aa04d59f3b/pydantic_core-2.46.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:3245406455a5d98187ec35530fd772b1d799b26667980872c8d4614991e2c4a2", size = 2106158, upload-time = "2026-05-06T13:38:57.215Z" }, + { url = "https://files.pythonhosted.org/packages/19/95/6195171e385007300f0f5574592e467c568becce2d937a0b6804f218bc49/pydantic_core-2.46.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:962ccbab7b642487b1d8b7df90ef677e03134cf1fd8880bf698649b22a69371f", size = 1951724, upload-time = "2026-05-06T13:37:02.697Z" }, + { url = "https://files.pythonhosted.org/packages/8e/bc/f47d1ff9cbb1620e1b5b697eef06010035735f07820180e74178226b27b3/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8233f2947cf85404441fd7e0085f53b10c93e0ee78611099b5c7237e36aacbf7", size = 1975742, upload-time = "2026-05-06T13:37:09.448Z" }, + { url = "https://files.pythonhosted.org/packages/5b/11/9b9a5b0306345664a2da6410877af6e8082481b5884b3ddd78d47c6013ce/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3a233125ac121aa3ffba9a2b59edfc4a985a76092dc8279586ab4b71390875e7", size = 2052418, upload-time = "2026-05-06T13:37:38.234Z" }, + { url = "https://files.pythonhosted.org/packages/f1/b7/a65fec226f5d78fc39f4a13c4cc0c768c22b113438f60c14adc9d2865038/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5b712b53160b79a5850310b912a5ef8e57e56947c8ad690c227f5c9d7e561712", size = 2232274, upload-time = "2026-05-06T13:38:27.753Z" }, + { url = "https://files.pythonhosted.org/packages/68/f0/92039db98b907ef49269a8271f67db9cb78ae2fc68062ef7e4e77adb5f61/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9401557acd873c3a7f3eb9383edef8ac4968f9510e340f4808d427e75667e7b4", size = 2309940, upload-time = "2026-05-06T13:38:05.353Z" }, + { url = "https://files.pythonhosted.org/packages/5f/97/2aab507d3d00ca626e8e57c1eac6a79e4e5fbcc63eb99733ff55d1717f65/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:926c9541b14b12b1681dca8a0b75feb510b06c6341b70a8e500c2fdcff837cce", size = 2094516, upload-time = "2026-05-06T13:39:10.577Z" }, + { url = "https://files.pythonhosted.org/packages/22/37/a8aca44d40d737dde2bc05b3c6c07dff0de07ce6f82e9f3167aeaf4d5dea/pydantic_core-2.46.4-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:56cb4851bcaf3d117eddcef4fe66afd750a50274b0da8e22be256d10e5611987", size = 2136854, upload-time = "2026-05-06T13:40:22.59Z" }, + { url = "https://files.pythonhosted.org/packages/24/99/fcef1b79238c06a8cbec70819ac722ba76e02bc8ada9b0fd66eba40da01b/pydantic_core-2.46.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c68fcd102d71ea85c5b2dfac3f4f8476eff42a9e078fd5faefff6d145063536b", size = 2180306, upload-time = "2026-05-06T13:40:10.666Z" }, + { url = "https://files.pythonhosted.org/packages/ae/6c/fc44000918855b42779d007ae63b0532794739027b2f417321cddbc44f6a/pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b2f69dec1725e79a012d920df1707de5caf7ed5e08f3be4435e25803efc47458", size = 2190044, upload-time = "2026-05-06T13:40:43.231Z" }, + { url = "https://files.pythonhosted.org/packages/6b/65/d9cadc9f1920d7a127ad2edba16c1db7916e59719285cd6c94600b0080ba/pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:8d0820e8192167f80d88d64038e609c31452eeca865b4e1d9950a27a4609b00b", size = 2329133, upload-time = "2026-05-06T13:39:57.365Z" }, + { url = "https://files.pythonhosted.org/packages/d0/cf/c873d91679f3a30bcf5e7ac280ce5573483e72295307685120d0d5ad3416/pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:fbdb89b3e1c94a30cc5edfce477c6e6a5dc4d8f84665b455c27582f211a1c72c", size = 2374464, upload-time = "2026-05-06T13:38:06.976Z" }, + { url = "https://files.pythonhosted.org/packages/47/bd/6f2fc8188f31bf10590f1e98e7b306336161fac930a8c514cd7bd828c7dc/pydantic_core-2.46.4-cp312-cp312-win32.whl", hash = "sha256:9aa768456404a8bf48a4406685ac2bec8e72b62c69313734fa3b73cf33b3a894", size = 1974823, upload-time = "2026-05-06T13:40:47.985Z" }, + { url = "https://files.pythonhosted.org/packages/40/8c/985c1d41ea1107c2534abd9870e4ed5c8e7669b5c308297835c001e7a1c4/pydantic_core-2.46.4-cp312-cp312-win_amd64.whl", hash = "sha256:e9c26f834c65f5752f3f06cb08cb86a913ceb7274d0db6e267808a708b46bc89", size = 2072919, upload-time = "2026-05-06T13:39:21.153Z" }, + { url = "https://files.pythonhosted.org/packages/c4/ba/f463d006e0c47373ca7ec5e1a261c59dc01ef4d62b2657af925fb0deee3a/pydantic_core-2.46.4-cp312-cp312-win_arm64.whl", hash = "sha256:4fc73cb559bdb54b1134a706a2802a4cddd27a0633f5abb7e53056268751ac6a", size = 2027604, upload-time = "2026-05-06T13:39:03.753Z" }, + { url = "https://files.pythonhosted.org/packages/51/a2/5d30b469c5267a17b39dec53208222f76a8d351dfac4af661888c5aee77d/pydantic_core-2.46.4-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:5d5902252db0d3cedf8d4a1bc68f70eeb430f7e4c7104c8c476753519b423008", size = 2106306, upload-time = "2026-05-06T13:37:48.029Z" }, + { url = "https://files.pythonhosted.org/packages/c1/81/4fa520eaffa8bd7d1525e644cd6d39e7d60b1592bc5b516693c7340b50f1/pydantic_core-2.46.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c94f0688e7b8d0a67abf40e57a7eaaecd17cc9586706a31b76c031f63df052b4", size = 1951906, upload-time = "2026-05-06T13:37:17.012Z" }, + { url = "https://files.pythonhosted.org/packages/03/d5/fd02da45b659668b05923b17ba3a0100a0a3d5541e3bd8fcc4ecb711309e/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f027324c56cd5406ca49c124b0db10e56c69064fec039acc571c29020cc87c76", size = 1976802, upload-time = "2026-05-06T13:37:35.113Z" }, + { url = "https://files.pythonhosted.org/packages/21/f2/95727e1368be3d3ed485eaab7adbd7dda408f33f7a36e8b48e0144002b91/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e739fee756ba1010f8bcccb534252e85a35fe45ae92c295a06059ce58b74ccd3", size = 2052446, upload-time = "2026-05-06T13:37:12.313Z" }, + { url = "https://files.pythonhosted.org/packages/9c/86/5d99feea3f77c7234b8718075b23db11532773c1a0dbd9b9490215dc2eeb/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9d56801be94b86a9da183e5f3766e6310752b99ff647e38b09a9500d88e46e76", size = 2232757, upload-time = "2026-05-06T13:39:01.149Z" }, + { url = "https://files.pythonhosted.org/packages/d2/3a/508ac615935ef7588cf6d9e9b91309fdc2da751af865e02a9098de88258c/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2412e734dcb48da14d4e4006b82b46b74f2518b8a26ee7e58c6844a6cd6d03c4", size = 2309275, upload-time = "2026-05-06T13:37:41.406Z" }, + { url = "https://files.pythonhosted.org/packages/07/f8/41db9de19d7987d6b04715a02b3b40aea467000275d9d758ffaa31af7d50/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9551187363ffc0de2a00b2e47c25aeaeb1020b69b668762966df15fc5659dd5a", size = 2094467, upload-time = "2026-05-06T13:39:18.847Z" }, + { url = "https://files.pythonhosted.org/packages/2c/e2/f35033184cb11d0052daf4416e8e10a502ea2ac006fc4f459aee872727d1/pydantic_core-2.46.4-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:0186750b482eefa11d7f435892b09c5c606193ef3375bcf94aa00ae6bfb66262", size = 2134417, upload-time = "2026-05-06T13:40:17.944Z" }, + { url = "https://files.pythonhosted.org/packages/7e/7b/6ceeb1cc90e193862f444ebe373d8fdf613f0a82572dde03fb10734c6c71/pydantic_core-2.46.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5855698a4856556d86e8e6cd8434bc3ac0314ee8e12089ae0e143f64c6256e4e", size = 2179782, upload-time = "2026-05-06T13:40:32.618Z" }, + { url = "https://files.pythonhosted.org/packages/5a/f2/c8d7773ede6af08036423a00ae0ceffce266c3c52a096c435d68c896083f/pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:cbaf13819775b7f769bf4a1f066cb6df7a28d4480081a589828ef190226881cd", size = 2188782, upload-time = "2026-05-06T13:36:51.018Z" }, + { url = "https://files.pythonhosted.org/packages/59/31/0c864784e31f09f05cdd87606f08923b9c9e7f6e51dd27f20f62f975ce9f/pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:633147d34cf4550417f12e2b1a0383973bdf5cdfde212cb09e9a581cf10820be", size = 2328334, upload-time = "2026-05-06T13:40:37.764Z" }, + { url = "https://files.pythonhosted.org/packages/c2/eb/4f6c8a41efa30baa755590f4141abf3a8c370fab610915733e74134a7270/pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:82cf5301172168103724d49a1444d3378cb20cdee30b116a1bd6031236298a5d", size = 2372986, upload-time = "2026-05-06T13:39:34.152Z" }, + { url = "https://files.pythonhosted.org/packages/5b/24/b375a480d53113860c299764bfe9f349a3dc9108b3adc0d7f0d786492ebf/pydantic_core-2.46.4-cp313-cp313-win32.whl", hash = "sha256:9fa8ae11da9e2b3126c6426f147e0fba88d96d65921799bb30c6abd1cb2c97fb", size = 1973693, upload-time = "2026-05-06T13:37:55.072Z" }, + { url = "https://files.pythonhosted.org/packages/7e/e8/cff247591966f2d22ec8c003cd7587e27b7ba7b81ab2fb888e3ab75dc285/pydantic_core-2.46.4-cp313-cp313-win_amd64.whl", hash = "sha256:6b3ace8194b0e5204818c92802dcdca7fc6d88aabbb799d7c795540d9cd6d292", size = 2071819, upload-time = "2026-05-06T13:38:49.139Z" }, + { url = "https://files.pythonhosted.org/packages/c6/1a/f4aee670d5670e9e148e0c82c7db98d780be566c6e6a97ee8035528ca0b3/pydantic_core-2.46.4-cp313-cp313-win_arm64.whl", hash = "sha256:184c081504d17f1c1066e430e117142b2c77d9448a97f7b65c6ac9fd9aee238d", size = 2027411, upload-time = "2026-05-06T13:40:45.796Z" }, + { url = "https://files.pythonhosted.org/packages/8d/74/228a26ddad29c6672b805d9fd78e8d251cd04004fa7eed0e622096cd0250/pydantic_core-2.46.4-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:428e04521a40150c85216fc8b85e8d39fece235a9cf5e383761238c7fa9b96fb", size = 2102079, upload-time = "2026-05-06T13:38:41.019Z" }, + { url = "https://files.pythonhosted.org/packages/ad/1f/8970b150a4b4365623ae00fc88603491f763c627311ae8031e3111356d6e/pydantic_core-2.46.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:23ace664830ee0bfe014a0c7bc248b1f7f25ed7ad103852c317624a1083af462", size = 1952179, upload-time = "2026-05-06T13:36:59.812Z" }, + { url = "https://files.pythonhosted.org/packages/95/30/5211a831ae054928054b2f79731661087a2bc5c01e825c672b3a4a8f1b3e/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ce5c1d2a8b27468f433ca974829c44060b8097eedc39933e3c206a90ee49c4a9", size = 1978926, upload-time = "2026-05-06T13:37:39.933Z" }, + { url = "https://files.pythonhosted.org/packages/57/e9/689668733b1eb67adeef047db3c2e8788fcf65a7fd9c9e2b46b7744fe245/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7283d57845ecf5a163403eb0702dfc220cc4fbdd18919cb5ccea4f95ee1cdab4", size = 2046785, upload-time = "2026-05-06T13:38:01.995Z" }, + { url = "https://files.pythonhosted.org/packages/60/d9/6715260422ff50a2109878fd24d948a6c3446bb2664f34ee78cd972b3acd/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8daafc69c93ee8a0204506a3b6b30f586ef54028f52aeeeb5c4cfc5184fd5914", size = 2228733, upload-time = "2026-05-06T13:40:50.371Z" }, + { url = "https://files.pythonhosted.org/packages/18/ae/fdb2f64316afca925640f8e70bb1a564b0ec2721c1389e25b8eb4bf9a299/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd2213145bcc2ba85884d0ac63d222fece9209678f77b9b4d76f054c561adb28", size = 2307534, upload-time = "2026-05-06T13:37:21.531Z" }, + { url = "https://files.pythonhosted.org/packages/89/1d/8eff589b45bb8190a9d12c49cfad0f176a5cbd1534908a6b5125e2886239/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a5f930472650a82629163023e630d160863fce524c616f4e5186e5de9d9a49b", size = 2099732, upload-time = "2026-05-06T13:39:31.942Z" }, + { url = "https://files.pythonhosted.org/packages/06/d5/ee5a3366637fee41dee51a1fc91562dcf12ddbc68fda34e6b253da2324bb/pydantic_core-2.46.4-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:c1b3f518abeca3aa13c712fd202306e145abf59a18b094a6bafb2d2bbf59192c", size = 2129627, upload-time = "2026-05-06T13:37:25.033Z" }, + { url = "https://files.pythonhosted.org/packages/94/33/2414be571d2c6a6c4d08be21f9292b6d3fdb08949a97b6dfe985017821db/pydantic_core-2.46.4-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1a7dd0b3ee80d90150e3495a3a13ac34dbcbfd4f012996a6a1d8900e91b5c0fb", size = 2179141, upload-time = "2026-05-06T13:37:14.046Z" }, + { url = "https://files.pythonhosted.org/packages/7b/79/7daa95be995be0eecc4cf75064cb33f9bbbfe3fe0158caf2f0d4a996a5c7/pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:3fb702cd90b0446a3a1c5e470bfa0dd23c0233b676a9099ddcc964fa6ca13898", size = 2184325, upload-time = "2026-05-06T13:36:53.615Z" }, + { url = "https://files.pythonhosted.org/packages/9f/cb/d0a382f5c0de8a222dc61c65348e0ce831b1f68e0a018450d31c2cace3a5/pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:b8458003118a712e66286df6a707db01c52c0f52f7db8e4a38f0da1d3b94fc4e", size = 2323990, upload-time = "2026-05-06T13:40:29.971Z" }, + { url = "https://files.pythonhosted.org/packages/05/db/d9ba624cc4a5aced1598e88c04fdbd8310c8a69b9d38b9a3d39ce3a61ed7/pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:372429a130e469c9cd698925ce5fc50940b7a1336b0d82038e63d5bbc4edc519", size = 2369978, upload-time = "2026-05-06T13:37:23.027Z" }, + { url = "https://files.pythonhosted.org/packages/f2/20/d15df15ba918c423461905802bfd2981c3af0bfa0e40d05e13edbfa48bc3/pydantic_core-2.46.4-cp314-cp314-win32.whl", hash = "sha256:85bb3611ff1802f3ee7fdd7dbff26b56f343fb432d57a4728fdd49b6ef35e2f4", size = 1966354, upload-time = "2026-05-06T13:38:03.499Z" }, + { url = "https://files.pythonhosted.org/packages/fc/b6/6b8de4c0a7d7ab3004c439c80c5c1e0a3e8d78bbae19379b01960383d9e5/pydantic_core-2.46.4-cp314-cp314-win_amd64.whl", hash = "sha256:811ff8e9c313ab425368bcbb36e5c4ebd7108c2bbf4e4089cfbb0b01eff63fac", size = 2072238, upload-time = "2026-05-06T13:39:40.807Z" }, + { url = "https://files.pythonhosted.org/packages/32/36/51eb763beec1f4cf59b1db243a7dcc39cbb41230f050a09b9d69faaf0a48/pydantic_core-2.46.4-cp314-cp314-win_arm64.whl", hash = "sha256:bfec22eab3c8cc2ceec0248aec886624116dc079afa027ecc8ad4a7e62010f8a", size = 2018251, upload-time = "2026-05-06T13:37:26.72Z" }, + { url = "https://files.pythonhosted.org/packages/e8/91/855af51d625b23aa987116a19e231d2aaef9c4a415273ddc189b79a45fee/pydantic_core-2.46.4-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:af8244b2bef6aaad6d92cda81372de7f8c8d36c9f0c3ea36e827c60e7d9467a0", size = 2099593, upload-time = "2026-05-06T13:39:47.682Z" }, + { url = "https://files.pythonhosted.org/packages/fb/1b/8784a54c65edb5f49f0a14d6977cf1b209bba85a4c77445b255c2de58ab3/pydantic_core-2.46.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5a4330cdbc57162e4b3aa303f588ba752257694c9c9be3e7ebb11b4aca659b5d", size = 1935226, upload-time = "2026-05-06T13:40:40.428Z" }, + { url = "https://files.pythonhosted.org/packages/e8/e7/1955d28d1afc56dd4b3ad7cc0cf39df1b9852964cf16e5d13912756d6d6b/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:29c61fc04a3d840155ff08e475a04809278972fe6aef51e2720554e96367e34b", size = 1974605, upload-time = "2026-05-06T13:37:32.029Z" }, + { url = "https://files.pythonhosted.org/packages/93/e2/3fedbf0ba7a22850e6e9fd78117f1c0f10f950182344d8a6c535d468fdd8/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c50f2528cf200c5eed56faf3f4e22fcd5f38c157a8b78576e6ba3168ec35f000", size = 2030777, upload-time = "2026-05-06T13:38:55.239Z" }, + { url = "https://files.pythonhosted.org/packages/f8/61/46be275fcaaba0b4f5b9669dd852267ce1ff616592dccf7a7845588df091/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0cbe8b01f948de4286c74cdd6c667aceb38f5c1e26f0693b3983d9d74887c65e", size = 2236641, upload-time = "2026-05-06T13:37:08.096Z" }, + { url = "https://files.pythonhosted.org/packages/60/db/12e93e46a8bac9988be3c016860f83293daea8c716c029c9ace279036f2f/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:617d7e2ca7dcb8c5cf6bcb8c59b8832c94b36196bbf1cbd1bfb56ed341905edd", size = 2286404, upload-time = "2026-05-06T13:40:20.221Z" }, + { url = "https://files.pythonhosted.org/packages/e2/4a/4d8b19008f38d31c53b8219cfedc2e3d5de5fe99d90076b7e767de29274f/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7027560ee92211647d0d34e3f7cd6f50da56399d26a9c8ad0da286d3869a53f3", size = 2109219, upload-time = "2026-05-06T13:38:12.153Z" }, + { url = "https://files.pythonhosted.org/packages/88/70/3cbc40978fefb7bb09c6708d40d4ad1a5d70fd7213c3d17f971de868ec1f/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:f99626688942fb746e545232e7726926f3be91b5975f8b55327665fafda991c7", size = 2110594, upload-time = "2026-05-06T13:40:02.971Z" }, + { url = "https://files.pythonhosted.org/packages/9d/20/b8d36736216e29491125531685b2f9e61aa5b4b2599893f8268551da3338/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fc3e9034a63de20e15e8ade85358bc6efc614008cab72898b4b4952bea0509ff", size = 2159542, upload-time = "2026-05-06T13:39:27.506Z" }, + { url = "https://files.pythonhosted.org/packages/1d/a2/367df868eb584dacf6bf82a389272406d7178e301c4ac82545ab98bc2dd9/pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:97e7cf2be5c77b7d1a9713a05605d49460d02c6078d38d8bef3cbe323c548424", size = 2168146, upload-time = "2026-05-06T13:38:31.93Z" }, + { url = "https://files.pythonhosted.org/packages/c1/b8/4460f77f7e201893f649a29ab355dddd3beee8a97bcb1a320db414f9a06e/pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:3bf92c5d0e00fefaab325a4d27828fe6b6e2a21848686b5b60d2d9eeb09d76c6", size = 2306309, upload-time = "2026-05-06T13:37:44.717Z" }, + { url = "https://files.pythonhosted.org/packages/64/c4/be2639293acd87dc8ddbcec41a73cee9b2ebf996fe6d892a1a74e88ad3f7/pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:3ecbc122d18468d06ca279dc26a8c2e2d5acb10943bb35e36ae92096dc3b5565", size = 2369736, upload-time = "2026-05-06T13:37:05.645Z" }, + { url = "https://files.pythonhosted.org/packages/30/a6/9f9f380dbb301f67023bf8f707aaa75daadf84f7152d95c410fd7e81d994/pydantic_core-2.46.4-cp314-cp314t-win32.whl", hash = "sha256:e846ae7835bf0703ae43f534ab79a867146dadd59dc9ca5c8b53d5c8f7c9ef02", size = 1955575, upload-time = "2026-05-06T13:38:51.116Z" }, + { url = "https://files.pythonhosted.org/packages/40/1f/f1eb9eb350e795d1af8586289746f5c5677d16043040d63710e22abc43c9/pydantic_core-2.46.4-cp314-cp314t-win_amd64.whl", hash = "sha256:2108ba5c1c1eca18030634489dc544844144ee36357f2f9f780b93e7ddbb44b5", size = 2051624, upload-time = "2026-05-06T13:38:21.672Z" }, + { url = "https://files.pythonhosted.org/packages/f6/d2/42dd53d0a85c27606f316d3aa5d2869c4e8470a5ed6dec30e4a1abe19192/pydantic_core-2.46.4-cp314-cp314t-win_arm64.whl", hash = "sha256:4fcbe087dbc2068af7eda3aa87634eba216dbda64d1ae73c8684b621d33f6596", size = 2017325, upload-time = "2026-05-06T13:40:52.723Z" }, + { url = "https://files.pythonhosted.org/packages/9d/1d/8987ad40f65ae1432753072f214fb5c74fe47ffbd0698bb9cbbb585664f8/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:1d8ba486450b14f3b1d63bc521d410ec7565e52f887b9fb671791886436a42f7", size = 2095527, upload-time = "2026-05-06T13:39:52.283Z" }, + { url = "https://files.pythonhosted.org/packages/64/d3/84c282a7eee1d3ac4c0377546ef5a1ea436ce26840d9ac3b7ed54a377507/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:3009f12e4e90b7f88b4f9adb1b0c4a3d58fe7820f3238c190047209d148026df", size = 1936024, upload-time = "2026-05-06T13:40:15.671Z" }, + { url = "https://files.pythonhosted.org/packages/d7/ca/eac61596cdeb4d7e174d3dc0bd8a6238f14f75f97a24e7b7db4c7e7340a0/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ad785e92e6dc634c21555edc8bd6b64957ab844541bcb96a1366c202951ae526", size = 1990696, upload-time = "2026-05-06T13:38:34.717Z" }, + { url = "https://files.pythonhosted.org/packages/fa/c3/7c8b240552251faf6b3a957db200fcfbbcec36763c050428b601e0c9b83b/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00c603d540afdd6b80eb39f078f33ebd46211f02f33e34a32d9f053bba711de0", size = 2147590, upload-time = "2026-05-06T13:39:29.883Z" }, +] + +[[package]] +name = "pydantic-settings" +version = "2.15.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pydantic" }, + { name = "python-dotenv" }, + { name = "typing-inspection" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/68/ca/31c57507b13119d7d3cfa1576dad2911a4861e3be07b579395f4e9d393f9/pydantic_settings-2.15.0.tar.gz", hash = "sha256:694b793e84f766ba76a90ebdefc01d0a9a045dab0382bee70393da93712ad117", size = 261253, upload-time = "2026-08-07T09:24:57.419Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/30/a4/2bffa9f8e804325a09867f0e9d30795c80ea9f8d62560bd1b6ad6220eb2f/pydantic_settings-2.15.0-py3-none-any.whl", hash = "sha256:0ba092c291c94baceb5eff768aa0d56400a457585bc0175925a5a5510303da42", size = 69413, upload-time = "2026-08-07T09:24:55.839Z" }, +] + [[package]] name = "pygments" version = "2.19.2" @@ -404,6 +860,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" }, ] +[[package]] +name = "python-dotenv" +version = "1.2.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/82/ed/0301aeeac3e5353ef3d94b6ec08bbcabd04a72018415dcb29e588514bba8/python_dotenv-1.2.2.tar.gz", hash = "sha256:2c371a91fbd7ba082c2c1dc1f8bf89ca22564a087c2c287cd9b662adde799cf3", size = 50135, upload-time = "2026-03-01T16:00:26.196Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0b/d7/1959b9648791274998a9c3526f6d0ec8fd2233e4d4acce81bbae76b44b2a/python_dotenv-1.2.2-py3-none-any.whl", hash = "sha256:1d8214789a24de455a8b8bd8ae6fe3c6b69a5e3d64aa8a8e5d68e694bbcb285a", size = 22101, upload-time = "2026-03-01T16:00:25.09Z" }, +] + [[package]] name = "python-slugify" version = "8.0.4" @@ -506,6 +971,43 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/25/7a/b0178788f8dc6cafce37a212c99565fa1fe7872c70c6c9c1e1a372d9d88f/rich-14.2.0-py3-none-any.whl", hash = "sha256:76bc51fe2e57d2b1be1f96c524b890b816e334ab4c1e45888799bfaab0021edd", size = 243393, upload-time = "2025-10-09T14:16:51.245Z" }, ] +[[package]] +name = "ruff" +version = "0.16.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/73/e1/4508a569211b35599016e84ba65c1a992b7a4004b4b6c4bea02a851cba1b/ruff-0.16.2.tar.gz", hash = "sha256:c3d7828d12e8927a6fc65fe38e2c2541b9e762d360a1786d752cb1b8883b3c9c", size = 4885811, upload-time = "2026-08-07T13:31:01.432Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/14/57/db19951540f98859c956b50bdb4d31089b4d91e9f15e2968e7d5193806d5/ruff-0.16.2-py3-none-linux_armv6l.whl", hash = "sha256:3c8de4cf2181f01d57946d87d777aa52916976fc09942aed89938fab5e013318", size = 10847925, upload-time = "2026-08-07T13:30:14.468Z" }, + { url = "https://files.pythonhosted.org/packages/13/5a/995fe85a8470d3e391ac0f7fa8054bb454eaf33ee138196d6172ed1079c0/ruff-0.16.2-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:9a48cc05c6fbc811ca81b5d7ba95375affea6582d1b8024e455e41afbbf55344", size = 11072662, upload-time = "2026-08-07T13:30:18.143Z" }, + { url = "https://files.pythonhosted.org/packages/32/53/370d767c61c71a971a4ace36703a7ecd8c393956349a7325d7fab2b56827/ruff-0.16.2-py3-none-macosx_11_0_arm64.whl", hash = "sha256:a2c0d14fcbb26c91f0f867a6dc9bd71bbc30b1b6151829c884f23faeab2e5700", size = 10566771, upload-time = "2026-08-07T13:30:20.899Z" }, + { url = "https://files.pythonhosted.org/packages/85/d6/9d96948caf5a632be62d62202d5ec914d6856f204fd79eb036e5915e79ea/ruff-0.16.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:335c621622c4650330be50842561c6586ac6971bb8ab5407fe34dcc9efb16bbe", size = 10975825, upload-time = "2026-08-07T13:30:23.517Z" }, + { url = "https://files.pythonhosted.org/packages/3b/92/ea87129b3414acb0b5770563779c51804d37ac67675c7ba35447ddb14773/ruff-0.16.2-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:20e66910f2c37cc753f9ef6580c914a621b80c4fa3549d3e3521e29d0f5bfc3f", size = 10649437, upload-time = "2026-08-07T13:30:26.097Z" }, + { url = "https://files.pythonhosted.org/packages/ac/43/f8f291dcd4af5bb7872b74fdfa41a7cd7c856ca1d4069670971cf1b9f5cb/ruff-0.16.2-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c7e36fbfba65510548156902bcf1350a979a958ce0347ce0f90d73894036b39f", size = 11446761, upload-time = "2026-08-07T13:30:28.752Z" }, + { url = "https://files.pythonhosted.org/packages/71/4a/ef991fb2fcf516ab71f0808adcdd8da5e18c8cde447f4ceaf5f47a5132a5/ruff-0.16.2-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f0eab35f80df8f134aae5d1630e751901321d317cc8e50dc39e36fa3ed34cd12", size = 12336364, upload-time = "2026-08-07T13:30:31.468Z" }, + { url = "https://files.pythonhosted.org/packages/f3/24/f615e74f307e6ca0e56a482872477b856c70d530aa356abfb6dfe5ca8a80/ruff-0.16.2-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:40ea8c0594feb894e89c8c61ab9c103d38b0ea72dfde6c594107147ca31b1140", size = 11630720, upload-time = "2026-08-07T13:30:34.426Z" }, + { url = "https://files.pythonhosted.org/packages/c5/d3/8ef50149e8412a77f7ab409efdef0e2b23803707a3863da4fc64cb23d459/ruff-0.16.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ab3d62dde0b19facdd632008cc4827fc28ada7736c6bd35ab6f1050f0bfed53f", size = 11466130, upload-time = "2026-08-07T13:30:36.958Z" }, + { url = "https://files.pythonhosted.org/packages/dd/a7/a19334985c4dea8c381981fa252cd854c7ee52dc4b1686dc16f4a911c702/ruff-0.16.2-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:e43e1f5b8388da9eca1b9e88328d47a5cec794633ccf6f7484ac2dd15eee92c0", size = 11523634, upload-time = "2026-08-07T13:30:39.822Z" }, + { url = "https://files.pythonhosted.org/packages/6e/6c/96d192b0e742412ceda08c0a50f9669b253dde9fd6a60ea1a10c9fa79a63/ruff-0.16.2-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:c24788a980581e1d7ea3a0cbe4344c4fbeb0a6a9b1f4713aa46bb104f8294690", size = 10949807, upload-time = "2026-08-07T13:30:42.745Z" }, + { url = "https://files.pythonhosted.org/packages/fa/51/e26599ceca11e79ee255c7df515995561edf87e9ca1893284e44d98f5a86/ruff-0.16.2-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:81806b08329130005dd4a8a8394a0c9da8c6f4cafb16ba438d2a2ee6a18bedf1", size = 10646891, upload-time = "2026-08-07T13:30:45.522Z" }, + { url = "https://files.pythonhosted.org/packages/68/01/800c4b1f97bc8d7c6029e06b1f20473a3cf1e13c4933d8f3342add83fc55/ruff-0.16.2-py3-none-musllinux_1_2_i686.whl", hash = "sha256:4ce4e02bad779bef557f541a1b31f20d6abeae1cc05ed1b1ac019d4ffd1044c8", size = 11162063, upload-time = "2026-08-07T13:30:48.131Z" }, + { url = "https://files.pythonhosted.org/packages/e4/d0/1477ea50fc5a0d4b0b71d1d63d50770bdd794d90b43e37a7618e63ec9894/ruff-0.16.2-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:e0422abdf70070255fc4073ce9dfc814cc03db577013761ddd09bc1e4a9a4fbd", size = 11556038, upload-time = "2026-08-07T13:30:50.686Z" }, + { url = "https://files.pythonhosted.org/packages/b8/76/a7776f32048d991e16d4fa8ff91790b877342d3596cc3ed04acdbf1aaedc/ruff-0.16.2-py3-none-win32.whl", hash = "sha256:bf3a63d78fb39f4bf5ac8ae52051c5520505301abe19ba4e204c453b3f09bb0b", size = 10872850, upload-time = "2026-08-07T13:30:53.471Z" }, + { url = "https://files.pythonhosted.org/packages/00/0d/929c800d920e61397d82a01b60bffc68da3052c17d31de59efaad2e4ed75/ruff-0.16.2-py3-none-win_amd64.whl", hash = "sha256:bcabe2f6d0fc7819f1431793005af4e4de7371927d037345bf941252b195b9fa", size = 12023338, upload-time = "2026-08-07T13:30:56.193Z" }, + { url = "https://files.pythonhosted.org/packages/5b/6c/93e26c22c5f78ff87363e07da49c84955affbeb1098bd1936bf3b3f293bf/ruff-0.16.2-py3-none-win_arm64.whl", hash = "sha256:d614e95cedf38a2053fd351c55b103ba30d017d61688fdbfd40ee0412852a99f", size = 11374065, upload-time = "2026-08-07T13:30:58.775Z" }, +] + +[[package]] +name = "s3transfer" +version = "0.19.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "botocore" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/76/43/35e4d8aa320bffe8287fe8f65f578fa2d2db0a64212f0e710dce58267854/s3transfer-0.19.2.tar.gz", hash = "sha256:ba0309fd86be3c27dbf78cdd813c13c5e1df16e5874b99d2535ebbdfb9892993", size = 165592, upload-time = "2026-07-22T19:30:44.432Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bc/e7/5c595c75e9f41a44f30e526eda465ea0b4eec93470e074e4a111b253f13a/s3transfer-0.19.2-py3-none-any.whl", hash = "sha256:d8168eccca828cbb2cd573675333f3bddd254313a9c42494b84c76b539e8ba25", size = 90216, upload-time = "2026-07-22T19:30:43.251Z" }, +] + [[package]] name = "setuptools" version = "80.9.0" @@ -515,6 +1017,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/a3/dc/17031897dae0efacfea57dfd3a82fdd2a2aeb58e0ff71b77b87e44edc772/setuptools-80.9.0-py3-none-any.whl", hash = "sha256:062d34222ad13e0cc312a4c02d73f059e86a4acbfbdea8f8f76b28c99f306922", size = 1201486, upload-time = "2025-05-27T00:56:49.664Z" }, ] +[[package]] +name = "shellingham" +version = "1.5.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/58/15/8b3609fd3830ef7b27b655beb4b4e9c62313a4e8da8c676e142cc210d58e/shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de", size = 10310, upload-time = "2023-10-24T04:13:40.426Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686", size = 9755, upload-time = "2023-10-24T04:13:38.866Z" }, +] + [[package]] name = "six" version = "1.17.0" @@ -542,6 +1053,80 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/44/6f/7120676b6d73228c96e17f1f794d8ab046fc910d781c8d151120c3f1569e/toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b", size = 16588, upload-time = "2020-11-01T01:40:20.672Z" }, ] +[[package]] +name = "trustme" +version = "1.2.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cryptography" }, + { name = "idna" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/4b/c5/931476f4cf1cd9e736f32651005078061a50dc164a2569fb874e00eb2786/trustme-1.2.1.tar.gz", hash = "sha256:6528ba2bbc7f2db41f33825c8dd13e3e3eb9d334ba0f909713c8c3139f4ae47f", size = 26844, upload-time = "2025-01-02T01:55:32.632Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b5/f3/c34dbabf6da5eda56fe923226769d40e11806952cd7f46655dd06e10f018/trustme-1.2.1-py3-none-any.whl", hash = "sha256:d768e5fc57c86dfc5ec9365102e9b092541cd6954b35d8c1eea01a84f35a762a", size = 16530, upload-time = "2025-01-02T01:55:30.181Z" }, +] + +[[package]] +name = "ty" +version = "0.0.70" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/58/ed/38a8ab52f1d7c3ed701442a31b23ba774cbc5d6909f2c00da9e1f3c590f9/ty-0.0.70.tar.gz", hash = "sha256:a01bebc128b4081c16002965d906fccb21323d69bb709b9108c1f2406bcffced", size = 6601156, upload-time = "2026-08-10T23:20:26.268Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3d/c5/ddd6cc5657fd3da85591b264f4dabb1ce5e5b535e73fd5174991c294978b/ty-0.0.70-py3-none-linux_armv6l.whl", hash = "sha256:4fb2d2e55e2160c07152361be2e1a26fdd4f6261055731317d5972daf1749935", size = 12537215, upload-time = "2026-08-10T23:19:43.512Z" }, + { url = "https://files.pythonhosted.org/packages/8b/8a/acc9b34331cde81e0d63cda92d4db9e4042def5b1efa7d47431ff1d30d17/ty-0.0.70-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:9b04d4c21cb029501c05598ca21e9c0829c0b2e35a7ceba1e5b78014c1e8104d", size = 12149070, upload-time = "2026-08-10T23:19:46.369Z" }, + { url = "https://files.pythonhosted.org/packages/fd/cd/51ac2708f4d077058a0bfeefa40d630883c5ec7a82fd2c15c86519140235/ty-0.0.70-py3-none-macosx_11_0_arm64.whl", hash = "sha256:c605ebf2643f5e64ec4bcb269640a1aa85966d29fa888fb746039c28570369b2", size = 11625729, upload-time = "2026-08-10T23:19:48.535Z" }, + { url = "https://files.pythonhosted.org/packages/b7/99/afc4fe7e630100dc782ff0cdc8c59c01acfb05299551ff0ef49c93814320/ty-0.0.70-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0c38ea76e12909c29e18fcd295ff223b63f3701c1d1c34db2cdc9736340b9de2", size = 12204810, upload-time = "2026-08-10T23:19:51.845Z" }, + { url = "https://files.pythonhosted.org/packages/ca/38/8cabc3c8ad4c3a02e585ecff12a71ff8e5881f8a00ee71745fd765201da5/ty-0.0.70-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c1ee6bf4edaabf7bd0307f0c0f9ddc2204df0390820fe89f6a9de65f07722aba", size = 12308114, upload-time = "2026-08-10T23:19:54.229Z" }, + { url = "https://files.pythonhosted.org/packages/44/7e/bb4e552ecd68bb490bae9368ef323e3d0c79ef85a811857139a2d0f59ddb/ty-0.0.70-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5eef3e11d7d6b800ef66da0cce8ea24f8be804d1ebf359683426cfe848729bf8", size = 13072240, upload-time = "2026-08-10T23:19:56.872Z" }, + { url = "https://files.pythonhosted.org/packages/f6/c4/1861e1d554e5b6e0d11b3a9f36d40c372253f44a3c4e56ad4bf840f2801e/ty-0.0.70-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a95e4ae7c2599197c9d89652e49ca344ab224f7d12376e6ad8beb0587e8ff83f", size = 13497678, upload-time = "2026-08-10T23:19:59.191Z" }, + { url = "https://files.pythonhosted.org/packages/77/22/3b22442133e8f641485e59e463a070a31184db0d6456851f871db8f59763/ty-0.0.70-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:06aca758d1e0016c0a1f57fe9d8de7a21ff83f692306f747a0d97f32de24e27f", size = 13232510, upload-time = "2026-08-10T23:20:01.456Z" }, + { url = "https://files.pythonhosted.org/packages/84/b8/911f1e6885b5485b6e1d29aaab19ce5e6deeb3e931b6ad82296da4f22051/ty-0.0.70-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d81825524f1b57ecbcb5fce7d61fb159cb4837a6167a4569309c9fa7fc15a77d", size = 12817128, upload-time = "2026-08-10T23:20:04.275Z" }, + { url = "https://files.pythonhosted.org/packages/e6/a6/9affc3ca11c32d75b348a66144ab83335fa7fa7100b1581356725e15a668/ty-0.0.70-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:3287dfb09f7320ef28f114f5f9aae5f697b7b1a6ee37fb2a4a3be94481c1b4f8", size = 13089208, upload-time = "2026-08-10T23:20:06.574Z" }, + { url = "https://files.pythonhosted.org/packages/f0/cb/4776108ea08ec4013b71375b101be9c8a632967cd10f24abbdd4664d66f4/ty-0.0.70-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:9fb1877f6401cdaac4db46c5bf762f327482ba5f891420ea462ebd15d0de4185", size = 12148890, upload-time = "2026-08-10T23:20:08.827Z" }, + { url = "https://files.pythonhosted.org/packages/c9/d9/814e71f698d9231ef6412bd2c1499d81c6f86f66c2764e952c991e2d6da5/ty-0.0.70-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:e78f4997dbb0db2d2270210eb3694466206e5b3a11985e24148770e702158a25", size = 12327084, upload-time = "2026-08-10T23:20:11.023Z" }, + { url = "https://files.pythonhosted.org/packages/3b/69/9f34bba534c5d1ace391d300ead4f1971f4348c5459e66dc466cfd60661c/ty-0.0.70-py3-none-musllinux_1_2_i686.whl", hash = "sha256:cf758d3b2dad910c9b1d22d2d62fea894b5bc9acd3c00365e8a73ace6090a452", size = 12604372, upload-time = "2026-08-10T23:20:13.366Z" }, + { url = "https://files.pythonhosted.org/packages/fe/ff/abb34674517b29a8e2489a39c4e01930fcf6aab993a7d6b33aa97f119117/ty-0.0.70-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:0d338761617279a4fb6a83e7fad7e21126394637b8c45e13e196b2bb675f39b2", size = 12917800, upload-time = "2026-08-10T23:20:15.841Z" }, + { url = "https://files.pythonhosted.org/packages/a4/1a/f8289572f4fad5cb16c883b94638548166bc78cf3eb569f0cd9199eb10a0/ty-0.0.70-py3-none-win32.whl", hash = "sha256:a45642cf09dde91f0a3ce9b9e6fffda9779ff69d73f7347c18acf4fb45007c07", size = 11921482, upload-time = "2026-08-10T23:20:18.244Z" }, + { url = "https://files.pythonhosted.org/packages/17/ae/8739d7618b4670c3ee4f52d641d53be87928bd121d8bda9c0e3450500b75/ty-0.0.70-py3-none-win_amd64.whl", hash = "sha256:33e7941a926cf39b82553911a59a6ed68ec98c3d3d5a415df633f4d1cd051e6c", size = 12986994, upload-time = "2026-08-10T23:20:20.53Z" }, + { url = "https://files.pythonhosted.org/packages/f1/44/2bc3301ba4356ad8866daac9f8cfb3687953e52d076ea204f113b9304c42/ty-0.0.70-py3-none-win_arm64.whl", hash = "sha256:0d380f735d52b1d4b773193f8f5c58c065725eab1ebb0008d38a7b830de14f47", size = 12301082, upload-time = "2026-08-10T23:20:23.816Z" }, +] + +[[package]] +name = "typer" +version = "0.27.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "annotated-doc" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "rich" }, + { name = "shellingham" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ae/40/4a3db7990d1f62a53182aa96eaef57aeb2886a27f90a195bc66713565d31/typer-0.27.1.tar.gz", hash = "sha256:a79bef8469a79c45498e7b814ecf8d603cc7644e9acbd9e19cac0334240b18df", size = 203994, upload-time = "2026-08-03T14:41:03.438Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/43/89/9518bc0c3929bee36b3a4a8e3daddd6e03f92f9961c66d4983b837160543/typer-0.27.1-py3-none-any.whl", hash = "sha256:53150287edd11baeb4e4722c8e394fcdf8181c0ae89485cba8d25c778d5edd56", size = 122874, upload-time = "2026-08-03T14:41:04.391Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f6/cc/6253133b5bb138fc3306cebfbda2c520f545d36b5be2c7255cc528bb45d6/typing_extensions-4.16.0.tar.gz", hash = "sha256:dc983d19a509c94dba722ee6abd33940f7c05a89e243c47e907eb4db6f1a43e5", size = 113555, upload-time = "2026-07-02T08:40:05.92Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/49/d3/b8441a820a491ddfc024b0b0cf0393375b75ea13866d9c66727e54c2fc80/typing_extensions-4.16.0-py3-none-any.whl", hash = "sha256:481caa481374e813c1b176ada14e97f1f67a4539ce9cfeb3f350d78d6370c2e8", size = 45571, upload-time = "2026-07-02T08:40:04.659Z" }, +] + +[[package]] +name = "typing-inspection" +version = "0.4.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6d/bc/4eae18cd40c65798a16267572ba346c11f599d44b01603dbd843342042bc/typing_inspection-0.4.3.tar.gz", hash = "sha256:c5f9ec1530b5c1e2c9bc34a84d9a3466ed1b2f3f2fa9f901368d9c5596210e4d", size = 76711, upload-time = "2026-08-10T09:39:18.063Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/42/f7/7a3935abdebd5cf18705a5f0335dd6a3a18bef3baa7cb9edc3b6b9922cc8/typing_inspection-0.4.3-py3-none-any.whl", hash = "sha256:5f42b23858a91e0b4ef521f5418f03a0da3c9216fd2995ef5e73463100e676cd", size = 14693, upload-time = "2026-08-10T09:39:16.693Z" }, +] + [[package]] name = "tzdata" version = "2025.2" @@ -573,3 +1158,21 @@ sdist = { url = "https://files.pythonhosted.org/packages/20/28/e6f1a6f655d620846 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 = "wcwidth" +version = "0.8.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/34/74/c6428f875774288bec1396f5bfcbc2d925700a4dad61727fd5f2b12f249d/wcwidth-0.8.2.tar.gz", hash = "sha256:91fbef97204b96a3d4d421609b80340b760cf33e26da123ff243d76b1fda8dda", size = 1466253, upload-time = "2026-06-29T18:11:11.601Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/96/42/3e5985a0a7e57de470b320c6d6a1a67c844f6737a587f3d44dd13d1819e7/wcwidth-0.8.2-py3-none-any.whl", hash = "sha256:d63947694a0539a1d51e01eda7caf800c291020e6cdd7e28ad7b14dd33ad4f85", size = 323166, upload-time = "2026-06-29T18:11:09.888Z" }, +] + +[[package]] +name = "win32-setctime" +version = "1.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b3/8f/705086c9d734d3b663af0e9bb3d4de6578d08f46b1b101c2442fd9aecaa2/win32_setctime-1.2.0.tar.gz", hash = "sha256:ae1fdf948f5640aae05c511ade119313fb6a30d7eabe25fef9764dca5873c4c0", size = 4867, upload-time = "2024-12-07T15:28:28.314Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e1/07/c6fe3ad3e685340704d314d765b7912993bcb8dc198f0e7a89382d37974b/win32_setctime-1.2.0-py3-none-any.whl", hash = "sha256:95d644c4e708aba81dc3704a116d8cbc974d70b3bdb8be1d150e36be6e9d1390", size = 4083, upload-time = "2024-12-07T15:28:26.465Z" }, +]