root / elixir / tags / 0.7.0 / elixir / fields.py

Revision 484, 8.2 kB (checked in by ged, 2 years ago)

update all web links

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