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,159 @@
|
|
|
1
|
+
# src/fleetpull/state/rosters.py
|
|
2
|
+
"""The fan-out roster store: the persisted set of fan-out keys, keyed by ``RosterKey``.
|
|
3
|
+
|
|
4
|
+
A ``rosters`` row is one member (``member``) of one roster, identified by a
|
|
5
|
+
``RosterKey`` ``(provider, name)`` -- the per-vehicle id set ``vehicle_locations``
|
|
6
|
+
fans out over, listed from the ``vehicles`` feeder and kept here so the fan-out reads
|
|
7
|
+
the roster, never the feeder's output parquet (the user's product, not fleetpull's
|
|
8
|
+
control plane). The roster's source endpoint and column are not stored here; they live
|
|
9
|
+
in its ``RosterDefinition``. Members are opaque strings; fleetpull never maps one back
|
|
10
|
+
to a VIN -- the VIN rides the fan-out response and the mapping is irrelevant to the
|
|
11
|
+
call.
|
|
12
|
+
|
|
13
|
+
This module ships ``RosterStore``, the table's read/write orchestrator; the pure
|
|
14
|
+
reconciliation half it applies -- ``RosterDelta``, ``reconcile``,
|
|
15
|
+
``is_roster_stale`` -- lives beside it in ``state/reconcile.py``. Refresh is
|
|
16
|
+
reconcile-then-apply: list the feeder, ``reconcile`` the listing against the
|
|
17
|
+
current roster into a three-set delta (reset, increment, evict), and ``apply``
|
|
18
|
+
it in one transaction. ``RosterStore`` keys by ``RosterKey`` (the ``roster/``
|
|
19
|
+
leaf), which ``state`` may import since the leaf sits below it.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
from typing import Final
|
|
23
|
+
|
|
24
|
+
from fleetpull.roster import RosterKey
|
|
25
|
+
from fleetpull.state.database import (
|
|
26
|
+
StateDatabase,
|
|
27
|
+
expect_int,
|
|
28
|
+
expect_text,
|
|
29
|
+
)
|
|
30
|
+
from fleetpull.state.reconcile import RosterDelta
|
|
31
|
+
|
|
32
|
+
__all__: list[str] = ['RosterStore']
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
_READ_COUNTS_SQL: Final[str] = """
|
|
36
|
+
SELECT member, absence_count FROM rosters
|
|
37
|
+
WHERE provider = ? AND name = ?
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
_READ_MEMBERS_SQL: Final[str] = """
|
|
41
|
+
SELECT member FROM rosters
|
|
42
|
+
WHERE provider = ? AND name = ?
|
|
43
|
+
ORDER BY member
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
_UPSERT_ZERO_SQL: Final[str] = """
|
|
47
|
+
INSERT INTO rosters (provider, name, member, absence_count)
|
|
48
|
+
VALUES (?, ?, ?, 0)
|
|
49
|
+
ON CONFLICT (provider, name, member)
|
|
50
|
+
DO UPDATE SET absence_count = 0
|
|
51
|
+
"""
|
|
52
|
+
|
|
53
|
+
_INCREMENT_SQL: Final[str] = """
|
|
54
|
+
UPDATE rosters SET absence_count = absence_count + 1
|
|
55
|
+
WHERE provider = ? AND name = ? AND member = ?
|
|
56
|
+
"""
|
|
57
|
+
|
|
58
|
+
_DELETE_SQL: Final[str] = """
|
|
59
|
+
DELETE FROM rosters
|
|
60
|
+
WHERE provider = ? AND name = ? AND member = ?
|
|
61
|
+
"""
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class RosterStore:
|
|
65
|
+
"""Read/write orchestrator for the ``rosters`` table.
|
|
66
|
+
|
|
67
|
+
Translates between the fan-out's needs and ``rosters`` rows, scoped per roster by
|
|
68
|
+
``RosterKey``. Holds a ``StateDatabase`` and nothing else; runs after
|
|
69
|
+
``migrate_to_head`` (the ``rosters`` table must exist). The store keys by
|
|
70
|
+
``RosterKey`` from the ``roster/`` leaf -- ``state`` may import the leaf, which
|
|
71
|
+
sits below both ``state`` and ``endpoints``; the source endpoint and column live
|
|
72
|
+
in the ``RosterDefinition``.
|
|
73
|
+
"""
|
|
74
|
+
|
|
75
|
+
def __init__(self, database: StateDatabase) -> None:
|
|
76
|
+
"""Bind the store to a state database.
|
|
77
|
+
|
|
78
|
+
Args:
|
|
79
|
+
database: The state database whose ``rosters`` table this reads and writes;
|
|
80
|
+
must already be migrated (the ``rosters`` table must exist).
|
|
81
|
+
"""
|
|
82
|
+
self._database = database
|
|
83
|
+
|
|
84
|
+
def read_counts(self, key: RosterKey) -> dict[str, int]:
|
|
85
|
+
"""Read the roster as ``{member: absence_count}`` for ``reconcile``.
|
|
86
|
+
|
|
87
|
+
Args:
|
|
88
|
+
key: The roster to read.
|
|
89
|
+
|
|
90
|
+
Returns:
|
|
91
|
+
Every roster member mapped to its absence count; empty when the roster
|
|
92
|
+
holds no rows.
|
|
93
|
+
|
|
94
|
+
Raises:
|
|
95
|
+
RuntimeError: A row's ``member`` or ``absence_count`` came back the wrong
|
|
96
|
+
type, violating the STRICT schema contract.
|
|
97
|
+
|
|
98
|
+
Side Effects:
|
|
99
|
+
Opens a connection and reads.
|
|
100
|
+
"""
|
|
101
|
+
with self._database.connect() as connection:
|
|
102
|
+
rows = connection.execute(
|
|
103
|
+
_READ_COUNTS_SQL, (key.provider.value, key.name)
|
|
104
|
+
).fetchall()
|
|
105
|
+
counts: dict[str, int] = {}
|
|
106
|
+
for row in rows:
|
|
107
|
+
counts[expect_text(row[0], 'rosters.member')] = expect_int(
|
|
108
|
+
row[1], 'rosters.absence_count'
|
|
109
|
+
)
|
|
110
|
+
return counts
|
|
111
|
+
|
|
112
|
+
def read_members(self, key: RosterKey) -> list[str]:
|
|
113
|
+
"""Read the roster's members for the fan-out, ascending.
|
|
114
|
+
|
|
115
|
+
Args:
|
|
116
|
+
key: The roster to read.
|
|
117
|
+
|
|
118
|
+
Returns:
|
|
119
|
+
The roster members as strings, ordered ascending for a deterministic
|
|
120
|
+
fan-out; empty when the roster holds no rows.
|
|
121
|
+
|
|
122
|
+
Raises:
|
|
123
|
+
RuntimeError: A row's ``member`` came back non-text, violating the STRICT
|
|
124
|
+
schema contract.
|
|
125
|
+
|
|
126
|
+
Side Effects:
|
|
127
|
+
Opens a connection and reads.
|
|
128
|
+
"""
|
|
129
|
+
with self._database.connect() as connection:
|
|
130
|
+
rows = connection.execute(
|
|
131
|
+
_READ_MEMBERS_SQL, (key.provider.value, key.name)
|
|
132
|
+
).fetchall()
|
|
133
|
+
return [expect_text(row[0], 'rosters.member') for row in rows]
|
|
134
|
+
|
|
135
|
+
def apply(self, key: RosterKey, delta: RosterDelta) -> None:
|
|
136
|
+
"""Apply a reconciliation delta in one transaction.
|
|
137
|
+
|
|
138
|
+
Upserts ``to_zero`` at count zero, increments ``to_increment``, deletes
|
|
139
|
+
``to_evict`` -- all in one transaction, so a refresh is atomic. An empty
|
|
140
|
+
set is a no-op batch; an all-empty delta touches nothing.
|
|
141
|
+
|
|
142
|
+
Args:
|
|
143
|
+
key: The roster to apply the delta to.
|
|
144
|
+
delta: The reset / increment / evict sets from ``reconcile``.
|
|
145
|
+
|
|
146
|
+
Side Effects:
|
|
147
|
+
Opens a connection, writes up to three statement batches, and commits.
|
|
148
|
+
"""
|
|
149
|
+
scope = (key.provider.value, key.name)
|
|
150
|
+
with self._database.transaction() as connection:
|
|
151
|
+
connection.executemany(
|
|
152
|
+
_UPSERT_ZERO_SQL, [(*scope, member) for member in delta.to_zero]
|
|
153
|
+
)
|
|
154
|
+
connection.executemany(
|
|
155
|
+
_INCREMENT_SQL, [(*scope, member) for member in delta.to_increment]
|
|
156
|
+
)
|
|
157
|
+
connection.executemany(
|
|
158
|
+
_DELETE_SQL, [(*scope, member) for member in delta.to_evict]
|
|
159
|
+
)
|