Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
b4c86e0
arrow: implement arrow wrapper
jorisvandenbossche Sep 13, 2021
1131c8c
arrow: share make_path_posix with localfs
isidentical Sep 13, 2021
4aa8dbc
arrow: get rid of LocalFileOpener, migrate to fs.open_*_stream
isidentical Sep 13, 2021
a0a37f8
arrow: remove debug statements
isidentical Sep 13, 2021
c1ac73d
arrow: cleanup info()
isidentical Sep 13, 2021
a0340e5
arrow: more cleanups to satisfy linters
isidentical Sep 13, 2021
7b0e3f2
arrow: implement rm_file, get rid of some methods
isidentical Sep 13, 2021
7d5149f
arrow: use self.fs.move instead of os.rename
isidentical Sep 13, 2021
3e1878d
fs: use fs.copy_file instead of shutil.copyfile
isidentical Sep 13, 2021
918bd25
arrow: remove auto_mkdir related logic since this is not localfs
isidentical Sep 13, 2021
b7ce601
arrow: support different schemes as well
isidentical Sep 13, 2021
2450e57
arrow: fallback on base when _parent() is called
isidentical Sep 13, 2021
a384fb2
arrow: implement ArrowFile, mirror_from, and exception management
isidentical Sep 13, 2021
a3afee5
arrow: implement clean exists(), and other minor stuff
isidentical Sep 13, 2021
d6758c1
arrow: implement get_kwargs_from_url
isidentical Sep 13, 2021
d0599ef
arrow: add basic fs tests
isidentical Sep 13, 2021
0f9c36a
arrow: register / document
isidentical Sep 13, 2021
2919679
arrow: skip when pyarrow is not available
isidentical Sep 13, 2021
f0544aa
arrow: drop Wrapper from HadoopFileSystem
isidentical Sep 14, 2021
a02969a
arrow: add test_move_recursive
isidentical Sep 14, 2021
b0c16d9
arrow: test for mirror_from
isidentical Sep 14, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions docs/source/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,8 @@ Built-in Implementations
.. autosummary::
fsspec.implementations.ftp.FTPFileSystem
fsspec.implementations.hdfs.PyArrowHDFS
fsspec.implementations.arrow.ArrowFSWrapper
fsspec.implementations.arrow.HadoopFileSystem
Comment thread
isidentical marked this conversation as resolved.
fsspec.implementations.dask.DaskWorkerFileSystem
fsspec.implementations.http.HTTPFileSystem
fsspec.implementations.local.LocalFileSystem
Expand All @@ -121,6 +123,12 @@ Built-in Implementations
.. autoclass:: fsspec.implementations.hdfs.PyArrowHDFS
:members: __init__

.. autoclass:: fsspec.implementations.hdfs.ArrowFSWrapper
:members: __init__

.. autoclass:: fsspec.implementations.hdfs.HadoopFileSystem
:members: __init__

.. autoclass:: fsspec.implementations.dask.DaskWorkerFileSystem
:members: __init__

Expand Down
241 changes: 241 additions & 0 deletions fsspec/implementations/arrow.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,241 @@
import errno
import io
import os
import secrets
import shutil
from contextlib import suppress
from functools import wraps

from fsspec.spec import AbstractFileSystem
from fsspec.utils import infer_storage_options, mirror_from, stringify_path


def wrap_exceptions(func):
@wraps(func)
def wrapper(*args, **kwargs):
try:
return func(*args, **kwargs)
except OSError as exception:
if not exception.args:
raise

message, *args = exception.args
if isinstance(message, str) and "does not exist" in message:
raise FileNotFoundError(errno.ENOENT, message) from exception
else:
raise

return wrapper
Comment thread
isidentical marked this conversation as resolved.


class ArrowFSWrapper(AbstractFileSystem):
"""FSSpec-compatible wrapper of pyarrow.fs.FileSystem.

Parameters
----------
fs : pyarrow.fs.FileSystem

"""

root_marker = "/"

def __init__(self, fs, **kwargs):
self.fs = fs
super().__init__(**kwargs)

@classmethod
def _strip_protocol(cls, path):
path = stringify_path(path)
if "://" in path:
_, _, path = path.partition("://")

return path

def ls(self, path, detail=False, **kwargs):
from pyarrow.fs import FileSelector

entries = [
self._make_entry(entry)
for entry in self.fs.get_file_info(FileSelector(path))
]
if detail:
return entries
else:
return [entry["name"] for entry in entries]

def info(self, path, **kwargs):
path = self._strip_protocol(path)
[info] = self.fs.get_file_info([path])
return self._make_entry(info)

def exists(self, path):
path = self._strip_protocol(path)
try:
self.info(path)
except FileNotFoundError:
return False
else:
return True

def _make_entry(self, info):
from pyarrow.fs import FileType

if info.type is FileType.Directory:
kind = "directory"
elif info.type is FileType.File:
kind = "file"
elif info.type is FileType.NotFound:
raise FileNotFoundError(errno.ENOENT, os.strerror(errno.ENOENT), info.path)
else:
kind = "other"

return {
"name": info.path,
"size": info.size,
"type": kind,
"mtime": info.mtime,
}

@wrap_exceptions
def cp_file(self, path1, path2, **kwargs):
path1 = self._strip_protocol(path1).rstrip("/")
path2 = self._strip_protocol(path2).rstrip("/")

with self._open(path1, "rb") as lstream:
tmp_fname = "/".join([self._parent(path2), f".tmp.{secrets.token_hex(16)}"])
try:
with self.open(tmp_fname, "wb") as rstream:
shutil.copyfileobj(lstream, rstream)
self.fs.move(tmp_fname, path2)
except BaseException: # noqa
with suppress(FileNotFoundError):
self.fs.delete_file(tmp_fname)
raise

@wrap_exceptions
def mv(self, path1, path2, **kwargs):
path1 = self._strip_protocol(path1).rstrip("/")
path2 = self._strip_protocol(path2).rstrip("/")
self.fs.move(path1, path2)

mv_file = mv

@wrap_exceptions
def rm_file(self, path):
path = self._strip_protocol(path)
self.fs.delete_file(path)

@wrap_exceptions
def rm(self, path, recursive=False, maxdepth=None):
path = self._strip_protocol(path).rstrip("/")
if self.isdir(path):
if recursive:
self.fs.delete_dir(path)
else:
raise ValueError("Can't delete directories without recursive=False")
else:
self.fs.delete_file(path)

@wrap_exceptions
def _open(self, path, mode="rb", block_size=None, **kwargs):
if mode == "rb":
stream = self.fs.open_input_stream(path)
elif mode == "wb":
stream = self.fs.open_output_stream(path)
else:
raise ValueError(f"unsupported mode for Arrow filesystem: {mode!r}")

return ArrowFile(self, stream, path, mode, block_size, **kwargs)

@wrap_exceptions
def mkdir(self, path, create_parents=True, **kwargs):
path = self._strip_protocol(path)
if create_parents:
self.makedirs(path, exist_ok=True)
else:
self.fs.create_dir(path, recursive=False)

@wrap_exceptions
def makedirs(self, path, exist_ok=False):
path = self._strip_protocol(path)
self.fs.create_dir(path, recursive=True)

@wrap_exceptions
def rmdir(self, path):
path = self._strip_protocol(path)
self.fs.delete_dir(path)


@mirror_from(
"stream", ["read", "seek", "tell", "write", "readable", "writable", "close"]
)
class ArrowFile(io.IOBase):
def __init__(self, fs, stream, path, mode, block_size=None, **kwargs):
self.path = path
self.mode = mode

self.fs = fs
self.stream = stream

self.blocksize = self.block_size = block_size
self.kwargs = kwargs

def __enter__(self):
return self

def __exit__(self, *args):
return self.close()


class HadoopFileSystem(ArrowFSWrapper):
"""A wrapper on top of the pyarrow.fs.HadoopFileSystem
to connect it's interface with fsspec"""

protocol = "hdfs"

def __init__(
self,
host="default",
port=0,
user=None,
kerb_ticket=None,
extra_conf=None,
**kwargs,
):
"""

Parameters
----------
host: str
Hostname, IP or "default" to try to read from Hadoop config
port: int
Port to connect on, or default from Hadoop config if 0
user: str or None
If given, connect as this username
kerb_ticket: str or None
If given, use this ticket for authentication
extra_conf: None or dict
Passed on to HadoopFileSystem
"""
from pyarrow.fs import HadoopFileSystem

fs = HadoopFileSystem(
host=host,
port=port,
user=user,
kerb_ticket=kerb_ticket,
extra_conf=extra_conf,
)
super().__init__(fs=fs, **kwargs)

@staticmethod
def _get_kwargs_from_urls(path):
ops = infer_storage_options(path)
out = {}
if ops.get("host", None):
out["host"] = ops["host"]
if ops.get("username", None):
out["user"] = ops["username"]
if ops.get("port", None):
out["port"] = ops["port"]
return out
Loading