zpdatafetch 2.1.1__tar.gz → 2.2.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.1.1/src/zpdatafetch.egg-info → zpdatafetch-2.2.0}/PKG-INFO +166 -15
- {zpdatafetch-2.1.1 → zpdatafetch-2.2.0}/README.md +165 -14
- {zpdatafetch-2.1.1 → zpdatafetch-2.2.0}/pyproject.toml +4 -5
- {zpdatafetch-2.1.1 → zpdatafetch-2.2.0}/src/shared/cli.py +4 -18
- {zpdatafetch-2.1.1 → zpdatafetch-2.2.0}/src/shared/validation.py +40 -0
- {zpdatafetch-2.1.1 → zpdatafetch-2.2.0}/src/zpdatafetch/cli.py +23 -5
- {zpdatafetch-2.1.1 → zpdatafetch-2.2.0}/src/zpdatafetch/zp.py +0 -1
- {zpdatafetch-2.1.1 → zpdatafetch-2.2.0}/src/zpdatafetch/zpcyclistfetch.py +1 -1
- {zpdatafetch-2.1.1 → zpdatafetch-2.2.0}/src/zpdatafetch/zpprimesfetch.py +3 -4
- {zpdatafetch-2.1.1 → zpdatafetch-2.2.0}/src/zpdatafetch/zpraceresult.py +10 -5
- {zpdatafetch-2.1.1 → zpdatafetch-2.2.0/src/zpdatafetch.egg-info}/PKG-INFO +166 -15
- {zpdatafetch-2.1.1 → zpdatafetch-2.2.0}/src/zpdatafetch.egg-info/SOURCES.txt +16 -1
- {zpdatafetch-2.1.1 → zpdatafetch-2.2.0}/src/zpdatafetch.egg-info/entry_points.txt +1 -0
- {zpdatafetch-2.1.1 → zpdatafetch-2.2.0}/src/zpdatafetch.egg-info/top_level.txt +1 -0
- {zpdatafetch-2.1.1 → zpdatafetch-2.2.0}/src/zrdatafetch/cli.py +51 -10
- {zpdatafetch-2.1.1 → zpdatafetch-2.2.0}/src/zrdatafetch/zrraceresult.py +12 -5
- {zpdatafetch-2.1.1 → zpdatafetch-2.2.0}/src/zrdatafetch/zrteamroster.py +12 -5
- zpdatafetch-2.2.0/src/zsdatafetch/__init__.py +73 -0
- zpdatafetch-2.2.0/src/zsdatafetch/async_zs.py +110 -0
- zpdatafetch-2.2.0/src/zsdatafetch/cli.py +323 -0
- zpdatafetch-2.2.0/src/zsdatafetch/logging_config.py +54 -0
- zpdatafetch-2.2.0/src/zsdatafetch/models/__init__.py +22 -0
- zpdatafetch-2.2.0/src/zsdatafetch/models/component.py +113 -0
- zpdatafetch-2.2.0/src/zsdatafetch/models/incident.py +208 -0
- zpdatafetch-2.2.0/src/zsdatafetch/models/maintenance.py +219 -0
- zpdatafetch-2.2.0/src/zsdatafetch/models/page.py +72 -0
- zpdatafetch-2.2.0/src/zsdatafetch/models/status.py +61 -0
- zpdatafetch-2.2.0/src/zsdatafetch/models/summary.py +109 -0
- zpdatafetch-2.2.0/src/zsdatafetch/zs.py +94 -0
- zpdatafetch-2.2.0/src/zsdatafetch/zsincidentfetch.py +196 -0
- zpdatafetch-2.2.0/src/zsdatafetch/zsmaintenancefetch.py +231 -0
- zpdatafetch-2.2.0/src/zsdatafetch/zssummaryfetch.py +208 -0
- {zpdatafetch-2.1.1 → zpdatafetch-2.2.0}/LICENSE +0 -0
- {zpdatafetch-2.1.1 → zpdatafetch-2.2.0}/setup.cfg +0 -0
- {zpdatafetch-2.1.1 → zpdatafetch-2.2.0}/src/shared/__init__.py +0 -0
- {zpdatafetch-2.1.1 → zpdatafetch-2.2.0}/src/shared/config.py +0 -0
- {zpdatafetch-2.1.1 → zpdatafetch-2.2.0}/src/shared/error_helpers.py +0 -0
- {zpdatafetch-2.1.1 → zpdatafetch-2.2.0}/src/shared/exceptions.py +0 -0
- {zpdatafetch-2.1.1 → zpdatafetch-2.2.0}/src/shared/http_client.py +0 -0
- {zpdatafetch-2.1.1 → zpdatafetch-2.2.0}/src/shared/json_helpers.py +0 -0
- {zpdatafetch-2.1.1 → zpdatafetch-2.2.0}/src/shared/logging.py +0 -0
- {zpdatafetch-2.1.1 → zpdatafetch-2.2.0}/src/zdatafetch/__init__.py +0 -0
- {zpdatafetch-2.1.1 → zpdatafetch-2.2.0}/src/zdatafetch/activity.py +0 -0
- {zpdatafetch-2.1.1 → zpdatafetch-2.2.0}/src/zdatafetch/auth.py +0 -0
- {zpdatafetch-2.1.1 → zpdatafetch-2.2.0}/src/zdatafetch/cli.py +0 -0
- {zpdatafetch-2.1.1 → zpdatafetch-2.2.0}/src/zdatafetch/config.py +0 -0
- {zpdatafetch-2.1.1 → zpdatafetch-2.2.0}/src/zdatafetch/followers.py +0 -0
- {zpdatafetch-2.1.1 → zpdatafetch-2.2.0}/src/zdatafetch/logging_config.py +0 -0
- {zpdatafetch-2.1.1 → zpdatafetch-2.2.0}/src/zdatafetch/profile.py +0 -0
- {zpdatafetch-2.1.1 → zpdatafetch-2.2.0}/src/zdatafetch/rideons.py +0 -0
- {zpdatafetch-2.1.1 → zpdatafetch-2.2.0}/src/zdatafetch/ridersinworld.py +0 -0
- {zpdatafetch-2.1.1 → zpdatafetch-2.2.0}/src/zdatafetch/worlds.py +0 -0
- {zpdatafetch-2.1.1 → zpdatafetch-2.2.0}/src/zpdatafetch/__init__.py +0 -0
- {zpdatafetch-2.1.1 → zpdatafetch-2.2.0}/src/zpdatafetch/async_zp.py +0 -0
- {zpdatafetch-2.1.1 → zpdatafetch-2.2.0}/src/zpdatafetch/config.py +0 -0
- {zpdatafetch-2.1.1 → zpdatafetch-2.2.0}/src/zpdatafetch/logging_config.py +0 -0
- {zpdatafetch-2.1.1 → zpdatafetch-2.2.0}/src/zpdatafetch/zp_obj.py +0 -0
- {zpdatafetch-2.1.1 → zpdatafetch-2.2.0}/src/zpdatafetch/zp_utils.py +0 -0
- {zpdatafetch-2.1.1 → zpdatafetch-2.2.0}/src/zpdatafetch/zpcyclist.py +0 -0
- {zpdatafetch-2.1.1 → zpdatafetch-2.2.0}/src/zpdatafetch/zpleague.py +0 -0
- {zpdatafetch-2.1.1 → zpdatafetch-2.2.0}/src/zpdatafetch/zpleaguefetch.py +0 -0
- {zpdatafetch-2.1.1 → zpdatafetch-2.2.0}/src/zpdatafetch/zpprime.py +0 -0
- {zpdatafetch-2.1.1 → zpdatafetch-2.2.0}/src/zpdatafetch/zpracefinish.py +0 -0
- {zpdatafetch-2.1.1 → zpdatafetch-2.2.0}/src/zpdatafetch/zpracelog.py +0 -0
- {zpdatafetch-2.1.1 → zpdatafetch-2.2.0}/src/zpdatafetch/zpracesignup.py +0 -0
- {zpdatafetch-2.1.1 → zpdatafetch-2.2.0}/src/zpdatafetch/zpracesprint.py +0 -0
- {zpdatafetch-2.1.1 → zpdatafetch-2.2.0}/src/zpdatafetch/zpresultfetch.py +0 -0
- {zpdatafetch-2.1.1 → zpdatafetch-2.2.0}/src/zpdatafetch/zpsignupfetch.py +0 -0
- {zpdatafetch-2.1.1 → zpdatafetch-2.2.0}/src/zpdatafetch/zpsprintsfetch.py +0 -0
- {zpdatafetch-2.1.1 → zpdatafetch-2.2.0}/src/zpdatafetch/zpteam.py +0 -0
- {zpdatafetch-2.1.1 → zpdatafetch-2.2.0}/src/zpdatafetch/zpteamfetch.py +0 -0
- {zpdatafetch-2.1.1 → zpdatafetch-2.2.0}/src/zpdatafetch.egg-info/dependency_links.txt +0 -0
- {zpdatafetch-2.1.1 → zpdatafetch-2.2.0}/src/zpdatafetch.egg-info/requires.txt +0 -0
- {zpdatafetch-2.1.1 → zpdatafetch-2.2.0}/src/zrdatafetch/__init__.py +0 -0
- {zpdatafetch-2.1.1 → zpdatafetch-2.2.0}/src/zrdatafetch/async_zr.py +0 -0
- {zpdatafetch-2.1.1 → zpdatafetch-2.2.0}/src/zrdatafetch/config.py +0 -0
- {zpdatafetch-2.1.1 → zpdatafetch-2.2.0}/src/zrdatafetch/logging_config.py +0 -0
- {zpdatafetch-2.1.1 → zpdatafetch-2.2.0}/src/zrdatafetch/rate_limiter.py +0 -0
- {zpdatafetch-2.1.1 → zpdatafetch-2.2.0}/src/zrdatafetch/zr.py +0 -0
- {zpdatafetch-2.1.1 → zpdatafetch-2.2.0}/src/zrdatafetch/zr_utils.py +0 -0
- {zpdatafetch-2.1.1 → zpdatafetch-2.2.0}/src/zrdatafetch/zrresultfetch.py +0 -0
- {zpdatafetch-2.1.1 → zpdatafetch-2.2.0}/src/zrdatafetch/zrrider.py +0 -0
- {zpdatafetch-2.1.1 → zpdatafetch-2.2.0}/src/zrdatafetch/zrriderfetch.py +0 -0
- {zpdatafetch-2.1.1 → zpdatafetch-2.2.0}/src/zrdatafetch/zrteamfetch.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: zpdatafetch
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.2.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
|
|
@@ -24,9 +24,9 @@ Provides-Extra: trio
|
|
|
24
24
|
Requires-Dist: trio>=0.26.0; extra == "trio"
|
|
25
25
|
Dynamic: license-file
|
|
26
26
|
|
|
27
|
-
# zpdatafetch &
|
|
27
|
+
# zpdatafetch, zrdatafetch & zsdatafetch
|
|
28
28
|
|
|
29
|
-
A python library and command-line tool for fetching data from ZwiftPower.com
|
|
29
|
+
A python library and command-line tool for fetching data from ZwiftPower.com, Zwiftracing.app, and Zwift Status APIs.
|
|
30
30
|
|
|
31
31
|
## Installation
|
|
32
32
|
|
|
@@ -50,16 +50,19 @@ real free-threaded environment. Please do
|
|
|
50
50
|
|
|
51
51
|
## Overview
|
|
52
52
|
|
|
53
|
-
This package provides
|
|
53
|
+
This package provides four command-line tools:
|
|
54
54
|
|
|
55
|
-
| Tool | API
|
|
56
|
-
| ------------ |
|
|
57
|
-
| **`zpdata`** | ZwiftPower
|
|
58
|
-
| **`zrdata`** | Zwiftracing
|
|
55
|
+
| Tool | API | Purpose | Data Types |
|
|
56
|
+
| ------------ | ------------ | ------------------------------------------ | --------------------------------------------------------- |
|
|
57
|
+
| **`zpdata`** | ZwiftPower | Race rankings, signups, results | Cyclist, Primes, Results, Signups, Sprints, Teams, League |
|
|
58
|
+
| **`zrdata`** | Zwiftracing | Rider ratings, race results, rosters | Rider Ratings, Race Results, Team Rosters |
|
|
59
|
+
| **`zdata`** | Zwift | Profiles, followers, activities, worlds | Profile, Followers, RideOns, Activity, Worlds, Riders |
|
|
60
|
+
| **`zsdata`** | Zwift Status | Service status, incidents, maintenance | Summary, Components, Incidents, Maintenance |
|
|
59
61
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
62
|
+
All tools support flexible logging and can be used as standalone CLI tools or
|
|
63
|
+
imported as Python libraries. `zpdata`, `zrdata`, and `zdata` require
|
|
64
|
+
credentials; `zsdata` uses the public Zwift Status API (no authentication
|
|
65
|
+
required).
|
|
63
66
|
|
|
64
67
|
## Key Features
|
|
65
68
|
|
|
@@ -80,6 +83,22 @@ stores for each API.
|
|
|
80
83
|
- **Race results** - Complete race result data with rating changes
|
|
81
84
|
- **Team rosters** - Full team member details and power metrics
|
|
82
85
|
|
|
86
|
+
### For zdata (Zwift)
|
|
87
|
+
|
|
88
|
+
- **Rider profiles** - Fetch rider profile data by Zwift ID
|
|
89
|
+
- **Followers** - Follower and followee lists with filtering
|
|
90
|
+
- **RideOns** - Fetch RideOn data or give RideOns to activities
|
|
91
|
+
- **Activity history** - Fetch activity data with pagination
|
|
92
|
+
- **Worlds** - List currently active Zwift worlds
|
|
93
|
+
- **Riders in world** - See who is riding in a specific world
|
|
94
|
+
|
|
95
|
+
### For zsdata (Zwift Status)
|
|
96
|
+
|
|
97
|
+
- **Service status** - Overall system status and component health
|
|
98
|
+
- **Components** - Individual service component status (Game, Web, Companion, etc.)
|
|
99
|
+
- **Incidents** - Current and historical incident reports with timelines
|
|
100
|
+
- **Maintenance** - Scheduled maintenance windows (all, upcoming, active)
|
|
101
|
+
|
|
83
102
|
### Common Features
|
|
84
103
|
|
|
85
104
|
- **Async support** - Concurrent fetching with asyncio or trio backends
|
|
@@ -107,10 +126,16 @@ For Zwiftracing (`zrdata`), you will need your Zwiftracing API authorization hea
|
|
|
107
126
|
|
|
108
127
|
```sh
|
|
109
128
|
keyring set zrdatafetch authorization
|
|
110
|
-
# or
|
|
129
|
+
# or with zrdata
|
|
111
130
|
zrdata config
|
|
112
131
|
```
|
|
113
132
|
|
|
133
|
+
For Zwift (`zdata`), you will need your Zwift account credentials:
|
|
134
|
+
|
|
135
|
+
```sh
|
|
136
|
+
zdata config
|
|
137
|
+
```
|
|
138
|
+
|
|
114
139
|
In principle, the library can use alternate backend keyrings, but I have not
|
|
115
140
|
tested this so far. At the moment, only the system keyring is used. See [the
|
|
116
141
|
keyring docs](https://keyring.readthedocs.io/en/latest/) for more details on how
|
|
@@ -295,7 +320,7 @@ including rider ratings, race results, and team rosters.
|
|
|
295
320
|
|
|
296
321
|
```sh
|
|
297
322
|
usage: zrdata [-h] [-v] [-vv] [--log-file PATH] [-r] [--v1fetch] [--noaction] [--sync]
|
|
298
|
-
[--batch] [--batch-file FILE] [--premium]
|
|
323
|
+
[--batch] [--batch-file FILE] [--premium] [--at DATETIME]
|
|
299
324
|
[{config,rider,result,team}] [id ...]
|
|
300
325
|
|
|
301
326
|
Module for fetching Zwiftracing data using the Zwiftracing API
|
|
@@ -317,6 +342,7 @@ options:
|
|
|
317
342
|
--batch use batch POST endpoint for multiple IDs (rider command only)
|
|
318
343
|
--batch-file FILE read IDs from file (one per line) for batch request (rider command only)
|
|
319
344
|
--premium use premium tier rate limits (higher request quotas)
|
|
345
|
+
--at DATETIME fetch historical ratings at a date/time in UTC (rider command only)
|
|
320
346
|
```
|
|
321
347
|
|
|
322
348
|
**Note:** All objects support both synchronous (`fetch()`) and asynchronous (`afetch()`) methods. See the Async API section below for details.
|
|
@@ -349,6 +375,28 @@ zrdata config
|
|
|
349
375
|
zrdata config # Will prompt for authorization header
|
|
350
376
|
```
|
|
351
377
|
|
|
378
|
+
### Historical Ratings
|
|
379
|
+
|
|
380
|
+
Use `--at` to fetch rider ratings at a specific point in time. Accepts ISO 8601
|
|
381
|
+
date/time strings, interpreted as UTC:
|
|
382
|
+
|
|
383
|
+
```sh
|
|
384
|
+
# Fetch ratings as of a specific date
|
|
385
|
+
zrdata rider --at 2024-06-15 12345
|
|
386
|
+
|
|
387
|
+
# Fetch ratings at a specific date and time
|
|
388
|
+
zrdata rider --at "2024-06-15 14:30" 12345
|
|
389
|
+
|
|
390
|
+
# ISO 8601 with T separator
|
|
391
|
+
zrdata rider --at 2024-06-15T14:30:00 12345
|
|
392
|
+
|
|
393
|
+
# Combine with batch
|
|
394
|
+
zrdata rider --batch --at 2024-06-15 12345 67890 11111
|
|
395
|
+
|
|
396
|
+
# Preview what would be fetched
|
|
397
|
+
zrdata rider --noaction --at 2024-06-15 12345
|
|
398
|
+
```
|
|
399
|
+
|
|
352
400
|
### Advanced Options
|
|
353
401
|
|
|
354
402
|
```sh
|
|
@@ -735,10 +783,11 @@ async def main():
|
|
|
735
783
|
for zwift_id, rider in riders.items():
|
|
736
784
|
print(f"{rider.name}: {rider.current_rating}")
|
|
737
785
|
|
|
738
|
-
# Batch fetch with historical data
|
|
786
|
+
# Batch fetch with historical data (epoch is a Unix timestamp)
|
|
787
|
+
from shared.validation import parse_datetime_to_epoch
|
|
739
788
|
historical = await ZRRider.afetch_batch(
|
|
740
789
|
12345, 67890,
|
|
741
|
-
epoch=
|
|
790
|
+
epoch=parse_datetime_to_epoch('2024-01-01'),
|
|
742
791
|
zr=zr
|
|
743
792
|
)
|
|
744
793
|
|
|
@@ -1130,6 +1179,108 @@ obj.asdict() # return the data as a dictionary
|
|
|
1130
1179
|
print(obj) # effectively the same as obj.asdict()
|
|
1131
1180
|
```
|
|
1132
1181
|
|
|
1182
|
+
## Zwift Data (zdata)
|
|
1183
|
+
|
|
1184
|
+
The `zdata` command-line tool provides access to Zwift's unofficial API for
|
|
1185
|
+
rider profiles, social data, activities, and world information. Requires Zwift
|
|
1186
|
+
account credentials.
|
|
1187
|
+
|
|
1188
|
+
### Command-line usage
|
|
1189
|
+
|
|
1190
|
+
```sh
|
|
1191
|
+
zdata config # Set up Zwift credentials
|
|
1192
|
+
zdata profile 550564 # Fetch rider profile
|
|
1193
|
+
zdata profile 550564 123456 # Fetch multiple profiles
|
|
1194
|
+
zdata followers 550564 # Fetch followers and followees
|
|
1195
|
+
zdata followers --followers-only 550564 # Followers only
|
|
1196
|
+
zdata followers --followees-only 550564 # Followees only
|
|
1197
|
+
zdata rideons 550564 12345678 # Fetch RideOns (rider_id activity_id)
|
|
1198
|
+
zdata rideons --give 550564 12345678 # Give a RideOn
|
|
1199
|
+
zdata activity 550564 # Fetch activity history
|
|
1200
|
+
zdata activity --limit 50 550564 # Fetch 50 activities
|
|
1201
|
+
zdata worlds # List active worlds
|
|
1202
|
+
zdata ridersinworld 1 # Riders in world by ID
|
|
1203
|
+
zdata ridersinworld watopia # Riders in world by name
|
|
1204
|
+
zdata profile --raw 550564 # Raw JSON output
|
|
1205
|
+
zdata -v profile 550564 # Verbose logging
|
|
1206
|
+
```
|
|
1207
|
+
|
|
1208
|
+
### Library usage
|
|
1209
|
+
|
|
1210
|
+
```python
|
|
1211
|
+
from zdatafetch import (
|
|
1212
|
+
ZwiftProfile,
|
|
1213
|
+
ZwiftFollowers,
|
|
1214
|
+
ZwiftRideOns,
|
|
1215
|
+
ZwiftActivity,
|
|
1216
|
+
ZwiftWorlds,
|
|
1217
|
+
ZwiftRidersInWorld,
|
|
1218
|
+
)
|
|
1219
|
+
|
|
1220
|
+
# Fetch rider profile
|
|
1221
|
+
profile = ZwiftProfile()
|
|
1222
|
+
profile.fetch(550564)
|
|
1223
|
+
print(profile.json())
|
|
1224
|
+
|
|
1225
|
+
# Fetch followers
|
|
1226
|
+
followers = ZwiftFollowers()
|
|
1227
|
+
followers.fetch(550564)
|
|
1228
|
+
print(f"Followers: {followers.follower_count()}")
|
|
1229
|
+
|
|
1230
|
+
# Fetch activity history
|
|
1231
|
+
activity = ZwiftActivity()
|
|
1232
|
+
activity.fetch(550564, start=0, limit=20)
|
|
1233
|
+
|
|
1234
|
+
# List active worlds
|
|
1235
|
+
worlds = ZwiftWorlds()
|
|
1236
|
+
worlds.fetch()
|
|
1237
|
+
|
|
1238
|
+
# Give a RideOn
|
|
1239
|
+
ZwiftRideOns.give_rideon(550564, 12345678)
|
|
1240
|
+
```
|
|
1241
|
+
|
|
1242
|
+
## Zwift Status Data (zsdata)
|
|
1243
|
+
|
|
1244
|
+
The `zsdata` command-line tool provides access to the public Zwift Status API
|
|
1245
|
+
(powered by Statuspage.io). No authentication required.
|
|
1246
|
+
|
|
1247
|
+
### Command-line usage
|
|
1248
|
+
|
|
1249
|
+
```sh
|
|
1250
|
+
zsdata status # Overall status summary
|
|
1251
|
+
zsdata status --components # Component list only
|
|
1252
|
+
zsdata incidents # All incidents
|
|
1253
|
+
zsdata incidents --unresolved # Unresolved incidents only
|
|
1254
|
+
zsdata maintenance # All scheduled maintenance
|
|
1255
|
+
zsdata maintenance --upcoming # Upcoming maintenance only
|
|
1256
|
+
zsdata maintenance --active # Active maintenance only
|
|
1257
|
+
zsdata status --raw # Raw JSON output
|
|
1258
|
+
zsdata status --json # Parsed JSON output
|
|
1259
|
+
zsdata -v status # Verbose logging
|
|
1260
|
+
```
|
|
1261
|
+
|
|
1262
|
+
### Library usage
|
|
1263
|
+
|
|
1264
|
+
```python
|
|
1265
|
+
from zsdatafetch import ZSSummaryFetch, ZSIncidentFetch, ZSMaintenanceFetch
|
|
1266
|
+
|
|
1267
|
+
# Get overall status
|
|
1268
|
+
summary = ZSSummaryFetch().fetch()
|
|
1269
|
+
print(summary.status.description) # "All Systems Operational"
|
|
1270
|
+
for comp in summary.components:
|
|
1271
|
+
print(f" {comp.name}: {comp.status}")
|
|
1272
|
+
|
|
1273
|
+
# Get unresolved incidents
|
|
1274
|
+
incidents = ZSIncidentFetch().fetch(unresolved_only=True)
|
|
1275
|
+
for incident in incidents:
|
|
1276
|
+
print(f"{incident.name} ({incident.impact})")
|
|
1277
|
+
|
|
1278
|
+
# Get upcoming maintenance
|
|
1279
|
+
maintenance = ZSMaintenanceFetch().fetch(upcoming=True)
|
|
1280
|
+
for m in maintenance:
|
|
1281
|
+
print(f"{m.name}: {m.scheduled_for} - {m.scheduled_until}")
|
|
1282
|
+
```
|
|
1283
|
+
|
|
1133
1284
|
## Development
|
|
1134
1285
|
|
|
1135
1286
|
I've switched over to using [https://astral.sh/](Astral)'s
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
# zpdatafetch &
|
|
1
|
+
# zpdatafetch, zrdatafetch & zsdatafetch
|
|
2
2
|
|
|
3
|
-
A python library and command-line tool for fetching data from ZwiftPower.com
|
|
3
|
+
A python library and command-line tool for fetching data from ZwiftPower.com, Zwiftracing.app, and Zwift Status APIs.
|
|
4
4
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
@@ -24,16 +24,19 @@ real free-threaded environment. Please do
|
|
|
24
24
|
|
|
25
25
|
## Overview
|
|
26
26
|
|
|
27
|
-
This package provides
|
|
27
|
+
This package provides four command-line tools:
|
|
28
28
|
|
|
29
|
-
| Tool | API
|
|
30
|
-
| ------------ |
|
|
31
|
-
| **`zpdata`** | ZwiftPower
|
|
32
|
-
| **`zrdata`** | Zwiftracing
|
|
29
|
+
| Tool | API | Purpose | Data Types |
|
|
30
|
+
| ------------ | ------------ | ------------------------------------------ | --------------------------------------------------------- |
|
|
31
|
+
| **`zpdata`** | ZwiftPower | Race rankings, signups, results | Cyclist, Primes, Results, Signups, Sprints, Teams, League |
|
|
32
|
+
| **`zrdata`** | Zwiftracing | Rider ratings, race results, rosters | Rider Ratings, Race Results, Team Rosters |
|
|
33
|
+
| **`zdata`** | Zwift | Profiles, followers, activities, worlds | Profile, Followers, RideOns, Activity, Worlds, Riders |
|
|
34
|
+
| **`zsdata`** | Zwift Status | Service status, incidents, maintenance | Summary, Components, Incidents, Maintenance |
|
|
33
35
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
36
|
+
All tools support flexible logging and can be used as standalone CLI tools or
|
|
37
|
+
imported as Python libraries. `zpdata`, `zrdata`, and `zdata` require
|
|
38
|
+
credentials; `zsdata` uses the public Zwift Status API (no authentication
|
|
39
|
+
required).
|
|
37
40
|
|
|
38
41
|
## Key Features
|
|
39
42
|
|
|
@@ -54,6 +57,22 @@ stores for each API.
|
|
|
54
57
|
- **Race results** - Complete race result data with rating changes
|
|
55
58
|
- **Team rosters** - Full team member details and power metrics
|
|
56
59
|
|
|
60
|
+
### For zdata (Zwift)
|
|
61
|
+
|
|
62
|
+
- **Rider profiles** - Fetch rider profile data by Zwift ID
|
|
63
|
+
- **Followers** - Follower and followee lists with filtering
|
|
64
|
+
- **RideOns** - Fetch RideOn data or give RideOns to activities
|
|
65
|
+
- **Activity history** - Fetch activity data with pagination
|
|
66
|
+
- **Worlds** - List currently active Zwift worlds
|
|
67
|
+
- **Riders in world** - See who is riding in a specific world
|
|
68
|
+
|
|
69
|
+
### For zsdata (Zwift Status)
|
|
70
|
+
|
|
71
|
+
- **Service status** - Overall system status and component health
|
|
72
|
+
- **Components** - Individual service component status (Game, Web, Companion, etc.)
|
|
73
|
+
- **Incidents** - Current and historical incident reports with timelines
|
|
74
|
+
- **Maintenance** - Scheduled maintenance windows (all, upcoming, active)
|
|
75
|
+
|
|
57
76
|
### Common Features
|
|
58
77
|
|
|
59
78
|
- **Async support** - Concurrent fetching with asyncio or trio backends
|
|
@@ -81,10 +100,16 @@ For Zwiftracing (`zrdata`), you will need your Zwiftracing API authorization hea
|
|
|
81
100
|
|
|
82
101
|
```sh
|
|
83
102
|
keyring set zrdatafetch authorization
|
|
84
|
-
# or
|
|
103
|
+
# or with zrdata
|
|
85
104
|
zrdata config
|
|
86
105
|
```
|
|
87
106
|
|
|
107
|
+
For Zwift (`zdata`), you will need your Zwift account credentials:
|
|
108
|
+
|
|
109
|
+
```sh
|
|
110
|
+
zdata config
|
|
111
|
+
```
|
|
112
|
+
|
|
88
113
|
In principle, the library can use alternate backend keyrings, but I have not
|
|
89
114
|
tested this so far. At the moment, only the system keyring is used. See [the
|
|
90
115
|
keyring docs](https://keyring.readthedocs.io/en/latest/) for more details on how
|
|
@@ -269,7 +294,7 @@ including rider ratings, race results, and team rosters.
|
|
|
269
294
|
|
|
270
295
|
```sh
|
|
271
296
|
usage: zrdata [-h] [-v] [-vv] [--log-file PATH] [-r] [--v1fetch] [--noaction] [--sync]
|
|
272
|
-
[--batch] [--batch-file FILE] [--premium]
|
|
297
|
+
[--batch] [--batch-file FILE] [--premium] [--at DATETIME]
|
|
273
298
|
[{config,rider,result,team}] [id ...]
|
|
274
299
|
|
|
275
300
|
Module for fetching Zwiftracing data using the Zwiftracing API
|
|
@@ -291,6 +316,7 @@ options:
|
|
|
291
316
|
--batch use batch POST endpoint for multiple IDs (rider command only)
|
|
292
317
|
--batch-file FILE read IDs from file (one per line) for batch request (rider command only)
|
|
293
318
|
--premium use premium tier rate limits (higher request quotas)
|
|
319
|
+
--at DATETIME fetch historical ratings at a date/time in UTC (rider command only)
|
|
294
320
|
```
|
|
295
321
|
|
|
296
322
|
**Note:** All objects support both synchronous (`fetch()`) and asynchronous (`afetch()`) methods. See the Async API section below for details.
|
|
@@ -323,6 +349,28 @@ zrdata config
|
|
|
323
349
|
zrdata config # Will prompt for authorization header
|
|
324
350
|
```
|
|
325
351
|
|
|
352
|
+
### Historical Ratings
|
|
353
|
+
|
|
354
|
+
Use `--at` to fetch rider ratings at a specific point in time. Accepts ISO 8601
|
|
355
|
+
date/time strings, interpreted as UTC:
|
|
356
|
+
|
|
357
|
+
```sh
|
|
358
|
+
# Fetch ratings as of a specific date
|
|
359
|
+
zrdata rider --at 2024-06-15 12345
|
|
360
|
+
|
|
361
|
+
# Fetch ratings at a specific date and time
|
|
362
|
+
zrdata rider --at "2024-06-15 14:30" 12345
|
|
363
|
+
|
|
364
|
+
# ISO 8601 with T separator
|
|
365
|
+
zrdata rider --at 2024-06-15T14:30:00 12345
|
|
366
|
+
|
|
367
|
+
# Combine with batch
|
|
368
|
+
zrdata rider --batch --at 2024-06-15 12345 67890 11111
|
|
369
|
+
|
|
370
|
+
# Preview what would be fetched
|
|
371
|
+
zrdata rider --noaction --at 2024-06-15 12345
|
|
372
|
+
```
|
|
373
|
+
|
|
326
374
|
### Advanced Options
|
|
327
375
|
|
|
328
376
|
```sh
|
|
@@ -709,10 +757,11 @@ async def main():
|
|
|
709
757
|
for zwift_id, rider in riders.items():
|
|
710
758
|
print(f"{rider.name}: {rider.current_rating}")
|
|
711
759
|
|
|
712
|
-
# Batch fetch with historical data
|
|
760
|
+
# Batch fetch with historical data (epoch is a Unix timestamp)
|
|
761
|
+
from shared.validation import parse_datetime_to_epoch
|
|
713
762
|
historical = await ZRRider.afetch_batch(
|
|
714
763
|
12345, 67890,
|
|
715
|
-
epoch=
|
|
764
|
+
epoch=parse_datetime_to_epoch('2024-01-01'),
|
|
716
765
|
zr=zr
|
|
717
766
|
)
|
|
718
767
|
|
|
@@ -1104,6 +1153,108 @@ obj.asdict() # return the data as a dictionary
|
|
|
1104
1153
|
print(obj) # effectively the same as obj.asdict()
|
|
1105
1154
|
```
|
|
1106
1155
|
|
|
1156
|
+
## Zwift Data (zdata)
|
|
1157
|
+
|
|
1158
|
+
The `zdata` command-line tool provides access to Zwift's unofficial API for
|
|
1159
|
+
rider profiles, social data, activities, and world information. Requires Zwift
|
|
1160
|
+
account credentials.
|
|
1161
|
+
|
|
1162
|
+
### Command-line usage
|
|
1163
|
+
|
|
1164
|
+
```sh
|
|
1165
|
+
zdata config # Set up Zwift credentials
|
|
1166
|
+
zdata profile 550564 # Fetch rider profile
|
|
1167
|
+
zdata profile 550564 123456 # Fetch multiple profiles
|
|
1168
|
+
zdata followers 550564 # Fetch followers and followees
|
|
1169
|
+
zdata followers --followers-only 550564 # Followers only
|
|
1170
|
+
zdata followers --followees-only 550564 # Followees only
|
|
1171
|
+
zdata rideons 550564 12345678 # Fetch RideOns (rider_id activity_id)
|
|
1172
|
+
zdata rideons --give 550564 12345678 # Give a RideOn
|
|
1173
|
+
zdata activity 550564 # Fetch activity history
|
|
1174
|
+
zdata activity --limit 50 550564 # Fetch 50 activities
|
|
1175
|
+
zdata worlds # List active worlds
|
|
1176
|
+
zdata ridersinworld 1 # Riders in world by ID
|
|
1177
|
+
zdata ridersinworld watopia # Riders in world by name
|
|
1178
|
+
zdata profile --raw 550564 # Raw JSON output
|
|
1179
|
+
zdata -v profile 550564 # Verbose logging
|
|
1180
|
+
```
|
|
1181
|
+
|
|
1182
|
+
### Library usage
|
|
1183
|
+
|
|
1184
|
+
```python
|
|
1185
|
+
from zdatafetch import (
|
|
1186
|
+
ZwiftProfile,
|
|
1187
|
+
ZwiftFollowers,
|
|
1188
|
+
ZwiftRideOns,
|
|
1189
|
+
ZwiftActivity,
|
|
1190
|
+
ZwiftWorlds,
|
|
1191
|
+
ZwiftRidersInWorld,
|
|
1192
|
+
)
|
|
1193
|
+
|
|
1194
|
+
# Fetch rider profile
|
|
1195
|
+
profile = ZwiftProfile()
|
|
1196
|
+
profile.fetch(550564)
|
|
1197
|
+
print(profile.json())
|
|
1198
|
+
|
|
1199
|
+
# Fetch followers
|
|
1200
|
+
followers = ZwiftFollowers()
|
|
1201
|
+
followers.fetch(550564)
|
|
1202
|
+
print(f"Followers: {followers.follower_count()}")
|
|
1203
|
+
|
|
1204
|
+
# Fetch activity history
|
|
1205
|
+
activity = ZwiftActivity()
|
|
1206
|
+
activity.fetch(550564, start=0, limit=20)
|
|
1207
|
+
|
|
1208
|
+
# List active worlds
|
|
1209
|
+
worlds = ZwiftWorlds()
|
|
1210
|
+
worlds.fetch()
|
|
1211
|
+
|
|
1212
|
+
# Give a RideOn
|
|
1213
|
+
ZwiftRideOns.give_rideon(550564, 12345678)
|
|
1214
|
+
```
|
|
1215
|
+
|
|
1216
|
+
## Zwift Status Data (zsdata)
|
|
1217
|
+
|
|
1218
|
+
The `zsdata` command-line tool provides access to the public Zwift Status API
|
|
1219
|
+
(powered by Statuspage.io). No authentication required.
|
|
1220
|
+
|
|
1221
|
+
### Command-line usage
|
|
1222
|
+
|
|
1223
|
+
```sh
|
|
1224
|
+
zsdata status # Overall status summary
|
|
1225
|
+
zsdata status --components # Component list only
|
|
1226
|
+
zsdata incidents # All incidents
|
|
1227
|
+
zsdata incidents --unresolved # Unresolved incidents only
|
|
1228
|
+
zsdata maintenance # All scheduled maintenance
|
|
1229
|
+
zsdata maintenance --upcoming # Upcoming maintenance only
|
|
1230
|
+
zsdata maintenance --active # Active maintenance only
|
|
1231
|
+
zsdata status --raw # Raw JSON output
|
|
1232
|
+
zsdata status --json # Parsed JSON output
|
|
1233
|
+
zsdata -v status # Verbose logging
|
|
1234
|
+
```
|
|
1235
|
+
|
|
1236
|
+
### Library usage
|
|
1237
|
+
|
|
1238
|
+
```python
|
|
1239
|
+
from zsdatafetch import ZSSummaryFetch, ZSIncidentFetch, ZSMaintenanceFetch
|
|
1240
|
+
|
|
1241
|
+
# Get overall status
|
|
1242
|
+
summary = ZSSummaryFetch().fetch()
|
|
1243
|
+
print(summary.status.description) # "All Systems Operational"
|
|
1244
|
+
for comp in summary.components:
|
|
1245
|
+
print(f" {comp.name}: {comp.status}")
|
|
1246
|
+
|
|
1247
|
+
# Get unresolved incidents
|
|
1248
|
+
incidents = ZSIncidentFetch().fetch(unresolved_only=True)
|
|
1249
|
+
for incident in incidents:
|
|
1250
|
+
print(f"{incident.name} ({incident.impact})")
|
|
1251
|
+
|
|
1252
|
+
# Get upcoming maintenance
|
|
1253
|
+
maintenance = ZSMaintenanceFetch().fetch(upcoming=True)
|
|
1254
|
+
for m in maintenance:
|
|
1255
|
+
print(f"{m.name}: {m.scheduled_for} - {m.scheduled_until}")
|
|
1256
|
+
```
|
|
1257
|
+
|
|
1107
1258
|
## Development
|
|
1108
1259
|
|
|
1109
1260
|
I've switched over to using [https://astral.sh/](Astral)'s
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "zpdatafetch"
|
|
3
|
-
version = "2.
|
|
3
|
+
version = "2.2.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"
|
|
@@ -39,6 +39,7 @@ Repository = "https://github.com/puckdoug/zpdatafetch"
|
|
|
39
39
|
zdata = "zdatafetch.cli:main"
|
|
40
40
|
zpdata = "zpdatafetch.cli:main"
|
|
41
41
|
zrdata = "zrdatafetch.cli:main"
|
|
42
|
+
zsdata = "zsdatafetch.cli:main"
|
|
42
43
|
|
|
43
44
|
[project.optional-dependencies]
|
|
44
45
|
# For users who want both backends
|
|
@@ -124,10 +125,6 @@ select = [
|
|
|
124
125
|
"UP", # pyupgrade
|
|
125
126
|
"ANN", # flake8-annotations (type hints)
|
|
126
127
|
]
|
|
127
|
-
ignore = [
|
|
128
|
-
"ANN101", # Missing type annotation for self
|
|
129
|
-
"ANN102", # Missing type annotation for cls
|
|
130
|
-
]
|
|
131
128
|
|
|
132
129
|
# Per-file ignores for justified Any usage
|
|
133
130
|
[tool.ruff.lint.per-file-ignores]
|
|
@@ -148,6 +145,8 @@ ignore = [
|
|
|
148
145
|
"src/zpdatafetch/*.py" = ["E501"]
|
|
149
146
|
# zdatafetch files have decorative comment lines that exceed 80 chars
|
|
150
147
|
"src/zdatafetch/*.py" = ["E501"]
|
|
148
|
+
# zsdatafetch async_zs uses Any for context manager exc params
|
|
149
|
+
"src/zsdatafetch/async_zs.py" = ["ANN401"]
|
|
151
150
|
# Test files don't need type annotations (fixtures don't have types) and allow long lines
|
|
152
151
|
"test/**/*.py" = ["ANN001", "ANN002", "ANN003", "ANN201", "ANN202", "E501"]
|
|
153
152
|
|
|
@@ -5,6 +5,7 @@ functionality for both zpdatafetch and zrdatafetch CLIs.
|
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
7
|
import logging
|
|
8
|
+
import sys
|
|
8
9
|
from argparse import ArgumentParser, Namespace
|
|
9
10
|
from collections.abc import Callable
|
|
10
11
|
from importlib.metadata import PackageNotFoundError, version
|
|
@@ -82,21 +83,6 @@ def create_base_parser(
|
|
|
82
83
|
action='store_true',
|
|
83
84
|
help='output fetched data as JSON (default: object repr)',
|
|
84
85
|
)
|
|
85
|
-
parser.add_argument(
|
|
86
|
-
'--extras',
|
|
87
|
-
action='store_true',
|
|
88
|
-
help='report recently added fields not handled natively',
|
|
89
|
-
)
|
|
90
|
-
parser.add_argument(
|
|
91
|
-
'--excluded',
|
|
92
|
-
action='store_true',
|
|
93
|
-
help='report recognized fields not yet explicitly handled',
|
|
94
|
-
)
|
|
95
|
-
parser.add_argument(
|
|
96
|
-
'--v1fetch',
|
|
97
|
-
action='store_true',
|
|
98
|
-
help='output fetched data in v1.8 format (for backward compatibility)',
|
|
99
|
-
)
|
|
100
86
|
|
|
101
87
|
# Dry-run argument
|
|
102
88
|
parser.add_argument(
|
|
@@ -216,7 +202,7 @@ def validate_command_name(
|
|
|
216
202
|
True if valid, False if invalid.
|
|
217
203
|
"""
|
|
218
204
|
if cmd not in valid_commands:
|
|
219
|
-
print(f'Error: Unknown command "{cmd}"')
|
|
205
|
+
print(f'Error: Unknown command "{cmd}"', file=sys.stderr)
|
|
220
206
|
return False
|
|
221
207
|
return True
|
|
222
208
|
|
|
@@ -235,7 +221,7 @@ def validate_ids_provided(
|
|
|
235
221
|
True if IDs provided, False otherwise.
|
|
236
222
|
"""
|
|
237
223
|
if not ids:
|
|
238
|
-
print(f'Error: {cmd} command requires one or more IDs')
|
|
224
|
+
print(f'Error: {cmd} command requires one or more IDs', file=sys.stderr)
|
|
239
225
|
return False
|
|
240
226
|
return True
|
|
241
227
|
|
|
@@ -271,5 +257,5 @@ def read_ids_from_file(filepath: str) -> list[str] | None:
|
|
|
271
257
|
with open(filepath) as f:
|
|
272
258
|
return [line.strip() for line in f if line.strip()]
|
|
273
259
|
except OSError as e:
|
|
274
|
-
print(f'Error reading batch file: {e}')
|
|
260
|
+
print(f'Error reading batch file: {e}', file=sys.stderr)
|
|
275
261
|
return None
|
|
@@ -15,7 +15,9 @@ These ranges are based on Python's native integer limits and observed
|
|
|
15
15
|
API behavior.
|
|
16
16
|
"""
|
|
17
17
|
|
|
18
|
+
import calendar
|
|
18
19
|
import sys
|
|
20
|
+
from datetime import datetime, timezone
|
|
19
21
|
from typing import Literal
|
|
20
22
|
|
|
21
23
|
# ID Range Constants
|
|
@@ -178,6 +180,44 @@ def validate_epoch(value: int) -> int:
|
|
|
178
180
|
return value
|
|
179
181
|
|
|
180
182
|
|
|
183
|
+
def parse_datetime_to_epoch(value: str) -> int:
|
|
184
|
+
"""Parse a date/time string to a validated Unix epoch timestamp (UTC).
|
|
185
|
+
|
|
186
|
+
Accepts ISO 8601 formats:
|
|
187
|
+
- '2024-06-15' (date only, midnight UTC)
|
|
188
|
+
- '2024-06-15 14:30' (date and time, UTC)
|
|
189
|
+
- '2024-06-15T14:30:00' (ISO 8601, UTC)
|
|
190
|
+
- '2024-06-15T14:30:00Z' (ISO 8601 with Z suffix)
|
|
191
|
+
- '2024-06-15T14:30:00+00:00' (ISO 8601 with timezone)
|
|
192
|
+
|
|
193
|
+
Args:
|
|
194
|
+
value: Date/time string to parse
|
|
195
|
+
|
|
196
|
+
Returns:
|
|
197
|
+
Unix epoch timestamp (int, UTC)
|
|
198
|
+
|
|
199
|
+
Raises:
|
|
200
|
+
ValidationError: If the string cannot be parsed or the resulting
|
|
201
|
+
epoch is out of range
|
|
202
|
+
"""
|
|
203
|
+
try:
|
|
204
|
+
# Replace trailing 'Z' with '+00:00' for Python 3.10 compatibility
|
|
205
|
+
normalized = value.replace('Z', '+00:00') if value.endswith('Z') else value
|
|
206
|
+
dt = datetime.fromisoformat(normalized)
|
|
207
|
+
except ValueError as e:
|
|
208
|
+
raise ValidationError(
|
|
209
|
+
f"Invalid date/time '{value}': use ISO 8601 format "
|
|
210
|
+
f"(e.g. '2024-06-15' or '2024-06-15T14:30:00')",
|
|
211
|
+
) from e
|
|
212
|
+
|
|
213
|
+
# Assume UTC if no timezone specified
|
|
214
|
+
if dt.tzinfo is None:
|
|
215
|
+
dt = dt.replace(tzinfo=timezone.utc)
|
|
216
|
+
|
|
217
|
+
epoch = calendar.timegm(dt.utctimetuple())
|
|
218
|
+
return validate_epoch(epoch)
|
|
219
|
+
|
|
220
|
+
|
|
181
221
|
def validate_batch_size(count: int, max_size: int = MAX_BATCH_SIZE) -> None:
|
|
182
222
|
"""Validate batch request size.
|
|
183
223
|
|