fleetpull
fleetpull pulls fleet telematics data from provider APIs — Motive, Samsara, and GeoTab — and delivers it as typed, dtype-coerced, lightly normalized tabular output: Polars DataFrames in memory, parquet on disk, staying as close to the raw API responses as is reasonable.
It is deliberately narrow. fleetpull does no cross-endpoint merging, builds no unified cross-provider schema, performs no semantic deduplication, loads no warehouse, and assumes no end use — downstream processing is the consumer's concern. What it does instead is be rigorous about retrieval: probed (never merely documented) provider behavior, crash-safe incremental state, token-bucket rate limiting at the transport boundary, and one explicit schema per (provider, endpoint).
Status: alpha. The two public verbs below are settled, and coverage is
broad — the Motive and Samsara endpoint inventories and the GeoTab Get and
feed surfaces are all shipped (see ENDPOINTS.md). Pre-1.0,
the internals are still free to improve.
Install
pip install fleetpull
# or
uv add fleetpull
The latest development version, straight from source:
pip install git+https://github.com/andrewjordan3/fleetpull
Python ≥ 3.12. Core dependencies: httpx, polars, pydantic 2.x,
pyyaml, truststore, tzdata.
To scaffold a starter config for the sync verb below, run
fleetpull init-config — it writes an annotated fleetpull_config.yaml
you edit and point sync at.
The two verbs
fetch — one snapshot, in memory
The programmatic convenience verb: one endpoint's full current listing as an eager, typed Polars DataFrame. No disk, no state, no configuration file.
from fleetpull import Endpoints, fetch
vehicles = fetch(Endpoints.Motive.vehicles, auth='your-api-key')
devices = fetch(
Endpoints.Geotab.devices,
auth={'username': '...', 'password': '...', 'database': '...'},
)
authis a bare API-key string for Motive/Samsara and named fields (a mapping orGeotabAuthConfig) for GeoTab. Credentials are wrapped inSecretStrat the boundary and never appear in errors or logs.- Behind a TLS-intercepting corporate proxy (Zscaler-class), pass
use_truststore=Trueto build TLS contexts from the OS trust store. fetchexposes snapshot endpoints only — a snapshot is bounded by entity count, so the in-memory contract stays honest. Windowed history issyncterritory, and the type checker (plus a runtime guard) enforces the split.- An empty result is a zero-row frame carrying the full typed schema, never
None.
Sync — config-driven incremental pipeline
The pipeline verb: a YAML config selects providers and endpoints; each run
fetches incrementally, writes parquet, and commits its resume state.
fleetpull init-config writes a documented starter config to edit.
from fleetpull import Sync
Sync('fleetpull_config.yaml').run()
sync:
default_start_date: 2025-01-01 # cold-start backfill anchor
storage:
dataset_root: /data/fleet # parquet lands here
logging:
console_level: INFO
providers:
motive:
endpoints: [vehicles, vehicle_locations, driving_periods]
# api_key: falls back to the MOTIVE_API_KEY environment variable
samsara:
endpoints: [vehicles]
# api_key: falls back to SAMSARA_API_KEY
geotab:
auth:
username: user@example.com
database: my_database
# password: falls back to GEOTAB_PASSWORD
endpoints: [devices, users, trips]
lookback_days: 7 # late-arrival refetch margin
The same run is available from the shell: fleetpull sync fleetpull_config.yaml.
Endpoints run and commit independently — one endpoint's failure never halts
its siblings; a run with failures ends by raising SyncFailuresError
carrying every failure (queue order within each provider — feeders then
consumers, config order within each — providers in config order).
Output is one folder per endpoint under dataset_root:
data/
motive/
vehicles/ # snapshot: one file, replaced each run
data.parquet
metadata.json # human-readable run summary — never read by the program
driving_periods/ # windowed: hive date partitions
date=2026-07-15/part.parquet
date=2026-07-16/part.parquet
metadata.json
Hive date=YYYY-MM-DD layout is read natively by pl.scan_parquet and
BigQuery external tables. Operational state (watermarks, run ledger, backfill
work units) lives in SQLite at <dataset_root>/.fleetpull/state.sqlite3;
crash-safety ordering (parquet first, cursor second) plus delete-by-window
merge makes interrupted runs refetch idempotently — at-least-once fetching,
exactly-once data.
Output contract
- One schema per (provider, endpoint). Column dtypes derive from each
endpoint's Pydantic response model; nested objects flatten to
double-underscore-joined columns (
driver__id). No cross-endpoint or cross-provider unification, ever. - Event timestamps are timezone-aware UTC end to end.
- Exact-duplicate rows (artifacts of pagination and crash refetch) are dropped at write time; same-id-different-payload reconciliation belongs to consumers.
- Values arrive as the provider sent them — no unit conversion, no semantic
cleanup. Provider quirks worth knowing (GeoTab's seconds-despite-the-name
engineHours, sentinel dates, per-endpoint window anchoring) are recorded in ENDPOINTS.md and DESIGN §8.
Errors
Consumers catch FleetpullError or its five public subclasses:
| Exception | When | Reasonable reaction |
|---|---|---|
ConfigurationError |
Bad config or wiring | Fix config, rerun |
AuthenticationError |
Rejected credentials | Fix credentials |
ProviderResponseError |
Non-retryable or contract-violating response | Investigate before rerunning |
RetriesExhaustedError |
Transient/rate-limit budget ran out | Rerun later |
SyncFailuresError |
One or more endpoints failed inside a sync run | Inspect failures, act per member |
Everything else is internal. Rate limits are respected automatically — a
shared token-bucket limiter sits at the transport boundary and a 429's
Retry-After pauses the whole quota scope.
Documentation
- ENDPOINTS.md — every shipped endpoint, its mechanics, and the port queue.
- DESIGN.md — the design of record: architecture, invariants, and the probe-captured provider behaviors every binding encodes.
- CLAUDE.md — engineering standards and verification gates.
Contributing
Contributions are welcome and encouraged — a new endpoint, a bug fix, a sharper docstring, or a provider quirk you've hit in the wild. Start with CONTRIBUTING.md; the short version:
uv sync --group dev
uv run ruff format . && uv run ruff check . \
&& uv run mypy src/ tests/ \
&& uv run lint-imports \
&& uv run pytest
These five gates are exactly what CI runs on every pull request, so a green local run is a green CI run. Tests never hit real provider APIs; new endpoints are built probe-first from live captures — the port discipline is in ENDPOINTS.md, and the engineering standards are in CLAUDE.md.
Acknowledgements
Built on Polars, Pydantic, httpx, and truststore — fleetpull is a thin, rigorous layer over their work.
Author
By Andrew Jordan. Questions, bugs, and endpoint requests are best raised as GitHub issues.
License
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 fleetpull-0.1.1.tar.gz.
File metadata
- Download URL: fleetpull-0.1.1.tar.gz
- Upload date:
- Size: 376.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0d682b513171a8dc5112f9e07ead64051fdc7b4a059a2092752ec224a0dc054d
|
|
| MD5 |
0696d3b26ade12bbeb3f5fdbfab2c7ed
|
|
| BLAKE2b-256 |
f7bf282dfb41191b1640b9a177425d8d66a0bd8102ede9c2b475a11a335d6ec8
|
Provenance
The following attestation bundles were made for fleetpull-0.1.1.tar.gz:
Publisher:
release.yml on andrewjordan3/fleetpull
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
fleetpull-0.1.1.tar.gz -
Subject digest:
0d682b513171a8dc5112f9e07ead64051fdc7b4a059a2092752ec224a0dc054d - Sigstore transparency entry: 2219604047
- Sigstore integration time:
-
Permalink:
andrewjordan3/fleetpull@9fb63df78513fc6bd2d5a4090cff3b61ce669966 -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/andrewjordan3
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@9fb63df78513fc6bd2d5a4090cff3b61ce669966 -
Trigger Event:
release
-
Statement type:
File details
Details for the file fleetpull-0.1.1-py3-none-any.whl.
File metadata
- Download URL: fleetpull-0.1.1-py3-none-any.whl
- Upload date:
- Size: 537.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6620623541f2acd6fd9819a59828a2c43f1df2ae7f175416ebc172be227e739a
|
|
| MD5 |
3d7c281c1ed65198ac7eec6909b8f721
|
|
| BLAKE2b-256 |
cab04646d2395cf84c059f3eded26a651c870486dd20857e73e22848204e53bb
|
Provenance
The following attestation bundles were made for fleetpull-0.1.1-py3-none-any.whl:
Publisher:
release.yml on andrewjordan3/fleetpull
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
fleetpull-0.1.1-py3-none-any.whl -
Subject digest:
6620623541f2acd6fd9819a59828a2c43f1df2ae7f175416ebc172be227e739a - Sigstore transparency entry: 2219604076
- Sigstore integration time:
-
Permalink:
andrewjordan3/fleetpull@9fb63df78513fc6bd2d5a4090cff3b61ce669966 -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/andrewjordan3
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@9fb63df78513fc6bd2d5a4090cff3b61ce669966 -
Trigger Event:
release
-
Statement type: