====== Elixir ======
* **Web**: [[http://elixir.ematia.de/trac/]]
* **Tutorial**: [[http://elixir.ematia.de/trac/wiki/TutorialDivingIn]]
* **FAQ**: [[http://elixir.ematia.de/trac/wiki/FAQ]]
* **Ejemplos**: [[http://elixir.ematia.de/trac/wiki/Examples]]
===== Básico =====
==== Inicio ====
=== Pasos ===
- Importaremos los elementos de Elixir mediante ''from elixir import *''.
- Indicaremos la base de datos mediante ''metadata.bind''
- Definiremos el modelo con clases ''Entity'' y relaciones.
- Crearemos el modelo con métodos ''setup'' y ''create''.
==== Definir la base de datos ====
=== Definición de una DB SQLite ===
Para indicar que nuestra base de datos será una SQLite en un fichero con nombre ''movies.sqlite''que está en el directorio del usuario haremos:
metadata.bind = "sqlite:///movies.sqlite"
==== Crear el modelo ====
Una vez tengamos el modelo definido utilizaremos los métodos siguientes:
* ''setup_all'' enlaza los objetos tabla que hemos indicado, es el método que tendremos que llamar para iniciar un modelo (tanto si existe como si no). Si le pasásemos ''True'' como argumento crearía todas las tablas de una vez.
* ''create_all'' lanza los SQL de creación a la base de datos.
==== Cosas a saber ====
* Si ''metadata.bind.echo'' es asignado a ''True'' se irá mostrando por consola las acciones que se vayan realizando.
* Si una clase ''Entity'' tiene el método ''__repr__'' definido Elixir mostrará sus objetos por consola tal y como este devuelva en formato string.
==== Ejemplo de definición ====
Definiremos el modelo en un archivo denominado ''model.py'' que contendrá lo siguiente:
from elixir import *
metadata.bind = "sqlite:///movies.sqlite"
metadata.bind.echo = True
class Movie(Entity):
title = Field(Unicode(30))
year = Field(Integer)
description = Field(UnicodeText)
def __repr__(self):
return '' % (self.title, self.year)
Cuando queramos que se cree la DB ejecutaremos:
from model import *
setup_all()
create_all()
==== Manipulación de registros ====
Manipularemos los registros a partir de objetos de los tipos que hayamos definido. Cuando llamemos a ''session.commit()'' se ejecutarán los comandos sobre la DB.
=== Inserción de registros ===
Una vez llamado a ''session.commit()'' se insertarán los objetos de los tipos de las tablas que hemos creado en memoria. \\
Agregaríamos un registro película de la siguiente forma:
Movie(title=u"Blade Runner", year=1982)
session.commit()
=== Modificación de registros ===
movie = Movie.query.first()
movie.year = 1983
session.commit()
=== Eliminación de registros ===
movie = Movie.query.first()
movie.delete()
session.commit()
===== Elementos del modelo =====
==== Tabla ====
Para crear una tabla definiremos una clase que herede de ''Entity''. Los campos serán atributos del tipo ''Field'' a los cuales les indicaremos el tipo.
class Movie(Entity):
title = Field(Unicode(30))
year = Field(Integer)
description = Field(UnicodeText)
=== Tipos de datos posibles ===
* ''Unicode(n)'', texto de hasta n carácteres.
* ''Integer''
* ''UnicodeText'', texto largo.
* ''BigInteger''
* ''Boolean''
* ''Date'', se introduciría por ejemplo como: ''element.date_retrieved = datetime.datetime.date(datetime.datetime.now())''
* ''DateTime''
* ''Enum'', para hacer enumeraciones:
class MyStuff(Entity):
e = Field(Enum(u'foobar', u'baz', u'quux', None))
=== Sobre los campos... ===
* Podemos indicar que un campo sea clave primaria pasándole por parámetro ''primary_key=True''. Si no lo hacemos la clave se generará una automáticamente.
* Si indicamos en dos campos que sean claves primarias entonces esta será compuesta por los dos.
* Podemos indicar que un campo es único utilizando la propiedad ''unique=True''.
class Person(Entity):
his_unique_number = Field(Integer, unique=True)
* Podemos indicar si un campo puede ser nulo mediante el parámetro ''nullable''.
* Podemos indicar que se indexe un campo agregando el parámetro ''index=True'':
class Article(Entity):
aid = Field(UnicodeText, index = True)
title = Field(UnicodeText, nullable = True)
* Podemos indicar que un valor por defecto lo coja de una función:
class Article(Entity):
last_updated = Field(DateTime, default=datetime.datetime.now)
=== Propiedades y métodos de las entidades ===
* Si se crea el campo identificador automáticamente será accesible desde la propiedad ''id''.
* El método ''to_dict()'' devuelve el objeto en formato diccionario.
==== Relaciones ====
Para indicar las relaciones entre clases\tablas utilizaremos las funciones ''ManyToOne'' y ''OneToMany'', y ''ManyToMany'' según la cardinalidad:
class Movie(Entity):
title = Field(Unicode(30))
year = Field(Integer)
description = Field(UnicodeText)
director = ManyToOne('Director')
def __repr__(self):
return '' % (self.title, self.year)
class Director(Entity):
name = Field(Unicode(60))
movies = OneToMany('Movie')
def __repr__(self):
return '' % self.name
A partir de ahora podremos insertar los datos de formas distintas:
rscott = Director(name=u"Ridley Scott")
glucas = Director(name=u"George Lucas")
alien = Movie(title=u"Alien", year=1979)
swars = Movie(title=u"Star Wars", year=1977)
brunner = Movie(title=u"Blade Runner", year=1982)
rscott.movies.append(brunner)
rscott.movies.append(alien)
swars.director = glucas
Para las relaciones muchos a muchos utilizaremos ''ManyToMany'':
class Genre(Entity):
name = Field(Unicode(15), primary_key=True)
movies = ManyToMany('Movie')
def __repr__(self):
return '' % self.name
class Movie(Entity):
title = Field(Unicode(30), primary_key=True)
year = Field(Integer, primary_key=True)
description = Field(UnicodeText)
director = ManyToOne('Director')
genres = ManyToMany('Genre')
def __repr__(self):
return '' % (self.title, self.year)
scifi = Genre(name=u"Science-Fiction")
rscott = Director(name=u"Ridley Scott")
glucas = Director(name=u"George Lucas")
alien = Movie(title=u"Alien", year=1979, director=rscott, genres=[scifi, Genre(name=u"Horror")])
brunner = Movie(title=u"Blade Runner", year=1982, director=rscott, genres=[scifi])
swars = Movie(title=u"Star Wars", year=1977, director=glucas, genres=[scifi])
Si quisieramos que en una relación ''ManyToOne'' la foreign key sea parte de la primary key haremos...
class User(Entity):
name = Field(String(30), primary_key=True)
class Post(Entity):
id = Field(Integer, primary_key=True)
user = ManyToOne('User', primary_key=True)
En el siguiente ejemplo ''User'' no contiene clave privada, en Post la clave primaria es compuesta por ''id'' y ''user_name'', que será generado. \\ \\
Para que una tabla tenga referencias consigo misma:
class Employee(Entity):
name = Field(String(32))
supervisor = ManyToOne('Employee')
employees = OneToMany('Employee') # esta línea es opcional
class User(Entity):
using_options(tablename="user")
friends = ManyToMany('User', inverse="is_friend_of")
is_friend_of = ManyToMany('User', inverse="friends")
enemies = ManyToMany('User', inverse="is_enemy_of")
is_enemy_of = ManyToMany('User', inverse="enemies")
==== Opciones ====
A partir de las opciones podemos indicar a Elixir cómo crear las tablas, para ello utilizaremos ''using_options'' en la definición de la tabla:
class Movie(Entity):
using_options(tablename='movies')
title = Field(Unicode(30))
year = Field(Integer)
description = Field(UnicodeText)
Las propiedades que podemos definir son:
* ''tablename'', el nombre de la tabla que ha de crear.
* ''inheritance'', el tipo de herencia de clases.
* ''autoload'', carga la definición de columnas de una DB ya existente.
* ''shortnames'', indica el tipo de generación de los nombres de tablas, si su valor es ''False'' (por defecto) se agregará el nombre del módulo al que pertenece.
* ''auto_primarykey'', se indica el nombre de la columna que será el identificador (automático).
* ''order_by'' es un string (o lista de strings) correspondientes a los nombres de columna a partir de los cuales se ordenarán los resultados.
* ''session'' indica el tipo de sesión para el contexto de la entidad.
==== Herencia entre tablas ====
Para ello haremos la herencia entre clases:
class Person(Entity):
using_options(inheritance='multi')
name = Field(Unicode(60))
def __repr__(self):
return '' % self.name
class Actor(Person):
using_options(inheritance='multi')
movies = ManyToMany('Movie')
def __repr__(self):
return '' % self.name
class Director(Person):
using_options(inheritance='multi')
movies = OneToMany('Movie')
def __repr__(self):
return '' % self.name
Elixir soporta la herencia del tipo ''single table'' y ''joined/multi-table'' de SQLAlchemy, en este ejemplo se ha utilizado la ''multi-table'', lo que significa que para cada cada una de las entidades se generará una sola tabla. \\
Es obligatorio indicar en cada una de las entidades (incluyendo el padre) qué tipo de herencia tendrá.
>>> rscott = Director(name=u"Ridley Scott")
>>> glucas = Director(name=u"George Lucas")
>>> hford = Actor(name=u"Harrison Ford")
>>> mhamill = Actor(name=u"Mark Hamill")
>>> sweaver = Actor(name=u"Sigourney Weaver")
>>> session.commit()
>>> Person.query.all()
[,
,
,
,
]
>>> Actor.query.all()
[, , ]
===== Consultas =====
Aunque las consultas se basan en las de SQLAlchemy y podemos lanzar con ese formato Elixir también nos agiliza dicha tarea.
* [[http://docs.sqlalchemy.org/en/rel_0_8/orm/tutorial.html#common-filter-operators|Operadores]], deberemos importar sqlalchemy: ''from sqlalchemy import desc''.
==== Consultas básicas ====
* Recoger todos: ''clase.query.all()''
* Recoger el primero: ''clase.query.first()''
==== Filtros ====
Movie.query.filter_by(title=u"Alien").one()
Movie.query.filter(Movie.year > 1980).all()
Movie.query.filter(Movie.director.has(name=u'Ridley Scott')).all()
Movie.query.filter(Movie.director.has(Director.name.endswith(u'Scott'))).all()
Movie.query.filter(Movie.genres.any(name=u"Horror")).all()
==== Generar una query ====
d = Director.get_by(name=u'Ridley Scott') # Class.get_by(xxx) is a shortcut for Class.query.filter_by(xxx).first()
q = Movie.query.filter_by(director=d)
q.filter_by(year=1979).all()
from sqlalchemy import desc
q.order_by(desc(Movie.year)).all()
==== Realizar un join ====
PlayerAchievements.query.filter(PlayerAchievements.player == p).filter(PlayerAchievements.teamconfig == t).all()
==== Otros ====
=== Consultar por identificador ===
e = Element.get(identifier)
=== Consultar una lista vacía ===
vendors = Vendors.query.filter(~Vendors.items.any())
=== Lanzar código SQL directamente ===
from elixir import *
for result in session.execute('select * from people'):
print result['name']
conn = metadata.bind.engine.connect()
conn.execute('delete from css_actions')
===== Avanzado =====
==== Sesiones ====
* Ver [[fw:sqlalchemy#sesiones|sesiones]] en SQLAlchemy.
Elixir utilizar una sesión global y todas las entidades de Elixir están linkadas a esta. Si se quiere usar otra sesión para otras entindades únicamente se ha de indicar utilizando la opción adecuada.
===== Notas =====
==== Otros ====
* Ejecutar SQL al iniciar la aplicación:
class MySetup:
def connect(self, dbapi_con, con_record):
dbapi_con.execute('alter session set NLS_SORT=BINARY_CI')
dbapi_con.execute('alter session set NLS_COMP=LINGUISTIC')
engine = create_engine('oracle:...', listeners=[MySetup()])
elixir.metadata.bind = engine