55import json
66import socket
77from datetime import datetime
8- from typing import AsyncIterator , Callable , List , Optional , Union
8+ from typing import Any , AsyncIterator , Callable , Dict , List , Optional , Union
99from urllib .parse import urlencode
1010
1111from websockets .asyncio .client import connect as async_ws_connect
@@ -249,10 +249,21 @@ def __init__(
249249 transport : RuntimeTransport ,
250250 get_connection_info ,
251251 runtime_proxy_override : Optional [str ] = None ,
252+ default_run_as : Optional [str ] = None ,
252253 ):
253254 self ._transport = transport
254255 self ._get_connection_info = get_connection_info
255256 self ._runtime_proxy_override = runtime_proxy_override
257+ self ._default_run_as = default_run_as .strip () if default_run_as else None
258+
259+ def with_run_as (self , run_as : Optional [str ]):
260+ normalized = run_as .strip () if run_as else None
261+ return SandboxFilesApi (
262+ self ._transport ,
263+ self ._get_connection_info ,
264+ self ._runtime_proxy_override ,
265+ default_run_as = normalized ,
266+ )
256267
257268 async def list (
258269 self ,
@@ -266,17 +277,19 @@ async def list(
266277
267278 payload = await self ._transport .request_json (
268279 "/sandbox/files" ,
269- params = {
270- "path" : path ,
271- "depth" : depth ,
272- },
280+ params = self ._with_run_as_params (
281+ {
282+ "path" : path ,
283+ "depth" : depth ,
284+ }
285+ ),
273286 )
274287 return [_normalize_file_info (entry ) for entry in payload .get ("entries" , [])]
275288
276289 async def get_info (self , path : str ) -> SandboxFileInfo :
277290 payload = await self ._transport .request_json (
278291 "/sandbox/files/stat" ,
279- params = {"path" : path },
292+ params = self . _with_run_as_params ( {"path" : path }) ,
280293 )
281294 return _normalize_file_info (payload ["file" ])
282295
@@ -357,10 +370,12 @@ async def write(
357370 payload = await self ._transport .request_json (
358371 "/sandbox/files/write" ,
359372 method = "POST" ,
360- json_body = {
361- "path" : path_or_files ,
362- ** _encode_write_data (data ),
363- },
373+ json_body = self ._with_run_as_body (
374+ {
375+ "path" : path_or_files ,
376+ ** _encode_write_data (data ),
377+ }
378+ ),
364379 headers = {"content-type" : "application/json" },
365380 )
366381 return _normalize_write_info (payload ["files" ][0 ])
@@ -377,7 +392,7 @@ async def write(
377392 payload = await self ._transport .request_json (
378393 "/sandbox/files/write" ,
379394 method = "POST" ,
380- json_body = {"files" : encoded_files },
395+ json_body = self . _with_run_as_body ( {"files" : encoded_files }) ,
381396 headers = {"content-type" : "application/json" },
382397 )
383398 return [_normalize_write_info (entry ) for entry in payload .get ("files" , [])]
@@ -419,15 +434,15 @@ async def upload(self, path: str, data: Union[str, bytes, bytearray]):
419434 payload = await self ._transport .request_json (
420435 "/sandbox/files/upload" ,
421436 method = "PUT" ,
422- params = {"path" : path },
437+ params = self . _with_run_as_params ( {"path" : path }) ,
423438 content = body ,
424439 )
425440 return SandboxFileTransferResult (** payload )
426441
427442 async def download (self , path : str ) -> bytes :
428443 return await self ._transport .request_bytes (
429444 "/sandbox/files/download" ,
430- params = {"path" : path },
445+ params = self . _with_run_as_params ( {"path" : path }) ,
431446 )
432447
433448 async def make_dir (
@@ -440,11 +455,13 @@ async def make_dir(
440455 payload = await self ._transport .request_json (
441456 "/sandbox/files/mkdir" ,
442457 method = "POST" ,
443- json_body = {
444- "path" : path ,
445- "parents" : parents ,
446- "mode" : mode ,
447- },
458+ json_body = self ._with_run_as_body (
459+ {
460+ "path" : path ,
461+ "parents" : parents ,
462+ "mode" : mode ,
463+ }
464+ ),
448465 headers = {"content-type" : "application/json" },
449466 )
450467 return bool (payload .get ("created" ))
@@ -475,7 +492,7 @@ async def rename(
475492 payload = await self ._transport .request_json (
476493 "/sandbox/files/move" ,
477494 method = "POST" ,
478- json_body = payload ,
495+ json_body = self . _with_run_as_body ( payload ) ,
479496 headers = {"content-type" : "application/json" },
480497 )
481498 return _normalize_file_info (payload ["entry" ])
@@ -493,10 +510,12 @@ async def remove(self, path: str, *, recursive: Optional[bool] = None) -> None:
493510 await self ._transport .request_json (
494511 "/sandbox/files/delete" ,
495512 method = "POST" ,
496- json_body = SandboxFileDeleteParams (
497- path = path ,
498- recursive = recursive ,
499- ).model_dump (exclude_none = True ),
513+ json_body = self ._with_run_as_body (
514+ SandboxFileDeleteParams (
515+ path = path ,
516+ recursive = recursive ,
517+ ).model_dump (exclude_none = True )
518+ ),
500519 headers = {"content-type" : "application/json" },
501520 )
502521
@@ -527,12 +546,14 @@ async def copy(
527546 payload = await self ._transport .request_json (
528547 "/sandbox/files/copy" ,
529548 method = "POST" ,
530- json_body = {
531- "from" : normalized .source ,
532- "to" : normalized .destination ,
533- "recursive" : normalized .recursive ,
534- "overwrite" : normalized .overwrite ,
535- },
549+ json_body = self ._with_run_as_body (
550+ {
551+ "from" : normalized .source ,
552+ "to" : normalized .destination ,
553+ "recursive" : normalized .recursive ,
554+ "overwrite" : normalized .overwrite ,
555+ }
556+ ),
536557 headers = {"content-type" : "application/json" },
537558 )
538559 return _normalize_file_info (payload ["entry" ])
@@ -558,7 +579,7 @@ async def chmod(
558579 await self ._transport .request_json (
559580 "/sandbox/files/chmod" ,
560581 method = "POST" ,
561- json_body = normalized .model_dump (exclude_none = True ),
582+ json_body = self . _with_run_as_body ( normalized .model_dump (exclude_none = True ) ),
562583 headers = {"content-type" : "application/json" },
563584 )
564585
@@ -585,18 +606,20 @@ async def chown(
585606 await self ._transport .request_json (
586607 "/sandbox/files/chown" ,
587608 method = "POST" ,
588- json_body = normalized .model_dump (exclude_none = True ),
609+ json_body = self . _with_run_as_body ( normalized .model_dump (exclude_none = True ) ),
589610 headers = {"content-type" : "application/json" },
590611 )
591612
592613 async def watch (self , path : str , * , recursive : Optional [bool ] = None ):
593614 payload = await self ._transport .request_json (
594615 "/sandbox/files/watch" ,
595616 method = "POST" ,
596- json_body = {
597- "path" : path ,
598- "recursive" : recursive ,
599- },
617+ json_body = self ._with_run_as_body (
618+ {
619+ "path" : path ,
620+ "recursive" : recursive ,
621+ }
622+ ),
600623 headers = {"content-type" : "application/json" },
601624 )
602625 return SandboxFileWatchHandle (
@@ -646,11 +669,13 @@ async def upload_url(
646669 payload = await self ._transport .request_json (
647670 "/sandbox/files/presign-upload" ,
648671 method = "POST" ,
649- json_body = SandboxPresignFileParams (
650- path = path ,
651- expires_in_seconds = expires_in_seconds ,
652- one_time = one_time ,
653- ).model_dump (exclude_none = True , by_alias = True ),
672+ json_body = self ._with_run_as_body (
673+ SandboxPresignFileParams (
674+ path = path ,
675+ expires_in_seconds = expires_in_seconds ,
676+ one_time = one_time ,
677+ ).model_dump (exclude_none = True , by_alias = True )
678+ ),
654679 headers = {"content-type" : "application/json" },
655680 )
656681 return SandboxPresignedUrl (** payload )
@@ -665,11 +690,13 @@ async def download_url(
665690 payload = await self ._transport .request_json (
666691 "/sandbox/files/presign-download" ,
667692 method = "POST" ,
668- json_body = SandboxPresignFileParams (
669- path = path ,
670- expires_in_seconds = expires_in_seconds ,
671- one_time = one_time ,
672- ).model_dump (exclude_none = True , by_alias = True ),
693+ json_body = self ._with_run_as_body (
694+ SandboxPresignFileParams (
695+ path = path ,
696+ expires_in_seconds = expires_in_seconds ,
697+ one_time = one_time ,
698+ ).model_dump (exclude_none = True , by_alias = True )
699+ ),
673700 headers = {"content-type" : "application/json" },
674701 )
675702 return SandboxPresignedUrl (** payload )
@@ -685,12 +712,14 @@ async def _read_wire(
685712 payload = await self ._transport .request_json (
686713 "/sandbox/files/read" ,
687714 method = "POST" ,
688- json_body = {
689- "path" : path ,
690- "offset" : offset ,
691- "length" : length ,
692- "encoding" : encoding ,
693- },
715+ json_body = self ._with_run_as_body (
716+ {
717+ "path" : path ,
718+ "offset" : offset ,
719+ "length" : length ,
720+ "encoding" : encoding ,
721+ }
722+ ),
694723 headers = {"content-type" : "application/json" },
695724 )
696725 return SandboxFileReadResult (** payload )
@@ -707,13 +736,31 @@ async def _write_single(
707736 payload = await self ._transport .request_json (
708737 "/sandbox/files/write" ,
709738 method = "POST" ,
710- json_body = {
711- "path" : path ,
712- "data" : data ,
713- "append" : append ,
714- "mode" : mode ,
715- "encoding" : encoding ,
716- },
739+ json_body = self ._with_run_as_body (
740+ {
741+ "path" : path ,
742+ "data" : data ,
743+ "append" : append ,
744+ "mode" : mode ,
745+ "encoding" : encoding ,
746+ }
747+ ),
717748 headers = {"content-type" : "application/json" },
718749 )
719750 return _normalize_write_info (payload ["files" ][0 ])
751+
752+ def _with_run_as_params (
753+ self , params : Dict [str , Union [str , int , bool , None ]]
754+ ) -> Dict [str , Union [str , int , bool , None ]]:
755+ if not self ._default_run_as :
756+ return params
757+ enriched = dict (params )
758+ enriched ["runAs" ] = self ._default_run_as
759+ return enriched
760+
761+ def _with_run_as_body (self , body : Dict [str , Any ]) -> Dict [str , Any ]:
762+ if not self ._default_run_as :
763+ return body
764+ enriched = dict (body )
765+ enriched ["runAs" ] = self ._default_run_as
766+ return enriched
0 commit comments