Back to Blog
Python

Python Requests: GET, POST, PUT, PATCH, DELETE

python requests get post put patch delete requests: Use the Python requests library for GET, POST, PUT, PATCH, and DELETE HTTP methods with practical examples for REST...

requestshttp-methodsrest-apiapi-integrationhttp-client
Illustration showing the Python requests library handling five HTTP methods with a request and response data flow.

The requests library is the standard HTTP client in Python. The python requests get post put patch delete requests pattern covers the five HTTP verbs you need for most REST API work: GET, POST, PUT, PATCH, and DELETE. Each verb has a dedicated function, a specific way to send payloads, and its own response-handling requirements. This article walks through the syntax for all five, how to send JSON and form data, how to read responses, and where the library's defaults can cause problems in production.

Installing and Importing requests

requests is not part of the Python standard library. Install it with pip:

pip install requests

Then import it at the top of your module:

import requests

The library exposes one function per HTTP verb. All of them return a Response object, which holds the status code, headers, and body. The differences between the methods are in the parameters they accept and the semantics of the request they send.

GET: Reading Resources

requests.get fetches a resource. It accepts query parameters through the params argument, which requests URL-encodes for you:

import requests params = {"page": 1, "status": "active"} response = requests.get("https://api.example.com/users", params=params) print(response.status_code) print(response.json())

The params dictionary is the correct way to send query strings. Passing them directly in the URL string works, but params handles encoding of special characters and keeps the URL construction in one place.

Check the status code before parsing the body. A common mistake is calling .json() on a response that contains an error page instead of JSON, which raises requests.exceptions.JSONDecodeError. Use response.raise_for_status() to surface HTTP errors early:

response = requests.get("https://api.example.com/users") response.raise_for_status() data = response.json()

raise_for_status() raises requests.exceptions.HTTPError for 4xx and 5xx responses. This keeps your error handling in one place instead of scattering if response.status_code == 200 checks through the code.

POST: Creating Resources

requests.post creates a resource. The payload goes in either json or data. Use json when the API expects a JSON body:

import requests payload = {"name": "Ada", "role": "admin"} response = requests.post("https://api.example.com/users", json=payload) print(response.status_code) print(response.json())

When you pass json=, requests sets the Content-Type header to application/json and serializes the dictionary with json.dumps. That means you should not pass a pre-serialized string to json; pass the dictionary or list directly.

Use data= for form-encoded bodies:

response = requests.post("https://api.example.com/login", data={"username": "ada", "password": "secret"})

requests encodes the dictionary as application/x-www-form-urlencoded when you use data with a dictionary. If you pass a string to data, it is sent as-is, so you are responsible for the encoding.

PUT and PATCH: Updating Resources

requests.put replaces a resource entirely. requests.patch applies a partial update. The distinction matters for API design: a PUT request typically requires the complete representation of the resource, while a PATCH request sends only the fields that changed.

# PUT: replace the whole resource full_payload = {"name": "Ada", "role": "admin", "email": "ada@example.com"} response = requests.put("https://api.example.com/users/42", json=full_payload) # PATCH: update only the role field partial_payload = {"role": "superadmin"} response = requests.patch("https://api.example.com/users/42", json=partial_payload)

Both methods accept the same json and data arguments as POST. The server decides what a partial update means; the client only sends the payload. If the API you are integrating does not support PATCH, sending it returns a 405 Method Not Allowed, which raise_for_status() will surface.

MethodSemanticsPayloadTypical use
PUTReplace the resourceFull representationIdempotent updates
PATCHPartial modificationChanged fields onlyNon-idempotent or partial updates

PUT is idempotent: sending the same PUT request twice produces the same result. PATCH is not guaranteed to be idempotent, because the server may apply the partial change differently depending on the current state.

DELETE: Removing Resources

requests.delete removes a resource. It typically takes no body:

response = requests.delete("https://api.example.com/users/42") if response.status_code == 204: print("Resource deleted")

A 204 No Content response has an empty body, so calling .json() on it raises an error. Check the status code instead. Some APIs return 200 with a deleted resource representation, and others return 404 if the resource does not exist. The exact contract depends on the API, so read the response status before parsing anything.

Handling Timeouts and Request Errors

The most common production failure with requests is a missing timeout. By default, requests waits indefinitely for a response. A slow or hung server can block your worker thread forever. Always pass timeout:

response = requests.get("https://api.example.com/users", timeout=5)

timeout=5 applies to both the connection and the read phase. If you want different limits, pass a tuple: timeout=(3, 10) for connect and read respectively. A timeout raises requests.exceptions.Timeout, which you should catch:

try: response = requests.get("https://api.example.com/users", timeout=5) response.raise_for_status() except requests.exceptions.Timeout: print("Request timed out") except requests.exceptions.RequestException as exc: print(f"Request failed: {exc}")

RequestException is the base class for all requests errors, including Timeout, ConnectionError, and HTTPError. Catching it at the top level ensures no network error escapes unhandled.

Reusing Connections with Session Objects

Every call to requests.get or requests.post opens a new connection. For a script that makes a few calls, that is fine. For a loop that makes hundreds of requests, connection reuse matters. A Session object pools connections and reuses them across requests:

with requests.Session() as session: session.headers.update({"Authorization": "Bearer token123"}) for user_id in range(100): response = session.get(f"https://api.example.com/users/{user_id}") process(response)

The session also persists cookies and applies headers to every request, which removes repetitive header code. Sessions are not thread-safe; share them across threads only with external synchronization, or create one session per thread.

Production Considerations: Retries and Connection Pooling

requests does not retry failed requests by default. A transient network error or a 5xx response is returned to your code immediately. If your integration needs retries, implement them with an HTTP adapter that mounts a Retry policy:

from requests.adapters import HTTPAdapter from urllib3.util.retry import Retry retry_strategy = Retry( total=3, backoff_factor=1, status_forcelist=[500, 502, 503, 504], ) with requests.Session() as session: adapter = HTTPAdapter(max_retries=retry_strategy) session.mount("https://", adapter) response = session.get("https://api.example.com/users", timeout=5)

The backoff_factor controls the delay between retries: the wait is backoff_factor * (2 ** retry_number) seconds. Keep the retry count small and respect the API's rate limits. Retrying POST requests is risky because the server may have already processed the request; retries should be limited to idempotent methods like GET, PUT, and DELETE unless the API supports idempotency keys.

Connection pooling is handled internally by urllib3, which requests uses under the hood. The default pool size is 10 connections per host. If your application makes many concurrent requests to the same host, raise the pool size by mounting a custom adapter:

adapter = HTTPAdapter(pool_connections=20, pool_maxsize=50) session.mount("https://", adapter)

pool_connections is the number of connection pools for different hosts, and pool_maxsize is the number of connections per host. Tune these values only after observing actual concurrency in your application; setting them arbitrarily high wastes memory.

python requests get post put patch delete requests: Practica | RYUSLOG DEV