diff --git a/README.md b/README.md index 4a0fee70..d3947190 100644 --- a/README.md +++ b/README.md @@ -1,34 +1,104 @@ # Garth -> **Garth is deprecated and no longer maintained.** Garmin changed their auth -> flow, breaking the mobile auth approach that Garth depends on. I'm not in a -> position to dedicate the time to adapt to these changes. See the -> [announcement](https://github.com/matin/garth/discussions/222) for details. -> Anyone is welcome to fork Garth as a starting point for a new library. - +[![CI](https://github.com/matin/garth/actions/workflows/ci.yml/badge.svg?branch=main&event=push)](https://github.com/matin/garth/actions/workflows/ci.yml?query=event%3Apush+branch%3Amain+workflow%3ACI) +[![codecov](https://codecov.io/gh/matin/garth/branch/main/graph/badge.svg?token=0EFFYJNFIL)](https://codecov.io/gh/matin/garth) [![PyPI version](https://img.shields.io/pypi/v/garth.svg?logo=python&logoColor=brightgreen&color=brightgreen)](https://pypi.org/project/garth/) [![PyPI - Downloads](https://img.shields.io/pypi/dm/garth)](https://pypistats.org/packages/garth) +[![Documentation](https://img.shields.io/badge/docs-readthedocs-blue)](https://garth.readthedocs.io) Garmin SSO auth + Connect Python client -## About +## Features + +- OAuth1/OAuth2 authentication (OAuth1 token lasts ~1 year) +- MFA support with custom handlers +- Auto-refresh of OAuth2 token +- Auto-resume from `GARTH_HOME` or `GARTH_TOKEN` environment variables +- Works on Google Colab +- Pydantic dataclasses for validated data +- Built-in telemetry for diagnosing auth issues +- Full test coverage + +## Installation + +```bash +pip install garth +``` + +## Quick Start + +### Authenticate and save session + +```python +import garth +from getpass import getpass + +garth.login(input("Email: "), getpass("Password: ")) +garth.save("~/.garth") +``` + +### Resume session + +```python +import garth +from garth.exc import GarthException -Garth was a Python library for Garmin Connect API access with OAuth -authentication. It reached 350k+ downloads per month and was translated into -multiple programming languages. +garth.resume("~/.garth") +try: + garth.client.username +except GarthException: + # Session is expired. You'll need to log in again + pass +``` -Garmin recently changed their auth flow, breaking the mobile auth approach -that Garth and other libraries depend on -([#217](https://github.com/matin/garth/issues/217)). This is the final -release. +Or use environment variables for automatic session restoration: -## For existing users +```bash +export GARTH_HOME=~/.garth +# or +export GARTH_TOKEN="eyJvYXV0aF90b2tlbi..." # from `uvx garth login` +``` -If you already have a saved session with a valid OAuth1 token, Garth may -continue to work until that token expires (~1 year from when it was issued). -New logins will not work. +```python +import garth +# Session is automatically loaded +garth.client.username +``` + +### Fetch data + +```python +# Get daily stress +garth.DailyStress.list("2023-07-23", 7) + +# Get sleep data +garth.SleepData.get("2023-07-20") + +# Get weight +garth.WeightData.list("2025-06-01", 30) + +# Direct API calls +garth.connectapi("/usersummary-service/stats/stress/weekly/2023-07-05/52") +``` ## Documentation -Documentation is still available at -**[garth.readthedocs.io](https://garth.readthedocs.io)** for reference. +Full documentation at **[garth.readthedocs.io](https://garth.readthedocs.io)** + +## MCP Server + +[`garth-mcp-server`](https://github.com/matin/garth-mcp-server) is in early development. + +To generate your `GARTH_TOKEN`, use `uvx garth login`. + +## Star History + + + + + + + Star History Chart + + + diff --git a/docs/getting-started.md b/docs/getting-started.md index 142b9647..603aa3f9 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -1,10 +1,5 @@ # Getting Started -!!! warning "Deprecated" - **Garth is deprecated and no longer maintained.** New logins will not work - due to changes in Garmin's auth flow. See the - [announcement](https://github.com/matin/garth/discussions/222) for details. - ## Installation ### From PyPI diff --git a/docs/index.md b/docs/index.md index fa7efbd7..fc6d7339 100644 --- a/docs/index.md +++ b/docs/index.md @@ -2,34 +2,65 @@ Garmin SSO auth + Connect Python client -!!! warning "Deprecated" - **Garth is deprecated and no longer maintained.** Garmin changed their auth - flow, breaking the mobile auth approach that Garth depends on. See the - [announcement](https://github.com/matin/garth/discussions/222) for details. - Anyone is welcome to fork Garth as a starting point for a new library. +[![CI](https://github.com/matin/garth/actions/workflows/ci.yml/badge.svg?branch=main&event=push)](https://github.com/matin/garth/actions/workflows/ci.yml?query=event%3Apush+branch%3Amain+workflow%3ACI) +[![codecov](https://codecov.io/gh/matin/garth/branch/main/graph/badge.svg?token=0EFFYJNFIL)](https://codecov.io/gh/matin/garth) +[![PyPI version](https://img.shields.io/pypi/v/garth.svg?logo=python&logoColor=brightgreen&color=brightgreen)](https://pypi.org/project/garth/) +[![PyPI - Downloads](https://img.shields.io/pypi/dm/garth)](https://pypistats.org/packages/garth) -## About +## Why Garth? -Garth was a Python library for Garmin Connect API access with OAuth -authentication. It reached 350k+ downloads per month and was translated into -multiple programming languages. +Garth is meant for personal use and follows the philosophy that your data is +your data. You should be able to download it and analyze it in the way that +you'd like. In my case, that means processing with Google Colab, Pandas, +Matplotlib, etc. -Garmin recently changed their auth flow, breaking the mobile auth approach -that Garth and other libraries depend on -([#217](https://github.com/matin/garth/issues/217)). This is the final -release. +There are already a few Garmin Connect libraries. Why write another? -## For existing users +### Authentication and stability -If you already have a saved session with a valid OAuth1 token, Garth may -continue to work until that token expires (~1 year from when it was issued). -New logins will not work. +The most important reasoning is to build a library with authentication that +works on [Google Colab](https://colab.research.google.com/) and doesn't require +tools like Cloudscraper. Garth, in comparison: -## Documentation +1. Uses OAuth1 and OAuth2 token authentication after initial login +1. OAuth1 token survives for a year +1. Supports MFA +1. Auto-refresh of OAuth2 token when expired +1. Works on Google Colab +1. Uses Pydantic dataclasses to validate and simplify use of data +1. Full test coverage -The rest of this documentation is preserved for reference. +### JSON vs HTML -- [Getting Started](getting-started.md) - Authentication and session management +Using `garth.connectapi()` allows you to make requests to the Connect API +and receive JSON vs needing to parse HTML. You can use the same endpoints the +mobile app uses. + +This also goes back to authentication. Garth manages the necessary Bearer +Authentication (along with auto-refresh) necessary to make requests routed to +the Connect API. + +## Quick Start + +```bash +pip install garth +``` + +```python +import garth +from getpass import getpass + +# Login and save session +garth.login(input("Email: "), getpass("Password: ")) +garth.save("~/.garth") + +# Later, resume the session +garth.resume("~/.garth") +``` + +## Next Steps + +- [Getting Started](getting-started.md) - Detailed authentication and session management - [Configuration](configuration.md) - Domain and proxy settings -- [API Reference](api/stats.md) - Available data types +- [API Reference](api/stats.md) - Explore available data types - [Examples](examples.md) - Google Colab notebooks diff --git a/docs/telemetry.md b/docs/telemetry.md index b306d087..81c17c2a 100644 --- a/docs/telemetry.md +++ b/docs/telemetry.md @@ -2,8 +2,29 @@ Garth includes built-in telemetry using [Pydantic Logfire](https://pydantic.dev/logfire) for logging and observability. -Telemetry is **disabled by default** as of v0.8.0 since Garth is no longer -actively maintained. +Telemetry is **enabled by default** to help diagnose authentication issues. It +is **isolated to Garth's requests only** — it won't affect other HTTP clients +in your application. + +## Why telemetry is on by default + +Garmin occasionally changes their authentication endpoints in ways that only +affect a subset of users — for example, the +[SSO migration](https://github.com/cyberjunky/python-garminconnect/issues/332) +that caused 403 errors for some users while others were unaffected. Without +telemetry, these issues are nearly impossible to reproduce or diagnose. With +default-on telemetry, maintainers can look up the exact request/response +sequence for a failing session using the session ID. + +Each session generates a unique `session_id` that is printed to stdout when +garth is imported: + +```text +Garth session: a01e3fc1d5ac4c9a +``` + +When reporting issues, include your session ID so maintainers can look up your +request logs. ## Disable telemetry @@ -45,8 +66,8 @@ Telemetry settings can be configured via environment variables with the | Environment Variable | Default | Description | |---|---|---| -| `GARTH_TELEMETRY_ENABLED` | `false` | Enable/disable telemetry | -| `GARTH_TELEMETRY_SEND_TO_LOGFIRE` | `false` | Send to Logfire Cloud | +| `GARTH_TELEMETRY_ENABLED` | `true` | Enable/disable telemetry | +| `GARTH_TELEMETRY_SEND_TO_LOGFIRE` | `true` | Send to Logfire Cloud | | `GARTH_TELEMETRY_TOKEN` | *(built-in)* | Logfire write token | ## What gets logged diff --git a/pyproject.toml b/pyproject.toml index a7320d76..32cea143 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -16,7 +16,7 @@ requires-python = ">=3.10" readme = "README.md" license = {text = "MIT"} classifiers = [ - "Development Status :: 7 - Inactive", + "Development Status :: 5 - Production/Stable", "License :: OSI Approved :: MIT License", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", diff --git a/src/garth/__init__.py b/src/garth/__init__.py index 64786c50..4245dc6b 100644 --- a/src/garth/__init__.py +++ b/src/garth/__init__.py @@ -1,5 +1,3 @@ -import warnings - from .data import ( Activity, BodyBatteryData, @@ -34,14 +32,6 @@ from .version import __version__ -warnings.warn( - "Garth is deprecated and no longer maintained. " - "See https://github.com/matin/garth/discussions/222", - DeprecationWarning, - stacklevel=2, -) - - __all__ = [ "Activity", "BodyBatteryData", diff --git a/src/garth/sso.py b/src/garth/sso.py index b8c0a004..a444bd7c 100644 --- a/src/garth/sso.py +++ b/src/garth/sso.py @@ -30,8 +30,9 @@ # The SSO endpoints run in a WebView, so requests must look like a # browser — not a Python HTTP client. _SSO_UA = ( - "Mozilla/5.0 (iPhone; CPU iPhone OS 18_7 like Mac OS X) " - "AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148" + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) " + "AppleWebKit/537.36 (KHTML, like Gecko) " + "Chrome/131.0.0.0 Safari/537.36" ) SSO_PAGE_HEADERS = { "User-Agent": _SSO_UA, diff --git a/src/garth/telemetry.py b/src/garth/telemetry.py index b16ab290..6a3dd973 100644 --- a/src/garth/telemetry.py +++ b/src/garth/telemetry.py @@ -16,7 +16,7 @@ LOGFIRE_AVAILABLE = True except ImportError: # pragma: no cover - logfire = None # type: ignore[assignment] # ty: ignore[invalid-assignment] + logfire = None # type: ignore[assignment] LOGFIRE_AVAILABLE = False @@ -109,8 +109,8 @@ class Telemetry(BaseSettings): extra="ignore", ) - enabled: bool = False - send_to_logfire: bool = False + enabled: bool = True + send_to_logfire: bool = True token: str = DEFAULT_TOKEN callback: Callable[[dict], None] | None = Field(default=None, exclude=True) session_id: str = Field( @@ -176,11 +176,11 @@ def configure( callback: Callable[[dict], None] | None = None, ): """ - Configure telemetry. Disabled by default. + Configure telemetry. Enabled by default. Args: - enabled: Enable/disable telemetry (default: False) - send_to_logfire: Send to Logfire Cloud (default: False) + enabled: Enable/disable telemetry (default: True) + send_to_logfire: Send to Logfire Cloud (default: True) token: Logfire write token callback: Custom callback for telemetry data. If provided, logfire will not be configured and data will be passed diff --git a/src/garth/version.py b/src/garth/version.py index 777f190d..af4852c0 100644 --- a/src/garth/version.py +++ b/src/garth/version.py @@ -1 +1 @@ -__version__ = "0.8.0" +__version__ = "0.7.12" diff --git a/tests/test_telemetry.py b/tests/test_telemetry.py index b9541077..11758ae5 100644 --- a/tests/test_telemetry.py +++ b/tests/test_telemetry.py @@ -52,8 +52,8 @@ def test_telemetry_defaults(monkeypatch): monkeypatch.delenv("GARTH_TELEMETRY_SEND_TO_LOGFIRE", raising=False) t = Telemetry() - assert t.enabled is False - assert t.send_to_logfire is False + assert t.enabled is True + assert t.send_to_logfire is True assert t.token == DEFAULT_TOKEN assert t.callback is None assert t.session_id # non-empty diff --git a/uv.lock b/uv.lock index 2538f753..62b43e13 100644 --- a/uv.lock +++ b/uv.lock @@ -2417,26 +2417,26 @@ wheels = [ [[package]] name = "ty" -version = "0.0.26" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/18/94/4879b81f8681117ccaf31544579304f6dc2ddcc0c67f872afb35869643a2/ty-0.0.26.tar.gz", hash = "sha256:0496b62405d62de7b954d6d677dc1cc5d3046197215d7a0a7fef37745d7b6d29", size = 5393643, upload-time = "2026-03-26T16:27:11.067Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/83/24/99fe33ecd7e16d23c53b0d4244778c6d1b6eb1663b091236dcba22882d67/ty-0.0.26-py3-none-linux_armv6l.whl", hash = "sha256:35beaa56cf59725fd59ab35d8445bbd40b97fe76db39b052b1fcb31f9bf8adf7", size = 10521856, upload-time = "2026-03-26T16:27:06.335Z" }, - { url = "https://files.pythonhosted.org/packages/55/97/1b5e939e2ff69b9bb279ab680bfa8f677d886309a1ac8d9588fd6ce58146/ty-0.0.26-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:487a0be58ab0eb02e31ba71eb6953812a0f88e50633469b0c0ce3fb795fe0fa1", size = 10320958, upload-time = "2026-03-26T16:27:13.849Z" }, - { url = "https://files.pythonhosted.org/packages/71/25/37081461e13d38a190e5646948d7bc42084f7bd1c6b44f12550be3923e7e/ty-0.0.26-py3-none-macosx_11_0_arm64.whl", hash = "sha256:a01b7de5693379646d423b68f119719a1338a20017ba48a93eefaff1ee56f97b", size = 9799905, upload-time = "2026-03-26T16:26:55.805Z" }, - { url = "https://files.pythonhosted.org/packages/a1/1c/295d8f55a7b0e037dfc3a5ec4bdda3ab3cbca6f492f725bf269f96a4d841/ty-0.0.26-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:628c3ee869d113dd2bd249925662fd39d9d0305a6cb38f640ddaa7436b74a1ef", size = 10317507, upload-time = "2026-03-26T16:27:31.887Z" }, - { url = "https://files.pythonhosted.org/packages/1d/62/48b3875c5d2f48fe017468d4bbdde1164c76a8184374f1d5e6162cf7d9b8/ty-0.0.26-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:63d04f35f5370cbc91c0b9675dc83e0c53678125a7b629c9c95769e86f123e65", size = 10319821, upload-time = "2026-03-26T16:27:29.647Z" }, - { url = "https://files.pythonhosted.org/packages/ff/28/cfb2d495046d5bf42d532325cea7412fa1189912d549dbfae417a24fd794/ty-0.0.26-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a53c4e6f6a91927f8b90e584a4b12bcde05b0c1870ddff8d17462168ad7947a", size = 10831757, upload-time = "2026-03-26T16:27:37.441Z" }, - { url = "https://files.pythonhosted.org/packages/26/bf/dbc3e42f448a2d862651de070b4108028c543ca18cab096b38d7de449915/ty-0.0.26-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:caf2ced0e58d898d5e3ba5cb843e0ebd377c8a461464748586049afbd9321f51", size = 11369556, upload-time = "2026-03-26T16:26:58.655Z" }, - { url = "https://files.pythonhosted.org/packages/92/4c/6d2f8f34bc6d502ab778c9345a4a936a72ae113de11329c1764bb1f204f6/ty-0.0.26-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:384807bbcb7d7ce9b97ee5aaa6417a8ae03ccfb426c52b08018ca62cf60f5430", size = 11085679, upload-time = "2026-03-26T16:27:21.746Z" }, - { url = "https://files.pythonhosted.org/packages/cc/f4/f3f61c203bc980dd9bba0ba7ed3c6e81ddfd36b286330f9487c2c7d041aa/ty-0.0.26-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a2c766a94d79b4f82995d41229702caf2d76e5c440ec7e543d05c70e98bf8ab", size = 10900581, upload-time = "2026-03-26T16:27:24.39Z" }, - { url = "https://files.pythonhosted.org/packages/3d/fd/3ca1b4e4bdd129829e9ce78677e0f8e0f1038a7702dccecfa52f037c6046/ty-0.0.26-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:f41ac45a0f8e3e8e181508d863a0a62156341db0f624ffd004b97ee550a9de80", size = 10294401, upload-time = "2026-03-26T16:27:03.999Z" }, - { url = "https://files.pythonhosted.org/packages/de/20/4ee3d8c3f90e008843795c765cb8bb245f188c23e5e5cc612c7697406fba/ty-0.0.26-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:73eb8327a34d529438dfe4db46796946c4e825167cbee434dc148569892e435f", size = 10351469, upload-time = "2026-03-26T16:27:19.003Z" }, - { url = "https://files.pythonhosted.org/packages/3d/b1/9fb154ade65906d4148f0b999c4a8257c2a34253cb72e15d84c1f04a064e/ty-0.0.26-py3-none-musllinux_1_2_i686.whl", hash = "sha256:4bb53a79259516535a1b55f613ba1619e9c666854946474ca8418c35a5c4fd60", size = 10529488, upload-time = "2026-03-26T16:27:01.378Z" }, - { url = "https://files.pythonhosted.org/packages/a5/70/9b02b03b1862e27b64143db65946d68b138160a5b6bfea193bee0b8bbc34/ty-0.0.26-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:2f0e75edc1aeb1b4b84af516c7891f631254a4ca3dcd15e848fa1e061e1fe9da", size = 10999015, upload-time = "2026-03-26T16:27:34.636Z" }, - { url = "https://files.pythonhosted.org/packages/21/16/0a56b8667296e2989b9d48095472d98ebf57a0006c71f2a101bbc62a142d/ty-0.0.26-py3-none-win32.whl", hash = "sha256:943c998c5523ed6b519c899c0c39b26b4c751a9759e460fb964765a44cde226f", size = 9912378, upload-time = "2026-03-26T16:27:08.999Z" }, - { url = "https://files.pythonhosted.org/packages/60/c2/fef0d4bba9cd89a82d725b3b1a66efb1b36629ecf0fb1d8e916cb75b8829/ty-0.0.26-py3-none-win_amd64.whl", hash = "sha256:19c856d343efeb1ecad8ee220848f5d2c424daf7b2feda357763ad3036e2172f", size = 10863737, upload-time = "2026-03-26T16:27:27.06Z" }, - { url = "https://files.pythonhosted.org/packages/4d/05/888ebcb3c4d3b6b72d5d3241fddd299142caa3c516e6d26a9cd887dfed3b/ty-0.0.26-py3-none-win_arm64.whl", hash = "sha256:2cde58ccffa046db1223dc28f3e7d4f2c7da8267e97cc5cd186af6fe85f1758a", size = 10285408, upload-time = "2026-03-26T16:27:16.432Z" }, +version = "0.0.23" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/75/ba/d3c998ff4cf6b5d75b39356db55fe1b7caceecc522b9586174e6a5dee6f7/ty-0.0.23.tar.gz", hash = "sha256:5fb05db58f202af366f80ef70f806e48f5237807fe424ec787c9f289e3f3a4ef", size = 5341461, upload-time = "2026-03-13T12:34:23.125Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/21/aab32603dfdfacd4819e52fa8c6074e7bd578218a5142729452fc6a62db6/ty-0.0.23-py3-none-linux_armv6l.whl", hash = "sha256:e810eef1a5f1cfc0731a58af8d2f334906a96835829767aed00026f1334a8dd7", size = 10329096, upload-time = "2026-03-13T12:34:09.432Z" }, + { url = "https://files.pythonhosted.org/packages/9f/a9/dd3287a82dce3df546ec560296208d4905dcf06346b6e18c2f3c63523bd1/ty-0.0.23-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:e43d36bd89a151ddcad01acaeff7dcc507cb73ff164c1878d2d11549d39a061c", size = 10156631, upload-time = "2026-03-13T12:34:53.122Z" }, + { url = "https://files.pythonhosted.org/packages/0f/01/3f25909b02fac29bb0a62b2251f8d62e65d697781ffa4cf6b47a4c075c85/ty-0.0.23-py3-none-macosx_11_0_arm64.whl", hash = "sha256:bd6a340969577b4645f231572c4e46012acba2d10d4c0c6570fe1ab74e76ae00", size = 9653211, upload-time = "2026-03-13T12:34:15.049Z" }, + { url = "https://files.pythonhosted.org/packages/d5/60/bfc0479572a6f4b90501c869635faf8d84c8c68ffc5dd87d04f049affabc/ty-0.0.23-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:341441783e626eeb7b1ec2160432956aed5734932ab2d1c26f94d0c98b229937", size = 10156143, upload-time = "2026-03-13T12:34:34.468Z" }, + { url = "https://files.pythonhosted.org/packages/3a/81/8a93e923535a340f54bea20ff196f6b2787782b2f2f399bd191c4bc132d6/ty-0.0.23-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8ce1dc66c26d4167e2c78d12fa870ef5a7ec9cc344d2baaa6243297cfa88bd52", size = 10136632, upload-time = "2026-03-13T12:34:28.832Z" }, + { url = "https://files.pythonhosted.org/packages/da/cb/2ac81c850c58acc9f976814404d28389c9c1c939676e32287b9cff61381e/ty-0.0.23-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bae1e7a294bf8528836f7617dc5c360ea2dddb63789fc9471ae6753534adca05", size = 10655025, upload-time = "2026-03-13T12:34:37.105Z" }, + { url = "https://files.pythonhosted.org/packages/b5/9b/bac771774c198c318ae699fc013d8cd99ed9caf993f661fba11238759244/ty-0.0.23-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d2b162768764d9dc177c83fb497a51532bb67cbebe57b8fa0f2668436bf53f3c", size = 11230107, upload-time = "2026-03-13T12:34:20.751Z" }, + { url = "https://files.pythonhosted.org/packages/14/09/7644fb0e297265e18243f878aca343593323b9bb19ed5278dcbc63781be0/ty-0.0.23-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d28384e48ca03b34e4e2beee0e230c39bbfb68994bb44927fec61ef3642900da", size = 10934177, upload-time = "2026-03-13T12:34:17.904Z" }, + { url = "https://files.pythonhosted.org/packages/18/14/69a25a0cad493fb6a947302471b579a03516a3b00e7bece77fdc6b4afb9b/ty-0.0.23-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:559d9a299df793cb7a7902caed5eda8a720ff69164c31c979673e928f02251ee", size = 10752487, upload-time = "2026-03-13T12:34:31.785Z" }, + { url = "https://files.pythonhosted.org/packages/9d/2a/42fc3cbccf95af0a62308ebed67e084798ab7a85ef073c9986ef18032743/ty-0.0.23-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:32a7b8a14a98e1d20a9d8d2af23637ed7efdb297ac1fa2450b8e465d05b94482", size = 10133007, upload-time = "2026-03-13T12:34:42.838Z" }, + { url = "https://files.pythonhosted.org/packages/e1/69/307833f1b52fa3670e0a1d496e43ef7df556ecde838192d3fcb9b35e360d/ty-0.0.23-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:6f803b9b9cca87af793467973b9abdd4b83e6b96d9b5e749d662cff7ead70b6d", size = 10169698, upload-time = "2026-03-13T12:34:12.351Z" }, + { url = "https://files.pythonhosted.org/packages/89/ae/5dd379ec22d0b1cba410d7af31c366fcedff191d5b867145913a64889f66/ty-0.0.23-py3-none-musllinux_1_2_i686.whl", hash = "sha256:4a0bf086ec8e2197b7ea7ebfcf4be36cb6a52b235f8be61647ef1b2d99d6ffd3", size = 10346080, upload-time = "2026-03-13T12:34:40.012Z" }, + { url = "https://files.pythonhosted.org/packages/98/c7/dfc83203d37998620bba9c4873a080c8850a784a8a46f56f8163c5b4e320/ty-0.0.23-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:252539c3fcd7aeb9b8d5c14e2040682c3e1d7ff640906d63fd2c4ce35865a4ba", size = 10848162, upload-time = "2026-03-13T12:34:45.421Z" }, + { url = "https://files.pythonhosted.org/packages/89/08/05481511cfbcc1fd834b6c67aaae090cb609a079189ddf2032139ccfc490/ty-0.0.23-py3-none-win32.whl", hash = "sha256:51b591d19eef23bbc3807aef77d38fa1f003c354e1da908aa80ea2dca0993f77", size = 9748283, upload-time = "2026-03-13T12:34:50.607Z" }, + { url = "https://files.pythonhosted.org/packages/31/2e/eaed4ff5c85e857a02415084c394e02c30476b65e158eec1938fdaa9a205/ty-0.0.23-py3-none-win_amd64.whl", hash = "sha256:1e137e955f05c501cfbb81dd2190c8fb7d01ec037c7e287024129c722a83c9ad", size = 10698355, upload-time = "2026-03-13T12:34:26.134Z" }, + { url = "https://files.pythonhosted.org/packages/91/29/b32cb7b4c7d56b9ed50117f8ad6e45834aec293e4cb14749daab4e9236d5/ty-0.0.23-py3-none-win_arm64.whl", hash = "sha256:a0399bd13fd2cd6683fd0a2d59b9355155d46546d8203e152c556ddbdeb20842", size = 10155890, upload-time = "2026-03-13T12:34:48.082Z" }, ] [[package]]