root / elixir / trunk / elixir / properties.py @ 484

Revision 484, 7.9 kB (checked in by ged, 11 months ago)

update all web links

RevLine 
[132]1'''
[236]2This module provides support for defining properties on your entities. It both
[347]3provides, the `Property` class which acts as a building block for common
4properties such as fields and relationships (for those, please consult the
5corresponding modules), but also provides some more specialized properties,
6such as `ColumnProperty` and `Synonym`. It also provides the GenericProperty
7class which allows you to wrap any SQLAlchemy property, and its DSL-syntax
[303]8equivalent: has_property_.
[132]9
10`has_property`
11--------------
[347]12The ``has_property`` statement allows you to define properties which rely on
[236]13their entity's table (and columns) being defined before they can be declared
14themselves. The `has_property` statement takes two arguments: first the name of
15the property to be defined and second a function (often given as an anonymous
16lambda) taking one argument and returning the desired SQLAlchemy property. That
17function will be called whenever the entity table is completely defined, and
[347]18will be given the .c attribute of the entity as argument (as a way to access
[236]19the entity columns).
[132]20
21Here is a quick example of how to use ``has_property``.
22
[223]23.. sourcecode:: python
[132]24
25    class OrderLine(Entity):
26        has_field('quantity', Float)
27        has_field('unit_price', Float)
[347]28        has_property('price',
[132]29                     lambda c: column_property(
30                         (c.quantity * c.unit_price).label('price')))
31'''
32
[221]33from elixir.statements import PropertyStatement
[303]34from sqlalchemy.orm import column_property, synonym
[132]35
[347]36__doc_all__ = ['EntityBuilder', 'Property', 'GenericProperty',
[236]37               'ColumnProperty']
[132]38
[221]39class EntityBuilder(object):
[236]40    '''
[347]41    Abstract base class for all entity builders. An Entity builder is a class
42    of objects which can be added to an Entity (usually by using special
[236]43    properties or statements) to "build" that entity. Building an entity,
44    meaning to add columns to its "main" table, create other tables, add
45    properties to its mapper, ... To do so an EntityBuilder must override the
46    corresponding method(s). This is to ensure the different operations happen
47    in the correct order (for example, that the table is fully created before
48    the mapper that use it is defined).
49    '''
[237]50    def create_pk_cols(self):
51        pass
[132]52
[237]53    def create_non_pk_cols(self):
54        pass
[132]55
[237]56    def before_table(self):
57        pass
58
59    def create_tables(self):
[362]60        '''
61        Subclasses may override this method to create tables.
62        '''
[237]63
64    def after_table(self):
65        pass
66
[243]67    def create_properties(self):
[362]68        '''
[382]69        Subclasses may override this method to add properties to the involved
[362]70        entity.
71        '''
[243]72
[237]73    def before_mapper(self):
[347]74        pass
[237]75
76    def after_mapper(self):
[347]77        pass
[237]78
79    def finalize(self):
[347]80        pass
[237]81
[350]82    # helper methods
[358]83    def add_table_column(self, column):
84        self.entity._descriptor.add_column(column)
85
[350]86    def add_mapper_property(self, name, prop):
87        self.entity._descriptor.add_property(name, prop)
[237]88
[358]89    def add_mapper_extension(self, ext):
90        self.entity._descriptor.add_mapper_extension(ext)
[350]91
92
[236]93class CounterMeta(type):
94    '''
95    A simple meta class which adds a ``_counter`` attribute to the instances of
96    the classes it is used on. This counter is simply incremented for each new
97    instance.
98    '''
[221]99    counter = 0
[132]100
[221]101    def __call__(self, *args, **kwargs):
102        instance = type.__call__(self, *args, **kwargs)
[236]103        instance._counter = CounterMeta.counter
104        CounterMeta.counter += 1
[221]105        return instance
106
107
108class Property(EntityBuilder):
[236]109    '''
110    Abstract base class for all properties of an Entity.
111    '''
112    __metaclass__ = CounterMeta
[347]113
[221]114    def __init__(self, *args, **kwargs):
115        self.entity = None
116        self.name = None
117
118    def attach(self, entity, name):
119        """Attach this property to its entity, using 'name' as name.
120
[236]121        Properties will be attached in the order they were declared.
[221]122        """
123        self.entity = entity
124        self.name = name
125
126        # register this property as a builder
127        entity._descriptor.builders.append(self)
128
129    def __repr__(self):
130        return "Property(%s, %s)" % (self.name, self.entity)
131
132
133class GenericProperty(Property):
[236]134    '''
135    Generic catch-all class to wrap an SQLAlchemy property.
[269]136
137    .. sourcecode:: python
138
139        class OrderLine(Entity):
140            quantity = Field(Float)
141            unit_price = Field(Numeric)
142            price = GenericProperty(lambda c: column_property(
143                             (c.quantity * c.unit_price).label('price')))
[236]144    '''
[347]145
[382]146    def __init__(self, prop, *args, **kwargs):
147        super(GenericProperty, self).__init__(*args, **kwargs)
[221]148        self.prop = prop
[383]149        #XXX: move this to Property?
[382]150        self.args = args
151        self.kwargs = kwargs
[221]152
153    def create_properties(self):
[443]154        if hasattr(self.prop, '__call__'):
[221]155            prop_value = self.prop(self.entity.table.c)
156        else:
157            prop_value = self.prop
158        prop_value = self.evaluate_property(prop_value)
[350]159        self.add_mapper_property(self.name, prop_value)
[221]160
161    def evaluate_property(self, prop):
[382]162        if self.args or self.kwargs:
163            raise Exception('superfluous arguments passed to GenericProperty')
[221]164        return prop
165
[303]166
[221]167class ColumnProperty(GenericProperty):
[236]168    '''
[347]169    A specialized form of the GenericProperty to generate SQLAlchemy
170    ``column_property``'s.
171
[382]172    It takes a function (often given as an anonymous lambda) as its first
173    argument. Other arguments and keyword arguments are forwarded to the
174    column_property construct. That first-argument function must accept exactly
175    one argument and must return the desired (scalar-returning) SQLAlchemy
176    ClauseElement.
[236]177
[382]178    The function will be called whenever the entity table is completely
179    defined, and will be given
180    the .c attribute of the table of the entity as argument (as a way to
181    access the entity columns). The ColumnProperty will first wrap your
182    ClauseElement in an
183    "empty" label (ie it will be labelled automatically during queries),
184    then wrap that in a column_property.
185
[236]186    .. sourcecode:: python
187
188        class OrderLine(Entity):
189            quantity = Field(Float)
190            unit_price = Field(Numeric)
[382]191            price = ColumnProperty(lambda c: c.quantity * c.unit_price,
192                                   deferred=True)
[236]193
[347]194    Please look at the `corresponding SQLAlchemy
[484]195    documentation <http://www.sqlalchemy.org/docs/05/mappers.html
196    #sql-expressions-as-mapped-attributes>`_ for details.
[236]197    '''
198
[221]199    def evaluate_property(self, prop):
[382]200        return column_property(prop.label(None), *self.args, **self.kwargs)
[221]201
[303]202
203class Synonym(GenericProperty):
204    '''
205    This class represents a synonym property of another property (column, ...)
[347]206    of an entity.  As opposed to the `synonym` kwarg to the Field class (which
207    share the same goal), this class can be used to define a synonym of a
[303]208    property defined in a parent class (of the current class). On the other
[347]209    hand, it cannot define a synonym for the purpose of using a standard python
[303]210    property in queries. See the Field class for details on that usage.
211
212    .. sourcecode:: python
213
214    class Person(Entity):
215        name = Field(String(30))
216        primary_email = Field(String(100))
217        email_address = Synonym('primary_email')
218
219    class User(Person):
220        user_name = Synonym('name')
221        password = Field(String(20))
222    '''
223
224    def evaluate_property(self, prop):
[382]225        return synonym(prop, *self.args, **self.kwargs)
[303]226
[221]227#class Composite(GenericProperty):
228#    def __init__(self, prop):
229#        super(GenericProperty, self).__init__()
230#        self.prop = prop
231
232#    def evaluate_property(self, prop):
233#        return composite(prop.label(self.name))
234
235#start = Composite(Point, lambda c: (c.x1, c.y1))
236
237#mapper(Vertex, vertices, properties={
238#    'start':composite(Point, vertices.c.x1, vertices.c.y1),
239#    'end':composite(Point, vertices.c.x2, vertices.c.y2)
240#})
241
242
243has_property = PropertyStatement(GenericProperty)
Note: See TracBrowser for help on using the browser.