Skip to content

Commit a5b08de

Browse files
0divmishushakov
andauthored
Add optional depth parameter to SDK file listing methods. (#649)
# Description Do not merge before e2b-dev/infra#524 is merged and deployed. The DX would look like this: ```js const files = await sandbox.files.list(dirName, { depth: 3 }) ``` - [x] update `envd` pb spec - [x] generate for `js-sdk` - [x] update `js-sdk` `file.list` method to include optional depth param - [x] update `js-sdk` tests - [x] generate for `python-sdk` - [x] update `python-sdk` `file.list` method to include optional depth param - [x] sync - [x] async - [x] update `python-sdk` tests - [x] sync - [x] async --------- Co-authored-by: Mish <10400064+mishushakov@users.noreply.github.com>
1 parent a32bd71 commit a5b08de

12 files changed

Lines changed: 492 additions & 87 deletions

File tree

api_ref/sandbox_sync.mdx

Whitespace-only changes.

packages/js-sdk/src/envd/filesystem/filesystem_pb.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import type { Message } from "@bufbuild/protobuf";
1010
* Describes the file filesystem/filesystem.proto.
1111
*/
1212
export const file_filesystem_filesystem: GenFile = /*@__PURE__*/
13-
fileDesc("ChtmaWxlc3lzdGVtL2ZpbGVzeXN0ZW0ucHJvdG8SCmZpbGVzeXN0ZW0iMgoLTW92ZVJlcXVlc3QSDgoGc291cmNlGAEgASgJEhMKC2Rlc3RpbmF0aW9uGAIgASgJIjQKDE1vdmVSZXNwb25zZRIkCgVlbnRyeRgBIAEoCzIVLmZpbGVzeXN0ZW0uRW50cnlJbmZvIh4KDk1ha2VEaXJSZXF1ZXN0EgwKBHBhdGgYASABKAkiNwoPTWFrZURpclJlc3BvbnNlEiQKBWVudHJ5GAEgASgLMhUuZmlsZXN5c3RlbS5FbnRyeUluZm8iHQoNUmVtb3ZlUmVxdWVzdBIMCgRwYXRoGAEgASgJIhAKDlJlbW92ZVJlc3BvbnNlIhsKC1N0YXRSZXF1ZXN0EgwKBHBhdGgYASABKAkiNAoMU3RhdFJlc3BvbnNlEiQKBWVudHJ5GAEgASgLMhUuZmlsZXN5c3RlbS5FbnRyeUluZm8iSwoJRW50cnlJbmZvEgwKBG5hbWUYASABKAkSIgoEdHlwZRgCIAEoDjIULmZpbGVzeXN0ZW0uRmlsZVR5cGUSDAoEcGF0aBgDIAEoCSIeCg5MaXN0RGlyUmVxdWVzdBIMCgRwYXRoGAEgASgJIjkKD0xpc3REaXJSZXNwb25zZRImCgdlbnRyaWVzGAEgAygLMhUuZmlsZXN5c3RlbS5FbnRyeUluZm8iMgoPV2F0Y2hEaXJSZXF1ZXN0EgwKBHBhdGgYASABKAkSEQoJcmVjdXJzaXZlGAIgASgIIkQKD0ZpbGVzeXN0ZW1FdmVudBIMCgRuYW1lGAEgASgJEiMKBHR5cGUYAiABKA4yFS5maWxlc3lzdGVtLkV2ZW50VHlwZSLgAQoQV2F0Y2hEaXJSZXNwb25zZRI4CgVzdGFydBgBIAEoCzInLmZpbGVzeXN0ZW0uV2F0Y2hEaXJSZXNwb25zZS5TdGFydEV2ZW50SAASMQoKZmlsZXN5c3RlbRgCIAEoCzIbLmZpbGVzeXN0ZW0uRmlsZXN5c3RlbUV2ZW50SAASOwoJa2VlcGFsaXZlGAMgASgLMiYuZmlsZXN5c3RlbS5XYXRjaERpclJlc3BvbnNlLktlZXBBbGl2ZUgAGgwKClN0YXJ0RXZlbnQaCwoJS2VlcEFsaXZlQgcKBWV2ZW50IjcKFENyZWF0ZVdhdGNoZXJSZXF1ZXN0EgwKBHBhdGgYASABKAkSEQoJcmVjdXJzaXZlGAIgASgIIisKFUNyZWF0ZVdhdGNoZXJSZXNwb25zZRISCgp3YXRjaGVyX2lkGAEgASgJIi0KF0dldFdhdGNoZXJFdmVudHNSZXF1ZXN0EhIKCndhdGNoZXJfaWQYASABKAkiRwoYR2V0V2F0Y2hlckV2ZW50c1Jlc3BvbnNlEisKBmV2ZW50cxgBIAMoCzIbLmZpbGVzeXN0ZW0uRmlsZXN5c3RlbUV2ZW50IioKFFJlbW92ZVdhdGNoZXJSZXF1ZXN0EhIKCndhdGNoZXJfaWQYASABKAkiFwoVUmVtb3ZlV2F0Y2hlclJlc3BvbnNlKlIKCEZpbGVUeXBlEhkKFUZJTEVfVFlQRV9VTlNQRUNJRklFRBAAEhIKDkZJTEVfVFlQRV9GSUxFEAESFwoTRklMRV9UWVBFX0RJUkVDVE9SWRACKpgBCglFdmVudFR5cGUSGgoWRVZFTlRfVFlQRV9VTlNQRUNJRklFRBAAEhUKEUVWRU5UX1RZUEVfQ1JFQVRFEAESFAoQRVZFTlRfVFlQRV9XUklURRACEhUKEUVWRU5UX1RZUEVfUkVNT1ZFEAMSFQoRRVZFTlRfVFlQRV9SRU5BTUUQBBIUChBFVkVOVF9UWVBFX0NITU9EEAUynwUKCkZpbGVzeXN0ZW0SOQoEU3RhdBIXLmZpbGVzeXN0ZW0uU3RhdFJlcXVlc3QaGC5maWxlc3lzdGVtLlN0YXRSZXNwb25zZRJCCgdNYWtlRGlyEhouZmlsZXN5c3RlbS5NYWtlRGlyUmVxdWVzdBobLmZpbGVzeXN0ZW0uTWFrZURpclJlc3BvbnNlEjkKBE1vdmUSFy5maWxlc3lzdGVtLk1vdmVSZXF1ZXN0GhguZmlsZXN5c3RlbS5Nb3ZlUmVzcG9uc2USQgoHTGlzdERpchIaLmZpbGVzeXN0ZW0uTGlzdERpclJlcXVlc3QaGy5maWxlc3lzdGVtLkxpc3REaXJSZXNwb25zZRI/CgZSZW1vdmUSGS5maWxlc3lzdGVtLlJlbW92ZVJlcXVlc3QaGi5maWxlc3lzdGVtLlJlbW92ZVJlc3BvbnNlEkcKCFdhdGNoRGlyEhsuZmlsZXN5c3RlbS5XYXRjaERpclJlcXVlc3QaHC5maWxlc3lzdGVtLldhdGNoRGlyUmVzcG9uc2UwARJUCg1DcmVhdGVXYXRjaGVyEiAuZmlsZXN5c3RlbS5DcmVhdGVXYXRjaGVyUmVxdWVzdBohLmZpbGVzeXN0ZW0uQ3JlYXRlV2F0Y2hlclJlc3BvbnNlEl0KEEdldFdhdGNoZXJFdmVudHMSIy5maWxlc3lzdGVtLkdldFdhdGNoZXJFdmVudHNSZXF1ZXN0GiQuZmlsZXN5c3RlbS5HZXRXYXRjaGVyRXZlbnRzUmVzcG9uc2USVAoNUmVtb3ZlV2F0Y2hlchIgLmZpbGVzeXN0ZW0uUmVtb3ZlV2F0Y2hlclJlcXVlc3QaIS5maWxlc3lzdGVtLlJlbW92ZVdhdGNoZXJSZXNwb25zZUJpCg5jb20uZmlsZXN5c3RlbUIPRmlsZXN5c3RlbVByb3RvUAGiAgNGWFiqAgpGaWxlc3lzdGVtygIKRmlsZXN5c3RlbeICFkZpbGVzeXN0ZW1cR1BCTWV0YWRhdGHqAgpGaWxlc3lzdGVtYgZwcm90bzM");
13+
fileDesc("ChtmaWxlc3lzdGVtL2ZpbGVzeXN0ZW0ucHJvdG8SCmZpbGVzeXN0ZW0iMgoLTW92ZVJlcXVlc3QSDgoGc291cmNlGAEgASgJEhMKC2Rlc3RpbmF0aW9uGAIgASgJIjQKDE1vdmVSZXNwb25zZRIkCgVlbnRyeRgBIAEoCzIVLmZpbGVzeXN0ZW0uRW50cnlJbmZvIh4KDk1ha2VEaXJSZXF1ZXN0EgwKBHBhdGgYASABKAkiNwoPTWFrZURpclJlc3BvbnNlEiQKBWVudHJ5GAEgASgLMhUuZmlsZXN5c3RlbS5FbnRyeUluZm8iHQoNUmVtb3ZlUmVxdWVzdBIMCgRwYXRoGAEgASgJIhAKDlJlbW92ZVJlc3BvbnNlIhsKC1N0YXRSZXF1ZXN0EgwKBHBhdGgYASABKAkiNAoMU3RhdFJlc3BvbnNlEiQKBWVudHJ5GAEgASgLMhUuZmlsZXN5c3RlbS5FbnRyeUluZm8iSwoJRW50cnlJbmZvEgwKBG5hbWUYASABKAkSIgoEdHlwZRgCIAEoDjIULmZpbGVzeXN0ZW0uRmlsZVR5cGUSDAoEcGF0aBgDIAEoCSItCg5MaXN0RGlyUmVxdWVzdBIMCgRwYXRoGAEgASgJEg0KBWRlcHRoGAIgASgNIjkKD0xpc3REaXJSZXNwb25zZRImCgdlbnRyaWVzGAEgAygLMhUuZmlsZXN5c3RlbS5FbnRyeUluZm8iMgoPV2F0Y2hEaXJSZXF1ZXN0EgwKBHBhdGgYASABKAkSEQoJcmVjdXJzaXZlGAIgASgIIkQKD0ZpbGVzeXN0ZW1FdmVudBIMCgRuYW1lGAEgASgJEiMKBHR5cGUYAiABKA4yFS5maWxlc3lzdGVtLkV2ZW50VHlwZSLgAQoQV2F0Y2hEaXJSZXNwb25zZRI4CgVzdGFydBgBIAEoCzInLmZpbGVzeXN0ZW0uV2F0Y2hEaXJSZXNwb25zZS5TdGFydEV2ZW50SAASMQoKZmlsZXN5c3RlbRgCIAEoCzIbLmZpbGVzeXN0ZW0uRmlsZXN5c3RlbUV2ZW50SAASOwoJa2VlcGFsaXZlGAMgASgLMiYuZmlsZXN5c3RlbS5XYXRjaERpclJlc3BvbnNlLktlZXBBbGl2ZUgAGgwKClN0YXJ0RXZlbnQaCwoJS2VlcEFsaXZlQgcKBWV2ZW50IjcKFENyZWF0ZVdhdGNoZXJSZXF1ZXN0EgwKBHBhdGgYASABKAkSEQoJcmVjdXJzaXZlGAIgASgIIisKFUNyZWF0ZVdhdGNoZXJSZXNwb25zZRISCgp3YXRjaGVyX2lkGAEgASgJIi0KF0dldFdhdGNoZXJFdmVudHNSZXF1ZXN0EhIKCndhdGNoZXJfaWQYASABKAkiRwoYR2V0V2F0Y2hlckV2ZW50c1Jlc3BvbnNlEisKBmV2ZW50cxgBIAMoCzIbLmZpbGVzeXN0ZW0uRmlsZXN5c3RlbUV2ZW50IioKFFJlbW92ZVdhdGNoZXJSZXF1ZXN0EhIKCndhdGNoZXJfaWQYASABKAkiFwoVUmVtb3ZlV2F0Y2hlclJlc3BvbnNlKlIKCEZpbGVUeXBlEhkKFUZJTEVfVFlQRV9VTlNQRUNJRklFRBAAEhIKDkZJTEVfVFlQRV9GSUxFEAESFwoTRklMRV9UWVBFX0RJUkVDVE9SWRACKpgBCglFdmVudFR5cGUSGgoWRVZFTlRfVFlQRV9VTlNQRUNJRklFRBAAEhUKEUVWRU5UX1RZUEVfQ1JFQVRFEAESFAoQRVZFTlRfVFlQRV9XUklURRACEhUKEUVWRU5UX1RZUEVfUkVNT1ZFEAMSFQoRRVZFTlRfVFlQRV9SRU5BTUUQBBIUChBFVkVOVF9UWVBFX0NITU9EEAUynwUKCkZpbGVzeXN0ZW0SOQoEU3RhdBIXLmZpbGVzeXN0ZW0uU3RhdFJlcXVlc3QaGC5maWxlc3lzdGVtLlN0YXRSZXNwb25zZRJCCgdNYWtlRGlyEhouZmlsZXN5c3RlbS5NYWtlRGlyUmVxdWVzdBobLmZpbGVzeXN0ZW0uTWFrZURpclJlc3BvbnNlEjkKBE1vdmUSFy5maWxlc3lzdGVtLk1vdmVSZXF1ZXN0GhguZmlsZXN5c3RlbS5Nb3ZlUmVzcG9uc2USQgoHTGlzdERpchIaLmZpbGVzeXN0ZW0uTGlzdERpclJlcXVlc3QaGy5maWxlc3lzdGVtLkxpc3REaXJSZXNwb25zZRI/CgZSZW1vdmUSGS5maWxlc3lzdGVtLlJlbW92ZVJlcXVlc3QaGi5maWxlc3lzdGVtLlJlbW92ZVJlc3BvbnNlEkcKCFdhdGNoRGlyEhsuZmlsZXN5c3RlbS5XYXRjaERpclJlcXVlc3QaHC5maWxlc3lzdGVtLldhdGNoRGlyUmVzcG9uc2UwARJUCg1DcmVhdGVXYXRjaGVyEiAuZmlsZXN5c3RlbS5DcmVhdGVXYXRjaGVyUmVxdWVzdBohLmZpbGVzeXN0ZW0uQ3JlYXRlV2F0Y2hlclJlc3BvbnNlEl0KEEdldFdhdGNoZXJFdmVudHMSIy5maWxlc3lzdGVtLkdldFdhdGNoZXJFdmVudHNSZXF1ZXN0GiQuZmlsZXN5c3RlbS5HZXRXYXRjaGVyRXZlbnRzUmVzcG9uc2USVAoNUmVtb3ZlV2F0Y2hlchIgLmZpbGVzeXN0ZW0uUmVtb3ZlV2F0Y2hlclJlcXVlc3QaIS5maWxlc3lzdGVtLlJlbW92ZVdhdGNoZXJSZXNwb25zZUJpCg5jb20uZmlsZXN5c3RlbUIPRmlsZXN5c3RlbVByb3RvUAGiAgNGWFiqAgpGaWxlc3lzdGVtygIKRmlsZXN5c3RlbeICFkZpbGVzeXN0ZW1cR1BCTWV0YWRhdGHqAgpGaWxlc3lzdGVtYgZwcm90bzM");
1414

1515
/**
1616
* @generated from message filesystem.MoveRequest
@@ -184,6 +184,11 @@ export type ListDirRequest = Message<"filesystem.ListDirRequest"> & {
184184
* @generated from field: string path = 1;
185185
*/
186186
path: string;
187+
188+
/**
189+
* @generated from field: uint32 depth = 2;
190+
*/
191+
depth: number;
187192
};
188193

189194
/**

packages/js-sdk/src/sandbox/filesystem/index.ts

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ import {
2626
import { FilesystemEvent, WatchHandle } from './watchHandle'
2727

2828
import { compareVersions } from 'compare-versions'
29-
import { TemplateError } from '../../errors'
29+
import { InvalidArgumentError, TemplateError } from '../../errors'
3030
import { ENVD_VERSION_RECURSIVE_WATCH } from '../../envd/versions'
3131

3232
/**
@@ -87,6 +87,13 @@ export interface FilesystemRequestOpts
8787
user?: Username
8888
}
8989

90+
export interface FilesystemListOpts extends FilesystemRequestOpts {
91+
/**
92+
* Depth of the directory to list.
93+
*/
94+
depth?: number
95+
}
96+
9097
/**
9198
* Options for watching a directory.
9299
*/
@@ -339,10 +346,17 @@ export class Filesystem {
339346
*
340347
* @returns list of entries in the sandbox filesystem directory.
341348
*/
342-
async list(path: string, opts?: FilesystemRequestOpts): Promise<EntryInfo[]> {
349+
async list(path: string, opts?: FilesystemListOpts): Promise<EntryInfo[]> {
350+
if (typeof opts?.depth === 'number' && opts.depth < 1) {
351+
throw new InvalidArgumentError('depth should be at least one')
352+
}
353+
343354
try {
344355
const res = await this.rpc.listDir(
345-
{ path },
356+
{
357+
path,
358+
depth: opts?.depth ?? 1,
359+
},
346360
{
347361
headers: authenticationHeader(opts?.user),
348362
signal: this.connectionConfig.getSignal(opts?.requestTimeoutMs),
Lines changed: 112 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,121 @@
1-
import { assert } from 'vitest'
1+
import { assert, onTestFinished } from 'vitest'
22

3-
import { sandboxTest } from '../../setup.js'
3+
import { sandboxTest, wait } from '../../setup.js'
4+
5+
const parentDirName = 'test_directory'
46

57
sandboxTest('list directory', async ({ sandbox }) => {
6-
const dirName = 'test_directory4'
8+
const homeDirName = '/home/user'
9+
await sandbox.files.makeDir(parentDirName)
10+
await sandbox.files.makeDir(`${parentDirName}/subdir1`)
11+
await sandbox.files.makeDir(`${parentDirName}/subdir2`)
12+
await sandbox.files.makeDir(`${parentDirName}/subdir1/subdir1_1`)
13+
await sandbox.files.makeDir(`${parentDirName}/subdir1/subdir1_2`)
14+
await sandbox.files.makeDir(`${parentDirName}/subdir2/subdir2_1`)
15+
await sandbox.files.makeDir(`${parentDirName}/subdir2/subdir2_2`)
16+
await sandbox.files.write(`${parentDirName}/file1.txt`, 'Hello, world!')
717

8-
await sandbox.files.makeDir(dirName)
18+
const testCases = [
19+
{
20+
test_name: 'default depth (1)',
21+
depth: undefined,
22+
expectedLen: 3,
23+
expectedFileNames: ['file1.txt', 'subdir1', 'subdir2'],
24+
expectedFileTypes: ['file', 'dir', 'dir'],
25+
expectedFilePaths: [
26+
`${homeDirName}/${parentDirName}/file1.txt`,
27+
`${homeDirName}/${parentDirName}/subdir1`,
28+
`${homeDirName}/${parentDirName}/subdir2`,
29+
],
30+
},
31+
{
32+
test_name: 'explicit depth 1',
33+
depth: 1,
34+
expectedLen: 3,
35+
expectedFileNames: ['file1.txt', 'subdir1', 'subdir2'],
36+
expectedFileTypes: ['file', 'dir', 'dir'],
37+
expectedFilePaths: [
38+
`${homeDirName}/${parentDirName}/file1.txt`,
39+
`${homeDirName}/${parentDirName}/subdir1`,
40+
`${homeDirName}/${parentDirName}/subdir2`,
41+
],
42+
},
43+
{
44+
test_name: 'explicit depth 2',
45+
depth: 2,
46+
expectedLen: 7,
47+
expectedFileTypes: ['file', 'dir', 'dir', 'dir', 'dir', 'dir', 'dir'],
48+
expectedFileNames: [
49+
'file1.txt',
50+
'subdir1',
51+
'subdir1_1',
52+
'subdir1_2',
53+
'subdir2',
54+
'subdir2_1',
55+
'subdir2_2',
56+
],
57+
expectedFilePaths: [
58+
`${homeDirName}/${parentDirName}/file1.txt`,
59+
`${homeDirName}/${parentDirName}/subdir1`,
60+
`${homeDirName}/${parentDirName}/subdir1/subdir1_1`,
61+
`${homeDirName}/${parentDirName}/subdir1/subdir1_2`,
62+
`${homeDirName}/${parentDirName}/subdir2`,
63+
`${homeDirName}/${parentDirName}/subdir2/subdir2_1`,
64+
`${homeDirName}/${parentDirName}/subdir2/subdir2_2`,
65+
],
66+
},
67+
{
68+
test_name: 'explicit depth 3 (should be the same as depth 2)',
69+
depth: 3,
70+
expectedLen: 7,
71+
expectedFileTypes: ['file', 'dir', 'dir', 'dir', 'dir', 'dir', 'dir'],
72+
expectedFileNames: [
73+
'file1.txt',
74+
'subdir1',
75+
'subdir1_1',
76+
'subdir1_2',
77+
'subdir2',
78+
'subdir2_1',
79+
'subdir2_2',
80+
],
81+
expectedFilePaths: [
82+
`${homeDirName}/${parentDirName}/file1.txt`,
83+
`${homeDirName}/${parentDirName}/subdir1`,
84+
`${homeDirName}/${parentDirName}/subdir1/subdir1_1`,
85+
`${homeDirName}/${parentDirName}/subdir1/subdir1_2`,
86+
`${homeDirName}/${parentDirName}/subdir2`,
87+
`${homeDirName}/${parentDirName}/subdir2/subdir2_1`,
88+
`${homeDirName}/${parentDirName}/subdir2/subdir2_2`,
89+
],
90+
},
91+
]
992

10-
const files = await sandbox.files.list(dirName)
11-
assert.equal(files.length, 0)
93+
for (const testCase of testCases) {
94+
const files = await sandbox.files.list(
95+
parentDirName,
96+
testCase.depth !== undefined ? { depth: testCase.depth } : undefined
97+
)
98+
assert.equal(files.length, testCase.expectedLen)
1299

13-
await sandbox.files.write('test_directory4/test_file', 'test')
100+
for (let i = 0; i < testCase.expectedFilePaths.length; i++) {
101+
assert.equal(files[i].type, testCase.expectedFileTypes[i])
102+
assert.equal(files[i].name, testCase.expectedFileNames[i])
103+
assert.equal(files[i].path, testCase.expectedFilePaths[i])
104+
}
105+
}
106+
})
14107

15-
const files1 = await sandbox.files.list(dirName)
16-
assert.equal(files1.length, 1)
17-
assert.equal(files1[0].name, 'test_file')
18-
assert.equal(files1[0].type, 'file')
19-
assert.equal(files1[0].path, `/home/user/${dirName}/test_file`)
108+
sandboxTest('list directory with invalid depth', async ({ sandbox }) => {
109+
await sandbox.files.makeDir(parentDirName)
20110

21-
const exists = await sandbox.files.exists(dirName)
22-
assert.isTrue(exists)
111+
try {
112+
await sandbox.files.list(parentDirName, { depth: -1 })
113+
assert.fail('Expected error but none was thrown')
114+
} catch (err) {
115+
const expectedErrorMessage = 'depth should be at least one'
116+
assert.ok(
117+
err.message.includes(expectedErrorMessage),
118+
`expected error message to include "${expectedErrorMessage}"`
119+
)
120+
}
23121
})

0 commit comments

Comments
 (0)