diff --git a/.envrc b/.envrc index 19ce05e9..6985d2d4 100644 --- a/.envrc +++ b/.envrc @@ -3,3 +3,6 @@ if [[ -f .env ]]; then dotenv .env fi + +PATH_add "$PWD/.venv/bin" +export VIRTUAL_ENV="$PWD/.venv" diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 2c54ece7..b83112df 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -15,8 +15,7 @@ jobs: lint: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - uses: actions/setup-python@v4 - with: - python-version: 3.11 + - uses: actions/checkout@v4 + - uses: astral-sh/setup-uv@v5 + - run: uv sync - uses: pre-commit/action@v3.0.0 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 8d09e3ea..45608c63 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -12,10 +12,6 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: actions/setup-python@v5 - with: - python-version: | - 3.11 - 3.12 - - run: pip install tox - - run: tox + - uses: astral-sh/setup-uv@v5 + - run: uv sync + - run: uv run coverage erase && uv run coverage run -m pytest && uv run coverage report diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index d4a3c211..b3156a74 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,30 +1,24 @@ repos: - - repo: https://github.com/asottile/reorder-python-imports - rev: v3.12.0 - hooks: - - id: reorder-python-imports - args: [--py311-plus, --add-import, "from __future__ import annotations"] - - repo: https://github.com/psf/black - rev: 23.10.0 - hooks: - - id: black - language_version: python3.11 - - repo: https://github.com/PyCQA/flake8 - rev: 6.1.0 - hooks: - - id: flake8 - language_version: python3.11 - - repo: https://github.com/pre-commit/mirrors-mypy - rev: v1.14.1 + - repo: local hooks: + - id: ruff-check + name: ruff check + entry: .venv/bin/ruff check --fix + language: system + types: [python] + - id: ruff-format + name: ruff format + entry: .venv/bin/ruff format + language: system + types: [python] - id: mypy - additional_dependencies: - - "pytest" - # I'd like to just: - # - "-r requirements-dev.in" - - repo: https://github.com/shellcheck-py/shellcheck-py - rev: v0.9.0.6 - hooks: + name: mypy + entry: .venv/bin/mypy + language: system + types: [python] - id: shellcheck + name: shellcheck + entry: .venv/bin/shellcheck + language: system + types: [shell] args: [--norc] - diff --git a/.python-version b/.python-version new file mode 100644 index 00000000..0c7d5f5f --- /dev/null +++ b/.python-version @@ -0,0 +1 @@ +3.11.4 diff --git a/AGENTS.md b/AGENTS.md index aef0be12..9dab5f6a 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -10,26 +10,23 @@ Co-Authored-By: Claude Opus 4.5 ## Setup ```bash -python3.11 -m venv .venv -source .venv/bin/activate -pip install -r requirements-dev.in -e . +uv sync ``` ## Running Tests ```bash -source .venv/bin/activate -pytest tests/ +uv run pytest tests/ ``` -Or with tox: `tox -e py311` +With coverage: `uv run coverage erase && uv run coverage run -m pytest && uv run coverage report` ## Code Style - Python 3.11+ -- Black formatter, 80 char line length +- Ruff formatter/linter, 80 char line length - Strict mypy/pyright typing -- Run `black .` and `mypy .` before committing +- Run `ruff check --fix .`, `ruff format .`, and `mypy .` before committing ## Project Structure diff --git a/Makefile b/Makefile index 2da55c53..3e9cfd56 100644 --- a/Makefile +++ b/Makefile @@ -1,13 +1,5 @@ test: docker-image gh act -W .github/workflows/bootstrap.yml --pull=false -venv: requirements-dev.in - rm -rf venv # prevent recursion - python3.11 -m venv --clear --upgrade-deps --prompt=devenv venv || \ - rm -rf venv # failure - ./venv/bin/pip install -r requirements-dev.in -e . || \ - rm -rf venv # failure - touch venv # success - docker-image: cd lib/gh-act && make diff --git a/README.md b/README.md index 25dadb5c..ac394ac8 100644 --- a/README.md +++ b/README.md @@ -350,8 +350,9 @@ version = v1.3.0 ## develop -We use `tox`. The easiest way to run devenv locally is just using the tox venv's executable: +We use `uv`. The easiest way to run devenv locally: ``` -~/code/sentry $ ~/code/devenv/.tox/py311/bin/devenv sync +~/code/devenv $ uv sync +~/code/sentry $ uv run --project ~/code/devenv devenv sync ``` diff --git a/devenv/bootstrap.py b/devenv/bootstrap.py index b3f146a7..e6479b31 100644 --- a/devenv/bootstrap.py +++ b/devenv/bootstrap.py @@ -83,8 +83,7 @@ def main(context: Context, argv: Sequence[str] | None = None) -> ExitCode: return 1 pubkey = github.generate_and_configure_ssh_keypair() if not constants.CI: - input( - f""" + input(f""" Failed to authenticate with an ssh key to GitHub. We've generated and configured one for you at ~/.ssh/sentry-github. Visit https://github.com/settings/ssh/new and add the following Authentication key: @@ -95,19 +94,16 @@ def main(context: Context, argv: Sequence[str] | None = None) -> ExitCode: and click Configure SSO, for the getsentry organization. When done, hit ENTER to continue. -""" - ) +""") while not github.check_ssh_access(): input( "Still failing to authenticate to GitHub. ENTER to retry, otherwise ^C to quit." ) # Mirror this in bootstrap.py. - print( - f"""\ + print(f"""\ Updating global tools (at {constants.root}/bin). -""" - ) +""") os.makedirs(f"{constants.root}/bin", exist_ok=True) if constants.DARWIN: @@ -121,14 +117,12 @@ def main(context: Context, argv: Sequence[str] | None = None) -> ExitCode: os.makedirs(context["code_root"], exist_ok=True) - print( - """ + print(""" All done! Please close this terminal window and start a fresh one. Afterward, start working on your project using the devenv fetch command e.g., devenv fetch sentry or devenv fetch ops -""" - ) +""") return 0 diff --git a/devenv/checks/limaDns.py b/devenv/checks/limaDns.py index 190fe918..3e83ead3 100644 --- a/devenv/checks/limaDns.py +++ b/devenv/checks/limaDns.py @@ -67,14 +67,12 @@ def fix() -> tuple[bool, str]: stdout=True, ) except RuntimeError as e: - print( - f""" + print(f""" failed to restart the vm's resolved: {e} we're going to try restarting colima -""" - ) +""") try: proc.run( (sys.executable, "-P", "-m", "devenv", "colima", "restart") diff --git a/devenv/doctor.py b/devenv/doctor.py index 4f2c9e07..4d0db748 100644 --- a/devenv/doctor.py +++ b/devenv/doctor.py @@ -164,7 +164,7 @@ def run_checks( def filter_failing_checks( - results: Dict[Check, tuple[bool, str]] + results: Dict[Check, tuple[bool, str]], ) -> List[Check]: """Print a report of the results, and return a list of failing checks.""" failing_checks: list[Check] = [] diff --git a/devenv/fetch.py b/devenv/fetch.py index bad76a09..3a8bd346 100644 --- a/devenv/fetch.py +++ b/devenv/fetch.py @@ -44,15 +44,13 @@ def main(context: Context, argv: Sequence[str] | None = None) -> ExitCode: if not constants.CI and not constants.EXTERNAL_CONTRIBUTOR: fetch(code_root, "getsentry/getsentry") - print( - f""" + print(f""" All done! Please close this terminal window and start a fresh one. Sentry has been set up in {code_root}/sentry. cd into it and you should be able to run `sentry devserver`. -""" - ) +""") else: fetch(code_root, args.repo) diff --git a/devenv/lib/archive.py b/devenv/lib/archive.py index 72468bef..36f307a4 100644 --- a/devenv/lib/archive.py +++ b/devenv/lib/archive.py @@ -103,15 +103,15 @@ def strip1( members: Sequence[tarfile.TarInfo], ) -> Generator[tarfile.TarInfo, None, None]: for member in members: - i = member.path.find("/") + i = member.name.find("/") if i == -1: continue elif i == 0: - i = member.path[1:].find("/") + 1 + i = member.name[1:].find("/") + 1 if i == 0: continue - member.path = member.path[i + 1 :] # noqa: E203 + member.name = member.name[i + 1 :] # noqa: E203 # hardlink target (linkname) is relative to the root of the # archive and must also be stripped @@ -135,7 +135,7 @@ def unpack( if strip1_new_prefix: for member in members: - member.path = f"{strip1_new_prefix}/{member.path}" + member.name = f"{strip1_new_prefix}/{member.name}" tarf.extractall(into, members=members, filter="tar") @@ -150,6 +150,6 @@ def unpack_strip_n(path: str, into: str, n: int, new_prefix: str = "") -> None: if new_prefix: for member in members: - member.path = f"{new_prefix}/{member.path}" + member.name = f"{new_prefix}/{member.name}" tarf.extractall(into, members=members, filter="tar") diff --git a/devenv/lib/brew.py b/devenv/lib/brew.py index c88f9982..28d7e004 100644 --- a/devenv/lib/brew.py +++ b/devenv/lib/brew.py @@ -4,9 +4,9 @@ from shutil import which from devenv.constants import DARWIN +from devenv.constants import INTEL_MAC from devenv.constants import homebrew_bin from devenv.constants import homebrew_repo -from devenv.constants import INTEL_MAC from devenv.constants import user from devenv.lib import fs from devenv.lib import proc diff --git a/devenv/lib/colima.py b/devenv/lib/colima.py index 1e448a15..bd738be6 100644 --- a/devenv/lib/colima.py +++ b/devenv/lib/colima.py @@ -6,16 +6,15 @@ import tempfile from enum import Enum +from devenv.constants import SYSTEM_MACHINE from devenv.constants import home from devenv.constants import root -from devenv.constants import SYSTEM_MACHINE from devenv.lib import archive from devenv.lib import docker from devenv.lib import fs from devenv.lib import proc from devenv.lib import rosetta - ColimaStatus = Enum("ColimaStatus", ("UP", "DOWN", "UNHEALTHY")) @@ -174,7 +173,12 @@ def start(restart: bool = False) -> ColimaStatus: raise SystemExit("failed to determine memsize_bytes") memsize_bytes = os.sysconf("SC_PAGE_SIZE") * os.sysconf("SC_PHYS_PAGES") - args = ["--cpu", f"{cpus//2}", "--memory", f"{memsize_bytes//(2*1024**3)}"] + args = [ + "--cpu", + f"{cpus // 2}", + "--memory", + f"{memsize_bytes // (2 * 1024**3)}", + ] if platform.machine() == "arm64": args = [*args, "--vm-type=vz", "--vz-rosetta", "--mount-type=virtiofs"] diff --git a/devenv/lib/config.py b/devenv/lib/config.py index 625ff699..9d17610a 100644 --- a/devenv/lib/config.py +++ b/devenv/lib/config.py @@ -55,9 +55,9 @@ def initialize_config(config_path: str, defaults: Config) -> None: val: str = config.get( section, var, - fallback=(opts.default() if opts else "") - if _val is None - else _val, + fallback=( + (opts.default() if opts else "") if _val is None else _val + ), ) if not CI: diff --git a/devenv/lib/direnv.py b/devenv/lib/direnv.py index 00f23dae..920b15d3 100644 --- a/devenv/lib/direnv.py +++ b/devenv/lib/direnv.py @@ -30,8 +30,7 @@ def install() -> None: machine = "arm64" if MACHINE == "arm64" else "amd64" name = f"direnv.{sys.platform}-{machine}" url = ( - "https://github.com/direnv/direnv/releases/download" - f"/v{_version}/{name}" + f"https://github.com/direnv/direnv/releases/download/v{_version}/{name}" ) archive.download(url, _sha256[name], dest=direnv_path) diff --git a/devenv/lib/docker.py b/devenv/lib/docker.py index f2d4b487..d400317c 100644 --- a/devenv/lib/docker.py +++ b/devenv/lib/docker.py @@ -7,9 +7,9 @@ import tempfile from threading import Thread +from devenv.constants import SYSTEM_MACHINE from devenv.constants import home from devenv.constants import root -from devenv.constants import SYSTEM_MACHINE from devenv.lib import archive from devenv.lib import proc diff --git a/devenv/lib/fs.py b/devenv/lib/fs.py index b8c46044..e047056d 100644 --- a/devenv/lib/fs.py +++ b/devenv/lib/fs.py @@ -36,7 +36,8 @@ def shellrc() -> str: def gitroot(cd: str = "") -> str: - from os.path import normpath, join + from os.path import join + from os.path import normpath if not cd: cd = os.getcwd() @@ -74,11 +75,9 @@ def ensure_binroot(reporoot: str) -> str: os.makedirs(binroot, exist_ok=True) if not os.path.exists(f"{binroot}/.gitignore"): with open(f"{binroot}/.gitignore", "w") as f: - f.write( - """* + f.write("""* # automatically written by devenv ensure_binroot! feel free to modify. -""" - ) +""") return binroot diff --git a/devenv/lib/limactl.py b/devenv/lib/limactl.py index dba5476a..1db55196 100644 --- a/devenv/lib/limactl.py +++ b/devenv/lib/limactl.py @@ -4,9 +4,9 @@ import shutil import tempfile +from devenv.constants import SYSTEM_MACHINE from devenv.constants import home from devenv.constants import root -from devenv.constants import SYSTEM_MACHINE from devenv.lib import archive from devenv.lib import proc diff --git a/devenv/lib/node.py b/devenv/lib/node.py index c3c02d09..b3bed2e5 100644 --- a/devenv/lib/node.py +++ b/devenv/lib/node.py @@ -9,7 +9,6 @@ from devenv.lib import fs from devenv.lib import proc - _shims = ("node", "npm", "npx") diff --git a/devenv/lib/proc.py b/devenv/lib/proc.py index 097327d2..1bfcb488 100644 --- a/devenv/lib/proc.py +++ b/devenv/lib/proc.py @@ -41,8 +41,7 @@ def run( env: dict[str, str] | None = None, cwd: Path | str | None = None, stdout: Literal[False] = False, -) -> None: - ... +) -> None: ... @overload @@ -54,8 +53,7 @@ def run( env: dict[str, str] | None = None, cwd: Path | str | None = None, stdout: Literal[True], -) -> str: - ... +) -> str: ... def run( diff --git a/devenv/lib/repository.py b/devenv/lib/repository.py index 62172d40..a6182ed8 100644 --- a/devenv/lib/repository.py +++ b/devenv/lib/repository.py @@ -46,8 +46,7 @@ def check_minimum_version(self) -> None: parsed_minimum_version = tuple(map(int, minimum_version.split("."))) if parsed_version < parsed_minimum_version: - raise SystemExit( - f""" + raise SystemExit(f""" Your devenv version ({version}) doesn't meet the minimum version ({minimum_version}) defined in the repo config. @@ -56,5 +55,4 @@ def check_minimum_version(self) -> None: {constants.root}/bin/devenv update {minimum_version} {constants.root}/bin/devenv sync -""" - ) +""") diff --git a/devenv/lib/venv.py b/devenv/lib/venv.py index d814c151..8213900a 100644 --- a/devenv/lib/venv.py +++ b/devenv/lib/venv.py @@ -117,8 +117,6 @@ def ensure(venv: str, python_version: str, url: str, sha256: str) -> None: ) with open(f"{venv}/.gitignore", "w") as f: - f.write( - """* + f.write("""* # automatically written by devenv -""" - ) +""") diff --git a/devenv/lib_check/types.py b/devenv/lib_check/types.py index 62a68bb2..4e2cc217 100644 --- a/devenv/lib_check/types.py +++ b/devenv/lib_check/types.py @@ -4,7 +4,7 @@ def checker( - f: Callable[[], tuple[bool, str]] + f: Callable[[], tuple[bool, str]], ) -> Callable[[], tuple[bool, str]]: return f diff --git a/devenv/main.py b/devenv/main.py index dfafbc30..c41202a7 100644 --- a/devenv/main.py +++ b/devenv/main.py @@ -97,6 +97,7 @@ def main() -> ExitCode: raise SystemExit("You shouldn't be running devenv as root.") import sys + import sentry_sdk sentry_sdk.init( diff --git a/devenv/pythons.py b/devenv/pythons.py index d8a2b956..281534de 100644 --- a/devenv/pythons.py +++ b/devenv/pythons.py @@ -7,7 +7,6 @@ from devenv.constants import root from devenv.lib import archive - # Forces download of the python interpreter even if the system interpreter is # compatible with the requested version. By default, devenv uses system python # on CI. diff --git a/devenv/update.py b/devenv/update.py index 09a922f7..bab395a7 100644 --- a/devenv/update.py +++ b/devenv/update.py @@ -30,11 +30,9 @@ def main(context: Context, argv: Sequence[str] | None = None) -> int: # This is so that people don't have to run update twice. if args.post_update: # Mirror this in bootstrap.py. - print( - f"""\ + print(f"""\ Updating global tools (at {constants.root}/bin). -""" - ) +""") os.makedirs(f"{constants.root}/bin", exist_ok=True) if constants.DARWIN: @@ -70,12 +68,10 @@ def main(context: Context, argv: Sequence[str] | None = None) -> int: }, ) - print( - f"""\ + print(f"""\ Global devenv at {constants.root}/bin/devenv updated. -""" - ) +""") # install-devenv.sh originally creates this symlink # but we should make sure it exists no matter what @@ -88,11 +84,9 @@ def main(context: Context, argv: Sequence[str] | None = None) -> int: proc.run((sys.executable, "-P", "-m", "devenv", "update", "--post-update")) if not is_global_devenv: - print( - """\ + print("""\ To update the local devenv, you'll want to run `devenv sync`. -""" - ) +""") return 0 diff --git a/pyproject.toml b/pyproject.toml index 855249f5..55115547 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,8 +12,7 @@ authors = [ ] description = "Utilities for setting up a Sentry development environment" readme = "README.md" -# only tested on 3.11+, this is so internal pypi is happy -requires-python = ">=3.10" +requires-python = "==3.11.4" classifiers = [ "Programming Language :: Python :: 3", "License :: OSI Approved :: Apache Software License", @@ -30,9 +29,19 @@ dependencies = [ [tool.setuptools.packages] find = {} -[tool.black] +[tool.ruff] +target-version = "py311" line-length = 80 -target-version = ['py311'] + +[tool.ruff.lint] +select = ["E", "F", "I"] +ignore = ["E501"] + +[tool.ruff.lint.isort] +force-single-line = true +required-imports = ["from __future__ import annotations"] + +[tool.ruff.format] skip-magic-trailing-comma = true @@ -77,3 +86,15 @@ reportMissingParameterType = "none" reportUnknownParameterType = "error" #### don't flag missing typestubs, as long as everything is typed ###reportMissingTypeStubs = false + +[dependency-groups] +dev = [ + "coverage", + "covdefaults", + "mypy", + "pre-commit", + "pytest", + "re-assert", + "ruff", + "shellcheck-py", +] diff --git a/requirements-dev.in b/requirements-dev.in deleted file mode 100644 index c8ec6f12..00000000 --- a/requirements-dev.in +++ /dev/null @@ -1,4 +0,0 @@ -mypy -pytest -pre-commit -black diff --git a/tests/doctor/test_filter_failing_checks.py b/tests/doctor/test_filter_failing_checks.py index 92a68d38..2512c055 100644 --- a/tests/doctor/test_filter_failing_checks.py +++ b/tests/doctor/test_filter_failing_checks.py @@ -34,6 +34,6 @@ def test_filter_failing_checks_no_duplicate_checks() -> None: { first_check: (True, ""), second_check: (False, ""), - second_check: (False, ""), + second_check: (False, ""), # noqa: F602 } ) == [second_check] diff --git a/tests/doctor/test_prompt_for_fix.py b/tests/doctor/test_prompt_for_fix.py index 73e84970..222a85a4 100644 --- a/tests/doctor/test_prompt_for_fix.py +++ b/tests/doctor/test_prompt_for_fix.py @@ -8,7 +8,6 @@ from devenv import doctor from tests.doctor.devenv.checks import passing_check - prompts_for_fix_yes: list[str] = [ "y", "Y", diff --git a/tests/lib/test_archive.py b/tests/lib/test_archive.py index 22596bb4..9557106d 100644 --- a/tests/lib/test_archive.py +++ b/tests/lib/test_archive.py @@ -122,7 +122,7 @@ def tar4(tmp_path: pathlib.Path) -> pathlib.Path: # /foo # /foo/bar for member in tarf.getmembers(): - member.path = f"/{member.path}" + member.name = f"/{member.name}" return tar diff --git a/tests/lib/test_brew.py b/tests/lib/test_brew.py index 734fcfe4..f85dff04 100644 --- a/tests/lib/test_brew.py +++ b/tests/lib/test_brew.py @@ -8,15 +8,14 @@ def test_install_brew_already_installed() -> None: - with patch( - "devenv.lib.brew.which", return_value="/usr/local/bin/brew" - ), patch( - "devenv.lib.brew.add_brew_to_shellrc" - ) as mock_add_brew_to_shellrc, patch( - "devenv.lib.brew.proc.run" - ) as mock_run, patch( - "os.symlink" - ) as mock_symlink: + with ( + patch("devenv.lib.brew.which", return_value="/usr/local/bin/brew"), + patch( + "devenv.lib.brew.add_brew_to_shellrc" + ) as mock_add_brew_to_shellrc, + patch("devenv.lib.brew.proc.run") as mock_run, + patch("os.symlink") as mock_symlink, + ): install() mock_run.assert_not_called() mock_run.assert_not_called() @@ -29,22 +28,21 @@ def test_install_brew_not_installed_intel_mac() -> None: homebrew_repo = "/path/to/homebrew_repo" homebrew_bin = "/path/to/homebrew/bin" - with patch("devenv.lib.brew.which", return_value=None), patch( - "devenv.lib.brew.create_dirs" - ) as mock_create_dirs, patch( - "devenv.lib.brew.add_brew_to_shellrc" - ) as mock_add_brew_to_shellrc, patch( - "devenv.lib.brew.INTEL_MAC", True - ), patch( - "devenv.lib.brew.DARWIN", True - ), patch( - "devenv.lib.brew.proc.run" - ) as mock_run, patch( - "os.symlink" - ) as mock_symlink, patch.multiple( - "devenv.lib.brew", - homebrew_repo=homebrew_repo, - homebrew_bin=homebrew_bin, + with ( + patch("devenv.lib.brew.which", return_value=None), + patch("devenv.lib.brew.create_dirs") as mock_create_dirs, + patch( + "devenv.lib.brew.add_brew_to_shellrc" + ) as mock_add_brew_to_shellrc, + patch("devenv.lib.brew.INTEL_MAC", True), + patch("devenv.lib.brew.DARWIN", True), + patch("devenv.lib.brew.proc.run") as mock_run, + patch("os.symlink") as mock_symlink, + patch.multiple( + "devenv.lib.brew", + homebrew_repo=homebrew_repo, + homebrew_bin=homebrew_bin, + ), ): install() mock_create_dirs.assert_called_once() @@ -70,22 +68,21 @@ def test_install_brew_not_installed_non_intel_mac() -> None: homebrew_repo = "/path/to/homebrew_repo" homebrew_bin = "/path/to/homebrew/bin" - with patch("devenv.lib.brew.which", return_value=None), patch( - "devenv.lib.brew.create_dirs" - ) as mock_create_dirs, patch( - "devenv.lib.brew.add_brew_to_shellrc" - ) as mock_add_brew_to_shellrc, patch( - "devenv.lib.brew.INTEL_MAC", False - ), patch( - "devenv.lib.brew.DARWIN", True - ), patch( - "devenv.lib.brew.proc.run" - ) as mock_run, patch( - "os.symlink" - ) as mock_symlink, patch.multiple( - "devenv.lib.brew", - homebrew_repo=homebrew_repo, - homebrew_bin=homebrew_bin, + with ( + patch("devenv.lib.brew.which", return_value=None), + patch("devenv.lib.brew.create_dirs") as mock_create_dirs, + patch( + "devenv.lib.brew.add_brew_to_shellrc" + ) as mock_add_brew_to_shellrc, + patch("devenv.lib.brew.INTEL_MAC", False), + patch("devenv.lib.brew.DARWIN", True), + patch("devenv.lib.brew.proc.run") as mock_run, + patch("os.symlink") as mock_symlink, + patch.multiple( + "devenv.lib.brew", + homebrew_repo=homebrew_repo, + homebrew_bin=homebrew_bin, + ), ): install() mock_create_dirs.assert_called_once() @@ -110,12 +107,12 @@ def test_add_brew_to_shellrc() -> None: shellrc_path = "/path/to/shellrc" homebrew_bin = "/path/to/homebrew/bin" - with patch( - "devenv.lib.brew.fs.shellrc", return_value=shellrc_path - ) as mock_shellrc, patch( - "devenv.lib.brew.fs.idempotent_add" - ) as mock_idempotent_add, patch( - "devenv.lib.brew.homebrew_bin", homebrew_bin + with ( + patch( + "devenv.lib.brew.fs.shellrc", return_value=shellrc_path + ) as mock_shellrc, + patch("devenv.lib.brew.fs.idempotent_add") as mock_idempotent_add, + patch("devenv.lib.brew.homebrew_bin", homebrew_bin), ): add_brew_to_shellrc() @@ -132,11 +129,14 @@ def test_create_dirs() -> None: INTEL_MAC = False # Mock the proc.run function - with patch("devenv.lib.brew.proc.run") as mock_run, patch.multiple( - "devenv.lib.brew", - user=user, - homebrew_repo=homebrew_repo, - INTEL_MAC=INTEL_MAC, + with ( + patch("devenv.lib.brew.proc.run") as mock_run, + patch.multiple( + "devenv.lib.brew", + user=user, + homebrew_repo=homebrew_repo, + INTEL_MAC=INTEL_MAC, + ), ): create_dirs() @@ -162,11 +162,14 @@ def test_create_dirs_intel_mac() -> None: INTEL_MAC = True # Mock the proc.run function - with patch("devenv.lib.brew.proc.run") as mock_run, patch.multiple( - "devenv.lib.brew", - user=user, - homebrew_repo=homebrew_repo, - INTEL_MAC=INTEL_MAC, + with ( + patch("devenv.lib.brew.proc.run") as mock_run, + patch.multiple( + "devenv.lib.brew", + user=user, + homebrew_repo=homebrew_repo, + INTEL_MAC=INTEL_MAC, + ), ): create_dirs() diff --git a/tests/lib/test_direnv.py b/tests/lib/test_direnv.py index 8ef3d7f0..b6f3ebfb 100644 --- a/tests/lib/test_direnv.py +++ b/tests/lib/test_direnv.py @@ -13,17 +13,18 @@ # imo this is enough coverage (as in just keeping it to bash + darwin-arm64) # we just want to verify that the important functions were called like fs_idempotent_add def test_install() -> None: - with patch("devenv.lib.direnv.MACHINE", "arm64"), patch( - "devenv.lib.direnv.sys.platform", "darwin" - ), patch("devenv.lib.archive.download") as mock_archive_download, patch( - "devenv.lib.fs.idempotent_add" - ) as mock_lib_fs_idempotent_add, patch( - "os.chmod" - ), patch( - "shutil.which", return_value=False - ), patch( - "devenv.lib.direnv.proc.run", side_effect=[_version] # direnv version - ) as mock_proc_run: + with ( + patch("devenv.lib.direnv.MACHINE", "arm64"), + patch("devenv.lib.direnv.sys.platform", "darwin"), + patch("devenv.lib.archive.download") as mock_archive_download, + patch("devenv.lib.fs.idempotent_add") as mock_lib_fs_idempotent_add, + patch("os.chmod"), + patch("shutil.which", return_value=False), + patch( + "devenv.lib.direnv.proc.run", + side_effect=[_version], # direnv version + ) as mock_proc_run, + ): install() assert mock_lib_fs_idempotent_add.mock_calls == [ # SHELL is set in conftest.py diff --git a/tests/lib/test_github.py b/tests/lib/test_github.py index 27f933cf..d121b80c 100644 --- a/tests/lib/test_github.py +++ b/tests/lib/test_github.py @@ -17,15 +17,15 @@ def test_generate_and_configure_ssh_keypair(tmp_path: str) -> None: private_key_path = f"{home}/.ssh/sentry-github" public_key_path = f"{private_key_path}.pub" - with patch("devenv.lib.github.home", home), patch( - "devenv.lib.github.fs.idempotent_add" - ) as mock_idempotent_add, patch( - "devenv.lib.github.os.path.exists", return_value=False - ) as mock_exists, patch( - "devenv.lib.github.proc.run" - ), patch( - "builtins.open" - ) as mock_open: + with ( + patch("devenv.lib.github.home", home), + patch("devenv.lib.github.fs.idempotent_add") as mock_idempotent_add, + patch( + "devenv.lib.github.os.path.exists", return_value=False + ) as mock_exists, + patch("devenv.lib.github.proc.run"), + patch("builtins.open") as mock_open, + ): mock_open.return_value.__enter__.return_value.read.return_value = ( "public_key" ) @@ -62,44 +62,53 @@ def test_check_ssh_authentication_failure() -> None: def test_check_ssh_access_success() -> None: - with patch("devenv.lib.github.CI", False), patch( - "devenv.lib.github.check_ssh_authentication", return_value=True - ), patch("devenv.lib.github.check_sso_configuration", return_value=True): + with ( + patch("devenv.lib.github.CI", False), + patch("devenv.lib.github.check_ssh_authentication", return_value=True), + patch("devenv.lib.github.check_sso_configuration", return_value=True), + ): assert check_ssh_access() def test_check_ssh_access_failure_no_ssh() -> None: - with patch("devenv.lib.github.CI", False), patch( - "devenv.lib.github.check_ssh_authentication", return_value=False - ), patch("devenv.lib.github.check_sso_configuration", return_value=True): + with ( + patch("devenv.lib.github.CI", False), + patch("devenv.lib.github.check_ssh_authentication", return_value=False), + patch("devenv.lib.github.check_sso_configuration", return_value=True), + ): assert not check_ssh_access() def test_check_ssh_access_failure_no_sso() -> None: - with patch("devenv.lib.github.CI", False), patch( - "devenv.lib.github.check_ssh_authentication", return_value=True - ), patch("devenv.lib.github.check_sso_configuration", return_value=False): + with ( + patch("devenv.lib.github.CI", False), + patch("devenv.lib.github.check_ssh_authentication", return_value=True), + patch("devenv.lib.github.check_sso_configuration", return_value=False), + ): assert not check_ssh_access() @pytest.mark.parametrize("ssh", [True, False]) @pytest.mark.parametrize("sso", [True, False]) def test_check_ssh_access_success_ci(ssh: bool, sso: bool) -> None: - with patch("devenv.lib.github.CI", True), patch( - "devenv.lib.github.check_ssh_authentication", return_value=ssh - ), patch("devenv.lib.github.check_sso_configuration", return_value=sso): + with ( + patch("devenv.lib.github.CI", True), + patch("devenv.lib.github.check_ssh_authentication", return_value=ssh), + patch("devenv.lib.github.check_sso_configuration", return_value=sso), + ): assert check_ssh_access() @pytest.mark.parametrize("sso", [True, False]) def test_check_ssh_access_success_external_contributor(sso: bool) -> None: - with patch("devenv.lib.github.CI", False), patch( - "devenv.lib.github.EXTERNAL_CONTRIBUTOR", "1" - ), patch( - "devenv.lib.github.check_ssh_authentication", return_value=True - ), patch( - "devenv.lib.github.check_sso_configuration", return_value=sso - ) as mock_check_sso_configuration: + with ( + patch("devenv.lib.github.CI", False), + patch("devenv.lib.github.EXTERNAL_CONTRIBUTOR", "1"), + patch("devenv.lib.github.check_ssh_authentication", return_value=True), + patch( + "devenv.lib.github.check_sso_configuration", return_value=sso + ) as mock_check_sso_configuration, + ): assert check_ssh_access() # If the user is an external contributor, we should not check for SSO @@ -107,18 +116,20 @@ def test_check_ssh_access_success_external_contributor(sso: bool) -> None: def test_check_ssh_access_failure_external_contributor() -> None: - with patch("devenv.lib.github.CI", False), patch( - "devenv.lib.github.EXTERNAL_CONTRIBUTOR", "1" - ), patch("devenv.lib.github.check_ssh_authentication", return_value=False): + with ( + patch("devenv.lib.github.CI", False), + patch("devenv.lib.github.EXTERNAL_CONTRIBUTOR", "1"), + patch("devenv.lib.github.check_ssh_authentication", return_value=False), + ): assert not check_ssh_access() def test_add_to_known_hosts(tmp_path: str) -> None: - with patch("devenv.lib.github.home", tmp_path), patch.object( - os, "makedirs" - ) as mock_makedirs, patch( - "devenv.lib.brew.fs.idempotent_add" - ) as mock_idempotent_add: + with ( + patch("devenv.lib.github.home", tmp_path), + patch.object(os, "makedirs") as mock_makedirs, + patch("devenv.lib.brew.fs.idempotent_add") as mock_idempotent_add, + ): add_to_known_hosts() mock_makedirs.assert_called_once_with(f"{tmp_path}/.ssh", exist_ok=True) diff --git a/tests/lib/test_node.py b/tests/lib/test_node.py index be7fe6a5..6a317a48 100644 --- a/tests/lib/test_node.py +++ b/tests/lib/test_node.py @@ -15,13 +15,19 @@ def test_install_pnpm(tmp_path: pathlib.Path) -> None: os.makedirs(binroot) open(f"{binroot}/node", "w").close() - with patch("devenv.lib.archive.download"), patch( - "devenv.lib.archive.unpack", - side_effect=lambda archive_file, tmpd, perform_strip1, strip1_new_prefix: os.makedirs( - f"{tmpd}/{strip1_new_prefix}" + with ( + patch("devenv.lib.archive.download"), + patch( + "devenv.lib.archive.unpack", + side_effect=lambda archive_file, tmpd, perform_strip1, strip1_new_prefix: ( + os.makedirs(f"{tmpd}/{strip1_new_prefix}") + ), + ), + patch("devenv.lib.node.os.path.exists"), + patch( + "devenv.lib.direnv.proc.run", + side_effect=["0.0.0"], # node --version ), - ), patch("devenv.lib.node.os.path.exists"), patch( - "devenv.lib.direnv.proc.run", side_effect=["0.0.0"] # node --version ): node.install("0.0.0", "bar", "baz", repo.path) diff --git a/tests/lib/test_proc.py b/tests/lib/test_proc.py index 88d4f707..0ef6a606 100644 --- a/tests/lib/test_proc.py +++ b/tests/lib/test_proc.py @@ -28,9 +28,10 @@ def test_run_without_stdout() -> None: def test_run_with_debug() -> None: cmd = ("echo", "Hello, World!") - with patch("devenv.lib.proc.constants.DEBUG", True), patch( - "devenv.lib.proc.xtrace" - ) as mock_xtrace: + with ( + patch("devenv.lib.proc.constants.DEBUG", True), + patch("devenv.lib.proc.xtrace") as mock_xtrace, + ): run(cmd) mock_xtrace.assert_called_once_with(cmd) diff --git a/tests/lib/test_venv.py b/tests/lib/test_venv.py index b1aae5de..b94e0cb6 100644 --- a/tests/lib/test_venv.py +++ b/tests/lib/test_venv.py @@ -56,9 +56,13 @@ def test_get_ensure(tmp_path: pathlib.Path) -> None: url, sha256 = config.get_python(repo.path, python_version) - with patch("devenv.lib.venv.proc.run") as mock_run, patch( - "devenv.lib.venv.pythons.get", return_value="python" - ) as mock_pythons_get, patch("shutil.rmtree"): + with ( + patch("devenv.lib.venv.proc.run") as mock_run, + patch( + "devenv.lib.venv.pythons.get", return_value="python" + ) as mock_pythons_get, + patch("shutil.rmtree"), + ): venv.ensure(venv_dir, python_version, url, sha256) assert mock_pythons_get.mock_calls == [ call(python_version, url, sha256) diff --git a/tests/test_sync.py b/tests/test_sync.py index 2acb05f0..8ba1ddc8 100644 --- a/tests/test_sync.py +++ b/tests/test_sync.py @@ -17,14 +17,12 @@ def test(tmp_path: str) -> None: with open(f"{reporoot}/devenv/config.ini", "w") as f: pass with open(f"{reporoot}/devenv/sync.py", "w") as f: - f.write( - """ + f.write(""" import devenv def main(context: dict[str, str]) -> int: return 123 -""" - ) +""") # devenv sync should be able to be run from places other than reporoot... os.chdir(f"{reporoot}/devenv") diff --git a/tox.ini b/tox.ini deleted file mode 100644 index d3b59342..00000000 --- a/tox.ini +++ /dev/null @@ -1,22 +0,0 @@ -[tox] -envlist = - py311 - py312 - -[testenv] -deps = - -rrequirements-dev.in - coverage - covdefaults - pytest - re-assert -commands = - coverage erase - coverage run -m pytest {posargs:.} - coverage report - -[testenv:htmlcov] -commands = - coverage erase - coverage run -m pytest {posargs:.} - coverage html diff --git a/uv.lock b/uv.lock new file mode 100644 index 00000000..b83edad2 --- /dev/null +++ b/uv.lock @@ -0,0 +1,427 @@ +version = 1 +revision = 3 +requires-python = "==3.11.4" + +[[package]] +name = "certifi" +version = "2026.2.25" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/af/2d/7bf41579a8986e348fa033a31cdd0e4121114f6bce2457e8876010b092dd/certifi-2026.2.25.tar.gz", hash = "sha256:e887ab5cee78ea814d3472169153c2d12cd43b14bd03329a39a9c6e2e80bfba7", size = 155029, upload-time = "2026-02-25T02:54:17.342Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9a/3c/c17fb3ca2d9c3acff52e30b309f538586f9f5b9c9cf454f3845fc9af4881/certifi-2026.2.25-py3-none-any.whl", hash = "sha256:027692e4402ad994f1c42e52a4997a9763c646b73e4096e4d5d6db8af1d6f0fa", size = 153684, upload-time = "2026-02-25T02:54:15.766Z" }, +] + +[[package]] +name = "cfgv" +version = "3.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4e/b5/721b8799b04bf9afe054a3899c6cf4e880fcf8563cc71c15610242490a0c/cfgv-3.5.0.tar.gz", hash = "sha256:d5b1034354820651caa73ede66a6294d6e95c1b00acc5e9b098e917404669132", size = 7334, upload-time = "2025-11-19T20:55:51.612Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl", hash = "sha256:a8dc6b26ad22ff227d2634a65cb388215ce6cc96bbcc5cfde7641ae87e8dacc0", size = 7445, upload-time = "2025-11-19T20:55:50.744Z" }, +] + +[[package]] +name = "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 = "covdefaults" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "coverage" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/44/ee/9a6f2611f72e4c5657ae5542a510cf4164d2c673687c0ea73bb1cbd85b4d/covdefaults-2.3.0.tar.gz", hash = "sha256:4e99f679f12d792bc62e5510fa3eb59546ed47bd569e36e4fddc4081c9c3ebf7", size = 4835, upload-time = "2023-03-05T16:43:34.779Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/76/4c/823bc951445aa97e5a1b7e337690db3abf85212c8d138e170922e7916ac8/covdefaults-2.3.0-py2.py3-none-any.whl", hash = "sha256:2832961f6ffcfe4b57c338bc3418a3526f495c26fb9c54565409c5532f7c41be", size = 5144, upload-time = "2023-03-05T16:43:33.245Z" }, +] + +[[package]] +name = "coverage" +version = "7.13.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/24/56/95b7e30fa389756cb56630faa728da46a27b8c6eb46f9d557c68fff12b65/coverage-7.13.4.tar.gz", hash = "sha256:e5c8f6ed1e61a8b2dcdf31eb0b9bbf0130750ca79c1c49eb898e2ad86f5ccc91", size = 827239, upload-time = "2026-02-09T12:59:03.86Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b4/ad/b59e5b451cf7172b8d1043dc0fa718f23aab379bc1521ee13d4bd9bfa960/coverage-7.13.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d490ba50c3f35dd7c17953c68f3270e7ccd1c6642e2d2afe2d8e720b98f5a053", size = 219278, upload-time = "2026-02-09T12:56:31.673Z" }, + { url = "https://files.pythonhosted.org/packages/f1/17/0cb7ca3de72e5f4ef2ec2fa0089beafbcaaaead1844e8b8a63d35173d77d/coverage-7.13.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:19bc3c88078789f8ef36acb014d7241961dbf883fd2533d18cb1e7a5b4e28b11", size = 219783, upload-time = "2026-02-09T12:56:33.104Z" }, + { url = "https://files.pythonhosted.org/packages/ab/63/325d8e5b11e0eaf6d0f6a44fad444ae58820929a9b0de943fa377fe73e85/coverage-7.13.4-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3998e5a32e62fdf410c0dbd3115df86297995d6e3429af80b8798aad894ca7aa", size = 250200, upload-time = "2026-02-09T12:56:34.474Z" }, + { url = "https://files.pythonhosted.org/packages/76/53/c16972708cbb79f2942922571a687c52bd109a7bd51175aeb7558dff2236/coverage-7.13.4-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:8e264226ec98e01a8e1054314af91ee6cde0eacac4f465cc93b03dbe0bce2fd7", size = 252114, upload-time = "2026-02-09T12:56:35.749Z" }, + { url = "https://files.pythonhosted.org/packages/eb/c2/7ab36d8b8cc412bec9ea2d07c83c48930eb4ba649634ba00cb7e4e0f9017/coverage-7.13.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a3aa4e7b9e416774b21797365b358a6e827ffadaaca81b69ee02946852449f00", size = 254220, upload-time = "2026-02-09T12:56:37.796Z" }, + { url = "https://files.pythonhosted.org/packages/d6/4d/cf52c9a3322c89a0e6febdfbc83bb45c0ed3c64ad14081b9503adee702e7/coverage-7.13.4-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:71ca20079dd8f27fcf808817e281e90220475cd75115162218d0e27549f95fef", size = 256164, upload-time = "2026-02-09T12:56:39.016Z" }, + { url = "https://files.pythonhosted.org/packages/78/e9/eb1dd17bd6de8289df3580e967e78294f352a5df8a57ff4671ee5fc3dcd0/coverage-7.13.4-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e2f25215f1a359ab17320b47bcdaca3e6e6356652e8256f2441e4ef972052903", size = 250325, upload-time = "2026-02-09T12:56:40.668Z" }, + { url = "https://files.pythonhosted.org/packages/71/07/8c1542aa873728f72267c07278c5cc0ec91356daf974df21335ccdb46368/coverage-7.13.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d65b2d373032411e86960604dc4edac91fdfb5dca539461cf2cbe78327d1e64f", size = 251913, upload-time = "2026-02-09T12:56:41.97Z" }, + { url = "https://files.pythonhosted.org/packages/74/d7/c62e2c5e4483a748e27868e4c32ad3daa9bdddbba58e1bc7a15e252baa74/coverage-7.13.4-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:94eb63f9b363180aff17de3e7c8760c3ba94664ea2695c52f10111244d16a299", size = 249974, upload-time = "2026-02-09T12:56:43.323Z" }, + { url = "https://files.pythonhosted.org/packages/98/9f/4c5c015a6e98ced54efd0f5cf8d31b88e5504ecb6857585fc0161bb1e600/coverage-7.13.4-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:e856bf6616714c3a9fbc270ab54103f4e685ba236fa98c054e8f87f266c93505", size = 253741, upload-time = "2026-02-09T12:56:45.155Z" }, + { url = "https://files.pythonhosted.org/packages/bd/59/0f4eef89b9f0fcd9633b5d350016f54126ab49426a70ff4c4e87446cabdc/coverage-7.13.4-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:65dfcbe305c3dfe658492df2d85259e0d79ead4177f9ae724b6fb245198f55d6", size = 249695, upload-time = "2026-02-09T12:56:46.636Z" }, + { url = "https://files.pythonhosted.org/packages/b5/2c/b7476f938deb07166f3eb281a385c262675d688ff4659ad56c6c6b8e2e70/coverage-7.13.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b507778ae8a4c915436ed5c2e05b4a6cecfa70f734e19c22a005152a11c7b6a9", size = 250599, upload-time = "2026-02-09T12:56:48.13Z" }, + { url = "https://files.pythonhosted.org/packages/b8/34/c3420709d9846ee3785b9f2831b4d94f276f38884032dca1457fa83f7476/coverage-7.13.4-cp311-cp311-win32.whl", hash = "sha256:784fc3cf8be001197b652d51d3fd259b1e2262888693a4636e18879f613a62a9", size = 221780, upload-time = "2026-02-09T12:56:50.479Z" }, + { url = "https://files.pythonhosted.org/packages/61/08/3d9c8613079d2b11c185b865de9a4c1a68850cfda2b357fae365cf609f29/coverage-7.13.4-cp311-cp311-win_amd64.whl", hash = "sha256:2421d591f8ca05b308cf0092807308b2facbefe54af7c02ac22548b88b95c98f", size = 222715, upload-time = "2026-02-09T12:56:51.815Z" }, + { url = "https://files.pythonhosted.org/packages/18/1a/54c3c80b2f056164cc0a6cdcb040733760c7c4be9d780fe655f356f433e4/coverage-7.13.4-cp311-cp311-win_arm64.whl", hash = "sha256:79e73a76b854d9c6088fe5d8b2ebe745f8681c55f7397c3c0a016192d681045f", size = 221385, upload-time = "2026-02-09T12:56:53.194Z" }, + { url = "https://files.pythonhosted.org/packages/0d/4a/331fe2caf6799d591109bb9c08083080f6de90a823695d412a935622abb2/coverage-7.13.4-py3-none-any.whl", hash = "sha256:1af1641e57cf7ba1bd67d677c9abdbcd6cc2ab7da3bca7fa1e2b7e50e65f2ad0", size = 211242, upload-time = "2026-02-09T12:59:02.032Z" }, +] + +[[package]] +name = "distlib" +version = "0.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/96/8e/709914eb2b5749865801041647dc7f4e6d00b549cfe88b65ca192995f07c/distlib-0.4.0.tar.gz", hash = "sha256:feec40075be03a04501a973d81f633735b4b69f98b05450592310c0f401a4e0d", size = 614605, upload-time = "2025-07-17T16:52:00.465Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl", hash = "sha256:9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16", size = 469047, upload-time = "2025-07-17T16:51:58.613Z" }, +] + +[[package]] +name = "filelock" +version = "3.25.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/77/18/a1fd2231c679dcb9726204645721b12498aeac28e1ad0601038f94b42556/filelock-3.25.0.tar.gz", hash = "sha256:8f00faf3abf9dc730a1ffe9c354ae5c04e079ab7d3a683b7c32da5dd05f26af3", size = 40158, upload-time = "2026-03-01T15:08:45.916Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f9/0b/de6f54d4a8bedfe8645c41497f3c18d749f0bd3218170c667bf4b81d0cdd/filelock-3.25.0-py3-none-any.whl", hash = "sha256:5ccf8069f7948f494968fc0713c10e5c182a9c9d9eef3a636307a20c2490f047", size = 26427, upload-time = "2026-03-01T15:08:44.593Z" }, +] + +[[package]] +name = "identify" +version = "2.6.17" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/57/84/376a3b96e5a8d33a7aa2c5b3b31a4b3c364117184bf0b17418055f6ace66/identify-2.6.17.tar.gz", hash = "sha256:f816b0b596b204c9fdf076ded172322f2723cf958d02f9c3587504834c8ff04d", size = 99579, upload-time = "2026-03-01T20:04:12.702Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/40/66/71c1227dff78aaeb942fed29dd5651f2aec166cc7c9aeea3e8b26a539b7d/identify-2.6.17-py2.py3-none-any.whl", hash = "sha256:be5f8412d5ed4b20f2bd41a65f920990bdccaa6a4a18a08f1eefdcd0bdd885f0", size = 99382, upload-time = "2026-03-01T20:04:11.439Z" }, +] + +[[package]] +name = "iniconfig" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, +] + +[[package]] +name = "librt" +version = "0.8.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/56/9c/b4b0c54d84da4a94b37bd44151e46d5e583c9534c7e02250b961b1b6d8a8/librt-0.8.1.tar.gz", hash = "sha256:be46a14693955b3bd96014ccbdb8339ee8c9346fbe11c1b78901b55125f14c73", size = 177471, upload-time = "2026-02-17T16:13:06.101Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1d/01/0e748af5e4fee180cf7cd12bd12b0513ad23b045dccb2a83191bde82d168/librt-0.8.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:681dc2451d6d846794a828c16c22dc452d924e9f700a485b7ecb887a30aad1fd", size = 65315, upload-time = "2026-02-17T16:11:25.152Z" }, + { url = "https://files.pythonhosted.org/packages/9d/4d/7184806efda571887c798d573ca4134c80ac8642dcdd32f12c31b939c595/librt-0.8.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a3b4350b13cc0e6f5bec8fa7caf29a8fb8cdc051a3bae45cfbfd7ce64f009965", size = 68021, upload-time = "2026-02-17T16:11:26.129Z" }, + { url = "https://files.pythonhosted.org/packages/ae/88/c3c52d2a5d5101f28d3dc89298444626e7874aa904eed498464c2af17627/librt-0.8.1-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:ac1e7817fd0ed3d14fd7c5df91daed84c48e4c2a11ee99c0547f9f62fdae13da", size = 194500, upload-time = "2026-02-17T16:11:27.177Z" }, + { url = "https://files.pythonhosted.org/packages/d6/5d/6fb0a25b6a8906e85b2c3b87bee1d6ed31510be7605b06772f9374ca5cb3/librt-0.8.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:747328be0c5b7075cde86a0e09d7a9196029800ba75a1689332348e998fb85c0", size = 205622, upload-time = "2026-02-17T16:11:28.242Z" }, + { url = "https://files.pythonhosted.org/packages/b2/a6/8006ae81227105476a45691f5831499e4d936b1c049b0c1feb17c11b02d1/librt-0.8.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f0af2bd2bc204fa27f3d6711d0f360e6b8c684a035206257a81673ab924aa11e", size = 218304, upload-time = "2026-02-17T16:11:29.344Z" }, + { url = "https://files.pythonhosted.org/packages/ee/19/60e07886ad16670aae57ef44dada41912c90906a6fe9f2b9abac21374748/librt-0.8.1-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d480de377f5b687b6b1bc0c0407426da556e2a757633cc7e4d2e1a057aa688f3", size = 211493, upload-time = "2026-02-17T16:11:30.445Z" }, + { url = "https://files.pythonhosted.org/packages/9c/cf/f666c89d0e861d05600438213feeb818c7514d3315bae3648b1fc145d2b6/librt-0.8.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d0ee06b5b5291f609ddb37b9750985b27bc567791bc87c76a569b3feed8481ac", size = 219129, upload-time = "2026-02-17T16:11:32.021Z" }, + { url = "https://files.pythonhosted.org/packages/8f/ef/f1bea01e40b4a879364c031476c82a0dc69ce068daad67ab96302fed2d45/librt-0.8.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:9e2c6f77b9ad48ce5603b83b7da9ee3e36b3ab425353f695cba13200c5d96596", size = 213113, upload-time = "2026-02-17T16:11:33.192Z" }, + { url = "https://files.pythonhosted.org/packages/9b/80/cdab544370cc6bc1b72ea369525f547a59e6938ef6863a11ab3cd24759af/librt-0.8.1-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:439352ba9373f11cb8e1933da194dcc6206daf779ff8df0ed69c5e39113e6a99", size = 212269, upload-time = "2026-02-17T16:11:34.373Z" }, + { url = "https://files.pythonhosted.org/packages/9d/9c/48d6ed8dac595654f15eceab2035131c136d1ae9a1e3548e777bb6dbb95d/librt-0.8.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:82210adabbc331dbb65d7868b105185464ef13f56f7f76688565ad79f648b0fe", size = 234673, upload-time = "2026-02-17T16:11:36.063Z" }, + { url = "https://files.pythonhosted.org/packages/16/01/35b68b1db517f27a01be4467593292eb5315def8900afad29fabf56304ba/librt-0.8.1-cp311-cp311-win32.whl", hash = "sha256:52c224e14614b750c0a6d97368e16804a98c684657c7518752c356834fff83bb", size = 54597, upload-time = "2026-02-17T16:11:37.544Z" }, + { url = "https://files.pythonhosted.org/packages/71/02/796fe8f02822235966693f257bf2c79f40e11337337a657a8cfebba5febc/librt-0.8.1-cp311-cp311-win_amd64.whl", hash = "sha256:c00e5c884f528c9932d278d5c9cbbea38a6b81eb62c02e06ae53751a83a4d52b", size = 61733, upload-time = "2026-02-17T16:11:38.691Z" }, + { url = "https://files.pythonhosted.org/packages/28/ad/232e13d61f879a42a4e7117d65e4984bb28371a34bb6fb9ca54ec2c8f54e/librt-0.8.1-cp311-cp311-win_arm64.whl", hash = "sha256:f7cdf7f26c2286ffb02e46d7bac56c94655540b26347673bea15fa52a6af17e9", size = 52273, upload-time = "2026-02-17T16:11:40.308Z" }, +] + +[[package]] +name = "mypy" +version = "1.19.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "librt", marker = "platform_python_implementation != 'PyPy'" }, + { name = "mypy-extensions" }, + { name = "pathspec" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f5/db/4efed9504bc01309ab9c2da7e352cc223569f05478012b5d9ece38fd44d2/mypy-1.19.1.tar.gz", hash = "sha256:19d88bb05303fe63f71dd2c6270daca27cb9401c4ca8255fe50d1d920e0eb9ba", size = 3582404, upload-time = "2025-12-15T05:03:48.42Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ef/47/6b3ebabd5474d9cdc170d1342fbf9dddc1b0ec13ec90bf9004ee6f391c31/mypy-1.19.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d8dfc6ab58ca7dda47d9237349157500468e404b17213d44fc1cb77bce532288", size = 13028539, upload-time = "2025-12-15T05:03:44.129Z" }, + { url = "https://files.pythonhosted.org/packages/5c/a6/ac7c7a88a3c9c54334f53a941b765e6ec6c4ebd65d3fe8cdcfbe0d0fd7db/mypy-1.19.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e3f276d8493c3c97930e354b2595a44a21348b320d859fb4a2b9f66da9ed27ab", size = 12083163, upload-time = "2025-12-15T05:03:37.679Z" }, + { url = "https://files.pythonhosted.org/packages/67/af/3afa9cf880aa4a2c803798ac24f1d11ef72a0c8079689fac5cfd815e2830/mypy-1.19.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2abb24cf3f17864770d18d673c85235ba52456b36a06b6afc1e07c1fdcd3d0e6", size = 12687629, upload-time = "2025-12-15T05:02:31.526Z" }, + { url = "https://files.pythonhosted.org/packages/2d/46/20f8a7114a56484ab268b0ab372461cb3a8f7deed31ea96b83a4e4cfcfca/mypy-1.19.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a009ffa5a621762d0c926a078c2d639104becab69e79538a494bcccb62cc0331", size = 13436933, upload-time = "2025-12-15T05:03:15.606Z" }, + { url = "https://files.pythonhosted.org/packages/5b/f8/33b291ea85050a21f15da910002460f1f445f8007adb29230f0adea279cb/mypy-1.19.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f7cee03c9a2e2ee26ec07479f38ea9c884e301d42c6d43a19d20fb014e3ba925", size = 13661754, upload-time = "2025-12-15T05:02:26.731Z" }, + { url = "https://files.pythonhosted.org/packages/fd/a3/47cbd4e85bec4335a9cd80cf67dbc02be21b5d4c9c23ad6b95d6c5196bac/mypy-1.19.1-cp311-cp311-win_amd64.whl", hash = "sha256:4b84a7a18f41e167f7995200a1d07a4a6810e89d29859df936f1c3923d263042", size = 10055772, upload-time = "2025-12-15T05:03:26.179Z" }, + { url = "https://files.pythonhosted.org/packages/8d/f4/4ce9a05ce5ded1de3ec1c1d96cf9f9504a04e54ce0ed55cfa38619a32b8d/mypy-1.19.1-py3-none-any.whl", hash = "sha256:f1235f5ea01b7db5468d53ece6aaddf1ad0b88d9e7462b86ef96fe04995d7247", size = 2471239, upload-time = "2025-12-15T05:03:07.248Z" }, +] + +[[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.10.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/24/bf/d1bda4f6168e0b2e9e5958945e01910052158313224ada5ce1fb2e1113b8/nodeenv-1.10.0.tar.gz", hash = "sha256:996c191ad80897d076bdfba80a41994c2b47c68e224c542b48feba42ba00f8bb", size = 55611, upload-time = "2025-12-20T14:08:54.006Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/b2/d0896bdcdc8d28a7fc5717c305f1a861c26e18c05047949fb371034d98bd/nodeenv-1.10.0-py2.py3-none-any.whl", hash = "sha256:5bb13e3eed2923615535339b3c620e76779af4cb4c6a90deccc9e36b274d3827", size = 23438, upload-time = "2025-12-20T14:08:52.782Z" }, +] + +[[package]] +name = "packaging" +version = "26.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/65/ee/299d360cdc32edc7d2cf530f3accf79c4fca01e96ffc950d8a52213bd8e4/packaging-26.0.tar.gz", hash = "sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4", size = 143416, upload-time = "2026-01-21T20:50:39.064Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/b9/c538f279a4e237a006a2c98387d081e9eb060d203d8ed34467cc0f0b9b53/packaging-26.0-py3-none-any.whl", hash = "sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529", size = 74366, upload-time = "2026-01-21T20:50:37.788Z" }, +] + +[[package]] +name = "pathspec" +version = "1.0.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fa/36/e27608899f9b8d4dff0617b2d9ab17ca5608956ca44461ac14ac48b44015/pathspec-1.0.4.tar.gz", hash = "sha256:0210e2ae8a21a9137c0d470578cb0e595af87edaa6ebf12ff176f14a02e0e645", size = 131200, upload-time = "2026-01-27T03:59:46.938Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ef/3c/2c197d226f9ea224a9ab8d197933f9da0ae0aac5b6e0f884e2b8d9c8e9f7/pathspec-1.0.4-py3-none-any.whl", hash = "sha256:fb6ae2fd4e7c921a165808a552060e722767cfa526f99ca5156ed2ce45a5c723", size = 55206, upload-time = "2026-01-27T03:59:45.137Z" }, +] + +[[package]] +name = "platformdirs" +version = "4.9.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1b/04/fea538adf7dbbd6d186f551d595961e564a3b6715bdf276b477460858672/platformdirs-4.9.2.tar.gz", hash = "sha256:9a33809944b9db043ad67ca0db94b14bf452cc6aeaac46a88ea55b26e2e9d291", size = 28394, upload-time = "2026-02-16T03:56:10.574Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/48/31/05e764397056194206169869b50cf2fee4dbbbc71b344705b9c0d878d4d8/platformdirs-4.9.2-py3-none-any.whl", hash = "sha256:9170634f126f8efdae22fb58ae8a0eaa86f38365bc57897a6c4f781d1f5875bd", size = 21168, upload-time = "2026-02-16T03:56:08.891Z" }, +] + +[[package]] +name = "pluggy" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, +] + +[[package]] +name = "pre-commit" +version = "4.5.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cfgv" }, + { name = "identify" }, + { name = "nodeenv" }, + { name = "pyyaml" }, + { name = "virtualenv" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/40/f1/6d86a29246dfd2e9b6237f0b5823717f60cad94d47ddc26afa916d21f525/pre_commit-4.5.1.tar.gz", hash = "sha256:eb545fcff725875197837263e977ea257a402056661f09dae08e4b149b030a61", size = 198232, upload-time = "2025-12-16T21:14:33.552Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5d/19/fd3ef348460c80af7bb4669ea7926651d1f95c23ff2df18b9d24bab4f3fa/pre_commit-4.5.1-py2.py3-none-any.whl", hash = "sha256:3b3afd891e97337708c1674210f8eba659b52a38ea5f822ff142d10786221f77", size = 226437, upload-time = "2025-12-16T21:14:32.409Z" }, +] + +[[package]] +name = "pygments" +version = "2.19.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, +] + +[[package]] +name = "pytest" +version = "9.0.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d1/db/7ef3487e0fb0049ddb5ce41d3a49c235bf9ad299b6a25d5780a89f19230f/pytest-9.0.2.tar.gz", hash = "sha256:75186651a92bd89611d1d9fc20f0b4345fd827c41ccd5c299a868a05d70edf11", size = 1568901, upload-time = "2025-12-06T21:30:51.014Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl", hash = "sha256:711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b", size = 374801, upload-time = "2025-12-06T21:30:49.154Z" }, +] + +[[package]] +name = "python-discovery" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "filelock" }, + { name = "platformdirs" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/82/bb/93a3e83bdf9322c7e21cafd092e56a4a17c4d8ef4277b6eb01af1a540a6f/python_discovery-1.1.0.tar.gz", hash = "sha256:447941ba1aed8cc2ab7ee3cb91be5fc137c5bdbb05b7e6ea62fbdcb66e50b268", size = 55674, upload-time = "2026-02-26T09:42:49.668Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/06/54/82a6e2ef37f0f23dccac604b9585bdcbd0698604feb64807dcb72853693e/python_discovery-1.1.0-py3-none-any.whl", hash = "sha256:a162893b8809727f54594a99ad2179d2ede4bf953e12d4c7abc3cc9cdbd1437b", size = 30687, upload-time = "2026-02-26T09:42:48.548Z" }, +] + +[[package]] +name = "pyyaml" +version = "6.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6d/16/a95b6757765b7b031c9374925bb718d55e0a9ba8a1b6a12d25962ea44347/pyyaml-6.0.3-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:44edc647873928551a01e7a563d7452ccdebee747728c1080d881d68af7b997e", size = 185826, upload-time = "2025-09-25T21:31:58.655Z" }, + { url = "https://files.pythonhosted.org/packages/16/19/13de8e4377ed53079ee996e1ab0a9c33ec2faf808a4647b7b4c0d46dd239/pyyaml-6.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:652cb6edd41e718550aad172851962662ff2681490a8a711af6a4d288dd96824", size = 175577, upload-time = "2025-09-25T21:32:00.088Z" }, + { url = "https://files.pythonhosted.org/packages/0c/62/d2eb46264d4b157dae1275b573017abec435397aa59cbcdab6fc978a8af4/pyyaml-6.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:10892704fc220243f5305762e276552a0395f7beb4dbf9b14ec8fd43b57f126c", size = 775556, upload-time = "2025-09-25T21:32:01.31Z" }, + { url = "https://files.pythonhosted.org/packages/10/cb/16c3f2cf3266edd25aaa00d6c4350381c8b012ed6f5276675b9eba8d9ff4/pyyaml-6.0.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:850774a7879607d3a6f50d36d04f00ee69e7fc816450e5f7e58d7f17f1ae5c00", size = 882114, upload-time = "2025-09-25T21:32:03.376Z" }, + { url = "https://files.pythonhosted.org/packages/71/60/917329f640924b18ff085ab889a11c763e0b573da888e8404ff486657602/pyyaml-6.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b8bb0864c5a28024fac8a632c443c87c5aa6f215c0b126c449ae1a150412f31d", size = 806638, upload-time = "2025-09-25T21:32:04.553Z" }, + { url = "https://files.pythonhosted.org/packages/dd/6f/529b0f316a9fd167281a6c3826b5583e6192dba792dd55e3203d3f8e655a/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d37d57ad971609cf3c53ba6a7e365e40660e3be0e5175fa9f2365a379d6095a", size = 767463, upload-time = "2025-09-25T21:32:06.152Z" }, + { url = "https://files.pythonhosted.org/packages/f2/6a/b627b4e0c1dd03718543519ffb2f1deea4a1e6d42fbab8021936a4d22589/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:37503bfbfc9d2c40b344d06b2199cf0e96e97957ab1c1b546fd4f87e53e5d3e4", size = 794986, upload-time = "2025-09-25T21:32:07.367Z" }, + { url = "https://files.pythonhosted.org/packages/45/91/47a6e1c42d9ee337c4839208f30d9f09caa9f720ec7582917b264defc875/pyyaml-6.0.3-cp311-cp311-win32.whl", hash = "sha256:8098f252adfa6c80ab48096053f512f2321f0b998f98150cea9bd23d83e1467b", size = 142543, upload-time = "2025-09-25T21:32:08.95Z" }, + { url = "https://files.pythonhosted.org/packages/da/e3/ea007450a105ae919a72393cb06f122f288ef60bba2dc64b26e2646fa315/pyyaml-6.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:9f3bfb4965eb874431221a3ff3fdcddc7e74e3b07799e0e84ca4a0f867d449bf", size = 158763, upload-time = "2025-09-25T21:32:09.96Z" }, +] + +[[package]] +name = "re-assert" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "regex" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/72/89/5801de335fa7a9cd9f402f66680b14be076d2084dd8244e3a3136a743262/re_assert-1.1.0.tar.gz", hash = "sha256:5172dfbd2047a15dff2347735dea7e495479cc7e58841199a4a4973256b20464", size = 3869, upload-time = "2020-02-25T00:05:34.116Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/95/b0/e70a69ece48c3b6862a5ae6ca87e1e76ff2941447885550073eb280235fb/re_assert-1.1.0-py2.py3-none-any.whl", hash = "sha256:6b2902d5beac2a0e715eb0312fcb226ab4da21d1ea6953441734734b5b68cd48", size = 4050, upload-time = "2020-02-25T00:05:32.593Z" }, +] + +[[package]] +name = "regex" +version = "2026.2.28" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8b/71/41455aa99a5a5ac1eaf311f5d8efd9ce6433c03ac1e0962de163350d0d97/regex-2026.2.28.tar.gz", hash = "sha256:a729e47d418ea11d03469f321aaf67cdee8954cde3ff2cf8403ab87951ad10f2", size = 415184, upload-time = "2026-02-28T02:19:42.792Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/db/8cbfd0ba3f302f2d09dd0019a9fcab74b63fee77a76c937d0e33161fb8c1/regex-2026.2.28-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e621fb7c8dc147419b28e1702f58a0177ff8308a76fa295c71f3e7827849f5d9", size = 488462, upload-time = "2026-02-28T02:16:22.616Z" }, + { url = "https://files.pythonhosted.org/packages/5d/10/ccc22c52802223f2368731964ddd117799e1390ffc39dbb31634a83022ee/regex-2026.2.28-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0d5bef2031cbf38757a0b0bc4298bb4824b6332d28edc16b39247228fbdbad97", size = 290774, upload-time = "2026-02-28T02:16:23.993Z" }, + { url = "https://files.pythonhosted.org/packages/62/b9/6796b3bf3101e64117201aaa3a5a030ec677ecf34b3cd6141b5d5c6c67d5/regex-2026.2.28-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bcb399ed84eabf4282587ba151f2732ad8168e66f1d3f85b1d038868fe547703", size = 288724, upload-time = "2026-02-28T02:16:25.403Z" }, + { url = "https://files.pythonhosted.org/packages/9c/02/291c0ae3f3a10cea941d0f5366da1843d8d1fa8a25b0671e20a0e454bb38/regex-2026.2.28-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7c1b34dfa72f826f535b20712afa9bb3ba580020e834f3c69866c5bddbf10098", size = 791924, upload-time = "2026-02-28T02:16:26.863Z" }, + { url = "https://files.pythonhosted.org/packages/0f/57/f0235cc520d9672742196c5c15098f8f703f2758d48d5a7465a56333e496/regex-2026.2.28-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:851fa70df44325e1e4cdb79c5e676e91a78147b1b543db2aec8734d2add30ec2", size = 860095, upload-time = "2026-02-28T02:16:28.772Z" }, + { url = "https://files.pythonhosted.org/packages/b3/7c/393c94cbedda79a0f5f2435ebd01644aba0b338d327eb24b4aa5b8d6c07f/regex-2026.2.28-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:516604edd17b1c2c3e579cf4e9b25a53bf8fa6e7cedddf1127804d3e0140ca64", size = 906583, upload-time = "2026-02-28T02:16:30.977Z" }, + { url = "https://files.pythonhosted.org/packages/2c/73/a72820f47ca5abf2b5d911d0407ba5178fc52cf9780191ed3a54f5f419a2/regex-2026.2.28-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e7ce83654d1ab701cb619285a18a8e5a889c1216d746ddc710c914ca5fd71022", size = 800234, upload-time = "2026-02-28T02:16:32.55Z" }, + { url = "https://files.pythonhosted.org/packages/34/b3/6e6a4b7b31fa998c4cf159a12cbeaf356386fbd1a8be743b1e80a3da51e4/regex-2026.2.28-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f2791948f7c70bb9335a9102df45e93d428f4b8128020d85920223925d73b9e1", size = 772803, upload-time = "2026-02-28T02:16:34.029Z" }, + { url = "https://files.pythonhosted.org/packages/10/e7/5da0280c765d5a92af5e1cd324b3fe8464303189cbaa449de9a71910e273/regex-2026.2.28-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:03a83cc26aa2acda6b8b9dfe748cf9e84cbd390c424a1de34fdcef58961a297a", size = 781117, upload-time = "2026-02-28T02:16:36.253Z" }, + { url = "https://files.pythonhosted.org/packages/76/39/0b8d7efb256ae34e1b8157acc1afd8758048a1cf0196e1aec2e71fd99f4b/regex-2026.2.28-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:ec6f5674c5dc836994f50f1186dd1fafde4be0666aae201ae2fcc3d29d8adf27", size = 854224, upload-time = "2026-02-28T02:16:38.119Z" }, + { url = "https://files.pythonhosted.org/packages/21/ff/a96d483ebe8fe6d1c67907729202313895d8de8495569ec319c6f29d0438/regex-2026.2.28-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:50c2fc924749543e0eacc93ada6aeeb3ea5f6715825624baa0dccaec771668ae", size = 761898, upload-time = "2026-02-28T02:16:40.333Z" }, + { url = "https://files.pythonhosted.org/packages/89/bd/d4f2e75cb4a54b484e796017e37c0d09d8a0a837de43d17e238adf163f4e/regex-2026.2.28-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:ba55c50f408fb5c346a3a02d2ce0ebc839784e24f7c9684fde328ff063c3cdea", size = 844832, upload-time = "2026-02-28T02:16:41.875Z" }, + { url = "https://files.pythonhosted.org/packages/8a/a7/428a135cf5e15e4e11d1e696eb2bf968362f8ea8a5f237122e96bc2ae950/regex-2026.2.28-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:edb1b1b3a5576c56f08ac46f108c40333f222ebfd5cf63afdfa3aab0791ebe5b", size = 788347, upload-time = "2026-02-28T02:16:43.472Z" }, + { url = "https://files.pythonhosted.org/packages/a9/59/68691428851cf9c9c3707217ab1d9b47cfeec9d153a49919e6c368b9e926/regex-2026.2.28-cp311-cp311-win32.whl", hash = "sha256:948c12ef30ecedb128903c2c2678b339746eb7c689c5c21957c4a23950c96d15", size = 266033, upload-time = "2026-02-28T02:16:45.094Z" }, + { url = "https://files.pythonhosted.org/packages/42/8b/1483de1c57024e89296cbcceb9cccb3f625d416ddb46e570be185c9b05a9/regex-2026.2.28-cp311-cp311-win_amd64.whl", hash = "sha256:fd63453f10d29097cc3dc62d070746523973fb5aa1c66d25f8558bebd47fed61", size = 277978, upload-time = "2026-02-28T02:16:46.75Z" }, + { url = "https://files.pythonhosted.org/packages/a4/36/abec45dc6e7252e3dbc797120496e43bb5730a7abf0d9cb69340696a2f2d/regex-2026.2.28-cp311-cp311-win_arm64.whl", hash = "sha256:00f2b8d9615aa165fdff0a13f1a92049bfad555ee91e20d246a51aa0b556c60a", size = 270340, upload-time = "2026-02-28T02:16:48.626Z" }, +] + +[[package]] +name = "ruff" +version = "0.15.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/da/31/d6e536cdebb6568ae75a7f00e4b4819ae0ad2640c3604c305a0428680b0c/ruff-0.15.4.tar.gz", hash = "sha256:3412195319e42d634470cc97aa9803d07e9d5c9223b99bcb1518f0c725f26ae1", size = 4569550, upload-time = "2026-02-26T20:04:14.959Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f2/82/c11a03cfec3a4d26a0ea1e571f0f44be5993b923f905eeddfc397c13d360/ruff-0.15.4-py3-none-linux_armv6l.whl", hash = "sha256:a1810931c41606c686bae8b5b9a8072adac2f611bb433c0ba476acba17a332e0", size = 10453333, upload-time = "2026-02-26T20:04:20.093Z" }, + { url = "https://files.pythonhosted.org/packages/ce/5d/6a1f271f6e31dffb31855996493641edc3eef8077b883eaf007a2f1c2976/ruff-0.15.4-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:5a1632c66672b8b4d3e1d1782859e98d6e0b4e70829530666644286600a33992", size = 10853356, upload-time = "2026-02-26T20:04:05.808Z" }, + { url = "https://files.pythonhosted.org/packages/b1/d8/0fab9f8842b83b1a9c2bf81b85063f65e93fb512e60effa95b0be49bfc54/ruff-0.15.4-py3-none-macosx_11_0_arm64.whl", hash = "sha256:a4386ba2cd6c0f4ff75252845906acc7c7c8e1ac567b7bc3d373686ac8c222ba", size = 10187434, upload-time = "2026-02-26T20:03:54.656Z" }, + { url = "https://files.pythonhosted.org/packages/85/cc/cc220fd9394eff5db8d94dec199eec56dd6c9f3651d8869d024867a91030/ruff-0.15.4-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b2496488bdfd3732747558b6f95ae427ff066d1fcd054daf75f5a50674411e75", size = 10535456, upload-time = "2026-02-26T20:03:52.738Z" }, + { url = "https://files.pythonhosted.org/packages/fa/0f/bced38fa5cf24373ec767713c8e4cadc90247f3863605fb030e597878661/ruff-0.15.4-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3f1c4893841ff2d54cbda1b2860fa3260173df5ddd7b95d370186f8a5e66a4ac", size = 10287772, upload-time = "2026-02-26T20:04:08.138Z" }, + { url = "https://files.pythonhosted.org/packages/2b/90/58a1802d84fed15f8f281925b21ab3cecd813bde52a8ca033a4de8ab0e7a/ruff-0.15.4-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:820b8766bd65503b6c30aaa6331e8ef3a6e564f7999c844e9a547c40179e440a", size = 11049051, upload-time = "2026-02-26T20:04:03.53Z" }, + { url = "https://files.pythonhosted.org/packages/d2/ac/b7ad36703c35f3866584564dc15f12f91cb1a26a897dc2fd13d7cb3ae1af/ruff-0.15.4-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c9fb74bab47139c1751f900f857fa503987253c3ef89129b24ed375e72873e85", size = 11890494, upload-time = "2026-02-26T20:04:10.497Z" }, + { url = "https://files.pythonhosted.org/packages/93/3d/3eb2f47a39a8b0da99faf9c54d3eb24720add1e886a5309d4d1be73a6380/ruff-0.15.4-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f80c98765949c518142b3a50a5db89343aa90f2c2bf7799de9986498ae6176db", size = 11326221, upload-time = "2026-02-26T20:04:12.84Z" }, + { url = "https://files.pythonhosted.org/packages/ff/90/bf134f4c1e5243e62690e09d63c55df948a74084c8ac3e48a88468314da6/ruff-0.15.4-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:451a2e224151729b3b6c9ffb36aed9091b2996fe4bdbd11f47e27d8f2e8888ec", size = 11168459, upload-time = "2026-02-26T20:04:00.969Z" }, + { url = "https://files.pythonhosted.org/packages/b5/e5/a64d27688789b06b5d55162aafc32059bb8c989c61a5139a36e1368285eb/ruff-0.15.4-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:a8f157f2e583c513c4f5f896163a93198297371f34c04220daf40d133fdd4f7f", size = 11104366, upload-time = "2026-02-26T20:03:48.099Z" }, + { url = "https://files.pythonhosted.org/packages/f1/f6/32d1dcb66a2559763fc3027bdd65836cad9eb09d90f2ed6a63d8e9252b02/ruff-0.15.4-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:917cc68503357021f541e69b35361c99387cdbbf99bd0ea4aa6f28ca99ff5338", size = 10510887, upload-time = "2026-02-26T20:03:45.771Z" }, + { url = "https://files.pythonhosted.org/packages/ff/92/22d1ced50971c5b6433aed166fcef8c9343f567a94cf2b9d9089f6aa80fe/ruff-0.15.4-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:e9737c8161da79fd7cfec19f1e35620375bd8b2a50c3e77fa3d2c16f574105cc", size = 10285939, upload-time = "2026-02-26T20:04:22.42Z" }, + { url = "https://files.pythonhosted.org/packages/e6/f4/7c20aec3143837641a02509a4668fb146a642fd1211846634edc17eb5563/ruff-0.15.4-py3-none-musllinux_1_2_i686.whl", hash = "sha256:291258c917539e18f6ba40482fe31d6f5ac023994ee11d7bdafd716f2aab8a68", size = 10765471, upload-time = "2026-02-26T20:03:58.924Z" }, + { url = "https://files.pythonhosted.org/packages/d0/09/6d2f7586f09a16120aebdff8f64d962d7c4348313c77ebb29c566cefc357/ruff-0.15.4-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:3f83c45911da6f2cd5936c436cf86b9f09f09165f033a99dcf7477e34041cbc3", size = 11263382, upload-time = "2026-02-26T20:04:24.424Z" }, + { url = "https://files.pythonhosted.org/packages/1b/fa/2ef715a1cd329ef47c1a050e10dee91a9054b7ce2fcfdd6a06d139afb7ec/ruff-0.15.4-py3-none-win32.whl", hash = "sha256:65594a2d557d4ee9f02834fcdf0a28daa8b3b9f6cb2cb93846025a36db47ef22", size = 10506664, upload-time = "2026-02-26T20:03:50.56Z" }, + { url = "https://files.pythonhosted.org/packages/d0/a8/c688ef7e29983976820d18710f955751d9f4d4eb69df658af3d006e2ba3e/ruff-0.15.4-py3-none-win_amd64.whl", hash = "sha256:04196ad44f0df220c2ece5b0e959c2f37c777375ec744397d21d15b50a75264f", size = 11651048, upload-time = "2026-02-26T20:04:17.191Z" }, + { url = "https://files.pythonhosted.org/packages/3e/0a/9e1be9035b37448ce2e68c978f0591da94389ade5a5abafa4cf99985d1b2/ruff-0.15.4-py3-none-win_arm64.whl", hash = "sha256:60d5177e8cfc70e51b9c5fad936c634872a74209f934c1e79107d11787ad5453", size = 10966776, upload-time = "2026-02-26T20:03:56.908Z" }, +] + +[[package]] +name = "sentry-devenv" +version = "1.28.0" +source = { editable = "." } +dependencies = [ + { name = "sentry-sdk" }, + { name = "typing-extensions" }, +] + +[package.dev-dependencies] +dev = [ + { name = "covdefaults" }, + { name = "coverage" }, + { name = "mypy" }, + { name = "pre-commit" }, + { name = "pytest" }, + { name = "re-assert" }, + { name = "ruff" }, + { name = "shellcheck-py" }, +] + +[package.metadata] +requires-dist = [ + { name = "sentry-sdk" }, + { name = "typing-extensions" }, +] + +[package.metadata.requires-dev] +dev = [ + { name = "covdefaults" }, + { name = "coverage" }, + { name = "mypy" }, + { name = "pre-commit" }, + { name = "pytest" }, + { name = "re-assert" }, + { name = "ruff" }, + { name = "shellcheck-py" }, +] + +[[package]] +name = "sentry-sdk" +version = "2.54.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c8/e9/2e3a46c304e7fa21eaa70612f60354e32699c7102eb961f67448e222ad7c/sentry_sdk-2.54.0.tar.gz", hash = "sha256:2620c2575128d009b11b20f7feb81e4e4e8ae08ec1d36cbc845705060b45cc1b", size = 413813, upload-time = "2026-03-02T15:12:41.355Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/53/39/be412cc86bc6247b8f69e9383d7950711bd86f8d0a4a4b0fe8fad685bc21/sentry_sdk-2.54.0-py2.py3-none-any.whl", hash = "sha256:fd74e0e281dcda63afff095d23ebcd6e97006102cdc8e78a29f19ecdf796a0de", size = 439198, upload-time = "2026-03-02T15:12:39.546Z" }, +] + +[[package]] +name = "shellcheck-py" +version = "0.11.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/df/55/455b097417b3df3d330eff029c72c32f08b25739e3010acb30ad06d268ef/shellcheck_py-0.11.0.1.tar.gz", hash = "sha256:5c620c88901e8f1d3be5934b31ea99e3310065e1245253741eafd0a275c8c9cc", size = 3139, upload-time = "2025-08-09T17:53:42.492Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/27/d75b03e5458cefdb6d3b674566cd20476c3e4d3fe6cc9d68b7e3b854b296/shellcheck_py-0.11.0.1-py2.py3-none-macosx_10_9_x86_64.whl", hash = "sha256:b6a3fee28efda2e16e38d6e6d59faf7224300256456639727370d404730849e8", size = 6774472, upload-time = "2025-08-09T17:53:34.573Z" }, + { url = "https://files.pythonhosted.org/packages/61/ac/2a84c37171c0cf5a10ea4b0a27d43eb0a1d29bd98b49c2c5ffe17ad24bbe/shellcheck_py-0.11.0.1-py2.py3-none-macosx_11_0_arm64.whl", hash = "sha256:6b88d0a244c82ed07e06a53e444da841f69330ca59ae15d4a66c391655dae7a0", size = 11381835, upload-time = "2025-08-09T17:53:36.852Z" }, + { url = "https://files.pythonhosted.org/packages/96/55/250e0e3367613a5c22bd82e33b16b889287d81ab0f7dda67e6514a4cccf4/shellcheck_py-0.11.0.1-py2.py3-none-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:1b274df81de5b000ff78db433e7328b87e52e3c38481c60f8e488c3095beef05", size = 3800600, upload-time = "2025-08-09T17:53:38.643Z" }, + { url = "https://files.pythonhosted.org/packages/15/5b/bb14c0a7474463b1aa3c09e866cb172dffc66ed2993b7ea8f1db581e86ee/shellcheck_py-0.11.0.1-py2.py3-none-win_amd64.whl", hash = "sha256:784156289ecb17e91c692cd783ab5152333309588cabb10032a047331c63e759", size = 8027541, upload-time = "2025-08-09T17:53:40.889Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.15.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, +] + +[[package]] +name = "urllib3" +version = "2.6.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c7/24/5f1b3bdffd70275f6661c76461e25f024d5a38a46f04aaca912426a2b1d3/urllib3-2.6.3.tar.gz", hash = "sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed", size = 435556, upload-time = "2026-01-07T16:24:43.925Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl", hash = "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4", size = 131584, upload-time = "2026-01-07T16:24:42.685Z" }, +] + +[[package]] +name = "virtualenv" +version = "21.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "distlib" }, + { name = "filelock" }, + { name = "platformdirs" }, + { name = "python-discovery" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2f/c9/18d4b36606d6091844daa3bd93cf7dc78e6f5da21d9f21d06c221104b684/virtualenv-21.1.0.tar.gz", hash = "sha256:1990a0188c8f16b6b9cf65c9183049007375b26aad415514d377ccacf1e4fb44", size = 5840471, upload-time = "2026-02-27T08:49:29.702Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/55/896b06bf93a49bec0f4ae2a6f1ed12bd05c8860744ac3a70eda041064e4d/virtualenv-21.1.0-py3-none-any.whl", hash = "sha256:164f5e14c5587d170cf98e60378eb91ea35bf037be313811905d3a24ea33cc07", size = 5825072, upload-time = "2026-02-27T08:49:27.516Z" }, +]