-
Notifications
You must be signed in to change notification settings - Fork 341
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #675 from isidentical/progress
callbacks: Implement fsspec.callbacks
- Loading branch information
Showing
13 changed files
with
483 additions
and
44 deletions.
There are no files selected for viewing
This file contains 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 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 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 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,199 @@ | ||
from .utils import stringify_path | ||
|
||
|
||
class Callback: | ||
__slots__ = ["properties", "hooks"] | ||
|
||
def __init__(self, properties=None, **hooks): | ||
self.hooks = hooks | ||
self.properties = properties or {} | ||
|
||
def call(self, hook, *args, **kwargs): | ||
"""Make a callback to a hook named ``hook``. If it can't | ||
find the hook, then this function will return None. Otherwise | ||
the return value of the hook will be used. | ||
Parameters | ||
---------- | ||
hook: str | ||
The name of the hook | ||
*args: Any | ||
All the positional arguments that will be passed | ||
to the ``hook``, if found. | ||
**kwargs: Any | ||
All the keyword arguments that will be passed | ||
tot the ``hook``, if found. | ||
""" | ||
callback = self.hooks.get(hook) | ||
if callback is not None: | ||
return callback(*args, **kwargs) | ||
|
||
def lazy_call(self, hook, func, *args, **kwargs): | ||
"""Make a callback to a hook named ``hook`` with a | ||
single value that will be lazily obtained from a call | ||
to the ``func`` with ``*args`` and ``**kwargs``. | ||
This method should be used for expensive operations, | ||
e.g ``len(data)`` since if there is no hook for the | ||
given ``hook`` parameter, that operation will be wasted. | ||
With this method, it will only evaluate function, if there | ||
is a hook attached to the given ``hook`` parameter. | ||
Parameters | ||
---------- | ||
hook: str | ||
The name of the hook | ||
func: Callable[..., Any] | ||
Function that will be called and passed as the argument | ||
to the hook, if found. | ||
*args: Any | ||
All the positional arguments that will be passed | ||
to the ``func``, if ``hook`` is found. | ||
**kwargs: Any | ||
All the keyword arguments that will be passed | ||
tot the ``func``, if ``hook` is found. | ||
""" | ||
callback = self.hooks.get(hook) | ||
if callback is not None: | ||
return callback(func(*args, **kwargs)) | ||
|
||
def wrap(self, iterable): | ||
"""Wrap an iterable to send ``relative_update`` hook | ||
on each iterations. | ||
Parameters | ||
---------- | ||
iterable: Iterable | ||
The iterable that is being wrapped | ||
""" | ||
for item in iterable: | ||
self.call("relative_update", 1) | ||
yield item | ||
|
||
|
||
class NoOpCallback(Callback): | ||
def call(self, hook, *args, **kwargs): | ||
return None | ||
|
||
def lazy_call(self, hook, *args, **kwargs): | ||
return None | ||
|
||
|
||
_DEFAULT_CALLBACK = NoOpCallback() | ||
|
||
|
||
def callback( | ||
*, | ||
set_size=None, | ||
relative_update=None, | ||
absolute_update=None, | ||
branch=None, | ||
properties=None, | ||
**hooks, | ||
): | ||
"""Create a new callback for filesystem APIs. | ||
Parameters | ||
---------- | ||
set_size: Callable[[Optional[int]], None] (optional) | ||
When transferring something quantifiable (e.g bytes in a file, or | ||
number of files), this hook will be called with the total number of | ||
items. Might set something to None, in that case it should be ignored. | ||
relative_update: Callable[[int], None] (optional) | ||
Update the total transferred items relative to the previous position. | ||
If the current cursor is at N, and a relative_update(Q) happens then | ||
the current cursor should now point at the N+Q. | ||
absolute_update: Callable[[int], None] (optional) | ||
Update the total transferred items to an absolute position. If | ||
the current cursor is at N, and a absolute_update(Q) happens then | ||
the current cursor should now point at the Q. If another one happens | ||
it will override the current value. | ||
branch: Callable[[os.PathLike, os.PathLike], Optional[fsspec.callbacks.Callback]] (optional) | ||
When some operations need branching (e.g each ``put()``/``get()` | ||
operation have their own callbacks, but they will also need to | ||
branch out for ``put_file()``/``get_file()`` since those might | ||
require additional child callbacks) the branch hook will be called | ||
with the paths that are being transffered and it is expected to | ||
either return a new fsspec.callbacks.Callback instance or None. If | ||
``stringify_paths`` property is set, the paths will be casted to | ||
string, and if ``posixify_paths`` property is set both arguments | ||
will be sanitized to the posix convention. | ||
properties: Dict[str, Any] (optional) | ||
A mapping of config option (callback related) to their values. | ||
hooks: Callable[..., Any] | ||
Optional hooks that are not generally available. | ||
Returns | ||
------- | ||
fsspec.callback.Callback | ||
""" # noqa: E501 | ||
|
||
return Callback( | ||
properties=properties, | ||
set_size=set_size, | ||
relative_update=relative_update, | ||
absolute_update=absolute_update, | ||
branch=branch, | ||
**hooks, | ||
) | ||
|
||
|
||
def as_callback(maybe_callback): | ||
"""Return the no-op callback if the maybe_callback parameter is None | ||
Parameters | ||
---------- | ||
maybe_callback: fsspec.callback.Callback or None | ||
Returns | ||
------- | ||
fsspec.callback.Callback | ||
""" | ||
if maybe_callback is None: | ||
return _DEFAULT_CALLBACK | ||
else: | ||
return maybe_callback | ||
|
||
|
||
def branch(callback, path_1, path_2, kwargs=None): | ||
"""Branch out from an existing callback. | ||
Parameters | ||
---------- | ||
callback: fsspec.callback.Callback | ||
Parent callback | ||
path_1: os.PathLike | ||
Left path | ||
path_2: os.PathLike | ||
Right path | ||
kwargs: Dict[str, Any] (optional) | ||
Update the ``callback`` key on the given ``kwargs`` | ||
if there is a brancher attached to the ``callback``. | ||
Returns | ||
------- | ||
fsspec.callback.Callback or None | ||
""" | ||
from .implementations.local import make_path_posix | ||
|
||
if callback.properties.get("stringify_paths"): | ||
path_1 = stringify_path(path_1) | ||
path_2 = stringify_path(path_2) | ||
|
||
if callback.properties.get("posixify_paths"): | ||
path_1 = make_path_posix(path_1) | ||
path_2 = make_path_posix(path_2) | ||
|
||
branched = callback.call("branch", path_1, path_2) | ||
if branched is None or branched is _DEFAULT_CALLBACK: | ||
return None | ||
|
||
if kwargs is not None: | ||
kwargs["callback"] = branched | ||
return branched |
This file contains 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 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 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
Oops, something went wrong.