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']
proseguiamo aggiungendo le nostre app. Come vedi oltre all’app appena creata, inseriamo anche il nostro '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)
Ora creiamo le migrazioni e migriamo tutto (trovi i comandi nel cheatsheet se non li ricordi) e via alla fase successiva.

Popoliamo il nostro database

Finalmente possiamo creare i nostri eroi e i nostri super cattivi. Hai due modi per farlo:
  • 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

Ok, abbiamo già fatto buona parte del lavoro. Ora dobbiamo istruire il nostro applicativo a gestire le relazioni tra i dati back-end e il front-end. Ossia, dobbiamo creare le nostre 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

Per prima cosa estendiamo gli URL a livello ROOT del nostro applicativo includendo massivamente gli URL che genereremo all’interno della nostra app. A questo punto per un mero tema di ordine cronologico, la nostra applicazione andrà in crash in quanto stiamo includendo un file che non abbiamo ancora creato. Poco male, risolviamo in un attimo.
# 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.

Beh non male come risultato che ne pensi? In forse neanche un’ora di lavoro abbiamo creato la nostra REST API da zero, e l’abbiamo resa fruibile online.
Il viaggio nel mondo delle Web APIs con Django è appena iniziato, ora non ti resta che proseguire nella scoperta!

Per approfondire

aziona risorse ebook guida al debito tecnico

Scarica l’ebook “La guida definitiva alla comprensione del Debito Tecnico”

Iscriviti alla newsletter e scarica l’ebook.

Ricevi aggiornamenti, tips e approfondimenti su tecnologia, innovazione e imprenditoria.