You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
245 lines
7.9 KiB
245 lines
7.9 KiB
14 years ago
|
'''
|
||
|
This module provides support for defining properties on your entities. It both
|
||
|
provides, the `Property` class which acts as a building block for common
|
||
|
properties such as fields and relationships (for those, please consult the
|
||
|
corresponding modules), but also provides some more specialized properties,
|
||
|
such as `ColumnProperty` and `Synonym`. It also provides the GenericProperty
|
||
|
class which allows you to wrap any SQLAlchemy property, and its DSL-syntax
|
||
|
equivalent: has_property_.
|
||
|
|
||
|
`has_property`
|
||
|
--------------
|
||
|
The ``has_property`` statement allows you to define properties which rely on
|
||
|
their entity's table (and columns) being defined before they can be declared
|
||
|
themselves. The `has_property` statement takes two arguments: first the name of
|
||
|
the property to be defined and second a function (often given as an anonymous
|
||
|
lambda) taking one argument and returning the desired SQLAlchemy property. That
|
||
|
function will be called whenever the entity table is completely defined, and
|
||
|
will be given the .c attribute of the entity as argument (as a way to access
|
||
|
the entity columns).
|
||
|
|
||
|
Here is a quick example of how to use ``has_property``.
|
||
|
|
||
|
.. sourcecode:: python
|
||
|
|
||
|
class OrderLine(Entity):
|
||
|
has_field('quantity', Float)
|
||
|
has_field('unit_price', Float)
|
||
|
has_property('price',
|
||
|
lambda c: column_property(
|
||
|
(c.quantity * c.unit_price).label('price')))
|
||
|
'''
|
||
|
|
||
|
from elixir.statements import PropertyStatement
|
||
|
from sqlalchemy.orm import column_property, synonym
|
||
|
|
||
|
__doc_all__ = ['EntityBuilder', 'Property', 'GenericProperty',
|
||
|
'ColumnProperty']
|
||
|
|
||
|
class EntityBuilder(object):
|
||
|
'''
|
||
|
Abstract base class for all entity builders. An Entity builder is a class
|
||
|
of objects which can be added to an Entity (usually by using special
|
||
|
properties or statements) to "build" that entity. Building an entity,
|
||
|
meaning to add columns to its "main" table, create other tables, add
|
||
|
properties to its mapper, ... To do so an EntityBuilder must override the
|
||
|
corresponding method(s). This is to ensure the different operations happen
|
||
|
in the correct order (for example, that the table is fully created before
|
||
|
the mapper that use it is defined).
|
||
|
'''
|
||
|
def create_pk_cols(self):
|
||
|
pass
|
||
|
|
||
|
def create_non_pk_cols(self):
|
||
|
pass
|
||
|
|
||
|
def before_table(self):
|
||
|
pass
|
||
|
|
||
|
def create_tables(self):
|
||
|
'''
|
||
|
Subclasses may override this method to create tables.
|
||
|
'''
|
||
|
|
||
|
def after_table(self):
|
||
|
pass
|
||
|
|
||
|
def create_properties(self):
|
||
|
'''
|
||
|
Subclasses may override this method to add properties to the involved
|
||
|
entity.
|
||
|
'''
|
||
|
|
||
|
def before_mapper(self):
|
||
|
pass
|
||
|
|
||
|
def after_mapper(self):
|
||
|
pass
|
||
|
|
||
|
def finalize(self):
|
||
|
pass
|
||
|
|
||
|
# helper methods
|
||
|
def add_table_column(self, column):
|
||
|
self.entity._descriptor.add_column(column)
|
||
|
|
||
|
def add_mapper_property(self, name, prop):
|
||
|
self.entity._descriptor.add_property(name, prop)
|
||
|
|
||
|
def add_mapper_extension(self, ext):
|
||
|
self.entity._descriptor.add_mapper_extension(ext)
|
||
|
|
||
|
|
||
|
class CounterMeta(type):
|
||
|
'''
|
||
|
A simple meta class which adds a ``_counter`` attribute to the instances of
|
||
|
the classes it is used on. This counter is simply incremented for each new
|
||
|
instance.
|
||
|
'''
|
||
|
counter = 0
|
||
|
|
||
|
def __call__(self, *args, **kwargs):
|
||
|
instance = type.__call__(self, *args, **kwargs)
|
||
|
instance._counter = CounterMeta.counter
|
||
|
CounterMeta.counter += 1
|
||
|
return instance
|
||
|
|
||
|
|
||
|
class Property(EntityBuilder):
|
||
|
'''
|
||
|
Abstract base class for all properties of an Entity.
|
||
|
'''
|
||
|
__metaclass__ = CounterMeta
|
||
|
|
||
|
def __init__(self, *args, **kwargs):
|
||
|
self.entity = None
|
||
|
self.name = None
|
||
|
|
||
|
def attach(self, entity, name):
|
||
|
"""Attach this property to its entity, using 'name' as name.
|
||
|
|
||
|
Properties will be attached in the order they were declared.
|
||
|
"""
|
||
|
self.entity = entity
|
||
|
self.name = name
|
||
|
|
||
|
# register this property as a builder
|
||
|
entity._descriptor.builders.append(self)
|
||
|
|
||
|
def __repr__(self):
|
||
|
return "Property(%s, %s)" % (self.name, self.entity)
|
||
|
|
||
|
|
||
|
class GenericProperty(Property):
|
||
|
'''
|
||
|
Generic catch-all class to wrap an SQLAlchemy property.
|
||
|
|
||
|
.. sourcecode:: python
|
||
|
|
||
|
class OrderLine(Entity):
|
||
|
quantity = Field(Float)
|
||
|
unit_price = Field(Numeric)
|
||
|
price = GenericProperty(lambda c: column_property(
|
||
|
(c.quantity * c.unit_price).label('price')))
|
||
|
'''
|
||
|
|
||
|
def __init__(self, prop, *args, **kwargs):
|
||
|
super(GenericProperty, self).__init__(*args, **kwargs)
|
||
|
self.prop = prop
|
||
|
#XXX: move this to Property?
|
||
|
self.args = args
|
||
|
self.kwargs = kwargs
|
||
|
|
||
|
def create_properties(self):
|
||
|
if hasattr(self.prop, '__call__'):
|
||
|
prop_value = self.prop(self.entity.table.c)
|
||
|
else:
|
||
|
prop_value = self.prop
|
||
|
prop_value = self.evaluate_property(prop_value)
|
||
|
self.add_mapper_property(self.name, prop_value)
|
||
|
|
||
|
def evaluate_property(self, prop):
|
||
|
if self.args or self.kwargs:
|
||
|
raise Exception('superfluous arguments passed to GenericProperty')
|
||
|
return prop
|
||
|
|
||
|
|
||
|
class ColumnProperty(GenericProperty):
|
||
|
'''
|
||
|
A specialized form of the GenericProperty to generate SQLAlchemy
|
||
|
``column_property``'s.
|
||
|
|
||
|
It takes a function (often given as an anonymous lambda) as its first
|
||
|
argument. Other arguments and keyword arguments are forwarded to the
|
||
|
column_property construct. That first-argument function must accept exactly
|
||
|
one argument and must return the desired (scalar-returning) SQLAlchemy
|
||
|
ClauseElement.
|
||
|
|
||
|
The function will be called whenever the entity table is completely
|
||
|
defined, and will be given
|
||
|
the .c attribute of the table of the entity as argument (as a way to
|
||
|
access the entity columns). The ColumnProperty will first wrap your
|
||
|
ClauseElement in an
|
||
|
"empty" label (ie it will be labelled automatically during queries),
|
||
|
then wrap that in a column_property.
|
||
|
|
||
|
.. sourcecode:: python
|
||
|
|
||
|
class OrderLine(Entity):
|
||
|
quantity = Field(Float)
|
||
|
unit_price = Field(Numeric)
|
||
|
price = ColumnProperty(lambda c: c.quantity * c.unit_price,
|
||
|
deferred=True)
|
||
|
|
||
|
Please look at the `corresponding SQLAlchemy
|
||
|
documentation <http://www.sqlalchemy.org/docs/05/mappers.html
|
||
|
#sql-expressions-as-mapped-attributes>`_ for details.
|
||
|
'''
|
||
|
|
||
|
def evaluate_property(self, prop):
|
||
|
return column_property(prop.label(None), *self.args, **self.kwargs)
|
||
|
|
||
|
|
||
|
class Synonym(GenericProperty):
|
||
|
'''
|
||
|
This class represents a synonym property of another property (column, ...)
|
||
|
of an entity. As opposed to the `synonym` kwarg to the Field class (which
|
||
|
share the same goal), this class can be used to define a synonym of a
|
||
|
property defined in a parent class (of the current class). On the other
|
||
|
hand, it cannot define a synonym for the purpose of using a standard python
|
||
|
property in queries. See the Field class for details on that usage.
|
||
|
|
||
|
.. sourcecode:: python
|
||
|
|
||
|
class Person(Entity):
|
||
|
name = Field(String(30))
|
||
|
primary_email = Field(String(100))
|
||
|
email_address = Synonym('primary_email')
|
||
|
|
||
|
class User(Person):
|
||
|
user_name = Synonym('name')
|
||
|
password = Field(String(20))
|
||
|
'''
|
||
|
|
||
|
def evaluate_property(self, prop):
|
||
|
return synonym(prop, *self.args, **self.kwargs)
|
||
|
|
||
|
#class Composite(GenericProperty):
|
||
|
# def __init__(self, prop):
|
||
|
# super(GenericProperty, self).__init__()
|
||
|
# self.prop = prop
|
||
|
|
||
|
# def evaluate_property(self, prop):
|
||
|
# return composite(prop.label(self.name))
|
||
|
|
||
|
#start = Composite(Point, lambda c: (c.x1, c.y1))
|
||
|
|
||
|
#mapper(Vertex, vertices, properties={
|
||
|
# 'start':composite(Point, vertices.c.x1, vertices.c.y1),
|
||
|
# 'end':composite(Point, vertices.c.x2, vertices.c.y2)
|
||
|
#})
|
||
|
|
||
|
|
||
|
has_property = PropertyStatement(GenericProperty)
|
||
|
|