root / elixir / trunk / elixir / options.py @ 513

Revision 513, 14.1 kB (checked in by ged, 4 years ago)

- do not leak options_defaults from a subclass into a parent class
- do not initialize useless attributes for base and abstract classes

Line 
1'''
2This module provides support for defining several options on your Elixir
3entities.  There are three different kinds of options that can be set
4up, and for this there are three different statements: using_options_,
5using_table_options_ and using_mapper_options_.
6
7Alternatively, these options can be set on all Elixir entities by modifying
8the `options_defaults` dictionary before defining any entity.
9
10`using_options`
11---------------
12The 'using_options' DSL statement allows you to set up some additional
13behaviors on your model objects, including table names, ordering, and
14more.  To specify an option, simply supply the option as a keyword
15argument onto the statement, as follows:
16
17.. sourcecode:: python
18
19    class Person(Entity):
20        name = Field(Unicode(64))
21
22        using_options(shortnames=True, order_by='name')
23
24The list of supported arguments are as follows:
25
26+---------------------+-------------------------------------------------------+
27| Option Name         | Description                                           |
28+=====================+=======================================================+
29| ``inheritance``     | Specify the type of inheritance this entity must use. |
30|                     | It can be one of ``single``, ``concrete`` or          |
31|                     | ``multi``. Defaults to ``single``.                    |
32|                     | Note that polymorphic concrete inheritance is         |
33|                     | currently not implemented. See:                       |
34|                     | http://www.sqlalchemy.org/docs/05/mappers.html        |
35|                     | #mapping-class-inheritance-hierarchies for an         |
36|                     | explanation of the different kinds of inheritances.   |
37+---------------------+-------------------------------------------------------+
38| ``abstract``        | Set 'abstract'=True to declare abstract entity.       |
39|                     | Abstract base classes are useful when you want to put |
40|                     | some common information into a number of other        |
41|                     | entities. Abstract entity will not be used to create  |
42|                     | any database table. Instead, when it is used as a base|
43|                     | class for other entity, its fields will be added to   |
44|                     | those of the child class.                             |
45+---------------------+-------------------------------------------------------+
46| ``polymorphic``     | Whether the inheritance should be polymorphic or not. |
47|                     | Defaults to ``True``. The column used to store the    |
48|                     | type of each row is named "row_type" by default. You  |
49|                     | can change this by passing the desired name for the   |
50|                     | column to this argument.                              |
51+---------------------+-------------------------------------------------------+
52| ``identity``        | Specify a custom polymorphic identity. When using     |
53|                     | polymorphic inheritance, this value (usually a        |
54|                     | string) will represent this particular entity (class) |
55|                     | . It will be used to differentiate it from other      |
56|                     | entities (classes) in your inheritance hierarchy when |
57|                     | loading from the database instances of different      |
58|                     | entities in that hierarchy at the same time.          |
59|                     | This value will be stored by default in the           |
60|                     | "row_type" column of the entity's table (see above).  |
61|                     | You can either provide a                              |
62|                     | plain string or a callable. The callable will be      |
63|                     | given the entity (ie class) as argument and must      |
64|                     | return a value (usually a string) representing the    |
65|                     | polymorphic identity of that entity.                  |
66|                     | By default, this value is automatically generated: it |
67|                     | is the name of the entity lower-cased.                |
68+---------------------+-------------------------------------------------------+
69| ``metadata``        | Specify a custom MetaData for this entity.            |
70|                     | By default, entities uses the global                  |
71|                     | ``elixir.metadata``.                                  |
72|                     | This option can also be set for all entities of a     |
73|                     | module by setting the ``__metadata__`` attribute of   |
74|                     | that module.                                          |
75+---------------------+-------------------------------------------------------+
76| ``autoload``        | Automatically load column definitions from the        |
77|                     | existing database table.                              |
78+---------------------+-------------------------------------------------------+
79| ``tablename``       | Specify a custom tablename. You can either provide a  |
80|                     | plain string or a callable. The callable will be      |
81|                     | given the entity (ie class) as argument and must      |
82|                     | return a string representing the name of the table    |
83|                     | for that entity. By default, the tablename is         |
84|                     | automatically generated: it is a concatenation of the |
85|                     | full module-path to the entity and the entity (class) |
86|                     | name itself. The result is lower-cased and separated  |
87|                     | by underscores ("_"), eg.: for an entity named        |
88|                     | "MyEntity" in the module "project1.model", the        |
89|                     | generated table name will be                          |
90|                     | "project1_model_myentity".                            |
91+---------------------+-------------------------------------------------------+
92| ``shortnames``      | Specify whether or not the automatically generated    |
93|                     | table names include the full module-path              |
94|                     | to the entity. If ``shortnames`` is ``True``, only    |
95|                     | the entity name is used. Defaults to ``False``.       |
96+---------------------+-------------------------------------------------------+
97| ``auto_primarykey`` | If given as string, it will represent the             |
98|                     | auto-primary-key's column name.  If this option       |
99|                     | is True, it will allow auto-creation of a primary     |
100|                     | key if there's no primary key defined for the         |
101|                     | corresponding entity.  If this option is False,       |
102|                     | it will disallow auto-creation of a primary key.      |
103|                     | Defaults to ``True``.                                 |
104+---------------------+-------------------------------------------------------+
105| ``version_id_col``  | If this option is True, it will create a version      |
106|                     | column automatically using the default name. If given |
107|                     | as string, it will create the column using that name. |
108|                     | This can be used to prevent concurrent modifications  |
109|                     | to the entity's table rows (i.e. it will raise an     |
110|                     | exception if it happens). Defaults to ``False``.      |
111+---------------------+-------------------------------------------------------+
112| ``order_by``        | How to order select results. Either a string or a     |
113|                     | list of strings, composed of the field name,          |
114|                     | optionally lead by a minus (for descending order).    |
115+---------------------+-------------------------------------------------------+
116| ``session``         | Specify a custom contextual session for this entity.  |
117|                     | By default, entities uses the global                  |
118|                     | ``elixir.session``.                                   |
119|                     | This option takes a ``ScopedSession`` object or       |
120|                     | ``None``. In the later case your entity will be       |
121|                     | mapped using a non-contextual mapper which requires   |
122|                     | manual session management, as seen in pure SQLAlchemy.|
123|                     | This option can also be set for all entities of a     |
124|                     | module by setting the ``__session__`` attribute of    |
125|                     | that module.                                          |
126+---------------------+-------------------------------------------------------+
127| ``allowcoloverride``| Specify whether it is allowed to override columns.    |
128|                     | By default, Elixir forbids you to add a column to an  |
129|                     | entity's table which already exist in that table. If  |
130|                     | you set this option to ``True`` it will skip that     |
131|                     | check. Use with care as it is easy to shoot oneself   |
132|                     | in the foot when overriding columns.                  |
133+---------------------+-------------------------------------------------------+
134
135For examples, please refer to the examples and unit tests.
136
137`using_table_options`
138---------------------
139The 'using_table_options' DSL statement allows you to set up some
140additional options on your entity table. It is meant only to handle the
141options which are not supported directly by the 'using_options' statement.
142By opposition to the 'using_options' statement, these options are passed
143directly to the underlying SQLAlchemy Table object (both non-keyword arguments
144and keyword arguments) without any processing.
145
146For further information, please refer to the `SQLAlchemy table's documentation
147<http://www.sqlalchemy.org/docs/05/reference/sqlalchemy/schema.html
148#sqlalchemy.schema.Table>`_.
149
150You might also be interested in the section about `constraints
151<http://www.sqlalchemy.org/docs/05/metadata.html
152#defining-constraints-and-indexes>`_.
153
154`using_mapper_options`
155----------------------
156The 'using_mapper_options' DSL statement allows you to set up some
157additional options on your entity mapper. It is meant only to handle the
158options which are not supported directly by the 'using_options' statement.
159By opposition to the 'using_options' statement, these options are passed
160directly to the underlying SQLAlchemy mapper (as keyword arguments)
161without any processing.
162
163For further information, please refer to the `SQLAlchemy mapper
164function's documentation
165<http://www.sqlalchemy.org/docs/05/reference/orm/mapping.html
166#sqlalchemy.orm.mapper>`_.
167
168`using_options_defaults`
169------------------------
170The 'using_options_defaults' DSL statement allows you to set up some
171default options on a custom base class. These will be used as the default value
172for options of all its subclasses. Note that any option not set within the
173using_options_defaults (nor specifically on a particular Entity) will use the
174global defaults, so you don't have to provide a default value for all options,
175but only those you want to change. Please also note that this statement does
176not work on normal entities, and the normal using_options statement does not
177work on base classes (because normal options do not and should not propagate to
178the children classes).
179'''
180
181from sqlalchemy import Integer, String
182
183from elixir.statements import ClassMutator
184
185__doc_all__ = ['options_defaults']
186
187OLD_M2MCOL_NAMEFORMAT = "%(tablename)s_%(key)s%(numifself)s"
188ALTERNATE_M2MCOL_NAMEFORMAT = "%(inversename)s_%(key)s"
189
190def default_m2m_column_formatter(data):
191    if data['selfref']:
192        return ALTERNATE_M2MCOL_NAMEFORMAT % data
193    else:
194        return OLD_M2MCOL_NAMEFORMAT % data
195
196NEW_M2MCOL_NAMEFORMAT = default_m2m_column_formatter
197
198# format constants
199FKCOL_NAMEFORMAT = "%(relname)s_%(key)s"
200M2MCOL_NAMEFORMAT = NEW_M2MCOL_NAMEFORMAT
201CONSTRAINT_NAMEFORMAT = "%(tablename)s_%(colnames)s_fk"
202MULTIINHERITANCECOL_NAMEFORMAT = "%(entity)s_%(key)s"
203
204# other global constants
205DEFAULT_AUTO_PRIMARYKEY_NAME = "id"
206DEFAULT_AUTO_PRIMARYKEY_TYPE = Integer
207DEFAULT_VERSION_ID_COL_NAME = "row_version"
208DEFAULT_POLYMORPHIC_COL_NAME = "row_type"
209POLYMORPHIC_COL_SIZE = 40
210POLYMORPHIC_COL_TYPE = String(POLYMORPHIC_COL_SIZE)
211
212# debugging/migration help
213MIGRATION_TO_07_AID = False
214
215#
216options_defaults = dict(
217    abstract=False,
218    inheritance='single',
219    polymorphic=True,
220    identity=None,
221    autoload=False,
222    tablename=None,
223    shortnames=False,
224    auto_primarykey=True,
225    version_id_col=False,
226    allowcoloverride=False,
227    order_by=None,
228    mapper_options={},
229    table_options={}
230)
231
232valid_options = options_defaults.keys() + [
233    'metadata',
234    'session',
235    'collection'
236]
237
238
239def using_options_defaults_handler(entity, **kwargs):
240    for kwarg in kwargs:
241        if kwarg not in valid_options:
242            raise Exception("'%s' is not a valid option for Elixir entities."
243                            % kwarg)
244
245    # We use __dict__ instead of hasattr to not check its presence within the
246    # parent, and thus update the parent dict instead of creating a local dict.
247    if not entity.__dict__.get('options_defaults'):
248        entity.options_defaults = {}
249    entity.options_defaults.update(kwargs)
250
251
252def using_options_handler(entity, *args, **kwargs):
253    for kwarg in kwargs:
254        if kwarg in valid_options:
255            setattr(entity._descriptor, kwarg, kwargs[kwarg])
256        else:
257            raise Exception("'%s' is not a valid option for Elixir entities."
258                            % kwarg)
259
260
261def using_table_options_handler(entity, *args, **kwargs):
262    entity._descriptor.table_args.extend(list(args))
263    entity._descriptor.table_options.update(kwargs)
264
265
266def using_mapper_options_handler(entity, *args, **kwargs):
267    entity._descriptor.mapper_options.update(kwargs)
268
269
270using_options_defaults = ClassMutator(using_options_defaults_handler)
271using_options = ClassMutator(using_options_handler)
272using_table_options = ClassMutator(using_table_options_handler)
273using_mapper_options = ClassMutator(using_mapper_options_handler)
Note: See TracBrowser for help on using the browser.