root / elixir / trunk / elixir / entity.py @ 307

Revision 307, 36.3 kB (checked in by ged, 6 years ago)

- Made Elixir python 2.3 compatible again (based on a patch from

Jason R. Coombs)

Line 
1'''
2This module provides the ``Entity`` base class, as well as its metaclass
3``EntityMeta``.
4'''
5
6from py23compat import set, rsplit
7
8import sys
9import warnings
10
11import sqlalchemy
12from sqlalchemy                    import Table, Column, Integer, \
13                                          desc, ForeignKey, and_
14from sqlalchemy.orm                import Query, MapperExtension,\
15                                          mapper, object_session, EXT_PASS
16from sqlalchemy.ext.sessioncontext import SessionContext
17
18import elixir
19from elixir.statements import process_mutators
20from elixir import options
21from elixir.properties import Property
22
23
24__doc_all__ = ['Entity', 'EntityMeta']
25
26
27try: 
28    from sqlalchemy.orm import ScopedSession
29except ImportError: 
30    # Not on sqlalchemy version 0.4
31    ScopedSession = type(None)
32
33
34def _do_mapping(session, cls, *args, **kwargs):
35    if session is None:
36        return mapper(cls, *args, **kwargs)
37    elif isinstance(session, ScopedSession):
38        return session.mapper(cls, *args, **kwargs)
39    elif isinstance(session, SessionContext):
40        extension = kwargs.pop('extension', None)
41        if extension is not None:
42            if not isinstance(extension, list):
43                extension = [extension]
44            extension.append(session.mapper_extension)
45        else:
46            extension = session.mapper_extension
47
48        class query(object):
49            def __getattr__(s, key):
50                return getattr(session.registry().query(cls), key)
51
52            def __call__(s):
53                return session.registry().query(cls)
54
55        if not 'query' in cls.__dict__: 
56            cls.query = query()
57
58        return mapper(cls, extension=extension, *args, **kwargs)
59    else:
60        raise Exception("Failed to map entity '%s' with its table or "
61                        "selectable" % cls.__name__)
62
63
64class EntityDescriptor(object):
65    '''
66    EntityDescriptor describes fields and options needed for table creation.
67    '''
68   
69    def __init__(self, entity):
70        entity.table = None
71        entity.mapper = None
72
73        self.entity = entity
74        self.module = sys.modules[entity.__module__]
75
76        self.has_pk = False
77        self._pk_col_done = False
78
79        self.builders = []
80
81        self.is_base = is_base(entity)
82        self.parent = None
83        self.children = []
84
85        for base in entity.__bases__:
86            if isinstance(base, EntityMeta) and not is_base(base):
87                if self.parent:
88                    raise Exception('%s entity inherits from several entities,'
89                                    ' and this is not supported.' 
90                                    % self.entity.__name__)
91                else:
92                    self.parent = base
93                    self.parent._descriptor.children.append(entity)
94
95        # columns and constraints waiting for a table to exist
96        self._columns = list()
97        self.constraints = list()
98        # properties waiting for a mapper to exist
99        self.properties = dict()
100
101        #
102        self.relationships = list()
103
104        # set default value for options
105        self.order_by = None
106        self.table_args = list()
107
108        # set default value for options with an optional module-level default
109        self.metadata = getattr(self.module, '__metadata__', elixir.metadata)
110        self.session = getattr(self.module, '__session__', elixir.session)
111        self.objectstore = None
112        self.collection = getattr(self.module, '__entity_collection__',
113                                  elixir.entities)
114
115        for option in ('autosetup', 'inheritance', 'polymorphic',
116                       'autoload', 'tablename', 'shortnames', 
117                       'auto_primarykey', 'version_id_col', 
118                       'allowcoloverride'):
119            setattr(self, option, options.options_defaults[option])
120
121        for option_dict in ('mapper_options', 'table_options'):
122            setattr(self, option_dict, 
123                    options.options_defaults[option_dict].copy())
124
125    def setup_options(self):
126        '''
127        Setup any values that might depend on using_options. For example, the
128        tablename or the metadata.
129        '''
130        elixir.metadatas.add(self.metadata)
131        if self.collection is not None:
132            self.collection.append(self.entity)
133
134        objectstore = None
135        session = self.session
136        if session is None or isinstance(session, ScopedSession):
137            # no stinking objectstore
138            pass
139        elif isinstance(session, SessionContext):
140            objectstore = elixir.Objectstore(session)
141        elif not hasattr(session, 'registry'):
142            # Both SessionContext and ScopedSession have a registry attribute,
143            # but objectstores (whether Elixir's or Activemapper's) don't, so
144            # if we are here, it means an Objectstore is used for the session.
145#XXX: still true for activemapper post 0.4?           
146            objectstore = session
147            session = objectstore.context
148
149        self.session = session
150        self.objectstore = objectstore
151
152        entity = self.entity
153        if self.inheritance == 'concrete' and self.polymorphic:
154            raise NotImplementedError("Polymorphic concrete inheritance is "
155                                      "not yet implemented.")
156
157        if self.parent:
158            if self.inheritance == 'single':
159                self.tablename = self.parent._descriptor.tablename
160
161        if not self.tablename:
162            if self.shortnames:
163                self.tablename = entity.__name__.lower()
164            else:
165                modulename = entity.__module__.replace('.', '_')
166                tablename = "%s_%s" % (modulename, entity.__name__)
167                self.tablename = tablename.lower()
168        elif callable(self.tablename):
169            self.tablename = self.tablename(entity)
170
171    #---------------------
172    # setup phase methods
173
174    def setup_autoload_table(self):
175        self.setup_table(True)
176
177    def create_pk_cols(self):
178        """
179        Create primary_key columns. That is, call the 'create_pk_cols'
180        builders then add a primary key to the table if it hasn't already got
181        one and needs one.
182       
183        This method is "semi-recursive" in some cases: it calls the
184        create_keys method on ManyToOne relationships and those in turn call
185        create_pk_cols on their target. It shouldn't be possible to have an
186        infinite loop since a loop of primary_keys is not a valid situation.
187        """
188        if self._pk_col_done:
189            return
190
191        self.call_builders('create_pk_cols')
192
193        if not self.autoload:
194            if self.parent:
195                if self.inheritance == 'multi':
196                    # add columns with foreign keys to the parent's primary
197                    # key columns
198                    parent_desc = self.parent._descriptor
199                    for pk_col in parent_desc.primary_keys:
200                        colname = options.MULTIINHERITANCECOL_NAMEFORMAT % \
201                                  {'entity': self.parent.__name__.lower(),
202                                   'key': pk_col.key}
203
204                        # it seems like SA ForeignKey is not happy being given
205                        # a real column object when said column is not yet
206                        # attached to a table
207                        pk_col_name = "%s.%s" % (parent_desc.tablename, 
208                                                 pk_col.key)
209                        fk = ForeignKey(pk_col_name, ondelete='cascade')
210                        col = Column(colname, pk_col.type, fk,
211                                     primary_key=True)
212                        self.add_column(col)
213            elif not self.has_pk and self.auto_primarykey:
214                if isinstance(self.auto_primarykey, basestring):
215                    colname = self.auto_primarykey
216                else:
217                    colname = options.DEFAULT_AUTO_PRIMARYKEY_NAME
218               
219                self.add_column(
220                    Column(colname, options.DEFAULT_AUTO_PRIMARYKEY_TYPE, 
221                           primary_key=True))
222        self._pk_col_done = True
223
224    def setup_relkeys(self):
225        self.call_builders('create_non_pk_cols')
226
227    def before_table(self):
228        self.call_builders('before_table')
229       
230    def setup_table(self, only_autoloaded=False):
231        '''
232        Create a SQLAlchemy table-object with all columns that have been
233        defined up to this point.
234        '''
235        if self.entity.table:
236            return
237
238        if self.autoload != only_autoloaded:
239            return
240       
241        if self.parent:
242            if self.inheritance == 'single':
243                # we know the parent is setup before the child
244                self.entity.table = self.parent.table 
245
246                # re-add the entity columns to the parent entity so that they
247                # are added to the parent's table (whether the parent's table
248                # is already setup or not).
249                for col in self.columns:
250                    self.parent._descriptor.add_column(col)
251                for constraint in self.constraints:
252                    self.parent._descriptor.add_constraint(constraint)
253                return
254            elif self.inheritance == 'concrete': 
255                #TODO: we should also copy columns from the parent table if the
256                # parent is a base entity (whatever the inheritance type -> elif
257                # will need to be changed)
258                # copy all columns from parent table
259                for col in self.parent._descriptor.columns:
260                    self.add_column(col.copy())
261                #FIXME: copy constraints. But those are not as simple to copy
262                #since the source column must be changed
263
264        if self.polymorphic and self.inheritance in ('single', 'multi') and \
265           self.children and not self.parent:
266            if not isinstance(self.polymorphic, basestring):
267                self.polymorphic = options.DEFAULT_POLYMORPHIC_COL_NAME
268               
269            self.add_column(Column(self.polymorphic, 
270                                   options.POLYMORPHIC_COL_TYPE))
271
272        if self.version_id_col:
273            if not isinstance(self.version_id_col, basestring):
274                self.version_id_col = options.DEFAULT_VERSION_ID_COL_NAME
275            self.add_column(Column(self.version_id_col, Integer))
276
277        # create list of columns and constraints
278        if self.autoload:
279            args = self.table_args
280        else:
281            args = self.columns + self.constraints + self.table_args
282       
283        # specify options
284        kwargs = self.table_options
285
286        if self.autoload:
287            kwargs['autoload'] = True
288
289        self.entity.table = Table(self.tablename, self.metadata, 
290                                  *args, **kwargs)
291
292    def setup_reltables(self):
293        self.call_builders('create_tables')
294
295    def after_table(self):
296        self.call_builders('after_table')
297
298    def setup_events(self):
299        def make_proxy_method(methods):
300            def proxy_method(self, mapper, connection, instance):
301                for func in methods:
302                    ret = func(instance)
303                    # I couldn't commit myself to force people to
304                    # systematicaly return EXT_PASS in all their event methods.
305                    # But not doing that diverge to how SQLAlchemy works.
306                    # I should try to convince Mike to do EXT_PASS by default,
307                    # and stop processing as the special case.
308#                    if ret != EXT_PASS:
309                    if ret is not None and ret != EXT_PASS:
310                        return ret
311                return EXT_PASS
312            return proxy_method
313
314        # create a list of callbacks for each event
315        methods = {}
316        for name, method in self.entity.__dict__.items():
317            if hasattr(method, '_elixir_events'):
318                for event in method._elixir_events:
319                    event_methods = methods.setdefault(event, [])
320                    event_methods.append(method)
321        if not methods:
322            return
323       
324        # transform that list into methods themselves
325        for event in methods:
326            methods[event] = make_proxy_method(methods[event])
327       
328        # create a custom mapper extension class, tailored to our entity
329        ext = type('EventMapperExtension', (MapperExtension,), methods)()
330       
331        # then, make sure that the entity's mapper has our mapper extension
332        self.add_mapper_extension(ext)
333
334    def before_mapper(self):
335        self.call_builders('before_mapper')
336
337    def _get_children(self):
338        children = self.children[:]
339        for child in self.children:
340            children.extend(child._descriptor._get_children())
341        return children
342
343    def translate_order_by(self, order_by):
344        if isinstance(order_by, basestring):
345            order_by = [order_by]
346       
347        order = list()
348        for colname in order_by:
349            col = self.get_column(colname.strip('-'))
350            if colname.startswith('-'):
351                col = desc(col)
352            order.append(col)
353        return order
354
355    def setup_mapper(self):
356        '''
357        Initializes and assign an (empty!) mapper to the entity.
358        '''
359        if self.entity.mapper:
360            return
361       
362        kwargs = self.mapper_options
363        if self.order_by:
364            kwargs['order_by'] = self.translate_order_by(self.order_by)
365       
366        if self.version_id_col:
367            kwargs['version_id_col'] = self.get_column(self.version_id_col)
368
369        if self.inheritance in ('single', 'concrete', 'multi'):
370            if self.parent and \
371               not (self.inheritance == 'concrete' and not self.polymorphic):
372                kwargs['inherits'] = self.parent.mapper
373
374            if self.inheritance == 'multi' and self.parent:
375                col_pairs = zip(self.primary_keys,
376                                self.parent._descriptor.primary_keys)
377                kwargs['inherit_condition'] = \
378                    and_(*[pc == c for c,pc in col_pairs])
379
380            if self.polymorphic:
381                if self.children and not self.parent:
382                    kwargs['polymorphic_on'] = \
383                        self.get_column(self.polymorphic)
384                    #TODO: this is an optimization, and it breaks the multi
385                    # table polymorphic inheritance test with a relation.
386                    # So I turn it off for now. We might want to provide an
387                    # option to turn it on.
388#                    if self.inheritance == 'multi':
389#                        children = self._get_children()
390#                        join = self.entity.table
391#                        for child in children:
392#                            join = join.outerjoin(child.table)
393#                        kwargs['select_table'] = join
394                   
395                if self.children or self.parent:
396                    #TODO: make this customizable (both callable and string)
397                    #TODO: include module name
398                    if 'polymorphic_identity' not in kwargs:
399                        kwargs['polymorphic_identity'] = \
400                            self.entity.__name__.lower()
401
402                if self.inheritance == 'concrete':
403                    kwargs['concrete'] = True
404
405        if 'primary_key' in kwargs:
406            cols = self.entity.table.c
407            kwargs['primary_key'] = [getattr(cols, colname) for
408                colname in kwargs['primary_key']]
409
410        if self.parent and self.inheritance == 'single':
411            args = []
412        else:
413            args = [self.entity.table]
414
415        self.entity.mapper = _do_mapping(self.session, self.entity,
416                                         properties=self.properties,
417                                         *args, **kwargs)
418
419    def after_mapper(self):
420        self.call_builders('after_mapper')
421
422    def setup_properties(self):
423        self.call_builders('create_properties')
424
425    def finalize(self):
426        self.call_builders('finalize')
427        self.entity._setup_done = True
428
429    #----------------
430    # helper methods
431
432    def call_builders(self, what):
433        for builder in self.builders:
434            if hasattr(builder, what):
435                getattr(builder, what)()
436
437    def add_column(self, col, check_duplicate=None):
438        '''when check_duplicate is None, the value of the allowcoloverride
439        option of the entity is used.
440        '''
441        if check_duplicate is None:
442            check_duplicate = not self.allowcoloverride
443       
444        if check_duplicate and self.get_column(col.key, False) is not None:
445            raise Exception("Column '%s' already exist in '%s' ! " % 
446                            (col.key, self.entity.__name__))
447        self._columns.append(col)
448       
449        if col.primary_key:
450            self.has_pk = True
451
452        # Autosetup triggers shouldn't be active anymore at this point, so we
453        # can theoretically access the entity's table safely. But the problem
454        # is that if, for some reason, the "trigger" removal phase didn't
455        # happen, we'll get an infinite loop. So we just make sure we don't
456        # get one in any case.
457        table = type.__getattribute__(self.entity, 'table')
458        if table:
459            if check_duplicate and col.key in table.columns.keys():
460                raise Exception("Column '%s' already exist in table '%s' ! " % 
461                                (col.key, table.name))
462            table.append_column(col)
463
464    def add_constraint(self, constraint):
465        self.constraints.append(constraint)
466       
467        table = self.entity.table
468        if table:
469            table.append_constraint(constraint)
470
471    def add_property(self, name, property, check_duplicate=True):
472        if check_duplicate and name in self.properties:
473            raise Exception("property '%s' already exist in '%s' ! " % 
474                            (name, self.entity.__name__))
475        self.properties[name] = property
476        mapper = self.entity.mapper
477        if mapper:
478            mapper.add_property(name, property)
479       
480    def add_mapper_extension(self, extension):
481        extensions = self.mapper_options.get('extension', [])
482        if not isinstance(extensions, list):
483            extensions = [extensions]
484        extensions.append(extension)
485        self.mapper_options['extension'] = extensions
486
487    def get_column(self, key, check_missing=True):
488        "need to support both the case where the table is already setup or not"
489        #TODO: support SA table/autoloaded entity
490        for col in self.columns:
491            if col.key == key:
492                return col
493        if check_missing:
494            raise Exception("No column named '%s' found in the table of the "
495                            "'%s' entity!" % (key, self.entity.__name__))
496        return None
497
498    def get_inverse_relation(self, rel, reverse=False):
499        '''
500        Return the inverse relation of rel, if any, None otherwise.
501        '''
502
503        matching_rel = None
504        for other_rel in self.relationships:
505            if other_rel.is_inverse(rel):
506                if matching_rel is None:
507                    matching_rel = other_rel
508                else:
509                    raise Exception(
510                            "Several relations match as inverse of the '%s' "
511                            "relation in entity '%s'. You should specify "
512                            "inverse relations manually by using the inverse "
513                            "keyword."
514                            % (rel.name, rel.entity.__name__))
515        # When a matching inverse is found, we check that it has only
516        # one relation matching as its own inverse. We don't need the result
517        # of the method though. But we do need to be careful not to start an
518        # infinite recursive loop.
519        if matching_rel and not reverse:
520            rel.entity._descriptor.get_inverse_relation(matching_rel, True)
521
522        return matching_rel
523
524    def find_relationship(self, name):
525        for rel in self.relationships:
526            if rel.name == name:
527                return rel
528        if self.parent:
529            return self.parent._descriptor.find_relationship(name)
530        else:
531            return None
532
533    def columns(self):
534        #FIXME: this would be more correct but it breaks inheritance, so I'll
535        # use the old test for now.
536#        if self.entity.table:
537        if self.autoload: 
538            return self.entity.table.columns
539        else:
540            #FIXME: depending on the type of inheritance, we should also
541            # return the parent entity's columns (for example for order_by
542            # using a column defined in the parent.
543            return self._columns
544    columns = property(columns)
545
546    def primary_keys(self):
547        if self.autoload:
548            return [col for col in self.entity.table.primary_key.columns]
549        else:
550            if self.parent and self.inheritance == 'single':
551                return self.parent._descriptor.primary_keys
552            else:
553                return [col for col in self.columns if col.primary_key]
554    primary_keys = property(primary_keys)
555
556
557class TriggerProxy(object):
558    """A class that serves as a "trigger" ; accessing its attributes runs
559    the setup_all function.
560
561    Note that the `setup_all` is called on each access of the attribute.
562    """
563
564    def __init__(self, class_, attrname):
565        self.class_ = class_
566        self.attrname = attrname
567
568    def __getattr__(self, name):
569        elixir.setup_all()
570        #FIXME: it's possible to get an infinite loop here if setup_all doesn't
571        #remove the triggers for this entity. This can happen if the entity is
572        #not in the `entities` list for some reason.
573        proxied_attr = getattr(self.class_, self.attrname)
574        return getattr(proxied_attr, name)
575
576    def __repr__(self):
577        proxied_attr = getattr(self.class_, self.attrname)
578        return "<TriggerProxy (%s)>" % (self.class_.__name__)
579
580
581class TriggerAttribute(object):
582
583    def __init__(self, attrname):
584        self.attrname = attrname
585
586    def __get__(self, instance, owner):
587        #FIXME: it's possible to get an infinite loop here if setup_all doesn't
588        #remove the triggers for this entity. This can happen if the entity is
589        #not in the `entities` list for some reason.
590        elixir.setup_all()
591        return getattr(owner, self.attrname)
592
593def is_base(cls):
594    """
595    Scan bases classes to see if any is an instance of EntityMeta. If we
596    don't find any, it means the current entity is a base class (like
597    the 'Entity' class).
598    """
599    for base in cls.__bases__:
600        if isinstance(base, EntityMeta):
601            return False
602    return True
603
604class EntityMeta(type):
605    """
606    Entity meta class.
607    You should only use it directly if you want to define your own base class
608    for your entities (ie you don't want to use the provided 'Entity' class).
609    """
610    _entities = {}
611
612    def __init__(cls, name, bases, dict_):
613        # Only process further subclasses of the base classes (Entity et al.),
614        # not the base classes themselves. We don't want the base entities to
615        # be registered in an entity collection, nor to have a table name and
616        # so on.
617        if is_base(cls):
618            return
619
620        # build a dict of entities for each frame where there are entities
621        # defined
622        caller_frame = sys._getframe(1)
623        cid = cls._caller = id(caller_frame)
624        caller_entities = EntityMeta._entities.setdefault(cid, {})
625        caller_entities[name] = cls
626
627        # Append all entities which are currently visible by the entity. This
628        # will find more entities only if some of them where imported from
629        # another module.
630        for entity in [e for e in caller_frame.f_locals.values() 
631                         if isinstance(e, EntityMeta)]:
632            caller_entities[entity.__name__] = entity
633
634        # create the entity descriptor
635        desc = cls._descriptor = EntityDescriptor(cls)
636
637        # Process attributes (using the assignment syntax), looking for
638        # 'Property' instances and attaching them to this entity.
639        properties = [(name, attr) for name, attr in dict_.iteritems() 
640                                   if isinstance(attr, Property)]
641        sorted_props = sorted(properties, key=lambda i: i[1]._counter)
642
643        for name, prop in sorted_props:
644            prop.attach(cls, name)
645
646        # Process mutators. Needed before _install_autosetup_triggers so that
647        # we know of the metadata
648        process_mutators(cls)
649
650        # setup misc options here (like tablename etc.)
651        desc.setup_options()
652
653        # create trigger proxies
654        # TODO: support entity_name... It makes sense only for autoloaded
655        # tables for now, and would make more sense if we support "external"
656        # tables
657        if desc.autosetup:
658            _install_autosetup_triggers(cls)
659
660    def __call__(cls, *args, **kwargs):
661        if cls._descriptor.autosetup and not hasattr(cls, '_setup_done'):
662            elixir.setup_all()
663        return type.__call__(cls, *args, **kwargs)
664
665
666def _install_autosetup_triggers(cls, entity_name=None):
667    #TODO: move as much as possible of those "_private" values to the
668    # descriptor, so that we don't mess the initial class.
669    tablename = cls._descriptor.tablename
670    schema = cls._descriptor.table_options.get('schema', None)
671    cls._table_key = sqlalchemy.schema._get_table_key(tablename, schema)
672
673    table_proxy = TriggerProxy(cls, 'table')
674
675    md = cls._descriptor.metadata
676    md.tables[cls._table_key] = table_proxy
677
678    # We need to monkeypatch the metadata's table iterator method because
679    # otherwise it doesn't work if the setup is triggered by the
680    # metadata.create_all().
681    # This is because ManyToMany relationships add tables AFTER the list
682    # of tables that are going to be created is "computed"
683    # (metadata.tables.values()).
684    # see:
685    # - table_iterator method in MetaData class in sqlalchemy/schema.py
686    # - visit_metadata method in sqlalchemy/ansisql.py
687    original_table_iterator = md.table_iterator
688    if not hasattr(original_table_iterator, 
689                   '_non_elixir_patched_iterator'):
690        def table_iterator(*args, **kwargs):
691            elixir.setup_all()
692            return original_table_iterator(*args, **kwargs)
693        table_iterator.__doc__ = original_table_iterator.__doc__
694        table_iterator._non_elixir_patched_iterator = \
695            original_table_iterator
696        md.table_iterator = table_iterator
697
698    #TODO: we might want to add all columns that will be available as
699    #attributes on the class itself (in SA 0.4). This would be a pretty
700    #rare usecase, as people will hit the query attribute before the
701    #column attributes, but still...
702    for name in ('c', 'table', 'mapper', 'query'):
703        setattr(cls, name, TriggerAttribute(name))
704
705    cls._has_triggers = True
706
707
708def _cleanup_autosetup_triggers(cls):
709    if not hasattr(cls, '_has_triggers'):
710        return
711
712    for name in ('table', 'mapper'):
713        setattr(cls, name, None)
714
715    for name in ('c', 'query'):
716        delattr(cls, name)
717
718    desc = cls._descriptor
719    md = desc.metadata
720
721    # the fake table could have already been removed (namely in a
722    # single table inheritance scenario)
723    md.tables.pop(cls._table_key, None)
724
725    # restore original table iterator if not done already
726    if hasattr(md.table_iterator, '_non_elixir_patched_iterator'):
727        md.table_iterator = \
728            md.table_iterator._non_elixir_patched_iterator
729
730    del cls._has_triggers
731
732   
733def setup_entities(entities):
734    '''Setup all entities in the list passed as argument'''
735
736    for entity in entities:
737        if entity._descriptor.autosetup:
738            _cleanup_autosetup_triggers(entity)
739
740    for method_name in (
741            'setup_autoload_table', 'create_pk_cols', 'setup_relkeys',
742            'before_table', 'setup_table', 'setup_reltables', 'after_table',
743            'setup_events',
744            'before_mapper', 'setup_mapper', 'after_mapper',
745            'setup_properties',
746            'finalize'):
747        for entity in entities:
748            if hasattr(entity, '_setup_done'):
749                continue
750            method = getattr(entity._descriptor, method_name)
751            method()
752
753
754def cleanup_entities(entities):
755    """
756    Try to revert back the list of entities passed as argument to the state
757    they had just before their setup phase. It will not work entirely for
758    autosetup entities as we need to remove the autosetup triggers.
759
760    As of now, this function is *not* functional in that it doesn't revert to
761    the exact same state the entities were before setup. For example, the
762    properties do not work yet as those would need to be regenerated (since the
763    columns they are based on are regenerated too -- and as such the
764    corresponding joins are not correct) but this doesn't happen because of
765    the way relationship setup is designed to be called only once (especially
766    the backref stuff in create_properties).
767    """
768    for entity in entities:
769        desc = entity._descriptor
770        if desc.autosetup:
771            _cleanup_autosetup_triggers(entity)
772
773        if hasattr(entity, '_setup_done'):
774            del entity._setup_done
775
776        entity.table = None
777        entity.mapper = None
778       
779        desc._pk_col_done = False
780        desc.has_pk = False
781        desc._columns = []
782        desc.constraints = []
783        desc.properties = {}
784
785
786class Entity(object):
787    '''
788    The base class for all entities
789   
790    All Elixir model objects should inherit from this class. Statements can
791    appear within the body of the definition of an entity to define its
792    fields, relationships, and other options.
793   
794    Here is an example:
795
796    .. sourcecode:: python
797   
798        class Person(Entity):
799            name = Field(Unicode(128))
800            birthdate = Field(DateTime, default=datetime.now)
801   
802    Please note, that if you don't specify any primary keys, Elixir will
803    automatically create one called ``id``.
804   
805    For further information, please refer to the provided examples or
806    tutorial.
807    '''
808    __metaclass__ = EntityMeta
809   
810    def __init__(self, **kwargs):
811        for key, value in kwargs.items():
812            setattr(self, key, value)
813
814    def set(self, **kwargs):
815        for key, value in kwargs.items():
816            setattr(self, key, value)
817
818    # session methods
819    def flush(self, *args, **kwargs):
820        return object_session(self).flush([self], *args, **kwargs)
821
822    def delete(self, *args, **kwargs):
823        return object_session(self).delete(self, *args, **kwargs)
824
825    def expire(self, *args, **kwargs):
826        return object_session(self).expire(self, *args, **kwargs)
827
828    def refresh(self, *args, **kwargs):
829        return object_session(self).refresh(self, *args, **kwargs)
830
831    def expunge(self, *args, **kwargs):
832        return object_session(self).expunge(self, *args, **kwargs)
833
834    # This bunch of session methods, along with all the query methods below
835    # only make sense when using a global/scoped/contextual session.
836    def _global_session(self):
837        return self._descriptor.session.registry()
838    _global_session = property(_global_session)
839
840    def merge(self, *args, **kwargs):
841        return self._global_session.merge(self, *args, **kwargs)
842
843    def save(self, *args, **kwargs):
844        return self._global_session.save(self, *args, **kwargs)
845
846    def update(self, *args, **kwargs):
847        return self._global_session.update(self, *args, **kwargs)
848
849    def save_or_update(self, *args, **kwargs):
850        return self._global_session.save_or_update(self, *args, **kwargs)
851
852    # query methods
853    def get_by(cls, *args, **kwargs):
854        return cls.query.filter_by(*args, **kwargs).first()
855    get_by = classmethod(get_by)
856
857    def get(cls, *args, **kwargs):
858        return cls.query.get(*args, **kwargs)
859    get = classmethod(get)
860
861    #-----------------#
862    # DEPRECATED LAND #
863    #-----------------#
864
865    def filter(cls, *args, **kwargs):
866        warnings.warn("The filter method on the class is deprecated."
867                      "You should use cls.query.filter(...)", 
868                      DeprecationWarning, stacklevel=2)
869        return cls.query.filter(*args, **kwargs)
870    filter = classmethod(filter)
871
872    def filter_by(cls, *args, **kwargs):
873        warnings.warn("The filter_by method on the class is deprecated."
874                      "You should use cls.query.filter_by(...)", 
875                      DeprecationWarning, stacklevel=2)
876        return cls.query.filter_by(*args, **kwargs)
877    filter_by = classmethod(filter_by)
878
879    def select(cls, *args, **kwargs):
880        warnings.warn("The select method on the class is deprecated."
881                      "You should use cls.query.filter(...).all()", 
882                      DeprecationWarning, stacklevel=2)
883        return cls.query.filter(*args, **kwargs).all()
884    select = classmethod(select)
885
886    def select_by(cls, *args, **kwargs):
887        warnings.warn("The select_by method on the class is deprecated."
888                      "You should use cls.query.filter_by(...).all()", 
889                      DeprecationWarning, stacklevel=2)
890        return cls.query.filter_by(*args, **kwargs).all()
891    select_by = classmethod(select_by)
892
893    def selectfirst(cls, *args, **kwargs):
894        warnings.warn("The selectfirst method on the class is deprecated."
895                      "You should use cls.query.filter(...).first()", 
896                      DeprecationWarning, stacklevel=2)
897        return cls.query.filter(*args, **kwargs).first()
898    selectfirst = classmethod(selectfirst)
899
900    def selectfirst_by(cls, *args, **kwargs):
901        warnings.warn("The selectfirst_by method on the class is deprecated."
902                      "You should use cls.query.filter_by(...).first()", 
903                      DeprecationWarning, stacklevel=2)
904        return cls.query.filter_by(*args, **kwargs).first()
905    selectfirst_by = classmethod(selectfirst_by)
906
907    def selectone(cls, *args, **kwargs):
908        warnings.warn("The selectone method on the class is deprecated."
909                      "You should use cls.query.filter(...).one()", 
910                      DeprecationWarning, stacklevel=2)
911        return cls.query.filter(*args, **kwargs).one()
912    selectone = classmethod(selectone)
913
914    def selectone_by(cls, *args, **kwargs):
915        warnings.warn("The selectone_by method on the class is deprecated."
916                      "You should use cls.query.filter_by(...).one()", 
917                      DeprecationWarning, stacklevel=2)
918        return cls.query.filter_by(*args, **kwargs).one()
919    selectone_by = classmethod(selectone_by)
920
921    def join_to(cls, *args, **kwargs):
922        warnings.warn("The join_to method on the class is deprecated."
923                      "You should use cls.query.join(...)", 
924                      DeprecationWarning, stacklevel=2)
925        return cls.query.join_to(*args, **kwargs).all()
926    join_to = classmethod(join_to)
927
928    def join_via(cls, *args, **kwargs):
929        warnings.warn("The join_via method on the class is deprecated."
930                      "You should use cls.query.join(...)", 
931                      DeprecationWarning, stacklevel=2)
932        return cls.query.join_via(*args, **kwargs).all()
933    join_via = classmethod(join_via)
934
935    def count(cls, *args, **kwargs):
936        warnings.warn("The count method on the class is deprecated."
937                      "You should use cls.query.filter(...).count()", 
938                      DeprecationWarning, stacklevel=2)
939        return cls.query.filter(*args, **kwargs).count()
940    count = classmethod(count)
941
942    def count_by(cls, *args, **kwargs):
943        warnings.warn("The count_by method on the class is deprecated."
944                      "You should use cls.query.filter_by(...).count()", 
945                      DeprecationWarning, stacklevel=2)
946        return cls.query.filter_by(*args, **kwargs).count()
947    count_by = classmethod(count_by)
948
949    def options(cls, *args, **kwargs):
950        warnings.warn("The options method on the class is deprecated."
951                      "You should use cls.query.options(...)", 
952                      DeprecationWarning, stacklevel=2)
953        return cls.query.options(*args, **kwargs)
954    options = classmethod(options)
955
956    def instances(cls, *args, **kwargs):
957        warnings.warn("The instances method on the class is deprecated."
958                      "You should use cls.query.instances(...)", 
959                      DeprecationWarning, stacklevel=2)
960        return cls.query.instances(*args, **kwargs)
961    instances = classmethod(instances)
962
963
Note: See TracBrowser for help on using the browser.