vessel-api-python 1.3.1__tar.gz → 2.0.1__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.1/CHANGELOG.md +138 -0
  2. {vessel_api_python-1.3.1 → vessel_api_python-2.0.1}/PKG-INFO +82 -30
  3. {vessel_api_python-1.3.1 → vessel_api_python-2.0.1}/README.md +79 -27
  4. {vessel_api_python-1.3.1 → vessel_api_python-2.0.1}/pyproject.toml +22 -4
  5. {vessel_api_python-1.3.1 → vessel_api_python-2.0.1}/src/vessel_api_python/__init__.py +9 -75
  6. {vessel_api_python-1.3.1 → vessel_api_python-2.0.1}/src/vessel_api_python/_client.py +0 -4
  7. {vessel_api_python-1.3.1 → vessel_api_python-2.0.1}/src/vessel_api_python/_constants.py +1 -1
  8. {vessel_api_python-1.3.1 → vessel_api_python-2.0.1}/src/vessel_api_python/_errors.py +49 -1
  9. {vessel_api_python-1.3.1 → vessel_api_python-2.0.1}/src/vessel_api_python/_models.py +50 -396
  10. vessel_api_python-2.0.1/src/vessel_api_python/_services.py +3019 -0
  11. {vessel_api_python-1.3.1 → vessel_api_python-2.0.1}/src/vessel_api_python/_transport.py +5 -2
  12. vessel_api_python-1.3.1/.github/workflows/ci.yml +0 -46
  13. vessel_api_python-1.3.1/.github/workflows/publish.yml +0 -24
  14. vessel_api_python-1.3.1/Makefile +0 -21
  15. vessel_api_python-1.3.1/examples/async_basic.py +0 -67
  16. vessel_api_python-1.3.1/examples/basic.py +0 -86
  17. vessel_api_python-1.3.1/openapi/swagger.json +0 -5779
  18. vessel_api_python-1.3.1/src/vessel_api_python/_services.py +0 -1225
  19. vessel_api_python-1.3.1/tests/__init__.py +0 -0
  20. vessel_api_python-1.3.1/tests/conftest.py +0 -34
  21. vessel_api_python-1.3.1/tests/test_client.py +0 -102
  22. vessel_api_python-1.3.1/tests/test_errors.py +0 -136
  23. vessel_api_python-1.3.1/tests/test_iterator.py +0 -136
  24. vessel_api_python-1.3.1/tests/test_services.py +0 -177
  25. vessel_api_python-1.3.1/tests/test_smoke.py +0 -601
  26. vessel_api_python-1.3.1/tests/test_transport.py +0 -215
  27. {vessel_api_python-1.3.1 → vessel_api_python-2.0.1}/.gitignore +0 -0
  28. {vessel_api_python-1.3.1 → vessel_api_python-2.0.1}/LICENSE +0 -0
  29. {vessel_api_python-1.3.1 → vessel_api_python-2.0.1}/SECURITY.md +0 -0
  30. {vessel_api_python-1.3.1 → vessel_api_python-2.0.1}/src/vessel_api_python/_iterator.py +0 -0
  31. {vessel_api_python-1.3.1 → vessel_api_python-2.0.1}/src/vessel_api_python/py.typed +0 -0
@@ -0,0 +1,138 @@
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.1] - 2026-10-08
7
+
8
+ Documentation only. No method, parameter or model changed.
9
+
10
+ ### Fixed
11
+
12
+ - The bundled OpenAPI spec named the satellite fallback parameter `sat`; the
13
+ API's parameter is `filter.sat`. Descriptions corrected.
14
+
15
+ ### Changed
16
+
17
+ - README: the satellite fallback note says when it fires (no recent terrestrial
18
+ position stored); the radius example describes the time window it searches
19
+ (default 2 hours, at most 4).
20
+
21
+ ## [2.0.0] - 2026-08-16
22
+
23
+ Vessel ownership, classification and inspection data, and NAVTEX messages, were
24
+ retired from the VesselAPI service on **10 August 2026**. This release removes
25
+ the corresponding client surface and brings the SDK back in line with the API.
26
+
27
+ Every change below applies to both the sync and async clients.
28
+
29
+ ### Removed (breaking)
30
+
31
+ These endpoints no longer exist. Calling them returned HTTP 410 from 10 August
32
+ 2026 onward; the methods are now gone so the failure surfaces at import or call
33
+ time rather than as a runtime error.
34
+
35
+ | Removed | Replacement |
36
+ |---|---|
37
+ | `vessels.classification()` | none |
38
+ | `vessels.inspections()` | none |
39
+ | `vessels.inspection_detail()` | none |
40
+ | `vessels.ownership()` | none |
41
+ | `client.navtex` (whole service: `list()`, `list_all()`) | none |
42
+
43
+ Also removed:
44
+
45
+ - **Search filters** `filter_class_society` and `filter_owner` on
46
+ `search.vessels()`. The API began rejecting both with HTTP 400, so any call
47
+ passing them was already failing.
48
+ - **Vessel fields** `builder`, `class_society`, `owner_name`, `manager_name`.
49
+ - **Models** for the removed endpoints (26 classes), plus the four
50
+ backwards-compatibility aliases `Classification`, `Inspection`,
51
+ `InspectionDetail` and `Ownership`: 30 public names in total.
52
+
53
+ **Migrating from 1.x:** remove calls to the methods above. There is no
54
+ alternative source for that data through this API. Drop `filter_class_society`
55
+ and `filter_owner` from any search call. If you read `owner_name`,
56
+ `manager_name`, `builder` or `class_society` from a vessel, those attributes are
57
+ gone.
58
+
59
+ ### Added
60
+
61
+ - **Search**: `q` (free-text across name, IMO, MMSI, ENI and callsign) and
62
+ `filter_eni` on `search.vessels()`.
63
+ - **Repeatable filters.** `filter_flag` and `filter_vessel_type` on
64
+ `search.vessels()`, and `filter_country`, `filter_port_type`, `filter_size`,
65
+ `filter_harbor_size` and `filter_harbor_use` on `search.ports()`, now accept
66
+ `str | list[str]` (exported as `MultiFilter`). A list matches any of the
67
+ values. Passing a plain string behaves exactly as before.
68
+ - **`filter_sat`** on `vessels.position()`. ⚠️ Satellite lookups draw on a
69
+ prepaid balance and are charged per call. Omit it to serve stored positions.
70
+ - **`time_from` / `time_to`** on `port_events.by_port()`, `by_ports()`,
71
+ `by_vessels()` and their iterators.
72
+ - **Vessel fields**: `eni`, `teu`, `vessel_subtype`, `name_ais`,
73
+ `summer_draught`, `draught_calculated_avg`, `draught_observed_max`,
74
+ `speed_calculated_avg`, `speed_observed_max`. All are declared by the API and
75
+ were previously discarded by the models. Each is optional, and how often one
76
+ is populated varies by field and by vessel.
77
+ - **Search metadata**: `FindVesselsResponse.meta` (`matched_on`, `query`) via
78
+ the new `VesselSearchMeta` model, and `ResolutionMeta.suggested_id_type`, a
79
+ hint to retry with the other identifier type when a lookup misses.
80
+ - **Error handling**: `VesselPaymentRequiredError` (402, satellite credits
81
+ exhausted) and `VesselForbiddenError` (403, key suspended or feature not on
82
+ your plan), plus `is_payment_required` / `is_forbidden` properties. All errors
83
+ now expose the API's machine-readable `code` and `error_type`. Branch on
84
+ those rather than on `message`, which may be reworded.
85
+
86
+ ### Changed (breaking)
87
+
88
+ - **Parameters the API requires are now required in Python too.** They are
89
+ keyword-only with no default, so omitting one raises `TypeError` at the call
90
+ instead of returning HTTP 400 from the server. Previously they defaulted to
91
+ `None`, which type-checked cleanly and then failed at runtime.
92
+ - `location.*_bounding_box()`: `lat_min`, `lat_max`, `lon_min`, `lon_max`.
93
+ - `location.*_radius()`: `latitude`, `longitude`, `radius`.
94
+ - `search.dgps()`, `search.light_aids()`, `search.modus()`,
95
+ `search.radio_beacons()`: `filter_name`.
96
+ - `port_events.by_ports()`: `filter_port_name`.
97
+ `port_events.by_vessels()`: `filter_vessel_name`.
98
+ - `vessels.positions()`: `filter_ids`.
99
+ - The matching `all_*` iterators enforce the same set. 38 methods in total,
100
+ on both the sync and async clients.
101
+ - **`vessels.positions()` parameter order changed.** `filter_ids` now precedes
102
+ `filter_id_type`. Both are keyword-only, so callers are unaffected.
103
+ - `search.vessels()` and `search.ports()` are unaffected: the API marks their
104
+ `filter_name` optional, and it stays optional here.
105
+ - `filter_id_type` is unchanged. The API requires it, but the SDK sends it on
106
+ every call and it keeps its `"imo"` default.
107
+ - **Iterator signatures are now explicit.** 50 of the `all_*` / `*_all`
108
+ iterators accepted `**kwargs` and forwarded blindly. They now declare exactly
109
+ the parameters they support, so mistyped keywords raise `TypeError` instead of
110
+ being silently ignored, and type checkers and editors can see them. This one
111
+ is breaking only in a narrow case:
112
+ - Anyone passing valid keyword arguments is unaffected.
113
+ - Passing `pagination_next_token` to an iterator is now rejected at the call.
114
+ It was already broken: the iterator supplies that token itself, so it
115
+ raised `got multiple values for keyword argument` at first iteration.
116
+
117
+ **Migrating from 1.x:** any call that relied on the server rejecting a missing
118
+ bound or name now fails at the call site instead. Supply the values listed above.
119
+
120
+ ### Fixed
121
+
122
+ - **`vessels.all_positions()` could not be time-bounded.** It did not accept
123
+ `time_from` / `time_to` at all, although `positions()` supported them, so
124
+ auto-paginating a historical window was impossible without hand-rolling the
125
+ token loop.
126
+ - **`ports.inbound()` and `ports.inbound_all()` required an ETA window** they
127
+ should not have. `eta_from` and `eta_to` were mandatory since 1.2.0, although
128
+ the service treats both as optional and defaults them to now and 72 hours
129
+ ahead. Both are now optional.
130
+ - The User-Agent reported `1.1.0` regardless of the installed version.
131
+ - Source distributions previously included files beyond the package itself. The
132
+ sdist is now scoped to the library, README, licence and metadata.
133
+
134
+ ### Attribution
135
+
136
+ Emissions and casualty data: © European Union. Source: European Maritime Safety
137
+ Agency (EMSA), THETIS-MRV (EU MRV, Regulation (EU) 2015/757) and the European
138
+ Marine Casualty Information Platform (EMCIP). See the README.
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: vessel-api-python
3
- Version: 1.3.1
4
- Summary: Python client for the Vessel Tracking API — maritime vessel tracking, port events, emissions, and navigation data.
3
+ Version: 2.0.1
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
@@ -10,7 +10,7 @@ Author-email: Vessel API <support@vesselapi.com>
10
10
  License-Expression: MIT
11
11
  License-File: LICENSE
12
12
  Keywords: ais,maritime,sdk,tracking,vessel,vesselapi
13
- Classifier: Development Status :: 7 - Inactive
13
+ Classifier: Development Status :: 5 - Production/Stable
14
14
  Classifier: Intended Audience :: Developers
15
15
  Classifier: License :: OSI Approved :: MIT License
16
16
  Classifier: Programming Language :: Python :: 3
@@ -34,25 +34,12 @@ Description-Content-Type: text/markdown
34
34
 
35
35
  # vesselapi-python
36
36
 
37
- > [!WARNING]
38
- > **This version is no longer maintained.** Several endpoints wrapped by 1.x
39
- > were retired from the VesselAPI service on 10 August 2026 and now return
40
- > HTTP 410. Please upgrade:
41
- >
42
- > ```bash
43
- > pip install 'vessel-api-python>=2'
44
- > ```
45
- >
46
- > 2.x removes the retired surface, requires the parameters the API requires,
47
- > and adds unified search, repeatable filters and satellite position fallback.
48
- > See the CHANGELOG for migration notes.
49
-
50
37
  [![CI](https://github.com/vessel-api/vesselapi-python/actions/workflows/ci.yml/badge.svg?branch=master)](https://github.com/vessel-api/vesselapi-python/actions/workflows/ci.yml)
51
38
  [![PyPI](https://img.shields.io/pypi/v/vessel-api-python.svg)](https://pypi.org/project/vessel-api-python/)
52
39
  [![Python](https://img.shields.io/pypi/pyversions/vessel-api-python.svg)](https://pypi.org/project/vessel-api-python/)
53
40
  [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
54
41
 
55
- 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.
56
43
 
57
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)
58
45
 
@@ -104,15 +91,33 @@ asyncio.run(main())
104
91
 
105
92
  | Service | Methods | Description |
106
93
  |---------|---------|-------------|
107
- | `vessels` | `get`, `position`, `casualties`, `classification`, `emissions`, `eta`, `inspections`, `inspection_detail`, `ownership`, `positions` | Vessel details, positions, and records |
108
- | `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 |
109
96
  | `port_events` | `list`, `by_port`, `by_ports`, `by_vessel`, `last_by_vessel`, `by_vessels` | Vessel arrival/departure events |
110
97
  | `emissions` | `list` | EU MRV emissions data |
111
98
  | `search` | `vessels`, `ports`, `dgps`, `light_aids`, `modus`, `radio_beacons` | Full-text search across entity types |
112
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 |
113
- | `navtex` | `list` | NAVTEX maritime safety messages |
114
100
 
115
- **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.
116
121
 
117
122
  ## Vessel Lookup & Location
118
123
 
@@ -121,16 +126,48 @@ asyncio.run(main())
121
126
  vessel = client.vessels.get("9811000")
122
127
  print(f"{vessel.vessel.name} ({vessel.vessel.vessel_type})")
123
128
 
124
- # Get the vessel's latest AIS position.
125
- 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")
126
132
  print(f"Position: {pos.vessel_position.latitude}, {pos.vessel_position.longitude}")
127
133
 
128
- # Find all vessels within 10 km of Rotterdam.
134
+ # filter_sat=True falls back to satellite AIS when no recent terrestrial position is stored.
135
+ # ⚠️ Satellite lookups draw on a prepaid balance and are charged per call.
136
+ # Omit it unless you need that fallback.
137
+
138
+ # Find vessels with an AIS position within 10 km of Rotterdam in the last 2 hours
139
+ # (the default time window, at most 4 hours).
129
140
  nearby = client.location.vessels_radius(latitude=51.9225, longitude=4.47917, radius=10000)
130
141
  for v in nearby.vessels or []:
131
142
  print(f"{v.vessel_name} at {v.latitude}, {v.longitude}")
132
143
  ```
133
144
 
145
+ ## Search
146
+
147
+ `search.vessels` accepts `q`, a unified search across IMO, MMSI, ENI, callsign and
148
+ name. One value can return several vessels, and `_meta.matched_on` maps each result's
149
+ index to the fields it matched on.
150
+
151
+ ```python
152
+ res = client.search.vessels(q="4606770")
153
+ print(res.meta.matched_on) # {"0": ["eni"]}
154
+ ```
155
+
156
+ Filters the API declares as repeatable take either one value or a list. A list is
157
+ sent as a repeated query parameter, so the values are OR-ed:
158
+
159
+ ```python
160
+ # ?filter.flag=PA&filter.flag=LR&filter.vesselType=Container%20Ship
161
+ client.search.vessels(filter_flag=["PA", "LR"], filter_vessel_type="Container Ship")
162
+
163
+ client.search.ports(filter_country=["NL", "BE"], filter_size=["LARGE", "MEDIUM"])
164
+ ```
165
+
166
+ These are `filter_flag` and `filter_vessel_type` on `search.vessels`;
167
+ `filter_country`, `filter_port_type`, `filter_size`, `filter_harbor_size` and
168
+ `filter_harbor_use` on `search.ports`; and `filter_ids` on `vessels.positions`,
169
+ which also accepts the comma-separated single-string form.
170
+
134
171
  ## Error Handling
135
172
 
136
173
  All methods raise specific exception types on non-2xx responses:
@@ -144,7 +181,7 @@ except VesselAPIError as err:
144
181
  if err.is_not_found:
145
182
  print("Port not found")
146
183
  elif err.is_rate_limited:
147
- print("Rate limited — back off")
184
+ print("Rate limited, backing off")
148
185
  elif err.is_auth_error:
149
186
  print("Check API key")
150
187
  print(err.status_code, err.message)
@@ -163,8 +200,14 @@ for vessel in client.search.all_vessels(filter_vessel_type="Tanker"):
163
200
  async for vessel in client.search.all_vessels(filter_vessel_type="Tanker"):
164
201
  print(vessel.name)
165
202
 
166
- # Collect a bounded set at once
203
+ # Collect every match at once. pagination_limit is the page size, not a total:
204
+ # .collect() walks to the end, so this is one request per page until exhausted.
167
205
  vessels = client.search.all_vessels(filter_vessel_type="Tanker", pagination_limit=50).collect()
206
+
207
+ # To take a fixed number, stop the iterator yourself.
208
+ from itertools import islice
209
+
210
+ first_50 = list(islice(client.search.all_vessels(filter_vessel_type="Tanker"), 50))
168
211
  ```
169
212
 
170
213
  ## Configuration
@@ -183,15 +226,24 @@ Retries use exponential backoff with jitter on 429 and 5xx responses. The `Retry
183
226
 
184
227
  ## Documentation
185
228
 
186
- - [API Documentation](https://vesselapi.com/docs) — endpoint guides, request/response schemas, and usage examples
187
- - [API Explorer](https://vesselapi.com/api-reference) — interactive API reference
188
- - [Dashboard](https://dashboard.vesselapi.com) — manage API keys and monitor usage
229
+ - [API Documentation](https://vesselapi.com/docs): endpoint guides, request/response schemas, and usage examples
230
+ - [API Explorer](https://vesselapi.com/api-reference): interactive API reference
231
+ - [Dashboard](https://dashboard.vesselapi.com): manage API keys and monitor usage
189
232
 
190
233
  ## Contributing & Support
191
234
 
192
235
  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).
193
236
 
194
- For security vulnerabilities, **do not** open a public issue — email security@vesselapi.com instead. See [SECURITY.md](SECURITY.md).
237
+ For security vulnerabilities, **do not** open a public issue. Email security@vesselapi.com instead. See [SECURITY.md](SECURITY.md).
238
+
239
+ ## Data Sources & Attribution
240
+
241
+ Emissions and casualty data: © European Union. Source: European Maritime Safety Agency
242
+ (EMSA): THETIS-MRV (EU MRV, Regulation (EU) 2015/757) and the European Marine Casualty
243
+ Information Platform (EMCIP). Reused under the European Commission reuse notice
244
+ (Commission Decision 2011/833/EU), which authorises reuse for commercial and
245
+ non-commercial purposes with acknowledgement of the source. Data may be transformed and
246
+ combined; EMSA does not endorse this service.
195
247
 
196
248
  ## License
197
249
 
@@ -1,24 +1,11 @@
1
1
  # vesselapi-python
2
2
 
3
- > [!WARNING]
4
- > **This version is no longer maintained.** Several endpoints wrapped by 1.x
5
- > were retired from the VesselAPI service on 10 August 2026 and now return
6
- > HTTP 410. Please upgrade:
7
- >
8
- > ```bash
9
- > pip install 'vessel-api-python>=2'
10
- > ```
11
- >
12
- > 2.x removes the retired surface, requires the parameters the API requires,
13
- > and adds unified search, repeatable filters and satellite position fallback.
14
- > See the CHANGELOG for migration notes.
15
-
16
3
  [![CI](https://github.com/vessel-api/vesselapi-python/actions/workflows/ci.yml/badge.svg?branch=master)](https://github.com/vessel-api/vesselapi-python/actions/workflows/ci.yml)
17
4
  [![PyPI](https://img.shields.io/pypi/v/vessel-api-python.svg)](https://pypi.org/project/vessel-api-python/)
18
5
  [![Python](https://img.shields.io/pypi/pyversions/vessel-api-python.svg)](https://pypi.org/project/vessel-api-python/)
19
6
  [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
20
7
 
21
- 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.
22
9
 
23
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)
24
11
 
@@ -70,15 +57,33 @@ asyncio.run(main())
70
57
 
71
58
  | Service | Methods | Description |
72
59
  |---------|---------|-------------|
73
- | `vessels` | `get`, `position`, `casualties`, `classification`, `emissions`, `eta`, `inspections`, `inspection_detail`, `ownership`, `positions` | Vessel details, positions, and records |
74
- | `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 |
75
62
  | `port_events` | `list`, `by_port`, `by_ports`, `by_vessel`, `last_by_vessel`, `by_vessels` | Vessel arrival/departure events |
76
63
  | `emissions` | `list` | EU MRV emissions data |
77
64
  | `search` | `vessels`, `ports`, `dgps`, `light_aids`, `modus`, `radio_beacons` | Full-text search across entity types |
78
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 |
79
- | `navtex` | `list` | NAVTEX maritime safety messages |
80
66
 
81
- **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.
82
87
 
83
88
  ## Vessel Lookup & Location
84
89
 
@@ -87,16 +92,48 @@ asyncio.run(main())
87
92
  vessel = client.vessels.get("9811000")
88
93
  print(f"{vessel.vessel.name} ({vessel.vessel.vessel_type})")
89
94
 
90
- # Get the vessel's latest AIS position.
91
- 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")
92
98
  print(f"Position: {pos.vessel_position.latitude}, {pos.vessel_position.longitude}")
93
99
 
94
- # Find all vessels within 10 km of Rotterdam.
100
+ # filter_sat=True falls back to satellite AIS when no recent terrestrial position is stored.
101
+ # ⚠️ Satellite lookups draw on a prepaid balance and are charged per call.
102
+ # Omit it unless you need that fallback.
103
+
104
+ # Find vessels with an AIS position within 10 km of Rotterdam in the last 2 hours
105
+ # (the default time window, at most 4 hours).
95
106
  nearby = client.location.vessels_radius(latitude=51.9225, longitude=4.47917, radius=10000)
96
107
  for v in nearby.vessels or []:
97
108
  print(f"{v.vessel_name} at {v.latitude}, {v.longitude}")
98
109
  ```
99
110
 
111
+ ## Search
112
+
113
+ `search.vessels` accepts `q`, a unified search across IMO, MMSI, ENI, callsign and
114
+ name. One value can return several vessels, and `_meta.matched_on` maps each result's
115
+ index to the fields it matched on.
116
+
117
+ ```python
118
+ res = client.search.vessels(q="4606770")
119
+ print(res.meta.matched_on) # {"0": ["eni"]}
120
+ ```
121
+
122
+ Filters the API declares as repeatable take either one value or a list. A list is
123
+ sent as a repeated query parameter, so the values are OR-ed:
124
+
125
+ ```python
126
+ # ?filter.flag=PA&filter.flag=LR&filter.vesselType=Container%20Ship
127
+ client.search.vessels(filter_flag=["PA", "LR"], filter_vessel_type="Container Ship")
128
+
129
+ client.search.ports(filter_country=["NL", "BE"], filter_size=["LARGE", "MEDIUM"])
130
+ ```
131
+
132
+ These are `filter_flag` and `filter_vessel_type` on `search.vessels`;
133
+ `filter_country`, `filter_port_type`, `filter_size`, `filter_harbor_size` and
134
+ `filter_harbor_use` on `search.ports`; and `filter_ids` on `vessels.positions`,
135
+ which also accepts the comma-separated single-string form.
136
+
100
137
  ## Error Handling
101
138
 
102
139
  All methods raise specific exception types on non-2xx responses:
@@ -110,7 +147,7 @@ except VesselAPIError as err:
110
147
  if err.is_not_found:
111
148
  print("Port not found")
112
149
  elif err.is_rate_limited:
113
- print("Rate limited — back off")
150
+ print("Rate limited, backing off")
114
151
  elif err.is_auth_error:
115
152
  print("Check API key")
116
153
  print(err.status_code, err.message)
@@ -129,8 +166,14 @@ for vessel in client.search.all_vessels(filter_vessel_type="Tanker"):
129
166
  async for vessel in client.search.all_vessels(filter_vessel_type="Tanker"):
130
167
  print(vessel.name)
131
168
 
132
- # Collect a bounded set at once
169
+ # Collect every match at once. pagination_limit is the page size, not a total:
170
+ # .collect() walks to the end, so this is one request per page until exhausted.
133
171
  vessels = client.search.all_vessels(filter_vessel_type="Tanker", pagination_limit=50).collect()
172
+
173
+ # To take a fixed number, stop the iterator yourself.
174
+ from itertools import islice
175
+
176
+ first_50 = list(islice(client.search.all_vessels(filter_vessel_type="Tanker"), 50))
134
177
  ```
135
178
 
136
179
  ## Configuration
@@ -149,15 +192,24 @@ Retries use exponential backoff with jitter on 429 and 5xx responses. The `Retry
149
192
 
150
193
  ## Documentation
151
194
 
152
- - [API Documentation](https://vesselapi.com/docs) — endpoint guides, request/response schemas, and usage examples
153
- - [API Explorer](https://vesselapi.com/api-reference) — interactive API reference
154
- - [Dashboard](https://dashboard.vesselapi.com) — manage API keys and monitor usage
195
+ - [API Documentation](https://vesselapi.com/docs): endpoint guides, request/response schemas, and usage examples
196
+ - [API Explorer](https://vesselapi.com/api-reference): interactive API reference
197
+ - [Dashboard](https://dashboard.vesselapi.com): manage API keys and monitor usage
155
198
 
156
199
  ## Contributing & Support
157
200
 
158
201
  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).
159
202
 
160
- For security vulnerabilities, **do not** open a public issue — email security@vesselapi.com instead. See [SECURITY.md](SECURITY.md).
203
+ For security vulnerabilities, **do not** open a public issue. Email security@vesselapi.com instead. See [SECURITY.md](SECURITY.md).
204
+
205
+ ## Data Sources & Attribution
206
+
207
+ Emissions and casualty data: © European Union. Source: European Maritime Safety Agency
208
+ (EMSA): THETIS-MRV (EU MRV, Regulation (EU) 2015/757) and the European Marine Casualty
209
+ Information Platform (EMCIP). Reused under the European Commission reuse notice
210
+ (Commission Decision 2011/833/EU), which authorises reuse for commercial and
211
+ non-commercial purposes with acknowledgement of the source. Data may be transformed and
212
+ combined; EMSA does not endorse this service.
161
213
 
162
214
  ## License
163
215
 
@@ -4,8 +4,8 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "vessel-api-python"
7
- version = "1.3.1"
8
- description = "Python client for the Vessel Tracking API — maritime vessel tracking, port events, emissions, and navigation data."
7
+ version = "2.0.1"
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"
@@ -14,7 +14,7 @@ authors = [
14
14
  ]
15
15
  keywords = ["vesselapi", "maritime", "vessel", "tracking", "ais", "sdk"]
16
16
  classifiers = [
17
- "Development Status :: 7 - Inactive",
17
+ "Development Status :: 5 - Production/Stable",
18
18
  "Intended Audience :: Developers",
19
19
  "License :: OSI Approved :: MIT License",
20
20
  "Programming Language :: Python :: 3",
@@ -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