Finis les requêtes SQL à la main — parle Python, laisse l'ORM traduire.
Un ORM est une couche d'abstraction entre ton code Python et ta base de données. Tu manipules des objets Python — l'ORM les traduit en SQL tout seul.
Ce projet couvre deux approches : MySQLdb (bas niveau, SQL direct) et SQLAlchemy (ORM complet, zéro SQL).
MySQLdb c'est comme jouer sans HUD : tu gères tout manuellement, tu vois les requêtes brutes. SQLAlchemy c'est l'interface du jeu : tu cliques sur "attaquer un ennemi", le jeu traduit en mécanique interne. Même résultat, niveau d'abstraction très différent.
MySQLdb ouvre une connexion directe au serveur MySQL. Tu obtiens un cursor pour exécuter des requêtes SQL sous forme de chaînes de caractères.
L'Engine est le point d'entrée de SQLAlchemy. Il contient les infos de connexion (hôte, port, user, db) et gère le pool de connexions.
create_engine() — c'est la porte vers ta base de données.La Session est ton espace de travail. Elle suit les objets que tu crées, modifies ou supprimes, puis les synchronise avec la base via commit().
Base est la classe parente de tous tes modèles. Chaque modèle est une classe Python qui mappe une table SQL — chaque attribut de classe = une colonne.
Au lieu d'écrire SELECT * FROM, tu utilises session.query(Model). Tu peux chaîner des filtres, tris et limites directement en Python.
L'option pool_pre_ping=True teste la connexion avant chaque utilisation. Indispensable pour éviter les erreurs "connexion perdue" sur un serveur idle.
Structure standard d'un script MySQLdb — connexion, curseur, requête, fermeture.
import MySQLdb import sys if __name__ == "__main__": # Récupère les arguments : user, password, db_name username = sys.argv[1] password = sys.argv[2] db_name = sys.argv[3] # Ouvre la connexion au serveur MySQL local conn = MySQLdb.connect( host="localhost", port=3306, user=username, passwd=password, db=db_name, charset="utf8" ) # Crée un curseur pour exécuter des requêtes cur = conn.cursor() # Exécute la requête SQL (ici en chaîne de caractères) cur.execute("SELECT * FROM states ORDER BY id ASC") # Récupère toutes les lignes sous forme de tuples rows = cur.fetchall() for row in rows: print(row) # Ferme curseur ET connexion — toujours dans cet ordre cur.close() conn.close()
cur.execute("SELECT * FROM states WHERE name = %s", (name,))(1, 'California'). Pour avoir des dicts, utiliser MySQLdb.cursors.DictCursor.Chaque modèle hérite de Base et déclare ses colonnes comme attributs de classe.
#!/usr/bin/python3 """Module définissant le modèle State pour SQLAlchemy.""" from sqlalchemy import Column, Integer, String from sqlalchemy.ext.declarative import declarative_base # Base est la classe parente de tous les modèles Base = declarative_base() class State(Base): """Modèle représentant la table 'states' en base de données.""" # Nom de la table en base __tablename__ = 'states' # Colonnes : id auto-incrémenté, clé primaire id = Column(Integer, primary_key=True, nullable=False) # name : chaîne de 128 chars, obligatoire name = Column(String(128), nullable=False)
Column(...) définit une colonne. L'ORM s'occupe de la correspondance — tu n'écris jamais le CREATE TABLE.#!/usr/bin/python3 """Script listant tous les states via SQLAlchemy ORM.""" import sys from sqlalchemy import create_engine from sqlalchemy.orm import Session from model_state import Base, State if __name__ == "__main__": # Crée le moteur de connexion — format : dialect+driver://user:pass@host/db engine = create_engine( 'mysql+mysqldb://{}:{}@localhost/{}'.format( sys.argv[1], sys.argv[2], sys.argv[3] ), pool_pre_ping=True # teste la connexion avant usage ) # Crée les tables si elles n'existent pas encore Base.metadata.create_all(engine) # Ouvre une session de travail with Session(engine) as session: # Requête Python — pas une seule ligne de SQL ! states = session.query(State).order_by(State.id).all() for state in states: print("{}: {}".format(state.id, state.name))
with Session(engine) as session: — la session se ferme automatiquement à la fin du bloc..execute() avec SQLAlchemy dans ce projet. Toujours passer par la Query API.| Méthode / Attribut | Module | Rôle | Retourne |
|---|---|---|---|
| MySQLdb.connect() | MySQLdb | Ouvre une connexion MySQL | Objet connexion |
| conn.cursor() | MySQLdb | Crée un curseur pour exécuter du SQL | Curseur |
| cur.execute(query) | MySQLdb | Exécute une requête SQL | Nombre de lignes affectées |
| cur.fetchall() | MySQLdb | Récupère toutes les lignes du résultat | Liste de tuples |
| create_engine(url) | SQLAlchemy | Crée le moteur de connexion | Engine |
| Base.metadata.create_all(engine) | SQLAlchemy | Crée les tables si inexistantes | None |
| session.query(Model) | SQLAlchemy | Démarre une requête sur un modèle | Query object (chaînable) |
| .filter(condition) | SQLAlchemy | Filtre les résultats (= WHERE) | Query object |
| .filter_by(attr=val) | SQLAlchemy | Filtre simple sur un attribut | Query object |
| .order_by(Model.col) | SQLAlchemy | Trie les résultats (= ORDER BY) | Query object |
| .all() | SQLAlchemy | Exécute et retourne toutes les lignes | Liste d'instances |
| .first() | SQLAlchemy | Retourne le premier résultat ou None | Instance ou None |
| .count() | SQLAlchemy | Compte les lignes (= COUNT) | int |
| session.add(obj) | SQLAlchemy | Prépare un objet pour insertion | None |
| session.commit() | SQLAlchemy | Valide les changements en base | None |
| session.delete(obj) | SQLAlchemy | Marque un objet pour suppression | None |
Objectif : lister tous les states d'une table triés par id avec MySQLdb.
#!/usr/bin/python3 """Liste tous les states depuis la DB — utilise MySQLdb.""" import MySQLdb import sys if __name__ == "__main__": conn = MySQLdb.connect(host="localhost", port=3306, user=sys.argv[1], passwd=sys.argv[2], db=sys.argv[3], charset="utf8") cur = conn.cursor() cur.execute("SELECT * FROM states ORDER BY id ASC") for row in cur.fetchall(): print(row) cur.close() conn.close()
(1, 'California') — chaque ligne est un tuple (id, name).Objectif : avec SQLAlchemy, trouver un state par son nom (insensible à la casse).
#!/usr/bin/python3 """Cherche un state par nom exact via SQLAlchemy ORM.""" import sys from sqlalchemy import create_engine from sqlalchemy.orm import Session from model_state import Base, State if __name__ == "__main__": engine = create_engine( 'mysql+mysqldb://{}:{}@localhost/{}'.format(*sys.argv[1:4]), pool_pre_ping=True ) Base.metadata.create_all(engine) with Session(engine) as session: # Filtre sur le nom exact — pas de SQL, juste Python result = session.query(State).filter( State.name == sys.argv[4] ).all() if result: for state in result: print("{}: {}".format(state.id, state.name)) else: print("Not found")
Objectif : créer un nouveau state, le modifier, puis le supprimer — tout sans SQL.
#!/usr/bin/python3 """CRUD complet sur un State avec SQLAlchemy ORM.""" import sys from sqlalchemy import create_engine from sqlalchemy.orm import Session from model_state import Base, State if __name__ == "__main__": engine = create_engine( 'mysql+mysqldb://{}:{}@localhost/{}'.format(*sys.argv[1:4]), pool_pre_ping=True ) Base.metadata.create_all(engine) with Session(engine) as session: # INSERT — crée un objet Python et l'ajoute à la session new_state = State(name="Louisiana") session.add(new_state) session.commit() print("Nouveau state id: {}".format(new_state.id)) # UPDATE — modifie l'attribut directement, commit enregistre state = session.query(State).filter_by(name="Louisiana").first() if state: state.name = "New Louisiana" session.commit() print("Modifié : {}".format(state.name)) # DELETE — marque pour suppression, commit confirme to_delete = session.query(State).filter_by(name="New Louisiana").first() if to_delete: session.delete(to_delete) session.commit() print("Supprimé.")
add() et commit(), l'objet est "détaché" par défaut. Accéder à new_state.id fonctionne car SQLAlchemy le recharge automatiquement.# NE JAMAIS FAIRE — injection SQL possible ! name = sys.argv[1] cur.execute("SELECT * FROM states WHERE name = '" + name + "'")
' OR '1'='1, il lit toute la table. Catastrophique.# Utilise les paramètres — MySQLdb échappe automatiquement name = sys.argv[1] cur.execute( "SELECT * FROM states WHERE name = %s", (name,) # tuple obligatoire, même pour 1 param )
# Connexion qui ne se ferme jamais conn = MySQLdb.connect(...) cur = conn.cursor() cur.execute("SELECT * FROM states") print(cur.fetchall()) # ← manque cur.close() et conn.close()
# Ferme dans l'ordre inverse d'ouverture conn = MySQLdb.connect(...) cur = conn.cursor() cur.execute("SELECT * FROM states") print(cur.fetchall()) cur.close() # curseur d'abord conn.close() # connexion ensuite
with pour automatiser la fermeture.# Interdit dans ce projet ! session.execute("SELECT * FROM states")
execute() avec SQLAlchemy. Toujours utiliser la Query API.# Requête via l'ORM — zéro SQL brut states = session.query(State)\ .order_by(State.id)\ .all() for state in states: print("{}: {}".format(state.id, state.name))
.filter(), .order_by(), .limit(), .all().class State(Base): __tablename__ = 'states' id = Column(Integer, primary_key=True) name = Column(String(128))
python3 -c 'print(State.__doc__)'. Sans docstring = 0 points.class State(Base): """Modèle représentant la table states.""" __tablename__ = 'states' id = Column(Integer, primary_key=True, nullable=False) name = Column(String(128), nullable=False)
Première ligne : #!/usr/bin/python3. Module, classe, fonction : chacun a sa docstring. Vérifié automatiquement par Holberton.
Avec MySQLdb, toujours cur.execute("... WHERE x = %s", (val,)). Le tuple en deuxième argument est obligatoire — même pour un seul paramètre.
Le code ne doit pas s'exécuter à l'import. Tout le code principal va dans ce bloc. Testé avec import dans les scripts de validation.
Évite les erreurs "MySQL server has gone away" sur les connexions idle. Toujours l'activer quand l'engine est créé.
Le projet interdit explicitement session.execute(). Passe toujours par session.query(Model).filter(...).all() et ses dérivés.
Vérifie ton code avec pycodestyle *.py. Maximum 79 chars par ligne, espaces autour des opérateurs, ligne vide en fin de fichier.
╔══════════════════════════════════════════════════════════════╗ ║ PYTHON ORM — RÉSUMÉ ║ ╠══════════════════════════════════════════════════════════════╣ ║ MYSQLDB (bas niveau) SQLALCHEMY (ORM complet) ║ ║ ────────────────────── ────────────────────────── ║ ║ 1. MySQLdb.connect(...) create_engine(url, ...) ║ ║ 2. conn.cursor() Base.metadata.create_all() ║ ║ 3. cur.execute("SQL", args) session.query(Model) ║ ║ 4. cur.fetchall() .filter(Model.col == val) ║ ║ 5. print(row) .order_by(Model.col) ║ ║ 6. cur.close() .all() / .first() / .count() ║ ║ 7. conn.close() session.add(obj) → commit() ║ ║ session.delete(obj) → commit() ║ ╠══════════════════════════════════════════════════════════════╣ ║ MODÈLE SQLALCHEMY ║ ║ ───────────────── ║ ║ Base = declarative_base() ║ ║ class Model(Base): ║ ║ __tablename__ = 'table_name' ║ ║ id = Column(Integer, primary_key=True) ║ ║ name = Column(String(128), nullable=False) ║ ╠══════════════════════════════════════════════════════════════╣ ║ RÈGLES HOLBERTON ║ ║ ──────────────── ║ ║ ✓ #!/usr/bin/python3 en ligne 1 ║ ║ ✓ Docstrings : module + classe + chaque fonction ║ ║ ✓ if __name__ == "__main__": pour tout code exécutable ║ ║ ✓ Paramètres %s pour MySQLdb — jamais de concat SQL ║ ║ ✗ .execute() interdit avec SQLAlchemy ║ ║ ✓ pycodestyle (2.7.*) sans erreurs ║ ╚══════════════════════════════════════════════════════════════╝