|
3 | 3 | # Authors: Benjamin Webb <benjamin.miller.webb@gmail.com> |
4 | 4 | # Authors: Tom Kralidis <tomkralidis@gmail.com> |
5 | 5 | # |
6 | | -# Copyright (c) 2024 Benjamin Webb |
| 6 | +# Copyright (c) 2025 Benjamin Webb |
7 | 7 | # Copyright (c) 2022 Tom Kralidis |
8 | 8 | # |
9 | 9 | # Permission is hereby granted, free of charge, to any person |
|
32 | 32 | from json.decoder import JSONDecodeError |
33 | 33 | import logging |
34 | 34 | from requests import Session |
| 35 | +from requests.exceptions import ConnectionError |
35 | 36 | from urllib.parse import urlparse |
36 | 37 |
|
37 | 38 | from pygeoapi.config import get_config |
38 | 39 | from pygeoapi.provider.base import ( |
39 | | - BaseProvider, ProviderQueryError, ProviderConnectionError) |
| 40 | + BaseProvider, ProviderQueryError, ProviderConnectionError, |
| 41 | + ProviderInvalidDataError) |
40 | 42 | from pygeoapi.util import ( |
41 | | - url_join, get_provider_default, crs_transform, get_base_url) |
| 43 | + url_join, get_provider_default, crs_transform, get_base_url, |
| 44 | + get_typed_value) |
42 | 45 |
|
43 | 46 | LOGGER = logging.getLogger(__name__) |
44 | 47 |
|
@@ -101,12 +104,16 @@ def get_fields(self): |
101 | 104 | :returns: dict of fields |
102 | 105 | """ |
103 | 106 | if not self._fields: |
104 | | - r = self._get_response(self._url, {'$top': 1}) |
105 | 107 | try: |
| 108 | + r = self._get_response(self._url, {'$top': 1}) |
106 | 109 | results = r['value'][0] |
107 | 110 | except IndexError: |
108 | 111 | LOGGER.warning('could not get fields; returning empty set') |
109 | 112 | return {} |
| 113 | + except (ConnectionError, ProviderConnectionError): |
| 114 | + msg = f'Unable to contact SensorThings endpoint at {self._url}' |
| 115 | + LOGGER.error(msg) |
| 116 | + raise ProviderConnectionError(msg) |
110 | 117 |
|
111 | 118 | for (n, v) in results.items(): |
112 | 119 | if isinstance(v, (int, float)) or \ |
@@ -155,6 +162,65 @@ def get(self, identifier, **kwargs): |
155 | 162 | response = self._get_response(f'{self._url}({identifier})') |
156 | 163 | return self._make_feature(response) |
157 | 164 |
|
| 165 | + def create(self, item): |
| 166 | + """ |
| 167 | + Create a new item |
| 168 | +
|
| 169 | + :param item: `dict` of new item |
| 170 | +
|
| 171 | + :returns: identifier of created item |
| 172 | + """ |
| 173 | + response = self.http.post(self._url, json=item) |
| 174 | + |
| 175 | + if response.status_code == 201: |
| 176 | + location = response.headers.get("Location") |
| 177 | + iotid = location[location.find("(")+1:location.find(")")] |
| 178 | + |
| 179 | + LOGGER.debug(f'Feature created with @iot.id: {iotid}') |
| 180 | + return get_typed_value(iotid) |
| 181 | + else: |
| 182 | + msg = f"Failed to create item: {response.text}" |
| 183 | + raise ProviderInvalidDataError(msg) |
| 184 | + |
| 185 | + def update(self, identifier, item): |
| 186 | + """ |
| 187 | + Updates an existing item |
| 188 | +
|
| 189 | + :param identifier: feature id |
| 190 | + :param item: `dict` of partial or full item |
| 191 | +
|
| 192 | + :returns: `bool` of update result |
| 193 | + """ |
| 194 | + id = f"'{identifier}'" \ |
| 195 | + if isinstance(identifier, str) else str(identifier) |
| 196 | + LOGGER.debug(f'Updating @iot.id: {id}') |
| 197 | + response = self.http.put(f"{self._url}({id})", json=item) |
| 198 | + |
| 199 | + if response.status_code == 200: |
| 200 | + return True |
| 201 | + else: |
| 202 | + msg = f'Failed to update item: {response.text}' |
| 203 | + raise ProviderConnectionError(msg) |
| 204 | + |
| 205 | + def delete(self, identifier): |
| 206 | + """ |
| 207 | + Deletes an existing item |
| 208 | +
|
| 209 | + :param identifier: item id |
| 210 | +
|
| 211 | + :returns: `bool` of deletion result |
| 212 | + """ |
| 213 | + id = f"'{identifier}'" \ |
| 214 | + if isinstance(identifier, str) else str(identifier) |
| 215 | + LOGGER.debug(f'Deleting @iot.id: {id}') |
| 216 | + response = self.http.delete(f"{self._url}({id})") |
| 217 | + |
| 218 | + if response.status_code == 200: |
| 219 | + return True |
| 220 | + else: |
| 221 | + msg = f"Failed to delete item: {response.text}" |
| 222 | + raise ProviderConnectionError(msg) |
| 223 | + |
158 | 224 | def _load(self, offset=0, limit=10, resulttype='results', |
159 | 225 | bbox=[], datetime_=None, properties=[], sortby=[], |
160 | 226 | select_properties=[], skip_geometry=False, q=None): |
@@ -208,13 +274,13 @@ def _load(self, offset=0, limit=10, resulttype='results', |
208 | 274 | v = response.get('value') |
209 | 275 | while len(v) < limit: |
210 | 276 | try: |
211 | | - LOGGER.debug('Fetching next set of values') |
212 | | - next_ = response['@iot.nextLink'] |
213 | | - |
214 | 277 | # Ensure we only use provided network location |
215 | | - next_ = next_.replace(urlparse(next_).netloc, |
216 | | - urlparse(self.data).netloc) |
| 278 | + next_ = urlparse(response['@iot.nextLink'])._replace( |
| 279 | + scheme=self.parsed_url.scheme, |
| 280 | + netloc=self.parsed_url.netloc |
| 281 | + ).geturl() |
217 | 282 |
|
| 283 | + LOGGER.debug('Fetching next set of values') |
218 | 284 | response = self._get_response(next_) |
219 | 285 | v.extend(response['value']) |
220 | 286 | except (ProviderConnectionError, KeyError): |
@@ -517,6 +583,8 @@ def _generate_mappings(self, provider_def: dict): |
517 | 583 | self._url = self.data |
518 | 584 | self.data = self._url.rstrip(f'/{self.entity}') |
519 | 585 |
|
| 586 | + self.parsed_url = urlparse(self.data) |
| 587 | + |
520 | 588 | # Default id |
521 | 589 | if self.id_field: |
522 | 590 | LOGGER.debug(f'Using id field: {self.id_field}') |
|
0 commit comments