Skip to content

Commit d62e7db

Browse files
add some models, tests retrieving raw systems, and slight reorganize
1 parent 2348e31 commit d62e7db

12 files changed

Lines changed: 338 additions & 30 deletions

File tree

external_models/__init__.py

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
# ==============================================================================
2+
# Copyright (c) 2024 Botts Innovative Research, Inc.
3+
# Date: 2024/5/31
4+
# Author: Ian Patterson
5+
# Contact Email: ian@botts-inc.com
6+
# ==============================================================================
7+
8+
from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator
9+
from shapely import Point
10+
from typing_extensions import Self
11+
12+
13+
class BoundingBox(BaseModel):
14+
model_config = ConfigDict(arbitrary_types_allowed=True)
15+
16+
lower_left_corner: Point = Field(..., description="The lower left corner of the bounding box.")
17+
upper_right_corner: Point = Field(..., description="The upper right corner of the bounding box.")
18+
min_value: float = Field(None, description="The minimum value of the bounding box.")
19+
max_value: float = Field(None, description="The maximum value of the bounding box.")
20+
21+
# @model_validator(mode='before')
22+
# def validate_minmax(self) -> Self:
23+
# if self.min_value > self.max_value:
24+
# raise ValueError("min_value must be less than max_value")
25+
# return self
26+
27+
28+
class DateTime(BaseModel):
29+
is_instant: bool = Field(True, description="Whether the date time is an instant or a period.")
30+
iso_date: str = Field(None, description="The ISO formatted date time.")
31+
time_period: tuple = Field(None, description="The time period of the date time.")
32+
33+
@model_validator(mode='before')
34+
def valid_datetime_type(self) -> Self:
35+
if self.is_instant:
36+
if self.iso_date is None:
37+
raise ValueError("Instant date time must have a valid ISO8601 date.")
38+
return self
39+
40+
@field_validator('iso_date')
41+
@classmethod
42+
def check_iso_date(cls, v) -> str:
43+
if not v:
44+
raise ValueError("Instant date time must have a valid ISO8601 date.")
45+
return v
46+
47+
48+
class SecurityConstraints:
49+
constraints: list
50+
51+
52+
class LegalConstraints:
53+
constraints: list
54+
55+
56+
class Characteristics:
57+
characteristics: list
58+
59+
60+
class Capabilities:
61+
capabilities: list
62+
63+
64+
class Contact:
65+
contact: list
66+
67+
68+
class Documentation:
69+
documentation: list
70+
71+
72+
class HistoryEvent:
73+
history_event: list
74+
75+
76+
class ConfigurationSettings:
77+
settings: list
78+
79+
80+
class FeatureOfInterest:
81+
feature: list
82+
83+
84+
class Input:
85+
input: list
86+
87+
88+
class Output:
89+
output: list
90+
91+
92+
class Parameter:
93+
parameter: list
94+
95+
96+
class Mode:
97+
mode: list
98+
99+
100+
class ProcessMethod:
101+
method: list

external_models/object_models.py

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
# ==============================================================================
2+
# Copyright (c) 2024 Botts Innovative Research, Inc.
3+
# Date: 2024/5/31
4+
# Author: Ian Patterson
5+
# Contact Email: ian@botts-inc.com
6+
# ==============================================================================
7+
import uuid
8+
from typing import List
9+
10+
from conSys4Py import DatastreamSchema
11+
from conSys4Py.datamodels.api_utils import Link
12+
from pydantic import BaseModel, ConfigDict, Field
13+
from shapely import Geometry
14+
15+
from external_models import BoundingBox, Capabilities, Characteristics, ConfigurationSettings, Contact, DateTime, \
16+
Documentation, \
17+
FeatureOfInterest, HistoryEvent, Input, LegalConstraints, \
18+
Mode, Output, Parameter, ProcessMethod, SecurityConstraints
19+
20+
21+
class System(BaseModel):
22+
model_config = ConfigDict(arbitrary_types_allowed=True)
23+
24+
feature_type: str = Field(None, serialization_alias="type")
25+
system_id: str = Field(None, serialization_alias="id")
26+
properties: dict = Field(None)
27+
geometry: Geometry | None = Field(None)
28+
bbox: BoundingBox = Field(None)
29+
links: List[Link] = Field(None)
30+
description: str = Field(None)
31+
uid: uuid.UUID = Field(None)
32+
label: str = Field(None)
33+
lang: str = Field(None)
34+
keywords: List[str] = Field(None)
35+
identifiers: List[str] = Field(None)
36+
classifiers: List[str] = Field(None)
37+
valid_time: DateTime = Field(None, serialization_alias="validTime")
38+
security_constraints: List[SecurityConstraints] = Field(None, serialization_alias="securityConstraints")
39+
legal_constraints: List[LegalConstraints] = Field(None, serialization_alias="legalConstraints")
40+
characteristics: List[Characteristics] = Field(None)
41+
capabilities: List[Capabilities] = Field(None)
42+
contacts: List[Contact] = Field(None)
43+
documentation: List[Documentation] = Field(None)
44+
history: List[HistoryEvent] = Field(None)
45+
definition: str = Field(None)
46+
type_of: str = Field(None, serialization_alias="typeOf")
47+
configuration: ConfigurationSettings = Field(None)
48+
features_of_interest: List[FeatureOfInterest] = Field(None, alias="featuresOfInterest")
49+
inputs: List[Input] = Field(None)
50+
outputs: List[Output] = Field(None)
51+
parameters: List[Parameter] = Field(None)
52+
modes: List[Mode] = Field(None)
53+
method: ProcessMethod = Field(None)
54+
55+
56+
class Datastream(BaseModel):
57+
ds_id: str = Field(..., serialization_alias="id")
58+
name: str = Field(...)
59+
description: str
60+
valid_time: DateTime = Field(..., serialization_alias="validTime")
61+
output_name: str = Field(None, serialization_alias="outputName")
62+
procedure_link: Link = Field(None, serialization_alias="procedureLink@link")
63+
deployment_link: Link = Field(None, serialization_alias="deploymentLink@link")
64+
ultimate_feature_of_interest_link: Link = Field(None, serialization_alias="ultimateFeatureOfInterest@link")
65+
sampling_feature_link: Link = Field(None, serialization_alias="samplingFeature@link")
66+
parameters: dict = Field(None)
67+
phenomenon_time: DateTime = Field(None, serialization_alias="phenomenonTimeInterval")
68+
result_time: DateTime = Field(None, serialization_alias="resultTimeInterval")
69+
ds_type: str = Field(None, serialization_alias="type")
70+
result_type: str = Field(None, serialization_alias="resultType")
71+
links: List[Link] = Field(None)
72+
schema: DatastreamSchema = Field(None)
73+
74+
75+
class Observation(BaseModel):
76+
sampling_feature_id: str = Field(None, serialization_alias="samplingFeature@Id")
77+
procedure_link: Link = Field(None, serialization_alias="procedure@link")
78+
phenomenon_time: DateTime = Field(None, serialization_alias="phenomenonTime")
79+
result_time: DateTime = Field(..., serialization_alias="resultTime")
80+
parameters: dict = Field(None)
81+
result: dict = Field(...)
82+
result_link: Link = Field(None, serialization_alias="result@link")

oshconnect/__init__.py

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
21
# ==============================================================================
32
# Copyright (c) 2024 Botts Innovative Research, Inc.
43
# Date: 2024/5/28
@@ -11,31 +10,40 @@
1110
from abc import ABC
1211
from dataclasses import dataclass
1312
from enum import Enum
14-
import oshdatacore as swe_common
13+
import swecommondm as swe_common
14+
from conSys4Py import APIResourceTypes
15+
from conSys4Py.core.default_api_helpers import APIHelper
1516

1617

1718
@dataclass(kw_only=True)
1819
class Endpoints:
19-
root: str = "/sensorhub"
20+
root: str = "sensorhub"
2021
sos: str = f"{root}/sos"
2122
connected_systems: str = f"{root}/api"
2223

2324

2425
@dataclass(kw_only=True)
25-
class Node(ABC):
26+
class Node:
2627
_id: str
2728
address: str
2829
port: int
2930
endpoints: Endpoints
3031
is_secure: bool
31-
_basic_auth: bytes
32+
_basic_auth: bytes = None
33+
_api_helper: APIHelper
3234

33-
def __init__(self, address: str, port: int, is_secure: bool):
35+
def __init__(self, address: str, port: int, username: str = None, password: str = None, **kwargs: dict):
3436
self._id = f'node-{uuid.uuid4()}'
3537
self.address = address
3638
self.port = port
37-
self.is_secure = is_secure
39+
self.is_secure = username is not None and password is not None
40+
if self.is_secure:
41+
self.add_basicauth(username, password)
3842
self.endpoints = Endpoints()
43+
self._api_helper = APIHelper(server_url=f'{self.address}:{self.port}',
44+
api_root=self.endpoints.connected_systems, username=username, password=password)
45+
if self.is_secure:
46+
self._api_helper.user_auth = True
3947

4048
def get_id(self):
4149
return self._id
@@ -50,17 +58,24 @@ def get_api_endpoint(self):
5058
return f"http{'s' if self.is_secure else ''}://{self.address}:{self.port}/{self.endpoints.connected_systems}"
5159

5260
def add_basicauth(self, username: str, password: str):
61+
if not self.is_secure:
62+
self.is_secure = True
5363
self._basic_auth = base64.b64encode(f"{username}:{password}".encode('utf-8'))
5464

5565
def get_decoded_auth(self):
5666
return self._basic_auth.decode('utf-8')
5767

68+
def discover_systems(self):
69+
res = self._api_helper.retrieve_resource(APIResourceTypes.SYSTEM, req_headers={})
70+
if res.ok:
71+
return res.json()
72+
else:
73+
return None
74+
5875

5976
class TemporalModes(Enum):
6077
REAL_TIME = 0
6178
ARCHIVE = 1
6279
BATCH = 2
6380
RT_SYNC = 3
6481
ARCHIVE_SYNC = 4
65-
66-

oshconnect/datamodels/__init__.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# ==============================================================================
2+
# Copyright (c) 2024 Botts Innovative Research, Inc.
3+
# Date: 2024/5/31
4+
# Author: Ian Patterson
5+
# Contact Email: ian@botts-inc.com
6+
# ==============================================================================
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
# ==============================================================================
2+
# Copyright (c) 2024 Botts Innovative Research, Inc.
3+
# Date: 2024/6/13
4+
# Author: Ian Patterson
5+
# Contact Email: ian@botts-inc.com
6+
# ==============================================================================
7+
from __future__ import annotations
8+
9+
import uuid
10+
11+
from oshconnect import Node
12+
from external_models.object_models import System as SystemResource
13+
14+
15+
class System:
16+
uid: uuid.UUID
17+
name: str
18+
label: str
19+
datastreams: list[Datastream]
20+
control_channels: list[ControlChannel]
21+
description: str
22+
_parent_node: Node
23+
24+
def __init__(self, name: str, label: str, **kwargs):
25+
self.uid = uuid.uuid4()
26+
self.name = name
27+
self.label = label
28+
self.datastreams = []
29+
self.control_channels = []
30+
31+
def update_parent_node(self, node: Node):
32+
self._parent_node = node
33+
34+
@staticmethod
35+
def from_system_resource(system_resource: SystemResource):
36+
other_props = SystemResource.__dict__
37+
return System(name=system_resource.name, label=system_resource.label)
38+
39+
40+
class Datastream:
41+
42+
def __init__(self):
43+
pass
44+
45+
46+
class ControlChannel:
47+
48+
def __init__(self):
49+
pass

oshconnect/datasource/__init__.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,3 @@ class Mode(Enum):
1717
REPLAY = "replay"
1818
BATCH = "batch"
1919
REAL_TIME = "realTime"
20-
21-
class DataSourceHandler:
22-
datasources

oshconnect/datastore/datastore.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,4 @@
77

88
class DataStore:
99
def __init__(self):
10-
pass
10+
pass

0 commit comments

Comments
 (0)