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.
Files changed (20) hide show
  1. {robot_wrapper_sdk-0.2.5 → robot_wrapper_sdk-0.2.6}/PKG-INFO +1 -1
  2. robot_wrapper_sdk-0.2.6/examples/main.py +80 -0
  3. {robot_wrapper_sdk-0.2.5 → robot_wrapper_sdk-0.2.6}/pyproject.toml +1 -1
  4. {robot_wrapper_sdk-0.2.5 → robot_wrapper_sdk-0.2.6}/robot_sdk/infrastructure/api_client.py +55 -20
  5. {robot_wrapper_sdk-0.2.5 → robot_wrapper_sdk-0.2.6}/robot_wrapper_sdk.egg-info/PKG-INFO +1 -1
  6. robot_wrapper_sdk-0.2.5/examples/main.py +0 -42
  7. {robot_wrapper_sdk-0.2.5 → robot_wrapper_sdk-0.2.6}/README.md +0 -0
  8. {robot_wrapper_sdk-0.2.5 → robot_wrapper_sdk-0.2.6}/robot_sdk/__init__.py +0 -0
  9. {robot_wrapper_sdk-0.2.5 → robot_wrapper_sdk-0.2.6}/robot_sdk/application/__init__.py +0 -0
  10. {robot_wrapper_sdk-0.2.5 → robot_wrapper_sdk-0.2.6}/robot_sdk/application/usecases.py +0 -0
  11. {robot_wrapper_sdk-0.2.5 → robot_wrapper_sdk-0.2.6}/robot_sdk/domain/__init__.py +0 -0
  12. {robot_wrapper_sdk-0.2.5 → robot_wrapper_sdk-0.2.6}/robot_sdk/domain/entities.py +0 -0
  13. {robot_wrapper_sdk-0.2.5 → robot_wrapper_sdk-0.2.6}/robot_sdk/domain/repositories.py +0 -0
  14. {robot_wrapper_sdk-0.2.5 → robot_wrapper_sdk-0.2.6}/robot_sdk/infrastructure/__init__.py +0 -0
  15. {robot_wrapper_sdk-0.2.5 → robot_wrapper_sdk-0.2.6}/robot_sdk/infrastructure/robot_api_repository.py +0 -0
  16. {robot_wrapper_sdk-0.2.5 → robot_wrapper_sdk-0.2.6}/robot_wrapper_sdk.egg-info/SOURCES.txt +0 -0
  17. {robot_wrapper_sdk-0.2.5 → robot_wrapper_sdk-0.2.6}/robot_wrapper_sdk.egg-info/dependency_links.txt +0 -0
  18. {robot_wrapper_sdk-0.2.5 → robot_wrapper_sdk-0.2.6}/robot_wrapper_sdk.egg-info/requires.txt +0 -0
  19. {robot_wrapper_sdk-0.2.5 → robot_wrapper_sdk-0.2.6}/robot_wrapper_sdk.egg-info/top_level.txt +0 -0
  20. {robot_wrapper_sdk-0.2.5 → robot_wrapper_sdk-0.2.6}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: robot-wrapper-sdk
3
- Version: 0.2.5
3
+ Version: 0.2.6
4
4
  Summary: Robot Platform API SDK
5
5
  Author-email: GH Robot Platform Team <team@ghrobot.com>
6
6
  License: MIT
@@ -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())
@@ -8,7 +8,7 @@ build-backend = "setuptools.build_meta"
8
8
 
9
9
  [project]
10
10
  name = "robot-wrapper-sdk"
11
- version = "0.2.5"
11
+ version = "0.2.6"
12
12
  description = "Robot Platform API SDK"
13
13
  readme = "README.md"
14
14
  requires-python = ">=3.10"
@@ -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
- return str(self.access_token) if self.access_token else ""
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.get("access_token")
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
- return str(self.access_token) if self.access_token else ""
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.get("access_token")
93
- self.refresh_token = data.get("refresh_token")
94
-
95
- return str(self.access_token) if self.access_token else ""
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 not path.startswith("/api/v1/developer"):
101
- # If path doesn't start with /api, add it
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
- return str(self.access_token) if self.access_token else ""
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.get("access_token")
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
- return str(self.access_token) if self.access_token else ""
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.get("access_token")
176
- self.refresh_token = data.get("refresh_token")
177
-
178
- return str(self.access_token) if self.access_token else ""
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,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: robot-wrapper-sdk
3
- Version: 0.2.5
3
+ Version: 0.2.6
4
4
  Summary: Robot Platform API SDK
5
5
  Author-email: GH Robot Platform Team <team@ghrobot.com>
6
6
  License: MIT
@@ -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())