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

Revision 236, 5.6 kB (checked in by ged, 7 years ago)

- migrate to attribute-based syntax in all examples and documentation.
- completed doc

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 allows you to wrap any SQLAlchemy property, and its DSL-syntax equivalent:
7has_property_.
8
9`has_property`
10--------------
11The ``has_property`` statement allows you to define properties which rely on
12their entity's table (and columns) being defined before they can be declared
13themselves. The `has_property` statement takes two arguments: first the name of
14the property to be defined and second a function (often given as an anonymous
15lambda) taking one argument and returning the desired SQLAlchemy property. That
16function will be called whenever the entity table is completely defined, and
17will be given the .c attribute of the entity as argument (as a way to access
18the entity columns).
19
20Here is a quick example of how to use ``has_property``.
21
22.. sourcecode:: python
23
24    class OrderLine(Entity):
25        has_field('quantity', Float)
26        has_field('unit_price', Float)
27        has_property('price',
28                     lambda c: column_property(
29                         (c.quantity * c.unit_price).label('price')))
30'''
31
32from elixir.statements import PropertyStatement
33from sqlalchemy.orm import column_property
34
35__doc_all__ = ['EntityBuilder', 'Property', 'GenericProperty', 
36               'ColumnProperty']
37
38class EntityBuilder(object):
39    '''
40    Abstract base class for all entity builders. An Entity builder is a class
41    of objects which can be added to an Entity (usually by using special
42    properties or statements) to "build" that entity. Building an entity,
43    meaning to add columns to its "main" table, create other tables, add
44    properties to its mapper, ... To do so an EntityBuilder must override the
45    corresponding method(s). This is to ensure the different operations happen
46    in the correct order (for example, that the table is fully created before
47    the mapper that use it is defined).
48    '''
49   
50    #TODO: add stub methods (create_cols, etc...)
51    #XXX: add helper methods: add_property, etc... here?
52    # either in addition to in EntityDescriptor or instead of there.
53    pass
54
55
56class CounterMeta(type):
57    '''
58    A simple meta class which adds a ``_counter`` attribute to the instances of
59    the classes it is used on. This counter is simply incremented for each new
60    instance.
61    '''
62    counter = 0
63
64    def __call__(self, *args, **kwargs):
65        instance = type.__call__(self, *args, **kwargs)
66        instance._counter = CounterMeta.counter
67        CounterMeta.counter += 1
68        return instance
69
70
71class Property(EntityBuilder):
72    '''
73    Abstract base class for all properties of an Entity.
74    '''
75    __metaclass__ = CounterMeta
76   
77    def __init__(self, *args, **kwargs):
78        self.entity = None
79        self.name = None
80
81    def attach(self, entity, name):
82        """Attach this property to its entity, using 'name' as name.
83
84        Properties will be attached in the order they were declared.
85        """
86        self.entity = entity
87        self.name = name
88
89        # register this property as a builder
90        entity._descriptor.builders.append(self)
91
92    def __repr__(self):
93        return "Property(%s, %s)" % (self.name, self.entity)
94
95
96class GenericProperty(Property):
97    '''
98    Generic catch-all class to wrap an SQLAlchemy property.
99    '''
100   
101    def __init__(self, prop):
102        super(GenericProperty, self).__init__()
103        self.prop = prop
104
105    def create_properties(self):
106        if callable(self.prop):
107            prop_value = self.prop(self.entity.table.c)
108        else:
109            prop_value = self.prop
110        prop_value = self.evaluate_property(prop_value)
111        self.entity.mapper.add_property(self.name, prop_value)
112
113    def evaluate_property(self, prop):
114        return prop
115
116class ColumnProperty(GenericProperty):
117    '''
118    A specialized form of the GenericProperty to generate SQLAlchemy
119    ``column_property``'s.
120   
121    It takes a single argument, which is a function (often
122    given as an anonymous lambda) taking one argument and returning the
123    desired (scalar-returning) SQLAlchemy ClauseElement. That function will be
124    called whenever the entity table is completely defined, and will be given
125    the .c attribute of the entity as argument (as a way to access the entity
126    columns). The ColumnProperty will first wrap your ClauseElement in a label
127    with the same name as the property, then wrap that in a column_property.
128
129    .. sourcecode:: python
130
131        class OrderLine(Entity):
132            quantity = Field(Float)
133            unit_price = Field(Numeric)
134            price = ColumnProperty(lambda c: c.quantity * c.unit_price)
135
136    Please look at the `corresponding SQLAlchemy
137    documentation <http://www.sqlalchemy.org/docs/04/mappers.html
138    #advdatamapping_mapper_expressions>`_ for details.
139    '''
140
141    def evaluate_property(self, prop):
142        return column_property(prop.label(self.name))
143
144#class Composite(GenericProperty):
145#    def __init__(self, prop):
146#        super(GenericProperty, self).__init__()
147#        self.prop = prop
148
149#    def evaluate_property(self, prop):
150#        return composite(prop.label(self.name))
151
152#start = Composite(Point, lambda c: (c.x1, c.y1))
153
154#mapper(Vertex, vertices, properties={
155#    'start':composite(Point, vertices.c.x1, vertices.c.y1),
156#    'end':composite(Point, vertices.c.x2, vertices.c.y2)
157#})
158
159
160has_property = PropertyStatement(GenericProperty)
Note: See TracBrowser for help on using the browser.