diff --git a/.gitignore b/.gitignore index bb81d20..87b2dfc 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,6 @@ mutants*/ run.json stats.md .nixos-test-history +.venv/ +.ruff_cache/ +__pycache__/ diff --git a/flake.nix b/flake.nix index 72a1b4a..6402ecc 100644 --- a/flake.nix +++ b/flake.nix @@ -89,6 +89,7 @@ prettier.enable = true; protolint.enable = true; taplo.enable = true; + ruff-format.enable = true; }; settings.formatter = { nixfmt.excludes = [ "doc/snippets/*.nix" ]; diff --git a/nix/hooks.nix b/nix/hooks.nix index 3ee3fdb..3528d00 100644 --- a/nix/hooks.nix +++ b/nix/hooks.nix @@ -4,6 +4,7 @@ toolchain, config, lib, + pkgs, ... }: { @@ -23,6 +24,7 @@ inherit (toolchain) cargo clippy; }; }; + ruff.enable = true; cargo-check = { enable = true; package = toolchain.cargo; @@ -32,6 +34,17 @@ name = "nix fmt"; entry = "${lib.getExe config.formatter} --no-cache"; }; + ty = { + enable = true; + name = "ty check"; + files = "\\.py$"; + entry = lib.getExe ( + pkgs.writeShellScriptBin "ty-check" '' + cd tests/nix + ${lib.getExe pkgs.uv} run ty check + '' + ); + }; typos = { enable = true; settings = { diff --git a/nix/shells.nix b/nix/shells.nix index 400387e..5b08e02 100644 --- a/nix/shells.nix +++ b/nix/shells.nix @@ -24,6 +24,7 @@ pkgs.sqlite pkgs.turso pkgs.zstd + pkgs.uv ]; PROTOC = lib.getExe pkgs.protobuf; diff --git a/tests/nix/.python-version b/tests/nix/.python-version new file mode 100644 index 0000000..24ee5b1 --- /dev/null +++ b/tests/nix/.python-version @@ -0,0 +1 @@ +3.13 diff --git a/tests/nix/default.nix b/tests/nix/default.nix index b10cc6b..9377bca 100644 --- a/tests/nix/default.nix +++ b/tests/nix/default.nix @@ -23,6 +23,13 @@ let lazyAttrsOf ; cfg = config.wire.testing; + + stripTyping = + value: + let + split = builtins.split "(from typing import TYPE_CHECKING|# typing-end)" value; + in + (builtins.elemAt split 0) + (builtins.elemAt split 4); in { imports = [ @@ -44,6 +51,7 @@ in type = lines; default = ''''; description = "test script for runNixOSTest"; + apply = stripTyping; }; testDir = mkOption { default = "${self}/tests/nix/suite/${name}"; @@ -163,7 +171,7 @@ in TEST_DIR="${injectedFlakeDir}/${path}" - ${builtins.readFile ./tools.py} + ${stripTyping (builtins.readFile ./tools/__init__.py)} '' + lib.concatStringsSep "\n" (mapAttrsToList (_: value: value._wire.testScript) value.nodes) + opts.testScript; diff --git a/tests/nix/pyproject.toml b/tests/nix/pyproject.toml new file mode 100644 index 0000000..14a294c --- /dev/null +++ b/tests/nix/pyproject.toml @@ -0,0 +1,18 @@ +[project] +name = "wire-vm-tests" +version = "0.0.0" +requires-python = ">=3.13" +dependencies = [ + "colorama>=0.4.6", + "ipython>=9.8.0", + "junit-xml>=1.9", + "nixos-test-driver", + "ptpython>=3.0.32", + "remote-pdb>=2.1.0", +] + +[tool.uv.sources] +nixos-test-driver = { git = "https://github.com/NixOS/nixpkgs", subdirectory = "nixos/lib/test-driver/src", branch = "nixos-25.11" } + +[dependency-groups] +dev = ["ty>=0.0.4"] diff --git a/tests/nix/suite/test_keys/default.nix b/tests/nix/suite/test_keys/default.nix index cf8bff9..7e48ed4 100644 --- a/tests/nix/suite/test_keys/default.nix +++ b/tests/nix/suite/test_keys/default.nix @@ -10,87 +10,6 @@ nodes.receiver = { _wire.receiver = true; }; - testScript = '' - deployer_so = collect_store_objects(deployer) - receiver_so = collect_store_objects(receiver) - - # build receiver with no keys - deployer.succeed(f"wire apply --no-progress --on receiver --path {TEST_DIR}/hive.nix --no-keys --ssh-accept-host -vvv >&2") - - receiver.wait_for_unit("sshd.service") - - # --no-keys should never push a key - receiver.fail("test -f /run/keys/source_string_name") - deployer.fail("test -f /run/keys/source_string_name") - - # key services are created - receiver.succeed("systemctl cat source_string_name-key.service") - - _, is_failed = receiver.execute("systemctl is-failed source_string_name-key.service") - assert is_failed == "inactive\n", f"source_string_name-key.service must be inactive before key exists ({is_failed})" - - def test_keys(target, target_object, non_interactive): - if non_interactive: - deployer.succeed(f"wire apply keys --on {target} --no-progress --path {TEST_DIR}/hive.nix --non-interactive --ssh-accept-host -vvv >&2") - else: - deployer.succeed(f"wire apply keys --on {target} --no-progress --path {TEST_DIR}/hive.nix --ssh-accept-host -vvv >&2") - - keys = [ - ("/run/keys/source_string_name", "hello_world_source", "root root 600", "source_string_name"), - ("/etc/keys/file", "hello_world_file", "root root 644", "file"), - ("/home/owner/some/deep/path/command", "hello_world_command", "owner owner 644", "command"), - ("/run/keys/environment", "string_from_environment", "root root 600", "environment"), - ] - - for path, value, permissions, name in keys: - # test existence & value - source_string = target_object.succeed(f"cat {path}") - assert value in source_string, f"{path} has correct contents ({target})" - - stat = target_object.succeed(f"stat -c '%U %G %a' {path}").rstrip() - assert permissions == stat, f"{path} has correct permissions ({target})" - - def perform_routine(target, target_object, non_interactive): - test_keys(target, target_object, non_interactive) - - # only check systemd units on receiver since deployer apply's are one time only - if target == "receiver": - target_object.succeed("systemctl start source_string_name-key.path") - target_object.succeed("systemctl start command-key.path") - target_object.wait_for_unit("source_string_name-key.service") - target_object.wait_for_unit("command-key.service") - - # Mess with the keys to make sure that every push refreshes the permissions - target_object.succeed("echo 'incorrect_value' > /run/keys/source_string") - target_object.succeed("chown 600 /etc/keys/file") - # Test having a key that doesn't exist mixed with keys that do - target_object.succeed("rm /home/owner/some/deep/path/command") - - if target == "receiver": - _, is_failed = target_object.execute("systemctl is-active command-key.service") - assert is_failed == "failed\n", f"command-key.service is failed after deletion ({is_failed})" - - # Test keys twice to ensure the operation is idempotent, - # especially around directory creation. - test_keys(target, target_object, non_interactive) - - perform_routine("receiver", receiver, True) - perform_routine("deployer", deployer, True) - perform_routine("receiver", receiver, False) - perform_routine("deployer", deployer, False) - - new_deployer_store_objects = collect_store_objects(deployer).difference(deployer_so) - new_receiver_store_objects = collect_store_objects(receiver).difference(receiver_so) - - # no one should have any keys introduced by the operation - for node, objects in [ - (deployer, new_deployer_store_objects), - (receiver, new_receiver_store_objects), - ]: - assert_store_not_poisoned(node, "hello_world_source", objects) - assert_store_not_poisoned(node, "hello_world_file", objects) - assert_store_not_poisoned(node, "hello_world_command", objects) - assert_store_not_poisoned(node, "string_from_environment", objects) - ''; + testScript = builtins.readFile ./script.py; }; } diff --git a/tests/nix/suite/test_keys/script.py b/tests/nix/suite/test_keys/script.py new file mode 100644 index 0000000..cd30d18 --- /dev/null +++ b/tests/nix/suite/test_keys/script.py @@ -0,0 +1,123 @@ +# SPDX-License-Identifier: AGPL-3.0-or-later +# Copyright 2024-2025 wire Contributors + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from test_driver.machine import Machine + from tools import collect_store_objects, assert_store_not_poisoned + + deployer: Machine = None # type: ignore[invalid-assignment] + receiver: Machine = None # type: ignore[invalid-assignment] + TEST_DIR = "" + +# typing-end + +deployer_so = collect_store_objects(deployer) +receiver_so = collect_store_objects(receiver) + +# build receiver with no keys +deployer.succeed( + f"wire apply --no-progress --on receiver --path {TEST_DIR}/hive.nix --no-keys --ssh-accept-host -vvv >&2" +) + +receiver.wait_for_unit("sshd.service") + +# --no-keys should never push a key +receiver.fail("test -f /run/keys/source_string_name") +deployer.fail("test -f /run/keys/source_string_name") + +# key services are created +receiver.succeed("systemctl cat source_string_name-key.service") + +_, is_failed = receiver.execute("systemctl is-failed source_string_name-key.service") +assert is_failed == "inactive\n", ( + f"source_string_name-key.service must be inactive before key exists ({is_failed})" +) + + +def test_keys(target, target_object, non_interactive): + if non_interactive: + deployer.succeed( + f"wire apply keys --on {target} --no-progress --path {TEST_DIR}/hive.nix --non-interactive --ssh-accept-host -vvv >&2" + ) + else: + deployer.succeed( + f"wire apply keys --on {target} --no-progress --path {TEST_DIR}/hive.nix --ssh-accept-host -vvv >&2" + ) + + keys = [ + ( + "/run/keys/source_string_name", + "hello_world_source", + "root root 600", + "source_string_name", + ), + ("/etc/keys/file", "hello_world_file", "root root 644", "file"), + ( + "/home/owner/some/deep/path/command", + "hello_world_command", + "owner owner 644", + "command", + ), + ( + "/run/keys/environment", + "string_from_environment", + "root root 600", + "environment", + ), + ] + + for path, value, permissions, name in keys: + # test existence & value + source_string = target_object.succeed(f"cat {path}") + assert value in source_string, f"{path} has correct contents ({target})" + + stat = target_object.succeed(f"stat -c '%U %G %a' {path}").rstrip() + assert permissions == stat, f"{path} has correct permissions ({target})" + + +def perform_routine(target, target_object, non_interactive): + test_keys(target, target_object, non_interactive) + + # only check systemd units on receiver since deployer apply's are one time only + if target == "receiver": + target_object.succeed("systemctl start source_string_name-key.path") + target_object.succeed("systemctl start command-key.path") + target_object.wait_for_unit("source_string_name-key.service") + target_object.wait_for_unit("command-key.service") + + # Mess with the keys to make sure that every push refreshes the permissions + target_object.succeed("echo 'incorrect_value' > /run/keys/source_string") + target_object.succeed("chown 600 /etc/keys/file") + # Test having a key that doesn't exist mixed with keys that do + target_object.succeed("rm /home/owner/some/deep/path/command") + + if target == "receiver": + _, is_failed = target_object.execute("systemctl is-active command-key.service") + assert is_failed == "failed\n", ( + f"command-key.service is failed after deletion ({is_failed})" + ) + + # Test keys twice to ensure the operation is idempotent, + # especially around directory creation. + test_keys(target, target_object, non_interactive) + + +perform_routine("receiver", receiver, True) +perform_routine("deployer", deployer, True) +perform_routine("receiver", receiver, False) +perform_routine("deployer", deployer, False) + +new_deployer_store_objects = collect_store_objects(deployer).difference(deployer_so) +new_receiver_store_objects = collect_store_objects(receiver).difference(receiver_so) + +# no one should have any keys introduced by the operation +for node, objects in [ + (deployer, new_deployer_store_objects), + (receiver, new_receiver_store_objects), +]: + assert_store_not_poisoned(node, "hello_world_source", objects) + assert_store_not_poisoned(node, "hello_world_file", objects) + assert_store_not_poisoned(node, "hello_world_command", objects) + assert_store_not_poisoned(node, "string_from_environment", objects) diff --git a/tests/nix/suite/test_local_deploy/default.nix b/tests/nix/suite/test_local_deploy/default.nix index 839fab6..fe327c7 100644 --- a/tests/nix/suite/test_local_deploy/default.nix +++ b/tests/nix/suite/test_local_deploy/default.nix @@ -7,9 +7,6 @@ _wire.deployer = true; _wire.receiver = true; }; - testScript = '' - deployer.succeed(f"wire apply --on deployer --no-progress --path {TEST_DIR}/hive.nix --no-keys -vvv >&2") - deployer.succeed("test -f /etc/a") - ''; + testScript = builtins.readFile ./script.py; }; } diff --git a/tests/nix/suite/test_local_deploy/script.py b/tests/nix/suite/test_local_deploy/script.py new file mode 100644 index 0000000..ae04540 --- /dev/null +++ b/tests/nix/suite/test_local_deploy/script.py @@ -0,0 +1,17 @@ +# SPDX-License-Identifier: AGPL-3.0-or-later +# Copyright 2024-2025 wire Contributors + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from test_driver.machine import Machine + + deployer: Machine = None # type: ignore[invalid-assignment] + TEST_DIR = "" + +# typing-end + +deployer.succeed( + f"wire apply --on deployer --no-progress --path {TEST_DIR}/hive.nix --no-keys -vvv >&2" +) +deployer.succeed("test -f /etc/a") diff --git a/tests/nix/suite/test_remote_deploy/default.nix b/tests/nix/suite/test_remote_deploy/default.nix index a48d467..0a8c9e5 100644 --- a/tests/nix/suite/test_remote_deploy/default.nix +++ b/tests/nix/suite/test_remote_deploy/default.nix @@ -9,41 +9,6 @@ nodes.receiver = { _wire.receiver = true; }; - testScript = '' - with subtest("Test unreachable hosts"): - deployer.fail(f"wire apply --on receiver-unreachable --no-progress --path {TEST_DIR}/hive.nix --no-keys -vvv >&2") - - with subtest("Check basic apply: Interactive"): - deployer.succeed(f"wire apply --on receiver --no-progress --path {TEST_DIR}/hive.nix --no-keys --ssh-accept-host -vvv >&2") - - identity = receiver.succeed("cat /etc/identity") - assert identity == "first", "Identity of first apply wasn't as expected" - - with subtest("Check basic apply: NonInteractive"): - deployer.succeed(f"wire apply --on receiver-third --no-progress --path {TEST_DIR}/hive.nix --no-keys --ssh-accept-host --non-interactive -vvv >&2") - - identity = receiver.succeed("cat /etc/identity") - assert identity == "third", "Identity of non-interactive apply wasn't as expected" - - with subtest("Check boot apply"): - first_system = receiver.succeed("readlink -f /run/current-system") - - deployer.succeed(f"wire apply boot --on receiver-second --no-progress --path {TEST_DIR}/hive.nix --no-keys --ssh-accept-host -vvv >&2") - - _first_system = receiver.succeed("readlink -f /run/current-system") - assert first_system == _first_system, "apply boot without --reboot changed /run/current-system" - - # with subtest("Check /etc/identity after reboot"): - # receiver.reboot() - # - # identity = receiver.succeed("cat /etc/identity") - # assert identity == "second", "Identity didn't change after second apply" - - # with subtest("Check --reboot"): - # deployer.succeed(f"wire apply boot --on receiver-third --no-progress --path {TEST_DIR}/hive.nix --reboot --no-keys -vvv >&2") - # - # identity = receiver.succeed("cat /etc/identity") - # assert identity == "third", "Identity didn't change after third apply" - ''; + testScript = builtins.readFile ./script.py; }; } diff --git a/tests/nix/suite/test_remote_deploy/script.py b/tests/nix/suite/test_remote_deploy/script.py new file mode 100644 index 0000000..a50554d --- /dev/null +++ b/tests/nix/suite/test_remote_deploy/script.py @@ -0,0 +1,63 @@ +# SPDX-License-Identifier: AGPL-3.0-or-later +# Copyright 2024-2025 wire Contributors + +from typing import TYPE_CHECKING +from typing import Callable, ContextManager + +if TYPE_CHECKING: + from test_driver.machine import Machine + + deployer: Machine = None # type: ignore[invalid-assignment] + receiver: Machine = None # type: ignore[invalid-assignment] + + TEST_DIR = "" + + # https://github.com/NixOS/nixpkgs/blob/d10d9933b1c206f9b2950e5e1d68268c5ed0a3c7/nixos/lib/test-script-prepend.py#L43 + subtest: Callable[[str], ContextManager[None]] = None # type: ignore[invalid-assignment] + +# typing-end + +with subtest("Test unreachable hosts"): + deployer.fail( + f"wire apply --on receiver-unreachable --no-progress --path {TEST_DIR}/hive.nix --no-keys -vvv >&2" + ) + +with subtest("Check basic apply: Interactive"): + deployer.succeed( + f"wire apply --on receiver --no-progress --path {TEST_DIR}/hive.nix --no-keys --ssh-accept-host -vvv >&2" + ) + + identity = receiver.succeed("cat /etc/identity") + assert identity == "first", "Identity of first apply wasn't as expected" + +with subtest("Check basic apply: NonInteractive"): + deployer.succeed( + f"wire apply --on receiver-third --no-progress --path {TEST_DIR}/hive.nix --no-keys --ssh-accept-host --non-interactive -vvv >&2" + ) + + identity = receiver.succeed("cat /etc/identity") + assert identity == "third", "Identity of non-interactive apply wasn't as expected" + +with subtest("Check boot apply"): + first_system = receiver.succeed("readlink -f /run/current-system") + +deployer.succeed( + f"wire apply boot --on receiver-second --no-progress --path {TEST_DIR}/hive.nix --no-keys --ssh-accept-host -vvv >&2" +) + +_first_system = receiver.succeed("readlink -f /run/current-system") +assert first_system == _first_system, ( + "apply boot without --reboot changed /run/current-system" +) + +# with subtest("Check /etc/identity after reboot"): +# receiver.reboot() +# +# identity = receiver.succeed("cat /etc/identity") +# assert identity == "second", "Identity didn't change after second apply" + +# with subtest("Check --reboot"): +# deployer.succeed(f"wire apply boot --on receiver-third --no-progress --path {TEST_DIR}/hive.nix --reboot --no-keys -vvv >&2") +# +# identity = receiver.succeed("cat /etc/identity") +# assert identity == "third", "Identity didn't change after third apply" diff --git a/tests/nix/suite/test_stdin/default.nix b/tests/nix/suite/test_stdin/default.nix index 9b62736..3c75223 100644 --- a/tests/nix/suite/test_stdin/default.nix +++ b/tests/nix/suite/test_stdin/default.nix @@ -7,9 +7,6 @@ _wire.deployer = true; _wire.receiver = true; }; - testScript = '' - deployer.succeed(f"echo @tag | wire apply --on deployer --no-progress --path {TEST_DIR}/hive.nix --no-keys -vvv >&2") - deployer.succeed("test -f /etc/a") - ''; + testScript = builtins.readFile ./script.py; }; } diff --git a/tests/nix/suite/test_stdin/script.py b/tests/nix/suite/test_stdin/script.py new file mode 100644 index 0000000..8dec106 --- /dev/null +++ b/tests/nix/suite/test_stdin/script.py @@ -0,0 +1,17 @@ +# SPDX-License-Identifier: AGPL-3.0-or-later +# Copyright 2024-2025 wire Contributors + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from test_driver.machine import Machine + + deployer: Machine = None # type: ignore[invalid-assignment] + TEST_DIR = "" + +# typing-end + +deployer.succeed( + f"echo @tag | wire apply --on deployer --no-progress --path {TEST_DIR}/hive.nix --no-keys -vvv >&2" +) +deployer.succeed("test -f /etc/a") diff --git a/tests/nix/tools.py b/tests/nix/tools/__init__.py similarity index 70% rename from tests/nix/tools.py rename to tests/nix/tools/__init__.py index 2e1a563..857a55e 100644 --- a/tests/nix/tools.py +++ b/tests/nix/tools/__init__.py @@ -1,6 +1,13 @@ # SPDX-License-Identifier: AGPL-3.0-or-later # Copyright 2024-2025 wire Contributors +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from test_driver.machine import Machine + +# typing-end + def collect_store_objects(machine: Machine) -> set[str]: return set(machine.succeed("ls /nix/store").strip().split("\n")) @@ -10,4 +17,4 @@ def assert_store_not_poisoned(machine: Machine, poison: str, objects: set[str]): paths = list(map(lambda n: f"/nix/store/{n}", objects)) machine.succeed("which rg") - machine.fail(f"rg '{poison}' {" ".join(paths)}") + machine.fail(f"rg '{poison}' {' '.join(paths)}") diff --git a/tests/nix/uv.lock b/tests/nix/uv.lock new file mode 100644 index 0000000..f1e79d0 --- /dev/null +++ b/tests/nix/uv.lock @@ -0,0 +1,303 @@ +version = 1 +revision = 3 +requires-python = ">=3.13" + +[[package]] +name = "appdirs" +version = "1.4.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d7/d8/05696357e0311f5b5c316d7b95f46c669dd9c15aaeecbb48c7d0aeb88c40/appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41", size = 13470, upload-time = "2020-05-11T07:59:51.037Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3b/00/2344469e2084fb287c2e0b57b72910309874c3245463acd6cf5e3db69324/appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128", size = 9566, upload-time = "2020-05-11T07:59:49.499Z" }, +] + +[[package]] +name = "asttokens" +version = "3.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/be/a5/8e3f9b6771b0b408517c82d97aed8f2036509bc247d46114925e32fe33f0/asttokens-3.0.1.tar.gz", hash = "sha256:71a4ee5de0bde6a31d64f6b13f2293ac190344478f081c3d1bccfcf5eacb0cb7", size = 62308, upload-time = "2025-11-15T16:43:48.578Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d2/39/e7eaf1799466a4aef85b6a4fe7bd175ad2b1c6345066aa33f1f58d4b18d0/asttokens-3.0.1-py3-none-any.whl", hash = "sha256:15a3ebc0f43c2d0a50eeafea25e19046c68398e487b9f1f5b517f7c0f40f976a", size = 27047, upload-time = "2025-11-15T16:43:16.109Z" }, +] + +[[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 = "executing" +version = "2.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cc/28/c14e053b6762b1044f34a13aab6859bbf40456d37d23aa286ac24cfd9a5d/executing-2.2.1.tar.gz", hash = "sha256:3632cc370565f6648cc328b32435bd120a1e4ebb20c77e3fdde9a13cd1e533c4", size = 1129488, upload-time = "2025-09-01T09:48:10.866Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c1/ea/53f2148663b321f21b5a606bd5f191517cf40b7072c0497d3c92c4a13b1e/executing-2.2.1-py2.py3-none-any.whl", hash = "sha256:760643d3452b4d777d295bb167ccc74c64a81df23fb5e08eff250c425a4b2017", size = 28317, upload-time = "2025-09-01T09:48:08.5Z" }, +] + +[[package]] +name = "ipython" +version = "9.8.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "decorator" }, + { name = "ipython-pygments-lexers" }, + { name = "jedi" }, + { name = "matplotlib-inline" }, + { name = "pexpect", marker = "sys_platform != 'emscripten' and sys_platform != 'win32'" }, + { name = "prompt-toolkit" }, + { name = "pygments" }, + { name = "stack-data" }, + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/12/51/a703c030f4928646d390b4971af4938a1b10c9dfce694f0d99a0bb073cb2/ipython-9.8.0.tar.gz", hash = "sha256:8e4ce129a627eb9dd221c41b1d2cdaed4ef7c9da8c17c63f6f578fe231141f83", size = 4424940, upload-time = "2025-12-03T10:18:24.353Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f1/df/8ee1c5dd1e3308b5d5b2f2dfea323bb2f3827da8d654abb6642051199049/ipython-9.8.0-py3-none-any.whl", hash = "sha256:ebe6d1d58d7d988fbf23ff8ff6d8e1622cfdb194daf4b7b73b792c4ec3b85385", size = 621374, upload-time = "2025-12-03T10:18:22.335Z" }, +] + +[[package]] +name = "ipython-pygments-lexers" +version = "1.1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ef/4c/5dd1d8af08107f88c7f741ead7a40854b8ac24ddf9ae850afbcf698aa552/ipython_pygments_lexers-1.1.1.tar.gz", hash = "sha256:09c0138009e56b6854f9535736f4171d855c8c08a563a0dcd8022f78355c7e81", size = 8393, upload-time = "2025-01-17T11:24:34.505Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d9/33/1f075bf72b0b747cb3288d011319aaf64083cf2efef8354174e3ed4540e2/ipython_pygments_lexers-1.1.1-py3-none-any.whl", hash = "sha256:a9462224a505ade19a605f71f8fa63c2048833ce50abc86768a0d81d876dc81c", size = 8074, upload-time = "2025-01-17T11:24:33.271Z" }, +] + +[[package]] +name = "jedi" +version = "0.19.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "parso" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/72/3a/79a912fbd4d8dd6fbb02bf69afd3bb72cf0c729bb3063c6f4498603db17a/jedi-0.19.2.tar.gz", hash = "sha256:4770dc3de41bde3966b02eb84fbcf557fb33cce26ad23da12c742fb50ecb11f0", size = 1231287, upload-time = "2024-11-11T01:41:42.873Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c0/5a/9cac0c82afec3d09ccd97c8b6502d48f165f9124db81b4bcb90b4af974ee/jedi-0.19.2-py2.py3-none-any.whl", hash = "sha256:a8ef22bde8490f57fe5c7681a3c83cb58874daf72b4784de3cce5b6ef6edb5b9", size = 1572278, upload-time = "2024-11-11T01:41:40.175Z" }, +] + +[[package]] +name = "junit-xml" +version = "1.9" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/98/af/bc988c914dd1ea2bc7540ecc6a0265c2b6faccc6d9cdb82f20e2094a8229/junit-xml-1.9.tar.gz", hash = "sha256:de16a051990d4e25a3982b2dd9e89d671067548718866416faec14d9de56db9f", size = 7349, upload-time = "2023-01-24T18:42:00.836Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/93/2d896b5fd3d79b4cadd8882c06650e66d003f465c9d12c488d92853dff78/junit_xml-1.9-py2.py3-none-any.whl", hash = "sha256:ec5ca1a55aefdd76d28fcc0b135251d156c7106fa979686a4b48d62b761b4732", size = 7130, upload-time = "2020-02-22T20:41:37.661Z" }, +] + +[[package]] +name = "matplotlib-inline" +version = "0.2.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c7/74/97e72a36efd4ae2bccb3463284300f8953f199b5ffbc04cbbb0ec78f74b1/matplotlib_inline-0.2.1.tar.gz", hash = "sha256:e1ee949c340d771fc39e241ea75683deb94762c8fa5f2927ec57c83c4dffa9fe", size = 8110, upload-time = "2025-10-23T09:00:22.126Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/af/33/ee4519fa02ed11a94aef9559552f3b17bb863f2ecfe1a35dc7f548cde231/matplotlib_inline-0.2.1-py3-none-any.whl", hash = "sha256:d56ce5156ba6085e00a9d54fead6ed29a9c47e215cd1bba2e976ef39f5710a76", size = 9516, upload-time = "2025-10-23T09:00:20.675Z" }, +] + +[[package]] +name = "nixos-test-driver" +version = "0.0.0" +source = { git = "https://github.com/NixOS/nixpkgs?subdirectory=nixos%2Flib%2Ftest-driver%2Fsrc&branch=nixos-25.11#c6f52ebd45e5925c188d1a20119978aa4ffd5ef6" } + +[[package]] +name = "parso" +version = "0.8.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d4/de/53e0bcf53d13e005bd8c92e7855142494f41171b34c2536b86187474184d/parso-0.8.5.tar.gz", hash = "sha256:034d7354a9a018bdce352f48b2a8a450f05e9d6ee85db84764e9b6bd96dafe5a", size = 401205, upload-time = "2025-08-23T15:15:28.028Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/16/32/f8e3c85d1d5250232a5d3477a2a28cc291968ff175caeadaf3cc19ce0e4a/parso-0.8.5-py2.py3-none-any.whl", hash = "sha256:646204b5ee239c396d040b90f9e272e9a8017c630092bf59980beb62fd033887", size = 106668, upload-time = "2025-08-23T15:15:25.663Z" }, +] + +[[package]] +name = "pexpect" +version = "4.9.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "ptyprocess" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/42/92/cc564bf6381ff43ce1f4d06852fc19a2f11d180f23dc32d9588bee2f149d/pexpect-4.9.0.tar.gz", hash = "sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f", size = 166450, upload-time = "2023-11-25T09:07:26.339Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9e/c3/059298687310d527a58bb01f3b1965787ee3b40dce76752eda8b44e9a2c5/pexpect-4.9.0-py2.py3-none-any.whl", hash = "sha256:7236d1e080e4936be2dc3e326cec0af72acf9212a7e1d060210e70a47e253523", size = 63772, upload-time = "2023-11-25T06:56:14.81Z" }, +] + +[[package]] +name = "prompt-toolkit" +version = "3.0.52" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "wcwidth" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a1/96/06e01a7b38dce6fe1db213e061a4602dd6032a8a97ef6c1a862537732421/prompt_toolkit-3.0.52.tar.gz", hash = "sha256:28cde192929c8e7321de85de1ddbe736f1375148b02f2e17edd840042b1be855", size = 434198, upload-time = "2025-08-27T15:24:02.057Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/84/03/0d3ce49e2505ae70cf43bc5bb3033955d2fc9f932163e84dc0779cc47f48/prompt_toolkit-3.0.52-py3-none-any.whl", hash = "sha256:9aac639a3bbd33284347de5ad8d68ecc044b91a762dc39b7c21095fcd6a19955", size = 391431, upload-time = "2025-08-27T15:23:59.498Z" }, +] + +[[package]] +name = "ptpython" +version = "3.0.32" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "appdirs" }, + { name = "jedi" }, + { name = "prompt-toolkit" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b6/8c/7e904ceeb512b4530c7ca1d918d3565d694a1fa7df337cdfc36a16347d68/ptpython-3.0.32.tar.gz", hash = "sha256:11651778236de95c582b42737294e50a66ba4a21fa01c0090ea70815af478fe0", size = 74080, upload-time = "2025-11-20T21:20:48.27Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4c/ac/0e35e5d7afd47ab0e2c71293ed2ad18df91a2a4a008c0ff59c2f22def377/ptpython-3.0.32-py3-none-any.whl", hash = "sha256:16435d323e5fc0a685d5f4dc5bb4494fb68ac68736689cd1247e1eda9369b616", size = 68099, upload-time = "2025-11-20T21:20:46.634Z" }, +] + +[[package]] +name = "ptyprocess" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/20/e5/16ff212c1e452235a90aeb09066144d0c5a6a8c0834397e03f5224495c4e/ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220", size = 70762, upload-time = "2020-12-28T15:15:30.155Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/22/a6/858897256d0deac81a172289110f31629fc4cee19b6f01283303e18c8db3/ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35", size = 13993, upload-time = "2020-12-28T15:15:28.35Z" }, +] + +[[package]] +name = "pure-eval" +version = "0.2.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cd/05/0a34433a064256a578f1783a10da6df098ceaa4a57bbeaa96a6c0352786b/pure_eval-0.2.3.tar.gz", hash = "sha256:5f4e983f40564c576c7c8635ae88db5956bb2229d7e9237d03b3c0b0190eaf42", size = 19752, upload-time = "2024-07-21T12:58:21.801Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8e/37/efad0257dc6e593a18957422533ff0f87ede7c9c6ea010a2177d738fb82f/pure_eval-0.2.3-py3-none-any.whl", hash = "sha256:1db8e35b67b3d218d818ae653e27f06c3aa420901fa7b081ca98cbedc874e0d0", size = 11842, upload-time = "2024-07-21T12:58:20.04Z" }, +] + +[[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 = "remote-pdb" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e4/b5/4944cac06fd9fc4a2e168313ec220aa25ed96ce83947b63eea5b4045b22d/remote-pdb-2.1.0.tar.gz", hash = "sha256:2d70c6f41e0eabf0165e8f1be58f82aa7a605aaeab8f2aefeb9ce246431091c1", size = 22295, upload-time = "2020-07-24T13:31:32.985Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/71/c5/d208c66344bb785d800adb61aef512290d3473052b9e7697890f0547aff2/remote_pdb-2.1.0-py2.py3-none-any.whl", hash = "sha256:94f73a92ac1248cf16189211011f97096bdada8a7baac8c79372663bbb57b5d0", size = 6304, upload-time = "2020-07-24T13:31:31.535Z" }, +] + +[[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 = "stack-data" +version = "0.6.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "asttokens" }, + { name = "executing" }, + { name = "pure-eval" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/28/e3/55dcc2cfbc3ca9c29519eb6884dd1415ecb53b0e934862d3559ddcb7e20b/stack_data-0.6.3.tar.gz", hash = "sha256:836a778de4fec4dcd1dcd89ed8abff8a221f58308462e1c4aa2a3cf30148f0b9", size = 44707, upload-time = "2023-09-30T13:58:05.479Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f1/7b/ce1eafaf1a76852e2ec9b22edecf1daa58175c090266e9f6c64afcd81d91/stack_data-0.6.3-py3-none-any.whl", hash = "sha256:d5558e0c25a4cb0853cddad3d77da9891a08cb85dd9f9f91b9f8cd66e511e695", size = 24521, upload-time = "2023-09-30T13:58:03.53Z" }, +] + +[[package]] +name = "traitlets" +version = "5.14.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/eb/79/72064e6a701c2183016abbbfedaba506d81e30e232a68c9f0d6f6fcd1574/traitlets-5.14.3.tar.gz", hash = "sha256:9ed0579d3502c94b4b3732ac120375cda96f923114522847de4b3bb98b96b6b7", size = 161621, upload-time = "2024-04-19T11:11:49.746Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl", hash = "sha256:b74e89e397b1ed28cc831db7aea759ba6640cb3de13090ca145426688ff1ac4f", size = 85359, upload-time = "2024-04-19T11:11:46.763Z" }, +] + +[[package]] +name = "ty" +version = "0.0.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/48/d9/97d5808e851f790e58f8a54efb5c7b9f404640baf9e295f424846040b316/ty-0.0.4.tar.gz", hash = "sha256:2ea47a0089d74730658ec4e988c8ef476a1e9bd92df3e56709c4003c2895ff3b", size = 4780289, upload-time = "2025-12-19T00:13:53.12Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b1/94/b32a962243cc8a16e8dc74cf1fe75e8bb013d0e13e71bb540e2c86214b61/ty-0.0.4-py3-none-linux_armv6l.whl", hash = "sha256:5225da65a8d1defeb21ee9d74298b1b97c6cbab36e235a310c1430d9079e4b6a", size = 9762399, upload-time = "2025-12-19T00:14:11.261Z" }, + { url = "https://files.pythonhosted.org/packages/d1/d2/7c76e0c22ddfc2fcd4a3458a65f87ce074070eb1c68c07ee475cc2b6ea68/ty-0.0.4-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:f87770d7988f470b795a2043185082fa959dbe1979a11b4bfe20f1214d37bd6e", size = 9590410, upload-time = "2025-12-19T00:13:55.759Z" }, + { url = "https://files.pythonhosted.org/packages/a5/84/de4b1fc85669faca3622071d5a3f3ec7bfb239971f368c28fae461d3398a/ty-0.0.4-py3-none-macosx_11_0_arm64.whl", hash = "sha256:ecf68b8ea48674a289d733b4786aecc259242a2d9a920b3ec8583db18c67496a", size = 9131113, upload-time = "2025-12-19T00:14:08.593Z" }, + { url = "https://files.pythonhosted.org/packages/a7/ff/b5bf385b6983be56a470856bbcbac1b7e816bcd765a7e9d39ab2399e387d/ty-0.0.4-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:efc396d76a57e527393cae4ee8faf23b93be3df9e93202f39925721a7a2bb7b8", size = 9599152, upload-time = "2025-12-19T00:13:40.484Z" }, + { url = "https://files.pythonhosted.org/packages/36/d6/9880ba106f2f20d13e6a5dca5d5ca44bfb3782936ee67ff635f89a2959c0/ty-0.0.4-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c893b968d2f9964a4d4db9992c9ba66b01f411b1f48dffcde08622e19cd6ab97", size = 9585368, upload-time = "2025-12-19T00:14:00.994Z" }, + { url = "https://files.pythonhosted.org/packages/3f/53/503cfc18bc4c7c4e02f89dd43debc41a6e343b41eb43df658dfb493a386d/ty-0.0.4-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:526c925b80d68a53c165044d2370fcfc0def1f119f7b7e483ee61d24da6fb891", size = 9998412, upload-time = "2025-12-19T00:14:18.653Z" }, + { url = "https://files.pythonhosted.org/packages/1d/bd/dd2d3e29834da5add2eda0ab5b433171ce9ce9a248c364d2e237f82073d7/ty-0.0.4-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:857f605a7fa366b6c6e6f38abc311d0606be513c2bee8977b5c8fd4bde1a82d5", size = 10853890, upload-time = "2025-12-19T00:13:50.891Z" }, + { url = "https://files.pythonhosted.org/packages/07/fe/28ba3be1672e6b8df46e43de66a02dc076ffba7853d391a5466421886225/ty-0.0.4-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b4cc981aa3ebdac2c233421b1e58c80b0df6a8e6e6fa8b9e69fbdfd2f82768af", size = 10587263, upload-time = "2025-12-19T00:14:21.577Z" }, + { url = "https://files.pythonhosted.org/packages/26/9c/bb598772043f686afe5bc26cb386020709c1a0bcc164bc22ad9da2b4f55d/ty-0.0.4-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b03b2708b0bf67c76424a860f848aebaa4772c05529170c3761bfcaea93ec199", size = 10401204, upload-time = "2025-12-19T00:13:43.453Z" }, + { url = "https://files.pythonhosted.org/packages/ac/18/71765e9d63669bf09461c3fea84a7a63232ccb0e83b84676f07b987fc217/ty-0.0.4-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:469890e885544beb129c21e2f8f15321f0573d094aec13da68593c5f86389ff9", size = 10129713, upload-time = "2025-12-19T00:14:13.725Z" }, + { url = "https://files.pythonhosted.org/packages/c3/2d/c03eba570aa85e9c361de5ed36d60b9ab139e93ee91057f455ab4af48e54/ty-0.0.4-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:abfd928d09567e12068aeca875e920def3badf1978896f474aa4b85b552703c4", size = 9586203, upload-time = "2025-12-19T00:14:03.423Z" }, + { url = "https://files.pythonhosted.org/packages/61/f1/8c3c82a8df69bd4417c77be4f895d043db26dd47bfcc90b33dc109cd0096/ty-0.0.4-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:44b8e94f9d64df12eae4cf8031c5ca9a4c610b57092b26ad3d68d91bcc7af122", size = 9608230, upload-time = "2025-12-19T00:13:58.252Z" }, + { url = "https://files.pythonhosted.org/packages/51/0c/d8ba3a85c089c246ef6bd49d0f0b40bc0f9209bb819e8c02ccbea5cb4d57/ty-0.0.4-py3-none-musllinux_1_2_i686.whl", hash = "sha256:9d6a439813e21a06769daf858105818c385d88018929d4a56970d4ddd5cd3df2", size = 9725125, upload-time = "2025-12-19T00:14:05.996Z" }, + { url = "https://files.pythonhosted.org/packages/4d/38/e30f64ad1e40905c766576ec70cffc69163591a5842ce14652672f6ab394/ty-0.0.4-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:c3cfcf26cfe6c828e91d7a529cc2dda37bc3b51ba06909c9be07002a6584af52", size = 10237174, upload-time = "2025-12-19T00:14:23.858Z" }, + { url = "https://files.pythonhosted.org/packages/cb/d7/8d650aa0be8936dd3ed74e2b0655230e2904caa6077c30c16a089b523cff/ty-0.0.4-py3-none-win32.whl", hash = "sha256:58bbf70dd27af6b00dedbdebeec92d5993aa238664f96fa5c0064930f7a0d30b", size = 9188434, upload-time = "2025-12-19T00:13:45.875Z" }, + { url = "https://files.pythonhosted.org/packages/82/d7/9fc0c81cf0b0d281ac9c18bfbdb4d6bae2173503ba79e40b210ab41c2c8b/ty-0.0.4-py3-none-win_amd64.whl", hash = "sha256:7c2db0f96218f08c140bd9d3fcbb1b3c8c5c4f0c9b0a5624487f0a2bf4b76163", size = 10019313, upload-time = "2025-12-19T00:14:15.968Z" }, + { url = "https://files.pythonhosted.org/packages/5f/b8/3e3246738eed1cd695c5964a401f3b9c757d20ac21fdae06281af9f40ef6/ty-0.0.4-py3-none-win_arm64.whl", hash = "sha256:69f14fc98e4a847afa9f8c5d5234d008820dbc09c7dcdb3ac1ba16628f5132df", size = 9561857, upload-time = "2025-12-19T00:13:48.382Z" }, +] + +[[package]] +name = "wcwidth" +version = "0.2.14" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/24/30/6b0809f4510673dc723187aeaf24c7f5459922d01e2f794277a3dfb90345/wcwidth-0.2.14.tar.gz", hash = "sha256:4d478375d31bc5395a3c55c40ccdf3354688364cd61c4f6adacaa9215d0b3605", size = 102293, upload-time = "2025-09-22T16:29:53.023Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/af/b5/123f13c975e9f27ab9c0770f514345bd406d0e8d3b7a0723af9d43f710af/wcwidth-0.2.14-py2.py3-none-any.whl", hash = "sha256:a7bb560c8aee30f9957e5f9895805edd20602f2d7f720186dfd906e82b4982e1", size = 37286, upload-time = "2025-09-22T16:29:51.641Z" }, +] + +[[package]] +name = "wire-vm-tests" +version = "0.0.0" +source = { virtual = "." } +dependencies = [ + { name = "colorama" }, + { name = "ipython" }, + { name = "junit-xml" }, + { name = "nixos-test-driver" }, + { name = "ptpython" }, + { name = "remote-pdb" }, +] + +[package.dev-dependencies] +dev = [ + { name = "ty" }, +] + +[package.metadata] +requires-dist = [ + { name = "colorama", specifier = ">=0.4.6" }, + { name = "ipython", specifier = ">=9.8.0" }, + { name = "junit-xml", specifier = ">=1.9" }, + { name = "nixos-test-driver", git = "https://github.com/NixOS/nixpkgs?subdirectory=nixos%2Flib%2Ftest-driver%2Fsrc&branch=nixos-25.11" }, + { name = "ptpython", specifier = ">=3.0.32" }, + { name = "remote-pdb", specifier = ">=2.1.0" }, +] + +[package.metadata.requires-dev] +dev = [{ name = "ty", specifier = ">=0.0.4" }]