aiomobilitydatabase 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.
@@ -0,0 +1,443 @@
1
+ """Models for the Mobility Database catalog API."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass, field
6
+ from datetime import datetime
7
+ from enum import StrEnum
8
+
9
+ from mashumaro import DataClassDictMixin
10
+
11
+
12
+ class DataType(StrEnum):
13
+ """Type of data a feed provides."""
14
+
15
+ GTFS = "gtfs"
16
+ GTFS_RT = "gtfs_rt"
17
+ GBFS = "gbfs"
18
+
19
+
20
+ class FeedStatus(StrEnum):
21
+ """Lifecycle status of a feed."""
22
+
23
+ ACTIVE = "active"
24
+ DEPRECATED = "deprecated"
25
+ INACTIVE = "inactive"
26
+ DEVELOPMENT = "development"
27
+ FUTURE = "future"
28
+
29
+
30
+ class EntityType(StrEnum):
31
+ """GTFS Realtime entity type."""
32
+
33
+ VEHICLE_POSITIONS = "vp"
34
+ TRIP_UPDATES = "tu"
35
+ SERVICE_ALERTS = "sa"
36
+
37
+
38
+ class LocationType(StrEnum):
39
+ """Type of a location in location search results."""
40
+
41
+ COUNTRY = "country"
42
+ SUBDIVISION = "subdivision"
43
+ MUNICIPALITY = "municipality"
44
+
45
+
46
+ class GbfsVersionSource(StrEnum):
47
+ """Origin of GBFS version information."""
48
+
49
+ AUTODISCOVERY = "autodiscovery"
50
+ GBFS_VERSIONS = "gbfs_versions"
51
+
52
+
53
+ class BoundingFilterMethod(StrEnum):
54
+ """Filtering method for bounding-box queries."""
55
+
56
+ COMPLETELY_ENCLOSED = "completely_enclosed"
57
+ PARTIALLY_ENCLOSED = "partially_enclosed"
58
+ DISJOINT = "disjoint"
59
+
60
+
61
+ class SortOrder(StrEnum):
62
+ """Sort order for list endpoints that support it."""
63
+
64
+ ASC = "asc"
65
+ DESC = "desc"
66
+
67
+
68
+ class LicenseRuleType(StrEnum):
69
+ """Type of a license rule."""
70
+
71
+ PERMISSION = "permission"
72
+ CONDITION = "condition"
73
+ LIMITATION = "limitation"
74
+
75
+
76
+ class RequestMethod(StrEnum):
77
+ """HTTP method used for a feed availability check."""
78
+
79
+ HEAD = "HEAD"
80
+ GET = "GET"
81
+
82
+
83
+ @dataclass
84
+ class Redirect(DataClassDictMixin):
85
+ """A feed redirect to a replacement feed ID."""
86
+
87
+ target_id: str | None = None
88
+ comment: str | None = None
89
+
90
+
91
+ @dataclass
92
+ class ExternalId(DataClassDictMixin):
93
+ """An ID for the feed in an external or legacy database."""
94
+
95
+ external_id: str | None = None
96
+ source: str | None = None
97
+
98
+
99
+ @dataclass
100
+ class SourceInfo(DataClassDictMixin):
101
+ """Information about the feed producer's source URL and license."""
102
+
103
+ producer_url: str | None = None
104
+ is_producer_url_unstable: bool | None = None
105
+ authentication_type: int | None = None
106
+ authentication_info_url: str | None = None
107
+ api_key_parameter_name: str | None = None
108
+ license_url: str | None = None
109
+ license_id: str | None = None
110
+ license_is_spdx: bool | None = None
111
+ license_notes: str | None = None
112
+ license_tags: list[str] | None = None
113
+
114
+
115
+ @dataclass
116
+ class BoundingBox(DataClassDictMixin):
117
+ """Geographic bounding box of a dataset."""
118
+
119
+ minimum_latitude: float | None = None
120
+ maximum_latitude: float | None = None
121
+ minimum_longitude: float | None = None
122
+ maximum_longitude: float | None = None
123
+
124
+
125
+ @dataclass
126
+ class Location(DataClassDictMixin):
127
+ """Geographic location served by a feed."""
128
+
129
+ country_code: str | None = None
130
+ country: str | None = None
131
+ subdivision_name: str | None = None
132
+ municipality: str | None = None
133
+
134
+
135
+ @dataclass
136
+ class FeedRelatedLink(DataClassDictMixin):
137
+ """A link related to a feed."""
138
+
139
+ code: str | None = None
140
+ description: str | None = None
141
+ url: str | None = None
142
+ created_at: datetime | None = None
143
+
144
+
145
+ @dataclass
146
+ class ValidationReportSummary(DataClassDictMixin):
147
+ """Validation counts embedded in a latest-dataset summary."""
148
+
149
+ features: list[str] | None = None
150
+ total_error: int | None = None
151
+ total_warning: int | None = None
152
+ total_info: int | None = None
153
+ unique_error_count: int | None = None
154
+ unique_warning_count: int | None = None
155
+ unique_info_count: int | None = None
156
+
157
+
158
+ @dataclass
159
+ class LatestDataset(DataClassDictMixin):
160
+ """Summary of the latest dataset for a GTFS feed."""
161
+
162
+ id: str | None = None
163
+ hosted_url: str | None = None
164
+ bounding_box: BoundingBox | None = None
165
+ downloaded_at: datetime | None = None
166
+ hash: str | None = None
167
+ hash_md5: str | None = None
168
+ service_date_range_start: datetime | None = None
169
+ service_date_range_end: datetime | None = None
170
+ agency_timezone: str | None = None
171
+ zipped_folder_size_mb: float | None = None
172
+ unzipped_folder_size_mb: float | None = None
173
+ validation_report: ValidationReportSummary | None = None
174
+
175
+
176
+ @dataclass
177
+ class Feed(DataClassDictMixin):
178
+ """Common feed fields shared by all feed types."""
179
+
180
+ id: str | None = None
181
+ data_type: DataType | None = None
182
+ created_at: datetime | None = None
183
+ external_ids: list[ExternalId] | None = None
184
+ provider: str | None = None
185
+ feed_contact_email: str | None = None
186
+ source_info: SourceInfo | None = None
187
+ redirects: list[Redirect] | None = None
188
+ status: FeedStatus | None = None
189
+ official: bool | None = None
190
+ official_updated_at: datetime | None = None
191
+ feed_name: str | None = None
192
+ note: str | None = None
193
+ related_links: list[FeedRelatedLink] | None = None
194
+
195
+
196
+ @dataclass
197
+ class GtfsFeed(Feed):
198
+ """A GTFS schedule feed."""
199
+
200
+ locations: list[Location] | None = None
201
+ latest_dataset: LatestDataset | None = None
202
+ bounding_box: BoundingBox | None = None
203
+ visualization_dataset_id: str | None = None
204
+
205
+
206
+ @dataclass
207
+ class GtfsRtFeed(Feed):
208
+ """A GTFS Realtime feed."""
209
+
210
+ entity_types: list[EntityType] | None = None
211
+ feed_references: list[str] | None = None
212
+ locations: list[Location] | None = None
213
+
214
+
215
+ @dataclass
216
+ class GbfsEndpoint(DataClassDictMixin):
217
+ """An endpoint available in a GBFS version."""
218
+
219
+ name: str | None = None
220
+ url: str | None = None
221
+ language: str | None = None
222
+ is_feature: bool | None = None
223
+
224
+
225
+ @dataclass
226
+ class GbfsValidationReport(DataClassDictMixin):
227
+ """A validation report for a GBFS feed version."""
228
+
229
+ validated_at: datetime | None = None
230
+ total_error: int | None = None
231
+ report_summary_url: str | None = None
232
+ validator_version: str | None = None
233
+
234
+
235
+ @dataclass
236
+ class GbfsVersion(DataClassDictMixin):
237
+ """A GBFS specification version supported by a feed."""
238
+
239
+ version: str | None = None
240
+ created_at: datetime | None = None
241
+ last_updated_at: datetime | None = None
242
+ source: GbfsVersionSource | None = None
243
+ endpoints: list[GbfsEndpoint] | None = None
244
+ latest_validation_report: GbfsValidationReport | None = None
245
+
246
+
247
+ @dataclass
248
+ class GbfsFeed(Feed):
249
+ """A GBFS feed.
250
+
251
+ The spec derives GbfsFeed from BasicFeed (without status/official/related
252
+ fields); sharing the Feed base here is harmless since all fields are
253
+ optional and unknown keys are ignored.
254
+ """
255
+
256
+ locations: list[Location] | None = None
257
+ system_id: str | None = None
258
+ provider_url: str | None = None
259
+ versions: list[GbfsVersion] | None = None
260
+ bounding_box: BoundingBox | None = None
261
+ bounding_box_generated_at: datetime | None = None
262
+
263
+
264
+ @dataclass
265
+ class SearchFeedItemResult(DataClassDictMixin):
266
+ """A single feed result from the search endpoint (union of feed types)."""
267
+
268
+ id: str
269
+ data_type: DataType
270
+ status: FeedStatus
271
+ created_at: datetime | None = None
272
+ official: bool | None = None
273
+ external_ids: list[ExternalId] | None = None
274
+ provider: str | None = None
275
+ feed_name: str | None = None
276
+ note: str | None = None
277
+ feed_contact_email: str | None = None
278
+ source_info: SourceInfo | None = None
279
+ redirects: list[Redirect] | None = None
280
+ locations: list[Location] | None = None
281
+ latest_dataset: LatestDataset | None = None
282
+ entity_types: list[EntityType] | None = None
283
+ versions: list[str] | None = None
284
+ feed_references: list[str] | None = None
285
+
286
+
287
+ @dataclass
288
+ class SearchResults(DataClassDictMixin):
289
+ """Response from the feed search endpoint."""
290
+
291
+ total: int | None = None
292
+ results: list[SearchFeedItemResult] = field(default_factory=list)
293
+
294
+
295
+ @dataclass
296
+ class ValidationReport(DataClassDictMixin):
297
+ """Full validation report for a GTFS dataset."""
298
+
299
+ validated_at: datetime | None = None
300
+ features: list[str] | None = None
301
+ validator_version: str | None = None
302
+ total_error: int | None = None
303
+ total_warning: int | None = None
304
+ total_info: int | None = None
305
+ unique_error_count: int | None = None
306
+ unique_warning_count: int | None = None
307
+ unique_info_count: int | None = None
308
+ url_json: str | None = None
309
+ url_html: str | None = None
310
+
311
+
312
+ @dataclass
313
+ class GtfsDataset(DataClassDictMixin):
314
+ """A GTFS dataset for a feed."""
315
+
316
+ id: str | None = None
317
+ feed_id: str | None = None
318
+ hosted_url: str | None = None
319
+ note: str | None = None
320
+ downloaded_at: datetime | None = None
321
+ hash: str | None = None
322
+ hash_md5: str | None = None
323
+ bounding_box: BoundingBox | None = None
324
+ validation_report: ValidationReport | None = None
325
+ service_date_range_start: datetime | None = None
326
+ service_date_range_end: datetime | None = None
327
+ agency_timezone: str | None = None
328
+ zipped_folder_size_mb: float | None = None
329
+ unzipped_folder_size_mb: float | None = None
330
+
331
+
332
+ @dataclass
333
+ class GtfsFeedAvailabilityCheck(DataClassDictMixin):
334
+ """A single availability check for a GTFS feed."""
335
+
336
+ checked_at: datetime
337
+ success: bool
338
+ request_method: RequestMethod
339
+ status_code: int | None = None
340
+ latency_ms: float | None = None
341
+ error_type: str | None = None
342
+
343
+
344
+ @dataclass
345
+ class GtfsFeedAvailability(DataClassDictMixin):
346
+ """Availability check history for a GTFS feed."""
347
+
348
+ feed_id: str
349
+ total: int
350
+ offset: int
351
+ limit: int
352
+ checks: list[GtfsFeedAvailabilityCheck] = field(default_factory=list)
353
+
354
+
355
+ @dataclass
356
+ class LocationSearchResult(DataClassDictMixin):
357
+ """A single location from the locations search endpoint."""
358
+
359
+ location_id: int | None = None
360
+ parent_location_id: int | None = None
361
+ name: str | None = None
362
+ alt_name: str | None = None
363
+ location_type: LocationType | None = None
364
+ country_name: str | None = None
365
+ country_code: str | None = None
366
+ subdivision_name: str | None = None
367
+ subdivision_code: str | None = None
368
+ path_names: list[str] | None = None
369
+ display_name: str | None = None
370
+
371
+
372
+ @dataclass
373
+ class LocationSearchResults(DataClassDictMixin):
374
+ """Response from the locations search endpoint."""
375
+
376
+ total: int | None = None
377
+ results: list[LocationSearchResult] = field(default_factory=list)
378
+
379
+
380
+ @dataclass
381
+ class LicenseRule(DataClassDictMixin):
382
+ """A rule (permission/condition/limitation) of a license."""
383
+
384
+ name: str | None = None
385
+ label: str | None = None
386
+ description: str | None = None
387
+ type: LicenseRuleType | None = None
388
+
389
+
390
+ @dataclass
391
+ class License(DataClassDictMixin):
392
+ """A license in the Mobility Database."""
393
+
394
+ id: str | None = None
395
+ type: str | None = None
396
+ is_spdx: bool | None = None
397
+ name: str | None = None
398
+ url: str | None = None
399
+ description: str | None = None
400
+ created_at: datetime | None = None
401
+ updated_at: datetime | None = None
402
+ license_tags: list[str] | None = None
403
+
404
+
405
+ @dataclass
406
+ class LicenseWithRules(License):
407
+ """A license including its rules."""
408
+
409
+ license_rules: list[LicenseRule] | None = None
410
+
411
+
412
+ @dataclass
413
+ class MatchingLicense(DataClassDictMixin):
414
+ """A license matched from a license URL."""
415
+
416
+ license_id: str | None = None
417
+ license_url: str | None = None
418
+ normalized_url: str | None = None
419
+ match_type: str | None = None
420
+ confidence: float | None = None
421
+ spdx_id: str | None = None
422
+ matched_name: str | None = None
423
+ matched_catalog_url: str | None = None
424
+ matched_source: str | None = None
425
+ notes: str | None = None
426
+ regional_id: str | None = None
427
+
428
+
429
+ @dataclass
430
+ class Metadata(DataClassDictMixin):
431
+ """Metadata about the API itself."""
432
+
433
+ version: str | None = None
434
+ commit_hash: str | None = None
435
+
436
+
437
+ @dataclass
438
+ class AccessToken(DataClassDictMixin):
439
+ """An access token response from the token endpoint."""
440
+
441
+ access_token: str
442
+ expiration_datetime_utc: datetime
443
+ token_type: str | None = None
File without changes
@@ -0,0 +1,111 @@
1
+ Metadata-Version: 2.4
2
+ Name: aiomobilitydatabase
3
+ Version: 0.1.0
4
+ Summary: Async Python client for the Mobility Database catalog API
5
+ Project-URL: Source Code, https://github.com/raman325/aiomobilitydatabase
6
+ Project-URL: Bug Reports, https://github.com/raman325/aiomobilitydatabase/issues
7
+ Author: Raman Gupta
8
+ License-Expression: Apache-2.0
9
+ License-File: LICENSE
10
+ Keywords: asyncio,gbfs,gtfs,gtfs-rt,mobility-database,transit
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.12
15
+ Classifier: Programming Language :: Python :: 3.13
16
+ Classifier: Programming Language :: Python :: 3.14
17
+ Classifier: Topic :: Software Development :: Libraries
18
+ Requires-Python: >=3.12
19
+ Requires-Dist: aiohttp>=3.9
20
+ Requires-Dist: mashumaro>=3.13
21
+ Description-Content-Type: text/markdown
22
+
23
+ # aiomobilitydatabase
24
+
25
+ Async Python client for the [Mobility Database](https://mobilitydatabase.org/) catalog API.
26
+
27
+ The Mobility Database is a catalog of GTFS, GTFS Realtime, and GBFS feeds from
28
+ around the world. This library wraps its
29
+ [catalog API](https://mobilitydata.github.io/mobility-feed-api/SwaggerUI/index.html)
30
+ with a fully typed asyncio client.
31
+
32
+ ## Installation
33
+
34
+ ```bash
35
+ pip install aiomobilitydatabase
36
+ ```
37
+
38
+ Requires Python 3.12+.
39
+
40
+ ## Authentication
41
+
42
+ Sign up at [mobilitydatabase.org](https://mobilitydatabase.org) and copy your
43
+ **refresh token** from the account page. The client exchanges it for short-lived
44
+ access tokens automatically (including proactive refresh before expiry).
45
+
46
+ ## Usage
47
+
48
+ ```python
49
+ import asyncio
50
+
51
+ from aiomobilitydatabase import DataType, MobilityDatabaseClient
52
+
53
+
54
+ async def main() -> None:
55
+ async with MobilityDatabaseClient("YOUR_REFRESH_TOKEN") as client:
56
+ # Free-text search across feed name, provider, and location
57
+ results = await client.search_feeds(
58
+ search_query="new york", data_types=[DataType.GTFS_RT], limit=10
59
+ )
60
+ for item in results.results:
61
+ print(item.id, item.provider, item.status)
62
+
63
+ # Full detail for one feed, including the producer URL
64
+ feed = await client.get_gtfs_rt_feed(results.results[0].id)
65
+ assert feed.source_info is not None
66
+ print(feed.source_info.producer_url)
67
+
68
+
69
+ asyncio.run(main())
70
+ ```
71
+
72
+ With an externally managed session (e.g. Home Assistant's shared session), the
73
+ client never closes it:
74
+
75
+ ```python
76
+ import aiohttp
77
+
78
+ from aiomobilitydatabase import MobilityDatabaseClient
79
+
80
+ session = aiohttp.ClientSession()
81
+ client = MobilityDatabaseClient("YOUR_REFRESH_TOKEN", session)
82
+ try:
83
+ metadata = await client.get_metadata()
84
+ finally:
85
+ await client.close() # session remains open; you own it
86
+ ```
87
+
88
+ ## Error handling
89
+
90
+ All errors derive from `MobilityDatabaseError`:
91
+
92
+ | Exception | Meaning |
93
+ | --- | --- |
94
+ | `MobilityDatabaseConnectionError` | Network failure, timeout, or invalid response body |
95
+ | `MobilityDatabaseAuthenticationError` | Invalid refresh token, or unauthorized after a token refresh |
96
+ | `MobilityDatabaseNotFoundError` | Resource not found (404) |
97
+ | `MobilityDatabaseRateLimitError` | Rate limited (429) |
98
+ | `MobilityDatabaseApiError` | Any other 4xx/5xx (carries `.status` and `.body`) |
99
+
100
+ ## Development
101
+
102
+ ```bash
103
+ uv sync --dev
104
+ uv run pytest
105
+ uv run ruff check . && uv run ruff format --check .
106
+ uv run mypy src
107
+ ```
108
+
109
+ ## License
110
+
111
+ [Apache-2.0](LICENSE)
@@ -0,0 +1,10 @@
1
+ aiomobilitydatabase/__init__.py,sha256=OYqrl5PcadwHwegdevvhgTZkVry2t9gNYcmVu_xB9fI,2210
2
+ aiomobilitydatabase/client.py,sha256=pMBtgPfYH00krf6ZzI7gtptt2SZ9myhYDl_OJpthtDw,19593
3
+ aiomobilitydatabase/const.py,sha256=eE_LpabmaS0NEmYBYfZ5LxvdctV5K0G1vVpiKF2cFAY,313
4
+ aiomobilitydatabase/exceptions.py,sha256=HG3YDraqWV3SRCNNTT-lG18cjYscVZsmE1zWpeILjIM,1052
5
+ aiomobilitydatabase/models.py,sha256=2cVy7GaDDRgTQFkxM3OSy3KisHLwq0VFwKl65ljnlZc,11889
6
+ aiomobilitydatabase/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
+ aiomobilitydatabase-0.1.0.dist-info/METADATA,sha256=yPQ_2v3aFO0Aokc9kDjtToAm6470zk-whzUyD0VWpEo,3394
8
+ aiomobilitydatabase-0.1.0.dist-info/WHEEL,sha256=lCkmxWfQsSc9CfIClYeavTdQeEX2toPqufh9gI35EQA,87
9
+ aiomobilitydatabase-0.1.0.dist-info/licenses/LICENSE,sha256=ggh1G2G-hlnC0YFJjNmS0-ZFFhtlgSW29NEcJH9scVM,11342
10
+ aiomobilitydatabase-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.31.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any