pathlib-next 0.1.0__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.
- pathlib_next/__init__.py +7 -0
- pathlib_next/fspath.py +103 -0
- pathlib_next/path.py +618 -0
- pathlib_next/uri/__init__.py +471 -0
- pathlib_next/uri/query.py +58 -0
- pathlib_next/uri/schemes/__init__.py +10 -0
- pathlib_next/uri/schemes/file.py +62 -0
- pathlib_next/uri/schemes/http.py +136 -0
- pathlib_next/uri/schemes/sftp.py +101 -0
- pathlib_next/uri/source.py +73 -0
- pathlib_next/utils/__init__.py +93 -0
- pathlib_next/utils/glob.py +145 -0
- pathlib_next/utils/stat.py +77 -0
- pathlib_next/utils/sync.py +91 -0
- pathlib_next-0.1.0.dist-info/METADATA +25 -0
- pathlib_next-0.1.0.dist-info/RECORD +18 -0
- pathlib_next-0.1.0.dist-info/WHEEL +4 -0
- pathlib_next-0.1.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,471 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import typing as _ty
|
|
3
|
+
import uritools
|
|
4
|
+
import posixpath as _posix
|
|
5
|
+
import pathlib as _pathlib
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
if _ty.TYPE_CHECKING:
|
|
9
|
+
from typing import Self
|
|
10
|
+
|
|
11
|
+
from .. import utils as _utils
|
|
12
|
+
from .query import Query
|
|
13
|
+
from .source import Source
|
|
14
|
+
|
|
15
|
+
from ..path import Path, Pathname
|
|
16
|
+
|
|
17
|
+
UriLike: _ty.TypeAlias = "str | Uri | os.PathLike"
|
|
18
|
+
|
|
19
|
+
_NOSOURCE = Source(None, None, None, None)
|
|
20
|
+
|
|
21
|
+
_U = _ty.TypeVar("_U", bound="Uri")
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _uriencode(text: str, safe=""):
|
|
25
|
+
return uritools.uriencode(text, safe=safe).decode()
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class Uri(Pathname):
|
|
29
|
+
|
|
30
|
+
__slots__ = (
|
|
31
|
+
"_raw_uris",
|
|
32
|
+
"_source",
|
|
33
|
+
"_path",
|
|
34
|
+
"_query",
|
|
35
|
+
"_fragment",
|
|
36
|
+
"_uri",
|
|
37
|
+
"_initiated",
|
|
38
|
+
"_normalized_path",
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
def __new__(cls, *uris, **options):
|
|
42
|
+
inst = object.__new__(cls)
|
|
43
|
+
for cls in cls.__mro__:
|
|
44
|
+
for slot in getattr(cls, "__slots__", ()):
|
|
45
|
+
if not hasattr(inst, slot):
|
|
46
|
+
setattr(inst, slot, None)
|
|
47
|
+
return inst
|
|
48
|
+
|
|
49
|
+
def __init__(self, *uris: UriLike, **options):
|
|
50
|
+
if self._raw_uris or self._initiated:
|
|
51
|
+
return
|
|
52
|
+
_uris: list[str | Uri] = []
|
|
53
|
+
for uri in uris:
|
|
54
|
+
if not uri:
|
|
55
|
+
uri = ""
|
|
56
|
+
if isinstance(uri, Uri):
|
|
57
|
+
_uris.append(uri)
|
|
58
|
+
elif isinstance(uri, (_pathlib.Path, Path)):
|
|
59
|
+
try:
|
|
60
|
+
uri = uri.as_uri()
|
|
61
|
+
except:
|
|
62
|
+
uri = f"file:{_uriencode(uri.as_posix(), safe='/')}"
|
|
63
|
+
_uris.append(uri)
|
|
64
|
+
elif isinstance(uri, (_pathlib.PurePath, Pathname)):
|
|
65
|
+
_uris.append(f"{_uriencode(uri.as_posix(), safe='/')}")
|
|
66
|
+
elif hasattr(uri, "as_uri"):
|
|
67
|
+
path = uri.as_uri
|
|
68
|
+
if callable(path):
|
|
69
|
+
path = path()
|
|
70
|
+
_uris.append(path)
|
|
71
|
+
elif isinstance(uri, str):
|
|
72
|
+
_uris.append(uri)
|
|
73
|
+
elif isinstance(uri, bytes):
|
|
74
|
+
_uris.append(uri.decode())
|
|
75
|
+
else:
|
|
76
|
+
path = None
|
|
77
|
+
try:
|
|
78
|
+
path = os.fspath(uri)
|
|
79
|
+
except (TypeError, NotImplementedError):
|
|
80
|
+
pass
|
|
81
|
+
if not isinstance(path, str):
|
|
82
|
+
raise TypeError(
|
|
83
|
+
"argument should be a str or an os.PathLike "
|
|
84
|
+
"object where __fspath__ returns a str, "
|
|
85
|
+
f"not {type(path).__name__!r}"
|
|
86
|
+
)
|
|
87
|
+
_uris.append(f"{_uriencode(uri.as_posix(), safe='/')}")
|
|
88
|
+
self._raw_uris = _uris
|
|
89
|
+
|
|
90
|
+
@classmethod
|
|
91
|
+
def _parse_uri(cls, uri: str) -> tuple[Source, str, Query, str]:
|
|
92
|
+
parsed = uritools.urisplit(uri)
|
|
93
|
+
return (
|
|
94
|
+
Source(
|
|
95
|
+
parsed.getscheme(),
|
|
96
|
+
parsed.getuserinfo(),
|
|
97
|
+
parsed.gethost() or "",
|
|
98
|
+
parsed.getport(),
|
|
99
|
+
),
|
|
100
|
+
parsed.getpath(),
|
|
101
|
+
Query(parsed.getquery() or ""),
|
|
102
|
+
parsed.getfragment() or "",
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
@property
|
|
106
|
+
def parts(self):
|
|
107
|
+
return (self.source, self.path, self.query, self.fragment)
|
|
108
|
+
|
|
109
|
+
def _load_parts(self):
|
|
110
|
+
uris = self._raw_uris
|
|
111
|
+
source = _NOSOURCE
|
|
112
|
+
query = fragment = None
|
|
113
|
+
_path = ""
|
|
114
|
+
|
|
115
|
+
if not uris:
|
|
116
|
+
pass
|
|
117
|
+
elif len(uris) == 1 and isinstance(uris[0], Uri):
|
|
118
|
+
source, _path, query, fragment = uris[0].parts
|
|
119
|
+
else:
|
|
120
|
+
paths: list[str] = []
|
|
121
|
+
for _uri in uris:
|
|
122
|
+
src, path, query, fragment = (
|
|
123
|
+
_uri.parts if isinstance(_uri, Uri) else self._parse_uri(_uri)
|
|
124
|
+
)
|
|
125
|
+
if bool(src):
|
|
126
|
+
source = src
|
|
127
|
+
paths.append(path)
|
|
128
|
+
|
|
129
|
+
for path in reversed(paths):
|
|
130
|
+
if not path:
|
|
131
|
+
continue
|
|
132
|
+
if path.endswith("/"):
|
|
133
|
+
_path = f"{path}{_path}"
|
|
134
|
+
elif _path:
|
|
135
|
+
_path = f"{path}/{_path}"
|
|
136
|
+
else:
|
|
137
|
+
_path = path
|
|
138
|
+
if _path.startswith("/"):
|
|
139
|
+
break
|
|
140
|
+
|
|
141
|
+
self._init(source, _path, query, fragment)
|
|
142
|
+
|
|
143
|
+
def _init(self, source: Source, path: str, query: str, fragment: str, **kwargs):
|
|
144
|
+
if self._initiated:
|
|
145
|
+
pass
|
|
146
|
+
# raise Exception(f"Uri._init should only be called once")
|
|
147
|
+
self._initiated = True
|
|
148
|
+
self._source = source
|
|
149
|
+
self._path = path
|
|
150
|
+
self._query = query
|
|
151
|
+
self._fragment = fragment
|
|
152
|
+
|
|
153
|
+
def _from_parsed_parts(
|
|
154
|
+
self, source: Source, path: str, query: str, fragment: str, /, **kwargs
|
|
155
|
+
):
|
|
156
|
+
cls = type(self)
|
|
157
|
+
uri = cls.__new__(cls)
|
|
158
|
+
uri._init(source, path, query, fragment, **kwargs)
|
|
159
|
+
return uri
|
|
160
|
+
|
|
161
|
+
@classmethod
|
|
162
|
+
def _format_parsed_parts(
|
|
163
|
+
cls,
|
|
164
|
+
source: Source,
|
|
165
|
+
path: str,
|
|
166
|
+
query: str,
|
|
167
|
+
fragment: str,
|
|
168
|
+
/,
|
|
169
|
+
sanitize=True,
|
|
170
|
+
) -> str:
|
|
171
|
+
parts = {
|
|
172
|
+
"path": path,
|
|
173
|
+
}
|
|
174
|
+
if query:
|
|
175
|
+
parts["query"] = query
|
|
176
|
+
if fragment:
|
|
177
|
+
parts["fragment"] = fragment
|
|
178
|
+
if source:
|
|
179
|
+
source_ = source._asdict()
|
|
180
|
+
if sanitize:
|
|
181
|
+
source_["userinfo"] = (source_["userinfo"] or "").split(
|
|
182
|
+
":", maxsplit=1
|
|
183
|
+
)[0]
|
|
184
|
+
parts.update(source_)
|
|
185
|
+
|
|
186
|
+
return uritools.uricompose(**{k: v for k, v in parts.items() if v})
|
|
187
|
+
|
|
188
|
+
def __str__(self):
|
|
189
|
+
"""Return the string representation of the path, suitable for
|
|
190
|
+
passing to system calls."""
|
|
191
|
+
return self.as_uri(sanitize=True)
|
|
192
|
+
|
|
193
|
+
def __fspath__(self):
|
|
194
|
+
if (self.source.scheme or "file") == "file":
|
|
195
|
+
if not self.source.host or self.is_local():
|
|
196
|
+
return self.path
|
|
197
|
+
elif os.name == "nt":
|
|
198
|
+
return f"//{self.source.host}/{self.path.removeprefix('/')}"
|
|
199
|
+
else:
|
|
200
|
+
raise NotImplementedError(f"OS Support for not local fspath")
|
|
201
|
+
|
|
202
|
+
raise NotImplementedError(f"fspath for {self.source.scheme}")
|
|
203
|
+
|
|
204
|
+
def __repr__(self):
|
|
205
|
+
return "{}({!r})".format(type(self).__name__, str(self))
|
|
206
|
+
|
|
207
|
+
def as_uri(self, /, sanitize=False):
|
|
208
|
+
if self._uri is None or sanitize:
|
|
209
|
+
uri = self._format_parsed_parts(
|
|
210
|
+
self.source, self.path, self.query, self.fragment, sanitize=sanitize
|
|
211
|
+
)
|
|
212
|
+
if not sanitize:
|
|
213
|
+
self._uri = uri
|
|
214
|
+
return uri
|
|
215
|
+
else:
|
|
216
|
+
return self._uri
|
|
217
|
+
|
|
218
|
+
@property
|
|
219
|
+
def source(self) -> Source:
|
|
220
|
+
if not self._initiated:
|
|
221
|
+
self._load_parts()
|
|
222
|
+
return self._source
|
|
223
|
+
|
|
224
|
+
@property
|
|
225
|
+
def path(self) -> str:
|
|
226
|
+
if not self._initiated:
|
|
227
|
+
self._load_parts()
|
|
228
|
+
return self._path
|
|
229
|
+
|
|
230
|
+
@property
|
|
231
|
+
def query(self) -> str:
|
|
232
|
+
if not self._initiated:
|
|
233
|
+
self._load_parts()
|
|
234
|
+
return self._query
|
|
235
|
+
|
|
236
|
+
@property
|
|
237
|
+
def fragment(self) -> str:
|
|
238
|
+
if not self._initiated:
|
|
239
|
+
self._load_parts()
|
|
240
|
+
return self._fragment
|
|
241
|
+
|
|
242
|
+
def _make_child_relpath(self, name: str, **kwargs) -> _ty.Self:
|
|
243
|
+
cls = type(self)
|
|
244
|
+
inst = cls.__new__(cls)
|
|
245
|
+
inst._init(
|
|
246
|
+
self.source, f"{self.path}/{name}" if self.path else name, "", "", **kwargs
|
|
247
|
+
)
|
|
248
|
+
return inst
|
|
249
|
+
|
|
250
|
+
def with_source(self, source: Source):
|
|
251
|
+
return self._from_parsed_parts(source, self.path, self.query, self.fragment)
|
|
252
|
+
|
|
253
|
+
def with_segments(self, *segments: str):
|
|
254
|
+
if not segments:
|
|
255
|
+
return self.with_path("")
|
|
256
|
+
return self.with_path("/".join(segments))
|
|
257
|
+
|
|
258
|
+
def with_path(self, path: str | Pathname):
|
|
259
|
+
return self._from_parsed_parts(
|
|
260
|
+
self.source,
|
|
261
|
+
path.as_posix() if isinstance(path, Pathname) else path,
|
|
262
|
+
self.query,
|
|
263
|
+
self.fragment,
|
|
264
|
+
)
|
|
265
|
+
|
|
266
|
+
def with_query(self, query: str):
|
|
267
|
+
if not isinstance(query, Query):
|
|
268
|
+
query = Query(query)
|
|
269
|
+
return self._from_parsed_parts(self.source, self.path, query, self.fragment)
|
|
270
|
+
|
|
271
|
+
def with_fragment(self, fragment: str):
|
|
272
|
+
return self._from_parsed_parts(self.source, self.path, self.query, fragment)
|
|
273
|
+
|
|
274
|
+
@property
|
|
275
|
+
def segments(self):
|
|
276
|
+
if not self.path:
|
|
277
|
+
return ()
|
|
278
|
+
return self.path.split("/")
|
|
279
|
+
|
|
280
|
+
@property
|
|
281
|
+
def parent(self):
|
|
282
|
+
"""The logical parent of the path."""
|
|
283
|
+
segments = self.segments
|
|
284
|
+
if not segments or len(segments) == 2 and segments[1] == "":
|
|
285
|
+
return self
|
|
286
|
+
return self.with_path("/".join(segments[:-1]))
|
|
287
|
+
|
|
288
|
+
@property
|
|
289
|
+
def normalized_path(self):
|
|
290
|
+
if self._normalized_path is None:
|
|
291
|
+
self._normalized_path = _posix.normpath(self.path)
|
|
292
|
+
return self._normalized_path
|
|
293
|
+
|
|
294
|
+
def is_absolute(self):
|
|
295
|
+
"""True if the path is absolute."""
|
|
296
|
+
return bool(self.source) and self.path.startswith("/")
|
|
297
|
+
|
|
298
|
+
def is_relative_to(self, other: UriLike):
|
|
299
|
+
"""Return True if the path is relative to another path or False."""
|
|
300
|
+
other = other if isinstance(other, Uri) else Uri(self, _ROOT, other)
|
|
301
|
+
if not (
|
|
302
|
+
(other.source == self.source)
|
|
303
|
+
or not (bool(self.source) and bool(other.source))
|
|
304
|
+
):
|
|
305
|
+
return False
|
|
306
|
+
_other = other.normalized_path
|
|
307
|
+
_self = self.normalized_path
|
|
308
|
+
return _self.startswith(_other)
|
|
309
|
+
|
|
310
|
+
def relative_to(self, other: UriLike, *, walk_up=False):
|
|
311
|
+
other = other if isinstance(other, Uri) else Uri(other)
|
|
312
|
+
if not self.is_relative_to(other):
|
|
313
|
+
raise ValueError(f"{str(self)!r} is not in the subpath of {str(other)!r}")
|
|
314
|
+
|
|
315
|
+
for step, path in enumerate([other] + list(other.parents)):
|
|
316
|
+
if self.is_relative_to(path):
|
|
317
|
+
break
|
|
318
|
+
elif not walk_up:
|
|
319
|
+
raise ValueError(
|
|
320
|
+
f"{str(self)!r} is not in the subpath of {str(other)!r}"
|
|
321
|
+
)
|
|
322
|
+
elif path.name == "..":
|
|
323
|
+
raise ValueError(f"'..' segment in {str(other)!r} cannot be walked")
|
|
324
|
+
else:
|
|
325
|
+
raise ValueError(f"{str(self)!r} and {str(other)!r} have different anchors")
|
|
326
|
+
parts = [".."] * step + self.segments[len(path.segments) :]
|
|
327
|
+
return self._from_parsed_parts(
|
|
328
|
+
_NOSOURCE, "/".join(parts), self.query, self.fragment
|
|
329
|
+
)
|
|
330
|
+
|
|
331
|
+
def is_local(self):
|
|
332
|
+
return self.source.is_local()
|
|
333
|
+
|
|
334
|
+
def __eq__(self, other: Pathname | str):
|
|
335
|
+
uri = other.as_uri() if isinstance(other, Pathname) else other
|
|
336
|
+
return self.as_uri() == uri
|
|
337
|
+
|
|
338
|
+
def as_posix(self):
|
|
339
|
+
source = self.source
|
|
340
|
+
host = None
|
|
341
|
+
posix = self.path
|
|
342
|
+
if source.host:
|
|
343
|
+
host = source.host
|
|
344
|
+
user, password = source.parsed_userinfo()
|
|
345
|
+
if user:
|
|
346
|
+
posix = f"{user}@{host}:{posix}"
|
|
347
|
+
else:
|
|
348
|
+
posix = f"{host}:{posix}"
|
|
349
|
+
return posix
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
class UriPath(Uri, Path):
|
|
353
|
+
__slots__ = ("_backend",)
|
|
354
|
+
__SCHEMES: _ty.Sequence[str] = ()
|
|
355
|
+
__SCHEMESMAP: _ty.Mapping[str, type["Self"]] = None
|
|
356
|
+
|
|
357
|
+
@classmethod
|
|
358
|
+
def _schemesmap(cls, reload=False) -> _ty.Mapping[str, type["Self"]]:
|
|
359
|
+
_propname = f"_{cls.__name__}__SCHEMESMAP"
|
|
360
|
+
if not reload:
|
|
361
|
+
try:
|
|
362
|
+
schemesmap = getattr(cls, _propname)
|
|
363
|
+
if schemesmap is not None:
|
|
364
|
+
return schemesmap
|
|
365
|
+
except AttributeError:
|
|
366
|
+
pass
|
|
367
|
+
schemesmap = cls._get_schemesmap()
|
|
368
|
+
setattr(cls, _propname, schemesmap)
|
|
369
|
+
return schemesmap
|
|
370
|
+
|
|
371
|
+
@classmethod
|
|
372
|
+
def _schemes(cls) -> _ty.Sequence[str]:
|
|
373
|
+
try:
|
|
374
|
+
return getattr(cls, f"_{cls.__name__}__SCHEMES")
|
|
375
|
+
except AttributeError as _e:
|
|
376
|
+
return ()
|
|
377
|
+
|
|
378
|
+
@classmethod
|
|
379
|
+
def _get_schemesmap(cls):
|
|
380
|
+
schemesmap = {scheme: cls for scheme in cls._schemes()}
|
|
381
|
+
for scls in cls.__subclasses__():
|
|
382
|
+
schemesmap.update(scls._get_schemesmap())
|
|
383
|
+
return schemesmap
|
|
384
|
+
|
|
385
|
+
def __new__(
|
|
386
|
+
cls,
|
|
387
|
+
*args,
|
|
388
|
+
schemesmap: dict[str, type["Self"]] = None,
|
|
389
|
+
findclass=False,
|
|
390
|
+
**kwargs,
|
|
391
|
+
) -> "UriPath":
|
|
392
|
+
if cls is UriPath or findclass:
|
|
393
|
+
uri = Uri(*args, **kwargs)
|
|
394
|
+
cls: type[UriPath] = uri.source.get_scheme_cls(schemesmap)
|
|
395
|
+
if cls is UriPath:
|
|
396
|
+
inst = Uri.__new__(cls, *args, **kwargs)
|
|
397
|
+
else:
|
|
398
|
+
inst = cls.__new__(cls, *args, **kwargs)
|
|
399
|
+
inst._init(uri.source, uri.path, uri.query, uri.fragment, **kwargs)
|
|
400
|
+
else:
|
|
401
|
+
inst = Uri.__new__(cls, *args, **kwargs)
|
|
402
|
+
return inst
|
|
403
|
+
|
|
404
|
+
def _initbackend(self):
|
|
405
|
+
return None
|
|
406
|
+
|
|
407
|
+
def _init(
|
|
408
|
+
self,
|
|
409
|
+
source: Source,
|
|
410
|
+
path: str,
|
|
411
|
+
query: str,
|
|
412
|
+
fragment: str,
|
|
413
|
+
/,
|
|
414
|
+
backend=None,
|
|
415
|
+
**kwargs,
|
|
416
|
+
):
|
|
417
|
+
if backend is not None:
|
|
418
|
+
self._backend = backend
|
|
419
|
+
super()._init(source, path, query, fragment, **kwargs)
|
|
420
|
+
|
|
421
|
+
@property
|
|
422
|
+
def backend(self):
|
|
423
|
+
if self._backend is None:
|
|
424
|
+
self._backend = self._initbackend()
|
|
425
|
+
return self._backend
|
|
426
|
+
|
|
427
|
+
def with_backend(self, backend):
|
|
428
|
+
return self._from_parsed_parts(*self.parts, backend=backend)
|
|
429
|
+
|
|
430
|
+
def _load_parts(self):
|
|
431
|
+
super()._load_parts()
|
|
432
|
+
if self.source and self.source.scheme and self._backend is None:
|
|
433
|
+
for uri in reversed(self._raw_uris):
|
|
434
|
+
if (
|
|
435
|
+
isinstance(uri, UriPath)
|
|
436
|
+
and uri.source.scheme == self.source.scheme
|
|
437
|
+
and uri._backend
|
|
438
|
+
):
|
|
439
|
+
self._backend = uri._backend
|
|
440
|
+
break
|
|
441
|
+
|
|
442
|
+
def __truediv__(self, key: str | Uri | os.PathLike):
|
|
443
|
+
try:
|
|
444
|
+
return type(self)(self, key, findclass=True)
|
|
445
|
+
except (TypeError, NotImplementedError):
|
|
446
|
+
return NotImplemented
|
|
447
|
+
|
|
448
|
+
def with_source(self, source: Source):
|
|
449
|
+
cls = type(self)
|
|
450
|
+
if not source:
|
|
451
|
+
inst = Uri.__new__(UriPath)
|
|
452
|
+
elif source.scheme not in cls._schemes():
|
|
453
|
+
inst = UriPath.__new__(UriPath, source.scheme + ":")
|
|
454
|
+
else:
|
|
455
|
+
inst = cls.__new__(cls, backend=self._backend)
|
|
456
|
+
inst._init(source, self.path, self.query, self.fragment)
|
|
457
|
+
return inst
|
|
458
|
+
|
|
459
|
+
@_utils.notimplemented
|
|
460
|
+
def _listdir(self) -> "_ty.Iterator[str]": ...
|
|
461
|
+
|
|
462
|
+
def _make_child_relpath(self, name: str, **kwargs) -> _ty.Self:
|
|
463
|
+
inst = super()._make_child_relpath(name, backend=self.backend, **kwargs)
|
|
464
|
+
return inst
|
|
465
|
+
|
|
466
|
+
def iterdir(self) -> "_ty.Iterator[Self]":
|
|
467
|
+
for path in self._listdir():
|
|
468
|
+
yield self._make_child_relpath(path)
|
|
469
|
+
|
|
470
|
+
|
|
471
|
+
_ROOT = Uri("/")
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import typing as _ty
|
|
2
|
+
import uritools as _uritools
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class Query(str):
|
|
6
|
+
__slots__ = ("_encoding", "_separator")
|
|
7
|
+
SEPARATOR = "&"
|
|
8
|
+
ENCODING = "utf-8"
|
|
9
|
+
|
|
10
|
+
def __new__(
|
|
11
|
+
cls,
|
|
12
|
+
query: (
|
|
13
|
+
str
|
|
14
|
+
| _ty.Sequence[tuple[str, str | None]]
|
|
15
|
+
| _ty.Mapping[str, str | None | _ty.Sequence[str | None]]
|
|
16
|
+
),
|
|
17
|
+
*,
|
|
18
|
+
encoding=ENCODING,
|
|
19
|
+
separator=SEPARATOR
|
|
20
|
+
):
|
|
21
|
+
if isinstance(query, Query):
|
|
22
|
+
_encoding = query._encoding
|
|
23
|
+
_separator = query._separator
|
|
24
|
+
else:
|
|
25
|
+
_encoding = None
|
|
26
|
+
_separator = None
|
|
27
|
+
|
|
28
|
+
encoding = encoding or _encoding or cls.ENCODING
|
|
29
|
+
separator = separator or _separator or cls.SEPARATOR
|
|
30
|
+
if isinstance(query, str):
|
|
31
|
+
pass
|
|
32
|
+
else:
|
|
33
|
+
if isinstance(query, _ty.Mapping):
|
|
34
|
+
query: str = _uritools._querydict(query, separator, encoding).decode()
|
|
35
|
+
else:
|
|
36
|
+
query = _uritools._querylist(query, separator, encoding).decode()
|
|
37
|
+
|
|
38
|
+
obj = str.__new__(cls, query)
|
|
39
|
+
obj._encoding = encoding
|
|
40
|
+
obj._separator = separator
|
|
41
|
+
return obj
|
|
42
|
+
|
|
43
|
+
def decode(query) -> list[tuple[str, str | None]]:
|
|
44
|
+
return _uritools.SplitResultString("", "", "", str(query), "").getquerylist(
|
|
45
|
+
query._separator, query._encoding
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
def __iter__(self):
|
|
49
|
+
return iter(self.decode())
|
|
50
|
+
|
|
51
|
+
def to_dict(query, *, single=False):
|
|
52
|
+
query_: dict[str, list[str | None]] = {}
|
|
53
|
+
for k, v in query.decode():
|
|
54
|
+
if single:
|
|
55
|
+
query_[k] = v
|
|
56
|
+
else:
|
|
57
|
+
query_.setdefault(k, []).append(v)
|
|
58
|
+
return query_
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
from ...fspath import LocalPath as _Local
|
|
2
|
+
from ...path import FsPathLike
|
|
3
|
+
import os as _os
|
|
4
|
+
from .. import UriPath, Source
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class FileUri(UriPath):
|
|
8
|
+
__SCHEMES = ("file",)
|
|
9
|
+
__slots__ = ("_filepath",)
|
|
10
|
+
|
|
11
|
+
@property
|
|
12
|
+
def filepath(self):
|
|
13
|
+
if self._filepath is None:
|
|
14
|
+
self._filepath = _Local(self.__fspath__())
|
|
15
|
+
return self._filepath
|
|
16
|
+
|
|
17
|
+
def _init(
|
|
18
|
+
self,
|
|
19
|
+
source: Source,
|
|
20
|
+
path: str,
|
|
21
|
+
query: str,
|
|
22
|
+
fragment: str,
|
|
23
|
+
/,
|
|
24
|
+
**kwargs,
|
|
25
|
+
):
|
|
26
|
+
if _os.name == "nt" and path and path[0] == "/":
|
|
27
|
+
root, *_ = path[1:].split("/", maxsplit=1)
|
|
28
|
+
if root and root[-1] == ":":
|
|
29
|
+
path = path.removeprefix("/")
|
|
30
|
+
super()._init(source, path, query, fragment, **kwargs)
|
|
31
|
+
|
|
32
|
+
def _listdir(self):
|
|
33
|
+
yield from _os.listdir(self.filepath)
|
|
34
|
+
|
|
35
|
+
def stat(self, *, follow_symlinks=True):
|
|
36
|
+
return self.filepath.stat(follow_symlinks=follow_symlinks)
|
|
37
|
+
|
|
38
|
+
def open(self, mode="r", buffering=-1, encoding=None, errors=None, newline=None):
|
|
39
|
+
return self.filepath.open(mode, buffering, encoding, errors, newline)
|
|
40
|
+
|
|
41
|
+
def mkdir(self, mode=511, parents=False, exist_ok=False):
|
|
42
|
+
return self.filepath.mkdir(mode, parents, exist_ok)
|
|
43
|
+
|
|
44
|
+
def chmod(self, mode, *, follow_symlinks=True):
|
|
45
|
+
return self.filepath.chmod(mode, follow_symlinks=follow_symlinks)
|
|
46
|
+
|
|
47
|
+
def unlink(self, missing_ok=False):
|
|
48
|
+
return self.filepath.unlink(missing_ok)
|
|
49
|
+
|
|
50
|
+
def rmdir(self):
|
|
51
|
+
return self.filepath.rmdir()
|
|
52
|
+
|
|
53
|
+
def rename(self, target: FsPathLike | str):
|
|
54
|
+
try:
|
|
55
|
+
_target = _os.fspath(target)
|
|
56
|
+
except (TypeError, NotImplementedError):
|
|
57
|
+
_target = NotImplemented
|
|
58
|
+
|
|
59
|
+
if _target is NotImplemented:
|
|
60
|
+
raise NotImplementedError("rename", target)
|
|
61
|
+
|
|
62
|
+
return self.filepath.rename(_target)
|