scalebox-sdk 0.1.22__py3-none-any.whl → 0.1.24__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.
- scalebox/__init__.py +1 -1
- scalebox/api/client/models/new_sandbox.py +1 -1
- scalebox/sandbox_sync/filesystem/filesystem.py +543 -543
- scalebox/version.py +2 -2
- {scalebox_sdk-0.1.22.dist-info → scalebox_sdk-0.1.24.dist-info}/METADATA +1 -1
- {scalebox_sdk-0.1.22.dist-info → scalebox_sdk-0.1.24.dist-info}/RECORD +10 -10
- {scalebox_sdk-0.1.22.dist-info → scalebox_sdk-0.1.24.dist-info}/WHEEL +0 -0
- {scalebox_sdk-0.1.22.dist-info → scalebox_sdk-0.1.24.dist-info}/entry_points.txt +0 -0
- {scalebox_sdk-0.1.22.dist-info → scalebox_sdk-0.1.24.dist-info}/licenses/LICENSE +0 -0
- {scalebox_sdk-0.1.22.dist-info → scalebox_sdk-0.1.24.dist-info}/top_level.txt +0 -0
scalebox/__init__.py
CHANGED
|
@@ -31,7 +31,7 @@ class NewSandbox:
|
|
|
31
31
|
timeout: Union[Unset, int] = 15
|
|
32
32
|
additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict)
|
|
33
33
|
is_async: Union[Unset, bool] = False
|
|
34
|
-
storage_gb: Union[Unset, int] =
|
|
34
|
+
storage_gb: Union[Unset, int] = UNSET
|
|
35
35
|
|
|
36
36
|
def to_dict(self) -> dict[str, Any]:
|
|
37
37
|
template_id = self.template_id
|
|
@@ -1,543 +1,543 @@
|
|
|
1
|
-
from io import IOBase
|
|
2
|
-
from typing import IO, Iterator, List, Literal, Optional, Union, overload
|
|
3
|
-
|
|
4
|
-
import httpcore
|
|
5
|
-
import httpx
|
|
6
|
-
import urllib3
|
|
7
|
-
from packaging.version import Version
|
|
8
|
-
|
|
9
|
-
from ... import csx_connect
|
|
10
|
-
from ...connection_config import (
|
|
11
|
-
KEEPALIVE_PING_HEADER,
|
|
12
|
-
KEEPALIVE_PING_INTERVAL_SEC,
|
|
13
|
-
ConnectionConfig,
|
|
14
|
-
Username,
|
|
15
|
-
)
|
|
16
|
-
from ...exceptions import InvalidArgumentException, TemplateException
|
|
17
|
-
from ...generated import api_pb2, api_pb2_connect
|
|
18
|
-
from ...generated.api import (
|
|
19
|
-
ENVD_API_DOWNLOAD_FILES_ROUTE,
|
|
20
|
-
ENVD_API_UPLOAD_FILES_ROUTE,
|
|
21
|
-
handle_envd_api_exception,
|
|
22
|
-
)
|
|
23
|
-
from ...generated.rpc import authentication_header, handle_rpc_exception
|
|
24
|
-
from ...generated.versions import ENVD_VERSION_RECURSIVE_WATCH
|
|
25
|
-
from ...sandbox.filesystem.filesystem import (
|
|
26
|
-
EntryInfo,
|
|
27
|
-
FileType,
|
|
28
|
-
WriteEntry,
|
|
29
|
-
WriteInfo,
|
|
30
|
-
map_file_type,
|
|
31
|
-
)
|
|
32
|
-
from ...sandbox_sync.filesystem.watch_handle import WatchHandle
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
class Filesystem:
|
|
36
|
-
"""
|
|
37
|
-
Module for interacting with the filesystem in the sandbox.
|
|
38
|
-
"""
|
|
39
|
-
|
|
40
|
-
def __init__(
|
|
41
|
-
self,
|
|
42
|
-
envd_api_url: str,
|
|
43
|
-
envd_version: Optional[str],
|
|
44
|
-
connection_config: ConnectionConfig,
|
|
45
|
-
pool: urllib3.PoolManager,
|
|
46
|
-
envd_api: httpx.Client,
|
|
47
|
-
) -> None:
|
|
48
|
-
self._envd_api_url = envd_api_url
|
|
49
|
-
self._envd_version = envd_version
|
|
50
|
-
self._connection_config = connection_config
|
|
51
|
-
self._pool = pool
|
|
52
|
-
self._envd_api = envd_api
|
|
53
|
-
|
|
54
|
-
self._rpc = api_pb2_connect.FilesystemClient(
|
|
55
|
-
envd_api_url,
|
|
56
|
-
http_client=pool
|
|
57
|
-
)
|
|
58
|
-
self._headers = self._connection_config.headers
|
|
59
|
-
|
|
60
|
-
@overload
|
|
61
|
-
def read(
|
|
62
|
-
self,
|
|
63
|
-
path: str,
|
|
64
|
-
format: Literal["text"] = "text",
|
|
65
|
-
user: Username = "user",
|
|
66
|
-
request_timeout: Optional[float] = None,
|
|
67
|
-
) -> str:
|
|
68
|
-
"""
|
|
69
|
-
Read file content as a `str`.
|
|
70
|
-
|
|
71
|
-
:param path: Path to the file
|
|
72
|
-
:param user: Run the operation as this user
|
|
73
|
-
:param format: Format of the file content—`text` by default
|
|
74
|
-
:param request_timeout: Timeout for the request in **seconds**
|
|
75
|
-
|
|
76
|
-
:return: File content as a `str`
|
|
77
|
-
"""
|
|
78
|
-
...
|
|
79
|
-
|
|
80
|
-
@overload
|
|
81
|
-
def read(
|
|
82
|
-
self,
|
|
83
|
-
path: str,
|
|
84
|
-
format: Literal["bytes"],
|
|
85
|
-
user: Username = "user",
|
|
86
|
-
request_timeout: Optional[float] = None,
|
|
87
|
-
) -> bytearray:
|
|
88
|
-
"""
|
|
89
|
-
Read file content as a `bytearray`.
|
|
90
|
-
|
|
91
|
-
:param path: Path to the file
|
|
92
|
-
:param user: Run the operation as this user
|
|
93
|
-
:param format: Format of the file content—`bytes`
|
|
94
|
-
:param request_timeout: Timeout for the request in **seconds**
|
|
95
|
-
|
|
96
|
-
:return: File content as a `bytearray`
|
|
97
|
-
"""
|
|
98
|
-
...
|
|
99
|
-
|
|
100
|
-
@overload
|
|
101
|
-
def read(
|
|
102
|
-
self,
|
|
103
|
-
path: str,
|
|
104
|
-
format: Literal["stream"],
|
|
105
|
-
user: Username = "user",
|
|
106
|
-
request_timeout: Optional[float] = None,
|
|
107
|
-
) -> Iterator[bytes]:
|
|
108
|
-
"""
|
|
109
|
-
Read file content as a `Iterator[bytes]`.
|
|
110
|
-
|
|
111
|
-
:param path: Path to the file
|
|
112
|
-
:param user: Run the operation as this user
|
|
113
|
-
:param format: Format of the file content—`stream`
|
|
114
|
-
:param request_timeout: Timeout for the request in **seconds**
|
|
115
|
-
|
|
116
|
-
:return: File content as an `Iterator[bytes]`
|
|
117
|
-
"""
|
|
118
|
-
...
|
|
119
|
-
|
|
120
|
-
def read(
|
|
121
|
-
self,
|
|
122
|
-
path: str,
|
|
123
|
-
format: Literal["text", "bytes", "stream"] = "text",
|
|
124
|
-
user: Username = "user",
|
|
125
|
-
request_timeout: Optional[float] = None,
|
|
126
|
-
):
|
|
127
|
-
# Use the /download/ endpoint from sandboxagent.go
|
|
128
|
-
download_url = f"/download/{path.lstrip('/')}"
|
|
129
|
-
|
|
130
|
-
r = self._envd_api.get(
|
|
131
|
-
download_url,
|
|
132
|
-
timeout=self._connection_config.get_request_timeout(request_timeout),
|
|
133
|
-
)
|
|
134
|
-
|
|
135
|
-
err = handle_envd_api_exception(r)
|
|
136
|
-
if err:
|
|
137
|
-
raise err
|
|
138
|
-
|
|
139
|
-
if format == "text":
|
|
140
|
-
return r.text
|
|
141
|
-
elif format == "bytes":
|
|
142
|
-
return bytearray(r.content)
|
|
143
|
-
elif format == "stream":
|
|
144
|
-
return r.iter_bytes()
|
|
145
|
-
|
|
146
|
-
@overload
|
|
147
|
-
def write(
|
|
148
|
-
self,
|
|
149
|
-
path: str,
|
|
150
|
-
data: Union[str, bytes, IO],
|
|
151
|
-
user: Username = "user",
|
|
152
|
-
request_timeout: Optional[float] = None,
|
|
153
|
-
) -> WriteInfo:
|
|
154
|
-
"""
|
|
155
|
-
Write content to a file on the path.
|
|
156
|
-
|
|
157
|
-
Writing to a file that doesn't exist creates the file.
|
|
158
|
-
|
|
159
|
-
Writing to a file that already exists overwrites the file.
|
|
160
|
-
|
|
161
|
-
Writing to a file at path that doesn't exist creates the necessary directories.
|
|
162
|
-
|
|
163
|
-
:param path: Path to the file
|
|
164
|
-
:param data: Data to write to the file, can be a `str`, `bytes`, or `IO`.
|
|
165
|
-
:param user: Run the operation as this user
|
|
166
|
-
:param request_timeout: Timeout for the request in **seconds**
|
|
167
|
-
|
|
168
|
-
:return: Information about the written file
|
|
169
|
-
"""
|
|
170
|
-
|
|
171
|
-
@overload
|
|
172
|
-
def write(
|
|
173
|
-
self,
|
|
174
|
-
files: List[WriteEntry],
|
|
175
|
-
user: Optional[Username] = "user",
|
|
176
|
-
request_timeout: Optional[float] = None,
|
|
177
|
-
) -> List[WriteInfo]:
|
|
178
|
-
"""
|
|
179
|
-
Writes a list of files to the filesystem.
|
|
180
|
-
When writing to a file that doesn't exist, the file will get created.
|
|
181
|
-
When writing to a file that already exists, the file will get overwritten.
|
|
182
|
-
When writing to a file that's in a directory that doesn't exist, you'll get an error.
|
|
183
|
-
|
|
184
|
-
:param files: list of files to write
|
|
185
|
-
:param user: Run the operation as this user
|
|
186
|
-
:param request_timeout: Timeout for the request
|
|
187
|
-
:return: Information about the written files
|
|
188
|
-
"""
|
|
189
|
-
|
|
190
|
-
def write(
|
|
191
|
-
self,
|
|
192
|
-
path_or_files: Union[str, List[WriteEntry]],
|
|
193
|
-
data_or_user: Union[str, bytes, IO, Username] = "user",
|
|
194
|
-
user_or_request_timeout: Optional[Union[float, Username]] = None,
|
|
195
|
-
request_timeout_or_none: Optional[float] = None,
|
|
196
|
-
) -> Union[WriteInfo, List[WriteInfo]]:
|
|
197
|
-
path, write_files, user, request_timeout = None, [], "user", None
|
|
198
|
-
if isinstance(path_or_files, str):
|
|
199
|
-
if isinstance(data_or_user, list):
|
|
200
|
-
raise Exception(
|
|
201
|
-
"Cannot specify both path and array of files. You have to specify either path and data for a single file or an array for multiple files."
|
|
202
|
-
)
|
|
203
|
-
path, write_files, user, request_timeout = (
|
|
204
|
-
path_or_files,
|
|
205
|
-
[{"path": path_or_files, "data": data_or_user}],
|
|
206
|
-
user_or_request_timeout or "user",
|
|
207
|
-
request_timeout_or_none,
|
|
208
|
-
)
|
|
209
|
-
else:
|
|
210
|
-
if path_or_files is None:
|
|
211
|
-
raise Exception("Path or files are required")
|
|
212
|
-
path, write_files, user, request_timeout = (
|
|
213
|
-
None,
|
|
214
|
-
path_or_files,
|
|
215
|
-
data_or_user,
|
|
216
|
-
user_or_request_timeout,
|
|
217
|
-
)
|
|
218
|
-
|
|
219
|
-
# Allow passing empty list of files
|
|
220
|
-
if len(write_files) == 0:
|
|
221
|
-
return []
|
|
222
|
-
|
|
223
|
-
# Use the /upload endpoint from sandboxagent.go
|
|
224
|
-
# This endpoint expects multipart/form-data with 'file' field and 'path' form field
|
|
225
|
-
results = []
|
|
226
|
-
for file in write_files:
|
|
227
|
-
file_path, file_data = file["path"], file["data"]
|
|
228
|
-
|
|
229
|
-
# Prepare file data
|
|
230
|
-
if isinstance(file_data, str):
|
|
231
|
-
file_content = file_data.encode('utf-8')
|
|
232
|
-
elif isinstance(file_data, bytes):
|
|
233
|
-
file_content = file_data
|
|
234
|
-
elif isinstance(file_data, IOBase):
|
|
235
|
-
file_content = file_data.read()
|
|
236
|
-
if isinstance(file_content, str):
|
|
237
|
-
file_content = file_content.encode('utf-8')
|
|
238
|
-
else:
|
|
239
|
-
raise ValueError(f"Unsupported data type for file {file_path}")
|
|
240
|
-
|
|
241
|
-
# Prepare multipart form data
|
|
242
|
-
files = [("file", (file_path, file_content))]
|
|
243
|
-
data = {"path": file_path}
|
|
244
|
-
|
|
245
|
-
r = self._envd_api.post(
|
|
246
|
-
"/upload",
|
|
247
|
-
files=files,
|
|
248
|
-
data=data,
|
|
249
|
-
timeout=self._connection_config.get_request_timeout(request_timeout),
|
|
250
|
-
)
|
|
251
|
-
|
|
252
|
-
err = handle_envd_api_exception(r)
|
|
253
|
-
if err:
|
|
254
|
-
raise err
|
|
255
|
-
|
|
256
|
-
# For now, create a mock WriteInfo since sandboxagent.go returns plain text
|
|
257
|
-
# In a real implementation, you might want to enhance the server to return JSON
|
|
258
|
-
results.append(WriteInfo(
|
|
259
|
-
path=file_path,
|
|
260
|
-
name=file_path.split('/')[-1] if '/' in file_path else file_path,
|
|
261
|
-
type=FileType.FILE,
|
|
262
|
-
))
|
|
263
|
-
|
|
264
|
-
# Return appropriate response based on input format
|
|
265
|
-
if len(results) == 1 and path:
|
|
266
|
-
return results[0]
|
|
267
|
-
else:
|
|
268
|
-
return results
|
|
269
|
-
|
|
270
|
-
def list(
|
|
271
|
-
self,
|
|
272
|
-
path: str,
|
|
273
|
-
depth: Optional[int] = 1,
|
|
274
|
-
user: Username = "user",
|
|
275
|
-
request_timeout: Optional[float] = None,
|
|
276
|
-
) -> List[EntryInfo]:
|
|
277
|
-
"""
|
|
278
|
-
List entries in a directory.
|
|
279
|
-
|
|
280
|
-
:param path: Path to the directory
|
|
281
|
-
:param depth: Depth of the directory to list
|
|
282
|
-
:param user: Run the operation as this user
|
|
283
|
-
:param request_timeout: Timeout for the request in **seconds**
|
|
284
|
-
|
|
285
|
-
:return: List of entries in the directory
|
|
286
|
-
"""
|
|
287
|
-
if depth is not None and depth < 1:
|
|
288
|
-
raise InvalidArgumentException("depth should be at least 1")
|
|
289
|
-
|
|
290
|
-
try:
|
|
291
|
-
res = self._rpc.list_dir(
|
|
292
|
-
api_pb2.ListDirRequest(path=path, depth=depth),
|
|
293
|
-
self._headers,
|
|
294
|
-
timeout_seconds=self._connection_config.get_request_timeout(
|
|
295
|
-
request_timeout
|
|
296
|
-
),
|
|
297
|
-
)
|
|
298
|
-
|
|
299
|
-
entries: List[EntryInfo] = []
|
|
300
|
-
for entry in res.entries:
|
|
301
|
-
event_type = map_file_type(entry.type)
|
|
302
|
-
|
|
303
|
-
if event_type:
|
|
304
|
-
entries.append(
|
|
305
|
-
EntryInfo(
|
|
306
|
-
name=entry.name,
|
|
307
|
-
type=event_type,
|
|
308
|
-
path=entry.path,
|
|
309
|
-
size=entry.size,
|
|
310
|
-
mode=entry.mode,
|
|
311
|
-
permissions=entry.permissions,
|
|
312
|
-
owner=entry.owner,
|
|
313
|
-
group=entry.group,
|
|
314
|
-
modified_time=entry.modified_time.ToDatetime(),
|
|
315
|
-
# Optional, we can't directly access symlink_target otherwise if will be "" instead of None
|
|
316
|
-
symlink_target=(
|
|
317
|
-
entry.symlink_target
|
|
318
|
-
if entry.HasField("symlink_target")
|
|
319
|
-
else None
|
|
320
|
-
),
|
|
321
|
-
)
|
|
322
|
-
)
|
|
323
|
-
|
|
324
|
-
return entries
|
|
325
|
-
except Exception as e:
|
|
326
|
-
raise handle_rpc_exception(e)
|
|
327
|
-
|
|
328
|
-
def exists(
|
|
329
|
-
self,
|
|
330
|
-
path: str,
|
|
331
|
-
user: Username = "user",
|
|
332
|
-
request_timeout: Optional[float] = None,
|
|
333
|
-
) -> bool:
|
|
334
|
-
"""
|
|
335
|
-
Check if a file or a directory exists.
|
|
336
|
-
|
|
337
|
-
:param path: Path to a file or a directory
|
|
338
|
-
:param user: Run the operation as this user
|
|
339
|
-
:param request_timeout: Timeout for the request in **seconds**
|
|
340
|
-
|
|
341
|
-
:return: `True` if the file or directory exists, `False` otherwise
|
|
342
|
-
"""
|
|
343
|
-
try:
|
|
344
|
-
self._rpc.stat(
|
|
345
|
-
api_pb2.StatRequest(path=path),
|
|
346
|
-
self._headers,
|
|
347
|
-
timeout_seconds=self._connection_config.get_request_timeout(
|
|
348
|
-
request_timeout
|
|
349
|
-
),
|
|
350
|
-
)
|
|
351
|
-
return True
|
|
352
|
-
|
|
353
|
-
except Exception as e:
|
|
354
|
-
if "no such file or directory" in str(e):
|
|
355
|
-
return False
|
|
356
|
-
raise handle_rpc_exception(e)
|
|
357
|
-
|
|
358
|
-
def get_info(
|
|
359
|
-
self,
|
|
360
|
-
path: str,
|
|
361
|
-
user: Username = "user",
|
|
362
|
-
request_timeout: Optional[float] = None,
|
|
363
|
-
) -> EntryInfo:
|
|
364
|
-
"""
|
|
365
|
-
Get information about a file or directory.
|
|
366
|
-
|
|
367
|
-
:param path: Path to a file or a directory
|
|
368
|
-
:param user: Run the operation as this user
|
|
369
|
-
:param request_timeout: Timeout for the request in **seconds**
|
|
370
|
-
|
|
371
|
-
:return: Information about the file or directory like name, type, and path
|
|
372
|
-
"""
|
|
373
|
-
try:
|
|
374
|
-
r = self._rpc.stat(
|
|
375
|
-
api_pb2.StatRequest(path=path),
|
|
376
|
-
self._headers,
|
|
377
|
-
timeout_seconds=self._connection_config.get_request_timeout(
|
|
378
|
-
request_timeout
|
|
379
|
-
),
|
|
380
|
-
)
|
|
381
|
-
|
|
382
|
-
return EntryInfo(
|
|
383
|
-
name=r.entry.name,
|
|
384
|
-
type=map_file_type(r.entry.type),
|
|
385
|
-
path=r.entry.path,
|
|
386
|
-
size=r.entry.size,
|
|
387
|
-
mode=r.entry.mode,
|
|
388
|
-
permissions=r.entry.permissions,
|
|
389
|
-
owner=r.entry.owner,
|
|
390
|
-
group=r.entry.group,
|
|
391
|
-
modified_time=r.entry.modified_time.ToDatetime(),
|
|
392
|
-
# Optional, we can't directly access symlink_target otherwise if will be "" instead of None
|
|
393
|
-
symlink_target=(
|
|
394
|
-
r.entry.symlink_target
|
|
395
|
-
if r.entry.HasField("symlink_target")
|
|
396
|
-
else None
|
|
397
|
-
),
|
|
398
|
-
)
|
|
399
|
-
except Exception as e:
|
|
400
|
-
raise handle_rpc_exception(e)
|
|
401
|
-
|
|
402
|
-
def remove(
|
|
403
|
-
self,
|
|
404
|
-
path: str,
|
|
405
|
-
user: Username = "user",
|
|
406
|
-
request_timeout: Optional[float] = None,
|
|
407
|
-
) -> None:
|
|
408
|
-
"""
|
|
409
|
-
Remove a file or a directory.
|
|
410
|
-
|
|
411
|
-
:param path: Path to a file or a directory
|
|
412
|
-
:param user: Run the operation as this user
|
|
413
|
-
:param request_timeout: Timeout for the request in **seconds**
|
|
414
|
-
"""
|
|
415
|
-
try:
|
|
416
|
-
self._rpc.remove(
|
|
417
|
-
api_pb2.RemoveRequest(path=path),
|
|
418
|
-
self._headers,
|
|
419
|
-
timeout_seconds=self._connection_config.get_request_timeout(
|
|
420
|
-
request_timeout
|
|
421
|
-
),
|
|
422
|
-
)
|
|
423
|
-
except Exception as e:
|
|
424
|
-
raise handle_rpc_exception(e)
|
|
425
|
-
|
|
426
|
-
def rename(
|
|
427
|
-
self,
|
|
428
|
-
old_path: str,
|
|
429
|
-
new_path: str,
|
|
430
|
-
user: Username = "user",
|
|
431
|
-
request_timeout: Optional[float] = None,
|
|
432
|
-
) -> EntryInfo:
|
|
433
|
-
"""
|
|
434
|
-
Rename a file or directory.
|
|
435
|
-
|
|
436
|
-
:param old_path: Path to the file or directory to rename
|
|
437
|
-
:param new_path: New path to the file or directory
|
|
438
|
-
:param user: Run the operation as this user
|
|
439
|
-
:param request_timeout: Timeout for the request in **seconds**
|
|
440
|
-
|
|
441
|
-
:return: Information about the renamed file or directory
|
|
442
|
-
"""
|
|
443
|
-
try:
|
|
444
|
-
r = self._rpc.move(
|
|
445
|
-
api_pb2.MoveRequest(
|
|
446
|
-
source=old_path,
|
|
447
|
-
destination=new_path,
|
|
448
|
-
),
|
|
449
|
-
self._headers,
|
|
450
|
-
timeout_seconds=self._connection_config.get_request_timeout(
|
|
451
|
-
request_timeout
|
|
452
|
-
),
|
|
453
|
-
)
|
|
454
|
-
|
|
455
|
-
return EntryInfo(
|
|
456
|
-
name=r.entry.name,
|
|
457
|
-
type=map_file_type(r.entry.type),
|
|
458
|
-
path=r.entry.path,
|
|
459
|
-
size=r.entry.size,
|
|
460
|
-
mode=r.entry.mode,
|
|
461
|
-
permissions=r.entry.permissions,
|
|
462
|
-
owner=r.entry.owner,
|
|
463
|
-
group=r.entry.group,
|
|
464
|
-
modified_time=r.entry.modified_time.ToDatetime(),
|
|
465
|
-
# Optional, we can't directly access symlink_target otherwise if will be "" instead of None
|
|
466
|
-
symlink_target=(
|
|
467
|
-
r.entry.symlink_target
|
|
468
|
-
if r.entry.HasField("symlink_target")
|
|
469
|
-
else None
|
|
470
|
-
),
|
|
471
|
-
)
|
|
472
|
-
except Exception as e:
|
|
473
|
-
raise handle_rpc_exception(e)
|
|
474
|
-
|
|
475
|
-
def make_dir(
|
|
476
|
-
self,
|
|
477
|
-
path: str,
|
|
478
|
-
user: Username = "user",
|
|
479
|
-
request_timeout: Optional[float] = None,
|
|
480
|
-
) -> bool:
|
|
481
|
-
"""
|
|
482
|
-
Create a new directory and all directories along the way if needed on the specified path.
|
|
483
|
-
|
|
484
|
-
:param path: Path to a new directory. For example '/dirA/dirB' when creating 'dirB'.
|
|
485
|
-
:param user: Run the operation as this user
|
|
486
|
-
:param request_timeout: Timeout for the request in **seconds**
|
|
487
|
-
|
|
488
|
-
:return: `True` if the directory was created, `False` if the directory already exists
|
|
489
|
-
"""
|
|
490
|
-
try:
|
|
491
|
-
self._rpc.make_dir(
|
|
492
|
-
api_pb2.MakeDirRequest(path=path),
|
|
493
|
-
self._headers,
|
|
494
|
-
timeout_seconds=self._connection_config.get_request_timeout(
|
|
495
|
-
request_timeout
|
|
496
|
-
),
|
|
497
|
-
)
|
|
498
|
-
|
|
499
|
-
return True
|
|
500
|
-
except Exception as e:
|
|
501
|
-
if "directory already exists" in str(e):
|
|
502
|
-
return False
|
|
503
|
-
raise handle_rpc_exception(e)
|
|
504
|
-
|
|
505
|
-
def watch_dir(
|
|
506
|
-
self,
|
|
507
|
-
path: str,
|
|
508
|
-
user: Username = "user",
|
|
509
|
-
request_timeout: Optional[float] = None,
|
|
510
|
-
recursive: bool = False,
|
|
511
|
-
) -> WatchHandle:
|
|
512
|
-
"""
|
|
513
|
-
Watch directory for filesystem events.
|
|
514
|
-
|
|
515
|
-
:param path: Path to a directory to watch
|
|
516
|
-
:param user: Run the operation as this user
|
|
517
|
-
:param request_timeout: Timeout for the request in **seconds**
|
|
518
|
-
:param recursive: Watch directory recursively
|
|
519
|
-
|
|
520
|
-
:return: `WatchHandle` object for stopping watching directory
|
|
521
|
-
"""
|
|
522
|
-
if (
|
|
523
|
-
recursive
|
|
524
|
-
and self._envd_version is not None
|
|
525
|
-
and Version(self._envd_version) < ENVD_VERSION_RECURSIVE_WATCH
|
|
526
|
-
):
|
|
527
|
-
raise TemplateException(
|
|
528
|
-
"You need to update the template to use recursive watching. "
|
|
529
|
-
"You can do this by running `
|
|
530
|
-
)
|
|
531
|
-
|
|
532
|
-
try:
|
|
533
|
-
r = self._rpc.create_watcher(
|
|
534
|
-
api_pb2.CreateWatcherRequest(path=path, recursive=recursive),
|
|
535
|
-
self._headers,
|
|
536
|
-
timeout_seconds=self._connection_config.get_request_timeout(
|
|
537
|
-
request_timeout
|
|
538
|
-
),
|
|
539
|
-
)
|
|
540
|
-
except Exception as e:
|
|
541
|
-
raise handle_rpc_exception(e)
|
|
542
|
-
|
|
543
|
-
return WatchHandle(self._rpc, r.watcher_id)
|
|
1
|
+
from io import IOBase
|
|
2
|
+
from typing import IO, Iterator, List, Literal, Optional, Union, overload
|
|
3
|
+
|
|
4
|
+
import httpcore
|
|
5
|
+
import httpx
|
|
6
|
+
import urllib3
|
|
7
|
+
from packaging.version import Version
|
|
8
|
+
|
|
9
|
+
from ... import csx_connect
|
|
10
|
+
from ...connection_config import (
|
|
11
|
+
KEEPALIVE_PING_HEADER,
|
|
12
|
+
KEEPALIVE_PING_INTERVAL_SEC,
|
|
13
|
+
ConnectionConfig,
|
|
14
|
+
Username,
|
|
15
|
+
)
|
|
16
|
+
from ...exceptions import InvalidArgumentException, TemplateException
|
|
17
|
+
from ...generated import api_pb2, api_pb2_connect
|
|
18
|
+
from ...generated.api import (
|
|
19
|
+
ENVD_API_DOWNLOAD_FILES_ROUTE,
|
|
20
|
+
ENVD_API_UPLOAD_FILES_ROUTE,
|
|
21
|
+
handle_envd_api_exception,
|
|
22
|
+
)
|
|
23
|
+
from ...generated.rpc import authentication_header, handle_rpc_exception
|
|
24
|
+
from ...generated.versions import ENVD_VERSION_RECURSIVE_WATCH
|
|
25
|
+
from ...sandbox.filesystem.filesystem import (
|
|
26
|
+
EntryInfo,
|
|
27
|
+
FileType,
|
|
28
|
+
WriteEntry,
|
|
29
|
+
WriteInfo,
|
|
30
|
+
map_file_type,
|
|
31
|
+
)
|
|
32
|
+
from ...sandbox_sync.filesystem.watch_handle import WatchHandle
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class Filesystem:
|
|
36
|
+
"""
|
|
37
|
+
Module for interacting with the filesystem in the sandbox.
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
def __init__(
|
|
41
|
+
self,
|
|
42
|
+
envd_api_url: str,
|
|
43
|
+
envd_version: Optional[str],
|
|
44
|
+
connection_config: ConnectionConfig,
|
|
45
|
+
pool: urllib3.PoolManager,
|
|
46
|
+
envd_api: httpx.Client,
|
|
47
|
+
) -> None:
|
|
48
|
+
self._envd_api_url = envd_api_url
|
|
49
|
+
self._envd_version = envd_version
|
|
50
|
+
self._connection_config = connection_config
|
|
51
|
+
self._pool = pool
|
|
52
|
+
self._envd_api = envd_api
|
|
53
|
+
|
|
54
|
+
self._rpc = api_pb2_connect.FilesystemClient(
|
|
55
|
+
envd_api_url,
|
|
56
|
+
http_client=pool
|
|
57
|
+
)
|
|
58
|
+
self._headers = self._connection_config.headers
|
|
59
|
+
|
|
60
|
+
@overload
|
|
61
|
+
def read(
|
|
62
|
+
self,
|
|
63
|
+
path: str,
|
|
64
|
+
format: Literal["text"] = "text",
|
|
65
|
+
user: Username = "user",
|
|
66
|
+
request_timeout: Optional[float] = None,
|
|
67
|
+
) -> str:
|
|
68
|
+
"""
|
|
69
|
+
Read file content as a `str`.
|
|
70
|
+
|
|
71
|
+
:param path: Path to the file
|
|
72
|
+
:param user: Run the operation as this user
|
|
73
|
+
:param format: Format of the file content—`text` by default
|
|
74
|
+
:param request_timeout: Timeout for the request in **seconds**
|
|
75
|
+
|
|
76
|
+
:return: File content as a `str`
|
|
77
|
+
"""
|
|
78
|
+
...
|
|
79
|
+
|
|
80
|
+
@overload
|
|
81
|
+
def read(
|
|
82
|
+
self,
|
|
83
|
+
path: str,
|
|
84
|
+
format: Literal["bytes"],
|
|
85
|
+
user: Username = "user",
|
|
86
|
+
request_timeout: Optional[float] = None,
|
|
87
|
+
) -> bytearray:
|
|
88
|
+
"""
|
|
89
|
+
Read file content as a `bytearray`.
|
|
90
|
+
|
|
91
|
+
:param path: Path to the file
|
|
92
|
+
:param user: Run the operation as this user
|
|
93
|
+
:param format: Format of the file content—`bytes`
|
|
94
|
+
:param request_timeout: Timeout for the request in **seconds**
|
|
95
|
+
|
|
96
|
+
:return: File content as a `bytearray`
|
|
97
|
+
"""
|
|
98
|
+
...
|
|
99
|
+
|
|
100
|
+
@overload
|
|
101
|
+
def read(
|
|
102
|
+
self,
|
|
103
|
+
path: str,
|
|
104
|
+
format: Literal["stream"],
|
|
105
|
+
user: Username = "user",
|
|
106
|
+
request_timeout: Optional[float] = None,
|
|
107
|
+
) -> Iterator[bytes]:
|
|
108
|
+
"""
|
|
109
|
+
Read file content as a `Iterator[bytes]`.
|
|
110
|
+
|
|
111
|
+
:param path: Path to the file
|
|
112
|
+
:param user: Run the operation as this user
|
|
113
|
+
:param format: Format of the file content—`stream`
|
|
114
|
+
:param request_timeout: Timeout for the request in **seconds**
|
|
115
|
+
|
|
116
|
+
:return: File content as an `Iterator[bytes]`
|
|
117
|
+
"""
|
|
118
|
+
...
|
|
119
|
+
|
|
120
|
+
def read(
|
|
121
|
+
self,
|
|
122
|
+
path: str,
|
|
123
|
+
format: Literal["text", "bytes", "stream"] = "text",
|
|
124
|
+
user: Username = "user",
|
|
125
|
+
request_timeout: Optional[float] = None,
|
|
126
|
+
):
|
|
127
|
+
# Use the /download/ endpoint from sandboxagent.go
|
|
128
|
+
download_url = f"/download/{path.lstrip('/')}"
|
|
129
|
+
|
|
130
|
+
r = self._envd_api.get(
|
|
131
|
+
download_url,
|
|
132
|
+
timeout=self._connection_config.get_request_timeout(request_timeout),
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
err = handle_envd_api_exception(r)
|
|
136
|
+
if err:
|
|
137
|
+
raise err
|
|
138
|
+
|
|
139
|
+
if format == "text":
|
|
140
|
+
return r.text
|
|
141
|
+
elif format == "bytes":
|
|
142
|
+
return bytearray(r.content)
|
|
143
|
+
elif format == "stream":
|
|
144
|
+
return r.iter_bytes()
|
|
145
|
+
|
|
146
|
+
@overload
|
|
147
|
+
def write(
|
|
148
|
+
self,
|
|
149
|
+
path: str,
|
|
150
|
+
data: Union[str, bytes, IO],
|
|
151
|
+
user: Username = "user",
|
|
152
|
+
request_timeout: Optional[float] = None,
|
|
153
|
+
) -> WriteInfo:
|
|
154
|
+
"""
|
|
155
|
+
Write content to a file on the path.
|
|
156
|
+
|
|
157
|
+
Writing to a file that doesn't exist creates the file.
|
|
158
|
+
|
|
159
|
+
Writing to a file that already exists overwrites the file.
|
|
160
|
+
|
|
161
|
+
Writing to a file at path that doesn't exist creates the necessary directories.
|
|
162
|
+
|
|
163
|
+
:param path: Path to the file
|
|
164
|
+
:param data: Data to write to the file, can be a `str`, `bytes`, or `IO`.
|
|
165
|
+
:param user: Run the operation as this user
|
|
166
|
+
:param request_timeout: Timeout for the request in **seconds**
|
|
167
|
+
|
|
168
|
+
:return: Information about the written file
|
|
169
|
+
"""
|
|
170
|
+
|
|
171
|
+
@overload
|
|
172
|
+
def write(
|
|
173
|
+
self,
|
|
174
|
+
files: List[WriteEntry],
|
|
175
|
+
user: Optional[Username] = "user",
|
|
176
|
+
request_timeout: Optional[float] = None,
|
|
177
|
+
) -> List[WriteInfo]:
|
|
178
|
+
"""
|
|
179
|
+
Writes a list of files to the filesystem.
|
|
180
|
+
When writing to a file that doesn't exist, the file will get created.
|
|
181
|
+
When writing to a file that already exists, the file will get overwritten.
|
|
182
|
+
When writing to a file that's in a directory that doesn't exist, you'll get an error.
|
|
183
|
+
|
|
184
|
+
:param files: list of files to write
|
|
185
|
+
:param user: Run the operation as this user
|
|
186
|
+
:param request_timeout: Timeout for the request
|
|
187
|
+
:return: Information about the written files
|
|
188
|
+
"""
|
|
189
|
+
|
|
190
|
+
def write(
|
|
191
|
+
self,
|
|
192
|
+
path_or_files: Union[str, List[WriteEntry]],
|
|
193
|
+
data_or_user: Union[str, bytes, IO, Username] = "user",
|
|
194
|
+
user_or_request_timeout: Optional[Union[float, Username]] = None,
|
|
195
|
+
request_timeout_or_none: Optional[float] = None,
|
|
196
|
+
) -> Union[WriteInfo, List[WriteInfo]]:
|
|
197
|
+
path, write_files, user, request_timeout = None, [], "user", None
|
|
198
|
+
if isinstance(path_or_files, str):
|
|
199
|
+
if isinstance(data_or_user, list):
|
|
200
|
+
raise Exception(
|
|
201
|
+
"Cannot specify both path and array of files. You have to specify either path and data for a single file or an array for multiple files."
|
|
202
|
+
)
|
|
203
|
+
path, write_files, user, request_timeout = (
|
|
204
|
+
path_or_files,
|
|
205
|
+
[{"path": path_or_files, "data": data_or_user}],
|
|
206
|
+
user_or_request_timeout or "user",
|
|
207
|
+
request_timeout_or_none,
|
|
208
|
+
)
|
|
209
|
+
else:
|
|
210
|
+
if path_or_files is None:
|
|
211
|
+
raise Exception("Path or files are required")
|
|
212
|
+
path, write_files, user, request_timeout = (
|
|
213
|
+
None,
|
|
214
|
+
path_or_files,
|
|
215
|
+
data_or_user,
|
|
216
|
+
user_or_request_timeout,
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
# Allow passing empty list of files
|
|
220
|
+
if len(write_files) == 0:
|
|
221
|
+
return []
|
|
222
|
+
|
|
223
|
+
# Use the /upload endpoint from sandboxagent.go
|
|
224
|
+
# This endpoint expects multipart/form-data with 'file' field and 'path' form field
|
|
225
|
+
results = []
|
|
226
|
+
for file in write_files:
|
|
227
|
+
file_path, file_data = file["path"], file["data"]
|
|
228
|
+
|
|
229
|
+
# Prepare file data
|
|
230
|
+
if isinstance(file_data, str):
|
|
231
|
+
file_content = file_data.encode('utf-8')
|
|
232
|
+
elif isinstance(file_data, bytes):
|
|
233
|
+
file_content = file_data
|
|
234
|
+
elif isinstance(file_data, IOBase):
|
|
235
|
+
file_content = file_data.read()
|
|
236
|
+
if isinstance(file_content, str):
|
|
237
|
+
file_content = file_content.encode('utf-8')
|
|
238
|
+
else:
|
|
239
|
+
raise ValueError(f"Unsupported data type for file {file_path}")
|
|
240
|
+
|
|
241
|
+
# Prepare multipart form data
|
|
242
|
+
files = [("file", (file_path, file_content))]
|
|
243
|
+
data = {"path": file_path}
|
|
244
|
+
|
|
245
|
+
r = self._envd_api.post(
|
|
246
|
+
"/upload",
|
|
247
|
+
files=files,
|
|
248
|
+
data=data,
|
|
249
|
+
timeout=self._connection_config.get_request_timeout(request_timeout),
|
|
250
|
+
)
|
|
251
|
+
|
|
252
|
+
err = handle_envd_api_exception(r)
|
|
253
|
+
if err:
|
|
254
|
+
raise err
|
|
255
|
+
|
|
256
|
+
# For now, create a mock WriteInfo since sandboxagent.go returns plain text
|
|
257
|
+
# In a real implementation, you might want to enhance the server to return JSON
|
|
258
|
+
results.append(WriteInfo(
|
|
259
|
+
path=file_path,
|
|
260
|
+
name=file_path.split('/')[-1] if '/' in file_path else file_path,
|
|
261
|
+
type=FileType.FILE,
|
|
262
|
+
))
|
|
263
|
+
|
|
264
|
+
# Return appropriate response based on input format
|
|
265
|
+
if len(results) == 1 and path:
|
|
266
|
+
return results[0]
|
|
267
|
+
else:
|
|
268
|
+
return results
|
|
269
|
+
|
|
270
|
+
def list(
|
|
271
|
+
self,
|
|
272
|
+
path: str,
|
|
273
|
+
depth: Optional[int] = 1,
|
|
274
|
+
user: Username = "user",
|
|
275
|
+
request_timeout: Optional[float] = None,
|
|
276
|
+
) -> List[EntryInfo]:
|
|
277
|
+
"""
|
|
278
|
+
List entries in a directory.
|
|
279
|
+
|
|
280
|
+
:param path: Path to the directory
|
|
281
|
+
:param depth: Depth of the directory to list
|
|
282
|
+
:param user: Run the operation as this user
|
|
283
|
+
:param request_timeout: Timeout for the request in **seconds**
|
|
284
|
+
|
|
285
|
+
:return: List of entries in the directory
|
|
286
|
+
"""
|
|
287
|
+
if depth is not None and depth < 1:
|
|
288
|
+
raise InvalidArgumentException("depth should be at least 1")
|
|
289
|
+
|
|
290
|
+
try:
|
|
291
|
+
res = self._rpc.list_dir(
|
|
292
|
+
api_pb2.ListDirRequest(path=path, depth=depth),
|
|
293
|
+
self._headers,
|
|
294
|
+
timeout_seconds=self._connection_config.get_request_timeout(
|
|
295
|
+
request_timeout
|
|
296
|
+
),
|
|
297
|
+
)
|
|
298
|
+
|
|
299
|
+
entries: List[EntryInfo] = []
|
|
300
|
+
for entry in res.entries:
|
|
301
|
+
event_type = map_file_type(entry.type)
|
|
302
|
+
|
|
303
|
+
if event_type:
|
|
304
|
+
entries.append(
|
|
305
|
+
EntryInfo(
|
|
306
|
+
name=entry.name,
|
|
307
|
+
type=event_type,
|
|
308
|
+
path=entry.path,
|
|
309
|
+
size=entry.size,
|
|
310
|
+
mode=entry.mode,
|
|
311
|
+
permissions=entry.permissions,
|
|
312
|
+
owner=entry.owner,
|
|
313
|
+
group=entry.group,
|
|
314
|
+
modified_time=entry.modified_time.ToDatetime(),
|
|
315
|
+
# Optional, we can't directly access symlink_target otherwise if will be "" instead of None
|
|
316
|
+
symlink_target=(
|
|
317
|
+
entry.symlink_target
|
|
318
|
+
if entry.HasField("symlink_target")
|
|
319
|
+
else None
|
|
320
|
+
),
|
|
321
|
+
)
|
|
322
|
+
)
|
|
323
|
+
|
|
324
|
+
return entries
|
|
325
|
+
except Exception as e:
|
|
326
|
+
raise handle_rpc_exception(e)
|
|
327
|
+
|
|
328
|
+
def exists(
|
|
329
|
+
self,
|
|
330
|
+
path: str,
|
|
331
|
+
user: Username = "user",
|
|
332
|
+
request_timeout: Optional[float] = None,
|
|
333
|
+
) -> bool:
|
|
334
|
+
"""
|
|
335
|
+
Check if a file or a directory exists.
|
|
336
|
+
|
|
337
|
+
:param path: Path to a file or a directory
|
|
338
|
+
:param user: Run the operation as this user
|
|
339
|
+
:param request_timeout: Timeout for the request in **seconds**
|
|
340
|
+
|
|
341
|
+
:return: `True` if the file or directory exists, `False` otherwise
|
|
342
|
+
"""
|
|
343
|
+
try:
|
|
344
|
+
self._rpc.stat(
|
|
345
|
+
api_pb2.StatRequest(path=path),
|
|
346
|
+
self._headers,
|
|
347
|
+
timeout_seconds=self._connection_config.get_request_timeout(
|
|
348
|
+
request_timeout
|
|
349
|
+
),
|
|
350
|
+
)
|
|
351
|
+
return True
|
|
352
|
+
|
|
353
|
+
except Exception as e:
|
|
354
|
+
if "no such file or directory" in str(e):
|
|
355
|
+
return False
|
|
356
|
+
raise handle_rpc_exception(e)
|
|
357
|
+
|
|
358
|
+
def get_info(
|
|
359
|
+
self,
|
|
360
|
+
path: str,
|
|
361
|
+
user: Username = "user",
|
|
362
|
+
request_timeout: Optional[float] = None,
|
|
363
|
+
) -> EntryInfo:
|
|
364
|
+
"""
|
|
365
|
+
Get information about a file or directory.
|
|
366
|
+
|
|
367
|
+
:param path: Path to a file or a directory
|
|
368
|
+
:param user: Run the operation as this user
|
|
369
|
+
:param request_timeout: Timeout for the request in **seconds**
|
|
370
|
+
|
|
371
|
+
:return: Information about the file or directory like name, type, and path
|
|
372
|
+
"""
|
|
373
|
+
try:
|
|
374
|
+
r = self._rpc.stat(
|
|
375
|
+
api_pb2.StatRequest(path=path),
|
|
376
|
+
self._headers,
|
|
377
|
+
timeout_seconds=self._connection_config.get_request_timeout(
|
|
378
|
+
request_timeout
|
|
379
|
+
),
|
|
380
|
+
)
|
|
381
|
+
|
|
382
|
+
return EntryInfo(
|
|
383
|
+
name=r.entry.name,
|
|
384
|
+
type=map_file_type(r.entry.type),
|
|
385
|
+
path=r.entry.path,
|
|
386
|
+
size=r.entry.size,
|
|
387
|
+
mode=r.entry.mode,
|
|
388
|
+
permissions=r.entry.permissions,
|
|
389
|
+
owner=r.entry.owner,
|
|
390
|
+
group=r.entry.group,
|
|
391
|
+
modified_time=r.entry.modified_time.ToDatetime(),
|
|
392
|
+
# Optional, we can't directly access symlink_target otherwise if will be "" instead of None
|
|
393
|
+
symlink_target=(
|
|
394
|
+
r.entry.symlink_target
|
|
395
|
+
if r.entry.HasField("symlink_target")
|
|
396
|
+
else None
|
|
397
|
+
),
|
|
398
|
+
)
|
|
399
|
+
except Exception as e:
|
|
400
|
+
raise handle_rpc_exception(e)
|
|
401
|
+
|
|
402
|
+
def remove(
|
|
403
|
+
self,
|
|
404
|
+
path: str,
|
|
405
|
+
user: Username = "user",
|
|
406
|
+
request_timeout: Optional[float] = None,
|
|
407
|
+
) -> None:
|
|
408
|
+
"""
|
|
409
|
+
Remove a file or a directory.
|
|
410
|
+
|
|
411
|
+
:param path: Path to a file or a directory
|
|
412
|
+
:param user: Run the operation as this user
|
|
413
|
+
:param request_timeout: Timeout for the request in **seconds**
|
|
414
|
+
"""
|
|
415
|
+
try:
|
|
416
|
+
self._rpc.remove(
|
|
417
|
+
api_pb2.RemoveRequest(path=path),
|
|
418
|
+
self._headers,
|
|
419
|
+
timeout_seconds=self._connection_config.get_request_timeout(
|
|
420
|
+
request_timeout
|
|
421
|
+
),
|
|
422
|
+
)
|
|
423
|
+
except Exception as e:
|
|
424
|
+
raise handle_rpc_exception(e)
|
|
425
|
+
|
|
426
|
+
def rename(
|
|
427
|
+
self,
|
|
428
|
+
old_path: str,
|
|
429
|
+
new_path: str,
|
|
430
|
+
user: Username = "user",
|
|
431
|
+
request_timeout: Optional[float] = None,
|
|
432
|
+
) -> EntryInfo:
|
|
433
|
+
"""
|
|
434
|
+
Rename a file or directory.
|
|
435
|
+
|
|
436
|
+
:param old_path: Path to the file or directory to rename
|
|
437
|
+
:param new_path: New path to the file or directory
|
|
438
|
+
:param user: Run the operation as this user
|
|
439
|
+
:param request_timeout: Timeout for the request in **seconds**
|
|
440
|
+
|
|
441
|
+
:return: Information about the renamed file or directory
|
|
442
|
+
"""
|
|
443
|
+
try:
|
|
444
|
+
r = self._rpc.move(
|
|
445
|
+
api_pb2.MoveRequest(
|
|
446
|
+
source=old_path,
|
|
447
|
+
destination=new_path,
|
|
448
|
+
),
|
|
449
|
+
self._headers,
|
|
450
|
+
timeout_seconds=self._connection_config.get_request_timeout(
|
|
451
|
+
request_timeout
|
|
452
|
+
),
|
|
453
|
+
)
|
|
454
|
+
|
|
455
|
+
return EntryInfo(
|
|
456
|
+
name=r.entry.name,
|
|
457
|
+
type=map_file_type(r.entry.type),
|
|
458
|
+
path=r.entry.path,
|
|
459
|
+
size=r.entry.size,
|
|
460
|
+
mode=r.entry.mode,
|
|
461
|
+
permissions=r.entry.permissions,
|
|
462
|
+
owner=r.entry.owner,
|
|
463
|
+
group=r.entry.group,
|
|
464
|
+
modified_time=r.entry.modified_time.ToDatetime(),
|
|
465
|
+
# Optional, we can't directly access symlink_target otherwise if will be "" instead of None
|
|
466
|
+
symlink_target=(
|
|
467
|
+
r.entry.symlink_target
|
|
468
|
+
if r.entry.HasField("symlink_target")
|
|
469
|
+
else None
|
|
470
|
+
),
|
|
471
|
+
)
|
|
472
|
+
except Exception as e:
|
|
473
|
+
raise handle_rpc_exception(e)
|
|
474
|
+
|
|
475
|
+
def make_dir(
|
|
476
|
+
self,
|
|
477
|
+
path: str,
|
|
478
|
+
user: Username = "user",
|
|
479
|
+
request_timeout: Optional[float] = None,
|
|
480
|
+
) -> bool:
|
|
481
|
+
"""
|
|
482
|
+
Create a new directory and all directories along the way if needed on the specified path.
|
|
483
|
+
|
|
484
|
+
:param path: Path to a new directory. For example '/dirA/dirB' when creating 'dirB'.
|
|
485
|
+
:param user: Run the operation as this user
|
|
486
|
+
:param request_timeout: Timeout for the request in **seconds**
|
|
487
|
+
|
|
488
|
+
:return: `True` if the directory was created, `False` if the directory already exists
|
|
489
|
+
"""
|
|
490
|
+
try:
|
|
491
|
+
self._rpc.make_dir(
|
|
492
|
+
api_pb2.MakeDirRequest(path=path),
|
|
493
|
+
self._headers,
|
|
494
|
+
timeout_seconds=self._connection_config.get_request_timeout(
|
|
495
|
+
request_timeout
|
|
496
|
+
),
|
|
497
|
+
)
|
|
498
|
+
|
|
499
|
+
return True
|
|
500
|
+
except Exception as e:
|
|
501
|
+
if "directory already exists" in str(e):
|
|
502
|
+
return False
|
|
503
|
+
raise handle_rpc_exception(e)
|
|
504
|
+
|
|
505
|
+
def watch_dir(
|
|
506
|
+
self,
|
|
507
|
+
path: str,
|
|
508
|
+
user: Username = "user",
|
|
509
|
+
request_timeout: Optional[float] = None,
|
|
510
|
+
recursive: bool = False,
|
|
511
|
+
) -> WatchHandle:
|
|
512
|
+
"""
|
|
513
|
+
Watch directory for filesystem events.
|
|
514
|
+
|
|
515
|
+
:param path: Path to a directory to watch
|
|
516
|
+
:param user: Run the operation as this user
|
|
517
|
+
:param request_timeout: Timeout for the request in **seconds**
|
|
518
|
+
:param recursive: Watch directory recursively
|
|
519
|
+
|
|
520
|
+
:return: `WatchHandle` object for stopping watching directory
|
|
521
|
+
"""
|
|
522
|
+
if (
|
|
523
|
+
recursive
|
|
524
|
+
and self._envd_version is not None
|
|
525
|
+
and Version(self._envd_version) < ENVD_VERSION_RECURSIVE_WATCH
|
|
526
|
+
):
|
|
527
|
+
raise TemplateException(
|
|
528
|
+
"You need to update the template to use recursive watching. "
|
|
529
|
+
"You can do this by running `scalebox template build` in the directory with the template."
|
|
530
|
+
)
|
|
531
|
+
|
|
532
|
+
try:
|
|
533
|
+
r = self._rpc.create_watcher(
|
|
534
|
+
api_pb2.CreateWatcherRequest(path=path, recursive=recursive),
|
|
535
|
+
self._headers,
|
|
536
|
+
timeout_seconds=self._connection_config.get_request_timeout(
|
|
537
|
+
request_timeout
|
|
538
|
+
),
|
|
539
|
+
)
|
|
540
|
+
except Exception as e:
|
|
541
|
+
raise handle_rpc_exception(e)
|
|
542
|
+
|
|
543
|
+
return WatchHandle(self._rpc, r.watcher_id)
|
scalebox/version.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: scalebox-sdk
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.24
|
|
4
4
|
Summary: ScaleBox Python SDK - A multi-language code execution sandbox with Python, R, Node.js, Deno/TypeScript, Java, and Bash support
|
|
5
5
|
Author-email: ScaleBox Team <dev@scalebox.dev>
|
|
6
6
|
Maintainer-email: ScaleBox Team <dev@scalebox.dev>
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
scalebox/__init__.py,sha256=
|
|
1
|
+
scalebox/__init__.py,sha256=mJ4xOcm_wLKtYS81Ir74xYu_wn40S_jwkdL94sULmm8,1812
|
|
2
2
|
scalebox/cli.py,sha256=HWIyGuhbP1WZm839CwTysauL78xMBOoatFychxzloxQ,3904
|
|
3
3
|
scalebox/connection_config.py,sha256=W2hMeXlxUHvR51guqOWEEDv35o4XP8SbLkDXqa20C0M,2655
|
|
4
4
|
scalebox/exceptions.py,sha256=10R9VXfvgO4XJJnxyzyrzkxliyeEBX0ZC36izXa8R5k,2053
|
|
5
5
|
scalebox/requirements.txt,sha256=LEYsk2VzoxKR-V44Y6qJuJ3vKdTYS79f1Gv1Ajleifo,567
|
|
6
|
-
scalebox/version.py,sha256=
|
|
6
|
+
scalebox/version.py,sha256=zPiqwFEMt--CAZTeF-4OEP4BpRKQzKJ3uTPWkbziA9Y,323
|
|
7
7
|
scalebox/api/__init__.py,sha256=_9nWyeNg4Y_Z30YpBNoDP6S92YdlO_5xBkrp-we0SIg,4167
|
|
8
8
|
scalebox/api/metadata.py,sha256=lg5ekfnFZYZoCoJxIPo961HEGVg_rLLRJBbw4ZApM_Y,512
|
|
9
9
|
scalebox/api/client/__init__.py,sha256=IVRaxvQcdPu1Xxc3t--g3ir3Wl5f3Y0zKMwy1nkKN80,155
|
|
@@ -34,7 +34,7 @@ scalebox/api/client/models/identifier_masking_details.py,sha256=97xSg4Nr3D5xbjGD
|
|
|
34
34
|
scalebox/api/client/models/listed_sandbox.py,sha256=oQUitQWjCk1HOXX7K7YdaGWBLQnPu4GnMt2E9-rQOTw,4040
|
|
35
35
|
scalebox/api/client/models/log_level.py,sha256=uv6u1nkqfzdxTjJAxLZdWuH5goVI_g-ULLhh6TU5dw8,189
|
|
36
36
|
scalebox/api/client/models/new_access_token.py,sha256=sjlQ7dEj4ByCgwud750SbxwyjggAKB1s-o_N7dRCo7s,1480
|
|
37
|
-
scalebox/api/client/models/new_sandbox.py,sha256
|
|
37
|
+
scalebox/api/client/models/new_sandbox.py,sha256=-mnu5wZPkofTGqsbDmA1nWTFDw-fzPu2Mg_rVISxd70,3937
|
|
38
38
|
scalebox/api/client/models/new_team_api_key.py,sha256=neYLvvvzyPcpXYV5gDHVkv_ovjKdIBKUTX04cD5QKGA,1473
|
|
39
39
|
scalebox/api/client/models/node.py,sha256=nuEZij7z7iXzBQYTFwardHlSixWrepztFmMIIqd8Ek4,4653
|
|
40
40
|
scalebox/api/client/models/node_detail.py,sha256=ywP1oDzyxi7hIJAMTvINQQ8CwMsd1PlH1R5bEw716Ig,4511
|
|
@@ -115,7 +115,7 @@ scalebox/sandbox_sync/commands/command.py,sha256=308RgsexBQaLVbUc2ui6oKOLh6xUFDM
|
|
|
115
115
|
scalebox/sandbox_sync/commands/command_handle.py,sha256=XPN1SpW00NqHxTuyceh4MLik-YQPEKfu4FOtf8pIC7Y,4615
|
|
116
116
|
scalebox/sandbox_sync/commands/pty.py,sha256=KQStIe-ts_sSWjE_zD01AMqMCGbq9wGeXzbN-i4js9Q,5629
|
|
117
117
|
scalebox/sandbox_sync/filesystem/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
118
|
-
scalebox/sandbox_sync/filesystem/filesystem.py,sha256=
|
|
118
|
+
scalebox/sandbox_sync/filesystem/filesystem.py,sha256=MqjXNWrUw2_JaFh2UrMkfZsHdKtyJwLU5dPux1EH8Zw,18741
|
|
119
119
|
scalebox/sandbox_sync/filesystem/watch_handle.py,sha256=dC9p74AHMv-3mQ1tudmOPjrpjvcJ7wFmCduNdity6os,2027
|
|
120
120
|
scalebox/test/CODE_INTERPRETER_TESTS_READY.md,sha256=gtS9jRp2VB44MVG-ztb1JgYiqWtea8Z-TMEK1mZzfgE,11365
|
|
121
121
|
scalebox/test/README.md,sha256=1rpgOo8uv-zT6QNw05MCCl8XzplD6xvWl3gE7frn9xI,9129
|
|
@@ -148,9 +148,9 @@ scalebox/test/testsandbox_sync.py,sha256=v1dFAJWKbyLnjIiafTR9TafxJF1gaUk-W2mmQU4
|
|
|
148
148
|
scalebox/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
149
149
|
scalebox/utils/httpcoreclient.py,sha256=kjTndd-YECPe3n_G1HfGgitzRwntC21tqtIqZ62V6Lg,9868
|
|
150
150
|
scalebox/utils/httpxclient.py,sha256=oLpCP2RChvnspS6Unl6ngmpY72yPokTfSqMm9m-7k38,13442
|
|
151
|
-
scalebox_sdk-0.1.
|
|
152
|
-
scalebox_sdk-0.1.
|
|
153
|
-
scalebox_sdk-0.1.
|
|
154
|
-
scalebox_sdk-0.1.
|
|
155
|
-
scalebox_sdk-0.1.
|
|
156
|
-
scalebox_sdk-0.1.
|
|
151
|
+
scalebox_sdk-0.1.24.dist-info/licenses/LICENSE,sha256=9zP32kHlBovkfji1R6ptx3H7WjJJvnf4UuwTpfogmsY,1069
|
|
152
|
+
scalebox_sdk-0.1.24.dist-info/METADATA,sha256=2w0Hw5R8QRgwCrs7lVqkP8JO2oFjMZthXkOeyIJ10YY,12736
|
|
153
|
+
scalebox_sdk-0.1.24.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
154
|
+
scalebox_sdk-0.1.24.dist-info/entry_points.txt,sha256=g7C1Trcg8EyvAGMnHpJ3alqtZzQuMypYUQVFK13kOFM,47
|
|
155
|
+
scalebox_sdk-0.1.24.dist-info/top_level.txt,sha256=CDjlibkbOG-MT-s1TRxs4Xe_iN1m11ii48spB6DOMj4,9
|
|
156
|
+
scalebox_sdk-0.1.24.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|