Maison > développement back-end > Tutoriel Python > Meilleures pratiques pour Alembic et SQLAlchemy

Meilleures pratiques pour Alembic et SQLAlchemy

Mary-Kate Olsen
Libérer: 2024-11-01 10:20:30
original
855 Les gens l'ont consulté

Dans cet article, je passerai brièvement en revue quelques bonnes pratiques qui aident à garder les projets organisés, à simplifier la maintenance des bases de données et à éviter les pièges courants lorsque vous travaillez avec Alembic et SQLAlchemy. Ces techniques m'ont évité des ennuis plus d'une fois. Voici ce que nous allons aborder :

  1. Conventions de dénomination
  2. Tri des migrations par date
  3. Commentaires sur les tables, colonnes et migrations
  4. Gestion des données dans les migrations sans modèles
  5. Test de migration (test d'escalier)
  6. Service d'exécution de migrations
  7. Utiliser des mixins pour les modèles

1. Conventions de dénomination

SQLAlchemy vous permet de mettre en place une convention de dénomination qui est automatiquement appliquée à toutes les tables et contraintes lors de la génération de migrations. Cela vous évite de nommer manuellement les index, les clés étrangères et d'autres contraintes, ce qui rend la structure de la base de données prévisible et cohérente.

Pour configurer cela dans un nouveau projet, ajoutez une convention à la classe de base afin qu'Alembic utilise automatiquement le format de nommage souhaité. Voici un exemple de convention qui fonctionne bien dans la plupart des cas :

from sqlalchemy import MetaData
from sqlalchemy.orm import DeclarativeBase

convention = {
    'all_column_names': lambda constraint, table: '_'.join(
        [column.name for column in constraint.columns.values()]
    ),
    'ix': 'ix__%(table_name)s__%(all_column_names)s',
    'uq': 'uq__%(table_name)s__%(all_column_names)s',
    'ck': 'ck__%(table_name)s__%(constraint_name)s',
    'fk': 'fk__%(table_name)s__%(all_column_names)s__%(referred_table_name)s',
    'pk': 'pk__%(table_name)s',
}

class BaseModel(DeclarativeBase):
    metadata = MetaData(naming_convention=convention)
Copier après la connexion
Copier après la connexion
Copier après la connexion

2. Trier les migrations par date

Les noms de fichiers de migration d'Alembic commencent généralement par une balise de révision, ce qui peut rendre l'ordre des migrations dans le répertoire aléatoire. Parfois, il est utile de les trier par ordre chronologique.

Alembic permet de personnaliser le modèle de nom de fichier de migration dans le fichier alembic.ini avec le paramètre file_template. Voici deux formats de dénomination pratiques pour organiser les migrations :

  1. Basé sur la date :
file_template = %%(year)d-%%(month).2d-%%(day).2d_%%(rev)s_%%(slug)s
Copier après la connexion
Copier après la connexion
Copier après la connexion
  1. Basé sur l'horodatage Unix :
file_template = %%(epoch)d_%%(rev)s_%%(slug)s
Copier après la connexion
Copier après la connexion

L'utilisation de dates ou d'horodatages Unix dans les noms de fichiers permet d'organiser les migrations, ce qui facilite la navigation. Je préfère utiliser les horodatages Unix, et un exemple sera fourni dans la section suivante.

3. Commentaires pour les tables et les migrations

Pour ceux qui travaillent en équipe, commenter les attributs est une bonne pratique. Avec les modèles SQLAlchemy, envisagez d'ajouter des commentaires directement aux colonnes et aux tables au lieu de vous fier aux docstrings. De cette façon, les commentaires sont disponibles à la fois dans le code et dans la base de données, ce qui permet aux administrateurs de base de données ou aux analystes de comprendre plus facilement les objectifs des tables et des champs.

class Event(BaseModel):
    __table_args__ = {'comment': 'System (service) event'}

    id: Mapped[uuid.UUID] = mapped_column(
        UUID(as_uuid=True),
        primary_key=True,
        comment='Event ID - PK',
    )
    service_id: Mapped[int] = mapped_column(
        sa.Integer,
        sa.ForeignKey(
            f'{IntegrationServiceModel.__tablename__}.id',
            ondelete='CASCADE',
        ),
        nullable=False,
        comment='FK to integration service that owns the event',
    )
    name: Mapped[str] = mapped_column(
        sa.String(256), nullable=False, comment='Event name'
    )
Copier après la connexion
Copier après la connexion

Il est également utile d'ajouter des commentaires aux migrations pour les rendre plus faciles à trouver dans le système de fichiers. Un commentaire peut être ajouté avec -m lors de la génération de la migration. Le commentaire apparaîtra dans la docstring et dans le nom du fichier. Cette dénomination facilite grandement la localisation de la migration requise.

from sqlalchemy import MetaData
from sqlalchemy.orm import DeclarativeBase

convention = {
    'all_column_names': lambda constraint, table: '_'.join(
        [column.name for column in constraint.columns.values()]
    ),
    'ix': 'ix__%(table_name)s__%(all_column_names)s',
    'uq': 'uq__%(table_name)s__%(all_column_names)s',
    'ck': 'ck__%(table_name)s__%(constraint_name)s',
    'fk': 'fk__%(table_name)s__%(all_column_names)s__%(referred_table_name)s',
    'pk': 'pk__%(table_name)s',
}

class BaseModel(DeclarativeBase):
    metadata = MetaData(naming_convention=convention)
Copier après la connexion
Copier après la connexion
Copier après la connexion

4. Évitez d'utiliser des modèles dans les migrations

Les modèles sont souvent utilisés pour des manipulations de données, telles que le transfert de données d'une table à une autre ou la modification des valeurs de colonnes. Cependant, l'utilisation de modèles ORM dans les migrations peut entraîner des problèmes si le modèle change après la création de la migration. Dans de tels cas, une migration basée sur l'ancien modèle sera interrompue une fois exécutée, car le schéma de la base de données risque de ne plus correspondre au modèle actuel.

Les migrations doivent être statiques et indépendantes de l'état actuel des modèles pour garantir une exécution correcte quelles que soient les modifications du code. Vous trouverez ci-dessous deux façons d'éviter d'utiliser des modèles pour manipuler des données.

  • Utilisez du SQL brut pour la manipulation des données :
file_template = %%(year)d-%%(month).2d-%%(day).2d_%%(rev)s_%%(slug)s
Copier après la connexion
Copier après la connexion
Copier après la connexion
  • Définir les tables directement dans la migration : Si vous souhaitez utiliser SQLAlchemy pour les manipulations de données, vous pouvez définir manuellement des tables directement dans la migration. Cela garantit un schéma statique au moment de l'exécution de la migration et ne dépendra pas des modifications apportées aux modèles.
file_template = %%(epoch)d_%%(rev)s_%%(slug)s
Copier après la connexion
Copier après la connexion

5. Test d'escalier pour les tests de migration

Le test Stairway consiste à tester progressivement les migrations de mise à niveau/rétrogradation, étape par étape, pour garantir que l'ensemble de la chaîne de migration fonctionne correctement. Cela garantit que chaque migration peut créer avec succès une nouvelle base de données à partir de zéro et rétrograder sans problème. L'ajout de ce test à CI est inestimable pour les équipes, car il permet d'économiser du temps et de la frustration.

Best Practices for Alembic and SQLAlchemy

L'intégration du test dans votre projet peut se faire facilement et rapidement. Vous pouvez trouver un exemple de code dans ce référentiel. Il comprend également d'autres tests de migration précieux qui peuvent être utiles.

6. Service des migrations

Un service distinct pour effectuer des migrations. Ce n’est qu’une façon d’effectuer des migrations. Lors du développement local ou dans des environnements similaires au développement, cette méthode s'intègre bien. Je voudrais vous rappeler la fonctionnalité conditionnelle depend_on, qui est pertinente ici. Nous prenons l'image de l'application avec Alambic et l'exécutons dans un conteneur séparé. Nous ajoutons une dépendance sur la base de données avec la condition que les migrations ne démarrent que lorsque la base de données est prête à gérer les requêtes (service_healthy). De plus, un depend_on conditionnel (service_completed_successfully) peut être ajouté pour l'application, garantissant qu'elle ne démarre qu'une fois les migrations terminées avec succès.

class Event(BaseModel):
    __table_args__ = {'comment': 'System (service) event'}

    id: Mapped[uuid.UUID] = mapped_column(
        UUID(as_uuid=True),
        primary_key=True,
        comment='Event ID - PK',
    )
    service_id: Mapped[int] = mapped_column(
        sa.Integer,
        sa.ForeignKey(
            f'{IntegrationServiceModel.__tablename__}.id',
            ondelete='CASCADE',
        ),
        nullable=False,
        comment='FK to integration service that owns the event',
    )
    name: Mapped[str] = mapped_column(
        sa.String(256), nullable=False, comment='Event name'
    )
Copier après la connexion
Copier après la connexion

La condition depend_on garantit que les migrations ne s'exécutent qu'une fois que la base de données est entièrement prête et que l'application démarre une fois les migrations terminées.

7. Mixins pour modèles

Bien que cela puisse paraître évident, il est important de ne pas le négliger. L'utilisation de mixins est un moyen pratique d'éviter la duplication de code. Les mixins sont des classes qui contiennent des champs et des méthodes fréquemment utilisés, qui peuvent être intégrés dans tous les modèles où ils sont nécessaires. Par exemple, nous avons souvent besoin des champs Created_at et Updated_at pour suivre les heures de création et de mise à jour des enregistrements. Il peut également être utile d'utiliser un identifiant basé sur l'UUID pour normaliser les clés primaires. Tout cela peut être encapsulé dans des mixins.

from sqlalchemy import MetaData
from sqlalchemy.orm import DeclarativeBase

convention = {
    'all_column_names': lambda constraint, table: '_'.join(
        [column.name for column in constraint.columns.values()]
    ),
    'ix': 'ix__%(table_name)s__%(all_column_names)s',
    'uq': 'uq__%(table_name)s__%(all_column_names)s',
    'ck': 'ck__%(table_name)s__%(constraint_name)s',
    'fk': 'fk__%(table_name)s__%(all_column_names)s__%(referred_table_name)s',
    'pk': 'pk__%(table_name)s',
}

class BaseModel(DeclarativeBase):
    metadata = MetaData(naming_convention=convention)
Copier après la connexion
Copier après la connexion
Copier après la connexion

En ajoutant ces mixins, nous pouvons inclure l'identifiant UUID et les horodatages dans n'importe quel modèle si nécessaire :

file_template = %%(year)d-%%(month).2d-%%(day).2d_%%(rev)s_%%(slug)s
Copier après la connexion
Copier après la connexion
Copier après la connexion

Conclusion

Gérer les migrations peut être difficile, mais suivre ces pratiques simples permet de garder les projets bien organisés et gérables. Les conventions de dénomination, le tri des dates, les commentaires et les tests m'ont sauvé du chaos et aidé à éviter les erreurs. J'espère que cet article vous sera utile — n'hésitez pas à partager vos propres conseils de migration dans les commentaires !

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

source:dev.to
Déclaration de ce site Web
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn
Derniers articles par auteur
Tutoriels populaires
Plus>
Derniers téléchargements
Plus>
effets Web
Code source du site Web
Matériel du site Web
Modèle frontal