Utilisation serveur
Connexion globale
Pour utiliser sqlite dans un serveur bottle, il est préférable de créer une connexion globale à la base de données et de l'utiliser dans les différentes routes.
Création de la connexion, création et remplissage de la table ingredients (télécharger le code) :
import sqlite3
# Connexion à la base de données
db_conn = sqlite3.connect('data.db')
db_conn.execute('PRAGMA foreign_keys = ON')
# Préparation de la base de données
db_conn.execute('''
CREATE TABLE IF NOT EXISTS ingredients (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL UNIQUE
);''')
try:
with db_conn:
db_conn.execute('''
INSERT INTO ingredients (name)
VALUES ('Mozzarella'),('Tomate'),('Basilic'),
('Jambon'),('Olive'),
('Oignon'),('Oeuf');
''')
except sqlite3.IntegrityError:
# Si la table a déjà été remplie, on ne fait rien
pass
Utilisation de la connexion
La connexion globale est ensuite utilisée dans les différentes routes. Exemple : affichage de la liste des ingrédients (télécharger le code) :
@route('/ingredients')
def ingredients():
# utilisation de la référence à la connexion globale
cursor = db_conn.cursor()
cursor.execute('SELECT * FROM ingredients')
rows = cursor.fetchall()
out = "<h1>Liste des ingrédients</h1>"
out += "<ul>"
for row in rows:
out += f"<li>{row[0]} : {row[1]}</li>"
out += "</ul>"
return out
En pratique
Modifiez le code ci-dessus pour :
- utiliser un template pour la page
/ingredients - en incluant un formulaire pour ajouter un nouvel ingrédient (via la méthode
POSTvers/ingredients) - en incluant un formulaire pour filtrer les ingrédients par nom (via la méthode
GETvers/ingredients?q=Tomatepar exemple) - en affichant la liste des ingrédients dans un tableau HTML
- en affichant un message d'erreur si l'ingrédient existe déjà (vous pouvez
utiliser un
try/exceptpour gérer l'erreur d'intégrité)

Correction
Template de la page /ingredients (télécharger le code) :
<body>
<h1>Liste des ingrédients</h1>
% if error is not None:
<p class="error">{{ error }}</p>
% end
<form action="/ingredients" method="post">
<input
type="text"
name="name"
placeholder="Nom de l'ingrédient"
required
/>
<button type="submit">Ajouter un ingrédient</button>
</form>
<form action="/ingredients" method="get">
<input type="text" name="q" placeholder="Filtrer par nom" value="{{q}}" />
<button type="submit">Filtrer</button>
</form>
<table>
<thead>
<tr>
<th>ID</th>
<th>Nom</th>
</tr>
</thead>
<tbody>
% for row in rows:
<tr>
<td>{{row[0]}}</td>
<td>{{row[1]}}</td>
</tr>
% end
</tbody>
</table>
</body>
from bottle import route, run, request, template
import sqlite3
# Connexion à la base de données
db_conn = sqlite3.connect('data.db')
db_conn.execute('PRAGMA foreign_keys = ON')
# Préparation de la base de données
db_conn.execute('''
CREATE TABLE IF NOT EXISTS ingredients (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL UNIQUE
);''')
try:
with db_conn:
db_conn.execute('''
INSERT INTO ingredients (name)
VALUES ('Mozzarella'),('Tomate'),('Basilic'),
('Jambon'),('Olive'),
('Oignon'),('Oeuf');
''')
except sqlite3.IntegrityError:
# Si la table a déjà été remplie, on ne fait rien
pass
@route('/ingredients', method=['GET', 'POST'])
def ingredients():
# utilisation de la référence à la connexion globale
cursor = db_conn.cursor()
q = request.query.get('q', '')
if request.method == 'POST':
name = request.forms.get('name')
try:
with db_conn:
cursor.execute('INSERT INTO ingredients (name) VALUES (?)',
(name,))
except sqlite3.IntegrityError:
cursor.execute(
'SELECT id, name FROM ingredients WHERE UPPER(name) LIKE ?',
(f'%{q.upper()}%',))
rows = cursor.fetchall()
return template('ingredients.html',
error="L'ingrédient existe déjà",
rows=rows, q=q)
cursor.execute('SELECT id, name FROM ingredients')
rows = cursor.fetchall()
return template('ingredients.html', rows=rows, q=q)
run(host='localhost', port=38083, debug=True, reloader=True)
Précautions
Par défaut, en mode développement, Bottle ne tourne que dans un seul thread et un seul processus. Ceci ne pose aucun problème pour SQLite. En revanche, pour un usage en production, notre application peut être lancée dans plusieurs threads voire processus. Dans ce cas, il faut faire attention à la gestion de la base de données. En effet, SQLite ne gère pas forcément bien les accès concurrents.
Pour vérifier le support des accès concurrents dans SQLITE (ref : https://docs.python.org/3/library/sqlite3.html#sqlite3.threadsafety, vous pouvez exécuter la commande suivante dans un terminal :
python -c "import sqlite3; print(sqlite3.threadsafety)"
Selon la valeur retournée, la gestion des accès concurrents peut être :
0: pas de gestion des accès concurrents, pas de partage du module, ni des connexions, ni des curseurs1: gestion des accès concurrents par le module sqlite3, mais pas de partage des connexions ni des curseurs3: gestion des accès concurrents par le module sqlite3 et partage des connexions et des curseurs
Si le threadsafety est à 0, il vaut mieux réfléchir à une autre solution
(changer de base de données, ou changer de distribution Python).