Skip to main content

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': '...'},
)
  • auth is a bare API-key string for Motive/Samsara and named fields (a mapping or GeotabAuthConfig) for GeoTab. Credentials are wrapped in SecretStr at the boundary and never appear in errors or logs.
  • Behind a TLS-intercepting corporate proxy (Zscaler-class), pass use_truststore=True to build TLS contexts from the OS trust store.
  • fetch exposes snapshot endpoints only — a snapshot is bounded by entity count, so the in-memory contract stays honest. Windowed history is sync territory, 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

Apache License 2.0.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

fleetpull-0.1.0.tar.gz (374.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

fleetpull-0.1.0-py3-none-any.whl (534.6 kB view details)

Uploaded Python 3

File details

Details for the file fleetpull-0.1.0.tar.gz.

File metadata

  • Download URL: fleetpull-0.1.0.tar.gz
  • Upload date:
  • Size: 374.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.14

File hashes

Hashes for fleetpull-0.1.0.tar.gz
Algorithm Hash digest
SHA256 93ecbe0ce8bd557a544fd6424f9c3d8fc10a4368361d91fe4fa2c64dea86f46a
MD5 2b3fd61a80a6cf721b4754ee60565e63
BLAKE2b-256 8b79790fd2b3142cb8262807733278446e486c6775bcfc13d7d187074fd18e96

See more details on using hashes here.

Provenance

The following attestation bundles were made for fleetpull-0.1.0.tar.gz:

Publisher: release.yml on andrewjordan3/fleetpull

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file fleetpull-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: fleetpull-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 534.6 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.14

File hashes

Hashes for fleetpull-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 24130597a25024292eb76c5ea22565dd393943960a7271c756dd8f6690aa4ef3
MD5 6938617979f6b94e2613d7c485912625
BLAKE2b-256 8fb7c35c308bfa65cba5c8dc49fccb69b1eed45591a40a40ca358778c65138e0

See more details on using hashes here.

Provenance

The following attestation bundles were made for fleetpull-0.1.0-py3-none-any.whl:

Publisher: release.yml on andrewjordan3/fleetpull

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

0.1.4

2 files

0.1.3

2 files

0.1.2

2 files

0.1.1

2 files

This release

0.1.0 This release

2 files

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