pepe-http 0.35.1__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.
pepe_http/__init__.py ADDED
@@ -0,0 +1,186 @@
1
+ """pepe from PyPI.
2
+
3
+ pepe is a Rust binary; this package is the `pepe` command that fetches it.
4
+ The first run downloads the release archive of this package's version for
5
+ the running platform from GitHub, checks it against the SHA-256 the release
6
+ carries, keeps the binary in a per-user cache directory, and runs it with
7
+ the arguments given; every later run only runs it. Nothing but the standard
8
+ library is used.
9
+
10
+ PEPE_HTTP_CACHE_DIR where the binary is kept (default: the user's
11
+ cache directory, under pepe-http/)
12
+ """
13
+
14
+ import hashlib
15
+ import io
16
+ import os
17
+ import platform
18
+ import subprocess
19
+ import sys
20
+ import tarfile
21
+ import tempfile
22
+ import urllib.error
23
+ import urllib.request
24
+ import zipfile
25
+
26
+ # pepe's version: the release this package downloads. The publish job
27
+ # (the publish-pypi job in .github/workflows/release.yml) sets it from the release tag.
28
+ __version__ = "0.35.1"
29
+
30
+ REPO = "omarmhaimdat/pepe"
31
+ RELEASE_URL = "https://github.com/" + REPO + "/releases/download/v{version}/{asset}"
32
+ INSTALL_DOCS = "https://pepe.mhaimdat.com/docs/install.html"
33
+
34
+
35
+ class Problem(Exception):
36
+ """Something this package could not do; the message says what."""
37
+
38
+
39
+ def target():
40
+ """The release target for this machine: (triple, archive extension)."""
41
+ system = platform.system()
42
+ machine = platform.machine().lower()
43
+ arm = machine in ("arm64", "aarch64")
44
+ x86 = machine in ("x86_64", "amd64")
45
+ if system == "Darwin" and arm:
46
+ return "aarch64-apple-darwin", "tar.xz"
47
+ if system == "Darwin" and x86:
48
+ return "x86_64-apple-darwin", "tar.xz"
49
+ if system == "Linux" and x86:
50
+ return "x86_64-unknown-linux-musl", "tar.xz"
51
+ if system == "Linux" and arm:
52
+ return "aarch64-unknown-linux-musl", "tar.xz"
53
+ if system == "Windows" and (x86 or arm):
54
+ # Windows on ARM runs the x86_64 build through its emulation
55
+ return "x86_64-pc-windows-msvc", "zip"
56
+ raise Problem(
57
+ "there is no pepe build for %s %s; the other ways to install are at %s"
58
+ % (system, machine, INSTALL_DOCS)
59
+ )
60
+
61
+
62
+ def cache_dir():
63
+ """Where the binary is kept: the user's cache directory, as the platform
64
+ has it, unless PEPE_HTTP_CACHE_DIR says otherwise."""
65
+ override = os.environ.get("PEPE_HTTP_CACHE_DIR")
66
+ if override:
67
+ return override
68
+ home = os.path.expanduser("~")
69
+ if sys.platform == "win32":
70
+ base = os.environ.get("LOCALAPPDATA") or os.path.join(home, "AppData", "Local")
71
+ return os.path.join(base, "pepe-http", "Cache")
72
+ if sys.platform == "darwin":
73
+ return os.path.join(home, "Library", "Caches", "pepe-http")
74
+ base = os.environ.get("XDG_CACHE_HOME") or os.path.join(home, ".cache")
75
+ return os.path.join(base, "pepe-http")
76
+
77
+
78
+ def binary_path():
79
+ """The binary of this version for this machine, in the cache (it may not
80
+ be there yet)."""
81
+ triple, _ = target()
82
+ name = "pepe.exe" if sys.platform == "win32" else "pepe"
83
+ return os.path.join(cache_dir(), "v" + __version__, triple, name)
84
+
85
+
86
+ def fetch(url):
87
+ """The bytes at a URL, or a Problem that says why not."""
88
+ request = urllib.request.Request(url, headers={"User-Agent": "pepe-http/" + __version__})
89
+ try:
90
+ with urllib.request.urlopen(request, timeout=60) as response:
91
+ return response.read()
92
+ except urllib.error.HTTPError as error:
93
+ if error.code == 404:
94
+ raise Problem(
95
+ "release v%s has no %s; the releases are at https://github.com/%s/releases"
96
+ % (__version__, url.rsplit("/", 1)[-1], REPO)
97
+ )
98
+ raise Problem("could not download %s: HTTP %s" % (url, error.code))
99
+ except (urllib.error.URLError, OSError) as error:
100
+ raise Problem("could not download %s: %s" % (url, error.reason if hasattr(error, "reason") else error))
101
+
102
+
103
+ def verify(archive, checksum_file, asset):
104
+ """The archive's SHA-256 must be the one the release says."""
105
+ expected = checksum_file.decode("utf-8", "replace").split()
106
+ if not expected or len(expected[0]) != 64:
107
+ raise Problem("the checksum file for %s is not one: %r" % (asset, checksum_file[:80]))
108
+ actual = hashlib.sha256(archive).hexdigest()
109
+ if actual != expected[0].lower():
110
+ raise Problem(
111
+ "%s does not match its checksum (expected %s, got %s); not installing it"
112
+ % (asset, expected[0], actual)
113
+ )
114
+
115
+
116
+ def binary_from(archive, extension, name):
117
+ """The bytes of the binary inside a release archive. Only that member is
118
+ read; the archive's own paths are not used for anything."""
119
+ if extension == "zip":
120
+ with zipfile.ZipFile(io.BytesIO(archive)) as zf:
121
+ for member in zf.infolist():
122
+ if member.filename.rsplit("/", 1)[-1] == name and not member.is_dir():
123
+ return zf.read(member)
124
+ else:
125
+ with tarfile.open(fileobj=io.BytesIO(archive), mode="r:xz") as tf:
126
+ for member in tf:
127
+ if member.isfile() and member.name.rsplit("/", 1)[-1] == name:
128
+ return tf.extractfile(member).read()
129
+ raise Problem("the release archive has no %s in it" % name)
130
+
131
+
132
+ def download(path):
133
+ """Download, verify and keep this version's binary at `path`."""
134
+ triple, extension = target()
135
+ asset = "pepe-%s.%s" % (triple, extension)
136
+ url = RELEASE_URL.format(version=__version__, asset=asset)
137
+ sys.stderr.write("pepe-http: downloading pepe v%s for %s\n" % (__version__, triple))
138
+ sys.stderr.flush()
139
+ archive = fetch(url)
140
+ verify(archive, fetch(url + ".sha256"), asset)
141
+ binary = binary_from(archive, extension, os.path.basename(path))
142
+
143
+ # Written beside its place and moved in, so a run that starts meanwhile
144
+ # sees either nothing or the whole binary
145
+ directory = os.path.dirname(path)
146
+ os.makedirs(directory, exist_ok=True)
147
+ fd, temporary = tempfile.mkstemp(prefix=".pepe-", dir=directory)
148
+ try:
149
+ with os.fdopen(fd, "wb") as handle:
150
+ handle.write(binary)
151
+ os.chmod(temporary, 0o755)
152
+ os.replace(temporary, path)
153
+ except BaseException:
154
+ try:
155
+ os.unlink(temporary)
156
+ except OSError:
157
+ pass
158
+ raise
159
+
160
+
161
+ def ensure():
162
+ """The path of the binary, downloaded if it isn't there."""
163
+ path = binary_path()
164
+ if not os.path.isfile(path):
165
+ download(path)
166
+ return path
167
+
168
+
169
+ def main(argv=None):
170
+ """Run pepe with the arguments given (the process's own by default).
171
+ Returns pepe's exit code, or 1 when the binary could not be had."""
172
+ args = list(sys.argv[1:] if argv is None else argv)
173
+ try:
174
+ exe = ensure()
175
+ except Problem as problem:
176
+ sys.stderr.write("pepe-http: %s\n" % problem)
177
+ return 1
178
+ if os.name == "posix":
179
+ os.execv(exe, [exe] + args)
180
+ # Windows has no exec: run it and pass its exit code on. Ctrl+C reaches
181
+ # both processes; let pepe finish its own handling of it
182
+ process = subprocess.Popen([exe] + args)
183
+ try:
184
+ return process.wait()
185
+ except KeyboardInterrupt:
186
+ return process.wait()
pepe_http/__main__.py ADDED
@@ -0,0 +1,8 @@
1
+ """`python -m pepe_http ...` is `pepe ...`."""
2
+
3
+ import sys
4
+
5
+ from pepe_http import main
6
+
7
+ if __name__ == "__main__":
8
+ sys.exit(main())
@@ -0,0 +1,56 @@
1
+ Metadata-Version: 2.4
2
+ Name: pepe-http
3
+ Version: 0.35.1
4
+ Summary: HTTP load generator and latency tool: a load test, a ping split into phases, a ramp to the capacity, an OpenAPI spec, a flow, an access log
5
+ License-Expression: MIT
6
+ Project-URL: Homepage, https://pepe.mhaimdat.com
7
+ Project-URL: Documentation, https://pepe.mhaimdat.com/docs/
8
+ Project-URL: Source, https://github.com/omarmhaimdat/pepe
9
+ Project-URL: Changelog, https://github.com/omarmhaimdat/pepe/blob/master/CHANGELOG.md
10
+ Project-URL: Issues, https://github.com/omarmhaimdat/pepe/issues
11
+ Keywords: http,load-testing,benchmark,latency,ping,openapi,cli
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Environment :: Console
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: Operating System :: MacOS
16
+ Classifier: Operating System :: POSIX :: Linux
17
+ Classifier: Operating System :: Microsoft :: Windows
18
+ Classifier: Programming Language :: Python :: 3
19
+ Classifier: Programming Language :: Rust
20
+ Classifier: Topic :: Internet :: WWW/HTTP
21
+ Classifier: Topic :: Software Development :: Testing :: Traffic Generation
22
+ Classifier: Topic :: System :: Benchmark
23
+ Requires-Python: >=3.9
24
+ Description-Content-Type: text/markdown
25
+ License-File: LICENSE
26
+ Dynamic: license-file
27
+
28
+ # pepe
29
+
30
+ HTTP load generator and latency tool, one binary: a load test with a live
31
+ dashboard, `pepe ping` (a request a second, split into DNS, connect, TLS,
32
+ first byte and download), `pepe ramp` (find the capacity), `pepe api` (an
33
+ OpenAPI spec), `pepe flow` (a sequence of requests), `pepe replay` (an
34
+ access log), `pepe compare` (two reports). The documentation is at
35
+ [pepe.mhaimdat.com/docs](https://pepe.mhaimdat.com/docs/).
36
+
37
+ ```bash
38
+ pipx install pepe-http # or: pip install pepe-http
39
+ pepe https://example.com
40
+ ```
41
+
42
+ pepe is written in Rust, and this package is the `pepe` command that
43
+ fetches it: the first run downloads the release of the same version for
44
+ your platform (macOS, Linux, Windows) from
45
+ [GitHub releases](https://github.com/omarmhaimdat/pepe/releases), checks
46
+ it against the SHA-256 the release carries, keeps it in your cache
47
+ directory (`~/.cache/pepe-http`, `~/Library/Caches/pepe-http` on macOS,
48
+ `%LOCALAPPDATA%\pepe-http\Cache` on Windows; `PEPE_HTTP_CACHE_DIR` moves
49
+ it) and runs it; every later run only runs it. There are no dependencies.
50
+ `python -m pepe_http` is the same command.
51
+
52
+ Upgrade with `pipx upgrade pepe-http` or `pip install -U pepe-http`: each
53
+ version of the package runs the pepe of that version, so `pepe
54
+ self-update` does not apply here. The other ways to install, with tab
55
+ completion and man pages, are on the
56
+ [install page](https://pepe.mhaimdat.com/docs/install.html).
@@ -0,0 +1,8 @@
1
+ pepe_http/__init__.py,sha256=IjPRwQebiqA0OxgbWF_6H7X64THYyXlI3IrFgAVVtyE,7070
2
+ pepe_http/__main__.py,sha256=Umx5P_JXF_6I0dcqO4mrdyqX9HBLdeF4vXHyrmku440,136
3
+ pepe_http-0.35.1.dist-info/licenses/LICENSE,sha256=dTGZWKd99YwoKg8s1sbL_m2aVPeka3Juxy7Cwb6wMIk,1070
4
+ pepe_http-0.35.1.dist-info/METADATA,sha256=Zryvhu4Ez5XwJxyebgYr_kxK9sinPmGPshxyKWi_17o,2635
5
+ pepe_http-0.35.1.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
6
+ pepe_http-0.35.1.dist-info/entry_points.txt,sha256=YfITDeh2rm_AL9uJijlvJ73aLOG_Qh0GNkK-SxMyKec,40
7
+ pepe_http-0.35.1.dist-info/top_level.txt,sha256=PHPvJHFQUZgFhmKKyVnmWBuzHS-yLR4-asmnbL3C7g0,10
8
+ pepe_http-0.35.1.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (84.0.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ pepe = pepe_http:main
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Omar MHAIMDAT
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1 @@
1
+ pepe_http