diff --git a/docs/source/getting-started/quickstart.md b/docs/source/getting-started/quickstart.md index fd3b4b3c..d210140d 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,61 +15,88 @@ 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) 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) +``` -## Emulation +### JSON -Emulate different browsers to bypass detection: +If the server returns a JSON body, parse it directly with `.json()`: ```python -import asyncio -from wreq import Client, Emulation +response = await client.get("https://httpbin.org/json") +data = await response.json() +print(data) +``` -async def main(): - client = Client(emulation=Emulation.Safari26) - resp = await client.get("https://tls.peet.ws/api/all") - print(await resp.text()) +### Response headers -asyncio.run(main()) +Response headers are available as a dictionary-like object: + +```python +print(response.headers["content-type"]) +# application/json ``` --- -## Using Proxies +## Sending Data + +### JSON body + +Pass a dictionary to the `json` argument and wreq will serialize it and set the correct `Content-Type` header automatically: 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: @@ -79,35 +108,87 @@ You create one using a named constructor that specifies the scope: Once created, you pass it to the `Client` and all requests will go through it automatically. ```python -import asyncio -from wreq import Client, Proxy +payload = {"name": "John", "age": 30} +response = await client.post("https://httpbin.org/post", json=payload) +result = await response.json() +print(result) +``` + +### Form-encoded data -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()) +To send HTML form data, use the `data` argument instead: -asyncio.run(main()) +```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 -import asyncio -from wreq import Client from wreq.header import HeaderMap -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()) +headers = HeaderMap() +headers["User-Agent"] = "MyApp/1.0" +headers["Accept"] = "application/json" + +response = await client.get("https://httpbin.org/headers", headers=headers) +print(await response.text()) +``` + +--- + +## Using Proxies + +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 proxy. +- `Proxy.http(url)` only intercepts plain HTTP requests. +- `Proxy.https(url)` only intercepts HTTPS requests. + +Pass the proxy to the `Client` and every subsequent request will use it: + +```python +from wreq import Client, Proxy + +client = Client(proxies=[Proxy.all("http://proxy.example.com:8080")]) +response = await client.get("https://httpbin.org/ip") +print(await response.text()) +``` + +--- + +## 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 +from wreq import Client, Emulation + +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}") ``` --- 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. + +--- diff --git a/docs/source/guide/proxy.md b/docs/source/guide/proxy.md index 17a76b96..f5982f53 100644 --- a/docs/source/guide/proxy.md +++ b/docs/source/guide/proxy.md @@ -131,7 +131,7 @@ from wreq import Proxy async def main(): 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())