ob-metaflow 2.15.13.1__py2.py3-none-any.whl → 2.19.7.1rc0__py2.py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (169) hide show
  1. metaflow/__init__.py +10 -3
  2. metaflow/_vendor/imghdr/__init__.py +186 -0
  3. metaflow/_vendor/yaml/__init__.py +427 -0
  4. metaflow/_vendor/yaml/composer.py +139 -0
  5. metaflow/_vendor/yaml/constructor.py +748 -0
  6. metaflow/_vendor/yaml/cyaml.py +101 -0
  7. metaflow/_vendor/yaml/dumper.py +62 -0
  8. metaflow/_vendor/yaml/emitter.py +1137 -0
  9. metaflow/_vendor/yaml/error.py +75 -0
  10. metaflow/_vendor/yaml/events.py +86 -0
  11. metaflow/_vendor/yaml/loader.py +63 -0
  12. metaflow/_vendor/yaml/nodes.py +49 -0
  13. metaflow/_vendor/yaml/parser.py +589 -0
  14. metaflow/_vendor/yaml/reader.py +185 -0
  15. metaflow/_vendor/yaml/representer.py +389 -0
  16. metaflow/_vendor/yaml/resolver.py +227 -0
  17. metaflow/_vendor/yaml/scanner.py +1435 -0
  18. metaflow/_vendor/yaml/serializer.py +111 -0
  19. metaflow/_vendor/yaml/tokens.py +104 -0
  20. metaflow/cards.py +4 -0
  21. metaflow/cli.py +125 -21
  22. metaflow/cli_components/init_cmd.py +1 -0
  23. metaflow/cli_components/run_cmds.py +204 -40
  24. metaflow/cli_components/step_cmd.py +160 -4
  25. metaflow/client/__init__.py +1 -0
  26. metaflow/client/core.py +198 -130
  27. metaflow/client/filecache.py +59 -32
  28. metaflow/cmd/code/__init__.py +2 -1
  29. metaflow/cmd/develop/stub_generator.py +49 -18
  30. metaflow/cmd/develop/stubs.py +9 -27
  31. metaflow/cmd/make_wrapper.py +30 -0
  32. metaflow/datastore/__init__.py +1 -0
  33. metaflow/datastore/content_addressed_store.py +40 -9
  34. metaflow/datastore/datastore_set.py +10 -1
  35. metaflow/datastore/flow_datastore.py +124 -4
  36. metaflow/datastore/spin_datastore.py +91 -0
  37. metaflow/datastore/task_datastore.py +92 -6
  38. metaflow/debug.py +5 -0
  39. metaflow/decorators.py +331 -82
  40. metaflow/extension_support/__init__.py +414 -356
  41. metaflow/extension_support/_empty_file.py +2 -2
  42. metaflow/flowspec.py +322 -82
  43. metaflow/graph.py +178 -15
  44. metaflow/includefile.py +25 -3
  45. metaflow/lint.py +94 -3
  46. metaflow/meta_files.py +13 -0
  47. metaflow/metadata_provider/metadata.py +13 -2
  48. metaflow/metaflow_config.py +66 -4
  49. metaflow/metaflow_environment.py +91 -25
  50. metaflow/metaflow_profile.py +18 -0
  51. metaflow/metaflow_version.py +16 -1
  52. metaflow/package/__init__.py +673 -0
  53. metaflow/packaging_sys/__init__.py +880 -0
  54. metaflow/packaging_sys/backend.py +128 -0
  55. metaflow/packaging_sys/distribution_support.py +153 -0
  56. metaflow/packaging_sys/tar_backend.py +99 -0
  57. metaflow/packaging_sys/utils.py +54 -0
  58. metaflow/packaging_sys/v1.py +527 -0
  59. metaflow/parameters.py +6 -2
  60. metaflow/plugins/__init__.py +6 -0
  61. metaflow/plugins/airflow/airflow.py +11 -1
  62. metaflow/plugins/airflow/airflow_cli.py +16 -5
  63. metaflow/plugins/argo/argo_client.py +42 -20
  64. metaflow/plugins/argo/argo_events.py +6 -6
  65. metaflow/plugins/argo/argo_workflows.py +1023 -344
  66. metaflow/plugins/argo/argo_workflows_cli.py +396 -94
  67. metaflow/plugins/argo/argo_workflows_decorator.py +9 -0
  68. metaflow/plugins/argo/argo_workflows_deployer_objects.py +75 -49
  69. metaflow/plugins/argo/capture_error.py +5 -2
  70. metaflow/plugins/argo/conditional_input_paths.py +35 -0
  71. metaflow/plugins/argo/exit_hooks.py +209 -0
  72. metaflow/plugins/argo/param_val.py +19 -0
  73. metaflow/plugins/aws/aws_client.py +6 -0
  74. metaflow/plugins/aws/aws_utils.py +33 -1
  75. metaflow/plugins/aws/batch/batch.py +72 -5
  76. metaflow/plugins/aws/batch/batch_cli.py +24 -3
  77. metaflow/plugins/aws/batch/batch_decorator.py +57 -6
  78. metaflow/plugins/aws/step_functions/step_functions.py +28 -3
  79. metaflow/plugins/aws/step_functions/step_functions_cli.py +49 -4
  80. metaflow/plugins/aws/step_functions/step_functions_deployer.py +3 -0
  81. metaflow/plugins/aws/step_functions/step_functions_deployer_objects.py +30 -0
  82. metaflow/plugins/cards/card_cli.py +20 -1
  83. metaflow/plugins/cards/card_creator.py +24 -1
  84. metaflow/plugins/cards/card_datastore.py +21 -49
  85. metaflow/plugins/cards/card_decorator.py +58 -6
  86. metaflow/plugins/cards/card_modules/basic.py +38 -9
  87. metaflow/plugins/cards/card_modules/bundle.css +1 -1
  88. metaflow/plugins/cards/card_modules/chevron/renderer.py +1 -1
  89. metaflow/plugins/cards/card_modules/components.py +592 -3
  90. metaflow/plugins/cards/card_modules/convert_to_native_type.py +34 -5
  91. metaflow/plugins/cards/card_modules/json_viewer.py +232 -0
  92. metaflow/plugins/cards/card_modules/main.css +1 -0
  93. metaflow/plugins/cards/card_modules/main.js +56 -41
  94. metaflow/plugins/cards/card_modules/test_cards.py +22 -6
  95. metaflow/plugins/cards/component_serializer.py +1 -8
  96. metaflow/plugins/cards/metadata.py +22 -0
  97. metaflow/plugins/catch_decorator.py +9 -0
  98. metaflow/plugins/datastores/local_storage.py +12 -6
  99. metaflow/plugins/datastores/spin_storage.py +12 -0
  100. metaflow/plugins/datatools/s3/s3.py +49 -17
  101. metaflow/plugins/datatools/s3/s3op.py +113 -66
  102. metaflow/plugins/env_escape/client_modules.py +102 -72
  103. metaflow/plugins/events_decorator.py +127 -121
  104. metaflow/plugins/exit_hook/__init__.py +0 -0
  105. metaflow/plugins/exit_hook/exit_hook_decorator.py +46 -0
  106. metaflow/plugins/exit_hook/exit_hook_script.py +52 -0
  107. metaflow/plugins/kubernetes/kubernetes.py +12 -1
  108. metaflow/plugins/kubernetes/kubernetes_cli.py +11 -0
  109. metaflow/plugins/kubernetes/kubernetes_decorator.py +25 -6
  110. metaflow/plugins/kubernetes/kubernetes_job.py +12 -4
  111. metaflow/plugins/kubernetes/kubernetes_jobsets.py +31 -30
  112. metaflow/plugins/metadata_providers/local.py +76 -82
  113. metaflow/plugins/metadata_providers/service.py +13 -9
  114. metaflow/plugins/metadata_providers/spin.py +16 -0
  115. metaflow/plugins/package_cli.py +36 -24
  116. metaflow/plugins/parallel_decorator.py +11 -2
  117. metaflow/plugins/parsers.py +16 -0
  118. metaflow/plugins/pypi/bootstrap.py +7 -1
  119. metaflow/plugins/pypi/conda_decorator.py +41 -82
  120. metaflow/plugins/pypi/conda_environment.py +14 -6
  121. metaflow/plugins/pypi/micromamba.py +9 -1
  122. metaflow/plugins/pypi/pip.py +41 -5
  123. metaflow/plugins/pypi/pypi_decorator.py +4 -4
  124. metaflow/plugins/pypi/utils.py +22 -0
  125. metaflow/plugins/secrets/__init__.py +3 -0
  126. metaflow/plugins/secrets/secrets_decorator.py +14 -178
  127. metaflow/plugins/secrets/secrets_func.py +49 -0
  128. metaflow/plugins/secrets/secrets_spec.py +101 -0
  129. metaflow/plugins/secrets/utils.py +74 -0
  130. metaflow/plugins/test_unbounded_foreach_decorator.py +2 -2
  131. metaflow/plugins/timeout_decorator.py +0 -1
  132. metaflow/plugins/uv/bootstrap.py +29 -1
  133. metaflow/plugins/uv/uv_environment.py +5 -3
  134. metaflow/pylint_wrapper.py +5 -1
  135. metaflow/runner/click_api.py +79 -26
  136. metaflow/runner/deployer.py +208 -6
  137. metaflow/runner/deployer_impl.py +32 -12
  138. metaflow/runner/metaflow_runner.py +266 -33
  139. metaflow/runner/subprocess_manager.py +21 -1
  140. metaflow/runner/utils.py +27 -16
  141. metaflow/runtime.py +660 -66
  142. metaflow/task.py +255 -26
  143. metaflow/user_configs/config_options.py +33 -21
  144. metaflow/user_configs/config_parameters.py +220 -58
  145. metaflow/user_decorators/__init__.py +0 -0
  146. metaflow/user_decorators/common.py +144 -0
  147. metaflow/user_decorators/mutable_flow.py +512 -0
  148. metaflow/user_decorators/mutable_step.py +424 -0
  149. metaflow/user_decorators/user_flow_decorator.py +264 -0
  150. metaflow/user_decorators/user_step_decorator.py +749 -0
  151. metaflow/util.py +197 -7
  152. metaflow/vendor.py +23 -7
  153. metaflow/version.py +1 -1
  154. {ob_metaflow-2.15.13.1.data → ob_metaflow-2.19.7.1rc0.data}/data/share/metaflow/devtools/Makefile +13 -2
  155. {ob_metaflow-2.15.13.1.data → ob_metaflow-2.19.7.1rc0.data}/data/share/metaflow/devtools/Tiltfile +107 -7
  156. {ob_metaflow-2.15.13.1.data → ob_metaflow-2.19.7.1rc0.data}/data/share/metaflow/devtools/pick_services.sh +1 -0
  157. {ob_metaflow-2.15.13.1.dist-info → ob_metaflow-2.19.7.1rc0.dist-info}/METADATA +2 -3
  158. {ob_metaflow-2.15.13.1.dist-info → ob_metaflow-2.19.7.1rc0.dist-info}/RECORD +162 -121
  159. {ob_metaflow-2.15.13.1.dist-info → ob_metaflow-2.19.7.1rc0.dist-info}/WHEEL +1 -1
  160. metaflow/_vendor/v3_5/__init__.py +0 -1
  161. metaflow/_vendor/v3_5/importlib_metadata/__init__.py +0 -644
  162. metaflow/_vendor/v3_5/importlib_metadata/_compat.py +0 -152
  163. metaflow/_vendor/v3_5/zipp.py +0 -329
  164. metaflow/info_file.py +0 -25
  165. metaflow/package.py +0 -203
  166. metaflow/user_configs/config_decorators.py +0 -568
  167. {ob_metaflow-2.15.13.1.dist-info → ob_metaflow-2.19.7.1rc0.dist-info}/entry_points.txt +0 -0
  168. {ob_metaflow-2.15.13.1.dist-info → ob_metaflow-2.19.7.1rc0.dist-info}/licenses/LICENSE +0 -0
  169. {ob_metaflow-2.15.13.1.dist-info → ob_metaflow-2.19.7.1rc0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,128 @@
1
+ from abc import ABC, abstractmethod
2
+ from io import BytesIO
3
+ from typing import Any, IO, List, Optional, Union
4
+
5
+
6
+ class PackagingBackend(ABC):
7
+ _mappings = {}
8
+ type = "none"
9
+
10
+ def __init_subclass__(cls, **kwargs):
11
+ super().__init_subclass__(**kwargs)
12
+ if cls.type in cls._mappings:
13
+ raise ValueError(f"PackagingBackend {cls.type} already exists")
14
+ cls._mappings[cls.type] = cls
15
+
16
+ @classmethod
17
+ def get_backend(cls, name: str) -> "PackagingBackend":
18
+ if name not in cls._mappings:
19
+ raise ValueError(f"PackagingBackend {name} not found")
20
+ return cls._mappings[name]
21
+
22
+ @classmethod
23
+ def backend_type(cls) -> str:
24
+ return cls.type
25
+
26
+ @classmethod
27
+ @abstractmethod
28
+ def get_extract_commands(cls, archive_name: str, dest_dir: str) -> List[str]:
29
+ pass
30
+
31
+ def __init__(self):
32
+ self._archive = None
33
+
34
+ @abstractmethod
35
+ def create(self) -> "PackagingBackend":
36
+ pass
37
+
38
+ @abstractmethod
39
+ def add_file(self, filename: str, arcname: Optional[str] = None):
40
+ pass
41
+
42
+ @abstractmethod
43
+ def add_data(self, data: BytesIO, arcname: str):
44
+ pass
45
+
46
+ @abstractmethod
47
+ def close(self):
48
+ pass
49
+
50
+ @abstractmethod
51
+ def get_blob(self) -> Optional[Union[bytes, bytearray]]:
52
+ pass
53
+
54
+ @classmethod
55
+ @abstractmethod
56
+ def cls_open(cls, content: IO[bytes]) -> Any:
57
+ """Open the archive from the given content."""
58
+ pass
59
+
60
+ @classmethod
61
+ @abstractmethod
62
+ def cls_member_name(cls, member: Union[Any, str]) -> str:
63
+ """
64
+ Returns the name of the member as a string.
65
+ This is used to ensure consistent naming across different archive formats.
66
+ """
67
+ pass
68
+
69
+ @classmethod
70
+ @abstractmethod
71
+ def cls_has_member(cls, archive: Any, name: str) -> bool:
72
+ pass
73
+
74
+ @classmethod
75
+ @abstractmethod
76
+ def cls_get_member(cls, archive: Any, name: str) -> Optional[bytes]:
77
+ pass
78
+
79
+ @classmethod
80
+ @abstractmethod
81
+ def cls_extract_members(
82
+ cls,
83
+ archive: Any,
84
+ members: Optional[List[Any]] = None,
85
+ dest_dir: str = ".",
86
+ ) -> None:
87
+ pass
88
+
89
+ @classmethod
90
+ @abstractmethod
91
+ def cls_list_names(cls, archive: Any) -> Optional[List[str]]:
92
+ pass
93
+
94
+ @classmethod
95
+ @abstractmethod
96
+ def cls_list_members(cls, archive: Any) -> Optional[List[Any]]:
97
+ """List all members in the archive."""
98
+ pass
99
+
100
+ def has_member(self, name: str) -> bool:
101
+ if self._archive:
102
+ return self.cls_has_member(self._archive, name)
103
+ raise ValueError("Cannot check for member in an uncreated archive")
104
+
105
+ def get_member(self, name: str) -> Optional[bytes]:
106
+ if self._archive:
107
+ return self.cls_get_member(self._archive, name)
108
+ raise ValueError("Cannot get member from an uncreated archive")
109
+
110
+ def extract_members(
111
+ self, members: Optional[List[Any]] = None, dest_dir: str = "."
112
+ ) -> None:
113
+ if self._archive:
114
+ self.cls_extract_members(self._archive, members, dest_dir)
115
+ else:
116
+ raise ValueError("Cannot extract from an uncreated archive")
117
+
118
+ def list_names(self) -> Optional[List[str]]:
119
+ if self._archive:
120
+ return self.cls_list_names(self._archive)
121
+ raise ValueError("Cannot list names from an uncreated archive")
122
+
123
+ def __enter__(self):
124
+ self.create()
125
+ return self
126
+
127
+ def __exit__(self, exc_type, exc_value, traceback):
128
+ self.close()
@@ -0,0 +1,153 @@
1
+ # Support saving of distribution information so we can give it back to users even
2
+ # if we do not install those distributions. This is used to package distributions in
3
+ # the MetaflowCodeContent package and provide an experience as if the packages were installed
4
+ # system-wide.
5
+
6
+ import os
7
+ import re
8
+ import sys
9
+ from pathlib import Path
10
+ from types import ModuleType
11
+ from typing import (
12
+ Callable,
13
+ Dict,
14
+ List,
15
+ Mapping,
16
+ NamedTuple,
17
+ Optional,
18
+ Set,
19
+ TYPE_CHECKING,
20
+ Union,
21
+ cast,
22
+ )
23
+
24
+ import inspect
25
+ from collections import defaultdict
26
+
27
+ from ..extension_support import metadata
28
+ from ..util import get_metaflow_root
29
+
30
+ if TYPE_CHECKING:
31
+ import pathlib
32
+
33
+ _cached_distributions = None
34
+
35
+ packages_distributions = None # type: Optional[Callable[[], Mapping[str, List[str]]]]
36
+ name_normalizer = re.compile(r"[-_.]+")
37
+
38
+ if sys.version_info[:2] >= (3, 10):
39
+ packages_distributions = metadata.packages_distributions
40
+ else:
41
+ # This is the code present in 3.10+ -- we replicate here for other versions
42
+ def _packages_distributions() -> Mapping[str, List[str]]:
43
+ """
44
+ Return a mapping of top-level packages to their
45
+ distributions.
46
+ """
47
+ pkg_to_dist = defaultdict(list)
48
+ for dist in metadata.distributions():
49
+ for pkg in _top_level_declared(dist) or _top_level_inferred(dist):
50
+ pkg_to_dist[pkg].append(dist.metadata["Name"])
51
+ return dict(pkg_to_dist)
52
+
53
+ def _top_level_declared(dist: metadata.Distribution) -> List[str]:
54
+ return (dist.read_text("top_level.txt") or "").split()
55
+
56
+ def _topmost(name: "pathlib.PurePosixPath") -> Optional[str]:
57
+ """
58
+ Return the top-most parent as long as there is a parent.
59
+ """
60
+ top, *rest = name.parts
61
+ return top if rest else None
62
+
63
+ def _get_toplevel_name(name: "pathlib.PurePosixPath") -> str:
64
+ return _topmost(name) or (
65
+ # python/typeshed#10328
66
+ inspect.getmodulename(name) # type: ignore
67
+ or str(name)
68
+ )
69
+
70
+ def _top_level_inferred(dist: "metadata.Distribution"):
71
+ opt_names = set(map(_get_toplevel_name, dist.files or []))
72
+
73
+ def importable_name(name):
74
+ return "." not in name
75
+
76
+ return filter(importable_name, opt_names)
77
+
78
+ packages_distributions = _packages_distributions
79
+
80
+
81
+ def modules_to_distributions() -> Dict[str, List[metadata.Distribution]]:
82
+ """
83
+ Return a mapping of top-level modules to their distributions.
84
+
85
+ Returns
86
+ -------
87
+ Dict[str, List[metadata.Distribution]]
88
+ A mapping of top-level modules to their distributions.
89
+ """
90
+ global _cached_distributions
91
+ pd = cast(Callable[[], Mapping[str, List[str]]], packages_distributions)
92
+ if _cached_distributions is None:
93
+ _cached_distributions = {
94
+ k: [metadata.distribution(d) for d in v] for k, v in pd().items()
95
+ }
96
+ return _cached_distributions
97
+
98
+
99
+ _ModuleInfo = NamedTuple(
100
+ "_ModuleInfo",
101
+ [
102
+ ("name", str),
103
+ ("root_paths", Set[str]),
104
+ ("module", ModuleType),
105
+ ("metaflow_module", bool),
106
+ ],
107
+ )
108
+
109
+
110
+ class PackagedDistribution(metadata.Distribution):
111
+ """
112
+ A Python Package packaged within a MetaflowCodeContent. This allows users to use use importlib
113
+ as they would regularly and the packaged Python Package would be considered as a
114
+ distribution even if it really isn't (since it is just included in the PythonPath).
115
+ """
116
+
117
+ def __init__(self, root: str, content: Dict[str, str]):
118
+ self._root = Path(root)
119
+ self._content = content
120
+
121
+ # Strongly inspired from PathDistribution in metadata.py
122
+ def read_text(self, filename: Union[str, os.PathLike]) -> Optional[str]:
123
+ if str(filename) in self._content:
124
+ return self._content[str(filename)]
125
+ return None
126
+
127
+ read_text.__doc__ = metadata.Distribution.read_text.__doc__
128
+
129
+ # Returns a metadata.SimplePath but not always present in importlib.metadata libs so
130
+ # skipping return type.
131
+ def locate_file(self, path: Union[str, os.PathLike]):
132
+ return self._root / path
133
+
134
+
135
+ class PackagedDistributionFinder(metadata.DistributionFinder):
136
+ def __init__(self, dist_info: Dict[str, Dict[str, str]]):
137
+ self._dist_info = dist_info
138
+
139
+ def find_distributions(self, context=metadata.DistributionFinder.Context()):
140
+ if context.name is None:
141
+ # Yields all known distributions
142
+ for name, info in self._dist_info.items():
143
+ yield PackagedDistribution(
144
+ os.path.join(get_metaflow_root(), name), info
145
+ )
146
+ return None
147
+ name = name_normalizer.sub("-", cast(str, context.name)).lower()
148
+ if name in self._dist_info:
149
+ yield PackagedDistribution(
150
+ os.path.join(get_metaflow_root(), cast(str, context.name)),
151
+ self._dist_info[name],
152
+ )
153
+ return None
@@ -0,0 +1,99 @@
1
+ import tarfile
2
+
3
+ from io import BytesIO
4
+ from typing import Any, IO, List, Optional, Union
5
+
6
+ from .backend import PackagingBackend
7
+
8
+
9
+ class TarPackagingBackend(PackagingBackend):
10
+ type = "tgz"
11
+
12
+ @classmethod
13
+ def get_extract_commands(cls, archive_name: str, dest_dir: str) -> List[str]:
14
+ return [
15
+ f"TAR_OPTIONS='--warning=no-timestamp' tar -xzf {archive_name} -C {dest_dir}"
16
+ ]
17
+
18
+ def __init__(self):
19
+ super().__init__()
20
+ self._buf = None
21
+
22
+ def create(self):
23
+ self._buf = BytesIO()
24
+ self._archive = tarfile.open(
25
+ fileobj=self._buf, mode="w:gz", compresslevel=3, dereference=True
26
+ )
27
+ return self
28
+
29
+ def add_file(self, filename: str, arcname: Optional[str] = None):
30
+ info = self._archive.gettarinfo(filename, arcname)
31
+ # Setting this default to Dec 3, 2019
32
+ info.mtime = 1575360000
33
+ with open(filename, mode="rb") as f:
34
+ self._archive.addfile(info, f)
35
+
36
+ def add_data(self, data: BytesIO, arcname: str):
37
+ info = tarfile.TarInfo(arcname)
38
+ data.seek(0)
39
+ info.size = len(data.getvalue())
40
+ # Setting this default to Dec 3, 2019
41
+ info.mtime = 1575360000
42
+ self._archive.addfile(info, data)
43
+
44
+ def close(self):
45
+ if self._archive:
46
+ self._archive.close()
47
+
48
+ def get_blob(self) -> Optional[Union[bytes, bytearray]]:
49
+ if self._buf:
50
+ blob = bytearray(self._buf.getvalue())
51
+ blob[4:8] = [0] * 4 # Reset 4 bytes from offset 4 to account for ts
52
+ return blob
53
+ return None
54
+
55
+ @classmethod
56
+ def cls_open(cls, content: IO[bytes]) -> tarfile.TarFile:
57
+ return tarfile.open(fileobj=content, mode="r:gz")
58
+
59
+ @classmethod
60
+ def cls_member_name(cls, member: Union[tarfile.TarInfo, str]) -> str:
61
+ """
62
+ Returns the name of the member as a string.
63
+ """
64
+ return member.name if isinstance(member, tarfile.TarInfo) else member
65
+
66
+ @classmethod
67
+ def cls_has_member(cls, archive: tarfile.TarFile, name: str) -> bool:
68
+ try:
69
+ archive.getmember(name)
70
+ return True
71
+ except KeyError:
72
+ return False
73
+
74
+ @classmethod
75
+ def cls_get_member(cls, archive: tarfile.TarFile, name: str) -> Optional[bytes]:
76
+ try:
77
+ member = archive.getmember(name)
78
+ return archive.extractfile(member).read()
79
+ except KeyError:
80
+ return None
81
+
82
+ @classmethod
83
+ def cls_extract_members(
84
+ cls,
85
+ archive: tarfile.TarFile,
86
+ members: Optional[List[Any]] = None,
87
+ dest_dir: str = ".",
88
+ ) -> None:
89
+ archive.extractall(path=dest_dir, members=members)
90
+
91
+ @classmethod
92
+ def cls_list_members(
93
+ cls, archive: tarfile.TarFile
94
+ ) -> Optional[List[tarfile.TarInfo]]:
95
+ return archive.getmembers() or None
96
+
97
+ @classmethod
98
+ def cls_list_names(cls, archive: tarfile.TarFile) -> Optional[List[str]]:
99
+ return archive.getnames() or None
@@ -0,0 +1,54 @@
1
+ import os
2
+ from contextlib import contextmanager
3
+ from typing import Callable, Generator, List, Optional, Tuple
4
+
5
+ from ..util import to_unicode, walk_without_cycles
6
+
7
+
8
+ def walk(
9
+ root: str,
10
+ exclude_hidden: bool = True,
11
+ file_filter: Optional[Callable[[str], bool]] = None,
12
+ exclude_tl_dirs: Optional[List[str]] = None,
13
+ ) -> Generator[Tuple[str, str], None, None]:
14
+ root = to_unicode(root) # handle files/folder with non ascii chars
15
+ prefixlen = len("%s/" % os.path.dirname(root))
16
+ for (
17
+ path,
18
+ _,
19
+ files,
20
+ ) in walk_without_cycles(root, exclude_tl_dirs):
21
+ if exclude_hidden and "/." in path:
22
+ continue
23
+ # path = path[2:] # strip the ./ prefix
24
+ # if path and (path[0] == '.' or './' in path):
25
+ # continue
26
+ for fname in files:
27
+ if file_filter is None or file_filter(fname):
28
+ p = os.path.join(path, fname)
29
+ yield p, p[prefixlen:]
30
+
31
+
32
+ def suffix_filter(suffixes: List[str]) -> Callable[[str], bool]:
33
+ """
34
+ Returns a filter function that checks if a file ends with any of the given suffixes.
35
+ """
36
+ suffixes = [s.lower() for s in suffixes]
37
+
38
+ def _filter(fname: str) -> bool:
39
+ fname = fname.lower()
40
+ return (
41
+ suffixes is None
42
+ or (fname[0] == "." and fname in suffixes)
43
+ or (fname[0] != "." and any(fname.endswith(suffix) for suffix in suffixes))
44
+ )
45
+
46
+ return _filter
47
+
48
+
49
+ @contextmanager
50
+ def with_dir(new_dir):
51
+ current_dir = os.getcwd()
52
+ os.chdir(new_dir)
53
+ yield new_dir
54
+ os.chdir(current_dir)