HTTPX - Basic

Introduction

HTTPX is a powerful and user-friendly HTTP client that allows developers to make HTTP requests with ease.

It offers support for asynchronous programming, HTTP/2, and connection pooling.

Getting Started

Clone the httpx-basic project:

Terminal window
git clone https://gitlab.com/xtec/python/httpx-basic.git

Start the FastAPI server:

Terminal window
cd httpx-basic
uv run app/main.py

Open the client.py file that is on the project root:

client.py
import json
import httpx
def main():
response = httpx.get('http://localhost:8000/person/1')
data = response.json()
print(json.dumps(data, indent=4))
if __name__ == "__main__":
main()

This script imports the httpx package and makes a GET request to the endpoint http://localhost:8000/person/1

Run this script; you should observe output similar to the following:

{
"id": 1,
"name": "David",
"surname": "de Mingo"
}

The server answers with a JSON object containing the data of person with id 1.

Response

When you make an HTTP request using httpx, the response object contains important information about the server’s reply. This includes the status code, headers, and response body, which help determine whether the request was successful and how to process the returned data.

Modify your client.py file to inspect different parts of the response:

client.py
import httpx
def main():
response = httpx.get('http://localhost:8000/person/1')
print(f"Status Code: {response.status_code}")
print(f"Headers: {response.headers}")
print(f"Content: {response.text}")
if __name__ == "__main__":
main()

This script displays important response details. Here’s what each part of the response object represents:

  • status_code – Indicates the HTTP status of the request (e.g., 200 for success, 404 for not found).
  • headers – A dictionary containing metadata about the response, such as content type, server information, and caching policies.
  • text – The full response body returned as a string.
  • json() – Parses the response body as JSON, converting it into a Python dictionary for easy data manipulation.

After running the script, you should see something like:

Terminal window
Status Code: 200
Headers: Headers({'content-length': '44', 'content-type': 'application/json', 'server': 'granian', 'date': 'Thu, 01 Jan 2026 16:37:25 GMT'})
Content: {"name":"David","surname":"de Mingo","id":1}

Status Code

The first thing to do when receiving a response is to check the status code to make sure the request was successful before processing the data.

If you try to get the person with id 10, the server will respond with a status_code of 404, which is the code for “Not Found”.

Task

Write a code that handles the case when the person is not found (status code 404) and prints an appropriate message.

Headers

HTTP headers provide additional information about the response, such as the content type or the server that sent the response.

Terminal window
Headers({'content-length': '44', 'content-type': 'application/json', 'server': 'granian', 'date': 'Thu, 01 Jan 2026 16:37:25 GMT'})

Understanding basic request methods

Now that you understand how HTTPX handles responses, let’s explore different types of HTTP requests beyond GET.

HTTPX supports a variety of methods, including POST, PUT, DELETE, and query parameters with GET, allowing you to interact with APIs that require data submission or modifications.

POST

A POST request is typically used to send data to a server, such as creating a new resource or submitting a form.

Remove all contents from client.py and replace it with the following code to send a POST request with JSON data:

client.py
response = httpx.post('http://localhost:8000/person', json={'name': 'Susan', 'surname': 'Sarandon'})
print(response.status_code)
print(json.dumps(response.json(), indent=4))

Run the script and observe the output:

Terminal window
201
{
"name": "Susan",
"surname": "Sarandon",
"id": 3
}

The server responds with a 201 status code, indicating that the request was successful and a new resource was created.

The response body contains the details of the newly created person, including their assigned ID.

Task

Write a script that gets person with id 3 and prints the person’s name.‘

PUT

While POST is used to create new resources, a PUT request is used to update existing ones.

When you need to modify an entry rather than add a new one, PUT ensures that the existing data is replaced with the updated values.

client.py
response = httpx.put('http://localhost:8000/person/3', json={'id': 3, 'name': 'Susan', 'surname': 'Ferguson'})
data = response.json()
print(json.dumps(data, indent=4))

This request updates an existing resource with new data, ensuring changes are correctly reflected on the server.

DELETE

If you need to remove a resource instead, a DELETE request is used.

This is particularly useful for deleting records from a database or removing items from an API:

client.py
response = httpx.delete('http://localhost:8000/person/3')
print(response.status_code)

The first time you run this script, a response status code of 204 indicates that the resource was successfully deleted.

If you try to delete a resource that doesn’t exist, the server responds with a 404 status code, indicating that the resource was not found.

Run the script again to see this behavior.

Handling query parameters in a GET request

You can request all persons from the server by sending a GET request without any parameters:

client.py
response = httpx.get('http://localhost:8000/person')
print(json.dumps(response.json(), indent=2))
[
{
"name": "David",
"surname": "de Mingo",
"id": 1
},
{
"name": "Roser",
"surname": "Salietti",
"id": 2
}
]

The server responds with a list of all persons, and it can be a very long list.

Query parameters allow you to filter the results returned by the API based on specific criteria.

To filter persons by name, you can send a GET request with the name parameter set to the desired value.

Modify client.py to send a GET request with a query parameter name=Roser to filter the results to only include persons with the name “Roser”.

client.py
response = httpx.get('http://localhost:8000/person', params={'name': 'Roser'})
print(json.dumps(response.json(), indent=2))

The response contains a list of persons with the name “Roser”.

[
{
"name": "Roser",
"surname": "Salietti",
"id": 2
}
]
Note

The ? character is used to separate the base URL from the query parameters.

Binary

The response content can also be accessed as bytes, for non-text responses:

r.content

For example, to save this image from binary data returned by a request,

you can use the following code:

file = "donald-duck.png"
r = httpx.get(f"https://xtec.dev/python/httpx/basic/{file}")
with open(file, 'wb') as file:
print(r.status_code)
if r.status_code == 200:
file.write(r.content)

And with the pillow library you can open the image directly from the response content:

from io import BytesIO
from PIL import Image
file = "donald-duck.png"
r = httpx.get(f"https://xtec.dev/python/httpx/basic/{file}")
if r.status_code == 200:
i = Image.open(BytesIO(r.content))
print(i.format)
print(i.size)
i.show()
Task

Download and reduce the image:

Any gzip and deflate HTTP response encodings will automatically be decoded for you.

If brotlipy is installed, then the brotli response encoding will be supported.

If zstandard is installed, then zstd response encodings will also be supported.

Tasks

Task

Download the CSV file from Quantitat d’aigua als embassaments de les Conques Internes de Catalunya and print the first 5 lines.

import csv
import os.path
import urllib3
def get_file(file, url):
if not os.path.isfile(file):
response = urllib3.request("GET", url)
print(response.data)
with open(file, "wb") as file:
file.write(response.data)
return file
def read_csv(file):
with open(file, mode="r", encoding="utf-8") as file:
reader = csv.DictReader(file)
result = [row_dict for row_dict in csv_reader]
return result
#for row in reader:
# print(row)
file = get_file(
"aigua.csv",
"https://analisi.transparenciacatalunya.cat/api/views/gn9e-3qhr/rows.csv?accessType=DOWNLOAD",
)
entries = read_csv(file)

Exemple: Descarregar i obrir un fitxer comprimit.

En aquest cas també apliquem la tècnica de la cache, per a descarregar i descomprimir el fitxer només en cas que no ho haguem fet abans.

import urllib3
import os
import csv
import zipfile
url = "https://gitlab.com/xtec/python/data/-/raw/main/airline-flight.zip?ref_type=heads"
zip_filename = "airline-flight.zip"
def download_zip(url, zip_filename):
# El with ens permet tancar automàticament la connexió al acabar.
with urllib3.PoolManager().request('GET', url, preload_content=False) as response, open(zip_filename, 'wb') as out_file:
print(f"Descarregant {zip_filename}...")
while (data := response.read(1024)): # Utilitzem l'operador d'assignació
out_file.write(data)
print(f"Descàrrega completada: {zip_filename}")
def unzip_file_and_get_csv(zip_filename):
with zipfile.ZipFile(zip_filename, 'r') as zip_ref:
zip_ref.extractall() # Descomprimim tots els fitxers
return next((file for file in zip_ref.namelist() if file.endswith('.csv')), None)
def save_csv_content(csv_filename):
with open(csv_filename, 'r') as f:
reader = csv.DictReader(f)
return [row for row in reader]
def main():
# Si hi hagués més d'un fitxer
# csv_filename = next((file for file in os.listdir() if file.endswith('.csv')), None)
csv_filename = "airline-flight.csv"
if not csv_filename:
if not os.path.exists(zip_filename):
download_zip(url, zip_filename)
csv_filename = unzip_file_and_get_csv(zip_filename)
csv_lines = save_csv_content(csv_filename)
print(csv_lines[:20]) # Provem si es veu el contingut
if __name__ == "__main__":
main()

Activitat: Embassaments

1.- Baixa les dades en format Json enlloc de csv.

Pistes:

2.- Llegeix el fitxer Json en un dict Python.

3.- Analitza les dades:

  • Agafa les dades de l’any 2024, d’un pantà, per exemple el de Riudecanyes.

  • Mostra el dia que hi ha el ‘Nivell absolut (msnm)’ màxim i el dia mínim.

  • Prova altres pantans, de seleccionar valors concrets del diccionari, altres anys…

Activitat: AEMET

A l’activitat Dades - JSON vas utilitzar dades de l’AEMET.

Ara anem a crear una aplicació per consultar dades metereològiques.

Instruccions

  1. Ves al projecte https://gitlab.com/xtec/python/aemet
  2. Fes un “fork” del projecte al teu compte de
  3. Clona el teu projecte al director arrel
  4. Instal.la les dependències amb poetry update
  5. Executa l’aplicació amb poetry run python app/app.py

Millores

Has de realitzar millores en el projecte.

Per exemple,

  • Dona l’opció d’obtenir altres dades
  • Que l’usuari pugui entra el nom del municipi enlloc del codi.
  • etc.

Pending

Getting Started with HTTPX: Python’s Modern HTTP Client