From cbb8927c4d30d78129955d285be502272c726831 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Dobr=C3=BD?= Date: Mon, 2 Dec 2024 16:49:29 +0100 Subject: [PATCH 01/15] Add watch dir recursive option (missing python client regeneration) --- .../src/envd/filesystem/filesystem_pb.ts | 12 +++- .../js-sdk/src/sandbox/filesystem/index.ts | 9 ++- .../js-sdk/tests/sandbox/files/watch.test.ts | 65 +++++++++++++++++++ .../sandbox_async/filesystem/filesystem.py | 4 +- .../e2b/sandbox_sync/filesystem/filesystem.py | 4 +- .../async/sandbox_async/files/test_watch.py | 51 +++++++++++++++ .../sync/sandbox_sync/files/test_watch.py | 51 +++++++++++++++ spec/envd/filesystem/filesystem.proto | 4 +- 8 files changed, 195 insertions(+), 5 deletions(-) diff --git a/packages/js-sdk/src/envd/filesystem/filesystem_pb.ts b/packages/js-sdk/src/envd/filesystem/filesystem_pb.ts index 10082f531..bf3763f36 100644 --- a/packages/js-sdk/src/envd/filesystem/filesystem_pb.ts +++ b/packages/js-sdk/src/envd/filesystem/filesystem_pb.ts @@ -10,7 +10,7 @@ import type { Message } from "@bufbuild/protobuf"; * Describes the file filesystem/filesystem.proto. */ export const file_filesystem_filesystem: GenFile = /*@__PURE__*/ - fileDesc("ChtmaWxlc3lzdGVtL2ZpbGVzeXN0ZW0ucHJvdG8SCmZpbGVzeXN0ZW0iMgoLTW92ZVJlcXVlc3QSDgoGc291cmNlGAEgASgJEhMKC2Rlc3RpbmF0aW9uGAIgASgJIjQKDE1vdmVSZXNwb25zZRIkCgVlbnRyeRgBIAEoCzIVLmZpbGVzeXN0ZW0uRW50cnlJbmZvIh4KDk1ha2VEaXJSZXF1ZXN0EgwKBHBhdGgYASABKAkiNwoPTWFrZURpclJlc3BvbnNlEiQKBWVudHJ5GAEgASgLMhUuZmlsZXN5c3RlbS5FbnRyeUluZm8iHQoNUmVtb3ZlUmVxdWVzdBIMCgRwYXRoGAEgASgJIhAKDlJlbW92ZVJlc3BvbnNlIhsKC1N0YXRSZXF1ZXN0EgwKBHBhdGgYASABKAkiNAoMU3RhdFJlc3BvbnNlEiQKBWVudHJ5GAEgASgLMhUuZmlsZXN5c3RlbS5FbnRyeUluZm8iSwoJRW50cnlJbmZvEgwKBG5hbWUYASABKAkSIgoEdHlwZRgCIAEoDjIULmZpbGVzeXN0ZW0uRmlsZVR5cGUSDAoEcGF0aBgDIAEoCSIeCg5MaXN0RGlyUmVxdWVzdBIMCgRwYXRoGAEgASgJIjkKD0xpc3REaXJSZXNwb25zZRImCgdlbnRyaWVzGAEgAygLMhUuZmlsZXN5c3RlbS5FbnRyeUluZm8iHwoPV2F0Y2hEaXJSZXF1ZXN0EgwKBHBhdGgYASABKAkiRAoPRmlsZXN5c3RlbUV2ZW50EgwKBG5hbWUYASABKAkSIwoEdHlwZRgCIAEoDjIVLmZpbGVzeXN0ZW0uRXZlbnRUeXBlIuABChBXYXRjaERpclJlc3BvbnNlEjgKBXN0YXJ0GAEgASgLMicuZmlsZXN5c3RlbS5XYXRjaERpclJlc3BvbnNlLlN0YXJ0RXZlbnRIABIxCgpmaWxlc3lzdGVtGAIgASgLMhsuZmlsZXN5c3RlbS5GaWxlc3lzdGVtRXZlbnRIABI7CglrZWVwYWxpdmUYAyABKAsyJi5maWxlc3lzdGVtLldhdGNoRGlyUmVzcG9uc2UuS2VlcEFsaXZlSAAaDAoKU3RhcnRFdmVudBoLCglLZWVwQWxpdmVCBwoFZXZlbnQiJAoUQ3JlYXRlV2F0Y2hlclJlcXVlc3QSDAoEcGF0aBgBIAEoCSIrChVDcmVhdGVXYXRjaGVyUmVzcG9uc2USEgoKd2F0Y2hlcl9pZBgBIAEoCSItChdHZXRXYXRjaGVyRXZlbnRzUmVxdWVzdBISCgp3YXRjaGVyX2lkGAEgASgJIkcKGEdldFdhdGNoZXJFdmVudHNSZXNwb25zZRIrCgZldmVudHMYASADKAsyGy5maWxlc3lzdGVtLkZpbGVzeXN0ZW1FdmVudCIqChRSZW1vdmVXYXRjaGVyUmVxdWVzdBISCgp3YXRjaGVyX2lkGAEgASgJIhcKFVJlbW92ZVdhdGNoZXJSZXNwb25zZSpSCghGaWxlVHlwZRIZChVGSUxFX1RZUEVfVU5TUEVDSUZJRUQQABISCg5GSUxFX1RZUEVfRklMRRABEhcKE0ZJTEVfVFlQRV9ESVJFQ1RPUlkQAiqYAQoJRXZlbnRUeXBlEhoKFkVWRU5UX1RZUEVfVU5TUEVDSUZJRUQQABIVChFFVkVOVF9UWVBFX0NSRUFURRABEhQKEEVWRU5UX1RZUEVfV1JJVEUQAhIVChFFVkVOVF9UWVBFX1JFTU9WRRADEhUKEUVWRU5UX1RZUEVfUkVOQU1FEAQSFAoQRVZFTlRfVFlQRV9DSE1PRBAFMp8FCgpGaWxlc3lzdGVtEjkKBFN0YXQSFy5maWxlc3lzdGVtLlN0YXRSZXF1ZXN0GhguZmlsZXN5c3RlbS5TdGF0UmVzcG9uc2USQgoHTWFrZURpchIaLmZpbGVzeXN0ZW0uTWFrZURpclJlcXVlc3QaGy5maWxlc3lzdGVtLk1ha2VEaXJSZXNwb25zZRI5CgRNb3ZlEhcuZmlsZXN5c3RlbS5Nb3ZlUmVxdWVzdBoYLmZpbGVzeXN0ZW0uTW92ZVJlc3BvbnNlEkIKB0xpc3REaXISGi5maWxlc3lzdGVtLkxpc3REaXJSZXF1ZXN0GhsuZmlsZXN5c3RlbS5MaXN0RGlyUmVzcG9uc2USPwoGUmVtb3ZlEhkuZmlsZXN5c3RlbS5SZW1vdmVSZXF1ZXN0GhouZmlsZXN5c3RlbS5SZW1vdmVSZXNwb25zZRJHCghXYXRjaERpchIbLmZpbGVzeXN0ZW0uV2F0Y2hEaXJSZXF1ZXN0GhwuZmlsZXN5c3RlbS5XYXRjaERpclJlc3BvbnNlMAESVAoNQ3JlYXRlV2F0Y2hlchIgLmZpbGVzeXN0ZW0uQ3JlYXRlV2F0Y2hlclJlcXVlc3QaIS5maWxlc3lzdGVtLkNyZWF0ZVdhdGNoZXJSZXNwb25zZRJdChBHZXRXYXRjaGVyRXZlbnRzEiMuZmlsZXN5c3RlbS5HZXRXYXRjaGVyRXZlbnRzUmVxdWVzdBokLmZpbGVzeXN0ZW0uR2V0V2F0Y2hlckV2ZW50c1Jlc3BvbnNlElQKDVJlbW92ZVdhdGNoZXISIC5maWxlc3lzdGVtLlJlbW92ZVdhdGNoZXJSZXF1ZXN0GiEuZmlsZXN5c3RlbS5SZW1vdmVXYXRjaGVyUmVzcG9uc2VCaQoOY29tLmZpbGVzeXN0ZW1CD0ZpbGVzeXN0ZW1Qcm90b1ABogIDRlhYqgIKRmlsZXN5c3RlbcoCCkZpbGVzeXN0ZW3iAhZGaWxlc3lzdGVtXEdQQk1ldGFkYXRh6gIKRmlsZXN5c3RlbWIGcHJvdG8z"); + fileDesc("ChtmaWxlc3lzdGVtL2ZpbGVzeXN0ZW0ucHJvdG8SCmZpbGVzeXN0ZW0iMgoLTW92ZVJlcXVlc3QSDgoGc291cmNlGAEgASgJEhMKC2Rlc3RpbmF0aW9uGAIgASgJIjQKDE1vdmVSZXNwb25zZRIkCgVlbnRyeRgBIAEoCzIVLmZpbGVzeXN0ZW0uRW50cnlJbmZvIh4KDk1ha2VEaXJSZXF1ZXN0EgwKBHBhdGgYASABKAkiNwoPTWFrZURpclJlc3BvbnNlEiQKBWVudHJ5GAEgASgLMhUuZmlsZXN5c3RlbS5FbnRyeUluZm8iHQoNUmVtb3ZlUmVxdWVzdBIMCgRwYXRoGAEgASgJIhAKDlJlbW92ZVJlc3BvbnNlIhsKC1N0YXRSZXF1ZXN0EgwKBHBhdGgYASABKAkiNAoMU3RhdFJlc3BvbnNlEiQKBWVudHJ5GAEgASgLMhUuZmlsZXN5c3RlbS5FbnRyeUluZm8iSwoJRW50cnlJbmZvEgwKBG5hbWUYASABKAkSIgoEdHlwZRgCIAEoDjIULmZpbGVzeXN0ZW0uRmlsZVR5cGUSDAoEcGF0aBgDIAEoCSIeCg5MaXN0RGlyUmVxdWVzdBIMCgRwYXRoGAEgASgJIjkKD0xpc3REaXJSZXNwb25zZRImCgdlbnRyaWVzGAEgAygLMhUuZmlsZXN5c3RlbS5FbnRyeUluZm8iMgoPV2F0Y2hEaXJSZXF1ZXN0EgwKBHBhdGgYASABKAkSEQoJcmVjdXJzaXZlGAIgASgIIkQKD0ZpbGVzeXN0ZW1FdmVudBIMCgRuYW1lGAEgASgJEiMKBHR5cGUYAiABKA4yFS5maWxlc3lzdGVtLkV2ZW50VHlwZSLgAQoQV2F0Y2hEaXJSZXNwb25zZRI4CgVzdGFydBgBIAEoCzInLmZpbGVzeXN0ZW0uV2F0Y2hEaXJSZXNwb25zZS5TdGFydEV2ZW50SAASMQoKZmlsZXN5c3RlbRgCIAEoCzIbLmZpbGVzeXN0ZW0uRmlsZXN5c3RlbUV2ZW50SAASOwoJa2VlcGFsaXZlGAMgASgLMiYuZmlsZXN5c3RlbS5XYXRjaERpclJlc3BvbnNlLktlZXBBbGl2ZUgAGgwKClN0YXJ0RXZlbnQaCwoJS2VlcEFsaXZlQgcKBWV2ZW50IjcKFENyZWF0ZVdhdGNoZXJSZXF1ZXN0EgwKBHBhdGgYASABKAkSEQoJcmVjdXJzaXZlGAIgASgIIisKFUNyZWF0ZVdhdGNoZXJSZXNwb25zZRISCgp3YXRjaGVyX2lkGAEgASgJIi0KF0dldFdhdGNoZXJFdmVudHNSZXF1ZXN0EhIKCndhdGNoZXJfaWQYASABKAkiRwoYR2V0V2F0Y2hlckV2ZW50c1Jlc3BvbnNlEisKBmV2ZW50cxgBIAMoCzIbLmZpbGVzeXN0ZW0uRmlsZXN5c3RlbUV2ZW50IioKFFJlbW92ZVdhdGNoZXJSZXF1ZXN0EhIKCndhdGNoZXJfaWQYASABKAkiFwoVUmVtb3ZlV2F0Y2hlclJlc3BvbnNlKlIKCEZpbGVUeXBlEhkKFUZJTEVfVFlQRV9VTlNQRUNJRklFRBAAEhIKDkZJTEVfVFlQRV9GSUxFEAESFwoTRklMRV9UWVBFX0RJUkVDVE9SWRACKpgBCglFdmVudFR5cGUSGgoWRVZFTlRfVFlQRV9VTlNQRUNJRklFRBAAEhUKEUVWRU5UX1RZUEVfQ1JFQVRFEAESFAoQRVZFTlRfVFlQRV9XUklURRACEhUKEUVWRU5UX1RZUEVfUkVNT1ZFEAMSFQoRRVZFTlRfVFlQRV9SRU5BTUUQBBIUChBFVkVOVF9UWVBFX0NITU9EEAUynwUKCkZpbGVzeXN0ZW0SOQoEU3RhdBIXLmZpbGVzeXN0ZW0uU3RhdFJlcXVlc3QaGC5maWxlc3lzdGVtLlN0YXRSZXNwb25zZRJCCgdNYWtlRGlyEhouZmlsZXN5c3RlbS5NYWtlRGlyUmVxdWVzdBobLmZpbGVzeXN0ZW0uTWFrZURpclJlc3BvbnNlEjkKBE1vdmUSFy5maWxlc3lzdGVtLk1vdmVSZXF1ZXN0GhguZmlsZXN5c3RlbS5Nb3ZlUmVzcG9uc2USQgoHTGlzdERpchIaLmZpbGVzeXN0ZW0uTGlzdERpclJlcXVlc3QaGy5maWxlc3lzdGVtLkxpc3REaXJSZXNwb25zZRI/CgZSZW1vdmUSGS5maWxlc3lzdGVtLlJlbW92ZVJlcXVlc3QaGi5maWxlc3lzdGVtLlJlbW92ZVJlc3BvbnNlEkcKCFdhdGNoRGlyEhsuZmlsZXN5c3RlbS5XYXRjaERpclJlcXVlc3QaHC5maWxlc3lzdGVtLldhdGNoRGlyUmVzcG9uc2UwARJUCg1DcmVhdGVXYXRjaGVyEiAuZmlsZXN5c3RlbS5DcmVhdGVXYXRjaGVyUmVxdWVzdBohLmZpbGVzeXN0ZW0uQ3JlYXRlV2F0Y2hlclJlc3BvbnNlEl0KEEdldFdhdGNoZXJFdmVudHMSIy5maWxlc3lzdGVtLkdldFdhdGNoZXJFdmVudHNSZXF1ZXN0GiQuZmlsZXN5c3RlbS5HZXRXYXRjaGVyRXZlbnRzUmVzcG9uc2USVAoNUmVtb3ZlV2F0Y2hlchIgLmZpbGVzeXN0ZW0uUmVtb3ZlV2F0Y2hlclJlcXVlc3QaIS5maWxlc3lzdGVtLlJlbW92ZVdhdGNoZXJSZXNwb25zZUJpCg5jb20uZmlsZXN5c3RlbUIPRmlsZXN5c3RlbVByb3RvUAGiAgNGWFiqAgpGaWxlc3lzdGVtygIKRmlsZXN5c3RlbeICFkZpbGVzeXN0ZW1cR1BCTWV0YWRhdGHqAgpGaWxlc3lzdGVtYgZwcm90bzM"); /** * @generated from message filesystem.MoveRequest @@ -218,6 +218,11 @@ export type WatchDirRequest = Message<"filesystem.WatchDirRequest"> & { * @generated from field: string path = 1; */ path: string; + + /** + * @generated from field: bool recursive = 2; + */ + recursive: boolean; }; /** @@ -318,6 +323,11 @@ export type CreateWatcherRequest = Message<"filesystem.CreateWatcherRequest"> & * @generated from field: string path = 1; */ path: string; + + /** + * @generated from field: bool recursive = 2; + */ + recursive: boolean; }; /** diff --git a/packages/js-sdk/src/sandbox/filesystem/index.ts b/packages/js-sdk/src/sandbox/filesystem/index.ts index f65003ae8..ccba155bc 100644 --- a/packages/js-sdk/src/sandbox/filesystem/index.ts +++ b/packages/js-sdk/src/sandbox/filesystem/index.ts @@ -90,6 +90,10 @@ export interface WatchOpts extends FilesystemRequestOpts { * Callback to call when the watch operation stops. */ onExit?: (err?: Error) => void | Promise + /** + * Watch the directory recursively + */ + recursive?: boolean } /** @@ -446,7 +450,10 @@ export class Filesystem { : undefined const events = this.rpc.watchDir( - { path }, + { + path, + recursive: opts?.recursive ?? false, + }, { headers: { ...authenticationHeader(opts?.user), diff --git a/packages/js-sdk/tests/sandbox/files/watch.test.ts b/packages/js-sdk/tests/sandbox/files/watch.test.ts index 4cb9251f9..9a4127061 100644 --- a/packages/js-sdk/tests/sandbox/files/watch.test.ts +++ b/packages/js-sdk/tests/sandbox/files/watch.test.ts @@ -31,6 +31,71 @@ sandboxTest('watch directory changes', async ({ sandbox }) => { await handle.stop() }) +sandboxTest('watch recursive directory changes', async ({ sandbox }) => { + const dirname = 'test_watch_dir' + const nestedDirname = 'test_nested_watch_dir' + const filename = 'test_watch.txt' + const content = 'This file will be watched.' + const newContent = 'This file has been modified.' + + await sandbox.files.remove(dirname) + await sandbox.files.makeDir(`${dirname}/${nestedDirname}`) + await sandbox.files.write(`${dirname}/${nestedDirname}/${filename}`, content) + + let trigger: () => void + + const eventPromise = new Promise((resolve) => { + trigger = resolve + }) + + const expectedFileName = `${nestedDirname}/${filename}` + const handle = await sandbox.files.watchDir(dirname, async (event) => { + if (event.type === FilesystemEventType.WRITE && event.name === expectedFileName) { + trigger() + } + }, { + recursive: true + }) + + await sandbox.files.write(`${dirname}/${nestedDirname}/${filename}`, newContent) + + await eventPromise + + await handle.stop() +}) + +sandboxTest('watch recursive directory folder addition', async ({ sandbox }) => { + const dirname = 'test_watch_dir' + const nestedDirname = 'test_nested_watch_dir' + const filename = 'test_watch.txt' + const content = 'This file will be watched.' + + await sandbox.files.remove(dirname) + await sandbox.files.makeDir(dirname) + + let trigger: () => void + + const eventPromise = new Promise((resolve) => { + trigger = resolve + }) + + const expectedFileName = `${nestedDirname}/${filename}` + const handle = await sandbox.files.watchDir(dirname, async (event) => { + if (event.type === FilesystemEventType.WRITE && event.name === expectedFileName) { + trigger() + } + }, { + recursive: true + }) + + await sandbox.files.makeDir(`${dirname}/${nestedDirname}`) + await sandbox.files.write(`${dirname}/${nestedDirname}/${filename}`, content) + + await eventPromise + + await handle.stop() +}) + sandboxTest('watch non-existing directory', async ({ sandbox }) => { const dirname = 'non_existing_watch_dir' diff --git a/packages/python-sdk/e2b/sandbox_async/filesystem/filesystem.py b/packages/python-sdk/e2b/sandbox_async/filesystem/filesystem.py index 216147c78..404faf21d 100644 --- a/packages/python-sdk/e2b/sandbox_async/filesystem/filesystem.py +++ b/packages/python-sdk/e2b/sandbox_async/filesystem/filesystem.py @@ -343,6 +343,7 @@ async def watch_dir( user: Username = "user", request_timeout: Optional[float] = None, timeout: Optional[float] = 60, + recursive: bool = False, ) -> AsyncWatchHandle: """ Watch directory for filesystem events. @@ -353,11 +354,12 @@ async def watch_dir( :param user: Run the operation as this user :param request_timeout: Timeout for the request in **seconds** :param timeout: Timeout for the watch operation in **seconds**. Using `0` will not limit the watch time + :param recursive: Watch directory recursively :return: `AsyncWatchHandle` object for stopping watching directory """ events = self._rpc.awatch_dir( - filesystem_pb2.WatchDirRequest(path=path), + filesystem_pb2.WatchDirRequest(path=path, recursive=recursive), request_timeout=self._connection_config.get_request_timeout( request_timeout ), diff --git a/packages/python-sdk/e2b/sandbox_sync/filesystem/filesystem.py b/packages/python-sdk/e2b/sandbox_sync/filesystem/filesystem.py index 094d82a71..68a8d606e 100644 --- a/packages/python-sdk/e2b/sandbox_sync/filesystem/filesystem.py +++ b/packages/python-sdk/e2b/sandbox_sync/filesystem/filesystem.py @@ -336,6 +336,7 @@ def watch_dir( path: str, user: Username = "user", request_timeout: Optional[float] = None, + recursive: bool = False, ) -> WatchHandle: """ Watch directory for filesystem events. @@ -343,12 +344,13 @@ def watch_dir( :param path: Path to a directory to watch :param user: Run the operation as this user :param request_timeout: Timeout for the request in **seconds** + :param recursive: Watch directory recursively :return: `WatchHandle` object for stopping watching directory """ try: r = self._rpc.create_watcher( - filesystem_pb2.CreateWatcherRequest(path=path), + filesystem_pb2.CreateWatcherRequest(path=path, recursive=recursive), request_timeout=self._connection_config.get_request_timeout( request_timeout ), diff --git a/packages/python-sdk/tests/async/sandbox_async/files/test_watch.py b/packages/python-sdk/tests/async/sandbox_async/files/test_watch.py index 19d319a95..0b3dcb211 100644 --- a/packages/python-sdk/tests/async/sandbox_async/files/test_watch.py +++ b/packages/python-sdk/tests/async/sandbox_async/files/test_watch.py @@ -35,6 +35,57 @@ def handle_event(e: FilesystemEvent): await handle.stop() +async def test_watch_recursive_directory_changes(async_sandbox: AsyncSandbox): + dirname = "test_watch_dir" + nested_dirname = "test_nested_watch_dir" + filename = "test_watch.txt" + content = "This file will be watched." + + await async_sandbox.files.remove(dirname) + await async_sandbox.files.make_dir(f"{dirname}/{nested_dirname}") + + event_triggered = Event() + + expected_filename = f"{nested_dirname}/{filename}" + def handle_event(e: FilesystemEvent): + if e.type == FilesystemEventType.WRITE and e.name == expected_filename: + event_triggered.set() + + handle = await async_sandbox.files.watch_dir(dirname, on_event=handle_event, recursive=True) + + await async_sandbox.files.write(f"{dirname}/{nested_dirname}/{filename}", content) + + await event_triggered.wait() + + await handle.stop() + + +async def test_watch_recursive_directory_folder_addition(async_sandbox: AsyncSandbox): + dirname = "test_watch_dir" + nested_dirname = "test_nested_watch_dir" + filename = "test_watch.txt" + content = "This file will be watched." + + await async_sandbox.files.remove(dirname) + await async_sandbox.files.make_dir(dirname) + + event_triggered = Event() + + expected_filename = f"{nested_dirname}/{filename}" + def handle_event(e: FilesystemEvent): + if e.type == FilesystemEventType.WRITE and e.name == expected_filename: + event_triggered.set() + + handle = await async_sandbox.files.watch_dir(dirname, on_event=handle_event, recursive=True) + + await async_sandbox.files.make_dir(f"{dirname}/{nested_dirname}") + await async_sandbox.files.write(f"{dirname}/{nested_dirname}/{filename}", content) + + await event_triggered.wait() + + await handle.stop() + + async def test_watch_non_existing_directory(async_sandbox: AsyncSandbox): dirname = "non_existing_watch_dir" diff --git a/packages/python-sdk/tests/sync/sandbox_sync/files/test_watch.py b/packages/python-sdk/tests/sync/sandbox_sync/files/test_watch.py index e9fc5faf1..6c701ef89 100644 --- a/packages/python-sdk/tests/sync/sandbox_sync/files/test_watch.py +++ b/packages/python-sdk/tests/sync/sandbox_sync/files/test_watch.py @@ -8,7 +8,9 @@ def test_watch_directory_changes(sandbox: Sandbox): filename = "test_watch.txt" content = "This file will be watched." + sandbox.files.remove(dirname) sandbox.files.make_dir(dirname) + handle = sandbox.files.watch_dir(dirname) sandbox.files.write(f"{dirname}/{filename}", content) @@ -46,6 +48,55 @@ def test_watch_iterated(sandbox: Sandbox): handle.stop() +def test_watch_recursive_directory_changes(sandbox: Sandbox): + dirname = "test_watch_dir" + nested_dirname = "test_nested_watch_dir" + filename = "test_watch.txt" + content = "This file will be watched." + + sandbox.files.remove(dirname) + sandbox.files.make_dir(f"{dirname}/{nested_dirname}") + + handle = sandbox.files.watch_dir(dirname, recursive=True) + sandbox.files.write(f"{dirname}/{nested_dirname}/{filename}", content) + + events = handle.get_new_events() + assert len(events) == 3 + expected_filename = f"{nested_dirname}/{filename}" + assert events[0].type == FilesystemEventType.CREATE + assert events[0].name == expected_filename + + handle.stop() + + +def test_watch_recursive_directory_folder_addition(sandbox: Sandbox): + dirname = "test_watch_dir" + nested_dirname = "test_nested_watch_dir" + filename = "test_watch.txt" + content = "This file will be watched." + + sandbox.files.remove(dirname) + sandbox.files.make_dir(dirname) + + handle = sandbox.files.watch_dir(dirname, recursive=True) + + sandbox.files.make_dir(f"{dirname}/{nested_dirname}") + sandbox.files.write(f"{dirname}/{nested_dirname}/{filename}", content) + + expected_filename = f"{nested_dirname}/{filename}" + + events = handle.get_new_events() + file_changed = False + for event in events: + if event.type == FilesystemEventType.WRITE and event.name == expected_filename: + file_changed = True + break + + assert file_changed + + handle.stop() + + def test_watch_non_existing_directory(sandbox: Sandbox): dirname = "non_existing_watch_dir" diff --git a/spec/envd/filesystem/filesystem.proto b/spec/envd/filesystem/filesystem.proto index 05694cb20..8c71c4f20 100644 --- a/spec/envd/filesystem/filesystem.proto +++ b/spec/envd/filesystem/filesystem.proto @@ -70,6 +70,7 @@ message ListDirResponse { message WatchDirRequest { string path = 1; + bool recursive = 2; } message FilesystemEvent { @@ -91,6 +92,7 @@ message WatchDirResponse { message CreateWatcherRequest { string path = 1; + bool recursive = 2; } message CreateWatcherResponse { @@ -106,7 +108,7 @@ message GetWatcherEventsResponse { } message RemoveWatcherRequest { - string watcher_id = 1; + string watcher_id = 1; } message RemoveWatcherResponse {} From fbdbddc8b660397e1dfb820325252a5474529ac3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Dobr=C3=BD?= Date: Mon, 2 Dec 2024 20:54:36 +0100 Subject: [PATCH 02/15] Update watch test file formatting --- .../tests/async/sandbox_async/files/test_watch.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/packages/python-sdk/tests/async/sandbox_async/files/test_watch.py b/packages/python-sdk/tests/async/sandbox_async/files/test_watch.py index 0b3dcb211..7973fc03a 100644 --- a/packages/python-sdk/tests/async/sandbox_async/files/test_watch.py +++ b/packages/python-sdk/tests/async/sandbox_async/files/test_watch.py @@ -47,11 +47,14 @@ async def test_watch_recursive_directory_changes(async_sandbox: AsyncSandbox): event_triggered = Event() expected_filename = f"{nested_dirname}/{filename}" + def handle_event(e: FilesystemEvent): if e.type == FilesystemEventType.WRITE and e.name == expected_filename: event_triggered.set() - handle = await async_sandbox.files.watch_dir(dirname, on_event=handle_event, recursive=True) + handle = await async_sandbox.files.watch_dir( + dirname, on_event=handle_event, recursive=True + ) await async_sandbox.files.write(f"{dirname}/{nested_dirname}/{filename}", content) @@ -72,11 +75,14 @@ async def test_watch_recursive_directory_folder_addition(async_sandbox: AsyncSan event_triggered = Event() expected_filename = f"{nested_dirname}/{filename}" + def handle_event(e: FilesystemEvent): if e.type == FilesystemEventType.WRITE and e.name == expected_filename: event_triggered.set() - handle = await async_sandbox.files.watch_dir(dirname, on_event=handle_event, recursive=True) + handle = await async_sandbox.files.watch_dir( + dirname, on_event=handle_event, recursive=True + ) await async_sandbox.files.make_dir(f"{dirname}/{nested_dirname}") await async_sandbox.files.write(f"{dirname}/{nested_dirname}/{filename}", content) From 22a2894db57436537176eff4c82c7df407c5ab07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Dobr=C3=BD?= Date: Mon, 2 Dec 2024 21:09:31 +0100 Subject: [PATCH 03/15] Add python generated stubs --- .../e2b/api/client/api/default/get_health.py | 8 ++- .../sandboxes/delete_sandboxes_sandbox_id.py | 8 ++- .../post_sandboxes_sandbox_id_refreshes.py | 12 +++- .../post_sandboxes_sandbox_id_timeout.py | 12 +++- .../templates/delete_templates_template_id.py | 8 ++- ...t_templates_template_id_builds_build_id.py | 8 ++- packages/python-sdk/e2b/api/client/client.py | 36 +++++++--- .../e2b/api/client/models/__init__.py | 4 +- .../e2b/envd/filesystem/filesystem_pb2.py | 65 ++++++++++--------- .../e2b/envd/filesystem/filesystem_pb2.pyi | 12 ++-- .../e2b/envd/process/process_pb2.py | 9 +-- packages/python-sdk/e2b/sandbox/main.py | 9 ++- 12 files changed, 124 insertions(+), 67 deletions(-) diff --git a/packages/python-sdk/e2b/api/client/api/default/get_health.py b/packages/python-sdk/e2b/api/client/api/default/get_health.py index 339a6905f..c295ec3c2 100644 --- a/packages/python-sdk/e2b/api/client/api/default/get_health.py +++ b/packages/python-sdk/e2b/api/client/api/default/get_health.py @@ -17,7 +17,9 @@ def _get_kwargs() -> Dict[str, Any]: return _kwargs -def _parse_response(*, client: Union[AuthenticatedClient, Client], response: httpx.Response) -> Optional[Any]: +def _parse_response( + *, client: Union[AuthenticatedClient, Client], response: httpx.Response +) -> Optional[Any]: if response.status_code == HTTPStatus.OK: return None if response.status_code == HTTPStatus.UNAUTHORIZED: @@ -28,7 +30,9 @@ def _parse_response(*, client: Union[AuthenticatedClient, Client], response: htt return None -def _build_response(*, client: Union[AuthenticatedClient, Client], response: httpx.Response) -> Response[Any]: +def _build_response( + *, client: Union[AuthenticatedClient, Client], response: httpx.Response +) -> Response[Any]: return Response( status_code=HTTPStatus(response.status_code), content=response.content, diff --git a/packages/python-sdk/e2b/api/client/api/sandboxes/delete_sandboxes_sandbox_id.py b/packages/python-sdk/e2b/api/client/api/sandboxes/delete_sandboxes_sandbox_id.py index fa9f37750..974f2d687 100644 --- a/packages/python-sdk/e2b/api/client/api/sandboxes/delete_sandboxes_sandbox_id.py +++ b/packages/python-sdk/e2b/api/client/api/sandboxes/delete_sandboxes_sandbox_id.py @@ -19,7 +19,9 @@ def _get_kwargs( return _kwargs -def _parse_response(*, client: Union[AuthenticatedClient, Client], response: httpx.Response) -> Optional[Any]: +def _parse_response( + *, client: Union[AuthenticatedClient, Client], response: httpx.Response +) -> Optional[Any]: if response.status_code == HTTPStatus.NO_CONTENT: return None if response.status_code == HTTPStatus.NOT_FOUND: @@ -34,7 +36,9 @@ def _parse_response(*, client: Union[AuthenticatedClient, Client], response: htt return None -def _build_response(*, client: Union[AuthenticatedClient, Client], response: httpx.Response) -> Response[Any]: +def _build_response( + *, client: Union[AuthenticatedClient, Client], response: httpx.Response +) -> Response[Any]: return Response( status_code=HTTPStatus(response.status_code), content=response.content, diff --git a/packages/python-sdk/e2b/api/client/api/sandboxes/post_sandboxes_sandbox_id_refreshes.py b/packages/python-sdk/e2b/api/client/api/sandboxes/post_sandboxes_sandbox_id_refreshes.py index 163bcf177..39dfc678a 100644 --- a/packages/python-sdk/e2b/api/client/api/sandboxes/post_sandboxes_sandbox_id_refreshes.py +++ b/packages/python-sdk/e2b/api/client/api/sandboxes/post_sandboxes_sandbox_id_refreshes.py @@ -5,7 +5,9 @@ from ... import errors from ...client import AuthenticatedClient, Client -from ...models.post_sandboxes_sandbox_id_refreshes_body import PostSandboxesSandboxIDRefreshesBody +from ...models.post_sandboxes_sandbox_id_refreshes_body import ( + PostSandboxesSandboxIDRefreshesBody, +) from ...types import Response @@ -30,7 +32,9 @@ def _get_kwargs( return _kwargs -def _parse_response(*, client: Union[AuthenticatedClient, Client], response: httpx.Response) -> Optional[Any]: +def _parse_response( + *, client: Union[AuthenticatedClient, Client], response: httpx.Response +) -> Optional[Any]: if response.status_code == HTTPStatus.NO_CONTENT: return None if response.status_code == HTTPStatus.UNAUTHORIZED: @@ -43,7 +47,9 @@ def _parse_response(*, client: Union[AuthenticatedClient, Client], response: htt return None -def _build_response(*, client: Union[AuthenticatedClient, Client], response: httpx.Response) -> Response[Any]: +def _build_response( + *, client: Union[AuthenticatedClient, Client], response: httpx.Response +) -> Response[Any]: return Response( status_code=HTTPStatus(response.status_code), content=response.content, diff --git a/packages/python-sdk/e2b/api/client/api/sandboxes/post_sandboxes_sandbox_id_timeout.py b/packages/python-sdk/e2b/api/client/api/sandboxes/post_sandboxes_sandbox_id_timeout.py index 148d97c87..615963abf 100644 --- a/packages/python-sdk/e2b/api/client/api/sandboxes/post_sandboxes_sandbox_id_timeout.py +++ b/packages/python-sdk/e2b/api/client/api/sandboxes/post_sandboxes_sandbox_id_timeout.py @@ -5,7 +5,9 @@ from ... import errors from ...client import AuthenticatedClient, Client -from ...models.post_sandboxes_sandbox_id_timeout_body import PostSandboxesSandboxIDTimeoutBody +from ...models.post_sandboxes_sandbox_id_timeout_body import ( + PostSandboxesSandboxIDTimeoutBody, +) from ...types import Response @@ -30,7 +32,9 @@ def _get_kwargs( return _kwargs -def _parse_response(*, client: Union[AuthenticatedClient, Client], response: httpx.Response) -> Optional[Any]: +def _parse_response( + *, client: Union[AuthenticatedClient, Client], response: httpx.Response +) -> Optional[Any]: if response.status_code == HTTPStatus.NO_CONTENT: return None if response.status_code == HTTPStatus.UNAUTHORIZED: @@ -45,7 +49,9 @@ def _parse_response(*, client: Union[AuthenticatedClient, Client], response: htt return None -def _build_response(*, client: Union[AuthenticatedClient, Client], response: httpx.Response) -> Response[Any]: +def _build_response( + *, client: Union[AuthenticatedClient, Client], response: httpx.Response +) -> Response[Any]: return Response( status_code=HTTPStatus(response.status_code), content=response.content, diff --git a/packages/python-sdk/e2b/api/client/api/templates/delete_templates_template_id.py b/packages/python-sdk/e2b/api/client/api/templates/delete_templates_template_id.py index d493420f3..b7c162b27 100644 --- a/packages/python-sdk/e2b/api/client/api/templates/delete_templates_template_id.py +++ b/packages/python-sdk/e2b/api/client/api/templates/delete_templates_template_id.py @@ -19,7 +19,9 @@ def _get_kwargs( return _kwargs -def _parse_response(*, client: Union[AuthenticatedClient, Client], response: httpx.Response) -> Optional[Any]: +def _parse_response( + *, client: Union[AuthenticatedClient, Client], response: httpx.Response +) -> Optional[Any]: if response.status_code == HTTPStatus.NO_CONTENT: return None if response.status_code == HTTPStatus.UNAUTHORIZED: @@ -32,7 +34,9 @@ def _parse_response(*, client: Union[AuthenticatedClient, Client], response: htt return None -def _build_response(*, client: Union[AuthenticatedClient, Client], response: httpx.Response) -> Response[Any]: +def _build_response( + *, client: Union[AuthenticatedClient, Client], response: httpx.Response +) -> Response[Any]: return Response( status_code=HTTPStatus(response.status_code), content=response.content, diff --git a/packages/python-sdk/e2b/api/client/api/templates/post_templates_template_id_builds_build_id.py b/packages/python-sdk/e2b/api/client/api/templates/post_templates_template_id_builds_build_id.py index 67e21c739..754229875 100644 --- a/packages/python-sdk/e2b/api/client/api/templates/post_templates_template_id_builds_build_id.py +++ b/packages/python-sdk/e2b/api/client/api/templates/post_templates_template_id_builds_build_id.py @@ -20,7 +20,9 @@ def _get_kwargs( return _kwargs -def _parse_response(*, client: Union[AuthenticatedClient, Client], response: httpx.Response) -> Optional[Any]: +def _parse_response( + *, client: Union[AuthenticatedClient, Client], response: httpx.Response +) -> Optional[Any]: if response.status_code == HTTPStatus.ACCEPTED: return None if response.status_code == HTTPStatus.UNAUTHORIZED: @@ -33,7 +35,9 @@ def _parse_response(*, client: Union[AuthenticatedClient, Client], response: htt return None -def _build_response(*, client: Union[AuthenticatedClient, Client], response: httpx.Response) -> Response[Any]: +def _build_response( + *, client: Union[AuthenticatedClient, Client], response: httpx.Response +) -> Response[Any]: return Response( status_code=HTTPStatus(response.status_code), content=response.content, diff --git a/packages/python-sdk/e2b/api/client/client.py b/packages/python-sdk/e2b/api/client/client.py index 63a2493b9..38b07d057 100644 --- a/packages/python-sdk/e2b/api/client/client.py +++ b/packages/python-sdk/e2b/api/client/client.py @@ -38,9 +38,15 @@ class Client: _base_url: str = field(alias="base_url") _cookies: Dict[str, str] = field(factory=dict, kw_only=True, alias="cookies") _headers: Dict[str, str] = field(factory=dict, kw_only=True, alias="headers") - _timeout: Optional[httpx.Timeout] = field(default=None, kw_only=True, alias="timeout") - _verify_ssl: Union[str, bool, ssl.SSLContext] = field(default=True, kw_only=True, alias="verify_ssl") - _follow_redirects: bool = field(default=False, kw_only=True, alias="follow_redirects") + _timeout: Optional[httpx.Timeout] = field( + default=None, kw_only=True, alias="timeout" + ) + _verify_ssl: Union[str, bool, ssl.SSLContext] = field( + default=True, kw_only=True, alias="verify_ssl" + ) + _follow_redirects: bool = field( + default=False, kw_only=True, alias="follow_redirects" + ) _httpx_args: Dict[str, Any] = field(factory=dict, kw_only=True, alias="httpx_args") _client: Optional[httpx.Client] = field(default=None, init=False) _async_client: Optional[httpx.AsyncClient] = field(default=None, init=False) @@ -168,9 +174,15 @@ class AuthenticatedClient: _base_url: str = field(alias="base_url") _cookies: Dict[str, str] = field(factory=dict, kw_only=True, alias="cookies") _headers: Dict[str, str] = field(factory=dict, kw_only=True, alias="headers") - _timeout: Optional[httpx.Timeout] = field(default=None, kw_only=True, alias="timeout") - _verify_ssl: Union[str, bool, ssl.SSLContext] = field(default=True, kw_only=True, alias="verify_ssl") - _follow_redirects: bool = field(default=False, kw_only=True, alias="follow_redirects") + _timeout: Optional[httpx.Timeout] = field( + default=None, kw_only=True, alias="timeout" + ) + _verify_ssl: Union[str, bool, ssl.SSLContext] = field( + default=True, kw_only=True, alias="verify_ssl" + ) + _follow_redirects: bool = field( + default=False, kw_only=True, alias="follow_redirects" + ) _httpx_args: Dict[str, Any] = field(factory=dict, kw_only=True, alias="httpx_args") _client: Optional[httpx.Client] = field(default=None, init=False) _async_client: Optional[httpx.AsyncClient] = field(default=None, init=False) @@ -214,7 +226,9 @@ def set_httpx_client(self, client: httpx.Client) -> "AuthenticatedClient": def get_httpx_client(self) -> httpx.Client: """Get the underlying httpx.Client, constructing a new one if not previously set""" if self._client is None: - self._headers[self.auth_header_name] = f"{self.prefix} {self.token}" if self.prefix else self.token + self._headers[self.auth_header_name] = ( + f"{self.prefix} {self.token}" if self.prefix else self.token + ) self._client = httpx.Client( base_url=self._base_url, cookies=self._cookies, @@ -235,7 +249,9 @@ def __exit__(self, *args: Any, **kwargs: Any) -> None: """Exit a context manager for internal httpx.Client (see httpx docs)""" self.get_httpx_client().__exit__(*args, **kwargs) - def set_async_httpx_client(self, async_client: httpx.AsyncClient) -> "AuthenticatedClient": + def set_async_httpx_client( + self, async_client: httpx.AsyncClient + ) -> "AuthenticatedClient": """Manually the underlying httpx.AsyncClient **NOTE**: This will override any other settings on the client, including cookies, headers, and timeout. @@ -246,7 +262,9 @@ def set_async_httpx_client(self, async_client: httpx.AsyncClient) -> "Authentica def get_async_httpx_client(self) -> httpx.AsyncClient: """Get the underlying httpx.AsyncClient, constructing a new one if not previously set""" if self._async_client is None: - self._headers[self.auth_header_name] = f"{self.prefix} {self.token}" if self.prefix else self.token + self._headers[self.auth_header_name] = ( + f"{self.prefix} {self.token}" if self.prefix else self.token + ) self._async_client = httpx.AsyncClient( base_url=self._base_url, cookies=self._cookies, diff --git a/packages/python-sdk/e2b/api/client/models/__init__.py b/packages/python-sdk/e2b/api/client/models/__init__.py index d55b958c2..85275a6de 100644 --- a/packages/python-sdk/e2b/api/client/models/__init__.py +++ b/packages/python-sdk/e2b/api/client/models/__init__.py @@ -2,7 +2,9 @@ from .error import Error from .new_sandbox import NewSandbox -from .post_sandboxes_sandbox_id_refreshes_body import PostSandboxesSandboxIDRefreshesBody +from .post_sandboxes_sandbox_id_refreshes_body import ( + PostSandboxesSandboxIDRefreshesBody, +) from .post_sandboxes_sandbox_id_timeout_body import PostSandboxesSandboxIDTimeoutBody from .running_sandbox import RunningSandbox from .sandbox import Sandbox diff --git a/packages/python-sdk/e2b/envd/filesystem/filesystem_pb2.py b/packages/python-sdk/e2b/envd/filesystem/filesystem_pb2.py index 27c58dac8..8091e0048 100644 --- a/packages/python-sdk/e2b/envd/filesystem/filesystem_pb2.py +++ b/packages/python-sdk/e2b/envd/filesystem/filesystem_pb2.py @@ -1,7 +1,8 @@ # -*- coding: utf-8 -*- # Generated by the protocol buffer compiler. DO NOT EDIT! +# NO CHECKED-IN PROTOBUF GENCODE # source: filesystem/filesystem.proto -# Protobuf Python Version: 5.27.3 +# Protobuf Python Version: 5.29.0 """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool @@ -14,7 +15,7 @@ DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile( - b'\n\x1b\x66ilesystem/filesystem.proto\x12\nfilesystem"G\n\x0bMoveRequest\x12\x16\n\x06source\x18\x01 \x01(\tR\x06source\x12 \n\x0b\x64\x65stination\x18\x02 \x01(\tR\x0b\x64\x65stination";\n\x0cMoveResponse\x12+\n\x05\x65ntry\x18\x01 \x01(\x0b\x32\x15.filesystem.EntryInfoR\x05\x65ntry"$\n\x0eMakeDirRequest\x12\x12\n\x04path\x18\x01 \x01(\tR\x04path">\n\x0fMakeDirResponse\x12+\n\x05\x65ntry\x18\x01 \x01(\x0b\x32\x15.filesystem.EntryInfoR\x05\x65ntry"#\n\rRemoveRequest\x12\x12\n\x04path\x18\x01 \x01(\tR\x04path"\x10\n\x0eRemoveResponse"!\n\x0bStatRequest\x12\x12\n\x04path\x18\x01 \x01(\tR\x04path";\n\x0cStatResponse\x12+\n\x05\x65ntry\x18\x01 \x01(\x0b\x32\x15.filesystem.EntryInfoR\x05\x65ntry"]\n\tEntryInfo\x12\x12\n\x04name\x18\x01 \x01(\tR\x04name\x12(\n\x04type\x18\x02 \x01(\x0e\x32\x14.filesystem.FileTypeR\x04type\x12\x12\n\x04path\x18\x03 \x01(\tR\x04path"$\n\x0eListDirRequest\x12\x12\n\x04path\x18\x01 \x01(\tR\x04path"B\n\x0fListDirResponse\x12/\n\x07\x65ntries\x18\x01 \x03(\x0b\x32\x15.filesystem.EntryInfoR\x07\x65ntries"%\n\x0fWatchDirRequest\x12\x12\n\x04path\x18\x01 \x01(\tR\x04path"P\n\x0f\x46ilesystemEvent\x12\x12\n\x04name\x18\x01 \x01(\tR\x04name\x12)\n\x04type\x18\x02 \x01(\x0e\x32\x15.filesystem.EventTypeR\x04type"\xfe\x01\n\x10WatchDirResponse\x12?\n\x05start\x18\x01 \x01(\x0b\x32\'.filesystem.WatchDirResponse.StartEventH\x00R\x05start\x12=\n\nfilesystem\x18\x02 \x01(\x0b\x32\x1b.filesystem.FilesystemEventH\x00R\nfilesystem\x12\x46\n\tkeepalive\x18\x03 \x01(\x0b\x32&.filesystem.WatchDirResponse.KeepAliveH\x00R\tkeepalive\x1a\x0c\n\nStartEvent\x1a\x0b\n\tKeepAliveB\x07\n\x05\x65vent"*\n\x14\x43reateWatcherRequest\x12\x12\n\x04path\x18\x01 \x01(\tR\x04path"6\n\x15\x43reateWatcherResponse\x12\x1d\n\nwatcher_id\x18\x01 \x01(\tR\twatcherId"8\n\x17GetWatcherEventsRequest\x12\x1d\n\nwatcher_id\x18\x01 \x01(\tR\twatcherId"O\n\x18GetWatcherEventsResponse\x12\x33\n\x06\x65vents\x18\x01 \x03(\x0b\x32\x1b.filesystem.FilesystemEventR\x06\x65vents"5\n\x14RemoveWatcherRequest\x12\x1d\n\nwatcher_id\x18\x01 \x01(\tR\twatcherId"\x17\n\x15RemoveWatcherResponse*R\n\x08\x46ileType\x12\x19\n\x15\x46ILE_TYPE_UNSPECIFIED\x10\x00\x12\x12\n\x0e\x46ILE_TYPE_FILE\x10\x01\x12\x17\n\x13\x46ILE_TYPE_DIRECTORY\x10\x02*\x98\x01\n\tEventType\x12\x1a\n\x16\x45VENT_TYPE_UNSPECIFIED\x10\x00\x12\x15\n\x11\x45VENT_TYPE_CREATE\x10\x01\x12\x14\n\x10\x45VENT_TYPE_WRITE\x10\x02\x12\x15\n\x11\x45VENT_TYPE_REMOVE\x10\x03\x12\x15\n\x11\x45VENT_TYPE_RENAME\x10\x04\x12\x14\n\x10\x45VENT_TYPE_CHMOD\x10\x05\x32\x9f\x05\n\nFilesystem\x12\x39\n\x04Stat\x12\x17.filesystem.StatRequest\x1a\x18.filesystem.StatResponse\x12\x42\n\x07MakeDir\x12\x1a.filesystem.MakeDirRequest\x1a\x1b.filesystem.MakeDirResponse\x12\x39\n\x04Move\x12\x17.filesystem.MoveRequest\x1a\x18.filesystem.MoveResponse\x12\x42\n\x07ListDir\x12\x1a.filesystem.ListDirRequest\x1a\x1b.filesystem.ListDirResponse\x12?\n\x06Remove\x12\x19.filesystem.RemoveRequest\x1a\x1a.filesystem.RemoveResponse\x12G\n\x08WatchDir\x12\x1b.filesystem.WatchDirRequest\x1a\x1c.filesystem.WatchDirResponse0\x01\x12T\n\rCreateWatcher\x12 .filesystem.CreateWatcherRequest\x1a!.filesystem.CreateWatcherResponse\x12]\n\x10GetWatcherEvents\x12#.filesystem.GetWatcherEventsRequest\x1a$.filesystem.GetWatcherEventsResponse\x12T\n\rRemoveWatcher\x12 .filesystem.RemoveWatcherRequest\x1a!.filesystem.RemoveWatcherResponseBi\n\x0e\x63om.filesystemB\x0f\x46ilesystemProtoP\x01\xa2\x02\x03\x46XX\xaa\x02\nFilesystem\xca\x02\nFilesystem\xe2\x02\x16\x46ilesystem\\GPBMetadata\xea\x02\nFilesystemb\x06proto3' + b'\n\x1b\x66ilesystem/filesystem.proto\x12\nfilesystem"G\n\x0bMoveRequest\x12\x16\n\x06source\x18\x01 \x01(\tR\x06source\x12 \n\x0b\x64\x65stination\x18\x02 \x01(\tR\x0b\x64\x65stination";\n\x0cMoveResponse\x12+\n\x05\x65ntry\x18\x01 \x01(\x0b\x32\x15.filesystem.EntryInfoR\x05\x65ntry"$\n\x0eMakeDirRequest\x12\x12\n\x04path\x18\x01 \x01(\tR\x04path">\n\x0fMakeDirResponse\x12+\n\x05\x65ntry\x18\x01 \x01(\x0b\x32\x15.filesystem.EntryInfoR\x05\x65ntry"#\n\rRemoveRequest\x12\x12\n\x04path\x18\x01 \x01(\tR\x04path"\x10\n\x0eRemoveResponse"!\n\x0bStatRequest\x12\x12\n\x04path\x18\x01 \x01(\tR\x04path";\n\x0cStatResponse\x12+\n\x05\x65ntry\x18\x01 \x01(\x0b\x32\x15.filesystem.EntryInfoR\x05\x65ntry"]\n\tEntryInfo\x12\x12\n\x04name\x18\x01 \x01(\tR\x04name\x12(\n\x04type\x18\x02 \x01(\x0e\x32\x14.filesystem.FileTypeR\x04type\x12\x12\n\x04path\x18\x03 \x01(\tR\x04path"$\n\x0eListDirRequest\x12\x12\n\x04path\x18\x01 \x01(\tR\x04path"B\n\x0fListDirResponse\x12/\n\x07\x65ntries\x18\x01 \x03(\x0b\x32\x15.filesystem.EntryInfoR\x07\x65ntries"C\n\x0fWatchDirRequest\x12\x12\n\x04path\x18\x01 \x01(\tR\x04path\x12\x1c\n\trecursive\x18\x02 \x01(\x08R\trecursive"P\n\x0f\x46ilesystemEvent\x12\x12\n\x04name\x18\x01 \x01(\tR\x04name\x12)\n\x04type\x18\x02 \x01(\x0e\x32\x15.filesystem.EventTypeR\x04type"\xfe\x01\n\x10WatchDirResponse\x12?\n\x05start\x18\x01 \x01(\x0b\x32\'.filesystem.WatchDirResponse.StartEventH\x00R\x05start\x12=\n\nfilesystem\x18\x02 \x01(\x0b\x32\x1b.filesystem.FilesystemEventH\x00R\nfilesystem\x12\x46\n\tkeepalive\x18\x03 \x01(\x0b\x32&.filesystem.WatchDirResponse.KeepAliveH\x00R\tkeepalive\x1a\x0c\n\nStartEvent\x1a\x0b\n\tKeepAliveB\x07\n\x05\x65vent"H\n\x14\x43reateWatcherRequest\x12\x12\n\x04path\x18\x01 \x01(\tR\x04path\x12\x1c\n\trecursive\x18\x02 \x01(\x08R\trecursive"6\n\x15\x43reateWatcherResponse\x12\x1d\n\nwatcher_id\x18\x01 \x01(\tR\twatcherId"8\n\x17GetWatcherEventsRequest\x12\x1d\n\nwatcher_id\x18\x01 \x01(\tR\twatcherId"O\n\x18GetWatcherEventsResponse\x12\x33\n\x06\x65vents\x18\x01 \x03(\x0b\x32\x1b.filesystem.FilesystemEventR\x06\x65vents"5\n\x14RemoveWatcherRequest\x12\x1d\n\nwatcher_id\x18\x01 \x01(\tR\twatcherId"\x17\n\x15RemoveWatcherResponse*R\n\x08\x46ileType\x12\x19\n\x15\x46ILE_TYPE_UNSPECIFIED\x10\x00\x12\x12\n\x0e\x46ILE_TYPE_FILE\x10\x01\x12\x17\n\x13\x46ILE_TYPE_DIRECTORY\x10\x02*\x98\x01\n\tEventType\x12\x1a\n\x16\x45VENT_TYPE_UNSPECIFIED\x10\x00\x12\x15\n\x11\x45VENT_TYPE_CREATE\x10\x01\x12\x14\n\x10\x45VENT_TYPE_WRITE\x10\x02\x12\x15\n\x11\x45VENT_TYPE_REMOVE\x10\x03\x12\x15\n\x11\x45VENT_TYPE_RENAME\x10\x04\x12\x14\n\x10\x45VENT_TYPE_CHMOD\x10\x05\x32\x9f\x05\n\nFilesystem\x12\x39\n\x04Stat\x12\x17.filesystem.StatRequest\x1a\x18.filesystem.StatResponse\x12\x42\n\x07MakeDir\x12\x1a.filesystem.MakeDirRequest\x1a\x1b.filesystem.MakeDirResponse\x12\x39\n\x04Move\x12\x17.filesystem.MoveRequest\x1a\x18.filesystem.MoveResponse\x12\x42\n\x07ListDir\x12\x1a.filesystem.ListDirRequest\x1a\x1b.filesystem.ListDirResponse\x12?\n\x06Remove\x12\x19.filesystem.RemoveRequest\x1a\x1a.filesystem.RemoveResponse\x12G\n\x08WatchDir\x12\x1b.filesystem.WatchDirRequest\x1a\x1c.filesystem.WatchDirResponse0\x01\x12T\n\rCreateWatcher\x12 .filesystem.CreateWatcherRequest\x1a!.filesystem.CreateWatcherResponse\x12]\n\x10GetWatcherEvents\x12#.filesystem.GetWatcherEventsRequest\x1a$.filesystem.GetWatcherEventsResponse\x12T\n\rRemoveWatcher\x12 .filesystem.RemoveWatcherRequest\x1a!.filesystem.RemoveWatcherResponseBi\n\x0e\x63om.filesystemB\x0f\x46ilesystemProtoP\x01\xa2\x02\x03\x46XX\xaa\x02\nFilesystem\xca\x02\nFilesystem\xe2\x02\x16\x46ilesystem\\GPBMetadata\xea\x02\nFilesystemb\x06proto3' ) _globals = globals() @@ -24,13 +25,13 @@ ) if not _descriptor._USE_C_DESCRIPTORS: _globals["DESCRIPTOR"]._loaded_options = None - _globals["DESCRIPTOR"]._serialized_options = ( - b"\n\016com.filesystemB\017FilesystemProtoP\001\242\002\003FXX\252\002\nFilesystem\312\002\nFilesystem\342\002\026Filesystem\\GPBMetadata\352\002\nFilesystem" - ) - _globals["_FILETYPE"]._serialized_start = 1328 - _globals["_FILETYPE"]._serialized_end = 1410 - _globals["_EVENTTYPE"]._serialized_start = 1413 - _globals["_EVENTTYPE"]._serialized_end = 1565 + _globals[ + "DESCRIPTOR" + ]._serialized_options = b"\n\016com.filesystemB\017FilesystemProtoP\001\242\002\003FXX\252\002\nFilesystem\312\002\nFilesystem\342\002\026Filesystem\\GPBMetadata\352\002\nFilesystem" + _globals["_FILETYPE"]._serialized_start = 1388 + _globals["_FILETYPE"]._serialized_end = 1470 + _globals["_EVENTTYPE"]._serialized_start = 1473 + _globals["_EVENTTYPE"]._serialized_end = 1625 _globals["_MOVEREQUEST"]._serialized_start = 43 _globals["_MOVEREQUEST"]._serialized_end = 114 _globals["_MOVERESPONSE"]._serialized_start = 116 @@ -54,27 +55,27 @@ _globals["_LISTDIRRESPONSE"]._serialized_start = 563 _globals["_LISTDIRRESPONSE"]._serialized_end = 629 _globals["_WATCHDIRREQUEST"]._serialized_start = 631 - _globals["_WATCHDIRREQUEST"]._serialized_end = 668 - _globals["_FILESYSTEMEVENT"]._serialized_start = 670 - _globals["_FILESYSTEMEVENT"]._serialized_end = 750 - _globals["_WATCHDIRRESPONSE"]._serialized_start = 753 - _globals["_WATCHDIRRESPONSE"]._serialized_end = 1007 - _globals["_WATCHDIRRESPONSE_STARTEVENT"]._serialized_start = 973 - _globals["_WATCHDIRRESPONSE_STARTEVENT"]._serialized_end = 985 - _globals["_WATCHDIRRESPONSE_KEEPALIVE"]._serialized_start = 987 - _globals["_WATCHDIRRESPONSE_KEEPALIVE"]._serialized_end = 998 - _globals["_CREATEWATCHERREQUEST"]._serialized_start = 1009 - _globals["_CREATEWATCHERREQUEST"]._serialized_end = 1051 - _globals["_CREATEWATCHERRESPONSE"]._serialized_start = 1053 - _globals["_CREATEWATCHERRESPONSE"]._serialized_end = 1107 - _globals["_GETWATCHEREVENTSREQUEST"]._serialized_start = 1109 - _globals["_GETWATCHEREVENTSREQUEST"]._serialized_end = 1165 - _globals["_GETWATCHEREVENTSRESPONSE"]._serialized_start = 1167 - _globals["_GETWATCHEREVENTSRESPONSE"]._serialized_end = 1246 - _globals["_REMOVEWATCHERREQUEST"]._serialized_start = 1248 - _globals["_REMOVEWATCHERREQUEST"]._serialized_end = 1301 - _globals["_REMOVEWATCHERRESPONSE"]._serialized_start = 1303 - _globals["_REMOVEWATCHERRESPONSE"]._serialized_end = 1326 - _globals["_FILESYSTEM"]._serialized_start = 1568 - _globals["_FILESYSTEM"]._serialized_end = 2239 + _globals["_WATCHDIRREQUEST"]._serialized_end = 698 + _globals["_FILESYSTEMEVENT"]._serialized_start = 700 + _globals["_FILESYSTEMEVENT"]._serialized_end = 780 + _globals["_WATCHDIRRESPONSE"]._serialized_start = 783 + _globals["_WATCHDIRRESPONSE"]._serialized_end = 1037 + _globals["_WATCHDIRRESPONSE_STARTEVENT"]._serialized_start = 1003 + _globals["_WATCHDIRRESPONSE_STARTEVENT"]._serialized_end = 1015 + _globals["_WATCHDIRRESPONSE_KEEPALIVE"]._serialized_start = 1017 + _globals["_WATCHDIRRESPONSE_KEEPALIVE"]._serialized_end = 1028 + _globals["_CREATEWATCHERREQUEST"]._serialized_start = 1039 + _globals["_CREATEWATCHERREQUEST"]._serialized_end = 1111 + _globals["_CREATEWATCHERRESPONSE"]._serialized_start = 1113 + _globals["_CREATEWATCHERRESPONSE"]._serialized_end = 1167 + _globals["_GETWATCHEREVENTSREQUEST"]._serialized_start = 1169 + _globals["_GETWATCHEREVENTSREQUEST"]._serialized_end = 1225 + _globals["_GETWATCHEREVENTSRESPONSE"]._serialized_start = 1227 + _globals["_GETWATCHEREVENTSRESPONSE"]._serialized_end = 1306 + _globals["_REMOVEWATCHERREQUEST"]._serialized_start = 1308 + _globals["_REMOVEWATCHERREQUEST"]._serialized_end = 1361 + _globals["_REMOVEWATCHERRESPONSE"]._serialized_start = 1363 + _globals["_REMOVEWATCHERRESPONSE"]._serialized_end = 1386 + _globals["_FILESYSTEM"]._serialized_start = 1628 + _globals["_FILESYSTEM"]._serialized_end = 2299 # @@protoc_insertion_point(module_scope) diff --git a/packages/python-sdk/e2b/envd/filesystem/filesystem_pb2.pyi b/packages/python-sdk/e2b/envd/filesystem/filesystem_pb2.pyi index a2208c060..c031b13ed 100644 --- a/packages/python-sdk/e2b/envd/filesystem/filesystem_pb2.pyi +++ b/packages/python-sdk/e2b/envd/filesystem/filesystem_pb2.pyi @@ -117,10 +117,12 @@ class ListDirResponse(_message.Message): ) -> None: ... class WatchDirRequest(_message.Message): - __slots__ = ("path",) + __slots__ = ("path", "recursive") PATH_FIELD_NUMBER: _ClassVar[int] + RECURSIVE_FIELD_NUMBER: _ClassVar[int] path: str - def __init__(self, path: _Optional[str] = ...) -> None: ... + recursive: bool + def __init__(self, path: _Optional[str] = ..., recursive: bool = ...) -> None: ... class FilesystemEvent(_message.Message): __slots__ = ("name", "type") @@ -156,10 +158,12 @@ class WatchDirResponse(_message.Message): ) -> None: ... class CreateWatcherRequest(_message.Message): - __slots__ = ("path",) + __slots__ = ("path", "recursive") PATH_FIELD_NUMBER: _ClassVar[int] + RECURSIVE_FIELD_NUMBER: _ClassVar[int] path: str - def __init__(self, path: _Optional[str] = ...) -> None: ... + recursive: bool + def __init__(self, path: _Optional[str] = ..., recursive: bool = ...) -> None: ... class CreateWatcherResponse(_message.Message): __slots__ = ("watcher_id",) diff --git a/packages/python-sdk/e2b/envd/process/process_pb2.py b/packages/python-sdk/e2b/envd/process/process_pb2.py index 1674f451a..7bdd06c5c 100644 --- a/packages/python-sdk/e2b/envd/process/process_pb2.py +++ b/packages/python-sdk/e2b/envd/process/process_pb2.py @@ -1,7 +1,8 @@ # -*- coding: utf-8 -*- # Generated by the protocol buffer compiler. DO NOT EDIT! +# NO CHECKED-IN PROTOBUF GENCODE # source: process/process.proto -# Protobuf Python Version: 5.27.3 +# Protobuf Python Version: 5.29.0 """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool @@ -22,9 +23,9 @@ _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, "process.process_pb2", _globals) if not _descriptor._USE_C_DESCRIPTORS: _globals["DESCRIPTOR"]._loaded_options = None - _globals["DESCRIPTOR"]._serialized_options = ( - b"\n\013com.processB\014ProcessProtoP\001\242\002\003PXX\252\002\007Process\312\002\007Process\342\002\023Process\\GPBMetadata\352\002\007Process" - ) + _globals[ + "DESCRIPTOR" + ]._serialized_options = b"\n\013com.processB\014ProcessProtoP\001\242\002\003PXX\252\002\007Process\312\002\007Process\342\002\023Process\\GPBMetadata\352\002\007Process" _globals["_PROCESSCONFIG_ENVSENTRY"]._loaded_options = None _globals["_PROCESSCONFIG_ENVSENTRY"]._serialized_options = b"8\001" _globals["_SIGNAL"]._serialized_start = 2316 diff --git a/packages/python-sdk/e2b/sandbox/main.py b/packages/python-sdk/e2b/sandbox/main.py index a87cbe07e..539a0ae7f 100644 --- a/packages/python-sdk/e2b/sandbox/main.py +++ b/packages/python-sdk/e2b/sandbox/main.py @@ -22,15 +22,18 @@ class SandboxSetup(ABC): @property @abstractmethod - def connection_config(self) -> ConnectionConfig: ... + def connection_config(self) -> ConnectionConfig: + ... @property @abstractmethod - def envd_api_url(self) -> str: ... + def envd_api_url(self) -> str: + ... @property @abstractmethod - def sandbox_id(self) -> str: ... + def sandbox_id(self) -> str: + ... def _file_url(self, path: Optional[str] = None) -> str: url = urllib.parse.urljoin(self.envd_api_url, ENVD_API_FILES_ROUTE) From aaf7d4df41af53a249ba8d22d198baefbc96a818 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Dobr=C3=BD?= Date: Mon, 2 Dec 2024 21:09:54 +0100 Subject: [PATCH 04/15] Improve watchDir jsSDK default value --- packages/js-sdk/src/sandbox/filesystem/index.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/js-sdk/src/sandbox/filesystem/index.ts b/packages/js-sdk/src/sandbox/filesystem/index.ts index ccba155bc..89f7c34ef 100644 --- a/packages/js-sdk/src/sandbox/filesystem/index.ts +++ b/packages/js-sdk/src/sandbox/filesystem/index.ts @@ -103,6 +103,7 @@ export class Filesystem { private readonly rpc: Client private readonly defaultWatchTimeout = 60_000 // 60 seconds + private readonly defaultWatchRecursive = false constructor( transport: Transport, @@ -452,7 +453,7 @@ export class Filesystem { const events = this.rpc.watchDir( { path, - recursive: opts?.recursive ?? false, + recursive: opts?.recursive ?? this.defaultWatchRecursive, }, { headers: { From 981c024a8b4e5bfbdda7477ff906ebcf29fc52a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Dobr=C3=BD?= Date: Mon, 2 Dec 2024 21:28:50 +0100 Subject: [PATCH 05/15] Add recursive directory watching to the web documentation --- .../app/(docs)/docs/filesystem/watch/page.mdx | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/apps/web/src/app/(docs)/docs/filesystem/watch/page.mdx b/apps/web/src/app/(docs)/docs/filesystem/watch/page.mdx index 48bd3e346..86fb26f92 100644 --- a/apps/web/src/app/(docs)/docs/filesystem/watch/page.mdx +++ b/apps/web/src/app/(docs)/docs/filesystem/watch/page.mdx @@ -39,3 +39,49 @@ for event in events: # $HighlightLine print(f"wrote to file {event.name}") # $HighlightLine ``` + + +## Recursive Watching + +You can enable recursive watching using the parameter `recursive`. + + + +```js +import { Sandbox, FilesystemEventType } from '@e2b/code-interpreter' + +const sandbox = await Sandbox.create() +const dirname = '/home/user' + +// Start watching directory for changes +const handle = await sandbox.files.watchDir(dirname, async (event) => { + console.log(event) + if (event.type === FilesystemEventType.WRITE) { + console.log(`wrote to file ${event.name}`) + } +}, { + recursive: true // $HighlightLine +}) + +// Trigger file write event +await sandbox.files.write(`${dirname}/my-folder/my-file`, 'hello') +``` +```python +from e2b_code_interpreter import Sandbox + +sandbox = Sandbox() +dirname = '/home/user' + +# Watch directory for changes +handle = sandbox.files.watch_dir(dirname, recursive=True) # $HighlightLine +# Trigger file write event +sandbox.files.write(f"{dirname}/my-folder/my-file", "hello") + +# Retrieve the latest new events since the last `get_new_events()` call +events = handle.get_new_events() +for event in events: + print(event) + if event.type == FilesystemEventType.Write: + print(f"wrote to file {event.name}") +``` + \ No newline at end of file From 378e6331cefa7dfdbf1ba1440f7554ac0a0e9ae7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Dobr=C3=BD?= Date: Mon, 2 Dec 2024 21:38:51 +0100 Subject: [PATCH 06/15] Update documentation highlights --- apps/web/src/app/(docs)/docs/filesystem/watch/page.mdx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/web/src/app/(docs)/docs/filesystem/watch/page.mdx b/apps/web/src/app/(docs)/docs/filesystem/watch/page.mdx index 86fb26f92..eaec2496e 100644 --- a/apps/web/src/app/(docs)/docs/filesystem/watch/page.mdx +++ b/apps/web/src/app/(docs)/docs/filesystem/watch/page.mdx @@ -64,7 +64,7 @@ const handle = await sandbox.files.watchDir(dirname, async (event) => { }) // Trigger file write event -await sandbox.files.write(`${dirname}/my-folder/my-file`, 'hello') +await sandbox.files.write(`${dirname}/my-folder/my-file`, 'hello') // $HighlightLine ``` ```python from e2b_code_interpreter import Sandbox @@ -75,7 +75,7 @@ dirname = '/home/user' # Watch directory for changes handle = sandbox.files.watch_dir(dirname, recursive=True) # $HighlightLine # Trigger file write event -sandbox.files.write(f"{dirname}/my-folder/my-file", "hello") +sandbox.files.write(f"{dirname}/my-folder/my-file", "hello") # $HighlightLine # Retrieve the latest new events since the last `get_new_events()` call events = handle.get_new_events() From 5fbb4de48267039234741838abde4a46295c4e4c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Dobr=C3=BD?= Date: Mon, 2 Dec 2024 22:02:01 +0100 Subject: [PATCH 07/15] Improve recursive watch tests and folders naming --- packages/js-sdk/tests/sandbox/files/watch.test.ts | 6 +++--- .../tests/async/sandbox_async/files/test_watch.py | 6 +++--- .../python-sdk/tests/sync/sandbox_sync/files/test_watch.py | 6 +++--- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/packages/js-sdk/tests/sandbox/files/watch.test.ts b/packages/js-sdk/tests/sandbox/files/watch.test.ts index 9a4127061..3a4b17da8 100644 --- a/packages/js-sdk/tests/sandbox/files/watch.test.ts +++ b/packages/js-sdk/tests/sandbox/files/watch.test.ts @@ -32,7 +32,7 @@ sandboxTest('watch directory changes', async ({ sandbox }) => { }) sandboxTest('watch recursive directory changes', async ({ sandbox }) => { - const dirname = 'test_watch_dir' + const dirname = 'test_recursive_watch_dir' const nestedDirname = 'test_nested_watch_dir' const filename = 'test_watch.txt' const content = 'This file will be watched.' @@ -64,8 +64,8 @@ sandboxTest('watch recursive directory changes', async ({ sandbox }) => { await handle.stop() }) -sandboxTest('watch recursive directory folder addition', async ({ sandbox }) => { - const dirname = 'test_watch_dir' +sandboxTest('watch recursive directory after nested folder addition', async ({ sandbox }) => { + const dirname = 'test_recursive_watch_dir_add' const nestedDirname = 'test_nested_watch_dir' const filename = 'test_watch.txt' const content = 'This file will be watched.' diff --git a/packages/python-sdk/tests/async/sandbox_async/files/test_watch.py b/packages/python-sdk/tests/async/sandbox_async/files/test_watch.py index 7973fc03a..5e21f823c 100644 --- a/packages/python-sdk/tests/async/sandbox_async/files/test_watch.py +++ b/packages/python-sdk/tests/async/sandbox_async/files/test_watch.py @@ -36,7 +36,7 @@ def handle_event(e: FilesystemEvent): async def test_watch_recursive_directory_changes(async_sandbox: AsyncSandbox): - dirname = "test_watch_dir" + dirname = "test_recursive_watch_dir" nested_dirname = "test_nested_watch_dir" filename = "test_watch.txt" content = "This file will be watched." @@ -63,8 +63,8 @@ def handle_event(e: FilesystemEvent): await handle.stop() -async def test_watch_recursive_directory_folder_addition(async_sandbox: AsyncSandbox): - dirname = "test_watch_dir" +async def test_watch_recursive_directory_after_nested_folder_addition(async_sandbox: AsyncSandbox): + dirname = "test_recursive_watch_dir_add" nested_dirname = "test_nested_watch_dir" filename = "test_watch.txt" content = "This file will be watched." diff --git a/packages/python-sdk/tests/sync/sandbox_sync/files/test_watch.py b/packages/python-sdk/tests/sync/sandbox_sync/files/test_watch.py index 6c701ef89..779db6ab7 100644 --- a/packages/python-sdk/tests/sync/sandbox_sync/files/test_watch.py +++ b/packages/python-sdk/tests/sync/sandbox_sync/files/test_watch.py @@ -49,7 +49,7 @@ def test_watch_iterated(sandbox: Sandbox): def test_watch_recursive_directory_changes(sandbox: Sandbox): - dirname = "test_watch_dir" + dirname = "test_recursive_watch_dir" nested_dirname = "test_nested_watch_dir" filename = "test_watch.txt" content = "This file will be watched." @@ -69,8 +69,8 @@ def test_watch_recursive_directory_changes(sandbox: Sandbox): handle.stop() -def test_watch_recursive_directory_folder_addition(sandbox: Sandbox): - dirname = "test_watch_dir" +def test_watch_recursive_directory_after_nested_folder_addition(sandbox: Sandbox): + dirname = "test_recursive_watch_dir_add" nested_dirname = "test_nested_watch_dir" filename = "test_watch.txt" content = "This file will be watched." From 262d80ee685173488f002a648713abbf959cf098 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Dobr=C3=BD?= Date: Tue, 3 Dec 2024 00:05:37 +0100 Subject: [PATCH 08/15] Regenerated python stubs with version 5.28.3 - Removed code runtime version check to prevent issues until further investigation --- packages/python-sdk/e2b/envd/filesystem/filesystem_pb2.py | 2 +- packages/python-sdk/e2b/envd/process/process_pb2.py | 2 +- .../python-sdk/tests/async/sandbox_async/files/test_watch.py | 4 +++- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/python-sdk/e2b/envd/filesystem/filesystem_pb2.py b/packages/python-sdk/e2b/envd/filesystem/filesystem_pb2.py index 8091e0048..626367ae3 100644 --- a/packages/python-sdk/e2b/envd/filesystem/filesystem_pb2.py +++ b/packages/python-sdk/e2b/envd/filesystem/filesystem_pb2.py @@ -2,7 +2,7 @@ # Generated by the protocol buffer compiler. DO NOT EDIT! # NO CHECKED-IN PROTOBUF GENCODE # source: filesystem/filesystem.proto -# Protobuf Python Version: 5.29.0 +# Protobuf Python Version: 5.28.3 """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool diff --git a/packages/python-sdk/e2b/envd/process/process_pb2.py b/packages/python-sdk/e2b/envd/process/process_pb2.py index 7bdd06c5c..4ef9098da 100644 --- a/packages/python-sdk/e2b/envd/process/process_pb2.py +++ b/packages/python-sdk/e2b/envd/process/process_pb2.py @@ -2,7 +2,7 @@ # Generated by the protocol buffer compiler. DO NOT EDIT! # NO CHECKED-IN PROTOBUF GENCODE # source: process/process.proto -# Protobuf Python Version: 5.29.0 +# Protobuf Python Version: 5.28.3 """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool diff --git a/packages/python-sdk/tests/async/sandbox_async/files/test_watch.py b/packages/python-sdk/tests/async/sandbox_async/files/test_watch.py index 5e21f823c..abb457bda 100644 --- a/packages/python-sdk/tests/async/sandbox_async/files/test_watch.py +++ b/packages/python-sdk/tests/async/sandbox_async/files/test_watch.py @@ -63,7 +63,9 @@ def handle_event(e: FilesystemEvent): await handle.stop() -async def test_watch_recursive_directory_after_nested_folder_addition(async_sandbox: AsyncSandbox): +async def test_watch_recursive_directory_after_nested_folder_addition( + async_sandbox: AsyncSandbox, +): dirname = "test_recursive_watch_dir_add" nested_dirname = "test_nested_watch_dir" filename = "test_watch.txt" From f84748398e577b25f43aef114a846334027d6f80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Dobr=C3=BD?= Date: Tue, 3 Dec 2024 09:19:00 +0100 Subject: [PATCH 09/15] Test for recursive folder creation, add newline after code to docs --- .../app/(docs)/docs/filesystem/watch/page.mdx | 2 +- .../js-sdk/tests/sandbox/files/watch.test.ts | 19 +++++++++++++------ .../async/sandbox_async/files/test_watch.py | 13 +++++++++---- .../sync/sandbox_sync/files/test_watch.py | 6 +++++- 4 files changed, 28 insertions(+), 12 deletions(-) diff --git a/apps/web/src/app/(docs)/docs/filesystem/watch/page.mdx b/apps/web/src/app/(docs)/docs/filesystem/watch/page.mdx index eaec2496e..7dce8d5e1 100644 --- a/apps/web/src/app/(docs)/docs/filesystem/watch/page.mdx +++ b/apps/web/src/app/(docs)/docs/filesystem/watch/page.mdx @@ -84,4 +84,4 @@ for event in events: if event.type == FilesystemEventType.Write: print(f"wrote to file {event.name}") ``` - \ No newline at end of file + diff --git a/packages/js-sdk/tests/sandbox/files/watch.test.ts b/packages/js-sdk/tests/sandbox/files/watch.test.ts index 3a4b17da8..83fe3743b 100644 --- a/packages/js-sdk/tests/sandbox/files/watch.test.ts +++ b/packages/js-sdk/tests/sandbox/files/watch.test.ts @@ -73,25 +73,32 @@ sandboxTest('watch recursive directory after nested folder addition', async ({ s await sandbox.files.remove(dirname) await sandbox.files.makeDir(dirname) - let trigger: () => void + let triggerFile: () => void + let triggerFolder: () => void - const eventPromise = new Promise((resolve) => { - trigger = resolve + const eventFilePromise = new Promise((resolve) => { + triggerFile = resolve + }) + const eventFolderPromise = new Promise((resolve) => { + triggerFolder = resolve }) const expectedFileName = `${nestedDirname}/${filename}` const handle = await sandbox.files.watchDir(dirname, async (event) => { if (event.type === FilesystemEventType.WRITE && event.name === expectedFileName) { - trigger() + triggerFile() + } else if (event.type === FilesystemEventType.CREATE && event.name === nestedDirname) { + triggerFolder() } }, { recursive: true }) await sandbox.files.makeDir(`${dirname}/${nestedDirname}`) - await sandbox.files.write(`${dirname}/${nestedDirname}/${filename}`, content) + await eventFolderPromise - await eventPromise + await sandbox.files.write(`${dirname}/${nestedDirname}/${filename}`, content) + await eventFilePromise await handle.stop() }) diff --git a/packages/python-sdk/tests/async/sandbox_async/files/test_watch.py b/packages/python-sdk/tests/async/sandbox_async/files/test_watch.py index abb457bda..af1f56663 100644 --- a/packages/python-sdk/tests/async/sandbox_async/files/test_watch.py +++ b/packages/python-sdk/tests/async/sandbox_async/files/test_watch.py @@ -74,22 +74,27 @@ async def test_watch_recursive_directory_after_nested_folder_addition( await async_sandbox.files.remove(dirname) await async_sandbox.files.make_dir(dirname) - event_triggered = Event() + event_triggered_file = Event() + event_triggered_folder = Event() expected_filename = f"{nested_dirname}/{filename}" def handle_event(e: FilesystemEvent): if e.type == FilesystemEventType.WRITE and e.name == expected_filename: - event_triggered.set() + event_triggered_file.set() + return + if e.type == FilesystemEventType.CREATE and e.name == nested_dirname: + event_triggered_folder.set() handle = await async_sandbox.files.watch_dir( dirname, on_event=handle_event, recursive=True ) await async_sandbox.files.make_dir(f"{dirname}/{nested_dirname}") - await async_sandbox.files.write(f"{dirname}/{nested_dirname}/{filename}", content) + await event_triggered_folder.wait() - await event_triggered.wait() + await async_sandbox.files.write(f"{dirname}/{nested_dirname}/{filename}", content) + await event_triggered_file.wait() await handle.stop() diff --git a/packages/python-sdk/tests/sync/sandbox_sync/files/test_watch.py b/packages/python-sdk/tests/sync/sandbox_sync/files/test_watch.py index 779db6ab7..859fc4ec1 100644 --- a/packages/python-sdk/tests/sync/sandbox_sync/files/test_watch.py +++ b/packages/python-sdk/tests/sync/sandbox_sync/files/test_watch.py @@ -87,11 +87,15 @@ def test_watch_recursive_directory_after_nested_folder_addition(sandbox: Sandbox events = handle.get_new_events() file_changed = False + folder_created = False for event in events: if event.type == FilesystemEventType.WRITE and event.name == expected_filename: file_changed = True - break + continue + if event.type == FilesystemEventType.CREATE and event.name == nested_dirname: + folder_created = True + assert folder_created assert file_changed handle.stop() From 724e857e51d2fca1d57acff34f0cd996ec741478 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Dobr=C3=BD?= Date: Tue, 3 Dec 2024 18:45:00 +0100 Subject: [PATCH 10/15] Run js tests cleanup using onTestFinished --- packages/js-sdk/tests/sandbox/files/watch.test.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/js-sdk/tests/sandbox/files/watch.test.ts b/packages/js-sdk/tests/sandbox/files/watch.test.ts index 83fe3743b..904e7a200 100644 --- a/packages/js-sdk/tests/sandbox/files/watch.test.ts +++ b/packages/js-sdk/tests/sandbox/files/watch.test.ts @@ -1,4 +1,4 @@ -import { expect } from 'vitest' +import { expect, onTestFinished } from 'vitest' import { sandboxTest } from '../../setup.js' import { FilesystemEventType, NotFoundError, SandboxError } from '../../../src' @@ -38,8 +38,9 @@ sandboxTest('watch recursive directory changes', async ({ sandbox }) => { const content = 'This file will be watched.' const newContent = 'This file has been modified.' - await sandbox.files.remove(dirname) await sandbox.files.makeDir(`${dirname}/${nestedDirname}`) + onTestFinished(() => sandbox.files.remove(dirname)) + await sandbox.files.write(`${dirname}/${nestedDirname}/${filename}`, content) let trigger: () => void @@ -70,8 +71,8 @@ sandboxTest('watch recursive directory after nested folder addition', async ({ s const filename = 'test_watch.txt' const content = 'This file will be watched.' - await sandbox.files.remove(dirname) await sandbox.files.makeDir(dirname) + onTestFinished(() => sandbox.files.remove(dirname)) let triggerFile: () => void let triggerFolder: () => void From af9b6dd49e860e13c085d3cbe42bfc166b39b7de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Dobr=C3=BD?= Date: Thu, 19 Dec 2024 10:18:19 +0100 Subject: [PATCH 11/15] Add note to the recursive watch with rapid folder creation --- apps/web/src/app/(docs)/docs/filesystem/watch/page.mdx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apps/web/src/app/(docs)/docs/filesystem/watch/page.mdx b/apps/web/src/app/(docs)/docs/filesystem/watch/page.mdx index 7dce8d5e1..00dc0c112 100644 --- a/apps/web/src/app/(docs)/docs/filesystem/watch/page.mdx +++ b/apps/web/src/app/(docs)/docs/filesystem/watch/page.mdx @@ -45,6 +45,9 @@ for event in events: # $HighlightLine You can enable recursive watching using the parameter `recursive`. + +When rapidly creating new folders (e.g., deeply nested path of folders), events other than `CREATE` might not be emitted. To avoid this behavior, create the required folder structure in advance. + ```js From 8279c9df3bff7051dbe7b2ff445ee2fd88400965 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Dobr=C3=BD?= Date: Thu, 19 Dec 2024 12:27:46 +0100 Subject: [PATCH 12/15] Add recursive watch envd version check --- .../js-sdk/src/sandbox/filesystem/index.ts | 10 ++++++++++ packages/js-sdk/src/sandbox/index.ts | 18 +++++++++++------- packages/js-sdk/src/sandbox/sandboxApi.ts | 16 +++++++++++----- 3 files changed, 32 insertions(+), 12 deletions(-) diff --git a/packages/js-sdk/src/sandbox/filesystem/index.ts b/packages/js-sdk/src/sandbox/filesystem/index.ts index 89f7c34ef..5536a2447 100644 --- a/packages/js-sdk/src/sandbox/filesystem/index.ts +++ b/packages/js-sdk/src/sandbox/filesystem/index.ts @@ -22,6 +22,9 @@ import { FileType as FsFileType, Filesystem as FilesystemService } from '../../e import { WatchHandle, FilesystemEvent } from './watchHandle' +import { compareVersions } from 'compare-versions' +import { TemplateError } from '../../errors' + /** * Sandbox filesystem object information. */ @@ -439,6 +442,13 @@ export class Filesystem { onEvent: (event: FilesystemEvent) => void | Promise, opts?: WatchOpts ): Promise { + if (opts?.recursive && this.envdApi.version && compareVersions(this.envdApi.version, '0.1.3') < 0) { + throw new TemplateError( + 'You need to update the template to use recursive watching. ' + + 'You can do this by running `e2b template build` in the directory with the template.' + ) + } + const requestTimeoutMs = opts?.requestTimeoutMs ?? this.connectionConfig.requestTimeoutMs diff --git a/packages/js-sdk/src/sandbox/index.ts b/packages/js-sdk/src/sandbox/index.ts index 37edbcae6..909df8c5c 100644 --- a/packages/js-sdk/src/sandbox/index.ts +++ b/packages/js-sdk/src/sandbox/index.ts @@ -99,6 +99,7 @@ export class Sandbox extends SandboxApi { constructor( opts: Omit & { sandboxId: string + envdVersion?: string } ) { super() @@ -115,10 +116,13 @@ export class Sandbox extends SandboxApi { interceptors: opts?.logger ? [createRpcLogger(opts.logger)] : undefined, }) - this.envdApi = new EnvdApiClient({ - apiUrl: this.envdApiUrl, - logger: opts?.logger, - }) + this.envdApi = new EnvdApiClient( + { + apiUrl: this.envdApiUrl, + logger: opts?.logger, + }, + opts?.envdVersion + ) this.files = new Filesystem( rpcTransport, this.envdApi, @@ -177,15 +181,15 @@ export class Sandbox extends SandboxApi { const config = new ConnectionConfig(sandboxOpts) - const sandboxId = config.debug - ? 'debug_sandbox_id' + const sandbox = config.debug + ? { sandboxId: 'debug_sandbox_id' } : await this.createSandbox( template, sandboxOpts?.timeoutMs ?? this.defaultSandboxTimeoutMs, sandboxOpts ) - const sbx = new this({ sandboxId, ...config }) as InstanceType + const sbx = new this({ ...sandbox, ...config }) as InstanceType return sbx } diff --git a/packages/js-sdk/src/sandbox/sandboxApi.ts b/packages/js-sdk/src/sandbox/sandboxApi.ts index 1cf997f4d..edddc15e2 100644 --- a/packages/js-sdk/src/sandbox/sandboxApi.ts +++ b/packages/js-sdk/src/sandbox/sandboxApi.ts @@ -159,7 +159,10 @@ export class SandboxApi { metadata?: Record envs?: Record } - ): Promise { + ): Promise<{ + sandboxId: string + envdVersion: string + }> { const config = new ConnectionConfig(opts) const client = new ApiClient(config) @@ -191,10 +194,13 @@ export class SandboxApi { 'You can do this by running `e2b template build` in the directory with the template.' ) } - return this.getSandboxId({ - sandboxId: res.data!.sandboxID, - clientId: res.data!.clientID, - }) + return { + sandboxId: this.getSandboxId({ + sandboxId: res.data!.sandboxID, + clientId: res.data!.clientID, + }), + envdVersion: res.data!.envdVersion + } } private static timeoutToSeconds(timeout: number): number { From e782f854e1e0c301b941462a701ac8cf782ea53b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Dobr=C3=BD?= Date: Thu, 19 Dec 2024 14:35:10 +0100 Subject: [PATCH 13/15] Add recursive watch envd version check python SDK, js SDK check --- packages/js-sdk/src/envd/api.ts | 5 ++++- .../e2b/sandbox_async/filesystem/filesystem.py | 15 ++++++++++++--- packages/python-sdk/e2b/sandbox_async/main.py | 9 +++++++-- .../python-sdk/e2b/sandbox_async/sandbox_api.py | 6 +++--- .../e2b/sandbox_sync/filesystem/filesystem.py | 11 +++++++++++ packages/python-sdk/e2b/sandbox_sync/main.py | 5 ++++- .../python-sdk/e2b/sandbox_sync/sandbox_api.py | 6 +++--- 7 files changed, 44 insertions(+), 13 deletions(-) diff --git a/packages/js-sdk/src/envd/api.ts b/packages/js-sdk/src/envd/api.ts index 0ae3a6389..57260778f 100644 --- a/packages/js-sdk/src/envd/api.ts +++ b/packages/js-sdk/src/envd/api.ts @@ -96,7 +96,10 @@ export async function handleWatchDirStartEvent( class EnvdApiClient { readonly api: ReturnType> - constructor(config: Pick) { + constructor( + config: Pick, + readonly version?: string + ) { this.api = createClient({ baseUrl: config.apiUrl, // keepalive: true, // TODO: Return keepalive diff --git a/packages/python-sdk/e2b/sandbox_async/filesystem/filesystem.py b/packages/python-sdk/e2b/sandbox_async/filesystem/filesystem.py index 404faf21d..7210b99bc 100644 --- a/packages/python-sdk/e2b/sandbox_async/filesystem/filesystem.py +++ b/packages/python-sdk/e2b/sandbox_async/filesystem/filesystem.py @@ -1,9 +1,10 @@ +import httpcore +import httpx from io import TextIOBase +from packaging.version import Version from typing import IO, AsyncIterator, List, Literal, Optional, Union, overload import e2b_connect as connect -import httpcore -import httpx from e2b.connection_config import ( ConnectionConfig, Username, @@ -13,7 +14,7 @@ from e2b.envd.api import ENVD_API_FILES_ROUTE, ahandle_envd_api_exception from e2b.envd.filesystem import filesystem_connect, filesystem_pb2 from e2b.envd.rpc import authentication_header, handle_rpc_exception -from e2b.exceptions import SandboxException +from e2b.exceptions import SandboxException, TemplateException from e2b.sandbox.filesystem.filesystem import EntryInfo, map_file_type from e2b.sandbox.filesystem.watch_handle import FilesystemEvent from e2b.sandbox_async.filesystem.watch_handle import AsyncWatchHandle @@ -28,11 +29,13 @@ class Filesystem: def __init__( self, envd_api_url: str, + envd_version: Optional[str], connection_config: ConnectionConfig, pool: httpcore.AsyncConnectionPool, envd_api: httpx.AsyncClient, ) -> None: self._envd_api_url = envd_api_url + self._envd_version = envd_version self._connection_config = connection_config self._pool = pool self._envd_api = envd_api @@ -358,6 +361,12 @@ async def watch_dir( :return: `AsyncWatchHandle` object for stopping watching directory """ + if recursive and self._envd_version is not None and Version(self._envd_version) < Version("0.1.3"): + raise TemplateException( + "You need to update the template to use recursive watching. " + "You can do this by running `e2b template build` in the directory with the template." + ) + events = self._rpc.awatch_dir( filesystem_pb2.WatchDirRequest(path=path, recursive=recursive), request_timeout=self._connection_config.get_request_timeout( diff --git a/packages/python-sdk/e2b/sandbox_async/main.py b/packages/python-sdk/e2b/sandbox_async/main.py index 066162d85..a18e1a3bf 100644 --- a/packages/python-sdk/e2b/sandbox_async/main.py +++ b/packages/python-sdk/e2b/sandbox_async/main.py @@ -31,6 +31,7 @@ async def handle_async_request(self, request): class AsyncSandboxOpts(TypedDict): sandbox_id: str + envd_version: Optional[str] connection_config: ConnectionConfig @@ -103,6 +104,7 @@ def __init__(self, **opts: Unpack[AsyncSandboxOpts]): self._connection_config = opts["connection_config"] self._envd_api_url = f"{'http' if self.connection_config.debug else 'https'}://{self.get_host(self.envd_port)}" + self._envd_version = opts["envd_version"] self._transport = AsyncTransportWithLogger(limits=self._limits) self._envd_api = httpx.AsyncClient( @@ -112,6 +114,7 @@ def __init__(self, **opts: Unpack[AsyncSandboxOpts]): self._filesystem = Filesystem( self.envd_api_url, + self._envd_version, self.connection_config, self._transport._pool, self._envd_api, @@ -198,8 +201,8 @@ async def create( request_timeout=request_timeout, ) - sandbox_id = ( - "debug_sandbox_id" + sandbox_id, envd_version = ( + "debug_sandbox_id", None if connection_config.debug else await SandboxApi._create_sandbox( template=template or cls.default_template, @@ -215,6 +218,7 @@ async def create( return cls( sandbox_id=sandbox_id, + envd_version=envd_version, connection_config=connection_config, ) @@ -251,6 +255,7 @@ async def connect( return cls( sandbox_id=sandbox_id, + envd_version=None, connection_config=connection_config, ) diff --git a/packages/python-sdk/e2b/sandbox_async/sandbox_api.py b/packages/python-sdk/e2b/sandbox_async/sandbox_api.py index ea99105cc..14c4430e6 100644 --- a/packages/python-sdk/e2b/sandbox_async/sandbox_api.py +++ b/packages/python-sdk/e2b/sandbox_async/sandbox_api.py @@ -1,4 +1,4 @@ -from typing import Optional, Dict, List +from typing import Optional, Dict, List, Tuple from packaging.version import Version from e2b.sandbox.sandbox_api import SandboxInfo, SandboxApiBase @@ -142,7 +142,7 @@ async def _create_sandbox( domain: Optional[str] = None, debug: Optional[bool] = None, request_timeout: Optional[float] = None, - ) -> str: + ) -> Tuple[str, str]: config = ConnectionConfig( api_key=api_key, domain=domain, @@ -182,7 +182,7 @@ async def _create_sandbox( return SandboxApi._get_sandbox_id( res.parsed.sandbox_id, res.parsed.client_id, - ) + ), res.parsed.envd_version @staticmethod def _get_sandbox_id(sandbox_id: str, client_id: str) -> str: diff --git a/packages/python-sdk/e2b/sandbox_sync/filesystem/filesystem.py b/packages/python-sdk/e2b/sandbox_sync/filesystem/filesystem.py index 68a8d606e..5945226ae 100644 --- a/packages/python-sdk/e2b/sandbox_sync/filesystem/filesystem.py +++ b/packages/python-sdk/e2b/sandbox_sync/filesystem/filesystem.py @@ -4,6 +4,9 @@ import e2b_connect import httpcore import httpx +from packaging.version import Version + +from e2b.exceptions import TemplateException from e2b.connection_config import ( ConnectionConfig, Username, @@ -25,11 +28,13 @@ class Filesystem: def __init__( self, envd_api_url: str, + envd_version: Optional[str], connection_config: ConnectionConfig, pool: httpcore.ConnectionPool, envd_api: httpx.Client, ) -> None: self._envd_api_url = envd_api_url + self._envd_version = envd_version self._connection_config = connection_config self._pool = pool self._envd_api = envd_api @@ -348,6 +353,12 @@ def watch_dir( :return: `WatchHandle` object for stopping watching directory """ + if recursive and self._envd_version is not None and Version(self._envd_version) < Version("0.1.3"): + raise TemplateException( + "You need to update the template to use recursive watching. " + "You can do this by running `e2b template build` in the directory with the template." + ) + try: r = self._rpc.create_watcher( filesystem_pb2.CreateWatcherRequest(path=path, recursive=recursive), diff --git a/packages/python-sdk/e2b/sandbox_sync/main.py b/packages/python-sdk/e2b/sandbox_sync/main.py index 2fa54ea86..31b85f655 100644 --- a/packages/python-sdk/e2b/sandbox_sync/main.py +++ b/packages/python-sdk/e2b/sandbox_sync/main.py @@ -129,12 +129,14 @@ def __init__( if self.connection_config.debug: self._sandbox_id = "debug_sandbox_id" + self._envd_version = None elif sandbox_id is not None: self._sandbox_id = sandbox_id + self._envd_version = None else: template = template or self.default_template timeout = timeout or self.default_sandbox_timeout - self._sandbox_id = SandboxApi._create_sandbox( + self._sandbox_id, self._envd_version = SandboxApi._create_sandbox( template=template, api_key=api_key, timeout=timeout, @@ -155,6 +157,7 @@ def __init__( self._filesystem = Filesystem( self.envd_api_url, + self._envd_version, self.connection_config, self._transport._pool, self._envd_api, diff --git a/packages/python-sdk/e2b/sandbox_sync/sandbox_api.py b/packages/python-sdk/e2b/sandbox_sync/sandbox_api.py index 8e37aab02..e3c5d6a22 100644 --- a/packages/python-sdk/e2b/sandbox_sync/sandbox_api.py +++ b/packages/python-sdk/e2b/sandbox_sync/sandbox_api.py @@ -1,5 +1,5 @@ from httpx import HTTPTransport -from typing import Optional, Dict, List +from typing import Optional, Dict, List, Tuple from packaging.version import Version from e2b.sandbox.sandbox_api import SandboxInfo, SandboxApiBase @@ -149,7 +149,7 @@ def _create_sandbox( domain: Optional[str] = None, debug: Optional[bool] = None, request_timeout: Optional[float] = None, - ) -> str: + ) -> Tuple[str, str]: config = ConnectionConfig( api_key=api_key, domain=domain, @@ -191,4 +191,4 @@ def _create_sandbox( return SandboxApi._get_sandbox_id( res.parsed.sandbox_id, res.parsed.client_id, - ) + ), res.parsed.envd_version From b5c80284460a327b74443375db848aa23af5b5f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Dobr=C3=BD?= Date: Thu, 19 Dec 2024 17:18:01 +0100 Subject: [PATCH 14/15] Cleanup tests only in debug --- packages/js-sdk/tests/sandbox/files/watch.test.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/packages/js-sdk/tests/sandbox/files/watch.test.ts b/packages/js-sdk/tests/sandbox/files/watch.test.ts index 904e7a200..e585405cb 100644 --- a/packages/js-sdk/tests/sandbox/files/watch.test.ts +++ b/packages/js-sdk/tests/sandbox/files/watch.test.ts @@ -1,6 +1,6 @@ import { expect, onTestFinished } from 'vitest' -import { sandboxTest } from '../../setup.js' +import { isDebug, sandboxTest } from '../../setup.js' import { FilesystemEventType, NotFoundError, SandboxError } from '../../../src' sandboxTest('watch directory changes', async ({ sandbox }) => { @@ -39,7 +39,9 @@ sandboxTest('watch recursive directory changes', async ({ sandbox }) => { const newContent = 'This file has been modified.' await sandbox.files.makeDir(`${dirname}/${nestedDirname}`) - onTestFinished(() => sandbox.files.remove(dirname)) + if (isDebug) { + onTestFinished(() => sandbox.files.remove(dirname)) + } await sandbox.files.write(`${dirname}/${nestedDirname}/${filename}`, content) @@ -72,7 +74,9 @@ sandboxTest('watch recursive directory after nested folder addition', async ({ s const content = 'This file will be watched.' await sandbox.files.makeDir(dirname) - onTestFinished(() => sandbox.files.remove(dirname)) + if (isDebug) { + onTestFinished(() => sandbox.files.remove(dirname)) + } let triggerFile: () => void let triggerFolder: () => void From 091ada15579f1401eb5a44ecb37ea92d05632190 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Dobr=C3=BD?= Date: Thu, 19 Dec 2024 17:33:19 +0100 Subject: [PATCH 15/15] JS SDK move version to object --- packages/js-sdk/src/envd/api.ts | 6 +++++- packages/js-sdk/src/sandbox/index.ts | 4 +++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/packages/js-sdk/src/envd/api.ts b/packages/js-sdk/src/envd/api.ts index 57260778f..7f55bf7eb 100644 --- a/packages/js-sdk/src/envd/api.ts +++ b/packages/js-sdk/src/envd/api.ts @@ -95,15 +95,19 @@ export async function handleWatchDirStartEvent( class EnvdApiClient { readonly api: ReturnType> + readonly version: string | undefined constructor( config: Pick, - readonly version?: string + metadata: { + version?: string + } ) { this.api = createClient({ baseUrl: config.apiUrl, // keepalive: true, // TODO: Return keepalive }) + this.version = metadata.version if (config.logger) { this.api.use(createApiLogger(config.logger)) diff --git a/packages/js-sdk/src/sandbox/index.ts b/packages/js-sdk/src/sandbox/index.ts index 909df8c5c..46239ff52 100644 --- a/packages/js-sdk/src/sandbox/index.ts +++ b/packages/js-sdk/src/sandbox/index.ts @@ -121,7 +121,9 @@ export class Sandbox extends SandboxApi { apiUrl: this.envdApiUrl, logger: opts?.logger, }, - opts?.envdVersion + { + version: opts?.envdVersion + } ) this.files = new Filesystem( rpcTransport,