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.
- {staplus_client-0.3.4/staplus_client.egg-info → staplus_client-0.4.2}/PKG-INFO +18 -3
- {staplus_client-0.3.4 → staplus_client-0.4.2}/README.md +16 -1
- staplus_client-0.4.2/setup.cfg +11 -0
- {staplus_client-0.3.4 → staplus_client-0.4.2}/setup.py +2 -1
- {staplus_client-0.3.4 → staplus_client-0.4.2}/staplus_client/__init__.py +9 -1
- {staplus_client-0.3.4 → staplus_client-0.4.2}/staplus_client/__version__.py +1 -1
- {staplus_client-0.3.4 → staplus_client-0.4.2}/staplus_client/model/campaign.py +7 -11
- {staplus_client-0.3.4 → staplus_client-0.4.2}/staplus_client/model/datastream.py +13 -8
- {staplus_client-0.3.4 → staplus_client-0.4.2}/staplus_client/model/entity.py +5 -0
- staplus_client-0.4.2/staplus_client/model/ext/cell_attribute.py +33 -0
- staplus_client-0.4.2/staplus_client/model/ext/datatypes.py +86 -0
- {staplus_client-0.3.4 → staplus_client-0.4.2}/staplus_client/model/ext/entity_list.py +8 -2
- {staplus_client-0.3.4 → staplus_client-0.4.2}/staplus_client/model/ext/entity_type.py +18 -8
- staplus_client-0.4.2/staplus_client/model/feature_of_interest.py +40 -0
- {staplus_client-0.3.4 → staplus_client-0.4.2}/staplus_client/model/license.py +5 -7
- staplus_client-0.4.2/staplus_client/model/location.py +45 -0
- {staplus_client-0.3.4 → staplus_client-0.4.2}/staplus_client/model/multi_datastream.py +9 -5
- {staplus_client-0.3.4 → staplus_client-0.4.2}/staplus_client/model/observation.py +11 -4
- {staplus_client-0.3.4 → staplus_client-0.4.2}/staplus_client/model/observation_group.py +1 -1
- {staplus_client-0.3.4 → staplus_client-0.4.2}/staplus_client/model/party.py +18 -3
- {staplus_client-0.3.4 → staplus_client-0.4.2}/staplus_client/model/relation.py +25 -20
- {staplus_client-0.3.4 → staplus_client-0.4.2}/staplus_client/model/thing.py +5 -0
- {staplus_client-0.3.4 → staplus_client-0.4.2}/staplus_client/service/staplusservice.py +6 -5
- {staplus_client-0.3.4 → staplus_client-0.4.2/staplus_client.egg-info}/PKG-INFO +18 -3
- {staplus_client-0.3.4 → staplus_client-0.4.2}/staplus_client.egg-info/SOURCES.txt +7 -1
- {staplus_client-0.3.4 → staplus_client-0.4.2}/staplus_client.egg-info/requires.txt +1 -1
- {staplus_client-0.3.4 → staplus_client-0.4.2}/staplus_client.egg-info/top_level.txt +1 -0
- staplus_client-0.4.2/tests/__init__.py +0 -0
- staplus_client-0.4.2/tests/test_sta.py +186 -0
- staplus_client-0.4.2/tests/test_staplus.py +493 -0
- staplus_client-0.3.4/setup.cfg +0 -4
- staplus_client-0.3.4/staplus_client/model/feature_of_interest.py +0 -17
- staplus_client-0.3.4/staplus_client/model/location.py +0 -22
- {staplus_client-0.3.4 → staplus_client-0.4.2}/LICENSE +0 -0
- {staplus_client-0.3.4 → staplus_client-0.4.2}/staplus_client/dao/__init__.py +0 -0
- {staplus_client-0.3.4 → staplus_client-0.4.2}/staplus_client/dao/actuator.py +0 -0
- {staplus_client-0.3.4 → staplus_client-0.4.2}/staplus_client/dao/base.py +0 -0
- {staplus_client-0.3.4 → staplus_client-0.4.2}/staplus_client/dao/campaign.py +0 -0
- {staplus_client-0.3.4 → staplus_client-0.4.2}/staplus_client/dao/datastream.py +0 -0
- {staplus_client-0.3.4 → staplus_client-0.4.2}/staplus_client/dao/features_of_interest.py +0 -0
- {staplus_client-0.3.4 → staplus_client-0.4.2}/staplus_client/dao/historical_location.py +0 -0
- {staplus_client-0.3.4 → staplus_client-0.4.2}/staplus_client/dao/license.py +0 -0
- {staplus_client-0.3.4 → staplus_client-0.4.2}/staplus_client/dao/location.py +0 -0
- {staplus_client-0.3.4 → staplus_client-0.4.2}/staplus_client/dao/multi_datastream.py +0 -0
- {staplus_client-0.3.4 → staplus_client-0.4.2}/staplus_client/dao/observation.py +0 -0
- {staplus_client-0.3.4 → staplus_client-0.4.2}/staplus_client/dao/observation_group.py +0 -0
- {staplus_client-0.3.4 → staplus_client-0.4.2}/staplus_client/dao/observedproperty.py +0 -0
- {staplus_client-0.3.4 → staplus_client-0.4.2}/staplus_client/dao/party.py +0 -0
- {staplus_client-0.3.4 → staplus_client-0.4.2}/staplus_client/dao/relation.py +0 -0
- {staplus_client-0.3.4 → staplus_client-0.4.2}/staplus_client/dao/sensor.py +0 -0
- {staplus_client-0.3.4 → staplus_client-0.4.2}/staplus_client/dao/task.py +0 -0
- {staplus_client-0.3.4 → staplus_client-0.4.2}/staplus_client/dao/tasking_capability.py +0 -0
- {staplus_client-0.3.4 → staplus_client-0.4.2}/staplus_client/dao/thing.py +0 -0
- {staplus_client-0.3.4 → staplus_client-0.4.2}/staplus_client/model/__init__.py +0 -0
- {staplus_client-0.3.4 → staplus_client-0.4.2}/staplus_client/model/ext/__init__.py +0 -0
- {staplus_client-0.3.4 → staplus_client-0.4.2}/staplus_client/model/ext/unitofmeasurement.py +0 -0
- {staplus_client-0.3.4 → staplus_client-0.4.2}/staplus_client/model/historical_location.py +0 -0
- {staplus_client-0.3.4 → staplus_client-0.4.2}/staplus_client/model/observedproperty.py +0 -0
- {staplus_client-0.3.4 → staplus_client-0.4.2}/staplus_client/model/sensor.py +0 -0
- {staplus_client-0.3.4 → staplus_client-0.4.2}/staplus_client/query/__init__.py +0 -0
- {staplus_client-0.3.4 → staplus_client-0.4.2}/staplus_client/query/query.py +0 -0
- {staplus_client-0.3.4 → staplus_client-0.4.2}/staplus_client/service/BearerAuth.py +0 -0
- {staplus_client-0.3.4 → staplus_client-0.4.2}/staplus_client/service/__init__.py +0 -0
- {staplus_client-0.3.4 → staplus_client-0.4.2}/staplus_client/service/auth_handler.py +0 -0
- {staplus_client-0.3.4 → staplus_client-0.4.2}/staplus_client/utils.py +0 -0
- {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
|
+
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
|
|
|
@@ -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',
|
|
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
|
|
|
@@ -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
|
-
'
|
|
64
|
+
'_privacy_policy': None, '_creation_time': '', '_start_time': None, '_end_time': None,
|
|
64
65
|
'_url': None, '_properties': None,
|
|
65
|
-
'_datastreams': None, '_multi_datastreams': 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
|
-
|
|
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['
|
|
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
|
|
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.
|
|
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__
|
|
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(
|
|
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.
|
|
142
|
+
if self._party is not None:
|
|
138
143
|
data['Party'] = self.party
|
|
139
|
-
if self.
|
|
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
|
|
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,
|
|
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', '
|
|
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', '
|
|
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
|
-
|
|
137
|
-
|
|
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
|
-
|
|
142
|
-
|
|
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
|
-
|
|
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.
|
|
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(
|
|
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__
|
|
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):
|