Skip to content

Commit 76ca9ca

Browse files
Fixes in SWE Schemas to allow for duck typing
1 parent cac856c commit 76ca9ca

7 files changed

Lines changed: 82 additions & 39 deletions

File tree

conSys/__init__.py

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,19 @@
1+
from .constants import *
2+
from .datamodels.swe_components import *
3+
from .endpoints.endpoints import Endpoint
14
from .part_1 import capabilities as Capabilities
25
from .part_1 import collections_ep as Collections
36
from .part_1 import deployments as Deployments
47
from .part_1 import procedures as Procedures
58
from .part_1 import properties as Properties
69
from .part_1 import sampling_features as SamplingFeatures
710
from .part_1 import systems as Systems
8-
911
from .part_2 import commands as Commands
1012
from .part_2 import control_channels as ControlChannels
1113
from .part_2 import datastreams as Datastreams
1214
from .part_2 import observations as Observations
1315
from .part_2 import system_events as SystemEvents
1416
from .part_2 import system_history as SystemHistory
15-
16-
from .endpoints.endpoints import Endpoint
17-
18-
from .request_bodies import *
19-
2017
from .querymodel import QueryModel
21-
from .constants import *
18+
from .request_bodies import *
2219
from .utilities import model_utils

conSys/datamodels/datastreams.py

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1-
from pydantic import BaseModel, Field, field_validator
1+
from pydantic import BaseModel, Field, field_validator, SerializeAsAny
22

33
from conSys import ObservationFormat
4+
from conSys.datamodels.encoding import Encoding
5+
from conSys.datamodels.swe_components import AnyComponentSchema
46

57

68
class DatastreamSchema(BaseModel):
@@ -11,13 +13,13 @@ class DatastreamSchema(BaseModel):
1113

1214

1315
class SWEDatastreamSchema(DatastreamSchema):
14-
encoding: dict = Field(...)
15-
record_schema: dict = Field(..., record_schema='recordSchema')
16+
encoding: SerializeAsAny[Encoding] = Field(...)
17+
record_schema: SerializeAsAny[AnyComponentSchema] = Field(..., serialization_alias='recordSchema')
1618

17-
@field_validator('obsFormat')
19+
@field_validator('obs_format')
1820
@classmethod
1921
def check_check_obs_format(cls, v):
20-
if v in [ObservationFormat.SWE_JSON.value, ObservationFormat.SWE_CSV.value,
21-
ObservationFormat.SWE_TEXT.value, ObservationFormat.SWE_BINARY.value]:
22+
if v not in [ObservationFormat.SWE_JSON.value, ObservationFormat.SWE_CSV.value,
23+
ObservationFormat.SWE_TEXT.value, ObservationFormat.SWE_BINARY.value]:
2224
raise ValueError('obsFormat must be on of the SWE formats')
2325
return v

conSys/datamodels/encoding.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
from pydantic import BaseModel, Field
2+
3+
4+
class Encoding(BaseModel):
5+
id: str = Field(None)
6+
type: str = Field(...)
7+
vector_as_arrays: bool = Field(False, alias='vectorAsArrays')
8+
9+
10+
class JSONEncoding(Encoding):
11+
type: str = "JSONEncoding"

conSys/datamodels/geometry.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
from pydantic import BaseModel, Field
2+
3+
from conSys import GeometryTypes
4+
5+
6+
# TODO: Add specific validations for each type
7+
class Geometry(BaseModel):
8+
"""
9+
A class to represent the geometry of a feature
10+
"""
11+
type: GeometryTypes = Field(...)
12+
coordinates: list
13+
bbox: list = None

conSys/datamodels/swe_components.py

Lines changed: 17 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
from __future__ import annotations
22

33
from numbers import Real
4-
from typing import Union
4+
from typing import Union, Any
55

6-
from pydantic import BaseModel, Field, field_validator
6+
from pydantic import BaseModel, Field, field_validator, SerializeAsAny
77

88
from conSys import GeometryTypes
99
from conSys.datamodels.api_utils import UCUMCode, URI
10+
from conSys.datamodels.geometry import Geometry
1011

1112
"""
1213
NOTE: The following classes are used to represent the Record Schemas that are required for use with Datastreams
@@ -23,18 +24,18 @@
2324

2425

2526
class AnyComponentSchema(BaseModel):
27+
type: str = Field(...)
2628
id: str = Field(None)
2729
label: str = Field(None)
2830
description: str = Field(None)
29-
type: str = Field(...)
3031
updatable: bool = Field(False)
3132
optional: bool = Field(False)
3233
definition: str = Field(None)
3334

3435

3536
class DataRecordSchema(AnyComponentSchema):
3637
type: str = "DataRecord"
37-
fields: list[AnyComponentSchema] = Field(...)
38+
fields: SerializeAsAny[list[AnyComponentSchema]] = Field(...)
3839

3940

4041
class VectorSchema(AnyComponentSchema):
@@ -44,21 +45,21 @@ class VectorSchema(AnyComponentSchema):
4445
reference_frame: str = Field(...)
4546
local_frame: str = Field(None)
4647
# TODO: VERIFY might need to be moved further down when these are defined
47-
coordinates: Union[list[CountSchema], list[QuantitySchema], list[TimeSchema]] = Field(...)
48+
coordinates: SerializeAsAny[Union[list[CountSchema], list[QuantitySchema], list[TimeSchema]]] = Field(...)
4849

4950

5051
class DataArraySchema(AnyComponentSchema):
5152
type: str = "DataArray"
5253
element_count: int = Field(..., serialization_alias='elementCount') # Should type of Count
53-
element_type: list[AnyComponentSchema] = Field(..., serialization_alias='elementType')
54+
element_type: SerializeAsAny[list[AnyComponentSchema]] = Field(..., serialization_alias='elementType')
5455
encoding: str = Field(...) # TODO: implement an encodings class
5556
values: list = Field(None)
5657

5758

5859
class MatrixSchema(AnyComponentSchema):
5960
type: str = "Matrix"
6061
element_count: int = Field(..., serialization_alias='elementCount') # Should be type of Count
61-
element_type: list[AnyComponentSchema] = Field(..., serialization_alias='elementType')
62+
element_type: SerializeAsAny[list[AnyComponentSchema]] = Field(..., serialization_alias='elementType')
6263
encoding: str = Field(...) # TODO: implement an encodings class
6364
values: list = Field(None)
6465
reference_frame: str = Field(None)
@@ -70,7 +71,7 @@ class DataChoiceSchema(AnyComponentSchema):
7071
updatable: bool = Field(False)
7172
optional: bool = Field(False)
7273
choice_value: CategorySchema = Field(..., serialization_alias='choiceValue') # TODO: Might be called "choiceValues"
73-
items: list[AnyComponentSchema] = Field(...)
74+
items: SerializeAsAny[list[AnyComponentSchema]] = Field(...)
7475

7576

7677
class GeometrySchema(AnyComponentSchema):
@@ -85,23 +86,23 @@ class GeometrySchema(AnyComponentSchema):
8586
GeometryTypes.MULTI_POLYGON.value]}
8687
nil_values: list = Field(None, serialization_alias='nilValues')
8788
srs: str = Field(...)
88-
value = Field(None)
89+
value: Geometry = Field(None)
8990

9091

9192
class AnySimpleComponentSchema(AnyComponentSchema):
9293
label: str = Field(...)
93-
description = Field(None)
94+
description: str = Field(None)
9495
type: str = Field(...)
95-
updatable = Field(False)
96-
optional = Field(False)
96+
updatable: bool = Field(False)
97+
optional: bool = Field(False)
9798
definition: str = Field(...)
9899
reference_frame: str = Field(None, serialization_alias='referenceFrame')
99100
axis_id: str = Field(None, serialization_alias='axisID')
100101
quality: Union[list[QuantitySchema], list[QuantityRangeSchema], list[CategorySchema], list[TextSchema]] = Field(
101102
None) # TODO: Union[Quantity, QuantityRange, Category, Text]
102103
nil_values: list = Field(None, serialization_alias='nilValues')
103-
constraint = Field(None)
104-
value = Field(None)
104+
constraint: Any = Field(None)
105+
value: Any = Field(None)
105106

106107

107108
class AnyScalarComponentSchema(AnySimpleComponentSchema):
@@ -123,7 +124,7 @@ class CountSchema(AnyScalarComponentSchema):
123124

124125
class QuantitySchema(AnyScalarComponentSchema):
125126
type: str = "Quantity"
126-
value: Union[Real, str] = Field(None)
127+
value: Union[float, str] = Field(None)
127128
uom: Union[UCUMCode, URI] = Field(...)
128129

129130
@field_validator('value')
@@ -172,7 +173,7 @@ class CountRangeSchema(AnySimpleComponentSchema):
172173

173174
class QuantityRangeSchema(AnySimpleComponentSchema):
174175
type: str = "QuantityRange"
175-
value: list[Union[Real, str]] = Field(None)
176+
value: list[Union[float, str]] = Field(None)
176177
uom: Union[UCUMCode, URI] = Field(...)
177178

178179

conSys/request_bodies.py

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
11
from typing import Union
22

3-
from pydantic import BaseModel, HttpUrl, Field, model_serializer, RootModel
3+
from pydantic import BaseModel, HttpUrl, Field, model_serializer, RootModel, SerializeAsAny
44

5+
from conSys.datamodels.datastreams import DatastreamSchema
56
from conSys.sensor_ml.sml import TypeOf
67
from conSys.constants import DatastreamResultTypes
78

89

10+
# TODO: Consider some sort of Abstract Base Class for all valid request bodies to inherit from to reduce the complexity
11+
# of the final request body.
12+
913
class GeoJSONBody(BaseModel):
1014
type: str
1115
id: str
@@ -73,14 +77,15 @@ class DatastreamBodyJSON(BaseModel):
7377
result_time_interval: str = Field(None, serialization_alias='resultTimeInterval')
7478
result_type: DatastreamResultTypes = Field(None, serialization_alias='resultType')
7579
links: list = Field(None)
76-
schema: dict = Field(None) # TODO: introduce a DatastreamSchema class
80+
schema: SerializeAsAny[DatastreamSchema] = Field(...)
7781

7882

7983
class RequestBody(BaseModel):
8084
"""
8185
Wrapper class to support different request json structures
8286
"""
83-
json_structure: Union[GeoJSONBody, SmlJSONBody, OMJSONBody] = Field(..., serialization_alias='json')
87+
json_structure: Union[GeoJSONBody, SmlJSONBody, OMJSONBody, DatastreamSchema] = Field(...,
88+
serialization_alias='json')
8489
test_extra: str = Field("Hello, I am test", serialization_alias='testExtra')
8590

8691
@model_serializer
@@ -90,4 +95,4 @@ def ser_model(self):
9095

9196

9297
class RequestBodyList(RootModel):
93-
root: list[Union[GeoJSONBody, SmlJSONBody, OMJSONBody]] = Field(...)
98+
root: list[Union[GeoJSONBody, SmlJSONBody, OMJSONBody, DatastreamSchema]] = Field(...)

tests/test_script_full_suite.py

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
1-
import json
21
import random
32

4-
from conSys import Systems, Procedures, SamplingFeatures, Datastreams, SmlJSONBody, GeoJSONBody, model_utils, \
5-
DatastreamBodyJSON
3+
from conSys import Systems, SamplingFeatures, Datastreams, SmlJSONBody, GeoJSONBody, model_utils, \
4+
DatastreamBodyJSON, ObservationFormat, URI
5+
from conSys.datamodels.datastreams import SWEDatastreamSchema
6+
from conSys.datamodels.encoding import JSONEncoding
7+
from conSys.datamodels.swe_components import BooleanSchema, TimeSchema, DataRecordSchema
68

7-
server_url = "http://localhost:8181/sensorhub"
9+
server_url = "http://localhost:8282/sensorhub"
810
geo_json_headers = {"Content-Type": "application/geo+json"}
911
sml_json_headers = {"Content-Type": "application/sml+json"}
1012
json_headers = {"Content-Type": "application/json"}
@@ -205,8 +207,20 @@ def test_retrieve_sampling_feature_by_id():
205207

206208

207209
def test_create_datastreams():
208-
datastream = DatastreamBodyJSON(name="Test Datastream", output_name="Test Output #1",)
209-
resp = Datastreams.add_datastreams_to_system(server_url, retrieved_systems[0]['id'], datastream.model_dump_json(exclude_none=True, by_alias=True),
210+
time_schema = TimeSchema(label="Test Datastream Time", definition="http://test.com/Time",
211+
uom=URI(href="http://test.com/TimeUOM"))
212+
bool_schema = BooleanSchema(label="Test Datastream Boolean", definition="http://test.com/Boolean")
213+
datarecord_schema = SWEDatastreamSchema(encoding=JSONEncoding(), obs_format=ObservationFormat.SWE_JSON.value,
214+
record_schema=DataRecordSchema(label="Test Datastream Record",
215+
definition="http://test.com/Record",
216+
fields=[time_schema, bool_schema]))
217+
218+
print(f'Datastream Schema: {datarecord_schema.model_dump_json(exclude_none=True, by_alias=True)}')
219+
datastream_body = DatastreamBodyJSON(name="Test Datastream", output_name="Test Output #1", schema=datarecord_schema)
220+
temp_test_json = datastream_body.model_dump_json(exclude_none=True, by_alias=True)
221+
print(f'Test Datastream JSON: {temp_test_json}')
222+
resp = Datastreams.add_datastreams_to_system(server_url, retrieved_systems[0]['id'],
223+
datastream_body.model_dump_json(exclude_none=True, by_alias=True),
210224
headers=json_headers)
211225
print(resp)
212226

0 commit comments

Comments
 (0)