iker-python-common 1.0.69__tar.gz → 1.0.71__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {iker_python_common-1.0.69 → iker_python_common-1.0.71}/PKG-INFO +1 -1
- iker_python_common-1.0.71/src/iker/common/utils/shutils.py +320 -0
- {iker_python_common-1.0.69 → iker_python_common-1.0.71}/src/iker_python_common.egg-info/PKG-INFO +1 -1
- {iker_python_common-1.0.69 → iker_python_common-1.0.71}/test/iker_tests/common/utils/csvutils_test.py +77 -0
- {iker_python_common-1.0.69 → iker_python_common-1.0.71}/test/iker_tests/common/utils/shutils_test.py +88 -72
- {iker_python_common-1.0.69 → iker_python_common-1.0.71}/test/iker_tests/common/utils/typeutils_test.py +5 -1
- iker_python_common-1.0.69/src/iker/common/utils/shutils.py +0 -239
- {iker_python_common-1.0.69 → iker_python_common-1.0.71}/.editorconfig +0 -0
- {iker_python_common-1.0.69 → iker_python_common-1.0.71}/.github/workflows/pr.yml +0 -0
- {iker_python_common-1.0.69 → iker_python_common-1.0.71}/.github/workflows/push.yml +0 -0
- {iker_python_common-1.0.69 → iker_python_common-1.0.71}/.gitignore +0 -0
- {iker_python_common-1.0.69 → iker_python_common-1.0.71}/MANIFEST.in +0 -0
- {iker_python_common-1.0.69 → iker_python_common-1.0.71}/README.md +0 -0
- {iker_python_common-1.0.69 → iker_python_common-1.0.71}/VERSION +0 -0
- {iker_python_common-1.0.69 → iker_python_common-1.0.71}/pyproject.toml +0 -0
- {iker_python_common-1.0.69 → iker_python_common-1.0.71}/resources/unittest/config/config.cfg +0 -0
- {iker_python_common-1.0.69 → iker_python_common-1.0.71}/resources/unittest/csvutils/data.csv +0 -0
- {iker_python_common-1.0.69 → iker_python_common-1.0.71}/resources/unittest/csvutils/data.tsv +0 -0
- {iker_python_common-1.0.69 → iker_python_common-1.0.71}/resources/unittest/shutils/dir.baz/file.bar.baz +0 -0
- {iker_python_common-1.0.69 → iker_python_common-1.0.71}/resources/unittest/shutils/dir.baz/file.foo.bar +0 -0
- {iker_python_common-1.0.69 → iker_python_common-1.0.71}/resources/unittest/shutils/dir.baz/file.foo.baz +0 -0
- {iker_python_common-1.0.69 → iker_python_common-1.0.71}/resources/unittest/shutils/dir.foo/dir.foo.bar/dir.foo.bar.baz/file.foo.bar.baz +0 -0
- {iker_python_common-1.0.69 → iker_python_common-1.0.71}/resources/unittest/shutils/dir.foo/dir.foo.bar/file.bar.baz +0 -0
- {iker_python_common-1.0.69 → iker_python_common-1.0.71}/resources/unittest/shutils/dir.foo/dir.foo.bar/file.foo.bar +0 -0
- {iker_python_common-1.0.69 → iker_python_common-1.0.71}/resources/unittest/shutils/dir.foo/dir.foo.bar/file.foo.baz +0 -0
- {iker_python_common-1.0.69 → iker_python_common-1.0.71}/resources/unittest/shutils/dir.foo/file.bar +0 -0
- {iker_python_common-1.0.69 → iker_python_common-1.0.71}/resources/unittest/shutils/dir.foo/file.baz +0 -0
- {iker_python_common-1.0.69 → iker_python_common-1.0.71}/resources/unittest/shutils/dir.foo/file.foo +0 -0
- {iker_python_common-1.0.69 → iker_python_common-1.0.71}/setup.cfg +0 -0
- {iker_python_common-1.0.69 → iker_python_common-1.0.71}/setup.py +0 -0
- {iker_python_common-1.0.69 → iker_python_common-1.0.71}/src/iker/common/__init__.py +0 -0
- {iker_python_common-1.0.69 → iker_python_common-1.0.71}/src/iker/common/utils/__init__.py +0 -0
- {iker_python_common-1.0.69 → iker_python_common-1.0.71}/src/iker/common/utils/argutils.py +0 -0
- {iker_python_common-1.0.69 → iker_python_common-1.0.71}/src/iker/common/utils/config.py +0 -0
- {iker_python_common-1.0.69 → iker_python_common-1.0.71}/src/iker/common/utils/csvutils.py +0 -0
- {iker_python_common-1.0.69 → iker_python_common-1.0.71}/src/iker/common/utils/dbutils.py +0 -0
- {iker_python_common-1.0.69 → iker_python_common-1.0.71}/src/iker/common/utils/dtutils.py +0 -0
- {iker_python_common-1.0.69 → iker_python_common-1.0.71}/src/iker/common/utils/funcutils.py +0 -0
- {iker_python_common-1.0.69 → iker_python_common-1.0.71}/src/iker/common/utils/iterutils.py +0 -0
- {iker_python_common-1.0.69 → iker_python_common-1.0.71}/src/iker/common/utils/jsonutils.py +0 -0
- {iker_python_common-1.0.69 → iker_python_common-1.0.71}/src/iker/common/utils/logger.py +0 -0
- {iker_python_common-1.0.69 → iker_python_common-1.0.71}/src/iker/common/utils/numutils.py +0 -0
- {iker_python_common-1.0.69 → iker_python_common-1.0.71}/src/iker/common/utils/randutils.py +0 -0
- {iker_python_common-1.0.69 → iker_python_common-1.0.71}/src/iker/common/utils/retry.py +0 -0
- {iker_python_common-1.0.69 → iker_python_common-1.0.71}/src/iker/common/utils/span.py +0 -0
- {iker_python_common-1.0.69 → iker_python_common-1.0.71}/src/iker/common/utils/strutils.py +0 -0
- {iker_python_common-1.0.69 → iker_python_common-1.0.71}/src/iker/common/utils/testutils.py +0 -0
- {iker_python_common-1.0.69 → iker_python_common-1.0.71}/src/iker/common/utils/typeutils.py +0 -0
- {iker_python_common-1.0.69 → iker_python_common-1.0.71}/src/iker_python_common.egg-info/SOURCES.txt +0 -0
- {iker_python_common-1.0.69 → iker_python_common-1.0.71}/src/iker_python_common.egg-info/dependency_links.txt +0 -0
- {iker_python_common-1.0.69 → iker_python_common-1.0.71}/src/iker_python_common.egg-info/not-zip-safe +0 -0
- {iker_python_common-1.0.69 → iker_python_common-1.0.71}/src/iker_python_common.egg-info/requires.txt +0 -0
- {iker_python_common-1.0.69 → iker_python_common-1.0.71}/src/iker_python_common.egg-info/top_level.txt +0 -0
- {iker_python_common-1.0.69 → iker_python_common-1.0.71}/test/iker_tests/__init__.py +0 -0
- {iker_python_common-1.0.69 → iker_python_common-1.0.71}/test/iker_tests/common/__init__.py +0 -0
- {iker_python_common-1.0.69 → iker_python_common-1.0.71}/test/iker_tests/common/utils/__init__.py +0 -0
- {iker_python_common-1.0.69 → iker_python_common-1.0.71}/test/iker_tests/common/utils/argutils_test.py +0 -0
- {iker_python_common-1.0.69 → iker_python_common-1.0.71}/test/iker_tests/common/utils/config_test.py +0 -0
- {iker_python_common-1.0.69 → iker_python_common-1.0.71}/test/iker_tests/common/utils/dbutils_test.py +0 -0
- {iker_python_common-1.0.69 → iker_python_common-1.0.71}/test/iker_tests/common/utils/dtutils_test.py +0 -0
- {iker_python_common-1.0.69 → iker_python_common-1.0.71}/test/iker_tests/common/utils/funcutils_test.py +0 -0
- {iker_python_common-1.0.69 → iker_python_common-1.0.71}/test/iker_tests/common/utils/iterutils_test.py +0 -0
- {iker_python_common-1.0.69 → iker_python_common-1.0.71}/test/iker_tests/common/utils/jsonutils_test.py +0 -0
- {iker_python_common-1.0.69 → iker_python_common-1.0.71}/test/iker_tests/common/utils/logger_test.py +0 -0
- {iker_python_common-1.0.69 → iker_python_common-1.0.71}/test/iker_tests/common/utils/numutils_test.py +0 -0
- {iker_python_common-1.0.69 → iker_python_common-1.0.71}/test/iker_tests/common/utils/randutils_test.py +0 -0
- {iker_python_common-1.0.69 → iker_python_common-1.0.71}/test/iker_tests/common/utils/retry_test.py +0 -0
- {iker_python_common-1.0.69 → iker_python_common-1.0.71}/test/iker_tests/common/utils/span_test.py +0 -0
- {iker_python_common-1.0.69 → iker_python_common-1.0.71}/test/iker_tests/common/utils/strutils_test.py +0 -0
- {iker_python_common-1.0.69 → iker_python_common-1.0.71}/test/iker_tests/common/utils/testutils_test.py +0 -0
- {iker_python_common-1.0.69 → iker_python_common-1.0.71}/test/testenv.py +0 -0
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
import fnmatch
|
|
2
|
+
import os
|
|
3
|
+
import pathlib
|
|
4
|
+
import shutil
|
|
5
|
+
import sys
|
|
6
|
+
from collections.abc import Generator
|
|
7
|
+
from typing import Protocol
|
|
8
|
+
|
|
9
|
+
from iker.common.utils.strutils import is_empty
|
|
10
|
+
|
|
11
|
+
__all__ = [
|
|
12
|
+
"make_path",
|
|
13
|
+
"fn_suffix",
|
|
14
|
+
"fn_suffixes",
|
|
15
|
+
"fn_stem",
|
|
16
|
+
"path_depth",
|
|
17
|
+
"glob_match",
|
|
18
|
+
"scan_files",
|
|
19
|
+
"copy_files",
|
|
20
|
+
"run",
|
|
21
|
+
"execute",
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def make_path(
|
|
26
|
+
path: str | os.PathLike[str] | None,
|
|
27
|
+
*,
|
|
28
|
+
expand: bool = False,
|
|
29
|
+
normalize: bool = False,
|
|
30
|
+
absolute: bool = False,
|
|
31
|
+
resolve: bool = False,
|
|
32
|
+
check_exists: bool = False,
|
|
33
|
+
check_is_dir: bool = False,
|
|
34
|
+
check_is_file: bool = False,
|
|
35
|
+
) -> pathlib.Path | None:
|
|
36
|
+
"""
|
|
37
|
+
Creates a ``pathlib.Path`` object from the given path string or path-like object, applying the specified
|
|
38
|
+
transformations and checks.
|
|
39
|
+
|
|
40
|
+
:param path: The path string or path-like object to convert to a ``pathlib.Path`` object.
|
|
41
|
+
:param expand: If ``True``, expands environment variables and user home directory in the path.
|
|
42
|
+
:param normalize: If ``True``, normalizes the path by collapsing redundant separators and up-level references.
|
|
43
|
+
:param absolute: If ``True``, converts the path to an absolute path.
|
|
44
|
+
:param resolve: If ``True``, resolves symbolic links and returns the canonical path.
|
|
45
|
+
:param check_exists: If ``True``, checks if the path exists and raises a ``FileNotFoundError`` if it does not.
|
|
46
|
+
:param check_is_dir: If ``True``, checks if the path is a directory and raises a ``NotADirectoryError`` if it is not.
|
|
47
|
+
:param check_is_file: If ``True``, checks if the path is a file and raises a ``FileNotFoundError`` if it is not.
|
|
48
|
+
:return: A ``pathlib.Path`` object representing the given path after applying the specified transformations and
|
|
49
|
+
checks, or ``None`` if the input path is ``None``.
|
|
50
|
+
"""
|
|
51
|
+
if path is None:
|
|
52
|
+
return None
|
|
53
|
+
path = os.fspath(path)
|
|
54
|
+
|
|
55
|
+
if expand:
|
|
56
|
+
path = os.path.expandvars(path)
|
|
57
|
+
path = os.path.expanduser(path)
|
|
58
|
+
if normalize:
|
|
59
|
+
path = os.path.normpath(path)
|
|
60
|
+
if absolute:
|
|
61
|
+
path = os.path.abspath(path)
|
|
62
|
+
if resolve:
|
|
63
|
+
path = os.path.realpath(path)
|
|
64
|
+
|
|
65
|
+
path = pathlib.Path(path)
|
|
66
|
+
|
|
67
|
+
if check_exists and not path.exists():
|
|
68
|
+
raise FileNotFoundError(f"path does not exist '{path}'")
|
|
69
|
+
if check_is_dir and not path.is_dir():
|
|
70
|
+
raise NotADirectoryError(f"path is not a directory '{path}'")
|
|
71
|
+
if check_is_file and not path.is_file():
|
|
72
|
+
raise FileNotFoundError(f"path is not a file '{path}'")
|
|
73
|
+
|
|
74
|
+
return path
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
if sys.version_info < (3, 14):
|
|
78
|
+
def fn_suffix(filename: str | os.PathLike[str] | None) -> str | None:
|
|
79
|
+
"""
|
|
80
|
+
Extracts the filename suffix from the given filename or path.
|
|
81
|
+
|
|
82
|
+
:param filename: The specific filename or path.
|
|
83
|
+
:return: The filename suffix, including the leading dot (e.g., ".txt" for "file.txt"),
|
|
84
|
+
or ``None`` if the input filename is ``None``.
|
|
85
|
+
"""
|
|
86
|
+
if (path := make_path(filename)) is not None:
|
|
87
|
+
_, suffix_name = os.path.splitext(path.name)
|
|
88
|
+
return suffix_name
|
|
89
|
+
return None
|
|
90
|
+
else:
|
|
91
|
+
def fn_suffix(filename: str | os.PathLike[str] | None) -> str | None:
|
|
92
|
+
"""
|
|
93
|
+
Extracts the filename suffix from the given filename or path.
|
|
94
|
+
|
|
95
|
+
:param filename: The specific filename or path.
|
|
96
|
+
:return: The filename suffix, including the leading dot (e.g., ".txt" for "file.txt"),
|
|
97
|
+
or ``None`` if the input filename is ``None``.
|
|
98
|
+
"""
|
|
99
|
+
if (path := make_path(filename)) is not None:
|
|
100
|
+
return path.suffix
|
|
101
|
+
return None
|
|
102
|
+
|
|
103
|
+
if sys.version_info < (3, 14):
|
|
104
|
+
def fn_suffixes(filename: str | os.PathLike[str] | None) -> list[str]:
|
|
105
|
+
"""
|
|
106
|
+
Extracts all filename suffixes from the given filename or path.
|
|
107
|
+
|
|
108
|
+
:param filename: The specific filename or path.
|
|
109
|
+
:return: A list of filename suffixes, each including the leading dot (e.g., [".tar", ".gz"] for "archive.tar.gz").
|
|
110
|
+
"""
|
|
111
|
+
if (path := make_path(filename)) is not None:
|
|
112
|
+
results = []
|
|
113
|
+
stem_name = path.name
|
|
114
|
+
while True:
|
|
115
|
+
stem_name, suffix_name = os.path.splitext(stem_name)
|
|
116
|
+
if is_empty(suffix_name):
|
|
117
|
+
return list(reversed(results))
|
|
118
|
+
results.append(suffix_name)
|
|
119
|
+
return []
|
|
120
|
+
else:
|
|
121
|
+
def fn_suffixes(filename: str | os.PathLike[str] | None) -> list[str]:
|
|
122
|
+
"""
|
|
123
|
+
Extracts all filename suffixes from the given filename or path.
|
|
124
|
+
|
|
125
|
+
:param filename: The specific filename or path.
|
|
126
|
+
:return: A list of filename suffixes, each including the leading dot (e.g., [".tar", ".gz"] for "archive.tar.gz").
|
|
127
|
+
"""
|
|
128
|
+
if (path := make_path(filename)) is not None:
|
|
129
|
+
return path.suffixes
|
|
130
|
+
return []
|
|
131
|
+
|
|
132
|
+
if sys.version_info < (3, 14):
|
|
133
|
+
def fn_stem(filename: str | os.PathLike[str] | None, minimal: bool = False) -> str | None:
|
|
134
|
+
"""
|
|
135
|
+
Extracts the filename stem from the given filename or path.
|
|
136
|
+
|
|
137
|
+
:param filename: The specific filename or path.
|
|
138
|
+
:param minimal: If ``True``, extracts the minimal (shortest) stem, otherwise the default stem.
|
|
139
|
+
:return: The filename stem.
|
|
140
|
+
"""
|
|
141
|
+
if (path := make_path(filename)) is not None:
|
|
142
|
+
if not minimal:
|
|
143
|
+
stem_name, _ = os.path.splitext(path.name)
|
|
144
|
+
return stem_name
|
|
145
|
+
else:
|
|
146
|
+
stem_name = path.name
|
|
147
|
+
while True:
|
|
148
|
+
stem_name, suffix_name = os.path.splitext(stem_name)
|
|
149
|
+
if is_empty(suffix_name):
|
|
150
|
+
return stem_name
|
|
151
|
+
return None
|
|
152
|
+
else:
|
|
153
|
+
def fn_stem(filename: str | os.PathLike[str] | None, minimal: bool = False) -> str | None:
|
|
154
|
+
"""
|
|
155
|
+
Extracts the filename stem from the given filename or path.
|
|
156
|
+
|
|
157
|
+
:param filename: The specific filename or path.
|
|
158
|
+
:param minimal: If ``True``, extracts the minimal (shortest) stem, otherwise the default stem.
|
|
159
|
+
:return: The filename stem.
|
|
160
|
+
"""
|
|
161
|
+
if (path := make_path(filename)) is not None:
|
|
162
|
+
if not minimal:
|
|
163
|
+
return path.stem
|
|
164
|
+
else:
|
|
165
|
+
while True:
|
|
166
|
+
if is_empty(path.suffix):
|
|
167
|
+
return path.stem
|
|
168
|
+
path = path.with_suffix("")
|
|
169
|
+
return None
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def path_depth(root: str | os.PathLike[str], child: str | os.PathLike[str]) -> int:
|
|
173
|
+
"""
|
|
174
|
+
Returns the relative path depth from the given ``child`` to the ``root``.
|
|
175
|
+
|
|
176
|
+
:param root: The root path.
|
|
177
|
+
:param child: The child path.
|
|
178
|
+
:return: Relative depth, or -1 if ``child`` is not under ``root``.
|
|
179
|
+
"""
|
|
180
|
+
root = make_path(root, expand=True, resolve=True)
|
|
181
|
+
child = make_path(child, expand=True, resolve=True)
|
|
182
|
+
if not child.is_relative_to(root):
|
|
183
|
+
return -1
|
|
184
|
+
return len(child.relative_to(root).parts)
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def glob_match(
|
|
188
|
+
names: list[str],
|
|
189
|
+
include_patterns: list[str] | None = None,
|
|
190
|
+
exclude_patterns: list[str] | None = None,
|
|
191
|
+
) -> list[str]:
|
|
192
|
+
"""
|
|
193
|
+
Applies the given inclusive and exclusive glob patterns to the given ``names`` and returns the filtered result.
|
|
194
|
+
|
|
195
|
+
:param names: Names to apply the glob patterns to.
|
|
196
|
+
:param include_patterns: Inclusive glob patterns.
|
|
197
|
+
:param exclude_patterns: Exclusive glob patterns.
|
|
198
|
+
:return: Filtered names matching the patterns.
|
|
199
|
+
"""
|
|
200
|
+
ret = set()
|
|
201
|
+
for pat in (include_patterns or []):
|
|
202
|
+
ret.update(fnmatch.filter(names, pat))
|
|
203
|
+
if include_patterns is None or len(include_patterns) == 0:
|
|
204
|
+
ret.update(names)
|
|
205
|
+
for pat in (exclude_patterns or []):
|
|
206
|
+
ret.difference_update(fnmatch.filter(names, pat))
|
|
207
|
+
return list(ret)
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
class CopyFuncProtocol(Protocol):
|
|
211
|
+
def __call__(self, src: str | os.PathLike[str], dst: str | os.PathLike[str], **kwargs) -> None: ...
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def scan_files(
|
|
215
|
+
path: str | os.PathLike[str],
|
|
216
|
+
*,
|
|
217
|
+
include_patterns: list[str] | None = None,
|
|
218
|
+
exclude_patterns: list[str] | None = None,
|
|
219
|
+
depth: int = 0,
|
|
220
|
+
) -> Generator[pathlib.Path, None, None]:
|
|
221
|
+
"""
|
|
222
|
+
Recursively scans the given ``path`` and returns a list of files whose names satisfy the given name patterns and the
|
|
223
|
+
relative depth of their folders to the given root path is not greater than the specified ``depth`` value.
|
|
224
|
+
|
|
225
|
+
:param path: The root path to scan.
|
|
226
|
+
:param include_patterns: Inclusive glob patterns applied to the filenames.
|
|
227
|
+
:param exclude_patterns: Exclusive glob patterns applied to the filenames.
|
|
228
|
+
:param depth: Maximum depth of the subdirectories included in the scan.
|
|
229
|
+
:return: A generator yielding the paths of the files matching the patterns.
|
|
230
|
+
"""
|
|
231
|
+
path = make_path(path, expand=True)
|
|
232
|
+
if path.exists() and not path.is_dir():
|
|
233
|
+
if len(glob_match([path.name], include_patterns, exclude_patterns)) == 0:
|
|
234
|
+
return
|
|
235
|
+
yield path
|
|
236
|
+
|
|
237
|
+
for parent, dirs, filenames in path.walk():
|
|
238
|
+
if 0 < depth <= path_depth(path, parent):
|
|
239
|
+
continue
|
|
240
|
+
for filename in glob_match(filenames, include_patterns, exclude_patterns):
|
|
241
|
+
yield parent / filename
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
def copy_files(
|
|
245
|
+
src: str | os.PathLike[str],
|
|
246
|
+
dst: str | os.PathLike[str],
|
|
247
|
+
*,
|
|
248
|
+
include_patterns: list[str] | None = None,
|
|
249
|
+
exclude_patterns: list[str] | None = None,
|
|
250
|
+
depth: int = 0,
|
|
251
|
+
follow_symlinks: bool = False,
|
|
252
|
+
ignore_dangling_symlinks: bool = False,
|
|
253
|
+
dirs_exist_ok: bool = False,
|
|
254
|
+
copy_func: CopyFuncProtocol = shutil.copy2
|
|
255
|
+
):
|
|
256
|
+
"""
|
|
257
|
+
Recursively copies the given source path to the destination path. Only copies the files whose names satisfy the
|
|
258
|
+
given name patterns and the relative depth of their folders to the given source path is not greater than the
|
|
259
|
+
specified ``depth`` value.
|
|
260
|
+
|
|
261
|
+
:param src: The source path or file.
|
|
262
|
+
:param dst: The destination path or file.
|
|
263
|
+
:param include_patterns: Inclusive glob patterns applied to the filenames.
|
|
264
|
+
:param exclude_patterns: Exclusive glob patterns applied to the filenames.
|
|
265
|
+
:param depth: Maximum depth of the subdirectories included in the scan.
|
|
266
|
+
:param follow_symlinks: If ``True``, create symbolic links for the symbolic links present in the source; otherwise,
|
|
267
|
+
make a physical copy.
|
|
268
|
+
:param ignore_dangling_symlinks: If ``True``, ignore errors if the file pointed by the symbolic link does not exist.
|
|
269
|
+
:param dirs_exist_ok: If ``True``, ignore errors if the destination directory and subdirectories exist.
|
|
270
|
+
:param copy_func: Copy function to use for copying files.
|
|
271
|
+
"""
|
|
272
|
+
src = make_path(src, expand=True)
|
|
273
|
+
dst = make_path(dst, expand=True)
|
|
274
|
+
if not src.is_dir():
|
|
275
|
+
if len(glob_match([src.name], include_patterns, exclude_patterns)) == 0:
|
|
276
|
+
return
|
|
277
|
+
if not dst.exists():
|
|
278
|
+
dst.parent.mkdir(parents=True, exist_ok=True)
|
|
279
|
+
copy_func(src, dst, follow_symlinks=follow_symlinks)
|
|
280
|
+
return
|
|
281
|
+
|
|
282
|
+
def ignore_func(parent: str | os.PathLike[str], names: list[str]) -> set[str]:
|
|
283
|
+
parent = make_path(parent, expand=True)
|
|
284
|
+
filenames = list(filter(lambda x: not (parent / x).is_dir(), names))
|
|
285
|
+
ret = set(filenames)
|
|
286
|
+
if 0 < depth <= path_depth(src, parent):
|
|
287
|
+
return ret
|
|
288
|
+
ret.difference_update(glob_match(filenames, include_patterns, exclude_patterns))
|
|
289
|
+
return ret
|
|
290
|
+
|
|
291
|
+
shutil.copytree(src,
|
|
292
|
+
dst,
|
|
293
|
+
symlinks=follow_symlinks,
|
|
294
|
+
ignore=ignore_func,
|
|
295
|
+
ignore_dangling_symlinks=ignore_dangling_symlinks,
|
|
296
|
+
dirs_exist_ok=dirs_exist_ok,
|
|
297
|
+
copy_function=copy_func)
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
def run(cmd: str) -> bool:
|
|
301
|
+
"""
|
|
302
|
+
Runs the given command and returns the success status.
|
|
303
|
+
|
|
304
|
+
:param cmd: Command to run.
|
|
305
|
+
:return: ``True`` if the command has been successfully run, ``False`` otherwise.
|
|
306
|
+
"""
|
|
307
|
+
return os.system(cmd) == 0
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
def execute(cmd: str, strip: bool = True) -> str:
|
|
311
|
+
"""
|
|
312
|
+
Executes the given command and returns contents collected from standard output.
|
|
313
|
+
|
|
314
|
+
:param cmd: Command to execute.
|
|
315
|
+
:param strip: If ``True``, the contents will be stripped.
|
|
316
|
+
:return: The content from standard output.
|
|
317
|
+
"""
|
|
318
|
+
if strip:
|
|
319
|
+
return os.popen(cmd).read().strip()
|
|
320
|
+
return os.popen(cmd).read()
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import itertools
|
|
2
2
|
import math
|
|
3
|
+
import tempfile
|
|
3
4
|
import unittest
|
|
4
5
|
|
|
5
6
|
import ddt
|
|
@@ -179,6 +180,72 @@ class CSVTest(unittest.TestCase):
|
|
|
179
180
|
lines + lines + lines_no_header + lines_no_header):
|
|
180
181
|
self.assertTrue(json_compare(a, e))
|
|
181
182
|
|
|
183
|
+
def test_lines__wrong_cols(self):
|
|
184
|
+
view = csv.view(
|
|
185
|
+
[
|
|
186
|
+
csv.column("dummy_str", loader=str, dumper=str, null_str=r"\N"),
|
|
187
|
+
csv.column("dummy_bool", loader=parse_bool, dumper=str, null_str=r"\N"),
|
|
188
|
+
csv.column("dummy_int", loader=int, dumper=str, null_str=r"\N"),
|
|
189
|
+
csv.column("dummy_float", loader=float, dumper=str, null_str=r"\N"),
|
|
190
|
+
],
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
lines = [
|
|
194
|
+
["dummy_str", "dummy_bool", "dummy_int", "dummy_float", "dummy_datetime", "dummy_params"],
|
|
195
|
+
["foo", "True", "1", "1.0", "2020-01-01T00:00:00", ""],
|
|
196
|
+
["bar", "False", "-1", "-1.0", "2020-01-01T00:00:00", "key_1=value_1;key_2;!key_3"],
|
|
197
|
+
["baz", r"\N", "100", "inf", "2020-01-01T00:00:00", r"\N"],
|
|
198
|
+
["", r"\N", "-100", "-inf", "2020-01-01T00:00:00", ""],
|
|
199
|
+
[r"\N", r"\N", "0", "nan", "2020-01-01T00:00:00", r"\N"],
|
|
200
|
+
[r"\N", r"\N", r"\N", r"\N", r"\N", r"\N"],
|
|
201
|
+
]
|
|
202
|
+
|
|
203
|
+
with self.assertRaises(ValueError):
|
|
204
|
+
list(view.load_lines(lines, has_header=True))
|
|
205
|
+
|
|
206
|
+
data = [
|
|
207
|
+
["foo", "True", "1", "1.0", "2020-01-01T00:00:00", ""],
|
|
208
|
+
["bar", "False", "-1", "-1.0", "2020-01-01T00:00:00", "key_1=value_1;key_2;!key_3"],
|
|
209
|
+
["baz", r"\N", "100", "inf", "2020-01-01T00:00:00", r"\N"],
|
|
210
|
+
["", r"\N", "-100", "-inf", "2020-01-01T00:00:00", ""],
|
|
211
|
+
[r"\N", r"\N", "0", "nan", "2020-01-01T00:00:00", r"\N"],
|
|
212
|
+
[r"\N", r"\N", r"\N", r"\N", r"\N", r"\N"],
|
|
213
|
+
]
|
|
214
|
+
|
|
215
|
+
with self.assertRaises(ValueError):
|
|
216
|
+
list(view.dump_lines(data, has_header=True))
|
|
217
|
+
|
|
218
|
+
def test_lines__wrong_col_name(self):
|
|
219
|
+
view = csv.view(
|
|
220
|
+
[
|
|
221
|
+
csv.column("another_dummy_str", loader=str, dumper=str, null_str=r"\N"),
|
|
222
|
+
csv.column("another_dummy_bool", loader=parse_bool, dumper=str, null_str=r"\N"),
|
|
223
|
+
csv.column("another_dummy_int", loader=int, dumper=str, null_str=r"\N"),
|
|
224
|
+
csv.column("another_dummy_float", loader=float, dumper=str, null_str=r"\N"),
|
|
225
|
+
csv.column("another_dummy_datetime",
|
|
226
|
+
loader=lambda x: dt_to_ts(dt_parse_iso(x)),
|
|
227
|
+
dumper=lambda x: dt_format_iso(dt_from_ts(x)),
|
|
228
|
+
null_str=r"\N"),
|
|
229
|
+
csv.column("another_dummy_params",
|
|
230
|
+
loader=lambda x: parse_params_string(x, delim=";", kv_delim="=", neg_prefix="!"),
|
|
231
|
+
dumper=lambda x: make_params_string(x, delim=";", kv_delim="=", neg_prefix="!"),
|
|
232
|
+
null_str=r"\N"),
|
|
233
|
+
],
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
lines = [
|
|
237
|
+
["dummy_str", "dummy_bool", "dummy_int", "dummy_float", "dummy_datetime", "dummy_params"],
|
|
238
|
+
["foo", "True", "1", "1.0", "2020-01-01T00:00:00", ""],
|
|
239
|
+
["bar", "False", "-1", "-1.0", "2020-01-01T00:00:00", "key_1=value_1;key_2;!key_3"],
|
|
240
|
+
["baz", r"\N", "100", "inf", "2020-01-01T00:00:00", r"\N"],
|
|
241
|
+
["", r"\N", "-100", "-inf", "2020-01-01T00:00:00", ""],
|
|
242
|
+
[r"\N", r"\N", "0", "nan", "2020-01-01T00:00:00", r"\N"],
|
|
243
|
+
[r"\N", r"\N", r"\N", r"\N", r"\N", r"\N"],
|
|
244
|
+
]
|
|
245
|
+
|
|
246
|
+
with self.assertRaises(ValueError):
|
|
247
|
+
list(view.load_lines(lines, has_header=True))
|
|
248
|
+
|
|
182
249
|
def test_file(self):
|
|
183
250
|
view_csv = csv.view(
|
|
184
251
|
[
|
|
@@ -236,3 +303,13 @@ class CSVTest(unittest.TestCase):
|
|
|
236
303
|
self.assertTrue(json_compare(c, d))
|
|
237
304
|
self.assertTrue(json_compare(t, d))
|
|
238
305
|
self.assertTrue(json_compare(c, t))
|
|
306
|
+
|
|
307
|
+
with tempfile.NamedTemporaryFile() as fh:
|
|
308
|
+
view_csv.dump_file(data, fh.name, has_header=True, encoding="utf-8")
|
|
309
|
+
for a, e in zip(view_csv.load_file(fh.name, has_header=True, encoding="utf-8"), data):
|
|
310
|
+
self.assertTrue(json_compare(a, e))
|
|
311
|
+
|
|
312
|
+
with tempfile.NamedTemporaryFile() as fh:
|
|
313
|
+
view_tsv.dump_file(data, fh.name, has_header=True, encoding="utf-8")
|
|
314
|
+
for a, e in zip(view_tsv.load_file(fh.name, has_header=True, encoding="utf-8"), data):
|
|
315
|
+
self.assertTrue(json_compare(a, e))
|
{iker_python_common-1.0.69 → iker_python_common-1.0.71}/test/iker_tests/common/utils/shutils_test.py
RENAMED
|
@@ -5,10 +5,10 @@ import unittest
|
|
|
5
5
|
|
|
6
6
|
import ddt
|
|
7
7
|
|
|
8
|
-
from iker.common.utils.shutils import
|
|
9
|
-
from iker.common.utils.shutils import
|
|
10
|
-
from iker.common.utils.shutils import extension, extensions, stem
|
|
8
|
+
from iker.common.utils.shutils import copy_files, scan_files
|
|
9
|
+
from iker.common.utils.shutils import fn_stem, fn_suffix, fn_suffixes
|
|
11
10
|
from iker.common.utils.shutils import glob_match
|
|
11
|
+
from iker.common.utils.shutils import make_path, path_depth
|
|
12
12
|
from testenv import resources_directory
|
|
13
13
|
|
|
14
14
|
work_dir = pathlib.Path.cwd
|
|
@@ -17,7 +17,7 @@ home_dir = pathlib.Path.home
|
|
|
17
17
|
|
|
18
18
|
@ddt.ddt
|
|
19
19
|
class ShUtilsTest(unittest.TestCase):
|
|
20
|
-
|
|
20
|
+
data_fn_suffix = [
|
|
21
21
|
(".", ""),
|
|
22
22
|
("..", ""),
|
|
23
23
|
("...", ""),
|
|
@@ -43,13 +43,44 @@ class ShUtilsTest(unittest.TestCase):
|
|
|
43
43
|
("s3://bucket/foo/bar/git.scrappy.ignored", ".ignored"),
|
|
44
44
|
]
|
|
45
45
|
|
|
46
|
-
@ddt.idata(
|
|
46
|
+
@ddt.idata(data_fn_suffix)
|
|
47
47
|
@ddt.unpack
|
|
48
|
-
def
|
|
49
|
-
self.assertEqual(expect,
|
|
48
|
+
def test_fn_suffix(self, data, expect):
|
|
49
|
+
self.assertEqual(expect, fn_suffix(data))
|
|
50
50
|
|
|
51
|
-
|
|
52
|
-
(".",
|
|
51
|
+
data_fn_suffixes = [
|
|
52
|
+
(".", []),
|
|
53
|
+
("..", []),
|
|
54
|
+
("...", []),
|
|
55
|
+
(".ignored", []),
|
|
56
|
+
(".ignored.", ["."]),
|
|
57
|
+
(".git.ignored", [".ignored"]),
|
|
58
|
+
("..git.ignored", [".ignored"]),
|
|
59
|
+
("..git..ignored", [".", ".ignored"]),
|
|
60
|
+
("git.ignored", [".ignored"]),
|
|
61
|
+
("git.scrappy.ignored", [".scrappy", ".ignored"]),
|
|
62
|
+
("~/git.scrappy.ignored", [".scrappy", ".ignored"]),
|
|
63
|
+
("./git.scrappy.ignored", [".scrappy", ".ignored"]),
|
|
64
|
+
("../git.scrappy.ignored", [".scrappy", ".ignored"]),
|
|
65
|
+
("/~/git.scrappy.ignored", [".scrappy", ".ignored"]),
|
|
66
|
+
("/./git.scrappy.ignored", [".scrappy", ".ignored"]),
|
|
67
|
+
("/../git.scrappy.ignored", [".scrappy", ".ignored"]),
|
|
68
|
+
("/foo/bar/git.scrappy.ignored", [".scrappy", ".ignored"]),
|
|
69
|
+
("/foo/bar/~/git.scrappy.ignored", [".scrappy", ".ignored"]),
|
|
70
|
+
("/foo/bar/./git.scrappy.ignored", [".scrappy", ".ignored"]),
|
|
71
|
+
("/foo/bar/../git.scrappy.ignored", [".scrappy", ".ignored"]),
|
|
72
|
+
("/foo/bar/git.scrappy.ignored..baz.", [".scrappy", ".ignored", ".", ".baz", "."]),
|
|
73
|
+
("http://domain/foo/bar/git.scrappy.ignored", [".scrappy", ".ignored"]),
|
|
74
|
+
("s3://bucket/foo/bar/git.scrappy.ignored", [".scrappy", ".ignored"]),
|
|
75
|
+
]
|
|
76
|
+
|
|
77
|
+
@ddt.idata(data_fn_suffixes)
|
|
78
|
+
@ddt.unpack
|
|
79
|
+
def test_fn_suffixes(self, data, expect):
|
|
80
|
+
self.assertEqual(expect, fn_suffixes(data))
|
|
81
|
+
|
|
82
|
+
data_fn_stem = [
|
|
83
|
+
(".", ""),
|
|
53
84
|
("..", ".."),
|
|
54
85
|
("...", "..."),
|
|
55
86
|
(".ignored", ".ignored"),
|
|
@@ -74,13 +105,13 @@ class ShUtilsTest(unittest.TestCase):
|
|
|
74
105
|
("s3://bucket/foo/bar/git.scrappy.ignored", "git.scrappy"),
|
|
75
106
|
]
|
|
76
107
|
|
|
77
|
-
@ddt.idata(
|
|
108
|
+
@ddt.idata(data_fn_stem)
|
|
78
109
|
@ddt.unpack
|
|
79
|
-
def
|
|
80
|
-
self.assertEqual(expect,
|
|
110
|
+
def test_fn_stem(self, data, expect):
|
|
111
|
+
self.assertEqual(expect, fn_stem(data))
|
|
81
112
|
|
|
82
|
-
|
|
83
|
-
(".", "
|
|
113
|
+
data_fn_stem__minimal = [
|
|
114
|
+
(".", ""),
|
|
84
115
|
("..", ".."),
|
|
85
116
|
("...", "..."),
|
|
86
117
|
(".ignored", ".ignored"),
|
|
@@ -105,43 +136,12 @@ class ShUtilsTest(unittest.TestCase):
|
|
|
105
136
|
("s3://bucket/foo/bar/git.scrappy.ignored", "git"),
|
|
106
137
|
]
|
|
107
138
|
|
|
108
|
-
@ddt.idata(
|
|
139
|
+
@ddt.idata(data_fn_stem__minimal)
|
|
109
140
|
@ddt.unpack
|
|
110
|
-
def
|
|
111
|
-
self.assertEqual(expect,
|
|
141
|
+
def test_fn_stem__minimal(self, data, expect):
|
|
142
|
+
self.assertEqual(expect, fn_stem(data, minimal=True))
|
|
112
143
|
|
|
113
|
-
|
|
114
|
-
(".", []),
|
|
115
|
-
("..", []),
|
|
116
|
-
("...", []),
|
|
117
|
-
(".ignored", []),
|
|
118
|
-
(".ignored.", ["."]),
|
|
119
|
-
(".git.ignored", [".ignored"]),
|
|
120
|
-
("..git.ignored", [".ignored"]),
|
|
121
|
-
("..git..ignored", [".ignored", "..ignored"]),
|
|
122
|
-
("git.ignored", [".ignored"]),
|
|
123
|
-
("git.scrappy.ignored", [".ignored", ".scrappy.ignored"]),
|
|
124
|
-
("~/git.scrappy.ignored", [".ignored", ".scrappy.ignored"]),
|
|
125
|
-
("./git.scrappy.ignored", [".ignored", ".scrappy.ignored"]),
|
|
126
|
-
("../git.scrappy.ignored", [".ignored", ".scrappy.ignored"]),
|
|
127
|
-
("/~/git.scrappy.ignored", [".ignored", ".scrappy.ignored"]),
|
|
128
|
-
("/./git.scrappy.ignored", [".ignored", ".scrappy.ignored"]),
|
|
129
|
-
("/../git.scrappy.ignored", [".ignored", ".scrappy.ignored"]),
|
|
130
|
-
("/foo/bar/git.scrappy.ignored", [".ignored", ".scrappy.ignored"]),
|
|
131
|
-
("/foo/bar/~/git.scrappy.ignored", [".ignored", ".scrappy.ignored"]),
|
|
132
|
-
("/foo/bar/./git.scrappy.ignored", [".ignored", ".scrappy.ignored"]),
|
|
133
|
-
("/foo/bar/../git.scrappy.ignored", [".ignored", ".scrappy.ignored"]),
|
|
134
|
-
("/foo/bar/git.scrappy.ignored..baz.", [".", ".baz.", "..baz.", ".ignored..baz.", ".scrappy.ignored..baz."]),
|
|
135
|
-
("http://domain/foo/bar/git.scrappy.ignored", [".ignored", ".scrappy.ignored"]),
|
|
136
|
-
("s3://bucket/foo/bar/git.scrappy.ignored", [".ignored", ".scrappy.ignored"]),
|
|
137
|
-
]
|
|
138
|
-
|
|
139
|
-
@ddt.idata(data_extensions)
|
|
140
|
-
@ddt.unpack
|
|
141
|
-
def test_extensions(self, data, expect):
|
|
142
|
-
self.assertEqual(expect, extensions(data))
|
|
143
|
-
|
|
144
|
-
data_expanded_path = [
|
|
144
|
+
data_make_path = [
|
|
145
145
|
(".", f"{work_dir()}"),
|
|
146
146
|
("./", f"{work_dir()}"),
|
|
147
147
|
("./foo", f"{work_dir()}/foo"),
|
|
@@ -186,12 +186,22 @@ class ShUtilsTest(unittest.TestCase):
|
|
|
186
186
|
("${EMPTY_ENV_PARAM}/foo/../bar/..", f"/"),
|
|
187
187
|
]
|
|
188
188
|
|
|
189
|
-
@ddt.idata(
|
|
189
|
+
@ddt.idata(data_make_path)
|
|
190
190
|
@ddt.unpack
|
|
191
|
-
def
|
|
191
|
+
def test_make_path(self, data, expect):
|
|
192
192
|
os.environ["DUMMY_ENV_PARAM"] = "dummy_parent"
|
|
193
193
|
os.environ["EMPTY_ENV_PARAM"] = ""
|
|
194
|
-
|
|
194
|
+
|
|
195
|
+
self.assertEqual(make_path(expect), make_path(expect, normalize=True))
|
|
196
|
+
self.assertEqual(make_path(expect), make_path(data, expand=True, normalize=True, absolute=True, resolve=True))
|
|
197
|
+
|
|
198
|
+
if not make_path(expect).exists():
|
|
199
|
+
with self.assertRaises(FileNotFoundError):
|
|
200
|
+
make_path(expect, check_exists=True)
|
|
201
|
+
with self.assertRaises(NotADirectoryError):
|
|
202
|
+
make_path(expect, check_is_dir=True)
|
|
203
|
+
with self.assertRaises(FileNotFoundError):
|
|
204
|
+
make_path(expect, check_is_file=True)
|
|
195
205
|
|
|
196
206
|
data_path_depth = [
|
|
197
207
|
(".", ".", 0),
|
|
@@ -272,10 +282,12 @@ class ShUtilsTest(unittest.TestCase):
|
|
|
272
282
|
@ddt.idata(data_glob_match)
|
|
273
283
|
@ddt.unpack
|
|
274
284
|
def test_glob_match(self, names, include_patterns, exclude_patterns, expect):
|
|
275
|
-
self.assertSetEqual(
|
|
276
|
-
|
|
285
|
+
self.assertSetEqual(
|
|
286
|
+
set(map(lambda x: make_path(x, normalize=True), glob_match(names, include_patterns, exclude_patterns))),
|
|
287
|
+
set(map(lambda x: make_path(x, normalize=True), expect)),
|
|
288
|
+
)
|
|
277
289
|
|
|
278
|
-
|
|
290
|
+
data_scan_files = [
|
|
279
291
|
(
|
|
280
292
|
"unittest/shutils",
|
|
281
293
|
[],
|
|
@@ -411,16 +423,18 @@ class ShUtilsTest(unittest.TestCase):
|
|
|
411
423
|
),
|
|
412
424
|
]
|
|
413
425
|
|
|
414
|
-
@ddt.idata(
|
|
426
|
+
@ddt.idata(data_scan_files)
|
|
415
427
|
@ddt.unpack
|
|
416
|
-
def
|
|
417
|
-
self.assertSetEqual(
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
428
|
+
def test_scan_files(self, path, include_patterns, exclude_patterns, depth, expect):
|
|
429
|
+
self.assertSetEqual(
|
|
430
|
+
set(map(lambda x: make_path(x, normalize=True), scan_files(resources_directory / path,
|
|
431
|
+
include_patterns=include_patterns,
|
|
432
|
+
exclude_patterns=exclude_patterns,
|
|
433
|
+
depth=depth))),
|
|
434
|
+
set(map(lambda x: make_path(x, normalize=True), map(lambda x: resources_directory / x, expect))),
|
|
435
|
+
)
|
|
422
436
|
|
|
423
|
-
|
|
437
|
+
data_copy_files = [
|
|
424
438
|
(
|
|
425
439
|
"unittest/shutils",
|
|
426
440
|
"unittest/shutils",
|
|
@@ -568,16 +582,18 @@ class ShUtilsTest(unittest.TestCase):
|
|
|
568
582
|
),
|
|
569
583
|
]
|
|
570
584
|
|
|
571
|
-
@ddt.idata(
|
|
585
|
+
@ddt.idata(data_copy_files)
|
|
572
586
|
@ddt.unpack
|
|
573
|
-
def
|
|
587
|
+
def test_copy_files(self, src, dst, include_patterns, exclude_patterns, depth, expect):
|
|
574
588
|
with tempfile.TemporaryDirectory() as temp_directory:
|
|
575
589
|
temp_directory = pathlib.Path(temp_directory)
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
590
|
+
copy_files(resources_directory / src,
|
|
591
|
+
temp_directory / dst,
|
|
592
|
+
include_patterns=include_patterns,
|
|
593
|
+
exclude_patterns=exclude_patterns,
|
|
594
|
+
depth=depth)
|
|
581
595
|
|
|
582
|
-
self.assertSetEqual(
|
|
583
|
-
|
|
596
|
+
self.assertSetEqual(
|
|
597
|
+
set(map(lambda x: make_path(x, normalize=True), scan_files(temp_directory / dst))),
|
|
598
|
+
set(map(lambda x: make_path(x, normalize=True), map(lambda x: temp_directory / x, expect))),
|
|
599
|
+
)
|
|
@@ -4,7 +4,7 @@ from typing import Dict, List, Optional, SupportsFloat, SupportsInt, Union
|
|
|
4
4
|
|
|
5
5
|
import ddt
|
|
6
6
|
|
|
7
|
-
from iker.common.utils.typeutils import is_identical_type, is_optional_type
|
|
7
|
+
from iker.common.utils.typeutils import is_identical_type, is_optional_type, print_type
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
class DummyClass(object):
|
|
@@ -309,6 +309,8 @@ class TypeUtilsTest(unittest.TestCase):
|
|
|
309
309
|
@ddt.idata(data_is_identical_type)
|
|
310
310
|
@ddt.unpack
|
|
311
311
|
def test_is_identical_type(self, a, b, strict_optional, expect):
|
|
312
|
+
print_type(a)
|
|
313
|
+
print_type(b)
|
|
312
314
|
self.assertEqual(is_identical_type(a, b, strict_optional=strict_optional), expect)
|
|
313
315
|
self.assertEqual(is_identical_type(b, a, strict_optional=strict_optional), expect)
|
|
314
316
|
|
|
@@ -407,6 +409,8 @@ class TypeUtilsTest(unittest.TestCase):
|
|
|
407
409
|
@ddt.idata(data_is_identical_type__variant)
|
|
408
410
|
@ddt.unpack
|
|
409
411
|
def test_is_identical_type__variant(self, a, b, covariant, contravariant, expect):
|
|
412
|
+
print_type(a)
|
|
413
|
+
print_type(b)
|
|
410
414
|
self.assertEqual(is_identical_type(a,
|
|
411
415
|
b,
|
|
412
416
|
strict_optional=False,
|
|
@@ -1,239 +0,0 @@
|
|
|
1
|
-
import fnmatch
|
|
2
|
-
import os
|
|
3
|
-
import shutil
|
|
4
|
-
from typing import Protocol
|
|
5
|
-
|
|
6
|
-
from iker.common.utils.iterutils import last, last_or_none, tail_iter
|
|
7
|
-
from iker.common.utils.strutils import is_empty
|
|
8
|
-
|
|
9
|
-
__all__ = [
|
|
10
|
-
"extension",
|
|
11
|
-
"extensions",
|
|
12
|
-
"stem",
|
|
13
|
-
"expanded_path",
|
|
14
|
-
"norm_path",
|
|
15
|
-
"path_depth",
|
|
16
|
-
"glob_match",
|
|
17
|
-
"listfile",
|
|
18
|
-
"copy",
|
|
19
|
-
"run",
|
|
20
|
-
"execute",
|
|
21
|
-
]
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
def extension(filename: str | os.PathLike[str]) -> str:
|
|
25
|
-
"""
|
|
26
|
-
Extracts the filename extension from the given filename or path.
|
|
27
|
-
|
|
28
|
-
:param filename: The specific filename or path.
|
|
29
|
-
:return: The filename extension, including the leading dot (e.g., ".txt").
|
|
30
|
-
"""
|
|
31
|
-
_, result = os.path.splitext(os.path.basename(filename))
|
|
32
|
-
return result
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
def stem(filename: str | os.PathLike[str], minimal: bool = False) -> str:
|
|
36
|
-
"""
|
|
37
|
-
Extracts the filename stem from the given filename or path.
|
|
38
|
-
|
|
39
|
-
:param filename: The specific filename or path.
|
|
40
|
-
:param minimal: If ``True``, extracts the minimal (shortest) stem, otherwise the default stem.
|
|
41
|
-
:return: The filename stem.
|
|
42
|
-
"""
|
|
43
|
-
base = os.path.basename(filename)
|
|
44
|
-
if not minimal:
|
|
45
|
-
result, _ = os.path.splitext(base)
|
|
46
|
-
return result
|
|
47
|
-
else:
|
|
48
|
-
maximal_extension = last_or_none(extensions(base))
|
|
49
|
-
if is_empty(maximal_extension):
|
|
50
|
-
return base
|
|
51
|
-
else:
|
|
52
|
-
return base[:-len(maximal_extension)]
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
def extensions(filename: str | os.PathLike[str]) -> list[str]:
|
|
56
|
-
"""
|
|
57
|
-
Extracts all filename extensions and compound extensions from the given filename or path.
|
|
58
|
-
|
|
59
|
-
:param filename: The specific filename or path.
|
|
60
|
-
:return: List of all extensions, ordered from shortest to longest, each including the leading dot.
|
|
61
|
-
"""
|
|
62
|
-
base = os.path.basename(filename)
|
|
63
|
-
results = [""]
|
|
64
|
-
while True:
|
|
65
|
-
fn, ext = os.path.splitext(base)
|
|
66
|
-
base = fn
|
|
67
|
-
if is_empty(ext):
|
|
68
|
-
break
|
|
69
|
-
results.append(ext + last(results))
|
|
70
|
-
return list(tail_iter(results))
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
def expanded_path(path: str | os.PathLike[str]) -> str:
|
|
74
|
-
"""
|
|
75
|
-
Returns the absolute expanded path, expanding environment variables and the home tilde.
|
|
76
|
-
|
|
77
|
-
:param path: The given path.
|
|
78
|
-
:return: The absolute canonical path.
|
|
79
|
-
"""
|
|
80
|
-
return os.path.abspath(os.path.expanduser(os.path.expandvars(path)))
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
def norm_path(path: str | os.PathLike[str]) -> str:
|
|
84
|
-
"""
|
|
85
|
-
Returns the normalized path, collapsing redundant separators and up-level references.
|
|
86
|
-
|
|
87
|
-
:param path: The given path.
|
|
88
|
-
:return: The normalized path.
|
|
89
|
-
"""
|
|
90
|
-
_, path = os.path.splitdrive(os.path.normpath(path))
|
|
91
|
-
return path
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
def path_depth(root: str | os.PathLike[str], child: str | os.PathLike[str]) -> int:
|
|
95
|
-
"""
|
|
96
|
-
Returns the relative path depth from the given ``child`` to the ``root``.
|
|
97
|
-
|
|
98
|
-
:param root: The root path.
|
|
99
|
-
:param child: The child path.
|
|
100
|
-
:return: Relative depth, or -1 if ``child`` is not under ``root``.
|
|
101
|
-
"""
|
|
102
|
-
root_expanded = expanded_path(root)
|
|
103
|
-
child_expanded = expanded_path(child)
|
|
104
|
-
if not child_expanded.startswith(root_expanded):
|
|
105
|
-
return -1
|
|
106
|
-
return child_expanded[len(root_expanded):].count(os.sep)
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
def glob_match(
|
|
110
|
-
names: list[str],
|
|
111
|
-
include_patterns: list[str] | None = None,
|
|
112
|
-
exclude_patterns: list[str] | None = None,
|
|
113
|
-
) -> list[str]:
|
|
114
|
-
"""
|
|
115
|
-
Applies the given inclusive and exclusive glob patterns to the given ``names`` and returns the filtered result.
|
|
116
|
-
|
|
117
|
-
:param names: Names to apply the glob patterns to.
|
|
118
|
-
:param include_patterns: Inclusive glob patterns.
|
|
119
|
-
:param exclude_patterns: Exclusive glob patterns.
|
|
120
|
-
:return: Filtered names matching the patterns.
|
|
121
|
-
"""
|
|
122
|
-
ret = set()
|
|
123
|
-
for pat in (include_patterns or []):
|
|
124
|
-
ret.update(fnmatch.filter(names, pat))
|
|
125
|
-
if include_patterns is None or len(include_patterns) == 0:
|
|
126
|
-
ret.update(names)
|
|
127
|
-
for pat in (exclude_patterns or []):
|
|
128
|
-
ret.difference_update(fnmatch.filter(names, pat))
|
|
129
|
-
return list(ret)
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
class CopyFuncProtocol(Protocol):
|
|
133
|
-
def __call__(self, src: str | os.PathLike[str], dst: str | os.PathLike[str], **kwargs) -> None: ...
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
def listfile(
|
|
137
|
-
path: str | os.PathLike[str],
|
|
138
|
-
*,
|
|
139
|
-
include_patterns: list[str] | None = None,
|
|
140
|
-
exclude_patterns: list[str] | None = None,
|
|
141
|
-
depth: int = 0,
|
|
142
|
-
) -> list[str]:
|
|
143
|
-
"""
|
|
144
|
-
Recursively scans the given ``path`` and returns a list of files whose names satisfy the given name patterns and the
|
|
145
|
-
relative depth of their folders to the given root path is not greater than the specified ``depth`` value.
|
|
146
|
-
|
|
147
|
-
:param path: The root path to scan.
|
|
148
|
-
:param include_patterns: Inclusive glob patterns applied to the filenames.
|
|
149
|
-
:param exclude_patterns: Exclusive glob patterns applied to the filenames.
|
|
150
|
-
:param depth: Maximum depth of the subdirectories included in the scan.
|
|
151
|
-
:return: List of file paths matching the criteria.
|
|
152
|
-
"""
|
|
153
|
-
if os.path.exists(path) and not os.path.isdir(path):
|
|
154
|
-
if len(glob_match([os.path.basename(path)], include_patterns, exclude_patterns)) == 0:
|
|
155
|
-
return []
|
|
156
|
-
return [path]
|
|
157
|
-
|
|
158
|
-
ret = []
|
|
159
|
-
for parent, dirs, filenames in os.walk(path):
|
|
160
|
-
if 0 < depth <= path_depth(path, parent):
|
|
161
|
-
continue
|
|
162
|
-
for filename in glob_match(filenames, include_patterns, exclude_patterns):
|
|
163
|
-
ret.append(os.path.join(parent, filename))
|
|
164
|
-
return ret
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
def copy(
|
|
168
|
-
src: str | os.PathLike[str],
|
|
169
|
-
dst: str | os.PathLike[str],
|
|
170
|
-
*,
|
|
171
|
-
include_patterns: list[str] | None = None,
|
|
172
|
-
exclude_patterns: list[str] | None = None,
|
|
173
|
-
depth: int = 0,
|
|
174
|
-
follow_symlinks: bool = False,
|
|
175
|
-
ignore_dangling_symlinks: bool = False,
|
|
176
|
-
dirs_exist_ok: bool = False,
|
|
177
|
-
copy_func: CopyFuncProtocol = shutil.copy2
|
|
178
|
-
):
|
|
179
|
-
"""
|
|
180
|
-
Recursively copies the given source path to the destination path. Only copies the files whose names satisfy the
|
|
181
|
-
given name patterns and the relative depth of their folders to the given source path is not greater than the
|
|
182
|
-
specified ``depth`` value.
|
|
183
|
-
|
|
184
|
-
:param src: The source path or file.
|
|
185
|
-
:param dst: The destination path or file.
|
|
186
|
-
:param include_patterns: Inclusive glob patterns applied to the filenames.
|
|
187
|
-
:param exclude_patterns: Exclusive glob patterns applied to the filenames.
|
|
188
|
-
:param depth: Maximum depth of the subdirectories included in the scan.
|
|
189
|
-
:param follow_symlinks: If ``True``, create symbolic links for the symbolic links present in the source; otherwise, make a physical copy.
|
|
190
|
-
:param ignore_dangling_symlinks: If ``True``, ignore errors if the file pointed by the symbolic link does not exist.
|
|
191
|
-
:param dirs_exist_ok: If ``True``, ignore errors if the destination directory and subdirectories exist.
|
|
192
|
-
:param copy_func: Copy function to use for copying files.
|
|
193
|
-
"""
|
|
194
|
-
if not os.path.isdir(src):
|
|
195
|
-
if len(glob_match([os.path.basename(src)], include_patterns, exclude_patterns)) == 0:
|
|
196
|
-
return
|
|
197
|
-
if not os.path.exists(dst):
|
|
198
|
-
os.makedirs(os.path.dirname(dst), exist_ok=True)
|
|
199
|
-
copy_func(src, dst, follow_symlinks=follow_symlinks)
|
|
200
|
-
return
|
|
201
|
-
|
|
202
|
-
def ignore_func(parent, names):
|
|
203
|
-
filenames = list(filter(lambda x: not os.path.isdir(os.path.join(parent, x)), names))
|
|
204
|
-
ret = set(filenames)
|
|
205
|
-
if 0 < depth <= path_depth(src, parent):
|
|
206
|
-
return ret
|
|
207
|
-
ret.difference_update(glob_match(filenames, include_patterns, exclude_patterns))
|
|
208
|
-
return ret
|
|
209
|
-
|
|
210
|
-
shutil.copytree(src,
|
|
211
|
-
dst,
|
|
212
|
-
symlinks=follow_symlinks,
|
|
213
|
-
ignore=ignore_func,
|
|
214
|
-
ignore_dangling_symlinks=ignore_dangling_symlinks,
|
|
215
|
-
dirs_exist_ok=dirs_exist_ok,
|
|
216
|
-
copy_function=copy_func)
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
def run(cmd: str) -> bool:
|
|
220
|
-
"""
|
|
221
|
-
Runs the given command and returns the success status.
|
|
222
|
-
|
|
223
|
-
:param cmd: Command to run.
|
|
224
|
-
:return: ``True`` if the command has been successfully run, ``False`` otherwise.
|
|
225
|
-
"""
|
|
226
|
-
return os.system(cmd) == 0
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
def execute(cmd: str, strip: bool = True) -> str:
|
|
230
|
-
"""
|
|
231
|
-
Executes the given command and returns contents collected from standard output.
|
|
232
|
-
|
|
233
|
-
:param cmd: Command to execute.
|
|
234
|
-
:param strip: If ``True``, the contents will be stripped.
|
|
235
|
-
:return: The content from standard output.
|
|
236
|
-
"""
|
|
237
|
-
if strip:
|
|
238
|
-
return os.popen(cmd).read().strip()
|
|
239
|
-
return os.popen(cmd).read()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{iker_python_common-1.0.69 → iker_python_common-1.0.71}/resources/unittest/config/config.cfg
RENAMED
|
File without changes
|
{iker_python_common-1.0.69 → iker_python_common-1.0.71}/resources/unittest/csvutils/data.csv
RENAMED
|
File without changes
|
{iker_python_common-1.0.69 → iker_python_common-1.0.71}/resources/unittest/csvutils/data.tsv
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{iker_python_common-1.0.69 → iker_python_common-1.0.71}/resources/unittest/shutils/dir.foo/file.bar
RENAMED
|
File without changes
|
{iker_python_common-1.0.69 → iker_python_common-1.0.71}/resources/unittest/shutils/dir.foo/file.baz
RENAMED
|
File without changes
|
{iker_python_common-1.0.69 → iker_python_common-1.0.71}/resources/unittest/shutils/dir.foo/file.foo
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{iker_python_common-1.0.69 → iker_python_common-1.0.71}/src/iker_python_common.egg-info/SOURCES.txt
RENAMED
|
File without changes
|
|
File without changes
|
{iker_python_common-1.0.69 → iker_python_common-1.0.71}/src/iker_python_common.egg-info/not-zip-safe
RENAMED
|
File without changes
|
{iker_python_common-1.0.69 → iker_python_common-1.0.71}/src/iker_python_common.egg-info/requires.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{iker_python_common-1.0.69 → iker_python_common-1.0.71}/test/iker_tests/common/utils/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{iker_python_common-1.0.69 → iker_python_common-1.0.71}/test/iker_tests/common/utils/config_test.py
RENAMED
|
File without changes
|
{iker_python_common-1.0.69 → iker_python_common-1.0.71}/test/iker_tests/common/utils/dbutils_test.py
RENAMED
|
File without changes
|
{iker_python_common-1.0.69 → iker_python_common-1.0.71}/test/iker_tests/common/utils/dtutils_test.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{iker_python_common-1.0.69 → iker_python_common-1.0.71}/test/iker_tests/common/utils/logger_test.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{iker_python_common-1.0.69 → iker_python_common-1.0.71}/test/iker_tests/common/utils/retry_test.py
RENAMED
|
File without changes
|
{iker_python_common-1.0.69 → iker_python_common-1.0.71}/test/iker_tests/common/utils/span_test.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|