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
hydroserverpy/__init__.py CHANGED
@@ -1,7 +1,6 @@
1
- from .core.service import HydroServer
2
- from .quality.service import HydroServerQualityControl
1
+ from .api.main import HydroServer
3
2
  from .etl.hydroserver_etl import HydroServerETL
4
-
3
+ from .quality import HydroServerQualityControl
5
4
 
6
5
  __all__ = [
7
6
  "HydroServer",
@@ -0,0 +1,24 @@
1
+ import json
2
+ from requests import HTTPError
3
+
4
+
5
+ def raise_for_hs_status(response):
6
+ """"""
7
+
8
+ try:
9
+ response.raise_for_status()
10
+ except HTTPError as e:
11
+ try:
12
+ http_error_msg = (
13
+ f"{response.status_code} Client Error: "
14
+ f"{str(json.loads(response.content).get('detail'))}"
15
+ )
16
+ except (
17
+ ValueError,
18
+ TypeError,
19
+ ):
20
+ http_error_msg = e
21
+ if 400 <= response.status_code < 500:
22
+ raise HTTPError(http_error_msg, response=response)
23
+ else:
24
+ raise HTTPError(str(e), response=response)
@@ -0,0 +1,152 @@
1
+ import requests
2
+ from typing import Optional, Tuple
3
+ from hydroserverpy.api.http import raise_for_hs_status
4
+ from hydroserverpy.api.services import (
5
+ WorkspaceService,
6
+ ThingService,
7
+ ObservedPropertyService,
8
+ UnitService,
9
+ ProcessingLevelService,
10
+ ResultQualifierService,
11
+ SensorService,
12
+ DatastreamService,
13
+ )
14
+
15
+
16
+ class HydroServer:
17
+ def __init__(
18
+ self,
19
+ host: str,
20
+ email: Optional[str] = None,
21
+ password: Optional[str] = None,
22
+ apikey: Optional[str] = None,
23
+ ):
24
+ self.host = host.strip("/")
25
+ self.auth = (
26
+ (
27
+ email or "__key__",
28
+ password or apikey,
29
+ )
30
+ if (email and password) or apikey
31
+ else None
32
+ )
33
+
34
+ self._auth_url = f"{self.host}/api/auth/app/session"
35
+
36
+ self._session = None
37
+ self._timeout = 60
38
+ self._auth_header = None
39
+
40
+ self._init_session()
41
+
42
+ def login(self, email: str, password: str) -> None:
43
+ """Provide your HydroServer credentials to log in to your account."""
44
+
45
+ self._init_session(auth=(email, password))
46
+
47
+ def logout(self) -> None:
48
+ """End your HydroServer session."""
49
+
50
+ self._session.delete(self._auth_url, timeout=self._timeout)
51
+
52
+ def _init_session(self, auth: Optional[Tuple[str, str]] = None) -> None:
53
+ if self._session is not None:
54
+ self.logout()
55
+ self._session.close()
56
+
57
+ self._session = requests.Session()
58
+
59
+ auth = auth or self.auth
60
+
61
+ if auth and auth[0] == "__key__":
62
+ self._session.headers.update({"key": auth[1]})
63
+ elif auth:
64
+ self._session.headers.update(
65
+ {"Authorization": f"Bearer {self._authenticate(auth[0], auth[1])}"}
66
+ )
67
+
68
+ def _authenticate(self, email: str, password: str) -> None:
69
+ response = self._session.post(
70
+ self._auth_url,
71
+ json={"email": email, "password": password},
72
+ timeout=self._timeout,
73
+ )
74
+ response.raise_for_status()
75
+ session_token = response.json().get("meta", {}).get("session_token")
76
+
77
+ if not session_token:
78
+ raise ValueError("Authentication failed: No access token returned.")
79
+
80
+ return session_token
81
+
82
+ def request(self, method, path, *args, **kwargs) -> requests.Response:
83
+ """Sends a request to HydroServer's API."""
84
+
85
+ for attempt in range(2):
86
+ try:
87
+ response = getattr(self._session, method)(
88
+ f"{self.host}/{path.strip('/')}",
89
+ timeout=self._timeout,
90
+ *args,
91
+ **kwargs,
92
+ )
93
+ raise_for_hs_status(response)
94
+ except (
95
+ requests.exceptions.HTTPError,
96
+ requests.exceptions.ConnectionError,
97
+ ) as e:
98
+ if attempt == 0:
99
+ self._init_session()
100
+ continue
101
+ else:
102
+ raise e
103
+
104
+ return response
105
+
106
+ @property
107
+ def workspaces(self):
108
+ """Utilities for managing HydroServer workspaces."""
109
+
110
+ return WorkspaceService(self)
111
+
112
+ @property
113
+ def things(self):
114
+ """Utilities for managing HydroServer things."""
115
+
116
+ return ThingService(self)
117
+
118
+ @property
119
+ def observedproperties(self):
120
+ """Utilities for managing HydroServer observed properties."""
121
+
122
+ return ObservedPropertyService(self)
123
+
124
+ @property
125
+ def units(self):
126
+ """Utilities for managing HydroServer units."""
127
+
128
+ return UnitService(self)
129
+
130
+ @property
131
+ def processinglevels(self):
132
+ """Utilities for managing HydroServer processing levels."""
133
+
134
+ return ProcessingLevelService(self)
135
+
136
+ @property
137
+ def resultqualifiers(self):
138
+ """Utilities for managing HydroServer result qualifiers."""
139
+
140
+ return ResultQualifierService(self)
141
+
142
+ @property
143
+ def sensors(self):
144
+ """Utilities for managing HydroServer sensors."""
145
+
146
+ return SensorService(self)
147
+
148
+ @property
149
+ def datastreams(self):
150
+ """Utilities for managing HydroServer datastreams."""
151
+
152
+ return DatastreamService(self)
@@ -0,0 +1,18 @@
1
+ from .iam.account import Account
2
+ from .iam.workspace import Workspace
3
+ from .iam.role import Role
4
+ from .iam.collaborator import Collaborator
5
+ from .iam.account import Account
6
+ from .sta.datastream import Datastream
7
+ from .sta.observed_property import ObservedProperty
8
+ from .sta.processing_level import ProcessingLevel
9
+ from .sta.result_qualifier import ResultQualifier
10
+ from .sta.sensor import Sensor
11
+ from .sta.thing import Thing
12
+ from .sta.unit import Unit
13
+
14
+ Workspace.model_rebuild()
15
+ Role.model_rebuild()
16
+ Collaborator.model_rebuild()
17
+
18
+ Unit.model_rebuild()
@@ -0,0 +1,74 @@
1
+ from typing import Optional
2
+ from uuid import UUID
3
+ from pydantic import BaseModel, PrivateAttr, ConfigDict, computed_field
4
+ from pydantic.alias_generators import to_camel
5
+
6
+
7
+ class HydroServerBaseModel(BaseModel):
8
+ _uid: Optional[UUID] = PrivateAttr()
9
+
10
+ def __init__(self, _uid: Optional[UUID] = None, **data):
11
+ super().__init__(**data)
12
+ self._uid = _uid
13
+
14
+ @computed_field
15
+ @property
16
+ def uid(self) -> Optional[UUID]:
17
+ """The unique identifier for this resource."""
18
+
19
+ return self._uid
20
+
21
+ model_config = ConfigDict(
22
+ validate_assignment=True,
23
+ populate_by_name=True,
24
+ str_strip_whitespace=True,
25
+ alias_generator=to_camel,
26
+ )
27
+
28
+
29
+ class HydroServerModel(HydroServerBaseModel):
30
+ _model_ref: str = PrivateAttr()
31
+ _original_data: Optional[dict] = PrivateAttr()
32
+
33
+ def __init__(self, _connection, _model_ref, _uid: Optional[UUID] = None, **data):
34
+ if isinstance(_uid, str):
35
+ _uid = UUID(_uid)
36
+
37
+ super().__init__(_uid=_uid, **data)
38
+
39
+ self._connection = _connection
40
+ self._model_ref = _model_ref
41
+ self._original_data = self.dict(by_alias=False).copy()
42
+
43
+ @property
44
+ def _patch_data(self) -> dict:
45
+ return {
46
+ key: getattr(self, key)
47
+ for key, value in self._original_data.items()
48
+ if hasattr(self, key) and getattr(self, key) != value
49
+ }
50
+
51
+ def _refresh(self) -> None:
52
+ """Refresh this resource from HydroServer."""
53
+
54
+ self._original_data = (
55
+ getattr(self._connection, self._model_ref)
56
+ .get(uid=self.uid)
57
+ .model_dump(exclude=["uid"])
58
+ )
59
+ self.__dict__.update(self._original_data)
60
+
61
+ def _save(self) -> None:
62
+ if self._patch_data:
63
+ entity = getattr(self._connection, self._model_ref).update(
64
+ uid=self.uid, **self._patch_data
65
+ )
66
+ self._original_data = entity.dict(by_alias=False, exclude=["uid"])
67
+ self.__dict__.update(self._original_data)
68
+
69
+ def _delete(self) -> None:
70
+ if not self._uid:
71
+ raise AttributeError("This resource cannot be deleted: UID is not set.")
72
+
73
+ getattr(self._connection, self._model_ref).delete(uid=self._uid)
74
+ self._uid = None
File without changes
File without changes
@@ -0,0 +1,12 @@
1
+ from typing import Optional
2
+ from pydantic import BaseModel, Field, EmailStr
3
+
4
+
5
+ class Account(BaseModel):
6
+ name: str = Field(..., max_length=255)
7
+ email: EmailStr
8
+ organization_name: Optional[str] = None
9
+ phone: Optional[str] = Field(None, max_length=15)
10
+ address: Optional[str] = Field(None, max_length=255)
11
+ link: Optional[str] = Field(None, max_length=2000)
12
+ user_type: str = Field(..., max_length=255, alias="type")
@@ -0,0 +1,34 @@
1
+ from typing import Union, TYPE_CHECKING
2
+ from uuid import UUID
3
+ from pydantic import BaseModel
4
+
5
+ if TYPE_CHECKING:
6
+ from hydroserverpy.api.models.iam.account import Account
7
+ from hydroserverpy.api.models.iam.role import Role
8
+
9
+
10
+ class CollaboratorFields(BaseModel):
11
+ user: "Account"
12
+ role: "Role"
13
+ workspace_id: Union[UUID, str]
14
+
15
+
16
+ class Collaborator(CollaboratorFields):
17
+ def __init__(self, _connection, **data):
18
+ super().__init__(**data)
19
+ self._connection = _connection
20
+
21
+ def edit_role(self, role: Union["Role", UUID, str]):
22
+ """Edit the role of this workspace collaborator."""
23
+
24
+ response = self._connection.workspaces.edit_collaborator_role(
25
+ uid=self.workspace_id, email=self.user.email, role=role
26
+ )
27
+ self.role = response.role
28
+
29
+ def remove(self):
30
+ """Remove this collaborator from the workspace."""
31
+
32
+ self._connection.workspaces.remove_collaborator(
33
+ uid=self.workspace_id, email=self.user.email
34
+ )
@@ -0,0 +1,10 @@
1
+ from typing import Optional, Union
2
+ from uuid import UUID
3
+ from pydantic import BaseModel, Field
4
+
5
+
6
+ class Role(BaseModel):
7
+ uid: UUID = Field(..., alias="id")
8
+ name: str = Field(..., max_length=255)
9
+ description: str
10
+ workspace_id: Optional[Union[UUID, str]] = None
@@ -0,0 +1,203 @@
1
+ from typing import List, Union, Optional, TYPE_CHECKING
2
+ from uuid import UUID
3
+ from pydantic import BaseModel, Field, EmailStr
4
+ from ..base import HydroServerModel
5
+
6
+ if TYPE_CHECKING:
7
+ from hydroserverpy import HydroServer
8
+ from hydroserverpy.api.models import (
9
+ Role,
10
+ Collaborator,
11
+ Account,
12
+ Thing,
13
+ ObservedProperty,
14
+ Sensor,
15
+ Unit,
16
+ ProcessingLevel,
17
+ ResultQualifier,
18
+ Datastream,
19
+ )
20
+
21
+
22
+ class WorkspaceFields(BaseModel):
23
+ name: str = Field(..., max_length=255)
24
+ is_private: bool
25
+ owner: "Account" = Field(..., json_schema_extra={"read_only": True})
26
+ collaborator_role: Optional["Role"] = None
27
+ pending_transfer_to: Optional["Account"] = None
28
+
29
+
30
+ class Workspace(HydroServerModel, WorkspaceFields):
31
+ def __init__(self, _connection: "HydroServer", _uid: Union[UUID, str], **data):
32
+ super().__init__(
33
+ _connection=_connection, _model_ref="workspaces", _uid=_uid, **data
34
+ )
35
+ self._roles = None
36
+ self._collaborators = None
37
+ self._things = None
38
+ self._observedproperties = None
39
+ self._processinglevels = None
40
+ self._resultqualifiers = None
41
+ self._units = None
42
+ self._sensors = None
43
+ self._datastreams = None
44
+
45
+ @property
46
+ def roles(self) -> List["Role"]:
47
+ """The roles that can be assigned for this workspace."""
48
+
49
+ if self._roles is None:
50
+ self._roles = self._connection.workspaces.list_roles(uid=self.uid)
51
+
52
+ return self._roles
53
+
54
+ @property
55
+ def collaborators(self) -> List["Collaborator"]:
56
+ """The collaborators associated with this workspace."""
57
+
58
+ if self._collaborators is None:
59
+ self._collaborators = self._connection.workspaces.list_collaborators(
60
+ uid=self.uid
61
+ )
62
+
63
+ return self._collaborators
64
+
65
+ @property
66
+ def things(self) -> List["Thing"]:
67
+ """The things associated with this workspace."""
68
+
69
+ if self._things is None:
70
+ self._things = self._connection.things.list(workspace=self.uid)
71
+
72
+ return self._things
73
+
74
+ @property
75
+ def observedproperties(self) -> List["ObservedProperty"]:
76
+ """The observed properties associated with this workspace."""
77
+
78
+ if self._observedproperties is None:
79
+ self._observedproperties = self._connection.observedproperties.list(
80
+ workspace=self.uid
81
+ )
82
+
83
+ return self._observedproperties
84
+
85
+ @property
86
+ def processinglevels(self) -> List["ProcessingLevel"]:
87
+ """The processing levels associated with this workspace."""
88
+
89
+ if self._processinglevels is None:
90
+ self._processinglevels = self._connection.processinglevels.list(
91
+ workspace=self.uid
92
+ )
93
+
94
+ return self._processinglevels
95
+
96
+ @property
97
+ def resultqualifiers(self) -> List["ResultQualifier"]:
98
+ """The result qualifiers associated with this workspace."""
99
+
100
+ if self._resultqualifiers is None:
101
+ self._resultqualifiers = self._connection.resultqualifiers.list(
102
+ workspace=self.uid
103
+ )
104
+
105
+ return self._resultqualifiers
106
+
107
+ @property
108
+ def units(self) -> List["Unit"]:
109
+ """The units associated with this workspace."""
110
+
111
+ if self._units is None:
112
+ self._units = self._connection.units.list(workspace=self.uid)
113
+
114
+ return self._units
115
+
116
+ @property
117
+ def sensors(self) -> List["Sensor"]:
118
+ """The sensors associated with this workspace."""
119
+
120
+ if self._sensors is None:
121
+ self._sensors = self._connection.sensors.list(workspace=self.uid)
122
+
123
+ return self._sensors
124
+
125
+ @property
126
+ def datastreams(self) -> List["Datastream"]:
127
+ """The datastreams associated with this workspace."""
128
+
129
+ if self._datastreams is None:
130
+ self._datastreams = self._connection.datastreams.list(workspace=self.uid)
131
+
132
+ return self._datastreams
133
+
134
+ def refresh(self) -> None:
135
+ """Refresh the workspace details from HydroServer."""
136
+
137
+ self._roles = None
138
+ self._collaborators = None
139
+ self._things = None
140
+ self._observedproperties = None
141
+ self._processinglevels = None
142
+ self._units = None
143
+ self._sensors = None
144
+ self._datastreams = None
145
+ super()._refresh()
146
+
147
+ def save(self):
148
+ """Save changes to this workspace to HydroServer."""
149
+
150
+ super()._save()
151
+
152
+ def delete(self):
153
+ """Delete this workspace from HydroServer."""
154
+
155
+ super()._delete()
156
+
157
+ def add_collaborator(
158
+ self, email: EmailStr, role: Union["Role", UUID, str]
159
+ ) -> "Collaborator":
160
+ """Add a new collaborator to the workspace."""
161
+
162
+ response = self._connection.workspaces.add_collaborator(
163
+ uid=self.uid, email=email, role=role
164
+ )
165
+ self._collaborators = None
166
+
167
+ return response
168
+
169
+ def edit_collaborator_role(
170
+ self, email: EmailStr, role: Union["Role", UUID, str]
171
+ ) -> "Collaborator":
172
+ """Edit a collaborator's role in this workspace."""
173
+
174
+ response = self._connection.workspaces.edit_collaborator_role(
175
+ uid=self.uid, email=email, role=role
176
+ )
177
+ self._collaborators = None
178
+
179
+ return response
180
+
181
+ def remove_collaborator(self, email: EmailStr) -> None:
182
+ """Remove a collaborator from the workspace."""
183
+
184
+ self._connection.workspaces.remove_collaborator(uid=self.uid, email=email)
185
+ self._collaborators = None
186
+
187
+ def transfer_ownership(self, email: EmailStr) -> None:
188
+ """Transfer ownership of this workspace to another HydroServer user."""
189
+
190
+ self._connection.workspaces.transfer_ownership(uid=self.uid, email=email)
191
+ self.refresh()
192
+
193
+ def accept_ownership_transfer(self) -> None:
194
+ """Accept ownership transfer of this workspace."""
195
+
196
+ self._connection.workspaces.accept_ownership_transfer(uid=self.uid)
197
+ self.refresh()
198
+
199
+ def cancel_ownership_transfer(self) -> None:
200
+ """Cancel ownership transfer of this workspace."""
201
+
202
+ self._connection.workspaces.cancel_ownership_transfer(uid=self.uid)
203
+ self.refresh()
File without changes