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.
Files changed (84) hide show
  1. {zpdatafetch-2.1.1/src/zpdatafetch.egg-info → zpdatafetch-2.2.0}/PKG-INFO +166 -15
  2. {zpdatafetch-2.1.1 → zpdatafetch-2.2.0}/README.md +165 -14
  3. {zpdatafetch-2.1.1 → zpdatafetch-2.2.0}/pyproject.toml +4 -5
  4. {zpdatafetch-2.1.1 → zpdatafetch-2.2.0}/src/shared/cli.py +4 -18
  5. {zpdatafetch-2.1.1 → zpdatafetch-2.2.0}/src/shared/validation.py +40 -0
  6. {zpdatafetch-2.1.1 → zpdatafetch-2.2.0}/src/zpdatafetch/cli.py +23 -5
  7. {zpdatafetch-2.1.1 → zpdatafetch-2.2.0}/src/zpdatafetch/zp.py +0 -1
  8. {zpdatafetch-2.1.1 → zpdatafetch-2.2.0}/src/zpdatafetch/zpcyclistfetch.py +1 -1
  9. {zpdatafetch-2.1.1 → zpdatafetch-2.2.0}/src/zpdatafetch/zpprimesfetch.py +3 -4
  10. {zpdatafetch-2.1.1 → zpdatafetch-2.2.0}/src/zpdatafetch/zpraceresult.py +10 -5
  11. {zpdatafetch-2.1.1 → zpdatafetch-2.2.0/src/zpdatafetch.egg-info}/PKG-INFO +166 -15
  12. {zpdatafetch-2.1.1 → zpdatafetch-2.2.0}/src/zpdatafetch.egg-info/SOURCES.txt +16 -1
  13. {zpdatafetch-2.1.1 → zpdatafetch-2.2.0}/src/zpdatafetch.egg-info/entry_points.txt +1 -0
  14. {zpdatafetch-2.1.1 → zpdatafetch-2.2.0}/src/zpdatafetch.egg-info/top_level.txt +1 -0
  15. {zpdatafetch-2.1.1 → zpdatafetch-2.2.0}/src/zrdatafetch/cli.py +51 -10
  16. {zpdatafetch-2.1.1 → zpdatafetch-2.2.0}/src/zrdatafetch/zrraceresult.py +12 -5
  17. {zpdatafetch-2.1.1 → zpdatafetch-2.2.0}/src/zrdatafetch/zrteamroster.py +12 -5
  18. zpdatafetch-2.2.0/src/zsdatafetch/__init__.py +73 -0
  19. zpdatafetch-2.2.0/src/zsdatafetch/async_zs.py +110 -0
  20. zpdatafetch-2.2.0/src/zsdatafetch/cli.py +323 -0
  21. zpdatafetch-2.2.0/src/zsdatafetch/logging_config.py +54 -0
  22. zpdatafetch-2.2.0/src/zsdatafetch/models/__init__.py +22 -0
  23. zpdatafetch-2.2.0/src/zsdatafetch/models/component.py +113 -0
  24. zpdatafetch-2.2.0/src/zsdatafetch/models/incident.py +208 -0
  25. zpdatafetch-2.2.0/src/zsdatafetch/models/maintenance.py +219 -0
  26. zpdatafetch-2.2.0/src/zsdatafetch/models/page.py +72 -0
  27. zpdatafetch-2.2.0/src/zsdatafetch/models/status.py +61 -0
  28. zpdatafetch-2.2.0/src/zsdatafetch/models/summary.py +109 -0
  29. zpdatafetch-2.2.0/src/zsdatafetch/zs.py +94 -0
  30. zpdatafetch-2.2.0/src/zsdatafetch/zsincidentfetch.py +196 -0
  31. zpdatafetch-2.2.0/src/zsdatafetch/zsmaintenancefetch.py +231 -0
  32. zpdatafetch-2.2.0/src/zsdatafetch/zssummaryfetch.py +208 -0
  33. {zpdatafetch-2.1.1 → zpdatafetch-2.2.0}/LICENSE +0 -0
  34. {zpdatafetch-2.1.1 → zpdatafetch-2.2.0}/setup.cfg +0 -0
  35. {zpdatafetch-2.1.1 → zpdatafetch-2.2.0}/src/shared/__init__.py +0 -0
  36. {zpdatafetch-2.1.1 → zpdatafetch-2.2.0}/src/shared/config.py +0 -0
  37. {zpdatafetch-2.1.1 → zpdatafetch-2.2.0}/src/shared/error_helpers.py +0 -0
  38. {zpdatafetch-2.1.1 → zpdatafetch-2.2.0}/src/shared/exceptions.py +0 -0
  39. {zpdatafetch-2.1.1 → zpdatafetch-2.2.0}/src/shared/http_client.py +0 -0
  40. {zpdatafetch-2.1.1 → zpdatafetch-2.2.0}/src/shared/json_helpers.py +0 -0
  41. {zpdatafetch-2.1.1 → zpdatafetch-2.2.0}/src/shared/logging.py +0 -0
  42. {zpdatafetch-2.1.1 → zpdatafetch-2.2.0}/src/zdatafetch/__init__.py +0 -0
  43. {zpdatafetch-2.1.1 → zpdatafetch-2.2.0}/src/zdatafetch/activity.py +0 -0
  44. {zpdatafetch-2.1.1 → zpdatafetch-2.2.0}/src/zdatafetch/auth.py +0 -0
  45. {zpdatafetch-2.1.1 → zpdatafetch-2.2.0}/src/zdatafetch/cli.py +0 -0
  46. {zpdatafetch-2.1.1 → zpdatafetch-2.2.0}/src/zdatafetch/config.py +0 -0
  47. {zpdatafetch-2.1.1 → zpdatafetch-2.2.0}/src/zdatafetch/followers.py +0 -0
  48. {zpdatafetch-2.1.1 → zpdatafetch-2.2.0}/src/zdatafetch/logging_config.py +0 -0
  49. {zpdatafetch-2.1.1 → zpdatafetch-2.2.0}/src/zdatafetch/profile.py +0 -0
  50. {zpdatafetch-2.1.1 → zpdatafetch-2.2.0}/src/zdatafetch/rideons.py +0 -0
  51. {zpdatafetch-2.1.1 → zpdatafetch-2.2.0}/src/zdatafetch/ridersinworld.py +0 -0
  52. {zpdatafetch-2.1.1 → zpdatafetch-2.2.0}/src/zdatafetch/worlds.py +0 -0
  53. {zpdatafetch-2.1.1 → zpdatafetch-2.2.0}/src/zpdatafetch/__init__.py +0 -0
  54. {zpdatafetch-2.1.1 → zpdatafetch-2.2.0}/src/zpdatafetch/async_zp.py +0 -0
  55. {zpdatafetch-2.1.1 → zpdatafetch-2.2.0}/src/zpdatafetch/config.py +0 -0
  56. {zpdatafetch-2.1.1 → zpdatafetch-2.2.0}/src/zpdatafetch/logging_config.py +0 -0
  57. {zpdatafetch-2.1.1 → zpdatafetch-2.2.0}/src/zpdatafetch/zp_obj.py +0 -0
  58. {zpdatafetch-2.1.1 → zpdatafetch-2.2.0}/src/zpdatafetch/zp_utils.py +0 -0
  59. {zpdatafetch-2.1.1 → zpdatafetch-2.2.0}/src/zpdatafetch/zpcyclist.py +0 -0
  60. {zpdatafetch-2.1.1 → zpdatafetch-2.2.0}/src/zpdatafetch/zpleague.py +0 -0
  61. {zpdatafetch-2.1.1 → zpdatafetch-2.2.0}/src/zpdatafetch/zpleaguefetch.py +0 -0
  62. {zpdatafetch-2.1.1 → zpdatafetch-2.2.0}/src/zpdatafetch/zpprime.py +0 -0
  63. {zpdatafetch-2.1.1 → zpdatafetch-2.2.0}/src/zpdatafetch/zpracefinish.py +0 -0
  64. {zpdatafetch-2.1.1 → zpdatafetch-2.2.0}/src/zpdatafetch/zpracelog.py +0 -0
  65. {zpdatafetch-2.1.1 → zpdatafetch-2.2.0}/src/zpdatafetch/zpracesignup.py +0 -0
  66. {zpdatafetch-2.1.1 → zpdatafetch-2.2.0}/src/zpdatafetch/zpracesprint.py +0 -0
  67. {zpdatafetch-2.1.1 → zpdatafetch-2.2.0}/src/zpdatafetch/zpresultfetch.py +0 -0
  68. {zpdatafetch-2.1.1 → zpdatafetch-2.2.0}/src/zpdatafetch/zpsignupfetch.py +0 -0
  69. {zpdatafetch-2.1.1 → zpdatafetch-2.2.0}/src/zpdatafetch/zpsprintsfetch.py +0 -0
  70. {zpdatafetch-2.1.1 → zpdatafetch-2.2.0}/src/zpdatafetch/zpteam.py +0 -0
  71. {zpdatafetch-2.1.1 → zpdatafetch-2.2.0}/src/zpdatafetch/zpteamfetch.py +0 -0
  72. {zpdatafetch-2.1.1 → zpdatafetch-2.2.0}/src/zpdatafetch.egg-info/dependency_links.txt +0 -0
  73. {zpdatafetch-2.1.1 → zpdatafetch-2.2.0}/src/zpdatafetch.egg-info/requires.txt +0 -0
  74. {zpdatafetch-2.1.1 → zpdatafetch-2.2.0}/src/zrdatafetch/__init__.py +0 -0
  75. {zpdatafetch-2.1.1 → zpdatafetch-2.2.0}/src/zrdatafetch/async_zr.py +0 -0
  76. {zpdatafetch-2.1.1 → zpdatafetch-2.2.0}/src/zrdatafetch/config.py +0 -0
  77. {zpdatafetch-2.1.1 → zpdatafetch-2.2.0}/src/zrdatafetch/logging_config.py +0 -0
  78. {zpdatafetch-2.1.1 → zpdatafetch-2.2.0}/src/zrdatafetch/rate_limiter.py +0 -0
  79. {zpdatafetch-2.1.1 → zpdatafetch-2.2.0}/src/zrdatafetch/zr.py +0 -0
  80. {zpdatafetch-2.1.1 → zpdatafetch-2.2.0}/src/zrdatafetch/zr_utils.py +0 -0
  81. {zpdatafetch-2.1.1 → zpdatafetch-2.2.0}/src/zrdatafetch/zrresultfetch.py +0 -0
  82. {zpdatafetch-2.1.1 → zpdatafetch-2.2.0}/src/zrdatafetch/zrrider.py +0 -0
  83. {zpdatafetch-2.1.1 → zpdatafetch-2.2.0}/src/zrdatafetch/zrriderfetch.py +0 -0
  84. {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.1.1
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 & zrdatafetch
27
+ # zpdatafetch, zrdatafetch & zsdatafetch
28
28
 
29
- A python library and command-line tool for fetching data from ZwiftPower.com and Zwiftracing.app APIs.
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 two main command-line tools:
53
+ This package provides four command-line tools:
54
54
 
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 |
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
- Both tools support batch operations, flexible logging, and can be used as
61
- standalone CLI tools or imported as libraries. They maintain separate credential
62
- stores for each API.
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 wioth zrdata
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=1704067200, # Unix timestamp
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 & zrdatafetch
1
+ # zpdatafetch, zrdatafetch & zsdatafetch
2
2
 
3
- A python library and command-line tool for fetching data from ZwiftPower.com and Zwiftracing.app APIs.
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 two main command-line tools:
27
+ This package provides four command-line tools:
28
28
 
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 |
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
- Both tools support batch operations, flexible logging, and can be used as
35
- standalone CLI tools or imported as libraries. They maintain separate credential
36
- stores for each API.
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 wioth zrdata
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=1704067200, # Unix timestamp
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.1.1"
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