pyfitparsernative
Fast FIT file parser using Rust, exposed to Python via PyO3.
Reads and writes Garmin FIT files using the rustyfit Rust crate. Roughly 5-10x faster than garmin-fit-sdk (Python) for reading, and ~20-25x faster for writing.
Both benchmarks below (tests/test_performance.py) use the same bundled real ride file (81km / 11,555 records, ~22.6k total messages across all message types) — read it once, then write the parsed result straight back out:
| Operation | garmin-fit-sdk (Python) | pyfitparsernative (Rust) | Speedup |
|---|---|---|---|
Read (parse_fit_file) |
~1.1-2.2s | ~0.1-0.2s | ~5-10x |
Write (write_fit_bytes) |
~2.0-2.7s | ~0.1s | ~20-25x |
Numbers vary by machine/container; both benchmarks measure through the full Python API (dict marshalling included), not raw Rust-only timings.
Installation
pip install pyfitparsernative
No Rust toolchain required — pre-built wheels are provided for Linux (glibc and musl), macOS (Intel and Apple Silicon), and Windows (x64). The macOS Intel (x86_64) wheel is cross-compiled and not tested in CI since GitHub no longer offers Intel macOS runners.
Usage
from pyfitparsernative import parse_fit_file, parse_fit_bytes, write_fit_file, write_fit_bytes
# Parse from a file path
messages = parse_fit_file("activity.fit")
# Parse from bytes
with open("activity.fit", "rb") as f:
messages = parse_fit_bytes(f.read())
Return type: list[dict[str, Any]]
Returns a flat list of message dicts in original FIT file order. Each dict includes a "message_type" key (e.g. "record", "session", "lap") alongside the field data. Timestamps are returned as naive ISO 8601 strings (YYYY-MM-DDTHH:MM:SS) — see "Timestamps and timezones" below for what timezone that string is actually in.
# Example: get average power from session message
session = [m for m in messages if m["message_type"] == "session"][0]
print(session["avg_power"]) # 160
print(session["max_power"]) # 489
# Example: iterate record (data) messages
for msg in messages:
if msg["message_type"] == "record":
print(msg["timestamp"], msg.get("power"), msg.get("heart_rate"))
Writing
write_fit_file/write_fit_bytes accept the same list[dict[str, Any]] shape that parse_fit_file/parse_fit_bytes return, so parsed messages can be modified and written back directly. Each dict must have a "message_type" key; field values use the same units/representation as the parser (naive ISO 8601 strings for timestamps, unscaled physical values for numeric fields). Input is validated up front: unrecognized field names, values outside a field's FIT type range (e.g. heart_rate: 300), and messages with no fields all raise ValueError, and a failed write_fit_file never leaves a partial file behind.
from pyfitparsernative import write_fit_file, write_fit_bytes
messages = [
{"message_type": "file_id", "type": "workout", "manufacturer": "garmin", "product": 0},
{"message_type": "workout", "sport": "running", "num_valid_steps": 1, "wkt_name": "Easy Run"},
{
"message_type": "workout_step",
"message_index": 0,
"duration_type": "time",
"duration_value": 1800000, # ms
"target_type": "speed",
"intensity": "active",
},
]
write_fit_file("workout.fit", messages)
data = write_fit_bytes(messages) # -> bytes
Timestamps and timezones
Timestamps are never converted to the timezone of the machine running this code — the FIT file itself decides what you get back, not where you happen to be running the parser. Two different kinds of timestamp field appear in FIT files, and pyfitparsernative decodes each the same way regardless of runtime timezone:
date_timefields (record.timestamp,session.timestamp,session.start_time,activity.timestamp, ...) are stored in the FIT file as UTC. pyfitparsernative returns that UTC value as a naive ISO string — it looks like a local time (no+00:00/Zsuffix) but the numbers are UTC.local_date_timefields (currently justactivity.local_timestamp) are stored by the recording device as its own local wall-clock time at the point of recording — the device bakes in whatever UTC offset it had configured, before writing the value. pyfitparsernative decodes this the same way it decodesdate_timefields (no additional offset applied), so what comes back is the activity's original recording-location time, unrelated to wherever the code runs.
Concretely, for the bundled sample file (recorded at a UTC+1 offset), decoded on a machine in any timezone:
record[0]["timestamp"] # "2026-01-31T13:41:14" <- UTC
activity[0]["timestamp"] # "2026-01-31T16:29:15" <- UTC (same instant as above, later in the ride)
activity[0]["local_timestamp"] # "2026-01-31T17:29:15" <- the recording device's own local time (UTC+1)
activity[0]["timestamp"] and activity[0]["local_timestamp"] describe the same instant; their difference (here, 1 hour) is the recording location's UTC offset at that time, as the device had it configured — pyfitparsernative doesn't compute or expose that offset itself, only the two raw values.
Differences from garmin-fit-sdk
pyfitparsernative returns the same field names and numeric values as garmin-fit-sdk, with the following differences:
- Timestamps — returned as naive ISO 8601 strings instead of timezone-aware
datetimeobjects (see "Timestamps and timezones" above for what timezone the string is actually in — it's never the runtime machine's). Exception: garmin-fit-sdk only applies this conversion todate_timefields, notlocal_date_timefields (e.g.activity.local_timestamp), which it leaves as a raw integer; pyfitparsernative converts both to ISO strings. - Enum fields — resolved to the same string labels as garmin-fit-sdk (e.g.
session["sport"] == "cycling", not2), using the same FIT SDK profile data. A raw integer with no defined label (an unassigned/reserved value, or a manufacturer-specific extension) is left as an int, matching garmin-fit-sdk's own fallback. Writing accepts either form back: the string label or the raw int. Two message types are exempt —field_descriptionanddeveloper_data_id— since their fields describe other fields' types (e.g.field_description.fit_base_type_id) rather than holding values of those types; garmin-fit-sdk leaves these as raw ints too. - Message types — no
_mesgssuffix (e.g."session"not"session_mesgs"). - Developer fields — garmin-fit-sdk uses integer dict keys for developer fields (e.g.
{61: 2554}); pyfitparsernative uses string keys ({"developer_field_61": 2554}). Native fields missing from the FIT profile use a separate"unknown_field_<N>"prefix, so the two numbering spaces never collide. When writing,developer_field_<N>keys are re-encoded as real developer fields; the message list must contain the definingfield_descriptionmessage (withdeveloper_data_index,field_definition_number, andfit_base_type_id) before the first message that uses the field — parsed files satisfy this automatically. - Component field expansion — garmin-fit-sdk derives
speedandaltitudefrom the more preciseenhanced_speed/enhanced_altitudefields when the base fields are absent from the message definition. pyfitparsernative applies the same expansion, so"speed"and"altitude"are always present inrecordmessages alongside theirenhanced_counterparts. - Sub-field expansion — not implemented. garmin-fit-sdk renames some fields based on a sibling field's value (e.g.
file_id's genericproductfield becomesgarmin_productwhenmanufacturer == garmin;event's genericdatafield becomesrider_positionfor certain event types). pyfitparsernative always exposes the generic field name with its raw/enum-resolved value; the sub-field name is not produced. - Unknown message types — message numbers not in rustyfit's generated FIT profile fall back to their raw numeric string (e.g.
"147") instead of a name.
Building from source
The docker/ directory contains scripts to build and test inside containers, avoiding the need for a local Rust toolchain. These are for local development only; CI/CD uses its own pipeline.
Build — compiles the wheel inside rust:1.93.1-trixie and writes it to dist/:
bash docker/docker-build.sh
Test — runs the test suite inside python:3.14.3-trixie against the wheel in dist/:
bash docker/docker-test.sh
On Windows, run these from Git Bash or WSL. Docker Desktop must be running with Linux containers enabled.
Releasing
Push a tag to trigger the CI/CD pipeline, which builds wheels for all platforms and publishes to PyPI:
git tag v0.1.0 && git push --tags
See .github/workflows/CI.yml for the full build matrix.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distributions
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 pyfitparsernative-0.2.2.tar.gz.
File metadata
- Download URL: pyfitparsernative-0.2.2.tar.gz
- Upload date:
- Size: 438.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
60ae1d5eb853e8bf538568390f5df977e60fac3cbb7ea2f09b7abf82eca94d67
|
|
| MD5 |
c99330b16e8d8c17a859d79f316580ff
|
|
| BLAKE2b-256 |
b147f24d8f68d58752de9fdc40ba8a1374352111de89ce15774f3318cf94dcff
|
Provenance
The following attestation bundles were made for pyfitparsernative-0.2.2.tar.gz:
Publisher:
CI.yml on GedRap/pyfitparsernative
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pyfitparsernative-0.2.2.tar.gz -
Subject digest:
60ae1d5eb853e8bf538568390f5df977e60fac3cbb7ea2f09b7abf82eca94d67 - Sigstore transparency entry: 2336614225
- Sigstore integration time:
-
Permalink:
GedRap/pyfitparsernative@b4ab84289b5ea8fe307bc45ccba0e65727c28871 -
Branch / Tag:
refs/tags/v0.2.2 - Owner: https://github.com/GedRap
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
CI.yml@b4ab84289b5ea8fe307bc45ccba0e65727c28871 -
Trigger Event:
push
-
Statement type:
File details
Details for the file pyfitparsernative-0.2.2-cp39-abi3-win_amd64.whl.
File metadata
- Download URL: pyfitparsernative-0.2.2-cp39-abi3-win_amd64.whl
- Upload date:
- Size: 308.9 kB
- Tags: CPython 3.9+, Windows x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
52b30bc0409f8191fba2d1d22d871b04176cdcaa1e7408f156c29d2257e5c8f7
|
|
| MD5 |
94617b29c9f6d20dc036dffb961a5ab1
|
|
| BLAKE2b-256 |
aa3b51405bcf9d8842ce47e2853339244e9e327e4e86bc72734fa6bb8b4d0705
|
Provenance
The following attestation bundles were made for pyfitparsernative-0.2.2-cp39-abi3-win_amd64.whl:
Publisher:
CI.yml on GedRap/pyfitparsernative
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pyfitparsernative-0.2.2-cp39-abi3-win_amd64.whl -
Subject digest:
52b30bc0409f8191fba2d1d22d871b04176cdcaa1e7408f156c29d2257e5c8f7 - Sigstore transparency entry: 2336614368
- Sigstore integration time:
-
Permalink:
GedRap/pyfitparsernative@b4ab84289b5ea8fe307bc45ccba0e65727c28871 -
Branch / Tag:
refs/tags/v0.2.2 - Owner: https://github.com/GedRap
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
CI.yml@b4ab84289b5ea8fe307bc45ccba0e65727c28871 -
Trigger Event:
push
-
Statement type:
File details
Details for the file pyfitparsernative-0.2.2-cp39-abi3-musllinux_1_2_x86_64.whl.
File metadata
- Download URL: pyfitparsernative-0.2.2-cp39-abi3-musllinux_1_2_x86_64.whl
- Upload date:
- Size: 633.4 kB
- Tags: CPython 3.9+, musllinux: musl 1.2+ x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
66aa6db87e79dac7730689e927498ce32a9d4640831aa1bdcfb05ff41a1cc920
|
|
| MD5 |
a62f5ff1cb0b591aa2e8a10b141c376a
|
|
| BLAKE2b-256 |
94265670e48a2699f32a7372591a3574f6ce22543f7df96f7131d08392233eba
|
Provenance
The following attestation bundles were made for pyfitparsernative-0.2.2-cp39-abi3-musllinux_1_2_x86_64.whl:
Publisher:
CI.yml on GedRap/pyfitparsernative
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pyfitparsernative-0.2.2-cp39-abi3-musllinux_1_2_x86_64.whl -
Subject digest:
66aa6db87e79dac7730689e927498ce32a9d4640831aa1bdcfb05ff41a1cc920 - Sigstore transparency entry: 2336614278
- Sigstore integration time:
-
Permalink:
GedRap/pyfitparsernative@b4ab84289b5ea8fe307bc45ccba0e65727c28871 -
Branch / Tag:
refs/tags/v0.2.2 - Owner: https://github.com/GedRap
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
CI.yml@b4ab84289b5ea8fe307bc45ccba0e65727c28871 -
Trigger Event:
push
-
Statement type:
File details
Details for the file pyfitparsernative-0.2.2-cp39-abi3-musllinux_1_2_aarch64.whl.
File metadata
- Download URL: pyfitparsernative-0.2.2-cp39-abi3-musllinux_1_2_aarch64.whl
- Upload date:
- Size: 585.6 kB
- Tags: CPython 3.9+, musllinux: musl 1.2+ ARM64
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d974ef9b637acea54cbe529d168a97675fab368a85512a607840612e67d44b12
|
|
| MD5 |
a49e98d4853e8cfbf3f6e7c7f0fb3964
|
|
| BLAKE2b-256 |
c3102dfa1f4ec1f74b082e88845e01336efd0df42c767c3fd6eb6eafab19e0d2
|
Provenance
The following attestation bundles were made for pyfitparsernative-0.2.2-cp39-abi3-musllinux_1_2_aarch64.whl:
Publisher:
CI.yml on GedRap/pyfitparsernative
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pyfitparsernative-0.2.2-cp39-abi3-musllinux_1_2_aarch64.whl -
Subject digest:
d974ef9b637acea54cbe529d168a97675fab368a85512a607840612e67d44b12 - Sigstore transparency entry: 2336614339
- Sigstore integration time:
-
Permalink:
GedRap/pyfitparsernative@b4ab84289b5ea8fe307bc45ccba0e65727c28871 -
Branch / Tag:
refs/tags/v0.2.2 - Owner: https://github.com/GedRap
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
CI.yml@b4ab84289b5ea8fe307bc45ccba0e65727c28871 -
Trigger Event:
push
-
Statement type:
File details
Details for the file pyfitparsernative-0.2.2-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.
File metadata
- Download URL: pyfitparsernative-0.2.2-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
- Upload date:
- Size: 429.6 kB
- Tags: CPython 3.9+, manylinux: glibc 2.17+ x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5eaea714e24e836185377f78ec9214a9f2d6f074ca7e795b37227bad7b5d1b10
|
|
| MD5 |
d12f7717224de7fa540bb4dc34089478
|
|
| BLAKE2b-256 |
6d5bfda64110b546c30c907b010645bb6bc7ce7636f12090ea23e0d7364f5fd2
|
Provenance
The following attestation bundles were made for pyfitparsernative-0.2.2-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl:
Publisher:
CI.yml on GedRap/pyfitparsernative
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pyfitparsernative-0.2.2-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl -
Subject digest:
5eaea714e24e836185377f78ec9214a9f2d6f074ca7e795b37227bad7b5d1b10 - Sigstore transparency entry: 2336614416
- Sigstore integration time:
-
Permalink:
GedRap/pyfitparsernative@b4ab84289b5ea8fe307bc45ccba0e65727c28871 -
Branch / Tag:
refs/tags/v0.2.2 - Owner: https://github.com/GedRap
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
CI.yml@b4ab84289b5ea8fe307bc45ccba0e65727c28871 -
Trigger Event:
push
-
Statement type:
File details
Details for the file pyfitparsernative-0.2.2-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl.
File metadata
- Download URL: pyfitparsernative-0.2.2-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
- Upload date:
- Size: 408.5 kB
- Tags: CPython 3.9+, manylinux: glibc 2.17+ ARM64
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
bb08fd6376bb8cce3abb3d85adea5a97955aab7eac70f118427729502bfcd69c
|
|
| MD5 |
0c804bb1a48ccb61bfd6783fd426bd99
|
|
| BLAKE2b-256 |
625b06a4ede976ac08aa9f87c3e0978ded08a1839eda8699df23b1a5b86d7164
|
Provenance
The following attestation bundles were made for pyfitparsernative-0.2.2-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl:
Publisher:
CI.yml on GedRap/pyfitparsernative
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pyfitparsernative-0.2.2-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl -
Subject digest:
bb08fd6376bb8cce3abb3d85adea5a97955aab7eac70f118427729502bfcd69c - Sigstore transparency entry: 2336614393
- Sigstore integration time:
-
Permalink:
GedRap/pyfitparsernative@b4ab84289b5ea8fe307bc45ccba0e65727c28871 -
Branch / Tag:
refs/tags/v0.2.2 - Owner: https://github.com/GedRap
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
CI.yml@b4ab84289b5ea8fe307bc45ccba0e65727c28871 -
Trigger Event:
push
-
Statement type:
File details
Details for the file pyfitparsernative-0.2.2-cp39-abi3-macosx_11_0_arm64.whl.
File metadata
- Download URL: pyfitparsernative-0.2.2-cp39-abi3-macosx_11_0_arm64.whl
- Upload date:
- Size: 371.0 kB
- Tags: CPython 3.9+, macOS 11.0+ ARM64
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
598ee6e5fb2f3c74964bfb4adc81018a3a3592e38caddbd2c6cf5aa90d8a0532
|
|
| MD5 |
324f3c3d4151b78c96a32f87b96bee4a
|
|
| BLAKE2b-256 |
9b910ce64be925ca5f22e7f8aa57d6a5c355b44e0d931bf32e4f6504300b5ba4
|
Provenance
The following attestation bundles were made for pyfitparsernative-0.2.2-cp39-abi3-macosx_11_0_arm64.whl:
Publisher:
CI.yml on GedRap/pyfitparsernative
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pyfitparsernative-0.2.2-cp39-abi3-macosx_11_0_arm64.whl -
Subject digest:
598ee6e5fb2f3c74964bfb4adc81018a3a3592e38caddbd2c6cf5aa90d8a0532 - Sigstore transparency entry: 2336614251
- Sigstore integration time:
-
Permalink:
GedRap/pyfitparsernative@b4ab84289b5ea8fe307bc45ccba0e65727c28871 -
Branch / Tag:
refs/tags/v0.2.2 - Owner: https://github.com/GedRap
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
CI.yml@b4ab84289b5ea8fe307bc45ccba0e65727c28871 -
Trigger Event:
push
-
Statement type:
File details
Details for the file pyfitparsernative-0.2.2-cp39-abi3-macosx_10_12_x86_64.whl.
File metadata
- Download URL: pyfitparsernative-0.2.2-cp39-abi3-macosx_10_12_x86_64.whl
- Upload date:
- Size: 377.8 kB
- Tags: CPython 3.9+, macOS 10.12+ x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0bc80176e9ff7383b47799175ff122c91266aeef09e21f4cea41e28abc89d5a1
|
|
| MD5 |
217bf64192b1e69ad6ffd0167e8ed15e
|
|
| BLAKE2b-256 |
414a651335b9367af25a6c4d4887da1f3653b5f3e9417d51a90562ead4e4814e
|
Provenance
The following attestation bundles were made for pyfitparsernative-0.2.2-cp39-abi3-macosx_10_12_x86_64.whl:
Publisher:
CI.yml on GedRap/pyfitparsernative
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pyfitparsernative-0.2.2-cp39-abi3-macosx_10_12_x86_64.whl -
Subject digest:
0bc80176e9ff7383b47799175ff122c91266aeef09e21f4cea41e28abc89d5a1 - Sigstore transparency entry: 2336614302
- Sigstore integration time:
-
Permalink:
GedRap/pyfitparsernative@b4ab84289b5ea8fe307bc45ccba0e65727c28871 -
Branch / Tag:
refs/tags/v0.2.2 - Owner: https://github.com/GedRap
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
CI.yml@b4ab84289b5ea8fe307bc45ccba0e65727c28871 -
Trigger Event:
push
-
Statement type: