Add files using upload-large-folder tool
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/fsspec/archive.py +73 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/fsspec/asyn.py +1081 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/fsspec/caching.py +875 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/fsspec/callbacks.py +324 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/fsspec/compression.py +174 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/fsspec/config.py +131 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/fsspec/core.py +710 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/fsspec/dircache.py +98 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/fsspec/exceptions.py +17 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/fsspec/fuse.py +324 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/fsspec/generic.py +403 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/fsspec/gui.py +414 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/fsspec/parquet.py +549 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/fsspec/spec.py +1965 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/functorch/__pycache__/__init__.cpython-311.pyc +0 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/functorch/_src/__init__.py +0 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/functorch/_src/eager_transforms/__init__.py +7 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/functorch/_src/eager_transforms/__pycache__/__init__.cpython-311.pyc +0 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/functorch/compile/__pycache__/__init__.cpython-311.pyc +0 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/functorch/dim/__pycache__/dim.cpython-311.pyc +0 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/functorch/dim/dim.py +121 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/functorch/dim/op_properties.py +311 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/functorch/dim/reference.py +645 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/functorch/dim/tree_map.py +14 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/functorch/experimental/__pycache__/__init__.cpython-311.pyc +0 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/functorch/experimental/ops.py +1 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/jinja2/__init__.py +37 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/jinja2/__pycache__/_identifier.cpython-311.pyc +0 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/jinja2/__pycache__/loaders.cpython-311.pyc +0 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/jinja2/__pycache__/runtime.cpython-311.pyc +0 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/jinja2/environment.py +1667 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/jinja2/loaders.py +661 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/jinja2/parser.py +1034 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/jinja2/utils.py +755 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/nvidia_cufft_cu11-10.9.0.58.dist-info/License.txt +1568 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/nvidia_cufft_cu11-10.9.0.58.dist-info/METADATA +35 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/nvidia_cufft_cu11-10.9.0.58.dist-info/WHEEL +5 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/nvidia_cufft_cu11-10.9.0.58.dist-info/top_level.txt +1 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/nvidia_cusolver_cu11-11.4.1.48.dist-info/METADATA +36 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/nvidia_cusolver_cu11-11.4.1.48.dist-info/top_level.txt +1 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/pip/_internal/commands/__init__.py +132 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/pip/_internal/commands/__pycache__/configuration.cpython-311.pyc +0 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/pip/_internal/commands/__pycache__/hash.cpython-311.pyc +0 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/pip/_internal/commands/__pycache__/help.cpython-311.pyc +0 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/pip/_internal/commands/__pycache__/inspect.cpython-311.pyc +0 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/pip/_internal/commands/__pycache__/install.cpython-311.pyc +0 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/pip/_internal/commands/__pycache__/uninstall.cpython-311.pyc +0 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/pip/_internal/commands/check.py +67 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/pip/_internal/commands/completion.py +130 -0
- tuning-competition-baseline/.venv/lib/python3.11/site-packages/pip/_internal/commands/inspect.py +92 -0
tuning-competition-baseline/.venv/lib/python3.11/site-packages/fsspec/archive.py
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from fsspec import AbstractFileSystem
|
| 2 |
+
from fsspec.utils import tokenize
|
| 3 |
+
|
| 4 |
+
|
| 5 |
+
class AbstractArchiveFileSystem(AbstractFileSystem):
|
| 6 |
+
"""
|
| 7 |
+
A generic superclass for implementing Archive-based filesystems.
|
| 8 |
+
|
| 9 |
+
Currently, it is shared amongst
|
| 10 |
+
:class:`~fsspec.implementations.zip.ZipFileSystem`,
|
| 11 |
+
:class:`~fsspec.implementations.libarchive.LibArchiveFileSystem` and
|
| 12 |
+
:class:`~fsspec.implementations.tar.TarFileSystem`.
|
| 13 |
+
"""
|
| 14 |
+
|
| 15 |
+
def __str__(self):
|
| 16 |
+
return f"<Archive-like object {type(self).__name__} at {id(self)}>"
|
| 17 |
+
|
| 18 |
+
__repr__ = __str__
|
| 19 |
+
|
| 20 |
+
def ukey(self, path):
|
| 21 |
+
return tokenize(path, self.fo, self.protocol)
|
| 22 |
+
|
| 23 |
+
def _all_dirnames(self, paths):
|
| 24 |
+
"""Returns *all* directory names for each path in paths, including intermediate
|
| 25 |
+
ones.
|
| 26 |
+
|
| 27 |
+
Parameters
|
| 28 |
+
----------
|
| 29 |
+
paths: Iterable of path strings
|
| 30 |
+
"""
|
| 31 |
+
if len(paths) == 0:
|
| 32 |
+
return set()
|
| 33 |
+
|
| 34 |
+
dirnames = {self._parent(path) for path in paths} - {self.root_marker}
|
| 35 |
+
return dirnames | self._all_dirnames(dirnames)
|
| 36 |
+
|
| 37 |
+
def info(self, path, **kwargs):
|
| 38 |
+
self._get_dirs()
|
| 39 |
+
path = self._strip_protocol(path)
|
| 40 |
+
if path in {"", "/"} and self.dir_cache:
|
| 41 |
+
return {"name": "", "type": "directory", "size": 0}
|
| 42 |
+
if path in self.dir_cache:
|
| 43 |
+
return self.dir_cache[path]
|
| 44 |
+
elif path + "/" in self.dir_cache:
|
| 45 |
+
return self.dir_cache[path + "/"]
|
| 46 |
+
else:
|
| 47 |
+
raise FileNotFoundError(path)
|
| 48 |
+
|
| 49 |
+
def ls(self, path, detail=True, **kwargs):
|
| 50 |
+
self._get_dirs()
|
| 51 |
+
paths = {}
|
| 52 |
+
for p, f in self.dir_cache.items():
|
| 53 |
+
p = p.rstrip("/")
|
| 54 |
+
if "/" in p:
|
| 55 |
+
root = p.rsplit("/", 1)[0]
|
| 56 |
+
else:
|
| 57 |
+
root = ""
|
| 58 |
+
if root == path.rstrip("/"):
|
| 59 |
+
paths[p] = f
|
| 60 |
+
elif all(
|
| 61 |
+
(a == b)
|
| 62 |
+
for a, b in zip(path.split("/"), [""] + p.strip("/").split("/"))
|
| 63 |
+
):
|
| 64 |
+
# root directory entry
|
| 65 |
+
ppath = p.rstrip("/").split("/", 1)[0]
|
| 66 |
+
if ppath not in paths:
|
| 67 |
+
out = {"name": ppath, "size": 0, "type": "directory"}
|
| 68 |
+
paths[ppath] = out
|
| 69 |
+
if detail:
|
| 70 |
+
out = sorted(paths.values(), key=lambda _: _["name"])
|
| 71 |
+
return out
|
| 72 |
+
else:
|
| 73 |
+
return sorted(paths)
|
tuning-competition-baseline/.venv/lib/python3.11/site-packages/fsspec/asyn.py
ADDED
|
@@ -0,0 +1,1081 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import asyncio
|
| 2 |
+
import asyncio.events
|
| 3 |
+
import functools
|
| 4 |
+
import inspect
|
| 5 |
+
import io
|
| 6 |
+
import numbers
|
| 7 |
+
import os
|
| 8 |
+
import re
|
| 9 |
+
import threading
|
| 10 |
+
from contextlib import contextmanager
|
| 11 |
+
from glob import has_magic
|
| 12 |
+
from typing import TYPE_CHECKING, Iterable
|
| 13 |
+
|
| 14 |
+
from .callbacks import DEFAULT_CALLBACK
|
| 15 |
+
from .exceptions import FSTimeoutError
|
| 16 |
+
from .implementations.local import LocalFileSystem, make_path_posix, trailing_sep
|
| 17 |
+
from .spec import AbstractBufferedFile, AbstractFileSystem
|
| 18 |
+
from .utils import glob_translate, is_exception, other_paths
|
| 19 |
+
|
| 20 |
+
private = re.compile("_[^_]")
|
| 21 |
+
iothread = [None] # dedicated fsspec IO thread
|
| 22 |
+
loop = [None] # global event loop for any non-async instance
|
| 23 |
+
_lock = None # global lock placeholder
|
| 24 |
+
get_running_loop = asyncio.get_running_loop
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
def get_lock():
|
| 28 |
+
"""Allocate or return a threading lock.
|
| 29 |
+
|
| 30 |
+
The lock is allocated on first use to allow setting one lock per forked process.
|
| 31 |
+
"""
|
| 32 |
+
global _lock
|
| 33 |
+
if not _lock:
|
| 34 |
+
_lock = threading.Lock()
|
| 35 |
+
return _lock
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
def reset_lock():
|
| 39 |
+
"""Reset the global lock.
|
| 40 |
+
|
| 41 |
+
This should be called only on the init of a forked process to reset the lock to
|
| 42 |
+
None, enabling the new forked process to get a new lock.
|
| 43 |
+
"""
|
| 44 |
+
global _lock
|
| 45 |
+
|
| 46 |
+
iothread[0] = None
|
| 47 |
+
loop[0] = None
|
| 48 |
+
_lock = None
|
| 49 |
+
|
| 50 |
+
|
| 51 |
+
async def _runner(event, coro, result, timeout=None):
|
| 52 |
+
timeout = timeout if timeout else None # convert 0 or 0.0 to None
|
| 53 |
+
if timeout is not None:
|
| 54 |
+
coro = asyncio.wait_for(coro, timeout=timeout)
|
| 55 |
+
try:
|
| 56 |
+
result[0] = await coro
|
| 57 |
+
except Exception as ex:
|
| 58 |
+
result[0] = ex
|
| 59 |
+
finally:
|
| 60 |
+
event.set()
|
| 61 |
+
|
| 62 |
+
|
| 63 |
+
def sync(loop, func, *args, timeout=None, **kwargs):
|
| 64 |
+
"""
|
| 65 |
+
Make loop run coroutine until it returns. Runs in other thread
|
| 66 |
+
|
| 67 |
+
Examples
|
| 68 |
+
--------
|
| 69 |
+
>>> fsspec.asyn.sync(fsspec.asyn.get_loop(), func, *args,
|
| 70 |
+
timeout=timeout, **kwargs)
|
| 71 |
+
"""
|
| 72 |
+
timeout = timeout if timeout else None # convert 0 or 0.0 to None
|
| 73 |
+
# NB: if the loop is not running *yet*, it is OK to submit work
|
| 74 |
+
# and we will wait for it
|
| 75 |
+
if loop is None or loop.is_closed():
|
| 76 |
+
raise RuntimeError("Loop is not running")
|
| 77 |
+
try:
|
| 78 |
+
loop0 = asyncio.events.get_running_loop()
|
| 79 |
+
if loop0 is loop:
|
| 80 |
+
raise NotImplementedError("Calling sync() from within a running loop")
|
| 81 |
+
except NotImplementedError:
|
| 82 |
+
raise
|
| 83 |
+
except RuntimeError:
|
| 84 |
+
pass
|
| 85 |
+
coro = func(*args, **kwargs)
|
| 86 |
+
result = [None]
|
| 87 |
+
event = threading.Event()
|
| 88 |
+
asyncio.run_coroutine_threadsafe(_runner(event, coro, result, timeout), loop)
|
| 89 |
+
while True:
|
| 90 |
+
# this loops allows thread to get interrupted
|
| 91 |
+
if event.wait(1):
|
| 92 |
+
break
|
| 93 |
+
if timeout is not None:
|
| 94 |
+
timeout -= 1
|
| 95 |
+
if timeout < 0:
|
| 96 |
+
raise FSTimeoutError
|
| 97 |
+
|
| 98 |
+
return_result = result[0]
|
| 99 |
+
if isinstance(return_result, asyncio.TimeoutError):
|
| 100 |
+
# suppress asyncio.TimeoutError, raise FSTimeoutError
|
| 101 |
+
raise FSTimeoutError from return_result
|
| 102 |
+
elif isinstance(return_result, BaseException):
|
| 103 |
+
raise return_result
|
| 104 |
+
else:
|
| 105 |
+
return return_result
|
| 106 |
+
|
| 107 |
+
|
| 108 |
+
def sync_wrapper(func, obj=None):
|
| 109 |
+
"""Given a function, make so can be called in blocking contexts
|
| 110 |
+
|
| 111 |
+
Leave obj=None if defining within a class. Pass the instance if attaching
|
| 112 |
+
as an attribute of the instance.
|
| 113 |
+
"""
|
| 114 |
+
|
| 115 |
+
@functools.wraps(func)
|
| 116 |
+
def wrapper(*args, **kwargs):
|
| 117 |
+
self = obj or args[0]
|
| 118 |
+
return sync(self.loop, func, *args, **kwargs)
|
| 119 |
+
|
| 120 |
+
return wrapper
|
| 121 |
+
|
| 122 |
+
|
| 123 |
+
@contextmanager
|
| 124 |
+
def _selector_policy():
|
| 125 |
+
original_policy = asyncio.get_event_loop_policy()
|
| 126 |
+
try:
|
| 127 |
+
if os.name == "nt" and hasattr(asyncio, "WindowsSelectorEventLoopPolicy"):
|
| 128 |
+
asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
|
| 129 |
+
|
| 130 |
+
yield
|
| 131 |
+
finally:
|
| 132 |
+
asyncio.set_event_loop_policy(original_policy)
|
| 133 |
+
|
| 134 |
+
|
| 135 |
+
def get_loop():
|
| 136 |
+
"""Create or return the default fsspec IO loop
|
| 137 |
+
|
| 138 |
+
The loop will be running on a separate thread.
|
| 139 |
+
"""
|
| 140 |
+
if loop[0] is None:
|
| 141 |
+
with get_lock():
|
| 142 |
+
# repeat the check just in case the loop got filled between the
|
| 143 |
+
# previous two calls from another thread
|
| 144 |
+
if loop[0] is None:
|
| 145 |
+
with _selector_policy():
|
| 146 |
+
loop[0] = asyncio.new_event_loop()
|
| 147 |
+
th = threading.Thread(target=loop[0].run_forever, name="fsspecIO")
|
| 148 |
+
th.daemon = True
|
| 149 |
+
th.start()
|
| 150 |
+
iothread[0] = th
|
| 151 |
+
return loop[0]
|
| 152 |
+
|
| 153 |
+
|
| 154 |
+
if TYPE_CHECKING:
|
| 155 |
+
import resource
|
| 156 |
+
|
| 157 |
+
ResourceError = resource.error
|
| 158 |
+
else:
|
| 159 |
+
try:
|
| 160 |
+
import resource
|
| 161 |
+
except ImportError:
|
| 162 |
+
resource = None
|
| 163 |
+
ResourceError = OSError
|
| 164 |
+
else:
|
| 165 |
+
ResourceError = getattr(resource, "error", OSError)
|
| 166 |
+
|
| 167 |
+
_DEFAULT_BATCH_SIZE = 128
|
| 168 |
+
_NOFILES_DEFAULT_BATCH_SIZE = 1280
|
| 169 |
+
|
| 170 |
+
|
| 171 |
+
def _get_batch_size(nofiles=False):
|
| 172 |
+
from fsspec.config import conf
|
| 173 |
+
|
| 174 |
+
if nofiles:
|
| 175 |
+
if "nofiles_gather_batch_size" in conf:
|
| 176 |
+
return conf["nofiles_gather_batch_size"]
|
| 177 |
+
else:
|
| 178 |
+
if "gather_batch_size" in conf:
|
| 179 |
+
return conf["gather_batch_size"]
|
| 180 |
+
if nofiles:
|
| 181 |
+
return _NOFILES_DEFAULT_BATCH_SIZE
|
| 182 |
+
if resource is None:
|
| 183 |
+
return _DEFAULT_BATCH_SIZE
|
| 184 |
+
|
| 185 |
+
try:
|
| 186 |
+
soft_limit, _ = resource.getrlimit(resource.RLIMIT_NOFILE)
|
| 187 |
+
except (ImportError, ValueError, ResourceError):
|
| 188 |
+
return _DEFAULT_BATCH_SIZE
|
| 189 |
+
|
| 190 |
+
if soft_limit == resource.RLIM_INFINITY:
|
| 191 |
+
return -1
|
| 192 |
+
else:
|
| 193 |
+
return soft_limit // 8
|
| 194 |
+
|
| 195 |
+
|
| 196 |
+
def running_async() -> bool:
|
| 197 |
+
"""Being executed by an event loop?"""
|
| 198 |
+
try:
|
| 199 |
+
asyncio.get_running_loop()
|
| 200 |
+
return True
|
| 201 |
+
except RuntimeError:
|
| 202 |
+
return False
|
| 203 |
+
|
| 204 |
+
|
| 205 |
+
async def _run_coros_in_chunks(
|
| 206 |
+
coros,
|
| 207 |
+
batch_size=None,
|
| 208 |
+
callback=DEFAULT_CALLBACK,
|
| 209 |
+
timeout=None,
|
| 210 |
+
return_exceptions=False,
|
| 211 |
+
nofiles=False,
|
| 212 |
+
):
|
| 213 |
+
"""Run the given coroutines in chunks.
|
| 214 |
+
|
| 215 |
+
Parameters
|
| 216 |
+
----------
|
| 217 |
+
coros: list of coroutines to run
|
| 218 |
+
batch_size: int or None
|
| 219 |
+
Number of coroutines to submit/wait on simultaneously.
|
| 220 |
+
If -1, then it will not be any throttling. If
|
| 221 |
+
None, it will be inferred from _get_batch_size()
|
| 222 |
+
callback: fsspec.callbacks.Callback instance
|
| 223 |
+
Gets a relative_update when each coroutine completes
|
| 224 |
+
timeout: number or None
|
| 225 |
+
If given, each coroutine times out after this time. Note that, since
|
| 226 |
+
there are multiple batches, the total run time of this function will in
|
| 227 |
+
general be longer
|
| 228 |
+
return_exceptions: bool
|
| 229 |
+
Same meaning as in asyncio.gather
|
| 230 |
+
nofiles: bool
|
| 231 |
+
If inferring the batch_size, does this operation involve local files?
|
| 232 |
+
If yes, you normally expect smaller batches.
|
| 233 |
+
"""
|
| 234 |
+
|
| 235 |
+
if batch_size is None:
|
| 236 |
+
batch_size = _get_batch_size(nofiles=nofiles)
|
| 237 |
+
|
| 238 |
+
if batch_size == -1:
|
| 239 |
+
batch_size = len(coros)
|
| 240 |
+
|
| 241 |
+
assert batch_size > 0
|
| 242 |
+
results = []
|
| 243 |
+
for start in range(0, len(coros), batch_size):
|
| 244 |
+
chunk = [
|
| 245 |
+
asyncio.Task(asyncio.wait_for(c, timeout=timeout))
|
| 246 |
+
for c in coros[start : start + batch_size]
|
| 247 |
+
]
|
| 248 |
+
if callback is not DEFAULT_CALLBACK:
|
| 249 |
+
[
|
| 250 |
+
t.add_done_callback(lambda *_, **__: callback.relative_update(1))
|
| 251 |
+
for t in chunk
|
| 252 |
+
]
|
| 253 |
+
results.extend(
|
| 254 |
+
await asyncio.gather(*chunk, return_exceptions=return_exceptions),
|
| 255 |
+
)
|
| 256 |
+
return results
|
| 257 |
+
|
| 258 |
+
|
| 259 |
+
# these methods should be implemented as async by any async-able backend
|
| 260 |
+
async_methods = [
|
| 261 |
+
"_ls",
|
| 262 |
+
"_cat_file",
|
| 263 |
+
"_get_file",
|
| 264 |
+
"_put_file",
|
| 265 |
+
"_rm_file",
|
| 266 |
+
"_cp_file",
|
| 267 |
+
"_pipe_file",
|
| 268 |
+
"_expand_path",
|
| 269 |
+
"_info",
|
| 270 |
+
"_isfile",
|
| 271 |
+
"_isdir",
|
| 272 |
+
"_exists",
|
| 273 |
+
"_walk",
|
| 274 |
+
"_glob",
|
| 275 |
+
"_find",
|
| 276 |
+
"_du",
|
| 277 |
+
"_size",
|
| 278 |
+
"_mkdir",
|
| 279 |
+
"_makedirs",
|
| 280 |
+
]
|
| 281 |
+
|
| 282 |
+
|
| 283 |
+
class AsyncFileSystem(AbstractFileSystem):
|
| 284 |
+
"""Async file operations, default implementations
|
| 285 |
+
|
| 286 |
+
Passes bulk operations to asyncio.gather for concurrent operation.
|
| 287 |
+
|
| 288 |
+
Implementations that have concurrent batch operations and/or async methods
|
| 289 |
+
should inherit from this class instead of AbstractFileSystem. Docstrings are
|
| 290 |
+
copied from the un-underscored method in AbstractFileSystem, if not given.
|
| 291 |
+
"""
|
| 292 |
+
|
| 293 |
+
# note that methods do not have docstring here; they will be copied
|
| 294 |
+
# for _* methods and inferred for overridden methods.
|
| 295 |
+
|
| 296 |
+
async_impl = True
|
| 297 |
+
mirror_sync_methods = True
|
| 298 |
+
disable_throttling = False
|
| 299 |
+
|
| 300 |
+
def __init__(self, *args, asynchronous=False, loop=None, batch_size=None, **kwargs):
|
| 301 |
+
self.asynchronous = asynchronous
|
| 302 |
+
self._pid = os.getpid()
|
| 303 |
+
if not asynchronous:
|
| 304 |
+
self._loop = loop or get_loop()
|
| 305 |
+
else:
|
| 306 |
+
self._loop = None
|
| 307 |
+
self.batch_size = batch_size
|
| 308 |
+
super().__init__(*args, **kwargs)
|
| 309 |
+
|
| 310 |
+
@property
|
| 311 |
+
def loop(self):
|
| 312 |
+
if self._pid != os.getpid():
|
| 313 |
+
raise RuntimeError("This class is not fork-safe")
|
| 314 |
+
return self._loop
|
| 315 |
+
|
| 316 |
+
async def _rm_file(self, path, **kwargs):
|
| 317 |
+
raise NotImplementedError
|
| 318 |
+
|
| 319 |
+
async def _rm(self, path, recursive=False, batch_size=None, **kwargs):
|
| 320 |
+
# TODO: implement on_error
|
| 321 |
+
batch_size = batch_size or self.batch_size
|
| 322 |
+
path = await self._expand_path(path, recursive=recursive)
|
| 323 |
+
return await _run_coros_in_chunks(
|
| 324 |
+
[self._rm_file(p, **kwargs) for p in reversed(path)],
|
| 325 |
+
batch_size=batch_size,
|
| 326 |
+
nofiles=True,
|
| 327 |
+
)
|
| 328 |
+
|
| 329 |
+
async def _cp_file(self, path1, path2, **kwargs):
|
| 330 |
+
raise NotImplementedError
|
| 331 |
+
|
| 332 |
+
async def _copy(
|
| 333 |
+
self,
|
| 334 |
+
path1,
|
| 335 |
+
path2,
|
| 336 |
+
recursive=False,
|
| 337 |
+
on_error=None,
|
| 338 |
+
maxdepth=None,
|
| 339 |
+
batch_size=None,
|
| 340 |
+
**kwargs,
|
| 341 |
+
):
|
| 342 |
+
if on_error is None and recursive:
|
| 343 |
+
on_error = "ignore"
|
| 344 |
+
elif on_error is None:
|
| 345 |
+
on_error = "raise"
|
| 346 |
+
|
| 347 |
+
if isinstance(path1, list) and isinstance(path2, list):
|
| 348 |
+
# No need to expand paths when both source and destination
|
| 349 |
+
# are provided as lists
|
| 350 |
+
paths1 = path1
|
| 351 |
+
paths2 = path2
|
| 352 |
+
else:
|
| 353 |
+
source_is_str = isinstance(path1, str)
|
| 354 |
+
paths1 = await self._expand_path(
|
| 355 |
+
path1, maxdepth=maxdepth, recursive=recursive
|
| 356 |
+
)
|
| 357 |
+
if source_is_str and (not recursive or maxdepth is not None):
|
| 358 |
+
# Non-recursive glob does not copy directories
|
| 359 |
+
paths1 = [
|
| 360 |
+
p for p in paths1 if not (trailing_sep(p) or await self._isdir(p))
|
| 361 |
+
]
|
| 362 |
+
if not paths1:
|
| 363 |
+
return
|
| 364 |
+
|
| 365 |
+
source_is_file = len(paths1) == 1
|
| 366 |
+
dest_is_dir = isinstance(path2, str) and (
|
| 367 |
+
trailing_sep(path2) or await self._isdir(path2)
|
| 368 |
+
)
|
| 369 |
+
|
| 370 |
+
exists = source_is_str and (
|
| 371 |
+
(has_magic(path1) and source_is_file)
|
| 372 |
+
or (not has_magic(path1) and dest_is_dir and not trailing_sep(path1))
|
| 373 |
+
)
|
| 374 |
+
paths2 = other_paths(
|
| 375 |
+
paths1,
|
| 376 |
+
path2,
|
| 377 |
+
exists=exists,
|
| 378 |
+
flatten=not source_is_str,
|
| 379 |
+
)
|
| 380 |
+
|
| 381 |
+
batch_size = batch_size or self.batch_size
|
| 382 |
+
coros = [self._cp_file(p1, p2, **kwargs) for p1, p2 in zip(paths1, paths2)]
|
| 383 |
+
result = await _run_coros_in_chunks(
|
| 384 |
+
coros, batch_size=batch_size, return_exceptions=True, nofiles=True
|
| 385 |
+
)
|
| 386 |
+
|
| 387 |
+
for ex in filter(is_exception, result):
|
| 388 |
+
if on_error == "ignore" and isinstance(ex, FileNotFoundError):
|
| 389 |
+
continue
|
| 390 |
+
raise ex
|
| 391 |
+
|
| 392 |
+
async def _pipe_file(self, path, value, **kwargs):
|
| 393 |
+
raise NotImplementedError
|
| 394 |
+
|
| 395 |
+
async def _pipe(self, path, value=None, batch_size=None, **kwargs):
|
| 396 |
+
if isinstance(path, str):
|
| 397 |
+
path = {path: value}
|
| 398 |
+
batch_size = batch_size or self.batch_size
|
| 399 |
+
return await _run_coros_in_chunks(
|
| 400 |
+
[self._pipe_file(k, v, **kwargs) for k, v in path.items()],
|
| 401 |
+
batch_size=batch_size,
|
| 402 |
+
nofiles=True,
|
| 403 |
+
)
|
| 404 |
+
|
| 405 |
+
async def _process_limits(self, url, start, end):
|
| 406 |
+
"""Helper for "Range"-based _cat_file"""
|
| 407 |
+
size = None
|
| 408 |
+
suff = False
|
| 409 |
+
if start is not None and start < 0:
|
| 410 |
+
# if start is negative and end None, end is the "suffix length"
|
| 411 |
+
if end is None:
|
| 412 |
+
end = -start
|
| 413 |
+
start = ""
|
| 414 |
+
suff = True
|
| 415 |
+
else:
|
| 416 |
+
size = size or (await self._info(url))["size"]
|
| 417 |
+
start = size + start
|
| 418 |
+
elif start is None:
|
| 419 |
+
start = 0
|
| 420 |
+
if not suff:
|
| 421 |
+
if end is not None and end < 0:
|
| 422 |
+
if start is not None:
|
| 423 |
+
size = size or (await self._info(url))["size"]
|
| 424 |
+
end = size + end
|
| 425 |
+
elif end is None:
|
| 426 |
+
end = ""
|
| 427 |
+
if isinstance(end, numbers.Integral):
|
| 428 |
+
end -= 1 # bytes range is inclusive
|
| 429 |
+
return f"bytes={start}-{end}"
|
| 430 |
+
|
| 431 |
+
async def _cat_file(self, path, start=None, end=None, **kwargs):
|
| 432 |
+
raise NotImplementedError
|
| 433 |
+
|
| 434 |
+
async def _cat(
|
| 435 |
+
self, path, recursive=False, on_error="raise", batch_size=None, **kwargs
|
| 436 |
+
):
|
| 437 |
+
paths = await self._expand_path(path, recursive=recursive)
|
| 438 |
+
coros = [self._cat_file(path, **kwargs) for path in paths]
|
| 439 |
+
batch_size = batch_size or self.batch_size
|
| 440 |
+
out = await _run_coros_in_chunks(
|
| 441 |
+
coros, batch_size=batch_size, nofiles=True, return_exceptions=True
|
| 442 |
+
)
|
| 443 |
+
if on_error == "raise":
|
| 444 |
+
ex = next(filter(is_exception, out), False)
|
| 445 |
+
if ex:
|
| 446 |
+
raise ex
|
| 447 |
+
if (
|
| 448 |
+
len(paths) > 1
|
| 449 |
+
or isinstance(path, list)
|
| 450 |
+
or paths[0] != self._strip_protocol(path)
|
| 451 |
+
):
|
| 452 |
+
return {
|
| 453 |
+
k: v
|
| 454 |
+
for k, v in zip(paths, out)
|
| 455 |
+
if on_error != "omit" or not is_exception(v)
|
| 456 |
+
}
|
| 457 |
+
else:
|
| 458 |
+
return out[0]
|
| 459 |
+
|
| 460 |
+
async def _cat_ranges(
|
| 461 |
+
self,
|
| 462 |
+
paths,
|
| 463 |
+
starts,
|
| 464 |
+
ends,
|
| 465 |
+
max_gap=None,
|
| 466 |
+
batch_size=None,
|
| 467 |
+
on_error="return",
|
| 468 |
+
**kwargs,
|
| 469 |
+
):
|
| 470 |
+
"""Get the contents of byte ranges from one or more files
|
| 471 |
+
|
| 472 |
+
Parameters
|
| 473 |
+
----------
|
| 474 |
+
paths: list
|
| 475 |
+
A list of of filepaths on this filesystems
|
| 476 |
+
starts, ends: int or list
|
| 477 |
+
Bytes limits of the read. If using a single int, the same value will be
|
| 478 |
+
used to read all the specified files.
|
| 479 |
+
"""
|
| 480 |
+
# TODO: on_error
|
| 481 |
+
if max_gap is not None:
|
| 482 |
+
# use utils.merge_offset_ranges
|
| 483 |
+
raise NotImplementedError
|
| 484 |
+
if not isinstance(paths, list):
|
| 485 |
+
raise TypeError
|
| 486 |
+
if not isinstance(starts, Iterable):
|
| 487 |
+
starts = [starts] * len(paths)
|
| 488 |
+
if not isinstance(ends, Iterable):
|
| 489 |
+
ends = [ends] * len(paths)
|
| 490 |
+
if len(starts) != len(paths) or len(ends) != len(paths):
|
| 491 |
+
raise ValueError
|
| 492 |
+
coros = [
|
| 493 |
+
self._cat_file(p, start=s, end=e, **kwargs)
|
| 494 |
+
for p, s, e in zip(paths, starts, ends)
|
| 495 |
+
]
|
| 496 |
+
batch_size = batch_size or self.batch_size
|
| 497 |
+
return await _run_coros_in_chunks(
|
| 498 |
+
coros, batch_size=batch_size, nofiles=True, return_exceptions=True
|
| 499 |
+
)
|
| 500 |
+
|
| 501 |
+
async def _put_file(self, lpath, rpath, **kwargs):
|
| 502 |
+
raise NotImplementedError
|
| 503 |
+
|
| 504 |
+
async def _put(
|
| 505 |
+
self,
|
| 506 |
+
lpath,
|
| 507 |
+
rpath,
|
| 508 |
+
recursive=False,
|
| 509 |
+
callback=DEFAULT_CALLBACK,
|
| 510 |
+
batch_size=None,
|
| 511 |
+
maxdepth=None,
|
| 512 |
+
**kwargs,
|
| 513 |
+
):
|
| 514 |
+
"""Copy file(s) from local.
|
| 515 |
+
|
| 516 |
+
Copies a specific file or tree of files (if recursive=True). If rpath
|
| 517 |
+
ends with a "/", it will be assumed to be a directory, and target files
|
| 518 |
+
will go within.
|
| 519 |
+
|
| 520 |
+
The put_file method will be called concurrently on a batch of files. The
|
| 521 |
+
batch_size option can configure the amount of futures that can be executed
|
| 522 |
+
at the same time. If it is -1, then all the files will be uploaded concurrently.
|
| 523 |
+
The default can be set for this instance by passing "batch_size" in the
|
| 524 |
+
constructor, or for all instances by setting the "gather_batch_size" key
|
| 525 |
+
in ``fsspec.config.conf``, falling back to 1/8th of the system limit .
|
| 526 |
+
"""
|
| 527 |
+
if isinstance(lpath, list) and isinstance(rpath, list):
|
| 528 |
+
# No need to expand paths when both source and destination
|
| 529 |
+
# are provided as lists
|
| 530 |
+
rpaths = rpath
|
| 531 |
+
lpaths = lpath
|
| 532 |
+
else:
|
| 533 |
+
source_is_str = isinstance(lpath, str)
|
| 534 |
+
if source_is_str:
|
| 535 |
+
lpath = make_path_posix(lpath)
|
| 536 |
+
fs = LocalFileSystem()
|
| 537 |
+
lpaths = fs.expand_path(lpath, recursive=recursive, maxdepth=maxdepth)
|
| 538 |
+
if source_is_str and (not recursive or maxdepth is not None):
|
| 539 |
+
# Non-recursive glob does not copy directories
|
| 540 |
+
lpaths = [p for p in lpaths if not (trailing_sep(p) or fs.isdir(p))]
|
| 541 |
+
if not lpaths:
|
| 542 |
+
return
|
| 543 |
+
|
| 544 |
+
source_is_file = len(lpaths) == 1
|
| 545 |
+
dest_is_dir = isinstance(rpath, str) and (
|
| 546 |
+
trailing_sep(rpath) or await self._isdir(rpath)
|
| 547 |
+
)
|
| 548 |
+
|
| 549 |
+
rpath = self._strip_protocol(rpath)
|
| 550 |
+
exists = source_is_str and (
|
| 551 |
+
(has_magic(lpath) and source_is_file)
|
| 552 |
+
or (not has_magic(lpath) and dest_is_dir and not trailing_sep(lpath))
|
| 553 |
+
)
|
| 554 |
+
rpaths = other_paths(
|
| 555 |
+
lpaths,
|
| 556 |
+
rpath,
|
| 557 |
+
exists=exists,
|
| 558 |
+
flatten=not source_is_str,
|
| 559 |
+
)
|
| 560 |
+
|
| 561 |
+
is_dir = {l: os.path.isdir(l) for l in lpaths}
|
| 562 |
+
rdirs = [r for l, r in zip(lpaths, rpaths) if is_dir[l]]
|
| 563 |
+
file_pairs = [(l, r) for l, r in zip(lpaths, rpaths) if not is_dir[l]]
|
| 564 |
+
|
| 565 |
+
await asyncio.gather(*[self._makedirs(d, exist_ok=True) for d in rdirs])
|
| 566 |
+
batch_size = batch_size or self.batch_size
|
| 567 |
+
|
| 568 |
+
coros = []
|
| 569 |
+
callback.set_size(len(file_pairs))
|
| 570 |
+
for lfile, rfile in file_pairs:
|
| 571 |
+
put_file = callback.branch_coro(self._put_file)
|
| 572 |
+
coros.append(put_file(lfile, rfile, **kwargs))
|
| 573 |
+
|
| 574 |
+
return await _run_coros_in_chunks(
|
| 575 |
+
coros, batch_size=batch_size, callback=callback
|
| 576 |
+
)
|
| 577 |
+
|
| 578 |
+
async def _get_file(self, rpath, lpath, **kwargs):
|
| 579 |
+
raise NotImplementedError
|
| 580 |
+
|
| 581 |
+
async def _get(
|
| 582 |
+
self,
|
| 583 |
+
rpath,
|
| 584 |
+
lpath,
|
| 585 |
+
recursive=False,
|
| 586 |
+
callback=DEFAULT_CALLBACK,
|
| 587 |
+
maxdepth=None,
|
| 588 |
+
**kwargs,
|
| 589 |
+
):
|
| 590 |
+
"""Copy file(s) to local.
|
| 591 |
+
|
| 592 |
+
Copies a specific file or tree of files (if recursive=True). If lpath
|
| 593 |
+
ends with a "/", it will be assumed to be a directory, and target files
|
| 594 |
+
will go within. Can submit a list of paths, which may be glob-patterns
|
| 595 |
+
and will be expanded.
|
| 596 |
+
|
| 597 |
+
The get_file method will be called concurrently on a batch of files. The
|
| 598 |
+
batch_size option can configure the amount of futures that can be executed
|
| 599 |
+
at the same time. If it is -1, then all the files will be uploaded concurrently.
|
| 600 |
+
The default can be set for this instance by passing "batch_size" in the
|
| 601 |
+
constructor, or for all instances by setting the "gather_batch_size" key
|
| 602 |
+
in ``fsspec.config.conf``, falling back to 1/8th of the system limit .
|
| 603 |
+
"""
|
| 604 |
+
if isinstance(lpath, list) and isinstance(rpath, list):
|
| 605 |
+
# No need to expand paths when both source and destination
|
| 606 |
+
# are provided as lists
|
| 607 |
+
rpaths = rpath
|
| 608 |
+
lpaths = lpath
|
| 609 |
+
else:
|
| 610 |
+
source_is_str = isinstance(rpath, str)
|
| 611 |
+
# First check for rpath trailing slash as _strip_protocol removes it.
|
| 612 |
+
source_not_trailing_sep = source_is_str and not trailing_sep(rpath)
|
| 613 |
+
rpath = self._strip_protocol(rpath)
|
| 614 |
+
rpaths = await self._expand_path(
|
| 615 |
+
rpath, recursive=recursive, maxdepth=maxdepth
|
| 616 |
+
)
|
| 617 |
+
if source_is_str and (not recursive or maxdepth is not None):
|
| 618 |
+
# Non-recursive glob does not copy directories
|
| 619 |
+
rpaths = [
|
| 620 |
+
p for p in rpaths if not (trailing_sep(p) or await self._isdir(p))
|
| 621 |
+
]
|
| 622 |
+
if not rpaths:
|
| 623 |
+
return
|
| 624 |
+
|
| 625 |
+
lpath = make_path_posix(lpath)
|
| 626 |
+
source_is_file = len(rpaths) == 1
|
| 627 |
+
dest_is_dir = isinstance(lpath, str) and (
|
| 628 |
+
trailing_sep(lpath) or LocalFileSystem().isdir(lpath)
|
| 629 |
+
)
|
| 630 |
+
|
| 631 |
+
exists = source_is_str and (
|
| 632 |
+
(has_magic(rpath) and source_is_file)
|
| 633 |
+
or (not has_magic(rpath) and dest_is_dir and source_not_trailing_sep)
|
| 634 |
+
)
|
| 635 |
+
lpaths = other_paths(
|
| 636 |
+
rpaths,
|
| 637 |
+
lpath,
|
| 638 |
+
exists=exists,
|
| 639 |
+
flatten=not source_is_str,
|
| 640 |
+
)
|
| 641 |
+
|
| 642 |
+
[os.makedirs(os.path.dirname(lp), exist_ok=True) for lp in lpaths]
|
| 643 |
+
batch_size = kwargs.pop("batch_size", self.batch_size)
|
| 644 |
+
|
| 645 |
+
coros = []
|
| 646 |
+
callback.set_size(len(lpaths))
|
| 647 |
+
for lpath, rpath in zip(lpaths, rpaths):
|
| 648 |
+
get_file = callback.branch_coro(self._get_file)
|
| 649 |
+
coros.append(get_file(rpath, lpath, **kwargs))
|
| 650 |
+
return await _run_coros_in_chunks(
|
| 651 |
+
coros, batch_size=batch_size, callback=callback
|
| 652 |
+
)
|
| 653 |
+
|
| 654 |
+
async def _isfile(self, path):
|
| 655 |
+
try:
|
| 656 |
+
return (await self._info(path))["type"] == "file"
|
| 657 |
+
except: # noqa: E722
|
| 658 |
+
return False
|
| 659 |
+
|
| 660 |
+
async def _isdir(self, path):
|
| 661 |
+
try:
|
| 662 |
+
return (await self._info(path))["type"] == "directory"
|
| 663 |
+
except OSError:
|
| 664 |
+
return False
|
| 665 |
+
|
| 666 |
+
async def _size(self, path):
|
| 667 |
+
return (await self._info(path)).get("size", None)
|
| 668 |
+
|
| 669 |
+
async def _sizes(self, paths, batch_size=None):
|
| 670 |
+
batch_size = batch_size or self.batch_size
|
| 671 |
+
return await _run_coros_in_chunks(
|
| 672 |
+
[self._size(p) for p in paths], batch_size=batch_size
|
| 673 |
+
)
|
| 674 |
+
|
| 675 |
+
async def _exists(self, path, **kwargs):
|
| 676 |
+
try:
|
| 677 |
+
await self._info(path, **kwargs)
|
| 678 |
+
return True
|
| 679 |
+
except FileNotFoundError:
|
| 680 |
+
return False
|
| 681 |
+
|
| 682 |
+
async def _info(self, path, **kwargs):
|
| 683 |
+
raise NotImplementedError
|
| 684 |
+
|
| 685 |
+
async def _ls(self, path, detail=True, **kwargs):
|
| 686 |
+
raise NotImplementedError
|
| 687 |
+
|
| 688 |
+
async def _walk(self, path, maxdepth=None, on_error="omit", **kwargs):
|
| 689 |
+
if maxdepth is not None and maxdepth < 1:
|
| 690 |
+
raise ValueError("maxdepth must be at least 1")
|
| 691 |
+
|
| 692 |
+
path = self._strip_protocol(path)
|
| 693 |
+
full_dirs = {}
|
| 694 |
+
dirs = {}
|
| 695 |
+
files = {}
|
| 696 |
+
|
| 697 |
+
detail = kwargs.pop("detail", False)
|
| 698 |
+
try:
|
| 699 |
+
listing = await self._ls(path, detail=True, **kwargs)
|
| 700 |
+
except (FileNotFoundError, OSError) as e:
|
| 701 |
+
if on_error == "raise":
|
| 702 |
+
raise
|
| 703 |
+
elif callable(on_error):
|
| 704 |
+
on_error(e)
|
| 705 |
+
if detail:
|
| 706 |
+
yield path, {}, {}
|
| 707 |
+
else:
|
| 708 |
+
yield path, [], []
|
| 709 |
+
return
|
| 710 |
+
|
| 711 |
+
for info in listing:
|
| 712 |
+
# each info name must be at least [path]/part , but here
|
| 713 |
+
# we check also for names like [path]/part/
|
| 714 |
+
pathname = info["name"].rstrip("/")
|
| 715 |
+
name = pathname.rsplit("/", 1)[-1]
|
| 716 |
+
if info["type"] == "directory" and pathname != path:
|
| 717 |
+
# do not include "self" path
|
| 718 |
+
full_dirs[name] = pathname
|
| 719 |
+
dirs[name] = info
|
| 720 |
+
elif pathname == path:
|
| 721 |
+
# file-like with same name as give path
|
| 722 |
+
files[""] = info
|
| 723 |
+
else:
|
| 724 |
+
files[name] = info
|
| 725 |
+
|
| 726 |
+
if detail:
|
| 727 |
+
yield path, dirs, files
|
| 728 |
+
else:
|
| 729 |
+
yield path, list(dirs), list(files)
|
| 730 |
+
|
| 731 |
+
if maxdepth is not None:
|
| 732 |
+
maxdepth -= 1
|
| 733 |
+
if maxdepth < 1:
|
| 734 |
+
return
|
| 735 |
+
|
| 736 |
+
for d in dirs:
|
| 737 |
+
async for _ in self._walk(
|
| 738 |
+
full_dirs[d], maxdepth=maxdepth, detail=detail, **kwargs
|
| 739 |
+
):
|
| 740 |
+
yield _
|
| 741 |
+
|
| 742 |
+
async def _glob(self, path, maxdepth=None, **kwargs):
|
| 743 |
+
if maxdepth is not None and maxdepth < 1:
|
| 744 |
+
raise ValueError("maxdepth must be at least 1")
|
| 745 |
+
|
| 746 |
+
import re
|
| 747 |
+
|
| 748 |
+
seps = (os.path.sep, os.path.altsep) if os.path.altsep else (os.path.sep,)
|
| 749 |
+
ends_with_sep = path.endswith(seps) # _strip_protocol strips trailing slash
|
| 750 |
+
path = self._strip_protocol(path)
|
| 751 |
+
append_slash_to_dirname = ends_with_sep or path.endswith(
|
| 752 |
+
tuple(sep + "**" for sep in seps)
|
| 753 |
+
)
|
| 754 |
+
idx_star = path.find("*") if path.find("*") >= 0 else len(path)
|
| 755 |
+
idx_qmark = path.find("?") if path.find("?") >= 0 else len(path)
|
| 756 |
+
idx_brace = path.find("[") if path.find("[") >= 0 else len(path)
|
| 757 |
+
|
| 758 |
+
min_idx = min(idx_star, idx_qmark, idx_brace)
|
| 759 |
+
|
| 760 |
+
detail = kwargs.pop("detail", False)
|
| 761 |
+
|
| 762 |
+
if not has_magic(path):
|
| 763 |
+
if await self._exists(path, **kwargs):
|
| 764 |
+
if not detail:
|
| 765 |
+
return [path]
|
| 766 |
+
else:
|
| 767 |
+
return {path: await self._info(path, **kwargs)}
|
| 768 |
+
else:
|
| 769 |
+
if not detail:
|
| 770 |
+
return [] # glob of non-existent returns empty
|
| 771 |
+
else:
|
| 772 |
+
return {}
|
| 773 |
+
elif "/" in path[:min_idx]:
|
| 774 |
+
min_idx = path[:min_idx].rindex("/")
|
| 775 |
+
root = path[: min_idx + 1]
|
| 776 |
+
depth = path[min_idx + 1 :].count("/") + 1
|
| 777 |
+
else:
|
| 778 |
+
root = ""
|
| 779 |
+
depth = path[min_idx + 1 :].count("/") + 1
|
| 780 |
+
|
| 781 |
+
if "**" in path:
|
| 782 |
+
if maxdepth is not None:
|
| 783 |
+
idx_double_stars = path.find("**")
|
| 784 |
+
depth_double_stars = path[idx_double_stars:].count("/") + 1
|
| 785 |
+
depth = depth - depth_double_stars + maxdepth
|
| 786 |
+
else:
|
| 787 |
+
depth = None
|
| 788 |
+
|
| 789 |
+
allpaths = await self._find(
|
| 790 |
+
root, maxdepth=depth, withdirs=True, detail=True, **kwargs
|
| 791 |
+
)
|
| 792 |
+
|
| 793 |
+
pattern = glob_translate(path + ("/" if ends_with_sep else ""))
|
| 794 |
+
pattern = re.compile(pattern)
|
| 795 |
+
|
| 796 |
+
out = {
|
| 797 |
+
p: info
|
| 798 |
+
for p, info in sorted(allpaths.items())
|
| 799 |
+
if pattern.match(
|
| 800 |
+
(
|
| 801 |
+
p + "/"
|
| 802 |
+
if append_slash_to_dirname and info["type"] == "directory"
|
| 803 |
+
else p
|
| 804 |
+
)
|
| 805 |
+
)
|
| 806 |
+
}
|
| 807 |
+
|
| 808 |
+
if detail:
|
| 809 |
+
return out
|
| 810 |
+
else:
|
| 811 |
+
return list(out)
|
| 812 |
+
|
| 813 |
+
async def _du(self, path, total=True, maxdepth=None, **kwargs):
|
| 814 |
+
sizes = {}
|
| 815 |
+
# async for?
|
| 816 |
+
for f in await self._find(path, maxdepth=maxdepth, **kwargs):
|
| 817 |
+
info = await self._info(f)
|
| 818 |
+
sizes[info["name"]] = info["size"]
|
| 819 |
+
if total:
|
| 820 |
+
return sum(sizes.values())
|
| 821 |
+
else:
|
| 822 |
+
return sizes
|
| 823 |
+
|
| 824 |
+
async def _find(self, path, maxdepth=None, withdirs=False, **kwargs):
|
| 825 |
+
path = self._strip_protocol(path)
|
| 826 |
+
out = {}
|
| 827 |
+
detail = kwargs.pop("detail", False)
|
| 828 |
+
|
| 829 |
+
# Add the root directory if withdirs is requested
|
| 830 |
+
# This is needed for posix glob compliance
|
| 831 |
+
if withdirs and path != "" and await self._isdir(path):
|
| 832 |
+
out[path] = await self._info(path)
|
| 833 |
+
|
| 834 |
+
# async for?
|
| 835 |
+
async for _, dirs, files in self._walk(path, maxdepth, detail=True, **kwargs):
|
| 836 |
+
if withdirs:
|
| 837 |
+
files.update(dirs)
|
| 838 |
+
out.update({info["name"]: info for name, info in files.items()})
|
| 839 |
+
if not out and (await self._isfile(path)):
|
| 840 |
+
# walk works on directories, but find should also return [path]
|
| 841 |
+
# when path happens to be a file
|
| 842 |
+
out[path] = {}
|
| 843 |
+
names = sorted(out)
|
| 844 |
+
if not detail:
|
| 845 |
+
return names
|
| 846 |
+
else:
|
| 847 |
+
return {name: out[name] for name in names}
|
| 848 |
+
|
| 849 |
+
async def _expand_path(self, path, recursive=False, maxdepth=None):
|
| 850 |
+
if maxdepth is not None and maxdepth < 1:
|
| 851 |
+
raise ValueError("maxdepth must be at least 1")
|
| 852 |
+
|
| 853 |
+
if isinstance(path, str):
|
| 854 |
+
out = await self._expand_path([path], recursive, maxdepth)
|
| 855 |
+
else:
|
| 856 |
+
out = set()
|
| 857 |
+
path = [self._strip_protocol(p) for p in path]
|
| 858 |
+
for p in path: # can gather here
|
| 859 |
+
if has_magic(p):
|
| 860 |
+
bit = set(await self._glob(p, maxdepth=maxdepth))
|
| 861 |
+
out |= bit
|
| 862 |
+
if recursive:
|
| 863 |
+
# glob call above expanded one depth so if maxdepth is defined
|
| 864 |
+
# then decrement it in expand_path call below. If it is zero
|
| 865 |
+
# after decrementing then avoid expand_path call.
|
| 866 |
+
if maxdepth is not None and maxdepth <= 1:
|
| 867 |
+
continue
|
| 868 |
+
out |= set(
|
| 869 |
+
await self._expand_path(
|
| 870 |
+
list(bit),
|
| 871 |
+
recursive=recursive,
|
| 872 |
+
maxdepth=maxdepth - 1 if maxdepth is not None else None,
|
| 873 |
+
)
|
| 874 |
+
)
|
| 875 |
+
continue
|
| 876 |
+
elif recursive:
|
| 877 |
+
rec = set(await self._find(p, maxdepth=maxdepth, withdirs=True))
|
| 878 |
+
out |= rec
|
| 879 |
+
if p not in out and (recursive is False or (await self._exists(p))):
|
| 880 |
+
# should only check once, for the root
|
| 881 |
+
out.add(p)
|
| 882 |
+
if not out:
|
| 883 |
+
raise FileNotFoundError(path)
|
| 884 |
+
return sorted(out)
|
| 885 |
+
|
| 886 |
+
async def _mkdir(self, path, create_parents=True, **kwargs):
|
| 887 |
+
pass # not necessary to implement, may not have directories
|
| 888 |
+
|
| 889 |
+
async def _makedirs(self, path, exist_ok=False):
|
| 890 |
+
pass # not necessary to implement, may not have directories
|
| 891 |
+
|
| 892 |
+
async def open_async(self, path, mode="rb", **kwargs):
|
| 893 |
+
if "b" not in mode or kwargs.get("compression"):
|
| 894 |
+
raise ValueError
|
| 895 |
+
raise NotImplementedError
|
| 896 |
+
|
| 897 |
+
|
| 898 |
+
def mirror_sync_methods(obj):
|
| 899 |
+
"""Populate sync and async methods for obj
|
| 900 |
+
|
| 901 |
+
For each method will create a sync version if the name refers to an async method
|
| 902 |
+
(coroutine) and there is no override in the child class; will create an async
|
| 903 |
+
method for the corresponding sync method if there is no implementation.
|
| 904 |
+
|
| 905 |
+
Uses the methods specified in
|
| 906 |
+
- async_methods: the set that an implementation is expected to provide
|
| 907 |
+
- default_async_methods: that can be derived from their sync version in
|
| 908 |
+
AbstractFileSystem
|
| 909 |
+
- AsyncFileSystem: async-specific default coroutines
|
| 910 |
+
"""
|
| 911 |
+
from fsspec import AbstractFileSystem
|
| 912 |
+
|
| 913 |
+
for method in async_methods + dir(AsyncFileSystem):
|
| 914 |
+
if not method.startswith("_"):
|
| 915 |
+
continue
|
| 916 |
+
smethod = method[1:]
|
| 917 |
+
if private.match(method):
|
| 918 |
+
isco = inspect.iscoroutinefunction(getattr(obj, method, None))
|
| 919 |
+
unsync = getattr(getattr(obj, smethod, False), "__func__", None)
|
| 920 |
+
is_default = unsync is getattr(AbstractFileSystem, smethod, "")
|
| 921 |
+
if isco and is_default:
|
| 922 |
+
mth = sync_wrapper(getattr(obj, method), obj=obj)
|
| 923 |
+
setattr(obj, smethod, mth)
|
| 924 |
+
if not mth.__doc__:
|
| 925 |
+
mth.__doc__ = getattr(
|
| 926 |
+
getattr(AbstractFileSystem, smethod, None), "__doc__", ""
|
| 927 |
+
)
|
| 928 |
+
|
| 929 |
+
|
| 930 |
+
class FSSpecCoroutineCancel(Exception):
|
| 931 |
+
pass
|
| 932 |
+
|
| 933 |
+
|
| 934 |
+
def _dump_running_tasks(
|
| 935 |
+
printout=True, cancel=True, exc=FSSpecCoroutineCancel, with_task=False
|
| 936 |
+
):
|
| 937 |
+
import traceback
|
| 938 |
+
|
| 939 |
+
tasks = [t for t in asyncio.tasks.all_tasks(loop[0]) if not t.done()]
|
| 940 |
+
if printout:
|
| 941 |
+
[task.print_stack() for task in tasks]
|
| 942 |
+
out = [
|
| 943 |
+
{
|
| 944 |
+
"locals": task._coro.cr_frame.f_locals,
|
| 945 |
+
"file": task._coro.cr_frame.f_code.co_filename,
|
| 946 |
+
"firstline": task._coro.cr_frame.f_code.co_firstlineno,
|
| 947 |
+
"linelo": task._coro.cr_frame.f_lineno,
|
| 948 |
+
"stack": traceback.format_stack(task._coro.cr_frame),
|
| 949 |
+
"task": task if with_task else None,
|
| 950 |
+
}
|
| 951 |
+
for task in tasks
|
| 952 |
+
]
|
| 953 |
+
if cancel:
|
| 954 |
+
for t in tasks:
|
| 955 |
+
cbs = t._callbacks
|
| 956 |
+
t.cancel()
|
| 957 |
+
asyncio.futures.Future.set_exception(t, exc)
|
| 958 |
+
asyncio.futures.Future.cancel(t)
|
| 959 |
+
[cb[0](t) for cb in cbs] # cancels any dependent concurrent.futures
|
| 960 |
+
try:
|
| 961 |
+
t._coro.throw(exc) # exits coro, unless explicitly handled
|
| 962 |
+
except exc:
|
| 963 |
+
pass
|
| 964 |
+
return out
|
| 965 |
+
|
| 966 |
+
|
| 967 |
+
class AbstractAsyncStreamedFile(AbstractBufferedFile):
|
| 968 |
+
# no read buffering, and always auto-commit
|
| 969 |
+
# TODO: readahead might still be useful here, but needs async version
|
| 970 |
+
|
| 971 |
+
async def read(self, length=-1):
|
| 972 |
+
"""
|
| 973 |
+
Return data from cache, or fetch pieces as necessary
|
| 974 |
+
|
| 975 |
+
Parameters
|
| 976 |
+
----------
|
| 977 |
+
length: int (-1)
|
| 978 |
+
Number of bytes to read; if <0, all remaining bytes.
|
| 979 |
+
"""
|
| 980 |
+
length = -1 if length is None else int(length)
|
| 981 |
+
if self.mode != "rb":
|
| 982 |
+
raise ValueError("File not in read mode")
|
| 983 |
+
if length < 0:
|
| 984 |
+
length = self.size - self.loc
|
| 985 |
+
if self.closed:
|
| 986 |
+
raise ValueError("I/O operation on closed file.")
|
| 987 |
+
if length == 0:
|
| 988 |
+
# don't even bother calling fetch
|
| 989 |
+
return b""
|
| 990 |
+
out = await self._fetch_range(self.loc, self.loc + length)
|
| 991 |
+
self.loc += len(out)
|
| 992 |
+
return out
|
| 993 |
+
|
| 994 |
+
async def write(self, data):
|
| 995 |
+
"""
|
| 996 |
+
Write data to buffer.
|
| 997 |
+
|
| 998 |
+
Buffer only sent on flush() or if buffer is greater than
|
| 999 |
+
or equal to blocksize.
|
| 1000 |
+
|
| 1001 |
+
Parameters
|
| 1002 |
+
----------
|
| 1003 |
+
data: bytes
|
| 1004 |
+
Set of bytes to be written.
|
| 1005 |
+
"""
|
| 1006 |
+
if self.mode not in {"wb", "ab"}:
|
| 1007 |
+
raise ValueError("File not in write mode")
|
| 1008 |
+
if self.closed:
|
| 1009 |
+
raise ValueError("I/O operation on closed file.")
|
| 1010 |
+
if self.forced:
|
| 1011 |
+
raise ValueError("This file has been force-flushed, can only close")
|
| 1012 |
+
out = self.buffer.write(data)
|
| 1013 |
+
self.loc += out
|
| 1014 |
+
if self.buffer.tell() >= self.blocksize:
|
| 1015 |
+
await self.flush()
|
| 1016 |
+
return out
|
| 1017 |
+
|
| 1018 |
+
async def close(self):
|
| 1019 |
+
"""Close file
|
| 1020 |
+
|
| 1021 |
+
Finalizes writes, discards cache
|
| 1022 |
+
"""
|
| 1023 |
+
if getattr(self, "_unclosable", False):
|
| 1024 |
+
return
|
| 1025 |
+
if self.closed:
|
| 1026 |
+
return
|
| 1027 |
+
if self.mode == "rb":
|
| 1028 |
+
self.cache = None
|
| 1029 |
+
else:
|
| 1030 |
+
if not self.forced:
|
| 1031 |
+
await self.flush(force=True)
|
| 1032 |
+
|
| 1033 |
+
if self.fs is not None:
|
| 1034 |
+
self.fs.invalidate_cache(self.path)
|
| 1035 |
+
self.fs.invalidate_cache(self.fs._parent(self.path))
|
| 1036 |
+
|
| 1037 |
+
self.closed = True
|
| 1038 |
+
|
| 1039 |
+
async def flush(self, force=False):
|
| 1040 |
+
if self.closed:
|
| 1041 |
+
raise ValueError("Flush on closed file")
|
| 1042 |
+
if force and self.forced:
|
| 1043 |
+
raise ValueError("Force flush cannot be called more than once")
|
| 1044 |
+
if force:
|
| 1045 |
+
self.forced = True
|
| 1046 |
+
|
| 1047 |
+
if self.mode not in {"wb", "ab"}:
|
| 1048 |
+
# no-op to flush on read-mode
|
| 1049 |
+
return
|
| 1050 |
+
|
| 1051 |
+
if not force and self.buffer.tell() < self.blocksize:
|
| 1052 |
+
# Defer write on small block
|
| 1053 |
+
return
|
| 1054 |
+
|
| 1055 |
+
if self.offset is None:
|
| 1056 |
+
# Initialize a multipart upload
|
| 1057 |
+
self.offset = 0
|
| 1058 |
+
try:
|
| 1059 |
+
await self._initiate_upload()
|
| 1060 |
+
except: # noqa: E722
|
| 1061 |
+
self.closed = True
|
| 1062 |
+
raise
|
| 1063 |
+
|
| 1064 |
+
if await self._upload_chunk(final=force) is not False:
|
| 1065 |
+
self.offset += self.buffer.seek(0, 2)
|
| 1066 |
+
self.buffer = io.BytesIO()
|
| 1067 |
+
|
| 1068 |
+
async def __aenter__(self):
|
| 1069 |
+
return self
|
| 1070 |
+
|
| 1071 |
+
async def __aexit__(self, exc_type, exc_val, exc_tb):
|
| 1072 |
+
await self.close()
|
| 1073 |
+
|
| 1074 |
+
async def _fetch_range(self, start, end):
|
| 1075 |
+
raise NotImplementedError
|
| 1076 |
+
|
| 1077 |
+
async def _initiate_upload(self):
|
| 1078 |
+
pass
|
| 1079 |
+
|
| 1080 |
+
async def _upload_chunk(self, final=False):
|
| 1081 |
+
raise NotImplementedError
|
tuning-competition-baseline/.venv/lib/python3.11/site-packages/fsspec/caching.py
ADDED
|
@@ -0,0 +1,875 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
import collections
|
| 4 |
+
import functools
|
| 5 |
+
import logging
|
| 6 |
+
import math
|
| 7 |
+
import os
|
| 8 |
+
import threading
|
| 9 |
+
import warnings
|
| 10 |
+
from concurrent.futures import Future, ThreadPoolExecutor
|
| 11 |
+
from typing import (
|
| 12 |
+
TYPE_CHECKING,
|
| 13 |
+
Any,
|
| 14 |
+
Callable,
|
| 15 |
+
ClassVar,
|
| 16 |
+
Generic,
|
| 17 |
+
NamedTuple,
|
| 18 |
+
OrderedDict,
|
| 19 |
+
TypeVar,
|
| 20 |
+
)
|
| 21 |
+
|
| 22 |
+
if TYPE_CHECKING:
|
| 23 |
+
import mmap
|
| 24 |
+
|
| 25 |
+
from typing_extensions import ParamSpec
|
| 26 |
+
|
| 27 |
+
P = ParamSpec("P")
|
| 28 |
+
else:
|
| 29 |
+
P = TypeVar("P")
|
| 30 |
+
|
| 31 |
+
T = TypeVar("T")
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
logger = logging.getLogger("fsspec")
|
| 35 |
+
|
| 36 |
+
Fetcher = Callable[[int, int], bytes] # Maps (start, end) to bytes
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
class BaseCache:
|
| 40 |
+
"""Pass-though cache: doesn't keep anything, calls every time
|
| 41 |
+
|
| 42 |
+
Acts as base class for other cachers
|
| 43 |
+
|
| 44 |
+
Parameters
|
| 45 |
+
----------
|
| 46 |
+
blocksize: int
|
| 47 |
+
How far to read ahead in numbers of bytes
|
| 48 |
+
fetcher: func
|
| 49 |
+
Function of the form f(start, end) which gets bytes from remote as
|
| 50 |
+
specified
|
| 51 |
+
size: int
|
| 52 |
+
How big this file is
|
| 53 |
+
"""
|
| 54 |
+
|
| 55 |
+
name: ClassVar[str] = "none"
|
| 56 |
+
|
| 57 |
+
def __init__(self, blocksize: int, fetcher: Fetcher, size: int) -> None:
|
| 58 |
+
self.blocksize = blocksize
|
| 59 |
+
self.fetcher = fetcher
|
| 60 |
+
self.size = size
|
| 61 |
+
|
| 62 |
+
def _fetch(self, start: int | None, stop: int | None) -> bytes:
|
| 63 |
+
if start is None:
|
| 64 |
+
start = 0
|
| 65 |
+
if stop is None:
|
| 66 |
+
stop = self.size
|
| 67 |
+
if start >= self.size or start >= stop:
|
| 68 |
+
return b""
|
| 69 |
+
return self.fetcher(start, stop)
|
| 70 |
+
|
| 71 |
+
|
| 72 |
+
class MMapCache(BaseCache):
|
| 73 |
+
"""memory-mapped sparse file cache
|
| 74 |
+
|
| 75 |
+
Opens temporary file, which is filled blocks-wise when data is requested.
|
| 76 |
+
Ensure there is enough disc space in the temporary location.
|
| 77 |
+
|
| 78 |
+
This cache method might only work on posix
|
| 79 |
+
"""
|
| 80 |
+
|
| 81 |
+
name = "mmap"
|
| 82 |
+
|
| 83 |
+
def __init__(
|
| 84 |
+
self,
|
| 85 |
+
blocksize: int,
|
| 86 |
+
fetcher: Fetcher,
|
| 87 |
+
size: int,
|
| 88 |
+
location: str | None = None,
|
| 89 |
+
blocks: set[int] | None = None,
|
| 90 |
+
) -> None:
|
| 91 |
+
super().__init__(blocksize, fetcher, size)
|
| 92 |
+
self.blocks = set() if blocks is None else blocks
|
| 93 |
+
self.location = location
|
| 94 |
+
self.cache = self._makefile()
|
| 95 |
+
|
| 96 |
+
def _makefile(self) -> mmap.mmap | bytearray:
|
| 97 |
+
import mmap
|
| 98 |
+
import tempfile
|
| 99 |
+
|
| 100 |
+
if self.size == 0:
|
| 101 |
+
return bytearray()
|
| 102 |
+
|
| 103 |
+
# posix version
|
| 104 |
+
if self.location is None or not os.path.exists(self.location):
|
| 105 |
+
if self.location is None:
|
| 106 |
+
fd = tempfile.TemporaryFile()
|
| 107 |
+
self.blocks = set()
|
| 108 |
+
else:
|
| 109 |
+
fd = open(self.location, "wb+")
|
| 110 |
+
fd.seek(self.size - 1)
|
| 111 |
+
fd.write(b"1")
|
| 112 |
+
fd.flush()
|
| 113 |
+
else:
|
| 114 |
+
fd = open(self.location, "r+b")
|
| 115 |
+
|
| 116 |
+
return mmap.mmap(fd.fileno(), self.size)
|
| 117 |
+
|
| 118 |
+
def _fetch(self, start: int | None, end: int | None) -> bytes:
|
| 119 |
+
logger.debug(f"MMap cache fetching {start}-{end}")
|
| 120 |
+
if start is None:
|
| 121 |
+
start = 0
|
| 122 |
+
if end is None:
|
| 123 |
+
end = self.size
|
| 124 |
+
if start >= self.size or start >= end:
|
| 125 |
+
return b""
|
| 126 |
+
start_block = start // self.blocksize
|
| 127 |
+
end_block = end // self.blocksize
|
| 128 |
+
need = [i for i in range(start_block, end_block + 1) if i not in self.blocks]
|
| 129 |
+
while need:
|
| 130 |
+
# TODO: not a for loop so we can consolidate blocks later to
|
| 131 |
+
# make fewer fetch calls; this could be parallel
|
| 132 |
+
i = need.pop(0)
|
| 133 |
+
sstart = i * self.blocksize
|
| 134 |
+
send = min(sstart + self.blocksize, self.size)
|
| 135 |
+
logger.debug(f"MMap get block #{i} ({sstart}-{send}")
|
| 136 |
+
self.cache[sstart:send] = self.fetcher(sstart, send)
|
| 137 |
+
self.blocks.add(i)
|
| 138 |
+
|
| 139 |
+
return self.cache[start:end]
|
| 140 |
+
|
| 141 |
+
def __getstate__(self) -> dict[str, Any]:
|
| 142 |
+
state = self.__dict__.copy()
|
| 143 |
+
# Remove the unpicklable entries.
|
| 144 |
+
del state["cache"]
|
| 145 |
+
return state
|
| 146 |
+
|
| 147 |
+
def __setstate__(self, state: dict[str, Any]) -> None:
|
| 148 |
+
# Restore instance attributes
|
| 149 |
+
self.__dict__.update(state)
|
| 150 |
+
self.cache = self._makefile()
|
| 151 |
+
|
| 152 |
+
|
| 153 |
+
class ReadAheadCache(BaseCache):
|
| 154 |
+
"""Cache which reads only when we get beyond a block of data
|
| 155 |
+
|
| 156 |
+
This is a much simpler version of BytesCache, and does not attempt to
|
| 157 |
+
fill holes in the cache or keep fragments alive. It is best suited to
|
| 158 |
+
many small reads in a sequential order (e.g., reading lines from a file).
|
| 159 |
+
"""
|
| 160 |
+
|
| 161 |
+
name = "readahead"
|
| 162 |
+
|
| 163 |
+
def __init__(self, blocksize: int, fetcher: Fetcher, size: int) -> None:
|
| 164 |
+
super().__init__(blocksize, fetcher, size)
|
| 165 |
+
self.cache = b""
|
| 166 |
+
self.start = 0
|
| 167 |
+
self.end = 0
|
| 168 |
+
|
| 169 |
+
def _fetch(self, start: int | None, end: int | None) -> bytes:
|
| 170 |
+
if start is None:
|
| 171 |
+
start = 0
|
| 172 |
+
if end is None or end > self.size:
|
| 173 |
+
end = self.size
|
| 174 |
+
if start >= self.size or start >= end:
|
| 175 |
+
return b""
|
| 176 |
+
l = end - start
|
| 177 |
+
if start >= self.start and end <= self.end:
|
| 178 |
+
# cache hit
|
| 179 |
+
return self.cache[start - self.start : end - self.start]
|
| 180 |
+
elif self.start <= start < self.end:
|
| 181 |
+
# partial hit
|
| 182 |
+
part = self.cache[start - self.start :]
|
| 183 |
+
l -= len(part)
|
| 184 |
+
start = self.end
|
| 185 |
+
else:
|
| 186 |
+
# miss
|
| 187 |
+
part = b""
|
| 188 |
+
end = min(self.size, end + self.blocksize)
|
| 189 |
+
self.cache = self.fetcher(start, end) # new block replaces old
|
| 190 |
+
self.start = start
|
| 191 |
+
self.end = self.start + len(self.cache)
|
| 192 |
+
return part + self.cache[:l]
|
| 193 |
+
|
| 194 |
+
|
| 195 |
+
class FirstChunkCache(BaseCache):
|
| 196 |
+
"""Caches the first block of a file only
|
| 197 |
+
|
| 198 |
+
This may be useful for file types where the metadata is stored in the header,
|
| 199 |
+
but is randomly accessed.
|
| 200 |
+
"""
|
| 201 |
+
|
| 202 |
+
name = "first"
|
| 203 |
+
|
| 204 |
+
def __init__(self, blocksize: int, fetcher: Fetcher, size: int) -> None:
|
| 205 |
+
super().__init__(blocksize, fetcher, size)
|
| 206 |
+
self.cache: bytes | None = None
|
| 207 |
+
|
| 208 |
+
def _fetch(self, start: int | None, end: int | None) -> bytes:
|
| 209 |
+
start = start or 0
|
| 210 |
+
end = end or self.size
|
| 211 |
+
if start < self.blocksize:
|
| 212 |
+
if self.cache is None:
|
| 213 |
+
if end > self.blocksize:
|
| 214 |
+
data = self.fetcher(0, end)
|
| 215 |
+
self.cache = data[: self.blocksize]
|
| 216 |
+
return data[start:]
|
| 217 |
+
self.cache = self.fetcher(0, self.blocksize)
|
| 218 |
+
part = self.cache[start:end]
|
| 219 |
+
if end > self.blocksize:
|
| 220 |
+
part += self.fetcher(self.blocksize, end)
|
| 221 |
+
return part
|
| 222 |
+
else:
|
| 223 |
+
return self.fetcher(start, end)
|
| 224 |
+
|
| 225 |
+
|
| 226 |
+
class BlockCache(BaseCache):
|
| 227 |
+
"""
|
| 228 |
+
Cache holding memory as a set of blocks.
|
| 229 |
+
|
| 230 |
+
Requests are only ever made ``blocksize`` at a time, and are
|
| 231 |
+
stored in an LRU cache. The least recently accessed block is
|
| 232 |
+
discarded when more than ``maxblocks`` are stored.
|
| 233 |
+
|
| 234 |
+
Parameters
|
| 235 |
+
----------
|
| 236 |
+
blocksize : int
|
| 237 |
+
The number of bytes to store in each block.
|
| 238 |
+
Requests are only ever made for ``blocksize``, so this
|
| 239 |
+
should balance the overhead of making a request against
|
| 240 |
+
the granularity of the blocks.
|
| 241 |
+
fetcher : Callable
|
| 242 |
+
size : int
|
| 243 |
+
The total size of the file being cached.
|
| 244 |
+
maxblocks : int
|
| 245 |
+
The maximum number of blocks to cache for. The maximum memory
|
| 246 |
+
use for this cache is then ``blocksize * maxblocks``.
|
| 247 |
+
"""
|
| 248 |
+
|
| 249 |
+
name = "blockcache"
|
| 250 |
+
|
| 251 |
+
def __init__(
|
| 252 |
+
self, blocksize: int, fetcher: Fetcher, size: int, maxblocks: int = 32
|
| 253 |
+
) -> None:
|
| 254 |
+
super().__init__(blocksize, fetcher, size)
|
| 255 |
+
self.nblocks = math.ceil(size / blocksize)
|
| 256 |
+
self.maxblocks = maxblocks
|
| 257 |
+
self._fetch_block_cached = functools.lru_cache(maxblocks)(self._fetch_block)
|
| 258 |
+
|
| 259 |
+
def __repr__(self) -> str:
|
| 260 |
+
return (
|
| 261 |
+
f"<BlockCache blocksize={self.blocksize}, "
|
| 262 |
+
f"size={self.size}, nblocks={self.nblocks}>"
|
| 263 |
+
)
|
| 264 |
+
|
| 265 |
+
def cache_info(self):
|
| 266 |
+
"""
|
| 267 |
+
The statistics on the block cache.
|
| 268 |
+
|
| 269 |
+
Returns
|
| 270 |
+
-------
|
| 271 |
+
NamedTuple
|
| 272 |
+
Returned directly from the LRU Cache used internally.
|
| 273 |
+
"""
|
| 274 |
+
return self._fetch_block_cached.cache_info()
|
| 275 |
+
|
| 276 |
+
def __getstate__(self) -> dict[str, Any]:
|
| 277 |
+
state = self.__dict__
|
| 278 |
+
del state["_fetch_block_cached"]
|
| 279 |
+
return state
|
| 280 |
+
|
| 281 |
+
def __setstate__(self, state: dict[str, Any]) -> None:
|
| 282 |
+
self.__dict__.update(state)
|
| 283 |
+
self._fetch_block_cached = functools.lru_cache(state["maxblocks"])(
|
| 284 |
+
self._fetch_block
|
| 285 |
+
)
|
| 286 |
+
|
| 287 |
+
def _fetch(self, start: int | None, end: int | None) -> bytes:
|
| 288 |
+
if start is None:
|
| 289 |
+
start = 0
|
| 290 |
+
if end is None:
|
| 291 |
+
end = self.size
|
| 292 |
+
if start >= self.size or start >= end:
|
| 293 |
+
return b""
|
| 294 |
+
|
| 295 |
+
# byte position -> block numbers
|
| 296 |
+
start_block_number = start // self.blocksize
|
| 297 |
+
end_block_number = end // self.blocksize
|
| 298 |
+
|
| 299 |
+
# these are cached, so safe to do multiple calls for the same start and end.
|
| 300 |
+
for block_number in range(start_block_number, end_block_number + 1):
|
| 301 |
+
self._fetch_block_cached(block_number)
|
| 302 |
+
|
| 303 |
+
return self._read_cache(
|
| 304 |
+
start,
|
| 305 |
+
end,
|
| 306 |
+
start_block_number=start_block_number,
|
| 307 |
+
end_block_number=end_block_number,
|
| 308 |
+
)
|
| 309 |
+
|
| 310 |
+
def _fetch_block(self, block_number: int) -> bytes:
|
| 311 |
+
"""
|
| 312 |
+
Fetch the block of data for `block_number`.
|
| 313 |
+
"""
|
| 314 |
+
if block_number > self.nblocks:
|
| 315 |
+
raise ValueError(
|
| 316 |
+
f"'block_number={block_number}' is greater than "
|
| 317 |
+
f"the number of blocks ({self.nblocks})"
|
| 318 |
+
)
|
| 319 |
+
|
| 320 |
+
start = block_number * self.blocksize
|
| 321 |
+
end = start + self.blocksize
|
| 322 |
+
logger.info("BlockCache fetching block %d", block_number)
|
| 323 |
+
block_contents = super()._fetch(start, end)
|
| 324 |
+
return block_contents
|
| 325 |
+
|
| 326 |
+
def _read_cache(
|
| 327 |
+
self, start: int, end: int, start_block_number: int, end_block_number: int
|
| 328 |
+
) -> bytes:
|
| 329 |
+
"""
|
| 330 |
+
Read from our block cache.
|
| 331 |
+
|
| 332 |
+
Parameters
|
| 333 |
+
----------
|
| 334 |
+
start, end : int
|
| 335 |
+
The start and end byte positions.
|
| 336 |
+
start_block_number, end_block_number : int
|
| 337 |
+
The start and end block numbers.
|
| 338 |
+
"""
|
| 339 |
+
start_pos = start % self.blocksize
|
| 340 |
+
end_pos = end % self.blocksize
|
| 341 |
+
|
| 342 |
+
if start_block_number == end_block_number:
|
| 343 |
+
block: bytes = self._fetch_block_cached(start_block_number)
|
| 344 |
+
return block[start_pos:end_pos]
|
| 345 |
+
|
| 346 |
+
else:
|
| 347 |
+
# read from the initial
|
| 348 |
+
out = []
|
| 349 |
+
out.append(self._fetch_block_cached(start_block_number)[start_pos:])
|
| 350 |
+
|
| 351 |
+
# intermediate blocks
|
| 352 |
+
# Note: it'd be nice to combine these into one big request. However
|
| 353 |
+
# that doesn't play nicely with our LRU cache.
|
| 354 |
+
for block_number in range(start_block_number + 1, end_block_number):
|
| 355 |
+
out.append(self._fetch_block_cached(block_number))
|
| 356 |
+
|
| 357 |
+
# final block
|
| 358 |
+
out.append(self._fetch_block_cached(end_block_number)[:end_pos])
|
| 359 |
+
|
| 360 |
+
return b"".join(out)
|
| 361 |
+
|
| 362 |
+
|
| 363 |
+
class BytesCache(BaseCache):
|
| 364 |
+
"""Cache which holds data in a in-memory bytes object
|
| 365 |
+
|
| 366 |
+
Implements read-ahead by the block size, for semi-random reads progressing
|
| 367 |
+
through the file.
|
| 368 |
+
|
| 369 |
+
Parameters
|
| 370 |
+
----------
|
| 371 |
+
trim: bool
|
| 372 |
+
As we read more data, whether to discard the start of the buffer when
|
| 373 |
+
we are more than a blocksize ahead of it.
|
| 374 |
+
"""
|
| 375 |
+
|
| 376 |
+
name: ClassVar[str] = "bytes"
|
| 377 |
+
|
| 378 |
+
def __init__(
|
| 379 |
+
self, blocksize: int, fetcher: Fetcher, size: int, trim: bool = True
|
| 380 |
+
) -> None:
|
| 381 |
+
super().__init__(blocksize, fetcher, size)
|
| 382 |
+
self.cache = b""
|
| 383 |
+
self.start: int | None = None
|
| 384 |
+
self.end: int | None = None
|
| 385 |
+
self.trim = trim
|
| 386 |
+
|
| 387 |
+
def _fetch(self, start: int | None, end: int | None) -> bytes:
|
| 388 |
+
# TODO: only set start/end after fetch, in case it fails?
|
| 389 |
+
# is this where retry logic might go?
|
| 390 |
+
if start is None:
|
| 391 |
+
start = 0
|
| 392 |
+
if end is None:
|
| 393 |
+
end = self.size
|
| 394 |
+
if start >= self.size or start >= end:
|
| 395 |
+
return b""
|
| 396 |
+
if (
|
| 397 |
+
self.start is not None
|
| 398 |
+
and start >= self.start
|
| 399 |
+
and self.end is not None
|
| 400 |
+
and end < self.end
|
| 401 |
+
):
|
| 402 |
+
# cache hit: we have all the required data
|
| 403 |
+
offset = start - self.start
|
| 404 |
+
return self.cache[offset : offset + end - start]
|
| 405 |
+
|
| 406 |
+
if self.blocksize:
|
| 407 |
+
bend = min(self.size, end + self.blocksize)
|
| 408 |
+
else:
|
| 409 |
+
bend = end
|
| 410 |
+
|
| 411 |
+
if bend == start or start > self.size:
|
| 412 |
+
return b""
|
| 413 |
+
|
| 414 |
+
if (self.start is None or start < self.start) and (
|
| 415 |
+
self.end is None or end > self.end
|
| 416 |
+
):
|
| 417 |
+
# First read, or extending both before and after
|
| 418 |
+
self.cache = self.fetcher(start, bend)
|
| 419 |
+
self.start = start
|
| 420 |
+
else:
|
| 421 |
+
assert self.start is not None
|
| 422 |
+
assert self.end is not None
|
| 423 |
+
|
| 424 |
+
if start < self.start:
|
| 425 |
+
if self.end is None or self.end - end > self.blocksize:
|
| 426 |
+
self.cache = self.fetcher(start, bend)
|
| 427 |
+
self.start = start
|
| 428 |
+
else:
|
| 429 |
+
new = self.fetcher(start, self.start)
|
| 430 |
+
self.start = start
|
| 431 |
+
self.cache = new + self.cache
|
| 432 |
+
elif self.end is not None and bend > self.end:
|
| 433 |
+
if self.end > self.size:
|
| 434 |
+
pass
|
| 435 |
+
elif end - self.end > self.blocksize:
|
| 436 |
+
self.cache = self.fetcher(start, bend)
|
| 437 |
+
self.start = start
|
| 438 |
+
else:
|
| 439 |
+
new = self.fetcher(self.end, bend)
|
| 440 |
+
self.cache = self.cache + new
|
| 441 |
+
|
| 442 |
+
self.end = self.start + len(self.cache)
|
| 443 |
+
offset = start - self.start
|
| 444 |
+
out = self.cache[offset : offset + end - start]
|
| 445 |
+
if self.trim:
|
| 446 |
+
num = (self.end - self.start) // (self.blocksize + 1)
|
| 447 |
+
if num > 1:
|
| 448 |
+
self.start += self.blocksize * num
|
| 449 |
+
self.cache = self.cache[self.blocksize * num :]
|
| 450 |
+
return out
|
| 451 |
+
|
| 452 |
+
def __len__(self) -> int:
|
| 453 |
+
return len(self.cache)
|
| 454 |
+
|
| 455 |
+
|
| 456 |
+
class AllBytes(BaseCache):
|
| 457 |
+
"""Cache entire contents of the file"""
|
| 458 |
+
|
| 459 |
+
name: ClassVar[str] = "all"
|
| 460 |
+
|
| 461 |
+
def __init__(
|
| 462 |
+
self,
|
| 463 |
+
blocksize: int | None = None,
|
| 464 |
+
fetcher: Fetcher | None = None,
|
| 465 |
+
size: int | None = None,
|
| 466 |
+
data: bytes | None = None,
|
| 467 |
+
) -> None:
|
| 468 |
+
super().__init__(blocksize, fetcher, size) # type: ignore[arg-type]
|
| 469 |
+
if data is None:
|
| 470 |
+
data = self.fetcher(0, self.size)
|
| 471 |
+
self.data = data
|
| 472 |
+
|
| 473 |
+
def _fetch(self, start: int | None, stop: int | None) -> bytes:
|
| 474 |
+
return self.data[start:stop]
|
| 475 |
+
|
| 476 |
+
|
| 477 |
+
class KnownPartsOfAFile(BaseCache):
|
| 478 |
+
"""
|
| 479 |
+
Cache holding known file parts.
|
| 480 |
+
|
| 481 |
+
Parameters
|
| 482 |
+
----------
|
| 483 |
+
blocksize: int
|
| 484 |
+
How far to read ahead in numbers of bytes
|
| 485 |
+
fetcher: func
|
| 486 |
+
Function of the form f(start, end) which gets bytes from remote as
|
| 487 |
+
specified
|
| 488 |
+
size: int
|
| 489 |
+
How big this file is
|
| 490 |
+
data: dict
|
| 491 |
+
A dictionary mapping explicit `(start, stop)` file-offset tuples
|
| 492 |
+
with known bytes.
|
| 493 |
+
strict: bool, default True
|
| 494 |
+
Whether to fetch reads that go beyond a known byte-range boundary.
|
| 495 |
+
If `False`, any read that ends outside a known part will be zero
|
| 496 |
+
padded. Note that zero padding will not be used for reads that
|
| 497 |
+
begin outside a known byte-range.
|
| 498 |
+
"""
|
| 499 |
+
|
| 500 |
+
name: ClassVar[str] = "parts"
|
| 501 |
+
|
| 502 |
+
def __init__(
|
| 503 |
+
self,
|
| 504 |
+
blocksize: int,
|
| 505 |
+
fetcher: Fetcher,
|
| 506 |
+
size: int,
|
| 507 |
+
data: dict[tuple[int, int], bytes] = {},
|
| 508 |
+
strict: bool = True,
|
| 509 |
+
**_: Any,
|
| 510 |
+
):
|
| 511 |
+
super().__init__(blocksize, fetcher, size)
|
| 512 |
+
self.strict = strict
|
| 513 |
+
|
| 514 |
+
# simple consolidation of contiguous blocks
|
| 515 |
+
if data:
|
| 516 |
+
old_offsets = sorted(data.keys())
|
| 517 |
+
offsets = [old_offsets[0]]
|
| 518 |
+
blocks = [data.pop(old_offsets[0])]
|
| 519 |
+
for start, stop in old_offsets[1:]:
|
| 520 |
+
start0, stop0 = offsets[-1]
|
| 521 |
+
if start == stop0:
|
| 522 |
+
offsets[-1] = (start0, stop)
|
| 523 |
+
blocks[-1] += data.pop((start, stop))
|
| 524 |
+
else:
|
| 525 |
+
offsets.append((start, stop))
|
| 526 |
+
blocks.append(data.pop((start, stop)))
|
| 527 |
+
|
| 528 |
+
self.data = dict(zip(offsets, blocks))
|
| 529 |
+
else:
|
| 530 |
+
self.data = data
|
| 531 |
+
|
| 532 |
+
def _fetch(self, start: int | None, stop: int | None) -> bytes:
|
| 533 |
+
if start is None:
|
| 534 |
+
start = 0
|
| 535 |
+
if stop is None:
|
| 536 |
+
stop = self.size
|
| 537 |
+
|
| 538 |
+
out = b""
|
| 539 |
+
for (loc0, loc1), data in self.data.items():
|
| 540 |
+
# If self.strict=False, use zero-padded data
|
| 541 |
+
# for reads beyond the end of a "known" buffer
|
| 542 |
+
if loc0 <= start < loc1:
|
| 543 |
+
off = start - loc0
|
| 544 |
+
out = data[off : off + stop - start]
|
| 545 |
+
if not self.strict or loc0 <= stop <= loc1:
|
| 546 |
+
# The request is within a known range, or
|
| 547 |
+
# it begins within a known range, and we
|
| 548 |
+
# are allowed to pad reads beyond the
|
| 549 |
+
# buffer with zero
|
| 550 |
+
out += b"\x00" * (stop - start - len(out))
|
| 551 |
+
return out
|
| 552 |
+
else:
|
| 553 |
+
# The request ends outside a known range,
|
| 554 |
+
# and we are being "strict" about reads
|
| 555 |
+
# beyond the buffer
|
| 556 |
+
start = loc1
|
| 557 |
+
break
|
| 558 |
+
|
| 559 |
+
# We only get here if there is a request outside the
|
| 560 |
+
# known parts of the file. In an ideal world, this
|
| 561 |
+
# should never happen
|
| 562 |
+
if self.fetcher is None:
|
| 563 |
+
# We cannot fetch the data, so raise an error
|
| 564 |
+
raise ValueError(f"Read is outside the known file parts: {(start, stop)}. ")
|
| 565 |
+
# We can fetch the data, but should warn the user
|
| 566 |
+
# that this may be slow
|
| 567 |
+
warnings.warn(
|
| 568 |
+
f"Read is outside the known file parts: {(start, stop)}. "
|
| 569 |
+
f"IO/caching performance may be poor!"
|
| 570 |
+
)
|
| 571 |
+
logger.debug(f"KnownPartsOfAFile cache fetching {start}-{stop}")
|
| 572 |
+
return out + super()._fetch(start, stop)
|
| 573 |
+
|
| 574 |
+
|
| 575 |
+
class UpdatableLRU(Generic[P, T]):
|
| 576 |
+
"""
|
| 577 |
+
Custom implementation of LRU cache that allows updating keys
|
| 578 |
+
|
| 579 |
+
Used by BackgroudBlockCache
|
| 580 |
+
"""
|
| 581 |
+
|
| 582 |
+
class CacheInfo(NamedTuple):
|
| 583 |
+
hits: int
|
| 584 |
+
misses: int
|
| 585 |
+
maxsize: int
|
| 586 |
+
currsize: int
|
| 587 |
+
|
| 588 |
+
def __init__(self, func: Callable[P, T], max_size: int = 128) -> None:
|
| 589 |
+
self._cache: OrderedDict[Any, T] = collections.OrderedDict()
|
| 590 |
+
self._func = func
|
| 591 |
+
self._max_size = max_size
|
| 592 |
+
self._hits = 0
|
| 593 |
+
self._misses = 0
|
| 594 |
+
self._lock = threading.Lock()
|
| 595 |
+
|
| 596 |
+
def __call__(self, *args: P.args, **kwargs: P.kwargs) -> T:
|
| 597 |
+
if kwargs:
|
| 598 |
+
raise TypeError(f"Got unexpected keyword argument {kwargs.keys()}")
|
| 599 |
+
with self._lock:
|
| 600 |
+
if args in self._cache:
|
| 601 |
+
self._cache.move_to_end(args)
|
| 602 |
+
self._hits += 1
|
| 603 |
+
return self._cache[args]
|
| 604 |
+
|
| 605 |
+
result = self._func(*args, **kwargs)
|
| 606 |
+
|
| 607 |
+
with self._lock:
|
| 608 |
+
self._cache[args] = result
|
| 609 |
+
self._misses += 1
|
| 610 |
+
if len(self._cache) > self._max_size:
|
| 611 |
+
self._cache.popitem(last=False)
|
| 612 |
+
|
| 613 |
+
return result
|
| 614 |
+
|
| 615 |
+
def is_key_cached(self, *args: Any) -> bool:
|
| 616 |
+
with self._lock:
|
| 617 |
+
return args in self._cache
|
| 618 |
+
|
| 619 |
+
def add_key(self, result: T, *args: Any) -> None:
|
| 620 |
+
with self._lock:
|
| 621 |
+
self._cache[args] = result
|
| 622 |
+
if len(self._cache) > self._max_size:
|
| 623 |
+
self._cache.popitem(last=False)
|
| 624 |
+
|
| 625 |
+
def cache_info(self) -> UpdatableLRU.CacheInfo:
|
| 626 |
+
with self._lock:
|
| 627 |
+
return self.CacheInfo(
|
| 628 |
+
maxsize=self._max_size,
|
| 629 |
+
currsize=len(self._cache),
|
| 630 |
+
hits=self._hits,
|
| 631 |
+
misses=self._misses,
|
| 632 |
+
)
|
| 633 |
+
|
| 634 |
+
|
| 635 |
+
class BackgroundBlockCache(BaseCache):
|
| 636 |
+
"""
|
| 637 |
+
Cache holding memory as a set of blocks with pre-loading of
|
| 638 |
+
the next block in the background.
|
| 639 |
+
|
| 640 |
+
Requests are only ever made ``blocksize`` at a time, and are
|
| 641 |
+
stored in an LRU cache. The least recently accessed block is
|
| 642 |
+
discarded when more than ``maxblocks`` are stored. If the
|
| 643 |
+
next block is not in cache, it is loaded in a separate thread
|
| 644 |
+
in non-blocking way.
|
| 645 |
+
|
| 646 |
+
Parameters
|
| 647 |
+
----------
|
| 648 |
+
blocksize : int
|
| 649 |
+
The number of bytes to store in each block.
|
| 650 |
+
Requests are only ever made for ``blocksize``, so this
|
| 651 |
+
should balance the overhead of making a request against
|
| 652 |
+
the granularity of the blocks.
|
| 653 |
+
fetcher : Callable
|
| 654 |
+
size : int
|
| 655 |
+
The total size of the file being cached.
|
| 656 |
+
maxblocks : int
|
| 657 |
+
The maximum number of blocks to cache for. The maximum memory
|
| 658 |
+
use for this cache is then ``blocksize * maxblocks``.
|
| 659 |
+
"""
|
| 660 |
+
|
| 661 |
+
name: ClassVar[str] = "background"
|
| 662 |
+
|
| 663 |
+
def __init__(
|
| 664 |
+
self, blocksize: int, fetcher: Fetcher, size: int, maxblocks: int = 32
|
| 665 |
+
) -> None:
|
| 666 |
+
super().__init__(blocksize, fetcher, size)
|
| 667 |
+
self.nblocks = math.ceil(size / blocksize)
|
| 668 |
+
self.maxblocks = maxblocks
|
| 669 |
+
self._fetch_block_cached = UpdatableLRU(self._fetch_block, maxblocks)
|
| 670 |
+
|
| 671 |
+
self._thread_executor = ThreadPoolExecutor(max_workers=1)
|
| 672 |
+
self._fetch_future_block_number: int | None = None
|
| 673 |
+
self._fetch_future: Future[bytes] | None = None
|
| 674 |
+
self._fetch_future_lock = threading.Lock()
|
| 675 |
+
|
| 676 |
+
def __repr__(self) -> str:
|
| 677 |
+
return (
|
| 678 |
+
f"<BackgroundBlockCache blocksize={self.blocksize}, "
|
| 679 |
+
f"size={self.size}, nblocks={self.nblocks}>"
|
| 680 |
+
)
|
| 681 |
+
|
| 682 |
+
def cache_info(self) -> UpdatableLRU.CacheInfo:
|
| 683 |
+
"""
|
| 684 |
+
The statistics on the block cache.
|
| 685 |
+
|
| 686 |
+
Returns
|
| 687 |
+
-------
|
| 688 |
+
NamedTuple
|
| 689 |
+
Returned directly from the LRU Cache used internally.
|
| 690 |
+
"""
|
| 691 |
+
return self._fetch_block_cached.cache_info()
|
| 692 |
+
|
| 693 |
+
def __getstate__(self) -> dict[str, Any]:
|
| 694 |
+
state = self.__dict__
|
| 695 |
+
del state["_fetch_block_cached"]
|
| 696 |
+
del state["_thread_executor"]
|
| 697 |
+
del state["_fetch_future_block_number"]
|
| 698 |
+
del state["_fetch_future"]
|
| 699 |
+
del state["_fetch_future_lock"]
|
| 700 |
+
return state
|
| 701 |
+
|
| 702 |
+
def __setstate__(self, state) -> None:
|
| 703 |
+
self.__dict__.update(state)
|
| 704 |
+
self._fetch_block_cached = UpdatableLRU(self._fetch_block, state["maxblocks"])
|
| 705 |
+
self._thread_executor = ThreadPoolExecutor(max_workers=1)
|
| 706 |
+
self._fetch_future_block_number = None
|
| 707 |
+
self._fetch_future = None
|
| 708 |
+
self._fetch_future_lock = threading.Lock()
|
| 709 |
+
|
| 710 |
+
def _fetch(self, start: int | None, end: int | None) -> bytes:
|
| 711 |
+
if start is None:
|
| 712 |
+
start = 0
|
| 713 |
+
if end is None:
|
| 714 |
+
end = self.size
|
| 715 |
+
if start >= self.size or start >= end:
|
| 716 |
+
return b""
|
| 717 |
+
|
| 718 |
+
# byte position -> block numbers
|
| 719 |
+
start_block_number = start // self.blocksize
|
| 720 |
+
end_block_number = end // self.blocksize
|
| 721 |
+
|
| 722 |
+
fetch_future_block_number = None
|
| 723 |
+
fetch_future = None
|
| 724 |
+
with self._fetch_future_lock:
|
| 725 |
+
# Background thread is running. Check we we can or must join it.
|
| 726 |
+
if self._fetch_future is not None:
|
| 727 |
+
assert self._fetch_future_block_number is not None
|
| 728 |
+
if self._fetch_future.done():
|
| 729 |
+
logger.info("BlockCache joined background fetch without waiting.")
|
| 730 |
+
self._fetch_block_cached.add_key(
|
| 731 |
+
self._fetch_future.result(), self._fetch_future_block_number
|
| 732 |
+
)
|
| 733 |
+
# Cleanup the fetch variables. Done with fetching the block.
|
| 734 |
+
self._fetch_future_block_number = None
|
| 735 |
+
self._fetch_future = None
|
| 736 |
+
else:
|
| 737 |
+
# Must join if we need the block for the current fetch
|
| 738 |
+
must_join = bool(
|
| 739 |
+
start_block_number
|
| 740 |
+
<= self._fetch_future_block_number
|
| 741 |
+
<= end_block_number
|
| 742 |
+
)
|
| 743 |
+
if must_join:
|
| 744 |
+
# Copy to the local variables to release lock
|
| 745 |
+
# before waiting for result
|
| 746 |
+
fetch_future_block_number = self._fetch_future_block_number
|
| 747 |
+
fetch_future = self._fetch_future
|
| 748 |
+
|
| 749 |
+
# Cleanup the fetch variables. Have a local copy.
|
| 750 |
+
self._fetch_future_block_number = None
|
| 751 |
+
self._fetch_future = None
|
| 752 |
+
|
| 753 |
+
# Need to wait for the future for the current read
|
| 754 |
+
if fetch_future is not None:
|
| 755 |
+
logger.info("BlockCache waiting for background fetch.")
|
| 756 |
+
# Wait until result and put it in cache
|
| 757 |
+
self._fetch_block_cached.add_key(
|
| 758 |
+
fetch_future.result(), fetch_future_block_number
|
| 759 |
+
)
|
| 760 |
+
|
| 761 |
+
# these are cached, so safe to do multiple calls for the same start and end.
|
| 762 |
+
for block_number in range(start_block_number, end_block_number + 1):
|
| 763 |
+
self._fetch_block_cached(block_number)
|
| 764 |
+
|
| 765 |
+
# fetch next block in the background if nothing is running in the background,
|
| 766 |
+
# the block is within file and it is not already cached
|
| 767 |
+
end_block_plus_1 = end_block_number + 1
|
| 768 |
+
with self._fetch_future_lock:
|
| 769 |
+
if (
|
| 770 |
+
self._fetch_future is None
|
| 771 |
+
and end_block_plus_1 <= self.nblocks
|
| 772 |
+
and not self._fetch_block_cached.is_key_cached(end_block_plus_1)
|
| 773 |
+
):
|
| 774 |
+
self._fetch_future_block_number = end_block_plus_1
|
| 775 |
+
self._fetch_future = self._thread_executor.submit(
|
| 776 |
+
self._fetch_block, end_block_plus_1, "async"
|
| 777 |
+
)
|
| 778 |
+
|
| 779 |
+
return self._read_cache(
|
| 780 |
+
start,
|
| 781 |
+
end,
|
| 782 |
+
start_block_number=start_block_number,
|
| 783 |
+
end_block_number=end_block_number,
|
| 784 |
+
)
|
| 785 |
+
|
| 786 |
+
def _fetch_block(self, block_number: int, log_info: str = "sync") -> bytes:
|
| 787 |
+
"""
|
| 788 |
+
Fetch the block of data for `block_number`.
|
| 789 |
+
"""
|
| 790 |
+
if block_number > self.nblocks:
|
| 791 |
+
raise ValueError(
|
| 792 |
+
f"'block_number={block_number}' is greater than "
|
| 793 |
+
f"the number of blocks ({self.nblocks})"
|
| 794 |
+
)
|
| 795 |
+
|
| 796 |
+
start = block_number * self.blocksize
|
| 797 |
+
end = start + self.blocksize
|
| 798 |
+
logger.info("BlockCache fetching block (%s) %d", log_info, block_number)
|
| 799 |
+
block_contents = super()._fetch(start, end)
|
| 800 |
+
return block_contents
|
| 801 |
+
|
| 802 |
+
def _read_cache(
|
| 803 |
+
self, start: int, end: int, start_block_number: int, end_block_number: int
|
| 804 |
+
) -> bytes:
|
| 805 |
+
"""
|
| 806 |
+
Read from our block cache.
|
| 807 |
+
|
| 808 |
+
Parameters
|
| 809 |
+
----------
|
| 810 |
+
start, end : int
|
| 811 |
+
The start and end byte positions.
|
| 812 |
+
start_block_number, end_block_number : int
|
| 813 |
+
The start and end block numbers.
|
| 814 |
+
"""
|
| 815 |
+
start_pos = start % self.blocksize
|
| 816 |
+
end_pos = end % self.blocksize
|
| 817 |
+
|
| 818 |
+
if start_block_number == end_block_number:
|
| 819 |
+
block = self._fetch_block_cached(start_block_number)
|
| 820 |
+
return block[start_pos:end_pos]
|
| 821 |
+
|
| 822 |
+
else:
|
| 823 |
+
# read from the initial
|
| 824 |
+
out = []
|
| 825 |
+
out.append(self._fetch_block_cached(start_block_number)[start_pos:])
|
| 826 |
+
|
| 827 |
+
# intermediate blocks
|
| 828 |
+
# Note: it'd be nice to combine these into one big request. However
|
| 829 |
+
# that doesn't play nicely with our LRU cache.
|
| 830 |
+
for block_number in range(start_block_number + 1, end_block_number):
|
| 831 |
+
out.append(self._fetch_block_cached(block_number))
|
| 832 |
+
|
| 833 |
+
# final block
|
| 834 |
+
out.append(self._fetch_block_cached(end_block_number)[:end_pos])
|
| 835 |
+
|
| 836 |
+
return b"".join(out)
|
| 837 |
+
|
| 838 |
+
|
| 839 |
+
caches: dict[str | None, type[BaseCache]] = {
|
| 840 |
+
# one custom case
|
| 841 |
+
None: BaseCache,
|
| 842 |
+
}
|
| 843 |
+
|
| 844 |
+
|
| 845 |
+
def register_cache(cls: type[BaseCache], clobber: bool = False) -> None:
|
| 846 |
+
"""'Register' cache implementation.
|
| 847 |
+
|
| 848 |
+
Parameters
|
| 849 |
+
----------
|
| 850 |
+
clobber: bool, optional
|
| 851 |
+
If set to True (default is False) - allow to overwrite existing
|
| 852 |
+
entry.
|
| 853 |
+
|
| 854 |
+
Raises
|
| 855 |
+
------
|
| 856 |
+
ValueError
|
| 857 |
+
"""
|
| 858 |
+
name = cls.name
|
| 859 |
+
if not clobber and name in caches:
|
| 860 |
+
raise ValueError(f"Cache with name {name!r} is already known: {caches[name]}")
|
| 861 |
+
caches[name] = cls
|
| 862 |
+
|
| 863 |
+
|
| 864 |
+
for c in (
|
| 865 |
+
BaseCache,
|
| 866 |
+
MMapCache,
|
| 867 |
+
BytesCache,
|
| 868 |
+
ReadAheadCache,
|
| 869 |
+
BlockCache,
|
| 870 |
+
FirstChunkCache,
|
| 871 |
+
AllBytes,
|
| 872 |
+
KnownPartsOfAFile,
|
| 873 |
+
BackgroundBlockCache,
|
| 874 |
+
):
|
| 875 |
+
register_cache(c)
|
tuning-competition-baseline/.venv/lib/python3.11/site-packages/fsspec/callbacks.py
ADDED
|
@@ -0,0 +1,324 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from functools import wraps
|
| 2 |
+
|
| 3 |
+
|
| 4 |
+
class Callback:
|
| 5 |
+
"""
|
| 6 |
+
Base class and interface for callback mechanism
|
| 7 |
+
|
| 8 |
+
This class can be used directly for monitoring file transfers by
|
| 9 |
+
providing ``callback=Callback(hooks=...)`` (see the ``hooks`` argument,
|
| 10 |
+
below), or subclassed for more specialised behaviour.
|
| 11 |
+
|
| 12 |
+
Parameters
|
| 13 |
+
----------
|
| 14 |
+
size: int (optional)
|
| 15 |
+
Nominal quantity for the value that corresponds to a complete
|
| 16 |
+
transfer, e.g., total number of tiles or total number of
|
| 17 |
+
bytes
|
| 18 |
+
value: int (0)
|
| 19 |
+
Starting internal counter value
|
| 20 |
+
hooks: dict or None
|
| 21 |
+
A dict of named functions to be called on each update. The signature
|
| 22 |
+
of these must be ``f(size, value, **kwargs)``
|
| 23 |
+
"""
|
| 24 |
+
|
| 25 |
+
def __init__(self, size=None, value=0, hooks=None, **kwargs):
|
| 26 |
+
self.size = size
|
| 27 |
+
self.value = value
|
| 28 |
+
self.hooks = hooks or {}
|
| 29 |
+
self.kw = kwargs
|
| 30 |
+
|
| 31 |
+
def __enter__(self):
|
| 32 |
+
return self
|
| 33 |
+
|
| 34 |
+
def __exit__(self, *exc_args):
|
| 35 |
+
self.close()
|
| 36 |
+
|
| 37 |
+
def close(self):
|
| 38 |
+
"""Close callback."""
|
| 39 |
+
|
| 40 |
+
def branched(self, path_1, path_2, **kwargs):
|
| 41 |
+
"""
|
| 42 |
+
Return callback for child transfers
|
| 43 |
+
|
| 44 |
+
If this callback is operating at a higher level, e.g., put, which may
|
| 45 |
+
trigger transfers that can also be monitored. The function returns a callback
|
| 46 |
+
that has to be passed to the child method, e.g., put_file,
|
| 47 |
+
as `callback=` argument.
|
| 48 |
+
|
| 49 |
+
The implementation uses `callback.branch` for compatibility.
|
| 50 |
+
When implementing callbacks, it is recommended to override this function instead
|
| 51 |
+
of `branch` and avoid calling `super().branched(...)`.
|
| 52 |
+
|
| 53 |
+
Prefer using this function over `branch`.
|
| 54 |
+
|
| 55 |
+
Parameters
|
| 56 |
+
----------
|
| 57 |
+
path_1: str
|
| 58 |
+
Child's source path
|
| 59 |
+
path_2: str
|
| 60 |
+
Child's destination path
|
| 61 |
+
**kwargs:
|
| 62 |
+
Arbitrary keyword arguments
|
| 63 |
+
|
| 64 |
+
Returns
|
| 65 |
+
-------
|
| 66 |
+
callback: Callback
|
| 67 |
+
A callback instance to be passed to the child method
|
| 68 |
+
"""
|
| 69 |
+
self.branch(path_1, path_2, kwargs)
|
| 70 |
+
# mutate kwargs so that we can force the caller to pass "callback=" explicitly
|
| 71 |
+
return kwargs.pop("callback", DEFAULT_CALLBACK)
|
| 72 |
+
|
| 73 |
+
def branch_coro(self, fn):
|
| 74 |
+
"""
|
| 75 |
+
Wraps a coroutine, and pass a new child callback to it.
|
| 76 |
+
"""
|
| 77 |
+
|
| 78 |
+
@wraps(fn)
|
| 79 |
+
async def func(path1, path2: str, **kwargs):
|
| 80 |
+
with self.branched(path1, path2, **kwargs) as child:
|
| 81 |
+
return await fn(path1, path2, callback=child, **kwargs)
|
| 82 |
+
|
| 83 |
+
return func
|
| 84 |
+
|
| 85 |
+
def set_size(self, size):
|
| 86 |
+
"""
|
| 87 |
+
Set the internal maximum size attribute
|
| 88 |
+
|
| 89 |
+
Usually called if not initially set at instantiation. Note that this
|
| 90 |
+
triggers a ``call()``.
|
| 91 |
+
|
| 92 |
+
Parameters
|
| 93 |
+
----------
|
| 94 |
+
size: int
|
| 95 |
+
"""
|
| 96 |
+
self.size = size
|
| 97 |
+
self.call()
|
| 98 |
+
|
| 99 |
+
def absolute_update(self, value):
|
| 100 |
+
"""
|
| 101 |
+
Set the internal value state
|
| 102 |
+
|
| 103 |
+
Triggers ``call()``
|
| 104 |
+
|
| 105 |
+
Parameters
|
| 106 |
+
----------
|
| 107 |
+
value: int
|
| 108 |
+
"""
|
| 109 |
+
self.value = value
|
| 110 |
+
self.call()
|
| 111 |
+
|
| 112 |
+
def relative_update(self, inc=1):
|
| 113 |
+
"""
|
| 114 |
+
Delta increment the internal counter
|
| 115 |
+
|
| 116 |
+
Triggers ``call()``
|
| 117 |
+
|
| 118 |
+
Parameters
|
| 119 |
+
----------
|
| 120 |
+
inc: int
|
| 121 |
+
"""
|
| 122 |
+
self.value += inc
|
| 123 |
+
self.call()
|
| 124 |
+
|
| 125 |
+
def call(self, hook_name=None, **kwargs):
|
| 126 |
+
"""
|
| 127 |
+
Execute hook(s) with current state
|
| 128 |
+
|
| 129 |
+
Each function is passed the internal size and current value
|
| 130 |
+
|
| 131 |
+
Parameters
|
| 132 |
+
----------
|
| 133 |
+
hook_name: str or None
|
| 134 |
+
If given, execute on this hook
|
| 135 |
+
kwargs: passed on to (all) hook(s)
|
| 136 |
+
"""
|
| 137 |
+
if not self.hooks:
|
| 138 |
+
return
|
| 139 |
+
kw = self.kw.copy()
|
| 140 |
+
kw.update(kwargs)
|
| 141 |
+
if hook_name:
|
| 142 |
+
if hook_name not in self.hooks:
|
| 143 |
+
return
|
| 144 |
+
return self.hooks[hook_name](self.size, self.value, **kw)
|
| 145 |
+
for hook in self.hooks.values() or []:
|
| 146 |
+
hook(self.size, self.value, **kw)
|
| 147 |
+
|
| 148 |
+
def wrap(self, iterable):
|
| 149 |
+
"""
|
| 150 |
+
Wrap an iterable to call ``relative_update`` on each iterations
|
| 151 |
+
|
| 152 |
+
Parameters
|
| 153 |
+
----------
|
| 154 |
+
iterable: Iterable
|
| 155 |
+
The iterable that is being wrapped
|
| 156 |
+
"""
|
| 157 |
+
for item in iterable:
|
| 158 |
+
self.relative_update()
|
| 159 |
+
yield item
|
| 160 |
+
|
| 161 |
+
def branch(self, path_1, path_2, kwargs):
|
| 162 |
+
"""
|
| 163 |
+
Set callbacks for child transfers
|
| 164 |
+
|
| 165 |
+
If this callback is operating at a higher level, e.g., put, which may
|
| 166 |
+
trigger transfers that can also be monitored. The passed kwargs are
|
| 167 |
+
to be *mutated* to add ``callback=``, if this class supports branching
|
| 168 |
+
to children.
|
| 169 |
+
|
| 170 |
+
Parameters
|
| 171 |
+
----------
|
| 172 |
+
path_1: str
|
| 173 |
+
Child's source path
|
| 174 |
+
path_2: str
|
| 175 |
+
Child's destination path
|
| 176 |
+
kwargs: dict
|
| 177 |
+
arguments passed to child method, e.g., put_file.
|
| 178 |
+
|
| 179 |
+
Returns
|
| 180 |
+
-------
|
| 181 |
+
|
| 182 |
+
"""
|
| 183 |
+
return None
|
| 184 |
+
|
| 185 |
+
def no_op(self, *_, **__):
|
| 186 |
+
pass
|
| 187 |
+
|
| 188 |
+
def __getattr__(self, item):
|
| 189 |
+
"""
|
| 190 |
+
If undefined methods are called on this class, nothing happens
|
| 191 |
+
"""
|
| 192 |
+
return self.no_op
|
| 193 |
+
|
| 194 |
+
@classmethod
|
| 195 |
+
def as_callback(cls, maybe_callback=None):
|
| 196 |
+
"""Transform callback=... into Callback instance
|
| 197 |
+
|
| 198 |
+
For the special value of ``None``, return the global instance of
|
| 199 |
+
``NoOpCallback``. This is an alternative to including
|
| 200 |
+
``callback=DEFAULT_CALLBACK`` directly in a method signature.
|
| 201 |
+
"""
|
| 202 |
+
if maybe_callback is None:
|
| 203 |
+
return DEFAULT_CALLBACK
|
| 204 |
+
return maybe_callback
|
| 205 |
+
|
| 206 |
+
|
| 207 |
+
class NoOpCallback(Callback):
|
| 208 |
+
"""
|
| 209 |
+
This implementation of Callback does exactly nothing
|
| 210 |
+
"""
|
| 211 |
+
|
| 212 |
+
def call(self, *args, **kwargs):
|
| 213 |
+
return None
|
| 214 |
+
|
| 215 |
+
|
| 216 |
+
class DotPrinterCallback(Callback):
|
| 217 |
+
"""
|
| 218 |
+
Simple example Callback implementation
|
| 219 |
+
|
| 220 |
+
Almost identical to Callback with a hook that prints a char; here we
|
| 221 |
+
demonstrate how the outer layer may print "#" and the inner layer "."
|
| 222 |
+
"""
|
| 223 |
+
|
| 224 |
+
def __init__(self, chr_to_print="#", **kwargs):
|
| 225 |
+
self.chr = chr_to_print
|
| 226 |
+
super().__init__(**kwargs)
|
| 227 |
+
|
| 228 |
+
def branch(self, path_1, path_2, kwargs):
|
| 229 |
+
"""Mutate kwargs to add new instance with different print char"""
|
| 230 |
+
kwargs["callback"] = DotPrinterCallback(".")
|
| 231 |
+
|
| 232 |
+
def call(self, **kwargs):
|
| 233 |
+
"""Just outputs a character"""
|
| 234 |
+
print(self.chr, end="")
|
| 235 |
+
|
| 236 |
+
|
| 237 |
+
class TqdmCallback(Callback):
|
| 238 |
+
"""
|
| 239 |
+
A callback to display a progress bar using tqdm
|
| 240 |
+
|
| 241 |
+
Parameters
|
| 242 |
+
----------
|
| 243 |
+
tqdm_kwargs : dict, (optional)
|
| 244 |
+
Any argument accepted by the tqdm constructor.
|
| 245 |
+
See the `tqdm doc <https://tqdm.github.io/docs/tqdm/#__init__>`_.
|
| 246 |
+
Will be forwarded to `tqdm_cls`.
|
| 247 |
+
tqdm_cls: (optional)
|
| 248 |
+
subclass of `tqdm.tqdm`. If not passed, it will default to `tqdm.tqdm`.
|
| 249 |
+
|
| 250 |
+
Examples
|
| 251 |
+
--------
|
| 252 |
+
>>> import fsspec
|
| 253 |
+
>>> from fsspec.callbacks import TqdmCallback
|
| 254 |
+
>>> fs = fsspec.filesystem("memory")
|
| 255 |
+
>>> path2distant_data = "/your-path"
|
| 256 |
+
>>> fs.upload(
|
| 257 |
+
".",
|
| 258 |
+
path2distant_data,
|
| 259 |
+
recursive=True,
|
| 260 |
+
callback=TqdmCallback(),
|
| 261 |
+
)
|
| 262 |
+
|
| 263 |
+
You can forward args to tqdm using the ``tqdm_kwargs`` parameter.
|
| 264 |
+
|
| 265 |
+
>>> fs.upload(
|
| 266 |
+
".",
|
| 267 |
+
path2distant_data,
|
| 268 |
+
recursive=True,
|
| 269 |
+
callback=TqdmCallback(tqdm_kwargs={"desc": "Your tqdm description"}),
|
| 270 |
+
)
|
| 271 |
+
|
| 272 |
+
You can also customize the progress bar by passing a subclass of `tqdm`.
|
| 273 |
+
|
| 274 |
+
.. code-block:: python
|
| 275 |
+
|
| 276 |
+
class TqdmFormat(tqdm):
|
| 277 |
+
'''Provides a `total_time` format parameter'''
|
| 278 |
+
@property
|
| 279 |
+
def format_dict(self):
|
| 280 |
+
d = super().format_dict
|
| 281 |
+
total_time = d["elapsed"] * (d["total"] or 0) / max(d["n"], 1)
|
| 282 |
+
d.update(total_time=self.format_interval(total_time) + " in total")
|
| 283 |
+
return d
|
| 284 |
+
|
| 285 |
+
>>> with TqdmCallback(
|
| 286 |
+
tqdm_kwargs={
|
| 287 |
+
"desc": "desc",
|
| 288 |
+
"bar_format": "{total_time}: {percentage:.0f}%|{bar}{r_bar}",
|
| 289 |
+
},
|
| 290 |
+
tqdm_cls=TqdmFormat,
|
| 291 |
+
) as callback:
|
| 292 |
+
fs.upload(".", path2distant_data, recursive=True, callback=callback)
|
| 293 |
+
"""
|
| 294 |
+
|
| 295 |
+
def __init__(self, tqdm_kwargs=None, *args, **kwargs):
|
| 296 |
+
try:
|
| 297 |
+
from tqdm import tqdm
|
| 298 |
+
|
| 299 |
+
except ImportError as exce:
|
| 300 |
+
raise ImportError(
|
| 301 |
+
"Using TqdmCallback requires tqdm to be installed"
|
| 302 |
+
) from exce
|
| 303 |
+
|
| 304 |
+
self._tqdm_cls = kwargs.pop("tqdm_cls", tqdm)
|
| 305 |
+
self._tqdm_kwargs = tqdm_kwargs or {}
|
| 306 |
+
self.tqdm = None
|
| 307 |
+
super().__init__(*args, **kwargs)
|
| 308 |
+
|
| 309 |
+
def call(self, *args, **kwargs):
|
| 310 |
+
if self.tqdm is None:
|
| 311 |
+
self.tqdm = self._tqdm_cls(total=self.size, **self._tqdm_kwargs)
|
| 312 |
+
self.tqdm.total = self.size
|
| 313 |
+
self.tqdm.update(self.value - self.tqdm.n)
|
| 314 |
+
|
| 315 |
+
def close(self):
|
| 316 |
+
if self.tqdm is not None:
|
| 317 |
+
self.tqdm.close()
|
| 318 |
+
self.tqdm = None
|
| 319 |
+
|
| 320 |
+
def __del__(self):
|
| 321 |
+
return self.close()
|
| 322 |
+
|
| 323 |
+
|
| 324 |
+
DEFAULT_CALLBACK = _DEFAULT_CALLBACK = NoOpCallback()
|
tuning-competition-baseline/.venv/lib/python3.11/site-packages/fsspec/compression.py
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Helper functions for a standard streaming compression API"""
|
| 2 |
+
from zipfile import ZipFile
|
| 3 |
+
|
| 4 |
+
import fsspec.utils
|
| 5 |
+
from fsspec.spec import AbstractBufferedFile
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
def noop_file(file, mode, **kwargs):
|
| 9 |
+
return file
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
# TODO: files should also be available as contexts
|
| 13 |
+
# should be functions of the form func(infile, mode=, **kwargs) -> file-like
|
| 14 |
+
compr = {None: noop_file}
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
def register_compression(name, callback, extensions, force=False):
|
| 18 |
+
"""Register an "inferable" file compression type.
|
| 19 |
+
|
| 20 |
+
Registers transparent file compression type for use with fsspec.open.
|
| 21 |
+
Compression can be specified by name in open, or "infer"-ed for any files
|
| 22 |
+
ending with the given extensions.
|
| 23 |
+
|
| 24 |
+
Args:
|
| 25 |
+
name: (str) The compression type name. Eg. "gzip".
|
| 26 |
+
callback: A callable of form (infile, mode, **kwargs) -> file-like.
|
| 27 |
+
Accepts an input file-like object, the target mode and kwargs.
|
| 28 |
+
Returns a wrapped file-like object.
|
| 29 |
+
extensions: (str, Iterable[str]) A file extension, or list of file
|
| 30 |
+
extensions for which to infer this compression scheme. Eg. "gz".
|
| 31 |
+
force: (bool) Force re-registration of compression type or extensions.
|
| 32 |
+
|
| 33 |
+
Raises:
|
| 34 |
+
ValueError: If name or extensions already registered, and not force.
|
| 35 |
+
|
| 36 |
+
"""
|
| 37 |
+
if isinstance(extensions, str):
|
| 38 |
+
extensions = [extensions]
|
| 39 |
+
|
| 40 |
+
# Validate registration
|
| 41 |
+
if name in compr and not force:
|
| 42 |
+
raise ValueError(f"Duplicate compression registration: {name}")
|
| 43 |
+
|
| 44 |
+
for ext in extensions:
|
| 45 |
+
if ext in fsspec.utils.compressions and not force:
|
| 46 |
+
raise ValueError(f"Duplicate compression file extension: {ext} ({name})")
|
| 47 |
+
|
| 48 |
+
compr[name] = callback
|
| 49 |
+
|
| 50 |
+
for ext in extensions:
|
| 51 |
+
fsspec.utils.compressions[ext] = name
|
| 52 |
+
|
| 53 |
+
|
| 54 |
+
def unzip(infile, mode="rb", filename=None, **kwargs):
|
| 55 |
+
if "r" not in mode:
|
| 56 |
+
filename = filename or "file"
|
| 57 |
+
z = ZipFile(infile, mode="w", **kwargs)
|
| 58 |
+
fo = z.open(filename, mode="w")
|
| 59 |
+
fo.close = lambda closer=fo.close: closer() or z.close()
|
| 60 |
+
return fo
|
| 61 |
+
z = ZipFile(infile)
|
| 62 |
+
if filename is None:
|
| 63 |
+
filename = z.namelist()[0]
|
| 64 |
+
return z.open(filename, mode="r", **kwargs)
|
| 65 |
+
|
| 66 |
+
|
| 67 |
+
register_compression("zip", unzip, "zip")
|
| 68 |
+
|
| 69 |
+
try:
|
| 70 |
+
from bz2 import BZ2File
|
| 71 |
+
except ImportError:
|
| 72 |
+
pass
|
| 73 |
+
else:
|
| 74 |
+
register_compression("bz2", BZ2File, "bz2")
|
| 75 |
+
|
| 76 |
+
try: # pragma: no cover
|
| 77 |
+
from isal import igzip
|
| 78 |
+
|
| 79 |
+
def isal(infile, mode="rb", **kwargs):
|
| 80 |
+
return igzip.IGzipFile(fileobj=infile, mode=mode, **kwargs)
|
| 81 |
+
|
| 82 |
+
register_compression("gzip", isal, "gz")
|
| 83 |
+
except ImportError:
|
| 84 |
+
from gzip import GzipFile
|
| 85 |
+
|
| 86 |
+
register_compression(
|
| 87 |
+
"gzip", lambda f, **kwargs: GzipFile(fileobj=f, **kwargs), "gz"
|
| 88 |
+
)
|
| 89 |
+
|
| 90 |
+
try:
|
| 91 |
+
from lzma import LZMAFile
|
| 92 |
+
|
| 93 |
+
register_compression("lzma", LZMAFile, "lzma")
|
| 94 |
+
register_compression("xz", LZMAFile, "xz")
|
| 95 |
+
except ImportError:
|
| 96 |
+
pass
|
| 97 |
+
|
| 98 |
+
try:
|
| 99 |
+
import lzmaffi
|
| 100 |
+
|
| 101 |
+
register_compression("lzma", lzmaffi.LZMAFile, "lzma", force=True)
|
| 102 |
+
register_compression("xz", lzmaffi.LZMAFile, "xz", force=True)
|
| 103 |
+
except ImportError:
|
| 104 |
+
pass
|
| 105 |
+
|
| 106 |
+
|
| 107 |
+
class SnappyFile(AbstractBufferedFile):
|
| 108 |
+
def __init__(self, infile, mode, **kwargs):
|
| 109 |
+
import snappy
|
| 110 |
+
|
| 111 |
+
super().__init__(
|
| 112 |
+
fs=None, path="snappy", mode=mode.strip("b") + "b", size=999999999, **kwargs
|
| 113 |
+
)
|
| 114 |
+
self.infile = infile
|
| 115 |
+
if "r" in mode:
|
| 116 |
+
self.codec = snappy.StreamDecompressor()
|
| 117 |
+
else:
|
| 118 |
+
self.codec = snappy.StreamCompressor()
|
| 119 |
+
|
| 120 |
+
def _upload_chunk(self, final=False):
|
| 121 |
+
self.buffer.seek(0)
|
| 122 |
+
out = self.codec.add_chunk(self.buffer.read())
|
| 123 |
+
self.infile.write(out)
|
| 124 |
+
return True
|
| 125 |
+
|
| 126 |
+
def seek(self, loc, whence=0):
|
| 127 |
+
raise NotImplementedError("SnappyFile is not seekable")
|
| 128 |
+
|
| 129 |
+
def seekable(self):
|
| 130 |
+
return False
|
| 131 |
+
|
| 132 |
+
def _fetch_range(self, start, end):
|
| 133 |
+
"""Get the specified set of bytes from remote"""
|
| 134 |
+
data = self.infile.read(end - start)
|
| 135 |
+
return self.codec.decompress(data)
|
| 136 |
+
|
| 137 |
+
|
| 138 |
+
try:
|
| 139 |
+
import snappy
|
| 140 |
+
|
| 141 |
+
snappy.compress
|
| 142 |
+
# Snappy may use the .sz file extension, but this is not part of the
|
| 143 |
+
# standard implementation.
|
| 144 |
+
register_compression("snappy", SnappyFile, [])
|
| 145 |
+
|
| 146 |
+
except (ImportError, NameError, AttributeError):
|
| 147 |
+
pass
|
| 148 |
+
|
| 149 |
+
try:
|
| 150 |
+
import lz4.frame
|
| 151 |
+
|
| 152 |
+
register_compression("lz4", lz4.frame.open, "lz4")
|
| 153 |
+
except ImportError:
|
| 154 |
+
pass
|
| 155 |
+
|
| 156 |
+
try:
|
| 157 |
+
import zstandard as zstd
|
| 158 |
+
|
| 159 |
+
def zstandard_file(infile, mode="rb"):
|
| 160 |
+
if "r" in mode:
|
| 161 |
+
cctx = zstd.ZstdDecompressor()
|
| 162 |
+
return cctx.stream_reader(infile)
|
| 163 |
+
else:
|
| 164 |
+
cctx = zstd.ZstdCompressor(level=10)
|
| 165 |
+
return cctx.stream_writer(infile)
|
| 166 |
+
|
| 167 |
+
register_compression("zstd", zstandard_file, "zst")
|
| 168 |
+
except ImportError:
|
| 169 |
+
pass
|
| 170 |
+
|
| 171 |
+
|
| 172 |
+
def available_compressions():
|
| 173 |
+
"""Return a list of the implemented compressions."""
|
| 174 |
+
return list(compr)
|
tuning-competition-baseline/.venv/lib/python3.11/site-packages/fsspec/config.py
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
import configparser
|
| 4 |
+
import json
|
| 5 |
+
import os
|
| 6 |
+
import warnings
|
| 7 |
+
from typing import Any
|
| 8 |
+
|
| 9 |
+
conf: dict[str, dict[str, Any]] = {}
|
| 10 |
+
default_conf_dir = os.path.join(os.path.expanduser("~"), ".config/fsspec")
|
| 11 |
+
conf_dir = os.environ.get("FSSPEC_CONFIG_DIR", default_conf_dir)
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
def set_conf_env(conf_dict, envdict=os.environ):
|
| 15 |
+
"""Set config values from environment variables
|
| 16 |
+
|
| 17 |
+
Looks for variables of the form ``FSSPEC_<protocol>`` and
|
| 18 |
+
``FSSPEC_<protocol>_<kwarg>``. For ``FSSPEC_<protocol>`` the value is parsed
|
| 19 |
+
as a json dictionary and used to ``update`` the config of the
|
| 20 |
+
corresponding protocol. For ``FSSPEC_<protocol>_<kwarg>`` there is no
|
| 21 |
+
attempt to convert the string value, but the kwarg keys will be lower-cased.
|
| 22 |
+
|
| 23 |
+
The ``FSSPEC_<protocol>_<kwarg>`` variables are applied after the
|
| 24 |
+
``FSSPEC_<protocol>`` ones.
|
| 25 |
+
|
| 26 |
+
Parameters
|
| 27 |
+
----------
|
| 28 |
+
conf_dict : dict(str, dict)
|
| 29 |
+
This dict will be mutated
|
| 30 |
+
envdict : dict-like(str, str)
|
| 31 |
+
Source for the values - usually the real environment
|
| 32 |
+
"""
|
| 33 |
+
kwarg_keys = []
|
| 34 |
+
for key in envdict:
|
| 35 |
+
if key.startswith("FSSPEC_") and len(key) > 7 and key[7] != "_":
|
| 36 |
+
if key.count("_") > 1:
|
| 37 |
+
kwarg_keys.append(key)
|
| 38 |
+
continue
|
| 39 |
+
try:
|
| 40 |
+
value = json.loads(envdict[key])
|
| 41 |
+
except json.decoder.JSONDecodeError as ex:
|
| 42 |
+
warnings.warn(
|
| 43 |
+
f"Ignoring environment variable {key} due to a parse failure: {ex}"
|
| 44 |
+
)
|
| 45 |
+
else:
|
| 46 |
+
if isinstance(value, dict):
|
| 47 |
+
_, proto = key.split("_", 1)
|
| 48 |
+
conf_dict.setdefault(proto.lower(), {}).update(value)
|
| 49 |
+
else:
|
| 50 |
+
warnings.warn(
|
| 51 |
+
f"Ignoring environment variable {key} due to not being a dict:"
|
| 52 |
+
f" {type(value)}"
|
| 53 |
+
)
|
| 54 |
+
elif key.startswith("FSSPEC"):
|
| 55 |
+
warnings.warn(
|
| 56 |
+
f"Ignoring environment variable {key} due to having an unexpected name"
|
| 57 |
+
)
|
| 58 |
+
|
| 59 |
+
for key in kwarg_keys:
|
| 60 |
+
_, proto, kwarg = key.split("_", 2)
|
| 61 |
+
conf_dict.setdefault(proto.lower(), {})[kwarg.lower()] = envdict[key]
|
| 62 |
+
|
| 63 |
+
|
| 64 |
+
def set_conf_files(cdir, conf_dict):
|
| 65 |
+
"""Set config values from files
|
| 66 |
+
|
| 67 |
+
Scans for INI and JSON files in the given dictionary, and uses their
|
| 68 |
+
contents to set the config. In case of repeated values, later values
|
| 69 |
+
win.
|
| 70 |
+
|
| 71 |
+
In the case of INI files, all values are strings, and these will not
|
| 72 |
+
be converted.
|
| 73 |
+
|
| 74 |
+
Parameters
|
| 75 |
+
----------
|
| 76 |
+
cdir : str
|
| 77 |
+
Directory to search
|
| 78 |
+
conf_dict : dict(str, dict)
|
| 79 |
+
This dict will be mutated
|
| 80 |
+
"""
|
| 81 |
+
if not os.path.isdir(cdir):
|
| 82 |
+
return
|
| 83 |
+
allfiles = sorted(os.listdir(cdir))
|
| 84 |
+
for fn in allfiles:
|
| 85 |
+
if fn.endswith(".ini"):
|
| 86 |
+
ini = configparser.ConfigParser()
|
| 87 |
+
ini.read(os.path.join(cdir, fn))
|
| 88 |
+
for key in ini:
|
| 89 |
+
if key == "DEFAULT":
|
| 90 |
+
continue
|
| 91 |
+
conf_dict.setdefault(key, {}).update(dict(ini[key]))
|
| 92 |
+
if fn.endswith(".json"):
|
| 93 |
+
with open(os.path.join(cdir, fn)) as f:
|
| 94 |
+
js = json.load(f)
|
| 95 |
+
for key in js:
|
| 96 |
+
conf_dict.setdefault(key, {}).update(dict(js[key]))
|
| 97 |
+
|
| 98 |
+
|
| 99 |
+
def apply_config(cls, kwargs, conf_dict=None):
|
| 100 |
+
"""Supply default values for kwargs when instantiating class
|
| 101 |
+
|
| 102 |
+
Augments the passed kwargs, by finding entries in the config dict
|
| 103 |
+
which match the classes ``.protocol`` attribute (one or more str)
|
| 104 |
+
|
| 105 |
+
Parameters
|
| 106 |
+
----------
|
| 107 |
+
cls : file system implementation
|
| 108 |
+
kwargs : dict
|
| 109 |
+
conf_dict : dict of dict
|
| 110 |
+
Typically this is the global configuration
|
| 111 |
+
|
| 112 |
+
Returns
|
| 113 |
+
-------
|
| 114 |
+
dict : the modified set of kwargs
|
| 115 |
+
"""
|
| 116 |
+
if conf_dict is None:
|
| 117 |
+
conf_dict = conf
|
| 118 |
+
protos = cls.protocol if isinstance(cls.protocol, (tuple, list)) else [cls.protocol]
|
| 119 |
+
kw = {}
|
| 120 |
+
for proto in protos:
|
| 121 |
+
# default kwargs from the current state of the config
|
| 122 |
+
if proto in conf_dict:
|
| 123 |
+
kw.update(conf_dict[proto])
|
| 124 |
+
# explicit kwargs always win
|
| 125 |
+
kw.update(**kwargs)
|
| 126 |
+
kwargs = kw
|
| 127 |
+
return kwargs
|
| 128 |
+
|
| 129 |
+
|
| 130 |
+
set_conf_files(conf_dir, conf)
|
| 131 |
+
set_conf_env(conf)
|
tuning-competition-baseline/.venv/lib/python3.11/site-packages/fsspec/core.py
ADDED
|
@@ -0,0 +1,710 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
import io
|
| 4 |
+
import logging
|
| 5 |
+
import os
|
| 6 |
+
import re
|
| 7 |
+
from glob import has_magic
|
| 8 |
+
from pathlib import Path
|
| 9 |
+
|
| 10 |
+
# for backwards compat, we export cache things from here too
|
| 11 |
+
from .caching import ( # noqa: F401
|
| 12 |
+
BaseCache,
|
| 13 |
+
BlockCache,
|
| 14 |
+
BytesCache,
|
| 15 |
+
MMapCache,
|
| 16 |
+
ReadAheadCache,
|
| 17 |
+
caches,
|
| 18 |
+
)
|
| 19 |
+
from .compression import compr
|
| 20 |
+
from .registry import filesystem, get_filesystem_class
|
| 21 |
+
from .utils import (
|
| 22 |
+
_unstrip_protocol,
|
| 23 |
+
build_name_function,
|
| 24 |
+
infer_compression,
|
| 25 |
+
stringify_path,
|
| 26 |
+
)
|
| 27 |
+
|
| 28 |
+
logger = logging.getLogger("fsspec")
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
class OpenFile:
|
| 32 |
+
"""
|
| 33 |
+
File-like object to be used in a context
|
| 34 |
+
|
| 35 |
+
Can layer (buffered) text-mode and compression over any file-system, which
|
| 36 |
+
are typically binary-only.
|
| 37 |
+
|
| 38 |
+
These instances are safe to serialize, as the low-level file object
|
| 39 |
+
is not created until invoked using ``with``.
|
| 40 |
+
|
| 41 |
+
Parameters
|
| 42 |
+
----------
|
| 43 |
+
fs: FileSystem
|
| 44 |
+
The file system to use for opening the file. Should be a subclass or duck-type
|
| 45 |
+
with ``fsspec.spec.AbstractFileSystem``
|
| 46 |
+
path: str
|
| 47 |
+
Location to open
|
| 48 |
+
mode: str like 'rb', optional
|
| 49 |
+
Mode of the opened file
|
| 50 |
+
compression: str or None, optional
|
| 51 |
+
Compression to apply
|
| 52 |
+
encoding: str or None, optional
|
| 53 |
+
The encoding to use if opened in text mode.
|
| 54 |
+
errors: str or None, optional
|
| 55 |
+
How to handle encoding errors if opened in text mode.
|
| 56 |
+
newline: None or str
|
| 57 |
+
Passed to TextIOWrapper in text mode, how to handle line endings.
|
| 58 |
+
autoopen: bool
|
| 59 |
+
If True, calls open() immediately. Mostly used by pickle
|
| 60 |
+
pos: int
|
| 61 |
+
If given and autoopen is True, seek to this location immediately
|
| 62 |
+
"""
|
| 63 |
+
|
| 64 |
+
def __init__(
|
| 65 |
+
self,
|
| 66 |
+
fs,
|
| 67 |
+
path,
|
| 68 |
+
mode="rb",
|
| 69 |
+
compression=None,
|
| 70 |
+
encoding=None,
|
| 71 |
+
errors=None,
|
| 72 |
+
newline=None,
|
| 73 |
+
):
|
| 74 |
+
self.fs = fs
|
| 75 |
+
self.path = path
|
| 76 |
+
self.mode = mode
|
| 77 |
+
self.compression = get_compression(path, compression)
|
| 78 |
+
self.encoding = encoding
|
| 79 |
+
self.errors = errors
|
| 80 |
+
self.newline = newline
|
| 81 |
+
self.fobjects = []
|
| 82 |
+
|
| 83 |
+
def __reduce__(self):
|
| 84 |
+
return (
|
| 85 |
+
OpenFile,
|
| 86 |
+
(
|
| 87 |
+
self.fs,
|
| 88 |
+
self.path,
|
| 89 |
+
self.mode,
|
| 90 |
+
self.compression,
|
| 91 |
+
self.encoding,
|
| 92 |
+
self.errors,
|
| 93 |
+
self.newline,
|
| 94 |
+
),
|
| 95 |
+
)
|
| 96 |
+
|
| 97 |
+
def __repr__(self):
|
| 98 |
+
return f"<OpenFile '{self.path}'>"
|
| 99 |
+
|
| 100 |
+
def __enter__(self):
|
| 101 |
+
mode = self.mode.replace("t", "").replace("b", "") + "b"
|
| 102 |
+
|
| 103 |
+
f = self.fs.open(self.path, mode=mode)
|
| 104 |
+
|
| 105 |
+
self.fobjects = [f]
|
| 106 |
+
|
| 107 |
+
if self.compression is not None:
|
| 108 |
+
compress = compr[self.compression]
|
| 109 |
+
f = compress(f, mode=mode[0])
|
| 110 |
+
self.fobjects.append(f)
|
| 111 |
+
|
| 112 |
+
if "b" not in self.mode:
|
| 113 |
+
# assume, for example, that 'r' is equivalent to 'rt' as in builtin
|
| 114 |
+
f = PickleableTextIOWrapper(
|
| 115 |
+
f, encoding=self.encoding, errors=self.errors, newline=self.newline
|
| 116 |
+
)
|
| 117 |
+
self.fobjects.append(f)
|
| 118 |
+
|
| 119 |
+
return self.fobjects[-1]
|
| 120 |
+
|
| 121 |
+
def __exit__(self, *args):
|
| 122 |
+
self.close()
|
| 123 |
+
|
| 124 |
+
@property
|
| 125 |
+
def full_name(self):
|
| 126 |
+
return _unstrip_protocol(self.path, self.fs)
|
| 127 |
+
|
| 128 |
+
def open(self):
|
| 129 |
+
"""Materialise this as a real open file without context
|
| 130 |
+
|
| 131 |
+
The OpenFile object should be explicitly closed to avoid enclosed file
|
| 132 |
+
instances persisting. You must, therefore, keep a reference to the OpenFile
|
| 133 |
+
during the life of the file-like it generates.
|
| 134 |
+
"""
|
| 135 |
+
return self.__enter__()
|
| 136 |
+
|
| 137 |
+
def close(self):
|
| 138 |
+
"""Close all encapsulated file objects"""
|
| 139 |
+
for f in reversed(self.fobjects):
|
| 140 |
+
if "r" not in self.mode and not f.closed:
|
| 141 |
+
f.flush()
|
| 142 |
+
f.close()
|
| 143 |
+
self.fobjects.clear()
|
| 144 |
+
|
| 145 |
+
|
| 146 |
+
class OpenFiles(list):
|
| 147 |
+
"""List of OpenFile instances
|
| 148 |
+
|
| 149 |
+
Can be used in a single context, which opens and closes all of the
|
| 150 |
+
contained files. Normal list access to get the elements works as
|
| 151 |
+
normal.
|
| 152 |
+
|
| 153 |
+
A special case is made for caching filesystems - the files will
|
| 154 |
+
be down/uploaded together at the start or end of the context, and
|
| 155 |
+
this may happen concurrently, if the target filesystem supports it.
|
| 156 |
+
"""
|
| 157 |
+
|
| 158 |
+
def __init__(self, *args, mode="rb", fs=None):
|
| 159 |
+
self.mode = mode
|
| 160 |
+
self.fs = fs
|
| 161 |
+
self.files = []
|
| 162 |
+
super().__init__(*args)
|
| 163 |
+
|
| 164 |
+
def __enter__(self):
|
| 165 |
+
if self.fs is None:
|
| 166 |
+
raise ValueError("Context has already been used")
|
| 167 |
+
|
| 168 |
+
fs = self.fs
|
| 169 |
+
while True:
|
| 170 |
+
if hasattr(fs, "open_many"):
|
| 171 |
+
# check for concurrent cache download; or set up for upload
|
| 172 |
+
self.files = fs.open_many(self)
|
| 173 |
+
return self.files
|
| 174 |
+
if hasattr(fs, "fs") and fs.fs is not None:
|
| 175 |
+
fs = fs.fs
|
| 176 |
+
else:
|
| 177 |
+
break
|
| 178 |
+
return [s.__enter__() for s in self]
|
| 179 |
+
|
| 180 |
+
def __exit__(self, *args):
|
| 181 |
+
fs = self.fs
|
| 182 |
+
[s.__exit__(*args) for s in self]
|
| 183 |
+
if "r" not in self.mode:
|
| 184 |
+
while True:
|
| 185 |
+
if hasattr(fs, "open_many"):
|
| 186 |
+
# check for concurrent cache upload
|
| 187 |
+
fs.commit_many(self.files)
|
| 188 |
+
return
|
| 189 |
+
if hasattr(fs, "fs") and fs.fs is not None:
|
| 190 |
+
fs = fs.fs
|
| 191 |
+
else:
|
| 192 |
+
break
|
| 193 |
+
|
| 194 |
+
def __getitem__(self, item):
|
| 195 |
+
out = super().__getitem__(item)
|
| 196 |
+
if isinstance(item, slice):
|
| 197 |
+
return OpenFiles(out, mode=self.mode, fs=self.fs)
|
| 198 |
+
return out
|
| 199 |
+
|
| 200 |
+
def __repr__(self):
|
| 201 |
+
return f"<List of {len(self)} OpenFile instances>"
|
| 202 |
+
|
| 203 |
+
|
| 204 |
+
def open_files(
|
| 205 |
+
urlpath,
|
| 206 |
+
mode="rb",
|
| 207 |
+
compression=None,
|
| 208 |
+
encoding="utf8",
|
| 209 |
+
errors=None,
|
| 210 |
+
name_function=None,
|
| 211 |
+
num=1,
|
| 212 |
+
protocol=None,
|
| 213 |
+
newline=None,
|
| 214 |
+
auto_mkdir=True,
|
| 215 |
+
expand=True,
|
| 216 |
+
**kwargs,
|
| 217 |
+
):
|
| 218 |
+
"""Given a path or paths, return a list of ``OpenFile`` objects.
|
| 219 |
+
|
| 220 |
+
For writing, a str path must contain the "*" character, which will be filled
|
| 221 |
+
in by increasing numbers, e.g., "part*" -> "part1", "part2" if num=2.
|
| 222 |
+
|
| 223 |
+
For either reading or writing, can instead provide explicit list of paths.
|
| 224 |
+
|
| 225 |
+
Parameters
|
| 226 |
+
----------
|
| 227 |
+
urlpath: string or list
|
| 228 |
+
Absolute or relative filepath(s). Prefix with a protocol like ``s3://``
|
| 229 |
+
to read from alternative filesystems. To read from multiple files you
|
| 230 |
+
can pass a globstring or a list of paths, with the caveat that they
|
| 231 |
+
must all have the same protocol.
|
| 232 |
+
mode: 'rb', 'wt', etc.
|
| 233 |
+
compression: string or None
|
| 234 |
+
If given, open file using compression codec. Can either be a compression
|
| 235 |
+
name (a key in ``fsspec.compression.compr``) or "infer" to guess the
|
| 236 |
+
compression from the filename suffix.
|
| 237 |
+
encoding: str
|
| 238 |
+
For text mode only
|
| 239 |
+
errors: None or str
|
| 240 |
+
Passed to TextIOWrapper in text mode
|
| 241 |
+
name_function: function or None
|
| 242 |
+
if opening a set of files for writing, those files do not yet exist,
|
| 243 |
+
so we need to generate their names by formatting the urlpath for
|
| 244 |
+
each sequence number
|
| 245 |
+
num: int [1]
|
| 246 |
+
if writing mode, number of files we expect to create (passed to
|
| 247 |
+
name+function)
|
| 248 |
+
protocol: str or None
|
| 249 |
+
If given, overrides the protocol found in the URL.
|
| 250 |
+
newline: bytes or None
|
| 251 |
+
Used for line terminator in text mode. If None, uses system default;
|
| 252 |
+
if blank, uses no translation.
|
| 253 |
+
auto_mkdir: bool (True)
|
| 254 |
+
If in write mode, this will ensure the target directory exists before
|
| 255 |
+
writing, by calling ``fs.mkdirs(exist_ok=True)``.
|
| 256 |
+
expand: bool
|
| 257 |
+
**kwargs: dict
|
| 258 |
+
Extra options that make sense to a particular storage connection, e.g.
|
| 259 |
+
host, port, username, password, etc.
|
| 260 |
+
|
| 261 |
+
Examples
|
| 262 |
+
--------
|
| 263 |
+
>>> files = open_files('2015-*-*.csv') # doctest: +SKIP
|
| 264 |
+
>>> files = open_files(
|
| 265 |
+
... 's3://bucket/2015-*-*.csv.gz', compression='gzip'
|
| 266 |
+
... ) # doctest: +SKIP
|
| 267 |
+
|
| 268 |
+
Returns
|
| 269 |
+
-------
|
| 270 |
+
An ``OpenFiles`` instance, which is a list of ``OpenFile`` objects that can
|
| 271 |
+
be used as a single context
|
| 272 |
+
|
| 273 |
+
Notes
|
| 274 |
+
-----
|
| 275 |
+
For a full list of the available protocols and the implementations that
|
| 276 |
+
they map across to see the latest online documentation:
|
| 277 |
+
|
| 278 |
+
- For implementations built into ``fsspec`` see
|
| 279 |
+
https://filesystem-spec.readthedocs.io/en/latest/api.html#built-in-implementations
|
| 280 |
+
- For implementations in separate packages see
|
| 281 |
+
https://filesystem-spec.readthedocs.io/en/latest/api.html#other-known-implementations
|
| 282 |
+
"""
|
| 283 |
+
fs, fs_token, paths = get_fs_token_paths(
|
| 284 |
+
urlpath,
|
| 285 |
+
mode,
|
| 286 |
+
num=num,
|
| 287 |
+
name_function=name_function,
|
| 288 |
+
storage_options=kwargs,
|
| 289 |
+
protocol=protocol,
|
| 290 |
+
expand=expand,
|
| 291 |
+
)
|
| 292 |
+
if fs.protocol == "file":
|
| 293 |
+
fs.auto_mkdir = auto_mkdir
|
| 294 |
+
elif "r" not in mode and auto_mkdir:
|
| 295 |
+
parents = {fs._parent(path) for path in paths}
|
| 296 |
+
for parent in parents:
|
| 297 |
+
try:
|
| 298 |
+
fs.makedirs(parent, exist_ok=True)
|
| 299 |
+
except PermissionError:
|
| 300 |
+
pass
|
| 301 |
+
return OpenFiles(
|
| 302 |
+
[
|
| 303 |
+
OpenFile(
|
| 304 |
+
fs,
|
| 305 |
+
path,
|
| 306 |
+
mode=mode,
|
| 307 |
+
compression=compression,
|
| 308 |
+
encoding=encoding,
|
| 309 |
+
errors=errors,
|
| 310 |
+
newline=newline,
|
| 311 |
+
)
|
| 312 |
+
for path in paths
|
| 313 |
+
],
|
| 314 |
+
mode=mode,
|
| 315 |
+
fs=fs,
|
| 316 |
+
)
|
| 317 |
+
|
| 318 |
+
|
| 319 |
+
def _un_chain(path, kwargs):
|
| 320 |
+
x = re.compile(".*[^a-z]+.*") # test for non protocol-like single word
|
| 321 |
+
bits = (
|
| 322 |
+
[p if "://" in p or x.match(p) else p + "://" for p in path.split("::")]
|
| 323 |
+
if "::" in path
|
| 324 |
+
else [path]
|
| 325 |
+
)
|
| 326 |
+
# [[url, protocol, kwargs], ...]
|
| 327 |
+
out = []
|
| 328 |
+
previous_bit = None
|
| 329 |
+
kwargs = kwargs.copy()
|
| 330 |
+
for bit in reversed(bits):
|
| 331 |
+
protocol = kwargs.pop("protocol", None) or split_protocol(bit)[0] or "file"
|
| 332 |
+
cls = get_filesystem_class(protocol)
|
| 333 |
+
extra_kwargs = cls._get_kwargs_from_urls(bit)
|
| 334 |
+
kws = kwargs.pop(protocol, {})
|
| 335 |
+
if bit is bits[0]:
|
| 336 |
+
kws.update(kwargs)
|
| 337 |
+
kw = dict(**extra_kwargs, **kws)
|
| 338 |
+
bit = cls._strip_protocol(bit)
|
| 339 |
+
if (
|
| 340 |
+
protocol in {"blockcache", "filecache", "simplecache"}
|
| 341 |
+
and "target_protocol" not in kw
|
| 342 |
+
):
|
| 343 |
+
bit = previous_bit
|
| 344 |
+
out.append((bit, protocol, kw))
|
| 345 |
+
previous_bit = bit
|
| 346 |
+
out = list(reversed(out))
|
| 347 |
+
return out
|
| 348 |
+
|
| 349 |
+
|
| 350 |
+
def url_to_fs(url, **kwargs):
|
| 351 |
+
"""
|
| 352 |
+
Turn fully-qualified and potentially chained URL into filesystem instance
|
| 353 |
+
|
| 354 |
+
Parameters
|
| 355 |
+
----------
|
| 356 |
+
url : str
|
| 357 |
+
The fsspec-compatible URL
|
| 358 |
+
**kwargs: dict
|
| 359 |
+
Extra options that make sense to a particular storage connection, e.g.
|
| 360 |
+
host, port, username, password, etc.
|
| 361 |
+
|
| 362 |
+
Returns
|
| 363 |
+
-------
|
| 364 |
+
filesystem : FileSystem
|
| 365 |
+
The new filesystem discovered from ``url`` and created with
|
| 366 |
+
``**kwargs``.
|
| 367 |
+
urlpath : str
|
| 368 |
+
The file-systems-specific URL for ``url``.
|
| 369 |
+
"""
|
| 370 |
+
# non-FS arguments that appear in fsspec.open()
|
| 371 |
+
# inspect could keep this in sync with open()'s signature
|
| 372 |
+
known_kwargs = {
|
| 373 |
+
"compression",
|
| 374 |
+
"encoding",
|
| 375 |
+
"errors",
|
| 376 |
+
"expand",
|
| 377 |
+
"mode",
|
| 378 |
+
"name_function",
|
| 379 |
+
"newline",
|
| 380 |
+
"num",
|
| 381 |
+
}
|
| 382 |
+
kwargs = {k: v for k, v in kwargs.items() if k not in known_kwargs}
|
| 383 |
+
chain = _un_chain(url, kwargs)
|
| 384 |
+
inkwargs = {}
|
| 385 |
+
# Reverse iterate the chain, creating a nested target_* structure
|
| 386 |
+
for i, ch in enumerate(reversed(chain)):
|
| 387 |
+
urls, protocol, kw = ch
|
| 388 |
+
if i == len(chain) - 1:
|
| 389 |
+
inkwargs = dict(**kw, **inkwargs)
|
| 390 |
+
continue
|
| 391 |
+
inkwargs["target_options"] = dict(**kw, **inkwargs)
|
| 392 |
+
inkwargs["target_protocol"] = protocol
|
| 393 |
+
inkwargs["fo"] = urls
|
| 394 |
+
urlpath, protocol, _ = chain[0]
|
| 395 |
+
fs = filesystem(protocol, **inkwargs)
|
| 396 |
+
return fs, urlpath
|
| 397 |
+
|
| 398 |
+
|
| 399 |
+
def open(
|
| 400 |
+
urlpath,
|
| 401 |
+
mode="rb",
|
| 402 |
+
compression=None,
|
| 403 |
+
encoding="utf8",
|
| 404 |
+
errors=None,
|
| 405 |
+
protocol=None,
|
| 406 |
+
newline=None,
|
| 407 |
+
**kwargs,
|
| 408 |
+
):
|
| 409 |
+
"""Given a path or paths, return one ``OpenFile`` object.
|
| 410 |
+
|
| 411 |
+
Parameters
|
| 412 |
+
----------
|
| 413 |
+
urlpath: string or list
|
| 414 |
+
Absolute or relative filepath. Prefix with a protocol like ``s3://``
|
| 415 |
+
to read from alternative filesystems. Should not include glob
|
| 416 |
+
character(s).
|
| 417 |
+
mode: 'rb', 'wt', etc.
|
| 418 |
+
compression: string or None
|
| 419 |
+
If given, open file using compression codec. Can either be a compression
|
| 420 |
+
name (a key in ``fsspec.compression.compr``) or "infer" to guess the
|
| 421 |
+
compression from the filename suffix.
|
| 422 |
+
encoding: str
|
| 423 |
+
For text mode only
|
| 424 |
+
errors: None or str
|
| 425 |
+
Passed to TextIOWrapper in text mode
|
| 426 |
+
protocol: str or None
|
| 427 |
+
If given, overrides the protocol found in the URL.
|
| 428 |
+
newline: bytes or None
|
| 429 |
+
Used for line terminator in text mode. If None, uses system default;
|
| 430 |
+
if blank, uses no translation.
|
| 431 |
+
**kwargs: dict
|
| 432 |
+
Extra options that make sense to a particular storage connection, e.g.
|
| 433 |
+
host, port, username, password, etc.
|
| 434 |
+
|
| 435 |
+
Examples
|
| 436 |
+
--------
|
| 437 |
+
>>> openfile = open('2015-01-01.csv') # doctest: +SKIP
|
| 438 |
+
>>> openfile = open(
|
| 439 |
+
... 's3://bucket/2015-01-01.csv.gz', compression='gzip'
|
| 440 |
+
... ) # doctest: +SKIP
|
| 441 |
+
>>> with openfile as f:
|
| 442 |
+
... df = pd.read_csv(f) # doctest: +SKIP
|
| 443 |
+
...
|
| 444 |
+
|
| 445 |
+
Returns
|
| 446 |
+
-------
|
| 447 |
+
``OpenFile`` object.
|
| 448 |
+
|
| 449 |
+
Notes
|
| 450 |
+
-----
|
| 451 |
+
For a full list of the available protocols and the implementations that
|
| 452 |
+
they map across to see the latest online documentation:
|
| 453 |
+
|
| 454 |
+
- For implementations built into ``fsspec`` see
|
| 455 |
+
https://filesystem-spec.readthedocs.io/en/latest/api.html#built-in-implementations
|
| 456 |
+
- For implementations in separate packages see
|
| 457 |
+
https://filesystem-spec.readthedocs.io/en/latest/api.html#other-known-implementations
|
| 458 |
+
"""
|
| 459 |
+
out = open_files(
|
| 460 |
+
urlpath=[urlpath],
|
| 461 |
+
mode=mode,
|
| 462 |
+
compression=compression,
|
| 463 |
+
encoding=encoding,
|
| 464 |
+
errors=errors,
|
| 465 |
+
protocol=protocol,
|
| 466 |
+
newline=newline,
|
| 467 |
+
expand=False,
|
| 468 |
+
**kwargs,
|
| 469 |
+
)
|
| 470 |
+
if not out:
|
| 471 |
+
raise FileNotFoundError(urlpath)
|
| 472 |
+
return out[0]
|
| 473 |
+
|
| 474 |
+
|
| 475 |
+
def open_local(
|
| 476 |
+
url: str | list[str] | Path | list[Path],
|
| 477 |
+
mode: str = "rb",
|
| 478 |
+
**storage_options: dict,
|
| 479 |
+
) -> str | list[str]:
|
| 480 |
+
"""Open file(s) which can be resolved to local
|
| 481 |
+
|
| 482 |
+
For files which either are local, or get downloaded upon open
|
| 483 |
+
(e.g., by file caching)
|
| 484 |
+
|
| 485 |
+
Parameters
|
| 486 |
+
----------
|
| 487 |
+
url: str or list(str)
|
| 488 |
+
mode: str
|
| 489 |
+
Must be read mode
|
| 490 |
+
storage_options:
|
| 491 |
+
passed on to FS for or used by open_files (e.g., compression)
|
| 492 |
+
"""
|
| 493 |
+
if "r" not in mode:
|
| 494 |
+
raise ValueError("Can only ensure local files when reading")
|
| 495 |
+
of = open_files(url, mode=mode, **storage_options)
|
| 496 |
+
if not getattr(of[0].fs, "local_file", False):
|
| 497 |
+
raise ValueError(
|
| 498 |
+
"open_local can only be used on a filesystem which"
|
| 499 |
+
" has attribute local_file=True"
|
| 500 |
+
)
|
| 501 |
+
with of as files:
|
| 502 |
+
paths = [f.name for f in files]
|
| 503 |
+
if (isinstance(url, str) and not has_magic(url)) or isinstance(url, Path):
|
| 504 |
+
return paths[0]
|
| 505 |
+
return paths
|
| 506 |
+
|
| 507 |
+
|
| 508 |
+
def get_compression(urlpath, compression):
|
| 509 |
+
if compression == "infer":
|
| 510 |
+
compression = infer_compression(urlpath)
|
| 511 |
+
if compression is not None and compression not in compr:
|
| 512 |
+
raise ValueError(f"Compression type {compression} not supported")
|
| 513 |
+
return compression
|
| 514 |
+
|
| 515 |
+
|
| 516 |
+
def split_protocol(urlpath):
|
| 517 |
+
"""Return protocol, path pair"""
|
| 518 |
+
urlpath = stringify_path(urlpath)
|
| 519 |
+
if "://" in urlpath:
|
| 520 |
+
protocol, path = urlpath.split("://", 1)
|
| 521 |
+
if len(protocol) > 1:
|
| 522 |
+
# excludes Windows paths
|
| 523 |
+
return protocol, path
|
| 524 |
+
if urlpath.startswith("data:"):
|
| 525 |
+
return urlpath.split(":", 1)
|
| 526 |
+
return None, urlpath
|
| 527 |
+
|
| 528 |
+
|
| 529 |
+
def strip_protocol(urlpath):
|
| 530 |
+
"""Return only path part of full URL, according to appropriate backend"""
|
| 531 |
+
protocol, _ = split_protocol(urlpath)
|
| 532 |
+
cls = get_filesystem_class(protocol)
|
| 533 |
+
return cls._strip_protocol(urlpath)
|
| 534 |
+
|
| 535 |
+
|
| 536 |
+
def expand_paths_if_needed(paths, mode, num, fs, name_function):
|
| 537 |
+
"""Expand paths if they have a ``*`` in them (write mode) or any of ``*?[]``
|
| 538 |
+
in them (read mode).
|
| 539 |
+
|
| 540 |
+
:param paths: list of paths
|
| 541 |
+
mode: str
|
| 542 |
+
Mode in which to open files.
|
| 543 |
+
num: int
|
| 544 |
+
If opening in writing mode, number of files we expect to create.
|
| 545 |
+
fs: filesystem object
|
| 546 |
+
name_function: callable
|
| 547 |
+
If opening in writing mode, this callable is used to generate path
|
| 548 |
+
names. Names are generated for each partition by
|
| 549 |
+
``urlpath.replace('*', name_function(partition_index))``.
|
| 550 |
+
:return: list of paths
|
| 551 |
+
"""
|
| 552 |
+
expanded_paths = []
|
| 553 |
+
paths = list(paths)
|
| 554 |
+
|
| 555 |
+
if "w" in mode: # read mode
|
| 556 |
+
if sum([1 for p in paths if "*" in p]) > 1:
|
| 557 |
+
raise ValueError(
|
| 558 |
+
"When writing data, only one filename mask can be specified."
|
| 559 |
+
)
|
| 560 |
+
num = max(num, len(paths))
|
| 561 |
+
|
| 562 |
+
for curr_path in paths:
|
| 563 |
+
if "*" in curr_path:
|
| 564 |
+
# expand using name_function
|
| 565 |
+
expanded_paths.extend(_expand_paths(curr_path, name_function, num))
|
| 566 |
+
else:
|
| 567 |
+
expanded_paths.append(curr_path)
|
| 568 |
+
# if we generated more paths that asked for, trim the list
|
| 569 |
+
if len(expanded_paths) > num:
|
| 570 |
+
expanded_paths = expanded_paths[:num]
|
| 571 |
+
|
| 572 |
+
else: # read mode
|
| 573 |
+
for curr_path in paths:
|
| 574 |
+
if has_magic(curr_path):
|
| 575 |
+
# expand using glob
|
| 576 |
+
expanded_paths.extend(fs.glob(curr_path))
|
| 577 |
+
else:
|
| 578 |
+
expanded_paths.append(curr_path)
|
| 579 |
+
|
| 580 |
+
return expanded_paths
|
| 581 |
+
|
| 582 |
+
|
| 583 |
+
def get_fs_token_paths(
|
| 584 |
+
urlpath,
|
| 585 |
+
mode="rb",
|
| 586 |
+
num=1,
|
| 587 |
+
name_function=None,
|
| 588 |
+
storage_options=None,
|
| 589 |
+
protocol=None,
|
| 590 |
+
expand=True,
|
| 591 |
+
):
|
| 592 |
+
"""Filesystem, deterministic token, and paths from a urlpath and options.
|
| 593 |
+
|
| 594 |
+
Parameters
|
| 595 |
+
----------
|
| 596 |
+
urlpath: string or iterable
|
| 597 |
+
Absolute or relative filepath, URL (may include protocols like
|
| 598 |
+
``s3://``), or globstring pointing to data.
|
| 599 |
+
mode: str, optional
|
| 600 |
+
Mode in which to open files.
|
| 601 |
+
num: int, optional
|
| 602 |
+
If opening in writing mode, number of files we expect to create.
|
| 603 |
+
name_function: callable, optional
|
| 604 |
+
If opening in writing mode, this callable is used to generate path
|
| 605 |
+
names. Names are generated for each partition by
|
| 606 |
+
``urlpath.replace('*', name_function(partition_index))``.
|
| 607 |
+
storage_options: dict, optional
|
| 608 |
+
Additional keywords to pass to the filesystem class.
|
| 609 |
+
protocol: str or None
|
| 610 |
+
To override the protocol specifier in the URL
|
| 611 |
+
expand: bool
|
| 612 |
+
Expand string paths for writing, assuming the path is a directory
|
| 613 |
+
"""
|
| 614 |
+
if isinstance(urlpath, (list, tuple, set)):
|
| 615 |
+
if not urlpath:
|
| 616 |
+
raise ValueError("empty urlpath sequence")
|
| 617 |
+
urlpath0 = stringify_path(list(urlpath)[0])
|
| 618 |
+
else:
|
| 619 |
+
urlpath0 = stringify_path(urlpath)
|
| 620 |
+
storage_options = storage_options or {}
|
| 621 |
+
if protocol:
|
| 622 |
+
storage_options["protocol"] = protocol
|
| 623 |
+
chain = _un_chain(urlpath0, storage_options or {})
|
| 624 |
+
inkwargs = {}
|
| 625 |
+
# Reverse iterate the chain, creating a nested target_* structure
|
| 626 |
+
for i, ch in enumerate(reversed(chain)):
|
| 627 |
+
urls, nested_protocol, kw = ch
|
| 628 |
+
if i == len(chain) - 1:
|
| 629 |
+
inkwargs = dict(**kw, **inkwargs)
|
| 630 |
+
continue
|
| 631 |
+
inkwargs["target_options"] = dict(**kw, **inkwargs)
|
| 632 |
+
inkwargs["target_protocol"] = nested_protocol
|
| 633 |
+
inkwargs["fo"] = urls
|
| 634 |
+
paths, protocol, _ = chain[0]
|
| 635 |
+
fs = filesystem(protocol, **inkwargs)
|
| 636 |
+
if isinstance(urlpath, (list, tuple, set)):
|
| 637 |
+
pchains = [
|
| 638 |
+
_un_chain(stringify_path(u), storage_options or {})[0] for u in urlpath
|
| 639 |
+
]
|
| 640 |
+
if len({pc[1] for pc in pchains}) > 1:
|
| 641 |
+
raise ValueError("Protocol mismatch getting fs from %s", urlpath)
|
| 642 |
+
paths = [pc[0] for pc in pchains]
|
| 643 |
+
else:
|
| 644 |
+
paths = fs._strip_protocol(paths)
|
| 645 |
+
if isinstance(paths, (list, tuple, set)):
|
| 646 |
+
paths = expand_paths_if_needed(paths, mode, num, fs, name_function)
|
| 647 |
+
else:
|
| 648 |
+
if "w" in mode and expand:
|
| 649 |
+
paths = _expand_paths(paths, name_function, num)
|
| 650 |
+
elif "x" in mode and expand:
|
| 651 |
+
paths = _expand_paths(paths, name_function, num)
|
| 652 |
+
elif "*" in paths:
|
| 653 |
+
paths = [f for f in sorted(fs.glob(paths)) if not fs.isdir(f)]
|
| 654 |
+
else:
|
| 655 |
+
paths = [paths]
|
| 656 |
+
|
| 657 |
+
return fs, fs._fs_token, paths
|
| 658 |
+
|
| 659 |
+
|
| 660 |
+
def _expand_paths(path, name_function, num):
|
| 661 |
+
if isinstance(path, str):
|
| 662 |
+
if path.count("*") > 1:
|
| 663 |
+
raise ValueError("Output path spec must contain exactly one '*'.")
|
| 664 |
+
elif "*" not in path:
|
| 665 |
+
path = os.path.join(path, "*.part")
|
| 666 |
+
|
| 667 |
+
if name_function is None:
|
| 668 |
+
name_function = build_name_function(num - 1)
|
| 669 |
+
|
| 670 |
+
paths = [path.replace("*", name_function(i)) for i in range(num)]
|
| 671 |
+
if paths != sorted(paths):
|
| 672 |
+
logger.warning(
|
| 673 |
+
"In order to preserve order between partitions"
|
| 674 |
+
" paths created with ``name_function`` should "
|
| 675 |
+
"sort to partition order"
|
| 676 |
+
)
|
| 677 |
+
elif isinstance(path, (tuple, list)):
|
| 678 |
+
assert len(path) == num
|
| 679 |
+
paths = list(path)
|
| 680 |
+
else:
|
| 681 |
+
raise ValueError(
|
| 682 |
+
"Path should be either\n"
|
| 683 |
+
"1. A list of paths: ['foo.json', 'bar.json', ...]\n"
|
| 684 |
+
"2. A directory: 'foo/\n"
|
| 685 |
+
"3. A path with a '*' in it: 'foo.*.json'"
|
| 686 |
+
)
|
| 687 |
+
return paths
|
| 688 |
+
|
| 689 |
+
|
| 690 |
+
class PickleableTextIOWrapper(io.TextIOWrapper):
|
| 691 |
+
"""TextIOWrapper cannot be pickled. This solves it.
|
| 692 |
+
|
| 693 |
+
Requires that ``buffer`` be pickleable, which all instances of
|
| 694 |
+
AbstractBufferedFile are.
|
| 695 |
+
"""
|
| 696 |
+
|
| 697 |
+
def __init__(
|
| 698 |
+
self,
|
| 699 |
+
buffer,
|
| 700 |
+
encoding=None,
|
| 701 |
+
errors=None,
|
| 702 |
+
newline=None,
|
| 703 |
+
line_buffering=False,
|
| 704 |
+
write_through=False,
|
| 705 |
+
):
|
| 706 |
+
self.args = buffer, encoding, errors, newline, line_buffering, write_through
|
| 707 |
+
super().__init__(*self.args)
|
| 708 |
+
|
| 709 |
+
def __reduce__(self):
|
| 710 |
+
return PickleableTextIOWrapper, self.args
|
tuning-competition-baseline/.venv/lib/python3.11/site-packages/fsspec/dircache.py
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import time
|
| 2 |
+
from collections.abc import MutableMapping
|
| 3 |
+
from functools import lru_cache
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
class DirCache(MutableMapping):
|
| 7 |
+
"""
|
| 8 |
+
Caching of directory listings, in a structure like::
|
| 9 |
+
|
| 10 |
+
{"path0": [
|
| 11 |
+
{"name": "path0/file0",
|
| 12 |
+
"size": 123,
|
| 13 |
+
"type": "file",
|
| 14 |
+
...
|
| 15 |
+
},
|
| 16 |
+
{"name": "path0/file1",
|
| 17 |
+
},
|
| 18 |
+
...
|
| 19 |
+
],
|
| 20 |
+
"path1": [...]
|
| 21 |
+
}
|
| 22 |
+
|
| 23 |
+
Parameters to this class control listing expiry or indeed turn
|
| 24 |
+
caching off
|
| 25 |
+
"""
|
| 26 |
+
|
| 27 |
+
def __init__(
|
| 28 |
+
self,
|
| 29 |
+
use_listings_cache=True,
|
| 30 |
+
listings_expiry_time=None,
|
| 31 |
+
max_paths=None,
|
| 32 |
+
**kwargs,
|
| 33 |
+
):
|
| 34 |
+
"""
|
| 35 |
+
|
| 36 |
+
Parameters
|
| 37 |
+
----------
|
| 38 |
+
use_listings_cache: bool
|
| 39 |
+
If False, this cache never returns items, but always reports KeyError,
|
| 40 |
+
and setting items has no effect
|
| 41 |
+
listings_expiry_time: int or float (optional)
|
| 42 |
+
Time in seconds that a listing is considered valid. If None,
|
| 43 |
+
listings do not expire.
|
| 44 |
+
max_paths: int (optional)
|
| 45 |
+
The number of most recent listings that are considered valid; 'recent'
|
| 46 |
+
refers to when the entry was set.
|
| 47 |
+
"""
|
| 48 |
+
self._cache = {}
|
| 49 |
+
self._times = {}
|
| 50 |
+
if max_paths:
|
| 51 |
+
self._q = lru_cache(max_paths + 1)(lambda key: self._cache.pop(key, None))
|
| 52 |
+
self.use_listings_cache = use_listings_cache
|
| 53 |
+
self.listings_expiry_time = listings_expiry_time
|
| 54 |
+
self.max_paths = max_paths
|
| 55 |
+
|
| 56 |
+
def __getitem__(self, item):
|
| 57 |
+
if self.listings_expiry_time is not None:
|
| 58 |
+
if self._times.get(item, 0) - time.time() < -self.listings_expiry_time:
|
| 59 |
+
del self._cache[item]
|
| 60 |
+
if self.max_paths:
|
| 61 |
+
self._q(item)
|
| 62 |
+
return self._cache[item] # maybe raises KeyError
|
| 63 |
+
|
| 64 |
+
def clear(self):
|
| 65 |
+
self._cache.clear()
|
| 66 |
+
|
| 67 |
+
def __len__(self):
|
| 68 |
+
return len(self._cache)
|
| 69 |
+
|
| 70 |
+
def __contains__(self, item):
|
| 71 |
+
try:
|
| 72 |
+
self[item]
|
| 73 |
+
return True
|
| 74 |
+
except KeyError:
|
| 75 |
+
return False
|
| 76 |
+
|
| 77 |
+
def __setitem__(self, key, value):
|
| 78 |
+
if not self.use_listings_cache:
|
| 79 |
+
return
|
| 80 |
+
if self.max_paths:
|
| 81 |
+
self._q(key)
|
| 82 |
+
self._cache[key] = value
|
| 83 |
+
if self.listings_expiry_time is not None:
|
| 84 |
+
self._times[key] = time.time()
|
| 85 |
+
|
| 86 |
+
def __delitem__(self, key):
|
| 87 |
+
del self._cache[key]
|
| 88 |
+
|
| 89 |
+
def __iter__(self):
|
| 90 |
+
entries = list(self._cache)
|
| 91 |
+
|
| 92 |
+
return (k for k in entries if k in self)
|
| 93 |
+
|
| 94 |
+
def __reduce__(self):
|
| 95 |
+
return (
|
| 96 |
+
DirCache,
|
| 97 |
+
(self.use_listings_cache, self.listings_expiry_time, self.max_paths),
|
| 98 |
+
)
|
tuning-competition-baseline/.venv/lib/python3.11/site-packages/fsspec/exceptions.py
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
fsspec user-defined exception classes
|
| 3 |
+
"""
|
| 4 |
+
import asyncio
|
| 5 |
+
|
| 6 |
+
|
| 7 |
+
class BlocksizeMismatchError(ValueError):
|
| 8 |
+
"""
|
| 9 |
+
Raised when a cached file is opened with a different blocksize than it was
|
| 10 |
+
written with
|
| 11 |
+
"""
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
class FSTimeoutError(asyncio.TimeoutError):
|
| 15 |
+
"""
|
| 16 |
+
Raised when a fsspec function timed out occurs
|
| 17 |
+
"""
|
tuning-competition-baseline/.venv/lib/python3.11/site-packages/fsspec/fuse.py
ADDED
|
@@ -0,0 +1,324 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import argparse
|
| 2 |
+
import logging
|
| 3 |
+
import os
|
| 4 |
+
import stat
|
| 5 |
+
import threading
|
| 6 |
+
import time
|
| 7 |
+
from errno import EIO, ENOENT
|
| 8 |
+
|
| 9 |
+
from fuse import FUSE, FuseOSError, LoggingMixIn, Operations
|
| 10 |
+
|
| 11 |
+
from fsspec import __version__
|
| 12 |
+
from fsspec.core import url_to_fs
|
| 13 |
+
|
| 14 |
+
logger = logging.getLogger("fsspec.fuse")
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
class FUSEr(Operations):
|
| 18 |
+
def __init__(self, fs, path, ready_file=False):
|
| 19 |
+
self.fs = fs
|
| 20 |
+
self.cache = {}
|
| 21 |
+
self.root = path.rstrip("/") + "/"
|
| 22 |
+
self.counter = 0
|
| 23 |
+
logger.info("Starting FUSE at %s", path)
|
| 24 |
+
self._ready_file = ready_file
|
| 25 |
+
|
| 26 |
+
def getattr(self, path, fh=None):
|
| 27 |
+
logger.debug("getattr %s", path)
|
| 28 |
+
if self._ready_file and path in ["/.fuse_ready", ".fuse_ready"]:
|
| 29 |
+
return {"type": "file", "st_size": 5}
|
| 30 |
+
|
| 31 |
+
path = "".join([self.root, path.lstrip("/")]).rstrip("/")
|
| 32 |
+
try:
|
| 33 |
+
info = self.fs.info(path)
|
| 34 |
+
except FileNotFoundError:
|
| 35 |
+
raise FuseOSError(ENOENT)
|
| 36 |
+
|
| 37 |
+
data = {"st_uid": info.get("uid", 1000), "st_gid": info.get("gid", 1000)}
|
| 38 |
+
perm = info.get("mode", 0o777)
|
| 39 |
+
|
| 40 |
+
if info["type"] != "file":
|
| 41 |
+
data["st_mode"] = stat.S_IFDIR | perm
|
| 42 |
+
data["st_size"] = 0
|
| 43 |
+
data["st_blksize"] = 0
|
| 44 |
+
else:
|
| 45 |
+
data["st_mode"] = stat.S_IFREG | perm
|
| 46 |
+
data["st_size"] = info["size"]
|
| 47 |
+
data["st_blksize"] = 5 * 2**20
|
| 48 |
+
data["st_nlink"] = 1
|
| 49 |
+
data["st_atime"] = info["atime"] if "atime" in info else time.time()
|
| 50 |
+
data["st_ctime"] = info["ctime"] if "ctime" in info else time.time()
|
| 51 |
+
data["st_mtime"] = info["mtime"] if "mtime" in info else time.time()
|
| 52 |
+
return data
|
| 53 |
+
|
| 54 |
+
def readdir(self, path, fh):
|
| 55 |
+
logger.debug("readdir %s", path)
|
| 56 |
+
path = "".join([self.root, path.lstrip("/")])
|
| 57 |
+
files = self.fs.ls(path, False)
|
| 58 |
+
files = [os.path.basename(f.rstrip("/")) for f in files]
|
| 59 |
+
return [".", ".."] + files
|
| 60 |
+
|
| 61 |
+
def mkdir(self, path, mode):
|
| 62 |
+
path = "".join([self.root, path.lstrip("/")])
|
| 63 |
+
self.fs.mkdir(path)
|
| 64 |
+
return 0
|
| 65 |
+
|
| 66 |
+
def rmdir(self, path):
|
| 67 |
+
path = "".join([self.root, path.lstrip("/")])
|
| 68 |
+
self.fs.rmdir(path)
|
| 69 |
+
return 0
|
| 70 |
+
|
| 71 |
+
def read(self, path, size, offset, fh):
|
| 72 |
+
logger.debug("read %s", (path, size, offset))
|
| 73 |
+
if self._ready_file and path in ["/.fuse_ready", ".fuse_ready"]:
|
| 74 |
+
# status indicator
|
| 75 |
+
return b"ready"
|
| 76 |
+
|
| 77 |
+
f = self.cache[fh]
|
| 78 |
+
f.seek(offset)
|
| 79 |
+
out = f.read(size)
|
| 80 |
+
return out
|
| 81 |
+
|
| 82 |
+
def write(self, path, data, offset, fh):
|
| 83 |
+
logger.debug("write %s", (path, offset))
|
| 84 |
+
f = self.cache[fh]
|
| 85 |
+
f.seek(offset)
|
| 86 |
+
f.write(data)
|
| 87 |
+
return len(data)
|
| 88 |
+
|
| 89 |
+
def create(self, path, flags, fi=None):
|
| 90 |
+
logger.debug("create %s", (path, flags))
|
| 91 |
+
fn = "".join([self.root, path.lstrip("/")])
|
| 92 |
+
self.fs.touch(fn) # OS will want to get attributes immediately
|
| 93 |
+
f = self.fs.open(fn, "wb")
|
| 94 |
+
self.cache[self.counter] = f
|
| 95 |
+
self.counter += 1
|
| 96 |
+
return self.counter - 1
|
| 97 |
+
|
| 98 |
+
def open(self, path, flags):
|
| 99 |
+
logger.debug("open %s", (path, flags))
|
| 100 |
+
fn = "".join([self.root, path.lstrip("/")])
|
| 101 |
+
if flags % 2 == 0:
|
| 102 |
+
# read
|
| 103 |
+
mode = "rb"
|
| 104 |
+
else:
|
| 105 |
+
# write/create
|
| 106 |
+
mode = "wb"
|
| 107 |
+
self.cache[self.counter] = self.fs.open(fn, mode)
|
| 108 |
+
self.counter += 1
|
| 109 |
+
return self.counter - 1
|
| 110 |
+
|
| 111 |
+
def truncate(self, path, length, fh=None):
|
| 112 |
+
fn = "".join([self.root, path.lstrip("/")])
|
| 113 |
+
if length != 0:
|
| 114 |
+
raise NotImplementedError
|
| 115 |
+
# maybe should be no-op since open with write sets size to zero anyway
|
| 116 |
+
self.fs.touch(fn)
|
| 117 |
+
|
| 118 |
+
def unlink(self, path):
|
| 119 |
+
fn = "".join([self.root, path.lstrip("/")])
|
| 120 |
+
try:
|
| 121 |
+
self.fs.rm(fn, False)
|
| 122 |
+
except (OSError, FileNotFoundError):
|
| 123 |
+
raise FuseOSError(EIO)
|
| 124 |
+
|
| 125 |
+
def release(self, path, fh):
|
| 126 |
+
try:
|
| 127 |
+
if fh in self.cache:
|
| 128 |
+
f = self.cache[fh]
|
| 129 |
+
f.close()
|
| 130 |
+
self.cache.pop(fh)
|
| 131 |
+
except Exception as e:
|
| 132 |
+
print(e)
|
| 133 |
+
return 0
|
| 134 |
+
|
| 135 |
+
def chmod(self, path, mode):
|
| 136 |
+
if hasattr(self.fs, "chmod"):
|
| 137 |
+
path = "".join([self.root, path.lstrip("/")])
|
| 138 |
+
return self.fs.chmod(path, mode)
|
| 139 |
+
raise NotImplementedError
|
| 140 |
+
|
| 141 |
+
|
| 142 |
+
def run(
|
| 143 |
+
fs,
|
| 144 |
+
path,
|
| 145 |
+
mount_point,
|
| 146 |
+
foreground=True,
|
| 147 |
+
threads=False,
|
| 148 |
+
ready_file=False,
|
| 149 |
+
ops_class=FUSEr,
|
| 150 |
+
):
|
| 151 |
+
"""Mount stuff in a local directory
|
| 152 |
+
|
| 153 |
+
This uses fusepy to make it appear as if a given path on an fsspec
|
| 154 |
+
instance is in fact resident within the local file-system.
|
| 155 |
+
|
| 156 |
+
This requires that fusepy by installed, and that FUSE be available on
|
| 157 |
+
the system (typically requiring a package to be installed with
|
| 158 |
+
apt, yum, brew, etc.).
|
| 159 |
+
|
| 160 |
+
Parameters
|
| 161 |
+
----------
|
| 162 |
+
fs: file-system instance
|
| 163 |
+
From one of the compatible implementations
|
| 164 |
+
path: str
|
| 165 |
+
Location on that file-system to regard as the root directory to
|
| 166 |
+
mount. Note that you typically should include the terminating "/"
|
| 167 |
+
character.
|
| 168 |
+
mount_point: str
|
| 169 |
+
An empty directory on the local file-system where the contents of
|
| 170 |
+
the remote path will appear.
|
| 171 |
+
foreground: bool
|
| 172 |
+
Whether or not calling this function will block. Operation will
|
| 173 |
+
typically be more stable if True.
|
| 174 |
+
threads: bool
|
| 175 |
+
Whether or not to create threads when responding to file operations
|
| 176 |
+
within the mounter directory. Operation will typically be more
|
| 177 |
+
stable if False.
|
| 178 |
+
ready_file: bool
|
| 179 |
+
Whether the FUSE process is ready. The ``.fuse_ready`` file will
|
| 180 |
+
exist in the ``mount_point`` directory if True. Debugging purpose.
|
| 181 |
+
ops_class: FUSEr or Subclass of FUSEr
|
| 182 |
+
To override the default behavior of FUSEr. For Example, logging
|
| 183 |
+
to file.
|
| 184 |
+
|
| 185 |
+
"""
|
| 186 |
+
func = lambda: FUSE(
|
| 187 |
+
ops_class(fs, path, ready_file=ready_file),
|
| 188 |
+
mount_point,
|
| 189 |
+
nothreads=not threads,
|
| 190 |
+
foreground=foreground,
|
| 191 |
+
)
|
| 192 |
+
if not foreground:
|
| 193 |
+
th = threading.Thread(target=func)
|
| 194 |
+
th.daemon = True
|
| 195 |
+
th.start()
|
| 196 |
+
return th
|
| 197 |
+
else: # pragma: no cover
|
| 198 |
+
try:
|
| 199 |
+
func()
|
| 200 |
+
except KeyboardInterrupt:
|
| 201 |
+
pass
|
| 202 |
+
|
| 203 |
+
|
| 204 |
+
def main(args):
|
| 205 |
+
"""Mount filesystem from chained URL to MOUNT_POINT.
|
| 206 |
+
|
| 207 |
+
Examples:
|
| 208 |
+
|
| 209 |
+
python3 -m fsspec.fuse memory /usr/share /tmp/mem
|
| 210 |
+
|
| 211 |
+
python3 -m fsspec.fuse local /tmp/source /tmp/local \\
|
| 212 |
+
-l /tmp/fsspecfuse.log
|
| 213 |
+
|
| 214 |
+
You can also mount chained-URLs and use special settings:
|
| 215 |
+
|
| 216 |
+
python3 -m fsspec.fuse 'filecache::zip::file://data.zip' \\
|
| 217 |
+
/ /tmp/zip \\
|
| 218 |
+
-o 'filecache-cache_storage=/tmp/simplecache'
|
| 219 |
+
|
| 220 |
+
You can specify the type of the setting by using `[int]` or `[bool]`,
|
| 221 |
+
(`true`, `yes`, `1` represents the Boolean value `True`):
|
| 222 |
+
|
| 223 |
+
python3 -m fsspec.fuse 'simplecache::ftp://ftp1.at.proftpd.org' \\
|
| 224 |
+
/historic/packages/RPMS /tmp/ftp \\
|
| 225 |
+
-o 'simplecache-cache_storage=/tmp/simplecache' \\
|
| 226 |
+
-o 'simplecache-check_files=false[bool]' \\
|
| 227 |
+
-o 'ftp-listings_expiry_time=60[int]' \\
|
| 228 |
+
-o 'ftp-username=anonymous' \\
|
| 229 |
+
-o 'ftp-password=xieyanbo'
|
| 230 |
+
"""
|
| 231 |
+
|
| 232 |
+
class RawDescriptionArgumentParser(argparse.ArgumentParser):
|
| 233 |
+
def format_help(self):
|
| 234 |
+
usage = super().format_help()
|
| 235 |
+
parts = usage.split("\n\n")
|
| 236 |
+
parts[1] = self.description.rstrip()
|
| 237 |
+
return "\n\n".join(parts)
|
| 238 |
+
|
| 239 |
+
parser = RawDescriptionArgumentParser(prog="fsspec.fuse", description=main.__doc__)
|
| 240 |
+
parser.add_argument("--version", action="version", version=__version__)
|
| 241 |
+
parser.add_argument("url", type=str, help="fs url")
|
| 242 |
+
parser.add_argument("source_path", type=str, help="source directory in fs")
|
| 243 |
+
parser.add_argument("mount_point", type=str, help="local directory")
|
| 244 |
+
parser.add_argument(
|
| 245 |
+
"-o",
|
| 246 |
+
"--option",
|
| 247 |
+
action="append",
|
| 248 |
+
help="Any options of protocol included in the chained URL",
|
| 249 |
+
)
|
| 250 |
+
parser.add_argument(
|
| 251 |
+
"-l", "--log-file", type=str, help="Logging FUSE debug info (Default: '')"
|
| 252 |
+
)
|
| 253 |
+
parser.add_argument(
|
| 254 |
+
"-f",
|
| 255 |
+
"--foreground",
|
| 256 |
+
action="store_false",
|
| 257 |
+
help="Running in foreground or not (Default: False)",
|
| 258 |
+
)
|
| 259 |
+
parser.add_argument(
|
| 260 |
+
"-t",
|
| 261 |
+
"--threads",
|
| 262 |
+
action="store_false",
|
| 263 |
+
help="Running with threads support (Default: False)",
|
| 264 |
+
)
|
| 265 |
+
parser.add_argument(
|
| 266 |
+
"-r",
|
| 267 |
+
"--ready-file",
|
| 268 |
+
action="store_false",
|
| 269 |
+
help="The `.fuse_ready` file will exist after FUSE is ready. "
|
| 270 |
+
"(Debugging purpose, Default: False)",
|
| 271 |
+
)
|
| 272 |
+
args = parser.parse_args(args)
|
| 273 |
+
|
| 274 |
+
kwargs = {}
|
| 275 |
+
for item in args.option or []:
|
| 276 |
+
key, sep, value = item.partition("=")
|
| 277 |
+
if not sep:
|
| 278 |
+
parser.error(message=f"Wrong option: {item!r}")
|
| 279 |
+
val = value.lower()
|
| 280 |
+
if val.endswith("[int]"):
|
| 281 |
+
value = int(value[: -len("[int]")])
|
| 282 |
+
elif val.endswith("[bool]"):
|
| 283 |
+
value = val[: -len("[bool]")] in ["1", "yes", "true"]
|
| 284 |
+
|
| 285 |
+
if "-" in key:
|
| 286 |
+
fs_name, setting_name = key.split("-", 1)
|
| 287 |
+
if fs_name in kwargs:
|
| 288 |
+
kwargs[fs_name][setting_name] = value
|
| 289 |
+
else:
|
| 290 |
+
kwargs[fs_name] = {setting_name: value}
|
| 291 |
+
else:
|
| 292 |
+
kwargs[key] = value
|
| 293 |
+
|
| 294 |
+
if args.log_file:
|
| 295 |
+
logging.basicConfig(
|
| 296 |
+
level=logging.DEBUG,
|
| 297 |
+
filename=args.log_file,
|
| 298 |
+
format="%(asctime)s %(message)s",
|
| 299 |
+
)
|
| 300 |
+
|
| 301 |
+
class LoggingFUSEr(FUSEr, LoggingMixIn):
|
| 302 |
+
pass
|
| 303 |
+
|
| 304 |
+
fuser = LoggingFUSEr
|
| 305 |
+
else:
|
| 306 |
+
fuser = FUSEr
|
| 307 |
+
|
| 308 |
+
fs, url_path = url_to_fs(args.url, **kwargs)
|
| 309 |
+
logger.debug("Mounting %s to %s", url_path, str(args.mount_point))
|
| 310 |
+
run(
|
| 311 |
+
fs,
|
| 312 |
+
args.source_path,
|
| 313 |
+
args.mount_point,
|
| 314 |
+
foreground=args.foreground,
|
| 315 |
+
threads=args.threads,
|
| 316 |
+
ready_file=args.ready_file,
|
| 317 |
+
ops_class=fuser,
|
| 318 |
+
)
|
| 319 |
+
|
| 320 |
+
|
| 321 |
+
if __name__ == "__main__":
|
| 322 |
+
import sys
|
| 323 |
+
|
| 324 |
+
main(sys.argv[1:])
|
tuning-competition-baseline/.venv/lib/python3.11/site-packages/fsspec/generic.py
ADDED
|
@@ -0,0 +1,403 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
import inspect
|
| 4 |
+
import logging
|
| 5 |
+
import os
|
| 6 |
+
import shutil
|
| 7 |
+
import uuid
|
| 8 |
+
from typing import Optional
|
| 9 |
+
|
| 10 |
+
from .asyn import AsyncFileSystem, _run_coros_in_chunks, sync_wrapper
|
| 11 |
+
from .callbacks import DEFAULT_CALLBACK
|
| 12 |
+
from .core import filesystem, get_filesystem_class, split_protocol, url_to_fs
|
| 13 |
+
|
| 14 |
+
_generic_fs = {}
|
| 15 |
+
logger = logging.getLogger("fsspec.generic")
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
def set_generic_fs(protocol, **storage_options):
|
| 19 |
+
_generic_fs[protocol] = filesystem(protocol, **storage_options)
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
default_method = "default"
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
def _resolve_fs(url, method=None, protocol=None, storage_options=None):
|
| 26 |
+
"""Pick instance of backend FS"""
|
| 27 |
+
method = method or default_method
|
| 28 |
+
protocol = protocol or split_protocol(url)[0]
|
| 29 |
+
storage_options = storage_options or {}
|
| 30 |
+
if method == "default":
|
| 31 |
+
return filesystem(protocol)
|
| 32 |
+
if method == "generic":
|
| 33 |
+
return _generic_fs[protocol]
|
| 34 |
+
if method == "current":
|
| 35 |
+
cls = get_filesystem_class(protocol)
|
| 36 |
+
return cls.current()
|
| 37 |
+
if method == "options":
|
| 38 |
+
fs, _ = url_to_fs(url, **storage_options.get(protocol, {}))
|
| 39 |
+
return fs
|
| 40 |
+
raise ValueError(f"Unknown FS resolution method: {method}")
|
| 41 |
+
|
| 42 |
+
|
| 43 |
+
def rsync(
|
| 44 |
+
source,
|
| 45 |
+
destination,
|
| 46 |
+
delete_missing=False,
|
| 47 |
+
source_field="size",
|
| 48 |
+
dest_field="size",
|
| 49 |
+
update_cond="different",
|
| 50 |
+
inst_kwargs=None,
|
| 51 |
+
fs=None,
|
| 52 |
+
**kwargs,
|
| 53 |
+
):
|
| 54 |
+
"""Sync files between two directory trees
|
| 55 |
+
|
| 56 |
+
(experimental)
|
| 57 |
+
|
| 58 |
+
Parameters
|
| 59 |
+
----------
|
| 60 |
+
source: str
|
| 61 |
+
Root of the directory tree to take files from. This must be a directory, but
|
| 62 |
+
do not include any terminating "/" character
|
| 63 |
+
destination: str
|
| 64 |
+
Root path to copy into. The contents of this location should be
|
| 65 |
+
identical to the contents of ``source`` when done. This will be made a
|
| 66 |
+
directory, and the terminal "/" should not be included.
|
| 67 |
+
delete_missing: bool
|
| 68 |
+
If there are paths in the destination that don't exist in the
|
| 69 |
+
source and this is True, delete them. Otherwise, leave them alone.
|
| 70 |
+
source_field: str | callable
|
| 71 |
+
If ``update_field`` is "different", this is the key in the info
|
| 72 |
+
of source files to consider for difference. Maybe a function of the
|
| 73 |
+
info dict.
|
| 74 |
+
dest_field: str | callable
|
| 75 |
+
If ``update_field`` is "different", this is the key in the info
|
| 76 |
+
of destination files to consider for difference. May be a function of
|
| 77 |
+
the info dict.
|
| 78 |
+
update_cond: "different"|"always"|"never"
|
| 79 |
+
If "always", every file is copied, regardless of whether it exists in
|
| 80 |
+
the destination. If "never", files that exist in the destination are
|
| 81 |
+
not copied again. If "different" (default), only copy if the info
|
| 82 |
+
fields given by ``source_field`` and ``dest_field`` (usually "size")
|
| 83 |
+
are different. Other comparisons may be added in the future.
|
| 84 |
+
inst_kwargs: dict|None
|
| 85 |
+
If ``fs`` is None, use this set of keyword arguments to make a
|
| 86 |
+
GenericFileSystem instance
|
| 87 |
+
fs: GenericFileSystem|None
|
| 88 |
+
Instance to use if explicitly given. The instance defines how to
|
| 89 |
+
to make downstream file system instances from paths.
|
| 90 |
+
"""
|
| 91 |
+
fs = fs or GenericFileSystem(**(inst_kwargs or {}))
|
| 92 |
+
source = fs._strip_protocol(source)
|
| 93 |
+
destination = fs._strip_protocol(destination)
|
| 94 |
+
allfiles = fs.find(source, withdirs=True, detail=True)
|
| 95 |
+
if not fs.isdir(source):
|
| 96 |
+
raise ValueError("Can only rsync on a directory")
|
| 97 |
+
otherfiles = fs.find(destination, withdirs=True, detail=True)
|
| 98 |
+
dirs = [
|
| 99 |
+
a
|
| 100 |
+
for a, v in allfiles.items()
|
| 101 |
+
if v["type"] == "directory" and a.replace(source, destination) not in otherfiles
|
| 102 |
+
]
|
| 103 |
+
logger.debug(f"{len(dirs)} directories to create")
|
| 104 |
+
if dirs:
|
| 105 |
+
fs.make_many_dirs(
|
| 106 |
+
[dirn.replace(source, destination) for dirn in dirs], exist_ok=True
|
| 107 |
+
)
|
| 108 |
+
allfiles = {a: v for a, v in allfiles.items() if v["type"] == "file"}
|
| 109 |
+
logger.debug(f"{len(allfiles)} files to consider for copy")
|
| 110 |
+
to_delete = [
|
| 111 |
+
o
|
| 112 |
+
for o, v in otherfiles.items()
|
| 113 |
+
if o.replace(destination, source) not in allfiles and v["type"] == "file"
|
| 114 |
+
]
|
| 115 |
+
for k, v in allfiles.copy().items():
|
| 116 |
+
otherfile = k.replace(source, destination)
|
| 117 |
+
if otherfile in otherfiles:
|
| 118 |
+
if update_cond == "always":
|
| 119 |
+
allfiles[k] = otherfile
|
| 120 |
+
elif update_cond == "different":
|
| 121 |
+
inf1 = source_field(v) if callable(source_field) else v[source_field]
|
| 122 |
+
v2 = otherfiles[otherfile]
|
| 123 |
+
inf2 = dest_field(v2) if callable(dest_field) else v2[dest_field]
|
| 124 |
+
if inf1 != inf2:
|
| 125 |
+
# details mismatch, make copy
|
| 126 |
+
allfiles[k] = otherfile
|
| 127 |
+
else:
|
| 128 |
+
# details match, don't copy
|
| 129 |
+
allfiles.pop(k)
|
| 130 |
+
else:
|
| 131 |
+
# file not in target yet
|
| 132 |
+
allfiles[k] = otherfile
|
| 133 |
+
logger.debug(f"{len(allfiles)} files to copy")
|
| 134 |
+
if allfiles:
|
| 135 |
+
source_files, target_files = zip(*allfiles.items())
|
| 136 |
+
fs.cp(source_files, target_files, **kwargs)
|
| 137 |
+
logger.debug(f"{len(to_delete)} files to delete")
|
| 138 |
+
if delete_missing:
|
| 139 |
+
fs.rm(to_delete)
|
| 140 |
+
|
| 141 |
+
|
| 142 |
+
class GenericFileSystem(AsyncFileSystem):
|
| 143 |
+
"""Wrapper over all other FS types
|
| 144 |
+
|
| 145 |
+
<experimental!>
|
| 146 |
+
|
| 147 |
+
This implementation is a single unified interface to be able to run FS operations
|
| 148 |
+
over generic URLs, and dispatch to the specific implementations using the URL
|
| 149 |
+
protocol prefix.
|
| 150 |
+
|
| 151 |
+
Note: instances of this FS are always async, even if you never use it with any async
|
| 152 |
+
backend.
|
| 153 |
+
"""
|
| 154 |
+
|
| 155 |
+
protocol = "generic" # there is no real reason to ever use a protocol with this FS
|
| 156 |
+
|
| 157 |
+
def __init__(self, default_method="default", **kwargs):
|
| 158 |
+
"""
|
| 159 |
+
|
| 160 |
+
Parameters
|
| 161 |
+
----------
|
| 162 |
+
default_method: str (optional)
|
| 163 |
+
Defines how to configure backend FS instances. Options are:
|
| 164 |
+
- "default": instantiate like FSClass(), with no
|
| 165 |
+
extra arguments; this is the default instance of that FS, and can be
|
| 166 |
+
configured via the config system
|
| 167 |
+
- "generic": takes instances from the `_generic_fs` dict in this module,
|
| 168 |
+
which you must populate before use. Keys are by protocol
|
| 169 |
+
- "current": takes the most recently instantiated version of each FS
|
| 170 |
+
"""
|
| 171 |
+
self.method = default_method
|
| 172 |
+
super().__init__(**kwargs)
|
| 173 |
+
|
| 174 |
+
def _parent(self, path):
|
| 175 |
+
fs = _resolve_fs(path, self.method)
|
| 176 |
+
return fs.unstrip_protocol(fs._parent(path))
|
| 177 |
+
|
| 178 |
+
def _strip_protocol(self, path):
|
| 179 |
+
# normalization only
|
| 180 |
+
fs = _resolve_fs(path, self.method)
|
| 181 |
+
return fs.unstrip_protocol(fs._strip_protocol(path))
|
| 182 |
+
|
| 183 |
+
async def _find(self, path, maxdepth=None, withdirs=False, detail=False, **kwargs):
|
| 184 |
+
fs = _resolve_fs(path, self.method)
|
| 185 |
+
if fs.async_impl:
|
| 186 |
+
out = await fs._find(
|
| 187 |
+
path, maxdepth=maxdepth, withdirs=withdirs, detail=True, **kwargs
|
| 188 |
+
)
|
| 189 |
+
else:
|
| 190 |
+
out = fs.find(
|
| 191 |
+
path, maxdepth=maxdepth, withdirs=withdirs, detail=True, **kwargs
|
| 192 |
+
)
|
| 193 |
+
result = {}
|
| 194 |
+
for k, v in out.items():
|
| 195 |
+
name = fs.unstrip_protocol(k)
|
| 196 |
+
v["name"] = name
|
| 197 |
+
result[name] = v
|
| 198 |
+
if detail:
|
| 199 |
+
return result
|
| 200 |
+
return list(result)
|
| 201 |
+
|
| 202 |
+
async def _info(self, url, **kwargs):
|
| 203 |
+
fs = _resolve_fs(url, self.method)
|
| 204 |
+
if fs.async_impl:
|
| 205 |
+
out = await fs._info(url, **kwargs)
|
| 206 |
+
else:
|
| 207 |
+
out = fs.info(url, **kwargs)
|
| 208 |
+
out["name"] = fs.unstrip_protocol(out["name"])
|
| 209 |
+
return out
|
| 210 |
+
|
| 211 |
+
async def _ls(
|
| 212 |
+
self,
|
| 213 |
+
url,
|
| 214 |
+
detail=True,
|
| 215 |
+
**kwargs,
|
| 216 |
+
):
|
| 217 |
+
fs = _resolve_fs(url, self.method)
|
| 218 |
+
if fs.async_impl:
|
| 219 |
+
out = await fs._ls(url, detail=True, **kwargs)
|
| 220 |
+
else:
|
| 221 |
+
out = fs.ls(url, detail=True, **kwargs)
|
| 222 |
+
for o in out:
|
| 223 |
+
o["name"] = fs.unstrip_protocol(o["name"])
|
| 224 |
+
if detail:
|
| 225 |
+
return out
|
| 226 |
+
else:
|
| 227 |
+
return [o["name"] for o in out]
|
| 228 |
+
|
| 229 |
+
async def _cat_file(
|
| 230 |
+
self,
|
| 231 |
+
url,
|
| 232 |
+
**kwargs,
|
| 233 |
+
):
|
| 234 |
+
fs = _resolve_fs(url, self.method)
|
| 235 |
+
if fs.async_impl:
|
| 236 |
+
return await fs._cat_file(url, **kwargs)
|
| 237 |
+
else:
|
| 238 |
+
return fs.cat_file(url, **kwargs)
|
| 239 |
+
|
| 240 |
+
async def _pipe_file(
|
| 241 |
+
self,
|
| 242 |
+
path,
|
| 243 |
+
value,
|
| 244 |
+
**kwargs,
|
| 245 |
+
):
|
| 246 |
+
fs = _resolve_fs(path, self.method)
|
| 247 |
+
if fs.async_impl:
|
| 248 |
+
return await fs._pipe_file(path, value, **kwargs)
|
| 249 |
+
else:
|
| 250 |
+
return fs.pipe_file(path, value, **kwargs)
|
| 251 |
+
|
| 252 |
+
async def _rm(self, url, **kwargs):
|
| 253 |
+
urls = url
|
| 254 |
+
if isinstance(urls, str):
|
| 255 |
+
urls = [urls]
|
| 256 |
+
fs = _resolve_fs(urls[0], self.method)
|
| 257 |
+
if fs.async_impl:
|
| 258 |
+
await fs._rm(urls, **kwargs)
|
| 259 |
+
else:
|
| 260 |
+
fs.rm(url, **kwargs)
|
| 261 |
+
|
| 262 |
+
async def _makedirs(self, path, exist_ok=False):
|
| 263 |
+
logger.debug("Make dir %s", path)
|
| 264 |
+
fs = _resolve_fs(path, self.method)
|
| 265 |
+
if fs.async_impl:
|
| 266 |
+
await fs._makedirs(path, exist_ok=exist_ok)
|
| 267 |
+
else:
|
| 268 |
+
fs.makedirs(path, exist_ok=exist_ok)
|
| 269 |
+
|
| 270 |
+
def rsync(self, source, destination, **kwargs):
|
| 271 |
+
"""Sync files between two directory trees
|
| 272 |
+
|
| 273 |
+
See `func:rsync` for more details.
|
| 274 |
+
"""
|
| 275 |
+
rsync(source, destination, fs=self, **kwargs)
|
| 276 |
+
|
| 277 |
+
async def _cp_file(
|
| 278 |
+
self,
|
| 279 |
+
url,
|
| 280 |
+
url2,
|
| 281 |
+
blocksize=2**20,
|
| 282 |
+
callback=DEFAULT_CALLBACK,
|
| 283 |
+
**kwargs,
|
| 284 |
+
):
|
| 285 |
+
fs = _resolve_fs(url, self.method)
|
| 286 |
+
fs2 = _resolve_fs(url2, self.method)
|
| 287 |
+
if fs is fs2:
|
| 288 |
+
# pure remote
|
| 289 |
+
if fs.async_impl:
|
| 290 |
+
return await fs._cp_file(url, url2, **kwargs)
|
| 291 |
+
else:
|
| 292 |
+
return fs.cp_file(url, url2, **kwargs)
|
| 293 |
+
kw = {"blocksize": 0, "cache_type": "none"}
|
| 294 |
+
try:
|
| 295 |
+
f1 = (
|
| 296 |
+
await fs.open_async(url, "rb")
|
| 297 |
+
if hasattr(fs, "open_async")
|
| 298 |
+
else fs.open(url, "rb", **kw)
|
| 299 |
+
)
|
| 300 |
+
callback.set_size(await maybe_await(f1.size))
|
| 301 |
+
f2 = (
|
| 302 |
+
await fs2.open_async(url2, "wb")
|
| 303 |
+
if hasattr(fs2, "open_async")
|
| 304 |
+
else fs2.open(url2, "wb", **kw)
|
| 305 |
+
)
|
| 306 |
+
while f1.size is None or f2.tell() < f1.size:
|
| 307 |
+
data = await maybe_await(f1.read(blocksize))
|
| 308 |
+
if f1.size is None and not data:
|
| 309 |
+
break
|
| 310 |
+
await maybe_await(f2.write(data))
|
| 311 |
+
callback.absolute_update(f2.tell())
|
| 312 |
+
finally:
|
| 313 |
+
try:
|
| 314 |
+
await maybe_await(f2.close())
|
| 315 |
+
await maybe_await(f1.close())
|
| 316 |
+
except NameError:
|
| 317 |
+
# fail while opening f1 or f2
|
| 318 |
+
pass
|
| 319 |
+
|
| 320 |
+
async def _make_many_dirs(self, urls, exist_ok=True):
|
| 321 |
+
fs = _resolve_fs(urls[0], self.method)
|
| 322 |
+
if fs.async_impl:
|
| 323 |
+
coros = [fs._makedirs(u, exist_ok=exist_ok) for u in urls]
|
| 324 |
+
await _run_coros_in_chunks(coros)
|
| 325 |
+
else:
|
| 326 |
+
for u in urls:
|
| 327 |
+
fs.makedirs(u, exist_ok=exist_ok)
|
| 328 |
+
|
| 329 |
+
make_many_dirs = sync_wrapper(_make_many_dirs)
|
| 330 |
+
|
| 331 |
+
async def _copy(
|
| 332 |
+
self,
|
| 333 |
+
path1: list[str],
|
| 334 |
+
path2: list[str],
|
| 335 |
+
recursive: bool = False,
|
| 336 |
+
on_error: str = "ignore",
|
| 337 |
+
maxdepth: Optional[int] = None,
|
| 338 |
+
batch_size: Optional[int] = None,
|
| 339 |
+
tempdir: Optional[str] = None,
|
| 340 |
+
**kwargs,
|
| 341 |
+
):
|
| 342 |
+
if recursive:
|
| 343 |
+
raise NotImplementedError
|
| 344 |
+
fs = _resolve_fs(path1[0], self.method)
|
| 345 |
+
fs2 = _resolve_fs(path2[0], self.method)
|
| 346 |
+
# not expanding paths atm., assume call is from rsync()
|
| 347 |
+
if fs is fs2:
|
| 348 |
+
# pure remote
|
| 349 |
+
if fs.async_impl:
|
| 350 |
+
return await fs._copy(path1, path2, **kwargs)
|
| 351 |
+
else:
|
| 352 |
+
return fs.copy(path1, path2, **kwargs)
|
| 353 |
+
await copy_file_op(
|
| 354 |
+
fs, path1, fs2, path2, tempdir, batch_size, on_error=on_error
|
| 355 |
+
)
|
| 356 |
+
|
| 357 |
+
|
| 358 |
+
async def copy_file_op(
|
| 359 |
+
fs1, url1, fs2, url2, tempdir=None, batch_size=20, on_error="ignore"
|
| 360 |
+
):
|
| 361 |
+
import tempfile
|
| 362 |
+
|
| 363 |
+
tempdir = tempdir or tempfile.mkdtemp()
|
| 364 |
+
try:
|
| 365 |
+
coros = [
|
| 366 |
+
_copy_file_op(
|
| 367 |
+
fs1,
|
| 368 |
+
u1,
|
| 369 |
+
fs2,
|
| 370 |
+
u2,
|
| 371 |
+
os.path.join(tempdir, uuid.uuid4().hex),
|
| 372 |
+
on_error=on_error,
|
| 373 |
+
)
|
| 374 |
+
for u1, u2 in zip(url1, url2)
|
| 375 |
+
]
|
| 376 |
+
await _run_coros_in_chunks(coros, batch_size=batch_size)
|
| 377 |
+
finally:
|
| 378 |
+
shutil.rmtree(tempdir)
|
| 379 |
+
|
| 380 |
+
|
| 381 |
+
async def _copy_file_op(fs1, url1, fs2, url2, local, on_error="ignore"):
|
| 382 |
+
ex = () if on_error == "raise" else Exception
|
| 383 |
+
logger.debug("Copy %s -> %s", url1, url2)
|
| 384 |
+
try:
|
| 385 |
+
if fs1.async_impl:
|
| 386 |
+
await fs1._get_file(url1, local)
|
| 387 |
+
else:
|
| 388 |
+
fs1.get_file(url1, local)
|
| 389 |
+
if fs2.async_impl:
|
| 390 |
+
await fs2._put_file(local, url2)
|
| 391 |
+
else:
|
| 392 |
+
fs2.put_file(local, url2)
|
| 393 |
+
os.unlink(local)
|
| 394 |
+
logger.debug("Copy %s -> %s; done", url1, url2)
|
| 395 |
+
except ex as e:
|
| 396 |
+
logger.debug("ignoring cp exception for %s: %s", url1, e)
|
| 397 |
+
|
| 398 |
+
|
| 399 |
+
async def maybe_await(cor):
|
| 400 |
+
if inspect.iscoroutine(cor):
|
| 401 |
+
return await cor
|
| 402 |
+
else:
|
| 403 |
+
return cor
|
tuning-competition-baseline/.venv/lib/python3.11/site-packages/fsspec/gui.py
ADDED
|
@@ -0,0 +1,414 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import ast
|
| 2 |
+
import contextlib
|
| 3 |
+
import logging
|
| 4 |
+
import os
|
| 5 |
+
import re
|
| 6 |
+
from typing import ClassVar, Sequence
|
| 7 |
+
|
| 8 |
+
import panel as pn
|
| 9 |
+
|
| 10 |
+
from .core import OpenFile, get_filesystem_class, split_protocol
|
| 11 |
+
from .registry import known_implementations
|
| 12 |
+
|
| 13 |
+
pn.extension()
|
| 14 |
+
logger = logging.getLogger("fsspec.gui")
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
class SigSlot:
|
| 18 |
+
"""Signal-slot mixin, for Panel event passing
|
| 19 |
+
|
| 20 |
+
Include this class in a widget manager's superclasses to be able to
|
| 21 |
+
register events and callbacks on Panel widgets managed by that class.
|
| 22 |
+
|
| 23 |
+
The method ``_register`` should be called as widgets are added, and external
|
| 24 |
+
code should call ``connect`` to associate callbacks.
|
| 25 |
+
|
| 26 |
+
By default, all signals emit a DEBUG logging statement.
|
| 27 |
+
"""
|
| 28 |
+
|
| 29 |
+
# names of signals that this class may emit each of which must be
|
| 30 |
+
# set by _register for any new instance
|
| 31 |
+
signals: ClassVar[Sequence[str]] = []
|
| 32 |
+
# names of actions that this class may respond to
|
| 33 |
+
slots: ClassVar[Sequence[str]] = []
|
| 34 |
+
|
| 35 |
+
# each of which must be a method name
|
| 36 |
+
|
| 37 |
+
def __init__(self):
|
| 38 |
+
self._ignoring_events = False
|
| 39 |
+
self._sigs = {}
|
| 40 |
+
self._map = {}
|
| 41 |
+
self._setup()
|
| 42 |
+
|
| 43 |
+
def _setup(self):
|
| 44 |
+
"""Create GUI elements and register signals"""
|
| 45 |
+
self.panel = pn.pane.PaneBase()
|
| 46 |
+
# no signals to set up in the base class
|
| 47 |
+
|
| 48 |
+
def _register(
|
| 49 |
+
self, widget, name, thing="value", log_level=logging.DEBUG, auto=False
|
| 50 |
+
):
|
| 51 |
+
"""Watch the given attribute of a widget and assign it a named event
|
| 52 |
+
|
| 53 |
+
This is normally called at the time a widget is instantiated, in the
|
| 54 |
+
class which owns it.
|
| 55 |
+
|
| 56 |
+
Parameters
|
| 57 |
+
----------
|
| 58 |
+
widget : pn.layout.Panel or None
|
| 59 |
+
Widget to watch. If None, an anonymous signal not associated with
|
| 60 |
+
any widget.
|
| 61 |
+
name : str
|
| 62 |
+
Name of this event
|
| 63 |
+
thing : str
|
| 64 |
+
Attribute of the given widget to watch
|
| 65 |
+
log_level : int
|
| 66 |
+
When the signal is triggered, a logging event of the given level
|
| 67 |
+
will be fired in the dfviz logger.
|
| 68 |
+
auto : bool
|
| 69 |
+
If True, automatically connects with a method in this class of the
|
| 70 |
+
same name.
|
| 71 |
+
"""
|
| 72 |
+
if name not in self.signals:
|
| 73 |
+
raise ValueError(f"Attempt to assign an undeclared signal: {name}")
|
| 74 |
+
self._sigs[name] = {
|
| 75 |
+
"widget": widget,
|
| 76 |
+
"callbacks": [],
|
| 77 |
+
"thing": thing,
|
| 78 |
+
"log": log_level,
|
| 79 |
+
}
|
| 80 |
+
wn = "-".join(
|
| 81 |
+
[
|
| 82 |
+
getattr(widget, "name", str(widget)) if widget is not None else "none",
|
| 83 |
+
thing,
|
| 84 |
+
]
|
| 85 |
+
)
|
| 86 |
+
self._map[wn] = name
|
| 87 |
+
if widget is not None:
|
| 88 |
+
widget.param.watch(self._signal, thing, onlychanged=True)
|
| 89 |
+
if auto and hasattr(self, name):
|
| 90 |
+
self.connect(name, getattr(self, name))
|
| 91 |
+
|
| 92 |
+
def _repr_mimebundle_(self, *args, **kwargs):
|
| 93 |
+
"""Display in a notebook or a server"""
|
| 94 |
+
try:
|
| 95 |
+
return self.panel._repr_mimebundle_(*args, **kwargs)
|
| 96 |
+
except (ValueError, AttributeError):
|
| 97 |
+
raise NotImplementedError("Panel does not seem to be set " "up properly")
|
| 98 |
+
|
| 99 |
+
def connect(self, signal, slot):
|
| 100 |
+
"""Associate call back with given event
|
| 101 |
+
|
| 102 |
+
The callback must be a function which takes the "new" value of the
|
| 103 |
+
watched attribute as the only parameter. If the callback return False,
|
| 104 |
+
this cancels any further processing of the given event.
|
| 105 |
+
|
| 106 |
+
Alternatively, the callback can be a string, in which case it means
|
| 107 |
+
emitting the correspondingly-named event (i.e., connect to self)
|
| 108 |
+
"""
|
| 109 |
+
self._sigs[signal]["callbacks"].append(slot)
|
| 110 |
+
|
| 111 |
+
def _signal(self, event):
|
| 112 |
+
"""This is called by a an action on a widget
|
| 113 |
+
|
| 114 |
+
Within an self.ignore_events context, nothing happens.
|
| 115 |
+
|
| 116 |
+
Tests can execute this method by directly changing the values of
|
| 117 |
+
widget components.
|
| 118 |
+
"""
|
| 119 |
+
if not self._ignoring_events:
|
| 120 |
+
wn = "-".join([event.obj.name, event.name])
|
| 121 |
+
if wn in self._map and self._map[wn] in self._sigs:
|
| 122 |
+
self._emit(self._map[wn], event.new)
|
| 123 |
+
|
| 124 |
+
@contextlib.contextmanager
|
| 125 |
+
def ignore_events(self):
|
| 126 |
+
"""Temporarily turn off events processing in this instance
|
| 127 |
+
|
| 128 |
+
(does not propagate to children)
|
| 129 |
+
"""
|
| 130 |
+
self._ignoring_events = True
|
| 131 |
+
try:
|
| 132 |
+
yield
|
| 133 |
+
finally:
|
| 134 |
+
self._ignoring_events = False
|
| 135 |
+
|
| 136 |
+
def _emit(self, sig, value=None):
|
| 137 |
+
"""An event happened, call its callbacks
|
| 138 |
+
|
| 139 |
+
This method can be used in tests to simulate message passing without
|
| 140 |
+
directly changing visual elements.
|
| 141 |
+
|
| 142 |
+
Calling of callbacks will halt whenever one returns False.
|
| 143 |
+
"""
|
| 144 |
+
logger.log(self._sigs[sig]["log"], f"{sig}: {value}")
|
| 145 |
+
for callback in self._sigs[sig]["callbacks"]:
|
| 146 |
+
if isinstance(callback, str):
|
| 147 |
+
self._emit(callback)
|
| 148 |
+
else:
|
| 149 |
+
try:
|
| 150 |
+
# running callbacks should not break the interface
|
| 151 |
+
ret = callback(value)
|
| 152 |
+
if ret is False:
|
| 153 |
+
break
|
| 154 |
+
except Exception as e:
|
| 155 |
+
logger.exception(
|
| 156 |
+
"Exception (%s) while executing callback for signal: %s",
|
| 157 |
+
e,
|
| 158 |
+
sig,
|
| 159 |
+
)
|
| 160 |
+
|
| 161 |
+
def show(self, threads=False):
|
| 162 |
+
"""Open a new browser tab and display this instance's interface"""
|
| 163 |
+
self.panel.show(threads=threads, verbose=False)
|
| 164 |
+
return self
|
| 165 |
+
|
| 166 |
+
|
| 167 |
+
class SingleSelect(SigSlot):
|
| 168 |
+
"""A multiselect which only allows you to select one item for an event"""
|
| 169 |
+
|
| 170 |
+
signals = ["_selected", "selected"] # the first is internal
|
| 171 |
+
slots = ["set_options", "set_selection", "add", "clear", "select"]
|
| 172 |
+
|
| 173 |
+
def __init__(self, **kwargs):
|
| 174 |
+
self.kwargs = kwargs
|
| 175 |
+
super().__init__()
|
| 176 |
+
|
| 177 |
+
def _setup(self):
|
| 178 |
+
self.panel = pn.widgets.MultiSelect(**self.kwargs)
|
| 179 |
+
self._register(self.panel, "_selected", "value")
|
| 180 |
+
self._register(None, "selected")
|
| 181 |
+
self.connect("_selected", self.select_one)
|
| 182 |
+
|
| 183 |
+
def _signal(self, *args, **kwargs):
|
| 184 |
+
super()._signal(*args, **kwargs)
|
| 185 |
+
|
| 186 |
+
def select_one(self, *_):
|
| 187 |
+
with self.ignore_events():
|
| 188 |
+
val = [self.panel.value[-1]] if self.panel.value else []
|
| 189 |
+
self.panel.value = val
|
| 190 |
+
self._emit("selected", self.panel.value)
|
| 191 |
+
|
| 192 |
+
def set_options(self, options):
|
| 193 |
+
self.panel.options = options
|
| 194 |
+
|
| 195 |
+
def clear(self):
|
| 196 |
+
self.panel.options = []
|
| 197 |
+
|
| 198 |
+
@property
|
| 199 |
+
def value(self):
|
| 200 |
+
return self.panel.value
|
| 201 |
+
|
| 202 |
+
def set_selection(self, selection):
|
| 203 |
+
self.panel.value = [selection]
|
| 204 |
+
|
| 205 |
+
|
| 206 |
+
class FileSelector(SigSlot):
|
| 207 |
+
"""Panel-based graphical file selector widget
|
| 208 |
+
|
| 209 |
+
Instances of this widget are interactive and can be displayed in jupyter by having
|
| 210 |
+
them as the output of a cell, or in a separate browser tab using ``.show()``.
|
| 211 |
+
"""
|
| 212 |
+
|
| 213 |
+
signals = [
|
| 214 |
+
"protocol_changed",
|
| 215 |
+
"selection_changed",
|
| 216 |
+
"directory_entered",
|
| 217 |
+
"home_clicked",
|
| 218 |
+
"up_clicked",
|
| 219 |
+
"go_clicked",
|
| 220 |
+
"filters_changed",
|
| 221 |
+
]
|
| 222 |
+
slots = ["set_filters", "go_home"]
|
| 223 |
+
|
| 224 |
+
def __init__(self, url=None, filters=None, ignore=None, kwargs=None):
|
| 225 |
+
"""
|
| 226 |
+
|
| 227 |
+
Parameters
|
| 228 |
+
----------
|
| 229 |
+
url : str (optional)
|
| 230 |
+
Initial value of the URL to populate the dialog; should include protocol
|
| 231 |
+
filters : list(str) (optional)
|
| 232 |
+
File endings to include in the listings. If not included, all files are
|
| 233 |
+
allowed. Does not affect directories.
|
| 234 |
+
If given, the endings will appear as checkboxes in the interface
|
| 235 |
+
ignore : list(str) (optional)
|
| 236 |
+
Regex(s) of file basename patterns to ignore, e.g., "\\." for typical
|
| 237 |
+
hidden files on posix
|
| 238 |
+
kwargs : dict (optional)
|
| 239 |
+
To pass to file system instance
|
| 240 |
+
"""
|
| 241 |
+
if url:
|
| 242 |
+
self.init_protocol, url = split_protocol(url)
|
| 243 |
+
else:
|
| 244 |
+
self.init_protocol, url = "file", os.getcwd()
|
| 245 |
+
self.init_url = url
|
| 246 |
+
self.init_kwargs = (kwargs if isinstance(kwargs, str) else str(kwargs)) or "{}"
|
| 247 |
+
self.filters = filters
|
| 248 |
+
self.ignore = [re.compile(i) for i in ignore or []]
|
| 249 |
+
self._fs = None
|
| 250 |
+
super().__init__()
|
| 251 |
+
|
| 252 |
+
def _setup(self):
|
| 253 |
+
self.url = pn.widgets.TextInput(
|
| 254 |
+
name="url",
|
| 255 |
+
value=self.init_url,
|
| 256 |
+
align="end",
|
| 257 |
+
sizing_mode="stretch_width",
|
| 258 |
+
width_policy="max",
|
| 259 |
+
)
|
| 260 |
+
self.protocol = pn.widgets.Select(
|
| 261 |
+
options=sorted(known_implementations),
|
| 262 |
+
value=self.init_protocol,
|
| 263 |
+
name="protocol",
|
| 264 |
+
align="center",
|
| 265 |
+
)
|
| 266 |
+
self.kwargs = pn.widgets.TextInput(
|
| 267 |
+
name="kwargs", value=self.init_kwargs, align="center"
|
| 268 |
+
)
|
| 269 |
+
self.go = pn.widgets.Button(name="⇨", align="end", width=45)
|
| 270 |
+
self.main = SingleSelect(size=10)
|
| 271 |
+
self.home = pn.widgets.Button(name="🏠", width=40, height=30, align="end")
|
| 272 |
+
self.up = pn.widgets.Button(name="‹", width=30, height=30, align="end")
|
| 273 |
+
|
| 274 |
+
self._register(self.protocol, "protocol_changed", auto=True)
|
| 275 |
+
self._register(self.go, "go_clicked", "clicks", auto=True)
|
| 276 |
+
self._register(self.up, "up_clicked", "clicks", auto=True)
|
| 277 |
+
self._register(self.home, "home_clicked", "clicks", auto=True)
|
| 278 |
+
self._register(None, "selection_changed")
|
| 279 |
+
self.main.connect("selected", self.selection_changed)
|
| 280 |
+
self._register(None, "directory_entered")
|
| 281 |
+
self.prev_protocol = self.protocol.value
|
| 282 |
+
self.prev_kwargs = self.storage_options
|
| 283 |
+
|
| 284 |
+
self.filter_sel = pn.widgets.CheckBoxGroup(
|
| 285 |
+
value=[], options=[], inline=False, align="end", width_policy="min"
|
| 286 |
+
)
|
| 287 |
+
self._register(self.filter_sel, "filters_changed", auto=True)
|
| 288 |
+
|
| 289 |
+
self.panel = pn.Column(
|
| 290 |
+
pn.Row(self.protocol, self.kwargs),
|
| 291 |
+
pn.Row(self.home, self.up, self.url, self.go, self.filter_sel),
|
| 292 |
+
self.main.panel,
|
| 293 |
+
)
|
| 294 |
+
self.set_filters(self.filters)
|
| 295 |
+
self.go_clicked()
|
| 296 |
+
|
| 297 |
+
def set_filters(self, filters=None):
|
| 298 |
+
self.filters = filters
|
| 299 |
+
if filters:
|
| 300 |
+
self.filter_sel.options = filters
|
| 301 |
+
self.filter_sel.value = filters
|
| 302 |
+
else:
|
| 303 |
+
self.filter_sel.options = []
|
| 304 |
+
self.filter_sel.value = []
|
| 305 |
+
|
| 306 |
+
@property
|
| 307 |
+
def storage_options(self):
|
| 308 |
+
"""Value of the kwargs box as a dictionary"""
|
| 309 |
+
return ast.literal_eval(self.kwargs.value) or {}
|
| 310 |
+
|
| 311 |
+
@property
|
| 312 |
+
def fs(self):
|
| 313 |
+
"""Current filesystem instance"""
|
| 314 |
+
if self._fs is None:
|
| 315 |
+
cls = get_filesystem_class(self.protocol.value)
|
| 316 |
+
self._fs = cls(**self.storage_options)
|
| 317 |
+
return self._fs
|
| 318 |
+
|
| 319 |
+
@property
|
| 320 |
+
def urlpath(self):
|
| 321 |
+
"""URL of currently selected item"""
|
| 322 |
+
return (
|
| 323 |
+
(f"{self.protocol.value}://{self.main.value[0]}")
|
| 324 |
+
if self.main.value
|
| 325 |
+
else None
|
| 326 |
+
)
|
| 327 |
+
|
| 328 |
+
def open_file(self, mode="rb", compression=None, encoding=None):
|
| 329 |
+
"""Create OpenFile instance for the currently selected item
|
| 330 |
+
|
| 331 |
+
For example, in a notebook you might do something like
|
| 332 |
+
|
| 333 |
+
.. code-block::
|
| 334 |
+
|
| 335 |
+
[ ]: sel = FileSelector(); sel
|
| 336 |
+
|
| 337 |
+
# user selects their file
|
| 338 |
+
|
| 339 |
+
[ ]: with sel.open_file('rb') as f:
|
| 340 |
+
... out = f.read()
|
| 341 |
+
|
| 342 |
+
Parameters
|
| 343 |
+
----------
|
| 344 |
+
mode: str (optional)
|
| 345 |
+
Open mode for the file.
|
| 346 |
+
compression: str (optional)
|
| 347 |
+
The interact with the file as compressed. Set to 'infer' to guess
|
| 348 |
+
compression from the file ending
|
| 349 |
+
encoding: str (optional)
|
| 350 |
+
If using text mode, use this encoding; defaults to UTF8.
|
| 351 |
+
"""
|
| 352 |
+
if self.urlpath is None:
|
| 353 |
+
raise ValueError("No file selected")
|
| 354 |
+
return OpenFile(self.fs, self.urlpath, mode, compression, encoding)
|
| 355 |
+
|
| 356 |
+
def filters_changed(self, values):
|
| 357 |
+
self.filters = values
|
| 358 |
+
self.go_clicked()
|
| 359 |
+
|
| 360 |
+
def selection_changed(self, *_):
|
| 361 |
+
if self.urlpath is None:
|
| 362 |
+
return
|
| 363 |
+
if self.fs.isdir(self.urlpath):
|
| 364 |
+
self.url.value = self.fs._strip_protocol(self.urlpath)
|
| 365 |
+
self.go_clicked()
|
| 366 |
+
|
| 367 |
+
def go_clicked(self, *_):
|
| 368 |
+
if (
|
| 369 |
+
self.prev_protocol != self.protocol.value
|
| 370 |
+
or self.prev_kwargs != self.storage_options
|
| 371 |
+
):
|
| 372 |
+
self._fs = None # causes fs to be recreated
|
| 373 |
+
self.prev_protocol = self.protocol.value
|
| 374 |
+
self.prev_kwargs = self.storage_options
|
| 375 |
+
listing = sorted(
|
| 376 |
+
self.fs.ls(self.url.value, detail=True), key=lambda x: x["name"]
|
| 377 |
+
)
|
| 378 |
+
listing = [
|
| 379 |
+
l
|
| 380 |
+
for l in listing
|
| 381 |
+
if not any(i.match(l["name"].rsplit("/", 1)[-1]) for i in self.ignore)
|
| 382 |
+
]
|
| 383 |
+
folders = {
|
| 384 |
+
"📁 " + o["name"].rsplit("/", 1)[-1]: o["name"]
|
| 385 |
+
for o in listing
|
| 386 |
+
if o["type"] == "directory"
|
| 387 |
+
}
|
| 388 |
+
files = {
|
| 389 |
+
"📄 " + o["name"].rsplit("/", 1)[-1]: o["name"]
|
| 390 |
+
for o in listing
|
| 391 |
+
if o["type"] == "file"
|
| 392 |
+
}
|
| 393 |
+
if self.filters:
|
| 394 |
+
files = {
|
| 395 |
+
k: v
|
| 396 |
+
for k, v in files.items()
|
| 397 |
+
if any(v.endswith(ext) for ext in self.filters)
|
| 398 |
+
}
|
| 399 |
+
self.main.set_options(dict(**folders, **files))
|
| 400 |
+
|
| 401 |
+
def protocol_changed(self, *_):
|
| 402 |
+
self._fs = None
|
| 403 |
+
self.main.options = []
|
| 404 |
+
self.url.value = ""
|
| 405 |
+
|
| 406 |
+
def home_clicked(self, *_):
|
| 407 |
+
self.protocol.value = self.init_protocol
|
| 408 |
+
self.kwargs.value = self.init_kwargs
|
| 409 |
+
self.url.value = self.init_url
|
| 410 |
+
self.go_clicked()
|
| 411 |
+
|
| 412 |
+
def up_clicked(self, *_):
|
| 413 |
+
self.url.value = self.fs._parent(self.url.value)
|
| 414 |
+
self.go_clicked()
|
tuning-competition-baseline/.venv/lib/python3.11/site-packages/fsspec/parquet.py
ADDED
|
@@ -0,0 +1,549 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import io
|
| 2 |
+
import json
|
| 3 |
+
import warnings
|
| 4 |
+
|
| 5 |
+
from .core import url_to_fs
|
| 6 |
+
from .utils import merge_offset_ranges
|
| 7 |
+
|
| 8 |
+
# Parquet-Specific Utilities for fsspec
|
| 9 |
+
#
|
| 10 |
+
# Most of the functions defined in this module are NOT
|
| 11 |
+
# intended for public consumption. The only exception
|
| 12 |
+
# to this is `open_parquet_file`, which should be used
|
| 13 |
+
# place of `fs.open()` to open parquet-formatted files
|
| 14 |
+
# on remote file systems.
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
def open_parquet_file(
|
| 18 |
+
path,
|
| 19 |
+
mode="rb",
|
| 20 |
+
fs=None,
|
| 21 |
+
metadata=None,
|
| 22 |
+
columns=None,
|
| 23 |
+
row_groups=None,
|
| 24 |
+
storage_options=None,
|
| 25 |
+
strict=False,
|
| 26 |
+
engine="auto",
|
| 27 |
+
max_gap=64_000,
|
| 28 |
+
max_block=256_000_000,
|
| 29 |
+
footer_sample_size=1_000_000,
|
| 30 |
+
**kwargs,
|
| 31 |
+
):
|
| 32 |
+
"""
|
| 33 |
+
Return a file-like object for a single Parquet file.
|
| 34 |
+
|
| 35 |
+
The specified parquet `engine` will be used to parse the
|
| 36 |
+
footer metadata, and determine the required byte ranges
|
| 37 |
+
from the file. The target path will then be opened with
|
| 38 |
+
the "parts" (`KnownPartsOfAFile`) caching strategy.
|
| 39 |
+
|
| 40 |
+
Note that this method is intended for usage with remote
|
| 41 |
+
file systems, and is unlikely to improve parquet-read
|
| 42 |
+
performance on local file systems.
|
| 43 |
+
|
| 44 |
+
Parameters
|
| 45 |
+
----------
|
| 46 |
+
path: str
|
| 47 |
+
Target file path.
|
| 48 |
+
mode: str, optional
|
| 49 |
+
Mode option to be passed through to `fs.open`. Default is "rb".
|
| 50 |
+
metadata: Any, optional
|
| 51 |
+
Parquet metadata object. Object type must be supported
|
| 52 |
+
by the backend parquet engine. For now, only the "fastparquet"
|
| 53 |
+
engine supports an explicit `ParquetFile` metadata object.
|
| 54 |
+
If a metadata object is supplied, the remote footer metadata
|
| 55 |
+
will not need to be transferred into local memory.
|
| 56 |
+
fs: AbstractFileSystem, optional
|
| 57 |
+
Filesystem object to use for opening the file. If nothing is
|
| 58 |
+
specified, an `AbstractFileSystem` object will be inferred.
|
| 59 |
+
engine : str, default "auto"
|
| 60 |
+
Parquet engine to use for metadata parsing. Allowed options
|
| 61 |
+
include "fastparquet", "pyarrow", and "auto". The specified
|
| 62 |
+
engine must be installed in the current environment. If
|
| 63 |
+
"auto" is specified, and both engines are installed,
|
| 64 |
+
"fastparquet" will take precedence over "pyarrow".
|
| 65 |
+
columns: list, optional
|
| 66 |
+
List of all column names that may be read from the file.
|
| 67 |
+
row_groups : list, optional
|
| 68 |
+
List of all row-groups that may be read from the file. This
|
| 69 |
+
may be a list of row-group indices (integers), or it may be
|
| 70 |
+
a list of `RowGroup` metadata objects (if the "fastparquet"
|
| 71 |
+
engine is used).
|
| 72 |
+
storage_options : dict, optional
|
| 73 |
+
Used to generate an `AbstractFileSystem` object if `fs` was
|
| 74 |
+
not specified.
|
| 75 |
+
strict : bool, optional
|
| 76 |
+
Whether the resulting `KnownPartsOfAFile` cache should
|
| 77 |
+
fetch reads that go beyond a known byte-range boundary.
|
| 78 |
+
If `False` (the default), any read that ends outside a
|
| 79 |
+
known part will be zero padded. Note that using
|
| 80 |
+
`strict=True` may be useful for debugging.
|
| 81 |
+
max_gap : int, optional
|
| 82 |
+
Neighboring byte ranges will only be merged when their
|
| 83 |
+
inter-range gap is <= `max_gap`. Default is 64KB.
|
| 84 |
+
max_block : int, optional
|
| 85 |
+
Neighboring byte ranges will only be merged when the size of
|
| 86 |
+
the aggregated range is <= `max_block`. Default is 256MB.
|
| 87 |
+
footer_sample_size : int, optional
|
| 88 |
+
Number of bytes to read from the end of the path to look
|
| 89 |
+
for the footer metadata. If the sampled bytes do not contain
|
| 90 |
+
the footer, a second read request will be required, and
|
| 91 |
+
performance will suffer. Default is 1MB.
|
| 92 |
+
**kwargs :
|
| 93 |
+
Optional key-word arguments to pass to `fs.open`
|
| 94 |
+
"""
|
| 95 |
+
|
| 96 |
+
# Make sure we have an `AbstractFileSystem` object
|
| 97 |
+
# to work with
|
| 98 |
+
if fs is None:
|
| 99 |
+
fs = url_to_fs(path, **(storage_options or {}))[0]
|
| 100 |
+
|
| 101 |
+
# For now, `columns == []` not supported. Just use
|
| 102 |
+
# default `open` command with `path` input
|
| 103 |
+
if columns is not None and len(columns) == 0:
|
| 104 |
+
return fs.open(path, mode=mode)
|
| 105 |
+
|
| 106 |
+
# Set the engine
|
| 107 |
+
engine = _set_engine(engine)
|
| 108 |
+
|
| 109 |
+
# Fetch the known byte ranges needed to read
|
| 110 |
+
# `columns` and/or `row_groups`
|
| 111 |
+
data = _get_parquet_byte_ranges(
|
| 112 |
+
[path],
|
| 113 |
+
fs,
|
| 114 |
+
metadata=metadata,
|
| 115 |
+
columns=columns,
|
| 116 |
+
row_groups=row_groups,
|
| 117 |
+
engine=engine,
|
| 118 |
+
max_gap=max_gap,
|
| 119 |
+
max_block=max_block,
|
| 120 |
+
footer_sample_size=footer_sample_size,
|
| 121 |
+
)
|
| 122 |
+
|
| 123 |
+
# Extract file name from `data`
|
| 124 |
+
fn = next(iter(data)) if data else path
|
| 125 |
+
|
| 126 |
+
# Call self.open with "parts" caching
|
| 127 |
+
options = kwargs.pop("cache_options", {}).copy()
|
| 128 |
+
return fs.open(
|
| 129 |
+
fn,
|
| 130 |
+
mode=mode,
|
| 131 |
+
cache_type="parts",
|
| 132 |
+
cache_options={
|
| 133 |
+
**options,
|
| 134 |
+
"data": data.get(fn, {}),
|
| 135 |
+
"strict": strict,
|
| 136 |
+
},
|
| 137 |
+
**kwargs,
|
| 138 |
+
)
|
| 139 |
+
|
| 140 |
+
|
| 141 |
+
def _get_parquet_byte_ranges(
|
| 142 |
+
paths,
|
| 143 |
+
fs,
|
| 144 |
+
metadata=None,
|
| 145 |
+
columns=None,
|
| 146 |
+
row_groups=None,
|
| 147 |
+
max_gap=64_000,
|
| 148 |
+
max_block=256_000_000,
|
| 149 |
+
footer_sample_size=1_000_000,
|
| 150 |
+
engine="auto",
|
| 151 |
+
):
|
| 152 |
+
"""Get a dictionary of the known byte ranges needed
|
| 153 |
+
to read a specific column/row-group selection from a
|
| 154 |
+
Parquet dataset. Each value in the output dictionary
|
| 155 |
+
is intended for use as the `data` argument for the
|
| 156 |
+
`KnownPartsOfAFile` caching strategy of a single path.
|
| 157 |
+
"""
|
| 158 |
+
|
| 159 |
+
# Set engine if necessary
|
| 160 |
+
if isinstance(engine, str):
|
| 161 |
+
engine = _set_engine(engine)
|
| 162 |
+
|
| 163 |
+
# Pass to specialized function if metadata is defined
|
| 164 |
+
if metadata is not None:
|
| 165 |
+
|
| 166 |
+
# Use the provided parquet metadata object
|
| 167 |
+
# to avoid transferring/parsing footer metadata
|
| 168 |
+
return _get_parquet_byte_ranges_from_metadata(
|
| 169 |
+
metadata,
|
| 170 |
+
fs,
|
| 171 |
+
engine,
|
| 172 |
+
columns=columns,
|
| 173 |
+
row_groups=row_groups,
|
| 174 |
+
max_gap=max_gap,
|
| 175 |
+
max_block=max_block,
|
| 176 |
+
)
|
| 177 |
+
|
| 178 |
+
# Get file sizes asynchronously
|
| 179 |
+
file_sizes = fs.sizes(paths)
|
| 180 |
+
|
| 181 |
+
# Populate global paths, starts, & ends
|
| 182 |
+
result = {}
|
| 183 |
+
data_paths = []
|
| 184 |
+
data_starts = []
|
| 185 |
+
data_ends = []
|
| 186 |
+
add_header_magic = True
|
| 187 |
+
if columns is None and row_groups is None:
|
| 188 |
+
# We are NOT selecting specific columns or row-groups.
|
| 189 |
+
#
|
| 190 |
+
# We can avoid sampling the footers, and just transfer
|
| 191 |
+
# all file data with cat_ranges
|
| 192 |
+
for i, path in enumerate(paths):
|
| 193 |
+
result[path] = {}
|
| 194 |
+
for b in range(0, file_sizes[i], max_block):
|
| 195 |
+
data_paths.append(path)
|
| 196 |
+
data_starts.append(b)
|
| 197 |
+
data_ends.append(min(b + max_block, file_sizes[i]))
|
| 198 |
+
add_header_magic = False # "Magic" should already be included
|
| 199 |
+
else:
|
| 200 |
+
# We ARE selecting specific columns or row-groups.
|
| 201 |
+
#
|
| 202 |
+
# Gather file footers.
|
| 203 |
+
# We just take the last `footer_sample_size` bytes of each
|
| 204 |
+
# file (or the entire file if it is smaller than that)
|
| 205 |
+
footer_starts = []
|
| 206 |
+
footer_ends = []
|
| 207 |
+
for i, path in enumerate(paths):
|
| 208 |
+
footer_ends.append(file_sizes[i])
|
| 209 |
+
sample_size = max(0, file_sizes[i] - footer_sample_size)
|
| 210 |
+
footer_starts.append(sample_size)
|
| 211 |
+
footer_samples = fs.cat_ranges(paths, footer_starts, footer_ends)
|
| 212 |
+
|
| 213 |
+
# Check our footer samples and re-sample if necessary.
|
| 214 |
+
missing_footer_starts = footer_starts.copy()
|
| 215 |
+
large_footer = 0
|
| 216 |
+
for i, path in enumerate(paths):
|
| 217 |
+
footer_size = int.from_bytes(footer_samples[i][-8:-4], "little")
|
| 218 |
+
real_footer_start = file_sizes[i] - (footer_size + 8)
|
| 219 |
+
if real_footer_start < footer_starts[i]:
|
| 220 |
+
missing_footer_starts[i] = real_footer_start
|
| 221 |
+
large_footer = max(large_footer, (footer_size + 8))
|
| 222 |
+
if large_footer:
|
| 223 |
+
warnings.warn(
|
| 224 |
+
f"Not enough data was used to sample the parquet footer. "
|
| 225 |
+
f"Try setting footer_sample_size >= {large_footer}."
|
| 226 |
+
)
|
| 227 |
+
for i, block in enumerate(
|
| 228 |
+
fs.cat_ranges(
|
| 229 |
+
paths,
|
| 230 |
+
missing_footer_starts,
|
| 231 |
+
footer_starts,
|
| 232 |
+
)
|
| 233 |
+
):
|
| 234 |
+
footer_samples[i] = block + footer_samples[i]
|
| 235 |
+
footer_starts[i] = missing_footer_starts[i]
|
| 236 |
+
|
| 237 |
+
# Calculate required byte ranges for each path
|
| 238 |
+
for i, path in enumerate(paths):
|
| 239 |
+
|
| 240 |
+
# Deal with small-file case.
|
| 241 |
+
# Just include all remaining bytes of the file
|
| 242 |
+
# in a single range.
|
| 243 |
+
if file_sizes[i] < max_block:
|
| 244 |
+
if footer_starts[i] > 0:
|
| 245 |
+
# Only need to transfer the data if the
|
| 246 |
+
# footer sample isn't already the whole file
|
| 247 |
+
data_paths.append(path)
|
| 248 |
+
data_starts.append(0)
|
| 249 |
+
data_ends.append(footer_starts[i])
|
| 250 |
+
continue
|
| 251 |
+
|
| 252 |
+
# Use "engine" to collect data byte ranges
|
| 253 |
+
path_data_starts, path_data_ends = engine._parquet_byte_ranges(
|
| 254 |
+
columns,
|
| 255 |
+
row_groups=row_groups,
|
| 256 |
+
footer=footer_samples[i],
|
| 257 |
+
footer_start=footer_starts[i],
|
| 258 |
+
)
|
| 259 |
+
|
| 260 |
+
data_paths += [path] * len(path_data_starts)
|
| 261 |
+
data_starts += path_data_starts
|
| 262 |
+
data_ends += path_data_ends
|
| 263 |
+
|
| 264 |
+
# Merge adjacent offset ranges
|
| 265 |
+
data_paths, data_starts, data_ends = merge_offset_ranges(
|
| 266 |
+
data_paths,
|
| 267 |
+
data_starts,
|
| 268 |
+
data_ends,
|
| 269 |
+
max_gap=max_gap,
|
| 270 |
+
max_block=max_block,
|
| 271 |
+
sort=False, # Should already be sorted
|
| 272 |
+
)
|
| 273 |
+
|
| 274 |
+
# Start by populating `result` with footer samples
|
| 275 |
+
for i, path in enumerate(paths):
|
| 276 |
+
result[path] = {(footer_starts[i], footer_ends[i]): footer_samples[i]}
|
| 277 |
+
|
| 278 |
+
# Transfer the data byte-ranges into local memory
|
| 279 |
+
_transfer_ranges(fs, result, data_paths, data_starts, data_ends)
|
| 280 |
+
|
| 281 |
+
# Add b"PAR1" to header if necessary
|
| 282 |
+
if add_header_magic:
|
| 283 |
+
_add_header_magic(result)
|
| 284 |
+
|
| 285 |
+
return result
|
| 286 |
+
|
| 287 |
+
|
| 288 |
+
def _get_parquet_byte_ranges_from_metadata(
|
| 289 |
+
metadata,
|
| 290 |
+
fs,
|
| 291 |
+
engine,
|
| 292 |
+
columns=None,
|
| 293 |
+
row_groups=None,
|
| 294 |
+
max_gap=64_000,
|
| 295 |
+
max_block=256_000_000,
|
| 296 |
+
):
|
| 297 |
+
"""Simplified version of `_get_parquet_byte_ranges` for
|
| 298 |
+
the case that an engine-specific `metadata` object is
|
| 299 |
+
provided, and the remote footer metadata does not need to
|
| 300 |
+
be transferred before calculating the required byte ranges.
|
| 301 |
+
"""
|
| 302 |
+
|
| 303 |
+
# Use "engine" to collect data byte ranges
|
| 304 |
+
data_paths, data_starts, data_ends = engine._parquet_byte_ranges(
|
| 305 |
+
columns,
|
| 306 |
+
row_groups=row_groups,
|
| 307 |
+
metadata=metadata,
|
| 308 |
+
)
|
| 309 |
+
|
| 310 |
+
# Merge adjacent offset ranges
|
| 311 |
+
data_paths, data_starts, data_ends = merge_offset_ranges(
|
| 312 |
+
data_paths,
|
| 313 |
+
data_starts,
|
| 314 |
+
data_ends,
|
| 315 |
+
max_gap=max_gap,
|
| 316 |
+
max_block=max_block,
|
| 317 |
+
sort=False, # Should be sorted
|
| 318 |
+
)
|
| 319 |
+
|
| 320 |
+
# Transfer the data byte-ranges into local memory
|
| 321 |
+
result = {fn: {} for fn in list(set(data_paths))}
|
| 322 |
+
_transfer_ranges(fs, result, data_paths, data_starts, data_ends)
|
| 323 |
+
|
| 324 |
+
# Add b"PAR1" to header
|
| 325 |
+
_add_header_magic(result)
|
| 326 |
+
|
| 327 |
+
return result
|
| 328 |
+
|
| 329 |
+
|
| 330 |
+
def _transfer_ranges(fs, blocks, paths, starts, ends):
|
| 331 |
+
# Use cat_ranges to gather the data byte_ranges
|
| 332 |
+
ranges = (paths, starts, ends)
|
| 333 |
+
for path, start, stop, data in zip(*ranges, fs.cat_ranges(*ranges)):
|
| 334 |
+
blocks[path][(start, stop)] = data
|
| 335 |
+
|
| 336 |
+
|
| 337 |
+
def _add_header_magic(data):
|
| 338 |
+
# Add b"PAR1" to file headers
|
| 339 |
+
for path in list(data.keys()):
|
| 340 |
+
add_magic = True
|
| 341 |
+
for k in data[path].keys():
|
| 342 |
+
if k[0] == 0 and k[1] >= 4:
|
| 343 |
+
add_magic = False
|
| 344 |
+
break
|
| 345 |
+
if add_magic:
|
| 346 |
+
data[path][(0, 4)] = b"PAR1"
|
| 347 |
+
|
| 348 |
+
|
| 349 |
+
def _set_engine(engine_str):
|
| 350 |
+
|
| 351 |
+
# Define a list of parquet engines to try
|
| 352 |
+
if engine_str == "auto":
|
| 353 |
+
try_engines = ("fastparquet", "pyarrow")
|
| 354 |
+
elif not isinstance(engine_str, str):
|
| 355 |
+
raise ValueError(
|
| 356 |
+
"Failed to set parquet engine! "
|
| 357 |
+
"Please pass 'fastparquet', 'pyarrow', or 'auto'"
|
| 358 |
+
)
|
| 359 |
+
elif engine_str not in ("fastparquet", "pyarrow"):
|
| 360 |
+
raise ValueError(f"{engine_str} engine not supported by `fsspec.parquet`")
|
| 361 |
+
else:
|
| 362 |
+
try_engines = [engine_str]
|
| 363 |
+
|
| 364 |
+
# Try importing the engines in `try_engines`,
|
| 365 |
+
# and choose the first one that succeeds
|
| 366 |
+
for engine in try_engines:
|
| 367 |
+
try:
|
| 368 |
+
if engine == "fastparquet":
|
| 369 |
+
return FastparquetEngine()
|
| 370 |
+
elif engine == "pyarrow":
|
| 371 |
+
return PyarrowEngine()
|
| 372 |
+
except ImportError:
|
| 373 |
+
pass
|
| 374 |
+
|
| 375 |
+
# Raise an error if a supported parquet engine
|
| 376 |
+
# was not found
|
| 377 |
+
raise ImportError(
|
| 378 |
+
f"The following parquet engines are not installed "
|
| 379 |
+
f"in your python environment: {try_engines}."
|
| 380 |
+
f"Please install 'fastparquert' or 'pyarrow' to "
|
| 381 |
+
f"utilize the `fsspec.parquet` module."
|
| 382 |
+
)
|
| 383 |
+
|
| 384 |
+
|
| 385 |
+
class FastparquetEngine:
|
| 386 |
+
|
| 387 |
+
# The purpose of the FastparquetEngine class is
|
| 388 |
+
# to check if fastparquet can be imported (on initialization)
|
| 389 |
+
# and to define a `_parquet_byte_ranges` method. In the
|
| 390 |
+
# future, this class may also be used to define other
|
| 391 |
+
# methods/logic that are specific to fastparquet.
|
| 392 |
+
|
| 393 |
+
def __init__(self):
|
| 394 |
+
import fastparquet as fp
|
| 395 |
+
|
| 396 |
+
self.fp = fp
|
| 397 |
+
|
| 398 |
+
def _row_group_filename(self, row_group, pf):
|
| 399 |
+
return pf.row_group_filename(row_group)
|
| 400 |
+
|
| 401 |
+
def _parquet_byte_ranges(
|
| 402 |
+
self,
|
| 403 |
+
columns,
|
| 404 |
+
row_groups=None,
|
| 405 |
+
metadata=None,
|
| 406 |
+
footer=None,
|
| 407 |
+
footer_start=None,
|
| 408 |
+
):
|
| 409 |
+
|
| 410 |
+
# Initialize offset ranges and define ParqetFile metadata
|
| 411 |
+
pf = metadata
|
| 412 |
+
data_paths, data_starts, data_ends = [], [], []
|
| 413 |
+
if pf is None:
|
| 414 |
+
pf = self.fp.ParquetFile(io.BytesIO(footer))
|
| 415 |
+
|
| 416 |
+
# Convert columns to a set and add any index columns
|
| 417 |
+
# specified in the pandas metadata (just in case)
|
| 418 |
+
column_set = None if columns is None else set(columns)
|
| 419 |
+
if column_set is not None and hasattr(pf, "pandas_metadata"):
|
| 420 |
+
md_index = [
|
| 421 |
+
ind
|
| 422 |
+
for ind in pf.pandas_metadata.get("index_columns", [])
|
| 423 |
+
# Ignore RangeIndex information
|
| 424 |
+
if not isinstance(ind, dict)
|
| 425 |
+
]
|
| 426 |
+
column_set |= set(md_index)
|
| 427 |
+
|
| 428 |
+
# Check if row_groups is a list of integers
|
| 429 |
+
# or a list of row-group metadata
|
| 430 |
+
if row_groups and not isinstance(row_groups[0], int):
|
| 431 |
+
# Input row_groups contains row-group metadata
|
| 432 |
+
row_group_indices = None
|
| 433 |
+
else:
|
| 434 |
+
# Input row_groups contains row-group indices
|
| 435 |
+
row_group_indices = row_groups
|
| 436 |
+
row_groups = pf.row_groups
|
| 437 |
+
|
| 438 |
+
# Loop through column chunks to add required byte ranges
|
| 439 |
+
for r, row_group in enumerate(row_groups):
|
| 440 |
+
# Skip this row-group if we are targeting
|
| 441 |
+
# specific row-groups
|
| 442 |
+
if row_group_indices is None or r in row_group_indices:
|
| 443 |
+
|
| 444 |
+
# Find the target parquet-file path for `row_group`
|
| 445 |
+
fn = self._row_group_filename(row_group, pf)
|
| 446 |
+
|
| 447 |
+
for column in row_group.columns:
|
| 448 |
+
name = column.meta_data.path_in_schema[0]
|
| 449 |
+
# Skip this column if we are targeting a
|
| 450 |
+
# specific columns
|
| 451 |
+
if column_set is None or name in column_set:
|
| 452 |
+
file_offset0 = column.meta_data.dictionary_page_offset
|
| 453 |
+
if file_offset0 is None:
|
| 454 |
+
file_offset0 = column.meta_data.data_page_offset
|
| 455 |
+
num_bytes = column.meta_data.total_compressed_size
|
| 456 |
+
if footer_start is None or file_offset0 < footer_start:
|
| 457 |
+
data_paths.append(fn)
|
| 458 |
+
data_starts.append(file_offset0)
|
| 459 |
+
data_ends.append(
|
| 460 |
+
min(
|
| 461 |
+
file_offset0 + num_bytes,
|
| 462 |
+
footer_start or (file_offset0 + num_bytes),
|
| 463 |
+
)
|
| 464 |
+
)
|
| 465 |
+
|
| 466 |
+
if metadata:
|
| 467 |
+
# The metadata in this call may map to multiple
|
| 468 |
+
# file paths. Need to include `data_paths`
|
| 469 |
+
return data_paths, data_starts, data_ends
|
| 470 |
+
return data_starts, data_ends
|
| 471 |
+
|
| 472 |
+
|
| 473 |
+
class PyarrowEngine:
|
| 474 |
+
|
| 475 |
+
# The purpose of the PyarrowEngine class is
|
| 476 |
+
# to check if pyarrow can be imported (on initialization)
|
| 477 |
+
# and to define a `_parquet_byte_ranges` method. In the
|
| 478 |
+
# future, this class may also be used to define other
|
| 479 |
+
# methods/logic that are specific to pyarrow.
|
| 480 |
+
|
| 481 |
+
def __init__(self):
|
| 482 |
+
import pyarrow.parquet as pq
|
| 483 |
+
|
| 484 |
+
self.pq = pq
|
| 485 |
+
|
| 486 |
+
def _row_group_filename(self, row_group, metadata):
|
| 487 |
+
raise NotImplementedError
|
| 488 |
+
|
| 489 |
+
def _parquet_byte_ranges(
|
| 490 |
+
self,
|
| 491 |
+
columns,
|
| 492 |
+
row_groups=None,
|
| 493 |
+
metadata=None,
|
| 494 |
+
footer=None,
|
| 495 |
+
footer_start=None,
|
| 496 |
+
):
|
| 497 |
+
|
| 498 |
+
if metadata is not None:
|
| 499 |
+
raise ValueError("metadata input not supported for PyarrowEngine")
|
| 500 |
+
|
| 501 |
+
data_starts, data_ends = [], []
|
| 502 |
+
md = self.pq.ParquetFile(io.BytesIO(footer)).metadata
|
| 503 |
+
|
| 504 |
+
# Convert columns to a set and add any index columns
|
| 505 |
+
# specified in the pandas metadata (just in case)
|
| 506 |
+
column_set = None if columns is None else set(columns)
|
| 507 |
+
if column_set is not None:
|
| 508 |
+
schema = md.schema.to_arrow_schema()
|
| 509 |
+
has_pandas_metadata = (
|
| 510 |
+
schema.metadata is not None and b"pandas" in schema.metadata
|
| 511 |
+
)
|
| 512 |
+
if has_pandas_metadata:
|
| 513 |
+
md_index = [
|
| 514 |
+
ind
|
| 515 |
+
for ind in json.loads(
|
| 516 |
+
schema.metadata[b"pandas"].decode("utf8")
|
| 517 |
+
).get("index_columns", [])
|
| 518 |
+
# Ignore RangeIndex information
|
| 519 |
+
if not isinstance(ind, dict)
|
| 520 |
+
]
|
| 521 |
+
column_set |= set(md_index)
|
| 522 |
+
|
| 523 |
+
# Loop through column chunks to add required byte ranges
|
| 524 |
+
for r in range(md.num_row_groups):
|
| 525 |
+
# Skip this row-group if we are targeting
|
| 526 |
+
# specific row-groups
|
| 527 |
+
if row_groups is None or r in row_groups:
|
| 528 |
+
row_group = md.row_group(r)
|
| 529 |
+
for c in range(row_group.num_columns):
|
| 530 |
+
column = row_group.column(c)
|
| 531 |
+
name = column.path_in_schema
|
| 532 |
+
# Skip this column if we are targeting a
|
| 533 |
+
# specific columns
|
| 534 |
+
split_name = name.split(".")[0]
|
| 535 |
+
if (
|
| 536 |
+
column_set is None
|
| 537 |
+
or name in column_set
|
| 538 |
+
or split_name in column_set
|
| 539 |
+
):
|
| 540 |
+
file_offset0 = column.dictionary_page_offset
|
| 541 |
+
if file_offset0 is None:
|
| 542 |
+
file_offset0 = column.data_page_offset
|
| 543 |
+
num_bytes = column.total_compressed_size
|
| 544 |
+
if file_offset0 < footer_start:
|
| 545 |
+
data_starts.append(file_offset0)
|
| 546 |
+
data_ends.append(
|
| 547 |
+
min(file_offset0 + num_bytes, footer_start)
|
| 548 |
+
)
|
| 549 |
+
return data_starts, data_ends
|
tuning-competition-baseline/.venv/lib/python3.11/site-packages/fsspec/spec.py
ADDED
|
@@ -0,0 +1,1965 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
import io
|
| 4 |
+
import logging
|
| 5 |
+
import os
|
| 6 |
+
import threading
|
| 7 |
+
import warnings
|
| 8 |
+
import weakref
|
| 9 |
+
from errno import ESPIPE
|
| 10 |
+
from glob import has_magic
|
| 11 |
+
from hashlib import sha256
|
| 12 |
+
from typing import ClassVar
|
| 13 |
+
|
| 14 |
+
from .callbacks import DEFAULT_CALLBACK
|
| 15 |
+
from .config import apply_config, conf
|
| 16 |
+
from .dircache import DirCache
|
| 17 |
+
from .transaction import Transaction
|
| 18 |
+
from .utils import (
|
| 19 |
+
_unstrip_protocol,
|
| 20 |
+
glob_translate,
|
| 21 |
+
isfilelike,
|
| 22 |
+
other_paths,
|
| 23 |
+
read_block,
|
| 24 |
+
stringify_path,
|
| 25 |
+
tokenize,
|
| 26 |
+
)
|
| 27 |
+
|
| 28 |
+
logger = logging.getLogger("fsspec")
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
def make_instance(cls, args, kwargs):
|
| 32 |
+
return cls(*args, **kwargs)
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
class _Cached(type):
|
| 36 |
+
"""
|
| 37 |
+
Metaclass for caching file system instances.
|
| 38 |
+
|
| 39 |
+
Notes
|
| 40 |
+
-----
|
| 41 |
+
Instances are cached according to
|
| 42 |
+
|
| 43 |
+
* The values of the class attributes listed in `_extra_tokenize_attributes`
|
| 44 |
+
* The arguments passed to ``__init__``.
|
| 45 |
+
|
| 46 |
+
This creates an additional reference to the filesystem, which prevents the
|
| 47 |
+
filesystem from being garbage collected when all *user* references go away.
|
| 48 |
+
A call to the :meth:`AbstractFileSystem.clear_instance_cache` must *also*
|
| 49 |
+
be made for a filesystem instance to be garbage collected.
|
| 50 |
+
"""
|
| 51 |
+
|
| 52 |
+
def __init__(cls, *args, **kwargs):
|
| 53 |
+
super().__init__(*args, **kwargs)
|
| 54 |
+
# Note: we intentionally create a reference here, to avoid garbage
|
| 55 |
+
# collecting instances when all other references are gone. To really
|
| 56 |
+
# delete a FileSystem, the cache must be cleared.
|
| 57 |
+
if conf.get("weakref_instance_cache"): # pragma: no cover
|
| 58 |
+
# debug option for analysing fork/spawn conditions
|
| 59 |
+
cls._cache = weakref.WeakValueDictionary()
|
| 60 |
+
else:
|
| 61 |
+
cls._cache = {}
|
| 62 |
+
cls._pid = os.getpid()
|
| 63 |
+
|
| 64 |
+
def __call__(cls, *args, **kwargs):
|
| 65 |
+
kwargs = apply_config(cls, kwargs)
|
| 66 |
+
extra_tokens = tuple(
|
| 67 |
+
getattr(cls, attr, None) for attr in cls._extra_tokenize_attributes
|
| 68 |
+
)
|
| 69 |
+
token = tokenize(
|
| 70 |
+
cls, cls._pid, threading.get_ident(), *args, *extra_tokens, **kwargs
|
| 71 |
+
)
|
| 72 |
+
skip = kwargs.pop("skip_instance_cache", False)
|
| 73 |
+
if os.getpid() != cls._pid:
|
| 74 |
+
cls._cache.clear()
|
| 75 |
+
cls._pid = os.getpid()
|
| 76 |
+
if not skip and cls.cachable and token in cls._cache:
|
| 77 |
+
cls._latest = token
|
| 78 |
+
return cls._cache[token]
|
| 79 |
+
else:
|
| 80 |
+
obj = super().__call__(*args, **kwargs)
|
| 81 |
+
# Setting _fs_token here causes some static linters to complain.
|
| 82 |
+
obj._fs_token_ = token
|
| 83 |
+
obj.storage_args = args
|
| 84 |
+
obj.storage_options = kwargs
|
| 85 |
+
if obj.async_impl and obj.mirror_sync_methods:
|
| 86 |
+
from .asyn import mirror_sync_methods
|
| 87 |
+
|
| 88 |
+
mirror_sync_methods(obj)
|
| 89 |
+
|
| 90 |
+
if cls.cachable and not skip:
|
| 91 |
+
cls._latest = token
|
| 92 |
+
cls._cache[token] = obj
|
| 93 |
+
return obj
|
| 94 |
+
|
| 95 |
+
|
| 96 |
+
class AbstractFileSystem(metaclass=_Cached):
|
| 97 |
+
"""
|
| 98 |
+
An abstract super-class for pythonic file-systems
|
| 99 |
+
|
| 100 |
+
Implementations are expected to be compatible with or, better, subclass
|
| 101 |
+
from here.
|
| 102 |
+
"""
|
| 103 |
+
|
| 104 |
+
cachable = True # this class can be cached, instances reused
|
| 105 |
+
_cached = False
|
| 106 |
+
blocksize = 2**22
|
| 107 |
+
sep = "/"
|
| 108 |
+
protocol: ClassVar[str | tuple[str, ...]] = "abstract"
|
| 109 |
+
_latest = None
|
| 110 |
+
async_impl = False
|
| 111 |
+
mirror_sync_methods = False
|
| 112 |
+
root_marker = "" # For some FSs, may require leading '/' or other character
|
| 113 |
+
transaction_type = Transaction
|
| 114 |
+
|
| 115 |
+
#: Extra *class attributes* that should be considered when hashing.
|
| 116 |
+
_extra_tokenize_attributes = ()
|
| 117 |
+
|
| 118 |
+
def __init__(self, *args, **storage_options):
|
| 119 |
+
"""Create and configure file-system instance
|
| 120 |
+
|
| 121 |
+
Instances may be cachable, so if similar enough arguments are seen
|
| 122 |
+
a new instance is not required. The token attribute exists to allow
|
| 123 |
+
implementations to cache instances if they wish.
|
| 124 |
+
|
| 125 |
+
A reasonable default should be provided if there are no arguments.
|
| 126 |
+
|
| 127 |
+
Subclasses should call this method.
|
| 128 |
+
|
| 129 |
+
Parameters
|
| 130 |
+
----------
|
| 131 |
+
use_listings_cache, listings_expiry_time, max_paths:
|
| 132 |
+
passed to ``DirCache``, if the implementation supports
|
| 133 |
+
directory listing caching. Pass use_listings_cache=False
|
| 134 |
+
to disable such caching.
|
| 135 |
+
skip_instance_cache: bool
|
| 136 |
+
If this is a cachable implementation, pass True here to force
|
| 137 |
+
creating a new instance even if a matching instance exists, and prevent
|
| 138 |
+
storing this instance.
|
| 139 |
+
asynchronous: bool
|
| 140 |
+
loop: asyncio-compatible IOLoop or None
|
| 141 |
+
"""
|
| 142 |
+
if self._cached:
|
| 143 |
+
# reusing instance, don't change
|
| 144 |
+
return
|
| 145 |
+
self._cached = True
|
| 146 |
+
self._intrans = False
|
| 147 |
+
self._transaction = None
|
| 148 |
+
self._invalidated_caches_in_transaction = []
|
| 149 |
+
self.dircache = DirCache(**storage_options)
|
| 150 |
+
|
| 151 |
+
if storage_options.pop("add_docs", None):
|
| 152 |
+
warnings.warn("add_docs is no longer supported.", FutureWarning)
|
| 153 |
+
|
| 154 |
+
if storage_options.pop("add_aliases", None):
|
| 155 |
+
warnings.warn("add_aliases has been removed.", FutureWarning)
|
| 156 |
+
# This is set in _Cached
|
| 157 |
+
self._fs_token_ = None
|
| 158 |
+
|
| 159 |
+
@property
|
| 160 |
+
def fsid(self):
|
| 161 |
+
"""Persistent filesystem id that can be used to compare filesystems
|
| 162 |
+
across sessions.
|
| 163 |
+
"""
|
| 164 |
+
raise NotImplementedError
|
| 165 |
+
|
| 166 |
+
@property
|
| 167 |
+
def _fs_token(self):
|
| 168 |
+
return self._fs_token_
|
| 169 |
+
|
| 170 |
+
def __dask_tokenize__(self):
|
| 171 |
+
return self._fs_token
|
| 172 |
+
|
| 173 |
+
def __hash__(self):
|
| 174 |
+
return int(self._fs_token, 16)
|
| 175 |
+
|
| 176 |
+
def __eq__(self, other):
|
| 177 |
+
return isinstance(other, type(self)) and self._fs_token == other._fs_token
|
| 178 |
+
|
| 179 |
+
def __reduce__(self):
|
| 180 |
+
return make_instance, (type(self), self.storage_args, self.storage_options)
|
| 181 |
+
|
| 182 |
+
@classmethod
|
| 183 |
+
def _strip_protocol(cls, path):
|
| 184 |
+
"""Turn path from fully-qualified to file-system-specific
|
| 185 |
+
|
| 186 |
+
May require FS-specific handling, e.g., for relative paths or links.
|
| 187 |
+
"""
|
| 188 |
+
if isinstance(path, list):
|
| 189 |
+
return [cls._strip_protocol(p) for p in path]
|
| 190 |
+
path = stringify_path(path)
|
| 191 |
+
protos = (cls.protocol,) if isinstance(cls.protocol, str) else cls.protocol
|
| 192 |
+
for protocol in protos:
|
| 193 |
+
if path.startswith(protocol + "://"):
|
| 194 |
+
path = path[len(protocol) + 3 :]
|
| 195 |
+
elif path.startswith(protocol + "::"):
|
| 196 |
+
path = path[len(protocol) + 2 :]
|
| 197 |
+
path = path.rstrip("/")
|
| 198 |
+
# use of root_marker to make minimum required path, e.g., "/"
|
| 199 |
+
return path or cls.root_marker
|
| 200 |
+
|
| 201 |
+
def unstrip_protocol(self, name: str) -> str:
|
| 202 |
+
"""Format FS-specific path to generic, including protocol"""
|
| 203 |
+
protos = (self.protocol,) if isinstance(self.protocol, str) else self.protocol
|
| 204 |
+
for protocol in protos:
|
| 205 |
+
if name.startswith(f"{protocol}://"):
|
| 206 |
+
return name
|
| 207 |
+
return f"{protos[0]}://{name}"
|
| 208 |
+
|
| 209 |
+
@staticmethod
|
| 210 |
+
def _get_kwargs_from_urls(path):
|
| 211 |
+
"""If kwargs can be encoded in the paths, extract them here
|
| 212 |
+
|
| 213 |
+
This should happen before instantiation of the class; incoming paths
|
| 214 |
+
then should be amended to strip the options in methods.
|
| 215 |
+
|
| 216 |
+
Examples may look like an sftp path "sftp://user@host:/my/path", where
|
| 217 |
+
the user and host should become kwargs and later get stripped.
|
| 218 |
+
"""
|
| 219 |
+
# by default, nothing happens
|
| 220 |
+
return {}
|
| 221 |
+
|
| 222 |
+
@classmethod
|
| 223 |
+
def current(cls):
|
| 224 |
+
"""Return the most recently instantiated FileSystem
|
| 225 |
+
|
| 226 |
+
If no instance has been created, then create one with defaults
|
| 227 |
+
"""
|
| 228 |
+
if cls._latest in cls._cache:
|
| 229 |
+
return cls._cache[cls._latest]
|
| 230 |
+
return cls()
|
| 231 |
+
|
| 232 |
+
@property
|
| 233 |
+
def transaction(self):
|
| 234 |
+
"""A context within which files are committed together upon exit
|
| 235 |
+
|
| 236 |
+
Requires the file class to implement `.commit()` and `.discard()`
|
| 237 |
+
for the normal and exception cases.
|
| 238 |
+
"""
|
| 239 |
+
if self._transaction is None:
|
| 240 |
+
self._transaction = self.transaction_type(self)
|
| 241 |
+
return self._transaction
|
| 242 |
+
|
| 243 |
+
def start_transaction(self):
|
| 244 |
+
"""Begin write transaction for deferring files, non-context version"""
|
| 245 |
+
self._intrans = True
|
| 246 |
+
self._transaction = self.transaction_type(self)
|
| 247 |
+
return self.transaction
|
| 248 |
+
|
| 249 |
+
def end_transaction(self):
|
| 250 |
+
"""Finish write transaction, non-context version"""
|
| 251 |
+
self.transaction.complete()
|
| 252 |
+
self._transaction = None
|
| 253 |
+
# The invalid cache must be cleared after the transaction is completed.
|
| 254 |
+
for path in self._invalidated_caches_in_transaction:
|
| 255 |
+
self.invalidate_cache(path)
|
| 256 |
+
self._invalidated_caches_in_transaction.clear()
|
| 257 |
+
|
| 258 |
+
def invalidate_cache(self, path=None):
|
| 259 |
+
"""
|
| 260 |
+
Discard any cached directory information
|
| 261 |
+
|
| 262 |
+
Parameters
|
| 263 |
+
----------
|
| 264 |
+
path: string or None
|
| 265 |
+
If None, clear all listings cached else listings at or under given
|
| 266 |
+
path.
|
| 267 |
+
"""
|
| 268 |
+
# Not necessary to implement invalidation mechanism, may have no cache.
|
| 269 |
+
# But if have, you should call this method of parent class from your
|
| 270 |
+
# subclass to ensure expiring caches after transacations correctly.
|
| 271 |
+
# See the implementation of FTPFileSystem in ftp.py
|
| 272 |
+
if self._intrans:
|
| 273 |
+
self._invalidated_caches_in_transaction.append(path)
|
| 274 |
+
|
| 275 |
+
def mkdir(self, path, create_parents=True, **kwargs):
|
| 276 |
+
"""
|
| 277 |
+
Create directory entry at path
|
| 278 |
+
|
| 279 |
+
For systems that don't have true directories, may create an for
|
| 280 |
+
this instance only and not touch the real filesystem
|
| 281 |
+
|
| 282 |
+
Parameters
|
| 283 |
+
----------
|
| 284 |
+
path: str
|
| 285 |
+
location
|
| 286 |
+
create_parents: bool
|
| 287 |
+
if True, this is equivalent to ``makedirs``
|
| 288 |
+
kwargs:
|
| 289 |
+
may be permissions, etc.
|
| 290 |
+
"""
|
| 291 |
+
pass # not necessary to implement, may not have directories
|
| 292 |
+
|
| 293 |
+
def makedirs(self, path, exist_ok=False):
|
| 294 |
+
"""Recursively make directories
|
| 295 |
+
|
| 296 |
+
Creates directory at path and any intervening required directories.
|
| 297 |
+
Raises exception if, for instance, the path already exists but is a
|
| 298 |
+
file.
|
| 299 |
+
|
| 300 |
+
Parameters
|
| 301 |
+
----------
|
| 302 |
+
path: str
|
| 303 |
+
leaf directory name
|
| 304 |
+
exist_ok: bool (False)
|
| 305 |
+
If False, will error if the target already exists
|
| 306 |
+
"""
|
| 307 |
+
pass # not necessary to implement, may not have directories
|
| 308 |
+
|
| 309 |
+
def rmdir(self, path):
|
| 310 |
+
"""Remove a directory, if empty"""
|
| 311 |
+
pass # not necessary to implement, may not have directories
|
| 312 |
+
|
| 313 |
+
def ls(self, path, detail=True, **kwargs):
|
| 314 |
+
"""List objects at path.
|
| 315 |
+
|
| 316 |
+
This should include subdirectories and files at that location. The
|
| 317 |
+
difference between a file and a directory must be clear when details
|
| 318 |
+
are requested.
|
| 319 |
+
|
| 320 |
+
The specific keys, or perhaps a FileInfo class, or similar, is TBD,
|
| 321 |
+
but must be consistent across implementations.
|
| 322 |
+
Must include:
|
| 323 |
+
|
| 324 |
+
- full path to the entry (without protocol)
|
| 325 |
+
- size of the entry, in bytes. If the value cannot be determined, will
|
| 326 |
+
be ``None``.
|
| 327 |
+
- type of entry, "file", "directory" or other
|
| 328 |
+
|
| 329 |
+
Additional information
|
| 330 |
+
may be present, appropriate to the file-system, e.g., generation,
|
| 331 |
+
checksum, etc.
|
| 332 |
+
|
| 333 |
+
May use refresh=True|False to allow use of self._ls_from_cache to
|
| 334 |
+
check for a saved listing and avoid calling the backend. This would be
|
| 335 |
+
common where listing may be expensive.
|
| 336 |
+
|
| 337 |
+
Parameters
|
| 338 |
+
----------
|
| 339 |
+
path: str
|
| 340 |
+
detail: bool
|
| 341 |
+
if True, gives a list of dictionaries, where each is the same as
|
| 342 |
+
the result of ``info(path)``. If False, gives a list of paths
|
| 343 |
+
(str).
|
| 344 |
+
kwargs: may have additional backend-specific options, such as version
|
| 345 |
+
information
|
| 346 |
+
|
| 347 |
+
Returns
|
| 348 |
+
-------
|
| 349 |
+
List of strings if detail is False, or list of directory information
|
| 350 |
+
dicts if detail is True.
|
| 351 |
+
"""
|
| 352 |
+
raise NotImplementedError
|
| 353 |
+
|
| 354 |
+
def _ls_from_cache(self, path):
|
| 355 |
+
"""Check cache for listing
|
| 356 |
+
|
| 357 |
+
Returns listing, if found (may be empty list for a directly that exists
|
| 358 |
+
but contains nothing), None if not in cache.
|
| 359 |
+
"""
|
| 360 |
+
parent = self._parent(path)
|
| 361 |
+
if path.rstrip("/") in self.dircache:
|
| 362 |
+
return self.dircache[path.rstrip("/")]
|
| 363 |
+
try:
|
| 364 |
+
files = [
|
| 365 |
+
f
|
| 366 |
+
for f in self.dircache[parent]
|
| 367 |
+
if f["name"] == path
|
| 368 |
+
or (f["name"] == path.rstrip("/") and f["type"] == "directory")
|
| 369 |
+
]
|
| 370 |
+
if len(files) == 0:
|
| 371 |
+
# parent dir was listed but did not contain this file
|
| 372 |
+
raise FileNotFoundError(path)
|
| 373 |
+
return files
|
| 374 |
+
except KeyError:
|
| 375 |
+
pass
|
| 376 |
+
|
| 377 |
+
def walk(self, path, maxdepth=None, topdown=True, on_error="omit", **kwargs):
|
| 378 |
+
"""Return all files belows path
|
| 379 |
+
|
| 380 |
+
List all files, recursing into subdirectories; output is iterator-style,
|
| 381 |
+
like ``os.walk()``. For a simple list of files, ``find()`` is available.
|
| 382 |
+
|
| 383 |
+
When topdown is True, the caller can modify the dirnames list in-place (perhaps
|
| 384 |
+
using del or slice assignment), and walk() will
|
| 385 |
+
only recurse into the subdirectories whose names remain in dirnames;
|
| 386 |
+
this can be used to prune the search, impose a specific order of visiting,
|
| 387 |
+
or even to inform walk() about directories the caller creates or renames before
|
| 388 |
+
it resumes walk() again.
|
| 389 |
+
Modifying dirnames when topdown is False has no effect. (see os.walk)
|
| 390 |
+
|
| 391 |
+
Note that the "files" outputted will include anything that is not
|
| 392 |
+
a directory, such as links.
|
| 393 |
+
|
| 394 |
+
Parameters
|
| 395 |
+
----------
|
| 396 |
+
path: str
|
| 397 |
+
Root to recurse into
|
| 398 |
+
maxdepth: int
|
| 399 |
+
Maximum recursion depth. None means limitless, but not recommended
|
| 400 |
+
on link-based file-systems.
|
| 401 |
+
topdown: bool (True)
|
| 402 |
+
Whether to walk the directory tree from the top downwards or from
|
| 403 |
+
the bottom upwards.
|
| 404 |
+
on_error: "omit", "raise", a collable
|
| 405 |
+
if omit (default), path with exception will simply be empty;
|
| 406 |
+
If raise, an underlying exception will be raised;
|
| 407 |
+
if callable, it will be called with a single OSError instance as argument
|
| 408 |
+
kwargs: passed to ``ls``
|
| 409 |
+
"""
|
| 410 |
+
if maxdepth is not None and maxdepth < 1:
|
| 411 |
+
raise ValueError("maxdepth must be at least 1")
|
| 412 |
+
|
| 413 |
+
path = self._strip_protocol(path)
|
| 414 |
+
full_dirs = {}
|
| 415 |
+
dirs = {}
|
| 416 |
+
files = {}
|
| 417 |
+
|
| 418 |
+
detail = kwargs.pop("detail", False)
|
| 419 |
+
try:
|
| 420 |
+
listing = self.ls(path, detail=True, **kwargs)
|
| 421 |
+
except (FileNotFoundError, OSError) as e:
|
| 422 |
+
if on_error == "raise":
|
| 423 |
+
raise
|
| 424 |
+
elif callable(on_error):
|
| 425 |
+
on_error(e)
|
| 426 |
+
if detail:
|
| 427 |
+
return path, {}, {}
|
| 428 |
+
return path, [], []
|
| 429 |
+
|
| 430 |
+
for info in listing:
|
| 431 |
+
# each info name must be at least [path]/part , but here
|
| 432 |
+
# we check also for names like [path]/part/
|
| 433 |
+
pathname = info["name"].rstrip("/")
|
| 434 |
+
name = pathname.rsplit("/", 1)[-1]
|
| 435 |
+
if info["type"] == "directory" and pathname != path:
|
| 436 |
+
# do not include "self" path
|
| 437 |
+
full_dirs[name] = pathname
|
| 438 |
+
dirs[name] = info
|
| 439 |
+
elif pathname == path:
|
| 440 |
+
# file-like with same name as give path
|
| 441 |
+
files[""] = info
|
| 442 |
+
else:
|
| 443 |
+
files[name] = info
|
| 444 |
+
|
| 445 |
+
if not detail:
|
| 446 |
+
dirs = list(dirs)
|
| 447 |
+
files = list(files)
|
| 448 |
+
|
| 449 |
+
if topdown:
|
| 450 |
+
# Yield before recursion if walking top down
|
| 451 |
+
yield path, dirs, files
|
| 452 |
+
|
| 453 |
+
if maxdepth is not None:
|
| 454 |
+
maxdepth -= 1
|
| 455 |
+
if maxdepth < 1:
|
| 456 |
+
if not topdown:
|
| 457 |
+
yield path, dirs, files
|
| 458 |
+
return
|
| 459 |
+
|
| 460 |
+
for d in dirs:
|
| 461 |
+
yield from self.walk(
|
| 462 |
+
full_dirs[d],
|
| 463 |
+
maxdepth=maxdepth,
|
| 464 |
+
detail=detail,
|
| 465 |
+
topdown=topdown,
|
| 466 |
+
**kwargs,
|
| 467 |
+
)
|
| 468 |
+
|
| 469 |
+
if not topdown:
|
| 470 |
+
# Yield after recursion if walking bottom up
|
| 471 |
+
yield path, dirs, files
|
| 472 |
+
|
| 473 |
+
def find(self, path, maxdepth=None, withdirs=False, detail=False, **kwargs):
|
| 474 |
+
"""List all files below path.
|
| 475 |
+
|
| 476 |
+
Like posix ``find`` command without conditions
|
| 477 |
+
|
| 478 |
+
Parameters
|
| 479 |
+
----------
|
| 480 |
+
path : str
|
| 481 |
+
maxdepth: int or None
|
| 482 |
+
If not None, the maximum number of levels to descend
|
| 483 |
+
withdirs: bool
|
| 484 |
+
Whether to include directory paths in the output. This is True
|
| 485 |
+
when used by glob, but users usually only want files.
|
| 486 |
+
kwargs are passed to ``ls``.
|
| 487 |
+
"""
|
| 488 |
+
# TODO: allow equivalent of -name parameter
|
| 489 |
+
path = self._strip_protocol(path)
|
| 490 |
+
out = {}
|
| 491 |
+
|
| 492 |
+
# Add the root directory if withdirs is requested
|
| 493 |
+
# This is needed for posix glob compliance
|
| 494 |
+
if withdirs and path != "" and self.isdir(path):
|
| 495 |
+
out[path] = self.info(path)
|
| 496 |
+
|
| 497 |
+
for _, dirs, files in self.walk(path, maxdepth, detail=True, **kwargs):
|
| 498 |
+
if withdirs:
|
| 499 |
+
files.update(dirs)
|
| 500 |
+
out.update({info["name"]: info for name, info in files.items()})
|
| 501 |
+
if not out and self.isfile(path):
|
| 502 |
+
# walk works on directories, but find should also return [path]
|
| 503 |
+
# when path happens to be a file
|
| 504 |
+
out[path] = {}
|
| 505 |
+
names = sorted(out)
|
| 506 |
+
if not detail:
|
| 507 |
+
return names
|
| 508 |
+
else:
|
| 509 |
+
return {name: out[name] for name in names}
|
| 510 |
+
|
| 511 |
+
def du(self, path, total=True, maxdepth=None, withdirs=False, **kwargs):
|
| 512 |
+
"""Space used by files and optionally directories within a path
|
| 513 |
+
|
| 514 |
+
Directory size does not include the size of its contents.
|
| 515 |
+
|
| 516 |
+
Parameters
|
| 517 |
+
----------
|
| 518 |
+
path: str
|
| 519 |
+
total: bool
|
| 520 |
+
Whether to sum all the file sizes
|
| 521 |
+
maxdepth: int or None
|
| 522 |
+
Maximum number of directory levels to descend, None for unlimited.
|
| 523 |
+
withdirs: bool
|
| 524 |
+
Whether to include directory paths in the output.
|
| 525 |
+
kwargs: passed to ``find``
|
| 526 |
+
|
| 527 |
+
Returns
|
| 528 |
+
-------
|
| 529 |
+
Dict of {path: size} if total=False, or int otherwise, where numbers
|
| 530 |
+
refer to bytes used.
|
| 531 |
+
"""
|
| 532 |
+
sizes = {}
|
| 533 |
+
if withdirs and self.isdir(path):
|
| 534 |
+
# Include top-level directory in output
|
| 535 |
+
info = self.info(path)
|
| 536 |
+
sizes[info["name"]] = info["size"]
|
| 537 |
+
for f in self.find(path, maxdepth=maxdepth, withdirs=withdirs, **kwargs):
|
| 538 |
+
info = self.info(f)
|
| 539 |
+
sizes[info["name"]] = info["size"]
|
| 540 |
+
if total:
|
| 541 |
+
return sum(sizes.values())
|
| 542 |
+
else:
|
| 543 |
+
return sizes
|
| 544 |
+
|
| 545 |
+
def glob(self, path, maxdepth=None, **kwargs):
|
| 546 |
+
"""
|
| 547 |
+
Find files by glob-matching.
|
| 548 |
+
|
| 549 |
+
If the path ends with '/', only folders are returned.
|
| 550 |
+
|
| 551 |
+
We support ``"**"``,
|
| 552 |
+
``"?"`` and ``"[..]"``. We do not support ^ for pattern negation.
|
| 553 |
+
|
| 554 |
+
The `maxdepth` option is applied on the first `**` found in the path.
|
| 555 |
+
|
| 556 |
+
kwargs are passed to ``ls``.
|
| 557 |
+
"""
|
| 558 |
+
if maxdepth is not None and maxdepth < 1:
|
| 559 |
+
raise ValueError("maxdepth must be at least 1")
|
| 560 |
+
|
| 561 |
+
import re
|
| 562 |
+
|
| 563 |
+
seps = (os.path.sep, os.path.altsep) if os.path.altsep else (os.path.sep,)
|
| 564 |
+
ends_with_sep = path.endswith(seps) # _strip_protocol strips trailing slash
|
| 565 |
+
path = self._strip_protocol(path)
|
| 566 |
+
append_slash_to_dirname = ends_with_sep or path.endswith(
|
| 567 |
+
tuple(sep + "**" for sep in seps)
|
| 568 |
+
)
|
| 569 |
+
idx_star = path.find("*") if path.find("*") >= 0 else len(path)
|
| 570 |
+
idx_qmark = path.find("?") if path.find("?") >= 0 else len(path)
|
| 571 |
+
idx_brace = path.find("[") if path.find("[") >= 0 else len(path)
|
| 572 |
+
|
| 573 |
+
min_idx = min(idx_star, idx_qmark, idx_brace)
|
| 574 |
+
|
| 575 |
+
detail = kwargs.pop("detail", False)
|
| 576 |
+
|
| 577 |
+
if not has_magic(path):
|
| 578 |
+
if self.exists(path, **kwargs):
|
| 579 |
+
if not detail:
|
| 580 |
+
return [path]
|
| 581 |
+
else:
|
| 582 |
+
return {path: self.info(path, **kwargs)}
|
| 583 |
+
else:
|
| 584 |
+
if not detail:
|
| 585 |
+
return [] # glob of non-existent returns empty
|
| 586 |
+
else:
|
| 587 |
+
return {}
|
| 588 |
+
elif "/" in path[:min_idx]:
|
| 589 |
+
min_idx = path[:min_idx].rindex("/")
|
| 590 |
+
root = path[: min_idx + 1]
|
| 591 |
+
depth = path[min_idx + 1 :].count("/") + 1
|
| 592 |
+
else:
|
| 593 |
+
root = ""
|
| 594 |
+
depth = path[min_idx + 1 :].count("/") + 1
|
| 595 |
+
|
| 596 |
+
if "**" in path:
|
| 597 |
+
if maxdepth is not None:
|
| 598 |
+
idx_double_stars = path.find("**")
|
| 599 |
+
depth_double_stars = path[idx_double_stars:].count("/") + 1
|
| 600 |
+
depth = depth - depth_double_stars + maxdepth
|
| 601 |
+
else:
|
| 602 |
+
depth = None
|
| 603 |
+
|
| 604 |
+
allpaths = self.find(root, maxdepth=depth, withdirs=True, detail=True, **kwargs)
|
| 605 |
+
|
| 606 |
+
pattern = glob_translate(path + ("/" if ends_with_sep else ""))
|
| 607 |
+
pattern = re.compile(pattern)
|
| 608 |
+
|
| 609 |
+
out = {
|
| 610 |
+
p: info
|
| 611 |
+
for p, info in sorted(allpaths.items())
|
| 612 |
+
if pattern.match(
|
| 613 |
+
(
|
| 614 |
+
p + "/"
|
| 615 |
+
if append_slash_to_dirname and info["type"] == "directory"
|
| 616 |
+
else p
|
| 617 |
+
)
|
| 618 |
+
)
|
| 619 |
+
}
|
| 620 |
+
|
| 621 |
+
if detail:
|
| 622 |
+
return out
|
| 623 |
+
else:
|
| 624 |
+
return list(out)
|
| 625 |
+
|
| 626 |
+
def exists(self, path, **kwargs):
|
| 627 |
+
"""Is there a file at the given path"""
|
| 628 |
+
try:
|
| 629 |
+
self.info(path, **kwargs)
|
| 630 |
+
return True
|
| 631 |
+
except: # noqa: E722
|
| 632 |
+
# any exception allowed bar FileNotFoundError?
|
| 633 |
+
return False
|
| 634 |
+
|
| 635 |
+
def lexists(self, path, **kwargs):
|
| 636 |
+
"""If there is a file at the given path (including
|
| 637 |
+
broken links)"""
|
| 638 |
+
return self.exists(path)
|
| 639 |
+
|
| 640 |
+
def info(self, path, **kwargs):
|
| 641 |
+
"""Give details of entry at path
|
| 642 |
+
|
| 643 |
+
Returns a single dictionary, with exactly the same information as ``ls``
|
| 644 |
+
would with ``detail=True``.
|
| 645 |
+
|
| 646 |
+
The default implementation should calls ls and could be overridden by a
|
| 647 |
+
shortcut. kwargs are passed on to ```ls()``.
|
| 648 |
+
|
| 649 |
+
Some file systems might not be able to measure the file's size, in
|
| 650 |
+
which case, the returned dict will include ``'size': None``.
|
| 651 |
+
|
| 652 |
+
Returns
|
| 653 |
+
-------
|
| 654 |
+
dict with keys: name (full path in the FS), size (in bytes), type (file,
|
| 655 |
+
directory, or something else) and other FS-specific keys.
|
| 656 |
+
"""
|
| 657 |
+
path = self._strip_protocol(path)
|
| 658 |
+
out = self.ls(self._parent(path), detail=True, **kwargs)
|
| 659 |
+
out = [o for o in out if o["name"].rstrip("/") == path]
|
| 660 |
+
if out:
|
| 661 |
+
return out[0]
|
| 662 |
+
out = self.ls(path, detail=True, **kwargs)
|
| 663 |
+
path = path.rstrip("/")
|
| 664 |
+
out1 = [o for o in out if o["name"].rstrip("/") == path]
|
| 665 |
+
if len(out1) == 1:
|
| 666 |
+
if "size" not in out1[0]:
|
| 667 |
+
out1[0]["size"] = None
|
| 668 |
+
return out1[0]
|
| 669 |
+
elif len(out1) > 1 or out:
|
| 670 |
+
return {"name": path, "size": 0, "type": "directory"}
|
| 671 |
+
else:
|
| 672 |
+
raise FileNotFoundError(path)
|
| 673 |
+
|
| 674 |
+
def checksum(self, path):
|
| 675 |
+
"""Unique value for current version of file
|
| 676 |
+
|
| 677 |
+
If the checksum is the same from one moment to another, the contents
|
| 678 |
+
are guaranteed to be the same. If the checksum changes, the contents
|
| 679 |
+
*might* have changed.
|
| 680 |
+
|
| 681 |
+
This should normally be overridden; default will probably capture
|
| 682 |
+
creation/modification timestamp (which would be good) or maybe
|
| 683 |
+
access timestamp (which would be bad)
|
| 684 |
+
"""
|
| 685 |
+
return int(tokenize(self.info(path)), 16)
|
| 686 |
+
|
| 687 |
+
def size(self, path):
|
| 688 |
+
"""Size in bytes of file"""
|
| 689 |
+
return self.info(path).get("size", None)
|
| 690 |
+
|
| 691 |
+
def sizes(self, paths):
|
| 692 |
+
"""Size in bytes of each file in a list of paths"""
|
| 693 |
+
return [self.size(p) for p in paths]
|
| 694 |
+
|
| 695 |
+
def isdir(self, path):
|
| 696 |
+
"""Is this entry directory-like?"""
|
| 697 |
+
try:
|
| 698 |
+
return self.info(path)["type"] == "directory"
|
| 699 |
+
except OSError:
|
| 700 |
+
return False
|
| 701 |
+
|
| 702 |
+
def isfile(self, path):
|
| 703 |
+
"""Is this entry file-like?"""
|
| 704 |
+
try:
|
| 705 |
+
return self.info(path)["type"] == "file"
|
| 706 |
+
except: # noqa: E722
|
| 707 |
+
return False
|
| 708 |
+
|
| 709 |
+
def read_text(self, path, encoding=None, errors=None, newline=None, **kwargs):
|
| 710 |
+
"""Get the contents of the file as a string.
|
| 711 |
+
|
| 712 |
+
Parameters
|
| 713 |
+
----------
|
| 714 |
+
path: str
|
| 715 |
+
URL of file on this filesystems
|
| 716 |
+
encoding, errors, newline: same as `open`.
|
| 717 |
+
"""
|
| 718 |
+
with self.open(
|
| 719 |
+
path,
|
| 720 |
+
mode="r",
|
| 721 |
+
encoding=encoding,
|
| 722 |
+
errors=errors,
|
| 723 |
+
newline=newline,
|
| 724 |
+
**kwargs,
|
| 725 |
+
) as f:
|
| 726 |
+
return f.read()
|
| 727 |
+
|
| 728 |
+
def write_text(
|
| 729 |
+
self, path, value, encoding=None, errors=None, newline=None, **kwargs
|
| 730 |
+
):
|
| 731 |
+
"""Write the text to the given file.
|
| 732 |
+
|
| 733 |
+
An existing file will be overwritten.
|
| 734 |
+
|
| 735 |
+
Parameters
|
| 736 |
+
----------
|
| 737 |
+
path: str
|
| 738 |
+
URL of file on this filesystems
|
| 739 |
+
value: str
|
| 740 |
+
Text to write.
|
| 741 |
+
encoding, errors, newline: same as `open`.
|
| 742 |
+
"""
|
| 743 |
+
with self.open(
|
| 744 |
+
path,
|
| 745 |
+
mode="w",
|
| 746 |
+
encoding=encoding,
|
| 747 |
+
errors=errors,
|
| 748 |
+
newline=newline,
|
| 749 |
+
**kwargs,
|
| 750 |
+
) as f:
|
| 751 |
+
return f.write(value)
|
| 752 |
+
|
| 753 |
+
def cat_file(self, path, start=None, end=None, **kwargs):
|
| 754 |
+
"""Get the content of a file
|
| 755 |
+
|
| 756 |
+
Parameters
|
| 757 |
+
----------
|
| 758 |
+
path: URL of file on this filesystems
|
| 759 |
+
start, end: int
|
| 760 |
+
Bytes limits of the read. If negative, backwards from end,
|
| 761 |
+
like usual python slices. Either can be None for start or
|
| 762 |
+
end of file, respectively
|
| 763 |
+
kwargs: passed to ``open()``.
|
| 764 |
+
"""
|
| 765 |
+
# explicitly set buffering off?
|
| 766 |
+
with self.open(path, "rb", **kwargs) as f:
|
| 767 |
+
if start is not None:
|
| 768 |
+
if start >= 0:
|
| 769 |
+
f.seek(start)
|
| 770 |
+
else:
|
| 771 |
+
f.seek(max(0, f.size + start))
|
| 772 |
+
if end is not None:
|
| 773 |
+
if end < 0:
|
| 774 |
+
end = f.size + end
|
| 775 |
+
return f.read(end - f.tell())
|
| 776 |
+
return f.read()
|
| 777 |
+
|
| 778 |
+
def pipe_file(self, path, value, **kwargs):
|
| 779 |
+
"""Set the bytes of given file"""
|
| 780 |
+
with self.open(path, "wb", **kwargs) as f:
|
| 781 |
+
f.write(value)
|
| 782 |
+
|
| 783 |
+
def pipe(self, path, value=None, **kwargs):
|
| 784 |
+
"""Put value into path
|
| 785 |
+
|
| 786 |
+
(counterpart to ``cat``)
|
| 787 |
+
|
| 788 |
+
Parameters
|
| 789 |
+
----------
|
| 790 |
+
path: string or dict(str, bytes)
|
| 791 |
+
If a string, a single remote location to put ``value`` bytes; if a dict,
|
| 792 |
+
a mapping of {path: bytesvalue}.
|
| 793 |
+
value: bytes, optional
|
| 794 |
+
If using a single path, these are the bytes to put there. Ignored if
|
| 795 |
+
``path`` is a dict
|
| 796 |
+
"""
|
| 797 |
+
if isinstance(path, str):
|
| 798 |
+
self.pipe_file(self._strip_protocol(path), value, **kwargs)
|
| 799 |
+
elif isinstance(path, dict):
|
| 800 |
+
for k, v in path.items():
|
| 801 |
+
self.pipe_file(self._strip_protocol(k), v, **kwargs)
|
| 802 |
+
else:
|
| 803 |
+
raise ValueError("path must be str or dict")
|
| 804 |
+
|
| 805 |
+
def cat_ranges(
|
| 806 |
+
self, paths, starts, ends, max_gap=None, on_error="return", **kwargs
|
| 807 |
+
):
|
| 808 |
+
"""Get the contents of byte ranges from one or more files
|
| 809 |
+
|
| 810 |
+
Parameters
|
| 811 |
+
----------
|
| 812 |
+
paths: list
|
| 813 |
+
A list of of filepaths on this filesystems
|
| 814 |
+
starts, ends: int or list
|
| 815 |
+
Bytes limits of the read. If using a single int, the same value will be
|
| 816 |
+
used to read all the specified files.
|
| 817 |
+
"""
|
| 818 |
+
if max_gap is not None:
|
| 819 |
+
raise NotImplementedError
|
| 820 |
+
if not isinstance(paths, list):
|
| 821 |
+
raise TypeError
|
| 822 |
+
if not isinstance(starts, list):
|
| 823 |
+
starts = [starts] * len(paths)
|
| 824 |
+
if not isinstance(ends, list):
|
| 825 |
+
ends = [ends] * len(paths)
|
| 826 |
+
if len(starts) != len(paths) or len(ends) != len(paths):
|
| 827 |
+
raise ValueError
|
| 828 |
+
out = []
|
| 829 |
+
for p, s, e in zip(paths, starts, ends):
|
| 830 |
+
try:
|
| 831 |
+
out.append(self.cat_file(p, s, e))
|
| 832 |
+
except Exception as e:
|
| 833 |
+
if on_error == "return":
|
| 834 |
+
out.append(e)
|
| 835 |
+
else:
|
| 836 |
+
raise
|
| 837 |
+
return out
|
| 838 |
+
|
| 839 |
+
def cat(self, path, recursive=False, on_error="raise", **kwargs):
|
| 840 |
+
"""Fetch (potentially multiple) paths' contents
|
| 841 |
+
|
| 842 |
+
Parameters
|
| 843 |
+
----------
|
| 844 |
+
recursive: bool
|
| 845 |
+
If True, assume the path(s) are directories, and get all the
|
| 846 |
+
contained files
|
| 847 |
+
on_error : "raise", "omit", "return"
|
| 848 |
+
If raise, an underlying exception will be raised (converted to KeyError
|
| 849 |
+
if the type is in self.missing_exceptions); if omit, keys with exception
|
| 850 |
+
will simply not be included in the output; if "return", all keys are
|
| 851 |
+
included in the output, but the value will be bytes or an exception
|
| 852 |
+
instance.
|
| 853 |
+
kwargs: passed to cat_file
|
| 854 |
+
|
| 855 |
+
Returns
|
| 856 |
+
-------
|
| 857 |
+
dict of {path: contents} if there are multiple paths
|
| 858 |
+
or the path has been otherwise expanded
|
| 859 |
+
"""
|
| 860 |
+
paths = self.expand_path(path, recursive=recursive)
|
| 861 |
+
if (
|
| 862 |
+
len(paths) > 1
|
| 863 |
+
or isinstance(path, list)
|
| 864 |
+
or paths[0] != self._strip_protocol(path)
|
| 865 |
+
):
|
| 866 |
+
out = {}
|
| 867 |
+
for path in paths:
|
| 868 |
+
try:
|
| 869 |
+
out[path] = self.cat_file(path, **kwargs)
|
| 870 |
+
except Exception as e:
|
| 871 |
+
if on_error == "raise":
|
| 872 |
+
raise
|
| 873 |
+
if on_error == "return":
|
| 874 |
+
out[path] = e
|
| 875 |
+
return out
|
| 876 |
+
else:
|
| 877 |
+
return self.cat_file(paths[0], **kwargs)
|
| 878 |
+
|
| 879 |
+
def get_file(self, rpath, lpath, callback=DEFAULT_CALLBACK, outfile=None, **kwargs):
|
| 880 |
+
"""Copy single remote file to local"""
|
| 881 |
+
from .implementations.local import LocalFileSystem
|
| 882 |
+
|
| 883 |
+
if isfilelike(lpath):
|
| 884 |
+
outfile = lpath
|
| 885 |
+
elif self.isdir(rpath):
|
| 886 |
+
os.makedirs(lpath, exist_ok=True)
|
| 887 |
+
return None
|
| 888 |
+
|
| 889 |
+
fs = LocalFileSystem(auto_mkdir=True)
|
| 890 |
+
fs.makedirs(fs._parent(lpath), exist_ok=True)
|
| 891 |
+
|
| 892 |
+
with self.open(rpath, "rb", **kwargs) as f1:
|
| 893 |
+
if outfile is None:
|
| 894 |
+
outfile = open(lpath, "wb")
|
| 895 |
+
|
| 896 |
+
try:
|
| 897 |
+
callback.set_size(getattr(f1, "size", None))
|
| 898 |
+
data = True
|
| 899 |
+
while data:
|
| 900 |
+
data = f1.read(self.blocksize)
|
| 901 |
+
segment_len = outfile.write(data)
|
| 902 |
+
if segment_len is None:
|
| 903 |
+
segment_len = len(data)
|
| 904 |
+
callback.relative_update(segment_len)
|
| 905 |
+
finally:
|
| 906 |
+
if not isfilelike(lpath):
|
| 907 |
+
outfile.close()
|
| 908 |
+
|
| 909 |
+
def get(
|
| 910 |
+
self,
|
| 911 |
+
rpath,
|
| 912 |
+
lpath,
|
| 913 |
+
recursive=False,
|
| 914 |
+
callback=DEFAULT_CALLBACK,
|
| 915 |
+
maxdepth=None,
|
| 916 |
+
**kwargs,
|
| 917 |
+
):
|
| 918 |
+
"""Copy file(s) to local.
|
| 919 |
+
|
| 920 |
+
Copies a specific file or tree of files (if recursive=True). If lpath
|
| 921 |
+
ends with a "/", it will be assumed to be a directory, and target files
|
| 922 |
+
will go within. Can submit a list of paths, which may be glob-patterns
|
| 923 |
+
and will be expanded.
|
| 924 |
+
|
| 925 |
+
Calls get_file for each source.
|
| 926 |
+
"""
|
| 927 |
+
if isinstance(lpath, list) and isinstance(rpath, list):
|
| 928 |
+
# No need to expand paths when both source and destination
|
| 929 |
+
# are provided as lists
|
| 930 |
+
rpaths = rpath
|
| 931 |
+
lpaths = lpath
|
| 932 |
+
else:
|
| 933 |
+
from .implementations.local import (
|
| 934 |
+
LocalFileSystem,
|
| 935 |
+
make_path_posix,
|
| 936 |
+
trailing_sep,
|
| 937 |
+
)
|
| 938 |
+
|
| 939 |
+
source_is_str = isinstance(rpath, str)
|
| 940 |
+
rpaths = self.expand_path(rpath, recursive=recursive, maxdepth=maxdepth)
|
| 941 |
+
if source_is_str and (not recursive or maxdepth is not None):
|
| 942 |
+
# Non-recursive glob does not copy directories
|
| 943 |
+
rpaths = [p for p in rpaths if not (trailing_sep(p) or self.isdir(p))]
|
| 944 |
+
if not rpaths:
|
| 945 |
+
return
|
| 946 |
+
|
| 947 |
+
if isinstance(lpath, str):
|
| 948 |
+
lpath = make_path_posix(lpath)
|
| 949 |
+
|
| 950 |
+
source_is_file = len(rpaths) == 1
|
| 951 |
+
dest_is_dir = isinstance(lpath, str) and (
|
| 952 |
+
trailing_sep(lpath) or LocalFileSystem().isdir(lpath)
|
| 953 |
+
)
|
| 954 |
+
|
| 955 |
+
exists = source_is_str and (
|
| 956 |
+
(has_magic(rpath) and source_is_file)
|
| 957 |
+
or (not has_magic(rpath) and dest_is_dir and not trailing_sep(rpath))
|
| 958 |
+
)
|
| 959 |
+
lpaths = other_paths(
|
| 960 |
+
rpaths,
|
| 961 |
+
lpath,
|
| 962 |
+
exists=exists,
|
| 963 |
+
flatten=not source_is_str,
|
| 964 |
+
)
|
| 965 |
+
|
| 966 |
+
callback.set_size(len(lpaths))
|
| 967 |
+
for lpath, rpath in callback.wrap(zip(lpaths, rpaths)):
|
| 968 |
+
with callback.branched(rpath, lpath) as child:
|
| 969 |
+
self.get_file(rpath, lpath, callback=child, **kwargs)
|
| 970 |
+
|
| 971 |
+
def put_file(self, lpath, rpath, callback=DEFAULT_CALLBACK, **kwargs):
|
| 972 |
+
"""Copy single file to remote"""
|
| 973 |
+
if os.path.isdir(lpath):
|
| 974 |
+
self.makedirs(rpath, exist_ok=True)
|
| 975 |
+
return None
|
| 976 |
+
|
| 977 |
+
with open(lpath, "rb") as f1:
|
| 978 |
+
size = f1.seek(0, 2)
|
| 979 |
+
callback.set_size(size)
|
| 980 |
+
f1.seek(0)
|
| 981 |
+
|
| 982 |
+
self.mkdirs(self._parent(os.fspath(rpath)), exist_ok=True)
|
| 983 |
+
with self.open(rpath, "wb", **kwargs) as f2:
|
| 984 |
+
while f1.tell() < size:
|
| 985 |
+
data = f1.read(self.blocksize)
|
| 986 |
+
segment_len = f2.write(data)
|
| 987 |
+
if segment_len is None:
|
| 988 |
+
segment_len = len(data)
|
| 989 |
+
callback.relative_update(segment_len)
|
| 990 |
+
|
| 991 |
+
def put(
|
| 992 |
+
self,
|
| 993 |
+
lpath,
|
| 994 |
+
rpath,
|
| 995 |
+
recursive=False,
|
| 996 |
+
callback=DEFAULT_CALLBACK,
|
| 997 |
+
maxdepth=None,
|
| 998 |
+
**kwargs,
|
| 999 |
+
):
|
| 1000 |
+
"""Copy file(s) from local.
|
| 1001 |
+
|
| 1002 |
+
Copies a specific file or tree of files (if recursive=True). If rpath
|
| 1003 |
+
ends with a "/", it will be assumed to be a directory, and target files
|
| 1004 |
+
will go within.
|
| 1005 |
+
|
| 1006 |
+
Calls put_file for each source.
|
| 1007 |
+
"""
|
| 1008 |
+
if isinstance(lpath, list) and isinstance(rpath, list):
|
| 1009 |
+
# No need to expand paths when both source and destination
|
| 1010 |
+
# are provided as lists
|
| 1011 |
+
rpaths = rpath
|
| 1012 |
+
lpaths = lpath
|
| 1013 |
+
else:
|
| 1014 |
+
from .implementations.local import (
|
| 1015 |
+
LocalFileSystem,
|
| 1016 |
+
make_path_posix,
|
| 1017 |
+
trailing_sep,
|
| 1018 |
+
)
|
| 1019 |
+
|
| 1020 |
+
source_is_str = isinstance(lpath, str)
|
| 1021 |
+
if source_is_str:
|
| 1022 |
+
lpath = make_path_posix(lpath)
|
| 1023 |
+
fs = LocalFileSystem()
|
| 1024 |
+
lpaths = fs.expand_path(lpath, recursive=recursive, maxdepth=maxdepth)
|
| 1025 |
+
if source_is_str and (not recursive or maxdepth is not None):
|
| 1026 |
+
# Non-recursive glob does not copy directories
|
| 1027 |
+
lpaths = [p for p in lpaths if not (trailing_sep(p) or fs.isdir(p))]
|
| 1028 |
+
if not lpaths:
|
| 1029 |
+
return
|
| 1030 |
+
|
| 1031 |
+
source_is_file = len(lpaths) == 1
|
| 1032 |
+
dest_is_dir = isinstance(rpath, str) and (
|
| 1033 |
+
trailing_sep(rpath) or self.isdir(rpath)
|
| 1034 |
+
)
|
| 1035 |
+
|
| 1036 |
+
rpath = (
|
| 1037 |
+
self._strip_protocol(rpath)
|
| 1038 |
+
if isinstance(rpath, str)
|
| 1039 |
+
else [self._strip_protocol(p) for p in rpath]
|
| 1040 |
+
)
|
| 1041 |
+
exists = source_is_str and (
|
| 1042 |
+
(has_magic(lpath) and source_is_file)
|
| 1043 |
+
or (not has_magic(lpath) and dest_is_dir and not trailing_sep(lpath))
|
| 1044 |
+
)
|
| 1045 |
+
rpaths = other_paths(
|
| 1046 |
+
lpaths,
|
| 1047 |
+
rpath,
|
| 1048 |
+
exists=exists,
|
| 1049 |
+
flatten=not source_is_str,
|
| 1050 |
+
)
|
| 1051 |
+
|
| 1052 |
+
callback.set_size(len(rpaths))
|
| 1053 |
+
for lpath, rpath in callback.wrap(zip(lpaths, rpaths)):
|
| 1054 |
+
with callback.branched(lpath, rpath) as child:
|
| 1055 |
+
self.put_file(lpath, rpath, callback=child, **kwargs)
|
| 1056 |
+
|
| 1057 |
+
def head(self, path, size=1024):
|
| 1058 |
+
"""Get the first ``size`` bytes from file"""
|
| 1059 |
+
with self.open(path, "rb") as f:
|
| 1060 |
+
return f.read(size)
|
| 1061 |
+
|
| 1062 |
+
def tail(self, path, size=1024):
|
| 1063 |
+
"""Get the last ``size`` bytes from file"""
|
| 1064 |
+
with self.open(path, "rb") as f:
|
| 1065 |
+
f.seek(max(-size, -f.size), 2)
|
| 1066 |
+
return f.read()
|
| 1067 |
+
|
| 1068 |
+
def cp_file(self, path1, path2, **kwargs):
|
| 1069 |
+
raise NotImplementedError
|
| 1070 |
+
|
| 1071 |
+
def copy(
|
| 1072 |
+
self, path1, path2, recursive=False, maxdepth=None, on_error=None, **kwargs
|
| 1073 |
+
):
|
| 1074 |
+
"""Copy within two locations in the filesystem
|
| 1075 |
+
|
| 1076 |
+
on_error : "raise", "ignore"
|
| 1077 |
+
If raise, any not-found exceptions will be raised; if ignore any
|
| 1078 |
+
not-found exceptions will cause the path to be skipped; defaults to
|
| 1079 |
+
raise unless recursive is true, where the default is ignore
|
| 1080 |
+
"""
|
| 1081 |
+
if on_error is None and recursive:
|
| 1082 |
+
on_error = "ignore"
|
| 1083 |
+
elif on_error is None:
|
| 1084 |
+
on_error = "raise"
|
| 1085 |
+
|
| 1086 |
+
if isinstance(path1, list) and isinstance(path2, list):
|
| 1087 |
+
# No need to expand paths when both source and destination
|
| 1088 |
+
# are provided as lists
|
| 1089 |
+
paths1 = path1
|
| 1090 |
+
paths2 = path2
|
| 1091 |
+
else:
|
| 1092 |
+
from .implementations.local import trailing_sep
|
| 1093 |
+
|
| 1094 |
+
source_is_str = isinstance(path1, str)
|
| 1095 |
+
paths1 = self.expand_path(path1, recursive=recursive, maxdepth=maxdepth)
|
| 1096 |
+
if source_is_str and (not recursive or maxdepth is not None):
|
| 1097 |
+
# Non-recursive glob does not copy directories
|
| 1098 |
+
paths1 = [p for p in paths1 if not (trailing_sep(p) or self.isdir(p))]
|
| 1099 |
+
if not paths1:
|
| 1100 |
+
return
|
| 1101 |
+
|
| 1102 |
+
source_is_file = len(paths1) == 1
|
| 1103 |
+
dest_is_dir = isinstance(path2, str) and (
|
| 1104 |
+
trailing_sep(path2) or self.isdir(path2)
|
| 1105 |
+
)
|
| 1106 |
+
|
| 1107 |
+
exists = source_is_str and (
|
| 1108 |
+
(has_magic(path1) and source_is_file)
|
| 1109 |
+
or (not has_magic(path1) and dest_is_dir and not trailing_sep(path1))
|
| 1110 |
+
)
|
| 1111 |
+
paths2 = other_paths(
|
| 1112 |
+
paths1,
|
| 1113 |
+
path2,
|
| 1114 |
+
exists=exists,
|
| 1115 |
+
flatten=not source_is_str,
|
| 1116 |
+
)
|
| 1117 |
+
|
| 1118 |
+
for p1, p2 in zip(paths1, paths2):
|
| 1119 |
+
try:
|
| 1120 |
+
self.cp_file(p1, p2, **kwargs)
|
| 1121 |
+
except FileNotFoundError:
|
| 1122 |
+
if on_error == "raise":
|
| 1123 |
+
raise
|
| 1124 |
+
|
| 1125 |
+
def expand_path(self, path, recursive=False, maxdepth=None, **kwargs):
|
| 1126 |
+
"""Turn one or more globs or directories into a list of all matching paths
|
| 1127 |
+
to files or directories.
|
| 1128 |
+
|
| 1129 |
+
kwargs are passed to ``glob`` or ``find``, which may in turn call ``ls``
|
| 1130 |
+
"""
|
| 1131 |
+
|
| 1132 |
+
if maxdepth is not None and maxdepth < 1:
|
| 1133 |
+
raise ValueError("maxdepth must be at least 1")
|
| 1134 |
+
|
| 1135 |
+
if isinstance(path, (str, os.PathLike)):
|
| 1136 |
+
out = self.expand_path([path], recursive, maxdepth)
|
| 1137 |
+
else:
|
| 1138 |
+
out = set()
|
| 1139 |
+
path = [self._strip_protocol(p) for p in path]
|
| 1140 |
+
for p in path:
|
| 1141 |
+
if has_magic(p):
|
| 1142 |
+
bit = set(self.glob(p, maxdepth=maxdepth, **kwargs))
|
| 1143 |
+
out |= bit
|
| 1144 |
+
if recursive:
|
| 1145 |
+
# glob call above expanded one depth so if maxdepth is defined
|
| 1146 |
+
# then decrement it in expand_path call below. If it is zero
|
| 1147 |
+
# after decrementing then avoid expand_path call.
|
| 1148 |
+
if maxdepth is not None and maxdepth <= 1:
|
| 1149 |
+
continue
|
| 1150 |
+
out |= set(
|
| 1151 |
+
self.expand_path(
|
| 1152 |
+
list(bit),
|
| 1153 |
+
recursive=recursive,
|
| 1154 |
+
maxdepth=maxdepth - 1 if maxdepth is not None else None,
|
| 1155 |
+
**kwargs,
|
| 1156 |
+
)
|
| 1157 |
+
)
|
| 1158 |
+
continue
|
| 1159 |
+
elif recursive:
|
| 1160 |
+
rec = set(
|
| 1161 |
+
self.find(
|
| 1162 |
+
p, maxdepth=maxdepth, withdirs=True, detail=False, **kwargs
|
| 1163 |
+
)
|
| 1164 |
+
)
|
| 1165 |
+
out |= rec
|
| 1166 |
+
if p not in out and (recursive is False or self.exists(p)):
|
| 1167 |
+
# should only check once, for the root
|
| 1168 |
+
out.add(p)
|
| 1169 |
+
if not out:
|
| 1170 |
+
raise FileNotFoundError(path)
|
| 1171 |
+
return sorted(out)
|
| 1172 |
+
|
| 1173 |
+
def mv(self, path1, path2, recursive=False, maxdepth=None, **kwargs):
|
| 1174 |
+
"""Move file(s) from one location to another"""
|
| 1175 |
+
if path1 == path2:
|
| 1176 |
+
logger.debug("%s mv: The paths are the same, so no files were moved.", self)
|
| 1177 |
+
else:
|
| 1178 |
+
self.copy(path1, path2, recursive=recursive, maxdepth=maxdepth)
|
| 1179 |
+
self.rm(path1, recursive=recursive)
|
| 1180 |
+
|
| 1181 |
+
def rm_file(self, path):
|
| 1182 |
+
"""Delete a file"""
|
| 1183 |
+
self._rm(path)
|
| 1184 |
+
|
| 1185 |
+
def _rm(self, path):
|
| 1186 |
+
"""Delete one file"""
|
| 1187 |
+
# this is the old name for the method, prefer rm_file
|
| 1188 |
+
raise NotImplementedError
|
| 1189 |
+
|
| 1190 |
+
def rm(self, path, recursive=False, maxdepth=None):
|
| 1191 |
+
"""Delete files.
|
| 1192 |
+
|
| 1193 |
+
Parameters
|
| 1194 |
+
----------
|
| 1195 |
+
path: str or list of str
|
| 1196 |
+
File(s) to delete.
|
| 1197 |
+
recursive: bool
|
| 1198 |
+
If file(s) are directories, recursively delete contents and then
|
| 1199 |
+
also remove the directory
|
| 1200 |
+
maxdepth: int or None
|
| 1201 |
+
Depth to pass to walk for finding files to delete, if recursive.
|
| 1202 |
+
If None, there will be no limit and infinite recursion may be
|
| 1203 |
+
possible.
|
| 1204 |
+
"""
|
| 1205 |
+
path = self.expand_path(path, recursive=recursive, maxdepth=maxdepth)
|
| 1206 |
+
for p in reversed(path):
|
| 1207 |
+
self.rm_file(p)
|
| 1208 |
+
|
| 1209 |
+
@classmethod
|
| 1210 |
+
def _parent(cls, path):
|
| 1211 |
+
path = cls._strip_protocol(path)
|
| 1212 |
+
if "/" in path:
|
| 1213 |
+
parent = path.rsplit("/", 1)[0].lstrip(cls.root_marker)
|
| 1214 |
+
return cls.root_marker + parent
|
| 1215 |
+
else:
|
| 1216 |
+
return cls.root_marker
|
| 1217 |
+
|
| 1218 |
+
def _open(
|
| 1219 |
+
self,
|
| 1220 |
+
path,
|
| 1221 |
+
mode="rb",
|
| 1222 |
+
block_size=None,
|
| 1223 |
+
autocommit=True,
|
| 1224 |
+
cache_options=None,
|
| 1225 |
+
**kwargs,
|
| 1226 |
+
):
|
| 1227 |
+
"""Return raw bytes-mode file-like from the file-system"""
|
| 1228 |
+
return AbstractBufferedFile(
|
| 1229 |
+
self,
|
| 1230 |
+
path,
|
| 1231 |
+
mode,
|
| 1232 |
+
block_size,
|
| 1233 |
+
autocommit,
|
| 1234 |
+
cache_options=cache_options,
|
| 1235 |
+
**kwargs,
|
| 1236 |
+
)
|
| 1237 |
+
|
| 1238 |
+
def open(
|
| 1239 |
+
self,
|
| 1240 |
+
path,
|
| 1241 |
+
mode="rb",
|
| 1242 |
+
block_size=None,
|
| 1243 |
+
cache_options=None,
|
| 1244 |
+
compression=None,
|
| 1245 |
+
**kwargs,
|
| 1246 |
+
):
|
| 1247 |
+
"""
|
| 1248 |
+
Return a file-like object from the filesystem
|
| 1249 |
+
|
| 1250 |
+
The resultant instance must function correctly in a context ``with``
|
| 1251 |
+
block.
|
| 1252 |
+
|
| 1253 |
+
Parameters
|
| 1254 |
+
----------
|
| 1255 |
+
path: str
|
| 1256 |
+
Target file
|
| 1257 |
+
mode: str like 'rb', 'w'
|
| 1258 |
+
See builtin ``open()``
|
| 1259 |
+
block_size: int
|
| 1260 |
+
Some indication of buffering - this is a value in bytes
|
| 1261 |
+
cache_options : dict, optional
|
| 1262 |
+
Extra arguments to pass through to the cache.
|
| 1263 |
+
compression: string or None
|
| 1264 |
+
If given, open file using compression codec. Can either be a compression
|
| 1265 |
+
name (a key in ``fsspec.compression.compr``) or "infer" to guess the
|
| 1266 |
+
compression from the filename suffix.
|
| 1267 |
+
encoding, errors, newline: passed on to TextIOWrapper for text mode
|
| 1268 |
+
"""
|
| 1269 |
+
import io
|
| 1270 |
+
|
| 1271 |
+
path = self._strip_protocol(path)
|
| 1272 |
+
if "b" not in mode:
|
| 1273 |
+
mode = mode.replace("t", "") + "b"
|
| 1274 |
+
|
| 1275 |
+
text_kwargs = {
|
| 1276 |
+
k: kwargs.pop(k)
|
| 1277 |
+
for k in ["encoding", "errors", "newline"]
|
| 1278 |
+
if k in kwargs
|
| 1279 |
+
}
|
| 1280 |
+
return io.TextIOWrapper(
|
| 1281 |
+
self.open(
|
| 1282 |
+
path,
|
| 1283 |
+
mode,
|
| 1284 |
+
block_size=block_size,
|
| 1285 |
+
cache_options=cache_options,
|
| 1286 |
+
compression=compression,
|
| 1287 |
+
**kwargs,
|
| 1288 |
+
),
|
| 1289 |
+
**text_kwargs,
|
| 1290 |
+
)
|
| 1291 |
+
else:
|
| 1292 |
+
ac = kwargs.pop("autocommit", not self._intrans)
|
| 1293 |
+
f = self._open(
|
| 1294 |
+
path,
|
| 1295 |
+
mode=mode,
|
| 1296 |
+
block_size=block_size,
|
| 1297 |
+
autocommit=ac,
|
| 1298 |
+
cache_options=cache_options,
|
| 1299 |
+
**kwargs,
|
| 1300 |
+
)
|
| 1301 |
+
if compression is not None:
|
| 1302 |
+
from fsspec.compression import compr
|
| 1303 |
+
from fsspec.core import get_compression
|
| 1304 |
+
|
| 1305 |
+
compression = get_compression(path, compression)
|
| 1306 |
+
compress = compr[compression]
|
| 1307 |
+
f = compress(f, mode=mode[0])
|
| 1308 |
+
|
| 1309 |
+
if not ac and "r" not in mode:
|
| 1310 |
+
self.transaction.files.append(f)
|
| 1311 |
+
return f
|
| 1312 |
+
|
| 1313 |
+
def touch(self, path, truncate=True, **kwargs):
|
| 1314 |
+
"""Create empty file, or update timestamp
|
| 1315 |
+
|
| 1316 |
+
Parameters
|
| 1317 |
+
----------
|
| 1318 |
+
path: str
|
| 1319 |
+
file location
|
| 1320 |
+
truncate: bool
|
| 1321 |
+
If True, always set file size to 0; if False, update timestamp and
|
| 1322 |
+
leave file unchanged, if backend allows this
|
| 1323 |
+
"""
|
| 1324 |
+
if truncate or not self.exists(path):
|
| 1325 |
+
with self.open(path, "wb", **kwargs):
|
| 1326 |
+
pass
|
| 1327 |
+
else:
|
| 1328 |
+
raise NotImplementedError # update timestamp, if possible
|
| 1329 |
+
|
| 1330 |
+
def ukey(self, path):
|
| 1331 |
+
"""Hash of file properties, to tell if it has changed"""
|
| 1332 |
+
return sha256(str(self.info(path)).encode()).hexdigest()
|
| 1333 |
+
|
| 1334 |
+
def read_block(self, fn, offset, length, delimiter=None):
|
| 1335 |
+
"""Read a block of bytes from
|
| 1336 |
+
|
| 1337 |
+
Starting at ``offset`` of the file, read ``length`` bytes. If
|
| 1338 |
+
``delimiter`` is set then we ensure that the read starts and stops at
|
| 1339 |
+
delimiter boundaries that follow the locations ``offset`` and ``offset
|
| 1340 |
+
+ length``. If ``offset`` is zero then we start at zero. The
|
| 1341 |
+
bytestring returned WILL include the end delimiter string.
|
| 1342 |
+
|
| 1343 |
+
If offset+length is beyond the eof, reads to eof.
|
| 1344 |
+
|
| 1345 |
+
Parameters
|
| 1346 |
+
----------
|
| 1347 |
+
fn: string
|
| 1348 |
+
Path to filename
|
| 1349 |
+
offset: int
|
| 1350 |
+
Byte offset to start read
|
| 1351 |
+
length: int
|
| 1352 |
+
Number of bytes to read. If None, read to end.
|
| 1353 |
+
delimiter: bytes (optional)
|
| 1354 |
+
Ensure reading starts and stops at delimiter bytestring
|
| 1355 |
+
|
| 1356 |
+
Examples
|
| 1357 |
+
--------
|
| 1358 |
+
>>> fs.read_block('data/file.csv', 0, 13) # doctest: +SKIP
|
| 1359 |
+
b'Alice, 100\\nBo'
|
| 1360 |
+
>>> fs.read_block('data/file.csv', 0, 13, delimiter=b'\\n') # doctest: +SKIP
|
| 1361 |
+
b'Alice, 100\\nBob, 200\\n'
|
| 1362 |
+
|
| 1363 |
+
Use ``length=None`` to read to the end of the file.
|
| 1364 |
+
>>> fs.read_block('data/file.csv', 0, None, delimiter=b'\\n') # doctest: +SKIP
|
| 1365 |
+
b'Alice, 100\\nBob, 200\\nCharlie, 300'
|
| 1366 |
+
|
| 1367 |
+
See Also
|
| 1368 |
+
--------
|
| 1369 |
+
:func:`fsspec.utils.read_block`
|
| 1370 |
+
"""
|
| 1371 |
+
with self.open(fn, "rb") as f:
|
| 1372 |
+
size = f.size
|
| 1373 |
+
if length is None:
|
| 1374 |
+
length = size
|
| 1375 |
+
if size is not None and offset + length > size:
|
| 1376 |
+
length = size - offset
|
| 1377 |
+
return read_block(f, offset, length, delimiter)
|
| 1378 |
+
|
| 1379 |
+
def to_json(self):
|
| 1380 |
+
"""
|
| 1381 |
+
JSON representation of this filesystem instance
|
| 1382 |
+
|
| 1383 |
+
Returns
|
| 1384 |
+
-------
|
| 1385 |
+
str: JSON structure with keys cls (the python location of this class),
|
| 1386 |
+
protocol (text name of this class's protocol, first one in case of
|
| 1387 |
+
multiple), args (positional args, usually empty), and all other
|
| 1388 |
+
kwargs as their own keys.
|
| 1389 |
+
"""
|
| 1390 |
+
import json
|
| 1391 |
+
|
| 1392 |
+
cls = type(self)
|
| 1393 |
+
cls = ".".join((cls.__module__, cls.__name__))
|
| 1394 |
+
proto = (
|
| 1395 |
+
self.protocol[0]
|
| 1396 |
+
if isinstance(self.protocol, (tuple, list))
|
| 1397 |
+
else self.protocol
|
| 1398 |
+
)
|
| 1399 |
+
return json.dumps(
|
| 1400 |
+
dict(
|
| 1401 |
+
cls=cls,
|
| 1402 |
+
protocol=proto,
|
| 1403 |
+
args=self.storage_args,
|
| 1404 |
+
**self.storage_options,
|
| 1405 |
+
)
|
| 1406 |
+
)
|
| 1407 |
+
|
| 1408 |
+
@staticmethod
|
| 1409 |
+
def from_json(blob):
|
| 1410 |
+
"""
|
| 1411 |
+
Recreate a filesystem instance from JSON representation
|
| 1412 |
+
|
| 1413 |
+
See ``.to_json()`` for the expected structure of the input
|
| 1414 |
+
|
| 1415 |
+
Parameters
|
| 1416 |
+
----------
|
| 1417 |
+
blob: str
|
| 1418 |
+
|
| 1419 |
+
Returns
|
| 1420 |
+
-------
|
| 1421 |
+
file system instance, not necessarily of this particular class.
|
| 1422 |
+
"""
|
| 1423 |
+
import json
|
| 1424 |
+
|
| 1425 |
+
from .registry import _import_class, get_filesystem_class
|
| 1426 |
+
|
| 1427 |
+
dic = json.loads(blob)
|
| 1428 |
+
protocol = dic.pop("protocol")
|
| 1429 |
+
try:
|
| 1430 |
+
cls = _import_class(dic.pop("cls"))
|
| 1431 |
+
except (ImportError, ValueError, RuntimeError, KeyError):
|
| 1432 |
+
cls = get_filesystem_class(protocol)
|
| 1433 |
+
return cls(*dic.pop("args", ()), **dic)
|
| 1434 |
+
|
| 1435 |
+
def _get_pyarrow_filesystem(self):
|
| 1436 |
+
"""
|
| 1437 |
+
Make a version of the FS instance which will be acceptable to pyarrow
|
| 1438 |
+
"""
|
| 1439 |
+
# all instances already also derive from pyarrow
|
| 1440 |
+
return self
|
| 1441 |
+
|
| 1442 |
+
def get_mapper(self, root="", check=False, create=False, missing_exceptions=None):
|
| 1443 |
+
"""Create key/value store based on this file-system
|
| 1444 |
+
|
| 1445 |
+
Makes a MutableMapping interface to the FS at the given root path.
|
| 1446 |
+
See ``fsspec.mapping.FSMap`` for further details.
|
| 1447 |
+
"""
|
| 1448 |
+
from .mapping import FSMap
|
| 1449 |
+
|
| 1450 |
+
return FSMap(
|
| 1451 |
+
root,
|
| 1452 |
+
self,
|
| 1453 |
+
check=check,
|
| 1454 |
+
create=create,
|
| 1455 |
+
missing_exceptions=missing_exceptions,
|
| 1456 |
+
)
|
| 1457 |
+
|
| 1458 |
+
@classmethod
|
| 1459 |
+
def clear_instance_cache(cls):
|
| 1460 |
+
"""
|
| 1461 |
+
Clear the cache of filesystem instances.
|
| 1462 |
+
|
| 1463 |
+
Notes
|
| 1464 |
+
-----
|
| 1465 |
+
Unless overridden by setting the ``cachable`` class attribute to False,
|
| 1466 |
+
the filesystem class stores a reference to newly created instances. This
|
| 1467 |
+
prevents Python's normal rules around garbage collection from working,
|
| 1468 |
+
since the instances refcount will not drop to zero until
|
| 1469 |
+
``clear_instance_cache`` is called.
|
| 1470 |
+
"""
|
| 1471 |
+
cls._cache.clear()
|
| 1472 |
+
|
| 1473 |
+
def created(self, path):
|
| 1474 |
+
"""Return the created timestamp of a file as a datetime.datetime"""
|
| 1475 |
+
raise NotImplementedError
|
| 1476 |
+
|
| 1477 |
+
def modified(self, path):
|
| 1478 |
+
"""Return the modified timestamp of a file as a datetime.datetime"""
|
| 1479 |
+
raise NotImplementedError
|
| 1480 |
+
|
| 1481 |
+
# ------------------------------------------------------------------------
|
| 1482 |
+
# Aliases
|
| 1483 |
+
|
| 1484 |
+
def read_bytes(self, path, start=None, end=None, **kwargs):
|
| 1485 |
+
"""Alias of `AbstractFileSystem.cat_file`."""
|
| 1486 |
+
return self.cat_file(path, start=start, end=end, **kwargs)
|
| 1487 |
+
|
| 1488 |
+
def write_bytes(self, path, value, **kwargs):
|
| 1489 |
+
"""Alias of `AbstractFileSystem.pipe_file`."""
|
| 1490 |
+
self.pipe_file(path, value, **kwargs)
|
| 1491 |
+
|
| 1492 |
+
def makedir(self, path, create_parents=True, **kwargs):
|
| 1493 |
+
"""Alias of `AbstractFileSystem.mkdir`."""
|
| 1494 |
+
return self.mkdir(path, create_parents=create_parents, **kwargs)
|
| 1495 |
+
|
| 1496 |
+
def mkdirs(self, path, exist_ok=False):
|
| 1497 |
+
"""Alias of `AbstractFileSystem.makedirs`."""
|
| 1498 |
+
return self.makedirs(path, exist_ok=exist_ok)
|
| 1499 |
+
|
| 1500 |
+
def listdir(self, path, detail=True, **kwargs):
|
| 1501 |
+
"""Alias of `AbstractFileSystem.ls`."""
|
| 1502 |
+
return self.ls(path, detail=detail, **kwargs)
|
| 1503 |
+
|
| 1504 |
+
def cp(self, path1, path2, **kwargs):
|
| 1505 |
+
"""Alias of `AbstractFileSystem.copy`."""
|
| 1506 |
+
return self.copy(path1, path2, **kwargs)
|
| 1507 |
+
|
| 1508 |
+
def move(self, path1, path2, **kwargs):
|
| 1509 |
+
"""Alias of `AbstractFileSystem.mv`."""
|
| 1510 |
+
return self.mv(path1, path2, **kwargs)
|
| 1511 |
+
|
| 1512 |
+
def stat(self, path, **kwargs):
|
| 1513 |
+
"""Alias of `AbstractFileSystem.info`."""
|
| 1514 |
+
return self.info(path, **kwargs)
|
| 1515 |
+
|
| 1516 |
+
def disk_usage(self, path, total=True, maxdepth=None, **kwargs):
|
| 1517 |
+
"""Alias of `AbstractFileSystem.du`."""
|
| 1518 |
+
return self.du(path, total=total, maxdepth=maxdepth, **kwargs)
|
| 1519 |
+
|
| 1520 |
+
def rename(self, path1, path2, **kwargs):
|
| 1521 |
+
"""Alias of `AbstractFileSystem.mv`."""
|
| 1522 |
+
return self.mv(path1, path2, **kwargs)
|
| 1523 |
+
|
| 1524 |
+
def delete(self, path, recursive=False, maxdepth=None):
|
| 1525 |
+
"""Alias of `AbstractFileSystem.rm`."""
|
| 1526 |
+
return self.rm(path, recursive=recursive, maxdepth=maxdepth)
|
| 1527 |
+
|
| 1528 |
+
def upload(self, lpath, rpath, recursive=False, **kwargs):
|
| 1529 |
+
"""Alias of `AbstractFileSystem.put`."""
|
| 1530 |
+
return self.put(lpath, rpath, recursive=recursive, **kwargs)
|
| 1531 |
+
|
| 1532 |
+
def download(self, rpath, lpath, recursive=False, **kwargs):
|
| 1533 |
+
"""Alias of `AbstractFileSystem.get`."""
|
| 1534 |
+
return self.get(rpath, lpath, recursive=recursive, **kwargs)
|
| 1535 |
+
|
| 1536 |
+
def sign(self, path, expiration=100, **kwargs):
|
| 1537 |
+
"""Create a signed URL representing the given path
|
| 1538 |
+
|
| 1539 |
+
Some implementations allow temporary URLs to be generated, as a
|
| 1540 |
+
way of delegating credentials.
|
| 1541 |
+
|
| 1542 |
+
Parameters
|
| 1543 |
+
----------
|
| 1544 |
+
path : str
|
| 1545 |
+
The path on the filesystem
|
| 1546 |
+
expiration : int
|
| 1547 |
+
Number of seconds to enable the URL for (if supported)
|
| 1548 |
+
|
| 1549 |
+
Returns
|
| 1550 |
+
-------
|
| 1551 |
+
URL : str
|
| 1552 |
+
The signed URL
|
| 1553 |
+
|
| 1554 |
+
Raises
|
| 1555 |
+
------
|
| 1556 |
+
NotImplementedError : if method is not implemented for a filesystem
|
| 1557 |
+
"""
|
| 1558 |
+
raise NotImplementedError("Sign is not implemented for this filesystem")
|
| 1559 |
+
|
| 1560 |
+
def _isfilestore(self):
|
| 1561 |
+
# Originally inherited from pyarrow DaskFileSystem. Keeping this
|
| 1562 |
+
# here for backwards compatibility as long as pyarrow uses its
|
| 1563 |
+
# legacy fsspec-compatible filesystems and thus accepts fsspec
|
| 1564 |
+
# filesystems as well
|
| 1565 |
+
return False
|
| 1566 |
+
|
| 1567 |
+
|
| 1568 |
+
class AbstractBufferedFile(io.IOBase):
|
| 1569 |
+
"""Convenient class to derive from to provide buffering
|
| 1570 |
+
|
| 1571 |
+
In the case that the backend does not provide a pythonic file-like object
|
| 1572 |
+
already, this class contains much of the logic to build one. The only
|
| 1573 |
+
methods that need to be overridden are ``_upload_chunk``,
|
| 1574 |
+
``_initiate_upload`` and ``_fetch_range``.
|
| 1575 |
+
"""
|
| 1576 |
+
|
| 1577 |
+
DEFAULT_BLOCK_SIZE = 5 * 2**20
|
| 1578 |
+
_details = None
|
| 1579 |
+
|
| 1580 |
+
def __init__(
|
| 1581 |
+
self,
|
| 1582 |
+
fs,
|
| 1583 |
+
path,
|
| 1584 |
+
mode="rb",
|
| 1585 |
+
block_size="default",
|
| 1586 |
+
autocommit=True,
|
| 1587 |
+
cache_type="readahead",
|
| 1588 |
+
cache_options=None,
|
| 1589 |
+
size=None,
|
| 1590 |
+
**kwargs,
|
| 1591 |
+
):
|
| 1592 |
+
"""
|
| 1593 |
+
Template for files with buffered reading and writing
|
| 1594 |
+
|
| 1595 |
+
Parameters
|
| 1596 |
+
----------
|
| 1597 |
+
fs: instance of FileSystem
|
| 1598 |
+
path: str
|
| 1599 |
+
location in file-system
|
| 1600 |
+
mode: str
|
| 1601 |
+
Normal file modes. Currently only 'wb', 'ab' or 'rb'. Some file
|
| 1602 |
+
systems may be read-only, and some may not support append.
|
| 1603 |
+
block_size: int
|
| 1604 |
+
Buffer size for reading or writing, 'default' for class default
|
| 1605 |
+
autocommit: bool
|
| 1606 |
+
Whether to write to final destination; may only impact what
|
| 1607 |
+
happens when file is being closed.
|
| 1608 |
+
cache_type: {"readahead", "none", "mmap", "bytes"}, default "readahead"
|
| 1609 |
+
Caching policy in read mode. See the definitions in ``core``.
|
| 1610 |
+
cache_options : dict
|
| 1611 |
+
Additional options passed to the constructor for the cache specified
|
| 1612 |
+
by `cache_type`.
|
| 1613 |
+
size: int
|
| 1614 |
+
If given and in read mode, suppressed having to look up the file size
|
| 1615 |
+
kwargs:
|
| 1616 |
+
Gets stored as self.kwargs
|
| 1617 |
+
"""
|
| 1618 |
+
from .core import caches
|
| 1619 |
+
|
| 1620 |
+
self.path = path
|
| 1621 |
+
self.fs = fs
|
| 1622 |
+
self.mode = mode
|
| 1623 |
+
self.blocksize = (
|
| 1624 |
+
self.DEFAULT_BLOCK_SIZE if block_size in ["default", None] else block_size
|
| 1625 |
+
)
|
| 1626 |
+
self.loc = 0
|
| 1627 |
+
self.autocommit = autocommit
|
| 1628 |
+
self.end = None
|
| 1629 |
+
self.start = None
|
| 1630 |
+
self.closed = False
|
| 1631 |
+
|
| 1632 |
+
if cache_options is None:
|
| 1633 |
+
cache_options = {}
|
| 1634 |
+
|
| 1635 |
+
if "trim" in kwargs:
|
| 1636 |
+
warnings.warn(
|
| 1637 |
+
"Passing 'trim' to control the cache behavior has been deprecated. "
|
| 1638 |
+
"Specify it within the 'cache_options' argument instead.",
|
| 1639 |
+
FutureWarning,
|
| 1640 |
+
)
|
| 1641 |
+
cache_options["trim"] = kwargs.pop("trim")
|
| 1642 |
+
|
| 1643 |
+
self.kwargs = kwargs
|
| 1644 |
+
|
| 1645 |
+
if mode not in {"ab", "rb", "wb"}:
|
| 1646 |
+
raise NotImplementedError("File mode not supported")
|
| 1647 |
+
if mode == "rb":
|
| 1648 |
+
if size is not None:
|
| 1649 |
+
self.size = size
|
| 1650 |
+
else:
|
| 1651 |
+
self.size = self.details["size"]
|
| 1652 |
+
self.cache = caches[cache_type](
|
| 1653 |
+
self.blocksize, self._fetch_range, self.size, **cache_options
|
| 1654 |
+
)
|
| 1655 |
+
else:
|
| 1656 |
+
self.buffer = io.BytesIO()
|
| 1657 |
+
self.offset = None
|
| 1658 |
+
self.forced = False
|
| 1659 |
+
self.location = None
|
| 1660 |
+
|
| 1661 |
+
@property
|
| 1662 |
+
def details(self):
|
| 1663 |
+
if self._details is None:
|
| 1664 |
+
self._details = self.fs.info(self.path)
|
| 1665 |
+
return self._details
|
| 1666 |
+
|
| 1667 |
+
@details.setter
|
| 1668 |
+
def details(self, value):
|
| 1669 |
+
self._details = value
|
| 1670 |
+
self.size = value["size"]
|
| 1671 |
+
|
| 1672 |
+
@property
|
| 1673 |
+
def full_name(self):
|
| 1674 |
+
return _unstrip_protocol(self.path, self.fs)
|
| 1675 |
+
|
| 1676 |
+
@property
|
| 1677 |
+
def closed(self):
|
| 1678 |
+
# get around this attr being read-only in IOBase
|
| 1679 |
+
# use getattr here, since this can be called during del
|
| 1680 |
+
return getattr(self, "_closed", True)
|
| 1681 |
+
|
| 1682 |
+
@closed.setter
|
| 1683 |
+
def closed(self, c):
|
| 1684 |
+
self._closed = c
|
| 1685 |
+
|
| 1686 |
+
def __hash__(self):
|
| 1687 |
+
if "w" in self.mode:
|
| 1688 |
+
return id(self)
|
| 1689 |
+
else:
|
| 1690 |
+
return int(tokenize(self.details), 16)
|
| 1691 |
+
|
| 1692 |
+
def __eq__(self, other):
|
| 1693 |
+
"""Files are equal if they have the same checksum, only in read mode"""
|
| 1694 |
+
if self is other:
|
| 1695 |
+
return True
|
| 1696 |
+
return self.mode == "rb" and other.mode == "rb" and hash(self) == hash(other)
|
| 1697 |
+
|
| 1698 |
+
def commit(self):
|
| 1699 |
+
"""Move from temp to final destination"""
|
| 1700 |
+
|
| 1701 |
+
def discard(self):
|
| 1702 |
+
"""Throw away temporary file"""
|
| 1703 |
+
|
| 1704 |
+
def info(self):
|
| 1705 |
+
"""File information about this path"""
|
| 1706 |
+
if "r" in self.mode:
|
| 1707 |
+
return self.details
|
| 1708 |
+
else:
|
| 1709 |
+
raise ValueError("Info not available while writing")
|
| 1710 |
+
|
| 1711 |
+
def tell(self):
|
| 1712 |
+
"""Current file location"""
|
| 1713 |
+
return self.loc
|
| 1714 |
+
|
| 1715 |
+
def seek(self, loc, whence=0):
|
| 1716 |
+
"""Set current file location
|
| 1717 |
+
|
| 1718 |
+
Parameters
|
| 1719 |
+
----------
|
| 1720 |
+
loc: int
|
| 1721 |
+
byte location
|
| 1722 |
+
whence: {0, 1, 2}
|
| 1723 |
+
from start of file, current location or end of file, resp.
|
| 1724 |
+
"""
|
| 1725 |
+
loc = int(loc)
|
| 1726 |
+
if not self.mode == "rb":
|
| 1727 |
+
raise OSError(ESPIPE, "Seek only available in read mode")
|
| 1728 |
+
if whence == 0:
|
| 1729 |
+
nloc = loc
|
| 1730 |
+
elif whence == 1:
|
| 1731 |
+
nloc = self.loc + loc
|
| 1732 |
+
elif whence == 2:
|
| 1733 |
+
nloc = self.size + loc
|
| 1734 |
+
else:
|
| 1735 |
+
raise ValueError(f"invalid whence ({whence}, should be 0, 1 or 2)")
|
| 1736 |
+
if nloc < 0:
|
| 1737 |
+
raise ValueError("Seek before start of file")
|
| 1738 |
+
self.loc = nloc
|
| 1739 |
+
return self.loc
|
| 1740 |
+
|
| 1741 |
+
def write(self, data):
|
| 1742 |
+
"""
|
| 1743 |
+
Write data to buffer.
|
| 1744 |
+
|
| 1745 |
+
Buffer only sent on flush() or if buffer is greater than
|
| 1746 |
+
or equal to blocksize.
|
| 1747 |
+
|
| 1748 |
+
Parameters
|
| 1749 |
+
----------
|
| 1750 |
+
data: bytes
|
| 1751 |
+
Set of bytes to be written.
|
| 1752 |
+
"""
|
| 1753 |
+
if self.mode not in {"wb", "ab"}:
|
| 1754 |
+
raise ValueError("File not in write mode")
|
| 1755 |
+
if self.closed:
|
| 1756 |
+
raise ValueError("I/O operation on closed file.")
|
| 1757 |
+
if self.forced:
|
| 1758 |
+
raise ValueError("This file has been force-flushed, can only close")
|
| 1759 |
+
out = self.buffer.write(data)
|
| 1760 |
+
self.loc += out
|
| 1761 |
+
if self.buffer.tell() >= self.blocksize:
|
| 1762 |
+
self.flush()
|
| 1763 |
+
return out
|
| 1764 |
+
|
| 1765 |
+
def flush(self, force=False):
|
| 1766 |
+
"""
|
| 1767 |
+
Write buffered data to backend store.
|
| 1768 |
+
|
| 1769 |
+
Writes the current buffer, if it is larger than the block-size, or if
|
| 1770 |
+
the file is being closed.
|
| 1771 |
+
|
| 1772 |
+
Parameters
|
| 1773 |
+
----------
|
| 1774 |
+
force: bool
|
| 1775 |
+
When closing, write the last block even if it is smaller than
|
| 1776 |
+
blocks are allowed to be. Disallows further writing to this file.
|
| 1777 |
+
"""
|
| 1778 |
+
|
| 1779 |
+
if self.closed:
|
| 1780 |
+
raise ValueError("Flush on closed file")
|
| 1781 |
+
if force and self.forced:
|
| 1782 |
+
raise ValueError("Force flush cannot be called more than once")
|
| 1783 |
+
if force:
|
| 1784 |
+
self.forced = True
|
| 1785 |
+
|
| 1786 |
+
if self.mode not in {"wb", "ab"}:
|
| 1787 |
+
# no-op to flush on read-mode
|
| 1788 |
+
return
|
| 1789 |
+
|
| 1790 |
+
if not force and self.buffer.tell() < self.blocksize:
|
| 1791 |
+
# Defer write on small block
|
| 1792 |
+
return
|
| 1793 |
+
|
| 1794 |
+
if self.offset is None:
|
| 1795 |
+
# Initialize a multipart upload
|
| 1796 |
+
self.offset = 0
|
| 1797 |
+
try:
|
| 1798 |
+
self._initiate_upload()
|
| 1799 |
+
except: # noqa: E722
|
| 1800 |
+
self.closed = True
|
| 1801 |
+
raise
|
| 1802 |
+
|
| 1803 |
+
if self._upload_chunk(final=force) is not False:
|
| 1804 |
+
self.offset += self.buffer.seek(0, 2)
|
| 1805 |
+
self.buffer = io.BytesIO()
|
| 1806 |
+
|
| 1807 |
+
def _upload_chunk(self, final=False):
|
| 1808 |
+
"""Write one part of a multi-block file upload
|
| 1809 |
+
|
| 1810 |
+
Parameters
|
| 1811 |
+
==========
|
| 1812 |
+
final: bool
|
| 1813 |
+
This is the last block, so should complete file, if
|
| 1814 |
+
self.autocommit is True.
|
| 1815 |
+
"""
|
| 1816 |
+
# may not yet have been initialized, may need to call _initialize_upload
|
| 1817 |
+
|
| 1818 |
+
def _initiate_upload(self):
|
| 1819 |
+
"""Create remote file/upload"""
|
| 1820 |
+
pass
|
| 1821 |
+
|
| 1822 |
+
def _fetch_range(self, start, end):
|
| 1823 |
+
"""Get the specified set of bytes from remote"""
|
| 1824 |
+
raise NotImplementedError
|
| 1825 |
+
|
| 1826 |
+
def read(self, length=-1):
|
| 1827 |
+
"""
|
| 1828 |
+
Return data from cache, or fetch pieces as necessary
|
| 1829 |
+
|
| 1830 |
+
Parameters
|
| 1831 |
+
----------
|
| 1832 |
+
length: int (-1)
|
| 1833 |
+
Number of bytes to read; if <0, all remaining bytes.
|
| 1834 |
+
"""
|
| 1835 |
+
length = -1 if length is None else int(length)
|
| 1836 |
+
if self.mode != "rb":
|
| 1837 |
+
raise ValueError("File not in read mode")
|
| 1838 |
+
if length < 0:
|
| 1839 |
+
length = self.size - self.loc
|
| 1840 |
+
if self.closed:
|
| 1841 |
+
raise ValueError("I/O operation on closed file.")
|
| 1842 |
+
logger.debug("%s read: %i - %i", self, self.loc, self.loc + length)
|
| 1843 |
+
if length == 0:
|
| 1844 |
+
# don't even bother calling fetch
|
| 1845 |
+
return b""
|
| 1846 |
+
out = self.cache._fetch(self.loc, self.loc + length)
|
| 1847 |
+
self.loc += len(out)
|
| 1848 |
+
return out
|
| 1849 |
+
|
| 1850 |
+
def readinto(self, b):
|
| 1851 |
+
"""mirrors builtin file's readinto method
|
| 1852 |
+
|
| 1853 |
+
https://docs.python.org/3/library/io.html#io.RawIOBase.readinto
|
| 1854 |
+
"""
|
| 1855 |
+
out = memoryview(b).cast("B")
|
| 1856 |
+
data = self.read(out.nbytes)
|
| 1857 |
+
out[: len(data)] = data
|
| 1858 |
+
return len(data)
|
| 1859 |
+
|
| 1860 |
+
def readuntil(self, char=b"\n", blocks=None):
|
| 1861 |
+
"""Return data between current position and first occurrence of char
|
| 1862 |
+
|
| 1863 |
+
char is included in the output, except if the end of the tile is
|
| 1864 |
+
encountered first.
|
| 1865 |
+
|
| 1866 |
+
Parameters
|
| 1867 |
+
----------
|
| 1868 |
+
char: bytes
|
| 1869 |
+
Thing to find
|
| 1870 |
+
blocks: None or int
|
| 1871 |
+
How much to read in each go. Defaults to file blocksize - which may
|
| 1872 |
+
mean a new read on every call.
|
| 1873 |
+
"""
|
| 1874 |
+
out = []
|
| 1875 |
+
while True:
|
| 1876 |
+
start = self.tell()
|
| 1877 |
+
part = self.read(blocks or self.blocksize)
|
| 1878 |
+
if len(part) == 0:
|
| 1879 |
+
break
|
| 1880 |
+
found = part.find(char)
|
| 1881 |
+
if found > -1:
|
| 1882 |
+
out.append(part[: found + len(char)])
|
| 1883 |
+
self.seek(start + found + len(char))
|
| 1884 |
+
break
|
| 1885 |
+
out.append(part)
|
| 1886 |
+
return b"".join(out)
|
| 1887 |
+
|
| 1888 |
+
def readline(self):
|
| 1889 |
+
"""Read until first occurrence of newline character
|
| 1890 |
+
|
| 1891 |
+
Note that, because of character encoding, this is not necessarily a
|
| 1892 |
+
true line ending.
|
| 1893 |
+
"""
|
| 1894 |
+
return self.readuntil(b"\n")
|
| 1895 |
+
|
| 1896 |
+
def __next__(self):
|
| 1897 |
+
out = self.readline()
|
| 1898 |
+
if out:
|
| 1899 |
+
return out
|
| 1900 |
+
raise StopIteration
|
| 1901 |
+
|
| 1902 |
+
def __iter__(self):
|
| 1903 |
+
return self
|
| 1904 |
+
|
| 1905 |
+
def readlines(self):
|
| 1906 |
+
"""Return all data, split by the newline character"""
|
| 1907 |
+
data = self.read()
|
| 1908 |
+
lines = data.split(b"\n")
|
| 1909 |
+
out = [l + b"\n" for l in lines[:-1]]
|
| 1910 |
+
if data.endswith(b"\n"):
|
| 1911 |
+
return out
|
| 1912 |
+
else:
|
| 1913 |
+
return out + [lines[-1]]
|
| 1914 |
+
# return list(self) ???
|
| 1915 |
+
|
| 1916 |
+
def readinto1(self, b):
|
| 1917 |
+
return self.readinto(b)
|
| 1918 |
+
|
| 1919 |
+
def close(self):
|
| 1920 |
+
"""Close file
|
| 1921 |
+
|
| 1922 |
+
Finalizes writes, discards cache
|
| 1923 |
+
"""
|
| 1924 |
+
if getattr(self, "_unclosable", False):
|
| 1925 |
+
return
|
| 1926 |
+
if self.closed:
|
| 1927 |
+
return
|
| 1928 |
+
if self.mode == "rb":
|
| 1929 |
+
self.cache = None
|
| 1930 |
+
else:
|
| 1931 |
+
if not self.forced:
|
| 1932 |
+
self.flush(force=True)
|
| 1933 |
+
|
| 1934 |
+
if self.fs is not None:
|
| 1935 |
+
self.fs.invalidate_cache(self.path)
|
| 1936 |
+
self.fs.invalidate_cache(self.fs._parent(self.path))
|
| 1937 |
+
|
| 1938 |
+
self.closed = True
|
| 1939 |
+
|
| 1940 |
+
def readable(self):
|
| 1941 |
+
"""Whether opened for reading"""
|
| 1942 |
+
return self.mode == "rb" and not self.closed
|
| 1943 |
+
|
| 1944 |
+
def seekable(self):
|
| 1945 |
+
"""Whether is seekable (only in read mode)"""
|
| 1946 |
+
return self.readable()
|
| 1947 |
+
|
| 1948 |
+
def writable(self):
|
| 1949 |
+
"""Whether opened for writing"""
|
| 1950 |
+
return self.mode in {"wb", "ab"} and not self.closed
|
| 1951 |
+
|
| 1952 |
+
def __del__(self):
|
| 1953 |
+
if not self.closed:
|
| 1954 |
+
self.close()
|
| 1955 |
+
|
| 1956 |
+
def __str__(self):
|
| 1957 |
+
return f"<File-like object {type(self.fs).__name__}, {self.path}>"
|
| 1958 |
+
|
| 1959 |
+
__repr__ = __str__
|
| 1960 |
+
|
| 1961 |
+
def __enter__(self):
|
| 1962 |
+
return self
|
| 1963 |
+
|
| 1964 |
+
def __exit__(self, *args):
|
| 1965 |
+
self.close()
|
tuning-competition-baseline/.venv/lib/python3.11/site-packages/functorch/__pycache__/__init__.cpython-311.pyc
ADDED
|
Binary file (933 Bytes). View file
|
|
|
tuning-competition-baseline/.venv/lib/python3.11/site-packages/functorch/_src/__init__.py
ADDED
|
File without changes
|
tuning-competition-baseline/.venv/lib/python3.11/site-packages/functorch/_src/eager_transforms/__init__.py
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# This file has moved to under torch/_functorch. It is not public API.
|
| 2 |
+
# If you are not a PyTorch developer and you are relying on the following
|
| 3 |
+
# imports, please file an issue.
|
| 4 |
+
from torch._functorch.eager_transforms import (
|
| 5 |
+
_assert_wrapped_functional,
|
| 6 |
+
_unwrap_functional_tensor,
|
| 7 |
+
)
|
tuning-competition-baseline/.venv/lib/python3.11/site-packages/functorch/_src/eager_transforms/__pycache__/__init__.cpython-311.pyc
ADDED
|
Binary file (392 Bytes). View file
|
|
|
tuning-competition-baseline/.venv/lib/python3.11/site-packages/functorch/compile/__pycache__/__init__.cpython-311.pyc
ADDED
|
Binary file (1.45 kB). View file
|
|
|
tuning-competition-baseline/.venv/lib/python3.11/site-packages/functorch/dim/__pycache__/dim.cpython-311.pyc
ADDED
|
Binary file (7.09 kB). View file
|
|
|
tuning-competition-baseline/.venv/lib/python3.11/site-packages/functorch/dim/dim.py
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Facebook, Inc. and its affiliates.
|
| 2 |
+
# All rights reserved.
|
| 3 |
+
#
|
| 4 |
+
# This source code is licensed under the BSD-style license found in the
|
| 5 |
+
# LICENSE file in the root directory of this source tree.
|
| 6 |
+
import dis
|
| 7 |
+
import inspect
|
| 8 |
+
|
| 9 |
+
from dataclasses import dataclass
|
| 10 |
+
from typing import Union
|
| 11 |
+
|
| 12 |
+
from . import DimList
|
| 13 |
+
|
| 14 |
+
_vmap_levels = []
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
@dataclass
|
| 18 |
+
class LevelInfo:
|
| 19 |
+
level: int
|
| 20 |
+
alive: bool = True
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
class Dim:
|
| 24 |
+
def __init__(self, name: str, size: Union[None, int] = None):
|
| 25 |
+
self.name = name
|
| 26 |
+
self._size = None
|
| 27 |
+
self._vmap_level = None
|
| 28 |
+
if size is not None:
|
| 29 |
+
self.size = size
|
| 30 |
+
|
| 31 |
+
def __del__(self):
|
| 32 |
+
if self._vmap_level is not None:
|
| 33 |
+
_vmap_active_levels[self._vmap_stack].alive = False # noqa: F821
|
| 34 |
+
while (
|
| 35 |
+
not _vmap_levels[-1].alive
|
| 36 |
+
and current_level() == _vmap_levels[-1].level # noqa: F821
|
| 37 |
+
):
|
| 38 |
+
_vmap_decrement_nesting() # noqa: F821
|
| 39 |
+
_vmap_levels.pop()
|
| 40 |
+
|
| 41 |
+
@property
|
| 42 |
+
def size(self):
|
| 43 |
+
assert self.is_bound
|
| 44 |
+
return self._size
|
| 45 |
+
|
| 46 |
+
@size.setter
|
| 47 |
+
def size(self, size: int):
|
| 48 |
+
from . import DimensionBindError
|
| 49 |
+
|
| 50 |
+
if self._size is None:
|
| 51 |
+
self._size = size
|
| 52 |
+
self._vmap_level = _vmap_increment_nesting(size, "same") # noqa: F821
|
| 53 |
+
self._vmap_stack = len(_vmap_levels)
|
| 54 |
+
_vmap_levels.append(LevelInfo(self._vmap_level))
|
| 55 |
+
|
| 56 |
+
elif self._size != size:
|
| 57 |
+
raise DimensionBindError(
|
| 58 |
+
f"Dim '{self}' previously bound to a dimension of size {self._size} cannot bind to a dimension of size {size}"
|
| 59 |
+
)
|
| 60 |
+
|
| 61 |
+
@property
|
| 62 |
+
def is_bound(self):
|
| 63 |
+
return self._size is not None
|
| 64 |
+
|
| 65 |
+
def __repr__(self):
|
| 66 |
+
return self.name
|
| 67 |
+
|
| 68 |
+
|
| 69 |
+
def extract_name(inst):
|
| 70 |
+
assert inst.opname == "STORE_FAST" or inst.opname == "STORE_NAME"
|
| 71 |
+
return inst.argval
|
| 72 |
+
|
| 73 |
+
|
| 74 |
+
_cache = {}
|
| 75 |
+
|
| 76 |
+
|
| 77 |
+
def dims(lists=0):
|
| 78 |
+
frame = inspect.currentframe()
|
| 79 |
+
assert frame is not None
|
| 80 |
+
calling_frame = frame.f_back
|
| 81 |
+
assert calling_frame is not None
|
| 82 |
+
code, lasti = calling_frame.f_code, calling_frame.f_lasti
|
| 83 |
+
key = (code, lasti)
|
| 84 |
+
if key not in _cache:
|
| 85 |
+
first = lasti // 2 + 1
|
| 86 |
+
instructions = list(dis.get_instructions(calling_frame.f_code))
|
| 87 |
+
unpack = instructions[first]
|
| 88 |
+
|
| 89 |
+
if unpack.opname == "STORE_FAST" or unpack.opname == "STORE_NAME":
|
| 90 |
+
# just a single dim, not a list
|
| 91 |
+
name = unpack.argval
|
| 92 |
+
ctor = Dim if lists == 0 else DimList
|
| 93 |
+
_cache[key] = lambda: ctor(name=name)
|
| 94 |
+
else:
|
| 95 |
+
assert unpack.opname == "UNPACK_SEQUENCE"
|
| 96 |
+
ndims = unpack.argval
|
| 97 |
+
names = tuple(
|
| 98 |
+
extract_name(instructions[first + 1 + i]) for i in range(ndims)
|
| 99 |
+
)
|
| 100 |
+
first_list = len(names) - lists
|
| 101 |
+
_cache[key] = lambda: tuple(
|
| 102 |
+
Dim(n) if i < first_list else DimList(name=n)
|
| 103 |
+
for i, n in enumerate(names)
|
| 104 |
+
)
|
| 105 |
+
return _cache[key]()
|
| 106 |
+
|
| 107 |
+
|
| 108 |
+
def _dim_set(positional, arg):
|
| 109 |
+
def convert(a):
|
| 110 |
+
if isinstance(a, Dim):
|
| 111 |
+
return a
|
| 112 |
+
else:
|
| 113 |
+
assert isinstance(a, int)
|
| 114 |
+
return positional[a]
|
| 115 |
+
|
| 116 |
+
if arg is None:
|
| 117 |
+
return positional
|
| 118 |
+
elif not isinstance(arg, (Dim, int)):
|
| 119 |
+
return tuple(convert(a) for a in arg)
|
| 120 |
+
else:
|
| 121 |
+
return (convert(arg),)
|
tuning-competition-baseline/.venv/lib/python3.11/site-packages/functorch/dim/op_properties.py
ADDED
|
@@ -0,0 +1,311 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Facebook, Inc. and its affiliates.
|
| 2 |
+
# All rights reserved.
|
| 3 |
+
#
|
| 4 |
+
# This source code is licensed under the BSD-style license found in the
|
| 5 |
+
# LICENSE file in the root directory of this source tree.
|
| 6 |
+
import torch
|
| 7 |
+
|
| 8 |
+
# pointwise operators can go through a faster pathway
|
| 9 |
+
|
| 10 |
+
tensor_magic_methods = ["add", ""]
|
| 11 |
+
pointwise_magic_methods_with_reverse = (
|
| 12 |
+
"add",
|
| 13 |
+
"sub",
|
| 14 |
+
"mul",
|
| 15 |
+
"floordiv",
|
| 16 |
+
"div",
|
| 17 |
+
"truediv",
|
| 18 |
+
"mod",
|
| 19 |
+
"pow",
|
| 20 |
+
"lshift",
|
| 21 |
+
"rshift",
|
| 22 |
+
"and",
|
| 23 |
+
"or",
|
| 24 |
+
"xor",
|
| 25 |
+
)
|
| 26 |
+
pointwise_magic_methods = (
|
| 27 |
+
*(x for m in pointwise_magic_methods_with_reverse for x in (m, "r" + m)),
|
| 28 |
+
"eq",
|
| 29 |
+
"gt",
|
| 30 |
+
"le",
|
| 31 |
+
"lt",
|
| 32 |
+
"ge",
|
| 33 |
+
"gt",
|
| 34 |
+
"ne",
|
| 35 |
+
"neg",
|
| 36 |
+
"pos",
|
| 37 |
+
"abs",
|
| 38 |
+
"invert",
|
| 39 |
+
"iadd",
|
| 40 |
+
"isub",
|
| 41 |
+
"imul",
|
| 42 |
+
"ifloordiv",
|
| 43 |
+
"idiv",
|
| 44 |
+
"itruediv",
|
| 45 |
+
"imod",
|
| 46 |
+
"ipow",
|
| 47 |
+
"ilshift",
|
| 48 |
+
"irshift",
|
| 49 |
+
"iand",
|
| 50 |
+
"ior",
|
| 51 |
+
"ixor",
|
| 52 |
+
"int",
|
| 53 |
+
"long",
|
| 54 |
+
"float",
|
| 55 |
+
"complex",
|
| 56 |
+
)
|
| 57 |
+
|
| 58 |
+
pointwise_methods = (*(f"__{m}__" for m in pointwise_magic_methods),)
|
| 59 |
+
|
| 60 |
+
pointwise = (
|
| 61 |
+
*(getattr(torch.Tensor, m) for m in pointwise_methods),
|
| 62 |
+
torch.nn.functional.dropout,
|
| 63 |
+
torch.where,
|
| 64 |
+
torch.Tensor.abs,
|
| 65 |
+
torch.abs,
|
| 66 |
+
torch.Tensor.acos,
|
| 67 |
+
torch.acos,
|
| 68 |
+
torch.Tensor.acosh,
|
| 69 |
+
torch.acosh,
|
| 70 |
+
torch.Tensor.add,
|
| 71 |
+
torch.add,
|
| 72 |
+
torch.Tensor.addcdiv,
|
| 73 |
+
torch.addcdiv,
|
| 74 |
+
torch.Tensor.addcmul,
|
| 75 |
+
torch.addcmul,
|
| 76 |
+
torch.Tensor.addr,
|
| 77 |
+
torch.addr,
|
| 78 |
+
torch.Tensor.angle,
|
| 79 |
+
torch.angle,
|
| 80 |
+
torch.Tensor.asin,
|
| 81 |
+
torch.asin,
|
| 82 |
+
torch.Tensor.asinh,
|
| 83 |
+
torch.asinh,
|
| 84 |
+
torch.Tensor.atan,
|
| 85 |
+
torch.atan,
|
| 86 |
+
torch.Tensor.atan2,
|
| 87 |
+
torch.atan2,
|
| 88 |
+
torch.Tensor.atanh,
|
| 89 |
+
torch.atanh,
|
| 90 |
+
torch.Tensor.bitwise_and,
|
| 91 |
+
torch.bitwise_and,
|
| 92 |
+
torch.Tensor.bitwise_left_shift,
|
| 93 |
+
torch.bitwise_left_shift,
|
| 94 |
+
torch.Tensor.bitwise_not,
|
| 95 |
+
torch.bitwise_not,
|
| 96 |
+
torch.Tensor.bitwise_or,
|
| 97 |
+
torch.bitwise_or,
|
| 98 |
+
torch.Tensor.bitwise_right_shift,
|
| 99 |
+
torch.bitwise_right_shift,
|
| 100 |
+
torch.Tensor.bitwise_xor,
|
| 101 |
+
torch.bitwise_xor,
|
| 102 |
+
torch.Tensor.ceil,
|
| 103 |
+
torch.ceil,
|
| 104 |
+
torch.celu,
|
| 105 |
+
torch.nn.functional.celu,
|
| 106 |
+
torch.Tensor.clamp,
|
| 107 |
+
torch.clamp,
|
| 108 |
+
torch.Tensor.clamp_max,
|
| 109 |
+
torch.clamp_max,
|
| 110 |
+
torch.Tensor.clamp_min,
|
| 111 |
+
torch.clamp_min,
|
| 112 |
+
torch.Tensor.copysign,
|
| 113 |
+
torch.copysign,
|
| 114 |
+
torch.Tensor.cos,
|
| 115 |
+
torch.cos,
|
| 116 |
+
torch.Tensor.cosh,
|
| 117 |
+
torch.cosh,
|
| 118 |
+
torch.Tensor.deg2rad,
|
| 119 |
+
torch.deg2rad,
|
| 120 |
+
torch.Tensor.digamma,
|
| 121 |
+
torch.digamma,
|
| 122 |
+
torch.Tensor.div,
|
| 123 |
+
torch.div,
|
| 124 |
+
torch.dropout,
|
| 125 |
+
torch.nn.functional.dropout,
|
| 126 |
+
torch.nn.functional.elu,
|
| 127 |
+
torch.Tensor.eq,
|
| 128 |
+
torch.eq,
|
| 129 |
+
torch.Tensor.erf,
|
| 130 |
+
torch.erf,
|
| 131 |
+
torch.Tensor.erfc,
|
| 132 |
+
torch.erfc,
|
| 133 |
+
torch.Tensor.erfinv,
|
| 134 |
+
torch.erfinv,
|
| 135 |
+
torch.Tensor.exp,
|
| 136 |
+
torch.exp,
|
| 137 |
+
torch.Tensor.exp2,
|
| 138 |
+
torch.exp2,
|
| 139 |
+
torch.Tensor.expm1,
|
| 140 |
+
torch.expm1,
|
| 141 |
+
torch.feature_dropout,
|
| 142 |
+
torch.Tensor.float_power,
|
| 143 |
+
torch.float_power,
|
| 144 |
+
torch.Tensor.floor,
|
| 145 |
+
torch.floor,
|
| 146 |
+
torch.Tensor.floor_divide,
|
| 147 |
+
torch.floor_divide,
|
| 148 |
+
torch.Tensor.fmod,
|
| 149 |
+
torch.fmod,
|
| 150 |
+
torch.Tensor.frac,
|
| 151 |
+
torch.frac,
|
| 152 |
+
torch.Tensor.frexp,
|
| 153 |
+
torch.frexp,
|
| 154 |
+
torch.Tensor.gcd,
|
| 155 |
+
torch.gcd,
|
| 156 |
+
torch.Tensor.ge,
|
| 157 |
+
torch.ge,
|
| 158 |
+
torch.nn.functional.gelu,
|
| 159 |
+
torch.nn.functional.glu,
|
| 160 |
+
torch.Tensor.gt,
|
| 161 |
+
torch.gt,
|
| 162 |
+
torch.Tensor.hardshrink,
|
| 163 |
+
torch.hardshrink,
|
| 164 |
+
torch.nn.functional.hardshrink,
|
| 165 |
+
torch.nn.functional.hardsigmoid,
|
| 166 |
+
torch.nn.functional.hardswish,
|
| 167 |
+
torch.nn.functional.hardtanh,
|
| 168 |
+
torch.Tensor.heaviside,
|
| 169 |
+
torch.heaviside,
|
| 170 |
+
torch.Tensor.hypot,
|
| 171 |
+
torch.hypot,
|
| 172 |
+
torch.Tensor.i0,
|
| 173 |
+
torch.i0,
|
| 174 |
+
torch.Tensor.igamma,
|
| 175 |
+
torch.igamma,
|
| 176 |
+
torch.Tensor.igammac,
|
| 177 |
+
torch.igammac,
|
| 178 |
+
torch.Tensor.isclose,
|
| 179 |
+
torch.isclose,
|
| 180 |
+
torch.Tensor.isfinite,
|
| 181 |
+
torch.isfinite,
|
| 182 |
+
torch.Tensor.isinf,
|
| 183 |
+
torch.isinf,
|
| 184 |
+
torch.Tensor.isnan,
|
| 185 |
+
torch.isnan,
|
| 186 |
+
torch.Tensor.isneginf,
|
| 187 |
+
torch.isneginf,
|
| 188 |
+
torch.Tensor.isposinf,
|
| 189 |
+
torch.isposinf,
|
| 190 |
+
torch.Tensor.isreal,
|
| 191 |
+
torch.isreal,
|
| 192 |
+
torch.Tensor.kron,
|
| 193 |
+
torch.kron,
|
| 194 |
+
torch.Tensor.lcm,
|
| 195 |
+
torch.lcm,
|
| 196 |
+
torch.Tensor.ldexp,
|
| 197 |
+
torch.ldexp,
|
| 198 |
+
torch.Tensor.le,
|
| 199 |
+
torch.le,
|
| 200 |
+
torch.nn.functional.leaky_relu,
|
| 201 |
+
torch.Tensor.lerp,
|
| 202 |
+
torch.lerp,
|
| 203 |
+
torch.Tensor.lgamma,
|
| 204 |
+
torch.lgamma,
|
| 205 |
+
torch.Tensor.log,
|
| 206 |
+
torch.log,
|
| 207 |
+
torch.Tensor.log10,
|
| 208 |
+
torch.log10,
|
| 209 |
+
torch.Tensor.log1p,
|
| 210 |
+
torch.log1p,
|
| 211 |
+
torch.Tensor.log2,
|
| 212 |
+
torch.log2,
|
| 213 |
+
torch.nn.functional.logsigmoid,
|
| 214 |
+
torch.Tensor.logical_and,
|
| 215 |
+
torch.logical_and,
|
| 216 |
+
torch.Tensor.logical_not,
|
| 217 |
+
torch.logical_not,
|
| 218 |
+
torch.Tensor.logical_or,
|
| 219 |
+
torch.logical_or,
|
| 220 |
+
torch.Tensor.logical_xor,
|
| 221 |
+
torch.logical_xor,
|
| 222 |
+
torch.Tensor.logit,
|
| 223 |
+
torch.logit,
|
| 224 |
+
torch.Tensor.lt,
|
| 225 |
+
torch.lt,
|
| 226 |
+
torch.Tensor.maximum,
|
| 227 |
+
torch.maximum,
|
| 228 |
+
torch.Tensor.minimum,
|
| 229 |
+
torch.minimum,
|
| 230 |
+
torch.nn.functional.mish,
|
| 231 |
+
torch.Tensor.mvlgamma,
|
| 232 |
+
torch.mvlgamma,
|
| 233 |
+
torch.Tensor.nan_to_num,
|
| 234 |
+
torch.nan_to_num,
|
| 235 |
+
torch.Tensor.ne,
|
| 236 |
+
torch.ne,
|
| 237 |
+
torch.Tensor.neg,
|
| 238 |
+
torch.neg,
|
| 239 |
+
torch.Tensor.nextafter,
|
| 240 |
+
torch.nextafter,
|
| 241 |
+
torch.Tensor.outer,
|
| 242 |
+
torch.outer,
|
| 243 |
+
torch.polar,
|
| 244 |
+
torch.Tensor.polygamma,
|
| 245 |
+
torch.polygamma,
|
| 246 |
+
torch.Tensor.positive,
|
| 247 |
+
torch.positive,
|
| 248 |
+
torch.Tensor.pow,
|
| 249 |
+
torch.pow,
|
| 250 |
+
torch.Tensor.prelu,
|
| 251 |
+
torch.prelu,
|
| 252 |
+
torch.nn.functional.prelu,
|
| 253 |
+
torch.Tensor.rad2deg,
|
| 254 |
+
torch.rad2deg,
|
| 255 |
+
torch.Tensor.reciprocal,
|
| 256 |
+
torch.reciprocal,
|
| 257 |
+
torch.Tensor.relu,
|
| 258 |
+
torch.relu,
|
| 259 |
+
torch.nn.functional.relu,
|
| 260 |
+
torch.nn.functional.relu6,
|
| 261 |
+
torch.Tensor.remainder,
|
| 262 |
+
torch.remainder,
|
| 263 |
+
torch.Tensor.round,
|
| 264 |
+
torch.round,
|
| 265 |
+
torch.rrelu,
|
| 266 |
+
torch.nn.functional.rrelu,
|
| 267 |
+
torch.Tensor.rsqrt,
|
| 268 |
+
torch.rsqrt,
|
| 269 |
+
torch.rsub,
|
| 270 |
+
torch.selu,
|
| 271 |
+
torch.nn.functional.selu,
|
| 272 |
+
torch.Tensor.sgn,
|
| 273 |
+
torch.sgn,
|
| 274 |
+
torch.Tensor.sigmoid,
|
| 275 |
+
torch.sigmoid,
|
| 276 |
+
torch.nn.functional.sigmoid,
|
| 277 |
+
torch.Tensor.sign,
|
| 278 |
+
torch.sign,
|
| 279 |
+
torch.Tensor.signbit,
|
| 280 |
+
torch.signbit,
|
| 281 |
+
torch.nn.functional.silu,
|
| 282 |
+
torch.Tensor.sin,
|
| 283 |
+
torch.sin,
|
| 284 |
+
torch.Tensor.sinc,
|
| 285 |
+
torch.sinc,
|
| 286 |
+
torch.Tensor.sinh,
|
| 287 |
+
torch.sinh,
|
| 288 |
+
torch.nn.functional.softplus,
|
| 289 |
+
torch.nn.functional.softshrink,
|
| 290 |
+
torch.Tensor.sqrt,
|
| 291 |
+
torch.sqrt,
|
| 292 |
+
torch.Tensor.square,
|
| 293 |
+
torch.square,
|
| 294 |
+
torch.Tensor.sub,
|
| 295 |
+
torch.sub,
|
| 296 |
+
torch.Tensor.tan,
|
| 297 |
+
torch.tan,
|
| 298 |
+
torch.Tensor.tanh,
|
| 299 |
+
torch.tanh,
|
| 300 |
+
torch.nn.functional.tanh,
|
| 301 |
+
torch.threshold,
|
| 302 |
+
torch.nn.functional.threshold,
|
| 303 |
+
torch.trapz,
|
| 304 |
+
torch.Tensor.true_divide,
|
| 305 |
+
torch.true_divide,
|
| 306 |
+
torch.Tensor.trunc,
|
| 307 |
+
torch.trunc,
|
| 308 |
+
torch.Tensor.xlogy,
|
| 309 |
+
torch.xlogy,
|
| 310 |
+
torch.rand_like,
|
| 311 |
+
)
|
tuning-competition-baseline/.venv/lib/python3.11/site-packages/functorch/dim/reference.py
ADDED
|
@@ -0,0 +1,645 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Facebook, Inc. and its affiliates.
|
| 2 |
+
# All rights reserved.
|
| 3 |
+
#
|
| 4 |
+
# This source code is licensed under the BSD-style license found in the
|
| 5 |
+
# LICENSE file in the root directory of this source tree.
|
| 6 |
+
|
| 7 |
+
# reference python implementations for C ops
|
| 8 |
+
import torch
|
| 9 |
+
|
| 10 |
+
from functorch._C import dim as _C
|
| 11 |
+
from . import op_properties
|
| 12 |
+
from .batch_tensor import _enable_layers
|
| 13 |
+
from .tree_map import tree_flatten, tree_map
|
| 14 |
+
|
| 15 |
+
DimList = _C.DimList
|
| 16 |
+
import operator
|
| 17 |
+
from functools import reduce
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
# use dict to avoid writing C++ bindings for set
|
| 21 |
+
pointwise = set(op_properties.pointwise)
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
def prod(x):
|
| 25 |
+
return reduce(operator.mul, x, 1)
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
def _wrap_dim(d, N, keepdim):
|
| 29 |
+
from . import Dim
|
| 30 |
+
|
| 31 |
+
if isinstance(d, Dim):
|
| 32 |
+
assert not keepdim, "cannot preserve first-class dimensions with keepdim=True"
|
| 33 |
+
return d
|
| 34 |
+
elif d >= 0:
|
| 35 |
+
return d - N
|
| 36 |
+
else:
|
| 37 |
+
return d
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
def _dims(d, N, keepdim, single_dim):
|
| 41 |
+
from . import Dim
|
| 42 |
+
|
| 43 |
+
if isinstance(d, (Dim, int)):
|
| 44 |
+
return ltuple((_wrap_dim(d, N, keepdim),))
|
| 45 |
+
assert not single_dim, f"expected a single dimension or int but found: {d}"
|
| 46 |
+
return ltuple(_wrap_dim(x, N, keepdim) for x in d)
|
| 47 |
+
|
| 48 |
+
|
| 49 |
+
def _bind_dims_to_size(lhs_size, rhs, lhs_debug):
|
| 50 |
+
from . import DimensionMismatchError
|
| 51 |
+
|
| 52 |
+
not_bound = tuple((i, r) for i, r in enumerate(rhs) if not r.is_bound)
|
| 53 |
+
if len(not_bound) == 1:
|
| 54 |
+
idx, d = not_bound[0]
|
| 55 |
+
rhs_so_far = prod(r.size for r in rhs if r.is_bound)
|
| 56 |
+
if lhs_size % rhs_so_far != 0:
|
| 57 |
+
rhs_s = tuple("?" if not r.is_bound else str(r.size) for r in rhs)
|
| 58 |
+
raise DimensionMismatchError(
|
| 59 |
+
f"inferred dimension does not evenly fit into larger dimension: {lhs_size} vs {rhs_s}"
|
| 60 |
+
)
|
| 61 |
+
new_size = lhs_size // rhs_so_far
|
| 62 |
+
d.size = new_size
|
| 63 |
+
elif len(not_bound) > 1:
|
| 64 |
+
rhs_s = tuple("?" if not r.is_bound else str(r.size) for r in rhs)
|
| 65 |
+
raise DimensionMismatchError(
|
| 66 |
+
f"cannot infer the size of two dimensions at once: {rhs} with sizes {rhs_s}"
|
| 67 |
+
)
|
| 68 |
+
else:
|
| 69 |
+
rhs_size = prod(r.size for r in rhs)
|
| 70 |
+
if lhs_size != rhs_size:
|
| 71 |
+
raise DimensionMismatchError(
|
| 72 |
+
f"Dimension sizes to do not match ({lhs_size} != {rhs_size}) when matching {lhs_debug} to {rhs}"
|
| 73 |
+
)
|
| 74 |
+
|
| 75 |
+
|
| 76 |
+
def _tensor_levels(inp):
|
| 77 |
+
from . import _Tensor
|
| 78 |
+
|
| 79 |
+
if isinstance(inp, _Tensor):
|
| 80 |
+
return inp._tensor, llist(inp._levels), inp._has_device
|
| 81 |
+
else:
|
| 82 |
+
return inp, llist(range(-inp.ndim, 0)), True
|
| 83 |
+
|
| 84 |
+
|
| 85 |
+
def _match_levels(v, from_levels, to_levels):
|
| 86 |
+
view = []
|
| 87 |
+
permute = []
|
| 88 |
+
requires_view = False
|
| 89 |
+
size = v.size()
|
| 90 |
+
for t in to_levels:
|
| 91 |
+
try:
|
| 92 |
+
idx = from_levels.index(t)
|
| 93 |
+
permute.append(idx)
|
| 94 |
+
view.append(size[idx])
|
| 95 |
+
except ValueError:
|
| 96 |
+
view.append(1)
|
| 97 |
+
requires_view = True
|
| 98 |
+
if permute != list(range(len(permute))):
|
| 99 |
+
v = v.permute(*permute)
|
| 100 |
+
if requires_view:
|
| 101 |
+
v = v.view(*view)
|
| 102 |
+
return v
|
| 103 |
+
|
| 104 |
+
|
| 105 |
+
# make a single dimension positional but do not permute it,
|
| 106 |
+
# used to do multi-tensor operators where the dim being acted on
|
| 107 |
+
# should not physically move if possible
|
| 108 |
+
def _positional_no_permute(self, dim, expand_dim=False):
|
| 109 |
+
from . import Tensor
|
| 110 |
+
|
| 111 |
+
ptensor, levels = self._tensor, llist(self._levels)
|
| 112 |
+
try:
|
| 113 |
+
idx = levels.index(dim)
|
| 114 |
+
except ValueError:
|
| 115 |
+
if not expand_dim:
|
| 116 |
+
raise
|
| 117 |
+
idx = 0
|
| 118 |
+
ptensor = ptensor.expand(dim.size, *ptensor.size())
|
| 119 |
+
levels.insert(0, 0)
|
| 120 |
+
idx_batched = 0
|
| 121 |
+
for i in range(idx):
|
| 122 |
+
if isinstance(levels[i], int):
|
| 123 |
+
levels[i] -= 1
|
| 124 |
+
idx_batched += 1
|
| 125 |
+
levels[idx] = -idx_batched - 1
|
| 126 |
+
return Tensor.from_positional(ptensor, levels, self._has_device), idx_batched
|
| 127 |
+
|
| 128 |
+
|
| 129 |
+
def seq(a, b):
|
| 130 |
+
from . import Dim
|
| 131 |
+
|
| 132 |
+
if isinstance(a, Dim) != isinstance(b, Dim):
|
| 133 |
+
return False
|
| 134 |
+
if isinstance(a, Dim):
|
| 135 |
+
return a is b
|
| 136 |
+
else:
|
| 137 |
+
return a == b
|
| 138 |
+
|
| 139 |
+
|
| 140 |
+
class isin:
|
| 141 |
+
def __contains__(self, item):
|
| 142 |
+
for x in self:
|
| 143 |
+
if seq(item, x):
|
| 144 |
+
return True
|
| 145 |
+
return False
|
| 146 |
+
|
| 147 |
+
def index(self, item):
|
| 148 |
+
for i, x in enumerate(self):
|
| 149 |
+
if seq(item, x):
|
| 150 |
+
return i
|
| 151 |
+
raise ValueError
|
| 152 |
+
|
| 153 |
+
|
| 154 |
+
class llist(isin, list):
|
| 155 |
+
pass
|
| 156 |
+
|
| 157 |
+
|
| 158 |
+
class ltuple(isin, tuple):
|
| 159 |
+
pass
|
| 160 |
+
|
| 161 |
+
|
| 162 |
+
empty_dict = {}
|
| 163 |
+
|
| 164 |
+
|
| 165 |
+
@classmethod
|
| 166 |
+
def __torch_function__(self, orig, cls, args, kwargs=empty_dict):
|
| 167 |
+
from . import _Tensor, Tensor, TensorLike
|
| 168 |
+
from .delayed_mul_tensor import DelayedMulTensor
|
| 169 |
+
|
| 170 |
+
if orig is torch.Tensor.__mul__:
|
| 171 |
+
lhs, rhs = args
|
| 172 |
+
if (
|
| 173 |
+
isinstance(lhs, _Tensor)
|
| 174 |
+
and isinstance(rhs, _Tensor)
|
| 175 |
+
and lhs.ndim == 0
|
| 176 |
+
and rhs.ndim == 0
|
| 177 |
+
):
|
| 178 |
+
return DelayedMulTensor(lhs, rhs)
|
| 179 |
+
all_dims = llist()
|
| 180 |
+
flat_args, unflatten = tree_flatten((args, kwargs))
|
| 181 |
+
device_holding_tensor = None
|
| 182 |
+
for f in flat_args:
|
| 183 |
+
if isinstance(f, _Tensor):
|
| 184 |
+
if f._has_device:
|
| 185 |
+
device_holding_tensor = f._batchtensor
|
| 186 |
+
for d in f.dims:
|
| 187 |
+
if d not in all_dims:
|
| 188 |
+
all_dims.append(d)
|
| 189 |
+
|
| 190 |
+
def unwrap(t):
|
| 191 |
+
if isinstance(t, _Tensor):
|
| 192 |
+
r = t._batchtensor
|
| 193 |
+
if device_holding_tensor is not None and not t._has_device:
|
| 194 |
+
r = r.to(device=device_holding_tensor.device)
|
| 195 |
+
return r
|
| 196 |
+
return t
|
| 197 |
+
|
| 198 |
+
if orig in pointwise:
|
| 199 |
+
result_levels = llist()
|
| 200 |
+
arg_levels = llist()
|
| 201 |
+
to_expand = []
|
| 202 |
+
for i, f in enumerate(flat_args):
|
| 203 |
+
if isinstance(f, TensorLike):
|
| 204 |
+
ptensor, levels, _ = _tensor_levels(f)
|
| 205 |
+
if (
|
| 206 |
+
isinstance(f, _Tensor)
|
| 207 |
+
and not f._has_device
|
| 208 |
+
and device_holding_tensor is not None
|
| 209 |
+
):
|
| 210 |
+
ptensor = ptensor.to(device=device_holding_tensor.device)
|
| 211 |
+
flat_args[i] = ptensor
|
| 212 |
+
for l in levels:
|
| 213 |
+
if l not in result_levels:
|
| 214 |
+
result_levels.append(l)
|
| 215 |
+
to_expand.append((i, levels))
|
| 216 |
+
|
| 217 |
+
for i, levels in to_expand:
|
| 218 |
+
flat_args[i] = _match_levels(flat_args[i], levels, result_levels)
|
| 219 |
+
args, kwargs = unflatten(flat_args)
|
| 220 |
+
result = orig(*args, **kwargs)
|
| 221 |
+
|
| 222 |
+
def wrap(t):
|
| 223 |
+
if isinstance(t, TensorLike):
|
| 224 |
+
return Tensor.from_positional(
|
| 225 |
+
t, result_levels, device_holding_tensor is not None
|
| 226 |
+
)
|
| 227 |
+
return t
|
| 228 |
+
|
| 229 |
+
return tree_map(wrap, result)
|
| 230 |
+
else:
|
| 231 |
+
|
| 232 |
+
def wrap(t):
|
| 233 |
+
if isinstance(t, TensorLike):
|
| 234 |
+
return Tensor.from_batched(t, device_holding_tensor is not None)
|
| 235 |
+
return t
|
| 236 |
+
|
| 237 |
+
with _enable_layers(all_dims):
|
| 238 |
+
print(f"batch_tensor for {orig}")
|
| 239 |
+
args, kwargs = unflatten(unwrap(f) for f in flat_args)
|
| 240 |
+
result = orig(*args, **kwargs)
|
| 241 |
+
# print("END", orig)
|
| 242 |
+
return tree_map(wrap, result)
|
| 243 |
+
|
| 244 |
+
|
| 245 |
+
def positional(self, *dims):
|
| 246 |
+
from . import Dim, DimensionBindError, Tensor
|
| 247 |
+
|
| 248 |
+
ptensor, levels = self._tensor, llist(self._levels)
|
| 249 |
+
flat_dims = llist()
|
| 250 |
+
view = []
|
| 251 |
+
needs_view = False
|
| 252 |
+
ndim = self.ndim
|
| 253 |
+
for d in dims:
|
| 254 |
+
if isinstance(d, DimList):
|
| 255 |
+
flat_dims.extend(d)
|
| 256 |
+
view.extend(e.size for e in d)
|
| 257 |
+
elif isinstance(d, Dim):
|
| 258 |
+
flat_dims.append(d)
|
| 259 |
+
view.append(d.size)
|
| 260 |
+
elif isinstance(d, int):
|
| 261 |
+
d = _wrap_dim(d, ndim, False)
|
| 262 |
+
flat_dims.append(d)
|
| 263 |
+
view.append(ptensor.size(d))
|
| 264 |
+
else:
|
| 265 |
+
flat_dims.extend(d)
|
| 266 |
+
view.append(prod(e.size for e in d))
|
| 267 |
+
needs_view = True
|
| 268 |
+
|
| 269 |
+
permute = list(range(len(levels)))
|
| 270 |
+
nflat = len(flat_dims)
|
| 271 |
+
for i, d in enumerate(flat_dims):
|
| 272 |
+
try:
|
| 273 |
+
idx = levels.index(d)
|
| 274 |
+
except ValueError as e:
|
| 275 |
+
raise DimensionBindError(
|
| 276 |
+
f"tensor of dimensions {self.dims} does not contain dim {d}"
|
| 277 |
+
) from e
|
| 278 |
+
p = permute[idx]
|
| 279 |
+
del levels[idx]
|
| 280 |
+
del permute[idx]
|
| 281 |
+
levels.insert(i, 0)
|
| 282 |
+
permute.insert(i, p)
|
| 283 |
+
ptensor = ptensor.permute(*permute)
|
| 284 |
+
seen = 0
|
| 285 |
+
for i in range(len(levels) - 1, -1, -1):
|
| 286 |
+
if isinstance(levels[i], int):
|
| 287 |
+
seen += 1
|
| 288 |
+
levels[i] = -seen
|
| 289 |
+
result = Tensor.from_positional(ptensor, levels, self._has_device)
|
| 290 |
+
if needs_view:
|
| 291 |
+
result = result.reshape(*view, *result.size()[len(flat_dims) :])
|
| 292 |
+
return result
|
| 293 |
+
|
| 294 |
+
|
| 295 |
+
def _contains_dim(input):
|
| 296 |
+
from . import Dim
|
| 297 |
+
|
| 298 |
+
for i in input:
|
| 299 |
+
if isinstance(i, Dim):
|
| 300 |
+
return True
|
| 301 |
+
|
| 302 |
+
|
| 303 |
+
def expand(self, *sizes):
|
| 304 |
+
if not _contains_dim(sizes):
|
| 305 |
+
return self.__torch_function__(torch.Tensor.expand, None, (self, *sizes))
|
| 306 |
+
dims = sizes
|
| 307 |
+
sizes = [d.size for d in dims] + [-1] * self.ndim
|
| 308 |
+
self = self.expand(*sizes)
|
| 309 |
+
return self[dims]
|
| 310 |
+
|
| 311 |
+
|
| 312 |
+
_not_present = object()
|
| 313 |
+
|
| 314 |
+
|
| 315 |
+
def _getarg(name, offset, args, kwargs, default):
|
| 316 |
+
if len(args) > offset:
|
| 317 |
+
return args[offset]
|
| 318 |
+
return kwargs.get(name, default)
|
| 319 |
+
|
| 320 |
+
|
| 321 |
+
def _patcharg(name, offset, args, kwargs, value):
|
| 322 |
+
if len(args) > offset:
|
| 323 |
+
args[offset] = value
|
| 324 |
+
else:
|
| 325 |
+
kwargs[name] = value
|
| 326 |
+
|
| 327 |
+
|
| 328 |
+
def _wrap(
|
| 329 |
+
orig, dim_offset=0, keepdim_offset=1, dim_name="dim", single_dim=False, reduce=True
|
| 330 |
+
):
|
| 331 |
+
from . import Dim, Tensor, TensorLike
|
| 332 |
+
|
| 333 |
+
def fn(self, *args, **kwargs):
|
| 334 |
+
dim = _getarg(dim_name, dim_offset, args, kwargs, _not_present)
|
| 335 |
+
if dim is _not_present or (single_dim and not isinstance(dim, Dim)):
|
| 336 |
+
with _enable_layers(self.dims):
|
| 337 |
+
print(f"dim fallback batch_tensor for {orig}")
|
| 338 |
+
return Tensor.from_batched(
|
| 339 |
+
orig(self._batchtensor, *args, **kwargs), self._has_device
|
| 340 |
+
)
|
| 341 |
+
keepdim = (
|
| 342 |
+
_getarg("keepdim", keepdim_offset, args, kwargs, False) if reduce else False
|
| 343 |
+
)
|
| 344 |
+
t, levels = self._tensor, llist(self._levels)
|
| 345 |
+
dims = _dims(dim, self._batchtensor.ndim, keepdim, single_dim)
|
| 346 |
+
dim_indices = tuple(levels.index(d) for d in dims)
|
| 347 |
+
if reduce and not keepdim:
|
| 348 |
+
new_levels = [l for i, l in enumerate(levels) if i not in dim_indices]
|
| 349 |
+
else:
|
| 350 |
+
new_levels = levels
|
| 351 |
+
|
| 352 |
+
if len(dim_indices) == 1:
|
| 353 |
+
dim_indices = dim_indices[
|
| 354 |
+
0
|
| 355 |
+
] # so that dims that really only take a single argument work...
|
| 356 |
+
args = list(args)
|
| 357 |
+
_patcharg(dim_name, dim_offset, args, kwargs, dim_indices)
|
| 358 |
+
|
| 359 |
+
def wrap(t):
|
| 360 |
+
if isinstance(t, TensorLike):
|
| 361 |
+
return Tensor.from_positional(t, new_levels, self._has_device)
|
| 362 |
+
return t
|
| 363 |
+
|
| 364 |
+
with _enable_layers(new_levels):
|
| 365 |
+
print(f"dim used batch_tensor for {orig}")
|
| 366 |
+
r = orig(t, *args, **kwargs)
|
| 367 |
+
return tree_map(wrap, r)
|
| 368 |
+
|
| 369 |
+
return fn
|
| 370 |
+
|
| 371 |
+
|
| 372 |
+
def _def(name, *args, **kwargs):
|
| 373 |
+
from . import _Tensor
|
| 374 |
+
|
| 375 |
+
orig = getattr(torch.Tensor, name)
|
| 376 |
+
setattr(_Tensor, name, _wrap(orig, *args, **kwargs))
|
| 377 |
+
|
| 378 |
+
|
| 379 |
+
no_slice = slice(None)
|
| 380 |
+
|
| 381 |
+
_orig_getitem = torch.Tensor.__getitem__
|
| 382 |
+
|
| 383 |
+
|
| 384 |
+
class dim_tracker:
|
| 385 |
+
def __init__(self):
|
| 386 |
+
self.dims = llist()
|
| 387 |
+
self.count = []
|
| 388 |
+
|
| 389 |
+
def record(self, d):
|
| 390 |
+
if d not in self.dims:
|
| 391 |
+
self.dims.append(d)
|
| 392 |
+
self.count.append(1)
|
| 393 |
+
|
| 394 |
+
def __getitem__(self, d):
|
| 395 |
+
return self.count[self.dims.index(d)]
|
| 396 |
+
|
| 397 |
+
|
| 398 |
+
def t__getitem__(self, input):
|
| 399 |
+
from . import _Tensor, Dim, DimensionBindError, DimList, Tensor, TensorLike
|
| 400 |
+
|
| 401 |
+
# * bail to original example if we have a single non-Dim tensor, or a non-tensor
|
| 402 |
+
# * locate ... or an unbound tensor list, and determine its size, bind dim list
|
| 403 |
+
# (remember that None does not count to the total dim count)
|
| 404 |
+
# * bind simple dims and dim-packs to their sizes, count the number of uses of each dim,
|
| 405 |
+
# produce the re-view if needed
|
| 406 |
+
# * for each single-use dim index, replace with no_slice and mark that it will be added
|
| 407 |
+
# (keep track of whether we have to call super)
|
| 408 |
+
# * call super if needed
|
| 409 |
+
# * if we have dims to bind, bind them (it will help if we eliminated ... and None before)
|
| 410 |
+
|
| 411 |
+
# this handles bool indexing handling, as well as some other simple cases.
|
| 412 |
+
|
| 413 |
+
is_simple = (
|
| 414 |
+
not isinstance(input, Dim)
|
| 415 |
+
and not isinstance(input, (tuple, list))
|
| 416 |
+
and
|
| 417 |
+
# WAR for functorch bug where zero time tensors in getitem are not handled correctly.
|
| 418 |
+
not (isinstance(input, TensorLike) and input.ndim == 0)
|
| 419 |
+
)
|
| 420 |
+
|
| 421 |
+
if is_simple:
|
| 422 |
+
if isinstance(self, _Tensor):
|
| 423 |
+
return _Tensor.__torch_function__(_orig_getitem, None, (self, input))
|
| 424 |
+
else:
|
| 425 |
+
return _orig_getitem(self, input)
|
| 426 |
+
|
| 427 |
+
# can further optimize this case
|
| 428 |
+
if not isinstance(input, tuple):
|
| 429 |
+
input = [input]
|
| 430 |
+
else:
|
| 431 |
+
input = list(input)
|
| 432 |
+
|
| 433 |
+
dims_indexed = 0
|
| 434 |
+
expanding_object = None
|
| 435 |
+
dimlists = []
|
| 436 |
+
for i, s in enumerate(input):
|
| 437 |
+
if s is ... or isinstance(s, DimList) and not s.is_bound:
|
| 438 |
+
if expanding_object is not None:
|
| 439 |
+
msg = (
|
| 440 |
+
"at most one ... or unbound dimension list can exist in indexing list but"
|
| 441 |
+
f" found 2 at offsets {i} and {expanding_object}"
|
| 442 |
+
)
|
| 443 |
+
raise DimensionBindError(msg)
|
| 444 |
+
expanding_object = i
|
| 445 |
+
|
| 446 |
+
if isinstance(s, DimList):
|
| 447 |
+
dims_indexed += len(s) if s.is_bound else 0
|
| 448 |
+
dimlists.append(i)
|
| 449 |
+
elif s is not None and s is not ...:
|
| 450 |
+
dims_indexed += 1
|
| 451 |
+
|
| 452 |
+
ndim = self.ndim
|
| 453 |
+
if dims_indexed > ndim:
|
| 454 |
+
raise IndexError(
|
| 455 |
+
f"at least {dims_indexed} indices were supplied but the tensor only has {ndim} dimensions."
|
| 456 |
+
)
|
| 457 |
+
if expanding_object is not None:
|
| 458 |
+
expanding_ndims = ndim - dims_indexed
|
| 459 |
+
obj = input[expanding_object]
|
| 460 |
+
if obj is ...:
|
| 461 |
+
input[expanding_object : expanding_object + 1] = [
|
| 462 |
+
no_slice
|
| 463 |
+
] * expanding_ndims
|
| 464 |
+
else:
|
| 465 |
+
obj.bind_len(expanding_ndims)
|
| 466 |
+
# flatten the dimslists into the indexing
|
| 467 |
+
for i in reversed(dimlists):
|
| 468 |
+
input[i : i + 1] = input[i]
|
| 469 |
+
dims_indexed = 0
|
| 470 |
+
requires_view = False
|
| 471 |
+
size = self.size()
|
| 472 |
+
view_sizes = []
|
| 473 |
+
dims_seen = dim_tracker()
|
| 474 |
+
|
| 475 |
+
def add_dims(t):
|
| 476 |
+
if not isinstance(t, _Tensor):
|
| 477 |
+
return
|
| 478 |
+
for d in t.dims:
|
| 479 |
+
dims_seen.record(d)
|
| 480 |
+
|
| 481 |
+
add_dims(self)
|
| 482 |
+
dim_packs = []
|
| 483 |
+
for i, idx in enumerate(input):
|
| 484 |
+
if idx is None:
|
| 485 |
+
input[i] = no_slice
|
| 486 |
+
view_sizes.append(1)
|
| 487 |
+
requires_view = True
|
| 488 |
+
else:
|
| 489 |
+
sz = size[dims_indexed]
|
| 490 |
+
if isinstance(idx, Dim):
|
| 491 |
+
idx.size = sz
|
| 492 |
+
dims_seen.record(idx)
|
| 493 |
+
view_sizes.append(sz)
|
| 494 |
+
elif isinstance(idx, (tuple, list)) and idx and isinstance(idx[0], Dim):
|
| 495 |
+
for d in idx:
|
| 496 |
+
dims_seen.record(idx)
|
| 497 |
+
_bind_dims_to_size(sz, idx, f"offset {i}")
|
| 498 |
+
view_sizes.extend(d.size for d in idx)
|
| 499 |
+
requires_view = True
|
| 500 |
+
dim_packs.append(i)
|
| 501 |
+
else:
|
| 502 |
+
add_dims(idx)
|
| 503 |
+
view_sizes.append(sz)
|
| 504 |
+
dims_indexed += 1
|
| 505 |
+
if requires_view:
|
| 506 |
+
self = self.view(*view_sizes)
|
| 507 |
+
for i in reversed(dim_packs):
|
| 508 |
+
input[i : i + 1] = input[i]
|
| 509 |
+
|
| 510 |
+
# currenty:
|
| 511 |
+
# input is flat, containing either Dim, or Tensor, or something valid for standard indexing
|
| 512 |
+
# self may have first-class dims as well.
|
| 513 |
+
|
| 514 |
+
# to index:
|
| 515 |
+
# drop the first class dims from self, they just become direct indices of their positions
|
| 516 |
+
|
| 517 |
+
# figure out the dimensions of the indexing tensors: union of all the dims in the tensors in the index.
|
| 518 |
+
# these dimensions will appear and need to be bound at the first place tensor occures
|
| 519 |
+
|
| 520 |
+
if isinstance(self, _Tensor):
|
| 521 |
+
ptensor_self, levels = self._tensor, list(self._levels)
|
| 522 |
+
# indices to ptensor rather than self which has first-class dimensions
|
| 523 |
+
input_it = iter(input)
|
| 524 |
+
flat_inputs = [next(input_it) if isinstance(l, int) else l for l in levels]
|
| 525 |
+
has_device = self._has_device
|
| 526 |
+
to_pad = 0
|
| 527 |
+
else:
|
| 528 |
+
ptensor_self, flat_inputs = self, input
|
| 529 |
+
to_pad = ptensor_self.ndim - len(flat_inputs)
|
| 530 |
+
has_device = True
|
| 531 |
+
|
| 532 |
+
result_levels = []
|
| 533 |
+
index_levels = []
|
| 534 |
+
tensor_insert_point = None
|
| 535 |
+
to_expand = {}
|
| 536 |
+
requires_getindex = False
|
| 537 |
+
for i, inp in enumerate(flat_inputs):
|
| 538 |
+
if isinstance(inp, Dim) and dims_seen[inp] == 1:
|
| 539 |
+
flat_inputs[i] = no_slice
|
| 540 |
+
result_levels.append(inp)
|
| 541 |
+
elif isinstance(inp, TensorLike):
|
| 542 |
+
requires_getindex = True
|
| 543 |
+
if tensor_insert_point is None:
|
| 544 |
+
tensor_insert_point = len(result_levels)
|
| 545 |
+
ptensor, levels, _ = _tensor_levels(inp)
|
| 546 |
+
to_expand[i] = levels
|
| 547 |
+
flat_inputs[i] = ptensor
|
| 548 |
+
for l in levels:
|
| 549 |
+
if l not in index_levels:
|
| 550 |
+
index_levels.append(l)
|
| 551 |
+
else:
|
| 552 |
+
requires_getindex = True
|
| 553 |
+
result_levels.append(0)
|
| 554 |
+
|
| 555 |
+
if tensor_insert_point is not None:
|
| 556 |
+
result_levels[tensor_insert_point:tensor_insert_point] = index_levels
|
| 557 |
+
|
| 558 |
+
for i, levels in to_expand.items():
|
| 559 |
+
flat_inputs[i] = _match_levels(flat_inputs[i], levels, index_levels)
|
| 560 |
+
|
| 561 |
+
if requires_getindex:
|
| 562 |
+
result = _orig_getitem(ptensor_self, flat_inputs)
|
| 563 |
+
else:
|
| 564 |
+
result = ptensor_self
|
| 565 |
+
|
| 566 |
+
next_positional = -1
|
| 567 |
+
if to_pad > 0:
|
| 568 |
+
result_levels.extend([0] * to_pad)
|
| 569 |
+
for i, r in enumerate(reversed(result_levels)):
|
| 570 |
+
if isinstance(r, int):
|
| 571 |
+
result_levels[-1 - i] = next_positional
|
| 572 |
+
next_positional -= 1
|
| 573 |
+
|
| 574 |
+
return Tensor.from_positional(result, result_levels, has_device)
|
| 575 |
+
|
| 576 |
+
|
| 577 |
+
# XXX - dim is optional and can be the outer-most dimension...
|
| 578 |
+
def stack(tensors, new_dim, dim=0, out=None):
|
| 579 |
+
if isinstance(dim, int):
|
| 580 |
+
return torch.stack(tensors, dim, out).index(dim, new_dim)
|
| 581 |
+
index = None
|
| 582 |
+
if out is not None:
|
| 583 |
+
out, index = _positional_no_permute(out, dim, expand_dim=True)
|
| 584 |
+
ptensors = []
|
| 585 |
+
for t in tensors:
|
| 586 |
+
pt, pi = _positional_no_permute(t, dim, expand_dim=True)
|
| 587 |
+
if index is not None and pi != index:
|
| 588 |
+
pt = pt.move_dim(pi, index)
|
| 589 |
+
else:
|
| 590 |
+
index = pi
|
| 591 |
+
ptensors.append(pt)
|
| 592 |
+
pr = torch.stack(ptensors, index, out=out)
|
| 593 |
+
return pr.index((index, index + 1), (new_dim, dim))
|
| 594 |
+
|
| 595 |
+
|
| 596 |
+
_orig_split = torch.Tensor.split
|
| 597 |
+
|
| 598 |
+
|
| 599 |
+
def split(self, split_size_or_sections, dim=0):
|
| 600 |
+
from . import _Tensor, Dim
|
| 601 |
+
|
| 602 |
+
if isinstance(split_size_or_sections, int) or any(
|
| 603 |
+
isinstance(t, int) for t in split_size_or_sections
|
| 604 |
+
):
|
| 605 |
+
if isinstance(dim, Dim):
|
| 606 |
+
raise ValueError(
|
| 607 |
+
"when dim is specified as a Dim object, split sizes must also be dimensions."
|
| 608 |
+
)
|
| 609 |
+
return _orig_split(self, split_size_or_sections, dim=dim)
|
| 610 |
+
|
| 611 |
+
if isinstance(dim, Dim):
|
| 612 |
+
assert isinstance(self, _Tensor), f"Tensor does not have dimension {dim}"
|
| 613 |
+
self, dim = _positional_no_permute(self, dim)
|
| 614 |
+
|
| 615 |
+
size = self.size(dim)
|
| 616 |
+
total_bound_size = 0
|
| 617 |
+
unbound = []
|
| 618 |
+
sizes = []
|
| 619 |
+
for i, d in enumerate(split_size_or_sections):
|
| 620 |
+
if d.is_bound:
|
| 621 |
+
sizes.append(d.size)
|
| 622 |
+
total_bound_size += d.size
|
| 623 |
+
else:
|
| 624 |
+
sizes.append(0)
|
| 625 |
+
unbound.append(i)
|
| 626 |
+
|
| 627 |
+
if unbound:
|
| 628 |
+
assert (
|
| 629 |
+
total_bound_size <= size
|
| 630 |
+
), f"result dimensions are larger than original: {total_bound_size} vs {size} ({split_size_or_sections})"
|
| 631 |
+
remaining_size = size - total_bound_size
|
| 632 |
+
chunk_size = -(-remaining_size // len(unbound))
|
| 633 |
+
for u in unbound:
|
| 634 |
+
sz = min(chunk_size, remaining_size)
|
| 635 |
+
split_size_or_sections[u].size = sz
|
| 636 |
+
sizes[u] = sz
|
| 637 |
+
remaining_size -= sz
|
| 638 |
+
else:
|
| 639 |
+
assert (
|
| 640 |
+
total_bound_size == size
|
| 641 |
+
), f"result dimensions do not match original: {total_bound_size} vs {size} ({split_size_or_sections})"
|
| 642 |
+
return tuple(
|
| 643 |
+
t.index(dim, d)
|
| 644 |
+
for d, t in zip(split_size_or_sections, _orig_split(self, sizes, dim=dim))
|
| 645 |
+
)
|
tuning-competition-baseline/.venv/lib/python3.11/site-packages/functorch/dim/tree_map.py
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Facebook, Inc. and its affiliates.
|
| 2 |
+
# All rights reserved.
|
| 3 |
+
#
|
| 4 |
+
# This source code is licensed under the BSD-style license found in the
|
| 5 |
+
# LICENSE file in the root directory of this source tree.
|
| 6 |
+
|
| 7 |
+
from functorch._C import dim
|
| 8 |
+
|
| 9 |
+
tree_flatten = dim.tree_flatten
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
def tree_map(fn, tree):
|
| 13 |
+
vs, unflatten = tree_flatten(tree)
|
| 14 |
+
return unflatten(fn(v) for v in vs)
|
tuning-competition-baseline/.venv/lib/python3.11/site-packages/functorch/experimental/__pycache__/__init__.cpython-311.pyc
ADDED
|
Binary file (591 Bytes). View file
|
|
|
tuning-competition-baseline/.venv/lib/python3.11/site-packages/functorch/experimental/ops.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
from torch._ops import HigherOrderOperator # noqa: F401
|
tuning-competition-baseline/.venv/lib/python3.11/site-packages/jinja2/__init__.py
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Jinja is a template engine written in pure Python. It provides a
|
| 2 |
+
non-XML syntax that supports inline expressions and an optional
|
| 3 |
+
sandboxed environment.
|
| 4 |
+
"""
|
| 5 |
+
from .bccache import BytecodeCache as BytecodeCache
|
| 6 |
+
from .bccache import FileSystemBytecodeCache as FileSystemBytecodeCache
|
| 7 |
+
from .bccache import MemcachedBytecodeCache as MemcachedBytecodeCache
|
| 8 |
+
from .environment import Environment as Environment
|
| 9 |
+
from .environment import Template as Template
|
| 10 |
+
from .exceptions import TemplateAssertionError as TemplateAssertionError
|
| 11 |
+
from .exceptions import TemplateError as TemplateError
|
| 12 |
+
from .exceptions import TemplateNotFound as TemplateNotFound
|
| 13 |
+
from .exceptions import TemplateRuntimeError as TemplateRuntimeError
|
| 14 |
+
from .exceptions import TemplatesNotFound as TemplatesNotFound
|
| 15 |
+
from .exceptions import TemplateSyntaxError as TemplateSyntaxError
|
| 16 |
+
from .exceptions import UndefinedError as UndefinedError
|
| 17 |
+
from .loaders import BaseLoader as BaseLoader
|
| 18 |
+
from .loaders import ChoiceLoader as ChoiceLoader
|
| 19 |
+
from .loaders import DictLoader as DictLoader
|
| 20 |
+
from .loaders import FileSystemLoader as FileSystemLoader
|
| 21 |
+
from .loaders import FunctionLoader as FunctionLoader
|
| 22 |
+
from .loaders import ModuleLoader as ModuleLoader
|
| 23 |
+
from .loaders import PackageLoader as PackageLoader
|
| 24 |
+
from .loaders import PrefixLoader as PrefixLoader
|
| 25 |
+
from .runtime import ChainableUndefined as ChainableUndefined
|
| 26 |
+
from .runtime import DebugUndefined as DebugUndefined
|
| 27 |
+
from .runtime import make_logging_undefined as make_logging_undefined
|
| 28 |
+
from .runtime import StrictUndefined as StrictUndefined
|
| 29 |
+
from .runtime import Undefined as Undefined
|
| 30 |
+
from .utils import clear_caches as clear_caches
|
| 31 |
+
from .utils import is_undefined as is_undefined
|
| 32 |
+
from .utils import pass_context as pass_context
|
| 33 |
+
from .utils import pass_environment as pass_environment
|
| 34 |
+
from .utils import pass_eval_context as pass_eval_context
|
| 35 |
+
from .utils import select_autoescape as select_autoescape
|
| 36 |
+
|
| 37 |
+
__version__ = "3.1.3"
|
tuning-competition-baseline/.venv/lib/python3.11/site-packages/jinja2/__pycache__/_identifier.cpython-311.pyc
ADDED
|
Binary file (2.16 kB). View file
|
|
|
tuning-competition-baseline/.venv/lib/python3.11/site-packages/jinja2/__pycache__/loaders.cpython-311.pyc
ADDED
|
Binary file (33.1 kB). View file
|
|
|
tuning-competition-baseline/.venv/lib/python3.11/site-packages/jinja2/__pycache__/runtime.cpython-311.pyc
ADDED
|
Binary file (50.7 kB). View file
|
|
|
tuning-competition-baseline/.venv/lib/python3.11/site-packages/jinja2/environment.py
ADDED
|
@@ -0,0 +1,1667 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Classes for managing templates and their runtime and compile time
|
| 2 |
+
options.
|
| 3 |
+
"""
|
| 4 |
+
import os
|
| 5 |
+
import typing
|
| 6 |
+
import typing as t
|
| 7 |
+
import weakref
|
| 8 |
+
from collections import ChainMap
|
| 9 |
+
from functools import lru_cache
|
| 10 |
+
from functools import partial
|
| 11 |
+
from functools import reduce
|
| 12 |
+
from types import CodeType
|
| 13 |
+
|
| 14 |
+
from markupsafe import Markup
|
| 15 |
+
|
| 16 |
+
from . import nodes
|
| 17 |
+
from .compiler import CodeGenerator
|
| 18 |
+
from .compiler import generate
|
| 19 |
+
from .defaults import BLOCK_END_STRING
|
| 20 |
+
from .defaults import BLOCK_START_STRING
|
| 21 |
+
from .defaults import COMMENT_END_STRING
|
| 22 |
+
from .defaults import COMMENT_START_STRING
|
| 23 |
+
from .defaults import DEFAULT_FILTERS
|
| 24 |
+
from .defaults import DEFAULT_NAMESPACE
|
| 25 |
+
from .defaults import DEFAULT_POLICIES
|
| 26 |
+
from .defaults import DEFAULT_TESTS
|
| 27 |
+
from .defaults import KEEP_TRAILING_NEWLINE
|
| 28 |
+
from .defaults import LINE_COMMENT_PREFIX
|
| 29 |
+
from .defaults import LINE_STATEMENT_PREFIX
|
| 30 |
+
from .defaults import LSTRIP_BLOCKS
|
| 31 |
+
from .defaults import NEWLINE_SEQUENCE
|
| 32 |
+
from .defaults import TRIM_BLOCKS
|
| 33 |
+
from .defaults import VARIABLE_END_STRING
|
| 34 |
+
from .defaults import VARIABLE_START_STRING
|
| 35 |
+
from .exceptions import TemplateNotFound
|
| 36 |
+
from .exceptions import TemplateRuntimeError
|
| 37 |
+
from .exceptions import TemplatesNotFound
|
| 38 |
+
from .exceptions import TemplateSyntaxError
|
| 39 |
+
from .exceptions import UndefinedError
|
| 40 |
+
from .lexer import get_lexer
|
| 41 |
+
from .lexer import Lexer
|
| 42 |
+
from .lexer import TokenStream
|
| 43 |
+
from .nodes import EvalContext
|
| 44 |
+
from .parser import Parser
|
| 45 |
+
from .runtime import Context
|
| 46 |
+
from .runtime import new_context
|
| 47 |
+
from .runtime import Undefined
|
| 48 |
+
from .utils import _PassArg
|
| 49 |
+
from .utils import concat
|
| 50 |
+
from .utils import consume
|
| 51 |
+
from .utils import import_string
|
| 52 |
+
from .utils import internalcode
|
| 53 |
+
from .utils import LRUCache
|
| 54 |
+
from .utils import missing
|
| 55 |
+
|
| 56 |
+
if t.TYPE_CHECKING:
|
| 57 |
+
import typing_extensions as te
|
| 58 |
+
from .bccache import BytecodeCache
|
| 59 |
+
from .ext import Extension
|
| 60 |
+
from .loaders import BaseLoader
|
| 61 |
+
|
| 62 |
+
_env_bound = t.TypeVar("_env_bound", bound="Environment")
|
| 63 |
+
|
| 64 |
+
|
| 65 |
+
# for direct template usage we have up to ten living environments
|
| 66 |
+
@lru_cache(maxsize=10)
|
| 67 |
+
def get_spontaneous_environment(cls: t.Type[_env_bound], *args: t.Any) -> _env_bound:
|
| 68 |
+
"""Return a new spontaneous environment. A spontaneous environment
|
| 69 |
+
is used for templates created directly rather than through an
|
| 70 |
+
existing environment.
|
| 71 |
+
|
| 72 |
+
:param cls: Environment class to create.
|
| 73 |
+
:param args: Positional arguments passed to environment.
|
| 74 |
+
"""
|
| 75 |
+
env = cls(*args)
|
| 76 |
+
env.shared = True
|
| 77 |
+
return env
|
| 78 |
+
|
| 79 |
+
|
| 80 |
+
def create_cache(
|
| 81 |
+
size: int,
|
| 82 |
+
) -> t.Optional[t.MutableMapping[t.Tuple[weakref.ref, str], "Template"]]:
|
| 83 |
+
"""Return the cache class for the given size."""
|
| 84 |
+
if size == 0:
|
| 85 |
+
return None
|
| 86 |
+
|
| 87 |
+
if size < 0:
|
| 88 |
+
return {}
|
| 89 |
+
|
| 90 |
+
return LRUCache(size) # type: ignore
|
| 91 |
+
|
| 92 |
+
|
| 93 |
+
def copy_cache(
|
| 94 |
+
cache: t.Optional[t.MutableMapping],
|
| 95 |
+
) -> t.Optional[t.MutableMapping[t.Tuple[weakref.ref, str], "Template"]]:
|
| 96 |
+
"""Create an empty copy of the given cache."""
|
| 97 |
+
if cache is None:
|
| 98 |
+
return None
|
| 99 |
+
|
| 100 |
+
if type(cache) is dict:
|
| 101 |
+
return {}
|
| 102 |
+
|
| 103 |
+
return LRUCache(cache.capacity) # type: ignore
|
| 104 |
+
|
| 105 |
+
|
| 106 |
+
def load_extensions(
|
| 107 |
+
environment: "Environment",
|
| 108 |
+
extensions: t.Sequence[t.Union[str, t.Type["Extension"]]],
|
| 109 |
+
) -> t.Dict[str, "Extension"]:
|
| 110 |
+
"""Load the extensions from the list and bind it to the environment.
|
| 111 |
+
Returns a dict of instantiated extensions.
|
| 112 |
+
"""
|
| 113 |
+
result = {}
|
| 114 |
+
|
| 115 |
+
for extension in extensions:
|
| 116 |
+
if isinstance(extension, str):
|
| 117 |
+
extension = t.cast(t.Type["Extension"], import_string(extension))
|
| 118 |
+
|
| 119 |
+
result[extension.identifier] = extension(environment)
|
| 120 |
+
|
| 121 |
+
return result
|
| 122 |
+
|
| 123 |
+
|
| 124 |
+
def _environment_config_check(environment: "Environment") -> "Environment":
|
| 125 |
+
"""Perform a sanity check on the environment."""
|
| 126 |
+
assert issubclass(
|
| 127 |
+
environment.undefined, Undefined
|
| 128 |
+
), "'undefined' must be a subclass of 'jinja2.Undefined'."
|
| 129 |
+
assert (
|
| 130 |
+
environment.block_start_string
|
| 131 |
+
!= environment.variable_start_string
|
| 132 |
+
!= environment.comment_start_string
|
| 133 |
+
), "block, variable and comment start strings must be different."
|
| 134 |
+
assert environment.newline_sequence in {
|
| 135 |
+
"\r",
|
| 136 |
+
"\r\n",
|
| 137 |
+
"\n",
|
| 138 |
+
}, "'newline_sequence' must be one of '\\n', '\\r\\n', or '\\r'."
|
| 139 |
+
return environment
|
| 140 |
+
|
| 141 |
+
|
| 142 |
+
class Environment:
|
| 143 |
+
r"""The core component of Jinja is the `Environment`. It contains
|
| 144 |
+
important shared variables like configuration, filters, tests,
|
| 145 |
+
globals and others. Instances of this class may be modified if
|
| 146 |
+
they are not shared and if no template was loaded so far.
|
| 147 |
+
Modifications on environments after the first template was loaded
|
| 148 |
+
will lead to surprising effects and undefined behavior.
|
| 149 |
+
|
| 150 |
+
Here are the possible initialization parameters:
|
| 151 |
+
|
| 152 |
+
`block_start_string`
|
| 153 |
+
The string marking the beginning of a block. Defaults to ``'{%'``.
|
| 154 |
+
|
| 155 |
+
`block_end_string`
|
| 156 |
+
The string marking the end of a block. Defaults to ``'%}'``.
|
| 157 |
+
|
| 158 |
+
`variable_start_string`
|
| 159 |
+
The string marking the beginning of a print statement.
|
| 160 |
+
Defaults to ``'{{'``.
|
| 161 |
+
|
| 162 |
+
`variable_end_string`
|
| 163 |
+
The string marking the end of a print statement. Defaults to
|
| 164 |
+
``'}}'``.
|
| 165 |
+
|
| 166 |
+
`comment_start_string`
|
| 167 |
+
The string marking the beginning of a comment. Defaults to ``'{#'``.
|
| 168 |
+
|
| 169 |
+
`comment_end_string`
|
| 170 |
+
The string marking the end of a comment. Defaults to ``'#}'``.
|
| 171 |
+
|
| 172 |
+
`line_statement_prefix`
|
| 173 |
+
If given and a string, this will be used as prefix for line based
|
| 174 |
+
statements. See also :ref:`line-statements`.
|
| 175 |
+
|
| 176 |
+
`line_comment_prefix`
|
| 177 |
+
If given and a string, this will be used as prefix for line based
|
| 178 |
+
comments. See also :ref:`line-statements`.
|
| 179 |
+
|
| 180 |
+
.. versionadded:: 2.2
|
| 181 |
+
|
| 182 |
+
`trim_blocks`
|
| 183 |
+
If this is set to ``True`` the first newline after a block is
|
| 184 |
+
removed (block, not variable tag!). Defaults to `False`.
|
| 185 |
+
|
| 186 |
+
`lstrip_blocks`
|
| 187 |
+
If this is set to ``True`` leading spaces and tabs are stripped
|
| 188 |
+
from the start of a line to a block. Defaults to `False`.
|
| 189 |
+
|
| 190 |
+
`newline_sequence`
|
| 191 |
+
The sequence that starts a newline. Must be one of ``'\r'``,
|
| 192 |
+
``'\n'`` or ``'\r\n'``. The default is ``'\n'`` which is a
|
| 193 |
+
useful default for Linux and OS X systems as well as web
|
| 194 |
+
applications.
|
| 195 |
+
|
| 196 |
+
`keep_trailing_newline`
|
| 197 |
+
Preserve the trailing newline when rendering templates.
|
| 198 |
+
The default is ``False``, which causes a single newline,
|
| 199 |
+
if present, to be stripped from the end of the template.
|
| 200 |
+
|
| 201 |
+
.. versionadded:: 2.7
|
| 202 |
+
|
| 203 |
+
`extensions`
|
| 204 |
+
List of Jinja extensions to use. This can either be import paths
|
| 205 |
+
as strings or extension classes. For more information have a
|
| 206 |
+
look at :ref:`the extensions documentation <jinja-extensions>`.
|
| 207 |
+
|
| 208 |
+
`optimized`
|
| 209 |
+
should the optimizer be enabled? Default is ``True``.
|
| 210 |
+
|
| 211 |
+
`undefined`
|
| 212 |
+
:class:`Undefined` or a subclass of it that is used to represent
|
| 213 |
+
undefined values in the template.
|
| 214 |
+
|
| 215 |
+
`finalize`
|
| 216 |
+
A callable that can be used to process the result of a variable
|
| 217 |
+
expression before it is output. For example one can convert
|
| 218 |
+
``None`` implicitly into an empty string here.
|
| 219 |
+
|
| 220 |
+
`autoescape`
|
| 221 |
+
If set to ``True`` the XML/HTML autoescaping feature is enabled by
|
| 222 |
+
default. For more details about autoescaping see
|
| 223 |
+
:class:`~markupsafe.Markup`. As of Jinja 2.4 this can also
|
| 224 |
+
be a callable that is passed the template name and has to
|
| 225 |
+
return ``True`` or ``False`` depending on autoescape should be
|
| 226 |
+
enabled by default.
|
| 227 |
+
|
| 228 |
+
.. versionchanged:: 2.4
|
| 229 |
+
`autoescape` can now be a function
|
| 230 |
+
|
| 231 |
+
`loader`
|
| 232 |
+
The template loader for this environment.
|
| 233 |
+
|
| 234 |
+
`cache_size`
|
| 235 |
+
The size of the cache. Per default this is ``400`` which means
|
| 236 |
+
that if more than 400 templates are loaded the loader will clean
|
| 237 |
+
out the least recently used template. If the cache size is set to
|
| 238 |
+
``0`` templates are recompiled all the time, if the cache size is
|
| 239 |
+
``-1`` the cache will not be cleaned.
|
| 240 |
+
|
| 241 |
+
.. versionchanged:: 2.8
|
| 242 |
+
The cache size was increased to 400 from a low 50.
|
| 243 |
+
|
| 244 |
+
`auto_reload`
|
| 245 |
+
Some loaders load templates from locations where the template
|
| 246 |
+
sources may change (ie: file system or database). If
|
| 247 |
+
``auto_reload`` is set to ``True`` (default) every time a template is
|
| 248 |
+
requested the loader checks if the source changed and if yes, it
|
| 249 |
+
will reload the template. For higher performance it's possible to
|
| 250 |
+
disable that.
|
| 251 |
+
|
| 252 |
+
`bytecode_cache`
|
| 253 |
+
If set to a bytecode cache object, this object will provide a
|
| 254 |
+
cache for the internal Jinja bytecode so that templates don't
|
| 255 |
+
have to be parsed if they were not changed.
|
| 256 |
+
|
| 257 |
+
See :ref:`bytecode-cache` for more information.
|
| 258 |
+
|
| 259 |
+
`enable_async`
|
| 260 |
+
If set to true this enables async template execution which
|
| 261 |
+
allows using async functions and generators.
|
| 262 |
+
"""
|
| 263 |
+
|
| 264 |
+
#: if this environment is sandboxed. Modifying this variable won't make
|
| 265 |
+
#: the environment sandboxed though. For a real sandboxed environment
|
| 266 |
+
#: have a look at jinja2.sandbox. This flag alone controls the code
|
| 267 |
+
#: generation by the compiler.
|
| 268 |
+
sandboxed = False
|
| 269 |
+
|
| 270 |
+
#: True if the environment is just an overlay
|
| 271 |
+
overlayed = False
|
| 272 |
+
|
| 273 |
+
#: the environment this environment is linked to if it is an overlay
|
| 274 |
+
linked_to: t.Optional["Environment"] = None
|
| 275 |
+
|
| 276 |
+
#: shared environments have this set to `True`. A shared environment
|
| 277 |
+
#: must not be modified
|
| 278 |
+
shared = False
|
| 279 |
+
|
| 280 |
+
#: the class that is used for code generation. See
|
| 281 |
+
#: :class:`~jinja2.compiler.CodeGenerator` for more information.
|
| 282 |
+
code_generator_class: t.Type["CodeGenerator"] = CodeGenerator
|
| 283 |
+
|
| 284 |
+
concat = "".join
|
| 285 |
+
|
| 286 |
+
#: the context class that is used for templates. See
|
| 287 |
+
#: :class:`~jinja2.runtime.Context` for more information.
|
| 288 |
+
context_class: t.Type[Context] = Context
|
| 289 |
+
|
| 290 |
+
template_class: t.Type["Template"]
|
| 291 |
+
|
| 292 |
+
def __init__(
|
| 293 |
+
self,
|
| 294 |
+
block_start_string: str = BLOCK_START_STRING,
|
| 295 |
+
block_end_string: str = BLOCK_END_STRING,
|
| 296 |
+
variable_start_string: str = VARIABLE_START_STRING,
|
| 297 |
+
variable_end_string: str = VARIABLE_END_STRING,
|
| 298 |
+
comment_start_string: str = COMMENT_START_STRING,
|
| 299 |
+
comment_end_string: str = COMMENT_END_STRING,
|
| 300 |
+
line_statement_prefix: t.Optional[str] = LINE_STATEMENT_PREFIX,
|
| 301 |
+
line_comment_prefix: t.Optional[str] = LINE_COMMENT_PREFIX,
|
| 302 |
+
trim_blocks: bool = TRIM_BLOCKS,
|
| 303 |
+
lstrip_blocks: bool = LSTRIP_BLOCKS,
|
| 304 |
+
newline_sequence: "te.Literal['\\n', '\\r\\n', '\\r']" = NEWLINE_SEQUENCE,
|
| 305 |
+
keep_trailing_newline: bool = KEEP_TRAILING_NEWLINE,
|
| 306 |
+
extensions: t.Sequence[t.Union[str, t.Type["Extension"]]] = (),
|
| 307 |
+
optimized: bool = True,
|
| 308 |
+
undefined: t.Type[Undefined] = Undefined,
|
| 309 |
+
finalize: t.Optional[t.Callable[..., t.Any]] = None,
|
| 310 |
+
autoescape: t.Union[bool, t.Callable[[t.Optional[str]], bool]] = False,
|
| 311 |
+
loader: t.Optional["BaseLoader"] = None,
|
| 312 |
+
cache_size: int = 400,
|
| 313 |
+
auto_reload: bool = True,
|
| 314 |
+
bytecode_cache: t.Optional["BytecodeCache"] = None,
|
| 315 |
+
enable_async: bool = False,
|
| 316 |
+
):
|
| 317 |
+
# !!Important notice!!
|
| 318 |
+
# The constructor accepts quite a few arguments that should be
|
| 319 |
+
# passed by keyword rather than position. However it's important to
|
| 320 |
+
# not change the order of arguments because it's used at least
|
| 321 |
+
# internally in those cases:
|
| 322 |
+
# - spontaneous environments (i18n extension and Template)
|
| 323 |
+
# - unittests
|
| 324 |
+
# If parameter changes are required only add parameters at the end
|
| 325 |
+
# and don't change the arguments (or the defaults!) of the arguments
|
| 326 |
+
# existing already.
|
| 327 |
+
|
| 328 |
+
# lexer / parser information
|
| 329 |
+
self.block_start_string = block_start_string
|
| 330 |
+
self.block_end_string = block_end_string
|
| 331 |
+
self.variable_start_string = variable_start_string
|
| 332 |
+
self.variable_end_string = variable_end_string
|
| 333 |
+
self.comment_start_string = comment_start_string
|
| 334 |
+
self.comment_end_string = comment_end_string
|
| 335 |
+
self.line_statement_prefix = line_statement_prefix
|
| 336 |
+
self.line_comment_prefix = line_comment_prefix
|
| 337 |
+
self.trim_blocks = trim_blocks
|
| 338 |
+
self.lstrip_blocks = lstrip_blocks
|
| 339 |
+
self.newline_sequence = newline_sequence
|
| 340 |
+
self.keep_trailing_newline = keep_trailing_newline
|
| 341 |
+
|
| 342 |
+
# runtime information
|
| 343 |
+
self.undefined: t.Type[Undefined] = undefined
|
| 344 |
+
self.optimized = optimized
|
| 345 |
+
self.finalize = finalize
|
| 346 |
+
self.autoescape = autoescape
|
| 347 |
+
|
| 348 |
+
# defaults
|
| 349 |
+
self.filters = DEFAULT_FILTERS.copy()
|
| 350 |
+
self.tests = DEFAULT_TESTS.copy()
|
| 351 |
+
self.globals = DEFAULT_NAMESPACE.copy()
|
| 352 |
+
|
| 353 |
+
# set the loader provided
|
| 354 |
+
self.loader = loader
|
| 355 |
+
self.cache = create_cache(cache_size)
|
| 356 |
+
self.bytecode_cache = bytecode_cache
|
| 357 |
+
self.auto_reload = auto_reload
|
| 358 |
+
|
| 359 |
+
# configurable policies
|
| 360 |
+
self.policies = DEFAULT_POLICIES.copy()
|
| 361 |
+
|
| 362 |
+
# load extensions
|
| 363 |
+
self.extensions = load_extensions(self, extensions)
|
| 364 |
+
|
| 365 |
+
self.is_async = enable_async
|
| 366 |
+
_environment_config_check(self)
|
| 367 |
+
|
| 368 |
+
def add_extension(self, extension: t.Union[str, t.Type["Extension"]]) -> None:
|
| 369 |
+
"""Adds an extension after the environment was created.
|
| 370 |
+
|
| 371 |
+
.. versionadded:: 2.5
|
| 372 |
+
"""
|
| 373 |
+
self.extensions.update(load_extensions(self, [extension]))
|
| 374 |
+
|
| 375 |
+
def extend(self, **attributes: t.Any) -> None:
|
| 376 |
+
"""Add the items to the instance of the environment if they do not exist
|
| 377 |
+
yet. This is used by :ref:`extensions <writing-extensions>` to register
|
| 378 |
+
callbacks and configuration values without breaking inheritance.
|
| 379 |
+
"""
|
| 380 |
+
for key, value in attributes.items():
|
| 381 |
+
if not hasattr(self, key):
|
| 382 |
+
setattr(self, key, value)
|
| 383 |
+
|
| 384 |
+
def overlay(
|
| 385 |
+
self,
|
| 386 |
+
block_start_string: str = missing,
|
| 387 |
+
block_end_string: str = missing,
|
| 388 |
+
variable_start_string: str = missing,
|
| 389 |
+
variable_end_string: str = missing,
|
| 390 |
+
comment_start_string: str = missing,
|
| 391 |
+
comment_end_string: str = missing,
|
| 392 |
+
line_statement_prefix: t.Optional[str] = missing,
|
| 393 |
+
line_comment_prefix: t.Optional[str] = missing,
|
| 394 |
+
trim_blocks: bool = missing,
|
| 395 |
+
lstrip_blocks: bool = missing,
|
| 396 |
+
newline_sequence: "te.Literal['\\n', '\\r\\n', '\\r']" = missing,
|
| 397 |
+
keep_trailing_newline: bool = missing,
|
| 398 |
+
extensions: t.Sequence[t.Union[str, t.Type["Extension"]]] = missing,
|
| 399 |
+
optimized: bool = missing,
|
| 400 |
+
undefined: t.Type[Undefined] = missing,
|
| 401 |
+
finalize: t.Optional[t.Callable[..., t.Any]] = missing,
|
| 402 |
+
autoescape: t.Union[bool, t.Callable[[t.Optional[str]], bool]] = missing,
|
| 403 |
+
loader: t.Optional["BaseLoader"] = missing,
|
| 404 |
+
cache_size: int = missing,
|
| 405 |
+
auto_reload: bool = missing,
|
| 406 |
+
bytecode_cache: t.Optional["BytecodeCache"] = missing,
|
| 407 |
+
enable_async: bool = False,
|
| 408 |
+
) -> "Environment":
|
| 409 |
+
"""Create a new overlay environment that shares all the data with the
|
| 410 |
+
current environment except for cache and the overridden attributes.
|
| 411 |
+
Extensions cannot be removed for an overlayed environment. An overlayed
|
| 412 |
+
environment automatically gets all the extensions of the environment it
|
| 413 |
+
is linked to plus optional extra extensions.
|
| 414 |
+
|
| 415 |
+
Creating overlays should happen after the initial environment was set
|
| 416 |
+
up completely. Not all attributes are truly linked, some are just
|
| 417 |
+
copied over so modifications on the original environment may not shine
|
| 418 |
+
through.
|
| 419 |
+
|
| 420 |
+
.. versionchanged:: 3.1.2
|
| 421 |
+
Added the ``newline_sequence``,, ``keep_trailing_newline``,
|
| 422 |
+
and ``enable_async`` parameters to match ``__init__``.
|
| 423 |
+
"""
|
| 424 |
+
args = dict(locals())
|
| 425 |
+
del args["self"], args["cache_size"], args["extensions"], args["enable_async"]
|
| 426 |
+
|
| 427 |
+
rv = object.__new__(self.__class__)
|
| 428 |
+
rv.__dict__.update(self.__dict__)
|
| 429 |
+
rv.overlayed = True
|
| 430 |
+
rv.linked_to = self
|
| 431 |
+
|
| 432 |
+
for key, value in args.items():
|
| 433 |
+
if value is not missing:
|
| 434 |
+
setattr(rv, key, value)
|
| 435 |
+
|
| 436 |
+
if cache_size is not missing:
|
| 437 |
+
rv.cache = create_cache(cache_size)
|
| 438 |
+
else:
|
| 439 |
+
rv.cache = copy_cache(self.cache)
|
| 440 |
+
|
| 441 |
+
rv.extensions = {}
|
| 442 |
+
for key, value in self.extensions.items():
|
| 443 |
+
rv.extensions[key] = value.bind(rv)
|
| 444 |
+
if extensions is not missing:
|
| 445 |
+
rv.extensions.update(load_extensions(rv, extensions))
|
| 446 |
+
|
| 447 |
+
if enable_async is not missing:
|
| 448 |
+
rv.is_async = enable_async
|
| 449 |
+
|
| 450 |
+
return _environment_config_check(rv)
|
| 451 |
+
|
| 452 |
+
@property
|
| 453 |
+
def lexer(self) -> Lexer:
|
| 454 |
+
"""The lexer for this environment."""
|
| 455 |
+
return get_lexer(self)
|
| 456 |
+
|
| 457 |
+
def iter_extensions(self) -> t.Iterator["Extension"]:
|
| 458 |
+
"""Iterates over the extensions by priority."""
|
| 459 |
+
return iter(sorted(self.extensions.values(), key=lambda x: x.priority))
|
| 460 |
+
|
| 461 |
+
def getitem(
|
| 462 |
+
self, obj: t.Any, argument: t.Union[str, t.Any]
|
| 463 |
+
) -> t.Union[t.Any, Undefined]:
|
| 464 |
+
"""Get an item or attribute of an object but prefer the item."""
|
| 465 |
+
try:
|
| 466 |
+
return obj[argument]
|
| 467 |
+
except (AttributeError, TypeError, LookupError):
|
| 468 |
+
if isinstance(argument, str):
|
| 469 |
+
try:
|
| 470 |
+
attr = str(argument)
|
| 471 |
+
except Exception:
|
| 472 |
+
pass
|
| 473 |
+
else:
|
| 474 |
+
try:
|
| 475 |
+
return getattr(obj, attr)
|
| 476 |
+
except AttributeError:
|
| 477 |
+
pass
|
| 478 |
+
return self.undefined(obj=obj, name=argument)
|
| 479 |
+
|
| 480 |
+
def getattr(self, obj: t.Any, attribute: str) -> t.Any:
|
| 481 |
+
"""Get an item or attribute of an object but prefer the attribute.
|
| 482 |
+
Unlike :meth:`getitem` the attribute *must* be a string.
|
| 483 |
+
"""
|
| 484 |
+
try:
|
| 485 |
+
return getattr(obj, attribute)
|
| 486 |
+
except AttributeError:
|
| 487 |
+
pass
|
| 488 |
+
try:
|
| 489 |
+
return obj[attribute]
|
| 490 |
+
except (TypeError, LookupError, AttributeError):
|
| 491 |
+
return self.undefined(obj=obj, name=attribute)
|
| 492 |
+
|
| 493 |
+
def _filter_test_common(
|
| 494 |
+
self,
|
| 495 |
+
name: t.Union[str, Undefined],
|
| 496 |
+
value: t.Any,
|
| 497 |
+
args: t.Optional[t.Sequence[t.Any]],
|
| 498 |
+
kwargs: t.Optional[t.Mapping[str, t.Any]],
|
| 499 |
+
context: t.Optional[Context],
|
| 500 |
+
eval_ctx: t.Optional[EvalContext],
|
| 501 |
+
is_filter: bool,
|
| 502 |
+
) -> t.Any:
|
| 503 |
+
if is_filter:
|
| 504 |
+
env_map = self.filters
|
| 505 |
+
type_name = "filter"
|
| 506 |
+
else:
|
| 507 |
+
env_map = self.tests
|
| 508 |
+
type_name = "test"
|
| 509 |
+
|
| 510 |
+
func = env_map.get(name) # type: ignore
|
| 511 |
+
|
| 512 |
+
if func is None:
|
| 513 |
+
msg = f"No {type_name} named {name!r}."
|
| 514 |
+
|
| 515 |
+
if isinstance(name, Undefined):
|
| 516 |
+
try:
|
| 517 |
+
name._fail_with_undefined_error()
|
| 518 |
+
except Exception as e:
|
| 519 |
+
msg = f"{msg} ({e}; did you forget to quote the callable name?)"
|
| 520 |
+
|
| 521 |
+
raise TemplateRuntimeError(msg)
|
| 522 |
+
|
| 523 |
+
args = [value, *(args if args is not None else ())]
|
| 524 |
+
kwargs = kwargs if kwargs is not None else {}
|
| 525 |
+
pass_arg = _PassArg.from_obj(func)
|
| 526 |
+
|
| 527 |
+
if pass_arg is _PassArg.context:
|
| 528 |
+
if context is None:
|
| 529 |
+
raise TemplateRuntimeError(
|
| 530 |
+
f"Attempted to invoke a context {type_name} without context."
|
| 531 |
+
)
|
| 532 |
+
|
| 533 |
+
args.insert(0, context)
|
| 534 |
+
elif pass_arg is _PassArg.eval_context:
|
| 535 |
+
if eval_ctx is None:
|
| 536 |
+
if context is not None:
|
| 537 |
+
eval_ctx = context.eval_ctx
|
| 538 |
+
else:
|
| 539 |
+
eval_ctx = EvalContext(self)
|
| 540 |
+
|
| 541 |
+
args.insert(0, eval_ctx)
|
| 542 |
+
elif pass_arg is _PassArg.environment:
|
| 543 |
+
args.insert(0, self)
|
| 544 |
+
|
| 545 |
+
return func(*args, **kwargs)
|
| 546 |
+
|
| 547 |
+
def call_filter(
|
| 548 |
+
self,
|
| 549 |
+
name: str,
|
| 550 |
+
value: t.Any,
|
| 551 |
+
args: t.Optional[t.Sequence[t.Any]] = None,
|
| 552 |
+
kwargs: t.Optional[t.Mapping[str, t.Any]] = None,
|
| 553 |
+
context: t.Optional[Context] = None,
|
| 554 |
+
eval_ctx: t.Optional[EvalContext] = None,
|
| 555 |
+
) -> t.Any:
|
| 556 |
+
"""Invoke a filter on a value the same way the compiler does.
|
| 557 |
+
|
| 558 |
+
This might return a coroutine if the filter is running from an
|
| 559 |
+
environment in async mode and the filter supports async
|
| 560 |
+
execution. It's your responsibility to await this if needed.
|
| 561 |
+
|
| 562 |
+
.. versionadded:: 2.7
|
| 563 |
+
"""
|
| 564 |
+
return self._filter_test_common(
|
| 565 |
+
name, value, args, kwargs, context, eval_ctx, True
|
| 566 |
+
)
|
| 567 |
+
|
| 568 |
+
def call_test(
|
| 569 |
+
self,
|
| 570 |
+
name: str,
|
| 571 |
+
value: t.Any,
|
| 572 |
+
args: t.Optional[t.Sequence[t.Any]] = None,
|
| 573 |
+
kwargs: t.Optional[t.Mapping[str, t.Any]] = None,
|
| 574 |
+
context: t.Optional[Context] = None,
|
| 575 |
+
eval_ctx: t.Optional[EvalContext] = None,
|
| 576 |
+
) -> t.Any:
|
| 577 |
+
"""Invoke a test on a value the same way the compiler does.
|
| 578 |
+
|
| 579 |
+
This might return a coroutine if the test is running from an
|
| 580 |
+
environment in async mode and the test supports async execution.
|
| 581 |
+
It's your responsibility to await this if needed.
|
| 582 |
+
|
| 583 |
+
.. versionchanged:: 3.0
|
| 584 |
+
Tests support ``@pass_context``, etc. decorators. Added
|
| 585 |
+
the ``context`` and ``eval_ctx`` parameters.
|
| 586 |
+
|
| 587 |
+
.. versionadded:: 2.7
|
| 588 |
+
"""
|
| 589 |
+
return self._filter_test_common(
|
| 590 |
+
name, value, args, kwargs, context, eval_ctx, False
|
| 591 |
+
)
|
| 592 |
+
|
| 593 |
+
@internalcode
|
| 594 |
+
def parse(
|
| 595 |
+
self,
|
| 596 |
+
source: str,
|
| 597 |
+
name: t.Optional[str] = None,
|
| 598 |
+
filename: t.Optional[str] = None,
|
| 599 |
+
) -> nodes.Template:
|
| 600 |
+
"""Parse the sourcecode and return the abstract syntax tree. This
|
| 601 |
+
tree of nodes is used by the compiler to convert the template into
|
| 602 |
+
executable source- or bytecode. This is useful for debugging or to
|
| 603 |
+
extract information from templates.
|
| 604 |
+
|
| 605 |
+
If you are :ref:`developing Jinja extensions <writing-extensions>`
|
| 606 |
+
this gives you a good overview of the node tree generated.
|
| 607 |
+
"""
|
| 608 |
+
try:
|
| 609 |
+
return self._parse(source, name, filename)
|
| 610 |
+
except TemplateSyntaxError:
|
| 611 |
+
self.handle_exception(source=source)
|
| 612 |
+
|
| 613 |
+
def _parse(
|
| 614 |
+
self, source: str, name: t.Optional[str], filename: t.Optional[str]
|
| 615 |
+
) -> nodes.Template:
|
| 616 |
+
"""Internal parsing function used by `parse` and `compile`."""
|
| 617 |
+
return Parser(self, source, name, filename).parse()
|
| 618 |
+
|
| 619 |
+
def lex(
|
| 620 |
+
self,
|
| 621 |
+
source: str,
|
| 622 |
+
name: t.Optional[str] = None,
|
| 623 |
+
filename: t.Optional[str] = None,
|
| 624 |
+
) -> t.Iterator[t.Tuple[int, str, str]]:
|
| 625 |
+
"""Lex the given sourcecode and return a generator that yields
|
| 626 |
+
tokens as tuples in the form ``(lineno, token_type, value)``.
|
| 627 |
+
This can be useful for :ref:`extension development <writing-extensions>`
|
| 628 |
+
and debugging templates.
|
| 629 |
+
|
| 630 |
+
This does not perform preprocessing. If you want the preprocessing
|
| 631 |
+
of the extensions to be applied you have to filter source through
|
| 632 |
+
the :meth:`preprocess` method.
|
| 633 |
+
"""
|
| 634 |
+
source = str(source)
|
| 635 |
+
try:
|
| 636 |
+
return self.lexer.tokeniter(source, name, filename)
|
| 637 |
+
except TemplateSyntaxError:
|
| 638 |
+
self.handle_exception(source=source)
|
| 639 |
+
|
| 640 |
+
def preprocess(
|
| 641 |
+
self,
|
| 642 |
+
source: str,
|
| 643 |
+
name: t.Optional[str] = None,
|
| 644 |
+
filename: t.Optional[str] = None,
|
| 645 |
+
) -> str:
|
| 646 |
+
"""Preprocesses the source with all extensions. This is automatically
|
| 647 |
+
called for all parsing and compiling methods but *not* for :meth:`lex`
|
| 648 |
+
because there you usually only want the actual source tokenized.
|
| 649 |
+
"""
|
| 650 |
+
return reduce(
|
| 651 |
+
lambda s, e: e.preprocess(s, name, filename),
|
| 652 |
+
self.iter_extensions(),
|
| 653 |
+
str(source),
|
| 654 |
+
)
|
| 655 |
+
|
| 656 |
+
def _tokenize(
|
| 657 |
+
self,
|
| 658 |
+
source: str,
|
| 659 |
+
name: t.Optional[str],
|
| 660 |
+
filename: t.Optional[str] = None,
|
| 661 |
+
state: t.Optional[str] = None,
|
| 662 |
+
) -> TokenStream:
|
| 663 |
+
"""Called by the parser to do the preprocessing and filtering
|
| 664 |
+
for all the extensions. Returns a :class:`~jinja2.lexer.TokenStream`.
|
| 665 |
+
"""
|
| 666 |
+
source = self.preprocess(source, name, filename)
|
| 667 |
+
stream = self.lexer.tokenize(source, name, filename, state)
|
| 668 |
+
|
| 669 |
+
for ext in self.iter_extensions():
|
| 670 |
+
stream = ext.filter_stream(stream) # type: ignore
|
| 671 |
+
|
| 672 |
+
if not isinstance(stream, TokenStream):
|
| 673 |
+
stream = TokenStream(stream, name, filename) # type: ignore
|
| 674 |
+
|
| 675 |
+
return stream
|
| 676 |
+
|
| 677 |
+
def _generate(
|
| 678 |
+
self,
|
| 679 |
+
source: nodes.Template,
|
| 680 |
+
name: t.Optional[str],
|
| 681 |
+
filename: t.Optional[str],
|
| 682 |
+
defer_init: bool = False,
|
| 683 |
+
) -> str:
|
| 684 |
+
"""Internal hook that can be overridden to hook a different generate
|
| 685 |
+
method in.
|
| 686 |
+
|
| 687 |
+
.. versionadded:: 2.5
|
| 688 |
+
"""
|
| 689 |
+
return generate( # type: ignore
|
| 690 |
+
source,
|
| 691 |
+
self,
|
| 692 |
+
name,
|
| 693 |
+
filename,
|
| 694 |
+
defer_init=defer_init,
|
| 695 |
+
optimized=self.optimized,
|
| 696 |
+
)
|
| 697 |
+
|
| 698 |
+
def _compile(self, source: str, filename: str) -> CodeType:
|
| 699 |
+
"""Internal hook that can be overridden to hook a different compile
|
| 700 |
+
method in.
|
| 701 |
+
|
| 702 |
+
.. versionadded:: 2.5
|
| 703 |
+
"""
|
| 704 |
+
return compile(source, filename, "exec")
|
| 705 |
+
|
| 706 |
+
@typing.overload
|
| 707 |
+
def compile( # type: ignore
|
| 708 |
+
self,
|
| 709 |
+
source: t.Union[str, nodes.Template],
|
| 710 |
+
name: t.Optional[str] = None,
|
| 711 |
+
filename: t.Optional[str] = None,
|
| 712 |
+
raw: "te.Literal[False]" = False,
|
| 713 |
+
defer_init: bool = False,
|
| 714 |
+
) -> CodeType:
|
| 715 |
+
...
|
| 716 |
+
|
| 717 |
+
@typing.overload
|
| 718 |
+
def compile(
|
| 719 |
+
self,
|
| 720 |
+
source: t.Union[str, nodes.Template],
|
| 721 |
+
name: t.Optional[str] = None,
|
| 722 |
+
filename: t.Optional[str] = None,
|
| 723 |
+
raw: "te.Literal[True]" = ...,
|
| 724 |
+
defer_init: bool = False,
|
| 725 |
+
) -> str:
|
| 726 |
+
...
|
| 727 |
+
|
| 728 |
+
@internalcode
|
| 729 |
+
def compile(
|
| 730 |
+
self,
|
| 731 |
+
source: t.Union[str, nodes.Template],
|
| 732 |
+
name: t.Optional[str] = None,
|
| 733 |
+
filename: t.Optional[str] = None,
|
| 734 |
+
raw: bool = False,
|
| 735 |
+
defer_init: bool = False,
|
| 736 |
+
) -> t.Union[str, CodeType]:
|
| 737 |
+
"""Compile a node or template source code. The `name` parameter is
|
| 738 |
+
the load name of the template after it was joined using
|
| 739 |
+
:meth:`join_path` if necessary, not the filename on the file system.
|
| 740 |
+
the `filename` parameter is the estimated filename of the template on
|
| 741 |
+
the file system. If the template came from a database or memory this
|
| 742 |
+
can be omitted.
|
| 743 |
+
|
| 744 |
+
The return value of this method is a python code object. If the `raw`
|
| 745 |
+
parameter is `True` the return value will be a string with python
|
| 746 |
+
code equivalent to the bytecode returned otherwise. This method is
|
| 747 |
+
mainly used internally.
|
| 748 |
+
|
| 749 |
+
`defer_init` is use internally to aid the module code generator. This
|
| 750 |
+
causes the generated code to be able to import without the global
|
| 751 |
+
environment variable to be set.
|
| 752 |
+
|
| 753 |
+
.. versionadded:: 2.4
|
| 754 |
+
`defer_init` parameter added.
|
| 755 |
+
"""
|
| 756 |
+
source_hint = None
|
| 757 |
+
try:
|
| 758 |
+
if isinstance(source, str):
|
| 759 |
+
source_hint = source
|
| 760 |
+
source = self._parse(source, name, filename)
|
| 761 |
+
source = self._generate(source, name, filename, defer_init=defer_init)
|
| 762 |
+
if raw:
|
| 763 |
+
return source
|
| 764 |
+
if filename is None:
|
| 765 |
+
filename = "<template>"
|
| 766 |
+
return self._compile(source, filename)
|
| 767 |
+
except TemplateSyntaxError:
|
| 768 |
+
self.handle_exception(source=source_hint)
|
| 769 |
+
|
| 770 |
+
def compile_expression(
|
| 771 |
+
self, source: str, undefined_to_none: bool = True
|
| 772 |
+
) -> "TemplateExpression":
|
| 773 |
+
"""A handy helper method that returns a callable that accepts keyword
|
| 774 |
+
arguments that appear as variables in the expression. If called it
|
| 775 |
+
returns the result of the expression.
|
| 776 |
+
|
| 777 |
+
This is useful if applications want to use the same rules as Jinja
|
| 778 |
+
in template "configuration files" or similar situations.
|
| 779 |
+
|
| 780 |
+
Example usage:
|
| 781 |
+
|
| 782 |
+
>>> env = Environment()
|
| 783 |
+
>>> expr = env.compile_expression('foo == 42')
|
| 784 |
+
>>> expr(foo=23)
|
| 785 |
+
False
|
| 786 |
+
>>> expr(foo=42)
|
| 787 |
+
True
|
| 788 |
+
|
| 789 |
+
Per default the return value is converted to `None` if the
|
| 790 |
+
expression returns an undefined value. This can be changed
|
| 791 |
+
by setting `undefined_to_none` to `False`.
|
| 792 |
+
|
| 793 |
+
>>> env.compile_expression('var')() is None
|
| 794 |
+
True
|
| 795 |
+
>>> env.compile_expression('var', undefined_to_none=False)()
|
| 796 |
+
Undefined
|
| 797 |
+
|
| 798 |
+
.. versionadded:: 2.1
|
| 799 |
+
"""
|
| 800 |
+
parser = Parser(self, source, state="variable")
|
| 801 |
+
try:
|
| 802 |
+
expr = parser.parse_expression()
|
| 803 |
+
if not parser.stream.eos:
|
| 804 |
+
raise TemplateSyntaxError(
|
| 805 |
+
"chunk after expression", parser.stream.current.lineno, None, None
|
| 806 |
+
)
|
| 807 |
+
expr.set_environment(self)
|
| 808 |
+
except TemplateSyntaxError:
|
| 809 |
+
self.handle_exception(source=source)
|
| 810 |
+
|
| 811 |
+
body = [nodes.Assign(nodes.Name("result", "store"), expr, lineno=1)]
|
| 812 |
+
template = self.from_string(nodes.Template(body, lineno=1))
|
| 813 |
+
return TemplateExpression(template, undefined_to_none)
|
| 814 |
+
|
| 815 |
+
def compile_templates(
|
| 816 |
+
self,
|
| 817 |
+
target: t.Union[str, os.PathLike],
|
| 818 |
+
extensions: t.Optional[t.Collection[str]] = None,
|
| 819 |
+
filter_func: t.Optional[t.Callable[[str], bool]] = None,
|
| 820 |
+
zip: t.Optional[str] = "deflated",
|
| 821 |
+
log_function: t.Optional[t.Callable[[str], None]] = None,
|
| 822 |
+
ignore_errors: bool = True,
|
| 823 |
+
) -> None:
|
| 824 |
+
"""Finds all the templates the loader can find, compiles them
|
| 825 |
+
and stores them in `target`. If `zip` is `None`, instead of in a
|
| 826 |
+
zipfile, the templates will be stored in a directory.
|
| 827 |
+
By default a deflate zip algorithm is used. To switch to
|
| 828 |
+
the stored algorithm, `zip` can be set to ``'stored'``.
|
| 829 |
+
|
| 830 |
+
`extensions` and `filter_func` are passed to :meth:`list_templates`.
|
| 831 |
+
Each template returned will be compiled to the target folder or
|
| 832 |
+
zipfile.
|
| 833 |
+
|
| 834 |
+
By default template compilation errors are ignored. In case a
|
| 835 |
+
log function is provided, errors are logged. If you want template
|
| 836 |
+
syntax errors to abort the compilation you can set `ignore_errors`
|
| 837 |
+
to `False` and you will get an exception on syntax errors.
|
| 838 |
+
|
| 839 |
+
.. versionadded:: 2.4
|
| 840 |
+
"""
|
| 841 |
+
from .loaders import ModuleLoader
|
| 842 |
+
|
| 843 |
+
if log_function is None:
|
| 844 |
+
|
| 845 |
+
def log_function(x: str) -> None:
|
| 846 |
+
pass
|
| 847 |
+
|
| 848 |
+
assert log_function is not None
|
| 849 |
+
assert self.loader is not None, "No loader configured."
|
| 850 |
+
|
| 851 |
+
def write_file(filename: str, data: str) -> None:
|
| 852 |
+
if zip:
|
| 853 |
+
info = ZipInfo(filename)
|
| 854 |
+
info.external_attr = 0o755 << 16
|
| 855 |
+
zip_file.writestr(info, data)
|
| 856 |
+
else:
|
| 857 |
+
with open(os.path.join(target, filename), "wb") as f:
|
| 858 |
+
f.write(data.encode("utf8"))
|
| 859 |
+
|
| 860 |
+
if zip is not None:
|
| 861 |
+
from zipfile import ZipFile, ZipInfo, ZIP_DEFLATED, ZIP_STORED
|
| 862 |
+
|
| 863 |
+
zip_file = ZipFile(
|
| 864 |
+
target, "w", dict(deflated=ZIP_DEFLATED, stored=ZIP_STORED)[zip]
|
| 865 |
+
)
|
| 866 |
+
log_function(f"Compiling into Zip archive {target!r}")
|
| 867 |
+
else:
|
| 868 |
+
if not os.path.isdir(target):
|
| 869 |
+
os.makedirs(target)
|
| 870 |
+
log_function(f"Compiling into folder {target!r}")
|
| 871 |
+
|
| 872 |
+
try:
|
| 873 |
+
for name in self.list_templates(extensions, filter_func):
|
| 874 |
+
source, filename, _ = self.loader.get_source(self, name)
|
| 875 |
+
try:
|
| 876 |
+
code = self.compile(source, name, filename, True, True)
|
| 877 |
+
except TemplateSyntaxError as e:
|
| 878 |
+
if not ignore_errors:
|
| 879 |
+
raise
|
| 880 |
+
log_function(f'Could not compile "{name}": {e}')
|
| 881 |
+
continue
|
| 882 |
+
|
| 883 |
+
filename = ModuleLoader.get_module_filename(name)
|
| 884 |
+
|
| 885 |
+
write_file(filename, code)
|
| 886 |
+
log_function(f'Compiled "{name}" as {filename}')
|
| 887 |
+
finally:
|
| 888 |
+
if zip:
|
| 889 |
+
zip_file.close()
|
| 890 |
+
|
| 891 |
+
log_function("Finished compiling templates")
|
| 892 |
+
|
| 893 |
+
def list_templates(
|
| 894 |
+
self,
|
| 895 |
+
extensions: t.Optional[t.Collection[str]] = None,
|
| 896 |
+
filter_func: t.Optional[t.Callable[[str], bool]] = None,
|
| 897 |
+
) -> t.List[str]:
|
| 898 |
+
"""Returns a list of templates for this environment. This requires
|
| 899 |
+
that the loader supports the loader's
|
| 900 |
+
:meth:`~BaseLoader.list_templates` method.
|
| 901 |
+
|
| 902 |
+
If there are other files in the template folder besides the
|
| 903 |
+
actual templates, the returned list can be filtered. There are two
|
| 904 |
+
ways: either `extensions` is set to a list of file extensions for
|
| 905 |
+
templates, or a `filter_func` can be provided which is a callable that
|
| 906 |
+
is passed a template name and should return `True` if it should end up
|
| 907 |
+
in the result list.
|
| 908 |
+
|
| 909 |
+
If the loader does not support that, a :exc:`TypeError` is raised.
|
| 910 |
+
|
| 911 |
+
.. versionadded:: 2.4
|
| 912 |
+
"""
|
| 913 |
+
assert self.loader is not None, "No loader configured."
|
| 914 |
+
names = self.loader.list_templates()
|
| 915 |
+
|
| 916 |
+
if extensions is not None:
|
| 917 |
+
if filter_func is not None:
|
| 918 |
+
raise TypeError(
|
| 919 |
+
"either extensions or filter_func can be passed, but not both"
|
| 920 |
+
)
|
| 921 |
+
|
| 922 |
+
def filter_func(x: str) -> bool:
|
| 923 |
+
return "." in x and x.rsplit(".", 1)[1] in extensions
|
| 924 |
+
|
| 925 |
+
if filter_func is not None:
|
| 926 |
+
names = [name for name in names if filter_func(name)]
|
| 927 |
+
|
| 928 |
+
return names
|
| 929 |
+
|
| 930 |
+
def handle_exception(self, source: t.Optional[str] = None) -> "te.NoReturn":
|
| 931 |
+
"""Exception handling helper. This is used internally to either raise
|
| 932 |
+
rewritten exceptions or return a rendered traceback for the template.
|
| 933 |
+
"""
|
| 934 |
+
from .debug import rewrite_traceback_stack
|
| 935 |
+
|
| 936 |
+
raise rewrite_traceback_stack(source=source)
|
| 937 |
+
|
| 938 |
+
def join_path(self, template: str, parent: str) -> str:
|
| 939 |
+
"""Join a template with the parent. By default all the lookups are
|
| 940 |
+
relative to the loader root so this method returns the `template`
|
| 941 |
+
parameter unchanged, but if the paths should be relative to the
|
| 942 |
+
parent template, this function can be used to calculate the real
|
| 943 |
+
template name.
|
| 944 |
+
|
| 945 |
+
Subclasses may override this method and implement template path
|
| 946 |
+
joining here.
|
| 947 |
+
"""
|
| 948 |
+
return template
|
| 949 |
+
|
| 950 |
+
@internalcode
|
| 951 |
+
def _load_template(
|
| 952 |
+
self, name: str, globals: t.Optional[t.MutableMapping[str, t.Any]]
|
| 953 |
+
) -> "Template":
|
| 954 |
+
if self.loader is None:
|
| 955 |
+
raise TypeError("no loader for this environment specified")
|
| 956 |
+
cache_key = (weakref.ref(self.loader), name)
|
| 957 |
+
if self.cache is not None:
|
| 958 |
+
template = self.cache.get(cache_key)
|
| 959 |
+
if template is not None and (
|
| 960 |
+
not self.auto_reload or template.is_up_to_date
|
| 961 |
+
):
|
| 962 |
+
# template.globals is a ChainMap, modifying it will only
|
| 963 |
+
# affect the template, not the environment globals.
|
| 964 |
+
if globals:
|
| 965 |
+
template.globals.update(globals)
|
| 966 |
+
|
| 967 |
+
return template
|
| 968 |
+
|
| 969 |
+
template = self.loader.load(self, name, self.make_globals(globals))
|
| 970 |
+
|
| 971 |
+
if self.cache is not None:
|
| 972 |
+
self.cache[cache_key] = template
|
| 973 |
+
return template
|
| 974 |
+
|
| 975 |
+
@internalcode
|
| 976 |
+
def get_template(
|
| 977 |
+
self,
|
| 978 |
+
name: t.Union[str, "Template"],
|
| 979 |
+
parent: t.Optional[str] = None,
|
| 980 |
+
globals: t.Optional[t.MutableMapping[str, t.Any]] = None,
|
| 981 |
+
) -> "Template":
|
| 982 |
+
"""Load a template by name with :attr:`loader` and return a
|
| 983 |
+
:class:`Template`. If the template does not exist a
|
| 984 |
+
:exc:`TemplateNotFound` exception is raised.
|
| 985 |
+
|
| 986 |
+
:param name: Name of the template to load. When loading
|
| 987 |
+
templates from the filesystem, "/" is used as the path
|
| 988 |
+
separator, even on Windows.
|
| 989 |
+
:param parent: The name of the parent template importing this
|
| 990 |
+
template. :meth:`join_path` can be used to implement name
|
| 991 |
+
transformations with this.
|
| 992 |
+
:param globals: Extend the environment :attr:`globals` with
|
| 993 |
+
these extra variables available for all renders of this
|
| 994 |
+
template. If the template has already been loaded and
|
| 995 |
+
cached, its globals are updated with any new items.
|
| 996 |
+
|
| 997 |
+
.. versionchanged:: 3.0
|
| 998 |
+
If a template is loaded from cache, ``globals`` will update
|
| 999 |
+
the template's globals instead of ignoring the new values.
|
| 1000 |
+
|
| 1001 |
+
.. versionchanged:: 2.4
|
| 1002 |
+
If ``name`` is a :class:`Template` object it is returned
|
| 1003 |
+
unchanged.
|
| 1004 |
+
"""
|
| 1005 |
+
if isinstance(name, Template):
|
| 1006 |
+
return name
|
| 1007 |
+
if parent is not None:
|
| 1008 |
+
name = self.join_path(name, parent)
|
| 1009 |
+
|
| 1010 |
+
return self._load_template(name, globals)
|
| 1011 |
+
|
| 1012 |
+
@internalcode
|
| 1013 |
+
def select_template(
|
| 1014 |
+
self,
|
| 1015 |
+
names: t.Iterable[t.Union[str, "Template"]],
|
| 1016 |
+
parent: t.Optional[str] = None,
|
| 1017 |
+
globals: t.Optional[t.MutableMapping[str, t.Any]] = None,
|
| 1018 |
+
) -> "Template":
|
| 1019 |
+
"""Like :meth:`get_template`, but tries loading multiple names.
|
| 1020 |
+
If none of the names can be loaded a :exc:`TemplatesNotFound`
|
| 1021 |
+
exception is raised.
|
| 1022 |
+
|
| 1023 |
+
:param names: List of template names to try loading in order.
|
| 1024 |
+
:param parent: The name of the parent template importing this
|
| 1025 |
+
template. :meth:`join_path` can be used to implement name
|
| 1026 |
+
transformations with this.
|
| 1027 |
+
:param globals: Extend the environment :attr:`globals` with
|
| 1028 |
+
these extra variables available for all renders of this
|
| 1029 |
+
template. If the template has already been loaded and
|
| 1030 |
+
cached, its globals are updated with any new items.
|
| 1031 |
+
|
| 1032 |
+
.. versionchanged:: 3.0
|
| 1033 |
+
If a template is loaded from cache, ``globals`` will update
|
| 1034 |
+
the template's globals instead of ignoring the new values.
|
| 1035 |
+
|
| 1036 |
+
.. versionchanged:: 2.11
|
| 1037 |
+
If ``names`` is :class:`Undefined`, an :exc:`UndefinedError`
|
| 1038 |
+
is raised instead. If no templates were found and ``names``
|
| 1039 |
+
contains :class:`Undefined`, the message is more helpful.
|
| 1040 |
+
|
| 1041 |
+
.. versionchanged:: 2.4
|
| 1042 |
+
If ``names`` contains a :class:`Template` object it is
|
| 1043 |
+
returned unchanged.
|
| 1044 |
+
|
| 1045 |
+
.. versionadded:: 2.3
|
| 1046 |
+
"""
|
| 1047 |
+
if isinstance(names, Undefined):
|
| 1048 |
+
names._fail_with_undefined_error()
|
| 1049 |
+
|
| 1050 |
+
if not names:
|
| 1051 |
+
raise TemplatesNotFound(
|
| 1052 |
+
message="Tried to select from an empty list of templates."
|
| 1053 |
+
)
|
| 1054 |
+
|
| 1055 |
+
for name in names:
|
| 1056 |
+
if isinstance(name, Template):
|
| 1057 |
+
return name
|
| 1058 |
+
if parent is not None:
|
| 1059 |
+
name = self.join_path(name, parent)
|
| 1060 |
+
try:
|
| 1061 |
+
return self._load_template(name, globals)
|
| 1062 |
+
except (TemplateNotFound, UndefinedError):
|
| 1063 |
+
pass
|
| 1064 |
+
raise TemplatesNotFound(names) # type: ignore
|
| 1065 |
+
|
| 1066 |
+
@internalcode
|
| 1067 |
+
def get_or_select_template(
|
| 1068 |
+
self,
|
| 1069 |
+
template_name_or_list: t.Union[
|
| 1070 |
+
str, "Template", t.List[t.Union[str, "Template"]]
|
| 1071 |
+
],
|
| 1072 |
+
parent: t.Optional[str] = None,
|
| 1073 |
+
globals: t.Optional[t.MutableMapping[str, t.Any]] = None,
|
| 1074 |
+
) -> "Template":
|
| 1075 |
+
"""Use :meth:`select_template` if an iterable of template names
|
| 1076 |
+
is given, or :meth:`get_template` if one name is given.
|
| 1077 |
+
|
| 1078 |
+
.. versionadded:: 2.3
|
| 1079 |
+
"""
|
| 1080 |
+
if isinstance(template_name_or_list, (str, Undefined)):
|
| 1081 |
+
return self.get_template(template_name_or_list, parent, globals)
|
| 1082 |
+
elif isinstance(template_name_or_list, Template):
|
| 1083 |
+
return template_name_or_list
|
| 1084 |
+
return self.select_template(template_name_or_list, parent, globals)
|
| 1085 |
+
|
| 1086 |
+
def from_string(
|
| 1087 |
+
self,
|
| 1088 |
+
source: t.Union[str, nodes.Template],
|
| 1089 |
+
globals: t.Optional[t.MutableMapping[str, t.Any]] = None,
|
| 1090 |
+
template_class: t.Optional[t.Type["Template"]] = None,
|
| 1091 |
+
) -> "Template":
|
| 1092 |
+
"""Load a template from a source string without using
|
| 1093 |
+
:attr:`loader`.
|
| 1094 |
+
|
| 1095 |
+
:param source: Jinja source to compile into a template.
|
| 1096 |
+
:param globals: Extend the environment :attr:`globals` with
|
| 1097 |
+
these extra variables available for all renders of this
|
| 1098 |
+
template. If the template has already been loaded and
|
| 1099 |
+
cached, its globals are updated with any new items.
|
| 1100 |
+
:param template_class: Return an instance of this
|
| 1101 |
+
:class:`Template` class.
|
| 1102 |
+
"""
|
| 1103 |
+
gs = self.make_globals(globals)
|
| 1104 |
+
cls = template_class or self.template_class
|
| 1105 |
+
return cls.from_code(self, self.compile(source), gs, None)
|
| 1106 |
+
|
| 1107 |
+
def make_globals(
|
| 1108 |
+
self, d: t.Optional[t.MutableMapping[str, t.Any]]
|
| 1109 |
+
) -> t.MutableMapping[str, t.Any]:
|
| 1110 |
+
"""Make the globals map for a template. Any given template
|
| 1111 |
+
globals overlay the environment :attr:`globals`.
|
| 1112 |
+
|
| 1113 |
+
Returns a :class:`collections.ChainMap`. This allows any changes
|
| 1114 |
+
to a template's globals to only affect that template, while
|
| 1115 |
+
changes to the environment's globals are still reflected.
|
| 1116 |
+
However, avoid modifying any globals after a template is loaded.
|
| 1117 |
+
|
| 1118 |
+
:param d: Dict of template-specific globals.
|
| 1119 |
+
|
| 1120 |
+
.. versionchanged:: 3.0
|
| 1121 |
+
Use :class:`collections.ChainMap` to always prevent mutating
|
| 1122 |
+
environment globals.
|
| 1123 |
+
"""
|
| 1124 |
+
if d is None:
|
| 1125 |
+
d = {}
|
| 1126 |
+
|
| 1127 |
+
return ChainMap(d, self.globals)
|
| 1128 |
+
|
| 1129 |
+
|
| 1130 |
+
class Template:
|
| 1131 |
+
"""A compiled template that can be rendered.
|
| 1132 |
+
|
| 1133 |
+
Use the methods on :class:`Environment` to create or load templates.
|
| 1134 |
+
The environment is used to configure how templates are compiled and
|
| 1135 |
+
behave.
|
| 1136 |
+
|
| 1137 |
+
It is also possible to create a template object directly. This is
|
| 1138 |
+
not usually recommended. The constructor takes most of the same
|
| 1139 |
+
arguments as :class:`Environment`. All templates created with the
|
| 1140 |
+
same environment arguments share the same ephemeral ``Environment``
|
| 1141 |
+
instance behind the scenes.
|
| 1142 |
+
|
| 1143 |
+
A template object should be considered immutable. Modifications on
|
| 1144 |
+
the object are not supported.
|
| 1145 |
+
"""
|
| 1146 |
+
|
| 1147 |
+
#: Type of environment to create when creating a template directly
|
| 1148 |
+
#: rather than through an existing environment.
|
| 1149 |
+
environment_class: t.Type[Environment] = Environment
|
| 1150 |
+
|
| 1151 |
+
environment: Environment
|
| 1152 |
+
globals: t.MutableMapping[str, t.Any]
|
| 1153 |
+
name: t.Optional[str]
|
| 1154 |
+
filename: t.Optional[str]
|
| 1155 |
+
blocks: t.Dict[str, t.Callable[[Context], t.Iterator[str]]]
|
| 1156 |
+
root_render_func: t.Callable[[Context], t.Iterator[str]]
|
| 1157 |
+
_module: t.Optional["TemplateModule"]
|
| 1158 |
+
_debug_info: str
|
| 1159 |
+
_uptodate: t.Optional[t.Callable[[], bool]]
|
| 1160 |
+
|
| 1161 |
+
def __new__(
|
| 1162 |
+
cls,
|
| 1163 |
+
source: t.Union[str, nodes.Template],
|
| 1164 |
+
block_start_string: str = BLOCK_START_STRING,
|
| 1165 |
+
block_end_string: str = BLOCK_END_STRING,
|
| 1166 |
+
variable_start_string: str = VARIABLE_START_STRING,
|
| 1167 |
+
variable_end_string: str = VARIABLE_END_STRING,
|
| 1168 |
+
comment_start_string: str = COMMENT_START_STRING,
|
| 1169 |
+
comment_end_string: str = COMMENT_END_STRING,
|
| 1170 |
+
line_statement_prefix: t.Optional[str] = LINE_STATEMENT_PREFIX,
|
| 1171 |
+
line_comment_prefix: t.Optional[str] = LINE_COMMENT_PREFIX,
|
| 1172 |
+
trim_blocks: bool = TRIM_BLOCKS,
|
| 1173 |
+
lstrip_blocks: bool = LSTRIP_BLOCKS,
|
| 1174 |
+
newline_sequence: "te.Literal['\\n', '\\r\\n', '\\r']" = NEWLINE_SEQUENCE,
|
| 1175 |
+
keep_trailing_newline: bool = KEEP_TRAILING_NEWLINE,
|
| 1176 |
+
extensions: t.Sequence[t.Union[str, t.Type["Extension"]]] = (),
|
| 1177 |
+
optimized: bool = True,
|
| 1178 |
+
undefined: t.Type[Undefined] = Undefined,
|
| 1179 |
+
finalize: t.Optional[t.Callable[..., t.Any]] = None,
|
| 1180 |
+
autoescape: t.Union[bool, t.Callable[[t.Optional[str]], bool]] = False,
|
| 1181 |
+
enable_async: bool = False,
|
| 1182 |
+
) -> t.Any: # it returns a `Template`, but this breaks the sphinx build...
|
| 1183 |
+
env = get_spontaneous_environment(
|
| 1184 |
+
cls.environment_class, # type: ignore
|
| 1185 |
+
block_start_string,
|
| 1186 |
+
block_end_string,
|
| 1187 |
+
variable_start_string,
|
| 1188 |
+
variable_end_string,
|
| 1189 |
+
comment_start_string,
|
| 1190 |
+
comment_end_string,
|
| 1191 |
+
line_statement_prefix,
|
| 1192 |
+
line_comment_prefix,
|
| 1193 |
+
trim_blocks,
|
| 1194 |
+
lstrip_blocks,
|
| 1195 |
+
newline_sequence,
|
| 1196 |
+
keep_trailing_newline,
|
| 1197 |
+
frozenset(extensions),
|
| 1198 |
+
optimized,
|
| 1199 |
+
undefined, # type: ignore
|
| 1200 |
+
finalize,
|
| 1201 |
+
autoescape,
|
| 1202 |
+
None,
|
| 1203 |
+
0,
|
| 1204 |
+
False,
|
| 1205 |
+
None,
|
| 1206 |
+
enable_async,
|
| 1207 |
+
)
|
| 1208 |
+
return env.from_string(source, template_class=cls)
|
| 1209 |
+
|
| 1210 |
+
@classmethod
|
| 1211 |
+
def from_code(
|
| 1212 |
+
cls,
|
| 1213 |
+
environment: Environment,
|
| 1214 |
+
code: CodeType,
|
| 1215 |
+
globals: t.MutableMapping[str, t.Any],
|
| 1216 |
+
uptodate: t.Optional[t.Callable[[], bool]] = None,
|
| 1217 |
+
) -> "Template":
|
| 1218 |
+
"""Creates a template object from compiled code and the globals. This
|
| 1219 |
+
is used by the loaders and environment to create a template object.
|
| 1220 |
+
"""
|
| 1221 |
+
namespace = {"environment": environment, "__file__": code.co_filename}
|
| 1222 |
+
exec(code, namespace)
|
| 1223 |
+
rv = cls._from_namespace(environment, namespace, globals)
|
| 1224 |
+
rv._uptodate = uptodate
|
| 1225 |
+
return rv
|
| 1226 |
+
|
| 1227 |
+
@classmethod
|
| 1228 |
+
def from_module_dict(
|
| 1229 |
+
cls,
|
| 1230 |
+
environment: Environment,
|
| 1231 |
+
module_dict: t.MutableMapping[str, t.Any],
|
| 1232 |
+
globals: t.MutableMapping[str, t.Any],
|
| 1233 |
+
) -> "Template":
|
| 1234 |
+
"""Creates a template object from a module. This is used by the
|
| 1235 |
+
module loader to create a template object.
|
| 1236 |
+
|
| 1237 |
+
.. versionadded:: 2.4
|
| 1238 |
+
"""
|
| 1239 |
+
return cls._from_namespace(environment, module_dict, globals)
|
| 1240 |
+
|
| 1241 |
+
@classmethod
|
| 1242 |
+
def _from_namespace(
|
| 1243 |
+
cls,
|
| 1244 |
+
environment: Environment,
|
| 1245 |
+
namespace: t.MutableMapping[str, t.Any],
|
| 1246 |
+
globals: t.MutableMapping[str, t.Any],
|
| 1247 |
+
) -> "Template":
|
| 1248 |
+
t: "Template" = object.__new__(cls)
|
| 1249 |
+
t.environment = environment
|
| 1250 |
+
t.globals = globals
|
| 1251 |
+
t.name = namespace["name"]
|
| 1252 |
+
t.filename = namespace["__file__"]
|
| 1253 |
+
t.blocks = namespace["blocks"]
|
| 1254 |
+
|
| 1255 |
+
# render function and module
|
| 1256 |
+
t.root_render_func = namespace["root"]
|
| 1257 |
+
t._module = None
|
| 1258 |
+
|
| 1259 |
+
# debug and loader helpers
|
| 1260 |
+
t._debug_info = namespace["debug_info"]
|
| 1261 |
+
t._uptodate = None
|
| 1262 |
+
|
| 1263 |
+
# store the reference
|
| 1264 |
+
namespace["environment"] = environment
|
| 1265 |
+
namespace["__jinja_template__"] = t
|
| 1266 |
+
|
| 1267 |
+
return t
|
| 1268 |
+
|
| 1269 |
+
def render(self, *args: t.Any, **kwargs: t.Any) -> str:
|
| 1270 |
+
"""This method accepts the same arguments as the `dict` constructor:
|
| 1271 |
+
A dict, a dict subclass or some keyword arguments. If no arguments
|
| 1272 |
+
are given the context will be empty. These two calls do the same::
|
| 1273 |
+
|
| 1274 |
+
template.render(knights='that say nih')
|
| 1275 |
+
template.render({'knights': 'that say nih'})
|
| 1276 |
+
|
| 1277 |
+
This will return the rendered template as a string.
|
| 1278 |
+
"""
|
| 1279 |
+
if self.environment.is_async:
|
| 1280 |
+
import asyncio
|
| 1281 |
+
|
| 1282 |
+
close = False
|
| 1283 |
+
|
| 1284 |
+
try:
|
| 1285 |
+
loop = asyncio.get_running_loop()
|
| 1286 |
+
except RuntimeError:
|
| 1287 |
+
loop = asyncio.new_event_loop()
|
| 1288 |
+
close = True
|
| 1289 |
+
|
| 1290 |
+
try:
|
| 1291 |
+
return loop.run_until_complete(self.render_async(*args, **kwargs))
|
| 1292 |
+
finally:
|
| 1293 |
+
if close:
|
| 1294 |
+
loop.close()
|
| 1295 |
+
|
| 1296 |
+
ctx = self.new_context(dict(*args, **kwargs))
|
| 1297 |
+
|
| 1298 |
+
try:
|
| 1299 |
+
return self.environment.concat(self.root_render_func(ctx)) # type: ignore
|
| 1300 |
+
except Exception:
|
| 1301 |
+
self.environment.handle_exception()
|
| 1302 |
+
|
| 1303 |
+
async def render_async(self, *args: t.Any, **kwargs: t.Any) -> str:
|
| 1304 |
+
"""This works similar to :meth:`render` but returns a coroutine
|
| 1305 |
+
that when awaited returns the entire rendered template string. This
|
| 1306 |
+
requires the async feature to be enabled.
|
| 1307 |
+
|
| 1308 |
+
Example usage::
|
| 1309 |
+
|
| 1310 |
+
await template.render_async(knights='that say nih; asynchronously')
|
| 1311 |
+
"""
|
| 1312 |
+
if not self.environment.is_async:
|
| 1313 |
+
raise RuntimeError(
|
| 1314 |
+
"The environment was not created with async mode enabled."
|
| 1315 |
+
)
|
| 1316 |
+
|
| 1317 |
+
ctx = self.new_context(dict(*args, **kwargs))
|
| 1318 |
+
|
| 1319 |
+
try:
|
| 1320 |
+
return self.environment.concat( # type: ignore
|
| 1321 |
+
[n async for n in self.root_render_func(ctx)] # type: ignore
|
| 1322 |
+
)
|
| 1323 |
+
except Exception:
|
| 1324 |
+
return self.environment.handle_exception()
|
| 1325 |
+
|
| 1326 |
+
def stream(self, *args: t.Any, **kwargs: t.Any) -> "TemplateStream":
|
| 1327 |
+
"""Works exactly like :meth:`generate` but returns a
|
| 1328 |
+
:class:`TemplateStream`.
|
| 1329 |
+
"""
|
| 1330 |
+
return TemplateStream(self.generate(*args, **kwargs))
|
| 1331 |
+
|
| 1332 |
+
def generate(self, *args: t.Any, **kwargs: t.Any) -> t.Iterator[str]:
|
| 1333 |
+
"""For very large templates it can be useful to not render the whole
|
| 1334 |
+
template at once but evaluate each statement after another and yield
|
| 1335 |
+
piece for piece. This method basically does exactly that and returns
|
| 1336 |
+
a generator that yields one item after another as strings.
|
| 1337 |
+
|
| 1338 |
+
It accepts the same arguments as :meth:`render`.
|
| 1339 |
+
"""
|
| 1340 |
+
if self.environment.is_async:
|
| 1341 |
+
import asyncio
|
| 1342 |
+
|
| 1343 |
+
async def to_list() -> t.List[str]:
|
| 1344 |
+
return [x async for x in self.generate_async(*args, **kwargs)]
|
| 1345 |
+
|
| 1346 |
+
yield from asyncio.run(to_list())
|
| 1347 |
+
return
|
| 1348 |
+
|
| 1349 |
+
ctx = self.new_context(dict(*args, **kwargs))
|
| 1350 |
+
|
| 1351 |
+
try:
|
| 1352 |
+
yield from self.root_render_func(ctx)
|
| 1353 |
+
except Exception:
|
| 1354 |
+
yield self.environment.handle_exception()
|
| 1355 |
+
|
| 1356 |
+
async def generate_async(
|
| 1357 |
+
self, *args: t.Any, **kwargs: t.Any
|
| 1358 |
+
) -> t.AsyncIterator[str]:
|
| 1359 |
+
"""An async version of :meth:`generate`. Works very similarly but
|
| 1360 |
+
returns an async iterator instead.
|
| 1361 |
+
"""
|
| 1362 |
+
if not self.environment.is_async:
|
| 1363 |
+
raise RuntimeError(
|
| 1364 |
+
"The environment was not created with async mode enabled."
|
| 1365 |
+
)
|
| 1366 |
+
|
| 1367 |
+
ctx = self.new_context(dict(*args, **kwargs))
|
| 1368 |
+
|
| 1369 |
+
try:
|
| 1370 |
+
async for event in self.root_render_func(ctx): # type: ignore
|
| 1371 |
+
yield event
|
| 1372 |
+
except Exception:
|
| 1373 |
+
yield self.environment.handle_exception()
|
| 1374 |
+
|
| 1375 |
+
def new_context(
|
| 1376 |
+
self,
|
| 1377 |
+
vars: t.Optional[t.Dict[str, t.Any]] = None,
|
| 1378 |
+
shared: bool = False,
|
| 1379 |
+
locals: t.Optional[t.Mapping[str, t.Any]] = None,
|
| 1380 |
+
) -> Context:
|
| 1381 |
+
"""Create a new :class:`Context` for this template. The vars
|
| 1382 |
+
provided will be passed to the template. Per default the globals
|
| 1383 |
+
are added to the context. If shared is set to `True` the data
|
| 1384 |
+
is passed as is to the context without adding the globals.
|
| 1385 |
+
|
| 1386 |
+
`locals` can be a dict of local variables for internal usage.
|
| 1387 |
+
"""
|
| 1388 |
+
return new_context(
|
| 1389 |
+
self.environment, self.name, self.blocks, vars, shared, self.globals, locals
|
| 1390 |
+
)
|
| 1391 |
+
|
| 1392 |
+
def make_module(
|
| 1393 |
+
self,
|
| 1394 |
+
vars: t.Optional[t.Dict[str, t.Any]] = None,
|
| 1395 |
+
shared: bool = False,
|
| 1396 |
+
locals: t.Optional[t.Mapping[str, t.Any]] = None,
|
| 1397 |
+
) -> "TemplateModule":
|
| 1398 |
+
"""This method works like the :attr:`module` attribute when called
|
| 1399 |
+
without arguments but it will evaluate the template on every call
|
| 1400 |
+
rather than caching it. It's also possible to provide
|
| 1401 |
+
a dict which is then used as context. The arguments are the same
|
| 1402 |
+
as for the :meth:`new_context` method.
|
| 1403 |
+
"""
|
| 1404 |
+
ctx = self.new_context(vars, shared, locals)
|
| 1405 |
+
return TemplateModule(self, ctx)
|
| 1406 |
+
|
| 1407 |
+
async def make_module_async(
|
| 1408 |
+
self,
|
| 1409 |
+
vars: t.Optional[t.Dict[str, t.Any]] = None,
|
| 1410 |
+
shared: bool = False,
|
| 1411 |
+
locals: t.Optional[t.Mapping[str, t.Any]] = None,
|
| 1412 |
+
) -> "TemplateModule":
|
| 1413 |
+
"""As template module creation can invoke template code for
|
| 1414 |
+
asynchronous executions this method must be used instead of the
|
| 1415 |
+
normal :meth:`make_module` one. Likewise the module attribute
|
| 1416 |
+
becomes unavailable in async mode.
|
| 1417 |
+
"""
|
| 1418 |
+
ctx = self.new_context(vars, shared, locals)
|
| 1419 |
+
return TemplateModule(
|
| 1420 |
+
self, ctx, [x async for x in self.root_render_func(ctx)] # type: ignore
|
| 1421 |
+
)
|
| 1422 |
+
|
| 1423 |
+
@internalcode
|
| 1424 |
+
def _get_default_module(self, ctx: t.Optional[Context] = None) -> "TemplateModule":
|
| 1425 |
+
"""If a context is passed in, this means that the template was
|
| 1426 |
+
imported. Imported templates have access to the current
|
| 1427 |
+
template's globals by default, but they can only be accessed via
|
| 1428 |
+
the context during runtime.
|
| 1429 |
+
|
| 1430 |
+
If there are new globals, we need to create a new module because
|
| 1431 |
+
the cached module is already rendered and will not have access
|
| 1432 |
+
to globals from the current context. This new module is not
|
| 1433 |
+
cached because the template can be imported elsewhere, and it
|
| 1434 |
+
should have access to only the current template's globals.
|
| 1435 |
+
"""
|
| 1436 |
+
if self.environment.is_async:
|
| 1437 |
+
raise RuntimeError("Module is not available in async mode.")
|
| 1438 |
+
|
| 1439 |
+
if ctx is not None:
|
| 1440 |
+
keys = ctx.globals_keys - self.globals.keys()
|
| 1441 |
+
|
| 1442 |
+
if keys:
|
| 1443 |
+
return self.make_module({k: ctx.parent[k] for k in keys})
|
| 1444 |
+
|
| 1445 |
+
if self._module is None:
|
| 1446 |
+
self._module = self.make_module()
|
| 1447 |
+
|
| 1448 |
+
return self._module
|
| 1449 |
+
|
| 1450 |
+
async def _get_default_module_async(
|
| 1451 |
+
self, ctx: t.Optional[Context] = None
|
| 1452 |
+
) -> "TemplateModule":
|
| 1453 |
+
if ctx is not None:
|
| 1454 |
+
keys = ctx.globals_keys - self.globals.keys()
|
| 1455 |
+
|
| 1456 |
+
if keys:
|
| 1457 |
+
return await self.make_module_async({k: ctx.parent[k] for k in keys})
|
| 1458 |
+
|
| 1459 |
+
if self._module is None:
|
| 1460 |
+
self._module = await self.make_module_async()
|
| 1461 |
+
|
| 1462 |
+
return self._module
|
| 1463 |
+
|
| 1464 |
+
@property
|
| 1465 |
+
def module(self) -> "TemplateModule":
|
| 1466 |
+
"""The template as module. This is used for imports in the
|
| 1467 |
+
template runtime but is also useful if one wants to access
|
| 1468 |
+
exported template variables from the Python layer:
|
| 1469 |
+
|
| 1470 |
+
>>> t = Template('{% macro foo() %}42{% endmacro %}23')
|
| 1471 |
+
>>> str(t.module)
|
| 1472 |
+
'23'
|
| 1473 |
+
>>> t.module.foo() == u'42'
|
| 1474 |
+
True
|
| 1475 |
+
|
| 1476 |
+
This attribute is not available if async mode is enabled.
|
| 1477 |
+
"""
|
| 1478 |
+
return self._get_default_module()
|
| 1479 |
+
|
| 1480 |
+
def get_corresponding_lineno(self, lineno: int) -> int:
|
| 1481 |
+
"""Return the source line number of a line number in the
|
| 1482 |
+
generated bytecode as they are not in sync.
|
| 1483 |
+
"""
|
| 1484 |
+
for template_line, code_line in reversed(self.debug_info):
|
| 1485 |
+
if code_line <= lineno:
|
| 1486 |
+
return template_line
|
| 1487 |
+
return 1
|
| 1488 |
+
|
| 1489 |
+
@property
|
| 1490 |
+
def is_up_to_date(self) -> bool:
|
| 1491 |
+
"""If this variable is `False` there is a newer version available."""
|
| 1492 |
+
if self._uptodate is None:
|
| 1493 |
+
return True
|
| 1494 |
+
return self._uptodate()
|
| 1495 |
+
|
| 1496 |
+
@property
|
| 1497 |
+
def debug_info(self) -> t.List[t.Tuple[int, int]]:
|
| 1498 |
+
"""The debug info mapping."""
|
| 1499 |
+
if self._debug_info:
|
| 1500 |
+
return [
|
| 1501 |
+
tuple(map(int, x.split("="))) # type: ignore
|
| 1502 |
+
for x in self._debug_info.split("&")
|
| 1503 |
+
]
|
| 1504 |
+
|
| 1505 |
+
return []
|
| 1506 |
+
|
| 1507 |
+
def __repr__(self) -> str:
|
| 1508 |
+
if self.name is None:
|
| 1509 |
+
name = f"memory:{id(self):x}"
|
| 1510 |
+
else:
|
| 1511 |
+
name = repr(self.name)
|
| 1512 |
+
return f"<{type(self).__name__} {name}>"
|
| 1513 |
+
|
| 1514 |
+
|
| 1515 |
+
class TemplateModule:
|
| 1516 |
+
"""Represents an imported template. All the exported names of the
|
| 1517 |
+
template are available as attributes on this object. Additionally
|
| 1518 |
+
converting it into a string renders the contents.
|
| 1519 |
+
"""
|
| 1520 |
+
|
| 1521 |
+
def __init__(
|
| 1522 |
+
self,
|
| 1523 |
+
template: Template,
|
| 1524 |
+
context: Context,
|
| 1525 |
+
body_stream: t.Optional[t.Iterable[str]] = None,
|
| 1526 |
+
) -> None:
|
| 1527 |
+
if body_stream is None:
|
| 1528 |
+
if context.environment.is_async:
|
| 1529 |
+
raise RuntimeError(
|
| 1530 |
+
"Async mode requires a body stream to be passed to"
|
| 1531 |
+
" a template module. Use the async methods of the"
|
| 1532 |
+
" API you are using."
|
| 1533 |
+
)
|
| 1534 |
+
|
| 1535 |
+
body_stream = list(template.root_render_func(context))
|
| 1536 |
+
|
| 1537 |
+
self._body_stream = body_stream
|
| 1538 |
+
self.__dict__.update(context.get_exported())
|
| 1539 |
+
self.__name__ = template.name
|
| 1540 |
+
|
| 1541 |
+
def __html__(self) -> Markup:
|
| 1542 |
+
return Markup(concat(self._body_stream))
|
| 1543 |
+
|
| 1544 |
+
def __str__(self) -> str:
|
| 1545 |
+
return concat(self._body_stream)
|
| 1546 |
+
|
| 1547 |
+
def __repr__(self) -> str:
|
| 1548 |
+
if self.__name__ is None:
|
| 1549 |
+
name = f"memory:{id(self):x}"
|
| 1550 |
+
else:
|
| 1551 |
+
name = repr(self.__name__)
|
| 1552 |
+
return f"<{type(self).__name__} {name}>"
|
| 1553 |
+
|
| 1554 |
+
|
| 1555 |
+
class TemplateExpression:
|
| 1556 |
+
"""The :meth:`jinja2.Environment.compile_expression` method returns an
|
| 1557 |
+
instance of this object. It encapsulates the expression-like access
|
| 1558 |
+
to the template with an expression it wraps.
|
| 1559 |
+
"""
|
| 1560 |
+
|
| 1561 |
+
def __init__(self, template: Template, undefined_to_none: bool) -> None:
|
| 1562 |
+
self._template = template
|
| 1563 |
+
self._undefined_to_none = undefined_to_none
|
| 1564 |
+
|
| 1565 |
+
def __call__(self, *args: t.Any, **kwargs: t.Any) -> t.Optional[t.Any]:
|
| 1566 |
+
context = self._template.new_context(dict(*args, **kwargs))
|
| 1567 |
+
consume(self._template.root_render_func(context))
|
| 1568 |
+
rv = context.vars["result"]
|
| 1569 |
+
if self._undefined_to_none and isinstance(rv, Undefined):
|
| 1570 |
+
rv = None
|
| 1571 |
+
return rv
|
| 1572 |
+
|
| 1573 |
+
|
| 1574 |
+
class TemplateStream:
|
| 1575 |
+
"""A template stream works pretty much like an ordinary python generator
|
| 1576 |
+
but it can buffer multiple items to reduce the number of total iterations.
|
| 1577 |
+
Per default the output is unbuffered which means that for every unbuffered
|
| 1578 |
+
instruction in the template one string is yielded.
|
| 1579 |
+
|
| 1580 |
+
If buffering is enabled with a buffer size of 5, five items are combined
|
| 1581 |
+
into a new string. This is mainly useful if you are streaming
|
| 1582 |
+
big templates to a client via WSGI which flushes after each iteration.
|
| 1583 |
+
"""
|
| 1584 |
+
|
| 1585 |
+
def __init__(self, gen: t.Iterator[str]) -> None:
|
| 1586 |
+
self._gen = gen
|
| 1587 |
+
self.disable_buffering()
|
| 1588 |
+
|
| 1589 |
+
def dump(
|
| 1590 |
+
self,
|
| 1591 |
+
fp: t.Union[str, t.IO],
|
| 1592 |
+
encoding: t.Optional[str] = None,
|
| 1593 |
+
errors: t.Optional[str] = "strict",
|
| 1594 |
+
) -> None:
|
| 1595 |
+
"""Dump the complete stream into a file or file-like object.
|
| 1596 |
+
Per default strings are written, if you want to encode
|
| 1597 |
+
before writing specify an `encoding`.
|
| 1598 |
+
|
| 1599 |
+
Example usage::
|
| 1600 |
+
|
| 1601 |
+
Template('Hello {{ name }}!').stream(name='foo').dump('hello.html')
|
| 1602 |
+
"""
|
| 1603 |
+
close = False
|
| 1604 |
+
|
| 1605 |
+
if isinstance(fp, str):
|
| 1606 |
+
if encoding is None:
|
| 1607 |
+
encoding = "utf-8"
|
| 1608 |
+
|
| 1609 |
+
fp = open(fp, "wb")
|
| 1610 |
+
close = True
|
| 1611 |
+
try:
|
| 1612 |
+
if encoding is not None:
|
| 1613 |
+
iterable = (x.encode(encoding, errors) for x in self) # type: ignore
|
| 1614 |
+
else:
|
| 1615 |
+
iterable = self # type: ignore
|
| 1616 |
+
|
| 1617 |
+
if hasattr(fp, "writelines"):
|
| 1618 |
+
fp.writelines(iterable)
|
| 1619 |
+
else:
|
| 1620 |
+
for item in iterable:
|
| 1621 |
+
fp.write(item)
|
| 1622 |
+
finally:
|
| 1623 |
+
if close:
|
| 1624 |
+
fp.close()
|
| 1625 |
+
|
| 1626 |
+
def disable_buffering(self) -> None:
|
| 1627 |
+
"""Disable the output buffering."""
|
| 1628 |
+
self._next = partial(next, self._gen)
|
| 1629 |
+
self.buffered = False
|
| 1630 |
+
|
| 1631 |
+
def _buffered_generator(self, size: int) -> t.Iterator[str]:
|
| 1632 |
+
buf: t.List[str] = []
|
| 1633 |
+
c_size = 0
|
| 1634 |
+
push = buf.append
|
| 1635 |
+
|
| 1636 |
+
while True:
|
| 1637 |
+
try:
|
| 1638 |
+
while c_size < size:
|
| 1639 |
+
c = next(self._gen)
|
| 1640 |
+
push(c)
|
| 1641 |
+
if c:
|
| 1642 |
+
c_size += 1
|
| 1643 |
+
except StopIteration:
|
| 1644 |
+
if not c_size:
|
| 1645 |
+
return
|
| 1646 |
+
yield concat(buf)
|
| 1647 |
+
del buf[:]
|
| 1648 |
+
c_size = 0
|
| 1649 |
+
|
| 1650 |
+
def enable_buffering(self, size: int = 5) -> None:
|
| 1651 |
+
"""Enable buffering. Buffer `size` items before yielding them."""
|
| 1652 |
+
if size <= 1:
|
| 1653 |
+
raise ValueError("buffer size too small")
|
| 1654 |
+
|
| 1655 |
+
self.buffered = True
|
| 1656 |
+
self._next = partial(next, self._buffered_generator(size))
|
| 1657 |
+
|
| 1658 |
+
def __iter__(self) -> "TemplateStream":
|
| 1659 |
+
return self
|
| 1660 |
+
|
| 1661 |
+
def __next__(self) -> str:
|
| 1662 |
+
return self._next() # type: ignore
|
| 1663 |
+
|
| 1664 |
+
|
| 1665 |
+
# hook in default template class. if anyone reads this comment: ignore that
|
| 1666 |
+
# it's possible to use custom templates ;-)
|
| 1667 |
+
Environment.template_class = Template
|
tuning-competition-baseline/.venv/lib/python3.11/site-packages/jinja2/loaders.py
ADDED
|
@@ -0,0 +1,661 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""API and implementations for loading templates from different data
|
| 2 |
+
sources.
|
| 3 |
+
"""
|
| 4 |
+
import importlib.util
|
| 5 |
+
import os
|
| 6 |
+
import posixpath
|
| 7 |
+
import sys
|
| 8 |
+
import typing as t
|
| 9 |
+
import weakref
|
| 10 |
+
import zipimport
|
| 11 |
+
from collections import abc
|
| 12 |
+
from hashlib import sha1
|
| 13 |
+
from importlib import import_module
|
| 14 |
+
from types import ModuleType
|
| 15 |
+
|
| 16 |
+
from .exceptions import TemplateNotFound
|
| 17 |
+
from .utils import internalcode
|
| 18 |
+
|
| 19 |
+
if t.TYPE_CHECKING:
|
| 20 |
+
from .environment import Environment
|
| 21 |
+
from .environment import Template
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
def split_template_path(template: str) -> t.List[str]:
|
| 25 |
+
"""Split a path into segments and perform a sanity check. If it detects
|
| 26 |
+
'..' in the path it will raise a `TemplateNotFound` error.
|
| 27 |
+
"""
|
| 28 |
+
pieces = []
|
| 29 |
+
for piece in template.split("/"):
|
| 30 |
+
if (
|
| 31 |
+
os.path.sep in piece
|
| 32 |
+
or (os.path.altsep and os.path.altsep in piece)
|
| 33 |
+
or piece == os.path.pardir
|
| 34 |
+
):
|
| 35 |
+
raise TemplateNotFound(template)
|
| 36 |
+
elif piece and piece != ".":
|
| 37 |
+
pieces.append(piece)
|
| 38 |
+
return pieces
|
| 39 |
+
|
| 40 |
+
|
| 41 |
+
class BaseLoader:
|
| 42 |
+
"""Baseclass for all loaders. Subclass this and override `get_source` to
|
| 43 |
+
implement a custom loading mechanism. The environment provides a
|
| 44 |
+
`get_template` method that calls the loader's `load` method to get the
|
| 45 |
+
:class:`Template` object.
|
| 46 |
+
|
| 47 |
+
A very basic example for a loader that looks up templates on the file
|
| 48 |
+
system could look like this::
|
| 49 |
+
|
| 50 |
+
from jinja2 import BaseLoader, TemplateNotFound
|
| 51 |
+
from os.path import join, exists, getmtime
|
| 52 |
+
|
| 53 |
+
class MyLoader(BaseLoader):
|
| 54 |
+
|
| 55 |
+
def __init__(self, path):
|
| 56 |
+
self.path = path
|
| 57 |
+
|
| 58 |
+
def get_source(self, environment, template):
|
| 59 |
+
path = join(self.path, template)
|
| 60 |
+
if not exists(path):
|
| 61 |
+
raise TemplateNotFound(template)
|
| 62 |
+
mtime = getmtime(path)
|
| 63 |
+
with open(path) as f:
|
| 64 |
+
source = f.read()
|
| 65 |
+
return source, path, lambda: mtime == getmtime(path)
|
| 66 |
+
"""
|
| 67 |
+
|
| 68 |
+
#: if set to `False` it indicates that the loader cannot provide access
|
| 69 |
+
#: to the source of templates.
|
| 70 |
+
#:
|
| 71 |
+
#: .. versionadded:: 2.4
|
| 72 |
+
has_source_access = True
|
| 73 |
+
|
| 74 |
+
def get_source(
|
| 75 |
+
self, environment: "Environment", template: str
|
| 76 |
+
) -> t.Tuple[str, t.Optional[str], t.Optional[t.Callable[[], bool]]]:
|
| 77 |
+
"""Get the template source, filename and reload helper for a template.
|
| 78 |
+
It's passed the environment and template name and has to return a
|
| 79 |
+
tuple in the form ``(source, filename, uptodate)`` or raise a
|
| 80 |
+
`TemplateNotFound` error if it can't locate the template.
|
| 81 |
+
|
| 82 |
+
The source part of the returned tuple must be the source of the
|
| 83 |
+
template as a string. The filename should be the name of the
|
| 84 |
+
file on the filesystem if it was loaded from there, otherwise
|
| 85 |
+
``None``. The filename is used by Python for the tracebacks
|
| 86 |
+
if no loader extension is used.
|
| 87 |
+
|
| 88 |
+
The last item in the tuple is the `uptodate` function. If auto
|
| 89 |
+
reloading is enabled it's always called to check if the template
|
| 90 |
+
changed. No arguments are passed so the function must store the
|
| 91 |
+
old state somewhere (for example in a closure). If it returns `False`
|
| 92 |
+
the template will be reloaded.
|
| 93 |
+
"""
|
| 94 |
+
if not self.has_source_access:
|
| 95 |
+
raise RuntimeError(
|
| 96 |
+
f"{type(self).__name__} cannot provide access to the source"
|
| 97 |
+
)
|
| 98 |
+
raise TemplateNotFound(template)
|
| 99 |
+
|
| 100 |
+
def list_templates(self) -> t.List[str]:
|
| 101 |
+
"""Iterates over all templates. If the loader does not support that
|
| 102 |
+
it should raise a :exc:`TypeError` which is the default behavior.
|
| 103 |
+
"""
|
| 104 |
+
raise TypeError("this loader cannot iterate over all templates")
|
| 105 |
+
|
| 106 |
+
@internalcode
|
| 107 |
+
def load(
|
| 108 |
+
self,
|
| 109 |
+
environment: "Environment",
|
| 110 |
+
name: str,
|
| 111 |
+
globals: t.Optional[t.MutableMapping[str, t.Any]] = None,
|
| 112 |
+
) -> "Template":
|
| 113 |
+
"""Loads a template. This method looks up the template in the cache
|
| 114 |
+
or loads one by calling :meth:`get_source`. Subclasses should not
|
| 115 |
+
override this method as loaders working on collections of other
|
| 116 |
+
loaders (such as :class:`PrefixLoader` or :class:`ChoiceLoader`)
|
| 117 |
+
will not call this method but `get_source` directly.
|
| 118 |
+
"""
|
| 119 |
+
code = None
|
| 120 |
+
if globals is None:
|
| 121 |
+
globals = {}
|
| 122 |
+
|
| 123 |
+
# first we try to get the source for this template together
|
| 124 |
+
# with the filename and the uptodate function.
|
| 125 |
+
source, filename, uptodate = self.get_source(environment, name)
|
| 126 |
+
|
| 127 |
+
# try to load the code from the bytecode cache if there is a
|
| 128 |
+
# bytecode cache configured.
|
| 129 |
+
bcc = environment.bytecode_cache
|
| 130 |
+
if bcc is not None:
|
| 131 |
+
bucket = bcc.get_bucket(environment, name, filename, source)
|
| 132 |
+
code = bucket.code
|
| 133 |
+
|
| 134 |
+
# if we don't have code so far (not cached, no longer up to
|
| 135 |
+
# date) etc. we compile the template
|
| 136 |
+
if code is None:
|
| 137 |
+
code = environment.compile(source, name, filename)
|
| 138 |
+
|
| 139 |
+
# if the bytecode cache is available and the bucket doesn't
|
| 140 |
+
# have a code so far, we give the bucket the new code and put
|
| 141 |
+
# it back to the bytecode cache.
|
| 142 |
+
if bcc is not None and bucket.code is None:
|
| 143 |
+
bucket.code = code
|
| 144 |
+
bcc.set_bucket(bucket)
|
| 145 |
+
|
| 146 |
+
return environment.template_class.from_code(
|
| 147 |
+
environment, code, globals, uptodate
|
| 148 |
+
)
|
| 149 |
+
|
| 150 |
+
|
| 151 |
+
class FileSystemLoader(BaseLoader):
|
| 152 |
+
"""Load templates from a directory in the file system.
|
| 153 |
+
|
| 154 |
+
The path can be relative or absolute. Relative paths are relative to
|
| 155 |
+
the current working directory.
|
| 156 |
+
|
| 157 |
+
.. code-block:: python
|
| 158 |
+
|
| 159 |
+
loader = FileSystemLoader("templates")
|
| 160 |
+
|
| 161 |
+
A list of paths can be given. The directories will be searched in
|
| 162 |
+
order, stopping at the first matching template.
|
| 163 |
+
|
| 164 |
+
.. code-block:: python
|
| 165 |
+
|
| 166 |
+
loader = FileSystemLoader(["/override/templates", "/default/templates"])
|
| 167 |
+
|
| 168 |
+
:param searchpath: A path, or list of paths, to the directory that
|
| 169 |
+
contains the templates.
|
| 170 |
+
:param encoding: Use this encoding to read the text from template
|
| 171 |
+
files.
|
| 172 |
+
:param followlinks: Follow symbolic links in the path.
|
| 173 |
+
|
| 174 |
+
.. versionchanged:: 2.8
|
| 175 |
+
Added the ``followlinks`` parameter.
|
| 176 |
+
"""
|
| 177 |
+
|
| 178 |
+
def __init__(
|
| 179 |
+
self,
|
| 180 |
+
searchpath: t.Union[str, os.PathLike, t.Sequence[t.Union[str, os.PathLike]]],
|
| 181 |
+
encoding: str = "utf-8",
|
| 182 |
+
followlinks: bool = False,
|
| 183 |
+
) -> None:
|
| 184 |
+
if not isinstance(searchpath, abc.Iterable) or isinstance(searchpath, str):
|
| 185 |
+
searchpath = [searchpath]
|
| 186 |
+
|
| 187 |
+
self.searchpath = [os.fspath(p) for p in searchpath]
|
| 188 |
+
self.encoding = encoding
|
| 189 |
+
self.followlinks = followlinks
|
| 190 |
+
|
| 191 |
+
def get_source(
|
| 192 |
+
self, environment: "Environment", template: str
|
| 193 |
+
) -> t.Tuple[str, str, t.Callable[[], bool]]:
|
| 194 |
+
pieces = split_template_path(template)
|
| 195 |
+
|
| 196 |
+
for searchpath in self.searchpath:
|
| 197 |
+
# Use posixpath even on Windows to avoid "drive:" or UNC
|
| 198 |
+
# segments breaking out of the search directory.
|
| 199 |
+
filename = posixpath.join(searchpath, *pieces)
|
| 200 |
+
|
| 201 |
+
if os.path.isfile(filename):
|
| 202 |
+
break
|
| 203 |
+
else:
|
| 204 |
+
raise TemplateNotFound(template)
|
| 205 |
+
|
| 206 |
+
with open(filename, encoding=self.encoding) as f:
|
| 207 |
+
contents = f.read()
|
| 208 |
+
|
| 209 |
+
mtime = os.path.getmtime(filename)
|
| 210 |
+
|
| 211 |
+
def uptodate() -> bool:
|
| 212 |
+
try:
|
| 213 |
+
return os.path.getmtime(filename) == mtime
|
| 214 |
+
except OSError:
|
| 215 |
+
return False
|
| 216 |
+
|
| 217 |
+
# Use normpath to convert Windows altsep to sep.
|
| 218 |
+
return contents, os.path.normpath(filename), uptodate
|
| 219 |
+
|
| 220 |
+
def list_templates(self) -> t.List[str]:
|
| 221 |
+
found = set()
|
| 222 |
+
for searchpath in self.searchpath:
|
| 223 |
+
walk_dir = os.walk(searchpath, followlinks=self.followlinks)
|
| 224 |
+
for dirpath, _, filenames in walk_dir:
|
| 225 |
+
for filename in filenames:
|
| 226 |
+
template = (
|
| 227 |
+
os.path.join(dirpath, filename)[len(searchpath) :]
|
| 228 |
+
.strip(os.path.sep)
|
| 229 |
+
.replace(os.path.sep, "/")
|
| 230 |
+
)
|
| 231 |
+
if template[:2] == "./":
|
| 232 |
+
template = template[2:]
|
| 233 |
+
if template not in found:
|
| 234 |
+
found.add(template)
|
| 235 |
+
return sorted(found)
|
| 236 |
+
|
| 237 |
+
|
| 238 |
+
class PackageLoader(BaseLoader):
|
| 239 |
+
"""Load templates from a directory in a Python package.
|
| 240 |
+
|
| 241 |
+
:param package_name: Import name of the package that contains the
|
| 242 |
+
template directory.
|
| 243 |
+
:param package_path: Directory within the imported package that
|
| 244 |
+
contains the templates.
|
| 245 |
+
:param encoding: Encoding of template files.
|
| 246 |
+
|
| 247 |
+
The following example looks up templates in the ``pages`` directory
|
| 248 |
+
within the ``project.ui`` package.
|
| 249 |
+
|
| 250 |
+
.. code-block:: python
|
| 251 |
+
|
| 252 |
+
loader = PackageLoader("project.ui", "pages")
|
| 253 |
+
|
| 254 |
+
Only packages installed as directories (standard pip behavior) or
|
| 255 |
+
zip/egg files (less common) are supported. The Python API for
|
| 256 |
+
introspecting data in packages is too limited to support other
|
| 257 |
+
installation methods the way this loader requires.
|
| 258 |
+
|
| 259 |
+
There is limited support for :pep:`420` namespace packages. The
|
| 260 |
+
template directory is assumed to only be in one namespace
|
| 261 |
+
contributor. Zip files contributing to a namespace are not
|
| 262 |
+
supported.
|
| 263 |
+
|
| 264 |
+
.. versionchanged:: 3.0
|
| 265 |
+
No longer uses ``setuptools`` as a dependency.
|
| 266 |
+
|
| 267 |
+
.. versionchanged:: 3.0
|
| 268 |
+
Limited PEP 420 namespace package support.
|
| 269 |
+
"""
|
| 270 |
+
|
| 271 |
+
def __init__(
|
| 272 |
+
self,
|
| 273 |
+
package_name: str,
|
| 274 |
+
package_path: "str" = "templates",
|
| 275 |
+
encoding: str = "utf-8",
|
| 276 |
+
) -> None:
|
| 277 |
+
package_path = os.path.normpath(package_path).rstrip(os.path.sep)
|
| 278 |
+
|
| 279 |
+
# normpath preserves ".", which isn't valid in zip paths.
|
| 280 |
+
if package_path == os.path.curdir:
|
| 281 |
+
package_path = ""
|
| 282 |
+
elif package_path[:2] == os.path.curdir + os.path.sep:
|
| 283 |
+
package_path = package_path[2:]
|
| 284 |
+
|
| 285 |
+
self.package_path = package_path
|
| 286 |
+
self.package_name = package_name
|
| 287 |
+
self.encoding = encoding
|
| 288 |
+
|
| 289 |
+
# Make sure the package exists. This also makes namespace
|
| 290 |
+
# packages work, otherwise get_loader returns None.
|
| 291 |
+
import_module(package_name)
|
| 292 |
+
spec = importlib.util.find_spec(package_name)
|
| 293 |
+
assert spec is not None, "An import spec was not found for the package."
|
| 294 |
+
loader = spec.loader
|
| 295 |
+
assert loader is not None, "A loader was not found for the package."
|
| 296 |
+
self._loader = loader
|
| 297 |
+
self._archive = None
|
| 298 |
+
template_root = None
|
| 299 |
+
|
| 300 |
+
if isinstance(loader, zipimport.zipimporter):
|
| 301 |
+
self._archive = loader.archive
|
| 302 |
+
pkgdir = next(iter(spec.submodule_search_locations)) # type: ignore
|
| 303 |
+
template_root = os.path.join(pkgdir, package_path).rstrip(os.path.sep)
|
| 304 |
+
else:
|
| 305 |
+
roots: t.List[str] = []
|
| 306 |
+
|
| 307 |
+
# One element for regular packages, multiple for namespace
|
| 308 |
+
# packages, or None for single module file.
|
| 309 |
+
if spec.submodule_search_locations:
|
| 310 |
+
roots.extend(spec.submodule_search_locations)
|
| 311 |
+
# A single module file, use the parent directory instead.
|
| 312 |
+
elif spec.origin is not None:
|
| 313 |
+
roots.append(os.path.dirname(spec.origin))
|
| 314 |
+
|
| 315 |
+
for root in roots:
|
| 316 |
+
root = os.path.join(root, package_path)
|
| 317 |
+
|
| 318 |
+
if os.path.isdir(root):
|
| 319 |
+
template_root = root
|
| 320 |
+
break
|
| 321 |
+
|
| 322 |
+
if template_root is None:
|
| 323 |
+
raise ValueError(
|
| 324 |
+
f"The {package_name!r} package was not installed in a"
|
| 325 |
+
" way that PackageLoader understands."
|
| 326 |
+
)
|
| 327 |
+
|
| 328 |
+
self._template_root = template_root
|
| 329 |
+
|
| 330 |
+
def get_source(
|
| 331 |
+
self, environment: "Environment", template: str
|
| 332 |
+
) -> t.Tuple[str, str, t.Optional[t.Callable[[], bool]]]:
|
| 333 |
+
# Use posixpath even on Windows to avoid "drive:" or UNC
|
| 334 |
+
# segments breaking out of the search directory. Use normpath to
|
| 335 |
+
# convert Windows altsep to sep.
|
| 336 |
+
p = os.path.normpath(
|
| 337 |
+
posixpath.join(self._template_root, *split_template_path(template))
|
| 338 |
+
)
|
| 339 |
+
up_to_date: t.Optional[t.Callable[[], bool]]
|
| 340 |
+
|
| 341 |
+
if self._archive is None:
|
| 342 |
+
# Package is a directory.
|
| 343 |
+
if not os.path.isfile(p):
|
| 344 |
+
raise TemplateNotFound(template)
|
| 345 |
+
|
| 346 |
+
with open(p, "rb") as f:
|
| 347 |
+
source = f.read()
|
| 348 |
+
|
| 349 |
+
mtime = os.path.getmtime(p)
|
| 350 |
+
|
| 351 |
+
def up_to_date() -> bool:
|
| 352 |
+
return os.path.isfile(p) and os.path.getmtime(p) == mtime
|
| 353 |
+
|
| 354 |
+
else:
|
| 355 |
+
# Package is a zip file.
|
| 356 |
+
try:
|
| 357 |
+
source = self._loader.get_data(p) # type: ignore
|
| 358 |
+
except OSError as e:
|
| 359 |
+
raise TemplateNotFound(template) from e
|
| 360 |
+
|
| 361 |
+
# Could use the zip's mtime for all template mtimes, but
|
| 362 |
+
# would need to safely reload the module if it's out of
|
| 363 |
+
# date, so just report it as always current.
|
| 364 |
+
up_to_date = None
|
| 365 |
+
|
| 366 |
+
return source.decode(self.encoding), p, up_to_date
|
| 367 |
+
|
| 368 |
+
def list_templates(self) -> t.List[str]:
|
| 369 |
+
results: t.List[str] = []
|
| 370 |
+
|
| 371 |
+
if self._archive is None:
|
| 372 |
+
# Package is a directory.
|
| 373 |
+
offset = len(self._template_root)
|
| 374 |
+
|
| 375 |
+
for dirpath, _, filenames in os.walk(self._template_root):
|
| 376 |
+
dirpath = dirpath[offset:].lstrip(os.path.sep)
|
| 377 |
+
results.extend(
|
| 378 |
+
os.path.join(dirpath, name).replace(os.path.sep, "/")
|
| 379 |
+
for name in filenames
|
| 380 |
+
)
|
| 381 |
+
else:
|
| 382 |
+
if not hasattr(self._loader, "_files"):
|
| 383 |
+
raise TypeError(
|
| 384 |
+
"This zip import does not have the required"
|
| 385 |
+
" metadata to list templates."
|
| 386 |
+
)
|
| 387 |
+
|
| 388 |
+
# Package is a zip file.
|
| 389 |
+
prefix = (
|
| 390 |
+
self._template_root[len(self._archive) :].lstrip(os.path.sep)
|
| 391 |
+
+ os.path.sep
|
| 392 |
+
)
|
| 393 |
+
offset = len(prefix)
|
| 394 |
+
|
| 395 |
+
for name in self._loader._files.keys():
|
| 396 |
+
# Find names under the templates directory that aren't directories.
|
| 397 |
+
if name.startswith(prefix) and name[-1] != os.path.sep:
|
| 398 |
+
results.append(name[offset:].replace(os.path.sep, "/"))
|
| 399 |
+
|
| 400 |
+
results.sort()
|
| 401 |
+
return results
|
| 402 |
+
|
| 403 |
+
|
| 404 |
+
class DictLoader(BaseLoader):
|
| 405 |
+
"""Loads a template from a Python dict mapping template names to
|
| 406 |
+
template source. This loader is useful for unittesting:
|
| 407 |
+
|
| 408 |
+
>>> loader = DictLoader({'index.html': 'source here'})
|
| 409 |
+
|
| 410 |
+
Because auto reloading is rarely useful this is disabled per default.
|
| 411 |
+
"""
|
| 412 |
+
|
| 413 |
+
def __init__(self, mapping: t.Mapping[str, str]) -> None:
|
| 414 |
+
self.mapping = mapping
|
| 415 |
+
|
| 416 |
+
def get_source(
|
| 417 |
+
self, environment: "Environment", template: str
|
| 418 |
+
) -> t.Tuple[str, None, t.Callable[[], bool]]:
|
| 419 |
+
if template in self.mapping:
|
| 420 |
+
source = self.mapping[template]
|
| 421 |
+
return source, None, lambda: source == self.mapping.get(template)
|
| 422 |
+
raise TemplateNotFound(template)
|
| 423 |
+
|
| 424 |
+
def list_templates(self) -> t.List[str]:
|
| 425 |
+
return sorted(self.mapping)
|
| 426 |
+
|
| 427 |
+
|
| 428 |
+
class FunctionLoader(BaseLoader):
|
| 429 |
+
"""A loader that is passed a function which does the loading. The
|
| 430 |
+
function receives the name of the template and has to return either
|
| 431 |
+
a string with the template source, a tuple in the form ``(source,
|
| 432 |
+
filename, uptodatefunc)`` or `None` if the template does not exist.
|
| 433 |
+
|
| 434 |
+
>>> def load_template(name):
|
| 435 |
+
... if name == 'index.html':
|
| 436 |
+
... return '...'
|
| 437 |
+
...
|
| 438 |
+
>>> loader = FunctionLoader(load_template)
|
| 439 |
+
|
| 440 |
+
The `uptodatefunc` is a function that is called if autoreload is enabled
|
| 441 |
+
and has to return `True` if the template is still up to date. For more
|
| 442 |
+
details have a look at :meth:`BaseLoader.get_source` which has the same
|
| 443 |
+
return value.
|
| 444 |
+
"""
|
| 445 |
+
|
| 446 |
+
def __init__(
|
| 447 |
+
self,
|
| 448 |
+
load_func: t.Callable[
|
| 449 |
+
[str],
|
| 450 |
+
t.Optional[
|
| 451 |
+
t.Union[
|
| 452 |
+
str, t.Tuple[str, t.Optional[str], t.Optional[t.Callable[[], bool]]]
|
| 453 |
+
]
|
| 454 |
+
],
|
| 455 |
+
],
|
| 456 |
+
) -> None:
|
| 457 |
+
self.load_func = load_func
|
| 458 |
+
|
| 459 |
+
def get_source(
|
| 460 |
+
self, environment: "Environment", template: str
|
| 461 |
+
) -> t.Tuple[str, t.Optional[str], t.Optional[t.Callable[[], bool]]]:
|
| 462 |
+
rv = self.load_func(template)
|
| 463 |
+
|
| 464 |
+
if rv is None:
|
| 465 |
+
raise TemplateNotFound(template)
|
| 466 |
+
|
| 467 |
+
if isinstance(rv, str):
|
| 468 |
+
return rv, None, None
|
| 469 |
+
|
| 470 |
+
return rv
|
| 471 |
+
|
| 472 |
+
|
| 473 |
+
class PrefixLoader(BaseLoader):
|
| 474 |
+
"""A loader that is passed a dict of loaders where each loader is bound
|
| 475 |
+
to a prefix. The prefix is delimited from the template by a slash per
|
| 476 |
+
default, which can be changed by setting the `delimiter` argument to
|
| 477 |
+
something else::
|
| 478 |
+
|
| 479 |
+
loader = PrefixLoader({
|
| 480 |
+
'app1': PackageLoader('mypackage.app1'),
|
| 481 |
+
'app2': PackageLoader('mypackage.app2')
|
| 482 |
+
})
|
| 483 |
+
|
| 484 |
+
By loading ``'app1/index.html'`` the file from the app1 package is loaded,
|
| 485 |
+
by loading ``'app2/index.html'`` the file from the second.
|
| 486 |
+
"""
|
| 487 |
+
|
| 488 |
+
def __init__(
|
| 489 |
+
self, mapping: t.Mapping[str, BaseLoader], delimiter: str = "/"
|
| 490 |
+
) -> None:
|
| 491 |
+
self.mapping = mapping
|
| 492 |
+
self.delimiter = delimiter
|
| 493 |
+
|
| 494 |
+
def get_loader(self, template: str) -> t.Tuple[BaseLoader, str]:
|
| 495 |
+
try:
|
| 496 |
+
prefix, name = template.split(self.delimiter, 1)
|
| 497 |
+
loader = self.mapping[prefix]
|
| 498 |
+
except (ValueError, KeyError) as e:
|
| 499 |
+
raise TemplateNotFound(template) from e
|
| 500 |
+
return loader, name
|
| 501 |
+
|
| 502 |
+
def get_source(
|
| 503 |
+
self, environment: "Environment", template: str
|
| 504 |
+
) -> t.Tuple[str, t.Optional[str], t.Optional[t.Callable[[], bool]]]:
|
| 505 |
+
loader, name = self.get_loader(template)
|
| 506 |
+
try:
|
| 507 |
+
return loader.get_source(environment, name)
|
| 508 |
+
except TemplateNotFound as e:
|
| 509 |
+
# re-raise the exception with the correct filename here.
|
| 510 |
+
# (the one that includes the prefix)
|
| 511 |
+
raise TemplateNotFound(template) from e
|
| 512 |
+
|
| 513 |
+
@internalcode
|
| 514 |
+
def load(
|
| 515 |
+
self,
|
| 516 |
+
environment: "Environment",
|
| 517 |
+
name: str,
|
| 518 |
+
globals: t.Optional[t.MutableMapping[str, t.Any]] = None,
|
| 519 |
+
) -> "Template":
|
| 520 |
+
loader, local_name = self.get_loader(name)
|
| 521 |
+
try:
|
| 522 |
+
return loader.load(environment, local_name, globals)
|
| 523 |
+
except TemplateNotFound as e:
|
| 524 |
+
# re-raise the exception with the correct filename here.
|
| 525 |
+
# (the one that includes the prefix)
|
| 526 |
+
raise TemplateNotFound(name) from e
|
| 527 |
+
|
| 528 |
+
def list_templates(self) -> t.List[str]:
|
| 529 |
+
result = []
|
| 530 |
+
for prefix, loader in self.mapping.items():
|
| 531 |
+
for template in loader.list_templates():
|
| 532 |
+
result.append(prefix + self.delimiter + template)
|
| 533 |
+
return result
|
| 534 |
+
|
| 535 |
+
|
| 536 |
+
class ChoiceLoader(BaseLoader):
|
| 537 |
+
"""This loader works like the `PrefixLoader` just that no prefix is
|
| 538 |
+
specified. If a template could not be found by one loader the next one
|
| 539 |
+
is tried.
|
| 540 |
+
|
| 541 |
+
>>> loader = ChoiceLoader([
|
| 542 |
+
... FileSystemLoader('/path/to/user/templates'),
|
| 543 |
+
... FileSystemLoader('/path/to/system/templates')
|
| 544 |
+
... ])
|
| 545 |
+
|
| 546 |
+
This is useful if you want to allow users to override builtin templates
|
| 547 |
+
from a different location.
|
| 548 |
+
"""
|
| 549 |
+
|
| 550 |
+
def __init__(self, loaders: t.Sequence[BaseLoader]) -> None:
|
| 551 |
+
self.loaders = loaders
|
| 552 |
+
|
| 553 |
+
def get_source(
|
| 554 |
+
self, environment: "Environment", template: str
|
| 555 |
+
) -> t.Tuple[str, t.Optional[str], t.Optional[t.Callable[[], bool]]]:
|
| 556 |
+
for loader in self.loaders:
|
| 557 |
+
try:
|
| 558 |
+
return loader.get_source(environment, template)
|
| 559 |
+
except TemplateNotFound:
|
| 560 |
+
pass
|
| 561 |
+
raise TemplateNotFound(template)
|
| 562 |
+
|
| 563 |
+
@internalcode
|
| 564 |
+
def load(
|
| 565 |
+
self,
|
| 566 |
+
environment: "Environment",
|
| 567 |
+
name: str,
|
| 568 |
+
globals: t.Optional[t.MutableMapping[str, t.Any]] = None,
|
| 569 |
+
) -> "Template":
|
| 570 |
+
for loader in self.loaders:
|
| 571 |
+
try:
|
| 572 |
+
return loader.load(environment, name, globals)
|
| 573 |
+
except TemplateNotFound:
|
| 574 |
+
pass
|
| 575 |
+
raise TemplateNotFound(name)
|
| 576 |
+
|
| 577 |
+
def list_templates(self) -> t.List[str]:
|
| 578 |
+
found = set()
|
| 579 |
+
for loader in self.loaders:
|
| 580 |
+
found.update(loader.list_templates())
|
| 581 |
+
return sorted(found)
|
| 582 |
+
|
| 583 |
+
|
| 584 |
+
class _TemplateModule(ModuleType):
|
| 585 |
+
"""Like a normal module but with support for weak references"""
|
| 586 |
+
|
| 587 |
+
|
| 588 |
+
class ModuleLoader(BaseLoader):
|
| 589 |
+
"""This loader loads templates from precompiled templates.
|
| 590 |
+
|
| 591 |
+
Example usage:
|
| 592 |
+
|
| 593 |
+
>>> loader = ChoiceLoader([
|
| 594 |
+
... ModuleLoader('/path/to/compiled/templates'),
|
| 595 |
+
... FileSystemLoader('/path/to/templates')
|
| 596 |
+
... ])
|
| 597 |
+
|
| 598 |
+
Templates can be precompiled with :meth:`Environment.compile_templates`.
|
| 599 |
+
"""
|
| 600 |
+
|
| 601 |
+
has_source_access = False
|
| 602 |
+
|
| 603 |
+
def __init__(
|
| 604 |
+
self, path: t.Union[str, os.PathLike, t.Sequence[t.Union[str, os.PathLike]]]
|
| 605 |
+
) -> None:
|
| 606 |
+
package_name = f"_jinja2_module_templates_{id(self):x}"
|
| 607 |
+
|
| 608 |
+
# create a fake module that looks for the templates in the
|
| 609 |
+
# path given.
|
| 610 |
+
mod = _TemplateModule(package_name)
|
| 611 |
+
|
| 612 |
+
if not isinstance(path, abc.Iterable) or isinstance(path, str):
|
| 613 |
+
path = [path]
|
| 614 |
+
|
| 615 |
+
mod.__path__ = [os.fspath(p) for p in path]
|
| 616 |
+
|
| 617 |
+
sys.modules[package_name] = weakref.proxy(
|
| 618 |
+
mod, lambda x: sys.modules.pop(package_name, None)
|
| 619 |
+
)
|
| 620 |
+
|
| 621 |
+
# the only strong reference, the sys.modules entry is weak
|
| 622 |
+
# so that the garbage collector can remove it once the
|
| 623 |
+
# loader that created it goes out of business.
|
| 624 |
+
self.module = mod
|
| 625 |
+
self.package_name = package_name
|
| 626 |
+
|
| 627 |
+
@staticmethod
|
| 628 |
+
def get_template_key(name: str) -> str:
|
| 629 |
+
return "tmpl_" + sha1(name.encode("utf-8")).hexdigest()
|
| 630 |
+
|
| 631 |
+
@staticmethod
|
| 632 |
+
def get_module_filename(name: str) -> str:
|
| 633 |
+
return ModuleLoader.get_template_key(name) + ".py"
|
| 634 |
+
|
| 635 |
+
@internalcode
|
| 636 |
+
def load(
|
| 637 |
+
self,
|
| 638 |
+
environment: "Environment",
|
| 639 |
+
name: str,
|
| 640 |
+
globals: t.Optional[t.MutableMapping[str, t.Any]] = None,
|
| 641 |
+
) -> "Template":
|
| 642 |
+
key = self.get_template_key(name)
|
| 643 |
+
module = f"{self.package_name}.{key}"
|
| 644 |
+
mod = getattr(self.module, module, None)
|
| 645 |
+
|
| 646 |
+
if mod is None:
|
| 647 |
+
try:
|
| 648 |
+
mod = __import__(module, None, None, ["root"])
|
| 649 |
+
except ImportError as e:
|
| 650 |
+
raise TemplateNotFound(name) from e
|
| 651 |
+
|
| 652 |
+
# remove the entry from sys.modules, we only want the attribute
|
| 653 |
+
# on the module object we have stored on the loader.
|
| 654 |
+
sys.modules.pop(module, None)
|
| 655 |
+
|
| 656 |
+
if globals is None:
|
| 657 |
+
globals = {}
|
| 658 |
+
|
| 659 |
+
return environment.template_class.from_module_dict(
|
| 660 |
+
environment, mod.__dict__, globals
|
| 661 |
+
)
|
tuning-competition-baseline/.venv/lib/python3.11/site-packages/jinja2/parser.py
ADDED
|
@@ -0,0 +1,1034 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Parse tokens from the lexer into nodes for the compiler."""
|
| 2 |
+
import typing
|
| 3 |
+
import typing as t
|
| 4 |
+
|
| 5 |
+
from . import nodes
|
| 6 |
+
from .exceptions import TemplateAssertionError
|
| 7 |
+
from .exceptions import TemplateSyntaxError
|
| 8 |
+
from .lexer import describe_token
|
| 9 |
+
from .lexer import describe_token_expr
|
| 10 |
+
|
| 11 |
+
if t.TYPE_CHECKING:
|
| 12 |
+
import typing_extensions as te
|
| 13 |
+
from .environment import Environment
|
| 14 |
+
|
| 15 |
+
_ImportInclude = t.TypeVar("_ImportInclude", nodes.Import, nodes.Include)
|
| 16 |
+
_MacroCall = t.TypeVar("_MacroCall", nodes.Macro, nodes.CallBlock)
|
| 17 |
+
|
| 18 |
+
_statement_keywords = frozenset(
|
| 19 |
+
[
|
| 20 |
+
"for",
|
| 21 |
+
"if",
|
| 22 |
+
"block",
|
| 23 |
+
"extends",
|
| 24 |
+
"print",
|
| 25 |
+
"macro",
|
| 26 |
+
"include",
|
| 27 |
+
"from",
|
| 28 |
+
"import",
|
| 29 |
+
"set",
|
| 30 |
+
"with",
|
| 31 |
+
"autoescape",
|
| 32 |
+
]
|
| 33 |
+
)
|
| 34 |
+
_compare_operators = frozenset(["eq", "ne", "lt", "lteq", "gt", "gteq"])
|
| 35 |
+
|
| 36 |
+
_math_nodes: t.Dict[str, t.Type[nodes.Expr]] = {
|
| 37 |
+
"add": nodes.Add,
|
| 38 |
+
"sub": nodes.Sub,
|
| 39 |
+
"mul": nodes.Mul,
|
| 40 |
+
"div": nodes.Div,
|
| 41 |
+
"floordiv": nodes.FloorDiv,
|
| 42 |
+
"mod": nodes.Mod,
|
| 43 |
+
}
|
| 44 |
+
|
| 45 |
+
|
| 46 |
+
class Parser:
|
| 47 |
+
"""This is the central parsing class Jinja uses. It's passed to
|
| 48 |
+
extensions and can be used to parse expressions or statements.
|
| 49 |
+
"""
|
| 50 |
+
|
| 51 |
+
def __init__(
|
| 52 |
+
self,
|
| 53 |
+
environment: "Environment",
|
| 54 |
+
source: str,
|
| 55 |
+
name: t.Optional[str] = None,
|
| 56 |
+
filename: t.Optional[str] = None,
|
| 57 |
+
state: t.Optional[str] = None,
|
| 58 |
+
) -> None:
|
| 59 |
+
self.environment = environment
|
| 60 |
+
self.stream = environment._tokenize(source, name, filename, state)
|
| 61 |
+
self.name = name
|
| 62 |
+
self.filename = filename
|
| 63 |
+
self.closed = False
|
| 64 |
+
self.extensions: t.Dict[
|
| 65 |
+
str, t.Callable[["Parser"], t.Union[nodes.Node, t.List[nodes.Node]]]
|
| 66 |
+
] = {}
|
| 67 |
+
for extension in environment.iter_extensions():
|
| 68 |
+
for tag in extension.tags:
|
| 69 |
+
self.extensions[tag] = extension.parse
|
| 70 |
+
self._last_identifier = 0
|
| 71 |
+
self._tag_stack: t.List[str] = []
|
| 72 |
+
self._end_token_stack: t.List[t.Tuple[str, ...]] = []
|
| 73 |
+
|
| 74 |
+
def fail(
|
| 75 |
+
self,
|
| 76 |
+
msg: str,
|
| 77 |
+
lineno: t.Optional[int] = None,
|
| 78 |
+
exc: t.Type[TemplateSyntaxError] = TemplateSyntaxError,
|
| 79 |
+
) -> "te.NoReturn":
|
| 80 |
+
"""Convenience method that raises `exc` with the message, passed
|
| 81 |
+
line number or last line number as well as the current name and
|
| 82 |
+
filename.
|
| 83 |
+
"""
|
| 84 |
+
if lineno is None:
|
| 85 |
+
lineno = self.stream.current.lineno
|
| 86 |
+
raise exc(msg, lineno, self.name, self.filename)
|
| 87 |
+
|
| 88 |
+
def _fail_ut_eof(
|
| 89 |
+
self,
|
| 90 |
+
name: t.Optional[str],
|
| 91 |
+
end_token_stack: t.List[t.Tuple[str, ...]],
|
| 92 |
+
lineno: t.Optional[int],
|
| 93 |
+
) -> "te.NoReturn":
|
| 94 |
+
expected: t.Set[str] = set()
|
| 95 |
+
for exprs in end_token_stack:
|
| 96 |
+
expected.update(map(describe_token_expr, exprs))
|
| 97 |
+
if end_token_stack:
|
| 98 |
+
currently_looking: t.Optional[str] = " or ".join(
|
| 99 |
+
map(repr, map(describe_token_expr, end_token_stack[-1]))
|
| 100 |
+
)
|
| 101 |
+
else:
|
| 102 |
+
currently_looking = None
|
| 103 |
+
|
| 104 |
+
if name is None:
|
| 105 |
+
message = ["Unexpected end of template."]
|
| 106 |
+
else:
|
| 107 |
+
message = [f"Encountered unknown tag {name!r}."]
|
| 108 |
+
|
| 109 |
+
if currently_looking:
|
| 110 |
+
if name is not None and name in expected:
|
| 111 |
+
message.append(
|
| 112 |
+
"You probably made a nesting mistake. Jinja is expecting this tag,"
|
| 113 |
+
f" but currently looking for {currently_looking}."
|
| 114 |
+
)
|
| 115 |
+
else:
|
| 116 |
+
message.append(
|
| 117 |
+
f"Jinja was looking for the following tags: {currently_looking}."
|
| 118 |
+
)
|
| 119 |
+
|
| 120 |
+
if self._tag_stack:
|
| 121 |
+
message.append(
|
| 122 |
+
"The innermost block that needs to be closed is"
|
| 123 |
+
f" {self._tag_stack[-1]!r}."
|
| 124 |
+
)
|
| 125 |
+
|
| 126 |
+
self.fail(" ".join(message), lineno)
|
| 127 |
+
|
| 128 |
+
def fail_unknown_tag(
|
| 129 |
+
self, name: str, lineno: t.Optional[int] = None
|
| 130 |
+
) -> "te.NoReturn":
|
| 131 |
+
"""Called if the parser encounters an unknown tag. Tries to fail
|
| 132 |
+
with a human readable error message that could help to identify
|
| 133 |
+
the problem.
|
| 134 |
+
"""
|
| 135 |
+
self._fail_ut_eof(name, self._end_token_stack, lineno)
|
| 136 |
+
|
| 137 |
+
def fail_eof(
|
| 138 |
+
self,
|
| 139 |
+
end_tokens: t.Optional[t.Tuple[str, ...]] = None,
|
| 140 |
+
lineno: t.Optional[int] = None,
|
| 141 |
+
) -> "te.NoReturn":
|
| 142 |
+
"""Like fail_unknown_tag but for end of template situations."""
|
| 143 |
+
stack = list(self._end_token_stack)
|
| 144 |
+
if end_tokens is not None:
|
| 145 |
+
stack.append(end_tokens)
|
| 146 |
+
self._fail_ut_eof(None, stack, lineno)
|
| 147 |
+
|
| 148 |
+
def is_tuple_end(
|
| 149 |
+
self, extra_end_rules: t.Optional[t.Tuple[str, ...]] = None
|
| 150 |
+
) -> bool:
|
| 151 |
+
"""Are we at the end of a tuple?"""
|
| 152 |
+
if self.stream.current.type in ("variable_end", "block_end", "rparen"):
|
| 153 |
+
return True
|
| 154 |
+
elif extra_end_rules is not None:
|
| 155 |
+
return self.stream.current.test_any(extra_end_rules) # type: ignore
|
| 156 |
+
return False
|
| 157 |
+
|
| 158 |
+
def free_identifier(self, lineno: t.Optional[int] = None) -> nodes.InternalName:
|
| 159 |
+
"""Return a new free identifier as :class:`~jinja2.nodes.InternalName`."""
|
| 160 |
+
self._last_identifier += 1
|
| 161 |
+
rv = object.__new__(nodes.InternalName)
|
| 162 |
+
nodes.Node.__init__(rv, f"fi{self._last_identifier}", lineno=lineno)
|
| 163 |
+
return rv
|
| 164 |
+
|
| 165 |
+
def parse_statement(self) -> t.Union[nodes.Node, t.List[nodes.Node]]:
|
| 166 |
+
"""Parse a single statement."""
|
| 167 |
+
token = self.stream.current
|
| 168 |
+
if token.type != "name":
|
| 169 |
+
self.fail("tag name expected", token.lineno)
|
| 170 |
+
self._tag_stack.append(token.value)
|
| 171 |
+
pop_tag = True
|
| 172 |
+
try:
|
| 173 |
+
if token.value in _statement_keywords:
|
| 174 |
+
f = getattr(self, f"parse_{self.stream.current.value}")
|
| 175 |
+
return f() # type: ignore
|
| 176 |
+
if token.value == "call":
|
| 177 |
+
return self.parse_call_block()
|
| 178 |
+
if token.value == "filter":
|
| 179 |
+
return self.parse_filter_block()
|
| 180 |
+
ext = self.extensions.get(token.value)
|
| 181 |
+
if ext is not None:
|
| 182 |
+
return ext(self)
|
| 183 |
+
|
| 184 |
+
# did not work out, remove the token we pushed by accident
|
| 185 |
+
# from the stack so that the unknown tag fail function can
|
| 186 |
+
# produce a proper error message.
|
| 187 |
+
self._tag_stack.pop()
|
| 188 |
+
pop_tag = False
|
| 189 |
+
self.fail_unknown_tag(token.value, token.lineno)
|
| 190 |
+
finally:
|
| 191 |
+
if pop_tag:
|
| 192 |
+
self._tag_stack.pop()
|
| 193 |
+
|
| 194 |
+
def parse_statements(
|
| 195 |
+
self, end_tokens: t.Tuple[str, ...], drop_needle: bool = False
|
| 196 |
+
) -> t.List[nodes.Node]:
|
| 197 |
+
"""Parse multiple statements into a list until one of the end tokens
|
| 198 |
+
is reached. This is used to parse the body of statements as it also
|
| 199 |
+
parses template data if appropriate. The parser checks first if the
|
| 200 |
+
current token is a colon and skips it if there is one. Then it checks
|
| 201 |
+
for the block end and parses until if one of the `end_tokens` is
|
| 202 |
+
reached. Per default the active token in the stream at the end of
|
| 203 |
+
the call is the matched end token. If this is not wanted `drop_needle`
|
| 204 |
+
can be set to `True` and the end token is removed.
|
| 205 |
+
"""
|
| 206 |
+
# the first token may be a colon for python compatibility
|
| 207 |
+
self.stream.skip_if("colon")
|
| 208 |
+
|
| 209 |
+
# in the future it would be possible to add whole code sections
|
| 210 |
+
# by adding some sort of end of statement token and parsing those here.
|
| 211 |
+
self.stream.expect("block_end")
|
| 212 |
+
result = self.subparse(end_tokens)
|
| 213 |
+
|
| 214 |
+
# we reached the end of the template too early, the subparser
|
| 215 |
+
# does not check for this, so we do that now
|
| 216 |
+
if self.stream.current.type == "eof":
|
| 217 |
+
self.fail_eof(end_tokens)
|
| 218 |
+
|
| 219 |
+
if drop_needle:
|
| 220 |
+
next(self.stream)
|
| 221 |
+
return result
|
| 222 |
+
|
| 223 |
+
def parse_set(self) -> t.Union[nodes.Assign, nodes.AssignBlock]:
|
| 224 |
+
"""Parse an assign statement."""
|
| 225 |
+
lineno = next(self.stream).lineno
|
| 226 |
+
target = self.parse_assign_target(with_namespace=True)
|
| 227 |
+
if self.stream.skip_if("assign"):
|
| 228 |
+
expr = self.parse_tuple()
|
| 229 |
+
return nodes.Assign(target, expr, lineno=lineno)
|
| 230 |
+
filter_node = self.parse_filter(None)
|
| 231 |
+
body = self.parse_statements(("name:endset",), drop_needle=True)
|
| 232 |
+
return nodes.AssignBlock(target, filter_node, body, lineno=lineno)
|
| 233 |
+
|
| 234 |
+
def parse_for(self) -> nodes.For:
|
| 235 |
+
"""Parse a for loop."""
|
| 236 |
+
lineno = self.stream.expect("name:for").lineno
|
| 237 |
+
target = self.parse_assign_target(extra_end_rules=("name:in",))
|
| 238 |
+
self.stream.expect("name:in")
|
| 239 |
+
iter = self.parse_tuple(
|
| 240 |
+
with_condexpr=False, extra_end_rules=("name:recursive",)
|
| 241 |
+
)
|
| 242 |
+
test = None
|
| 243 |
+
if self.stream.skip_if("name:if"):
|
| 244 |
+
test = self.parse_expression()
|
| 245 |
+
recursive = self.stream.skip_if("name:recursive")
|
| 246 |
+
body = self.parse_statements(("name:endfor", "name:else"))
|
| 247 |
+
if next(self.stream).value == "endfor":
|
| 248 |
+
else_ = []
|
| 249 |
+
else:
|
| 250 |
+
else_ = self.parse_statements(("name:endfor",), drop_needle=True)
|
| 251 |
+
return nodes.For(target, iter, body, else_, test, recursive, lineno=lineno)
|
| 252 |
+
|
| 253 |
+
def parse_if(self) -> nodes.If:
|
| 254 |
+
"""Parse an if construct."""
|
| 255 |
+
node = result = nodes.If(lineno=self.stream.expect("name:if").lineno)
|
| 256 |
+
while True:
|
| 257 |
+
node.test = self.parse_tuple(with_condexpr=False)
|
| 258 |
+
node.body = self.parse_statements(("name:elif", "name:else", "name:endif"))
|
| 259 |
+
node.elif_ = []
|
| 260 |
+
node.else_ = []
|
| 261 |
+
token = next(self.stream)
|
| 262 |
+
if token.test("name:elif"):
|
| 263 |
+
node = nodes.If(lineno=self.stream.current.lineno)
|
| 264 |
+
result.elif_.append(node)
|
| 265 |
+
continue
|
| 266 |
+
elif token.test("name:else"):
|
| 267 |
+
result.else_ = self.parse_statements(("name:endif",), drop_needle=True)
|
| 268 |
+
break
|
| 269 |
+
return result
|
| 270 |
+
|
| 271 |
+
def parse_with(self) -> nodes.With:
|
| 272 |
+
node = nodes.With(lineno=next(self.stream).lineno)
|
| 273 |
+
targets: t.List[nodes.Expr] = []
|
| 274 |
+
values: t.List[nodes.Expr] = []
|
| 275 |
+
while self.stream.current.type != "block_end":
|
| 276 |
+
if targets:
|
| 277 |
+
self.stream.expect("comma")
|
| 278 |
+
target = self.parse_assign_target()
|
| 279 |
+
target.set_ctx("param")
|
| 280 |
+
targets.append(target)
|
| 281 |
+
self.stream.expect("assign")
|
| 282 |
+
values.append(self.parse_expression())
|
| 283 |
+
node.targets = targets
|
| 284 |
+
node.values = values
|
| 285 |
+
node.body = self.parse_statements(("name:endwith",), drop_needle=True)
|
| 286 |
+
return node
|
| 287 |
+
|
| 288 |
+
def parse_autoescape(self) -> nodes.Scope:
|
| 289 |
+
node = nodes.ScopedEvalContextModifier(lineno=next(self.stream).lineno)
|
| 290 |
+
node.options = [nodes.Keyword("autoescape", self.parse_expression())]
|
| 291 |
+
node.body = self.parse_statements(("name:endautoescape",), drop_needle=True)
|
| 292 |
+
return nodes.Scope([node])
|
| 293 |
+
|
| 294 |
+
def parse_block(self) -> nodes.Block:
|
| 295 |
+
node = nodes.Block(lineno=next(self.stream).lineno)
|
| 296 |
+
node.name = self.stream.expect("name").value
|
| 297 |
+
node.scoped = self.stream.skip_if("name:scoped")
|
| 298 |
+
node.required = self.stream.skip_if("name:required")
|
| 299 |
+
|
| 300 |
+
# common problem people encounter when switching from django
|
| 301 |
+
# to jinja. we do not support hyphens in block names, so let's
|
| 302 |
+
# raise a nicer error message in that case.
|
| 303 |
+
if self.stream.current.type == "sub":
|
| 304 |
+
self.fail(
|
| 305 |
+
"Block names in Jinja have to be valid Python identifiers and may not"
|
| 306 |
+
" contain hyphens, use an underscore instead."
|
| 307 |
+
)
|
| 308 |
+
|
| 309 |
+
node.body = self.parse_statements(("name:endblock",), drop_needle=True)
|
| 310 |
+
|
| 311 |
+
# enforce that required blocks only contain whitespace or comments
|
| 312 |
+
# by asserting that the body, if not empty, is just TemplateData nodes
|
| 313 |
+
# with whitespace data
|
| 314 |
+
if node.required:
|
| 315 |
+
for body_node in node.body:
|
| 316 |
+
if not isinstance(body_node, nodes.Output) or any(
|
| 317 |
+
not isinstance(output_node, nodes.TemplateData)
|
| 318 |
+
or not output_node.data.isspace()
|
| 319 |
+
for output_node in body_node.nodes
|
| 320 |
+
):
|
| 321 |
+
self.fail("Required blocks can only contain comments or whitespace")
|
| 322 |
+
|
| 323 |
+
self.stream.skip_if("name:" + node.name)
|
| 324 |
+
return node
|
| 325 |
+
|
| 326 |
+
def parse_extends(self) -> nodes.Extends:
|
| 327 |
+
node = nodes.Extends(lineno=next(self.stream).lineno)
|
| 328 |
+
node.template = self.parse_expression()
|
| 329 |
+
return node
|
| 330 |
+
|
| 331 |
+
def parse_import_context(
|
| 332 |
+
self, node: _ImportInclude, default: bool
|
| 333 |
+
) -> _ImportInclude:
|
| 334 |
+
if self.stream.current.test_any(
|
| 335 |
+
"name:with", "name:without"
|
| 336 |
+
) and self.stream.look().test("name:context"):
|
| 337 |
+
node.with_context = next(self.stream).value == "with"
|
| 338 |
+
self.stream.skip()
|
| 339 |
+
else:
|
| 340 |
+
node.with_context = default
|
| 341 |
+
return node
|
| 342 |
+
|
| 343 |
+
def parse_include(self) -> nodes.Include:
|
| 344 |
+
node = nodes.Include(lineno=next(self.stream).lineno)
|
| 345 |
+
node.template = self.parse_expression()
|
| 346 |
+
if self.stream.current.test("name:ignore") and self.stream.look().test(
|
| 347 |
+
"name:missing"
|
| 348 |
+
):
|
| 349 |
+
node.ignore_missing = True
|
| 350 |
+
self.stream.skip(2)
|
| 351 |
+
else:
|
| 352 |
+
node.ignore_missing = False
|
| 353 |
+
return self.parse_import_context(node, True)
|
| 354 |
+
|
| 355 |
+
def parse_import(self) -> nodes.Import:
|
| 356 |
+
node = nodes.Import(lineno=next(self.stream).lineno)
|
| 357 |
+
node.template = self.parse_expression()
|
| 358 |
+
self.stream.expect("name:as")
|
| 359 |
+
node.target = self.parse_assign_target(name_only=True).name
|
| 360 |
+
return self.parse_import_context(node, False)
|
| 361 |
+
|
| 362 |
+
def parse_from(self) -> nodes.FromImport:
|
| 363 |
+
node = nodes.FromImport(lineno=next(self.stream).lineno)
|
| 364 |
+
node.template = self.parse_expression()
|
| 365 |
+
self.stream.expect("name:import")
|
| 366 |
+
node.names = []
|
| 367 |
+
|
| 368 |
+
def parse_context() -> bool:
|
| 369 |
+
if self.stream.current.value in {
|
| 370 |
+
"with",
|
| 371 |
+
"without",
|
| 372 |
+
} and self.stream.look().test("name:context"):
|
| 373 |
+
node.with_context = next(self.stream).value == "with"
|
| 374 |
+
self.stream.skip()
|
| 375 |
+
return True
|
| 376 |
+
return False
|
| 377 |
+
|
| 378 |
+
while True:
|
| 379 |
+
if node.names:
|
| 380 |
+
self.stream.expect("comma")
|
| 381 |
+
if self.stream.current.type == "name":
|
| 382 |
+
if parse_context():
|
| 383 |
+
break
|
| 384 |
+
target = self.parse_assign_target(name_only=True)
|
| 385 |
+
if target.name.startswith("_"):
|
| 386 |
+
self.fail(
|
| 387 |
+
"names starting with an underline can not be imported",
|
| 388 |
+
target.lineno,
|
| 389 |
+
exc=TemplateAssertionError,
|
| 390 |
+
)
|
| 391 |
+
if self.stream.skip_if("name:as"):
|
| 392 |
+
alias = self.parse_assign_target(name_only=True)
|
| 393 |
+
node.names.append((target.name, alias.name))
|
| 394 |
+
else:
|
| 395 |
+
node.names.append(target.name)
|
| 396 |
+
if parse_context() or self.stream.current.type != "comma":
|
| 397 |
+
break
|
| 398 |
+
else:
|
| 399 |
+
self.stream.expect("name")
|
| 400 |
+
if not hasattr(node, "with_context"):
|
| 401 |
+
node.with_context = False
|
| 402 |
+
return node
|
| 403 |
+
|
| 404 |
+
def parse_signature(self, node: _MacroCall) -> None:
|
| 405 |
+
args = node.args = []
|
| 406 |
+
defaults = node.defaults = []
|
| 407 |
+
self.stream.expect("lparen")
|
| 408 |
+
while self.stream.current.type != "rparen":
|
| 409 |
+
if args:
|
| 410 |
+
self.stream.expect("comma")
|
| 411 |
+
arg = self.parse_assign_target(name_only=True)
|
| 412 |
+
arg.set_ctx("param")
|
| 413 |
+
if self.stream.skip_if("assign"):
|
| 414 |
+
defaults.append(self.parse_expression())
|
| 415 |
+
elif defaults:
|
| 416 |
+
self.fail("non-default argument follows default argument")
|
| 417 |
+
args.append(arg)
|
| 418 |
+
self.stream.expect("rparen")
|
| 419 |
+
|
| 420 |
+
def parse_call_block(self) -> nodes.CallBlock:
|
| 421 |
+
node = nodes.CallBlock(lineno=next(self.stream).lineno)
|
| 422 |
+
if self.stream.current.type == "lparen":
|
| 423 |
+
self.parse_signature(node)
|
| 424 |
+
else:
|
| 425 |
+
node.args = []
|
| 426 |
+
node.defaults = []
|
| 427 |
+
|
| 428 |
+
call_node = self.parse_expression()
|
| 429 |
+
if not isinstance(call_node, nodes.Call):
|
| 430 |
+
self.fail("expected call", node.lineno)
|
| 431 |
+
node.call = call_node
|
| 432 |
+
node.body = self.parse_statements(("name:endcall",), drop_needle=True)
|
| 433 |
+
return node
|
| 434 |
+
|
| 435 |
+
def parse_filter_block(self) -> nodes.FilterBlock:
|
| 436 |
+
node = nodes.FilterBlock(lineno=next(self.stream).lineno)
|
| 437 |
+
node.filter = self.parse_filter(None, start_inline=True) # type: ignore
|
| 438 |
+
node.body = self.parse_statements(("name:endfilter",), drop_needle=True)
|
| 439 |
+
return node
|
| 440 |
+
|
| 441 |
+
def parse_macro(self) -> nodes.Macro:
|
| 442 |
+
node = nodes.Macro(lineno=next(self.stream).lineno)
|
| 443 |
+
node.name = self.parse_assign_target(name_only=True).name
|
| 444 |
+
self.parse_signature(node)
|
| 445 |
+
node.body = self.parse_statements(("name:endmacro",), drop_needle=True)
|
| 446 |
+
return node
|
| 447 |
+
|
| 448 |
+
def parse_print(self) -> nodes.Output:
|
| 449 |
+
node = nodes.Output(lineno=next(self.stream).lineno)
|
| 450 |
+
node.nodes = []
|
| 451 |
+
while self.stream.current.type != "block_end":
|
| 452 |
+
if node.nodes:
|
| 453 |
+
self.stream.expect("comma")
|
| 454 |
+
node.nodes.append(self.parse_expression())
|
| 455 |
+
return node
|
| 456 |
+
|
| 457 |
+
@typing.overload
|
| 458 |
+
def parse_assign_target(
|
| 459 |
+
self, with_tuple: bool = ..., name_only: "te.Literal[True]" = ...
|
| 460 |
+
) -> nodes.Name:
|
| 461 |
+
...
|
| 462 |
+
|
| 463 |
+
@typing.overload
|
| 464 |
+
def parse_assign_target(
|
| 465 |
+
self,
|
| 466 |
+
with_tuple: bool = True,
|
| 467 |
+
name_only: bool = False,
|
| 468 |
+
extra_end_rules: t.Optional[t.Tuple[str, ...]] = None,
|
| 469 |
+
with_namespace: bool = False,
|
| 470 |
+
) -> t.Union[nodes.NSRef, nodes.Name, nodes.Tuple]:
|
| 471 |
+
...
|
| 472 |
+
|
| 473 |
+
def parse_assign_target(
|
| 474 |
+
self,
|
| 475 |
+
with_tuple: bool = True,
|
| 476 |
+
name_only: bool = False,
|
| 477 |
+
extra_end_rules: t.Optional[t.Tuple[str, ...]] = None,
|
| 478 |
+
with_namespace: bool = False,
|
| 479 |
+
) -> t.Union[nodes.NSRef, nodes.Name, nodes.Tuple]:
|
| 480 |
+
"""Parse an assignment target. As Jinja allows assignments to
|
| 481 |
+
tuples, this function can parse all allowed assignment targets. Per
|
| 482 |
+
default assignments to tuples are parsed, that can be disable however
|
| 483 |
+
by setting `with_tuple` to `False`. If only assignments to names are
|
| 484 |
+
wanted `name_only` can be set to `True`. The `extra_end_rules`
|
| 485 |
+
parameter is forwarded to the tuple parsing function. If
|
| 486 |
+
`with_namespace` is enabled, a namespace assignment may be parsed.
|
| 487 |
+
"""
|
| 488 |
+
target: nodes.Expr
|
| 489 |
+
|
| 490 |
+
if with_namespace and self.stream.look().type == "dot":
|
| 491 |
+
token = self.stream.expect("name")
|
| 492 |
+
next(self.stream) # dot
|
| 493 |
+
attr = self.stream.expect("name")
|
| 494 |
+
target = nodes.NSRef(token.value, attr.value, lineno=token.lineno)
|
| 495 |
+
elif name_only:
|
| 496 |
+
token = self.stream.expect("name")
|
| 497 |
+
target = nodes.Name(token.value, "store", lineno=token.lineno)
|
| 498 |
+
else:
|
| 499 |
+
if with_tuple:
|
| 500 |
+
target = self.parse_tuple(
|
| 501 |
+
simplified=True, extra_end_rules=extra_end_rules
|
| 502 |
+
)
|
| 503 |
+
else:
|
| 504 |
+
target = self.parse_primary()
|
| 505 |
+
|
| 506 |
+
target.set_ctx("store")
|
| 507 |
+
|
| 508 |
+
if not target.can_assign():
|
| 509 |
+
self.fail(
|
| 510 |
+
f"can't assign to {type(target).__name__.lower()!r}", target.lineno
|
| 511 |
+
)
|
| 512 |
+
|
| 513 |
+
return target # type: ignore
|
| 514 |
+
|
| 515 |
+
def parse_expression(self, with_condexpr: bool = True) -> nodes.Expr:
|
| 516 |
+
"""Parse an expression. Per default all expressions are parsed, if
|
| 517 |
+
the optional `with_condexpr` parameter is set to `False` conditional
|
| 518 |
+
expressions are not parsed.
|
| 519 |
+
"""
|
| 520 |
+
if with_condexpr:
|
| 521 |
+
return self.parse_condexpr()
|
| 522 |
+
return self.parse_or()
|
| 523 |
+
|
| 524 |
+
def parse_condexpr(self) -> nodes.Expr:
|
| 525 |
+
lineno = self.stream.current.lineno
|
| 526 |
+
expr1 = self.parse_or()
|
| 527 |
+
expr3: t.Optional[nodes.Expr]
|
| 528 |
+
|
| 529 |
+
while self.stream.skip_if("name:if"):
|
| 530 |
+
expr2 = self.parse_or()
|
| 531 |
+
if self.stream.skip_if("name:else"):
|
| 532 |
+
expr3 = self.parse_condexpr()
|
| 533 |
+
else:
|
| 534 |
+
expr3 = None
|
| 535 |
+
expr1 = nodes.CondExpr(expr2, expr1, expr3, lineno=lineno)
|
| 536 |
+
lineno = self.stream.current.lineno
|
| 537 |
+
return expr1
|
| 538 |
+
|
| 539 |
+
def parse_or(self) -> nodes.Expr:
|
| 540 |
+
lineno = self.stream.current.lineno
|
| 541 |
+
left = self.parse_and()
|
| 542 |
+
while self.stream.skip_if("name:or"):
|
| 543 |
+
right = self.parse_and()
|
| 544 |
+
left = nodes.Or(left, right, lineno=lineno)
|
| 545 |
+
lineno = self.stream.current.lineno
|
| 546 |
+
return left
|
| 547 |
+
|
| 548 |
+
def parse_and(self) -> nodes.Expr:
|
| 549 |
+
lineno = self.stream.current.lineno
|
| 550 |
+
left = self.parse_not()
|
| 551 |
+
while self.stream.skip_if("name:and"):
|
| 552 |
+
right = self.parse_not()
|
| 553 |
+
left = nodes.And(left, right, lineno=lineno)
|
| 554 |
+
lineno = self.stream.current.lineno
|
| 555 |
+
return left
|
| 556 |
+
|
| 557 |
+
def parse_not(self) -> nodes.Expr:
|
| 558 |
+
if self.stream.current.test("name:not"):
|
| 559 |
+
lineno = next(self.stream).lineno
|
| 560 |
+
return nodes.Not(self.parse_not(), lineno=lineno)
|
| 561 |
+
return self.parse_compare()
|
| 562 |
+
|
| 563 |
+
def parse_compare(self) -> nodes.Expr:
|
| 564 |
+
lineno = self.stream.current.lineno
|
| 565 |
+
expr = self.parse_math1()
|
| 566 |
+
ops = []
|
| 567 |
+
while True:
|
| 568 |
+
token_type = self.stream.current.type
|
| 569 |
+
if token_type in _compare_operators:
|
| 570 |
+
next(self.stream)
|
| 571 |
+
ops.append(nodes.Operand(token_type, self.parse_math1()))
|
| 572 |
+
elif self.stream.skip_if("name:in"):
|
| 573 |
+
ops.append(nodes.Operand("in", self.parse_math1()))
|
| 574 |
+
elif self.stream.current.test("name:not") and self.stream.look().test(
|
| 575 |
+
"name:in"
|
| 576 |
+
):
|
| 577 |
+
self.stream.skip(2)
|
| 578 |
+
ops.append(nodes.Operand("notin", self.parse_math1()))
|
| 579 |
+
else:
|
| 580 |
+
break
|
| 581 |
+
lineno = self.stream.current.lineno
|
| 582 |
+
if not ops:
|
| 583 |
+
return expr
|
| 584 |
+
return nodes.Compare(expr, ops, lineno=lineno)
|
| 585 |
+
|
| 586 |
+
def parse_math1(self) -> nodes.Expr:
|
| 587 |
+
lineno = self.stream.current.lineno
|
| 588 |
+
left = self.parse_concat()
|
| 589 |
+
while self.stream.current.type in ("add", "sub"):
|
| 590 |
+
cls = _math_nodes[self.stream.current.type]
|
| 591 |
+
next(self.stream)
|
| 592 |
+
right = self.parse_concat()
|
| 593 |
+
left = cls(left, right, lineno=lineno)
|
| 594 |
+
lineno = self.stream.current.lineno
|
| 595 |
+
return left
|
| 596 |
+
|
| 597 |
+
def parse_concat(self) -> nodes.Expr:
|
| 598 |
+
lineno = self.stream.current.lineno
|
| 599 |
+
args = [self.parse_math2()]
|
| 600 |
+
while self.stream.current.type == "tilde":
|
| 601 |
+
next(self.stream)
|
| 602 |
+
args.append(self.parse_math2())
|
| 603 |
+
if len(args) == 1:
|
| 604 |
+
return args[0]
|
| 605 |
+
return nodes.Concat(args, lineno=lineno)
|
| 606 |
+
|
| 607 |
+
def parse_math2(self) -> nodes.Expr:
|
| 608 |
+
lineno = self.stream.current.lineno
|
| 609 |
+
left = self.parse_pow()
|
| 610 |
+
while self.stream.current.type in ("mul", "div", "floordiv", "mod"):
|
| 611 |
+
cls = _math_nodes[self.stream.current.type]
|
| 612 |
+
next(self.stream)
|
| 613 |
+
right = self.parse_pow()
|
| 614 |
+
left = cls(left, right, lineno=lineno)
|
| 615 |
+
lineno = self.stream.current.lineno
|
| 616 |
+
return left
|
| 617 |
+
|
| 618 |
+
def parse_pow(self) -> nodes.Expr:
|
| 619 |
+
lineno = self.stream.current.lineno
|
| 620 |
+
left = self.parse_unary()
|
| 621 |
+
while self.stream.current.type == "pow":
|
| 622 |
+
next(self.stream)
|
| 623 |
+
right = self.parse_unary()
|
| 624 |
+
left = nodes.Pow(left, right, lineno=lineno)
|
| 625 |
+
lineno = self.stream.current.lineno
|
| 626 |
+
return left
|
| 627 |
+
|
| 628 |
+
def parse_unary(self, with_filter: bool = True) -> nodes.Expr:
|
| 629 |
+
token_type = self.stream.current.type
|
| 630 |
+
lineno = self.stream.current.lineno
|
| 631 |
+
node: nodes.Expr
|
| 632 |
+
|
| 633 |
+
if token_type == "sub":
|
| 634 |
+
next(self.stream)
|
| 635 |
+
node = nodes.Neg(self.parse_unary(False), lineno=lineno)
|
| 636 |
+
elif token_type == "add":
|
| 637 |
+
next(self.stream)
|
| 638 |
+
node = nodes.Pos(self.parse_unary(False), lineno=lineno)
|
| 639 |
+
else:
|
| 640 |
+
node = self.parse_primary()
|
| 641 |
+
node = self.parse_postfix(node)
|
| 642 |
+
if with_filter:
|
| 643 |
+
node = self.parse_filter_expr(node)
|
| 644 |
+
return node
|
| 645 |
+
|
| 646 |
+
def parse_primary(self) -> nodes.Expr:
|
| 647 |
+
token = self.stream.current
|
| 648 |
+
node: nodes.Expr
|
| 649 |
+
if token.type == "name":
|
| 650 |
+
if token.value in ("true", "false", "True", "False"):
|
| 651 |
+
node = nodes.Const(token.value in ("true", "True"), lineno=token.lineno)
|
| 652 |
+
elif token.value in ("none", "None"):
|
| 653 |
+
node = nodes.Const(None, lineno=token.lineno)
|
| 654 |
+
else:
|
| 655 |
+
node = nodes.Name(token.value, "load", lineno=token.lineno)
|
| 656 |
+
next(self.stream)
|
| 657 |
+
elif token.type == "string":
|
| 658 |
+
next(self.stream)
|
| 659 |
+
buf = [token.value]
|
| 660 |
+
lineno = token.lineno
|
| 661 |
+
while self.stream.current.type == "string":
|
| 662 |
+
buf.append(self.stream.current.value)
|
| 663 |
+
next(self.stream)
|
| 664 |
+
node = nodes.Const("".join(buf), lineno=lineno)
|
| 665 |
+
elif token.type in ("integer", "float"):
|
| 666 |
+
next(self.stream)
|
| 667 |
+
node = nodes.Const(token.value, lineno=token.lineno)
|
| 668 |
+
elif token.type == "lparen":
|
| 669 |
+
next(self.stream)
|
| 670 |
+
node = self.parse_tuple(explicit_parentheses=True)
|
| 671 |
+
self.stream.expect("rparen")
|
| 672 |
+
elif token.type == "lbracket":
|
| 673 |
+
node = self.parse_list()
|
| 674 |
+
elif token.type == "lbrace":
|
| 675 |
+
node = self.parse_dict()
|
| 676 |
+
else:
|
| 677 |
+
self.fail(f"unexpected {describe_token(token)!r}", token.lineno)
|
| 678 |
+
return node
|
| 679 |
+
|
| 680 |
+
def parse_tuple(
|
| 681 |
+
self,
|
| 682 |
+
simplified: bool = False,
|
| 683 |
+
with_condexpr: bool = True,
|
| 684 |
+
extra_end_rules: t.Optional[t.Tuple[str, ...]] = None,
|
| 685 |
+
explicit_parentheses: bool = False,
|
| 686 |
+
) -> t.Union[nodes.Tuple, nodes.Expr]:
|
| 687 |
+
"""Works like `parse_expression` but if multiple expressions are
|
| 688 |
+
delimited by a comma a :class:`~jinja2.nodes.Tuple` node is created.
|
| 689 |
+
This method could also return a regular expression instead of a tuple
|
| 690 |
+
if no commas where found.
|
| 691 |
+
|
| 692 |
+
The default parsing mode is a full tuple. If `simplified` is `True`
|
| 693 |
+
only names and literals are parsed. The `no_condexpr` parameter is
|
| 694 |
+
forwarded to :meth:`parse_expression`.
|
| 695 |
+
|
| 696 |
+
Because tuples do not require delimiters and may end in a bogus comma
|
| 697 |
+
an extra hint is needed that marks the end of a tuple. For example
|
| 698 |
+
for loops support tuples between `for` and `in`. In that case the
|
| 699 |
+
`extra_end_rules` is set to ``['name:in']``.
|
| 700 |
+
|
| 701 |
+
`explicit_parentheses` is true if the parsing was triggered by an
|
| 702 |
+
expression in parentheses. This is used to figure out if an empty
|
| 703 |
+
tuple is a valid expression or not.
|
| 704 |
+
"""
|
| 705 |
+
lineno = self.stream.current.lineno
|
| 706 |
+
if simplified:
|
| 707 |
+
parse = self.parse_primary
|
| 708 |
+
elif with_condexpr:
|
| 709 |
+
parse = self.parse_expression
|
| 710 |
+
else:
|
| 711 |
+
|
| 712 |
+
def parse() -> nodes.Expr:
|
| 713 |
+
return self.parse_expression(with_condexpr=False)
|
| 714 |
+
|
| 715 |
+
args: t.List[nodes.Expr] = []
|
| 716 |
+
is_tuple = False
|
| 717 |
+
|
| 718 |
+
while True:
|
| 719 |
+
if args:
|
| 720 |
+
self.stream.expect("comma")
|
| 721 |
+
if self.is_tuple_end(extra_end_rules):
|
| 722 |
+
break
|
| 723 |
+
args.append(parse())
|
| 724 |
+
if self.stream.current.type == "comma":
|
| 725 |
+
is_tuple = True
|
| 726 |
+
else:
|
| 727 |
+
break
|
| 728 |
+
lineno = self.stream.current.lineno
|
| 729 |
+
|
| 730 |
+
if not is_tuple:
|
| 731 |
+
if args:
|
| 732 |
+
return args[0]
|
| 733 |
+
|
| 734 |
+
# if we don't have explicit parentheses, an empty tuple is
|
| 735 |
+
# not a valid expression. This would mean nothing (literally
|
| 736 |
+
# nothing) in the spot of an expression would be an empty
|
| 737 |
+
# tuple.
|
| 738 |
+
if not explicit_parentheses:
|
| 739 |
+
self.fail(
|
| 740 |
+
"Expected an expression,"
|
| 741 |
+
f" got {describe_token(self.stream.current)!r}"
|
| 742 |
+
)
|
| 743 |
+
|
| 744 |
+
return nodes.Tuple(args, "load", lineno=lineno)
|
| 745 |
+
|
| 746 |
+
def parse_list(self) -> nodes.List:
|
| 747 |
+
token = self.stream.expect("lbracket")
|
| 748 |
+
items: t.List[nodes.Expr] = []
|
| 749 |
+
while self.stream.current.type != "rbracket":
|
| 750 |
+
if items:
|
| 751 |
+
self.stream.expect("comma")
|
| 752 |
+
if self.stream.current.type == "rbracket":
|
| 753 |
+
break
|
| 754 |
+
items.append(self.parse_expression())
|
| 755 |
+
self.stream.expect("rbracket")
|
| 756 |
+
return nodes.List(items, lineno=token.lineno)
|
| 757 |
+
|
| 758 |
+
def parse_dict(self) -> nodes.Dict:
|
| 759 |
+
token = self.stream.expect("lbrace")
|
| 760 |
+
items: t.List[nodes.Pair] = []
|
| 761 |
+
while self.stream.current.type != "rbrace":
|
| 762 |
+
if items:
|
| 763 |
+
self.stream.expect("comma")
|
| 764 |
+
if self.stream.current.type == "rbrace":
|
| 765 |
+
break
|
| 766 |
+
key = self.parse_expression()
|
| 767 |
+
self.stream.expect("colon")
|
| 768 |
+
value = self.parse_expression()
|
| 769 |
+
items.append(nodes.Pair(key, value, lineno=key.lineno))
|
| 770 |
+
self.stream.expect("rbrace")
|
| 771 |
+
return nodes.Dict(items, lineno=token.lineno)
|
| 772 |
+
|
| 773 |
+
def parse_postfix(self, node: nodes.Expr) -> nodes.Expr:
|
| 774 |
+
while True:
|
| 775 |
+
token_type = self.stream.current.type
|
| 776 |
+
if token_type == "dot" or token_type == "lbracket":
|
| 777 |
+
node = self.parse_subscript(node)
|
| 778 |
+
# calls are valid both after postfix expressions (getattr
|
| 779 |
+
# and getitem) as well as filters and tests
|
| 780 |
+
elif token_type == "lparen":
|
| 781 |
+
node = self.parse_call(node)
|
| 782 |
+
else:
|
| 783 |
+
break
|
| 784 |
+
return node
|
| 785 |
+
|
| 786 |
+
def parse_filter_expr(self, node: nodes.Expr) -> nodes.Expr:
|
| 787 |
+
while True:
|
| 788 |
+
token_type = self.stream.current.type
|
| 789 |
+
if token_type == "pipe":
|
| 790 |
+
node = self.parse_filter(node) # type: ignore
|
| 791 |
+
elif token_type == "name" and self.stream.current.value == "is":
|
| 792 |
+
node = self.parse_test(node)
|
| 793 |
+
# calls are valid both after postfix expressions (getattr
|
| 794 |
+
# and getitem) as well as filters and tests
|
| 795 |
+
elif token_type == "lparen":
|
| 796 |
+
node = self.parse_call(node)
|
| 797 |
+
else:
|
| 798 |
+
break
|
| 799 |
+
return node
|
| 800 |
+
|
| 801 |
+
def parse_subscript(
|
| 802 |
+
self, node: nodes.Expr
|
| 803 |
+
) -> t.Union[nodes.Getattr, nodes.Getitem]:
|
| 804 |
+
token = next(self.stream)
|
| 805 |
+
arg: nodes.Expr
|
| 806 |
+
|
| 807 |
+
if token.type == "dot":
|
| 808 |
+
attr_token = self.stream.current
|
| 809 |
+
next(self.stream)
|
| 810 |
+
if attr_token.type == "name":
|
| 811 |
+
return nodes.Getattr(
|
| 812 |
+
node, attr_token.value, "load", lineno=token.lineno
|
| 813 |
+
)
|
| 814 |
+
elif attr_token.type != "integer":
|
| 815 |
+
self.fail("expected name or number", attr_token.lineno)
|
| 816 |
+
arg = nodes.Const(attr_token.value, lineno=attr_token.lineno)
|
| 817 |
+
return nodes.Getitem(node, arg, "load", lineno=token.lineno)
|
| 818 |
+
if token.type == "lbracket":
|
| 819 |
+
args: t.List[nodes.Expr] = []
|
| 820 |
+
while self.stream.current.type != "rbracket":
|
| 821 |
+
if args:
|
| 822 |
+
self.stream.expect("comma")
|
| 823 |
+
args.append(self.parse_subscribed())
|
| 824 |
+
self.stream.expect("rbracket")
|
| 825 |
+
if len(args) == 1:
|
| 826 |
+
arg = args[0]
|
| 827 |
+
else:
|
| 828 |
+
arg = nodes.Tuple(args, "load", lineno=token.lineno)
|
| 829 |
+
return nodes.Getitem(node, arg, "load", lineno=token.lineno)
|
| 830 |
+
self.fail("expected subscript expression", token.lineno)
|
| 831 |
+
|
| 832 |
+
def parse_subscribed(self) -> nodes.Expr:
|
| 833 |
+
lineno = self.stream.current.lineno
|
| 834 |
+
args: t.List[t.Optional[nodes.Expr]]
|
| 835 |
+
|
| 836 |
+
if self.stream.current.type == "colon":
|
| 837 |
+
next(self.stream)
|
| 838 |
+
args = [None]
|
| 839 |
+
else:
|
| 840 |
+
node = self.parse_expression()
|
| 841 |
+
if self.stream.current.type != "colon":
|
| 842 |
+
return node
|
| 843 |
+
next(self.stream)
|
| 844 |
+
args = [node]
|
| 845 |
+
|
| 846 |
+
if self.stream.current.type == "colon":
|
| 847 |
+
args.append(None)
|
| 848 |
+
elif self.stream.current.type not in ("rbracket", "comma"):
|
| 849 |
+
args.append(self.parse_expression())
|
| 850 |
+
else:
|
| 851 |
+
args.append(None)
|
| 852 |
+
|
| 853 |
+
if self.stream.current.type == "colon":
|
| 854 |
+
next(self.stream)
|
| 855 |
+
if self.stream.current.type not in ("rbracket", "comma"):
|
| 856 |
+
args.append(self.parse_expression())
|
| 857 |
+
else:
|
| 858 |
+
args.append(None)
|
| 859 |
+
else:
|
| 860 |
+
args.append(None)
|
| 861 |
+
|
| 862 |
+
return nodes.Slice(lineno=lineno, *args) # noqa: B026
|
| 863 |
+
|
| 864 |
+
def parse_call_args(self) -> t.Tuple:
|
| 865 |
+
token = self.stream.expect("lparen")
|
| 866 |
+
args = []
|
| 867 |
+
kwargs = []
|
| 868 |
+
dyn_args = None
|
| 869 |
+
dyn_kwargs = None
|
| 870 |
+
require_comma = False
|
| 871 |
+
|
| 872 |
+
def ensure(expr: bool) -> None:
|
| 873 |
+
if not expr:
|
| 874 |
+
self.fail("invalid syntax for function call expression", token.lineno)
|
| 875 |
+
|
| 876 |
+
while self.stream.current.type != "rparen":
|
| 877 |
+
if require_comma:
|
| 878 |
+
self.stream.expect("comma")
|
| 879 |
+
|
| 880 |
+
# support for trailing comma
|
| 881 |
+
if self.stream.current.type == "rparen":
|
| 882 |
+
break
|
| 883 |
+
|
| 884 |
+
if self.stream.current.type == "mul":
|
| 885 |
+
ensure(dyn_args is None and dyn_kwargs is None)
|
| 886 |
+
next(self.stream)
|
| 887 |
+
dyn_args = self.parse_expression()
|
| 888 |
+
elif self.stream.current.type == "pow":
|
| 889 |
+
ensure(dyn_kwargs is None)
|
| 890 |
+
next(self.stream)
|
| 891 |
+
dyn_kwargs = self.parse_expression()
|
| 892 |
+
else:
|
| 893 |
+
if (
|
| 894 |
+
self.stream.current.type == "name"
|
| 895 |
+
and self.stream.look().type == "assign"
|
| 896 |
+
):
|
| 897 |
+
# Parsing a kwarg
|
| 898 |
+
ensure(dyn_kwargs is None)
|
| 899 |
+
key = self.stream.current.value
|
| 900 |
+
self.stream.skip(2)
|
| 901 |
+
value = self.parse_expression()
|
| 902 |
+
kwargs.append(nodes.Keyword(key, value, lineno=value.lineno))
|
| 903 |
+
else:
|
| 904 |
+
# Parsing an arg
|
| 905 |
+
ensure(dyn_args is None and dyn_kwargs is None and not kwargs)
|
| 906 |
+
args.append(self.parse_expression())
|
| 907 |
+
|
| 908 |
+
require_comma = True
|
| 909 |
+
|
| 910 |
+
self.stream.expect("rparen")
|
| 911 |
+
return args, kwargs, dyn_args, dyn_kwargs
|
| 912 |
+
|
| 913 |
+
def parse_call(self, node: nodes.Expr) -> nodes.Call:
|
| 914 |
+
# The lparen will be expected in parse_call_args, but the lineno
|
| 915 |
+
# needs to be recorded before the stream is advanced.
|
| 916 |
+
token = self.stream.current
|
| 917 |
+
args, kwargs, dyn_args, dyn_kwargs = self.parse_call_args()
|
| 918 |
+
return nodes.Call(node, args, kwargs, dyn_args, dyn_kwargs, lineno=token.lineno)
|
| 919 |
+
|
| 920 |
+
def parse_filter(
|
| 921 |
+
self, node: t.Optional[nodes.Expr], start_inline: bool = False
|
| 922 |
+
) -> t.Optional[nodes.Expr]:
|
| 923 |
+
while self.stream.current.type == "pipe" or start_inline:
|
| 924 |
+
if not start_inline:
|
| 925 |
+
next(self.stream)
|
| 926 |
+
token = self.stream.expect("name")
|
| 927 |
+
name = token.value
|
| 928 |
+
while self.stream.current.type == "dot":
|
| 929 |
+
next(self.stream)
|
| 930 |
+
name += "." + self.stream.expect("name").value
|
| 931 |
+
if self.stream.current.type == "lparen":
|
| 932 |
+
args, kwargs, dyn_args, dyn_kwargs = self.parse_call_args()
|
| 933 |
+
else:
|
| 934 |
+
args = []
|
| 935 |
+
kwargs = []
|
| 936 |
+
dyn_args = dyn_kwargs = None
|
| 937 |
+
node = nodes.Filter(
|
| 938 |
+
node, name, args, kwargs, dyn_args, dyn_kwargs, lineno=token.lineno
|
| 939 |
+
)
|
| 940 |
+
start_inline = False
|
| 941 |
+
return node
|
| 942 |
+
|
| 943 |
+
def parse_test(self, node: nodes.Expr) -> nodes.Expr:
|
| 944 |
+
token = next(self.stream)
|
| 945 |
+
if self.stream.current.test("name:not"):
|
| 946 |
+
next(self.stream)
|
| 947 |
+
negated = True
|
| 948 |
+
else:
|
| 949 |
+
negated = False
|
| 950 |
+
name = self.stream.expect("name").value
|
| 951 |
+
while self.stream.current.type == "dot":
|
| 952 |
+
next(self.stream)
|
| 953 |
+
name += "." + self.stream.expect("name").value
|
| 954 |
+
dyn_args = dyn_kwargs = None
|
| 955 |
+
kwargs = []
|
| 956 |
+
if self.stream.current.type == "lparen":
|
| 957 |
+
args, kwargs, dyn_args, dyn_kwargs = self.parse_call_args()
|
| 958 |
+
elif self.stream.current.type in {
|
| 959 |
+
"name",
|
| 960 |
+
"string",
|
| 961 |
+
"integer",
|
| 962 |
+
"float",
|
| 963 |
+
"lparen",
|
| 964 |
+
"lbracket",
|
| 965 |
+
"lbrace",
|
| 966 |
+
} and not self.stream.current.test_any("name:else", "name:or", "name:and"):
|
| 967 |
+
if self.stream.current.test("name:is"):
|
| 968 |
+
self.fail("You cannot chain multiple tests with is")
|
| 969 |
+
arg_node = self.parse_primary()
|
| 970 |
+
arg_node = self.parse_postfix(arg_node)
|
| 971 |
+
args = [arg_node]
|
| 972 |
+
else:
|
| 973 |
+
args = []
|
| 974 |
+
node = nodes.Test(
|
| 975 |
+
node, name, args, kwargs, dyn_args, dyn_kwargs, lineno=token.lineno
|
| 976 |
+
)
|
| 977 |
+
if negated:
|
| 978 |
+
node = nodes.Not(node, lineno=token.lineno)
|
| 979 |
+
return node
|
| 980 |
+
|
| 981 |
+
def subparse(
|
| 982 |
+
self, end_tokens: t.Optional[t.Tuple[str, ...]] = None
|
| 983 |
+
) -> t.List[nodes.Node]:
|
| 984 |
+
body: t.List[nodes.Node] = []
|
| 985 |
+
data_buffer: t.List[nodes.Node] = []
|
| 986 |
+
add_data = data_buffer.append
|
| 987 |
+
|
| 988 |
+
if end_tokens is not None:
|
| 989 |
+
self._end_token_stack.append(end_tokens)
|
| 990 |
+
|
| 991 |
+
def flush_data() -> None:
|
| 992 |
+
if data_buffer:
|
| 993 |
+
lineno = data_buffer[0].lineno
|
| 994 |
+
body.append(nodes.Output(data_buffer[:], lineno=lineno))
|
| 995 |
+
del data_buffer[:]
|
| 996 |
+
|
| 997 |
+
try:
|
| 998 |
+
while self.stream:
|
| 999 |
+
token = self.stream.current
|
| 1000 |
+
if token.type == "data":
|
| 1001 |
+
if token.value:
|
| 1002 |
+
add_data(nodes.TemplateData(token.value, lineno=token.lineno))
|
| 1003 |
+
next(self.stream)
|
| 1004 |
+
elif token.type == "variable_begin":
|
| 1005 |
+
next(self.stream)
|
| 1006 |
+
add_data(self.parse_tuple(with_condexpr=True))
|
| 1007 |
+
self.stream.expect("variable_end")
|
| 1008 |
+
elif token.type == "block_begin":
|
| 1009 |
+
flush_data()
|
| 1010 |
+
next(self.stream)
|
| 1011 |
+
if end_tokens is not None and self.stream.current.test_any(
|
| 1012 |
+
*end_tokens
|
| 1013 |
+
):
|
| 1014 |
+
return body
|
| 1015 |
+
rv = self.parse_statement()
|
| 1016 |
+
if isinstance(rv, list):
|
| 1017 |
+
body.extend(rv)
|
| 1018 |
+
else:
|
| 1019 |
+
body.append(rv)
|
| 1020 |
+
self.stream.expect("block_end")
|
| 1021 |
+
else:
|
| 1022 |
+
raise AssertionError("internal parsing error")
|
| 1023 |
+
|
| 1024 |
+
flush_data()
|
| 1025 |
+
finally:
|
| 1026 |
+
if end_tokens is not None:
|
| 1027 |
+
self._end_token_stack.pop()
|
| 1028 |
+
return body
|
| 1029 |
+
|
| 1030 |
+
def parse(self) -> nodes.Template:
|
| 1031 |
+
"""Parse the whole template into a `Template` node."""
|
| 1032 |
+
result = nodes.Template(self.subparse(), lineno=1)
|
| 1033 |
+
result.set_environment(self.environment)
|
| 1034 |
+
return result
|
tuning-competition-baseline/.venv/lib/python3.11/site-packages/jinja2/utils.py
ADDED
|
@@ -0,0 +1,755 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import enum
|
| 2 |
+
import json
|
| 3 |
+
import os
|
| 4 |
+
import re
|
| 5 |
+
import typing as t
|
| 6 |
+
from collections import abc
|
| 7 |
+
from collections import deque
|
| 8 |
+
from random import choice
|
| 9 |
+
from random import randrange
|
| 10 |
+
from threading import Lock
|
| 11 |
+
from types import CodeType
|
| 12 |
+
from urllib.parse import quote_from_bytes
|
| 13 |
+
|
| 14 |
+
import markupsafe
|
| 15 |
+
|
| 16 |
+
if t.TYPE_CHECKING:
|
| 17 |
+
import typing_extensions as te
|
| 18 |
+
|
| 19 |
+
F = t.TypeVar("F", bound=t.Callable[..., t.Any])
|
| 20 |
+
|
| 21 |
+
# special singleton representing missing values for the runtime
|
| 22 |
+
missing: t.Any = type("MissingType", (), {"__repr__": lambda x: "missing"})()
|
| 23 |
+
|
| 24 |
+
internal_code: t.MutableSet[CodeType] = set()
|
| 25 |
+
|
| 26 |
+
concat = "".join
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
def pass_context(f: F) -> F:
|
| 30 |
+
"""Pass the :class:`~jinja2.runtime.Context` as the first argument
|
| 31 |
+
to the decorated function when called while rendering a template.
|
| 32 |
+
|
| 33 |
+
Can be used on functions, filters, and tests.
|
| 34 |
+
|
| 35 |
+
If only ``Context.eval_context`` is needed, use
|
| 36 |
+
:func:`pass_eval_context`. If only ``Context.environment`` is
|
| 37 |
+
needed, use :func:`pass_environment`.
|
| 38 |
+
|
| 39 |
+
.. versionadded:: 3.0.0
|
| 40 |
+
Replaces ``contextfunction`` and ``contextfilter``.
|
| 41 |
+
"""
|
| 42 |
+
f.jinja_pass_arg = _PassArg.context # type: ignore
|
| 43 |
+
return f
|
| 44 |
+
|
| 45 |
+
|
| 46 |
+
def pass_eval_context(f: F) -> F:
|
| 47 |
+
"""Pass the :class:`~jinja2.nodes.EvalContext` as the first argument
|
| 48 |
+
to the decorated function when called while rendering a template.
|
| 49 |
+
See :ref:`eval-context`.
|
| 50 |
+
|
| 51 |
+
Can be used on functions, filters, and tests.
|
| 52 |
+
|
| 53 |
+
If only ``EvalContext.environment`` is needed, use
|
| 54 |
+
:func:`pass_environment`.
|
| 55 |
+
|
| 56 |
+
.. versionadded:: 3.0.0
|
| 57 |
+
Replaces ``evalcontextfunction`` and ``evalcontextfilter``.
|
| 58 |
+
"""
|
| 59 |
+
f.jinja_pass_arg = _PassArg.eval_context # type: ignore
|
| 60 |
+
return f
|
| 61 |
+
|
| 62 |
+
|
| 63 |
+
def pass_environment(f: F) -> F:
|
| 64 |
+
"""Pass the :class:`~jinja2.Environment` as the first argument to
|
| 65 |
+
the decorated function when called while rendering a template.
|
| 66 |
+
|
| 67 |
+
Can be used on functions, filters, and tests.
|
| 68 |
+
|
| 69 |
+
.. versionadded:: 3.0.0
|
| 70 |
+
Replaces ``environmentfunction`` and ``environmentfilter``.
|
| 71 |
+
"""
|
| 72 |
+
f.jinja_pass_arg = _PassArg.environment # type: ignore
|
| 73 |
+
return f
|
| 74 |
+
|
| 75 |
+
|
| 76 |
+
class _PassArg(enum.Enum):
|
| 77 |
+
context = enum.auto()
|
| 78 |
+
eval_context = enum.auto()
|
| 79 |
+
environment = enum.auto()
|
| 80 |
+
|
| 81 |
+
@classmethod
|
| 82 |
+
def from_obj(cls, obj: F) -> t.Optional["_PassArg"]:
|
| 83 |
+
if hasattr(obj, "jinja_pass_arg"):
|
| 84 |
+
return obj.jinja_pass_arg # type: ignore
|
| 85 |
+
|
| 86 |
+
return None
|
| 87 |
+
|
| 88 |
+
|
| 89 |
+
def internalcode(f: F) -> F:
|
| 90 |
+
"""Marks the function as internally used"""
|
| 91 |
+
internal_code.add(f.__code__)
|
| 92 |
+
return f
|
| 93 |
+
|
| 94 |
+
|
| 95 |
+
def is_undefined(obj: t.Any) -> bool:
|
| 96 |
+
"""Check if the object passed is undefined. This does nothing more than
|
| 97 |
+
performing an instance check against :class:`Undefined` but looks nicer.
|
| 98 |
+
This can be used for custom filters or tests that want to react to
|
| 99 |
+
undefined variables. For example a custom default filter can look like
|
| 100 |
+
this::
|
| 101 |
+
|
| 102 |
+
def default(var, default=''):
|
| 103 |
+
if is_undefined(var):
|
| 104 |
+
return default
|
| 105 |
+
return var
|
| 106 |
+
"""
|
| 107 |
+
from .runtime import Undefined
|
| 108 |
+
|
| 109 |
+
return isinstance(obj, Undefined)
|
| 110 |
+
|
| 111 |
+
|
| 112 |
+
def consume(iterable: t.Iterable[t.Any]) -> None:
|
| 113 |
+
"""Consumes an iterable without doing anything with it."""
|
| 114 |
+
for _ in iterable:
|
| 115 |
+
pass
|
| 116 |
+
|
| 117 |
+
|
| 118 |
+
def clear_caches() -> None:
|
| 119 |
+
"""Jinja keeps internal caches for environments and lexers. These are
|
| 120 |
+
used so that Jinja doesn't have to recreate environments and lexers all
|
| 121 |
+
the time. Normally you don't have to care about that but if you are
|
| 122 |
+
measuring memory consumption you may want to clean the caches.
|
| 123 |
+
"""
|
| 124 |
+
from .environment import get_spontaneous_environment
|
| 125 |
+
from .lexer import _lexer_cache
|
| 126 |
+
|
| 127 |
+
get_spontaneous_environment.cache_clear()
|
| 128 |
+
_lexer_cache.clear()
|
| 129 |
+
|
| 130 |
+
|
| 131 |
+
def import_string(import_name: str, silent: bool = False) -> t.Any:
|
| 132 |
+
"""Imports an object based on a string. This is useful if you want to
|
| 133 |
+
use import paths as endpoints or something similar. An import path can
|
| 134 |
+
be specified either in dotted notation (``xml.sax.saxutils.escape``)
|
| 135 |
+
or with a colon as object delimiter (``xml.sax.saxutils:escape``).
|
| 136 |
+
|
| 137 |
+
If the `silent` is True the return value will be `None` if the import
|
| 138 |
+
fails.
|
| 139 |
+
|
| 140 |
+
:return: imported object
|
| 141 |
+
"""
|
| 142 |
+
try:
|
| 143 |
+
if ":" in import_name:
|
| 144 |
+
module, obj = import_name.split(":", 1)
|
| 145 |
+
elif "." in import_name:
|
| 146 |
+
module, _, obj = import_name.rpartition(".")
|
| 147 |
+
else:
|
| 148 |
+
return __import__(import_name)
|
| 149 |
+
return getattr(__import__(module, None, None, [obj]), obj)
|
| 150 |
+
except (ImportError, AttributeError):
|
| 151 |
+
if not silent:
|
| 152 |
+
raise
|
| 153 |
+
|
| 154 |
+
|
| 155 |
+
def open_if_exists(filename: str, mode: str = "rb") -> t.Optional[t.IO]:
|
| 156 |
+
"""Returns a file descriptor for the filename if that file exists,
|
| 157 |
+
otherwise ``None``.
|
| 158 |
+
"""
|
| 159 |
+
if not os.path.isfile(filename):
|
| 160 |
+
return None
|
| 161 |
+
|
| 162 |
+
return open(filename, mode)
|
| 163 |
+
|
| 164 |
+
|
| 165 |
+
def object_type_repr(obj: t.Any) -> str:
|
| 166 |
+
"""Returns the name of the object's type. For some recognized
|
| 167 |
+
singletons the name of the object is returned instead. (For
|
| 168 |
+
example for `None` and `Ellipsis`).
|
| 169 |
+
"""
|
| 170 |
+
if obj is None:
|
| 171 |
+
return "None"
|
| 172 |
+
elif obj is Ellipsis:
|
| 173 |
+
return "Ellipsis"
|
| 174 |
+
|
| 175 |
+
cls = type(obj)
|
| 176 |
+
|
| 177 |
+
if cls.__module__ == "builtins":
|
| 178 |
+
return f"{cls.__name__} object"
|
| 179 |
+
|
| 180 |
+
return f"{cls.__module__}.{cls.__name__} object"
|
| 181 |
+
|
| 182 |
+
|
| 183 |
+
def pformat(obj: t.Any) -> str:
|
| 184 |
+
"""Format an object using :func:`pprint.pformat`."""
|
| 185 |
+
from pprint import pformat
|
| 186 |
+
|
| 187 |
+
return pformat(obj)
|
| 188 |
+
|
| 189 |
+
|
| 190 |
+
_http_re = re.compile(
|
| 191 |
+
r"""
|
| 192 |
+
^
|
| 193 |
+
(
|
| 194 |
+
(https?://|www\.) # scheme or www
|
| 195 |
+
(([\w%-]+\.)+)? # subdomain
|
| 196 |
+
(
|
| 197 |
+
[a-z]{2,63} # basic tld
|
| 198 |
+
|
|
| 199 |
+
xn--[\w%]{2,59} # idna tld
|
| 200 |
+
)
|
| 201 |
+
|
|
| 202 |
+
([\w%-]{2,63}\.)+ # basic domain
|
| 203 |
+
(com|net|int|edu|gov|org|info|mil) # basic tld
|
| 204 |
+
|
|
| 205 |
+
(https?://) # scheme
|
| 206 |
+
(
|
| 207 |
+
(([\d]{1,3})(\.[\d]{1,3}){3}) # IPv4
|
| 208 |
+
|
|
| 209 |
+
(\[([\da-f]{0,4}:){2}([\da-f]{0,4}:?){1,6}]) # IPv6
|
| 210 |
+
)
|
| 211 |
+
)
|
| 212 |
+
(?::[\d]{1,5})? # port
|
| 213 |
+
(?:[/?#]\S*)? # path, query, and fragment
|
| 214 |
+
$
|
| 215 |
+
""",
|
| 216 |
+
re.IGNORECASE | re.VERBOSE,
|
| 217 |
+
)
|
| 218 |
+
_email_re = re.compile(r"^\S+@\w[\w.-]*\.\w+$")
|
| 219 |
+
|
| 220 |
+
|
| 221 |
+
def urlize(
|
| 222 |
+
text: str,
|
| 223 |
+
trim_url_limit: t.Optional[int] = None,
|
| 224 |
+
rel: t.Optional[str] = None,
|
| 225 |
+
target: t.Optional[str] = None,
|
| 226 |
+
extra_schemes: t.Optional[t.Iterable[str]] = None,
|
| 227 |
+
) -> str:
|
| 228 |
+
"""Convert URLs in text into clickable links.
|
| 229 |
+
|
| 230 |
+
This may not recognize links in some situations. Usually, a more
|
| 231 |
+
comprehensive formatter, such as a Markdown library, is a better
|
| 232 |
+
choice.
|
| 233 |
+
|
| 234 |
+
Works on ``http://``, ``https://``, ``www.``, ``mailto:``, and email
|
| 235 |
+
addresses. Links with trailing punctuation (periods, commas, closing
|
| 236 |
+
parentheses) and leading punctuation (opening parentheses) are
|
| 237 |
+
recognized excluding the punctuation. Email addresses that include
|
| 238 |
+
header fields are not recognized (for example,
|
| 239 |
+
``mailto:address@example.com?cc=copy@example.com``).
|
| 240 |
+
|
| 241 |
+
:param text: Original text containing URLs to link.
|
| 242 |
+
:param trim_url_limit: Shorten displayed URL values to this length.
|
| 243 |
+
:param target: Add the ``target`` attribute to links.
|
| 244 |
+
:param rel: Add the ``rel`` attribute to links.
|
| 245 |
+
:param extra_schemes: Recognize URLs that start with these schemes
|
| 246 |
+
in addition to the default behavior.
|
| 247 |
+
|
| 248 |
+
.. versionchanged:: 3.0
|
| 249 |
+
The ``extra_schemes`` parameter was added.
|
| 250 |
+
|
| 251 |
+
.. versionchanged:: 3.0
|
| 252 |
+
Generate ``https://`` links for URLs without a scheme.
|
| 253 |
+
|
| 254 |
+
.. versionchanged:: 3.0
|
| 255 |
+
The parsing rules were updated. Recognize email addresses with
|
| 256 |
+
or without the ``mailto:`` scheme. Validate IP addresses. Ignore
|
| 257 |
+
parentheses and brackets in more cases.
|
| 258 |
+
"""
|
| 259 |
+
if trim_url_limit is not None:
|
| 260 |
+
|
| 261 |
+
def trim_url(x: str) -> str:
|
| 262 |
+
if len(x) > trim_url_limit:
|
| 263 |
+
return f"{x[:trim_url_limit]}..."
|
| 264 |
+
|
| 265 |
+
return x
|
| 266 |
+
|
| 267 |
+
else:
|
| 268 |
+
|
| 269 |
+
def trim_url(x: str) -> str:
|
| 270 |
+
return x
|
| 271 |
+
|
| 272 |
+
words = re.split(r"(\s+)", str(markupsafe.escape(text)))
|
| 273 |
+
rel_attr = f' rel="{markupsafe.escape(rel)}"' if rel else ""
|
| 274 |
+
target_attr = f' target="{markupsafe.escape(target)}"' if target else ""
|
| 275 |
+
|
| 276 |
+
for i, word in enumerate(words):
|
| 277 |
+
head, middle, tail = "", word, ""
|
| 278 |
+
match = re.match(r"^([(<]|<)+", middle)
|
| 279 |
+
|
| 280 |
+
if match:
|
| 281 |
+
head = match.group()
|
| 282 |
+
middle = middle[match.end() :]
|
| 283 |
+
|
| 284 |
+
# Unlike lead, which is anchored to the start of the string,
|
| 285 |
+
# need to check that the string ends with any of the characters
|
| 286 |
+
# before trying to match all of them, to avoid backtracking.
|
| 287 |
+
if middle.endswith((")", ">", ".", ",", "\n", ">")):
|
| 288 |
+
match = re.search(r"([)>.,\n]|>)+$", middle)
|
| 289 |
+
|
| 290 |
+
if match:
|
| 291 |
+
tail = match.group()
|
| 292 |
+
middle = middle[: match.start()]
|
| 293 |
+
|
| 294 |
+
# Prefer balancing parentheses in URLs instead of ignoring a
|
| 295 |
+
# trailing character.
|
| 296 |
+
for start_char, end_char in ("(", ")"), ("<", ">"), ("<", ">"):
|
| 297 |
+
start_count = middle.count(start_char)
|
| 298 |
+
|
| 299 |
+
if start_count <= middle.count(end_char):
|
| 300 |
+
# Balanced, or lighter on the left
|
| 301 |
+
continue
|
| 302 |
+
|
| 303 |
+
# Move as many as possible from the tail to balance
|
| 304 |
+
for _ in range(min(start_count, tail.count(end_char))):
|
| 305 |
+
end_index = tail.index(end_char) + len(end_char)
|
| 306 |
+
# Move anything in the tail before the end char too
|
| 307 |
+
middle += tail[:end_index]
|
| 308 |
+
tail = tail[end_index:]
|
| 309 |
+
|
| 310 |
+
if _http_re.match(middle):
|
| 311 |
+
if middle.startswith("https://") or middle.startswith("http://"):
|
| 312 |
+
middle = (
|
| 313 |
+
f'<a href="{middle}"{rel_attr}{target_attr}>{trim_url(middle)}</a>'
|
| 314 |
+
)
|
| 315 |
+
else:
|
| 316 |
+
middle = (
|
| 317 |
+
f'<a href="https://{middle}"{rel_attr}{target_attr}>'
|
| 318 |
+
f"{trim_url(middle)}</a>"
|
| 319 |
+
)
|
| 320 |
+
|
| 321 |
+
elif middle.startswith("mailto:") and _email_re.match(middle[7:]):
|
| 322 |
+
middle = f'<a href="{middle}">{middle[7:]}</a>'
|
| 323 |
+
|
| 324 |
+
elif (
|
| 325 |
+
"@" in middle
|
| 326 |
+
and not middle.startswith("www.")
|
| 327 |
+
and ":" not in middle
|
| 328 |
+
and _email_re.match(middle)
|
| 329 |
+
):
|
| 330 |
+
middle = f'<a href="mailto:{middle}">{middle}</a>'
|
| 331 |
+
|
| 332 |
+
elif extra_schemes is not None:
|
| 333 |
+
for scheme in extra_schemes:
|
| 334 |
+
if middle != scheme and middle.startswith(scheme):
|
| 335 |
+
middle = f'<a href="{middle}"{rel_attr}{target_attr}>{middle}</a>'
|
| 336 |
+
|
| 337 |
+
words[i] = f"{head}{middle}{tail}"
|
| 338 |
+
|
| 339 |
+
return "".join(words)
|
| 340 |
+
|
| 341 |
+
|
| 342 |
+
def generate_lorem_ipsum(
|
| 343 |
+
n: int = 5, html: bool = True, min: int = 20, max: int = 100
|
| 344 |
+
) -> str:
|
| 345 |
+
"""Generate some lorem ipsum for the template."""
|
| 346 |
+
from .constants import LOREM_IPSUM_WORDS
|
| 347 |
+
|
| 348 |
+
words = LOREM_IPSUM_WORDS.split()
|
| 349 |
+
result = []
|
| 350 |
+
|
| 351 |
+
for _ in range(n):
|
| 352 |
+
next_capitalized = True
|
| 353 |
+
last_comma = last_fullstop = 0
|
| 354 |
+
word = None
|
| 355 |
+
last = None
|
| 356 |
+
p = []
|
| 357 |
+
|
| 358 |
+
# each paragraph contains out of 20 to 100 words.
|
| 359 |
+
for idx, _ in enumerate(range(randrange(min, max))):
|
| 360 |
+
while True:
|
| 361 |
+
word = choice(words)
|
| 362 |
+
if word != last:
|
| 363 |
+
last = word
|
| 364 |
+
break
|
| 365 |
+
if next_capitalized:
|
| 366 |
+
word = word.capitalize()
|
| 367 |
+
next_capitalized = False
|
| 368 |
+
# add commas
|
| 369 |
+
if idx - randrange(3, 8) > last_comma:
|
| 370 |
+
last_comma = idx
|
| 371 |
+
last_fullstop += 2
|
| 372 |
+
word += ","
|
| 373 |
+
# add end of sentences
|
| 374 |
+
if idx - randrange(10, 20) > last_fullstop:
|
| 375 |
+
last_comma = last_fullstop = idx
|
| 376 |
+
word += "."
|
| 377 |
+
next_capitalized = True
|
| 378 |
+
p.append(word)
|
| 379 |
+
|
| 380 |
+
# ensure that the paragraph ends with a dot.
|
| 381 |
+
p_str = " ".join(p)
|
| 382 |
+
|
| 383 |
+
if p_str.endswith(","):
|
| 384 |
+
p_str = p_str[:-1] + "."
|
| 385 |
+
elif not p_str.endswith("."):
|
| 386 |
+
p_str += "."
|
| 387 |
+
|
| 388 |
+
result.append(p_str)
|
| 389 |
+
|
| 390 |
+
if not html:
|
| 391 |
+
return "\n\n".join(result)
|
| 392 |
+
return markupsafe.Markup(
|
| 393 |
+
"\n".join(f"<p>{markupsafe.escape(x)}</p>" for x in result)
|
| 394 |
+
)
|
| 395 |
+
|
| 396 |
+
|
| 397 |
+
def url_quote(obj: t.Any, charset: str = "utf-8", for_qs: bool = False) -> str:
|
| 398 |
+
"""Quote a string for use in a URL using the given charset.
|
| 399 |
+
|
| 400 |
+
:param obj: String or bytes to quote. Other types are converted to
|
| 401 |
+
string then encoded to bytes using the given charset.
|
| 402 |
+
:param charset: Encode text to bytes using this charset.
|
| 403 |
+
:param for_qs: Quote "/" and use "+" for spaces.
|
| 404 |
+
"""
|
| 405 |
+
if not isinstance(obj, bytes):
|
| 406 |
+
if not isinstance(obj, str):
|
| 407 |
+
obj = str(obj)
|
| 408 |
+
|
| 409 |
+
obj = obj.encode(charset)
|
| 410 |
+
|
| 411 |
+
safe = b"" if for_qs else b"/"
|
| 412 |
+
rv = quote_from_bytes(obj, safe)
|
| 413 |
+
|
| 414 |
+
if for_qs:
|
| 415 |
+
rv = rv.replace("%20", "+")
|
| 416 |
+
|
| 417 |
+
return rv
|
| 418 |
+
|
| 419 |
+
|
| 420 |
+
@abc.MutableMapping.register
|
| 421 |
+
class LRUCache:
|
| 422 |
+
"""A simple LRU Cache implementation."""
|
| 423 |
+
|
| 424 |
+
# this is fast for small capacities (something below 1000) but doesn't
|
| 425 |
+
# scale. But as long as it's only used as storage for templates this
|
| 426 |
+
# won't do any harm.
|
| 427 |
+
|
| 428 |
+
def __init__(self, capacity: int) -> None:
|
| 429 |
+
self.capacity = capacity
|
| 430 |
+
self._mapping: t.Dict[t.Any, t.Any] = {}
|
| 431 |
+
self._queue: "te.Deque[t.Any]" = deque()
|
| 432 |
+
self._postinit()
|
| 433 |
+
|
| 434 |
+
def _postinit(self) -> None:
|
| 435 |
+
# alias all queue methods for faster lookup
|
| 436 |
+
self._popleft = self._queue.popleft
|
| 437 |
+
self._pop = self._queue.pop
|
| 438 |
+
self._remove = self._queue.remove
|
| 439 |
+
self._wlock = Lock()
|
| 440 |
+
self._append = self._queue.append
|
| 441 |
+
|
| 442 |
+
def __getstate__(self) -> t.Mapping[str, t.Any]:
|
| 443 |
+
return {
|
| 444 |
+
"capacity": self.capacity,
|
| 445 |
+
"_mapping": self._mapping,
|
| 446 |
+
"_queue": self._queue,
|
| 447 |
+
}
|
| 448 |
+
|
| 449 |
+
def __setstate__(self, d: t.Mapping[str, t.Any]) -> None:
|
| 450 |
+
self.__dict__.update(d)
|
| 451 |
+
self._postinit()
|
| 452 |
+
|
| 453 |
+
def __getnewargs__(self) -> t.Tuple:
|
| 454 |
+
return (self.capacity,)
|
| 455 |
+
|
| 456 |
+
def copy(self) -> "LRUCache":
|
| 457 |
+
"""Return a shallow copy of the instance."""
|
| 458 |
+
rv = self.__class__(self.capacity)
|
| 459 |
+
rv._mapping.update(self._mapping)
|
| 460 |
+
rv._queue.extend(self._queue)
|
| 461 |
+
return rv
|
| 462 |
+
|
| 463 |
+
def get(self, key: t.Any, default: t.Any = None) -> t.Any:
|
| 464 |
+
"""Return an item from the cache dict or `default`"""
|
| 465 |
+
try:
|
| 466 |
+
return self[key]
|
| 467 |
+
except KeyError:
|
| 468 |
+
return default
|
| 469 |
+
|
| 470 |
+
def setdefault(self, key: t.Any, default: t.Any = None) -> t.Any:
|
| 471 |
+
"""Set `default` if the key is not in the cache otherwise
|
| 472 |
+
leave unchanged. Return the value of this key.
|
| 473 |
+
"""
|
| 474 |
+
try:
|
| 475 |
+
return self[key]
|
| 476 |
+
except KeyError:
|
| 477 |
+
self[key] = default
|
| 478 |
+
return default
|
| 479 |
+
|
| 480 |
+
def clear(self) -> None:
|
| 481 |
+
"""Clear the cache."""
|
| 482 |
+
with self._wlock:
|
| 483 |
+
self._mapping.clear()
|
| 484 |
+
self._queue.clear()
|
| 485 |
+
|
| 486 |
+
def __contains__(self, key: t.Any) -> bool:
|
| 487 |
+
"""Check if a key exists in this cache."""
|
| 488 |
+
return key in self._mapping
|
| 489 |
+
|
| 490 |
+
def __len__(self) -> int:
|
| 491 |
+
"""Return the current size of the cache."""
|
| 492 |
+
return len(self._mapping)
|
| 493 |
+
|
| 494 |
+
def __repr__(self) -> str:
|
| 495 |
+
return f"<{type(self).__name__} {self._mapping!r}>"
|
| 496 |
+
|
| 497 |
+
def __getitem__(self, key: t.Any) -> t.Any:
|
| 498 |
+
"""Get an item from the cache. Moves the item up so that it has the
|
| 499 |
+
highest priority then.
|
| 500 |
+
|
| 501 |
+
Raise a `KeyError` if it does not exist.
|
| 502 |
+
"""
|
| 503 |
+
with self._wlock:
|
| 504 |
+
rv = self._mapping[key]
|
| 505 |
+
|
| 506 |
+
if self._queue[-1] != key:
|
| 507 |
+
try:
|
| 508 |
+
self._remove(key)
|
| 509 |
+
except ValueError:
|
| 510 |
+
# if something removed the key from the container
|
| 511 |
+
# when we read, ignore the ValueError that we would
|
| 512 |
+
# get otherwise.
|
| 513 |
+
pass
|
| 514 |
+
|
| 515 |
+
self._append(key)
|
| 516 |
+
|
| 517 |
+
return rv
|
| 518 |
+
|
| 519 |
+
def __setitem__(self, key: t.Any, value: t.Any) -> None:
|
| 520 |
+
"""Sets the value for an item. Moves the item up so that it
|
| 521 |
+
has the highest priority then.
|
| 522 |
+
"""
|
| 523 |
+
with self._wlock:
|
| 524 |
+
if key in self._mapping:
|
| 525 |
+
self._remove(key)
|
| 526 |
+
elif len(self._mapping) == self.capacity:
|
| 527 |
+
del self._mapping[self._popleft()]
|
| 528 |
+
|
| 529 |
+
self._append(key)
|
| 530 |
+
self._mapping[key] = value
|
| 531 |
+
|
| 532 |
+
def __delitem__(self, key: t.Any) -> None:
|
| 533 |
+
"""Remove an item from the cache dict.
|
| 534 |
+
Raise a `KeyError` if it does not exist.
|
| 535 |
+
"""
|
| 536 |
+
with self._wlock:
|
| 537 |
+
del self._mapping[key]
|
| 538 |
+
|
| 539 |
+
try:
|
| 540 |
+
self._remove(key)
|
| 541 |
+
except ValueError:
|
| 542 |
+
pass
|
| 543 |
+
|
| 544 |
+
def items(self) -> t.Iterable[t.Tuple[t.Any, t.Any]]:
|
| 545 |
+
"""Return a list of items."""
|
| 546 |
+
result = [(key, self._mapping[key]) for key in list(self._queue)]
|
| 547 |
+
result.reverse()
|
| 548 |
+
return result
|
| 549 |
+
|
| 550 |
+
def values(self) -> t.Iterable[t.Any]:
|
| 551 |
+
"""Return a list of all values."""
|
| 552 |
+
return [x[1] for x in self.items()]
|
| 553 |
+
|
| 554 |
+
def keys(self) -> t.Iterable[t.Any]:
|
| 555 |
+
"""Return a list of all keys ordered by most recent usage."""
|
| 556 |
+
return list(self)
|
| 557 |
+
|
| 558 |
+
def __iter__(self) -> t.Iterator[t.Any]:
|
| 559 |
+
return reversed(tuple(self._queue))
|
| 560 |
+
|
| 561 |
+
def __reversed__(self) -> t.Iterator[t.Any]:
|
| 562 |
+
"""Iterate over the keys in the cache dict, oldest items
|
| 563 |
+
coming first.
|
| 564 |
+
"""
|
| 565 |
+
return iter(tuple(self._queue))
|
| 566 |
+
|
| 567 |
+
__copy__ = copy
|
| 568 |
+
|
| 569 |
+
|
| 570 |
+
def select_autoescape(
|
| 571 |
+
enabled_extensions: t.Collection[str] = ("html", "htm", "xml"),
|
| 572 |
+
disabled_extensions: t.Collection[str] = (),
|
| 573 |
+
default_for_string: bool = True,
|
| 574 |
+
default: bool = False,
|
| 575 |
+
) -> t.Callable[[t.Optional[str]], bool]:
|
| 576 |
+
"""Intelligently sets the initial value of autoescaping based on the
|
| 577 |
+
filename of the template. This is the recommended way to configure
|
| 578 |
+
autoescaping if you do not want to write a custom function yourself.
|
| 579 |
+
|
| 580 |
+
If you want to enable it for all templates created from strings or
|
| 581 |
+
for all templates with `.html` and `.xml` extensions::
|
| 582 |
+
|
| 583 |
+
from jinja2 import Environment, select_autoescape
|
| 584 |
+
env = Environment(autoescape=select_autoescape(
|
| 585 |
+
enabled_extensions=('html', 'xml'),
|
| 586 |
+
default_for_string=True,
|
| 587 |
+
))
|
| 588 |
+
|
| 589 |
+
Example configuration to turn it on at all times except if the template
|
| 590 |
+
ends with `.txt`::
|
| 591 |
+
|
| 592 |
+
from jinja2 import Environment, select_autoescape
|
| 593 |
+
env = Environment(autoescape=select_autoescape(
|
| 594 |
+
disabled_extensions=('txt',),
|
| 595 |
+
default_for_string=True,
|
| 596 |
+
default=True,
|
| 597 |
+
))
|
| 598 |
+
|
| 599 |
+
The `enabled_extensions` is an iterable of all the extensions that
|
| 600 |
+
autoescaping should be enabled for. Likewise `disabled_extensions` is
|
| 601 |
+
a list of all templates it should be disabled for. If a template is
|
| 602 |
+
loaded from a string then the default from `default_for_string` is used.
|
| 603 |
+
If nothing matches then the initial value of autoescaping is set to the
|
| 604 |
+
value of `default`.
|
| 605 |
+
|
| 606 |
+
For security reasons this function operates case insensitive.
|
| 607 |
+
|
| 608 |
+
.. versionadded:: 2.9
|
| 609 |
+
"""
|
| 610 |
+
enabled_patterns = tuple(f".{x.lstrip('.').lower()}" for x in enabled_extensions)
|
| 611 |
+
disabled_patterns = tuple(f".{x.lstrip('.').lower()}" for x in disabled_extensions)
|
| 612 |
+
|
| 613 |
+
def autoescape(template_name: t.Optional[str]) -> bool:
|
| 614 |
+
if template_name is None:
|
| 615 |
+
return default_for_string
|
| 616 |
+
template_name = template_name.lower()
|
| 617 |
+
if template_name.endswith(enabled_patterns):
|
| 618 |
+
return True
|
| 619 |
+
if template_name.endswith(disabled_patterns):
|
| 620 |
+
return False
|
| 621 |
+
return default
|
| 622 |
+
|
| 623 |
+
return autoescape
|
| 624 |
+
|
| 625 |
+
|
| 626 |
+
def htmlsafe_json_dumps(
|
| 627 |
+
obj: t.Any, dumps: t.Optional[t.Callable[..., str]] = None, **kwargs: t.Any
|
| 628 |
+
) -> markupsafe.Markup:
|
| 629 |
+
"""Serialize an object to a string of JSON with :func:`json.dumps`,
|
| 630 |
+
then replace HTML-unsafe characters with Unicode escapes and mark
|
| 631 |
+
the result safe with :class:`~markupsafe.Markup`.
|
| 632 |
+
|
| 633 |
+
This is available in templates as the ``|tojson`` filter.
|
| 634 |
+
|
| 635 |
+
The following characters are escaped: ``<``, ``>``, ``&``, ``'``.
|
| 636 |
+
|
| 637 |
+
The returned string is safe to render in HTML documents and
|
| 638 |
+
``<script>`` tags. The exception is in HTML attributes that are
|
| 639 |
+
double quoted; either use single quotes or the ``|forceescape``
|
| 640 |
+
filter.
|
| 641 |
+
|
| 642 |
+
:param obj: The object to serialize to JSON.
|
| 643 |
+
:param dumps: The ``dumps`` function to use. Defaults to
|
| 644 |
+
``env.policies["json.dumps_function"]``, which defaults to
|
| 645 |
+
:func:`json.dumps`.
|
| 646 |
+
:param kwargs: Extra arguments to pass to ``dumps``. Merged onto
|
| 647 |
+
``env.policies["json.dumps_kwargs"]``.
|
| 648 |
+
|
| 649 |
+
.. versionchanged:: 3.0
|
| 650 |
+
The ``dumper`` parameter is renamed to ``dumps``.
|
| 651 |
+
|
| 652 |
+
.. versionadded:: 2.9
|
| 653 |
+
"""
|
| 654 |
+
if dumps is None:
|
| 655 |
+
dumps = json.dumps
|
| 656 |
+
|
| 657 |
+
return markupsafe.Markup(
|
| 658 |
+
dumps(obj, **kwargs)
|
| 659 |
+
.replace("<", "\\u003c")
|
| 660 |
+
.replace(">", "\\u003e")
|
| 661 |
+
.replace("&", "\\u0026")
|
| 662 |
+
.replace("'", "\\u0027")
|
| 663 |
+
)
|
| 664 |
+
|
| 665 |
+
|
| 666 |
+
class Cycler:
|
| 667 |
+
"""Cycle through values by yield them one at a time, then restarting
|
| 668 |
+
once the end is reached. Available as ``cycler`` in templates.
|
| 669 |
+
|
| 670 |
+
Similar to ``loop.cycle``, but can be used outside loops or across
|
| 671 |
+
multiple loops. For example, render a list of folders and files in a
|
| 672 |
+
list, alternating giving them "odd" and "even" classes.
|
| 673 |
+
|
| 674 |
+
.. code-block:: html+jinja
|
| 675 |
+
|
| 676 |
+
{% set row_class = cycler("odd", "even") %}
|
| 677 |
+
<ul class="browser">
|
| 678 |
+
{% for folder in folders %}
|
| 679 |
+
<li class="folder {{ row_class.next() }}">{{ folder }}
|
| 680 |
+
{% endfor %}
|
| 681 |
+
{% for file in files %}
|
| 682 |
+
<li class="file {{ row_class.next() }}">{{ file }}
|
| 683 |
+
{% endfor %}
|
| 684 |
+
</ul>
|
| 685 |
+
|
| 686 |
+
:param items: Each positional argument will be yielded in the order
|
| 687 |
+
given for each cycle.
|
| 688 |
+
|
| 689 |
+
.. versionadded:: 2.1
|
| 690 |
+
"""
|
| 691 |
+
|
| 692 |
+
def __init__(self, *items: t.Any) -> None:
|
| 693 |
+
if not items:
|
| 694 |
+
raise RuntimeError("at least one item has to be provided")
|
| 695 |
+
self.items = items
|
| 696 |
+
self.pos = 0
|
| 697 |
+
|
| 698 |
+
def reset(self) -> None:
|
| 699 |
+
"""Resets the current item to the first item."""
|
| 700 |
+
self.pos = 0
|
| 701 |
+
|
| 702 |
+
@property
|
| 703 |
+
def current(self) -> t.Any:
|
| 704 |
+
"""Return the current item. Equivalent to the item that will be
|
| 705 |
+
returned next time :meth:`next` is called.
|
| 706 |
+
"""
|
| 707 |
+
return self.items[self.pos]
|
| 708 |
+
|
| 709 |
+
def next(self) -> t.Any:
|
| 710 |
+
"""Return the current item, then advance :attr:`current` to the
|
| 711 |
+
next item.
|
| 712 |
+
"""
|
| 713 |
+
rv = self.current
|
| 714 |
+
self.pos = (self.pos + 1) % len(self.items)
|
| 715 |
+
return rv
|
| 716 |
+
|
| 717 |
+
__next__ = next
|
| 718 |
+
|
| 719 |
+
|
| 720 |
+
class Joiner:
|
| 721 |
+
"""A joining helper for templates."""
|
| 722 |
+
|
| 723 |
+
def __init__(self, sep: str = ", ") -> None:
|
| 724 |
+
self.sep = sep
|
| 725 |
+
self.used = False
|
| 726 |
+
|
| 727 |
+
def __call__(self) -> str:
|
| 728 |
+
if not self.used:
|
| 729 |
+
self.used = True
|
| 730 |
+
return ""
|
| 731 |
+
return self.sep
|
| 732 |
+
|
| 733 |
+
|
| 734 |
+
class Namespace:
|
| 735 |
+
"""A namespace object that can hold arbitrary attributes. It may be
|
| 736 |
+
initialized from a dictionary or with keyword arguments."""
|
| 737 |
+
|
| 738 |
+
def __init__(*args: t.Any, **kwargs: t.Any) -> None: # noqa: B902
|
| 739 |
+
self, args = args[0], args[1:]
|
| 740 |
+
self.__attrs = dict(*args, **kwargs)
|
| 741 |
+
|
| 742 |
+
def __getattribute__(self, name: str) -> t.Any:
|
| 743 |
+
# __class__ is needed for the awaitable check in async mode
|
| 744 |
+
if name in {"_Namespace__attrs", "__class__"}:
|
| 745 |
+
return object.__getattribute__(self, name)
|
| 746 |
+
try:
|
| 747 |
+
return self.__attrs[name]
|
| 748 |
+
except KeyError:
|
| 749 |
+
raise AttributeError(name) from None
|
| 750 |
+
|
| 751 |
+
def __setitem__(self, name: str, value: t.Any) -> None:
|
| 752 |
+
self.__attrs[name] = value
|
| 753 |
+
|
| 754 |
+
def __repr__(self) -> str:
|
| 755 |
+
return f"<Namespace {self.__attrs!r}>"
|
tuning-competition-baseline/.venv/lib/python3.11/site-packages/nvidia_cufft_cu11-10.9.0.58.dist-info/License.txt
ADDED
|
@@ -0,0 +1,1568 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
End User License Agreement
|
| 2 |
+
--------------------------
|
| 3 |
+
|
| 4 |
+
|
| 5 |
+
Preface
|
| 6 |
+
-------
|
| 7 |
+
|
| 8 |
+
The Software License Agreement in Chapter 1 and the Supplement
|
| 9 |
+
in Chapter 2 contain license terms and conditions that govern
|
| 10 |
+
the use of NVIDIA software. By accepting this agreement, you
|
| 11 |
+
agree to comply with all the terms and conditions applicable
|
| 12 |
+
to the product(s) included herein.
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
NVIDIA Driver
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
Description
|
| 19 |
+
|
| 20 |
+
This package contains the operating system driver and
|
| 21 |
+
fundamental system software components for NVIDIA GPUs.
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
NVIDIA CUDA Toolkit
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
Description
|
| 28 |
+
|
| 29 |
+
The NVIDIA CUDA Toolkit provides command-line and graphical
|
| 30 |
+
tools for building, debugging and optimizing the performance
|
| 31 |
+
of applications accelerated by NVIDIA GPUs, runtime and math
|
| 32 |
+
libraries, and documentation including programming guides,
|
| 33 |
+
user manuals, and API references.
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
Default Install Location of CUDA Toolkit
|
| 37 |
+
|
| 38 |
+
Windows platform:
|
| 39 |
+
|
| 40 |
+
%ProgramFiles%\NVIDIA GPU Computing Toolkit\CUDA\v#.#
|
| 41 |
+
|
| 42 |
+
Linux platform:
|
| 43 |
+
|
| 44 |
+
/usr/local/cuda-#.#
|
| 45 |
+
|
| 46 |
+
Mac platform:
|
| 47 |
+
|
| 48 |
+
/Developer/NVIDIA/CUDA-#.#
|
| 49 |
+
|
| 50 |
+
|
| 51 |
+
NVIDIA CUDA Samples
|
| 52 |
+
|
| 53 |
+
|
| 54 |
+
Description
|
| 55 |
+
|
| 56 |
+
This package includes over 100+ CUDA examples that demonstrate
|
| 57 |
+
various CUDA programming principles, and efficient CUDA
|
| 58 |
+
implementation of algorithms in specific application domains.
|
| 59 |
+
|
| 60 |
+
|
| 61 |
+
Default Install Location of CUDA Samples
|
| 62 |
+
|
| 63 |
+
Windows platform:
|
| 64 |
+
|
| 65 |
+
%ProgramData%\NVIDIA Corporation\CUDA Samples\v#.#
|
| 66 |
+
|
| 67 |
+
Linux platform:
|
| 68 |
+
|
| 69 |
+
/usr/local/cuda-#.#/samples
|
| 70 |
+
|
| 71 |
+
and
|
| 72 |
+
|
| 73 |
+
$HOME/NVIDIA_CUDA-#.#_Samples
|
| 74 |
+
|
| 75 |
+
Mac platform:
|
| 76 |
+
|
| 77 |
+
/Developer/NVIDIA/CUDA-#.#/samples
|
| 78 |
+
|
| 79 |
+
|
| 80 |
+
NVIDIA Nsight Visual Studio Edition (Windows only)
|
| 81 |
+
|
| 82 |
+
|
| 83 |
+
Description
|
| 84 |
+
|
| 85 |
+
NVIDIA Nsight Development Platform, Visual Studio Edition is a
|
| 86 |
+
development environment integrated into Microsoft Visual
|
| 87 |
+
Studio that provides tools for debugging, profiling, analyzing
|
| 88 |
+
and optimizing your GPU computing and graphics applications.
|
| 89 |
+
|
| 90 |
+
|
| 91 |
+
Default Install Location of Nsight Visual Studio Edition
|
| 92 |
+
|
| 93 |
+
Windows platform:
|
| 94 |
+
|
| 95 |
+
%ProgramFiles(x86)%\NVIDIA Corporation\Nsight Visual Studio Edition #.#
|
| 96 |
+
|
| 97 |
+
|
| 98 |
+
1. License Agreement for NVIDIA Software Development Kits
|
| 99 |
+
---------------------------------------------------------
|
| 100 |
+
|
| 101 |
+
|
| 102 |
+
Release Date: July 26, 2018
|
| 103 |
+
---------------------------
|
| 104 |
+
|
| 105 |
+
|
| 106 |
+
Important NoticeRead before downloading, installing,
|
| 107 |
+
copying or using the licensed software:
|
| 108 |
+
-------------------------------------------------------
|
| 109 |
+
|
| 110 |
+
This license agreement, including exhibits attached
|
| 111 |
+
("Agreement”) is a legal agreement between you and NVIDIA
|
| 112 |
+
Corporation ("NVIDIA") and governs your use of a NVIDIA
|
| 113 |
+
software development kit (“SDK”).
|
| 114 |
+
|
| 115 |
+
Each SDK has its own set of software and materials, but here
|
| 116 |
+
is a description of the types of items that may be included in
|
| 117 |
+
a SDK: source code, header files, APIs, data sets and assets
|
| 118 |
+
(examples include images, textures, models, scenes, videos,
|
| 119 |
+
native API input/output files), binary software, sample code,
|
| 120 |
+
libraries, utility programs, programming code and
|
| 121 |
+
documentation.
|
| 122 |
+
|
| 123 |
+
This Agreement can be accepted only by an adult of legal age
|
| 124 |
+
of majority in the country in which the SDK is used.
|
| 125 |
+
|
| 126 |
+
If you are entering into this Agreement on behalf of a company
|
| 127 |
+
or other legal entity, you represent that you have the legal
|
| 128 |
+
authority to bind the entity to this Agreement, in which case
|
| 129 |
+
“you” will mean the entity you represent.
|
| 130 |
+
|
| 131 |
+
If you don’t have the required age or authority to accept
|
| 132 |
+
this Agreement, or if you don’t accept all the terms and
|
| 133 |
+
conditions of this Agreement, do not download, install or use
|
| 134 |
+
the SDK.
|
| 135 |
+
|
| 136 |
+
You agree to use the SDK only for purposes that are permitted
|
| 137 |
+
by (a) this Agreement, and (b) any applicable law, regulation
|
| 138 |
+
or generally accepted practices or guidelines in the relevant
|
| 139 |
+
jurisdictions.
|
| 140 |
+
|
| 141 |
+
|
| 142 |
+
1.1. License
|
| 143 |
+
|
| 144 |
+
|
| 145 |
+
1.1.1. License Grant
|
| 146 |
+
|
| 147 |
+
Subject to the terms of this Agreement, NVIDIA hereby grants
|
| 148 |
+
you a non-exclusive, non-transferable license, without the
|
| 149 |
+
right to sublicense (except as expressly provided in this
|
| 150 |
+
Agreement) to:
|
| 151 |
+
|
| 152 |
+
1. Install and use the SDK,
|
| 153 |
+
|
| 154 |
+
2. Modify and create derivative works of sample source code
|
| 155 |
+
delivered in the SDK, and
|
| 156 |
+
|
| 157 |
+
3. Distribute those portions of the SDK that are identified
|
| 158 |
+
in this Agreement as distributable, as incorporated in
|
| 159 |
+
object code format into a software application that meets
|
| 160 |
+
the distribution requirements indicated in this Agreement.
|
| 161 |
+
|
| 162 |
+
|
| 163 |
+
1.1.2. Distribution Requirements
|
| 164 |
+
|
| 165 |
+
These are the distribution requirements for you to exercise
|
| 166 |
+
the distribution grant:
|
| 167 |
+
|
| 168 |
+
1. Your application must have material additional
|
| 169 |
+
functionality, beyond the included portions of the SDK.
|
| 170 |
+
|
| 171 |
+
2. The distributable portions of the SDK shall only be
|
| 172 |
+
accessed by your application.
|
| 173 |
+
|
| 174 |
+
3. The following notice shall be included in modifications
|
| 175 |
+
and derivative works of sample source code distributed:
|
| 176 |
+
“This software contains source code provided by NVIDIA
|
| 177 |
+
Corporation.”
|
| 178 |
+
|
| 179 |
+
4. Unless a developer tool is identified in this Agreement
|
| 180 |
+
as distributable, it is delivered for your internal use
|
| 181 |
+
only.
|
| 182 |
+
|
| 183 |
+
5. The terms under which you distribute your application
|
| 184 |
+
must be consistent with the terms of this Agreement,
|
| 185 |
+
including (without limitation) terms relating to the
|
| 186 |
+
license grant and license restrictions and protection of
|
| 187 |
+
NVIDIA’s intellectual property rights. Additionally, you
|
| 188 |
+
agree that you will protect the privacy, security and
|
| 189 |
+
legal rights of your application users.
|
| 190 |
+
|
| 191 |
+
6. You agree to notify NVIDIA in writing of any known or
|
| 192 |
+
suspected distribution or use of the SDK not in compliance
|
| 193 |
+
with the requirements of this Agreement, and to enforce
|
| 194 |
+
the terms of your agreements with respect to distributed
|
| 195 |
+
SDK.
|
| 196 |
+
|
| 197 |
+
|
| 198 |
+
1.1.3. Authorized Users
|
| 199 |
+
|
| 200 |
+
You may allow employees and contractors of your entity or of
|
| 201 |
+
your subsidiary(ies) to access and use the SDK from your
|
| 202 |
+
secure network to perform work on your behalf.
|
| 203 |
+
|
| 204 |
+
If you are an academic institution you may allow users
|
| 205 |
+
enrolled or employed by the academic institution to access and
|
| 206 |
+
use the SDK from your secure network.
|
| 207 |
+
|
| 208 |
+
You are responsible for the compliance with the terms of this
|
| 209 |
+
Agreement by your authorized users. If you become aware that
|
| 210 |
+
your authorized users didn’t follow the terms of this
|
| 211 |
+
Agreement, you agree to take reasonable steps to resolve the
|
| 212 |
+
non-compliance and prevent new occurrences.
|
| 213 |
+
|
| 214 |
+
|
| 215 |
+
1.1.4. Pre-Release SDK
|
| 216 |
+
|
| 217 |
+
The SDK versions identified as alpha, beta, preview or
|
| 218 |
+
otherwise as pre-release, may not be fully functional, may
|
| 219 |
+
contain errors or design flaws, and may have reduced or
|
| 220 |
+
different security, privacy, accessibility, availability, and
|
| 221 |
+
reliability standards relative to commercial versions of
|
| 222 |
+
NVIDIA software and materials. Use of a pre-release SDK may
|
| 223 |
+
result in unexpected results, loss of data, project delays or
|
| 224 |
+
other unpredictable damage or loss.
|
| 225 |
+
|
| 226 |
+
You may use a pre-release SDK at your own risk, understanding
|
| 227 |
+
that pre-release SDKs are not intended for use in production
|
| 228 |
+
or business-critical systems.
|
| 229 |
+
|
| 230 |
+
NVIDIA may choose not to make available a commercial version
|
| 231 |
+
of any pre-release SDK. NVIDIA may also choose to abandon
|
| 232 |
+
development and terminate the availability of a pre-release
|
| 233 |
+
SDK at any time without liability.
|
| 234 |
+
|
| 235 |
+
|
| 236 |
+
1.1.5. Updates
|
| 237 |
+
|
| 238 |
+
NVIDIA may, at its option, make available patches, workarounds
|
| 239 |
+
or other updates to this SDK. Unless the updates are provided
|
| 240 |
+
with their separate governing terms, they are deemed part of
|
| 241 |
+
the SDK licensed to you as provided in this Agreement. You
|
| 242 |
+
agree that the form and content of the SDK that NVIDIA
|
| 243 |
+
provides may change without prior notice to you. While NVIDIA
|
| 244 |
+
generally maintains compatibility between versions, NVIDIA may
|
| 245 |
+
in some cases make changes that introduce incompatibilities in
|
| 246 |
+
future versions of the SDK.
|
| 247 |
+
|
| 248 |
+
|
| 249 |
+
1.1.6. Third Party Licenses
|
| 250 |
+
|
| 251 |
+
The SDK may come bundled with, or otherwise include or be
|
| 252 |
+
distributed with, third party software licensed by a NVIDIA
|
| 253 |
+
supplier and/or open source software provided under an open
|
| 254 |
+
source license. Use of third party software is subject to the
|
| 255 |
+
third-party license terms, or in the absence of third party
|
| 256 |
+
terms, the terms of this Agreement. Copyright to third party
|
| 257 |
+
software is held by the copyright holders indicated in the
|
| 258 |
+
third-party software or license.
|
| 259 |
+
|
| 260 |
+
|
| 261 |
+
1.1.7. Reservation of Rights
|
| 262 |
+
|
| 263 |
+
NVIDIA reserves all rights, title, and interest in and to the
|
| 264 |
+
SDK, not expressly granted to you under this Agreement.
|
| 265 |
+
|
| 266 |
+
|
| 267 |
+
1.2. Limitations
|
| 268 |
+
|
| 269 |
+
The following license limitations apply to your use of the
|
| 270 |
+
SDK:
|
| 271 |
+
|
| 272 |
+
1. You may not reverse engineer, decompile or disassemble,
|
| 273 |
+
or remove copyright or other proprietary notices from any
|
| 274 |
+
portion of the SDK or copies of the SDK.
|
| 275 |
+
|
| 276 |
+
2. Except as expressly provided in this Agreement, you may
|
| 277 |
+
not copy, sell, rent, sublicense, transfer, distribute,
|
| 278 |
+
modify, or create derivative works of any portion of the
|
| 279 |
+
SDK. For clarity, you may not distribute or sublicense the
|
| 280 |
+
SDK as a stand-alone product.
|
| 281 |
+
|
| 282 |
+
3. Unless you have an agreement with NVIDIA for this
|
| 283 |
+
purpose, you may not indicate that an application created
|
| 284 |
+
with the SDK is sponsored or endorsed by NVIDIA.
|
| 285 |
+
|
| 286 |
+
4. You may not bypass, disable, or circumvent any
|
| 287 |
+
encryption, security, digital rights management or
|
| 288 |
+
authentication mechanism in the SDK.
|
| 289 |
+
|
| 290 |
+
5. You may not use the SDK in any manner that would cause it
|
| 291 |
+
to become subject to an open source software license. As
|
| 292 |
+
examples, licenses that require as a condition of use,
|
| 293 |
+
modification, and/or distribution that the SDK be:
|
| 294 |
+
|
| 295 |
+
a. Disclosed or distributed in source code form;
|
| 296 |
+
|
| 297 |
+
b. Licensed for the purpose of making derivative works;
|
| 298 |
+
or
|
| 299 |
+
|
| 300 |
+
c. Redistributable at no charge.
|
| 301 |
+
|
| 302 |
+
6. Unless you have an agreement with NVIDIA for this
|
| 303 |
+
purpose, you may not use the SDK with any system or
|
| 304 |
+
application where the use or failure of the system or
|
| 305 |
+
application can reasonably be expected to threaten or
|
| 306 |
+
result in personal injury, death, or catastrophic loss.
|
| 307 |
+
Examples include use in avionics, navigation, military,
|
| 308 |
+
medical, life support or other life critical applications.
|
| 309 |
+
NVIDIA does not design, test or manufacture the SDK for
|
| 310 |
+
these critical uses and NVIDIA shall not be liable to you
|
| 311 |
+
or any third party, in whole or in part, for any claims or
|
| 312 |
+
damages arising from such uses.
|
| 313 |
+
|
| 314 |
+
7. You agree to defend, indemnify and hold harmless NVIDIA
|
| 315 |
+
and its affiliates, and their respective employees,
|
| 316 |
+
contractors, agents, officers and directors, from and
|
| 317 |
+
against any and all claims, damages, obligations, losses,
|
| 318 |
+
liabilities, costs or debt, fines, restitutions and
|
| 319 |
+
expenses (including but not limited to attorney’s fees
|
| 320 |
+
and costs incident to establishing the right of
|
| 321 |
+
indemnification) arising out of or related to your use of
|
| 322 |
+
the SDK outside of the scope of this Agreement, or not in
|
| 323 |
+
compliance with its terms.
|
| 324 |
+
|
| 325 |
+
|
| 326 |
+
1.3. Ownership
|
| 327 |
+
|
| 328 |
+
1. NVIDIA or its licensors hold all rights, title and
|
| 329 |
+
interest in and to the SDK and its modifications and
|
| 330 |
+
derivative works, including their respective intellectual
|
| 331 |
+
property rights, subject to your rights described in this
|
| 332 |
+
section. This SDK may include software and materials from
|
| 333 |
+
NVIDIA’s licensors, and these licensors are intended
|
| 334 |
+
third party beneficiaries that may enforce this Agreement
|
| 335 |
+
with respect to their intellectual property rights.
|
| 336 |
+
|
| 337 |
+
2. You hold all rights, title and interest in and to your
|
| 338 |
+
applications and your derivative works of the sample
|
| 339 |
+
source code delivered in the SDK, including their
|
| 340 |
+
respective intellectual property rights, subject to
|
| 341 |
+
NVIDIA’s rights described in this section.
|
| 342 |
+
|
| 343 |
+
3. You may, but don’t have to, provide to NVIDIA
|
| 344 |
+
suggestions, feature requests or other feedback regarding
|
| 345 |
+
the SDK, including possible enhancements or modifications
|
| 346 |
+
to the SDK. For any feedback that you voluntarily provide,
|
| 347 |
+
you hereby grant NVIDIA and its affiliates a perpetual,
|
| 348 |
+
non-exclusive, worldwide, irrevocable license to use,
|
| 349 |
+
reproduce, modify, license, sublicense (through multiple
|
| 350 |
+
tiers of sublicensees), and distribute (through multiple
|
| 351 |
+
tiers of distributors) it without the payment of any
|
| 352 |
+
royalties or fees to you. NVIDIA will use feedback at its
|
| 353 |
+
choice. NVIDIA is constantly looking for ways to improve
|
| 354 |
+
its products, so you may send feedback to NVIDIA through
|
| 355 |
+
the developer portal at https://developer.nvidia.com.
|
| 356 |
+
|
| 357 |
+
|
| 358 |
+
1.4. No Warranties
|
| 359 |
+
|
| 360 |
+
THE SDK IS PROVIDED BY NVIDIA “AS IS” AND “WITH ALL
|
| 361 |
+
FAULTS.” TO THE MAXIMUM EXTENT PERMITTED BY LAW, NVIDIA AND
|
| 362 |
+
ITS AFFILIATES EXPRESSLY DISCLAIM ALL WARRANTIES OF ANY KIND
|
| 363 |
+
OR NATURE, WHETHER EXPRESS, IMPLIED OR STATUTORY, INCLUDING,
|
| 364 |
+
BUT NOT LIMITED TO, ANY WARRANTIES OF MERCHANTABILITY, FITNESS
|
| 365 |
+
FOR A PARTICULAR PURPOSE, TITLE, NON-INFRINGEMENT, OR THE
|
| 366 |
+
ABSENCE OF ANY DEFECTS THEREIN, WHETHER LATENT OR PATENT. NO
|
| 367 |
+
WARRANTY IS MADE ON THE BASIS OF TRADE USAGE, COURSE OF
|
| 368 |
+
DEALING OR COURSE OF TRADE.
|
| 369 |
+
|
| 370 |
+
|
| 371 |
+
1.5. Limitation of Liability
|
| 372 |
+
|
| 373 |
+
TO THE MAXIMUM EXTENT PERMITTED BY LAW, NVIDIA AND ITS
|
| 374 |
+
AFFILIATES SHALL NOT BE LIABLE FOR ANY SPECIAL, INCIDENTAL,
|
| 375 |
+
PUNITIVE OR CONSEQUENTIAL DAMAGES, OR ANY LOST PROFITS, LOSS
|
| 376 |
+
OF USE, LOSS OF DATA OR LOSS OF GOODWILL, OR THE COSTS OF
|
| 377 |
+
PROCURING SUBSTITUTE PRODUCTS, ARISING OUT OF OR IN CONNECTION
|
| 378 |
+
WITH THIS AGREEMENT OR THE USE OR PERFORMANCE OF THE SDK,
|
| 379 |
+
WHETHER SUCH LIABILITY ARISES FROM ANY CLAIM BASED UPON BREACH
|
| 380 |
+
OF CONTRACT, BREACH OF WARRANTY, TORT (INCLUDING NEGLIGENCE),
|
| 381 |
+
PRODUCT LIABILITY OR ANY OTHER CAUSE OF ACTION OR THEORY OF
|
| 382 |
+
LIABILITY. IN NO EVENT WILL NVIDIA’S AND ITS AFFILIATES
|
| 383 |
+
TOTAL CUMULATIVE LIABILITY UNDER OR ARISING OUT OF THIS
|
| 384 |
+
AGREEMENT EXCEED US$10.00. THE NATURE OF THE LIABILITY OR THE
|
| 385 |
+
NUMBER OF CLAIMS OR SUITS SHALL NOT ENLARGE OR EXTEND THIS
|
| 386 |
+
LIMIT.
|
| 387 |
+
|
| 388 |
+
These exclusions and limitations of liability shall apply
|
| 389 |
+
regardless if NVIDIA or its affiliates have been advised of
|
| 390 |
+
the possibility of such damages, and regardless of whether a
|
| 391 |
+
remedy fails its essential purpose. These exclusions and
|
| 392 |
+
limitations of liability form an essential basis of the
|
| 393 |
+
bargain between the parties, and, absent any of these
|
| 394 |
+
exclusions or limitations of liability, the provisions of this
|
| 395 |
+
Agreement, including, without limitation, the economic terms,
|
| 396 |
+
would be substantially different.
|
| 397 |
+
|
| 398 |
+
|
| 399 |
+
1.6. Termination
|
| 400 |
+
|
| 401 |
+
1. This Agreement will continue to apply until terminated by
|
| 402 |
+
either you or NVIDIA as described below.
|
| 403 |
+
|
| 404 |
+
2. If you want to terminate this Agreement, you may do so by
|
| 405 |
+
stopping to use the SDK.
|
| 406 |
+
|
| 407 |
+
3. NVIDIA may, at any time, terminate this Agreement if:
|
| 408 |
+
|
| 409 |
+
a. (i) you fail to comply with any term of this
|
| 410 |
+
Agreement and the non-compliance is not fixed within
|
| 411 |
+
thirty (30) days following notice from NVIDIA (or
|
| 412 |
+
immediately if you violate NVIDIA’s intellectual
|
| 413 |
+
property rights);
|
| 414 |
+
|
| 415 |
+
b. (ii) you commence or participate in any legal
|
| 416 |
+
proceeding against NVIDIA with respect to the SDK; or
|
| 417 |
+
|
| 418 |
+
c. (iii) NVIDIA decides to no longer provide the SDK in
|
| 419 |
+
a country or, in NVIDIA’s sole discretion, the
|
| 420 |
+
continued use of it is no longer commercially viable.
|
| 421 |
+
|
| 422 |
+
4. Upon any termination of this Agreement, you agree to
|
| 423 |
+
promptly discontinue use of the SDK and destroy all copies
|
| 424 |
+
in your possession or control. Your prior distributions in
|
| 425 |
+
accordance with this Agreement are not affected by the
|
| 426 |
+
termination of this Agreement. Upon written request, you
|
| 427 |
+
will certify in writing that you have complied with your
|
| 428 |
+
commitments under this section. Upon any termination of
|
| 429 |
+
this Agreement all provisions survive except for the
|
| 430 |
+
license grant provisions.
|
| 431 |
+
|
| 432 |
+
|
| 433 |
+
1.7. General
|
| 434 |
+
|
| 435 |
+
If you wish to assign this Agreement or your rights and
|
| 436 |
+
obligations, including by merger, consolidation, dissolution
|
| 437 |
+
or operation of law, contact NVIDIA to ask for permission. Any
|
| 438 |
+
attempted assignment not approved by NVIDIA in writing shall
|
| 439 |
+
be void and of no effect. NVIDIA may assign, delegate or
|
| 440 |
+
transfer this Agreement and its rights and obligations, and if
|
| 441 |
+
to a non-affiliate you will be notified.
|
| 442 |
+
|
| 443 |
+
You agree to cooperate with NVIDIA and provide reasonably
|
| 444 |
+
requested information to verify your compliance with this
|
| 445 |
+
Agreement.
|
| 446 |
+
|
| 447 |
+
This Agreement will be governed in all respects by the laws of
|
| 448 |
+
the United States and of the State of Delaware as those laws
|
| 449 |
+
are applied to contracts entered into and performed entirely
|
| 450 |
+
within Delaware by Delaware residents, without regard to the
|
| 451 |
+
conflicts of laws principles. The United Nations Convention on
|
| 452 |
+
Contracts for the International Sale of Goods is specifically
|
| 453 |
+
disclaimed. You agree to all terms of this Agreement in the
|
| 454 |
+
English language.
|
| 455 |
+
|
| 456 |
+
The state or federal courts residing in Santa Clara County,
|
| 457 |
+
California shall have exclusive jurisdiction over any dispute
|
| 458 |
+
or claim arising out of this Agreement. Notwithstanding this,
|
| 459 |
+
you agree that NVIDIA shall still be allowed to apply for
|
| 460 |
+
injunctive remedies or an equivalent type of urgent legal
|
| 461 |
+
relief in any jurisdiction.
|
| 462 |
+
|
| 463 |
+
If any court of competent jurisdiction determines that any
|
| 464 |
+
provision of this Agreement is illegal, invalid or
|
| 465 |
+
unenforceable, such provision will be construed as limited to
|
| 466 |
+
the extent necessary to be consistent with and fully
|
| 467 |
+
enforceable under the law and the remaining provisions will
|
| 468 |
+
remain in full force and effect. Unless otherwise specified,
|
| 469 |
+
remedies are cumulative.
|
| 470 |
+
|
| 471 |
+
Each party acknowledges and agrees that the other is an
|
| 472 |
+
independent contractor in the performance of this Agreement.
|
| 473 |
+
|
| 474 |
+
The SDK has been developed entirely at private expense and is
|
| 475 |
+
“commercial items” consisting of “commercial computer
|
| 476 |
+
software” and “commercial computer software
|
| 477 |
+
documentation” provided with RESTRICTED RIGHTS. Use,
|
| 478 |
+
duplication or disclosure by the U.S. Government or a U.S.
|
| 479 |
+
Government subcontractor is subject to the restrictions in
|
| 480 |
+
this Agreement pursuant to DFARS 227.7202-3(a) or as set forth
|
| 481 |
+
in subparagraphs (c)(1) and (2) of the Commercial Computer
|
| 482 |
+
Software - Restricted Rights clause at FAR 52.227-19, as
|
| 483 |
+
applicable. Contractor/manufacturer is NVIDIA, 2788 San Tomas
|
| 484 |
+
Expressway, Santa Clara, CA 95051.
|
| 485 |
+
|
| 486 |
+
The SDK is subject to United States export laws and
|
| 487 |
+
regulations. You agree that you will not ship, transfer or
|
| 488 |
+
export the SDK into any country, or use the SDK in any manner,
|
| 489 |
+
prohibited by the United States Bureau of Industry and
|
| 490 |
+
Security or economic sanctions regulations administered by the
|
| 491 |
+
U.S. Department of Treasury’s Office of Foreign Assets
|
| 492 |
+
Control (OFAC), or any applicable export laws, restrictions or
|
| 493 |
+
regulations. These laws include restrictions on destinations,
|
| 494 |
+
end users and end use. By accepting this Agreement, you
|
| 495 |
+
confirm that you are not a resident or citizen of any country
|
| 496 |
+
currently embargoed by the U.S. and that you are not otherwise
|
| 497 |
+
prohibited from receiving the SDK.
|
| 498 |
+
|
| 499 |
+
Any notice delivered by NVIDIA to you under this Agreement
|
| 500 |
+
will be delivered via mail, email or fax. You agree that any
|
| 501 |
+
notices that NVIDIA sends you electronically will satisfy any
|
| 502 |
+
legal communication requirements. Please direct your legal
|
| 503 |
+
notices or other correspondence to NVIDIA Corporation, 2788
|
| 504 |
+
San Tomas Expressway, Santa Clara, California 95051, United
|
| 505 |
+
States of America, Attention: Legal Department.
|
| 506 |
+
|
| 507 |
+
This Agreement and any exhibits incorporated into this
|
| 508 |
+
Agreement constitute the entire agreement of the parties with
|
| 509 |
+
respect to the subject matter of this Agreement and supersede
|
| 510 |
+
all prior negotiations or documentation exchanged between the
|
| 511 |
+
parties relating to this SDK license. Any additional and/or
|
| 512 |
+
conflicting terms on documents issued by you are null, void,
|
| 513 |
+
and invalid. Any amendment or waiver under this Agreement
|
| 514 |
+
shall be in writing and signed by representatives of both
|
| 515 |
+
parties.
|
| 516 |
+
|
| 517 |
+
|
| 518 |
+
2. CUDA Toolkit Supplement to Software License Agreement for
|
| 519 |
+
NVIDIA Software Development Kits
|
| 520 |
+
------------------------------------------------------------
|
| 521 |
+
|
| 522 |
+
|
| 523 |
+
Release date: August 16, 2018
|
| 524 |
+
-----------------------------
|
| 525 |
+
|
| 526 |
+
The terms in this supplement govern your use of the NVIDIA
|
| 527 |
+
CUDA Toolkit SDK under the terms of your license agreement
|
| 528 |
+
(“Agreement”) as modified by this supplement. Capitalized
|
| 529 |
+
terms used but not defined below have the meaning assigned to
|
| 530 |
+
them in the Agreement.
|
| 531 |
+
|
| 532 |
+
This supplement is an exhibit to the Agreement and is
|
| 533 |
+
incorporated as an integral part of the Agreement. In the
|
| 534 |
+
event of conflict between the terms in this supplement and the
|
| 535 |
+
terms in the Agreement, the terms in this supplement govern.
|
| 536 |
+
|
| 537 |
+
|
| 538 |
+
2.1. License Scope
|
| 539 |
+
|
| 540 |
+
The SDK is licensed for you to develop applications only for
|
| 541 |
+
use in systems with NVIDIA GPUs.
|
| 542 |
+
|
| 543 |
+
|
| 544 |
+
2.2. Distribution
|
| 545 |
+
|
| 546 |
+
The portions of the SDK that are distributable under the
|
| 547 |
+
Agreement are listed in Attachment A.
|
| 548 |
+
|
| 549 |
+
|
| 550 |
+
2.3. Operating Systems
|
| 551 |
+
|
| 552 |
+
Those portions of the SDK designed exclusively for use on the
|
| 553 |
+
Linux or FreeBSD operating systems, or other operating systems
|
| 554 |
+
derived from the source code to these operating systems, may
|
| 555 |
+
be copied and redistributed for use in accordance with this
|
| 556 |
+
Agreement, provided that the object code files are not
|
| 557 |
+
modified in any way (except for unzipping of compressed
|
| 558 |
+
files).
|
| 559 |
+
|
| 560 |
+
|
| 561 |
+
2.4. Audio and Video Encoders and Decoders
|
| 562 |
+
|
| 563 |
+
You acknowledge and agree that it is your sole responsibility
|
| 564 |
+
to obtain any additional third-party licenses required to
|
| 565 |
+
make, have made, use, have used, sell, import, and offer for
|
| 566 |
+
sale your products or services that include or incorporate any
|
| 567 |
+
third-party software and content relating to audio and/or
|
| 568 |
+
video encoders and decoders from, including but not limited
|
| 569 |
+
to, Microsoft, Thomson, Fraunhofer IIS, Sisvel S.p.A.,
|
| 570 |
+
MPEG-LA, and Coding Technologies. NVIDIA does not grant to you
|
| 571 |
+
under this Agreement any necessary patent or other rights with
|
| 572 |
+
respect to any audio and/or video encoders and decoders.
|
| 573 |
+
|
| 574 |
+
|
| 575 |
+
2.5. Licensing
|
| 576 |
+
|
| 577 |
+
If the distribution terms in this Agreement are not suitable
|
| 578 |
+
for your organization, or for any questions regarding this
|
| 579 |
+
Agreement, please contact NVIDIA at
|
| 580 |
+
nvidia-compute-license-questions@nvidia.com.
|
| 581 |
+
|
| 582 |
+
|
| 583 |
+
2.6. Attachment A
|
| 584 |
+
|
| 585 |
+
The following portions of the SDK are distributable under the
|
| 586 |
+
Agreement:
|
| 587 |
+
|
| 588 |
+
Component
|
| 589 |
+
|
| 590 |
+
CUDA Runtime
|
| 591 |
+
|
| 592 |
+
Windows
|
| 593 |
+
|
| 594 |
+
cudart.dll, cudart_static.lib, cudadevrt.lib
|
| 595 |
+
|
| 596 |
+
Mac OSX
|
| 597 |
+
|
| 598 |
+
libcudart.dylib, libcudart_static.a, libcudadevrt.a
|
| 599 |
+
|
| 600 |
+
Linux
|
| 601 |
+
|
| 602 |
+
libcudart.so, libcudart_static.a, libcudadevrt.a
|
| 603 |
+
|
| 604 |
+
Android
|
| 605 |
+
|
| 606 |
+
libcudart.so, libcudart_static.a, libcudadevrt.a
|
| 607 |
+
|
| 608 |
+
Component
|
| 609 |
+
|
| 610 |
+
CUDA FFT Library
|
| 611 |
+
|
| 612 |
+
Windows
|
| 613 |
+
|
| 614 |
+
cufft.dll, cufftw.dll, cufft.lib, cufftw.lib
|
| 615 |
+
|
| 616 |
+
Mac OSX
|
| 617 |
+
|
| 618 |
+
libcufft.dylib, libcufft_static.a, libcufftw.dylib,
|
| 619 |
+
libcufftw_static.a
|
| 620 |
+
|
| 621 |
+
Linux
|
| 622 |
+
|
| 623 |
+
libcufft.so, libcufft_static.a, libcufftw.so,
|
| 624 |
+
libcufftw_static.a
|
| 625 |
+
|
| 626 |
+
Android
|
| 627 |
+
|
| 628 |
+
libcufft.so, libcufft_static.a, libcufftw.so,
|
| 629 |
+
libcufftw_static.a
|
| 630 |
+
|
| 631 |
+
Component
|
| 632 |
+
|
| 633 |
+
CUDA BLAS Library
|
| 634 |
+
|
| 635 |
+
Windows
|
| 636 |
+
|
| 637 |
+
cublas.dll, cublasLt.dll
|
| 638 |
+
|
| 639 |
+
Mac OSX
|
| 640 |
+
|
| 641 |
+
libcublas.dylib, libcublasLt.dylib, libcublas_static.a,
|
| 642 |
+
libcublasLt_static.a
|
| 643 |
+
|
| 644 |
+
Linux
|
| 645 |
+
|
| 646 |
+
libcublas.so, libcublasLt.so, libcublas_static.a,
|
| 647 |
+
libcublasLt_static.a
|
| 648 |
+
|
| 649 |
+
Android
|
| 650 |
+
|
| 651 |
+
libcublas.so, libcublasLt.so, libcublas_static.a,
|
| 652 |
+
libcublasLt_static.a
|
| 653 |
+
|
| 654 |
+
Component
|
| 655 |
+
|
| 656 |
+
NVIDIA "Drop-in" BLAS Library
|
| 657 |
+
|
| 658 |
+
Windows
|
| 659 |
+
|
| 660 |
+
nvblas.dll
|
| 661 |
+
|
| 662 |
+
Mac OSX
|
| 663 |
+
|
| 664 |
+
libnvblas.dylib
|
| 665 |
+
|
| 666 |
+
Linux
|
| 667 |
+
|
| 668 |
+
libnvblas.so
|
| 669 |
+
|
| 670 |
+
Component
|
| 671 |
+
|
| 672 |
+
CUDA Sparse Matrix Library
|
| 673 |
+
|
| 674 |
+
Windows
|
| 675 |
+
|
| 676 |
+
cusparse.dll, cusparse.lib
|
| 677 |
+
|
| 678 |
+
Mac OSX
|
| 679 |
+
|
| 680 |
+
libcusparse.dylib, libcusparse_static.a
|
| 681 |
+
|
| 682 |
+
Linux
|
| 683 |
+
|
| 684 |
+
libcusparse.so, libcusparse_static.a
|
| 685 |
+
|
| 686 |
+
Android
|
| 687 |
+
|
| 688 |
+
libcusparse.so, libcusparse_static.a
|
| 689 |
+
|
| 690 |
+
Component
|
| 691 |
+
|
| 692 |
+
CUDA Linear Solver Library
|
| 693 |
+
|
| 694 |
+
Windows
|
| 695 |
+
|
| 696 |
+
cusolver.dll, cusolver.lib
|
| 697 |
+
|
| 698 |
+
Mac OSX
|
| 699 |
+
|
| 700 |
+
libcusolver.dylib, libcusolver_static.a
|
| 701 |
+
|
| 702 |
+
Linux
|
| 703 |
+
|
| 704 |
+
libcusolver.so, libcusolver_static.a
|
| 705 |
+
|
| 706 |
+
Android
|
| 707 |
+
|
| 708 |
+
libcusolver.so, libcusolver_static.a
|
| 709 |
+
|
| 710 |
+
Component
|
| 711 |
+
|
| 712 |
+
CUDA Random Number Generation Library
|
| 713 |
+
|
| 714 |
+
Windows
|
| 715 |
+
|
| 716 |
+
curand.dll, curand.lib
|
| 717 |
+
|
| 718 |
+
Mac OSX
|
| 719 |
+
|
| 720 |
+
libcurand.dylib, libcurand_static.a
|
| 721 |
+
|
| 722 |
+
Linux
|
| 723 |
+
|
| 724 |
+
libcurand.so, libcurand_static.a
|
| 725 |
+
|
| 726 |
+
Android
|
| 727 |
+
|
| 728 |
+
libcurand.so, libcurand_static.a
|
| 729 |
+
|
| 730 |
+
Component
|
| 731 |
+
|
| 732 |
+
CUDA Accelerated Graph Library
|
| 733 |
+
|
| 734 |
+
Component
|
| 735 |
+
|
| 736 |
+
NVIDIA Performance Primitives Library
|
| 737 |
+
|
| 738 |
+
Windows
|
| 739 |
+
|
| 740 |
+
nppc.dll, nppc.lib, nppial.dll, nppial.lib, nppicc.dll,
|
| 741 |
+
nppicc.lib, nppicom.dll, nppicom.lib, nppidei.dll,
|
| 742 |
+
nppidei.lib, nppif.dll, nppif.lib, nppig.dll, nppig.lib,
|
| 743 |
+
nppim.dll, nppim.lib, nppist.dll, nppist.lib, nppisu.dll,
|
| 744 |
+
nppisu.lib, nppitc.dll, nppitc.lib, npps.dll, npps.lib
|
| 745 |
+
|
| 746 |
+
Mac OSX
|
| 747 |
+
|
| 748 |
+
libnppc.dylib, libnppc_static.a, libnppial.dylib,
|
| 749 |
+
libnppial_static.a, libnppicc.dylib, libnppicc_static.a,
|
| 750 |
+
libnppicom.dylib, libnppicom_static.a, libnppidei.dylib,
|
| 751 |
+
libnppidei_static.a, libnppif.dylib, libnppif_static.a,
|
| 752 |
+
libnppig.dylib, libnppig_static.a, libnppim.dylib,
|
| 753 |
+
libnppisu_static.a, libnppitc.dylib, libnppitc_static.a,
|
| 754 |
+
libnpps.dylib, libnpps_static.a
|
| 755 |
+
|
| 756 |
+
Linux
|
| 757 |
+
|
| 758 |
+
libnppc.so, libnppc_static.a, libnppial.so,
|
| 759 |
+
libnppial_static.a, libnppicc.so, libnppicc_static.a,
|
| 760 |
+
libnppicom.so, libnppicom_static.a, libnppidei.so,
|
| 761 |
+
libnppidei_static.a, libnppif.so, libnppif_static.a
|
| 762 |
+
libnppig.so, libnppig_static.a, libnppim.so,
|
| 763 |
+
libnppim_static.a, libnppist.so, libnppist_static.a,
|
| 764 |
+
libnppisu.so, libnppisu_static.a, libnppitc.so
|
| 765 |
+
libnppitc_static.a, libnpps.so, libnpps_static.a
|
| 766 |
+
|
| 767 |
+
Android
|
| 768 |
+
|
| 769 |
+
libnppc.so, libnppc_static.a, libnppial.so,
|
| 770 |
+
libnppial_static.a, libnppicc.so, libnppicc_static.a,
|
| 771 |
+
libnppicom.so, libnppicom_static.a, libnppidei.so,
|
| 772 |
+
libnppidei_static.a, libnppif.so, libnppif_static.a
|
| 773 |
+
libnppig.so, libnppig_static.a, libnppim.so,
|
| 774 |
+
libnppim_static.a, libnppist.so, libnppist_static.a,
|
| 775 |
+
libnppisu.so, libnppisu_static.a, libnppitc.so
|
| 776 |
+
libnppitc_static.a, libnpps.so, libnpps_static.a
|
| 777 |
+
|
| 778 |
+
Component
|
| 779 |
+
|
| 780 |
+
NVIDIA JPEG Library
|
| 781 |
+
|
| 782 |
+
Linux
|
| 783 |
+
|
| 784 |
+
libnvjpeg.so, libnvjpeg_static.a
|
| 785 |
+
|
| 786 |
+
Component
|
| 787 |
+
|
| 788 |
+
Internal common library required for statically linking to
|
| 789 |
+
cuBLAS, cuSPARSE, cuFFT, cuRAND, nvJPEG and NPP
|
| 790 |
+
|
| 791 |
+
Mac OSX
|
| 792 |
+
|
| 793 |
+
libculibos.a
|
| 794 |
+
|
| 795 |
+
Linux
|
| 796 |
+
|
| 797 |
+
libculibos.a
|
| 798 |
+
|
| 799 |
+
Component
|
| 800 |
+
|
| 801 |
+
NVIDIA Runtime Compilation Library and Header
|
| 802 |
+
|
| 803 |
+
All
|
| 804 |
+
|
| 805 |
+
nvrtc.h
|
| 806 |
+
|
| 807 |
+
Windows
|
| 808 |
+
|
| 809 |
+
nvrtc.dll, nvrtc-builtins.dll
|
| 810 |
+
|
| 811 |
+
Mac OSX
|
| 812 |
+
|
| 813 |
+
libnvrtc.dylib, libnvrtc-builtins.dylib
|
| 814 |
+
|
| 815 |
+
Linux
|
| 816 |
+
|
| 817 |
+
libnvrtc.so, libnvrtc-builtins.so
|
| 818 |
+
|
| 819 |
+
Component
|
| 820 |
+
|
| 821 |
+
NVIDIA Optimizing Compiler Library
|
| 822 |
+
|
| 823 |
+
Windows
|
| 824 |
+
|
| 825 |
+
nvvm.dll
|
| 826 |
+
|
| 827 |
+
Mac OSX
|
| 828 |
+
|
| 829 |
+
libnvvm.dylib
|
| 830 |
+
|
| 831 |
+
Linux
|
| 832 |
+
|
| 833 |
+
libnvvm.so
|
| 834 |
+
|
| 835 |
+
Component
|
| 836 |
+
|
| 837 |
+
NVIDIA Common Device Math Functions Library
|
| 838 |
+
|
| 839 |
+
Windows
|
| 840 |
+
|
| 841 |
+
libdevice.10.bc
|
| 842 |
+
|
| 843 |
+
Mac OSX
|
| 844 |
+
|
| 845 |
+
libdevice.10.bc
|
| 846 |
+
|
| 847 |
+
Linux
|
| 848 |
+
|
| 849 |
+
libdevice.10.bc
|
| 850 |
+
|
| 851 |
+
Component
|
| 852 |
+
|
| 853 |
+
CUDA Occupancy Calculation Header Library
|
| 854 |
+
|
| 855 |
+
All
|
| 856 |
+
|
| 857 |
+
cuda_occupancy.h
|
| 858 |
+
|
| 859 |
+
Component
|
| 860 |
+
|
| 861 |
+
CUDA Half Precision Headers
|
| 862 |
+
|
| 863 |
+
All
|
| 864 |
+
|
| 865 |
+
cuda_fp16.h, cuda_fp16.hpp
|
| 866 |
+
|
| 867 |
+
Component
|
| 868 |
+
|
| 869 |
+
CUDA Profiling Tools Interface (CUPTI) Library
|
| 870 |
+
|
| 871 |
+
Windows
|
| 872 |
+
|
| 873 |
+
cupti.dll
|
| 874 |
+
|
| 875 |
+
Mac OSX
|
| 876 |
+
|
| 877 |
+
libcupti.dylib
|
| 878 |
+
|
| 879 |
+
Linux
|
| 880 |
+
|
| 881 |
+
libcupti.so
|
| 882 |
+
|
| 883 |
+
Component
|
| 884 |
+
|
| 885 |
+
NVIDIA Tools Extension Library
|
| 886 |
+
|
| 887 |
+
Windows
|
| 888 |
+
|
| 889 |
+
nvToolsExt.dll, nvToolsExt.lib
|
| 890 |
+
|
| 891 |
+
Mac OSX
|
| 892 |
+
|
| 893 |
+
libnvToolsExt.dylib
|
| 894 |
+
|
| 895 |
+
Linux
|
| 896 |
+
|
| 897 |
+
libnvToolsExt.so
|
| 898 |
+
|
| 899 |
+
Component
|
| 900 |
+
|
| 901 |
+
NVIDIA CUDA Driver Libraries
|
| 902 |
+
|
| 903 |
+
Linux
|
| 904 |
+
|
| 905 |
+
libcuda.so, libnvidia-fatbinaryloader.so,
|
| 906 |
+
libnvidia-ptxjitcompiler.so
|
| 907 |
+
|
| 908 |
+
The NVIDIA CUDA Driver Libraries are only distributable in
|
| 909 |
+
applications that meet this criteria:
|
| 910 |
+
|
| 911 |
+
1. The application was developed starting from a NVIDIA CUDA
|
| 912 |
+
container obtained from Docker Hub or the NVIDIA GPU
|
| 913 |
+
Cloud, and
|
| 914 |
+
|
| 915 |
+
2. The resulting application is packaged as a Docker
|
| 916 |
+
container and distributed to users on Docker Hub or the
|
| 917 |
+
NVIDIA GPU Cloud only.
|
| 918 |
+
|
| 919 |
+
|
| 920 |
+
2.7. Attachment B
|
| 921 |
+
|
| 922 |
+
|
| 923 |
+
Additional Licensing Obligations
|
| 924 |
+
|
| 925 |
+
The following third party components included in the SOFTWARE
|
| 926 |
+
are licensed to Licensee pursuant to the following terms and
|
| 927 |
+
conditions:
|
| 928 |
+
|
| 929 |
+
1. Licensee's use of the GDB third party component is
|
| 930 |
+
subject to the terms and conditions of GNU GPL v3:
|
| 931 |
+
|
| 932 |
+
This product includes copyrighted third-party software licensed
|
| 933 |
+
under the terms of the GNU General Public License v3 ("GPL v3").
|
| 934 |
+
All third-party software packages are copyright by their respective
|
| 935 |
+
authors. GPL v3 terms and conditions are hereby incorporated into
|
| 936 |
+
the Agreement by this reference: http://www.gnu.org/licenses/gpl.txt
|
| 937 |
+
|
| 938 |
+
Consistent with these licensing requirements, the software
|
| 939 |
+
listed below is provided under the terms of the specified
|
| 940 |
+
open source software licenses. To obtain source code for
|
| 941 |
+
software provided under licenses that require
|
| 942 |
+
redistribution of source code, including the GNU General
|
| 943 |
+
Public License (GPL) and GNU Lesser General Public License
|
| 944 |
+
(LGPL), contact oss-requests@nvidia.com. This offer is
|
| 945 |
+
valid for a period of three (3) years from the date of the
|
| 946 |
+
distribution of this product by NVIDIA CORPORATION.
|
| 947 |
+
|
| 948 |
+
Component License
|
| 949 |
+
CUDA-GDB GPL v3
|
| 950 |
+
|
| 951 |
+
2. Licensee represents and warrants that any and all third
|
| 952 |
+
party licensing and/or royalty payment obligations in
|
| 953 |
+
connection with Licensee's use of the H.264 video codecs
|
| 954 |
+
are solely the responsibility of Licensee.
|
| 955 |
+
|
| 956 |
+
3. Licensee's use of the Thrust library is subject to the
|
| 957 |
+
terms and conditions of the Apache License Version 2.0.
|
| 958 |
+
All third-party software packages are copyright by their
|
| 959 |
+
respective authors. Apache License Version 2.0 terms and
|
| 960 |
+
conditions are hereby incorporated into the Agreement by
|
| 961 |
+
this reference.
|
| 962 |
+
http://www.apache.org/licenses/LICENSE-2.0.html
|
| 963 |
+
|
| 964 |
+
In addition, Licensee acknowledges the following notice:
|
| 965 |
+
Thrust includes source code from the Boost Iterator,
|
| 966 |
+
Tuple, System, and Random Number libraries.
|
| 967 |
+
|
| 968 |
+
Boost Software License - Version 1.0 - August 17th, 2003
|
| 969 |
+
. . . .
|
| 970 |
+
|
| 971 |
+
Permission is hereby granted, free of charge, to any person or
|
| 972 |
+
organization obtaining a copy of the software and accompanying
|
| 973 |
+
documentation covered by this license (the "Software") to use,
|
| 974 |
+
reproduce, display, distribute, execute, and transmit the Software,
|
| 975 |
+
and to prepare derivative works of the Software, and to permit
|
| 976 |
+
third-parties to whom the Software is furnished to do so, all
|
| 977 |
+
subject to the following:
|
| 978 |
+
|
| 979 |
+
The copyright notices in the Software and this entire statement,
|
| 980 |
+
including the above license grant, this restriction and the following
|
| 981 |
+
disclaimer, must be included in all copies of the Software, in whole
|
| 982 |
+
or in part, and all derivative works of the Software, unless such
|
| 983 |
+
copies or derivative works are solely in the form of machine-executable
|
| 984 |
+
object code generated by a source language processor.
|
| 985 |
+
|
| 986 |
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
| 987 |
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
| 988 |
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND
|
| 989 |
+
NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR
|
| 990 |
+
ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR
|
| 991 |
+
OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING
|
| 992 |
+
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
| 993 |
+
OTHER DEALINGS IN THE SOFTWARE.
|
| 994 |
+
|
| 995 |
+
4. Licensee's use of the LLVM third party component is
|
| 996 |
+
subject to the following terms and conditions:
|
| 997 |
+
|
| 998 |
+
======================================================
|
| 999 |
+
LLVM Release License
|
| 1000 |
+
======================================================
|
| 1001 |
+
University of Illinois/NCSA
|
| 1002 |
+
Open Source License
|
| 1003 |
+
|
| 1004 |
+
Copyright (c) 2003-2010 University of Illinois at Urbana-Champaign.
|
| 1005 |
+
All rights reserved.
|
| 1006 |
+
|
| 1007 |
+
Developed by:
|
| 1008 |
+
|
| 1009 |
+
LLVM Team
|
| 1010 |
+
|
| 1011 |
+
University of Illinois at Urbana-Champaign
|
| 1012 |
+
|
| 1013 |
+
http://llvm.org
|
| 1014 |
+
|
| 1015 |
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
| 1016 |
+
of this software and associated documentation files (the "Software"), to
|
| 1017 |
+
deal with the Software without restriction, including without limitation the
|
| 1018 |
+
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
| 1019 |
+
sell copies of the Software, and to permit persons to whom the Software is
|
| 1020 |
+
furnished to do so, subject to the following conditions:
|
| 1021 |
+
|
| 1022 |
+
* Redistributions of source code must retain the above copyright notice,
|
| 1023 |
+
this list of conditions and the following disclaimers.
|
| 1024 |
+
|
| 1025 |
+
* Redistributions in binary form must reproduce the above copyright
|
| 1026 |
+
notice, this list of conditions and the following disclaimers in the
|
| 1027 |
+
documentation and/or other materials provided with the distribution.
|
| 1028 |
+
|
| 1029 |
+
* Neither the names of the LLVM Team, University of Illinois at Urbana-
|
| 1030 |
+
Champaign, nor the names of its contributors may be used to endorse or
|
| 1031 |
+
promote products derived from this Software without specific prior
|
| 1032 |
+
written permission.
|
| 1033 |
+
|
| 1034 |
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
| 1035 |
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
| 1036 |
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
| 1037 |
+
THE CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
| 1038 |
+
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
| 1039 |
+
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
| 1040 |
+
DEALINGS WITH THE SOFTWARE.
|
| 1041 |
+
|
| 1042 |
+
5. Licensee's use (e.g. nvprof) of the PCRE third party
|
| 1043 |
+
component is subject to the following terms and
|
| 1044 |
+
conditions:
|
| 1045 |
+
|
| 1046 |
+
------------
|
| 1047 |
+
PCRE LICENCE
|
| 1048 |
+
------------
|
| 1049 |
+
PCRE is a library of functions to support regular expressions whose syntax
|
| 1050 |
+
and semantics are as close as possible to those of the Perl 5 language.
|
| 1051 |
+
Release 8 of PCRE is distributed under the terms of the "BSD" licence, as
|
| 1052 |
+
specified below. The documentation for PCRE, supplied in the "doc"
|
| 1053 |
+
directory, is distributed under the same terms as the software itself. The
|
| 1054 |
+
basic library functions are written in C and are freestanding. Also
|
| 1055 |
+
included in the distribution is a set of C++ wrapper functions, and a just-
|
| 1056 |
+
in-time compiler that can be used to optimize pattern matching. These are
|
| 1057 |
+
both optional features that can be omitted when the library is built.
|
| 1058 |
+
|
| 1059 |
+
THE BASIC LIBRARY FUNCTIONS
|
| 1060 |
+
---------------------------
|
| 1061 |
+
Written by: Philip Hazel
|
| 1062 |
+
Email local part: ph10
|
| 1063 |
+
Email domain: cam.ac.uk
|
| 1064 |
+
University of Cambridge Computing Service,
|
| 1065 |
+
Cambridge, England.
|
| 1066 |
+
Copyright (c) 1997-2012 University of Cambridge
|
| 1067 |
+
All rights reserved.
|
| 1068 |
+
|
| 1069 |
+
PCRE JUST-IN-TIME COMPILATION SUPPORT
|
| 1070 |
+
-------------------------------------
|
| 1071 |
+
Written by: Zoltan Herczeg
|
| 1072 |
+
Email local part: hzmester
|
| 1073 |
+
Emain domain: freemail.hu
|
| 1074 |
+
Copyright(c) 2010-2012 Zoltan Herczeg
|
| 1075 |
+
All rights reserved.
|
| 1076 |
+
|
| 1077 |
+
STACK-LESS JUST-IN-TIME COMPILER
|
| 1078 |
+
--------------------------------
|
| 1079 |
+
Written by: Zoltan Herczeg
|
| 1080 |
+
Email local part: hzmester
|
| 1081 |
+
Emain domain: freemail.hu
|
| 1082 |
+
Copyright(c) 2009-2012 Zoltan Herczeg
|
| 1083 |
+
All rights reserved.
|
| 1084 |
+
|
| 1085 |
+
THE C++ WRAPPER FUNCTIONS
|
| 1086 |
+
-------------------------
|
| 1087 |
+
Contributed by: Google Inc.
|
| 1088 |
+
Copyright (c) 2007-2012, Google Inc.
|
| 1089 |
+
All rights reserved.
|
| 1090 |
+
|
| 1091 |
+
THE "BSD" LICENCE
|
| 1092 |
+
-----------------
|
| 1093 |
+
Redistribution and use in source and binary forms, with or without
|
| 1094 |
+
modification, are permitted provided that the following conditions are met:
|
| 1095 |
+
|
| 1096 |
+
* Redistributions of source code must retain the above copyright notice,
|
| 1097 |
+
this list of conditions and the following disclaimer.
|
| 1098 |
+
|
| 1099 |
+
* Redistributions in binary form must reproduce the above copyright
|
| 1100 |
+
notice, this list of conditions and the following disclaimer in the
|
| 1101 |
+
documentation and/or other materials provided with the distribution.
|
| 1102 |
+
|
| 1103 |
+
* Neither the name of the University of Cambridge nor the name of Google
|
| 1104 |
+
Inc. nor the names of their contributors may be used to endorse or
|
| 1105 |
+
promote products derived from this software without specific prior
|
| 1106 |
+
written permission.
|
| 1107 |
+
|
| 1108 |
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
| 1109 |
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
| 1110 |
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
| 1111 |
+
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
| 1112 |
+
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
| 1113 |
+
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
| 1114 |
+
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
| 1115 |
+
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
| 1116 |
+
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
| 1117 |
+
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
| 1118 |
+
POSSIBILITY OF SUCH DAMAGE.
|
| 1119 |
+
|
| 1120 |
+
6. Some of the cuBLAS library routines were written by or
|
| 1121 |
+
derived from code written by Vasily Volkov and are subject
|
| 1122 |
+
to the Modified Berkeley Software Distribution License as
|
| 1123 |
+
follows:
|
| 1124 |
+
|
| 1125 |
+
Copyright (c) 2007-2009, Regents of the University of California
|
| 1126 |
+
|
| 1127 |
+
All rights reserved.
|
| 1128 |
+
|
| 1129 |
+
Redistribution and use in source and binary forms, with or without
|
| 1130 |
+
modification, are permitted provided that the following conditions are
|
| 1131 |
+
met:
|
| 1132 |
+
* Redistributions of source code must retain the above copyright
|
| 1133 |
+
notice, this list of conditions and the following disclaimer.
|
| 1134 |
+
* Redistributions in binary form must reproduce the above
|
| 1135 |
+
copyright notice, this list of conditions and the following
|
| 1136 |
+
disclaimer in the documentation and/or other materials provided
|
| 1137 |
+
with the distribution.
|
| 1138 |
+
* Neither the name of the University of California, Berkeley nor
|
| 1139 |
+
the names of its contributors may be used to endorse or promote
|
| 1140 |
+
products derived from this software without specific prior
|
| 1141 |
+
written permission.
|
| 1142 |
+
|
| 1143 |
+
THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR
|
| 1144 |
+
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
| 1145 |
+
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
| 1146 |
+
DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
|
| 1147 |
+
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
| 1148 |
+
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
| 1149 |
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
| 1150 |
+
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
| 1151 |
+
STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
|
| 1152 |
+
IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
| 1153 |
+
POSSIBILITY OF SUCH DAMAGE.
|
| 1154 |
+
|
| 1155 |
+
7. Some of the cuBLAS library routines were written by or
|
| 1156 |
+
derived from code written by Davide Barbieri and are
|
| 1157 |
+
subject to the Modified Berkeley Software Distribution
|
| 1158 |
+
License as follows:
|
| 1159 |
+
|
| 1160 |
+
Copyright (c) 2008-2009 Davide Barbieri @ University of Rome Tor Vergata.
|
| 1161 |
+
|
| 1162 |
+
All rights reserved.
|
| 1163 |
+
|
| 1164 |
+
Redistribution and use in source and binary forms, with or without
|
| 1165 |
+
modification, are permitted provided that the following conditions are
|
| 1166 |
+
met:
|
| 1167 |
+
* Redistributions of source code must retain the above copyright
|
| 1168 |
+
notice, this list of conditions and the following disclaimer.
|
| 1169 |
+
* Redistributions in binary form must reproduce the above
|
| 1170 |
+
copyright notice, this list of conditions and the following
|
| 1171 |
+
disclaimer in the documentation and/or other materials provided
|
| 1172 |
+
with the distribution.
|
| 1173 |
+
* The name of the author may not be used to endorse or promote
|
| 1174 |
+
products derived from this software without specific prior
|
| 1175 |
+
written permission.
|
| 1176 |
+
|
| 1177 |
+
THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR
|
| 1178 |
+
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
| 1179 |
+
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
| 1180 |
+
DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
|
| 1181 |
+
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
| 1182 |
+
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
| 1183 |
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
| 1184 |
+
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
| 1185 |
+
STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
|
| 1186 |
+
IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
| 1187 |
+
POSSIBILITY OF SUCH DAMAGE.
|
| 1188 |
+
|
| 1189 |
+
8. Some of the cuBLAS library routines were derived from
|
| 1190 |
+
code developed by the University of Tennessee and are
|
| 1191 |
+
subject to the Modified Berkeley Software Distribution
|
| 1192 |
+
License as follows:
|
| 1193 |
+
|
| 1194 |
+
Copyright (c) 2010 The University of Tennessee.
|
| 1195 |
+
|
| 1196 |
+
All rights reserved.
|
| 1197 |
+
|
| 1198 |
+
Redistribution and use in source and binary forms, with or without
|
| 1199 |
+
modification, are permitted provided that the following conditions are
|
| 1200 |
+
met:
|
| 1201 |
+
* Redistributions of source code must retain the above copyright
|
| 1202 |
+
notice, this list of conditions and the following disclaimer.
|
| 1203 |
+
* Redistributions in binary form must reproduce the above
|
| 1204 |
+
copyright notice, this list of conditions and the following
|
| 1205 |
+
disclaimer listed in this license in the documentation and/or
|
| 1206 |
+
other materials provided with the distribution.
|
| 1207 |
+
* Neither the name of the copyright holders nor the names of its
|
| 1208 |
+
contributors may be used to endorse or promote products derived
|
| 1209 |
+
from this software without specific prior written permission.
|
| 1210 |
+
|
| 1211 |
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
| 1212 |
+
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
| 1213 |
+
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
| 1214 |
+
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
| 1215 |
+
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
| 1216 |
+
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
| 1217 |
+
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
| 1218 |
+
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
| 1219 |
+
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
| 1220 |
+
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
| 1221 |
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
| 1222 |
+
|
| 1223 |
+
9. Some of the cuBLAS library routines were written by or
|
| 1224 |
+
derived from code written by Jonathan Hogg and are subject
|
| 1225 |
+
to the Modified Berkeley Software Distribution License as
|
| 1226 |
+
follows:
|
| 1227 |
+
|
| 1228 |
+
Copyright (c) 2012, The Science and Technology Facilities Council (STFC).
|
| 1229 |
+
|
| 1230 |
+
All rights reserved.
|
| 1231 |
+
|
| 1232 |
+
Redistribution and use in source and binary forms, with or without
|
| 1233 |
+
modification, are permitted provided that the following conditions are
|
| 1234 |
+
met:
|
| 1235 |
+
* Redistributions of source code must retain the above copyright
|
| 1236 |
+
notice, this list of conditions and the following disclaimer.
|
| 1237 |
+
* Redistributions in binary form must reproduce the above
|
| 1238 |
+
copyright notice, this list of conditions and the following
|
| 1239 |
+
disclaimer in the documentation and/or other materials provided
|
| 1240 |
+
with the distribution.
|
| 1241 |
+
* Neither the name of the STFC nor the names of its contributors
|
| 1242 |
+
may be used to endorse or promote products derived from this
|
| 1243 |
+
software without specific prior written permission.
|
| 1244 |
+
|
| 1245 |
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
| 1246 |
+
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
| 1247 |
+
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
| 1248 |
+
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE STFC BE
|
| 1249 |
+
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
| 1250 |
+
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
| 1251 |
+
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
|
| 1252 |
+
BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
| 1253 |
+
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
|
| 1254 |
+
OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
|
| 1255 |
+
IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
| 1256 |
+
|
| 1257 |
+
10. Some of the cuBLAS library routines were written by or
|
| 1258 |
+
derived from code written by Ahmad M. Abdelfattah, David
|
| 1259 |
+
Keyes, and Hatem Ltaief, and are subject to the Apache
|
| 1260 |
+
License, Version 2.0, as follows:
|
| 1261 |
+
|
| 1262 |
+
-- (C) Copyright 2013 King Abdullah University of Science and Technology
|
| 1263 |
+
Authors:
|
| 1264 |
+
Ahmad Abdelfattah (ahmad.ahmad@kaust.edu.sa)
|
| 1265 |
+
David Keyes (david.keyes@kaust.edu.sa)
|
| 1266 |
+
Hatem Ltaief (hatem.ltaief@kaust.edu.sa)
|
| 1267 |
+
|
| 1268 |
+
Redistribution and use in source and binary forms, with or without
|
| 1269 |
+
modification, are permitted provided that the following conditions
|
| 1270 |
+
are met:
|
| 1271 |
+
|
| 1272 |
+
* Redistributions of source code must retain the above copyright
|
| 1273 |
+
notice, this list of conditions and the following disclaimer.
|
| 1274 |
+
* Redistributions in binary form must reproduce the above copyright
|
| 1275 |
+
notice, this list of conditions and the following disclaimer in the
|
| 1276 |
+
documentation and/or other materials provided with the distribution.
|
| 1277 |
+
* Neither the name of the King Abdullah University of Science and
|
| 1278 |
+
Technology nor the names of its contributors may be used to endorse
|
| 1279 |
+
or promote products derived from this software without specific prior
|
| 1280 |
+
written permission.
|
| 1281 |
+
|
| 1282 |
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
| 1283 |
+
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
| 1284 |
+
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
| 1285 |
+
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
| 1286 |
+
HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
| 1287 |
+
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
| 1288 |
+
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
| 1289 |
+
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
| 1290 |
+
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
| 1291 |
+
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
| 1292 |
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE
|
| 1293 |
+
|
| 1294 |
+
11. Some of the cuSPARSE library routines were written by or
|
| 1295 |
+
derived from code written by Li-Wen Chang and are subject
|
| 1296 |
+
to the NCSA Open Source License as follows:
|
| 1297 |
+
|
| 1298 |
+
Copyright (c) 2012, University of Illinois.
|
| 1299 |
+
|
| 1300 |
+
All rights reserved.
|
| 1301 |
+
|
| 1302 |
+
Developed by: IMPACT Group, University of Illinois, http://impact.crhc.illinois.edu
|
| 1303 |
+
|
| 1304 |
+
Permission is hereby granted, free of charge, to any person obtaining
|
| 1305 |
+
a copy of this software and associated documentation files (the
|
| 1306 |
+
"Software"), to deal with the Software without restriction, including
|
| 1307 |
+
without limitation the rights to use, copy, modify, merge, publish,
|
| 1308 |
+
distribute, sublicense, and/or sell copies of the Software, and to
|
| 1309 |
+
permit persons to whom the Software is furnished to do so, subject to
|
| 1310 |
+
the following conditions:
|
| 1311 |
+
* Redistributions of source code must retain the above copyright
|
| 1312 |
+
notice, this list of conditions and the following disclaimer.
|
| 1313 |
+
* Redistributions in binary form must reproduce the above
|
| 1314 |
+
copyright notice, this list of conditions and the following
|
| 1315 |
+
disclaimers in the documentation and/or other materials provided
|
| 1316 |
+
with the distribution.
|
| 1317 |
+
* Neither the names of IMPACT Group, University of Illinois, nor
|
| 1318 |
+
the names of its contributors may be used to endorse or promote
|
| 1319 |
+
products derived from this Software without specific prior
|
| 1320 |
+
written permission.
|
| 1321 |
+
|
| 1322 |
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
| 1323 |
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
| 1324 |
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
| 1325 |
+
NONINFRINGEMENT. IN NO EVENT SHALL THE CONTRIBUTORS OR COPYRIGHT
|
| 1326 |
+
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
| 1327 |
+
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
|
| 1328 |
+
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS WITH THE
|
| 1329 |
+
SOFTWARE.
|
| 1330 |
+
|
| 1331 |
+
12. Some of the cuRAND library routines were written by or
|
| 1332 |
+
derived from code written by Mutsuo Saito and Makoto
|
| 1333 |
+
Matsumoto and are subject to the following license:
|
| 1334 |
+
|
| 1335 |
+
Copyright (c) 2009, 2010 Mutsuo Saito, Makoto Matsumoto and Hiroshima
|
| 1336 |
+
University. All rights reserved.
|
| 1337 |
+
|
| 1338 |
+
Copyright (c) 2011 Mutsuo Saito, Makoto Matsumoto, Hiroshima
|
| 1339 |
+
University and University of Tokyo. All rights reserved.
|
| 1340 |
+
|
| 1341 |
+
Redistribution and use in source and binary forms, with or without
|
| 1342 |
+
modification, are permitted provided that the following conditions are
|
| 1343 |
+
met:
|
| 1344 |
+
* Redistributions of source code must retain the above copyright
|
| 1345 |
+
notice, this list of conditions and the following disclaimer.
|
| 1346 |
+
* Redistributions in binary form must reproduce the above
|
| 1347 |
+
copyright notice, this list of conditions and the following
|
| 1348 |
+
disclaimer in the documentation and/or other materials provided
|
| 1349 |
+
with the distribution.
|
| 1350 |
+
* Neither the name of the Hiroshima University nor the names of
|
| 1351 |
+
its contributors may be used to endorse or promote products
|
| 1352 |
+
derived from this software without specific prior written
|
| 1353 |
+
permission.
|
| 1354 |
+
|
| 1355 |
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
| 1356 |
+
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
| 1357 |
+
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
| 1358 |
+
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
| 1359 |
+
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
| 1360 |
+
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
| 1361 |
+
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
| 1362 |
+
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
| 1363 |
+
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
| 1364 |
+
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
| 1365 |
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
| 1366 |
+
|
| 1367 |
+
13. Some of the cuRAND library routines were derived from
|
| 1368 |
+
code developed by D. E. Shaw Research and are subject to
|
| 1369 |
+
the following license:
|
| 1370 |
+
|
| 1371 |
+
Copyright 2010-2011, D. E. Shaw Research.
|
| 1372 |
+
|
| 1373 |
+
All rights reserved.
|
| 1374 |
+
|
| 1375 |
+
Redistribution and use in source and binary forms, with or without
|
| 1376 |
+
modification, are permitted provided that the following conditions are
|
| 1377 |
+
met:
|
| 1378 |
+
* Redistributions of source code must retain the above copyright
|
| 1379 |
+
notice, this list of conditions, and the following disclaimer.
|
| 1380 |
+
* Redistributions in binary form must reproduce the above
|
| 1381 |
+
copyright notice, this list of conditions, and the following
|
| 1382 |
+
disclaimer in the documentation and/or other materials provided
|
| 1383 |
+
with the distribution.
|
| 1384 |
+
* Neither the name of D. E. Shaw Research nor the names of its
|
| 1385 |
+
contributors may be used to endorse or promote products derived
|
| 1386 |
+
from this software without specific prior written permission.
|
| 1387 |
+
|
| 1388 |
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
| 1389 |
+
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
| 1390 |
+
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
| 1391 |
+
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
| 1392 |
+
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
| 1393 |
+
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
| 1394 |
+
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
| 1395 |
+
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
| 1396 |
+
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
| 1397 |
+
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
| 1398 |
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
| 1399 |
+
|
| 1400 |
+
14. Some of the Math library routines were written by or
|
| 1401 |
+
derived from code developed by Norbert Juffa and are
|
| 1402 |
+
subject to the following license:
|
| 1403 |
+
|
| 1404 |
+
Copyright (c) 2015-2017, Norbert Juffa
|
| 1405 |
+
All rights reserved.
|
| 1406 |
+
|
| 1407 |
+
Redistribution and use in source and binary forms, with or without
|
| 1408 |
+
modification, are permitted provided that the following conditions
|
| 1409 |
+
are met:
|
| 1410 |
+
|
| 1411 |
+
1. Redistributions of source code must retain the above copyright
|
| 1412 |
+
notice, this list of conditions and the following disclaimer.
|
| 1413 |
+
|
| 1414 |
+
2. Redistributions in binary form must reproduce the above copyright
|
| 1415 |
+
notice, this list of conditions and the following disclaimer in the
|
| 1416 |
+
documentation and/or other materials provided with the distribution.
|
| 1417 |
+
|
| 1418 |
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
| 1419 |
+
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
| 1420 |
+
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
| 1421 |
+
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
| 1422 |
+
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
| 1423 |
+
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
| 1424 |
+
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
| 1425 |
+
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
| 1426 |
+
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
| 1427 |
+
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
| 1428 |
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
| 1429 |
+
|
| 1430 |
+
15. Licensee's use of the lz4 third party component is
|
| 1431 |
+
subject to the following terms and conditions:
|
| 1432 |
+
|
| 1433 |
+
Copyright (C) 2011-2013, Yann Collet.
|
| 1434 |
+
BSD 2-Clause License (http://www.opensource.org/licenses/bsd-license.php)
|
| 1435 |
+
|
| 1436 |
+
Redistribution and use in source and binary forms, with or without
|
| 1437 |
+
modification, are permitted provided that the following conditions are
|
| 1438 |
+
met:
|
| 1439 |
+
|
| 1440 |
+
* Redistributions of source code must retain the above copyright
|
| 1441 |
+
notice, this list of conditions and the following disclaimer.
|
| 1442 |
+
* Redistributions in binary form must reproduce the above
|
| 1443 |
+
copyright notice, this list of conditions and the following disclaimer
|
| 1444 |
+
in the documentation and/or other materials provided with the
|
| 1445 |
+
distribution.
|
| 1446 |
+
|
| 1447 |
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
| 1448 |
+
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
| 1449 |
+
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
| 1450 |
+
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
| 1451 |
+
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
| 1452 |
+
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
| 1453 |
+
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
| 1454 |
+
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
| 1455 |
+
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
| 1456 |
+
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
| 1457 |
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
| 1458 |
+
|
| 1459 |
+
16. The NPP library uses code from the Boost Math Toolkit,
|
| 1460 |
+
and is subject to the following license:
|
| 1461 |
+
|
| 1462 |
+
Boost Software License - Version 1.0 - August 17th, 2003
|
| 1463 |
+
. . . .
|
| 1464 |
+
|
| 1465 |
+
Permission is hereby granted, free of charge, to any person or
|
| 1466 |
+
organization obtaining a copy of the software and accompanying
|
| 1467 |
+
documentation covered by this license (the "Software") to use,
|
| 1468 |
+
reproduce, display, distribute, execute, and transmit the Software,
|
| 1469 |
+
and to prepare derivative works of the Software, and to permit
|
| 1470 |
+
third-parties to whom the Software is furnished to do so, all
|
| 1471 |
+
subject to the following:
|
| 1472 |
+
|
| 1473 |
+
The copyright notices in the Software and this entire statement,
|
| 1474 |
+
including the above license grant, this restriction and the following
|
| 1475 |
+
disclaimer, must be included in all copies of the Software, in whole
|
| 1476 |
+
or in part, and all derivative works of the Software, unless such
|
| 1477 |
+
copies or derivative works are solely in the form of machine-executable
|
| 1478 |
+
object code generated by a source language processor.
|
| 1479 |
+
|
| 1480 |
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
| 1481 |
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
| 1482 |
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND
|
| 1483 |
+
NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR
|
| 1484 |
+
ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR
|
| 1485 |
+
OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING
|
| 1486 |
+
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
| 1487 |
+
OTHER DEALINGS IN THE SOFTWARE.
|
| 1488 |
+
|
| 1489 |
+
17. Portions of the Nsight Eclipse Edition is subject to the
|
| 1490 |
+
following license:
|
| 1491 |
+
|
| 1492 |
+
The Eclipse Foundation makes available all content in this plug-in
|
| 1493 |
+
("Content"). Unless otherwise indicated below, the Content is provided
|
| 1494 |
+
to you under the terms and conditions of the Eclipse Public License
|
| 1495 |
+
Version 1.0 ("EPL"). A copy of the EPL is available at http://
|
| 1496 |
+
www.eclipse.org/legal/epl-v10.html. For purposes of the EPL, "Program"
|
| 1497 |
+
will mean the Content.
|
| 1498 |
+
|
| 1499 |
+
If you did not receive this Content directly from the Eclipse
|
| 1500 |
+
Foundation, the Content is being redistributed by another party
|
| 1501 |
+
("Redistributor") and different terms and conditions may apply to your
|
| 1502 |
+
use of any object code in the Content. Check the Redistributor's
|
| 1503 |
+
license that was provided with the Content. If no such license exists,
|
| 1504 |
+
contact the Redistributor. Unless otherwise indicated below, the terms
|
| 1505 |
+
and conditions of the EPL still apply to any source code in the
|
| 1506 |
+
Content and such source code may be obtained at http://www.eclipse.org.
|
| 1507 |
+
|
| 1508 |
+
18. Some of the cuBLAS library routines uses code from
|
| 1509 |
+
OpenAI, which is subject to the following license:
|
| 1510 |
+
|
| 1511 |
+
License URL
|
| 1512 |
+
https://github.com/openai/openai-gemm/blob/master/LICENSE
|
| 1513 |
+
|
| 1514 |
+
License Text
|
| 1515 |
+
The MIT License
|
| 1516 |
+
|
| 1517 |
+
Copyright (c) 2016 OpenAI (http://openai.com), 2016 Google Inc.
|
| 1518 |
+
|
| 1519 |
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
| 1520 |
+
of this software and associated documentation files (the "Software"), to deal
|
| 1521 |
+
in the Software without restriction, including without limitation the rights
|
| 1522 |
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
| 1523 |
+
copies of the Software, and to permit persons to whom the Software is
|
| 1524 |
+
furnished to do so, subject to the following conditions:
|
| 1525 |
+
|
| 1526 |
+
The above copyright notice and this permission notice shall be included in
|
| 1527 |
+
all copies or substantial portions of the Software.
|
| 1528 |
+
|
| 1529 |
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
| 1530 |
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
| 1531 |
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
| 1532 |
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
| 1533 |
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
| 1534 |
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
| 1535 |
+
THE SOFTWARE.
|
| 1536 |
+
|
| 1537 |
+
19. Licensee's use of the Visual Studio Setup Configuration
|
| 1538 |
+
Samples is subject to the following license:
|
| 1539 |
+
|
| 1540 |
+
The MIT License (MIT)
|
| 1541 |
+
Copyright (C) Microsoft Corporation. All rights reserved.
|
| 1542 |
+
|
| 1543 |
+
Permission is hereby granted, free of charge, to any person
|
| 1544 |
+
obtaining a copy of this software and associated documentation
|
| 1545 |
+
files (the "Software"), to deal in the Software without restriction,
|
| 1546 |
+
including without limitation the rights to use, copy, modify, merge,
|
| 1547 |
+
publish, distribute, sublicense, and/or sell copies of the Software,
|
| 1548 |
+
and to permit persons to whom the Software is furnished to do so,
|
| 1549 |
+
subject to the following conditions:
|
| 1550 |
+
|
| 1551 |
+
The above copyright notice and this permission notice shall be included
|
| 1552 |
+
in all copies or substantial portions of the Software.
|
| 1553 |
+
|
| 1554 |
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
| 1555 |
+
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
| 1556 |
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
| 1557 |
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
| 1558 |
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
| 1559 |
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
| 1560 |
+
|
| 1561 |
+
20. Licensee's use of linmath.h header for CPU functions for
|
| 1562 |
+
GL vector/matrix operations from lunarG is subject to the
|
| 1563 |
+
Apache License Version 2.0.
|
| 1564 |
+
|
| 1565 |
+
21. The DX12-CUDA sample uses the d3dx12.h header, which is
|
| 1566 |
+
subject to the MIT license .
|
| 1567 |
+
|
| 1568 |
+
-----------------
|
tuning-competition-baseline/.venv/lib/python3.11/site-packages/nvidia_cufft_cu11-10.9.0.58.dist-info/METADATA
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Metadata-Version: 2.1
|
| 2 |
+
Name: nvidia-cufft-cu11
|
| 3 |
+
Version: 10.9.0.58
|
| 4 |
+
Summary: CUFFT native runtime libraries
|
| 5 |
+
Home-page: https://developer.nvidia.com/cuda-zone
|
| 6 |
+
Author: Nvidia CUDA Installer Team
|
| 7 |
+
Author-email: cuda_installer@nvidia.com
|
| 8 |
+
License: NVIDIA Proprietary Software
|
| 9 |
+
Keywords: cuda,nvidia,runtime,machine learning,deep learning
|
| 10 |
+
Classifier: Development Status :: 4 - Beta
|
| 11 |
+
Classifier: Intended Audience :: Developers
|
| 12 |
+
Classifier: Intended Audience :: Education
|
| 13 |
+
Classifier: Intended Audience :: Science/Research
|
| 14 |
+
Classifier: License :: Other/Proprietary License
|
| 15 |
+
Classifier: Natural Language :: English
|
| 16 |
+
Classifier: Programming Language :: Python :: 3
|
| 17 |
+
Classifier: Programming Language :: Python :: 3.5
|
| 18 |
+
Classifier: Programming Language :: Python :: 3.6
|
| 19 |
+
Classifier: Programming Language :: Python :: 3.7
|
| 20 |
+
Classifier: Programming Language :: Python :: 3.8
|
| 21 |
+
Classifier: Programming Language :: Python :: 3.9
|
| 22 |
+
Classifier: Programming Language :: Python :: 3.10
|
| 23 |
+
Classifier: Programming Language :: Python :: 3.11
|
| 24 |
+
Classifier: Programming Language :: Python :: 3 :: Only
|
| 25 |
+
Classifier: Topic :: Scientific/Engineering
|
| 26 |
+
Classifier: Topic :: Scientific/Engineering :: Mathematics
|
| 27 |
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
| 28 |
+
Classifier: Topic :: Software Development
|
| 29 |
+
Classifier: Topic :: Software Development :: Libraries
|
| 30 |
+
Classifier: Operating System :: Microsoft :: Windows
|
| 31 |
+
Classifier: Operating System :: POSIX :: Linux
|
| 32 |
+
Requires-Python: >=3
|
| 33 |
+
License-File: License.txt
|
| 34 |
+
|
| 35 |
+
CUFFT native runtime libraries
|
tuning-competition-baseline/.venv/lib/python3.11/site-packages/nvidia_cufft_cu11-10.9.0.58.dist-info/WHEEL
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Wheel-Version: 1.0
|
| 2 |
+
Generator: bdist_wheel (0.37.1)
|
| 3 |
+
Root-Is-Purelib: true
|
| 4 |
+
Tag: py3-none-manylinux1_x86_64
|
| 5 |
+
|
tuning-competition-baseline/.venv/lib/python3.11/site-packages/nvidia_cufft_cu11-10.9.0.58.dist-info/top_level.txt
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
nvidia
|
tuning-competition-baseline/.venv/lib/python3.11/site-packages/nvidia_cusolver_cu11-11.4.1.48.dist-info/METADATA
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Metadata-Version: 2.1
|
| 2 |
+
Name: nvidia-cusolver-cu11
|
| 3 |
+
Version: 11.4.1.48
|
| 4 |
+
Summary: CUDA solver native runtime libraries
|
| 5 |
+
Home-page: https://developer.nvidia.com/cuda-zone
|
| 6 |
+
Author: Nvidia CUDA Installer Team
|
| 7 |
+
Author-email: cuda_installer@nvidia.com
|
| 8 |
+
License: NVIDIA Proprietary Software
|
| 9 |
+
Keywords: cuda,nvidia,runtime,machine learning,deep learning
|
| 10 |
+
Classifier: Development Status :: 4 - Beta
|
| 11 |
+
Classifier: Intended Audience :: Developers
|
| 12 |
+
Classifier: Intended Audience :: Education
|
| 13 |
+
Classifier: Intended Audience :: Science/Research
|
| 14 |
+
Classifier: License :: Other/Proprietary License
|
| 15 |
+
Classifier: Natural Language :: English
|
| 16 |
+
Classifier: Programming Language :: Python :: 3
|
| 17 |
+
Classifier: Programming Language :: Python :: 3.5
|
| 18 |
+
Classifier: Programming Language :: Python :: 3.6
|
| 19 |
+
Classifier: Programming Language :: Python :: 3.7
|
| 20 |
+
Classifier: Programming Language :: Python :: 3.8
|
| 21 |
+
Classifier: Programming Language :: Python :: 3.9
|
| 22 |
+
Classifier: Programming Language :: Python :: 3.10
|
| 23 |
+
Classifier: Programming Language :: Python :: 3.11
|
| 24 |
+
Classifier: Programming Language :: Python :: 3 :: Only
|
| 25 |
+
Classifier: Topic :: Scientific/Engineering
|
| 26 |
+
Classifier: Topic :: Scientific/Engineering :: Mathematics
|
| 27 |
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
| 28 |
+
Classifier: Topic :: Software Development
|
| 29 |
+
Classifier: Topic :: Software Development :: Libraries
|
| 30 |
+
Classifier: Operating System :: Microsoft :: Windows
|
| 31 |
+
Classifier: Operating System :: POSIX :: Linux
|
| 32 |
+
Requires-Python: >=3
|
| 33 |
+
License-File: License.txt
|
| 34 |
+
Requires-Dist: nvidia-cublas-cu11
|
| 35 |
+
|
| 36 |
+
CUDA solver native runtime libraries
|
tuning-competition-baseline/.venv/lib/python3.11/site-packages/nvidia_cusolver_cu11-11.4.1.48.dist-info/top_level.txt
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
nvidia
|
tuning-competition-baseline/.venv/lib/python3.11/site-packages/pip/_internal/commands/__init__.py
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Package containing all pip commands
|
| 3 |
+
"""
|
| 4 |
+
|
| 5 |
+
import importlib
|
| 6 |
+
from collections import namedtuple
|
| 7 |
+
from typing import Any, Dict, Optional
|
| 8 |
+
|
| 9 |
+
from pip._internal.cli.base_command import Command
|
| 10 |
+
|
| 11 |
+
CommandInfo = namedtuple("CommandInfo", "module_path, class_name, summary")
|
| 12 |
+
|
| 13 |
+
# This dictionary does a bunch of heavy lifting for help output:
|
| 14 |
+
# - Enables avoiding additional (costly) imports for presenting `--help`.
|
| 15 |
+
# - The ordering matters for help display.
|
| 16 |
+
#
|
| 17 |
+
# Even though the module path starts with the same "pip._internal.commands"
|
| 18 |
+
# prefix, the full path makes testing easier (specifically when modifying
|
| 19 |
+
# `commands_dict` in test setup / teardown).
|
| 20 |
+
commands_dict: Dict[str, CommandInfo] = {
|
| 21 |
+
"install": CommandInfo(
|
| 22 |
+
"pip._internal.commands.install",
|
| 23 |
+
"InstallCommand",
|
| 24 |
+
"Install packages.",
|
| 25 |
+
),
|
| 26 |
+
"download": CommandInfo(
|
| 27 |
+
"pip._internal.commands.download",
|
| 28 |
+
"DownloadCommand",
|
| 29 |
+
"Download packages.",
|
| 30 |
+
),
|
| 31 |
+
"uninstall": CommandInfo(
|
| 32 |
+
"pip._internal.commands.uninstall",
|
| 33 |
+
"UninstallCommand",
|
| 34 |
+
"Uninstall packages.",
|
| 35 |
+
),
|
| 36 |
+
"freeze": CommandInfo(
|
| 37 |
+
"pip._internal.commands.freeze",
|
| 38 |
+
"FreezeCommand",
|
| 39 |
+
"Output installed packages in requirements format.",
|
| 40 |
+
),
|
| 41 |
+
"inspect": CommandInfo(
|
| 42 |
+
"pip._internal.commands.inspect",
|
| 43 |
+
"InspectCommand",
|
| 44 |
+
"Inspect the python environment.",
|
| 45 |
+
),
|
| 46 |
+
"list": CommandInfo(
|
| 47 |
+
"pip._internal.commands.list",
|
| 48 |
+
"ListCommand",
|
| 49 |
+
"List installed packages.",
|
| 50 |
+
),
|
| 51 |
+
"show": CommandInfo(
|
| 52 |
+
"pip._internal.commands.show",
|
| 53 |
+
"ShowCommand",
|
| 54 |
+
"Show information about installed packages.",
|
| 55 |
+
),
|
| 56 |
+
"check": CommandInfo(
|
| 57 |
+
"pip._internal.commands.check",
|
| 58 |
+
"CheckCommand",
|
| 59 |
+
"Verify installed packages have compatible dependencies.",
|
| 60 |
+
),
|
| 61 |
+
"config": CommandInfo(
|
| 62 |
+
"pip._internal.commands.configuration",
|
| 63 |
+
"ConfigurationCommand",
|
| 64 |
+
"Manage local and global configuration.",
|
| 65 |
+
),
|
| 66 |
+
"search": CommandInfo(
|
| 67 |
+
"pip._internal.commands.search",
|
| 68 |
+
"SearchCommand",
|
| 69 |
+
"Search PyPI for packages.",
|
| 70 |
+
),
|
| 71 |
+
"cache": CommandInfo(
|
| 72 |
+
"pip._internal.commands.cache",
|
| 73 |
+
"CacheCommand",
|
| 74 |
+
"Inspect and manage pip's wheel cache.",
|
| 75 |
+
),
|
| 76 |
+
"index": CommandInfo(
|
| 77 |
+
"pip._internal.commands.index",
|
| 78 |
+
"IndexCommand",
|
| 79 |
+
"Inspect information available from package indexes.",
|
| 80 |
+
),
|
| 81 |
+
"wheel": CommandInfo(
|
| 82 |
+
"pip._internal.commands.wheel",
|
| 83 |
+
"WheelCommand",
|
| 84 |
+
"Build wheels from your requirements.",
|
| 85 |
+
),
|
| 86 |
+
"hash": CommandInfo(
|
| 87 |
+
"pip._internal.commands.hash",
|
| 88 |
+
"HashCommand",
|
| 89 |
+
"Compute hashes of package archives.",
|
| 90 |
+
),
|
| 91 |
+
"completion": CommandInfo(
|
| 92 |
+
"pip._internal.commands.completion",
|
| 93 |
+
"CompletionCommand",
|
| 94 |
+
"A helper command used for command completion.",
|
| 95 |
+
),
|
| 96 |
+
"debug": CommandInfo(
|
| 97 |
+
"pip._internal.commands.debug",
|
| 98 |
+
"DebugCommand",
|
| 99 |
+
"Show information useful for debugging.",
|
| 100 |
+
),
|
| 101 |
+
"help": CommandInfo(
|
| 102 |
+
"pip._internal.commands.help",
|
| 103 |
+
"HelpCommand",
|
| 104 |
+
"Show help for commands.",
|
| 105 |
+
),
|
| 106 |
+
}
|
| 107 |
+
|
| 108 |
+
|
| 109 |
+
def create_command(name: str, **kwargs: Any) -> Command:
|
| 110 |
+
"""
|
| 111 |
+
Create an instance of the Command class with the given name.
|
| 112 |
+
"""
|
| 113 |
+
module_path, class_name, summary = commands_dict[name]
|
| 114 |
+
module = importlib.import_module(module_path)
|
| 115 |
+
command_class = getattr(module, class_name)
|
| 116 |
+
command = command_class(name=name, summary=summary, **kwargs)
|
| 117 |
+
|
| 118 |
+
return command
|
| 119 |
+
|
| 120 |
+
|
| 121 |
+
def get_similar_commands(name: str) -> Optional[str]:
|
| 122 |
+
"""Command name auto-correct."""
|
| 123 |
+
from difflib import get_close_matches
|
| 124 |
+
|
| 125 |
+
name = name.lower()
|
| 126 |
+
|
| 127 |
+
close_commands = get_close_matches(name, commands_dict.keys())
|
| 128 |
+
|
| 129 |
+
if close_commands:
|
| 130 |
+
return close_commands[0]
|
| 131 |
+
else:
|
| 132 |
+
return None
|
tuning-competition-baseline/.venv/lib/python3.11/site-packages/pip/_internal/commands/__pycache__/configuration.cpython-311.pyc
ADDED
|
Binary file (14.9 kB). View file
|
|
|
tuning-competition-baseline/.venv/lib/python3.11/site-packages/pip/_internal/commands/__pycache__/hash.cpython-311.pyc
ADDED
|
Binary file (3.38 kB). View file
|
|
|
tuning-competition-baseline/.venv/lib/python3.11/site-packages/pip/_internal/commands/__pycache__/help.cpython-311.pyc
ADDED
|
Binary file (1.99 kB). View file
|
|
|
tuning-competition-baseline/.venv/lib/python3.11/site-packages/pip/_internal/commands/__pycache__/inspect.cpython-311.pyc
ADDED
|
Binary file (4.47 kB). View file
|
|
|
tuning-competition-baseline/.venv/lib/python3.11/site-packages/pip/_internal/commands/__pycache__/install.cpython-311.pyc
ADDED
|
Binary file (31.4 kB). View file
|
|
|
tuning-competition-baseline/.venv/lib/python3.11/site-packages/pip/_internal/commands/__pycache__/uninstall.cpython-311.pyc
ADDED
|
Binary file (5.18 kB). View file
|
|
|
tuning-competition-baseline/.venv/lib/python3.11/site-packages/pip/_internal/commands/check.py
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import logging
|
| 2 |
+
from optparse import Values
|
| 3 |
+
from typing import List
|
| 4 |
+
|
| 5 |
+
from pip._internal.cli.base_command import Command
|
| 6 |
+
from pip._internal.cli.status_codes import ERROR, SUCCESS
|
| 7 |
+
from pip._internal.metadata import get_default_environment
|
| 8 |
+
from pip._internal.operations.check import (
|
| 9 |
+
check_package_set,
|
| 10 |
+
check_unsupported,
|
| 11 |
+
create_package_set_from_installed,
|
| 12 |
+
)
|
| 13 |
+
from pip._internal.utils.compatibility_tags import get_supported
|
| 14 |
+
from pip._internal.utils.misc import write_output
|
| 15 |
+
|
| 16 |
+
logger = logging.getLogger(__name__)
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
class CheckCommand(Command):
|
| 20 |
+
"""Verify installed packages have compatible dependencies."""
|
| 21 |
+
|
| 22 |
+
ignore_require_venv = True
|
| 23 |
+
usage = """
|
| 24 |
+
%prog [options]"""
|
| 25 |
+
|
| 26 |
+
def run(self, options: Values, args: List[str]) -> int:
|
| 27 |
+
package_set, parsing_probs = create_package_set_from_installed()
|
| 28 |
+
missing, conflicting = check_package_set(package_set)
|
| 29 |
+
unsupported = list(
|
| 30 |
+
check_unsupported(
|
| 31 |
+
get_default_environment().iter_installed_distributions(),
|
| 32 |
+
get_supported(),
|
| 33 |
+
)
|
| 34 |
+
)
|
| 35 |
+
|
| 36 |
+
for project_name in missing:
|
| 37 |
+
version = package_set[project_name].version
|
| 38 |
+
for dependency in missing[project_name]:
|
| 39 |
+
write_output(
|
| 40 |
+
"%s %s requires %s, which is not installed.",
|
| 41 |
+
project_name,
|
| 42 |
+
version,
|
| 43 |
+
dependency[0],
|
| 44 |
+
)
|
| 45 |
+
|
| 46 |
+
for project_name in conflicting:
|
| 47 |
+
version = package_set[project_name].version
|
| 48 |
+
for dep_name, dep_version, req in conflicting[project_name]:
|
| 49 |
+
write_output(
|
| 50 |
+
"%s %s has requirement %s, but you have %s %s.",
|
| 51 |
+
project_name,
|
| 52 |
+
version,
|
| 53 |
+
req,
|
| 54 |
+
dep_name,
|
| 55 |
+
dep_version,
|
| 56 |
+
)
|
| 57 |
+
for package in unsupported:
|
| 58 |
+
write_output(
|
| 59 |
+
"%s %s is not supported on this platform",
|
| 60 |
+
package.raw_name,
|
| 61 |
+
package.version,
|
| 62 |
+
)
|
| 63 |
+
if missing or conflicting or parsing_probs or unsupported:
|
| 64 |
+
return ERROR
|
| 65 |
+
else:
|
| 66 |
+
write_output("No broken requirements found.")
|
| 67 |
+
return SUCCESS
|
tuning-competition-baseline/.venv/lib/python3.11/site-packages/pip/_internal/commands/completion.py
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import sys
|
| 2 |
+
import textwrap
|
| 3 |
+
from optparse import Values
|
| 4 |
+
from typing import List
|
| 5 |
+
|
| 6 |
+
from pip._internal.cli.base_command import Command
|
| 7 |
+
from pip._internal.cli.status_codes import SUCCESS
|
| 8 |
+
from pip._internal.utils.misc import get_prog
|
| 9 |
+
|
| 10 |
+
BASE_COMPLETION = """
|
| 11 |
+
# pip {shell} completion start{script}# pip {shell} completion end
|
| 12 |
+
"""
|
| 13 |
+
|
| 14 |
+
COMPLETION_SCRIPTS = {
|
| 15 |
+
"bash": """
|
| 16 |
+
_pip_completion()
|
| 17 |
+
{{
|
| 18 |
+
COMPREPLY=( $( COMP_WORDS="${{COMP_WORDS[*]}}" \\
|
| 19 |
+
COMP_CWORD=$COMP_CWORD \\
|
| 20 |
+
PIP_AUTO_COMPLETE=1 $1 2>/dev/null ) )
|
| 21 |
+
}}
|
| 22 |
+
complete -o default -F _pip_completion {prog}
|
| 23 |
+
""",
|
| 24 |
+
"zsh": """
|
| 25 |
+
#compdef -P pip[0-9.]#
|
| 26 |
+
__pip() {{
|
| 27 |
+
compadd $( COMP_WORDS="$words[*]" \\
|
| 28 |
+
COMP_CWORD=$((CURRENT-1)) \\
|
| 29 |
+
PIP_AUTO_COMPLETE=1 $words[1] 2>/dev/null )
|
| 30 |
+
}}
|
| 31 |
+
if [[ $zsh_eval_context[-1] == loadautofunc ]]; then
|
| 32 |
+
# autoload from fpath, call function directly
|
| 33 |
+
__pip "$@"
|
| 34 |
+
else
|
| 35 |
+
# eval/source/. command, register function for later
|
| 36 |
+
compdef __pip -P 'pip[0-9.]#'
|
| 37 |
+
fi
|
| 38 |
+
""",
|
| 39 |
+
"fish": """
|
| 40 |
+
function __fish_complete_pip
|
| 41 |
+
set -lx COMP_WORDS (commandline -o) ""
|
| 42 |
+
set -lx COMP_CWORD ( \\
|
| 43 |
+
math (contains -i -- (commandline -t) $COMP_WORDS)-1 \\
|
| 44 |
+
)
|
| 45 |
+
set -lx PIP_AUTO_COMPLETE 1
|
| 46 |
+
string split \\ -- (eval $COMP_WORDS[1])
|
| 47 |
+
end
|
| 48 |
+
complete -fa "(__fish_complete_pip)" -c {prog}
|
| 49 |
+
""",
|
| 50 |
+
"powershell": """
|
| 51 |
+
if ((Test-Path Function:\\TabExpansion) -and -not `
|
| 52 |
+
(Test-Path Function:\\_pip_completeBackup)) {{
|
| 53 |
+
Rename-Item Function:\\TabExpansion _pip_completeBackup
|
| 54 |
+
}}
|
| 55 |
+
function TabExpansion($line, $lastWord) {{
|
| 56 |
+
$lastBlock = [regex]::Split($line, '[|;]')[-1].TrimStart()
|
| 57 |
+
if ($lastBlock.StartsWith("{prog} ")) {{
|
| 58 |
+
$Env:COMP_WORDS=$lastBlock
|
| 59 |
+
$Env:COMP_CWORD=$lastBlock.Split().Length - 1
|
| 60 |
+
$Env:PIP_AUTO_COMPLETE=1
|
| 61 |
+
(& {prog}).Split()
|
| 62 |
+
Remove-Item Env:COMP_WORDS
|
| 63 |
+
Remove-Item Env:COMP_CWORD
|
| 64 |
+
Remove-Item Env:PIP_AUTO_COMPLETE
|
| 65 |
+
}}
|
| 66 |
+
elseif (Test-Path Function:\\_pip_completeBackup) {{
|
| 67 |
+
# Fall back on existing tab expansion
|
| 68 |
+
_pip_completeBackup $line $lastWord
|
| 69 |
+
}}
|
| 70 |
+
}}
|
| 71 |
+
""",
|
| 72 |
+
}
|
| 73 |
+
|
| 74 |
+
|
| 75 |
+
class CompletionCommand(Command):
|
| 76 |
+
"""A helper command to be used for command completion."""
|
| 77 |
+
|
| 78 |
+
ignore_require_venv = True
|
| 79 |
+
|
| 80 |
+
def add_options(self) -> None:
|
| 81 |
+
self.cmd_opts.add_option(
|
| 82 |
+
"--bash",
|
| 83 |
+
"-b",
|
| 84 |
+
action="store_const",
|
| 85 |
+
const="bash",
|
| 86 |
+
dest="shell",
|
| 87 |
+
help="Emit completion code for bash",
|
| 88 |
+
)
|
| 89 |
+
self.cmd_opts.add_option(
|
| 90 |
+
"--zsh",
|
| 91 |
+
"-z",
|
| 92 |
+
action="store_const",
|
| 93 |
+
const="zsh",
|
| 94 |
+
dest="shell",
|
| 95 |
+
help="Emit completion code for zsh",
|
| 96 |
+
)
|
| 97 |
+
self.cmd_opts.add_option(
|
| 98 |
+
"--fish",
|
| 99 |
+
"-f",
|
| 100 |
+
action="store_const",
|
| 101 |
+
const="fish",
|
| 102 |
+
dest="shell",
|
| 103 |
+
help="Emit completion code for fish",
|
| 104 |
+
)
|
| 105 |
+
self.cmd_opts.add_option(
|
| 106 |
+
"--powershell",
|
| 107 |
+
"-p",
|
| 108 |
+
action="store_const",
|
| 109 |
+
const="powershell",
|
| 110 |
+
dest="shell",
|
| 111 |
+
help="Emit completion code for powershell",
|
| 112 |
+
)
|
| 113 |
+
|
| 114 |
+
self.parser.insert_option_group(0, self.cmd_opts)
|
| 115 |
+
|
| 116 |
+
def run(self, options: Values, args: List[str]) -> int:
|
| 117 |
+
"""Prints the completion code of the given shell"""
|
| 118 |
+
shells = COMPLETION_SCRIPTS.keys()
|
| 119 |
+
shell_options = ["--" + shell for shell in sorted(shells)]
|
| 120 |
+
if options.shell in shells:
|
| 121 |
+
script = textwrap.dedent(
|
| 122 |
+
COMPLETION_SCRIPTS.get(options.shell, "").format(prog=get_prog())
|
| 123 |
+
)
|
| 124 |
+
print(BASE_COMPLETION.format(script=script, shell=options.shell))
|
| 125 |
+
return SUCCESS
|
| 126 |
+
else:
|
| 127 |
+
sys.stderr.write(
|
| 128 |
+
"ERROR: You must pass {}\n".format(" or ".join(shell_options))
|
| 129 |
+
)
|
| 130 |
+
return SUCCESS
|
tuning-competition-baseline/.venv/lib/python3.11/site-packages/pip/_internal/commands/inspect.py
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import logging
|
| 2 |
+
from optparse import Values
|
| 3 |
+
from typing import Any, Dict, List
|
| 4 |
+
|
| 5 |
+
from pip._vendor.packaging.markers import default_environment
|
| 6 |
+
from pip._vendor.rich import print_json
|
| 7 |
+
|
| 8 |
+
from pip import __version__
|
| 9 |
+
from pip._internal.cli import cmdoptions
|
| 10 |
+
from pip._internal.cli.base_command import Command
|
| 11 |
+
from pip._internal.cli.status_codes import SUCCESS
|
| 12 |
+
from pip._internal.metadata import BaseDistribution, get_environment
|
| 13 |
+
from pip._internal.utils.compat import stdlib_pkgs
|
| 14 |
+
from pip._internal.utils.urls import path_to_url
|
| 15 |
+
|
| 16 |
+
logger = logging.getLogger(__name__)
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
class InspectCommand(Command):
|
| 20 |
+
"""
|
| 21 |
+
Inspect the content of a Python environment and produce a report in JSON format.
|
| 22 |
+
"""
|
| 23 |
+
|
| 24 |
+
ignore_require_venv = True
|
| 25 |
+
usage = """
|
| 26 |
+
%prog [options]"""
|
| 27 |
+
|
| 28 |
+
def add_options(self) -> None:
|
| 29 |
+
self.cmd_opts.add_option(
|
| 30 |
+
"--local",
|
| 31 |
+
action="store_true",
|
| 32 |
+
default=False,
|
| 33 |
+
help=(
|
| 34 |
+
"If in a virtualenv that has global access, do not list "
|
| 35 |
+
"globally-installed packages."
|
| 36 |
+
),
|
| 37 |
+
)
|
| 38 |
+
self.cmd_opts.add_option(
|
| 39 |
+
"--user",
|
| 40 |
+
dest="user",
|
| 41 |
+
action="store_true",
|
| 42 |
+
default=False,
|
| 43 |
+
help="Only output packages installed in user-site.",
|
| 44 |
+
)
|
| 45 |
+
self.cmd_opts.add_option(cmdoptions.list_path())
|
| 46 |
+
self.parser.insert_option_group(0, self.cmd_opts)
|
| 47 |
+
|
| 48 |
+
def run(self, options: Values, args: List[str]) -> int:
|
| 49 |
+
cmdoptions.check_list_path_option(options)
|
| 50 |
+
dists = get_environment(options.path).iter_installed_distributions(
|
| 51 |
+
local_only=options.local,
|
| 52 |
+
user_only=options.user,
|
| 53 |
+
skip=set(stdlib_pkgs),
|
| 54 |
+
)
|
| 55 |
+
output = {
|
| 56 |
+
"version": "1",
|
| 57 |
+
"pip_version": __version__,
|
| 58 |
+
"installed": [self._dist_to_dict(dist) for dist in dists],
|
| 59 |
+
"environment": default_environment(),
|
| 60 |
+
# TODO tags? scheme?
|
| 61 |
+
}
|
| 62 |
+
print_json(data=output)
|
| 63 |
+
return SUCCESS
|
| 64 |
+
|
| 65 |
+
def _dist_to_dict(self, dist: BaseDistribution) -> Dict[str, Any]:
|
| 66 |
+
res: Dict[str, Any] = {
|
| 67 |
+
"metadata": dist.metadata_dict,
|
| 68 |
+
"metadata_location": dist.info_location,
|
| 69 |
+
}
|
| 70 |
+
# direct_url. Note that we don't have download_info (as in the installation
|
| 71 |
+
# report) since it is not recorded in installed metadata.
|
| 72 |
+
direct_url = dist.direct_url
|
| 73 |
+
if direct_url is not None:
|
| 74 |
+
res["direct_url"] = direct_url.to_dict()
|
| 75 |
+
else:
|
| 76 |
+
# Emulate direct_url for legacy editable installs.
|
| 77 |
+
editable_project_location = dist.editable_project_location
|
| 78 |
+
if editable_project_location is not None:
|
| 79 |
+
res["direct_url"] = {
|
| 80 |
+
"url": path_to_url(editable_project_location),
|
| 81 |
+
"dir_info": {
|
| 82 |
+
"editable": True,
|
| 83 |
+
},
|
| 84 |
+
}
|
| 85 |
+
# installer
|
| 86 |
+
installer = dist.installer
|
| 87 |
+
if dist.installer:
|
| 88 |
+
res["installer"] = installer
|
| 89 |
+
# requested
|
| 90 |
+
if dist.installed_with_dist_info:
|
| 91 |
+
res["requested"] = dist.requested
|
| 92 |
+
return res
|