scalebox-sdk 0.1.25__py3-none-any.whl → 1.0.1__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.
Files changed (70) hide show
  1. scalebox/__init__.py +2 -2
  2. scalebox/api/__init__.py +3 -1
  3. scalebox/api/client/api/sandboxes/post_sandboxes_sandbox_id_connect.py +193 -0
  4. scalebox/api/client/models/connect_sandbox.py +59 -0
  5. scalebox/api/client/models/error.py +2 -2
  6. scalebox/api/client/models/listed_sandbox.py +19 -1
  7. scalebox/api/client/models/new_sandbox.py +10 -0
  8. scalebox/api/client/models/sandbox.py +13 -0
  9. scalebox/api/client/models/sandbox_detail.py +24 -0
  10. scalebox/cli.py +125 -125
  11. scalebox/client/aclient.py +57 -57
  12. scalebox/client/client.py +102 -102
  13. scalebox/code_interpreter/__init__.py +12 -12
  14. scalebox/code_interpreter/charts.py +230 -230
  15. scalebox/code_interpreter/constants.py +3 -3
  16. scalebox/code_interpreter/exceptions.py +13 -13
  17. scalebox/code_interpreter/models.py +485 -485
  18. scalebox/connection_config.py +34 -1
  19. scalebox/csx_connect/__init__.py +1 -1
  20. scalebox/csx_connect/client.py +485 -485
  21. scalebox/csx_desktop/main.py +651 -651
  22. scalebox/exceptions.py +83 -83
  23. scalebox/generated/api.py +61 -61
  24. scalebox/generated/api_pb2.py +203 -203
  25. scalebox/generated/api_pb2.pyi +956 -956
  26. scalebox/generated/api_pb2_connect.py +1407 -1407
  27. scalebox/generated/rpc.py +50 -50
  28. scalebox/sandbox/main.py +146 -139
  29. scalebox/sandbox/sandbox_api.py +105 -91
  30. scalebox/sandbox/signature.py +40 -40
  31. scalebox/sandbox/utils.py +34 -34
  32. scalebox/sandbox_async/main.py +226 -44
  33. scalebox/sandbox_async/sandbox_api.py +124 -3
  34. scalebox/sandbox_sync/main.py +205 -130
  35. scalebox/sandbox_sync/sandbox_api.py +119 -3
  36. scalebox/test/CODE_INTERPRETER_TESTS_READY.md +323 -323
  37. scalebox/test/README.md +329 -329
  38. scalebox/test/bedrock_openai_adapter.py +67 -0
  39. scalebox/test/code_interpreter_test.py +34 -34
  40. scalebox/test/code_interpreter_test_sync.py +34 -34
  41. scalebox/test/run_stress_code_interpreter_sync.py +166 -0
  42. scalebox/test/simple_upload_example.py +123 -0
  43. scalebox/test/stabitiy_test.py +310 -0
  44. scalebox/test/test_browser_use.py +25 -0
  45. scalebox/test/test_browser_use_scalebox.py +61 -0
  46. scalebox/test/test_code_interpreter_sync_comprehensive.py +115 -65
  47. scalebox/test/test_connect_pause_async.py +277 -0
  48. scalebox/test/test_connect_pause_sync.py +267 -0
  49. scalebox/test/test_desktop_sandbox_sf.py +117 -0
  50. scalebox/test/test_download_url.py +49 -0
  51. scalebox/test/test_sandbox_async_comprehensive.py +1 -1
  52. scalebox/test/test_sandbox_object_storage_example.py +146 -0
  53. scalebox/test/test_sandbox_object_storage_example_async.py +156 -0
  54. scalebox/test/test_sf.py +137 -0
  55. scalebox/test/test_watch_dir_async.py +56 -0
  56. scalebox/test/testacreate.py +1 -1
  57. scalebox/test/testagetinfo.py +1 -1
  58. scalebox/test/testcomputeuse.py +243 -243
  59. scalebox/test/testsandbox_api.py +1 -3
  60. scalebox/test/testsandbox_sync.py +1 -1
  61. scalebox/test/upload_100mb_example.py +355 -0
  62. scalebox/utils/httpcoreclient.py +297 -297
  63. scalebox/utils/httpxclient.py +403 -403
  64. scalebox/version.py +2 -2
  65. {scalebox_sdk-0.1.25.dist-info → scalebox_sdk-1.0.1.dist-info}/METADATA +1 -1
  66. {scalebox_sdk-0.1.25.dist-info → scalebox_sdk-1.0.1.dist-info}/RECORD +70 -53
  67. {scalebox_sdk-0.1.25.dist-info → scalebox_sdk-1.0.1.dist-info}/WHEEL +1 -1
  68. {scalebox_sdk-0.1.25.dist-info → scalebox_sdk-1.0.1.dist-info}/entry_points.txt +0 -0
  69. {scalebox_sdk-0.1.25.dist-info → scalebox_sdk-1.0.1.dist-info}/licenses/LICENSE +0 -0
  70. {scalebox_sdk-0.1.25.dist-info → scalebox_sdk-1.0.1.dist-info}/top_level.txt +0 -0
scalebox/generated/rpc.py CHANGED
@@ -1,50 +1,50 @@
1
- import base64
2
- from typing import Optional
3
-
4
- from ..connection_config import Username, default_username
5
- from ..csx_connect.client import Code, ConnectException
6
- from ..exceptions import (
7
- AuthenticationException,
8
- InvalidArgumentException,
9
- NotFoundException,
10
- RateLimitException,
11
- SandboxException,
12
- TimeoutException,
13
- sandbox_timeout_exception,
14
- )
15
-
16
-
17
- def handle_rpc_exception(e: Exception):
18
- if isinstance(e, ConnectException):
19
- if e.status == Code.invalid_argument:
20
- return InvalidArgumentException(e.message)
21
- elif e.status == Code.unauthenticated:
22
- return AuthenticationException(e.message)
23
- elif e.status == Code.not_found:
24
- return NotFoundException(e.message)
25
- elif e.status == Code.unavailable:
26
- return sandbox_timeout_exception(e.message)
27
- elif e.status == Code.resource_exhausted:
28
- return RateLimitException(
29
- f"{e.message}: Rate limit exceeded, please try again later."
30
- )
31
- elif e.status == Code.canceled:
32
- return TimeoutException(
33
- f"{e.message}: This error is likely due to exceeding 'request_timeout'. You can pass the request timeout value as an option when making the request."
34
- )
35
- elif e.status == Code.deadline_exceeded:
36
- return TimeoutException(
37
- f"{e.message}: This error is likely due to exceeding 'timeout' — the total time a long running request (like process or directory watch) can be active. It can be modified by passing 'timeout' when making the request. Use '0' to disable the timeout."
38
- )
39
- else:
40
- return SandboxException(f"{e.status}: {e.message}")
41
- else:
42
- return e
43
-
44
-
45
- def authentication_header(user: Optional[Username] = None):
46
- value = f"{user if user is not None else default_username}:"
47
-
48
- encoded = base64.b64encode(value.encode("utf-8")).decode("utf-8")
49
-
50
- return {"Authorization": f"Basic {encoded}"}
1
+ import base64
2
+ from typing import Optional
3
+
4
+ from ..connection_config import Username, default_username
5
+ from ..csx_connect.client import Code, ConnectException
6
+ from ..exceptions import (
7
+ AuthenticationException,
8
+ InvalidArgumentException,
9
+ NotFoundException,
10
+ RateLimitException,
11
+ SandboxException,
12
+ TimeoutException,
13
+ sandbox_timeout_exception,
14
+ )
15
+
16
+
17
+ def handle_rpc_exception(e: Exception):
18
+ if isinstance(e, ConnectException):
19
+ if e.status == Code.invalid_argument:
20
+ return InvalidArgumentException(e.message)
21
+ elif e.status == Code.unauthenticated:
22
+ return AuthenticationException(e.message)
23
+ elif e.status == Code.not_found:
24
+ return NotFoundException(e.message)
25
+ elif e.status == Code.unavailable:
26
+ return sandbox_timeout_exception(e.message)
27
+ elif e.status == Code.resource_exhausted:
28
+ return RateLimitException(
29
+ f"{e.message}: Rate limit exceeded, please try again later."
30
+ )
31
+ elif e.status == Code.canceled:
32
+ return TimeoutException(
33
+ f"{e.message}: This error is likely due to exceeding 'request_timeout'. You can pass the request timeout value as an option when making the request."
34
+ )
35
+ elif e.status == Code.deadline_exceeded:
36
+ return TimeoutException(
37
+ f"{e.message}: This error is likely due to exceeding 'timeout' — the total time a long running request (like process or directory watch) can be active. It can be modified by passing 'timeout' when making the request. Use '0' to disable the timeout."
38
+ )
39
+ else:
40
+ return SandboxException(f"{e.status}: {e.message}")
41
+ else:
42
+ return e
43
+
44
+
45
+ def authentication_header(user: Optional[Username] = None):
46
+ value = f"{user if user is not None else default_username}:"
47
+
48
+ encoded = base64.b64encode(value.encode("utf-8")).decode("utf-8")
49
+
50
+ return {"Authorization": f"Basic {encoded}"}
scalebox/sandbox/main.py CHANGED
@@ -1,139 +1,146 @@
1
- import urllib.parse
2
- from abc import ABC, abstractmethod
3
- from typing import Optional
4
-
5
- from httpx import Limits
6
-
7
- from ..connection_config import ConnectionConfig
8
- from ..generated.api import ENVD_API_FILES_ROUTE
9
- from .signature import get_signature
10
-
11
-
12
- class SandboxSetup(ABC):
13
- _limits = Limits(
14
- max_keepalive_connections=40,
15
- max_connections=40,
16
- keepalive_expiry=300,
17
- )
18
-
19
- envd_port = 443
20
-
21
- default_sandbox_timeout = 300
22
- default_template = "base"
23
-
24
- @property
25
- @abstractmethod
26
- def connection_config(self) -> ConnectionConfig: ...
27
-
28
- @property
29
- @abstractmethod
30
- def _envd_access_token(self) -> Optional[str]: ...
31
-
32
- @property
33
- @abstractmethod
34
- def envd_api_url(self) -> str: ...
35
-
36
- @property
37
- @abstractmethod
38
- def sandbox_id(self) -> str: ...
39
-
40
- @property
41
- @abstractmethod
42
- def sandbox_domain(self) -> str: ...
43
-
44
- def _file_url(
45
- self,
46
- path: Optional[str] = None,
47
- user: str = "user",
48
- signature: Optional[str] = None,
49
- signature_expiration: Optional[int] = None,
50
- ) -> str:
51
- url = urllib.parse.urljoin(self.envd_api_url, ENVD_API_FILES_ROUTE)
52
- query = {"path": path} if path else {}
53
- query = {**query, "username": user}
54
-
55
- if signature:
56
- query["signature"] = signature
57
-
58
- if signature_expiration:
59
- if signature is None:
60
- raise ValueError("signature_expiration requires signature to be set")
61
- query["signature_expiration"] = str(signature_expiration)
62
-
63
- params = urllib.parse.urlencode(
64
- query,
65
- quote_via=urllib.parse.quote,
66
- )
67
- url = urllib.parse.urljoin(url, f"?{params}")
68
-
69
- return url
70
-
71
- def download_url(
72
- self,
73
- path: str,
74
- user: str = "user",
75
- use_signature_expiration: Optional[int] = None,
76
- ) -> str:
77
- """
78
- Get the URL to download a file from the sandbox.
79
-
80
- :param path: Path to the file to download
81
- :param user: User to upload the file as
82
- :param use_signature_expiration: Expiration time for the signed URL in seconds
83
-
84
- :return: URL for downloading file
85
- """
86
-
87
- use_signature = self._envd_access_token is not None
88
- if use_signature:
89
- signature = get_signature(
90
- path, "read", user, self._envd_access_token, use_signature_expiration
91
- )
92
- return self._file_url(
93
- path, user, signature["signature"], signature["expiration"]
94
- )
95
- else:
96
- return self._file_url(path)
97
-
98
- def upload_url(
99
- self,
100
- path: Optional[str] = None,
101
- user: str = "user",
102
- use_signature_expiration: Optional[int] = None,
103
- ) -> str:
104
- """
105
- Get the URL to upload a file to the sandbox.
106
-
107
- You have to send a POST request to this URL with the file as multipart/form-data.
108
-
109
- :param path: Path to the file to upload
110
- :param user: User to upload the file as
111
- :param use_signature_expiration: Expiration time for the signed URL in seconds
112
-
113
- :return: URL for uploading file
114
- """
115
-
116
- use_signature = self._envd_access_token is not None
117
- if use_signature:
118
- signature = get_signature(
119
- path, "write", user, self._envd_access_token, use_signature_expiration
120
- )
121
- return self._file_url(
122
- path, user, signature["signature"], signature["expiration"]
123
- )
124
- else:
125
- return self._file_url(path)
126
-
127
- def get_host(self, port: int) -> str:
128
- """
129
- Get the host address to connect to the sandbox.
130
- You can then use this address to connect to the sandbox port from outside the sandbox via HTTP or WebSocket.
131
-
132
- :param port: Port to connect to
133
-
134
- :return: Host address to connect to
135
- """
136
- if self.connection_config.debug:
137
- return f"localhost:{port}"
138
- return f"{self.sandbox_domain}"
139
- # return f"{port}-{self.sandbox_id}.{self.sandbox_domain}"
1
+ import urllib.parse
2
+ from abc import ABC, abstractmethod
3
+ from typing import Optional
4
+
5
+ from httpx import Limits
6
+
7
+ from ..connection_config import ConnectionConfig
8
+ from ..generated.api import ENVD_API_DOWNLOAD_FILES_ROUTE,ENVD_API_UPLOAD_FILES_ROUTE
9
+ from .signature import get_signature, Operation
10
+
11
+
12
+ class SandboxSetup(ABC):
13
+ _limits = Limits(
14
+ max_keepalive_connections=40,
15
+ max_connections=40,
16
+ keepalive_expiry=300,
17
+ )
18
+
19
+ envd_port = 443
20
+
21
+ default_sandbox_timeout = 300
22
+ default_template = "base"
23
+
24
+ @property
25
+ @abstractmethod
26
+ def connection_config(self) -> ConnectionConfig: ...
27
+
28
+ @property
29
+ @abstractmethod
30
+ def _envd_access_token(self) -> Optional[str]: ...
31
+
32
+ @property
33
+ @abstractmethod
34
+ def envd_api_url(self) -> str: ...
35
+
36
+ @property
37
+ @abstractmethod
38
+ def sandbox_id(self) -> str: ...
39
+
40
+ @property
41
+ @abstractmethod
42
+ def sandbox_domain(self) -> str: ...
43
+
44
+ def _file_url(
45
+ self,
46
+ path: Optional[str] = None,
47
+ operation: Operation = "read",
48
+ user: str = "root",
49
+ signature: Optional[str] = None,
50
+ signature_expiration: Optional[int] = None,
51
+ ) -> str:
52
+ query = {}
53
+ url = urllib.parse.urljoin(self.envd_api_url, ENVD_API_DOWNLOAD_FILES_ROUTE)
54
+ if operation == "write":
55
+ url = urllib.parse.urljoin(self.envd_api_url, ENVD_API_UPLOAD_FILES_ROUTE)
56
+ query = {**query,"path": path}
57
+ else:
58
+ url = f"{url}/{path.lstrip('/')}"
59
+ query = {**query, "username": user}
60
+
61
+ if signature:
62
+ query["signature"] = signature
63
+
64
+ if signature_expiration:
65
+ if signature is None:
66
+ raise ValueError("signature_expiration requires signature to be set")
67
+ query["signature_expiration"] = str(signature_expiration)
68
+
69
+ params = urllib.parse.urlencode(
70
+ query,
71
+ quote_via=urllib.parse.quote,
72
+ )
73
+ url = urllib.parse.urljoin(url, f"?{params}")
74
+
75
+ return url
76
+
77
+ def download_url(
78
+ self,
79
+ path: str,
80
+ user: str = "root",
81
+ use_signature_expiration: Optional[int] = None,
82
+ ) -> str:
83
+ """
84
+ Get the URL to download a file from the sandbox.
85
+
86
+ :param path: Path to the file to download
87
+ :param user: User to upload the file as
88
+ :param use_signature_expiration: Expiration time for the signed URL in seconds
89
+
90
+ :return: URL for downloading file
91
+ """
92
+
93
+ use_signature = self._envd_access_token is not None
94
+ if use_signature:
95
+ signature = get_signature(
96
+ path, "read", user, self._envd_access_token, use_signature_expiration
97
+ )
98
+ return self._file_url(
99
+ path,"read", user, signature["signature"], signature["expiration"]
100
+ )
101
+ else:
102
+ return self._file_url(path)
103
+
104
+ def upload_url(
105
+ self,
106
+ path: Optional[str] = None,
107
+ user: str = "root",
108
+ use_signature_expiration: Optional[int] = None,
109
+ ) -> str:
110
+ """
111
+ Get the URL to upload a file to the sandbox.
112
+
113
+ You have to send a POST request to this URL with the file as multipart/form-data.
114
+
115
+ :param path: Path to the file to upload
116
+ :param user: User to upload the file as
117
+ :param use_signature_expiration: Expiration time for the signed URL in seconds
118
+
119
+ :return: URL for uploading file
120
+ """
121
+
122
+ use_signature = self._envd_access_token is not None
123
+ if use_signature:
124
+ signature = get_signature(
125
+ path, "write", user, self._envd_access_token, use_signature_expiration
126
+ )
127
+ return self._file_url(
128
+ path,"write", user, signature["signature"], signature["expiration"]
129
+ )
130
+ else:
131
+ return self._file_url(path)
132
+
133
+ def get_host(self, port: int) -> str:
134
+ """
135
+ Get the host address to connect to the sandbox.
136
+ You can then use this address to connect to the sandbox port from outside the sandbox via HTTP or WebSocket.
137
+
138
+ :param port: Port to connect to
139
+
140
+ :return: Host address to connect to
141
+ """
142
+ if self.connection_config.debug:
143
+ debug_host = self.connection_config.debug_host
144
+ return f"{debug_host}:{port}"
145
+ return f"{self.sandbox_domain}"
146
+ # return f"{port}-{self.sandbox_id}.{self.sandbox_domain}"
@@ -1,91 +1,105 @@
1
- from abc import ABC
2
- from dataclasses import dataclass
3
- from datetime import datetime
4
- from typing import Dict, Optional
5
-
6
- from httpx import Limits
7
-
8
- from ..api.client.models import SandboxState
9
-
10
-
11
- @dataclass
12
- class SandboxInfo:
13
- """Information about a sandbox."""
14
-
15
- sandbox_id: str
16
- """Sandbox ID."""
17
- sandbox_domain: Optional[str]
18
- """Domain where the sandbox is hosted."""
19
- template_id: str
20
- """Template ID."""
21
- name: Optional[str]
22
- """Template name."""
23
- metadata: Dict[str, str]
24
- """Saved sandbox metadata."""
25
- started_at: datetime
26
- """Sandbox start time."""
27
- end_at: datetime
28
- """Sandbox expiration date."""
29
- envd_version: Optional[str]
30
- """Envd version."""
31
- _envd_access_token: Optional[str]
32
- """Envd access token."""
33
-
34
-
35
- @dataclass
36
- class ListedSandbox:
37
- """Information about a sandbox."""
38
-
39
- sandbox_id: str
40
- """Sandbox ID."""
41
- template_id: str
42
- """Template ID."""
43
- name: Optional[str]
44
- """Template Alias."""
45
- state: SandboxState
46
- """Sandbox state."""
47
- cpu_count: int
48
- """Sandbox CPU count."""
49
- memory_mb: int
50
- """Sandbox Memory size in MB."""
51
- metadata: Dict[str, str]
52
- """Saved sandbox metadata."""
53
- started_at: datetime
54
- """Sandbox start time."""
55
- end_at: datetime
56
-
57
-
58
- @dataclass
59
- class SandboxQuery:
60
- """Query parameters for listing sandboxes."""
61
-
62
- metadata: Optional[dict[str, str]] = None
63
- """Filter sandboxes by metadata."""
64
-
65
-
66
- @dataclass
67
- class SandboxMetrics:
68
- """Sandbox metrics."""
69
-
70
- cpu_count: int
71
- """Number of CPUs."""
72
- cpu_used_pct: float
73
- """CPU usage percentage."""
74
- disk_total: int
75
- """Total disk space in bytes."""
76
- disk_used: int
77
- """Disk used in bytes."""
78
- mem_total: int
79
- """Total memory in bytes."""
80
- mem_used: int
81
- """Memory used in bytes."""
82
- timestamp: datetime
83
- """Timestamp of the metric entry."""
84
-
85
-
86
- class SandboxApiBase(ABC):
87
- _limits = Limits(
88
- max_keepalive_connections=10,
89
- max_connections=20,
90
- keepalive_expiry=20,
91
- )
1
+ from abc import ABC
2
+ from dataclasses import dataclass
3
+ from datetime import datetime
4
+ from typing import Dict, Optional
5
+
6
+ from httpx import Limits
7
+
8
+ from ..api.client.models import SandboxState
9
+
10
+
11
+ @dataclass
12
+ class SandboxInfo:
13
+ """Information about a sandbox."""
14
+
15
+ sandbox_id: str
16
+ """Sandbox ID."""
17
+ sandbox_domain: Optional[str]
18
+ """Domain where the sandbox is hosted."""
19
+ template_id: str
20
+ """Template ID."""
21
+ name: Optional[str]
22
+ """Template name."""
23
+ metadata: Dict[str, str]
24
+ """Saved sandbox metadata."""
25
+ started_at: datetime
26
+ """Sandbox start time."""
27
+ uptime: int
28
+ """Sandbox up time."""
29
+ end_at: datetime
30
+ """Sandbox expiration date."""
31
+ timeout: int
32
+ """Sandbox timeout."""
33
+ envd_version: Optional[str]
34
+ """Envd version."""
35
+ _envd_access_token: Optional[str]
36
+ """Envd access token."""
37
+ object_storage: Optional[Dict[str, str]]
38
+ """Object storage."""
39
+
40
+
41
+ @dataclass
42
+ class ListedSandbox:
43
+ """Information about a sandbox."""
44
+
45
+ sandbox_id: str
46
+ """Sandbox ID."""
47
+ template_id: str
48
+ """Template ID."""
49
+ name: Optional[str]
50
+ """Template Alias."""
51
+ state: SandboxState
52
+ """Sandbox state."""
53
+ cpu_count: int
54
+ """Sandbox CPU count."""
55
+ memory_mb: int
56
+ """Sandbox Memory size in MB."""
57
+ metadata: Dict[str, str]
58
+ """Saved sandbox metadata."""
59
+ started_at: datetime
60
+ """Sandbox start time."""
61
+ uptime: int
62
+ """Sandbox up time."""
63
+ end_at: datetime
64
+ """Sandbox expiration date."""
65
+ timeout: int
66
+ """Sandbox timeout."""
67
+ object_storage: Optional[Dict[str, str]]
68
+ """Object storage."""
69
+
70
+
71
+ @dataclass
72
+ class SandboxQuery:
73
+ """Query parameters for listing sandboxes."""
74
+
75
+ metadata: Optional[dict[str, str]] = None
76
+ """Filter sandboxes by metadata."""
77
+
78
+
79
+ @dataclass
80
+ class SandboxMetrics:
81
+ """Sandbox metrics."""
82
+
83
+ cpu_count: int
84
+ """Number of CPUs."""
85
+ cpu_used_pct: float
86
+ """CPU usage percentage."""
87
+ disk_total: int
88
+ """Total disk space in bytes."""
89
+ disk_used: int
90
+ """Disk used in bytes."""
91
+ mem_total: int
92
+ """Total memory in bytes."""
93
+ mem_used: int
94
+ """Memory used in bytes."""
95
+ timestamp: datetime
96
+ """Timestamp of the metric entry."""
97
+
98
+
99
+ class SandboxApiBase(ABC):
100
+ _limits = Limits(
101
+ max_keepalive_connections=10,
102
+ max_connections=20,
103
+ keepalive_expiry=20,
104
+ )
105
+ default_sandbox_timeout = 300