├── dtuhpc ├── __init__.py ├── cli │ ├── __init__.py │ ├── commands │ │ ├── __init__.py │ │ ├── server │ │ │ ├── __init__.py │ │ │ ├── bqueues.py │ │ │ ├── bsub.py │ │ │ ├── showstart.py │ │ │ ├── bstat.py │ │ │ ├── nodestat.py │ │ │ └── bkill.py │ │ ├── ssh.py │ │ ├── exec.py │ │ ├── server_command.py │ │ ├── auth.py │ │ ├── init.py │ │ └── deploy.py │ ├── cli.py │ └── cli_context.py ├── jobwriter │ ├── __init__.py │ ├── commands │ │ ├── __init__.py │ │ ├── command.py │ │ └── load_module_command.py │ ├── options │ │ ├── name_option.py │ │ ├── queue_option.py │ │ ├── n_cpu_cores_option.py │ │ ├── send_notification_at_end_option.py │ │ ├── send_notification_at_start_option.py │ │ ├── email_for_notifications_option.py │ │ ├── single_host_option.py │ │ ├── core_block_size_option.py │ │ ├── core_ptile_size_option.py │ │ ├── memory_per_core_option.py │ │ ├── memory_per_core_kill_limit_option.py │ │ ├── wall_time_option.py │ │ ├── error_output_file_path_option.py │ │ ├── standard_output_file_path_option.py │ │ ├── option.py │ │ ├── __init__.py │ │ └── use_gpu_option.py │ ├── job_writer.py │ └── job_reader.py ├── commands │ ├── bsub.py │ ├── bqueues.py │ ├── __init__.py │ ├── showstart.py │ ├── command.py │ ├── bstat.py │ ├── nodestat.py │ └── bkill.py ├── default_jobs │ ├── init-pip.toml │ └── init-poetry.toml ├── console.py └── connector.py ├── tests ├── .gitkeep └── test_test.py ├── tox.ini ├── Makefile ├── .github ├── dependabot.yml └── workflows │ ├── flake8.yml │ ├── isort.yml │ ├── tests.yml │ ├── check-version-bump.yml │ └── release.yml ├── .pre-commit-config.yaml ├── LICENSE ├── .gitignore ├── pyproject.toml ├── CHANGELOG.md ├── README.md └── poetry.lock /dtuhpc/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /dtuhpc/cli/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /dtuhpc/jobwriter/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [flake8] 2 | max-line-length = 88 3 | max-complexity = 10 -------------------------------------------------------------------------------- /dtuhpc/commands/bsub.py: -------------------------------------------------------------------------------- 1 | from .command import Command 2 | 3 | 4 | class BSub(Command): 5 | base_command = "bsub" 6 | -------------------------------------------------------------------------------- /tests/test_test.py: -------------------------------------------------------------------------------- 1 | class TestTestClass: 2 | def test_one(self): 3 | x = "this" 4 | assert "h" in x 5 | -------------------------------------------------------------------------------- /dtuhpc/commands/bqueues.py: -------------------------------------------------------------------------------- 1 | from .command import Command 2 | 3 | 4 | class BQueues(Command): 5 | base_command = "bqueues" 6 | -------------------------------------------------------------------------------- /dtuhpc/jobwriter/commands/__init__.py: -------------------------------------------------------------------------------- 1 | from .command import Command 2 | from .load_module_command import LoadModuleCommand 3 | 4 | __all__ = ["Command", "LoadModuleCommand"] 5 | -------------------------------------------------------------------------------- /dtuhpc/jobwriter/commands/command.py: -------------------------------------------------------------------------------- 1 | class Command: 2 | command: str 3 | 4 | def __init__(self, command: str): 5 | self.command = command 6 | 7 | def get_command(self): 8 | return self.command 9 | -------------------------------------------------------------------------------- /dtuhpc/cli/commands/__init__.py: -------------------------------------------------------------------------------- 1 | from .deploy import deploy 2 | from .exec import exec 3 | from .init import init 4 | from .server_command import server_command 5 | from .ssh import ssh 6 | 7 | __all__ = ["deploy", "exec", "init", "ssh", "server_command"] 8 | -------------------------------------------------------------------------------- /dtuhpc/jobwriter/options/name_option.py: -------------------------------------------------------------------------------- 1 | from .option import Option 2 | 3 | 4 | class NameOption(Option): 5 | name: str 6 | 7 | def __init__(self, name: str): 8 | self.name = name 9 | super().__init__(option="J", value=name) 10 | -------------------------------------------------------------------------------- /dtuhpc/jobwriter/options/queue_option.py: -------------------------------------------------------------------------------- 1 | from .option import Option 2 | 3 | 4 | class QueueOption(Option): 5 | queue: str 6 | 7 | def __init__(self, queue: str): 8 | self.queue = queue 9 | super().__init__(option="q", value=queue) 10 | -------------------------------------------------------------------------------- /dtuhpc/jobwriter/options/n_cpu_cores_option.py: -------------------------------------------------------------------------------- 1 | from .option import Option 2 | 3 | 4 | class NCPUCoresOption(Option): 5 | cores: int 6 | 7 | def __init__(self, cores: int): 8 | self.cores = cores 9 | super().__init__(option="n", value=cores) 10 | -------------------------------------------------------------------------------- /dtuhpc/jobwriter/options/send_notification_at_end_option.py: -------------------------------------------------------------------------------- 1 | from .option import Option 2 | 3 | 4 | class SendNotificationAtEndOption(Option): 5 | send: bool 6 | 7 | def __init__(self, send: bool = True): 8 | self.send = send 9 | super().__init__(option="N") 10 | -------------------------------------------------------------------------------- /dtuhpc/jobwriter/options/send_notification_at_start_option.py: -------------------------------------------------------------------------------- 1 | from .option import Option 2 | 3 | 4 | class SendNotificationAtStartOption(Option): 5 | send: bool 6 | 7 | def __init__(self, send: bool = True): 8 | self.send = send 9 | super().__init__(option="B") 10 | -------------------------------------------------------------------------------- /dtuhpc/jobwriter/commands/load_module_command.py: -------------------------------------------------------------------------------- 1 | from .command import Command 2 | 3 | 4 | class LoadModuleCommand(Command): 5 | module: str 6 | 7 | def __init__(self, module: str): 8 | self.module = module 9 | 10 | super().__init__(command=f"module load {module}") 11 | -------------------------------------------------------------------------------- /dtuhpc/jobwriter/options/email_for_notifications_option.py: -------------------------------------------------------------------------------- 1 | from .option import Option 2 | 3 | 4 | class EmailForNotificationsOption(Option): 5 | email: str 6 | 7 | def __init__(self, email: str): 8 | self.email = email 9 | super().__init__(option="u", value=email) 10 | -------------------------------------------------------------------------------- /dtuhpc/cli/commands/server/__init__.py: -------------------------------------------------------------------------------- 1 | from .bkill import bkill 2 | from .bqueues import bqueues 3 | from .bstat import bstat 4 | from .bsub import bsub 5 | from .nodestat import nodestat 6 | from .showstart import showstart 7 | 8 | __all__ = ["bkill", "bqueues", "bstat", "bsub", "nodestat", "showstart"] 9 | -------------------------------------------------------------------------------- /dtuhpc/cli/commands/ssh.py: -------------------------------------------------------------------------------- 1 | import click 2 | 3 | from dtuhpc.cli.cli_context import CLIContext 4 | 5 | 6 | @click.command() 7 | @click.pass_obj 8 | def ssh(context: CLIContext): 9 | """SSH into DTU's server.""" 10 | conn = context.connection 11 | conn.open_shell() 12 | conn.close() 13 | -------------------------------------------------------------------------------- /dtuhpc/jobwriter/options/single_host_option.py: -------------------------------------------------------------------------------- 1 | from .option import Option 2 | 3 | 4 | class SingleHostOption(Option): 5 | def __init__(self, single_host: bool = True): 6 | if single_host: 7 | super().__init__(option="R", value="span[hosts=1]") 8 | else: 9 | super().__init__() 10 | -------------------------------------------------------------------------------- /dtuhpc/commands/__init__.py: -------------------------------------------------------------------------------- 1 | from .bkill import BKill 2 | from .bqueues import BQueues 3 | from .bstat import BStat 4 | from .bsub import BSub 5 | from .command import Command 6 | from .nodestat import Nodestat 7 | from .showstart import Showstart 8 | 9 | __all__ = ["BKill", "BQueues", "BStat", "BSub", "Showstart", "Nodestat", "Command"] 10 | -------------------------------------------------------------------------------- /dtuhpc/jobwriter/options/core_block_size_option.py: -------------------------------------------------------------------------------- 1 | from .option import Option 2 | 3 | 4 | class CoreBlockSizeOption(Option): 5 | block_size: int 6 | 7 | def __init__(self, block_size: int): 8 | assert block_size > 0 9 | self.block_size = block_size 10 | 11 | super().__init__(option="R", value=f"span[block={block_size}]") 12 | -------------------------------------------------------------------------------- /dtuhpc/jobwriter/options/core_ptile_size_option.py: -------------------------------------------------------------------------------- 1 | from .option import Option 2 | 3 | 4 | class CorePTileSizeOption(Option): 5 | ptile_size: int 6 | 7 | def __init__(self, ptile_size: int): 8 | assert ptile_size > 0 9 | self.ptile_size = ptile_size 10 | 11 | super().__init__(option="R", value=f"span[ptile={ptile_size}]") 12 | -------------------------------------------------------------------------------- /dtuhpc/cli/commands/server/bqueues.py: -------------------------------------------------------------------------------- 1 | import click 2 | 3 | from dtuhpc.cli.cli_context import CLIContext 4 | from dtuhpc.commands import BQueues 5 | 6 | 7 | @click.command() 8 | @click.pass_obj 9 | def bqueues(ctx: CLIContext): 10 | """Show current running jobs.""" 11 | conn = ctx.connection 12 | 13 | cmd = BQueues(conn) 14 | cmd.run() 15 | conn.close() 16 | -------------------------------------------------------------------------------- /dtuhpc/cli/commands/exec.py: -------------------------------------------------------------------------------- 1 | import click 2 | 3 | from dtuhpc.cli.cli_context import CLIContext 4 | 5 | 6 | @click.command() 7 | @click.argument("command", nargs=1, type=str, required=True) 8 | @click.pass_obj 9 | def exec(ctx: CLIContext, command: str): 10 | """Executes COMMAND on the DTU HPC Server.""" 11 | conn = ctx.connection 12 | conn.run(command) 13 | 14 | conn.close() 15 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: requirements format lint test bump-major bump-minor bump-patch docs 2 | 3 | requirements: 4 | pip install -e . 5 | pip install -r requirements-dev.txt 6 | pip install -r requirements-test.txt 7 | pip install -r requirements.txt 8 | 9 | format: 10 | black dtuhpc/ tests/ 11 | isort dtuhpc/ tests/ 12 | 13 | lint: 14 | flake8 dtuhpc/ tests/ 15 | 16 | test: 17 | pytest -v tests/ 18 | -------------------------------------------------------------------------------- /dtuhpc/cli/commands/server/bsub.py: -------------------------------------------------------------------------------- 1 | import click 2 | 3 | from dtuhpc.cli.cli_context import CLIContext 4 | from dtuhpc.commands import BSub 5 | 6 | 7 | @click.command() 8 | @click.argument("script_path", nargs=1, type=str) 9 | @click.pass_obj 10 | def bsub(ctx: CLIContext, script_path: str): 11 | """Submit a job to queue.""" 12 | conn = ctx.connection 13 | 14 | cmd = BSub(conn) 15 | cmd.run(f"< {script_path}") 16 | conn.close() 17 | -------------------------------------------------------------------------------- /dtuhpc/jobwriter/options/memory_per_core_option.py: -------------------------------------------------------------------------------- 1 | from .option import Option 2 | 3 | 4 | class MemoryPerCoreOption(Option): 5 | memory_in_gb_per_core: int 6 | 7 | def __init__(self, memory_per_core_in_gb: int): 8 | self.memory_in_gb_per_core = memory_per_core_in_gb 9 | super().__init__(option="R", value=self.format_ram_option()) 10 | 11 | def format_ram_option(self): 12 | return f"rusage[mem={self.memory_in_gb_per_core}G]" 13 | -------------------------------------------------------------------------------- /dtuhpc/jobwriter/options/memory_per_core_kill_limit_option.py: -------------------------------------------------------------------------------- 1 | from .option import Option 2 | 3 | 4 | class MemoryPerCoreKillLimitOption(Option): 5 | memory_in_gb_per_core: int 6 | 7 | def __init__(self, memory_per_core_in_gb: int): 8 | self.memory_in_gb_per_core = memory_per_core_in_gb 9 | super().__init__(option="M", value=self.format_ram_option()) 10 | 11 | def format_ram_option(self): 12 | return f"rusage[mem={self.memory_in_gb_per_core}GB]" 13 | -------------------------------------------------------------------------------- /dtuhpc/default_jobs/init-pip.toml: -------------------------------------------------------------------------------- 1 | queue = "hpc" 2 | name = "init_${{ project_name }}" 3 | walltime = { hours = 0, minutes = 15 } 4 | single_host = true 5 | cpu = 2 6 | memory = 4 7 | standard_output = "init_${{ project_name }}.out" 8 | error_output = "init_${{ project_name }}.err" 9 | 10 | commands = [ 11 | "git clone ${{ git_url }} ${{ project_path }}", 12 | "module load python3/3.10.7", 13 | "cd ${{ project_path }}", 14 | "python3 -m venv ${{ project_path }}/venv", 15 | "source ${{ project_path }}/venv/bin/activate", 16 | "pip3 install -r requirements.txt", 17 | ] 18 | -------------------------------------------------------------------------------- /dtuhpc/commands/showstart.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | from .command import Command 4 | 5 | 6 | class Showstart(Command): 7 | base_command = "showstart" 8 | 9 | def run( 10 | self, 11 | *job_ids: list[str], 12 | user: Optional[str] = None, 13 | queue: Optional[str] = None, 14 | ): 15 | args = [] 16 | 17 | if user is not None: 18 | args.append(f"-u {user}") 19 | if queue is not None: 20 | args.append(f"-q {queue}") 21 | 22 | args += job_ids 23 | 24 | return super().run(*args) 25 | -------------------------------------------------------------------------------- /dtuhpc/cli/commands/server/showstart.py: -------------------------------------------------------------------------------- 1 | import click 2 | 3 | from dtuhpc.cli.cli_context import CLIContext 4 | from dtuhpc.commands import Showstart 5 | 6 | 7 | @click.command() 8 | @click.option("--user", "-u", default=None, type=str) 9 | @click.option("--queue", "-q", default=None, type=str) 10 | @click.argument("job_ids", nargs=-1) 11 | @click.pass_obj 12 | def showstart(config: CLIContext, user, queue, job_ids): 13 | """Show start times for jobs.""" 14 | conn = config.connection 15 | 16 | cmd = Showstart(conn) 17 | cmd.run(*job_ids, user=user, queue=queue) 18 | conn.close() 19 | -------------------------------------------------------------------------------- /dtuhpc/default_jobs/init-poetry.toml: -------------------------------------------------------------------------------- 1 | queue = "hpc" 2 | name = "init_${{ project_name }}" 3 | walltime = { hours = 0, minutes = 15 } 4 | single_host = true 5 | cpu = 2 6 | memory = 4 7 | standard_output = "init_${{ project_name }}.out" 8 | error_output = "init_${{ project_name }}.err" 9 | 10 | commands = [ 11 | "git clone ${{ git_url }} ${{ project_path }}", 12 | "module load python3/3.10.7", 13 | "cd ${{ project_path }}", 14 | "python3 -m venv ${{ project_path }}/venv", 15 | "source ${{ project_path }}/venv/bin/activate", 16 | "pip3 install 'poetry==1.3.2'", 17 | "poetry install", 18 | ] 19 | -------------------------------------------------------------------------------- /dtuhpc/commands/command.py: -------------------------------------------------------------------------------- 1 | from typing import Union 2 | 3 | from fabric.runners import Result 4 | 5 | from dtuhpc.connector import HPCConnection 6 | 7 | 8 | class Command: 9 | """Base class for all commands.""" 10 | 11 | base_command: str 12 | connection: HPCConnection 13 | 14 | def __init__(self, connection: HPCConnection): 15 | self.connection = connection 16 | 17 | def run(self, *args: Union[str, int, float]) -> Result: 18 | parsed_args = " ".join([str(arg) for arg in args]) 19 | 20 | command = self.base_command + " " + parsed_args 21 | cmd = self.connection.run(command) 22 | return cmd 23 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "pip" # See documentation for possible values 9 | directory: "/" # Location of package manifests 10 | schedule: 11 | interval: "weekly" 12 | - package-ecosystem: "github-actions" 13 | directory: "/" 14 | schedule: 15 | interval: "weekly" 16 | -------------------------------------------------------------------------------- /.github/workflows/flake8.yml: -------------------------------------------------------------------------------- 1 | name: "Run flake8" 2 | on: 3 | push: 4 | branches: [ master, main ] 5 | pull_request: 6 | branches: [ master, main ] 7 | jobs: 8 | flake8: 9 | runs-on: "ubuntu-latest" 10 | steps: 11 | - name: Checkout 12 | uses: actions/checkout@v3 13 | - name: Install poetry 14 | run: pipx install poetry 15 | - name: Set up Python 16 | uses: actions/setup-python@v4 17 | with: 18 | python-version: '3.10' 19 | cache: 'poetry' 20 | - name: 21 | run: | 22 | poetry install --only=dev 23 | - name: Lint with flake8 24 | run: | 25 | poetry run flake8 dtuhpc/ tests/ 26 | -------------------------------------------------------------------------------- /.github/workflows/isort.yml: -------------------------------------------------------------------------------- 1 | name: "Run isort" 2 | on: 3 | push: 4 | branches: [ master, main ] 5 | pull_request: 6 | branches: [ master, main ] 7 | jobs: 8 | isort: 9 | runs-on: "ubuntu-latest" 10 | steps: 11 | - name: Checkout 12 | uses: actions/checkout@v3 13 | - name: Install poetry 14 | run: pipx install poetry 15 | - name: Set up Python 16 | uses: actions/setup-python@v4 17 | with: 18 | python-version: '3.10' 19 | cache: 'poetry' 20 | - name: Run poetry install 21 | run: | 22 | poetry install --only=dev 23 | - name: Run isort 24 | run: | 25 | poetry run isort dtuhpc/ tests/ 26 | -------------------------------------------------------------------------------- /dtuhpc/jobwriter/options/wall_time_option.py: -------------------------------------------------------------------------------- 1 | from .option import Option 2 | 3 | 4 | class WallTimeOption(Option): 5 | hours: int 6 | minutes: int 7 | 8 | def __init__(self, hours: int = 0, minutes: int = 0): 9 | assert hours >= 0 and minutes >= 0 10 | assert hours > 0 or minutes > 0 11 | 12 | self.hours = hours 13 | self.minutes = minutes 14 | 15 | super().__init__(option="W", value=self.format_time()) 16 | 17 | @staticmethod 18 | def time_prefix(time: int) -> str: 19 | return f"0{time}" if time < 10 else str(time) 20 | 21 | def format_time(self): 22 | return f"{self.time_prefix(self.hours)}:{self.time_prefix(self.minutes)}" 23 | -------------------------------------------------------------------------------- /dtuhpc/jobwriter/options/error_output_file_path_option.py: -------------------------------------------------------------------------------- 1 | from .option import Option 2 | 3 | 4 | class ErrorOutputFilePathOption(Option): 5 | name: str 6 | suffix_job_id: bool 7 | overwrite: bool 8 | 9 | def __init__(self, name: str, suffix_job_id: bool = True, overwrite: bool = False): 10 | self.name = name 11 | self.suffix_job_id = suffix_job_id 12 | self.overwrite = overwrite 13 | 14 | super().__init__(option=self.get_option(), value=self.get_job_name()) 15 | 16 | def get_option(self): 17 | return "e" if not self.overwrite else "eo" 18 | 19 | def get_job_name(self): 20 | return self.name if self.suffix_job_id else f"{self.name}_%J" 21 | -------------------------------------------------------------------------------- /dtuhpc/jobwriter/options/standard_output_file_path_option.py: -------------------------------------------------------------------------------- 1 | from .option import Option 2 | 3 | 4 | class StandardOutputFilePathOption(Option): 5 | name: str 6 | suffix_job_id: bool 7 | overwrite: bool 8 | 9 | def __init__(self, name: str, suffix_job_id: bool = True, overwrite: bool = False): 10 | self.name = name 11 | self.suffix_job_id = suffix_job_id 12 | self.overwrite = overwrite 13 | 14 | super().__init__(option=self.get_option(), value=self.get_job_name()) 15 | 16 | def get_option(self): 17 | return "o" if not self.overwrite else "oo" 18 | 19 | def get_job_name(self): 20 | return self.name if self.suffix_job_id else f"{self.name}_%J" 21 | -------------------------------------------------------------------------------- /dtuhpc/cli/commands/server_command.py: -------------------------------------------------------------------------------- 1 | import click 2 | 3 | from dtuhpc.cli.commands.server.bkill import bkill 4 | from dtuhpc.cli.commands.server.bqueues import bqueues 5 | from dtuhpc.cli.commands.server.bstat import bstat 6 | from dtuhpc.cli.commands.server.bsub import bsub 7 | from dtuhpc.cli.commands.server.nodestat import nodestat 8 | from dtuhpc.cli.commands.server.showstart import showstart 9 | 10 | 11 | @click.group(name="c") 12 | def server_command() -> None: 13 | """Execute a predefined command.""" 14 | pass 15 | 16 | 17 | server_command.add_command(bkill) 18 | server_command.add_command(nodestat) 19 | server_command.add_command(bqueues) 20 | server_command.add_command(bstat) 21 | server_command.add_command(bsub) 22 | server_command.add_command(showstart) 23 | -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: "Run tests" 2 | 3 | on: 4 | push: 5 | branches: [ master, main ] 6 | pull_request: 7 | branches: [ master, main ] 8 | jobs: 9 | test: 10 | runs-on: ubuntu-latest 11 | strategy: 12 | matrix: 13 | python-version: ['3.9', '3.10', '3.11'] 14 | steps: 15 | - name: Checkout 16 | uses: actions/checkout@v3 17 | - name: Install poetry 18 | run: pipx install poetry 19 | - name: Set up Python 20 | uses: actions/setup-python@v4 21 | with: 22 | python-version: ${{ matrix.python-version }} 23 | cache: 'poetry' 24 | - name: 25 | run: | 26 | poetry install --with=test 27 | - name: Test with pytest 28 | run: | 29 | poetry run pytest -v tests/ 30 | -------------------------------------------------------------------------------- /dtuhpc/commands/bstat.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | from .command import Command 4 | 5 | 6 | class BStat(Command): 7 | base_command = "bstat" 8 | 9 | def run( 10 | self, 11 | *job_ids: list[str], 12 | cpu_usage: bool = False, 13 | memory_usage: bool = False, 14 | user: Optional[str] = None, 15 | queue: Optional[str] = None, 16 | ): 17 | args = [] 18 | 19 | if cpu_usage: 20 | args.append("-C") 21 | if memory_usage: 22 | args.append("-M") 23 | if user is not None: 24 | args.append(f"-u {user}") 25 | if queue is not None: 26 | args.append(f"-q {queue}") 27 | 28 | args += job_ids 29 | 30 | return super().run(*args) 31 | -------------------------------------------------------------------------------- /.github/workflows/check-version-bump.yml: -------------------------------------------------------------------------------- 1 | name: "Check version has been bumped" 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - release 7 | 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | name: Test if version has been bumped 12 | steps: 13 | - uses: actions/checkout@v3 14 | with: 15 | fetch-depth: 0 # OR "2" -> To retrieve the preceding commit. 16 | - name: Get changed files in the docs folder 17 | id: changed-files-specific 18 | uses: tj-actions/changed-files@v35 19 | with: 20 | files: | 21 | VERSION 22 | 23 | - name: Run step if any file(s) in the docs folder change 24 | if: steps.changed-files-specific.outputs.any_changed == 'false' 25 | run: | 26 | echo "No version bump detected" 27 | exit 1 28 | -------------------------------------------------------------------------------- /dtuhpc/jobwriter/options/option.py: -------------------------------------------------------------------------------- 1 | from typing import Optional, Union 2 | 3 | 4 | class Option: 5 | option: Optional[str] 6 | value: Optional[Union[str, int]] 7 | config_name: str 8 | 9 | def __init__( 10 | self, option: Optional[str] = None, value: Optional[Union[str, int]] = None 11 | ): 12 | self.option = option 13 | self.value = value 14 | 15 | def _get_option_value(self): 16 | return ( 17 | '"' + str(self.value) + '"' 18 | if isinstance(self.value, str) 19 | else str(self.value) 20 | ) 21 | 22 | def get_option_line(self) -> str: 23 | if self.option is None: 24 | return "" 25 | 26 | return ( 27 | f"#BSUB -{self.option} {self._get_option_value()}" 28 | if self.value is not None 29 | else f"#BSUB -{self.option}" 30 | ) 31 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/pre-commit/pre-commit-hooks 3 | rev: v4.4.0 4 | hooks: 5 | - id: trailing-whitespace 6 | - id: end-of-file-fixer 7 | - id: check-yaml 8 | - id: debug-statements 9 | - id: name-tests-test 10 | - id: requirements-txt-fixer 11 | - id: check-added-large-files 12 | - id: mixed-line-ending 13 | - id: check-ast 14 | - id: check-merge-conflict 15 | - id: no-commit-to-branch 16 | - repo: https://github.com/psf/black 17 | rev: 22.12.0 18 | hooks: 19 | - id: black 20 | args: [dtuhpc/, tests/] 21 | - repo: https://github.com/PyCQA/isort 22 | rev: 5.11.4 23 | hooks: 24 | - id: isort 25 | args: [dtuhpc/, tests/] 26 | - repo: https://github.com/pycqa/flake8 27 | rev: 6.0.0 # pick a git hash / tag to point to 28 | hooks: 29 | - id: flake8 30 | args: [dtuhpc/, tests/] 31 | exclude: docs 32 | -------------------------------------------------------------------------------- /dtuhpc/cli/commands/server/bstat.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | import click 4 | 5 | from dtuhpc.cli.cli_context import CLIContext 6 | from dtuhpc.commands import BStat 7 | 8 | 9 | @click.command() 10 | @click.option("--cpu", "-c", default=False, help="Show CPU usage.") 11 | @click.option("--memory", "-m", default=False, help="Show memory usage.") 12 | @click.option("--user", "-u", default=None, type=str) 13 | @click.option("--queue", "-q", default=None, type=str) 14 | @click.argument("job_ids", nargs=-1) 15 | @click.pass_obj 16 | def bstat( 17 | ctx: CLIContext, 18 | cpu: bool, 19 | memory: bool, 20 | user: Optional[str], 21 | queue: Optional[str], 22 | job_ids: list[str], 23 | ): 24 | """Show current running jobs.""" 25 | conn = ctx.connection 26 | 27 | cmd = BStat(conn) 28 | cmd.run( 29 | *job_ids, 30 | cpu_usage=cpu, 31 | memory_usage=memory, 32 | user=user, 33 | queue=queue, 34 | ) 35 | conn.close() 36 | -------------------------------------------------------------------------------- /dtuhpc/cli/cli.py: -------------------------------------------------------------------------------- 1 | import click 2 | 3 | from dtuhpc.cli.cli_context import CLIContext 4 | from dtuhpc.cli.commands import deploy, exec, init, server_command, ssh 5 | from dtuhpc.cli.commands.auth import auth 6 | 7 | 8 | @click.group() 9 | @click.option( 10 | "--hide/--no-hide", 11 | default=False, 12 | show_default=True, 13 | help="hide output from the server.", 14 | ) 15 | @click.option( 16 | "--config", default=None, type=click.Path(exists=True), help="path to config file." 17 | ) 18 | @click.option( 19 | "--cwd", 20 | default=None, 21 | type=str, 22 | help="default working directory for executing commands.", 23 | ) 24 | @click.pass_context 25 | def cli(ctx, hide, config, cwd): 26 | ctx.obj = CLIContext(config_path=config, hide=hide, cwd=cwd) 27 | 28 | 29 | def main(): 30 | cli.add_command(server_command) 31 | cli.add_command(init) 32 | cli.add_command(exec) 33 | cli.add_command(deploy) 34 | cli.add_command(ssh) 35 | cli.add_command(auth) 36 | 37 | cli() 38 | 39 | 40 | if __name__ == "__main__": 41 | main() 42 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Jonas Hoffmann 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /dtuhpc/commands/nodestat.py: -------------------------------------------------------------------------------- 1 | from .command import Command 2 | 3 | 4 | class Nodestat(Command): 5 | base_command = "nodestat" 6 | 7 | def run( # noqa: C901 8 | self, 9 | *queues: list[str], 10 | cpu: bool = False, 11 | features: bool = False, 12 | gpu: bool = False, 13 | gpu_model: bool = False, 14 | jobs: bool = False, 15 | queue_jobs: bool = False, 16 | load_utilization: bool = False, 17 | visual_load_utilization: bool = False, 18 | memory: bool = False, 19 | reserved_slots: bool = False, 20 | ): 21 | args = [] 22 | 23 | if cpu: 24 | args.append("-f") 25 | if features: 26 | args.append("-F") 27 | if gpu: 28 | args.append("-g") 29 | if gpu_model: 30 | args.append("-G") 31 | if jobs: 32 | args.append("-j") 33 | if queue_jobs: 34 | args.append("-J") 35 | if load_utilization: 36 | args.append("-l") 37 | if visual_load_utilization: 38 | args.append("-L") 39 | if memory: 40 | args.append("-m") 41 | if reserved_slots: 42 | args.append("-r") 43 | 44 | args += queues 45 | 46 | return super().run(*args) 47 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | 5 | # C extensions 6 | *.so 7 | 8 | # Distribution / packaging 9 | .Python 10 | env/ 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | *.egg-info/ 23 | .installed.cfg 24 | *.egg 25 | 26 | # PyInstaller 27 | # Usually these files are written by a python script from a template 28 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 29 | *.manifest 30 | *.spec 31 | 32 | # Installer logs 33 | pip-log.txt 34 | pip-delete-this-directory.txt 35 | 36 | # Unit test / coverage reports 37 | htmlcov/ 38 | .tox/ 39 | .coverage 40 | .coverage.* 41 | .cache 42 | nosetests.xml 43 | coverage.xml 44 | *.cover 45 | 46 | # Translations 47 | *.mo 48 | *.pot 49 | 50 | # Django stuff: 51 | *.log 52 | 53 | # Sphinx documentation 54 | docs/_build/ 55 | 56 | # PyBuilder 57 | target/ 58 | 59 | # DotEnv configuration 60 | .env 61 | 62 | # Database 63 | *.db 64 | *.rdb 65 | 66 | # Pycharm 67 | .idea 68 | 69 | # VS Code 70 | .vscode/ 71 | 72 | # Spyder 73 | .spyproject/ 74 | 75 | # Jupyter NB Checkpoints 76 | .ipynb_checkpoints/ 77 | 78 | # exclude data from source control by default 79 | /data/ 80 | /wandb/ 81 | 82 | # Mac OS-specific storage files 83 | .DS_Store 84 | 85 | # vim 86 | *.swp 87 | *.swo 88 | 89 | # Mypy cache 90 | .mypy_cache/ 91 | 92 | .pytest_cache/ 93 | 94 | # HPC config file 95 | .dtuhpc.toml 96 | venv/ 97 | -------------------------------------------------------------------------------- /dtuhpc/jobwriter/options/__init__.py: -------------------------------------------------------------------------------- 1 | from .core_block_size_option import CoreBlockSizeOption 2 | from .core_ptile_size_option import CorePTileSizeOption 3 | from .email_for_notifications_option import EmailForNotificationsOption 4 | from .error_output_file_path_option import ErrorOutputFilePathOption 5 | from .memory_per_core_kill_limit_option import MemoryPerCoreKillLimitOption 6 | from .memory_per_core_option import MemoryPerCoreOption 7 | from .n_cpu_cores_option import NCPUCoresOption 8 | from .name_option import NameOption 9 | from .option import Option 10 | from .queue_option import QueueOption 11 | from .send_notification_at_end_option import SendNotificationAtEndOption 12 | from .send_notification_at_start_option import SendNotificationAtStartOption 13 | from .single_host_option import SingleHostOption 14 | from .standard_output_file_path_option import StandardOutputFilePathOption 15 | from .use_gpu_option import UseGPUOption 16 | from .wall_time_option import WallTimeOption 17 | 18 | __all__ = [ 19 | "CoreBlockSizeOption", 20 | "CorePTileSizeOption", 21 | "EmailForNotificationsOption", 22 | "ErrorOutputFilePathOption", 23 | "MemoryPerCoreKillLimitOption", 24 | "MemoryPerCoreOption", 25 | "NCPUCoresOption", 26 | "NameOption", 27 | "Option", 28 | "QueueOption", 29 | "SendNotificationAtStartOption", 30 | "SendNotificationAtEndOption", 31 | "SingleHostOption", 32 | "StandardOutputFilePathOption", 33 | "SingleHostOption", 34 | "UseGPUOption", 35 | "WallTimeOption", 36 | ] 37 | -------------------------------------------------------------------------------- /dtuhpc/cli/commands/server/nodestat.py: -------------------------------------------------------------------------------- 1 | import click 2 | 3 | from dtuhpc.cli.cli_context import CLIContext 4 | from dtuhpc.commands import Nodestat 5 | 6 | 7 | @click.command() 8 | @click.option("--cpu", "-c", default=False, is_flag=True) 9 | @click.option("--features", "-f", default=False, is_flag=True) 10 | @click.option("--gpu", "-g", default=False, is_flag=True) 11 | @click.option("--gpu_model", "-G", default=False, is_flag=True) 12 | @click.option("--jobs", "-j", default=False, is_flag=True) 13 | @click.option("--queue_jobs", "-J", default=False, is_flag=True) 14 | @click.option("--load_util", "-l", default=False, is_flag=True) 15 | @click.option("--visual_load_util", "-v", default=False, is_flag=True) 16 | @click.option("--memory", "-m", default=False, is_flag=True) 17 | @click.option("--reserved", "-r", default=False, is_flag=True) 18 | @click.argument("queues", nargs=-1) 19 | @click.pass_obj 20 | def nodestat( 21 | ctx: CLIContext, 22 | cpu, 23 | features, 24 | gpu, 25 | gpu_model, 26 | jobs, 27 | queue_jobs, 28 | load_util, 29 | visual_load_util, 30 | memory, 31 | reserved, 32 | queues, 33 | ): 34 | """Print the nodestat from the server.""" 35 | conn = ctx.connection 36 | 37 | cmd = Nodestat(conn) 38 | cmd.run( 39 | *queues, 40 | cpu=cpu, 41 | features=features, 42 | gpu=gpu, 43 | gpu_model=gpu_model, 44 | jobs=jobs, 45 | queue_jobs=queue_jobs, 46 | load_utilization=load_util, 47 | visual_load_utilization=visual_load_util, 48 | memory=memory, 49 | reserved_slots=reserved, 50 | ) 51 | 52 | conn.close() 53 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Semantic Release 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | 8 | jobs: 9 | release: 10 | runs-on: ubuntu-latest 11 | concurrency: release 12 | if: github.event_name == 'push' && github.ref == 'refs/heads/master' && github.actor != 'github-actions' 13 | steps: 14 | - uses: actions/checkout@v2 15 | with: 16 | fetch-depth: 0 17 | token: ${{ secrets.GH_API_TOKEN }} 18 | - name: Install poetry 19 | run: | 20 | pipx install poetry 21 | - name: Set up Python 3.10 22 | uses: actions/setup-python@v4 23 | with: 24 | python-version: '3.10' 25 | cache: 'poetry' 26 | - name: Poetry install 27 | run: poetry install 28 | - name: Use Python Semantic Release to prepare release 29 | env: 30 | GH_TOKEN: ${{ secrets.GH_API_TOKEN }} 31 | run: | 32 | git config user.name github-actions 33 | git config user.email github-actions@github.com 34 | poetry run semantic-release publish 35 | - name: Publish to TestPyPI 36 | uses: pypa/gh-action-pypi-publish@release/v1 37 | with: 38 | user: __token__ 39 | password: ${{ secrets.TEST_PYPI_API_TOKEN }} 40 | repository_url: https://test.pypi.org/legacy/ 41 | skip_existing: true 42 | 43 | - name: Test install from TestPyPI 44 | run: | 45 | pip install \ 46 | --index-url https://test.pypi.org/simple/ \ 47 | --extra-index-url https://pypi.org/simple \ 48 | dtuhpc 49 | 50 | - name: Publish to PyPI 51 | uses: pypa/gh-action-pypi-publish@release/v1 52 | with: 53 | user: __token__ 54 | password: ${{ secrets.PYPI_API_TOKEN }} 55 | skip_existing: true 56 | -------------------------------------------------------------------------------- /dtuhpc/cli/commands/auth.py: -------------------------------------------------------------------------------- 1 | import base64 2 | import json 3 | import os 4 | 5 | import click 6 | from cryptography.fernet import Fernet 7 | from cryptography.hazmat.primitives import hashes 8 | from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC 9 | 10 | from dtuhpc.cli.cli_context import CLIContext 11 | from dtuhpc.console import console 12 | 13 | 14 | @click.command() 15 | def auth(): 16 | """Authenticate with the DTU HPC cluster.""" 17 | auth_path = CLIContext.get_global_auth_path() 18 | 19 | if auth_path.exists(): 20 | click.confirm( 21 | f"Auth file already exists at {auth_path}. Do you want to overwrite it?", 22 | abort=True, 23 | ) 24 | 25 | username = click.prompt("Username") 26 | ssh_password = bytes(click.prompt("Password", hide_input=True), "utf-8") 27 | 28 | console.primary( 29 | "You will now be asked to enter an encryption key, \ 30 | for encrypting your password." 31 | ) 32 | encryption_key = bytes(click.prompt("Encryption key", hide_input=True), "utf-8") 33 | 34 | salt = os.urandom(16) 35 | kdf = PBKDF2HMAC( 36 | algorithm=hashes.SHA256(), 37 | length=32, 38 | salt=salt, 39 | iterations=480000, 40 | ) 41 | key = base64.urlsafe_b64encode(kdf.derive(encryption_key)) 42 | 43 | f = Fernet(key) 44 | 45 | auth_conf = { 46 | "username": username, 47 | "password": f.encrypt(ssh_password).decode("utf-8"), 48 | "salt": base64.urlsafe_b64encode(salt).decode("utf-8"), 49 | } 50 | auth_path.parent.mkdir(parents=True, exist_ok=True) 51 | 52 | with open(auth_path, "w") as auth_file: 53 | json.dump(auth_conf, auth_file) 54 | 55 | console.success(f"Successfully saved auth file to {auth_path}") 56 | -------------------------------------------------------------------------------- /dtuhpc/jobwriter/options/use_gpu_option.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | from .option import Option 4 | 5 | 6 | class UseGPUOption(Option): 7 | num_of_gpus: int 8 | per_task: bool 9 | mode: str 10 | aff: bool 11 | block: bool 12 | gpu_model_name: Optional[str] 13 | memory_size: Optional[int] 14 | 15 | def __init__( 16 | self, 17 | num_of_gpus: int = 1, 18 | per_task: bool = False, 19 | mode: str = "exclusive_process", 20 | aff: Optional[bool] = None, 21 | block: Optional[bool] = None, 22 | gpu_model_name: Optional[str] = None, 23 | memory_size: Optional[int] = None, 24 | ): 25 | self.num_of_gpus = num_of_gpus 26 | self.per_task = per_task 27 | self.mode = mode 28 | self.aff = aff 29 | self.block = block 30 | self.gpu_model_name = gpu_model_name 31 | self.memory_size = memory_size 32 | 33 | super().__init__(option="gpu", value=self.format_value()) 34 | 35 | @staticmethod 36 | def format_bool_as_yes_no(boolvar): 37 | return "yes" if boolvar else "no" 38 | 39 | def format_value(self): 40 | options = [ 41 | f"num={self.num_of_gpus}" 42 | if self.per_task is False 43 | else f"num={self.num_of_gpus}/task", 44 | f"mode={self.mode}", 45 | ] 46 | 47 | if self.aff is not None: 48 | options.append(f"aff={self.format_bool_as_yes_no(self.aff)}") 49 | 50 | if self.block is not None: 51 | options.append(f"block={self.format_bool_as_yes_no(self.block)}") 52 | 53 | if self.gpu_model_name is not None: 54 | options.append(f"gmodel={self.gpu_model_name}") 55 | 56 | if self.memory_size is not None: 57 | options.append(f"gmem={self.memory_size}G") 58 | 59 | return ":".join(options) 60 | -------------------------------------------------------------------------------- /dtuhpc/commands/bkill.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | from .command import Command 4 | 5 | 6 | class BKill(Command): 7 | base_command = "bkill" 8 | 9 | def run( # noqa: C901 10 | self, 11 | *job_ids: list[str], 12 | kill_all: bool = False, 13 | as_done: bool = False, 14 | list_signals: bool = False, 15 | remove_without_waiting: False = True, 16 | application_profile_name: Optional[str] = None, 17 | kill_reason: Optional[str] = None, 18 | group_name: Optional[str] = None, 19 | job_name: Optional[str] = None, 20 | host_name: Optional[str] = None, 21 | queue: Optional[str] = None, 22 | signal_name: Optional[str] = None, 23 | status: Optional[str] = None, 24 | kill_all_satisfying: bool = False, 25 | ): 26 | args = [] 27 | 28 | if kill_all: 29 | args.append("-b") 30 | if as_done: 31 | args.append("-d") 32 | if list_signals: 33 | args.append("-l") 34 | if remove_without_waiting: 35 | args.append("-r") 36 | if application_profile_name is not None: 37 | args.append(f"-app {application_profile_name}") 38 | if kill_reason is not None: 39 | args.append(f"-C {kill_reason}") 40 | if group_name is not None: 41 | args.append(f"-g {group_name}") 42 | if job_name is not None: 43 | args.append(f"-J {job_name}") 44 | if host_name is not None: 45 | args.append(f"-m {host_name}") 46 | if queue is not None: 47 | args.append(f"-q {queue}") 48 | if signal_name is not None: 49 | args.append(f"-s {signal_name}") 50 | if status is not None: 51 | args.append(f"-stat {status}") 52 | if kill_all_satisfying: 53 | args.append("0") 54 | 55 | args += job_ids 56 | 57 | return super().run(*args) 58 | -------------------------------------------------------------------------------- /dtuhpc/cli/commands/server/bkill.py: -------------------------------------------------------------------------------- 1 | import click 2 | 3 | from dtuhpc.cli.cli_context import CLIContext 4 | from dtuhpc.commands import BKill 5 | 6 | 7 | @click.command() 8 | @click.option("--kill-all", "-a", default=False, is_flag=True) 9 | @click.option("--done", "-d", default=False, is_flag=True) 10 | @click.option("--list-signals", "-l", default=False, is_flag=True) 11 | @click.option("--remove-without-waiting", "-r", default=False, is_flag=True) 12 | @click.option("--application-profile", "-app", default=None, type=str) 13 | @click.option("--kill-reason", "-K", default=None, type=str) 14 | @click.option("--group-name", "-g", default=None, type=str) 15 | @click.option("--job-name", "-J", default=None, type=str) 16 | @click.option("--host-name", "-m", default=None, type=str) 17 | @click.option("--queue", "-q", default=None, type=str) 18 | @click.option("--signal-name", "-s", default=None, type=str) 19 | @click.option("--status", "-S", default=None, type=str) 20 | @click.option("--zero", "-z", default=None, type=str) 21 | @click.argument("job_ids", nargs=-1) 22 | @click.pass_obj 23 | def bkill( 24 | ctx: CLIContext, 25 | kill_all, 26 | done, 27 | list_signals, 28 | remove_without_waiting, 29 | application_profile, 30 | kill_reason, 31 | group_name, 32 | job_name, 33 | host_name, 34 | queue, 35 | signal_name, 36 | status, 37 | zero, 38 | job_ids, 39 | ): 40 | """Kill a job.""" 41 | conn = ctx.connection 42 | 43 | cmd = BKill(conn) 44 | cmd.run( 45 | *job_ids, 46 | kill_all=kill_all, 47 | as_done=done, 48 | list_signals=list_signals, 49 | remove_without_waiting=remove_without_waiting, 50 | application_profile_name=application_profile, 51 | kill_reason=kill_reason, 52 | group_name=group_name, 53 | job_name=job_name, 54 | host_name=host_name, 55 | queue=queue, 56 | signal_name=signal_name, 57 | status=status, 58 | kill_all_satisfying=zero, 59 | ) 60 | conn.close() 61 | -------------------------------------------------------------------------------- /dtuhpc/cli/commands/init.py: -------------------------------------------------------------------------------- 1 | import os 2 | from io import StringIO 3 | 4 | import click 5 | 6 | from dtuhpc.cli.cli_context import CLIContext 7 | from dtuhpc.commands import BSub 8 | from dtuhpc.jobwriter.job_reader import JobReader 9 | 10 | 11 | @click.command() 12 | @click.option( 13 | "--poetry", 14 | is_flag=True, 15 | default=False, 16 | help="Use poetry to install dependencies on HPC.", 17 | ) 18 | @click.option( 19 | "--custom-job", 20 | default=None, 21 | type=click.Path(exists=True), 22 | help="Use a custom job to initiate repository on HPC.", 23 | ) 24 | @click.pass_obj 25 | def init(ctx: CLIContext, poetry: bool, custom_job: click.Path): 26 | """Initiates the current project on DTU's HPC server.""" 27 | repo = ctx.git_repo 28 | repo_remote = repo.remote("origin") 29 | repo_url = repo_remote.url 30 | repo.close() 31 | 32 | project_name = ctx.config["project"]["name"] 33 | project_path = ctx.config["project"]["path"] 34 | variables = { 35 | "project_name": project_name, 36 | "project_path": project_path, 37 | "git_url": repo_url, 38 | } 39 | 40 | if poetry and custom_job is not None: 41 | raise ValueError("Cannot use both poetry and custom job.") 42 | elif poetry: 43 | job_path = os.path.join( 44 | os.path.dirname(__file__), "..", "..", "default_jobs/init-poetry.toml" 45 | ) 46 | elif custom_job is not None: 47 | job_path = custom_job 48 | else: 49 | job_path = os.path.join( 50 | os.path.dirname(__file__), "..", "..", "default_jobs/init-pip.toml" 51 | ) 52 | 53 | job_reader = JobReader(job_path, variables) 54 | job_reader.parse() 55 | 56 | conn = ctx.connection 57 | 58 | conn.run("mkdir -p .dtuhpc") 59 | conn.conn.put( 60 | StringIO(job_reader.to_str()), 61 | os.path.join(ctx.cwd, ".dtuhpc/", f"initialize_{project_name}_job.sh"), 62 | ) 63 | 64 | bsub = BSub(conn) 65 | bsub.run(f"< .dtuhpc/initialize_{project_name}_job.sh") 66 | 67 | conn.close() 68 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "dtuhpc" 3 | description = "Python package for interfacing with DTU's HPC cluster" 4 | authors = [ 5 | { name = "Jonas Hoffmann", email = "jonashoffmanns@gmail.com" } 6 | ] 7 | readme = "README.md" 8 | requires-python = ">=3.9" 9 | classifiers = [ 10 | "Environment :: Console", 11 | "Intended Audience :: Developers", 12 | "License :: OSI Approved :: MIT License", 13 | "Operating System :: OS Independent", 14 | "Programming Language :: Python", 15 | "Programming Language :: Python :: 3 :: Only", 16 | "Programming Language :: Python :: 3.9", 17 | "Programming Language :: Python :: 3.10", 18 | "Programming Language :: Python :: 3.11" 19 | ] 20 | 21 | [tool.poetry] 22 | name = "dtuhpc" 23 | version = "2.1.3" 24 | description = "" 25 | authors = ["Jonas Hoffmannn "] 26 | readme = "README.md" 27 | packages = [{include = "dtuhpc"}] 28 | 29 | [tool.poetry.scripts] 30 | dtuhpc = "dtuhpc.cli.cli:main" 31 | 32 | [tool.poetry.dependencies] 33 | python = "^3.9" 34 | rich = "^13.2.0" 35 | gitpython = "^3.1.30" 36 | pygithub = "^1.57" 37 | tomli = "^2.0.1" 38 | cryptography = "^39.0.1" 39 | python-semantic-release = "^7.33.1" 40 | fabric = "^3.2.2" 41 | 42 | [tool.poetry.group.test.dependencies] 43 | pytest = "^6.0.0" 44 | pytest-mock = "*" 45 | 46 | [tool.poetry.group.dev.dependencies] 47 | black = "^22.12.0" 48 | bump2version = "^1.0.1" 49 | flake8 = "^6.0.0" 50 | isort = "^5.11.4" 51 | pre-commit = "^3.0.4" 52 | 53 | [build-system] 54 | requires = ["poetry-core"] 55 | build-backend = "poetry.core.masonry.api" 56 | 57 | [tool.isort] 58 | atomic = true 59 | profile = "black" 60 | line_length = 88 61 | skip_gitignore = true 62 | 63 | [tool.semantic_release] 64 | version_toml = "pyproject.toml:tool.poetry.version" 65 | branch = "master" # branch to make releases of 66 | build_command = "poetry build" # command to build the package 67 | dist_path = "dist/" # path to the distribution files 68 | remove_dist = false # remove the dist folder before building 69 | changelog_file = "CHANGELOG.md" # changelog file 70 | upload_to_release = true # auto-create GitHub release 71 | upload_to_pypi = false # don't auto-upload to PyPI 72 | patch_without_tag = true 73 | -------------------------------------------------------------------------------- /dtuhpc/console.py: -------------------------------------------------------------------------------- 1 | """Console module for outputting to consoleself. 2 | 3 | This module defines a class for outputting to the console, 4 | with the capabilities of the rich package. 5 | """ 6 | 7 | from rich.console import Console as RichConsole 8 | 9 | 10 | class Console(RichConsole): 11 | """Class for providing rich console output with custom methods 12 | Args: 13 | RichConsole: Inherits from rich.console.Console 14 | """ 15 | 16 | def primary(self, format: str) -> None: 17 | """Prints text in blue color. Should be used for primary text output. 18 | 19 | Args: 20 | format (str): Text to be printed 21 | """ 22 | self.print(f"[blue]{format}[/blue]") 23 | 24 | def success(self, format: str): 25 | """Prints text in green. Should be used for success messages. 26 | 27 | Args: 28 | format (str): Text to be printed 29 | """ 30 | self.print(f"[green]{format}[/green]") 31 | 32 | def error(self, format: str): 33 | """Prints text in red color. Should be used for error messages. 34 | 35 | Args: 36 | format (str): Text to be printed 37 | """ 38 | self.print(f"[bold red]{format}[/bold red]") 39 | 40 | def prompt_list(self, question: str, options: list[str]) -> int: 41 | """Prints a list of options, and prompts the user for an answer. 42 | It will continue to prompt the user until a valid answer is inputted. 43 | 44 | Args: 45 | question (str): The prompt message. 46 | options (list[str]): The different options that should be displayed. 47 | 48 | Returns: 49 | int: Returns the index of the chosen option. 50 | """ 51 | for i, option in enumerate(options): 52 | self.print(f"[{i}]: {option}") 53 | 54 | def print_invalid_input(): 55 | return self.error("Invalid input") 56 | 57 | try: 58 | question_idx = int(self.input(question)) 59 | except ValueError: 60 | print_invalid_input() 61 | return self.prompt_list(question, options) 62 | 63 | if question_idx >= len(options) or question_idx < 0: 64 | print_invalid_input() 65 | return self.prompt_list(question, options) 66 | 67 | return question_idx 68 | 69 | 70 | console = Console() 71 | -------------------------------------------------------------------------------- /dtuhpc/connector.py: -------------------------------------------------------------------------------- 1 | """Connection module. 2 | 3 | This module defines the connection to the DTU HPC server. 4 | """ 5 | from typing import Optional 6 | 7 | from fabric import Connection, Result 8 | 9 | 10 | class HPCConnection: 11 | """Class for connecting and running commands on DTU HPC server. 12 | 13 | Attributes: 14 | _conn (Connection): Connection object from the fabric library. 15 | cwd (Optional[str]): Current working directory. 16 | hide (bool): Hide the output of the command. 17 | """ 18 | 19 | conn: Connection 20 | cwd: Optional[str] 21 | hide: bool 22 | 23 | def __init__( 24 | self, 25 | user: str, 26 | host: str, 27 | key_filename: str, 28 | password: Optional[str] = None, 29 | hide: bool = False, 30 | cwd: Optional[str] = None, 31 | **kwargs, 32 | ): 33 | """Initialize the connection with the required parameters. 34 | 35 | Args: 36 | user (str): Username for the DTU HPC server. 37 | host (str): Hostname for the DTU HPC server. 38 | password (str, optional): Password for the DTU HPC server. Defaults to None. 39 | hide (bool, optional): Hide the output of the command. Defaults to False. 40 | cwd (Optional[str], optional): Current working directory. Defaults to None. 41 | **kwargs: Additional arguments parsed directly into Paramikos Client. 42 | """ 43 | connect_kwargs = {"key_filename": key_filename} 44 | 45 | if password is not None: 46 | connect_kwargs["password"] = password 47 | 48 | self.hide = hide 49 | self.cwd = cwd 50 | self.conn = Connection( 51 | user=user, 52 | host=host, 53 | connect_kwargs=connect_kwargs, 54 | **kwargs, 55 | ) 56 | 57 | def run(self, command: str) -> Result: 58 | """Run a command on the DTU HPC server. 59 | 60 | Args: 61 | command (str): Command to run. 62 | 63 | Returns: 64 | Result: Result object from the fabric library. 65 | """ 66 | 67 | if self.cwd is not None: 68 | with self.conn.cd(self.cwd): 69 | return self.conn.run(f"bash -l -c '{command}'", hide=self.hide) 70 | 71 | return self.conn.run(f"bash -l -c '{command}'", hide=self.hide) 72 | 73 | def open_shell(self) -> None: 74 | """Open a shell on the DTU HPC server.""" 75 | self.conn.shell() 76 | 77 | def close(self): 78 | """Close the connection to the DTU HPC server.""" 79 | self.conn.close() 80 | -------------------------------------------------------------------------------- /dtuhpc/jobwriter/job_writer.py: -------------------------------------------------------------------------------- 1 | """This module contains the JobWriter class, which is used to write LSF job scripts.""" 2 | from sys import stdout 3 | from typing import Union 4 | 5 | from .commands import Command 6 | from .options import Option 7 | 8 | 9 | class JobWriter: 10 | """This class is used to write LSF job scripts. 11 | 12 | Attributes: 13 | options (list[Option]): List of options to add to the job script. 14 | commands (list[Command]): List of commands to add to the job script. 15 | """ 16 | 17 | options: list[Option] = [] 18 | commands: list[Command] = [] 19 | 20 | def __init__(self, opts: list[Option] = [], cmds: list[Command] = []): 21 | """Initialize the JobWriter with the given options and commands. 22 | 23 | Args: 24 | opts (list[Option], optional): List of options to add to the job script. 25 | Defaults to []. 26 | cmds (list[Command], optional): List of commands to add to the job script. 27 | Defaults to []. 28 | """ 29 | self.options = opts 30 | self.commands = cmds 31 | 32 | def add(self, opt_or_cmd: Union[Option, Command]): 33 | if isinstance(opt_or_cmd, Option): 34 | self.add_option(opt_or_cmd) 35 | if isinstance(opt_or_cmd, Command): 36 | self.add_command(opt_or_cmd) 37 | 38 | return self 39 | 40 | def add_option(self, option: Option) -> "JobWriter": 41 | """Add an option to the job script. 42 | 43 | Args: 44 | option (Option): Option to add to the job script. 45 | 46 | Returns: 47 | JobWriter: The JobWriter object. 48 | """ 49 | self.options.append(option) 50 | return self 51 | 52 | def add_command(self, command: Command) -> "JobWriter": 53 | """Add a command to the job script. 54 | 55 | Args: 56 | command (Command): Command to add to the job script. 57 | 58 | Returns: 59 | JobWriter: The JobWriter object. 60 | """ 61 | self.commands.append(command) 62 | return self 63 | 64 | def to_string(self) -> str: 65 | """Convert the job script to a string. 66 | 67 | Returns: 68 | str: The job script as a string. 69 | """ 70 | 71 | lines = ["#!/bin/sh", "# LSF Job options"] 72 | lines += list(map(lambda o: o.get_option_line(), self.options)) 73 | lines.append("# Commands") 74 | lines += list(map(lambda c: c.get_command(), self.commands)) 75 | 76 | return "\n".join(lines) 77 | 78 | def to_stdout(self): 79 | """Write the job script to stdout. 80 | 81 | Returns: 82 | JobWriter: The JobWriter object. 83 | """ 84 | stdout.write(self.to_string()) 85 | return self 86 | -------------------------------------------------------------------------------- /dtuhpc/cli/commands/deploy.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | from io import StringIO 4 | from typing import Optional 5 | 6 | import click 7 | 8 | from dtuhpc.cli.cli_context import CLIContext 9 | from dtuhpc.console import console 10 | from dtuhpc.jobwriter.job_reader import JobReader 11 | 12 | 13 | @click.command() 14 | @click.option("--pr", "-p", default=None, type=int) 15 | @click.option("--branch", "-b", default=None, type=str) 16 | @click.option("--jsm", "-j", is_flag=True, type=bool, default=False) 17 | @click.argument("job_name", type=str, default=None) 18 | @click.pass_obj 19 | def deploy( 20 | ctx: CLIContext, 21 | pr: Optional[int], 22 | branch: Optional[str], 23 | job_name: Optional[str], 24 | jsm: bool, 25 | ): 26 | """Deploy a job.""" 27 | gh = ctx.github 28 | 29 | repo = ctx.git_repo 30 | repo_remote = repo.remote("origin") 31 | repo_url = repo_remote.url 32 | repo.close() 33 | 34 | repo_id = ( 35 | repo_url.replace("git@github.com:", "") 36 | .replace("https://github.com/", "") 37 | .replace(".git", "") 38 | ) 39 | 40 | gh_repo = gh.get_repo(repo_id) 41 | 42 | if pr is not None: 43 | pr = gh_repo.get_pull(pr) 44 | branch_name = pr.head.ref 45 | elif branch is not None: 46 | branch_name = branch 47 | else: 48 | method = console.prompt_list("Branch or PR: ", ["Branch", "PR"]) 49 | 50 | if method == 0: 51 | all_branches = gh_repo.get_branches() 52 | all_branch_names = [branch.name for branch in all_branches] 53 | 54 | branch_index = console.prompt_list("Pick a branch: ", all_branch_names) 55 | branch_name = all_branch_names[branch_index] 56 | else: 57 | pull_requests = gh_repo.get_pulls(state="open", sort="created") 58 | 59 | options = [ 60 | f"#{pr.number}: {pr.title} ({pr.head.ref})" for pr in pull_requests 61 | ] 62 | 63 | if len(options) == 0: 64 | console.error("No open pull requests.") 65 | sys.exit(1) 66 | 67 | option_idx = console.prompt_list("Pick a PR: ", options) 68 | pr = pull_requests[option_idx] 69 | branch_name = pr.head.ref 70 | 71 | ctx.cwd = ctx.config["project"]["path"] 72 | 73 | conn = ctx.connection 74 | conn.run("git fetch") 75 | conn.run(f"git checkout {branch_name}") 76 | conn.run("git pull") 77 | 78 | job_reader = JobReader(job_name) 79 | job_reader.parse() 80 | job_contents = job_reader.to_str() 81 | 82 | deploy_job_path = os.path.join( 83 | ctx.config["ssh"]["default_cwd"], ".dtuhpc/", "deploy_job.sh" 84 | ) 85 | conn.conn.put(StringIO(job_contents), deploy_job_path) 86 | 87 | jsm = " -jsm y" if jsm else "" 88 | 89 | conn.run(f"bsub -cwd {ctx.cwd}{jsm} < {deploy_job_path}") 90 | conn.run(f"rm {deploy_job_path}") 91 | 92 | conn.close() 93 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | 4 | 5 | ## v2.1.3 (2023-11-18) 6 | 7 | ### Fix 8 | 9 | * Cannot read keys correctly ([`45f349d`](https://github.com/jhoffe/dtuhpc/commit/45f349d30c7e121f3ad40c756af02462400378df)) 10 | 11 | ## v2.1.2 (2023-02-14) 12 | 13 | 14 | ## v2.1.1 (2023-02-12) 15 | 16 | 17 | ## v2.1.0 (2023-02-12) 18 | ### Feature 19 | * Add jsm option to deploy ([`501901a`](https://github.com/jhoffe/dtuhpc/commit/501901a90fd37b1dbe1ed5bfae464ee38ac4103c)) 20 | 21 | ## v2.0.3 (2023-02-12) 22 | 23 | 24 | ## v2.0.2 (2023-02-12) 25 | ### Fix 26 | * **options:** Remove square brackets ([#60](https://github.com/jhoffe/dtuhpc/issues/60)) ([`b71e04a`](https://github.com/jhoffe/dtuhpc/commit/b71e04af9e8b378fcf0e553f8566693a61f6882c)) 27 | 28 | ## v2.0.1 (2023-02-12) 29 | ### Fix 30 | * Fix job loading ([#59](https://github.com/jhoffe/dtuhpc/issues/59)) ([`baaffad`](https://github.com/jhoffe/dtuhpc/commit/baaffad9241c9adfd1396caf5bfa1ee5f32205cf)) 31 | 32 | ## v2.0.0 (2023-02-12) 33 | ### Feature 34 | * Update version in pyproject ([#56](https://github.com/jhoffe/dtuhpc/issues/56)) ([`e5a4595`](https://github.com/jhoffe/dtuhpc/commit/e5a4595af2d963ab93b05416b8cb0482ed946e30)) 35 | 36 | ### Breaking 37 | * remove parse command ([`e5a4595`](https://github.com/jhoffe/dtuhpc/commit/e5a4595af2d963ab93b05416b8cb0482ed946e30)) 38 | 39 | ### Documentation 40 | * Update readme docs ([#55](https://github.com/jhoffe/dtuhpc/issues/55)) ([`7cdced5`](https://github.com/jhoffe/dtuhpc/commit/7cdced5c43e7a31e6869686b081a5195b54dfdff)) 41 | 42 | ## v1.0.12 (2023-02-12) 43 | 44 | 45 | ## v1.0.11 (2023-02-12) 46 | 47 | 48 | ## v1.0.10 (2023-02-12) 49 | 50 | 51 | ## v1.0.9 (2023-02-12) 52 | 53 | 54 | ## v1.0.8 (2023-02-12) 55 | ### Fix 56 | * Release ([#48](https://github.com/jhoffe/dtuhpc/issues/48)) ([`3201324`](https://github.com/jhoffe/dtuhpc/commit/32013249159dde63322c739de27b96ba04702c63)) 57 | 58 | ## v1.0.7 (2023-02-12) 59 | 60 | 61 | ## v1.0.6 (2023-02-12) 62 | ### Fix 63 | * **workflows:** Workflow update ([#46](https://github.com/jhoffe/dtuhpc/issues/46)) ([`de7e564`](https://github.com/jhoffe/dtuhpc/commit/de7e564cb1b43fcce5f144f268b8d872df182790)) 64 | 65 | ## v1.0.5 (2023-02-12) 66 | 67 | 68 | ## v1.0.4 (2023-02-12) 69 | 70 | 71 | ## v1.0.3 (2023-02-12) 72 | 73 | 74 | ## v1.0.2 (2023-02-12) 75 | 76 | 77 | ## v1.0.1 (2023-02-12) 78 | 79 | 80 | ## v1.0.0 (2023-02-12) 81 | ### Feature 82 | * Add semantic release workflow ([#31](https://github.com/jhoffe/dtuhpc/issues/31)) ([`5b022ce`](https://github.com/jhoffe/dtuhpc/commit/5b022ce7449827365319b15e36c938a0d5bf43a5)) 83 | * Use poetry instead of setuptools ([#25](https://github.com/jhoffe/dtuhpc/issues/25)) ([`d94ee83`](https://github.com/jhoffe/dtuhpc/commit/d94ee834622e081771d5608149e0b71b186692a0)) 84 | * Add job writer ([#20](https://github.com/jhoffe/dtuhpc/issues/20)) ([`c32919d`](https://github.com/jhoffe/dtuhpc/commit/c32919dd176a7935f0ab974ced99bcff06bc695a)) 85 | 86 | ### Fix 87 | * **workflows:** Add version_variable ([#32](https://github.com/jhoffe/dtuhpc/issues/32)) ([`0066464`](https://github.com/jhoffe/dtuhpc/commit/00664644a0f68a8b18ea7b890b6260eb2decd7be)) 88 | 89 | ### Breaking 90 | * add automatic releases and deployments. ([`5b022ce`](https://github.com/jhoffe/dtuhpc/commit/5b022ce7449827365319b15e36c938a0d5bf43a5)) 91 | -------------------------------------------------------------------------------- /dtuhpc/jobwriter/job_reader.py: -------------------------------------------------------------------------------- 1 | import re 2 | from pathlib import Path 3 | 4 | import tomli 5 | 6 | from dtuhpc.jobwriter.commands import Command, LoadModuleCommand 7 | from dtuhpc.jobwriter.job_writer import JobWriter 8 | from dtuhpc.jobwriter.options import ( 9 | CoreBlockSizeOption, 10 | CorePTileSizeOption, 11 | EmailForNotificationsOption, 12 | ErrorOutputFilePathOption, 13 | MemoryPerCoreKillLimitOption, 14 | MemoryPerCoreOption, 15 | NameOption, 16 | NCPUCoresOption, 17 | Option, 18 | QueueOption, 19 | SendNotificationAtEndOption, 20 | SendNotificationAtStartOption, 21 | SingleHostOption, 22 | StandardOutputFilePathOption, 23 | UseGPUOption, 24 | WallTimeOption, 25 | ) 26 | 27 | MAPPING = { 28 | "core_block_size": CoreBlockSizeOption, 29 | "core_p_tile_size": CorePTileSizeOption, 30 | "email": EmailForNotificationsOption, 31 | "error_output": ErrorOutputFilePathOption, 32 | "memory_kill_limit": MemoryPerCoreKillLimitOption, 33 | "memory": MemoryPerCoreOption, 34 | "cpu": NCPUCoresOption, 35 | "name": NameOption, 36 | "queue": QueueOption, 37 | "single_host": SingleHostOption, 38 | "standard_output": StandardOutputFilePathOption, 39 | "notification_start": SendNotificationAtStartOption, 40 | "notification_end": SendNotificationAtEndOption, 41 | "walltime": WallTimeOption, 42 | "use_gpu": UseGPUOption, 43 | "option": Option, 44 | "module": LoadModuleCommand, 45 | "commands": Command, 46 | } 47 | 48 | 49 | class JobReader: 50 | job_file_path: Path 51 | config: dict 52 | job_writer: JobWriter 53 | 54 | def __init__(self, job_file_path: str, variables: dict = None): 55 | self.job_file_path = Path(job_file_path) 56 | self.job_writer = JobWriter() 57 | self.load_job(job_file_path, variables) 58 | 59 | def load_job(self, job_file_path: str, variables: dict = None) -> "JobReader": 60 | with open(job_file_path, "r") as f: 61 | contents = self.replace_variables(f.read(), variables or {}) 62 | self.config = tomli.loads(contents) 63 | 64 | return self 65 | 66 | @staticmethod 67 | def replace_variables(string: str, variables: dict) -> str: 68 | regex = re.compile(r"\${{\s*(?P[a-zA-Z_]+[0-9]*)\s*}}") 69 | 70 | for match in regex.finditer(string): 71 | var_name = match.group("var_name") 72 | if var_name in variables: 73 | string = string.replace(match.group(0), variables[var_name]) 74 | else: 75 | raise ValueError(f"Variable {var_name} not found in job file") 76 | 77 | return string 78 | 79 | def parse(self) -> "JobReader": 80 | for key, value in self.config.items(): 81 | if key in MAPPING: 82 | option = MAPPING[key] 83 | 84 | if type(value) is dict: 85 | self.job_writer.add(option(**value)) 86 | elif type(value) is list: 87 | for item in value: 88 | self.job_writer.add( 89 | option(**item) if type(item) is dict else option(item) 90 | ) 91 | else: 92 | self.job_writer.add(option(value)) 93 | else: 94 | raise ValueError(f"Unknown option: {key}") 95 | return self 96 | 97 | def to_str(self): 98 | return self.job_writer.to_string() 99 | -------------------------------------------------------------------------------- /dtuhpc/cli/cli_context.py: -------------------------------------------------------------------------------- 1 | import base64 2 | import json 3 | import os 4 | import sys 5 | from pathlib import Path 6 | from typing import Optional 7 | 8 | import click 9 | import tomli 10 | from cryptography.fernet import Fernet 11 | from cryptography.hazmat.primitives import hashes 12 | from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC 13 | from git import Repo 14 | from github import Github 15 | 16 | from dtuhpc.connector import HPCConnection 17 | from dtuhpc.console import console 18 | 19 | 20 | class CLIContext: 21 | _hide: bool = False 22 | _config: Optional[dict] = None 23 | _auth: Optional[dict] = None 24 | _github: Optional[Github] = None 25 | _conn: Optional[HPCConnection] = None 26 | _config_path: Optional[str] = None 27 | _cwd: Optional[str] = None 28 | 29 | def __init__( 30 | self, 31 | config_path: Optional[str] = None, 32 | hide: bool = False, 33 | cwd: Optional[str] = None, 34 | ): 35 | self.hide = hide 36 | self._config_path = config_path 37 | self._cwd = cwd 38 | 39 | @staticmethod 40 | def _get_global_config_dir() -> Path: 41 | return Path(os.path.join(Path.home(), ".config", "dtuhpc")) 42 | 43 | @staticmethod 44 | def _get_local_config_path() -> Path: 45 | return Path(os.path.join(Path.cwd(), ".dtuhpc.toml")) 46 | 47 | @staticmethod 48 | def _get_global_config_path() -> Path: 49 | return CLIContext._get_global_config_dir() / ".dtuhpc.toml" 50 | 51 | @staticmethod 52 | def get_global_auth_path() -> Path: 53 | return CLIContext._get_global_config_dir() / "auth.json" 54 | 55 | @property 56 | def config_path(self) -> Path: 57 | if self._config_path is not None: 58 | return Path(self._config_path) 59 | 60 | local_path = self._get_local_config_path() 61 | if local_path.exists(): 62 | return local_path 63 | 64 | global_path = self._get_global_config_path() 65 | if global_path.exists(): 66 | return global_path 67 | 68 | console.error("Could not find any config file.") 69 | sys.exit(1) 70 | 71 | @property 72 | def config(self): 73 | if self._config is None: 74 | self._load_config() 75 | 76 | return self._config 77 | 78 | def _load_config(self) -> "CLIContext": 79 | with open(self.config_path, "rb") as config_file: 80 | config = tomli.load(config_file) 81 | 82 | self._config = config 83 | self._cwd = self._cwd if self.cwd is not None else config["ssh"]["default_cwd"] 84 | 85 | return self 86 | 87 | @property 88 | def cwd(self) -> str: 89 | if self._cwd is None: 90 | return self.config["ssh"]["default_cwd"] 91 | 92 | return self._cwd 93 | 94 | @cwd.setter 95 | def cwd(self, cwd: str): 96 | self._cwd = cwd 97 | 98 | @property 99 | def auth(self): 100 | if self._auth is None: 101 | self._load_auth() 102 | 103 | return self._auth 104 | 105 | def _load_auth(self) -> "CLIContext": 106 | if not os.path.exists(self.get_global_auth_path()): 107 | console.error("Could not find auth file. Please call 'dtuhpc auth' first.") 108 | sys.exit(1) 109 | 110 | with open(self.get_global_auth_path(), "rb") as auth_file: 111 | auth = json.load(auth_file) 112 | 113 | encryption_key = bytes(click.prompt("Encryption key", hide_input=True), "utf-8") 114 | 115 | salt = base64.urlsafe_b64decode(auth["salt"].encode("utf-8")) 116 | kdf = PBKDF2HMAC( 117 | algorithm=hashes.SHA256(), 118 | length=32, 119 | salt=salt, 120 | iterations=480000, 121 | ) 122 | key = base64.urlsafe_b64encode(kdf.derive(encryption_key)) 123 | 124 | f = Fernet(key) 125 | 126 | auth["password"] = f.decrypt(bytes(auth["password"], "utf-8")).decode("utf-8") 127 | 128 | self._auth = auth 129 | 130 | return self 131 | 132 | @property 133 | def connection(self) -> HPCConnection: 134 | if self._conn is None: 135 | console.primary("Connecting...") 136 | self._conn = HPCConnection( 137 | user=self.auth["username"], 138 | host=self.config["ssh"]["host"], 139 | key_filename=self.config["ssh"]["key_filename"], 140 | password=self.auth["password"], 141 | hide=self.hide, 142 | cwd=self.cwd, 143 | ) 144 | console.success("Connected!") 145 | 146 | return self._conn 147 | 148 | @property 149 | def git_repo(self) -> Repo: 150 | return Repo(os.getcwd()) 151 | 152 | @property 153 | def github(self) -> Github: 154 | if self._github is None: 155 | self._github = Github(self.config["github"]["access_token"]) 156 | 157 | return self._github 158 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DTU HPC 2 | 3 | DTU HPC is a collection of scripts and tools for running jobs on the DTU HPC cluster. 4 | It should help you to get started with running jobs on the cluster, and to make your life easier. 5 | 6 | ## Installation 7 | 8 | To install just run: 9 | 10 | ```bash 11 | pip install dtuhpc 12 | ``` 13 | 14 | ## Getting started 15 | 16 | To get started you first need to run: 17 | 18 | ```bash 19 | dtuhpc auth 20 | ``` 21 | 22 | It will ask you for your username and password for DTU, and it will then 23 | ask you for an encryption password. This password is used to encrypt your 24 | DTU password, so that it can be stored on your computer. You will need to 25 | remember this password, as it is used to decrypt your password when you 26 | run commands. 27 | 28 | Afterwards, you should create a configuration file for your project. This 29 | should be named `.dtuhpc.toml` and should be placed in the root of your project. 30 | You can use the following template: 31 | 32 | ```toml 33 | [ssh] 34 | user = "" 35 | host = "" 36 | default_cwd = "" 37 | key_filename = "" 38 | 39 | [github] 40 | access_token = "" 41 | 42 | [project] 43 | name = "" 44 | path = "" 45 | default_deploy_branch = "master" 46 | ``` 47 | 48 | The `ssh` section is used to configure the ssh connection to the cluster. 49 | The GitHub access token can be generated from the following [page](https://github.com/settings/tokens). 50 | 51 | ### Setup project 52 | 53 | To set up a project, you can run: 54 | 55 | ```bash 56 | dtuhpc init [--poetry] [--custom-job=] 57 | ``` 58 | 59 | This will dispatch a job to the cluster, which will clone your project, create a 60 | virtual environment, and install the dependencies. 61 | You can choose to use either poetry, pip, or a custom job script. How to define jobs 62 | will be explained in the next section. 63 | 64 | ### Writing jobs 65 | 66 | Jobs are defined as toml files. It contains numerous options: 67 | 68 | ``` 69 | name = "" 70 | queue = "" 71 | single_host = 72 | walltime = { hours = , minutes = } 73 | standard_output = "" 74 | error_output = "" 75 | memory = 76 | memory_kill_limit = 77 | cores = 78 | email = "" 79 | notification_start = 80 | notification_end = 81 | core_block_size = 82 | core_p_tile_size = 83 | use_gpu = { num_of_gpus = , per_task = } 84 | 85 | commands = [ 86 | "", 87 | "", 88 | ... 89 | ] 90 | ``` 91 | 92 | An example of a script can be seen here: 93 | 94 | ```toml 95 | queue = "hpc" 96 | name = "init_${{ project_name }}" 97 | walltime = { hours = 0, minutes = 15 } 98 | single_host = true 99 | cpu = 2 100 | memory = 4 101 | standard_output = "init_${{ project_name }}.out" 102 | error_output = "init_${{ project_name }}.err" 103 | 104 | commands = [ 105 | "git clone ${{ git_url }} ${{ project_path }}", 106 | "module load python3/3.10.7", 107 | "cd ${{ project_path }}", 108 | "python3 -m venv ${{ project_path }}/venv", 109 | "source ${{ project_path }}/venv/bin/activate", 110 | "pip3 install 'poetry==1.3.2'", 111 | "poetry install", 112 | ] 113 | ``` 114 | 115 | In this script, we can see that we can use variables in the script. These variables 116 | are some default ones that are only available for the `init` job. 117 | 118 | ### Deploying jobs 119 | 120 | To deploy a job you just run the following command: 121 | 122 | ```bash 123 | dtuhpc deploy 124 | ``` 125 | 126 | It will then ask you to pick from branches or PR's. It will then dispatch the job 127 | to the cluster. 128 | 129 | ### Other commands 130 | 131 | Some other commands: 132 | 133 | #### Exec commands on cluster 134 | 135 | To execute commands on the cluster, you can run: 136 | 137 | ```bash 138 | dtuhpc exec '' 139 | ``` 140 | 141 | It will run in the default working directory, which is defined in the configuration file. 142 | 143 | #### SSH into cluster 144 | 145 | To ssh into the cluster, you can run: 146 | 147 | ```bash 148 | dtuhpc ssh 149 | ``` 150 | 151 | It will then open an ssh connection to the cluster. From here you can run commands 152 | as you would normally. 153 | 154 | #### Predefined subcommands 155 | 156 | There are also some predefined subcommands, which are just wrappers around the 157 | cluster commands. They are all prefixed by `dtuhpc c `. To get the 158 | full documentation for the commands, you can run: 159 | 160 | ```bash 161 | dtuhpc c --help 162 | ``` 163 | 164 | ##### bkill 165 | 166 | Kill a job on the cluster. 167 | 168 | ```bash 169 | dtuhpc c bkill 170 | ``` 171 | 172 | ##### bqueues 173 | 174 | List all queues on the cluster. 175 | 176 | ```bash 177 | dtuhpc c bqueues 178 | ``` 179 | 180 | ##### bstat 181 | 182 | Get the status of a job on the cluster. 183 | 184 | ```bash 185 | dtuhpc c bstat 186 | ``` 187 | 188 | ##### bsub 189 | 190 | Submit a job to the cluster. 191 | 192 | ```bash 193 | dtuhpc c bsub 194 | ``` 195 | 196 | ##### nodestat 197 | 198 | Get the status of the nodes on the cluster. 199 | 200 | ```bash 201 | dtuhpc c nodestat 202 | ``` 203 | 204 | ##### showstart 205 | 206 | Show the start time of a job on the cluster. 207 | 208 | ```bash 209 | dtuhpc c showstart 210 | ``` 211 | -------------------------------------------------------------------------------- /poetry.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand. 2 | 3 | [[package]] 4 | name = "atomicwrites" 5 | version = "1.4.1" 6 | description = "Atomic file writes." 7 | optional = false 8 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 9 | files = [ 10 | {file = "atomicwrites-1.4.1.tar.gz", hash = "sha256:81b2c9071a49367a7f770170e5eec8cb66567cfbbc8c73d20ce5ca4a8d71cf11"}, 11 | ] 12 | 13 | [[package]] 14 | name = "attrs" 15 | version = "22.2.0" 16 | description = "Classes Without Boilerplate" 17 | optional = false 18 | python-versions = ">=3.6" 19 | files = [ 20 | {file = "attrs-22.2.0-py3-none-any.whl", hash = "sha256:29e95c7f6778868dbd49170f98f8818f78f3dc5e0e37c0b1f474e3561b240836"}, 21 | {file = "attrs-22.2.0.tar.gz", hash = "sha256:c9227bfc2f01993c03f68db37d1d15c9690188323c067c641f1a35ca58185f99"}, 22 | ] 23 | 24 | [package.extras] 25 | cov = ["attrs[tests]", "coverage-enable-subprocess", "coverage[toml] (>=5.3)"] 26 | dev = ["attrs[docs,tests]"] 27 | docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier", "zope.interface"] 28 | tests = ["attrs[tests-no-zope]", "zope.interface"] 29 | tests-no-zope = ["cloudpickle", "cloudpickle", "hypothesis", "hypothesis", "mypy (>=0.971,<0.990)", "mypy (>=0.971,<0.990)", "pympler", "pympler", "pytest (>=4.3.0)", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-mypy-plugins", "pytest-xdist[psutil]", "pytest-xdist[psutil]"] 30 | 31 | [[package]] 32 | name = "bcrypt" 33 | version = "4.0.1" 34 | description = "Modern password hashing for your software and your servers" 35 | optional = false 36 | python-versions = ">=3.6" 37 | files = [ 38 | {file = "bcrypt-4.0.1-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:b1023030aec778185a6c16cf70f359cbb6e0c289fd564a7cfa29e727a1c38f8f"}, 39 | {file = "bcrypt-4.0.1-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:08d2947c490093a11416df18043c27abe3921558d2c03e2076ccb28a116cb6d0"}, 40 | {file = "bcrypt-4.0.1-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0eaa47d4661c326bfc9d08d16debbc4edf78778e6aaba29c1bc7ce67214d4410"}, 41 | {file = "bcrypt-4.0.1-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ae88eca3024bb34bb3430f964beab71226e761f51b912de5133470b649d82344"}, 42 | {file = "bcrypt-4.0.1-cp36-abi3-manylinux_2_24_x86_64.whl", hash = "sha256:a522427293d77e1c29e303fc282e2d71864579527a04ddcfda6d4f8396c6c36a"}, 43 | {file = "bcrypt-4.0.1-cp36-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:fbdaec13c5105f0c4e5c52614d04f0bca5f5af007910daa8b6b12095edaa67b3"}, 44 | {file = "bcrypt-4.0.1-cp36-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:ca3204d00d3cb2dfed07f2d74a25f12fc12f73e606fcaa6975d1f7ae69cacbb2"}, 45 | {file = "bcrypt-4.0.1-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:089098effa1bc35dc055366740a067a2fc76987e8ec75349eb9484061c54f535"}, 46 | {file = "bcrypt-4.0.1-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:e9a51bbfe7e9802b5f3508687758b564069ba937748ad7b9e890086290d2f79e"}, 47 | {file = "bcrypt-4.0.1-cp36-abi3-win32.whl", hash = "sha256:2caffdae059e06ac23fce178d31b4a702f2a3264c20bfb5ff541b338194d8fab"}, 48 | {file = "bcrypt-4.0.1-cp36-abi3-win_amd64.whl", hash = "sha256:8a68f4341daf7522fe8d73874de8906f3a339048ba406be6ddc1b3ccb16fc0d9"}, 49 | {file = "bcrypt-4.0.1-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf4fa8b2ca74381bb5442c089350f09a3f17797829d958fad058d6e44d9eb83c"}, 50 | {file = "bcrypt-4.0.1-pp37-pypy37_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:67a97e1c405b24f19d08890e7ae0c4f7ce1e56a712a016746c8b2d7732d65d4b"}, 51 | {file = "bcrypt-4.0.1-pp37-pypy37_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:b3b85202d95dd568efcb35b53936c5e3b3600c7cdcc6115ba461df3a8e89f38d"}, 52 | {file = "bcrypt-4.0.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cbb03eec97496166b704ed663a53680ab57c5084b2fc98ef23291987b525cb7d"}, 53 | {file = "bcrypt-4.0.1-pp38-pypy38_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:5ad4d32a28b80c5fa6671ccfb43676e8c1cc232887759d1cd7b6f56ea4355215"}, 54 | {file = "bcrypt-4.0.1-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:b57adba8a1444faf784394de3436233728a1ecaeb6e07e8c22c8848f179b893c"}, 55 | {file = "bcrypt-4.0.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:705b2cea8a9ed3d55b4491887ceadb0106acf7c6387699fca771af56b1cdeeda"}, 56 | {file = "bcrypt-4.0.1-pp39-pypy39_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:2b3ac11cf45161628f1f3733263e63194f22664bf4d0c0f3ab34099c02134665"}, 57 | {file = "bcrypt-4.0.1-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:3100851841186c25f127731b9fa11909ab7b1df6fc4b9f8353f4f1fd952fbf71"}, 58 | {file = "bcrypt-4.0.1.tar.gz", hash = "sha256:27d375903ac8261cfe4047f6709d16f7d18d39b1ec92aaf72af989552a650ebd"}, 59 | ] 60 | 61 | [package.extras] 62 | tests = ["pytest (>=3.2.1,!=3.3.0)"] 63 | typecheck = ["mypy"] 64 | 65 | [[package]] 66 | name = "black" 67 | version = "22.12.0" 68 | description = "The uncompromising code formatter." 69 | optional = false 70 | python-versions = ">=3.7" 71 | files = [ 72 | {file = "black-22.12.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9eedd20838bd5d75b80c9f5487dbcb06836a43833a37846cf1d8c1cc01cef59d"}, 73 | {file = "black-22.12.0-cp310-cp310-win_amd64.whl", hash = "sha256:159a46a4947f73387b4d83e87ea006dbb2337eab6c879620a3ba52699b1f4351"}, 74 | {file = "black-22.12.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d30b212bffeb1e252b31dd269dfae69dd17e06d92b87ad26e23890f3efea366f"}, 75 | {file = "black-22.12.0-cp311-cp311-win_amd64.whl", hash = "sha256:7412e75863aa5c5411886804678b7d083c7c28421210180d67dfd8cf1221e1f4"}, 76 | {file = "black-22.12.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c116eed0efb9ff870ded8b62fe9f28dd61ef6e9ddd28d83d7d264a38417dcee2"}, 77 | {file = "black-22.12.0-cp37-cp37m-win_amd64.whl", hash = "sha256:1f58cbe16dfe8c12b7434e50ff889fa479072096d79f0a7f25e4ab8e94cd8350"}, 78 | {file = "black-22.12.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:77d86c9f3db9b1bf6761244bc0b3572a546f5fe37917a044e02f3166d5aafa7d"}, 79 | {file = "black-22.12.0-cp38-cp38-win_amd64.whl", hash = "sha256:82d9fe8fee3401e02e79767016b4907820a7dc28d70d137eb397b92ef3cc5bfc"}, 80 | {file = "black-22.12.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:101c69b23df9b44247bd88e1d7e90154336ac4992502d4197bdac35dd7ee3320"}, 81 | {file = "black-22.12.0-cp39-cp39-win_amd64.whl", hash = "sha256:559c7a1ba9a006226f09e4916060982fd27334ae1998e7a38b3f33a37f7a2148"}, 82 | {file = "black-22.12.0-py3-none-any.whl", hash = "sha256:436cc9167dd28040ad90d3b404aec22cedf24a6e4d7de221bec2730ec0c97bcf"}, 83 | {file = "black-22.12.0.tar.gz", hash = "sha256:229351e5a18ca30f447bf724d007f890f97e13af070bb6ad4c0a441cd7596a2f"}, 84 | ] 85 | 86 | [package.dependencies] 87 | click = ">=8.0.0" 88 | mypy-extensions = ">=0.4.3" 89 | pathspec = ">=0.9.0" 90 | platformdirs = ">=2" 91 | tomli = {version = ">=1.1.0", markers = "python_full_version < \"3.11.0a7\""} 92 | typing-extensions = {version = ">=3.10.0.0", markers = "python_version < \"3.10\""} 93 | 94 | [package.extras] 95 | colorama = ["colorama (>=0.4.3)"] 96 | d = ["aiohttp (>=3.7.4)"] 97 | jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] 98 | uvloop = ["uvloop (>=0.15.2)"] 99 | 100 | [[package]] 101 | name = "bleach" 102 | version = "6.0.0" 103 | description = "An easy safelist-based HTML-sanitizing tool." 104 | optional = false 105 | python-versions = ">=3.7" 106 | files = [ 107 | {file = "bleach-6.0.0-py3-none-any.whl", hash = "sha256:33c16e3353dbd13028ab4799a0f89a83f113405c766e9c122df8a06f5b85b3f4"}, 108 | {file = "bleach-6.0.0.tar.gz", hash = "sha256:1a1a85c1595e07d8db14c5f09f09e6433502c51c595970edc090551f0db99414"}, 109 | ] 110 | 111 | [package.dependencies] 112 | six = ">=1.9.0" 113 | webencodings = "*" 114 | 115 | [package.extras] 116 | css = ["tinycss2 (>=1.1.0,<1.2)"] 117 | 118 | [[package]] 119 | name = "bump2version" 120 | version = "1.0.1" 121 | description = "Version-bump your software with a single command!" 122 | optional = false 123 | python-versions = ">=3.5" 124 | files = [ 125 | {file = "bump2version-1.0.1-py2.py3-none-any.whl", hash = "sha256:37f927ea17cde7ae2d7baf832f8e80ce3777624554a653006c9144f8017fe410"}, 126 | {file = "bump2version-1.0.1.tar.gz", hash = "sha256:762cb2bfad61f4ec8e2bdf452c7c267416f8c70dd9ecb1653fd0bbb01fa936e6"}, 127 | ] 128 | 129 | [[package]] 130 | name = "certifi" 131 | version = "2022.12.7" 132 | description = "Python package for providing Mozilla's CA Bundle." 133 | optional = false 134 | python-versions = ">=3.6" 135 | files = [ 136 | {file = "certifi-2022.12.7-py3-none-any.whl", hash = "sha256:4ad3232f5e926d6718ec31cfc1fcadfde020920e278684144551c91769c7bc18"}, 137 | {file = "certifi-2022.12.7.tar.gz", hash = "sha256:35824b4c3a97115964b408844d64aa14db1cc518f6562e8d7261699d1350a9e3"}, 138 | ] 139 | 140 | [[package]] 141 | name = "cffi" 142 | version = "1.15.1" 143 | description = "Foreign Function Interface for Python calling C code." 144 | optional = false 145 | python-versions = "*" 146 | files = [ 147 | {file = "cffi-1.15.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:a66d3508133af6e8548451b25058d5812812ec3798c886bf38ed24a98216fab2"}, 148 | {file = "cffi-1.15.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:470c103ae716238bbe698d67ad020e1db9d9dba34fa5a899b5e21577e6d52ed2"}, 149 | {file = "cffi-1.15.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:9ad5db27f9cabae298d151c85cf2bad1d359a1b9c686a275df03385758e2f914"}, 150 | {file = "cffi-1.15.1-cp27-cp27m-win32.whl", hash = "sha256:b3bbeb01c2b273cca1e1e0c5df57f12dce9a4dd331b4fa1635b8bec26350bde3"}, 151 | {file = "cffi-1.15.1-cp27-cp27m-win_amd64.whl", hash = "sha256:e00b098126fd45523dd056d2efba6c5a63b71ffe9f2bbe1a4fe1716e1d0c331e"}, 152 | {file = "cffi-1.15.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:d61f4695e6c866a23a21acab0509af1cdfd2c013cf256bbf5b6b5e2695827162"}, 153 | {file = "cffi-1.15.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:ed9cb427ba5504c1dc15ede7d516b84757c3e3d7868ccc85121d9310d27eed0b"}, 154 | {file = "cffi-1.15.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:39d39875251ca8f612b6f33e6b1195af86d1b3e60086068be9cc053aa4376e21"}, 155 | {file = "cffi-1.15.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:285d29981935eb726a4399badae8f0ffdff4f5050eaa6d0cfc3f64b857b77185"}, 156 | {file = "cffi-1.15.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3eb6971dcff08619f8d91607cfc726518b6fa2a9eba42856be181c6d0d9515fd"}, 157 | {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:21157295583fe8943475029ed5abdcf71eb3911894724e360acff1d61c1d54bc"}, 158 | {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5635bd9cb9731e6d4a1132a498dd34f764034a8ce60cef4f5319c0541159392f"}, 159 | {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2012c72d854c2d03e45d06ae57f40d78e5770d252f195b93f581acf3ba44496e"}, 160 | {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd86c085fae2efd48ac91dd7ccffcfc0571387fe1193d33b6394db7ef31fe2a4"}, 161 | {file = "cffi-1.15.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:fa6693661a4c91757f4412306191b6dc88c1703f780c8234035eac011922bc01"}, 162 | {file = "cffi-1.15.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:59c0b02d0a6c384d453fece7566d1c7e6b7bae4fc5874ef2ef46d56776d61c9e"}, 163 | {file = "cffi-1.15.1-cp310-cp310-win32.whl", hash = "sha256:cba9d6b9a7d64d4bd46167096fc9d2f835e25d7e4c121fb2ddfc6528fb0413b2"}, 164 | {file = "cffi-1.15.1-cp310-cp310-win_amd64.whl", hash = "sha256:ce4bcc037df4fc5e3d184794f27bdaab018943698f4ca31630bc7f84a7b69c6d"}, 165 | {file = "cffi-1.15.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3d08afd128ddaa624a48cf2b859afef385b720bb4b43df214f85616922e6a5ac"}, 166 | {file = "cffi-1.15.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3799aecf2e17cf585d977b780ce79ff0dc9b78d799fc694221ce814c2c19db83"}, 167 | {file = "cffi-1.15.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a591fe9e525846e4d154205572a029f653ada1a78b93697f3b5a8f1f2bc055b9"}, 168 | {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3548db281cd7d2561c9ad9984681c95f7b0e38881201e157833a2342c30d5e8c"}, 169 | {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:91fc98adde3d7881af9b59ed0294046f3806221863722ba7d8d120c575314325"}, 170 | {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94411f22c3985acaec6f83c6df553f2dbe17b698cc7f8ae751ff2237d96b9e3c"}, 171 | {file = "cffi-1.15.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:03425bdae262c76aad70202debd780501fabeaca237cdfddc008987c0e0f59ef"}, 172 | {file = "cffi-1.15.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:cc4d65aeeaa04136a12677d3dd0b1c0c94dc43abac5860ab33cceb42b801c1e8"}, 173 | {file = "cffi-1.15.1-cp311-cp311-win32.whl", hash = "sha256:a0f100c8912c114ff53e1202d0078b425bee3649ae34d7b070e9697f93c5d52d"}, 174 | {file = "cffi-1.15.1-cp311-cp311-win_amd64.whl", hash = "sha256:04ed324bda3cda42b9b695d51bb7d54b680b9719cfab04227cdd1e04e5de3104"}, 175 | {file = "cffi-1.15.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50a74364d85fd319352182ef59c5c790484a336f6db772c1a9231f1c3ed0cbd7"}, 176 | {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e263d77ee3dd201c3a142934a086a4450861778baaeeb45db4591ef65550b0a6"}, 177 | {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cec7d9412a9102bdc577382c3929b337320c4c4c4849f2c5cdd14d7368c5562d"}, 178 | {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4289fc34b2f5316fbb762d75362931e351941fa95fa18789191b33fc4cf9504a"}, 179 | {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:173379135477dc8cac4bc58f45db08ab45d228b3363adb7af79436135d028405"}, 180 | {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:6975a3fac6bc83c4a65c9f9fcab9e47019a11d3d2cf7f3c0d03431bf145a941e"}, 181 | {file = "cffi-1.15.1-cp36-cp36m-win32.whl", hash = "sha256:2470043b93ff09bf8fb1d46d1cb756ce6132c54826661a32d4e4d132e1977adf"}, 182 | {file = "cffi-1.15.1-cp36-cp36m-win_amd64.whl", hash = "sha256:30d78fbc8ebf9c92c9b7823ee18eb92f2e6ef79b45ac84db507f52fbe3ec4497"}, 183 | {file = "cffi-1.15.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:198caafb44239b60e252492445da556afafc7d1e3ab7a1fb3f0584ef6d742375"}, 184 | {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5ef34d190326c3b1f822a5b7a45f6c4535e2f47ed06fec77d3d799c450b2651e"}, 185 | {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8102eaf27e1e448db915d08afa8b41d6c7ca7a04b7d73af6514df10a3e74bd82"}, 186 | {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5df2768244d19ab7f60546d0c7c63ce1581f7af8b5de3eb3004b9b6fc8a9f84b"}, 187 | {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a8c4917bd7ad33e8eb21e9a5bbba979b49d9a97acb3a803092cbc1133e20343c"}, 188 | {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e2642fe3142e4cc4af0799748233ad6da94c62a8bec3a6648bf8ee68b1c7426"}, 189 | {file = "cffi-1.15.1-cp37-cp37m-win32.whl", hash = "sha256:e229a521186c75c8ad9490854fd8bbdd9a0c9aa3a524326b55be83b54d4e0ad9"}, 190 | {file = "cffi-1.15.1-cp37-cp37m-win_amd64.whl", hash = "sha256:a0b71b1b8fbf2b96e41c4d990244165e2c9be83d54962a9a1d118fd8657d2045"}, 191 | {file = "cffi-1.15.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:320dab6e7cb2eacdf0e658569d2575c4dad258c0fcc794f46215e1e39f90f2c3"}, 192 | {file = "cffi-1.15.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e74c6b51a9ed6589199c787bf5f9875612ca4a8a0785fb2d4a84429badaf22a"}, 193 | {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5c84c68147988265e60416b57fc83425a78058853509c1b0629c180094904a5"}, 194 | {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3b926aa83d1edb5aa5b427b4053dc420ec295a08e40911296b9eb1b6170f6cca"}, 195 | {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:87c450779d0914f2861b8526e035c5e6da0a3199d8f1add1a665e1cbc6fc6d02"}, 196 | {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f2c9f67e9821cad2e5f480bc8d83b8742896f1242dba247911072d4fa94c192"}, 197 | {file = "cffi-1.15.1-cp38-cp38-win32.whl", hash = "sha256:8b7ee99e510d7b66cdb6c593f21c043c248537a32e0bedf02e01e9553a172314"}, 198 | {file = "cffi-1.15.1-cp38-cp38-win_amd64.whl", hash = "sha256:00a9ed42e88df81ffae7a8ab6d9356b371399b91dbdf0c3cb1e84c03a13aceb5"}, 199 | {file = "cffi-1.15.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:54a2db7b78338edd780e7ef7f9f6c442500fb0d41a5a4ea24fff1c929d5af585"}, 200 | {file = "cffi-1.15.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:fcd131dd944808b5bdb38e6f5b53013c5aa4f334c5cad0c72742f6eba4b73db0"}, 201 | {file = "cffi-1.15.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7473e861101c9e72452f9bf8acb984947aa1661a7704553a9f6e4baa5ba64415"}, 202 | {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c9a799e985904922a4d207a94eae35c78ebae90e128f0c4e521ce339396be9d"}, 203 | {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3bcde07039e586f91b45c88f8583ea7cf7a0770df3a1649627bf598332cb6984"}, 204 | {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:33ab79603146aace82c2427da5ca6e58f2b3f2fb5da893ceac0c42218a40be35"}, 205 | {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d598b938678ebf3c67377cdd45e09d431369c3b1a5b331058c338e201f12b27"}, 206 | {file = "cffi-1.15.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:db0fbb9c62743ce59a9ff687eb5f4afbe77e5e8403d6697f7446e5f609976f76"}, 207 | {file = "cffi-1.15.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:98d85c6a2bef81588d9227dde12db8a7f47f639f4a17c9ae08e773aa9c697bf3"}, 208 | {file = "cffi-1.15.1-cp39-cp39-win32.whl", hash = "sha256:40f4774f5a9d4f5e344f31a32b5096977b5d48560c5592e2f3d2c4374bd543ee"}, 209 | {file = "cffi-1.15.1-cp39-cp39-win_amd64.whl", hash = "sha256:70df4e3b545a17496c9b3f41f5115e69a4f2e77e94e1d2a8e1070bc0c38c8a3c"}, 210 | {file = "cffi-1.15.1.tar.gz", hash = "sha256:d400bfb9a37b1351253cb402671cea7e89bdecc294e8016a707f6d1d8ac934f9"}, 211 | ] 212 | 213 | [package.dependencies] 214 | pycparser = "*" 215 | 216 | [[package]] 217 | name = "cfgv" 218 | version = "3.3.1" 219 | description = "Validate configuration and produce human readable error messages." 220 | optional = false 221 | python-versions = ">=3.6.1" 222 | files = [ 223 | {file = "cfgv-3.3.1-py2.py3-none-any.whl", hash = "sha256:c6a0883f3917a037485059700b9e75da2464e6c27051014ad85ba6aaa5884426"}, 224 | {file = "cfgv-3.3.1.tar.gz", hash = "sha256:f5a830efb9ce7a445376bb66ec94c638a9787422f96264c98edc6bdeed8ab736"}, 225 | ] 226 | 227 | [[package]] 228 | name = "charset-normalizer" 229 | version = "3.0.1" 230 | description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." 231 | optional = false 232 | python-versions = "*" 233 | files = [ 234 | {file = "charset-normalizer-3.0.1.tar.gz", hash = "sha256:ebea339af930f8ca5d7a699b921106c6e29c617fe9606fa7baa043c1cdae326f"}, 235 | {file = "charset_normalizer-3.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:88600c72ef7587fe1708fd242b385b6ed4b8904976d5da0893e31df8b3480cb6"}, 236 | {file = "charset_normalizer-3.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c75ffc45f25324e68ab238cb4b5c0a38cd1c3d7f1fb1f72b5541de469e2247db"}, 237 | {file = "charset_normalizer-3.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:db72b07027db150f468fbada4d85b3b2729a3db39178abf5c543b784c1254539"}, 238 | {file = "charset_normalizer-3.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:62595ab75873d50d57323a91dd03e6966eb79c41fa834b7a1661ed043b2d404d"}, 239 | {file = "charset_normalizer-3.0.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ff6f3db31555657f3163b15a6b7c6938d08df7adbfc9dd13d9d19edad678f1e8"}, 240 | {file = "charset_normalizer-3.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:772b87914ff1152b92a197ef4ea40efe27a378606c39446ded52c8f80f79702e"}, 241 | {file = "charset_normalizer-3.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70990b9c51340e4044cfc394a81f614f3f90d41397104d226f21e66de668730d"}, 242 | {file = "charset_normalizer-3.0.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:292d5e8ba896bbfd6334b096e34bffb56161c81408d6d036a7dfa6929cff8783"}, 243 | {file = "charset_normalizer-3.0.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:2edb64ee7bf1ed524a1da60cdcd2e1f6e2b4f66ef7c077680739f1641f62f555"}, 244 | {file = "charset_normalizer-3.0.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:31a9ddf4718d10ae04d9b18801bd776693487cbb57d74cc3458a7673f6f34639"}, 245 | {file = "charset_normalizer-3.0.1-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:44ba614de5361b3e5278e1241fda3dc1838deed864b50a10d7ce92983797fa76"}, 246 | {file = "charset_normalizer-3.0.1-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:12db3b2c533c23ab812c2b25934f60383361f8a376ae272665f8e48b88e8e1c6"}, 247 | {file = "charset_normalizer-3.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c512accbd6ff0270939b9ac214b84fb5ada5f0409c44298361b2f5e13f9aed9e"}, 248 | {file = "charset_normalizer-3.0.1-cp310-cp310-win32.whl", hash = "sha256:502218f52498a36d6bf5ea77081844017bf7982cdbe521ad85e64cabee1b608b"}, 249 | {file = "charset_normalizer-3.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:601f36512f9e28f029d9481bdaf8e89e5148ac5d89cffd3b05cd533eeb423b59"}, 250 | {file = "charset_normalizer-3.0.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0298eafff88c99982a4cf66ba2efa1128e4ddaca0b05eec4c456bbc7db691d8d"}, 251 | {file = "charset_normalizer-3.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a8d0fc946c784ff7f7c3742310cc8a57c5c6dc31631269876a88b809dbeff3d3"}, 252 | {file = "charset_normalizer-3.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:87701167f2a5c930b403e9756fab1d31d4d4da52856143b609e30a1ce7160f3c"}, 253 | {file = "charset_normalizer-3.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:14e76c0f23218b8f46c4d87018ca2e441535aed3632ca134b10239dfb6dadd6b"}, 254 | {file = "charset_normalizer-3.0.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0c0a590235ccd933d9892c627dec5bc7511ce6ad6c1011fdf5b11363022746c1"}, 255 | {file = "charset_normalizer-3.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8c7fe7afa480e3e82eed58e0ca89f751cd14d767638e2550c77a92a9e749c317"}, 256 | {file = "charset_normalizer-3.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:79909e27e8e4fcc9db4addea88aa63f6423ebb171db091fb4373e3312cb6d603"}, 257 | {file = "charset_normalizer-3.0.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8ac7b6a045b814cf0c47f3623d21ebd88b3e8cf216a14790b455ea7ff0135d18"}, 258 | {file = "charset_normalizer-3.0.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:72966d1b297c741541ca8cf1223ff262a6febe52481af742036a0b296e35fa5a"}, 259 | {file = "charset_normalizer-3.0.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:f9d0c5c045a3ca9bedfc35dca8526798eb91a07aa7a2c0fee134c6c6f321cbd7"}, 260 | {file = "charset_normalizer-3.0.1-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:5995f0164fa7df59db4746112fec3f49c461dd6b31b841873443bdb077c13cfc"}, 261 | {file = "charset_normalizer-3.0.1-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4a8fcf28c05c1f6d7e177a9a46a1c52798bfe2ad80681d275b10dcf317deaf0b"}, 262 | {file = "charset_normalizer-3.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:761e8904c07ad053d285670f36dd94e1b6ab7f16ce62b9805c475b7aa1cffde6"}, 263 | {file = "charset_normalizer-3.0.1-cp311-cp311-win32.whl", hash = "sha256:71140351489970dfe5e60fc621ada3e0f41104a5eddaca47a7acb3c1b851d6d3"}, 264 | {file = "charset_normalizer-3.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:9ab77acb98eba3fd2a85cd160851816bfce6871d944d885febf012713f06659c"}, 265 | {file = "charset_normalizer-3.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:84c3990934bae40ea69a82034912ffe5a62c60bbf6ec5bc9691419641d7d5c9a"}, 266 | {file = "charset_normalizer-3.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:74292fc76c905c0ef095fe11e188a32ebd03bc38f3f3e9bcb85e4e6db177b7ea"}, 267 | {file = "charset_normalizer-3.0.1-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c95a03c79bbe30eec3ec2b7f076074f4281526724c8685a42872974ef4d36b72"}, 268 | {file = "charset_normalizer-3.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f4c39b0e3eac288fedc2b43055cfc2ca7a60362d0e5e87a637beac5d801ef478"}, 269 | {file = "charset_normalizer-3.0.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:df2c707231459e8a4028eabcd3cfc827befd635b3ef72eada84ab13b52e1574d"}, 270 | {file = "charset_normalizer-3.0.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:93ad6d87ac18e2a90b0fe89df7c65263b9a99a0eb98f0a3d2e079f12a0735837"}, 271 | {file = "charset_normalizer-3.0.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:59e5686dd847347e55dffcc191a96622f016bc0ad89105e24c14e0d6305acbc6"}, 272 | {file = "charset_normalizer-3.0.1-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:cd6056167405314a4dc3c173943f11249fa0f1b204f8b51ed4bde1a9cd1834dc"}, 273 | {file = "charset_normalizer-3.0.1-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:083c8d17153ecb403e5e1eb76a7ef4babfc2c48d58899c98fcaa04833e7a2f9a"}, 274 | {file = "charset_normalizer-3.0.1-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:f5057856d21e7586765171eac8b9fc3f7d44ef39425f85dbcccb13b3ebea806c"}, 275 | {file = "charset_normalizer-3.0.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:7eb33a30d75562222b64f569c642ff3dc6689e09adda43a082208397f016c39a"}, 276 | {file = "charset_normalizer-3.0.1-cp36-cp36m-win32.whl", hash = "sha256:95dea361dd73757c6f1c0a1480ac499952c16ac83f7f5f4f84f0658a01b8ef41"}, 277 | {file = "charset_normalizer-3.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:eaa379fcd227ca235d04152ca6704c7cb55564116f8bc52545ff357628e10602"}, 278 | {file = "charset_normalizer-3.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:3e45867f1f2ab0711d60c6c71746ac53537f1684baa699f4f668d4c6f6ce8e14"}, 279 | {file = "charset_normalizer-3.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cadaeaba78750d58d3cc6ac4d1fd867da6fc73c88156b7a3212a3cd4819d679d"}, 280 | {file = "charset_normalizer-3.0.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:911d8a40b2bef5b8bbae2e36a0b103f142ac53557ab421dc16ac4aafee6f53dc"}, 281 | {file = "charset_normalizer-3.0.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:503e65837c71b875ecdd733877d852adbc465bd82c768a067badd953bf1bc5a3"}, 282 | {file = "charset_normalizer-3.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a60332922359f920193b1d4826953c507a877b523b2395ad7bc716ddd386d866"}, 283 | {file = "charset_normalizer-3.0.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:16a8663d6e281208d78806dbe14ee9903715361cf81f6d4309944e4d1e59ac5b"}, 284 | {file = "charset_normalizer-3.0.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:a16418ecf1329f71df119e8a65f3aa68004a3f9383821edcb20f0702934d8087"}, 285 | {file = "charset_normalizer-3.0.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:9d9153257a3f70d5f69edf2325357251ed20f772b12e593f3b3377b5f78e7ef8"}, 286 | {file = "charset_normalizer-3.0.1-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:02a51034802cbf38db3f89c66fb5d2ec57e6fe7ef2f4a44d070a593c3688667b"}, 287 | {file = "charset_normalizer-3.0.1-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:2e396d70bc4ef5325b72b593a72c8979999aa52fb8bcf03f701c1b03e1166918"}, 288 | {file = "charset_normalizer-3.0.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:11b53acf2411c3b09e6af37e4b9005cba376c872503c8f28218c7243582df45d"}, 289 | {file = "charset_normalizer-3.0.1-cp37-cp37m-win32.whl", hash = "sha256:0bf2dae5291758b6f84cf923bfaa285632816007db0330002fa1de38bfcb7154"}, 290 | {file = "charset_normalizer-3.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:2c03cc56021a4bd59be889c2b9257dae13bf55041a3372d3295416f86b295fb5"}, 291 | {file = "charset_normalizer-3.0.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:024e606be3ed92216e2b6952ed859d86b4cfa52cd5bc5f050e7dc28f9b43ec42"}, 292 | {file = "charset_normalizer-3.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:4b0d02d7102dd0f997580b51edc4cebcf2ab6397a7edf89f1c73b586c614272c"}, 293 | {file = "charset_normalizer-3.0.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:358a7c4cb8ba9b46c453b1dd8d9e431452d5249072e4f56cfda3149f6ab1405e"}, 294 | {file = "charset_normalizer-3.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:81d6741ab457d14fdedc215516665050f3822d3e56508921cc7239f8c8e66a58"}, 295 | {file = "charset_normalizer-3.0.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8b8af03d2e37866d023ad0ddea594edefc31e827fee64f8de5611a1dbc373174"}, 296 | {file = "charset_normalizer-3.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9cf4e8ad252f7c38dd1f676b46514f92dc0ebeb0db5552f5f403509705e24753"}, 297 | {file = "charset_normalizer-3.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e696f0dd336161fca9adbb846875d40752e6eba585843c768935ba5c9960722b"}, 298 | {file = "charset_normalizer-3.0.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c22d3fe05ce11d3671297dc8973267daa0f938b93ec716e12e0f6dee81591dc1"}, 299 | {file = "charset_normalizer-3.0.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:109487860ef6a328f3eec66f2bf78b0b72400280d8f8ea05f69c51644ba6521a"}, 300 | {file = "charset_normalizer-3.0.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:37f8febc8ec50c14f3ec9637505f28e58d4f66752207ea177c1d67df25da5aed"}, 301 | {file = "charset_normalizer-3.0.1-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:f97e83fa6c25693c7a35de154681fcc257c1c41b38beb0304b9c4d2d9e164479"}, 302 | {file = "charset_normalizer-3.0.1-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:a152f5f33d64a6be73f1d30c9cc82dfc73cec6477ec268e7c6e4c7d23c2d2291"}, 303 | {file = "charset_normalizer-3.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:39049da0ffb96c8cbb65cbf5c5f3ca3168990adf3551bd1dee10c48fce8ae820"}, 304 | {file = "charset_normalizer-3.0.1-cp38-cp38-win32.whl", hash = "sha256:4457ea6774b5611f4bed5eaa5df55f70abde42364d498c5134b7ef4c6958e20e"}, 305 | {file = "charset_normalizer-3.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:e62164b50f84e20601c1ff8eb55620d2ad25fb81b59e3cd776a1902527a788af"}, 306 | {file = "charset_normalizer-3.0.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:8eade758719add78ec36dc13201483f8e9b5d940329285edcd5f70c0a9edbd7f"}, 307 | {file = "charset_normalizer-3.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8499ca8f4502af841f68135133d8258f7b32a53a1d594aa98cc52013fff55678"}, 308 | {file = "charset_normalizer-3.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3fc1c4a2ffd64890aebdb3f97e1278b0cc72579a08ca4de8cd2c04799a3a22be"}, 309 | {file = "charset_normalizer-3.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:00d3ffdaafe92a5dc603cb9bd5111aaa36dfa187c8285c543be562e61b755f6b"}, 310 | {file = "charset_normalizer-3.0.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c2ac1b08635a8cd4e0cbeaf6f5e922085908d48eb05d44c5ae9eabab148512ca"}, 311 | {file = "charset_normalizer-3.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f6f45710b4459401609ebebdbcfb34515da4fc2aa886f95107f556ac69a9147e"}, 312 | {file = "charset_normalizer-3.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ae1de54a77dc0d6d5fcf623290af4266412a7c4be0b1ff7444394f03f5c54e3"}, 313 | {file = "charset_normalizer-3.0.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3b590df687e3c5ee0deef9fc8c547d81986d9a1b56073d82de008744452d6541"}, 314 | {file = "charset_normalizer-3.0.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ab5de034a886f616a5668aa5d098af2b5385ed70142090e2a31bcbd0af0fdb3d"}, 315 | {file = "charset_normalizer-3.0.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9cb3032517f1627cc012dbc80a8ec976ae76d93ea2b5feaa9d2a5b8882597579"}, 316 | {file = "charset_normalizer-3.0.1-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:608862a7bf6957f2333fc54ab4399e405baad0163dc9f8d99cb236816db169d4"}, 317 | {file = "charset_normalizer-3.0.1-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:0f438ae3532723fb6ead77e7c604be7c8374094ef4ee2c5e03a3a17f1fca256c"}, 318 | {file = "charset_normalizer-3.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:356541bf4381fa35856dafa6a965916e54bed415ad8a24ee6de6e37deccf2786"}, 319 | {file = "charset_normalizer-3.0.1-cp39-cp39-win32.whl", hash = "sha256:39cf9ed17fe3b1bc81f33c9ceb6ce67683ee7526e65fde1447c772afc54a1bb8"}, 320 | {file = "charset_normalizer-3.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:0a11e971ed097d24c534c037d298ad32c6ce81a45736d31e0ff0ad37ab437d59"}, 321 | {file = "charset_normalizer-3.0.1-py3-none-any.whl", hash = "sha256:7e189e2e1d3ed2f4aebabd2d5b0f931e883676e51c7624826e0a4e5fe8a0bf24"}, 322 | ] 323 | 324 | [[package]] 325 | name = "click" 326 | version = "8.1.3" 327 | description = "Composable command line interface toolkit" 328 | optional = false 329 | python-versions = ">=3.7" 330 | files = [ 331 | {file = "click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"}, 332 | {file = "click-8.1.3.tar.gz", hash = "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e"}, 333 | ] 334 | 335 | [package.dependencies] 336 | colorama = {version = "*", markers = "platform_system == \"Windows\""} 337 | 338 | [[package]] 339 | name = "click-log" 340 | version = "0.4.0" 341 | description = "Logging integration for Click" 342 | optional = false 343 | python-versions = "*" 344 | files = [ 345 | {file = "click-log-0.4.0.tar.gz", hash = "sha256:3970f8570ac54491237bcdb3d8ab5e3eef6c057df29f8c3d1151a51a9c23b975"}, 346 | {file = "click_log-0.4.0-py2.py3-none-any.whl", hash = "sha256:a43e394b528d52112af599f2fc9e4b7cf3c15f94e53581f74fa6867e68c91756"}, 347 | ] 348 | 349 | [package.dependencies] 350 | click = "*" 351 | 352 | [[package]] 353 | name = "colorama" 354 | version = "0.4.6" 355 | description = "Cross-platform colored terminal text." 356 | optional = false 357 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" 358 | files = [ 359 | {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, 360 | {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, 361 | ] 362 | 363 | [[package]] 364 | name = "cryptography" 365 | version = "39.0.1" 366 | description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." 367 | optional = false 368 | python-versions = ">=3.6" 369 | files = [ 370 | {file = "cryptography-39.0.1-cp36-abi3-macosx_10_12_universal2.whl", hash = "sha256:6687ef6d0a6497e2b58e7c5b852b53f62142cfa7cd1555795758934da363a965"}, 371 | {file = "cryptography-39.0.1-cp36-abi3-macosx_10_12_x86_64.whl", hash = "sha256:706843b48f9a3f9b9911979761c91541e3d90db1ca905fd63fee540a217698bc"}, 372 | {file = "cryptography-39.0.1-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:5d2d8b87a490bfcd407ed9d49093793d0f75198a35e6eb1a923ce1ee86c62b41"}, 373 | {file = "cryptography-39.0.1-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:83e17b26de248c33f3acffb922748151d71827d6021d98c70e6c1a25ddd78505"}, 374 | {file = "cryptography-39.0.1-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e124352fd3db36a9d4a21c1aa27fd5d051e621845cb87fb851c08f4f75ce8be6"}, 375 | {file = "cryptography-39.0.1-cp36-abi3-manylinux_2_24_x86_64.whl", hash = "sha256:5aa67414fcdfa22cf052e640cb5ddc461924a045cacf325cd164e65312d99502"}, 376 | {file = "cryptography-39.0.1-cp36-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:35f7c7d015d474f4011e859e93e789c87d21f6f4880ebdc29896a60403328f1f"}, 377 | {file = "cryptography-39.0.1-cp36-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:f24077a3b5298a5a06a8e0536e3ea9ec60e4c7ac486755e5fb6e6ea9b3500106"}, 378 | {file = "cryptography-39.0.1-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:f0c64d1bd842ca2633e74a1a28033d139368ad959872533b1bab8c80e8240a0c"}, 379 | {file = "cryptography-39.0.1-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:0f8da300b5c8af9f98111ffd512910bc792b4c77392a9523624680f7956a99d4"}, 380 | {file = "cryptography-39.0.1-cp36-abi3-win32.whl", hash = "sha256:fe913f20024eb2cb2f323e42a64bdf2911bb9738a15dba7d3cce48151034e3a8"}, 381 | {file = "cryptography-39.0.1-cp36-abi3-win_amd64.whl", hash = "sha256:ced4e447ae29ca194449a3f1ce132ded8fcab06971ef5f618605aacaa612beac"}, 382 | {file = "cryptography-39.0.1-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:807ce09d4434881ca3a7594733669bd834f5b2c6d5c7e36f8c00f691887042ad"}, 383 | {file = "cryptography-39.0.1-pp38-pypy38_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:c5caeb8188c24888c90b5108a441c106f7faa4c4c075a2bcae438c6e8ca73cef"}, 384 | {file = "cryptography-39.0.1-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:4789d1e3e257965e960232345002262ede4d094d1a19f4d3b52e48d4d8f3b885"}, 385 | {file = "cryptography-39.0.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:96f1157a7c08b5b189b16b47bc9db2332269d6680a196341bf30046330d15388"}, 386 | {file = "cryptography-39.0.1-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:e422abdec8b5fa8462aa016786680720d78bdce7a30c652b7fadf83a4ba35336"}, 387 | {file = "cryptography-39.0.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:b0afd054cd42f3d213bf82c629efb1ee5f22eba35bf0eec88ea9ea7304f511a2"}, 388 | {file = "cryptography-39.0.1-pp39-pypy39_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:6f8ba7f0328b79f08bdacc3e4e66fb4d7aab0c3584e0bd41328dce5262e26b2e"}, 389 | {file = "cryptography-39.0.1-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:ef8b72fa70b348724ff1218267e7f7375b8de4e8194d1636ee60510aae104cd0"}, 390 | {file = "cryptography-39.0.1-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:aec5a6c9864be7df2240c382740fcf3b96928c46604eaa7f3091f58b878c0bb6"}, 391 | {file = "cryptography-39.0.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:fdd188c8a6ef8769f148f88f859884507b954cc64db6b52f66ef199bb9ad660a"}, 392 | {file = "cryptography-39.0.1.tar.gz", hash = "sha256:d1f6198ee6d9148405e49887803907fe8962a23e6c6f83ea7d98f1c0de375695"}, 393 | ] 394 | 395 | [package.dependencies] 396 | cffi = ">=1.12" 397 | 398 | [package.extras] 399 | docs = ["sphinx (>=5.3.0)", "sphinx-rtd-theme (>=1.1.1)"] 400 | docstest = ["pyenchant (>=1.6.11)", "sphinxcontrib-spelling (>=4.0.1)", "twine (>=1.12.0)"] 401 | pep8test = ["black", "check-manifest", "mypy", "ruff", "types-pytz", "types-requests"] 402 | sdist = ["setuptools-rust (>=0.11.4)"] 403 | ssh = ["bcrypt (>=3.1.5)"] 404 | test = ["hypothesis (>=1.11.4,!=3.79.2)", "iso8601", "pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-shard (>=0.1.2)", "pytest-subtests", "pytest-xdist", "pytz"] 405 | test-randomorder = ["pytest-randomly"] 406 | tox = ["tox"] 407 | 408 | [[package]] 409 | name = "decorator" 410 | version = "5.1.1" 411 | description = "Decorators for Humans" 412 | optional = false 413 | python-versions = ">=3.5" 414 | files = [ 415 | {file = "decorator-5.1.1-py3-none-any.whl", hash = "sha256:b8c3f85900b9dc423225913c5aace94729fe1fa9763b38939a95226f02d37186"}, 416 | {file = "decorator-5.1.1.tar.gz", hash = "sha256:637996211036b6385ef91435e4fae22989472f9d571faba8927ba8253acbc330"}, 417 | ] 418 | 419 | [[package]] 420 | name = "deprecated" 421 | version = "1.2.13" 422 | description = "Python @deprecated decorator to deprecate old python classes, functions or methods." 423 | optional = false 424 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 425 | files = [ 426 | {file = "Deprecated-1.2.13-py2.py3-none-any.whl", hash = "sha256:64756e3e14c8c5eea9795d93c524551432a0be75629f8f29e67ab8caf076c76d"}, 427 | {file = "Deprecated-1.2.13.tar.gz", hash = "sha256:43ac5335da90c31c24ba028af536a91d41d53f9e6901ddb021bcc572ce44e38d"}, 428 | ] 429 | 430 | [package.dependencies] 431 | wrapt = ">=1.10,<2" 432 | 433 | [package.extras] 434 | dev = ["PyTest", "PyTest (<5)", "PyTest-Cov", "PyTest-Cov (<2.6)", "bump2version (<1)", "configparser (<5)", "importlib-metadata (<3)", "importlib-resources (<4)", "sphinx (<2)", "sphinxcontrib-websupport (<2)", "tox", "zipp (<2)"] 435 | 436 | [[package]] 437 | name = "distlib" 438 | version = "0.3.6" 439 | description = "Distribution utilities" 440 | optional = false 441 | python-versions = "*" 442 | files = [ 443 | {file = "distlib-0.3.6-py2.py3-none-any.whl", hash = "sha256:f35c4b692542ca110de7ef0bea44d73981caeb34ca0b9b6b2e6d7790dda8f80e"}, 444 | {file = "distlib-0.3.6.tar.gz", hash = "sha256:14bad2d9b04d3a36127ac97f30b12a19268f211063d8f8ee4f47108896e11b46"}, 445 | ] 446 | 447 | [[package]] 448 | name = "docutils" 449 | version = "0.19" 450 | description = "Docutils -- Python Documentation Utilities" 451 | optional = false 452 | python-versions = ">=3.7" 453 | files = [ 454 | {file = "docutils-0.19-py3-none-any.whl", hash = "sha256:5e1de4d849fee02c63b040a4a3fd567f4ab104defd8a5511fbbc24a8a017efbc"}, 455 | {file = "docutils-0.19.tar.gz", hash = "sha256:33995a6753c30b7f577febfc2c50411fec6aac7f7ffeb7c4cfe5991072dcf9e6"}, 456 | ] 457 | 458 | [[package]] 459 | name = "dotty-dict" 460 | version = "1.3.1" 461 | description = "Dictionary wrapper for quick access to deeply nested keys." 462 | optional = false 463 | python-versions = ">=3.5,<4.0" 464 | files = [ 465 | {file = "dotty_dict-1.3.1-py3-none-any.whl", hash = "sha256:5022d234d9922f13aa711b4950372a06a6d64cb6d6db9ba43d0ba133ebfce31f"}, 466 | {file = "dotty_dict-1.3.1.tar.gz", hash = "sha256:4b016e03b8ae265539757a53eba24b9bfda506fb94fbce0bee843c6f05541a15"}, 467 | ] 468 | 469 | [[package]] 470 | name = "fabric" 471 | version = "3.2.2" 472 | description = "High level SSH command execution" 473 | optional = false 474 | python-versions = "*" 475 | files = [ 476 | {file = "fabric-3.2.2-py3-none-any.whl", hash = "sha256:91c47c0be68b14936c88b34da8a1f55e5710fd28397dac5d4ff2e21558113a6f"}, 477 | {file = "fabric-3.2.2.tar.gz", hash = "sha256:8783ca42e3b0076f08b26901aac6b9d9b1f19c410074e7accfab902c184ff4a3"}, 478 | ] 479 | 480 | [package.dependencies] 481 | decorator = ">=5" 482 | deprecated = ">=1.2" 483 | invoke = ">=2.0" 484 | paramiko = ">=2.4" 485 | 486 | [package.extras] 487 | pytest = ["pytest (>=7)"] 488 | 489 | [[package]] 490 | name = "filelock" 491 | version = "3.9.0" 492 | description = "A platform independent file lock." 493 | optional = false 494 | python-versions = ">=3.7" 495 | files = [ 496 | {file = "filelock-3.9.0-py3-none-any.whl", hash = "sha256:f58d535af89bb9ad5cd4df046f741f8553a418c01a7856bf0d173bbc9f6bd16d"}, 497 | {file = "filelock-3.9.0.tar.gz", hash = "sha256:7b319f24340b51f55a2bf7a12ac0755a9b03e718311dac567a0f4f7fabd2f5de"}, 498 | ] 499 | 500 | [package.extras] 501 | docs = ["furo (>=2022.12.7)", "sphinx (>=5.3)", "sphinx-autodoc-typehints (>=1.19.5)"] 502 | testing = ["covdefaults (>=2.2.2)", "coverage (>=7.0.1)", "pytest (>=7.2)", "pytest-cov (>=4)", "pytest-timeout (>=2.1)"] 503 | 504 | [[package]] 505 | name = "flake8" 506 | version = "6.0.0" 507 | description = "the modular source code checker: pep8 pyflakes and co" 508 | optional = false 509 | python-versions = ">=3.8.1" 510 | files = [ 511 | {file = "flake8-6.0.0-py2.py3-none-any.whl", hash = "sha256:3833794e27ff64ea4e9cf5d410082a8b97ff1a06c16aa3d2027339cd0f1195c7"}, 512 | {file = "flake8-6.0.0.tar.gz", hash = "sha256:c61007e76655af75e6785a931f452915b371dc48f56efd765247c8fe68f2b181"}, 513 | ] 514 | 515 | [package.dependencies] 516 | mccabe = ">=0.7.0,<0.8.0" 517 | pycodestyle = ">=2.10.0,<2.11.0" 518 | pyflakes = ">=3.0.0,<3.1.0" 519 | 520 | [[package]] 521 | name = "gitdb" 522 | version = "4.0.10" 523 | description = "Git Object Database" 524 | optional = false 525 | python-versions = ">=3.7" 526 | files = [ 527 | {file = "gitdb-4.0.10-py3-none-any.whl", hash = "sha256:c286cf298426064079ed96a9e4a9d39e7f3e9bf15ba60701e95f5492f28415c7"}, 528 | {file = "gitdb-4.0.10.tar.gz", hash = "sha256:6eb990b69df4e15bad899ea868dc46572c3f75339735663b81de79b06f17eb9a"}, 529 | ] 530 | 531 | [package.dependencies] 532 | smmap = ">=3.0.1,<6" 533 | 534 | [[package]] 535 | name = "gitpython" 536 | version = "3.1.30" 537 | description = "GitPython is a python library used to interact with Git repositories" 538 | optional = false 539 | python-versions = ">=3.7" 540 | files = [ 541 | {file = "GitPython-3.1.30-py3-none-any.whl", hash = "sha256:cd455b0000615c60e286208ba540271af9fe531fa6a87cc590a7298785ab2882"}, 542 | {file = "GitPython-3.1.30.tar.gz", hash = "sha256:769c2d83e13f5d938b7688479da374c4e3d49f71549aaf462b646db9602ea6f8"}, 543 | ] 544 | 545 | [package.dependencies] 546 | gitdb = ">=4.0.1,<5" 547 | 548 | [[package]] 549 | name = "identify" 550 | version = "2.5.17" 551 | description = "File identification library for Python" 552 | optional = false 553 | python-versions = ">=3.7" 554 | files = [ 555 | {file = "identify-2.5.17-py2.py3-none-any.whl", hash = "sha256:7d526dd1283555aafcc91539acc061d8f6f59adb0a7bba462735b0a318bff7ed"}, 556 | {file = "identify-2.5.17.tar.gz", hash = "sha256:93cc61a861052de9d4c541a7acb7e3dcc9c11b398a2144f6e52ae5285f5f4f06"}, 557 | ] 558 | 559 | [package.extras] 560 | license = ["ukkonen"] 561 | 562 | [[package]] 563 | name = "idna" 564 | version = "3.4" 565 | description = "Internationalized Domain Names in Applications (IDNA)" 566 | optional = false 567 | python-versions = ">=3.5" 568 | files = [ 569 | {file = "idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"}, 570 | {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"}, 571 | ] 572 | 573 | [[package]] 574 | name = "importlib-metadata" 575 | version = "6.0.0" 576 | description = "Read metadata from Python packages" 577 | optional = false 578 | python-versions = ">=3.7" 579 | files = [ 580 | {file = "importlib_metadata-6.0.0-py3-none-any.whl", hash = "sha256:7efb448ec9a5e313a57655d35aa54cd3e01b7e1fbcf72dce1bf06119420f5bad"}, 581 | {file = "importlib_metadata-6.0.0.tar.gz", hash = "sha256:e354bedeb60efa6affdcc8ae121b73544a7aa74156d047311948f6d711cd378d"}, 582 | ] 583 | 584 | [package.dependencies] 585 | zipp = ">=0.5" 586 | 587 | [package.extras] 588 | docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] 589 | perf = ["ipython"] 590 | testing = ["flake8 (<5)", "flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)"] 591 | 592 | [[package]] 593 | name = "iniconfig" 594 | version = "2.0.0" 595 | description = "brain-dead simple config-ini parsing" 596 | optional = false 597 | python-versions = ">=3.7" 598 | files = [ 599 | {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, 600 | {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, 601 | ] 602 | 603 | [[package]] 604 | name = "invoke" 605 | version = "2.2.0" 606 | description = "Pythonic task execution" 607 | optional = false 608 | python-versions = ">=3.6" 609 | files = [ 610 | {file = "invoke-2.2.0-py3-none-any.whl", hash = "sha256:6ea924cc53d4f78e3d98bc436b08069a03077e6f85ad1ddaa8a116d7dad15820"}, 611 | {file = "invoke-2.2.0.tar.gz", hash = "sha256:ee6cbb101af1a859c7fe84f2a264c059020b0cb7fe3535f9424300ab568f6bd5"}, 612 | ] 613 | 614 | [[package]] 615 | name = "isort" 616 | version = "5.12.0" 617 | description = "A Python utility / library to sort Python imports." 618 | optional = false 619 | python-versions = ">=3.8.0" 620 | files = [ 621 | {file = "isort-5.12.0-py3-none-any.whl", hash = "sha256:f84c2818376e66cf843d497486ea8fed8700b340f308f076c6fb1229dff318b6"}, 622 | {file = "isort-5.12.0.tar.gz", hash = "sha256:8bef7dde241278824a6d83f44a544709b065191b95b6e50894bdc722fcba0504"}, 623 | ] 624 | 625 | [package.extras] 626 | colors = ["colorama (>=0.4.3)"] 627 | pipfile-deprecated-finder = ["pip-shims (>=0.5.2)", "pipreqs", "requirementslib"] 628 | plugins = ["setuptools"] 629 | requirements-deprecated-finder = ["pip-api", "pipreqs"] 630 | 631 | [[package]] 632 | name = "jaraco-classes" 633 | version = "3.2.3" 634 | description = "Utility functions for Python class constructs" 635 | optional = false 636 | python-versions = ">=3.7" 637 | files = [ 638 | {file = "jaraco.classes-3.2.3-py3-none-any.whl", hash = "sha256:2353de3288bc6b82120752201c6b1c1a14b058267fa424ed5ce5984e3b922158"}, 639 | {file = "jaraco.classes-3.2.3.tar.gz", hash = "sha256:89559fa5c1d3c34eff6f631ad80bb21f378dbcbb35dd161fd2c6b93f5be2f98a"}, 640 | ] 641 | 642 | [package.dependencies] 643 | more-itertools = "*" 644 | 645 | [package.extras] 646 | docs = ["jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)"] 647 | testing = ["flake8 (<5)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] 648 | 649 | [[package]] 650 | name = "jeepney" 651 | version = "0.8.0" 652 | description = "Low-level, pure Python DBus protocol wrapper." 653 | optional = false 654 | python-versions = ">=3.7" 655 | files = [ 656 | {file = "jeepney-0.8.0-py3-none-any.whl", hash = "sha256:c0a454ad016ca575060802ee4d590dd912e35c122fa04e70306de3d076cce755"}, 657 | {file = "jeepney-0.8.0.tar.gz", hash = "sha256:5efe48d255973902f6badc3ce55e2aa6c5c3b3bc642059ef3a91247bcfcc5806"}, 658 | ] 659 | 660 | [package.extras] 661 | test = ["async-timeout", "pytest", "pytest-asyncio (>=0.17)", "pytest-trio", "testpath", "trio"] 662 | trio = ["async_generator", "trio"] 663 | 664 | [[package]] 665 | name = "keyring" 666 | version = "23.13.1" 667 | description = "Store and access your passwords safely." 668 | optional = false 669 | python-versions = ">=3.7" 670 | files = [ 671 | {file = "keyring-23.13.1-py3-none-any.whl", hash = "sha256:771ed2a91909389ed6148631de678f82ddc73737d85a927f382a8a1b157898cd"}, 672 | {file = "keyring-23.13.1.tar.gz", hash = "sha256:ba2e15a9b35e21908d0aaf4e0a47acc52d6ae33444df0da2b49d41a46ef6d678"}, 673 | ] 674 | 675 | [package.dependencies] 676 | importlib-metadata = {version = ">=4.11.4", markers = "python_version < \"3.12\""} 677 | "jaraco.classes" = "*" 678 | jeepney = {version = ">=0.4.2", markers = "sys_platform == \"linux\""} 679 | pywin32-ctypes = {version = ">=0.2.0", markers = "sys_platform == \"win32\""} 680 | SecretStorage = {version = ">=3.2", markers = "sys_platform == \"linux\""} 681 | 682 | [package.extras] 683 | completion = ["shtab"] 684 | docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)"] 685 | testing = ["flake8 (<5)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] 686 | 687 | [[package]] 688 | name = "markdown-it-py" 689 | version = "2.1.0" 690 | description = "Python port of markdown-it. Markdown parsing, done right!" 691 | optional = false 692 | python-versions = ">=3.7" 693 | files = [ 694 | {file = "markdown-it-py-2.1.0.tar.gz", hash = "sha256:cf7e59fed14b5ae17c0006eff14a2d9a00ed5f3a846148153899a0224e2c07da"}, 695 | {file = "markdown_it_py-2.1.0-py3-none-any.whl", hash = "sha256:93de681e5c021a432c63147656fe21790bc01231e0cd2da73626f1aa3ac0fe27"}, 696 | ] 697 | 698 | [package.dependencies] 699 | mdurl = ">=0.1,<1.0" 700 | 701 | [package.extras] 702 | benchmarking = ["psutil", "pytest", "pytest-benchmark (>=3.2,<4.0)"] 703 | code-style = ["pre-commit (==2.6)"] 704 | compare = ["commonmark (>=0.9.1,<0.10.0)", "markdown (>=3.3.6,<3.4.0)", "mistletoe (>=0.8.1,<0.9.0)", "mistune (>=2.0.2,<2.1.0)", "panflute (>=2.1.3,<2.2.0)"] 705 | linkify = ["linkify-it-py (>=1.0,<2.0)"] 706 | plugins = ["mdit-py-plugins"] 707 | profiling = ["gprof2dot"] 708 | rtd = ["attrs", "myst-parser", "pyyaml", "sphinx", "sphinx-copybutton", "sphinx-design", "sphinx_book_theme"] 709 | testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] 710 | 711 | [[package]] 712 | name = "mccabe" 713 | version = "0.7.0" 714 | description = "McCabe checker, plugin for flake8" 715 | optional = false 716 | python-versions = ">=3.6" 717 | files = [ 718 | {file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"}, 719 | {file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"}, 720 | ] 721 | 722 | [[package]] 723 | name = "mdurl" 724 | version = "0.1.2" 725 | description = "Markdown URL utilities" 726 | optional = false 727 | python-versions = ">=3.7" 728 | files = [ 729 | {file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"}, 730 | {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}, 731 | ] 732 | 733 | [[package]] 734 | name = "more-itertools" 735 | version = "9.0.0" 736 | description = "More routines for operating on iterables, beyond itertools" 737 | optional = false 738 | python-versions = ">=3.7" 739 | files = [ 740 | {file = "more-itertools-9.0.0.tar.gz", hash = "sha256:5a6257e40878ef0520b1803990e3e22303a41b5714006c32a3fd8304b26ea1ab"}, 741 | {file = "more_itertools-9.0.0-py3-none-any.whl", hash = "sha256:250e83d7e81d0c87ca6bd942e6aeab8cc9daa6096d12c5308f3f92fa5e5c1f41"}, 742 | ] 743 | 744 | [[package]] 745 | name = "mypy-extensions" 746 | version = "1.0.0" 747 | description = "Type system extensions for programs checked with the mypy type checker." 748 | optional = false 749 | python-versions = ">=3.5" 750 | files = [ 751 | {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, 752 | {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, 753 | ] 754 | 755 | [[package]] 756 | name = "nodeenv" 757 | version = "1.7.0" 758 | description = "Node.js virtual environment builder" 759 | optional = false 760 | python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*" 761 | files = [ 762 | {file = "nodeenv-1.7.0-py2.py3-none-any.whl", hash = "sha256:27083a7b96a25f2f5e1d8cb4b6317ee8aeda3bdd121394e5ac54e498028a042e"}, 763 | {file = "nodeenv-1.7.0.tar.gz", hash = "sha256:e0e7f7dfb85fc5394c6fe1e8fa98131a2473e04311a45afb6508f7cf1836fa2b"}, 764 | ] 765 | 766 | [package.dependencies] 767 | setuptools = "*" 768 | 769 | [[package]] 770 | name = "packaging" 771 | version = "23.0" 772 | description = "Core utilities for Python packages" 773 | optional = false 774 | python-versions = ">=3.7" 775 | files = [ 776 | {file = "packaging-23.0-py3-none-any.whl", hash = "sha256:714ac14496c3e68c99c29b00845f7a2b85f3bb6f1078fd9f72fd20f0570002b2"}, 777 | {file = "packaging-23.0.tar.gz", hash = "sha256:b6ad297f8907de0fa2fe1ccbd26fdaf387f5f47c7275fedf8cce89f99446cf97"}, 778 | ] 779 | 780 | [[package]] 781 | name = "paramiko" 782 | version = "3.3.1" 783 | description = "SSH2 protocol library" 784 | optional = false 785 | python-versions = ">=3.6" 786 | files = [ 787 | {file = "paramiko-3.3.1-py3-none-any.whl", hash = "sha256:b7bc5340a43de4287bbe22fe6de728aa2c22468b2a849615498dd944c2f275eb"}, 788 | {file = "paramiko-3.3.1.tar.gz", hash = "sha256:6a3777a961ac86dbef375c5f5b8d50014a1a96d0fd7f054a43bc880134b0ff77"}, 789 | ] 790 | 791 | [package.dependencies] 792 | bcrypt = ">=3.2" 793 | cryptography = ">=3.3" 794 | pynacl = ">=1.5" 795 | 796 | [package.extras] 797 | all = ["gssapi (>=1.4.1)", "invoke (>=2.0)", "pyasn1 (>=0.1.7)", "pywin32 (>=2.1.8)"] 798 | gssapi = ["gssapi (>=1.4.1)", "pyasn1 (>=0.1.7)", "pywin32 (>=2.1.8)"] 799 | invoke = ["invoke (>=2.0)"] 800 | 801 | [[package]] 802 | name = "pathspec" 803 | version = "0.11.0" 804 | description = "Utility library for gitignore style pattern matching of file paths." 805 | optional = false 806 | python-versions = ">=3.7" 807 | files = [ 808 | {file = "pathspec-0.11.0-py3-none-any.whl", hash = "sha256:3a66eb970cbac598f9e5ccb5b2cf58930cd8e3ed86d393d541eaf2d8b1705229"}, 809 | {file = "pathspec-0.11.0.tar.gz", hash = "sha256:64d338d4e0914e91c1792321e6907b5a593f1ab1851de7fc269557a21b30ebbc"}, 810 | ] 811 | 812 | [[package]] 813 | name = "pkginfo" 814 | version = "1.9.6" 815 | description = "Query metadata from sdists / bdists / installed packages." 816 | optional = false 817 | python-versions = ">=3.6" 818 | files = [ 819 | {file = "pkginfo-1.9.6-py3-none-any.whl", hash = "sha256:4b7a555a6d5a22169fcc9cf7bfd78d296b0361adad412a346c1226849af5e546"}, 820 | {file = "pkginfo-1.9.6.tar.gz", hash = "sha256:8fd5896e8718a4372f0ea9cc9d96f6417c9b986e23a4d116dda26b62cc29d046"}, 821 | ] 822 | 823 | [package.extras] 824 | testing = ["pytest", "pytest-cov"] 825 | 826 | [[package]] 827 | name = "platformdirs" 828 | version = "3.0.0" 829 | description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." 830 | optional = false 831 | python-versions = ">=3.7" 832 | files = [ 833 | {file = "platformdirs-3.0.0-py3-none-any.whl", hash = "sha256:b1d5eb14f221506f50d6604a561f4c5786d9e80355219694a1b244bcd96f4567"}, 834 | {file = "platformdirs-3.0.0.tar.gz", hash = "sha256:8a1228abb1ef82d788f74139988b137e78692984ec7b08eaa6c65f1723af28f9"}, 835 | ] 836 | 837 | [package.extras] 838 | docs = ["furo (>=2022.12.7)", "proselint (>=0.13)", "sphinx (>=6.1.3)", "sphinx-autodoc-typehints (>=1.22,!=1.23.4)"] 839 | test = ["appdirs (==1.4.4)", "covdefaults (>=2.2.2)", "pytest (>=7.2.1)", "pytest-cov (>=4)", "pytest-mock (>=3.10)"] 840 | 841 | [[package]] 842 | name = "pluggy" 843 | version = "1.0.0" 844 | description = "plugin and hook calling mechanisms for python" 845 | optional = false 846 | python-versions = ">=3.6" 847 | files = [ 848 | {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, 849 | {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"}, 850 | ] 851 | 852 | [package.extras] 853 | dev = ["pre-commit", "tox"] 854 | testing = ["pytest", "pytest-benchmark"] 855 | 856 | [[package]] 857 | name = "pre-commit" 858 | version = "3.0.4" 859 | description = "A framework for managing and maintaining multi-language pre-commit hooks." 860 | optional = false 861 | python-versions = ">=3.8" 862 | files = [ 863 | {file = "pre_commit-3.0.4-py2.py3-none-any.whl", hash = "sha256:9e3255edb0c9e7fe9b4f328cb3dc86069f8fdc38026f1bf521018a05eaf4d67b"}, 864 | {file = "pre_commit-3.0.4.tar.gz", hash = "sha256:bc4687478d55578c4ac37272fe96df66f73d9b5cf81be6f28627d4e712e752d5"}, 865 | ] 866 | 867 | [package.dependencies] 868 | cfgv = ">=2.0.0" 869 | identify = ">=1.0.0" 870 | nodeenv = ">=0.11.1" 871 | pyyaml = ">=5.1" 872 | virtualenv = ">=20.10.0" 873 | 874 | [[package]] 875 | name = "py" 876 | version = "1.11.0" 877 | description = "library with cross-python path, ini-parsing, io, code, log facilities" 878 | optional = false 879 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 880 | files = [ 881 | {file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"}, 882 | {file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"}, 883 | ] 884 | 885 | [[package]] 886 | name = "pycodestyle" 887 | version = "2.10.0" 888 | description = "Python style guide checker" 889 | optional = false 890 | python-versions = ">=3.6" 891 | files = [ 892 | {file = "pycodestyle-2.10.0-py2.py3-none-any.whl", hash = "sha256:8a4eaf0d0495c7395bdab3589ac2db602797d76207242c17d470186815706610"}, 893 | {file = "pycodestyle-2.10.0.tar.gz", hash = "sha256:347187bdb476329d98f695c213d7295a846d1152ff4fe9bacb8a9590b8ee7053"}, 894 | ] 895 | 896 | [[package]] 897 | name = "pycparser" 898 | version = "2.21" 899 | description = "C parser in Python" 900 | optional = false 901 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 902 | files = [ 903 | {file = "pycparser-2.21-py2.py3-none-any.whl", hash = "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9"}, 904 | {file = "pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"}, 905 | ] 906 | 907 | [[package]] 908 | name = "pyflakes" 909 | version = "3.0.1" 910 | description = "passive checker of Python programs" 911 | optional = false 912 | python-versions = ">=3.6" 913 | files = [ 914 | {file = "pyflakes-3.0.1-py2.py3-none-any.whl", hash = "sha256:ec55bf7fe21fff7f1ad2f7da62363d749e2a470500eab1b555334b67aa1ef8cf"}, 915 | {file = "pyflakes-3.0.1.tar.gz", hash = "sha256:ec8b276a6b60bd80defed25add7e439881c19e64850afd9b346283d4165fd0fd"}, 916 | ] 917 | 918 | [[package]] 919 | name = "pygithub" 920 | version = "1.57" 921 | description = "Use the full Github API v3" 922 | optional = false 923 | python-versions = ">=3.7" 924 | files = [ 925 | {file = "PyGithub-1.57-py3-none-any.whl", hash = "sha256:5822febeac2391f1306c55a99af2bc8f86c8bf82ded000030cd02c18f31b731f"}, 926 | {file = "PyGithub-1.57.tar.gz", hash = "sha256:c273f252b278fb81f1769505cc6921bdb6791e1cebd6ac850cc97dad13c31ff3"}, 927 | ] 928 | 929 | [package.dependencies] 930 | deprecated = "*" 931 | pyjwt = ">=2.4.0" 932 | pynacl = ">=1.4.0" 933 | requests = ">=2.14.0" 934 | 935 | [package.extras] 936 | integrations = ["cryptography"] 937 | 938 | [[package]] 939 | name = "pygments" 940 | version = "2.14.0" 941 | description = "Pygments is a syntax highlighting package written in Python." 942 | optional = false 943 | python-versions = ">=3.6" 944 | files = [ 945 | {file = "Pygments-2.14.0-py3-none-any.whl", hash = "sha256:fa7bd7bd2771287c0de303af8bfdfc731f51bd2c6a47ab69d117138893b82717"}, 946 | {file = "Pygments-2.14.0.tar.gz", hash = "sha256:b3ed06a9e8ac9a9aae5a6f5dbe78a8a58655d17b43b93c078f094ddc476ae297"}, 947 | ] 948 | 949 | [package.extras] 950 | plugins = ["importlib-metadata"] 951 | 952 | [[package]] 953 | name = "pyjwt" 954 | version = "2.6.0" 955 | description = "JSON Web Token implementation in Python" 956 | optional = false 957 | python-versions = ">=3.7" 958 | files = [ 959 | {file = "PyJWT-2.6.0-py3-none-any.whl", hash = "sha256:d83c3d892a77bbb74d3e1a2cfa90afaadb60945205d1095d9221f04466f64c14"}, 960 | {file = "PyJWT-2.6.0.tar.gz", hash = "sha256:69285c7e31fc44f68a1feb309e948e0df53259d579295e6cfe2b1792329f05fd"}, 961 | ] 962 | 963 | [package.extras] 964 | crypto = ["cryptography (>=3.4.0)"] 965 | dev = ["coverage[toml] (==5.0.4)", "cryptography (>=3.4.0)", "pre-commit", "pytest (>=6.0.0,<7.0.0)", "sphinx (>=4.5.0,<5.0.0)", "sphinx-rtd-theme", "zope.interface"] 966 | docs = ["sphinx (>=4.5.0,<5.0.0)", "sphinx-rtd-theme", "zope.interface"] 967 | tests = ["coverage[toml] (==5.0.4)", "pytest (>=6.0.0,<7.0.0)"] 968 | 969 | [[package]] 970 | name = "pynacl" 971 | version = "1.5.0" 972 | description = "Python binding to the Networking and Cryptography (NaCl) library" 973 | optional = false 974 | python-versions = ">=3.6" 975 | files = [ 976 | {file = "PyNaCl-1.5.0-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:401002a4aaa07c9414132aaed7f6836ff98f59277a234704ff66878c2ee4a0d1"}, 977 | {file = "PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:52cb72a79269189d4e0dc537556f4740f7f0a9ec41c1322598799b0bdad4ef92"}, 978 | {file = "PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a36d4a9dda1f19ce6e03c9a784a2921a4b726b02e1c736600ca9c22029474394"}, 979 | {file = "PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:0c84947a22519e013607c9be43706dd42513f9e6ae5d39d3613ca1e142fba44d"}, 980 | {file = "PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:06b8f6fa7f5de8d5d2f7573fe8c863c051225a27b61e6860fd047b1775807858"}, 981 | {file = "PyNaCl-1.5.0-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:a422368fc821589c228f4c49438a368831cb5bbc0eab5ebe1d7fac9dded6567b"}, 982 | {file = "PyNaCl-1.5.0-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:61f642bf2378713e2c2e1de73444a3778e5f0a38be6fee0fe532fe30060282ff"}, 983 | {file = "PyNaCl-1.5.0-cp36-abi3-win32.whl", hash = "sha256:e46dae94e34b085175f8abb3b0aaa7da40767865ac82c928eeb9e57e1ea8a543"}, 984 | {file = "PyNaCl-1.5.0-cp36-abi3-win_amd64.whl", hash = "sha256:20f42270d27e1b6a29f54032090b972d97f0a1b0948cc52392041ef7831fee93"}, 985 | {file = "PyNaCl-1.5.0.tar.gz", hash = "sha256:8ac7448f09ab85811607bdd21ec2464495ac8b7c66d146bf545b0f08fb9220ba"}, 986 | ] 987 | 988 | [package.dependencies] 989 | cffi = ">=1.4.1" 990 | 991 | [package.extras] 992 | docs = ["sphinx (>=1.6.5)", "sphinx-rtd-theme"] 993 | tests = ["hypothesis (>=3.27.0)", "pytest (>=3.2.1,!=3.3.0)"] 994 | 995 | [[package]] 996 | name = "pytest" 997 | version = "6.2.5" 998 | description = "pytest: simple powerful testing with Python" 999 | optional = false 1000 | python-versions = ">=3.6" 1001 | files = [ 1002 | {file = "pytest-6.2.5-py3-none-any.whl", hash = "sha256:7310f8d27bc79ced999e760ca304d69f6ba6c6649c0b60fb0e04a4a77cacc134"}, 1003 | {file = "pytest-6.2.5.tar.gz", hash = "sha256:131b36680866a76e6781d13f101efb86cf674ebb9762eb70d3082b6f29889e89"}, 1004 | ] 1005 | 1006 | [package.dependencies] 1007 | atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""} 1008 | attrs = ">=19.2.0" 1009 | colorama = {version = "*", markers = "sys_platform == \"win32\""} 1010 | iniconfig = "*" 1011 | packaging = "*" 1012 | pluggy = ">=0.12,<2.0" 1013 | py = ">=1.8.2" 1014 | toml = "*" 1015 | 1016 | [package.extras] 1017 | testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"] 1018 | 1019 | [[package]] 1020 | name = "pytest-mock" 1021 | version = "3.10.0" 1022 | description = "Thin-wrapper around the mock package for easier use with pytest" 1023 | optional = false 1024 | python-versions = ">=3.7" 1025 | files = [ 1026 | {file = "pytest-mock-3.10.0.tar.gz", hash = "sha256:fbbdb085ef7c252a326fd8cdcac0aa3b1333d8811f131bdcc701002e1be7ed4f"}, 1027 | {file = "pytest_mock-3.10.0-py3-none-any.whl", hash = "sha256:f4c973eeae0282963eb293eb173ce91b091a79c1334455acfac9ddee8a1c784b"}, 1028 | ] 1029 | 1030 | [package.dependencies] 1031 | pytest = ">=5.0" 1032 | 1033 | [package.extras] 1034 | dev = ["pre-commit", "pytest-asyncio", "tox"] 1035 | 1036 | [[package]] 1037 | name = "python-gitlab" 1038 | version = "3.13.0" 1039 | description = "Interact with GitLab API" 1040 | optional = false 1041 | python-versions = ">=3.7.0" 1042 | files = [ 1043 | {file = "python-gitlab-3.13.0.tar.gz", hash = "sha256:ad502b72b5d1137f4af37d4a68ae20fe7d6c9778f67cbe2aec566f7995053c7d"}, 1044 | {file = "python_gitlab-3.13.0-py3-none-any.whl", hash = "sha256:85ae778d8953aba87ad4b78aef7fbb5dae053980d2c20ff200bea29799685743"}, 1045 | ] 1046 | 1047 | [package.dependencies] 1048 | requests = ">=2.25.0" 1049 | requests-toolbelt = ">=0.10.1" 1050 | 1051 | [package.extras] 1052 | autocompletion = ["argcomplete (>=1.10.0,<3)"] 1053 | yaml = ["PyYaml (>=5.2)"] 1054 | 1055 | [[package]] 1056 | name = "python-semantic-release" 1057 | version = "7.34.6" 1058 | description = "Automatic Semantic Versioning for Python projects" 1059 | optional = false 1060 | python-versions = "*" 1061 | files = [ 1062 | {file = "python-semantic-release-7.34.6.tar.gz", hash = "sha256:e9b8fb788024ae9510a924136d573588415a16eeca31cc5240f2754a80a2e831"}, 1063 | {file = "python_semantic_release-7.34.6-py3-none-any.whl", hash = "sha256:7e3969ba4663d9b2087b02bf3ac140e202551377bf045c34e09bfe19753e19ab"}, 1064 | ] 1065 | 1066 | [package.dependencies] 1067 | click = ">=7,<9" 1068 | click-log = ">=0.3,<1" 1069 | dotty-dict = ">=1.3.0,<2" 1070 | gitpython = ">=3.0.8,<4" 1071 | invoke = ">=1.4.1,<3" 1072 | packaging = "*" 1073 | python-gitlab = ">=2,<4" 1074 | requests = ">=2.25,<3" 1075 | semver = ">=2.10,<3" 1076 | tomlkit = ">=0.10,<1.0" 1077 | twine = ">=3,<4" 1078 | wheel = "*" 1079 | 1080 | [package.extras] 1081 | dev = ["black", "isort", "tox"] 1082 | docs = ["Jinja2 (==3.0.3)", "Sphinx (==1.8.6)"] 1083 | mypy = ["mypy", "types-requests"] 1084 | test = ["coverage (>=5,<6)", "mock (==1.3.0)", "pytest (>=7,<8)", "pytest-mock (>=2,<3)", "pytest-xdist (>=1,<2)", "responses (==0.13.3)"] 1085 | 1086 | [[package]] 1087 | name = "pywin32-ctypes" 1088 | version = "0.2.0" 1089 | description = "" 1090 | optional = false 1091 | python-versions = "*" 1092 | files = [ 1093 | {file = "pywin32-ctypes-0.2.0.tar.gz", hash = "sha256:24ffc3b341d457d48e8922352130cf2644024a4ff09762a2261fd34c36ee5942"}, 1094 | {file = "pywin32_ctypes-0.2.0-py2.py3-none-any.whl", hash = "sha256:9dc2d991b3479cc2df15930958b674a48a227d5361d413827a4cfd0b5876fc98"}, 1095 | ] 1096 | 1097 | [[package]] 1098 | name = "pyyaml" 1099 | version = "6.0" 1100 | description = "YAML parser and emitter for Python" 1101 | optional = false 1102 | python-versions = ">=3.6" 1103 | files = [ 1104 | {file = "PyYAML-6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53"}, 1105 | {file = "PyYAML-6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c"}, 1106 | {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc"}, 1107 | {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b"}, 1108 | {file = "PyYAML-6.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5"}, 1109 | {file = "PyYAML-6.0-cp310-cp310-win32.whl", hash = "sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513"}, 1110 | {file = "PyYAML-6.0-cp310-cp310-win_amd64.whl", hash = "sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a"}, 1111 | {file = "PyYAML-6.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d4b0ba9512519522b118090257be113b9468d804b19d63c71dbcf4a48fa32358"}, 1112 | {file = "PyYAML-6.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:81957921f441d50af23654aa6c5e5eaf9b06aba7f0a19c18a538dc7ef291c5a1"}, 1113 | {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:afa17f5bc4d1b10afd4466fd3a44dc0e245382deca5b3c353d8b757f9e3ecb8d"}, 1114 | {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dbad0e9d368bb989f4515da330b88a057617d16b6a8245084f1b05400f24609f"}, 1115 | {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:432557aa2c09802be39460360ddffd48156e30721f5e8d917f01d31694216782"}, 1116 | {file = "PyYAML-6.0-cp311-cp311-win32.whl", hash = "sha256:bfaef573a63ba8923503d27530362590ff4f576c626d86a9fed95822a8255fd7"}, 1117 | {file = "PyYAML-6.0-cp311-cp311-win_amd64.whl", hash = "sha256:01b45c0191e6d66c470b6cf1b9531a771a83c1c4208272ead47a3ae4f2f603bf"}, 1118 | {file = "PyYAML-6.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86"}, 1119 | {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f"}, 1120 | {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92"}, 1121 | {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4"}, 1122 | {file = "PyYAML-6.0-cp36-cp36m-win32.whl", hash = "sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293"}, 1123 | {file = "PyYAML-6.0-cp36-cp36m-win_amd64.whl", hash = "sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57"}, 1124 | {file = "PyYAML-6.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c"}, 1125 | {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0"}, 1126 | {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4"}, 1127 | {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9"}, 1128 | {file = "PyYAML-6.0-cp37-cp37m-win32.whl", hash = "sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737"}, 1129 | {file = "PyYAML-6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d"}, 1130 | {file = "PyYAML-6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b"}, 1131 | {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba"}, 1132 | {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34"}, 1133 | {file = "PyYAML-6.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287"}, 1134 | {file = "PyYAML-6.0-cp38-cp38-win32.whl", hash = "sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78"}, 1135 | {file = "PyYAML-6.0-cp38-cp38-win_amd64.whl", hash = "sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07"}, 1136 | {file = "PyYAML-6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b"}, 1137 | {file = "PyYAML-6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174"}, 1138 | {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803"}, 1139 | {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3"}, 1140 | {file = "PyYAML-6.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0"}, 1141 | {file = "PyYAML-6.0-cp39-cp39-win32.whl", hash = "sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb"}, 1142 | {file = "PyYAML-6.0-cp39-cp39-win_amd64.whl", hash = "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c"}, 1143 | {file = "PyYAML-6.0.tar.gz", hash = "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2"}, 1144 | ] 1145 | 1146 | [[package]] 1147 | name = "readme-renderer" 1148 | version = "37.3" 1149 | description = "readme_renderer is a library for rendering \"readme\" descriptions for Warehouse" 1150 | optional = false 1151 | python-versions = ">=3.7" 1152 | files = [ 1153 | {file = "readme_renderer-37.3-py3-none-any.whl", hash = "sha256:f67a16caedfa71eef48a31b39708637a6f4664c4394801a7b0d6432d13907343"}, 1154 | {file = "readme_renderer-37.3.tar.gz", hash = "sha256:cd653186dfc73055656f090f227f5cb22a046d7f71a841dfa305f55c9a513273"}, 1155 | ] 1156 | 1157 | [package.dependencies] 1158 | bleach = ">=2.1.0" 1159 | docutils = ">=0.13.1" 1160 | Pygments = ">=2.5.1" 1161 | 1162 | [package.extras] 1163 | md = ["cmarkgfm (>=0.8.0)"] 1164 | 1165 | [[package]] 1166 | name = "requests" 1167 | version = "2.28.2" 1168 | description = "Python HTTP for Humans." 1169 | optional = false 1170 | python-versions = ">=3.7, <4" 1171 | files = [ 1172 | {file = "requests-2.28.2-py3-none-any.whl", hash = "sha256:64299f4909223da747622c030b781c0d7811e359c37124b4bd368fb8c6518baa"}, 1173 | {file = "requests-2.28.2.tar.gz", hash = "sha256:98b1b2782e3c6c4904938b84c0eb932721069dfdb9134313beff7c83c2df24bf"}, 1174 | ] 1175 | 1176 | [package.dependencies] 1177 | certifi = ">=2017.4.17" 1178 | charset-normalizer = ">=2,<4" 1179 | idna = ">=2.5,<4" 1180 | urllib3 = ">=1.21.1,<1.27" 1181 | 1182 | [package.extras] 1183 | socks = ["PySocks (>=1.5.6,!=1.5.7)"] 1184 | use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] 1185 | 1186 | [[package]] 1187 | name = "requests-toolbelt" 1188 | version = "0.10.1" 1189 | description = "A utility belt for advanced users of python-requests" 1190 | optional = false 1191 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 1192 | files = [ 1193 | {file = "requests-toolbelt-0.10.1.tar.gz", hash = "sha256:62e09f7ff5ccbda92772a29f394a49c3ad6cb181d568b1337626b2abb628a63d"}, 1194 | {file = "requests_toolbelt-0.10.1-py2.py3-none-any.whl", hash = "sha256:18565aa58116d9951ac39baa288d3adb5b3ff975c4f25eee78555d89e8f247f7"}, 1195 | ] 1196 | 1197 | [package.dependencies] 1198 | requests = ">=2.0.1,<3.0.0" 1199 | 1200 | [[package]] 1201 | name = "rfc3986" 1202 | version = "2.0.0" 1203 | description = "Validating URI References per RFC 3986" 1204 | optional = false 1205 | python-versions = ">=3.7" 1206 | files = [ 1207 | {file = "rfc3986-2.0.0-py2.py3-none-any.whl", hash = "sha256:50b1502b60e289cb37883f3dfd34532b8873c7de9f49bb546641ce9cbd256ebd"}, 1208 | {file = "rfc3986-2.0.0.tar.gz", hash = "sha256:97aacf9dbd4bfd829baad6e6309fa6573aaf1be3f6fa735c8ab05e46cecb261c"}, 1209 | ] 1210 | 1211 | [package.extras] 1212 | idna2008 = ["idna"] 1213 | 1214 | [[package]] 1215 | name = "rich" 1216 | version = "13.3.1" 1217 | description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" 1218 | optional = false 1219 | python-versions = ">=3.7.0" 1220 | files = [ 1221 | {file = "rich-13.3.1-py3-none-any.whl", hash = "sha256:8aa57747f3fc3e977684f0176a88e789be314a99f99b43b75d1e9cb5dc6db9e9"}, 1222 | {file = "rich-13.3.1.tar.gz", hash = "sha256:125d96d20c92b946b983d0d392b84ff945461e5a06d3867e9f9e575f8697b67f"}, 1223 | ] 1224 | 1225 | [package.dependencies] 1226 | markdown-it-py = ">=2.1.0,<3.0.0" 1227 | pygments = ">=2.14.0,<3.0.0" 1228 | 1229 | [package.extras] 1230 | jupyter = ["ipywidgets (>=7.5.1,<9)"] 1231 | 1232 | [[package]] 1233 | name = "secretstorage" 1234 | version = "3.3.3" 1235 | description = "Python bindings to FreeDesktop.org Secret Service API" 1236 | optional = false 1237 | python-versions = ">=3.6" 1238 | files = [ 1239 | {file = "SecretStorage-3.3.3-py3-none-any.whl", hash = "sha256:f356e6628222568e3af06f2eba8df495efa13b3b63081dafd4f7d9a7b7bc9f99"}, 1240 | {file = "SecretStorage-3.3.3.tar.gz", hash = "sha256:2403533ef369eca6d2ba81718576c5e0f564d5cca1b58f73a8b23e7d4eeebd77"}, 1241 | ] 1242 | 1243 | [package.dependencies] 1244 | cryptography = ">=2.0" 1245 | jeepney = ">=0.6" 1246 | 1247 | [[package]] 1248 | name = "semver" 1249 | version = "2.13.0" 1250 | description = "Python helper for Semantic Versioning (http://semver.org/)" 1251 | optional = false 1252 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 1253 | files = [ 1254 | {file = "semver-2.13.0-py2.py3-none-any.whl", hash = "sha256:ced8b23dceb22134307c1b8abfa523da14198793d9787ac838e70e29e77458d4"}, 1255 | {file = "semver-2.13.0.tar.gz", hash = "sha256:fa0fe2722ee1c3f57eac478820c3a5ae2f624af8264cbdf9000c980ff7f75e3f"}, 1256 | ] 1257 | 1258 | [[package]] 1259 | name = "setuptools" 1260 | version = "67.2.0" 1261 | description = "Easily download, build, install, upgrade, and uninstall Python packages" 1262 | optional = false 1263 | python-versions = ">=3.7" 1264 | files = [ 1265 | {file = "setuptools-67.2.0-py3-none-any.whl", hash = "sha256:16ccf598aab3b506593c17378473978908a2734d7336755a8769b480906bec1c"}, 1266 | {file = "setuptools-67.2.0.tar.gz", hash = "sha256:b440ee5f7e607bb8c9de15259dba2583dd41a38879a7abc1d43a71c59524da48"}, 1267 | ] 1268 | 1269 | [package.extras] 1270 | docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (==0.8.3)", "sphinx-reredirects", "sphinxcontrib-towncrier"] 1271 | testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8 (<5)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pip-run (>=8.8)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] 1272 | testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] 1273 | 1274 | [[package]] 1275 | name = "six" 1276 | version = "1.16.0" 1277 | description = "Python 2 and 3 compatibility utilities" 1278 | optional = false 1279 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" 1280 | files = [ 1281 | {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, 1282 | {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, 1283 | ] 1284 | 1285 | [[package]] 1286 | name = "smmap" 1287 | version = "5.0.0" 1288 | description = "A pure Python implementation of a sliding window memory map manager" 1289 | optional = false 1290 | python-versions = ">=3.6" 1291 | files = [ 1292 | {file = "smmap-5.0.0-py3-none-any.whl", hash = "sha256:2aba19d6a040e78d8b09de5c57e96207b09ed71d8e55ce0959eeee6c8e190d94"}, 1293 | {file = "smmap-5.0.0.tar.gz", hash = "sha256:c840e62059cd3be204b0c9c9f74be2c09d5648eddd4580d9314c3ecde0b30936"}, 1294 | ] 1295 | 1296 | [[package]] 1297 | name = "toml" 1298 | version = "0.10.2" 1299 | description = "Python Library for Tom's Obvious, Minimal Language" 1300 | optional = false 1301 | python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" 1302 | files = [ 1303 | {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, 1304 | {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, 1305 | ] 1306 | 1307 | [[package]] 1308 | name = "tomli" 1309 | version = "2.0.1" 1310 | description = "A lil' TOML parser" 1311 | optional = false 1312 | python-versions = ">=3.7" 1313 | files = [ 1314 | {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, 1315 | {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, 1316 | ] 1317 | 1318 | [[package]] 1319 | name = "tomlkit" 1320 | version = "0.11.6" 1321 | description = "Style preserving TOML library" 1322 | optional = false 1323 | python-versions = ">=3.6" 1324 | files = [ 1325 | {file = "tomlkit-0.11.6-py3-none-any.whl", hash = "sha256:07de26b0d8cfc18f871aec595fda24d95b08fef89d147caa861939f37230bf4b"}, 1326 | {file = "tomlkit-0.11.6.tar.gz", hash = "sha256:71b952e5721688937fb02cf9d354dbcf0785066149d2855e44531ebdd2b65d73"}, 1327 | ] 1328 | 1329 | [[package]] 1330 | name = "tqdm" 1331 | version = "4.64.1" 1332 | description = "Fast, Extensible Progress Meter" 1333 | optional = false 1334 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" 1335 | files = [ 1336 | {file = "tqdm-4.64.1-py2.py3-none-any.whl", hash = "sha256:6fee160d6ffcd1b1c68c65f14c829c22832bc401726335ce92c52d395944a6a1"}, 1337 | {file = "tqdm-4.64.1.tar.gz", hash = "sha256:5f4f682a004951c1b450bc753c710e9280c5746ce6ffedee253ddbcbf54cf1e4"}, 1338 | ] 1339 | 1340 | [package.dependencies] 1341 | colorama = {version = "*", markers = "platform_system == \"Windows\""} 1342 | 1343 | [package.extras] 1344 | dev = ["py-make (>=0.1.0)", "twine", "wheel"] 1345 | notebook = ["ipywidgets (>=6)"] 1346 | slack = ["slack-sdk"] 1347 | telegram = ["requests"] 1348 | 1349 | [[package]] 1350 | name = "twine" 1351 | version = "3.8.0" 1352 | description = "Collection of utilities for publishing packages on PyPI" 1353 | optional = false 1354 | python-versions = ">=3.6" 1355 | files = [ 1356 | {file = "twine-3.8.0-py3-none-any.whl", hash = "sha256:d0550fca9dc19f3d5e8eadfce0c227294df0a2a951251a4385797c8a6198b7c8"}, 1357 | {file = "twine-3.8.0.tar.gz", hash = "sha256:8efa52658e0ae770686a13b675569328f1fba9837e5de1867bfe5f46a9aefe19"}, 1358 | ] 1359 | 1360 | [package.dependencies] 1361 | colorama = ">=0.4.3" 1362 | importlib-metadata = ">=3.6" 1363 | keyring = ">=15.1" 1364 | pkginfo = ">=1.8.1" 1365 | readme-renderer = ">=21.0" 1366 | requests = ">=2.20" 1367 | requests-toolbelt = ">=0.8.0,<0.9.0 || >0.9.0" 1368 | rfc3986 = ">=1.4.0" 1369 | tqdm = ">=4.14" 1370 | urllib3 = ">=1.26.0" 1371 | 1372 | [[package]] 1373 | name = "typing-extensions" 1374 | version = "4.4.0" 1375 | description = "Backported and Experimental Type Hints for Python 3.7+" 1376 | optional = false 1377 | python-versions = ">=3.7" 1378 | files = [ 1379 | {file = "typing_extensions-4.4.0-py3-none-any.whl", hash = "sha256:16fa4864408f655d35ec496218b85f79b3437c829e93320c7c9215ccfd92489e"}, 1380 | {file = "typing_extensions-4.4.0.tar.gz", hash = "sha256:1511434bb92bf8dd198c12b1cc812e800d4181cfcb867674e0f8279cc93087aa"}, 1381 | ] 1382 | 1383 | [[package]] 1384 | name = "urllib3" 1385 | version = "1.26.14" 1386 | description = "HTTP library with thread-safe connection pooling, file post, and more." 1387 | optional = false 1388 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" 1389 | files = [ 1390 | {file = "urllib3-1.26.14-py2.py3-none-any.whl", hash = "sha256:75edcdc2f7d85b137124a6c3c9fc3933cdeaa12ecb9a6a959f22797a0feca7e1"}, 1391 | {file = "urllib3-1.26.14.tar.gz", hash = "sha256:076907bf8fd355cde77728471316625a4d2f7e713c125f51953bb5b3eecf4f72"}, 1392 | ] 1393 | 1394 | [package.extras] 1395 | brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"] 1396 | secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)", "urllib3-secure-extra"] 1397 | socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] 1398 | 1399 | [[package]] 1400 | name = "virtualenv" 1401 | version = "20.19.0" 1402 | description = "Virtual Python Environment builder" 1403 | optional = false 1404 | python-versions = ">=3.7" 1405 | files = [ 1406 | {file = "virtualenv-20.19.0-py3-none-any.whl", hash = "sha256:54eb59e7352b573aa04d53f80fc9736ed0ad5143af445a1e539aada6eb947dd1"}, 1407 | {file = "virtualenv-20.19.0.tar.gz", hash = "sha256:37a640ba82ed40b226599c522d411e4be5edb339a0c0de030c0dc7b646d61590"}, 1408 | ] 1409 | 1410 | [package.dependencies] 1411 | distlib = ">=0.3.6,<1" 1412 | filelock = ">=3.4.1,<4" 1413 | platformdirs = ">=2.4,<4" 1414 | 1415 | [package.extras] 1416 | docs = ["furo (>=2022.12.7)", "proselint (>=0.13)", "sphinx (>=6.1.3)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=22.12)"] 1417 | test = ["covdefaults (>=2.2.2)", "coverage (>=7.1)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23)", "pytest (>=7.2.1)", "pytest-env (>=0.8.1)", "pytest-freezegun (>=0.4.2)", "pytest-mock (>=3.10)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)"] 1418 | 1419 | [[package]] 1420 | name = "webencodings" 1421 | version = "0.5.1" 1422 | description = "Character encoding aliases for legacy web content" 1423 | optional = false 1424 | python-versions = "*" 1425 | files = [ 1426 | {file = "webencodings-0.5.1-py2.py3-none-any.whl", hash = "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78"}, 1427 | {file = "webencodings-0.5.1.tar.gz", hash = "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923"}, 1428 | ] 1429 | 1430 | [[package]] 1431 | name = "wheel" 1432 | version = "0.38.4" 1433 | description = "A built-package format for Python" 1434 | optional = false 1435 | python-versions = ">=3.7" 1436 | files = [ 1437 | {file = "wheel-0.38.4-py3-none-any.whl", hash = "sha256:b60533f3f5d530e971d6737ca6d58681ee434818fab630c83a734bb10c083ce8"}, 1438 | {file = "wheel-0.38.4.tar.gz", hash = "sha256:965f5259b566725405b05e7cf774052044b1ed30119b5d586b2703aafe8719ac"}, 1439 | ] 1440 | 1441 | [package.extras] 1442 | test = ["pytest (>=3.0.0)"] 1443 | 1444 | [[package]] 1445 | name = "wrapt" 1446 | version = "1.14.1" 1447 | description = "Module for decorators, wrappers and monkey patching." 1448 | optional = false 1449 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" 1450 | files = [ 1451 | {file = "wrapt-1.14.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:1b376b3f4896e7930f1f772ac4b064ac12598d1c38d04907e696cc4d794b43d3"}, 1452 | {file = "wrapt-1.14.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:903500616422a40a98a5a3c4ff4ed9d0066f3b4c951fa286018ecdf0750194ef"}, 1453 | {file = "wrapt-1.14.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:5a9a0d155deafd9448baff28c08e150d9b24ff010e899311ddd63c45c2445e28"}, 1454 | {file = "wrapt-1.14.1-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:ddaea91abf8b0d13443f6dac52e89051a5063c7d014710dcb4d4abb2ff811a59"}, 1455 | {file = "wrapt-1.14.1-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:36f582d0c6bc99d5f39cd3ac2a9062e57f3cf606ade29a0a0d6b323462f4dd87"}, 1456 | {file = "wrapt-1.14.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:7ef58fb89674095bfc57c4069e95d7a31cfdc0939e2a579882ac7d55aadfd2a1"}, 1457 | {file = "wrapt-1.14.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:e2f83e18fe2f4c9e7db597e988f72712c0c3676d337d8b101f6758107c42425b"}, 1458 | {file = "wrapt-1.14.1-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:ee2b1b1769f6707a8a445162ea16dddf74285c3964f605877a20e38545c3c462"}, 1459 | {file = "wrapt-1.14.1-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:833b58d5d0b7e5b9832869f039203389ac7cbf01765639c7309fd50ef619e0b1"}, 1460 | {file = "wrapt-1.14.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:80bb5c256f1415f747011dc3604b59bc1f91c6e7150bd7db03b19170ee06b320"}, 1461 | {file = "wrapt-1.14.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:07f7a7d0f388028b2df1d916e94bbb40624c59b48ecc6cbc232546706fac74c2"}, 1462 | {file = "wrapt-1.14.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:02b41b633c6261feff8ddd8d11c711df6842aba629fdd3da10249a53211a72c4"}, 1463 | {file = "wrapt-1.14.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2fe803deacd09a233e4762a1adcea5db5d31e6be577a43352936179d14d90069"}, 1464 | {file = "wrapt-1.14.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:257fd78c513e0fb5cdbe058c27a0624c9884e735bbd131935fd49e9fe719d310"}, 1465 | {file = "wrapt-1.14.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:4fcc4649dc762cddacd193e6b55bc02edca674067f5f98166d7713b193932b7f"}, 1466 | {file = "wrapt-1.14.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:11871514607b15cfeb87c547a49bca19fde402f32e2b1c24a632506c0a756656"}, 1467 | {file = "wrapt-1.14.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8ad85f7f4e20964db4daadcab70b47ab05c7c1cf2a7c1e51087bfaa83831854c"}, 1468 | {file = "wrapt-1.14.1-cp310-cp310-win32.whl", hash = "sha256:a9a52172be0b5aae932bef82a79ec0a0ce87288c7d132946d645eba03f0ad8a8"}, 1469 | {file = "wrapt-1.14.1-cp310-cp310-win_amd64.whl", hash = "sha256:6d323e1554b3d22cfc03cd3243b5bb815a51f5249fdcbb86fda4bf62bab9e164"}, 1470 | {file = "wrapt-1.14.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ecee4132c6cd2ce5308e21672015ddfed1ff975ad0ac8d27168ea82e71413f55"}, 1471 | {file = "wrapt-1.14.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2020f391008ef874c6d9e208b24f28e31bcb85ccff4f335f15a3251d222b92d9"}, 1472 | {file = "wrapt-1.14.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2feecf86e1f7a86517cab34ae6c2f081fd2d0dac860cb0c0ded96d799d20b335"}, 1473 | {file = "wrapt-1.14.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:240b1686f38ae665d1b15475966fe0472f78e71b1b4903c143a842659c8e4cb9"}, 1474 | {file = "wrapt-1.14.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a9008dad07d71f68487c91e96579c8567c98ca4c3881b9b113bc7b33e9fd78b8"}, 1475 | {file = "wrapt-1.14.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:6447e9f3ba72f8e2b985a1da758767698efa72723d5b59accefd716e9e8272bf"}, 1476 | {file = "wrapt-1.14.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:acae32e13a4153809db37405f5eba5bac5fbe2e2ba61ab227926a22901051c0a"}, 1477 | {file = "wrapt-1.14.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:49ef582b7a1152ae2766557f0550a9fcbf7bbd76f43fbdc94dd3bf07cc7168be"}, 1478 | {file = "wrapt-1.14.1-cp311-cp311-win32.whl", hash = "sha256:358fe87cc899c6bb0ddc185bf3dbfa4ba646f05b1b0b9b5a27c2cb92c2cea204"}, 1479 | {file = "wrapt-1.14.1-cp311-cp311-win_amd64.whl", hash = "sha256:26046cd03936ae745a502abf44dac702a5e6880b2b01c29aea8ddf3353b68224"}, 1480 | {file = "wrapt-1.14.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:43ca3bbbe97af00f49efb06e352eae40434ca9d915906f77def219b88e85d907"}, 1481 | {file = "wrapt-1.14.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:6b1a564e6cb69922c7fe3a678b9f9a3c54e72b469875aa8018f18b4d1dd1adf3"}, 1482 | {file = "wrapt-1.14.1-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:00b6d4ea20a906c0ca56d84f93065b398ab74b927a7a3dbd470f6fc503f95dc3"}, 1483 | {file = "wrapt-1.14.1-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:a85d2b46be66a71bedde836d9e41859879cc54a2a04fad1191eb50c2066f6e9d"}, 1484 | {file = "wrapt-1.14.1-cp35-cp35m-win32.whl", hash = "sha256:dbcda74c67263139358f4d188ae5faae95c30929281bc6866d00573783c422b7"}, 1485 | {file = "wrapt-1.14.1-cp35-cp35m-win_amd64.whl", hash = "sha256:b21bb4c09ffabfa0e85e3a6b623e19b80e7acd709b9f91452b8297ace2a8ab00"}, 1486 | {file = "wrapt-1.14.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:9e0fd32e0148dd5dea6af5fee42beb949098564cc23211a88d799e434255a1f4"}, 1487 | {file = "wrapt-1.14.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9736af4641846491aedb3c3f56b9bc5568d92b0692303b5a305301a95dfd38b1"}, 1488 | {file = "wrapt-1.14.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5b02d65b9ccf0ef6c34cba6cf5bf2aab1bb2f49c6090bafeecc9cd81ad4ea1c1"}, 1489 | {file = "wrapt-1.14.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21ac0156c4b089b330b7666db40feee30a5d52634cc4560e1905d6529a3897ff"}, 1490 | {file = "wrapt-1.14.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:9f3e6f9e05148ff90002b884fbc2a86bd303ae847e472f44ecc06c2cd2fcdb2d"}, 1491 | {file = "wrapt-1.14.1-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:6e743de5e9c3d1b7185870f480587b75b1cb604832e380d64f9504a0535912d1"}, 1492 | {file = "wrapt-1.14.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:d79d7d5dc8a32b7093e81e97dad755127ff77bcc899e845f41bf71747af0c569"}, 1493 | {file = "wrapt-1.14.1-cp36-cp36m-win32.whl", hash = "sha256:81b19725065dcb43df02b37e03278c011a09e49757287dca60c5aecdd5a0b8ed"}, 1494 | {file = "wrapt-1.14.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b014c23646a467558be7da3d6b9fa409b2c567d2110599b7cf9a0c5992b3b471"}, 1495 | {file = "wrapt-1.14.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:88bd7b6bd70a5b6803c1abf6bca012f7ed963e58c68d76ee20b9d751c74a3248"}, 1496 | {file = "wrapt-1.14.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b5901a312f4d14c59918c221323068fad0540e34324925c8475263841dbdfe68"}, 1497 | {file = "wrapt-1.14.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d77c85fedff92cf788face9bfa3ebaa364448ebb1d765302e9af11bf449ca36d"}, 1498 | {file = "wrapt-1.14.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d649d616e5c6a678b26d15ece345354f7c2286acd6db868e65fcc5ff7c24a77"}, 1499 | {file = "wrapt-1.14.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7d2872609603cb35ca513d7404a94d6d608fc13211563571117046c9d2bcc3d7"}, 1500 | {file = "wrapt-1.14.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:ee6acae74a2b91865910eef5e7de37dc6895ad96fa23603d1d27ea69df545015"}, 1501 | {file = "wrapt-1.14.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:2b39d38039a1fdad98c87279b48bc5dce2c0ca0d73483b12cb72aa9609278e8a"}, 1502 | {file = "wrapt-1.14.1-cp37-cp37m-win32.whl", hash = "sha256:60db23fa423575eeb65ea430cee741acb7c26a1365d103f7b0f6ec412b893853"}, 1503 | {file = "wrapt-1.14.1-cp37-cp37m-win_amd64.whl", hash = "sha256:709fe01086a55cf79d20f741f39325018f4df051ef39fe921b1ebe780a66184c"}, 1504 | {file = "wrapt-1.14.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8c0ce1e99116d5ab21355d8ebe53d9460366704ea38ae4d9f6933188f327b456"}, 1505 | {file = "wrapt-1.14.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:e3fb1677c720409d5f671e39bac6c9e0e422584e5f518bfd50aa4cbbea02433f"}, 1506 | {file = "wrapt-1.14.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:642c2e7a804fcf18c222e1060df25fc210b9c58db7c91416fb055897fc27e8cc"}, 1507 | {file = "wrapt-1.14.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7b7c050ae976e286906dd3f26009e117eb000fb2cf3533398c5ad9ccc86867b1"}, 1508 | {file = "wrapt-1.14.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ef3f72c9666bba2bab70d2a8b79f2c6d2c1a42a7f7e2b0ec83bb2f9e383950af"}, 1509 | {file = "wrapt-1.14.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:01c205616a89d09827986bc4e859bcabd64f5a0662a7fe95e0d359424e0e071b"}, 1510 | {file = "wrapt-1.14.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:5a0f54ce2c092aaf439813735584b9537cad479575a09892b8352fea5e988dc0"}, 1511 | {file = "wrapt-1.14.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2cf71233a0ed05ccdabe209c606fe0bac7379fdcf687f39b944420d2a09fdb57"}, 1512 | {file = "wrapt-1.14.1-cp38-cp38-win32.whl", hash = "sha256:aa31fdcc33fef9eb2552cbcbfee7773d5a6792c137b359e82879c101e98584c5"}, 1513 | {file = "wrapt-1.14.1-cp38-cp38-win_amd64.whl", hash = "sha256:d1967f46ea8f2db647c786e78d8cc7e4313dbd1b0aca360592d8027b8508e24d"}, 1514 | {file = "wrapt-1.14.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3232822c7d98d23895ccc443bbdf57c7412c5a65996c30442ebe6ed3df335383"}, 1515 | {file = "wrapt-1.14.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:988635d122aaf2bdcef9e795435662bcd65b02f4f4c1ae37fbee7401c440b3a7"}, 1516 | {file = "wrapt-1.14.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cca3c2cdadb362116235fdbd411735de4328c61425b0aa9f872fd76d02c4e86"}, 1517 | {file = "wrapt-1.14.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d52a25136894c63de15a35bc0bdc5adb4b0e173b9c0d07a2be9d3ca64a332735"}, 1518 | {file = "wrapt-1.14.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40e7bc81c9e2b2734ea4bc1aceb8a8f0ceaac7c5299bc5d69e37c44d9081d43b"}, 1519 | {file = "wrapt-1.14.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b9b7a708dd92306328117d8c4b62e2194d00c365f18eff11a9b53c6f923b01e3"}, 1520 | {file = "wrapt-1.14.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:6a9a25751acb379b466ff6be78a315e2b439d4c94c1e99cb7266d40a537995d3"}, 1521 | {file = "wrapt-1.14.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:34aa51c45f28ba7f12accd624225e2b1e5a3a45206aa191f6f9aac931d9d56fe"}, 1522 | {file = "wrapt-1.14.1-cp39-cp39-win32.whl", hash = "sha256:dee0ce50c6a2dd9056c20db781e9c1cfd33e77d2d569f5d1d9321c641bb903d5"}, 1523 | {file = "wrapt-1.14.1-cp39-cp39-win_amd64.whl", hash = "sha256:dee60e1de1898bde3b238f18340eec6148986da0455d8ba7848d50470a7a32fb"}, 1524 | {file = "wrapt-1.14.1.tar.gz", hash = "sha256:380a85cf89e0e69b7cfbe2ea9f765f004ff419f34194018a6827ac0e3edfed4d"}, 1525 | ] 1526 | 1527 | [[package]] 1528 | name = "zipp" 1529 | version = "3.13.0" 1530 | description = "Backport of pathlib-compatible object wrapper for zip files" 1531 | optional = false 1532 | python-versions = ">=3.7" 1533 | files = [ 1534 | {file = "zipp-3.13.0-py3-none-any.whl", hash = "sha256:e8b2a36ea17df80ffe9e2c4fda3f693c3dad6df1697d3cd3af232db680950b0b"}, 1535 | {file = "zipp-3.13.0.tar.gz", hash = "sha256:23f70e964bc11a34cef175bc90ba2914e1e4545ea1e3e2f67c079671883f9cb6"}, 1536 | ] 1537 | 1538 | [package.extras] 1539 | docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] 1540 | testing = ["flake8 (<5)", "func-timeout", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] 1541 | 1542 | [metadata] 1543 | lock-version = "2.0" 1544 | python-versions = "^3.9" 1545 | content-hash = "72cd91ec8e93f29716071369873f3d3fe6bd11e38e96afe9b378f5a6b5433778" 1546 | --------------------------------------------------------------------------------