root / elixir / tags / 0.6.0 / elixir / fields.py

Revision 370, 9.3 kB (checked in by ged, 4 years ago)

added an example of a field with a default value, as per a suggestion on IRC

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