diff --git a/.envrc b/.envrc new file mode 100644 index 0000000..f27138e --- /dev/null +++ b/.envrc @@ -0,0 +1,3 @@ +export TOWER_ACCESS_TOKEN=$(op read "op://Employee/Enterprise Staging Platform/credential" --no-newline) +export TOWER_WORKSPACE_ID=$(op read "op://Employee/Enterprise Staging Platform/workspace_id" --no-newline) +export TOWER_API_ENDPOINT=$(op read "op://Employee/Enterprise Staging Platform/hostname" --no-newline) diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 9996130..b4dedfd 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -29,6 +29,8 @@ conda env create -f environment.yml conda activate seqerakit ``` +Or [install tw-cli from GitHub](https://github.com/seqeralabs/tower-cli?tab=readme-ov-file#getting-started). + You can then install the local repository for development using the `pip -e` command which will install it in place without copying it to your `PYTHONPATH`. Using `--no-deps` will ignore dependencies which have already been installed via Conda. This assumes the current working directory is a clone of this repository. ```console @@ -37,6 +39,15 @@ pip install -e . --no-deps You can then develop the code before committing changes and opening a pull request. +#### uv workflow + +1. [Install uv](https://docs.astral.sh/uv/getting-started/installation/) +2. `uv run seqerakit -h` + +##### Publishing + +1. [uv publish](https://docs.astral.sh/uv/guides/publish/) + ### pre-commit We use [pre-commit](https://pre-commit.com/) which runs [Black](https://github.com/psf/black) and [Ruff](https://github.com/astral-sh/ruff) to ensure code consistency during development. Install pre-commit and configure with `pre-commit install` which will now run before every commit ensuring code consistency. diff --git a/.github/workflows/e2e-testing.yml b/.github/workflows/e2e-testing.yml index f569933..2f44382 100644 --- a/.github/workflows/e2e-testing.yml +++ b/.github/workflows/e2e-testing.yml @@ -38,21 +38,15 @@ jobs: SENTIEON_LICENSE_BASE64: ${{ secrets.SENTIEON_LICENSE_BASE64 }} steps: - - name: Check out source-code repository - uses: actions/checkout@v3 - - - name: Setup Python - uses: actions/setup-python@v2 + - uses: actions/checkout@v4 + - uses: astral-sh/setup-uv@v4 with: - python-version: '3.12' - - - name: Install pip & seqerakit - run: | - pip install -e . + enable-cache: true + cache-dependency-glob: 'uv.lock' - - name: Install Tower CLI v0.9.1 + - name: Install Tower CLI run: | - wget https://github.com/seqeralabs/tower-cli/releases/download/v0.9.1/tw-linux-x86_64 \ + wget https://github.com/seqeralabs/tower-cli/releases/download/v0.10.3/tw-linux-x86_64 \ && chmod +x tw-linux-x86_64 \ && sudo mv tw-linux-x86_64 /usr/local/bin/tw @@ -64,7 +58,7 @@ jobs: echo $GOOGLE_KEY | base64 -d > $temp_file export GOOGLE_KEY=$temp_file - seqerakit examples/yaml/e2e/*.yml --delete || true + uv run seqerakit examples/yaml/e2e/*.yml --delete || true - name: dryrun run: | @@ -80,7 +74,7 @@ jobs: echo $GOOGLE_KEY | base64 -d > $temp_file export GOOGLE_KEY=$temp_file - seqerakit examples/yaml/e2e/*.yml + uv run seqerakit examples/yaml/e2e/*.yml - name: teardown if: ( success() || failure() ) && ( github.event_name != 'workflow_dispatch' || inputs.clearup ) @@ -89,4 +83,4 @@ jobs: echo $GOOGLE_KEY | base64 -d > $temp_file export GOOGLE_KEY=$temp_file - seqerakit examples/yaml/e2e/*.yml --delete + uv runseqerakit examples/yaml/e2e/*.yml --delete diff --git a/.github/workflows/python-testing.yml b/.github/workflows/python-testing.yml index 2ce0e91..d24764c 100644 --- a/.github/workflows/python-testing.yml +++ b/.github/workflows/python-testing.yml @@ -1,29 +1,22 @@ -name: Lint-and-test +name: Lint and Test on: [push] jobs: - lint: + pre-commit: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: psf/black@stable - - uses: chartboost/ruff-action@v1 + - uses: actions/setup-python@v4 + - uses: pre-commit/action@v3.0.1 test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: actions/setup-python@v4 + - uses: astral-sh/setup-uv@v4 with: - python-version: 3.8 - cache: 'pip' # caching pip dependencies - cache-dependency-path: setup.py - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install pytest - pip install pytest-mock - pip install -e . + enable-cache: true + cache-dependency-glob: "uv.lock" - name: Run tests run: | - python -m pytest --import-mode=append tests/ + uv run pytest diff --git a/.github/workflows/teardown.yml b/.github/workflows/teardown.yml index 12a8a9a..3c61934 100644 --- a/.github/workflows/teardown.yml +++ b/.github/workflows/teardown.yml @@ -26,23 +26,17 @@ jobs: SENTIEON_LICENSE_BASE64: ${{ secrets.SENTIEON_LICENSE_BASE64 }} steps: - - name: Check out source-code repository - uses: actions/checkout@v3 - - - name: Setup conda - uses: conda-incubator/setup-miniconda@v3 + - uses: actions/checkout@v4 + - uses: astral-sh/setup-uv@v4 with: - auto-update-conda: true - environment-file: environment.yml - python-version: '3.12' - mamba-version: '*' - channels: conda-forge,bioconda - activate-environment: seqerakit - use-mamba: true + enable-cache: true + cache-dependency-glob: 'uv.lock' - - name: Install pip & seqerakit + - name: Install Tower CLI v0.9.1 run: | - pip install -e . + wget https://github.com/seqeralabs/tower-cli/releases/download/v0.10.3/tw-linux-x86_64 \ + && chmod +x tw-linux-x86_64 \ + && sudo mv tw-linux-x86_64 /usr/local/bin/tw - name: teardown run: | @@ -51,4 +45,4 @@ jobs: echo $GOOGLE_KEY | base64 -d > $temp_file export GOOGLE_KEY=$temp_file - seqerakit --delete examples/yaml/seqerakit-e2e.yml + uv run seqerakit --delete examples/yaml/seqerakit-e2e.yml diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index c75522c..a191203 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -14,13 +14,13 @@ repos: rev: 'v2.7.1' hooks: - id: prettier - - repo: https://github.com/psf/black - rev: 24.10.0 - hooks: - - id: black - repo: https://github.com/astral-sh/ruff-pre-commit - # Ruff version. - rev: v0.0.272 + rev: v0.8.3 hooks: - id: ruff args: [--fix, --exit-non-zero-on-fix] + - id: ruff-format + - repo: https://github.com/astral-sh/uv-pre-commit + rev: 0.5.9 + hooks: + - id: uv-lock diff --git a/.python-version b/.python-version new file mode 100644 index 0000000..e4fba21 --- /dev/null +++ b/.python-version @@ -0,0 +1 @@ +3.12 diff --git a/examples/yaml/datasets/rnaseq_samples.csv b/examples/yaml/datasets/rnaseq_samples.csv index 6a6fcbd..25b7dbb 100644 --- a/examples/yaml/datasets/rnaseq_samples.csv +++ b/examples/yaml/datasets/rnaseq_samples.csv @@ -5,4 +5,4 @@ WT_REP2,s3://ngi-igenomes/test-data/rnaseq/minimal/GSE110004/SRR6357072_1.fastq. RAP1_UNINDUCED_REP1,s3://ngi-igenomes/test-data/rnaseq/minimal/GSE110004/SRR6357073_1.fastq.gz,,auto RAP1_UNINDUCED_REP2,s3://ngi-igenomes/test-data/rnaseq/minimal/GSE110004/SRR6357074_1.fastq.gz,,reverse RAP1_UNINDUCED_REP2,s3://ngi-igenomes/test-data/rnaseq/minimal/GSE110004/SRR6357075_1.fastq.gz,,reverse -RAP1_IAA_30M_REP1,s3://ngi-igenomes/test-data/rnaseq/minimal/GSE110004/SRR6357076_1.fastq.gz,s3://ngi-igenomes/test-data/rnaseq/minimal/GSE110004/SRR6357076_2.fastq.gz,reverse \ No newline at end of file +RAP1_IAA_30M_REP1,s3://ngi-igenomes/test-data/rnaseq/minimal/GSE110004/SRR6357076_1.fastq.gz,s3://ngi-igenomes/test-data/rnaseq/minimal/GSE110004/SRR6357076_2.fastq.gz,reverse diff --git a/examples/yaml/pipelines/nextflow.config b/examples/yaml/pipelines/nextflow.config index 612d322..bab9792 100644 --- a/examples/yaml/pipelines/nextflow.config +++ b/examples/yaml/pipelines/nextflow.config @@ -1 +1 @@ -process.maxRetries = 1 \ No newline at end of file +process.maxRetries = 1 diff --git a/examples/yaml/pipelines/nf_core_rnaseq_params.yml b/examples/yaml/pipelines/nf_core_rnaseq_params.yml index ce0e8c9..4cfcf78 100644 --- a/examples/yaml/pipelines/nf_core_rnaseq_params.yml +++ b/examples/yaml/pipelines/nf_core_rnaseq_params.yml @@ -1 +1 @@ -outdir: 'az://seqeralabs-showcase/nf-core-rnaseq/results' \ No newline at end of file +outdir: 'az://seqeralabs-showcase/nf-core-rnaseq/results' diff --git a/examples/yaml/pipelines/nf_core_viralrecon_illumina_params.yml b/examples/yaml/pipelines/nf_core_viralrecon_illumina_params.yml index e369cf8..9ec28c1 100644 --- a/examples/yaml/pipelines/nf_core_viralrecon_illumina_params.yml +++ b/examples/yaml/pipelines/nf_core_viralrecon_illumina_params.yml @@ -1 +1 @@ -outdir: 'gs://seqeralabs-showcase-eu-north-1/nf-core-viralrecon/illumina/results' \ No newline at end of file +outdir: 'gs://seqeralabs-showcase-eu-north-1/nf-core-viralrecon/illumina/results' diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..dabdb4b --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,56 @@ +[project] +name = "seqerakit" +version = "0.4.9" +description = "Automate creation of Seqera Platform resources" +readme = "README.md" +requires-python = ">=3.8,<4" +keywords = [ + "nextflow", + "bioinformatics", + "workflow", + "pipeline", + "seqera-platform", + "seqera", +] +authors = [ + {name = "Esha Joshi", email = "esha.joshi@seqera.io"}, + {name = "Adam Talbot", email = "adam.talbot@seqera.io"}, + {name = "Harshil Patel", email = "harshil.patel@seqera.io"}, +] +# TODO +maintainers = [ + {name = "Esha Joshi", email = "esha.joshi@seqera.io"}, +] +# TODO https://pypi.org/classifiers/ +classifiers = [ + "License :: OSI Approved :: Apache Software License" +] +dependencies = [ + "pyyaml>=6.0.0", + "typer>=0.15.1", +] +[project.urls] +Homepage = "https://seqera.io/" +Documentation = "https://docs.seqera.io/platform/24.2/seqerakit/installation" +Repository = "https://github.com/seqeralabs/seqera-kit" +Issues = "https://github.com/seqeralabs/seqera-kit/issues" +Changelog = "https://github.com/seqeralabs/seqera-kit/releases" + +[project.scripts] +seqerakit = "seqerakit.cli:run" + +[build-system] +requires = ["setuptools>=61.0"] +build-backend = "setuptools.build_meta" + +[tool.setuptools] +packages = ["seqerakit"] +zip-safe = false +include-package-data = true + +[dependency-groups] +dev = [ + "pre-commit>=3.5.0", + "pytest-mock>=3.14.0", + "pytest>=8.3.4", +] diff --git a/seqerakit/cli.py b/seqerakit/cli.py index bc5905e..2e71005 100644 --- a/seqerakit/cli.py +++ b/seqerakit/cli.py @@ -17,81 +17,28 @@ Requires a YAML file that defines the resources to be created in Seqera Platform and the required options for each resource based on the Seqera Platform CLI. """ -import argparse + import logging import sys +from typing import List, Optional +from typing_extensions import Annotated + +import typer +from rich.console import Console from seqerakit import seqeraplatform, helper, overwrite from seqerakit.seqeraplatform import ResourceExistsError, ResourceCreationError from seqerakit import __version__ -logger = logging.getLogger(__name__) - - -def parse_args(args=None): - parser = argparse.ArgumentParser( - description="Seqerakit: Python wrapper for the Seqera Platform CLI" - ) - # General options - general = parser.add_argument_group("General Options") - general.add_argument( - "-l", - "--log_level", - default="INFO", - choices=("CRITICAL", "ERROR", "WARNING", "INFO", "DEBUG"), - help="Set the logging level.", - ) - general.add_argument( - "--info", - "-i", - action="store_true", - help="Display Seqera Platform information and exit.", - ) - general.add_argument( - "-j", "--json", action="store_true", help="Output JSON format in stdout." - ) - general.add_argument( - "--dryrun", - "-d", - action="store_true", - help="Print the commands that would be executed.", - ) - general.add_argument( - "--version", - "-v", - action="version", - version=f"%(prog)s {__version__}", - help="Show version number and exit.", - ) - - # YAML processing options - yaml_processing = parser.add_argument_group("YAML Processing Options") - yaml_processing.add_argument( - "yaml", - nargs="*", - help="One or more YAML files with Seqera Platform resource definitions.", - ) - yaml_processing.add_argument( - "--delete", - action="store_true", - help="Recursively delete resources defined in the YAML files.", - ) - yaml_processing.add_argument( - "--cli", - dest="cli_args", - type=str, - help="Additional Seqera Platform CLI specific options to be passed," - " enclosed in double quotes (e.g. '--cli=\"--insecure\"').", - ) - yaml_processing.add_argument( - "--targets", - dest="targets", - type=str, - help="Specify the resources to be targeted for creation in a YAML file through " - "a comma-separated list (e.g. '--targets=teams,participants').", - ) - return parser.parse_args(args) +# Initialize typer app and console +app = typer.Typer( + help="Seqerakit: Python wrapper for the Seqera Platform CLI", + rich_markup_mode="rich", +) +console = Console() +# Set up logging +logger = logging.getLogger(__name__) class BlockParser: """ @@ -124,7 +71,7 @@ def __init__(self, sp, list_for_add_method): def handle_block(self, block, args, destroy=False, dryrun=False): # Check if delete is set to True, and call delete handler if destroy: - logging.debug(" The '--delete' flag has been specified.\n") + logging.debug("The '--delete' flag has been specified.\n") self.overwrite_method.handle_overwrite( block, args["cmd_args"], overwrite=False, destroy=True ) @@ -144,7 +91,7 @@ def handle_block(self, block, args, destroy=False, dryrun=False): # Check if overwrite is set to True, and call overwrite handler overwrite_option = args.get("overwrite", False) if overwrite_option and not dryrun: - logging.debug(f" Overwrite is set to 'True' for {block}\n") + logging.debug(f"Overwrite is set to 'True' for {block}\n") self.overwrite_method.handle_overwrite( block, args["cmd_args"], overwrite_option ) @@ -156,39 +103,101 @@ def handle_block(self, block, args, destroy=False, dryrun=False): else: logger.error(f"Unrecognized resource block in YAML: {block}") - -def main(args=None): - options = parse_args(args if args is not None else sys.argv[1:]) - logging.basicConfig(level=getattr(logging, options.log_level.upper())) +@app.command() +def main( + yaml: Annotated[ + Optional[List[str]], + typer.Argument( + help="One or more YAML files with Seqera Platform resource definitions", + show_default=False + ) + ] = None, + log_level: Annotated[ + str, + typer.Option( + "--log-level", "-l", + help="Set the logging level", + case_sensitive=False + ) + ] = "INFO", + info: Annotated[ + bool, + typer.Option( + "--info", "-i", + help="Display Seqera Platform information and exit" + ) + ] = False, + json_output: Annotated[ + bool, + typer.Option( + "--json", "-j", + help="Output JSON format in stdout" + ) + ] = False, + dryrun: Annotated[ + bool, + typer.Option( + "--dryrun", "-d", + help="Print the commands that would be executed" + ) + ] = False, + delete: Annotated[ + bool, + typer.Option( + help="Recursively delete resources defined in the YAML files" + ) + ] = False, + cli_args: Annotated[ + Optional[str], + typer.Option( + "--cli", + help="Additional Seqera Platform CLI specific options to be passed" + ) + ] = None, + targets: Annotated[ + Optional[str], + typer.Option( + help="Specify the resources to be targeted for creation" + ) + ] = None, +): + """ + Process YAML configuration files to manage Seqera Platform resources. + """ + # Set up logging + logging.basicConfig(level=getattr(logging, log_level.upper())) # Parse CLI arguments into a list - cli_args_list = options.cli_args.split() if options.cli_args else [] + cli_args_list = cli_args.split() if cli_args else [] + # Initialize SeqeraPlatform sp = seqeraplatform.SeqeraPlatform( - cli_args=cli_args_list, dryrun=options.dryrun, json=options.json + cli_args=cli_args_list, + dryrun=dryrun, + json=json_output ) - # If the info flag is set, run 'tw info' - if options.info: + # Handle info command + if info: result = sp.info() - if not options.dryrun: - print(result) + if not dryrun: + console.print(result) return - if not options.yaml: + # Handle YAML input + if not yaml: if sys.stdin.isatty(): - logging.error( - " No YAML(s) provided and no input from stdin. Please provide " + raise typer.BadParameter( + "No YAML(s) provided and no input from stdin. Please provide " "at least one YAML configuration file or pipe input from stdin." ) - sys.exit(1) - else: - options.yaml = [sys.stdin] + yaml = [sys.stdin] + # Initialize BlockParser block_manager = BlockParser( sp, [ - "organizations", # all use method.add + "organizations", # all use method.add "workspaces", "labels", "members", @@ -203,17 +212,20 @@ def main(args=None): # and get a dictionary of command line arguments try: cmd_args_dict = helper.parse_all_yaml( - options.yaml, destroy=options.delete, targets=options.targets + yaml, destroy=delete, targets=targets ) for block, args_list in cmd_args_dict.items(): for args in args_list: block_manager.handle_block( - block, args, destroy=options.delete, dryrun=options.dryrun + block, args, destroy=delete, dryrun=dryrun ) except (ResourceExistsError, ResourceCreationError, ValueError) as e: - logging.error(e) - sys.exit(1) + logger.error(str(e)) + raise typer.Exit(code=1) +def run(): + """Entry point for the CLI""" + app(prog_name="seqerakit") if __name__ == "__main__": - main() + run() diff --git a/seqerakit/computeenvs.py b/seqerakit/computeenvs.py index 9254a54..785103c 100644 --- a/seqerakit/computeenvs.py +++ b/seqerakit/computeenvs.py @@ -15,6 +15,7 @@ """ Subclass of SeqeraPlatform class for overriding compute environments subcommand methods. """ + from pathlib import Path from seqerakit.seqeraplatform import SeqeraPlatform diff --git a/seqerakit/helper.py b/seqerakit/helper.py index 86a71a1..e439439 100644 --- a/seqerakit/helper.py +++ b/seqerakit/helper.py @@ -17,6 +17,7 @@ Including handling methods for each block in the YAML file, and parsing methods for each block in the YAML file. """ + import yaml from seqerakit import utils import sys diff --git a/seqerakit/pipelines.py b/seqerakit/pipelines.py index 8596e80..92d7005 100644 --- a/seqerakit/pipelines.py +++ b/seqerakit/pipelines.py @@ -15,6 +15,7 @@ """ Subclass of SeqeraPlatform class for overriding pipelines subcommand methods. """ + from pathlib import Path from seqerakit.seqeraplatform import SeqeraPlatform diff --git a/setup.py b/setup.py deleted file mode 100644 index ee30640..0000000 --- a/setup.py +++ /dev/null @@ -1,34 +0,0 @@ -#!/usr/bin/env python - -from setuptools import find_packages, setup - -VERSION = "0.4.9" - -with open("README.md") as f: - readme = f.read() - -setup( - name="seqerakit", - version=VERSION, - description="Automate creation of Seqera Platform resources", - long_description=readme, - long_description_content_type="text/markdown", - keywords=[ - "nextflow", - "bioinformatics", - "workflow", - "pipeline", - "seqera-platform", - "seqera", - ], - author="Esha Joshi, Adam Talbot, Harshil Patel", - author_email="esha.joshi@seqera.io, adam.talbot@seqera.io, harshil.patel@seqera.io", - url="https://github.com/seqeralabs/seqera-kit", - license="Apache 2.0", - entry_points={"console_scripts": ["seqerakit=seqerakit.cli:main"]}, - python_requires=">=3.8, <4", # untested - install_requires=["pyyaml>=6.0.0"], - packages=find_packages(exclude=("docs")), - include_package_data=True, - zip_safe=False, -) diff --git a/templates/compute-envs.yml b/templates/compute-envs.yml index d5c074f..bbe4219 100644 --- a/templates/compute-envs.yml +++ b/templates/compute-envs.yml @@ -10,14 +10,14 @@ compute-envs: wait: 'AVAILABLE' # optional file-path: './compute-envs/my_aws_compute_environment.json' # required overwrite: True # optional - + # To create a compute environment with options specified through YAML (AWS Example) - type: aws-batch # required config-mode: forge # required for AWS and Azure name: 'my_aws_compute_environment' # required workspace: 'my_organization/my_workspace' # optional credentials: 'my_aws_credentials' # required - region: 'eu-west-1' # required + region: 'eu-west-1' # required work-dir: 's3://my-bucket' # required provisioning-model: 'SPOT' # optional fusion-v2: False # optional @@ -25,12 +25,12 @@ compute-envs: fargate: False # optional fast-storage: False # optional instance-types: 'c6i,r6i,m6i' # optional, comma separated list - no-ebs-auto-scale: True # optional + no-ebs-auto-scale: True # optional max-cpus: 500 # required labels: 'label1,label2' # optional, comma separated list - vpc-id: 'vpc-1234567890' # optional + vpc-id: 'vpc-1234567890' # optional subnets: 'subnet-1234567890,subnet-1234567891' # optional, comma separated list security-groups: 'sg-1234567890,sg-1234567891' # optional, comma separated list allow-buckets: 's3://my-bucket,s3://my-other-bucket' # optional, comma separated list - wait: 'AVAILABLE' # optional - overwrite: False # optional \ No newline at end of file + wait: 'AVAILABLE' # optional + overwrite: False # optional diff --git a/templates/datasets.yml b/templates/datasets.yml index 7c6fe99..b0177ca 100644 --- a/templates/datasets.yml +++ b/templates/datasets.yml @@ -5,4 +5,4 @@ datasets: header: true # optional workspace: 'my_organization/my_workspace' # optional file-path: './datasets/my_dataset.csv' # required - overwrite: True # optional \ No newline at end of file + overwrite: True # optional diff --git a/templates/members.yml b/templates/members.yml index d3f2d82..82f7a6a 100644 --- a/templates/members.yml +++ b/templates/members.yml @@ -2,4 +2,4 @@ participants: - user: 'bob@myorg.io' # required organization: 'myorg' # required - overwrite: True # optional \ No newline at end of file + overwrite: True # optional diff --git a/templates/organizations.yml b/templates/organizations.yml index acd4d18..2184e51 100644 --- a/templates/organizations.yml +++ b/templates/organizations.yml @@ -5,4 +5,4 @@ organizations: description: 'My test organisation' # optional location: 'Global' # optional website: 'https://my_organization/' # optional - overwrite: True # optional \ No newline at end of file + overwrite: True # optional diff --git a/templates/participants.yml b/templates/participants.yml index e0d69cc..5c3256a 100644 --- a/templates/participants.yml +++ b/templates/participants.yml @@ -7,4 +7,4 @@ participants: - name: 'my_team_member@gmail.com' # required type: 'MEMBER' # required workspace: 'my_organization/my_workspace' # required - role: 'LAUNCH' # required \ No newline at end of file + role: 'LAUNCH' # required diff --git a/templates/teams.yml b/templates/teams.yml index e6f921b..329a0df 100644 --- a/templates/teams.yml +++ b/templates/teams.yml @@ -5,4 +5,4 @@ teams: description: 'My test team' # optional members: # optional - 'my_team_member@gmail.com' - overwrite: True # optional \ No newline at end of file + overwrite: True # optional diff --git a/templates/workspaces.yml b/templates/workspaces.yml index c11cea3..d0f4b7f 100644 --- a/templates/workspaces.yml +++ b/templates/workspaces.yml @@ -5,4 +5,4 @@ workspaces: organization: 'my_organization' # required description: 'My test workspace' # optional visibility: 'PRIVATE' # optional - overwrite: True # optional \ No newline at end of file + overwrite: True # optional diff --git a/tests/unit/test_cli.py b/tests/unit/test_cli.py new file mode 100644 index 0000000..2545880 --- /dev/null +++ b/tests/unit/test_cli.py @@ -0,0 +1,142 @@ +import pytest +from typer.testing import CliRunner +from seqerakit.cli import app +import tempfile +import os + +runner = CliRunner() + + +@pytest.mark.xfail(reason="Not implemented") +def test_cli_version(): + """Test the version output""" + result = runner.invoke(app, ["--version"]) + assert result.exit_code == 0 + assert "seqerakit" in result.stdout + + +def test_cli_help(): + """Test the help output""" + result = runner.invoke(app, ["--help"]) + assert result.exit_code == 0 + assert "Process YAML configuration files to manage Seqera Platform resources." in result.stdout + + +def test_cli_info(): + """Test the info command""" + result = runner.invoke(app, ["--info"]) + assert result.exit_code == 0 + + +def test_cli_no_yaml(): + """Test error when no YAML is provided""" + result = runner.invoke(app) + assert result.exit_code == 1 + # FIXME assert "No YAML" in result.stdout + + +def test_cli_invalid_yaml(): + """Test error with invalid YAML file""" + with tempfile.NamedTemporaryFile(suffix=".yml") as tmp: + tmp.write(b"invalid: - yaml: content") + tmp.flush() + result = runner.invoke(app, [tmp.name, "--dryrun"]) + assert result.exit_code == 1 + + +def test_cli_dryrun(): + """Test dryrun mode with valid YAML""" + yaml_content = """ +launch: + - name: 'test-pipeline' + workspace: 'test/workspace' + compute-env: 'test-env' + pipeline: 'https://github.com/test/pipeline' +""" + with tempfile.NamedTemporaryFile(suffix=".yml", mode="w") as tmp: + tmp.write(yaml_content) + tmp.flush() + result = runner.invoke(app, [tmp.name, "--dryrun"]) + assert result.exit_code == 0 + + +def test_cli_targets(): + """Test targeting specific resources""" + yaml_content = """ +pipelines: + - name: 'test-pipeline' + url: 'https://github.com/test/pipeline' + workspace: 'test/workspace' +launch: + - name: 'test-launch' + workspace: 'test/workspace' + pipeline: 'https://github.com/test/pipeline' +""" + with tempfile.NamedTemporaryFile(suffix=".yml", mode="w") as tmp: + tmp.write(yaml_content) + tmp.flush() + result = runner.invoke(app, [tmp.name, "--targets", "pipelines", "--dryrun"]) + assert result.exit_code == 0 + + +def test_cli_json_output(): + """Test JSON output format""" + yaml_content = """ +launch: + - name: 'test-pipeline' + workspace: 'seqeralabs/scidev-testing' + compute-env: 'seqera_aws_london_fusion_nvme' + pipeline: 'https://github.com/test/pipeline' +""" + with tempfile.NamedTemporaryFile(suffix=".yml", mode="w") as tmp: + tmp.write(yaml_content) + tmp.flush() + result = runner.invoke(app, [tmp.name, "--json", "--dryrun"]) + assert result.exit_code == 0 + + +def test_cli_log_level(): + """Test different log levels""" + yaml_content = """ +launch: + - name: 'test-pipeline' + workspace: 'seqeralabs/scidev-testing' + compute-env: 'seqera_aws_london_fusion_nvme' + pipeline: 'https://github.com/test/pipeline' +""" + with tempfile.NamedTemporaryFile(suffix=".yml", mode="w") as tmp: + tmp.write(yaml_content) + tmp.flush() + result = runner.invoke(app, [tmp.name, "--log-level", "DEBUG", "--dryrun"]) + assert result.exit_code == 0 + + +def test_cli_delete(): + """Test delete mode""" + yaml_content = """ +pipelines: + - name: 'test-pipeline' + url: 'https://github.com/test/pipeline' + workspace: 'seqeralabs/scidev-testing' +""" + with tempfile.NamedTemporaryFile(suffix=".yml", mode="w") as tmp: + tmp.write(yaml_content) + tmp.flush() + result = runner.invoke(app, [tmp.name, "--delete", "--dryrun"]) + assert result.exit_code == 0 + + +def test_cli_additional_args(): + """Test passing additional CLI arguments""" + yaml_content = """ +launch: + - name: 'test-pipeline' + workspace: 'test/workspace' + compute-env: 'test-env' + pipeline: 'https://github.com/test/pipeline' +""" + with tempfile.NamedTemporaryFile(suffix=".yml", mode="w") as tmp: + tmp.write(yaml_content) + tmp.flush() + result = runner.invoke(app, [tmp.name, "--cli", "--insecure", "--dryrun"]) + assert result.exit_code == 0 diff --git a/uv.lock b/uv.lock new file mode 100644 index 0000000..4c09171 --- /dev/null +++ b/uv.lock @@ -0,0 +1,377 @@ +version = 1 +requires-python = ">=3.8, <4" + +[[package]] +name = "cfgv" +version = "3.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/11/74/539e56497d9bd1d484fd863dd69cbbfa653cd2aa27abfe35653494d85e94/cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560", size = 7114 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c5/55/51844dd50c4fc7a33b653bfaba4c2456f06955289ca770a5dbd5fd267374/cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9", size = 7249 }, +] + +[[package]] +name = "click" +version = "8.1.7" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "platform_system == 'Windows'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/96/d3/f04c7bfcf5c1862a2a5b845c6b2b360488cf47af55dfa79c98f6a6bf98b5/click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de", size = 336121 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/00/2e/d53fa4befbf2cfa713304affc7ca780ce4fc1fd8710527771b58311a3229/click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28", size = 97941 }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 }, +] + +[[package]] +name = "distlib" +version = "0.3.9" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0d/dd/1bec4c5ddb504ca60fc29472f3d27e8d4da1257a854e1d96742f15c1d02d/distlib-0.3.9.tar.gz", hash = "sha256:a60f20dea646b8a33f3e7772f74dc0b2d0772d2837ee1342a00645c81edf9403", size = 613923 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/91/a1/cf2472db20f7ce4a6be1253a81cfdf85ad9c7885ffbed7047fb72c24cf87/distlib-0.3.9-py2.py3-none-any.whl", hash = "sha256:47f8c22fd27c27e25a65601af709b38e4f0a45ea4fc2e710f65755fa8caaaf87", size = 468973 }, +] + +[[package]] +name = "exceptiongroup" +version = "1.2.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/09/35/2495c4ac46b980e4ca1f6ad6db102322ef3ad2410b79fdde159a4b0f3b92/exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc", size = 28883 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/02/cc/b7e31358aac6ed1ef2bb790a9746ac2c69bcb3c8588b41616914eb106eaf/exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b", size = 16453 }, +] + +[[package]] +name = "filelock" +version = "3.16.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9d/db/3ef5bb276dae18d6ec2124224403d1d67bccdbefc17af4cc8f553e341ab1/filelock-3.16.1.tar.gz", hash = "sha256:c249fbfcd5db47e5e2d6d62198e565475ee65e4831e2561c8e313fa7eb961435", size = 18037 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b9/f8/feced7779d755758a52d1f6635d990b8d98dc0a29fa568bbe0625f18fdf3/filelock-3.16.1-py3-none-any.whl", hash = "sha256:2082e5703d51fbf98ea75855d9d5527e33d8ff23099bec374a134febee6946b0", size = 16163 }, +] + +[[package]] +name = "identify" +version = "2.6.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/29/bb/25024dbcc93516c492b75919e76f389bac754a3e4248682fba32b250c880/identify-2.6.1.tar.gz", hash = "sha256:91478c5fb7c3aac5ff7bf9b4344f803843dc586832d5f110d672b19aa1984c98", size = 99097 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7d/0c/4ef72754c050979fdcc06c744715ae70ea37e734816bb6514f79df77a42f/identify-2.6.1-py2.py3-none-any.whl", hash = "sha256:53863bcac7caf8d2ed85bd20312ea5dcfc22226800f6d6881f232d861db5a8f0", size = 98972 }, +] + +[[package]] +name = "iniconfig" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d7/4b/cbd8e699e64a6f16ca3a8220661b5f83792b3017d0f79807cb8708d33913/iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3", size = 4646 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374", size = 5892 }, +] + +[[package]] +name = "markdown-it-py" +version = "3.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mdurl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/38/71/3b932df36c1a044d397a1f92d1cf91ee0a503d91e470cbd670aa66b07ed0/markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb", size = 74596 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/42/d7/1ec15b46af6af88f19b8e5ffea08fa375d433c998b8a7639e76935c14f1f/markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1", size = 87528 }, +] + +[[package]] +name = "mdurl" +version = "0.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979 }, +] + +[[package]] +name = "nodeenv" +version = "1.9.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/43/16/fc88b08840de0e0a72a2f9d8c6bae36be573e475a6326ae854bcc549fc45/nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f", size = 47437 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d2/1d/1b658dbd2b9fa9c4c9f32accbfc0205d532c8c6194dc0f2a4c0428e7128a/nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9", size = 22314 }, +] + +[[package]] +name = "packaging" +version = "24.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d0/63/68dbb6eb2de9cb10ee4c9c14a0148804425e13c4fb20d61cce69f53106da/packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f", size = 163950 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/ef/eb23f262cca3c0c4eb7ab1933c3b1f03d021f2c48f54763065b6f0e321be/packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759", size = 65451 }, +] + +[[package]] +name = "platformdirs" +version = "4.3.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/13/fc/128cc9cb8f03208bdbf93d3aa862e16d376844a14f9a0ce5cf4507372de4/platformdirs-4.3.6.tar.gz", hash = "sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907", size = 21302 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3c/a6/bc1012356d8ece4d66dd75c4b9fc6c1f6650ddd5991e421177d9f8f671be/platformdirs-4.3.6-py3-none-any.whl", hash = "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb", size = 18439 }, +] + +[[package]] +name = "pluggy" +version = "1.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/96/2d/02d4312c973c6050a18b314a5ad0b3210edb65a906f868e31c111dede4a6/pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1", size = 67955 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/5f/e351af9a41f866ac3f1fac4ca0613908d9a41741cfcf2228f4ad853b697d/pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669", size = 20556 }, +] + +[[package]] +name = "pre-commit" +version = "3.5.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cfgv" }, + { name = "identify" }, + { name = "nodeenv" }, + { name = "pyyaml" }, + { name = "virtualenv" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/04/b3/4ae08d21eb097162f5aad37f4585f8069a86402ed7f5362cc9ae097f9572/pre_commit-3.5.0.tar.gz", hash = "sha256:5804465c675b659b0862f07907f96295d490822a450c4c40e747d0b1c6ebcb32", size = 177079 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6c/75/526915fedf462e05eeb1c75ceaf7e3f9cde7b5ce6f62740fe5f7f19a0050/pre_commit-3.5.0-py2.py3-none-any.whl", hash = "sha256:841dc9aef25daba9a0238cd27984041fa0467b4199fc4852e27950664919f660", size = 203698 }, +] + +[[package]] +name = "pygments" +version = "2.18.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8e/62/8336eff65bcbc8e4cb5d05b55faf041285951b6e80f33e2bff2024788f31/pygments-2.18.0.tar.gz", hash = "sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199", size = 4891905 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f7/3f/01c8b82017c199075f8f788d0d906b9ffbbc5a47dc9918a945e13d5a2bda/pygments-2.18.0-py3-none-any.whl", hash = "sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a", size = 1205513 }, +] + +[[package]] +name = "pytest" +version = "8.3.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/05/35/30e0d83068951d90a01852cb1cef56e5d8a09d20c7f511634cc2f7e0372a/pytest-8.3.4.tar.gz", hash = "sha256:965370d062bce11e73868e0335abac31b4d3de0e82f4007408d242b4f8610761", size = 1445919 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/11/92/76a1c94d3afee238333bc0a42b82935dd8f9cf8ce9e336ff87ee14d9e1cf/pytest-8.3.4-py3-none-any.whl", hash = "sha256:50e16d954148559c9a74109af1eaf0c945ba2d8f30f0a3d3335edde19788b6f6", size = 343083 }, +] + +[[package]] +name = "pytest-mock" +version = "3.14.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pytest" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c6/90/a955c3ab35ccd41ad4de556596fa86685bf4fc5ffcc62d22d856cfd4e29a/pytest-mock-3.14.0.tar.gz", hash = "sha256:2719255a1efeceadbc056d6bf3df3d1c5015530fb40cf347c0f9afac88410bd0", size = 32814 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f2/3b/b26f90f74e2986a82df6e7ac7e319b8ea7ccece1caec9f8ab6104dc70603/pytest_mock-3.14.0-py3-none-any.whl", hash = "sha256:0b72c38033392a5f4621342fe11e9219ac11ec9d375f8e2a0c164539e0d70f6f", size = 9863 }, +] + +[[package]] +name = "pyyaml" +version = "6.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/54/ed/79a089b6be93607fa5cdaedf301d7dfb23af5f25c398d5ead2525b063e17/pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", size = 130631 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9b/95/a3fac87cb7158e231b5a6012e438c647e1a87f09f8e0d123acec8ab8bf71/PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086", size = 184199 }, + { url = "https://files.pythonhosted.org/packages/c7/7a/68bd47624dab8fd4afbfd3c48e3b79efe09098ae941de5b58abcbadff5cb/PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf", size = 171758 }, + { url = "https://files.pythonhosted.org/packages/49/ee/14c54df452143b9ee9f0f29074d7ca5516a36edb0b4cc40c3f280131656f/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237", size = 718463 }, + { url = "https://files.pythonhosted.org/packages/4d/61/de363a97476e766574650d742205be468921a7b532aa2499fcd886b62530/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b", size = 719280 }, + { url = "https://files.pythonhosted.org/packages/6b/4e/1523cb902fd98355e2e9ea5e5eb237cbc5f3ad5f3075fa65087aa0ecb669/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed", size = 751239 }, + { url = "https://files.pythonhosted.org/packages/b7/33/5504b3a9a4464893c32f118a9cc045190a91637b119a9c881da1cf6b7a72/PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180", size = 695802 }, + { url = "https://files.pythonhosted.org/packages/5c/20/8347dcabd41ef3a3cdc4f7b7a2aff3d06598c8779faa189cdbf878b626a4/PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68", size = 720527 }, + { url = "https://files.pythonhosted.org/packages/be/aa/5afe99233fb360d0ff37377145a949ae258aaab831bde4792b32650a4378/PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99", size = 144052 }, + { url = "https://files.pythonhosted.org/packages/b5/84/0fa4b06f6d6c958d207620fc60005e241ecedceee58931bb20138e1e5776/PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e", size = 161774 }, + { url = "https://files.pythonhosted.org/packages/f8/aa/7af4e81f7acba21a4c6be026da38fd2b872ca46226673c89a758ebdc4fd2/PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774", size = 184612 }, + { url = "https://files.pythonhosted.org/packages/8b/62/b9faa998fd185f65c1371643678e4d58254add437edb764a08c5a98fb986/PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee", size = 172040 }, + { url = "https://files.pythonhosted.org/packages/ad/0c/c804f5f922a9a6563bab712d8dcc70251e8af811fce4524d57c2c0fd49a4/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c", size = 736829 }, + { url = "https://files.pythonhosted.org/packages/51/16/6af8d6a6b210c8e54f1406a6b9481febf9c64a3109c541567e35a49aa2e7/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317", size = 764167 }, + { url = "https://files.pythonhosted.org/packages/75/e4/2c27590dfc9992f73aabbeb9241ae20220bd9452df27483b6e56d3975cc5/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85", size = 762952 }, + { url = "https://files.pythonhosted.org/packages/9b/97/ecc1abf4a823f5ac61941a9c00fe501b02ac3ab0e373c3857f7d4b83e2b6/PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4", size = 735301 }, + { url = "https://files.pythonhosted.org/packages/45/73/0f49dacd6e82c9430e46f4a027baa4ca205e8b0a9dce1397f44edc23559d/PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e", size = 756638 }, + { url = "https://files.pythonhosted.org/packages/22/5f/956f0f9fc65223a58fbc14459bf34b4cc48dec52e00535c79b8db361aabd/PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5", size = 143850 }, + { url = "https://files.pythonhosted.org/packages/ed/23/8da0bbe2ab9dcdd11f4f4557ccaf95c10b9811b13ecced089d43ce59c3c8/PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44", size = 161980 }, + { url = "https://files.pythonhosted.org/packages/86/0c/c581167fc46d6d6d7ddcfb8c843a4de25bdd27e4466938109ca68492292c/PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab", size = 183873 }, + { url = "https://files.pythonhosted.org/packages/a8/0c/38374f5bb272c051e2a69281d71cba6fdb983413e6758b84482905e29a5d/PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725", size = 173302 }, + { url = "https://files.pythonhosted.org/packages/c3/93/9916574aa8c00aa06bbac729972eb1071d002b8e158bd0e83a3b9a20a1f7/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5", size = 739154 }, + { url = "https://files.pythonhosted.org/packages/95/0f/b8938f1cbd09739c6da569d172531567dbcc9789e0029aa070856f123984/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425", size = 766223 }, + { url = "https://files.pythonhosted.org/packages/b9/2b/614b4752f2e127db5cc206abc23a8c19678e92b23c3db30fc86ab731d3bd/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476", size = 767542 }, + { url = "https://files.pythonhosted.org/packages/d4/00/dd137d5bcc7efea1836d6264f049359861cf548469d18da90cd8216cf05f/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48", size = 731164 }, + { url = "https://files.pythonhosted.org/packages/c9/1f/4f998c900485e5c0ef43838363ba4a9723ac0ad73a9dc42068b12aaba4e4/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b", size = 756611 }, + { url = "https://files.pythonhosted.org/packages/df/d1/f5a275fdb252768b7a11ec63585bc38d0e87c9e05668a139fea92b80634c/PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4", size = 140591 }, + { url = "https://files.pythonhosted.org/packages/0c/e8/4f648c598b17c3d06e8753d7d13d57542b30d56e6c2dedf9c331ae56312e/PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8", size = 156338 }, + { url = "https://files.pythonhosted.org/packages/ef/e3/3af305b830494fa85d95f6d95ef7fa73f2ee1cc8ef5b495c7c3269fb835f/PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba", size = 181309 }, + { url = "https://files.pythonhosted.org/packages/45/9f/3b1c20a0b7a3200524eb0076cc027a970d320bd3a6592873c85c92a08731/PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1", size = 171679 }, + { url = "https://files.pythonhosted.org/packages/7c/9a/337322f27005c33bcb656c655fa78325b730324c78620e8328ae28b64d0c/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133", size = 733428 }, + { url = "https://files.pythonhosted.org/packages/a3/69/864fbe19e6c18ea3cc196cbe5d392175b4cf3d5d0ac1403ec3f2d237ebb5/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484", size = 763361 }, + { url = "https://files.pythonhosted.org/packages/04/24/b7721e4845c2f162d26f50521b825fb061bc0a5afcf9a386840f23ea19fa/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5", size = 759523 }, + { url = "https://files.pythonhosted.org/packages/2b/b2/e3234f59ba06559c6ff63c4e10baea10e5e7df868092bf9ab40e5b9c56b6/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc", size = 726660 }, + { url = "https://files.pythonhosted.org/packages/fe/0f/25911a9f080464c59fab9027482f822b86bf0608957a5fcc6eaac85aa515/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652", size = 751597 }, + { url = "https://files.pythonhosted.org/packages/14/0d/e2c3b43bbce3cf6bd97c840b46088a3031085179e596d4929729d8d68270/PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183", size = 140527 }, + { url = "https://files.pythonhosted.org/packages/fa/de/02b54f42487e3d3c6efb3f89428677074ca7bf43aae402517bc7cca949f3/PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", size = 156446 }, + { url = "https://files.pythonhosted.org/packages/74/d9/323a59d506f12f498c2097488d80d16f4cf965cee1791eab58b56b19f47a/PyYAML-6.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:24471b829b3bf607e04e88d79542a9d48bb037c2267d7927a874e6c205ca7e9a", size = 183218 }, + { url = "https://files.pythonhosted.org/packages/74/cc/20c34d00f04d785f2028737e2e2a8254e1425102e730fee1d6396f832577/PyYAML-6.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7fded462629cfa4b685c5416b949ebad6cec74af5e2d42905d41e257e0869f5", size = 728067 }, + { url = "https://files.pythonhosted.org/packages/20/52/551c69ca1501d21c0de51ddafa8c23a0191ef296ff098e98358f69080577/PyYAML-6.0.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d84a1718ee396f54f3a086ea0a66d8e552b2ab2017ef8b420e92edbc841c352d", size = 757812 }, + { url = "https://files.pythonhosted.org/packages/fd/7f/2c3697bba5d4aa5cc2afe81826d73dfae5f049458e44732c7a0938baa673/PyYAML-6.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9056c1ecd25795207ad294bcf39f2db3d845767be0ea6e6a34d856f006006083", size = 746531 }, + { url = "https://files.pythonhosted.org/packages/8c/ab/6226d3df99900e580091bb44258fde77a8433511a86883bd4681ea19a858/PyYAML-6.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:82d09873e40955485746739bcb8b4586983670466c23382c19cffecbf1fd8706", size = 800820 }, + { url = "https://files.pythonhosted.org/packages/a0/99/a9eb0f3e710c06c5d922026f6736e920d431812ace24aae38228d0d64b04/PyYAML-6.0.2-cp38-cp38-win32.whl", hash = "sha256:43fa96a3ca0d6b1812e01ced1044a003533c47f6ee8aca31724f78e93ccc089a", size = 145514 }, + { url = "https://files.pythonhosted.org/packages/75/8a/ee831ad5fafa4431099aa4e078d4c8efd43cd5e48fbc774641d233b683a9/PyYAML-6.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff", size = 162702 }, + { url = "https://files.pythonhosted.org/packages/65/d8/b7a1db13636d7fb7d4ff431593c510c8b8fca920ade06ca8ef20015493c5/PyYAML-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d", size = 184777 }, + { url = "https://files.pythonhosted.org/packages/0a/02/6ec546cd45143fdf9840b2c6be8d875116a64076218b61d68e12548e5839/PyYAML-6.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f", size = 172318 }, + { url = "https://files.pythonhosted.org/packages/0e/9a/8cc68be846c972bda34f6c2a93abb644fb2476f4dcc924d52175786932c9/PyYAML-6.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290", size = 720891 }, + { url = "https://files.pythonhosted.org/packages/e9/6c/6e1b7f40181bc4805e2e07f4abc10a88ce4648e7e95ff1abe4ae4014a9b2/PyYAML-6.0.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12", size = 722614 }, + { url = "https://files.pythonhosted.org/packages/3d/32/e7bd8535d22ea2874cef6a81021ba019474ace0d13a4819c2a4bce79bd6a/PyYAML-6.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19", size = 737360 }, + { url = "https://files.pythonhosted.org/packages/d7/12/7322c1e30b9be969670b672573d45479edef72c9a0deac3bb2868f5d7469/PyYAML-6.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e", size = 699006 }, + { url = "https://files.pythonhosted.org/packages/82/72/04fcad41ca56491995076630c3ec1e834be241664c0c09a64c9a2589b507/PyYAML-6.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725", size = 723577 }, + { url = "https://files.pythonhosted.org/packages/ed/5e/46168b1f2757f1fcd442bc3029cd8767d88a98c9c05770d8b420948743bb/PyYAML-6.0.2-cp39-cp39-win32.whl", hash = "sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631", size = 144593 }, + { url = "https://files.pythonhosted.org/packages/19/87/5124b1c1f2412bb95c59ec481eaf936cd32f0fe2a7b16b97b81c4c017a6a/PyYAML-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8", size = 162312 }, +] + +[[package]] +name = "rich" +version = "13.9.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markdown-it-py" }, + { name = "pygments" }, + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ab/3a/0316b28d0761c6734d6bc14e770d85506c986c85ffb239e688eeaab2c2bc/rich-13.9.4.tar.gz", hash = "sha256:439594978a49a09530cff7ebc4b5c7103ef57baf48d5ea3184f21d9a2befa098", size = 223149 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/19/71/39c7c0d87f8d4e6c020a393182060eaefeeae6c01dab6a84ec346f2567df/rich-13.9.4-py3-none-any.whl", hash = "sha256:6049d5e6ec054bf2779ab3358186963bac2ea89175919d699e378b99738c2a90", size = 242424 }, +] + +[[package]] +name = "seqerakit" +version = "0.4.9" +source = { editable = "." } +dependencies = [ + { name = "pyyaml" }, + { name = "typer" }, +] + +[package.dev-dependencies] +dev = [ + { name = "pre-commit" }, + { name = "pytest" }, + { name = "pytest-mock" }, +] + +[package.metadata] +requires-dist = [ + { name = "pyyaml", specifier = ">=6.0.0" }, + { name = "typer", specifier = ">=0.15.1" }, +] + +[package.metadata.requires-dev] +dev = [ + { name = "pre-commit", specifier = ">=3.5.0" }, + { name = "pytest", specifier = ">=8.3.4" }, + { name = "pytest-mock", specifier = ">=3.14.0" }, +] + +[[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 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686", size = 9755 }, +] + +[[package]] +name = "tomli" +version = "2.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/18/87/302344fed471e44a87289cf4967697d07e532f2421fdaf868a303cbae4ff/tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff", size = 17175 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/43/ca/75707e6efa2b37c77dadb324ae7d9571cb424e61ea73fad7c56c2d14527f/tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249", size = 131077 }, + { url = "https://files.pythonhosted.org/packages/c7/16/51ae563a8615d472fdbffc43a3f3d46588c264ac4f024f63f01283becfbb/tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6", size = 123429 }, + { url = "https://files.pythonhosted.org/packages/f1/dd/4f6cd1e7b160041db83c694abc78e100473c15d54620083dbd5aae7b990e/tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a", size = 226067 }, + { url = "https://files.pythonhosted.org/packages/a9/6b/c54ede5dc70d648cc6361eaf429304b02f2871a345bbdd51e993d6cdf550/tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee", size = 236030 }, + { url = "https://files.pythonhosted.org/packages/1f/47/999514fa49cfaf7a92c805a86c3c43f4215621855d151b61c602abb38091/tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e", size = 240898 }, + { url = "https://files.pythonhosted.org/packages/73/41/0a01279a7ae09ee1573b423318e7934674ce06eb33f50936655071d81a24/tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4", size = 229894 }, + { url = "https://files.pythonhosted.org/packages/55/18/5d8bc5b0a0362311ce4d18830a5d28943667599a60d20118074ea1b01bb7/tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106", size = 245319 }, + { url = "https://files.pythonhosted.org/packages/92/a3/7ade0576d17f3cdf5ff44d61390d4b3febb8a9fc2b480c75c47ea048c646/tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8", size = 238273 }, + { url = "https://files.pythonhosted.org/packages/72/6f/fa64ef058ac1446a1e51110c375339b3ec6be245af9d14c87c4a6412dd32/tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff", size = 98310 }, + { url = "https://files.pythonhosted.org/packages/6a/1c/4a2dcde4a51b81be3530565e92eda625d94dafb46dbeb15069df4caffc34/tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b", size = 108309 }, + { url = "https://files.pythonhosted.org/packages/52/e1/f8af4c2fcde17500422858155aeb0d7e93477a0d59a98e56cbfe75070fd0/tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea", size = 132762 }, + { url = "https://files.pythonhosted.org/packages/03/b8/152c68bb84fc00396b83e7bbddd5ec0bd3dd409db4195e2a9b3e398ad2e3/tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8", size = 123453 }, + { url = "https://files.pythonhosted.org/packages/c8/d6/fc9267af9166f79ac528ff7e8c55c8181ded34eb4b0e93daa767b8841573/tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192", size = 233486 }, + { url = "https://files.pythonhosted.org/packages/5c/51/51c3f2884d7bab89af25f678447ea7d297b53b5a3b5730a7cb2ef6069f07/tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222", size = 242349 }, + { url = "https://files.pythonhosted.org/packages/ab/df/bfa89627d13a5cc22402e441e8a931ef2108403db390ff3345c05253935e/tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77", size = 252159 }, + { url = "https://files.pythonhosted.org/packages/9e/6e/fa2b916dced65763a5168c6ccb91066f7639bdc88b48adda990db10c8c0b/tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6", size = 237243 }, + { url = "https://files.pythonhosted.org/packages/b4/04/885d3b1f650e1153cbb93a6a9782c58a972b94ea4483ae4ac5cedd5e4a09/tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd", size = 259645 }, + { url = "https://files.pythonhosted.org/packages/9c/de/6b432d66e986e501586da298e28ebeefd3edc2c780f3ad73d22566034239/tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e", size = 244584 }, + { url = "https://files.pythonhosted.org/packages/1c/9a/47c0449b98e6e7d1be6cbac02f93dd79003234ddc4aaab6ba07a9a7482e2/tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98", size = 98875 }, + { url = "https://files.pythonhosted.org/packages/ef/60/9b9638f081c6f1261e2688bd487625cd1e660d0a85bd469e91d8db969734/tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4", size = 109418 }, + { url = "https://files.pythonhosted.org/packages/04/90/2ee5f2e0362cb8a0b6499dc44f4d7d48f8fff06d28ba46e6f1eaa61a1388/tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7", size = 132708 }, + { url = "https://files.pythonhosted.org/packages/c0/ec/46b4108816de6b385141f082ba99e315501ccd0a2ea23db4a100dd3990ea/tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c", size = 123582 }, + { url = "https://files.pythonhosted.org/packages/a0/bd/b470466d0137b37b68d24556c38a0cc819e8febe392d5b199dcd7f578365/tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13", size = 232543 }, + { url = "https://files.pythonhosted.org/packages/d9/e5/82e80ff3b751373f7cead2815bcbe2d51c895b3c990686741a8e56ec42ab/tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281", size = 241691 }, + { url = "https://files.pythonhosted.org/packages/05/7e/2a110bc2713557d6a1bfb06af23dd01e7dde52b6ee7dadc589868f9abfac/tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272", size = 251170 }, + { url = "https://files.pythonhosted.org/packages/64/7b/22d713946efe00e0adbcdfd6d1aa119ae03fd0b60ebed51ebb3fa9f5a2e5/tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140", size = 236530 }, + { url = "https://files.pythonhosted.org/packages/38/31/3a76f67da4b0cf37b742ca76beaf819dca0ebef26d78fc794a576e08accf/tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2", size = 258666 }, + { url = "https://files.pythonhosted.org/packages/07/10/5af1293da642aded87e8a988753945d0cf7e00a9452d3911dd3bb354c9e2/tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744", size = 243954 }, + { url = "https://files.pythonhosted.org/packages/5b/b9/1ed31d167be802da0fc95020d04cd27b7d7065cc6fbefdd2f9186f60d7bd/tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec", size = 98724 }, + { url = "https://files.pythonhosted.org/packages/c7/32/b0963458706accd9afcfeb867c0f9175a741bf7b19cd424230714d722198/tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69", size = 109383 }, + { url = "https://files.pythonhosted.org/packages/6e/c2/61d3e0f47e2b74ef40a68b9e6ad5984f6241a942f7cd3bbfbdbd03861ea9/tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc", size = 14257 }, +] + +[[package]] +name = "typer" +version = "0.15.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "rich" }, + { name = "shellingham" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cb/ce/dca7b219718afd37a0068f4f2530a727c2b74a8b6e8e0c0080a4c0de4fcd/typer-0.15.1.tar.gz", hash = "sha256:a0588c0a7fa68a1978a069818657778f86abe6ff5ea6abf472f940a08bfe4f0a", size = 99789 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d0/cc/0a838ba5ca64dc832aa43f727bd586309846b0ffb2ce52422543e6075e8a/typer-0.15.1-py3-none-any.whl", hash = "sha256:7994fb7b8155b64d3402518560648446072864beefd44aa2dc36972a5972e847", size = 44908 }, +] + +[[package]] +name = "typing-extensions" +version = "4.12.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/df/db/f35a00659bc03fec321ba8bce9420de607a1d37f8342eee1863174c69557/typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8", size = 85321 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/26/9f/ad63fc0248c5379346306f8668cda6e2e2e9c95e01216d2b8ffd9ff037d0/typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d", size = 37438 }, +] + +[[package]] +name = "virtualenv" +version = "20.28.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "distlib" }, + { name = "filelock" }, + { name = "platformdirs" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/bf/75/53316a5a8050069228a2f6d11f32046cfa94fbb6cc3f08703f59b873de2e/virtualenv-20.28.0.tar.gz", hash = "sha256:2c9c3262bb8e7b87ea801d715fae4495e6032450c71d2309be9550e7364049aa", size = 7650368 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/10/f9/0919cf6f1432a8c4baa62511f8f8da8225432d22e83e3476f5be1a1edc6e/virtualenv-20.28.0-py3-none-any.whl", hash = "sha256:23eae1b4516ecd610481eda647f3a7c09aea295055337331bb4e6892ecce47b0", size = 4276702 }, +]