From 7d3d04d57ae75fb72bece5f122a05ed46a5ea654 Mon Sep 17 00:00:00 2001 From: Marc <50042689+mrcsnv@users.noreply.github.com> Date: Tue, 14 Apr 2026 15:07:01 +0200 Subject: [PATCH 1/6] Enhance proxy usage documentation with the correct usage. Expanded the proxy usage documentation to include authentication, per-request proxies with custom headers, and clarified Unix socket proxy usage. --- docs/source/guide/proxy.md | 148 +++++++++++++++++++++++++++++-------- 1 file changed, 118 insertions(+), 30 deletions(-) diff --git a/docs/source/guide/proxy.md b/docs/source/guide/proxy.md index cec9ad19..6c64b16a 100644 --- a/docs/source/guide/proxy.md +++ b/docs/source/guide/proxy.md @@ -2,67 +2,155 @@ !!! info "On this page" - HTTP/HTTPS proxy - - Unix Socket proxy + - Proxy with Authentication + - Per-request proxy with custom headers + - Unix Socket proxy for local services (Docker, Podman) + - Constructor reference and choosing the right one -### HTTP/HTTPS Proxy +The `Proxy` class provides several constructors depending on what you want to intercept: -Using proxies with authentication: +| Constructor | What it proxies | +|---|---| +| `Proxy.all(url)` | All requests (HTTP + HTTPS) | +| `Proxy.http(url)` | HTTP requests only | +| `Proxy.https(url)` | HTTPS requests only | +| `Proxy.unix(path)` | Requests via a Unix socket | + +You can pass a proxy in two ways: +- **Per-client** via `Client(proxies=[...])` — applies to every request made by that client. +- **Per-request** via `wreq.get(..., proxy=...)` — overrides or sets a proxy for a single request. + +--- + +## HTTP / HTTPS Proxy + +### Basic usage with a client ```python import asyncio -import wreq from wreq import Client, Proxy - async def main(): - # Create a client with multiple proxies client = Client( - proxies=[Proxy.http("socks5h://abc:def@127.0.0.1:6152")], + proxies=[Proxy.all("http://proxy.example.com:8080")] ) - # Send request via the client proxy - resp = await client.get("https://httpbin.io/anything") + resp = await client.get("https://httpbin.io/ip") print(await resp.text()) - # Send request via custom proxy +asyncio.run(main()) +``` + +All requests made through this `client` will be routed via the proxy. + +--- + +### Proxy with authentication + +If your proxy requires credentials, include them directly in the URL using the `username:password@host:port` syntax. + +**HTTP / HTTPS proxy with credentials:** + +```python +proxy = Proxy.all("http://username:password@proxy.example.com:8080") + +client = Client(proxies=[proxy]) +``` + +Or using the credentials separately + +```python +proxy = Proxy.all( + url="http://proxy.example.com:8080", + username="username", + password="password" +) + +client = Client(proxies=[proxy]) +``` + +**SOCKS5 proxy with credentials:** + +```python +# SOCKS5 +client = Client( + proxies=[Proxy.http("socks5://username:password@127.0.0.1:1080")] +) + + +# SOCKS5h (DNS also resolved by the proxy) +client = Client( + proxies=[Proxy.http("socks5h://username:password@127.0.0.1:6152")] +) +``` + +> The difference between `socks5://` and `socks5h://` is that `socks5h` delegates DNS resolution to the proxy server, which helps avoid DNS leaks. + +--- + +### Per-request proxy with custom headers + +You can configure a proxy for a single request and attach custom headers sent to the proxy server: + +```python +import asyncio +import wreq +from wreq import Proxy + +async def main(): resp = await wreq.get( "https://httpbin.io/anything", - proxy=Proxy.all( - url="http://127.0.0.1:6152", - custom_http_headers={ - "user-agent": "wreq", - "accept": "*/*", - "accept-encoding": "gzip, deflate, br", - "x-proxy": "wreq", - }, - ), + proxies=[ + Proxy.all( + url="http://127.0.0.1:6152", + custom_http_headers={ + "user-agent": "wreq", + "accept": "*/*", + "accept-encoding": "gzip, deflate, br", + "x-proxy": "wreq", + }, + ) + ], ) print(await resp.text()) - -if __name__ == "__main__": - asyncio.run(main()) +asyncio.run(main()) ``` -### Unix Socket Proxy +> **Note:** `custom_http_headers` are headers sent *to the proxy itself*, not to the final destination server. -Using Unix sockets for local services like Docker: +--- + +## Unix Socket Proxy + +Unix sockets allow communication with local services without going through a network port. This is common when working with Docker, Podman, or other daemons that expose a socket file. ```python import asyncio import wreq from wreq import Proxy - async def main(): - # Send request via Unix socket proxy resp = await wreq.get( "http://localhost/v1.41/containers/json", - proxy=Proxy.unix("/var/run/docker.sock"), + proxies=[Proxy.unix("/var/run/docker.sock")], ) print(await resp.text()) - -if __name__ == "__main__": - asyncio.run(main()) +asyncio.run(main()) ``` + +Even though the URL says `http://localhost`, the request never touches the network. It goes directly through the socket file at the given path. + +--- + +## Choosing the right constructor + +Each constructor controls which requests are intercepted by the proxy: + +| Constructor | Intercepts | +|---|---| +| `Proxy.all(url)` | All requests (HTTP + HTTPS) | +| `Proxy.http(url)` | HTTP requests only | +| `Proxy.https(url)` | HTTPS requests only | +| `Proxy.unix(path)` | Requests via a Unix socket | From 06ac143e2472d0c213f17bacb6633f4a23441467 Mon Sep 17 00:00:00 2001 From: Marc <50042689+mrcsnv@users.noreply.github.com> Date: Tue, 14 Apr 2026 15:10:43 +0200 Subject: [PATCH 2/6] Revise proxy example in quickstart documentation Updated the proxy usage example to reflect the new Client initialization with a list of proxies. --- docs/source/getting-started/quickstart.md | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/docs/source/getting-started/quickstart.md b/docs/source/getting-started/quickstart.md index 715c59c7..0cb72772 100644 --- a/docs/source/getting-started/quickstart.md +++ b/docs/source/getting-started/quickstart.md @@ -69,13 +69,21 @@ asyncio.run(main()) ## Using Proxies +The [Proxy](../guide/proxy.md) object defines how your HTTP client routes traffic through a proxy server. +You create one using a named constructor that specifies the scope: + +- `Proxy.all(url)` — routes all traffic through the given proxy +- `Proxy.http(url)` - only intercepts HTTP requests +- `Proxy.https(url)` — only intercepts HTTPS requests + +Once created, you pass it to the `Client` and all requests will go through it automatically. + ```python import asyncio -from wreq import Client -from wreq.proxy import Proxy +from wreq import Client, Proxy async def main(): - client = Client(proxy=Proxy.all("http://proxy.example.com:8080")) + client = Client(proxies=[Proxy.all("http://proxy.example.com:8080")]) resp = await client.get("https://httpbin.org/ip") print(await resp.text()) @@ -107,4 +115,4 @@ asyncio.run(main()) ## Next Steps - See the [Examples](../guide/basic.md) for more code samples -- Explore the [API Reference](../api/wreq.md) for detailed documentation \ No newline at end of file +- Explore the [API Reference](../api/wreq.md) for detailed documentation From 6039d80d5f1ff6d9adcb1bf0ec8f80b9671aa77a Mon Sep 17 00:00:00 2001 From: Marc <50042689+mrcsnv@users.noreply.github.com> Date: Tue, 14 Apr 2026 15:12:02 +0200 Subject: [PATCH 3/6] Correct bullet point formatting in quickstart.md Fix formatting of bullet points in quickstart.md --- docs/source/getting-started/quickstart.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/source/getting-started/quickstart.md b/docs/source/getting-started/quickstart.md index 0cb72772..fd3b4b3c 100644 --- a/docs/source/getting-started/quickstart.md +++ b/docs/source/getting-started/quickstart.md @@ -72,9 +72,9 @@ asyncio.run(main()) The [Proxy](../guide/proxy.md) object defines how your HTTP client routes traffic through a proxy server. You create one using a named constructor that specifies the scope: -- `Proxy.all(url)` — routes all traffic through the given proxy +- `Proxy.all(url)` - routes all traffic through the given proxy - `Proxy.http(url)` - only intercepts HTTP requests -- `Proxy.https(url)` — only intercepts HTTPS requests +- `Proxy.https(url)` - only intercepts HTTPS requests Once created, you pass it to the `Client` and all requests will go through it automatically. From 4ca63200d9599b3a71a29feabb6eff219bd38474 Mon Sep 17 00:00:00 2001 From: Marc <50042689+mrcsnv@users.noreply.github.com> Date: Wed, 15 Apr 2026 17:02:31 +0200 Subject: [PATCH 4/6] Update quickstart guide for wreq usage More context added. And anchoring to API references for easy navigation between classes and models. To learn how to use it better --- docs/source/getting-started/quickstart.md | 183 +++++++++++++++------- 1 file changed, 128 insertions(+), 55 deletions(-) diff --git a/docs/source/getting-started/quickstart.md b/docs/source/getting-started/quickstart.md index fd3b4b3c..50c8f32e 100644 --- a/docs/source/getting-started/quickstart.md +++ b/docs/source/getting-started/quickstart.md @@ -1,10 +1,12 @@ -# :zap: Quick Start +# Quickstart -This page will help you get up and running with wreq in minutes. +This page covers the basics of making HTTP requests with wreq. By the end, you will be able to send requests, read responses, pass headers, work with JSON, and route traffic through a proxy. --- -## Basic GET Request +## Making a Request + +wreq supports both async and blocking usage. The async client is the default and recommended approach for most use cases. === "Async" ```python @@ -13,106 +15,177 @@ This page will help you get up and running with wreq in minutes. async def main(): client = Client() - resp = await client.get("https://httpbin.org/get") - print(resp.status_code) - print(await resp.text()) + response = await client.get("https://httpbin.org/get") + print(response.status_code) asyncio.run(main()) ``` + === "Blocking" ```python from wreq.blocking import Client client = Client() - resp = client.get("https://httpbin.org/get") - print(resp.status_code) - print(resp.text()) + response = client.get("https://httpbin.org/get") + print(response.status_code) ``` +The same interface works for all standard HTTP methods: + +```python +response = await client.post("https://httpbin.org/post") +response = await client.put("https://httpbin.org/put") +response = await client.delete("https://httpbin.org/delete") +response = await client.head("https://httpbin.org/get") +``` + --- -## POST with JSON +## Passing Parameters in URLs + +To append query parameters to a URL, pass a dictionary to the `params` argument: ```python -import asyncio -from wreq import Client +params = {"search": "wreq", "page": "1"} +response = await client.get("https://httpbin.org/get", params=params) +print(response.url) +# https://httpbin.org/get?search=wreq&page=1 +``` -async def main(): - client = Client() - data = {"name": "John", "age": 30} - resp = await client.post("https://httpbin.org/post", json=data) - result = await resp.json() - print(result) +--- + +## Reading the Response + +### Status code -asyncio.run(main()) +```python +response = await client.get("https://httpbin.org/get") +print(response.status_code) +# 200 +``` + +### Text + +```python +text = await response.text() +print(text) +``` + +### JSON + +If the server returns a JSON body, parse it directly with `.json()`: + +```python +response = await client.get("https://httpbin.org/json") +data = await response.json() +print(data) +``` + +### Response headers + +Response headers are available as a dictionary-like object: + +```python +print(response.headers["content-type"]) +# application/json ``` --- -## Emulation +## Sending Data + +### JSON body -Emulate different browsers to bypass detection: +Pass a dictionary to the `json` argument and wreq will serialize it and set the correct `Content-Type` header automatically: ```python -import asyncio -from wreq import Client, Emulation +payload = {"name": "John", "age": 30} +response = await client.post("https://httpbin.org/post", json=payload) +result = await response.json() +print(result) +``` + +### Form-encoded data + +To send HTML form data, use the `data` argument instead: + +```python +form = {"username": "john", "password": "secret"} +response = await client.post("https://httpbin.org/post", data=form) +``` + +--- + +## Custom Headers + +Pass a [HeaderMap](../api/header.md?h=HeaderMap#wreq.header.HeaderMap) to attach additional headers to a request: + +```python +from wreq.header import HeaderMap -async def main(): - client = Client(emulation=Emulation.Safari26) - resp = await client.get("https://tls.peet.ws/api/all") - print(await resp.text()) +headers = HeaderMap() +headers["User-Agent"] = "MyApp/1.0" +headers["Accept"] = "application/json" -asyncio.run(main()) +response = await client.get("https://httpbin.org/headers", headers=headers) +print(await response.text()) ``` --- ## Using Proxies -The [Proxy](../guide/proxy.md) object defines how your HTTP client routes traffic through a proxy server. -You create one using a named constructor that specifies the scope: +The [Proxy](../guide/proxy.md) object controls how the client routes traffic. You create one using a named constructor that defines its scope: -- `Proxy.all(url)` - routes all traffic through the given proxy -- `Proxy.http(url)` - only intercepts HTTP requests -- `Proxy.https(url)` - only intercepts HTTPS requests +- `Proxy.all(url)` routes all traffic through the proxy. +- `Proxy.http(url)` only intercepts plain HTTP requests. +- `Proxy.https(url)` only intercepts HTTPS requests. -Once created, you pass it to the `Client` and all requests will go through it automatically. +Pass the proxy to the `Client` and every subsequent request will use it: ```python -import asyncio from wreq import Client, Proxy -async def main(): - client = Client(proxies=[Proxy.all("http://proxy.example.com:8080")]) - resp = await client.get("https://httpbin.org/ip") - print(await resp.text()) - -asyncio.run(main()) +client = Client(proxies=[Proxy.all("http://proxy.example.com:8080")]) +response = await client.get("https://httpbin.org/ip") +print(await response.text()) ``` --- -## Custom Headers +## Browser Emulation + +wreq can emulate the TLS fingerprint and headers of real browsers, which is useful when connecting to servers that inspect these signals. Pass an [Emulation](../guide/emulation.md) preset to the `Client`: ```python -import asyncio -from wreq import Client -from wreq.header import HeaderMap +from wreq import Client, Emulation -async def main(): - client = Client() - headers = HeaderMap() - headers["User-Agent"] = "MyApp/1.0" - headers["Custom-Header"] = "value" - resp = await client.get("https://httpbin.org/headers", headers=headers) - print(await resp.text()) +client = Client(emulation=Emulation.Safari26) +response = await client.get("https://tls.peet.ws/api/all") +print(await response.text()) +``` + +Available presets are listed in the [Emulation reference](../getting-started/introduction.md#behavior). + +--- + +## Error Handling + +Check the status code manually, or call `raise_for_status()` to raise an exception on any 4xx or 5xx response: + +```python +response = await client.get("https://httpbin.org/status/404") -asyncio.run(main()) +try: + response.raise_for_status() +except Exception as exc: + print(f"Request failed with status {response.status_code}") ``` --- ## Next Steps -- See the [Examples](../guide/basic.md) for more code samples -- Explore the [API Reference](../api/wreq.md) for detailed documentation +- [Examples](../guide/basic.md) for more complete code samples +- [API Reference](../api/wreq.md) for a full list of parameters and types +- [Proxy guide](../guide/proxy.md) for advanced proxy configuration From 70e752fbacd58a26014ccd991d7ce7e9f7ee3c9c Mon Sep 17 00:00:00 2001 From: Marc <50042689+mrcsnv@users.noreply.github.com> Date: Wed, 15 Apr 2026 17:10:28 +0200 Subject: [PATCH 5/6] Revise request examples in basic guide Updated examples for GET and POST requests, including form data and headers. Improved clarity and consistency in code snippets. --- docs/source/guide/basic.md | 270 ++++++++++++++++++------------------- 1 file changed, 131 insertions(+), 139 deletions(-) diff --git a/docs/source/guide/basic.md b/docs/source/guide/basic.md index b6744866..1a9fa0bc 100644 --- a/docs/source/guide/basic.md +++ b/docs/source/guide/basic.md @@ -6,179 +6,171 @@ - Custom headers - Query parameters & streaming -### Simple GET Request - +This page covers the most common request patterns in wreq: sending GET and POST requests, working with form data and JSON, customizing headers, passing query parameters, and reading streaming responses. + +--- + +## Making Requests + +### GET + +The simplest way to make a request is through the top-level `wreq.get()` shortcut: + ```python import asyncio import wreq -from wreq import Method - - + async def main(): - resp: wreq.Response = await wreq.request(Method.GET, url="https://www.google.com/") - print("Status Code: ", resp.status) - print("Version: ", resp.version) - print("Response URL: ", resp.url) - print("Headers: ", resp.headers) - print("Cookies: ", resp.cookies) - print("Content-Length: ", resp.content_length) - print("Remote Address: ", resp.remote_addr) - - -if __name__ == "__main__": - asyncio.run(main()) + response = await wreq.get("https://httpbin.org/get") + print(response.status) + +asyncio.run(main()) ``` - -### POST Request with JSON - + +If you need access to the full response metadata, all of it is available on the response object: + ```python -import asyncio -import wreq - - async def main(): - resp = await wreq.post( - "https://httpbin.io/anything", + response = await wreq.get("https://httpbin.org/get") + print(response.status) + print(response.version) + print(response.url) + print(response.headers) + print(response.cookies) + print(response.content_length) + print(response.remote_addr) +``` + +### POST with JSON + +Pass a dictionary to the `json` argument. wreq will serialize it and set the `Content-Type` header to `application/json` automatically: + +```python +async def main(): + response = await wreq.post( + "https://httpbin.org/post", json={"key": "value"}, ) - print(await resp.json()) - - -if __name__ == "__main__": - asyncio.run(main()) + print(await response.json()) ``` - -### Form Data - + +--- + +## Form Data + +To send form-encoded data, use the `form` argument. You can pass either a list of tuples or a dictionary. The list of tuples is useful when you need to send the same key more than once: + ```python -import asyncio -import wreq - - +from wreq import Client + async def main(): - client = wreq.Client() - - # use a list of tuples - resp = await client.post( - "https://httpbin.io/anything", + client = Client() + + # List of tuples — preserves key order and allows duplicate keys + response = await client.post( + "https://httpbin.org/post", form=[ ("key1", "value1"), ("key2", "value2"), - ("number", 123), - ("flag", True), - ("float", 45.67), + ("count", 3), ], ) - print(await resp.text()) - - # OR use a dictionary - resp = await client.post( - "https://httpbin.io/anything", + print(await response.text()) + + # Dictionary — simpler when keys are unique + response = await client.post( + "https://httpbin.org/post", form={ - "keyA": "valueA", - "keyB": "valueB", - "number": 789, - "flag": False, - "float": 12.34, + "key1": "value1", + "key2": "value2", + "count": 3, }, ) - print(await resp.text()) - - -if __name__ == "__main__": - asyncio.run(main()) -``` - -### Custom Headers - -```python -from wreq.header import HeaderMap - - -if __name__ == "__main__": - headers = HeaderMap() - # Add Content-Type header - headers.insert("Content-Type", "application/json") - # Add Accept header (first value) - headers.insert("Accept", "application/json") - # Add Accept header (second value) - headers.insert("Accept", "text/html") - # Get all values for 'Accept' header - print("All Accept:", list(headers.get_all("Accept"))) - # Get the value for 'Content-Type' header - print("Content-Type:", headers.get("Content-Type")) + print(await response.text()) ``` - -### Query Parameters - + +Non-string values such as integers, booleans, and floats are accepted and will be serialized automatically. + +--- + +## Query Parameters + +Use the `query` argument to append parameters to the URL. Like `form`, it accepts both a list of tuples and a dictionary: + ```python -import asyncio -import wreq - - async def main(): - # Send list of tuples as query parameters - resp = await wreq.get( - "https://httpbin.io/anything", + # List of tuples + response = await wreq.get( + "https://httpbin.org/get", query=[ - ("key1", "value1"), - ("key2", "value2"), - ("number", 123), - ("flag", True), - ("float", 45.67), + ("search", "wreq"), + ("page", 1), + ("active", True), ], ) - print(await resp.text()) - - # OR send dictionary as query parameters - resp = await wreq.get( - "https://httpbin.io/anything", + print(await response.text()) + + # Dictionary + response = await wreq.get( + "https://httpbin.org/get", query={ - "keyA": "valueA", - "keyB": "valueB", - "number": 789, - "flag": False, - "float": 12.34, + "search": "wreq", + "page": 1, + "active": True, }, ) - print(await resp.text()) - - -if __name__ == "__main__": - asyncio.run(main()) + print(await response.text()) ``` - -### Streaming Response - + +--- + +## Custom Headers + +wreq uses a [HeaderMap](../api/header.md?h=HeaerMap#wreq.header.HeaderMap) object to represent request headers. Unlike a regular dictionary, `HeaderMap` supports multiple values per key, which some headers such as `Accept` require: + ```python -import asyncio -import wreq -from wreq import Response - - -async def main(): - resp: Response = await wreq.get("https://httpbin.io/stream/20") - async with resp: - async with resp.stream() as streamer: - async for chunk in streamer: - print(chunk) - await asyncio.sleep(0.1) - - -if __name__ == "__main__": - asyncio.run(main()) +from wreq.header import HeaderMap + +headers = HeaderMap() + +headers.insert("Content-Type", "application/json") + +# A header can hold multiple values +headers.insert("Accept", "application/json") +headers.insert("Accept", "text/html") + +# Retrieve a single value +print(headers.get("Content-Type")) +# application/json + +# Retrieve all values for a multi-value header +print(list(headers.get_all("Accept"))) +# ['application/json', 'text/html'] ``` - + +Pass the `HeaderMap` to any request method via the `headers` argument: + +```python +response = await wreq.get("https://httpbin.org/headers", headers=headers) +``` + +--- + +## Streaming Responses + +For large responses, you can read the body incrementally instead of loading it all into memory at once. Use `resp.stream()` as an async iterator: + ```python -import asyncio from wreq import Client - + async def main(): client = Client() - resp = await client.get("https://httpbin.org/stream/10") - - async for chunk in resp.stream(): - print(chunk.decode('utf-8')) - -asyncio.run(main()) + response = await client.get("https://httpbin.org/stream/10") + + async for chunk in response.stream(): + print(chunk.decode("utf-8")) ``` + +Each `chunk` is a `bytes` object. Decode it to a string only if you know the response body is text. + +--- From 3b7d2a3c91c0e082b5c4ca84d5640bc6faf02d3b Mon Sep 17 00:00:00 2001 From: Marc <50042689+mrcsnv@users.noreply.github.com> Date: Thu, 16 Apr 2026 15:41:47 +0200 Subject: [PATCH 6/6] Change print statement to use response.status --- docs/source/getting-started/quickstart.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/getting-started/quickstart.md b/docs/source/getting-started/quickstart.md index 50c8f32e..e68aab43 100644 --- a/docs/source/getting-started/quickstart.md +++ b/docs/source/getting-started/quickstart.md @@ -16,7 +16,7 @@ wreq supports both async and blocking usage. The async client is the default and async def main(): client = Client() response = await client.get("https://httpbin.org/get") - print(response.status_code) + print(response.status) asyncio.run(main()) ```