Metadata-Version: 2.4
Name: pyconcept2
Version: 0.1.0
Summary: A modern Python client for the Concept2 Logbook API.
Project-URL: Homepage, https://github.com/gickowtf/pyconcept2
Project-URL: Issues, https://github.com/gickowtf/pyconcept2/issues
Author: pyconcept2 contributors
License: MIT
Keywords: api,concept2,erg,logbook,rowing
Classifier: Development Status :: 2 - Pre-Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Typing :: Typed
Requires-Python: >=3.11
Requires-Dist: httpx<1.0,>=0.27
Requires-Dist: pydantic<3.0,>=2.7
Provides-Extra: dev
Requires-Dist: mypy>=1.10; extra == 'dev'
Requires-Dist: pytest>=8.0; extra == 'dev'
Requires-Dist: respx>=0.21; extra == 'dev'
Requires-Dist: ruff>=0.6; extra == 'dev'
Description-Content-Type: text/markdown

# 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

```bash
pip install pyconcept2
```

For local development:

```bash
pip install -e ".[dev]"
```

## Authentication

Authenticated Logbook endpoints require a Concept2 access token:

```python
from pyconcept2 import Concept2Client

client = Concept2Client(access_token="...")
```

Or load the token from `CONCEPT2_ACCESS_TOKEN`:

```python
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:

```python
client = Concept2Client()
```

## Usage

```python
from pyconcept2 import Concept2Client

client = Concept2Client(access_token="...")
profile = client.get_profile()

print(profile.username)
```

Fetch workouts:

```python
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:

```python
workouts = client.get_workouts(
    from_date="2026-01-01",
    to_date="2026-01-31",
    workout_type="rower",
    number=100,
)
```

Fetch every available page:

```python
workouts = client.get_workouts(all_pages=True, number=250)
```

Fetch a page with pagination metadata:

```python
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:

```python
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:

```python
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:

```python
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:

```python
fit_file = client.export_workout(12345, "fit")
csv_file = client.export_workout(12345, "csv")
tcx_file = client.export_workout(12345, "tcx")
```

Summarize workouts:

```python
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:

```python
from pyconcept2 import Concept2Client

with Concept2Client(access_token="...") as client:
    profile = client.get_profile()
```

Logbook challenge endpoints do not require an access token:

```python
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 `time` values are tenths of a second.
- Stroke `t` values are tenths of a second.
- Stroke `d` values are decimeters.
- `pace` is tenths of a second per 500m for RowErg and SkiErg workouts.

For display, use fields such as `pace_formatted` where available:

```python
splits = client.get_time_splits(12345)

for split in splits:
    print(split.pace, split.pace_formatted)
```

## Development

```bash
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.
