fleetpull 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- fleetpull/__init__.py +36 -0
- fleetpull/api/__init__.py +32 -0
- fleetpull/api/auth_ingress.py +216 -0
- fleetpull/api/catalog.py +139 -0
- fleetpull/api/fetch.py +229 -0
- fleetpull/api/identity.py +90 -0
- fleetpull/api/sync.py +735 -0
- fleetpull/cli.py +146 -0
- fleetpull/config/__init__.py +47 -0
- fleetpull/config/base.py +18 -0
- fleetpull/config/example.py +90 -0
- fleetpull/config/geotab.py +78 -0
- fleetpull/config/http.py +32 -0
- fleetpull/config/loading.py +195 -0
- fleetpull/config/logger.py +139 -0
- fleetpull/config/providers.py +520 -0
- fleetpull/config/rate_limit.py +50 -0
- fleetpull/config/resolution.py +156 -0
- fleetpull/config/retry.py +69 -0
- fleetpull/config/root.py +157 -0
- fleetpull/config/sections.py +146 -0
- fleetpull/endpoints/__init__.py +24 -0
- fleetpull/endpoints/geotab/__init__.py +12 -0
- fleetpull/endpoints/geotab/_requests.py +427 -0
- fleetpull/endpoints/geotab/annotation_logs.py +73 -0
- fleetpull/endpoints/geotab/audits.py +70 -0
- fleetpull/endpoints/geotab/devices.py +79 -0
- fleetpull/endpoints/geotab/driver_changes.py +73 -0
- fleetpull/endpoints/geotab/duty_status_logs.py +75 -0
- fleetpull/endpoints/geotab/dvir_logs.py +76 -0
- fleetpull/endpoints/geotab/exception_events.py +119 -0
- fleetpull/endpoints/geotab/fault_data.py +72 -0
- fleetpull/endpoints/geotab/fill_ups.py +79 -0
- fleetpull/endpoints/geotab/fuel_and_energy_used.py +74 -0
- fleetpull/endpoints/geotab/fuel_tax_details.py +75 -0
- fleetpull/endpoints/geotab/log_records.py +74 -0
- fleetpull/endpoints/geotab/media_files.py +74 -0
- fleetpull/endpoints/geotab/shipment_logs.py +71 -0
- fleetpull/endpoints/geotab/status_data.py +71 -0
- fleetpull/endpoints/geotab/text_messages.py +77 -0
- fleetpull/endpoints/geotab/trips.py +102 -0
- fleetpull/endpoints/geotab/users.py +81 -0
- fleetpull/endpoints/motive/__init__.py +12 -0
- fleetpull/endpoints/motive/_spec_builders.py +94 -0
- fleetpull/endpoints/motive/driver_idle_rollups.py +113 -0
- fleetpull/endpoints/motive/driving_periods.py +85 -0
- fleetpull/endpoints/motive/groups.py +61 -0
- fleetpull/endpoints/motive/idle_events.py +94 -0
- fleetpull/endpoints/motive/users.py +64 -0
- fleetpull/endpoints/motive/vehicle_locations.py +170 -0
- fleetpull/endpoints/motive/vehicle_utilizations.py +122 -0
- fleetpull/endpoints/motive/vehicles.py +106 -0
- fleetpull/endpoints/registry.py +280 -0
- fleetpull/endpoints/samsara/__init__.py +12 -0
- fleetpull/endpoints/samsara/_spec_builders.py +202 -0
- fleetpull/endpoints/samsara/addresses.py +77 -0
- fleetpull/endpoints/samsara/asset_locations.py +217 -0
- fleetpull/endpoints/samsara/driver_fuel_energy_reports.py +124 -0
- fleetpull/endpoints/samsara/driver_vehicle_assignments.py +211 -0
- fleetpull/endpoints/samsara/drivers.py +169 -0
- fleetpull/endpoints/samsara/engine_states.py +114 -0
- fleetpull/endpoints/samsara/gps_readings.py +113 -0
- fleetpull/endpoints/samsara/idling_events.py +195 -0
- fleetpull/endpoints/samsara/odometer_readings.py +116 -0
- fleetpull/endpoints/samsara/trips.py +217 -0
- fleetpull/endpoints/samsara/vehicle_fuel_energy_reports.py +139 -0
- fleetpull/endpoints/samsara/vehicles.py +121 -0
- fleetpull/endpoints/shared/__init__.py +57 -0
- fleetpull/endpoints/shared/base.py +529 -0
- fleetpull/endpoints/shared/request_shape.py +227 -0
- fleetpull/endpoints/shared/resume.py +74 -0
- fleetpull/endpoints/shared/spec_builders.py +59 -0
- fleetpull/endpoints/shared/sync_mode.py +159 -0
- fleetpull/endpoints/shared/url_paths.py +149 -0
- fleetpull/exceptions.py +320 -0
- fleetpull/incremental/__init__.py +27 -0
- fleetpull/incremental/cursor.py +66 -0
- fleetpull/incremental/resolution.py +152 -0
- fleetpull/incremental/seed.py +53 -0
- fleetpull/incremental/window.py +94 -0
- fleetpull/logger/__init__.py +5 -0
- fleetpull/logger/setup.py +145 -0
- fleetpull/model_contract/__init__.py +6 -0
- fleetpull/model_contract/coercions.py +33 -0
- fleetpull/model_contract/response.py +45 -0
- fleetpull/models/__init__.py +7 -0
- fleetpull/models/geotab/__init__.py +166 -0
- fleetpull/models/geotab/annotation_log.py +109 -0
- fleetpull/models/geotab/audit.py +56 -0
- fleetpull/models/geotab/device.py +217 -0
- fleetpull/models/geotab/driver_change.py +102 -0
- fleetpull/models/geotab/duty_status_log.py +200 -0
- fleetpull/models/geotab/dvir_log.py +184 -0
- fleetpull/models/geotab/exception_event.py +135 -0
- fleetpull/models/geotab/fault_data.py +168 -0
- fleetpull/models/geotab/fill_up.py +189 -0
- fleetpull/models/geotab/fuel_and_energy_used.py +81 -0
- fleetpull/models/geotab/fuel_tax_detail.py +150 -0
- fleetpull/models/geotab/log_record.py +68 -0
- fleetpull/models/geotab/media_file.py +133 -0
- fleetpull/models/geotab/shared.py +221 -0
- fleetpull/models/geotab/shipment_log.py +105 -0
- fleetpull/models/geotab/status_data.py +108 -0
- fleetpull/models/geotab/text_message.py +125 -0
- fleetpull/models/geotab/trip.py +162 -0
- fleetpull/models/geotab/user.py +187 -0
- fleetpull/models/motive/__init__.py +43 -0
- fleetpull/models/motive/driver_idle_rollup.py +109 -0
- fleetpull/models/motive/driving_period.py +82 -0
- fleetpull/models/motive/group.py +49 -0
- fleetpull/models/motive/idle_event.py +77 -0
- fleetpull/models/motive/shared.py +192 -0
- fleetpull/models/motive/user.py +217 -0
- fleetpull/models/motive/vehicle.py +162 -0
- fleetpull/models/motive/vehicle_location.py +127 -0
- fleetpull/models/motive/vehicle_utilization.py +128 -0
- fleetpull/models/samsara/__init__.py +108 -0
- fleetpull/models/samsara/address.py +149 -0
- fleetpull/models/samsara/asset_location.py +131 -0
- fleetpull/models/samsara/driver.py +232 -0
- fleetpull/models/samsara/driver_fuel_energy_report.py +153 -0
- fleetpull/models/samsara/driver_vehicle_assignment.py +168 -0
- fleetpull/models/samsara/engine_state.py +92 -0
- fleetpull/models/samsara/gps_reading.py +146 -0
- fleetpull/models/samsara/idling_event.py +174 -0
- fleetpull/models/samsara/odometer_reading.py +90 -0
- fleetpull/models/samsara/trip.py +199 -0
- fleetpull/models/samsara/vehicle.py +167 -0
- fleetpull/models/samsara/vehicle_fuel_energy_report.py +191 -0
- fleetpull/network/__init__.py +9 -0
- fleetpull/network/auth/__init__.py +15 -0
- fleetpull/network/auth/authenticate.py +271 -0
- fleetpull/network/auth/manager.py +223 -0
- fleetpull/network/auth/models.py +57 -0
- fleetpull/network/auth/strategies.py +169 -0
- fleetpull/network/classifiers/__init__.py +11 -0
- fleetpull/network/classifiers/geotab.py +145 -0
- fleetpull/network/classifiers/motive.py +79 -0
- fleetpull/network/classifiers/samsara.py +80 -0
- fleetpull/network/client/__init__.py +25 -0
- fleetpull/network/client/page.py +32 -0
- fleetpull/network/client/profile.py +27 -0
- fleetpull/network/client/registry.py +103 -0
- fleetpull/network/client/registry_base.py +150 -0
- fleetpull/network/client/runtime.py +44 -0
- fleetpull/network/client/transport.py +381 -0
- fleetpull/network/contract/__init__.py +50 -0
- fleetpull/network/contract/auth.py +48 -0
- fleetpull/network/contract/classifier.py +189 -0
- fleetpull/network/contract/envelope_fetcher.py +33 -0
- fleetpull/network/contract/envelopes.py +189 -0
- fleetpull/network/contract/outcome.py +43 -0
- fleetpull/network/contract/page_decoder.py +101 -0
- fleetpull/network/contract/request.py +97 -0
- fleetpull/network/decoders/__init__.py +40 -0
- fleetpull/network/decoders/_window_stamp.py +78 -0
- fleetpull/network/decoders/geotab.py +309 -0
- fleetpull/network/decoders/motive.py +204 -0
- fleetpull/network/decoders/motive_reports.py +114 -0
- fleetpull/network/decoders/samsara.py +343 -0
- fleetpull/network/decoders/samsara_reports.py +119 -0
- fleetpull/network/decoders/single_page.py +55 -0
- fleetpull/network/limits/__init__.py +13 -0
- fleetpull/network/limits/bucket_math.py +58 -0
- fleetpull/network/limits/limiter.py +153 -0
- fleetpull/network/limits/registry.py +96 -0
- fleetpull/network/posture/__init__.py +6 -0
- fleetpull/network/posture/client_options.py +96 -0
- fleetpull/network/retry/__init__.py +13 -0
- fleetpull/network/retry/decision.py +155 -0
- fleetpull/network/tls/__init__.py +5 -0
- fleetpull/network/tls/truststore_context.py +35 -0
- fleetpull/orchestrator/__init__.py +30 -0
- fleetpull/orchestrator/backfill.py +127 -0
- fleetpull/orchestrator/batch.py +155 -0
- fleetpull/orchestrator/bisection.py +271 -0
- fleetpull/orchestrator/drivers.py +350 -0
- fleetpull/orchestrator/entry.py +347 -0
- fleetpull/orchestrator/executors.py +128 -0
- fleetpull/orchestrator/fanout.py +163 -0
- fleetpull/orchestrator/feed_drive.py +266 -0
- fleetpull/orchestrator/metadata_projection.py +195 -0
- fleetpull/orchestrator/outcome.py +53 -0
- fleetpull/orchestrator/recording.py +85 -0
- fleetpull/orchestrator/resume.py +143 -0
- fleetpull/orchestrator/roster_harvest.py +108 -0
- fleetpull/orchestrator/roster_refresh.py +363 -0
- fleetpull/orchestrator/runner.py +262 -0
- fleetpull/orchestrator/shape_resolution.py +221 -0
- fleetpull/orchestrator/spine.py +166 -0
- fleetpull/orchestrator/streaming.py +153 -0
- fleetpull/orchestrator/unit_loop.py +273 -0
- fleetpull/orchestrator/watermark_drive.py +455 -0
- fleetpull/paths/__init__.py +13 -0
- fleetpull/paths/datasets.py +38 -0
- fleetpull/paths/partitions.py +41 -0
- fleetpull/paths/resolution.py +98 -0
- fleetpull/polars_typing/__init__.py +20 -0
- fleetpull/py.typed +0 -0
- fleetpull/records/__init__.py +19 -0
- fleetpull/records/convert.py +53 -0
- fleetpull/records/dataframe.py +63 -0
- fleetpull/records/event_time.py +70 -0
- fleetpull/records/fields.py +190 -0
- fleetpull/records/flatten.py +54 -0
- fleetpull/records/roster_members.py +74 -0
- fleetpull/records/schema.py +73 -0
- fleetpull/records/validation.py +62 -0
- fleetpull/resources/__init__.py +10 -0
- fleetpull/resources/config.example.yaml +222 -0
- fleetpull/roster/__init__.py +16 -0
- fleetpull/roster/definition.py +46 -0
- fleetpull/roster/key.py +40 -0
- fleetpull/roster/registry.py +93 -0
- fleetpull/state/__init__.py +36 -0
- fleetpull/state/cursors.py +417 -0
- fleetpull/state/database.py +463 -0
- fleetpull/state/migrations.py +430 -0
- fleetpull/state/reconcile.py +127 -0
- fleetpull/state/rosters.py +159 -0
- fleetpull/state/run_ledger.py +572 -0
- fleetpull/state/work_units.py +648 -0
- fleetpull/storage/__init__.py +30 -0
- fleetpull/storage/append.py +187 -0
- fleetpull/storage/atomic.py +161 -0
- fleetpull/storage/files.py +176 -0
- fleetpull/storage/frames.py +98 -0
- fleetpull/storage/metadata.py +161 -0
- fleetpull/storage/partitioned.py +205 -0
- fleetpull/storage/pruning.py +169 -0
- fleetpull/storage/read.py +35 -0
- fleetpull/storage/result.py +34 -0
- fleetpull/storage/single_file.py +130 -0
- fleetpull/storage/splitting.py +88 -0
- fleetpull/storage/staging.py +178 -0
- fleetpull/storage/writers.py +145 -0
- fleetpull/timing/__init__.py +21 -0
- fleetpull/timing/canon.py +92 -0
- fleetpull/timing/clock.py +192 -0
- fleetpull/timing/codec.py +122 -0
- fleetpull/timing/sleeper.py +65 -0
- fleetpull/vocabulary/__init__.py +16 -0
- fleetpull/vocabulary/json_types.py +20 -0
- fleetpull/vocabulary/provider.py +29 -0
- fleetpull/vocabulary/quota_scope.py +47 -0
- fleetpull/vocabulary/response_category.py +32 -0
- fleetpull-0.1.0.dist-info/METADATA +248 -0
- fleetpull-0.1.0.dist-info/RECORD +252 -0
- fleetpull-0.1.0.dist-info/WHEEL +5 -0
- fleetpull-0.1.0.dist-info/entry_points.txt +2 -0
- fleetpull-0.1.0.dist-info/licenses/LICENSE +202 -0
- fleetpull-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
# fleetpull configuration -- copy this file (e.g. to config.yaml), edit the
|
|
2
|
+
# required values, and hand its path to the sync entry point
|
|
3
|
+
# (FleetpullConfig.from_yaml). Commented-out keys are optional and show the
|
|
4
|
+
# default that applies when the key is absent. Unknown keys are rejected at
|
|
5
|
+
# any level. Every path key is normalized (~ expansion, relative-to-absolute)
|
|
6
|
+
# at load time.
|
|
7
|
+
|
|
8
|
+
sync:
|
|
9
|
+
# REQUIRED. The cold-start anchor: the UTC calendar date history begins
|
|
10
|
+
# from on a watermark endpoint's very first run. Goes inert once observed
|
|
11
|
+
# data or completed coverage exists.
|
|
12
|
+
default_start_date: 2026-06-01
|
|
13
|
+
|
|
14
|
+
# Package-wide late-arrival window knobs, in whole days. A value declared
|
|
15
|
+
# here fans into every provider that does not set its own key; a
|
|
16
|
+
# per-provider key always wins (provider key > this > provider default).
|
|
17
|
+
# lookback_days: 7
|
|
18
|
+
# cutoff_days: 0
|
|
19
|
+
|
|
20
|
+
# The width, in whole days, of the work units a windowed run is planned
|
|
21
|
+
# into. Every windowed pull -- the daily sync and a long backfill alike --
|
|
22
|
+
# is tiled into units that fetch and commit independently (partitions,
|
|
23
|
+
# ledger row, watermark), so a crash resumes from the last completed unit
|
|
24
|
+
# instead of refetching the whole window. Smaller chunks mean finer
|
|
25
|
+
# crash-resume granularity at the cost of more commits; a window smaller
|
|
26
|
+
# than one chunk is simply one unit. An endpoint whose rows are
|
|
27
|
+
# per-request-window rollups declares its own fixed unit width, which
|
|
28
|
+
# wins over this value (e.g. the Samsara fuel-energy report pair is
|
|
29
|
+
# pinned to 1-day units). Optional; default 7.
|
|
30
|
+
# backfill_chunk_days: 7
|
|
31
|
+
|
|
32
|
+
# How many of a windowed endpoint's work units drive concurrently. The
|
|
33
|
+
# watermark stays truthful under any completion order (the prefix-advance
|
|
34
|
+
# rule), and the provider's rate budget still governs at the wire, so
|
|
35
|
+
# workers beyond the budget simply pace on rate-limit tokens. 1 is the
|
|
36
|
+
# serial path. Optional; default 4.
|
|
37
|
+
# backfill_unit_workers: 4
|
|
38
|
+
|
|
39
|
+
storage:
|
|
40
|
+
# REQUIRED. The root directory the parquet dataset is written under
|
|
41
|
+
# ({root}/{provider}/{endpoint}/). Use a real local path -- never a
|
|
42
|
+
# cloud-synced folder (OneDrive, Dropbox, Google Drive): sync clients
|
|
43
|
+
# fight the writer's atomic renames.
|
|
44
|
+
dataset_root: /data/fleetpull
|
|
45
|
+
|
|
46
|
+
# Whether write-time compaction drops exact-duplicate rows (semantic
|
|
47
|
+
# dedup is out of scope by design). Optional; default true.
|
|
48
|
+
# drop_exact_duplicates: true
|
|
49
|
+
|
|
50
|
+
# state:
|
|
51
|
+
# # Where operational state (SQLite) lives. Keep it on a real local disk
|
|
52
|
+
# # even when dataset_root sits on a network filesystem. Optional; default:
|
|
53
|
+
# database_path: /data/fleetpull/.fleetpull/state.sqlite3
|
|
54
|
+
|
|
55
|
+
# logging:
|
|
56
|
+
# # Minimum level for console output (name or standard integer). Optional.
|
|
57
|
+
# console_level: INFO
|
|
58
|
+
# # Either file key enables file logging; the missing partner is defaulted
|
|
59
|
+
# # (file_level -> DEBUG, file_path -> <dataset_root>/.fleetpull/fleetpull.log).
|
|
60
|
+
# # file_level: DEBUG
|
|
61
|
+
# # file_path: /var/log/fleetpull.log
|
|
62
|
+
|
|
63
|
+
# http:
|
|
64
|
+
# # Build TLS contexts from the operating system's trust store -- required
|
|
65
|
+
# # behind TLS-intercepting corporate proxies (Zscaler and kin). Optional.
|
|
66
|
+
# use_truststore: false
|
|
67
|
+
# connect_timeout_seconds: 10.0
|
|
68
|
+
# read_timeout_seconds: 30.0
|
|
69
|
+
|
|
70
|
+
# retry:
|
|
71
|
+
# # Attempt budgets and backoff shape; the defaults suit almost everyone.
|
|
72
|
+
# transient_max_failures: 3
|
|
73
|
+
# transient_backoff_base_seconds: 1.0
|
|
74
|
+
# transient_backoff_cap_seconds: 30.0
|
|
75
|
+
# rate_limited_max_failures: 10
|
|
76
|
+
# fallback_penalty_seconds: 60.0
|
|
77
|
+
|
|
78
|
+
providers:
|
|
79
|
+
motive:
|
|
80
|
+
# Set your API key as an environment variable rather than writing it here:
|
|
81
|
+
# Linux/macOS (current shell): export MOTIVE_API_KEY='...'
|
|
82
|
+
# persistent: add that line to ~/.bashrc or ~/.zshrc
|
|
83
|
+
# Windows PowerShell (current shell): $env:MOTIVE_API_KEY = '...'
|
|
84
|
+
# persistent: setx MOTIVE_API_KEY "..."
|
|
85
|
+
# (setx takes effect in NEW shells only -- reopen your terminal)
|
|
86
|
+
# A key written here wins over the environment variable.
|
|
87
|
+
# api_key: ''
|
|
88
|
+
|
|
89
|
+
# The endpoints to sync, by catalog name. A provider with endpoints
|
|
90
|
+
# listed requires a resolvable credential; a credential with no
|
|
91
|
+
# endpoints leaves the provider disabled (with a load-time warning).
|
|
92
|
+
endpoints:
|
|
93
|
+
- vehicles
|
|
94
|
+
- vehicle_locations
|
|
95
|
+
- driving_periods
|
|
96
|
+
- idle_events
|
|
97
|
+
- groups
|
|
98
|
+
- users
|
|
99
|
+
- vehicle_utilizations
|
|
100
|
+
- driver_idle_rollups
|
|
101
|
+
|
|
102
|
+
# base_url: https://api.gomotive.com # optional; Motive's production host
|
|
103
|
+
# records_per_page: 100 # optional; 1..100
|
|
104
|
+
|
|
105
|
+
# Per-provider window knobs, in whole days. Optional overrides: a key
|
|
106
|
+
# set here beats sync's package-wide value, which beats this default.
|
|
107
|
+
# lookback_days: 7
|
|
108
|
+
# cutoff_days: 0
|
|
109
|
+
|
|
110
|
+
# rate_limit: # optional; conservative defaults
|
|
111
|
+
# requests_per_period: 60
|
|
112
|
+
# period_seconds: 60.0
|
|
113
|
+
# burst: 10
|
|
114
|
+
# max_concurrency: 2
|
|
115
|
+
|
|
116
|
+
samsara:
|
|
117
|
+
# Set your API token as an environment variable rather than writing it
|
|
118
|
+
# here (same shell mechanics as MOTIVE_API_KEY above):
|
|
119
|
+
# export SAMSARA_API_KEY='...'
|
|
120
|
+
# A token written here wins over the environment variable.
|
|
121
|
+
# api_key: ''
|
|
122
|
+
|
|
123
|
+
# The endpoints to sync, by catalog name.
|
|
124
|
+
endpoints:
|
|
125
|
+
- vehicles
|
|
126
|
+
- drivers
|
|
127
|
+
- trips
|
|
128
|
+
- idling_events
|
|
129
|
+
- addresses
|
|
130
|
+
- engine_states
|
|
131
|
+
- gps_readings
|
|
132
|
+
- odometer_readings
|
|
133
|
+
- asset_locations
|
|
134
|
+
- driver_vehicle_assignments
|
|
135
|
+
- vehicle_fuel_energy_reports
|
|
136
|
+
- driver_fuel_energy_reports
|
|
137
|
+
|
|
138
|
+
# base_url: https://api.samsara.com # optional; Samsara's production host
|
|
139
|
+
|
|
140
|
+
# Per-provider window knobs for watermark endpoints (Samsara trips,
|
|
141
|
+
# idling_events, the vehicle-stats triple, asset_locations,
|
|
142
|
+
# driver_vehicle_assignments, and the fuel-energy report pair
|
|
143
|
+
# vehicle_fuel_energy_reports / driver_fuel_energy_reports), in
|
|
144
|
+
# whole days. Optional overrides: a key set here beats sync's
|
|
145
|
+
# package-wide value, which beats this default.
|
|
146
|
+
# lookback_days: 7
|
|
147
|
+
# cutoff_days: 0
|
|
148
|
+
|
|
149
|
+
# rate_limit: # optional; defaults to the
|
|
150
|
+
# requests_per_period: 100 # tightest documented endpoint
|
|
151
|
+
# period_seconds: 60.0 # tier (see SamsaraConfig)
|
|
152
|
+
# burst: 10
|
|
153
|
+
# max_concurrency: 2
|
|
154
|
+
|
|
155
|
+
geotab:
|
|
156
|
+
# The four-field GeoTab credential, nested. Set the password as an
|
|
157
|
+
# environment variable rather than writing it here:
|
|
158
|
+
# Linux/macOS: export GEOTAB_PASSWORD='...'
|
|
159
|
+
# Windows PowerShell: $env:GEOTAB_PASSWORD = '...'
|
|
160
|
+
# A password written here wins over the environment variable; the
|
|
161
|
+
# other three fields are not secrets and always come from this file.
|
|
162
|
+
auth:
|
|
163
|
+
username: user@example.com
|
|
164
|
+
database: exampledb
|
|
165
|
+
# server: my.geotab.com # optional; the authentication host
|
|
166
|
+
|
|
167
|
+
# The endpoints to sync, by catalog name. The feed endpoints
|
|
168
|
+
# (log_records through media_files) drive GeoTab's GetFeed
|
|
169
|
+
# version-token stream into append-only storage.
|
|
170
|
+
endpoints:
|
|
171
|
+
- devices
|
|
172
|
+
- users
|
|
173
|
+
- trips
|
|
174
|
+
- exception_events
|
|
175
|
+
- log_records
|
|
176
|
+
- status_data
|
|
177
|
+
- fill_ups
|
|
178
|
+
- fuel_and_energy_used
|
|
179
|
+
- fuel_tax_details
|
|
180
|
+
- fault_data
|
|
181
|
+
- duty_status_logs
|
|
182
|
+
- driver_changes
|
|
183
|
+
- dvir_logs
|
|
184
|
+
- annotation_logs
|
|
185
|
+
- shipment_logs
|
|
186
|
+
- audits
|
|
187
|
+
- text_messages
|
|
188
|
+
- media_files
|
|
189
|
+
|
|
190
|
+
# Per-provider window knobs for watermark endpoints (GeoTab trips and
|
|
191
|
+
# exception_events; the feed endpoints resume from a version token and
|
|
192
|
+
# take no window), in whole days. Optional overrides: a key set here
|
|
193
|
+
# beats sync's package-wide value, which beats this default. The
|
|
194
|
+
# lookback margin is also what absorbs GeoTab's Trip recalculation.
|
|
195
|
+
# lookback_days: 7
|
|
196
|
+
# cutoff_days: 0
|
|
197
|
+
|
|
198
|
+
# The Get method-class budget (GeoTab meters per method class). The
|
|
199
|
+
# default cites the captured 2026-07-09 rate headers (~650/min; a
|
|
200
|
+
# single datum, so burst errs conservative).
|
|
201
|
+
# rate_limit:
|
|
202
|
+
# requests_per_period: 650
|
|
203
|
+
# period_seconds: 60.0
|
|
204
|
+
# burst: 100
|
|
205
|
+
# max_concurrency: 2
|
|
206
|
+
|
|
207
|
+
# The GetFeed method-class budget the feed endpoints spend from
|
|
208
|
+
# (~60/min, the 2026-07-21 header-decrement probe).
|
|
209
|
+
# feed_rate_limit:
|
|
210
|
+
# requests_per_period: 60
|
|
211
|
+
# period_seconds: 60.0
|
|
212
|
+
# burst: 10
|
|
213
|
+
# max_concurrency: 2
|
|
214
|
+
|
|
215
|
+
# The Authenticate method-class budget: 10/min (June 2026 capture;
|
|
216
|
+
# docs Status: Active). Authenticate fires rarely behind the session
|
|
217
|
+
# manager's single-flight lock.
|
|
218
|
+
# authenticate_rate_limit:
|
|
219
|
+
# requests_per_period: 10
|
|
220
|
+
# period_seconds: 60.0
|
|
221
|
+
# burst: 2
|
|
222
|
+
# max_concurrency: 1
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# src/fleetpull/roster/__init__.py
|
|
2
|
+
"""The roster layer: the fan-out roster's identity, declaration, and catalog.
|
|
3
|
+
|
|
4
|
+
A leaf both the endpoints layer (the roster-backed shapes on a consuming endpoint) and
|
|
5
|
+
the state layer (the ``RosterStore``) key by -- ``state`` cannot import ``endpoints``,
|
|
6
|
+
so the shared roster identity sits below both. ``RosterKey`` is the opaque handle a
|
|
7
|
+
consumer carries; ``RosterDefinition`` is the registry's record of where a key's
|
|
8
|
+
members come from and the refresh policy; ``RosterRegistry`` resolves a key to its
|
|
9
|
+
definition. Imports only ``vocabulary`` and ``exceptions``, nothing higher.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from fleetpull.roster.definition import RosterDefinition
|
|
13
|
+
from fleetpull.roster.key import RosterKey
|
|
14
|
+
from fleetpull.roster.registry import RosterRegistry
|
|
15
|
+
|
|
16
|
+
__all__: list[str] = ['RosterDefinition', 'RosterKey', 'RosterRegistry']
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# src/fleetpull/roster/definition.py
|
|
2
|
+
"""The roster declaration: ``RosterDefinition`` -- a key, its source, and its policy.
|
|
3
|
+
|
|
4
|
+
The registry's record for one ``RosterKey``: the feeder endpoint and column its
|
|
5
|
+
members are listed from, and the staleness and eviction policy a refresh applies. The
|
|
6
|
+
consuming endpoint never sees this -- it carries only the ``RosterKey``; the registry
|
|
7
|
+
resolves the key to this when a refresh runs. Homed beside the key it references.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from dataclasses import dataclass
|
|
11
|
+
from datetime import timedelta
|
|
12
|
+
|
|
13
|
+
from fleetpull.roster.key import RosterKey
|
|
14
|
+
|
|
15
|
+
__all__: list[str] = ['RosterDefinition']
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@dataclass(frozen=True, slots=True)
|
|
19
|
+
class RosterDefinition:
|
|
20
|
+
"""One roster's source and refresh policy, keyed by its ``RosterKey``.
|
|
21
|
+
|
|
22
|
+
Maps a ``RosterKey`` to the feeder endpoint and column its members are listed
|
|
23
|
+
from, plus the staleness bound and eviction threshold a refresh applies. The
|
|
24
|
+
source is named by endpoint and column (strings), not an ``EndpointDefinition`` --
|
|
25
|
+
resolving the name to a runnable binding is the coordinator's job, against the
|
|
26
|
+
endpoint catalog. ``max_age`` and ``eviction_threshold`` are the policy the pure
|
|
27
|
+
``is_roster_stale`` and ``reconcile`` already take.
|
|
28
|
+
|
|
29
|
+
Attributes:
|
|
30
|
+
key: The roster this defines.
|
|
31
|
+
source_endpoint: The feeder endpoint whose listing supplies the members
|
|
32
|
+
(e.g. ``'vehicles'``).
|
|
33
|
+
source_column: The feeder frame column whose distinct values are the members
|
|
34
|
+
(e.g. ``'vehicle_id'``) -- the model field name after the records-layer
|
|
35
|
+
flatten, not the wire key.
|
|
36
|
+
max_age: The staleness bound -- a refresh is due once the feeder's last
|
|
37
|
+
success is older than this.
|
|
38
|
+
eviction_threshold: Consecutive-miss count past which a member is evicted, or
|
|
39
|
+
``None`` for append-only (never evict).
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
key: RosterKey
|
|
43
|
+
source_endpoint: str
|
|
44
|
+
source_column: str
|
|
45
|
+
max_age: timedelta
|
|
46
|
+
eviction_threshold: int | None
|
fleetpull/roster/key.py
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# src/fleetpull/roster/key.py
|
|
2
|
+
"""The roster identity: ``RosterKey``, the opaque handle a consumer references.
|
|
3
|
+
|
|
4
|
+
A ``RosterKey`` names one roster -- ``(provider, name)`` -- and is the only roster
|
|
5
|
+
fact a fan-out consumer carries (its shape's ``roster`` field): the consumer knows
|
|
6
|
+
*that* a roster of its keys exists, never where those keys come from. The mapping
|
|
7
|
+
from a key to its source endpoint and column lives in the ``RosterRegistry`` (the
|
|
8
|
+
``RosterDefinition``), and the persisted members live in the ``RosterStore``, both
|
|
9
|
+
keyed by this. Homed in this leaf so both the endpoints layer (the binding) and the
|
|
10
|
+
state layer (the store) can key by it -- ``state`` cannot import ``endpoints``, so the
|
|
11
|
+
shared identity sits below both.
|
|
12
|
+
|
|
13
|
+
``name`` is the logical roster, deliberately not the source column: ``'vehicle_ids'``
|
|
14
|
+
the roster, not ``'vehicle_id'`` the feeder field -- the gap is the decoupling.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from dataclasses import dataclass
|
|
18
|
+
|
|
19
|
+
from fleetpull.vocabulary import Provider
|
|
20
|
+
|
|
21
|
+
__all__: list[str] = ['RosterKey']
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@dataclass(frozen=True, slots=True)
|
|
25
|
+
class RosterKey:
|
|
26
|
+
"""The identity of one roster: a provider and a logical roster name.
|
|
27
|
+
|
|
28
|
+
The opaque handle a fan-out consumer references and the key the registry and
|
|
29
|
+
store map from. Two rosters are the same iff their provider and name match;
|
|
30
|
+
nothing else (the source endpoint, the column, the policy) is part of the
|
|
31
|
+
identity -- those live in the ``RosterDefinition`` the registry holds.
|
|
32
|
+
|
|
33
|
+
Attributes:
|
|
34
|
+
provider: The provider whose roster this is.
|
|
35
|
+
name: The logical roster name (e.g. ``'vehicle_ids'``), distinct from the
|
|
36
|
+
feeder column its members are listed from.
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
provider: Provider
|
|
40
|
+
name: str
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# src/fleetpull/roster/registry.py
|
|
2
|
+
"""The roster catalog: ``RosterRegistry``, ``RosterKey`` -> ``RosterDefinition``.
|
|
3
|
+
|
|
4
|
+
Holds the roster declarations and resolves them both ways. Forward
|
|
5
|
+
(``get``): a ``RosterKey`` to its ``RosterDefinition`` -- the source endpoint,
|
|
6
|
+
column, and policy a refresh needs; the consuming endpoint carries only the
|
|
7
|
+
key, and the coordinator asks the registry for the key's definition when it
|
|
8
|
+
refreshes. Reverse (``sourced_by``): which roster definitions a feeder
|
|
9
|
+
``(provider, endpoint)`` sources -- the lookup the feeder tap reads so every
|
|
10
|
+
execution of a feeder endpoint reconciles its rosters. Construction rejects
|
|
11
|
+
two definitions claiming the same key.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from collections.abc import Iterable
|
|
15
|
+
|
|
16
|
+
from fleetpull.exceptions import ConfigurationError
|
|
17
|
+
from fleetpull.roster.definition import RosterDefinition
|
|
18
|
+
from fleetpull.roster.key import RosterKey
|
|
19
|
+
from fleetpull.vocabulary import Provider
|
|
20
|
+
|
|
21
|
+
__all__: list[str] = ['RosterRegistry']
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class RosterRegistry:
|
|
25
|
+
"""An immutable catalog mapping each ``RosterKey`` to its ``RosterDefinition``.
|
|
26
|
+
|
|
27
|
+
Built once from the roster definitions, it answers ``get(key)``. The map is
|
|
28
|
+
private and frozen at construction; a duplicate key is a wiring bug and raises.
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
definitions: The roster definitions to catalog; their keys must be distinct.
|
|
32
|
+
|
|
33
|
+
Raises:
|
|
34
|
+
ConfigurationError: Two definitions share a ``RosterKey`` -- a wiring bug.
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
def __init__(self, definitions: Iterable[RosterDefinition]) -> None:
|
|
38
|
+
by_key: dict[RosterKey, RosterDefinition] = {}
|
|
39
|
+
for definition in definitions:
|
|
40
|
+
if definition.key in by_key:
|
|
41
|
+
raise ConfigurationError(
|
|
42
|
+
'duplicate roster definition',
|
|
43
|
+
provider=definition.key.provider.value,
|
|
44
|
+
detail=f'roster {definition.key.name!r} is defined twice',
|
|
45
|
+
)
|
|
46
|
+
by_key[definition.key] = definition
|
|
47
|
+
self._by_key: dict[RosterKey, RosterDefinition] = by_key
|
|
48
|
+
|
|
49
|
+
def get(self, key: RosterKey) -> RosterDefinition:
|
|
50
|
+
"""Return the definition for a roster key.
|
|
51
|
+
|
|
52
|
+
Args:
|
|
53
|
+
key: The roster to resolve.
|
|
54
|
+
|
|
55
|
+
Returns:
|
|
56
|
+
The roster's definition (source endpoint, column, and policy).
|
|
57
|
+
|
|
58
|
+
Raises:
|
|
59
|
+
ConfigurationError: No definition is registered for ``key`` -- a consumer
|
|
60
|
+
references a roster the catalog does not declare.
|
|
61
|
+
"""
|
|
62
|
+
try:
|
|
63
|
+
return self._by_key[key]
|
|
64
|
+
except KeyError:
|
|
65
|
+
raise ConfigurationError(
|
|
66
|
+
'unknown roster',
|
|
67
|
+
provider=key.provider.value,
|
|
68
|
+
detail=f'no roster definition registered for {key.name!r}',
|
|
69
|
+
) from None
|
|
70
|
+
|
|
71
|
+
def sourced_by(
|
|
72
|
+
self, provider: Provider, endpoint: str
|
|
73
|
+
) -> tuple[RosterDefinition, ...]:
|
|
74
|
+
"""Return the roster definitions sourced by one feeder endpoint.
|
|
75
|
+
|
|
76
|
+
The reverse lookup the feeder tap reads: which rosters must be
|
|
77
|
+
reconciled when this ``(provider, endpoint)`` runs. A linear scan --
|
|
78
|
+
the catalog holds a handful of definitions -- in registration order.
|
|
79
|
+
|
|
80
|
+
Args:
|
|
81
|
+
provider: The feeder's provider.
|
|
82
|
+
endpoint: The feeder's endpoint name (e.g. ``'vehicles'``).
|
|
83
|
+
|
|
84
|
+
Returns:
|
|
85
|
+
The definitions whose ``source_endpoint`` is this endpoint; empty
|
|
86
|
+
when the endpoint sources no roster.
|
|
87
|
+
"""
|
|
88
|
+
return tuple(
|
|
89
|
+
definition
|
|
90
|
+
for definition in self._by_key.values()
|
|
91
|
+
if definition.key.provider is provider
|
|
92
|
+
and definition.source_endpoint == endpoint
|
|
93
|
+
)
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# src/fleetpull/state/__init__.py
|
|
2
|
+
"""SQLite operational state store: the database lifecycle (connections, schema
|
|
3
|
+
migrations, integrity verification) and the stores over it -- incremental
|
|
4
|
+
cursors, the run ledger, feeder rosters, and the backfill work-unit queue."""
|
|
5
|
+
|
|
6
|
+
from fleetpull.state.cursors import CursorKind, CursorStore
|
|
7
|
+
from fleetpull.state.database import StateDatabase
|
|
8
|
+
from fleetpull.state.migrations import migrate_to_head
|
|
9
|
+
from fleetpull.state.reconcile import RosterDelta, is_roster_stale, reconcile
|
|
10
|
+
from fleetpull.state.rosters import RosterStore
|
|
11
|
+
from fleetpull.state.run_ledger import RunLedger, RunStatus
|
|
12
|
+
from fleetpull.state.work_units import (
|
|
13
|
+
ClaimedWorkUnit,
|
|
14
|
+
WorkUnitProgress,
|
|
15
|
+
WorkUnitSpec,
|
|
16
|
+
WorkUnitStatus,
|
|
17
|
+
WorkUnitStore,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
__all__: list[str] = [
|
|
21
|
+
'ClaimedWorkUnit',
|
|
22
|
+
'CursorKind',
|
|
23
|
+
'CursorStore',
|
|
24
|
+
'RosterDelta',
|
|
25
|
+
'RosterStore',
|
|
26
|
+
'RunLedger',
|
|
27
|
+
'RunStatus',
|
|
28
|
+
'StateDatabase',
|
|
29
|
+
'WorkUnitProgress',
|
|
30
|
+
'WorkUnitSpec',
|
|
31
|
+
'WorkUnitStatus',
|
|
32
|
+
'WorkUnitStore',
|
|
33
|
+
'is_roster_stale',
|
|
34
|
+
'migrate_to_head',
|
|
35
|
+
'reconcile',
|
|
36
|
+
]
|