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 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()