hydroserverpy 0.4.0__py3-none-any.whl → 0.5.0b1__py3-none-any.whl

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.

Potentially problematic release.


This version of hydroserverpy might be problematic. Click here for more details.

Files changed (65) hide show
  1. hydroserverpy/__init__.py +2 -3
  2. hydroserverpy/api/http.py +24 -0
  3. hydroserverpy/api/main.py +152 -0
  4. hydroserverpy/api/models/__init__.py +18 -0
  5. hydroserverpy/api/models/base.py +74 -0
  6. hydroserverpy/api/models/etl/__init__.py +0 -0
  7. hydroserverpy/api/models/iam/__init__.py +0 -0
  8. hydroserverpy/api/models/iam/account.py +12 -0
  9. hydroserverpy/api/models/iam/collaborator.py +34 -0
  10. hydroserverpy/api/models/iam/role.py +10 -0
  11. hydroserverpy/api/models/iam/workspace.py +203 -0
  12. hydroserverpy/api/models/sta/__init__.py +0 -0
  13. hydroserverpy/api/models/sta/datastream.py +336 -0
  14. hydroserverpy/api/models/sta/observed_property.py +72 -0
  15. hydroserverpy/api/models/sta/processing_level.py +50 -0
  16. hydroserverpy/api/models/sta/result_qualifier.py +49 -0
  17. hydroserverpy/api/models/sta/sensor.py +105 -0
  18. hydroserverpy/api/models/sta/thing.py +217 -0
  19. hydroserverpy/api/models/sta/unit.py +49 -0
  20. hydroserverpy/api/services/__init__.py +8 -0
  21. hydroserverpy/api/services/base.py +92 -0
  22. hydroserverpy/api/services/etl/__init__.py +0 -0
  23. hydroserverpy/api/services/iam/__init__.py +0 -0
  24. hydroserverpy/api/services/iam/workspace.py +126 -0
  25. hydroserverpy/api/services/sta/__init__.py +0 -0
  26. hydroserverpy/api/services/sta/datastream.py +354 -0
  27. hydroserverpy/api/services/sta/observed_property.py +98 -0
  28. hydroserverpy/api/services/sta/processing_level.py +78 -0
  29. hydroserverpy/api/services/sta/result_qualifier.py +74 -0
  30. hydroserverpy/api/services/sta/sensor.py +116 -0
  31. hydroserverpy/api/services/sta/thing.py +188 -0
  32. hydroserverpy/api/services/sta/unit.py +82 -0
  33. hydroserverpy/etl/loaders/hydroserver_loader.py +1 -1
  34. hydroserverpy/etl_csv/hydroserver_etl_csv.py +1 -1
  35. {hydroserverpy-0.4.0.dist-info → hydroserverpy-0.5.0b1.dist-info}/METADATA +4 -3
  36. hydroserverpy-0.5.0b1.dist-info/RECORD +59 -0
  37. {hydroserverpy-0.4.0.dist-info → hydroserverpy-0.5.0b1.dist-info}/WHEEL +1 -1
  38. hydroserverpy/core/endpoints/__init__.py +0 -9
  39. hydroserverpy/core/endpoints/base.py +0 -146
  40. hydroserverpy/core/endpoints/data_loaders.py +0 -93
  41. hydroserverpy/core/endpoints/data_sources.py +0 -93
  42. hydroserverpy/core/endpoints/datastreams.py +0 -225
  43. hydroserverpy/core/endpoints/observed_properties.py +0 -111
  44. hydroserverpy/core/endpoints/processing_levels.py +0 -111
  45. hydroserverpy/core/endpoints/result_qualifiers.py +0 -111
  46. hydroserverpy/core/endpoints/sensors.py +0 -111
  47. hydroserverpy/core/endpoints/things.py +0 -261
  48. hydroserverpy/core/endpoints/units.py +0 -111
  49. hydroserverpy/core/schemas/__init__.py +0 -9
  50. hydroserverpy/core/schemas/base.py +0 -124
  51. hydroserverpy/core/schemas/data_loaders.py +0 -73
  52. hydroserverpy/core/schemas/data_sources.py +0 -223
  53. hydroserverpy/core/schemas/datastreams.py +0 -330
  54. hydroserverpy/core/schemas/observed_properties.py +0 -43
  55. hydroserverpy/core/schemas/processing_levels.py +0 -31
  56. hydroserverpy/core/schemas/result_qualifiers.py +0 -26
  57. hydroserverpy/core/schemas/sensors.py +0 -68
  58. hydroserverpy/core/schemas/things.py +0 -346
  59. hydroserverpy/core/schemas/units.py +0 -29
  60. hydroserverpy/core/service.py +0 -200
  61. hydroserverpy-0.4.0.dist-info/RECORD +0 -51
  62. /hydroserverpy/{core → api}/__init__.py +0 -0
  63. {hydroserverpy-0.4.0.dist-info → hydroserverpy-0.5.0b1.dist-info/licenses}/LICENSE +0 -0
  64. {hydroserverpy-0.4.0.dist-info → hydroserverpy-0.5.0b1.dist-info}/top_level.txt +0 -0
  65. {hydroserverpy-0.4.0.dist-info → hydroserverpy-0.5.0b1.dist-info}/zip-safe +0 -0
@@ -0,0 +1,217 @@
1
+ from typing import Optional, Union, List, Dict, IO, TYPE_CHECKING
2
+ from uuid import UUID
3
+ from pydantic import (
4
+ BaseModel,
5
+ Field,
6
+ AliasChoices,
7
+ AliasPath,
8
+ AnyHttpUrl,
9
+ field_validator,
10
+ )
11
+ from ..base import HydroServerModel
12
+
13
+ if TYPE_CHECKING:
14
+ from hydroserverpy import HydroServer
15
+ from hydroserverpy.api.models import Workspace, Datastream
16
+
17
+
18
+ class ThingFields(BaseModel):
19
+ name: str = Field(..., max_length=200)
20
+ description: str
21
+ sampling_feature_type: str = Field(
22
+ ...,
23
+ max_length=200,
24
+ validation_alias=AliasChoices(
25
+ "samplingFeatureType", AliasPath("properties", "samplingFeatureType")
26
+ ),
27
+ )
28
+ sampling_feature_code: str = Field(
29
+ ...,
30
+ max_length=200,
31
+ validation_alias=AliasChoices(
32
+ "samplingFeatureCode", AliasPath("properties", "samplingFeatureCode")
33
+ ),
34
+ )
35
+ site_type: str = Field(
36
+ ...,
37
+ max_length=200,
38
+ validation_alias=AliasChoices("siteType", AliasPath("properties", "siteType")),
39
+ )
40
+ tags: Dict[str, str] = Field(
41
+ ...,
42
+ json_schema_extra={"editable": False, "read_only": True},
43
+ validation_alias=AliasChoices("tags", AliasPath("properties", "tags")),
44
+ )
45
+ photos: Dict[str, AnyHttpUrl] = Field(
46
+ ...,
47
+ json_schema_extra={"editable": False, "read_only": True},
48
+ validation_alias=AliasChoices("photos", AliasPath("properties", "photos")),
49
+ )
50
+ data_disclaimer: Optional[str] = Field(
51
+ None,
52
+ validation_alias=AliasChoices(
53
+ "dataDisclaimer", AliasPath("properties", "dataDisclaimer")
54
+ ),
55
+ )
56
+ is_private: bool = Field(
57
+ ...,
58
+ validation_alias=AliasChoices(
59
+ "isPrivate", AliasPath("properties", "isPrivate")
60
+ ),
61
+ )
62
+
63
+ @field_validator("tags", mode="before")
64
+ def convert_tags(
65
+ cls, value: Union[List[Dict[str, str]], Dict[str, str]]
66
+ ) -> Dict[str, str]:
67
+ if isinstance(value, list):
68
+ return {item["key"]: item["value"] for item in value}
69
+ return value
70
+
71
+ @field_validator("photos", mode="before")
72
+ def convert_photos(
73
+ cls, value: Union[List[Dict[str, str]], Dict[str, str]]
74
+ ) -> Dict[str, str]:
75
+ if isinstance(value, list):
76
+ return {item["name"]: item["link"] for item in value}
77
+ return value
78
+
79
+
80
+ class LocationFields(BaseModel):
81
+ latitude: float = Field(
82
+ ...,
83
+ ge=-90,
84
+ le=90,
85
+ validation_alias=AliasChoices(
86
+ "latitude",
87
+ AliasPath("Locations", 0, "location", "geometry", "coordinates", 0),
88
+ ),
89
+ )
90
+ longitude: float = Field(
91
+ ...,
92
+ ge=-180,
93
+ le=180,
94
+ validation_alias=AliasChoices(
95
+ "longitude",
96
+ AliasPath("Locations", 0, "location", "geometry", "coordinates", 1),
97
+ ),
98
+ )
99
+ elevation_m: Optional[float] = Field(
100
+ None,
101
+ ge=-99999,
102
+ le=99999,
103
+ validation_alias=AliasChoices(
104
+ "elevation_m", AliasPath("Locations", 0, "properties", "elevation_m")
105
+ ),
106
+ )
107
+ elevation_datum: Optional[str] = Field(
108
+ None,
109
+ max_length=255,
110
+ validation_alias=AliasChoices(
111
+ "elevationDatum", AliasPath("Locations", 0, "properties", "elevationDatum")
112
+ ),
113
+ )
114
+ state: Optional[str] = Field(
115
+ None,
116
+ max_length=200,
117
+ validation_alias=AliasChoices(
118
+ "state", AliasPath("Locations", 0, "properties", "state")
119
+ ),
120
+ )
121
+ county: Optional[str] = Field(
122
+ None,
123
+ max_length=200,
124
+ validation_alias=AliasChoices(
125
+ "county", AliasPath("Locations", 0, "properties", "county")
126
+ ),
127
+ )
128
+ country: Optional[str] = Field(
129
+ None,
130
+ max_length=2,
131
+ validation_alias=AliasChoices(
132
+ "country", AliasPath("Locations", 0, "properties", "country")
133
+ ),
134
+ )
135
+
136
+
137
+ class Thing(HydroServerModel, ThingFields, LocationFields):
138
+ def __init__(self, _connection: "HydroServer", _uid: Union[UUID, str], **data):
139
+ super().__init__(
140
+ _connection=_connection, _model_ref="things", _uid=_uid, **data
141
+ )
142
+
143
+ self._workspace_id = str(
144
+ data.get("workspace_id")
145
+ or data.get("workspaceId")
146
+ or data["properties"]["workspace"]["id"]
147
+ )
148
+
149
+ self._workspace = None
150
+ self._datastreams = None
151
+ self._photos = None
152
+ self._tags = None
153
+
154
+ @property
155
+ def workspace(self) -> "Workspace":
156
+ """The workspace this thing belongs to."""
157
+
158
+ if self._workspace is None:
159
+ self._workspace = self._connection.workspaces.get(uid=self._workspace_id)
160
+
161
+ return self._workspace
162
+
163
+ @property
164
+ def datastreams(self) -> List["Datastream"]:
165
+ """The datastreams collected at this thing."""
166
+
167
+ if self._datastreams is None:
168
+ self._datastreams = self._connection.datastreams.list(thing=self.uid)
169
+
170
+ return self._datastreams
171
+
172
+ def refresh(self):
173
+ """Refresh this thing from HydroServer."""
174
+
175
+ super()._refresh()
176
+ self._workspace = None
177
+ self._datastreams = None
178
+
179
+ def save(self):
180
+ """Save changes to this thing to HydroServer."""
181
+
182
+ super()._save()
183
+
184
+ def delete(self):
185
+ """Delete this thing from HydroServer."""
186
+
187
+ super()._delete()
188
+
189
+ def add_tag(self, key: str, value: str):
190
+ """Add a tag to this thing."""
191
+
192
+ self._connection.things.add_tag(uid=self.uid, key=key, value=value)
193
+ self.tags[key] = value
194
+
195
+ def update_tag(self, key: str, value: str):
196
+ """Edit a tag of this thing."""
197
+
198
+ self._connection.things.update_tag(uid=self.uid, key=key, value=value)
199
+ self.tags[key] = value
200
+
201
+ def delete_tag(self, key: str):
202
+ """Delete a tag of this thing."""
203
+
204
+ self._connection.things.delete_tag(uid=self.uid, key=key)
205
+ del self.tags[key]
206
+
207
+ def add_photo(self, file: IO[bytes]):
208
+ """Add a photo of this thing."""
209
+
210
+ photo = self._connection.things.add_photo(uid=self.uid, file=file)
211
+ self.photos[photo["name"]] = photo["link"]
212
+
213
+ def delete_photo(self, name: str):
214
+ """Delete a photo of this thing."""
215
+
216
+ self._connection.things.delete_photo(uid=self.uid, name=name)
217
+ del self.photos[name]
@@ -0,0 +1,49 @@
1
+ from typing import Union, TYPE_CHECKING
2
+ from uuid import UUID
3
+ from pydantic import BaseModel, Field
4
+ from ..base import HydroServerModel
5
+
6
+ if TYPE_CHECKING:
7
+ from hydroserverpy import HydroServer
8
+ from hydroserverpy.api.models import Workspace
9
+
10
+
11
+ class UnitFields(BaseModel):
12
+ name: str = Field(..., max_length=255)
13
+ symbol: str = Field(..., max_length=255)
14
+ definition: str
15
+ unit_type: str = Field(..., max_length=255, alias="type")
16
+
17
+
18
+ class Unit(HydroServerModel, UnitFields):
19
+ def __init__(self, _connection: "HydroServer", _uid: Union[UUID, str], **data):
20
+ super().__init__(_connection=_connection, _model_ref="units", _uid=_uid, **data)
21
+
22
+ self._workspace_id = str(data.get("workspace_id") or data["workspaceId"])
23
+
24
+ self._workspace = None
25
+
26
+ @property
27
+ def workspace(self) -> "Workspace":
28
+ """The workspace this unit belongs to."""
29
+
30
+ if self._workspace is None and self._workspace_id:
31
+ self._workspace = self._connection.workspaces.get(uid=self._workspace_id)
32
+
33
+ return self._workspace
34
+
35
+ def refresh(self):
36
+ """Refresh this unit from HydroServer."""
37
+
38
+ super()._refresh()
39
+ self._workspace = None
40
+
41
+ def save(self):
42
+ """Save changes to this unit to HydroServer."""
43
+
44
+ super()._save()
45
+
46
+ def delete(self):
47
+ """Delete this unit from HydroServer."""
48
+
49
+ super()._delete()
@@ -0,0 +1,8 @@
1
+ from .iam.workspace import WorkspaceService
2
+ from .sta.thing import ThingService
3
+ from .sta.observed_property import ObservedPropertyService
4
+ from .sta.unit import UnitService
5
+ from .sta.processing_level import ProcessingLevelService
6
+ from .sta.result_qualifier import ResultQualifierService
7
+ from .sta.sensor import SensorService
8
+ from .sta.datastream import DatastreamService
@@ -0,0 +1,92 @@
1
+ from typing import TYPE_CHECKING, Type, Union, Optional
2
+ from uuid import UUID
3
+
4
+ if TYPE_CHECKING:
5
+ from hydroserverpy import HydroServer
6
+ from hydroserverpy.api.models.base import HydroServerModel
7
+
8
+
9
+ class EndpointService:
10
+ _model: Type["HydroServerModel"]
11
+ _api_route: str
12
+ _endpoint_route: str
13
+
14
+ def __init__(self, connection: "HydroServer") -> None:
15
+ self._connection = connection
16
+
17
+ def _list(self, params: Optional[dict] = None):
18
+ path = f"/{self._api_route}/{self._endpoint_route}"
19
+
20
+ response = self._connection.request("get", path, params=params).json()
21
+
22
+ return [
23
+ self._model(
24
+ _connection=self._connection, _uid=UUID(str(obj.pop("id"))), **obj
25
+ )
26
+ for obj in response
27
+ ]
28
+
29
+ def _get(self, uid: Union[UUID, str]):
30
+ path = f"/{self._api_route}/{self._endpoint_route}/{str(uid)}"
31
+ response = self._connection.request("get", path).json()
32
+
33
+ return self._model(
34
+ _connection=self._connection, _uid=UUID(str(response.pop("id"))), **response
35
+ )
36
+
37
+ def _create(self, **kwargs):
38
+ path = f"/{self._api_route}/{self._endpoint_route}"
39
+ headers = {"Content-type": "application/json"}
40
+ response = self._connection.request(
41
+ "post", path, headers=headers, json=kwargs
42
+ ).json()
43
+
44
+ return self._model(
45
+ _connection=self._connection, _uid=UUID(str(response.pop("id"))), **response
46
+ )
47
+
48
+ def _update(self, uid: Union[UUID, str], **kwargs):
49
+ path = f"/{self._api_route}/{self._endpoint_route}/{str(uid)}"
50
+ headers = {"Content-type": "application/json"}
51
+ response = self._connection.request(
52
+ "patch", path, headers=headers, json=kwargs
53
+ ).json()
54
+
55
+ return self._model(
56
+ _connection=self._connection, _uid=UUID(str(response.pop("id"))), **response
57
+ )
58
+
59
+ def _delete(self, uid: Union[UUID, str]):
60
+ path = f"/{self._api_route}/{self._endpoint_route}/{str(uid)}"
61
+ response = self._connection.request("delete", path)
62
+
63
+ return response
64
+
65
+
66
+ class SensorThingsService(EndpointService):
67
+ _sta_route: str
68
+
69
+ def __init__(self, connection) -> None:
70
+ super().__init__(connection)
71
+
72
+ def _list(self, path: Optional[str] = None, params: Optional[dict] = None):
73
+ path = path or f"/{self._sta_route}"
74
+ response = self._connection.request("get", path, params=params)
75
+
76
+ return [
77
+ self._model(_connection=self._connection, _uid=obj.pop("@iot.id"), **obj)
78
+ for obj in response.json()["value"]
79
+ ]
80
+
81
+ def _get(
82
+ self,
83
+ uid: Union[UUID, str],
84
+ path: Optional[str] = None,
85
+ params: Optional[dict] = None,
86
+ ):
87
+ path = path or f"/{self._sta_route}('{str(uid)}')"
88
+ response = self._connection.request("get", path, params=params).json()
89
+
90
+ return self._model(
91
+ _connection=self._connection, _uid=response.pop("@iot.id"), **response
92
+ )
File without changes
File without changes
@@ -0,0 +1,126 @@
1
+ from typing import TYPE_CHECKING, Union, List
2
+ from pydantic import EmailStr
3
+ from uuid import UUID
4
+ from hydroserverpy.api.models import Workspace, Role, Collaborator
5
+ from ..base import EndpointService
6
+
7
+
8
+ if TYPE_CHECKING:
9
+ from hydroserverpy import HydroServer
10
+
11
+
12
+ class WorkspaceService(EndpointService):
13
+ def __init__(self, connection: "HydroServer"):
14
+ self._model = Workspace
15
+ self._api_route = "api/auth"
16
+ self._endpoint_route = "workspaces"
17
+
18
+ super().__init__(connection)
19
+
20
+ def list(self, associated_only: bool = False) -> List["Workspace"]:
21
+ """Fetch a collection of HydroServer resources."""
22
+
23
+ return super()._list(params={"associated_only": associated_only})
24
+
25
+ def get(self, uid: Union[UUID, str]) -> "Workspace":
26
+ """Get a workspace by ID."""
27
+
28
+ return super()._get(uid=str(uid))
29
+
30
+ def create(self, name: str, is_private: bool, **_) -> "Workspace":
31
+ """Create a new workspace."""
32
+
33
+ kwargs = {"name": name, "isPrivate": is_private}
34
+
35
+ return super()._create(**kwargs)
36
+
37
+ def update(
38
+ self, uid: Union[UUID, str], name: str = ..., is_private: bool = ..., **_
39
+ ) -> "Workspace":
40
+ """Update a workspace."""
41
+
42
+ kwargs = {"name": name, "isPrivate": is_private}
43
+
44
+ return super()._update(
45
+ uid=str(uid), **{k: v for k, v in kwargs.items() if v is not ...}
46
+ )
47
+
48
+ def delete(self, uid: Union[UUID, str]) -> None:
49
+ """Delete a workspace."""
50
+
51
+ super()._delete(uid=str(uid))
52
+
53
+ def list_roles(self, uid: Union[UUID, str]) -> List["Role"]:
54
+ """Get all roles that can be assigned within a workspace."""
55
+
56
+ path = f"/{self._api_route}/{self._endpoint_route}/{str(uid)}/roles"
57
+ response = self._connection.request("get", path)
58
+
59
+ return [Role(**obj) for obj in response.json()]
60
+
61
+ def list_collaborators(self, uid: Union[UUID, str]) -> List["Collaborator"]:
62
+ """Get all collaborators associated with a workspace."""
63
+
64
+ path = f"/{self._api_route}/{self._endpoint_route}/{str(uid)}/collaborators"
65
+ response = self._connection.request("get", path)
66
+
67
+ return [
68
+ Collaborator(_connection=self._connection, workspace_id=uid, **obj)
69
+ for obj in response.json()
70
+ ]
71
+
72
+ def add_collaborator(
73
+ self, uid: Union[UUID, str], email: EmailStr, role: Union["Role", UUID, str]
74
+ ) -> "Collaborator":
75
+ """Add a collaborator to a workspace."""
76
+
77
+ path = f"/{self._api_route}/{self._endpoint_route}/{str(uid)}/collaborators"
78
+ response = self._connection.request(
79
+ "post",
80
+ path,
81
+ json={"email": email, "roleId": str(getattr(role, "uid", role))},
82
+ )
83
+
84
+ return Collaborator(
85
+ _connection=self._connection, workspace_id=uid, **response.json()
86
+ )
87
+
88
+ def edit_collaborator_role(
89
+ self, uid: Union[UUID, str], email: EmailStr, role: Union["Role", UUID, str]
90
+ ) -> "Collaborator":
91
+ """Edit the role of a collaborator in a workspace."""
92
+
93
+ path = f"/{self._api_route}/{self._endpoint_route}/{str(uid)}/collaborators"
94
+ response = self._connection.request(
95
+ "put",
96
+ path,
97
+ json={"email": email, "roleId": str(getattr(role, "uid", role))},
98
+ )
99
+
100
+ return Collaborator(
101
+ _connection=self._connection, workspace_id=uid, **response.json()
102
+ )
103
+
104
+ def remove_collaborator(self, uid: Union[UUID, str], email: EmailStr) -> None:
105
+ """Remove a collaborator from a workspace."""
106
+
107
+ path = f"/{self._api_route}/{self._endpoint_route}/{str(uid)}/collaborators"
108
+ self._connection.request("delete", path, json={"email": email})
109
+
110
+ def transfer_ownership(self, uid: Union[UUID, str], email: str) -> None:
111
+ """Transfer ownership of a workspace to another HydroServer user."""
112
+
113
+ path = f"/{self._api_route}/{self._endpoint_route}/{str(uid)}/transfer"
114
+ self._connection.request("post", path, json={"newOwner": email})
115
+
116
+ def accept_ownership_transfer(self, uid: Union[UUID, str]) -> None:
117
+ """Accept ownership transfer of a workspace."""
118
+
119
+ path = f"/{self._api_route}/{self._endpoint_route}/{str(uid)}/transfer"
120
+ self._connection.request("put", path)
121
+
122
+ def cancel_ownership_transfer(self, uid: Union[UUID, str]) -> None:
123
+ """Cancel ownership transfer of a workspace."""
124
+
125
+ path = f"/{self._api_route}/{self._endpoint_route}/{str(uid)}/transfer"
126
+ self._connection.request("delete", path)
File without changes