Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 18 additions & 10 deletions hyperbrowser/client/managers/async_manager/sandbox.py
Original file line number Diff line number Diff line change
Expand Up @@ -217,16 +217,24 @@ async def create_runtime_session(
)
return _copy_model(self._runtime_session)

async def exec(self, input: Union[str, SandboxExecParams]):
if isinstance(input, str):
params = SandboxExecParams(command=input)
else:
if not isinstance(input, SandboxExecParams):
raise TypeError(
"input must be a command string or SandboxExecParams instance"
)
params = input
return await self.processes.exec(params)
async def exec(
self,
input: Union[str, SandboxExecParams],
*,
cwd: Optional[str] = None,
env: Optional[Dict[str, str]] = None,
timeout_ms: Optional[int] = None,
timeout_sec: Optional[int] = None,
run_as: Optional[str] = None,
):
return await self.processes.exec(
input,
cwd=cwd,
env=env,
timeout_ms=timeout_ms,
timeout_sec=timeout_sec,
run_as=run_as,
)

async def get_process(self, process_id: str) -> SandboxProcessHandle:
return await self.processes.get(process_id)
Expand Down
163 changes: 105 additions & 58 deletions hyperbrowser/client/managers/async_manager/sandboxes/sandbox_files.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import json
import socket
from datetime import datetime
from typing import AsyncIterator, Callable, List, Optional, Union
from typing import Any, AsyncIterator, Callable, Dict, List, Optional, Union
from urllib.parse import urlencode

from websockets.asyncio.client import connect as async_ws_connect
Expand Down Expand Up @@ -249,10 +249,21 @@ def __init__(
transport: RuntimeTransport,
get_connection_info,
runtime_proxy_override: Optional[str] = None,
default_run_as: Optional[str] = None,
):
self._transport = transport
self._get_connection_info = get_connection_info
self._runtime_proxy_override = runtime_proxy_override
self._default_run_as = default_run_as.strip() if default_run_as else None

def with_run_as(self, run_as: Optional[str]):
normalized = run_as.strip() if run_as else None
return SandboxFilesApi(
self._transport,
self._get_connection_info,
self._runtime_proxy_override,
default_run_as=normalized,
)

async def list(
self,
Expand All @@ -266,17 +277,19 @@ async def list(

payload = await self._transport.request_json(
"/sandbox/files",
params={
"path": path,
"depth": depth,
},
params=self._with_run_as_params(
{
"path": path,
"depth": depth,
}
),
)
return [_normalize_file_info(entry) for entry in payload.get("entries", [])]

async def get_info(self, path: str) -> SandboxFileInfo:
payload = await self._transport.request_json(
"/sandbox/files/stat",
params={"path": path},
params=self._with_run_as_params({"path": path}),
)
return _normalize_file_info(payload["file"])

Expand Down Expand Up @@ -357,10 +370,12 @@ async def write(
payload = await self._transport.request_json(
"/sandbox/files/write",
method="POST",
json_body={
"path": path_or_files,
**_encode_write_data(data),
},
json_body=self._with_run_as_body(
{
"path": path_or_files,
**_encode_write_data(data),
}
),
headers={"content-type": "application/json"},
)
return _normalize_write_info(payload["files"][0])
Expand All @@ -377,7 +392,7 @@ async def write(
payload = await self._transport.request_json(
"/sandbox/files/write",
method="POST",
json_body={"files": encoded_files},
json_body=self._with_run_as_body({"files": encoded_files}),
headers={"content-type": "application/json"},
)
return [_normalize_write_info(entry) for entry in payload.get("files", [])]
Expand Down Expand Up @@ -419,15 +434,15 @@ async def upload(self, path: str, data: Union[str, bytes, bytearray]):
payload = await self._transport.request_json(
"/sandbox/files/upload",
method="PUT",
params={"path": path},
params=self._with_run_as_params({"path": path}),
content=body,
)
return SandboxFileTransferResult(**payload)

async def download(self, path: str) -> bytes:
return await self._transport.request_bytes(
"/sandbox/files/download",
params={"path": path},
params=self._with_run_as_params({"path": path}),
)

async def make_dir(
Expand All @@ -440,11 +455,13 @@ async def make_dir(
payload = await self._transport.request_json(
"/sandbox/files/mkdir",
method="POST",
json_body={
"path": path,
"parents": parents,
"mode": mode,
},
json_body=self._with_run_as_body(
{
"path": path,
"parents": parents,
"mode": mode,
}
),
headers={"content-type": "application/json"},
)
return bool(payload.get("created"))
Expand Down Expand Up @@ -475,7 +492,7 @@ async def rename(
payload = await self._transport.request_json(
"/sandbox/files/move",
method="POST",
json_body=payload,
json_body=self._with_run_as_body(payload),
headers={"content-type": "application/json"},
)
return _normalize_file_info(payload["entry"])
Expand All @@ -493,10 +510,12 @@ async def remove(self, path: str, *, recursive: Optional[bool] = None) -> None:
await self._transport.request_json(
"/sandbox/files/delete",
method="POST",
json_body=SandboxFileDeleteParams(
path=path,
recursive=recursive,
).model_dump(exclude_none=True),
json_body=self._with_run_as_body(
SandboxFileDeleteParams(
path=path,
recursive=recursive,
).model_dump(exclude_none=True)
),
headers={"content-type": "application/json"},
)

Expand Down Expand Up @@ -527,12 +546,14 @@ async def copy(
payload = await self._transport.request_json(
"/sandbox/files/copy",
method="POST",
json_body={
"from": normalized.source,
"to": normalized.destination,
"recursive": normalized.recursive,
"overwrite": normalized.overwrite,
},
json_body=self._with_run_as_body(
{
"from": normalized.source,
"to": normalized.destination,
"recursive": normalized.recursive,
"overwrite": normalized.overwrite,
}
),
headers={"content-type": "application/json"},
)
return _normalize_file_info(payload["entry"])
Expand All @@ -558,7 +579,7 @@ async def chmod(
await self._transport.request_json(
"/sandbox/files/chmod",
method="POST",
json_body=normalized.model_dump(exclude_none=True),
json_body=self._with_run_as_body(normalized.model_dump(exclude_none=True)),
headers={"content-type": "application/json"},
)

Expand All @@ -585,18 +606,20 @@ async def chown(
await self._transport.request_json(
"/sandbox/files/chown",
method="POST",
json_body=normalized.model_dump(exclude_none=True),
json_body=self._with_run_as_body(normalized.model_dump(exclude_none=True)),
headers={"content-type": "application/json"},
)

async def watch(self, path: str, *, recursive: Optional[bool] = None):
payload = await self._transport.request_json(
"/sandbox/files/watch",
method="POST",
json_body={
"path": path,
"recursive": recursive,
},
json_body=self._with_run_as_body(
{
"path": path,
"recursive": recursive,
}
),
headers={"content-type": "application/json"},
)
return SandboxFileWatchHandle(
Expand Down Expand Up @@ -646,11 +669,13 @@ async def upload_url(
payload = await self._transport.request_json(
"/sandbox/files/presign-upload",
method="POST",
json_body=SandboxPresignFileParams(
path=path,
expires_in_seconds=expires_in_seconds,
one_time=one_time,
).model_dump(exclude_none=True, by_alias=True),
json_body=self._with_run_as_body(
SandboxPresignFileParams(
path=path,
expires_in_seconds=expires_in_seconds,
one_time=one_time,
).model_dump(exclude_none=True, by_alias=True)
),
headers={"content-type": "application/json"},
)
return SandboxPresignedUrl(**payload)
Expand All @@ -665,11 +690,13 @@ async def download_url(
payload = await self._transport.request_json(
"/sandbox/files/presign-download",
method="POST",
json_body=SandboxPresignFileParams(
path=path,
expires_in_seconds=expires_in_seconds,
one_time=one_time,
).model_dump(exclude_none=True, by_alias=True),
json_body=self._with_run_as_body(
SandboxPresignFileParams(
path=path,
expires_in_seconds=expires_in_seconds,
one_time=one_time,
).model_dump(exclude_none=True, by_alias=True)
),
headers={"content-type": "application/json"},
)
return SandboxPresignedUrl(**payload)
Expand All @@ -685,12 +712,14 @@ async def _read_wire(
payload = await self._transport.request_json(
"/sandbox/files/read",
method="POST",
json_body={
"path": path,
"offset": offset,
"length": length,
"encoding": encoding,
},
json_body=self._with_run_as_body(
{
"path": path,
"offset": offset,
"length": length,
"encoding": encoding,
}
),
headers={"content-type": "application/json"},
)
return SandboxFileReadResult(**payload)
Expand All @@ -707,13 +736,31 @@ async def _write_single(
payload = await self._transport.request_json(
"/sandbox/files/write",
method="POST",
json_body={
"path": path,
"data": data,
"append": append,
"mode": mode,
"encoding": encoding,
},
json_body=self._with_run_as_body(
{
"path": path,
"data": data,
"append": append,
"mode": mode,
"encoding": encoding,
}
),
headers={"content-type": "application/json"},
)
return _normalize_write_info(payload["files"][0])

def _with_run_as_params(
self, params: Dict[str, Union[str, int, bool, None]]
) -> Dict[str, Union[str, int, bool, None]]:
if not self._default_run_as:
return params
enriched = dict(params)
enriched["runAs"] = self._default_run_as
return enriched

def _with_run_as_body(self, body: Dict[str, Any]) -> Dict[str, Any]:
if not self._default_run_as:
return body
enriched = dict(body)
enriched["runAs"] = self._default_run_as
return enriched
Loading