robot-wrapper-sdk 0.2.5__tar.gz → 0.2.6__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.5 → robot_wrapper_sdk-0.2.6}/PKG-INFO +1 -1
- robot_wrapper_sdk-0.2.6/examples/main.py +80 -0
- {robot_wrapper_sdk-0.2.5 → robot_wrapper_sdk-0.2.6}/pyproject.toml +1 -1
- {robot_wrapper_sdk-0.2.5 → robot_wrapper_sdk-0.2.6}/robot_sdk/infrastructure/api_client.py +55 -20
- {robot_wrapper_sdk-0.2.5 → robot_wrapper_sdk-0.2.6}/robot_wrapper_sdk.egg-info/PKG-INFO +1 -1
- robot_wrapper_sdk-0.2.5/examples/main.py +0 -42
- {robot_wrapper_sdk-0.2.5 → robot_wrapper_sdk-0.2.6}/README.md +0 -0
- {robot_wrapper_sdk-0.2.5 → robot_wrapper_sdk-0.2.6}/robot_sdk/__init__.py +0 -0
- {robot_wrapper_sdk-0.2.5 → robot_wrapper_sdk-0.2.6}/robot_sdk/application/__init__.py +0 -0
- {robot_wrapper_sdk-0.2.5 → robot_wrapper_sdk-0.2.6}/robot_sdk/application/usecases.py +0 -0
- {robot_wrapper_sdk-0.2.5 → robot_wrapper_sdk-0.2.6}/robot_sdk/domain/__init__.py +0 -0
- {robot_wrapper_sdk-0.2.5 → robot_wrapper_sdk-0.2.6}/robot_sdk/domain/entities.py +0 -0
- {robot_wrapper_sdk-0.2.5 → robot_wrapper_sdk-0.2.6}/robot_sdk/domain/repositories.py +0 -0
- {robot_wrapper_sdk-0.2.5 → robot_wrapper_sdk-0.2.6}/robot_sdk/infrastructure/__init__.py +0 -0
- {robot_wrapper_sdk-0.2.5 → robot_wrapper_sdk-0.2.6}/robot_sdk/infrastructure/robot_api_repository.py +0 -0
- {robot_wrapper_sdk-0.2.5 → robot_wrapper_sdk-0.2.6}/robot_wrapper_sdk.egg-info/SOURCES.txt +0 -0
- {robot_wrapper_sdk-0.2.5 → robot_wrapper_sdk-0.2.6}/robot_wrapper_sdk.egg-info/dependency_links.txt +0 -0
- {robot_wrapper_sdk-0.2.5 → robot_wrapper_sdk-0.2.6}/robot_wrapper_sdk.egg-info/requires.txt +0 -0
- {robot_wrapper_sdk-0.2.5 → robot_wrapper_sdk-0.2.6}/robot_wrapper_sdk.egg-info/top_level.txt +0 -0
- {robot_wrapper_sdk-0.2.5 → robot_wrapper_sdk-0.2.6}/setup.cfg +0 -0
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import os
|
|
3
|
+
import sys
|
|
4
|
+
|
|
5
|
+
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
|
|
6
|
+
from robot_sdk import RobotPlatformModule, AsyncRobotPlatformModule
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def setup_environ():
|
|
10
|
+
# Update theo develop-app auth flow
|
|
11
|
+
os.environ["ROBOT_PLATFORM_BASE_URL"] = "http://localhost:8085"
|
|
12
|
+
os.environ["ROBOT_PLATFORM_APP_ID"] = "your_app_id"
|
|
13
|
+
os.environ["ROBOT_PLATFORM_APP_SECRET"] = "your_app_secret"
|
|
14
|
+
# os.environ["ROBOT_PLATFORM_PROXY_URL"] = "socks5h://192.168.3.100:1080"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def test_sync():
|
|
18
|
+
print("--- SYNC EXECUTION ---")
|
|
19
|
+
module = RobotPlatformModule()
|
|
20
|
+
try:
|
|
21
|
+
robot_id = "2047631542552334336"
|
|
22
|
+
|
|
23
|
+
robot = module.get_robot(robot_id)
|
|
24
|
+
if robot:
|
|
25
|
+
print("Robot:", robot.username, robot.platform)
|
|
26
|
+
else:
|
|
27
|
+
print("Robot not found")
|
|
28
|
+
|
|
29
|
+
login_tasks = module.acquire_need_login(limit=20, lock_minutes=30)
|
|
30
|
+
print("Acquire login tasks:", len(login_tasks))
|
|
31
|
+
|
|
32
|
+
module.update_auth_status(robot_id, "authorized")
|
|
33
|
+
print("Updated auth_status -> authorized")
|
|
34
|
+
|
|
35
|
+
hardening_tasks = module.acquire_unhardened(limit=20, lock_minutes=30, min_age_days=0)
|
|
36
|
+
print("Acquire hardening tasks:", len(hardening_tasks))
|
|
37
|
+
|
|
38
|
+
module.update_security_hardened(robot_id, True)
|
|
39
|
+
print("Updated security_hardened -> true")
|
|
40
|
+
|
|
41
|
+
except Exception as e:
|
|
42
|
+
print("Sync demo error:", e)
|
|
43
|
+
finally:
|
|
44
|
+
module.close()
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
async def test_async():
|
|
48
|
+
print("--- ASYNC EXECUTION ---")
|
|
49
|
+
module = AsyncRobotPlatformModule()
|
|
50
|
+
try:
|
|
51
|
+
robot_id = "2047631542552334336"
|
|
52
|
+
|
|
53
|
+
robot = await module.get_robot(robot_id)
|
|
54
|
+
if robot:
|
|
55
|
+
print("Robot:", robot.username, robot.platform)
|
|
56
|
+
else:
|
|
57
|
+
print("Robot not found")
|
|
58
|
+
|
|
59
|
+
login_tasks = await module.acquire_need_login(limit=20, lock_minutes=30)
|
|
60
|
+
print("Acquire login tasks:", len(login_tasks))
|
|
61
|
+
|
|
62
|
+
await module.update_auth_status(robot_id, "authorized")
|
|
63
|
+
print("Updated auth_status -> authorized")
|
|
64
|
+
|
|
65
|
+
hardening_tasks = await module.acquire_unhardened(limit=20, lock_minutes=30, min_age_days=0)
|
|
66
|
+
print("Acquire hardening tasks:", len(hardening_tasks))
|
|
67
|
+
|
|
68
|
+
await module.update_security_hardened(robot_id, True)
|
|
69
|
+
print("Updated security_hardened -> true")
|
|
70
|
+
|
|
71
|
+
except Exception as e:
|
|
72
|
+
print("Async demo error:", e)
|
|
73
|
+
finally:
|
|
74
|
+
await module.close()
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
if __name__ == "__main__":
|
|
78
|
+
setup_environ()
|
|
79
|
+
test_sync()
|
|
80
|
+
asyncio.run(test_async())
|
|
@@ -2,6 +2,19 @@ import httpx
|
|
|
2
2
|
import os
|
|
3
3
|
from typing import Dict, Any, Optional
|
|
4
4
|
|
|
5
|
+
def _extract_tokens(payload: Dict[str, Any]) -> tuple[Optional[str], Optional[str]]:
|
|
6
|
+
data = payload.get("data") if isinstance(payload, dict) else None
|
|
7
|
+
if isinstance(data, dict):
|
|
8
|
+
access_token = data.get("access_token")
|
|
9
|
+
refresh_token = data.get("refresh_token")
|
|
10
|
+
else:
|
|
11
|
+
access_token = payload.get("access_token") if isinstance(payload, dict) else None
|
|
12
|
+
refresh_token = payload.get("refresh_token") if isinstance(payload, dict) else None
|
|
13
|
+
|
|
14
|
+
access_token = str(access_token).strip() if access_token else None
|
|
15
|
+
refresh_token = str(refresh_token).strip() if refresh_token else None
|
|
16
|
+
return access_token, refresh_token
|
|
17
|
+
|
|
5
18
|
class RobotAuth(httpx.Auth):
|
|
6
19
|
def __init__(self, login_func, refresh_func):
|
|
7
20
|
self.login_func = login_func
|
|
@@ -9,11 +22,15 @@ class RobotAuth(httpx.Auth):
|
|
|
9
22
|
|
|
10
23
|
def auth_flow(self, request):
|
|
11
24
|
token = self.login_func()
|
|
25
|
+
if not token:
|
|
26
|
+
raise RuntimeError("Missing access token. Authenticate first.")
|
|
12
27
|
request.headers["Authorization"] = f"Bearer {token}"
|
|
13
28
|
response = yield request
|
|
14
29
|
|
|
15
30
|
if response.status_code == 401:
|
|
16
31
|
token = self.refresh_func()
|
|
32
|
+
if not token:
|
|
33
|
+
raise RuntimeError("Token refresh failed: empty access token")
|
|
17
34
|
request.headers["Authorization"] = f"Bearer {token}"
|
|
18
35
|
yield request
|
|
19
36
|
|
|
@@ -24,11 +41,15 @@ class AsyncRobotAuth(httpx.Auth):
|
|
|
24
41
|
|
|
25
42
|
async def async_auth_flow(self, request):
|
|
26
43
|
token = await self.login_func()
|
|
44
|
+
if not token:
|
|
45
|
+
raise RuntimeError("Missing access token. Authenticate first.")
|
|
27
46
|
request.headers["Authorization"] = f"Bearer {token}"
|
|
28
47
|
response = yield request
|
|
29
48
|
|
|
30
49
|
if response.status_code == 401:
|
|
31
50
|
token = await self.refresh_func()
|
|
51
|
+
if not token:
|
|
52
|
+
raise RuntimeError("Token refresh failed: empty access token")
|
|
32
53
|
request.headers["Authorization"] = f"Bearer {token}"
|
|
33
54
|
yield request
|
|
34
55
|
|
|
@@ -56,7 +77,10 @@ class HTTPClient:
|
|
|
56
77
|
def _get_or_login(self) -> str:
|
|
57
78
|
if not self.access_token:
|
|
58
79
|
self._login()
|
|
59
|
-
|
|
80
|
+
token = str(self.access_token).strip() if self.access_token else ""
|
|
81
|
+
if not token:
|
|
82
|
+
raise RuntimeError("Empty access token after login")
|
|
83
|
+
return token
|
|
60
84
|
|
|
61
85
|
def _login(self) -> None:
|
|
62
86
|
with httpx.Client(proxy=self.proxy_url) as c:
|
|
@@ -67,8 +91,7 @@ class HTTPClient:
|
|
|
67
91
|
resp.raise_for_status()
|
|
68
92
|
try:
|
|
69
93
|
data = resp.json()
|
|
70
|
-
self.access_token = data
|
|
71
|
-
self.refresh_token = data.get("refresh_token")
|
|
94
|
+
self.access_token, self.refresh_token = _extract_tokens(data)
|
|
72
95
|
except Exception as e:
|
|
73
96
|
print(f"❌ Failed to parse JSON from _login: {e}")
|
|
74
97
|
print(f"📄 Login Response Content: {resp.text[:500]}")
|
|
@@ -78,7 +101,10 @@ class HTTPClient:
|
|
|
78
101
|
# Developer tokens currently don't use refresh flow in the same way, but keeping structure
|
|
79
102
|
if not self.refresh_token:
|
|
80
103
|
self._login()
|
|
81
|
-
|
|
104
|
+
token = str(self.access_token).strip() if self.access_token else ""
|
|
105
|
+
if not token:
|
|
106
|
+
raise RuntimeError("Empty access token after login")
|
|
107
|
+
return token
|
|
82
108
|
|
|
83
109
|
with httpx.Client(proxy=self.proxy_url) as c:
|
|
84
110
|
resp = c.post(
|
|
@@ -89,20 +115,22 @@ class HTTPClient:
|
|
|
89
115
|
self._login()
|
|
90
116
|
else:
|
|
91
117
|
data = resp.json()
|
|
92
|
-
self.access_token = data
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
118
|
+
self.access_token, self.refresh_token = _extract_tokens(data)
|
|
119
|
+
|
|
120
|
+
token = str(self.access_token).strip() if self.access_token else ""
|
|
121
|
+
if not token:
|
|
122
|
+
raise RuntimeError("Token refresh failed: empty access token")
|
|
123
|
+
return token
|
|
96
124
|
|
|
97
125
|
def request(self, method: str, path: str, **kwargs: Any) -> Dict[str, Any]:
|
|
98
|
-
# Intelligent path prefixing
|
|
99
126
|
full_path = path
|
|
100
|
-
if
|
|
101
|
-
|
|
127
|
+
if path.startswith("/api/"):
|
|
128
|
+
full_path = path
|
|
129
|
+
else:
|
|
102
130
|
if not path.startswith("/"):
|
|
103
131
|
path = f"/{path}"
|
|
104
132
|
full_path = f"/api/v1/developer{path}"
|
|
105
|
-
|
|
133
|
+
|
|
106
134
|
print(f"🚀 Requesting: {self.base_url}{full_path}")
|
|
107
135
|
resp = self.client.request(method, full_path, **kwargs)
|
|
108
136
|
resp.raise_for_status()
|
|
@@ -140,7 +168,10 @@ class AsyncHTTPClient:
|
|
|
140
168
|
async def _get_or_login(self) -> str:
|
|
141
169
|
if not self.access_token:
|
|
142
170
|
await self._login()
|
|
143
|
-
|
|
171
|
+
token = str(self.access_token).strip() if self.access_token else ""
|
|
172
|
+
if not token:
|
|
173
|
+
raise RuntimeError("Empty access token after login")
|
|
174
|
+
return token
|
|
144
175
|
|
|
145
176
|
async def _login(self) -> None:
|
|
146
177
|
async with httpx.AsyncClient(proxy=self.proxy_url) as c:
|
|
@@ -151,8 +182,7 @@ class AsyncHTTPClient:
|
|
|
151
182
|
resp.raise_for_status()
|
|
152
183
|
try:
|
|
153
184
|
data = resp.json()
|
|
154
|
-
self.access_token = data
|
|
155
|
-
self.refresh_token = data.get("refresh_token")
|
|
185
|
+
self.access_token, self.refresh_token = _extract_tokens(data)
|
|
156
186
|
except Exception as e:
|
|
157
187
|
print(f"❌ Failed to parse JSON from async _login: {e}")
|
|
158
188
|
print(f"📄 Login Response Content: {resp.text[:500]}")
|
|
@@ -161,7 +191,10 @@ class AsyncHTTPClient:
|
|
|
161
191
|
async def _refresh(self) -> str:
|
|
162
192
|
if not self.refresh_token:
|
|
163
193
|
await self._login()
|
|
164
|
-
|
|
194
|
+
token = str(self.access_token).strip() if self.access_token else ""
|
|
195
|
+
if not token:
|
|
196
|
+
raise RuntimeError("Empty access token after login")
|
|
197
|
+
return token
|
|
165
198
|
|
|
166
199
|
async with httpx.AsyncClient(proxy=self.proxy_url) as c:
|
|
167
200
|
resp = await c.post(
|
|
@@ -172,10 +205,12 @@ class AsyncHTTPClient:
|
|
|
172
205
|
await self._login()
|
|
173
206
|
else:
|
|
174
207
|
data = resp.json()
|
|
175
|
-
self.access_token = data
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
208
|
+
self.access_token, self.refresh_token = _extract_tokens(data)
|
|
209
|
+
|
|
210
|
+
token = str(self.access_token).strip() if self.access_token else ""
|
|
211
|
+
if not token:
|
|
212
|
+
raise RuntimeError("Token refresh failed: empty access token")
|
|
213
|
+
return token
|
|
179
214
|
|
|
180
215
|
async def request(self, method: str, path: str, **kwargs: Any) -> Dict[str, Any]:
|
|
181
216
|
# Intelligent path prefixing
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
import asyncio
|
|
2
|
-
import os
|
|
3
|
-
import sys
|
|
4
|
-
|
|
5
|
-
# Mở rộng đường dẫn tránh lỗi module khi chạy local
|
|
6
|
-
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
|
|
7
|
-
from robot_sdk import RobotPlatformModule, AsyncRobotPlatformModule
|
|
8
|
-
|
|
9
|
-
def setup_environ():
|
|
10
|
-
os.environ["ROBOT_PLATFORM_BASE_URL"] = "http://localhost:8080"
|
|
11
|
-
os.environ["ROBOT_PLATFORM_USERNAME"] = "testuser"
|
|
12
|
-
os.environ["ROBOT_PLATFORM_PASSWORD"] = "testpass"
|
|
13
|
-
|
|
14
|
-
def test_sync():
|
|
15
|
-
"""Ví dụ sử dụng thư viện đồng bộ."""
|
|
16
|
-
print("--- SYNC EXECUTION ---")
|
|
17
|
-
module = RobotPlatformModule()
|
|
18
|
-
try:
|
|
19
|
-
robot = module.robot_usecase.get_robot_profile("1234")
|
|
20
|
-
print("Tải thông tin thành công:", robot.username)
|
|
21
|
-
except Exception as e:
|
|
22
|
-
print("Lỗi Demo:", e)
|
|
23
|
-
finally:
|
|
24
|
-
module.close()
|
|
25
|
-
|
|
26
|
-
async def test_async():
|
|
27
|
-
"""Ví dụ sử dụng thư viện bất đồng bộ."""
|
|
28
|
-
print("--- ASYNC EXECUTION ---")
|
|
29
|
-
module = AsyncRobotPlatformModule()
|
|
30
|
-
try:
|
|
31
|
-
robot = await module.robot_usecase.get_robot_profile("1234")
|
|
32
|
-
print("Tải thông tin thành công:", robot.username)
|
|
33
|
-
except Exception as e:
|
|
34
|
-
print("Lỗi Demo:", e)
|
|
35
|
-
finally:
|
|
36
|
-
await module.close()
|
|
37
|
-
|
|
38
|
-
if __name__ == "__main__":
|
|
39
|
-
setup_environ()
|
|
40
|
-
|
|
41
|
-
test_sync()
|
|
42
|
-
asyncio.run(test_async())
|
|
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.5 → robot_wrapper_sdk-0.2.6}/robot_sdk/infrastructure/robot_api_repository.py
RENAMED
|
File without changes
|
|
File without changes
|
{robot_wrapper_sdk-0.2.5 → robot_wrapper_sdk-0.2.6}/robot_wrapper_sdk.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
{robot_wrapper_sdk-0.2.5 → robot_wrapper_sdk-0.2.6}/robot_wrapper_sdk.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|