vessel-api-python 1.3.0__tar.gz → 2.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. vessel_api_python-2.0.0/CHANGELOG.md +123 -0
  2. {vessel_api_python-1.3.0 → vessel_api_python-2.0.0}/PKG-INFO +80 -16
  3. {vessel_api_python-1.3.0 → vessel_api_python-2.0.0}/README.md +77 -13
  4. {vessel_api_python-1.3.0 → vessel_api_python-2.0.0}/pyproject.toml +21 -3
  5. {vessel_api_python-1.3.0 → vessel_api_python-2.0.0}/src/vessel_api_python/__init__.py +9 -64
  6. {vessel_api_python-1.3.0 → vessel_api_python-2.0.0}/src/vessel_api_python/_client.py +0 -4
  7. {vessel_api_python-1.3.0 → vessel_api_python-2.0.0}/src/vessel_api_python/_constants.py +1 -1
  8. {vessel_api_python-1.3.0 → vessel_api_python-2.0.0}/src/vessel_api_python/_errors.py +49 -1
  9. {vessel_api_python-1.3.0 → vessel_api_python-2.0.0}/src/vessel_api_python/_models.py +50 -396
  10. vessel_api_python-2.0.0/src/vessel_api_python/_services.py +3019 -0
  11. {vessel_api_python-1.3.0 → vessel_api_python-2.0.0}/src/vessel_api_python/_transport.py +5 -2
  12. vessel_api_python-1.3.0/.github/workflows/ci.yml +0 -46
  13. vessel_api_python-1.3.0/.github/workflows/publish.yml +0 -24
  14. vessel_api_python-1.3.0/Makefile +0 -21
  15. vessel_api_python-1.3.0/examples/async_basic.py +0 -67
  16. vessel_api_python-1.3.0/examples/basic.py +0 -86
  17. vessel_api_python-1.3.0/openapi/swagger.json +0 -5779
  18. vessel_api_python-1.3.0/src/vessel_api_python/_services.py +0 -1225
  19. vessel_api_python-1.3.0/tests/__init__.py +0 -0
  20. vessel_api_python-1.3.0/tests/conftest.py +0 -34
  21. vessel_api_python-1.3.0/tests/test_client.py +0 -102
  22. vessel_api_python-1.3.0/tests/test_errors.py +0 -136
  23. vessel_api_python-1.3.0/tests/test_iterator.py +0 -136
  24. vessel_api_python-1.3.0/tests/test_services.py +0 -177
  25. vessel_api_python-1.3.0/tests/test_smoke.py +0 -601
  26. vessel_api_python-1.3.0/tests/test_transport.py +0 -215
  27. {vessel_api_python-1.3.0 → vessel_api_python-2.0.0}/.gitignore +0 -0
  28. {vessel_api_python-1.3.0 → vessel_api_python-2.0.0}/LICENSE +0 -0
  29. {vessel_api_python-1.3.0 → vessel_api_python-2.0.0}/SECURITY.md +0 -0
  30. {vessel_api_python-1.3.0 → vessel_api_python-2.0.0}/src/vessel_api_python/_iterator.py +0 -0
  31. {vessel_api_python-1.3.0 → vessel_api_python-2.0.0}/src/vessel_api_python/py.typed +0 -0
@@ -0,0 +1,123 @@
1
+ # Changelog
2
+
3
+ All notable changes to this package are documented here.
4
+ This project follows [Semantic Versioning](https://semver.org/).
5
+
6
+ ## [2.0.0] - 2026-08-16
7
+
8
+ Vessel ownership, classification and inspection data, and NAVTEX messages, were
9
+ retired from the VesselAPI service on **10 August 2026**. This release removes
10
+ the corresponding client surface and brings the SDK back in line with the API.
11
+
12
+ Every change below applies to both the sync and async clients.
13
+
14
+ ### Removed (breaking)
15
+
16
+ These endpoints no longer exist. Calling them returned HTTP 410 from 10 August
17
+ 2026 onward; the methods are now gone so the failure surfaces at import or call
18
+ time rather than as a runtime error.
19
+
20
+ | Removed | Replacement |
21
+ |---|---|
22
+ | `vessels.classification()` | none |
23
+ | `vessels.inspections()` | none |
24
+ | `vessels.inspection_detail()` | none |
25
+ | `vessels.ownership()` | none |
26
+ | `client.navtex` (whole service: `list()`, `list_all()`) | none |
27
+
28
+ Also removed:
29
+
30
+ - **Search filters** `filter_class_society` and `filter_owner` on
31
+ `search.vessels()`. The API began rejecting both with HTTP 400, so any call
32
+ passing them was already failing.
33
+ - **Vessel fields** `builder`, `class_society`, `owner_name`, `manager_name`.
34
+ - **Models** for the removed endpoints (26 classes), plus the four
35
+ backwards-compatibility aliases `Classification`, `Inspection`,
36
+ `InspectionDetail` and `Ownership`: 30 public names in total.
37
+
38
+ **Migrating from 1.x:** remove calls to the methods above. There is no
39
+ alternative source for that data through this API. Drop `filter_class_society`
40
+ and `filter_owner` from any search call. If you read `owner_name`,
41
+ `manager_name`, `builder` or `class_society` from a vessel, those attributes are
42
+ gone.
43
+
44
+ ### Added
45
+
46
+ - **Search**: `q` (free-text across name, IMO, MMSI, ENI and callsign) and
47
+ `filter_eni` on `search.vessels()`.
48
+ - **Repeatable filters.** `filter_flag` and `filter_vessel_type` on
49
+ `search.vessels()`, and `filter_country`, `filter_port_type`, `filter_size`,
50
+ `filter_harbor_size` and `filter_harbor_use` on `search.ports()`, now accept
51
+ `str | list[str]` (exported as `MultiFilter`). A list matches any of the
52
+ values. Passing a plain string behaves exactly as before.
53
+ - **`filter_sat`** on `vessels.position()`. ⚠️ Satellite lookups draw on a
54
+ prepaid balance and are charged per call. Omit it to serve stored positions.
55
+ - **`time_from` / `time_to`** on `port_events.by_port()`, `by_ports()`,
56
+ `by_vessels()` and their iterators.
57
+ - **Vessel fields**: `eni`, `teu`, `vessel_subtype`, `name_ais`,
58
+ `summer_draught`, `draught_calculated_avg`, `draught_observed_max`,
59
+ `speed_calculated_avg`, `speed_observed_max`. All are declared by the API and
60
+ were previously discarded by the models. Each is optional, and how often one
61
+ is populated varies by field and by vessel.
62
+ - **Search metadata**: `FindVesselsResponse.meta` (`matched_on`, `query`) via
63
+ the new `VesselSearchMeta` model, and `ResolutionMeta.suggested_id_type`, a
64
+ hint to retry with the other identifier type when a lookup misses.
65
+ - **Error handling**: `VesselPaymentRequiredError` (402, satellite credits
66
+ exhausted) and `VesselForbiddenError` (403, key suspended or feature not on
67
+ your plan), plus `is_payment_required` / `is_forbidden` properties. All errors
68
+ now expose the API's machine-readable `code` and `error_type`. Branch on
69
+ those rather than on `message`, which may be reworded.
70
+
71
+ ### Changed (breaking)
72
+
73
+ - **Parameters the API requires are now required in Python too.** They are
74
+ keyword-only with no default, so omitting one raises `TypeError` at the call
75
+ instead of returning HTTP 400 from the server. Previously they defaulted to
76
+ `None`, which type-checked cleanly and then failed at runtime.
77
+ - `location.*_bounding_box()`: `lat_min`, `lat_max`, `lon_min`, `lon_max`.
78
+ - `location.*_radius()`: `latitude`, `longitude`, `radius`.
79
+ - `search.dgps()`, `search.light_aids()`, `search.modus()`,
80
+ `search.radio_beacons()`: `filter_name`.
81
+ - `port_events.by_ports()`: `filter_port_name`.
82
+ `port_events.by_vessels()`: `filter_vessel_name`.
83
+ - `vessels.positions()`: `filter_ids`.
84
+ - The matching `all_*` iterators enforce the same set. 38 methods in total,
85
+ on both the sync and async clients.
86
+ - **`vessels.positions()` parameter order changed.** `filter_ids` now precedes
87
+ `filter_id_type`. Both are keyword-only, so callers are unaffected.
88
+ - `search.vessels()` and `search.ports()` are unaffected: the API marks their
89
+ `filter_name` optional, and it stays optional here.
90
+ - `filter_id_type` is unchanged. The API requires it, but the SDK sends it on
91
+ every call and it keeps its `"imo"` default.
92
+ - **Iterator signatures are now explicit.** 50 of the `all_*` / `*_all`
93
+ iterators accepted `**kwargs` and forwarded blindly. They now declare exactly
94
+ the parameters they support, so mistyped keywords raise `TypeError` instead of
95
+ being silently ignored, and type checkers and editors can see them. This one
96
+ is breaking only in a narrow case:
97
+ - Anyone passing valid keyword arguments is unaffected.
98
+ - Passing `pagination_next_token` to an iterator is now rejected at the call.
99
+ It was already broken: the iterator supplies that token itself, so it
100
+ raised `got multiple values for keyword argument` at first iteration.
101
+
102
+ **Migrating from 1.x:** any call that relied on the server rejecting a missing
103
+ bound or name now fails at the call site instead. Supply the values listed above.
104
+
105
+ ### Fixed
106
+
107
+ - **`vessels.all_positions()` could not be time-bounded.** It did not accept
108
+ `time_from` / `time_to` at all, although `positions()` supported them, so
109
+ auto-paginating a historical window was impossible without hand-rolling the
110
+ token loop.
111
+ - **`ports.inbound()` and `ports.inbound_all()` required an ETA window** they
112
+ should not have. `eta_from` and `eta_to` were mandatory since 1.2.0, although
113
+ the service treats both as optional and defaults them to now and 72 hours
114
+ ahead. Both are now optional.
115
+ - The User-Agent reported `1.1.0` regardless of the installed version.
116
+ - Source distributions previously included files beyond the package itself. The
117
+ sdist is now scoped to the library, README, licence and metadata.
118
+
119
+ ### Attribution
120
+
121
+ Emissions and casualty data: © European Union. Source: European Maritime Safety
122
+ Agency (EMSA), THETIS-MRV (EU MRV, Regulation (EU) 2015/757) and the European
123
+ Marine Casualty Information Platform (EMCIP). See the README.
@@ -1,7 +1,7 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.5
2
2
  Name: vessel-api-python
3
- Version: 1.3.0
4
- Summary: Python client for the Vessel Tracking API — maritime vessel tracking, port events, emissions, and navigation data.
3
+ Version: 2.0.0
4
+ Summary: Python client for the Vessel Tracking API: maritime vessel tracking, port events, emissions, and nautical infrastructure.
5
5
  Project-URL: Documentation, https://vesselapi.com/docs
6
6
  Project-URL: Repository, https://github.com/vessel-api/vesselapi-python
7
7
  Project-URL: Issues, https://github.com/vessel-api/vesselapi-python/issues
@@ -39,7 +39,7 @@ Description-Content-Type: text/markdown
39
39
  [![Python](https://img.shields.io/pypi/pyversions/vessel-api-python.svg)](https://pypi.org/project/vessel-api-python/)
40
40
  [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
41
41
 
42
- Python client for the [Vessel Tracking API](https://vesselapi.com) — maritime vessel tracking, port events, emissions, and navigation data.
42
+ Python client for the [Vessel Tracking API](https://vesselapi.com): maritime vessel tracking, port events, emissions, and nautical infrastructure.
43
43
 
44
44
  **Resources**: [Documentation](https://vesselapi.com/docs) | [API Explorer](https://vesselapi.com/api-reference) | [Dashboard](https://dashboard.vesselapi.com) | [Contact Support](mailto:support@vesselapi.com)
45
45
 
@@ -91,15 +91,33 @@ asyncio.run(main())
91
91
 
92
92
  | Service | Methods | Description |
93
93
  |---------|---------|-------------|
94
- | `vessels` | `get`, `position`, `casualties`, `classification`, `emissions`, `eta`, `inspections`, `inspection_detail`, `ownership`, `positions` | Vessel details, positions, and records |
95
- | `ports` | `get` | Port lookup by UN/LOCODE |
94
+ | `vessels` | `get`, `position`, `casualties`, `emissions`, `eta`, `positions` | Vessel details, positions, and records |
95
+ | `ports` | `get`, `inbound`, `inbound_all` | Port lookup by UN/LOCODE and inbound vessels |
96
96
  | `port_events` | `list`, `by_port`, `by_ports`, `by_vessel`, `last_by_vessel`, `by_vessels` | Vessel arrival/departure events |
97
97
  | `emissions` | `list` | EU MRV emissions data |
98
98
  | `search` | `vessels`, `ports`, `dgps`, `light_aids`, `modus`, `radio_beacons` | Full-text search across entity types |
99
99
  | `location` | `vessels_bounding_box`, `vessels_radius`, `ports_bounding_box`, `ports_radius`, `dgps_bounding_box`, `dgps_radius`, `light_aids_bounding_box`, `light_aids_radius`, `modus_bounding_box`, `modus_radius`, `radio_beacons_bounding_box`, `radio_beacons_radius` | Geo queries by bounding box or radius |
100
- | `navtex` | `list` | NAVTEX maritime safety messages |
101
100
 
102
- **37 methods total.**
101
+ **33 methods total**, one per API endpoint, plus 28 auto-pagination iterators.
102
+
103
+ ## Required Parameters
104
+
105
+ Parameters the API requires are keyword-only with no default, so leaving one out
106
+ raises `TypeError` at the call instead of returning HTTP 400 from the server:
107
+
108
+ ```python
109
+ client.location.vessels_radius(latitude=51.9225, longitude=4.47917, radius=10000)
110
+ client.location.vessels_radius(latitude=51.9225) # TypeError: missing longitude, radius
111
+ ```
112
+
113
+ This covers the geo bounds on every `location` method (`lat_min`, `lat_max`,
114
+ `lon_min`, `lon_max` for bounding boxes; `latitude`, `longitude`, `radius` for
115
+ radius queries), `filter_name` on `search.dgps`, `search.light_aids`,
116
+ `search.modus` and `search.radio_beacons`, `filter_port_name` on
117
+ `port_events.by_ports`, `filter_vessel_name` on `port_events.by_vessels`, and
118
+ `filter_ids` on `vessels.positions`, along with the matching `all_*` iterators.
119
+ `filter_id_type` is required by the API but keeps its `"imo"` default, since the
120
+ SDK always sends it.
103
121
 
104
122
  ## Vessel Lookup & Location
105
123
 
@@ -108,16 +126,47 @@ asyncio.run(main())
108
126
  vessel = client.vessels.get("9811000")
109
127
  print(f"{vessel.vessel.name} ({vessel.vessel.vessel_type})")
110
128
 
111
- # Get the vessel's latest AIS position.
112
- pos = client.vessels.position("9811000")
129
+ # Get a vessel's latest stored AIS position. A vessel that exists may have no
130
+ # stored position, which raises VesselNotFoundError rather than returning empty.
131
+ pos = client.vessels.position("232003239", filter_id_type="mmsi")
113
132
  print(f"Position: {pos.vessel_position.latitude}, {pos.vessel_position.longitude}")
114
133
 
134
+ # filter_sat=True falls back to satellite AIS when nothing is stored.
135
+ # ⚠️ Satellite lookups draw on a prepaid balance and are charged per call.
136
+ # Omit it unless you need that fallback.
137
+
115
138
  # Find all vessels within 10 km of Rotterdam.
116
139
  nearby = client.location.vessels_radius(latitude=51.9225, longitude=4.47917, radius=10000)
117
140
  for v in nearby.vessels or []:
118
141
  print(f"{v.vessel_name} at {v.latitude}, {v.longitude}")
119
142
  ```
120
143
 
144
+ ## Search
145
+
146
+ `search.vessels` accepts `q`, a unified search across IMO, MMSI, ENI, callsign and
147
+ name. One value can return several vessels, and `_meta.matched_on` maps each result's
148
+ index to the fields it matched on.
149
+
150
+ ```python
151
+ res = client.search.vessels(q="4606770")
152
+ print(res.meta.matched_on) # {"0": ["eni"]}
153
+ ```
154
+
155
+ Filters the API declares as repeatable take either one value or a list. A list is
156
+ sent as a repeated query parameter, so the values are OR-ed:
157
+
158
+ ```python
159
+ # ?filter.flag=PA&filter.flag=LR&filter.vesselType=Container%20Ship
160
+ client.search.vessels(filter_flag=["PA", "LR"], filter_vessel_type="Container Ship")
161
+
162
+ client.search.ports(filter_country=["NL", "BE"], filter_size=["LARGE", "MEDIUM"])
163
+ ```
164
+
165
+ These are `filter_flag` and `filter_vessel_type` on `search.vessels`;
166
+ `filter_country`, `filter_port_type`, `filter_size`, `filter_harbor_size` and
167
+ `filter_harbor_use` on `search.ports`; and `filter_ids` on `vessels.positions`,
168
+ which also accepts the comma-separated single-string form.
169
+
121
170
  ## Error Handling
122
171
 
123
172
  All methods raise specific exception types on non-2xx responses:
@@ -131,7 +180,7 @@ except VesselAPIError as err:
131
180
  if err.is_not_found:
132
181
  print("Port not found")
133
182
  elif err.is_rate_limited:
134
- print("Rate limited — back off")
183
+ print("Rate limited, backing off")
135
184
  elif err.is_auth_error:
136
185
  print("Check API key")
137
186
  print(err.status_code, err.message)
@@ -150,8 +199,14 @@ for vessel in client.search.all_vessels(filter_vessel_type="Tanker"):
150
199
  async for vessel in client.search.all_vessels(filter_vessel_type="Tanker"):
151
200
  print(vessel.name)
152
201
 
153
- # Collect a bounded set at once
202
+ # Collect every match at once. pagination_limit is the page size, not a total:
203
+ # .collect() walks to the end, so this is one request per page until exhausted.
154
204
  vessels = client.search.all_vessels(filter_vessel_type="Tanker", pagination_limit=50).collect()
205
+
206
+ # To take a fixed number, stop the iterator yourself.
207
+ from itertools import islice
208
+
209
+ first_50 = list(islice(client.search.all_vessels(filter_vessel_type="Tanker"), 50))
155
210
  ```
156
211
 
157
212
  ## Configuration
@@ -170,15 +225,24 @@ Retries use exponential backoff with jitter on 429 and 5xx responses. The `Retry
170
225
 
171
226
  ## Documentation
172
227
 
173
- - [API Documentation](https://vesselapi.com/docs) — endpoint guides, request/response schemas, and usage examples
174
- - [API Explorer](https://vesselapi.com/api-reference) — interactive API reference
175
- - [Dashboard](https://dashboard.vesselapi.com) — manage API keys and monitor usage
228
+ - [API Documentation](https://vesselapi.com/docs): endpoint guides, request/response schemas, and usage examples
229
+ - [API Explorer](https://vesselapi.com/api-reference): interactive API reference
230
+ - [Dashboard](https://dashboard.vesselapi.com): manage API keys and monitor usage
176
231
 
177
232
  ## Contributing & Support
178
233
 
179
234
  Found a bug, have a feature request, or need help? You're welcome to [open an issue](https://github.com/vessel-api/vesselapi-python/issues). For API-level bugs and feature requests, please use the [main VesselAPI repository](https://github.com/vessel-api/VesselApi/issues).
180
235
 
181
- For security vulnerabilities, **do not** open a public issue — email security@vesselapi.com instead. See [SECURITY.md](SECURITY.md).
236
+ For security vulnerabilities, **do not** open a public issue. Email security@vesselapi.com instead. See [SECURITY.md](SECURITY.md).
237
+
238
+ ## Data Sources & Attribution
239
+
240
+ Emissions and casualty data: © European Union. Source: European Maritime Safety Agency
241
+ (EMSA): THETIS-MRV (EU MRV, Regulation (EU) 2015/757) and the European Marine Casualty
242
+ Information Platform (EMCIP). Reused under the European Commission reuse notice
243
+ (Commission Decision 2011/833/EU), which authorises reuse for commercial and
244
+ non-commercial purposes with acknowledgement of the source. Data may be transformed and
245
+ combined; EMSA does not endorse this service.
182
246
 
183
247
  ## License
184
248
 
@@ -5,7 +5,7 @@
5
5
  [![Python](https://img.shields.io/pypi/pyversions/vessel-api-python.svg)](https://pypi.org/project/vessel-api-python/)
6
6
  [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
7
7
 
8
- Python client for the [Vessel Tracking API](https://vesselapi.com) — maritime vessel tracking, port events, emissions, and navigation data.
8
+ Python client for the [Vessel Tracking API](https://vesselapi.com): maritime vessel tracking, port events, emissions, and nautical infrastructure.
9
9
 
10
10
  **Resources**: [Documentation](https://vesselapi.com/docs) | [API Explorer](https://vesselapi.com/api-reference) | [Dashboard](https://dashboard.vesselapi.com) | [Contact Support](mailto:support@vesselapi.com)
11
11
 
@@ -57,15 +57,33 @@ asyncio.run(main())
57
57
 
58
58
  | Service | Methods | Description |
59
59
  |---------|---------|-------------|
60
- | `vessels` | `get`, `position`, `casualties`, `classification`, `emissions`, `eta`, `inspections`, `inspection_detail`, `ownership`, `positions` | Vessel details, positions, and records |
61
- | `ports` | `get` | Port lookup by UN/LOCODE |
60
+ | `vessels` | `get`, `position`, `casualties`, `emissions`, `eta`, `positions` | Vessel details, positions, and records |
61
+ | `ports` | `get`, `inbound`, `inbound_all` | Port lookup by UN/LOCODE and inbound vessels |
62
62
  | `port_events` | `list`, `by_port`, `by_ports`, `by_vessel`, `last_by_vessel`, `by_vessels` | Vessel arrival/departure events |
63
63
  | `emissions` | `list` | EU MRV emissions data |
64
64
  | `search` | `vessels`, `ports`, `dgps`, `light_aids`, `modus`, `radio_beacons` | Full-text search across entity types |
65
65
  | `location` | `vessels_bounding_box`, `vessels_radius`, `ports_bounding_box`, `ports_radius`, `dgps_bounding_box`, `dgps_radius`, `light_aids_bounding_box`, `light_aids_radius`, `modus_bounding_box`, `modus_radius`, `radio_beacons_bounding_box`, `radio_beacons_radius` | Geo queries by bounding box or radius |
66
- | `navtex` | `list` | NAVTEX maritime safety messages |
67
66
 
68
- **37 methods total.**
67
+ **33 methods total**, one per API endpoint, plus 28 auto-pagination iterators.
68
+
69
+ ## Required Parameters
70
+
71
+ Parameters the API requires are keyword-only with no default, so leaving one out
72
+ raises `TypeError` at the call instead of returning HTTP 400 from the server:
73
+
74
+ ```python
75
+ client.location.vessels_radius(latitude=51.9225, longitude=4.47917, radius=10000)
76
+ client.location.vessels_radius(latitude=51.9225) # TypeError: missing longitude, radius
77
+ ```
78
+
79
+ This covers the geo bounds on every `location` method (`lat_min`, `lat_max`,
80
+ `lon_min`, `lon_max` for bounding boxes; `latitude`, `longitude`, `radius` for
81
+ radius queries), `filter_name` on `search.dgps`, `search.light_aids`,
82
+ `search.modus` and `search.radio_beacons`, `filter_port_name` on
83
+ `port_events.by_ports`, `filter_vessel_name` on `port_events.by_vessels`, and
84
+ `filter_ids` on `vessels.positions`, along with the matching `all_*` iterators.
85
+ `filter_id_type` is required by the API but keeps its `"imo"` default, since the
86
+ SDK always sends it.
69
87
 
70
88
  ## Vessel Lookup & Location
71
89
 
@@ -74,16 +92,47 @@ asyncio.run(main())
74
92
  vessel = client.vessels.get("9811000")
75
93
  print(f"{vessel.vessel.name} ({vessel.vessel.vessel_type})")
76
94
 
77
- # Get the vessel's latest AIS position.
78
- pos = client.vessels.position("9811000")
95
+ # Get a vessel's latest stored AIS position. A vessel that exists may have no
96
+ # stored position, which raises VesselNotFoundError rather than returning empty.
97
+ pos = client.vessels.position("232003239", filter_id_type="mmsi")
79
98
  print(f"Position: {pos.vessel_position.latitude}, {pos.vessel_position.longitude}")
80
99
 
100
+ # filter_sat=True falls back to satellite AIS when nothing is stored.
101
+ # ⚠️ Satellite lookups draw on a prepaid balance and are charged per call.
102
+ # Omit it unless you need that fallback.
103
+
81
104
  # Find all vessels within 10 km of Rotterdam.
82
105
  nearby = client.location.vessels_radius(latitude=51.9225, longitude=4.47917, radius=10000)
83
106
  for v in nearby.vessels or []:
84
107
  print(f"{v.vessel_name} at {v.latitude}, {v.longitude}")
85
108
  ```
86
109
 
110
+ ## Search
111
+
112
+ `search.vessels` accepts `q`, a unified search across IMO, MMSI, ENI, callsign and
113
+ name. One value can return several vessels, and `_meta.matched_on` maps each result's
114
+ index to the fields it matched on.
115
+
116
+ ```python
117
+ res = client.search.vessels(q="4606770")
118
+ print(res.meta.matched_on) # {"0": ["eni"]}
119
+ ```
120
+
121
+ Filters the API declares as repeatable take either one value or a list. A list is
122
+ sent as a repeated query parameter, so the values are OR-ed:
123
+
124
+ ```python
125
+ # ?filter.flag=PA&filter.flag=LR&filter.vesselType=Container%20Ship
126
+ client.search.vessels(filter_flag=["PA", "LR"], filter_vessel_type="Container Ship")
127
+
128
+ client.search.ports(filter_country=["NL", "BE"], filter_size=["LARGE", "MEDIUM"])
129
+ ```
130
+
131
+ These are `filter_flag` and `filter_vessel_type` on `search.vessels`;
132
+ `filter_country`, `filter_port_type`, `filter_size`, `filter_harbor_size` and
133
+ `filter_harbor_use` on `search.ports`; and `filter_ids` on `vessels.positions`,
134
+ which also accepts the comma-separated single-string form.
135
+
87
136
  ## Error Handling
88
137
 
89
138
  All methods raise specific exception types on non-2xx responses:
@@ -97,7 +146,7 @@ except VesselAPIError as err:
97
146
  if err.is_not_found:
98
147
  print("Port not found")
99
148
  elif err.is_rate_limited:
100
- print("Rate limited — back off")
149
+ print("Rate limited, backing off")
101
150
  elif err.is_auth_error:
102
151
  print("Check API key")
103
152
  print(err.status_code, err.message)
@@ -116,8 +165,14 @@ for vessel in client.search.all_vessels(filter_vessel_type="Tanker"):
116
165
  async for vessel in client.search.all_vessels(filter_vessel_type="Tanker"):
117
166
  print(vessel.name)
118
167
 
119
- # Collect a bounded set at once
168
+ # Collect every match at once. pagination_limit is the page size, not a total:
169
+ # .collect() walks to the end, so this is one request per page until exhausted.
120
170
  vessels = client.search.all_vessels(filter_vessel_type="Tanker", pagination_limit=50).collect()
171
+
172
+ # To take a fixed number, stop the iterator yourself.
173
+ from itertools import islice
174
+
175
+ first_50 = list(islice(client.search.all_vessels(filter_vessel_type="Tanker"), 50))
121
176
  ```
122
177
 
123
178
  ## Configuration
@@ -136,15 +191,24 @@ Retries use exponential backoff with jitter on 429 and 5xx responses. The `Retry
136
191
 
137
192
  ## Documentation
138
193
 
139
- - [API Documentation](https://vesselapi.com/docs) — endpoint guides, request/response schemas, and usage examples
140
- - [API Explorer](https://vesselapi.com/api-reference) — interactive API reference
141
- - [Dashboard](https://dashboard.vesselapi.com) — manage API keys and monitor usage
194
+ - [API Documentation](https://vesselapi.com/docs): endpoint guides, request/response schemas, and usage examples
195
+ - [API Explorer](https://vesselapi.com/api-reference): interactive API reference
196
+ - [Dashboard](https://dashboard.vesselapi.com): manage API keys and monitor usage
142
197
 
143
198
  ## Contributing & Support
144
199
 
145
200
  Found a bug, have a feature request, or need help? You're welcome to [open an issue](https://github.com/vessel-api/vesselapi-python/issues). For API-level bugs and feature requests, please use the [main VesselAPI repository](https://github.com/vessel-api/VesselApi/issues).
146
201
 
147
- For security vulnerabilities, **do not** open a public issue — email security@vesselapi.com instead. See [SECURITY.md](SECURITY.md).
202
+ For security vulnerabilities, **do not** open a public issue. Email security@vesselapi.com instead. See [SECURITY.md](SECURITY.md).
203
+
204
+ ## Data Sources & Attribution
205
+
206
+ Emissions and casualty data: © European Union. Source: European Maritime Safety Agency
207
+ (EMSA): THETIS-MRV (EU MRV, Regulation (EU) 2015/757) and the European Marine Casualty
208
+ Information Platform (EMCIP). Reused under the European Commission reuse notice
209
+ (Commission Decision 2011/833/EU), which authorises reuse for commercial and
210
+ non-commercial purposes with acknowledgement of the source. Data may be transformed and
211
+ combined; EMSA does not endorse this service.
148
212
 
149
213
  ## License
150
214
 
@@ -4,8 +4,8 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "vessel-api-python"
7
- version = "1.3.0"
8
- description = "Python client for the Vessel Tracking API — maritime vessel tracking, port events, emissions, and navigation data."
7
+ version = "2.0.0"
8
+ description = "Python client for the Vessel Tracking API: maritime vessel tracking, port events, emissions, and nautical infrastructure."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9"
11
11
  license = "MIT"
@@ -49,6 +49,19 @@ Changelog = "https://github.com/vessel-api/vesselapi-python/releases"
49
49
  [tool.hatch.build.targets.wheel]
50
50
  packages = ["src/vessel_api_python"]
51
51
 
52
+ # Scope the sdist explicitly. Hatchling's default is "everything not gitignored",
53
+ # which pulls in the specification, the smoke tests and the CI workflows. Keep
54
+ # this list to what a consumer of the package actually needs.
55
+ [tool.hatch.build.targets.sdist]
56
+ include = [
57
+ "src/vessel_api_python",
58
+ "README.md",
59
+ "CHANGELOG.md",
60
+ "LICENSE",
61
+ "SECURITY.md",
62
+ "pyproject.toml",
63
+ ]
64
+
52
65
  [tool.ruff]
53
66
  target-version = "py39"
54
67
  line-length = 120
@@ -58,7 +71,12 @@ select = ["E", "F", "I", "W", "UP", "B", "SIM"]
58
71
  ignore = ["E501"] # Line length handled by formatter
59
72
 
60
73
  [tool.mypy]
61
- python_version = "3.9"
74
+ # 3.10 is the lowest version current mypy accepts. The package still supports
75
+ # 3.9 (see requires-python): that floor is enforced by ruff's target-version
76
+ # below, which rejects 3.10-only idioms, and verified by the 3.9 job in the CI
77
+ # matrix, which runs the full test suite. Setting 3.9 here is silently ignored
78
+ # by mypy, which then checks against whatever interpreter it happens to run on.
79
+ python_version = "3.10"
62
80
  strict = true
63
81
  warn_return_any = true
64
82
  warn_unused_configs = true
@@ -13,28 +13,15 @@ from ._constants import DEFAULT_BASE_URL, VERSION
13
13
  from ._errors import (
14
14
  VesselAPIError,
15
15
  VesselAuthError,
16
+ VesselForbiddenError,
16
17
  VesselNotFoundError,
18
+ VesselPaymentRequiredError,
17
19
  VesselRateLimitError,
18
20
  VesselServerError,
19
21
  )
20
22
  from ._iterator import AsyncIterator, SyncIterator
21
23
  from ._models import (
22
24
  MODU,
23
- BroadcastStation,
24
- Classification,
25
- ClassificationCertificate,
26
- ClassificationCondition,
27
- ClassificationDimensions,
28
- ClassificationHull,
29
- ClassificationIdentification,
30
- ClassificationInfo,
31
- ClassificationMachinery,
32
- ClassificationOwner,
33
- ClassificationPurpose,
34
- ClassificationResponse,
35
- ClassificationSurvey,
36
- ClassificationVessel,
37
- ClassificationYard,
38
25
  DGPSStation,
39
26
  DGPSStationsWithinLocationResponse,
40
27
  FindDGPSStationsResponse,
@@ -44,22 +31,11 @@ from ._models import (
44
31
  FindRadioBeaconsResponse,
45
32
  FindVesselsResponse,
46
33
  GeoJSON,
47
- Inspection,
48
- InspectionDeficiency,
49
- InspectionDetail,
50
- InspectionDetailRecord,
51
- InspectionDetailResponseData,
52
- InspectionRecord,
53
- InspectionsResponseData,
54
34
  LightAid,
55
35
  LightAidsWithinLocationResponse,
56
36
  MarineCasualtiesResponse,
57
37
  MarineCasualty,
58
38
  MODUsWithinLocationResponse,
59
- Navtex,
60
- NavtexMessagesResponse,
61
- Ownership,
62
- OwnershipResponseData,
63
39
  Port,
64
40
  PortCountry,
65
41
  PortEvent,
@@ -72,23 +48,21 @@ from ._models import (
72
48
  RadioBeacon,
73
49
  RadioBeaconsWithinLocationResponse,
74
50
  ResolutionMeta,
75
- TypesInspectionDetailResponse,
76
- TypesInspectionsResponse,
77
- TypesOwnershipResponse,
78
51
  Vessel,
79
52
  VesselEmission,
80
53
  VesselEmissionsResponse,
81
54
  VesselETA,
82
55
  VesselETAResponse,
83
56
  VesselFormerName,
84
- VesselOwnership,
85
57
  VesselPosition,
86
58
  VesselPositionResponse,
87
59
  VesselPositionsResponse,
88
60
  VesselReference,
89
61
  VesselResponse,
62
+ VesselSearchMeta,
90
63
  VesselsWithinLocationResponse,
91
64
  )
65
+ from ._services import MultiFilter
92
66
 
93
67
  __all__ = [
94
68
  # Clients
@@ -97,7 +71,9 @@ __all__ = [
97
71
  # Errors
98
72
  "VesselAPIError",
99
73
  "VesselAuthError",
74
+ "VesselForbiddenError",
100
75
  "VesselNotFoundError",
76
+ "VesselPaymentRequiredError",
101
77
  "VesselRateLimitError",
102
78
  "VesselServerError",
103
79
  # Iterators
@@ -106,27 +82,15 @@ __all__ = [
106
82
  # Constants
107
83
  "VERSION",
108
84
  "DEFAULT_BASE_URL",
85
+ # Typing helpers
86
+ "MultiFilter",
109
87
  # Models — Shared / helper
110
88
  "GeoJSON",
111
89
  "PortCountry",
112
90
  "PortReference",
113
91
  "VesselReference",
114
92
  "VesselFormerName",
115
- "BroadcastStation",
116
93
  "ResolutionMeta",
117
- # Models — Classification sub-models
118
- "ClassificationCertificate",
119
- "ClassificationCondition",
120
- "ClassificationDimensions",
121
- "ClassificationHull",
122
- "ClassificationIdentification",
123
- "ClassificationInfo",
124
- "ClassificationMachinery",
125
- "ClassificationOwner",
126
- "ClassificationPurpose",
127
- "ClassificationSurvey",
128
- "ClassificationVessel",
129
- "ClassificationYard",
130
94
  # Models — Vessel
131
95
  "Vessel",
132
96
  "VesselResponse",
@@ -135,27 +99,10 @@ __all__ = [
135
99
  "VesselPositionsResponse",
136
100
  "MarineCasualty",
137
101
  "MarineCasualtiesResponse",
138
- "Classification",
139
- "ClassificationResponse",
140
102
  "VesselEmission",
141
103
  "VesselEmissionsResponse",
142
104
  "VesselETA",
143
105
  "VesselETAResponse",
144
- # Models — Inspection
145
- "InspectionRecord",
146
- "Inspection",
147
- "InspectionDeficiency",
148
- "InspectionDetailRecord",
149
- "InspectionDetail",
150
- "InspectionsResponseData",
151
- "InspectionDetailResponseData",
152
- "TypesInspectionsResponse",
153
- "TypesInspectionDetailResponse",
154
- # Models — Ownership
155
- "VesselOwnership",
156
- "Ownership",
157
- "OwnershipResponseData",
158
- "TypesOwnershipResponse",
159
106
  # Models — Port
160
107
  "Port",
161
108
  "PortResponse",
@@ -164,6 +111,7 @@ __all__ = [
164
111
  "PortEventsResponse",
165
112
  "PortInboundResponse",
166
113
  # Models — Search
114
+ "VesselSearchMeta",
167
115
  "FindVesselsResponse",
168
116
  "FindPortsResponse",
169
117
  "DGPSStation",
@@ -181,7 +129,4 @@ __all__ = [
181
129
  "LightAidsWithinLocationResponse",
182
130
  "MODUsWithinLocationResponse",
183
131
  "RadioBeaconsWithinLocationResponse",
184
- # Models — Navtex
185
- "Navtex",
186
- "NavtexMessagesResponse",
187
132
  ]
@@ -24,14 +24,12 @@ from ._constants import (
24
24
  from ._services import (
25
25
  AsyncEmissionsService,
26
26
  AsyncLocationService,
27
- AsyncNavtexService,
28
27
  AsyncPortEventsService,
29
28
  AsyncPortsService,
30
29
  AsyncSearchService,
31
30
  AsyncVesselsService,
32
31
  EmissionsService,
33
32
  LocationService,
34
- NavtexService,
35
33
  PortEventsService,
36
34
  PortsService,
37
35
  SearchService,
@@ -91,7 +89,6 @@ class VesselClient:
91
89
  self.emissions = EmissionsService(self._client)
92
90
  self.search = SearchService(self._client)
93
91
  self.location = LocationService(self._client)
94
- self.navtex = NavtexService(self._client)
95
92
 
96
93
  def close(self) -> None:
97
94
  """Close the underlying HTTP client and release resources."""
@@ -150,7 +147,6 @@ class AsyncVesselClient:
150
147
  self.emissions = AsyncEmissionsService(self._client)
151
148
  self.search = AsyncSearchService(self._client)
152
149
  self.location = AsyncLocationService(self._client)
153
- self.navtex = AsyncNavtexService(self._client)
154
150
 
155
151
  async def aclose(self) -> None:
156
152
  """Close the underlying HTTP client and release resources."""