Completed
Push — master ( f86ad8...1b3f0d )
by Ionel Cristian
55s
created

src.hunter.trace()   B

Complexity

Conditions 2

Size

Total Lines 24

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 24
rs 8.9714
cc 2
1
from __future__ import absolute_import
2
3
import atexit
4
import inspect
5
import os
6
from functools import partial
7
8
from .actions import Action
9
from .actions import CodePrinter
10
from .actions import Debugger
11
from .actions import VarsPrinter
12
13
try:
14
    if os.environ.get("PUREPYTHONHUNTER"):
15
        raise ImportError("Skipped")
16
17
    from ._predicates import And as _And
18
    from ._predicates import Not
19
    from ._predicates import Or as _Or
20
    from ._predicates import When
21
    from ._predicates import Query
22
    from ._tracer import Tracer
23
except ImportError:
24
    from .predicates import And as _And
25
    from .predicates import Not
26
    from .predicates import Or as _Or
27
    from .predicates import When
28
    from .predicates import Query
29
    from .tracer import Tracer
30
31
__version__ = "0.6.0"
32
__all__ = (
33
    'And',
34
    'CodePrinter',
35
    'Debugger',
36
    'Not',
37
    'Or',
38
    'Q',
39
    'Query',
40
    'stop',
41
    'trace',
42
    'VarsPrinter',
43
    'When',
44
)
45
46
47
def Q(*predicates, **query):
48
    """
49
    Handles situations where :class:`hunter.Query` objects (or other callables) are passed in as positional arguments.
50
    Conveniently converts that to an :class:`hunter.And` predicate.
51
    """
52
    optional_actions = query.pop("actions", [])
53
    if "action" in query:
54
        optional_actions.append(query.pop("action"))
55
56
    if predicates:
57
        predicates = tuple(
58
            p() if inspect.isclass(p) and issubclass(p, Action) else p
59
            for p in predicates
60
        )
61
        if any(isinstance(p, CodePrinter) for p in predicates):
62
            if CodePrinter in optional_actions:
63
                optional_actions.remove(CodePrinter)
64
        if query:
65
            predicates += Query(**query),
66
67
        result = And(*predicates)
68
    else:
69
        result = Query(**query)
70
71
    if optional_actions:
72
        result = When(result, *optional_actions)
73
74
    return result
75
76
77
def _flatten(predicate, *predicates, **kwargs):
78
    cls = kwargs.pop('cls')
79
    if kwargs:
80
        raise TypeError("Did not expecte keyword arguments")
81
82
    if not predicates:
83
        return predicate
84
    else:
85
        all_predicates = []
86
        if isinstance(predicate, cls):
87
            all_predicates.extend(predicate.predicates)
88
        else:
89
            all_predicates.append(predicate)
90
91
        for p in predicates:
92
            if isinstance(p, cls):
93
                all_predicates.extend(p.predicates)
94
            else:
95
                all_predicates.append(p)
96
        return cls(*all_predicates)
97
98
99
And = partial(_flatten, cls=_And)
100
Or = partial(_flatten, cls=_Or)
101
102
_current_tracer = None
103
104
105
def stop():
106
    """
107
    Stop tracing.
108
109
    Notes:
110
        Restores previous tracer (if there was any).
111
    """
112
    global _current_tracer
113
114
    if _current_tracer is not None:
115
        _current_tracer.stop()
116
        _current_tracer = None
117
118
119
def _prepare_predicate(*predicates, **options):
120
    if "action" not in options and "actions" not in options:
121
        options["action"] = CodePrinter
122
123
    return Q(*predicates, **options)
124
125
126
def trace(*predicates, **options):
127
    """
128
    Starts tracing. Can be used as a context manager (with slightly incorrect semantics - it starts tracing
129
    before ``__enter__`` is called).
130
131
    Parameters:
132
        *predicates (callables): Runs actions if any of the given predicates match.
133
    Keyword Args:
134
        clear_env_var: Disables tracing in subprocess. Default: ``False``.
135
        action: Action to run if all the predicates return ``True``. Default: ``CodePrinter``.
136
        actions: Actions to run (in case you want more than 1).
137
    """
138
    global _current_tracer
139
140
    predicate = _prepare_predicate(*predicates, **options)
141
    clear_env_var = options.pop("clear_env_var", False)
142
143
    if clear_env_var:
144
        os.environ.pop("PYTHONHUNTER", None)
145
    try:
146
        _current_tracer = Tracer()
147
        return _current_tracer.trace(predicate)
148
    finally:
149
        atexit.register(_current_tracer.stop)
150