Skip to main content

A Python client for the RideWithGPS API. Not affiliated with RideWithGPS.

Project description

pyrwgps

A simple Python client for the RideWithGPS API.

This project is not affiliated with or endorsed by RideWithGPS.

Note: This client is not yet feature-complete. Read the code before you use it and report any bugs you find.

API versions: RideWithGPS has a v1 API (/api/v1/...) and a legacy API (no version prefix). This client supports both, but v1 coverage is incomplete. See API Versions for a full breakdown of what uses v1 vs legacy.

Authentication: Two methods are supported via the same RideWithGPS class: API key and OAuth 2.0. See Authentication for details.

PyPI version PyPI downloads License: MIT Python versions

black flake8 mypy pylint pytest

Features

  • Single RideWithGPS client supporting both API key and OAuth 2.0 authentication.
  • Makes any API request — get, put, post, patch, delete — to v1 or legacy endpoints.
  • Built-in rate limiting, caching, and pagination.
  • Use higher-level abstractions like list to iterate collections with automatic pagination.

API Versions

RideWithGPS has a v1 API (/api/v1/...) and a legacy API (no version prefix). This client supports both. The v1 API is preferred where available, but several things are not yet in v1 and still require the legacy API.

Uses v1 API (/api/v1/...)

Resource Operations
Authentication Create token (email + password + API key, no OAuth needed)
Trips List, get single, delete, get polyline
Routes List, get single, delete, get polyline
Collections List, get single, get pinned
Events List, create, get single, update, delete
Club Members List, get single, update
Points of Interest List, create, get single, update, delete, associate/disassociate with route
Sync Get items for sync
Users Get current user

For list over a v1 endpoint, pass result_key matching the response root key (e.g. result_key="trips"). See v1 API usage for examples.

Still uses legacy API (no version prefix)

These are not available on v1 and require the legacy endpoints:

Resource / Feature Legacy endpoint Reason
Gear /users/{user_id}/gear.json No v1 gear endpoint exists
Create trip /trips.json (POST) v1 trips is read/delete only
Update trip /trips/{id}.json (PUT/PATCH) v1 trips is read/delete only
Create route /routes.json (POST) v1 routes is read/delete only
Update route /routes/{id}.json (PUT/PATCH) v1 routes is read/delete only
Download trip file /trips/{id}.gpx, .tcx, .kml No v1 file-download endpoint exists

v1 API

v1 endpoints live under /api/v1/ and use page/page_size pagination, returning results under a named root key (e.g. {"trips": [...], "meta": {...}}).

All of list, get, put, post, patch, and delete work with v1 paths. For list, pass result_key matching the response root key:

# List all trips (v1)
for trip in client.list("/api/v1/trips.json", result_key="trips"):
    print(trip.name, trip.id)

# Download the TCX file for the most recent trip (legacy API)
most_recent = next(client.list("/api/v1/trips.json", result_key="trips", limit=1))
with open(f"trip_{most_recent.id}.tcx", "wb") as f:
    f.write(client.download_trip_file(most_recent.id, "tcx"))

# List routes, up to 50 (v1)
for route in client.list("/api/v1/routes.json", result_key="routes", limit=50):
    print(route.name, route.id)

# Get a single resource (v1)
route = client.get(path="/api/v1/routes/123456.json")
print(route.route.name)

# Get the authenticated user's pinned collection (v1)
pinned = client.get(path="/api/v1/collections/pinned.json")

# Create an event (v1)
event = client.post(
    path="/api/v1/events.json",
    params={"name": "My Gran Fondo", "start_date": "2026-06-01"},
)

Authentication

RideWithGPS supports two authentication methods. Pass the appropriate credentials at init time.

API key (shared secret)

Simple option for scripts or server-side access to your own account.

from pyrwgps import RideWithGPS

client = RideWithGPS(apikey="yourapikey")
user = client.authenticate(email="your@email.com", password="yourpassword")

OAuth 2.0

For applications acting on behalf of users without handling their password. Requires a registered API client — see RideWithGPS authentication docs.

Full authorization code flow:

from pyrwgps import RideWithGPS

client = RideWithGPS(client_id="your_client_id", client_secret="your_client_secret")

# Step 1: redirect the user to this URL
auth_url = client.authorization_url(redirect_uri="https://yourapp.com/callback")
print(f"Redirect user to: {auth_url}")

# Step 2: after the user grants access, exchange the code from ?code=...
client.exchange_code(code="code_from_redirect", redirect_uri="https://yourapp.com/callback")

# Step 3: make authenticated API calls
user = client.get(path="/api/v1/users/current.json")
print(user.user.display_name)

With an existing access token:

client = RideWithGPS(
    client_id="your_client_id",
    client_secret="your_client_secret",
    access_token="your_stored_token",
)
user = client.get(path="/api/v1/users/current.json")

Both auth methods expose the same get, put, post, patch, delete, list, and download_trip_file methods.


Installation

The package is published on PyPI.


Usage

First, install the package:

pip install pyrwgps

Then, in your Python code (API key example — see Authentication for both methods):

from pyrwgps import RideWithGPS

# Initialize client and authenticate, with optional in-memory GET cache enabled
client = RideWithGPS(apikey="yourapikey", cache=True)
user_info = client.authenticate(email="your@email.com", password="yourpassword")

print(user_info.id, user_info.display_name)

# Update the name of a trip (legacy API — trip update not yet in v1)
activity_id = "123456"
new_name = "Morning Ride"
response = client.put(
    path=f"/trips/{activity_id}.json",
    params={"name": new_name}
)
updated_name = response.trip.name if hasattr(response, "trip") else None
if updated_name == new_name:
    print(f"Activity name updated to: {updated_name}")
else:
    print("Failed to update activity name.")

# We changed something, so probably should clear the cache.
client.clear_cache()

# Simple GET: fetch a single trip via the v1 API
trip = client.get(path="/api/v1/trips/123456.json")
print(trip.trip.name, trip.trip.id)

# Automatically paginate: List up to 25 trips via the v1 API
for trip in client.list(
    path="/api/v1/trips.json",
    result_key="trips",
    limit=25,
):
    print(trip.name, trip.id)

# Automatically paginate: List all routes via the v1 API
for route in client.list(
    path="/api/v1/routes.json",
    result_key="routes",
):
    print(route.name, route.id)

# Get the authenticated user's pinned collection (v1)
pinned = client.get(path="/api/v1/collections/pinned.json")
print(pinned)

# Automatically paginate: List all gear for this user (legacy API — no v1 gear endpoint yet)
gear = {}
for g in client.list(
    path=f"/users/{user_info.id}/gear.json",
    params={},
):
    gear[g.id] = g.nickname
print(gear)

# Download a trip as a GPX file (legacy API — no v1 file-download endpoint yet)
with open("trip_123.gpx", "wb") as f:
    f.write(client.download_trip_file(123456, "gpx"))

# TCX and KML formats are also supported
tcx_bytes = client.download_trip_file(123456, "tcx")
kml_bytes = client.download_trip_file(123456, "kml")

Note:

  • All API responses are automatically converted from JSON to Python objects with attribute access.
  • You must provide your own RideWithGPS credentials and API key.
  • Use v1 endpoints (/api/v1/...) for trips, routes, collections, events, club members, points of interest, and sync. See the v1 API section for what is and isn't available.
  • The list, get, put, post, patch, delete, and download_trip_file methods are the recommended interface for making API requests; see the code and RideWithGPS API docs for available endpoints and parameters.
  • download_trip_file(trip_id, file_format) returns raw bytes (not a Python object); write directly to a file or process as needed. Supported formats: "gpx", "tcx", "kml".

Development

Set up environment

If you use this as VS Dev Container, you can skip using a venv.

python3 -m venv env
source env/bin/activate
pip install .[dev]

Or, for local development with editable install:

git clone https://github.com/ckdake/pyrwgps.git
cd pyrwgps
pip install -e '.[dev]'

Run tests

python -m pytest --cov=pyrwgps --cov-report=term-missing -v

Run an example

python3 scripts/example.py

Linting and Formatting

Run these tools locally to check and format your code:

  • pylint (static code analysis):

    pylint pyrwgps
    
  • flake8 (style and lint checks):

    flake8 pyrwgps
    
  • black (auto-formatting):

    black pyrwgps
    
  • mypy (type checking):

    mypy pyrwgps
    

Updating Integration Cassettes

If you need to update the VCR cassettes for integration tests:

  1. Set required environment variables:

    • RIDEWITHGPS_EMAIL
    • RIDEWITHGPS_PASSWORD
    • RIDEWITHGPS_KEY

    Example:

    export RIDEWITHGPS_EMAIL=your@email.com
    export RIDEWITHGPS_PASSWORD=yourpassword
    export RIDEWITHGPS_KEY=yourapikey
    
  2. Delete all existing cassettes (including backups) and re-record:

    rm tests/cassettes/*.yaml tests/cassettes/*.yaml.original
    python -m pytest --cov=pyrwgps --cov-report=term-missing -v
    
  3. Scrub sensitive data from the cassette:

    python scripts/scrub_cassettes.py
    
    • This will back up your cassettes to *.yaml.original (if not already present).
    • The sanitized cassettes will overwrite *.yaml.
  4. Re-run tests to verify:

    python -m pytest --cov=pyrwgps --cov-report=term-missing -v
    

Publishing a Release

To publish a new version of this package:

  1. Update the version number
    Edit pyproject.toml and increment the version. Add a new entry to CHANGELOG.md.

  2. Commit and push

    git add pyproject.toml CHANGELOG.md
    git commit -m "Release vX.Y.Z"
    git push
    
  3. Install build tools

    pip install .[dev]
    
  4. Build the distribution

    python -m build
    

    This will create dist/pyrwgps-<version>.tar.gz and .whl files.

  5. Check the distribution (optional but recommended)

    twine check dist/*
    
  6. Upload to PyPI

    twine upload dist/*
    

    You will be prompted for your PyPI API key.

  7. Create a GitHub Release
    Go to github.com/ckdake/pyrwgps/releases/new, create a tag (e.g. vX.Y.Z) pointing at main, use the version as the title, and paste the relevant section from CHANGELOG.md as the description. This populates the Releases sidebar on the repo and gives users a permanent URL for the release notes.

  8. Open your package on PyPI (optional)

    $BROWSER https://pypi.org/project/pyrwgps/
    

Note:

  • Configure your ~/.pypirc if you want to avoid entering credentials each time.
  • For test uploads, use twine upload --repository testpypi dist/* and visit TestPyPI.


License

MIT License


This project is not affiliated with or endorsed by RideWithGPS.

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

pyrwgps-0.2.1.tar.gz (11.6 kB view details)

Uploaded Source

Built Distribution

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

pyrwgps-0.2.1-py3-none-any.whl (13.5 kB view details)

Uploaded Python 3

File details

Details for the file pyrwgps-0.2.1.tar.gz.

File metadata

  • Download URL: pyrwgps-0.2.1.tar.gz
  • Upload date:
  • Size: 11.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.11

File hashes

Hashes for pyrwgps-0.2.1.tar.gz
Algorithm Hash digest
SHA256 4aa1d58b2d15e54881a829cc0e72bd059a8aa7a83feba704a2b52b67a282e015
MD5 900071486103d25189b76f38d66f9a24
BLAKE2b-256 cdbbf34b51d0c4ad1cce29ed05cd7f507f977e949cde06aed625e98f7aa46468

See more details on using hashes here.

File details

Details for the file pyrwgps-0.2.1-py3-none-any.whl.

File metadata

  • Download URL: pyrwgps-0.2.1-py3-none-any.whl
  • Upload date:
  • Size: 13.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.11

File hashes

Hashes for pyrwgps-0.2.1-py3-none-any.whl
Algorithm Hash digest
SHA256 b4624c2699e5b3344641644e26da63e3771b1f674d6214278f74005cf4d9f3ab
MD5 863803c59b0873d8c1dffa3b5c072155
BLAKE2b-256 cdc9dd0c4712b55dcfddb166a9f7117c044ad1a0bb0dcaea3feb25835829d8f8

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