Esta web usa cookies propias y de terceros para mejorar tu experiencia de navegación y realizar tareas de análisis. Al continuar con tu navegación entendemos que das tu consentimiento a nuestra política de cookies.

MY NEW STATIC BLOG. WHY? BECAUSE FUCK YOU, THAT'S WHY!




Continuación de http://neorazorx.blogspot.com/2012/01/construyendo-kelinux.html

SQLalchemy es un framework genial, pero su documentación no lo es tanto. Una de las maravillas que tiene es la facilidad para operar con relaciones entre tablas. Creas dos clases que estén relacionadas entre sí y puedes acceder desde una a la otra con si fuese un atributo: clase1.clase2. El problema es definirlas, veamos el ejemplo de la documentación oficial:

from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()

class Parent(Base):
    __tablename__ = 'parent'
    id = Column(Integer, primary_key=True)
    children = relationship("Child", backref="parent")

class Child(Base):
    __tablename__ = 'child'
    id = Column(Integer, primary_key=True)
    parent_id = Column(Integer, ForeignKey('parent.id'))

MAL!!! Esto ni siquiera inicia porque se ha definido la relación en la primera clase, sin haber definido todavía la segunda. El ejemplo correcto es este:

from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()

class Parent(Base):
    __tablename__ = 'parent'
    id = Column(Integer, primary_key=True)

class Child(Base):
    __tablename__ = 'child'
    id = Column(Integer, primary_key=True)
    parent_id = Column(Integer, ForeignKey('parent.id'))
    parent = relationship("Parent", backref=backref('children'))

Esto si que funciona y es equivalente. Lo que definimos es la relación "parent" de Child con Parent (que es bidireccional) y además le decimos que la relación de Parent con Child se llama "children". Ahora podemos acceder a Child.parent y nos devolverá el objeto de la clase Parent relacionado. También podemos coger un objeto de la clase Parent y si accedemos a su propiedad children nos devolverá una lista con todos los objetos de la clase Child relacionados con él.

Y ahora que hemos definido correctamente las clases que representan las tablas de la base de datos, ahora que tenemos cherrypy funcionando y jinja2 perfectamente integrado ¿Se acabaron los problemas? NO!!! Tan sólo queda una problema con el que lidiar, pero no te lo vas a encontrar hasta que pongas la web en marcha y empiece a tener tráfico y a PETAR continuamente.

¿Cómo es posible? ¿Dónde está el problema? El problema es que Cherrypy es multihilo, es decir, genera un hilo de ejecución para cada petición, de forma que puede atender a muchos usuarios a la vez, pero SQLachemy no, o por lo menos nadie se lo ha dicho XD

La forma de trabajar con SQLalchemy es sencilla, creas un objeto, modificas lo que quieras y si quieres guardarlo lo añades al objeto sesión, y haces el commit. La clase sesión de SQLalchemy es la clase que se encarga de meter y sacar cosas de la base de datos. Lo normal es crear un único objeto sesión cuando se inicia la aplicación y usarlo, pero claro, si tienes un montón de hilos de ejecución lo que sucede es que para cuando uno quiere guardar la sesión quizás hay otro que la ha reiniciado o que tiene que hacer otras modificaciones antes de guardar. Con un único objeto sesión los errores están asegurados, lo que hay que hacer es definir una sesión para cada petición de cherrypy, esto se hace así:

from cherrypy.process import wspbus, plugins


class SAEnginePlugin(plugins.SimplePlugin):
    def __init__(self, bus):
        plugins.SimplePlugin.__init__(self, bus)
        self.sa_engine = None
        self.bus.subscribe("bind", self.bind)
 
    def start(self):
        # creamos el engine para sqlalchemy
        self.sa_engine = create_engine("mysql://%s:%[email protected]%s:%s/%s" % (MYSQL_USER, MYSQL_PASS, MYSQL_HOST, MYSQL_PORT, MYSQL_DBNAME),
                                       encoding='utf-8', convert_unicode=True, echo=APP_DEBUG)
        # creamos/comprobamos la estructura de tablas
        Base.metadata.create_all(self.sa_engine)
 
    def stop(self):
        if self.sa_engine:
            self.sa_engine.dispose()
            self.sa_engine = None
 
    def bind(self, session):
        session.configure(bind=self.sa_engine)


class SATool(cherrypy.Tool):
    def __init__(self):
        cherrypy.Tool.__init__(self, 'on_start_resource', self.bind_session, priority=20)
        self.session = scoped_session(sessionmaker(autoflush=False, autocommit=False))
 
    def _setup(self):
        cherrypy.Tool._setup(self)
        cherrypy.request.hooks.attach('on_end_resource', self.commit_transaction, priority=80)
 
    def bind_session(self):
        cherrypy.engine.publish('bind', self.session)
        cherrypy.request.db = self.session
 
    def commit_transaction(self):
        cherrypy.request.db = None
        self.session.remove()

cp_config['global']['tools.db.on'] = True

La clase SAEnginePlugin se encarga de iniciar el engine (decirle que se mysql, en el host tal, puerto tal...) y generar las tablas de la base de datos. La clase SATool es la que se encarga de crear una sesión para cada petición de cherrypy.

En kelinux ademas uso el plugin daemonizer para demonizar el proceso y PIDFile para que me devuelve el PID del proceso que se ha creado (para poder matarlo).

Todos estos plugins los integro en cherrypy de esta forma:

if __name__ == "__main__":
    SAEnginePlugin(cherrypy.engine).subscribe()
    cherrypy.tools.db = SATool()
    if APP_RUN_AS_DAEMON:
        d = Daemonizer(cherrypy.engine)
        d.subscribe()
        p = PIDFile(cherrypy.engine, Ke_current_path+"/kelinux.pid")
        p.subscribe()
        cp_config['global']['log.screen'] = False
    cherrypy.quickstart(Main_web(), config=cp_config)

Así tengo que si la variable APP_RUN_AS_DAEMON es True se demoniza, y en cualquier caso se activa cherrypy.tools.db y se usa la clase Main_web para servir las páginas. cp_config es un diccionario con la configuración que uso para cherrypy.

Lo importante es que ahora para guardar las modificaciones en la base de datos hay que hacer:
cherrypy.request.db.commit()
Y para dechacerlas:
cherrypy.request.db.rollback()

No se si esto ha quedado muy claro, en cualquier caso si tienes alguna duda puedes plantearla en kelinux ;-)
comments powered by Disqus

Powered by PussyPress.