mercuto-client 0.3.0__py3-none-any.whl → 0.3.4a3__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.
- mercuto_client/_tests/test_ingester/test_file_processor.py +171 -13
- mercuto_client/acl.py +20 -0
- mercuto_client/client.py +41 -8
- mercuto_client/ingester/__main__.py +11 -8
- mercuto_client/ingester/processor.py +57 -37
- mercuto_client/mocks/__init__.py +76 -2
- mercuto_client/mocks/_utility.py +6 -0
- mercuto_client/mocks/mock_core.py +44 -0
- mercuto_client/mocks/mock_media.py +117 -0
- mercuto_client/mocks/mock_notifications.py +65 -0
- mercuto_client/modules/__init__.py +8 -4
- mercuto_client/modules/core.py +117 -287
- mercuto_client/modules/data.py +39 -41
- mercuto_client/modules/fatigue.py +19 -19
- mercuto_client/modules/identity.py +31 -31
- mercuto_client/modules/media.py +324 -0
- mercuto_client/modules/notifications.py +88 -0
- mercuto_client/modules/reports.py +222 -0
- mercuto_client/util.py +1 -1
- {mercuto_client-0.3.0.dist-info → mercuto_client-0.3.4a3.dist-info}/METADATA +1 -1
- {mercuto_client-0.3.0.dist-info → mercuto_client-0.3.4a3.dist-info}/RECORD +24 -18
- {mercuto_client-0.3.0.dist-info → mercuto_client-0.3.4a3.dist-info}/WHEEL +0 -0
- {mercuto_client-0.3.0.dist-info → mercuto_client-0.3.4a3.dist-info}/licenses/LICENSE +0 -0
- {mercuto_client-0.3.0.dist-info → mercuto_client-0.3.4a3.dist-info}/top_level.txt +0 -0
mercuto_client/modules/core.py
CHANGED
|
@@ -1,11 +1,9 @@
|
|
|
1
|
-
import mimetypes
|
|
2
|
-
import os
|
|
3
1
|
from datetime import datetime, timedelta
|
|
4
2
|
from typing import TYPE_CHECKING, Any, Literal, Optional
|
|
5
3
|
|
|
6
4
|
from pydantic import TypeAdapter
|
|
7
5
|
|
|
8
|
-
from . import
|
|
6
|
+
from . import PayloadType
|
|
9
7
|
from ._util import BaseModel, serialise_timedelta
|
|
10
8
|
|
|
11
9
|
if TYPE_CHECKING:
|
|
@@ -100,6 +98,15 @@ class Event(BaseModel):
|
|
|
100
98
|
tags: list[EventTag]
|
|
101
99
|
|
|
102
100
|
|
|
101
|
+
class EventStatisticsOut(BaseModel):
|
|
102
|
+
n_events_last_week: int
|
|
103
|
+
n_events_last_month: int
|
|
104
|
+
n_events_last_year: int
|
|
105
|
+
n_events_all_time: int
|
|
106
|
+
n_events_in_range: int
|
|
107
|
+
last_event: Optional[Event] = None
|
|
108
|
+
|
|
109
|
+
|
|
103
110
|
UserContactMethod = Literal['EMAIL', 'SMS']
|
|
104
111
|
|
|
105
112
|
|
|
@@ -124,7 +131,7 @@ class AlertConfiguration(BaseModel):
|
|
|
124
131
|
project: str
|
|
125
132
|
label: str
|
|
126
133
|
conditions: list[Condition]
|
|
127
|
-
contact_group: Optional[
|
|
134
|
+
contact_group: Optional[str]
|
|
128
135
|
retrigger_interval: Optional[datetime]
|
|
129
136
|
|
|
130
137
|
|
|
@@ -194,85 +201,27 @@ class Device(BaseModel):
|
|
|
194
201
|
channels: list[DeviceChannel]
|
|
195
202
|
|
|
196
203
|
|
|
197
|
-
class
|
|
198
|
-
aggregate: Literal["max", "greatest", "min", "median", "abs-max", "mean", "rms", "peak-to-peak", "daf"]
|
|
199
|
-
enabled: bool = True
|
|
200
|
-
options: Optional[dict[str, Any]] = None
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
class Camera(BaseModel):
|
|
204
|
-
code: str
|
|
205
|
-
project: str
|
|
206
|
-
label: str
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
class Video(BaseModel):
|
|
204
|
+
class DeviceGroup(BaseModel):
|
|
210
205
|
code: str
|
|
211
|
-
project:
|
|
212
|
-
camera: str | None
|
|
213
|
-
start_time: str
|
|
214
|
-
end_time: str
|
|
215
|
-
mime_type: str
|
|
216
|
-
size_bytes: int
|
|
217
|
-
name: str
|
|
218
|
-
event: str | None
|
|
219
|
-
access_url: str | None
|
|
220
|
-
access_expires: str
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
class Image(BaseModel):
|
|
224
|
-
code: str
|
|
225
|
-
project: str
|
|
226
|
-
camera: str | None
|
|
227
|
-
timestamp: str | None
|
|
228
|
-
mime_type: str
|
|
229
|
-
size_bytes: int
|
|
230
|
-
name: str
|
|
231
|
-
event: str | None
|
|
232
|
-
access_url: str | None
|
|
233
|
-
access_expires: str
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
class ScheduledReport(BaseModel):
|
|
237
|
-
code: str
|
|
238
|
-
project: str
|
|
206
|
+
project: ItemCode
|
|
239
207
|
label: str
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
contact_group: Optional[str]
|
|
243
|
-
last_scheduled: Optional[str]
|
|
208
|
+
description: str
|
|
209
|
+
group_label: Optional[str] = None
|
|
244
210
|
|
|
245
211
|
|
|
246
|
-
class
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
actual_finish: Optional[str]
|
|
252
|
-
status: Literal['IN_PROGRESS', 'COMPLETED', 'FAILED']
|
|
253
|
-
message: Optional[str]
|
|
254
|
-
access_url: Optional[str]
|
|
255
|
-
mime_type: Optional[str]
|
|
256
|
-
filename: Optional[str]
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
class ReportSourceCodeRevision(BaseModel):
|
|
260
|
-
code: str
|
|
261
|
-
revision_date: datetime
|
|
262
|
-
description: str
|
|
263
|
-
source_code_url: str
|
|
212
|
+
class EventAggregate(BaseModel):
|
|
213
|
+
aggregate: Literal["max", "greatest", "min", "median",
|
|
214
|
+
"abs-max", "mean", "rms", "peak-to-peak", "daf"]
|
|
215
|
+
enabled: bool = True
|
|
216
|
+
options: Optional[dict[str, Any]] = None
|
|
264
217
|
|
|
265
218
|
|
|
266
219
|
_ProjectListAdapter = TypeAdapter(list[Project])
|
|
267
220
|
_EventsListAdapter = TypeAdapter(list[Event])
|
|
268
221
|
_DevicesListAdapter = TypeAdapter(list[Device])
|
|
269
222
|
_DeviceTypeListAdapter = TypeAdapter(list[DeviceType])
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
_CameraListAdapter = TypeAdapter(list[Camera])
|
|
273
|
-
_ContactGroupListAdapter = TypeAdapter(list[ContactGroup])
|
|
274
|
-
_ScheduledReportListAdapter = TypeAdapter(list[ScheduledReport])
|
|
275
|
-
_ScheduledReportLogListAdapter = TypeAdapter(list[ScheduledReportLog])
|
|
223
|
+
_DeviceGroupListAdapter = TypeAdapter(list[DeviceGroup])
|
|
224
|
+
_ConditionListAdapter = TypeAdapter(list[Condition])
|
|
276
225
|
|
|
277
226
|
|
|
278
227
|
class MercutoCoreService:
|
|
@@ -280,7 +229,7 @@ class MercutoCoreService:
|
|
|
280
229
|
self._client = client
|
|
281
230
|
|
|
282
231
|
def healthcheck(self) -> Healthcheck:
|
|
283
|
-
r = self._client.
|
|
232
|
+
r = self._client.request("/healthcheck", "GET")
|
|
284
233
|
return Healthcheck.model_validate_json(r.text)
|
|
285
234
|
|
|
286
235
|
# Projects
|
|
@@ -288,11 +237,11 @@ class MercutoCoreService:
|
|
|
288
237
|
def get_project(self, code: str) -> Project:
|
|
289
238
|
if len(code) == 0:
|
|
290
239
|
raise ValueError("Project code must not be empty")
|
|
291
|
-
r = self._client.
|
|
240
|
+
r = self._client.request(f'/projects/{code}', 'GET')
|
|
292
241
|
return Project.model_validate_json(r.text)
|
|
293
242
|
|
|
294
243
|
def list_projects(self) -> list[Project]:
|
|
295
|
-
r = self._client.
|
|
244
|
+
r = self._client.request('/projects', 'GET')
|
|
296
245
|
return _ProjectListAdapter.validate_json(r.text)
|
|
297
246
|
|
|
298
247
|
def create_project(self, name: str, project_number: str, description: str, tenant: str,
|
|
@@ -300,7 +249,7 @@ class MercutoCoreService:
|
|
|
300
249
|
latitude: Optional[float] = None,
|
|
301
250
|
longitude: Optional[float] = None) -> Project:
|
|
302
251
|
|
|
303
|
-
payload:
|
|
252
|
+
payload: PayloadType = {
|
|
304
253
|
'name': name,
|
|
305
254
|
'project_number': project_number,
|
|
306
255
|
'description': description,
|
|
@@ -312,25 +261,29 @@ class MercutoCoreService:
|
|
|
312
261
|
if longitude is not None:
|
|
313
262
|
payload['longitude'] = longitude
|
|
314
263
|
|
|
315
|
-
r = self._client.
|
|
264
|
+
r = self._client.request('/projects', 'PUT', json=payload)
|
|
316
265
|
return Project.model_validate_json(r.text)
|
|
317
266
|
|
|
318
267
|
def ping_project(self, project: str, ip_address: str) -> None:
|
|
319
|
-
self._client.
|
|
268
|
+
self._client.request(
|
|
269
|
+
f'/projects/{project}/ping', 'POST', json={'ip_address': ip_address})
|
|
320
270
|
|
|
321
271
|
def create_dashboard(self, project_code: str, dashboards: Dashboards) -> None:
|
|
322
272
|
json = dashboards.model_dump()
|
|
323
|
-
self._client.
|
|
273
|
+
self._client.request(
|
|
274
|
+
f'/projects/{project_code}/dashboard', 'POST', json=json)
|
|
324
275
|
|
|
325
276
|
def set_project_event_detection(self, project: str, datatables: list[str]) -> ProjectEventDetection:
|
|
326
277
|
if len(datatables) == 0:
|
|
327
|
-
raise ValueError(
|
|
278
|
+
raise ValueError(
|
|
279
|
+
'At least one datatable must be provided to enable event detection')
|
|
328
280
|
|
|
329
|
-
params:
|
|
281
|
+
params: PayloadType = {
|
|
330
282
|
"enabled": True,
|
|
331
283
|
"datatables": datatables
|
|
332
284
|
}
|
|
333
|
-
r = self._client.
|
|
285
|
+
r = self._client.request(
|
|
286
|
+
f'/projects/{project}/event-detection', 'POST', json=params)
|
|
334
287
|
return ProjectEventDetection.model_validate_json(r.text)
|
|
335
288
|
|
|
336
289
|
# EVENTS
|
|
@@ -339,25 +292,47 @@ class MercutoCoreService:
|
|
|
339
292
|
if start_time.tzinfo is None or end_time.tzinfo is None:
|
|
340
293
|
raise ValueError("Timestamp must be timezone aware")
|
|
341
294
|
|
|
342
|
-
json:
|
|
295
|
+
json: PayloadType = {
|
|
343
296
|
'project': project,
|
|
344
297
|
'start_time': start_time.isoformat(),
|
|
345
298
|
'end_time': end_time.isoformat(),
|
|
346
299
|
}
|
|
347
|
-
r = self._client.
|
|
300
|
+
r = self._client.request('/events', 'PUT', json=json)
|
|
348
301
|
return Event.model_validate_json(r.text)
|
|
349
302
|
|
|
350
|
-
def list_events(self, project: str
|
|
351
|
-
|
|
352
|
-
|
|
303
|
+
def list_events(self, project: str,
|
|
304
|
+
start_time: Optional[datetime] = None,
|
|
305
|
+
end_time: Optional[datetime] = None,
|
|
306
|
+
limit: Optional[int] = None, offset: Optional[int] = 0,
|
|
307
|
+
ascending: bool = True) -> list[Event]:
|
|
308
|
+
"""
|
|
309
|
+
Lists events for a project, optionally filtered by time range.
|
|
310
|
+
:param project: Project code to list events for.
|
|
311
|
+
:param start_time: Optional start time to filter events from.
|
|
312
|
+
:param end_time: Optional end time to filter events to.
|
|
313
|
+
:param limit: Optional maximum number of events to return. Default is set by API (usually 10).
|
|
314
|
+
:param offset: Optional offset for pagination.
|
|
315
|
+
:param ascending: Whether to sort events in ascending order by start time.
|
|
316
|
+
:return: List of Event objects.
|
|
317
|
+
"""
|
|
318
|
+
params: PayloadType = {'project_code': project, 'ascending': ascending}
|
|
319
|
+
if start_time is not None:
|
|
320
|
+
params['start_time'] = start_time.isoformat()
|
|
321
|
+
if end_time is not None:
|
|
322
|
+
params['end_time'] = end_time.isoformat()
|
|
323
|
+
if limit is not None:
|
|
324
|
+
params['limit'] = limit
|
|
325
|
+
if offset is not None:
|
|
326
|
+
params['offset'] = offset
|
|
327
|
+
r = self._client.request('/events', 'GET', params=params)
|
|
353
328
|
return _EventsListAdapter.validate_json(r.text)
|
|
354
329
|
|
|
355
330
|
def get_event(self, event: str) -> Event:
|
|
356
|
-
r = self._client.
|
|
331
|
+
r = self._client.request(f'/events/{event}', 'GET')
|
|
357
332
|
return Event.model_validate_json(r.text)
|
|
358
333
|
|
|
359
334
|
def delete_event(self, event: str) -> None:
|
|
360
|
-
self._client.
|
|
335
|
+
self._client.request(f'/events/{event}', 'DELETE')
|
|
361
336
|
|
|
362
337
|
def get_nearest_event(
|
|
363
338
|
self,
|
|
@@ -365,32 +340,55 @@ class MercutoCoreService:
|
|
|
365
340
|
to: datetime,
|
|
366
341
|
maximum_delta: timedelta | None = None,
|
|
367
342
|
) -> Event:
|
|
368
|
-
params:
|
|
343
|
+
params: PayloadType = {
|
|
369
344
|
'project_code': project_code,
|
|
370
345
|
'to': to.isoformat(),
|
|
371
346
|
}
|
|
372
347
|
if maximum_delta is not None:
|
|
373
348
|
params['maximum_delta'] = serialise_timedelta(maximum_delta)
|
|
374
349
|
|
|
375
|
-
r = self._client.
|
|
350
|
+
r = self._client.request('/events/nearest', 'GET', params=params)
|
|
376
351
|
return Event.model_validate_json(r.text)
|
|
377
352
|
|
|
353
|
+
def get_event_statistics(
|
|
354
|
+
self,
|
|
355
|
+
project_code: str,
|
|
356
|
+
start_time: datetime,
|
|
357
|
+
end_time: datetime,
|
|
358
|
+
) -> EventStatisticsOut:
|
|
359
|
+
params: PayloadType = {
|
|
360
|
+
'project_code': project_code,
|
|
361
|
+
'start_time': start_time.isoformat(),
|
|
362
|
+
'end_time': end_time.isoformat(),
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
r = self._client.request('/events/statistics', 'GET', params=params)
|
|
366
|
+
return EventStatisticsOut.model_validate_json(r.text)
|
|
367
|
+
|
|
378
368
|
def set_event_aggregates(self, project: str, aggregates: list[EventAggregate]) -> None:
|
|
379
|
-
self._client.
|
|
380
|
-
|
|
381
|
-
|
|
369
|
+
self._client.request('/aggregates', 'PUT',
|
|
370
|
+
json=[agg.model_dump(mode='json') for agg in aggregates], # type: ignore
|
|
371
|
+
params={'project_code': project})
|
|
382
372
|
|
|
383
373
|
# ALERTS
|
|
374
|
+
def list_conditions(self, project: str, limit: int = 100, offset: int = 0) -> list[Condition]:
|
|
375
|
+
params: PayloadType = {
|
|
376
|
+
'project': project,
|
|
377
|
+
'limit': limit,
|
|
378
|
+
'offset': offset
|
|
379
|
+
}
|
|
380
|
+
r = self._client.request('/alerts/conditions', 'GET', params=params)
|
|
381
|
+
return _ConditionListAdapter.validate_json(r.text)
|
|
384
382
|
|
|
385
383
|
def get_condition(self, code: str) -> Condition:
|
|
386
|
-
r = self._client.
|
|
384
|
+
r = self._client.request(f'/alerts/conditions/{code}', 'GET')
|
|
387
385
|
return Condition.model_validate_json(r.text)
|
|
388
386
|
|
|
389
387
|
def create_condition(self, source: str, description: str, *,
|
|
390
388
|
lower_bound: Optional[float] = None,
|
|
391
389
|
upper_bound: Optional[float] = None,
|
|
392
390
|
neutral_position: float = 0) -> Condition:
|
|
393
|
-
json:
|
|
391
|
+
json: PayloadType = {
|
|
394
392
|
'source_channel_code': source,
|
|
395
393
|
'description': description,
|
|
396
394
|
'neutral_position': neutral_position
|
|
@@ -399,24 +397,25 @@ class MercutoCoreService:
|
|
|
399
397
|
json['lower_inclusive_bound'] = lower_bound
|
|
400
398
|
if upper_bound is not None:
|
|
401
399
|
json['upper_exclusive_bound'] = upper_bound
|
|
402
|
-
r = self._client.
|
|
400
|
+
r = self._client.request('/alerts/conditions', 'PUT', json=json)
|
|
403
401
|
return Condition.model_validate_json(r.text)
|
|
404
402
|
|
|
405
403
|
def create_alert_configuration(self, label: str,
|
|
406
404
|
conditions: list[str],
|
|
407
405
|
contact_group: Optional[str] = None) -> AlertConfiguration:
|
|
408
|
-
json:
|
|
406
|
+
json: PayloadType = {
|
|
409
407
|
'label': label,
|
|
410
408
|
'conditions': conditions,
|
|
411
409
|
|
|
412
410
|
}
|
|
413
411
|
if contact_group is not None:
|
|
414
412
|
json['contact_group'] = contact_group
|
|
415
|
-
r = self._client.
|
|
413
|
+
r = self._client.request(
|
|
414
|
+
'/alerts/configurations', 'PUT', json=json)
|
|
416
415
|
return AlertConfiguration.model_validate_json(r.text)
|
|
417
416
|
|
|
418
417
|
def get_alert_configuration(self, code: str) -> AlertConfiguration:
|
|
419
|
-
r = self._client.
|
|
418
|
+
r = self._client.request(f'/alerts/configurations/{code}', 'GET')
|
|
420
419
|
return AlertConfiguration.model_validate_json(r.text)
|
|
421
420
|
|
|
422
421
|
def list_alert_logs(
|
|
@@ -430,7 +429,7 @@ class MercutoCoreService:
|
|
|
430
429
|
offset: int = 0,
|
|
431
430
|
latest_only: bool = False,
|
|
432
431
|
) -> AlertSummary:
|
|
433
|
-
params:
|
|
432
|
+
params: PayloadType = {
|
|
434
433
|
'limit': limit,
|
|
435
434
|
'offset': offset,
|
|
436
435
|
'latest_only': latest_only,
|
|
@@ -443,39 +442,41 @@ class MercutoCoreService:
|
|
|
443
442
|
if channels is not None:
|
|
444
443
|
params['channels'] = channels
|
|
445
444
|
if start_time is not None:
|
|
446
|
-
params['start_time'] = start_time.isoformat() if isinstance(
|
|
445
|
+
params['start_time'] = start_time.isoformat() if isinstance(
|
|
446
|
+
start_time, datetime) else start_time
|
|
447
447
|
if end_time is not None:
|
|
448
|
-
params['end_time'] = end_time.isoformat() if isinstance(
|
|
448
|
+
params['end_time'] = end_time.isoformat() if isinstance(
|
|
449
|
+
end_time, datetime) else end_time
|
|
449
450
|
|
|
450
|
-
r = self._client.
|
|
451
|
+
r = self._client.request('/alerts/logs', 'GET', params=params)
|
|
451
452
|
return AlertSummary.model_validate_json(r.text)
|
|
452
453
|
|
|
453
454
|
# DEVICES
|
|
454
455
|
|
|
455
456
|
def list_device_types(self) -> list[DeviceType]:
|
|
456
|
-
r = self._client.
|
|
457
|
+
r = self._client.request('/devices/types', 'GET')
|
|
457
458
|
return _DeviceTypeListAdapter.validate_json(r.text)
|
|
458
459
|
|
|
459
460
|
def create_device_type(self, description: str, manufacturer: str, model_number: str) -> DeviceType:
|
|
460
|
-
json:
|
|
461
|
+
json: PayloadType = {
|
|
461
462
|
'description': description,
|
|
462
463
|
'manufacturer': manufacturer,
|
|
463
464
|
'model_number': model_number
|
|
464
465
|
}
|
|
465
|
-
r = self._client.
|
|
466
|
+
r = self._client.request('/devices/types', 'PUT', json=json)
|
|
466
467
|
return DeviceType.model_validate_json(r.text)
|
|
467
468
|
|
|
468
469
|
def list_devices(self, project_code: str, limit: int, offset: int) -> list[Device]:
|
|
469
|
-
params:
|
|
470
|
+
params: PayloadType = {
|
|
470
471
|
'project_code': project_code,
|
|
471
472
|
'limit': limit,
|
|
472
473
|
'offset': offset
|
|
473
474
|
}
|
|
474
|
-
r = self._client.
|
|
475
|
+
r = self._client.request('/devices', 'GET', params=params)
|
|
475
476
|
return _DevicesListAdapter.validate_json(r.text)
|
|
476
477
|
|
|
477
478
|
def get_device(self, device_code: str) -> Device:
|
|
478
|
-
r = self._client.
|
|
479
|
+
r = self._client.request(f'/devices/{device_code}', 'GET')
|
|
479
480
|
return Device.model_validate_json(r.text)
|
|
480
481
|
|
|
481
482
|
def create_device(self,
|
|
@@ -485,7 +486,7 @@ class MercutoCoreService:
|
|
|
485
486
|
groups: list[str],
|
|
486
487
|
location_description: Optional[str] = None,
|
|
487
488
|
channels: Optional[list[DeviceChannel]] = None) -> Device:
|
|
488
|
-
json:
|
|
489
|
+
json: PayloadType = {
|
|
489
490
|
'project_code': project_code,
|
|
490
491
|
'label': label,
|
|
491
492
|
'device_type_code': device_type_code,
|
|
@@ -495,180 +496,9 @@ class MercutoCoreService:
|
|
|
495
496
|
json['location_description'] = location_description
|
|
496
497
|
if channels is not None:
|
|
497
498
|
json['channels'] = [channel.model_dump(mode='json') for channel in channels] # type: ignore[assignment]
|
|
498
|
-
r = self._client.
|
|
499
|
+
r = self._client.request('/devices', 'PUT', json=json)
|
|
499
500
|
return Device.model_validate_json(r.text)
|
|
500
501
|
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
params: _PayloadType = {}
|
|
505
|
-
params['project_code'] = project
|
|
506
|
-
r = self._client._http_request('/media/cameras', 'GET', params=params)
|
|
507
|
-
return _CameraListAdapter.validate_json(r.text)
|
|
508
|
-
|
|
509
|
-
def list_videos(self, project: Optional[str] = None, event: Optional[str] = None, camera: Optional[str] = None) -> list[Video]:
|
|
510
|
-
params: _PayloadType = {}
|
|
511
|
-
if project is not None:
|
|
512
|
-
params['project'] = project
|
|
513
|
-
if event is not None:
|
|
514
|
-
params['event'] = event
|
|
515
|
-
if camera is not None:
|
|
516
|
-
params['camera'] = camera
|
|
517
|
-
r = self._client._http_request('/media/videos', 'GET', params=params)
|
|
518
|
-
return _VideoListAdapter.validate_json(r.text)
|
|
519
|
-
|
|
520
|
-
def get_video(self, code: str) -> Video:
|
|
521
|
-
r = self._client._http_request(f'/media/videos/{code}', 'GET')
|
|
522
|
-
return Video.model_validate_json(r.text)
|
|
523
|
-
|
|
524
|
-
def list_images(self, project: Optional[str] = None, event: Optional[str] = None, camera: Optional[str] = None) -> list[Image]:
|
|
525
|
-
params = {}
|
|
526
|
-
if project is not None:
|
|
527
|
-
params['project'] = project
|
|
528
|
-
if event is not None:
|
|
529
|
-
params['event'] = event
|
|
530
|
-
if camera is not None:
|
|
531
|
-
params['camera'] = camera
|
|
532
|
-
r = self._client._http_request('/media/images', 'GET', params=params)
|
|
533
|
-
return _ImageListAdapter.validate_json(r.text)
|
|
534
|
-
|
|
535
|
-
def get_image(self, code: str) -> Image:
|
|
536
|
-
r = self._client._http_request(f'/media/images/{code}', 'GET')
|
|
537
|
-
return Image.model_validate_json(r.text)
|
|
538
|
-
|
|
539
|
-
def upload_image(self, project: str, file: str, event: Optional[str] = None,
|
|
540
|
-
camera: Optional[str] = None, timestamp: Optional[datetime] = None,
|
|
541
|
-
filename: Optional[str] = None) -> Image:
|
|
542
|
-
if timestamp is not None and timestamp.tzinfo is None:
|
|
543
|
-
raise ValueError("Timestamp must be timezone aware")
|
|
544
|
-
|
|
545
|
-
mimetype, _ = mimetypes.guess_type(file, strict=False)
|
|
546
|
-
if mimetype is None or not mimetype.startswith('image/'):
|
|
547
|
-
raise ValueError(f"File {file} is not an image")
|
|
548
|
-
|
|
549
|
-
if os.stat(file).st_size > 5_000_000:
|
|
550
|
-
raise ValueError(f"File {file} is too large")
|
|
551
|
-
|
|
552
|
-
if filename is None:
|
|
553
|
-
filename = os.path.basename(file)
|
|
554
|
-
|
|
555
|
-
params: _PayloadType = {}
|
|
556
|
-
params['project'] = project
|
|
557
|
-
if event is not None:
|
|
558
|
-
params['event'] = event
|
|
559
|
-
if camera is not None:
|
|
560
|
-
params['camera'] = camera
|
|
561
|
-
if timestamp is not None:
|
|
562
|
-
params['timestamp'] = timestamp.isoformat()
|
|
563
|
-
|
|
564
|
-
with open(file, 'rb') as f:
|
|
565
|
-
r = self._client._http_request('/media/images', 'PUT',
|
|
566
|
-
params=params,
|
|
567
|
-
files={
|
|
568
|
-
'file': (filename, f, mimetype)
|
|
569
|
-
})
|
|
570
|
-
return Image.model_validate_json(r.text)
|
|
571
|
-
|
|
572
|
-
def upload_video(self, project: str, file: str,
|
|
573
|
-
start_time: datetime, end_time: datetime,
|
|
574
|
-
event: Optional[str] = None,
|
|
575
|
-
filename: Optional[str] = None) -> Video:
|
|
576
|
-
if start_time.tzinfo is None:
|
|
577
|
-
raise ValueError("Timestamp must be timezone aware")
|
|
578
|
-
if end_time.tzinfo is None:
|
|
579
|
-
raise ValueError("Timestamp must be timezone aware")
|
|
580
|
-
|
|
581
|
-
mimetype, _ = mimetypes.guess_type(file, strict=False)
|
|
582
|
-
if mimetype is None or not mimetype.startswith('video/'):
|
|
583
|
-
raise ValueError(f"File {file} is not a video")
|
|
584
|
-
|
|
585
|
-
if os.stat(file).st_size > 5_000_000:
|
|
586
|
-
raise ValueError(f"File {file} is too large")
|
|
587
|
-
|
|
588
|
-
if filename is None:
|
|
589
|
-
filename = os.path.basename(file)
|
|
590
|
-
|
|
591
|
-
params: _PayloadType = {}
|
|
592
|
-
params['project'] = project
|
|
593
|
-
params['start_time'] = start_time.isoformat()
|
|
594
|
-
params['end_time'] = end_time.isoformat()
|
|
595
|
-
if event is not None:
|
|
596
|
-
params['event'] = event
|
|
597
|
-
|
|
598
|
-
with open(file, 'rb') as f:
|
|
599
|
-
r = self._client._http_request('/media/videos', 'PUT',
|
|
600
|
-
params=params,
|
|
601
|
-
files={
|
|
602
|
-
'file': (filename, f, mimetype)
|
|
603
|
-
})
|
|
604
|
-
return Video.model_validate_json(r.text)
|
|
605
|
-
|
|
606
|
-
# Contacts
|
|
607
|
-
|
|
608
|
-
def list_contact_groups(self, project: Optional[str] = None) -> list[ContactGroup]:
|
|
609
|
-
params: _PayloadType = {}
|
|
610
|
-
if project is not None:
|
|
611
|
-
params['project'] = project
|
|
612
|
-
r = self._client._http_request('/notifications/contact_groups', 'GET', params=params)
|
|
613
|
-
return _ContactGroupListAdapter.validate_json(r.text)
|
|
614
|
-
|
|
615
|
-
def get_contact_group(self, code: str) -> ContactGroup:
|
|
616
|
-
r = self._client._http_request(f'/notifications/contact_groups/{code}', 'GET')
|
|
617
|
-
return ContactGroup.model_validate_json(r.text)
|
|
618
|
-
|
|
619
|
-
def create_contact_group(self, project: str, label: str, users: dict[str, list[UserContactMethod]]) -> ContactGroup:
|
|
620
|
-
r = self._client._http_request('/notifications/contact_groups', 'PUT',
|
|
621
|
-
json={
|
|
622
|
-
'project': project,
|
|
623
|
-
'label': label,
|
|
624
|
-
'users': users
|
|
625
|
-
})
|
|
626
|
-
return ContactGroup.model_validate_json(r.text)
|
|
627
|
-
|
|
628
|
-
# Reports
|
|
629
|
-
def list_reports(self, project: Optional[str] = None) -> list['ScheduledReport']:
|
|
630
|
-
params: _PayloadType = {}
|
|
631
|
-
if project is not None:
|
|
632
|
-
params['project'] = project
|
|
633
|
-
r = self._client._http_request('/reports/scheduled', 'GET', params=params)
|
|
634
|
-
return _ScheduledReportListAdapter.validate_json(r.text)
|
|
635
|
-
|
|
636
|
-
def create_report(self, project: str, label: str, schedule: str, revision: str,
|
|
637
|
-
api_key: Optional[str] = None, contact_group: Optional[str] = None) -> ScheduledReport:
|
|
638
|
-
json: _PayloadType = {
|
|
639
|
-
'project': project,
|
|
640
|
-
'label': label,
|
|
641
|
-
'schedule': schedule,
|
|
642
|
-
'revision': revision,
|
|
643
|
-
'execution_role_api_key': api_key,
|
|
644
|
-
'contact_group': contact_group
|
|
645
|
-
}
|
|
646
|
-
r = self._client._http_request('/reports/scheduled', 'PUT', json=json)
|
|
647
|
-
return ScheduledReport.model_validate_json(r.text)
|
|
648
|
-
|
|
649
|
-
def generate_report(self, report: str, timestamp: datetime, mark_as_scheduled: bool = False) -> ScheduledReportLog:
|
|
650
|
-
r = self._client._http_request(f'/reports/scheduled/{report}/generate', 'PUT', json={
|
|
651
|
-
'timestamp': timestamp.isoformat(),
|
|
652
|
-
'mark_as_scheduled': mark_as_scheduled
|
|
653
|
-
})
|
|
654
|
-
return ScheduledReportLog.model_validate_json(r.text)
|
|
655
|
-
|
|
656
|
-
def list_report_logs(self, report: str, project: Optional[str] = None) -> list[ScheduledReportLog]:
|
|
657
|
-
params: _PayloadType = {}
|
|
658
|
-
if project is not None:
|
|
659
|
-
params['project'] = project
|
|
660
|
-
r = self._client._http_request(f'/reports/scheduled/{report}/logs', 'GET', params=params)
|
|
661
|
-
return _ScheduledReportLogListAdapter.validate_json(r.text)
|
|
662
|
-
|
|
663
|
-
def get_report_log(self, report: str, log: str) -> ScheduledReportLog:
|
|
664
|
-
r = self._client._http_request(f'/reports/scheduled/{report}/logs/{log}', 'GET')
|
|
665
|
-
return ScheduledReportLog.model_validate_json(r.text)
|
|
666
|
-
|
|
667
|
-
def create_report_revision(self, project: str, revision_date: datetime, description: str, source_code_data_url: str) -> ReportSourceCodeRevision:
|
|
668
|
-
json = {
|
|
669
|
-
'revision_date': revision_date.isoformat(),
|
|
670
|
-
'description': description,
|
|
671
|
-
'source_code_data_url': source_code_data_url,
|
|
672
|
-
}
|
|
673
|
-
r = self._client._http_request('/reports/revisions', 'PUT', json=json, params={'project': project})
|
|
674
|
-
return ReportSourceCodeRevision.model_validate_json(r.text)
|
|
502
|
+
def list_device_groups(self, project: str) -> list[DeviceGroup]:
|
|
503
|
+
r = self._client.request('/devices/groups', 'GET', params={'project_code': project})
|
|
504
|
+
return _DeviceGroupListAdapter.validate_json(r.text)
|