Explication à propos de l'initialisation / configuration de SQLAlchemy et Elixir dans Pylons

Voici dans ce billet quelques notes à propos de la configuration de Elixir dans Pylons "0.9.7". J'y ai aussi ajouté une "explication" personnelle de l'initialisation/configuration de SQLAlchemy dans Pylons.

1.   SQLAlchemy

1.1.   Généralités

L'initialisation de SQLAlchemy consiste par exemple à :

  • créer un objet "metadata"
from sqlalchemy import MetaData

metadata = MetaData()
  • créer un objet "session"
from sqlalchemy.orm import sessionmaker
session = sessionmaker()
  • créer un objet "engine"
from sqlalchemy import create_engine
engine = create_engine('sqlite://') # in memory
  • connecter la session à un "engine"
session.configure(bind = engine)

En résumé, il nous faut 3 éléments :

  • un objet MetaData
  • un objet Session
  • un objet Engine

Nous retrouverons chacun de ces trois éléments dans les deux méthodes de configurations présentées ci-dessous.

1.2.   Par défaut, comment Pylons initialise SQLAlchemy ?

Pylons défini quelques conventions quant à l'initialisation de SQLAlchemy.

Les fichiers en questions :

config/environment.py
model/__init__.py
model/meta.py
1.2.1.   model/meta.py

Le module "model/meta.py" permet un accès standardisé aux variables "engine", "Session" et "metadata".

Extrait de quelques lignes du module "model/meta.py" :

from sqlalchemy import MetaData

engine = None
Session = None
metadata = MetaData()
1.2.2.   model/__init__.py

Le module "model/__init__.py" se charge de l'initialisation de la couche modèle. Dans notre contexte (utilisation de SQLAlchemy) ce module se charge d'initialiser SQLAlchemy.

Extrait de quelques lignes du module "model/__init__.py" :

from sqlalchemy import orm

from pylonsapp.model import meta

def init_model(engine):
    sm = orm.sessionmaker(autoflush=True, autocommit=False, bind=engine)

    meta.engine = engine
    meta.Session = orm.scoped_session(sm)

Ce module défini une fonction nommée "init_model" avec un seul paramètre nommé "engine". Cette fonction se charge de créer un objet Session et de connecter ce dernier avec l'objet engine passé en paramètre. Cette fonction est appelé par le module "config/environment.py".

1.2.3.   config/environment.py

Pour finir, le module config/environment.py est chargé de la configuration globale du framework Pylons. Celle-ci s'effectue en fonction de paramètres définies dans un fichier ".ini". Ce fichier ".ini" est passé en paramètre lors du lancement de Pylons (par exemple : paster serve development.ini).

Extrait de quelques lignes du module "config/environment.py" :

from sqlalchemy import engine_from_config

...

from pylonsapp.model import init_model

def load_environment(global_conf, app_conf):

    ...

    engine = engine_from_config(config, 'sqlalchemy.')
    init_model(engine)

La fonction "engine_from_config" crée un objet Engine en fonction des paramètres présents dans le fichier ".ini".

1.2.4.   Résumé

Pour résumer :

  • model/meta.py : création de l'objet MetaData
  • config/environment.py : création de l'objet Engine en fonction des paramètres définis dans un fichier ".ini"
  • model/__init__.py : création de l'objet Session et connection de ce dernier avec l'objet engine créé dans environment.py

Note : par convention, model/__init__.py doit aussi se charger de l'initialisation des entités de la couche modèle.

2.   Elixir

2.1.   Configuration standard de Elixir

Voici la méthode standard d'initialisation de Elixir :

import elixir

elixir.metadata.bind = "sqlite:///movies.sqlite"
elixir.metadata.bind.echo = True

# ... déclaration des entités

elixir.setup_all()

Elixir met directement à disposition les objets suivants :

  • Metadata : elixir.metadata
  • Session : elixir.session

L'objet fournit par "elixir.metadata" est identique à un objet créé via "sqlalchemy.MetaData()".

Il est tout à fait possible de remplacer l'objet Session accessible via "elixir.session"... c'est ce qui va être fait dans la section suivante.

2.2.   Initialiser Elixir dans Pylons

Voici maintenant comment configurer Elixir dans Pylons.

2.2.1.   model/meta.py

Extrait de quelques lignes du module "model/meta.py" :

import elixir

engine = None
Session = None
metadata = elixir.metadata

Cette fois metadata contient l'objet Metadata défini par Elixir.

2.2.2.   model/__init__.py

Extrait de quelques lignes du module "model/__init__.py" :

from sqlalchemy import orm
import elixir

from pylonsapp.model import meta

meta.Session = orm.scoped_session(orm.sessionmaker(autoflush=False, expire_on_commit=False))
elixir.options_defaults.update(dict(shortnames=True, inheritance='multi', polymorphic=True))
elixir.session = meta.Session
from pylonsapp.model.entities import *

def init_model(engine):
    meta.metadata.bind = engine
    meta.engine = engine
    elixir.setup_all()

Explications du contenu/changement de ce module :

  • cette fois, l'objet Session n'est pas créé dans la fonction "init_model" mais avant le chargement des classes "Entities"
  • "elixir.options_defaults.update" permet de configurer certains "comportements" de Elixir
  • le module "pylonsapp.model.entities" contient la déclaration des classes entities
  • la fonction "init_model" permet de connecter l'objet Engine à l'objet Metadata créé par Elixir. Ensuite "elixir.setup_all()" permet l'initialisation des entities.

3.   Modification de "lib/base.py"

Le billet Using Elixir with Pylons précise qu'il faut modifier le module "lib/base.py" afin de s'assurer du "nettoyage" de la Session de SQLAlchemy.

from pylonsapp.meta import Session

class BaseController(WSGIController):

    def __call__(self, environ, start_response):
        try:
            return WSGIController.__call__(self, environ, start_response)
        finally:
            Session.remove()

4.   Notes

  • Cette configuration de Elixir permet toujours de créer des tables, classes... via les méthodes classiques de SQLAlchemy
  • En cas du problème suivant : rien n'est enregistré lors de l'exécution de session.commit() c'est sans doute parce que les entities ont été sauvegardé dans une autre session.
blog comments powered by Disqus

Mes flux :