Terzo articolo di approfondimento sul mondo delle API – Application Programming Interfaces
Oggi costruiamo (in modo semplice) un’API REST da zero, con Django, uno dei framework Python più utilizzati.
Nelle puntate precedenti abbiamo esplorato la teoria dietro le API (API, cosa sono e come funzionano), poi abbiamo costruito una web API da zero con Flask (Sviluppiamo una Web API con Python, Flask e SQLite), e con Litestar (Sviluppiamo una web API con Litestar).
Oggi faremo un ulteriore passo avanti nel nostro viaggio costruendo una RESTful API con Python e Django.
Se vuoi andare subito al risultato finale, lo trovi sul nostro GitHub (Django REST API).
Flask vs Django
Quando abbiamo costruito la nostra API con Flask abbiamo dovuto letteralmente fare tutto da zero. Dalla costruzione del nostro database SQLite, alla scrittura delle singole chiamate SQL al database per la gestione degli eventi. Il risultato che ne è venuto fuori è stato infine abbastanza mediocre, sì, funzionava tutto ma il nostro applicativo era embrionale, spoglio. La API non rispondeva ai requisiti REST.
Flask è definito un micro-framework in quanto non richiede strumenti o librerie particolari. Non ha un livello di astrazione del database, la validazione dei moduli o altri componenti per i quali librerie di terze parti preesistenti forniscono funzioni comuni.
Django è un framework di alto livello, open source, costruito su Python che incoraggia uno sviluppo rapido e un design pulito e pragmatico. Si basa sul paradigma MTV (facile da ricordare per gli ’80s kids come noi), ossia “Model-Template-View”. In questo esplicita la sua natura full-stack, in quanto gestiamo in modo olistico le interazioni tra la parte back-end (i modelli) e la parte front-end (i template) tramite viste (view). Una buona panoramica del ciclo di vita di una web request in Django è fornita a questo video.
Django come vedremo gestirà per noi la parte di heavy lifting, permettendoci di ottenere un applicativo funzionale, sicuro, RESTful e anche decente nella parte di interfaccia grafica.
Quindi Django è meglio di Flask? No. Come sempre, dipende dall’uso che se ne vuole fare e dalle finalità.
Piccola curiosità: uno dei creatori di Django, Adrian Holovaty, è un talentuoso chitarrista jazz. Per questo ha dedicato la sua creazione al chitarrista Django Reinhardt.
Premesse
Quest’articolo vuole affrontare in modo divulgativo e esemplificativo la creazione di un’API con Django, non un’applicazione production-ready.
Semplificheremo quindi alcuni passaggi e anche best-practice relative alla costruzione di un applicativo Django, ad esempio la creazione di unit test e integration test.
Ci sono tanti modi per risolvere un problema tramite Django, ma tipicamente solo uno è (o dovrebbe) essere quello giusto.
Tenere in considerazione le best practice e la struttura classica del framework è fondamentale per rendere il nostro codice mantenibile, chiaro e comprensibile anche ad altri sviluppatori che saranno soliti aspettarsi una logica a loro ben nota espressa dal nostro applicativo.
Il consiglio migliore è sempre di avere la documentazione sottomano. La community di Django è molto accogliente e viva.
Se vuoi giocare con Django e scoprirne altre funzionalità, trovi altri progetti sul mio GitHub, in particolare nel Django Boilerplate troverai diversi applicativi pronti all’uso, una wiki, un walktrought e altra documentazione costruita a scopo didattico.
Django Cheatsheet
Bene, iniziamo il nostro viaggio verso la costruzione di una RESTful API con Django.
Django presuppone l’utilizzo di una serie di comandi da terminale necessari per gestire la totalità del progetto. Visto che riportarli qui passo per passo sarebbe veramente lungo, ti lascio il link al cheatsheet che ho creato come mio memo.
In questo cheatsheet trovi le istruzioni per partire con la creazione della nostra Django API:
- Creare un ambiente virtuale.
- Installare Django.
- Creare un nuovo progetto.
- Creare un’app all’interno del progetto.
- Avviare la creazione del DB e migrare.
- Creare un superuser.
Se vuoi seguire questo progetto passo-passo, ti consiglio di nominare il tuo progetto restapi
e la tua app web_apis
.
Inoltre per evitare errori in sede di runtime ti suggerisco di installare le librerie che trovi sul requirements.txt relativo a questo progetto.
Pertanto fatto tutto ciò in modo corretto, lanciando dal terminale il comando
python manage.py runserver
e visitando la porta di default di Django su localhost (http://127.0.0.1:8000/) dovresti vedere il familiare razzo verde di Django. Partiamo!
Django REST API step by step
Questi gli step che affronteremo per la creazione della nostra Django API:
- Configurazione di base dell’applicativo.
- Creazione dei modelli sul nostro database.
- Configurazione dei servizi REST e integrazione con admin dashboard.
- Creazione degli oggetti e delle viste associate ad essi.
- Creazione degli URI endpoint che permetteranno di ottenere i dati.
Configurazione generale dell’applicativo Django
Per prima cosa, liberiamoci della SECRET_KEY in chiaro esposta in settings.py
creando un file .env
.
Io utilizzo la libreria python-dotenv ma va bene qualsiasi metodo che tu ritenga adatto.
Per la creazione della nuova SECRET_KEY puoi usare il divertente Djecrety – Django Secret Key Generator.
#settings.py import os from dotenv import load_dotenv, find_dotenv load_dotenv(find_dotenv()) SECRET_KEY = os.environ['SECRET_KEY']
'rest_framework'
che avremmo importato a inizio progetto.# settings.py INSTALLED_APPS = [ ...', 'rest_framework', 'web_apis', ]
Django REST API: creiamo i nostri modelli
Entriamo nel vivo del nostro progetto. La creazione dei modelli è il cuore dell’applicativo. Adesso stiamo facendo qualcosa di molto semplice, ma per architetture più complesse consiglio di partire con uno studio di alto livello, anche tracciando su carta le relazioni tra le diverse tabelle.
Per questo esempio, ho creato un database che gestisce l’identità di alcuni supereroi, di alcuni super cattivi, e associa a ciascun supereroe un super cattivo. Un esempio semplice ma che esplora già alcune feature interessanti a livello di relazioni tra oggetti.
# web_apis/models.py class Hero(models.Model):
real_name = models.CharField(max_length=50) hero_name = models.CharField(max_length=50) def __str__(self): return f"Real Name: {self.real_name}, Hero Name: {self.hero_name}" class Villain(models.Model): villain_name = models.CharField(max_length=50) def __str__(self): return self.villain_name class Archenemy(models.Model): hero = models.ForeignKey(Hero, on_delete=models.CASCADE) villain = models.ForeignKey(Villain, on_delete=models.CASCADE) def __str__(self): return f"Hero {self.hero.hero_name} hates {self.villain.villain_name}"
É tutto abbastanza chiaro e autoesplicativo. Siamo in pieno paradigma OOP (Object Oriented Programming).
Il metodo __str__
rappresenta l’oggetto come una stringa, ossia lo rende leggibile a schermo. Ci si riferisce spesso a questi metodi come dunder methods, (metodi double under_score).
Qui apprezziamo anche la potenza dello Django ORM (object-relational mapper). Tramite l’ORM possiamo interagire con il nostro database relazionale senza scrivere queries SQL riga per riga. Dopo aver eseguito le migrazioni, possiamo dare uno sguardo sotto al cofano cercando il folder migrations. Qui troveremo ordinatamente tutti i comandi che l’ORM ha eseguito per noi, sia per creare che per modificare le singole tabelle del nostro database.
Andiamo adesso a registrare i nostri metodi nella sezione amministrativa, così possiamo interagire con essi dal back-end del nostro applicativo risparmiandoci tanta fatica.
# web_apis/admin.py from django.contrib import admin
from . import models admin.site.register(models.Hero) admin.site.register(models.Villain) admin.site.register(models.Archenemy)
Popoliamo il nostro database
- Lanciando una shell e scrivendo le query una per una (se vuoi divertirti facendo pratica).
- Accedendo al back-end tramite pannello admin (http://127.0.0.1:8000/admin) e le credenziali che avrai creato all’inizio del progetto. Se tutto fila liscio vedrai i tre modelli che abbiamo creato sopra (eroi, cattivi, e nemici giurati). Inserisci i caratteri a tuo piacimento, basta averne un tre-quattro per poter testare la parte successiva.
Seriliazziamo i nostri modelli dati
Come fa la nostra REST API a ritornare i dati in formato JSON, se noi li abbiamo forniti in una tabella SQL? Dobbiamo serializzarli.
La serializzazione è il processo di conversione di una struttura di dati o di un oggetto in un formato che può essere memorizzato in un file o in un buffer di memoria, o trasmessi attraverso un collegamento di rete per essere ricostruiti in un secondo momento nello stesso o in un altro ambiente. É un processo alla base della creazione di oggetti fruibili tramite API.
Per prima cosa quindi, nel folder della nostra app, creiamo un nuovo file, serializers.py. All’interno di questo, inseriamo le classi relative ai nostri modelli.
# web_apis/serializers.py from rest_framework import serializers from . import models
class HeroSerializer(serializers.HyperlinkedModelSerializer): """ API endpoint that allows groups to be viewed or edited. """ class Meta: model = models.Hero fields = '__all__' class VillainsSerializer(serializers.HyperlinkedModelSerializer): """ API endpoint that allows groups to be viewed or edited. """ class Meta: model = models.Villain fields = '__all__' class ArchenemySerializer(serializers.HyperlinkedModelSerializer): """ API endpoint that allows groups to be viewed or edited. """ id = serializers.IntegerField(read_only=True) hero = serializers.StringRelatedField() villain = serializers.StringRelatedField() class Meta: model = models.Archenemy fields = '__all__'
Questa parte si presta ad alcuni bivi architetturali, che avranno conseguenze poi su come si andrà a interagire con la nostra API.
In questo caso abbiamo optato per offrire un livello di astrazione che esprime le relazioni tramite URL piuttosto che tramite chiavi primarie. Il tema è piuttosto vasto, puoi iniziare ad approfondire da qui (HyperlinkedModelSerializer in serializers).
Django REST API: creiamo le viste
# web_apis/views.py from django.shortcuts import render from rest_framework import viewsets from . import models, serializers
class HeroViewSet(viewsets.ModelViewSet): queryset = models.Hero.objects.all().order_by('hero_name') serializer_class = serializers.HeroSerializer class VillainViewSet(viewsets.ModelViewSet): queryset = models.Villain.objects.all().order_by('villain_name') serializer_class = serializers.VillainsSerializer class ArchenemyViewSet(viewsets.ModelViewSet): queryset = models.Archenemy.objects.all().order_by('hero') serializer_class = serializers.ArchenemySerializer
Qui ricorriamo alle classi ViewSet
messe a disposizione da Django Rest Framework. Concettualmente sono controller che permettono la creazione delle viste, partendo dai modelli definiti in precedenza.
Chiudiamo anche il cerchio importando e collegando i nostri oggetti serializzati alle rispettive viste.
Creiamo gli URLs per la nostra API
# urls.py from django.contrib import admin from django.urls import path, include urlpatterns = [
path('admin/', admin.site.urls), path('', include('web_apis.urls')), ] # web_apis/urls.py from django.urls import include, path from rest_framework import routers from . import views
router = routers.DefaultRouter() router.register(r'heroes', views.HeroViewSet) router.register(r'villains', views.VillainViewSet) router.register(r'archenemies', views.ArchenemyViewSet) # wire up our API using automatic URL routing. # additionally, we include login URLs for the browsable API. urlpatterns = [ path('', include(router.urls)), path('api-auth/', include('rest_framework.urls', namespace='rest_framework')) ]
Vediamo qui una serie di comandi abbastanza atipici: router.register
.
I router sono uno strumento incluso nel Django REST Framework (DRF) veramente potente. Gestiscono in modo dinamico, in accoppiata con le viewset
che abbiamo incluso nel nostro views.py
poco sopra, le richieste ai singoli URLs in modo consequenziale fino ad aggiornare il nostro database. In altre parole permettono i comandi GET, POST, PUT, PATCH, etc.
Django REST API: proviamolo!
Ci siamo! Abbiamo concluso la prima tappa del nostro viaggio alla scoperta delle API REST con Django.
Ora possiamo avviare il nostro applicativo con il solito runserver
, navigare su localhost
e interagire con esso.
L’interfaccia è già presentabile, abbiamo modo di scegliere se ricevere i dati in formato JSON grezzo o tramite GUI, cliccando sui diversi URL endpoint arriviamo ai singoli oggetti, quindi gli eroi, i cattivi e la lista di nemici giurati.
Vediamo che, a livello di oggetto, possiamo modificare il dato o creare addirittura dei nuovi inserimenti, tutto tramite interfaccia web.
Naturalmente tutto è funzionale anche per semplici interazioni tramite URL nella barra del browser, quindi pronto per interazioni con altri applicativi.