Djangos leichtgewichtiger Bruder

2
Sep

RESTful APIs mit dem Webframework Flask

Das Python-Framework Flask verfolgt einen minimalistischen Ansatz und eignet sich mit kurzen Reaktionszeiten gut für Microservices und Programmierschnittstellen. RESTful APIs haben sich mittlerweile als Standard für die Kommunikation unter Maschinen durchgesetzt. Dieser Artikel zeigt, wie Sie solche Interfaces mit Flask aufsetzen können, inklusive objektrelationaler Datenbankanbindung mit SQLAlchemy.

Programmierschnittstellen spielen heutzutage eine große Rolle im Internet. Neben der normalen Benutzung über Seiten im Browser wird mittlerweile zu jedem guten Web-Service ein API (Application Programming Interface) angeboten, über das Anwender alternativ auf Angebote zugreifen können. Das ist zum Beispiel der Fall bei Twitter, Wikipedia, GitHub, DigitalOcean und vielen anderen, und auch CMS-Systeme wie WordPress lassen sich über externe Schnittstellen ansprechen und nutzen. Andere Services wie zum Beispiel Amazons Objektspeicher S3 sind als Geschäftsmodell direkt als API aufgesetzt. Auch wenn es nicht ausgeschlossen ist, sind diese als Interface aufgesetzten Zugangslösungen eigentlich nicht für den händischen Gebrauch mit bestimmten Standardwerkzeugen gedacht, sondern dienen vor allem dazu, mittels speziell dafür geschriebener Software angesprochen zu werden. Die angebotenen APIs richten sich dabei vor allem an Entwickler von Endanwenderlösungen, um für den verfügbaren Service speziell konfektionierte Applikationen herzustellen oder in eigene Angebote wie etwa Metaportale zu integrieren. Prinzipiell steht es dabei aber jedem Benutzer offen, sich selbst Programme zu schreiben mit denen er APIs anzapft um Daten auszulesen oder zu schreiben, und die für ein Webportal erworbene Zugangsberechtigung gilt meistens auch für diesen alternativen Kommunikationsweg. Die Anbieter von Web-Services auf der anderen Seite sind vor allem aber daran interessiert, welche Mittel gut dafür geeignet sind, um ein API aufzusetzen und zur Verfügung zu stellen.

Werde Teil der API-Revolution!
Alle News & Updates zur API Conference 2019

Der Standard für Programmierschnittstellen, der seit einigen Jahren verstärkt eingesetzt wird und der sich neben Konkurrenten wie SOAP und XML-RPC mittlerweile durchgesetzt hat, ist REST (Representational State Transfer) [1]. Bei dem im Jahr 2000 entwickelten REST handelt es sich aber nicht um eine Spezifikation, sondern um eine Richtlinie beziehungsweise ein Designprinzip für die Kommunikation von Maschinen, das gut oder schlecht umgesetzt werden kann (im Englischen gibt es dafür den Ausdruck „RESTfulness“). Der theoretische Hintergrund von RESTful APIs ist ein Thema für sich, denn es handelt sich um einen abstrakten Architekturstil, der eine konkrete Implementierung eigentlich nicht vorgibt, und die jeweilige Anwendungslogik muss von Entwicklern selbst auf die REST-Prinzipien abgebildet werden. In der Praxis bedeutet das, zwischen den bei einem Service beteiligten Komponenten eine zusätzliche Schicht einzufügen, die unabhängig von der spezifischen Infrastruktur als Abstraktionsebene funktioniert. Für den API-Endanwender spielt es dann keine Rolle, wie das Schnittstellen-Backend technisch realisiert ist. Als Grundlage für die Umsetzung der im Hintergrund stehenden Prinzipien setzen RESTful APIs als Web-Services konkret auf das bewährte, zustandslose Webprotokoll HTTP und machen sich dessen Eigenschaften wie die eingebauten Statuscodes zunutze. In Bezug auf Datensätze werden die HTTP-Methoden POST, GET, PUT/PATCH und DELETE verwendet, um die Elemente des CRUD-Zyklus (Create, Read, Update, Delete) umzusetzen. Dabei werden Ressourcen im allgemeinen Sinn, wie zum Beispiel Datenbankeinträge, als eindeutige Webadressen zur Verfügung gestellt (etwa http://beispiel.de/rechnungen/2018/07), die Sie mit beliebigen HTTP-Klienten wie curl oder HTTPie ansprechen und manipulieren können. Bei einem RESTful API liegt der Fokus auf den repräsentierten Objekten und das Interface erklärt sich dabei praktisch selbst – und das ist, alles in allem, ein attraktives Konzept mit vielen positiven Effekten. So können sich z. B. externe Entwickler und neue Mitarbeiter schnell in die damit umgesetzte Anwendungslogik einarbeiten.

Flask

Bei Flask [2] („Fläschchen“) handelt es sich um ein BSD-lizenziertes Framework für Python, mit dem Sie beliebige Webapplikationen schreiben können [3]. Anders als sein „großer Bruder“, das Python-Webframework Django, ist Flask auf Minimalität angelegt: Es wird nicht automatisch immer eine Datenbank benötigt, und es gibt nur wenige Grundabhängigkeiten, zu denen das WSGI-Toolkit „Werkzeug“ gehört und die Template-Engine Jinja2, mit der das Framework dynamische HTML-Seiten rendert. Für ausgewachsene Webapplikationen steht für Flask eine ganze Reihe von Erweiterungspaketen zur Verfügung, wie etwa eine Administrationsoberfläche und ein Benutzermanagement, Anbindungen an SQL- und NoSQL-Datenbanken, Pakete für Twitter Bootstrap, WTForms, Bcrypt und vieles mehr. Der minimalistische Grundansatz als „Mikroframework“ und die daraus resultierenden schnellen Reaktionszeiten prädestinieren Flask aber geradezu dazu, damit Microservices [4] und Webinterfaces [5] aufzusetzen. Flask ist als Python-Bibliothek implementiert und Sie können es über die übliche Wege auf den Arbeitsrechner bekommen: als Paket für Ihre Linux-Distribution, über den Python Package Index, direkt aus dem Code-Repository bei GitHub [6], oder auf noch anderen Wegen. Ein minimales Beispiel (Listing 1) verdeutlicht, wie Flask angewendet wird. Gegenüber dem üblichen statischen „Hello, World!“-Beispiel hier direkt eine kleine Applikation, die dynamische Endpunkte und GET-Argumente vom Klienten auswertet.

Importieren Sie zunächst die benötigten Komponenten aus der Bibliothek flask. Für die Hauptklasse Flask benötigen Sie zunächst natürlich eine Instanz, und dafür wird üblicherweise die Variable app und das Argument __name__ benutzt, wie hier auch. Beliebige Funktionen wie flasktest() im Beispiel stellen die eigentliche Funktionalität für die Applikation zur Verfügung, und mit dem Decorator @app.route können Sie dafür beliebige Routen festlegen. Anstatt statische Endpunkte anzusetzen, können Sie auch umgekehrt die Routen aus den HTTP-Anfragen evaluieren – dafür sind zwischen Größer-/Kleinerzeichen geschriebene Variablen zu verwenden wie <myroute> im Beispiel. Zusätzlich können Sie mit dem Parameter methods eine oder mehrere HTTP-Methoden angeben, die auf einen Endpunkt angewendet werden können. GET ist allerdings Default und Sie können das auch weglassen, wenn das die einzige Methode sein soll (siehe Listing 3). Das Beispiel übernimmt den vom Klienten gelieferten Endpunkt und wertet zunächst ein mitgegebenes GET-Argument (<myroute>?argument=foo) mit request.args.get() aus. Beide Informationen werden dann in das Dictionary return_this geschrieben und dieses über die eingebaute Funktion jsonify() als JSON-Objekt mit dem passenden MIME-Type im Returnheader zurückgegeben. Die Überprüfung von __name__ am Schluss ist eine bewährte Praxis für Python-Applikationen, mit der verhindert wird, dass die App startet, wenn Sie das Skript aus Versehen oder probeweise als Bibliothek aufrufen. Die Methode app.run() schließlich startet den in Werkzeug eingebauten Server für die Flask-Applikation. Den gewünschten Port und ob die Debugging-Ausgabe eingeschaltet werden soll können Sie hier als Argumente mitgeben, wie im Beispiel in der letzten Zeile.

Starten Sie dieses Skript und sprechen Sie dann die zunächst nur lokal laufende Applikation über einen Webbrowser oder einen anderen HTTP-Klienten wie curl an (Listing 2). Die auf dem eingebauten WSGI-Server von Werkzeug laufende Flask-App (siehe Server im Returnheader) gibt dann einfach ein JSON-Objekt mit den aus der Anfrage ausgewerteten Informationen zurück. Für Entwicklungszwecke reicht der eingebaute Miniserver vollkommen aus, in Produktion sollten Sie allerdings entweder den WSGI-Server Gunicorn [7] oder eine andere robuste Lösung einsetzen, wie zum Beispiel Apache mit der Erweiterung mod_wsgi. In diesem Beispiel haben Sie bereits einige wichtige Bausteine für ein RESTful API mit Flask versammelt.

Listing 1: flasktest.py
#!/usr/bin/python3
from flask import Flask, request, jsonify
app = Flask(__name__)

@app.route('/', methods=['GET'])
def flasktest(myroute):
  myargument = request.args.get('argument')
  return_this = {'Route': myroute, 'Argument': myargument}
  return jsonify(return_this)

if __name__ == "__main__":
  app.run(port=5000, debug=True)
Listing 2
$ curl -i 127.0.0.1:5000/testroute?argument=hallo
HTTP/1.0 200 OK
Content-Type: application/json
Content-Length: 46
Server: Werkzeug/0.11.15 Python/3.5.3
Date: Fri, 03 Aug 2018 19:17:01 GMT

{
  "Argument": "hallo",
  "Route": "testroute"
}
 

POST Requests

Ein weiteres kleines Beispiel einer experimentellen Gebrauchtwagenliste mit eingebetteten Dictionaries (Listing 3) verdeutlicht, wie Sie die POST-Methode von HTTP verwenden, um Datensätze anzulegen und in Flask HTTP-Statuscodes für die Rückmeldungen von dem API einzusetzen. Der Endpunkt /autos, wenn standardmäßig über die GET-Methode angesprochen, gibt mit get_autos() immer die gesamte Liste aller aufgenommenen Fahrzeuge zurück, mit POST angesprochen können Sie ihn allerdings dazu benutzen, neue Datensätze anzulegen – das erledigt dann add_auto(). Der Inhalt eines POST Request wird mit request_get_json() ausgelesen, woraufhin das Programm zuerst einmal die Funktion checkPostObjekt() anwendet, um zu überprüfen, ob die Eingabe zumindest einem Minimalstandard von erwarteten Anforderungen (ein dreistelliges Dictionary mit modell, preis und baujahr zu sein, siehe Listing 4) entspricht. Wenn das der Fall ist, wird dem evaluierten und dem von request_get_json() in Python übertragenen Dictionary ein UUID als id hinzugefügt und der Datensatz mit der Python-Funktion append() der Liste autos angehangen. Mit der Response-Klasse können Sie den HTTP-Statuscode 201/CREATED verwenden und bei Erfolg zurückgeben lassen, wie im Beispiel als success. Der Status 400/BAD REQUEST (die 400er-Statuscodes sind immer Klientenfehler) wird ausgegeben, falls das übermittelte JSON-Objekt nicht den Kriterienkatalog in checkPostObject() erfüllt, und der Datensatz dann vom Programm einfach verworfen. Das Beispiel bietet noch einen weiteren Endpunkt, um die gespeicherten Gebrauchtwagen mit auto_by_id() direkt über ihre ID auszulesen, wobei wieder der Statuscode 400 ausgegeben wird, falls eine ID nicht bekannt ist. Im Rückgabeheader eines POST Request gibt die App auch noch den URI eines neu hinzugefügten Datensatzes als zusätzliche Information unter Location zurück, was Sie bei Flask einfach mit der headers()-Methode der Response-Klasse erreichen können. Dieses Vorgehen erfüllt das REST-Prinzip und der zurückgegebene URL kann später von einer speziell für dieses API aufgesetzten Clientsoftware direkt aus der Rückgabe vom Server ausgelesen und ausgewertet werden.

Wenn die Flask-App läuft, können Sie zum Beispiel das Linux-Kommandozeilenwerkzeug curl [8] verwenden, um POST Requests an den Server abzusetzen (Listing 4). Weitere Funktionen, um bereits angelegte Datensätze zu überschreiben, zu verändern und zu löschen, können Sie auf die gleiche Weise aufsetzen, der <id>-Endpunkt müsste dabei nur noch mit weiteren HTTP-Methoden (PUT, PATCH und DELETE) ansprechbar gemacht werden.

Listing 3: „gebrauchtwagen1.py“
#!/usr/bin/env python3
from flask import Flask, request, jsonify, Response
import uuid

app = Flask(__name__)
autos = []

@app.route('/autos')
def get_autos():
  return jsonify({'gebrauchtwagen': autos})

def checkPostObjekt(post_objekt):
  regeln = [len(post_objekt) == 3,
          'modell' in post_objekt,
          'preis' in post_objekt,
          'baujahr' in post_objekt]
  if all(regeln):
    return True
  else:
    return False

@app.route('/autos', methods=['POST'])
def add_auto():
  post_content = request.get_json()
  if checkPostObjekt(post_content):
    post_content['id'] = uuid.uuid4().hex
    autos.append(post_content)
    success = Response('', status=201)
    success.headers['Location'] = '/autos/' + post_content['id']
    return success
  else:
    failure = Response('', status=400)
    return failure

@app.route('/autos/')
def auto_by_id(id):
  return_object = {}
  for auto in autos:
    if auto['id'] == id:
      return_object = auto
  if not return_object:
    failure = Response('', status=400)
    return failure
  else:
    return jsonify(return_object)

app.run(port=5000)

Listing 4
$ curl 127.0.0.1:5000/autos --include --request POST --header 'Content-Type: application/json' --data '{"modell": "Opel Caravan", "preis": 2600}'
HTTP/1.0 400 BAD REQUEST
Content-Type: text/html; charset=utf-8
Content-Length: 0
Server: Werkzeug/0.11.15 Python/3.5.3
Date: Sat, 04 Aug 2018 18:15:18 GMT

$ curl 127.0.0.1:5000/autos --include --request POST --header 'Content-Type: application/json' --data '{"modell": "Opel Caravan", "preis": 2600, "baujahr": 1992}'
HTTP/1.0 201 CREATED
Content-Type: text/html; charset=utf-8
Content-Length: 0
Location: http://127.0.0.1:5000/autos/cfc2ccd94bbe41c7b86dc33a6cb051a3
Server: Werkzeug/0.11.15 Python/3.5.3
Date: Sat, 04 Aug 2018 18:15:42 GMT

$ curl http://127.0.0.1:5000/autos/cfc2ccd94bbe41c7b86dc33a6cb051a3
{
  "baujahr": 1992,
  "modell": "Opel Caravan",
  "preis": 2600
}

$ curl 127.0.0.1:5000/autos --request POST --header 'Content-Type: application/json' --data '{"modell": "Mercedes-Benz Unimog", "preis": 4500, "baujahr": 1987}'

$ curl http://127.0.0.1:5000/autos
{
  "gebrauchtwagen": [
    {
      "baujahr": 1992,
      "id": "cfc2ccd94bbe41c7b86dc33a6cb051a3",
      "modell": "Opel Caravan",
      "preis": 2600
    },
    {
      "baujahr": 1987,
      "id": "71222944242a4bedbbbe9ca6d31e5589",
      "modell": "Mercedes-Benz Unimog",
      "preis": 4500
    }
  ]
}

SQLAlchemy

Mit Flask-SQLAlchemy [9] gibt es für Flask eine Anbindung an die Datenbankbibliothek SQLAlchemy [10], die Sie sehr gut für API-Projekte einsetzen können. SQLAlchemy bietet objektrelationale Anbindung an eine Reihe von Datenbanksystemen. Dem Anwender bringt die Abstraktion von einer darunterliegenden MySQL-Datenbank zum Beispiel, dass Sie damit im Python-Code nicht sperrig mit SQL-Sequenzen hantieren müssen. Eine Abwandlung des vorherigen Gebrauchtwagenbeispiels (Listing 5) demonstriert, wie Sie Flask-SQLAlchemy mit einer SQLite-Datenbank einsetzen. Den Pfad zur Datenbank geben Sie über eine config-Methode für die Instanz der Flask-Klasse an, dann instanziieren Sie SQLAlchemy etwa als db, wie im Beispiel. Dann müssen Sie eine eigene Klasse als Abkömmling von db.Model anlegen (im Beispiel Auto), in die alles rein kommt, was mit der Datenbank zu tun hat. Dazu gehören zunächst ein Tabellenname und die Definition der für das Beispiel benötigten Spalten, die Sie einzeln mit db.Column und einem Datentypen (db.String, db.Integer) definieren. Alle möglichen Operationen an der Datenbank (get_autos() usw.) schreiben Sie dann einfach als Methoden dieser aufgesetzten Klasse unter Zuhilfenahme der Methoden, die SQLAlchemy zur Verfügung stellt (query_all(), query_filter_by(), session.add() usw.). Die Flask-Applikation stellt im weiteren Verlauf hauptsächlich die Endpunkte mit den gewünschten HTTP-Methoden zur Verfügung und nutzt dabei die Auto-Klasse, um auf die Datenbank zuzugreifen. Das Beispiel beschränkt sich aus Platzgründen weiterhin auf GET/Daten abfragen und POST/neuen Datensatz anlegen (und es gibt gegenüber der vorherigen Fassung noch Kürzungen), weitere Funktionen wie die Daten zu überschreiben können Sie aber ähnlich wie mit weiteren HTTP-Methoden (PUT/PATCH) für den Endpunkt /autos/<id> anlegen.

Bevor die Applikation laufen gelassen werden kann, muss zunächst eine leere Datenbank angelegt werden, was Sie mit ./gebrauchtwagen2.py createdb auslösen können (siehe unten im Code die dafür eingebaute Abfrage von sys.argv). Die Beispielanfragen aus Listing 4 funktionieren hierbei genauso, der einzige Unterschied ist, dass die Daten nun in einer Datenbank aufbewahrt werden und nicht mehr verloren gehen, wenn der App-Server beendet wird. Wenn Sie möchten, importieren Sie die Auto-Klasse händisch in einen laufen Python-Interpreter (Listing 6) und lösen Sie die angelegten Operationen unmittelbar aus, ohne dass ein App-Server läuft (die Methode AUTO.delete_auto() ist im Code mit aufgesetzt, ohne – auch aus Platzgründen – für das API berücksichtigt worden zu sein).

Listing 5: „gebrauchtwagen2.py“
#!/usr/bin/env python3
from flask import Flask, request, jsonify, Response
from flask_sqlalchemy import SQLAlchemy
import sys, uuid

app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///./database.db'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
db = SQLAlchemy(app)

class Auto(db.Model):
  __tablename__ = 'autos'
  id = db.Column(db.String(32), primary_key=True)
  modell = db.Column(db.String(32), nullable=False)
  preis = db.Column(db.Integer, nullable=False)
  baujahr = db.Column(db.Integer, nullable=False)

  def json(self):
    return {'modell': self.modell, 'preis': self.preis, 'baujahr': self.baujahr, 'id': self.id}

  def get_autos():
    return [Auto.json(auto) for auto in Auto.query.all()]

  def auto_by_id(_id):
    return Auto.json(Auto.query.filter_by(id=_id).first())

  def add_auto(_id, _modell, _preis, _baujahr):
    new_auto = Auto(id=_id, modell=_modell, preis=_preis, baujahr=_baujahr)
    db.session.add(new_auto)
    db.session.commit()

  def delete_auto(_id):
    Auto.query.filter_by(id=_id).delete()

@app.route('/autos')
def get_autos():
  return jsonify({'gebrauchtwagen': Auto.get_autos()})

def checkPostObjekt(post_objekt):
  regeln = [len(post_objekt) == 3,
          'modell' in post_objekt,
          'preis' in post_objekt,
          'baujahr' in post_objekt]
  if all(regeln):
    return True
  else:
    return False

@app.route('/autos', methods=['POST'])
def add_auto():
  post_content = request.get_json()
  if checkPostObjekt(post_content):
    id = uuid.uuid4().hex
    Auto.add_auto(id, post_content['modell'], post_content['preis'], post_content['baujahr'])
    success = Response('', status=201)
    success.headers['Location'] = '/autos/' + id
    return success
  else:
    failure = Response('', status=400)
    return failure

@app.route('/autos/')
def auto_by_id(id):
  return jsonify(Auto.auto_by_id(id))

if __name__ == '__main__':
  if 'createdb' in sys.argv:
    with app.app_context():
        db.create_all()
  else:
    app.run(port=5000)
 
Listing 6
$ python3
Python 3.5.3 (default, Jan 19 2017, 14:11:04)
[GCC 6.3.0 20170118] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from gebrauchtwagen2 import Auto
>>> Auto.get_autos()
[{'id': '1372a6d2e22e41b0bb1eeae36452c67c', 'baujahr': 1993, 'modell': 'Toyota Corolla', 'preis': 9600}, {'id': '27134b7bb4154935949c65be4a521bf3', 'baujahr': 1987, 'modell': 'Opel Manta', 'preis': 1400}]
>>> Auto.delete_auto("27134b7bb4154935949c65be4a521bf3")
>>> Auto.get_autos()
[{'id': '1372a6d2e22e41b0bb1eeae36452c67c', 'baujahr': 1993, 'modell': 'Toyota Corolla', 'preis': 9600}]
 

Flask-RESTful

Es gibt mit Flask-RESTful [11] mittlerweile eine spezielle Erweiterung für Flask, die das Schreiben von APIs damit noch weiter vereinfacht. Ein weiteres, diesmal wieder reduziertes aber funktionstüchtiges Gebrauchtwagenbeispiel (Listing 7) zeigt, wie Sie diese Erweiterung anwenden (es fehlen gegenüber Listing 3 aber lediglich checkPostObjekt() und die Rückgabe von Statuscode 400 bei nicht vorhandenen IDs). Die durch bestimmte HTTP-Methoden vom Klienten auslösbaren Operationen definieren Sie hier als Klassen (Abkömmlinge von Resource), die Sie mit api.add_resource() bestimmten Endpunkten zuweisen. Zurückgegeben mit return werden grundsätzlich JSON-Objekte mit passendem Content-Type im HTTP-Header, die Funktion jsonify() von Flask ist hier also überflüssig. Statuscodes für die Rückgabe müssen nicht erst als Instanz von Response angelegt werden, sondern können praktischerweise direkt bei return mit angegeben werden. Diese Erweiterung macht es noch einfacher, RESTful APIs mit Flask aufzusetzen, klassenbasiert entwickeln zu können, und bietet eine saubere Trennung von Geschäftslogik und Endpunktdefinition.

Listing 7: „gebrauchtwagen3.py“
#!/usr/bin/env python3
from flask import Flask, request
from flask_restful import Resource, Api
import uuid

app = Flask(__name__)
api = Api(app)
autos = []

class getAutos(Resource):
  def get(self):
    return {'gebrauchtwagen': autos}
  
class addAuto(Resource):
  def post(self):
      post_content = request.get_json()
      post_content['id'] = uuid.uuid4().hex
      autos.append(post_content)
      return post_content, 201

class autoByID(Resource):
  def get(self, id):
    for auto in autos:
      if auto['id'] == id:
        return auto

api.add_resource(getAutos, '/autos')
api.add_resource(addAuto, '/autos')
api.add_resource(autoByID, '/autos/')

app.run(port=5000)

Fazit

Über die rudimentären Anwendungsbeispiele hinaus handelt es sich bei Flask um ein produktionsreifes Werkzeug mit dem Sie auch komplexe RESTful APIs für die Kommunikation von Maschinen untereinander aufsetzen können. Dieses Mikroframework ist dafür sehr geeignet und stellt die benötigten Mittel unkompliziert zur Verfügung. Mit der Wahl von Flask als Grundlage steht der gesamte Fundus der Python-Bibliotheken für die Entwicklung bereit und es gibt eine ganze Reihe von Flask-Erweiterungen, die spezielle Anbindungen an Python-Highlights wie zum Beispiel SQLAlchemy zur Verfügung stellen. Flask bietet mit seinem minimalistischen Ansatz zudem schnelles Prototyping und gute Reaktionszeiten, was gegenüber anderen Webframeworks einen zusätzlichen Pluspunkt ausmacht.

 

Links & Literatur
[1] Tilkov, Stephan u. a.: „REST und HTTP: Entwicklung und Integration nach dem Architekturstil des Web.“ dpunkt.verlag: Heidelberg
[2] http://flask.pocoo.org/
[3] Stender, Daniel: „Mit Flask Webapplikationen in Python entwickeln“, in: Entwickler Magazin 6.2017
[4] Grinberg, Miguel: „Microservices with Python and Flask“. Workshop auf der PyCon2017: https://www.youtube.com/watch?v=nrzLdMWTRMM
[5] Baumgold, David: „Prototyping new APIs with Flask“. Vortrag auf der PyCon2016: https://www.youtube.com/watch?v=6RdZNiyISVUs
[6] https://github.com/pallets/flask
[7] http://gunicorn.org/
[8] https://curl.haxx.se/
[9] http://flask-sqlalchemy.pocoo.org
[10] http://www.sqlalchemy.org/
[11] http://flask-restful.readthedocs.io/

All News & Updates of API Conference 2019:

Behind the Tracks

API Management

A detailed look at the development of APIs

API Development

Architecture of APIs and API systems

API Design & Documentation

From policies and identities to monitoring

API Platforms & Software as a Service

Web APIs for a larger audience & API platforms related to SaaS