robot-wrapper-sdk 0.2.8__tar.gz → 0.2.10__tar.gz
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.
- {robot_wrapper_sdk-0.2.8 → robot_wrapper_sdk-0.2.10}/PKG-INFO +1 -1
- {robot_wrapper_sdk-0.2.8 → robot_wrapper_sdk-0.2.10}/pyproject.toml +1 -1
- {robot_wrapper_sdk-0.2.8 → robot_wrapper_sdk-0.2.10}/robot_sdk/domain/entities.py +12 -6
- {robot_wrapper_sdk-0.2.8 → robot_wrapper_sdk-0.2.10}/robot_sdk/infrastructure/api_client.py +58 -10
- {robot_wrapper_sdk-0.2.8 → robot_wrapper_sdk-0.2.10}/robot_sdk/infrastructure/robot_api_repository.py +23 -8
- {robot_wrapper_sdk-0.2.8 → robot_wrapper_sdk-0.2.10}/robot_wrapper_sdk.egg-info/PKG-INFO +1 -1
- {robot_wrapper_sdk-0.2.8 → robot_wrapper_sdk-0.2.10}/README.md +0 -0
- {robot_wrapper_sdk-0.2.8 → robot_wrapper_sdk-0.2.10}/examples/main.py +0 -0
- {robot_wrapper_sdk-0.2.8 → robot_wrapper_sdk-0.2.10}/robot_sdk/__init__.py +0 -0
- {robot_wrapper_sdk-0.2.8 → robot_wrapper_sdk-0.2.10}/robot_sdk/application/__init__.py +0 -0
- {robot_wrapper_sdk-0.2.8 → robot_wrapper_sdk-0.2.10}/robot_sdk/application/usecases.py +0 -0
- {robot_wrapper_sdk-0.2.8 → robot_wrapper_sdk-0.2.10}/robot_sdk/domain/__init__.py +0 -0
- {robot_wrapper_sdk-0.2.8 → robot_wrapper_sdk-0.2.10}/robot_sdk/domain/repositories.py +0 -0
- {robot_wrapper_sdk-0.2.8 → robot_wrapper_sdk-0.2.10}/robot_sdk/infrastructure/__init__.py +0 -0
- {robot_wrapper_sdk-0.2.8 → robot_wrapper_sdk-0.2.10}/robot_wrapper_sdk.egg-info/SOURCES.txt +0 -0
- {robot_wrapper_sdk-0.2.8 → robot_wrapper_sdk-0.2.10}/robot_wrapper_sdk.egg-info/dependency_links.txt +0 -0
- {robot_wrapper_sdk-0.2.8 → robot_wrapper_sdk-0.2.10}/robot_wrapper_sdk.egg-info/requires.txt +0 -0
- {robot_wrapper_sdk-0.2.8 → robot_wrapper_sdk-0.2.10}/robot_wrapper_sdk.egg-info/top_level.txt +0 -0
- {robot_wrapper_sdk-0.2.8 → robot_wrapper_sdk-0.2.10}/setup.cfg +0 -0
|
@@ -32,25 +32,23 @@ def normalize_robot_auth_status(status: str | RobotAuthStatus) -> str:
|
|
|
32
32
|
def ensure_valid_robot_status(status: str) -> None:
|
|
33
33
|
allowed = {s.value for s in RobotStatus}
|
|
34
34
|
if status not in allowed:
|
|
35
|
-
raise ValueError(
|
|
35
|
+
raise ValueError(
|
|
36
|
+
f"invalid status: {status}. allowed={sorted(allowed)}")
|
|
36
37
|
|
|
37
38
|
|
|
38
39
|
def ensure_valid_robot_auth_status(status: str) -> None:
|
|
39
40
|
allowed = {s.value for s in RobotAuthStatus}
|
|
40
41
|
if status not in allowed:
|
|
41
|
-
raise ValueError(
|
|
42
|
+
raise ValueError(
|
|
43
|
+
f"invalid auth_status: {status}. allowed={sorted(allowed)}")
|
|
42
44
|
|
|
43
45
|
|
|
44
46
|
def ensure_valid_worker_robot_auth_status(status: str) -> None:
|
|
45
47
|
ensure_valid_robot_auth_status(status)
|
|
46
|
-
if status == RobotAuthStatus.LOGIN_ERROR.value:
|
|
47
|
-
raise ValueError("auth_status login_error is not allowed for this endpoint")
|
|
48
48
|
|
|
49
49
|
|
|
50
50
|
def ensure_valid_worker_robot_status(status: str) -> None:
|
|
51
51
|
ensure_valid_robot_status(status)
|
|
52
|
-
if status == RobotStatus.CHECKPOINT.value:
|
|
53
|
-
raise ValueError("status checkpoint is not allowed for this endpoint")
|
|
54
52
|
|
|
55
53
|
|
|
56
54
|
def ensure_valid_metadata(metadata: Dict[str, Any]) -> None:
|
|
@@ -74,16 +72,19 @@ class Robot:
|
|
|
74
72
|
username: str
|
|
75
73
|
platform: str
|
|
76
74
|
status: str
|
|
75
|
+
auth_status: str
|
|
77
76
|
metadata: Dict[str, Any] = field(default_factory=dict)
|
|
78
77
|
project_id: Optional[str] = None
|
|
79
78
|
primary_environment: Optional[str] = None
|
|
80
79
|
created_at: Optional[str] = None
|
|
81
80
|
|
|
81
|
+
|
|
82
82
|
@dataclass
|
|
83
83
|
class RobotSecrets:
|
|
84
84
|
password: str
|
|
85
85
|
two_fa_secret: str
|
|
86
86
|
|
|
87
|
+
|
|
87
88
|
@dataclass
|
|
88
89
|
class RobotActivity:
|
|
89
90
|
id: str
|
|
@@ -91,25 +92,30 @@ class RobotActivity:
|
|
|
91
92
|
status: str
|
|
92
93
|
message: str
|
|
93
94
|
|
|
95
|
+
|
|
94
96
|
@dataclass
|
|
95
97
|
class AcquireNeedLoginRequest:
|
|
96
98
|
limit: int = 20
|
|
97
99
|
lock_minutes: int = 30
|
|
98
100
|
|
|
101
|
+
|
|
99
102
|
@dataclass
|
|
100
103
|
class AcquireUnhardenedRequest:
|
|
101
104
|
limit: int = 20
|
|
102
105
|
lock_minutes: int = 30
|
|
103
106
|
min_age_days: int = 0
|
|
104
107
|
|
|
108
|
+
|
|
105
109
|
@dataclass
|
|
106
110
|
class UpdateAuthStatusRequest:
|
|
107
111
|
auth_status: str
|
|
108
112
|
|
|
113
|
+
|
|
109
114
|
@dataclass
|
|
110
115
|
class UpdateSecurityHardenedRequest:
|
|
111
116
|
security_hardened: bool
|
|
112
117
|
|
|
118
|
+
|
|
113
119
|
@dataclass
|
|
114
120
|
class StandardResponse:
|
|
115
121
|
status: str
|
|
@@ -3,6 +3,54 @@ import os
|
|
|
3
3
|
from typing import Dict, Any, Optional
|
|
4
4
|
from httpx import RemoteProtocolError
|
|
5
5
|
|
|
6
|
+
|
|
7
|
+
def _build_httpx_client(base_url: str, auth: httpx.Auth, proxy_url: Optional[str]) -> httpx.Client:
|
|
8
|
+
kwargs: Dict[str, Any] = {"base_url": base_url, "auth": auth}
|
|
9
|
+
if proxy_url:
|
|
10
|
+
kwargs["proxy"] = proxy_url
|
|
11
|
+
try:
|
|
12
|
+
return httpx.Client(**kwargs)
|
|
13
|
+
except TypeError:
|
|
14
|
+
if "proxy" in kwargs:
|
|
15
|
+
kwargs["proxies"] = kwargs.pop("proxy")
|
|
16
|
+
return httpx.Client(**kwargs)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _build_async_httpx_client(base_url: str, auth: httpx.Auth, proxy_url: Optional[str]) -> httpx.AsyncClient:
|
|
20
|
+
kwargs: Dict[str, Any] = {"base_url": base_url, "auth": auth}
|
|
21
|
+
if proxy_url:
|
|
22
|
+
kwargs["proxy"] = proxy_url
|
|
23
|
+
try:
|
|
24
|
+
return httpx.AsyncClient(**kwargs)
|
|
25
|
+
except TypeError:
|
|
26
|
+
if "proxy" in kwargs:
|
|
27
|
+
kwargs["proxies"] = kwargs.pop("proxy")
|
|
28
|
+
return httpx.AsyncClient(**kwargs)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _build_plain_httpx_client(proxy_url: Optional[str]) -> httpx.Client:
|
|
32
|
+
kwargs: Dict[str, Any] = {}
|
|
33
|
+
if proxy_url:
|
|
34
|
+
kwargs["proxy"] = proxy_url
|
|
35
|
+
try:
|
|
36
|
+
return httpx.Client(**kwargs)
|
|
37
|
+
except TypeError:
|
|
38
|
+
if "proxy" in kwargs:
|
|
39
|
+
kwargs["proxies"] = kwargs.pop("proxy")
|
|
40
|
+
return httpx.Client(**kwargs)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _build_plain_async_httpx_client(proxy_url: Optional[str]) -> httpx.AsyncClient:
|
|
44
|
+
kwargs: Dict[str, Any] = {}
|
|
45
|
+
if proxy_url:
|
|
46
|
+
kwargs["proxy"] = proxy_url
|
|
47
|
+
try:
|
|
48
|
+
return httpx.AsyncClient(**kwargs)
|
|
49
|
+
except TypeError:
|
|
50
|
+
if "proxy" in kwargs:
|
|
51
|
+
kwargs["proxies"] = kwargs.pop("proxy")
|
|
52
|
+
return httpx.AsyncClient(**kwargs)
|
|
53
|
+
|
|
6
54
|
def _extract_tokens(payload: Dict[str, Any]) -> tuple[Optional[str], Optional[str]]:
|
|
7
55
|
data = payload.get("data") if isinstance(payload, dict) else None
|
|
8
56
|
if isinstance(data, dict):
|
|
@@ -69,10 +117,10 @@ class HTTPClient:
|
|
|
69
117
|
self.access_token: Optional[str] = None
|
|
70
118
|
self.refresh_token: Optional[str] = None
|
|
71
119
|
|
|
72
|
-
self.client =
|
|
120
|
+
self.client = _build_httpx_client(
|
|
73
121
|
base_url=self.base_url,
|
|
74
122
|
auth=RobotAuth(self._get_or_login, self._refresh),
|
|
75
|
-
|
|
123
|
+
proxy_url=self.proxy_url,
|
|
76
124
|
)
|
|
77
125
|
|
|
78
126
|
def _get_or_login(self) -> str:
|
|
@@ -84,7 +132,7 @@ class HTTPClient:
|
|
|
84
132
|
return token
|
|
85
133
|
|
|
86
134
|
def _login(self) -> None:
|
|
87
|
-
with
|
|
135
|
+
with _build_plain_httpx_client(self.proxy_url) as c:
|
|
88
136
|
resp = c.post(
|
|
89
137
|
f"{self.base_url}/api/v1/developer/auth/token",
|
|
90
138
|
json={"app_id": self.app_id, "app_secret": self.app_secret}
|
|
@@ -107,7 +155,7 @@ class HTTPClient:
|
|
|
107
155
|
raise RuntimeError("Empty access token after login")
|
|
108
156
|
return token
|
|
109
157
|
|
|
110
|
-
with
|
|
158
|
+
with _build_plain_httpx_client(self.proxy_url) as c:
|
|
111
159
|
resp = c.post(
|
|
112
160
|
f"{self.base_url}/api/v1/developer/auth/refresh",
|
|
113
161
|
json={"refresh_token": self.refresh_token}
|
|
@@ -146,10 +194,10 @@ class HTTPClient:
|
|
|
146
194
|
raise
|
|
147
195
|
last_err = err
|
|
148
196
|
self.client.close()
|
|
149
|
-
self.client =
|
|
197
|
+
self.client = _build_httpx_client(
|
|
150
198
|
base_url=self.base_url,
|
|
151
199
|
auth=RobotAuth(self._get_or_login, self._refresh),
|
|
152
|
-
|
|
200
|
+
proxy_url=self.proxy_url,
|
|
153
201
|
)
|
|
154
202
|
|
|
155
203
|
if resp is None:
|
|
@@ -183,10 +231,10 @@ class AsyncHTTPClient:
|
|
|
183
231
|
self.access_token: Optional[str] = None
|
|
184
232
|
self.refresh_token: Optional[str] = None
|
|
185
233
|
|
|
186
|
-
self.client =
|
|
234
|
+
self.client = _build_async_httpx_client(
|
|
187
235
|
base_url=self.base_url,
|
|
188
236
|
auth=AsyncRobotAuth(self._get_or_login, self._refresh),
|
|
189
|
-
|
|
237
|
+
proxy_url=self.proxy_url,
|
|
190
238
|
)
|
|
191
239
|
|
|
192
240
|
async def _get_or_login(self) -> str:
|
|
@@ -198,7 +246,7 @@ class AsyncHTTPClient:
|
|
|
198
246
|
return token
|
|
199
247
|
|
|
200
248
|
async def _login(self) -> None:
|
|
201
|
-
async with
|
|
249
|
+
async with _build_plain_async_httpx_client(self.proxy_url) as c:
|
|
202
250
|
resp = await c.post(
|
|
203
251
|
f"{self.base_url}/api/v1/developer/auth/token",
|
|
204
252
|
json={"app_id": self.app_id, "app_secret": self.app_secret}
|
|
@@ -220,7 +268,7 @@ class AsyncHTTPClient:
|
|
|
220
268
|
raise RuntimeError("Empty access token after login")
|
|
221
269
|
return token
|
|
222
270
|
|
|
223
|
-
async with
|
|
271
|
+
async with _build_plain_async_httpx_client(self.proxy_url) as c:
|
|
224
272
|
resp = await c.post(
|
|
225
273
|
f"{self.base_url}/api/v1/developer/auth/refresh",
|
|
226
274
|
json={"refresh_token": self.refresh_token}
|
|
@@ -39,13 +39,26 @@ def _extract_items(resp: Dict[str, Any]) -> List[Dict[str, Any]]:
|
|
|
39
39
|
return [item for item in nested if isinstance(item, dict)]
|
|
40
40
|
return []
|
|
41
41
|
|
|
42
|
+
|
|
43
|
+
def _to_robot_payload(item: Dict[str, Any]) -> Dict[str, Any]:
|
|
44
|
+
payload = dict(item)
|
|
45
|
+
payload.setdefault("auth_status", "")
|
|
46
|
+
payload.setdefault("metadata", {})
|
|
47
|
+
return payload
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _to_robots(items: List[Dict[str, Any]]) -> List[Robot]:
|
|
51
|
+
return [Robot(**_to_robot_payload(item)) for item in items]
|
|
52
|
+
|
|
42
53
|
class RobotAPIRepository(RobotRepository):
|
|
43
54
|
def __init__(self, client: HTTPClient):
|
|
44
55
|
self.client = client
|
|
45
56
|
|
|
46
57
|
def find_by_id(self, robot_id: str) -> Optional[Robot]:
|
|
47
58
|
resp = self.client.request("GET", f"/robots/{robot_id}")
|
|
48
|
-
|
|
59
|
+
payload = resp.get("data") if isinstance(resp, dict) else None
|
|
60
|
+
data = payload if isinstance(payload, dict) else resp
|
|
61
|
+
return Robot(**data) if isinstance(data, dict) else None
|
|
49
62
|
|
|
50
63
|
def update_status(self, robot_id: str, status: str | RobotStatus) -> None:
|
|
51
64
|
normalized_status = normalize_robot_status(status)
|
|
@@ -76,7 +89,7 @@ class RobotAPIRepository(RobotRepository):
|
|
|
76
89
|
json={"limit": limit, "lock_minutes": lock_minutes},
|
|
77
90
|
)
|
|
78
91
|
data = _extract_items(resp)
|
|
79
|
-
return
|
|
92
|
+
return _to_robots(data)
|
|
80
93
|
|
|
81
94
|
def acquire_unhardened(self, limit: int = 20, lock_minutes: int = 30, min_age_days: int = 0) -> List[Robot]:
|
|
82
95
|
resp = self.client.request(
|
|
@@ -85,7 +98,7 @@ class RobotAPIRepository(RobotRepository):
|
|
|
85
98
|
json={"limit": limit, "lock_minutes": lock_minutes, "min_age_days": min_age_days},
|
|
86
99
|
)
|
|
87
100
|
data = _extract_items(resp)
|
|
88
|
-
return
|
|
101
|
+
return _to_robots(data)
|
|
89
102
|
|
|
90
103
|
def update_auth_status(self, robot_id: str, auth_status: str | RobotAuthStatus) -> None:
|
|
91
104
|
normalized_auth_status = normalize_robot_auth_status(auth_status)
|
|
@@ -106,7 +119,7 @@ class RobotAPIRepository(RobotRepository):
|
|
|
106
119
|
def update_metadata(self, robot_id: str, metadata: Dict[str, Any]) -> None:
|
|
107
120
|
self.client.request(
|
|
108
121
|
"PATCH",
|
|
109
|
-
f"/robots/{robot_id}",
|
|
122
|
+
f"/robots/{robot_id}/metadata",
|
|
110
123
|
json={"metadata": metadata},
|
|
111
124
|
)
|
|
112
125
|
|
|
@@ -116,7 +129,9 @@ class AsyncRobotAPIRepository(AsyncRobotRepository):
|
|
|
116
129
|
|
|
117
130
|
async def find_by_id(self, robot_id: str) -> Optional[Robot]:
|
|
118
131
|
resp = await self.client.request("GET", f"/robots/{robot_id}")
|
|
119
|
-
|
|
132
|
+
payload = resp.get("data") if isinstance(resp, dict) else None
|
|
133
|
+
data = payload if isinstance(payload, dict) else resp
|
|
134
|
+
return Robot(**data) if isinstance(data, dict) else None
|
|
120
135
|
|
|
121
136
|
async def update_status(self, robot_id: str, status: str | RobotStatus) -> None:
|
|
122
137
|
normalized_status = normalize_robot_status(status)
|
|
@@ -147,7 +162,7 @@ class AsyncRobotAPIRepository(AsyncRobotRepository):
|
|
|
147
162
|
json={"limit": limit, "lock_minutes": lock_minutes},
|
|
148
163
|
)
|
|
149
164
|
data = _extract_items(resp)
|
|
150
|
-
return
|
|
165
|
+
return _to_robots(data)
|
|
151
166
|
|
|
152
167
|
async def acquire_unhardened(self, limit: int = 20, lock_minutes: int = 30, min_age_days: int = 0) -> List[Robot]:
|
|
153
168
|
resp = await self.client.request(
|
|
@@ -156,7 +171,7 @@ class AsyncRobotAPIRepository(AsyncRobotRepository):
|
|
|
156
171
|
json={"limit": limit, "lock_minutes": lock_minutes, "min_age_days": min_age_days},
|
|
157
172
|
)
|
|
158
173
|
data = _extract_items(resp)
|
|
159
|
-
return
|
|
174
|
+
return _to_robots(data)
|
|
160
175
|
|
|
161
176
|
async def update_auth_status(self, robot_id: str, auth_status: str | RobotAuthStatus) -> None:
|
|
162
177
|
normalized_auth_status = normalize_robot_auth_status(auth_status)
|
|
@@ -177,6 +192,6 @@ class AsyncRobotAPIRepository(AsyncRobotRepository):
|
|
|
177
192
|
async def update_metadata(self, robot_id: str, metadata: Dict[str, Any]) -> None:
|
|
178
193
|
await self.client.request(
|
|
179
194
|
"PATCH",
|
|
180
|
-
f"/robots/{robot_id}",
|
|
195
|
+
f"/robots/{robot_id}/metadata",
|
|
181
196
|
json={"metadata": metadata},
|
|
182
197
|
)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{robot_wrapper_sdk-0.2.8 → robot_wrapper_sdk-0.2.10}/robot_wrapper_sdk.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
{robot_wrapper_sdk-0.2.8 → robot_wrapper_sdk-0.2.10}/robot_wrapper_sdk.egg-info/requires.txt
RENAMED
|
File without changes
|
{robot_wrapper_sdk-0.2.8 → robot_wrapper_sdk-0.2.10}/robot_wrapper_sdk.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|