zpdatafetch 2.2.6__tar.gz → 2.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {zpdatafetch-2.2.6/src/zpdatafetch.egg-info → zpdatafetch-2.3.0}/PKG-INFO +2 -2
- {zpdatafetch-2.2.6 → zpdatafetch-2.3.0}/pyproject.toml +4 -6
- {zpdatafetch-2.2.6 → zpdatafetch-2.3.0}/src/shared/http_client.py +29 -29
- {zpdatafetch-2.2.6 → zpdatafetch-2.3.0}/src/zdatafetch/activity.py +5 -5
- {zpdatafetch-2.2.6 → zpdatafetch-2.3.0}/src/zdatafetch/auth.py +7 -7
- {zpdatafetch-2.2.6 → zpdatafetch-2.3.0}/src/zdatafetch/followers.py +5 -5
- {zpdatafetch-2.2.6 → zpdatafetch-2.3.0}/src/zdatafetch/profile.py +5 -5
- {zpdatafetch-2.2.6 → zpdatafetch-2.3.0}/src/zdatafetch/rideons.py +8 -8
- {zpdatafetch-2.2.6 → zpdatafetch-2.3.0}/src/zdatafetch/ridersinworld.py +5 -5
- {zpdatafetch-2.2.6 → zpdatafetch-2.3.0}/src/zdatafetch/worlds.py +4 -4
- {zpdatafetch-2.2.6 → zpdatafetch-2.3.0}/src/zpdatafetch/async_zp.py +22 -21
- {zpdatafetch-2.2.6 → zpdatafetch-2.3.0}/src/zpdatafetch/zp.py +17 -19
- {zpdatafetch-2.2.6 → zpdatafetch-2.3.0}/src/zpdatafetch/zpprimesfetch.py +3 -3
- {zpdatafetch-2.2.6 → zpdatafetch-2.3.0/src/zpdatafetch.egg-info}/PKG-INFO +2 -2
- {zpdatafetch-2.2.6 → zpdatafetch-2.3.0}/src/zpdatafetch.egg-info/requires.txt +1 -1
- {zpdatafetch-2.2.6 → zpdatafetch-2.3.0}/src/zrdatafetch/async_zr.py +16 -16
- {zpdatafetch-2.2.6 → zpdatafetch-2.3.0}/src/zrdatafetch/zr.py +8 -8
- {zpdatafetch-2.2.6 → zpdatafetch-2.3.0}/src/zsdatafetch/async_zs.py +11 -7
- {zpdatafetch-2.2.6 → zpdatafetch-2.3.0}/src/zsdatafetch/zs.py +13 -9
- {zpdatafetch-2.2.6 → zpdatafetch-2.3.0}/LICENSE +0 -0
- {zpdatafetch-2.2.6 → zpdatafetch-2.3.0}/README.md +0 -0
- {zpdatafetch-2.2.6 → zpdatafetch-2.3.0}/setup.cfg +0 -0
- {zpdatafetch-2.2.6 → zpdatafetch-2.3.0}/src/shared/__init__.py +0 -0
- {zpdatafetch-2.2.6 → zpdatafetch-2.3.0}/src/shared/cli.py +0 -0
- {zpdatafetch-2.2.6 → zpdatafetch-2.3.0}/src/shared/config.py +0 -0
- {zpdatafetch-2.2.6 → zpdatafetch-2.3.0}/src/shared/error_helpers.py +0 -0
- {zpdatafetch-2.2.6 → zpdatafetch-2.3.0}/src/shared/exceptions.py +0 -0
- {zpdatafetch-2.2.6 → zpdatafetch-2.3.0}/src/shared/json_helpers.py +0 -0
- {zpdatafetch-2.2.6 → zpdatafetch-2.3.0}/src/shared/logging.py +0 -0
- {zpdatafetch-2.2.6 → zpdatafetch-2.3.0}/src/shared/validation.py +0 -0
- {zpdatafetch-2.2.6 → zpdatafetch-2.3.0}/src/zdatafetch/__init__.py +0 -0
- {zpdatafetch-2.2.6 → zpdatafetch-2.3.0}/src/zdatafetch/cli.py +0 -0
- {zpdatafetch-2.2.6 → zpdatafetch-2.3.0}/src/zdatafetch/config.py +0 -0
- {zpdatafetch-2.2.6 → zpdatafetch-2.3.0}/src/zdatafetch/logging_config.py +0 -0
- {zpdatafetch-2.2.6 → zpdatafetch-2.3.0}/src/zpdatafetch/__init__.py +0 -0
- {zpdatafetch-2.2.6 → zpdatafetch-2.3.0}/src/zpdatafetch/cli.py +0 -0
- {zpdatafetch-2.2.6 → zpdatafetch-2.3.0}/src/zpdatafetch/config.py +0 -0
- {zpdatafetch-2.2.6 → zpdatafetch-2.3.0}/src/zpdatafetch/logging_config.py +0 -0
- {zpdatafetch-2.2.6 → zpdatafetch-2.3.0}/src/zpdatafetch/zp_obj.py +0 -0
- {zpdatafetch-2.2.6 → zpdatafetch-2.3.0}/src/zpdatafetch/zp_utils.py +0 -0
- {zpdatafetch-2.2.6 → zpdatafetch-2.3.0}/src/zpdatafetch/zpcyclist.py +0 -0
- {zpdatafetch-2.2.6 → zpdatafetch-2.3.0}/src/zpdatafetch/zpcyclistfetch.py +0 -0
- {zpdatafetch-2.2.6 → zpdatafetch-2.3.0}/src/zpdatafetch/zpleague.py +0 -0
- {zpdatafetch-2.2.6 → zpdatafetch-2.3.0}/src/zpdatafetch/zpleaguefetch.py +0 -0
- {zpdatafetch-2.2.6 → zpdatafetch-2.3.0}/src/zpdatafetch/zpprime.py +0 -0
- {zpdatafetch-2.2.6 → zpdatafetch-2.3.0}/src/zpdatafetch/zpracefinish.py +0 -0
- {zpdatafetch-2.2.6 → zpdatafetch-2.3.0}/src/zpdatafetch/zpracelog.py +0 -0
- {zpdatafetch-2.2.6 → zpdatafetch-2.3.0}/src/zpdatafetch/zpraceresult.py +0 -0
- {zpdatafetch-2.2.6 → zpdatafetch-2.3.0}/src/zpdatafetch/zpracesignup.py +0 -0
- {zpdatafetch-2.2.6 → zpdatafetch-2.3.0}/src/zpdatafetch/zpracesprint.py +0 -0
- {zpdatafetch-2.2.6 → zpdatafetch-2.3.0}/src/zpdatafetch/zpresultfetch.py +0 -0
- {zpdatafetch-2.2.6 → zpdatafetch-2.3.0}/src/zpdatafetch/zpsignupfetch.py +0 -0
- {zpdatafetch-2.2.6 → zpdatafetch-2.3.0}/src/zpdatafetch/zpsprintsfetch.py +0 -0
- {zpdatafetch-2.2.6 → zpdatafetch-2.3.0}/src/zpdatafetch/zpteam.py +0 -0
- {zpdatafetch-2.2.6 → zpdatafetch-2.3.0}/src/zpdatafetch/zpteamfetch.py +0 -0
- {zpdatafetch-2.2.6 → zpdatafetch-2.3.0}/src/zpdatafetch.egg-info/SOURCES.txt +0 -0
- {zpdatafetch-2.2.6 → zpdatafetch-2.3.0}/src/zpdatafetch.egg-info/dependency_links.txt +0 -0
- {zpdatafetch-2.2.6 → zpdatafetch-2.3.0}/src/zpdatafetch.egg-info/entry_points.txt +0 -0
- {zpdatafetch-2.2.6 → zpdatafetch-2.3.0}/src/zpdatafetch.egg-info/top_level.txt +0 -0
- {zpdatafetch-2.2.6 → zpdatafetch-2.3.0}/src/zrdatafetch/__init__.py +0 -0
- {zpdatafetch-2.2.6 → zpdatafetch-2.3.0}/src/zrdatafetch/cli.py +0 -0
- {zpdatafetch-2.2.6 → zpdatafetch-2.3.0}/src/zrdatafetch/config.py +0 -0
- {zpdatafetch-2.2.6 → zpdatafetch-2.3.0}/src/zrdatafetch/logging_config.py +0 -0
- {zpdatafetch-2.2.6 → zpdatafetch-2.3.0}/src/zrdatafetch/rate_limiter.py +0 -0
- {zpdatafetch-2.2.6 → zpdatafetch-2.3.0}/src/zrdatafetch/zr_utils.py +0 -0
- {zpdatafetch-2.2.6 → zpdatafetch-2.3.0}/src/zrdatafetch/zrraceresult.py +0 -0
- {zpdatafetch-2.2.6 → zpdatafetch-2.3.0}/src/zrdatafetch/zrresultfetch.py +0 -0
- {zpdatafetch-2.2.6 → zpdatafetch-2.3.0}/src/zrdatafetch/zrrider.py +0 -0
- {zpdatafetch-2.2.6 → zpdatafetch-2.3.0}/src/zrdatafetch/zrriderfetch.py +0 -0
- {zpdatafetch-2.2.6 → zpdatafetch-2.3.0}/src/zrdatafetch/zrteamfetch.py +0 -0
- {zpdatafetch-2.2.6 → zpdatafetch-2.3.0}/src/zrdatafetch/zrteamroster.py +0 -0
- {zpdatafetch-2.2.6 → zpdatafetch-2.3.0}/src/zsdatafetch/__init__.py +0 -0
- {zpdatafetch-2.2.6 → zpdatafetch-2.3.0}/src/zsdatafetch/cli.py +0 -0
- {zpdatafetch-2.2.6 → zpdatafetch-2.3.0}/src/zsdatafetch/logging_config.py +0 -0
- {zpdatafetch-2.2.6 → zpdatafetch-2.3.0}/src/zsdatafetch/models/__init__.py +0 -0
- {zpdatafetch-2.2.6 → zpdatafetch-2.3.0}/src/zsdatafetch/models/component.py +0 -0
- {zpdatafetch-2.2.6 → zpdatafetch-2.3.0}/src/zsdatafetch/models/incident.py +0 -0
- {zpdatafetch-2.2.6 → zpdatafetch-2.3.0}/src/zsdatafetch/models/maintenance.py +0 -0
- {zpdatafetch-2.2.6 → zpdatafetch-2.3.0}/src/zsdatafetch/models/page.py +0 -0
- {zpdatafetch-2.2.6 → zpdatafetch-2.3.0}/src/zsdatafetch/models/status.py +0 -0
- {zpdatafetch-2.2.6 → zpdatafetch-2.3.0}/src/zsdatafetch/models/summary.py +0 -0
- {zpdatafetch-2.2.6 → zpdatafetch-2.3.0}/src/zsdatafetch/zsincidentfetch.py +0 -0
- {zpdatafetch-2.2.6 → zpdatafetch-2.3.0}/src/zsdatafetch/zsmaintenancefetch.py +0 -0
- {zpdatafetch-2.2.6 → zpdatafetch-2.3.0}/src/zsdatafetch/zssummaryfetch.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: zpdatafetch
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.3.0
|
|
4
4
|
Summary: A package for fetching data from Zwiftpower and Zwiftracing.app
|
|
5
5
|
Author-email: Doug Morris <doug@mhost.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -14,7 +14,7 @@ License-File: LICENSE
|
|
|
14
14
|
Requires-Dist: anyio>=4.4.0
|
|
15
15
|
Requires-Dist: beautifulsoup4>=4.12.3
|
|
16
16
|
Requires-Dist: cryptography>=46.0.7
|
|
17
|
-
Requires-Dist:
|
|
17
|
+
Requires-Dist: httpx2>=2.9.1
|
|
18
18
|
Requires-Dist: idna>=3.16
|
|
19
19
|
Requires-Dist: keyring>=25.2.0
|
|
20
20
|
Requires-Dist: keyrings-alt
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "zpdatafetch"
|
|
3
|
-
version = "2.
|
|
3
|
+
version = "2.3.0"
|
|
4
4
|
description = "A package for fetching data from Zwiftpower and Zwiftracing.app"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.10"
|
|
@@ -20,11 +20,9 @@ dependencies = [
|
|
|
20
20
|
"anyio>=4.4.0",
|
|
21
21
|
"beautifulsoup4>=4.12.3",
|
|
22
22
|
"cryptography>=46.0.7",
|
|
23
|
-
#
|
|
24
|
-
#
|
|
25
|
-
|
|
26
|
-
# This will be resolved when httpx 0.29.0+ is released (depends on httpcore 1.2.0+).
|
|
27
|
-
"httpx>=0.28.0",
|
|
23
|
+
# httpx2: Pydantic's maintained continuation of httpx, built on httpcore2,
|
|
24
|
+
# which resolves the httpcore 1.1.x / Python 3.14 typing.Union issue.
|
|
25
|
+
"httpx2>=2.9.1",
|
|
28
26
|
"idna>=3.16",
|
|
29
27
|
"keyring>=25.2.0",
|
|
30
28
|
"keyrings-alt",
|
|
@@ -15,7 +15,7 @@ from abc import ABC, abstractmethod
|
|
|
15
15
|
from typing import Any
|
|
16
16
|
|
|
17
17
|
import anyio
|
|
18
|
-
import
|
|
18
|
+
import httpx2
|
|
19
19
|
|
|
20
20
|
from shared.exceptions import NetworkError
|
|
21
21
|
|
|
@@ -25,21 +25,21 @@ from shared.exceptions import NetworkError
|
|
|
25
25
|
|
|
26
26
|
|
|
27
27
|
def fetch_with_retry_sync(
|
|
28
|
-
client:
|
|
28
|
+
client: httpx2.Client,
|
|
29
29
|
url: str,
|
|
30
30
|
method: str = 'GET',
|
|
31
31
|
max_retries: int = 3,
|
|
32
32
|
backoff_factor: float = 1.0,
|
|
33
33
|
logger: logging.Logger | None = None,
|
|
34
34
|
**kwargs: Any,
|
|
35
|
-
) ->
|
|
35
|
+
) -> httpx2.Response:
|
|
36
36
|
"""Fetch URL with exponential backoff retry logic (sync variant).
|
|
37
37
|
|
|
38
38
|
Retries on transient errors (connection errors, timeouts, 5xx errors)
|
|
39
39
|
but not on client errors (4xx).
|
|
40
40
|
|
|
41
41
|
Args:
|
|
42
|
-
client:
|
|
42
|
+
client: httpx2.Client instance
|
|
43
43
|
url: URL to fetch
|
|
44
44
|
method: HTTP method (default: 'GET')
|
|
45
45
|
max_retries: Maximum number of retry attempts (default: 3)
|
|
@@ -48,7 +48,7 @@ def fetch_with_retry_sync(
|
|
|
48
48
|
**kwargs: Additional arguments to pass to client.request()
|
|
49
49
|
|
|
50
50
|
Returns:
|
|
51
|
-
|
|
51
|
+
httpx2.Response: The successful response
|
|
52
52
|
|
|
53
53
|
Raises:
|
|
54
54
|
NetworkError: If all retries are exhausted
|
|
@@ -64,7 +64,7 @@ def fetch_with_retry_sync(
|
|
|
64
64
|
response = client.request(method, url, **kwargs)
|
|
65
65
|
response.raise_for_status()
|
|
66
66
|
return response
|
|
67
|
-
except (
|
|
67
|
+
except (httpx2.ConnectError, httpx2.TimeoutException) as e:
|
|
68
68
|
last_exception = e
|
|
69
69
|
if attempt == max_retries - 1:
|
|
70
70
|
break
|
|
@@ -74,7 +74,7 @@ def fetch_with_retry_sync(
|
|
|
74
74
|
f'Retrying in {wait_time:.1f}s...',
|
|
75
75
|
)
|
|
76
76
|
time.sleep(wait_time)
|
|
77
|
-
except
|
|
77
|
+
except httpx2.HTTPStatusError as e:
|
|
78
78
|
if 500 <= e.response.status_code < 600:
|
|
79
79
|
last_exception = e
|
|
80
80
|
if attempt == max_retries - 1:
|
|
@@ -88,7 +88,7 @@ def fetch_with_retry_sync(
|
|
|
88
88
|
else:
|
|
89
89
|
# Client errors (4xx) - don't retry, re-raise for caller to handle
|
|
90
90
|
raise
|
|
91
|
-
except
|
|
91
|
+
except httpx2.RequestError as e:
|
|
92
92
|
last_exception = e
|
|
93
93
|
if attempt == max_retries - 1:
|
|
94
94
|
break
|
|
@@ -109,21 +109,21 @@ def fetch_with_retry_sync(
|
|
|
109
109
|
|
|
110
110
|
|
|
111
111
|
async def fetch_with_retry_async(
|
|
112
|
-
client:
|
|
112
|
+
client: httpx2.AsyncClient,
|
|
113
113
|
url: str,
|
|
114
114
|
method: str = 'GET',
|
|
115
115
|
max_retries: int = 3,
|
|
116
116
|
backoff_factor: float = 1.0,
|
|
117
117
|
logger: logging.Logger | None = None,
|
|
118
118
|
**kwargs: Any,
|
|
119
|
-
) ->
|
|
119
|
+
) -> httpx2.Response:
|
|
120
120
|
"""Fetch URL with exponential backoff retry logic (async variant).
|
|
121
121
|
|
|
122
122
|
Async version using anyio.sleep() for compatibility with both asyncio
|
|
123
123
|
and trio.
|
|
124
124
|
|
|
125
125
|
Args:
|
|
126
|
-
client:
|
|
126
|
+
client: httpx2.AsyncClient instance
|
|
127
127
|
url: URL to fetch
|
|
128
128
|
method: HTTP method (default: 'GET')
|
|
129
129
|
max_retries: Maximum number of retry attempts (default: 3)
|
|
@@ -132,7 +132,7 @@ async def fetch_with_retry_async(
|
|
|
132
132
|
**kwargs: Additional arguments to pass to client.request()
|
|
133
133
|
|
|
134
134
|
Returns:
|
|
135
|
-
|
|
135
|
+
httpx2.Response: The successful response
|
|
136
136
|
|
|
137
137
|
Raises:
|
|
138
138
|
NetworkError: If all retries are exhausted
|
|
@@ -148,7 +148,7 @@ async def fetch_with_retry_async(
|
|
|
148
148
|
response = await client.request(method, url, **kwargs)
|
|
149
149
|
response.raise_for_status()
|
|
150
150
|
return response
|
|
151
|
-
except (
|
|
151
|
+
except (httpx2.ConnectError, httpx2.TimeoutException) as e:
|
|
152
152
|
last_exception = e
|
|
153
153
|
if attempt == max_retries - 1:
|
|
154
154
|
break
|
|
@@ -158,7 +158,7 @@ async def fetch_with_retry_async(
|
|
|
158
158
|
f'Retrying in {wait_time:.1f}s...',
|
|
159
159
|
)
|
|
160
160
|
await anyio.sleep(wait_time)
|
|
161
|
-
except
|
|
161
|
+
except httpx2.HTTPStatusError as e:
|
|
162
162
|
if 500 <= e.response.status_code < 600:
|
|
163
163
|
last_exception = e
|
|
164
164
|
if attempt == max_retries - 1:
|
|
@@ -172,7 +172,7 @@ async def fetch_with_retry_async(
|
|
|
172
172
|
else:
|
|
173
173
|
# Client errors (4xx) - don't retry, re-raise for caller to handle
|
|
174
174
|
raise
|
|
175
|
-
except
|
|
175
|
+
except httpx2.RequestError as e:
|
|
176
176
|
last_exception = e
|
|
177
177
|
if attempt == max_retries - 1:
|
|
178
178
|
break
|
|
@@ -204,8 +204,8 @@ class BaseHTTPClient(ABC):
|
|
|
204
204
|
Subclasses implement hooks for package-specific behavior.
|
|
205
205
|
"""
|
|
206
206
|
|
|
207
|
-
_client:
|
|
208
|
-
_shared_client:
|
|
207
|
+
_client: httpx2.Client | None = None
|
|
208
|
+
_shared_client: httpx2.Client | None = None
|
|
209
209
|
_owns_client: bool = False
|
|
210
210
|
|
|
211
211
|
# ----------------------------------------------------------------------------
|
|
@@ -213,13 +213,13 @@ class BaseHTTPClient(ABC):
|
|
|
213
213
|
# ----------------------------------------------------------------------------
|
|
214
214
|
|
|
215
215
|
@abstractmethod
|
|
216
|
-
def _create_client(self) ->
|
|
216
|
+
def _create_client(self) -> httpx2.Client:
|
|
217
217
|
"""Create and configure an HTTP client.
|
|
218
218
|
|
|
219
219
|
Subclasses implement this to provide package-specific client configuration.
|
|
220
220
|
|
|
221
221
|
Returns:
|
|
222
|
-
Configured
|
|
222
|
+
Configured httpx2.Client instance
|
|
223
223
|
"""
|
|
224
224
|
|
|
225
225
|
def _before_request(
|
|
@@ -241,7 +241,7 @@ class BaseHTTPClient(ABC):
|
|
|
241
241
|
**kwargs: Additional request parameters
|
|
242
242
|
"""
|
|
243
243
|
|
|
244
|
-
def _after_request(self, response:
|
|
244
|
+
def _after_request(self, response: httpx2.Response) -> None:
|
|
245
245
|
"""Hook called after receiving a response.
|
|
246
246
|
|
|
247
247
|
Default: no-op. Override in subclasses for post-response operations like:
|
|
@@ -266,7 +266,7 @@ class BaseHTTPClient(ABC):
|
|
|
266
266
|
# SHARED IMPLEMENTATIONS
|
|
267
267
|
# ----------------------------------------------------------------------------
|
|
268
268
|
|
|
269
|
-
def init_client(self, client:
|
|
269
|
+
def init_client(self, client: httpx2.Client | None = None) -> None:
|
|
270
270
|
"""Initialize or replace the HTTP client.
|
|
271
271
|
|
|
272
272
|
Three-way logic:
|
|
@@ -275,7 +275,7 @@ class BaseHTTPClient(ABC):
|
|
|
275
275
|
3. Else create new client via _create_client()
|
|
276
276
|
|
|
277
277
|
Args:
|
|
278
|
-
client: Optional
|
|
278
|
+
client: Optional httpx2.Client instance to use
|
|
279
279
|
"""
|
|
280
280
|
if client:
|
|
281
281
|
self._client = client
|
|
@@ -327,8 +327,8 @@ class AsyncBaseHTTPClient(ABC):
|
|
|
327
327
|
Async version of BaseHTTPClient with same pattern but async methods.
|
|
328
328
|
"""
|
|
329
329
|
|
|
330
|
-
_client:
|
|
331
|
-
_shared_client:
|
|
330
|
+
_client: httpx2.AsyncClient | None = None
|
|
331
|
+
_shared_client: httpx2.AsyncClient | None = None
|
|
332
332
|
_owns_client: bool = False
|
|
333
333
|
|
|
334
334
|
# ----------------------------------------------------------------------------
|
|
@@ -336,11 +336,11 @@ class AsyncBaseHTTPClient(ABC):
|
|
|
336
336
|
# ----------------------------------------------------------------------------
|
|
337
337
|
|
|
338
338
|
@abstractmethod
|
|
339
|
-
async def _create_client(self) ->
|
|
339
|
+
async def _create_client(self) -> httpx2.AsyncClient:
|
|
340
340
|
"""Create and configure an async HTTP client.
|
|
341
341
|
|
|
342
342
|
Args:
|
|
343
|
-
Returns: Configured
|
|
343
|
+
Returns: Configured httpx2.AsyncClient instance
|
|
344
344
|
"""
|
|
345
345
|
|
|
346
346
|
async def _before_request(
|
|
@@ -354,7 +354,7 @@ class AsyncBaseHTTPClient(ABC):
|
|
|
354
354
|
Default: no-op. Override for pre-request operations.
|
|
355
355
|
"""
|
|
356
356
|
|
|
357
|
-
async def _after_request(self, response:
|
|
357
|
+
async def _after_request(self, response: httpx2.Response) -> None:
|
|
358
358
|
"""Hook called after receiving a response.
|
|
359
359
|
|
|
360
360
|
Default: no-op. Override for post-response operations.
|
|
@@ -370,7 +370,7 @@ class AsyncBaseHTTPClient(ABC):
|
|
|
370
370
|
# SHARED IMPLEMENTATIONS
|
|
371
371
|
# ----------------------------------------------------------------------------
|
|
372
372
|
|
|
373
|
-
async def init_client(self, client:
|
|
373
|
+
async def init_client(self, client: httpx2.AsyncClient | None = None) -> None:
|
|
374
374
|
"""Initialize or replace the async HTTP client.
|
|
375
375
|
|
|
376
376
|
Three-way logic:
|
|
@@ -379,7 +379,7 @@ class AsyncBaseHTTPClient(ABC):
|
|
|
379
379
|
3. Else create new client via _create_client()
|
|
380
380
|
|
|
381
381
|
Args:
|
|
382
|
-
client: Optional
|
|
382
|
+
client: Optional httpx2.AsyncClient instance to use
|
|
383
383
|
"""
|
|
384
384
|
if client:
|
|
385
385
|
self._client = client
|
|
@@ -6,7 +6,7 @@ Provides access to activity history from Zwift's unofficial API.
|
|
|
6
6
|
import json
|
|
7
7
|
from typing import Any
|
|
8
8
|
|
|
9
|
-
import
|
|
9
|
+
import httpx2
|
|
10
10
|
|
|
11
11
|
from shared.exceptions import ConfigError, NetworkError
|
|
12
12
|
from shared.json_helpers import parse_json_safe
|
|
@@ -110,7 +110,7 @@ class ZwiftActivity:
|
|
|
110
110
|
params = {'start': start, 'limit': limit}
|
|
111
111
|
|
|
112
112
|
try:
|
|
113
|
-
with
|
|
113
|
+
with httpx2.Client() as client:
|
|
114
114
|
response = client.get(url, headers=headers, params=params, timeout=30.0)
|
|
115
115
|
|
|
116
116
|
if response.status_code == 404:
|
|
@@ -130,11 +130,11 @@ class ZwiftActivity:
|
|
|
130
130
|
f'Successfully fetched {len(self.activities)} activities for rider {rider_id}',
|
|
131
131
|
)
|
|
132
132
|
|
|
133
|
-
except
|
|
133
|
+
except httpx2.TimeoutException as e:
|
|
134
134
|
raise NetworkError(
|
|
135
135
|
f'Request timed out fetching activities for rider {rider_id}: {e}',
|
|
136
136
|
) from e
|
|
137
|
-
except
|
|
137
|
+
except httpx2.HTTPError as e:
|
|
138
138
|
raise NetworkError(
|
|
139
139
|
f'Network error fetching activities for rider {rider_id}: {e}',
|
|
140
140
|
) from e
|
|
@@ -188,7 +188,7 @@ class ZwiftActivity:
|
|
|
188
188
|
|
|
189
189
|
# Fetch all activities
|
|
190
190
|
results = {}
|
|
191
|
-
with
|
|
191
|
+
with httpx2.Client() as client:
|
|
192
192
|
for rider_id in rider_ids:
|
|
193
193
|
try:
|
|
194
194
|
url = f'{cls.BASE_URL}/api/profiles/{rider_id}/activities/'
|
|
@@ -7,7 +7,7 @@ token acquisition, storage, and automatic refresh.
|
|
|
7
7
|
import time
|
|
8
8
|
from typing import Any
|
|
9
9
|
|
|
10
|
-
import
|
|
10
|
+
import httpx2
|
|
11
11
|
|
|
12
12
|
from shared.exceptions import AuthenticationError, NetworkError
|
|
13
13
|
from zdatafetch.logging_config import get_logger
|
|
@@ -75,7 +75,7 @@ class ZwiftAuth:
|
|
|
75
75
|
}
|
|
76
76
|
|
|
77
77
|
try:
|
|
78
|
-
with
|
|
78
|
+
with httpx2.Client() as client:
|
|
79
79
|
response = client.post(self.AUTH_URL, data=data, timeout=30.0)
|
|
80
80
|
|
|
81
81
|
if response.status_code == 401:
|
|
@@ -91,9 +91,9 @@ class ZwiftAuth:
|
|
|
91
91
|
f'Authentication successful (token expires in {self.expires_in}s)',
|
|
92
92
|
)
|
|
93
93
|
|
|
94
|
-
except
|
|
94
|
+
except httpx2.TimeoutException as e:
|
|
95
95
|
raise NetworkError(f'Authentication request timed out: {e}') from e
|
|
96
|
-
except
|
|
96
|
+
except httpx2.HTTPError as e:
|
|
97
97
|
raise NetworkError(f'Authentication request failed: {e}') from e
|
|
98
98
|
|
|
99
99
|
def _parse_token_response(self, token_data: dict[str, Any]) -> None:
|
|
@@ -162,7 +162,7 @@ class ZwiftAuth:
|
|
|
162
162
|
}
|
|
163
163
|
|
|
164
164
|
try:
|
|
165
|
-
with
|
|
165
|
+
with httpx2.Client() as client:
|
|
166
166
|
response = client.post(self.AUTH_URL, data=data, timeout=30.0)
|
|
167
167
|
|
|
168
168
|
if response.status_code == 401:
|
|
@@ -178,9 +178,9 @@ class ZwiftAuth:
|
|
|
178
178
|
|
|
179
179
|
logger.debug('Token refreshed successfully')
|
|
180
180
|
|
|
181
|
-
except
|
|
181
|
+
except httpx2.TimeoutException as e:
|
|
182
182
|
raise NetworkError(f'Token refresh request timed out: {e}') from e
|
|
183
|
-
except
|
|
183
|
+
except httpx2.HTTPError as e:
|
|
184
184
|
raise NetworkError(f'Token refresh request failed: {e}') from e
|
|
185
185
|
|
|
186
186
|
def is_authenticated(self) -> bool:
|
|
@@ -6,7 +6,7 @@ Provides access to follower/followee relationship data from Zwift's unofficial A
|
|
|
6
6
|
import json
|
|
7
7
|
from typing import Any
|
|
8
8
|
|
|
9
|
-
import
|
|
9
|
+
import httpx2
|
|
10
10
|
|
|
11
11
|
from shared.exceptions import ConfigError, NetworkError
|
|
12
12
|
from shared.json_helpers import parse_json_safe
|
|
@@ -112,7 +112,7 @@ class ZwiftFollowers:
|
|
|
112
112
|
raw_data = {}
|
|
113
113
|
|
|
114
114
|
try:
|
|
115
|
-
with
|
|
115
|
+
with httpx2.Client() as client:
|
|
116
116
|
# Fetch followers
|
|
117
117
|
if include_followers:
|
|
118
118
|
url = f'{self.BASE_URL}/api/profiles/{rider_id}/followers'
|
|
@@ -154,11 +154,11 @@ class ZwiftFollowers:
|
|
|
154
154
|
self._raw = json.dumps(self._fetched, indent=2)
|
|
155
155
|
logger.info(f'Successfully fetched follower data for rider {rider_id}')
|
|
156
156
|
|
|
157
|
-
except
|
|
157
|
+
except httpx2.TimeoutException as e:
|
|
158
158
|
raise NetworkError(
|
|
159
159
|
f'Request timed out fetching follower data for rider {rider_id}: {e}',
|
|
160
160
|
) from e
|
|
161
|
-
except
|
|
161
|
+
except httpx2.HTTPError as e:
|
|
162
162
|
raise NetworkError(
|
|
163
163
|
f'Network error fetching follower data for rider {rider_id}: {e}',
|
|
164
164
|
) from e
|
|
@@ -218,7 +218,7 @@ class ZwiftFollowers:
|
|
|
218
218
|
|
|
219
219
|
# Fetch all follower data
|
|
220
220
|
results = {}
|
|
221
|
-
with
|
|
221
|
+
with httpx2.Client() as client:
|
|
222
222
|
for rider_id in rider_ids:
|
|
223
223
|
try:
|
|
224
224
|
raw_data = {}
|
|
@@ -7,7 +7,7 @@ including demographics, statistics, and connected services.
|
|
|
7
7
|
import json
|
|
8
8
|
from typing import Any
|
|
9
9
|
|
|
10
|
-
import
|
|
10
|
+
import httpx2
|
|
11
11
|
|
|
12
12
|
from shared.exceptions import ConfigError, NetworkError
|
|
13
13
|
from shared.json_helpers import parse_json_safe
|
|
@@ -199,7 +199,7 @@ class ZwiftProfile:
|
|
|
199
199
|
headers = {'Authorization': f'Bearer {token}', 'Accept': 'application/json'}
|
|
200
200
|
|
|
201
201
|
try:
|
|
202
|
-
with
|
|
202
|
+
with httpx2.Client() as client:
|
|
203
203
|
response = client.get(url, headers=headers, timeout=30.0)
|
|
204
204
|
|
|
205
205
|
if response.status_code == 404:
|
|
@@ -217,11 +217,11 @@ class ZwiftProfile:
|
|
|
217
217
|
self._parse_response()
|
|
218
218
|
logger.info(f'Successfully fetched profile for rider {rider_id}')
|
|
219
219
|
|
|
220
|
-
except
|
|
220
|
+
except httpx2.TimeoutException as e:
|
|
221
221
|
raise NetworkError(
|
|
222
222
|
f'Request timed out fetching profile for rider {rider_id}: {e}',
|
|
223
223
|
) from e
|
|
224
|
-
except
|
|
224
|
+
except httpx2.HTTPError as e:
|
|
225
225
|
raise NetworkError(
|
|
226
226
|
f'Network error fetching profile for rider {rider_id}: {e}',
|
|
227
227
|
) from e
|
|
@@ -268,7 +268,7 @@ class ZwiftProfile:
|
|
|
268
268
|
|
|
269
269
|
# Fetch all profiles
|
|
270
270
|
results = {}
|
|
271
|
-
with
|
|
271
|
+
with httpx2.Client() as client:
|
|
272
272
|
for rider_id in rider_ids:
|
|
273
273
|
try:
|
|
274
274
|
url = f'{cls.BASE_URL}/api/profiles/{rider_id}'
|
|
@@ -7,7 +7,7 @@ fetching RideOns received on activities and giving RideOns.
|
|
|
7
7
|
import json
|
|
8
8
|
from typing import Any
|
|
9
9
|
|
|
10
|
-
import
|
|
10
|
+
import httpx2
|
|
11
11
|
|
|
12
12
|
from shared.exceptions import ConfigError, NetworkError
|
|
13
13
|
from shared.json_helpers import parse_json_safe
|
|
@@ -109,7 +109,7 @@ class ZwiftRideOns:
|
|
|
109
109
|
url = f'{self.BASE_URL}/api/profiles/{rider_id}/activities/{activity_id}/rideons'
|
|
110
110
|
|
|
111
111
|
try:
|
|
112
|
-
with
|
|
112
|
+
with httpx2.Client() as client:
|
|
113
113
|
response = client.get(url, headers=headers, timeout=30.0)
|
|
114
114
|
|
|
115
115
|
if response.status_code == 404:
|
|
@@ -131,11 +131,11 @@ class ZwiftRideOns:
|
|
|
131
131
|
f'Successfully fetched RideOns for activity {activity_id} (rider {rider_id})',
|
|
132
132
|
)
|
|
133
133
|
|
|
134
|
-
except
|
|
134
|
+
except httpx2.TimeoutException as e:
|
|
135
135
|
raise NetworkError(
|
|
136
136
|
f'Request timed out fetching RideOns for activity {activity_id}: {e}',
|
|
137
137
|
) from e
|
|
138
|
-
except
|
|
138
|
+
except httpx2.HTTPError as e:
|
|
139
139
|
raise NetworkError(
|
|
140
140
|
f'Network error fetching RideOns for activity {activity_id}: {e}',
|
|
141
141
|
) from e
|
|
@@ -187,7 +187,7 @@ class ZwiftRideOns:
|
|
|
187
187
|
|
|
188
188
|
# Fetch all RideOns
|
|
189
189
|
results = {}
|
|
190
|
-
with
|
|
190
|
+
with httpx2.Client() as client:
|
|
191
191
|
for rider_id, activity_id in activity_tuples:
|
|
192
192
|
try:
|
|
193
193
|
url = f'{cls.BASE_URL}/api/profiles/{rider_id}/activities/{activity_id}/rideons'
|
|
@@ -261,7 +261,7 @@ class ZwiftRideOns:
|
|
|
261
261
|
url = f'{ZwiftRideOns.BASE_URL}/api/profiles/{rider_id}/activities/{activity_id}/rideon'
|
|
262
262
|
|
|
263
263
|
try:
|
|
264
|
-
with
|
|
264
|
+
with httpx2.Client() as client:
|
|
265
265
|
response = client.post(url, headers=headers, timeout=30.0)
|
|
266
266
|
|
|
267
267
|
if response.status_code in (200, 201, 204):
|
|
@@ -280,12 +280,12 @@ class ZwiftRideOns:
|
|
|
280
280
|
)
|
|
281
281
|
return False
|
|
282
282
|
|
|
283
|
-
except
|
|
283
|
+
except httpx2.TimeoutException as e:
|
|
284
284
|
logger.error(
|
|
285
285
|
f'Request timed out giving RideOn to activity {activity_id}: {e}',
|
|
286
286
|
)
|
|
287
287
|
return False
|
|
288
|
-
except
|
|
288
|
+
except httpx2.HTTPError as e:
|
|
289
289
|
logger.error(
|
|
290
290
|
f'Network error giving RideOn to activity {activity_id}: {e}',
|
|
291
291
|
)
|
|
@@ -6,7 +6,7 @@ Provides access to riders currently in a specific world from Zwift's unofficial
|
|
|
6
6
|
import json
|
|
7
7
|
from typing import Any
|
|
8
8
|
|
|
9
|
-
import
|
|
9
|
+
import httpx2
|
|
10
10
|
|
|
11
11
|
from shared.exceptions import ConfigError, NetworkError
|
|
12
12
|
from shared.json_helpers import parse_json_safe
|
|
@@ -99,7 +99,7 @@ class ZwiftRidersInWorld:
|
|
|
99
99
|
url = f'{self.BASE_URL}/relay/worlds/{world_id}'
|
|
100
100
|
|
|
101
101
|
try:
|
|
102
|
-
with
|
|
102
|
+
with httpx2.Client() as client:
|
|
103
103
|
response = client.get(url, headers=headers, timeout=30.0)
|
|
104
104
|
|
|
105
105
|
if response.status_code == 404:
|
|
@@ -119,11 +119,11 @@ class ZwiftRidersInWorld:
|
|
|
119
119
|
f'Successfully fetched {len(self.riders)} riders in world {world_id}',
|
|
120
120
|
)
|
|
121
121
|
|
|
122
|
-
except
|
|
122
|
+
except httpx2.TimeoutException as e:
|
|
123
123
|
raise NetworkError(
|
|
124
124
|
f'Request timed out fetching riders for world {world_id}: {e}',
|
|
125
125
|
) from e
|
|
126
|
-
except
|
|
126
|
+
except httpx2.HTTPError as e:
|
|
127
127
|
raise NetworkError(
|
|
128
128
|
f'Network error fetching riders for world {world_id}: {e}',
|
|
129
129
|
) from e
|
|
@@ -195,7 +195,7 @@ class ZwiftRidersInWorld:
|
|
|
195
195
|
|
|
196
196
|
# Fetch all riders
|
|
197
197
|
results = {}
|
|
198
|
-
with
|
|
198
|
+
with httpx2.Client() as client:
|
|
199
199
|
for world_id in world_ids:
|
|
200
200
|
try:
|
|
201
201
|
url = f'{cls.BASE_URL}/relay/worlds/{world_id}'
|
|
@@ -6,7 +6,7 @@ Provides access to active worlds from Zwift's unofficial API.
|
|
|
6
6
|
import json
|
|
7
7
|
from typing import Any
|
|
8
8
|
|
|
9
|
-
import
|
|
9
|
+
import httpx2
|
|
10
10
|
|
|
11
11
|
from shared.exceptions import ConfigError, NetworkError
|
|
12
12
|
from shared.json_helpers import parse_json_safe
|
|
@@ -129,7 +129,7 @@ class ZwiftWorlds:
|
|
|
129
129
|
url = f'{self.BASE_URL}/relay/worlds'
|
|
130
130
|
|
|
131
131
|
try:
|
|
132
|
-
with
|
|
132
|
+
with httpx2.Client() as client:
|
|
133
133
|
response = client.get(url, headers=headers, timeout=30.0)
|
|
134
134
|
|
|
135
135
|
if response.status_code != 200:
|
|
@@ -144,9 +144,9 @@ class ZwiftWorlds:
|
|
|
144
144
|
self._raw = json.dumps(self._fetched, indent=2)
|
|
145
145
|
logger.info(f'Successfully fetched {len(self.worlds)} active worlds')
|
|
146
146
|
|
|
147
|
-
except
|
|
147
|
+
except httpx2.TimeoutException as e:
|
|
148
148
|
raise NetworkError(f'Request timed out fetching worlds: {e}') from e
|
|
149
|
-
except
|
|
149
|
+
except httpx2.HTTPError as e:
|
|
150
150
|
raise NetworkError(f'Network error fetching worlds: {e}') from e
|
|
151
151
|
|
|
152
152
|
def _parse_response(self, raw_json: str) -> None:
|