Automatiser un peu plus SQLAlchemy declarative 3


Avec l’intégration de l’interface déclarative d’SQLAlchemy, le projet Elexir est mort, et bien mort. Mais avec lui, la syntaxe la plus simple de déclaration pour cet ORM. En effet, par défaut, SQLA vous oblige à spécifier le nom de la table et l’attribut ID pour chaque classe :

from sqlalchemy import Column, Integer, String
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import create_engine
 
Base = declarative_base()
 
class Person(Base):
    # explicit is better than... fuck it !
    __tablename__ = 'person' 
    id = Column(Integer, primary_key=True)
    name = Column(String(250), nullable=False)
 
class Animal(Base):
    __tablename__ = 'animal'
    id = Column(Integer, primary_key=True) # Paie ton DRY
    name = Column(String(250), nullable=False)
 
engine = create_engine('sqlite:///db.db.db...db')
Base.metadata.create_all(engine)

SQLA est très flexible, et il existe des tas de raisons pour vouloir un PK ou un nom de table custo. Mais dans beaucoup de projets, le nom de la table peut être le nom de la classe, et la PK peut être un ID auto incrémenté.

Heureusement, la lib vous permet de customiser absolument tout, même la manière dont on doit la customiser. Si c’est pas méta, tout ça…

Bref, on peut créer sa propre base déclarative qui va faire tout ça pour nous :

from sqlalchemy.ext.declarative import as_declarative, declared_attr
 
# Ceci sera le parent de tout nos objets
@as_declarative()
class Base(object):
 
    # Vu qu'on a pas vraiment envie de se réécrire la
    # métaclasse, SQLA nous file ce gentil décorateur 
    # pour déclarer des attributs qui ne sont pas des 
    # champs
    @declared_attr
    def __tablename__(cls):
        # le nom de la table est le nom de la classe
        return cls.__name__.lower()
    # L'id c'est la vie
    id = Column(Integer, primary_key=True)
 
class Person(Base):
    name = Column(String(250), nullable=False)
 
class Animal(Base):
    name = Column(String(250), nullable=False)

Evidemment il faut que vous soyez certains d’éviter les conflits liés à cette décision, mais c’est quand même vachement pratique.

3 thoughts on “Automatiser un peu plus SQLAlchemy declarative

  • Pierre

    J’ai lu pas plus tard que ce matin dans un petit bouquin dédié à Flask que la convention pour nommer les tables, c’était généralement d’utiliser un nom au pluriel (par exemple si ta classe est Animal, alors la table sera animals… oui, en anglais, parce qu’en français animals ça fait un peu con). L’auteur explique que c’est pour ça que SQLAlchemy n’impose pas un nom particulier et qu’il faut le définir à la mimine.

  • Sam Post author

    En vérité c’est plus compliqué que ça. Si j’ai une table “comments” par exemple, il est fort probable qu’une autre app puisse utiliser un nom similaire. Il faut donc aussi se mettre d’accord sur un préfixe qui servira d’espace de nom. Ce que fait par exemple Django en utilisant le nom de l’app à cette fin. De plus, certains systèmes ont une taille de nom de table limité en caractères. Ainsi, il faut aussi trouver un moyen de réduire le nom si il est trop grand. Ce que fait django en tronquant le nom sous la limite des caractères – 4, puis en rajoutant un numéro basé sur un hash du nom de la table. Comme d’hab, “we need to go deeper”.

  • moumoutte

    SQLAlchemy, ça poutre. Mais la syntaxe django, c’est mieux (session.query(Model)).filter(Model.id == 12) WHAT !?).

    Du coup, j’avais écrit un djangoificator à l’époque quand je découvrais SQLAlchemy. http://sqla-helpers.readthedocs.org/en/latest/sqla_helpers.html#module-sqla_helpers

    Ca mériterait d’etre appronfondie, parce que je prefère la syntaxe django couplé à la puissace d’SQLAlchemy.
    C’est un projet sans prétention et il faudrait encore quelques trucs pour que ça deviennent *vraiment* utilisable dans des cas compliqués (je pense notamment au requête sur du many-to-many). Si quelqu’un à le temps, il est le bienvenue.

Leave a comment

Des questions Python sans rapport avec l'article ? Posez-les sur IndexError.