httpr 0.2.3.dev163__tar.gz → 0.2.6__tar.gz

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 (63) hide show
  1. {httpr-0.2.3.dev163 → httpr-0.2.6}/.github/workflows/CI.yml +21 -41
  2. {httpr-0.2.3.dev163 → httpr-0.2.6}/Cargo.lock +1 -1
  3. {httpr-0.2.3.dev163 → httpr-0.2.6}/Cargo.toml +1 -1
  4. httpr-0.2.6/PKG-INFO +418 -0
  5. {httpr-0.2.3.dev163 → httpr-0.2.6}/README.md +13 -13
  6. {httpr-0.2.3.dev163 → httpr-0.2.6}/docs/index.md +2 -2
  7. {httpr-0.2.3.dev163 → httpr-0.2.6}/pyproject.toml +2 -1
  8. httpr-0.2.3.dev163/PKG-INFO +0 -54
  9. {httpr-0.2.3.dev163 → httpr-0.2.6}/.github/actions/set-version/action.yml +0 -0
  10. {httpr-0.2.3.dev163 → httpr-0.2.6}/.github/copilot-instructions.md +0 -0
  11. {httpr-0.2.3.dev163 → httpr-0.2.6}/.github/workflows/CI.yml.backup +0 -0
  12. {httpr-0.2.3.dev163 → httpr-0.2.6}/.github/workflows/copilot-setup-steps.yml +0 -0
  13. {httpr-0.2.3.dev163 → httpr-0.2.6}/.github/workflows/mkdocs.yml +0 -0
  14. {httpr-0.2.3.dev163 → httpr-0.2.6}/.github/workflows/set_version.py +0 -0
  15. {httpr-0.2.3.dev163 → httpr-0.2.6}/.gitignore +0 -0
  16. {httpr-0.2.3.dev163 → httpr-0.2.6}/.pre-commit-config.yaml +0 -0
  17. {httpr-0.2.3.dev163 → httpr-0.2.6}/CLAUDE.md +0 -0
  18. {httpr-0.2.3.dev163 → httpr-0.2.6}/LICENSE +0 -0
  19. {httpr-0.2.3.dev163 → httpr-0.2.6}/benchmark/README.md +0 -0
  20. {httpr-0.2.3.dev163 → httpr-0.2.6}/benchmark/benchmark.py +0 -0
  21. {httpr-0.2.3.dev163 → httpr-0.2.6}/benchmark/benchmark_cbor.py +0 -0
  22. {httpr-0.2.3.dev163 → httpr-0.2.6}/benchmark/generate_image.py +0 -0
  23. {httpr-0.2.3.dev163 → httpr-0.2.6}/benchmark/server.py +0 -0
  24. {httpr-0.2.3.dev163 → httpr-0.2.6}/benchmark.jpg +0 -0
  25. {httpr-0.2.3.dev163 → httpr-0.2.6}/docs/advanced/cookies.md +0 -0
  26. {httpr-0.2.3.dev163 → httpr-0.2.6}/docs/advanced/index.md +0 -0
  27. {httpr-0.2.3.dev163 → httpr-0.2.6}/docs/advanced/proxy.md +0 -0
  28. {httpr-0.2.3.dev163 → httpr-0.2.6}/docs/advanced/ssl-tls.md +0 -0
  29. {httpr-0.2.3.dev163 → httpr-0.2.6}/docs/api/async-client.md +0 -0
  30. {httpr-0.2.3.dev163 → httpr-0.2.6}/docs/api/client.md +0 -0
  31. {httpr-0.2.3.dev163 → httpr-0.2.6}/docs/api/functions.md +0 -0
  32. {httpr-0.2.3.dev163 → httpr-0.2.6}/docs/api/index.md +0 -0
  33. {httpr-0.2.3.dev163 → httpr-0.2.6}/docs/api/response.md +0 -0
  34. {httpr-0.2.3.dev163 → httpr-0.2.6}/docs/quickstart.md +0 -0
  35. {httpr-0.2.3.dev163 → httpr-0.2.6}/docs/tutorial/async.md +0 -0
  36. {httpr-0.2.3.dev163 → httpr-0.2.6}/docs/tutorial/authentication.md +0 -0
  37. {httpr-0.2.3.dev163 → httpr-0.2.6}/docs/tutorial/index.md +0 -0
  38. {httpr-0.2.3.dev163 → httpr-0.2.6}/docs/tutorial/making-requests.md +0 -0
  39. {httpr-0.2.3.dev163 → httpr-0.2.6}/docs/tutorial/response-handling.md +0 -0
  40. {httpr-0.2.3.dev163 → httpr-0.2.6}/docs/writings/index.md +0 -0
  41. {httpr-0.2.3.dev163 → httpr-0.2.6}/docs/writings/posts/2025-02-24-python-http-clients-suck.md +0 -0
  42. {httpr-0.2.3.dev163 → httpr-0.2.6}/httpr/__init__.py +0 -0
  43. {httpr-0.2.3.dev163 → httpr-0.2.6}/httpr/httpr.pyi +0 -0
  44. {httpr-0.2.3.dev163 → httpr-0.2.6}/httpr/py.typed +0 -0
  45. {httpr-0.2.3.dev163 → httpr-0.2.6}/httpr.code-workspace +0 -0
  46. {httpr-0.2.3.dev163 → httpr-0.2.6}/mkdocs.yml +0 -0
  47. {httpr-0.2.3.dev163 → httpr-0.2.6}/src/exceptions.rs +0 -0
  48. {httpr-0.2.3.dev163 → httpr-0.2.6}/src/lib.rs +0 -0
  49. {httpr-0.2.3.dev163 → httpr-0.2.6}/src/response.rs +0 -0
  50. {httpr-0.2.3.dev163 → httpr-0.2.6}/src/traits.rs +0 -0
  51. {httpr-0.2.3.dev163 → httpr-0.2.6}/src/utils.rs +0 -0
  52. {httpr-0.2.3.dev163 → httpr-0.2.6}/tests/cbor_test_server.py +0 -0
  53. {httpr-0.2.3.dev163 → httpr-0.2.6}/tests/conftest.py +0 -0
  54. {httpr-0.2.3.dev163 → httpr-0.2.6}/tests/httpx_conns.py +0 -0
  55. {httpr-0.2.3.dev163 → httpr-0.2.6}/tests/test_asyncclient.py +0 -0
  56. {httpr-0.2.3.dev163 → httpr-0.2.6}/tests/test_cbor.py +0 -0
  57. {httpr-0.2.3.dev163 → httpr-0.2.6}/tests/test_client.py +0 -0
  58. {httpr-0.2.3.dev163 → httpr-0.2.6}/tests/test_defs.py +0 -0
  59. {httpr-0.2.3.dev163 → httpr-0.2.6}/tests/test_docs.py +0 -0
  60. {httpr-0.2.3.dev163 → httpr-0.2.6}/tests/test_exceptions.py +0 -0
  61. {httpr-0.2.3.dev163 → httpr-0.2.6}/tests/test_ssl.py +0 -0
  62. {httpr-0.2.3.dev163 → httpr-0.2.6}/tests/test_streaming.py +0 -0
  63. {httpr-0.2.3.dev163 → httpr-0.2.6}/uv.lock +0 -0
@@ -7,9 +7,6 @@ on:
7
7
  tags:
8
8
  - "*"
9
9
  pull_request:
10
- schedule:
11
- # Run weekly on Sundays at midnight UTC
12
- - cron: "0 0 * * 0"
13
10
  workflow_dispatch:
14
11
 
15
12
  permissions:
@@ -39,10 +36,10 @@ jobs:
39
36
  - name: Rust clippy
40
37
  run: cargo clippy -- -D warnings
41
38
 
42
- # Fast test job - runs on PRs and main branch pushes with Python version matrix
39
+ # Fast test job - runs on PRs, main branch pushes, and tag pushes with Python version matrix
43
40
  test:
44
41
  runs-on: ubuntu-22.04
45
- if: github.event_name == 'pull_request' || (github.event_name == 'push' && github.ref == 'refs/heads/main')
42
+ if: github.event_name == 'pull_request' || (github.event_name == 'push' && (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/')))
46
43
  strategy:
47
44
  matrix:
48
45
  python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
@@ -80,13 +77,11 @@ jobs:
80
77
  uv sync --extra docs
81
78
  uv run mkdocs build --strict
82
79
 
83
- # Build jobs - run on tags, main branch pushes, weekly schedule, or manual dispatch
80
+ # Build jobs - run only on tags or manual dispatch
84
81
  linux:
85
82
  runs-on: ${{ matrix.platform.runner }}
86
- # Wait for test job if it runs, but don't fail if it was skipped (schedule/tag/dispatch)
87
83
  needs: [test]
88
- # Always run unless test job explicitly failed
89
- if: ${{ always() && github.event_name != 'pull_request' && (needs.test.result == 'success' || needs.test.result == 'skipped') }}
84
+ if: ${{ always() && (startsWith(github.ref, 'refs/tags/') || github.event_name == 'workflow_dispatch') && (needs.test.result == 'success' || needs.test.result == 'skipped') }}
90
85
  strategy:
91
86
  matrix:
92
87
  platform:
@@ -140,27 +135,13 @@ jobs:
140
135
  set -e
141
136
  uv sync --extra dev --find-links dist --reinstall
142
137
  uv run pytest
143
- - name: pytest
144
- if: ${{ matrix.platform.target == 'armv7' }}
145
- uses: uraimo/run-on-arch-action@v2
146
- with:
147
- arch: ${{ matrix.platform.target }}
148
- distro: ubuntu22.04
149
- githubToken: ${{ github.token }}
150
- install: |
151
- apt-get update
152
- apt-get install -y --no-install-recommends python3 python3-pip python3-dev build-essential libffi-dev libssl-dev pkg-config curl
153
- curl -LsSf https://astral.sh/uv/install.sh | sh
154
- run: |
155
- set -e
156
- export PATH="$HOME/.local/bin:$PATH"
157
- uv sync --extra dev --find-links dist --reinstall
158
- uv run pytest
138
+ # Skip pytest on armv7 - QEMU emulation is 20x+ slower than native.
139
+ # Tests already run on x86_64 and aarch64 which provides sufficient coverage.
159
140
 
160
141
  musllinux:
161
142
  runs-on: ${{ matrix.platform.runner }}
162
143
  needs: [test]
163
- if: ${{ always() && github.event_name != 'pull_request' && (needs.test.result == 'success' || needs.test.result == 'skipped') }}
144
+ if: ${{ always() && (startsWith(github.ref, 'refs/tags/') || github.event_name == 'workflow_dispatch') && (needs.test.result == 'success' || needs.test.result == 'skipped') }}
164
145
  strategy:
165
146
  matrix:
166
147
  platform:
@@ -218,7 +199,7 @@ jobs:
218
199
  windows:
219
200
  runs-on: ${{ matrix.platform.runner }}
220
201
  needs: [test]
221
- if: ${{ always() && github.event_name != 'pull_request' && (needs.test.result == 'success' || needs.test.result == 'skipped') }}
202
+ if: ${{ always() && (startsWith(github.ref, 'refs/tags/') || github.event_name == 'workflow_dispatch') && (needs.test.result == 'success' || needs.test.result == 'skipped') }}
222
203
  strategy:
223
204
  matrix:
224
205
  platform:
@@ -269,7 +250,7 @@ jobs:
269
250
  macos:
270
251
  runs-on: ${{ matrix.platform.runner }}
271
252
  needs: [test]
272
- if: ${{ always() && github.event_name != 'pull_request' && (needs.test.result == 'success' || needs.test.result == 'skipped') }}
253
+ if: ${{ always() && (startsWith(github.ref, 'refs/tags/') || github.event_name == 'workflow_dispatch') && (needs.test.result == 'success' || needs.test.result == 'skipped') }}
273
254
  strategy:
274
255
  matrix:
275
256
  platform:
@@ -315,7 +296,7 @@ jobs:
315
296
  sdist:
316
297
  runs-on: ubuntu-latest
317
298
  needs: [test]
318
- if: ${{ always() && github.event_name != 'pull_request' && (needs.test.result == 'success' || needs.test.result == 'skipped') }}
299
+ if: ${{ always() && (startsWith(github.ref, 'refs/tags/') || github.event_name == 'workflow_dispatch') && (needs.test.result == 'success' || needs.test.result == 'skipped') }}
319
300
  steps:
320
301
  - uses: actions/checkout@v4
321
302
  - uses: astral-sh/setup-uv@v7
@@ -341,8 +322,8 @@ jobs:
341
322
  name: Release
342
323
  environment: pypi
343
324
  runs-on: ubuntu-latest
344
- # Release on tag pushes, main branch pushes, or manual dispatch
345
- if: ${{ startsWith(github.ref, 'refs/tags/') || (github.event_name == 'push' && github.ref == 'refs/heads/main') || github.event_name == 'workflow_dispatch' }}
325
+ # Release only on tag pushes or manual dispatch
326
+ if: ${{ startsWith(github.ref, 'refs/tags/') || github.event_name == 'workflow_dispatch' }}
346
327
  needs: [linux, musllinux, windows, macos, sdist]
347
328
  permissions:
348
329
  # Use to sign the release artifacts
@@ -358,8 +339,8 @@ jobs:
358
339
  with:
359
340
  subject-path: "wheels-*/*"
360
341
  - name: Publish to PyPI
361
- # Publish on tag pushes (stable releases), main branch pushes (dev releases), and manual dispatch
362
- if: ${{ startsWith(github.ref, 'refs/tags/') || (github.event_name == 'push' && github.ref == 'refs/heads/main') || github.event_name == 'workflow_dispatch' }}
342
+ # Publish on tag pushes or manual dispatch
343
+ if: ${{ startsWith(github.ref, 'refs/tags/') || github.event_name == 'workflow_dispatch' }}
363
344
  uses: PyO3/maturin-action@v1
364
345
  env:
365
346
  MATURIN_PYPI_TOKEN: ${{ secrets.PYPI_API_TOKEN }}
@@ -372,6 +353,7 @@ jobs:
372
353
  contents: write
373
354
  runs-on: ubuntu-latest
374
355
  needs: [linux]
356
+ if: ${{ startsWith(github.ref, 'refs/tags/') || github.event_name == 'workflow_dispatch' }}
375
357
  steps:
376
358
  - uses: actions/checkout@v4
377
359
  with:
@@ -393,20 +375,18 @@ jobs:
393
375
  - name: Run benchmark
394
376
  run: uv run python benchmark/benchmark.py
395
377
  - name: Generate and commit benchmark image
396
- if: startsWith(github.ref, 'refs/tags/') || github.event_name == 'schedule'
378
+ if: startsWith(github.ref, 'refs/tags/')
397
379
  run: |
398
380
  uv run python benchmark/generate_image.py
399
381
  git config --global user.name 'github-actions[bot]'
400
382
  git config --global user.email '41898282+github-actions[bot]@users.noreply.github.com'
383
+ # Checkout main branch to avoid detached HEAD issues
384
+ git fetch origin main
385
+ git checkout main
401
386
  git add *.jpg
402
387
  if ! git diff --quiet --cached; then
403
- if [ "${{ github.event_name }}" = "schedule" ]; then
404
- git commit -m "Update benchmark image (weekly)"
405
- else
406
- git commit -m "Update benchmark image for ${{ github.ref_name }}"
407
- fi
408
- # Push to main branch explicitly (handles detached HEAD state from tag checkout)
409
- git push origin HEAD:main
388
+ git commit -m "Update benchmark image for ${{ github.ref_name }}"
389
+ git push origin main
410
390
  else
411
391
  echo "No changes to commit"
412
392
  fi
@@ -470,7 +470,7 @@ checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87"
470
470
 
471
471
  [[package]]
472
472
  name = "httpr"
473
- version = "0.2.3"
473
+ version = "0.2.6"
474
474
  dependencies = [
475
475
  "anyhow",
476
476
  "bytes",
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "httpr"
3
- version = "0.2.3"
3
+ version = "0.2.6"
4
4
  edition = "2021"
5
5
  description = "Fast HTTP client for python"
6
6
  authors = ["thomasht86"]
httpr-0.2.6/PKG-INFO ADDED
@@ -0,0 +1,418 @@
1
+ Metadata-Version: 2.4
2
+ Name: httpr
3
+ Version: 0.2.6
4
+ Classifier: Programming Language :: Rust
5
+ Classifier: Programming Language :: Python :: 3
6
+ Classifier: Programming Language :: Python :: 3 :: Only
7
+ Classifier: Programming Language :: Python :: 3.10
8
+ Classifier: Programming Language :: Python :: 3.11
9
+ Classifier: Programming Language :: Python :: 3.12
10
+ Classifier: Programming Language :: Python :: 3.13
11
+ Classifier: Programming Language :: Python :: Implementation :: CPython
12
+ Classifier: Programming Language :: Python :: Implementation :: PyPy
13
+ Classifier: Topic :: Internet :: WWW/HTTP
14
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
15
+ Requires-Dist: certifi ; extra == 'dev'
16
+ Requires-Dist: pytest>=8.1.1 ; extra == 'dev'
17
+ Requires-Dist: pytest-asyncio>=0.25.3 ; extra == 'dev'
18
+ Requires-Dist: pytest-httpbin>=2.1.0 ; extra == 'dev'
19
+ Requires-Dist: typing-extensions ; python_full_version < '3.12' and extra == 'dev'
20
+ Requires-Dist: mypy>=1.14.1 ; extra == 'dev'
21
+ Requires-Dist: ruff>=0.9.2 ; extra == 'dev'
22
+ Requires-Dist: maturin ; extra == 'dev'
23
+ Requires-Dist: trustme ; extra == 'dev'
24
+ Requires-Dist: cbor2 ; extra == 'dev'
25
+ Requires-Dist: mkdocs-material[imaging] ; extra == 'docs'
26
+ Requires-Dist: mkdocstrings[python]>=0.27.0 ; extra == 'docs'
27
+ Requires-Dist: mkdocs-gen-files ; extra == 'docs'
28
+ Requires-Dist: mkdocs-literate-nav ; extra == 'docs'
29
+ Requires-Dist: mkdocs-llmstxt ; extra == 'docs'
30
+ Requires-Dist: matplotlib ; extra == 'benchmark'
31
+ Requires-Dist: pandas ; extra == 'benchmark'
32
+ Requires-Dist: starlette ; extra == 'benchmark'
33
+ Requires-Dist: uvicorn ; extra == 'benchmark'
34
+ Requires-Dist: requests ; extra == 'benchmark'
35
+ Requires-Dist: httpx ; extra == 'benchmark'
36
+ Requires-Dist: tls-client ; extra == 'benchmark'
37
+ Requires-Dist: curl-cffi ; extra == 'benchmark'
38
+ Requires-Dist: pycurl ; extra == 'benchmark'
39
+ Requires-Dist: typing-extensions ; extra == 'benchmark'
40
+ Requires-Dist: aiohttp ; extra == 'benchmark'
41
+ Requires-Dist: cbor2 ; extra == 'benchmark'
42
+ Requires-Dist: jupyter ; extra == 'benchmark'
43
+ Requires-Dist: ipykernel ; extra == 'benchmark'
44
+ Requires-Dist: gunicorn ; extra == 'benchmark'
45
+ Requires-Dist: fastapi ; extra == 'benchmark'
46
+ Provides-Extra: dev
47
+ Provides-Extra: docs
48
+ Provides-Extra: benchmark
49
+ License-File: LICENSE
50
+ Summary: Fast HTTP client for Python
51
+ Keywords: python,request
52
+ Author: thomasht86
53
+ License: MIT License
54
+ Requires-Python: >=3.10
55
+ Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
56
+
57
+ # httpr
58
+
59
+ **Blazing fast http-client** for Python in Rust 🦀 that can be used as drop-in replacement for `httpx` and `requests` in most cases.
60
+
61
+ - **Fast**: `httpr` is built on top of `reqwests`, which is a blazing fast http client in Rust. Check out the [benchmark](#benchmark).
62
+ - **Both async and sync**: `httpr` provides both a sync and async client.
63
+ - **Lightweight**: `httpr` is a lightweight http client with zero python-dependencies.
64
+ - **Async**: first-class async support.
65
+ - **Streaming**: supports streaming responses for efficient memory usage with large payloads.
66
+ - **http2**: `httpr` supports HTTP/2.
67
+ - **mTLS**: `httpr` supports mTLS.
68
+
69
+ ## Not implemented yet
70
+
71
+ - **Fine-grained error handling**: Fine-grained error handling is not implemented yet.
72
+
73
+ ## Documentation
74
+
75
+ 📖 **Full documentation**: [thomasht86.github.io/httpr](https://thomasht86.github.io/httpr)
76
+
77
+ 🤖 **LLM-friendly docs**: [llms.txt](https://thomasht86.github.io/httpr/llms.txt) | [llms-full.txt](https://thomasht86.github.io/httpr/llms-full.txt)
78
+
79
+ ## Table of Contents
80
+
81
+ - [httpr](#httpr)
82
+ - [Not implemented yet](#not-implemented-yet)
83
+ - [Documentation](#documentation)
84
+ - [Table of Contents](#table-of-contents)
85
+ - [Installation](#installation)
86
+ - [Install with uv](#install-with-uv)
87
+ - [Install from PyPI](#install-from-pypi)
88
+ - [Benchmark](#benchmark)
89
+ - [Usage](#usage)
90
+ - [I. Client](#i-client)
91
+ - [Client methods](#client-methods)
92
+ - [Response object](#response-object)
93
+ - [Streaming responses](#streaming-responses)
94
+ - [Examples](#examples)
95
+ - [II. AsyncClient](#ii-asyncclient)
96
+ - [Precompiled wheels](#precompiled-wheels)
97
+ - [CI](#ci)
98
+ - [Acknowledgements](#acknowledgements)
99
+
100
+ ## Installation
101
+
102
+ ### Install with uv
103
+
104
+ ```python
105
+ uv add httpr
106
+ ```
107
+
108
+ or
109
+
110
+ ```python
111
+ uv pip install httpr
112
+ ```
113
+
114
+ ### Install from PyPI
115
+
116
+ ```python
117
+ pip install -U httpr
118
+ ```
119
+
120
+ ## Benchmark
121
+
122
+ ![](https://github.com/thomasht86/httpr/blob/main/benchmark.jpg?raw=true)
123
+
124
+ ## Usage
125
+
126
+ ### I. Client
127
+
128
+ ```python
129
+ class Client:
130
+ """Initializes an HTTP client.
131
+
132
+ Args:
133
+ auth (tuple[str, str| None] | None): Username and password for basic authentication. Default is None.
134
+ auth_bearer (str | None): Bearer token for authentication. Default is None.
135
+ params (dict[str, str] | None): Default query parameters to include in all requests. Default is None.
136
+ headers (dict[str, str] | None): Default headers to send with requests.
137
+ cookies (dict[str, str] | None): - Map of cookies to send with requests as the `Cookie` header.
138
+ timeout (float | None): HTTP request timeout in seconds. Default is 30.
139
+ cookie_store (bool | None): Enable a persistent cookie store. Received cookies will be preserved and included
140
+ in additional requests. Default is True.
141
+ referer (bool | None): Enable or disable automatic setting of the `Referer` header. Default is True.
142
+ proxy (str | None): Proxy URL for HTTP requests. Example: "socks5://127.0.0.1:9150". Default is None.
143
+ follow_redirects (bool | None): Whether to follow redirects. Default is True.
144
+ max_redirects (int | None): Maximum redirects to follow. Default 20. Applies if `follow_redirects` is True.
145
+ verify (bool | None): Verify SSL certificates. Default is True.
146
+ ca_cert_file (str | None): Path to CA certificate store. Default is None.
147
+ https_only` (bool | None): Restrict the Client to be used with HTTPS only requests. Default is `false`.
148
+ http2_only` (bool | None): If true - use only HTTP/2; if false - use only HTTP/1. Default is `false`.
149
+
150
+ """
151
+ ```
152
+
153
+ #### Client methods
154
+
155
+ The `Client` class provides a set of methods for making HTTP requests: `get`, `head`, `options`, `delete`, `post`, `put`, `patch`, each of which internally utilizes the `request()` method for execution. The parameters for these methods closely resemble those in `httpx`.
156
+ ```python
157
+ def get(
158
+ url: str,
159
+ params: dict[str, str] | None = None,
160
+ headers: dict[str, str] | None = None,
161
+ cookies: dict[str, str] | None = None,
162
+ auth: tuple[str, str| None] | None = None,
163
+ auth_bearer: str | None = None,
164
+ timeout: float | None = 30,
165
+ ):
166
+ """Performs a GET request to the specified URL.
167
+
168
+ Args:
169
+ url (str): The URL to which the request will be made.
170
+ params (dict[str, str] | None): A map of query parameters to append to the URL. Default is None.
171
+ headers (dict[str, str] | None): A map of HTTP headers to send with the request. Default is None.
172
+ cookies (dict[str, str] | None): - An optional map of cookies to send with requests as the `Cookie` header.
173
+ auth (tuple[str, str| None] | None): A tuple containing the username and an optional password
174
+ for basic authentication. Default is None.
175
+ auth_bearer (str | None): A string representing the bearer token for bearer token authentication. Default is None.
176
+ timeout (float | None): The timeout for the request in seconds. Default is 30.
177
+
178
+ """
179
+ ```
180
+ ```python
181
+ def post(
182
+ url: str,
183
+ params: dict[str, str] | None = None,
184
+ headers: dict[str, str] | None = None,
185
+ cookies: dict[str, str] | None = None,
186
+ content: bytes | None = None,
187
+ data: dict[str, Any] | None = None,
188
+ json: Any | None = None,
189
+ files: dict[str, str] | None = None,
190
+ auth: tuple[str, str| None] | None = None,
191
+ auth_bearer: str | None = None,
192
+ timeout: float | None = 30,
193
+ ):
194
+ """Performs a POST request to the specified URL.
195
+
196
+ Args:
197
+ url (str): The URL to which the request will be made.
198
+ params (dict[str, str] | None): A map of query parameters to append to the URL. Default is None.
199
+ headers (dict[str, str] | None): A map of HTTP headers to send with the request. Default is None.
200
+ cookies (dict[str, str] | None): - An optional map of cookies to send with requests as the `Cookie` header.
201
+ content (bytes | None): The content to send in the request body as bytes. Default is None.
202
+ data (dict[str, Any] | None): The form data to send in the request body. Default is None.
203
+ json (Any | None): A JSON serializable object to send in the request body. Default is None.
204
+ files (dict[str, str] | None): A map of file fields to file paths to be sent as multipart/form-data. Default is None.
205
+ auth (tuple[str, str| None] | None): A tuple containing the username and an optional password
206
+ for basic authentication. Default is None.
207
+ auth_bearer (str | None): A string representing the bearer token for bearer token authentication. Default is None.
208
+ timeout (float | None): The timeout for the request in seconds. Default is 30.
209
+
210
+ """
211
+ ```
212
+
213
+ #### Response object
214
+
215
+ The `Client` class returns a `Response` object that contains the following attributes and methods:
216
+
217
+ ```python
218
+ resp.content
219
+ resp.cookies
220
+ resp.encoding
221
+ resp.headers
222
+ resp.json()
223
+ resp.status_code
224
+ resp.text
225
+ resp.text_markdown # html is converted to markdown text using html2text-rs
226
+ resp.text_plain # html is converted to plain text
227
+ resp.text_rich # html is converted to rich text
228
+ resp.url
229
+ ```
230
+
231
+ #### Streaming responses
232
+
233
+ The `Client` class supports streaming responses for efficient memory usage when handling large payloads. Use the `stream()` context manager to iterate over response data without buffering the entire response in memory.
234
+
235
+ ```python
236
+ # Stream bytes chunks
237
+ with client.stream("GET", "https://example.com/large-file") as response:
238
+ print(f"Status: {response.status_code}")
239
+ for chunk in response.iter_bytes():
240
+ process(chunk)
241
+
242
+ # Stream text chunks
243
+ with client.stream("GET", "https://example.com/text") as response:
244
+ for text in response.iter_text():
245
+ print(text, end="")
246
+
247
+ # Stream line by line (useful for Server-Sent Events)
248
+ with client.stream("GET", "https://example.com/events") as response:
249
+ for line in response.iter_lines():
250
+ print(line.strip())
251
+
252
+ # Read entire response (if needed after checking headers)
253
+ with client.stream("GET", url) as response:
254
+ if response.status_code == 200:
255
+ content = response.read()
256
+ ```
257
+
258
+ **StreamingResponse attributes:**
259
+ - `status_code` - HTTP status code
260
+ - `headers` - Response headers (case-insensitive)
261
+ - `cookies` - Response cookies
262
+ - `url` - Final URL after redirects
263
+ - `is_closed` - Whether the stream has been closed
264
+ - `is_consumed` - Whether the stream has been fully consumed
265
+
266
+ **StreamingResponse methods:**
267
+ - `iter_bytes()` - Iterate over response as bytes chunks
268
+ - `iter_text()` - Iterate over response as text chunks (decoded using response encoding)
269
+ - `iter_lines()` - Iterate over response line by line
270
+ - `read()` - Read entire remaining response body into memory
271
+ - `close()` - Close the stream and release resources
272
+
273
+ **Important notes:**
274
+ - Streaming must be used as a context manager (with statement)
275
+ - Headers, cookies, and status code are available immediately before reading the body
276
+ - The response body is only read when you iterate over it or call `read()`
277
+ - Once consumed, the stream cannot be read again
278
+ - Streaming is supported for all HTTP methods (GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS)
279
+
280
+ #### Examples
281
+
282
+ ```python
283
+ import httpr
284
+
285
+ # Initialize the client
286
+ client = httpr.Client()
287
+
288
+ # GET request
289
+ resp = client.get("https://tls.peet.ws/api/all")
290
+ print(resp.json())
291
+
292
+ # GET request with passing params and setting timeout
293
+ params = {"param1": "value1", "param2": "value2"}
294
+ resp = client.post(url="https://httpbin.org/anything", params=params, timeout=10)
295
+ print(r.text)
296
+
297
+ # POST Binary Request Data
298
+ content = b"some_data"
299
+ resp = client.post(url="https://httpbin.org/anything", content=content)
300
+ print(r.text)
301
+
302
+ # POST Form Encoded Data
303
+ data = {"key1": "value1", "key2": "value2"}
304
+ resp = client.post(url="https://httpbin.org/anything", data=data)
305
+ print(r.text)
306
+
307
+ # POST JSON Encoded Data
308
+ json = {"key1": "value1", "key2": "value2"}
309
+ resp = client.post(url="https://httpbin.org/anything", json=json)
310
+ print(r.text)
311
+
312
+ # POST Multipart-Encoded Files
313
+ files = {'file1': '/home/root/file1.txt', 'file2': 'home/root/file2.txt'}
314
+ r = client.post("https://httpbin.org/post", files=files)
315
+ print(r.text)
316
+
317
+ # Authentication using user/password
318
+ auth = ("user", "password")
319
+ resp = client.post(url="https://httpbin.org/anything", auth=auth)
320
+ print(r.text)
321
+
322
+ # Authentication using auth bearer
323
+ auth_bearer = "bearerXXXXXXXXXXXXXXXXXXXX"
324
+ resp = client.post(url="https://httpbin.org/anything", auth_bearer=auth_bearer)
325
+ print(r.text)
326
+
327
+ # Using proxy or env var HTTPR_PROXY
328
+ resp = httpr.Client(proxy="http://127.0.0.1:8080").get("https://tls.peet.ws/api/all")
329
+ print(resp.json())
330
+ export HTTPR_PROXY="socks5://127.0.0.1:1080"
331
+ resp = httpr.Client().get("https://tls.peet.ws/api/all")
332
+ print(resp.json())
333
+
334
+ # Using custom CA certificate store: env var HTTPR_CA_BUNDLE
335
+ resp = httpr.Client(ca_cert_file="/cert/cacert.pem").get("https://tls.peet.ws/api/all")
336
+ print(resp.json())
337
+ resp = httpr.Client(ca_cert_file=certifi.where()).get("https://tls.peet.ws/api/all")
338
+ print(resp.json())
339
+ export HTTPR_CA_BUNDLE="/home/user/Downloads/cert.pem"
340
+ resp = httpr.Client().get("https://tls.peet.ws/api/all")
341
+ print(resp.json())
342
+
343
+ # You can also use convenience functions that use a default Client instance under the hood:
344
+ # httpr.get() | httpr.head() | httpr.options() | httpr.delete() | httpr.post() | httpr.patch() | httpr.put()
345
+ resp = httpr.get("https://httpbin.org/anything")
346
+ print(r.text)
347
+ ```
348
+
349
+ ### II. AsyncClient
350
+
351
+ `httpr.AsyncClient()` is an asynchronous wrapper around the `httpr.Client` class, offering the same functions, behavior, and input arguments.
352
+
353
+ ```python
354
+ import asyncio
355
+ import logging
356
+
357
+ import httpr
358
+
359
+ async def aget_text(url):
360
+ async with httpr.AsyncClient() as client:
361
+ resp = await client.get(url)
362
+ return resp.text
363
+
364
+ async def main():
365
+ urls = ["https://nytimes.com/", "https://cnn.com/", "https://abcnews.go.com/"]
366
+ tasks = [aget_text(u) for u in urls]
367
+ results = await asyncio.gather(*tasks)
368
+
369
+ if __name__ == "__main__":
370
+ logging.basicConfig(level=logging.INFO)
371
+ asyncio.run(main())
372
+ ```
373
+
374
+ **Streaming with AsyncClient:**
375
+
376
+ The `AsyncClient` also supports streaming responses with the same API:
377
+
378
+ ```python
379
+ async with httpr.AsyncClient() as client:
380
+ async with client.stream("GET", "https://example.com/large-file") as response:
381
+ for chunk in response.iter_bytes():
382
+ process(chunk)
383
+ ```
384
+
385
+ Note: While the context manager is async, the iteration over chunks (`iter_bytes()`, `iter_text()`, `iter_lines()`) is synchronous.
386
+
387
+ ## Precompiled wheels
388
+
389
+ Provides precompiled wheels for the following platforms:
390
+
391
+ - 🐧 linux: `amd64`, `aarch64`, `armv7` (aarch64 and armv7 builds are `manylinux_2_34` compatible. `ubuntu>=22.04`, `debian>=12`)
392
+ - 🐧 musllinux: `amd64`, `aarch64`
393
+ - 🪟 windows: `amd64`
394
+ - 🍏 macos: `amd64`, `aarch64`.
395
+
396
+ ## CI
397
+
398
+ | Job | PRs | Push to main | Tags (Release) | Manual |
399
+ |-----|:---:|:------------:|:--------------:|:------:|
400
+ | `lint` | ✓ | | | |
401
+ | `test` (Python 3.10-3.14) | ✓ | ✓ | ✓ | |
402
+ | `docs` (build) | ✓ | | | |
403
+ | `linux`, `musllinux`, `windows`, `macos`, `sdist` | | | ✓ | ✓ |
404
+ | `release` (PyPI publish) | | | ✓ | ✓ |
405
+ | `benchmark` | | | ✓ | ✓ |
406
+
407
+ - **PRs**: Run lint, tests across Python 3.10-3.14 matrix, and verify docs build
408
+ - **Push to main**: Run tests only
409
+ - **Tags**: Run tests, build wheels, publish stable release to PyPI, run benchmarks
410
+ - **Manual**: Full multi-platform wheel builds with release and benchmarks
411
+
412
+ ## Acknowledgements
413
+
414
+ - [uv](https://docs.astral.sh/uv/): The package manager used, and for leading the way in the "Rust for python tools"-sphere.
415
+ - [primp](https://github.com/deedy5/primp): *A lot* of code is borrowed from primp, that wraps rust library `rquest` for python in a similar way. If primp supported mTLS, I would have used it instead.
416
+ - [reqwests](https://github.com/seanmonstar/reqwest): The rust library that powers httpr.
417
+ - [pyo3](https://github.com/PyO3/pyo3)
418
+ - [maturin](https://github.com/PyO3/maturin)
@@ -339,19 +339,19 @@ Provides precompiled wheels for the following platforms:
339
339
 
340
340
  ## CI
341
341
 
342
- | Job | PRs | Push to main | Tags (Release) | Weekly (Sunday) | Manual |
343
- |-----|:---:|:------------:|:--------------:|:---------------:|:------:|
344
- | `test` (Python 3.10-3.14) | ✓ | | | | |
345
- | `docs` (build) | ✓ | | | | |
346
- | `docs` (deploy) | | ✓ | | | |
347
- | `linux`, `musllinux`, `windows`, `macos`, `sdist` | | | ✓ | ✓ | ✓ |
348
- | `release` (PyPI publish) | | ✓ (dev) | ✓ (stable) | | ✓ |
349
- | `benchmark` | | | ✓ | ✓ | ✓ |
350
-
351
- - **PRs**: Run tests across Python 3.10-3.14 matrix and verify docs build
352
- - **Push to main**: Run tests, build wheels, publish dev release to PyPI (e.g., `0.1.0.dev42`)
353
- - **Tags**: Build wheels, publish stable release to PyPI (e.g., `0.2.0`)
354
- - **Weekly/Manual**: Full multi-platform wheel builds with benchmarks
342
+ | Job | PRs | Push to main | Tags (Release) | Manual |
343
+ |-----|:---:|:------------:|:--------------:|:------:|
344
+ | `lint` | ✓ | | | |
345
+ | `test` (Python 3.10-3.14) | ✓ | | | |
346
+ | `docs` (build) | ✓ | | | |
347
+ | `linux`, `musllinux`, `windows`, `macos`, `sdist` | | | ✓ | ✓ |
348
+ | `release` (PyPI publish) | | | ✓ | ✓ |
349
+ | `benchmark` | | | ✓ | ✓ |
350
+
351
+ - **PRs**: Run lint, tests across Python 3.10-3.14 matrix, and verify docs build
352
+ - **Push to main**: Run tests only
353
+ - **Tags**: Run tests, build wheels, publish stable release to PyPI, run benchmarks
354
+ - **Manual**: Full multi-platform wheel builds with release and benchmarks
355
355
 
356
356
  ## Acknowledgements
357
357
 
@@ -149,8 +149,8 @@ print(response.cookies) # Response cookies
149
149
 
150
150
  This documentation is available in LLM-optimized formats:
151
151
 
152
- - **[llms.txt](llms.txt)** - Documentation index for LLMs
153
- - **[llms-full.txt](llms-full.txt)** - Complete documentation in a single file
152
+ - **[llms.txt](https://thomasht86.github.io/httpr/llms.txt)** - Documentation index for LLMs
153
+ - **[llms-full.txt](https://thomasht86.github.io/httpr/llms-full.txt)** - Complete documentation in a single file
154
154
 
155
155
  ---
156
156
 
@@ -5,10 +5,11 @@ build-backend = "maturin"
5
5
  [project]
6
6
  name = "httpr"
7
7
  description = "Fast HTTP client for Python"
8
+ readme = "README.md"
8
9
  requires-python = ">=3.10"
9
10
  keywords = [ "python", "request",]
10
11
  classifiers = [ "Programming Language :: Rust", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3 :: Only", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: PyPy", "Topic :: Internet :: WWW/HTTP", "Topic :: Software Development :: Libraries :: Python Modules",]
11
- version = "0.2.3.dev163"
12
+ version = "0.2.6"
12
13
  dependencies = []
13
14
  [[project.authors]]
14
15
  name = "thomasht86"
@@ -1,54 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: httpr
3
- Version: 0.2.3.dev163
4
- Classifier: Programming Language :: Rust
5
- Classifier: Programming Language :: Python :: 3
6
- Classifier: Programming Language :: Python :: 3 :: Only
7
- Classifier: Programming Language :: Python :: 3.10
8
- Classifier: Programming Language :: Python :: 3.11
9
- Classifier: Programming Language :: Python :: 3.12
10
- Classifier: Programming Language :: Python :: 3.13
11
- Classifier: Programming Language :: Python :: Implementation :: CPython
12
- Classifier: Programming Language :: Python :: Implementation :: PyPy
13
- Classifier: Topic :: Internet :: WWW/HTTP
14
- Classifier: Topic :: Software Development :: Libraries :: Python Modules
15
- Requires-Dist: certifi ; extra == 'dev'
16
- Requires-Dist: pytest>=8.1.1 ; extra == 'dev'
17
- Requires-Dist: pytest-asyncio>=0.25.3 ; extra == 'dev'
18
- Requires-Dist: pytest-httpbin>=2.1.0 ; extra == 'dev'
19
- Requires-Dist: typing-extensions ; python_full_version < '3.12' and extra == 'dev'
20
- Requires-Dist: mypy>=1.14.1 ; extra == 'dev'
21
- Requires-Dist: ruff>=0.9.2 ; extra == 'dev'
22
- Requires-Dist: maturin ; extra == 'dev'
23
- Requires-Dist: trustme ; extra == 'dev'
24
- Requires-Dist: cbor2 ; extra == 'dev'
25
- Requires-Dist: mkdocs-material[imaging] ; extra == 'docs'
26
- Requires-Dist: mkdocstrings[python]>=0.27.0 ; extra == 'docs'
27
- Requires-Dist: mkdocs-gen-files ; extra == 'docs'
28
- Requires-Dist: mkdocs-literate-nav ; extra == 'docs'
29
- Requires-Dist: mkdocs-llmstxt ; extra == 'docs'
30
- Requires-Dist: matplotlib ; extra == 'benchmark'
31
- Requires-Dist: pandas ; extra == 'benchmark'
32
- Requires-Dist: starlette ; extra == 'benchmark'
33
- Requires-Dist: uvicorn ; extra == 'benchmark'
34
- Requires-Dist: requests ; extra == 'benchmark'
35
- Requires-Dist: httpx ; extra == 'benchmark'
36
- Requires-Dist: tls-client ; extra == 'benchmark'
37
- Requires-Dist: curl-cffi ; extra == 'benchmark'
38
- Requires-Dist: pycurl ; extra == 'benchmark'
39
- Requires-Dist: typing-extensions ; extra == 'benchmark'
40
- Requires-Dist: aiohttp ; extra == 'benchmark'
41
- Requires-Dist: cbor2 ; extra == 'benchmark'
42
- Requires-Dist: jupyter ; extra == 'benchmark'
43
- Requires-Dist: ipykernel ; extra == 'benchmark'
44
- Requires-Dist: gunicorn ; extra == 'benchmark'
45
- Requires-Dist: fastapi ; extra == 'benchmark'
46
- Provides-Extra: dev
47
- Provides-Extra: docs
48
- Provides-Extra: benchmark
49
- License-File: LICENSE
50
- Summary: Fast HTTP client for Python
51
- Keywords: python,request
52
- Author: thomasht86
53
- License: MIT License
54
- Requires-Python: >=3.10
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes