root / elixir / trunk / elixir / fields.py @ 347

Revision 347, 9.4 kB (checked in by ged, 6 years ago)

removed trailing spaces

Line 
1'''
2This module provides support for defining the fields (columns) of your
3entities. Elixir currently supports two syntaxes to do so: the default
4`Attribute-based syntax`_ as well as the has_field_ DSL statement.
5
6Note that the old with_fields_ statement is currently deprecated in favor of
7the `Attribute-based syntax`_.
8
9Attribute-based syntax
10----------------------
11
12Here is a quick example of how to use the object-oriented syntax.
13
14.. sourcecode:: python
15
16    class Person(Entity):
17        id = Field(Integer, primary_key=True)
18        name = Field(String(50), required=True)
19        biography = Field(Text)
20        photo = Field(Binary, deferred=True)
21        _email = Field(String(20), colname='email', synonym='email')
22
23        def _set_email(self, email):
24           self._email = email
25        def _get_email(self):
26           return self._email
27        email = property(_get_email, _set_email)
28
29
30The Field class takes one mandatory argument, which is its type. Please refer
31to SQLAlchemy documentation for a list of `types supported by SQLAlchemy
32<http://www.sqlalchemy.org/docs/04/types.html>`_.
33
34Following that first mandatory argument, fields can take any number of
35optional keyword arguments. Please note that all the **arguments** that are
36**not specifically processed by Elixir**, as mentioned in the documentation
37below **are passed on to the SQLAlchemy ``Column`` object**. Please refer to
38the `SQLAlchemy Column object's documentation
39<http://www.sqlalchemy.org/docs/04/sqlalchemy_schema.html
40#docstrings_sqlalchemy.schema_Column>`_ for more details about other
41supported keyword arguments.
42
43The following Elixir-specific arguments are supported:
44
45+-------------------+---------------------------------------------------------+
46| Argument Name     | Description                                             |
47+===================+=========================================================+
48| ``required``      | Specify whether or not this field can be set to None    |
49|                   | (left without a value). Defaults to ``False``, unless   |
50|                   | the field is a primary key.                             |
51+-------------------+---------------------------------------------------------+
52| ``colname``       | Specify a custom name for the column of this field. By  |
53|                   | default the column will have the same name as the       |
54|                   | attribute.                                              |
55+-------------------+---------------------------------------------------------+
56| ``deferred``      | Specify whether this particular column should be        |
57|                   | fetched by default (along with the other columns) when  |
58|                   | an instance of the entity is fetched from the database  |
59|                   | or rather only later on when this particular column is  |
60|                   | first referenced. This can be useful when one wants to  |
61|                   | avoid loading a large text or binary field into memory  |
62|                   | when its not needed. Individual columns can be lazy     |
63|                   | loaded by themselves (by using ``deferred=True``)       |
64|                   | or placed into groups that lazy-load together (by using |
65|                   | ``deferred`` = `"group_name"`).                         |
66+-------------------+---------------------------------------------------------+
67| ``synonym``       | Specify a synonym name for this field. The field will   |
68|                   | also be usable under that name in keyword-based Query   |
69|                   | functions such as filter_by. The Synonym class (see the |
70|                   | `properties` module) provides a similar functionality   |
71|                   | with an (arguably) nicer syntax, but a limited scope.   |
72+-------------------+---------------------------------------------------------+
73
74has_field
75---------
76
77The `has_field` statement allows you to define fields one at a time.
78
79The first argument is the name of the field, the second is its type. Following
80these, any number of keyword arguments can be specified for additional
81behavior. The following arguments are supported:
82
83+-------------------+---------------------------------------------------------+
84| Argument Name     | Description                                             |
85+===================+=========================================================+
86| ``through``       | Specify a relation name to go through. This field will  |
87|                   | not exist as a column on the database but will be a     |
88|                   | property which automatically proxy values to the        |
89|                   | ``attribute`` attribute of the object pointed to by the |
90|                   | relation. If the ``attribute`` argument is not present, |
91|                   | the name of the current field will be used. In an       |
92|                   | has_field statement, you can only proxy through a       |
93|                   | belongs_to or an has_one relationship.                  |
94+-------------------+---------------------------------------------------------+
95| ``attribute``     | Name of the "endpoint" attribute to proxy to. This      |
96|                   | should only be used in combination with the ``through`` |
97|                   | argument.                                               |
98+-------------------+---------------------------------------------------------+
99
100
101Here is a quick example of how to use ``has_field``.
102
103.. sourcecode:: python
104
105    class Person(Entity):
106        has_field('id', Integer, primary_key=True)
107        has_field('name', String(50))
108
109
110with_fields
111-----------
112The `with_fields` statement is **deprecated** in favor of the `attribute-based
113syntax`_.
114
115It allows you to define all fields of an entity at once.
116Each keyword argument to this statement represents one field, which should
117be a `Field` object. The first argument to a Field object is its type.
118Following it, any number of keyword arguments can be specified for
119additional behavior. The `with_fields` statement supports the same keyword
120arguments than the `has_field` statement.
121
122Here is a quick example of how to use ``with_fields``.
123
124.. sourcecode:: python
125
126    class Person(Entity):
127        with_fields(
128            id = Field(Integer, primary_key=True),
129            name = Field(String(50))
130        )
131'''
132import sys
133
134from sqlalchemy import Column
135from sqlalchemy.orm import deferred, synonym
136from sqlalchemy.ext.associationproxy import association_proxy
137
138from elixir.statements import ClassMutator
139from elixir.properties import Property
140
141__doc_all__ = ['Field']
142
143
144class Field(Property):
145    '''
146    Represents the definition of a 'field' on an entity.
147
148    This class represents a column on the table where the entity is stored.
149    This object is only used with the `with_fields` syntax for defining all
150    fields for an entity at the same time. The `has_field` syntax does not
151    require the manual creation of this object.
152    '''
153
154    def __init__(self, type, *args, **kwargs):
155        super(Field, self).__init__()
156
157        self.colname = kwargs.pop('colname', None)
158        self.synonym = kwargs.pop('synonym', None)
159        self.deferred = kwargs.pop('deferred', False)
160        if 'required' in kwargs:
161            kwargs['nullable'] = not kwargs.pop('required')
162        self.type = type
163        self.primary_key = kwargs.get('primary_key', False)
164
165        self.column = None
166        self.property = None
167
168        self.args = args
169        self.kwargs = kwargs
170
171    def attach(self, entity, name):
172        # If no colname was defined (through the 'colname' kwarg), set
173        # it to the name of the attr.
174        if self.colname is None:
175            self.colname = name
176        super(Field, self).attach(entity, name)
177
178    def create_pk_cols(self):
179        if self.primary_key:
180            self.create_col()
181
182    def create_non_pk_cols(self):
183        if not self.primary_key:
184            self.create_col()
185
186    def create_col(self):
187        self.column = Column(self.colname, self.type,
188                             *self.args, **self.kwargs)
189        self.entity._descriptor.add_column(self.column)
190
191    def create_properties(self):
192        if self.deferred:
193            group = None
194            if isinstance(self.deferred, basestring):
195                group = self.deferred
196            self.property = deferred(self.column, group=group)
197        elif self.name != self.colname:
198            # if the property name is different from the column name, we need to
199            # add an explicit property (otherwise nothing is needed as it's done
200            # automatically by SA)
201            self.property = self.column
202
203        if self.property:
204            self.entity._descriptor.add_property(self.name, self.property)
205
206        if self.synonym:
207            self.entity._descriptor.add_property(self.synonym,
208                                                 synonym(self.name))
209
210
211def has_field_handler(entity, name, *args, **kwargs):
212    if 'through' in kwargs:
213        setattr(entity, name,
214                association_proxy(kwargs.pop('through'),
215                                  kwargs.pop('attribute', name),
216                                  **kwargs))
217        return
218    field = Field(*args, **kwargs)
219    field.attach(entity, name)
220
221
222def with_fields_handler(entity, *args, **fields):
223    for name, field in fields.iteritems():
224        field.attach(entity, name)
225
226
227has_field = ClassMutator(has_field_handler)
228with_fields = ClassMutator(with_fields_handler)
Note: See TracBrowser for help on using the browser.