Skip to content

Commit c23674c

Browse files
Add marker for chained filesystems for path passthrough (#1929)
Co-authored-by: Martin Durant <[email protected]>
1 parent e12aa75 commit c23674c

File tree

5 files changed

+70
-5
lines changed

5 files changed

+70
-5
lines changed

docs/source/features.rst

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -241,7 +241,9 @@ reads the same zip-file, but extracts the CSV files and stores them locally in t
241241
**For developers**: this "chaining" methods works by formatting the arguments passed to ``open_*``
242242
into ``target_protocol`` (a simple string) and ``target_options`` (a dict) and also optionally
243243
``fo`` (target path, if a specific file is required). In order for an implementation to chain
244-
successfully like this, it must look for exactly those named arguments.
244+
successfully like this, it must look for exactly those named arguments. Implementations that
245+
require access to the target path of their nested targets should inherit from ``ChainedFileSystem``,
246+
which will trigger pass-through of the nested path automatically.
245247

246248
Caching Files Locally
247249
---------------------

fsspec/core.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -330,7 +330,7 @@ def open_files(
330330

331331
def _un_chain(path, kwargs):
332332
# Avoid a circular import
333-
from fsspec.implementations.cached import CachingFileSystem
333+
from fsspec.implementations.chained import ChainedFileSystem
334334

335335
if "::" in path:
336336
x = re.compile(".*[^a-z]+.*") # test for non protocol-like single word
@@ -358,7 +358,7 @@ def _un_chain(path, kwargs):
358358
**kws,
359359
)
360360
bit = cls._strip_protocol(bit)
361-
if "target_protocol" not in kw and issubclass(cls, CachingFileSystem):
361+
if "target_protocol" not in kw and issubclass(cls, ChainedFileSystem):
362362
bit = previous_bit
363363
out.append((bit, protocol, kw))
364364
previous_bit = bit

fsspec/implementations/cached.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,14 @@
99
from shutil import rmtree
1010
from typing import TYPE_CHECKING, Any, Callable, ClassVar
1111

12-
from fsspec import AbstractFileSystem, filesystem
12+
from fsspec import filesystem
1313
from fsspec.callbacks import DEFAULT_CALLBACK
1414
from fsspec.compression import compr
1515
from fsspec.core import BaseCache, MMapCache
1616
from fsspec.exceptions import BlocksizeMismatchError
1717
from fsspec.implementations.cache_mapper import create_cache_mapper
1818
from fsspec.implementations.cache_metadata import CacheMetadata
19+
from fsspec.implementations.chained import ChainedFileSystem
1920
from fsspec.implementations.local import LocalFileSystem
2021
from fsspec.spec import AbstractBufferedFile
2122
from fsspec.transaction import Transaction
@@ -39,7 +40,7 @@ def complete(self, commit=True):
3940
self.fs = None # break cycle
4041

4142

42-
class CachingFileSystem(AbstractFileSystem):
43+
class CachingFileSystem(ChainedFileSystem):
4344
"""Locally caching filesystem, layer over any other FS
4445
4546
This class implements chunk-wise local storage of remote files, for quick

fsspec/implementations/chained.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
from typing import ClassVar
2+
3+
from fsspec import AbstractFileSystem
4+
5+
__all__ = ("ChainedFileSystem",)
6+
7+
8+
class ChainedFileSystem(AbstractFileSystem):
9+
"""Chained filesystem base class.
10+
11+
A chained filesystem is designed to be layered over another FS.
12+
This is useful to implement things like caching.
13+
14+
This base class does very little on its own, but is used as a marker
15+
that the class is designed for chaining.
16+
17+
Right now this is only used in `url_to_fs` to provide the path argument
18+
(`fo`) to the chained filesystem from the underlying filesystem.
19+
20+
Additional functionality may be added in the future.
21+
"""
22+
23+
protocol: ClassVar[str] = "chained"

fsspec/tests/test_chained.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import pytest
2+
3+
from fsspec import AbstractFileSystem, filesystem, register_implementation, url_to_fs
4+
from fsspec.implementations.cached import ChainedFileSystem
5+
6+
7+
class MyChainedFS(ChainedFileSystem):
8+
protocol = "mychain"
9+
10+
def __init__(self, target_protocol="", target_options=None, **kwargs):
11+
super().__init__(**kwargs)
12+
self.fs = filesystem(target_protocol, **target_options)
13+
14+
15+
class MyNonChainedFS(AbstractFileSystem):
16+
protocol = "mynonchain"
17+
18+
19+
@pytest.fixture(scope="module")
20+
def register_fs():
21+
register_implementation(MyChainedFS.protocol, MyChainedFS)
22+
register_implementation(MyNonChainedFS.protocol, MyNonChainedFS)
23+
yield
24+
25+
26+
def test_token_passthrough_to_chained(register_fs):
27+
# First, run a sanity check
28+
fs, rest = url_to_fs("mynonchain://path/to/file")
29+
assert isinstance(fs, MyNonChainedFS)
30+
assert fs.protocol == "mynonchain"
31+
assert rest == "path/to/file"
32+
33+
# Now test that the chained FS works
34+
fs, rest = url_to_fs("mychain::mynonchain://path/to/file")
35+
assert isinstance(fs, MyChainedFS)
36+
assert fs.protocol == "mychain"
37+
assert rest == "path/to/file"
38+
assert isinstance(fs.fs, MyNonChainedFS)
39+
assert fs.fs.protocol == "mynonchain"

0 commit comments

Comments
 (0)