staplus-client 0.3.4__tar.gz → 0.4.2__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 (66) hide show
  1. {staplus_client-0.3.4/staplus_client.egg-info → staplus_client-0.4.2}/PKG-INFO +18 -3
  2. {staplus_client-0.3.4 → staplus_client-0.4.2}/README.md +16 -1
  3. staplus_client-0.4.2/setup.cfg +11 -0
  4. {staplus_client-0.3.4 → staplus_client-0.4.2}/setup.py +2 -1
  5. {staplus_client-0.3.4 → staplus_client-0.4.2}/staplus_client/__init__.py +9 -1
  6. {staplus_client-0.3.4 → staplus_client-0.4.2}/staplus_client/__version__.py +1 -1
  7. {staplus_client-0.3.4 → staplus_client-0.4.2}/staplus_client/model/campaign.py +7 -11
  8. {staplus_client-0.3.4 → staplus_client-0.4.2}/staplus_client/model/datastream.py +13 -8
  9. {staplus_client-0.3.4 → staplus_client-0.4.2}/staplus_client/model/entity.py +5 -0
  10. staplus_client-0.4.2/staplus_client/model/ext/cell_attribute.py +33 -0
  11. staplus_client-0.4.2/staplus_client/model/ext/datatypes.py +86 -0
  12. {staplus_client-0.3.4 → staplus_client-0.4.2}/staplus_client/model/ext/entity_list.py +8 -2
  13. {staplus_client-0.3.4 → staplus_client-0.4.2}/staplus_client/model/ext/entity_type.py +18 -8
  14. staplus_client-0.4.2/staplus_client/model/feature_of_interest.py +40 -0
  15. {staplus_client-0.3.4 → staplus_client-0.4.2}/staplus_client/model/license.py +5 -7
  16. staplus_client-0.4.2/staplus_client/model/location.py +45 -0
  17. {staplus_client-0.3.4 → staplus_client-0.4.2}/staplus_client/model/multi_datastream.py +9 -5
  18. {staplus_client-0.3.4 → staplus_client-0.4.2}/staplus_client/model/observation.py +11 -4
  19. {staplus_client-0.3.4 → staplus_client-0.4.2}/staplus_client/model/observation_group.py +1 -1
  20. {staplus_client-0.3.4 → staplus_client-0.4.2}/staplus_client/model/party.py +18 -3
  21. {staplus_client-0.3.4 → staplus_client-0.4.2}/staplus_client/model/relation.py +25 -20
  22. {staplus_client-0.3.4 → staplus_client-0.4.2}/staplus_client/model/thing.py +5 -0
  23. {staplus_client-0.3.4 → staplus_client-0.4.2}/staplus_client/service/staplusservice.py +6 -5
  24. {staplus_client-0.3.4 → staplus_client-0.4.2/staplus_client.egg-info}/PKG-INFO +18 -3
  25. {staplus_client-0.3.4 → staplus_client-0.4.2}/staplus_client.egg-info/SOURCES.txt +7 -1
  26. {staplus_client-0.3.4 → staplus_client-0.4.2}/staplus_client.egg-info/requires.txt +1 -1
  27. {staplus_client-0.3.4 → staplus_client-0.4.2}/staplus_client.egg-info/top_level.txt +1 -0
  28. staplus_client-0.4.2/tests/__init__.py +0 -0
  29. staplus_client-0.4.2/tests/test_sta.py +186 -0
  30. staplus_client-0.4.2/tests/test_staplus.py +493 -0
  31. staplus_client-0.3.4/setup.cfg +0 -4
  32. staplus_client-0.3.4/staplus_client/model/feature_of_interest.py +0 -17
  33. staplus_client-0.3.4/staplus_client/model/location.py +0 -22
  34. {staplus_client-0.3.4 → staplus_client-0.4.2}/LICENSE +0 -0
  35. {staplus_client-0.3.4 → staplus_client-0.4.2}/staplus_client/dao/__init__.py +0 -0
  36. {staplus_client-0.3.4 → staplus_client-0.4.2}/staplus_client/dao/actuator.py +0 -0
  37. {staplus_client-0.3.4 → staplus_client-0.4.2}/staplus_client/dao/base.py +0 -0
  38. {staplus_client-0.3.4 → staplus_client-0.4.2}/staplus_client/dao/campaign.py +0 -0
  39. {staplus_client-0.3.4 → staplus_client-0.4.2}/staplus_client/dao/datastream.py +0 -0
  40. {staplus_client-0.3.4 → staplus_client-0.4.2}/staplus_client/dao/features_of_interest.py +0 -0
  41. {staplus_client-0.3.4 → staplus_client-0.4.2}/staplus_client/dao/historical_location.py +0 -0
  42. {staplus_client-0.3.4 → staplus_client-0.4.2}/staplus_client/dao/license.py +0 -0
  43. {staplus_client-0.3.4 → staplus_client-0.4.2}/staplus_client/dao/location.py +0 -0
  44. {staplus_client-0.3.4 → staplus_client-0.4.2}/staplus_client/dao/multi_datastream.py +0 -0
  45. {staplus_client-0.3.4 → staplus_client-0.4.2}/staplus_client/dao/observation.py +0 -0
  46. {staplus_client-0.3.4 → staplus_client-0.4.2}/staplus_client/dao/observation_group.py +0 -0
  47. {staplus_client-0.3.4 → staplus_client-0.4.2}/staplus_client/dao/observedproperty.py +0 -0
  48. {staplus_client-0.3.4 → staplus_client-0.4.2}/staplus_client/dao/party.py +0 -0
  49. {staplus_client-0.3.4 → staplus_client-0.4.2}/staplus_client/dao/relation.py +0 -0
  50. {staplus_client-0.3.4 → staplus_client-0.4.2}/staplus_client/dao/sensor.py +0 -0
  51. {staplus_client-0.3.4 → staplus_client-0.4.2}/staplus_client/dao/task.py +0 -0
  52. {staplus_client-0.3.4 → staplus_client-0.4.2}/staplus_client/dao/tasking_capability.py +0 -0
  53. {staplus_client-0.3.4 → staplus_client-0.4.2}/staplus_client/dao/thing.py +0 -0
  54. {staplus_client-0.3.4 → staplus_client-0.4.2}/staplus_client/model/__init__.py +0 -0
  55. {staplus_client-0.3.4 → staplus_client-0.4.2}/staplus_client/model/ext/__init__.py +0 -0
  56. {staplus_client-0.3.4 → staplus_client-0.4.2}/staplus_client/model/ext/unitofmeasurement.py +0 -0
  57. {staplus_client-0.3.4 → staplus_client-0.4.2}/staplus_client/model/historical_location.py +0 -0
  58. {staplus_client-0.3.4 → staplus_client-0.4.2}/staplus_client/model/observedproperty.py +0 -0
  59. {staplus_client-0.3.4 → staplus_client-0.4.2}/staplus_client/model/sensor.py +0 -0
  60. {staplus_client-0.3.4 → staplus_client-0.4.2}/staplus_client/query/__init__.py +0 -0
  61. {staplus_client-0.3.4 → staplus_client-0.4.2}/staplus_client/query/query.py +0 -0
  62. {staplus_client-0.3.4 → staplus_client-0.4.2}/staplus_client/service/BearerAuth.py +0 -0
  63. {staplus_client-0.3.4 → staplus_client-0.4.2}/staplus_client/service/__init__.py +0 -0
  64. {staplus_client-0.3.4 → staplus_client-0.4.2}/staplus_client/service/auth_handler.py +0 -0
  65. {staplus_client-0.3.4 → staplus_client-0.4.2}/staplus_client/utils.py +0 -0
  66. {staplus_client-0.3.4 → staplus_client-0.4.2}/staplus_client.egg-info/dependency_links.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: staplus-client
3
- Version: 0.3.4
3
+ Version: 0.4.2
4
4
  Summary: A client library to facilitate interaction with a STAplus Server
5
5
  Home-page: https://github.com/securedimensions/STAplus-Python-Client
6
6
  Author: Andreas Matheus
@@ -17,7 +17,7 @@ Requires-Dist: requests>=2.26.0
17
17
  Requires-Dist: jsonpatch
18
18
  Requires-Dist: python-dateutil
19
19
  Requires-Dist: pillow
20
- Requires-Dist: sd-frost-sta-client
20
+ Requires-Dist: sd-frost-sta-client>=1.1.47
21
21
  Dynamic: author
22
22
  Dynamic: author-email
23
23
  Dynamic: description
@@ -31,7 +31,7 @@ Dynamic: summary
31
31
 
32
32
  # OGC STAplus (SensorThings) API Python Client
33
33
 
34
- This STAplus Python Client implements the [STAplus](https://docs.ogc.org/is/22-022r1/22-022r1.html) API. The aim is to simplify development of STAplus and SensorThings enabled client applications.
34
+ This STAplus Python Client implements the STAplus 1.0 Corrigendum (not published yet) on [STAplus](https://docs.ogc.org/is/22-022r1/22-022r1.html) API. The aim is to simplify development of STAplus and SensorThings enabled client applications.
35
35
 
36
36
  **_NOTE:_** This implementation is an extension to the [FROST API Python Client](https://github.com/FraunhoferIOSB/FROST-Python-Client). To have better control over bug fixing and improvements, this implementation is actually based on a fork of the FROST API Python Client, hosted on a Secure Dimensions Github repository: [STA Python Client](https://github.com/securedimensions/STA-Python-Client).
37
37
 
@@ -51,6 +51,21 @@ This library requires Python 3.11 or better to support parsing ISO datetime valu
51
51
  ## Installation via PyPi
52
52
  Installation via pip: `pip install staplus-client`
53
53
 
54
+ ## Combined with STA-DGGS
55
+
56
+ The STAplus `Cell` entity lives in the [STA-DGGS Python Client](https://github.com/securedimensions/STA-DGGS-Python-Client). A client that needs both STAplus (Party, License, Campaign, …) and Cell uses `compose()`. That returns one service whose Datastream, MultiDatastream, and Observation classes carry both models. Install `sta-dggs-client` in addition to `staplus-client`. `compose()` accepts the same constructor arguments as STA-Python-Client (`url`, `auth_handler`, `proxies`).
57
+
58
+ ```python
59
+ import staplus_client as plus
60
+ import sta_dggs_client as dggs
61
+ service = dggs.compose(url, auth_handler=auth)
62
+ ds = plus.Datastream(name='Temperature', description='temp',
63
+ party=party, cell=cell)
64
+ mds = plus.MultiDatastream(name='Air quality', description='multi',
65
+ party=party, cell=cell)
66
+ obs = plus.Observation(result=21.5, cell=cell)
67
+ ```
68
+
54
69
  ## Limitations
55
70
  This implementation has the following limitations for the Entity `Location` and `FeatureOfInterest`:
56
71
 
@@ -1,6 +1,6 @@
1
1
  # OGC STAplus (SensorThings) API Python Client
2
2
 
3
- This STAplus Python Client implements the [STAplus](https://docs.ogc.org/is/22-022r1/22-022r1.html) API. The aim is to simplify development of STAplus and SensorThings enabled client applications.
3
+ This STAplus Python Client implements the STAplus 1.0 Corrigendum (not published yet) on [STAplus](https://docs.ogc.org/is/22-022r1/22-022r1.html) API. The aim is to simplify development of STAplus and SensorThings enabled client applications.
4
4
 
5
5
  **_NOTE:_** This implementation is an extension to the [FROST API Python Client](https://github.com/FraunhoferIOSB/FROST-Python-Client). To have better control over bug fixing and improvements, this implementation is actually based on a fork of the FROST API Python Client, hosted on a Secure Dimensions Github repository: [STA Python Client](https://github.com/securedimensions/STA-Python-Client).
6
6
 
@@ -20,6 +20,21 @@ This library requires Python 3.11 or better to support parsing ISO datetime valu
20
20
  ## Installation via PyPi
21
21
  Installation via pip: `pip install staplus-client`
22
22
 
23
+ ## Combined with STA-DGGS
24
+
25
+ The STAplus `Cell` entity lives in the [STA-DGGS Python Client](https://github.com/securedimensions/STA-DGGS-Python-Client). A client that needs both STAplus (Party, License, Campaign, …) and Cell uses `compose()`. That returns one service whose Datastream, MultiDatastream, and Observation classes carry both models. Install `sta-dggs-client` in addition to `staplus-client`. `compose()` accepts the same constructor arguments as STA-Python-Client (`url`, `auth_handler`, `proxies`).
26
+
27
+ ```python
28
+ import staplus_client as plus
29
+ import sta_dggs_client as dggs
30
+ service = dggs.compose(url, auth_handler=auth)
31
+ ds = plus.Datastream(name='Temperature', description='temp',
32
+ party=party, cell=cell)
33
+ mds = plus.MultiDatastream(name='Air quality', description='multi',
34
+ party=party, cell=cell)
35
+ obs = plus.Observation(result=21.5, cell=cell)
36
+ ```
37
+
23
38
  ## Limitations
24
39
  This implementation has the following limitations for the Entity `Location` and `FeatureOfInterest`:
25
40
 
@@ -0,0 +1,11 @@
1
+ [project]
2
+ name = "staplus_client"
3
+ dynamic = ["version"]
4
+
5
+ [tool.setuptools.dynamic]
6
+ version = {attr = "package.__version__"}
7
+
8
+ [egg_info]
9
+ tag_build =
10
+ tag_date = 0
11
+
@@ -22,6 +22,7 @@ setup(
22
22
  packages=find_packages(exclude="tests"),
23
23
  #data_files=[('.',['__version__.py'])],
24
24
  install_requires=['demjson3>=3.0.5', 'furl>=2.1.3', 'geojson>=2.5.0', 'jsonpickle>=2.0.0',
25
- 'requests>=2.26.0', 'jsonpatch', 'python-dateutil', 'pillow', 'sd-frost-sta-client'],
25
+ 'requests>=2.26.0', 'jsonpatch', 'python-dateutil', 'pillow',
26
+ 'sd-frost-sta-client>=1.1.47'],
26
27
  keywords=['ogc', 'staplus', 'sensorthingsapi', 'IoT']
27
28
  )
@@ -18,7 +18,14 @@ from staplus_client import dao
18
18
  from staplus_client import service
19
19
  from staplus_client import query
20
20
 
21
- from staplus_client.model.party import Party
21
+ from staplus_client.model.party import Party, PARTY_ROLE_CODES, PARTY_ROLE_INDIVIDUAL, PARTY_ROLE_INSTITUTIONAL, PARTY_ROLE_SYSTEM
22
+ from staplus_client.model.ext.datatypes import (
23
+ ENCODING_TYPES,
24
+ ENCODING_TYPE_GEOJSON,
25
+ ENCODING_TYPE_GEOJSON_VND,
26
+ ENCODING_TYPE_JSON_FG,
27
+ ENCODING_TYPE_WKT,
28
+ )
22
29
  from staplus_client.model.license import License
23
30
  from staplus_client.model.thing import Thing
24
31
  from staplus_client.model.datastream import Datastream
@@ -36,6 +43,7 @@ from staplus_client.model.ext.entity_type import EntityTypes
36
43
  from staplus_client.model.ext.unitofmeasurement import UnitOfMeasurement
37
44
  from staplus_client.service.staplusservice import STAplusService
38
45
 
46
+
39
47
  import jsonpickle
40
48
  import demjson3
41
49
 
@@ -1,5 +1,5 @@
1
1
  __title__ = 'staplus-client'
2
- __version__ = '0.3.4'
2
+ __version__ = '0.4.2'
3
3
  __license__ = 'LGPL3'
4
4
  __author__ = 'Andreas Matheus'
5
5
  __copyright__ = 'Secure Dimensions'
@@ -16,6 +16,7 @@ from datetime import datetime
16
16
 
17
17
  from frost_sta_client.utils import check_datetime
18
18
  from staplus_client.utils import transform_json_to_entity_list
19
+ from staplus_client.model.ext.datatypes import check_url
19
20
  from staplus_client.model import entity, datastream, multi_datastream, license, observation_group, party
20
21
  from staplus_client.model.ext import entity_list, entity_type
21
22
  from staplus_client.dao.campaign import CampaignDao
@@ -60,9 +61,9 @@ class Campaign(entity.Entity):
60
61
  def __new__(cls, *args, **kwargs):
61
62
  new_campaign = super().__new__(cls)
62
63
  attributes = {'_id': None, '_name': '', '_description': '', '_classification': None, '_terms_of_use': '',
63
- '_privacy_statement': None, '_creation_time': '', '_start_time': None, '_end_start': None,
64
+ '_privacy_policy': None, '_creation_time': '', '_start_time': None, '_end_time': None,
64
65
  '_url': None, '_properties': None,
65
- '_datastreams': None, '_multi_datastreams': None, 'observation_groups': None,
66
+ '_datastreams': None, '_multi_datastreams': None, '_observation_groups': None,
66
67
  '_party': None, '_license': None,
67
68
  '_self_link': '', '_service': None}
68
69
  for key, value in attributes.items():
@@ -173,12 +174,7 @@ class Campaign(entity.Entity):
173
174
 
174
175
  @url.setter
175
176
  def url(self, value):
176
- if value is None:
177
- self._url = None
178
- return
179
- if not isinstance(value, str):
180
- raise ValueError('url should be of type str!')
181
- self._url = value
177
+ self._url = check_url(value, 'url')
182
178
 
183
179
  @property
184
180
  def properties(self):
@@ -249,7 +245,7 @@ class Campaign(entity.Entity):
249
245
  self._observation_groups = None
250
246
  return
251
247
  if isinstance(values, list) and all(isinstance(gs, observation_group.ObservationGroup) for gs in values):
252
- entity_class = entity_type.EntityTypes['ObservationGroups']['class']
248
+ entity_class = entity_type.EntityTypes['ObservationGroup']['class']
253
249
  self._observation_groups = entity_list.EntityList(entity_class=entity_class, entities=values)
254
250
  return
255
251
  if not isinstance(values, entity_list.EntityList) or \
@@ -289,7 +285,7 @@ class Campaign(entity.Entity):
289
285
  return
290
286
  raise ValueError('license should be of type License!')
291
287
 
292
- def get_licence(self):
288
+ def get_license(self):
293
289
  result = self.service.license()
294
290
  result.parent = self
295
291
  return result
@@ -358,7 +354,7 @@ class Campaign(entity.Entity):
358
354
  data['endTime'] = datetime.fromisoformat(self.end_time)
359
355
  if self.url is not None and self.url != '':
360
356
  data['url'] = self.url
361
- if self.properties is not None and self.terms_of_use != {}:
357
+ if self.properties is not None and self.properties != {}:
362
358
  data['properties'] = self.properties
363
359
  if self._datastreams is not None and len(self.datastreams.entities) > 0:
364
360
  data['Datastreams'] = self.datastreams.__getstate__()
@@ -16,11 +16,12 @@
16
16
  from frost_sta_client.model import datastream
17
17
  from staplus_client import utils
18
18
  from staplus_client.model.ext import entity_list, entity_type
19
+ from staplus_client.model.ext.cell_attribute import CellAttribute
19
20
  from staplus_client.dao.datastream import DatastreamDao
20
21
  from staplus_client.model import license, party, campaign
21
22
 
22
23
 
23
- class Datastream(datastream.Datastream):
24
+ class Datastream(CellAttribute, datastream.Datastream):
24
25
 
25
26
  def __init__(self,
26
27
  name='',
@@ -38,6 +39,7 @@ class Datastream(datastream.Datastream):
38
39
  campaigns=None,
39
40
  party=None,
40
41
  license=None,
42
+ cell=None,
41
43
  **kwargs):
42
44
  super().__init__(name, description, observation_type, unit_of_measurement,
43
45
  observed_area, properties, phenomenon_time, result_time, thing,
@@ -45,12 +47,13 @@ class Datastream(datastream.Datastream):
45
47
  self.campaigns = campaigns
46
48
  self.party = party
47
49
  self.license = license
50
+ self.cell = cell
48
51
 
49
52
  def __new__(cls, *args, **kwargs):
50
- new_datastream = super().__new__(cls, args, kwargs)
51
- attributes = dict(_campaigns=None, _party=None, _license=None, _self_link='', _service=None)
53
+ new_datastream = super().__new__(cls, *args, **kwargs)
54
+ attributes = dict(_campaigns=None, _party=None, _license=None, _cell=None, _self_link='', _service=None)
52
55
  for key, value in attributes.items():
53
- new_datastream.__dict__[key] = value
56
+ new_datastream.__dict__.setdefault(key, value)
54
57
  return new_datastream
55
58
 
56
59
  @property
@@ -99,7 +102,7 @@ class Datastream(datastream.Datastream):
99
102
  result = self.service.observed_property()
100
103
  result.parent = self
101
104
  return result
102
-
105
+
103
106
  @property
104
107
  def campaigns(self):
105
108
  return self._campaigns
@@ -109,7 +112,7 @@ class Datastream(datastream.Datastream):
109
112
  if values is None:
110
113
  self._campaigns = None
111
114
  return
112
- if isinstance(values, list) and all(isinstance(ps, campaign.Campaign) for ps in values):
115
+ if isinstance(values, list) and all(isinstance(cs, campaign.Campaign) for cs in values):
113
116
  entity_class = entity_type.EntityTypes['Campaign']['class']
114
117
  self._campaigns = entity_list.EntityList(entity_class=entity_class, entities=values)
115
118
  return
@@ -131,12 +134,14 @@ class Datastream(datastream.Datastream):
131
134
  self.license.set_service(service)
132
135
  if self.campaigns is not None:
133
136
  self.campaigns.set_service(service)
137
+ if self.cell is not None and hasattr(self.cell, 'set_service'):
138
+ self.cell.set_service(service)
134
139
 
135
140
  def __getstate__(self):
136
141
  data = super().__getstate__()
137
- if self.party is not None:
142
+ if self._party is not None:
138
143
  data['Party'] = self.party
139
- if self.license is not None:
144
+ if self._license is not None:
140
145
  data['License'] = self.license
141
146
  if self._campaigns is not None and len(self.campaigns.entities) > 0:
142
147
  data['Campaigns'] = self.campaigns.__getstate__()
@@ -17,6 +17,11 @@ from frost_sta_client.model import entity
17
17
  from staplus_client.service.staplusservice import STAplusService
18
18
 
19
19
  class Entity(entity.Entity):
20
+ @property
21
+ def service(self):
22
+ return self._service
23
+
24
+ @service.setter
20
25
  def service(self, value):
21
26
  if value is None or isinstance(value, STAplusService):
22
27
  self._service = value
@@ -0,0 +1,33 @@
1
+ # Copyright (C) 2026 Secure Dimensions GmbH, Munich, Germany.
2
+ #
3
+ # This program is free software: you can redistribute it and/or modify
4
+ # it under the terms of the GNU Lesser General Public License as published by
5
+ # the Free Software Foundation, either version 3 of the License, or
6
+ # (at your option) any later version.
7
+ #
8
+ # This program is distributed in the hope that it will be useful,
9
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
10
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
+ # GNU Lesser General Public License for more details.
12
+ #
13
+ # You should have received a copy of the GNU Lesser General Public License
14
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
15
+
16
+ from typing import Any
17
+
18
+
19
+ class CellAttribute:
20
+ """
21
+ Optional Cell navigation for type checkers and for sta_dggs_client.compose().
22
+
23
+ Cell JSON round-trip is provided by STA-DGGS when the clients are composed.
24
+ The value is typically a sta_dggs_client.model.cell.Cell instance.
25
+ """
26
+
27
+ @property
28
+ def cell(self) -> Any:
29
+ return getattr(self, '_cell', None)
30
+
31
+ @cell.setter
32
+ def cell(self, value: Any) -> None:
33
+ self._cell = value
@@ -0,0 +1,86 @@
1
+ # Copyright (C) 2023-2024 Secure Dimensions GmbH, Munich, Germany.
2
+ #
3
+ # This program is free software: you can redistribute it and/or modify
4
+ # it under the terms of the GNU Lesser General Public License as published by
5
+ # the Free Software Foundation, either version 3 of the License, or
6
+ # (at your option) any later version.
7
+ #
8
+ # This program is distributed in the hope that it will be useful,
9
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
10
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
+ # GNU Lesser General Public License for more details.
12
+ #
13
+ # You should have received a copy of the GNU Lesser General Public License
14
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
15
+
16
+ import re
17
+ from urllib.parse import urlparse
18
+
19
+ # STAplus 1.0 clause 13 media types for FeatureOfInterest and Location.encodingType.
20
+ # application/vnd.geo+json is the SensorThings 1.0 Table 8-6 ValueCode (STAplus extends STA 1.0).
21
+ ENCODING_TYPE_GEOJSON = 'application/geo+json'
22
+ ENCODING_TYPE_GEOJSON_VND = 'application/vnd.geo+json'
23
+ ENCODING_TYPE_JSON_FG = 'application/vnd.ogc.fg+json'
24
+ ENCODING_TYPE_WKT = 'application/wkt'
25
+ ENCODING_TYPES = (
26
+ ENCODING_TYPE_GEOJSON,
27
+ ENCODING_TYPE_GEOJSON_VND,
28
+ ENCODING_TYPE_JSON_FG,
29
+ ENCODING_TYPE_WKT,
30
+ )
31
+
32
+ _URI_SCHEME = re.compile(r'^[A-Za-z][A-Za-z0-9+.-]*$')
33
+ _URL_SCHEMES = ('http', 'https')
34
+
35
+
36
+ def is_uri(value):
37
+ if not isinstance(value, str):
38
+ return False
39
+ value = value.strip()
40
+ if not value or any(ch.isspace() for ch in value):
41
+ return False
42
+ parsed = urlparse(value)
43
+ if not parsed.scheme or not _URI_SCHEME.fullmatch(parsed.scheme):
44
+ return False
45
+ if parsed.scheme.lower() in _URL_SCHEMES or parsed.scheme.lower() in ('ftp', 'ftps'):
46
+ return bool(parsed.netloc)
47
+ return bool(parsed.netloc or parsed.path)
48
+
49
+
50
+ def is_url(value):
51
+ if not is_uri(value):
52
+ return False
53
+ parsed = urlparse(value)
54
+ return parsed.scheme.lower() in _URL_SCHEMES and bool(parsed.netloc)
55
+
56
+
57
+ def check_encoding_type(value, field_name='encodingType'):
58
+ if value is None or value == '':
59
+ return None
60
+ if not isinstance(value, str):
61
+ raise ValueError(f'{field_name} should be of type str!')
62
+ if value not in ENCODING_TYPES:
63
+ raise ValueError(
64
+ f'{field_name} should be one of: {", ".join(ENCODING_TYPES)}'
65
+ )
66
+ return value
67
+
68
+
69
+ def check_uri(value, field_name='URI'):
70
+ if value is None or value == '':
71
+ return None
72
+ if not isinstance(value, str):
73
+ raise ValueError(f'{field_name} should be of type str!')
74
+ if not is_uri(value):
75
+ raise ValueError(f'{field_name} should be a URI!')
76
+ return value
77
+
78
+
79
+ def check_url(value, field_name='URL'):
80
+ if value is None or value == '':
81
+ return None
82
+ if not isinstance(value, str):
83
+ raise ValueError(f'{field_name} should be of type str!')
84
+ if not is_url(value):
85
+ raise ValueError(f'{field_name} should be a URL!')
86
+ return value
@@ -16,8 +16,9 @@
16
16
  import logging
17
17
  import requests
18
18
 
19
+ import staplus_client
19
20
  from staplus_client.service.staplusservice import STAplusService
20
- from staplus_client.model.entity import Entity
21
+ from frost_sta_client.model.entity import Entity as FrostEntity
21
22
  from frost_sta_client.model.ext import entity_list
22
23
 
23
24
  class EntityList(entity_list.EntityList):
@@ -68,8 +69,13 @@ class EntityList(entity_list.EntityList):
68
69
  for entity in self.entities:
69
70
  entity.set_service(service)
70
71
 
72
+ @property
73
+ def entities(self):
74
+ return self._entities
75
+
76
+ @entities.setter
71
77
  def entities(self, values):
72
- if isinstance(values, list) and all(isinstance(v, Entity) for v in values):
78
+ if isinstance(values, list) and all(isinstance(v, FrostEntity) for v in values):
73
79
  self._entities = values
74
80
  return
75
81
  raise ValueError('entities should be a list of STAplus entities')
@@ -18,13 +18,13 @@ EntityTypes = {
18
18
  'singular': 'Datastream',
19
19
  'plural': 'Datastreams',
20
20
  'class': 'staplus_client.model.datastream.Datastream',
21
- 'relations_list': ['Sensor', 'Thing', 'ObservedProperty', 'Observations', 'Party', 'License', 'Campaign']
21
+ 'relations_list': ['Sensor', 'Thing', 'ObservedProperty', 'Observations', 'Party', 'License', 'Campaigns']
22
22
  },
23
23
  'MultiDatastream': {
24
24
  'singular': 'MultiDatastream',
25
25
  'plural': 'MultiDatastreams',
26
26
  'class': 'staplus_client.model.multi_datastream.MultiDatastream',
27
- 'relations_list': ['Sensor', 'Thing', 'ObservedProperties', 'Observations', 'Party', 'License', 'Campaign']
27
+ 'relations_list': ['Sensor', 'Thing', 'ObservedProperties', 'Observations', 'Party', 'License', 'Campaigns']
28
28
  },
29
29
  'FeatureOfInterest': {
30
30
  'singular': 'FeatureOfInterest',
@@ -54,7 +54,7 @@ EntityTypes = {
54
54
  'singular': 'Observation',
55
55
  'plural': 'Observations',
56
56
  'class': 'staplus_client.model.observation.Observation',
57
- 'relations_list': ['FeatureOfInterest', 'Datastream', 'MultiDatastream', 'Subjects', 'Objects']
57
+ 'relations_list': ['FeatureOfInterest', 'Datastream', 'MultiDatastream', 'Subjects', 'Objects', 'ObservationGroups']
58
58
  },
59
59
  'Thing': {
60
60
  'singular': 'Thing',
@@ -129,14 +129,24 @@ EntityTypes = {
129
129
  'plural': 'Relations',
130
130
  'class': 'staplus_client.model.relation.Relation',
131
131
  'relations_list': ['Subject', 'Object', 'ObservationGroups']
132
- }
132
+ },
133
133
  }
134
134
 
135
135
  list_for_class = {}
136
- for key, entity_type in EntityTypes.items():
137
- list_for_class[entity_type["class"]] = entity_type["plural"]
136
+
137
+
138
+ def rebuild_class_index():
139
+ list_for_class.clear()
140
+ for entity_type in EntityTypes.values():
141
+ list_for_class[entity_type["class"]] = entity_type["plural"]
142
+
143
+
144
+ rebuild_class_index()
138
145
 
139
146
 
140
147
  def get_list_for_class(clazz):
141
- clazz_name = clazz.__module__ + "." + clazz.__name__
142
- return list_for_class[clazz_name]
148
+ for base in clazz.__mro__:
149
+ clazz_name = base.__module__ + "." + base.__name__
150
+ if clazz_name in list_for_class:
151
+ return list_for_class[clazz_name]
152
+ raise KeyError(clazz.__module__ + "." + clazz.__name__)
@@ -0,0 +1,40 @@
1
+ from frost_sta_client.model import feature_of_interest
2
+ from staplus_client.dao.features_of_interest import FeaturesOfInterestDao
3
+ from staplus_client.model.ext.datatypes import (
4
+ ENCODING_TYPE_GEOJSON,
5
+ ENCODING_TYPE_GEOJSON_VND,
6
+ ENCODING_TYPE_JSON_FG,
7
+ ENCODING_TYPE_WKT,
8
+ ENCODING_TYPES,
9
+ check_encoding_type,
10
+ )
11
+
12
+ class FeatureOfInterest(feature_of_interest.FeatureOfInterest):
13
+
14
+ ENCODING_TYPE_GEOJSON = ENCODING_TYPE_GEOJSON
15
+ ENCODING_TYPE_GEOJSON_VND = ENCODING_TYPE_GEOJSON_VND
16
+ ENCODING_TYPE_JSON_FG = ENCODING_TYPE_JSON_FG
17
+ ENCODING_TYPE_WKT = ENCODING_TYPE_WKT
18
+ ENCODING_TYPES = ENCODING_TYPES
19
+
20
+ @property
21
+ def encoding_type(self):
22
+ return self._encoding_type
23
+
24
+ @encoding_type.setter
25
+ def encoding_type(self, value):
26
+ self._encoding_type = check_encoding_type(value)
27
+
28
+
29
+ def clone(self):
30
+ entity = self.__class__()
31
+ entity.id = self.id
32
+ return entity
33
+
34
+ def get_observations(self):
35
+ result = self.service.observations()
36
+ result.parent = self
37
+ return result
38
+
39
+ def get_dao(self, service):
40
+ return FeaturesOfInterestDao(service)
@@ -15,6 +15,7 @@
15
15
 
16
16
  from staplus_client import utils
17
17
  from staplus_client.model import entity, campaign, datastream, multi_datastream, observation_group
18
+ from staplus_client.model.ext.datatypes import check_uri
18
19
  from staplus_client.model.ext import entity_list, entity_type
19
20
  from staplus_client.dao.license import LicenseDao
20
21
 
@@ -86,12 +87,7 @@ class License(entity.Entity):
86
87
 
87
88
  @definition.setter
88
89
  def definition(self, value):
89
- if value is None:
90
- self._definition = None
91
- return
92
- if not isinstance(value, str):
93
- raise ValueError('definition should be of type str!')
94
- self._definition = value
90
+ self._definition = check_uri(value, 'definition')
95
91
 
96
92
  @property
97
93
  def logo(self):
@@ -100,7 +96,7 @@ class License(entity.Entity):
100
96
  @logo.setter
101
97
  def logo(self, value):
102
98
  if value is None:
103
- self._value = None
99
+ self._logo = None
104
100
  return
105
101
  if not isinstance(value, str):
106
102
  raise ValueError('logo should be of type str!')
@@ -245,6 +241,8 @@ class License(entity.Entity):
245
241
 
246
242
  def __getstate__(self):
247
243
  data = super().__getstate__()
244
+ if self.name is not None and self.name != '':
245
+ data['name'] = self.name
248
246
  if self.description is not None and self.description != '':
249
247
  data['description'] = self.description
250
248
  if self.definition is not None and self.definition != '':
@@ -0,0 +1,45 @@
1
+ from frost_sta_client.model import location
2
+ from staplus_client.dao.location import LocationDao
3
+ from staplus_client.model.ext.datatypes import (
4
+ ENCODING_TYPE_GEOJSON,
5
+ ENCODING_TYPE_GEOJSON_VND,
6
+ ENCODING_TYPE_JSON_FG,
7
+ ENCODING_TYPE_WKT,
8
+ ENCODING_TYPES,
9
+ check_encoding_type,
10
+ )
11
+
12
+ class Location(location.Location):
13
+
14
+ ENCODING_TYPE_GEOJSON = ENCODING_TYPE_GEOJSON
15
+ ENCODING_TYPE_GEOJSON_VND = ENCODING_TYPE_GEOJSON_VND
16
+ ENCODING_TYPE_JSON_FG = ENCODING_TYPE_JSON_FG
17
+ ENCODING_TYPE_WKT = ENCODING_TYPE_WKT
18
+ ENCODING_TYPES = ENCODING_TYPES
19
+
20
+ @property
21
+ def encoding_type(self):
22
+ return self._encoding_type
23
+
24
+ @encoding_type.setter
25
+ def encoding_type(self, value):
26
+ self._encoding_type = check_encoding_type(value)
27
+
28
+
29
+ def clone(self):
30
+ entity = self.__class__()
31
+ entity.id = self.id
32
+ return entity
33
+
34
+ def get_things(self):
35
+ result = self.service.things()
36
+ result.parent = self
37
+ return result
38
+
39
+ def get_historical_locations(self):
40
+ result = self.service.historical_locations()
41
+ result.parent = self
42
+ return result
43
+
44
+ def get_dao(self, service):
45
+ return LocationDao(service)
@@ -16,10 +16,11 @@
16
16
  from frost_sta_client.model import multi_datastream
17
17
  from staplus_client import utils
18
18
  from staplus_client.model.ext import entity_list, entity_type
19
+ from staplus_client.model.ext.cell_attribute import CellAttribute
19
20
  from staplus_client.dao.multi_datastream import MultiDatastreamDao
20
21
  from staplus_client.model import license, campaign, party
21
22
 
22
- class MultiDatastream(multi_datastream.MultiDatastream):
23
+ class MultiDatastream(CellAttribute, multi_datastream.MultiDatastream):
23
24
  def __init__(self,
24
25
  name='',
25
26
  description='',
@@ -37,6 +38,7 @@ class MultiDatastream(multi_datastream.MultiDatastream):
37
38
  campaigns=None,
38
39
  party=None,
39
40
  license=None,
41
+ cell=None,
40
42
  **kwargs):
41
43
  super().__init__(name, description, properties, unit_of_measurements, observation_type,
42
44
  multi_observation_data_types, observed_area, phenomenon_time, result_time,
@@ -44,12 +46,13 @@ class MultiDatastream(multi_datastream.MultiDatastream):
44
46
  self.party = party
45
47
  self.license = license
46
48
  self.campaigns = campaigns
49
+ self.cell = cell
47
50
 
48
51
  def __new__(cls, *args, **kwargs):
49
- new_mds = super().__new__(cls, args, kwargs)
50
- attributes = dict( _campaigns=None, _party=None, _license=None, _self_link='', _service=None)
52
+ new_mds = super().__new__(cls, *args, **kwargs)
53
+ attributes = dict(_campaigns=None, _party=None, _license=None, _cell=None, _self_link='', _service=None)
51
54
  for key, value in attributes.items():
52
- new_mds.__dict__[key] = value
55
+ new_mds.__dict__.setdefault(key, value)
53
56
  return new_mds
54
57
 
55
58
  @property
@@ -125,6 +128,8 @@ class MultiDatastream(multi_datastream.MultiDatastream):
125
128
  self.license.set_service(service)
126
129
  if self.campaigns is not None:
127
130
  self.campaigns.set_service(service)
131
+ if self.cell is not None and hasattr(self.cell, 'set_service'):
132
+ self.cell.set_service(service)
128
133
 
129
134
  def __getstate__(self):
130
135
  data = super().__getstate__()
@@ -134,7 +139,6 @@ class MultiDatastream(multi_datastream.MultiDatastream):
134
139
  data['License'] = self.license
135
140
  if self._campaigns is not None and len(self.campaigns.entities) > 0:
136
141
  data['Campaigns'] = self.campaigns.__getstate__()
137
-
138
142
  return data
139
143
 
140
144
  def __setstate__(self, state):