~ubuntu-branches/debian/jessie/sqlalchemy/jessie

« back to all changes in this revision

Viewing changes to lib/sqlalchemy/orm/instrumentation.py

  • Committer: Package Import Robot
  • Author(s): Piotr Ożarowski, Jakub Wilk, Piotr Ożarowski
  • Date: 2013-07-06 20:53:52 UTC
  • mfrom: (1.4.23) (16.1.17 experimental)
  • Revision ID: package-import@ubuntu.com-20130706205352-ryppl1eto3illd79
Tags: 0.8.2-1
[ Jakub Wilk ]
* Use canonical URIs for Vcs-* fields.

[ Piotr Ożarowski ]
* New upstream release
* Upload to unstable
* Build depend on python3-all instead of -dev, extensions are not built for
  Python 3.X 

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
1
# orm/instrumentation.py
2
 
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
 
2
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
3
3
#
4
4
# This module is part of SQLAlchemy and is released under
5
5
# the MIT License: http://www.opensource.org/licenses/mit-license.php
14
14
and attributes.py which establish per-instance and per-class-attribute
15
15
instrumentation, respectively.
16
16
 
17
 
SQLA's instrumentation system is completely customizable, in which
18
 
case an understanding of the general mechanics of this module is helpful.
19
 
An example of full customization is in /examples/custom_attributes.
20
 
 
21
 
"""
22
 
 
23
 
 
24
 
from sqlalchemy.orm import exc, collections, events
25
 
from operator import attrgetter, itemgetter
26
 
from sqlalchemy import event, util
27
 
import weakref
28
 
from sqlalchemy.orm import state, attributes
29
 
 
30
 
 
31
 
INSTRUMENTATION_MANAGER = '__sa_instrumentation_manager__'
32
 
"""Attribute, elects custom instrumentation when present on a mapped class.
33
 
 
34
 
Allows a class to specify a slightly or wildly different technique for
35
 
tracking changes made to mapped attributes and collections.
36
 
 
37
 
Only one instrumentation implementation is allowed in a given object
38
 
inheritance hierarchy.
39
 
 
40
 
The value of this attribute must be a callable and will be passed a class
41
 
object.  The callable must return one of:
42
 
 
43
 
  - An instance of an interfaces.InstrumentationManager or subclass
44
 
  - An object implementing all or some of InstrumentationManager (TODO)
45
 
  - A dictionary of callables, implementing all or some of the above (TODO)
46
 
  - An instance of a ClassManager or subclass
47
 
 
48
 
interfaces.InstrumentationManager is public API and will remain stable
49
 
between releases.  ClassManager is not public and no guarantees are made
50
 
about stability.  Caveat emptor.
51
 
 
52
 
This attribute is consulted by the default SQLAlchemy instrumentation
53
 
resolution code.  If custom finders are installed in the global
54
 
instrumentation_finders list, they may or may not choose to honor this
55
 
attribute.
56
 
 
57
 
"""
58
 
 
59
 
instrumentation_finders = []
60
 
"""An extensible sequence of instrumentation implementation finding callables.
61
 
 
62
 
Finders callables will be passed a class object.  If None is returned, the
63
 
next finder in the sequence is consulted.  Otherwise the return must be an
64
 
instrumentation factory that follows the same guidelines as
65
 
INSTRUMENTATION_MANAGER.
66
 
 
67
 
By default, the only finder is find_native_user_instrumentation_hook, which
68
 
searches for INSTRUMENTATION_MANAGER.  If all finders return None, standard
69
 
ClassManager instrumentation is used.
70
 
 
71
 
"""
 
17
The class instrumentation system can be customized on a per-class
 
18
or global basis using the :mod:`sqlalchemy.ext.instrumentation`
 
19
module, which provides the means to build and specify
 
20
alternate instrumentation forms.
 
21
 
 
22
.. versionchanged: 0.8
 
23
   The instrumentation extension system was moved out of the
 
24
   ORM and into the external :mod:`sqlalchemy.ext.instrumentation`
 
25
   package.  When that package is imported, it installs
 
26
   itself within sqlalchemy.orm so that its more comprehensive
 
27
   resolution mechanics take effect.
 
28
 
 
29
"""
 
30
 
 
31
 
 
32
from . import exc, collections, events, interfaces
 
33
from operator import attrgetter
 
34
from .. import event, util
 
35
state = util.importlater("sqlalchemy.orm", "state")
72
36
 
73
37
 
74
38
class ClassManager(dict):
81
45
 
82
46
    original_init = object.__init__
83
47
 
 
48
    factory = None
 
49
 
84
50
    def __init__(self, class_):
85
51
        self.class_ = class_
86
 
        self.factory = None  # where we came from, for inheritance bookkeeping
87
52
        self.info = {}
88
53
        self.new_init = None
89
 
        self.mutable_attributes = set()
90
54
        self.local_attrs = {}
91
55
        self.originals = {}
92
56
 
99
63
        for base in self._bases:
100
64
            self.update(base)
101
65
 
 
66
        events._InstanceEventsHold.populate(class_, self)
 
67
 
 
68
        for basecls in class_.__mro__:
 
69
            mgr = manager_of_class(basecls)
 
70
            if mgr is not None:
 
71
                self.dispatch._update(mgr.dispatch)
102
72
        self.manage()
103
73
        self._instrument_init()
104
74
 
 
75
        if '__del__' in class_.__dict__:
 
76
            util.warn("__del__() method on class %s will "
 
77
                        "cause unreachable cycles and memory leaks, "
 
78
                        "as SQLAlchemy instrumentation often creates "
 
79
                        "reference cycles.  Please remove this method." %
 
80
                        class_)
 
81
 
105
82
    dispatch = event.dispatcher(events.InstanceEvents)
106
83
 
107
84
    @property
113
90
        # raises unless self.mapper has been assigned
114
91
        raise exc.UnmappedClassError(self.class_)
115
92
 
 
93
    def _all_sqla_attributes(self, exclude=None):
 
94
        """return an iterator of all classbound attributes that are
 
95
        implement :class:`._InspectionAttr`.
 
96
 
 
97
        This includes :class:`.QueryableAttribute` as well as extension
 
98
        types such as :class:`.hybrid_property` and :class:`.AssociationProxy`.
 
99
 
 
100
        """
 
101
        if exclude is None:
 
102
            exclude = set()
 
103
        for supercls in self.class_.__mro__:
 
104
            for key in set(supercls.__dict__).difference(exclude):
 
105
                exclude.add(key)
 
106
                val = supercls.__dict__[key]
 
107
                if isinstance(val, interfaces._InspectionAttr):
 
108
                    yield key, val
 
109
 
 
110
 
116
111
    def _attr_has_impl(self, key):
117
112
        """Return True if the given attribute is fully initialized.
118
113
 
134
129
        """
135
130
        manager = manager_of_class(cls)
136
131
        if manager is None:
137
 
            manager = _create_manager_for_cls(cls, _source=self)
 
132
            manager = _instrumentation_factory.create_manager_for_cls(cls)
138
133
        return manager
139
134
 
140
135
    def _instrument_init(self):
155
150
    @util.memoized_property
156
151
    def _state_constructor(self):
157
152
        self.dispatch.first_init(self, self.class_)
158
 
        if self.mutable_attributes:
159
 
            return state.MutableAttrInstanceState
160
 
        else:
161
 
            return state.InstanceState
 
153
        return state.InstanceState
162
154
 
163
155
    def manage(self):
164
156
        """Mark this instance as the manager for its class."""
170
162
 
171
163
        delattr(self.class_, self.MANAGER_ATTR)
172
164
 
 
165
    @util.hybridmethod
173
166
    def manager_getter(self):
174
 
        return attrgetter(self.MANAGER_ATTR)
 
167
        def manager_of_class(cls):
 
168
            return cls.__dict__.get(ClassManager.MANAGER_ATTR, None)
 
169
        return manager_of_class
 
170
 
 
171
    @util.hybridmethod
 
172
    def state_getter(self):
 
173
        """Return a (instance) -> InstanceState callable.
 
174
 
 
175
        "state getter" callables should raise either KeyError or
 
176
        AttributeError if no InstanceState could be found for the
 
177
        instance.
 
178
        """
 
179
 
 
180
        return attrgetter(self.STATE_ATTR)
 
181
 
 
182
    @util.hybridmethod
 
183
    def dict_getter(self):
 
184
        return attrgetter('__dict__')
175
185
 
176
186
    def instrument_attribute(self, key, inst, propagated=False):
177
187
        if propagated:
196
206
                        yield m
197
207
 
198
208
    def post_configure_attribute(self, key):
199
 
        instrumentation_registry.dispatch.\
 
209
        _instrumentation_factory.dispatch.\
200
210
                attribute_instrument(self.class_, key, self[key])
201
211
 
202
212
    def uninstrument_attribute(self, key, propagated=False):
209
219
            del self.local_attrs[key]
210
220
            self.uninstall_descriptor(key)
211
221
        del self[key]
212
 
        if key in self.mutable_attributes:
213
 
            self.mutable_attributes.remove(key)
214
222
        for cls in self.class_.__subclasses__():
215
223
            manager = manager_of_class(cls)
216
224
            if manager:
310
318
            setattr(instance, self.STATE_ATTR, state)
311
319
            return state
312
320
 
313
 
    def state_getter(self):
314
 
        """Return a (instance) -> InstanceState callable.
315
 
 
316
 
        "state getter" callables should raise either KeyError or
317
 
        AttributeError if no InstanceState could be found for the
318
 
        instance.
319
 
        """
320
 
 
321
 
        return attrgetter(self.STATE_ATTR)
322
 
 
323
 
    def dict_getter(self):
324
 
        return attrgetter('__dict__')
325
 
 
326
321
    def has_state(self, instance):
327
322
        return hasattr(instance, self.STATE_ATTR)
328
323
 
338
333
        return '<%s of %r at %x>' % (
339
334
            self.__class__.__name__, self.class_, id(self))
340
335
 
341
 
class _ClassInstrumentationAdapter(ClassManager):
342
 
    """Adapts a user-defined InstrumentationManager to a ClassManager."""
343
 
 
344
 
    def __init__(self, class_, override, **kw):
345
 
        self._adapted = override
346
 
        self._get_state = self._adapted.state_getter(class_)
347
 
        self._get_dict = self._adapted.dict_getter(class_)
348
 
 
349
 
        ClassManager.__init__(self, class_, **kw)
350
 
 
351
 
    def manage(self):
352
 
        self._adapted.manage(self.class_, self)
353
 
 
354
 
    def dispose(self):
355
 
        self._adapted.dispose(self.class_)
356
 
 
357
 
    def manager_getter(self):
358
 
        return self._adapted.manager_getter(self.class_)
359
 
 
360
 
    def instrument_attribute(self, key, inst, propagated=False):
361
 
        ClassManager.instrument_attribute(self, key, inst, propagated)
362
 
        if not propagated:
363
 
            self._adapted.instrument_attribute(self.class_, key, inst)
364
 
 
365
 
    def post_configure_attribute(self, key):
366
 
        super(_ClassInstrumentationAdapter, self).post_configure_attribute(key)
367
 
        self._adapted.post_configure_attribute(self.class_, key, self[key])
368
 
 
369
 
    def install_descriptor(self, key, inst):
370
 
        self._adapted.install_descriptor(self.class_, key, inst)
371
 
 
372
 
    def uninstall_descriptor(self, key):
373
 
        self._adapted.uninstall_descriptor(self.class_, key)
374
 
 
375
 
    def install_member(self, key, implementation):
376
 
        self._adapted.install_member(self.class_, key, implementation)
377
 
 
378
 
    def uninstall_member(self, key):
379
 
        self._adapted.uninstall_member(self.class_, key)
380
 
 
381
 
    def instrument_collection_class(self, key, collection_class):
382
 
        return self._adapted.instrument_collection_class(
383
 
            self.class_, key, collection_class)
384
 
 
385
 
    def initialize_collection(self, key, state, factory):
386
 
        delegate = getattr(self._adapted, 'initialize_collection', None)
387
 
        if delegate:
388
 
            return delegate(key, state, factory)
389
 
        else:
390
 
            return ClassManager.initialize_collection(self, key,
391
 
                                                        state, factory)
392
 
 
393
 
    def new_instance(self, state=None):
394
 
        instance = self.class_.__new__(self.class_)
395
 
        self.setup_instance(instance, state)
396
 
        return instance
397
 
 
398
 
    def _new_state_if_none(self, instance):
399
 
        """Install a default InstanceState if none is present.
400
 
 
401
 
        A private convenience method used by the __init__ decorator.
402
 
        """
403
 
        if self.has_state(instance):
404
 
            return False
405
 
        else:
406
 
            return self.setup_instance(instance)
407
 
 
408
 
    def setup_instance(self, instance, state=None):
409
 
        self._adapted.initialize_instance_dict(self.class_, instance)
410
 
 
411
 
        if state is None:
412
 
            state = self._state_constructor(instance, self)
413
 
 
414
 
        # the given instance is assumed to have no state
415
 
        self._adapted.install_state(self.class_, instance, state)
416
 
        return state
417
 
 
418
 
    def teardown_instance(self, instance):
419
 
        self._adapted.remove_state(self.class_, instance)
420
 
 
421
 
    def has_state(self, instance):
422
 
        try:
423
 
            state = self._get_state(instance)
424
 
        except exc.NO_STATE:
425
 
            return False
426
 
        else:
427
 
            return True
428
 
 
429
 
    def state_getter(self):
430
 
        return self._get_state
431
 
 
432
 
    def dict_getter(self):
433
 
        return self._get_dict
434
 
 
435
 
def register_class(class_, **kw):
 
336
 
 
337
class InstrumentationFactory(object):
 
338
    """Factory for new ClassManager instances."""
 
339
 
 
340
    dispatch = event.dispatcher(events.InstrumentationEvents)
 
341
 
 
342
    def create_manager_for_cls(self, class_):
 
343
        assert class_ is not None
 
344
        assert manager_of_class(class_) is None
 
345
 
 
346
        # give a more complicated subclass
 
347
        # a chance to do what it wants here
 
348
        manager, factory = self._locate_extended_factory(class_)
 
349
 
 
350
        if factory is None:
 
351
            factory = ClassManager
 
352
            manager = factory(class_)
 
353
 
 
354
        self._check_conflicts(class_, factory)
 
355
 
 
356
        manager.factory = factory
 
357
 
 
358
        self.dispatch.class_instrument(class_)
 
359
        return manager
 
360
 
 
361
    def _locate_extended_factory(self, class_):
 
362
        """Overridden by a subclass to do an extended lookup."""
 
363
        return None, None
 
364
 
 
365
    def _check_conflicts(self, class_, factory):
 
366
        """Overridden by a subclass to test for conflicting factories."""
 
367
        return
 
368
 
 
369
    def unregister(self, class_):
 
370
        manager = manager_of_class(class_)
 
371
        manager.unregister()
 
372
        manager.dispose()
 
373
        self.dispatch.class_uninstrument(class_)
 
374
        if ClassManager.MANAGER_ATTR in class_.__dict__:
 
375
            delattr(class_, ClassManager.MANAGER_ATTR)
 
376
 
 
377
# this attribute is replaced by sqlalchemy.ext.instrumentation
 
378
# when importred.
 
379
_instrumentation_factory = InstrumentationFactory()
 
380
 
 
381
 
 
382
def register_class(class_):
436
383
    """Register class instrumentation.
437
384
 
438
385
    Returns the existing or newly created class manager.
 
386
 
439
387
    """
440
388
 
441
389
    manager = manager_of_class(class_)
442
390
    if manager is None:
443
 
        manager = _create_manager_for_cls(class_, **kw)
 
391
        manager = _instrumentation_factory.create_manager_for_cls(class_)
444
392
    return manager
445
393
 
 
394
 
446
395
def unregister_class(class_):
447
396
    """Unregister class instrumentation."""
448
397
 
449
 
    instrumentation_registry.unregister(class_)
 
398
    _instrumentation_factory.unregister(class_)
450
399
 
451
400
 
452
401
def is_instrumented(instance, key):
460
409
    return manager_of_class(instance.__class__).\
461
410
                        is_instrumented(key, search=True)
462
411
 
463
 
class InstrumentationRegistry(object):
464
 
    """Private instrumentation registration singleton.
465
 
 
466
 
    All classes are routed through this registry
467
 
    when first instrumented, however the InstrumentationRegistry
468
 
    is not actually needed unless custom ClassManagers are in use.
469
 
 
470
 
    """
471
 
 
472
 
    _manager_finders = weakref.WeakKeyDictionary()
473
 
    _state_finders = util.WeakIdentityMapping()
474
 
    _dict_finders = util.WeakIdentityMapping()
475
 
    _extended = False
476
 
 
477
 
    dispatch = event.dispatcher(events.InstrumentationEvents)
478
 
 
479
 
    def create_manager_for_cls(self, class_, **kw):
480
 
        assert class_ is not None
481
 
        assert manager_of_class(class_) is None
482
 
 
483
 
        for finder in instrumentation_finders:
484
 
            factory = finder(class_)
485
 
            if factory is not None:
486
 
                break
487
 
        else:
488
 
            factory = ClassManager
489
 
 
490
 
        existing_factories = self._collect_management_factories_for(class_).\
491
 
                                difference([factory])
492
 
        if existing_factories:
493
 
            raise TypeError(
494
 
                "multiple instrumentation implementations specified "
495
 
                "in %s inheritance hierarchy: %r" % (
496
 
                    class_.__name__, list(existing_factories)))
497
 
 
498
 
        manager = factory(class_)
499
 
        if not isinstance(manager, ClassManager):
500
 
            manager = _ClassInstrumentationAdapter(class_, manager)
501
 
 
502
 
        if factory != ClassManager and not self._extended:
503
 
            # somebody invoked a custom ClassManager.
504
 
            # reinstall global "getter" functions with the more
505
 
            # expensive ones.
506
 
            self._extended = True
507
 
            _install_lookup_strategy(self)
508
 
 
509
 
        manager.factory = factory
510
 
        self._manager_finders[class_] = manager.manager_getter()
511
 
        self._state_finders[class_] = manager.state_getter()
512
 
        self._dict_finders[class_] = manager.dict_getter()
513
 
 
514
 
        self.dispatch.class_instrument(class_)
515
 
 
516
 
        return manager
517
 
 
518
 
    def _collect_management_factories_for(self, cls):
519
 
        """Return a collection of factories in play or specified for a
520
 
        hierarchy.
521
 
 
522
 
        Traverses the entire inheritance graph of a cls and returns a
523
 
        collection of instrumentation factories for those classes. Factories
524
 
        are extracted from active ClassManagers, if available, otherwise
525
 
        instrumentation_finders is consulted.
526
 
 
527
 
        """
528
 
        hierarchy = util.class_hierarchy(cls)
529
 
        factories = set()
530
 
        for member in hierarchy:
531
 
            manager = manager_of_class(member)
532
 
            if manager is not None:
533
 
                factories.add(manager.factory)
534
 
            else:
535
 
                for finder in instrumentation_finders:
536
 
                    factory = finder(member)
537
 
                    if factory is not None:
538
 
                        break
539
 
                else:
540
 
                    factory = None
541
 
                factories.add(factory)
542
 
        factories.discard(None)
543
 
        return factories
544
 
 
545
 
    def manager_of_class(self, cls):
546
 
        # this is only called when alternate instrumentation
547
 
        # has been established
548
 
        if cls is None:
549
 
            return None
550
 
        try:
551
 
            finder = self._manager_finders[cls]
552
 
        except KeyError:
553
 
            return None
554
 
        else:
555
 
            return finder(cls)
556
 
 
557
 
    def state_of(self, instance):
558
 
        # this is only called when alternate instrumentation
559
 
        # has been established
560
 
        if instance is None:
561
 
            raise AttributeError("None has no persistent state.")
562
 
        try:
563
 
            return self._state_finders[instance.__class__](instance)
564
 
        except KeyError:
565
 
            raise AttributeError("%r is not instrumented" %
566
 
                                    instance.__class__)
567
 
 
568
 
    def dict_of(self, instance):
569
 
        # this is only called when alternate instrumentation
570
 
        # has been established
571
 
        if instance is None:
572
 
            raise AttributeError("None has no persistent state.")
573
 
        try:
574
 
            return self._dict_finders[instance.__class__](instance)
575
 
        except KeyError:
576
 
            raise AttributeError("%r is not instrumented" %
577
 
                                    instance.__class__)
578
 
 
579
 
    def unregister(self, class_):
580
 
        if class_ in self._manager_finders:
581
 
            manager = self.manager_of_class(class_)
582
 
            self.dispatch.class_uninstrument(class_)
583
 
            manager.unregister()
584
 
            manager.dispose()
585
 
            del self._manager_finders[class_]
586
 
            del self._state_finders[class_]
587
 
            del self._dict_finders[class_]
588
 
        if ClassManager.MANAGER_ATTR in class_.__dict__:
589
 
            delattr(class_, ClassManager.MANAGER_ATTR)
590
 
 
591
 
instrumentation_registry = InstrumentationRegistry()
592
 
 
593
 
 
594
 
def _install_lookup_strategy(implementation):
595
 
    """Replace global class/object management functions
596
 
    with either faster or more comprehensive implementations,
597
 
    based on whether or not extended class instrumentation
598
 
    has been detected.
599
 
 
600
 
    This function is called only by InstrumentationRegistry()
601
 
    and unit tests specific to this behavior.
602
 
 
603
 
    """
604
 
    global instance_state, instance_dict, manager_of_class
605
 
    if implementation is util.symbol('native'):
606
 
        instance_state = attrgetter(ClassManager.STATE_ATTR)
607
 
        instance_dict = attrgetter("__dict__")
608
 
        def manager_of_class(cls):
609
 
            return cls.__dict__.get(ClassManager.MANAGER_ATTR, None)
610
 
    else:
611
 
        instance_state = instrumentation_registry.state_of
612
 
        instance_dict = instrumentation_registry.dict_of
613
 
        manager_of_class = instrumentation_registry.manager_of_class
614
 
    attributes.instance_state = instance_state
615
 
    attributes.instance_dict = instance_dict
616
 
    attributes.manager_of_class = manager_of_class
617
 
 
618
 
_create_manager_for_cls = instrumentation_registry.create_manager_for_cls
619
 
 
620
 
# Install default "lookup" strategies.  These are basically
621
 
# very fast attrgetters for key attributes.
622
 
# When a custom ClassManager is installed, more expensive per-class
623
 
# strategies are copied over these.
624
 
_install_lookup_strategy(util.symbol('native'))
625
 
 
626
 
 
627
 
def find_native_user_instrumentation_hook(cls):
628
 
    """Find user-specified instrumentation management for a class."""
629
 
    return getattr(cls, INSTRUMENTATION_MANAGER, None)
630
 
instrumentation_finders.append(find_native_user_instrumentation_hook)
 
412
# these attributes are replaced by sqlalchemy.ext.instrumentation
 
413
# when a non-standard InstrumentationManager class is first
 
414
# used to instrument a class.
 
415
instance_state = _default_state_getter = ClassManager.state_getter()
 
416
 
 
417
instance_dict = _default_dict_getter = ClassManager.dict_getter()
 
418
 
 
419
manager_of_class = _default_manager_getter = ClassManager.manager_getter()
 
420
 
631
421
 
632
422
def _generate_init(class_, class_manager):
633
423
    """Build an __init__ decorator that triggers ClassManager events."""
647
437
def __init__(%(apply_pos)s):
648
438
    new_state = class_manager._new_state_if_none(%(self_arg)s)
649
439
    if new_state:
650
 
        return new_state.initialize_instance(%(apply_kw)s)
 
440
        return new_state._initialize_instance(%(apply_kw)s)
651
441
    else:
652
442
        return original__init__(%(apply_kw)s)
653
443
"""