mirror of
https://github.com/sqlalchemy/sqlalchemy.git
synced 2026-06-04 06:48:27 -04:00
8755dc3d66
- Fixed query.join() when used in conjunction with a columns-only clause and an SQL-expression ON clause in the join.
201 lines
6.1 KiB
Python
201 lines
6.1 KiB
Python
"""this example illustrates a "vertical table". an object is stored with individual attributes
|
|
represented in distinct database rows. This allows objects to be created with dynamically changing
|
|
fields that are all persisted in a normalized fashion."""
|
|
|
|
from sqlalchemy import *
|
|
from sqlalchemy.orm import *
|
|
from sqlalchemy.orm.collections import mapped_collection
|
|
import datetime
|
|
|
|
engine = create_engine('sqlite://', echo=False)
|
|
meta = MetaData(engine)
|
|
|
|
Session = scoped_session(sessionmaker())
|
|
|
|
# represent Entity objects
|
|
entities = Table('entities', meta,
|
|
Column('entity_id', Integer, primary_key=True),
|
|
Column('title', String(100), nullable=False),
|
|
)
|
|
|
|
# represent named, typed fields
|
|
entity_fields = Table('entity_fields', meta,
|
|
Column('field_id', Integer, primary_key=True),
|
|
Column('name', String(40), nullable=False),
|
|
Column('datatype', String(30), nullable=False))
|
|
|
|
# associate a field row with an entity row, including a typed value
|
|
entity_values = Table('entity_values', meta,
|
|
Column('value_id', Integer, primary_key=True),
|
|
Column('field_id', Integer, ForeignKey('entity_fields.field_id'), nullable=False),
|
|
Column('entity_id', Integer, ForeignKey('entities.entity_id'), nullable=False),
|
|
Column('int_value', Integer),
|
|
Column('string_value', String(500)),
|
|
Column('binary_value', PickleType),
|
|
Column('datetime_value', DateTime))
|
|
|
|
meta.create_all()
|
|
|
|
class Entity(object):
|
|
"""a persistable dynamic object.
|
|
|
|
Marshalls attributes into a dictionary which is
|
|
mapped to the database.
|
|
|
|
"""
|
|
def __init__(self, **kwargs):
|
|
for k in kwargs:
|
|
setattr(self, k, kwargs[k])
|
|
|
|
def __getattr__(self, key):
|
|
"""Proxy requests for attributes to the underlying _entities dictionary."""
|
|
|
|
if key[0] == '_':
|
|
return super(Entity, self).__getattr__(key)
|
|
try:
|
|
return self._entities[key].value
|
|
except KeyError:
|
|
raise AttributeError(key)
|
|
|
|
def __setattr__(self, key, value):
|
|
"""Proxy requests for attribute set operations to the underlying _entities dictionary."""
|
|
|
|
if key[0] == "_" or hasattr(Entity, key):
|
|
object.__setattr__(self, key, value)
|
|
return
|
|
|
|
try:
|
|
ev = self._entities[key]
|
|
ev.value = value
|
|
except KeyError:
|
|
ev = _EntityValue(key, value)
|
|
self._entities[key] = ev
|
|
|
|
class _EntityField(object):
|
|
"""Represents a field of a particular name and datatype."""
|
|
|
|
def __init__(self, name, datatype):
|
|
self.name = name
|
|
self.datatype = datatype
|
|
|
|
class _EntityValue(object):
|
|
"""Represents an individual value."""
|
|
|
|
def __init__(self, key, value):
|
|
datatype = self._figure_datatype(value)
|
|
field = \
|
|
Session.query(_EntityField).filter(
|
|
and_(_EntityField.name==key, _EntityField.datatype==datatype)
|
|
).first()
|
|
|
|
if not field:
|
|
field = _EntityField(key, datatype)
|
|
Session.add(field)
|
|
|
|
self.field = field
|
|
setattr(self, self.field.datatype + "_value", value)
|
|
|
|
def _figure_datatype(self, value):
|
|
typemap = {
|
|
int:'int',
|
|
str:'string',
|
|
datetime.datetime:'datetime',
|
|
}
|
|
for k in typemap:
|
|
if isinstance(value, k):
|
|
return typemap[k]
|
|
else:
|
|
return 'binary'
|
|
|
|
def _get_value(self):
|
|
return getattr(self, self.field.datatype + "_value")
|
|
|
|
def _set_value(self, value):
|
|
setattr(self, self.field.datatype + "_value", value)
|
|
value = property(_get_value, _set_value)
|
|
|
|
def name(self):
|
|
return self.field.name
|
|
name = property(name)
|
|
|
|
|
|
# the mappers are a straightforward eager chain of
|
|
# Entity--(1->many)->EntityValue-(many->1)->EntityField
|
|
# notice that we are identifying each mapper to its connecting
|
|
# relation by just the class itself.
|
|
mapper(_EntityField, entity_fields)
|
|
mapper(
|
|
_EntityValue, entity_values,
|
|
properties = {
|
|
'field' : relation(_EntityField, lazy=False, cascade='all')
|
|
}
|
|
)
|
|
|
|
mapper(Entity, entities, properties = {
|
|
'_entities' : relation(
|
|
_EntityValue,
|
|
lazy=False,
|
|
cascade='all',
|
|
collection_class=mapped_collection(lambda entityvalue: entityvalue.field.name)
|
|
)
|
|
})
|
|
|
|
session = Session()
|
|
entity1 = Entity(
|
|
title = 'this is the first entity',
|
|
name = 'this is the name',
|
|
price = 43,
|
|
data = ('hello', 'there')
|
|
)
|
|
|
|
entity2 = Entity(
|
|
title = 'this is the second entity',
|
|
name = 'this is another name',
|
|
price = 50,
|
|
data = ('hoo', 'ha')
|
|
)
|
|
|
|
session.add_all([entity1, entity2])
|
|
session.commit()
|
|
|
|
for entity in session.query(Entity):
|
|
print "Entity id %d:" % entity.entity_id, entity.title, entity.name, entity.price, entity.data
|
|
|
|
# perform some changes, add a new Entity
|
|
|
|
entity1.price = 90
|
|
entity1.title = 'another new title'
|
|
entity2.data = {'oof':5,'lala':8}
|
|
|
|
entity3 = Entity(
|
|
title = 'third entity',
|
|
name = 'new name',
|
|
price = '$1.95', # note we change 'price' to be a string.
|
|
# this creates a new _EntityField separate from the
|
|
# one used by integer 'price'.
|
|
data = 'some data'
|
|
)
|
|
session.add(entity3)
|
|
|
|
session.commit()
|
|
|
|
print "----------------"
|
|
for entity in session.query(Entity):
|
|
print "Entity id %d:" % entity.entity_id, entity.title, entity.name, entity.price, entity.data
|
|
|
|
print "----------------"
|
|
# illustrate each _EntityField that's been created and list each Entity which uses it
|
|
for ent_id, name, datatype in session.query(_EntityField.field_id, _EntityField.name, _EntityField.datatype):
|
|
print name, datatype, "(Enitites:", ",".join([
|
|
str(entid) for entid in session.query(Entity.entity_id).\
|
|
join(
|
|
(_EntityValue, _EntityValue.entity_id==Entity.entity_id),
|
|
(_EntityField, _EntityField.field_id==_EntityValue.field_id)
|
|
).filter(_EntityField.field_id==ent_id)
|
|
]), ")"
|
|
|
|
# delete all the Entity objects
|
|
for entity in session.query(Entity):
|
|
session.delete(entity)
|
|
session.commit()
|