hydroserverpy 1.2.0__py3-none-any.whl → 1.3.0__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 (47) hide show
  1. hydroserverpy/__init__.py +1 -1
  2. hydroserverpy/api/{main.py → client.py} +52 -22
  3. hydroserverpy/api/models/__init__.py +1 -2
  4. hydroserverpy/api/models/base.py +180 -47
  5. hydroserverpy/api/models/etl/data_archive.py +31 -59
  6. hydroserverpy/api/models/etl/data_source.py +34 -76
  7. hydroserverpy/api/models/etl/orchestration_system.py +23 -38
  8. hydroserverpy/api/models/iam/apikey.py +57 -38
  9. hydroserverpy/api/models/iam/collaborator.py +55 -19
  10. hydroserverpy/api/models/iam/role.py +32 -4
  11. hydroserverpy/api/models/iam/workspace.py +58 -86
  12. hydroserverpy/api/models/sta/datastream.py +122 -214
  13. hydroserverpy/api/models/sta/observation.py +101 -0
  14. hydroserverpy/api/models/sta/observed_property.py +18 -53
  15. hydroserverpy/api/models/sta/processing_level.py +16 -31
  16. hydroserverpy/api/models/sta/result_qualifier.py +16 -31
  17. hydroserverpy/api/models/sta/sensor.py +27 -88
  18. hydroserverpy/api/models/sta/thing.py +48 -152
  19. hydroserverpy/api/models/sta/unit.py +16 -29
  20. hydroserverpy/api/services/__init__.py +1 -0
  21. hydroserverpy/api/services/base.py +92 -76
  22. hydroserverpy/api/services/etl/data_archive.py +42 -72
  23. hydroserverpy/api/services/etl/data_source.py +42 -72
  24. hydroserverpy/api/services/etl/orchestration_system.py +25 -33
  25. hydroserverpy/api/services/iam/role.py +38 -0
  26. hydroserverpy/api/services/iam/workspace.py +96 -99
  27. hydroserverpy/api/services/sta/datastream.py +150 -211
  28. hydroserverpy/api/services/sta/observed_property.py +31 -49
  29. hydroserverpy/api/services/sta/processing_level.py +30 -36
  30. hydroserverpy/api/services/sta/result_qualifier.py +24 -34
  31. hydroserverpy/api/services/sta/sensor.py +34 -48
  32. hydroserverpy/api/services/sta/thing.py +96 -89
  33. hydroserverpy/api/services/sta/unit.py +30 -34
  34. hydroserverpy/api/utils.py +22 -0
  35. hydroserverpy/etl/extractors/base.py +3 -5
  36. hydroserverpy/etl/loaders/hydroserver_loader.py +1 -0
  37. hydroserverpy/etl/timestamp_parser.py +82 -48
  38. hydroserverpy/etl/transformers/base.py +6 -10
  39. hydroserverpy/etl_csv/hydroserver_etl_csv.py +18 -24
  40. {hydroserverpy-1.2.0.dist-info → hydroserverpy-1.3.0.dist-info}/METADATA +1 -1
  41. hydroserverpy-1.3.0.dist-info/RECORD +70 -0
  42. hydroserverpy/api/http.py +0 -22
  43. hydroserverpy-1.2.0.dist-info/RECORD +0 -68
  44. {hydroserverpy-1.2.0.dist-info → hydroserverpy-1.3.0.dist-info}/WHEEL +0 -0
  45. {hydroserverpy-1.2.0.dist-info → hydroserverpy-1.3.0.dist-info}/licenses/LICENSE +0 -0
  46. {hydroserverpy-1.2.0.dist-info → hydroserverpy-1.3.0.dist-info}/top_level.txt +0 -0
  47. {hydroserverpy-1.2.0.dist-info → hydroserverpy-1.3.0.dist-info}/zip-safe +0 -0
@@ -1,60 +1,49 @@
1
- import requests
1
+ import uuid
2
2
  import tempfile
3
- from typing import Union, List, Optional, TYPE_CHECKING
4
- from uuid import UUID
5
- from pydantic import BaseModel, Field
3
+ import requests
4
+ from typing import Union, ClassVar, Optional, TYPE_CHECKING, List
5
+ from pydantic import Field
6
6
  from hydroserverpy.etl_csv.hydroserver_etl_csv import HydroServerETLCSV
7
7
  from .orchestration_system import OrchestrationSystem
8
8
  from .orchestration_configuration import OrchestrationConfigurationFields
9
9
  from ..sta.datastream import Datastream
10
- from ..base import HydroServerModel
10
+ from ..base import HydroServerBaseModel
11
11
 
12
12
  if TYPE_CHECKING:
13
13
  from hydroserverpy import HydroServer
14
14
  from hydroserverpy.api.models import Workspace
15
15
 
16
16
 
17
- class DataSourceFields(BaseModel):
17
+ class DataSource(
18
+ HydroServerBaseModel, OrchestrationConfigurationFields
19
+ ):
18
20
  name: str = Field(..., max_length=255)
19
21
  settings: Optional[dict] = None
22
+ orchestration_system_id: uuid.UUID
23
+ workspace_id: uuid.UUID
20
24
 
25
+ _editable_fields: ClassVar[set[str]] = {
26
+ "name", "settings", "interval", "interval_units", "crontab", "start_time", "end_time", "last_run_successful",
27
+ "last_run_message", "last_run", "next_run", "paused"
28
+ }
21
29
 
22
- class DataSource(HydroServerModel, DataSourceFields, OrchestrationConfigurationFields):
23
- def __init__(self, _connection: "HydroServer", _uid: Union[UUID, str], **data):
24
- super().__init__(
25
- _connection=_connection, _model_ref="datasources", _uid=_uid, **data
26
- )
27
-
28
- self._workspace_id = str(data.get("workspace_id") or data["workspaceId"])
29
- self._orchestration_system_id = str(
30
- data.get("orchestration_system_id") or data["orchestrationSystem"]["id"]
31
- )
30
+ def __init__(self, client: "HydroServer", **data):
31
+ super().__init__(client=client, service=client.datasources, **data)
32
32
 
33
33
  self._workspace = None
34
+ self._orchestration_system = None
35
+ self._datastreams = None
34
36
 
35
- if data.get("orchestrationSystem"):
36
- self._orchestration_system = OrchestrationSystem(
37
- _connection=_connection,
38
- _uid=self._orchestration_system_id,
39
- **data["orchestrationSystem"]
40
- )
41
- else:
42
- self._orchestration_system = None
43
-
44
- if data.get("datastreams"):
45
- self._datastreams = [
46
- Datastream(_connection=_connection, _uid=datastream["id"], **datastream)
47
- for datastream in data["datastreams"]
48
- ]
49
- else:
50
- self._datastreams = []
37
+ @classmethod
38
+ def get_route(cls):
39
+ return "data-sources"
51
40
 
52
41
  @property
53
42
  def workspace(self) -> "Workspace":
54
43
  """The workspace this data source belongs to."""
55
44
 
56
- if self._workspace is None and self._workspace_id:
57
- self._workspace = self._connection.workspaces.get(uid=self._workspace_id)
45
+ if self._workspace is None:
46
+ self._workspace = self.client.workspaces.get(uid=self.workspace_id)
58
47
 
59
48
  return self._workspace
60
49
 
@@ -62,62 +51,31 @@ class DataSource(HydroServerModel, DataSourceFields, OrchestrationConfigurationF
62
51
  def orchestration_system(self) -> "OrchestrationSystem":
63
52
  """The orchestration system that manages this data source."""
64
53
 
65
- if self._orchestration_system is None and self._orchestration_system_id:
66
- self._orchestration_system = self._connection.orchestration_systems.get(
67
- uid=self._orchestration_system_id
68
- )
54
+ if self._orchestration_system is None:
55
+ self._orchestration_system = self.client.orchestrationsystems.get(uid=self.orchestration_system_id)
69
56
 
70
57
  return self._orchestration_system
71
58
 
72
- @orchestration_system.setter
73
- def orchestration_system(
74
- self, orchestration_system: Union["OrchestrationSystem", UUID, str]
75
- ):
76
- if not orchestration_system:
77
- raise ValueError("Orchestration system of data source cannot be None.")
78
- if str(getattr(orchestration_system, "uid", orchestration_system)) != str(
79
- self.orchestration_system.uid
80
- ):
81
- self._orchestration_system = self._connection.orchestrationsystems.get(
82
- uid=str(getattr(orchestration_system, "uid", orchestration_system))
83
- )
84
-
85
59
  @property
86
60
  def datastreams(self) -> List["Datastream"]:
87
61
  """The datastreams this data source provides data for."""
88
62
 
89
63
  if self._datastreams is None:
90
- data_source = self._connection.datasources.get(uid=self.uid)
91
- self._datastreams = data_source.datastreams
64
+ self._datastreams = self.client.datastreams.list(data_source=self.uid, fetch_all=True).items
92
65
 
93
66
  return self._datastreams
94
67
 
95
- def refresh(self):
96
- """Refresh this data source from HydroServer."""
97
-
98
- super()._refresh()
99
- self._workspace = None
100
- self._datastreams = None
101
-
102
- def save(self):
103
- """Save changes to this data source to HydroServer."""
104
-
105
- super()._save()
106
-
107
- def delete(self):
108
- """Delete this data source from HydroServer."""
109
-
110
- super()._delete()
111
-
112
- def add_datastream(self, datastream: Union["Datastream", UUID, str]):
68
+ def add_datastream(self, datastream: Union["Datastream", uuid.UUID, str]):
113
69
  """Add a datastream to this data source."""
114
70
 
115
- self._connection.datasources.add_datastream(uid=self.uid, datastream=datastream)
71
+ self.client.datasources.add_datastream(
72
+ uid=self.uid, datastream=datastream
73
+ )
116
74
 
117
- def remove_datastream(self, datastream: Union["Datastream", UUID, str]):
75
+ def remove_datastream(self, datastream: Union["Datastream", uuid.UUID, str]):
118
76
  """Remove a datastream from this data source."""
119
77
 
120
- self._connection.datasources.remove_datastream(
78
+ self.client.datasources.remove_datastream(
121
79
  uid=self.uid, datastream=datastream
122
80
  )
123
81
 
@@ -131,7 +89,7 @@ class DataSource(HydroServerModel, DataSourceFields, OrchestrationConfigurationF
131
89
  if self.settings["extractor"]["type"] == "local":
132
90
  with open(self.settings["extractor"]["sourceUri"]) as data_file:
133
91
  loader = HydroServerETLCSV(
134
- self._connection, data_file=data_file, data_source=self
92
+ self.client, data_file=data_file, data_source=self
135
93
  )
136
94
  loader.run()
137
95
  elif self.settings["extractor"]["type"] == "HTTP":
@@ -148,6 +106,6 @@ class DataSource(HydroServerModel, DataSourceFields, OrchestrationConfigurationF
148
106
  temp_file.write(chunk.decode("utf-8"))
149
107
  temp_file.seek(0)
150
108
  loader = HydroServerETLCSV(
151
- self._connection, data_file=temp_file, data_source=self
109
+ self.client, data_file=temp_file, data_source=self
152
110
  )
153
111
  loader.run()
@@ -1,7 +1,7 @@
1
- from typing import Union, List, TYPE_CHECKING
2
- from uuid import UUID
1
+ import uuid
2
+ from typing import Optional, ClassVar, List, TYPE_CHECKING
3
3
  from pydantic import BaseModel, Field
4
- from ..base import HydroServerModel
4
+ from ..base import HydroServerBaseModel
5
5
 
6
6
  if TYPE_CHECKING:
7
7
  from hydroserverpy import HydroServer
@@ -13,27 +13,30 @@ class OrchestrationSystemFields(BaseModel):
13
13
  orchestration_system_type: str = Field(..., max_length=255, alias="type")
14
14
 
15
15
 
16
- class OrchestrationSystem(HydroServerModel, OrchestrationSystemFields):
17
- def __init__(self, _connection: "HydroServer", _uid: Union[UUID, str], **data):
18
- super().__init__(
19
- _connection=_connection,
20
- _model_ref="orchestrationsystems",
21
- _uid=_uid,
22
- **data
23
- )
16
+ class OrchestrationSystem(HydroServerBaseModel):
17
+ name: str = Field(..., max_length=255)
18
+ orchestration_system_type: str = Field(..., max_length=255, alias="type")
19
+ workspace_id: Optional[uuid.UUID] = None
24
20
 
25
- self._workspace_id = str(data.get("workspace_id") or data["workspaceId"])
21
+ _editable_fields: ClassVar[set[str]] = {"name", "orchestration_system_type"}
22
+
23
+ def __init__(self, client: "HydroServer", **data):
24
+ super().__init__(client=client, service=client.orchestrationsystems, **data)
26
25
 
27
26
  self._workspace = None
28
27
  self._datasources = None
29
28
  self._dataarchives = None
30
29
 
30
+ @classmethod
31
+ def get_route(cls):
32
+ return "orchestration-systems"
33
+
31
34
  @property
32
35
  def workspace(self) -> "Workspace":
33
36
  """The workspace this orchestration system belongs to."""
34
37
 
35
- if self._workspace is None and self._workspace_id:
36
- self._workspace = self._connection.workspaces.get(uid=self._workspace_id)
38
+ if self._workspace is None and self.workspace_id:
39
+ self._workspace = self.client.workspaces.get(uid=self.workspace_id)
37
40
 
38
41
  return self._workspace
39
42
 
@@ -42,9 +45,9 @@ class OrchestrationSystem(HydroServerModel, OrchestrationSystemFields):
42
45
  """The data sources associated with this workspace."""
43
46
 
44
47
  if self._datasources is None:
45
- self._datasources = self._connection.datasources.list(
46
- orchestration_system=self.uid
47
- )
48
+ self._datasources = self.client.datasources.list(
49
+ orchestration_system=self.uid, fetch_all=True
50
+ ).items
48
51
 
49
52
  return self._datasources
50
53
 
@@ -53,26 +56,8 @@ class OrchestrationSystem(HydroServerModel, OrchestrationSystemFields):
53
56
  """The data archives associated with this workspace."""
54
57
 
55
58
  if self._dataarchives is None:
56
- self._dataarchives = self._connection.dataarchives.list(
57
- orchestration_system=self.uid
58
- )
59
+ self._dataarchives = self.client.dataarchives.list(
60
+ orchestration_system=self.uid, fetch_all=True
61
+ ).items
59
62
 
60
63
  return self._dataarchives
61
-
62
- def refresh(self):
63
- """Refresh this orchestration system from HydroServer."""
64
-
65
- super()._refresh()
66
- self._workspace = None
67
- self._datasources = None
68
- self._dataarchives = None
69
-
70
- def save(self):
71
- """Save changes to this orchestration system to HydroServer."""
72
-
73
- super()._save()
74
-
75
- def delete(self):
76
- """Delete this orchestration system from HydroServer."""
77
-
78
- super()._delete()
@@ -1,77 +1,96 @@
1
- from typing import Optional, Union, TYPE_CHECKING
1
+ from typing import Optional, Union, ClassVar, TYPE_CHECKING
2
2
  from uuid import UUID
3
3
  from datetime import datetime
4
- from pydantic import BaseModel
5
- from ..base import HydroServerModel
4
+ from hydroserverpy.api.models.iam.role import Role
5
+ from hydroserverpy.api.utils import normalize_uuid
6
+ from ..base import HydroServerBaseModel
6
7
 
7
8
  if TYPE_CHECKING:
8
9
  from hydroserverpy import HydroServer
9
- from hydroserverpy.api.models import Workspace, Role
10
+ from hydroserverpy.api.models import Workspace
10
11
 
11
12
 
12
- class APIKeyFields(BaseModel):
13
+ class APIKey(HydroServerBaseModel):
13
14
  name: str
15
+ role_id: Union[UUID, str]
16
+ workspace_id: Union[UUID, str]
14
17
  description: Optional[str] = None
15
18
  is_active: bool
16
19
  expires_at: Optional[datetime] = None
17
- role: "Role"
18
20
 
21
+ _editable_fields: ClassVar[set[str]] = {"name", "description", "role_id", "is_active", "expires_at"}
19
22
 
20
- class APIKey(HydroServerModel, APIKeyFields):
21
- def __init__(self, _connection: "HydroServer", _uid: Union[UUID, str], **data):
22
- super().__init__(
23
- _connection=_connection, _model_ref="apikeys", _uid=_uid, **data
24
- )
23
+ def __init__(self, client: "HydroServer", **data):
24
+ super().__init__(client=client, service=None, **data)
25
25
 
26
- self._workspace_id = str(data.get("workspace_id") or data["workspaceId"])
27
26
  self._workspace = None
27
+ self._role = None
28
28
 
29
29
  @property
30
30
  def workspace(self) -> "Workspace":
31
- """The workspace this data source belongs to."""
31
+ """The workspace this API key belongs to."""
32
32
 
33
- if self._workspace is None and self._workspace_id:
34
- self._workspace = self._connection.workspaces.get(uid=self._workspace_id)
33
+ if self._workspace is None:
34
+ self._workspace = self.client.workspaces.get(uid=self.workspace_id)
35
35
 
36
36
  return self._workspace
37
37
 
38
- def refresh(self):
39
- """Refresh this data source from HydroServer."""
38
+ @property
39
+ def role(self) -> "Role":
40
+ """The role this API key is assigned."""
40
41
 
41
- self._original_data = (
42
- self._connection.workspaces.get_api_key(
43
- uid=self._workspace_id, api_key_id=self.uid
44
- ).model_dump(exclude=["uid"])
45
- )
46
- self.__dict__.update(self._original_data)
47
- self._workspace = None
42
+ if self._role is None:
43
+ self._role = self.client.roles.get(uid=self.role_id)
44
+
45
+ return self._role
46
+
47
+ @role.setter
48
+ def role(self, role: Union["Role", UUID, str] = ...):
49
+ if not role:
50
+ raise ValueError("Role of API key cannot be None.")
51
+ if normalize_uuid(role) != str(self.role_id):
52
+ self.role_id = normalize_uuid(role)
53
+ self._role = None
48
54
 
49
55
  def save(self):
50
- """Save changes to this data source to HydroServer."""
56
+ """Saves changes to this resource to HydroServer."""
57
+
58
+ if not self.uid:
59
+ raise AttributeError("Data cannot be saved: UID is not set.")
51
60
 
52
- if self._patch_data:
53
- api_key = self._connection.workspaces.update_api_key(
54
- uid=self._workspace_id, api_key_id=self.uid, **self._patch_data
61
+ if self.unsaved_changes:
62
+ saved_resource = self.client.workspaces.update_api_key(
63
+ uid=self.workspace_id, api_key_id=self.uid, **self.unsaved_changes
55
64
  )
56
- self._original_data = api_key.dict(by_alias=False, exclude=["uid"])
57
- self.__dict__.update(self._original_data)
65
+ self._server_data = saved_resource.dict(by_alias=False).copy()
66
+ self.__dict__.update(saved_resource.__dict__)
67
+
68
+ def refresh(self):
69
+ """Refreshes this resource from HydroServer."""
70
+
71
+ if self.uid is None:
72
+ raise ValueError("Cannot refresh data without a valid ID.")
73
+
74
+ refreshed_resource = self.client.workspaces.get_api_key(uid=self.workspace_id, api_key_id=self.uid)
75
+ self._server_data = refreshed_resource.dict(by_alias=False).copy()
76
+ self.__dict__.update(refreshed_resource.__dict__)
58
77
 
59
78
  def delete(self):
60
- """Delete this data source from HydroServer."""
79
+ """Deletes this resource from HydroServer."""
61
80
 
62
- if not self._uid:
63
- raise AttributeError("This resource cannot be deleted: UID is not set.")
81
+ if self.uid is None:
82
+ raise AttributeError("Cannot delete data without a valid ID.")
64
83
 
65
- self._connection.workspaces.delete_api_key(
66
- uid=self._workspace_id, api_key_id=self.uid
84
+ self.client.workspaces.delete_api_key(
85
+ uid=self.workspace_id, api_key_id=self.uid
67
86
  )
68
- self._uid = None
87
+ self.uid = None
69
88
 
70
89
  def regenerate(self):
71
90
  """Regenerates this API key. WARNING: Previous key will be invalidated."""
72
91
 
73
- _, key = self._connection.workspaces.regenerate_api_key(
74
- uid=self._workspace_id, api_key_id=self.uid
92
+ _, key = self.client.workspaces.regenerate_api_key(
93
+ uid=self.workspace_id, api_key_id=self.uid
75
94
  )
76
95
 
77
96
  return key
@@ -1,34 +1,70 @@
1
- from typing import Union, TYPE_CHECKING
1
+ from typing import Union, ClassVar, TYPE_CHECKING
2
2
  from uuid import UUID
3
- from pydantic import BaseModel
3
+ from pydantic import Field, AliasPath
4
+ from hydroserverpy.api.models.iam.role import Role
5
+ from hydroserverpy.api.utils import normalize_uuid
6
+ from ..base import HydroServerBaseModel
4
7
 
5
8
  if TYPE_CHECKING:
9
+ from hydroserverpy import HydroServer
10
+ from hydroserverpy.api.models.iam.workspace import Workspace
6
11
  from hydroserverpy.api.models.iam.account import Account
7
- from hydroserverpy.api.models.iam.role import Role
8
12
 
9
13
 
10
- class CollaboratorFields(BaseModel):
14
+ class Collaborator(HydroServerBaseModel):
11
15
  user: "Account"
12
- role: "Role"
16
+ role_id: Union[UUID, str] = Field(..., validation_alias=AliasPath("role", "id"))
13
17
  workspace_id: Union[UUID, str]
14
18
 
19
+ _editable_fields: ClassVar[set[str]] = {"role_id"}
15
20
 
16
- class Collaborator(CollaboratorFields):
17
- def __init__(self, _connection, **data):
18
- super().__init__(**data)
19
- self._connection = _connection
21
+ def __init__(self, client: "HydroServer", **data):
22
+ super().__init__(client=client, service=None, **data)
20
23
 
21
- def edit_role(self, role: Union["Role", UUID, str]):
22
- """Edit the role of this workspace collaborator."""
24
+ self._workspace = None
25
+ self._role = Role(client=client, **data.get("role"))
23
26
 
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
27
+ @property
28
+ def workspace(self) -> "Workspace":
29
+ """The workspace this collaborator belongs to."""
30
+
31
+ if self._workspace is None:
32
+ self._workspace = self.client.workspaces.get(uid=self.workspace_id)
33
+
34
+ return self._workspace
35
+
36
+ @property
37
+ def role(self) -> "Role":
38
+ """The role this collaborator is assigned."""
39
+
40
+ if self._role is None:
41
+ self._role = self.client.roles.get(uid=self.role_id)
42
+
43
+ return self._role
44
+
45
+ @role.setter
46
+ def role(self, role: Union["Role", UUID, str] = ...):
47
+ if not role:
48
+ raise ValueError("Role of collaborator cannot be None.")
49
+ if normalize_uuid(role) != str(self.role_id):
50
+ self.role_id = normalize_uuid(role)
51
+ self._role = None
52
+
53
+ def save(self):
54
+ """Saves changes to this resource to HydroServer."""
55
+
56
+ if self.unsaved_changes:
57
+ self.client.workspaces.edit_collaborator_role(
58
+ uid=str(self.workspace_id), email=self.user.email, role=self.role
59
+ )
60
+ self._role = None
61
+ self._server_data["role_id"] = self.role_id
62
+ self.__dict__.update({"role_id": self.role_id})
28
63
 
29
- def remove(self):
30
- """Remove this collaborator from the workspace."""
64
+ def delete(self):
65
+ """Deletes this resource from HydroServer."""
31
66
 
32
- self._connection.workspaces.remove_collaborator(
33
- uid=self.workspace_id, email=self.user.email
67
+ self.client.workspaces.remove_collaborator(
68
+ uid=str(self.workspace_id), email=self.user.email
34
69
  )
70
+ self.uid = None
@@ -1,10 +1,38 @@
1
- from typing import Optional, Union
1
+ from typing import Optional, Union, TYPE_CHECKING
2
2
  from uuid import UUID
3
- from pydantic import BaseModel, Field
3
+ from pydantic import Field
4
+ from ..base import HydroServerBaseModel
4
5
 
6
+ if TYPE_CHECKING:
7
+ from hydroserverpy import HydroServer
8
+ from hydroserverpy.api.models import Workspace
5
9
 
6
- class Role(BaseModel):
7
- uid: UUID = Field(..., alias="id")
10
+
11
+ class Role(HydroServerBaseModel):
8
12
  name: str = Field(..., max_length=255)
9
13
  description: str
10
14
  workspace_id: Optional[Union[UUID, str]] = None
15
+
16
+ def __init__(self, client: "HydroServer", **data):
17
+ super().__init__(client=client, service=client.workspaces, **data)
18
+
19
+ self._workspace = None
20
+
21
+ @classmethod
22
+ def get_route(cls):
23
+ return "roles"
24
+
25
+ @property
26
+ def workspace(self) -> "Workspace":
27
+ """The workspace this role belongs to."""
28
+
29
+ if self._workspace is None and self.workspace_id is not None:
30
+ self._workspace = self.client.workspaces.get(uid=self.workspace_id)
31
+
32
+ return self._workspace
33
+
34
+ def save(self):
35
+ raise NotImplementedError("Editing roles not enabled.")
36
+
37
+ def delete(self):
38
+ raise NotImplementedError("Deleting roles not enabled.")