HTTP API
pathapi.http
HTTP requests on the standard library, with a replaceable transport.
HTTPClient sends a request through a transport (an object with send():
HTTPTransport on http.client, or one another distribution provides) and
follows redirects under one rule for credentials: Authorization goes to the
origin it was given for and to no other scheme, host or port. A reply comes
back as an HTTPResponse. translate_errors() maps a failed request to the
OSError types pathlib raises.
Importing this package loads http.client and ssl; import pathapi
does not. Nothing here needs a third-party library.
DEFAULT_TIMEOUT = (10, 60)
module-attribute
(connect, read) timeout, in seconds, a client sends with every request
unless it is given timeout (HTTPClient(timeout=...) or per request);
timeout=None waits without a limit.
TRANSPORT_ENV = 'PATHAPI_HTTP_TRANSPORT'
module-attribute
Names the transport an HTTPClient builds for itself: builtin, or the
name of one that an installed distribution declares in TRANSPORT_GROUP
(requests, from pathapi-requests). Read when that transport is first
needed; unset means builtin.
TRANSPORT_GROUP = 'pathapi.http_transports'
module-attribute
The entry-point group of the HTTP transports. An entry's name is the
transport's name (requests) and its value names a class that is built with
no arguments.
HTTPClient(transport=None, *, headers=None, timeout=DEFAULT_TIMEOUT, user_agent=None)
A transport with the settings every request is sent with, and the rules for what happens after a redirect.
transport is any object with a send() method as HTTPTransport.send();
without one the client builds the transport that PATHAPI_HTTP_TRANSPORT
names (the standard-library HTTPTransport by default) on first use and
closes it with close() or at the end of a with block. A transport it
was given stays the caller's. headers are sent with every request,
request-specific headers merged over them by name, over a User-Agent of
user_agent (pathapi/<version> by default) and Accept: */*.
Every request gets timeout=DEFAULT_TIMEOUT ((10, 60) seconds) unless
timeout here or in the call says otherwise (None waits without limit).
The client, not the transport, follows redirects. GET, HEAD and
OPTIONS follow up to 30; a redirect that changes the scheme, host or
port drops Authorization (and nothing else). Any other method is sent
without following: a 307 or 308 (for PROPFIND also a 301, 302 or 303) to
the same scheme, host and port is sent once more with the same method and
body, and any other 3xx, a redirect to another origin or a second redirect
raises OSError(EIO).
Source code in src/pathapi/http/_client.py
57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 | |
headers = dict(headers or {})
instance-attribute
timeout = timeout
instance-attribute
transport
property
The transport: the one the client was given, or one built on first use. Read once per request.
user_agent = user_agent or f'pathapi/{__version__}'
instance-attribute
close()
Closes the transport this client built for itself; one it was given stays the caller's. Harmless when called again.
Source code in src/pathapi/http/_client.py
90 91 92 93 94 95 96 97 98 | |
redirect(method, url, resp, *, headers=None, stream=False, timeout=_UNSET, auth=None)
Send method to where resp, the answer to a request for url,
redirects to, and follow what it redirects to in turn. The request
carries what url is sent with, except that Authorization is left
off whenever it goes to another scheme, host or port than url's. A
redirect with no usable Location is OSError(EIO).
Source code in src/pathapi/http/_client.py
187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 | |
request(method, url, *, data=None, headers=None, stream=False, allow_redirects=None, timeout=_UNSET, auth=None)
Send method to url and return the final response. A data that
is not bytes (a file, an iterable) cannot be sent twice, so it is not
re-sent after a redirect or a dropped connection.
allow_redirects=False returns the first answer of a GET, HEAD or
OPTIONS as it is. auth is a (user, password) pair sent as
Authorization: Basic, or a str sent as the Authorization value
("Bearer <token>"), to the origin of url unless a header names an
Authorization. ValueError for a URL (credentials in it included)
or a header with a control character, before anything is sent.
With stream=False the body is read before this returns, and a decoded
body over MAX_REPLY_BYTES is OSError(EFBIG); with stream=True the
caller reads it, and closes the response.
Source code in src/pathapi/http/_client.py
159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 | |
HTTPResponse(status, reason, headers, url, body=b'', *, decode=True)
One response: status, reason, headers (a read-only mapping that
ignores the case of a name), url (the URL the request went to) and the
body, read with read() or iter_bytes(). A context manager that closes.
The body is decoded when the response says it is gzip or deflate
encoded, unless the request set its own Accept-Encoding; headers are
the headers as received, so Content-Length counts the encoded bytes. A
body that ends before the length the server stated is OSError(EIO), never
a shorter body that reads as complete.
A transport builds one with body as bytes, or as an object with
read(amount) -> bytes (empty at the end) and close(), and
decode=False for a body it already decoded or that was not asked for
in a coding.
Source code in src/pathapi/http/_response.py
129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 | |
headers = headers if isinstance(headers, Headers) else Headers(headers)
instance-attribute
reason = reason or ''
instance-attribute
status = int(status)
instance-attribute
url = url
instance-attribute
buffered()
Reads the rest of the body into memory, closes the connection and
returns this response, which can then be read after it is closed. A
transport returns its response this way when send() is called with
stream=False.
Source code in src/pathapi/http/_response.py
233 234 235 236 237 238 | |
close()
Releases the connection. Harmless when called again. A body that was not read to its end is abandoned, and its connection is closed.
Source code in src/pathapi/http/_response.py
226 227 228 229 230 231 | |
iter_bytes(chunk_size=CHUNK)
The rest of the body in pieces of at most chunk_size bytes, each
as soon as it has arrived.
Source code in src/pathapi/http/_response.py
215 216 217 218 219 220 221 222 223 224 | |
read(size=-1)
Up to size bytes of the decoded body, fewer only at its end; the
whole rest of it when size is negative or None.
Source code in src/pathapi/http/_response.py
195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 | |
HTTPStatusError(response)
Bases: Exception
A reply with an error status (4xx or 5xx). response is the reply;
translate_errors() turns the error into the exception pathlib raises.
Source code in src/pathapi/http/_status.py
18 19 20 | |
response = response
instance-attribute
HTTPTransport(*, ssl_context=None, verify=True, proxies=None)
Sends HTTP requests on the standard library: http.client over a socket,
with TLS from ssl, and no third-party code.
send() makes exactly one request: it follows no redirect, adds no
credentials and retries only as described below. A transport is a context
manager, and close() closes every connection it holds.
https: servers are verified against ssl_context; without one, against
ssl.create_default_context() (the system trust store). verify=False
is the one way to turn verification off, and ssl_context wins over it.
proxies decides which proxy a request uses. None reads the
environment (HTTP_PROXY, HTTPS_PROXY, ALL_PROXY, NO_PROXY, through
urllib.request.getproxies() and proxy_bypass()). Otherwise it is an
object indexed with the request URL: it returns None (no proxy), one
proxy, or an iterable of them to try in order when a connection cannot be
made; KeyError means no proxy and the environment is not asked. A proxy
is a URL or any object with a url attribute; a
plain dict may use the keys requests uses ("http", "all", ...).
Only http:// proxies are supported (CONNECT for https:), with Basic
credentials from the proxy URL; any other scheme is
OSError(EPROTONOSUPPORT). {} sends everything direct.
Each thread keeps one idle connection per server (at most 8), which a later
request reuses unless the server has closed it or sent bytes on it since;
the connections of a thread are closed when it ends. A
request that fails before any response byte on a reused connection because
the server had closed it is sent once more on a new connection, but only
if it is repeatable (GET, HEAD, OPTIONS, PUT, DELETE,
PROPFIND) and its body is None or in memory. Any other request reuses a
connection only if it carried a response within the last second.
Source code in src/pathapi/http/_builtin.py
287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 | |
close()
Closes every connection, idle or in use. Harmless when called again; a later request connects afresh.
Source code in src/pathapi/http/_builtin.py
313 314 315 316 | |
send(method, url, *, headers, body=None, timeout, stream=False)
One request, one response.
headers are sent as given; Accept-Encoding: gzip, deflate is added
(and a coded body decoded) unless they name an Accept-Encoding.
A body whose length is known without reading it goes under a
Content-Length, any other chunked; a Content-Length in headers
is used as it is. timeout is seconds for the connection and for each wait on the
server: a number, a (connect, read) pair, or None to wait for ever.
With stream=False the body has been read when this returns; with
stream=True it is read from the response, which must be closed.
Raises ValueError before anything is sent for a header or URL with
a control character, a URL with credentials or another scheme;
TimeoutError, ConnectionError and ssl.SSLError for the network;
OSError(EIO) for a reply that is not valid HTTP or ends early.
Source code in src/pathapi/http/_builtin.py
323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 | |
Headers(pairs=())
Bases: Mapping
The headers of a response: a read-only mapping whose names compare
without regard to case. A name sent more than once reads as its values
joined with ", " (RFC 9110 5.3); get_all() returns them separately.
Source code in src/pathapi/http/_headers.py
25 26 27 28 29 30 31 32 33 34 35 | |
get_all(name)
Every value sent for name, in order.
Source code in src/pathapi/http/_headers.py
49 50 51 52 | |
PreparedRequest
Bases: NamedTuple
A request that passed the checks of the transport contract.
headers hold text values, with the Content-Length of a body whose
length is known (0 for a PATCH, POST or PUT without a body) and an
Accept-Encoding of gzip, deflate unless the caller named one. body is
None, bytes, an object that sends exactly the Content-Length bytes
when it is read or iterated, or an iterable of bytes to send chunked.
timeout is (connect, read). decode says whether the response decodes
a gzip or deflate body: build it with HTTPResponse(...,
decode=decode).
body
instance-attribute
decode
instance-attribute
headers
instance-attribute
method
instance-attribute
timeout
instance-attribute
url
instance-attribute
default_transport()
A new transport of the kind PATHAPI_HTTP_TRANSPORT names. A value
that is empty, differently cased or unknown is ValueError naming the
choices; one that another distribution provides and that is not installed
is ImportError naming it.
Source code in src/pathapi/http/_transports.py
100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 | |
drop_body(resp, limit=None)
Gives up the body of a reply nobody looks at: it is read, up to limit
bytes (default MAX_REPLY_BYTES), so the connection can serve the next
request, and resp is closed (which closes the connection when the body
was longer). A failure while reading it is not the caller's concern: the
status was already used.
Source code in src/pathapi/http/_status.py
45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 | |
get_transport(name)
The class of the transport called name (case-sensitive), loading its
entry point. builtin is always HTTPTransport: no entry point replaces
it.
Raises ImportError naming what to install for a transport that another
distribution provides and that is not installed, and ValueError naming
the choices for any other name.
Source code in src/pathapi/http/_transports.py
71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 | |
is_redirect(resp)
Source code in src/pathapi/http/_follow.py
93 94 | |
json_body(resp, what, limit=None)
The JSON document in the body of resp (at most limit bytes, default
MAX_REPLY_BYTES, else OSError(EFBIG) naming what); ValueError when
it is not one.
Source code in src/pathapi/http/_status.py
64 65 66 67 68 | |
os_error(error_class, subject)
error_class built as pathlib builds it -- (errno, strerror,
filename) -- for subject (a path or a URL, shown through str()), so
e.errno and e.filename are set.
Source code in src/pathapi/http/_errors.py
21 22 23 24 25 26 | |
peer_text(text, limit=120)
text that came from the other end (a reason phrase, a Location, a
header value) as an error message may show it: characters a terminal or a
log would act on (controls, line and bidirectional separators) escaped, and
cut to limit characters.
Source code in src/pathapi/http/_headers.py
59 60 61 62 63 64 65 66 67 68 69 70 | |
prepare_request(method, url, *, headers, body=None, timeout)
The arguments of send() as the contract reads them.
method is upper-cased. Raises ValueError, before anything is sent, for
a method or header name that is not an HTTP token, a header value or URL
with a control character, a URL with credentials or another scheme, a
Content-Length that is not a count of bytes or exceeds the body, and a
timeout that is not a positive number, None or a pair of them;
TypeError for a str body. None of the messages quotes the URL or a
header value. The fragment is dropped from url.
Source code in src/pathapi/http/_transports.py
139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 | |
raise_for_status(resp)
Raises HTTPStatusError for a reply with status 400 or above.
Source code in src/pathapi/http/_status.py
23 24 25 26 | |
read_limited(resp, what, limit=None)
The decoded body of resp, which must be at most limit bytes (default
MAX_REPLY_BYTES): OSError(EFBIG) naming what otherwise, with the
connection closed.
Source code in src/pathapi/http/_status.py
36 37 38 39 40 41 42 | |
server_size(text)
A byte count a server stated (Content-Length, getcontentlength) as
an int, or None when it is not a plain non-negative decimal number.
Source code in src/pathapi/http/_errors.py
29 30 31 32 33 34 35 | |
split_userinfo(url)
Split the userinfo out of an absolute URL: (url_without_userinfo,
(user, password) | None), both parts percent-decoded to the octets they
stand for. Credentials inside a request URL would end up in
HTTPResponse.url, in error text and in redirect handling; they are sent
in an Authorization header instead.
Source code in src/pathapi/http/_follow.py
41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 | |
translate_errors(subject, *, conflict=FileExistsError)
Map a failed request to the exception pathlib raises for subject.
An HTTPStatusError becomes FileNotFoundError (404, 410),
PermissionError (401, 403; 405 and 501 too, naming the status),
conflict (409) or OSError(EIO) naming the status; a TimeoutError or
ConnectionError from the transport is raised again with a message that
holds subject and the exception's type name. conflict is what a 409
means: for a write (PUT, MKCOL, MOVE) RFC 4918 uses 409 for a missing
parent collection, so writers pass FileNotFoundError. The cause is not
chained: a traceback would otherwise print a URL, a header or a
credential that the transport's own error carries.
Source code in src/pathapi/http/_errors.py
38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 | |
transport_names()
The names of the HTTP transports, sorted: builtin, every entry of
TRANSPORT_GROUP, and the transports that another distribution provides
whether or not it is installed (requests). Nothing is imported.
Source code in src/pathapi/http/_transports.py
64 65 66 67 68 | |