diff --git a/docs/source/developers/contents.rst b/docs/source/developers/contents.rst index 6910535f30..6c10b84bf2 100644 --- a/docs/source/developers/contents.rst +++ b/docs/source/developers/contents.rst @@ -59,6 +59,10 @@ Models may contain the following entries: | | ``None`` | if any. (:ref:`See | | | | Below`) | +--------------------+------------+-------------------------------+ +| **item_count** | int or | The number of items in a | +| | ``None`` | directory, or ``None`` for | +| | | files and notebooks. | ++--------------------+------------+-------------------------------+ | **format** | unicode or | The format of ``content``, | | | ``None`` | if any. (:ref:`See | | | | Below`) | @@ -110,6 +114,7 @@ model. There are three model types: **notebook**, **file**, and **directory**. - The ``content`` field contains a list of :ref:`content-free` models representing the entities in the directory. - The ``hash`` field is always ``None``. + - The ``item_count`` field contains the number of items in the directory .. note:: diff --git a/jupyter_server/services/contents/filemanager.py b/jupyter_server/services/contents/filemanager.py index 96029d96d6..21452f95e0 100644 --- a/jupyter_server/services/contents/filemanager.py +++ b/jupyter_server/services/contents/filemanager.py @@ -272,6 +272,7 @@ def _base_model(self, path): model["writable"] = self.is_writable(path) model["hash"] = None model["hash_algorithm"] = None + model["item_count"] = None return model @@ -293,9 +294,20 @@ def _dir_model(self, path, content=True): model = self._base_model(path) model["type"] = "directory" model["size"] = None + os_dir = self._get_os_path(path) + dir_contents = os.listdir(os_dir) + filtered_count = 0 + for name in dir_contents: + try: + os_path = os.path.join(os_dir, name) + if self.should_list(name) and (self.allow_hidden or not is_file_hidden(os_path)): + filtered_count += 1 + except OSError as e: + self.log.warning("Error accessing %s: %s", os.path.join(os_dir, name), e) + + model["item_count"] = filtered_count if content: model["content"] = contents = [] - os_dir = self._get_os_path(path) for name in os.listdir(os_dir): try: os_path = os.path.join(os_dir, name) @@ -334,7 +346,6 @@ def _dir_model(self, path, content=True): os_path, exc_info=True, ) - model["format"] = "json" return model @@ -470,6 +481,7 @@ def _save_directory(self, os_path, model, path=""): if not os.path.exists(os_path): with self.perm_to_403(): os.mkdir(os_path) + model["item_count"] = 0 elif not os.path.isdir(os_path): raise web.HTTPError(400, "Not a directory: %s" % (os_path)) else: @@ -765,10 +777,20 @@ async def _dir_model(self, path, content=True): model = self._base_model(path) model["type"] = "directory" model["size"] = None + os_dir = self._get_os_path(path) + dir_contents = await run_sync(os.listdir, os_dir) + filtered_count = 0 + for name in dir_contents: + try: + os_path = os.path.join(os_dir, name) + if self.should_list(name) and (self.allow_hidden or not is_file_hidden(os_path)): + filtered_count += 1 + except OSError as e: + self.log.warning("Error accessing %s: %s", os.path.join(os_dir, name), e) + + model["item_count"] = filtered_count if content: model["content"] = contents = [] - os_dir = self._get_os_path(path) - dir_contents = await run_sync(os.listdir, os_dir) for name in dir_contents: try: os_path = os.path.join(os_dir, name) diff --git a/tests/services/contents/test_manager.py b/tests/services/contents/test_manager.py index 5c3e2eca50..157038764f 100644 --- a/tests/services/contents/test_manager.py +++ b/tests/services/contents/test_manager.py @@ -590,6 +590,7 @@ async def test_get(jp_contents_manager): assert isinstance(model2, dict) assert "name" in model2 assert "path" in model2 + assert "item_count" in model2 assert "content" in model2 assert model2["name"] == "Untitled.ipynb" assert model2["path"] == "{}/{}".format(sub_dir.strip("/"), name) @@ -631,6 +632,7 @@ async def test_get(jp_contents_manager): for key, value in expected_model.items(): assert file_model[key] == value assert "created" in file_model + assert "item_count" in file_model assert "last_modified" in file_model assert file_model["hash"] @@ -639,8 +641,10 @@ async def test_get(jp_contents_manager): _make_dir(cm, "foo/bar") dirmodel = await ensure_async(cm.get("foo")) assert dirmodel["type"] == "directory" + assert "item_count" in dirmodel assert isinstance(dirmodel["content"], list) assert len(dirmodel["content"]) == 3 + assert dirmodel["item_count"] == 3 assert dirmodel["path"] == "foo" assert dirmodel["name"] == "foo" @@ -649,6 +653,7 @@ async def test_get(jp_contents_manager): model2_no_content = await ensure_async(cm.get(sub_dir + name, content=False)) file_model_no_content = await ensure_async(cm.get("foo/untitled.txt", content=False)) sub_sub_dir_no_content = await ensure_async(cm.get("foo/bar", content=False)) + assert "item_count" in model2_no_content assert sub_sub_dir_no_content["path"] == "foo/bar" assert sub_sub_dir_no_content["name"] == "bar"