pathlib-next 0.3.0__tar.gz → 0.3.5__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.
Files changed (27) hide show
  1. {pathlib_next-0.3.0 → pathlib_next-0.3.5}/.gitignore +1 -0
  2. {pathlib_next-0.3.0 → pathlib_next-0.3.5}/PKG-INFO +1 -1
  3. {pathlib_next-0.3.0 → pathlib_next-0.3.5}/pyproject.toml +1 -1
  4. {pathlib_next-0.3.0 → pathlib_next-0.3.5}/src/example.py +4 -6
  5. {pathlib_next-0.3.0 → pathlib_next-0.3.5}/src/pathlib_next/uri/schemes/http.py +7 -2
  6. pathlib_next-0.3.5/src/pathlib_next/utils/sync.py +258 -0
  7. pathlib_next-0.3.0/src/pathlib_next/utils/sync.py +0 -104
  8. {pathlib_next-0.3.0 → pathlib_next-0.3.5}/LICENSE +0 -0
  9. {pathlib_next-0.3.0 → pathlib_next-0.3.5}/README.md +0 -0
  10. {pathlib_next-0.3.0 → pathlib_next-0.3.5}/src/pathlib_next/__init__.py +0 -0
  11. {pathlib_next-0.3.0 → pathlib_next-0.3.5}/src/pathlib_next/fspath.py +0 -0
  12. {pathlib_next-0.3.0 → pathlib_next-0.3.5}/src/pathlib_next/mempath.py +0 -0
  13. {pathlib_next-0.3.0 → pathlib_next-0.3.5}/src/pathlib_next/path.py +0 -0
  14. {pathlib_next-0.3.0 → pathlib_next-0.3.5}/src/pathlib_next/protocols/__init__.py +0 -0
  15. {pathlib_next-0.3.0 → pathlib_next-0.3.5}/src/pathlib_next/protocols/fs.py +0 -0
  16. {pathlib_next-0.3.0 → pathlib_next-0.3.5}/src/pathlib_next/protocols/io.py +0 -0
  17. {pathlib_next-0.3.0 → pathlib_next-0.3.5}/src/pathlib_next/uri/__init__.py +0 -0
  18. {pathlib_next-0.3.0 → pathlib_next-0.3.5}/src/pathlib_next/uri/query.py +0 -0
  19. {pathlib_next-0.3.0 → pathlib_next-0.3.5}/src/pathlib_next/uri/schemes/__init__.py +0 -0
  20. {pathlib_next-0.3.0 → pathlib_next-0.3.5}/src/pathlib_next/uri/schemes/file.py +0 -0
  21. {pathlib_next-0.3.0 → pathlib_next-0.3.5}/src/pathlib_next/uri/schemes/sftp.py +0 -0
  22. {pathlib_next-0.3.0 → pathlib_next-0.3.5}/src/pathlib_next/uri/source.py +0 -0
  23. {pathlib_next-0.3.0 → pathlib_next-0.3.5}/src/pathlib_next/utils/__init__.py +0 -0
  24. {pathlib_next-0.3.0 → pathlib_next-0.3.5}/src/pathlib_next/utils/glob.py +0 -0
  25. {pathlib_next-0.3.0 → pathlib_next-0.3.5}/src/pathlib_next/utils/stat.py +0 -0
  26. {pathlib_next-0.3.0 → pathlib_next-0.3.5}/tests/test_local.py +0 -0
  27. {pathlib_next-0.3.0 → pathlib_next-0.3.5}/tests/test_uri.py +0 -0
@@ -1,4 +1,5 @@
1
1
  __pycache__
2
+ .pytest_cache
2
3
  /_*
3
4
  [a-zA-Z]*__
4
5
  [a-zA-Z]*__.*
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: pathlib_next
3
- Version: 0.3.0
3
+ Version: 0.3.5
4
4
  Summary: Generic Path Protocol based pathlib
5
5
  Project-URL: Homepage, https://github.com/jose-pr/pathlib_next/
6
6
  Project-URL: Issues, https://github.com/jose-pr/pathlib_next/issues
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "pathlib_next"
7
- version = "0.3.0"
7
+ version = "0.3.5"
8
8
  authors = [{ name = "Jose A" }]
9
9
  description = "Generic Path Protocol based pathlib"
10
10
  readme = "README.md"
@@ -3,7 +3,7 @@ import os
3
3
  from pathlib_next import Path, glob
4
4
  from pathlib_next.mempath import MemPath
5
5
  from pathlib_next.uri import Query, Source, Uri, UriPath
6
- from pathlib_next.utils.sync import PathSyncer
6
+ from pathlib_next.utils.sync import PathAndStat, PathSyncer
7
7
 
8
8
  rootless = Uri("sftp://root@sftpexample")
9
9
  rootless.source
@@ -18,8 +18,6 @@ mempath.write_text("test")
18
18
  check = mempath.read_text()
19
19
  mempath.parent.rm(recursive=True)
20
20
 
21
- test = list(os.scandir(local))
22
- print(list(local.iterdir()))
23
21
  query = Query({"test": "://$#!1", "test2&": [1, 2]})
24
22
  q2 = Query(str(query)).to_dict()
25
23
  for name, value in query:
@@ -53,13 +51,13 @@ authkeys = sftp_root / "root/.ssh/authorized_keys"
53
51
  print(authkeys.as_posix())
54
52
 
55
53
 
56
- def checksum(uri: UriPath):
57
- stat = uri.stat()
54
+ def checksum(uri: PathAndStat):
55
+ stat = uri.stat
58
56
  return hash(stat.st_size)
59
57
 
60
58
 
61
59
  syncer = PathSyncer(checksum, remove_missing=False)
62
- syncer.sync((sftp_root / "root/.ssh"), dest, dry_run=True)
60
+ syncer.sync((sftp_root / "root/.ssh"), dest, dry_run=False)
63
61
 
64
62
  rocky_repo = UriPath("http://dl.rockylinux.org/pub")
65
63
 
@@ -6,6 +6,9 @@ import bs4 as _bs4
6
6
  import requests as _req
7
7
  from htmllistparse import parse as _htmlparse
8
8
 
9
+ if _ty.TYPE_CHECKING:
10
+ from urllib3.response import HTTPResponse
11
+
9
12
  from ... import utils as _utils
10
13
  from ...utils.stat import FileStat
11
14
  from .. import UriPath
@@ -119,10 +122,12 @@ class HttpPath(UriPath):
119
122
  raise NotImplementedError(mode)
120
123
  buffer_size = _io.DEFAULT_BUFFER_SIZE if buffering < 0 else buffering
121
124
  req = self.backend.request("GET", self.as_uri(), stream=True)
125
+ resp: "HTTPResponse" = req.raw
126
+ resp.auto_close = False
122
127
  return (
123
- req.raw
128
+ resp
124
129
  if buffer_size == 0
125
- else _io.BufferedReader(req.raw, buffer_size=buffer_size)
130
+ else _io.BufferedReader(resp, buffer_size=buffer_size)
126
131
  )
127
132
 
128
133
  def is_dir(self):
@@ -0,0 +1,258 @@
1
+ import enum as _enum
2
+ import typing as _ty
3
+
4
+ from ..path import Path
5
+ from ..utils.stat import FileStat
6
+
7
+
8
+ class SyncEvent(_enum.Enum):
9
+ Copy = 1
10
+ RemovedMissing = 2
11
+ TypeMismatch = 6
12
+ SyncStart = 5
13
+ Synced = 3
14
+ CreatedDirectory = 4
15
+ CheckTargetChild = _enum.auto()
16
+ CheckTargetChildren = _enum.auto()
17
+ SyncChild = _enum.auto()
18
+ SyncChildren = _enum.auto()
19
+
20
+
21
+ class PathAndStat(object):
22
+ __slots__ = ("_path", "_stat")
23
+
24
+ def __init__(self, path: Path, *, follow_symlink=None) -> None:
25
+ self._path = path
26
+ self.refresh(follow_symlink)
27
+
28
+ def __str__(self) -> str:
29
+ return str(self.path)
30
+
31
+ def __repr__(self) -> str:
32
+ return str((self.path, self._stat))
33
+
34
+ @property
35
+ def path(self):
36
+ return self._path
37
+
38
+ @property
39
+ def stat(self):
40
+ return self._stat
41
+
42
+ def exists(self):
43
+ return self.stat != None
44
+
45
+ def refresh(self, follow_symlink: bool):
46
+ self._stat = FileStat.from_path(self.path, follow_symlink=follow_symlink)
47
+
48
+ def __getattr__(self, name: str):
49
+ if name.startswith("is_"):
50
+ if self.stat:
51
+ return getattr(self.stat, name)
52
+ else:
53
+ return lambda *args, **kwargs: False
54
+
55
+
56
+ if _ty.TYPE_CHECKING:
57
+
58
+ class PathAndStat(PathAndStat, FileStat): ...
59
+
60
+
61
+ class _OnPathSyncerError(_ty.Protocol):
62
+ def __call__(
63
+ self,
64
+ error: Exception,
65
+ source: PathAndStat,
66
+ target: PathAndStat,
67
+ event: SyncEvent,
68
+ ) -> bool: ...
69
+
70
+
71
+ class PathSyncer(object):
72
+ __slots__ = (
73
+ "checksum",
74
+ "_hook",
75
+ "remove_missing",
76
+ "follow_symlinks",
77
+ "ignore_error",
78
+ )
79
+ EVENT_LOG_FORMAT = "[{event}] Source:{source} Target:{target} DryRun:{dry_run}"
80
+
81
+ def __init__(
82
+ self,
83
+ checksum: _ty.Callable[[PathAndStat], int],
84
+ /,
85
+ remove_missing: bool = False,
86
+ follow_symlinks: bool = True,
87
+ hook: _ty.Callable[[PathAndStat, PathAndStat, SyncEvent, bool], None] = None,
88
+ ignore_error: _OnPathSyncerError | bool = False,
89
+ ) -> None:
90
+ self.checksum = checksum
91
+ self.remove_missing = remove_missing
92
+ self._hook = hook
93
+ self.follow_symlinks = follow_symlinks
94
+ if not callable(ignore_error):
95
+ _ignore_error = lambda *args, **kwargs: bool(ignore_error)
96
+ else:
97
+ _ignore_error = ignore_error
98
+ self.ignore_error = _ty.cast(_OnPathSyncerError, _ignore_error)
99
+
100
+ def log(self, msg: str, **kwargs: str):
101
+ print(msg.format_map(kwargs))
102
+
103
+ def hook(
104
+ self,
105
+ source: PathAndStat,
106
+ target: PathAndStat,
107
+ event: SyncEvent,
108
+ dry_run: bool,
109
+ do: _ty.Callable[[], None] = None,
110
+ ):
111
+ if not dry_run and do:
112
+ try:
113
+ do()
114
+ except Exception as e:
115
+ if self.ignore_error(e, source, target, event):
116
+ return e
117
+ raise
118
+ if self._hook:
119
+ self._hook(source, target, event, dry_run)
120
+ self.log(
121
+ self.EVENT_LOG_FORMAT,
122
+ event=event,
123
+ source=source,
124
+ target=target,
125
+ dry_run=dry_run,
126
+ )
127
+
128
+ def sync(
129
+ self,
130
+ source: Path | PathAndStat,
131
+ target: Path | PathAndStat,
132
+ /,
133
+ dry_run: bool = False,
134
+ ignore_error: (
135
+ bool | _ty.Callable[[Exception, PathAndStat, PathAndStat], None]
136
+ ) = False,
137
+ ):
138
+ checksum = self.checksum
139
+
140
+ def start():
141
+ nonlocal source, target
142
+ source = (
143
+ PathAndStat(source, follow_symlink=self.follow_symlinks)
144
+ if not isinstance(source, PathAndStat)
145
+ else source
146
+ )
147
+ target = (
148
+ PathAndStat(target, follow_symlink=self.follow_symlinks)
149
+ if not isinstance(target, PathAndStat)
150
+ else target
151
+ )
152
+
153
+ if self.hook(source, target, SyncEvent.SyncStart, False, start):
154
+ return
155
+
156
+ if not source.exists():
157
+ if self.remove_missing:
158
+ if self.hook(
159
+ source,
160
+ target,
161
+ SyncEvent.RemovedMissing,
162
+ dry_run,
163
+ lambda: target.path.rm(recursive=True, missing_ok=True),
164
+ ):
165
+ return
166
+ elif source.is_symlink():
167
+ error = NotImplementedError("symlink sync not implemented yet")
168
+ if not ignore_error(error, source, target, None):
169
+ raise error
170
+ return
171
+ elif source.is_file():
172
+ synced = False
173
+ if target.is_file():
174
+ if checksum(target) == checksum(source):
175
+ synced = True
176
+ if not synced:
177
+
178
+ def copy():
179
+ if target.is_file() or target.is_symlink():
180
+ target.path.unlink()
181
+ else:
182
+ if target.exists():
183
+ target.path.rm(recursive=target.is_dir())
184
+ source.path.copy(target.path)
185
+
186
+ if self.hook(source, target, SyncEvent.Copy, dry_run, copy):
187
+ return
188
+ else:
189
+ if target.is_file():
190
+ if self.hook(
191
+ source,
192
+ target,
193
+ SyncEvent.TypeMismatch,
194
+ dry_run,
195
+ lambda: target.path.unlink(),
196
+ ):
197
+ return
198
+
199
+ target._stat = None
200
+
201
+ if not target.exists():
202
+ if self.hook(
203
+ source,
204
+ target,
205
+ SyncEvent.CreatedDirectory,
206
+ dry_run,
207
+ lambda: target.path.mkdir(),
208
+ ):
209
+ return
210
+
211
+ if self.remove_missing:
212
+
213
+ def checkchildren():
214
+ for child in target.path.iterdir():
215
+
216
+ def checkchild():
217
+ if not (source.path / child.name).exists():
218
+ self.hook(
219
+ source,
220
+ target,
221
+ SyncEvent.RemovedMissing,
222
+ dry_run,
223
+ lambda: child.rm(recursive=True),
224
+ )
225
+
226
+ self.hook(
227
+ source,
228
+ target,
229
+ SyncEvent.CheckTargetChild,
230
+ False,
231
+ checkchild,
232
+ )
233
+
234
+ self.hook(
235
+ source,
236
+ target,
237
+ SyncEvent.CheckTargetChildren,
238
+ False,
239
+ checkchildren,
240
+ )
241
+
242
+ def sync_children():
243
+ for child in source.path.iterdir():
244
+ self.hook(
245
+ source,
246
+ target,
247
+ SyncEvent.SyncChild,
248
+ False,
249
+ lambda: self.sync(
250
+ child,
251
+ target.path / (child.name or child.parent.name),
252
+ dry_run,
253
+ ),
254
+ )
255
+
256
+ self.hook(source, target, SyncEvent.SyncChildren, False, sync_children)
257
+
258
+ self.hook(source, target, SyncEvent.Synced, dry_run)
@@ -1,104 +0,0 @@
1
- import enum as _enum
2
- import typing as _ty
3
-
4
- from ..path import Path
5
- from ..utils.stat import FileStat
6
-
7
-
8
- class SyncEvent(_enum.Enum):
9
- Copy = 1
10
- RemovedMissing = 2
11
- SyncStart = 5
12
- Synced = 3
13
- CreatedDirectory = 4
14
-
15
-
16
- class PathSyncer(object):
17
- __slots__ = ("checksum", "_hook", "remove_missing", "follow_symlinks")
18
- EVENT_LOG_FORMAT = "[{event}] Source:{source} Target:{target} DryRun:{dry_run}"
19
-
20
- def __init__(
21
- self,
22
- checksum: _ty.Callable[[Path], int],
23
- /,
24
- remove_missing: bool = False,
25
- follow_symlinks: bool = True,
26
- hook: _ty.Callable[[Path, Path, SyncEvent, bool], None] = None,
27
- ) -> None:
28
- self.checksum = checksum
29
- self.remove_missing = remove_missing
30
- self._hook = hook
31
- self.follow_symlinks = follow_symlinks
32
-
33
- def log(self, msg: str, **kwargs: str):
34
- print(msg.format_map(kwargs))
35
-
36
- def hook(
37
- self,
38
- source: Path,
39
- target: Path,
40
- event: SyncEvent,
41
- dry_run: bool,
42
- ):
43
- if self._hook:
44
- self._hook(source, target, event, dry_run)
45
- self.log(
46
- self.EVENT_LOG_FORMAT,
47
- event=event,
48
- source=source,
49
- target=target,
50
- dry_run=dry_run,
51
- )
52
-
53
- def sync(self, source: Path, target: Path, /, dry_run: bool = False):
54
- checksum = self.checksum
55
- self.hook(source, target, SyncEvent.SyncStart, dry_run)
56
-
57
- src_stat = FileStat.from_path(source, follow_symlink=self.follow_symlinks)
58
- tgt_stat = FileStat.from_path(target, follow_symlink=self.follow_symlinks)
59
-
60
- if src_stat is None:
61
- if self.remove_missing:
62
- if not dry_run:
63
- target.rm(recursive=True, missing_ok=True)
64
- self.hook(source, target, SyncEvent.RemovedMissing, dry_run)
65
- elif src_stat.is_symlink():
66
- raise NotImplementedError("symlink sync not implemented yet")
67
- elif src_stat.is_file():
68
- synced = False
69
- if tgt_stat and tgt_stat.is_file():
70
- if checksum(target) == checksum(source):
71
- synced = True
72
- if not synced:
73
- if not dry_run:
74
- if tgt_stat:
75
- if tgt_stat.is_file() or tgt_stat.is_symlink():
76
- target.unlink()
77
- else:
78
- if target:
79
- target.rm(recursive=tgt_stat.is_dir())
80
- source.copy(target)
81
- self.hook(source, target, SyncEvent.Copy, dry_run)
82
- else:
83
- t_exists = tgt_stat != None
84
- if tgt_stat and tgt_stat.is_file():
85
- if not dry_run:
86
- target.unlink()
87
- t_exists = False
88
-
89
- if not t_exists:
90
- if not dry_run:
91
- target.mkdir()
92
- self.hook(source, target, SyncEvent.CreatedDirectory, dry_run)
93
-
94
- if self.remove_missing:
95
- for child in target.iterdir():
96
- if not (source / child.name).exists():
97
- if not dry_run:
98
- child.rm(recursive=True)
99
- self.hook(source, target, SyncEvent.RemovedMissing, dry_run)
100
-
101
- for child in source.iterdir():
102
- self.sync(child, target / child.name, dry_run)
103
-
104
- self.hook(source, target, SyncEvent.Synced, dry_run)
File without changes
File without changes