diff --git a/pyproject.toml b/pyproject.toml index 3a71fd4..5f4d5d5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -21,7 +21,7 @@ gimmegit = "gimmegit._cli:main" dev = [ "ruff", "pytest", - "ty>=0.0.1a27", + "ty>=0.0.1a33", ] [build-system] diff --git a/src/gimmegit/_args.py b/src/gimmegit/_args.py index ba41581..6d61543 100644 --- a/src/gimmegit/_args.py +++ b/src/gimmegit/_args.py @@ -33,6 +33,7 @@ def parse_args(args_to_parse) -> ArgsWithUsage: parser.add_argument("-u", "--upstream-owner", nargs="?") parser.add_argument("repo", nargs="?") parser.add_argument("new_branch", nargs="?") + parser.add_argument("-c", "--compare", action="store_const") parser.add_argument("-h", "--help", action="store_const") parser.add_argument("--version", action="store_const") parser.add_argument("--parse-url", nargs="?") @@ -50,6 +51,8 @@ def parse_args(args_to_parse) -> ArgsWithUsage: # Handle usages of the gimmegit command. if hasattr(args, "repo"): return parse_as_primary(args, unknown_args) + if hasattr(args, "compare"): + return parse_as_compare(args, unknown_args) if hasattr(args, "help"): return parse_as_help(args, unknown_args) if hasattr(args, "version"): @@ -93,6 +96,27 @@ def done(error: str | None) -> ArgsWithUsage: if not hasattr(args, "new_branch"): args.new_branch = None # Handle unknown args. + if hasattr(args, "compare"): + unknown_args.append("-c/--compare") + if hasattr(args, "help"): + unknown_args.append("-h/--help") + if hasattr(args, "version"): + unknown_args.append("--version") + if hasattr(args, "parse_url"): + unknown_args.append("--parse-url") + if unknown_args: + return done(f"Unexpected options: {', '.join(unknown_args)}.") + return done(None) + + +def parse_as_compare(args: argparse.Namespace, unknown_args: list[str]) -> ArgsWithUsage: + def done(error: str | None) -> ArgsWithUsage: + return ArgsWithUsage(args=args, error=error, usage="compare") + + # Handle unknown args. + unknown_args = add_non_primary_unknown_args(args, unknown_args) + if hasattr(args, "ssh"): + unknown_args.append("--ssh") if hasattr(args, "help"): unknown_args.append("-h/--help") if hasattr(args, "version"): diff --git a/src/gimmegit/_cli.py b/src/gimmegit/_cli.py index c8a6a38..5198117 100644 --- a/src/gimmegit/_cli.py +++ b/src/gimmegit/_cli.py @@ -11,6 +11,7 @@ import sys import tempfile import urllib.parse +import webbrowser import git import github @@ -108,38 +109,44 @@ def main() -> None: if not status: logger.error("The working directory is inside a repo.") sys.exit(1) - else: - status_usage(status) - logger.warning( - "Skipped cloning because the working directory is inside a gimmegit clone." - ) - return + assert status # For the type checker. + status_usage(status) + logger.warning( + "Skipped cloning because the working directory is inside a gimmegit clone." + ) + return primary_usage(args, cloning_args) + elif args_with_usage.usage == "compare": + working = _inspect.get_outer_repo() + status = _status.get_status(working) if working else None + if not status: + logger.error("The working directory is not inside a gimmegit clone.") + sys.exit(1) + assert status # For the type checker. + compare_usage(status) elif args_with_usage.usage == "help": logger.info(_help.help) elif args_with_usage.usage == "version": logger.log(DATA_LEVEL, f"gimmegit {_version.__version__}") elif args_with_usage.usage == "tool": parsed_url = parse_github_url(args.parse_url) - if parsed_url: - logger.log(DATA_LEVEL, json.dumps(asdict(parsed_url))) - else: + if not parsed_url: logger.error(f"'{args.parse_url}' is not a supported GitHub URL.") sys.exit(1) + assert parsed_url # For the type checker. + logger.log(DATA_LEVEL, json.dumps(asdict(parsed_url))) elif args_with_usage.usage == "bare": working = _inspect.get_outer_repo() - if working: - status = _status.get_status(working) - if not status: - logger.error( - "The working directory is inside a repo that is not supported by gimmegit." - ) - sys.exit(1) - else: - status_usage(status) - else: + if not working: logger.error("No repo specified. Run 'gimmegit -h' for help.") sys.exit(2) + assert working # For the type checker. + status = _status.get_status(working) + if not status: + logger.error("The working directory is not inside a gimmegit clone.") + sys.exit(1) + assert status # For the type checker. + status_usage(status) def clone(context: Context, cloning_args: list[str]) -> None: @@ -217,6 +224,33 @@ def clone(context: Context, cloning_args: list[str]) -> None: ) +def compare_usage(status: _status.Status) -> None: + if not status.has_remote: + logger.error("The review branch has not been created.") + sys.exit(1) + if not os.isatty(sys.stdout.fileno()): + logger.log(DATA_LEVEL, status.compare_url) + return + # Try xdg-open first, to suppress a Linux/snap/Firefox error message: + # Gtk-Message: ... Not loading module "atk-bridge"... + if shutil.which("xdg-open"): + result = subprocess.run( + ["xdg-open", status.compare_url], + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + ) + if result.returncode: + logger.log(DATA_LEVEL, status.compare_url) + return + try: + opened = webbrowser.open(status.compare_url, new=2) + except webbrowser.Error: + logger.log(DATA_LEVEL, status.compare_url) + else: + if not opened: + logger.log(DATA_LEVEL, status.compare_url) + + def configure_logger_data() -> None: retval = logging.StreamHandler(sys.stdout) retval.setFormatter(logging.Formatter("%(message)s")) @@ -430,7 +464,12 @@ def is_valid_branch_name(branch: str) -> bool: # When run in a repo, 'git check-ref-format --branch' expands "previous checkout" references. # Such references should be flagged as invalid, so we run the Git command in an empty dir. with tempfile.TemporaryDirectory() as empty_dir: - command = ["git", "check-ref-format", "--branch", branch] + command = [ + "git", + "check-ref-format", + "--branch", + branch, + ] result = subprocess.run( command, cwd=empty_dir, diff --git a/src/gimmegit/_help.py b/src/gimmegit/_help.py index a8035dc..6e715dc 100644 --- a/src/gimmegit/_help.py +++ b/src/gimmegit/_help.py @@ -7,6 +7,9 @@ gimmegit [--color {auto,always,never}] Additional commands: + gimmegit -c + gimmegit --compare + gimmegit -h gimmegit --help diff --git a/tests/functional/test_arg_errors.py b/tests/functional/test_arg_errors.py index ac64445..4c5ccaf 100644 --- a/tests/functional/test_arg_errors.py +++ b/tests/functional/test_arg_errors.py @@ -148,6 +148,27 @@ def test_invalid_ssh(uv_run, test_dir): assert result.stderr == expected_stderr +def test_repo_with_compare(uv_run, test_dir): + command = [ + *uv_run, + "gimmegit", + "-c", + "dwilding/jubilant", + ] + result = subprocess.run( + command, + cwd=test_dir, + capture_output=True, + text=True, + ) + assert result.returncode == 2 + assert not result.stdout + expected_stderr = """\ +Error: Unexpected options: -c/--compare. Run 'gimmegit -h' for help. +""" + assert result.stderr == expected_stderr + + def test_repo_with_help(uv_run, test_dir): command = [ *uv_run, diff --git a/tests/functional/test_bare_compare.py b/tests/functional/test_bare_compare.py new file mode 100644 index 0000000..54ac052 --- /dev/null +++ b/tests/functional/test_bare_compare.py @@ -0,0 +1,21 @@ +import subprocess + + +def test_compre_no_outer(uv_run, test_dir): + command = [ + *uv_run, + "gimmegit", + "-c", + ] + result = subprocess.run( + command, + cwd=test_dir, + capture_output=True, + text=True, + ) + assert result.returncode == 1 + assert not result.stdout + expected_stderr = """\ +Error: The working directory is not inside a gimmegit clone. +""" + assert result.stderr == expected_stderr diff --git a/tests/functional/test_clone.py b/tests/functional/test_clone.py index 4efe907..4c77d7b 100644 --- a/tests/functional/test_clone.py +++ b/tests/functional/test_clone.py @@ -96,7 +96,10 @@ def test_existing_clone(uv_run, test_dir): def test_dashboard(uv_run, test_dir): working_dir = Path(test_dir) / "operator/canonical-2.23-maintenance" - command = [*uv_run, "gimmegit"] + command = [ + *uv_run, + "gimmegit", + ] result = subprocess.run( command, cwd=working_dir, @@ -113,7 +116,10 @@ def test_dashboard(uv_run, test_dir): def test_dashboard_no_remote(uv_run, test_dir): working_dir = Path(test_dir) / "jubilant/dwilding-my-feature/docs" - command = [*uv_run, "gimmegit"] + command = [ + *uv_run, + "gimmegit", + ] result = subprocess.run( command, cwd=working_dir, @@ -130,7 +136,11 @@ def test_dashboard_no_remote(uv_run, test_dir): def test_dashboard_warning(uv_run, test_dir): working_dir = Path(test_dir) / "jubilant/dwilding-my-feature/docs" - command = [*uv_run, "gimmegit", "some-project"] + command = [ + *uv_run, + "gimmegit", + "some-project", + ] result = subprocess.run( command, cwd=working_dir, @@ -149,6 +159,48 @@ def test_dashboard_warning(uv_run, test_dir): assert result.stderr == expected_stderr +def test_compare(uv_run, test_dir): + working_dir = Path(test_dir) / "operator/canonical-2.23-maintenance" + command = [ + *uv_run, + "gimmegit", + "-c", + ] + result = subprocess.run( + command, + cwd=working_dir, + capture_output=True, + text=True, + check=True, + ) + # Normally 'gimmegit -c' opens the URL, but if the output isn't going to a terminal + # then gimmegit outputs the URL instead. + expected_stdout = """\ +https://github.com/canonical/operator/compare/main...canonical:operator:2.23-maintenance?expand=1 +""" + assert result.stdout == expected_stdout + + +def test_compare_no_remote(uv_run, test_dir): + working_dir = Path(test_dir) / "jubilant/dwilding-my-feature/docs" + command = [ + *uv_run, + "gimmegit", + "-c", + ] + result = subprocess.run( + command, + cwd=working_dir, + capture_output=True, + text=True, + ) + assert not result.stdout + expected_stderr = """\ +Error: The review branch has not been created. +""" + assert result.stderr == expected_stderr + + def test_in_project_dir(uv_run, test_dir): # . # └── jubilant Try running 'gimmegit dwilding/jubilant my-feature' diff --git a/tests/functional/test_outer.py b/tests/functional/test_outer.py index 403be31..bcb9f5d 100644 --- a/tests/functional/test_outer.py +++ b/tests/functional/test_outer.py @@ -16,7 +16,34 @@ def test_working_repo_no_dashboard(uv_run, test_dir): ) working_dir = repo_dir / "foo" working_dir.mkdir() - command = [*uv_run, "gimmegit"] + command = [ + *uv_run, + "gimmegit", + ] + result = subprocess.run( + command, + cwd=working_dir, + capture_output=True, + text=True, + ) + assert result.returncode == 1 + assert not result.stdout + expected_stderr = """\ +Error: The working directory is not inside a gimmegit clone. +""" + assert result.stderr == expected_stderr + + +def test_working_repo_no_compare(uv_run, test_dir): + # . + # └── frogtab Suppose that this dir is a repo + # └── foo Try running 'gimmegit -c' + working_dir = Path(test_dir) / "frogtab/foo" + command = [ + *uv_run, + "gimmegit", + "-c", + ] result = subprocess.run( command, cwd=working_dir, @@ -26,7 +53,7 @@ def test_working_repo_no_dashboard(uv_run, test_dir): assert result.returncode == 1 assert not result.stdout expected_stderr = """\ -Error: The working directory is inside a repo that is not supported by gimmegit. +Error: The working directory is not inside a gimmegit clone. """ assert result.stderr == expected_stderr @@ -36,7 +63,11 @@ def test_working_repo_no_clone(uv_run, test_dir): # └── frogtab Suppose that this dir is a repo # └── foo Try running 'gimmegit some-project' working_dir = Path(test_dir) / "frogtab/foo" - command = [*uv_run, "gimmegit", "some-project"] + command = [ + *uv_run, + "gimmegit", + "some-project", + ] result = subprocess.run( command, cwd=working_dir, diff --git a/uv.lock b/uv.lock index e510cea..862ae56 100644 --- a/uv.lock +++ b/uv.lock @@ -264,7 +264,7 @@ requires-dist = [ dev = [ { name = "pytest" }, { name = "ruff" }, - { name = "ty", specifier = ">=0.0.1a27" }, + { name = "ty", specifier = ">=0.0.1a33" }, ] [[package]] @@ -611,27 +611,27 @@ wheels = [ [[package]] name = "ty" -version = "0.0.1a27" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/8f/65/3592d7c73d80664378fc90d0a00c33449a99cbf13b984433c883815245f3/ty-0.0.1a27.tar.gz", hash = "sha256:d34fe04979f2c912700cbf0919e8f9b4eeaa10c4a2aff7450e5e4c90f998bc28", size = 4516059, upload-time = "2025-11-18T21:55:18.381Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e6/05/7945aa97356446fd53ed3ddc7ee02a88d8ad394217acd9428f472d6b109d/ty-0.0.1a27-py3-none-linux_armv6l.whl", hash = "sha256:3cbb735f5ecb3a7a5f5b82fb24da17912788c109086df4e97d454c8fb236fbc5", size = 9375047, upload-time = "2025-11-18T21:54:31.577Z" }, - { url = "https://files.pythonhosted.org/packages/69/4e/89b167a03de0e9ec329dc89bc02e8694768e4576337ef6c0699987681342/ty-0.0.1a27-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:4a6367236dc456ba2416563301d498aef8c6f8959be88777ef7ba5ac1bf15f0b", size = 9169540, upload-time = "2025-11-18T21:54:34.036Z" }, - { url = "https://files.pythonhosted.org/packages/38/07/e62009ab9cc242e1becb2bd992097c80a133fce0d4f055fba6576150d08a/ty-0.0.1a27-py3-none-macosx_11_0_arm64.whl", hash = "sha256:8e93e231a1bcde964cdb062d2d5e549c24493fb1638eecae8fcc42b81e9463a4", size = 8711942, upload-time = "2025-11-18T21:54:36.3Z" }, - { url = "https://files.pythonhosted.org/packages/b5/43/f35716ec15406f13085db52e762a3cc663c651531a8124481d0ba602eca0/ty-0.0.1a27-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c5b6a8166b60117da1179851a3d719cc798bf7e61f91b35d76242f0059e9ae1d", size = 8984208, upload-time = "2025-11-18T21:54:39.453Z" }, - { url = "https://files.pythonhosted.org/packages/2d/79/486a3374809523172379768de882c7a369861165802990177fe81489b85f/ty-0.0.1a27-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bfbe8b0e831c072b79a078d6c126d7f4d48ca17f64a103de1b93aeda32265dc5", size = 9157209, upload-time = "2025-11-18T21:54:42.664Z" }, - { url = "https://files.pythonhosted.org/packages/ff/08/9a7c8efcb327197d7d347c548850ef4b54de1c254981b65e8cd0672dc327/ty-0.0.1a27-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:90e09678331552e7c25d7eb47868b0910dc5b9b212ae22c8ce71a52d6576ddbb", size = 9519207, upload-time = "2025-11-18T21:54:45.311Z" }, - { url = "https://files.pythonhosted.org/packages/e0/9d/7b4680683e83204b9edec551bb91c21c789ebc586b949c5218157ee474b7/ty-0.0.1a27-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:88c03e4beeca79d85a5618921e44b3a6ea957e0453e08b1cdd418b51da645939", size = 10148794, upload-time = "2025-11-18T21:54:48.329Z" }, - { url = "https://files.pythonhosted.org/packages/89/21/8b961b0ab00c28223f06b33222427a8e31aa04f39d1b236acc93021c626c/ty-0.0.1a27-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3ece5811322789fefe22fc088ed36c5879489cd39e913f9c1ff2a7678f089c61", size = 9900563, upload-time = "2025-11-18T21:54:51.214Z" }, - { url = "https://files.pythonhosted.org/packages/85/eb/95e1f0b426c2ea8d443aa923fcab509059c467bbe64a15baaf573fea1203/ty-0.0.1a27-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2f2ccb4f0fddcd6e2017c268dfce2489e9a36cb82a5900afe6425835248b1086", size = 9926355, upload-time = "2025-11-18T21:54:53.927Z" }, - { url = "https://files.pythonhosted.org/packages/f5/78/40e7f072049e63c414f2845df780be3a494d92198c87c2ffa65e63aecf3f/ty-0.0.1a27-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:33450528312e41d003e96a1647780b2783ab7569bbc29c04fc76f2d1908061e3", size = 9480580, upload-time = "2025-11-18T21:54:56.617Z" }, - { url = "https://files.pythonhosted.org/packages/18/da/f4a2dfedab39096808ddf7475f35ceb750d9a9da840bee4afd47b871742f/ty-0.0.1a27-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:a0a9ac635deaa2b15947701197ede40cdecd13f89f19351872d16f9ccd773fa1", size = 8957524, upload-time = "2025-11-18T21:54:59.085Z" }, - { url = "https://files.pythonhosted.org/packages/21/ea/26fee9a20cf77a157316fd3ab9c6db8ad5a0b20b2d38a43f3452622587ac/ty-0.0.1a27-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:797fb2cd49b6b9b3ac9f2f0e401fb02d3aa155badc05a8591d048d38d28f1e0c", size = 9201098, upload-time = "2025-11-18T21:55:01.845Z" }, - { url = "https://files.pythonhosted.org/packages/b0/53/e14591d1275108c9ae28f97ac5d4b93adcc2c8a4b1b9a880dfa9d07c15f8/ty-0.0.1a27-py3-none-musllinux_1_2_i686.whl", hash = "sha256:7fe81679a0941f85e98187d444604e24b15bde0a85874957c945751756314d03", size = 9275470, upload-time = "2025-11-18T21:55:04.23Z" }, - { url = "https://files.pythonhosted.org/packages/37/44/e2c9acecac70bf06fb41de285e7be2433c2c9828f71e3bf0e886fc85c4fd/ty-0.0.1a27-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:355f651d0cdb85535a82bd9f0583f77b28e3fd7bba7b7da33dcee5a576eff28b", size = 9592394, upload-time = "2025-11-18T21:55:06.542Z" }, - { url = "https://files.pythonhosted.org/packages/ee/a7/4636369731b24ed07c2b4c7805b8d990283d677180662c532d82e4ef1a36/ty-0.0.1a27-py3-none-win32.whl", hash = "sha256:61782e5f40e6df622093847b34c366634b75d53f839986f1bf4481672ad6cb55", size = 8783816, upload-time = "2025-11-18T21:55:09.648Z" }, - { url = "https://files.pythonhosted.org/packages/a7/1d/b76487725628d9e81d9047dc0033a5e167e0d10f27893d04de67fe1a9763/ty-0.0.1a27-py3-none-win_amd64.whl", hash = "sha256:c682b238085d3191acddcf66ef22641562946b1bba2a7f316012d5b2a2f4de11", size = 9616833, upload-time = "2025-11-18T21:55:12.457Z" }, - { url = "https://files.pythonhosted.org/packages/3a/db/c7cd5276c8f336a3cf87992b75ba9d486a7cf54e753fcd42495b3bc56fb7/ty-0.0.1a27-py3-none-win_arm64.whl", hash = "sha256:e146dfa32cbb0ac6afb0cb65659e87e4e313715e68d76fe5ae0a4b3d5b912ce8", size = 9137796, upload-time = "2025-11-18T21:55:15.897Z" }, +version = "0.0.1a33" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/60/34/82f76e63277f0a6585ea48a8d373cfee417a73755daa078250af65421c77/ty-0.0.1a33.tar.gz", hash = "sha256:1db139aa7cbc9879e93146c99bf5f1f5273ca608683f71b3a9a75f9f812b729f", size = 4704365, upload-time = "2025-12-09T22:35:19.424Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/99/4c/4aec80e452268432f60f17da3840ffd6fef46394300808d0af32766dc989/ty-0.0.1a33-py3-none-linux_armv6l.whl", hash = "sha256:2126e6b62a50dc807d45f56629668861bac95944c77b4b6b6dc13f629d5a5a7e", size = 9674171, upload-time = "2025-12-09T22:34:59.757Z" }, + { url = "https://files.pythonhosted.org/packages/fe/71/ad51a14e00aa0d7e57533f2a68f0865b240bd197c36b87ddab1dd12a1cdd/ty-0.0.1a33-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:f171a278a242b06c2f99327dacfa9c7f2d0328140f2976a46ca46e18cde2d6e3", size = 9466420, upload-time = "2025-12-09T22:34:54.884Z" }, + { url = "https://files.pythonhosted.org/packages/79/fa/72bf596a977e5d5343893bb1eb4092fdd0f22ed8c0f11427cc2201225bdb/ty-0.0.1a33-py3-none-macosx_11_0_arm64.whl", hash = "sha256:7b4249f030d24deeae7b25949d33832b4a25b5c893d679b32df1042584b9091f", size = 9009208, upload-time = "2025-12-09T22:35:27.871Z" }, + { url = "https://files.pythonhosted.org/packages/c4/0d/0e20c21e4473a6ea7109c252f6c6bbc41f895b18307e507d6c12a636e6b6/ty-0.0.1a33-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:176f56fc7a6ea176b36d397c42b35efebb441f1fa42524a010579d7019ca8b67", size = 9280560, upload-time = "2025-12-09T22:35:24.258Z" }, + { url = "https://files.pythonhosted.org/packages/6e/dd/627a0a3e2a270b7200c5f92cb01382a3f9ac4f072abf5e7eb3be8f2f4267/ty-0.0.1a33-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8ace2379e9c915c4c6d4dfd3737b290ebe2b008c20031233f4a6e9df0758f427", size = 9457161, upload-time = "2025-12-09T22:35:02.394Z" }, + { url = "https://files.pythonhosted.org/packages/ad/5a/974a48b39c885a17471c3f0847165567a77f05beef3b2573984b9b722378/ty-0.0.1a33-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4341a1daa7857b4de3a68658bad7aaa85577a82448182af2c6b412da02b19c14", size = 9873399, upload-time = "2025-12-09T22:34:49.919Z" }, + { url = "https://files.pythonhosted.org/packages/04/0e/8c09a95b91e3ba0d75a6cea69b06b0a070f085de7dd2aabf86d999175f29/ty-0.0.1a33-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:42c45b50b242af5868198131569d9f4ea37f83212a72494b2553e60f385874cc", size = 10487274, upload-time = "2025-12-09T22:34:52.579Z" }, + { url = "https://files.pythonhosted.org/packages/56/37/8d6e898ecf85f67a9bfaaff9c5194d9eaf4d826363a7dab27460eb2d630c/ty-0.0.1a33-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:229b8d927d7815ba4af0b45f1a97766813b62ee97599199900b8ccc1be911284", size = 10244389, upload-time = "2025-12-09T22:35:09.667Z" }, + { url = "https://files.pythonhosted.org/packages/e2/c4/f98a35b12b552d28feb4157334484aa5f472c30944418e23b4a49fad2e40/ty-0.0.1a33-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5018ac5b64865d416b098246da38d2809fdc69e9d86b4e1cf94266e102e7c77f", size = 10224857, upload-time = "2025-12-09T22:35:04.661Z" }, + { url = "https://files.pythonhosted.org/packages/3b/5d/fffb85c5fd7bdffcef212f514b439f229ecaee14e9bf7c199a625819c502/ty-0.0.1a33-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2cd6d6304302ad28e0412d80118a5f63d01af37d3cb39abf33856d348c0819e1", size = 9792377, upload-time = "2025-12-09T22:34:44.634Z" }, + { url = "https://files.pythonhosted.org/packages/6e/ea/0664b0e4a2c286bd880be47121c781befad8077e15cd8a50b9b1f51b8676/ty-0.0.1a33-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:38b75adc050d26a88bbf85d55a4f7633216e455b76e9ee21d6f38640aa040d73", size = 9262018, upload-time = "2025-12-09T22:34:47.274Z" }, + { url = "https://files.pythonhosted.org/packages/89/dd/3d99564d7e649326c98c72b863f0aad771abfc75140413e7b70559ae4850/ty-0.0.1a33-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:efabd881d5b00058c3945b08abbe853b19c93cd0c7148bbcfd27c5d9e6c738f3", size = 9494056, upload-time = "2025-12-09T22:35:21.967Z" }, + { url = "https://files.pythonhosted.org/packages/49/9b/3471118edc5f945e2589c66c27e71b5d9a9efe21c82ced03ea698dbe9a19/ty-0.0.1a33-py3-none-musllinux_1_2_i686.whl", hash = "sha256:ca3b8f84fe661bfb60d1e7665e54dd9c6c84769bff117b00e76ef537473cc59c", size = 9623498, upload-time = "2025-12-09T22:35:07.072Z" }, + { url = "https://files.pythonhosted.org/packages/b8/6d/12dcb22b015a4d3e677f394ab7dc80307f2b59f898ea785ea6bfcef8cffa/ty-0.0.1a33-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:f46ae07e353a54512b64b590eae4d82eb22c3a5f5947cea04f950dc1993f64f1", size = 9904193, upload-time = "2025-12-09T22:34:57.106Z" }, + { url = "https://files.pythonhosted.org/packages/c2/e8/628063386fda2f9182089bfe0c8a27ede0c1a120bef74294008468cd2d7d/ty-0.0.1a33-py3-none-win32.whl", hash = "sha256:9020b8be11a184bbe26d07b1a8f0b2e3b75302b08b98b4b1fb6d5d2d03e64aca", size = 9095241, upload-time = "2025-12-09T22:35:14.475Z" }, + { url = "https://files.pythonhosted.org/packages/e0/fe/8ad29c47c9499132849cd5401f67c6bdd2912be8dcb298e774b4f39e1cce/ty-0.0.1a33-py3-none-win_amd64.whl", hash = "sha256:553b5281d424c69389508a60dfd8af8e3014529ca6856dfed1f231020bc58d09", size = 9948007, upload-time = "2025-12-09T22:35:17.043Z" }, + { url = "https://files.pythonhosted.org/packages/2f/fc/1825f1f8c77d4d8fe75543882d9ad5934e568aa807e1a4cb7e999f701750/ty-0.0.1a33-py3-none-win_arm64.whl", hash = "sha256:d9937e9ddc7b383c6b1ab3065982fb2b8d0a2884ae5bd7b542e4208a807e326e", size = 9471473, upload-time = "2025-12-09T22:35:12.105Z" }, ] [[package]]