httpp 0.7.0__cp315-cp315t-win_amd64.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.
- httpp/__init__.py +61 -0
- httpp/__main__.py +257 -0
- httpp/_http.py +130 -0
- httpp/_params.py +448 -0
- httpp/_run.py +167 -0
- httpp/_schema.py +409 -0
- httpp/_validate.py +226 -0
- httpp/_validate_cy.cp315t-win_amd64.pyd +0 -0
- httpp/cmake/httppConfig.cmake +32 -0
- httpp/cmake/httppConfigVersion.cmake +65 -0
- httpp/cmake/httppTargets-release.cmake +19 -0
- httpp/cmake/httppTargets.cmake +106 -0
- httpp/errors.py +50 -0
- httpp/httpp_cy.cp315t-win_amd64.pyd +0 -0
- httpp/include/httpp/client.hpp +299 -0
- httpp/include/httpp/curl.hpp +52 -0
- httpp/include/httpp/curl_compat.h +102 -0
- httpp/include/httpp/download.hpp +129 -0
- httpp/include/httpp/export.hpp +26 -0
- httpp/include/httpp/log.hpp +31 -0
- httpp/include/httpp/progress.hpp +86 -0
- httpp/include/httpp/server.hpp +192 -0
- httpp/include/httpp/sha256.hpp +24 -0
- httpp/include/httpp/swagger_ui.hpp +30 -0
- httpp/include/httpp/url.hpp +42 -0
- httpp/include/httpp.h +57 -0
- httpp/lib/httpp_core.dll +0 -0
- httpp/lib/httpp_core.lib +0 -0
- httpp/models.py +497 -0
- httpp/progress.py +34 -0
- httpp/server.py +900 -0
- httpp-0.7.0.dist-info/METADATA +430 -0
- httpp-0.7.0.dist-info/RECORD +36 -0
- httpp-0.7.0.dist-info/WHEEL +5 -0
- httpp-0.7.0.dist-info/entry_points.txt +3 -0
- httpp-0.7.0.dist-info/licenses/LICENSE.txt +21 -0
httpp/__init__.py
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"""httpp: a lightweight, zero-system-dependency HTTP client + server."""
|
|
2
|
+
|
|
3
|
+
import os as _os
|
|
4
|
+
import sys as _sys
|
|
5
|
+
|
|
6
|
+
try:
|
|
7
|
+
# Reads the version scikit-build-core actually wrote into this wheel's
|
|
8
|
+
# metadata (derived from include/httpp.h at build time) — never
|
|
9
|
+
# hardcoded here, so it can't drift from a real release version.
|
|
10
|
+
from importlib.metadata import version as _pkg_version
|
|
11
|
+
__version__ = _pkg_version("httpp")
|
|
12
|
+
except Exception:
|
|
13
|
+
__version__ = "0.0.0+unknown"
|
|
14
|
+
|
|
15
|
+
_pkg_dir = _os.path.dirname(__file__)
|
|
16
|
+
_lib_dir = _os.path.join(_pkg_dir, "lib")
|
|
17
|
+
|
|
18
|
+
# On Windows, a .pyd's own directory (and its dependencies' directory) isn't
|
|
19
|
+
# automatically searched for dependent DLLs — libhttpp_core.dll lives in
|
|
20
|
+
# httpp/lib/, next to but not inside httpp/. Linux/macOS solve the
|
|
21
|
+
# equivalent problem at link time via RPATH ($ORIGIN/lib / @loader_path/lib,
|
|
22
|
+
# see src/bindings/python/CMakeLists.txt), so this only runs on win32.
|
|
23
|
+
if _sys.platform == "win32" and hasattr(_os, "add_dll_directory"):
|
|
24
|
+
_os.add_dll_directory(_lib_dir)
|
|
25
|
+
|
|
26
|
+
# Import the compiled Cython module
|
|
27
|
+
from .httpp_cy import Client, download, Download, DownloadResult, Request, Response, URL
|
|
28
|
+
from .httpp_cy import set_log_level, set_log_file, set_log_callback
|
|
29
|
+
from . import progress
|
|
30
|
+
from .server import Server, ServerRequest, ResponseHead, file, redirect, set_cookie
|
|
31
|
+
from ._params import Query, Path, Header, Cookie, Body, Depends
|
|
32
|
+
from ._run import run
|
|
33
|
+
from ._http import AsyncClient, request, get, head, options, post, put, patch, delete
|
|
34
|
+
from .errors import Error, HTTPError, RequestError, Timeout, TLSError, TooManyRedirects, InvalidURL
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def get_include_dir():
|
|
38
|
+
"""Directory containing httpp.h and httpp/*.hpp, for use in a
|
|
39
|
+
downstream CMakeLists.txt or a plain compiler -I flag."""
|
|
40
|
+
return _os.path.join(_pkg_dir, "include")
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def get_lib_dir():
|
|
44
|
+
"""Directory containing the compiled httpp C++ library."""
|
|
45
|
+
return _lib_dir
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def get_cmake_dir():
|
|
49
|
+
"""Directory containing httppConfig.cmake, for
|
|
50
|
+
find_package(httpp CONFIG). See the package README for a full example."""
|
|
51
|
+
return _os.path.join(_pkg_dir, "cmake")
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
__all__ = [
|
|
55
|
+
"Client", "AsyncClient", "Request", "Response", "request", "get", "head", "options", "post", "put",
|
|
56
|
+
"patch", "delete", "Server", "run", "ServerRequest", "ResponseHead", "file", "redirect", "set_cookie", "download", "Download", "DownloadResult", "URL", "progress",
|
|
57
|
+
"Query", "Path", "Header", "Cookie", "Body", "Depends",
|
|
58
|
+
"Error", "HTTPError", "RequestError", "Timeout", "TLSError", "TooManyRedirects", "InvalidURL",
|
|
59
|
+
"get_include_dir", "get_lib_dir", "get_cmake_dir",
|
|
60
|
+
"set_log_level", "set_log_file", "set_log_callback",
|
|
61
|
+
]
|
httpp/__main__.py
ADDED
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
"""Command-line interface for httpp. Stdlib only (argparse) — no
|
|
2
|
+
third-party dependencies, matching the rest of the project."""
|
|
3
|
+
|
|
4
|
+
import argparse
|
|
5
|
+
import shutil
|
|
6
|
+
import sys
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def _workers_arg(text):
|
|
11
|
+
"""argparse type for --workers: a positive integer or 'auto'."""
|
|
12
|
+
if text == "auto":
|
|
13
|
+
return "auto"
|
|
14
|
+
try:
|
|
15
|
+
n = int(text)
|
|
16
|
+
except ValueError:
|
|
17
|
+
n = 0
|
|
18
|
+
if n < 1:
|
|
19
|
+
raise argparse.ArgumentTypeError(f"workers must be an integer >= 1 or 'auto', not {text!r}")
|
|
20
|
+
return n
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _cmd_server(args):
|
|
24
|
+
from httpp import Server
|
|
25
|
+
|
|
26
|
+
srv = Server()
|
|
27
|
+
srv.serve_directory("/", args.directory)
|
|
28
|
+
print(f"Serving {args.directory!r} at http://{args.host}:{args.port}/")
|
|
29
|
+
try:
|
|
30
|
+
srv.listen(args.host, args.port)
|
|
31
|
+
except KeyboardInterrupt:
|
|
32
|
+
srv.stop()
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _cmd_run(args):
|
|
36
|
+
from httpp import run
|
|
37
|
+
|
|
38
|
+
try:
|
|
39
|
+
return run(args.app, args.host, args.port, workers=args.workers, reuse_port=args.reuse_port, log_level=args.log_level)
|
|
40
|
+
except (ValueError, TypeError, OSError) as e:
|
|
41
|
+
print(f"httpp: error: {e}", file=sys.stderr)
|
|
42
|
+
return 1
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _cmd_download(args):
|
|
46
|
+
if args.output:
|
|
47
|
+
# Real download to a file, with a terminal progress bar (see
|
|
48
|
+
# httpp::download / httpp::progress::bar) — nothing here
|
|
49
|
+
# reimplements the request or the progress logic, it's all in C++.
|
|
50
|
+
from httpp import Download
|
|
51
|
+
|
|
52
|
+
result = Download(args.url, args.output).enable_progress(not args.quiet).run()
|
|
53
|
+
if not result.ok:
|
|
54
|
+
print(f"error: {result.error}", file=sys.stderr)
|
|
55
|
+
return 1
|
|
56
|
+
return 0
|
|
57
|
+
|
|
58
|
+
# No destination given: behave like `curl URL` with no -o — print the
|
|
59
|
+
# body to stdout. URL parsing happens entirely in the C++ layer
|
|
60
|
+
# (httpp::client::fetch, backed by the vendored liburlparser).
|
|
61
|
+
from httpp import Client
|
|
62
|
+
|
|
63
|
+
res = Client.fetch(args.url)
|
|
64
|
+
if not res.ok:
|
|
65
|
+
print(f"error: request failed with status {res.status}", file=sys.stderr)
|
|
66
|
+
return 1
|
|
67
|
+
print(res.body)
|
|
68
|
+
return 0
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _sdk_base(args):
|
|
72
|
+
if args.prefix:
|
|
73
|
+
return Path(args.prefix)
|
|
74
|
+
if args.user:
|
|
75
|
+
return Path.home() / ".local"
|
|
76
|
+
return Path("/usr/local")
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def _cmd_install(args):
|
|
80
|
+
import httpp as _httpp
|
|
81
|
+
|
|
82
|
+
base = _sdk_base(args)
|
|
83
|
+
target = base / "httpp"
|
|
84
|
+
sources = {
|
|
85
|
+
"include": _httpp.get_include_dir(),
|
|
86
|
+
"lib": _httpp.get_lib_dir(),
|
|
87
|
+
"cmake": _httpp.get_cmake_dir(),
|
|
88
|
+
}
|
|
89
|
+
try:
|
|
90
|
+
for name, src in sources.items():
|
|
91
|
+
shutil.copytree(src, target / name, dirs_exist_ok=True)
|
|
92
|
+
except PermissionError:
|
|
93
|
+
print(
|
|
94
|
+
f"error: no permission to write to {target}\n"
|
|
95
|
+
f"Try `httpp install --user`, or re-run with sudo.",
|
|
96
|
+
file=sys.stderr,
|
|
97
|
+
)
|
|
98
|
+
return 1
|
|
99
|
+
|
|
100
|
+
print(f"Installed httpp headers/lib/cmake config to {target}")
|
|
101
|
+
print(f"To use: cmake -DCMAKE_PREFIX_PATH={base} ... then find_package(httpp CONFIG)")
|
|
102
|
+
return 0
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def _cmd_uninstall(args):
|
|
106
|
+
base = _sdk_base(args)
|
|
107
|
+
target = base / "httpp"
|
|
108
|
+
if not target.exists():
|
|
109
|
+
print(f"nothing installed at {target}")
|
|
110
|
+
return 0
|
|
111
|
+
try:
|
|
112
|
+
shutil.rmtree(target)
|
|
113
|
+
except PermissionError:
|
|
114
|
+
print(
|
|
115
|
+
f"error: no permission to remove {target}\n"
|
|
116
|
+
f"Try `httpp uninstall --user`, or re-run with sudo.",
|
|
117
|
+
file=sys.stderr,
|
|
118
|
+
)
|
|
119
|
+
return 1
|
|
120
|
+
print(f"Removed {target}")
|
|
121
|
+
return 0
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def _cmd_curl(args):
|
|
125
|
+
from httpp import Request
|
|
126
|
+
|
|
127
|
+
req = Request(args.url)
|
|
128
|
+
if args.request:
|
|
129
|
+
req.method(args.request)
|
|
130
|
+
for h in args.header or []:
|
|
131
|
+
if ":" not in h:
|
|
132
|
+
print(f"error: malformed header (expected 'Name: value'): {h!r}", file=sys.stderr)
|
|
133
|
+
return 1
|
|
134
|
+
name, value = h.split(":", 1)
|
|
135
|
+
req.header(name.strip(), value.strip())
|
|
136
|
+
if args.data is not None:
|
|
137
|
+
req.data(args.data)
|
|
138
|
+
|
|
139
|
+
res = req.run()
|
|
140
|
+
if args.output:
|
|
141
|
+
with open(args.output, "w", encoding="utf-8") as f:
|
|
142
|
+
f.write(res.body)
|
|
143
|
+
else:
|
|
144
|
+
print(res.body)
|
|
145
|
+
if not res.ok:
|
|
146
|
+
print(f"error: request failed with status {res.status}", file=sys.stderr)
|
|
147
|
+
return 1
|
|
148
|
+
return 0
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def _cmd_path(args):
|
|
152
|
+
import httpp as _httpp
|
|
153
|
+
|
|
154
|
+
printed = False
|
|
155
|
+
if args.include_dir:
|
|
156
|
+
print(_httpp.get_include_dir())
|
|
157
|
+
printed = True
|
|
158
|
+
if args.lib_dir:
|
|
159
|
+
print(_httpp.get_lib_dir())
|
|
160
|
+
printed = True
|
|
161
|
+
if args.cmake_dir:
|
|
162
|
+
print(_httpp.get_cmake_dir())
|
|
163
|
+
printed = True
|
|
164
|
+
if not printed:
|
|
165
|
+
# no flag given: print all three, labeled
|
|
166
|
+
print(f"include: {_httpp.get_include_dir()}")
|
|
167
|
+
print(f"lib: {_httpp.get_lib_dir()}")
|
|
168
|
+
print(f"cmake: {_httpp.get_cmake_dir()}")
|
|
169
|
+
return 0
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def build_parser():
|
|
173
|
+
parser = argparse.ArgumentParser(
|
|
174
|
+
prog="httpp", description="httpp: a lightweight HTTP client + server toolkit."
|
|
175
|
+
)
|
|
176
|
+
# Top-level path flags (nanobind/pybind11 style: `httpp --cmake-dir`,
|
|
177
|
+
# same info as `python -c "import httpp; print(httpp.get_cmake_dir())"`)
|
|
178
|
+
# work standalone, without a subcommand.
|
|
179
|
+
parser.add_argument("--cmake-dir", action="store_true", help="Print the directory containing httppConfig.cmake and exit.")
|
|
180
|
+
parser.add_argument("--include-dir", action="store_true", help="Print the directory containing httpp.h and exit.")
|
|
181
|
+
parser.add_argument("--lib-dir", action="store_true", help="Print the directory containing the compiled httpp library and exit.")
|
|
182
|
+
|
|
183
|
+
sub = parser.add_subparsers(dest="command")
|
|
184
|
+
|
|
185
|
+
p_server = sub.add_parser("server", help="Serve a directory over HTTP, like `python -m http.server`.")
|
|
186
|
+
p_server.add_argument("directory", nargs="?", default=".")
|
|
187
|
+
p_server.add_argument("--host", default="0.0.0.0")
|
|
188
|
+
p_server.add_argument("--port", "-p", type=int, default=8000)
|
|
189
|
+
p_server.set_defaults(func=_cmd_server)
|
|
190
|
+
|
|
191
|
+
p_run = sub.add_parser("run", help="Run an httpp app: `httpp run main:app`.")
|
|
192
|
+
p_run.add_argument("app", help="import string 'module:attribute' of an httpp.Server")
|
|
193
|
+
p_run.add_argument("--host", default="127.0.0.1", help="default 127.0.0.1; use 0.0.0.0 to listen on every interface")
|
|
194
|
+
p_run.add_argument("--port", "-p", type=int, default=8000)
|
|
195
|
+
p_run.add_argument("--workers", "-w", type=_workers_arg, default=1, metavar="N|auto",
|
|
196
|
+
help="processes sharing the port (Linux only); gets past the GIL. "
|
|
197
|
+
"'auto' = one per usable CPU (default 1)")
|
|
198
|
+
p_run.add_argument("--reuse-port", action="store_true", help="let other httpp processes share the port (Linux only)")
|
|
199
|
+
p_run.add_argument("--log-level", choices=("trace", "debug", "info", "warning", "error", "off"), default=None)
|
|
200
|
+
p_run.set_defaults(func=_cmd_run)
|
|
201
|
+
|
|
202
|
+
p_download = sub.add_parser("download", help="Download a URL (to stdout, or a file with -o).")
|
|
203
|
+
p_download.add_argument("url")
|
|
204
|
+
p_download.add_argument("-o", "--output", default=None, help="Save to this file (shows a progress bar) instead of printing to stdout.")
|
|
205
|
+
p_download.add_argument("-q", "--quiet", action="store_true", help="With -o, suppress the progress bar.")
|
|
206
|
+
p_download.set_defaults(func=_cmd_download)
|
|
207
|
+
|
|
208
|
+
p_curl = sub.add_parser("curl", help="Make a request (limited curl-like subset: -X/-H/-d).")
|
|
209
|
+
p_curl.add_argument("url")
|
|
210
|
+
p_curl.add_argument("-X", "--request", default=None, help="HTTP method (default: GET, or POST if -d is given).")
|
|
211
|
+
p_curl.add_argument("-H", "--header", action="append", default=None, help="Extra header 'Name: value' (repeatable).")
|
|
212
|
+
p_curl.add_argument("-d", "--data", default=None, help="Request body.")
|
|
213
|
+
p_curl.add_argument("-o", "--output", default=None, help="Write the response body to this file instead of stdout.")
|
|
214
|
+
p_curl.set_defaults(func=_cmd_curl)
|
|
215
|
+
|
|
216
|
+
sdk_parent = argparse.ArgumentParser(add_help=False)
|
|
217
|
+
sdk_parent.add_argument("--prefix", default=None, help="Install location (parent of httpp/). Default: /usr/local.")
|
|
218
|
+
sdk_parent.add_argument("--user", action="store_true", help="Use ~/.local instead of /usr/local.")
|
|
219
|
+
|
|
220
|
+
p_install = sub.add_parser(
|
|
221
|
+
"install", parents=[sdk_parent],
|
|
222
|
+
help="Install the httpp C++ SDK (headers, lib, CMake config) onto the system.",
|
|
223
|
+
)
|
|
224
|
+
p_install.set_defaults(func=_cmd_install)
|
|
225
|
+
|
|
226
|
+
p_uninstall = sub.add_parser(
|
|
227
|
+
"uninstall", parents=[sdk_parent],
|
|
228
|
+
help="Remove what `httpp install` copied.",
|
|
229
|
+
)
|
|
230
|
+
p_uninstall.set_defaults(func=_cmd_uninstall)
|
|
231
|
+
|
|
232
|
+
# Same three flags as a subcommand too: `httpp path --cmake-dir`.
|
|
233
|
+
p_path = sub.add_parser("path", help="Print include/lib/cmake directories (see also the top-level --cmake-dir etc.).")
|
|
234
|
+
p_path.add_argument("--cmake-dir", action="store_true")
|
|
235
|
+
p_path.add_argument("--include-dir", action="store_true")
|
|
236
|
+
p_path.add_argument("--lib-dir", action="store_true")
|
|
237
|
+
p_path.set_defaults(func=_cmd_path)
|
|
238
|
+
|
|
239
|
+
return parser
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
def main(argv=None):
|
|
243
|
+
parser = build_parser()
|
|
244
|
+
args = parser.parse_args(argv)
|
|
245
|
+
|
|
246
|
+
# Top-level path flags work with no subcommand: `httpp --cmake-dir`.
|
|
247
|
+
if getattr(args, "command", None) is None:
|
|
248
|
+
if args.cmake_dir or args.include_dir or args.lib_dir:
|
|
249
|
+
return _cmd_path(args)
|
|
250
|
+
parser.print_help()
|
|
251
|
+
return 1
|
|
252
|
+
|
|
253
|
+
return args.func(args) or 0
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
if __name__ == "__main__":
|
|
257
|
+
sys.exit(main())
|
httpp/_http.py
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
"""One-call helpers (httpp.get(...), httpp.post(...), ...) and AsyncClient.
|
|
2
|
+
|
|
3
|
+
Both are thin layers over the compiled Client / Request; every capability here exists in the C++
|
|
4
|
+
API too (httpp::client::request, httpp::client, httpp::async_client).
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import asyncio
|
|
8
|
+
|
|
9
|
+
from .httpp_cy import Client, Request, _AsyncClientCore
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def request(method, url, *, params=None, headers=None, data=None, json=None, form=None,
|
|
13
|
+
content_type=None, timeout=None, follow_redirects=False, auth=None, token=None,
|
|
14
|
+
cookies=None, verify=True, ca_file=None, proxy=None):
|
|
15
|
+
"""Make one request and return its Response. See Request for what each option means.
|
|
16
|
+
|
|
17
|
+
r = httpp.request("POST", "http://localhost:8000/items", json={"name": "apple"}, timeout=10)
|
|
18
|
+
r.raise_for_status().json()
|
|
19
|
+
"""
|
|
20
|
+
req = Request(url).method(method)
|
|
21
|
+
if params:
|
|
22
|
+
req.params(params)
|
|
23
|
+
for name, value in (headers.items() if hasattr(headers, "items") else headers or ()):
|
|
24
|
+
req.header(name, value)
|
|
25
|
+
if json is not None:
|
|
26
|
+
req.json(json)
|
|
27
|
+
elif form is not None:
|
|
28
|
+
req.form(form)
|
|
29
|
+
elif data is not None:
|
|
30
|
+
req.data(data)
|
|
31
|
+
if content_type is not None:
|
|
32
|
+
req.content_type(content_type)
|
|
33
|
+
if timeout is not None:
|
|
34
|
+
req.timeout(timeout)
|
|
35
|
+
if follow_redirects:
|
|
36
|
+
req.follow_redirects()
|
|
37
|
+
if auth is not None:
|
|
38
|
+
req.basic_auth(*auth)
|
|
39
|
+
if token is not None:
|
|
40
|
+
req.bearer(token)
|
|
41
|
+
for name, value in (cookies.items() if hasattr(cookies, "items") else cookies or ()):
|
|
42
|
+
req.cookie(name, value)
|
|
43
|
+
if not verify:
|
|
44
|
+
req.verify(False)
|
|
45
|
+
if ca_file is not None:
|
|
46
|
+
req.ca_file(ca_file)
|
|
47
|
+
if proxy is not None:
|
|
48
|
+
req.proxy(*proxy)
|
|
49
|
+
return req.run()
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def get(url, **kwargs):
|
|
53
|
+
return request("GET", url, **kwargs)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def head(url, **kwargs):
|
|
57
|
+
return request("HEAD", url, **kwargs)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def options(url, **kwargs):
|
|
61
|
+
return request("OPTIONS", url, **kwargs)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def post(url, **kwargs):
|
|
65
|
+
return request("POST", url, **kwargs)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def put(url, **kwargs):
|
|
69
|
+
return request("PUT", url, **kwargs)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def patch(url, **kwargs):
|
|
73
|
+
return request("PATCH", url, **kwargs)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def delete(url, **kwargs):
|
|
77
|
+
return request("DELETE", url, **kwargs)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class AsyncClient:
|
|
81
|
+
"""Many requests to one server at once, from asyncio code.
|
|
82
|
+
|
|
83
|
+
async with httpp.AsyncClient("http://localhost:8000", max_connections=16) as c:
|
|
84
|
+
replies = await asyncio.gather(*(c.get(f"/items/{i}") for i in range(100)))
|
|
85
|
+
|
|
86
|
+
Requests run on the C++ network thread (non-blocking sockets, Asio + llhttp): no thread per
|
|
87
|
+
request, connections are kept alive and reused, and at most `max_connections` requests are in
|
|
88
|
+
flight (extra ones wait their turn). The C++ counterpart is httpp::async_client.
|
|
89
|
+
|
|
90
|
+
`**client_options` are the same as for Client: headers, timeout, follow_redirects, auth, token,
|
|
91
|
+
verify, ca_file, proxy.
|
|
92
|
+
"""
|
|
93
|
+
|
|
94
|
+
def __init__(self, base_url, *, max_connections=8, **client_options):
|
|
95
|
+
if max_connections < 1:
|
|
96
|
+
raise ValueError("max_connections must be at least 1")
|
|
97
|
+
self._core = _AsyncClientCore(base_url, max_connections, **client_options) # validates the URL now
|
|
98
|
+
|
|
99
|
+
async def request(self, method, path, **kwargs):
|
|
100
|
+
return await self._core.request(method, path, **kwargs)
|
|
101
|
+
|
|
102
|
+
async def get(self, path, **kwargs):
|
|
103
|
+
return await self.request("GET", path, **kwargs)
|
|
104
|
+
|
|
105
|
+
async def head(self, path, **kwargs):
|
|
106
|
+
return await self.request("HEAD", path, **kwargs)
|
|
107
|
+
|
|
108
|
+
async def options(self, path, **kwargs):
|
|
109
|
+
return await self.request("OPTIONS", path, **kwargs)
|
|
110
|
+
|
|
111
|
+
async def post(self, path, **kwargs):
|
|
112
|
+
return await self.request("POST", path, **kwargs)
|
|
113
|
+
|
|
114
|
+
async def put(self, path, **kwargs):
|
|
115
|
+
return await self.request("PUT", path, **kwargs)
|
|
116
|
+
|
|
117
|
+
async def patch(self, path, **kwargs):
|
|
118
|
+
return await self.request("PATCH", path, **kwargs)
|
|
119
|
+
|
|
120
|
+
async def delete(self, path, **kwargs):
|
|
121
|
+
return await self.request("DELETE", path, **kwargs)
|
|
122
|
+
|
|
123
|
+
async def aclose(self):
|
|
124
|
+
pass # connections belong to the shared network thread's pool and close when idle too long
|
|
125
|
+
|
|
126
|
+
async def __aenter__(self):
|
|
127
|
+
return self
|
|
128
|
+
|
|
129
|
+
async def __aexit__(self, *exc):
|
|
130
|
+
await self.aclose()
|