diff --git a/api_ref/sandbox_sync.mdx b/api_ref/sandbox_sync.mdx deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/packages/js-sdk/src/envd/filesystem/filesystem_pb.ts b/packages/js-sdk/src/envd/filesystem/filesystem_pb.ts index bf3763f369..5394b2fb8a 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("ChtmaWxlc3lzdGVtL2ZpbGVzeXN0ZW0ucHJvdG8SCmZpbGVzeXN0ZW0iMgoLTW92ZVJlcXVlc3QSDgoGc291cmNlGAEgASgJEhMKC2Rlc3RpbmF0aW9uGAIgASgJIjQKDE1vdmVSZXNwb25zZRIkCgVlbnRyeRgBIAEoCzIVLmZpbGVzeXN0ZW0uRW50cnlJbmZvIh4KDk1ha2VEaXJSZXF1ZXN0EgwKBHBhdGgYASABKAkiNwoPTWFrZURpclJlc3BvbnNlEiQKBWVudHJ5GAEgASgLMhUuZmlsZXN5c3RlbS5FbnRyeUluZm8iHQoNUmVtb3ZlUmVxdWVzdBIMCgRwYXRoGAEgASgJIhAKDlJlbW92ZVJlc3BvbnNlIhsKC1N0YXRSZXF1ZXN0EgwKBHBhdGgYASABKAkiNAoMU3RhdFJlc3BvbnNlEiQKBWVudHJ5GAEgASgLMhUuZmlsZXN5c3RlbS5FbnRyeUluZm8iSwoJRW50cnlJbmZvEgwKBG5hbWUYASABKAkSIgoEdHlwZRgCIAEoDjIULmZpbGVzeXN0ZW0uRmlsZVR5cGUSDAoEcGF0aBgDIAEoCSIeCg5MaXN0RGlyUmVxdWVzdBIMCgRwYXRoGAEgASgJIjkKD0xpc3REaXJSZXNwb25zZRImCgdlbnRyaWVzGAEgAygLMhUuZmlsZXN5c3RlbS5FbnRyeUluZm8iMgoPV2F0Y2hEaXJSZXF1ZXN0EgwKBHBhdGgYASABKAkSEQoJcmVjdXJzaXZlGAIgASgIIkQKD0ZpbGVzeXN0ZW1FdmVudBIMCgRuYW1lGAEgASgJEiMKBHR5cGUYAiABKA4yFS5maWxlc3lzdGVtLkV2ZW50VHlwZSLgAQoQV2F0Y2hEaXJSZXNwb25zZRI4CgVzdGFydBgBIAEoCzInLmZpbGVzeXN0ZW0uV2F0Y2hEaXJSZXNwb25zZS5TdGFydEV2ZW50SAASMQoKZmlsZXN5c3RlbRgCIAEoCzIbLmZpbGVzeXN0ZW0uRmlsZXN5c3RlbUV2ZW50SAASOwoJa2VlcGFsaXZlGAMgASgLMiYuZmlsZXN5c3RlbS5XYXRjaERpclJlc3BvbnNlLktlZXBBbGl2ZUgAGgwKClN0YXJ0RXZlbnQaCwoJS2VlcEFsaXZlQgcKBWV2ZW50IjcKFENyZWF0ZVdhdGNoZXJSZXF1ZXN0EgwKBHBhdGgYASABKAkSEQoJcmVjdXJzaXZlGAIgASgIIisKFUNyZWF0ZVdhdGNoZXJSZXNwb25zZRISCgp3YXRjaGVyX2lkGAEgASgJIi0KF0dldFdhdGNoZXJFdmVudHNSZXF1ZXN0EhIKCndhdGNoZXJfaWQYASABKAkiRwoYR2V0V2F0Y2hlckV2ZW50c1Jlc3BvbnNlEisKBmV2ZW50cxgBIAMoCzIbLmZpbGVzeXN0ZW0uRmlsZXN5c3RlbUV2ZW50IioKFFJlbW92ZVdhdGNoZXJSZXF1ZXN0EhIKCndhdGNoZXJfaWQYASABKAkiFwoVUmVtb3ZlV2F0Y2hlclJlc3BvbnNlKlIKCEZpbGVUeXBlEhkKFUZJTEVfVFlQRV9VTlNQRUNJRklFRBAAEhIKDkZJTEVfVFlQRV9GSUxFEAESFwoTRklMRV9UWVBFX0RJUkVDVE9SWRACKpgBCglFdmVudFR5cGUSGgoWRVZFTlRfVFlQRV9VTlNQRUNJRklFRBAAEhUKEUVWRU5UX1RZUEVfQ1JFQVRFEAESFAoQRVZFTlRfVFlQRV9XUklURRACEhUKEUVWRU5UX1RZUEVfUkVNT1ZFEAMSFQoRRVZFTlRfVFlQRV9SRU5BTUUQBBIUChBFVkVOVF9UWVBFX0NITU9EEAUynwUKCkZpbGVzeXN0ZW0SOQoEU3RhdBIXLmZpbGVzeXN0ZW0uU3RhdFJlcXVlc3QaGC5maWxlc3lzdGVtLlN0YXRSZXNwb25zZRJCCgdNYWtlRGlyEhouZmlsZXN5c3RlbS5NYWtlRGlyUmVxdWVzdBobLmZpbGVzeXN0ZW0uTWFrZURpclJlc3BvbnNlEjkKBE1vdmUSFy5maWxlc3lzdGVtLk1vdmVSZXF1ZXN0GhguZmlsZXN5c3RlbS5Nb3ZlUmVzcG9uc2USQgoHTGlzdERpchIaLmZpbGVzeXN0ZW0uTGlzdERpclJlcXVlc3QaGy5maWxlc3lzdGVtLkxpc3REaXJSZXNwb25zZRI/CgZSZW1vdmUSGS5maWxlc3lzdGVtLlJlbW92ZVJlcXVlc3QaGi5maWxlc3lzdGVtLlJlbW92ZVJlc3BvbnNlEkcKCFdhdGNoRGlyEhsuZmlsZXN5c3RlbS5XYXRjaERpclJlcXVlc3QaHC5maWxlc3lzdGVtLldhdGNoRGlyUmVzcG9uc2UwARJUCg1DcmVhdGVXYXRjaGVyEiAuZmlsZXN5c3RlbS5DcmVhdGVXYXRjaGVyUmVxdWVzdBohLmZpbGVzeXN0ZW0uQ3JlYXRlV2F0Y2hlclJlc3BvbnNlEl0KEEdldFdhdGNoZXJFdmVudHMSIy5maWxlc3lzdGVtLkdldFdhdGNoZXJFdmVudHNSZXF1ZXN0GiQuZmlsZXN5c3RlbS5HZXRXYXRjaGVyRXZlbnRzUmVzcG9uc2USVAoNUmVtb3ZlV2F0Y2hlchIgLmZpbGVzeXN0ZW0uUmVtb3ZlV2F0Y2hlclJlcXVlc3QaIS5maWxlc3lzdGVtLlJlbW92ZVdhdGNoZXJSZXNwb25zZUJpCg5jb20uZmlsZXN5c3RlbUIPRmlsZXN5c3RlbVByb3RvUAGiAgNGWFiqAgpGaWxlc3lzdGVtygIKRmlsZXN5c3RlbeICFkZpbGVzeXN0ZW1cR1BCTWV0YWRhdGHqAgpGaWxlc3lzdGVtYgZwcm90bzM"); + fileDesc("ChtmaWxlc3lzdGVtL2ZpbGVzeXN0ZW0ucHJvdG8SCmZpbGVzeXN0ZW0iMgoLTW92ZVJlcXVlc3QSDgoGc291cmNlGAEgASgJEhMKC2Rlc3RpbmF0aW9uGAIgASgJIjQKDE1vdmVSZXNwb25zZRIkCgVlbnRyeRgBIAEoCzIVLmZpbGVzeXN0ZW0uRW50cnlJbmZvIh4KDk1ha2VEaXJSZXF1ZXN0EgwKBHBhdGgYASABKAkiNwoPTWFrZURpclJlc3BvbnNlEiQKBWVudHJ5GAEgASgLMhUuZmlsZXN5c3RlbS5FbnRyeUluZm8iHQoNUmVtb3ZlUmVxdWVzdBIMCgRwYXRoGAEgASgJIhAKDlJlbW92ZVJlc3BvbnNlIhsKC1N0YXRSZXF1ZXN0EgwKBHBhdGgYASABKAkiNAoMU3RhdFJlc3BvbnNlEiQKBWVudHJ5GAEgASgLMhUuZmlsZXN5c3RlbS5FbnRyeUluZm8iSwoJRW50cnlJbmZvEgwKBG5hbWUYASABKAkSIgoEdHlwZRgCIAEoDjIULmZpbGVzeXN0ZW0uRmlsZVR5cGUSDAoEcGF0aBgDIAEoCSItCg5MaXN0RGlyUmVxdWVzdBIMCgRwYXRoGAEgASgJEg0KBWRlcHRoGAIgASgNIjkKD0xpc3REaXJSZXNwb25zZRImCgdlbnRyaWVzGAEgAygLMhUuZmlsZXN5c3RlbS5FbnRyeUluZm8iMgoPV2F0Y2hEaXJSZXF1ZXN0EgwKBHBhdGgYASABKAkSEQoJcmVjdXJzaXZlGAIgASgIIkQKD0ZpbGVzeXN0ZW1FdmVudBIMCgRuYW1lGAEgASgJEiMKBHR5cGUYAiABKA4yFS5maWxlc3lzdGVtLkV2ZW50VHlwZSLgAQoQV2F0Y2hEaXJSZXNwb25zZRI4CgVzdGFydBgBIAEoCzInLmZpbGVzeXN0ZW0uV2F0Y2hEaXJSZXNwb25zZS5TdGFydEV2ZW50SAASMQoKZmlsZXN5c3RlbRgCIAEoCzIbLmZpbGVzeXN0ZW0uRmlsZXN5c3RlbUV2ZW50SAASOwoJa2VlcGFsaXZlGAMgASgLMiYuZmlsZXN5c3RlbS5XYXRjaERpclJlc3BvbnNlLktlZXBBbGl2ZUgAGgwKClN0YXJ0RXZlbnQaCwoJS2VlcEFsaXZlQgcKBWV2ZW50IjcKFENyZWF0ZVdhdGNoZXJSZXF1ZXN0EgwKBHBhdGgYASABKAkSEQoJcmVjdXJzaXZlGAIgASgIIisKFUNyZWF0ZVdhdGNoZXJSZXNwb25zZRISCgp3YXRjaGVyX2lkGAEgASgJIi0KF0dldFdhdGNoZXJFdmVudHNSZXF1ZXN0EhIKCndhdGNoZXJfaWQYASABKAkiRwoYR2V0V2F0Y2hlckV2ZW50c1Jlc3BvbnNlEisKBmV2ZW50cxgBIAMoCzIbLmZpbGVzeXN0ZW0uRmlsZXN5c3RlbUV2ZW50IioKFFJlbW92ZVdhdGNoZXJSZXF1ZXN0EhIKCndhdGNoZXJfaWQYASABKAkiFwoVUmVtb3ZlV2F0Y2hlclJlc3BvbnNlKlIKCEZpbGVUeXBlEhkKFUZJTEVfVFlQRV9VTlNQRUNJRklFRBAAEhIKDkZJTEVfVFlQRV9GSUxFEAESFwoTRklMRV9UWVBFX0RJUkVDVE9SWRACKpgBCglFdmVudFR5cGUSGgoWRVZFTlRfVFlQRV9VTlNQRUNJRklFRBAAEhUKEUVWRU5UX1RZUEVfQ1JFQVRFEAESFAoQRVZFTlRfVFlQRV9XUklURRACEhUKEUVWRU5UX1RZUEVfUkVNT1ZFEAMSFQoRRVZFTlRfVFlQRV9SRU5BTUUQBBIUChBFVkVOVF9UWVBFX0NITU9EEAUynwUKCkZpbGVzeXN0ZW0SOQoEU3RhdBIXLmZpbGVzeXN0ZW0uU3RhdFJlcXVlc3QaGC5maWxlc3lzdGVtLlN0YXRSZXNwb25zZRJCCgdNYWtlRGlyEhouZmlsZXN5c3RlbS5NYWtlRGlyUmVxdWVzdBobLmZpbGVzeXN0ZW0uTWFrZURpclJlc3BvbnNlEjkKBE1vdmUSFy5maWxlc3lzdGVtLk1vdmVSZXF1ZXN0GhguZmlsZXN5c3RlbS5Nb3ZlUmVzcG9uc2USQgoHTGlzdERpchIaLmZpbGVzeXN0ZW0uTGlzdERpclJlcXVlc3QaGy5maWxlc3lzdGVtLkxpc3REaXJSZXNwb25zZRI/CgZSZW1vdmUSGS5maWxlc3lzdGVtLlJlbW92ZVJlcXVlc3QaGi5maWxlc3lzdGVtLlJlbW92ZVJlc3BvbnNlEkcKCFdhdGNoRGlyEhsuZmlsZXN5c3RlbS5XYXRjaERpclJlcXVlc3QaHC5maWxlc3lzdGVtLldhdGNoRGlyUmVzcG9uc2UwARJUCg1DcmVhdGVXYXRjaGVyEiAuZmlsZXN5c3RlbS5DcmVhdGVXYXRjaGVyUmVxdWVzdBohLmZpbGVzeXN0ZW0uQ3JlYXRlV2F0Y2hlclJlc3BvbnNlEl0KEEdldFdhdGNoZXJFdmVudHMSIy5maWxlc3lzdGVtLkdldFdhdGNoZXJFdmVudHNSZXF1ZXN0GiQuZmlsZXN5c3RlbS5HZXRXYXRjaGVyRXZlbnRzUmVzcG9uc2USVAoNUmVtb3ZlV2F0Y2hlchIgLmZpbGVzeXN0ZW0uUmVtb3ZlV2F0Y2hlclJlcXVlc3QaIS5maWxlc3lzdGVtLlJlbW92ZVdhdGNoZXJSZXNwb25zZUJpCg5jb20uZmlsZXN5c3RlbUIPRmlsZXN5c3RlbVByb3RvUAGiAgNGWFiqAgpGaWxlc3lzdGVtygIKRmlsZXN5c3RlbeICFkZpbGVzeXN0ZW1cR1BCTWV0YWRhdGHqAgpGaWxlc3lzdGVtYgZwcm90bzM"); /** * @generated from message filesystem.MoveRequest @@ -184,6 +184,11 @@ export type ListDirRequest = Message<"filesystem.ListDirRequest"> & { * @generated from field: string path = 1; */ path: string; + + /** + * @generated from field: uint32 depth = 2; + */ + depth: number; }; /** diff --git a/packages/js-sdk/src/sandbox/filesystem/index.ts b/packages/js-sdk/src/sandbox/filesystem/index.ts index 13f511458b..46c384fbcd 100644 --- a/packages/js-sdk/src/sandbox/filesystem/index.ts +++ b/packages/js-sdk/src/sandbox/filesystem/index.ts @@ -26,7 +26,7 @@ import { import { FilesystemEvent, WatchHandle } from './watchHandle' import { compareVersions } from 'compare-versions' -import { TemplateError } from '../../errors' +import { InvalidArgumentError, TemplateError } from '../../errors' import { ENVD_VERSION_RECURSIVE_WATCH } from '../../envd/versions' /** @@ -87,6 +87,13 @@ export interface FilesystemRequestOpts user?: Username } +export interface FilesystemListOpts extends FilesystemRequestOpts { + /** + * Depth of the directory to list. + */ + depth?: number +} + /** * Options for watching a directory. */ @@ -339,10 +346,17 @@ export class Filesystem { * * @returns list of entries in the sandbox filesystem directory. */ - async list(path: string, opts?: FilesystemRequestOpts): Promise { + async list(path: string, opts?: FilesystemListOpts): Promise { + if (typeof opts?.depth === 'number' && opts.depth < 1) { + throw new InvalidArgumentError('depth should be at least one') + } + try { const res = await this.rpc.listDir( - { path }, + { + path, + depth: opts?.depth ?? 1, + }, { headers: authenticationHeader(opts?.user), signal: this.connectionConfig.getSignal(opts?.requestTimeoutMs), diff --git a/packages/js-sdk/tests/sandbox/files/list.test.ts b/packages/js-sdk/tests/sandbox/files/list.test.ts index 448417f48a..8fc8c0e103 100644 --- a/packages/js-sdk/tests/sandbox/files/list.test.ts +++ b/packages/js-sdk/tests/sandbox/files/list.test.ts @@ -1,23 +1,121 @@ -import { assert } from 'vitest' +import { assert, onTestFinished } from 'vitest' -import { sandboxTest } from '../../setup.js' +import { sandboxTest, wait } from '../../setup.js' + +const parentDirName = 'test_directory' sandboxTest('list directory', async ({ sandbox }) => { - const dirName = 'test_directory4' + const homeDirName = '/home/user' + await sandbox.files.makeDir(parentDirName) + await sandbox.files.makeDir(`${parentDirName}/subdir1`) + await sandbox.files.makeDir(`${parentDirName}/subdir2`) + await sandbox.files.makeDir(`${parentDirName}/subdir1/subdir1_1`) + await sandbox.files.makeDir(`${parentDirName}/subdir1/subdir1_2`) + await sandbox.files.makeDir(`${parentDirName}/subdir2/subdir2_1`) + await sandbox.files.makeDir(`${parentDirName}/subdir2/subdir2_2`) + await sandbox.files.write(`${parentDirName}/file1.txt`, 'Hello, world!') - await sandbox.files.makeDir(dirName) + const testCases = [ + { + test_name: 'default depth (1)', + depth: undefined, + expectedLen: 3, + expectedFileNames: ['file1.txt', 'subdir1', 'subdir2'], + expectedFileTypes: ['file', 'dir', 'dir'], + expectedFilePaths: [ + `${homeDirName}/${parentDirName}/file1.txt`, + `${homeDirName}/${parentDirName}/subdir1`, + `${homeDirName}/${parentDirName}/subdir2`, + ], + }, + { + test_name: 'explicit depth 1', + depth: 1, + expectedLen: 3, + expectedFileNames: ['file1.txt', 'subdir1', 'subdir2'], + expectedFileTypes: ['file', 'dir', 'dir'], + expectedFilePaths: [ + `${homeDirName}/${parentDirName}/file1.txt`, + `${homeDirName}/${parentDirName}/subdir1`, + `${homeDirName}/${parentDirName}/subdir2`, + ], + }, + { + test_name: 'explicit depth 2', + depth: 2, + expectedLen: 7, + expectedFileTypes: ['file', 'dir', 'dir', 'dir', 'dir', 'dir', 'dir'], + expectedFileNames: [ + 'file1.txt', + 'subdir1', + 'subdir1_1', + 'subdir1_2', + 'subdir2', + 'subdir2_1', + 'subdir2_2', + ], + expectedFilePaths: [ + `${homeDirName}/${parentDirName}/file1.txt`, + `${homeDirName}/${parentDirName}/subdir1`, + `${homeDirName}/${parentDirName}/subdir1/subdir1_1`, + `${homeDirName}/${parentDirName}/subdir1/subdir1_2`, + `${homeDirName}/${parentDirName}/subdir2`, + `${homeDirName}/${parentDirName}/subdir2/subdir2_1`, + `${homeDirName}/${parentDirName}/subdir2/subdir2_2`, + ], + }, + { + test_name: 'explicit depth 3 (should be the same as depth 2)', + depth: 3, + expectedLen: 7, + expectedFileTypes: ['file', 'dir', 'dir', 'dir', 'dir', 'dir', 'dir'], + expectedFileNames: [ + 'file1.txt', + 'subdir1', + 'subdir1_1', + 'subdir1_2', + 'subdir2', + 'subdir2_1', + 'subdir2_2', + ], + expectedFilePaths: [ + `${homeDirName}/${parentDirName}/file1.txt`, + `${homeDirName}/${parentDirName}/subdir1`, + `${homeDirName}/${parentDirName}/subdir1/subdir1_1`, + `${homeDirName}/${parentDirName}/subdir1/subdir1_2`, + `${homeDirName}/${parentDirName}/subdir2`, + `${homeDirName}/${parentDirName}/subdir2/subdir2_1`, + `${homeDirName}/${parentDirName}/subdir2/subdir2_2`, + ], + }, + ] - const files = await sandbox.files.list(dirName) - assert.equal(files.length, 0) + for (const testCase of testCases) { + const files = await sandbox.files.list( + parentDirName, + testCase.depth !== undefined ? { depth: testCase.depth } : undefined + ) + assert.equal(files.length, testCase.expectedLen) - await sandbox.files.write('test_directory4/test_file', 'test') + for (let i = 0; i < testCase.expectedFilePaths.length; i++) { + assert.equal(files[i].type, testCase.expectedFileTypes[i]) + assert.equal(files[i].name, testCase.expectedFileNames[i]) + assert.equal(files[i].path, testCase.expectedFilePaths[i]) + } + } +}) - const files1 = await sandbox.files.list(dirName) - assert.equal(files1.length, 1) - assert.equal(files1[0].name, 'test_file') - assert.equal(files1[0].type, 'file') - assert.equal(files1[0].path, `/home/user/${dirName}/test_file`) +sandboxTest('list directory with invalid depth', async ({ sandbox }) => { + await sandbox.files.makeDir(parentDirName) - const exists = await sandbox.files.exists(dirName) - assert.isTrue(exists) + try { + await sandbox.files.list(parentDirName, { depth: -1 }) + assert.fail('Expected error but none was thrown') + } catch (err) { + const expectedErrorMessage = 'depth should be at least one' + assert.ok( + err.message.includes(expectedErrorMessage), + `expected error message to include "${expectedErrorMessage}"` + ) + } }) diff --git a/packages/python-sdk/e2b/envd/filesystem/filesystem_pb2.py b/packages/python-sdk/e2b/envd/filesystem/filesystem_pb2.py index b7e6c5f052..1a0da40fa4 100644 --- a/packages/python-sdk/e2b/envd/filesystem/filesystem_pb2.py +++ b/packages/python-sdk/e2b/envd/filesystem/filesystem_pb2.py @@ -19,7 +19,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"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' + 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\x12\x14\n\x05\x64\x65pth\x18\x02 \x01(\rR\x05\x64\x65pth"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() @@ -32,10 +32,10 @@ _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["_FILETYPE"]._serialized_start = 1410 + _globals["_FILETYPE"]._serialized_end = 1492 + _globals["_EVENTTYPE"]._serialized_start = 1495 + _globals["_EVENTTYPE"]._serialized_end = 1647 _globals["_MOVEREQUEST"]._serialized_start = 43 _globals["_MOVEREQUEST"]._serialized_end = 114 _globals["_MOVERESPONSE"]._serialized_start = 116 @@ -55,31 +55,31 @@ _globals["_ENTRYINFO"]._serialized_start = 430 _globals["_ENTRYINFO"]._serialized_end = 523 _globals["_LISTDIRREQUEST"]._serialized_start = 525 - _globals["_LISTDIRREQUEST"]._serialized_end = 561 - _globals["_LISTDIRRESPONSE"]._serialized_start = 563 - _globals["_LISTDIRRESPONSE"]._serialized_end = 629 - _globals["_WATCHDIRREQUEST"]._serialized_start = 631 - _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 + _globals["_LISTDIRREQUEST"]._serialized_end = 583 + _globals["_LISTDIRRESPONSE"]._serialized_start = 585 + _globals["_LISTDIRRESPONSE"]._serialized_end = 651 + _globals["_WATCHDIRREQUEST"]._serialized_start = 653 + _globals["_WATCHDIRREQUEST"]._serialized_end = 720 + _globals["_FILESYSTEMEVENT"]._serialized_start = 722 + _globals["_FILESYSTEMEVENT"]._serialized_end = 802 + _globals["_WATCHDIRRESPONSE"]._serialized_start = 805 + _globals["_WATCHDIRRESPONSE"]._serialized_end = 1059 + _globals["_WATCHDIRRESPONSE_STARTEVENT"]._serialized_start = 1025 + _globals["_WATCHDIRRESPONSE_STARTEVENT"]._serialized_end = 1037 + _globals["_WATCHDIRRESPONSE_KEEPALIVE"]._serialized_start = 1039 + _globals["_WATCHDIRRESPONSE_KEEPALIVE"]._serialized_end = 1050 + _globals["_CREATEWATCHERREQUEST"]._serialized_start = 1061 + _globals["_CREATEWATCHERREQUEST"]._serialized_end = 1133 + _globals["_CREATEWATCHERRESPONSE"]._serialized_start = 1135 + _globals["_CREATEWATCHERRESPONSE"]._serialized_end = 1189 + _globals["_GETWATCHEREVENTSREQUEST"]._serialized_start = 1191 + _globals["_GETWATCHEREVENTSREQUEST"]._serialized_end = 1247 + _globals["_GETWATCHEREVENTSRESPONSE"]._serialized_start = 1249 + _globals["_GETWATCHEREVENTSRESPONSE"]._serialized_end = 1328 + _globals["_REMOVEWATCHERREQUEST"]._serialized_start = 1330 + _globals["_REMOVEWATCHERREQUEST"]._serialized_end = 1383 + _globals["_REMOVEWATCHERRESPONSE"]._serialized_start = 1385 + _globals["_REMOVEWATCHERRESPONSE"]._serialized_end = 1408 + _globals["_FILESYSTEM"]._serialized_start = 1650 + _globals["_FILESYSTEM"]._serialized_end = 2321 # @@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 c031b13ed4..0b93efc1d6 100644 --- a/packages/python-sdk/e2b/envd/filesystem/filesystem_pb2.pyi +++ b/packages/python-sdk/e2b/envd/filesystem/filesystem_pb2.pyi @@ -103,10 +103,14 @@ class EntryInfo(_message.Message): ) -> None: ... class ListDirRequest(_message.Message): - __slots__ = ("path",) + __slots__ = ("path", "depth") PATH_FIELD_NUMBER: _ClassVar[int] + DEPTH_FIELD_NUMBER: _ClassVar[int] path: str - def __init__(self, path: _Optional[str] = ...) -> None: ... + depth: int + def __init__( + self, path: _Optional[str] = ..., depth: _Optional[int] = ... + ) -> None: ... class ListDirResponse(_message.Message): __slots__ = ("entries",) diff --git a/packages/python-sdk/e2b/sandbox_async/filesystem/filesystem.py b/packages/python-sdk/e2b/sandbox_async/filesystem/filesystem.py index d1871bd704..8d240311c5 100644 --- a/packages/python-sdk/e2b/sandbox_async/filesystem/filesystem.py +++ b/packages/python-sdk/e2b/sandbox_async/filesystem/filesystem.py @@ -4,7 +4,6 @@ from packaging.version import Version from typing import AsyncIterator, IO, List, Literal, Optional, overload, Union from e2b.sandbox.filesystem.filesystem import WriteEntry - import e2b_connect as connect from e2b.connection_config import ( ConnectionConfig, @@ -16,7 +15,7 @@ from e2b.envd.filesystem import filesystem_connect, filesystem_pb2 from e2b.envd.rpc import authentication_header, handle_rpc_exception from e2b.envd.versions import ENVD_VERSION_RECURSIVE_WATCH -from e2b.exceptions import SandboxException, TemplateException +from e2b.exceptions import SandboxException, TemplateException, InvalidArgumentException 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 @@ -254,6 +253,7 @@ async def write( async def list( self, path: str, + depth: Optional[int] = 1, user: Username = "user", request_timeout: Optional[float] = None, ) -> List[EntryInfo]: @@ -261,14 +261,18 @@ async def list( List entries in a directory. :param path: Path to the directory + :param depth: Depth of the directory to list :param user: Run the operation as this user :param request_timeout: Timeout for the request in **seconds** :return: List of entries in the directory """ + if depth is not None and depth < 1: + raise InvalidArgumentException("depth should be at least 1") + try: res = await self._rpc.alist_dir( - filesystem_pb2.ListDirRequest(path=path), + filesystem_pb2.ListDirRequest(path=path, depth=depth), 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 76c1ab4bf6..1ff9e9bf1a 100644 --- a/packages/python-sdk/e2b/sandbox_sync/filesystem/filesystem.py +++ b/packages/python-sdk/e2b/sandbox_sync/filesystem/filesystem.py @@ -8,7 +8,7 @@ from packaging.version import Version from e2b.envd.versions import ENVD_VERSION_RECURSIVE_WATCH -from e2b.exceptions import TemplateException +from e2b.exceptions import TemplateException, InvalidArgumentException from e2b.connection_config import ( ConnectionConfig, Username, @@ -250,6 +250,7 @@ def write( def list( self, path: str, + depth: Optional[int] = 1, user: Username = "user", request_timeout: Optional[float] = None, ) -> List[EntryInfo]: @@ -257,14 +258,18 @@ def list( List entries in a directory. :param path: Path to the directory + :param depth: Depth of the directory to list :param user: Run the operation as this user :param request_timeout: Timeout for the request in **seconds** :return: List of entries in the directory """ + if depth is not None and depth < 1: + raise InvalidArgumentException("depth should be at least 1") + try: res = self._rpc.list_dir( - filesystem_pb2.ListDirRequest(path=path), + filesystem_pb2.ListDirRequest(path=path, depth=depth), request_timeout=self._connection_config.get_request_timeout( request_timeout ), diff --git a/packages/python-sdk/tests/async/sandbox_async/files/test_files_list.py b/packages/python-sdk/tests/async/sandbox_async/files/test_files_list.py index 62113160f4..39104a1aa1 100644 --- a/packages/python-sdk/tests/async/sandbox_async/files/test_files_list.py +++ b/packages/python-sdk/tests/async/sandbox_async/files/test_files_list.py @@ -4,15 +4,152 @@ async def test_list_directory(async_sandbox: AsyncSandbox): - dir_name = f"test_directory_{uuid.uuid4()}" - - await async_sandbox.files.make_dir(dir_name) - files = await async_sandbox.files.list(dir_name) - assert len(files) == 0 - - await async_sandbox.files.write(f"{dir_name}/test_file", "test") - files1 = await async_sandbox.files.list(dir_name) - assert len(files1) == 1 - assert files1[0].name == "test_file" - assert files1[0].type == FileType.FILE - assert files1[0].path == f"/home/user/{dir_name}/test_file" + home_dir_name = "/home/user" + parent_dir_name = f"test_directory_{uuid.uuid4()}" + + await async_sandbox.files.make_dir(parent_dir_name) + await async_sandbox.files.make_dir(f"{parent_dir_name}/subdir1") + await async_sandbox.files.make_dir(f"{parent_dir_name}/subdir2") + await async_sandbox.files.make_dir(f"{parent_dir_name}/subdir1/subdir1_1") + await async_sandbox.files.make_dir(f"{parent_dir_name}/subdir1/subdir1_2") + await async_sandbox.files.make_dir(f"{parent_dir_name}/subdir2/subdir2_1") + await async_sandbox.files.make_dir(f"{parent_dir_name}/subdir2/subdir2_2") + await async_sandbox.files.write(f"{parent_dir_name}/file1.txt", "Hello, world!") + + test_cases = [ + { + "name": "default depth (1)", + "depth": None, + "expected_len": 3, + "expected_file_names": [ + "file1.txt", + "subdir1", + "subdir2", + ], + "expected_file_types": [ + FileType.FILE, + FileType.DIR, + FileType.DIR, + ], + "expected_file_paths": [ + f"{home_dir_name}/{parent_dir_name}/file1.txt", + f"{home_dir_name}/{parent_dir_name}/subdir1", + f"{home_dir_name}/{parent_dir_name}/subdir2", + ], + }, + { + "name": "explicit depth 1", + "depth": 1, + "expected_len": 3, + "expected_file_names": [ + "file1.txt", + "subdir1", + "subdir2", + ], + "expected_file_types": [ + FileType.FILE, + FileType.DIR, + FileType.DIR, + ], + "expected_file_paths": [ + f"{home_dir_name}/{parent_dir_name}/file1.txt", + f"{home_dir_name}/{parent_dir_name}/subdir1", + f"{home_dir_name}/{parent_dir_name}/subdir2", + ], + }, + { + "name": "explicit depth 2", + "depth": 2, + "expected_len": 7, + "expected_file_types": [ + FileType.FILE, + FileType.DIR, + FileType.DIR, + FileType.DIR, + FileType.DIR, + FileType.DIR, + FileType.DIR, + ], + "expected_file_names": [ + "file1.txt", + "subdir1", + "subdir1_1", + "subdir1_2", + "subdir2", + "subdir2_1", + "subdir2_2", + ], + "expected_file_paths": [ + f"{home_dir_name}/{parent_dir_name}/file1.txt", + f"{home_dir_name}/{parent_dir_name}/subdir1", + f"{home_dir_name}/{parent_dir_name}/subdir1/subdir1_1", + f"{home_dir_name}/{parent_dir_name}/subdir1/subdir1_2", + f"{home_dir_name}/{parent_dir_name}/subdir2", + f"{home_dir_name}/{parent_dir_name}/subdir2/subdir2_1", + f"{home_dir_name}/{parent_dir_name}/subdir2/subdir2_2", + ], + }, + { + "name": "explicit depth 3 (should be the same as depth 2)", + "depth": 3, + "expected_len": 7, + "expected_file_names": [ + "file1.txt", + "subdir1", + "subdir1_1", + "subdir1_2", + "subdir2", + "subdir2_1", + "subdir2_2", + ], + "expected_file_types": [ + FileType.FILE, + FileType.DIR, + FileType.DIR, + FileType.DIR, + FileType.DIR, + FileType.DIR, + FileType.DIR, + ], + "expected_file_paths": [ + f"{home_dir_name}/{parent_dir_name}/file1.txt", + f"{home_dir_name}/{parent_dir_name}/subdir1", + f"{home_dir_name}/{parent_dir_name}/subdir1/subdir1_1", + f"{home_dir_name}/{parent_dir_name}/subdir1/subdir1_2", + f"{home_dir_name}/{parent_dir_name}/subdir2", + f"{home_dir_name}/{parent_dir_name}/subdir2/subdir2_1", + f"{home_dir_name}/{parent_dir_name}/subdir2/subdir2_2", + ], + }, + ] + + for test_case in test_cases: + files = await async_sandbox.files.list( + parent_dir_name, + depth=test_case["depth"] if test_case["depth"] is not None else None, + ) + + assert len(files) == test_case["expected_len"] + + for i in range(len(test_case["expected_file_names"])): + assert files[i].name == test_case["expected_file_names"][i] + assert files[i].path == test_case["expected_file_paths"][i] + assert files[i].type == test_case["expected_file_types"][i] + + await async_sandbox.files.remove(parent_dir_name) + + +async def test_list_directory_error_cases(async_sandbox: AsyncSandbox): + parent_dir_name = f"test_directory_{uuid.uuid4()}" + await async_sandbox.files.make_dir(parent_dir_name) + + expected_error_message = "depth should be at least 1" + try: + await async_sandbox.files.list(parent_dir_name, depth=-1) + assert False, "Expected error but none was thrown" + except Exception as err: + assert expected_error_message in str( + err + ), f'expected error message to include "{expected_error_message}"' + + await async_sandbox.files.remove(parent_dir_name) diff --git a/packages/python-sdk/tests/sync/sandbox_sync/files/test_files_list.py b/packages/python-sdk/tests/sync/sandbox_sync/files/test_files_list.py new file mode 100644 index 0000000000..9e265ef34a --- /dev/null +++ b/packages/python-sdk/tests/sync/sandbox_sync/files/test_files_list.py @@ -0,0 +1,155 @@ +import uuid + +from e2b import Sandbox, FileType + + +def test_list_directory(sandbox: Sandbox): + home_dir_name = "/home/user" + parent_dir_name = f"test_directory_{uuid.uuid4()}" + + sandbox.files.make_dir(parent_dir_name) + sandbox.files.make_dir(f"{parent_dir_name}/subdir1") + sandbox.files.make_dir(f"{parent_dir_name}/subdir2") + sandbox.files.make_dir(f"{parent_dir_name}/subdir1/subdir1_1") + sandbox.files.make_dir(f"{parent_dir_name}/subdir1/subdir1_2") + sandbox.files.make_dir(f"{parent_dir_name}/subdir2/subdir2_1") + sandbox.files.make_dir(f"{parent_dir_name}/subdir2/subdir2_2") + sandbox.files.write(f"{parent_dir_name}/file1.txt", "Hello, world!") + + test_cases = [ + { + "name": "default depth (1)", + "depth": None, + "expected_len": 3, + "expected_file_names": [ + "file1.txt", + "subdir1", + "subdir2", + ], + "expected_file_types": [ + FileType.FILE, + FileType.DIR, + FileType.DIR, + ], + "expected_file_paths": [ + f"{home_dir_name}/{parent_dir_name}/file1.txt", + f"{home_dir_name}/{parent_dir_name}/subdir1", + f"{home_dir_name}/{parent_dir_name}/subdir2", + ], + }, + { + "name": "explicit depth 1", + "depth": 1, + "expected_len": 3, + "expected_file_names": [ + "file1.txt", + "subdir1", + "subdir2", + ], + "expected_file_types": [ + FileType.FILE, + FileType.DIR, + FileType.DIR, + ], + "expected_file_paths": [ + f"{home_dir_name}/{parent_dir_name}/file1.txt", + f"{home_dir_name}/{parent_dir_name}/subdir1", + f"{home_dir_name}/{parent_dir_name}/subdir2", + ], + }, + { + "name": "explicit depth 2", + "depth": 2, + "expected_len": 7, + "expected_file_types": [ + FileType.FILE, + FileType.DIR, + FileType.DIR, + FileType.DIR, + FileType.DIR, + FileType.DIR, + FileType.DIR, + ], + "expected_file_names": [ + "file1.txt", + "subdir1", + "subdir1_1", + "subdir1_2", + "subdir2", + "subdir2_1", + "subdir2_2", + ], + "expected_file_paths": [ + f"{home_dir_name}/{parent_dir_name}/file1.txt", + f"{home_dir_name}/{parent_dir_name}/subdir1", + f"{home_dir_name}/{parent_dir_name}/subdir1/subdir1_1", + f"{home_dir_name}/{parent_dir_name}/subdir1/subdir1_2", + f"{home_dir_name}/{parent_dir_name}/subdir2", + f"{home_dir_name}/{parent_dir_name}/subdir2/subdir2_1", + f"{home_dir_name}/{parent_dir_name}/subdir2/subdir2_2", + ], + }, + { + "name": "explicit depth 3 (should be the same as depth 2)", + "depth": 3, + "expected_len": 7, + "expected_file_names": [ + "file1.txt", + "subdir1", + "subdir1_1", + "subdir1_2", + "subdir2", + "subdir2_1", + "subdir2_2", + ], + "expected_file_types": [ + FileType.FILE, + FileType.DIR, + FileType.DIR, + FileType.DIR, + FileType.DIR, + FileType.DIR, + FileType.DIR, + ], + "expected_file_paths": [ + f"{home_dir_name}/{parent_dir_name}/file1.txt", + f"{home_dir_name}/{parent_dir_name}/subdir1", + f"{home_dir_name}/{parent_dir_name}/subdir1/subdir1_1", + f"{home_dir_name}/{parent_dir_name}/subdir1/subdir1_2", + f"{home_dir_name}/{parent_dir_name}/subdir2", + f"{home_dir_name}/{parent_dir_name}/subdir2/subdir2_1", + f"{home_dir_name}/{parent_dir_name}/subdir2/subdir2_2", + ], + }, + ] + + for test_case in test_cases: + files = sandbox.files.list( + parent_dir_name, + depth=test_case["depth"] if test_case["depth"] is not None else None, + ) + + assert len(files) == test_case["expected_len"] + + for i in range(len(test_case["expected_file_names"])): + assert files[i].name == test_case["expected_file_names"][i] + assert files[i].path == test_case["expected_file_paths"][i] + assert files[i].type == test_case["expected_file_types"][i] + + sandbox.files.remove(parent_dir_name) + + +def test_list_directory_error_cases(sandbox: Sandbox): + parent_dir_name = f"test_directory_{uuid.uuid4()}" + sandbox.files.make_dir(parent_dir_name) + + expected_error_message = "depth should be at least 1" + try: + sandbox.files.list(parent_dir_name, depth=-1) + assert False, "Expected error but none was thrown" + except Exception as err: + assert expected_error_message in str( + err + ), f'expected error message to include "{expected_error_message}"' + + sandbox.files.remove(parent_dir_name) diff --git a/packages/python-sdk/tests/sync/sandbox_sync/files/test_list_dir.py b/packages/python-sdk/tests/sync/sandbox_sync/files/test_list_dir.py deleted file mode 100644 index b243ce96a4..0000000000 --- a/packages/python-sdk/tests/sync/sandbox_sync/files/test_list_dir.py +++ /dev/null @@ -1,18 +0,0 @@ -import uuid - -from e2b import Sandbox, FileType - - -def test_list_directory(sandbox: Sandbox): - dir_name = f"test_directory_{uuid.uuid4()}" - - sandbox.files.make_dir(dir_name) - files = sandbox.files.list(dir_name) - assert len(files) == 0 - - sandbox.files.write(f"{dir_name}/test_file", "test") - files1 = sandbox.files.list(dir_name) - assert len(files1) == 1 - assert files1[0].name == "test_file" - assert files1[0].type == FileType.FILE - assert files1[0].path == f"/home/user/{dir_name}/test_file" diff --git a/spec/envd/filesystem/filesystem.proto b/spec/envd/filesystem/filesystem.proto index 8c71c4f200..fc0976ac8a 100644 --- a/spec/envd/filesystem/filesystem.proto +++ b/spec/envd/filesystem/filesystem.proto @@ -62,6 +62,7 @@ enum FileType { message ListDirRequest { string path = 1; + uint32 depth = 2; } message ListDirResponse {