Completed
Push — master ( 78e743...e8dc26 )
by Ionel Cristian
56s
created

src.aspectlib.__init__()   B

Complexity

Conditions 5

Size

Total Lines 6

Duplication

Lines 0
Ratio 0 %
Metric Value
cc 5
dl 0
loc 6
rs 8.5454
1
from __future__ import print_function
2
3
import re
4
import sys
5
import warnings
6
from collections import deque
7
from functools import partial
8
from inspect import isclass
9
from inspect import isfunction
10
from inspect import isgenerator
11
from inspect import isgeneratorfunction
12
from inspect import ismethod
13
from inspect import ismethoddescriptor
14
from inspect import ismodule
15
from inspect import isroutine
16
from logging import getLogger
17
18
from .utils import PY2
19
from .utils import PY3
20
from .utils import Sentinel
21
from .utils import basestring
22
from .utils import force_bind
23
from .utils import logf
24
from .utils import make_method_matcher
25
from .utils import mimic
26
27
try:
28
    from types import InstanceType
29
except ImportError:
30
    InstanceType = None
31
32
try:
33
    import __builtin__
34
except ImportError:
35
    import builtins as __builtin__  # pylint: disable=F0401
36
37
try:
38
    from types import ClassType
39
except ImportError:
40
    ClassType = type
41
42
43
__all__ = 'weave', 'Aspect', 'Proceed', 'Return', 'ALL_METHODS', 'NORMAL_METHODS', 'ABSOLUTELY_ALL_METHODS'
44
__version__ = '1.3.3'
45
46
logger = getLogger(__name__)
47
logdebug = logf(logger.debug)
48
logexception = logf(logger.exception)
49
50
UNSPECIFIED = Sentinel('UNSPECIFIED')
51
ABSOLUTELLY_ALL_METHODS = re.compile('.*')
52
ABSOLUTELY_ALL_METHODS = ABSOLUTELLY_ALL_METHODS
53
ALL_METHODS = re.compile('(?!__getattribute__$)')
54
NORMAL_METHODS = re.compile('(?!__.*__$)')
55
VALID_IDENTIFIER = re.compile(r'^[^\W\d]\w*$', re.UNICODE if PY3 else 0)
56
57
58
class UnacceptableAdvice(RuntimeError):
59
    pass
60
61
62
class ExpectedGenerator(TypeError):
63
    pass
64
65
66
class ExpectedGeneratorFunction(ExpectedGenerator):
67
    pass
68
69
70
class ExpectedAdvice(TypeError):
71
    pass
72
73
74
class UnsupportedType(TypeError):
75
    pass
76
77
78
class Proceed(object):
79
    """
80
    Instruction for calling the decorated function. Can be used multiple times.
81
82
    If not used as an instance then the default args and kwargs are used.
83
    """
84
    __slots__ = 'args', 'kwargs'
85
86
    def __init__(self, *args, **kwargs):
87
        self.args = args
88
        self.kwargs = kwargs
89
90
91
class Return(object):
92
    """
93
    Instruction for returning a *optional* value.
94
95
    If not used as an instance then ``None`` is returned.
96
    """
97
    __slots__ = 'value',
98
99
    def __init__(self, value):
100
        self.value = value
101
102
103
class Aspect(object):
104
    """
105
    Container for the advice yielding generator. Can be used as a decorator on other function to change behavior
106
    according to the advices yielded from the generator.
107
108
    Args:
109
        advising_function (generator function): A generator function that yields :ref:`advices`.
110
        bind (bool): A convenience flag so you can access the cutpoint function (you'll get it as an argument).
111
112
    Usage::
113
114
        >>> @Aspect
115
        ... def my_decorator(*args, **kwargs):
116
        ...     print("Got called with args: %s kwargs: %s" % (args, kwargs))
117
        ...     result = yield
118
        ...     print(" ... and the result is: %s" % (result,))
119
        >>> @my_decorator
120
        ... def foo(a, b, c=1):
121
        ...     print((a, b, c))
122
        >>> foo(1, 2, c=3)
123
        Got called with args: (1, 2) kwargs: {'c': 3}
124
        (1, 2, 3)
125
         ... and the result is: None
126
127
    Normally you don't have access to the cutpoints (the functions you're going to use the aspect/decorator on) because
128
    you don't and should not call them directly. There are situations where you'd want to get the name or other data
129
    from the function. This is where you use the ``bind=True`` option::
130
131
        >>> @Aspect(bind=True)
132
        ... def my_decorator(cutpoint, *args, **kwargs):
133
        ...     print("`%s` got called with args: %s kwargs: %s" % (cutpoint.__name__, args, kwargs))
134
        ...     result = yield
135
        ...     print(" ... and the result is: %s" % (result,))
136
        >>> @my_decorator
137
        ... def foo(a, b, c=1):
138
        ...     print((a, b, c))
139
        >>> foo(1, 2, c=3)
140
        `foo` got called with args: (1, 2) kwargs: {'c': 3}
141
        (1, 2, 3)
142
         ... and the result is: None
143
144
    """
145
    __slots__ = 'advising_function', 'bind'
146
147
    def __new__(cls, advising_function=UNSPECIFIED, bind=False):
148
        if advising_function is UNSPECIFIED:
149
            return partial(cls, bind=bind)
150
        else:
151
            self = super(Aspect, cls).__new__(cls)
152
            self.__init__(advising_function, bind)
153
            return self
154
155
    def __init__(self, advising_function, bind=False):
156
        if not isgeneratorfunction(advising_function):
157
            raise ExpectedGeneratorFunction("advising_function %s must be a generator function." % advising_function)
158
        self.advising_function = advising_function
159
        self.bind = bind
160
161
    def __call__(self, cutpoint_function):
162
        if isgeneratorfunction(cutpoint_function):
163
            if PY3:
164
                from aspectlib.py3support import decorate_advising_generator_py3
165
                return decorate_advising_generator_py3(self.advising_function, cutpoint_function, self.bind)
166
            else:
167
                def advising_generator_wrapper(*args, **kwargs):
168
                    if self.bind:
169
                        advisor = self.advising_function(cutpoint_function, *args, **kwargs)
170
                    else:
171
                        advisor = self.advising_function(*args, **kwargs)
172
                    if not isgenerator(advisor):
173
                        raise ExpectedGenerator("advising_function %s did not return a generator." % self.advising_function)
174
                    try:
175
                        advice = next(advisor)
176
                        while True:
177
                            logdebug('Got advice %r from %s', advice, self.advising_function)
178
                            if advice is Proceed or advice is None or isinstance(advice, Proceed):
179
                                if isinstance(advice, Proceed):
180
                                    args = advice.args
181
                                    kwargs = advice.kwargs
182
                                gen = cutpoint_function(*args, **kwargs)
183
                                try:
184
                                    try:
185
                                        generated = next(gen)
186
                                    except StopIteration as exc:
187
                                        logexception("The cutpoint has been exhausted (early).")
188
                                        result = exc.args
189
                                        if result:
190
                                            if len(result) == 1:
191
                                                result = exc.args[0]
192
                                        else:
193
                                            result = None
194
                                    else:
195
                                        while True:
196
                                            try:
197
                                                sent = yield generated
198
                                            except GeneratorExit as exc:
199
                                                logexception("Got GeneratorExit while consuming the cutpoint")
200
                                                gen.close()
201
                                                raise exc
202
                                            except BaseException as exc:
203
                                                logexception("Got exception %r. Throwing it the cutpoint", exc)
204
                                                try:
205
                                                    generated = gen.throw(*sys.exc_info())
206
                                                except StopIteration as exc:
207
                                                    logexception("The cutpoint has been exhausted.")
208
                                                    result = exc.args
209
                                                    if result:
210
                                                        if len(result) == 1:
211
                                                            result = exc.args[0]
212
                                                    else:
213
                                                        result = None
214
                                                    break
215
                                            else:
216
                                                try:
217
                                                    if sent is None:
218
                                                        generated = next(gen)
219
                                                    else:
220
                                                        generated = gen.send(sent)
221
                                                except StopIteration as exc:
222
                                                    logexception("The cutpoint has been exhausted.")
223
                                                    result = exc.args
224
                                                    if result:
225
                                                        if len(result) == 1:
226
                                                            result = exc.args[0]
227
                                                    else:
228
                                                        result = None
229
                                                    break
230
                                except BaseException as exc:
231
                                    advice = advisor.throw(*sys.exc_info())
232
                                else:
233
                                    try:
234
                                        advice = advisor.send(result)
235
                                    except StopIteration:
236
                                        raise StopIteration(result)
237
                                finally:
238
                                    gen.close()
239
                            elif advice is Return:
240
                                return
241
                            elif isinstance(advice, Return):
242
                                raise StopIteration(advice.value)
243
                            else:
244
                                raise UnacceptableAdvice("Unknown advice %s" % advice)
245
                    finally:
246
                        advisor.close()
247
                return mimic(advising_generator_wrapper, cutpoint_function)
248
        else:
249
            def advising_function_wrapper(*args, **kwargs):
250
                if self.bind:
251
                    advisor = self.advising_function(cutpoint_function, *args, **kwargs)
252
                else:
253
                    advisor = self.advising_function(*args, **kwargs)
254
                if not isgenerator(advisor):
255
                    raise ExpectedGenerator("advising_function %s did not return a generator." % self.advising_function)
256
                try:
257
                    advice = next(advisor)
258
                    while True:
259
                        logdebug('Got advice %r from %s', advice, self.advising_function)
260
                        if advice is Proceed or advice is None or isinstance(advice, Proceed):
261
                            if isinstance(advice, Proceed):
262
                                args = advice.args
263
                                kwargs = advice.kwargs
264
                            try:
265
                                result = cutpoint_function(*args, **kwargs)
266
                            except Exception:
267
                                advice = advisor.throw(*sys.exc_info())
268
                            else:
269
                                try:
270
                                    advice = advisor.send(result)
271
                                except StopIteration:
272
                                    return result
273
                        elif advice is Return:
274
                            return
275
                        elif isinstance(advice, Return):
276
                            return advice.value
277
                        else:
278
                            raise UnacceptableAdvice("Unknown advice %s" % advice)
279
                finally:
280
                    advisor.close()
281
            return mimic(advising_function_wrapper, cutpoint_function)
282
283
284
class Fabric(object):
285
    pass
286
287
288
class Rollback(object):
289
    """
290
    When called, rollbacks all the patches and changes the :func:`weave` has done.
291
    """
292
    __slots__ = '_rollbacks'
293
294
    def __init__(self, rollback=None):
295
        if rollback is None:
296
            self._rollbacks = []
297
        elif isinstance(rollback, (list, tuple)):
298
            self._rollbacks = rollback
299
        else:
300
            self._rollbacks = [rollback]
301
302
    def merge(self, *others):
303
        self._rollbacks.extend(others)
304
305
    def __enter__(self):
306
        return self
307
308
    def __exit__(self, *_):
309
        for rollback in self._rollbacks:
310
            rollback()
311
        del self._rollbacks[:]
312
313
    rollback = __call__ = __exit__
314
315
316
class ObjectBag(object):
317
    def __init__(self):
318
        self._objects = {}
319
320
    def has(self, obj):
321
        if id(obj) in self._objects:
322
            logdebug('  --- ObjectBag ALREADY HAS %r', obj)
323
            return True
324
        else:
325
            self._objects[id(obj)] = obj
326
            return False
327
328
BrokenBag = type('BrokenBag', (), dict(has=lambda self, obj: False))()
329
330
331
class EmptyRollback(object):
332
    def __enter__(self):
333
        return self
334
335
    def __exit__(self, *_):
336
        pass
337
338
    rollback = __call__ = __exit__
339
340
Nothing = EmptyRollback()
341
342
343
def _checked_apply(aspects, function, module=None):
344
    logdebug('  applying aspects %s to function %s.', aspects, function)
345
    if callable(aspects):
346
        wrapper = aspects(function)
347
        assert callable(wrapper), 'Aspect %s did not return a callable (it return %s).' % (aspects, wrapper)
348
    else:
349
        wrapper = function
350
        for aspect in aspects:
351
            wrapper = aspect(wrapper)
352
            assert callable(wrapper), 'Aspect %s did not return a callable (it return %s).' % (aspect, wrapper)
353
    return mimic(wrapper, function, module=module)
354
355
356
def _check_name(name):
357
    if not VALID_IDENTIFIER.match(name):
358
        raise SyntaxError(
359
            "Could not match %r to %r. It should be a string of "
360
            "letters, numbers and underscore that starts with a letter or underscore." % (
361
                name, VALID_IDENTIFIER.pattern
362
            )
363
        )
364
365
366
def weave(target, aspects, **options):
367
    """
368
    Send a message to a recipient
369
370
    Args:
371
        target (string, class, instance, function or builtin):
372
            The object to weave.
373
        aspects (:py:obj:`aspectlib.Aspect`, function decorator or list of):
374
            The aspects to apply to the object.
375
        subclasses (bool):
376
            If ``True``, subclasses of target are weaved. *Only available for classes*
377
        aliases (bool):
378
            If ``True``, aliases of target are replaced.
379
        lazy (bool):
380
            If ``True`` only target's ``__init__`` method is patched, the rest of the methods are patched after
381
            ``__init__`` is called. *Only available for classes*.
382
        methods (list or regex or string):
383
            Methods from target to patch. *Only available for classes*
384
385
    Returns:
386
        aspectlib.Rollback: An object that can rollback the patches.
387
388
    Raises:
389
        TypeError: If target is a unacceptable object, or the specified options are not available for that type of
390
            object.
391
392
    .. versionchanged:: 0.4.0
393
394
        Replaced `only_methods`, `skip_methods`, `skip_magicmethods` options with `methods`.
395
        Renamed `on_init` option to `lazy`.
396
        Added `aliases` option.
397
        Replaced `skip_subclasses` option with `subclasses`.
398
    """
399
    if not callable(aspects):
400
        if not hasattr(aspects, '__iter__'):
401
            raise ExpectedAdvice('%s must be an `Aspect` instance, a callable or an iterable of.' % aspects)
402
        for obj in aspects:
403
            if not callable(obj):
404
                raise ExpectedAdvice('%s must be an `Aspect` instance or a callable.' % obj)
405
    assert target, "Can't weave falsy value %r." % target
406
    logdebug("weave (target=%s, aspects=%s, **options=%s)", target, aspects, options)
407
408
    bag = options.setdefault('bag', ObjectBag())
409
410
    if isinstance(target, (list, tuple)):
411
        return Rollback([
412
            weave(item, aspects, **options) for item in target
413
        ])
414
    elif isinstance(target, basestring):
415
        parts = target.split('.')
416
        for part in parts:
417
            _check_name(part)
418
419
        if len(parts) == 1:
420
            __import__(part)
421
            return weave_module(sys.modules[part], aspects, **options)
422
423
        for pos in reversed(range(1, len(parts))):
424
            owner, name = '.'.join(parts[:pos]), '.'.join(parts[pos:])
425
            try:
426
                __import__(owner)
427
                owner = sys.modules[owner]
428
            except ImportError:
429
                continue
430
            else:
431
                break
432
        else:
433
            raise ImportError("Could not import %r. Last try was for %s" % (target, owner))
434
435
        if '.' in name:
436
            path, name = name.rsplit('.', 1)
437
            path = deque(path.split('.'))
438
            while path:
439
                owner = getattr(owner, path.popleft())
440
441
        logdebug("@ patching %s from %s ...", name, owner)
442
        obj = getattr(owner, name)
443
444
        if isinstance(obj, (type, ClassType)):
445
            logdebug("   .. as a class %r.", obj)
446
            return weave_class(
447
                obj, aspects,
448
                owner=owner, name=name, **options
449
            )
450
        elif callable(obj):  # or isinstance(obj, FunctionType) ??
451
            logdebug("   .. as a callable %r.", obj)
452
            if bag.has(obj):
453
                return Nothing
454
            return patch_module_function(owner, obj, aspects, force_name=name, **options)
455
        else:
456
            return weave(obj, aspects, **options)
457
458
    name = getattr(target, '__name__', None)
459
    if name and getattr(__builtin__, name, None) is target:
460
        if bag.has(target):
461
            return Nothing
462
        return patch_module_function(__builtin__, target, aspects, **options)
463
    elif PY3 and ismethod(target):
464
        if bag.has(target):
465
            return Nothing
466
        inst = target.__self__
467
        name = target.__name__
468
        logdebug("@ patching %r (%s) as instance method.", target, name)
469
        func = target.__func__
470
        setattr(inst, name, _checked_apply(aspects, func).__get__(inst, type(inst)))
471
        return Rollback(lambda: delattr(inst, name))
472
    elif PY3 and isfunction(target):
473
        if bag.has(target):
474
            return Nothing
475
        owner = __import__(target.__module__)
476
        path = deque(target.__qualname__.split('.')[:-1])
477
        while path:
478
            owner = getattr(owner, path.popleft())
479
        name = target.__name__
480
        logdebug("@ patching %r (%s) as a property.", target, name)
481
        func = owner.__dict__[name]
482
        return patch_module(owner, name, _checked_apply(aspects, func), func, **options)
483
    elif PY2 and isfunction(target):
484
        if bag.has(target):
485
            return Nothing
486
        return patch_module_function(__import__(target.__module__), target, aspects, **options)
487
    elif PY2 and ismethod(target):
488
        if target.im_self:
489
            if bag.has(target):
490
                return Nothing
491
            inst = target.im_self
492
            name = target.__name__
493
            logdebug("@ patching %r (%s) as instance method.", target, name)
494
            func = target.im_func
495
            setattr(inst, name, _checked_apply(aspects, func).__get__(inst, type(inst)))
496
            return Rollback(lambda: delattr(inst, name))
497
        else:
498
            klass = target.im_class
499
            name = target.__name__
500
            return weave(klass, aspects, methods='%s$' % name, **options)
501
    elif isclass(target):
502
        return weave_class(target, aspects, **options)
503
    elif ismodule(target):
504
        return weave_module(target, aspects, **options)
505
    elif type(target).__module__ not in ('builtins', '__builtin__') or InstanceType and isinstance(target, InstanceType):
506
        return weave_instance(target, aspects, **options)
507
    else:
508
        raise UnsupportedType("Can't weave object %s of type %s" % (target, type(target)))
509
510
511
def _rewrap_method(func, klass, aspect):
512
    if isinstance(func, staticmethod):
513
        if hasattr(func, '__func__'):
514
            return staticmethod(_checked_apply(aspect, func.__func__))
515
        else:
516
            return staticmethod(_checked_apply(aspect, func.__get__(None, klass)))
517
    elif isinstance(func, classmethod):
518
        if hasattr(func, '__func__'):
519
            return classmethod(_checked_apply(aspect, func.__func__))
520
        else:
521
            return classmethod(_checked_apply(aspect, func.__get__(None, klass).im_func))
522
    else:
523
        return _checked_apply(aspect, func)
524
525
526
def weave_instance(instance, aspect, methods=NORMAL_METHODS, lazy=False, bag=BrokenBag, **options):
527
    """
528
    Low-level weaver for instances.
529
530
    .. warning:: You should not use this directly.
531
532
    :returns: An :obj:`aspectlib.Rollback` object.
533
    """
534
    if bag.has(instance):
535
        return Nothing
536
537
    entanglement = Rollback()
538
    method_matches = make_method_matcher(methods)
539
    logdebug("weave_instance (module=%r, aspect=%s, methods=%s, lazy=%s, **options=%s)",
540
             instance, aspect, methods, lazy, options)
541
542
    def fixup(func):
543
        return func.__get__(instance, type(instance))
544
    fixed_aspect = aspect + [fixup] if isinstance(aspect, (list, tuple)) else [aspect, fixup]
545
546
    for attr in dir(instance):
547
        func = getattr(instance, attr)
548
        if method_matches(attr):
549
            if ismethod(func):
550
                if hasattr(func, '__func__'):
551
                    realfunc = func.__func__
552
                else:
553
                    realfunc = func.im_func
554
                entanglement.merge(
555
                    patch_module(instance, attr, _checked_apply(fixed_aspect, realfunc, module=None), **options)
556
                )
557
    return entanglement
558
559
560
def weave_module(module, aspect, methods=NORMAL_METHODS, lazy=False, bag=BrokenBag, **options):
561
    """
562
    Low-level weaver for "whole module weaving".
563
564
    .. warning:: You should not use this directly.
565
566
    :returns: An :obj:`aspectlib.Rollback` object.
567
    """
568
    if bag.has(module):
569
        return Nothing
570
571
    entanglement = Rollback()
572
    method_matches = make_method_matcher(methods)
573
    logdebug("weave_module (module=%r, aspect=%s, methods=%s, lazy=%s, **options=%s)",
574
             module, aspect, methods, lazy, options)
575
576
    for attr in dir(module):
577
        func = getattr(module, attr)
578
        if method_matches(attr):
579
            if isroutine(func):
580
                entanglement.merge(patch_module_function(module, func, aspect, force_name=attr, **options))
581
            elif isclass(func):
582
                entanglement.merge(
583
                    weave_class(func, aspect, owner=module, name=attr, methods=methods, lazy=lazy, bag=bag, **options),
584
                    #  it's not consistent with the other ways of weaving a class (it's never weaved as a routine).
585
                    #  therefore it's disabled until it's considered useful.
586
                    #  #patch_module_function(module, getattr(module, attr), aspect, force_name=attr, **options),
587
                )
588
    return entanglement
589
590
591
def weave_class(klass, aspect, methods=NORMAL_METHODS, subclasses=True, lazy=False,
592
                owner=None, name=None, aliases=True, bases=True, bag=BrokenBag):
593
    """
594
    Low-level weaver for classes.
595
596
    .. warning:: You should not use this directly.
597
    """
598
    assert isclass(klass), "Can't weave %r. Must be a class." % klass
599
600
    if bag.has(klass):
601
        return Nothing
602
603
    entanglement = Rollback()
604
    method_matches = make_method_matcher(methods)
605
    logdebug("weave_class (klass=%r, methods=%s, subclasses=%s, lazy=%s, owner=%s, name=%s, aliases=%s, bases=%s)",
606
             klass, methods, subclasses, lazy, owner, name, aliases, bases)
607
608
    if subclasses and hasattr(klass, '__subclasses__'):
609
        sub_targets = klass.__subclasses__()
610
        if sub_targets:
611
            logdebug("~ weaving subclasses: %s", sub_targets)
612
        for sub_class in sub_targets:
613
            if not issubclass(sub_class, Fabric):
614
                entanglement.merge(weave_class(sub_class, aspect,
615
                                               methods=methods, subclasses=subclasses, lazy=lazy, bag=bag))
616
    if lazy:
617
        def __init__(self, *args, **kwargs):
618
            super(SubClass, self).__init__(*args, **kwargs)
619
            for attr in dir(self):
620
                func = getattr(self, attr, None)
621
                if method_matches(attr) and attr not in wrappers and isroutine(func):
622
                    setattr(self, attr, _checked_apply(aspect, force_bind(func)).__get__(self, SubClass))
623
624
        wrappers = {
625
            '__init__': _checked_apply(aspect, __init__) if method_matches('__init__') else __init__
626
        }
627
        for attr, func in klass.__dict__.items():
628
            if method_matches(attr):
629
                if ismethoddescriptor(func):
630
                    wrappers[attr] = _rewrap_method(func, klass, aspect)
631
632
        logdebug(" * creating subclass with attributes %r", wrappers)
633
        name = name or klass.__name__
634
        SubClass = type(name, (klass, Fabric), wrappers)
635
        SubClass.__module__ = klass.__module__
636
        module = owner or __import__(klass.__module__)
637
        entanglement.merge(patch_module(module, name, SubClass, original=klass, aliases=aliases))
638
    else:
639
        original = {}
640
        for attr, func in klass.__dict__.items():
641
            if method_matches(attr):
642
                if isroutine(func):
643
                    logdebug("@ patching attribute %r (original: %r).", attr, func)
644
                    setattr(klass, attr, _rewrap_method(func, klass, aspect))
645
                else:
646
                    continue
647
                original[attr] = func
648
        entanglement.merge(lambda: deque((
649
            setattr(klass, attr, func) for attr, func in original.items()
650
        ), maxlen=0))
651
        if bases:
652
            super_original = set()
653
            for sklass in _find_super_classes(klass):
654
                if sklass is not object:
655
                    for attr, func in sklass.__dict__.items():
656
                        if method_matches(attr) and attr not in original and attr not in super_original:
657
                            if isroutine(func):
658
                                logdebug("@ patching attribute %r (from superclass: %s, original: %r).",
659
                                         attr, sklass.__name__, func)
660
                                setattr(klass, attr, _rewrap_method(func, sklass, aspect))
661
                            else:
662
                                continue
663
                            super_original.add(attr)
664
            entanglement.merge(lambda: deque((
665
                delattr(klass, attr) for attr in super_original
666
            ), maxlen=0))
667
668
    return entanglement
669
670
671
def _find_super_classes(klass):
672
    if hasattr(klass, '__mro__'):
673
        for k in klass.__mro__:
674
            yield k
675
    else:
676
        for base in klass.__bases__:
677
            yield base
678
            for k in _find_super_classes(base):
679
                yield k
680
681
682
def patch_module(module, name, replacement, original=UNSPECIFIED, aliases=True, location=None, **_bogus_options):
683
    """
684
    Low-level attribute patcher.
685
686
    :param module module: Object to patch.
687
    :param str name: Attribute to patch
688
    :param replacement: The replacement value.
689
    :param original: The original value (in case the object beeing patched uses descriptors or is plain weird).
690
    :param bool aliases: If ``True`` patch all the attributes that have the same original value.
691
692
    :returns: An :obj:`aspectlib.Rollback` object.
693
    """
694
    rollback = Rollback()
695
    seen = False
696
    original = getattr(module, name) if original is UNSPECIFIED else original
697
    location = module.__name__ if hasattr(module, '__name__') else type(module).__module__
698
    target = module.__name__ if hasattr(module, '__name__') else type(module).__name__
699
    try:
700
        replacement.__module__ = location
701
    except (TypeError, AttributeError):
702
        pass
703
    for alias in dir(module):
704
        logdebug("alias:%s (%s)", alias, name)
705
        if hasattr(module, alias):
706
            obj = getattr(module, alias)
707
            logdebug("- %s:%s (%s)", obj, original, obj is original)
708
            if obj is original:
709
                if aliases or alias == name:
710
                    logdebug("= saving %s on %s.%s ...", replacement, target, alias)
711
                    setattr(module, alias, replacement)
712
                    rollback.merge(lambda alias=alias: setattr(module, alias, original))
713
                if alias == name:
714
                    seen = True
715
            elif alias == name:
716
                if ismethod(obj):
717
                    logdebug("= saving %s on %s.%s ...", replacement, target, alias)
718
                    setattr(module, alias, replacement)
719
                    rollback.merge(lambda alias=alias: setattr(module, alias, original))
720
                    seen = True
721
                else:
722
                    raise AssertionError("%s.%s = %s is not %s." % (module, alias, obj, original))
723
724
    if not seen:
725
        warnings.warn('Setting %s.%s to %s. There was no previous definition, probably patching the wrong module.' % (
726
            target, name, replacement
727
        ))
728
        logdebug("= saving %s on %s.%s ...", replacement, target, name)
729
        setattr(module, name, replacement)
730
        rollback.merge(lambda: setattr(module, name, original))
731
    return rollback
732
733
734
def patch_module_function(module, target, aspect, force_name=None, bag=BrokenBag, **options):
735
    """
736
    Low-level patcher for one function from a specified module.
737
738
    .. warning:: You should not use this directly.
739
740
    :returns: An :obj:`aspectlib.Rollback` object.
741
    """
742
    logdebug("patch_module_function (module=%s, target=%s, aspect=%s, force_name=%s, **options=%s",
743
             module, target, aspect, force_name, options)
744
    name = force_name or target.__name__
745
    return patch_module(module, name, _checked_apply(aspect, target, module=module), original=target, **options)
746