Skip to content

Commit 14a4ae4

Browse files
committed
Add marker for chained filesystems for path passthrough
1 parent e12aa75 commit 14a4ae4

File tree

5 files changed

+54
-5
lines changed

5 files changed

+54
-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: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
from typing import ClassVar
2+
3+
from fsspec import AbstractFileSystem
4+
5+
__all__ = ("ChainedFileSystem",)
6+
7+
8+
class ChainedFileSystem(AbstractFileSystem):
9+
chained: ClassVar[str] = "chained"

fsspec/tests/test_chained.py

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

0 commit comments

Comments
 (0)