Repository: fsspec/filesystem_spec

Issue:
Docs could use an example for callbacks.
Hi !

Thank you for fsspec ! :heart: 🙏 

I think the docs could use an example for callbacks. I've come up with this little hook with `tqdm`, not sure if it's the right way to do it:
```python

import tqdm
import fsspec


class FsspecTqdmCallback(tqdm.tqdm):
    """Provides `update_to(n)` which uses `tqdm.update(delta_n)`."""

    def update_to(self, total_size, value, **kwargs):
        """
        total_size  : int
            Total size (in tqdm units)
        value  : int, optional
            Number of blocks transferred so far.
        """
        self.total = total_size
        return self.update(value - self.n)


storage_opts = {
    "key": KEY,
    "secret": SECRET_KEY,
}

fs = fsspec.filesystem("s3", **storage_opts)
path2distant_data = "s3://your-path"
with FsspecTqdmCallback(
    desc="test",
) as pbar:
    fs.upload(
        "local_path/",
        path2distant_data,
        recursive=True,
        callback=fsspec.callbacks.Callback(hooks={"tqdm": pbar.update_to}),
    )

```

Discussion:
martindurant: @efiop @isidentical you probably hav some typical callbacks to compare with
dtrifiro: @martindurant:

https://github.com/iterative/dvc/blob/5ff1d768261c759bbde01d402ada12c79758e7ce/dvc/fs/_callback.py#L6-L32
martindurant: I think it's nice to have, actually - code that lives in the code, and is immediately useful to anyone calling it.

Patch:
diff --git a/README.md b/README.md
index 411240c..bffc475 100644
--- a/README.md
+++ b/README.md
@@ -40,7 +40,7 @@ environments. First, install conda with tox and tox-conda in a base environment
 used to configure a development environment and run tests.
 
 First, setup a development conda environment via ``tox -e {env}`` where ``env`` is one of ``{py36,py37,py38,py39}``.
-This will install fspec dependencies, test & dev tools, and install fsspec in develop
+This will install fsspec dependencies, test & dev tools, and install fsspec in develop
 mode. You may activate the dev environment under ``.tox/{env}`` via ``conda activate .tox/{env}``.
 
 ### Testing
diff --git a/docs/source/api.rst b/docs/source/api.rst
index 81639a7..5d379c4 100644
--- a/docs/source/api.rst
+++ b/docs/source/api.rst
@@ -51,6 +51,7 @@ Base Classes
    fsspec.callbacks.Callback
    fsspec.callbacks.NoOpCallback
    fsspec.callbacks.DotPrinterCallback
+   fsspec.callbacks.TqdmCallback
 
 .. autoclass:: fsspec.spec.AbstractFileSystem
    :members:
@@ -96,6 +97,9 @@ Base Classes
 .. autoclass:: fsspec.callbacks.DotPrinterCallback
    :members:
 
+.. autoclass:: fsspec.callbacks.TqdmCallback
+   :members:
+
 .. _implementations:
 
 Built-in Implementations
diff --git a/docs/source/features.rst b/docs/source/features.rst
index 2f309d5..700d84f 100644
--- a/docs/source/features.rst
+++ b/docs/source/features.rst
@@ -399,3 +399,5 @@ feature is new and experimental and supported by varying amounts in the
 backends.
 
 See the docstrings in the callbacks module for further details.
+``fsspec.callbacks.TqdmCallback`` can be used to display a progress bar using
+tqdm.
diff --git a/fsspec/callbacks.py b/fsspec/callbacks.py
index f8312ad..30590b2 100644
--- a/fsspec/callbacks.py
+++ b/fsspec/callbacks.py
@@ -177,4 +177,44 @@ class DotPrinterCallback(Callback):
         print(self.chr, end="")
 
 
+class TqdmCallback(Callback):
+    """
+    A callback to display a progress bar using tqdm
+
+    Examples
+    --------
+    >>> import fsspec
+    >>> from fsspec.callbacks import TqdmCallback
+    >>> fs = fsspec.filesystem("memory")
+    >>> path2distant_data = "/your-path"
+    >>> fs.upload(
+            ".",
+            path2distant_data,
+            recursive=True,
+            callback=TqdmCallback(),
+        )
+    """
+
+    def __init__(self, *args, **kwargs):
+        try:
+            import tqdm
+
+            self._tqdm = tqdm
+        except ImportError as exce:
+            raise ImportError(
+                "Using TqdmCallback requires tqdm to be installed"
+            ) from exce
+        super().__init__(*args, **kwargs)
+
+    def set_size(self, size):
+        self.tqdm = self._tqdm.tqdm(desc="test", total=size)
+
+    def relative_update(self, inc=1):
+        self.tqdm.update(inc)
+
+    def __del__(self):
+        self.tqdm.close()
+        self.tqdm = None
+
+
 _DEFAULT_CALLBACK = NoOpCallback()
diff --git a/fsspec/mapping.py b/fsspec/mapping.py
index d6346f4..8bad2de 100644
--- a/fsspec/mapping.py
+++ b/fsspec/mapping.py
@@ -187,7 +187,7 @@ def maybe_convert(value):
 
 
 def get_mapper(
-    url,
+    url="",
     check=False,
     create=False,
     missing_exceptions=None,
diff --git a/fsspec/spec.py b/fsspec/spec.py
index fa8feb3..7f9a241 100644
--- a/fsspec/spec.py
+++ b/fsspec/spec.py
@@ -1153,7 +1153,7 @@ class AbstractFileSystem(metaclass=_Cached):
         # all instances already also derive from pyarrow
         return self
 
-    def get_mapper(self, root, check=False, create=False):
+    def get_mapper(self, root="", check=False, create=False):
         """Create key/value store based on this file-system
 
         Makes a MutableMapping interface to the FS at the given root path.
diff --git a/setup.py b/setup.py
index 70f99f4..04251e0 100644
--- a/setup.py
+++ b/setup.py
@@ -54,6 +54,7 @@ setup(
         "fuse": ["fusepy"],
         "libarchive": ["libarchive-c"],
         "gui": ["panel"],
+        "tqdm": ["tqdm"],
     },
     zip_safe=False,
 )


Test patch:
diff --git a/fsspec/implementations/tests/test_archive.py b/fsspec/implementations/tests/test_archive.py
index 816f826..abc6d35 100644
--- a/fsspec/implementations/tests/test_archive.py
+++ b/fsspec/implementations/tests/test_archive.py
@@ -249,7 +249,7 @@ class TestAnyArchive:
     def test_mapping(self, scenario: ArchiveTestScenario):
         with scenario.provider(archive_data) as archive:
             fs = fsspec.filesystem(scenario.protocol, fo=archive)
-            m = fs.get_mapper("")
+            m = fs.get_mapper()
             assert list(m) == ["a", "b", "deeply/nested/path"]
             assert m["b"] == archive_data["b"]
 
diff --git a/fsspec/implementations/tests/test_memory.py b/fsspec/implementations/tests/test_memory.py
index c647fac..7cf29e4 100644
--- a/fsspec/implementations/tests/test_memory.py
+++ b/fsspec/implementations/tests/test_memory.py
@@ -9,7 +9,7 @@ def test_1(m):
     files = m.find("")
     assert files == ["/afiles/and/another", "/somefile"]
 
-    files = sorted(m.get_mapper("/"))
+    files = sorted(m.get_mapper())
     assert files == ["afiles/and/another", "somefile"]
 
 
diff --git a/fsspec/tests/test_mapping.py b/fsspec/tests/test_mapping.py
index 71de1e0..ed5a3b0 100644
--- a/fsspec/tests/test_mapping.py
+++ b/fsspec/tests/test_mapping.py
@@ -5,6 +5,7 @@ import sys
 import pytest
 
 import fsspec
+from fsspec.implementations.local import LocalFileSystem
 from fsspec.implementations.memory import MemoryFileSystem
 
 
@@ -143,3 +144,8 @@ def test_setitem_numpy():
         dtype="<m8[ns]",
     )  # timedelta64 scalar
     assert m["c"] == b',M"\x9e\xc6\x99A\x065\x1c\xf0Rn4\xcb+'
+
+
+def test_empty_url():
+    m = fsspec.get_mapper()
+    assert isinstance(m.fs, LocalFileSystem)