zpdatafetch 2.3.2__tar.gz → 2.4.1__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.3.2/src/zpdatafetch.egg-info → zpdatafetch-2.4.1}/PKG-INFO +104 -22
- {zpdatafetch-2.3.2 → zpdatafetch-2.4.1}/README.md +103 -21
- {zpdatafetch-2.3.2 → zpdatafetch-2.4.1}/pyproject.toml +1 -1
- {zpdatafetch-2.3.2 → zpdatafetch-2.4.1}/src/zdatafetch/followers.py +119 -51
- {zpdatafetch-2.3.2 → zpdatafetch-2.4.1}/src/zdatafetch/rideons.py +56 -6
- {zpdatafetch-2.3.2 → zpdatafetch-2.4.1}/src/zpdatafetch/cli.py +80 -1
- {zpdatafetch-2.3.2 → zpdatafetch-2.4.1}/src/zpdatafetch/zpcyclistfetch.py +1 -1
- zpdatafetch-2.4.1/src/zpdatafetch/zpleague.py +1153 -0
- {zpdatafetch-2.3.2 → zpdatafetch-2.4.1}/src/zpdatafetch/zpleaguefetch.py +248 -47
- {zpdatafetch-2.3.2 → zpdatafetch-2.4.1/src/zpdatafetch.egg-info}/PKG-INFO +104 -22
- {zpdatafetch-2.3.2 → zpdatafetch-2.4.1}/src/zpdatafetch.egg-info/SOURCES.txt +2 -0
- {zpdatafetch-2.3.2 → zpdatafetch-2.4.1}/src/zrdatafetch/__init__.py +6 -0
- {zpdatafetch-2.3.2 → zpdatafetch-2.4.1}/src/zrdatafetch/cli.py +48 -2
- zpdatafetch-2.4.1/src/zrdatafetch/zrcategories.py +257 -0
- zpdatafetch-2.4.1/src/zrdatafetch/zrcategoriesfetch.py +271 -0
- zpdatafetch-2.3.2/src/zpdatafetch/zpleague.py +0 -496
- {zpdatafetch-2.3.2 → zpdatafetch-2.4.1}/LICENSE +0 -0
- {zpdatafetch-2.3.2 → zpdatafetch-2.4.1}/setup.cfg +0 -0
- {zpdatafetch-2.3.2 → zpdatafetch-2.4.1}/src/shared/__init__.py +0 -0
- {zpdatafetch-2.3.2 → zpdatafetch-2.4.1}/src/shared/cli.py +0 -0
- {zpdatafetch-2.3.2 → zpdatafetch-2.4.1}/src/shared/config.py +0 -0
- {zpdatafetch-2.3.2 → zpdatafetch-2.4.1}/src/shared/error_helpers.py +0 -0
- {zpdatafetch-2.3.2 → zpdatafetch-2.4.1}/src/shared/exceptions.py +0 -0
- {zpdatafetch-2.3.2 → zpdatafetch-2.4.1}/src/shared/http_client.py +0 -0
- {zpdatafetch-2.3.2 → zpdatafetch-2.4.1}/src/shared/json_helpers.py +0 -0
- {zpdatafetch-2.3.2 → zpdatafetch-2.4.1}/src/shared/logging.py +0 -0
- {zpdatafetch-2.3.2 → zpdatafetch-2.4.1}/src/shared/validation.py +0 -0
- {zpdatafetch-2.3.2 → zpdatafetch-2.4.1}/src/zdatafetch/__init__.py +0 -0
- {zpdatafetch-2.3.2 → zpdatafetch-2.4.1}/src/zdatafetch/activity.py +0 -0
- {zpdatafetch-2.3.2 → zpdatafetch-2.4.1}/src/zdatafetch/auth.py +0 -0
- {zpdatafetch-2.3.2 → zpdatafetch-2.4.1}/src/zdatafetch/cli.py +0 -0
- {zpdatafetch-2.3.2 → zpdatafetch-2.4.1}/src/zdatafetch/config.py +0 -0
- {zpdatafetch-2.3.2 → zpdatafetch-2.4.1}/src/zdatafetch/logging_config.py +0 -0
- {zpdatafetch-2.3.2 → zpdatafetch-2.4.1}/src/zdatafetch/profile.py +0 -0
- {zpdatafetch-2.3.2 → zpdatafetch-2.4.1}/src/zdatafetch/ridersinworld.py +0 -0
- {zpdatafetch-2.3.2 → zpdatafetch-2.4.1}/src/zdatafetch/worlds.py +0 -0
- {zpdatafetch-2.3.2 → zpdatafetch-2.4.1}/src/zpdatafetch/__init__.py +0 -0
- {zpdatafetch-2.3.2 → zpdatafetch-2.4.1}/src/zpdatafetch/async_zp.py +0 -0
- {zpdatafetch-2.3.2 → zpdatafetch-2.4.1}/src/zpdatafetch/config.py +0 -0
- {zpdatafetch-2.3.2 → zpdatafetch-2.4.1}/src/zpdatafetch/logging_config.py +0 -0
- {zpdatafetch-2.3.2 → zpdatafetch-2.4.1}/src/zpdatafetch/zp.py +0 -0
- {zpdatafetch-2.3.2 → zpdatafetch-2.4.1}/src/zpdatafetch/zp_obj.py +0 -0
- {zpdatafetch-2.3.2 → zpdatafetch-2.4.1}/src/zpdatafetch/zp_utils.py +0 -0
- {zpdatafetch-2.3.2 → zpdatafetch-2.4.1}/src/zpdatafetch/zpcyclist.py +0 -0
- {zpdatafetch-2.3.2 → zpdatafetch-2.4.1}/src/zpdatafetch/zpprime.py +0 -0
- {zpdatafetch-2.3.2 → zpdatafetch-2.4.1}/src/zpdatafetch/zpprimesfetch.py +0 -0
- {zpdatafetch-2.3.2 → zpdatafetch-2.4.1}/src/zpdatafetch/zpracefinish.py +0 -0
- {zpdatafetch-2.3.2 → zpdatafetch-2.4.1}/src/zpdatafetch/zpracelog.py +0 -0
- {zpdatafetch-2.3.2 → zpdatafetch-2.4.1}/src/zpdatafetch/zpraceresult.py +0 -0
- {zpdatafetch-2.3.2 → zpdatafetch-2.4.1}/src/zpdatafetch/zpracesignup.py +0 -0
- {zpdatafetch-2.3.2 → zpdatafetch-2.4.1}/src/zpdatafetch/zpracesprint.py +0 -0
- {zpdatafetch-2.3.2 → zpdatafetch-2.4.1}/src/zpdatafetch/zpresultfetch.py +0 -0
- {zpdatafetch-2.3.2 → zpdatafetch-2.4.1}/src/zpdatafetch/zpsignupfetch.py +0 -0
- {zpdatafetch-2.3.2 → zpdatafetch-2.4.1}/src/zpdatafetch/zpsprintsfetch.py +0 -0
- {zpdatafetch-2.3.2 → zpdatafetch-2.4.1}/src/zpdatafetch/zpteam.py +0 -0
- {zpdatafetch-2.3.2 → zpdatafetch-2.4.1}/src/zpdatafetch/zpteamfetch.py +0 -0
- {zpdatafetch-2.3.2 → zpdatafetch-2.4.1}/src/zpdatafetch.egg-info/dependency_links.txt +0 -0
- {zpdatafetch-2.3.2 → zpdatafetch-2.4.1}/src/zpdatafetch.egg-info/entry_points.txt +0 -0
- {zpdatafetch-2.3.2 → zpdatafetch-2.4.1}/src/zpdatafetch.egg-info/requires.txt +0 -0
- {zpdatafetch-2.3.2 → zpdatafetch-2.4.1}/src/zpdatafetch.egg-info/top_level.txt +0 -0
- {zpdatafetch-2.3.2 → zpdatafetch-2.4.1}/src/zrdatafetch/async_zr.py +0 -0
- {zpdatafetch-2.3.2 → zpdatafetch-2.4.1}/src/zrdatafetch/config.py +0 -0
- {zpdatafetch-2.3.2 → zpdatafetch-2.4.1}/src/zrdatafetch/logging_config.py +0 -0
- {zpdatafetch-2.3.2 → zpdatafetch-2.4.1}/src/zrdatafetch/rate_limiter.py +0 -0
- {zpdatafetch-2.3.2 → zpdatafetch-2.4.1}/src/zrdatafetch/zr.py +0 -0
- {zpdatafetch-2.3.2 → zpdatafetch-2.4.1}/src/zrdatafetch/zr_utils.py +0 -0
- {zpdatafetch-2.3.2 → zpdatafetch-2.4.1}/src/zrdatafetch/zrraceresult.py +0 -0
- {zpdatafetch-2.3.2 → zpdatafetch-2.4.1}/src/zrdatafetch/zrresultfetch.py +0 -0
- {zpdatafetch-2.3.2 → zpdatafetch-2.4.1}/src/zrdatafetch/zrrider.py +0 -0
- {zpdatafetch-2.3.2 → zpdatafetch-2.4.1}/src/zrdatafetch/zrriderfetch.py +0 -0
- {zpdatafetch-2.3.2 → zpdatafetch-2.4.1}/src/zrdatafetch/zrteamfetch.py +0 -0
- {zpdatafetch-2.3.2 → zpdatafetch-2.4.1}/src/zrdatafetch/zrteamroster.py +0 -0
- {zpdatafetch-2.3.2 → zpdatafetch-2.4.1}/src/zsdatafetch/__init__.py +0 -0
- {zpdatafetch-2.3.2 → zpdatafetch-2.4.1}/src/zsdatafetch/async_zs.py +0 -0
- {zpdatafetch-2.3.2 → zpdatafetch-2.4.1}/src/zsdatafetch/cli.py +0 -0
- {zpdatafetch-2.3.2 → zpdatafetch-2.4.1}/src/zsdatafetch/logging_config.py +0 -0
- {zpdatafetch-2.3.2 → zpdatafetch-2.4.1}/src/zsdatafetch/models/__init__.py +0 -0
- {zpdatafetch-2.3.2 → zpdatafetch-2.4.1}/src/zsdatafetch/models/component.py +0 -0
- {zpdatafetch-2.3.2 → zpdatafetch-2.4.1}/src/zsdatafetch/models/incident.py +0 -0
- {zpdatafetch-2.3.2 → zpdatafetch-2.4.1}/src/zsdatafetch/models/maintenance.py +0 -0
- {zpdatafetch-2.3.2 → zpdatafetch-2.4.1}/src/zsdatafetch/models/page.py +0 -0
- {zpdatafetch-2.3.2 → zpdatafetch-2.4.1}/src/zsdatafetch/models/status.py +0 -0
- {zpdatafetch-2.3.2 → zpdatafetch-2.4.1}/src/zsdatafetch/models/summary.py +0 -0
- {zpdatafetch-2.3.2 → zpdatafetch-2.4.1}/src/zsdatafetch/zs.py +0 -0
- {zpdatafetch-2.3.2 → zpdatafetch-2.4.1}/src/zsdatafetch/zsincidentfetch.py +0 -0
- {zpdatafetch-2.3.2 → zpdatafetch-2.4.1}/src/zsdatafetch/zsmaintenancefetch.py +0 -0
- {zpdatafetch-2.3.2 → zpdatafetch-2.4.1}/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.4.1
|
|
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
|
|
@@ -58,7 +58,7 @@ This package provides four command-line tools:
|
|
|
58
58
|
| Tool | API | Purpose | Data Types |
|
|
59
59
|
| ------------ | ------------ | ------------------------------------------ | --------------------------------------------------------- |
|
|
60
60
|
| **`zpdata`** | ZwiftPower | Race rankings, signups, results | Cyclist, Primes, Results, Signups, Sprints, Teams, League |
|
|
61
|
-
| **`zrdata`** | Zwiftracing | Rider ratings, race results, rosters | Rider Ratings, Race Results, Team Rosters
|
|
61
|
+
| **`zrdata`** | Zwiftracing | Rider ratings, race results, rosters | Rider Ratings, Race Results, Team Rosters, Category Ranges |
|
|
62
62
|
| **`zdata`** | Zwift | Profiles, followers, activities, worlds | Profile, Followers, RideOns, Activity, Worlds, Riders |
|
|
63
63
|
| **`zsdata`** | Zwift Status | Service status, incidents, maintenance | Summary, Components, Incidents, Maintenance |
|
|
64
64
|
|
|
@@ -77,7 +77,7 @@ required).
|
|
|
77
77
|
- **Primes** - Prime results for Fastest Through Segment (FTS) and First Across the Line (FAL)
|
|
78
78
|
- **Sprints** - Sprint data including sprint details and positions
|
|
79
79
|
- **Team data** - Team rosters and member information
|
|
80
|
-
- **League data** - League standings and
|
|
80
|
+
- **League data** - League standings, events, team standings, and league metadata (name, categories, contact)
|
|
81
81
|
|
|
82
82
|
### For zrdata (Zwiftracing)
|
|
83
83
|
|
|
@@ -312,7 +312,25 @@ available classes are as follows:
|
|
|
312
312
|
- Signup: fetch signups for a particular event by event id
|
|
313
313
|
- Sprints: fetch sprints from one or more races using event id
|
|
314
314
|
- Team: fetch team data by team id
|
|
315
|
-
- League: fetch league standings by league id
|
|
315
|
+
- League: fetch league standings, events, team standings, team-event results and metadata by league id
|
|
316
|
+
|
|
317
|
+
**League data example:**
|
|
318
|
+
|
|
319
|
+
```python
|
|
320
|
+
from zpdatafetch import ZPLeagueFetch
|
|
321
|
+
|
|
322
|
+
lf = ZPLeagueFetch()
|
|
323
|
+
league = lf.fetch(3379)[3379]
|
|
324
|
+
|
|
325
|
+
league.info() # ZPLeagueInfo: name, contact, categories, ...
|
|
326
|
+
league.events() # list[ZPLeagueEvent]: event_id, title, start_datetime
|
|
327
|
+
league.team_standings() # list[ZPLeagueTeamStanding]
|
|
328
|
+
league.team_event_results() # list[ZPLeagueTeamEventResult]
|
|
329
|
+
print(league.json()) # all collections as JSON (keys omitted when empty)
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
Each source is fetched independently: a league without a standings file (e.g.
|
|
333
|
+
3388) still returns its events and metadata, and vice versa.
|
|
316
334
|
|
|
317
335
|
## Zwiftracing Data (zrdata)
|
|
318
336
|
|
|
@@ -322,30 +340,39 @@ including rider ratings, race results, and team rosters.
|
|
|
322
340
|
### Command-line usage
|
|
323
341
|
|
|
324
342
|
```sh
|
|
325
|
-
usage: zrdata [-h] [-v] [-vv] [--log-file PATH] [-r] [--
|
|
343
|
+
usage: zrdata [-h] [--version] [-v] [-vv] [--log-file PATH] [-r] [--json]
|
|
344
|
+
[--noaction] [--sync] [--extras] [--excluded] [--v1fetch]
|
|
326
345
|
[--batch] [--batch-file FILE] [--premium] [--at DATETIME]
|
|
327
|
-
[
|
|
346
|
+
[CMD] [id ...]
|
|
328
347
|
|
|
329
348
|
Module for fetching Zwiftracing data using the Zwiftracing API
|
|
330
349
|
|
|
331
350
|
positional arguments:
|
|
332
|
-
{config,rider,result,team}
|
|
333
|
-
|
|
334
|
-
id the id to search for
|
|
351
|
+
CMD command to execute: {config,rider,result,team,categories}
|
|
352
|
+
id ID(s) for the command
|
|
335
353
|
|
|
336
354
|
options:
|
|
337
|
-
-h, --help
|
|
338
|
-
|
|
339
|
-
-
|
|
340
|
-
|
|
341
|
-
-
|
|
342
|
-
--
|
|
343
|
-
--
|
|
344
|
-
--
|
|
345
|
-
--
|
|
346
|
-
--
|
|
347
|
-
--
|
|
348
|
-
--
|
|
355
|
+
-h, --help show this help message and exit
|
|
356
|
+
--version show program's version number and exit
|
|
357
|
+
-v, --verbose enable verbose output (INFO level logging)
|
|
358
|
+
-vv, --debug enable debug output (DEBUG level logging)
|
|
359
|
+
--log-file PATH write logging output to file
|
|
360
|
+
-r, --raw print raw result data as received from the server
|
|
361
|
+
--json output fetched data as JSON (default: object repr)
|
|
362
|
+
--noaction show what would be done without actually fetching data
|
|
363
|
+
--sync use synchronous (non-parallel) requests
|
|
364
|
+
--extras report recently added fields not handled natively
|
|
365
|
+
--excluded report recognized fields not yet explicitly handled
|
|
366
|
+
--v1fetch output fetched data in v1.8 format (for backward
|
|
367
|
+
compatibility)
|
|
368
|
+
--batch use batch POST endpoint for multiple IDs (rider command
|
|
369
|
+
only)
|
|
370
|
+
--batch-file FILE read IDs from file (one per line) for batch request
|
|
371
|
+
(rider command only)
|
|
372
|
+
--premium use premium tier rate limits (higher request quotas)
|
|
373
|
+
--at DATETIME fetch historical ratings at a given date/time (UTC), e.g.
|
|
374
|
+
'2024-06-15' or '2024-06-15T14:30:00' (rider command
|
|
375
|
+
only)
|
|
349
376
|
```
|
|
350
377
|
|
|
351
378
|
**Note:** All objects support both synchronous (`fetch()`) and asynchronous (`afetch()`) methods. See the Async API section below for details.
|
|
@@ -371,6 +398,9 @@ zrdata result 3590800
|
|
|
371
398
|
# Fetch team roster
|
|
372
399
|
zrdata team 456
|
|
373
400
|
|
|
401
|
+
# Fetch vELO2 category ranges
|
|
402
|
+
zrdata categories
|
|
403
|
+
|
|
374
404
|
# View current configuration
|
|
375
405
|
zrdata config
|
|
376
406
|
|
|
@@ -378,6 +408,50 @@ zrdata config
|
|
|
378
408
|
zrdata config # Will prompt for authorization header
|
|
379
409
|
```
|
|
380
410
|
|
|
411
|
+
### Category Ranges (vELO2)
|
|
412
|
+
|
|
413
|
+
Fetch the vELO2 category ranges used to bucket riders by rating:
|
|
414
|
+
|
|
415
|
+
```sh
|
|
416
|
+
zrdata categories
|
|
417
|
+
# Output: the full repr — every entry with number, name, and range:
|
|
418
|
+
# ZRCategories(scale='1-1000', categories=[ZRvELOCategory(
|
|
419
|
+
# number=1, name='Diamond', min=920, max=None), ZRvELOCategory(
|
|
420
|
+
# number=2, name='Ruby', min=840, max=919), ...])
|
|
421
|
+
```
|
|
422
|
+
|
|
423
|
+
Use `--json` for the full mapping:
|
|
424
|
+
|
|
425
|
+
```sh
|
|
426
|
+
zrdata categories --json
|
|
427
|
+
# Output:
|
|
428
|
+
# {
|
|
429
|
+
# "scale": "1-1000",
|
|
430
|
+
# "categories": [
|
|
431
|
+
# {"number": 1, "name": "Diamond", "min": 920, "max": null},
|
|
432
|
+
# {"number": 2, "name": "Ruby", "min": 840, "max": 919},
|
|
433
|
+
# ...
|
|
434
|
+
# {"number": 10, "name": "Copper", "min": 0, "max": 359}
|
|
435
|
+
# ]
|
|
436
|
+
# }
|
|
437
|
+
```
|
|
438
|
+
|
|
439
|
+
`--raw` prints the unmodified API response; `--noaction` previews the fetch.
|
|
440
|
+
|
|
441
|
+
```python
|
|
442
|
+
# Library usage
|
|
443
|
+
from zrdatafetch import ZRCategoriesFetch
|
|
444
|
+
|
|
445
|
+
categories = ZRCategoriesFetch().fetch()
|
|
446
|
+
for entry in categories: # iterate in API order (0 = Diamond)
|
|
447
|
+
print(f"{entry.number}: {entry.name} {entry.min}-{entry.max}")
|
|
448
|
+
|
|
449
|
+
silver = categories['Silver'] # lookup by exact name
|
|
450
|
+
diamond = categories[0] # positional access
|
|
451
|
+
'Silver' in categories # membership -> True
|
|
452
|
+
len(categories) # 10
|
|
453
|
+
```
|
|
454
|
+
|
|
381
455
|
### Historical Ratings
|
|
382
456
|
|
|
383
457
|
Use `--at` to fetch rider ratings at a specific point in time. Accepts ISO 8601
|
|
@@ -1237,7 +1311,7 @@ profile = ZwiftProfile()
|
|
|
1237
1311
|
profile.fetch(550564)
|
|
1238
1312
|
print(profile.json())
|
|
1239
1313
|
|
|
1240
|
-
# Fetch followers
|
|
1314
|
+
# Fetch followers (all pages; the API paginates at 200 entries per page)
|
|
1241
1315
|
followers = ZwiftFollowers()
|
|
1242
1316
|
followers.fetch(550564)
|
|
1243
1317
|
print(f"Followers: {followers.follower_count()}")
|
|
@@ -1251,7 +1325,15 @@ worlds = ZwiftWorlds()
|
|
|
1251
1325
|
worlds.fetch()
|
|
1252
1326
|
|
|
1253
1327
|
# Give a RideOn
|
|
1328
|
+
# rider_id = activity owner; your own id is resolved automatically
|
|
1329
|
+
# via GET /api/profiles/me and sent as {"profileId": <your id>}
|
|
1254
1330
|
ZwiftRideOns.give_rideon(550564, 12345678)
|
|
1331
|
+
|
|
1332
|
+
# Check who gave RideOns on an activity
|
|
1333
|
+
rideons = ZwiftRideOns()
|
|
1334
|
+
rideons.fetch(550564, 12345678)
|
|
1335
|
+
print(rideons.rideon_ids()) # rider IDs of RideOn givers
|
|
1336
|
+
print(rideons.has_rideon_from(766087)) # did this rider give a RideOn?
|
|
1255
1337
|
```
|
|
1256
1338
|
|
|
1257
1339
|
## Zwift Status Data (zsdata)
|
|
@@ -29,7 +29,7 @@ This package provides four command-line tools:
|
|
|
29
29
|
| Tool | API | Purpose | Data Types |
|
|
30
30
|
| ------------ | ------------ | ------------------------------------------ | --------------------------------------------------------- |
|
|
31
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
|
|
32
|
+
| **`zrdata`** | Zwiftracing | Rider ratings, race results, rosters | Rider Ratings, Race Results, Team Rosters, Category Ranges |
|
|
33
33
|
| **`zdata`** | Zwift | Profiles, followers, activities, worlds | Profile, Followers, RideOns, Activity, Worlds, Riders |
|
|
34
34
|
| **`zsdata`** | Zwift Status | Service status, incidents, maintenance | Summary, Components, Incidents, Maintenance |
|
|
35
35
|
|
|
@@ -48,7 +48,7 @@ required).
|
|
|
48
48
|
- **Primes** - Prime results for Fastest Through Segment (FTS) and First Across the Line (FAL)
|
|
49
49
|
- **Sprints** - Sprint data including sprint details and positions
|
|
50
50
|
- **Team data** - Team rosters and member information
|
|
51
|
-
- **League data** - League standings and
|
|
51
|
+
- **League data** - League standings, events, team standings, and league metadata (name, categories, contact)
|
|
52
52
|
|
|
53
53
|
### For zrdata (Zwiftracing)
|
|
54
54
|
|
|
@@ -283,7 +283,25 @@ available classes are as follows:
|
|
|
283
283
|
- Signup: fetch signups for a particular event by event id
|
|
284
284
|
- Sprints: fetch sprints from one or more races using event id
|
|
285
285
|
- Team: fetch team data by team id
|
|
286
|
-
- League: fetch league standings by league id
|
|
286
|
+
- League: fetch league standings, events, team standings, team-event results and metadata by league id
|
|
287
|
+
|
|
288
|
+
**League data example:**
|
|
289
|
+
|
|
290
|
+
```python
|
|
291
|
+
from zpdatafetch import ZPLeagueFetch
|
|
292
|
+
|
|
293
|
+
lf = ZPLeagueFetch()
|
|
294
|
+
league = lf.fetch(3379)[3379]
|
|
295
|
+
|
|
296
|
+
league.info() # ZPLeagueInfo: name, contact, categories, ...
|
|
297
|
+
league.events() # list[ZPLeagueEvent]: event_id, title, start_datetime
|
|
298
|
+
league.team_standings() # list[ZPLeagueTeamStanding]
|
|
299
|
+
league.team_event_results() # list[ZPLeagueTeamEventResult]
|
|
300
|
+
print(league.json()) # all collections as JSON (keys omitted when empty)
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
Each source is fetched independently: a league without a standings file (e.g.
|
|
304
|
+
3388) still returns its events and metadata, and vice versa.
|
|
287
305
|
|
|
288
306
|
## Zwiftracing Data (zrdata)
|
|
289
307
|
|
|
@@ -293,30 +311,39 @@ including rider ratings, race results, and team rosters.
|
|
|
293
311
|
### Command-line usage
|
|
294
312
|
|
|
295
313
|
```sh
|
|
296
|
-
usage: zrdata [-h] [-v] [-vv] [--log-file PATH] [-r] [--
|
|
314
|
+
usage: zrdata [-h] [--version] [-v] [-vv] [--log-file PATH] [-r] [--json]
|
|
315
|
+
[--noaction] [--sync] [--extras] [--excluded] [--v1fetch]
|
|
297
316
|
[--batch] [--batch-file FILE] [--premium] [--at DATETIME]
|
|
298
|
-
[
|
|
317
|
+
[CMD] [id ...]
|
|
299
318
|
|
|
300
319
|
Module for fetching Zwiftracing data using the Zwiftracing API
|
|
301
320
|
|
|
302
321
|
positional arguments:
|
|
303
|
-
{config,rider,result,team}
|
|
304
|
-
|
|
305
|
-
id the id to search for
|
|
322
|
+
CMD command to execute: {config,rider,result,team,categories}
|
|
323
|
+
id ID(s) for the command
|
|
306
324
|
|
|
307
325
|
options:
|
|
308
|
-
-h, --help
|
|
309
|
-
|
|
310
|
-
-
|
|
311
|
-
|
|
312
|
-
-
|
|
313
|
-
--
|
|
314
|
-
--
|
|
315
|
-
--
|
|
316
|
-
--
|
|
317
|
-
--
|
|
318
|
-
--
|
|
319
|
-
--
|
|
326
|
+
-h, --help show this help message and exit
|
|
327
|
+
--version show program's version number and exit
|
|
328
|
+
-v, --verbose enable verbose output (INFO level logging)
|
|
329
|
+
-vv, --debug enable debug output (DEBUG level logging)
|
|
330
|
+
--log-file PATH write logging output to file
|
|
331
|
+
-r, --raw print raw result data as received from the server
|
|
332
|
+
--json output fetched data as JSON (default: object repr)
|
|
333
|
+
--noaction show what would be done without actually fetching data
|
|
334
|
+
--sync use synchronous (non-parallel) requests
|
|
335
|
+
--extras report recently added fields not handled natively
|
|
336
|
+
--excluded report recognized fields not yet explicitly handled
|
|
337
|
+
--v1fetch output fetched data in v1.8 format (for backward
|
|
338
|
+
compatibility)
|
|
339
|
+
--batch use batch POST endpoint for multiple IDs (rider command
|
|
340
|
+
only)
|
|
341
|
+
--batch-file FILE read IDs from file (one per line) for batch request
|
|
342
|
+
(rider command only)
|
|
343
|
+
--premium use premium tier rate limits (higher request quotas)
|
|
344
|
+
--at DATETIME fetch historical ratings at a given date/time (UTC), e.g.
|
|
345
|
+
'2024-06-15' or '2024-06-15T14:30:00' (rider command
|
|
346
|
+
only)
|
|
320
347
|
```
|
|
321
348
|
|
|
322
349
|
**Note:** All objects support both synchronous (`fetch()`) and asynchronous (`afetch()`) methods. See the Async API section below for details.
|
|
@@ -342,6 +369,9 @@ zrdata result 3590800
|
|
|
342
369
|
# Fetch team roster
|
|
343
370
|
zrdata team 456
|
|
344
371
|
|
|
372
|
+
# Fetch vELO2 category ranges
|
|
373
|
+
zrdata categories
|
|
374
|
+
|
|
345
375
|
# View current configuration
|
|
346
376
|
zrdata config
|
|
347
377
|
|
|
@@ -349,6 +379,50 @@ zrdata config
|
|
|
349
379
|
zrdata config # Will prompt for authorization header
|
|
350
380
|
```
|
|
351
381
|
|
|
382
|
+
### Category Ranges (vELO2)
|
|
383
|
+
|
|
384
|
+
Fetch the vELO2 category ranges used to bucket riders by rating:
|
|
385
|
+
|
|
386
|
+
```sh
|
|
387
|
+
zrdata categories
|
|
388
|
+
# Output: the full repr — every entry with number, name, and range:
|
|
389
|
+
# ZRCategories(scale='1-1000', categories=[ZRvELOCategory(
|
|
390
|
+
# number=1, name='Diamond', min=920, max=None), ZRvELOCategory(
|
|
391
|
+
# number=2, name='Ruby', min=840, max=919), ...])
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
Use `--json` for the full mapping:
|
|
395
|
+
|
|
396
|
+
```sh
|
|
397
|
+
zrdata categories --json
|
|
398
|
+
# Output:
|
|
399
|
+
# {
|
|
400
|
+
# "scale": "1-1000",
|
|
401
|
+
# "categories": [
|
|
402
|
+
# {"number": 1, "name": "Diamond", "min": 920, "max": null},
|
|
403
|
+
# {"number": 2, "name": "Ruby", "min": 840, "max": 919},
|
|
404
|
+
# ...
|
|
405
|
+
# {"number": 10, "name": "Copper", "min": 0, "max": 359}
|
|
406
|
+
# ]
|
|
407
|
+
# }
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
`--raw` prints the unmodified API response; `--noaction` previews the fetch.
|
|
411
|
+
|
|
412
|
+
```python
|
|
413
|
+
# Library usage
|
|
414
|
+
from zrdatafetch import ZRCategoriesFetch
|
|
415
|
+
|
|
416
|
+
categories = ZRCategoriesFetch().fetch()
|
|
417
|
+
for entry in categories: # iterate in API order (0 = Diamond)
|
|
418
|
+
print(f"{entry.number}: {entry.name} {entry.min}-{entry.max}")
|
|
419
|
+
|
|
420
|
+
silver = categories['Silver'] # lookup by exact name
|
|
421
|
+
diamond = categories[0] # positional access
|
|
422
|
+
'Silver' in categories # membership -> True
|
|
423
|
+
len(categories) # 10
|
|
424
|
+
```
|
|
425
|
+
|
|
352
426
|
### Historical Ratings
|
|
353
427
|
|
|
354
428
|
Use `--at` to fetch rider ratings at a specific point in time. Accepts ISO 8601
|
|
@@ -1208,7 +1282,7 @@ profile = ZwiftProfile()
|
|
|
1208
1282
|
profile.fetch(550564)
|
|
1209
1283
|
print(profile.json())
|
|
1210
1284
|
|
|
1211
|
-
# Fetch followers
|
|
1285
|
+
# Fetch followers (all pages; the API paginates at 200 entries per page)
|
|
1212
1286
|
followers = ZwiftFollowers()
|
|
1213
1287
|
followers.fetch(550564)
|
|
1214
1288
|
print(f"Followers: {followers.follower_count()}")
|
|
@@ -1222,7 +1296,15 @@ worlds = ZwiftWorlds()
|
|
|
1222
1296
|
worlds.fetch()
|
|
1223
1297
|
|
|
1224
1298
|
# Give a RideOn
|
|
1299
|
+
# rider_id = activity owner; your own id is resolved automatically
|
|
1300
|
+
# via GET /api/profiles/me and sent as {"profileId": <your id>}
|
|
1225
1301
|
ZwiftRideOns.give_rideon(550564, 12345678)
|
|
1302
|
+
|
|
1303
|
+
# Check who gave RideOns on an activity
|
|
1304
|
+
rideons = ZwiftRideOns()
|
|
1305
|
+
rideons.fetch(550564, 12345678)
|
|
1306
|
+
print(rideons.rideon_ids()) # rider IDs of RideOn givers
|
|
1307
|
+
print(rideons.has_rideon_from(766087)) # did this rider give a RideOn?
|
|
1226
1308
|
```
|
|
1227
1309
|
|
|
1228
1310
|
## Zwift Status Data (zsdata)
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
"""Zwift follower and followee data fetching and management.
|
|
2
2
|
|
|
3
3
|
Provides access to follower/followee relationship data from Zwift's unofficial API.
|
|
4
|
+
|
|
5
|
+
Both endpoints are server-paginated (200 entries per page); every page is
|
|
6
|
+
collected so the complete follower/followee lists are returned.
|
|
4
7
|
"""
|
|
5
8
|
|
|
6
9
|
import json
|
|
7
|
-
from typing import Any
|
|
10
|
+
from typing import Any, Literal
|
|
8
11
|
|
|
9
12
|
import httpx2
|
|
10
13
|
|
|
@@ -16,6 +19,89 @@ from zdatafetch.logging_config import get_logger
|
|
|
16
19
|
|
|
17
20
|
logger = get_logger(__name__)
|
|
18
21
|
|
|
22
|
+
PAGE_SIZE = 200
|
|
23
|
+
MAX_PAGES = 500
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _fetch_paginated(
|
|
27
|
+
client: httpx2.Client,
|
|
28
|
+
url: str,
|
|
29
|
+
headers: dict[str, str],
|
|
30
|
+
rider_id: int,
|
|
31
|
+
kind: str,
|
|
32
|
+
mode: Literal['raise', 'partial'],
|
|
33
|
+
) -> list[dict[str, Any]]:
|
|
34
|
+
"""Fetch all pages of a followers/followees endpoint.
|
|
35
|
+
|
|
36
|
+
Zwift caps these endpoints at ``PAGE_SIZE`` entries per request. Each
|
|
37
|
+
request sends ``start``/``limit`` query parameters; pagination stops
|
|
38
|
+
when a page is empty or shorter than ``PAGE_SIZE``.
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
client: httpx2 client to request with
|
|
42
|
+
url: Endpoint URL (without start/limit parameters)
|
|
43
|
+
headers: Request headers
|
|
44
|
+
rider_id: Zwift rider ID (for logs and error messages)
|
|
45
|
+
kind: Endpoint label, 'followers' or 'followees'
|
|
46
|
+
mode: 'raise' raises NetworkError on a failed page; 'partial'
|
|
47
|
+
logs a warning and returns the pages collected so far
|
|
48
|
+
|
|
49
|
+
Returns:
|
|
50
|
+
Records merged across all fetched pages
|
|
51
|
+
|
|
52
|
+
Raises:
|
|
53
|
+
NetworkError: In 'raise' mode, if a page does not return 200
|
|
54
|
+
"""
|
|
55
|
+
records: list[dict[str, Any]] = []
|
|
56
|
+
for page in range(MAX_PAGES):
|
|
57
|
+
start = page * PAGE_SIZE
|
|
58
|
+
response = client.get(
|
|
59
|
+
url,
|
|
60
|
+
headers=headers,
|
|
61
|
+
params={'start': start, 'limit': PAGE_SIZE},
|
|
62
|
+
timeout=30.0,
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
if response.status_code != 200:
|
|
66
|
+
if mode == 'raise':
|
|
67
|
+
if response.status_code == 404:
|
|
68
|
+
raise NetworkError(f'Rider {rider_id} not found')
|
|
69
|
+
raise NetworkError(
|
|
70
|
+
f'Failed to fetch {kind} for rider {rider_id}: '
|
|
71
|
+
f'HTTP {response.status_code} - {response.text}',
|
|
72
|
+
)
|
|
73
|
+
logger.warning(
|
|
74
|
+
f'Failed to fetch {kind} for rider {rider_id}: '
|
|
75
|
+
f'HTTP {response.status_code}',
|
|
76
|
+
)
|
|
77
|
+
break
|
|
78
|
+
|
|
79
|
+
page_records = parse_json_safe(response.text, context=kind)
|
|
80
|
+
if not isinstance(page_records, list):
|
|
81
|
+
logger.warning(
|
|
82
|
+
f'Unexpected payload for {kind} of rider {rider_id} '
|
|
83
|
+
f'at start={start}: expected list, '
|
|
84
|
+
f'got {type(page_records).__name__}',
|
|
85
|
+
)
|
|
86
|
+
break
|
|
87
|
+
|
|
88
|
+
records.extend(page_records)
|
|
89
|
+
logger.debug(
|
|
90
|
+
f'Fetched {kind} page for rider {rider_id} '
|
|
91
|
+
f'(start={start}): {len(page_records)} records',
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
if len(page_records) < PAGE_SIZE:
|
|
95
|
+
break
|
|
96
|
+
else:
|
|
97
|
+
logger.warning(
|
|
98
|
+
f'Pagination cap of {MAX_PAGES} pages reached for {kind} of '
|
|
99
|
+
f'rider {rider_id}: returning partial data '
|
|
100
|
+
f'({len(records)} records)',
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
return records
|
|
104
|
+
|
|
19
105
|
|
|
20
106
|
class ZwiftFollowers:
|
|
21
107
|
"""Zwift follower and followee data.
|
|
@@ -27,6 +113,9 @@ class ZwiftFollowers:
|
|
|
27
113
|
GET https://us-or-rly101.zwift.com/api/profiles/{riderId}/followers
|
|
28
114
|
GET https://us-or-rly101.zwift.com/api/profiles/{riderId}/followees
|
|
29
115
|
|
|
116
|
+
Both endpoints are server-paginated (200 entries per page); fetch() and
|
|
117
|
+
fetch_multiple() collect every page so the complete lists are returned.
|
|
118
|
+
|
|
30
119
|
Documentation: https://github.com/strukturunion-mmw/zwift-api-documentation
|
|
31
120
|
|
|
32
121
|
Synchronous usage:
|
|
@@ -74,6 +163,9 @@ class ZwiftFollowers:
|
|
|
74
163
|
Loads credentials from Config, authenticates, fetches data,
|
|
75
164
|
and populates instance attributes.
|
|
76
165
|
|
|
166
|
+
Both lists are fetched in full; the API paginates at 200 entries per
|
|
167
|
+
page and all pages are collected.
|
|
168
|
+
|
|
77
169
|
Args:
|
|
78
170
|
rider_id: Zwift rider ID
|
|
79
171
|
include_followers: Whether to fetch followers list
|
|
@@ -113,39 +205,23 @@ class ZwiftFollowers:
|
|
|
113
205
|
|
|
114
206
|
try:
|
|
115
207
|
with httpx2.Client() as client:
|
|
116
|
-
# Fetch followers
|
|
208
|
+
# Fetch followers (all pages; failures are fatal)
|
|
117
209
|
if include_followers:
|
|
118
210
|
url = f'{self.BASE_URL}/api/profiles/{rider_id}/followers'
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
if response.status_code != 200:
|
|
124
|
-
raise NetworkError(
|
|
125
|
-
f'Failed to fetch followers for rider {rider_id}: '
|
|
126
|
-
f'HTTP {response.status_code} - {response.text}',
|
|
127
|
-
)
|
|
128
|
-
|
|
129
|
-
raw_data['followers'] = response.text
|
|
211
|
+
records = _fetch_paginated(
|
|
212
|
+
client, url, headers, rider_id, 'followers', mode='raise',
|
|
213
|
+
)
|
|
214
|
+
raw_data['followers'] = json.dumps(records)
|
|
130
215
|
logger.debug(f'Successfully fetched followers for rider {rider_id}')
|
|
131
216
|
|
|
132
|
-
# Fetch followees
|
|
217
|
+
# Fetch followees (all pages; failures keep collected data)
|
|
133
218
|
if include_followees:
|
|
134
219
|
url = f'{self.BASE_URL}/api/profiles/{rider_id}/followees'
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
logger.warning(
|
|
141
|
-
f'Failed to fetch followees for rider {rider_id}: '
|
|
142
|
-
f'HTTP {response.status_code}',
|
|
143
|
-
)
|
|
144
|
-
# Continue with just followers data
|
|
145
|
-
raw_data['followees'] = '[]'
|
|
146
|
-
else:
|
|
147
|
-
raw_data['followees'] = response.text
|
|
148
|
-
logger.debug(f'Successfully fetched followees for rider {rider_id}')
|
|
220
|
+
records = _fetch_paginated(
|
|
221
|
+
client, url, headers, rider_id, 'followees', mode='partial',
|
|
222
|
+
)
|
|
223
|
+
raw_data['followees'] = json.dumps(records)
|
|
224
|
+
logger.debug(f'Successfully fetched followees for rider {rider_id}')
|
|
149
225
|
|
|
150
226
|
# Parse and populate attributes
|
|
151
227
|
self._parse_response(raw_data)
|
|
@@ -172,6 +248,9 @@ class ZwiftFollowers:
|
|
|
172
248
|
) -> dict[int, 'ZwiftFollowers']:
|
|
173
249
|
"""Fetch multiple riders' follower data, returning dict of objects.
|
|
174
250
|
|
|
251
|
+
Both lists are fetched in full; the API paginates at 200 entries per
|
|
252
|
+
page and all pages are collected.
|
|
253
|
+
|
|
175
254
|
Args:
|
|
176
255
|
*rider_ids: Zwift rider IDs to fetch
|
|
177
256
|
include_followers: Whether to fetch followers lists
|
|
@@ -223,33 +302,22 @@ class ZwiftFollowers:
|
|
|
223
302
|
try:
|
|
224
303
|
raw_data = {}
|
|
225
304
|
|
|
226
|
-
# Fetch followers
|
|
305
|
+
# Fetch followers (all pages; failure skips the rider via
|
|
306
|
+
# the except below)
|
|
227
307
|
if include_followers:
|
|
228
308
|
url = f'{cls.BASE_URL}/api/profiles/{rider_id}/followers'
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
f'Failed to fetch followers for rider {rider_id}: '
|
|
236
|
-
f'HTTP {response.status_code}',
|
|
237
|
-
)
|
|
238
|
-
continue
|
|
239
|
-
|
|
240
|
-
# Fetch followees
|
|
309
|
+
records = _fetch_paginated(
|
|
310
|
+
client, url, headers, rider_id, 'followers', mode='raise',
|
|
311
|
+
)
|
|
312
|
+
raw_data['followers'] = json.dumps(records)
|
|
313
|
+
|
|
314
|
+
# Fetch followees (all pages; failures keep collected data)
|
|
241
315
|
if include_followees:
|
|
242
316
|
url = f'{cls.BASE_URL}/api/profiles/{rider_id}/followees'
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
else:
|
|
248
|
-
logger.warning(
|
|
249
|
-
f'Failed to fetch followees for rider {rider_id}: '
|
|
250
|
-
f'HTTP {response.status_code}',
|
|
251
|
-
)
|
|
252
|
-
raw_data['followees'] = '[]'
|
|
317
|
+
records = _fetch_paginated(
|
|
318
|
+
client, url, headers, rider_id, 'followees', mode='partial',
|
|
319
|
+
)
|
|
320
|
+
raw_data['followees'] = json.dumps(records)
|
|
253
321
|
|
|
254
322
|
# Create object and populate
|
|
255
323
|
followers_obj = cls()
|