-
Notifications
You must be signed in to change notification settings - Fork 465
arrow: introduce Arrow wrapper #754
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
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 1131c8c
arrow: share make_path_posix with localfs
isidentical 4aa8dbc
arrow: get rid of LocalFileOpener, migrate to fs.open_*_stream
isidentical a0a37f8
arrow: remove debug statements
isidentical c1ac73d
arrow: cleanup info()
isidentical a0340e5
arrow: more cleanups to satisfy linters
isidentical 7b0e3f2
arrow: implement rm_file, get rid of some methods
isidentical 7d5149f
arrow: use self.fs.move instead of os.rename
isidentical 3e1878d
fs: use fs.copy_file instead of shutil.copyfile
isidentical 918bd25
arrow: remove auto_mkdir related logic since this is not localfs
isidentical b7ce601
arrow: support different schemes as well
isidentical 2450e57
arrow: fallback on base when _parent() is called
isidentical a384fb2
arrow: implement ArrowFile, mirror_from, and exception management
isidentical a3afee5
arrow: implement clean exists(), and other minor stuff
isidentical d6758c1
arrow: implement get_kwargs_from_url
isidentical d0599ef
arrow: add basic fs tests
isidentical 0f9c36a
arrow: register / document
isidentical 2919679
arrow: skip when pyarrow is not available
isidentical f0544aa
arrow: drop Wrapper from HadoopFileSystem
isidentical a02969a
arrow: add test_move_recursive
isidentical b0c16d9
arrow: test for mirror_from
isidentical File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 | ||
|
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 | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.