root / elixir / tags / 0.5.0 / elixir / entity.py

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