Skip to main content
FastAPI est un framework web Python asynchrone, et SQLAlchemy est l’ORM Python le plus répandu. Dans ce guide, vous allez définir un modèle SQLAlchemy typé, créer la table correspondante à l’aide d’une migration Alembic, puis exposer avec FastAPI des endpoints de création, de lecture, de mise à jour et de suppression. L’application s’appuie sur le driver asyncpg, et chaque connexion est chiffrée via TLS avec vérification complète des certificats.

Prérequis

  • uv et Python 3.11 ou version ultérieure. Ce guide a été testé avec Python 3.13, FastAPI 0.142, SQLAlchemy 2.1, asyncpg 0.31, Alembic 1.20 et Uvicorn 0.54.
  • Un compte ClickHouse Cloud
  • psql, pour créer la base de données. Vous pouvez également exécuter le statement CREATE DATABASE dans la console SQL.

Créer un service ClickHouse Managed Postgres

Dans la console ClickHouse Cloud, cliquez sur New service et sélectionnez Postgres. L’instance est prête en quelques minutes. Pour une présentation pas à pas, consultez le guide de démarrage rapide.

Obtenir vos informations de connexion

Cliquez sur Connect dans la barre latérale gauche de votre service. La fenêtre modale affiche votre nom d’utilisateur, votre mot de passe, le serveur et le port, et propose un sélecteur Directly / via PgBouncer. Ce guide utilise les deux types de connexion :
  • PgBouncer (port 6432) pour l’application. SQLAlchemy maintient un pool de connexions dans chaque worker Uvicorn, et le PgBouncer intégré permet à de nombreux workers et répliques de se partager un nombre restreint de connexions Postgres. Il fonctionne en mode pool de connexions par transaction.
  • Direct (port 5432) pour Alembic. Les migrations exécutent du DDL, peuvent s’exécuter longtemps et dépendre de l’état de session ; elles doivent donc communiquer directement avec Postgres.
Activez Use SSL et cliquez sur Download CA certificate. Vous pouvez également le télécharger depuis Settings → CA Certificate. Ce certificat est propre à votre instance, ce qui permet au driver de vérifier qu’il communique bien avec votre serveur. Lorsque Use SSL est activé, l’URL affichée dans la fenêtre modale se termine par sslmode=verify-full&sslrootcert=.... asyncpg ne lit pas ces paramètres ; copiez donc uniquement le mot de passe et l’hôte depuis la fenêtre modale. L’application transmet en effet le certificat de CA directement dans le code (voir Configurer la connexion).

Configurer le projet

Créez un projet et installez FastAPI, Uvicorn, SQLAlchemy avec son extra asyncio, asyncpg et Alembic :
Vous l’intégrez à une application existante ?Ignorez les commandes uv init et mkdir. Dans votre projet, exécutez uv add "sqlalchemy[asyncio]" asyncpg alembic, puis passez directement à Configurer la connexion. Si votre application utilise SQLite via SQLAlchemy en mode asynchrone :
  • Modifiez votre appel à create_async_engine pour qu’il corresponde à celui de app/db.py. Dans migrations/env.py, importez Base et vos modèles depuis vos propres modules.
  • Retirez Base.metadata.create_all de la phase de démarrage. Alembic crée vos tables existantes dans Postgres, mais ne copie pas les données de SQLite.

Configurer la connexion

Déplacez le certificat de CA que vous avez téléchargé dans le répertoire du projet et renommez-le en ca-certificate.pem. Créez une base de données pour l’application. Remplacez <PASSWORD> et l’hôte par les valeurs affichées dans la fenêtre modale Connect :
Créez un fichier .env à la racine du projet, ou ajoutez ces variables à votre fichier existant. Les deux URL ne diffèrent que par le port, utilisent le schéma postgresql+asyncpg et ne contiennent aucun paramètre SSL :
.env
Le chemin DATABASE_CA_CERT est résolu par rapport au répertoire depuis lequel vous exécutez les commandes. Si votre mot de passe contient des caractères spéciaux tels que @, / ou #, encodez-les au format URL. Ne versionnez pas le fichier .env : il doit rester hors du contrôle de version. Créez app/db.py. Ce fichier définit les paramètres TLS, le moteur et une fabrique de sessions partagée par le reste de l’application :
app/db.py
Un ssl.SSLContext créé avec PROTOCOL_TLS_CLIENT exige un certificat valide et vérifie le nom d’hôte, ce qui équivaut à sslmode=verify-full. SQLAlchemy le transmet à asyncpg via connect_args.
Transmettez le certificat de CA dans connect_args
  • Sans argument ssl, asyncpg utilise TLS si le serveur le propose, mais ne vérifie pas le certificat.
  • Ne recopiez pas sslmode ni sslrootcert de la fenêtre modale Connect dans l’URL. SQLAlchemy transmet les paramètres d’URL à asyncpg sous forme d’arguments nommés, et la connexion échoue avec l’erreur TypeError: connect() got an unexpected keyword argument 'sslmode'.
  • Sous Python 3.13 et versions ultérieures, un contexte créé avec ssl.create_default_context échoue avec l’erreur certificate verify failed: Missing Authority Key Identifier, car cette fonction active des vérifications X.509 strictes. Créez plutôt le contexte avec ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT), comme indiqué.
  • Si le certificat de CA est incorrect, la connexion échoue avec l’erreur certificate verify failed: unable to get local issuer certificate. Si le nom d’hôte ne correspond pas, par exemple lorsque vous vous connectez par adresse IP, elle échoue avec l’erreur certificate verify failed: IP address mismatch.
Si vous préférez conserver les paramètres TLS dans l’URL, ou si votre application utilise des sessions SQLAlchemy synchrones, utilisez le driver psycopg plutôt qu’asyncpg. Reposant sur libpq, il accepte les paramètres de la fenêtre modale Connect, par exemple postgresql+psycopg://...:5432/guide_fastapi?sslmode=verify-full&sslrootcert=ca-certificate.pem, aussi bien avec create_engine qu’avec create_async_engine.

Définir le modèle

Créez app/models.py contenant un modèle SQLAlchemy typé :
app/models.py

Exécuter les migrations

Créez un environnement Alembic à partir du modèle async :
Remplacez le contenu du fichier migrations/env.py généré. La nouvelle version charge vos modèles pour l’autogénération et se connecte directement à Postgres avec les mêmes paramètres TLS que l’application :
migrations/env.py
Alembic ignore la valeur de substitution sqlalchemy.url définie dans alembic.ini, car env.py crée lui-même le moteur. Générez une migration à partir du modèle, puis appliquez-la :
Vérifiez le fichier généré dans migrations/versions, puis commitez-le avec votre code. Alembic enregistre la révision appliquée dans la table alembic_version. À chaque modification d’un modèle, exécutez de nouveau revision --autogenerate et upgrade head.

Ajouter les endpoints de l’API

Créez app/main.py. Chaque requête dispose de sa propre AsyncSession grâce à une dépendance FastAPI, et le pool du moteur est fermé à l’arrêt de l’application :
app/main.py
SQLAlchemy exécute les statements de chaque session dans une seule transaction, qui prend fin lors de l’appel à session.commit. À la fermeture de la session, toute modification non validée est annulée. Lors d’un INSERT, SQLAlchemy récupère les valeurs générées de id et created_at à l’aide de RETURNING, si bien que create_todo n’a pas besoin d’une requête supplémentaire.

Exécuter et vérifier

Démarrez l’application :
Dans un second terminal, créez deux tâches, marquez la première comme terminée, puis supprimez la seconde :
La requête DELETE renvoie 204 No Content. Une requête portant sur un todo inexistant renvoie 404 :
Ouvrez http://localhost:3112/todos dans votre navigateur pour afficher la liste des tâches restantes, ou http://localhost:3112/docs pour tester les endpoints dans la documentation interactive de l’API générée par FastAPI. Pour vérifier que la connexion directe utilise bien une connexion TLS vérifiée, interrogez pg_stat_ssl avec les mêmes paramètres TLS que l’application :
Pour afficher les données dans la console, ouvrez la console SQL dans la barre latérale gauche de votre service, développez guide_fastapi puis public, et cliquez sur la table todos. La table alembic_version juste à côté contient l’historique des migrations. La table todos contient la ligne restante : Arrêtez l’application avec Ctrl+C.

Utiliser PgBouncer

L’application fonctionne via PgBouncer avec les paramètres par défaut d’asyncpg et de SQLAlchemy :
  • asyncpg exécute chaque requête sous forme de requête préparée nommée au niveau du protocole, et SQLAlchemy en met jusqu’à 100 en cache par connexion. Le PgBouncer fourni prend en charge ces requêtes en mode pool de connexions par transaction, et asyncpg les libère au moyen d’un message Close au niveau du protocole plutôt qu’avec la commande SQL DEALLOCATE. Vous n’avez donc besoin ni de statement_cache_size=0 ni d’une fonction prepared_statement_name_func personnalisée. Si vous désactivez malgré tout le cache de requêtes d’asyncpg, ajoutez également prepared_statement_cache_size=0 à l’URL pour désactiver le cache de SQLAlchemy.
  • Les paramètres de session définis avec SET, comme search_path, ne sont pas conservés d’une transaction à l’autre et peuvent fuiter vers d’autres clients. Utilisez plutôt SET LOCAL à l’intérieur d’une transaction.
  • pg_stat_ssl rend compte de la connexion entre PgBouncer et Postgres ; la vérification TLS ci-dessus renvoie donc (False, None) via le port 6432. La connexion de l’application à PgBouncer est pourtant bien vérifiée : un certificat de CA incorrect provoque le même échec.
  • Alembic ne pose pas de verrous consultatifs et exécute chaque upgrade dans une seule transaction ; il fonctionne donc lui aussi via PgBouncer. La connexion directe reste toutefois préférable pour les migrations longues.
Modifier le type d’une colonnePgBouncer conserve les requêtes préparées sur ses connexions Postgres. Après une migration qui modifie le type d’une colonne renvoyée par une requête, par exemple de integer à bigint, cette requête échoue via PgBouncer avec InvalidCachedStatementError: cached statement plan is invalid due to a database schema or configuration change, y compris depuis de nouvelles connexions, jusqu’à ce que PgBouncer remplace ses connexions Postgres. Pour rétablir la situation, fermez les connexions Postgres de PgBouncer à la base de données via la connexion directe, en vous connectant avec le même utilisateur que l’application. PgBouncer se connecte à Postgres en local : ses connexions n’ont donc pas de client_addr, et la requête épargne les connexions directes. PgBouncer ouvre alors de nouvelles connexions, et chaque connexion de l’application issue du pool peut encore échouer une fois avant que SQLAlchemy ne rafraîchisse son cache :
L’ajout d’une colonne ne provoque pas ce problème, car SQLAlchemy énumère explicitement les colonnes qu’il sélectionne.

Étapes suivantes

  • Connexion : chaînes de connexion, PgBouncer et TLS
  • Paramètres : paramètres de Postgres et de PgBouncer, comme max_connections
  • Répliques en lecture : acheminer les requêtes en lecture seule vers une réplique dotée d’un second moteur
  • Sécurité : listes d’accès IP et réseau privé
Dernière modification le 14 octobre 2026