pathapi-uri 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.
Files changed (48) hide show
  1. pathapi_uri-0.1.0.dist-info/METADATA +259 -0
  2. pathapi_uri-0.1.0.dist-info/RECORD +48 -0
  3. pathapi_uri-0.1.0.dist-info/WHEEL +4 -0
  4. pathapi_uri-0.1.0.dist-info/entry_points.txt +17 -0
  5. pathapi_uri-0.1.0.dist-info/licenses/LICENSE +21 -0
  6. uripath/AGENTS.md +1094 -0
  7. uripath/README.md +213 -0
  8. uripath/__init__.py +27 -0
  9. uripath/__main__.py +7 -0
  10. uripath/_pathtext.py +32 -0
  11. uripath/_rfc3986.py +67 -0
  12. uripath/_transfer.py +108 -0
  13. uripath/_uri.py +1007 -0
  14. uripath/_version.py +11 -0
  15. uripath/cli/__init__.py +54 -0
  16. uripath/cli/_common.py +48 -0
  17. uripath/cli/_root.py +70 -0
  18. uripath/cli/cp.py +61 -0
  19. uripath/cli/read.py +22 -0
  20. uripath/cli/rm.py +38 -0
  21. uripath/cli/sync.py +78 -0
  22. uripath/cli/write.py +49 -0
  23. uripath/exceptions.py +38 -0
  24. uripath/py.typed +1 -0
  25. uripath/query.py +240 -0
  26. uripath/schemes/__init__.py +120 -0
  27. uripath/schemes/_archive.py +338 -0
  28. uripath/schemes/_data.py +99 -0
  29. uripath/schemes/_dav.py +459 -0
  30. uripath/schemes/_delegate.py +127 -0
  31. uripath/schemes/_elsewhere.py +102 -0
  32. uripath/schemes/_file.py +186 -0
  33. uripath/schemes/_ftp.py +200 -0
  34. uripath/schemes/_http/__init__.py +4 -0
  35. uripath/schemes/_http/_backend.py +132 -0
  36. uripath/schemes/_http/_headers.py +8 -0
  37. uripath/schemes/_http/_listing.py +403 -0
  38. uripath/schemes/_http/_path.py +371 -0
  39. uripath/schemes/_http/_settings.py +6 -0
  40. uripath/schemes/_http/_streams.py +230 -0
  41. uripath/schemes/_httpdate.py +48 -0
  42. uripath/schemes/_path.py +629 -0
  43. uripath/schemes/_registry.py +134 -0
  44. uripath/source/__init__.py +20 -0
  45. uripath/source/_compose.py +212 -0
  46. uripath/source/_locality.py +44 -0
  47. uripath/source/_parse.py +183 -0
  48. uripath/source/_source.py +239 -0
@@ -0,0 +1,186 @@
1
+ from __future__ import annotations
2
+
3
+ import os as _os
4
+
5
+ from pathapi import FsPathLike
6
+ from pathapi import LocalPath as _Local
7
+ from ..source import Source
8
+ from ..source import is_drive
9
+ from ._delegate import DelegatingURIPath
10
+
11
+
12
+ class FileURIPath(DelegatingURIPath):
13
+ """`file:` scheme: a `URIPath` wrapping a `LocalPath` (`filepath`),
14
+ delegating all I/O to it."""
15
+
16
+ __SCHEMES = ("file",)
17
+ __slots__ = ()
18
+ _DELEGATED = ("_scandir", "_chown", "_utime", "unlink", "rmdir", "chmod")
19
+ _RELABEL = False
20
+
21
+ def _make_core(self):
22
+ return _Local(self.__fspath__())
23
+
24
+ @property
25
+ def filepath(self):
26
+ return self._core_path
27
+
28
+ def _unc_share(self) -> "str | None":
29
+ """The share of a UNC path ("/share" under a named host), else None.
30
+ On Windows a named host is a server and `//server/share` is the anchor
31
+ of everything below it. Only the text is looked at; nothing is
32
+ resolved."""
33
+ host = self.source.host
34
+ segments = self.segments
35
+ if (
36
+ _os.name == "nt"
37
+ and host
38
+ and not (isinstance(host, str) and host.lower() == "localhost")
39
+ and segments[:1] == ("",)
40
+ and len(segments) > 1
41
+ and segments[1]
42
+ ):
43
+ return "/" + segments[1]
44
+ return None
45
+
46
+ @property
47
+ def parent(self):
48
+ if _os.name == "nt" and is_drive(self.path.removeprefix("/")):
49
+ # A bare drive is its own parent, as in `PureWindowsPath("C:")`.
50
+ return self
51
+ share = self._unc_share()
52
+ if share is not None:
53
+ if self.path.rstrip("/") == share:
54
+ return self
55
+ parent = super().parent
56
+ if parent.path == share:
57
+ # The parent of a top-level name in a share is the share root.
58
+ return parent.with_path(share + "/")
59
+ return parent
60
+ parent = super().parent
61
+ path = parent.path
62
+ if _os.name == "nt" and is_drive(path.removeprefix("/")):
63
+ # "C:" alone is drive-RELATIVE (the current directory on C:); the
64
+ # parent of a top-level "C:/Windows" is the drive root "C:/".
65
+ return parent.with_path(path + "/")
66
+ return parent
67
+
68
+ def _anchor_segments(self):
69
+ # The drive is the anchor of "C:/a/b" (and "/C:/a/b" under a host),
70
+ # so `parents` ends at the drive root "C:/" like `parent` does; the
71
+ # share is the anchor of a UNC path.
72
+ segments = self.segments
73
+ if _os.name == "nt":
74
+ if segments[:1] and is_drive(segments[0]):
75
+ return segments[:1]
76
+ if segments[:1] == ("",) and len(segments) > 1 and is_drive(segments[1]):
77
+ return segments[:2]
78
+ if self._unc_share() is not None:
79
+ return segments[:2]
80
+ return super()._anchor_segments()
81
+
82
+ def with_segments(self, *segments):
83
+ # A Windows path string separates its names with backslashes.
84
+ if _os.name == "nt":
85
+ segments = tuple(
86
+ item.replace("\\", "/") if isinstance(item, str) else item
87
+ for item in segments
88
+ )
89
+ return super().with_segments(*segments)
90
+
91
+ def _join_decoded(self, key: str):
92
+ # A Windows path string separates its names with backslashes;
93
+ # elsewhere a backslash is an ordinary character of a file name.
94
+ if _os.name == "nt" and "\\" in key:
95
+ key = key.replace("\\", "/")
96
+ return super()._join_decoded(key)
97
+
98
+ def _is_absolute_decoded(self, path: str) -> bool:
99
+ """A drive path restarts a join, as `PureWindowsPath` does: joining
100
+ "C:/Temp/x" onto "D:/proj" gives "C:/Temp/x", not a child of it.
101
+ `is_absolute()` already answers True for such a path, so the join
102
+ has to agree with it."""
103
+ if _os.name == "nt":
104
+ drive, sep, _ = path.partition("/")
105
+ if is_drive(drive):
106
+ return bool(sep)
107
+ return super()._is_absolute_decoded(path)
108
+
109
+ def is_absolute(self):
110
+ """Also True for a Windows drive path ("C:/x"), which `_init()`
111
+ stores without the "/" a URI path gets in front of it. "C:" alone is
112
+ drive-relative, as for `PureWindowsPath`."""
113
+ path = self.path
114
+ if _os.name == "nt":
115
+ drive, sep, _ = path.partition("/")
116
+ if is_drive(drive):
117
+ return bool(sep)
118
+ return super().is_absolute()
119
+
120
+ def _init(
121
+ self,
122
+ source: Source,
123
+ path: str,
124
+ query: str,
125
+ fragment: str,
126
+ /,
127
+ **kwargs,
128
+ ):
129
+ # With a host ("file://localhost/C:/x") the "/" before the drive
130
+ # stays: a path under an authority must start with "/", and without
131
+ # it str()/repr()/hash() raised ValueError. `__fspath__` drops it.
132
+ if _os.name == "nt" and path and path[0] == "/" and not source.host:
133
+ root, *_ = path[1:].split("/", maxsplit=1)
134
+ if root and root[-1] == ":":
135
+ path = path.removeprefix("/")
136
+ super()._init(source, path, query, fragment, **kwargs)
137
+
138
+ @classmethod
139
+ def _format_parsed_parts(cls, source, path, query, fragment, /, sanitize=True):
140
+ if _os.name == "nt" and is_drive(path.partition("/")[0]):
141
+ # Render "C:/x" as "file:/C:/x", the spelling `URI` itself
142
+ # parses it to, so `URI(p) == URIPath(p)` for a local path.
143
+ path = "/" + path
144
+ return super()._format_parsed_parts(
145
+ source, path, query, fragment, sanitize=sanitize
146
+ )
147
+
148
+ def _listdir(self):
149
+ yield from _os.listdir(self.filepath)
150
+
151
+ def is_junction(self) -> bool:
152
+ return self.filepath.is_junction()
153
+
154
+ def is_mount(self) -> bool:
155
+ return self.filepath.is_mount()
156
+
157
+ def stat(self, *, follow_symlinks=True):
158
+ # LocalPath.stat() itself shims the 3.10+-only follow_symlinks= kwarg
159
+ # for pathlib.Path (see fspath.py), so just delegate.
160
+ return self.filepath.stat(follow_symlinks=follow_symlinks)
161
+
162
+ def open(self, mode="r", buffering=-1, encoding=None, errors=None, newline=None):
163
+ return self.filepath.open(mode, buffering, encoding, errors, newline)
164
+
165
+ def mkdir(self, mode=511, parents=False, exist_ok=False):
166
+ return self.filepath.mkdir(mode, parents, exist_ok)
167
+
168
+ def touch(self, mode=None, exist_ok=True):
169
+ # pathlib's touch: the mode goes through os.open(), so the umask
170
+ # applies, and an existing file's mtime is bumped. The generic
171
+ # Path.touch can do neither.
172
+ if mode is None:
173
+ return self.filepath.touch(exist_ok=exist_ok)
174
+ return self.filepath.touch(mode, exist_ok)
175
+
176
+ def rename(self, target: FsPathLike | str):
177
+ # Through `_rename_target()` like every scheme: a relative str is a sibling rename
178
+ # (not relative to the process cwd), a local path is accepted, and a remote URI
179
+ # raises NotImplementedError so move() copies.
180
+ dest = self._rename_target(target)
181
+ if not isinstance(dest, FileURIPath):
182
+ dest = self._from_parsed_parts(
183
+ dest.source or self.source, dest.path, None, None
184
+ )
185
+ self.filepath.rename(dest.filepath)
186
+ return dest
@@ -0,0 +1,200 @@
1
+ from __future__ import annotations
2
+
3
+ import logging as _logging
4
+ import threading as _thread
5
+ import typing as _ty
6
+ import weakref as _weakref
7
+
8
+ import netimps as _netimps
9
+
10
+ from pathapi.ftp import DEFAULT_TIMEOUT as DEFAULT_TIMEOUT
11
+ from pathapi.ftp import BaseFTPBackend, FTPBackend, FTPPath
12
+ from pathapi.providers import notimplemented as _notimplemented
13
+ from .._uri import URI
14
+ from ..source import Source, fold_host
15
+ from ._delegate import DelegatingURIPath
16
+
17
+ if _ty.TYPE_CHECKING:
18
+ import ssl as _ssl
19
+
20
+ _log = _logging.getLogger(__name__)
21
+
22
+
23
+ class BaseFTPURIBackend(object):
24
+ """Protocol for obtaining a connected+logged-in FTP client (or
25
+ its TLS variant) for a `Source`. Subclass this to plug in custom connection
26
+ handling (e.g. tests mock it directly, no real server);
27
+ `FTPURIBackend` is the real implementation. A path to each endpoint
28
+ (`Source` and `tls`) runs on a core `pathapi.ftp.BaseFTPBackend`
29
+ that asks `client()` for its connections.
30
+
31
+ A context manager: leaving the block calls `close()`. One backend may be
32
+ shared between threads; each thread keeps its own connection per server."""
33
+
34
+ # Weakly referenceable so `URIPath` can record which backends a path
35
+ # derived for itself (see `URIPath._supplied_backend()`).
36
+ __slots__ = ("__weakref__",)
37
+
38
+ @_notimplemented
39
+ def client(self, source: Source, tls: bool) -> "_ty.Any":
40
+ """The connected, logged-in client for `source`; `tls` asks for `ftps:`."""
41
+
42
+ def close(self) -> None:
43
+ """Closes every control connection the paths of this backend hold,
44
+ whichever thread opened it; one a command is running on closes when
45
+ that command ends. Harmless when called again; a later use connects
46
+ afresh."""
47
+ for endpoint in _endpoints_of(self):
48
+ endpoint.close()
49
+
50
+ def __enter__(self):
51
+ return self
52
+
53
+ def __exit__(self, *exc_info) -> None:
54
+ self.close()
55
+
56
+
57
+ class FTPURIBackend(BaseFTPURIBackend):
58
+ """Connects via the stdlib FTP client (`ftp:`) or its TLS variant
59
+ (`ftps:`, with `PROT P` for an encrypted data channel too).
60
+
61
+ `timeout` (seconds, default `DEFAULT_TIMEOUT` = 30) bounds connect,
62
+ every reply and every transfer read; `None` blocks forever.
63
+
64
+ `ftps:` verifies the server certificate and host name by default
65
+ (`ssl.create_default_context()`), before `USER`/`PASS` are sent. To
66
+ trust a private CA or a self-signed certificate, pass your own
67
+ `ssl_context` (e.g. `ssl.create_default_context(cafile=...)`). To turn
68
+ verification off entirely -- accepting any certificate, so anyone on
69
+ the network path can read the password -- pass `verify=False`.
70
+ `ssl_context` wins over `verify` when both are given. Data connections
71
+ reuse the control connection's TLS session."""
72
+
73
+ __slots__ = ("timeout", "ssl_context", "verify")
74
+
75
+ def __init__(
76
+ self,
77
+ timeout: "float | None" = DEFAULT_TIMEOUT,
78
+ ssl_context: "_ssl.SSLContext | None" = None,
79
+ verify: bool = True,
80
+ ) -> None:
81
+ self.timeout = timeout
82
+ self.ssl_context = ssl_context
83
+ self.verify = verify
84
+ if not verify and ssl_context is None:
85
+ _log.warning(
86
+ "FTPURIBackend(verify=False): TLS certificates are not verified"
87
+ )
88
+
89
+ def client(self, source: Source, tls: bool):
90
+ user, password = source.parsed_userinfo()
91
+ return FTPBackend(
92
+ str(source.host),
93
+ source.port or _netimps.get_default_port("ftp"),
94
+ user=user,
95
+ password=password,
96
+ tls=tls,
97
+ timeout=self.timeout,
98
+ ssl_context=self.ssl_context,
99
+ verify=self.verify,
100
+ ).client()
101
+
102
+
103
+ _DEFAULT_BACKEND = FTPURIBackend()
104
+ """The backend every `FTPURIPath` built without `backend=` shares, so separately
105
+ constructed paths to one server reuse one connection per thread instead of
106
+ opening (and keeping) one each."""
107
+
108
+
109
+ class _EndpointBackend(BaseFTPBackend):
110
+ """The core backend of one endpoint of a `BaseFTPURIBackend`: its connections
111
+ are the ones the owner's `client(source, tls)` opens, and its endpoint is
112
+ the `Source`, so paths to one server through one owner share them."""
113
+
114
+ __slots__ = ("owner", "source", "tls", "endpoint")
115
+
116
+ def __init__(self, owner: BaseFTPURIBackend, source: Source, tls: bool):
117
+ self.owner = owner
118
+ self.source = source
119
+ self.tls = tls
120
+ # The key of the connections: `ftp://NAS/` and `ftp://nas/` are one server.
121
+ self.endpoint = _endpoint(source)
122
+
123
+ def client(self):
124
+ return self.owner.client(self.source, self.tls)
125
+
126
+
127
+ def _endpoint(source: Source) -> Source:
128
+ """`source` with its host name folded, to key what is shared per server."""
129
+ return source._replace(host=fold_host(source.host))
130
+
131
+
132
+ _ENDPOINTS: "_weakref.WeakValueDictionary[tuple, _EndpointBackend]" = (
133
+ _weakref.WeakValueDictionary()
134
+ )
135
+ _ENDPOINTS_LOCK = _thread.Lock()
136
+
137
+
138
+ def _endpoint_backend(owner: BaseFTPURIBackend, source: Source, tls: bool):
139
+ """The one core backend for `owner`, `source` and `tls`, while any path or
140
+ cached connection holds it. The owner is held by the entry, so its `id`
141
+ names it for as long as the entry lives."""
142
+ key = (id(owner), _endpoint(source), tls)
143
+ with _ENDPOINTS_LOCK:
144
+ found = _ENDPOINTS.get(key)
145
+ if found is None:
146
+ found = _ENDPOINTS[key] = _EndpointBackend(owner, source, tls)
147
+ return found
148
+
149
+
150
+ def _endpoints_of(owner: BaseFTPURIBackend) -> "list[_EndpointBackend]":
151
+ """The core backends that `owner` has made and that something still holds."""
152
+ with _ENDPOINTS_LOCK:
153
+ return [found for key, found in list(_ENDPOINTS.items()) if key[0] == id(owner)]
154
+
155
+
156
+ class FTPURIPath(DelegatingURIPath):
157
+ """`ftp:`/`ftps:` scheme: full read/write access via the stdlib FTP client,
158
+ done by a `pathapi.ftp.FTPPath` on the connections of `backend`
159
+ (a thread-keyed LRU cache; a thread's connections close when it ends). A
160
+ listing prefers MLSD (RFC 3659 -- type/size/modify facts in one round
161
+ trip) and a stat MLST of the entry itself where FEAT lists it, else its
162
+ parent's MLSD; servers that don't support them fall back to NLST for
163
+ listing (names only) and SIZE/CWD for stat (no portable "not found vs. is
164
+ a directory" distinction in that path)."""
165
+
166
+ __SCHEMES = ("ftp", "ftps")
167
+ __slots__ = ()
168
+ _DELEGATED = (
169
+ "_scandir",
170
+ "_listdir",
171
+ "_open",
172
+ "_mkdir",
173
+ "unlink",
174
+ "rmdir",
175
+ "chmod",
176
+ "_utime",
177
+ )
178
+
179
+ if _ty.TYPE_CHECKING:
180
+ backend: BaseFTPURIBackend
181
+
182
+ def _initbackend(self):
183
+ return _DEFAULT_BACKEND
184
+
185
+ def _make_core(self) -> FTPPath:
186
+ source = self.source
187
+ backend = _endpoint_backend(self.backend, source, source.scheme == "ftps")
188
+ return FTPPath(self.path or "/", backend=backend)
189
+
190
+ def rename(self, target: "FTPURIPath | URI | str"):
191
+ # A plain str target is a sibling rename (relative to self's
192
+ # parent), matching sftp.py's rename() semantics.
193
+ target = self._rename_target(target)
194
+ destination = self.with_path(target.path)
195
+ core = self._core_path
196
+ moved = core.with_segments(target.path or "/")
197
+ with self._delegated((str(moved), str(destination))):
198
+ core.rename(moved)
199
+ # pathlib returns the new path.
200
+ return destination
@@ -0,0 +1,4 @@
1
+ """HTTP for `http:` and `dav:`, on the transport of `pathapi.http`. Private: the public names are exported
2
+ by `uripath.schemes`."""
3
+
4
+ from __future__ import annotations
@@ -0,0 +1,132 @@
1
+ from __future__ import annotations
2
+
3
+ import typing as _ty
4
+
5
+ from pathapi.http import HTTPClient, HTTPResponse, split_userinfo
6
+
7
+ from ..._version import __version__
8
+ from .._path import DerivedBackend
9
+ from . import _settings
10
+
11
+ if _ty.TYPE_CHECKING:
12
+ from ._path import HTTPURIPath
13
+
14
+
15
+ _UNSET = object()
16
+ """The `timeout` of a call that did not give one: the backend's own."""
17
+
18
+
19
+ class HTTPBackend(HTTPClient):
20
+ """The transport and the settings shared by an `HTTPURIPath` tree (see
21
+ `with_transport()` and `with_session()`): a `pathapi.http.HTTPClient`
22
+ that takes a path or a URL for its address and adds `write_method` and
23
+ `append_mode`.
24
+
25
+ `transport` is any object with a `send()` method as
26
+ `pathapi.http.HTTPTransport.send()`; without one the backend builds the
27
+ built-in `HTTPTransport` on first use and closes it with `close()` or at the
28
+ end of a `with` block. A transport it was given stays the caller's.
29
+ `headers` are sent with every request, request-specific headers merged
30
+ over them by name; every request gets `timeout=(10, 60)` seconds unless
31
+ `timeout` here or in the call says otherwise (`None` waits without limit).
32
+ URL userinfo (`http://user:password@host/`) is never sent inside the
33
+ request URL: it is stripped and sent as an `Authorization: Basic` header
34
+ unless `headers` or the call name one.
35
+
36
+ The backend, through `HTTPClient`, follows redirects. `GET`, `HEAD` and
37
+ `OPTIONS` follow up to 30; a redirect that changes the scheme, host or
38
+ port drops `Authorization` (and nothing else). Any other method
39
+ (`PROPFIND`, `PUT`, `PATCH`, `DELETE`, `MKCOL`, `MOVE`, a `write_method`
40
+ such as `POST`) is sent without following: a 307 or 308 (for `PROPFIND` also
41
+ a 301, 302 or 303) to the same scheme, host and port is sent once more
42
+ with the same method and body, and any other 3xx, a redirect to another
43
+ origin or a second redirect raises `OSError(EIO)`."""
44
+
45
+ __slots__ = ("write_method", "append_mode")
46
+
47
+ def __init__(
48
+ self,
49
+ transport=None,
50
+ *,
51
+ headers=None,
52
+ timeout=_settings.DEFAULT_TIMEOUT,
53
+ write_method: str = "PUT",
54
+ append_mode: str = "rewrite",
55
+ ):
56
+ super().__init__(
57
+ transport,
58
+ headers=headers,
59
+ timeout=timeout,
60
+ user_agent=f"uripath/{__version__}",
61
+ )
62
+ self.write_method = write_method
63
+ self.append_mode = append_mode
64
+
65
+ def __repr__(self) -> str:
66
+ return f"{type(self).__name__}(write_method={self.write_method!r})"
67
+
68
+ def request(
69
+ self,
70
+ method,
71
+ uri: "HTTPURIPath|str",
72
+ *,
73
+ data=None,
74
+ headers=None,
75
+ stream: bool = False,
76
+ allow_redirects=None,
77
+ timeout=_UNSET,
78
+ ) -> HTTPResponse:
79
+ """Send `method` to `uri` and return the final response. A `data`
80
+ that is not `bytes` (a file, an iterable) cannot be sent twice, so it
81
+ is not re-sent after a redirect or a dropped connection.
82
+ `allow_redirects=False` returns the first answer of a `GET`, `HEAD`
83
+ or `OPTIONS` as it is. `ValueError` for a URL or header with a control
84
+ character, before anything is sent.
85
+
86
+ With `stream=False` the body is read before this returns, and a decoded
87
+ body over `pathapi.http.MAX_REPLY_BYTES` is `OSError(EFBIG)`; with
88
+ `stream=True` the caller reads it, and closes the response."""
89
+ url, auth = split_userinfo(uri if isinstance(uri, str) else uri.as_uri(False))
90
+ return super().request(
91
+ method,
92
+ url,
93
+ data=data,
94
+ headers=headers,
95
+ stream=stream,
96
+ allow_redirects=allow_redirects,
97
+ timeout=self.timeout if timeout is _UNSET else timeout,
98
+ auth=auth,
99
+ )
100
+
101
+ def redirect(
102
+ self,
103
+ method,
104
+ uri: "HTTPURIPath|str",
105
+ resp: HTTPResponse,
106
+ *,
107
+ headers=None,
108
+ stream: bool = False,
109
+ timeout=_UNSET,
110
+ ) -> HTTPResponse:
111
+ """Send `method` to where `resp`, the answer to a request for `uri`,
112
+ redirects to, and follow what it redirects to in turn. The request
113
+ carries what `uri` is sent with, except that `Authorization` is left
114
+ off whenever it goes to another scheme, host or port than `uri`'s.
115
+ A redirect with no usable `Location` is `OSError(EIO)`."""
116
+ url, auth = split_userinfo(uri if isinstance(uri, str) else uri.as_uri(False))
117
+ return super().redirect(
118
+ method,
119
+ url,
120
+ resp,
121
+ headers=headers,
122
+ stream=stream,
123
+ timeout=self.timeout if timeout is _UNSET else timeout,
124
+ auth=auth,
125
+ )
126
+
127
+
128
+ class _DerivedHTTPBackend(HTTPBackend, DerivedBackend):
129
+ """What `HTTPURIPath._initbackend()` returns: the class itself says the path
130
+ derived this backend for itself (see `URIPath._supplied_backend()`)."""
131
+
132
+ __slots__ = ()
@@ -0,0 +1,8 @@
1
+ """Headers the HTTP schemes send for their own purposes."""
2
+
3
+ from __future__ import annotations
4
+
5
+ _IDENTITY_ENCODING = {"Accept-Encoding": "identity"}
6
+ """Sent on content GETs and `stat()` probes: a transport advertises
7
+ gzip/deflate by default, and a stored blob must not come back as its encoded
8
+ form, while `Content-Length` would count the encoded bytes."""