loopix-sdk 2.30.0__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 (238) hide show
  1. loopix/__init__.py +260 -0
  2. loopix/api/__init__.py +287 -0
  3. loopix/api/client/__init__.py +8 -0
  4. loopix/api/client/api/__init__.py +1 -0
  5. loopix/api/client/api/sandboxes/__init__.py +1 -0
  6. loopix/api/client/api/sandboxes/delete_sandboxes_sandbox_id.py +161 -0
  7. loopix/api/client/api/sandboxes/get_sandboxes.py +176 -0
  8. loopix/api/client/api/sandboxes/get_sandboxes_metrics.py +173 -0
  9. loopix/api/client/api/sandboxes/get_sandboxes_sandbox_id.py +163 -0
  10. loopix/api/client/api/sandboxes/get_sandboxes_sandbox_id_logs.py +199 -0
  11. loopix/api/client/api/sandboxes/get_sandboxes_sandbox_id_metrics.py +212 -0
  12. loopix/api/client/api/sandboxes/get_v2_sandboxes.py +230 -0
  13. loopix/api/client/api/sandboxes/get_v_2_sandboxes_sandbox_id_logs.py +254 -0
  14. loopix/api/client/api/sandboxes/post_sandboxes.py +172 -0
  15. loopix/api/client/api/sandboxes/post_sandboxes_sandbox_id_connect.py +193 -0
  16. loopix/api/client/api/sandboxes/post_sandboxes_sandbox_id_pause.py +187 -0
  17. loopix/api/client/api/sandboxes/post_sandboxes_sandbox_id_refreshes.py +181 -0
  18. loopix/api/client/api/sandboxes/post_sandboxes_sandbox_id_resume.py +189 -0
  19. loopix/api/client/api/sandboxes/post_sandboxes_sandbox_id_snapshots.py +195 -0
  20. loopix/api/client/api/sandboxes/post_sandboxes_sandbox_id_timeout.py +193 -0
  21. loopix/api/client/api/sandboxes/put_sandboxes_sandbox_id_network.py +199 -0
  22. loopix/api/client/api/snapshots/__init__.py +1 -0
  23. loopix/api/client/api/snapshots/get_snapshots.py +202 -0
  24. loopix/api/client/api/tags/__init__.py +1 -0
  25. loopix/api/client/api/tags/delete_templates_tags.py +174 -0
  26. loopix/api/client/api/tags/get_templates_template_id_tags.py +172 -0
  27. loopix/api/client/api/tags/post_templates_tags.py +176 -0
  28. loopix/api/client/api/templates/__init__.py +1 -0
  29. loopix/api/client/api/templates/delete_templates_template_id.py +157 -0
  30. loopix/api/client/api/templates/get_templates.py +172 -0
  31. loopix/api/client/api/templates/get_templates_aliases_alias.py +167 -0
  32. loopix/api/client/api/templates/get_templates_template_id.py +195 -0
  33. loopix/api/client/api/templates/get_templates_template_id_builds_build_id_logs.py +272 -0
  34. loopix/api/client/api/templates/get_templates_template_id_builds_build_id_status.py +232 -0
  35. loopix/api/client/api/templates/get_templates_template_id_files_hash.py +180 -0
  36. loopix/api/client/api/templates/patch_templates_template_id.py +183 -0
  37. loopix/api/client/api/templates/patch_v_2_templates_template_id.py +185 -0
  38. loopix/api/client/api/templates/post_templates.py +172 -0
  39. loopix/api/client/api/templates/post_templates_template_id.py +181 -0
  40. loopix/api/client/api/templates/post_templates_template_id_builds_build_id.py +170 -0
  41. loopix/api/client/api/templates/post_v2_templates.py +172 -0
  42. loopix/api/client/api/templates/post_v3_templates.py +176 -0
  43. loopix/api/client/api/templates/post_v_2_templates_template_id_builds_build_id.py +192 -0
  44. loopix/api/client/api/volumes/__init__.py +1 -0
  45. loopix/api/client/api/volumes/delete_volumes_volume_id.py +161 -0
  46. loopix/api/client/api/volumes/get_volumes.py +140 -0
  47. loopix/api/client/api/volumes/get_volumes_volume_id.py +163 -0
  48. loopix/api/client/api/volumes/post_volumes.py +172 -0
  49. loopix/api/client/client.py +286 -0
  50. loopix/api/client/errors.py +16 -0
  51. loopix/api/client/models/__init__.py +185 -0
  52. loopix/api/client/models/admin_build_cancel_result.py +67 -0
  53. loopix/api/client/models/admin_sandbox_kill_result.py +67 -0
  54. loopix/api/client/models/assign_template_tags_request.py +67 -0
  55. loopix/api/client/models/assigned_template_tags.py +68 -0
  56. loopix/api/client/models/aws_registry.py +85 -0
  57. loopix/api/client/models/aws_registry_type.py +8 -0
  58. loopix/api/client/models/build_log_entry.py +89 -0
  59. loopix/api/client/models/build_status_reason.py +95 -0
  60. loopix/api/client/models/connect_sandbox.py +59 -0
  61. loopix/api/client/models/created_access_token.py +100 -0
  62. loopix/api/client/models/created_team_api_key.py +166 -0
  63. loopix/api/client/models/delete_template_tags_request.py +67 -0
  64. loopix/api/client/models/disk_metrics.py +91 -0
  65. loopix/api/client/models/error.py +67 -0
  66. loopix/api/client/models/gcp_registry.py +69 -0
  67. loopix/api/client/models/gcp_registry_type.py +8 -0
  68. loopix/api/client/models/general_registry.py +77 -0
  69. loopix/api/client/models/general_registry_type.py +8 -0
  70. loopix/api/client/models/identifier_masking_details.py +83 -0
  71. loopix/api/client/models/listed_sandbox.py +179 -0
  72. loopix/api/client/models/log_level.py +11 -0
  73. loopix/api/client/models/logs_direction.py +9 -0
  74. loopix/api/client/models/logs_source.py +9 -0
  75. loopix/api/client/models/machine_info.py +83 -0
  76. loopix/api/client/models/max_team_metric.py +78 -0
  77. loopix/api/client/models/mcp_type_0.py +44 -0
  78. loopix/api/client/models/new_access_token.py +59 -0
  79. loopix/api/client/models/new_sandbox.py +224 -0
  80. loopix/api/client/models/new_team_api_key.py +59 -0
  81. loopix/api/client/models/new_volume.py +59 -0
  82. loopix/api/client/models/node.py +160 -0
  83. loopix/api/client/models/node_detail.py +160 -0
  84. loopix/api/client/models/node_metrics.py +122 -0
  85. loopix/api/client/models/node_status.py +12 -0
  86. loopix/api/client/models/node_status_change.py +82 -0
  87. loopix/api/client/models/post_sandboxes_sandbox_id_refreshes_body.py +59 -0
  88. loopix/api/client/models/post_sandboxes_sandbox_id_snapshots_body.py +60 -0
  89. loopix/api/client/models/post_sandboxes_sandbox_id_timeout_body.py +59 -0
  90. loopix/api/client/models/resumed_sandbox.py +68 -0
  91. loopix/api/client/models/sandbox.py +145 -0
  92. loopix/api/client/models/sandbox_auto_resume_config.py +60 -0
  93. loopix/api/client/models/sandbox_detail.py +267 -0
  94. loopix/api/client/models/sandbox_lifecycle.py +70 -0
  95. loopix/api/client/models/sandbox_log.py +70 -0
  96. loopix/api/client/models/sandbox_log_entry.py +93 -0
  97. loopix/api/client/models/sandbox_log_entry_fields.py +44 -0
  98. loopix/api/client/models/sandbox_logs.py +91 -0
  99. loopix/api/client/models/sandbox_logs_v2_response.py +73 -0
  100. loopix/api/client/models/sandbox_metric.py +126 -0
  101. loopix/api/client/models/sandbox_network_config.py +118 -0
  102. loopix/api/client/models/sandbox_network_config_rules.py +72 -0
  103. loopix/api/client/models/sandbox_network_rule.py +74 -0
  104. loopix/api/client/models/sandbox_network_transform.py +79 -0
  105. loopix/api/client/models/sandbox_network_transform_headers.py +47 -0
  106. loopix/api/client/models/sandbox_network_update_config.py +114 -0
  107. loopix/api/client/models/sandbox_network_update_config_rules.py +71 -0
  108. loopix/api/client/models/sandbox_on_timeout.py +9 -0
  109. loopix/api/client/models/sandbox_pause_request.py +62 -0
  110. loopix/api/client/models/sandbox_state.py +9 -0
  111. loopix/api/client/models/sandbox_volume_mount.py +67 -0
  112. loopix/api/client/models/sandboxes_with_metrics.py +59 -0
  113. loopix/api/client/models/snapshot_info.py +70 -0
  114. loopix/api/client/models/team.py +83 -0
  115. loopix/api/client/models/team_api_key.py +158 -0
  116. loopix/api/client/models/team_metric.py +86 -0
  117. loopix/api/client/models/team_user.py +75 -0
  118. loopix/api/client/models/template.py +225 -0
  119. loopix/api/client/models/template_alias_response.py +67 -0
  120. loopix/api/client/models/template_build.py +139 -0
  121. loopix/api/client/models/template_build_file_upload.py +70 -0
  122. loopix/api/client/models/template_build_info.py +126 -0
  123. loopix/api/client/models/template_build_logs_response.py +73 -0
  124. loopix/api/client/models/template_build_request.py +115 -0
  125. loopix/api/client/models/template_build_request_v2.py +88 -0
  126. loopix/api/client/models/template_build_request_v3.py +107 -0
  127. loopix/api/client/models/template_build_start_v2.py +184 -0
  128. loopix/api/client/models/template_build_status.py +11 -0
  129. loopix/api/client/models/template_legacy.py +207 -0
  130. loopix/api/client/models/template_request_response_v3.py +99 -0
  131. loopix/api/client/models/template_step.py +91 -0
  132. loopix/api/client/models/template_tag.py +78 -0
  133. loopix/api/client/models/template_update_request.py +59 -0
  134. loopix/api/client/models/template_update_response.py +59 -0
  135. loopix/api/client/models/template_with_builds.py +156 -0
  136. loopix/api/client/models/update_team_api_key.py +59 -0
  137. loopix/api/client/models/volume.py +67 -0
  138. loopix/api/client/models/volume_and_token.py +75 -0
  139. loopix/api/client/models/volume_token.py +59 -0
  140. loopix/api/client/py.typed +1 -0
  141. loopix/api/client/types.py +54 -0
  142. loopix/api/client_async/__init__.py +74 -0
  143. loopix/api/client_sync/__init__.py +73 -0
  144. loopix/api/metadata.py +14 -0
  145. loopix/connection_config.py +309 -0
  146. loopix/envd/api.py +170 -0
  147. loopix/envd/filesystem/filesystem_connect.py +193 -0
  148. loopix/envd/filesystem/filesystem_pb2.py +80 -0
  149. loopix/envd/filesystem/filesystem_pb2.pyi +272 -0
  150. loopix/envd/process/process_connect.py +174 -0
  151. loopix/envd/process/process_pb2.py +96 -0
  152. loopix/envd/process/process_pb2.pyi +316 -0
  153. loopix/envd/rpc.py +139 -0
  154. loopix/envd/versions.py +11 -0
  155. loopix/exceptions.py +133 -0
  156. loopix/io_utils.py +57 -0
  157. loopix/paginator.py +52 -0
  158. loopix/py.typed +0 -0
  159. loopix/sandbox/_git/__init__.py +85 -0
  160. loopix/sandbox/_git/args.py +363 -0
  161. loopix/sandbox/_git/auth.py +132 -0
  162. loopix/sandbox/_git/config.py +32 -0
  163. loopix/sandbox/_git/parse.py +222 -0
  164. loopix/sandbox/_git/types.py +149 -0
  165. loopix/sandbox/commands/command_handle.py +69 -0
  166. loopix/sandbox/commands/main.py +39 -0
  167. loopix/sandbox/filesystem/filesystem.py +337 -0
  168. loopix/sandbox/filesystem/watch_handle.py +70 -0
  169. loopix/sandbox/main.py +227 -0
  170. loopix/sandbox/mcp.py +1949 -0
  171. loopix/sandbox/network.py +8 -0
  172. loopix/sandbox/sandbox_api.py +624 -0
  173. loopix/sandbox/signature.py +47 -0
  174. loopix/sandbox/utils.py +34 -0
  175. loopix/sandbox_async/commands/command.py +396 -0
  176. loopix/sandbox_async/commands/command_handle.py +298 -0
  177. loopix/sandbox_async/commands/pty.py +257 -0
  178. loopix/sandbox_async/filesystem/filesystem.py +720 -0
  179. loopix/sandbox_async/filesystem/watch_handle.py +97 -0
  180. loopix/sandbox_async/git.py +1100 -0
  181. loopix/sandbox_async/main.py +987 -0
  182. loopix/sandbox_async/paginator.py +140 -0
  183. loopix/sandbox_async/sandbox_api.py +504 -0
  184. loopix/sandbox_async/utils.py +7 -0
  185. loopix/sandbox_domains.py +5 -0
  186. loopix/sandbox_sync/commands/command.py +420 -0
  187. loopix/sandbox_sync/commands/command_handle.py +239 -0
  188. loopix/sandbox_sync/commands/pty.py +279 -0
  189. loopix/sandbox_sync/filesystem/filesystem.py +710 -0
  190. loopix/sandbox_sync/filesystem/watch_handle.py +102 -0
  191. loopix/sandbox_sync/git.py +1077 -0
  192. loopix/sandbox_sync/main.py +975 -0
  193. loopix/sandbox_sync/paginator.py +140 -0
  194. loopix/sandbox_sync/sandbox_api.py +491 -0
  195. loopix/template/consts.py +45 -0
  196. loopix/template/dockerfile_parser.py +286 -0
  197. loopix/template/logger.py +232 -0
  198. loopix/template/main.py +1368 -0
  199. loopix/template/readycmd.py +144 -0
  200. loopix/template/types.py +194 -0
  201. loopix/template/utils.py +426 -0
  202. loopix/template_async/build_api.py +419 -0
  203. loopix/template_async/main.py +528 -0
  204. loopix/template_sync/build_api.py +409 -0
  205. loopix/template_sync/main.py +529 -0
  206. loopix/volume/client/__init__.py +8 -0
  207. loopix/volume/client/api/__init__.py +1 -0
  208. loopix/volume/client/api/volumes/__init__.py +1 -0
  209. loopix/volume/client/api/volumes/delete_volumecontent_volume_id_path.py +174 -0
  210. loopix/volume/client/api/volumes/get_volumecontent_volume_id_dir.py +204 -0
  211. loopix/volume/client/api/volumes/get_volumecontent_volume_id_file.py +179 -0
  212. loopix/volume/client/api/volumes/get_volumecontent_volume_id_path.py +176 -0
  213. loopix/volume/client/api/volumes/patch_volumecontent_volume_id_path.py +203 -0
  214. loopix/volume/client/api/volumes/post_volumecontent_volume_id_dir.py +239 -0
  215. loopix/volume/client/api/volumes/put_volumecontent_volume_id_file.py +259 -0
  216. loopix/volume/client/client.py +286 -0
  217. loopix/volume/client/errors.py +16 -0
  218. loopix/volume/client/models/__init__.py +13 -0
  219. loopix/volume/client/models/error.py +67 -0
  220. loopix/volume/client/models/patch_volumecontent_volume_id_path_body.py +77 -0
  221. loopix/volume/client/models/volume_entry_stat.py +145 -0
  222. loopix/volume/client/models/volume_entry_stat_type.py +11 -0
  223. loopix/volume/client/py.typed +1 -0
  224. loopix/volume/client/types.py +54 -0
  225. loopix/volume/client_async/__init__.py +88 -0
  226. loopix/volume/client_sync/__init__.py +80 -0
  227. loopix/volume/connection_config.py +145 -0
  228. loopix/volume/types.py +62 -0
  229. loopix/volume/utils.py +52 -0
  230. loopix/volume/volume_async.py +639 -0
  231. loopix/volume/volume_sync.py +639 -0
  232. loopix_connect/__init__.py +1 -0
  233. loopix_connect/client.py +534 -0
  234. loopix_connect/py.typed +0 -0
  235. loopix_sdk-2.30.0.dist-info/METADATA +98 -0
  236. loopix_sdk-2.30.0.dist-info/RECORD +238 -0
  237. loopix_sdk-2.30.0.dist-info/WHEEL +4 -0
  238. loopix_sdk-2.30.0.dist-info/licenses/LICENSE +9 -0
@@ -0,0 +1,34 @@
1
+ from typing import TypeVar, Any, Generic, cast, Optional, Type
2
+ import functools
3
+
4
+ T = TypeVar("T")
5
+
6
+
7
+ class class_method_variant(Generic[T]):
8
+ def __init__(self, class_method_name):
9
+ self.class_method_name = class_method_name
10
+
11
+ method: Any
12
+
13
+ def __call__(self, method: T) -> T:
14
+ self.method = method
15
+ return cast(T, self)
16
+
17
+ def __get__(self, obj, objtype: Optional[Type[Any]] = None):
18
+ @functools.wraps(self.method)
19
+ def _wrapper(*args, **kwargs):
20
+ if obj is not None:
21
+ # Method was called as an instance method, e.g.
22
+ # instance.method(...)
23
+ return self.method(obj, *args, **kwargs)
24
+ elif len(args) > 0 and objtype is not None and isinstance(args[0], objtype):
25
+ # Method was called as a class method with the instance as the
26
+ # first argument, e.g. Class.method(instance, ...) which in
27
+ # Python is the same thing as calling an instance method
28
+ return self.method(args[0], *args[1:], **kwargs)
29
+ else:
30
+ # Method was called as a class method, e.g. Class.method(...)
31
+ class_method = getattr(objtype, self.class_method_name)
32
+ return class_method(*args, **kwargs)
33
+
34
+ return _wrapper
@@ -0,0 +1,396 @@
1
+ from typing import Dict, List, Literal, Optional, Union, overload
2
+
3
+ import loopix_connect
4
+ import httpcore
5
+ import httpx
6
+ from packaging.version import Version
7
+ from loopix.connection_config import (
8
+ ConnectionConfig,
9
+ Username,
10
+ KEEPALIVE_PING_HEADER,
11
+ KEEPALIVE_PING_INTERVAL_SEC,
12
+ )
13
+ from loopix.envd.process import process_connect, process_pb2
14
+ from loopix.envd.api import acheck_sandbox_health
15
+ from loopix.envd.rpc import authentication_header, ahandle_rpc_exception_with_health
16
+ from loopix.envd.versions import ENVD_COMMANDS_STDIN, ENVD_ENVD_CLOSE
17
+ from loopix.exceptions import SandboxException
18
+ from loopix.sandbox.commands.main import ProcessInfo
19
+ from loopix.sandbox.commands.command_handle import CommandResult
20
+ from loopix.sandbox_async.commands.command_handle import AsyncCommandHandle, Stderr, Stdout
21
+ from loopix.sandbox_async.utils import OutputHandler
22
+
23
+
24
+ class Commands:
25
+ """
26
+ Module for executing commands in the sandbox.
27
+ """
28
+
29
+ def __init__(
30
+ self,
31
+ envd_api_url: str,
32
+ connection_config: ConnectionConfig,
33
+ pool: httpcore.AsyncConnectionPool,
34
+ envd_version: Version,
35
+ envd_api: httpx.AsyncClient,
36
+ ) -> None:
37
+ self._connection_config = connection_config
38
+ self._envd_version = envd_version
39
+ self._check_health = lambda: acheck_sandbox_health(envd_api)
40
+ self._rpc = process_connect.ProcessClient(
41
+ envd_api_url,
42
+ # TODO: Fix and enable compression again — the headers compression is not solved for streaming.
43
+ # compressor=loopix_connect.GzipCompressor,
44
+ async_pool=pool,
45
+ json=True,
46
+ headers=connection_config.sandbox_headers,
47
+ logger=connection_config.logger,
48
+ )
49
+
50
+ async def list(
51
+ self,
52
+ request_timeout: Optional[float] = None,
53
+ ) -> List[ProcessInfo]:
54
+ """
55
+ Lists all running commands and PTY sessions.
56
+
57
+ :param request_timeout: Timeout for the request in **seconds**
58
+
59
+ :return: List of running commands and PTY sessions
60
+ """
61
+ try:
62
+ res = await self._rpc.alist(
63
+ process_pb2.ListRequest(),
64
+ request_timeout=self._connection_config.get_request_timeout(
65
+ request_timeout
66
+ ),
67
+ )
68
+ return [
69
+ ProcessInfo(
70
+ pid=p.pid,
71
+ tag=p.tag if p.HasField("tag") else None,
72
+ cmd=p.config.cmd,
73
+ args=list(p.config.args),
74
+ envs=dict(p.config.envs),
75
+ cwd=p.config.cwd if p.config.HasField("cwd") else None,
76
+ )
77
+ for p in res.processes
78
+ ]
79
+ except Exception as e:
80
+ raise await ahandle_rpc_exception_with_health(e, self._check_health)
81
+
82
+ async def kill(
83
+ self,
84
+ pid: int,
85
+ request_timeout: Optional[float] = None,
86
+ ) -> bool:
87
+ """
88
+ Kill a running command specified by its process ID.
89
+ It uses `SIGKILL` signal to kill the command.
90
+
91
+ :param pid: Process ID of the command. You can get the list of processes using `sandbox.commands.list()`
92
+ :param request_timeout: Timeout for the request in **seconds**
93
+
94
+ :return: `True` if the command was killed, `False` if the command was not found
95
+ """
96
+ try:
97
+ await self._rpc.asend_signal(
98
+ process_pb2.SendSignalRequest(
99
+ process=process_pb2.ProcessSelector(pid=pid),
100
+ signal=process_pb2.Signal.SIGNAL_SIGKILL,
101
+ ),
102
+ request_timeout=self._connection_config.get_request_timeout(
103
+ request_timeout
104
+ ),
105
+ )
106
+ return True
107
+ except Exception as e:
108
+ if isinstance(e, loopix_connect.ConnectException):
109
+ if e.status == loopix_connect.Code.not_found:
110
+ return False
111
+ raise await ahandle_rpc_exception_with_health(e, self._check_health)
112
+
113
+ async def send_stdin(
114
+ self,
115
+ pid: int,
116
+ data: Union[str, bytes],
117
+ request_timeout: Optional[float] = None,
118
+ ) -> None:
119
+ """
120
+ Send data to command stdin.
121
+
122
+ :param pid Process ID of the command. You can get the list of processes using `sandbox.commands.list()`.
123
+ :param data: Data to send to the command
124
+ :param request_timeout: Timeout for the request in **seconds**
125
+ """
126
+ try:
127
+ await self._rpc.asend_input(
128
+ process_pb2.SendInputRequest(
129
+ process=process_pb2.ProcessSelector(pid=pid),
130
+ input=process_pb2.ProcessInput(
131
+ stdin=data.encode() if isinstance(data, str) else data,
132
+ ),
133
+ ),
134
+ request_timeout=self._connection_config.get_request_timeout(
135
+ request_timeout
136
+ ),
137
+ )
138
+ except Exception as e:
139
+ raise await ahandle_rpc_exception_with_health(e, self._check_health)
140
+
141
+ async def close_stdin(
142
+ self,
143
+ pid: int,
144
+ request_timeout: Optional[float] = None,
145
+ ) -> None:
146
+ """
147
+ Close the command stdin.
148
+
149
+ This signals EOF to the command. The command must have been started with `stdin=True`.
150
+
151
+ :param pid Process ID of the command. You can get the list of processes using `sandbox.commands.list()`.
152
+ :param request_timeout: Timeout for the request in **seconds**
153
+ """
154
+ if self._envd_version < ENVD_ENVD_CLOSE:
155
+ raise SandboxException(
156
+ f"Sandbox envd version {self._envd_version} doesn't support closing stdin. "
157
+ f"Please rebuild your template to pick up the latest sandbox version."
158
+ )
159
+
160
+ try:
161
+ await self._rpc.aclose_stdin(
162
+ process_pb2.CloseStdinRequest(
163
+ process=process_pb2.ProcessSelector(pid=pid),
164
+ ),
165
+ request_timeout=self._connection_config.get_request_timeout(
166
+ request_timeout
167
+ ),
168
+ )
169
+ except Exception as e:
170
+ raise await ahandle_rpc_exception_with_health(e, self._check_health)
171
+
172
+ @overload
173
+ async def run(
174
+ self,
175
+ cmd: str,
176
+ background: Union[Literal[False], None] = None,
177
+ envs: Optional[Dict[str, str]] = None,
178
+ user: Optional[Username] = None,
179
+ cwd: Optional[str] = None,
180
+ on_stdout: Optional[OutputHandler[Stdout]] = None,
181
+ on_stderr: Optional[OutputHandler[Stderr]] = None,
182
+ stdin: Optional[bool] = None,
183
+ timeout: Optional[float] = 60,
184
+ request_timeout: Optional[float] = None,
185
+ ) -> CommandResult:
186
+ """
187
+ Start a new command and wait until it finishes executing.
188
+
189
+ :param cmd: Command to execute
190
+ :param background: **`False` if the command should be executed in the foreground**, `True` if the command should be executed in the background
191
+ :param envs: Environment variables used for the command
192
+ :param user: User to run the command as
193
+ :param cwd: Working directory to run the command
194
+ :param on_stdout: Callback for command stdout output
195
+ :param on_stderr: Callback for command stderr output
196
+ :param stdin: If `True`, the command will have a stdin stream that you can send data to using `sandbox.commands.send_stdin()`
197
+ :param timeout: Timeout for the command connection in **seconds**. Using `0` will not limit the command connection time
198
+ :param request_timeout: Timeout for the request in **seconds**
199
+
200
+ :return: `CommandResult` result of the command execution
201
+ """
202
+ ...
203
+
204
+ @overload
205
+ async def run(
206
+ self,
207
+ cmd: str,
208
+ background: Literal[True],
209
+ envs: Optional[Dict[str, str]] = None,
210
+ user: Optional[Username] = None,
211
+ cwd: Optional[str] = None,
212
+ on_stdout: Optional[OutputHandler[Stdout]] = None,
213
+ on_stderr: Optional[OutputHandler[Stderr]] = None,
214
+ stdin: Optional[bool] = None,
215
+ timeout: Optional[float] = 60,
216
+ request_timeout: Optional[float] = None,
217
+ ) -> AsyncCommandHandle:
218
+ """
219
+ Start a new command and return a handle to interact with it.
220
+
221
+ :param cmd: Command to execute
222
+ :param background: `False` if the command should be executed in the foreground, **`True` if the command should be executed in the background**
223
+ :param envs: Environment variables used for the command
224
+ :param user: User to run the command as
225
+ :param cwd: Working directory to run the command
226
+ :param on_stdout: Callback for command stdout output
227
+ :param on_stderr: Callback for command stderr output
228
+ :param stdin: If `True`, the command will have a stdin stream that you can send data to using `sandbox.commands.send_stdin()`
229
+ :param timeout: Timeout for the command connection in **seconds**. Using `0` will not limit the command connection time
230
+ :param request_timeout: Timeout for the request in **seconds**
231
+
232
+ :return: `AsyncCommandHandle` handle to interact with the running command
233
+ """
234
+ ...
235
+
236
+ async def run(
237
+ self,
238
+ cmd: str,
239
+ background: Union[bool, None] = None,
240
+ envs: Optional[Dict[str, str]] = None,
241
+ user: Optional[Username] = None,
242
+ cwd: Optional[str] = None,
243
+ on_stdout: Optional[OutputHandler[Stdout]] = None,
244
+ on_stderr: Optional[OutputHandler[Stderr]] = None,
245
+ stdin: Optional[bool] = None,
246
+ timeout: Optional[float] = 60,
247
+ request_timeout: Optional[float] = None,
248
+ ):
249
+ # Check version for stdin support
250
+ if stdin is False and self._envd_version < ENVD_COMMANDS_STDIN:
251
+ raise SandboxException(
252
+ f"Sandbox envd version {self._envd_version} can't specify stdin, it's always turned on. "
253
+ f"Please rebuild your template if you need this feature."
254
+ )
255
+
256
+ # Default to `False`
257
+ stdin = stdin or False
258
+
259
+ proc = await self._start(
260
+ cmd,
261
+ envs,
262
+ user,
263
+ cwd,
264
+ stdin,
265
+ timeout,
266
+ request_timeout,
267
+ on_stdout=on_stdout,
268
+ on_stderr=on_stderr,
269
+ )
270
+
271
+ return proc if background else await proc.wait()
272
+
273
+ async def _start(
274
+ self,
275
+ cmd: str,
276
+ envs: Optional[Dict[str, str]],
277
+ user: Optional[Username],
278
+ cwd: Optional[str],
279
+ stdin: bool,
280
+ timeout: Optional[float],
281
+ request_timeout: Optional[float],
282
+ on_stdout: Optional[OutputHandler[Stdout]],
283
+ on_stderr: Optional[OutputHandler[Stderr]],
284
+ ) -> AsyncCommandHandle:
285
+ events = self._rpc.astart(
286
+ process_pb2.StartRequest(
287
+ process=process_pb2.ProcessConfig(
288
+ cmd="/bin/bash",
289
+ envs=envs,
290
+ args=["-l", "-c", cmd],
291
+ cwd=cwd,
292
+ ),
293
+ stdin=stdin,
294
+ ),
295
+ headers={
296
+ **authentication_header(self._envd_version, user),
297
+ KEEPALIVE_PING_HEADER: str(KEEPALIVE_PING_INTERVAL_SEC),
298
+ },
299
+ timeout=timeout,
300
+ request_timeout=self._connection_config.get_request_timeout(
301
+ request_timeout
302
+ ),
303
+ )
304
+
305
+ try:
306
+ start_event = await events.__anext__()
307
+
308
+ if not start_event.HasField("event"):
309
+ raise SandboxException(
310
+ f"Failed to start process: expected start event, got {start_event}"
311
+ )
312
+
313
+ pid = start_event.event.start.pid
314
+ return AsyncCommandHandle(
315
+ pid=pid,
316
+ handle_kill=lambda: self.kill(pid),
317
+ events=events,
318
+ on_stdout=on_stdout,
319
+ on_stderr=on_stderr,
320
+ handle_send_stdin=lambda data, request_timeout=None: self.send_stdin(
321
+ pid, data, request_timeout
322
+ ),
323
+ handle_close_stdin=lambda request_timeout=None: self.close_stdin(
324
+ pid, request_timeout
325
+ ),
326
+ check_health=self._check_health,
327
+ )
328
+ except Exception as e:
329
+ try:
330
+ await events.aclose()
331
+ except Exception:
332
+ pass
333
+ raise await ahandle_rpc_exception_with_health(e, self._check_health)
334
+
335
+ async def connect(
336
+ self,
337
+ pid: int,
338
+ timeout: Optional[float] = 60,
339
+ request_timeout: Optional[float] = None,
340
+ on_stdout: Optional[OutputHandler[Stdout]] = None,
341
+ on_stderr: Optional[OutputHandler[Stderr]] = None,
342
+ ) -> AsyncCommandHandle:
343
+ """
344
+ Connects to a running command.
345
+ You can use `AsyncCommandHandle.wait()` to wait for the command to finish and get execution results.
346
+
347
+ :param pid: Process ID of the command to connect to. You can get the list of processes using `sandbox.commands.list()`
348
+ :param request_timeout: Request timeout in **seconds**
349
+ :param timeout: Timeout for the command connection in **seconds**. Using `0` will not limit the command connection time
350
+ :param on_stdout: Callback for command stdout output
351
+ :param on_stderr: Callback for command stderr output
352
+
353
+ :return: `AsyncCommandHandle` handle to interact with the running command
354
+ """
355
+ events = self._rpc.aconnect(
356
+ process_pb2.ConnectRequest(
357
+ process=process_pb2.ProcessSelector(pid=pid),
358
+ ),
359
+ headers={
360
+ KEEPALIVE_PING_HEADER: str(KEEPALIVE_PING_INTERVAL_SEC),
361
+ },
362
+ timeout=timeout,
363
+ request_timeout=self._connection_config.get_request_timeout(
364
+ request_timeout
365
+ ),
366
+ )
367
+
368
+ try:
369
+ start_event = await events.__anext__()
370
+
371
+ if not start_event.HasField("event"):
372
+ raise SandboxException(
373
+ f"Failed to connect to process: expected start event, got {start_event}"
374
+ )
375
+
376
+ pid = start_event.event.start.pid
377
+ return AsyncCommandHandle(
378
+ pid=pid,
379
+ handle_kill=lambda: self.kill(pid),
380
+ events=events,
381
+ on_stdout=on_stdout,
382
+ on_stderr=on_stderr,
383
+ handle_send_stdin=lambda data, request_timeout=None: self.send_stdin(
384
+ pid, data, request_timeout
385
+ ),
386
+ handle_close_stdin=lambda request_timeout=None: self.close_stdin(
387
+ pid, request_timeout
388
+ ),
389
+ check_health=self._check_health,
390
+ )
391
+ except Exception as e:
392
+ try:
393
+ await events.aclose()
394
+ except Exception:
395
+ pass
396
+ raise await ahandle_rpc_exception_with_health(e, self._check_health)