vessel-api-python 1.3.0__py3-none-any.whl → 2.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.
@@ -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."""
@@ -1,6 +1,6 @@
1
1
  """Constants for the Vessel API Python SDK."""
2
2
 
3
- VERSION = "1.1.0"
3
+ VERSION = "2.0.0"
4
4
  DEFAULT_BASE_URL = "https://api.vesselapi.com/v1"
5
5
  DEFAULT_USER_AGENT = f"vesselapi-python/{VERSION}"
6
6
  DEFAULT_MAX_RETRIES = 3
@@ -13,6 +13,10 @@ class VesselAPIError(Exception):
13
13
  status_code: The HTTP status code from the response.
14
14
  message: A human-readable error message.
15
15
  body: The raw response body bytes, available for re-parsing.
16
+ code: The API's machine-readable error code (e.g. ``insufficient_credits``,
17
+ ``feature_not_available``), or None if the response carried none.
18
+ Branch on this rather than on ``message``, which may be reworded.
19
+ error_type: The API's error type (e.g. ``payment_required_error``), or None.
16
20
  """
17
21
 
18
22
  def __init__(
@@ -20,10 +24,14 @@ class VesselAPIError(Exception):
20
24
  status_code: int,
21
25
  message: str,
22
26
  body: bytes = b"",
27
+ code: str | None = None,
28
+ error_type: str | None = None,
23
29
  ) -> None:
24
30
  self.status_code = status_code
25
31
  self.message = message
26
32
  self.body = body
33
+ self.code = code
34
+ self.error_type = error_type
27
35
  super().__init__(f"vesselapi: {message} (status {status_code})")
28
36
 
29
37
  @property
@@ -41,11 +49,39 @@ class VesselAPIError(Exception):
41
49
  """True if the error is a 401 Unauthorized response."""
42
50
  return self.status_code == 401
43
51
 
52
+ @property
53
+ def is_payment_required(self) -> bool:
54
+ """True if the error is a 402 Payment Required response.
55
+
56
+ Returned when a satellite-backed call is made with no satellite credits
57
+ left. Retry without ``filter_sat`` to serve stored data instead, or top
58
+ up the balance.
59
+ """
60
+ return self.status_code == 402
61
+
62
+ @property
63
+ def is_forbidden(self) -> bool:
64
+ """True if the error is a 403 Forbidden response.
65
+
66
+ Distinct from 401: the key is valid but not permitted. Check ``code`` to
67
+ tell a temporarily suspended key from a feature that is not on the
68
+ caller's plan.
69
+ """
70
+ return self.status_code == 403
71
+
44
72
 
45
73
  class VesselAuthError(VesselAPIError):
46
74
  """Raised on 401 Unauthorized responses."""
47
75
 
48
76
 
77
+ class VesselPaymentRequiredError(VesselAPIError):
78
+ """Raised on 402 Payment Required responses (satellite credits exhausted)."""
79
+
80
+
81
+ class VesselForbiddenError(VesselAPIError):
82
+ """Raised on 403 Forbidden responses (key suspended, or feature not on plan)."""
83
+
84
+
49
85
  class VesselNotFoundError(VesselAPIError):
50
86
  """Raised on 404 Not Found responses."""
51
87
 
@@ -76,6 +112,8 @@ def error_from_response(
76
112
 
77
113
  Raises:
78
114
  VesselAuthError: On 401 responses.
115
+ VesselPaymentRequiredError: On 402 responses (satellite credits exhausted).
116
+ VesselForbiddenError: On 403 responses (suspended key, or feature not on plan).
79
117
  VesselNotFoundError: On 404 responses.
80
118
  VesselRateLimitError: On 429 responses.
81
119
  VesselServerError: On 5xx responses.
@@ -86,6 +124,8 @@ def error_from_response(
86
124
 
87
125
  # Try to extract a human-readable message from the response body.
88
126
  msg = _status_text(status_code)
127
+ code: str | None = None
128
+ error_type: str | None = None
89
129
  if body:
90
130
  try:
91
131
  data = json.loads(body)
@@ -98,6 +138,10 @@ def error_from_response(
98
138
  flat_msg = data.get("message") if isinstance(data, dict) else None
99
139
  if flat_msg and isinstance(flat_msg, str):
100
140
  msg = flat_msg
141
+ # The API also sends machine-readable discriminators. Expose them:
142
+ # branching on `message` breaks whenever the wording changes.
143
+ code = _get_nested(data, "error", "code")
144
+ error_type = _get_nested(data, "error", "type")
101
145
  # If both fail, msg stays as HTTP status text.
102
146
  # Raw body is always available in VesselAPIError.body.
103
147
  except (json.JSONDecodeError, TypeError, AttributeError):
@@ -107,6 +151,10 @@ def error_from_response(
107
151
  error_cls: type[VesselAPIError]
108
152
  if status_code == 401:
109
153
  error_cls = VesselAuthError
154
+ elif status_code == 402:
155
+ error_cls = VesselPaymentRequiredError
156
+ elif status_code == 403:
157
+ error_cls = VesselForbiddenError
110
158
  elif status_code == 404:
111
159
  error_cls = VesselNotFoundError
112
160
  elif status_code == 429:
@@ -116,7 +164,7 @@ def error_from_response(
116
164
  else:
117
165
  error_cls = VesselAPIError
118
166
 
119
- raise error_cls(status_code=status_code, message=msg, body=body)
167
+ raise error_cls(status_code=status_code, message=msg, body=body, code=code, error_type=error_type)
120
168
 
121
169
 
122
170
  def _get_nested(data: dict[str, object] | list[object] | None, *keys: str) -> str | None: