diff --git a/scripts/koji/.gitignore b/scripts/koji/.gitignore new file mode 100644 index 00000000..d78fb88f --- /dev/null +++ b/scripts/koji/.gitignore @@ -0,0 +1,5 @@ +__pycache__ + +# Distribution / packaging +dist/ +*.egg-info/ diff --git a/scripts/koji/.python-version b/scripts/koji/.python-version new file mode 100644 index 00000000..2d4715b6 --- /dev/null +++ b/scripts/koji/.python-version @@ -0,0 +1 @@ +3.11.11 diff --git a/scripts/koji/create_user_target.py b/scripts/koji/create_user_target.py index 9ee0fc5a..b7e2066f 100755 --- a/scripts/koji/create_user_target.py +++ b/scripts/koji/create_user_target.py @@ -35,4 +35,3 @@ def main(): if __name__ == "__main__": main() - diff --git a/scripts/koji/koji_build.py b/scripts/koji/koji_build.py index 05f2f22f..2e48267e 100755 --- a/scripts/koji/koji_build.py +++ b/scripts/koji/koji_build.py @@ -1,251 +1,9 @@ #!/usr/bin/env python3 -import argparse -import logging -import os -import re -import subprocess -from contextlib import contextmanager -from datetime import datetime, timedelta -from pathlib import Path -try: - from specfile import Specfile -except ImportError: - print("error: specfile module can't be imported. Please install it with 'pip install --user specfile'.") - exit(1) +import sys -TIME_FORMAT = '%Y-%m-%d-%H-%M-%S' +from koji_utils.koji_build import main -# target -> required branch -PROTECTED_TARGETS = { - "v8.2-ci": "8.2", - "v8.2-fasttrack": "8.2", - "v8.2-incoming": "8.2", - "v8.3-ci": "master", - "v8.3-fasttrack": "master", - "v8.3-incoming": "master", -} - -@contextmanager -def cd(dir): - """Change to a directory temporarily. To be used in a with statement.""" - prevdir = os.getcwd() - os.chdir(dir) - try: - yield os.path.realpath(dir) - finally: - os.chdir(prevdir) - -def check_dir(dirpath): - if not os.path.isdir(dirpath): - raise Exception("Directory %s doesn't exist" % dirpath) - return dirpath - -def check_git_repo(dirpath): - """check that the working copy is a working directory and is clean.""" - with cd(dirpath): - return subprocess.run(['git', 'diff-index', '--quiet', 'HEAD', '--']).returncode == 0 - -def check_commit_is_available_remotely(dirpath, hash, target, warn): - with cd(dirpath): - if not subprocess.check_output(['git', 'branch', '-r', '--contains', hash]): - raise Exception("The current commit is not available in the remote repository") - try: - expected_branch = PROTECTED_TARGETS.get(target) - if ( - expected_branch is not None - and not is_remote_branch_commit(dirpath, hash, expected_branch) - ): - raise Exception(f"The current commit is not the last commit in the remote branch {expected_branch}.\n" - f"This is required when using the protected target {target}.\n") - except Exception as e: - if warn: - print(f"warning: {e}", flush=True) - else: - raise e - -def get_repo_and_commit_info(dirpath): - with cd(dirpath): - remote = subprocess.check_output(['git', 'config', '--get', 'remote.origin.url']).decode().strip() - # We want the exact hash for accurate build history - hash = subprocess.check_output(['git', 'rev-parse', 'HEAD']).decode().strip() - return remote, hash - -def koji_url(remote, hash): - if remote.startswith('git@'): - remote = re.sub(r'git@(.+):', r'git+https://\1/', remote) - elif remote.startswith('https://'): - remote = 'git+' + remote - else: - raise Exception("Unrecognized remote URL") - return remote + "?#" + hash - -@contextmanager -def local_branch(branch): - prev_branch = subprocess.check_output(['git', 'branch', '--show-current']).strip() - commit = subprocess.check_output(['git', 'rev-parse', 'HEAD']).decode().strip() - subprocess.check_call(['git', 'checkout', '--quiet', commit]) - try: - yield branch - finally: - subprocess.check_call(['git', 'checkout', prev_branch]) - -def is_old_branch(b): - branch_time = datetime.strptime(b.split('/')[-1], TIME_FORMAT) - return branch_time < datetime.now() - timedelta(hours=3) - -def clean_old_branches(git_repo): - with cd(git_repo): - remote_branches = [ - line.split()[-1] for line in subprocess.check_output(['git', 'ls-remote']).decode().splitlines() - ] - remote_branches = [b for b in remote_branches if b.startswith('refs/heads/koji/test/')] - old_branches = [b for b in remote_branches if is_old_branch(b)] - if old_branches: - print("removing outdated remote branch(es)", flush=True) - subprocess.check_call(['git', 'push', '--delete', 'origin'] + old_branches) - -def xcpng_version(target): - xcpng_version_match = re.match(r'^v(\d+\.\d+)-u-\S+$', target) - if xcpng_version_match is None: - raise Exception(f"Can't find XCP-ng version in {target}") - return xcpng_version_match.group(1) - -def find_next_release(package, spec, target, test_build_id, pre_build_id): - assert test_build_id is not None or pre_build_id is not None - builds = subprocess.check_output(['koji', 'list-builds', '--quiet', '--package', package]).decode().splitlines() - if test_build_id: - base_nvr = f'{package}-{spec.version}-{spec.release}.0.{test_build_id}.' - else: - base_nvr = f'{package}-{spec.version}-{spec.release}~{pre_build_id}.' - # use a regex to match %{macro} without actually expanding the macros - base_nvr_re = ( - re.escape(re.sub('%{.+}', "@@@", base_nvr)).replace('@@@', '.*') - + r'(\d+)' - + re.escape(f'.xcpng{xcpng_version(target)}') - ) - build_matches = [re.match(base_nvr_re, b) for b in builds] - build_nbs = [int(m.group(1)) for m in build_matches if m] - build_nb = sorted(build_nbs)[-1] + 1 if build_nbs else 1 - if test_build_id: - return f'{spec.release}.0.{test_build_id}.{build_nb}' - else: - return f'{spec.release}~{pre_build_id}.{build_nb}' - -def push_bumped_release(git_repo, target, test_build_id, pre_build_id): - t = datetime.now().strftime(TIME_FORMAT) - branch = f'koji/test/{test_build_id or pre_build_id}/{t}' - with cd(git_repo), local_branch(branch): - spec_paths = subprocess.check_output(['git', 'ls-files', 'SPECS/*.spec']).decode().splitlines() - assert len(spec_paths) == 1 - spec_path = spec_paths[0] - with Specfile(spec_path) as spec: - # find the next build number - package = Path(spec_path).stem - spec.release = find_next_release(package, spec, target, test_build_id, pre_build_id) - subprocess.check_call(['git', 'commit', '--quiet', '-m', "bump release for test build", spec_path]) - subprocess.check_call(['git', 'push', 'origin', f'HEAD:refs/heads/{branch}']) - commit = subprocess.check_output(['git', 'rev-parse', 'HEAD']).decode().strip() - return commit - -def is_remote_branch_commit(git_repo, sha, branch): - with cd(git_repo): - remote_sha = ( - subprocess.check_output(['git', 'ls-remote', 'origin', f'refs/heads/{branch}']).decode().strip().split()[0] - ) - return sha == remote_sha - -def build_id_of(name, candidate): - if candidate is None: - return None - - length = len(candidate) - if length > 16: - logging.error(f"The {name} build id must be at most 16 characters long, it's {length} characters long") - exit(1) - - invalid_chars = any(re.match(r'[a-zA-Z0-9]', char) is None for char in candidate) - - if invalid_chars: - pp_invalid = ''.join("^" if re.match(r'[a-zA-Z0-9]', char) is None else " " for char in candidate) - logging.error(f"The {name} build id must only contain letters and digits:") - logging.error(f" {candidate}") - logging.error(f" {pp_invalid}") - exit(1) - - return candidate - -def main(): - parser = argparse.ArgumentParser( - description='Build a package or chain-build several from local git repos for RPM sources' - ) - parser.add_argument('target', help='Koji target for the build') - parser.add_argument('git_repos', nargs='+', - help='local path to one or more git repositories. If several are provided, ' - 'a chained build will be started in the order of the arguments') - parser.add_argument('--scratch', action="store_true", help='Perform scratch build') - parser.add_argument('--nowait', action="store_true", help='Do not wait for the build to end') - parser.add_argument('--force', action="store_true", help='Bypass sanity checks') - parser.add_argument( - '--test-build', - metavar="ID", - help='Run a test build. The provided ID will be used to build a unique release tag.', - ) - parser.add_argument( - '--pre-build', - metavar="ID", - help='Run a pre build. The provided ID will be used to build a unique release tag.', - ) - args = parser.parse_args() - - target = args.target - git_repos = [os.path.abspath(check_dir(d)) for d in args.git_repos] - is_scratch = args.scratch - is_nowait = args.nowait - - test_build = build_id_of("test", args.test_build) - pre_build = build_id_of("pre", args.pre_build) - - if test_build and pre_build: - logging.error("--pre-build and --test-build can't be used together") - exit(1) - - if len(git_repos) > 1 and is_scratch: - parser.error("--scratch is not compatible with chained builds.") - - for d in git_repos: - if not check_git_repo(d): - parser.error("%s is not in a clean state (or is not a git repository)." % d) - - if len(git_repos) == 1: - clean_old_branches(git_repos[0]) - remote, hash = get_repo_and_commit_info(git_repos[0]) - if test_build or pre_build: - hash = push_bumped_release(git_repos[0], target, test_build, pre_build) - else: - check_commit_is_available_remotely(git_repos[0], hash, None if is_scratch else target, args.force) - url = koji_url(remote, hash) - command = ( - ['koji', 'build'] - + (['--scratch'] if is_scratch else []) - + [target, url] - + (['--nowait'] if is_nowait else []) - ) - print(' '.join(command), flush=True) - subprocess.check_call(command) - else: - urls = [] - for d in git_repos: - clean_old_branches(d) - remote, hash = get_repo_and_commit_info(d) - if test_build or pre_build: - hash = push_bumped_release(d, target, test_build, pre_build) - else: - check_commit_is_available_remotely(d, hash, None if is_scratch else target, args.force) - urls.append(koji_url(remote, hash)) - command = ['koji', 'chain-build', target] + (' : '.join(urls)).split(' ') + (['--nowait'] if is_nowait else []) - print(' '.join(command), flush=True) - subprocess.check_call(command) - -if __name__ == "__main__": - main() +print("\033[33mwarning: koji_build.py as moved to koji_utils/koji_build.py. " + "Please update your configuration to use that file.\033[0m", file=sys.stderr) +main() diff --git a/scripts/koji/koji_import_rpms.py b/scripts/koji/koji_import_rpms.py index 8217472e..dba2f5d6 100755 --- a/scripts/koji/koji_import_rpms.py +++ b/scripts/koji/koji_import_rpms.py @@ -5,7 +5,6 @@ import os import subprocess - def get_srpm_info(srpmpath): return subprocess.check_output(['rpm', '-qp', srpmpath, '--qf', '%{name};;%{nvr}']).decode().split(';;') @@ -21,7 +20,9 @@ def main(): parser.add_argument('package_tags', help='comma-separated list of tags for the package(s)') parser.add_argument('build_tags', help='comma-separated list of tags for imported build(s)') parser.add_argument('--owner', help='owner for the package(s)', default='kojiadmin') - parser.add_argument('--create-build', help='create the build even if there\'s no SRPM', action='store_true', default=False) + parser.add_argument( + '--create-build', help='create the build even if there\'s no SRPM', action='store_true', default=False + ) args = parser.parse_args() srpm_directory = os.path.abspath(check_dir(args.srpm_directory)) diff --git a/scripts/koji/koji_utils/__init__.py b/scripts/koji/koji_utils/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/scripts/koji/koji_utils/koji_build.py b/scripts/koji/koji_utils/koji_build.py new file mode 100755 index 00000000..05f2f22f --- /dev/null +++ b/scripts/koji/koji_utils/koji_build.py @@ -0,0 +1,251 @@ +#!/usr/bin/env python3 +import argparse +import logging +import os +import re +import subprocess +from contextlib import contextmanager +from datetime import datetime, timedelta +from pathlib import Path + +try: + from specfile import Specfile +except ImportError: + print("error: specfile module can't be imported. Please install it with 'pip install --user specfile'.") + exit(1) + +TIME_FORMAT = '%Y-%m-%d-%H-%M-%S' + +# target -> required branch +PROTECTED_TARGETS = { + "v8.2-ci": "8.2", + "v8.2-fasttrack": "8.2", + "v8.2-incoming": "8.2", + "v8.3-ci": "master", + "v8.3-fasttrack": "master", + "v8.3-incoming": "master", +} + +@contextmanager +def cd(dir): + """Change to a directory temporarily. To be used in a with statement.""" + prevdir = os.getcwd() + os.chdir(dir) + try: + yield os.path.realpath(dir) + finally: + os.chdir(prevdir) + +def check_dir(dirpath): + if not os.path.isdir(dirpath): + raise Exception("Directory %s doesn't exist" % dirpath) + return dirpath + +def check_git_repo(dirpath): + """check that the working copy is a working directory and is clean.""" + with cd(dirpath): + return subprocess.run(['git', 'diff-index', '--quiet', 'HEAD', '--']).returncode == 0 + +def check_commit_is_available_remotely(dirpath, hash, target, warn): + with cd(dirpath): + if not subprocess.check_output(['git', 'branch', '-r', '--contains', hash]): + raise Exception("The current commit is not available in the remote repository") + try: + expected_branch = PROTECTED_TARGETS.get(target) + if ( + expected_branch is not None + and not is_remote_branch_commit(dirpath, hash, expected_branch) + ): + raise Exception(f"The current commit is not the last commit in the remote branch {expected_branch}.\n" + f"This is required when using the protected target {target}.\n") + except Exception as e: + if warn: + print(f"warning: {e}", flush=True) + else: + raise e + +def get_repo_and_commit_info(dirpath): + with cd(dirpath): + remote = subprocess.check_output(['git', 'config', '--get', 'remote.origin.url']).decode().strip() + # We want the exact hash for accurate build history + hash = subprocess.check_output(['git', 'rev-parse', 'HEAD']).decode().strip() + return remote, hash + +def koji_url(remote, hash): + if remote.startswith('git@'): + remote = re.sub(r'git@(.+):', r'git+https://\1/', remote) + elif remote.startswith('https://'): + remote = 'git+' + remote + else: + raise Exception("Unrecognized remote URL") + return remote + "?#" + hash + +@contextmanager +def local_branch(branch): + prev_branch = subprocess.check_output(['git', 'branch', '--show-current']).strip() + commit = subprocess.check_output(['git', 'rev-parse', 'HEAD']).decode().strip() + subprocess.check_call(['git', 'checkout', '--quiet', commit]) + try: + yield branch + finally: + subprocess.check_call(['git', 'checkout', prev_branch]) + +def is_old_branch(b): + branch_time = datetime.strptime(b.split('/')[-1], TIME_FORMAT) + return branch_time < datetime.now() - timedelta(hours=3) + +def clean_old_branches(git_repo): + with cd(git_repo): + remote_branches = [ + line.split()[-1] for line in subprocess.check_output(['git', 'ls-remote']).decode().splitlines() + ] + remote_branches = [b for b in remote_branches if b.startswith('refs/heads/koji/test/')] + old_branches = [b for b in remote_branches if is_old_branch(b)] + if old_branches: + print("removing outdated remote branch(es)", flush=True) + subprocess.check_call(['git', 'push', '--delete', 'origin'] + old_branches) + +def xcpng_version(target): + xcpng_version_match = re.match(r'^v(\d+\.\d+)-u-\S+$', target) + if xcpng_version_match is None: + raise Exception(f"Can't find XCP-ng version in {target}") + return xcpng_version_match.group(1) + +def find_next_release(package, spec, target, test_build_id, pre_build_id): + assert test_build_id is not None or pre_build_id is not None + builds = subprocess.check_output(['koji', 'list-builds', '--quiet', '--package', package]).decode().splitlines() + if test_build_id: + base_nvr = f'{package}-{spec.version}-{spec.release}.0.{test_build_id}.' + else: + base_nvr = f'{package}-{spec.version}-{spec.release}~{pre_build_id}.' + # use a regex to match %{macro} without actually expanding the macros + base_nvr_re = ( + re.escape(re.sub('%{.+}', "@@@", base_nvr)).replace('@@@', '.*') + + r'(\d+)' + + re.escape(f'.xcpng{xcpng_version(target)}') + ) + build_matches = [re.match(base_nvr_re, b) for b in builds] + build_nbs = [int(m.group(1)) for m in build_matches if m] + build_nb = sorted(build_nbs)[-1] + 1 if build_nbs else 1 + if test_build_id: + return f'{spec.release}.0.{test_build_id}.{build_nb}' + else: + return f'{spec.release}~{pre_build_id}.{build_nb}' + +def push_bumped_release(git_repo, target, test_build_id, pre_build_id): + t = datetime.now().strftime(TIME_FORMAT) + branch = f'koji/test/{test_build_id or pre_build_id}/{t}' + with cd(git_repo), local_branch(branch): + spec_paths = subprocess.check_output(['git', 'ls-files', 'SPECS/*.spec']).decode().splitlines() + assert len(spec_paths) == 1 + spec_path = spec_paths[0] + with Specfile(spec_path) as spec: + # find the next build number + package = Path(spec_path).stem + spec.release = find_next_release(package, spec, target, test_build_id, pre_build_id) + subprocess.check_call(['git', 'commit', '--quiet', '-m', "bump release for test build", spec_path]) + subprocess.check_call(['git', 'push', 'origin', f'HEAD:refs/heads/{branch}']) + commit = subprocess.check_output(['git', 'rev-parse', 'HEAD']).decode().strip() + return commit + +def is_remote_branch_commit(git_repo, sha, branch): + with cd(git_repo): + remote_sha = ( + subprocess.check_output(['git', 'ls-remote', 'origin', f'refs/heads/{branch}']).decode().strip().split()[0] + ) + return sha == remote_sha + +def build_id_of(name, candidate): + if candidate is None: + return None + + length = len(candidate) + if length > 16: + logging.error(f"The {name} build id must be at most 16 characters long, it's {length} characters long") + exit(1) + + invalid_chars = any(re.match(r'[a-zA-Z0-9]', char) is None for char in candidate) + + if invalid_chars: + pp_invalid = ''.join("^" if re.match(r'[a-zA-Z0-9]', char) is None else " " for char in candidate) + logging.error(f"The {name} build id must only contain letters and digits:") + logging.error(f" {candidate}") + logging.error(f" {pp_invalid}") + exit(1) + + return candidate + +def main(): + parser = argparse.ArgumentParser( + description='Build a package or chain-build several from local git repos for RPM sources' + ) + parser.add_argument('target', help='Koji target for the build') + parser.add_argument('git_repos', nargs='+', + help='local path to one or more git repositories. If several are provided, ' + 'a chained build will be started in the order of the arguments') + parser.add_argument('--scratch', action="store_true", help='Perform scratch build') + parser.add_argument('--nowait', action="store_true", help='Do not wait for the build to end') + parser.add_argument('--force', action="store_true", help='Bypass sanity checks') + parser.add_argument( + '--test-build', + metavar="ID", + help='Run a test build. The provided ID will be used to build a unique release tag.', + ) + parser.add_argument( + '--pre-build', + metavar="ID", + help='Run a pre build. The provided ID will be used to build a unique release tag.', + ) + args = parser.parse_args() + + target = args.target + git_repos = [os.path.abspath(check_dir(d)) for d in args.git_repos] + is_scratch = args.scratch + is_nowait = args.nowait + + test_build = build_id_of("test", args.test_build) + pre_build = build_id_of("pre", args.pre_build) + + if test_build and pre_build: + logging.error("--pre-build and --test-build can't be used together") + exit(1) + + if len(git_repos) > 1 and is_scratch: + parser.error("--scratch is not compatible with chained builds.") + + for d in git_repos: + if not check_git_repo(d): + parser.error("%s is not in a clean state (or is not a git repository)." % d) + + if len(git_repos) == 1: + clean_old_branches(git_repos[0]) + remote, hash = get_repo_and_commit_info(git_repos[0]) + if test_build or pre_build: + hash = push_bumped_release(git_repos[0], target, test_build, pre_build) + else: + check_commit_is_available_remotely(git_repos[0], hash, None if is_scratch else target, args.force) + url = koji_url(remote, hash) + command = ( + ['koji', 'build'] + + (['--scratch'] if is_scratch else []) + + [target, url] + + (['--nowait'] if is_nowait else []) + ) + print(' '.join(command), flush=True) + subprocess.check_call(command) + else: + urls = [] + for d in git_repos: + clean_old_branches(d) + remote, hash = get_repo_and_commit_info(d) + if test_build or pre_build: + hash = push_bumped_release(d, target, test_build, pre_build) + else: + check_commit_is_available_remotely(d, hash, None if is_scratch else target, args.force) + urls.append(koji_url(remote, hash)) + command = ['koji', 'chain-build', target] + (' : '.join(urls)).split(' ') + (['--nowait'] if is_nowait else []) + print(' '.join(command), flush=True) + subprocess.check_call(command) + +if __name__ == "__main__": + main() diff --git a/scripts/koji/pyproject.toml b/scripts/koji/pyproject.toml new file mode 100644 index 00000000..3c8754ab --- /dev/null +++ b/scripts/koji/pyproject.toml @@ -0,0 +1,105 @@ +[project] +name = "koji-utils" +description = "Koji utils" +readme = "README.md" +requires-python = "~=3.11" +dynamic = ["version"] +dependencies = [ + "koji", + "specfile", +] + +[project.scripts] +koji-build = "koji_utils.koji_build:main" + +[dependency-groups] +dev = [ + "icecream", + "mypy", + "flake8", + "pyright", + "ruff", + "typing-extensions", + "flake8-pyproject", +] + +[build-system] +requires = ["setuptools >= 77.0.3", "setuptools-scm>=8", "wheel"] +build-backend = "setuptools.build_meta" + +[tool.setuptools_scm] +root = "../.." + +[tool.pyright] +typeCheckingMode = "standard" + +[tool.ruff] +preview = true +line-length = 120 +exclude = [".git"] + +[tool.ruff.format] +quote-style = "preserve" + +[tool.ruff.lint] +select = [ + "D", # pydocstyle + "F", # Pyflakes + "I", # isort + "SLF", # flake8-self + "SIM", # flake8-simplify +] +# don't use some of the default D and SIM rules +ignore = [ + "D100", # undocumented-public-module + "D101", # undocumented-public-class + "D102", # undocumented-public-method + "D103", # undocumented-public-function + "D104", # undocumented-public-package + "D105", # undocumented-magic-method + "D106", # undocumented-public-nested-class + "D107", # undocumented-public-init + "D200", # unnecessary-multiline-docstring + "D203", # incorrect-blank-line-before-class + "D204", # incorrect-blank-line-after-class + "D205", # missing-blank-line-after-summary + "D210", # surrounding-whitespace + "D212", # incorrect-blank-line-before-class + "D400", # missing-trailing-period + "D401", # non-imperative-mood + "D403", # first-word-uncapitalized + "SIM105", # suppressible-exception + "SIM108", # if-else-block-instead-of-if-exp +] + +# restrict to the PEP 257 rules +pydocstyle.convention = "pep257" + +[tool.ruff.lint.isort.sections] +testing = ["pytest*"] +typing = ["typing"] + +[tool.ruff.lint.isort] +lines-after-imports = 1 +section-order = [ + "future", + "testing", + "standard-library", + "third-party", + "first-party", + "local-folder", + "typing", +] + +# ruff doesn't provide all the pycodestyle rules, and pycodestyle is not well +# supported by some IDEs, so we use flake8 for that +[tool.flake8] +max-line-length = 120 +ignore = [ + "E261", # At least two spaces before inline comment + "E302", # Expected 2 blank lines, found 0 + "E305", # Expected 2 blank lines after end of function or class + "W503", # Line break occurred before a binary operator + "F", # already done by ruff +] +exclude=[".git", ".venv"] diff --git a/scripts/koji/requirements.txt b/scripts/koji/requirements.txt deleted file mode 100644 index c09fb9e5..00000000 --- a/scripts/koji/requirements.txt +++ /dev/null @@ -1 +0,0 @@ -specfile>=0.34 diff --git a/scripts/koji/requirements/base.txt b/scripts/koji/requirements/base.txt new file mode 100644 index 00000000..de3cbe5a --- /dev/null +++ b/scripts/koji/requirements/base.txt @@ -0,0 +1,2 @@ +# generated with update_requirements.py, do not edit manually +specfile diff --git a/scripts/koji/requirements/dev.txt b/scripts/koji/requirements/dev.txt new file mode 100644 index 00000000..467f872c --- /dev/null +++ b/scripts/koji/requirements/dev.txt @@ -0,0 +1,9 @@ +# generated with update_requirements.py, do not edit manually +icecream +mypy +flake8 +pyright +ruff +typing-extensions +flake8-pyproject +-r base.txt diff --git a/scripts/koji/requirements/update_requirements.py b/scripts/koji/requirements/update_requirements.py new file mode 100755 index 00000000..e0a08511 --- /dev/null +++ b/scripts/koji/requirements/update_requirements.py @@ -0,0 +1,28 @@ +#!/usr/bin/env python3 + +import argparse +import tomllib +from pathlib import Path + +parser = argparse.ArgumentParser(description="Convert the dependencies from pyproject.toml in requirements.txt files") +args = parser.parse_args() + +PROJECT_DIR = Path(__file__).parent.parent +HEADER = "# generated with update_requirements.py, do not edit manually" + +with open(f'{PROJECT_DIR}/pyproject.toml', 'rb') as f: + pyproject = tomllib.load(f) + + +main_deps = pyproject['project']['dependencies'] +with open(f'{PROJECT_DIR}/requirements/base.txt', 'w') as f: + print(HEADER, file=f) + for dep in main_deps: + print(dep, file=f) + +dev_deps = pyproject['dependency-groups']['dev'] +with open(f'{PROJECT_DIR}/requirements/dev.txt', 'w') as f: + print(HEADER, file=f) + for dep in dev_deps: + print(dep, file=f) + print('-r base.txt', file=f) diff --git a/scripts/koji/setup.cfg b/scripts/koji/setup.cfg new file mode 100644 index 00000000..a44b04a3 --- /dev/null +++ b/scripts/koji/setup.cfg @@ -0,0 +1,4 @@ +[flake8] +max-line-length=120 +ignore=E261,E302,E305,W503,F +exclude=.git,.venv diff --git a/scripts/koji/sync_repo_from_koji.py b/scripts/koji/sync_repo_from_koji.py index 4ebea1c1..d1029bcb 100755 --- a/scripts/koji/sync_repo_from_koji.py +++ b/scripts/koji/sync_repo_from_koji.py @@ -10,6 +10,7 @@ import sys import tempfile from datetime import datetime +from subprocess import DEVNULL USER_REPO_HTTPS = "https://koji.xcp-ng.org/repos/user/" @@ -21,7 +22,7 @@ '8.3', ] -DEV_VERSIONS = [ +DEV_VERSIONS: list[str] = [ ] VERSIONS = DEV_VERSIONS + RELEASE_VERSIONS @@ -62,7 +63,7 @@ 'v8.0-base', 'v8.1-base', 'v8.2-base', -# 'v8.3-base', # special case: we have a history of pre-release builds that users might need for troubleshooting + # 'v8.3-base', # special case: we have a history of pre-release builds that users might need for troubleshooting ] # tags for which we want to export a stripped repo for offline updates @@ -87,6 +88,7 @@ def version_from_tag(tag): matches = re.match(r'v(\d+\.\d+)', tag) + assert matches is not None return matches.group(1) def repo_name_from_tag(tag): @@ -213,7 +215,7 @@ def sign_unsigned_rpms(tag): for line in output.strip().splitlines(): try: key, rpm = line.split(' ') - except: + except ValueError: # couldn't unpack values... no signature. continue if key == KEY_ID: @@ -321,7 +323,9 @@ def offline_repo_dir(): print("\n-- Make koji write the repository for tag %s" % tag) with_non_latest = [] if tag in RELEASE_TAGS else ['--non-latest'] sys.stdout.flush() - subprocess.check_call(['koji', 'dist-repo', tag, '3fd3ac9e', '--with-src', '--noinherit'] + with_non_latest) + subprocess.check_call( + ['koji', 'dist-repo', tag, '3fd3ac9e', '--with-src', '--noinherit'] + with_non_latest + ) # write repository to the appropriate destination directory for the tag write_repo(tag, dest_dir_for_tag(tag), tmp_root_dir) diff --git a/scripts/koji/untag_lone_builds.py b/scripts/koji/untag_lone_builds.py index f01b26a3..b82d446a 100755 --- a/scripts/koji/untag_lone_builds.py +++ b/scripts/koji/untag_lone_builds.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 -import koji +import koji # type: ignore config = koji.read_config("koji") s = koji.ClientSession('https://kojihub.xcp-ng.org', config) diff --git a/scripts/koji/update_vendor_tags.py b/scripts/koji/update_vendor_tags.py index 52c790a2..8562b817 100755 --- a/scripts/koji/update_vendor_tags.py +++ b/scripts/koji/update_vendor_tags.py @@ -5,6 +5,7 @@ import os import re import subprocess +from subprocess import DEVNULL XS_buildhosts = [ '1b68968c4e4e', @@ -42,9 +43,9 @@ def update_vendor_tag_for_build(build, is_bootstrap=False): rpm_path = "" for line in output.splitlines(): first_element = line.split()[0] - if re.match('.+/src/.+\.src\.rpm', first_element): + if re.match(r'.+/src/.+\.src\.rpm', first_element): srpm_path = "" - if re.match('.+\.rpm', first_element): + if re.match(r'.+\.rpm', first_element): rpm_path = first_element if not rpm_path: @@ -58,10 +59,12 @@ def update_vendor_tag_for_build(build, is_bootstrap=False): # get vendor information output = subprocess.check_output( - ['rpm', '-qp', rpm_path, '--qf', '%{vendor};;%{buildhost}'], stderr=devprocess.DEVNULL + ['rpm', '-qp', rpm_path, '--qf', '%{vendor};;%{buildhost}'], stderr=DEVNULL ).decode() vendor, buildhost = output.split(';;') - package = re.search('/packages/([^/]+)/', rpm_path).group(1) + rpm_match = re.search('/packages/([^/]+)/', rpm_path) + assert rpm_match is not None + package = rpm_match.group(1) tag = None if buildhost in XS_buildhosts: @@ -120,6 +123,7 @@ def main(): last_sync_event_filepath = os.path.join(data_dir, 'last_sync_event') need_update = True + timestamp = 0 if os.path.exists(last_sync_event_filepath): with open(last_sync_event_filepath) as f: last_sync_event = json.loads(f.read()) @@ -128,9 +132,6 @@ def main(): need_update = False else: timestamp = last_sync_event['ts'] - else: - timestamp = 0 # first update ever - if not need_update: if not quiet: diff --git a/scripts/koji/uv.lock b/scripts/koji/uv.lock new file mode 100644 index 00000000..0f6b7207 --- /dev/null +++ b/scripts/koji/uv.lock @@ -0,0 +1,450 @@ +version = 1 +revision = 2 +requires-python = ">=3.11, <4" + +[[package]] +name = "asttokens" +version = "3.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4a/e7/82da0a03e7ba5141f05cce0d302e6eed121ae055e0456ca228bf693984bc/asttokens-3.0.0.tar.gz", hash = "sha256:0dcd8baa8d62b0c1d118b399b2ddba3c4aff271d0d7a9e0d4c1681c79035bbc7", size = 61978, upload-time = "2024-11-30T04:30:14.439Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/25/8a/c46dcc25341b5bce5472c718902eb3d38600a903b14fa6aeecef3f21a46f/asttokens-3.0.0-py3-none-any.whl", hash = "sha256:e3078351a059199dd5138cb1c706e6430c05eff2ff136af5eb4790f9d28932e2", size = 26918, upload-time = "2024-11-30T04:30:10.946Z" }, +] + +[[package]] +name = "certifi" +version = "2025.7.14" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b3/76/52c535bcebe74590f296d6c77c86dabf761c41980e1347a2422e4aa2ae41/certifi-2025.7.14.tar.gz", hash = "sha256:8ea99dbdfaaf2ba2f9bac77b9249ef62ec5218e7c2b2e903378ed5fccf765995", size = 163981, upload-time = "2025-07-14T03:29:28.449Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4f/52/34c6cf5bb9285074dc3531c437b3919e825d976fde097a7a73f79e726d03/certifi-2025.7.14-py3-none-any.whl", hash = "sha256:6b31f564a415d79ee77df69d757bb49a5bb53bd9f756cbbe24394ffd6fc1f4b2", size = 162722, upload-time = "2025-07-14T03:29:26.863Z" }, +] + +[[package]] +name = "charset-normalizer" +version = "3.4.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e4/33/89c2ced2b67d1c2a61c19c6751aa8902d46ce3dacb23600a283619f5a12d/charset_normalizer-3.4.2.tar.gz", hash = "sha256:5baececa9ecba31eff645232d59845c07aa030f0c81ee70184a90d35099a0e63", size = 126367, upload-time = "2025-05-02T08:34:42.01Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/05/85/4c40d00dcc6284a1c1ad5de5e0996b06f39d8232f1031cd23c2f5c07ee86/charset_normalizer-3.4.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:be1e352acbe3c78727a16a455126d9ff83ea2dfdcbc83148d2982305a04714c2", size = 198794, upload-time = "2025-05-02T08:32:11.945Z" }, + { url = "https://files.pythonhosted.org/packages/41/d9/7a6c0b9db952598e97e93cbdfcb91bacd89b9b88c7c983250a77c008703c/charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa88ca0b1932e93f2d961bf3addbb2db902198dca337d88c89e1559e066e7645", size = 142846, upload-time = "2025-05-02T08:32:13.946Z" }, + { url = "https://files.pythonhosted.org/packages/66/82/a37989cda2ace7e37f36c1a8ed16c58cf48965a79c2142713244bf945c89/charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d524ba3f1581b35c03cb42beebab4a13e6cdad7b36246bd22541fa585a56cccd", size = 153350, upload-time = "2025-05-02T08:32:15.873Z" }, + { url = "https://files.pythonhosted.org/packages/df/68/a576b31b694d07b53807269d05ec3f6f1093e9545e8607121995ba7a8313/charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28a1005facc94196e1fb3e82a3d442a9d9110b8434fc1ded7a24a2983c9888d8", size = 145657, upload-time = "2025-05-02T08:32:17.283Z" }, + { url = "https://files.pythonhosted.org/packages/92/9b/ad67f03d74554bed3aefd56fe836e1623a50780f7c998d00ca128924a499/charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fdb20a30fe1175ecabed17cbf7812f7b804b8a315a25f24678bcdf120a90077f", size = 147260, upload-time = "2025-05-02T08:32:18.807Z" }, + { url = "https://files.pythonhosted.org/packages/a6/e6/8aebae25e328160b20e31a7e9929b1578bbdc7f42e66f46595a432f8539e/charset_normalizer-3.4.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0f5d9ed7f254402c9e7d35d2f5972c9bbea9040e99cd2861bd77dc68263277c7", size = 149164, upload-time = "2025-05-02T08:32:20.333Z" }, + { url = "https://files.pythonhosted.org/packages/8b/f2/b3c2f07dbcc248805f10e67a0262c93308cfa149a4cd3d1fe01f593e5fd2/charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:efd387a49825780ff861998cd959767800d54f8308936b21025326de4b5a42b9", size = 144571, upload-time = "2025-05-02T08:32:21.86Z" }, + { url = "https://files.pythonhosted.org/packages/60/5b/c3f3a94bc345bc211622ea59b4bed9ae63c00920e2e8f11824aa5708e8b7/charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:f0aa37f3c979cf2546b73e8222bbfa3dc07a641585340179d768068e3455e544", size = 151952, upload-time = "2025-05-02T08:32:23.434Z" }, + { url = "https://files.pythonhosted.org/packages/e2/4d/ff460c8b474122334c2fa394a3f99a04cf11c646da895f81402ae54f5c42/charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:e70e990b2137b29dc5564715de1e12701815dacc1d056308e2b17e9095372a82", size = 155959, upload-time = "2025-05-02T08:32:24.993Z" }, + { url = "https://files.pythonhosted.org/packages/a2/2b/b964c6a2fda88611a1fe3d4c400d39c66a42d6c169c924818c848f922415/charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:0c8c57f84ccfc871a48a47321cfa49ae1df56cd1d965a09abe84066f6853b9c0", size = 153030, upload-time = "2025-05-02T08:32:26.435Z" }, + { url = "https://files.pythonhosted.org/packages/59/2e/d3b9811db26a5ebf444bc0fa4f4be5aa6d76fc6e1c0fd537b16c14e849b6/charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6b66f92b17849b85cad91259efc341dce9c1af48e2173bf38a85c6329f1033e5", size = 148015, upload-time = "2025-05-02T08:32:28.376Z" }, + { url = "https://files.pythonhosted.org/packages/90/07/c5fd7c11eafd561bb51220d600a788f1c8d77c5eef37ee49454cc5c35575/charset_normalizer-3.4.2-cp311-cp311-win32.whl", hash = "sha256:daac4765328a919a805fa5e2720f3e94767abd632ae410a9062dff5412bae65a", size = 98106, upload-time = "2025-05-02T08:32:30.281Z" }, + { url = "https://files.pythonhosted.org/packages/a8/05/5e33dbef7e2f773d672b6d79f10ec633d4a71cd96db6673625838a4fd532/charset_normalizer-3.4.2-cp311-cp311-win_amd64.whl", hash = "sha256:e53efc7c7cee4c1e70661e2e112ca46a575f90ed9ae3fef200f2a25e954f4b28", size = 105402, upload-time = "2025-05-02T08:32:32.191Z" }, + { url = "https://files.pythonhosted.org/packages/d7/a4/37f4d6035c89cac7930395a35cc0f1b872e652eaafb76a6075943754f095/charset_normalizer-3.4.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0c29de6a1a95f24b9a1aa7aefd27d2487263f00dfd55a77719b530788f75cff7", size = 199936, upload-time = "2025-05-02T08:32:33.712Z" }, + { url = "https://files.pythonhosted.org/packages/ee/8a/1a5e33b73e0d9287274f899d967907cd0bf9c343e651755d9307e0dbf2b3/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cddf7bd982eaa998934a91f69d182aec997c6c468898efe6679af88283b498d3", size = 143790, upload-time = "2025-05-02T08:32:35.768Z" }, + { url = "https://files.pythonhosted.org/packages/66/52/59521f1d8e6ab1482164fa21409c5ef44da3e9f653c13ba71becdd98dec3/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fcbe676a55d7445b22c10967bceaaf0ee69407fbe0ece4d032b6eb8d4565982a", size = 153924, upload-time = "2025-05-02T08:32:37.284Z" }, + { url = "https://files.pythonhosted.org/packages/86/2d/fb55fdf41964ec782febbf33cb64be480a6b8f16ded2dbe8db27a405c09f/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d41c4d287cfc69060fa91cae9683eacffad989f1a10811995fa309df656ec214", size = 146626, upload-time = "2025-05-02T08:32:38.803Z" }, + { url = "https://files.pythonhosted.org/packages/8c/73/6ede2ec59bce19b3edf4209d70004253ec5f4e319f9a2e3f2f15601ed5f7/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e594135de17ab3866138f496755f302b72157d115086d100c3f19370839dd3a", size = 148567, upload-time = "2025-05-02T08:32:40.251Z" }, + { url = "https://files.pythonhosted.org/packages/09/14/957d03c6dc343c04904530b6bef4e5efae5ec7d7990a7cbb868e4595ee30/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cf713fe9a71ef6fd5adf7a79670135081cd4431c2943864757f0fa3a65b1fafd", size = 150957, upload-time = "2025-05-02T08:32:41.705Z" }, + { url = "https://files.pythonhosted.org/packages/0d/c8/8174d0e5c10ccebdcb1b53cc959591c4c722a3ad92461a273e86b9f5a302/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a370b3e078e418187da8c3674eddb9d983ec09445c99a3a263c2011993522981", size = 145408, upload-time = "2025-05-02T08:32:43.709Z" }, + { url = "https://files.pythonhosted.org/packages/58/aa/8904b84bc8084ac19dc52feb4f5952c6df03ffb460a887b42615ee1382e8/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a955b438e62efdf7e0b7b52a64dc5c3396e2634baa62471768a64bc2adb73d5c", size = 153399, upload-time = "2025-05-02T08:32:46.197Z" }, + { url = "https://files.pythonhosted.org/packages/c2/26/89ee1f0e264d201cb65cf054aca6038c03b1a0c6b4ae998070392a3ce605/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:7222ffd5e4de8e57e03ce2cef95a4c43c98fcb72ad86909abdfc2c17d227fc1b", size = 156815, upload-time = "2025-05-02T08:32:48.105Z" }, + { url = "https://files.pythonhosted.org/packages/fd/07/68e95b4b345bad3dbbd3a8681737b4338ff2c9df29856a6d6d23ac4c73cb/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:bee093bf902e1d8fc0ac143c88902c3dfc8941f7ea1d6a8dd2bcb786d33db03d", size = 154537, upload-time = "2025-05-02T08:32:49.719Z" }, + { url = "https://files.pythonhosted.org/packages/77/1a/5eefc0ce04affb98af07bc05f3bac9094513c0e23b0562d64af46a06aae4/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:dedb8adb91d11846ee08bec4c8236c8549ac721c245678282dcb06b221aab59f", size = 149565, upload-time = "2025-05-02T08:32:51.404Z" }, + { url = "https://files.pythonhosted.org/packages/37/a0/2410e5e6032a174c95e0806b1a6585eb21e12f445ebe239fac441995226a/charset_normalizer-3.4.2-cp312-cp312-win32.whl", hash = "sha256:db4c7bf0e07fc3b7d89ac2a5880a6a8062056801b83ff56d8464b70f65482b6c", size = 98357, upload-time = "2025-05-02T08:32:53.079Z" }, + { url = "https://files.pythonhosted.org/packages/6c/4f/c02d5c493967af3eda9c771ad4d2bbc8df6f99ddbeb37ceea6e8716a32bc/charset_normalizer-3.4.2-cp312-cp312-win_amd64.whl", hash = "sha256:5a9979887252a82fefd3d3ed2a8e3b937a7a809f65dcb1e068b090e165bbe99e", size = 105776, upload-time = "2025-05-02T08:32:54.573Z" }, + { url = "https://files.pythonhosted.org/packages/ea/12/a93df3366ed32db1d907d7593a94f1fe6293903e3e92967bebd6950ed12c/charset_normalizer-3.4.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:926ca93accd5d36ccdabd803392ddc3e03e6d4cd1cf17deff3b989ab8e9dbcf0", size = 199622, upload-time = "2025-05-02T08:32:56.363Z" }, + { url = "https://files.pythonhosted.org/packages/04/93/bf204e6f344c39d9937d3c13c8cd5bbfc266472e51fc8c07cb7f64fcd2de/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eba9904b0f38a143592d9fc0e19e2df0fa2e41c3c3745554761c5f6447eedabf", size = 143435, upload-time = "2025-05-02T08:32:58.551Z" }, + { url = "https://files.pythonhosted.org/packages/22/2a/ea8a2095b0bafa6c5b5a55ffdc2f924455233ee7b91c69b7edfcc9e02284/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3fddb7e2c84ac87ac3a947cb4e66d143ca5863ef48e4a5ecb83bd48619e4634e", size = 153653, upload-time = "2025-05-02T08:33:00.342Z" }, + { url = "https://files.pythonhosted.org/packages/b6/57/1b090ff183d13cef485dfbe272e2fe57622a76694061353c59da52c9a659/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98f862da73774290f251b9df8d11161b6cf25b599a66baf087c1ffe340e9bfd1", size = 146231, upload-time = "2025-05-02T08:33:02.081Z" }, + { url = "https://files.pythonhosted.org/packages/e2/28/ffc026b26f441fc67bd21ab7f03b313ab3fe46714a14b516f931abe1a2d8/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c9379d65defcab82d07b2a9dfbfc2e95bc8fe0ebb1b176a3190230a3ef0e07c", size = 148243, upload-time = "2025-05-02T08:33:04.063Z" }, + { url = "https://files.pythonhosted.org/packages/c0/0f/9abe9bd191629c33e69e47c6ef45ef99773320e9ad8e9cb08b8ab4a8d4cb/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e635b87f01ebc977342e2697d05b56632f5f879a4f15955dfe8cef2448b51691", size = 150442, upload-time = "2025-05-02T08:33:06.418Z" }, + { url = "https://files.pythonhosted.org/packages/67/7c/a123bbcedca91d5916c056407f89a7f5e8fdfce12ba825d7d6b9954a1a3c/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1c95a1e2902a8b722868587c0e1184ad5c55631de5afc0eb96bc4b0d738092c0", size = 145147, upload-time = "2025-05-02T08:33:08.183Z" }, + { url = "https://files.pythonhosted.org/packages/ec/fe/1ac556fa4899d967b83e9893788e86b6af4d83e4726511eaaad035e36595/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ef8de666d6179b009dce7bcb2ad4c4a779f113f12caf8dc77f0162c29d20490b", size = 153057, upload-time = "2025-05-02T08:33:09.986Z" }, + { url = "https://files.pythonhosted.org/packages/2b/ff/acfc0b0a70b19e3e54febdd5301a98b72fa07635e56f24f60502e954c461/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:32fc0341d72e0f73f80acb0a2c94216bd704f4f0bce10aedea38f30502b271ff", size = 156454, upload-time = "2025-05-02T08:33:11.814Z" }, + { url = "https://files.pythonhosted.org/packages/92/08/95b458ce9c740d0645feb0e96cea1f5ec946ea9c580a94adfe0b617f3573/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:289200a18fa698949d2b39c671c2cc7a24d44096784e76614899a7ccf2574b7b", size = 154174, upload-time = "2025-05-02T08:33:13.707Z" }, + { url = "https://files.pythonhosted.org/packages/78/be/8392efc43487ac051eee6c36d5fbd63032d78f7728cb37aebcc98191f1ff/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4a476b06fbcf359ad25d34a057b7219281286ae2477cc5ff5e3f70a246971148", size = 149166, upload-time = "2025-05-02T08:33:15.458Z" }, + { url = "https://files.pythonhosted.org/packages/44/96/392abd49b094d30b91d9fbda6a69519e95802250b777841cf3bda8fe136c/charset_normalizer-3.4.2-cp313-cp313-win32.whl", hash = "sha256:aaeeb6a479c7667fbe1099af9617c83aaca22182d6cf8c53966491a0f1b7ffb7", size = 98064, upload-time = "2025-05-02T08:33:17.06Z" }, + { url = "https://files.pythonhosted.org/packages/e9/b0/0200da600134e001d91851ddc797809e2fe0ea72de90e09bec5a2fbdaccb/charset_normalizer-3.4.2-cp313-cp313-win_amd64.whl", hash = "sha256:aa6af9e7d59f9c12b33ae4e9450619cf2488e2bbe9b44030905877f0b2324980", size = 105641, upload-time = "2025-05-02T08:33:18.753Z" }, + { url = "https://files.pythonhosted.org/packages/20/94/c5790835a017658cbfabd07f3bfb549140c3ac458cfc196323996b10095a/charset_normalizer-3.4.2-py3-none-any.whl", hash = "sha256:7f56930ab0abd1c45cd15be65cc741c28b1c9a34876ce8c17a2fa107810c0af0", size = 52626, upload-time = "2025-05-02T08:34:40.053Z" }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, +] + +[[package]] +name = "decorator" +version = "5.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/43/fa/6d96a0978d19e17b68d634497769987b16c8f4cd0a7a05048bec693caa6b/decorator-5.2.1.tar.gz", hash = "sha256:65f266143752f734b0a7cc83c46f4618af75b8c5911b00ccb61d0ac9b6da0360", size = 56711, upload-time = "2025-02-24T04:41:34.073Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4e/8c/f3147f5c4b73e7550fe5f9352eaa956ae838d5c51eb58e7a25b9f3e2643b/decorator-5.2.1-py3-none-any.whl", hash = "sha256:d316bb415a2d9e2d2b3abcc4084c6502fc09240e292cd76a76afc106a1c8e04a", size = 9190, upload-time = "2025-02-24T04:41:32.565Z" }, +] + +[[package]] +name = "defusedxml" +version = "0.7.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0f/d5/c66da9b79e5bdb124974bfe172b4daf3c984ebd9c2a06e2b8a4dc7331c72/defusedxml-0.7.1.tar.gz", hash = "sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69", size = 75520, upload-time = "2021-03-08T10:59:26.269Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/07/6c/aa3f2f849e01cb6a001cd8554a88d4c77c5c1a31c95bdf1cf9301e6d9ef4/defusedxml-0.7.1-py2.py3-none-any.whl", hash = "sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61", size = 25604, upload-time = "2021-03-08T10:59:24.45Z" }, +] + +[[package]] +name = "executing" +version = "2.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/91/50/a9d80c47ff289c611ff12e63f7c5d13942c65d68125160cefd768c73e6e4/executing-2.2.0.tar.gz", hash = "sha256:5d108c028108fe2551d1a7b2e8b713341e2cb4fc0aa7dcf966fa4327a5226755", size = 978693, upload-time = "2025-01-22T15:41:29.403Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7b/8f/c4d9bafc34ad7ad5d8dc16dd1347ee0e507a52c3adb6bfa8887e1c6a26ba/executing-2.2.0-py2.py3-none-any.whl", hash = "sha256:11387150cad388d62750327a53d3339fad4888b39a6fe233c3afbb54ecffd3aa", size = 26702, upload-time = "2025-01-22T15:41:25.929Z" }, +] + +[[package]] +name = "flake8" +version = "7.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mccabe" }, + { name = "pycodestyle" }, + { name = "pyflakes" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9b/af/fbfe3c4b5a657d79e5c47a2827a362f9e1b763336a52f926126aa6dc7123/flake8-7.3.0.tar.gz", hash = "sha256:fe044858146b9fc69b551a4b490d69cf960fcb78ad1edcb84e7fbb1b4a8e3872", size = 48326, upload-time = "2025-06-20T19:31:35.838Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9f/56/13ab06b4f93ca7cac71078fbe37fcea175d3216f31f85c3168a6bbd0bb9a/flake8-7.3.0-py2.py3-none-any.whl", hash = "sha256:b9696257b9ce8beb888cdbe31cf885c90d31928fe202be0889a7cdafad32f01e", size = 57922, upload-time = "2025-06-20T19:31:34.425Z" }, +] + +[[package]] +name = "flake8-pyproject" +version = "1.2.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "flake8" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/5f/1d/635e86f9f3a96b7ea9e9f19b5efe17a987e765c39ca496e4a893bb999112/flake8_pyproject-1.2.3-py3-none-any.whl", hash = "sha256:6249fe53545205af5e76837644dc80b4c10037e73a0e5db87ff562d75fb5bd4a", size = 4756, upload-time = "2023-03-21T20:51:38.911Z" }, +] + +[[package]] +name = "gssapi" +version = "1.9.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "decorator" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/04/2f/fcffb772a00e658f608e657791484e3111a19a722b464e893fef35f35097/gssapi-1.9.0.tar.gz", hash = "sha256:f468fac8f3f5fca8f4d1ca19e3cd4d2e10bd91074e7285464b22715d13548afe", size = 94285, upload-time = "2024-10-03T06:13:02.484Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/14/39d320ac0c8c8ab05f4b48322d38aacb1572f7a51b2c5b908e51f141e367/gssapi-1.9.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:67d9be5e34403e47fb5749d5a1ad4e5a85b568e6a9add1695edb4a5b879f7560", size = 707912, upload-time = "2024-10-03T06:12:27.354Z" }, + { url = "https://files.pythonhosted.org/packages/cc/04/5d46c5b37b96f87a8efb320ab347e876db2493e1aedaa29068936b063097/gssapi-1.9.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:11e9b92cef11da547fc8c210fa720528fd854038504103c1b15ae2a89dce5fcd", size = 683779, upload-time = "2024-10-03T06:12:29.395Z" }, + { url = "https://files.pythonhosted.org/packages/05/29/b673b4ed994796e133e3e7eeec0d8991b7dcbed6b0b4bfc95ac0fe3871ff/gssapi-1.9.0-cp311-cp311-win32.whl", hash = "sha256:6c5f8a549abd187687440ec0b72e5b679d043d620442b3637d31aa2766b27cbe", size = 776532, upload-time = "2024-10-03T06:12:31.246Z" }, + { url = "https://files.pythonhosted.org/packages/31/07/3bb8521da3ca89e202b50f8de46a9e8e793be7f24318a4f7aaaa022d15d1/gssapi-1.9.0-cp311-cp311-win_amd64.whl", hash = "sha256:59e1a1a9a6c5dc430dc6edfcf497f5ca00cf417015f781c9fac2e85652cd738f", size = 874225, upload-time = "2024-10-03T06:12:33.105Z" }, + { url = "https://files.pythonhosted.org/packages/98/f1/76477c66aa9f2abc9ab53f936e9085402d6697db93834437e5ee651e5106/gssapi-1.9.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b66a98827fbd2864bf8993677a039d7ba4a127ca0d2d9ed73e0ef4f1baa7fd7f", size = 698148, upload-time = "2024-10-03T06:12:34.545Z" }, + { url = "https://files.pythonhosted.org/packages/96/34/b737e2a46efc63c6a6ad3baf0f3a8484d7698e673874b060a7d52abfa7b4/gssapi-1.9.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2bddd1cc0c9859c5e0fd96d4d88eb67bd498fdbba45b14cdccfe10bfd329479f", size = 681597, upload-time = "2024-10-03T06:12:36.435Z" }, + { url = "https://files.pythonhosted.org/packages/71/4b/4cbb8b6bc34ed02591e05af48bd4722facb99b10defc321e3b177114dbeb/gssapi-1.9.0-cp312-cp312-win32.whl", hash = "sha256:10134db0cf01bd7d162acb445762dbcc58b5c772a613e17c46cf8ad956c4dfec", size = 770295, upload-time = "2024-10-03T06:12:37.859Z" }, + { url = "https://files.pythonhosted.org/packages/c1/73/33a65e9d6c5ea43cdb1ee184b201678adaf3a7bbb4f7a1c7a80195c884ac/gssapi-1.9.0-cp312-cp312-win_amd64.whl", hash = "sha256:e28c7d45da68b7e36ed3fb3326744bfe39649f16e8eecd7b003b082206039c76", size = 867625, upload-time = "2024-10-03T06:12:39.518Z" }, + { url = "https://files.pythonhosted.org/packages/bc/bb/6fbbeff852b6502e1d33858865822ab2e0efd84764caad1ce9e3ed182b53/gssapi-1.9.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:cea344246935b5337e6f8a69bb6cc45619ab3a8d74a29fcb0a39fd1e5843c89c", size = 686934, upload-time = "2024-10-03T06:12:41.76Z" }, + { url = "https://files.pythonhosted.org/packages/c9/72/89eeb28a2cebe8ec3a560be79e89092913d6cf9dc68b32eb4774e8bac785/gssapi-1.9.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1a5786bd9fcf435bd0c87dc95ae99ad68cefcc2bcc80c71fef4cb0ccdfb40f1e", size = 672249, upload-time = "2024-10-03T06:12:43.7Z" }, + { url = "https://files.pythonhosted.org/packages/5f/f7/3d9d4a198e34b844dc4acb25891e2405f8dca069a8f346f51127196436bc/gssapi-1.9.0-cp313-cp313-win32.whl", hash = "sha256:c99959a9dd62358e370482f1691e936cb09adf9a69e3e10d4f6a097240e9fd28", size = 755372, upload-time = "2024-10-03T06:12:45.758Z" }, + { url = "https://files.pythonhosted.org/packages/67/00/f4be5211d5dd8e9ca551ded3071b1433880729006768123e1ee7b744b1d8/gssapi-1.9.0-cp313-cp313-win_amd64.whl", hash = "sha256:a2e43f50450e81fe855888c53df70cdd385ada979db79463b38031710a12acd9", size = 845005, upload-time = "2024-10-03T06:12:47.885Z" }, +] + +[[package]] +name = "icecream" +version = "2.1.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "asttokens" }, + { name = "colorama" }, + { name = "executing" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0e/5f/9635c20f14f558768e89b0e20c32ca852640b8973e102ff2f6ee229bbebc/icecream-2.1.5.tar.gz", hash = "sha256:14d21e3383326a69a8c1a3bcf11f83283459f0d269ece5af83fce2c0d663efec", size = 16744, upload-time = "2025-06-25T18:02:31.44Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/36/0a/9496191b82b2839cc92fc713a5888931ab301dfcc55b9d5a49665cdb2aa5/icecream-2.1.5-py3-none-any.whl", hash = "sha256:c020917c5cb180a528dbba170250ccd8b74ebc75f2a7a8e839819bf959b9f80c", size = 14378, upload-time = "2025-06-25T18:02:30.262Z" }, +] + +[[package]] +name = "idna" +version = "3.10" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490, upload-time = "2024-09-15T18:07:39.745Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442, upload-time = "2024-09-15T18:07:37.964Z" }, +] + +[[package]] +name = "koji" +version = "1.35.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "defusedxml" }, + { name = "python-dateutil" }, + { name = "requests" }, + { name = "requests-gssapi" }, + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/15/72/7808fff5c381df25f413523638153c6c8f0ccf0194704b03e10f1939fe98/koji-1.35.3.tar.gz", hash = "sha256:a72f4cad73ade373e34fd96ef9017a0f01920ad22b6ee674705d6b524a59ada4", size = 220039, upload-time = "2025-04-28T20:47:19.99Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/32/95/57fef6449f2ed953de290f80f6194a184bb922a6e97a72dce339a669a092/koji-1.35.3-py3-none-any.whl", hash = "sha256:7c3c0a18a2efe7d2370103829ca87843e900e59d714555c88fb6e76c9c45d224", size = 227513, upload-time = "2025-04-28T20:47:18.791Z" }, +] + +[[package]] +name = "koji-utils" +source = { editable = "." } +dependencies = [ + { name = "koji" }, + { name = "specfile" }, +] + +[package.dev-dependencies] +dev = [ + { name = "flake8" }, + { name = "flake8-pyproject" }, + { name = "icecream" }, + { name = "mypy" }, + { name = "pyright" }, + { name = "ruff" }, + { name = "typing-extensions" }, +] + +[package.metadata] +requires-dist = [ + { name = "koji" }, + { name = "specfile" }, +] + +[package.metadata.requires-dev] +dev = [ + { name = "flake8" }, + { name = "flake8-pyproject" }, + { name = "icecream" }, + { name = "mypy" }, + { name = "pyright" }, + { name = "ruff" }, + { name = "typing-extensions" }, +] + +[[package]] +name = "mccabe" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e7/ff/0ffefdcac38932a54d2b5eed4e0ba8a408f215002cd178ad1df0f2806ff8/mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325", size = 9658, upload-time = "2022-01-24T01:14:51.113Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/27/1a/1f68f9ba0c207934b35b86a8ca3aad8395a3d6dd7921c0686e23853ff5a9/mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e", size = 7350, upload-time = "2022-01-24T01:14:49.62Z" }, +] + +[[package]] +name = "mypy" +version = "1.17.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mypy-extensions" }, + { name = "pathspec" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1e/e3/034322d5a779685218ed69286c32faa505247f1f096251ef66c8fd203b08/mypy-1.17.0.tar.gz", hash = "sha256:e5d7ccc08ba089c06e2f5629c660388ef1fee708444f1dee0b9203fa031dee03", size = 3352114, upload-time = "2025-07-14T20:34:30.181Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d4/24/82efb502b0b0f661c49aa21cfe3e1999ddf64bf5500fc03b5a1536a39d39/mypy-1.17.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9d4fe5c72fd262d9c2c91c1117d16aac555e05f5beb2bae6a755274c6eec42be", size = 10914150, upload-time = "2025-07-14T20:31:51.985Z" }, + { url = "https://files.pythonhosted.org/packages/03/96/8ef9a6ff8cedadff4400e2254689ca1dc4b420b92c55255b44573de10c54/mypy-1.17.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d96b196e5c16f41b4f7736840e8455958e832871990c7ba26bf58175e357ed61", size = 10039845, upload-time = "2025-07-14T20:32:30.527Z" }, + { url = "https://files.pythonhosted.org/packages/df/32/7ce359a56be779d38021d07941cfbb099b41411d72d827230a36203dbb81/mypy-1.17.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:73a0ff2dd10337ceb521c080d4147755ee302dcde6e1a913babd59473904615f", size = 11837246, upload-time = "2025-07-14T20:32:01.28Z" }, + { url = "https://files.pythonhosted.org/packages/82/16/b775047054de4d8dbd668df9137707e54b07fe18c7923839cd1e524bf756/mypy-1.17.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:24cfcc1179c4447854e9e406d3af0f77736d631ec87d31c6281ecd5025df625d", size = 12571106, upload-time = "2025-07-14T20:34:26.942Z" }, + { url = "https://files.pythonhosted.org/packages/a1/cf/fa33eaf29a606102c8d9ffa45a386a04c2203d9ad18bf4eef3e20c43ebc8/mypy-1.17.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3c56f180ff6430e6373db7a1d569317675b0a451caf5fef6ce4ab365f5f2f6c3", size = 12759960, upload-time = "2025-07-14T20:33:42.882Z" }, + { url = "https://files.pythonhosted.org/packages/94/75/3f5a29209f27e739ca57e6350bc6b783a38c7621bdf9cac3ab8a08665801/mypy-1.17.0-cp311-cp311-win_amd64.whl", hash = "sha256:eafaf8b9252734400f9b77df98b4eee3d2eecab16104680d51341c75702cad70", size = 9503888, upload-time = "2025-07-14T20:32:34.392Z" }, + { url = "https://files.pythonhosted.org/packages/12/e9/e6824ed620bbf51d3bf4d6cbbe4953e83eaf31a448d1b3cfb3620ccb641c/mypy-1.17.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f986f1cab8dbec39ba6e0eaa42d4d3ac6686516a5d3dccd64be095db05ebc6bb", size = 11086395, upload-time = "2025-07-14T20:34:11.452Z" }, + { url = "https://files.pythonhosted.org/packages/ba/51/a4afd1ae279707953be175d303f04a5a7bd7e28dc62463ad29c1c857927e/mypy-1.17.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:51e455a54d199dd6e931cd7ea987d061c2afbaf0960f7f66deef47c90d1b304d", size = 10120052, upload-time = "2025-07-14T20:33:09.897Z" }, + { url = "https://files.pythonhosted.org/packages/8a/71/19adfeac926ba8205f1d1466d0d360d07b46486bf64360c54cb5a2bd86a8/mypy-1.17.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3204d773bab5ff4ebbd1f8efa11b498027cd57017c003ae970f310e5b96be8d8", size = 11861806, upload-time = "2025-07-14T20:32:16.028Z" }, + { url = "https://files.pythonhosted.org/packages/0b/64/d6120eca3835baf7179e6797a0b61d6c47e0bc2324b1f6819d8428d5b9ba/mypy-1.17.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1051df7ec0886fa246a530ae917c473491e9a0ba6938cfd0ec2abc1076495c3e", size = 12744371, upload-time = "2025-07-14T20:33:33.503Z" }, + { url = "https://files.pythonhosted.org/packages/1f/dc/56f53b5255a166f5bd0f137eed960e5065f2744509dfe69474ff0ba772a5/mypy-1.17.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f773c6d14dcc108a5b141b4456b0871df638eb411a89cd1c0c001fc4a9d08fc8", size = 12914558, upload-time = "2025-07-14T20:33:56.961Z" }, + { url = "https://files.pythonhosted.org/packages/69/ac/070bad311171badc9add2910e7f89271695a25c136de24bbafc7eded56d5/mypy-1.17.0-cp312-cp312-win_amd64.whl", hash = "sha256:1619a485fd0e9c959b943c7b519ed26b712de3002d7de43154a489a2d0fd817d", size = 9585447, upload-time = "2025-07-14T20:32:20.594Z" }, + { url = "https://files.pythonhosted.org/packages/be/7b/5f8ab461369b9e62157072156935cec9d272196556bdc7c2ff5f4c7c0f9b/mypy-1.17.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:2c41aa59211e49d717d92b3bb1238c06d387c9325d3122085113c79118bebb06", size = 11070019, upload-time = "2025-07-14T20:32:07.99Z" }, + { url = "https://files.pythonhosted.org/packages/9c/f8/c49c9e5a2ac0badcc54beb24e774d2499748302c9568f7f09e8730e953fa/mypy-1.17.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0e69db1fb65b3114f98c753e3930a00514f5b68794ba80590eb02090d54a5d4a", size = 10114457, upload-time = "2025-07-14T20:33:47.285Z" }, + { url = "https://files.pythonhosted.org/packages/89/0c/fb3f9c939ad9beed3e328008b3fb90b20fda2cddc0f7e4c20dbefefc3b33/mypy-1.17.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:03ba330b76710f83d6ac500053f7727270b6b8553b0423348ffb3af6f2f7b889", size = 11857838, upload-time = "2025-07-14T20:33:14.462Z" }, + { url = "https://files.pythonhosted.org/packages/4c/66/85607ab5137d65e4f54d9797b77d5a038ef34f714929cf8ad30b03f628df/mypy-1.17.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:037bc0f0b124ce46bfde955c647f3e395c6174476a968c0f22c95a8d2f589bba", size = 12731358, upload-time = "2025-07-14T20:32:25.579Z" }, + { url = "https://files.pythonhosted.org/packages/73/d0/341dbbfb35ce53d01f8f2969facbb66486cee9804048bf6c01b048127501/mypy-1.17.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:c38876106cb6132259683632b287238858bd58de267d80defb6f418e9ee50658", size = 12917480, upload-time = "2025-07-14T20:34:21.868Z" }, + { url = "https://files.pythonhosted.org/packages/64/63/70c8b7dbfc520089ac48d01367a97e8acd734f65bd07813081f508a8c94c/mypy-1.17.0-cp313-cp313-win_amd64.whl", hash = "sha256:d30ba01c0f151998f367506fab31c2ac4527e6a7b2690107c7a7f9e3cb419a9c", size = 9589666, upload-time = "2025-07-14T20:34:16.841Z" }, + { url = "https://files.pythonhosted.org/packages/e3/fc/ee058cc4316f219078464555873e99d170bde1d9569abd833300dbeb484a/mypy-1.17.0-py3-none-any.whl", hash = "sha256:15d9d0018237ab058e5de3d8fce61b6fa72cc59cc78fd91f1b474bce12abf496", size = 2283195, upload-time = "2025-07-14T20:31:54.753Z" }, +] + +[[package]] +name = "mypy-extensions" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/6e/371856a3fb9d31ca8dac321cda606860fa4548858c0cc45d9d1d4ca2628b/mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558", size = 6343, upload-time = "2025-04-22T14:54:24.164Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963, upload-time = "2025-04-22T14:54:22.983Z" }, +] + +[[package]] +name = "nodeenv" +version = "1.9.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/43/16/fc88b08840de0e0a72a2f9d8c6bae36be573e475a6326ae854bcc549fc45/nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f", size = 47437, upload-time = "2024-06-04T18:44:11.171Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d2/1d/1b658dbd2b9fa9c4c9f32accbfc0205d532c8c6194dc0f2a4c0428e7128a/nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9", size = 22314, upload-time = "2024-06-04T18:44:08.352Z" }, +] + +[[package]] +name = "pathspec" +version = "0.12.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ca/bc/f35b8446f4531a7cb215605d100cd88b7ac6f44ab3fc94870c120ab3adbf/pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712", size = 51043, upload-time = "2023-12-10T22:30:45Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", size = 31191, upload-time = "2023-12-10T22:30:43.14Z" }, +] + +[[package]] +name = "pycodestyle" +version = "2.14.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/11/e0/abfd2a0d2efe47670df87f3e3a0e2edda42f055053c85361f19c0e2c1ca8/pycodestyle-2.14.0.tar.gz", hash = "sha256:c4b5b517d278089ff9d0abdec919cd97262a3367449ea1c8b49b91529167b783", size = 39472, upload-time = "2025-06-20T18:49:48.75Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d7/27/a58ddaf8c588a3ef080db9d0b7e0b97215cee3a45df74f3a94dbbf5c893a/pycodestyle-2.14.0-py2.py3-none-any.whl", hash = "sha256:dd6bf7cb4ee77f8e016f9c8e74a35ddd9f67e1d5fd4184d86c3b98e07099f42d", size = 31594, upload-time = "2025-06-20T18:49:47.491Z" }, +] + +[[package]] +name = "pyflakes" +version = "3.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/45/dc/fd034dc20b4b264b3d015808458391acbf9df40b1e54750ef175d39180b1/pyflakes-3.4.0.tar.gz", hash = "sha256:b24f96fafb7d2ab0ec5075b7350b3d2d2218eab42003821c06344973d3ea2f58", size = 64669, upload-time = "2025-06-20T18:45:27.834Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c2/2f/81d580a0fb83baeb066698975cb14a618bdbed7720678566f1b046a95fe8/pyflakes-3.4.0-py2.py3-none-any.whl", hash = "sha256:f742a7dbd0d9cb9ea41e9a24a918996e8170c799fa528688d40dd582c8265f4f", size = 63551, upload-time = "2025-06-20T18:45:26.937Z" }, +] + +[[package]] +name = "pygments" +version = "2.19.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, +] + +[[package]] +name = "pyright" +version = "1.1.403" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "nodeenv" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fe/f6/35f885264ff08c960b23d1542038d8da86971c5d8c955cfab195a4f672d7/pyright-1.1.403.tar.gz", hash = "sha256:3ab69b9f41c67fb5bbb4d7a36243256f0d549ed3608678d381d5f51863921104", size = 3913526, upload-time = "2025-07-09T07:15:52.882Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/49/b6/b04e5c2f41a5ccad74a1a4759da41adb20b4bc9d59a5e08d29ba60084d07/pyright-1.1.403-py3-none-any.whl", hash = "sha256:c0eeca5aa76cbef3fcc271259bbd785753c7ad7bcac99a9162b4c4c7daed23b3", size = 5684504, upload-time = "2025-07-09T07:15:50.958Z" }, +] + +[[package]] +name = "python-dateutil" +version = "2.9.0.post0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432, upload-time = "2024-03-01T18:36:20.211Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" }, +] + +[[package]] +name = "requests" +version = "2.32.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "charset-normalizer" }, + { name = "idna" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e1/0a/929373653770d8a0d7ea76c37de6e41f11eb07559b103b1c02cafb3f7cf8/requests-2.32.4.tar.gz", hash = "sha256:27d0316682c8a29834d3264820024b62a36942083d52caf2f14c0591336d3422", size = 135258, upload-time = "2025-06-09T16:43:07.34Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7c/e4/56027c4a6b4ae70ca9de302488c5ca95ad4a39e190093d6c1a8ace08341b/requests-2.32.4-py3-none-any.whl", hash = "sha256:27babd3cda2a6d50b30443204ee89830707d396671944c998b5975b031ac2b2c", size = 64847, upload-time = "2025-06-09T16:43:05.728Z" }, +] + +[[package]] +name = "requests-gssapi" +version = "1.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "gssapi" }, + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cf/65/3fcdb60ef9130ea857422651dd4a90e44a6991f7cb832d45872e492649bd/requests-gssapi-1.3.0.tar.gz", hash = "sha256:4d52bf8c2aa2a829130efcca85c14943fdd0aa75455aab985b2b8726159c20ca", size = 18681, upload-time = "2024-02-16T02:38:07.76Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/dd/e6/86bf684d56220efd3c1d937b6646230abd17d3b482dcd94a63addccf4650/requests_gssapi-1.3.0-py3-none-any.whl", hash = "sha256:f36303cd989c54d54630c30f1ef73d7e23acdede2285c941631192e51b4da418", size = 12319, upload-time = "2024-02-16T02:38:05.951Z" }, +] + +[[package]] +name = "rpm" +version = "0.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/bd/ce/8db44d2b8fd6713a59e391d12b6816854b7bee8121ae7370c2d565de4265/rpm-0.4.0.tar.gz", hash = "sha256:79adbefa82318e2625d6e4fa16666cf88543498a1f2c10dc3879165d1dc3ecee", size = 11237, upload-time = "2025-04-08T08:57:26.261Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/17/96/0f4e9ba318c6e09b76357ee88d6c73101f8799f8ed707cfdc1df131b4234/rpm-0.4.0-py3-none-any.whl", hash = "sha256:0ef697cb5fb73bf9300a13d423529d7ec215239bf95c5ecb145e6610645f6067", size = 5151, upload-time = "2025-04-08T08:57:24.971Z" }, +] + +[[package]] +name = "ruff" +version = "0.12.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c3/2a/43955b530c49684d3c38fcda18c43caf91e99204c2a065552528e0552d4f/ruff-0.12.3.tar.gz", hash = "sha256:f1b5a4b6668fd7b7ea3697d8d98857390b40c1320a63a178eee6be0899ea2d77", size = 4459341, upload-time = "2025-07-11T13:21:16.086Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e2/fd/b44c5115539de0d598d75232a1cc7201430b6891808df111b8b0506aae43/ruff-0.12.3-py3-none-linux_armv6l.whl", hash = "sha256:47552138f7206454eaf0c4fe827e546e9ddac62c2a3d2585ca54d29a890137a2", size = 10430499, upload-time = "2025-07-11T13:20:26.321Z" }, + { url = "https://files.pythonhosted.org/packages/43/c5/9eba4f337970d7f639a37077be067e4ec80a2ad359e4cc6c5b56805cbc66/ruff-0.12.3-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:0a9153b000c6fe169bb307f5bd1b691221c4286c133407b8827c406a55282041", size = 11213413, upload-time = "2025-07-11T13:20:30.017Z" }, + { url = "https://files.pythonhosted.org/packages/e2/2c/fac3016236cf1fe0bdc8e5de4f24c76ce53c6dd9b5f350d902549b7719b2/ruff-0.12.3-py3-none-macosx_11_0_arm64.whl", hash = "sha256:fa6b24600cf3b750e48ddb6057e901dd5b9aa426e316addb2a1af185a7509882", size = 10586941, upload-time = "2025-07-11T13:20:33.046Z" }, + { url = "https://files.pythonhosted.org/packages/c5/0f/41fec224e9dfa49a139f0b402ad6f5d53696ba1800e0f77b279d55210ca9/ruff-0.12.3-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e2506961bf6ead54887ba3562604d69cb430f59b42133d36976421bc8bd45901", size = 10783001, upload-time = "2025-07-11T13:20:35.534Z" }, + { url = "https://files.pythonhosted.org/packages/0d/ca/dd64a9ce56d9ed6cad109606ac014860b1c217c883e93bf61536400ba107/ruff-0.12.3-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c4faaff1f90cea9d3033cbbcdf1acf5d7fb11d8180758feb31337391691f3df0", size = 10269641, upload-time = "2025-07-11T13:20:38.459Z" }, + { url = "https://files.pythonhosted.org/packages/63/5c/2be545034c6bd5ce5bb740ced3e7014d7916f4c445974be11d2a406d5088/ruff-0.12.3-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40dced4a79d7c264389de1c59467d5d5cefd79e7e06d1dfa2c75497b5269a5a6", size = 11875059, upload-time = "2025-07-11T13:20:41.517Z" }, + { url = "https://files.pythonhosted.org/packages/8e/d4/a74ef1e801ceb5855e9527dae105eaff136afcb9cc4d2056d44feb0e4792/ruff-0.12.3-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:0262d50ba2767ed0fe212aa7e62112a1dcbfd46b858c5bf7bbd11f326998bafc", size = 12658890, upload-time = "2025-07-11T13:20:44.442Z" }, + { url = "https://files.pythonhosted.org/packages/13/c8/1057916416de02e6d7c9bcd550868a49b72df94e3cca0aeb77457dcd9644/ruff-0.12.3-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:12371aec33e1a3758597c5c631bae9a5286f3c963bdfb4d17acdd2d395406687", size = 12232008, upload-time = "2025-07-11T13:20:47.374Z" }, + { url = "https://files.pythonhosted.org/packages/f5/59/4f7c130cc25220392051fadfe15f63ed70001487eca21d1796db46cbcc04/ruff-0.12.3-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:560f13b6baa49785665276c963edc363f8ad4b4fc910a883e2625bdb14a83a9e", size = 11499096, upload-time = "2025-07-11T13:20:50.348Z" }, + { url = "https://files.pythonhosted.org/packages/d4/01/a0ad24a5d2ed6be03a312e30d32d4e3904bfdbc1cdbe63c47be9d0e82c79/ruff-0.12.3-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:023040a3499f6f974ae9091bcdd0385dd9e9eb4942f231c23c57708147b06311", size = 11688307, upload-time = "2025-07-11T13:20:52.945Z" }, + { url = "https://files.pythonhosted.org/packages/93/72/08f9e826085b1f57c9a0226e48acb27643ff19b61516a34c6cab9d6ff3fa/ruff-0.12.3-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:883d844967bffff5ab28bba1a4d246c1a1b2933f48cb9840f3fdc5111c603b07", size = 10661020, upload-time = "2025-07-11T13:20:55.799Z" }, + { url = "https://files.pythonhosted.org/packages/80/a0/68da1250d12893466c78e54b4a0ff381370a33d848804bb51279367fc688/ruff-0.12.3-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:2120d3aa855ff385e0e562fdee14d564c9675edbe41625c87eeab744a7830d12", size = 10246300, upload-time = "2025-07-11T13:20:58.222Z" }, + { url = "https://files.pythonhosted.org/packages/6a/22/5f0093d556403e04b6fd0984fc0fb32fbb6f6ce116828fd54306a946f444/ruff-0.12.3-py3-none-musllinux_1_2_i686.whl", hash = "sha256:6b16647cbb470eaf4750d27dddc6ebf7758b918887b56d39e9c22cce2049082b", size = 11263119, upload-time = "2025-07-11T13:21:01.503Z" }, + { url = "https://files.pythonhosted.org/packages/92/c9/f4c0b69bdaffb9968ba40dd5fa7df354ae0c73d01f988601d8fac0c639b1/ruff-0.12.3-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:e1417051edb436230023575b149e8ff843a324557fe0a265863b7602df86722f", size = 11746990, upload-time = "2025-07-11T13:21:04.524Z" }, + { url = "https://files.pythonhosted.org/packages/fe/84/7cc7bd73924ee6be4724be0db5414a4a2ed82d06b30827342315a1be9e9c/ruff-0.12.3-py3-none-win32.whl", hash = "sha256:dfd45e6e926deb6409d0616078a666ebce93e55e07f0fb0228d4b2608b2c248d", size = 10589263, upload-time = "2025-07-11T13:21:07.148Z" }, + { url = "https://files.pythonhosted.org/packages/07/87/c070f5f027bd81f3efee7d14cb4d84067ecf67a3a8efb43aadfc72aa79a6/ruff-0.12.3-py3-none-win_amd64.whl", hash = "sha256:a946cf1e7ba3209bdef039eb97647f1c77f6f540e5845ec9c114d3af8df873e7", size = 11695072, upload-time = "2025-07-11T13:21:11.004Z" }, + { url = "https://files.pythonhosted.org/packages/e0/30/f3eaf6563c637b6e66238ed6535f6775480db973c836336e4122161986fc/ruff-0.12.3-py3-none-win_arm64.whl", hash = "sha256:5f9c7c9c8f84c2d7f27e93674d27136fbf489720251544c4da7fb3d742e011b1", size = 10805855, upload-time = "2025-07-11T13:21:13.547Z" }, +] + +[[package]] +name = "six" +version = "1.17.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" }, +] + +[[package]] +name = "specfile" +version = "0.36.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "rpm" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7f/de/93ac6b94f284463da85d16951ad5c4cc5fcfce193c921d40ea0df9a5e141/specfile-0.36.0.tar.gz", hash = "sha256:006b35d7a0fa1a57cccc221e1625ee31a8b64dbaad29a85e898820b9502d1238", size = 113284, upload-time = "2025-05-30T13:09:40.723Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/50/63/a221191c486714bc17dc4783ebbaccf807f7fa849415471302ff3d289e65/specfile-0.36.0-py3-none-any.whl", hash = "sha256:811a11710571cd0c6adb6d40520b7f28a661ec2aea0999cea3b954ac0fc0218a", size = 67447, upload-time = "2025-05-30T13:09:38.799Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.14.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/98/5a/da40306b885cc8c09109dc2e1abd358d5684b1425678151cdaed4731c822/typing_extensions-4.14.1.tar.gz", hash = "sha256:38b39f4aeeab64884ce9f74c94263ef78f3c22467c8724005483154c26648d36", size = 107673, upload-time = "2025-07-04T13:28:34.16Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b5/00/d631e67a838026495268c2f6884f3711a15a9a2a96cd244fdaea53b823fb/typing_extensions-4.14.1-py3-none-any.whl", hash = "sha256:d1e1e3b58374dc93031d6eda2420a48ea44a36c2b4766a4fdeb3710755731d76", size = 43906, upload-time = "2025-07-04T13:28:32.743Z" }, +] + +[[package]] +name = "urllib3" +version = "2.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/15/22/9ee70a2574a4f4599c47dd506532914ce044817c7752a79b6a51286319bc/urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760", size = 393185, upload-time = "2025-06-18T14:07:41.644Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc", size = 129795, upload-time = "2025-06-18T14:07:40.39Z" }, +] diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index c13a87bb..00000000 --- a/setup.cfg +++ /dev/null @@ -1,4 +0,0 @@ -# pycodestyle doesn't support pyproject.toml -[pycodestyle] -max-line-length=120 -ignore=E261,E302,E305,W503