Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes15kdownloads
instrumentation.py451 linesDownload Raw Back to ext
1# ext/instrumentation.py
2# Copyright (C) 2005-2026 the SQLAlchemy authors and contributors
3# <see AUTHORS file>
4#
5# This module is part of SQLAlchemy and is released under
6# the MIT License: https://www.opensource.org/licenses/mit-license.php
7# mypy: ignore-errors
8
9"""Extensible class instrumentation.
10
11The :mod:`sqlalchemy.ext.instrumentation` package provides for alternate
12systems of class instrumentation within the ORM.  Class instrumentation
13refers to how the ORM places attributes on the class which maintain
14data and track changes to that data, as well as event hooks installed
15on the class.
16
17.. note::
18    The extension package is provided for the benefit of integration
19    with other object management packages, which already perform
20    their own instrumentation.  It is not intended for general use.
21
22For examples of how the instrumentation extension is used,
23see the example :ref:`examples_instrumentation`.
24
25"""
26import weakref
27
28from .. import util
29from ..orm import attributes
30from ..orm import base as orm_base
31from ..orm import collections
32from ..orm import exc as orm_exc
33from ..orm import instrumentation as orm_instrumentation
34from ..orm import util as orm_util
35from ..orm.instrumentation import _default_dict_getter
36from ..orm.instrumentation import _default_manager_getter
37from ..orm.instrumentation import _default_opt_manager_getter
38from ..orm.instrumentation import _default_state_getter
39from ..orm.instrumentation import ClassManager
40from ..orm.instrumentation import InstrumentationFactory
41
42
43INSTRUMENTATION_MANAGER = "__sa_instrumentation_manager__"
44"""Attribute, elects custom instrumentation when present on a mapped class.
45
46Allows a class to specify a slightly or wildly different technique for
47tracking changes made to mapped attributes and collections.
48
49Only one instrumentation implementation is allowed in a given object
50inheritance hierarchy.
51
52The value of this attribute must be a callable and will be passed a class
53object.  The callable must return one of:
54
55  - An instance of an :class:`.InstrumentationManager` or subclass
56  - An object implementing all or some of InstrumentationManager (TODO)
57  - A dictionary of callables, implementing all or some of the above (TODO)
58  - An instance of a :class:`.ClassManager` or subclass
59
60This attribute is consulted by SQLAlchemy instrumentation
61resolution, once the :mod:`sqlalchemy.ext.instrumentation` module
62has been imported.  If custom finders are installed in the global
63instrumentation_finders list, they may or may not choose to honor this
64attribute.
65
66"""
67
68
69def find_native_user_instrumentation_hook(cls):
70    """Find user-specified instrumentation management for a class."""
71    return getattr(cls, INSTRUMENTATION_MANAGER, None)
72
73
74instrumentation_finders = [find_native_user_instrumentation_hook]
75"""An extensible sequence of callables which return instrumentation
76implementations
77
78When a class is registered, each callable will be passed a class object.
79If None is returned, the
80next finder in the sequence is consulted.  Otherwise the return must be an
81instrumentation factory that follows the same guidelines as
82sqlalchemy.ext.instrumentation.INSTRUMENTATION_MANAGER.
83
84By default, the only finder is find_native_user_instrumentation_hook, which
85searches for INSTRUMENTATION_MANAGER.  If all finders return None, standard
86ClassManager instrumentation is used.
87
88"""
89
90
91class ExtendedInstrumentationRegistry(InstrumentationFactory):
92    """Extends :class:`.InstrumentationFactory` with additional
93    bookkeeping, to accommodate multiple types of
94    class managers.
95
96    """
97
98    _manager_finders = weakref.WeakKeyDictionary()
99    _state_finders = weakref.WeakKeyDictionary()
100    _dict_finders = weakref.WeakKeyDictionary()
101    _extended = False
102
103    def _locate_extended_factory(self, class_):
104        for finder in instrumentation_finders:
105            factory = finder(class_)
106            if factory is not None:
107                manager = self._extended_class_manager(class_, factory)
108                return manager, factory
109        else:
110            return None, None
111
112    def _check_conflicts(self, class_, factory):
113        existing_factories = self._collect_management_factories_for(
114            class_
115        ).difference([factory])
116        if existing_factories:
117            raise TypeError(
118                "multiple instrumentation implementations specified "
119                "in %s inheritance hierarchy: %r"
120                % (class_.__name__, list(existing_factories))
121            )
122
123    def _extended_class_manager(self, class_, factory):
124        manager = factory(class_)
125        if not isinstance(manager, ClassManager):
126            manager = _ClassInstrumentationAdapter(class_, manager)
127
128        if factory != ClassManager and not self._extended:
129            # somebody invoked a custom ClassManager.
130            # reinstall global "getter" functions with the more
131            # expensive ones.
132            self._extended = True
133            _install_instrumented_lookups()
134
135        self._manager_finders[class_] = manager.manager_getter()
136        self._state_finders[class_] = manager.state_getter()
137        self._dict_finders[class_] = manager.dict_getter()
138        return manager
139
140    def _collect_management_factories_for(self, cls):
141        """Return a collection of factories in play or specified for a
142        hierarchy.
143
144        Traverses the entire inheritance graph of a cls and returns a
145        collection of instrumentation factories for those classes. Factories
146        are extracted from active ClassManagers, if available, otherwise
147        instrumentation_finders is consulted.
148
149        """
150        hierarchy = util.class_hierarchy(cls)
151        factories = set()
152        for member in hierarchy:
153            manager = self.opt_manager_of_class(member)
154            if manager is not None:
155                factories.add(manager.factory)
156            else:
157                for finder in instrumentation_finders:
158                    factory = finder(member)
159                    if factory is not None:
160                        break
161                else:
162                    factory = None
163                factories.add(factory)
164        factories.discard(None)
165        return factories
166
167    def unregister(self, class_):
168        super().unregister(class_)
169        if class_ in self._manager_finders:
170            del self._manager_finders[class_]
171            del self._state_finders[class_]
172            del self._dict_finders[class_]
173
174    def opt_manager_of_class(self, cls):
175        try:
176            finder = self._manager_finders.get(
177                cls, _default_opt_manager_getter
178            )
179        except TypeError:
180            # due to weakref lookup on invalid object
181            return None
182        else:
183            return finder(cls)
184
185    def manager_of_class(self, cls):
186        try:
187            finder = self._manager_finders.get(cls, _default_manager_getter)
188        except TypeError:
189            # due to weakref lookup on invalid object
190            raise orm_exc.UnmappedClassError(
191                cls, f"Can't locate an instrumentation manager for class {cls}"
192            )
193        else:
194            manager = finder(cls)
195            if manager is None:
196                raise orm_exc.UnmappedClassError(
197                    cls,
198                    f"Can't locate an instrumentation manager for class {cls}",
199                )
200            return manager
201
202    def state_of(self, instance):
203        if instance is None:
204            raise AttributeError("None has no persistent state.")
205        return self._state_finders.get(
206            instance.__class__, _default_state_getter
207        )(instance)
208
209    def dict_of(self, instance):
210        if instance is None:
211            raise AttributeError("None has no persistent state.")
212        return self._dict_finders.get(
213            instance.__class__, _default_dict_getter
214        )(instance)
215
216
217orm_instrumentation._instrumentation_factory = _instrumentation_factory = (
218    ExtendedInstrumentationRegistry()
219)
220orm_instrumentation.instrumentation_finders = instrumentation_finders
221
222
223class InstrumentationManager:
224    """User-defined class instrumentation extension.
225
226    :class:`.InstrumentationManager` can be subclassed in order
227    to change
228    how class instrumentation proceeds. This class exists for
229    the purposes of integration with other object management
230    frameworks which would like to entirely modify the
231    instrumentation methodology of the ORM, and is not intended
232    for regular usage.  For interception of class instrumentation
233    events, see :class:`.InstrumentationEvents`.
234
235    The API for this class should be considered as semi-stable,
236    and may change slightly with new releases.
237
238    """
239
240    # r4361 added a mandatory (cls) constructor to this interface.
241    # given that, perhaps class_ should be dropped from all of these
242    # signatures.
243
244    def __init__(self, class_):
245        pass
246
247    def manage(self, class_, manager):
248        setattr(class_, "_default_class_manager", manager)
249
250    def unregister(self, class_, manager):
251        delattr(class_, "_default_class_manager")
252
253    def manager_getter(self, class_):
254        def get(cls):
255            return cls._default_class_manager
256
257        return get
258
259    def instrument_attribute(self, class_, key, inst):
260        pass
261
262    def post_configure_attribute(self, class_, key, inst):
263        pass
264
265    def install_descriptor(self, class_, key, inst):
266        setattr(class_, key, inst)
267
268    def uninstall_descriptor(self, class_, key):
269        delattr(class_, key)
270
271    def install_member(self, class_, key, implementation):
272        setattr(class_, key, implementation)
273
274    def uninstall_member(self, class_, key):
275        delattr(class_, key)
276
277    def instrument_collection_class(self, class_, key, collection_class):
278        return collections.prepare_instrumentation(collection_class)
279
280    def get_instance_dict(self, class_, instance):
281        return instance.__dict__
282
283    def initialize_instance_dict(self, class_, instance):
284        pass
285
286    def install_state(self, class_, instance, state):
287        setattr(instance, "_default_state", state)
288
289    def remove_state(self, class_, instance):
290        delattr(instance, "_default_state")
291
292    def state_getter(self, class_):
293        return lambda instance: getattr(instance, "_default_state")
294
295    def dict_getter(self, class_):
296        return lambda inst: self.get_instance_dict(class_, inst)
297
298
299class _ClassInstrumentationAdapter(ClassManager):
300    """Adapts a user-defined InstrumentationManager to a ClassManager."""
301
302    def __init__(self, class_, override):
303        self._adapted = override
304        self._get_state = self._adapted.state_getter(class_)
305        self._get_dict = self._adapted.dict_getter(class_)
306
307        ClassManager.__init__(self, class_)
308
309    def manage(self):
310        self._adapted.manage(self.class_, self)
311
312    def unregister(self):
313        self._adapted.unregister(self.class_, self)
314
315    def manager_getter(self):
316        return self._adapted.manager_getter(self.class_)
317
318    def instrument_attribute(self, key, inst, propagated=False):
319        ClassManager.instrument_attribute(self, key, inst, propagated)
320        if not propagated:
321            self._adapted.instrument_attribute(self.class_, key, inst)
322
323    def post_configure_attribute(self, key):
324        super().post_configure_attribute(key)
325        self._adapted.post_configure_attribute(self.class_, key, self[key])
326
327    def install_descriptor(self, key, inst):
328        self._adapted.install_descriptor(self.class_, key, inst)
329
330    def uninstall_descriptor(self, key):
331        self._adapted.uninstall_descriptor(self.class_, key)
332
333    def install_member(self, key, implementation):
334        self._adapted.install_member(self.class_, key, implementation)
335
336    def uninstall_member(self, key):
337        self._adapted.uninstall_member(self.class_, key)
338
339    def instrument_collection_class(self, key, collection_class):
340        return self._adapted.instrument_collection_class(
341            self.class_, key, collection_class
342        )
343
344    def initialize_collection(self, key, state, factory):
345        delegate = getattr(self._adapted, "initialize_collection", None)
346        if delegate:
347            return delegate(key, state, factory)
348        else:
349            return ClassManager.initialize_collection(
350                self, key, state, factory
351            )
352
353    def new_instance(self, state=None):
354        instance = self.class_.__new__(self.class_)
355        self.setup_instance(instance, state)
356        return instance
357
358    def _new_state_if_none(self, instance):
359        """Install a default InstanceState if none is present.
360
361        A private convenience method used by the __init__ decorator.
362        """
363        if self.has_state(instance):
364            return False
365        else:
366            return self.setup_instance(instance)
367
368    def setup_instance(self, instance, state=None):
369        self._adapted.initialize_instance_dict(self.class_, instance)
370
371        if state is None:
372            state = self._state_constructor(instance, self)
373
374        # the given instance is assumed to have no state
375        self._adapted.install_state(self.class_, instance, state)
376        return state
377
378    def teardown_instance(self, instance):
379        self._adapted.remove_state(self.class_, instance)
380
381    def has_state(self, instance):
382        try:
383            self._get_state(instance)
384        except orm_exc.NO_STATE:
385            return False
386        else:
387            return True
388
389    def state_getter(self):
390        return self._get_state
391
392    def dict_getter(self):
393        return self._get_dict
394
395
396def _install_instrumented_lookups():
397    """Replace global class/object management functions
398    with ExtendedInstrumentationRegistry implementations, which
399    allow multiple types of class managers to be present,
400    at the cost of performance.
401
402    This function is called only by ExtendedInstrumentationRegistry
403    and unit tests specific to this behavior.
404
405    The _reinstall_default_lookups() function can be called
406    after this one to re-establish the default functions.
407
408    """
409    _install_lookups(
410        dict(
411            instance_state=_instrumentation_factory.state_of,
412            instance_dict=_instrumentation_factory.dict_of,
413            manager_of_class=_instrumentation_factory.manager_of_class,
414            opt_manager_of_class=_instrumentation_factory.opt_manager_of_class,
415        )
416    )
417
418
419def _reinstall_default_lookups():
420    """Restore simplified lookups."""
421    _install_lookups(
422        dict(
423            instance_state=_default_state_getter,
424            instance_dict=_default_dict_getter,
425            manager_of_class=_default_manager_getter,
426            opt_manager_of_class=_default_opt_manager_getter,
427        )
428    )
429    _instrumentation_factory._extended = False
430
431
432def _install_lookups(lookups):
433    global instance_state, instance_dict
434    global manager_of_class, opt_manager_of_class
435    instance_state = lookups["instance_state"]
436    instance_dict = lookups["instance_dict"]
437    manager_of_class = lookups["manager_of_class"]
438    opt_manager_of_class = lookups["opt_manager_of_class"]
439    orm_base.instance_state = attributes.instance_state = (
440        orm_instrumentation.instance_state
441    ) = instance_state
442    orm_base.instance_dict = attributes.instance_dict = (
443        orm_instrumentation.instance_dict
444    ) = instance_dict
445    orm_base.manager_of_class = attributes.manager_of_class = (
446        orm_instrumentation.manager_of_class
447    ) = manager_of_class
448    orm_base.opt_manager_of_class = orm_util.opt_manager_of_class = (
449        attributes.opt_manager_of_class
450    ) = orm_instrumentation.opt_manager_of_class = opt_manager_of_class
451 
codekingpro/portable-devtools · Team Ai