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.
Files changed (252) hide show
  1. fleetpull/__init__.py +36 -0
  2. fleetpull/api/__init__.py +32 -0
  3. fleetpull/api/auth_ingress.py +216 -0
  4. fleetpull/api/catalog.py +139 -0
  5. fleetpull/api/fetch.py +229 -0
  6. fleetpull/api/identity.py +90 -0
  7. fleetpull/api/sync.py +735 -0
  8. fleetpull/cli.py +146 -0
  9. fleetpull/config/__init__.py +47 -0
  10. fleetpull/config/base.py +18 -0
  11. fleetpull/config/example.py +90 -0
  12. fleetpull/config/geotab.py +78 -0
  13. fleetpull/config/http.py +32 -0
  14. fleetpull/config/loading.py +195 -0
  15. fleetpull/config/logger.py +139 -0
  16. fleetpull/config/providers.py +520 -0
  17. fleetpull/config/rate_limit.py +50 -0
  18. fleetpull/config/resolution.py +156 -0
  19. fleetpull/config/retry.py +69 -0
  20. fleetpull/config/root.py +157 -0
  21. fleetpull/config/sections.py +146 -0
  22. fleetpull/endpoints/__init__.py +24 -0
  23. fleetpull/endpoints/geotab/__init__.py +12 -0
  24. fleetpull/endpoints/geotab/_requests.py +427 -0
  25. fleetpull/endpoints/geotab/annotation_logs.py +73 -0
  26. fleetpull/endpoints/geotab/audits.py +70 -0
  27. fleetpull/endpoints/geotab/devices.py +79 -0
  28. fleetpull/endpoints/geotab/driver_changes.py +73 -0
  29. fleetpull/endpoints/geotab/duty_status_logs.py +75 -0
  30. fleetpull/endpoints/geotab/dvir_logs.py +76 -0
  31. fleetpull/endpoints/geotab/exception_events.py +119 -0
  32. fleetpull/endpoints/geotab/fault_data.py +72 -0
  33. fleetpull/endpoints/geotab/fill_ups.py +79 -0
  34. fleetpull/endpoints/geotab/fuel_and_energy_used.py +74 -0
  35. fleetpull/endpoints/geotab/fuel_tax_details.py +75 -0
  36. fleetpull/endpoints/geotab/log_records.py +74 -0
  37. fleetpull/endpoints/geotab/media_files.py +74 -0
  38. fleetpull/endpoints/geotab/shipment_logs.py +71 -0
  39. fleetpull/endpoints/geotab/status_data.py +71 -0
  40. fleetpull/endpoints/geotab/text_messages.py +77 -0
  41. fleetpull/endpoints/geotab/trips.py +102 -0
  42. fleetpull/endpoints/geotab/users.py +81 -0
  43. fleetpull/endpoints/motive/__init__.py +12 -0
  44. fleetpull/endpoints/motive/_spec_builders.py +94 -0
  45. fleetpull/endpoints/motive/driver_idle_rollups.py +113 -0
  46. fleetpull/endpoints/motive/driving_periods.py +85 -0
  47. fleetpull/endpoints/motive/groups.py +61 -0
  48. fleetpull/endpoints/motive/idle_events.py +94 -0
  49. fleetpull/endpoints/motive/users.py +64 -0
  50. fleetpull/endpoints/motive/vehicle_locations.py +170 -0
  51. fleetpull/endpoints/motive/vehicle_utilizations.py +122 -0
  52. fleetpull/endpoints/motive/vehicles.py +106 -0
  53. fleetpull/endpoints/registry.py +280 -0
  54. fleetpull/endpoints/samsara/__init__.py +12 -0
  55. fleetpull/endpoints/samsara/_spec_builders.py +202 -0
  56. fleetpull/endpoints/samsara/addresses.py +77 -0
  57. fleetpull/endpoints/samsara/asset_locations.py +217 -0
  58. fleetpull/endpoints/samsara/driver_fuel_energy_reports.py +124 -0
  59. fleetpull/endpoints/samsara/driver_vehicle_assignments.py +211 -0
  60. fleetpull/endpoints/samsara/drivers.py +169 -0
  61. fleetpull/endpoints/samsara/engine_states.py +114 -0
  62. fleetpull/endpoints/samsara/gps_readings.py +113 -0
  63. fleetpull/endpoints/samsara/idling_events.py +195 -0
  64. fleetpull/endpoints/samsara/odometer_readings.py +116 -0
  65. fleetpull/endpoints/samsara/trips.py +217 -0
  66. fleetpull/endpoints/samsara/vehicle_fuel_energy_reports.py +139 -0
  67. fleetpull/endpoints/samsara/vehicles.py +121 -0
  68. fleetpull/endpoints/shared/__init__.py +57 -0
  69. fleetpull/endpoints/shared/base.py +529 -0
  70. fleetpull/endpoints/shared/request_shape.py +227 -0
  71. fleetpull/endpoints/shared/resume.py +74 -0
  72. fleetpull/endpoints/shared/spec_builders.py +59 -0
  73. fleetpull/endpoints/shared/sync_mode.py +159 -0
  74. fleetpull/endpoints/shared/url_paths.py +149 -0
  75. fleetpull/exceptions.py +320 -0
  76. fleetpull/incremental/__init__.py +27 -0
  77. fleetpull/incremental/cursor.py +66 -0
  78. fleetpull/incremental/resolution.py +152 -0
  79. fleetpull/incremental/seed.py +53 -0
  80. fleetpull/incremental/window.py +94 -0
  81. fleetpull/logger/__init__.py +5 -0
  82. fleetpull/logger/setup.py +145 -0
  83. fleetpull/model_contract/__init__.py +6 -0
  84. fleetpull/model_contract/coercions.py +33 -0
  85. fleetpull/model_contract/response.py +45 -0
  86. fleetpull/models/__init__.py +7 -0
  87. fleetpull/models/geotab/__init__.py +166 -0
  88. fleetpull/models/geotab/annotation_log.py +109 -0
  89. fleetpull/models/geotab/audit.py +56 -0
  90. fleetpull/models/geotab/device.py +217 -0
  91. fleetpull/models/geotab/driver_change.py +102 -0
  92. fleetpull/models/geotab/duty_status_log.py +200 -0
  93. fleetpull/models/geotab/dvir_log.py +184 -0
  94. fleetpull/models/geotab/exception_event.py +135 -0
  95. fleetpull/models/geotab/fault_data.py +168 -0
  96. fleetpull/models/geotab/fill_up.py +189 -0
  97. fleetpull/models/geotab/fuel_and_energy_used.py +81 -0
  98. fleetpull/models/geotab/fuel_tax_detail.py +150 -0
  99. fleetpull/models/geotab/log_record.py +68 -0
  100. fleetpull/models/geotab/media_file.py +133 -0
  101. fleetpull/models/geotab/shared.py +221 -0
  102. fleetpull/models/geotab/shipment_log.py +105 -0
  103. fleetpull/models/geotab/status_data.py +108 -0
  104. fleetpull/models/geotab/text_message.py +125 -0
  105. fleetpull/models/geotab/trip.py +162 -0
  106. fleetpull/models/geotab/user.py +187 -0
  107. fleetpull/models/motive/__init__.py +43 -0
  108. fleetpull/models/motive/driver_idle_rollup.py +109 -0
  109. fleetpull/models/motive/driving_period.py +82 -0
  110. fleetpull/models/motive/group.py +49 -0
  111. fleetpull/models/motive/idle_event.py +77 -0
  112. fleetpull/models/motive/shared.py +192 -0
  113. fleetpull/models/motive/user.py +217 -0
  114. fleetpull/models/motive/vehicle.py +162 -0
  115. fleetpull/models/motive/vehicle_location.py +127 -0
  116. fleetpull/models/motive/vehicle_utilization.py +128 -0
  117. fleetpull/models/samsara/__init__.py +108 -0
  118. fleetpull/models/samsara/address.py +149 -0
  119. fleetpull/models/samsara/asset_location.py +131 -0
  120. fleetpull/models/samsara/driver.py +232 -0
  121. fleetpull/models/samsara/driver_fuel_energy_report.py +153 -0
  122. fleetpull/models/samsara/driver_vehicle_assignment.py +168 -0
  123. fleetpull/models/samsara/engine_state.py +92 -0
  124. fleetpull/models/samsara/gps_reading.py +146 -0
  125. fleetpull/models/samsara/idling_event.py +174 -0
  126. fleetpull/models/samsara/odometer_reading.py +90 -0
  127. fleetpull/models/samsara/trip.py +199 -0
  128. fleetpull/models/samsara/vehicle.py +167 -0
  129. fleetpull/models/samsara/vehicle_fuel_energy_report.py +191 -0
  130. fleetpull/network/__init__.py +9 -0
  131. fleetpull/network/auth/__init__.py +15 -0
  132. fleetpull/network/auth/authenticate.py +271 -0
  133. fleetpull/network/auth/manager.py +223 -0
  134. fleetpull/network/auth/models.py +57 -0
  135. fleetpull/network/auth/strategies.py +169 -0
  136. fleetpull/network/classifiers/__init__.py +11 -0
  137. fleetpull/network/classifiers/geotab.py +145 -0
  138. fleetpull/network/classifiers/motive.py +79 -0
  139. fleetpull/network/classifiers/samsara.py +80 -0
  140. fleetpull/network/client/__init__.py +25 -0
  141. fleetpull/network/client/page.py +32 -0
  142. fleetpull/network/client/profile.py +27 -0
  143. fleetpull/network/client/registry.py +103 -0
  144. fleetpull/network/client/registry_base.py +150 -0
  145. fleetpull/network/client/runtime.py +44 -0
  146. fleetpull/network/client/transport.py +381 -0
  147. fleetpull/network/contract/__init__.py +50 -0
  148. fleetpull/network/contract/auth.py +48 -0
  149. fleetpull/network/contract/classifier.py +189 -0
  150. fleetpull/network/contract/envelope_fetcher.py +33 -0
  151. fleetpull/network/contract/envelopes.py +189 -0
  152. fleetpull/network/contract/outcome.py +43 -0
  153. fleetpull/network/contract/page_decoder.py +101 -0
  154. fleetpull/network/contract/request.py +97 -0
  155. fleetpull/network/decoders/__init__.py +40 -0
  156. fleetpull/network/decoders/_window_stamp.py +78 -0
  157. fleetpull/network/decoders/geotab.py +309 -0
  158. fleetpull/network/decoders/motive.py +204 -0
  159. fleetpull/network/decoders/motive_reports.py +114 -0
  160. fleetpull/network/decoders/samsara.py +343 -0
  161. fleetpull/network/decoders/samsara_reports.py +119 -0
  162. fleetpull/network/decoders/single_page.py +55 -0
  163. fleetpull/network/limits/__init__.py +13 -0
  164. fleetpull/network/limits/bucket_math.py +58 -0
  165. fleetpull/network/limits/limiter.py +153 -0
  166. fleetpull/network/limits/registry.py +96 -0
  167. fleetpull/network/posture/__init__.py +6 -0
  168. fleetpull/network/posture/client_options.py +96 -0
  169. fleetpull/network/retry/__init__.py +13 -0
  170. fleetpull/network/retry/decision.py +155 -0
  171. fleetpull/network/tls/__init__.py +5 -0
  172. fleetpull/network/tls/truststore_context.py +35 -0
  173. fleetpull/orchestrator/__init__.py +30 -0
  174. fleetpull/orchestrator/backfill.py +127 -0
  175. fleetpull/orchestrator/batch.py +155 -0
  176. fleetpull/orchestrator/bisection.py +271 -0
  177. fleetpull/orchestrator/drivers.py +350 -0
  178. fleetpull/orchestrator/entry.py +347 -0
  179. fleetpull/orchestrator/executors.py +128 -0
  180. fleetpull/orchestrator/fanout.py +163 -0
  181. fleetpull/orchestrator/feed_drive.py +266 -0
  182. fleetpull/orchestrator/metadata_projection.py +195 -0
  183. fleetpull/orchestrator/outcome.py +53 -0
  184. fleetpull/orchestrator/recording.py +85 -0
  185. fleetpull/orchestrator/resume.py +143 -0
  186. fleetpull/orchestrator/roster_harvest.py +108 -0
  187. fleetpull/orchestrator/roster_refresh.py +363 -0
  188. fleetpull/orchestrator/runner.py +262 -0
  189. fleetpull/orchestrator/shape_resolution.py +221 -0
  190. fleetpull/orchestrator/spine.py +166 -0
  191. fleetpull/orchestrator/streaming.py +153 -0
  192. fleetpull/orchestrator/unit_loop.py +273 -0
  193. fleetpull/orchestrator/watermark_drive.py +455 -0
  194. fleetpull/paths/__init__.py +13 -0
  195. fleetpull/paths/datasets.py +38 -0
  196. fleetpull/paths/partitions.py +41 -0
  197. fleetpull/paths/resolution.py +98 -0
  198. fleetpull/polars_typing/__init__.py +20 -0
  199. fleetpull/py.typed +0 -0
  200. fleetpull/records/__init__.py +19 -0
  201. fleetpull/records/convert.py +53 -0
  202. fleetpull/records/dataframe.py +63 -0
  203. fleetpull/records/event_time.py +70 -0
  204. fleetpull/records/fields.py +190 -0
  205. fleetpull/records/flatten.py +54 -0
  206. fleetpull/records/roster_members.py +74 -0
  207. fleetpull/records/schema.py +73 -0
  208. fleetpull/records/validation.py +62 -0
  209. fleetpull/resources/__init__.py +10 -0
  210. fleetpull/resources/config.example.yaml +222 -0
  211. fleetpull/roster/__init__.py +16 -0
  212. fleetpull/roster/definition.py +46 -0
  213. fleetpull/roster/key.py +40 -0
  214. fleetpull/roster/registry.py +93 -0
  215. fleetpull/state/__init__.py +36 -0
  216. fleetpull/state/cursors.py +417 -0
  217. fleetpull/state/database.py +463 -0
  218. fleetpull/state/migrations.py +430 -0
  219. fleetpull/state/reconcile.py +127 -0
  220. fleetpull/state/rosters.py +159 -0
  221. fleetpull/state/run_ledger.py +572 -0
  222. fleetpull/state/work_units.py +648 -0
  223. fleetpull/storage/__init__.py +30 -0
  224. fleetpull/storage/append.py +187 -0
  225. fleetpull/storage/atomic.py +161 -0
  226. fleetpull/storage/files.py +176 -0
  227. fleetpull/storage/frames.py +98 -0
  228. fleetpull/storage/metadata.py +161 -0
  229. fleetpull/storage/partitioned.py +205 -0
  230. fleetpull/storage/pruning.py +169 -0
  231. fleetpull/storage/read.py +35 -0
  232. fleetpull/storage/result.py +34 -0
  233. fleetpull/storage/single_file.py +130 -0
  234. fleetpull/storage/splitting.py +88 -0
  235. fleetpull/storage/staging.py +178 -0
  236. fleetpull/storage/writers.py +145 -0
  237. fleetpull/timing/__init__.py +21 -0
  238. fleetpull/timing/canon.py +92 -0
  239. fleetpull/timing/clock.py +192 -0
  240. fleetpull/timing/codec.py +122 -0
  241. fleetpull/timing/sleeper.py +65 -0
  242. fleetpull/vocabulary/__init__.py +16 -0
  243. fleetpull/vocabulary/json_types.py +20 -0
  244. fleetpull/vocabulary/provider.py +29 -0
  245. fleetpull/vocabulary/quota_scope.py +47 -0
  246. fleetpull/vocabulary/response_category.py +32 -0
  247. fleetpull-0.1.0.dist-info/METADATA +248 -0
  248. fleetpull-0.1.0.dist-info/RECORD +252 -0
  249. fleetpull-0.1.0.dist-info/WHEEL +5 -0
  250. fleetpull-0.1.0.dist-info/entry_points.txt +2 -0
  251. fleetpull-0.1.0.dist-info/licenses/LICENSE +202 -0
  252. 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
@@ -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
+ ]