GraFIT
Loads Garmin FIT files into a SQLite database and visualizes them in Grafana.
GraFIT parses .fit files (activities, sleep, GPS, etc.) using python-fitparse, writes them to SQLite using a schema generated from the FIT profile, and ships with Grafana provisioning (datasource + dashboards) so you can browse the data immediately.
Install
uv tool install grafit
Or run without installing:
uvx grafit <command> [options]
Requires Python 3.11+.
Usage
uv run grafit <command> [options]
Commands
init <db_path>— create and initialize the SQLite database.import <path> <db_path>— import a FIT file or directory of FIT files into the database.--forcereimport files already present in the database.--no-recursivedo not recurse into subdirectories.--fetch-weatherlook up historical hourly weather (temperature, apparent temperature, humidity) for each session's start coordinates and date from the Open-Meteo archive. Failures are logged and skipped — the FIT import itself never fails because of weather.
backfill-weather <db_path>— fetch weather for sessions already imported without--fetch-weather. Safe to re-run: only sessions missing weather are queried; rate-limited or failed sessions are picked up on the next run.--forcere-fetch weather even for sessions that already have it.
stats <db_path>— show row counts per table.purge <db_path> --file-hash <sha256>— remove all rows imported from a given file.generate-schema <output>— write the full DDL SQL to a file.install-provisioning <dest>— copy the bundled Grafana provisioning (datasource + dashboards) to a directory.--forceoverwrite the destination if it exists.
demo-data <dest>— generate a synthetic Garmin FIT tree (see Demo data).--daysdays of history (default 365),--end-date YYYY-MM-DDlast day (default yesterday),--seedrandom seed,--forcewrite into a non-empty directory.
demo-weather <db_path>— fillweather_hourlywith modelled Los Angeles weather for demo data, without calling Open-Meteo.
Global flag: -v / --verbose for debug logging.
Upgrading
The schema is generated from the FIT profile, so upgrading GraFIT (or its python-fitparse dependency) can add new fields. On the next import/init, missing columns are added to existing tables automatically (you'll see a Schema: added N new column(s) notice). New columns are NULL for already-imported rows — to backfill history, re-import the affected files with --force or rebuild the database from your FIT source.
Monitor files imported by older versions have no timestamps on the 24/7 heart rate and intraday step samples; import reports how many such rows remain. Re-import them with grafit import <GARMIN>/Monitor <db_path> --force.
Grafana
A docker-compose.yml is provided to run Grafana with the SQLite datasource plugin against ./fit_data.db and the bundled dashboards mounted from ./grafana-provisioning.
Note: the bundled compose file enables anonymous admin access and disables the login form. It is intended for local use only — do not expose it to a network.
Drop your FIT files into ./GARMIN and bring it up:
docker compose up
A grafit-init container installs the bundled provisioning, runs grafit init, and runs grafit import ./GARMIN against shared volumes; Grafana waits for it to finish before starting. Re-running docker compose up reuses the database — already-imported files are skipped by hash.
Grafana is then available at http://localhost:3000.
Demo data
For demo/testing purposes, GraFIT can generate a synthetic dataset for a fictional Los Angeles athlete — a year of activities, sleep, stress, body battery, HRV, SpO2, saved locations etc:
docker compose -f docker-compose.demo.yml up
That generates the FIT files into their own volume, imports them into a separate database and starts Grafana on http://localhost:3000 with everything populated. Set GRAFIT_DEMO_DAYS=90 to generate less history — a year takes a couple of minutes and about 460 MB of database, 90 days about 115 MB.
Demo route geometry © OpenStreetMap contributors (ODbL), elevations from Open-Meteo (Copernicus DEM GLO-90).
Release files for grafit 1.45.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| grafit-1.45.0.tar.gz | 73.0 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| grafit-1.45.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 159.0 kB
Release files / grafit-1.45.0.tar.gz
| Download URL | grafit-1.45.0.tar.gz |
|---|---|
| Size | 73.0 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
45b58cc815718cf9aadeef78f2f1cee6c9e5e16b2fad66d43fe7813cb71217d7
|
|
BLAKE2b-256 checksum How to use checksums |
610e8319dd6fe88d49fd0caeaae394e309c51ce076b8b7cb46b9d32f56d1e923
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.12.18 {"installer":{"name":"uv","version":"0.12.18","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Debian GNU/Linux","version":"10","id":"buster","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|
Release files / grafit-1.45.0-py3-none-any.whl
| Download URL | grafit-1.45.0-py3-none-any.whl |
|---|---|
| Size | 86.1 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
974b4e59e7dc4a14961a3d7b741333ff31b5c6513c781ffa5b0ee47a18a30155
|
|
BLAKE2b-256 checksum How to use checksums |
bf857991eb7c471f56ccd12295e2f50a4fa0abac35479f8360b1465247fdca7c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.12.18 {"installer":{"name":"uv","version":"0.12.18","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Debian GNU/Linux","version":"10","id":"buster","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|