Entwerfen gut strukturierter REST-APIs mit Flask-RestPlus: Teil 1

Foto: Simone Viani @ Unsplash

Hinweis: Dieser Artikel wird in Kürze ausschließlich in meinem Blog verfügbar sein. Bevor ich es von Medium entferne, möchte ich alle bitten, dort weitere Kommentare und Feature-Anfragen zu verfassen. Vielen Dank!

Dies ist der erste Teil einer zweiteiligen Serie. In diesem Beitrag stelle ich Flask-RestPlus vor und zeige, wie man APIs anhand ihrer einfachen REST-basierten Konventionen organisiert. Das nächste Mal werde ich mich mit dem Thema Anforderungs- / Antwort-Marshalling (Serialisierung) und Validierung befassen.

Als erfahrener Spring-Entwickler fühlte ich mich etwas unwohl, als ich zum ersten Mal eine Flask-basierte API entwarf und zukunftssicher machte. Ich habe kürzlich begonnen, Python zu verwenden, weit über meine ursprüngliche Absicht hinaus, nur mit Daten zu spielen, und Flask als supereinfache Micro-Service-Alternative zu Spring Boot oder Ktor befunden. Das einzige, worüber ich mir wirklich Sorgen machte, war, sicherzustellen, dass das API-Anforderungs- / Antwortformat standardisiert, gut dokumentiert und validiert war (siehe Swagger-Schema). Während der Arbeit mit Java würde ein Großteil davon direkt vom Compiler selbst stammen, da die Sprache vom statischen Typ ist. Wenn Sie dies mit einigen großartigen Bibliotheken wie Jackson und SpringFox kombinieren, wird die API-Kommunikation mit minimalem Eingriff in den eigentlichen Code dokumentiert und validiert. In Python würde dies mühsame Prüfungen erfordern, wenn-sonst überall ... oder so, dachte ich.

Flask-RestPlus zur Rettung

Im Gegensatz zu Django wird Flask nicht mit Batterien geliefert, aber es gibt ein ganzes Ökosystem von Open-Source-Bibliotheken und -Erweiterungen, die von der Community beigesteuert wurden. Eines davon heißt Flask-RestPlus und ist der absolute Traum eines jeden Flask-API-Designers. Flask-RestPlus ist eine Flask-Erweiterungsbibliothek, die, wie der Name schon sagt, die Erstellung strukturierter RESTful-APIs mit minimalem Setup erleichtert und Best Practices fördert. Flask RestPlus folgt bestimmten Konventionen, besteht aber nicht darauf, wie Django es tut. In gewisser Weise versucht Flask-RestPlus dabei zu helfen, ein wachsendes Flask-Projekt zu organisieren, ohne jedoch den minimalen Overhead zu verlieren, der den größten Charme von Flask ausmacht.

Das Ziel dieser Serie ist es, mit einer einfachen Flask-App zu beginnen und zu versuchen, die folgenden Punkte gleichzeitig mit etwas Flask-RestPlus zu lösen:

  1. Eine API strukturieren und automatisch dokumentieren (Teil 1)
  2. Validierung der Nutzdaten für Anforderung / Antwort sicherstellen (Teil 2)

Demo App

Beginnen wir mit einer einfachen Flask-basierten API für eine Konferenzverwaltungsanwendung:

aus der Flasche importieren

app = Flasche (__ name__)


@ app.route ("/ meetings /")
def get_all__conferences ():
    "" "
    gibt eine Liste von Konferenzen zurück
    "" "


@ app.route ("/ meetings /", methods = ['POST'])
def add_conference ():
    "" "
    Fügt der Liste eine neue Konferenz hinzu
    "" "


@ app.route ("/ meetings / ")
def get_conference (id):
    "" "
    Zeigt die Details einer Konferenz an
    "" "
@ app.route ("/ meetings / ")
def edit_conference (id):
    "" "
    Bearbeitet eine ausgewählte Konferenz
    "" "

Die Installation von Flask-RestPlus ist ganz einfach:

Pip installieren Flask-RestPlus

Stellen wir einfach ein Api-Objekt vor, versuchen Sie, unsere App-Instanz damit zu verpacken, ersetzen Sie die Routing-Dekoratoren und sehen Sie, was passiert:

aus der Flasche importieren
aus flask_restplus importieren Api
app = Flasche (__ name__)
api = Api (app = app)
@ api.route ("/ meetings /")
def get_all__conferences ():
    "" "
    gibt eine Liste von Konferenzen zurück
    "" "
@ api.route ("/ meetings /", methods = ['POST'])
def add_conference ():
    "" "
    Fügt der Liste eine neue Konferenz hinzu
    "" "
@ api.route ("/ meetings / ")
def get_conference (id):
    "" "
    Zeigt die Details einer Konferenz an
    "" "
@ api.route ("/ meetings / ")
def edit_conference (id):
    "" "
    Bearbeitet eine ausgewählte Konferenz
    "" "

Sobald die App startet, erhalten wir folgende Fehlermeldung:

AttributeError: Objekt 'function' hat kein Attribut 'as_view'

Dies liegt daran, dass Sie, wenn Sie RestPlus für einige Ihrer Flask-Funktionen verwenden möchten, diese in eine Scoping-Klasse einschließen müssen. Nicht nur das, sondern innerhalb der einschließenden Klasse sollten Sie Ihre Methoden benennen, die den HTTP-Methoden entsprechen, auf denen REST basiert: GET, POST, PUT und DELETE:

@ api.route ("/ meetings /")
Klasse ConferenceList (Ressource):
    def get (self):
        "" "
        gibt eine Liste von Konferenzen zurück
        "" "

Lassen Sie mich erklären, warum dies hilfreich ist, bevor jemand Einwände erhebt. Flask-RestPlus verwendet das Flask-Konzept von „Pluggable Views“, um die Ressource einzuführen (wie in REST-Ressource).

Lass uns ehrlich sein. Während die meisten Flask-Anwendungen einfach beginnen, wachsen viele von ihnen aus der ursprünglichen Idee heraus, und das Überfüllen mehrerer Handlerfunktionen im Bereich des Hauptmoduls wird schnell zu einem Chaos. Aus diesem Grund gibt es Flask Blueprints, um die gemeinsame Funktionalität in mehrere Module aufzuteilen.

Flask-RestPlus nutzt auch Blueprints in großem Umfang, wie ich später zeigen werde, aber die Ressourcen gehen eine Granularitätsstufe weiter. Eine Ressourcenklasse kann mehrere Methoden haben, aber jede sollte nach einem der akzeptierten HTTP-Verben benannt werden. Was ist, wenn Sie mehr als eine GET- oder POST-Methode für Ihre API benötigen? Nun, erstellen Sie mehrere Ressourcenklassen und ordnen Sie jede Methode der entsprechenden Ressourcenklasse zu. Es mag auf den ersten Blick ein wenig überwältigend aussehen, da Flask so einfach zu bedienen ist, aber mit ein bisschen Herumspielen wird es kein Problem sein und sich auf lange Sicht enorm auszahlen.

Mal sehen, wie unsere winzige App nach den Transformationen aussehen wird:

aus der Flasche importieren
aus flask_restplus importieren Api, Ressource
app = Flasche (__ name__)
api = Api (app = app)
@ api.route ("/ meetings /")
Klasse ConferenceList (Ressource):
    def get (self):
        "" "
        gibt eine Liste von Konferenzen zurück
        "" "
    def post (self):
        "" "
        Fügt der Liste eine neue Konferenz hinzu
        "" "
@ api.route ("/ meetings / ")
Klasse Konferenz (Ressource):
    def get (self, id):
        "" "
        Zeigt die Details einer Konferenz an
        "" "
    def put (self, id):
        "" "
        Bearbeitet eine ausgewählte Konferenz
        "" "

Mit diesem winzigen Aufwand (wenn Sie diesen überhaupt in Betracht ziehen) erhalten Sie so viel Gegenleistung. Starten Sie die App und zeigen Sie auf http: // localhost: 5000. Sie werden sehen, dass aus der Indexseite eine Swagger-Benutzeroberfläche geworden ist, die die bereits definierten API-Endpunkte zeigt, die übersichtlich in Kategorien (Namespaces) organisiert sind:

Dies ist ideal, um das API-Schema zu dokumentieren, damit zu spielen und es mit anderen zu teilen. Dies ist jedoch bei weitem nicht das einzige, was Flask-RestPlus für Sie erledigt. Es geht über die bloße Dokumentation der API hinaus, indem sichergestellt wird, dass die API mit dem Schema kompatibel ist. Vereinfacht ausgedrückt stellt Flask-RestPlus sicher, dass bestimmte Anforderungsparameter, die als obligatorisch gekennzeichnet sind oder wenn Anforderungs- / Antwortmodelle eine bestimmte Struktur haben sollen, zur Laufzeit überprüft und validiert werden. Meiner Meinung nach ist dies ein echter Vorteil von Flask-RestPlus, der auf einer Flask-Anwendung sitzt. Das aktuelle Beispiel ist zu einfach, um die tatsächliche Leistungsfähigkeit des Anforderungs- / Antwort-Marshalling und der Validierung zu demonstrieren, aber beide werden in Teil 2 ausführlich beschrieben.

Namespaces

Namespaces sind optional und verleihen der API eine zusätzliche organisatorische Note, vor allem aus Dokumentationssicht. Mit einem Namespace können Sie verwandte Ressourcen unter einem gemeinsamen Stamm gruppieren und auf einfache Weise erstellen:

ns_conf = api.namespace ('Konferenzen', description = 'Konferenzbetrieb')

Um bestimmte Ressourcen unter einen bestimmten Namespace zu bringen, müssen Sie lediglich @api durch @ns_conf ersetzen. Beachten Sie auch, dass der Name des Namespace den Namen der Ressource ersetzt, sodass Endpunkte einfach auf / verweisen können, anstatt den Namen der Ressource immer wieder zu kopieren:

aus der Flasche importieren
aus flask_restplus importieren Api, Ressource
app = Flasche (__ name__)
api = Api (app = app)
ns_conf = api.namespace ('Konferenzen', description = 'Konferenzbetrieb')
@ ns_conf.route ("/")
Klasse ConferenceList (Ressource):
    def get (self):
        "" "
        gibt eine Liste von Konferenzen zurück
        "" "
    def post (self):
        "" "
        Fügt der Liste eine neue Konferenz hinzu
        "" "
@ ns_conf.route ("/ ")
Klasse Konferenz (Ressource):
    def get (self, id):
        "" "
        Zeigt die Details einer Konferenz an
        "" "
    def put (self, id):
        "" "
        Bearbeitet eine ausgewählte Konferenz
        "" "

Man wird später bemerken, dass sich auch die Anzeige der Swagger-Benutzeroberfläche geändert hat, um den Namensraum widerzuspiegeln:

Blaupausen

Flask Blueprints sind eine beliebte Methode zum Entwerfen modularer Anwendungen. Gleiches gilt für Flask-RestPlus. Die Produktionsversion unserer Anwendung wird mit Sicherheit über die vier Endpunkte hinauswachsen, mit denen wir begonnen haben. Möglicherweise gibt es andere Ressourcen, oder Sie möchten zumindest Ihre API aus dem Stammverzeichnis Ihrer App entfernen. Beide Fälle sind ein perfekter Kandidat für eine Blaupause. Verschieben wir alle unsere API-Endpunkte unter / api / v1, ohne auch nur die Routen eines von ihnen zu berühren. Dieses Beispiel stammt direkt aus der Flask-RestPlus-Dokumentation und ist illustrativ genug, um dieses Kapitel der Reise zu schließen:

Erstellen Sie einen Blueprint auf die übliche Weise, und anstatt unsere App-Instanz mit der RestPlus-API zu verpacken, verpacken wir stattdessen den Blueprint. Auf diese Weise können wir unseren API-Teil unabhängig von unserer App in ein anderes Modul verschieben: (z. B. blueprint / api.py)

aus der Flasche importieren Blueprint
aus flask_restplus importieren Api
blueprint = Blueprint ('api', __name__)
api = Api (Blaupause)
# Geben Sie hier den Rest unseres API-Codes ein

So bleibt nur ein winziger Überbrückungscode, um den Blueprint in die Haupt-App einzuführen und das URL-Präfix festzulegen. Beim nächsten Start Ihrer App sind die API-Endpunkte nur unter dem angegebenen URL-Präfix (/ api / v1) verfügbar.

aus der Flasche importieren
von apis blueprint als api importieren
app = Flasche (__ name__)
app.register_blueprint (api, url_prefix = '/ api / 1')

Last but not least ist es immer eine gute Idee, die Swagger-Benutzeroberflächendokumentation von der Wurzel zu entfernen. Wie alles in RestPlus ist auch dieser Teil extrem einfach. Sie können den Standardspeicherort überschreiben, indem Sie dem Initialisierer einen zusätzlichen Parameter übergeben:

api = Api (app = app, doc = '/ docs')

Dies fasst den ersten Teil meiner Serie zusammen. Ich hoffe, dass es informativ war und Ihnen helfen wird, Ihre flaschenbasierten REST-APIs in Zukunft besser zu strukturieren. Bis zum nächsten Mal!

Weitere Lektüre