robot-wrapper-sdk 0.2.7__tar.gz → 0.2.8__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.7 → robot_wrapper_sdk-0.2.8}/PKG-INFO +19 -8
  2. {robot_wrapper_sdk-0.2.7 → robot_wrapper_sdk-0.2.8}/README.md +18 -7
  3. {robot_wrapper_sdk-0.2.7 → robot_wrapper_sdk-0.2.8}/pyproject.toml +1 -1
  4. {robot_wrapper_sdk-0.2.7 → robot_wrapper_sdk-0.2.8}/robot_sdk/__init__.py +19 -6
  5. {robot_wrapper_sdk-0.2.7 → robot_wrapper_sdk-0.2.8}/robot_sdk/application/usecases.py +11 -5
  6. robot_wrapper_sdk-0.2.8/robot_sdk/domain/entities.py +118 -0
  7. {robot_wrapper_sdk-0.2.7 → robot_wrapper_sdk-0.2.8}/robot_sdk/domain/repositories.py +13 -5
  8. {robot_wrapper_sdk-0.2.7 → robot_wrapper_sdk-0.2.8}/robot_sdk/infrastructure/robot_api_repository.py +40 -9
  9. {robot_wrapper_sdk-0.2.7 → robot_wrapper_sdk-0.2.8}/robot_wrapper_sdk.egg-info/PKG-INFO +19 -8
  10. robot_wrapper_sdk-0.2.7/robot_sdk/domain/entities.py +0 -51
  11. {robot_wrapper_sdk-0.2.7 → robot_wrapper_sdk-0.2.8}/examples/main.py +0 -0
  12. {robot_wrapper_sdk-0.2.7 → robot_wrapper_sdk-0.2.8}/robot_sdk/application/__init__.py +0 -0
  13. {robot_wrapper_sdk-0.2.7 → robot_wrapper_sdk-0.2.8}/robot_sdk/domain/__init__.py +0 -0
  14. {robot_wrapper_sdk-0.2.7 → robot_wrapper_sdk-0.2.8}/robot_sdk/infrastructure/__init__.py +0 -0
  15. {robot_wrapper_sdk-0.2.7 → robot_wrapper_sdk-0.2.8}/robot_sdk/infrastructure/api_client.py +0 -0
  16. {robot_wrapper_sdk-0.2.7 → robot_wrapper_sdk-0.2.8}/robot_wrapper_sdk.egg-info/SOURCES.txt +0 -0
  17. {robot_wrapper_sdk-0.2.7 → robot_wrapper_sdk-0.2.8}/robot_wrapper_sdk.egg-info/dependency_links.txt +0 -0
  18. {robot_wrapper_sdk-0.2.7 → robot_wrapper_sdk-0.2.8}/robot_wrapper_sdk.egg-info/requires.txt +0 -0
  19. {robot_wrapper_sdk-0.2.7 → robot_wrapper_sdk-0.2.8}/robot_wrapper_sdk.egg-info/top_level.txt +0 -0
  20. {robot_wrapper_sdk-0.2.7 → robot_wrapper_sdk-0.2.8}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: robot-wrapper-sdk
3
- Version: 0.2.7
3
+ Version: 0.2.8
4
4
  Summary: Robot Platform API SDK
5
5
  Author-email: GH Robot Platform Team <team@ghrobot.com>
6
6
  License: MIT
@@ -38,7 +38,7 @@ Or pass values directly when creating the module.
38
38
  ## Quick Start (Sync)
39
39
 
40
40
  ```python
41
- from robot_sdk import RobotPlatformModule
41
+ from robot_sdk import RobotPlatformModule, RobotStatus, RobotAuthStatus
42
42
 
43
43
  sdk = RobotPlatformModule(
44
44
  base_url="http://localhost:8085",
@@ -51,14 +51,14 @@ sdk = RobotPlatformModule(
51
51
  robot = sdk.get_robot("2047631542552334336")
52
52
  print(robot.platform if robot else "not found")
53
53
 
54
- # Update lifecycle status
55
- sdk.update_status("2047631542552334336", "live")
54
+ # Update lifecycle status (prefer enum)
55
+ sdk.update_status("2047631542552334336", RobotStatus.LIVE)
56
56
 
57
57
  # Acquire login tasks
58
58
  login_tasks = sdk.acquire_need_login(limit=20, lock_minutes=30)
59
59
 
60
- # Update auth status after worker result
61
- sdk.update_auth_status("2047631542552334336", "authorized")
60
+ # Update auth status after worker result (prefer enum)
61
+ sdk.update_auth_status("2047631542552334336", RobotAuthStatus.AUTHORIZED)
62
62
  # allowed values: authorized | unauthorized | logged_out
63
63
 
64
64
  # Acquire hardening tasks
@@ -67,6 +67,12 @@ hardening_tasks = sdk.acquire_unhardened(limit=20, lock_minutes=30, min_age_days
67
67
  # Update hardening status after worker result
68
68
  sdk.update_security_hardened("2047631542552334336", True)
69
69
 
70
+ # Update metadata (partial patch)
71
+ sdk.update_metadata("2047631542552334336", {
72
+ "country_code": "VN",
73
+ "note": "updated by sdk"
74
+ })
75
+
70
76
  sdk.close()
71
77
  ```
72
78
 
@@ -74,7 +80,7 @@ sdk.close()
74
80
 
75
81
  ```python
76
82
  import asyncio
77
- from robot_sdk import AsyncRobotPlatformModule
83
+ from robot_sdk import AsyncRobotPlatformModule, RobotAuthStatus
78
84
 
79
85
  async def main():
80
86
  sdk = AsyncRobotPlatformModule(
@@ -86,8 +92,12 @@ async def main():
86
92
  robot = await sdk.get_robot("2047631542552334336")
87
93
  print(robot.platform if robot else "not found")
88
94
 
89
- await sdk.update_auth_status("2047631542552334336", "authorized")
95
+ await sdk.update_auth_status("2047631542552334336", RobotAuthStatus.AUTHORIZED)
90
96
  await sdk.update_security_hardened("2047631542552334336", True)
97
+ await sdk.update_metadata("2047631542552334336", {
98
+ "country_code": "VN",
99
+ "note": "updated by async sdk"
100
+ })
91
101
 
92
102
  await sdk.close()
93
103
 
@@ -145,6 +155,7 @@ Update endpoints return:
145
155
  - `acquire_unhardened(limit=20, lock_minutes=30, min_age_days=0)`
146
156
  - `update_auth_status(robot_id, auth_status)`
147
157
  - `update_security_hardened(robot_id, security_hardened)`
158
+ - `update_metadata(robot_id, metadata)`
148
159
  - `close()`
149
160
 
150
161
  ### `AsyncRobotPlatformModule` (async)
@@ -28,7 +28,7 @@ Or pass values directly when creating the module.
28
28
  ## Quick Start (Sync)
29
29
 
30
30
  ```python
31
- from robot_sdk import RobotPlatformModule
31
+ from robot_sdk import RobotPlatformModule, RobotStatus, RobotAuthStatus
32
32
 
33
33
  sdk = RobotPlatformModule(
34
34
  base_url="http://localhost:8085",
@@ -41,14 +41,14 @@ sdk = RobotPlatformModule(
41
41
  robot = sdk.get_robot("2047631542552334336")
42
42
  print(robot.platform if robot else "not found")
43
43
 
44
- # Update lifecycle status
45
- sdk.update_status("2047631542552334336", "live")
44
+ # Update lifecycle status (prefer enum)
45
+ sdk.update_status("2047631542552334336", RobotStatus.LIVE)
46
46
 
47
47
  # Acquire login tasks
48
48
  login_tasks = sdk.acquire_need_login(limit=20, lock_minutes=30)
49
49
 
50
- # Update auth status after worker result
51
- sdk.update_auth_status("2047631542552334336", "authorized")
50
+ # Update auth status after worker result (prefer enum)
51
+ sdk.update_auth_status("2047631542552334336", RobotAuthStatus.AUTHORIZED)
52
52
  # allowed values: authorized | unauthorized | logged_out
53
53
 
54
54
  # Acquire hardening tasks
@@ -57,6 +57,12 @@ hardening_tasks = sdk.acquire_unhardened(limit=20, lock_minutes=30, min_age_days
57
57
  # Update hardening status after worker result
58
58
  sdk.update_security_hardened("2047631542552334336", True)
59
59
 
60
+ # Update metadata (partial patch)
61
+ sdk.update_metadata("2047631542552334336", {
62
+ "country_code": "VN",
63
+ "note": "updated by sdk"
64
+ })
65
+
60
66
  sdk.close()
61
67
  ```
62
68
 
@@ -64,7 +70,7 @@ sdk.close()
64
70
 
65
71
  ```python
66
72
  import asyncio
67
- from robot_sdk import AsyncRobotPlatformModule
73
+ from robot_sdk import AsyncRobotPlatformModule, RobotAuthStatus
68
74
 
69
75
  async def main():
70
76
  sdk = AsyncRobotPlatformModule(
@@ -76,8 +82,12 @@ async def main():
76
82
  robot = await sdk.get_robot("2047631542552334336")
77
83
  print(robot.platform if robot else "not found")
78
84
 
79
- await sdk.update_auth_status("2047631542552334336", "authorized")
85
+ await sdk.update_auth_status("2047631542552334336", RobotAuthStatus.AUTHORIZED)
80
86
  await sdk.update_security_hardened("2047631542552334336", True)
87
+ await sdk.update_metadata("2047631542552334336", {
88
+ "country_code": "VN",
89
+ "note": "updated by async sdk"
90
+ })
81
91
 
82
92
  await sdk.close()
83
93
 
@@ -135,6 +145,7 @@ Update endpoints return:
135
145
  - `acquire_unhardened(limit=20, lock_minutes=30, min_age_days=0)`
136
146
  - `update_auth_status(robot_id, auth_status)`
137
147
  - `update_security_hardened(robot_id, security_hardened)`
148
+ - `update_metadata(robot_id, metadata)`
138
149
  - `close()`
139
150
 
140
151
  ### `AsyncRobotPlatformModule` (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.7"
11
+ version = "0.2.8"
12
12
  description = "Robot Platform API SDK"
13
13
  readme = "README.md"
14
14
  requires-python = ">=3.10"
@@ -2,7 +2,7 @@ from typing import Optional, List, Dict, Any, Tuple
2
2
  from .infrastructure.api_client import HTTPClient, AsyncHTTPClient
3
3
  from .infrastructure.robot_api_repository import RobotAPIRepository, AsyncRobotAPIRepository
4
4
  from .application.usecases import RobotUsecase, AsyncRobotUsecase
5
- from .domain.entities import Robot, RobotSecrets
5
+ from .domain.entities import Robot, RobotSecrets, RobotStatus, RobotAuthStatus
6
6
 
7
7
  __version__ = "0.2.1"
8
8
 
@@ -28,7 +28,7 @@ class RobotPlatformModule:
28
28
  def get_secrets(self, robot_id: str) -> Optional[RobotSecrets]:
29
29
  return self.robot_usecase.get_secrets(robot_id)
30
30
 
31
- def update_status(self, robot_id: str, status: str) -> None:
31
+ def update_status(self, robot_id: str, status: str | RobotStatus) -> None:
32
32
  self.robot_usecase.update_status(robot_id, status)
33
33
 
34
34
  def acquire_need_login(self, limit: int = 20, lock_minutes: int = 30):
@@ -37,12 +37,15 @@ class RobotPlatformModule:
37
37
  def acquire_unhardened(self, limit: int = 20, lock_minutes: int = 30, min_age_days: int = 0):
38
38
  return self.robot_usecase.acquire_unhardened(limit, lock_minutes, min_age_days)
39
39
 
40
- def update_auth_status(self, robot_id: str, auth_status: str) -> None:
40
+ def update_auth_status(self, robot_id: str, auth_status: str | RobotAuthStatus) -> None:
41
41
  self.robot_usecase.update_auth_status(robot_id, auth_status)
42
42
 
43
43
  def update_security_hardened(self, robot_id: str, security_hardened: bool) -> None:
44
44
  self.robot_usecase.update_security_hardened(robot_id, security_hardened)
45
45
 
46
+ def update_metadata(self, robot_id: str, metadata: Dict[str, Any]) -> None:
47
+ self.robot_usecase.update_metadata(robot_id, metadata)
48
+
46
49
 
47
50
  class AsyncRobotPlatformModule:
48
51
  """Asynchronous Facade for the Robot Platform SDK."""
@@ -66,7 +69,7 @@ class AsyncRobotPlatformModule:
66
69
  async def get_secrets(self, robot_id: str) -> Optional[RobotSecrets]:
67
70
  return await self.robot_usecase.get_secrets(robot_id)
68
71
 
69
- async def update_status(self, robot_id: str, status: str) -> None:
72
+ async def update_status(self, robot_id: str, status: str | RobotStatus) -> None:
70
73
  await self.robot_usecase.update_status(robot_id, status)
71
74
 
72
75
  async def acquire_need_login(self, limit: int = 20, lock_minutes: int = 30):
@@ -75,10 +78,20 @@ class AsyncRobotPlatformModule:
75
78
  async def acquire_unhardened(self, limit: int = 20, lock_minutes: int = 30, min_age_days: int = 0):
76
79
  return await self.robot_usecase.acquire_unhardened(limit, lock_minutes, min_age_days)
77
80
 
78
- async def update_auth_status(self, robot_id: str, auth_status: str) -> None:
81
+ async def update_auth_status(self, robot_id: str, auth_status: str | RobotAuthStatus) -> None:
79
82
  await self.robot_usecase.update_auth_status(robot_id, auth_status)
80
83
 
81
84
  async def update_security_hardened(self, robot_id: str, security_hardened: bool) -> None:
82
85
  await self.robot_usecase.update_security_hardened(robot_id, security_hardened)
83
86
 
84
- __all__ = ["RobotPlatformModule", "AsyncRobotPlatformModule", "Robot", "RobotSecrets"]
87
+ async def update_metadata(self, robot_id: str, metadata: Dict[str, Any]) -> None:
88
+ await self.robot_usecase.update_metadata(robot_id, metadata)
89
+
90
+ __all__ = [
91
+ "RobotPlatformModule",
92
+ "AsyncRobotPlatformModule",
93
+ "Robot",
94
+ "RobotSecrets",
95
+ "RobotStatus",
96
+ "RobotAuthStatus",
97
+ ]
@@ -1,6 +1,6 @@
1
1
  from typing import Optional, List, Tuple, Any, Dict
2
2
  from ..domain.repositories import RobotRepository, AsyncRobotRepository
3
- from ..domain.entities import Robot, RobotSecrets
3
+ from ..domain.entities import Robot, RobotSecrets, RobotStatus, RobotAuthStatus
4
4
 
5
5
  class RobotUsecase:
6
6
  def __init__(self, repo: RobotRepository):
@@ -15,7 +15,7 @@ class RobotUsecase:
15
15
  def get_secrets(self, robot_id: str) -> Optional[RobotSecrets]:
16
16
  return self.repo.get_secrets(robot_id)
17
17
 
18
- def update_status(self, robot_id: str, status: str) -> None:
18
+ def update_status(self, robot_id: str, status: str | RobotStatus) -> None:
19
19
  self.repo.update_status(robot_id, status)
20
20
 
21
21
  def list_robots(self, platform: Optional[str] = None, status: Optional[str] = None, project_id: Optional[int] = None, page: int = 1, limit: int = 20) -> Dict[str, Any]:
@@ -33,12 +33,15 @@ class RobotUsecase:
33
33
  def acquire_unhardened(self, limit: int = 20, lock_minutes: int = 30, min_age_days: int = 0) -> List[Robot]:
34
34
  return self.repo.acquire_unhardened(limit, lock_minutes, min_age_days)
35
35
 
36
- def update_auth_status(self, robot_id: str, auth_status: str) -> None:
36
+ def update_auth_status(self, robot_id: str, auth_status: str | RobotAuthStatus) -> None:
37
37
  self.repo.update_auth_status(robot_id, auth_status)
38
38
 
39
39
  def update_security_hardened(self, robot_id: str, security_hardened: bool) -> None:
40
40
  self.repo.update_security_hardened(robot_id, security_hardened)
41
41
 
42
+ def update_metadata(self, robot_id: str, metadata: Dict[str, Any]) -> None:
43
+ self.repo.update_metadata(robot_id, metadata)
44
+
42
45
 
43
46
  class AsyncRobotUsecase:
44
47
  def __init__(self, repo: AsyncRobotRepository):
@@ -53,7 +56,7 @@ class AsyncRobotUsecase:
53
56
  async def get_secrets(self, robot_id: str) -> Optional[RobotSecrets]:
54
57
  return await self.repo.get_secrets(robot_id)
55
58
 
56
- async def update_status(self, robot_id: str, status: str) -> None:
59
+ async def update_status(self, robot_id: str, status: str | RobotStatus) -> None:
57
60
  await self.repo.update_status(robot_id, status)
58
61
 
59
62
  async def list_robots(self, platform: Optional[str] = None, status: Optional[str] = None, project_id: Optional[int] = None, page: int = 1, limit: int = 20) -> Dict[str, Any]:
@@ -71,8 +74,11 @@ class AsyncRobotUsecase:
71
74
  async def acquire_unhardened(self, limit: int = 20, lock_minutes: int = 30, min_age_days: int = 0) -> List[Robot]:
72
75
  return await self.repo.acquire_unhardened(limit, lock_minutes, min_age_days)
73
76
 
74
- async def update_auth_status(self, robot_id: str, auth_status: str) -> None:
77
+ async def update_auth_status(self, robot_id: str, auth_status: str | RobotAuthStatus) -> None:
75
78
  await self.repo.update_auth_status(robot_id, auth_status)
76
79
 
77
80
  async def update_security_hardened(self, robot_id: str, security_hardened: bool) -> None:
78
81
  await self.repo.update_security_hardened(robot_id, security_hardened)
82
+
83
+ async def update_metadata(self, robot_id: str, metadata: Dict[str, Any]) -> None:
84
+ await self.repo.update_metadata(robot_id, metadata)
@@ -0,0 +1,118 @@
1
+ from dataclasses import dataclass, field
2
+ from enum import Enum
3
+ from typing import Optional, Any, Dict
4
+
5
+
6
+ class RobotStatus(str, Enum):
7
+ LIVE = "live"
8
+ DEAD = "dead"
9
+ RUNNING = "running"
10
+ CHECKPOINT = "checkpoint"
11
+
12
+
13
+ class RobotAuthStatus(str, Enum):
14
+ AUTHORIZED = "authorized"
15
+ UNAUTHORIZED = "unauthorized"
16
+ LOGGED_OUT = "logged_out"
17
+ LOGIN_ERROR = "login_error"
18
+
19
+
20
+ def normalize_robot_status(status: str | RobotStatus) -> str:
21
+ if isinstance(status, RobotStatus):
22
+ return status.value
23
+ return str(status).strip().lower()
24
+
25
+
26
+ def normalize_robot_auth_status(status: str | RobotAuthStatus) -> str:
27
+ if isinstance(status, RobotAuthStatus):
28
+ return status.value
29
+ return str(status).strip().lower()
30
+
31
+
32
+ def ensure_valid_robot_status(status: str) -> None:
33
+ allowed = {s.value for s in RobotStatus}
34
+ if status not in allowed:
35
+ raise ValueError(f"invalid status: {status}. allowed={sorted(allowed)}")
36
+
37
+
38
+ def ensure_valid_robot_auth_status(status: str) -> None:
39
+ allowed = {s.value for s in RobotAuthStatus}
40
+ if status not in allowed:
41
+ raise ValueError(f"invalid auth_status: {status}. allowed={sorted(allowed)}")
42
+
43
+
44
+ def ensure_valid_worker_robot_auth_status(status: str) -> None:
45
+ 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
+
49
+
50
+ def ensure_valid_worker_robot_status(status: str) -> None:
51
+ ensure_valid_robot_status(status)
52
+ if status == RobotStatus.CHECKPOINT.value:
53
+ raise ValueError("status checkpoint is not allowed for this endpoint")
54
+
55
+
56
+ def ensure_valid_metadata(metadata: Dict[str, Any]) -> None:
57
+ if not isinstance(metadata, dict):
58
+ raise ValueError("metadata must be a dict")
59
+
60
+ for key in metadata.keys():
61
+ if not isinstance(key, str):
62
+ raise ValueError("metadata keys must be strings")
63
+
64
+ try:
65
+ import json
66
+ json.dumps(metadata)
67
+ except Exception as e:
68
+ raise ValueError(f"metadata must be JSON-serializable: {e}") from e
69
+
70
+
71
+ @dataclass
72
+ class Robot:
73
+ id: str
74
+ username: str
75
+ platform: str
76
+ status: str
77
+ metadata: Dict[str, Any] = field(default_factory=dict)
78
+ project_id: Optional[str] = None
79
+ primary_environment: Optional[str] = None
80
+ created_at: Optional[str] = None
81
+
82
+ @dataclass
83
+ class RobotSecrets:
84
+ password: str
85
+ two_fa_secret: str
86
+
87
+ @dataclass
88
+ class RobotActivity:
89
+ id: str
90
+ activity_type: str
91
+ status: str
92
+ message: str
93
+
94
+ @dataclass
95
+ class AcquireNeedLoginRequest:
96
+ limit: int = 20
97
+ lock_minutes: int = 30
98
+
99
+ @dataclass
100
+ class AcquireUnhardenedRequest:
101
+ limit: int = 20
102
+ lock_minutes: int = 30
103
+ min_age_days: int = 0
104
+
105
+ @dataclass
106
+ class UpdateAuthStatusRequest:
107
+ auth_status: str
108
+
109
+ @dataclass
110
+ class UpdateSecurityHardenedRequest:
111
+ security_hardened: bool
112
+
113
+ @dataclass
114
+ class StandardResponse:
115
+ status: str
116
+ code: int
117
+ message: str
118
+ data: Any = None
@@ -1,6 +1,6 @@
1
1
  from abc import ABC, abstractmethod
2
2
  from typing import Optional, List, Tuple
3
- from .entities import Robot, RobotSecrets
3
+ from .entities import Robot, RobotSecrets, RobotStatus, RobotAuthStatus
4
4
 
5
5
  class RobotRepository(ABC):
6
6
  @abstractmethod
@@ -8,7 +8,7 @@ class RobotRepository(ABC):
8
8
  pass
9
9
 
10
10
  @abstractmethod
11
- def update_status(self, robot_id: str, status: str) -> None:
11
+ def update_status(self, robot_id: str, status: str | RobotStatus) -> None:
12
12
  pass
13
13
 
14
14
  @abstractmethod
@@ -32,20 +32,24 @@ class RobotRepository(ABC):
32
32
  pass
33
33
 
34
34
  @abstractmethod
35
- def update_auth_status(self, robot_id: str, auth_status: str) -> None:
35
+ def update_auth_status(self, robot_id: str, auth_status: str | RobotAuthStatus) -> None:
36
36
  pass
37
37
 
38
38
  @abstractmethod
39
39
  def update_security_hardened(self, robot_id: str, security_hardened: bool) -> None:
40
40
  pass
41
41
 
42
+ @abstractmethod
43
+ def update_metadata(self, robot_id: str, metadata: dict) -> None:
44
+ pass
45
+
42
46
  class AsyncRobotRepository(ABC):
43
47
  @abstractmethod
44
48
  async def find_by_id(self, robot_id: str) -> Optional[Robot]:
45
49
  pass
46
50
 
47
51
  @abstractmethod
48
- async def update_status(self, robot_id: str, status: str) -> None:
52
+ async def update_status(self, robot_id: str, status: str | RobotStatus) -> None:
49
53
  pass
50
54
 
51
55
  @abstractmethod
@@ -69,9 +73,13 @@ class AsyncRobotRepository(ABC):
69
73
  pass
70
74
 
71
75
  @abstractmethod
72
- async def update_auth_status(self, robot_id: str, auth_status: str) -> None:
76
+ async def update_auth_status(self, robot_id: str, auth_status: str | RobotAuthStatus) -> None:
73
77
  pass
74
78
 
75
79
  @abstractmethod
76
80
  async def update_security_hardened(self, robot_id: str, security_hardened: bool) -> None:
77
81
  pass
82
+
83
+ @abstractmethod
84
+ async def update_metadata(self, robot_id: str, metadata: dict) -> None:
85
+ pass
@@ -1,7 +1,16 @@
1
1
  from typing import Optional, List, Tuple, Dict, Any
2
2
  from .api_client import HTTPClient, AsyncHTTPClient
3
3
  from ..domain.repositories import RobotRepository, AsyncRobotRepository
4
- from ..domain.entities import Robot, RobotSecrets
4
+ from ..domain.entities import (
5
+ Robot,
6
+ RobotSecrets,
7
+ RobotStatus,
8
+ RobotAuthStatus,
9
+ normalize_robot_status,
10
+ normalize_robot_auth_status,
11
+ ensure_valid_worker_robot_status,
12
+ ensure_valid_worker_robot_auth_status,
13
+ )
5
14
 
6
15
 
7
16
  def _extract_list_payload(resp: Dict[str, Any]) -> Tuple[List[Dict[str, Any]], int]:
@@ -38,8 +47,10 @@ class RobotAPIRepository(RobotRepository):
38
47
  resp = self.client.request("GET", f"/robots/{robot_id}")
39
48
  return Robot(**resp) if resp else None
40
49
 
41
- def update_status(self, robot_id: str, status: str) -> None:
42
- self.client.request("PATCH", f"/robots/{robot_id}/status", json={"status": status})
50
+ def update_status(self, robot_id: str, status: str | RobotStatus) -> None:
51
+ normalized_status = normalize_robot_status(status)
52
+ ensure_valid_worker_robot_status(normalized_status)
53
+ self.client.request("PATCH", f"/robots/{robot_id}/status", json={"status": normalized_status})
43
54
 
44
55
  def delete_robot(self, robot_id: str) -> None:
45
56
  self.client.request("DELETE", f"/robots/{robot_id}")
@@ -76,11 +87,13 @@ class RobotAPIRepository(RobotRepository):
76
87
  data = _extract_items(resp)
77
88
  return [Robot(**r) for r in data]
78
89
 
79
- def update_auth_status(self, robot_id: str, auth_status: str) -> None:
90
+ def update_auth_status(self, robot_id: str, auth_status: str | RobotAuthStatus) -> None:
91
+ normalized_auth_status = normalize_robot_auth_status(auth_status)
92
+ ensure_valid_worker_robot_auth_status(normalized_auth_status)
80
93
  self.client.request(
81
94
  "PATCH",
82
95
  f"/robots/{robot_id}/auth-status",
83
- json={"auth_status": auth_status},
96
+ json={"auth_status": normalized_auth_status},
84
97
  )
85
98
 
86
99
  def update_security_hardened(self, robot_id: str, security_hardened: bool) -> None:
@@ -90,6 +103,13 @@ class RobotAPIRepository(RobotRepository):
90
103
  json={"security_hardened": security_hardened},
91
104
  )
92
105
 
106
+ def update_metadata(self, robot_id: str, metadata: Dict[str, Any]) -> None:
107
+ self.client.request(
108
+ "PATCH",
109
+ f"/robots/{robot_id}",
110
+ json={"metadata": metadata},
111
+ )
112
+
93
113
  class AsyncRobotAPIRepository(AsyncRobotRepository):
94
114
  def __init__(self, client: AsyncHTTPClient):
95
115
  self.client = client
@@ -98,8 +118,10 @@ class AsyncRobotAPIRepository(AsyncRobotRepository):
98
118
  resp = await self.client.request("GET", f"/robots/{robot_id}")
99
119
  return Robot(**resp) if resp else None
100
120
 
101
- async def update_status(self, robot_id: str, status: str) -> None:
102
- await self.client.request("PATCH", f"/robots/{robot_id}/status", json={"status": status})
121
+ async def update_status(self, robot_id: str, status: str | RobotStatus) -> None:
122
+ normalized_status = normalize_robot_status(status)
123
+ ensure_valid_worker_robot_status(normalized_status)
124
+ await self.client.request("PATCH", f"/robots/{robot_id}/status", json={"status": normalized_status})
103
125
 
104
126
  async def delete_robot(self, robot_id: str) -> None:
105
127
  await self.client.request("DELETE", f"/robots/{robot_id}")
@@ -136,11 +158,13 @@ class AsyncRobotAPIRepository(AsyncRobotRepository):
136
158
  data = _extract_items(resp)
137
159
  return [Robot(**r) for r in data]
138
160
 
139
- async def update_auth_status(self, robot_id: str, auth_status: str) -> None:
161
+ async def update_auth_status(self, robot_id: str, auth_status: str | RobotAuthStatus) -> None:
162
+ normalized_auth_status = normalize_robot_auth_status(auth_status)
163
+ ensure_valid_worker_robot_auth_status(normalized_auth_status)
140
164
  await self.client.request(
141
165
  "PATCH",
142
166
  f"/robots/{robot_id}/auth-status",
143
- json={"auth_status": auth_status},
167
+ json={"auth_status": normalized_auth_status},
144
168
  )
145
169
 
146
170
  async def update_security_hardened(self, robot_id: str, security_hardened: bool) -> None:
@@ -149,3 +173,10 @@ class AsyncRobotAPIRepository(AsyncRobotRepository):
149
173
  f"/robots/{robot_id}/security-hardened",
150
174
  json={"security_hardened": security_hardened},
151
175
  )
176
+
177
+ async def update_metadata(self, robot_id: str, metadata: Dict[str, Any]) -> None:
178
+ await self.client.request(
179
+ "PATCH",
180
+ f"/robots/{robot_id}",
181
+ json={"metadata": metadata},
182
+ )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: robot-wrapper-sdk
3
- Version: 0.2.7
3
+ Version: 0.2.8
4
4
  Summary: Robot Platform API SDK
5
5
  Author-email: GH Robot Platform Team <team@ghrobot.com>
6
6
  License: MIT
@@ -38,7 +38,7 @@ Or pass values directly when creating the module.
38
38
  ## Quick Start (Sync)
39
39
 
40
40
  ```python
41
- from robot_sdk import RobotPlatformModule
41
+ from robot_sdk import RobotPlatformModule, RobotStatus, RobotAuthStatus
42
42
 
43
43
  sdk = RobotPlatformModule(
44
44
  base_url="http://localhost:8085",
@@ -51,14 +51,14 @@ sdk = RobotPlatformModule(
51
51
  robot = sdk.get_robot("2047631542552334336")
52
52
  print(robot.platform if robot else "not found")
53
53
 
54
- # Update lifecycle status
55
- sdk.update_status("2047631542552334336", "live")
54
+ # Update lifecycle status (prefer enum)
55
+ sdk.update_status("2047631542552334336", RobotStatus.LIVE)
56
56
 
57
57
  # Acquire login tasks
58
58
  login_tasks = sdk.acquire_need_login(limit=20, lock_minutes=30)
59
59
 
60
- # Update auth status after worker result
61
- sdk.update_auth_status("2047631542552334336", "authorized")
60
+ # Update auth status after worker result (prefer enum)
61
+ sdk.update_auth_status("2047631542552334336", RobotAuthStatus.AUTHORIZED)
62
62
  # allowed values: authorized | unauthorized | logged_out
63
63
 
64
64
  # Acquire hardening tasks
@@ -67,6 +67,12 @@ hardening_tasks = sdk.acquire_unhardened(limit=20, lock_minutes=30, min_age_days
67
67
  # Update hardening status after worker result
68
68
  sdk.update_security_hardened("2047631542552334336", True)
69
69
 
70
+ # Update metadata (partial patch)
71
+ sdk.update_metadata("2047631542552334336", {
72
+ "country_code": "VN",
73
+ "note": "updated by sdk"
74
+ })
75
+
70
76
  sdk.close()
71
77
  ```
72
78
 
@@ -74,7 +80,7 @@ sdk.close()
74
80
 
75
81
  ```python
76
82
  import asyncio
77
- from robot_sdk import AsyncRobotPlatformModule
83
+ from robot_sdk import AsyncRobotPlatformModule, RobotAuthStatus
78
84
 
79
85
  async def main():
80
86
  sdk = AsyncRobotPlatformModule(
@@ -86,8 +92,12 @@ async def main():
86
92
  robot = await sdk.get_robot("2047631542552334336")
87
93
  print(robot.platform if robot else "not found")
88
94
 
89
- await sdk.update_auth_status("2047631542552334336", "authorized")
95
+ await sdk.update_auth_status("2047631542552334336", RobotAuthStatus.AUTHORIZED)
90
96
  await sdk.update_security_hardened("2047631542552334336", True)
97
+ await sdk.update_metadata("2047631542552334336", {
98
+ "country_code": "VN",
99
+ "note": "updated by async sdk"
100
+ })
91
101
 
92
102
  await sdk.close()
93
103
 
@@ -145,6 +155,7 @@ Update endpoints return:
145
155
  - `acquire_unhardened(limit=20, lock_minutes=30, min_age_days=0)`
146
156
  - `update_auth_status(robot_id, auth_status)`
147
157
  - `update_security_hardened(robot_id, security_hardened)`
158
+ - `update_metadata(robot_id, metadata)`
148
159
  - `close()`
149
160
 
150
161
  ### `AsyncRobotPlatformModule` (async)
@@ -1,51 +0,0 @@
1
- from dataclasses import dataclass, field
2
- from typing import Optional, Any, Dict
3
-
4
- @dataclass
5
- class Robot:
6
- id: str
7
- username: str
8
- platform: str
9
- status: str
10
- metadata: Dict[str, Any] = field(default_factory=dict)
11
- project_id: Optional[str] = None
12
- primary_environment: Optional[str] = None
13
- created_at: Optional[str] = None
14
-
15
- @dataclass
16
- class RobotSecrets:
17
- password: str
18
- two_fa_secret: str
19
-
20
- @dataclass
21
- class RobotActivity:
22
- id: str
23
- activity_type: str
24
- status: str
25
- message: str
26
-
27
- @dataclass
28
- class AcquireNeedLoginRequest:
29
- limit: int = 20
30
- lock_minutes: int = 30
31
-
32
- @dataclass
33
- class AcquireUnhardenedRequest:
34
- limit: int = 20
35
- lock_minutes: int = 30
36
- min_age_days: int = 0
37
-
38
- @dataclass
39
- class UpdateAuthStatusRequest:
40
- auth_status: str
41
-
42
- @dataclass
43
- class UpdateSecurityHardenedRequest:
44
- security_hardened: bool
45
-
46
- @dataclass
47
- class StandardResponse:
48
- status: str
49
- code: int
50
- message: str
51
- data: Any = None