vessel-api-python 1.0.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,1180 @@
1
+ """Service classes wrapping Vessel API endpoints.
2
+
3
+ Each service groups related API endpoints and provides typed methods with
4
+ sensible defaults. All endpoints that require ``filter_id_type`` default
5
+ to ``"imo"`` to match the Go SDK behaviour.
6
+
7
+ Services come in sync and async variants. The async variants have the same
8
+ method signatures but use ``await`` for HTTP calls.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ from typing import TYPE_CHECKING, Any
14
+
15
+ from ._errors import error_from_response
16
+ from ._iterator import AsyncIterator, SyncIterator
17
+ from ._models import (
18
+ ClassificationResponse,
19
+ DGPSStationsWithinLocationResponse,
20
+ FindDGPSStationsResponse,
21
+ FindLightAidsResponse,
22
+ FindMODUsResponse,
23
+ FindPortsResponse,
24
+ FindRadioBeaconsResponse,
25
+ FindVesselsResponse,
26
+ LightAidsWithinLocationResponse,
27
+ MarineCasualtiesResponse,
28
+ MODUsWithinLocationResponse,
29
+ NavtexMessagesResponse,
30
+ PortEventResponse,
31
+ PortEventsResponse,
32
+ PortResponse,
33
+ PortsWithinLocationResponse,
34
+ RadioBeaconsWithinLocationResponse,
35
+ TypesInspectionDetailResponse,
36
+ TypesInspectionsResponse,
37
+ TypesOwnershipResponse,
38
+ VesselEmissionsResponse,
39
+ VesselETAResponse,
40
+ VesselPositionResponse,
41
+ VesselPositionsResponse,
42
+ VesselResponse,
43
+ VesselsWithinLocationResponse,
44
+ )
45
+
46
+ if TYPE_CHECKING:
47
+ import httpx
48
+
49
+
50
+ def _strip_none(params: dict[str, Any]) -> dict[str, Any]:
51
+ """Remove keys with None values from a dict of query params."""
52
+ return {k: v for k, v in params.items() if v is not None}
53
+
54
+
55
+ # ===================================================================
56
+ # Sync services
57
+ # ===================================================================
58
+
59
+
60
+ class VesselsService:
61
+ """Vessel-related API endpoints (sync)."""
62
+
63
+ def __init__(self, client: httpx.Client) -> None:
64
+ self._client = client
65
+
66
+ def get(self, vessel_id: str, *, filter_id_type: str = "imo") -> VesselResponse:
67
+ """Retrieve vessel details by ID (IMO or MMSI)."""
68
+ r = self._client.get(f"/vessel/{vessel_id}", params=_strip_none({"filter.idType": filter_id_type}))
69
+ error_from_response(r.status_code, r.content)
70
+ return VesselResponse.model_validate(r.json())
71
+
72
+ def position(self, vessel_id: str, *, filter_id_type: str = "imo") -> VesselPositionResponse:
73
+ """Retrieve the latest position for a vessel."""
74
+ r = self._client.get(f"/vessel/{vessel_id}/position", params=_strip_none({"filter.idType": filter_id_type}))
75
+ error_from_response(r.status_code, r.content)
76
+ return VesselPositionResponse.model_validate(r.json())
77
+
78
+ def casualties(self, vessel_id: str, *, filter_id_type: str = "imo", pagination_limit: int | None = None, pagination_next_token: str | None = None) -> MarineCasualtiesResponse:
79
+ """Retrieve marine casualty records for a vessel."""
80
+ r = self._client.get(f"/vessel/{vessel_id}/casualties", params=_strip_none({"filter.idType": filter_id_type, "pagination.limit": pagination_limit, "pagination.nextToken": pagination_next_token}))
81
+ error_from_response(r.status_code, r.content)
82
+ return MarineCasualtiesResponse.model_validate(r.json())
83
+
84
+ def classification(self, vessel_id: str, *, filter_id_type: str = "imo") -> ClassificationResponse:
85
+ """Retrieve classification data for a vessel."""
86
+ r = self._client.get(f"/vessel/{vessel_id}/classification", params=_strip_none({"filter.idType": filter_id_type}))
87
+ error_from_response(r.status_code, r.content)
88
+ return ClassificationResponse.model_validate(r.json())
89
+
90
+ def emissions(self, vessel_id: str, *, filter_id_type: str = "imo", pagination_limit: int | None = None, pagination_next_token: str | None = None) -> VesselEmissionsResponse:
91
+ """Retrieve emissions data for a vessel."""
92
+ r = self._client.get(f"/vessel/{vessel_id}/emissions", params=_strip_none({"filter.idType": filter_id_type, "pagination.limit": pagination_limit, "pagination.nextToken": pagination_next_token}))
93
+ error_from_response(r.status_code, r.content)
94
+ return VesselEmissionsResponse.model_validate(r.json())
95
+
96
+ def eta(self, vessel_id: str, *, filter_id_type: str = "imo") -> VesselETAResponse:
97
+ """Retrieve the estimated time of arrival for a vessel."""
98
+ r = self._client.get(f"/vessel/{vessel_id}/eta", params=_strip_none({"filter.idType": filter_id_type}))
99
+ error_from_response(r.status_code, r.content)
100
+ return VesselETAResponse.model_validate(r.json())
101
+
102
+ def inspections(self, vessel_id: str, *, filter_id_type: str = "imo", pagination_limit: int | None = None, pagination_next_token: str | None = None) -> TypesInspectionsResponse:
103
+ """Retrieve inspection records for a vessel."""
104
+ r = self._client.get(f"/vessel/{vessel_id}/inspections", params=_strip_none({"filter.idType": filter_id_type, "pagination.limit": pagination_limit, "pagination.nextToken": pagination_next_token}))
105
+ error_from_response(r.status_code, r.content)
106
+ return TypesInspectionsResponse.model_validate(r.json())
107
+
108
+ def inspection_detail(self, vessel_id: str, detail_id: str, *, filter_id_type: str = "imo") -> TypesInspectionDetailResponse:
109
+ """Retrieve detailed inspection data."""
110
+ r = self._client.get(f"/vessel/{vessel_id}/inspections/{detail_id}", params=_strip_none({"filter.idType": filter_id_type}))
111
+ error_from_response(r.status_code, r.content)
112
+ return TypesInspectionDetailResponse.model_validate(r.json())
113
+
114
+ def ownership(self, vessel_id: str, *, filter_id_type: str = "imo") -> TypesOwnershipResponse:
115
+ """Retrieve ownership data for a vessel."""
116
+ r = self._client.get(f"/vessel/{vessel_id}/ownership", params=_strip_none({"filter.idType": filter_id_type}))
117
+ error_from_response(r.status_code, r.content)
118
+ return TypesOwnershipResponse.model_validate(r.json())
119
+
120
+ def positions(self, *, filter_id_type: str = "imo", filter_ids: str | None = None, pagination_limit: int | None = None, pagination_next_token: str | None = None) -> VesselPositionsResponse:
121
+ """Retrieve positions for multiple vessels."""
122
+ r = self._client.get("/vessels/positions", params=_strip_none({"filter.idType": filter_id_type, "filter.ids": filter_ids, "pagination.limit": pagination_limit, "pagination.nextToken": pagination_next_token}))
123
+ error_from_response(r.status_code, r.content)
124
+ return VesselPositionsResponse.model_validate(r.json())
125
+
126
+ # --- Iterators ---
127
+
128
+ def all_casualties(self, vessel_id: str, *, filter_id_type: str = "imo", pagination_limit: int | None = None) -> SyncIterator[Any]:
129
+ """Iterate over all casualties for a vessel across pages."""
130
+ token: str | None = None
131
+ def fetch() -> tuple[list[Any], str | None]:
132
+ nonlocal token
133
+ resp = self.casualties(vessel_id, filter_id_type=filter_id_type, pagination_limit=pagination_limit, pagination_next_token=token)
134
+ token = resp.next_token
135
+ return resp.casualties or [], token
136
+ return SyncIterator(fetch)
137
+
138
+ def all_emissions(self, vessel_id: str, *, filter_id_type: str = "imo", pagination_limit: int | None = None) -> SyncIterator[Any]:
139
+ """Iterate over all emissions for a vessel across pages."""
140
+ token: str | None = None
141
+ def fetch() -> tuple[list[Any], str | None]:
142
+ nonlocal token
143
+ resp = self.emissions(vessel_id, filter_id_type=filter_id_type, pagination_limit=pagination_limit, pagination_next_token=token)
144
+ token = resp.next_token
145
+ return resp.emissions or [], token
146
+ return SyncIterator(fetch)
147
+
148
+ def all_positions(self, *, filter_id_type: str = "imo", filter_ids: str | None = None, pagination_limit: int | None = None) -> SyncIterator[Any]:
149
+ """Iterate over all positions for multiple vessels across pages."""
150
+ token: str | None = None
151
+ def fetch() -> tuple[list[Any], str | None]:
152
+ nonlocal token
153
+ resp = self.positions(filter_id_type=filter_id_type, filter_ids=filter_ids, pagination_limit=pagination_limit, pagination_next_token=token)
154
+ token = resp.next_token
155
+ return resp.vessel_positions or [], token
156
+ return SyncIterator(fetch)
157
+
158
+
159
+ class PortsService:
160
+ """Port lookup endpoints (sync)."""
161
+
162
+ def __init__(self, client: httpx.Client) -> None:
163
+ self._client = client
164
+
165
+ def get(self, unlocode: str) -> PortResponse:
166
+ """Retrieve a port by its UN/LOCODE."""
167
+ r = self._client.get(f"/port/{unlocode}")
168
+ error_from_response(r.status_code, r.content)
169
+ return PortResponse.model_validate(r.json())
170
+
171
+
172
+ class PortEventsService:
173
+ """Port event endpoints (sync)."""
174
+
175
+ def __init__(self, client: httpx.Client) -> None:
176
+ self._client = client
177
+
178
+ def list(self, *, time_from: str | None = None, time_to: str | None = None, filter_country: str | None = None, filter_unlocode: str | None = None, filter_event_type: str | None = None, filter_vessel_name: str | None = None, filter_port_name: str | None = None, pagination_limit: int | None = None, pagination_next_token: str | None = None) -> PortEventsResponse:
179
+ """Retrieve port events with optional filtering."""
180
+ r = self._client.get("/portevents", params=_strip_none({"time.from": time_from, "time.to": time_to, "filter.country": filter_country, "filter.unlocode": filter_unlocode, "filter.eventType": filter_event_type, "filter.vesselName": filter_vessel_name, "filter.portName": filter_port_name, "pagination.limit": pagination_limit, "pagination.nextToken": pagination_next_token}))
181
+ error_from_response(r.status_code, r.content)
182
+ return PortEventsResponse.model_validate(r.json())
183
+
184
+ def by_port(self, unlocode: str, *, pagination_limit: int | None = None, pagination_next_token: str | None = None) -> PortEventsResponse:
185
+ """Retrieve port events for a specific port by UNLOCODE."""
186
+ r = self._client.get(f"/portevents/port/{unlocode}", params=_strip_none({"pagination.limit": pagination_limit, "pagination.nextToken": pagination_next_token}))
187
+ error_from_response(r.status_code, r.content)
188
+ return PortEventsResponse.model_validate(r.json())
189
+
190
+ def by_ports(self, *, filter_port_name: str | None = None, pagination_limit: int | None = None, pagination_next_token: str | None = None) -> PortEventsResponse:
191
+ """Retrieve port events by port name search."""
192
+ r = self._client.get("/portevents/ports", params=_strip_none({"filter.portName": filter_port_name, "pagination.limit": pagination_limit, "pagination.nextToken": pagination_next_token}))
193
+ error_from_response(r.status_code, r.content)
194
+ return PortEventsResponse.model_validate(r.json())
195
+
196
+ def by_vessel(self, vessel_id: str, *, filter_id_type: str = "imo", filter_event_type: str | None = None, filter_sort_order: str | None = None, time_from: str | None = None, time_to: str | None = None, pagination_limit: int | None = None, pagination_next_token: str | None = None) -> PortEventsResponse:
197
+ """Retrieve port events for a specific vessel."""
198
+ r = self._client.get(f"/portevents/vessel/{vessel_id}", params=_strip_none({"filter.idType": filter_id_type, "filter.eventType": filter_event_type, "filter.sortOrder": filter_sort_order, "time.from": time_from, "time.to": time_to, "pagination.limit": pagination_limit, "pagination.nextToken": pagination_next_token}))
199
+ error_from_response(r.status_code, r.content)
200
+ return PortEventsResponse.model_validate(r.json())
201
+
202
+ def last_by_vessel(self, vessel_id: str, *, filter_id_type: str = "imo") -> PortEventResponse:
203
+ """Retrieve the last port event for a vessel."""
204
+ r = self._client.get(f"/portevents/vessel/{vessel_id}/last", params=_strip_none({"filter.idType": filter_id_type}))
205
+ error_from_response(r.status_code, r.content)
206
+ return PortEventResponse.model_validate(r.json())
207
+
208
+ def by_vessels(self, *, filter_vessel_name: str | None = None, pagination_limit: int | None = None, pagination_next_token: str | None = None) -> PortEventsResponse:
209
+ """Retrieve port events by vessel name search."""
210
+ r = self._client.get("/portevents/vessels", params=_strip_none({"filter.vesselName": filter_vessel_name, "pagination.limit": pagination_limit, "pagination.nextToken": pagination_next_token}))
211
+ error_from_response(r.status_code, r.content)
212
+ return PortEventsResponse.model_validate(r.json())
213
+
214
+ # --- Iterators ---
215
+
216
+ def list_all(self, **kwargs: Any) -> SyncIterator[Any]:
217
+ """Iterate over all port events across pages."""
218
+ token: str | None = None
219
+ def fetch() -> tuple[list[Any], str | None]:
220
+ nonlocal token
221
+ resp = self.list(**kwargs, pagination_next_token=token)
222
+ token = resp.next_token
223
+ return resp.port_events or [], token
224
+ return SyncIterator(fetch)
225
+
226
+ def all_by_port(self, unlocode: str, **kwargs: Any) -> SyncIterator[Any]:
227
+ """Iterate over all port events for a specific port."""
228
+ token: str | None = None
229
+ def fetch() -> tuple[list[Any], str | None]:
230
+ nonlocal token
231
+ resp = self.by_port(unlocode, **kwargs, pagination_next_token=token)
232
+ token = resp.next_token
233
+ return resp.port_events or [], token
234
+ return SyncIterator(fetch)
235
+
236
+ def all_by_ports(self, **kwargs: Any) -> SyncIterator[Any]:
237
+ """Iterate over all port events by port name search."""
238
+ token: str | None = None
239
+ def fetch() -> tuple[list[Any], str | None]:
240
+ nonlocal token
241
+ resp = self.by_ports(**kwargs, pagination_next_token=token)
242
+ token = resp.next_token
243
+ return resp.port_events or [], token
244
+ return SyncIterator(fetch)
245
+
246
+ def all_by_vessel(self, vessel_id: str, **kwargs: Any) -> SyncIterator[Any]:
247
+ """Iterate over all port events for a vessel."""
248
+ token: str | None = None
249
+ def fetch() -> tuple[list[Any], str | None]:
250
+ nonlocal token
251
+ resp = self.by_vessel(vessel_id, **kwargs, pagination_next_token=token)
252
+ token = resp.next_token
253
+ return resp.port_events or [], token
254
+ return SyncIterator(fetch)
255
+
256
+ def all_by_vessels(self, **kwargs: Any) -> SyncIterator[Any]:
257
+ """Iterate over all port events by vessel name search."""
258
+ token: str | None = None
259
+ def fetch() -> tuple[list[Any], str | None]:
260
+ nonlocal token
261
+ resp = self.by_vessels(**kwargs, pagination_next_token=token)
262
+ token = resp.next_token
263
+ return resp.port_events or [], token
264
+ return SyncIterator(fetch)
265
+
266
+
267
+ class EmissionsService:
268
+ """Emissions endpoints (sync)."""
269
+
270
+ def __init__(self, client: httpx.Client) -> None:
271
+ self._client = client
272
+
273
+ def list(self, *, filter_period: int | None = None, pagination_limit: int | None = None, pagination_next_token: str | None = None) -> VesselEmissionsResponse:
274
+ """Retrieve vessel emissions data."""
275
+ r = self._client.get("/emissions", params=_strip_none({"filter.period": filter_period, "pagination.limit": pagination_limit, "pagination.nextToken": pagination_next_token}))
276
+ error_from_response(r.status_code, r.content)
277
+ return VesselEmissionsResponse.model_validate(r.json())
278
+
279
+ def list_all(self, **kwargs: Any) -> SyncIterator[Any]:
280
+ """Iterate over all emissions across pages."""
281
+ token: str | None = None
282
+ def fetch() -> tuple[list[Any], str | None]:
283
+ nonlocal token
284
+ resp = self.list(**kwargs, pagination_next_token=token)
285
+ token = resp.next_token
286
+ return resp.emissions or [], token
287
+ return SyncIterator(fetch)
288
+
289
+
290
+ class SearchService:
291
+ """Search endpoints (sync)."""
292
+
293
+ def __init__(self, client: httpx.Client) -> None:
294
+ self._client = client
295
+
296
+ def vessels(self, *, filter_name: str | None = None, filter_imo: str | None = None, filter_mmsi: str | None = None, filter_flag: str | None = None, filter_vessel_type: str | None = None, filter_callsign: str | None = None, pagination_limit: int | None = None, pagination_next_token: str | None = None) -> FindVesselsResponse:
297
+ """Search for vessels by name, callsign, flag, type, and other filters."""
298
+ r = self._client.get("/search/vessels", params=_strip_none({"filter.name": filter_name, "filter.imo": filter_imo, "filter.mmsi": filter_mmsi, "filter.flag": filter_flag, "filter.vesselType": filter_vessel_type, "filter.callsign": filter_callsign, "pagination.limit": pagination_limit, "pagination.nextToken": pagination_next_token}))
299
+ error_from_response(r.status_code, r.content)
300
+ return FindVesselsResponse.model_validate(r.json())
301
+
302
+ def ports(self, *, filter_name: str | None = None, filter_country: str | None = None, filter_port_type: str | None = None, filter_region: str | None = None, pagination_limit: int | None = None, pagination_next_token: str | None = None) -> FindPortsResponse:
303
+ """Search for ports by name, country, type, region, and other filters."""
304
+ r = self._client.get("/search/ports", params=_strip_none({"filter.name": filter_name, "filter.country": filter_country, "filter.type": filter_port_type, "filter.region": filter_region, "pagination.limit": pagination_limit, "pagination.nextToken": pagination_next_token}))
305
+ error_from_response(r.status_code, r.content)
306
+ return FindPortsResponse.model_validate(r.json())
307
+
308
+ def dgps(self, *, filter_name: str | None = None, pagination_limit: int | None = None, pagination_next_token: str | None = None) -> FindDGPSStationsResponse:
309
+ """Search for DGPS stations by name."""
310
+ r = self._client.get("/search/dgps", params=_strip_none({"filter.name": filter_name, "pagination.limit": pagination_limit, "pagination.nextToken": pagination_next_token}))
311
+ error_from_response(r.status_code, r.content)
312
+ return FindDGPSStationsResponse.model_validate(r.json())
313
+
314
+ def light_aids(self, *, filter_name: str | None = None, pagination_limit: int | None = None, pagination_next_token: str | None = None) -> FindLightAidsResponse:
315
+ """Search for light aids by name."""
316
+ r = self._client.get("/search/lightaids", params=_strip_none({"filter.name": filter_name, "pagination.limit": pagination_limit, "pagination.nextToken": pagination_next_token}))
317
+ error_from_response(r.status_code, r.content)
318
+ return FindLightAidsResponse.model_validate(r.json())
319
+
320
+ def modus(self, *, filter_name: str | None = None, pagination_limit: int | None = None, pagination_next_token: str | None = None) -> FindMODUsResponse:
321
+ """Search for MODUs (Mobile Offshore Drilling Units) by name."""
322
+ r = self._client.get("/search/modus", params=_strip_none({"filter.name": filter_name, "pagination.limit": pagination_limit, "pagination.nextToken": pagination_next_token}))
323
+ error_from_response(r.status_code, r.content)
324
+ return FindMODUsResponse.model_validate(r.json())
325
+
326
+ def radio_beacons(self, *, filter_name: str | None = None, pagination_limit: int | None = None, pagination_next_token: str | None = None) -> FindRadioBeaconsResponse:
327
+ """Search for radio beacons by name."""
328
+ r = self._client.get("/search/radiobeacons", params=_strip_none({"filter.name": filter_name, "pagination.limit": pagination_limit, "pagination.nextToken": pagination_next_token}))
329
+ error_from_response(r.status_code, r.content)
330
+ return FindRadioBeaconsResponse.model_validate(r.json())
331
+
332
+ # --- Iterators ---
333
+
334
+ def all_vessels(self, **kwargs: Any) -> SyncIterator[Any]:
335
+ """Iterate over all vessel search results across pages."""
336
+ token: str | None = None
337
+ def fetch() -> tuple[list[Any], str | None]:
338
+ nonlocal token
339
+ resp = self.vessels(**kwargs, pagination_next_token=token)
340
+ token = resp.next_token
341
+ return resp.vessels or [], token
342
+ return SyncIterator(fetch)
343
+
344
+ def all_ports(self, **kwargs: Any) -> SyncIterator[Any]:
345
+ """Iterate over all port search results across pages."""
346
+ token: str | None = None
347
+ def fetch() -> tuple[list[Any], str | None]:
348
+ nonlocal token
349
+ resp = self.ports(**kwargs, pagination_next_token=token)
350
+ token = resp.next_token
351
+ return resp.ports or [], token
352
+ return SyncIterator(fetch)
353
+
354
+ def all_dgps(self, **kwargs: Any) -> SyncIterator[Any]:
355
+ """Iterate over all DGPS station search results."""
356
+ token: str | None = None
357
+ def fetch() -> tuple[list[Any], str | None]:
358
+ nonlocal token
359
+ resp = self.dgps(**kwargs, pagination_next_token=token)
360
+ token = resp.next_token
361
+ return resp.dgps_stations or [], token
362
+ return SyncIterator(fetch)
363
+
364
+ def all_light_aids(self, **kwargs: Any) -> SyncIterator[Any]:
365
+ """Iterate over all light aid search results."""
366
+ token: str | None = None
367
+ def fetch() -> tuple[list[Any], str | None]:
368
+ nonlocal token
369
+ resp = self.light_aids(**kwargs, pagination_next_token=token)
370
+ token = resp.next_token
371
+ return resp.light_aids or [], token
372
+ return SyncIterator(fetch)
373
+
374
+ def all_modus(self, **kwargs: Any) -> SyncIterator[Any]:
375
+ """Iterate over all MODU search results."""
376
+ token: str | None = None
377
+ def fetch() -> tuple[list[Any], str | None]:
378
+ nonlocal token
379
+ resp = self.modus(**kwargs, pagination_next_token=token)
380
+ token = resp.next_token
381
+ return resp.modus or [], token
382
+ return SyncIterator(fetch)
383
+
384
+ def all_radio_beacons(self, **kwargs: Any) -> SyncIterator[Any]:
385
+ """Iterate over all radio beacon search results."""
386
+ token: str | None = None
387
+ def fetch() -> tuple[list[Any], str | None]:
388
+ nonlocal token
389
+ resp = self.radio_beacons(**kwargs, pagination_next_token=token)
390
+ token = resp.next_token
391
+ return resp.radio_beacons or [], token
392
+ return SyncIterator(fetch)
393
+
394
+
395
+ class LocationService:
396
+ """Location-based query endpoints (sync)."""
397
+
398
+ def __init__(self, client: httpx.Client) -> None:
399
+ self._client = client
400
+
401
+ def vessels_bounding_box(self, *, lat_min: float | None = None, lat_max: float | None = None, lon_min: float | None = None, lon_max: float | None = None, pagination_limit: int | None = None, pagination_next_token: str | None = None) -> VesselsWithinLocationResponse:
402
+ """Retrieve vessel positions within a bounding box."""
403
+ r = self._client.get("/location/vessels/bounding-box", params=_strip_none({"filter.latBottom": lat_min, "filter.latTop": lat_max, "filter.lonLeft": lon_min, "filter.lonRight": lon_max, "pagination.limit": pagination_limit, "pagination.nextToken": pagination_next_token}))
404
+ error_from_response(r.status_code, r.content)
405
+ return VesselsWithinLocationResponse.model_validate(r.json())
406
+
407
+ def vessels_radius(self, *, latitude: float | None = None, longitude: float | None = None, radius: float | None = None, pagination_limit: int | None = None, pagination_next_token: str | None = None) -> VesselsWithinLocationResponse:
408
+ """Retrieve vessel positions within a radius."""
409
+ r = self._client.get("/location/vessels/radius", params=_strip_none({"filter.latitude": latitude, "filter.longitude": longitude, "filter.radius": radius, "pagination.limit": pagination_limit, "pagination.nextToken": pagination_next_token}))
410
+ error_from_response(r.status_code, r.content)
411
+ return VesselsWithinLocationResponse.model_validate(r.json())
412
+
413
+ def ports_bounding_box(self, *, lat_min: float | None = None, lat_max: float | None = None, lon_min: float | None = None, lon_max: float | None = None, pagination_limit: int | None = None, pagination_next_token: str | None = None) -> PortsWithinLocationResponse:
414
+ """Retrieve ports within a bounding box."""
415
+ r = self._client.get("/location/ports/bounding-box", params=_strip_none({"filter.latBottom": lat_min, "filter.latTop": lat_max, "filter.lonLeft": lon_min, "filter.lonRight": lon_max, "pagination.limit": pagination_limit, "pagination.nextToken": pagination_next_token}))
416
+ error_from_response(r.status_code, r.content)
417
+ return PortsWithinLocationResponse.model_validate(r.json())
418
+
419
+ def ports_radius(self, *, latitude: float | None = None, longitude: float | None = None, radius: float | None = None, pagination_limit: int | None = None, pagination_next_token: str | None = None) -> PortsWithinLocationResponse:
420
+ """Retrieve ports within a radius."""
421
+ r = self._client.get("/location/ports/radius", params=_strip_none({"filter.latitude": latitude, "filter.longitude": longitude, "filter.radius": radius, "pagination.limit": pagination_limit, "pagination.nextToken": pagination_next_token}))
422
+ error_from_response(r.status_code, r.content)
423
+ return PortsWithinLocationResponse.model_validate(r.json())
424
+
425
+ def dgps_bounding_box(self, *, lat_min: float | None = None, lat_max: float | None = None, lon_min: float | None = None, lon_max: float | None = None, pagination_limit: int | None = None, pagination_next_token: str | None = None) -> DGPSStationsWithinLocationResponse:
426
+ """Retrieve DGPS stations within a bounding box."""
427
+ r = self._client.get("/location/dgps/bounding-box", params=_strip_none({"filter.latBottom": lat_min, "filter.latTop": lat_max, "filter.lonLeft": lon_min, "filter.lonRight": lon_max, "pagination.limit": pagination_limit, "pagination.nextToken": pagination_next_token}))
428
+ error_from_response(r.status_code, r.content)
429
+ return DGPSStationsWithinLocationResponse.model_validate(r.json())
430
+
431
+ def dgps_radius(self, *, latitude: float | None = None, longitude: float | None = None, radius: float | None = None, pagination_limit: int | None = None, pagination_next_token: str | None = None) -> DGPSStationsWithinLocationResponse:
432
+ """Retrieve DGPS stations within a radius."""
433
+ r = self._client.get("/location/dgps/radius", params=_strip_none({"filter.latitude": latitude, "filter.longitude": longitude, "filter.radius": radius, "pagination.limit": pagination_limit, "pagination.nextToken": pagination_next_token}))
434
+ error_from_response(r.status_code, r.content)
435
+ return DGPSStationsWithinLocationResponse.model_validate(r.json())
436
+
437
+ def light_aids_bounding_box(self, *, lat_min: float | None = None, lat_max: float | None = None, lon_min: float | None = None, lon_max: float | None = None, pagination_limit: int | None = None, pagination_next_token: str | None = None) -> LightAidsWithinLocationResponse:
438
+ """Retrieve light aids within a bounding box."""
439
+ r = self._client.get("/location/lightaids/bounding-box", params=_strip_none({"filter.latBottom": lat_min, "filter.latTop": lat_max, "filter.lonLeft": lon_min, "filter.lonRight": lon_max, "pagination.limit": pagination_limit, "pagination.nextToken": pagination_next_token}))
440
+ error_from_response(r.status_code, r.content)
441
+ return LightAidsWithinLocationResponse.model_validate(r.json())
442
+
443
+ def light_aids_radius(self, *, latitude: float | None = None, longitude: float | None = None, radius: float | None = None, pagination_limit: int | None = None, pagination_next_token: str | None = None) -> LightAidsWithinLocationResponse:
444
+ """Retrieve light aids within a radius."""
445
+ r = self._client.get("/location/lightaids/radius", params=_strip_none({"filter.latitude": latitude, "filter.longitude": longitude, "filter.radius": radius, "pagination.limit": pagination_limit, "pagination.nextToken": pagination_next_token}))
446
+ error_from_response(r.status_code, r.content)
447
+ return LightAidsWithinLocationResponse.model_validate(r.json())
448
+
449
+ def modus_bounding_box(self, *, lat_min: float | None = None, lat_max: float | None = None, lon_min: float | None = None, lon_max: float | None = None, pagination_limit: int | None = None, pagination_next_token: str | None = None) -> MODUsWithinLocationResponse:
450
+ """Retrieve MODUs within a bounding box."""
451
+ r = self._client.get("/location/modu/bounding-box", params=_strip_none({"filter.latBottom": lat_min, "filter.latTop": lat_max, "filter.lonLeft": lon_min, "filter.lonRight": lon_max, "pagination.limit": pagination_limit, "pagination.nextToken": pagination_next_token}))
452
+ error_from_response(r.status_code, r.content)
453
+ return MODUsWithinLocationResponse.model_validate(r.json())
454
+
455
+ def modus_radius(self, *, latitude: float | None = None, longitude: float | None = None, radius: float | None = None, pagination_limit: int | None = None, pagination_next_token: str | None = None) -> MODUsWithinLocationResponse:
456
+ """Retrieve MODUs within a radius."""
457
+ r = self._client.get("/location/modu/radius", params=_strip_none({"filter.latitude": latitude, "filter.longitude": longitude, "filter.radius": radius, "pagination.limit": pagination_limit, "pagination.nextToken": pagination_next_token}))
458
+ error_from_response(r.status_code, r.content)
459
+ return MODUsWithinLocationResponse.model_validate(r.json())
460
+
461
+ def radio_beacons_bounding_box(self, *, lat_min: float | None = None, lat_max: float | None = None, lon_min: float | None = None, lon_max: float | None = None, pagination_limit: int | None = None, pagination_next_token: str | None = None) -> RadioBeaconsWithinLocationResponse:
462
+ """Retrieve radio beacons within a bounding box."""
463
+ r = self._client.get("/location/radiobeacons/bounding-box", params=_strip_none({"filter.latBottom": lat_min, "filter.latTop": lat_max, "filter.lonLeft": lon_min, "filter.lonRight": lon_max, "pagination.limit": pagination_limit, "pagination.nextToken": pagination_next_token}))
464
+ error_from_response(r.status_code, r.content)
465
+ return RadioBeaconsWithinLocationResponse.model_validate(r.json())
466
+
467
+ def radio_beacons_radius(self, *, latitude: float | None = None, longitude: float | None = None, radius: float | None = None, pagination_limit: int | None = None, pagination_next_token: str | None = None) -> RadioBeaconsWithinLocationResponse:
468
+ """Retrieve radio beacons within a radius."""
469
+ r = self._client.get("/location/radiobeacons/radius", params=_strip_none({"filter.latitude": latitude, "filter.longitude": longitude, "filter.radius": radius, "pagination.limit": pagination_limit, "pagination.nextToken": pagination_next_token}))
470
+ error_from_response(r.status_code, r.content)
471
+ return RadioBeaconsWithinLocationResponse.model_validate(r.json())
472
+
473
+ # --- Iterators ---
474
+
475
+ def all_vessels_bounding_box(self, **kwargs: Any) -> SyncIterator[Any]:
476
+ """Iterate over all vessel positions in a bounding box."""
477
+ token: str | None = None
478
+ def fetch() -> tuple[list[Any], str | None]:
479
+ nonlocal token
480
+ resp = self.vessels_bounding_box(**kwargs, pagination_next_token=token)
481
+ token = resp.next_token
482
+ return resp.vessels or [], token
483
+ return SyncIterator(fetch)
484
+
485
+ def all_vessels_radius(self, **kwargs: Any) -> SyncIterator[Any]:
486
+ """Iterate over all vessel positions within a radius."""
487
+ token: str | None = None
488
+ def fetch() -> tuple[list[Any], str | None]:
489
+ nonlocal token
490
+ resp = self.vessels_radius(**kwargs, pagination_next_token=token)
491
+ token = resp.next_token
492
+ return resp.vessels or [], token
493
+ return SyncIterator(fetch)
494
+
495
+ def all_ports_bounding_box(self, **kwargs: Any) -> SyncIterator[Any]:
496
+ """Iterate over all ports in a bounding box."""
497
+ token: str | None = None
498
+ def fetch() -> tuple[list[Any], str | None]:
499
+ nonlocal token
500
+ resp = self.ports_bounding_box(**kwargs, pagination_next_token=token)
501
+ token = resp.next_token
502
+ return resp.ports or [], token
503
+ return SyncIterator(fetch)
504
+
505
+ def all_ports_radius(self, **kwargs: Any) -> SyncIterator[Any]:
506
+ """Iterate over all ports within a radius."""
507
+ token: str | None = None
508
+ def fetch() -> tuple[list[Any], str | None]:
509
+ nonlocal token
510
+ resp = self.ports_radius(**kwargs, pagination_next_token=token)
511
+ token = resp.next_token
512
+ return resp.ports or [], token
513
+ return SyncIterator(fetch)
514
+
515
+ def all_dgps_bounding_box(self, **kwargs: Any) -> SyncIterator[Any]:
516
+ """Iterate over all DGPS stations in a bounding box."""
517
+ token: str | None = None
518
+ def fetch() -> tuple[list[Any], str | None]:
519
+ nonlocal token
520
+ resp = self.dgps_bounding_box(**kwargs, pagination_next_token=token)
521
+ token = resp.next_token
522
+ return resp.dgps_stations or [], token
523
+ return SyncIterator(fetch)
524
+
525
+ def all_dgps_radius(self, **kwargs: Any) -> SyncIterator[Any]:
526
+ """Iterate over all DGPS stations within a radius."""
527
+ token: str | None = None
528
+ def fetch() -> tuple[list[Any], str | None]:
529
+ nonlocal token
530
+ resp = self.dgps_radius(**kwargs, pagination_next_token=token)
531
+ token = resp.next_token
532
+ return resp.dgps_stations or [], token
533
+ return SyncIterator(fetch)
534
+
535
+ def all_light_aids_bounding_box(self, **kwargs: Any) -> SyncIterator[Any]:
536
+ """Iterate over all light aids in a bounding box."""
537
+ token: str | None = None
538
+ def fetch() -> tuple[list[Any], str | None]:
539
+ nonlocal token
540
+ resp = self.light_aids_bounding_box(**kwargs, pagination_next_token=token)
541
+ token = resp.next_token
542
+ return resp.light_aids or [], token
543
+ return SyncIterator(fetch)
544
+
545
+ def all_light_aids_radius(self, **kwargs: Any) -> SyncIterator[Any]:
546
+ """Iterate over all light aids within a radius."""
547
+ token: str | None = None
548
+ def fetch() -> tuple[list[Any], str | None]:
549
+ nonlocal token
550
+ resp = self.light_aids_radius(**kwargs, pagination_next_token=token)
551
+ token = resp.next_token
552
+ return resp.light_aids or [], token
553
+ return SyncIterator(fetch)
554
+
555
+ def all_modus_bounding_box(self, **kwargs: Any) -> SyncIterator[Any]:
556
+ """Iterate over all MODUs in a bounding box."""
557
+ token: str | None = None
558
+ def fetch() -> tuple[list[Any], str | None]:
559
+ nonlocal token
560
+ resp = self.modus_bounding_box(**kwargs, pagination_next_token=token)
561
+ token = resp.next_token
562
+ return resp.modus or [], token
563
+ return SyncIterator(fetch)
564
+
565
+ def all_modus_radius(self, **kwargs: Any) -> SyncIterator[Any]:
566
+ """Iterate over all MODUs within a radius."""
567
+ token: str | None = None
568
+ def fetch() -> tuple[list[Any], str | None]:
569
+ nonlocal token
570
+ resp = self.modus_radius(**kwargs, pagination_next_token=token)
571
+ token = resp.next_token
572
+ return resp.modus or [], token
573
+ return SyncIterator(fetch)
574
+
575
+ def all_radio_beacons_bounding_box(self, **kwargs: Any) -> SyncIterator[Any]:
576
+ """Iterate over all radio beacons in a bounding box."""
577
+ token: str | None = None
578
+ def fetch() -> tuple[list[Any], str | None]:
579
+ nonlocal token
580
+ resp = self.radio_beacons_bounding_box(**kwargs, pagination_next_token=token)
581
+ token = resp.next_token
582
+ return resp.radio_beacons or [], token
583
+ return SyncIterator(fetch)
584
+
585
+ def all_radio_beacons_radius(self, **kwargs: Any) -> SyncIterator[Any]:
586
+ """Iterate over all radio beacons within a radius."""
587
+ token: str | None = None
588
+ def fetch() -> tuple[list[Any], str | None]:
589
+ nonlocal token
590
+ resp = self.radio_beacons_radius(**kwargs, pagination_next_token=token)
591
+ token = resp.next_token
592
+ return resp.radio_beacons or [], token
593
+ return SyncIterator(fetch)
594
+
595
+
596
+ class NavtexService:
597
+ """NAVTEX message endpoints (sync)."""
598
+
599
+ def __init__(self, client: httpx.Client) -> None:
600
+ self._client = client
601
+
602
+ def list(self, *, time_from: str | None = None, time_to: str | None = None, pagination_limit: int | None = None, pagination_next_token: str | None = None) -> NavtexMessagesResponse:
603
+ """Retrieve NAVTEX maritime safety messages."""
604
+ r = self._client.get("/navtex", params=_strip_none({"time.from": time_from, "time.to": time_to, "pagination.limit": pagination_limit, "pagination.nextToken": pagination_next_token}))
605
+ error_from_response(r.status_code, r.content)
606
+ return NavtexMessagesResponse.model_validate(r.json())
607
+
608
+ def list_all(self, **kwargs: Any) -> SyncIterator[Any]:
609
+ """Iterate over all NAVTEX messages across pages."""
610
+ token: str | None = None
611
+ def fetch() -> tuple[list[Any], str | None]:
612
+ nonlocal token
613
+ resp = self.list(**kwargs, pagination_next_token=token)
614
+ token = resp.next_token
615
+ return resp.navtex_messages or [], token
616
+ return SyncIterator(fetch)
617
+
618
+
619
+ # ===================================================================
620
+ # Async services
621
+ # ===================================================================
622
+
623
+
624
+ class AsyncVesselsService:
625
+ """Vessel-related API endpoints (async)."""
626
+
627
+ def __init__(self, client: httpx.AsyncClient) -> None:
628
+ self._client = client
629
+
630
+ async def get(self, vessel_id: str, *, filter_id_type: str = "imo") -> VesselResponse:
631
+ """Retrieve vessel details by ID (IMO or MMSI)."""
632
+ r = await self._client.get(f"/vessel/{vessel_id}", params=_strip_none({"filter.idType": filter_id_type}))
633
+ error_from_response(r.status_code, r.content)
634
+ return VesselResponse.model_validate(r.json())
635
+
636
+ async def position(self, vessel_id: str, *, filter_id_type: str = "imo") -> VesselPositionResponse:
637
+ """Retrieve the latest position for a vessel."""
638
+ r = await self._client.get(f"/vessel/{vessel_id}/position", params=_strip_none({"filter.idType": filter_id_type}))
639
+ error_from_response(r.status_code, r.content)
640
+ return VesselPositionResponse.model_validate(r.json())
641
+
642
+ async def casualties(self, vessel_id: str, *, filter_id_type: str = "imo", pagination_limit: int | None = None, pagination_next_token: str | None = None) -> MarineCasualtiesResponse:
643
+ """Retrieve marine casualty records for a vessel."""
644
+ r = await self._client.get(f"/vessel/{vessel_id}/casualties", params=_strip_none({"filter.idType": filter_id_type, "pagination.limit": pagination_limit, "pagination.nextToken": pagination_next_token}))
645
+ error_from_response(r.status_code, r.content)
646
+ return MarineCasualtiesResponse.model_validate(r.json())
647
+
648
+ async def classification(self, vessel_id: str, *, filter_id_type: str = "imo") -> ClassificationResponse:
649
+ """Retrieve classification data for a vessel."""
650
+ r = await self._client.get(f"/vessel/{vessel_id}/classification", params=_strip_none({"filter.idType": filter_id_type}))
651
+ error_from_response(r.status_code, r.content)
652
+ return ClassificationResponse.model_validate(r.json())
653
+
654
+ async def emissions(self, vessel_id: str, *, filter_id_type: str = "imo", pagination_limit: int | None = None, pagination_next_token: str | None = None) -> VesselEmissionsResponse:
655
+ """Retrieve emissions data for a vessel."""
656
+ r = await self._client.get(f"/vessel/{vessel_id}/emissions", params=_strip_none({"filter.idType": filter_id_type, "pagination.limit": pagination_limit, "pagination.nextToken": pagination_next_token}))
657
+ error_from_response(r.status_code, r.content)
658
+ return VesselEmissionsResponse.model_validate(r.json())
659
+
660
+ async def eta(self, vessel_id: str, *, filter_id_type: str = "imo") -> VesselETAResponse:
661
+ """Retrieve the estimated time of arrival for a vessel."""
662
+ r = await self._client.get(f"/vessel/{vessel_id}/eta", params=_strip_none({"filter.idType": filter_id_type}))
663
+ error_from_response(r.status_code, r.content)
664
+ return VesselETAResponse.model_validate(r.json())
665
+
666
+ async def inspections(self, vessel_id: str, *, filter_id_type: str = "imo", pagination_limit: int | None = None, pagination_next_token: str | None = None) -> TypesInspectionsResponse:
667
+ """Retrieve inspection records for a vessel."""
668
+ r = await self._client.get(f"/vessel/{vessel_id}/inspections", params=_strip_none({"filter.idType": filter_id_type, "pagination.limit": pagination_limit, "pagination.nextToken": pagination_next_token}))
669
+ error_from_response(r.status_code, r.content)
670
+ return TypesInspectionsResponse.model_validate(r.json())
671
+
672
+ async def inspection_detail(self, vessel_id: str, detail_id: str, *, filter_id_type: str = "imo") -> TypesInspectionDetailResponse:
673
+ """Retrieve detailed inspection data."""
674
+ r = await self._client.get(f"/vessel/{vessel_id}/inspections/{detail_id}", params=_strip_none({"filter.idType": filter_id_type}))
675
+ error_from_response(r.status_code, r.content)
676
+ return TypesInspectionDetailResponse.model_validate(r.json())
677
+
678
+ async def ownership(self, vessel_id: str, *, filter_id_type: str = "imo") -> TypesOwnershipResponse:
679
+ """Retrieve ownership data for a vessel."""
680
+ r = await self._client.get(f"/vessel/{vessel_id}/ownership", params=_strip_none({"filter.idType": filter_id_type}))
681
+ error_from_response(r.status_code, r.content)
682
+ return TypesOwnershipResponse.model_validate(r.json())
683
+
684
+ async def positions(self, *, filter_id_type: str = "imo", filter_ids: str | None = None, pagination_limit: int | None = None, pagination_next_token: str | None = None) -> VesselPositionsResponse:
685
+ """Retrieve positions for multiple vessels."""
686
+ r = await self._client.get("/vessels/positions", params=_strip_none({"filter.idType": filter_id_type, "filter.ids": filter_ids, "pagination.limit": pagination_limit, "pagination.nextToken": pagination_next_token}))
687
+ error_from_response(r.status_code, r.content)
688
+ return VesselPositionsResponse.model_validate(r.json())
689
+
690
+ # --- Iterators ---
691
+
692
+ def all_casualties(self, vessel_id: str, *, filter_id_type: str = "imo", pagination_limit: int | None = None) -> AsyncIterator[Any]:
693
+ """Iterate over all casualties for a vessel across pages."""
694
+ token: str | None = None
695
+ async def fetch() -> tuple[list[Any], str | None]:
696
+ nonlocal token
697
+ resp = await self.casualties(vessel_id, filter_id_type=filter_id_type, pagination_limit=pagination_limit, pagination_next_token=token)
698
+ token = resp.next_token
699
+ return resp.casualties or [], token
700
+ return AsyncIterator(fetch)
701
+
702
+ def all_emissions(self, vessel_id: str, *, filter_id_type: str = "imo", pagination_limit: int | None = None) -> AsyncIterator[Any]:
703
+ """Iterate over all emissions for a vessel across pages."""
704
+ token: str | None = None
705
+ async def fetch() -> tuple[list[Any], str | None]:
706
+ nonlocal token
707
+ resp = await self.emissions(vessel_id, filter_id_type=filter_id_type, pagination_limit=pagination_limit, pagination_next_token=token)
708
+ token = resp.next_token
709
+ return resp.emissions or [], token
710
+ return AsyncIterator(fetch)
711
+
712
+ def all_positions(self, *, filter_id_type: str = "imo", filter_ids: str | None = None, pagination_limit: int | None = None) -> AsyncIterator[Any]:
713
+ """Iterate over all positions for multiple vessels across pages."""
714
+ token: str | None = None
715
+ async def fetch() -> tuple[list[Any], str | None]:
716
+ nonlocal token
717
+ resp = await self.positions(filter_id_type=filter_id_type, filter_ids=filter_ids, pagination_limit=pagination_limit, pagination_next_token=token)
718
+ token = resp.next_token
719
+ return resp.vessel_positions or [], token
720
+ return AsyncIterator(fetch)
721
+
722
+
723
+ class AsyncPortsService:
724
+ """Port lookup endpoints (async)."""
725
+
726
+ def __init__(self, client: httpx.AsyncClient) -> None:
727
+ self._client = client
728
+
729
+ async def get(self, unlocode: str) -> PortResponse:
730
+ """Retrieve a port by its UN/LOCODE."""
731
+ r = await self._client.get(f"/port/{unlocode}")
732
+ error_from_response(r.status_code, r.content)
733
+ return PortResponse.model_validate(r.json())
734
+
735
+
736
+ class AsyncPortEventsService:
737
+ """Port event endpoints (async)."""
738
+
739
+ def __init__(self, client: httpx.AsyncClient) -> None:
740
+ self._client = client
741
+
742
+ async def list(self, *, time_from: str | None = None, time_to: str | None = None, filter_country: str | None = None, filter_unlocode: str | None = None, filter_event_type: str | None = None, filter_vessel_name: str | None = None, filter_port_name: str | None = None, pagination_limit: int | None = None, pagination_next_token: str | None = None) -> PortEventsResponse:
743
+ """Retrieve port events with optional filtering."""
744
+ r = await self._client.get("/portevents", params=_strip_none({"time.from": time_from, "time.to": time_to, "filter.country": filter_country, "filter.unlocode": filter_unlocode, "filter.eventType": filter_event_type, "filter.vesselName": filter_vessel_name, "filter.portName": filter_port_name, "pagination.limit": pagination_limit, "pagination.nextToken": pagination_next_token}))
745
+ error_from_response(r.status_code, r.content)
746
+ return PortEventsResponse.model_validate(r.json())
747
+
748
+ async def by_port(self, unlocode: str, *, pagination_limit: int | None = None, pagination_next_token: str | None = None) -> PortEventsResponse:
749
+ """Retrieve port events for a specific port by UNLOCODE."""
750
+ r = await self._client.get(f"/portevents/port/{unlocode}", params=_strip_none({"pagination.limit": pagination_limit, "pagination.nextToken": pagination_next_token}))
751
+ error_from_response(r.status_code, r.content)
752
+ return PortEventsResponse.model_validate(r.json())
753
+
754
+ async def by_ports(self, *, filter_port_name: str | None = None, pagination_limit: int | None = None, pagination_next_token: str | None = None) -> PortEventsResponse:
755
+ """Retrieve port events by port name search."""
756
+ r = await self._client.get("/portevents/ports", params=_strip_none({"filter.portName": filter_port_name, "pagination.limit": pagination_limit, "pagination.nextToken": pagination_next_token}))
757
+ error_from_response(r.status_code, r.content)
758
+ return PortEventsResponse.model_validate(r.json())
759
+
760
+ async def by_vessel(self, vessel_id: str, *, filter_id_type: str = "imo", filter_event_type: str | None = None, filter_sort_order: str | None = None, time_from: str | None = None, time_to: str | None = None, pagination_limit: int | None = None, pagination_next_token: str | None = None) -> PortEventsResponse:
761
+ """Retrieve port events for a specific vessel."""
762
+ r = await self._client.get(f"/portevents/vessel/{vessel_id}", params=_strip_none({"filter.idType": filter_id_type, "filter.eventType": filter_event_type, "filter.sortOrder": filter_sort_order, "time.from": time_from, "time.to": time_to, "pagination.limit": pagination_limit, "pagination.nextToken": pagination_next_token}))
763
+ error_from_response(r.status_code, r.content)
764
+ return PortEventsResponse.model_validate(r.json())
765
+
766
+ async def last_by_vessel(self, vessel_id: str, *, filter_id_type: str = "imo") -> PortEventResponse:
767
+ """Retrieve the last port event for a vessel."""
768
+ r = await self._client.get(f"/portevents/vessel/{vessel_id}/last", params=_strip_none({"filter.idType": filter_id_type}))
769
+ error_from_response(r.status_code, r.content)
770
+ return PortEventResponse.model_validate(r.json())
771
+
772
+ async def by_vessels(self, *, filter_vessel_name: str | None = None, pagination_limit: int | None = None, pagination_next_token: str | None = None) -> PortEventsResponse:
773
+ """Retrieve port events by vessel name search."""
774
+ r = await self._client.get("/portevents/vessels", params=_strip_none({"filter.vesselName": filter_vessel_name, "pagination.limit": pagination_limit, "pagination.nextToken": pagination_next_token}))
775
+ error_from_response(r.status_code, r.content)
776
+ return PortEventsResponse.model_validate(r.json())
777
+
778
+ # --- Iterators ---
779
+
780
+ def list_all(self, **kwargs: Any) -> AsyncIterator[Any]:
781
+ """Iterate over all port events across pages."""
782
+ token: str | None = None
783
+ async def fetch() -> tuple[list[Any], str | None]:
784
+ nonlocal token
785
+ resp = await self.list(**kwargs, pagination_next_token=token)
786
+ token = resp.next_token
787
+ return resp.port_events or [], token
788
+ return AsyncIterator(fetch)
789
+
790
+ def all_by_port(self, unlocode: str, **kwargs: Any) -> AsyncIterator[Any]:
791
+ """Iterate over all port events for a specific port."""
792
+ token: str | None = None
793
+ async def fetch() -> tuple[list[Any], str | None]:
794
+ nonlocal token
795
+ resp = await self.by_port(unlocode, **kwargs, pagination_next_token=token)
796
+ token = resp.next_token
797
+ return resp.port_events or [], token
798
+ return AsyncIterator(fetch)
799
+
800
+ def all_by_ports(self, **kwargs: Any) -> AsyncIterator[Any]:
801
+ """Iterate over all port events by port name search."""
802
+ token: str | None = None
803
+ async def fetch() -> tuple[list[Any], str | None]:
804
+ nonlocal token
805
+ resp = await self.by_ports(**kwargs, pagination_next_token=token)
806
+ token = resp.next_token
807
+ return resp.port_events or [], token
808
+ return AsyncIterator(fetch)
809
+
810
+ def all_by_vessel(self, vessel_id: str, **kwargs: Any) -> AsyncIterator[Any]:
811
+ """Iterate over all port events for a vessel."""
812
+ token: str | None = None
813
+ async def fetch() -> tuple[list[Any], str | None]:
814
+ nonlocal token
815
+ resp = await self.by_vessel(vessel_id, **kwargs, pagination_next_token=token)
816
+ token = resp.next_token
817
+ return resp.port_events or [], token
818
+ return AsyncIterator(fetch)
819
+
820
+ def all_by_vessels(self, **kwargs: Any) -> AsyncIterator[Any]:
821
+ """Iterate over all port events by vessel name search."""
822
+ token: str | None = None
823
+ async def fetch() -> tuple[list[Any], str | None]:
824
+ nonlocal token
825
+ resp = await self.by_vessels(**kwargs, pagination_next_token=token)
826
+ token = resp.next_token
827
+ return resp.port_events or [], token
828
+ return AsyncIterator(fetch)
829
+
830
+
831
+ class AsyncEmissionsService:
832
+ """Emissions endpoints (async)."""
833
+
834
+ def __init__(self, client: httpx.AsyncClient) -> None:
835
+ self._client = client
836
+
837
+ async def list(self, *, filter_period: int | None = None, pagination_limit: int | None = None, pagination_next_token: str | None = None) -> VesselEmissionsResponse:
838
+ """Retrieve vessel emissions data."""
839
+ r = await self._client.get("/emissions", params=_strip_none({"filter.period": filter_period, "pagination.limit": pagination_limit, "pagination.nextToken": pagination_next_token}))
840
+ error_from_response(r.status_code, r.content)
841
+ return VesselEmissionsResponse.model_validate(r.json())
842
+
843
+ def list_all(self, **kwargs: Any) -> AsyncIterator[Any]:
844
+ """Iterate over all emissions across pages."""
845
+ token: str | None = None
846
+ async def fetch() -> tuple[list[Any], str | None]:
847
+ nonlocal token
848
+ resp = await self.list(**kwargs, pagination_next_token=token)
849
+ token = resp.next_token
850
+ return resp.emissions or [], token
851
+ return AsyncIterator(fetch)
852
+
853
+
854
+ class AsyncSearchService:
855
+ """Search endpoints (async)."""
856
+
857
+ def __init__(self, client: httpx.AsyncClient) -> None:
858
+ self._client = client
859
+
860
+ async def vessels(self, *, filter_name: str | None = None, filter_imo: str | None = None, filter_mmsi: str | None = None, filter_flag: str | None = None, filter_vessel_type: str | None = None, filter_callsign: str | None = None, pagination_limit: int | None = None, pagination_next_token: str | None = None) -> FindVesselsResponse:
861
+ """Search for vessels."""
862
+ r = await self._client.get("/search/vessels", params=_strip_none({"filter.name": filter_name, "filter.imo": filter_imo, "filter.mmsi": filter_mmsi, "filter.flag": filter_flag, "filter.vesselType": filter_vessel_type, "filter.callsign": filter_callsign, "pagination.limit": pagination_limit, "pagination.nextToken": pagination_next_token}))
863
+ error_from_response(r.status_code, r.content)
864
+ return FindVesselsResponse.model_validate(r.json())
865
+
866
+ async def ports(self, *, filter_name: str | None = None, filter_country: str | None = None, filter_port_type: str | None = None, filter_region: str | None = None, pagination_limit: int | None = None, pagination_next_token: str | None = None) -> FindPortsResponse:
867
+ """Search for ports."""
868
+ r = await self._client.get("/search/ports", params=_strip_none({"filter.name": filter_name, "filter.country": filter_country, "filter.type": filter_port_type, "filter.region": filter_region, "pagination.limit": pagination_limit, "pagination.nextToken": pagination_next_token}))
869
+ error_from_response(r.status_code, r.content)
870
+ return FindPortsResponse.model_validate(r.json())
871
+
872
+ async def dgps(self, *, filter_name: str | None = None, pagination_limit: int | None = None, pagination_next_token: str | None = None) -> FindDGPSStationsResponse:
873
+ """Search for DGPS stations."""
874
+ r = await self._client.get("/search/dgps", params=_strip_none({"filter.name": filter_name, "pagination.limit": pagination_limit, "pagination.nextToken": pagination_next_token}))
875
+ error_from_response(r.status_code, r.content)
876
+ return FindDGPSStationsResponse.model_validate(r.json())
877
+
878
+ async def light_aids(self, *, filter_name: str | None = None, pagination_limit: int | None = None, pagination_next_token: str | None = None) -> FindLightAidsResponse:
879
+ """Search for light aids."""
880
+ r = await self._client.get("/search/lightaids", params=_strip_none({"filter.name": filter_name, "pagination.limit": pagination_limit, "pagination.nextToken": pagination_next_token}))
881
+ error_from_response(r.status_code, r.content)
882
+ return FindLightAidsResponse.model_validate(r.json())
883
+
884
+ async def modus(self, *, filter_name: str | None = None, pagination_limit: int | None = None, pagination_next_token: str | None = None) -> FindMODUsResponse:
885
+ """Search for MODUs."""
886
+ r = await self._client.get("/search/modus", params=_strip_none({"filter.name": filter_name, "pagination.limit": pagination_limit, "pagination.nextToken": pagination_next_token}))
887
+ error_from_response(r.status_code, r.content)
888
+ return FindMODUsResponse.model_validate(r.json())
889
+
890
+ async def radio_beacons(self, *, filter_name: str | None = None, pagination_limit: int | None = None, pagination_next_token: str | None = None) -> FindRadioBeaconsResponse:
891
+ """Search for radio beacons."""
892
+ r = await self._client.get("/search/radiobeacons", params=_strip_none({"filter.name": filter_name, "pagination.limit": pagination_limit, "pagination.nextToken": pagination_next_token}))
893
+ error_from_response(r.status_code, r.content)
894
+ return FindRadioBeaconsResponse.model_validate(r.json())
895
+
896
+ # --- Iterators ---
897
+
898
+ def all_vessels(self, **kwargs: Any) -> AsyncIterator[Any]:
899
+ """Iterate over all vessel search results."""
900
+ token: str | None = None
901
+ async def fetch() -> tuple[list[Any], str | None]:
902
+ nonlocal token
903
+ resp = await self.vessels(**kwargs, pagination_next_token=token)
904
+ token = resp.next_token
905
+ return resp.vessels or [], token
906
+ return AsyncIterator(fetch)
907
+
908
+ def all_ports(self, **kwargs: Any) -> AsyncIterator[Any]:
909
+ """Iterate over all port search results."""
910
+ token: str | None = None
911
+ async def fetch() -> tuple[list[Any], str | None]:
912
+ nonlocal token
913
+ resp = await self.ports(**kwargs, pagination_next_token=token)
914
+ token = resp.next_token
915
+ return resp.ports or [], token
916
+ return AsyncIterator(fetch)
917
+
918
+ def all_dgps(self, **kwargs: Any) -> AsyncIterator[Any]:
919
+ """Iterate over all DGPS station search results."""
920
+ token: str | None = None
921
+ async def fetch() -> tuple[list[Any], str | None]:
922
+ nonlocal token
923
+ resp = await self.dgps(**kwargs, pagination_next_token=token)
924
+ token = resp.next_token
925
+ return resp.dgps_stations or [], token
926
+ return AsyncIterator(fetch)
927
+
928
+ def all_light_aids(self, **kwargs: Any) -> AsyncIterator[Any]:
929
+ """Iterate over all light aid search results."""
930
+ token: str | None = None
931
+ async def fetch() -> tuple[list[Any], str | None]:
932
+ nonlocal token
933
+ resp = await self.light_aids(**kwargs, pagination_next_token=token)
934
+ token = resp.next_token
935
+ return resp.light_aids or [], token
936
+ return AsyncIterator(fetch)
937
+
938
+ def all_modus(self, **kwargs: Any) -> AsyncIterator[Any]:
939
+ """Iterate over all MODU search results."""
940
+ token: str | None = None
941
+ async def fetch() -> tuple[list[Any], str | None]:
942
+ nonlocal token
943
+ resp = await self.modus(**kwargs, pagination_next_token=token)
944
+ token = resp.next_token
945
+ return resp.modus or [], token
946
+ return AsyncIterator(fetch)
947
+
948
+ def all_radio_beacons(self, **kwargs: Any) -> AsyncIterator[Any]:
949
+ """Iterate over all radio beacon search results."""
950
+ token: str | None = None
951
+ async def fetch() -> tuple[list[Any], str | None]:
952
+ nonlocal token
953
+ resp = await self.radio_beacons(**kwargs, pagination_next_token=token)
954
+ token = resp.next_token
955
+ return resp.radio_beacons or [], token
956
+ return AsyncIterator(fetch)
957
+
958
+
959
+ class AsyncLocationService:
960
+ """Location-based query endpoints (async)."""
961
+
962
+ def __init__(self, client: httpx.AsyncClient) -> None:
963
+ self._client = client
964
+
965
+ async def vessels_bounding_box(self, *, lat_min: float | None = None, lat_max: float | None = None, lon_min: float | None = None, lon_max: float | None = None, pagination_limit: int | None = None, pagination_next_token: str | None = None) -> VesselsWithinLocationResponse:
966
+ """Retrieve vessel positions within a bounding box."""
967
+ r = await self._client.get("/location/vessels/bounding-box", params=_strip_none({"filter.latBottom": lat_min, "filter.latTop": lat_max, "filter.lonLeft": lon_min, "filter.lonRight": lon_max, "pagination.limit": pagination_limit, "pagination.nextToken": pagination_next_token}))
968
+ error_from_response(r.status_code, r.content)
969
+ return VesselsWithinLocationResponse.model_validate(r.json())
970
+
971
+ async def vessels_radius(self, *, latitude: float | None = None, longitude: float | None = None, radius: float | None = None, pagination_limit: int | None = None, pagination_next_token: str | None = None) -> VesselsWithinLocationResponse:
972
+ """Retrieve vessel positions within a radius."""
973
+ r = await self._client.get("/location/vessels/radius", params=_strip_none({"filter.latitude": latitude, "filter.longitude": longitude, "filter.radius": radius, "pagination.limit": pagination_limit, "pagination.nextToken": pagination_next_token}))
974
+ error_from_response(r.status_code, r.content)
975
+ return VesselsWithinLocationResponse.model_validate(r.json())
976
+
977
+ async def ports_bounding_box(self, *, lat_min: float | None = None, lat_max: float | None = None, lon_min: float | None = None, lon_max: float | None = None, pagination_limit: int | None = None, pagination_next_token: str | None = None) -> PortsWithinLocationResponse:
978
+ """Retrieve ports within a bounding box."""
979
+ r = await self._client.get("/location/ports/bounding-box", params=_strip_none({"filter.latBottom": lat_min, "filter.latTop": lat_max, "filter.lonLeft": lon_min, "filter.lonRight": lon_max, "pagination.limit": pagination_limit, "pagination.nextToken": pagination_next_token}))
980
+ error_from_response(r.status_code, r.content)
981
+ return PortsWithinLocationResponse.model_validate(r.json())
982
+
983
+ async def ports_radius(self, *, latitude: float | None = None, longitude: float | None = None, radius: float | None = None, pagination_limit: int | None = None, pagination_next_token: str | None = None) -> PortsWithinLocationResponse:
984
+ """Retrieve ports within a radius."""
985
+ r = await self._client.get("/location/ports/radius", params=_strip_none({"filter.latitude": latitude, "filter.longitude": longitude, "filter.radius": radius, "pagination.limit": pagination_limit, "pagination.nextToken": pagination_next_token}))
986
+ error_from_response(r.status_code, r.content)
987
+ return PortsWithinLocationResponse.model_validate(r.json())
988
+
989
+ async def dgps_bounding_box(self, *, lat_min: float | None = None, lat_max: float | None = None, lon_min: float | None = None, lon_max: float | None = None, pagination_limit: int | None = None, pagination_next_token: str | None = None) -> DGPSStationsWithinLocationResponse:
990
+ """Retrieve DGPS stations within a bounding box."""
991
+ r = await self._client.get("/location/dgps/bounding-box", params=_strip_none({"filter.latBottom": lat_min, "filter.latTop": lat_max, "filter.lonLeft": lon_min, "filter.lonRight": lon_max, "pagination.limit": pagination_limit, "pagination.nextToken": pagination_next_token}))
992
+ error_from_response(r.status_code, r.content)
993
+ return DGPSStationsWithinLocationResponse.model_validate(r.json())
994
+
995
+ async def dgps_radius(self, *, latitude: float | None = None, longitude: float | None = None, radius: float | None = None, pagination_limit: int | None = None, pagination_next_token: str | None = None) -> DGPSStationsWithinLocationResponse:
996
+ """Retrieve DGPS stations within a radius."""
997
+ r = await self._client.get("/location/dgps/radius", params=_strip_none({"filter.latitude": latitude, "filter.longitude": longitude, "filter.radius": radius, "pagination.limit": pagination_limit, "pagination.nextToken": pagination_next_token}))
998
+ error_from_response(r.status_code, r.content)
999
+ return DGPSStationsWithinLocationResponse.model_validate(r.json())
1000
+
1001
+ async def light_aids_bounding_box(self, *, lat_min: float | None = None, lat_max: float | None = None, lon_min: float | None = None, lon_max: float | None = None, pagination_limit: int | None = None, pagination_next_token: str | None = None) -> LightAidsWithinLocationResponse:
1002
+ """Retrieve light aids within a bounding box."""
1003
+ r = await self._client.get("/location/lightaids/bounding-box", params=_strip_none({"filter.latBottom": lat_min, "filter.latTop": lat_max, "filter.lonLeft": lon_min, "filter.lonRight": lon_max, "pagination.limit": pagination_limit, "pagination.nextToken": pagination_next_token}))
1004
+ error_from_response(r.status_code, r.content)
1005
+ return LightAidsWithinLocationResponse.model_validate(r.json())
1006
+
1007
+ async def light_aids_radius(self, *, latitude: float | None = None, longitude: float | None = None, radius: float | None = None, pagination_limit: int | None = None, pagination_next_token: str | None = None) -> LightAidsWithinLocationResponse:
1008
+ """Retrieve light aids within a radius."""
1009
+ r = await self._client.get("/location/lightaids/radius", params=_strip_none({"filter.latitude": latitude, "filter.longitude": longitude, "filter.radius": radius, "pagination.limit": pagination_limit, "pagination.nextToken": pagination_next_token}))
1010
+ error_from_response(r.status_code, r.content)
1011
+ return LightAidsWithinLocationResponse.model_validate(r.json())
1012
+
1013
+ async def modus_bounding_box(self, *, lat_min: float | None = None, lat_max: float | None = None, lon_min: float | None = None, lon_max: float | None = None, pagination_limit: int | None = None, pagination_next_token: str | None = None) -> MODUsWithinLocationResponse:
1014
+ """Retrieve MODUs within a bounding box."""
1015
+ r = await self._client.get("/location/modu/bounding-box", params=_strip_none({"filter.latBottom": lat_min, "filter.latTop": lat_max, "filter.lonLeft": lon_min, "filter.lonRight": lon_max, "pagination.limit": pagination_limit, "pagination.nextToken": pagination_next_token}))
1016
+ error_from_response(r.status_code, r.content)
1017
+ return MODUsWithinLocationResponse.model_validate(r.json())
1018
+
1019
+ async def modus_radius(self, *, latitude: float | None = None, longitude: float | None = None, radius: float | None = None, pagination_limit: int | None = None, pagination_next_token: str | None = None) -> MODUsWithinLocationResponse:
1020
+ """Retrieve MODUs within a radius."""
1021
+ r = await self._client.get("/location/modu/radius", params=_strip_none({"filter.latitude": latitude, "filter.longitude": longitude, "filter.radius": radius, "pagination.limit": pagination_limit, "pagination.nextToken": pagination_next_token}))
1022
+ error_from_response(r.status_code, r.content)
1023
+ return MODUsWithinLocationResponse.model_validate(r.json())
1024
+
1025
+ async def radio_beacons_bounding_box(self, *, lat_min: float | None = None, lat_max: float | None = None, lon_min: float | None = None, lon_max: float | None = None, pagination_limit: int | None = None, pagination_next_token: str | None = None) -> RadioBeaconsWithinLocationResponse:
1026
+ """Retrieve radio beacons within a bounding box."""
1027
+ r = await self._client.get("/location/radiobeacons/bounding-box", params=_strip_none({"filter.latBottom": lat_min, "filter.latTop": lat_max, "filter.lonLeft": lon_min, "filter.lonRight": lon_max, "pagination.limit": pagination_limit, "pagination.nextToken": pagination_next_token}))
1028
+ error_from_response(r.status_code, r.content)
1029
+ return RadioBeaconsWithinLocationResponse.model_validate(r.json())
1030
+
1031
+ async def radio_beacons_radius(self, *, latitude: float | None = None, longitude: float | None = None, radius: float | None = None, pagination_limit: int | None = None, pagination_next_token: str | None = None) -> RadioBeaconsWithinLocationResponse:
1032
+ """Retrieve radio beacons within a radius."""
1033
+ r = await self._client.get("/location/radiobeacons/radius", params=_strip_none({"filter.latitude": latitude, "filter.longitude": longitude, "filter.radius": radius, "pagination.limit": pagination_limit, "pagination.nextToken": pagination_next_token}))
1034
+ error_from_response(r.status_code, r.content)
1035
+ return RadioBeaconsWithinLocationResponse.model_validate(r.json())
1036
+
1037
+ # --- Iterators ---
1038
+
1039
+ def all_vessels_bounding_box(self, **kwargs: Any) -> AsyncIterator[Any]:
1040
+ """Iterate over all vessel positions in a bounding box."""
1041
+ token: str | None = None
1042
+ async def fetch() -> tuple[list[Any], str | None]:
1043
+ nonlocal token
1044
+ resp = await self.vessels_bounding_box(**kwargs, pagination_next_token=token)
1045
+ token = resp.next_token
1046
+ return resp.vessels or [], token
1047
+ return AsyncIterator(fetch)
1048
+
1049
+ def all_vessels_radius(self, **kwargs: Any) -> AsyncIterator[Any]:
1050
+ """Iterate over all vessel positions within a radius."""
1051
+ token: str | None = None
1052
+ async def fetch() -> tuple[list[Any], str | None]:
1053
+ nonlocal token
1054
+ resp = await self.vessels_radius(**kwargs, pagination_next_token=token)
1055
+ token = resp.next_token
1056
+ return resp.vessels or [], token
1057
+ return AsyncIterator(fetch)
1058
+
1059
+ def all_ports_bounding_box(self, **kwargs: Any) -> AsyncIterator[Any]:
1060
+ """Iterate over all ports in a bounding box."""
1061
+ token: str | None = None
1062
+ async def fetch() -> tuple[list[Any], str | None]:
1063
+ nonlocal token
1064
+ resp = await self.ports_bounding_box(**kwargs, pagination_next_token=token)
1065
+ token = resp.next_token
1066
+ return resp.ports or [], token
1067
+ return AsyncIterator(fetch)
1068
+
1069
+ def all_ports_radius(self, **kwargs: Any) -> AsyncIterator[Any]:
1070
+ """Iterate over all ports within a radius."""
1071
+ token: str | None = None
1072
+ async def fetch() -> tuple[list[Any], str | None]:
1073
+ nonlocal token
1074
+ resp = await self.ports_radius(**kwargs, pagination_next_token=token)
1075
+ token = resp.next_token
1076
+ return resp.ports or [], token
1077
+ return AsyncIterator(fetch)
1078
+
1079
+ def all_dgps_bounding_box(self, **kwargs: Any) -> AsyncIterator[Any]:
1080
+ """Iterate over all DGPS stations in a bounding box."""
1081
+ token: str | None = None
1082
+ async def fetch() -> tuple[list[Any], str | None]:
1083
+ nonlocal token
1084
+ resp = await self.dgps_bounding_box(**kwargs, pagination_next_token=token)
1085
+ token = resp.next_token
1086
+ return resp.dgps_stations or [], token
1087
+ return AsyncIterator(fetch)
1088
+
1089
+ def all_dgps_radius(self, **kwargs: Any) -> AsyncIterator[Any]:
1090
+ """Iterate over all DGPS stations within a radius."""
1091
+ token: str | None = None
1092
+ async def fetch() -> tuple[list[Any], str | None]:
1093
+ nonlocal token
1094
+ resp = await self.dgps_radius(**kwargs, pagination_next_token=token)
1095
+ token = resp.next_token
1096
+ return resp.dgps_stations or [], token
1097
+ return AsyncIterator(fetch)
1098
+
1099
+ def all_light_aids_bounding_box(self, **kwargs: Any) -> AsyncIterator[Any]:
1100
+ """Iterate over all light aids in a bounding box."""
1101
+ token: str | None = None
1102
+ async def fetch() -> tuple[list[Any], str | None]:
1103
+ nonlocal token
1104
+ resp = await self.light_aids_bounding_box(**kwargs, pagination_next_token=token)
1105
+ token = resp.next_token
1106
+ return resp.light_aids or [], token
1107
+ return AsyncIterator(fetch)
1108
+
1109
+ def all_light_aids_radius(self, **kwargs: Any) -> AsyncIterator[Any]:
1110
+ """Iterate over all light aids within a radius."""
1111
+ token: str | None = None
1112
+ async def fetch() -> tuple[list[Any], str | None]:
1113
+ nonlocal token
1114
+ resp = await self.light_aids_radius(**kwargs, pagination_next_token=token)
1115
+ token = resp.next_token
1116
+ return resp.light_aids or [], token
1117
+ return AsyncIterator(fetch)
1118
+
1119
+ def all_modus_bounding_box(self, **kwargs: Any) -> AsyncIterator[Any]:
1120
+ """Iterate over all MODUs in a bounding box."""
1121
+ token: str | None = None
1122
+ async def fetch() -> tuple[list[Any], str | None]:
1123
+ nonlocal token
1124
+ resp = await self.modus_bounding_box(**kwargs, pagination_next_token=token)
1125
+ token = resp.next_token
1126
+ return resp.modus or [], token
1127
+ return AsyncIterator(fetch)
1128
+
1129
+ def all_modus_radius(self, **kwargs: Any) -> AsyncIterator[Any]:
1130
+ """Iterate over all MODUs within a radius."""
1131
+ token: str | None = None
1132
+ async def fetch() -> tuple[list[Any], str | None]:
1133
+ nonlocal token
1134
+ resp = await self.modus_radius(**kwargs, pagination_next_token=token)
1135
+ token = resp.next_token
1136
+ return resp.modus or [], token
1137
+ return AsyncIterator(fetch)
1138
+
1139
+ def all_radio_beacons_bounding_box(self, **kwargs: Any) -> AsyncIterator[Any]:
1140
+ """Iterate over all radio beacons in a bounding box."""
1141
+ token: str | None = None
1142
+ async def fetch() -> tuple[list[Any], str | None]:
1143
+ nonlocal token
1144
+ resp = await self.radio_beacons_bounding_box(**kwargs, pagination_next_token=token)
1145
+ token = resp.next_token
1146
+ return resp.radio_beacons or [], token
1147
+ return AsyncIterator(fetch)
1148
+
1149
+ def all_radio_beacons_radius(self, **kwargs: Any) -> AsyncIterator[Any]:
1150
+ """Iterate over all radio beacons within a radius."""
1151
+ token: str | None = None
1152
+ async def fetch() -> tuple[list[Any], str | None]:
1153
+ nonlocal token
1154
+ resp = await self.radio_beacons_radius(**kwargs, pagination_next_token=token)
1155
+ token = resp.next_token
1156
+ return resp.radio_beacons or [], token
1157
+ return AsyncIterator(fetch)
1158
+
1159
+
1160
+ class AsyncNavtexService:
1161
+ """NAVTEX message endpoints (async)."""
1162
+
1163
+ def __init__(self, client: httpx.AsyncClient) -> None:
1164
+ self._client = client
1165
+
1166
+ async def list(self, *, time_from: str | None = None, time_to: str | None = None, pagination_limit: int | None = None, pagination_next_token: str | None = None) -> NavtexMessagesResponse:
1167
+ """Retrieve NAVTEX maritime safety messages."""
1168
+ r = await self._client.get("/navtex", params=_strip_none({"time.from": time_from, "time.to": time_to, "pagination.limit": pagination_limit, "pagination.nextToken": pagination_next_token}))
1169
+ error_from_response(r.status_code, r.content)
1170
+ return NavtexMessagesResponse.model_validate(r.json())
1171
+
1172
+ def list_all(self, **kwargs: Any) -> AsyncIterator[Any]:
1173
+ """Iterate over all NAVTEX messages across pages."""
1174
+ token: str | None = None
1175
+ async def fetch() -> tuple[list[Any], str | None]:
1176
+ nonlocal token
1177
+ resp = await self.list(**kwargs, pagination_next_token=token)
1178
+ token = resp.next_token
1179
+ return resp.navtex_messages or [], token
1180
+ return AsyncIterator(fetch)