A modern Python client for the Concept2 Logbook API.
Project description
pyconcept2
pyconcept2 is a modern Python client for the Concept2 Logbook API.
The project is intentionally small for now: it provides a typed client, clean exceptions, and pydantic models that can grow with the API surface.
Features
- Read the authenticated athlete profile.
- Read workouts/results, including paginated access and all-page convenience.
- Fetch workout details, stroke data, and file exports.
- Derive custom time splits from stroke data.
- Read Concept2 Logbook challenges without an access token.
- Use typed pydantic models while tolerating new API fields.
Installation
pip install pyconcept2
For local development:
pip install -e ".[dev]"
Authentication
Authenticated Logbook endpoints require a Concept2 access token:
from pyconcept2 import Concept2Client
client = Concept2Client(access_token="...")
Or load the token from CONCEPT2_ACCESS_TOKEN:
from pyconcept2 import Concept2Client
client = Concept2Client.from_env()
For read-only workout access, the token needs the Concept2 results:read scope.
Profile access needs user:read. Logbook challenge endpoints are public and can
be used without a token:
client = Concept2Client()
Usage
from pyconcept2 import Concept2Client
client = Concept2Client(access_token="...")
profile = client.get_profile()
print(profile.username)
Fetch workouts:
from pyconcept2 import Concept2Client
client = Concept2Client(access_token="...")
workouts = client.get_workouts(newest=10)
for workout in workouts:
print(
workout.date,
workout.machine_type,
workout.distance,
workout.time_formatted,
workout.calories,
)
Filter workouts:
workouts = client.get_workouts(
from_date="2026-01-01",
to_date="2026-01-31",
workout_type="rower",
number=100,
)
Fetch every available page:
workouts = client.get_workouts(all_pages=True, number=250)
Fetch a page with pagination metadata:
page = client.get_workouts_page(page=1, number=25)
print(page.pagination.total if page.pagination else None)
for workout in page.items:
print(workout.id, workout.distance)
Fetch workout details:
workout = client.get_workout(12345, include=["strokes", "metadata", "user"])
print(workout.id)
print(workout.calories)
print(workout.strokes[0].spm if workout.strokes else None)
if workout.details:
for split in workout.details.splits:
print(split.time, split.distance, split.calories, split.stroke_rate)
for interval in workout.details.intervals:
print(interval.type, interval.time, interval.distance)
Fetch stroke data:
strokes = client.get_strokes(12345)
for stroke in strokes:
print(stroke.t, stroke.d, stroke.spm, stroke.hr)
Derive 2-minute splits from stroke data:
splits = client.get_time_splits(12345, split_time=1200)
for split in splits:
print(
split.time_total_formatted,
split.distance_total,
split.time_formatted,
split.distance,
split.pace_formatted,
split.stroke_rate,
)
split.pace is the raw value in tenths of a second per 500m for RowErg and
SkiErg workouts. Use split.pace_formatted for display, e.g. 2:38.7.
Export a workout file:
fit_file = client.export_workout(12345, "fit")
csv_file = client.export_workout(12345, "csv")
tcx_file = client.export_workout(12345, "tcx")
Summarize workouts:
summary = client.get_workout_summary(workout_type="rower")
print(summary.count)
print(summary.distance)
print(summary.time)
print(summary.calories)
The client can also be used as a context manager:
from pyconcept2 import Concept2Client
with Concept2Client(access_token="...") as client:
profile = client.get_profile()
Logbook challenge endpoints do not require an access token:
from pyconcept2 import Concept2Client
client = Concept2Client()
challenges = client.get_challenges(number=25)
current = client.get_current_challenges()
upcoming = client.get_upcoming_challenges(days=60)
recent = client.get_recent_challenges(days=60)
season = client.get_challenges_for_season(2026)
events = client.get_events_for_year(2026)
API Overview
Authenticated methods:
get_profile()get_workouts(...)get_workouts_page(...)get_workout(result_id, include=None)get_strokes(result_id)get_time_splits(result_id, split_time=1200)export_workout(result_id, export_type="csv")get_workout_summary(...)
Public challenge methods:
get_challenges(...)get_current_challenges()get_upcoming_challenges(days=None)get_recent_challenges(days=None)get_challenges_for_season(season)get_events_for_year(year)
Units
Concept2 uses a few compact numeric units:
- Workout and split
timevalues are tenths of a second. - Stroke
tvalues are tenths of a second. - Stroke
dvalues are decimeters. paceis tenths of a second per 500m for RowErg and SkiErg workouts.
For display, use fields such as pace_formatted where available:
splits = client.get_time_splits(12345)
for split in splits:
print(split.pace, split.pace_formatted)
Development
PYTHONPATH=src pytest
PYTHONPATH=src ruff check src tests
PYTHONPATH=src mypy src
Status
This package is pre-alpha and intentionally read-only. OAuth helpers and workout write support are out of scope for now.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file pyconcept2-0.1.0.tar.gz.
File metadata
- Download URL: pyconcept2-0.1.0.tar.gz
- Upload date:
- Size: 12.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
2159e87d3254582dad13df14908f0c3f14dff2570858ac0b0049e3a575df8ae9
|
|
| MD5 |
2d9cab808bd21b4bc32fd72f15f8f6fb
|
|
| BLAKE2b-256 |
4c7fd1fe60f693df8e4280c8b5b319a6c2012168ae7f573ce81453b20b775ea6
|
Provenance
The following attestation bundles were made for pyconcept2-0.1.0.tar.gz:
Publisher:
publish.yml on gickowtf/pyconcept2
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pyconcept2-0.1.0.tar.gz -
Subject digest:
2159e87d3254582dad13df14908f0c3f14dff2570858ac0b0049e3a575df8ae9 - Sigstore transparency entry: 2118828897
- Sigstore integration time:
-
Permalink:
gickowtf/pyconcept2@90010fb01c1911dc4a108aa5b19b86ee4f7105e0 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/gickowtf
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@90010fb01c1911dc4a108aa5b19b86ee4f7105e0 -
Trigger Event:
release
-
Statement type:
File details
Details for the file pyconcept2-0.1.0-py3-none-any.whl.
File metadata
- Download URL: pyconcept2-0.1.0-py3-none-any.whl
- Upload date:
- Size: 10.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
2498c45055b53a4c30f02ba43606c622f9803a17bc835f7fc8cd787295bd7ce1
|
|
| MD5 |
d90a21dc04c7cca4aa31f45f6ae91a50
|
|
| BLAKE2b-256 |
7ca50162a30a3f6d954e348000f45dd45b5d4cedabbca140cc6c3e9001804e6d
|
Provenance
The following attestation bundles were made for pyconcept2-0.1.0-py3-none-any.whl:
Publisher:
publish.yml on gickowtf/pyconcept2
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pyconcept2-0.1.0-py3-none-any.whl -
Subject digest:
2498c45055b53a4c30f02ba43606c622f9803a17bc835f7fc8cd787295bd7ce1 - Sigstore transparency entry: 2118830143
- Sigstore integration time:
-
Permalink:
gickowtf/pyconcept2@90010fb01c1911dc4a108aa5b19b86ee4f7105e0 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/gickowtf
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@90010fb01c1911dc4a108aa5b19b86ee4f7105e0 -
Trigger Event:
release
-
Statement type: