- Introduction
- Getting Started
- Response
- Headers
- Understanding basic request methods
- Binary
- Tasks
- Activitat: Embassaments
- Activitat: AEMET
- Pending
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:
git clone https://gitlab.com/xtec/python/httpx-basic.gitStart the FastAPI server:
cd httpx-basicuv run app/main.pyOpen the client.py file that is on the project root:
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:
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.,200for success,404for 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:
Status Code: 200Headers: 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”.
Write a code that handles the case when the person is not found (status code 404) and prints an appropriate message.
response = httpx.get('http://localhost:8000/person/3')if response.status_code != 200: print(f"{response.status_code} Person not found")else: data = response.json() print(json.dumps(data, indent=4))Headers
HTTP headers provide additional information about the response, such as the content type or the server that sent the response.
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:
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:
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.
Write a script that gets person with id 3 and prints the person’s name.‘
response = httpx.get('http://localhost:8000/person/3')data = response.json()print(data['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.
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:
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:
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”.
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 }]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.contentFor 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 BytesIOfrom 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()Download and reduce the 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)) i = i.reduce(2) i.show()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
Download the CSV file from Quantitat d’aigua als embassaments de les Conques Internes de Catalunya and print the first 5 lines.
import csvimport os.pathimport 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 urllib3import osimport csvimport 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:
La URL pel JSON és:
https://analisi.transparenciacatalunya.cat/api/views/gn9e-3qhr/rows.jsonRecorda que has d’eliminar les metadades, presents al node ‘meta’ i que les dades es troben a ‘data’.
2.- Llegeix el fitxer Json en un dict Python.
Pista: Si no pots llegir el Json pots llegir el CSV en un dict, però has de ser capaç de transformar qualsevol dels 2.
Per transformar el Json simplement has de seleccionar només les 5 últimes columnes del fitxer CSV.
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…
from datetime import datetime
# Funció per llegir el fitxer CSV i retornar les línies com una llista de diccionarisdef llegir_csv(nom_fitxer): with open(nom_fitxer, 'r') as f: lines = f.readlines()
columnes = lines[0].strip().split(',') dades = [] for linia in lines[1:]: parts = linia.strip().split(',') dades.append({col: val for col, val in zip(columnes, parts)}) return dades
# Funció per filtrar les dades de l'any 2024def filtrar_any(dades, any): return [d for d in dades if d['Dia'].split('/')[-1] == str(any)]
# Funció per filtrar les dades de Riudecanyesdef filtrar_estacio(dades, estacio): return [d for d in dades if estacio in d['Estació']]
# Funció per ordenar les dades per data de més recent a més anticdef ordenar_dades(dades): return sorted(dades, key=lambda d: datetime.strptime(d['Dia'], '%d/%m/%Y'), reverse=True)
# Funció per obtenir màxim i mínimdef max_min_percentatge(dades): max_dada = dades[0] min_dada = dades[0]
for d in dades: percentatge = float(d['Nivell absolut (msnm)']) if percentatge > float(max_dada['Nivell absolut (msnm)']): max_dada = d if percentatge < float(min_dada['Nivell absolut (msnm)']): min_dada = d
return max_dada, min_dada
# Principalfitxer = 'aigua.csv'dades = llegir_csv(fitxer)
# Filtrar les dadesdades_2024 = filtrar_any(dades, 2024)dades_riudecanyes = filtrar_estacio(dades_2024, 'Riudecanyes')
# Ordenar les dadesdades_ordenades = ordenar_dades(dades_riudecanyes)
# Obtenir màxim i mínimmax_dada, min_dada = max_min_percentatge(dades_ordenades)
# Imprimir resultatsprint("Dades de 2024 per Riudecanyes ordenades per data:")for d in dades_ordenades: print(d)
print("\nPercentatge volum embassat màxim:")print(max_dada)
print("\nPercentatge volum embassat mínim:")print(min_dada)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
- Ves al projecte https://gitlab.com/xtec/python/aemet
- Fes un “fork” del projecte al teu compte de
- Clona el teu projecte al director arrel
- Instal.la les dependències amb
poetry update - 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.