root/galaxy-central/eggs/SQLAlchemy-0.5.6_dev_r6498-py2.6.egg/sqlalchemy/orm/interfaces.py

リビジョン 3, 32.7 KB (コミッタ: kohda, 14 年 前)

Install Unix tools  http://hannonlab.cshl.edu/galaxy_unix_tools/galaxy.html

行番号 
1# interfaces.py
2# Copyright (C) 2005, 2006, 2007, 2008, 2009 Michael Bayer mike_mp@zzzcomputing.com
3#
4# This module is part of SQLAlchemy and is released under
5# the MIT License: http://www.opensource.org/licenses/mit-license.php
6
7"""
8
9Semi-private module containing various base classes used throughout the ORM.
10
11Defines the extension classes :class:`MapperExtension`,
12:class:`SessionExtension`, and :class:`AttributeExtension` as
13well as other user-subclassable extension objects.
14
15"""
16
17from itertools import chain
18
19import sqlalchemy.exceptions as sa_exc
20from sqlalchemy import log, util
21from sqlalchemy.sql import expression
22
23class_mapper = None
24collections = None
25
26__all__ = (
27    'AttributeExtension',
28    'EXT_CONTINUE',
29    'EXT_STOP',
30    'ExtensionOption',
31    'InstrumentationManager',
32    'LoaderStrategy',
33    'MapperExtension',
34    'MapperOption',
35    'MapperProperty',
36    'PropComparator',
37    'PropertyOption',
38    'SessionExtension',
39    'StrategizedOption',
40    'StrategizedProperty',
41    'build_path',
42    )
43
44EXT_CONTINUE = util.symbol('EXT_CONTINUE')
45EXT_STOP = util.symbol('EXT_STOP')
46
47ONETOMANY = util.symbol('ONETOMANY')
48MANYTOONE = util.symbol('MANYTOONE')
49MANYTOMANY = util.symbol('MANYTOMANY')
50
51class MapperExtension(object):
52    """Base implementation for customizing Mapper behavior.
53
54    For each method in MapperExtension, returning a result of EXT_CONTINUE
55    will allow processing to continue to the next MapperExtension in line or
56    use the default functionality if there are no other extensions.
57
58    Returning EXT_STOP will halt processing of further extensions handling
59    that method.  Some methods such as ``load`` have other return
60    requirements, see the individual documentation for details.  Other than
61    these exception cases, any return value other than EXT_CONTINUE or
62    EXT_STOP will be interpreted as equivalent to EXT_STOP.
63
64    """
65    def instrument_class(self, mapper, class_):
66        return EXT_CONTINUE
67
68    def init_instance(self, mapper, class_, oldinit, instance, args, kwargs):
69        return EXT_CONTINUE
70
71    def init_failed(self, mapper, class_, oldinit, instance, args, kwargs):
72        return EXT_CONTINUE
73
74    def translate_row(self, mapper, context, row):
75        """Perform pre-processing on the given result row and return a
76        new row instance.
77
78        This is called when the mapper first receives a row, before
79        the object identity or the instance itself has been derived
80        from that row.
81
82        """
83        return EXT_CONTINUE
84
85    def create_instance(self, mapper, selectcontext, row, class_):
86        """Receive a row when a new object instance is about to be
87        created from that row.
88
89        The method can choose to create the instance itself, or it can return
90        EXT_CONTINUE to indicate normal object creation should take place.
91
92        mapper
93          The mapper doing the operation
94
95        selectcontext
96          SelectionContext corresponding to the instances() call
97
98        row
99          The result row from the database
100
101        class\_
102          The class we are mapping.
103
104        return value
105          A new object instance, or EXT_CONTINUE
106
107        """
108        return EXT_CONTINUE
109
110    def append_result(self, mapper, selectcontext, row, instance, result, **flags):
111        """Receive an object instance before that instance is appended
112        to a result list.
113
114        If this method returns EXT_CONTINUE, result appending will proceed
115        normally.  if this method returns any other value or None,
116        result appending will not proceed for this instance, giving
117        this extension an opportunity to do the appending itself, if
118        desired.
119
120        mapper
121          The mapper doing the operation.
122
123        selectcontext
124          SelectionContext corresponding to the instances() call.
125
126        row
127          The result row from the database.
128
129        instance
130          The object instance to be appended to the result.
131
132        result
133          List to which results are being appended.
134
135        \**flags
136          extra information about the row, same as criterion in
137          ``create_row_processor()`` method of :class:`~sqlalchemy.orm.interfaces.MapperProperty`
138        """
139
140        return EXT_CONTINUE
141
142    def populate_instance(self, mapper, selectcontext, row, instance, **flags):
143        """Receive an instance before that instance has
144        its attributes populated.
145
146        This usually corresponds to a newly loaded instance but may
147        also correspond to an already-loaded instance which has
148        unloaded attributes to be populated.  The method may be called
149        many times for a single instance, as multiple result rows are
150        used to populate eagerly loaded collections.
151
152        If this method returns EXT_CONTINUE, instance population will
153        proceed normally.  If any other value or None is returned,
154        instance population will not proceed, giving this extension an
155        opportunity to populate the instance itself, if desired.
156
157        As of 0.5, most usages of this hook are obsolete.  For a
158        generic "object has been newly created from a row" hook, use
159        ``reconstruct_instance()``, or the ``@orm.reconstructor``
160        decorator.
161
162        """
163        return EXT_CONTINUE
164
165    def reconstruct_instance(self, mapper, instance):
166        """Receive an object instance after it has been created via
167        ``__new__``, and after initial attribute population has
168        occurred.
169
170        This typically occurs when the instance is created based on
171        incoming result rows, and is only called once for that
172        instance's lifetime.
173
174        Note that during a result-row load, this method is called upon
175        the first row received for this instance. If eager loaders are
176        set to further populate collections on the instance, those
177        will *not* yet be completely loaded.
178
179        """
180        return EXT_CONTINUE
181
182    def before_insert(self, mapper, connection, instance):
183        """Receive an object instance before that instance is INSERTed
184        into its table.
185
186        This is a good place to set up primary key values and such
187        that aren't handled otherwise.
188
189        Column-based attributes can be modified within this method
190        which will result in the new value being inserted.  However
191        *no* changes to the overall flush plan can be made; this means
192        any collection modification or save() operations which occur
193        within this method will not take effect until the next flush
194        call.
195
196        """
197
198        return EXT_CONTINUE
199
200    def after_insert(self, mapper, connection, instance):
201        """Receive an object instance after that instance is INSERTed."""
202
203        return EXT_CONTINUE
204
205    def before_update(self, mapper, connection, instance):
206        """Receive an object instance before that instance is UPDATEed.
207
208        Note that this method is called for all instances that are marked as
209        "dirty", even those which have no net changes to their column-based
210        attributes.  An object is marked as dirty when any of its column-based
211        attributes have a "set attribute" operation called or when any of its
212        collections are modified.  If, at update time, no column-based attributes
213        have any net changes, no UPDATE statement will be issued.  This means
214        that an instance being sent to before_update is *not* a guarantee that
215        an UPDATE statement will be issued (although you can affect the outcome
216        here).
217
218        To detect if the column-based attributes on the object have net changes,
219        and will therefore generate an UPDATE statement, use
220        ``object_session(instance).is_modified(instance, include_collections=False)``.
221
222        Column-based attributes can be modified within this method which will
223        result in their being updated.  However *no* changes to the overall
224        flush plan can be made; this means any collection modification or
225        save() operations which occur within this method will not take effect
226        until the next flush call.
227
228        """
229
230        return EXT_CONTINUE
231
232    def after_update(self, mapper, connection, instance):
233        """Receive an object instance after that instance is UPDATEed."""
234
235        return EXT_CONTINUE
236
237    def before_delete(self, mapper, connection, instance):
238        """Receive an object instance before that instance is DELETEed.
239
240        Note that *no* changes to the overall
241        flush plan can be made here; this means any collection modification,
242        save() or delete() operations which occur within this method will
243        not take effect until the next flush call.
244
245        """
246
247        return EXT_CONTINUE
248
249    def after_delete(self, mapper, connection, instance):
250        """Receive an object instance after that instance is DELETEed."""
251
252        return EXT_CONTINUE
253
254class SessionExtension(object):
255    """An extension hook object for Sessions.  Subclasses may be installed into a Session
256    (or sessionmaker) using the ``extension`` keyword argument.
257    """
258
259    def before_commit(self, session):
260        """Execute right before commit is called.
261
262        Note that this may not be per-flush if a longer running transaction is ongoing."""
263
264    def after_commit(self, session):
265        """Execute after a commit has occured.
266
267        Note that this may not be per-flush if a longer running transaction is ongoing."""
268
269    def after_rollback(self, session):
270        """Execute after a rollback has occured.
271
272        Note that this may not be per-flush if a longer running transaction is ongoing."""
273
274    def before_flush(self, session, flush_context, instances):
275        """Execute before flush process has started.
276
277        `instances` is an optional list of objects which were passed to the ``flush()``
278        method.
279        """
280
281    def after_flush(self, session, flush_context):
282        """Execute after flush has completed, but before commit has been called.
283
284        Note that the session's state is still in pre-flush, i.e. 'new', 'dirty',
285        and 'deleted' lists still show pre-flush state as well as the history
286        settings on instance attributes."""
287
288    def after_flush_postexec(self, session, flush_context):
289        """Execute after flush has completed, and after the post-exec state occurs.
290
291        This will be when the 'new', 'dirty', and 'deleted' lists are in their final
292        state.  An actual commit() may or may not have occured, depending on whether or not
293        the flush started its own transaction or participated in a larger transaction.
294        """
295
296    def after_begin(self, session, transaction, connection):
297        """Execute after a transaction is begun on a connection
298
299        `transaction` is the SessionTransaction. This method is called after an
300        engine level transaction is begun on a connection.
301        """
302
303    def after_attach(self, session, instance):
304        """Execute after an instance is attached to a session.
305
306        This is called after an add, delete or merge.
307        """
308
309    def after_bulk_update(self, session, query, query_context, result):
310        """Execute after a bulk update operation to the session.
311
312        This is called after a session.query(...).update()
313
314        `query` is the query object that this update operation was called on.
315        `query_context` was the query context object.
316        `result` is the result object returned from the bulk operation.
317        """
318
319    def after_bulk_delete(self, session, query, query_context, result):
320        """Execute after a bulk delete operation to the session.
321
322        This is called after a session.query(...).delete()
323
324        `query` is the query object that this delete operation was called on.
325        `query_context` was the query context object.
326        `result` is the result object returned from the bulk operation.
327        """
328
329class MapperProperty(object):
330    """Manage the relationship of a ``Mapper`` to a single class
331    attribute, as well as that attribute as it appears on individual
332    instances of the class, including attribute instrumentation,
333    attribute access, loading behavior, and dependency calculations.
334    """
335
336    def setup(self, context, entity, path, adapter, **kwargs):
337        """Called by Query for the purposes of constructing a SQL statement.
338
339        Each MapperProperty associated with the target mapper processes the
340        statement referenced by the query context, adding columns and/or
341        criterion as appropriate.
342        """
343
344        pass
345
346    def create_row_processor(self, selectcontext, path, mapper, row, adapter):
347        """Return a 2-tuple consiting of two row processing functions and an instance post-processing function.
348
349        Input arguments are the query.SelectionContext and the *first*
350        applicable row of a result set obtained within
351        query.Query.instances(), called only the first time a particular
352        mapper's populate_instance() method is invoked for the overall result.
353
354        The settings contained within the SelectionContext as well as the
355        columns present in the row (which will be the same columns present in
356        all rows) are used to determine the presence and behavior of the
357        returned callables.  The callables will then be used to process all
358        rows and instances.
359
360        Callables are of the following form::
361
362            def new_execute(state, dict_, row, **flags):
363                # process incoming instance state and given row.  the instance is
364                # "new" and was just created upon receipt of this row.
365                # flags is a dictionary containing at least the following
366                # attributes:
367                #   isnew - indicates if the instance was newly created as a
368                #           result of reading this row
369                #   instancekey - identity key of the instance
370
371            def existing_execute(state, dict_, row, **flags):
372                # process incoming instance state and given row.  the instance is
373                # "existing" and was created based on a previous row.
374
375            return (new_execute, existing_execute)
376
377        Either of the three tuples can be ``None`` in which case no function
378        is called.
379        """
380
381        raise NotImplementedError()
382
383    def cascade_iterator(self, type_, state, visited_instances=None, halt_on=None):
384        """Iterate through instances related to the given instance for
385        a particular 'cascade', starting with this MapperProperty.
386
387        See PropertyLoader for the related instance implementation.
388        """
389
390        return iter(())
391
392    def set_parent(self, parent):
393        self.parent = parent
394
395    def instrument_class(self, mapper):
396        raise NotImplementedError()
397
398    _compile_started = False
399    _compile_finished = False
400
401    def init(self):
402        """Called after all mappers are created to assemble
403        relationships between mappers and perform other post-mapper-creation
404        initialization steps.
405
406        """
407        self._compile_started = True
408        self.do_init()
409        self._compile_finished = True
410
411    def do_init(self):
412        """Perform subclass-specific initialization post-mapper-creation steps.
413
414        This is a *template* method called by the
415        ``MapperProperty`` object's init() method.
416
417        """
418        pass
419
420    def post_instrument_class(self, mapper):
421        """Perform instrumentation adjustments that need to occur
422        after init() has completed.
423
424        """
425        pass
426
427    def register_dependencies(self, *args, **kwargs):
428        """Called by the ``Mapper`` in response to the UnitOfWork
429        calling the ``Mapper``'s register_dependencies operation.
430        Establishes a topological dependency between two mappers
431        which will affect the order in which mappers persist data.
432       
433        """
434
435        pass
436
437    def register_processors(self, *args, **kwargs):
438        """Called by the ``Mapper`` in response to the UnitOfWork
439        calling the ``Mapper``'s register_processors operation.
440        Establishes a processor object between two mappers which
441        will link data and state between parent/child objects.
442       
443        """
444
445        pass
446       
447    def is_primary(self):
448        """Return True if this ``MapperProperty``'s mapper is the
449        primary mapper for its class.
450
451        This flag is used to indicate that the ``MapperProperty`` can
452        define attribute instrumentation for the class at the class
453        level (as opposed to the individual instance level).
454        """
455
456        return not self.parent.non_primary
457
458    def merge(self, session, source, dest, dont_load, _recursive):
459        """Merge the attribute represented by this ``MapperProperty``
460        from source to destination object"""
461
462        raise NotImplementedError()
463
464    def compare(self, operator, value):
465        """Return a compare operation for the columns represented by
466        this ``MapperProperty`` to the given value, which may be a
467        column value or an instance.  'operator' is an operator from
468        the operators module, or from sql.Comparator.
469
470        By default uses the PropComparator attached to this MapperProperty
471        under the attribute name "comparator".
472        """
473
474        return operator(self.comparator, value)
475
476class PropComparator(expression.ColumnOperators):
477    """defines comparison operations for MapperProperty objects.
478
479    PropComparator instances should also define an accessor 'property'
480    which returns the MapperProperty associated with this
481    PropComparator.
482    """
483
484    def __init__(self, prop, mapper, adapter=None):
485        self.prop = self.property = prop
486        self.mapper = mapper
487        self.adapter = adapter
488
489    def __clause_element__(self):
490        raise NotImplementedError("%r" % self)
491
492    def adapted(self, adapter):
493        """Return a copy of this PropComparator which will use the given adaption function
494        on the local side of generated expressions.
495
496        """
497        return self.__class__(self.prop, self.mapper, adapter)
498
499    @staticmethod
500    def any_op(a, b, **kwargs):
501        return a.any(b, **kwargs)
502
503    @staticmethod
504    def has_op(a, b, **kwargs):
505        return a.has(b, **kwargs)
506
507    @staticmethod
508    def of_type_op(a, class_):
509        return a.of_type(class_)
510
511    def of_type(self, class_):
512        """Redefine this object in terms of a polymorphic subclass.
513
514        Returns a new PropComparator from which further criterion can be evaluated.
515
516        e.g.::
517
518            query.join(Company.employees.of_type(Engineer)).\\
519               filter(Engineer.name=='foo')
520
521        \class_
522            a class or mapper indicating that criterion will be against
523            this specific subclass.
524
525
526        """
527
528        return self.operate(PropComparator.of_type_op, class_)
529
530    def any(self, criterion=None, **kwargs):
531        """Return true if this collection contains any member that meets the given criterion.
532
533        criterion
534          an optional ClauseElement formulated against the member class' table
535          or attributes.
536
537        \**kwargs
538          key/value pairs corresponding to member class attribute names which
539          will be compared via equality to the corresponding values.
540        """
541
542        return self.operate(PropComparator.any_op, criterion, **kwargs)
543
544    def has(self, criterion=None, **kwargs):
545        """Return true if this element references a member which meets the given criterion.
546
547        criterion
548          an optional ClauseElement formulated against the member class' table
549          or attributes.
550
551        \**kwargs
552          key/value pairs corresponding to member class attribute names which
553          will be compared via equality to the corresponding values.
554        """
555
556        return self.operate(PropComparator.has_op, criterion, **kwargs)
557
558
559class StrategizedProperty(MapperProperty):
560    """A MapperProperty which uses selectable strategies to affect
561    loading behavior.
562
563    There is a single default strategy selected by default.  Alternate
564    strategies can be selected at Query time through the usage of
565    ``StrategizedOption`` objects via the Query.options() method.
566    """
567
568    def __get_context_strategy(self, context, path):
569        cls = context.attributes.get(("loaderstrategy", path), None)
570        if cls:
571            try:
572                return self.__all_strategies[cls]
573            except KeyError:
574                return self.__init_strategy(cls)
575        else:
576            return self.strategy
577
578    def _get_strategy(self, cls):
579        try:
580            return self.__all_strategies[cls]
581        except KeyError:
582            return self.__init_strategy(cls)
583
584    def __init_strategy(self, cls):
585        self.__all_strategies[cls] = strategy = cls(self)
586        strategy.init()
587        return strategy
588
589    def setup(self, context, entity, path, adapter, **kwargs):
590        self.__get_context_strategy(context, path + (self.key,)).setup_query(context, entity, path, adapter, **kwargs)
591
592    def create_row_processor(self, context, path, mapper, row, adapter):
593        return self.__get_context_strategy(context, path + (self.key,)).create_row_processor(context, path, mapper, row, adapter)
594
595    def do_init(self):
596        self.__all_strategies = {}
597        self.strategy = self.__init_strategy(self.strategy_class)
598
599    def post_instrument_class(self, mapper):
600        if self.is_primary():
601            self.strategy.init_class_attribute(mapper)
602
603def build_path(entity, key, prev=None):
604    if prev:
605        return prev + (entity, key)
606    else:
607        return (entity, key)
608
609def serialize_path(path):
610    if path is None:
611        return None
612
613    return [
614        (mapper.class_, key)
615        for mapper, key in [(path[i], path[i+1]) for i in range(0, len(path)-1, 2)]
616    ]
617
618def deserialize_path(path):
619    if path is None:
620        return None
621
622    global class_mapper
623    if class_mapper is None:
624        from sqlalchemy.orm import class_mapper
625
626    return tuple(
627        chain(*[(class_mapper(cls), key) for cls, key in path])
628    )
629
630class MapperOption(object):
631    """Describe a modification to a Query."""
632
633    propagate_to_loaders = False
634    """if True, indicate this option should be carried along
635    Query object generated by scalar or object lazy loaders.
636    """
637   
638    def process_query(self, query):
639        pass
640
641    def process_query_conditionally(self, query):
642        """same as process_query(), except that this option may not apply
643        to the given query.
644
645        Used when secondary loaders resend existing options to a new
646        Query."""
647        self.process_query(query)
648
649class ExtensionOption(MapperOption):
650    """a MapperOption that applies a MapperExtension to a query operation."""
651   
652    def __init__(self, ext):
653        self.ext = ext
654
655    def process_query(self, query):
656        entity = query._generate_mapper_zero()
657        entity.extension = entity.extension.copy()
658        entity.extension.push(self.ext)
659
660class PropertyOption(MapperOption):
661    """A MapperOption that is applied to a property off the mapper or
662    one of its child mappers, identified by a dot-separated key.
663    """
664
665    def __init__(self, key, mapper=None):
666        self.key = key
667        self.mapper = mapper
668
669    def process_query(self, query):
670        self._process(query, True)
671
672    def process_query_conditionally(self, query):
673        self._process(query, False)
674
675    def _process(self, query, raiseerr):
676        paths, mappers = self.__get_paths(query, raiseerr)
677        if paths:
678            self.process_query_property(query, paths, mappers)
679
680    def process_query_property(self, query, paths, mappers):
681        pass
682
683    def __find_entity(self, query, mapper, raiseerr):
684        from sqlalchemy.orm.util import _class_to_mapper, _is_aliased_class
685
686        if _is_aliased_class(mapper):
687            searchfor = mapper
688        else:
689            searchfor = _class_to_mapper(mapper).base_mapper
690       
691        for ent in query._mapper_entities:
692            if ent.path_entity is searchfor:
693                return ent
694        else:
695            if raiseerr:
696                raise sa_exc.ArgumentError("Can't find entity %s in Query.  Current list: %r"
697                    % (searchfor, [str(m.path_entity) for m in query._entities]))
698            else:
699                return None
700
701    def __getstate__(self):
702        d = self.__dict__.copy()
703        d['key'] = ret = []
704        for token in util.to_list(self.key):
705            if isinstance(token, PropComparator):
706                ret.append((token.mapper.class_, token.key))
707            else:
708                ret.append(token)
709        return d
710
711    def __setstate__(self, state):
712        ret = []
713        for key in state['key']:
714            if isinstance(key, tuple):
715                cls, propkey = key
716                ret.append(getattr(cls, propkey))
717            else:
718                ret.append(key)
719        state['key'] = tuple(ret)
720        self.__dict__ = state
721
722    def __get_paths(self, query, raiseerr):
723        path = None
724        entity = None
725        l = []
726        mappers = []
727       
728        # _current_path implies we're in a secondary load
729        # with an existing path
730        current_path = list(query._current_path)
731           
732        if self.mapper:
733            entity = self.__find_entity(query, self.mapper, raiseerr)
734            mapper = entity.mapper
735            path_element = entity.path_entity
736
737        for key in util.to_list(self.key):
738            if isinstance(key, basestring):
739                tokens = key.split('.')
740            else:
741                tokens = [key]
742            for token in tokens:
743                if isinstance(token, basestring):
744                    if not entity:
745                        entity = query._entity_zero()
746                        path_element = entity.path_entity
747                        mapper = entity.mapper
748                    mappers.append(mapper)
749                    prop = mapper.get_property(token, resolve_synonyms=True, raiseerr=raiseerr)
750                    key = token
751                elif isinstance(token, PropComparator):
752                    prop = token.property
753                    if not entity:
754                        entity = self.__find_entity(query, token.parententity, raiseerr)
755                        if not entity:
756                            return [], []
757                        path_element = entity.path_entity
758                    mappers.append(prop.parent)
759                    key = prop.key
760                else:
761                    raise sa_exc.ArgumentError("mapper option expects string key or list of attributes")
762
763                if current_path and key == current_path[1]:
764                    current_path = current_path[2:]
765                    continue
766                   
767                if prop is None:
768                    return [], []
769
770                path = build_path(path_element, prop.key, path)
771                l.append(path)
772                if getattr(token, '_of_type', None):
773                    path_element = mapper = token._of_type
774                else:
775                    path_element = mapper = getattr(prop, 'mapper', None)
776
777                if path_element:
778                    path_element = path_element.base_mapper
779                   
780               
781        # if current_path tokens remain, then
782        # we didn't have an exact path match.
783        if current_path:
784            return [], []
785           
786        return l, mappers
787
788class AttributeExtension(object):
789    """An event handler for individual attribute change events.
790
791    AttributeExtension is assembled within the descriptors associated
792    with a mapped class.
793
794    """
795
796    active_history = True
797    """indicates that the set() method would like to receive the 'old' value,
798    even if it means firing lazy callables.
799    """
800   
801    def append(self, state, value, initiator):
802        """Receive a collection append event.
803
804        The returned value will be used as the actual value to be
805        appended.
806
807        """
808        return value
809
810    def remove(self, state, value, initiator):
811        """Receive a remove event.
812
813        No return value is defined.
814
815        """
816        pass
817
818    def set(self, state, value, oldvalue, initiator):
819        """Receive a set event.
820
821        The returned value will be used as the actual value to be
822        set.
823
824        """
825        return value
826
827
828class StrategizedOption(PropertyOption):
829    """A MapperOption that affects which LoaderStrategy will be used
830    for an operation by a StrategizedProperty.
831    """
832
833    def is_chained(self):
834        return False
835
836    def process_query_property(self, query, paths, mappers):
837        if self.is_chained():
838            for path in paths:
839                query._attributes[("loaderstrategy", path)] = self.get_strategy_class()
840        else:
841            query._attributes[("loaderstrategy", paths[-1])] = self.get_strategy_class()
842
843    def get_strategy_class(self):
844        raise NotImplementedError()
845
846
847class LoaderStrategy(object):
848    """Describe the loading behavior of a StrategizedProperty object.
849
850    The ``LoaderStrategy`` interacts with the querying process in three
851    ways:
852
853    * it controls the configuration of the ``InstrumentedAttribute``
854      placed on a class to handle the behavior of the attribute.  this
855      may involve setting up class-level callable functions to fire
856      off a select operation when the attribute is first accessed
857      (i.e. a lazy load)
858
859    * it processes the ``QueryContext`` at statement construction time,
860      where it can modify the SQL statement that is being produced.
861      simple column attributes may add their represented column to the
862      list of selected columns, *eager loading* properties may add
863      ``LEFT OUTER JOIN`` clauses to the statement.
864
865    * it processes the ``SelectionContext`` at row-processing time.  This
866      includes straight population of attributes corresponding to rows,
867      setting instance-level lazyloader callables on newly
868      constructed instances, and appending child items to scalar/collection
869      attributes in response to eagerly-loaded relations.
870    """
871
872    def __init__(self, parent):
873        self.parent_property = parent
874        self.is_class_level = False
875        self.parent = self.parent_property.parent
876        self.key = self.parent_property.key
877
878    def init(self):
879        raise NotImplementedError("LoaderStrategy")
880
881    def init_class_attribute(self, mapper):
882        pass
883
884    def setup_query(self, context, entity, path, adapter, **kwargs):
885        pass
886
887    def create_row_processor(self, selectcontext, path, mapper, row, adapter):
888        """Return row processing functions which fulfill the contract specified
889        by MapperProperty.create_row_processor.
890
891        StrategizedProperty delegates its create_row_processor method directly
892        to this method.
893        """
894
895        raise NotImplementedError()
896
897    def __str__(self):
898        return str(self.parent_property)
899
900    def debug_callable(self, fn, logger, announcement, logfn):
901        if announcement:
902            logger.debug(announcement)
903        if logfn:
904            def call(*args, **kwargs):
905                logger.debug(logfn(*args, **kwargs))
906                return fn(*args, **kwargs)
907            return call
908        else:
909            return fn
910
911class InstrumentationManager(object):
912    """User-defined class instrumentation extension.
913   
914    The API for this class should be considered as semi-stable,
915    and may change slightly with new releases.
916   
917    """
918
919    # r4361 added a mandatory (cls) constructor to this interface.
920    # given that, perhaps class_ should be dropped from all of these
921    # signatures.
922
923    def __init__(self, class_):
924        pass
925
926    def manage(self, class_, manager):
927        setattr(class_, '_default_class_manager', manager)
928
929    def dispose(self, class_, manager):
930        delattr(class_, '_default_class_manager')
931
932    def manager_getter(self, class_):
933        def get(cls):
934            return cls._default_class_manager
935        return get
936
937    def instrument_attribute(self, class_, key, inst):
938        pass
939
940    def post_configure_attribute(self, class_, key, inst):
941        pass
942
943    def install_descriptor(self, class_, key, inst):
944        setattr(class_, key, inst)
945
946    def uninstall_descriptor(self, class_, key):
947        delattr(class_, key)
948
949    def install_member(self, class_, key, implementation):
950        setattr(class_, key, implementation)
951
952    def uninstall_member(self, class_, key):
953        delattr(class_, key)
954
955    def instrument_collection_class(self, class_, key, collection_class):
956        global collections
957        if collections is None:
958            from sqlalchemy.orm import collections
959        return collections.prepare_instrumentation(collection_class)
960
961    def get_instance_dict(self, class_, instance):
962        return instance.__dict__
963
964    def initialize_instance_dict(self, class_, instance):
965        pass
966
967    def install_state(self, class_, instance, state):
968        setattr(instance, '_default_state', state)
969
970    def remove_state(self, class_, instance):
971        delattr(instance, '_default_state', state)
972
973    def state_getter(self, class_):
974        return lambda instance: getattr(instance, '_default_state')
975
976    def dict_getter(self, class_):
977        return lambda inst: self.get_instance_dict(class_, inst)
978       
Note: リポジトリブラウザについてのヘルプは TracBrowser を参照してください。