Journal Un peu de sucre pour une meilleure alchimie ?

Posté par . Licence CC by-sa
Tags : aucun
7
26
jan.
2013

Yo nal' !

Récemment, pour le boulot, j'ai eu l'occasion de me mettre à SQLAlchemy, et je dois bien avouer que c'est plutôt pas mal. Par contre, j'ai trouvé que les notions de bases ne sont pas spécialement évidentes à appréhender. Ces histoires de sessions qui englobent des transactions qui sont auto-gérées par des {data/transaction}manager, etc. Bref, il y a encore des notions que je ne maitrise pas bien, mais je crois que là, ça commence à rentrer.

Je suis arrivé comme une fleur sur un développement d'un projet en cours, et il y avait des bugs de connexions IDLE avec des transactions commencées et jamais terminées.

Le truc chiant quoi.

Cela dit, c'était une excellente occasion de se pencher sur la doc, de voir comment se servir de la techno et de repérer dans le code du dit-projet comment tout ça était utilisé.

De fil en aiguille ça nous à amener à pas mal de refonte de code, on s'est aperçu que les sessions et transactions n'étaient pas utilisées correctement.

Lors de la refonte, j'ai eu envie de virer des fonctions écrites qui faisaient des choses comme ça :

mylib.getMyObjById(id)

Le truc lourd c'était qu'il y avait autant de méthode de récupération d'objets qu'il y'avait d'attribut par objet (J'exagère mais à peine). Alors, j'ai sorti mes petites mimines et j'ai commencé à faire un truc un peu plus générique.

Ca a commencé par l'implémentation d'une classe Mixin qui fournit des méthodes de classes genre, le traditionnel get.

MyObj.get(session, id=12)

Au fur et à mesure de la refonte, on s'est aperçu que c'était relativement lisible et clair que d'utiliser cette méthode, avec peu de code écrit à la base. On a un peu poussé le truc et du coup, on se retrouve avec quelques méthodes all, filter, search comme méthodes fournies.

Il y a quelques choses aussi qui nous semblait un "peu lourd" (je dis bien "un peu") à écrire , c'était la récupération d'objet suivant l'attribut d'un objet en relation.

Par exemple si on veut récupérer tous les traitements dont le status à pour nom "Ok", il fallait faire

session.query.join("status", Status.id == Treatment.status_id).filter(Status.name == u'Ok')

(Je sais, on aurait pu utiliser la jointure implicite des relations d'objet)
Ayant eu quelques fricotages avec Django, je trouvais ça quand même un peu moins verbeux pour filtrer suivant un attribut d'une relation.

Alors j'ai poussé un peu la démarche, et j'ai django-ifié une syntaxe qui faisait appel à du SQLAlchemy, du coup on pouvait faire

Treatment.filter(session, status__name=u'Ok')

On s'en est servit pour refondre complétement le code d'une application web et ça a pour le moment pas trop mal fonctionné.

Et là on est partie avec sur un application plus lourde avec un schéma beaucoup plus dense et on a bon espoir.

L'idée, ce n'est pas de refaire complétement une sur-couche à SQLAlchemy, juste de fournir un peu de sucre syntaxique pour récupérer ponctuellement un ou plusieurs objets. Je reste parfaitement conscient que pour des schémas bien compliqués et bien denses, cela ne suffira jamais. Mais utilisé ponctuellement pour la récupération simple d'objet, ça nous a fait gagner des lignes de codes.

Je suis au courant que cette syntaxe ne plait pas trop. Le double '_' (underscore) ça rebute. Mais voilà, nous ça nous a servit et on apprécie le filtrage par attribut de relation assez simplement. Encore une fois, ça n'a pas pour but de mettre une surcouche complète à SQLAlchemy, et j'insiste sur ce point.

Tout ça pour te dire, cher journal, que si jamais tu es intéressé par l'idée, nous sommes en train de mettre le bousin à disposition.

Déjà, tu pourras trouver sur github les sources: https://github.com/moumoutte/sqla_helpers/
Et un peu de doc ailleurs(parce que je sais pas encore utiliser github correctement)

Ca manque un peu de travail pour ce qui est du packaging (il faut juste prendre le temps de le faire) ce n'est pas encore sur le Pypi officiel, mais si l'idée plaît, c'est avec plaisir que nous prendrons du temps pour faire ça correctement.

Ou bien, même, il est possible que l'idée te plaise mais que tu es déjà trouvé des projets qui faisaient déjà ça et en mieux (nous on a pas trouvé, à part Elixir mais un peu trop complet (et pas assez maintenu ?) par rapport à ce qu'on voulait). Dans ce cas, hésite pas à me le dire, je pense qu'on sera content d'utiliser un truc mieux :)

  • # Read the docs

    Posté par (page perso) . Évalué à  2 .

    Pour la doc qui se synchronise avec github : https://readthedocs.org/ (c'est ce que tout le monde utilise à l'heure actuelle). Par contre, avec la doc en français tu te limite à un public restreint. Quelques tests pourraient aussi être sympas.

    Je suis un peu embête par la session en argument à tout les appels. Avec pyramid, j'ai l'habitude d'avoir DBSession en variable globale (à peu près), du coup passer la session en argument partout me fait pas des masses envie. Je sais pas comment ça pourrait s'interfacer avec ça.

    J'ai l'habitude de faire un truc similaire dans mes projets (en plus fainéant), qui ressemble à ça :

    class Base(object):
        id = Column(Integer, primary_key=True)
    
        @property
        @classmethod # ça marche pas direct, mais c'est l'idée
        def q(cls):
            return DBSession.query(cls)
    
    Base = declarative_base(cls=Base)
    
    
    • [^] # Re: Read the docs

      Posté par . Évalué à  2 . Dernière modification : le 26/01/13 à 16:57

      Tootafay d'accord avec la session.

      Le problème c'est qu'on a pas que des applications qui font du pyramid, et qui plus est, difficile de faire un "import" d'une application particulière dans un bibliothèque que se veut être utilisable partout.

      La prochaine étape, si ce projet continue à évoluer, ça serait justement de plus trop s'embeter avec la session. Mais la c'était le moyen le plus rapide et le plus simple à mettre en œuvre.

      Mais oui, passer la session partout, au début ça nous à déranger aussi. Maintenant, c'était quand même mieux que l'ancienne version du code, alors on a pris ça comme ça.

      Et merci pour le lien de la doc, je vais regarder ça :)

      • [^] # Re: Read the docs

        Posté par (page perso) . Évalué à  3 .

        Peut être un truc dans ce goût la :
        BaseModel.register(DBSession)

        et dans le code de BaseModel:
        @classmethod
        def get(cls, **kwargs):
        session = BaseModel.getSession()
        query = cls.search(session, **kwargs)
        return query.one()

        • [^] # Re: Read the docs

          Posté par . Évalué à  2 .

          Ca y est. J'ai commité le changement basé sur ton idée.

          BaseModel fournit un attribut à setter , sessionmaker, qui permet d'enregistrer une machine à Session (Comment on traduit 'session maker' ?) plutôt qu'une session déjà construite.

          Et ainsi, on peut fournir un 'scoped_session'. Je n'ai pas encore trouvé comment utiliser un sessionmaker ou une session déjà construite. Mais l'idée d'utiliser le scoped_session, ça ramène toujours la même session (dans un même thread). A voir si on peut pas se contenter de setter le sessionmaker avec le retour de la fonction scoped_session.

          L'enregistrement doit se faire au moment de l'initialisation de l'appli. Et à l'utilisation, c'est sympa (merci Georges Dubus) d'utiliser l'attribut 'cls' de la fonction declarative_base. Plus besoin de Mixin.

          Merci Cyprien Lepannérer et Georges Dubus pour vos idées.

          • [^] # Re: Read the docs

            Posté par . Évalué à  2 .

            Prise de recul sur ce qui vient d'être commité:

            • Pour passer une session globale : BaseModel.sessionmaker = lambda: DBSession
            • Si la session n'est plus passée en paramètre , comment gérer les applications qui ont besoin de plusieurs bases ?
  • # Merci

    Posté par (page perso) . Évalué à  4 .

    Un grand merci de la part des DBA (_peut être même de ton DBA qui habite dans la cave de Port Royal_) !
    Les softs qui laissent des " In transaction" dans Postgresql sans raison c'est galère !

    • [^] # Re: Merci

      Posté par . Évalué à  3 .

      Merci aux DBA's surtout de nous fournir des outils pour détecter ce genre de trucs  !

  • # Éditer ses commentaires !

    Posté par . Évalué à  1 .

    Visiblement on peut pas trop.. Dans ce cas, est-ce que je m'auto-répond quitte à flooder un peu la page ou j'arrête de faire des commentaires ?

    Ou bien je demande gentiment à un modérateur ? (En particulier je voudrais répondre à ma question sur les multiples bases, il parait qu'en fait c'est à SQLAlchemy de gérer ça, mais j'en ai pas la certitude, je connais mal les options avancées : http://docs.sqlalchemy.org/en/latest/orm/session.html#partitioning-strategies)

    (L'option "réfléchir avant de poster un commentaire" a été envisagée, mais n'a pas été retenue.)

    • [^] # Re: Éditer ses commentaires !

      Posté par (page perso) . Évalué à  3 .

      (L'option "réfléchir avant de poster un commentaire" a été envisagée, mais n'a pas été retenue.)

      pas de souci, tu te réponds. Tu auras une belle image à la clé :)

Suivre le flux des commentaires

Note : les commentaires appartiennent à ceux qui les ont postés. Nous n'en sommes pas responsables.