Skip to main content

A package for fetching data from Zwiftpower

Project description

zpdatafetch

A python library and command-line tool for fetching data from zwiftpower.

Installation

pip install zpdatafetch

or

uv add zpdatafetch

This currently works with python versions 3.10 - 3.14 including 3.14t but excluding 3.13t. Build fails on 3.13t and I am unlikely to fix it. If you want free threading, use 3.14t.

Note that while this builds and runs, it'd not yet properly tested to run in a real free-threaded environment.

Usage

zpdatafetch comes with a command-line tool named zpdata. This can be used to fetch data directly from zwiftpower. It sends the response to stdout. It can also act as a guide for how to use the library in your own program.

For both command-line and library usage, you will need to have a zwiftpower account. You will need set up your credentials in your system keyring. This can be done using the following commands from they python keyring library (installed as part of zpdatafetch if not already available on your system):

keyring set zpdatafetch username
keyring set zpdatafetch password

In principle, the library can use alternate backend keyrings, but I have not tested this so far. At the moment, only the system keyring is used. See the keyring docs for more details on how to use the keyring and keyring library for your system.

Command-line example

usage: zpdata [-h] [-v] [-vv] [--log-file PATH] [-r] [{config,cyclist,primes,result,signup,team}] [id ...]

Module for fetching zwiftpower data using the Zwifpower API

positional arguments:
  {config,cyclist,primes,result,signup,team}
                        which command to run
  id                    the id to search for, ignored for config

options:
  -h, --help            show this help message and exit
  -v, --verbose         enable INFO level logging to console
  -vv, --debug          enable DEBUG level logging to console
  --log-file PATH       path to log file (enables file logging)
  -r, --raw             print the raw results returned to screen

Basic usage:

# Fetch cyclist data (quiet mode - only errors shown)
zpdata cyclist 1234567

# Verbose mode - show INFO messages
zpdata -v cyclist 1234567

# Debug mode - show DEBUG messages
zpdata -vv cyclist 1234567

# Log to file only (quiet console)
zpdata --log-file zpdatafetch.log cyclist 1234567

# Both console and file logging
zpdata -v --log-file zpdatafetch.log cyclist 1234567

Library example (Synchronous API)

from zpdatafetch import Cyclist

c = Cyclist()
c.fetch(1234567) # fetch data for cyclist with zwift id 1234567
print(c.json())

The interface for each of the objects is effectively the same as the example above, with the individual class and id number changed as appropriate. The available classes are as follows:

  • Cyclist: fetch one or more cyclists by zwift id
  • Primes: fetch primes from one or more races using event id
  • Result: fetch results from one or more races (finish, points) using event id
  • Signup: fetch signups for a particular event by event id
  • Team: fetch team data by team id

Async Library example

The library also provides a full async/await API for concurrent operations:

import anyio
from zpdatafetch import AsyncCyclist, AsyncResult, AsyncZP

async def main():
    # Use async context manager
    async with AsyncZP() as zp:
        cyclist = AsyncCyclist()
        result = AsyncResult()

        cyclist.set_session(zp)
        result.set_session(zp)

        # Fetch multiple resources concurrently
        async with anyio.create_task_group() as tg:
            tg.start_soon(cyclist.fetch, 1234567, 7654321)  # Multiple cyclists
            tg.start_soon(result.fetch, 3590800, 3590801)   # Multiple races

        print(cyclist.json())
        print(result.json())

anyio.run(main)

Available async classes:

  • AsyncZP: Async authentication and HTTP client
  • AsyncCyclist: Async cyclist data fetching
  • AsyncResult: Async race results fetching
  • AsyncSignup: Async signup list fetching
  • AsyncTeam: Async team data fetching
  • AsyncPrimes: Async prime/sprint data fetching

Async backend support:

The async API uses anyio to support both asyncio and trio backends:

  • asyncio (default): Built into Python, widely used
  • trio (optional): Install with pip install zpdatafetch[trio]

You can use either backend transparently - the same code works with both!

Benefits of the async API:

  • 2-3x faster for batch operations (concurrent fetching)
  • Perfect for web services (FastAPI, aiohttp)
  • Modern async/await syntax
  • Connection pooling support
  • Choice of async backend (asyncio or trio)

See local/ASYNC_API_DOCUMENTATION.md and examples/async_*.py for detailed async usage examples.

The ZP class is the main driver for the library. It is used to fetch the data from zwiftpower. The other classes are used to parse the data into a more useful format.

Context Manager (Resource Management)

The library now supports context managers for automatic resource cleanup. This is especially useful when making multiple requests, as it ensures proper cleanup of the underlying HTTP session:

from zpdatafetch import Cyclist

# Using context manager for automatic cleanup
with Cyclist() as c:
    c.fetch([1234567, 7654321])  # fetch multiple cyclists
    print(c.json())
# HTTP session is automatically closed

Connection Pooling (Performance Optimization)

For batch operations, you can enable connection pooling to reuse a single HTTP client across multiple requests. This significantly improves performance when making multiple API calls:

from zpdatafetch import Cyclist, Result

# Multiple operations share a single connection pool
with Cyclist(shared_client=True) as cyclist:
    cyclist.fetch([1234567, 7654321, 9876543])
    cyclist_data = cyclist.json()

with Result(shared_client=True) as result:
    result.fetch([111111, 222222, 333333])
    result_data = result.json()

# Clean up shared session when done
from zpdatafetch.zp import ZP
ZP.close_shared_session()

The shared_client=True option (enabled by default) allows multiple instances to reuse the same HTTP connection pool, reducing overhead and improving throughput.

Automatic Retry with Exponential Backoff

The library includes built-in retry logic with exponential backoff for handling transient network failures. This is automatically applied to fetch_json() and fetch_page() methods:

from zpdatafetch import Cyclist

c = Cyclist()

# Retries are automatically handled internally
# Default: 3 retries with exponential backoff
c.fetch(1234567)  # Automatically retries on transient errors
print(c.json())

For direct HTTP operations via the ZP class, you can configure retry behavior:

from zpdatafetch.zp import ZP

zp = ZP()

# Fetch with custom retry settings
data = zp.fetch_json(
    '/some/endpoint',
    max_retries=5,           # number of retries
    backoff_factor=1.5       # exponential backoff multiplier
)

The retry mechanism automatically handles:

  • Connection errors
  • Timeout errors
  • Request errors
  • HTTP 5xx server errors

This makes the library more resilient to temporary network issues and server hiccups.

Logging

zpdatafetch provides flexible logging support for both library and command-line usage.

Default Behavior (Quiet Mode)

By default, the library is completely quiet except for errors, which are sent to stderr. This ensures that library users get clean output unless something goes wrong.

Library Usage with Logging

To enable logging when using zpdatafetch as a library, use the setup_logging() function:

from zpdatafetch import setup_logging, Cyclist

# Enable console logging at INFO level
setup_logging(console_level='INFO')

c = Cyclist()
c.fetch(1234567)

Logging Configuration Options:

from zpdatafetch import setup_logging

# File logging only (no console output except errors)
setup_logging(log_file='zpdatafetch.log', force_console=False)

# Console logging at DEBUG level
setup_logging(console_level='DEBUG')

# Both console (INFO) and file (DEBUG) logging
setup_logging(
    log_file='debug.log',
    console_level='INFO',    # Simple messages to console
    file_level='DEBUG'       # Detailed logs to file
)

# Force console logging even when not in a TTY
setup_logging(console_level='INFO', force_console=True)

Log Format:

  • Console output: Simple, clean format showing only messages (e.g., "Logging in to Zwiftpower")
  • File output: Detailed format with timestamps, module names, log levels, function names, and line numbers
    2025-10-24 15:17:39 - zpdatafetch.zp - INFO - login:90 - Logging in to Zwiftpower
    

Available Log Levels:

  • 'DEBUG' - Detailed diagnostic information
  • 'INFO' - General informational messages
  • 'WARNING' - Warning messages
  • 'ERROR' - Error messages (default)

Object signature

Each object has a common set of methods available:

obj.fetch(id) or obj.fetch([id1, id2, id3]) # fetch the data from zwiftpower. As argument, fetch expects a single ID or a list (tuple or array) of IDs.
obj.json() # return the data as a json object
obj.asdict() # return the data as a dictionary
print(obj) # effectively the same as obj.asdict()

Development

I've switched over to using https://astral.sh/'s https://astral.sh/uv/ for the development toolchain. Directions below try to cover both options.

  1. Install this package
  2. Install the requirements
pip install -r requirements.txt

or

uv sync
  1. Set up your keyring. You may want to use a account that is separate from the one you use for riding and racing for this.
keyring set zpdatafetch username
keyring set zpdatafetch password
  1. Run the downloader
  PYTHONPATH=`pwd`/src python src/zpdatafetch/zp.py

or

  uv run src/zpdatafetch/zp.py

This should return a '200' message if you've set everything up correctly, proving that the program can log in correctly to Zwiftpower.

With a few exceptions, each object has a callable interface that can be used for simple direct access to experiment without additional code wrapped around it - yours or the provided command-line tool. They each respond to the -h flag to provide help. Basic examples follow.

Cyclist example

PYTHONPATH=`pwd`/src python src/zpdatafetch/cyclist.py -v -r <zwift_id>

Team example

PYTHONPATH=`pwd`/src python src/zpdatafetch/team.py -v -r <team_id>

Signup example

PYTHONPATH=`pwd`/src python src/zpdatafetch/signup.py -v -r <race_id>

Result example

PYTHONPATH=`pwd`/src python src/zpdatafetch/result.py -v -r <race_id>

Primes example

PYTHONPATH=`pwd`/src python src/zpdatafetch/primes.py -v -r <race_id>
  1. Build the project
build

or

uvx --from build pyproject-build --installer uv

To Do & Known Issues

While useful and usable, there's a bit that can be done to improve this package. Anyone interested to contribute is welcome to do so. These are the areas where I could use help:

  • Check if there are any objects not handled
  • Update the interface to allow alternate keyrings

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

zpdatafetch-1.3.2.tar.gz (37.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

zpdatafetch-1.3.2-py3-none-any.whl (38.6 kB view details)

Uploaded Python 3

File details

Details for the file zpdatafetch-1.3.2.tar.gz.

File metadata

  • Download URL: zpdatafetch-1.3.2.tar.gz
  • Upload date:
  • Size: 37.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for zpdatafetch-1.3.2.tar.gz
Algorithm Hash digest
SHA256 e783644e4604938e4a2d6fe65619affe8ebfc8ce852a522f97318a5f5639784c
MD5 9b522e3d900b5352163becb3c7f82981
BLAKE2b-256 2739b51f04067d794c8f760c24b102e8b06116549eba63326c56907e5e3dc4ac

See more details on using hashes here.

File details

Details for the file zpdatafetch-1.3.2-py3-none-any.whl.

File metadata

  • Download URL: zpdatafetch-1.3.2-py3-none-any.whl
  • Upload date:
  • Size: 38.6 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for zpdatafetch-1.3.2-py3-none-any.whl
Algorithm Hash digest
SHA256 7dfb966b9f8fe79c6c2e9b6ad149c6fb6c40c55c0796e5f81baeeadda03dfad5
MD5 997175bdbc6b9ab0856bde4e65f2b3e3
BLAKE2b-256 b388b7262e49606bb7fb7e232dd4651a94da04e9d138f6d09ce6e96ed0ecddc1

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page