restreamsolutions 0.1.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.
- restreamsolutions/__init__.py +4 -0
- restreamsolutions/base_interface.py +239 -0
- restreamsolutions/base_pad_site.py +707 -0
- restreamsolutions/communicator.py +523 -0
- restreamsolutions/constants.py +75 -0
- restreamsolutions/data_changes.py +342 -0
- restreamsolutions/data_object.py +327 -0
- restreamsolutions/exceptions.py +28 -0
- restreamsolutions/pad.py +679 -0
- restreamsolutions/site.py +595 -0
- restreamsolutions/state.py +121 -0
- restreamsolutions-0.1.0.dist-info/METADATA +582 -0
- restreamsolutions-0.1.0.dist-info/RECORD +15 -0
- restreamsolutions-0.1.0.dist-info/WHEEL +4 -0
- restreamsolutions-0.1.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from datetime import datetime
|
|
3
|
+
from typing import get_type_hints
|
|
4
|
+
|
|
5
|
+
from dateutil import parser
|
|
6
|
+
|
|
7
|
+
from .exceptions import APICompatibilityError
|
|
8
|
+
from .constants import RESTREAM_HOST
|
|
9
|
+
from .communicator import Communicator
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class BaseInterface:
|
|
13
|
+
_auth_token: str = None
|
|
14
|
+
_api_url_single_object: str = None
|
|
15
|
+
_api_url_multiple_objects: str = None
|
|
16
|
+
id: int = None
|
|
17
|
+
|
|
18
|
+
def __init__(self, id: int, auth_token: str = None, **kwargs):
|
|
19
|
+
"""Initialize the model instance.
|
|
20
|
+
|
|
21
|
+
Args:
|
|
22
|
+
id: Unique identifier of the model instance.
|
|
23
|
+
auth_token: Optional auth token used for API requests; if not provided, environment variable RESTREAM_AUTH_TOKEN
|
|
24
|
+
will be used instead.
|
|
25
|
+
**kwargs: Additional fields which will be set as attributes of the instance with possible type conversions
|
|
26
|
+
according to the type hints of the child class.
|
|
27
|
+
"""
|
|
28
|
+
self.id = id
|
|
29
|
+
self._auth_token = auth_token
|
|
30
|
+
self._hints = get_type_hints(self.__class__)
|
|
31
|
+
for key, value in kwargs.items():
|
|
32
|
+
setattr(self, key, self._try_convert_value(key, value))
|
|
33
|
+
|
|
34
|
+
@classmethod
|
|
35
|
+
def _format_url(cls, url, is_websocket: bool = False, **params) -> str:
|
|
36
|
+
"""Compose a full API URL using the base host and path template.
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
url: API path template (e.g., "/api/v1/resource/{id}").
|
|
40
|
+
is_websocket: If True, convert the resulting URL to a WebSocket scheme (http->ws, https->wss).
|
|
41
|
+
**params: Parameters to format into the URL template.
|
|
42
|
+
|
|
43
|
+
Returns:
|
|
44
|
+
The fully formatted absolute URL as a string.
|
|
45
|
+
"""
|
|
46
|
+
base_url = os.environ.get('RESTREAM_HOST', RESTREAM_HOST)
|
|
47
|
+
api_url = f'{base_url}{url}'.format(**params)
|
|
48
|
+
|
|
49
|
+
if not is_websocket:
|
|
50
|
+
return api_url
|
|
51
|
+
|
|
52
|
+
if api_url.startswith('https://'):
|
|
53
|
+
api_url = api_url.replace('https://', 'wss://')
|
|
54
|
+
elif api_url.startswith('http://'):
|
|
55
|
+
api_url = api_url.replace('http://', 'ws://')
|
|
56
|
+
return api_url
|
|
57
|
+
|
|
58
|
+
@classmethod
|
|
59
|
+
def _build_single_from_response(cls, json_response, id: int, auth_token: str, as_dict: bool):
|
|
60
|
+
"""Create a single model instance (or dict) from a JSON response.
|
|
61
|
+
|
|
62
|
+
Args:
|
|
63
|
+
json_response: Parsed JSON body expected to be a dict for object details.
|
|
64
|
+
id: Identifier of the object being fetched.
|
|
65
|
+
auth_token: Token to be passed to the constructed instance.
|
|
66
|
+
as_dict: If True, return the raw JSON response as python dict instead of a model instance.
|
|
67
|
+
|
|
68
|
+
Returns:
|
|
69
|
+
An instance of the current class or the raw JSON dict if as_dict is True.
|
|
70
|
+
|
|
71
|
+
Raises:
|
|
72
|
+
APICompatibilityError: If the response is not a dict when a model instance is expected.
|
|
73
|
+
"""
|
|
74
|
+
if as_dict:
|
|
75
|
+
return json_response
|
|
76
|
+
if not isinstance(json_response, dict):
|
|
77
|
+
raise APICompatibilityError(f"Expected a JSON object for a single model, but received: {json_response}")
|
|
78
|
+
return cls(auth_token=auth_token, **{'id': id, **json_response})
|
|
79
|
+
|
|
80
|
+
@classmethod
|
|
81
|
+
def _select_token(cls, auth_token: str) -> str | None:
|
|
82
|
+
"""Choose the authentication token to use for requests.
|
|
83
|
+
|
|
84
|
+
Prefers the explicitly provided token; falls back to the RESTREAM_AUTH_TOKEN
|
|
85
|
+
environment variable if not provided.
|
|
86
|
+
|
|
87
|
+
Args:
|
|
88
|
+
auth_token: Optional token passed directly to the call.
|
|
89
|
+
|
|
90
|
+
Returns:
|
|
91
|
+
The selected token string or None if nothing is available.
|
|
92
|
+
"""
|
|
93
|
+
return auth_token or os.environ.get("RESTREAM_AUTH_TOKEN")
|
|
94
|
+
|
|
95
|
+
@classmethod
|
|
96
|
+
def _build_multiple_from_response(cls, json_response, auth_token: str, as_dict: bool):
|
|
97
|
+
"""Create multiple model instances (or list of dicts) from a JSON response.
|
|
98
|
+
|
|
99
|
+
Args:
|
|
100
|
+
json_response: Parsed JSON body expected to be a list of dicts.
|
|
101
|
+
auth_token: Token to be passed to each constructed instance.
|
|
102
|
+
as_dict: If True, return the raw JSON response as list instead of model instances.
|
|
103
|
+
|
|
104
|
+
Returns:
|
|
105
|
+
A list of class instances or the raw JSON list if as_dict is True.
|
|
106
|
+
|
|
107
|
+
Raises:
|
|
108
|
+
APICompatibilityError: If the response is not a list of dicts when instances are expected.
|
|
109
|
+
"""
|
|
110
|
+
if as_dict:
|
|
111
|
+
return json_response
|
|
112
|
+
if not isinstance(json_response, list) or not all(isinstance(o, dict) for o in json_response):
|
|
113
|
+
raise APICompatibilityError(f"Expected a JSON array, but received: {json_response}")
|
|
114
|
+
return [cls(auth_token=auth_token, **json_object) for json_object in json_response]
|
|
115
|
+
|
|
116
|
+
def _try_convert_value(self, key, value):
|
|
117
|
+
"""Attempt to convert a raw value to the annotated type in a child class for the given attribute.
|
|
118
|
+
|
|
119
|
+
- Converts ISO-like strings to datetime when the type hint is datetime.
|
|
120
|
+
- Instantiates nested BaseInterface subclasses from dicts when appropriate.
|
|
121
|
+
- Falls back to direct casting using the target type.
|
|
122
|
+
|
|
123
|
+
Args:
|
|
124
|
+
key: The attribute name.
|
|
125
|
+
value: The raw value to convert.
|
|
126
|
+
|
|
127
|
+
Returns:
|
|
128
|
+
The converted value or the original value if no conversion is needed.
|
|
129
|
+
|
|
130
|
+
Raises:
|
|
131
|
+
APICompatibilityError: If the value cannot be converted to the hinted type.
|
|
132
|
+
"""
|
|
133
|
+
# TODO: Reimplement using pydantic
|
|
134
|
+
if value is None:
|
|
135
|
+
return None
|
|
136
|
+
if key in self._hints and not isinstance(value, self._hints[key]):
|
|
137
|
+
try:
|
|
138
|
+
if self._hints[key] == datetime:
|
|
139
|
+
return parser.parse(str(value), ignoretz=False)
|
|
140
|
+
if issubclass(self._hints[key], BaseInterface) and isinstance(value, dict):
|
|
141
|
+
return self._hints[key](**value)
|
|
142
|
+
return self._hints[key](value)
|
|
143
|
+
except TypeError:
|
|
144
|
+
raise APICompatibilityError(f"Can not convert {key}={value}: to {self._hints[key]}")
|
|
145
|
+
return value
|
|
146
|
+
|
|
147
|
+
@classmethod
|
|
148
|
+
def get_model(cls, id: int = None, auth_token: str = None, as_dict=False, **filters) -> "BaseInterface | dict":
|
|
149
|
+
"""Fetch a single model from the API.
|
|
150
|
+
|
|
151
|
+
Args:
|
|
152
|
+
id: Identifier of the object to fetch.
|
|
153
|
+
auth_token: Optional auth token; falls back to environment variable RESTREAM_AUTH_TOKEN if not provided.
|
|
154
|
+
as_dict: If True, return raw JSON dict instead of a model instance.
|
|
155
|
+
**filters: Extra query parameters passed to the HTTP request.
|
|
156
|
+
|
|
157
|
+
Returns:
|
|
158
|
+
An instance of the current class or a raw dict if as_dict is True.
|
|
159
|
+
"""
|
|
160
|
+
current_auth_token = cls._select_token(auth_token)
|
|
161
|
+
url = cls._format_url(cls._api_url_single_object, id=id)
|
|
162
|
+
json_response = Communicator.send_get_request(url, current_auth_token, **filters)
|
|
163
|
+
return cls._build_single_from_response(json_response, id=id, auth_token=auth_token, as_dict=as_dict)
|
|
164
|
+
|
|
165
|
+
@classmethod
|
|
166
|
+
def get_models(cls, auth_token: str = None, as_dict=False, **filters) -> "BaseInterface | list[dict]":
|
|
167
|
+
"""Fetch a collection of models from the API.
|
|
168
|
+
|
|
169
|
+
Args:
|
|
170
|
+
auth_token: Optional auth token; falls back to environment variable RESTREAM_AUTH_TOKEN if not provided.
|
|
171
|
+
as_dict: If True, return raw JSON list instead of model instances.
|
|
172
|
+
**filters: Extra query parameters passed to the HTTP request.
|
|
173
|
+
|
|
174
|
+
Returns:
|
|
175
|
+
A list of class instances or a raw list of dicts if as_dict is True.
|
|
176
|
+
"""
|
|
177
|
+
current_auth_token = cls._select_token(auth_token)
|
|
178
|
+
url = cls._format_url(cls._api_url_multiple_objects)
|
|
179
|
+
json_response = Communicator.send_get_request(url, current_auth_token, **filters)
|
|
180
|
+
return cls._build_multiple_from_response(json_response, auth_token=auth_token, as_dict=as_dict)
|
|
181
|
+
|
|
182
|
+
@classmethod
|
|
183
|
+
async def aget_model(cls, id: int = None, auth_token: str = None, as_dict=False, **filters):
|
|
184
|
+
"""Asynchronously fetch a single model from the API.
|
|
185
|
+
|
|
186
|
+
Args:
|
|
187
|
+
id: Identifier of the object to fetch.
|
|
188
|
+
auth_token: Optional auth token; falls back to environment variable RESTREAM_AUTH_TOKEN if not provided.
|
|
189
|
+
as_dict: If True, return raw JSON dict instead of a model instance.
|
|
190
|
+
**filters: Extra query parameters passed to the HTTP request.
|
|
191
|
+
|
|
192
|
+
Returns:
|
|
193
|
+
An instance of the current class or a raw dict if as_dict is True.
|
|
194
|
+
"""
|
|
195
|
+
current_auth_token = cls._select_token(auth_token)
|
|
196
|
+
url = cls._format_url(cls._api_url_single_object, id=id)
|
|
197
|
+
json_response = await Communicator.send_get_request_async(url, current_auth_token, **filters)
|
|
198
|
+
return cls._build_single_from_response(json_response, id=id, auth_token=auth_token, as_dict=as_dict)
|
|
199
|
+
|
|
200
|
+
@classmethod
|
|
201
|
+
async def aget_models(cls, auth_token: str = None, as_dict=False, **filters):
|
|
202
|
+
"""Asynchronously fetch a collection of models from the API.
|
|
203
|
+
|
|
204
|
+
Args:
|
|
205
|
+
auth_token: Optional auth token; falls back to environment variable RESTREAM_AUTH_TOKEN if not provided.
|
|
206
|
+
as_dict: If True, return raw JSON list instead of model instances.
|
|
207
|
+
**filters: Extra query parameters passed to the HTTP request.
|
|
208
|
+
|
|
209
|
+
Returns:
|
|
210
|
+
A list of class instances or a raw list of dicts if as_dict is True.
|
|
211
|
+
"""
|
|
212
|
+
current_auth_token = cls._select_token(auth_token)
|
|
213
|
+
url = cls._format_url(cls._api_url_multiple_objects)
|
|
214
|
+
json_response = await Communicator.send_get_request_async(url, current_auth_token, **filters)
|
|
215
|
+
return cls._build_multiple_from_response(json_response, auth_token=auth_token, as_dict=as_dict)
|
|
216
|
+
|
|
217
|
+
def update(self):
|
|
218
|
+
"""Refresh this instance with the latest data from the API using its id.
|
|
219
|
+
|
|
220
|
+
Returns:
|
|
221
|
+
None. The instance is updated in-place.
|
|
222
|
+
"""
|
|
223
|
+
updated_dict = self.get_model(id=self.id, as_dict=True, auth_token=self._auth_token)
|
|
224
|
+
for key, value in updated_dict.items():
|
|
225
|
+
setattr(self, key, self._try_convert_value(key, value))
|
|
226
|
+
|
|
227
|
+
async def aupdate(self):
|
|
228
|
+
"""Asynchronously refresh this instance with the latest data from the API using its id.
|
|
229
|
+
|
|
230
|
+
Returns:
|
|
231
|
+
None. The instance is updated in-place.
|
|
232
|
+
"""
|
|
233
|
+
updated_dict = await self.aget_model(id=self.id, as_dict=True, auth_token=self._auth_token)
|
|
234
|
+
for key, value in updated_dict.items():
|
|
235
|
+
setattr(self, key, self._try_convert_value(key, value))
|
|
236
|
+
|
|
237
|
+
def __repr__(self):
|
|
238
|
+
"""Return a concise string representation of the instance."""
|
|
239
|
+
return f"{self.__class__.__name__}(id={self.id})"
|