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
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
from datetime import datetime
|
|
2
|
+
from typing import TYPE_CHECKING, Literal, Optional
|
|
3
|
+
|
|
4
|
+
from pydantic import Field, TypeAdapter
|
|
5
|
+
|
|
6
|
+
if TYPE_CHECKING:
|
|
7
|
+
from ..client import MercutoClient
|
|
8
|
+
|
|
9
|
+
from . import _PayloadType
|
|
10
|
+
from ._util import BaseModel
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class RainflowConfiguration(BaseModel):
|
|
14
|
+
project: str
|
|
15
|
+
max_bins: int
|
|
16
|
+
bin_size: float
|
|
17
|
+
multiplier: float
|
|
18
|
+
reservoir_adjustment: bool
|
|
19
|
+
sources: list[str]
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class FatigueConnection(BaseModel):
|
|
23
|
+
project: str
|
|
24
|
+
code: str
|
|
25
|
+
label: str
|
|
26
|
+
multiplier: float
|
|
27
|
+
c_d: float
|
|
28
|
+
m: float
|
|
29
|
+
s_0: float
|
|
30
|
+
bs7608_failure_probability: Optional[float]
|
|
31
|
+
bs7608_detail_category: Optional[str]
|
|
32
|
+
initial_date: datetime
|
|
33
|
+
initial_damage: float
|
|
34
|
+
sources: list[str]
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class ConnectionRemnantCapacity(BaseModel):
|
|
38
|
+
connection: FatigueConnection
|
|
39
|
+
remaining_life_years: float = Field(description="Remaining life of the connection in years")
|
|
40
|
+
total_damage: float = Field(description="Total damage accumulated in the connection up to the 'end_time' specified")
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class Healthcheck(BaseModel):
|
|
44
|
+
status: str
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
_RainflowConfigurationlistAdapter = TypeAdapter(list[RainflowConfiguration])
|
|
48
|
+
_FatigueConnectionlistAdapter = TypeAdapter(list[FatigueConnection])
|
|
49
|
+
_ConnectionRemnantCapacitylistAdapter = TypeAdapter(list[ConnectionRemnantCapacity])
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class MercutoFatigueService:
|
|
53
|
+
def __init__(self, client: 'MercutoClient', path: str = '/fatigue') -> None:
|
|
54
|
+
self._client = client
|
|
55
|
+
self._path = path
|
|
56
|
+
|
|
57
|
+
def healthcheck(self) -> Healthcheck:
|
|
58
|
+
r = self._client._http_request(f"{self._path}/healthcheck", "GET")
|
|
59
|
+
return Healthcheck.model_validate_json(r.text)
|
|
60
|
+
|
|
61
|
+
# --- Rainflow routes ---
|
|
62
|
+
|
|
63
|
+
def list_rainflow_config(self, project: str) -> list[RainflowConfiguration]:
|
|
64
|
+
params: _PayloadType = {"project": project}
|
|
65
|
+
r = self._client._http_request(f"{self._path}/rainflow/setup", "GET", params=params)
|
|
66
|
+
return _RainflowConfigurationlistAdapter.validate_json(r.text)
|
|
67
|
+
|
|
68
|
+
def setup_rainflow(
|
|
69
|
+
self,
|
|
70
|
+
project: str,
|
|
71
|
+
max_bins: int,
|
|
72
|
+
bin_size: float,
|
|
73
|
+
multiplier: float,
|
|
74
|
+
reservoir_adjustment: bool,
|
|
75
|
+
sources: list[str]
|
|
76
|
+
) -> RainflowConfiguration:
|
|
77
|
+
payload: _PayloadType = {
|
|
78
|
+
"project": project,
|
|
79
|
+
"max_bins": max_bins,
|
|
80
|
+
"bin_size": bin_size,
|
|
81
|
+
"multiplier": multiplier,
|
|
82
|
+
"reservoir_adjustment": reservoir_adjustment,
|
|
83
|
+
"sources": sources,
|
|
84
|
+
}
|
|
85
|
+
r = self._client._http_request(f"{self._path}/rainflow/setup", "PUT", json=payload)
|
|
86
|
+
return RainflowConfiguration.model_validate_json(r.text)
|
|
87
|
+
|
|
88
|
+
def get_cycle_counts(
|
|
89
|
+
self, project: str, start_time: datetime, end_time: datetime
|
|
90
|
+
) -> bytes:
|
|
91
|
+
params: _PayloadType = {
|
|
92
|
+
"project": project,
|
|
93
|
+
"start_time": start_time.isoformat(),
|
|
94
|
+
"end_time": end_time.isoformat(),
|
|
95
|
+
}
|
|
96
|
+
r = self._client._http_request(
|
|
97
|
+
f"{self._path}/rainflow/cycle_counts", "GET", params=params, stream=True
|
|
98
|
+
)
|
|
99
|
+
return r.content
|
|
100
|
+
|
|
101
|
+
def delete_cycle_counts(
|
|
102
|
+
self, project: str, start_time: datetime, end_time: datetime, ignore_if_not_configured: bool = False
|
|
103
|
+
) -> None:
|
|
104
|
+
params: _PayloadType = {
|
|
105
|
+
"project": project,
|
|
106
|
+
"start_time": start_time.isoformat(),
|
|
107
|
+
"end_time": end_time.isoformat(),
|
|
108
|
+
"ignore_if_not_configured": ignore_if_not_configured,
|
|
109
|
+
}
|
|
110
|
+
self._client._http_request(
|
|
111
|
+
f"{self._path}/rainflow/cycle_counts", "DELETE", params=params
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
def calculate_cycle_counts(
|
|
115
|
+
self,
|
|
116
|
+
project: str,
|
|
117
|
+
event: str,
|
|
118
|
+
presigned_url: str,
|
|
119
|
+
mime_type: Literal['application/feather'],
|
|
120
|
+
url_expiry: Optional[datetime] = None,
|
|
121
|
+
ignore_if_not_configured: bool = False
|
|
122
|
+
) -> None:
|
|
123
|
+
payload: _PayloadType = {
|
|
124
|
+
"project": project,
|
|
125
|
+
"event": event,
|
|
126
|
+
"presigned_url": presigned_url,
|
|
127
|
+
"mime_type": mime_type,
|
|
128
|
+
}
|
|
129
|
+
if url_expiry is not None:
|
|
130
|
+
payload["url_expiry"] = url_expiry.isoformat()
|
|
131
|
+
params = {"ignore_if_not_configured": ignore_if_not_configured}
|
|
132
|
+
self._client._http_request(
|
|
133
|
+
f"{self._path}/rainflow/cycle_counts/calculate", "PUT", json=payload, params=params
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
# --- Fatigue Connections routes ---
|
|
137
|
+
|
|
138
|
+
def get_connections(self, project: str) -> list[FatigueConnection]:
|
|
139
|
+
params: _PayloadType = {"project": project}
|
|
140
|
+
r = self._client._http_request(f"{self._path}/connections", "GET", params=params)
|
|
141
|
+
return _FatigueConnectionlistAdapter.validate_json(r.text)
|
|
142
|
+
|
|
143
|
+
def add_connection(
|
|
144
|
+
self,
|
|
145
|
+
project: str,
|
|
146
|
+
label: str,
|
|
147
|
+
multiplier: float,
|
|
148
|
+
c_d: float,
|
|
149
|
+
m: float,
|
|
150
|
+
s_0: float,
|
|
151
|
+
bs7608_failure_probability: float,
|
|
152
|
+
bs7608_detail_category: str,
|
|
153
|
+
initial_date: datetime,
|
|
154
|
+
initial_damage: float,
|
|
155
|
+
sources: list[str]
|
|
156
|
+
) -> FatigueConnection:
|
|
157
|
+
payload: _PayloadType = {
|
|
158
|
+
"project": project,
|
|
159
|
+
"label": label,
|
|
160
|
+
"multiplier": multiplier,
|
|
161
|
+
"c_d": c_d,
|
|
162
|
+
"m": m,
|
|
163
|
+
"s_0": s_0,
|
|
164
|
+
"bs7608_failure_probability": bs7608_failure_probability,
|
|
165
|
+
"bs7608_detail_category": bs7608_detail_category,
|
|
166
|
+
"initial_date": initial_date.isoformat(),
|
|
167
|
+
"initial_damage": initial_damage,
|
|
168
|
+
"sources": sources,
|
|
169
|
+
}
|
|
170
|
+
r = self._client._http_request(f"{self._path}/connections", "PUT", json=payload)
|
|
171
|
+
return FatigueConnection.model_validate_json(r.text)
|
|
172
|
+
|
|
173
|
+
def delete_connection(self, connection_code: str) -> None:
|
|
174
|
+
self._client._http_request(f"{self._path}/connections/{connection_code}", "DELETE")
|
|
175
|
+
|
|
176
|
+
# --- Connection Data routes ---
|
|
177
|
+
|
|
178
|
+
def get_connection_remnant_capacity(
|
|
179
|
+
self, project: str, start_time: datetime, end_time: datetime
|
|
180
|
+
) -> list[ConnectionRemnantCapacity]:
|
|
181
|
+
params: _PayloadType = {
|
|
182
|
+
"project": project,
|
|
183
|
+
"start_time": start_time.isoformat(),
|
|
184
|
+
"end_time": end_time.isoformat(),
|
|
185
|
+
}
|
|
186
|
+
r = self._client._http_request(
|
|
187
|
+
f"{self._path}/connection_data/remnant-capacity", "GET", params=params
|
|
188
|
+
)
|
|
189
|
+
return _ConnectionRemnantCapacitylistAdapter.validate_json(r.text)
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
from typing import TYPE_CHECKING, Optional
|
|
2
|
+
|
|
3
|
+
from pydantic import TypeAdapter
|
|
4
|
+
|
|
5
|
+
if TYPE_CHECKING:
|
|
6
|
+
from ..client import MercutoClient
|
|
7
|
+
|
|
8
|
+
from . import _PayloadType
|
|
9
|
+
from ._util import BaseModel
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class PermissionGroup(BaseModel):
|
|
13
|
+
tenant: str
|
|
14
|
+
code: str
|
|
15
|
+
label: str
|
|
16
|
+
acl_policy: str
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class Tenant(BaseModel):
|
|
20
|
+
code: str
|
|
21
|
+
name: str
|
|
22
|
+
description: str
|
|
23
|
+
logo_url: Optional[str] = None
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class HiddenUserAPIKey(BaseModel):
|
|
27
|
+
code: str
|
|
28
|
+
description: str
|
|
29
|
+
last_used: Optional[str]
|
|
30
|
+
custom_policy: Optional[str] = None
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class UserDetails(BaseModel):
|
|
34
|
+
code: str
|
|
35
|
+
username: Optional[str] = None
|
|
36
|
+
email_address: Optional[str] = None
|
|
37
|
+
mobile_number: Optional[str] = None
|
|
38
|
+
first_name: Optional[str] = None
|
|
39
|
+
last_name: Optional[str] = None
|
|
40
|
+
api_keys: list[HiddenUserAPIKey] = []
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class User(BaseModel):
|
|
44
|
+
code: str
|
|
45
|
+
username: Optional[str] = None
|
|
46
|
+
description: str
|
|
47
|
+
tenant: str
|
|
48
|
+
permission_group: str
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class CurrentUser(BaseModel):
|
|
52
|
+
code: str
|
|
53
|
+
username: Optional[str] = None
|
|
54
|
+
description: str
|
|
55
|
+
tenant: Tenant
|
|
56
|
+
permission_group: PermissionGroup
|
|
57
|
+
current_permission_policy: str
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class VisibleUserAPIKey(BaseModel):
|
|
61
|
+
code: str
|
|
62
|
+
new_api_key: str
|
|
63
|
+
description: str
|
|
64
|
+
custom_policy: Optional[str]
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class VerifyMyPermissions(BaseModel):
|
|
68
|
+
user: Optional[str]
|
|
69
|
+
acl_policy: str
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class Healthcheck(BaseModel):
|
|
73
|
+
status: str
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
# --- TypeAdapters for lists ---
|
|
77
|
+
_PermissionGrouplistAdapter = TypeAdapter(list[PermissionGroup])
|
|
78
|
+
_TenantlistAdapter = TypeAdapter(list[Tenant])
|
|
79
|
+
_UserlistAdapter = TypeAdapter(list[User])
|
|
80
|
+
_HiddenUserAPIKeylistAdapter = TypeAdapter(list[HiddenUserAPIKey])
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
class MercutoIdentityService:
|
|
84
|
+
def __init__(self, client: 'MercutoClient', path: str = '/identity') -> None:
|
|
85
|
+
self._client = client
|
|
86
|
+
self._path = path
|
|
87
|
+
|
|
88
|
+
def healthcheck(self) -> Healthcheck:
|
|
89
|
+
r = self._client._http_request(f"{self._path}/healthcheck", "GET")
|
|
90
|
+
return Healthcheck.model_validate_json(r.text)
|
|
91
|
+
|
|
92
|
+
# --- Verify routes ---
|
|
93
|
+
|
|
94
|
+
def get_my_permissions(self) -> VerifyMyPermissions:
|
|
95
|
+
r = self._client._http_request(f"{self._path}/verify/me", "GET")
|
|
96
|
+
return VerifyMyPermissions.model_validate_json(r.text)
|
|
97
|
+
|
|
98
|
+
# --- User routes ---
|
|
99
|
+
|
|
100
|
+
def list_users(self, tenant: Optional[str] = None) -> list[User]:
|
|
101
|
+
params: _PayloadType = {}
|
|
102
|
+
if tenant is not None:
|
|
103
|
+
params["tenant"] = tenant
|
|
104
|
+
r = self._client._http_request(f"{self._path}/users", "GET", params=params)
|
|
105
|
+
return _UserlistAdapter.validate_json(r.text)
|
|
106
|
+
|
|
107
|
+
def create_user(
|
|
108
|
+
self,
|
|
109
|
+
username: str,
|
|
110
|
+
tenant: str,
|
|
111
|
+
description: str,
|
|
112
|
+
group: str,
|
|
113
|
+
default_password: Optional[str] = None
|
|
114
|
+
) -> User:
|
|
115
|
+
payload: _PayloadType = {
|
|
116
|
+
"username": username,
|
|
117
|
+
"tenant_code": tenant,
|
|
118
|
+
"description": description,
|
|
119
|
+
"group_code": group,
|
|
120
|
+
"default_password": default_password,
|
|
121
|
+
}
|
|
122
|
+
r = self._client._http_request(f"{self._path}/users", "PUT", json=payload)
|
|
123
|
+
return User.model_validate_json(r.text)
|
|
124
|
+
|
|
125
|
+
def get_current_user(self) -> CurrentUser:
|
|
126
|
+
r = self._client._http_request(f"{self._path}/users/me", "GET")
|
|
127
|
+
return CurrentUser.model_validate_json(r.text)
|
|
128
|
+
|
|
129
|
+
def get_user(self, code: str) -> User:
|
|
130
|
+
r = self._client._http_request(f"{self._path}/users/{code}", "GET")
|
|
131
|
+
return User.model_validate_json(r.text)
|
|
132
|
+
|
|
133
|
+
def delete_user(self, code: str) -> None:
|
|
134
|
+
self._client._http_request(f"{self._path}/users/{code}", "DELETE")
|
|
135
|
+
|
|
136
|
+
def edit_user(
|
|
137
|
+
self,
|
|
138
|
+
code: str,
|
|
139
|
+
description: str,
|
|
140
|
+
group: str
|
|
141
|
+
) -> User:
|
|
142
|
+
payload: _PayloadType = {
|
|
143
|
+
"description": description,
|
|
144
|
+
"group_code": group,
|
|
145
|
+
}
|
|
146
|
+
r = self._client._http_request(f"{self._path}/users/{code}", "PATCH", json=payload)
|
|
147
|
+
return User.model_validate_json(r.text)
|
|
148
|
+
|
|
149
|
+
def get_user_details(self, code: str) -> UserDetails:
|
|
150
|
+
r = self._client._http_request(f"{self._path}/users/{code}/details", "GET")
|
|
151
|
+
return UserDetails.model_validate_json(r.text)
|
|
152
|
+
|
|
153
|
+
def set_user_details(
|
|
154
|
+
self,
|
|
155
|
+
code: str,
|
|
156
|
+
email_address: Optional[str] = None,
|
|
157
|
+
mobile_number: Optional[str] = None,
|
|
158
|
+
first_name: Optional[str] = None,
|
|
159
|
+
last_name: Optional[str] = None
|
|
160
|
+
) -> UserDetails:
|
|
161
|
+
payload: _PayloadType = {
|
|
162
|
+
"email_address": email_address,
|
|
163
|
+
"mobile_number": mobile_number,
|
|
164
|
+
"first_name": first_name,
|
|
165
|
+
"last_name": last_name,
|
|
166
|
+
}
|
|
167
|
+
r = self._client._http_request(f"{self._path}/users/{code}/details", "PATCH", json=payload)
|
|
168
|
+
return UserDetails.model_validate_json(r.text)
|
|
169
|
+
|
|
170
|
+
def get_user_api_keys(self, user: str) -> list[HiddenUserAPIKey]:
|
|
171
|
+
r = self._client._http_request(f"{self._path}/users/{user}/api_keys", "GET")
|
|
172
|
+
return _HiddenUserAPIKeylistAdapter.validate_json(r.text)
|
|
173
|
+
|
|
174
|
+
def generate_api_key_for_user(
|
|
175
|
+
self,
|
|
176
|
+
user: str,
|
|
177
|
+
description: str,
|
|
178
|
+
custom_policy: Optional[str] = None
|
|
179
|
+
) -> VisibleUserAPIKey:
|
|
180
|
+
payload: _PayloadType = {
|
|
181
|
+
"description": description,
|
|
182
|
+
"custom_policy": custom_policy,
|
|
183
|
+
}
|
|
184
|
+
r = self._client._http_request(f"{self._path}/users/{user}/api_keys", "POST", json=payload)
|
|
185
|
+
return VisibleUserAPIKey.model_validate_json(r.text)
|
|
186
|
+
|
|
187
|
+
def delete_api_key(self, user: str, key_code: str) -> None:
|
|
188
|
+
self._client._http_request(f"{self._path}/users/{user}/api_keys/{key_code}", "DELETE")
|
|
189
|
+
|
|
190
|
+
# --- Tenants routes ---
|
|
191
|
+
|
|
192
|
+
def list_tenants(self) -> list[Tenant]:
|
|
193
|
+
r = self._client._http_request(f"{self._path}/tenants", "GET")
|
|
194
|
+
return _TenantlistAdapter.validate_json(r.text)
|
|
195
|
+
|
|
196
|
+
def get_tenant(self, code: str) -> Tenant:
|
|
197
|
+
r = self._client._http_request(f"{self._path}/tenants/{code}", "GET")
|
|
198
|
+
return Tenant.model_validate_json(r.text)
|
|
199
|
+
|
|
200
|
+
def create_tenant(
|
|
201
|
+
self,
|
|
202
|
+
name: str,
|
|
203
|
+
description: str,
|
|
204
|
+
logo_url: Optional[str] = None
|
|
205
|
+
) -> Tenant:
|
|
206
|
+
payload: _PayloadType = {
|
|
207
|
+
"name": name,
|
|
208
|
+
"description": description,
|
|
209
|
+
"logo_url": logo_url,
|
|
210
|
+
}
|
|
211
|
+
r = self._client._http_request(f"{self._path}/tenants", "PUT", json=payload)
|
|
212
|
+
return Tenant.model_validate_json(r.text)
|
|
213
|
+
|
|
214
|
+
# --- Permission Groups routes ---
|
|
215
|
+
|
|
216
|
+
def get_permission_groups(self, tenant: Optional[str] = None) -> list[PermissionGroup]:
|
|
217
|
+
params = {}
|
|
218
|
+
if tenant is not None:
|
|
219
|
+
params["tenant"] = tenant
|
|
220
|
+
r = self._client._http_request(f"{self._path}/permissions", "GET", params=params)
|
|
221
|
+
return _PermissionGrouplistAdapter.validate_json(r.text)
|
|
222
|
+
|
|
223
|
+
def create_permission_group(
|
|
224
|
+
self,
|
|
225
|
+
tenant: str,
|
|
226
|
+
label: str,
|
|
227
|
+
acl_policy: str
|
|
228
|
+
) -> PermissionGroup:
|
|
229
|
+
payload: _PayloadType = {
|
|
230
|
+
"tenant": tenant,
|
|
231
|
+
"label": label,
|
|
232
|
+
"acl_policy": acl_policy,
|
|
233
|
+
}
|
|
234
|
+
r = self._client._http_request(f"{self._path}/permissions", "PUT", json=payload)
|
|
235
|
+
return PermissionGroup.model_validate_json(r.text)
|
|
236
|
+
|
|
237
|
+
def get_permission_group(self, group: str) -> PermissionGroup:
|
|
238
|
+
r = self._client._http_request(f"{self._path}/permissions/{group}", "GET")
|
|
239
|
+
return PermissionGroup.model_validate_json(r.text)
|
|
240
|
+
|
|
241
|
+
def delete_permission_group(self, group: str) -> None:
|
|
242
|
+
self._client._http_request(f"{self._path}/permissions/{group}", "DELETE")
|
|
243
|
+
|
|
244
|
+
def modify_permission_group(
|
|
245
|
+
self,
|
|
246
|
+
group: str,
|
|
247
|
+
label: str,
|
|
248
|
+
acl_policy: str
|
|
249
|
+
) -> None:
|
|
250
|
+
payload: _PayloadType = {
|
|
251
|
+
"label": label,
|
|
252
|
+
"acl_policy": acl_policy,
|
|
253
|
+
}
|
|
254
|
+
self._client._http_request(f"{self._path}/permissions/{group}", "PATCH", json=payload)
|
|
@@ -1,11 +1,24 @@
|
|
|
1
1
|
import itertools
|
|
2
2
|
import shutil
|
|
3
|
+
from datetime import timedelta
|
|
3
4
|
from pathlib import Path
|
|
4
5
|
from typing import Iterable, Iterator, TypeVar
|
|
5
6
|
|
|
6
7
|
import requests
|
|
7
8
|
|
|
8
9
|
|
|
10
|
+
def timedelta_isoformat(td: timedelta) -> str:
|
|
11
|
+
"""
|
|
12
|
+
ISO 8601 encoding for Python timedelta object.
|
|
13
|
+
Taken from pydantic source:
|
|
14
|
+
https://github.com/pydantic/pydantic/blob/3704eccce4661455acdda1cdcf716bd4b3382e08/pydantic/deprecated/json.py#L135-L140
|
|
15
|
+
|
|
16
|
+
"""
|
|
17
|
+
minutes, seconds = divmod(td.seconds, 60)
|
|
18
|
+
hours, minutes = divmod(minutes, 60)
|
|
19
|
+
return f'{"-" if td.days < 0 else ""}P{abs(td.days)}DT{hours:d}H{minutes:d}M{seconds:d}.{td.microseconds:06d}S'
|
|
20
|
+
|
|
21
|
+
|
|
9
22
|
def get_my_public_ip() -> str:
|
|
10
23
|
"""
|
|
11
24
|
Fetches the public IP address of the machine making the request.
|
|
@@ -51,14 +64,17 @@ def get_free_space_excluding_files(directory: str) -> int:
|
|
|
51
64
|
|
|
52
65
|
T = TypeVar('T')
|
|
53
66
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
67
|
+
try:
|
|
68
|
+
from itertools import batched # type: ignore
|
|
69
|
+
except ImportError:
|
|
70
|
+
# Python < 3.12
|
|
71
|
+
def batched(iterable: Iterable[T], n: int) -> Iterator[tuple[T, ...]]: # type: ignore[no-redef]
|
|
72
|
+
"""
|
|
73
|
+
Implementation of itertools.batched for < Python 3.12
|
|
74
|
+
"""
|
|
75
|
+
it = iter(iterable)
|
|
76
|
+
while True:
|
|
77
|
+
chunk = tuple(itertools.islice(it, n))
|
|
78
|
+
if not chunk:
|
|
79
|
+
break
|
|
80
|
+
yield chunk
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: mercuto-client
|
|
3
|
+
Version: 0.3.0a0
|
|
4
|
+
Summary: Library for interfacing with Rockfield's Mercuto API
|
|
5
|
+
Author-email: Daniel Whipp <daniel.whipp@rocktech.com.au>
|
|
6
|
+
License-Expression: AGPL-3.0-only
|
|
7
|
+
Project-URL: Homepage, https://mercuto.rockfieldcloud.com.au
|
|
8
|
+
Project-URL: Repository, https://github.com/RockfieldTechnologiesAustralia/mercuto-client
|
|
9
|
+
Project-URL: Documentation, https://github.com/RockfieldTechnologiesAustralia/mercuto-client/blob/main/README.md
|
|
10
|
+
Keywords: mercuto,rockfield,infratech
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Topic :: Software Development :: Build Tools
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Requires-Python: >=3.10
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
License-File: LICENSE
|
|
20
|
+
Requires-Dist: requests>=2.32
|
|
21
|
+
Requires-Dist: pyftpdlib>=2.0.1
|
|
22
|
+
Requires-Dist: python-dateutil>=2.9.0.post0
|
|
23
|
+
Requires-Dist: pytz>=2025.2
|
|
24
|
+
Requires-Dist: schedule>=1.2.2
|
|
25
|
+
Requires-Dist: pydantic>=2.0
|
|
26
|
+
Dynamic: license-file
|
|
27
|
+
|
|
28
|
+
# Mercuto Client Python Library
|
|
29
|
+
|
|
30
|
+
Library for interfacing with Rockfield's Mercuto public API.
|
|
31
|
+
This library is in an early development state and is subject to major structural changes at any time.
|
|
32
|
+
|
|
33
|
+
(Visit our Github Repository)[https://github.com/RockfieldTechnologiesAustralia/mercuto-client]
|
|
34
|
+
|
|
35
|
+
## Installation
|
|
36
|
+
Install from PyPi: `pip install mercuto-client` or adding the same line into your `requirements.txt`.
|
|
37
|
+
|
|
38
|
+
## Basic Usage
|
|
39
|
+
|
|
40
|
+
Use the `connect()` function exposed within the main package and provide your API key.
|
|
41
|
+
|
|
42
|
+
```python
|
|
43
|
+
from mercuto_client import connect
|
|
44
|
+
|
|
45
|
+
client = connect(api_key="<YOUR API KEY>")
|
|
46
|
+
print(client.core().list_projects())
|
|
47
|
+
|
|
48
|
+
# Logout after finished.
|
|
49
|
+
client.logout()
|
|
50
|
+
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
You can also use the client as a context manager. It will logout automatically.
|
|
54
|
+
|
|
55
|
+
```python
|
|
56
|
+
from mercuto_client import MercutoClient
|
|
57
|
+
|
|
58
|
+
with MercutoClient.as_credentials(api_key='<YOUR API KEY>') as client:
|
|
59
|
+
print(client.core().list_projects())
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Current Status
|
|
63
|
+
This library is incomplete and may not be fully compliant with the latest Mercuto version. It is only updated periodically and provided for use without any warranty or guarantees.
|
|
64
|
+
|
|
65
|
+
- [x] API Based login (Completed)
|
|
66
|
+
- [ ] Username/password login
|
|
67
|
+
|
|
68
|
+
## Running tests
|
|
69
|
+
Install test packages:
|
|
70
|
+
`python -m uv sync --group tests`
|
|
71
|
+
Run tests:
|
|
72
|
+
`uv run pytest`
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
mercuto_client/__init__.py,sha256=wsBZ73gKjbKLHfJD0nSSl1xOY69HxwALGX73vm7m6iA,283
|
|
2
|
+
mercuto_client/_authentication.py,sha256=M213OBPYs7x5anK9PL92K_8w-orh9QNYm1-Rsdze6gQ,2700
|
|
3
|
+
mercuto_client/acl.py,sha256=_1ogLx62JrYe3igqZwgop-m5NbLoUZPXCo0pEpSR6pc,2927
|
|
4
|
+
mercuto_client/client.py,sha256=GnKB-q0IWPcMkLYr4WL_Mu8p8IzRRYY2FCNO1pi3JvM,6931
|
|
5
|
+
mercuto_client/exceptions.py,sha256=xyUXVSUOXKoMV23hh0qHrRtQuojDBv5e-I0dU7x6a0c,507
|
|
6
|
+
mercuto_client/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
7
|
+
mercuto_client/util.py,sha256=PWNp8BU9wa_sKMPxW0R1yveEF1XslZXG8sw6BZvUoO8,2603
|
|
8
|
+
mercuto_client/_tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
9
|
+
mercuto_client/_tests/conftest.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
10
|
+
mercuto_client/_tests/test_ingester/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
11
|
+
mercuto_client/_tests/test_ingester/test_file_processor.py,sha256=kC1DC0phmjl7jBMMBJYrs9Tx4NL9xKJNmqVX5FNH59s,7399
|
|
12
|
+
mercuto_client/_tests/test_ingester/test_ftp.py,sha256=w1CHAGcZy88D2-nY61Gj16l1nHcer9LIKaMc_DXk23o,1318
|
|
13
|
+
mercuto_client/_tests/test_ingester/test_parsers.py,sha256=R9GnzAaGu_tva0s23VpwEVfEsEUcto3kN3EloIxZvVY,5917
|
|
14
|
+
mercuto_client/_tests/test_mocking/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
15
|
+
mercuto_client/_tests/test_mocking/conftest.py,sha256=M-HikiSj_uuYAEYQnFA0uDtSBpZjO2YXjzxNbluQdMQ,266
|
|
16
|
+
mercuto_client/_tests/test_mocking/test_mock_identity.py,sha256=394r6A_xK78tse4CZA345JpIlIKF_GnPz2xXEjc_Nho,313
|
|
17
|
+
mercuto_client/ingester/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
18
|
+
mercuto_client/ingester/__main__.py,sha256=HZisjXuj5wzdUJ9vLqjhCDqLobflLLVLuI-7PclvVgw,6762
|
|
19
|
+
mercuto_client/ingester/ftp.py,sha256=3-gMzoRCWjLZWeynjkwOXV59B4f0F2VnWp97fuUFTX4,4441
|
|
20
|
+
mercuto_client/ingester/mercuto.py,sha256=djlP9HVoBoB2R-pQVogyMvHpPpbWEjCX5vbMhlEhc8U,6077
|
|
21
|
+
mercuto_client/ingester/processor.py,sha256=XlMMM0taSHZzth39qVMsUkPO0g_ahC7Xcb01rOjQp3I,11906
|
|
22
|
+
mercuto_client/ingester/parsers/__init__.py,sha256=lOt4TyP08hK56wl7XHMlqmj62mDE0Idx8UJL0wljtwM,1398
|
|
23
|
+
mercuto_client/ingester/parsers/campbell.py,sha256=S5enYbajVTm3zSQYkEP6JRVUw94Z7ky100j8p5qLCls,441
|
|
24
|
+
mercuto_client/ingester/parsers/generic_csv.py,sha256=v4rwO4oJb1Ue6zirO0TGtvewOSp9f6ZUT_sWvozvQHo,4051
|
|
25
|
+
mercuto_client/ingester/parsers/worldsensing.py,sha256=rY3Io4mh8htfV4TghnCDkehPZIVoMqGWxEWnDVmZM6I,1028
|
|
26
|
+
mercuto_client/mocks/__init__.py,sha256=Y01V4eyzC5hGX3A9XwqPC82glTSIaWLAPB3lOyBymqg,2988
|
|
27
|
+
mercuto_client/mocks/_utility.py,sha256=YiUm_LzDOdHEhgkve_A5AEUAr9FmcPdVJi3R_izO8yw,2174
|
|
28
|
+
mercuto_client/mocks/mock_data.py,sha256=PqVtKe6eShkfAvMUkZyf_w4-yed2TEx3CncwGIRmxWA,17964
|
|
29
|
+
mercuto_client/mocks/mock_fatigue.py,sha256=GYcud-Rmpyz9wTYY9rsePvCUviIvu6c8wGXqe3L0MDk,940
|
|
30
|
+
mercuto_client/mocks/mock_identity.py,sha256=2Qc9hze9lpPnfa8aj25NDYdDii15hgqej0ymBc3DWQQ,7318
|
|
31
|
+
mercuto_client/modules/__init__.py,sha256=oPbPfkgL0JfCYRITS8O7uL8Zh8pjJTTPoEpK-nHmRuU,747
|
|
32
|
+
mercuto_client/modules/_util.py,sha256=edPLMJsdxd3oIqMDYNtUIAbg_wKypmr-C8ilBzmafDY,403
|
|
33
|
+
mercuto_client/modules/core.py,sha256=KzSmKH71qK9wDM6I-LvXY9oA2nBJFLoQXawK2ei3uCY,22828
|
|
34
|
+
mercuto_client/modules/data.py,sha256=hMhzkkr0f7N9kVKvUQE65uXDpcCxGILcNUhZLSD3nD4,20742
|
|
35
|
+
mercuto_client/modules/fatigue.py,sha256=SPftwW5rMk6LcsIGWO8OHc9r6DA99qt0AyAVOaNRt_8,6397
|
|
36
|
+
mercuto_client/modules/identity.py,sha256=VeMGsq-s5B9xorP9G2TxwIDYUSyyDGuFAJ8Ilg7_mn4,7898
|
|
37
|
+
mercuto_client-0.3.0a0.dist-info/licenses/LICENSE,sha256=0R2QbX4pr5XSiwUc2JoGS7Ja4npcQHyZlGJsR-E73I8,32386
|
|
38
|
+
mercuto_client-0.3.0a0.dist-info/METADATA,sha256=u2Yn0V3LiTyZaMtKoJenocAtWXKsqvHN-7BrVBa7YGc,2423
|
|
39
|
+
mercuto_client-0.3.0a0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
40
|
+
mercuto_client-0.3.0a0.dist-info/top_level.txt,sha256=ecV4spooVaOU8AlclvojxY1LzLW1byDywh-ayLHvKCs,15
|
|
41
|
+
mercuto_client-0.3.0a0.dist-info/RECORD,,
|