mercuto-client 0.2.7__py3-none-any.whl → 0.3.0a0__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 mercuto-client might be problematic. Click here for more details.
- mercuto_client/__init__.py +2 -24
- mercuto_client/_authentication.py +72 -0
- mercuto_client/_tests/test_ingester/test_parsers.py +67 -67
- mercuto_client/_tests/test_mocking/__init__.py +0 -0
- mercuto_client/_tests/test_mocking/conftest.py +13 -0
- mercuto_client/_tests/test_mocking/test_mock_identity.py +8 -0
- mercuto_client/acl.py +16 -10
- mercuto_client/client.py +53 -779
- mercuto_client/exceptions.py +5 -1
- mercuto_client/ingester/__main__.py +1 -1
- mercuto_client/ingester/mercuto.py +15 -16
- mercuto_client/ingester/parsers/__init__.py +3 -3
- mercuto_client/ingester/parsers/campbell.py +2 -2
- mercuto_client/ingester/parsers/generic_csv.py +5 -5
- mercuto_client/ingester/parsers/worldsensing.py +4 -3
- mercuto_client/mocks/__init__.py +92 -0
- mercuto_client/mocks/_utility.py +69 -0
- mercuto_client/mocks/mock_data.py +402 -0
- mercuto_client/mocks/mock_fatigue.py +30 -0
- mercuto_client/mocks/mock_identity.py +188 -0
- mercuto_client/modules/__init__.py +19 -0
- mercuto_client/modules/_util.py +18 -0
- mercuto_client/modules/core.py +674 -0
- mercuto_client/modules/data.py +623 -0
- mercuto_client/modules/fatigue.py +189 -0
- mercuto_client/modules/identity.py +254 -0
- mercuto_client/{ingester/util.py → util.py} +27 -11
- mercuto_client-0.3.0a0.dist-info/METADATA +72 -0
- mercuto_client-0.3.0a0.dist-info/RECORD +41 -0
- mercuto_client/_tests/test_mocking.py +0 -93
- mercuto_client/_util.py +0 -13
- mercuto_client/mocks.py +0 -203
- mercuto_client/types.py +0 -409
- mercuto_client-0.2.7.dist-info/METADATA +0 -20
- mercuto_client-0.2.7.dist-info/RECORD +0 -30
- {mercuto_client-0.2.7.dist-info → mercuto_client-0.3.0a0.dist-info}/WHEEL +0 -0
- {mercuto_client-0.2.7.dist-info → mercuto_client-0.3.0a0.dist-info}/licenses/LICENSE +0 -0
- {mercuto_client-0.2.7.dist-info → mercuto_client-0.3.0a0.dist-info}/top_level.txt +0 -0
mercuto_client/client.py
CHANGED
|
@@ -1,121 +1,36 @@
|
|
|
1
|
-
import base64
|
|
2
1
|
import contextlib
|
|
2
|
+
import json as json_stdlib
|
|
3
3
|
import logging
|
|
4
|
-
import mimetypes
|
|
5
4
|
import os
|
|
6
5
|
import time
|
|
7
|
-
import
|
|
8
|
-
from contextlib import nullcontext
|
|
9
|
-
from datetime import datetime, timedelta
|
|
10
|
-
from typing import (Any, BinaryIO, Iterable, Iterator, List, Literal, Mapping,
|
|
11
|
-
Optional, Sequence, TextIO, Type, TypeVar)
|
|
6
|
+
from typing import Any, Iterator, Mapping, Optional, Protocol, Type, TypeVar
|
|
12
7
|
|
|
13
8
|
import requests
|
|
14
9
|
import requests.cookies
|
|
15
10
|
|
|
16
|
-
from .
|
|
11
|
+
from ._authentication import (IAuthenticationMethod,
|
|
12
|
+
create_authentication_method)
|
|
17
13
|
from .exceptions import MercutoClientException, MercutoHTTPException
|
|
18
|
-
from .
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
Image, ListAlertsResponseType, NewUserApiKey, Object,
|
|
23
|
-
PermissionGroup, Project, RainflowConfiguration,
|
|
24
|
-
ScheduledReport, ScheduledReportLog,
|
|
25
|
-
SystemHealthcheckResult, Tenant, Units, User,
|
|
26
|
-
UserContactMethod, UserDetails, VerifyMeResult, Video)
|
|
14
|
+
from .modules.core import MercutoCoreService
|
|
15
|
+
from .modules.data import MercutoDataService
|
|
16
|
+
from .modules.fatigue import MercutoFatigueService
|
|
17
|
+
from .modules.identity import MercutoIdentityService
|
|
27
18
|
|
|
28
19
|
logger = logging.getLogger(__name__)
|
|
29
20
|
|
|
30
21
|
|
|
31
|
-
class
|
|
32
|
-
def
|
|
33
|
-
return
|
|
34
|
-
|
|
35
|
-
def unique_key(self) -> str:
|
|
36
|
-
raise NotImplementedError(
|
|
37
|
-
f"unique_key not implemented for type {self.__class__.__name__}")
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
class ApiKeyAuthentication(IAuthenticationMethod):
|
|
41
|
-
def __init__(self, api_key: str) -> None:
|
|
42
|
-
self.api_key = api_key
|
|
43
|
-
|
|
44
|
-
def update_header(self, header: dict[str, str]) -> None:
|
|
45
|
-
header['X-Api-Key'] = self.api_key
|
|
46
|
-
|
|
47
|
-
def unique_key(self) -> str:
|
|
48
|
-
return f'api-key:{self.api_key}'
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
class ServiceTokenAuthentication(IAuthenticationMethod):
|
|
52
|
-
def __init__(self, service_token: str) -> None:
|
|
53
|
-
self.service_token = service_token
|
|
54
|
-
|
|
55
|
-
def update_header(self, header: dict[str, str]) -> None:
|
|
56
|
-
header['X-Service-Token'] = self.service_token
|
|
57
|
-
|
|
58
|
-
def unique_key(self) -> str:
|
|
59
|
-
return f'service-token:{self.service_token}'
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
class AuthorizationHeaderAuthentication(IAuthenticationMethod):
|
|
63
|
-
def __init__(self, authorization_header: str) -> None:
|
|
64
|
-
self.authorization_header = authorization_header
|
|
65
|
-
|
|
66
|
-
def update_header(self, header: dict[str, str]) -> None:
|
|
67
|
-
header['Authorization'] = self.authorization_header
|
|
68
|
-
|
|
69
|
-
def unique_key(self) -> str:
|
|
70
|
-
return f'auth-bearer:{self.authorization_header}'
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
class NullAuthenticationMethod(IAuthenticationMethod):
|
|
74
|
-
def update_header(self, header: dict[str, str]) -> None:
|
|
22
|
+
class _ModuleBase(Protocol):
|
|
23
|
+
def __init__(self, client: 'MercutoClient', *args: Any, **kwargs: Any) -> None:
|
|
75
24
|
pass
|
|
76
25
|
|
|
77
|
-
def unique_key(self) -> str:
|
|
78
|
-
return 'null-authentication'
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
def create_authentication_method(api_key: Optional[str] = None,
|
|
82
|
-
service_token: Optional[str] = None,
|
|
83
|
-
headers: Optional[Mapping[str, str]] = None) -> IAuthenticationMethod:
|
|
84
|
-
if api_key is not None and service_token is not None and headers is not None:
|
|
85
|
-
raise MercutoClientException(
|
|
86
|
-
"Only one of api_key or service_token can be provided")
|
|
87
|
-
authorization_header = None
|
|
88
|
-
if headers is not None:
|
|
89
|
-
api_key = headers.get('X-Api-Key', None)
|
|
90
|
-
service_token = headers.get('X-Service-Token', None)
|
|
91
|
-
authorization_header = headers.get('Authorization', None)
|
|
92
|
-
if api_key is not None:
|
|
93
|
-
return ApiKeyAuthentication(api_key)
|
|
94
|
-
elif service_token is not None:
|
|
95
|
-
return ServiceTokenAuthentication(service_token)
|
|
96
|
-
elif authorization_header is not None:
|
|
97
|
-
return AuthorizationHeaderAuthentication(authorization_header)
|
|
98
|
-
else:
|
|
99
|
-
return NullAuthenticationMethod()
|
|
100
|
-
|
|
101
26
|
|
|
102
|
-
|
|
103
|
-
def __init__(self, client: 'MercutoClient') -> None:
|
|
104
|
-
self._client = client
|
|
105
|
-
|
|
106
|
-
@property
|
|
107
|
-
def client(self) -> 'MercutoClient':
|
|
108
|
-
return self._client
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
T = TypeVar('T', bound='Module')
|
|
27
|
+
_T = TypeVar('_T', bound=_ModuleBase)
|
|
112
28
|
|
|
113
29
|
|
|
114
30
|
class MercutoClient:
|
|
115
31
|
def __init__(self, url: Optional[str] = None, verify_ssl: bool = True, active_session: Optional[requests.Session] = None) -> None:
|
|
116
32
|
if url is None:
|
|
117
|
-
url = os.environ.get(
|
|
118
|
-
'MERCUTO_API_URL', 'https://api.rockfieldcloud.com.au')
|
|
33
|
+
url = os.environ.get('MERCUTO_API_URL', 'https://api.rockfieldcloud.com.au')
|
|
119
34
|
assert isinstance(url, str)
|
|
120
35
|
|
|
121
36
|
if url.endswith('/'):
|
|
@@ -135,7 +50,7 @@ class MercutoClient:
|
|
|
135
50
|
self._auth_method: Optional[IAuthenticationMethod] = None
|
|
136
51
|
self._cookies = requests.cookies.RequestsCookieJar()
|
|
137
52
|
|
|
138
|
-
self._modules: dict[str,
|
|
53
|
+
self._modules: dict[str, _ModuleBase] = {}
|
|
139
54
|
|
|
140
55
|
def url(self) -> str:
|
|
141
56
|
return self._url
|
|
@@ -157,20 +72,21 @@ class MercutoClient:
|
|
|
157
72
|
@contextlib.contextmanager
|
|
158
73
|
def as_credentials(self, api_key: Optional[str] = None,
|
|
159
74
|
service_token: Optional[str] = None,
|
|
75
|
+
bearer_token: Optional[str] = None,
|
|
160
76
|
headers: Optional[Mapping[str, str]] = None) -> Iterator['MercutoClient']:
|
|
161
77
|
"""
|
|
162
78
|
Same as .connect(), but as a context manager. Will automatically logout when exiting the context.
|
|
163
79
|
"""
|
|
164
80
|
# TODO: We are passing the current session along to re-use connections for speed. Will this cause security issues?
|
|
165
|
-
other = MercutoClient(self._url, self._verify_ssl,
|
|
166
|
-
self._current_session)
|
|
81
|
+
other = MercutoClient(self._url, self._verify_ssl, self._current_session)
|
|
167
82
|
try:
|
|
168
|
-
yield other.connect(api_key=api_key, service_token=service_token, headers=headers)
|
|
83
|
+
yield other.connect(api_key=api_key, service_token=service_token, bearer_token=bearer_token, headers=headers)
|
|
169
84
|
finally:
|
|
170
85
|
other.logout()
|
|
171
86
|
|
|
172
87
|
def connect(self, *, api_key: Optional[str] = None,
|
|
173
88
|
service_token: Optional[str] = None,
|
|
89
|
+
bearer_token: Optional[str] = None,
|
|
174
90
|
headers: Optional[Mapping[str, str]] = None) -> 'MercutoClient':
|
|
175
91
|
"""
|
|
176
92
|
Attempt to connect using any available method.
|
|
@@ -180,8 +96,7 @@ class MercutoClient:
|
|
|
180
96
|
headers should be a dictionary of headers that would be sent in a request. Useful for using existing authenation mechanism for forwarding.
|
|
181
97
|
|
|
182
98
|
"""
|
|
183
|
-
authentication = create_authentication_method(
|
|
184
|
-
api_key=api_key, service_token=service_token, headers=headers)
|
|
99
|
+
authentication = create_authentication_method(api_key=api_key, service_token=service_token, bearer_token=bearer_token, headers=headers)
|
|
185
100
|
self.login(authentication)
|
|
186
101
|
return self
|
|
187
102
|
|
|
@@ -193,14 +108,15 @@ class MercutoClient:
|
|
|
193
108
|
base.update(headers)
|
|
194
109
|
return base
|
|
195
110
|
|
|
196
|
-
def
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
111
|
+
def _http_request(self, url: str, method: str,
|
|
112
|
+
params: Optional[dict[str, Any]] = None,
|
|
113
|
+
json: Optional[dict[str, Any]] = None,
|
|
114
|
+
raise_for_status: bool = True,
|
|
115
|
+
**kwargs: Any) -> requests.Response:
|
|
116
|
+
if url.startswith('/'):
|
|
117
|
+
url = url[1:]
|
|
118
|
+
full_url = f"{self._url}/{url}"
|
|
202
119
|
|
|
203
|
-
def _request_json(self, method: str, url: str, *args: Any, **kwargs: Any) -> Any:
|
|
204
120
|
if 'timeout' not in kwargs:
|
|
205
121
|
kwargs['timeout'] = 10
|
|
206
122
|
kwargs['headers'] = self._update_headers(kwargs.get('headers', {}))
|
|
@@ -210,67 +126,46 @@ class MercutoClient:
|
|
|
210
126
|
|
|
211
127
|
if 'cookies' not in kwargs:
|
|
212
128
|
kwargs['cookies'] = self._cookies
|
|
213
|
-
return self._make_request(method, url, *args, **kwargs)
|
|
214
129
|
|
|
215
|
-
|
|
216
|
-
if
|
|
217
|
-
|
|
218
|
-
|
|
130
|
+
# Custom parsing json to support NAN
|
|
131
|
+
if json is not None and kwargs.get('data') is None:
|
|
132
|
+
kwargs['data'] = json_stdlib.dumps(json, allow_nan=True)
|
|
133
|
+
kwargs['headers']['Content-Type'] = 'application/json'
|
|
134
|
+
json = None
|
|
135
|
+
|
|
219
136
|
start = time.time()
|
|
220
|
-
resp = self._current_session.request(method, full_url,
|
|
137
|
+
resp = self._current_session.request(method, full_url, params=params, json=json, **kwargs)
|
|
221
138
|
duration = time.time() - start
|
|
222
|
-
logger.debug("Made request to %s %s in %.2f seconds (code=%s)",
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
139
|
+
logger.debug("Made request to %s %s in %.2f seconds (code=%s)", method, full_url, duration, resp.status_code)
|
|
140
|
+
if raise_for_status and not resp.ok:
|
|
141
|
+
try:
|
|
142
|
+
error_json = resp.json()
|
|
143
|
+
except Exception:
|
|
144
|
+
raise MercutoHTTPException(resp.text, resp.status_code)
|
|
145
|
+
else:
|
|
146
|
+
if 'detail' in error_json and isinstance(error_json['detail'], str):
|
|
147
|
+
raise MercutoHTTPException(error_json['detail'], resp.status_code)
|
|
148
|
+
else:
|
|
149
|
+
raise MercutoHTTPException(resp.text, resp.status_code)
|
|
228
150
|
resp.cookies.update(self._cookies)
|
|
229
|
-
return resp
|
|
230
|
-
|
|
231
|
-
def request(self, method: str, url: str, *args: Any, **kwargs: Any) -> Any:
|
|
232
|
-
return self._request_json(method, url, *args, **kwargs)
|
|
151
|
+
return resp
|
|
233
152
|
|
|
234
|
-
def
|
|
153
|
+
def _add_and_fetch_module(self, name: str, module: Type[_T]) -> _T:
|
|
235
154
|
if name not in self._modules:
|
|
236
155
|
self._modules[name] = module(self)
|
|
237
156
|
return self._modules[name] # type: ignore
|
|
238
157
|
|
|
239
158
|
def identity(self) -> 'MercutoIdentityService':
|
|
240
|
-
return self.
|
|
241
|
-
|
|
242
|
-
def projects(self) -> 'MercutoProjectService':
|
|
243
|
-
return self.add_and_fetch_module('projects', MercutoProjectService)
|
|
159
|
+
return self._add_and_fetch_module('identity', MercutoIdentityService)
|
|
244
160
|
|
|
245
161
|
def fatigue(self) -> 'MercutoFatigueService':
|
|
246
|
-
return self.
|
|
247
|
-
|
|
248
|
-
def channels(self) -> 'MercutoChannelService':
|
|
249
|
-
return self.add_and_fetch_module('channels', MercutoChannelService)
|
|
162
|
+
return self._add_and_fetch_module('fatigue', MercutoFatigueService)
|
|
250
163
|
|
|
251
164
|
def data(self) -> 'MercutoDataService':
|
|
252
|
-
return self.
|
|
253
|
-
|
|
254
|
-
def events(self) -> 'MercutoEventService':
|
|
255
|
-
return self.add_and_fetch_module('events', MercutoEventService)
|
|
256
|
-
|
|
257
|
-
def alerts(self) -> 'MercutoAlertService':
|
|
258
|
-
return self.add_and_fetch_module('alerts', MercutoAlertService)
|
|
259
|
-
|
|
260
|
-
def devices(self) -> 'MercutoDeviceService':
|
|
261
|
-
return self.add_and_fetch_module('devices', MercutoDeviceService)
|
|
262
|
-
|
|
263
|
-
def notifications(self) -> 'MercutoNotificationsService':
|
|
264
|
-
return self.add_and_fetch_module('notifications', MercutoNotificationsService)
|
|
165
|
+
return self._add_and_fetch_module('data', MercutoDataService)
|
|
265
166
|
|
|
266
|
-
def
|
|
267
|
-
return self.
|
|
268
|
-
|
|
269
|
-
def objects(self) -> 'MercutoObjectService':
|
|
270
|
-
return self.add_and_fetch_module('objects', MercutoObjectService)
|
|
271
|
-
|
|
272
|
-
def media(self) -> 'MercutoMediaService':
|
|
273
|
-
return self.add_and_fetch_module('media', MercutoMediaService)
|
|
167
|
+
def core(self) -> 'MercutoCoreService':
|
|
168
|
+
return self._add_and_fetch_module('core', MercutoCoreService)
|
|
274
169
|
|
|
275
170
|
def login(self, authentication: IAuthenticationMethod) -> None:
|
|
276
171
|
self._auth_method = authentication
|
|
@@ -278,626 +173,5 @@ class MercutoClient:
|
|
|
278
173
|
def logout(self) -> None:
|
|
279
174
|
self._auth_method = None
|
|
280
175
|
|
|
281
|
-
def
|
|
282
|
-
return self.
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
class MercutoDataService(Module):
|
|
286
|
-
def __init__(self, client: 'MercutoClient') -> None:
|
|
287
|
-
super().__init__(client)
|
|
288
|
-
|
|
289
|
-
def upload_samples(self, samples: Sequence[DataSample]) -> None:
|
|
290
|
-
self._client._request_json(
|
|
291
|
-
'POST', '/data/upload/samples', json=samples)
|
|
292
|
-
|
|
293
|
-
def upload_file(self, project: str, datatable: str, file: str | bytes | TextIO | BinaryIO, filename: Optional[str] = None) -> None:
|
|
294
|
-
if isinstance(file, str):
|
|
295
|
-
ctx = open(file, 'rb')
|
|
296
|
-
filename = filename or os.path.basename(file)
|
|
297
|
-
else:
|
|
298
|
-
ctx = nullcontext(file) # type: ignore
|
|
299
|
-
filename = filename or 'file.dat'
|
|
300
|
-
|
|
301
|
-
with ctx as f:
|
|
302
|
-
self._client._request_json('POST', '/files/upload/small', params={'project_code': project, 'datatable_code': datatable},
|
|
303
|
-
files={'file': (filename, f, 'text/csv')})
|
|
304
|
-
|
|
305
|
-
def get_data_url(
|
|
306
|
-
self,
|
|
307
|
-
project_code: str,
|
|
308
|
-
start_time: datetime | str | None = None,
|
|
309
|
-
end_time: datetime | str | None = None,
|
|
310
|
-
event_code: str | None = None,
|
|
311
|
-
channel_codes: Iterable[str] | None = None,
|
|
312
|
-
primary_channels: bool | None = None,
|
|
313
|
-
channels_like: str | None = None,
|
|
314
|
-
file_format: Literal['DAT', 'CSV', 'PARQUET', 'FEATHER'] = 'DAT',
|
|
315
|
-
frame_format: Literal['RECORDS', 'COLUMNS'] = 'COLUMNS',
|
|
316
|
-
channel_format: Literal['CODE', 'LABEL'] = 'LABEL',
|
|
317
|
-
timeout: timedelta | None = timedelta(seconds=20),
|
|
318
|
-
) -> str:
|
|
319
|
-
request = self.get_data_request(
|
|
320
|
-
project_code=project_code,
|
|
321
|
-
start_time=start_time,
|
|
322
|
-
end_time=end_time,
|
|
323
|
-
event_code=event_code,
|
|
324
|
-
channel_codes=channel_codes,
|
|
325
|
-
primary_channels=primary_channels,
|
|
326
|
-
channels_like=channels_like,
|
|
327
|
-
file_format=file_format,
|
|
328
|
-
frame_format=frame_format,
|
|
329
|
-
channel_format=channel_format,
|
|
330
|
-
timeout=timeout,
|
|
331
|
-
)
|
|
332
|
-
if request['presigned_url'] is None:
|
|
333
|
-
raise MercutoClientException("No presigned URL available")
|
|
334
|
-
|
|
335
|
-
return request['presigned_url']
|
|
336
|
-
|
|
337
|
-
def get_data_request(
|
|
338
|
-
self,
|
|
339
|
-
project_code: str,
|
|
340
|
-
start_time: datetime | str | None = None,
|
|
341
|
-
end_time: datetime | str | None = None,
|
|
342
|
-
event_code: str | None = None,
|
|
343
|
-
channel_codes: Iterable[str] | None = None,
|
|
344
|
-
primary_channels: bool | None = None,
|
|
345
|
-
channels_like: str | None = None,
|
|
346
|
-
file_format: Literal['DAT', 'CSV', 'PARQUET', 'FEATHER'] = 'DAT',
|
|
347
|
-
frame_format: Literal['RECORDS', 'COLUMNS'] = 'COLUMNS',
|
|
348
|
-
channel_format: Literal['CODE', 'LABEL'] = 'LABEL',
|
|
349
|
-
timeout: timedelta | None = timedelta(seconds=20),
|
|
350
|
-
) -> DataRequest:
|
|
351
|
-
params = {
|
|
352
|
-
'timeout': 0
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
body = {
|
|
356
|
-
'project_code': project_code,
|
|
357
|
-
'start_time': start_time.isoformat() if isinstance(start_time, datetime) else start_time,
|
|
358
|
-
'end_time': end_time.isoformat() if isinstance(end_time, datetime) else end_time,
|
|
359
|
-
'event_code': event_code,
|
|
360
|
-
'channel_codes': channel_codes,
|
|
361
|
-
'primary_channels': primary_channels,
|
|
362
|
-
'channels_like': channels_like,
|
|
363
|
-
'data_format': file_format,
|
|
364
|
-
'frame_format': frame_format,
|
|
365
|
-
'channel_format': channel_format,
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
request: DataRequest = self._client._request_json(
|
|
369
|
-
'POST',
|
|
370
|
-
'/data/requests',
|
|
371
|
-
params=params,
|
|
372
|
-
json=body)
|
|
373
|
-
|
|
374
|
-
start = time.time()
|
|
375
|
-
while True:
|
|
376
|
-
if timeout is not None and time.time() - start > timeout.total_seconds():
|
|
377
|
-
raise MercutoClientException(
|
|
378
|
-
"Timeout waiting for data request")
|
|
379
|
-
if request['completed_at'] is not None:
|
|
380
|
-
return request
|
|
381
|
-
|
|
382
|
-
time.sleep(1)
|
|
383
|
-
request = self._client._request_json(
|
|
384
|
-
'GET', f'/data/requests/{request["code"]}')
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
class MercutoEventService(Module):
|
|
388
|
-
def __init__(self, client: 'MercutoClient') -> None:
|
|
389
|
-
super().__init__(client)
|
|
390
|
-
|
|
391
|
-
def list_events(self, project: str) -> list[Event]:
|
|
392
|
-
params: dict[str, str] = {'project_code': project}
|
|
393
|
-
return self._client._request_json('GET', '/events', params=params)
|
|
394
|
-
|
|
395
|
-
def get_nearest_event(
|
|
396
|
-
self,
|
|
397
|
-
project_code: str,
|
|
398
|
-
to: datetime | str,
|
|
399
|
-
maximum_delta: timedelta | None = None,
|
|
400
|
-
) -> Event:
|
|
401
|
-
params = {
|
|
402
|
-
'project_code': project_code,
|
|
403
|
-
'to': to.isoformat() if isinstance(to, datetime) else to,
|
|
404
|
-
'maximum_delta': timedelta_isoformat(maximum_delta) if maximum_delta is not None else None,
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
return self.client._request_json('GET', '/events/nearest', params=params)
|
|
408
|
-
|
|
409
|
-
def get_nearest_event_url(
|
|
410
|
-
self,
|
|
411
|
-
project_code: str,
|
|
412
|
-
to: datetime | str,
|
|
413
|
-
maximum_delta: timedelta | None = None,
|
|
414
|
-
file_format: Literal['DAT', 'CSV', 'PARQUET', 'FEATHER'] = 'DAT',
|
|
415
|
-
frame_format: Literal['COLUMNS', 'RECORDS'] = 'COLUMNS',
|
|
416
|
-
) -> str:
|
|
417
|
-
event = self.get_nearest_event(project_code, to, maximum_delta)
|
|
418
|
-
|
|
419
|
-
return self.client.data().get_data_url(
|
|
420
|
-
project_code=project_code,
|
|
421
|
-
event_code=event['code'],
|
|
422
|
-
primary_channels=True,
|
|
423
|
-
file_format=file_format,
|
|
424
|
-
frame_format=frame_format)
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
class MercutoAlertService(Module):
|
|
428
|
-
def __init__(self, client: 'MercutoClient') -> None:
|
|
429
|
-
super().__init__(client)
|
|
430
|
-
|
|
431
|
-
def get_condition(self, code: str) -> Condition:
|
|
432
|
-
return self._client._request_json('GET', f'/alerts/conditions/{code}')
|
|
433
|
-
|
|
434
|
-
def create_condition(self, source: str, description: str, *,
|
|
435
|
-
lower_bound: Optional[float] = None,
|
|
436
|
-
upper_bound: Optional[float] = None,
|
|
437
|
-
neutral_position: float = 0) -> Condition:
|
|
438
|
-
json = {
|
|
439
|
-
'source_channel_code': source,
|
|
440
|
-
'description': description,
|
|
441
|
-
'neutral_position': neutral_position
|
|
442
|
-
}
|
|
443
|
-
if lower_bound is not None:
|
|
444
|
-
json['lower_inclusive_bound'] = lower_bound
|
|
445
|
-
if upper_bound is not None:
|
|
446
|
-
json['upper_exclusive_bound'] = upper_bound
|
|
447
|
-
return self._client._request_json('PUT', '/alerts/conditions', json=json)
|
|
448
|
-
|
|
449
|
-
def create_configuration(self, label: str, conditions: list[str], contact_group: Optional[str] = None) -> AlertConfiguration:
|
|
450
|
-
json = {
|
|
451
|
-
'label': label,
|
|
452
|
-
'conditions': conditions,
|
|
453
|
-
|
|
454
|
-
}
|
|
455
|
-
if contact_group is not None:
|
|
456
|
-
json['contact_group'] = contact_group
|
|
457
|
-
return self._client._request_json('PUT', '/alerts/configurations', json=json)
|
|
458
|
-
|
|
459
|
-
def get_alert_configuration(self, code: str) -> AlertConfiguration:
|
|
460
|
-
return self._client._request_json('GET', f'/alerts/configurations/{code}')
|
|
461
|
-
|
|
462
|
-
def list_logs(
|
|
463
|
-
self,
|
|
464
|
-
project: str | None = None,
|
|
465
|
-
configuration: str | None = None,
|
|
466
|
-
channels: list[str] | None = None,
|
|
467
|
-
start_time: datetime | str | None = None,
|
|
468
|
-
end_time: datetime | str | None = None,
|
|
469
|
-
limit: int = 10,
|
|
470
|
-
offset: int = 0,
|
|
471
|
-
latest_only: bool = False,
|
|
472
|
-
) -> ListAlertsResponseType:
|
|
473
|
-
params = {
|
|
474
|
-
'project': project,
|
|
475
|
-
'configuration_code': configuration,
|
|
476
|
-
'channels': channels,
|
|
477
|
-
'start_time': start_time.isoformat() if isinstance(start_time, datetime) else start_time,
|
|
478
|
-
'end_time': end_time.isoformat() if isinstance(end_time, datetime) else end_time,
|
|
479
|
-
'limit': limit,
|
|
480
|
-
'offset': offset,
|
|
481
|
-
'latest_only': latest_only,
|
|
482
|
-
}
|
|
483
|
-
|
|
484
|
-
return self._client._request_json('GET', '/alerts/logs', params=params)
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
class MercutoProjectService(Module):
|
|
488
|
-
def __init__(self, client: 'MercutoClient') -> None:
|
|
489
|
-
super().__init__(client)
|
|
490
|
-
|
|
491
|
-
def get_project(self, code: str) -> Project:
|
|
492
|
-
return self._client._request_json('GET', f'/projects/{code}')
|
|
493
|
-
|
|
494
|
-
def get_projects(self) -> list[Project]:
|
|
495
|
-
return self._client._request_json('GET', '/projects')
|
|
496
|
-
|
|
497
|
-
def create_project(self, name: str, project_number: str, description: str, tenant: str,
|
|
498
|
-
timezone: str, latitude: Optional[float] = None,
|
|
499
|
-
longitude: Optional[float] = None) -> Project:
|
|
500
|
-
|
|
501
|
-
return self._client._request_json('PUT', '/projects',
|
|
502
|
-
json={'name': name, 'project_number': project_number, 'description': description,
|
|
503
|
-
'tenant_code': tenant,
|
|
504
|
-
'timezone': timezone,
|
|
505
|
-
'latitude': latitude,
|
|
506
|
-
'longitude': longitude,
|
|
507
|
-
'project_type': 1,
|
|
508
|
-
'channels': []})
|
|
509
|
-
|
|
510
|
-
def ping_project(self, project: str, ip_address: str) -> None:
|
|
511
|
-
self._client._request_json(
|
|
512
|
-
'POST', f'/projects/{project}/ping', json={'ip_address': ip_address})
|
|
513
|
-
|
|
514
|
-
def create_channel(self, project: str, label: str, sampling_period: timedelta) -> Channel:
|
|
515
|
-
return self._client._request_json('PUT', '/channels', json={
|
|
516
|
-
'project_code': project,
|
|
517
|
-
'label': label,
|
|
518
|
-
'classification': 'SECONDARY',
|
|
519
|
-
'sampling_period': timedelta_isoformat(sampling_period),
|
|
520
|
-
})
|
|
521
|
-
|
|
522
|
-
def create_dashboard(self, project_code: str, dashboards: Dashboards) -> bool:
|
|
523
|
-
return self._client._request_json('POST', f'/projects/{project_code}/dashboard', json=dashboards) is None
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
class MercutoFatigueService(Module):
|
|
527
|
-
def __init__(self, client: 'MercutoClient') -> None:
|
|
528
|
-
super().__init__(client)
|
|
529
|
-
|
|
530
|
-
def setup_rainflow(self, project: str,
|
|
531
|
-
max_bins: int,
|
|
532
|
-
bin_size: float,
|
|
533
|
-
multiplier: float,
|
|
534
|
-
channels: list[str],
|
|
535
|
-
reservoir_adjustment: bool = True,
|
|
536
|
-
enable_root_mean_cubed: bool = True,
|
|
537
|
-
enable_root_sum_cubed: bool = True) -> RainflowConfiguration:
|
|
538
|
-
return self.client._request_json('PUT', '/fatigue/rainflow/setup', json=dict(
|
|
539
|
-
project=project,
|
|
540
|
-
max_bins=max_bins,
|
|
541
|
-
bin_size=bin_size,
|
|
542
|
-
multiplier=multiplier,
|
|
543
|
-
reservoir_adjustment=reservoir_adjustment,
|
|
544
|
-
channels=channels,
|
|
545
|
-
enable_root_mean_cubed=enable_root_mean_cubed,
|
|
546
|
-
enable_root_sum_cubed=enable_root_sum_cubed
|
|
547
|
-
))
|
|
548
|
-
|
|
549
|
-
def add_connection(self, project: str, label: str,
|
|
550
|
-
multiplier: float, c_d: float, m: float, s_0: float,
|
|
551
|
-
bs7608_failure_probability: float, bs7608_detail_category: str,
|
|
552
|
-
initial_date: datetime, initial_damage: float,
|
|
553
|
-
sources: list[str]) -> FatigueConnection:
|
|
554
|
-
"""
|
|
555
|
-
Sources should be a list of Primary Channel codes.
|
|
556
|
-
"""
|
|
557
|
-
return self.client._request_json('PUT', '/fatigue/connections', json=dict(
|
|
558
|
-
project=project,
|
|
559
|
-
label=label,
|
|
560
|
-
multiplier=multiplier,
|
|
561
|
-
c_d=c_d,
|
|
562
|
-
m=m,
|
|
563
|
-
s_0=s_0,
|
|
564
|
-
bs7608_failure_probability=bs7608_failure_probability,
|
|
565
|
-
bs7608_detail_category=bs7608_detail_category,
|
|
566
|
-
initial_date=initial_date.isoformat(),
|
|
567
|
-
initial_damage=initial_damage,
|
|
568
|
-
sources=sources
|
|
569
|
-
))
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
class MercutoChannelService(Module):
|
|
573
|
-
def __init__(self, client: MercutoClient) -> None:
|
|
574
|
-
super().__init__(client)
|
|
575
|
-
|
|
576
|
-
def get_units(self) -> List[Units]:
|
|
577
|
-
return self._client._request_json('GET', '/channels/units')
|
|
578
|
-
|
|
579
|
-
def create_units(self, name: str, unit: str) -> Units:
|
|
580
|
-
return self._client._request_json('PUT', '/channels/units', json=dict(name=name, unit=unit))
|
|
581
|
-
|
|
582
|
-
def create_channel(self, project_code: str, label: str, classification: CHANNEL_CLASSIFICATION, sampling_period: timedelta) -> Channel:
|
|
583
|
-
return self._client._request_json('PUT', '/channels', json=dict(
|
|
584
|
-
project_code=project_code,
|
|
585
|
-
label=label,
|
|
586
|
-
classification=classification,
|
|
587
|
-
sampling_period=timedelta_isoformat(sampling_period)))
|
|
588
|
-
|
|
589
|
-
def modify_channel(self,
|
|
590
|
-
channel: str,
|
|
591
|
-
label: Optional[str] = None,
|
|
592
|
-
units_code: Optional[str] = None,
|
|
593
|
-
device_code: Optional[str] = None,
|
|
594
|
-
metric: Optional[str] = None,
|
|
595
|
-
multiplier: float = 1,
|
|
596
|
-
offset: float = 0) -> Channel:
|
|
597
|
-
data: dict = {}
|
|
598
|
-
if label is not None:
|
|
599
|
-
data['label'] = label
|
|
600
|
-
if units_code is not None:
|
|
601
|
-
data['units_code'] = units_code
|
|
602
|
-
if metric is not None:
|
|
603
|
-
data['metric'] = metric
|
|
604
|
-
if device_code is not None:
|
|
605
|
-
data['device_code'] = device_code
|
|
606
|
-
data['multiplier'] = multiplier
|
|
607
|
-
data['offset'] = offset
|
|
608
|
-
return self._client._request_json('PATCH', f'/channels/{channel}', json=data)
|
|
609
|
-
|
|
610
|
-
def get_channels(
|
|
611
|
-
self,
|
|
612
|
-
project_code: str | None = None,
|
|
613
|
-
classification: CHANNEL_CLASSIFICATION | None = None,
|
|
614
|
-
aggregate: str | None = None,
|
|
615
|
-
metric: str | None = None,
|
|
616
|
-
) -> list[Channel]:
|
|
617
|
-
limit = 200
|
|
618
|
-
offset = 0
|
|
619
|
-
channels: list[Channel] = []
|
|
620
|
-
|
|
621
|
-
while True:
|
|
622
|
-
params = {
|
|
623
|
-
'project_code': project_code,
|
|
624
|
-
'classification': classification,
|
|
625
|
-
'aggregate': aggregate,
|
|
626
|
-
'metric': metric,
|
|
627
|
-
'limit': limit,
|
|
628
|
-
'offset': offset,
|
|
629
|
-
}
|
|
630
|
-
|
|
631
|
-
resp = self._client._request_json(
|
|
632
|
-
'GET', '/channels', params=params)
|
|
633
|
-
channels.extend(resp)
|
|
634
|
-
|
|
635
|
-
if len(resp) < limit:
|
|
636
|
-
break
|
|
637
|
-
|
|
638
|
-
offset += limit
|
|
639
|
-
|
|
640
|
-
return channels
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
class MercutoDeviceService(Module):
|
|
644
|
-
def __init__(self, client: MercutoClient) -> None:
|
|
645
|
-
super().__init__(client)
|
|
646
|
-
|
|
647
|
-
def get_device_types(self) -> list[DeviceType]:
|
|
648
|
-
return self._client._request_json('GET', '/devices/types')
|
|
649
|
-
|
|
650
|
-
def create_device_type(self, description: str, manufacturer: str, model_number: str) -> DeviceType:
|
|
651
|
-
return self._client._request_json('PUT', '/devices/types', json=dict(
|
|
652
|
-
description=description,
|
|
653
|
-
manufacturer=manufacturer,
|
|
654
|
-
model_number=model_number))
|
|
655
|
-
|
|
656
|
-
def get_devices(self, project_code: str, limit: int, offset: int) -> list[Device]:
|
|
657
|
-
return self._client._request_json('GET', '/devices', params=dict(project_code=project_code, limit=limit, offset=offset))
|
|
658
|
-
|
|
659
|
-
def get_device(self, device_code: str) -> Device:
|
|
660
|
-
return self._client._request_json('GET', f'/devices/{device_code}')
|
|
661
|
-
|
|
662
|
-
def create_device(self,
|
|
663
|
-
project_code: str,
|
|
664
|
-
label: str,
|
|
665
|
-
device_type_code: str,
|
|
666
|
-
groups: list[str],
|
|
667
|
-
location_description: Optional[str] = None) -> Device:
|
|
668
|
-
return self._client._request_json('PUT', '/devices', json=dict(
|
|
669
|
-
project_code=project_code,
|
|
670
|
-
label=label,
|
|
671
|
-
device_type_code=device_type_code,
|
|
672
|
-
groups=groups,
|
|
673
|
-
location_description=location_description))
|
|
674
|
-
|
|
675
|
-
def list_dataloggers(self, project: str) -> list[Datalogger]:
|
|
676
|
-
return self._client._request_json('GET', '/dataloggers', params={'project_code': project})
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
class MercutoIdentityService(Module):
|
|
680
|
-
def __init__(self, client: 'MercutoClient') -> None:
|
|
681
|
-
super().__init__(client)
|
|
682
|
-
|
|
683
|
-
def create_tenant(self, name: str, description: str, logo_url: Optional[str] = None) -> Tenant:
|
|
684
|
-
return self._client._request_json('PUT', '/identity/tenants',
|
|
685
|
-
json={'name': name, 'description': description, 'logo_url': logo_url})
|
|
686
|
-
|
|
687
|
-
def list_tenants(self) -> list[Tenant]:
|
|
688
|
-
return self._client._request_json('GET', '/identity/tenants')
|
|
689
|
-
|
|
690
|
-
def get_tenant(self, code: str) -> Tenant:
|
|
691
|
-
return self._client._request_json('GET', f'/identity/tenants/{code}')
|
|
692
|
-
|
|
693
|
-
def create_user(self, username: str, tenant: str, description: str,
|
|
694
|
-
group: str, password: Optional[str] = None) -> User:
|
|
695
|
-
return self._client._request_json('PUT', '/identity/users',
|
|
696
|
-
json={'username': username, 'tenant_code': tenant, 'description': description,
|
|
697
|
-
'group_code': group, 'default_password': password})
|
|
698
|
-
|
|
699
|
-
def list_users(self, project: Optional[str] = None,
|
|
700
|
-
tenant: Optional[str] = None) -> list[User]:
|
|
701
|
-
params = {}
|
|
702
|
-
if project is not None:
|
|
703
|
-
params['project'] = project
|
|
704
|
-
if tenant is not None:
|
|
705
|
-
params['tenant'] = tenant
|
|
706
|
-
return self._client._request_json('GET', '/identity/users', params=params)
|
|
707
|
-
|
|
708
|
-
def get_user(self, code: str) -> User:
|
|
709
|
-
return self._client._request_json('GET', f'/identity/users/{code}')
|
|
710
|
-
|
|
711
|
-
def get_user_details(self, code: str) -> UserDetails:
|
|
712
|
-
return self._client._request_json('GET', f'/identity/users/{code}/details')
|
|
713
|
-
|
|
714
|
-
def edit_user_details(self, code: str, first_name: Optional[str], last_name: Optional[str],
|
|
715
|
-
email_address: Optional[str], mobile_number: Optional[str]) -> UserDetails:
|
|
716
|
-
return self._client._request_json('PATCH', f'/identity/users/{code}/details', json={
|
|
717
|
-
'first_name': first_name,
|
|
718
|
-
'last_name': last_name,
|
|
719
|
-
'email_address': email_address,
|
|
720
|
-
'mobile_number': mobile_number
|
|
721
|
-
})
|
|
722
|
-
|
|
723
|
-
def generate_api_key(self, user: str, description: str) -> NewUserApiKey:
|
|
724
|
-
return self._client._request_json('POST', f'/identity/users/{user}/api_keys',
|
|
725
|
-
json={'description': description})
|
|
726
|
-
|
|
727
|
-
def grant_user_permission(self, user: str, resource: str, action: str) -> None:
|
|
728
|
-
return self._client._request_json('POST', f'/identity/users/{user}/grant',
|
|
729
|
-
json={'resource': resource, 'action': action})
|
|
730
|
-
|
|
731
|
-
def create_permission_group(self, tenant: str, label: str,
|
|
732
|
-
acl_json: str) -> PermissionGroup:
|
|
733
|
-
return self._client._request_json('PUT', '/identity/permissions', json={
|
|
734
|
-
'tenant': tenant,
|
|
735
|
-
'label': label,
|
|
736
|
-
'acl_policy': acl_json
|
|
737
|
-
})
|
|
738
|
-
|
|
739
|
-
def list_permission_groups(self) -> list[PermissionGroup]:
|
|
740
|
-
return self._client._request_json('GET', '/identity/permissions')
|
|
741
|
-
|
|
742
|
-
def get_permission_group(self, code: str) -> PermissionGroup:
|
|
743
|
-
return self._client._request_json('GET', f'/identity/permissions/{code}')
|
|
744
|
-
|
|
745
|
-
def update_permission_group(self, code: str, label: str,
|
|
746
|
-
acl_json: str) -> None:
|
|
747
|
-
return self._client._request_json('PATCH', f'/identity/permissions/{code}', json={
|
|
748
|
-
'label': label,
|
|
749
|
-
'acl_policy': acl_json
|
|
750
|
-
})
|
|
751
|
-
|
|
752
|
-
def verify_me(self) -> VerifyMeResult:
|
|
753
|
-
return self._client._request_json('GET', '/identity/verify/me')
|
|
754
|
-
|
|
755
|
-
def healthcheck(self) -> AuthHealthcheckResult:
|
|
756
|
-
return self._client._request_json('GET', '/identity/healthcheck')
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
class MercutoNotificationsService(Module):
|
|
760
|
-
def __init__(self, client: 'MercutoClient') -> None:
|
|
761
|
-
super().__init__(client)
|
|
762
|
-
|
|
763
|
-
def list_contact_groups(self, project: Optional[str] = None) -> list[ContactGroup]:
|
|
764
|
-
params = {}
|
|
765
|
-
if project is not None:
|
|
766
|
-
params['project'] = project
|
|
767
|
-
return self._client._request_json('GET', '/notifications/contact_groups', params=params)
|
|
768
|
-
|
|
769
|
-
def get_contact_group(self, code: str) -> ContactGroup:
|
|
770
|
-
return self._client._request_json('GET', f'/notifications/contact_groups/{code}')
|
|
771
|
-
|
|
772
|
-
def create_contact_group(self, project: str, label: str, users: dict[str, list[UserContactMethod]]) -> ContactGroup:
|
|
773
|
-
return self._client._request_json('PUT', '/notifications/contact_groups',
|
|
774
|
-
json={
|
|
775
|
-
'project': project,
|
|
776
|
-
'label': label,
|
|
777
|
-
'users': users
|
|
778
|
-
})
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
class MercutoReportingService(Module):
|
|
782
|
-
def __init__(self, client: 'MercutoClient') -> None:
|
|
783
|
-
super().__init__(client)
|
|
784
|
-
|
|
785
|
-
def list_reports(self, project: Optional[str] = None) -> list['ScheduledReport']:
|
|
786
|
-
params = {}
|
|
787
|
-
if project is not None:
|
|
788
|
-
params['project'] = project
|
|
789
|
-
return self._client._request_json('GET', '/reports/scheduled', params=params)
|
|
790
|
-
|
|
791
|
-
def create_report(self, project: str, label: str, schedule: str, revision: str,
|
|
792
|
-
api_key: Optional[str] = None, contact_group: Optional[str] = None) -> ScheduledReport:
|
|
793
|
-
return self._client._request_json('PUT', '/reports/scheduled', json={
|
|
794
|
-
'project': project,
|
|
795
|
-
'label': label,
|
|
796
|
-
'schedule': schedule,
|
|
797
|
-
'revision': revision,
|
|
798
|
-
'execution_role_api_key': api_key,
|
|
799
|
-
'contact_group': contact_group
|
|
800
|
-
})
|
|
801
|
-
|
|
802
|
-
def generate_report(self, report: str, timestamp: datetime, mark_as_scheduled: bool = False) -> ScheduledReportLog:
|
|
803
|
-
return self._client._request_json('PUT', f'/reports/scheduled/{report}/generate', json={
|
|
804
|
-
'timestamp': timestamp.isoformat(),
|
|
805
|
-
'mark_as_scheduled': mark_as_scheduled
|
|
806
|
-
})
|
|
807
|
-
|
|
808
|
-
def list_report_logs(self, report: str, project: Optional[str] = None) -> list[ScheduledReportLog]:
|
|
809
|
-
params: dict[str, str] = {}
|
|
810
|
-
if project is not None:
|
|
811
|
-
params['project'] = project
|
|
812
|
-
return self._client._request_json('GET', f'/reports/scheduled/{report}/logs', params=params)
|
|
813
|
-
|
|
814
|
-
def get_report_log(self, report: str, log: str) -> ScheduledReportLog:
|
|
815
|
-
return self._client._request_json('GET', f'/reports/scheduled/{report}/logs/{log}')
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
class MercutoObjectService(Module):
|
|
819
|
-
def __init__(self, client: 'MercutoClient') -> None:
|
|
820
|
-
super().__init__(client)
|
|
821
|
-
|
|
822
|
-
def upload_file(self, project_code: str, file: str, event_code: Optional[str] = None,
|
|
823
|
-
mime_type: Optional[str] = None) -> Object:
|
|
824
|
-
with open(file, 'rb') as f:
|
|
825
|
-
if mime_type is None:
|
|
826
|
-
mime_type = mimetypes.guess_type(file, strict=False)[0]
|
|
827
|
-
if mime_type is None:
|
|
828
|
-
raise MercutoClientException(
|
|
829
|
-
f"Could not determine mime type for {file}")
|
|
830
|
-
base64_data = base64.b64encode(f.read()).decode('utf-8')
|
|
831
|
-
data_url = f'data:{mime_type};base64,{base64_data}'
|
|
832
|
-
|
|
833
|
-
return self._client._request_json('POST', '/objects/upload', params={
|
|
834
|
-
'project_code': project_code,
|
|
835
|
-
'event_code': event_code
|
|
836
|
-
}, json={
|
|
837
|
-
'filename': os.path.basename(file),
|
|
838
|
-
'mime_type': mime_type,
|
|
839
|
-
'data_url': data_url
|
|
840
|
-
})
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
class MercutoMediaService(Module):
|
|
844
|
-
def __init__(self, client: 'MercutoClient') -> None:
|
|
845
|
-
super().__init__(client)
|
|
846
|
-
|
|
847
|
-
def list_cameras(self, project: str) -> list[Camera]:
|
|
848
|
-
params = {}
|
|
849
|
-
params['project_code'] = project
|
|
850
|
-
return self._client._request_json('GET', '/media/cameras', params=params)
|
|
851
|
-
|
|
852
|
-
def list_videos(self, project: Optional[str] = None, event: Optional[str] = None, camera: Optional[str] = None) -> list[Video]:
|
|
853
|
-
params = {}
|
|
854
|
-
if project is not None:
|
|
855
|
-
params['project'] = project
|
|
856
|
-
if event is not None:
|
|
857
|
-
params['event'] = event
|
|
858
|
-
if camera is not None:
|
|
859
|
-
params['camera'] = camera
|
|
860
|
-
return self._client._request_json('GET', '/media/videos', params=params)
|
|
861
|
-
|
|
862
|
-
def list_images(self, project: Optional[str] = None, event: Optional[str] = None, camera: Optional[str] = None) -> list[Image]:
|
|
863
|
-
params = {}
|
|
864
|
-
if project is not None:
|
|
865
|
-
params['project'] = project
|
|
866
|
-
if event is not None:
|
|
867
|
-
params['event'] = event
|
|
868
|
-
if camera is not None:
|
|
869
|
-
params['camera'] = camera
|
|
870
|
-
return self._client._request_json('GET', '/media/images', params=params)
|
|
871
|
-
|
|
872
|
-
def get_image(self, code: str) -> Image:
|
|
873
|
-
return self._client._request_json('GET', f'/media/images/{code}') # type: ignore[no-any-return]
|
|
874
|
-
|
|
875
|
-
def upload_image(self, project: str, file: str, event: Optional[str] = None,
|
|
876
|
-
camera: Optional[str] = None, timestamp: Optional[datetime] = None,
|
|
877
|
-
filename: Optional[str] = None) -> Image:
|
|
878
|
-
if timestamp is not None and timestamp.tzinfo is None:
|
|
879
|
-
raise MercutoClientException("Timestamp must be timezone aware")
|
|
880
|
-
|
|
881
|
-
mimetype, _ = mimetypes.guess_type(file, strict=False)
|
|
882
|
-
if mimetype is None or not mimetype.startswith('image/'):
|
|
883
|
-
raise MercutoClientException(f"File {file} is not an image")
|
|
884
|
-
|
|
885
|
-
if os.stat(file).st_size > 5_000_000:
|
|
886
|
-
raise MercutoClientException(f"File {file} is too large")
|
|
887
|
-
|
|
888
|
-
if filename is None:
|
|
889
|
-
filename = os.path.basename(file)
|
|
890
|
-
|
|
891
|
-
params = {}
|
|
892
|
-
params['project'] = project
|
|
893
|
-
if event is not None:
|
|
894
|
-
params['event'] = event
|
|
895
|
-
if camera is not None:
|
|
896
|
-
params['camera'] = camera
|
|
897
|
-
if timestamp is not None:
|
|
898
|
-
params['timestamp'] = timestamp.isoformat()
|
|
899
|
-
|
|
900
|
-
with open(file, 'rb') as f:
|
|
901
|
-
return self._client._request_json('PUT', '/media/images', params=params, files={
|
|
902
|
-
'file': (filename, f, mimetype)
|
|
903
|
-
})
|
|
176
|
+
def is_logged_in(self) -> bool:
|
|
177
|
+
return self._auth_method is not None
|