root / elixir / tags / 0.5.0 / elixir / fields.py

Revision 267, 9.0 kB (checked in by ged, 5 years ago)
  • cleanup class attributes (in the attributes-based syntax) after the
    property is attached to its entity, so that SQLAlchemy is not confused.
    Only caused problem in the case of single inheritance and when omitting
    some values. See SA ticket #866.
  • some PEP8 fixes
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(String)
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.                            |
70+-------------------+---------------------------------------------------------+
71
72has_field
73---------
74
75The `has_field` statement allows you to define fields one at a time.
76
77The first argument is the name of the field, the second is its type. Following
78these, any number of keyword arguments can be specified for additional
79behavior. The following arguments are supported:
80
81+-------------------+---------------------------------------------------------+
82| Argument Name     | Description                                             |
83+===================+=========================================================+
84| ``through``       | Specify a relation name to go through. This field will  |
85|                   | not exist as a column on the database but will be a     |
86|                   | property which automatically proxy values to the        |
87|                   | ``attribute`` attribute of the object pointed to by the |
88|                   | relation. If the ``attribute`` argument is not present, |
89|                   | the name of the current field will be used. In an       |
90|                   | has_field statement, you can only proxy through a       |
91|                   | belongs_to or an has_one relationship.                  |
92+-------------------+---------------------------------------------------------+
93| ``attribute``     | Name of the "endpoint" attribute to proxy to. This      |
94|                   | should only be used in combination with the ``through`` |
95|                   | argument.                                               |
96+-------------------+---------------------------------------------------------+
97
98
99Here is a quick example of how to use ``has_field``.
100
101.. sourcecode:: python
102
103    class Person(Entity):
104        has_field('id', Integer, primary_key=True)
105        has_field('name', String(50))
106
107
108with_fields
109-----------
110The `with_fields` statement is **deprecated** in favor of the `attribute-based
111syntax`_.
112
113It allows you to define all fields of an entity at once.
114Each keyword argument to this statement represents one field, which should
115be a `Field` object. The first argument to a Field object is its type.
116Following it, any number of keyword arguments can be specified for
117additional behavior. The `with_fields` statement supports the same keyword
118arguments than the `has_field` statement.
119
120Here is a quick example of how to use ``with_fields``.
121
122.. sourcecode:: python
123
124    class Person(Entity):
125        with_fields(
126            id = Field(Integer, primary_key=True),
127            name = Field(String(50))
128        )
129'''
130import sys
131
132from sqlalchemy import Column
133from sqlalchemy.orm import deferred, synonym
134from sqlalchemy.ext.associationproxy import association_proxy
135
136from elixir.statements import ClassMutator
137from elixir.properties import Property
138
139__doc_all__ = ['Field']
140
141
142class Field(Property):
143    '''
144    Represents the definition of a 'field' on an entity.
145   
146    This class represents a column on the table where the entity is stored.
147    This object is only used with the `with_fields` syntax for defining all
148    fields for an entity at the same time. The `has_field` syntax does not
149    require the manual creation of this object.
150    '''
151   
152    def __init__(self, type, *args, **kwargs):
153        super(Field, self).__init__()
154       
155        self.colname = kwargs.pop('colname', None)
156        self.synonym = kwargs.pop('synonym', None)
157        self.deferred = kwargs.pop('deferred', False)
158        if 'required' in kwargs:
159            kwargs['nullable'] = not kwargs.pop('required')
160        self.type = type
161        self.primary_key = kwargs.get('primary_key', False)
162
163        self.column = None
164        self.property = None
165       
166        self.args = args
167        self.kwargs = kwargs
168
169    def attach(self, entity, name):
170        # If no colname was defined (through the 'colname' kwarg), set
171        # it to the name of the attr.
172        if self.colname is None:
173            self.colname = name
174        super(Field, self).attach(entity, name)
175
176    def create_pk_cols(self):
177        if self.primary_key:
178            self.create_col()
179
180    def create_non_pk_cols(self):
181        if not self.primary_key:
182            self.create_col()
183
184    def create_col(self):
185        self.column = Column(self.colname, self.type,
186                             *self.args, **self.kwargs)
187        self.entity._descriptor.add_column(self.column)
188
189    def create_properties(self):
190        if self.deferred:
191            group = None
192            if isinstance(self.deferred, basestring):
193                group = self.deferred
194            self.property = deferred(self.column, group=group)
195        elif self.name != self.colname:
196            self.property = self.column
197
198        if self.property:
199            self.entity._descriptor.add_property(self.name, self.property)
200
201        if self.synonym:
202            self.entity._descriptor.add_property(self.synonym, 
203                                                 synonym(self.name))
204
205
206def has_field_handler(entity, name, *args, **kwargs):
207    if 'through' in kwargs:
208        setattr(entity, name, 
209                association_proxy(kwargs.pop('through'), 
210                                  kwargs.pop('attribute', name),
211                                  **kwargs))
212        return
213    field = Field(*args, **kwargs)
214    field.attach(entity, name)
215
216
217def with_fields_handler(entity, *args, **fields):
218    for name, field in fields.iteritems():
219        field.attach(entity, name)
220
221
222has_field = ClassMutator(has_field_handler)
223with_fields = ClassMutator(with_fields_handler)
Note: See TracBrowser for help on using the browser.