| 1 | ''' |
|---|
| 2 | This module provides support for defining the fields (columns) of your |
|---|
| 3 | entities. Elixir currently supports two syntaxes to do so: the default |
|---|
| 4 | `Attribute-based syntax`_ as well as the has_field_ DSL statement. |
|---|
| 5 | |
|---|
| 6 | Note that the old with_fields_ statement is currently deprecated in favor of |
|---|
| 7 | the `Attribute-based syntax`_. |
|---|
| 8 | |
|---|
| 9 | Attribute-based syntax |
|---|
| 10 | ---------------------- |
|---|
| 11 | |
|---|
| 12 | Here 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 | |
|---|
| 30 | The Field class takes one mandatory argument, which is its type. Please refer |
|---|
| 31 | to SQLAlchemy documentation for a list of `types supported by SQLAlchemy |
|---|
| 32 | <http://www.sqlalchemy.org/docs/04/types.html>`_. |
|---|
| 33 | |
|---|
| 34 | Following that first mandatory argument, fields can take any number of |
|---|
| 35 | optional keyword arguments. Please note that all the **arguments** that are |
|---|
| 36 | **not specifically processed by Elixir**, as mentioned in the documentation |
|---|
| 37 | below **are passed on to the SQLAlchemy ``Column`` object**. Please refer to |
|---|
| 38 | the `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 |
|---|
| 41 | supported keyword arguments. |
|---|
| 42 | |
|---|
| 43 | The 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 | |
|---|
| 72 | has_field |
|---|
| 73 | --------- |
|---|
| 74 | |
|---|
| 75 | The `has_field` statement allows you to define fields one at a time. |
|---|
| 76 | |
|---|
| 77 | The first argument is the name of the field, the second is its type. Following |
|---|
| 78 | these, any number of keyword arguments can be specified for additional |
|---|
| 79 | behavior. 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 | |
|---|
| 99 | Here 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 | |
|---|
| 108 | with_fields |
|---|
| 109 | ----------- |
|---|
| 110 | The `with_fields` statement is **deprecated** in favor of the `attribute-based |
|---|
| 111 | syntax`_. |
|---|
| 112 | |
|---|
| 113 | It allows you to define all fields of an entity at once. |
|---|
| 114 | Each keyword argument to this statement represents one field, which should |
|---|
| 115 | be a `Field` object. The first argument to a Field object is its type. |
|---|
| 116 | Following it, any number of keyword arguments can be specified for |
|---|
| 117 | additional behavior. The `with_fields` statement supports the same keyword |
|---|
| 118 | arguments than the `has_field` statement. |
|---|
| 119 | |
|---|
| 120 | Here 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 | ''' |
|---|
| 130 | import sys |
|---|
| 131 | |
|---|
| 132 | from sqlalchemy import Column |
|---|
| 133 | from sqlalchemy.orm import deferred, synonym |
|---|
| 134 | from sqlalchemy.ext.associationproxy import association_proxy |
|---|
| 135 | |
|---|
| 136 | from elixir.statements import ClassMutator |
|---|
| 137 | from elixir.properties import Property |
|---|
| 138 | |
|---|
| 139 | __doc_all__ = ['Field'] |
|---|
| 140 | |
|---|
| 141 | |
|---|
| 142 | class 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 | |
|---|
| 206 | def 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 | |
|---|
| 217 | def with_fields_handler(entity, *args, **fields): |
|---|
| 218 | for name, field in fields.iteritems(): |
|---|
| 219 | field.attach(entity, name) |
|---|
| 220 | |
|---|
| 221 | |
|---|
| 222 | has_field = ClassMutator(has_field_handler) |
|---|
| 223 | with_fields = ClassMutator(with_fields_handler) |
|---|