mercuto-client 0.2.8__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.

Files changed (37) hide show
  1. mercuto_client/__init__.py +2 -24
  2. mercuto_client/_authentication.py +72 -0
  3. mercuto_client/_tests/test_ingester/test_parsers.py +67 -67
  4. mercuto_client/_tests/test_mocking/__init__.py +0 -0
  5. mercuto_client/_tests/test_mocking/conftest.py +13 -0
  6. mercuto_client/_tests/test_mocking/test_mock_identity.py +8 -0
  7. mercuto_client/acl.py +16 -10
  8. mercuto_client/client.py +53 -779
  9. mercuto_client/exceptions.py +5 -1
  10. mercuto_client/ingester/__main__.py +1 -1
  11. mercuto_client/ingester/mercuto.py +15 -16
  12. mercuto_client/ingester/parsers/__init__.py +3 -3
  13. mercuto_client/ingester/parsers/campbell.py +2 -2
  14. mercuto_client/ingester/parsers/generic_csv.py +5 -5
  15. mercuto_client/ingester/parsers/worldsensing.py +4 -3
  16. mercuto_client/mocks/__init__.py +92 -0
  17. mercuto_client/mocks/_utility.py +69 -0
  18. mercuto_client/mocks/mock_data.py +402 -0
  19. mercuto_client/mocks/mock_fatigue.py +30 -0
  20. mercuto_client/mocks/mock_identity.py +188 -0
  21. mercuto_client/modules/__init__.py +19 -0
  22. mercuto_client/modules/_util.py +18 -0
  23. mercuto_client/modules/core.py +674 -0
  24. mercuto_client/modules/data.py +623 -0
  25. mercuto_client/modules/fatigue.py +189 -0
  26. mercuto_client/modules/identity.py +254 -0
  27. mercuto_client/{ingester/util.py → util.py} +27 -11
  28. {mercuto_client-0.2.8.dist-info → mercuto_client-0.3.0a0.dist-info}/METADATA +10 -3
  29. mercuto_client-0.3.0a0.dist-info/RECORD +41 -0
  30. mercuto_client/_tests/test_mocking.py +0 -93
  31. mercuto_client/_util.py +0 -13
  32. mercuto_client/mocks.py +0 -203
  33. mercuto_client/types.py +0 -409
  34. mercuto_client-0.2.8.dist-info/RECORD +0 -30
  35. {mercuto_client-0.2.8.dist-info → mercuto_client-0.3.0a0.dist-info}/WHEEL +0 -0
  36. {mercuto_client-0.2.8.dist-info → mercuto_client-0.3.0a0.dist-info}/licenses/LICENSE +0 -0
  37. {mercuto_client-0.2.8.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
- def batched(iterable: Iterable[T], n: int) -> Iterator[tuple[T, ...]]:
56
- """
57
- Implementation of itertools.batched for < Python 3.12
58
- """
59
- it = iter(iterable)
60
- while True:
61
- chunk = tuple(itertools.islice(it, n))
62
- if not chunk:
63
- break
64
- yield chunk
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
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mercuto-client
3
- Version: 0.2.8
3
+ Version: 0.3.0a0
4
4
  Summary: Library for interfacing with Rockfield's Mercuto API
5
5
  Author-email: Daniel Whipp <daniel.whipp@rocktech.com.au>
6
6
  License-Expression: AGPL-3.0-only
@@ -22,6 +22,7 @@ Requires-Dist: pyftpdlib>=2.0.1
22
22
  Requires-Dist: python-dateutil>=2.9.0.post0
23
23
  Requires-Dist: pytz>=2025.2
24
24
  Requires-Dist: schedule>=1.2.2
25
+ Requires-Dist: pydantic>=2.0
25
26
  Dynamic: license-file
26
27
 
27
28
  # Mercuto Client Python Library
@@ -42,7 +43,7 @@ Use the `connect()` function exposed within the main package and provide your AP
42
43
  from mercuto_client import connect
43
44
 
44
45
  client = connect(api_key="<YOUR API KEY>")
45
- print(client.projects().get_projects())
46
+ print(client.core().list_projects())
46
47
 
47
48
  # Logout after finished.
48
49
  client.logout()
@@ -55,7 +56,7 @@ You can also use the client as a context manager. It will logout automatically.
55
56
  from mercuto_client import MercutoClient
56
57
 
57
58
  with MercutoClient.as_credentials(api_key='<YOUR API KEY>') as client:
58
- print(client.projects().get_projects())
59
+ print(client.core().list_projects())
59
60
  ```
60
61
 
61
62
  ## Current Status
@@ -63,3 +64,9 @@ This library is incomplete and may not be fully compliant with the latest Mercut
63
64
 
64
65
  - [x] API Based login (Completed)
65
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,,
@@ -1,93 +0,0 @@
1
- import pytest
2
-
3
- from .. import MercutoClient
4
- from ..mocks import mock_client
5
-
6
-
7
- def test_mock_injection_before_client_creation():
8
- count = 0
9
-
10
- def on_get_healthcheck(*args, **kwargs):
11
- nonlocal count
12
- count += 1
13
- return 'mocked'
14
- with mock_client() as mock:
15
- mock.on('GET', '/healthcheck', on_get_healthcheck)
16
- client = MercutoClient()
17
- assert client.healthcheck() == 'mocked'
18
- assert count == 1
19
-
20
-
21
- def test_mock_injection_after_client_creation():
22
- count = 0
23
- client = MercutoClient()
24
-
25
- def on_get_healthcheck(*args, **kwargs):
26
- nonlocal count
27
- count += 1
28
- return 'mocked'
29
- with mock_client() as mock:
30
- mock.on('GET', '/healthcheck', on_get_healthcheck)
31
- assert client.healthcheck() == 'mocked'
32
- assert count == 1
33
-
34
-
35
- def test_mock_releases_after_end_of_context():
36
- client = MercutoClient()
37
- with mock_client() as mock:
38
- key = mock.add_user(user='this is a test')
39
- client.connect(api_key=key)
40
- assert client.identity().verify_me()['user'] == 'this is a test'
41
-
42
- with pytest.raises(Exception):
43
- client.identity().verify_me()
44
-
45
-
46
- def test_mock_verify_me():
47
- client = MercutoClient()
48
- with mock_client() as mock:
49
- with pytest.raises(Exception):
50
- client.identity().verify_me()
51
-
52
- client.connect(api_key='bad api key')
53
- with pytest.raises(Exception):
54
- client.identity().verify_me()
55
-
56
- key = mock.add_user(user='this is a test user',
57
- tenant='test-tenant', permission_group='test-group')
58
- client.connect(api_key=key)
59
- assert client.identity().verify_me()['user'] == 'this is a test user'
60
- assert client.identity().verify_me()['tenant'] == 'test-tenant'
61
- assert client.identity().verify_me()[
62
- 'permission_group'] == 'test-group'
63
-
64
- mock.delete_user(key)
65
- with pytest.raises(Exception):
66
- client.identity().verify_me()
67
-
68
-
69
- def test_mock_get_user():
70
- client = MercutoClient()
71
- with mock_client() as mock:
72
- client.connect(api_key='bad api key')
73
- with pytest.raises(Exception):
74
- client.identity().get_user('12345')
75
-
76
- key = mock.add_user(user='code1', tenant='test-tenant', permission_group='test-group',
77
- username='testing@example.com')
78
- client.connect(api_key=key)
79
-
80
- assert client.identity().get_user('code1')['code'] == 'code1'
81
- assert client.identity().get_user(
82
- 'code1')['username'] == 'testing@example.com'
83
-
84
- mock.delete_user(key)
85
- with pytest.raises(Exception):
86
- client.identity().verify_me()
87
-
88
-
89
- def test_mock_unsupported_endpoint():
90
- client = MercutoClient()
91
- with mock_client():
92
- with pytest.raises(NotImplementedError):
93
- client.identity().list_tenants()
mercuto_client/_util.py DELETED
@@ -1,13 +0,0 @@
1
- from datetime import timedelta
2
-
3
-
4
- def timedelta_isoformat(td: timedelta) -> str:
5
- """
6
- ISO 8601 encoding for Python timedelta object.
7
- Taken from pydantic source:
8
- https://github.com/pydantic/pydantic/blob/3704eccce4661455acdda1cdcf716bd4b3382e08/pydantic/deprecated/json.py#L135-L140
9
-
10
- """
11
- minutes, seconds = divmod(td.seconds, 60)
12
- hours, minutes = divmod(minutes, 60)
13
- return f'{"-" if td.days < 0 else ""}P{abs(td.days)}DT{hours:d}H{minutes:d}M{seconds:d}.{td.microseconds:06d}S'