osmapi.errors

Error classes for the OpenStreetMap API.

  1"""
  2Error classes for the OpenStreetMap API."""
  3
  4import warnings
  5from typing import Any
  6
  7
  8class OsmApiError(Exception):
  9    """
 10    General OsmApi error class to provide a superclass for all other errors
 11    """
 12
 13
 14class MaximumRetryLimitReachedError(OsmApiError):
 15    """
 16    Error when the maximum amount of retries is reached and we have to give up
 17    """
 18
 19
 20class AuthenticationMissingError(OsmApiError):
 21    """
 22    Error when a request requires authentication, but no session was provided
 23    that could carry credentials.
 24
 25    Pass an authenticated `requests.Session` (e.g. an OAuth 2.0 session, see
 26    the [README](https://github.com/metaodi/osmapi#oauth-authentication)) to
 27    `OsmApi` to make authenticated requests.
 28
 29    This error is raised before the request is sent, and only when `OsmApi`
 30    created the http session itself — in that case there is no way for the
 31    request to be authenticated. A session that was passed in is never
 32    second-guessed (it can carry a token in `session.auth`, in an
 33    `Authorization` header, in a transport adapter, or add it per request),
 34    so a missing or invalid authorization on such a session is reported by
 35    the API as `OsmApi.UnauthorizedApiError` (HTTP 401) instead.
 36
 37    Before version 6.0 this error was called `UsernamePasswordMissingError`.
 38    That name still works, but it is deprecated and will be removed in
 39    version 7.0.
 40    """
 41
 42    pass
 43
 44
 45class NoChangesetOpenError(OsmApiError):
 46    """
 47    Error when an operation requires an open changeset, but currently
 48    no changeset _is_ open
 49    """
 50
 51    pass
 52
 53
 54class ChangesetAlreadyOpenError(OsmApiError):
 55    """
 56    Error when a user tries to open a changeset when there is already
 57    an open changeset
 58    """
 59
 60    pass
 61
 62
 63class OsmTypeAlreadyExistsError(OsmApiError):
 64    """
 65    Error when a user tries to create an object that already exsits
 66    """
 67
 68    pass
 69
 70
 71class XmlResponseInvalidError(OsmApiError):
 72    """
 73    Error if the XML response from the OpenStreetMap API is invalid
 74    """
 75
 76
 77class ApiError(OsmApiError):
 78    """
 79    Error class, is thrown when an API request fails
 80    """
 81
 82    def __init__(self, status: int, reason: str, payload: Any) -> None:
 83        self.status = status
 84        """HTTP error code"""
 85
 86        self.reason = reason
 87        """Error message"""
 88
 89        self.payload = payload
 90        """Payload of API when this error occured"""
 91
 92    @property
 93    def payload_str(self) -> str:
 94        """
 95        The payload decoded as text.
 96
 97        `payload` is usually the raw `bytes` body of the response; use this
 98        when the payload needs to be matched against or shown as a string.
 99        """
100        if isinstance(self.payload, bytes):
101            return self.payload.decode("utf-8", errors="replace")
102        return str(self.payload)
103
104    def __str__(self) -> str:
105        return f"Request failed: {self.status} - {self.reason} - {self.payload}"
106
107
108class UnauthorizedApiError(ApiError):
109    """
110    Error when the API returned an Unauthorized error,
111    e.g. when the provided OAuth token is expired
112    """
113
114    pass
115
116
117class AlreadySubscribedApiError(ApiError):
118    """
119    Error when a user tries to subscribe to a changeset
120    that she is already subscribed to
121    """
122
123    pass
124
125
126class NotSubscribedApiError(ApiError):
127    """
128    Error when user tries to unsubscribe from a changeset
129    that he is not subscribed to
130    """
131
132    pass
133
134
135class ElementDeletedApiError(ApiError):
136    """
137    Error when the requested element is deleted
138    """
139
140    pass
141
142
143class ElementNotFoundApiError(ApiError):
144    """
145    Error if the the requested element was not found
146    """
147
148
149class ResponseEmptyApiError(ApiError):
150    """
151    Error when the response to the request is empty
152    """
153
154    pass
155
156
157class ChangesetClosedApiError(ApiError):
158    """
159    Error if the the changeset in question has already been closed
160    """
161
162
163class NoteAlreadyClosedApiError(ApiError):
164    """
165    Error if the the note in question has already been closed
166    """
167
168
169class VersionMismatchApiError(ApiError):
170    """
171    Error if the provided version does not match the database version
172    of the element
173    """
174
175
176class PreconditionFailedApiError(ApiError):
177    """
178    Error if the precondition of the operation was not met:
179    - When a way has nodes that do not exist or are not visible
180    - When a relation has elements that do not exist or are not visible
181    - When a node/way/relation is still used in a way/relation
182    """
183
184
185class TimeoutApiError(ApiError):
186    """
187    Error if the http request ran into a timeout
188    """
189
190
191class ConnectionApiError(ApiError):
192    """
193    Error if there was a network error (e.g. DNS failure, refused connection)
194    while connecting to the remote server.
195    """
196
197
198DEPRECATED_ERROR_NAMES = {
199    "UsernamePasswordMissingError": "AuthenticationMissingError",
200}
201"""
202Error names that were renamed, mapped to their replacement.
203
204The old names still resolve to the new class (so `except` clauses keep
205working), but emit a `DeprecationWarning`. They will be removed in
206version 7.0.
207"""
208
209
210def resolve_deprecated_name(name: str) -> Any:
211    """
212    Return the error class a deprecated name refers to, with a warning.
213
214    Raises `AttributeError` if `name` is not a deprecated error name. The
215    `DeprecationWarning` is reported against the caller of the module-level
216    `__getattr__` that calls this, i.e. the code using the old name.
217    """
218    new_name = DEPRECATED_ERROR_NAMES.get(name)
219    if not new_name:
220        raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
221    warnings.warn(
222        f"{name} has been renamed to {new_name}, "
223        f"the old name is deprecated and will be removed in osmapi 7.0",
224        DeprecationWarning,
225        stacklevel=3,
226    )
227    return globals()[new_name]
228
229
230def __getattr__(name: str) -> Any:
231    """
232    Resolve a deprecated error name to its replacement (see PEP 562).
233
234    Accessing `osmapi.errors.UsernamePasswordMissingError` returns
235    `AuthenticationMissingError` and emits a `DeprecationWarning`.
236    """
237    return resolve_deprecated_name(name)
class OsmApiError(builtins.Exception):
 9class OsmApiError(Exception):
10    """
11    General OsmApi error class to provide a superclass for all other errors
12    """

General OsmApi error class to provide a superclass for all other errors

class MaximumRetryLimitReachedError(OsmApiError):
15class MaximumRetryLimitReachedError(OsmApiError):
16    """
17    Error when the maximum amount of retries is reached and we have to give up
18    """

Error when the maximum amount of retries is reached and we have to give up

class AuthenticationMissingError(OsmApiError):
21class AuthenticationMissingError(OsmApiError):
22    """
23    Error when a request requires authentication, but no session was provided
24    that could carry credentials.
25
26    Pass an authenticated `requests.Session` (e.g. an OAuth 2.0 session, see
27    the [README](https://github.com/metaodi/osmapi#oauth-authentication)) to
28    `OsmApi` to make authenticated requests.
29
30    This error is raised before the request is sent, and only when `OsmApi`
31    created the http session itself — in that case there is no way for the
32    request to be authenticated. A session that was passed in is never
33    second-guessed (it can carry a token in `session.auth`, in an
34    `Authorization` header, in a transport adapter, or add it per request),
35    so a missing or invalid authorization on such a session is reported by
36    the API as `OsmApi.UnauthorizedApiError` (HTTP 401) instead.
37
38    Before version 6.0 this error was called `UsernamePasswordMissingError`.
39    That name still works, but it is deprecated and will be removed in
40    version 7.0.
41    """
42
43    pass

Error when a request requires authentication, but no session was provided that could carry credentials.

Pass an authenticated requests.Session (e.g. an OAuth 2.0 session, see the README) to OsmApi to make authenticated requests.

This error is raised before the request is sent, and only when OsmApi created the http session itself — in that case there is no way for the request to be authenticated. A session that was passed in is never second-guessed (it can carry a token in session.auth, in an Authorization header, in a transport adapter, or add it per request), so a missing or invalid authorization on such a session is reported by the API as OsmApi.UnauthorizedApiError (HTTP 401) instead.

Before version 6.0 this error was called UsernamePasswordMissingError. That name still works, but it is deprecated and will be removed in version 7.0.

class NoChangesetOpenError(OsmApiError):
46class NoChangesetOpenError(OsmApiError):
47    """
48    Error when an operation requires an open changeset, but currently
49    no changeset _is_ open
50    """
51
52    pass

Error when an operation requires an open changeset, but currently no changeset _is_ open

class ChangesetAlreadyOpenError(OsmApiError):
55class ChangesetAlreadyOpenError(OsmApiError):
56    """
57    Error when a user tries to open a changeset when there is already
58    an open changeset
59    """
60
61    pass

Error when a user tries to open a changeset when there is already an open changeset

class OsmTypeAlreadyExistsError(OsmApiError):
64class OsmTypeAlreadyExistsError(OsmApiError):
65    """
66    Error when a user tries to create an object that already exsits
67    """
68
69    pass

Error when a user tries to create an object that already exsits

class XmlResponseInvalidError(OsmApiError):
72class XmlResponseInvalidError(OsmApiError):
73    """
74    Error if the XML response from the OpenStreetMap API is invalid
75    """

Error if the XML response from the OpenStreetMap API is invalid

class ApiError(OsmApiError):
 78class ApiError(OsmApiError):
 79    """
 80    Error class, is thrown when an API request fails
 81    """
 82
 83    def __init__(self, status: int, reason: str, payload: Any) -> None:
 84        self.status = status
 85        """HTTP error code"""
 86
 87        self.reason = reason
 88        """Error message"""
 89
 90        self.payload = payload
 91        """Payload of API when this error occured"""
 92
 93    @property
 94    def payload_str(self) -> str:
 95        """
 96        The payload decoded as text.
 97
 98        `payload` is usually the raw `bytes` body of the response; use this
 99        when the payload needs to be matched against or shown as a string.
100        """
101        if isinstance(self.payload, bytes):
102            return self.payload.decode("utf-8", errors="replace")
103        return str(self.payload)
104
105    def __str__(self) -> str:
106        return f"Request failed: {self.status} - {self.reason} - {self.payload}"

Error class, is thrown when an API request fails

ApiError(status: int, reason: str, payload: Any)
83    def __init__(self, status: int, reason: str, payload: Any) -> None:
84        self.status = status
85        """HTTP error code"""
86
87        self.reason = reason
88        """Error message"""
89
90        self.payload = payload
91        """Payload of API when this error occured"""
status

HTTP error code

reason

Error message

payload

Payload of API when this error occured

payload_str: str
 93    @property
 94    def payload_str(self) -> str:
 95        """
 96        The payload decoded as text.
 97
 98        `payload` is usually the raw `bytes` body of the response; use this
 99        when the payload needs to be matched against or shown as a string.
100        """
101        if isinstance(self.payload, bytes):
102            return self.payload.decode("utf-8", errors="replace")
103        return str(self.payload)

The payload decoded as text.

payload is usually the raw bytes body of the response; use this when the payload needs to be matched against or shown as a string.

class UnauthorizedApiError(ApiError):
109class UnauthorizedApiError(ApiError):
110    """
111    Error when the API returned an Unauthorized error,
112    e.g. when the provided OAuth token is expired
113    """
114
115    pass

Error when the API returned an Unauthorized error, e.g. when the provided OAuth token is expired

class AlreadySubscribedApiError(ApiError):
118class AlreadySubscribedApiError(ApiError):
119    """
120    Error when a user tries to subscribe to a changeset
121    that she is already subscribed to
122    """
123
124    pass

Error when a user tries to subscribe to a changeset that she is already subscribed to

class NotSubscribedApiError(ApiError):
127class NotSubscribedApiError(ApiError):
128    """
129    Error when user tries to unsubscribe from a changeset
130    that he is not subscribed to
131    """
132
133    pass

Error when user tries to unsubscribe from a changeset that he is not subscribed to

class ElementDeletedApiError(ApiError):
136class ElementDeletedApiError(ApiError):
137    """
138    Error when the requested element is deleted
139    """
140
141    pass

Error when the requested element is deleted

class ElementNotFoundApiError(ApiError):
144class ElementNotFoundApiError(ApiError):
145    """
146    Error if the the requested element was not found
147    """

Error if the the requested element was not found

class ResponseEmptyApiError(ApiError):
150class ResponseEmptyApiError(ApiError):
151    """
152    Error when the response to the request is empty
153    """
154
155    pass

Error when the response to the request is empty

class ChangesetClosedApiError(ApiError):
158class ChangesetClosedApiError(ApiError):
159    """
160    Error if the the changeset in question has already been closed
161    """

Error if the the changeset in question has already been closed

class NoteAlreadyClosedApiError(ApiError):
164class NoteAlreadyClosedApiError(ApiError):
165    """
166    Error if the the note in question has already been closed
167    """

Error if the the note in question has already been closed

class VersionMismatchApiError(ApiError):
170class VersionMismatchApiError(ApiError):
171    """
172    Error if the provided version does not match the database version
173    of the element
174    """

Error if the provided version does not match the database version of the element

class PreconditionFailedApiError(ApiError):
177class PreconditionFailedApiError(ApiError):
178    """
179    Error if the precondition of the operation was not met:
180    - When a way has nodes that do not exist or are not visible
181    - When a relation has elements that do not exist or are not visible
182    - When a node/way/relation is still used in a way/relation
183    """

Error if the precondition of the operation was not met:

  • When a way has nodes that do not exist or are not visible
  • When a relation has elements that do not exist or are not visible
  • When a node/way/relation is still used in a way/relation
class TimeoutApiError(ApiError):
186class TimeoutApiError(ApiError):
187    """
188    Error if the http request ran into a timeout
189    """

Error if the http request ran into a timeout

class ConnectionApiError(ApiError):
192class ConnectionApiError(ApiError):
193    """
194    Error if there was a network error (e.g. DNS failure, refused connection)
195    while connecting to the remote server.
196    """

Error if there was a network error (e.g. DNS failure, refused connection) while connecting to the remote server.

DEPRECATED_ERROR_NAMES = {'UsernamePasswordMissingError': 'AuthenticationMissingError'}

Error names that were renamed, mapped to their replacement.

The old names still resolve to the new class (so except clauses keep working), but emit a DeprecationWarning. They will be removed in version 7.0.

def resolve_deprecated_name(name: str) -> Any:
211def resolve_deprecated_name(name: str) -> Any:
212    """
213    Return the error class a deprecated name refers to, with a warning.
214
215    Raises `AttributeError` if `name` is not a deprecated error name. The
216    `DeprecationWarning` is reported against the caller of the module-level
217    `__getattr__` that calls this, i.e. the code using the old name.
218    """
219    new_name = DEPRECATED_ERROR_NAMES.get(name)
220    if not new_name:
221        raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
222    warnings.warn(
223        f"{name} has been renamed to {new_name}, "
224        f"the old name is deprecated and will be removed in osmapi 7.0",
225        DeprecationWarning,
226        stacklevel=3,
227    )
228    return globals()[new_name]

Return the error class a deprecated name refers to, with a warning.

Raises AttributeError if name is not a deprecated error name. The DeprecationWarning is reported against the caller of the module-level __getattr__ that calls this, i.e. the code using the old name.