root / elixir / tags / 0.5.0 / elixir / properties.py

Revision 269, 6.4 kB (checked in by ged, 4 years ago)

- the columns created by ManyToOne relationships create an index by default,

but this is only a default now instead of an hardcoded value: it can be
disabled now (through column_kwargs). Suggestion by Jason R. Coombs.

- added example of GenericProperty in docstring

Line 
1'''
2This module provides support for defining properties on your entities. It both
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`. It also provides the GenericProperty class which
7allows you to wrap any SQLAlchemy property, and its DSL-syntax equivalent:
8has_property_.
9
10`has_property`
11--------------
12The ``has_property`` statement allows you to define properties which rely on
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
18will be given the .c attribute of the entity as argument (as a way to access
19the entity columns).
20
21Here is a quick example of how to use ``has_property``.
22
23.. sourcecode:: python
24
25    class OrderLine(Entity):
26        has_field('quantity', Float)
27        has_field('unit_price', Float)
28        has_property('price',
29                     lambda c: column_property(
30                         (c.quantity * c.unit_price).label('price')))
31'''
32
33from elixir.statements import PropertyStatement
34from sqlalchemy.orm import column_property
35
36__doc_all__ = ['EntityBuilder', 'Property', 'GenericProperty', 
37               'ColumnProperty']
38
39class EntityBuilder(object):
40    '''
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
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    '''
50    #XXX: add helper methods: add_property, etc... here?
51    # either in addition to in EntityDescriptor or instead of there.
52    def create_pk_cols(self):
53        pass
54
55    def create_non_pk_cols(self):
56        pass
57
58    def before_table(self):
59        pass
60
61    def create_tables(self):
62        pass
63
64    def after_table(self):
65        pass
66
67    def create_properties(self):
68        pass   
69
70    def before_mapper(self):
71        pass   
72
73    def after_mapper(self):
74        pass   
75
76    def finalize(self):
77        pass   
78
79
80class CounterMeta(type):
81    '''
82    A simple meta class which adds a ``_counter`` attribute to the instances of
83    the classes it is used on. This counter is simply incremented for each new
84    instance.
85    '''
86    counter = 0
87
88    def __call__(self, *args, **kwargs):
89        instance = type.__call__(self, *args, **kwargs)
90        instance._counter = CounterMeta.counter
91        CounterMeta.counter += 1
92        return instance
93
94
95class Property(EntityBuilder):
96    '''
97    Abstract base class for all properties of an Entity.
98    '''
99    __metaclass__ = CounterMeta
100   
101    def __init__(self, *args, **kwargs):
102        self.entity = None
103        self.name = None
104
105    def attach(self, entity, name):
106        """Attach this property to its entity, using 'name' as name.
107
108        Properties will be attached in the order they were declared.
109        """
110        self.entity = entity
111        self.name = name
112
113        # register this property as a builder
114        entity._descriptor.builders.append(self)
115
116        # delete the original attribute so that it doesn't interfere with
117        # SQLAlchemy.
118        if hasattr(entity, name):
119            delattr(entity, name)
120
121    def __repr__(self):
122        return "Property(%s, %s)" % (self.name, self.entity)
123
124
125class GenericProperty(Property):
126    '''
127    Generic catch-all class to wrap an SQLAlchemy property.
128
129    .. sourcecode:: python
130
131        class OrderLine(Entity):
132            quantity = Field(Float)
133            unit_price = Field(Numeric)
134            price = GenericProperty(lambda c: column_property(
135                             (c.quantity * c.unit_price).label('price')))
136    '''
137   
138    def __init__(self, prop):
139        super(GenericProperty, self).__init__()
140        self.prop = prop
141
142    def create_properties(self):
143        if callable(self.prop):
144            prop_value = self.prop(self.entity.table.c)
145        else:
146            prop_value = self.prop
147        prop_value = self.evaluate_property(prop_value)
148        self.entity._descriptor.add_property(self.name, prop_value)
149
150    def evaluate_property(self, prop):
151        return prop
152
153class ColumnProperty(GenericProperty):
154    '''
155    A specialized form of the GenericProperty to generate SQLAlchemy
156    ``column_property``'s.
157   
158    It takes a single argument, which is a function (often
159    given as an anonymous lambda) taking one argument and returning the
160    desired (scalar-returning) SQLAlchemy ClauseElement. That function will be
161    called whenever the entity table is completely defined, and will be given
162    the .c attribute of the entity as argument (as a way to access the entity
163    columns). The ColumnProperty will first wrap your ClauseElement in a label
164    with the same name as the property, then wrap that in a column_property.
165
166    .. sourcecode:: python
167
168        class OrderLine(Entity):
169            quantity = Field(Float)
170            unit_price = Field(Numeric)
171            price = ColumnProperty(lambda c: c.quantity * c.unit_price)
172
173    Please look at the `corresponding SQLAlchemy
174    documentation <http://www.sqlalchemy.org/docs/04/mappers.html
175    #advdatamapping_mapper_expressions>`_ for details.
176    '''
177
178    def evaluate_property(self, prop):
179        return column_property(prop.label(self.name))
180
181#class Composite(GenericProperty):
182#    def __init__(self, prop):
183#        super(GenericProperty, self).__init__()
184#        self.prop = prop
185
186#    def evaluate_property(self, prop):
187#        return composite(prop.label(self.name))
188
189#start = Composite(Point, lambda c: (c.x1, c.y1))
190
191#mapper(Vertex, vertices, properties={
192#    'start':composite(Point, vertices.c.x1, vertices.c.y1),
193#    'end':composite(Point, vertices.c.x2, vertices.c.y2)
194#})
195
196
197has_property = PropertyStatement(GenericProperty)
Note: See TracBrowser for help on using the browser.