0% ont trouvé ce document utile (0 vote)
52 vues23 pages

Psycopg2 vs Psycopg3 en Python

Ce document décrit l'utilisation de Psycopg, un adaptateur permettant d'interagir avec une base de données PostgreSQL depuis Python. Il explique comment se connecter à une base, utiliser des curseurs pour exécuter des requêtes SQL et récupérer les résultats, et souligne l'importance de ne jamais concaténer des données utilisateur dans les requêtes pour éviter les injections SQL.

Transféré par

delahaut sara
Copyright
© © All Rights Reserved
Nous prenons très au sérieux les droits relatifs au contenu. Si vous pensez qu’il s’agit de votre contenu, signalez une atteinte au droit d’auteur ici.
Formats disponibles
Téléchargez aux formats PDF, TXT ou lisez en ligne sur Scribd
0% ont trouvé ce document utile (0 vote)
52 vues23 pages

Psycopg2 vs Psycopg3 en Python

Ce document décrit l'utilisation de Psycopg, un adaptateur permettant d'interagir avec une base de données PostgreSQL depuis Python. Il explique comment se connecter à une base, utiliser des curseurs pour exécuter des requêtes SQL et récupérer les résultats, et souligne l'importance de ne jamais concaténer des données utilisateur dans les requêtes pour éviter les injections SQL.

Transféré par

delahaut sara
Copyright
© © All Rights Reserved
Nous prenons très au sérieux les droits relatifs au contenu. Si vous pensez qu’il s’agit de votre contenu, signalez une atteinte au droit d’auteur ici.
Formats disponibles
Téléchargez aux formats PDF, TXT ou lisez en ligne sur Scribd

Bases de données

Interaction Python - PostgreSQL avec Psycopg

Nadime Francis

Université Gustave Eiffel


LIGM - 4B130 Copernic
[email protected]

1 / 12
Psycopg

Psycopg

Psycopg : database adapter de PostgreSQL vers Python


Envoie les requêtes à PostgreSQL

Traduit les résultats dans un format exploitable par Python

Remarque : ce cours utilise Psycopg 2


Psycopg 3 est en cours de développement (version 3.0 le 13/10/2021)

2 / 12
Connexion à PostgreSQL

Fonction connect : création d’une nouvelle connexion à Postgres


⇒ requiert les mêmes informations de connexion que psql

import psycopg2
import psycopg2
conn = psycopg2.connect(
host = "sqletud.u-pem.fr", conn = psycopg2.connect(
dbname = "zelia_db", dbname = "DemoPsyco",
password = "miaou18", )
)
# connexion à la base de données
# connexion à la base de données # locale 'DemoPsyco' sans mdp
# étudiante 'zelia_db'

Fermeture de la connexion :
conn.close()

3 / 12
Curseurs

La connexion communique avec la base à l’aide de curseurs

Une connexion peut avoir plusieurs curseurs


# creation du curseur
cur = conn.cursor()

# on peut utiliser le curseur ici


cur.execute('SELECT ∗ FROM players')

# fermeture du curseur
cur.close()

4 / 12
Curseurs

La connexion communique avec la base à l’aide de curseurs

Une connexion peut avoir plusieurs curseurs


# creation du curseur
cur = conn.cursor()

# on peut utiliser le curseur ici


cur.execute('SELECT ∗ FROM players')

# fermeture du curseur
cur.close()

Les curseurs peuvent être utilisés comme context managers


(fonctionne aussi avec les connexions !)

# creation du curseur dans un contexte


with conn.cursor() as cur:
cur.execute('SELECT ∗ FROM players')

# cur.close() est implicitement appelé à la sortie du contexte

4 / 12
Requêtes SELECT
Les requêtes sont transmises à PostgreSQL par la méthode execute :
cur.execute('SELECT ∗ FROM players')

On peut récupérer les résultats :


1 En itérant sur le curseur :
for result in cur:
print(result)

2 Avec les méthodes fetchone(), fetchmany(nb) et fetchall() :


# récupération du premier resultat (ou None s'il n'y en a pas)
first_result = cur.fetchone()
# récupération des résutlats restants dans une liste
# le premier resultat a déjà été récupéré par fetchone
lst_results = cur.fetchall()

Remarques :
Par défaut, les résultats sont des tuples Python
Le curseur se vide au fur et à mesure qu’il renvoie les résultats
5 / 12
Tuples nommés
Par défaut, les résultats sont des tuples Python
psycopg2.extras fournit plusieurs alternatives, dont les tuples nommés

cur = conn.cursor(cursor_factory = psycopg2.extras.NamedTupleCursor)

cur.execute('SELECT nick, score FROM players')


for result in cur:
print(result.nick, result.score) # même résultat que print(result[0], result[1])

6 / 12
Tuples nommés
Par défaut, les résultats sont des tuples Python
psycopg2.extras fournit plusieurs alternatives, dont les tuples nommés

cur = conn.cursor(cursor_factory = psycopg2.extras.NamedTupleCursor)

cur.execute('SELECT nick, score FROM players')


for result in cur:
print(result.nick, result.score) # même résultat que print(result[0], result[1])

Remarques :
Accès aux champs du tuple par indice ou par nom

Attention, tous les champs doivent avoir un nom différent


cur.execute('SELECT max(level), max(score) FROM players')
# erreur : par défaut, les deux champs s'appellent 'max'

cur.execute('SELECT max(level) AS mLevel, max(score) AS mScore FROM players')


# version corrigée

6 / 12
Requêtes INSERT, UPDATE et DELETE

Les mises-à-jour ne sont pas immédiatement visibles dans la base !


But : éviter les exécutions partielles et interférences d’autres connexions

cur.execute('UPDATE players SET level = level + 1 WHERE score >= 1000')


# Et si le serveur tombe en panne ici ? Ou si un joueur gagne des points ?
cur.execute('UPDATE players SET score = score - 1000 WHERE score >= 1000')

7 / 12
Requêtes INSERT, UPDATE et DELETE

Les mises-à-jour ne sont pas immédiatement visibles dans la base !


But : éviter les exécutions partielles et interférences d’autres connexions

cur.execute('UPDATE players SET level = level + 1 WHERE score >= 1000')


# Et si le serveur tombe en panne ici ? Ou si un joueur gagne des points ?
cur.execute('UPDATE players SET score = score - 1000 WHERE score >= 1000')

La connexion doit valider les modifications :


conn.commit() : rend visible les modifications en attente
conn.rollback() : annule et revient à l’état du dernier commit

7 / 12
Requêtes INSERT, UPDATE et DELETE

Les mises-à-jour ne sont pas immédiatement visibles dans la base !


But : éviter les exécutions partielles et interférences d’autres connexions

cur.execute('UPDATE players SET level = level + 1 WHERE score >= 1000')


# Et si le serveur tombe en panne ici ? Ou si un joueur gagne des points ?
cur.execute('UPDATE players SET score = score - 1000 WHERE score >= 1000')

La connexion doit valider les modifications :


conn.commit() : rend visible les modifications en attente
conn.rollback() : annule et revient à l’état du dernier commit

Remarque : la gestion de la concurrence est complexe et délicate


Pour ce cours, on va utiliser le mode autocommit
⇒ validation immédiate et automatique des requêtes
(C’est dommage, mais chaque chose en son temps... Rendez-vous en L3 pour la suite !)

7 / 12
Intégration au site web
Une connexion à la base par requête HTTP
⇒ deux utilisateurs ne partagent pas la même connexion !

Un curseur par requête SQL


⇒ deux fonctions Python ne partagent pas le même curseur !

8 / 12
Intégration au site web
Une connexion à la base par requête HTTP
⇒ deux utilisateurs ne partagent pas la même connexion !

Un curseur par requête SQL


⇒ deux fonctions Python ne partagent pas le même curseur !
Pour le projet : fichier db.py obligatoirement paramétré comme suit :
import psycopg2
import psycopg2.extras

def connect():
conn = psycopg2.connect(
host = 'sqletud.u-pem.fr',
dbname = 'zelia_db', # nom de votre base de données
password = 'miaou18', # mot de passe de la base
cursor_factory = psycopg2.extras.NamedTupleCursor,
)
conn.autocommit = True
return conn

conn = db.connect() à chaque requête HTTP utilisant la base de données


conn.close() avant le rendu du template
8 / 12
Exemple : liste des joueurs et page de profil

La fonction playerList :
@app.route("/playerList")
def playerList():
with db.connect() as conn:
with conn.cursor() as cur:
cur.execute("select pid, nick, avatar, level from players order by level desc")
result = cur.fetchall()
return render_template("player_list.html", plist = result)

Et la fonction profile :
@app.route("/profile/<int:pid>")
def profile(pid):
with db.connect() as conn:
with conn.cursor() as cur:
cur.execute("select ∗ from players where pid = %s", (pid,))
player = cur.fetchone()
if not player:
return render_template("profile_error.html")
return render_template("profile.html", player = player)

9 / 12
Injection SQL
On ne fait JAMAIS confiance à une donnée rentrée par l’utilisateur :
Erreurs involontaires : fautes de frappe, oublis, mauvais format...
Exploitation de failles de sécurité, en particulier injection SQL

10 / 12
Injection SQL
On ne fait JAMAIS confiance à une donnée rentrée par l’utilisateur :
Erreurs involontaires : fautes de frappe, oublis, mauvais format...
Exploitation de failles de sécurité, en particulier injection SQL
Extrait de la documentation de Psycopg
Never, never, NEVER use Python string concatenation (+) or string parameters
interpolation (%) to pass variables to a SQL query string. Not even at gunpoint.

10 / 12
Injection SQL
On ne fait JAMAIS confiance à une donnée rentrée par l’utilisateur :
Erreurs involontaires : fautes de frappe, oublis, mauvais format...
Exploitation de failles de sécurité, en particulier injection SQL
Extrait de la documentation de Psycopg
Never, never, NEVER use Python string concatenation (+) or string parameters
interpolation (%) to pass variables to a SQL query string. Not even at gunpoint.

source : xkcd.com
10 / 12
Injection SQL
On ne fait JAMAIS confiance à une donnée rentrée par l’utilisateur :
Erreurs involontaires : fautes de frappe, oublis, mauvais format...
Exploitation de failles de sécurité, en particulier injection SQL
Extrait de la documentation de Psycopg
Never, never, NEVER use Python string concatenation (+) or string parameters
interpolation (%) to pass variables to a SQL query string. Not even at gunpoint.

Ce qu’il ne faut pas faire :


with conn.cursor() as cur:
cur.execute(
"insert into players(nick, message) values ('"+form_nick+"','"+form_message+"')"
)

10 / 12
Injection SQL
On ne fait JAMAIS confiance à une donnée rentrée par l’utilisateur :
Erreurs involontaires : fautes de frappe, oublis, mauvais format...
Exploitation de failles de sécurité, en particulier injection SQL
Extrait de la documentation de Psycopg
Never, never, NEVER use Python string concatenation (+) or string parameters
interpolation (%) to pass variables to a SQL query string. Not even at gunpoint.

Ce qu’il ne faut pas faire :


with conn.cursor() as cur:
cur.execute(
"insert into players(nick, message) values ('"+form_nick+"','"+form_message+"')"
)

execute permet d’encapsuler correctement les paramètres de la requête :


with conn.cursor() as cur:
cur.execute(
"insert into players(nick, message) values (%s,%s)", (form_nick, form_message)
)

10 / 12
Gestion des mots de passe

On ne stocke JAMAIS de mots de passe en clair !


En cas de fuite, l’attaquant peut se faire passer pour l’utilisateur
En cas de réutilisation, les autres comptes sont aussi vulnérables

11 / 12
Gestion des mots de passe

On ne stocke JAMAIS de mots de passe en clair !


En cas de fuite, l’attaquant peut se faire passer pour l’utilisateur
En cas de réutilisation, les autres comptes sont aussi vulnérables

Idée : on stocke une empreinte du mot de passe, appelée hash


Le calcul mot de passe → hash est très simple
Le calcul hash → mot de passe est presque impossible
Il est très rare que deux mots de passe différents aient le même hash

11 / 12
Gestion des mots de passe

On ne stocke JAMAIS de mots de passe en clair !


En cas de fuite, l’attaquant peut se faire passer pour l’utilisateur
En cas de réutilisation, les autres comptes sont aussi vulnérables

Idée : on stocke une empreinte du mot de passe, appelée hash


Le calcul mot de passe → hash est très simple
Le calcul hash → mot de passe est presque impossible
Il est très rare que deux mots de passe différents aient le même hash

L’étude du hachage sort du cadre de ce cours (C’est de la crypto !)


Nous allons utiliser une bibliothèque Python qui fait le travail pour nous :
from passlib.context import CryptContext
password_ctx = CryptContext(schemes=['bcrypt']) # configuration de la bibliothèque

hash_pw = password_ctx.hash("miaou18") # calcul du hash, à stocker dans la base


password_ctx.verify("miaou18", hash_pw) # test à effectuer au login de l'utilisateur

11 / 12
Pour aller plus loin...
Ce cours n’aborde pas de nombreuses fonctionnalités avancées, dont :

Gestion de la concurrence et des transactions (Rendez-vous en L3)

L’accès à la structure de la base et des réponses


⇒ schéma, type de données, nombre de résultats, temps de calcul...

Les curseurs côté serveur

Les contextes des requêtes HTTP dans Flask


⇒ Comment ne pas ouvrir et fermer la connexion à chaque fonction ?

Le fonctionnement et les paramètres de passlib

Et bien d’autres encore...


Pour aller plus loin, consulter les documentations officielles :

https://www.psycopg.org/docs/
https://passlib.readthedocs.io/

12 / 12

Vous aimerez peut-être aussi