Skip to content

Commit 6adb142

Browse files
fix: FsFile handle methods + add tests (#24)
1 parent 18cc240 commit 6adb142

File tree

5 files changed

+256
-60
lines changed

5 files changed

+256
-60
lines changed

src/deno_sandbox/api_generated.py

Lines changed: 0 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@
2929
ExpandGlobOptions,
3030
MakeTempDirOptions,
3131
MakeTempFileOptions,
32-
FsOpenOptions,
3332
SymlinkOptions,
3433
)
3534

@@ -41,10 +40,6 @@
4140
AsyncPaginatedList,
4241
PaginatedList,
4342
)
44-
from .wrappers import (
45-
FsFile,
46-
AsyncFsFile,
47-
)
4843

4944

5045
class Apps:
@@ -471,14 +466,6 @@ def expand_glob(
471466

472467
return result
473468

474-
def create(self, path: str) -> FsFile:
475-
"""Create a new empty file at the specified path."""
476-
477-
params = {"path": path}
478-
result = self._rpc.call("create", params)
479-
480-
return result
481-
482469
def link(self, target: str, path: str) -> None:
483470
"""Create a hard link pointing to an existing file."""
484471

@@ -516,17 +503,6 @@ def make_temp_file(self, options: Optional[MakeTempFileOptions] = None) -> str:
516503

517504
return result
518505

519-
def open(self, path: str, options: Optional[FsOpenOptions] = None) -> FsFile:
520-
"""Open a file and return a file descriptor."""
521-
522-
params = {"path": path}
523-
if options is not None:
524-
params["options"] = convert_to_camel_case(options)
525-
526-
result = self._rpc.call("open", params)
527-
528-
return result
529-
530506
def read_link(self, path: str) -> str:
531507
"""Read the target of a symbolic link."""
532508

@@ -727,14 +703,6 @@ async def expand_glob(
727703

728704
return result
729705

730-
async def create(self, path: str) -> AsyncFsFile:
731-
"""Create a new empty file at the specified path."""
732-
733-
params = {"path": path}
734-
result = await self._rpc.call("create", params)
735-
736-
return result
737-
738706
async def link(self, target: str, path: str) -> None:
739707
"""Create a hard link pointing to an existing file."""
740708

@@ -774,19 +742,6 @@ async def make_temp_file(
774742

775743
return result
776744

777-
async def open(
778-
self, path: str, options: Optional[FsOpenOptions] = None
779-
) -> AsyncFsFile:
780-
"""Open a file and return a file descriptor."""
781-
782-
params = {"path": path}
783-
if options is not None:
784-
params["options"] = convert_to_camel_case(options)
785-
786-
result = await self._rpc.call("open", params)
787-
788-
return result
789-
790745
async def read_link(self, path: str) -> str:
791746
"""Read the target of a symbolic link."""
792747

src/deno_sandbox/api_types_generated.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -617,6 +617,10 @@ class ExpandGlobOptions(TypedDict):
617617
"""Whether the glob matching should be case insensitive. Default: false."""
618618

619619

620+
class FsFileHandle(TypedDict):
621+
file_handle_id: int
622+
623+
620624
class MakeTempDirOptions(TypedDict):
621625
dir: NotRequired[str | None]
622626
prefix: NotRequired[str | None]

src/deno_sandbox/sandbox.py

Lines changed: 72 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,15 @@
1414

1515
from .api_generated import (
1616
AsyncSandboxEnv,
17-
AsyncSandboxFs,
17+
AsyncSandboxFs as AsyncSandboxFsGenerated,
1818
SandboxEnv,
19-
SandboxFs,
19+
SandboxFs as SandboxFsGenerated,
2020
)
2121
from .api_types_generated import (
2222
DenoReplOptions,
2323
DenoRunOptions,
24+
FsFileHandle,
25+
FsOpenOptions,
2426
SandboxListOptions,
2527
SandboxCreateOptions,
2628
SandboxConnectOptions,
@@ -39,16 +41,23 @@
3941
from .transport import (
4042
WebSocketTransport,
4143
)
42-
from .utils import to_camel_case, to_snake_case
44+
from .utils import (
45+
convert_to_camel_case,
46+
convert_to_snake_case,
47+
to_camel_case,
48+
to_snake_case,
49+
)
4350
from .wrappers import (
4451
AsyncChildProcess,
4552
AsyncDenoProcess,
4653
AsyncDenoRepl,
4754
AsyncFetchResponse,
55+
AsyncFsFile,
4856
ChildProcess,
4957
DenoProcess,
5058
DenoRepl,
5159
FetchResponse,
60+
FsFile,
5261
ProcessSpawnResult,
5362
RemoteProcessOptions,
5463
)
@@ -562,6 +571,66 @@ def __exit__(self, exc_type, exc_val, exc_tb):
562571
self._client._bridge.run(self._async.__aexit__(exc_type, exc_val, exc_tb))
563572

564573

574+
class AsyncSandboxFs(AsyncSandboxFsGenerated):
575+
"""Filesystem operations inside the sandbox."""
576+
577+
async def create(self, path: str) -> AsyncFsFile:
578+
"""Create a new, empty file at the specified path."""
579+
580+
params = {"path": path}
581+
result = await self._rpc.call("create", params)
582+
583+
raw_result = convert_to_snake_case(result)
584+
handle = cast(FsFileHandle, raw_result)
585+
586+
return AsyncFsFile(self._rpc, handle["file_handle_id"])
587+
588+
async def open(
589+
self, path: str, options: Optional[FsOpenOptions] = None
590+
) -> AsyncFsFile:
591+
"""Open a file and return a file descriptor."""
592+
593+
params = {"path": path}
594+
if options is not None:
595+
params["options"] = convert_to_camel_case(options)
596+
597+
result = await self._rpc.call("open", params)
598+
599+
raw_result = convert_to_snake_case(result)
600+
handle = cast(FsFileHandle, raw_result)
601+
602+
return AsyncFsFile(self._rpc, handle["file_handle_id"])
603+
604+
605+
class SandboxFs(SandboxFsGenerated):
606+
"""Filesystem operations inside the sandbox."""
607+
608+
def create(self, path: str) -> FsFile:
609+
"""Create a new, empty file at the specified path."""
610+
611+
params = {"path": path}
612+
result = self._rpc.call("create", params)
613+
614+
raw_result = convert_to_snake_case(result)
615+
handle = cast(FsFileHandle, raw_result)
616+
617+
return FsFile(self._rpc, handle["file_handle_id"])
618+
619+
def open(self, path: str, options: Optional[FsOpenOptions] = None) -> FsFile:
620+
"""Open a file and return a file descriptor."""
621+
622+
params = {"path": path}
623+
if options is not None:
624+
params["options"] = convert_to_camel_case(options)
625+
626+
result = self._rpc.call("open", params)
627+
628+
raw_result = convert_to_snake_case(result)
629+
handle = cast(FsFileHandle, raw_result)
630+
631+
return FsFile(self._rpc, handle["file_handle_id"])
632+
633+
565634
class AsyncVsCode:
566635
"""Experimental! A VSCode instance running inside the sandbox."""
567636

src/deno_sandbox/wrappers.py

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import asyncio
2+
import base64
23
import sys
34
from typing import Any, BinaryIO, Callable, Optional, TypedDict, cast
45
from typing_extensions import Literal
@@ -49,9 +50,10 @@ async def write(self, data: bytes) -> int:
4950
"""Write data to the file. Returns number of bytes written."""
5051

5152
result = await self._rpc.call(
52-
"fileWrite", {"data": data, "fileHandleId": self._fd}
53+
"fileWrite",
54+
{"data": base64.b64encode(data).decode("ascii"), "fileHandleId": self._fd},
5355
)
54-
return result["bytesWritten"]
56+
return result["bytes_written"]
5557

5658
async def truncate(self, size: Optional[int]) -> None:
5759
"""Truncate the file to the given size. If size is None, truncate to 0."""
@@ -64,7 +66,7 @@ async def read(self, size: int) -> bytes:
6466
result = await self._rpc.call(
6567
"fileRead", {"length": size, "fileHandleId": self._fd}
6668
)
67-
return result
69+
return base64.b64decode(result["data"])
6870

6971
async def seek(self, offset: int, whence: int) -> int:
7072
"""Seek to a position in the file. Returns the new position."""
@@ -80,12 +82,12 @@ async def stat(self) -> FileInfo:
8082
result = await self._rpc.call("fileStat", {"fileHandleId": self._fd})
8183
return cast(FileInfo, result)
8284

83-
async def flush(self) -> None:
84-
"""Flush the file's internal buffer."""
85+
async def sync(self) -> None:
86+
"""Flushes any pending data and metadata operations of the given file stream to disk."""
8587

86-
await self._rpc.call("fileFlush", {"fileHandleId": self._fd})
88+
await self._rpc.call("fileSync", {"fileHandleId": self._fd})
8789

88-
async def syncData(self) -> None:
90+
async def sync_data(self) -> None:
8991
"""Sync the file's data to disk."""
9092

9193
await self._rpc.call("fileSyncData", {"fileHandleId": self._fd})
@@ -152,15 +154,15 @@ def stat(self) -> FileInfo:
152154

153155
return self._rpc._bridge.run(self._async.stat())
154156

155-
def flush(self) -> None:
156-
"""Flush the file's internal buffer."""
157+
def sync(self) -> None:
158+
"""Flushes any pending data and metadata operations of the given file stream to disk."""
157159

158-
return self._rpc._bridge.run(self._async.flush())
160+
return self._rpc._bridge.run(self._async.sync())
159161

160-
def syncData(self) -> None:
162+
def sync_data(self) -> None:
161163
"""Sync the file's data to disk."""
162164

163-
return self._rpc._bridge.run(self._async.syncData())
165+
return self._rpc._bridge.run(self._async.sync_data())
164166

165167
def utime(self, atime: int, mtime: int) -> None:
166168
"""Update the file's access and modification times."""

0 commit comments

Comments
 (0)