c4 Python Api
c4 Python Api
c4 Python Api
technologies Web
1. Programmer des API avec Python et Flask
Salim Lardjane
Université de Bretagne Sud
Objectifs
• Les API (Application Programming Interfaces) Web sont des
outils permettant de rendre de l’information et des
fonctionnalités accessibles via internet.
• Notre jeu de données est important, ce qui rend le téléchargement par FTP
lourd ou consommateur de ressources importantes.
• Les utilisateurs ont besoin d’accéder aux données en temps réel, par
exemple pour une visualisation sur un site web ou comme une partie d’une
application.
• Les utilisateurs n’ont besoin d’accéder qu’à une partie des données à la
fois.
import flask
app = flask.Flask(__name__)
app.config["DEBUG"] = True
@app.route('/', methods=['GET'])
def home():
return "<h1>Distant Reading Archive</h1><p>This site
is a prototype API for distant reading of science
fiction novels.</p>"
app.run()
Flask
• On sauvegarde ensuite le programme sous le nom api.py
dans le répertoire api précédemment créé.
• L’instruction :
@app.route('/', methods=[‘GET'])
import flask
from flask import request, jsonify
app = flask.Flask(__name__)
app.config["DEBUG"] = True
# Create some test data for our catalog in the form of a list of dictionaries.
books = [
{'id': 0,
'title': 'A Fire Upon the Deep',
'author': 'Vernor Vinge',
'first_sentence': 'The coldsleep itself was dreamless.',
'year_published': '1992'},
{'id': 1,
'title': 'The Ones Who Walk Away From Omelas',
'author': 'Ursula K. Le Guin',
'first_sentence': 'With a clamor of bells that set the swallows soaring, the Festival of Summer came to the
city Omelas, bright-towered by the sea.',
'published': '1973'},
{'id': 2,
'title': 'Dhalgren',
'author': 'Samuel R. Delany',
'first_sentence': 'to wound the autumnal city.',
'published': '1975'}
]
Création de l’API
@app.route('/', methods=['GET'])
def home():
return '''<h1>Distant Reading Archive</h1>
<p>A prototype API for distant reading of science fiction novels.</p>'''
app.run()
Création de l’API
http://127.0.0.1:5000/api/v1/resources/books/all
Création de l’API
Création de l’API
• On a utilisé la fonction jsonify de Flask. Celle-ci permet de
convertir les listes et les dictionnaires au format JSON.
• Via la route qu’on a créé, nos données sur les livres sont
converties d’une liste de dictionnaires vers le format JSON,
avant d’être fournies à l’utilisateur.
import flask
from flask import request, jsonify
app = flask.Flask(__name__)
app.config["DEBUG"] = True
# Create some test data for our catalog in the form of a list of dictionaries.
books = [
{'id': 0,
'title': 'A Fire Upon the Deep',
'author': 'Vernor Vinge',
'first_sentence': 'The coldsleep itself was dreamless.',
'year_published': '1992'},
{'id': 1,
'title': 'The Ones Who Walk Away From Omelas',
'author': 'Ursula K. Le Guin',
'first_sentence': 'With a clamor of bells that set the swallows soaring, the Festival of Summer
came to the city Omelas, bright-towered by the sea.',
'published': '1973'},
{'id': 2,
'title': 'Dhalgren',
'author': 'Samuel R. Delany',
'first_sentence': 'to wound the autumnal city.',
'published': '1975'}
]
Accéder à des ressources
spécifiques
@app.route('/', methods=['GET'])
def home():
return '''<h1>Distant Reading Archive</h1>
<p>A prototype API for distant reading of science fiction novels.</p>'''
@app.route('/api/v1/resources/books/all', methods=['GET'])
def api_all():
return jsonify(books)
@app.route('/api/v1/resources/books', methods=['GET'])
def api_id():
# Check if an ID was provided as part of the URL.
# If ID is provided, assign it to a variable.
# If no ID is provided, display an error in the browser.
if 'id' in request.args:
id = int(request.args['id'])
else:
return "Error: No id field provided. Please specify an id."
# Loop through the data and match results that fit the requested ID.
# IDs are unique, but other fields might return many results
for book in books:
if book['id'] == id:
results.append(book)
app.run()
Accéder à des ressources
spécifiques
• Après avoir saisi, sauvegardé et exécuté le code sous
Spyder 3, on peut saisir les adresses suivantes dans le
navigateur :
127.0.0.1:5000/api/v1/resources/books?id=0
127.0.0.1:5000/api/v1/resources/books?id=1
127.0.0.1:5000/api/v1/resources/books?id=2
127.0.0.1:5000/api/v1/resources/books?id=3
Accéder à des ressources
spécifiques
Accéder à des ressources
spécifiques
if 'id' in request.args:
id = int(request.args['id'])
else:
return "Error: No id field
provided. Please specify an id."
Comprendre la nouvelle API
http://api.example.com/getbook/10
Conception des requêtes
• Cette requête pose un certain nombre de problèmes : le
premier est sémantique ; dans une API REST, les verbes
typiques sont GET, POST, PUT et DELETE, et sont
déterminés par la méthode de requête plutôt que par l’URL
de requête. Cela entraine que le mot « get » ne doit pas
apparaître dans la requête, puisque « get » est impliqué par
le fait qu’on utilise une requête HTTP GET.
http://api.example.com/books/10
http://api.example.com/books?author=Ursula+K.
+Le Guin&published=1969&output=xml
Conception des requêtes
• Quand on met au point la structure des requêtes
soumises à une API, il est également raisonnable de
prévoir les développement futurs.
http://api.example.com/resources/books?id=10
Conception des requêtes
https://api.example.com/v1/resources/images?id=10
https://api.example.com/v1/resources/all
Conception des requêtes
https://api.example.com/v1/resources/books?id=10
Exemples et documentation
• Sans documentation, même la meilleure API est inutilisable.
import flask
from flask import request, jsonify
import sqlite3
app = flask.Flask(__name__)
app.config["DEBUG"] = True
@app.route('/', methods=['GET'])
def home():
return '''<h1>Distant Reading Archive</h1>
<p>A prototype API for distant reading of science fiction novels.</p>'''
@app.route('/api/v1/resources/books/all', methods=['GET'])
def api_all():
conn = sqlite3.connect('books.db')
conn.row_factory = dict_factory
cur = conn.cursor()
all_books = cur.execute('SELECT * FROM books;').fetchall()
return jsonify(all_books)
Connexion à une base de
données
def page_not_found(e):
return "<h1>404</h1><p>The resource could not be found.</p>", 404
@app.route('/api/v1/resources/books', methods=['GET'])
def api_filter():
query_parameters = request.args
id = query_parameters.get('id')
published = query_parameters.get('published')
author = query_parameters.get('author')
if id:
query += ' id=? AND'
to_filter.append(id)
if published:
query += ' published=? AND'
to_filter.append(published)
if author:
query += ' author=? AND'
to_filter.append(author)
if not (id or published or author):
return page_not_found(404)
conn = sqlite3.connect('books.db')
conn.row_factory = dict_factory
cur = conn.cursor()
return jsonify(results)
app.run()
Connexion à une base de
données
• On saisit le code sous Spyder 3 et on le sauvegarde sous
le nom api-final.py dans notre répertoire api.
http://127.0.0.1:5000/api/v1/resources/books/all
http://127.0.0.1:5000/api/v1/resources/books?author=Connie+Willis
http://127.0.0.1:5000/api/v1/resources/books?author=Connie+Willis&published=1999
http://127.0.0.1:5000/api/v1/resources/books?published=2010
Connexion à une base de
données
def api_all():
conn = sqlite3.connect('books.db')
conn.row_factory = dict_factory
cur = conn.cursor()
all_books = cur.execute('SELECT * FROM books;').fetchall()
return jsonify(all_books)
Comprendre la nouvelle API
@app.errorhandler(404)
def page_not_found(e):
return "<h1>404</h1><p>The resource
could not be found.</p>
Comprendre la nouvelle API
query_parameters = request.args
Comprendre la nouvelle API
id = query_parameters.get('id')
published = query_parameters.get('published')
author = query_parameters.get('author')
Comprendre la nouvelle API
if id:
query += ' id=? AND'
to_filter.append(id)
if published:
query += ' published=? AND'
to_filter.append(published)
if author:
query += ' author=? AND'
to_filter.append(author)
Comprendre la nouvelle API
conn = sqlite3.connect('books.db')
conn.row_factory = dict_factory
cur = conn.cursor()
results = cur.execute(query,
to_filter).fetchall()
Comprendre la nouvelle API
return jsonify(results)
Utilisation de la nouvelle
API
• Notre nouvelle API autorise des requêtes plus
sophistiquées de la part des utilisateurs.
Salim Lardjane
Université de Bretagne Sud
Introduction
/api/substituteString/<string>/<search_string>/<sub_string>
HTML (templates/home.html)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Application Home Page</title>
</head>
<body>
<h2>
Hello World!
</h2>
</body>
</html>
Premiers pas
YAML (swagger.yml)
swagger: "2.0"
info:
description: This is the swagger file that goes with our server code
version: "1.0.0"
title: Swagger REST Article
consumes:
- "application/json"
produces:
- "application/json"
basePath: "/api"
Connexion
# Paths supported by the server application
paths:
/people:
get:
operationId: "people.read"
tags:
- "People"
summary: "The people data structure supported by the server application"
description: "Read the list of people"
responses:
200:
description: "Successful read people list operation"
schema:
type: "array"
items:
properties:
fname:
type: "string"
lname:
type: "string"
timestamp:
type: "string"
Connexion
• Afin d’utiliser Connexion, on commence par l’importer,
puis on crée l’application à l’aide de Connexion plutôt que
Flask. L’application Flask est quand même créée de façon
sous-jacente mais avec des fonctionnalités additionnelles.
def get_timestamp():
return datetime.now().strftime(("%Y-%m-%d %H:%M:%S"))
• La structure de la réponse
HTML (home.html)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Application Home Page</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/normalize/8.0.0/
normalize.min.css">
<link rel="stylesheet" href="static/css/home.css">
<script
src="http://code.jquery.com/jquery-3.3.1.min.js"
integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8="
crossorigin="anonymous">
</script>
</head>
Application Web
<body>
<div class="container">
<h1 class="banner">People Demo Application</h1>
<div class="section editor">
<label for="fname">First Name
<input id="fname" type="text" />
</label>
<br />
<label for="lname">Last Name
<input id="lname" type="text" />
</label>
<br />
<button id="create">Create</button>
<button id="update">Update</button>
<button id="delete">Delete</button>
<button id="reset">Reset</button>
</div>
<div class="people">
<table>
<caption>People</caption>
<thead>
<tr>
<th>First Name</th>
<th>Last Name</th>
<th>Update Time</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
<div class="error">
</div>
</div>
</body>
<script src="static/js/home.js"></script>
</html>
Application Web
• Le fichier précédent est disponible sur le forum, dans le sous-
répertoire templates du sous-répertoire version_4 du répertoire
api.
static/
│
├── css/
│ └── home.css
│
└── js/
└── home.js
Application Web
Salim Lardjane
Université de Bretagne Sud
Prérequis
SQL
SHELL
import sqlite3
2
3 conn = sqlite3.connect('people.db')
4 cur = conn.cursor()
5 cur.execute('SELECT * FROM person ORDER BY lname')
6 people = cur.fetchall()
7 for person in people:
8 print(f'{person[2]} {person[1]}')
Interaction avec la base de
données
• Le programme précédent se déroule de la façon suivante :
PYTHON
people = [
(2, 'Brockman', 'Kent', '2018-08-08 21:16:01.888444'),
(3, 'Easter', 'Bunny', '2018-08-08 21:16:01.889060'),
(1, 'Farrell', 'Doug', '2018-08-08 21:16:01.886834')
]
Interaction avec la base de
données
SHELL
Kent Brockman
Bunny Easter
Doug Farrell
Interaction avec la base de
données
• Dans le programme précédent, on doit savoir que le nom de
famille est en position 1 et le prénom en position 2 dans les
tuples renvoyés.
GET /api/people/{lname}
PYTHON
lname = 'Farrell'
cur.execute('SELECT * FROM person WHERE
lname = \'{}\''.format(lname))
Interaction avec la base de
données
• Le code précédent fonctionne de la façon suivante:
SQL
SQL
class Person(db.Model):
__tablename__ = 'person'
person_id = db.Column(db.Integer,
primary_key=True)
lname = db.Column(db.String)
fname = db.Column(db.String)
timestamp = db.Column(db.DateTime,
default=datetime.utcnow,
onupdate=datetime.utcnow)
Le modèle SQLAlchemy
• Cela permet notamment d’effectuer des calculs plus simples sur les
dates/heures.
SQL
PYTHON
• L’instruction SQLAlchemy
Person.query.order_by(Person.lname).all( ) fournit une liste
d’objets de classe Person correspondant à tous les
enregistrements de la table person, triée par nom de
famille.
PYTHON
class PersonSchema(ma.ModelSchema):
class Meta:
model = Person
sqla_session = db.session
Marshmallow
1. Elle est fournie par défaut avec Python et n’a donc pas à
être installée comme un module séparé.
import os
import connexion
from flask_sqlalchemy import SQLAlchemy
from flask_marshmallow import Marshmallow
basedir = os.path.abspath(os.path.dirname(__file__))
# Initialize Marshmallow
ma = Marshmallow(app)
Le module Config
• Les lignes 2-4 importent Connexion, SQLAlchemy et
Marshmallow.
PYTHON (models.py)
class Person(db.Model):
__tablename__ = 'person'
person_id = db.Column(db.Integer, primary_key=True)
lname = db.Column(db.String(32), index=True)
fname = db.Column(db.String(32))
timestamp = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
class PersonSchema(ma.ModelSchema):
class Meta:
model = Person
sqla_session = db.session
Le module Models
• La ligne 1 importe la classe datetime du module datetime
disponible par défaut sous Python
import os
from config import db
from models import Person
db.session.commit()
Créer la base de données
• La ligne 2 importe l’objet db du module config.py.
2019-03-09 00:54:02,192 INFO sqlalchemy.engine.base.Engine SELECT CAST('test plain returns' AS VARCHAR(60)) AS anon_1
2019-03-09 00:54:02,193 INFO sqlalchemy.engine.base.Engine SELECT CAST('test unicode returns' AS VARCHAR(60)) AS anon_1
lname VARCHAR(32),
fname VARCHAR(32),
timestamp DATETIME,
)
Créer la base de données
2019-03-09 00:54:02,199 INFO sqlalchemy.engine.base.Engine ()
2019-03-09 00:54:02,203 INFO sqlalchemy.engine.base.Engine INSERT INTO person (lname, fname, timestamp) VALUES (?, ?, ?)
2019-03-09 00:54:02,205 INFO sqlalchemy.engine.base.Engine INSERT INTO person (lname, fname, timestamp) VALUES (?, ?, ?)
2019-03-09 00:54:02,206 INFO sqlalchemy.engine.base.Engine INSERT INTO person (lname, fname, timestamp) VALUES (?, ?, ?)
def read_all():
"""
This function responds to a request for /api/people
with the complete lists of people
def read_one(person_id):
"""
This function responds to a request for /api/people/{person_id}
with one matching person from people
existing_person = Person.query \
.filter(Person.fname == fname) \
.filter(Person.lname == lname) \
.one_or_none()
# Create a person instance using the schema and the passed-in person
schema = PersonSchema()
new_person = schema.load(person, session=db.session).data
Salim Lardjane
Université de Bretagne Sud
Les données
import pandas as pd
df = pd.read_csv('winequality-red.csv', delimiter=";")
label = df['quality']
import pandas as pd
import numpy as np
#loading and separating our wine dataset into labels and features
df = pd.read_csv('winequality-red.csv', delimiter=";")
label = df['quality']
#defining our linear regression estimator and training it with our wine data
regr = linear_model.LinearRegression()
regr.fit(features, label)
print regr.predict([[7.4,0.66,0,1.8,0.075,13,40,0.9978,3.51,0.56,9.4]]).tolist()
Sérialisation
regr = linear_model.LinearRegression()
regr.fit(features, label)
pickle.dump(regr, open("model.pkl","wb"))
model = pickle.load(open("model.pkl","r"))
Flask
import pickle
import flask
app = flask.Flask(__name__)
model = pickle.load(open("model.pkl","r"))
Ajouter le modèle au
serveur
if __name__ == "__main__":
app.run(debug=True)
Test
• Afin de tester le déploiement de notre modèle, on doit
passer une requête HTTP POST à notre serveur.
0 : negative
1 : somewhat negative
2 : neutral
3 : somewhat positive
4 : positive
Données
PYTHON
# argument parsing
parser = reqparse.RequestParser()
parser.add_argument('query')
API REST
• Dans la suite, on va utiliser une ressource Flask-RESTful.
return output
API REST
• Le code suivant définit l’URL de base pour la ressource de prédiction
de sentiment.
• On peut simplement lister les ressources les unes après les autres,
comme le montre le code suivant :
API REST
PYTHON
api.add_resource(PredictSentiment, '/')
if __name__ == '__main__':
app.run(debug=True)
Requêtes
• L’ensemble du code est disponible sur le forum, dans le
répertoire sentiment-clf.