dvc-objects 3.0.5__tar.gz → 4.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {dvc-objects-3.0.5 → dvc-objects-4.0.0}/.github/workflows/tests.yml +1 -0
- {dvc-objects-3.0.5/src/dvc_objects.egg-info → dvc-objects-4.0.0}/PKG-INFO +2 -2
- {dvc-objects-3.0.5 → dvc-objects-4.0.0}/pyproject.toml +2 -2
- {dvc-objects-3.0.5 → dvc-objects-4.0.0}/src/dvc_objects/db.py +4 -2
- {dvc-objects-3.0.5 → dvc-objects-4.0.0}/src/dvc_objects/executors.py +1 -1
- {dvc-objects-3.0.5 → dvc-objects-4.0.0}/src/dvc_objects/fs/base.py +17 -14
- dvc-objects-4.0.0/src/dvc_objects/fs/callbacks.py +57 -0
- {dvc-objects-3.0.5 → dvc-objects-4.0.0}/src/dvc_objects/fs/generic.py +44 -25
- {dvc-objects-3.0.5 → dvc-objects-4.0.0}/src/dvc_objects/fs/utils.py +7 -4
- {dvc-objects-3.0.5 → dvc-objects-4.0.0/src/dvc_objects.egg-info}/PKG-INFO +2 -2
- {dvc-objects-3.0.5 → dvc-objects-4.0.0}/src/dvc_objects.egg-info/SOURCES.txt +1 -0
- {dvc-objects-3.0.5 → dvc-objects-4.0.0}/src/dvc_objects.egg-info/requires.txt +1 -1
- dvc-objects-4.0.0/tests/fs/test_callbacks.py +17 -0
- dvc-objects-4.0.0/tests/fs/test_generic.py +120 -0
- dvc-objects-3.0.5/src/dvc_objects/fs/callbacks.py +0 -184
- dvc-objects-3.0.5/tests/fs/test_callbacks.py +0 -148
- {dvc-objects-3.0.5 → dvc-objects-4.0.0}/.cruft.json +0 -0
- {dvc-objects-3.0.5 → dvc-objects-4.0.0}/.gitattributes +0 -0
- {dvc-objects-3.0.5 → dvc-objects-4.0.0}/.github/dependabot.yml +0 -0
- {dvc-objects-3.0.5 → dvc-objects-4.0.0}/.github/workflows/release.yml +0 -0
- {dvc-objects-3.0.5 → dvc-objects-4.0.0}/.github/workflows/update-template.yaml +0 -0
- {dvc-objects-3.0.5 → dvc-objects-4.0.0}/.gitignore +0 -0
- {dvc-objects-3.0.5 → dvc-objects-4.0.0}/.pre-commit-config.yaml +0 -0
- {dvc-objects-3.0.5 → dvc-objects-4.0.0}/CODE_OF_CONDUCT.rst +0 -0
- {dvc-objects-3.0.5 → dvc-objects-4.0.0}/CONTRIBUTING.rst +0 -0
- {dvc-objects-3.0.5 → dvc-objects-4.0.0}/LICENSE +0 -0
- {dvc-objects-3.0.5 → dvc-objects-4.0.0}/README.rst +0 -0
- {dvc-objects-3.0.5 → dvc-objects-4.0.0}/noxfile.py +0 -0
- {dvc-objects-3.0.5 → dvc-objects-4.0.0}/setup.cfg +0 -0
- {dvc-objects-3.0.5 → dvc-objects-4.0.0}/src/dvc_objects/__init__.py +0 -0
- {dvc-objects-3.0.5 → dvc-objects-4.0.0}/src/dvc_objects/_tqdm.py +0 -0
- {dvc-objects-3.0.5 → dvc-objects-4.0.0}/src/dvc_objects/compat.py +0 -0
- {dvc-objects-3.0.5 → dvc-objects-4.0.0}/src/dvc_objects/errors.py +0 -0
- {dvc-objects-3.0.5 → dvc-objects-4.0.0}/src/dvc_objects/fs/__init__.py +0 -0
- {dvc-objects-3.0.5 → dvc-objects-4.0.0}/src/dvc_objects/fs/errors.py +0 -0
- {dvc-objects-3.0.5 → dvc-objects-4.0.0}/src/dvc_objects/fs/local.py +0 -0
- {dvc-objects-3.0.5 → dvc-objects-4.0.0}/src/dvc_objects/fs/memory.py +0 -0
- {dvc-objects-3.0.5 → dvc-objects-4.0.0}/src/dvc_objects/fs/scheme.py +0 -0
- {dvc-objects-3.0.5 → dvc-objects-4.0.0}/src/dvc_objects/fs/system.py +0 -0
- {dvc-objects-3.0.5 → dvc-objects-4.0.0}/src/dvc_objects/obj.py +0 -0
- {dvc-objects-3.0.5 → dvc-objects-4.0.0}/src/dvc_objects/py.typed +0 -0
- {dvc-objects-3.0.5 → dvc-objects-4.0.0}/src/dvc_objects/transfer.py +0 -0
- {dvc-objects-3.0.5 → dvc-objects-4.0.0}/src/dvc_objects.egg-info/dependency_links.txt +0 -0
- {dvc-objects-3.0.5 → dvc-objects-4.0.0}/src/dvc_objects.egg-info/top_level.txt +0 -0
- {dvc-objects-3.0.5 → dvc-objects-4.0.0}/tests/__init__.py +0 -0
- {dvc-objects-3.0.5 → dvc-objects-4.0.0}/tests/benchmarks/__init__.py +0 -0
- {dvc-objects-3.0.5 → dvc-objects-4.0.0}/tests/benchmarks/test_fs.py +0 -0
- {dvc-objects-3.0.5 → dvc-objects-4.0.0}/tests/conftest.py +0 -0
- {dvc-objects-3.0.5 → dvc-objects-4.0.0}/tests/fs/__init__.py +0 -0
- {dvc-objects-3.0.5 → dvc-objects-4.0.0}/tests/fs/test_localfs.py +0 -0
- {dvc-objects-3.0.5 → dvc-objects-4.0.0}/tests/fs/test_system.py +0 -0
- {dvc-objects-3.0.5 → dvc-objects-4.0.0}/tests/fs/test_utils.py +0 -0
- {dvc-objects-3.0.5 → dvc-objects-4.0.0}/tests/test_odb.py +0 -0
- {dvc-objects-3.0.5 → dvc-objects-4.0.0}/tests/test_reflink.py +0 -0
- {dvc-objects-3.0.5 → dvc-objects-4.0.0}/tests/test_transfer.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: dvc-objects
|
|
3
|
-
Version:
|
|
3
|
+
Version: 4.0.0
|
|
4
4
|
Summary: dvc objects - filesystem and object-db level abstractions to use in dvc and dvc-data
|
|
5
5
|
Author-email: Iterative <support@dvc.org>
|
|
6
6
|
License: Apache-2.0
|
|
@@ -17,7 +17,7 @@ Requires-Python: >=3.8
|
|
|
17
17
|
Description-Content-Type: text/x-rst
|
|
18
18
|
License-File: LICENSE
|
|
19
19
|
Requires-Dist: funcy>=1.14; python_version < "3.12"
|
|
20
|
-
Requires-Dist: fsspec>=
|
|
20
|
+
Requires-Dist: fsspec>=2024.2.0
|
|
21
21
|
Provides-Extra: tests
|
|
22
22
|
Requires-Dist: pytest<8,>=7; extra == "tests"
|
|
23
23
|
Requires-Dist: pytest-sugar; extra == "tests"
|
|
@@ -23,7 +23,7 @@ requires-python = ">=3.8"
|
|
|
23
23
|
dynamic = ["version"]
|
|
24
24
|
dependencies = [
|
|
25
25
|
"funcy>=1.14; python_version < '3.12'",
|
|
26
|
-
"fsspec>=
|
|
26
|
+
"fsspec>=2024.2.0",
|
|
27
27
|
]
|
|
28
28
|
|
|
29
29
|
[project.urls]
|
|
@@ -104,7 +104,7 @@ module = [
|
|
|
104
104
|
]
|
|
105
105
|
|
|
106
106
|
[tool.codespell]
|
|
107
|
-
ignore-words-list = "
|
|
107
|
+
ignore-words-list = "cachable,"
|
|
108
108
|
|
|
109
109
|
[tool.ruff]
|
|
110
110
|
ignore = [
|
|
@@ -16,13 +16,15 @@ from typing import (
|
|
|
16
16
|
cast,
|
|
17
17
|
)
|
|
18
18
|
|
|
19
|
+
from fsspec.callbacks import DEFAULT_CALLBACK
|
|
20
|
+
|
|
19
21
|
from .errors import ObjectDBPermissionError
|
|
20
|
-
from .fs.callbacks import DEFAULT_CALLBACK
|
|
21
22
|
from .obj import Object
|
|
22
23
|
|
|
23
24
|
if TYPE_CHECKING:
|
|
25
|
+
from fsspec import Callback
|
|
26
|
+
|
|
24
27
|
from .fs.base import AnyFSPath, FileSystem
|
|
25
|
-
from .fs.callbacks import Callback
|
|
26
28
|
|
|
27
29
|
|
|
28
30
|
logger = logging.getLogger(__name__)
|
|
@@ -21,22 +21,18 @@ from typing import (
|
|
|
21
21
|
Sequence,
|
|
22
22
|
Tuple,
|
|
23
23
|
Union,
|
|
24
|
-
cast,
|
|
25
24
|
overload,
|
|
26
25
|
)
|
|
27
26
|
from urllib.parse import urlsplit, urlunsplit
|
|
28
27
|
|
|
29
28
|
import fsspec
|
|
30
29
|
from fsspec.asyn import get_loop
|
|
30
|
+
from fsspec.callbacks import DEFAULT_CALLBACK
|
|
31
31
|
|
|
32
32
|
from dvc_objects.compat import cached_property
|
|
33
33
|
from dvc_objects.executors import ThreadPoolExecutor, batch_coros
|
|
34
34
|
|
|
35
|
-
from .callbacks import
|
|
36
|
-
DEFAULT_CALLBACK,
|
|
37
|
-
CallbackStream,
|
|
38
|
-
wrap_and_branch_callback,
|
|
39
|
-
)
|
|
35
|
+
from .callbacks import wrap_file
|
|
40
36
|
from .errors import RemoteMissingDepsError
|
|
41
37
|
|
|
42
38
|
if TYPE_CHECKING:
|
|
@@ -637,7 +633,7 @@ class FileSystem:
|
|
|
637
633
|
if size:
|
|
638
634
|
callback.set_size(size)
|
|
639
635
|
if hasattr(from_file, "read"):
|
|
640
|
-
stream =
|
|
636
|
+
stream = wrap_file(from_file, callback)
|
|
641
637
|
self.upload_fobj(stream, to_info, size=size)
|
|
642
638
|
else:
|
|
643
639
|
assert isinstance(from_file, str)
|
|
@@ -707,9 +703,14 @@ class FileSystem:
|
|
|
707
703
|
|
|
708
704
|
callback.set_size(len(from_infos))
|
|
709
705
|
executor = ThreadPoolExecutor(max_workers=jobs, cancel_on_error=True)
|
|
706
|
+
|
|
707
|
+
def put_file(from_path, to_path):
|
|
708
|
+
with callback.branched(from_path, to_path) as child:
|
|
709
|
+
return self.put_file(from_path, to_path, callback=child)
|
|
710
|
+
|
|
710
711
|
with executor:
|
|
711
|
-
|
|
712
|
-
list(
|
|
712
|
+
it = executor.imap_unordered(put_file, from_infos, to_infos)
|
|
713
|
+
list(callback.wrap(it))
|
|
713
714
|
|
|
714
715
|
def get(
|
|
715
716
|
self,
|
|
@@ -725,9 +726,8 @@ class FileSystem:
|
|
|
725
726
|
|
|
726
727
|
def get_file(rpath, lpath, **kwargs):
|
|
727
728
|
localfs.makedirs(localfs.parent(lpath), exist_ok=True)
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
get_file = wrap_and_branch_callback(callback, get_file)
|
|
729
|
+
with callback.branched(rpath, lpath) as child:
|
|
730
|
+
self.fs.get_file(rpath, lpath, callback=child, **kwargs)
|
|
731
731
|
|
|
732
732
|
if isinstance(from_info, list) and isinstance(to_info, list):
|
|
733
733
|
from_infos: List[AnyFSPath] = from_info
|
|
@@ -738,7 +738,9 @@ class FileSystem:
|
|
|
738
738
|
|
|
739
739
|
if not self.isdir(from_info):
|
|
740
740
|
callback.set_size(1)
|
|
741
|
-
|
|
741
|
+
get_file(from_info, to_info)
|
|
742
|
+
callback.relative_update()
|
|
743
|
+
return
|
|
742
744
|
|
|
743
745
|
from_infos = list(self.find(from_info))
|
|
744
746
|
if not from_infos:
|
|
@@ -761,7 +763,8 @@ class FileSystem:
|
|
|
761
763
|
callback.set_size(len(from_infos))
|
|
762
764
|
executor = ThreadPoolExecutor(max_workers=jobs, cancel_on_error=True)
|
|
763
765
|
with executor:
|
|
764
|
-
|
|
766
|
+
it = executor.imap_unordered(get_file, from_infos, to_infos)
|
|
767
|
+
list(callback.wrap(it))
|
|
765
768
|
|
|
766
769
|
def ukey(self, path: AnyFSPath) -> str:
|
|
767
770
|
return self.fs.ukey(path)
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
from functools import wraps
|
|
2
|
+
from typing import TYPE_CHECKING, BinaryIO, Optional, Type, cast
|
|
3
|
+
|
|
4
|
+
import fsspec
|
|
5
|
+
from fsspec.callbacks import DEFAULT_CALLBACK, Callback, NoOpCallback
|
|
6
|
+
|
|
7
|
+
if TYPE_CHECKING:
|
|
8
|
+
from typing import Union
|
|
9
|
+
|
|
10
|
+
from tqdm import tqdm
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
__all__ = ["Callback", "NoOpCallback", "TqdmCallback", "DEFAULT_CALLBACK"]
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class CallbackStream:
|
|
17
|
+
def __init__(self, stream, callback: Callback):
|
|
18
|
+
self.stream = stream
|
|
19
|
+
|
|
20
|
+
@wraps(stream.read)
|
|
21
|
+
def read(*args, **kwargs):
|
|
22
|
+
data = stream.read(*args, **kwargs)
|
|
23
|
+
callback.relative_update(len(data))
|
|
24
|
+
return data
|
|
25
|
+
|
|
26
|
+
self.read = read
|
|
27
|
+
|
|
28
|
+
def __getattr__(self, attr):
|
|
29
|
+
return getattr(self.stream, attr)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class TqdmCallback(fsspec.callbacks.TqdmCallback):
|
|
33
|
+
def __init__(
|
|
34
|
+
self,
|
|
35
|
+
size: Optional[int] = None,
|
|
36
|
+
value: int = 0,
|
|
37
|
+
progress_bar: Optional["tqdm"] = None,
|
|
38
|
+
tqdm_cls: Optional[Type["tqdm"]] = None,
|
|
39
|
+
**tqdm_kwargs,
|
|
40
|
+
):
|
|
41
|
+
from dvc_objects._tqdm import Tqdm
|
|
42
|
+
|
|
43
|
+
tqdm_kwargs.pop("total", None)
|
|
44
|
+
tqdm_cls = tqdm_cls or Tqdm
|
|
45
|
+
super().__init__(
|
|
46
|
+
tqdm_kwargs=tqdm_kwargs, tqdm_cls=tqdm_cls, size=size, value=value
|
|
47
|
+
)
|
|
48
|
+
if progress_bar is None:
|
|
49
|
+
self.tqdm = progress_bar
|
|
50
|
+
|
|
51
|
+
def branched(self, path_1: "Union[str, BinaryIO]", path_2: str, **kwargs):
|
|
52
|
+
desc = path_1 if isinstance(path_1, str) else path_2
|
|
53
|
+
return TqdmCallback(bytes=True, desc=desc)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def wrap_file(file, callback: Callback) -> BinaryIO:
|
|
57
|
+
return cast(BinaryIO, CallbackStream(file, callback))
|
|
@@ -7,16 +7,17 @@ from functools import wraps
|
|
|
7
7
|
from typing import TYPE_CHECKING, Any, Callable, List, Optional, Union
|
|
8
8
|
|
|
9
9
|
from fsspec.asyn import get_loop
|
|
10
|
+
from fsspec.callbacks import DEFAULT_CALLBACK
|
|
10
11
|
|
|
11
12
|
from dvc_objects.executors import ThreadPoolExecutor, batch_coros
|
|
12
13
|
|
|
13
|
-
from .callbacks import DEFAULT_CALLBACK, wrap_and_branch_callback
|
|
14
14
|
from .local import LocalFileSystem, localfs
|
|
15
15
|
from .utils import as_atomic, umask
|
|
16
16
|
|
|
17
17
|
if TYPE_CHECKING:
|
|
18
|
+
from fsspec import Callback
|
|
19
|
+
|
|
18
20
|
from .base import AnyFSPath, FileSystem
|
|
19
|
-
from .callbacks import Callback
|
|
20
21
|
|
|
21
22
|
logger = logging.getLogger(__name__)
|
|
22
23
|
|
|
@@ -103,7 +104,6 @@ def copy(
|
|
|
103
104
|
)
|
|
104
105
|
|
|
105
106
|
jobs = batch_size or to_fs.jobs
|
|
106
|
-
put_file = wrap_and_branch_callback(callback, to_fs.put_file)
|
|
107
107
|
put_file_kwargs = {}
|
|
108
108
|
if hasattr(to_fs.fs, "max_concurrency"):
|
|
109
109
|
put_file_kwargs["max_concurrency"] = jobs if len(from_path) == 1 else 1
|
|
@@ -112,9 +112,10 @@ def copy(
|
|
|
112
112
|
try:
|
|
113
113
|
with from_fs.open(from_p, mode="rb") as fobj:
|
|
114
114
|
size = from_fs.size(from_p)
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
115
|
+
with callback.branched(from_p, to_p) as child:
|
|
116
|
+
return to_fs.put_file(
|
|
117
|
+
fobj, to_p, size=size, callback=child, **put_file_kwargs
|
|
118
|
+
)
|
|
118
119
|
except Exception as exc: # noqa: BLE001
|
|
119
120
|
if on_error is not None:
|
|
120
121
|
on_error(from_p, to_p, exc)
|
|
@@ -122,14 +123,16 @@ def copy(
|
|
|
122
123
|
raise
|
|
123
124
|
|
|
124
125
|
if len(from_path) == 1:
|
|
125
|
-
|
|
126
|
+
_copy_one(from_path[0], to_path[0])
|
|
127
|
+
return callback.relative_update()
|
|
126
128
|
|
|
127
129
|
executor = ThreadPoolExecutor(max_workers=jobs, cancel_on_error=True)
|
|
128
130
|
with executor:
|
|
129
|
-
|
|
131
|
+
it = executor.imap_unordered(_copy_one, from_path, to_path)
|
|
132
|
+
list(callback.wrap(it))
|
|
130
133
|
|
|
131
134
|
|
|
132
|
-
def _put(
|
|
135
|
+
def _put( # noqa: C901
|
|
133
136
|
from_paths: List["AnyFSPath"],
|
|
134
137
|
to_fs: "FileSystem",
|
|
135
138
|
to_paths: List["AnyFSPath"],
|
|
@@ -138,14 +141,16 @@ def _put(
|
|
|
138
141
|
on_error: Optional[TransferErrorHandler] = None,
|
|
139
142
|
) -> None:
|
|
140
143
|
jobs = batch_size or to_fs.jobs
|
|
141
|
-
put_file = wrap_and_branch_callback(callback, to_fs.put_file)
|
|
142
144
|
put_file_kwargs = {}
|
|
143
145
|
if hasattr(to_fs.fs, "max_concurrency"):
|
|
144
146
|
put_file_kwargs["max_concurrency"] = jobs if len(from_paths) == 1 else 1
|
|
145
147
|
|
|
146
148
|
def _put_one(from_path: "AnyFSPath", to_path: "AnyFSPath"):
|
|
147
149
|
try:
|
|
148
|
-
|
|
150
|
+
with callback.branched(from_path, to_path) as child:
|
|
151
|
+
return to_fs.put_file(
|
|
152
|
+
from_path, to_path, callback=child, **put_file_kwargs
|
|
153
|
+
)
|
|
149
154
|
except Exception as exc: # noqa: BLE001
|
|
150
155
|
if on_error is not None:
|
|
151
156
|
on_error(from_path, to_path, exc)
|
|
@@ -153,18 +158,27 @@ def _put(
|
|
|
153
158
|
raise
|
|
154
159
|
|
|
155
160
|
if len(from_paths) == 1:
|
|
156
|
-
|
|
161
|
+
_put_one(from_paths[0], to_paths[0])
|
|
162
|
+
return callback.relative_update()
|
|
157
163
|
|
|
158
164
|
if to_fs.fs.async_impl:
|
|
159
|
-
|
|
165
|
+
to_fs_async = to_fs.fs
|
|
166
|
+
|
|
167
|
+
async def put_coro(from_path, to_path, **kwargs):
|
|
168
|
+
with callback.branched(from_path, to_path) as child:
|
|
169
|
+
return await to_fs_async._put_file(
|
|
170
|
+
from_path, to_path, callback=child, **kwargs
|
|
171
|
+
)
|
|
172
|
+
|
|
160
173
|
loop = get_loop()
|
|
161
174
|
fut = asyncio.run_coroutine_threadsafe(
|
|
162
175
|
batch_coros(
|
|
163
176
|
[
|
|
164
|
-
put_coro(from_path, to_path,
|
|
177
|
+
put_coro(from_path, to_path, **put_file_kwargs)
|
|
165
178
|
for from_path, to_path in zip(from_paths, to_paths)
|
|
166
179
|
],
|
|
167
180
|
batch_size=jobs,
|
|
181
|
+
callback=callback,
|
|
168
182
|
return_exceptions=True,
|
|
169
183
|
),
|
|
170
184
|
loop,
|
|
@@ -179,7 +193,8 @@ def _put(
|
|
|
179
193
|
|
|
180
194
|
executor = ThreadPoolExecutor(max_workers=jobs, cancel_on_error=True)
|
|
181
195
|
with executor:
|
|
182
|
-
|
|
196
|
+
it = executor.imap_unordered(_put_one, from_paths, to_paths)
|
|
197
|
+
list(callback.wrap(it))
|
|
183
198
|
|
|
184
199
|
|
|
185
200
|
def _get( # noqa: C901
|
|
@@ -191,7 +206,6 @@ def _get( # noqa: C901
|
|
|
191
206
|
on_error: Optional[TransferErrorHandler] = None,
|
|
192
207
|
) -> None:
|
|
193
208
|
jobs = batch_size or from_fs.jobs
|
|
194
|
-
get_file = wrap_and_branch_callback(callback, from_fs.get_file)
|
|
195
209
|
get_file_kwargs = {}
|
|
196
210
|
if hasattr(from_fs.fs, "max_concurrency"):
|
|
197
211
|
get_file_kwargs["max_concurrency"] = jobs if len(from_paths) == 1 else 1
|
|
@@ -199,9 +213,10 @@ def _get( # noqa: C901
|
|
|
199
213
|
def _get_one(from_path: "AnyFSPath", to_path: "AnyFSPath"):
|
|
200
214
|
with as_atomic(localfs, to_path, create_parents=True) as tmp_file:
|
|
201
215
|
try:
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
216
|
+
with callback.branched(from_path, to_path) as child:
|
|
217
|
+
return from_fs.get_file(
|
|
218
|
+
from_path, tmp_file, callback=child, **get_file_kwargs
|
|
219
|
+
)
|
|
205
220
|
except Exception as exc: # noqa: BLE001
|
|
206
221
|
if on_error is not None:
|
|
207
222
|
on_error(from_path, to_path, exc)
|
|
@@ -209,16 +224,18 @@ def _get( # noqa: C901
|
|
|
209
224
|
raise
|
|
210
225
|
|
|
211
226
|
if len(from_paths) == 1:
|
|
212
|
-
|
|
227
|
+
_get_one(from_paths[0], to_paths[0])
|
|
228
|
+
return callback.relative_update()
|
|
213
229
|
|
|
214
230
|
if from_fs.fs.async_impl:
|
|
231
|
+
from_async_fs = from_fs.fs
|
|
215
232
|
|
|
216
233
|
async def _get_one_coro(from_path: "AnyFSPath", to_path: "AnyFSPath"):
|
|
217
|
-
get_coro = wrap_and_branch_callback(callback, from_fs.fs._get_file)
|
|
218
234
|
with as_atomic(localfs, to_path, create_parents=True) as tmp_file:
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
235
|
+
with callback.branched(from_path, to_path) as child:
|
|
236
|
+
return await from_async_fs._get_file(
|
|
237
|
+
from_path, tmp_file, callback=child, **get_file_kwargs
|
|
238
|
+
)
|
|
222
239
|
|
|
223
240
|
loop = get_loop()
|
|
224
241
|
fut = asyncio.run_coroutine_threadsafe(
|
|
@@ -229,6 +246,7 @@ def _get( # noqa: C901
|
|
|
229
246
|
],
|
|
230
247
|
batch_size=jobs,
|
|
231
248
|
return_exceptions=True,
|
|
249
|
+
callback=callback,
|
|
232
250
|
),
|
|
233
251
|
loop,
|
|
234
252
|
)
|
|
@@ -242,7 +260,8 @@ def _get( # noqa: C901
|
|
|
242
260
|
|
|
243
261
|
executor = ThreadPoolExecutor(max_workers=jobs, cancel_on_error=True)
|
|
244
262
|
with executor:
|
|
245
|
-
|
|
263
|
+
it = executor.imap_unordered(_get_one, from_paths, to_paths)
|
|
264
|
+
list(callback.wrap(it))
|
|
246
265
|
|
|
247
266
|
|
|
248
267
|
def _try_links(
|
|
@@ -10,14 +10,17 @@ from contextlib import contextmanager, suppress
|
|
|
10
10
|
from secrets import token_urlsafe
|
|
11
11
|
from typing import TYPE_CHECKING, Any, Collection, Dict, Iterator, Optional, Set, Union
|
|
12
12
|
|
|
13
|
+
from fsspec.callbacks import DEFAULT_CALLBACK
|
|
14
|
+
|
|
13
15
|
from dvc_objects.executors import ThreadPoolExecutor
|
|
14
16
|
|
|
15
17
|
from . import system
|
|
16
|
-
from .callbacks import
|
|
18
|
+
from .callbacks import wrap_file
|
|
17
19
|
|
|
18
20
|
if TYPE_CHECKING:
|
|
21
|
+
from fsspec import Callback
|
|
22
|
+
|
|
19
23
|
from .base import AnyFSPath, FileSystem
|
|
20
|
-
from .callbacks import Callback
|
|
21
24
|
|
|
22
25
|
|
|
23
26
|
logger = logging.getLogger(__name__)
|
|
@@ -168,8 +171,8 @@ def copyfile(
|
|
|
168
171
|
|
|
169
172
|
callback.set_size(total)
|
|
170
173
|
with open(src, "rb") as fsrc, open(dest, "wb+") as fdest:
|
|
171
|
-
wrapped =
|
|
172
|
-
shutil.copyfileobj(
|
|
174
|
+
wrapped = wrap_file(fsrc, callback)
|
|
175
|
+
shutil.copyfileobj(wrapped, fdest, length=LOCAL_CHUNK_SIZE)
|
|
173
176
|
|
|
174
177
|
|
|
175
178
|
def tmp_fname(prefix: str = "") -> str:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: dvc-objects
|
|
3
|
-
Version:
|
|
3
|
+
Version: 4.0.0
|
|
4
4
|
Summary: dvc objects - filesystem and object-db level abstractions to use in dvc and dvc-data
|
|
5
5
|
Author-email: Iterative <support@dvc.org>
|
|
6
6
|
License: Apache-2.0
|
|
@@ -17,7 +17,7 @@ Requires-Python: >=3.8
|
|
|
17
17
|
Description-Content-Type: text/x-rst
|
|
18
18
|
License-File: LICENSE
|
|
19
19
|
Requires-Dist: funcy>=1.14; python_version < "3.12"
|
|
20
|
-
Requires-Dist: fsspec>=
|
|
20
|
+
Requires-Dist: fsspec>=2024.2.0
|
|
21
21
|
Provides-Extra: tests
|
|
22
22
|
Requires-Dist: pytest<8,>=7; extra == "tests"
|
|
23
23
|
Requires-Dist: pytest-sugar; extra == "tests"
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from fsspec.callbacks import Callback
|
|
2
|
+
|
|
3
|
+
from dvc_objects.fs.callbacks import wrap_file
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def test_wrap_file(memfs):
|
|
7
|
+
memfs.pipe_file("/file", b"foo\n")
|
|
8
|
+
|
|
9
|
+
callback = Callback()
|
|
10
|
+
|
|
11
|
+
callback.set_size(4)
|
|
12
|
+
with memfs.open("/file", mode="rb") as f:
|
|
13
|
+
wrapped = wrap_file(f, callback)
|
|
14
|
+
assert wrapped.read() == b"foo\n"
|
|
15
|
+
|
|
16
|
+
assert callback.value == 4
|
|
17
|
+
assert callback.size == 4
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
from fsspec import Callback
|
|
3
|
+
from fsspec.asyn import AsyncFileSystem
|
|
4
|
+
from fsspec.implementations.memory import MemoryFileSystem as MemoryFS
|
|
5
|
+
|
|
6
|
+
from dvc_objects.fs.generic import copy, transfer
|
|
7
|
+
from dvc_objects.fs.local import LocalFileSystem
|
|
8
|
+
from dvc_objects.fs.memory import MemoryFileSystem
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def awrap(fn):
|
|
12
|
+
async def inner(self, *args, **kwargs):
|
|
13
|
+
return fn(self.fs, *args, **kwargs)
|
|
14
|
+
|
|
15
|
+
return inner
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class AsyncMemoryFS(AsyncFileSystem):
|
|
19
|
+
cachable = False
|
|
20
|
+
|
|
21
|
+
def __init__(self, *args, **kwargs):
|
|
22
|
+
super().__init__(*args, **kwargs)
|
|
23
|
+
self.fs = MemoryFS()
|
|
24
|
+
self.fs.store = {}
|
|
25
|
+
self.fs.pseudo_dirs = [""]
|
|
26
|
+
|
|
27
|
+
def _open(self, *args, **kwargs):
|
|
28
|
+
return self.fs.open(*args, **kwargs)
|
|
29
|
+
|
|
30
|
+
_info = awrap(MemoryFS.info)
|
|
31
|
+
_ls = awrap(MemoryFS.ls)
|
|
32
|
+
_mkdir = awrap(MemoryFS.mkdir)
|
|
33
|
+
_makedirs = awrap(MemoryFS.makedirs)
|
|
34
|
+
_get_file = awrap(MemoryFS.get_file)
|
|
35
|
+
_put_file = awrap(MemoryFS.put_file)
|
|
36
|
+
_cat_file = awrap(MemoryFS.cat_file)
|
|
37
|
+
_pipe_file = awrap(MemoryFS.pipe_file)
|
|
38
|
+
_cp_file = awrap(MemoryFS.cp_file)
|
|
39
|
+
_rm_file = awrap(MemoryFS.rm_file)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
fs_clses = [
|
|
43
|
+
LocalFileSystem,
|
|
44
|
+
lambda: MemoryFileSystem(global_store=False),
|
|
45
|
+
lambda: MemoryFileSystem(fs=AsyncMemoryFS()),
|
|
46
|
+
]
|
|
47
|
+
fs_clses[1].__name__ = MemoryFileSystem.__name__ # type: ignore[attr-defined]
|
|
48
|
+
fs_clses[2].__name__ = "Async" + MemoryFileSystem.__name__ # type: ignore[attr-defined]
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@pytest.mark.parametrize("files", [{"foo": b"foo"}, {"foo": b"foo", "bar": b"barr"}])
|
|
52
|
+
@pytest.mark.parametrize("fs_cls1", fs_clses)
|
|
53
|
+
@pytest.mark.parametrize("fs_cls2", fs_clses)
|
|
54
|
+
def test_copy(tmp_path, files, fs_cls1, fs_cls2, mocker):
|
|
55
|
+
fs1, fs2 = fs_cls1(), fs_cls2()
|
|
56
|
+
fs1_root = tmp_path if isinstance(fs1, LocalFileSystem) else fs1.root_marker
|
|
57
|
+
fs2_root = tmp_path if isinstance(fs2, LocalFileSystem) else fs2.root_marker
|
|
58
|
+
src_root = fs1.join(fs1_root, "src")
|
|
59
|
+
dest_root = fs2.join(fs2_root, "dest")
|
|
60
|
+
|
|
61
|
+
src_files = {fs1.join(src_root, f): c for f, c in files.items()}
|
|
62
|
+
dest_files = {fs2.join(dest_root, f): c for f, c in files.items()}
|
|
63
|
+
fs1.mkdir(src_root)
|
|
64
|
+
fs1.pipe(src_files)
|
|
65
|
+
|
|
66
|
+
callback = Callback()
|
|
67
|
+
spy_close = mocker.spy(Callback, "close")
|
|
68
|
+
child_callbacks = [Callback() for _ in files]
|
|
69
|
+
|
|
70
|
+
branched = mocker.patch.object(callback, "branched", side_effect=child_callbacks)
|
|
71
|
+
copy(fs1, list(src_files), fs2, list(dest_files), callback=callback)
|
|
72
|
+
|
|
73
|
+
assert fs2.cat(list(dest_files)) == dest_files
|
|
74
|
+
|
|
75
|
+
n = len(files)
|
|
76
|
+
# assert main callback works
|
|
77
|
+
assert callback.value == n
|
|
78
|
+
assert callback.size is None # does not set sizes
|
|
79
|
+
# assert child callbacks are handled correctly
|
|
80
|
+
assert branched.call_count == n, f"expected branched to be called {n} times"
|
|
81
|
+
assert spy_close.call_count == n, f"expected close to be called {n} times"
|
|
82
|
+
|
|
83
|
+
if isinstance(fs1, LocalFileSystem) and isinstance(fs2, LocalFileSystem):
|
|
84
|
+
# localfs copy avoids calling set_size or update if fs supports reflink
|
|
85
|
+
# or, file size is less than 1GB
|
|
86
|
+
return
|
|
87
|
+
assert {c.size for c in child_callbacks} == {len(c) for c in files.values()}
|
|
88
|
+
assert {c.value for c in child_callbacks} == {len(c) for c in files.values()}
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
@pytest.mark.parametrize("files", [{"foo": b"foo"}, {"foo": b"foo", "bar": b"barr"}])
|
|
92
|
+
@pytest.mark.parametrize(
|
|
93
|
+
"link_type",
|
|
94
|
+
[
|
|
95
|
+
pytest.param("reflink", marks=pytest.mark.xfail(reason="unsupported")),
|
|
96
|
+
"symlink",
|
|
97
|
+
"hardlink",
|
|
98
|
+
"copy",
|
|
99
|
+
],
|
|
100
|
+
)
|
|
101
|
+
def test_transfer_between_local_fses(mocker, tmp_path, files, link_type):
|
|
102
|
+
fs = LocalFileSystem()
|
|
103
|
+
fs.mkdir(fs.join(tmp_path, "src"))
|
|
104
|
+
fs.mkdir(fs.join(tmp_path, "dest"))
|
|
105
|
+
|
|
106
|
+
src_files = {fs.join(tmp_path, "src", f): c for f, c in files.items()}
|
|
107
|
+
dest_files = {fs.join(tmp_path, "dest", f): c for f, c in files.items()}
|
|
108
|
+
|
|
109
|
+
fs.pipe(src_files)
|
|
110
|
+
|
|
111
|
+
callback = Callback()
|
|
112
|
+
|
|
113
|
+
branched = mocker.patch.object(callback, "branched")
|
|
114
|
+
transfer(
|
|
115
|
+
fs, list(src_files), fs, list(dest_files), callback=callback, links=[link_type]
|
|
116
|
+
)
|
|
117
|
+
assert fs.cat([str(tmp_path / "dest" / file) for file in files]) == dest_files
|
|
118
|
+
assert callback.value == len(files)
|
|
119
|
+
assert callback.size is None # does not set sizes
|
|
120
|
+
assert branched.call_count == (len(files) if link_type == "copy" else 0)
|
|
@@ -1,184 +0,0 @@
|
|
|
1
|
-
import asyncio
|
|
2
|
-
from functools import wraps
|
|
3
|
-
from typing import TYPE_CHECKING, Any, Callable, Dict, Optional, TypeVar
|
|
4
|
-
|
|
5
|
-
import fsspec
|
|
6
|
-
|
|
7
|
-
if TYPE_CHECKING:
|
|
8
|
-
from typing import BinaryIO, Union
|
|
9
|
-
|
|
10
|
-
from dvc_objects._tqdm import Tqdm
|
|
11
|
-
|
|
12
|
-
F = TypeVar("F", bound=Callable)
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
class CallbackStream:
|
|
16
|
-
def __init__(self, stream, callback, method="read"):
|
|
17
|
-
self.stream = stream
|
|
18
|
-
if method == "write":
|
|
19
|
-
|
|
20
|
-
@wraps(stream.write)
|
|
21
|
-
def write(data, *args, **kwargs):
|
|
22
|
-
res = stream.write(data, *args, **kwargs)
|
|
23
|
-
callback.relative_update(len(data))
|
|
24
|
-
return res
|
|
25
|
-
|
|
26
|
-
self.write = write
|
|
27
|
-
else:
|
|
28
|
-
|
|
29
|
-
@wraps(stream.read)
|
|
30
|
-
def read(*args, **kwargs):
|
|
31
|
-
data = stream.read(*args, **kwargs)
|
|
32
|
-
callback.relative_update(len(data))
|
|
33
|
-
return data
|
|
34
|
-
|
|
35
|
-
self.read = read
|
|
36
|
-
|
|
37
|
-
def __getattr__(self, attr):
|
|
38
|
-
return getattr(self.stream, attr)
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
class ScopedCallback(fsspec.Callback):
|
|
42
|
-
def __enter__(self):
|
|
43
|
-
return self
|
|
44
|
-
|
|
45
|
-
def __exit__(self, *exc_args):
|
|
46
|
-
self.close()
|
|
47
|
-
|
|
48
|
-
def close(self):
|
|
49
|
-
"""Handle here on exit."""
|
|
50
|
-
|
|
51
|
-
def branch(
|
|
52
|
-
self,
|
|
53
|
-
path_1: "Union[str, BinaryIO]",
|
|
54
|
-
path_2: str,
|
|
55
|
-
kwargs: Dict[str, Any],
|
|
56
|
-
child: Optional["Callback"] = None,
|
|
57
|
-
) -> "Callback":
|
|
58
|
-
child = kwargs["callback"] = child or DEFAULT_CALLBACK
|
|
59
|
-
return child
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
class Callback(ScopedCallback):
|
|
63
|
-
def absolute_update(self, value: int) -> None:
|
|
64
|
-
value = value if value is not None else self.value
|
|
65
|
-
return super().absolute_update(value)
|
|
66
|
-
|
|
67
|
-
@classmethod
|
|
68
|
-
def as_callback(
|
|
69
|
-
cls, maybe_callback: Optional[fsspec.Callback] = None
|
|
70
|
-
) -> "Callback":
|
|
71
|
-
if maybe_callback is None:
|
|
72
|
-
return DEFAULT_CALLBACK
|
|
73
|
-
if isinstance(maybe_callback, Callback):
|
|
74
|
-
return maybe_callback
|
|
75
|
-
return FsspecCallbackWrapper(maybe_callback)
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
class NoOpCallback(Callback, fsspec.callbacks.NoOpCallback):
|
|
79
|
-
pass
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
class TqdmCallback(Callback):
|
|
83
|
-
def __init__(
|
|
84
|
-
self,
|
|
85
|
-
size: Optional[int] = None,
|
|
86
|
-
value: int = 0,
|
|
87
|
-
progress_bar: Optional["Tqdm"] = None,
|
|
88
|
-
**tqdm_kwargs,
|
|
89
|
-
):
|
|
90
|
-
from dvc_objects._tqdm import Tqdm
|
|
91
|
-
|
|
92
|
-
tqdm_kwargs.pop("total", None)
|
|
93
|
-
self._tqdm_kwargs = tqdm_kwargs
|
|
94
|
-
self._tqdm_cls = Tqdm
|
|
95
|
-
self.tqdm = progress_bar
|
|
96
|
-
super().__init__(size=size, value=value)
|
|
97
|
-
|
|
98
|
-
def close(self):
|
|
99
|
-
if self.tqdm is not None:
|
|
100
|
-
self.tqdm.close()
|
|
101
|
-
self.tqdm = None
|
|
102
|
-
|
|
103
|
-
def call(self, hook_name=None, **kwargs):
|
|
104
|
-
if self.tqdm is None:
|
|
105
|
-
self.tqdm = self._tqdm_cls(**self._tqdm_kwargs, total=self.size or -1)
|
|
106
|
-
self.tqdm.update_to(self.value, total=self.size)
|
|
107
|
-
|
|
108
|
-
def branch(
|
|
109
|
-
self,
|
|
110
|
-
path_1: "Union[str, BinaryIO]",
|
|
111
|
-
path_2: str,
|
|
112
|
-
kwargs: Dict[str, Any],
|
|
113
|
-
child: Optional[Callback] = None,
|
|
114
|
-
):
|
|
115
|
-
desc = path_1 if isinstance(path_1, str) else path_2
|
|
116
|
-
child = child or TqdmCallback(bytes=True, desc=desc)
|
|
117
|
-
return super().branch(path_1, path_2, kwargs, child=child)
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
class FsspecCallbackWrapper(Callback):
|
|
121
|
-
def __init__(self, callback: fsspec.Callback):
|
|
122
|
-
object.__setattr__(self, "_callback", callback)
|
|
123
|
-
|
|
124
|
-
def __getattr__(self, name: str):
|
|
125
|
-
return getattr(self._callback, name)
|
|
126
|
-
|
|
127
|
-
def __setattr__(self, name: str, value: Any):
|
|
128
|
-
setattr(self._callback, name, value)
|
|
129
|
-
|
|
130
|
-
def absolute_update(self, value: int) -> None:
|
|
131
|
-
value = value if value is not None else self.value
|
|
132
|
-
return self._callback.absolute_update(value)
|
|
133
|
-
|
|
134
|
-
def branch(
|
|
135
|
-
self,
|
|
136
|
-
path_1: "Union[str, BinaryIO]",
|
|
137
|
-
path_2: str,
|
|
138
|
-
kwargs: Dict[str, Any],
|
|
139
|
-
child: Optional["Callback"] = None,
|
|
140
|
-
) -> "Callback":
|
|
141
|
-
if not child:
|
|
142
|
-
self._callback.branch(path_1, path_2, kwargs)
|
|
143
|
-
child = self.as_callback(kwargs.get("callback"))
|
|
144
|
-
return super().branch(path_1, path_2, kwargs, child=child)
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
def wrap_fn(callback: fsspec.Callback, fn: F) -> F:
|
|
148
|
-
@wraps(fn)
|
|
149
|
-
async def async_wrapper(*args, **kwargs):
|
|
150
|
-
res = await fn(*args, **kwargs)
|
|
151
|
-
callback.relative_update()
|
|
152
|
-
return res
|
|
153
|
-
|
|
154
|
-
@wraps(fn)
|
|
155
|
-
def sync_wrapper(*args, **kwargs):
|
|
156
|
-
res = fn(*args, **kwargs)
|
|
157
|
-
callback.relative_update()
|
|
158
|
-
return res
|
|
159
|
-
|
|
160
|
-
return async_wrapper if asyncio.iscoroutinefunction(fn) else sync_wrapper # type: ignore[return-value]
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
def branch_callback(callback: fsspec.Callback, fn: F) -> F:
|
|
164
|
-
callback = Callback.as_callback(callback)
|
|
165
|
-
|
|
166
|
-
@wraps(fn)
|
|
167
|
-
async def async_wrapper(path1: "Union[str, BinaryIO]", path2: str, **kwargs):
|
|
168
|
-
with callback.branch(path1, path2, kwargs):
|
|
169
|
-
return await fn(path1, path2, **kwargs)
|
|
170
|
-
|
|
171
|
-
@wraps(fn)
|
|
172
|
-
def sync_wrapper(path1: "Union[str, BinaryIO]", path2: str, **kwargs):
|
|
173
|
-
with callback.branch(path1, path2, kwargs):
|
|
174
|
-
return fn(path1, path2, **kwargs)
|
|
175
|
-
|
|
176
|
-
return async_wrapper if asyncio.iscoroutinefunction(fn) else sync_wrapper # type: ignore[return-value]
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
def wrap_and_branch_callback(callback: fsspec.Callback, fn: F) -> F:
|
|
180
|
-
branch_wrapper = branch_callback(callback, fn)
|
|
181
|
-
return wrap_fn(callback, branch_wrapper)
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
DEFAULT_CALLBACK = NoOpCallback()
|
|
@@ -1,148 +0,0 @@
|
|
|
1
|
-
from typing import Optional
|
|
2
|
-
|
|
3
|
-
import fsspec
|
|
4
|
-
import pytest
|
|
5
|
-
|
|
6
|
-
from dvc_objects.fs.callbacks import (
|
|
7
|
-
DEFAULT_CALLBACK,
|
|
8
|
-
Callback,
|
|
9
|
-
TqdmCallback,
|
|
10
|
-
branch_callback,
|
|
11
|
-
wrap_and_branch_callback,
|
|
12
|
-
wrap_fn,
|
|
13
|
-
)
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
@pytest.mark.parametrize("api", ["set_size", "absolute_update"])
|
|
17
|
-
def test_callback_with_none(request, api, mocker):
|
|
18
|
-
"""
|
|
19
|
-
Test that callback don't fail if they receive None.
|
|
20
|
-
The callbacks should not receive None, but there may be some
|
|
21
|
-
filesystems that are not compliant, we may want to maintain
|
|
22
|
-
maximum compatibility, and not break UI in these edge-cases.
|
|
23
|
-
See https://github.com/iterative/dvc/issues/7704.
|
|
24
|
-
"""
|
|
25
|
-
callback = Callback.as_callback()
|
|
26
|
-
request.addfinalizer(callback.close)
|
|
27
|
-
|
|
28
|
-
call_mock = mocker.spy(callback, "call")
|
|
29
|
-
method = getattr(callback, api)
|
|
30
|
-
method(None)
|
|
31
|
-
call_mock.assert_called_once_with()
|
|
32
|
-
if callback is not DEFAULT_CALLBACK:
|
|
33
|
-
assert callback.size is None
|
|
34
|
-
assert callback.value == 0
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
def test_wrap_fsspec():
|
|
38
|
-
def _branch_fn(*args, callback: Optional["Callback"] = None, **kwargs):
|
|
39
|
-
pass
|
|
40
|
-
|
|
41
|
-
callback = fsspec.callbacks.Callback()
|
|
42
|
-
assert callback.value == 0
|
|
43
|
-
with Callback.as_callback(callback) as cb:
|
|
44
|
-
assert not isinstance(cb, TqdmCallback)
|
|
45
|
-
assert cb.value == 0
|
|
46
|
-
cb.relative_update()
|
|
47
|
-
assert cb.value == 1
|
|
48
|
-
assert callback.value == 1
|
|
49
|
-
|
|
50
|
-
with cb.branch("foo", "bar", {}) as child:
|
|
51
|
-
_branch_fn("foo", "bar", callback=child)
|
|
52
|
-
cb.relative_update()
|
|
53
|
-
|
|
54
|
-
assert cb.value == 2
|
|
55
|
-
assert callback.value == 2
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
def ids_func(cb_type):
|
|
59
|
-
return f"{cb_type.__module__}.{cb_type.__qualname__}"
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
class IsDVCCallback:
|
|
63
|
-
def __eq__(self, value: object) -> bool:
|
|
64
|
-
return isinstance(value, Callback)
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
@pytest.fixture(params=[Callback, fsspec.Callback], ids=ids_func)
|
|
68
|
-
def cb_class(request):
|
|
69
|
-
return request.param
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
def test_wrap_fn_sync(mocker, cb_class):
|
|
73
|
-
m = mocker.MagicMock(return_value=1)
|
|
74
|
-
callback = cb_class()
|
|
75
|
-
|
|
76
|
-
wrapped = wrap_fn(callback, m)
|
|
77
|
-
|
|
78
|
-
assert wrapped("arg") == 1
|
|
79
|
-
assert callback.value == 1
|
|
80
|
-
m.assert_called_once_with("arg")
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
@pytest.mark.asyncio
|
|
84
|
-
async def test_wrap_fn_async(mocker, cb_class):
|
|
85
|
-
m = mocker.AsyncMock(return_value=1)
|
|
86
|
-
callback = cb_class()
|
|
87
|
-
|
|
88
|
-
wrapped = wrap_fn(callback, m)
|
|
89
|
-
|
|
90
|
-
assert await wrapped("arg") == 1
|
|
91
|
-
assert callback.value == 1
|
|
92
|
-
m.assert_called_once_with("arg")
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
def test_branch_fn_sync(mocker, cb_class):
|
|
96
|
-
m = mocker.MagicMock(return_value=1)
|
|
97
|
-
callback = cb_class()
|
|
98
|
-
spy = mocker.spy(callback, "branch")
|
|
99
|
-
wrapped = branch_callback(callback, m)
|
|
100
|
-
|
|
101
|
-
assert wrapped("arg1", "arg2") == 1
|
|
102
|
-
assert callback.value == 0
|
|
103
|
-
assert spy.call_count == 1
|
|
104
|
-
m.assert_called_once_with("arg1", "arg2", callback=IsDVCCallback())
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
@pytest.mark.asyncio
|
|
108
|
-
async def test_branch_fn_async(mocker, cb_class):
|
|
109
|
-
m = mocker.AsyncMock(return_value=1)
|
|
110
|
-
callback = cb_class()
|
|
111
|
-
spy = mocker.spy(callback, "branch")
|
|
112
|
-
wrapped = branch_callback(callback, m)
|
|
113
|
-
|
|
114
|
-
assert await wrapped("arg1", "arg2") == 1
|
|
115
|
-
assert callback.value == 0
|
|
116
|
-
assert spy.call_count == 1
|
|
117
|
-
m.assert_called_once_with("arg1", "arg2", callback=IsDVCCallback())
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
def test_wrap_and_branch_callback_sync(mocker, cb_class):
|
|
121
|
-
m = mocker.MagicMock(return_value=1)
|
|
122
|
-
callback = cb_class()
|
|
123
|
-
spy = mocker.spy(callback, "branch")
|
|
124
|
-
wrapped = wrap_and_branch_callback(callback, m)
|
|
125
|
-
|
|
126
|
-
assert wrapped("arg1", "arg2", arg3="arg3") == 1
|
|
127
|
-
assert wrapped("argA", "argB", arg3="argC") == 1
|
|
128
|
-
|
|
129
|
-
m.assert_any_call("arg1", "arg2", arg3="arg3", callback=IsDVCCallback())
|
|
130
|
-
m.assert_any_call("argA", "argB", arg3="argC", callback=IsDVCCallback())
|
|
131
|
-
assert callback.value == 2
|
|
132
|
-
assert spy.call_count == 2
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
@pytest.mark.asyncio
|
|
136
|
-
async def test_wrap_and_branch_callback_async(mocker, cb_class):
|
|
137
|
-
m = mocker.AsyncMock(return_value=1)
|
|
138
|
-
callback = cb_class()
|
|
139
|
-
spy = mocker.spy(callback, "branch")
|
|
140
|
-
wrapped = wrap_and_branch_callback(callback, m)
|
|
141
|
-
|
|
142
|
-
assert await wrapped("arg1", "arg2", arg3="arg3") == 1
|
|
143
|
-
assert await wrapped("argA", "argB", arg3="argC") == 1
|
|
144
|
-
|
|
145
|
-
m.assert_any_call("arg1", "arg2", arg3="arg3", callback=IsDVCCallback())
|
|
146
|
-
m.assert_any_call("argA", "argB", arg3="argC", callback=IsDVCCallback())
|
|
147
|
-
assert callback.value == 2
|
|
148
|
-
assert spy.call_count == 2
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|