superpathlib 2.0.9__tar.gz → 2.0.10__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.
- {superpathlib-2.0.9/src/superpathlib.egg-info → superpathlib-2.0.10}/PKG-INFO +5 -4
- {superpathlib-2.0.9 → superpathlib-2.0.10}/pyproject.toml +3 -5
- {superpathlib-2.0.9 → superpathlib-2.0.10}/src/superpathlib/cached_content.py +1 -1
- {superpathlib-2.0.9 → superpathlib-2.0.10}/src/superpathlib/common_folders.py +15 -12
- {superpathlib-2.0.9 → superpathlib-2.0.10}/src/superpathlib/content_properties.py +2 -2
- {superpathlib-2.0.9 → superpathlib-2.0.10}/src/superpathlib/encryption.py +1 -0
- {superpathlib-2.0.9 → superpathlib-2.0.10}/src/superpathlib/extra_functionality.py +35 -34
- {superpathlib-2.0.9 → superpathlib-2.0.10}/src/superpathlib/metadata_properties.py +1 -9
- {superpathlib-2.0.9 → superpathlib-2.0.10}/src/superpathlib/override.py +7 -5
- {superpathlib-2.0.9 → superpathlib-2.0.10}/src/superpathlib/utils.py +2 -2
- {superpathlib-2.0.9 → superpathlib-2.0.10/src/superpathlib.egg-info}/PKG-INFO +5 -4
- {superpathlib-2.0.9 → superpathlib-2.0.10}/src/superpathlib.egg-info/requires.txt +1 -1
- {superpathlib-2.0.9 → superpathlib-2.0.10}/tests/test_cached_content.py +2 -2
- {superpathlib-2.0.9 → superpathlib-2.0.10}/tests/test_functionality.py +4 -0
- {superpathlib-2.0.9 → superpathlib-2.0.10}/tests/test_metadata.py +2 -2
- {superpathlib-2.0.9 → superpathlib-2.0.10}/LICENSE +0 -0
- {superpathlib-2.0.9 → superpathlib-2.0.10}/README.md +0 -0
- {superpathlib-2.0.9 → superpathlib-2.0.10}/setup.cfg +0 -0
- {superpathlib-2.0.9 → superpathlib-2.0.10}/src/superpathlib/__init__.py +0 -0
- {superpathlib-2.0.9 → superpathlib-2.0.10}/src/superpathlib/base.py +0 -0
- {superpathlib-2.0.9 → superpathlib-2.0.10}/src/superpathlib/path.py +0 -0
- {superpathlib-2.0.9 → superpathlib-2.0.10}/src/superpathlib/py.typed +0 -0
- {superpathlib-2.0.9 → superpathlib-2.0.10}/src/superpathlib/tags.py +0 -0
- {superpathlib-2.0.9 → superpathlib-2.0.10}/src/superpathlib.egg-info/SOURCES.txt +0 -0
- {superpathlib-2.0.9 → superpathlib-2.0.10}/src/superpathlib.egg-info/dependency_links.txt +0 -0
- {superpathlib-2.0.9 → superpathlib-2.0.10}/src/superpathlib.egg-info/top_level.txt +0 -0
- {superpathlib-2.0.9 → superpathlib-2.0.10}/tests/test_common_folders.py +0 -0
- {superpathlib-2.0.9 → superpathlib-2.0.10}/tests/test_content.py +0 -0
- {superpathlib-2.0.9 → superpathlib-2.0.10}/tests/test_encrypted_content.py +0 -0
- {superpathlib-2.0.9 → superpathlib-2.0.10}/tests/test_inheritance.py +0 -0
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: superpathlib
|
|
3
|
-
Version: 2.0.
|
|
3
|
+
Version: 2.0.10
|
|
4
4
|
Summary: Extended Pathlib
|
|
5
5
|
Author-email: Quinten Roets <qdr2104@columbia.edu>
|
|
6
|
-
License: MIT
|
|
6
|
+
License-Expression: MIT
|
|
7
7
|
Project-URL: Source Code, https://github.com/quintenroets/superpathlib
|
|
8
8
|
Requires-Python: >=3.10
|
|
9
9
|
Description-Content-Type: text/markdown
|
|
@@ -17,13 +17,14 @@ Requires-Dist: PyYaml<7,>=6.0.1; extra == "full"
|
|
|
17
17
|
Requires-Dist: xattr<2,>=0.10.1; extra == "full"
|
|
18
18
|
Provides-Extra: dev
|
|
19
19
|
Requires-Dist: hypothesis<7,>=6.97.1; extra == "dev"
|
|
20
|
-
Requires-Dist: package-dev-tools<1,>=0.
|
|
20
|
+
Requires-Dist: package-dev-tools<1,>=0.7.1; extra == "dev"
|
|
21
21
|
Requires-Dist: types-PyYaml<7,>=6.0.12.12; extra == "dev"
|
|
22
22
|
Requires-Dist: dirhash<1,>=0.2.1; extra == "dev"
|
|
23
23
|
Requires-Dist: numpy<3,>=1.26.4; extra == "dev"
|
|
24
24
|
Requires-Dist: package-utils<1,>=0.6.1; extra == "dev"
|
|
25
25
|
Requires-Dist: PyYaml<7,>=6.0.1; extra == "dev"
|
|
26
26
|
Requires-Dist: xattr<2,>=0.10.1; extra == "dev"
|
|
27
|
+
Dynamic: license-file
|
|
27
28
|
|
|
28
29
|
# Superpathlib
|
|
29
30
|
[](https://badge.fury.io/py/superpathlib)
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "superpathlib"
|
|
3
|
-
version = "2.0.
|
|
3
|
+
version = "2.0.10"
|
|
4
4
|
description = "Extended Pathlib"
|
|
5
5
|
authors = [{name = "Quinten Roets", email = "qdr2104@columbia.edu"}]
|
|
6
|
-
license =
|
|
6
|
+
license = "MIT"
|
|
7
7
|
readme = "README.md"
|
|
8
8
|
requires-python = ">=3.10"
|
|
9
9
|
dependencies = [
|
|
@@ -20,7 +20,7 @@ full = [
|
|
|
20
20
|
]
|
|
21
21
|
dev = [
|
|
22
22
|
"hypothesis >=6.97.1, <7",
|
|
23
|
-
"package-dev-tools >=0.
|
|
23
|
+
"package-dev-tools >=0.7.1, <1",
|
|
24
24
|
|
|
25
25
|
# types
|
|
26
26
|
"types-PyYaml >=6.0.12.12, <7",
|
|
@@ -69,8 +69,6 @@ fix = true
|
|
|
69
69
|
[tool.ruff.lint]
|
|
70
70
|
select = ["ALL"]
|
|
71
71
|
ignore = [
|
|
72
|
-
"ANN101", # annotate self
|
|
73
|
-
"ANN102", # annotate cls
|
|
74
72
|
"ANN401", # annotated with Any
|
|
75
73
|
"D", # docstrings
|
|
76
74
|
"G004", # logging f-string
|
|
@@ -4,6 +4,7 @@ import typing
|
|
|
4
4
|
from typing import Any, TypeVar
|
|
5
5
|
|
|
6
6
|
from simple_classproperty import classproperty
|
|
7
|
+
from typing_extensions import Self
|
|
7
8
|
|
|
8
9
|
from . import base
|
|
9
10
|
|
|
@@ -27,7 +28,9 @@ class PropertyMeta(abc.ABCMeta):
|
|
|
27
28
|
) -> "PropertyMeta":
|
|
28
29
|
meta_class = super().__new__(cls, name, bases, attributes)
|
|
29
30
|
if sys.version_info >= (3, 13):
|
|
30
|
-
enable_classproperties(
|
|
31
|
+
enable_classproperties(
|
|
32
|
+
meta_class, # type: ignore[arg-type]
|
|
33
|
+
) # pragma: nocover
|
|
31
34
|
return meta_class
|
|
32
35
|
|
|
33
36
|
|
|
@@ -42,37 +45,37 @@ class Path(base.Path, metaclass=PropertyMeta):
|
|
|
42
45
|
|
|
43
46
|
@classmethod
|
|
44
47
|
@classproperty
|
|
45
|
-
def HOME(cls
|
|
48
|
+
def HOME(cls) -> Self: # noqa: N802
|
|
46
49
|
return cls.home()
|
|
47
50
|
|
|
48
51
|
@classmethod
|
|
49
52
|
@classproperty
|
|
50
|
-
def docs(cls
|
|
53
|
+
def docs(cls) -> Self:
|
|
51
54
|
path = cls.HOME / "Documents"
|
|
52
|
-
return typing.cast(
|
|
55
|
+
return typing.cast("Self", path)
|
|
53
56
|
|
|
54
57
|
@classmethod
|
|
55
58
|
@classproperty
|
|
56
|
-
def scripts(cls
|
|
59
|
+
def scripts(cls) -> Self:
|
|
57
60
|
path = cls.docs / "Scripts"
|
|
58
|
-
return typing.cast(
|
|
61
|
+
return typing.cast("Self", path)
|
|
59
62
|
|
|
60
63
|
@classmethod
|
|
61
64
|
@classproperty
|
|
62
|
-
def script_assets(cls
|
|
65
|
+
def script_assets(cls) -> Self:
|
|
63
66
|
path = cls.scripts / "assets"
|
|
64
|
-
return typing.cast(
|
|
67
|
+
return typing.cast("Self", path)
|
|
65
68
|
|
|
66
69
|
@classmethod
|
|
67
70
|
@classproperty
|
|
68
|
-
def assets(cls
|
|
71
|
+
def assets(cls) -> Self:
|
|
69
72
|
"""
|
|
70
73
|
Often overwritten by child classes for specific project.
|
|
71
74
|
"""
|
|
72
|
-
return typing.cast(
|
|
75
|
+
return typing.cast("Self", cls.script_assets)
|
|
73
76
|
|
|
74
77
|
@classmethod
|
|
75
78
|
@classproperty
|
|
76
|
-
def draft(cls
|
|
79
|
+
def draft(cls) -> Self:
|
|
77
80
|
path = cls.docs / "draft.txt"
|
|
78
|
-
return typing.cast(
|
|
81
|
+
return typing.cast("Self", path)
|
|
@@ -53,12 +53,12 @@ class Path(base.Path):
|
|
|
53
53
|
@content_lines.setter
|
|
54
54
|
def content_lines(self, lines: Iterable[Any]) -> None:
|
|
55
55
|
lines = (line for line in lines if line)
|
|
56
|
-
self.lines = typing.cast(list[str], lines)
|
|
56
|
+
self.lines = typing.cast("list[str]", lines)
|
|
57
57
|
|
|
58
58
|
@property
|
|
59
59
|
def json(self) -> dict[str, Any] | list[Any]:
|
|
60
60
|
value = json.loads(self.text or "{}")
|
|
61
|
-
return typing.cast(dict[str, Any] | list[Any], value)
|
|
61
|
+
return typing.cast("dict[str, Any] | list[Any]", value)
|
|
62
62
|
|
|
63
63
|
@json.setter
|
|
64
64
|
def json(self, content: dict[Any, Any] | list[Any]) -> None:
|
|
@@ -5,43 +5,44 @@ import tempfile
|
|
|
5
5
|
import time
|
|
6
6
|
import typing
|
|
7
7
|
import urllib.parse
|
|
8
|
+
from collections import deque
|
|
8
9
|
from collections.abc import Callable, Iterator
|
|
9
10
|
from functools import cached_property
|
|
10
11
|
from types import TracebackType
|
|
11
|
-
from typing import Any,
|
|
12
|
+
from typing import Any, cast
|
|
13
|
+
|
|
14
|
+
from typing_extensions import Self
|
|
12
15
|
|
|
13
16
|
from . import cached_content
|
|
14
17
|
from .utils import find_first_match
|
|
15
18
|
|
|
16
|
-
PathType = TypeVar("PathType", bound="Path")
|
|
17
|
-
|
|
18
19
|
|
|
19
20
|
class Path(cached_content.Path):
|
|
20
21
|
"""
|
|
21
22
|
Additional functionality.
|
|
22
23
|
"""
|
|
23
24
|
|
|
24
|
-
def create_parent(self
|
|
25
|
+
def create_parent(self) -> Self:
|
|
25
26
|
self.parent.mkdir(parents=True, exist_ok=True)
|
|
26
27
|
return self.parent
|
|
27
28
|
|
|
28
|
-
def with_nonexistent_name(self
|
|
29
|
+
def with_nonexistent_name(self) -> Self:
|
|
29
30
|
path = self
|
|
30
31
|
if path.exists():
|
|
31
32
|
stem = path.stem
|
|
32
33
|
|
|
33
|
-
def with_number(i: int) ->
|
|
34
|
+
def with_number(i: int) -> "Path":
|
|
34
35
|
return path.with_stem(f"{stem} ({i})")
|
|
35
36
|
|
|
36
37
|
def nonexistent(i: int) -> bool:
|
|
37
38
|
return not with_number(i).exists()
|
|
38
39
|
|
|
39
40
|
first_free_number = find_first_match(nonexistent)
|
|
40
|
-
path = with_number(first_free_number)
|
|
41
|
+
path = cast("Self", with_number(first_free_number))
|
|
41
42
|
|
|
42
43
|
return path
|
|
43
44
|
|
|
44
|
-
def with_timestamp(self
|
|
45
|
+
def with_timestamp(self) -> Self:
|
|
45
46
|
from datetime import datetime, timezone
|
|
46
47
|
|
|
47
48
|
timestamp = int(time.time()) # precision up to second
|
|
@@ -50,7 +51,7 @@ class Path(cached_content.Path):
|
|
|
50
51
|
|
|
51
52
|
def copy_to(
|
|
52
53
|
self,
|
|
53
|
-
dest:
|
|
54
|
+
dest: Self,
|
|
54
55
|
*,
|
|
55
56
|
include_properties: bool = True,
|
|
56
57
|
only_if_newer: bool = False,
|
|
@@ -60,7 +61,7 @@ class Path(cached_content.Path):
|
|
|
60
61
|
if include_properties:
|
|
61
62
|
self.copy_properties_to(dest)
|
|
62
63
|
|
|
63
|
-
def copy_properties_to(self, dest:
|
|
64
|
+
def copy_properties_to(self, dest: Self) -> None:
|
|
64
65
|
for path in dest.find():
|
|
65
66
|
path.tag = self.tag
|
|
66
67
|
path.mtime = self.mtime
|
|
@@ -70,20 +71,20 @@ class Path(cached_content.Path):
|
|
|
70
71
|
# noinspection PyProtectedMember
|
|
71
72
|
path_str = str(self)
|
|
72
73
|
format_ = shutil._find_unpack_format(path_str) # type: ignore[attr-defined] # noqa: SLF001
|
|
73
|
-
return typing.cast(str, format_)
|
|
74
|
+
return typing.cast("str", format_)
|
|
74
75
|
|
|
75
76
|
def unpack_if_archive(
|
|
76
77
|
self,
|
|
77
78
|
*,
|
|
78
|
-
extraction_directory:
|
|
79
|
+
extraction_directory: Self | None = None,
|
|
79
80
|
recursive: bool = True,
|
|
80
81
|
) -> None:
|
|
81
82
|
if self.archive_format is not None:
|
|
82
83
|
self.unpack(extraction_directory, recursive=recursive)
|
|
83
84
|
|
|
84
85
|
def unpack( # noqa: PLR0913
|
|
85
|
-
self
|
|
86
|
-
extraction_directory:
|
|
86
|
+
self,
|
|
87
|
+
extraction_directory: Self | None = None,
|
|
87
88
|
*,
|
|
88
89
|
remove_existing: bool = True,
|
|
89
90
|
preserve_properties: bool = True,
|
|
@@ -91,7 +92,7 @@ class Path(cached_content.Path):
|
|
|
91
92
|
archive_format: str | None = None,
|
|
92
93
|
recursive: bool = True,
|
|
93
94
|
) -> None:
|
|
94
|
-
def cleanup(cleanup_path:
|
|
95
|
+
def cleanup(cleanup_path: Self) -> None:
|
|
95
96
|
(cleanup_path / "__MACOSX").rmtree(missing_ok=True)
|
|
96
97
|
subfolder = cleanup_path / cleanup_path.name
|
|
97
98
|
if subfolder.exists() and cleanup_path.number_of_children == 1:
|
|
@@ -129,7 +130,7 @@ class Path(cached_content.Path):
|
|
|
129
130
|
for path in extraction_directory.find():
|
|
130
131
|
path.unpack_if_archive()
|
|
131
132
|
|
|
132
|
-
def create_extraction_directory(self
|
|
133
|
+
def create_extraction_directory(self, archive_format: str) -> Self:
|
|
133
134
|
extract_name = self.name
|
|
134
135
|
# noinspection PyProtectedMember
|
|
135
136
|
unpack_formats = shutil._UNPACK_FORMATS # type: ignore[attr-defined] # noqa: SLF001
|
|
@@ -180,7 +181,7 @@ class Path(cached_content.Path):
|
|
|
180
181
|
def update(self, value: dict[Any, Any]) -> dict[Any, Any]:
|
|
181
182
|
# only read and write if value to add not empty
|
|
182
183
|
if value:
|
|
183
|
-
current_content = cast(dict[Any, Any], self.yaml)
|
|
184
|
+
current_content = cast("dict[Any, Any]", self.yaml)
|
|
184
185
|
updated_content = current_content | value
|
|
185
186
|
self.yaml = updated_content
|
|
186
187
|
else:
|
|
@@ -188,20 +189,20 @@ class Path(cached_content.Path):
|
|
|
188
189
|
return updated_content
|
|
189
190
|
|
|
190
191
|
def find(
|
|
191
|
-
self
|
|
192
|
-
condition: Callable[[
|
|
193
|
-
exclude: Callable[[
|
|
192
|
+
self,
|
|
193
|
+
condition: Callable[[Self], bool] | None = None,
|
|
194
|
+
exclude: Callable[[Self], bool] = lambda _: False,
|
|
194
195
|
*,
|
|
195
196
|
recurse_on_match: bool = False,
|
|
196
197
|
follow_symlinks: bool = False,
|
|
197
198
|
only_folders: bool = False,
|
|
198
|
-
) -> Iterator[
|
|
199
|
+
) -> Iterator[Self]:
|
|
199
200
|
"""Find all subpaths under path that match condition.
|
|
200
201
|
|
|
201
202
|
only_folders option can be used for efficiency reasons
|
|
202
203
|
"""
|
|
203
204
|
|
|
204
|
-
def extract_children_to_recurse_on(path:
|
|
205
|
+
def extract_children_to_recurse_on(path: Self) -> Iterator[Self]:
|
|
205
206
|
# skip folders that do not allow listing
|
|
206
207
|
with contextlib.suppress(PermissionError):
|
|
207
208
|
for child in path.iterdir():
|
|
@@ -213,12 +214,12 @@ class Path(cached_content.Path):
|
|
|
213
214
|
if condition is None:
|
|
214
215
|
recurse_on_match = True
|
|
215
216
|
|
|
216
|
-
def condition(_:
|
|
217
|
+
def condition(_: Self) -> bool:
|
|
217
218
|
return True
|
|
218
219
|
|
|
219
|
-
to_traverse = [self] if self.exists() else []
|
|
220
|
+
to_traverse = deque([self] if self.exists() else [])
|
|
220
221
|
while to_traverse:
|
|
221
|
-
path = to_traverse.
|
|
222
|
+
path = to_traverse.popleft()
|
|
222
223
|
if not exclude(path):
|
|
223
224
|
match = condition(path)
|
|
224
225
|
if match:
|
|
@@ -226,7 +227,7 @@ class Path(cached_content.Path):
|
|
|
226
227
|
should_recurse = recurse_on_match or not match
|
|
227
228
|
should_recurse_folder = only_folders or path.is_dir()
|
|
228
229
|
if should_recurse and should_recurse_folder:
|
|
229
|
-
to_traverse
|
|
230
|
+
to_traverse.extend(extract_children_to_recurse_on(path))
|
|
230
231
|
|
|
231
232
|
def rmtree(
|
|
232
233
|
self,
|
|
@@ -248,18 +249,18 @@ class Path(cached_content.Path):
|
|
|
248
249
|
@classmethod
|
|
249
250
|
def _on_error(
|
|
250
251
|
cls,
|
|
251
|
-
|
|
252
|
+
func: Callable[[str], Any],
|
|
252
253
|
path_str: str,
|
|
253
254
|
exc_info: tuple[type[Exception], Exception, TracebackType],
|
|
254
255
|
) -> None:
|
|
255
256
|
if exc_info[0] is PermissionError and os.name == "nt": # pragma: nocover
|
|
256
257
|
path = Path(path_str)
|
|
257
258
|
path.chmod(0o777)
|
|
258
|
-
|
|
259
|
+
func(path_str)
|
|
259
260
|
else:
|
|
260
261
|
raise exc_info[0]
|
|
261
262
|
|
|
262
|
-
def subpath(self
|
|
263
|
+
def subpath(self, *parts: str) -> Self:
|
|
263
264
|
path = self
|
|
264
265
|
tokens_to_replace = os.sep, "."
|
|
265
266
|
for part in parts:
|
|
@@ -269,18 +270,18 @@ class Path(cached_content.Path):
|
|
|
269
270
|
return path
|
|
270
271
|
|
|
271
272
|
@classmethod
|
|
272
|
-
def from_uri(cls
|
|
273
|
+
def from_uri(cls, uri: str) -> Self:
|
|
273
274
|
path_str = urllib.parse.urlparse(uri).path
|
|
274
275
|
return cls(path_str)
|
|
275
276
|
|
|
276
277
|
@classmethod
|
|
277
278
|
def tempfile(
|
|
278
|
-
cls
|
|
279
|
+
cls,
|
|
279
280
|
*,
|
|
280
281
|
in_memory: bool = True,
|
|
281
282
|
create: bool = True,
|
|
282
283
|
**kwargs: Any,
|
|
283
|
-
) ->
|
|
284
|
+
) -> Self:
|
|
284
285
|
"""Usage:
|
|
285
286
|
|
|
286
287
|
with Path.tempfile() as tmp: run_command(log_file=tmp) logs = tmp.text
|
|
@@ -298,12 +299,12 @@ class Path(cached_content.Path):
|
|
|
298
299
|
return path
|
|
299
300
|
|
|
300
301
|
@classmethod
|
|
301
|
-
def tempdir(cls
|
|
302
|
+
def tempdir(cls, *, in_memory: bool = True) -> Self:
|
|
302
303
|
path = cls.tempfile(in_memory=in_memory, create=False)
|
|
303
304
|
path.mkdir()
|
|
304
305
|
return path
|
|
305
306
|
|
|
306
|
-
def __enter__(self
|
|
307
|
+
def __enter__(self) -> Self:
|
|
307
308
|
return self
|
|
308
309
|
|
|
309
310
|
def __exit__(
|
|
@@ -1,8 +1,6 @@
|
|
|
1
|
-
import contextlib
|
|
2
1
|
import hashlib
|
|
3
2
|
import mimetypes
|
|
4
3
|
import os
|
|
5
|
-
import subprocess
|
|
6
4
|
import warnings
|
|
7
5
|
from collections.abc import Callable
|
|
8
6
|
from functools import wraps
|
|
@@ -44,12 +42,6 @@ class Path(content_properties.Path):
|
|
|
44
42
|
def mtime(self, time: float) -> None:
|
|
45
43
|
os.utime(self, (time, time)) # set create time as well
|
|
46
44
|
|
|
47
|
-
command = "touch", "-d", f"@{time}", self
|
|
48
|
-
with contextlib.suppress(
|
|
49
|
-
subprocess.CalledProcessError,
|
|
50
|
-
): # Doesn't work on Windows
|
|
51
|
-
subprocess.run(command, check=False) # noqa: S603
|
|
52
|
-
|
|
53
45
|
@property
|
|
54
46
|
def tags(self) -> list[str]:
|
|
55
47
|
from .tags import XDGTags # , autoimport
|
|
@@ -119,7 +111,7 @@ class Path(content_properties.Path):
|
|
|
119
111
|
|
|
120
112
|
# use default algorithm used in cloud provider checksums
|
|
121
113
|
# can be efficient because not used for cryptographic security
|
|
122
|
-
return cast(str, dirhash.dirhash(self, "md5")) if self.has_children else None
|
|
114
|
+
return cast("str", dirhash.dirhash(self, "md5")) if self.has_children else None
|
|
123
115
|
|
|
124
116
|
@property
|
|
125
117
|
def file_content_hash(self) -> str:
|
|
@@ -6,6 +6,8 @@ from functools import wraps
|
|
|
6
6
|
from os import PathLike
|
|
7
7
|
from typing import IO, Any, TypeVar
|
|
8
8
|
|
|
9
|
+
from typing_extensions import Self
|
|
10
|
+
|
|
9
11
|
from . import encryption
|
|
10
12
|
from .metadata_properties import catch_missing
|
|
11
13
|
|
|
@@ -47,12 +49,12 @@ class Path(encryption.Path):
|
|
|
47
49
|
def rmdir(self) -> None:
|
|
48
50
|
return super().rmdir()
|
|
49
51
|
|
|
50
|
-
def iterdir(self
|
|
52
|
+
def iterdir(self, *, missing_ok: bool = True) -> Generator[Self, None, None]:
|
|
51
53
|
if self.exists() or not missing_ok:
|
|
52
54
|
yield from super().iterdir()
|
|
53
55
|
|
|
54
56
|
@create_parent_on_missing
|
|
55
|
-
def rename(self
|
|
57
|
+
def rename(self, target: str | Self, *, exist_ok: bool = False) -> Self:
|
|
56
58
|
target_path = self.__class__(target)
|
|
57
59
|
rename = super().replace if exist_ok else super().rename
|
|
58
60
|
try:
|
|
@@ -71,7 +73,7 @@ class Path(encryption.Path):
|
|
|
71
73
|
else:
|
|
72
74
|
target_path.unlink() # pragma: nocover
|
|
73
75
|
else:
|
|
74
|
-
message = f"Target already exists: {target_path
|
|
76
|
+
message = f"Target already exists: {target_path}"
|
|
75
77
|
raise RuntimeError(message) from exception
|
|
76
78
|
else:
|
|
77
79
|
target_path.create_parent()
|
|
@@ -80,9 +82,9 @@ class Path(encryption.Path):
|
|
|
80
82
|
raise
|
|
81
83
|
return target_path
|
|
82
84
|
|
|
83
|
-
def replace(self
|
|
85
|
+
def replace(self, target: str | PathLike[str]) -> Self:
|
|
84
86
|
path = self.rename(target, exist_ok=True)
|
|
85
|
-
return typing.cast(
|
|
87
|
+
return typing.cast("Self", path)
|
|
86
88
|
|
|
87
89
|
def open(self, mode: str = "r", **kwargs: Any) -> IO[Any]: # type: ignore[override]
|
|
88
90
|
try:
|
|
@@ -4,8 +4,8 @@ from collections.abc import Callable
|
|
|
4
4
|
def find_first_match(condition: Callable[..., bool]) -> int:
|
|
5
5
|
"""
|
|
6
6
|
:param condition: Condition that number needs to match.
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
The condition is assumed to be valid for all integers starting
|
|
8
|
+
from an initial value.
|
|
9
9
|
:return: First integer for which condition is valid.
|
|
10
10
|
"""
|
|
11
11
|
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: superpathlib
|
|
3
|
-
Version: 2.0.
|
|
3
|
+
Version: 2.0.10
|
|
4
4
|
Summary: Extended Pathlib
|
|
5
5
|
Author-email: Quinten Roets <qdr2104@columbia.edu>
|
|
6
|
-
License: MIT
|
|
6
|
+
License-Expression: MIT
|
|
7
7
|
Project-URL: Source Code, https://github.com/quintenroets/superpathlib
|
|
8
8
|
Requires-Python: >=3.10
|
|
9
9
|
Description-Content-Type: text/markdown
|
|
@@ -17,13 +17,14 @@ Requires-Dist: PyYaml<7,>=6.0.1; extra == "full"
|
|
|
17
17
|
Requires-Dist: xattr<2,>=0.10.1; extra == "full"
|
|
18
18
|
Provides-Extra: dev
|
|
19
19
|
Requires-Dist: hypothesis<7,>=6.97.1; extra == "dev"
|
|
20
|
-
Requires-Dist: package-dev-tools<1,>=0.
|
|
20
|
+
Requires-Dist: package-dev-tools<1,>=0.7.1; extra == "dev"
|
|
21
21
|
Requires-Dist: types-PyYaml<7,>=6.0.12.12; extra == "dev"
|
|
22
22
|
Requires-Dist: dirhash<1,>=0.2.1; extra == "dev"
|
|
23
23
|
Requires-Dist: numpy<3,>=1.26.4; extra == "dev"
|
|
24
24
|
Requires-Dist: package-utils<1,>=0.6.1; extra == "dev"
|
|
25
25
|
Requires-Dist: PyYaml<7,>=6.0.1; extra == "dev"
|
|
26
26
|
Requires-Dist: xattr<2,>=0.10.1; extra == "dev"
|
|
27
|
+
Dynamic: license-file
|
|
27
28
|
|
|
28
29
|
# Superpathlib
|
|
29
30
|
[](https://badge.fury.io/py/superpathlib)
|
|
@@ -32,7 +32,7 @@ def test_text(content: str) -> None:
|
|
|
32
32
|
def test_content(path: Path, content: dict[str, dict[str, str]]) -> None:
|
|
33
33
|
class Storage:
|
|
34
34
|
content: CachedFileContent[dict[str, dict[str, str]]] = typing.cast(
|
|
35
|
-
CachedFileContent[dict[str, dict[str, str]]],
|
|
35
|
+
"CachedFileContent[dict[str, dict[str, str]]]",
|
|
36
36
|
path.cached_content,
|
|
37
37
|
)
|
|
38
38
|
|
|
@@ -44,7 +44,7 @@ def test_content(path: Path, content: dict[str, dict[str, str]]) -> None:
|
|
|
44
44
|
def test_created_content(path: Path, content: dict[str, dict[str, str]]) -> None:
|
|
45
45
|
class Storage:
|
|
46
46
|
content: CachedFileContent[dict[str, dict[str, str]]] = typing.cast(
|
|
47
|
-
CachedFileContent[dict[str, dict[str, str]]],
|
|
47
|
+
"CachedFileContent[dict[str, dict[str, str]]]",
|
|
48
48
|
path.create_cached_content({}),
|
|
49
49
|
)
|
|
50
50
|
|
|
@@ -11,6 +11,8 @@ from tests.content import (
|
|
|
11
11
|
)
|
|
12
12
|
from tests.utils import ignore_fixture_warning
|
|
13
13
|
|
|
14
|
+
MTIME_TOLERANCE = 0.01
|
|
15
|
+
|
|
14
16
|
|
|
15
17
|
def test_tempfile() -> None:
|
|
16
18
|
with Path.tempfile() as path:
|
|
@@ -297,3 +299,5 @@ def test_rmdir(directory: Path) -> None:
|
|
|
297
299
|
|
|
298
300
|
def test_touch(path: Path) -> None:
|
|
299
301
|
path.touch(mtime=1)
|
|
302
|
+
assert path.exists()
|
|
303
|
+
assert abs(path.mtime - 1) < MTIME_TOLERANCE
|
|
@@ -6,7 +6,7 @@ from hypothesis import given, strategies
|
|
|
6
6
|
from hypothesis.strategies import lists
|
|
7
7
|
|
|
8
8
|
from superpathlib import Path
|
|
9
|
-
from tests.content import byte_content, text_strategy
|
|
9
|
+
from tests.content import byte_content, slower_test_settings, text_strategy
|
|
10
10
|
from tests.utils import ignore_fixture_warning
|
|
11
11
|
|
|
12
12
|
|
|
@@ -44,7 +44,7 @@ def test_tag(path: Path, content: str) -> None:
|
|
|
44
44
|
assert path.tag == content
|
|
45
45
|
|
|
46
46
|
|
|
47
|
-
@
|
|
47
|
+
@slower_test_settings
|
|
48
48
|
@byte_content
|
|
49
49
|
def test_size(path: Path, content: bytes) -> None:
|
|
50
50
|
assert isinstance(Path.size, property)
|
|
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
|