Skip to content

Commit cc0749b

Browse files
authored
Merge pull request #142 from RRosio/fs_error
Capture inactive filesystem error
2 parents 2670e09 + 2841d5e commit cc0749b

7 files changed

Lines changed: 128 additions & 22 deletions

File tree

conftest.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,24 @@ def malformed_config(tmp_path: Path):
8989
print(f"Patching jupyter_config_dir to: {config_dir}")
9090

9191

92+
@pytest.fixture(scope="function")
93+
def hdfs_config(tmp_path: Path):
94+
config_dir = tmp_path / "config"
95+
config_dir.mkdir(exist_ok=True)
96+
97+
yaml_content = """sources:
98+
- name: "TestSourceHDF"
99+
path: "hdfs://namenode.example.com:9000"
100+
"""
101+
yaml_file = config_dir / "jupyter-fsspec.yaml"
102+
yaml_file.write_text(yaml_content)
103+
104+
with patch(
105+
"jupyter_fsspec.file_manager.jupyter_config_dir", return_value=str(config_dir)
106+
):
107+
print(f"Patching jupyter_config_dir to: {config_dir}")
108+
109+
92110
@pytest.fixture(scope="function")
93111
def bad_info_config(tmp_path: Path):
94112
config_dir = tmp_path / "config"

jupyter_fsspec/file_manager.py

Lines changed: 38 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -192,14 +192,6 @@ def initialize_filesystems(self):
192192
)
193193
continue
194194

195-
fs_class = fsspec.get_filesystem_class(fs_protocol)
196-
if fs_class.async_impl:
197-
fs = fsspec.filesystem(fs_protocol, asynchronous=True, *args, **kwargs)
198-
else:
199-
sync_fs = fsspec.filesystem(fs_protocol, *args, **kwargs)
200-
fs = AsyncFileSystemWrapper(sync_fs)
201-
logger.debug("fs_path: %s", fs_path)
202-
203195
# Store the filesystem instance
204196
fs_info = {
205197
"instance": None,
@@ -211,21 +203,46 @@ def initialize_filesystems(self):
211203
"args": args,
212204
"kwargs": kwargs,
213205
}
214-
new_filesystems[key] = fs_info
215-
fs_class = fsspec.get_filesystem_class(fs_protocol)
216-
if fs_class.async_impl:
217-
fs = FileSystemManager.construct_fs(fs_protocol, True, *args, **kwargs)
218-
fs_info["instance"] = fs
219-
else:
220-
sync_fs = FileSystemManager.construct_fs(
221-
fs_protocol, False, *args, **kwargs
206+
try:
207+
fs_class = fsspec.get_filesystem_class(fs_protocol)
208+
209+
if fs_class.async_impl:
210+
fs = FileSystemManager.construct_fs(
211+
fs_protocol, True, *args, **kwargs
212+
)
213+
fs_info["instance"] = fs
214+
else:
215+
sync_fs = FileSystemManager.construct_fs(
216+
fs_protocol, False, *args, **kwargs
217+
)
218+
fs = AsyncFileSystemWrapper(sync_fs)
219+
fs_info["instance"] = fs
220+
221+
logger.debug(
222+
f"Initialized filesystem '{fs_name}' with protocol '{fs_protocol}' at path '{fs_path}'"
223+
)
224+
except Exception:
225+
fs_info["instance"] = None
226+
logger.error(
227+
f"Failed to initialize filesystem '{fs_name}' at path '{fs_path}'."
222228
)
223-
fs = AsyncFileSystemWrapper(sync_fs)
224-
fs_info["instance"] = fs
225229

226-
logger.debug(
227-
f"Initialized filesystem '{fs_name}' with protocol '{fs_protocol}' at path '{fs_path}'"
228-
)
230+
import traceback
231+
232+
traceback.print_exc()
233+
234+
exc_type, exc_value, exc_tb = sys.exc_info()
235+
error_info = {
236+
"type": exc_type.__name__,
237+
"message": str(exc_value),
238+
"short_traceback": traceback.format_exception_only(
239+
exc_type, exc_value
240+
)[-1].strip(),
241+
"traceback_list": traceback.format_tb(exc_tb),
242+
}
243+
fs_info["error"] = error_info
244+
245+
new_filesystems[key] = fs_info
229246

230247
self.filesystems = new_filesystems
231248
self.name_to_prefix = name_to_prefix

jupyter_fsspec/handlers.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,8 @@ async def get(self):
9696
"canonical_path": fs_info["canonical_path"],
9797
"kwargs": fs_info["kwargs"],
9898
}
99+
if fs_info.get("error", None):
100+
instance["error"] = fs_info["error"]
99101
file_systems.append(instance)
100102

101103
self.set_status(200)

jupyter_fsspec/tests/test_api.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -424,6 +424,22 @@ async def xtest_action_same_fs_files(fs_manager_instance, jp_fetch):
424424
)
425425

426426

427+
@pytest.mark.no_setup_config_file_fs
428+
async def test_hdfs_config(hdfs_config, jp_fetch):
429+
fetch_config = await jp_fetch("jupyter_fsspec", "config", method="GET")
430+
assert fetch_config.code == 200
431+
config_json = fetch_config.body.decode("utf-8")
432+
config = json.loads(config_json)
433+
content = config["content"]
434+
assert len(content) == 1
435+
item = content[0]
436+
assert item["error"]
437+
assert (
438+
item["error"]["short_traceback"]
439+
== "ModuleNotFoundError: No module named 'pyarrow'"
440+
)
441+
442+
427443
async def test_upload_download(fs_manager_instance, jp_fetch):
428444
fs_manager = await fs_manager_instance
429445
remote_key = "TestSourceAWS"

src/FssFilesysItem.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,10 @@ class FssFilesysItem {
3636

3737
const fsItem = document.createElement('div');
3838
fsItem.classList.add('jfss-fsitem-root');
39+
40+
if ('error' in fsInfo) {
41+
fsItem.classList.add('jfss-fsitem-error');
42+
}
3943
fsItem.addEventListener('mouseenter', this.handleFsysHover.bind(this));
4044
fsItem.addEventListener('mouseleave', this.handleFsysHover.bind(this));
4145
fsItem.dataset.fssname = fsInfo.name;
@@ -186,7 +190,11 @@ class FssFilesysItem {
186190
protocol: this.filesysProtocol,
187191
path: this.fsInfo.path
188192
});
189-
193+
if (this.fsInfo.error) {
194+
this.logger.error('Inactive filesystem', {
195+
...this.fsInfo.error
196+
});
197+
}
190198
this.selected = true;
191199
this.filesysClicked.emit(this.fsInfo);
192200
}

style/base.css

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,10 @@
159159
background-color: var(--jp-layout-color2);
160160
}
161161

162+
.jfss-fsitem-error {
163+
color: darkgrey;
164+
}
165+
162166
.jfss-browseAreaLabel {
163167
width: 100%;
164168
margin: 0 0.5rem 0.5rem 0;

ui-tests/tests/filesystem_interaction.test.ts

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,23 @@ const emptyConfig = {
6060
content: []
6161
};
6262

63+
const hdfsConfig = {
64+
status: 'success',
65+
description: 'Retrieved available filesystems from configuration file.',
66+
content: [
67+
{
68+
key: 'myhdfs',
69+
name: 'myhdfs',
70+
path: 'hdfs://namenode.example.com:9000',
71+
prefix_path: 'myhdfs',
72+
canonical_path: 'hdfs://namenode.example.com:9000',
73+
args: [],
74+
kwargs: {},
75+
error: { name: 'ImportError' }
76+
}
77+
]
78+
};
79+
6380
const rootMyMemFs = {
6481
content: [
6582
{
@@ -151,6 +168,30 @@ test('test open jupyterFsspec with empty config', async ({ page }) => {
151168
await expect.soft(page.locator('.jfss-resultarea')).toBeVisible();
152169
});
153170

171+
test('test open jupyterFsspec with hdfs config', async ({ page }) => {
172+
await page.route('http://localhost:8888/jupyter_fsspec/config?**', route => {
173+
route.fulfill({
174+
status: 200,
175+
contentType: 'application/json',
176+
body: JSON.stringify(hdfsConfig)
177+
});
178+
});
179+
180+
await page.goto();
181+
await page.getByText('FSSpec', { exact: true }).click();
182+
183+
// verify filesystem item was created
184+
await expect.soft(page.locator('.jfss-fsitem-root')).toBeVisible();
185+
186+
// filesystem details should match as expected
187+
await expect.soft(page.locator('.jfss-fsitem-error')).toBeVisible();
188+
await expect.soft(page.locator('.jfss-fsitem-name')).toBeVisible();
189+
await expect.soft(page.locator('.jfss-fsitem-name')).toHaveText('myhdfs');
190+
await expect
191+
.soft(page.locator('.jfss-fsitem-protocol'))
192+
.toHaveText('Path: myhdfs');
193+
});
194+
154195
test('test memory filesystem with mock config data', async ({ page }) => {
155196
await page.goto();
156197
await page.getByText('FSSpec', { exact: true }).click();

0 commit comments

Comments
 (0)