Skip to main content

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_time fields (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/Z suffix) but the numbers are UTC.
  • local_date_time fields (currently just activity.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 decodes date_time fields (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 datetime objects (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 to date_time fields, not local_date_time fields (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", not 2), 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_description and developer_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 _mesgs suffix (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 defining field_description message (with developer_data_index, field_definition_number, and fit_base_type_id) before the first message that uses the field — parsed files satisfy this automatically.
  • Component field expansion — garmin-fit-sdk derives speed and altitude from the more precise enhanced_speed/enhanced_altitude fields when the base fields are absent from the message definition. pyfitparsernative applies the same expansion, so "speed" and "altitude" are always present in record messages alongside their enhanced_ counterparts.
  • Sub-field expansion — not implemented. garmin-fit-sdk renames some fields based on a sibling field's value (e.g. file_id's generic product field becomes garmin_product when manufacturer == garmin; event's generic data field becomes rider_position for 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.

Metadata

Release files for pyfitparsernative 0.2.3

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for pyfitparsernative 0.2.3
File Size Uploaded Provenance Actions
pyfitparsernative-0.2.3.tar.gz 461.1 kB With provenance Details for pyfitparsernative-0.2.3.tar.gz

Built distributions (wheels)

Table of built distributions (wheels) for pyfitparsernative 0.2.3
File Actions
pyfitparsernative-0.2.3-cp39-abi3-win_amd64.whl CPython 3.9 abi3 Windows x86-64 Details for pyfitparsernative-0.2.3-cp39-abi3-win_amd64.whl
pyfitparsernative-0.2.3-cp39-abi3-musllinux_1_2_x86_64.whl CPython 3.9 abi3 Linux musl 1.2+ x86-64 Details for pyfitparsernative-0.2.3-cp39-abi3-musllinux_1_2_x86_64.whl
pyfitparsernative-0.2.3-cp39-abi3-musllinux_1_2_aarch64.whl CPython 3.9 abi3 Linux musl 1.2+ ARM64 Details for pyfitparsernative-0.2.3-cp39-abi3-musllinux_1_2_aarch64.whl
pyfitparsernative-0.2.3-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl CPython 3.9 abi3 Linux glibc 2.17+ x86-64 Details for pyfitparsernative-0.2.3-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
pyfitparsernative-0.2.3-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl CPython 3.9 abi3 Linux glibc 2.17+ ARM64 Details for pyfitparsernative-0.2.3-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
pyfitparsernative-0.2.3-cp39-abi3-macosx_11_0_arm64.whl CPython 3.9 abi3 macOS 11.0+ ARM64 Details for pyfitparsernative-0.2.3-cp39-abi3-macosx_11_0_arm64.whl
pyfitparsernative-0.2.3-cp39-abi3-macosx_10_12_x86_64.whl CPython 3.9 abi3 macOS 10.12+ x86-64 Details for pyfitparsernative-0.2.3-cp39-abi3-macosx_10_12_x86_64.whl

Total release size: 3.6 MB

Release files / pyfitparsernative-0.2.3.tar.gz

Download URL pyfitparsernative-0.2.3.tar.gz
Size 461.1 kB
Tags Source
SHA-256 checksum
How to use checksums
2b314631f490c761851dd61a0d05d4021f4377bcb7abc4549f1299a4c9c525aa
BLAKE2b-256 checksum
How to use checksums
d7f7bb19303da9fac1cb9264d23a5789749436e547bcf940c7f30839b7c44ef9
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 4, 2026.

Transparency log

Release files / pyfitparsernative-0.2.3-cp39-abi3-win_amd64.whl

Download URL pyfitparsernative-0.2.3-cp39-abi3-win_amd64.whl
Size 309.4 kB
Tags CPython 3.9 Windows x86-64 abi3
SHA-256 checksum
How to use checksums
8d8913d36fc67e0a26a4a4a365339e7aa76ae0a4d69dda7fe5fe5d746d312cc3
BLAKE2b-256 checksum
How to use checksums
a8a86285657cb52da971d16c68c06bedf684b102f25803c17920cd38a7abe9fe
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 4, 2026.

Transparency log

Release files / pyfitparsernative-0.2.3-cp39-abi3-musllinux_1_2_x86_64.whl

Download URL pyfitparsernative-0.2.3-cp39-abi3-musllinux_1_2_x86_64.whl
Size 633.5 kB
Tags CPython 3.9 Linux musl 1.2+ x86-64 abi3
SHA-256 checksum
How to use checksums
b98961a12bd6dadc1b163ff69b802f7bed28c6830a1b8c19c7a2b3aa98266d66
BLAKE2b-256 checksum
How to use checksums
7522b2f11b07da6dbbfc19a9931f62731de1390581b1433fa26e315d9655204c
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 4, 2026.

Transparency log

Release files / pyfitparsernative-0.2.3-cp39-abi3-musllinux_1_2_aarch64.whl

Download URL pyfitparsernative-0.2.3-cp39-abi3-musllinux_1_2_aarch64.whl
Size 585.2 kB
Tags CPython 3.9 Linux musl 1.2+ ARM64 abi3
SHA-256 checksum
How to use checksums
ab07540428b1f8b3341cc72e7674d5338055ea9725d630336ad44e4ed9a8ffd6
BLAKE2b-256 checksum
How to use checksums
d8f03a697f923982008fa057cc69d9f4f61c66b212045699a83f5e7f0e45beb6
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 4, 2026.

Transparency log

Release files / pyfitparsernative-0.2.3-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl

Download URL pyfitparsernative-0.2.3-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Size 429.8 kB
Tags CPython 3.9 Linux glibc 2.17+ x86-64 abi3
SHA-256 checksum
How to use checksums
ef034cc67a565eb2ac47ed966f9826f208f4d022bc735650765d1934c45b9a12
BLAKE2b-256 checksum
How to use checksums
ef67fe918c9ab929896e5b90454c09557a39a72a926fa4682980646080b7fd56
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 4, 2026.

Transparency log

Release files / pyfitparsernative-0.2.3-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl

Download URL pyfitparsernative-0.2.3-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Size 408.5 kB
Tags CPython 3.9 Linux glibc 2.17+ ARM64 abi3
SHA-256 checksum
How to use checksums
843a78cfb4c8715ddec142b4461eb823050583e28c26129ee03e22ec1077deb3
BLAKE2b-256 checksum
How to use checksums
f5b4a9d2eeab749f57c841819ad6446d4a55d56a268cfd4b44d2e89d1324a249
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 4, 2026.

Transparency log

Release files / pyfitparsernative-0.2.3-cp39-abi3-macosx_11_0_arm64.whl

Download URL pyfitparsernative-0.2.3-cp39-abi3-macosx_11_0_arm64.whl
Size 371.0 kB
Tags CPython 3.9 abi3 macOS 11.0+ ARM64
SHA-256 checksum
How to use checksums
a99acd6f10a2973c4e6ad007cae01b1e85926c71df69e12c9ec9cf380417c2cd
BLAKE2b-256 checksum
How to use checksums
9c1755bef9f4c85af0e3fa557cd0de9deb7635d29f954f6ca47e3a1feb726ce4
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 4, 2026.

Transparency log

Release files / pyfitparsernative-0.2.3-cp39-abi3-macosx_10_12_x86_64.whl

Download URL pyfitparsernative-0.2.3-cp39-abi3-macosx_10_12_x86_64.whl
Size 378.0 kB
Tags CPython 3.9 abi3 macOS 10.12+ x86-64
SHA-256 checksum
How to use checksums
dc103433021d60049c47aefb5f8056f7863e57b15c8c32f3ab5f9ef043874f9e
BLAKE2b-256 checksum
How to use checksums
ce2639221da87f97bcf57fb68f3142db62473b986315a95a675d05ff9a801175
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 4, 2026.

Transparency log

Security for pyfitparsernative 0.2.3

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 8 files in this release have been uploaded with provenance metadata.

Select from 8 files

Attestation for pyfitparsernative-0.2.3.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 4, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page