cs-debug 20250325__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,162 @@
1
+ Metadata-Version: 2.4
2
+ Name: cs-debug
3
+ Version: 20250325
4
+ Summary: Assorted debugging facilities.
5
+ Keywords: python2,python3
6
+ Author-email: Cameron Simpson <cs@cskk.id.au>
7
+ Description-Content-Type: text/markdown
8
+ Classifier: Programming Language :: Python
9
+ Classifier: Programming Language :: Python :: 2
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Operating System :: OS Independent
14
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
15
+ Classifier: License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)
16
+ Requires-Dist: cs.deco>=20250306
17
+ Requires-Dist: cs.fs>=20250325
18
+ Requires-Dist: cs.lex>=20250323
19
+ Requires-Dist: cs.logutils>=20250323
20
+ Requires-Dist: cs.obj>=20250306
21
+ Requires-Dist: cs.pfx>=20250308
22
+ Requires-Dist: cs.py.func>=20240630
23
+ Requires-Dist: cs.py.stack>=20250306
24
+ Requires-Dist: cs.py3>=20220523
25
+ Requires-Dist: cs.seq>=20250306
26
+ Requires-Dist: cs.upd>=20240630
27
+ Requires-Dist: cs.x>=20240630
28
+ Project-URL: MonoRepo Commits, https://bitbucket.org/cameron_simpson/css/commits/branch/main
29
+ Project-URL: Monorepo Git Mirror, https://github.com/cameron-simpson/css
30
+ Project-URL: Monorepo Hg/Mercurial Mirror, https://hg.sr.ht/~cameron-simpson/css
31
+ Project-URL: Source, https://github.com/cameron-simpson/css/blob/main/lib/python/cs/debug.py
32
+
33
+ Assorted debugging facilities.
34
+
35
+ *Latest release 20250325*:
36
+ * stack_dump: stack may also be a traceback object or an exception.
37
+ * stack_dump: move the logic to obtain the stack into cs.py.stack.frames().
38
+
39
+ If the environment variable `$CS_DEBUG_BUILTINS` is set to a comma
40
+ separated list of names then the `builtins` module will be monkey
41
+ patched with those names, enabling trite debug use of those names
42
+ anywhere in the code provided this module has been imported somewhere.
43
+
44
+ The allowed names are the list `cs.debug.__all__` and include:
45
+ * `X`: `cs.x.X`
46
+ * `abrk`: a decorator to call `breakpoint()` on an `AssertionError`
47
+ * `pformat`: `pprint.pformat`
48
+ * `pprint`: `pprint.pprint`
49
+ * `print`: `cs.upd.print`
50
+ * `r`: `cs.lex.r`
51
+ * `redirect_stdout`: `contextlib.redirect_stdout`
52
+ * `s`: `cs.lex.s`
53
+ * `stack_dump`: dump current `Thread`'s call stack
54
+ * `thread_dump` dump the active `Thread`s with their call stacks
55
+ * `trace`: the `@trace` decorator
56
+ `$CS_DEBUG_BUILTINS` can also be set to `"1"` to install all of
57
+ `__all__` in the builtins.
58
+
59
+ Module contents:
60
+ - <a name="abrk"></a>`abrk(*da, **dkw)`: A decorator to intercept certain exceptions
61
+ (by default `AssertionError`, `NameError`, `RuntimeError`)
62
+ and call `breakpoint()`.
63
+ The breakpoint frame contains:
64
+ - `func`: the wrapper function
65
+ - `func_a`, `func_kw`: the function positional and keyword arguments
66
+ - <a name="stack_dump"></a>`stack_dump(stack=None, limit=None, logger=None, log_level=None)`: Dump a stack trace to a logger.
67
+
68
+ Parameters:
69
+ * `stack`: a stack list as returned by `traceback.extract_stack`.
70
+ If missing or `None`, use the result of `traceback.extract_stack()`.
71
+ If `stack` has a `.tb_frame` or `.__traceback__` attribute,
72
+ extract the stack from that (this covers traceback objects and exceptions).
73
+ * `limit`: a limit to the number of stack entries to dump.
74
+ If missing or `None`, dump all entries.
75
+ * `logger`: a `logger.Logger` ducktype or the name of a logger.
76
+ If missing or `None`, obtain a logger from `logging.getLogger()`.
77
+ * `log_level`: the logging level for the dump.
78
+ If missing or `None`, use `cs.logutils.loginfo.level`.
79
+ - <a name="thread_dump"></a>`thread_dump(Ts=None, fp=None)`: Write thread identifiers and stack traces to the file `fp`.
80
+
81
+ Parameters:
82
+ * `Ts`: the `Thread`s to dump; if unspecified use `threading.enumerate()`.
83
+ * `fp`: the file to which to write; if unspecified use `sys.stderr`.
84
+ - <a name="TimingOutLock"></a>`Class `TimingOutLock`: A `Lock` replacement which times out, used for locating deadlock points.
85
+ - <a name="trace"></a>`trace(*da, **dkw)`: Decorator to report the call and return of a function.
86
+
87
+ Decorator parameters:
88
+ * `call`: trace the call, default `True`
89
+ * `retval`: trace the return, default `False`
90
+ * `exception`: trace raised exceptions, default `True`
91
+ * `use_pformat`: present the return value using
92
+ `pformat` instead of `repr`, default `False`
93
+ * `with_caller`: include the caller if this function, default `False`
94
+ * `with_pfx`: include the current `Pfx` prefix, default `False`
95
+
96
+ # Release Log
97
+
98
+
99
+
100
+ *Release 20250325*:
101
+ * stack_dump: stack may also be a traceback object or an exception.
102
+ * stack_dump: move the logic to obtain the stack into cs.py.stack.frames().
103
+
104
+ *Release 20241005*:
105
+ * New log_via_print(msg, *args[, file=stdout]) function to use cs.upd.print as a logging call.
106
+ * @trace: new $CS_DEBUG_TRACE envvar which may be "print" or "warning" or "X".
107
+ * New @abrk decorator to intercept AssertionError, NameError and RuntimeError and call breakpoint.
108
+
109
+ *Release 20240630*:
110
+ Assorted updates.
111
+
112
+ *Release 20240519*:
113
+ trace_caller: access frame.name instead of frame.funcname.
114
+
115
+ *Release 20240423*:
116
+ * Support "import *" by populating __all__ with X, r, s, TimingOutLock, thread_dump, stack_dump, trace.
117
+ * @trace: include the elapsed time on the return/exception log message.
118
+
119
+ *Release 20230613.1*:
120
+ Bugfix builtins monkey patch.
121
+
122
+ *Release 20230613*:
123
+ Honour $CS_DEBUG_BUILTINS envvar to monkey patch the builtins module, constraints via a white list.
124
+
125
+ *Release 20230610*:
126
+ * DebuggingRLock fixes.
127
+ * Move @trace from cs.py.func to cs.debug.
128
+ * Drop Lock and RLock alias factories - importers should just use the debugging lock classes directly.
129
+ * Rename threading.Thread to threading_Thread.
130
+ * Simplify the debugging lock classes.
131
+
132
+ *Release 20221118*:
133
+ stack_dump: cope when cs.logutils.setup_logging not run yet.
134
+
135
+ *Release 20211208*:
136
+ @trace moved to cs.pyfunc, other minor changes.
137
+
138
+ *Release 20200318*:
139
+ Remove use of cs.obj.O, universally supplanted by types.SimpleNamespace.
140
+
141
+ *Release 20181231*:
142
+ * New TimingOutLock for locating deadlock points, grew from debugging cs.vt.index.
143
+ * Other minor changes.
144
+
145
+ *Release 20171231*:
146
+ * Update imports for recentchanges.
147
+ * New context manager TraceSuite to trace start and end of a code suite.
148
+
149
+ *Release 20160918*:
150
+ selftest(): fix parameter ordering to match unittest.
151
+
152
+ *Release 20160828*:
153
+ Update metadata with "install_requires" instead of "requires".
154
+
155
+ *Release 20160827*:
156
+ * New openfiles() to return selected pathnames of open files via lsof(8).
157
+ * New selftest() to invoke unittests with benefits.
158
+ * DebugShell, a cmd.Cmd subclass for debugging - current use case calls this with self.__dict__ in a test case tearDwon.
159
+ * debug_object_shell: convenience wrapper for DebugShell to call it on an object's attributes.
160
+
161
+ *Release 20150116*:
162
+ PyPI prep.
@@ -0,0 +1,187 @@
1
+ [project]
2
+ name = "cs-debug"
3
+ description = "Assorted debugging facilities."
4
+ authors = [
5
+ { name = "Cameron Simpson", email = "cs@cskk.id.au" },
6
+ ]
7
+ keywords = [
8
+ "python2",
9
+ "python3",
10
+ ]
11
+ dependencies = [
12
+ "cs.deco>=20250306",
13
+ "cs.fs>=20250325",
14
+ "cs.lex>=20250323",
15
+ "cs.logutils>=20250323",
16
+ "cs.obj>=20250306",
17
+ "cs.pfx>=20250308",
18
+ "cs.py.func>=20240630",
19
+ "cs.py.stack>=20250306",
20
+ "cs.py3>=20220523",
21
+ "cs.seq>=20250306",
22
+ "cs.upd>=20240630",
23
+ "cs.x>=20240630",
24
+ ]
25
+ classifiers = [
26
+ "Programming Language :: Python",
27
+ "Programming Language :: Python :: 2",
28
+ "Programming Language :: Python :: 3",
29
+ "Development Status :: 4 - Beta",
30
+ "Intended Audience :: Developers",
31
+ "Operating System :: OS Independent",
32
+ "Topic :: Software Development :: Libraries :: Python Modules",
33
+ "License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)",
34
+ ]
35
+ version = "20250325"
36
+
37
+ [project.license]
38
+ text = "GNU General Public License v3 or later (GPLv3+)"
39
+
40
+ [project.urls]
41
+ "Monorepo Hg/Mercurial Mirror" = "https://hg.sr.ht/~cameron-simpson/css"
42
+ "Monorepo Git Mirror" = "https://github.com/cameron-simpson/css"
43
+ "MonoRepo Commits" = "https://bitbucket.org/cameron_simpson/css/commits/branch/main"
44
+ Source = "https://github.com/cameron-simpson/css/blob/main/lib/python/cs/debug.py"
45
+
46
+ [project.readme]
47
+ text = """
48
+ Assorted debugging facilities.
49
+
50
+ *Latest release 20250325*:
51
+ * stack_dump: stack may also be a traceback object or an exception.
52
+ * stack_dump: move the logic to obtain the stack into cs.py.stack.frames().
53
+
54
+ If the environment variable `$CS_DEBUG_BUILTINS` is set to a comma
55
+ separated list of names then the `builtins` module will be monkey
56
+ patched with those names, enabling trite debug use of those names
57
+ anywhere in the code provided this module has been imported somewhere.
58
+
59
+ The allowed names are the list `cs.debug.__all__` and include:
60
+ * `X`: `cs.x.X`
61
+ * `abrk`: a decorator to call `breakpoint()` on an `AssertionError`
62
+ * `pformat`: `pprint.pformat`
63
+ * `pprint`: `pprint.pprint`
64
+ * `print`: `cs.upd.print`
65
+ * `r`: `cs.lex.r`
66
+ * `redirect_stdout`: `contextlib.redirect_stdout`
67
+ * `s`: `cs.lex.s`
68
+ * `stack_dump`: dump current `Thread`'s call stack
69
+ * `thread_dump` dump the active `Thread`s with their call stacks
70
+ * `trace`: the `@trace` decorator
71
+ `$CS_DEBUG_BUILTINS` can also be set to `\"1\"` to install all of
72
+ `__all__` in the builtins.
73
+
74
+ Module contents:
75
+ - <a name=\"abrk\"></a>`abrk(*da, **dkw)`: A decorator to intercept certain exceptions
76
+ (by default `AssertionError`, `NameError`, `RuntimeError`)
77
+ and call `breakpoint()`.
78
+ The breakpoint frame contains:
79
+ - `func`: the wrapper function
80
+ - `func_a`, `func_kw`: the function positional and keyword arguments
81
+ - <a name=\"stack_dump\"></a>`stack_dump(stack=None, limit=None, logger=None, log_level=None)`: Dump a stack trace to a logger.
82
+
83
+ Parameters:
84
+ * `stack`: a stack list as returned by `traceback.extract_stack`.
85
+ If missing or `None`, use the result of `traceback.extract_stack()`.
86
+ If `stack` has a `.tb_frame` or `.__traceback__` attribute,
87
+ extract the stack from that (this covers traceback objects and exceptions).
88
+ * `limit`: a limit to the number of stack entries to dump.
89
+ If missing or `None`, dump all entries.
90
+ * `logger`: a `logger.Logger` ducktype or the name of a logger.
91
+ If missing or `None`, obtain a logger from `logging.getLogger()`.
92
+ * `log_level`: the logging level for the dump.
93
+ If missing or `None`, use `cs.logutils.loginfo.level`.
94
+ - <a name=\"thread_dump\"></a>`thread_dump(Ts=None, fp=None)`: Write thread identifiers and stack traces to the file `fp`.
95
+
96
+ Parameters:
97
+ * `Ts`: the `Thread`s to dump; if unspecified use `threading.enumerate()`.
98
+ * `fp`: the file to which to write; if unspecified use `sys.stderr`.
99
+ - <a name=\"TimingOutLock\"></a>`Class `TimingOutLock`: A `Lock` replacement which times out, used for locating deadlock points.
100
+ - <a name=\"trace\"></a>`trace(*da, **dkw)`: Decorator to report the call and return of a function.
101
+
102
+ Decorator parameters:
103
+ * `call`: trace the call, default `True`
104
+ * `retval`: trace the return, default `False`
105
+ * `exception`: trace raised exceptions, default `True`
106
+ * `use_pformat`: present the return value using
107
+ `pformat` instead of `repr`, default `False`
108
+ * `with_caller`: include the caller if this function, default `False`
109
+ * `with_pfx`: include the current `Pfx` prefix, default `False`
110
+
111
+ # Release Log
112
+
113
+
114
+
115
+ *Release 20250325*:
116
+ * stack_dump: stack may also be a traceback object or an exception.
117
+ * stack_dump: move the logic to obtain the stack into cs.py.stack.frames().
118
+
119
+ *Release 20241005*:
120
+ * New log_via_print(msg, *args[, file=stdout]) function to use cs.upd.print as a logging call.
121
+ * @trace: new $CS_DEBUG_TRACE envvar which may be \"print\" or \"warning\" or \"X\".
122
+ * New @abrk decorator to intercept AssertionError, NameError and RuntimeError and call breakpoint.
123
+
124
+ *Release 20240630*:
125
+ Assorted updates.
126
+
127
+ *Release 20240519*:
128
+ trace_caller: access frame.name instead of frame.funcname.
129
+
130
+ *Release 20240423*:
131
+ * Support \"import *\" by populating __all__ with X, r, s, TimingOutLock, thread_dump, stack_dump, trace.
132
+ * @trace: include the elapsed time on the return/exception log message.
133
+
134
+ *Release 20230613.1*:
135
+ Bugfix builtins monkey patch.
136
+
137
+ *Release 20230613*:
138
+ Honour $CS_DEBUG_BUILTINS envvar to monkey patch the builtins module, constraints via a white list.
139
+
140
+ *Release 20230610*:
141
+ * DebuggingRLock fixes.
142
+ * Move @trace from cs.py.func to cs.debug.
143
+ * Drop Lock and RLock alias factories - importers should just use the debugging lock classes directly.
144
+ * Rename threading.Thread to threading_Thread.
145
+ * Simplify the debugging lock classes.
146
+
147
+ *Release 20221118*:
148
+ stack_dump: cope when cs.logutils.setup_logging not run yet.
149
+
150
+ *Release 20211208*:
151
+ @trace moved to cs.pyfunc, other minor changes.
152
+
153
+ *Release 20200318*:
154
+ Remove use of cs.obj.O, universally supplanted by types.SimpleNamespace.
155
+
156
+ *Release 20181231*:
157
+ * New TimingOutLock for locating deadlock points, grew from debugging cs.vt.index.
158
+ * Other minor changes.
159
+
160
+ *Release 20171231*:
161
+ * Update imports for recentchanges.
162
+ * New context manager TraceSuite to trace start and end of a code suite.
163
+
164
+ *Release 20160918*:
165
+ selftest(): fix parameter ordering to match unittest.
166
+
167
+ *Release 20160828*:
168
+ Update metadata with \"install_requires\" instead of \"requires\".
169
+
170
+ *Release 20160827*:
171
+ * New openfiles() to return selected pathnames of open files via lsof(8).
172
+ * New selftest() to invoke unittests with benefits.
173
+ * DebugShell, a cmd.Cmd subclass for debugging - current use case calls this with self.__dict__ in a test case tearDwon.
174
+ * debug_object_shell: convenience wrapper for DebugShell to call it on an object's attributes.
175
+
176
+ *Release 20150116*:
177
+ PyPI prep."""
178
+ content-type = "text/markdown"
179
+
180
+ [build-system]
181
+ build-backend = "flit_core.buildapi"
182
+ requires = [
183
+ "flit_core >=3.2,<4",
184
+ ]
185
+
186
+ [tool.flit.module]
187
+ name = "cs.debug"
@@ -0,0 +1,890 @@
1
+ #!/usr/bin/python
2
+ #
3
+ # Assorted debugging facilities.
4
+ # - Cameron Simpson <cs@cskk.id.au> 20apr2013
5
+ #
6
+
7
+ r'''
8
+ Assorted debugging facilities.
9
+
10
+ If the environment variable `$CS_DEBUG_BUILTINS` is set to a comma
11
+ separated list of names then the `builtins` module will be monkey
12
+ patched with those names, enabling trite debug use of those names
13
+ anywhere in the code provided this module has been imported somewhere.
14
+
15
+ The allowed names are the list `cs.debug.__all__` and include:
16
+ * `X`: `cs.x.X`
17
+ * `abrk`: a decorator to call `breakpoint()` on an `AssertionError`
18
+ * `pformat`: `pprint.pformat`
19
+ * `pprint`: `pprint.pprint`
20
+ * `print`: `cs.upd.print`
21
+ * `r`: `cs.lex.r`
22
+ * `redirect_stdout`: `contextlib.redirect_stdout`
23
+ * `s`: `cs.lex.s`
24
+ * `stack_dump`: dump current `Thread`'s call stack
25
+ * `thread_dump` dump the active `Thread`s with their call stacks
26
+ * `trace`: the `@trace` decorator
27
+ `$CS_DEBUG_BUILTINS` can also be set to `"1"` to install all of
28
+ `__all__` in the builtins.
29
+ '''
30
+
31
+ from __future__ import print_function
32
+ from cmd import Cmd
33
+ from contextlib import redirect_stdout
34
+ import inspect
35
+ import logging
36
+ import os
37
+ from pprint import pformat, pprint # pylint: disable=unused-import
38
+ from subprocess import Popen, PIPE
39
+ import sys
40
+ from threading import (
41
+ enumerate as enumerate_threads,
42
+ Lock as threading_Lock,
43
+ RLock as threading_RLock,
44
+ Thread as threading_Thread,
45
+ )
46
+ import time
47
+ import traceback
48
+ from types import SimpleNamespace as NS
49
+
50
+ from cs.deco import ALL, decorator
51
+ from cs.fs import shortpath
52
+ from cs.lex import s, r, is_identifier, is_dotted_identifier # pylint: disable=unused-import
53
+ import cs.logutils
54
+ from cs.logutils import debug, error, warning, D, ifdebug, loginfo
55
+ from cs.obj import Proxy
56
+ from cs.pfx import Pfx
57
+ from cs.py.func import funccite, funcname, func_a_kw_fmt
58
+ from cs.py.stack import caller, frames
59
+ from cs.py3 import Queue, Queue_Empty, exec_code
60
+ from cs.seq import seq
61
+ from cs.threads import ThreadState
62
+ from cs.upd import print # pylint: disable=redefined-builtin
63
+ from cs.x import X
64
+
65
+ __version__ = '20250325'
66
+
67
+ DISTINFO = {
68
+ 'keywords': ["python2", "python3"],
69
+ 'classifiers': [
70
+ "Programming Language :: Python",
71
+ "Programming Language :: Python :: 2",
72
+ "Programming Language :: Python :: 3",
73
+ ],
74
+ 'install_requires': [
75
+ 'cs.deco',
76
+ 'cs.fs',
77
+ 'cs.lex',
78
+ 'cs.logutils',
79
+ 'cs.obj',
80
+ 'cs.pfx',
81
+ 'cs.py.func',
82
+ 'cs.py.stack',
83
+ 'cs.py3',
84
+ 'cs.seq',
85
+ 'cs.upd',
86
+ 'cs.x',
87
+ ],
88
+ }
89
+
90
+ __all__ = ['X', 'pformat', 'pprint', 'print', 'r', 'redirect_stdout', 's']
91
+
92
+ # environment variable specifying names to become built in
93
+ CS_DEBUG_BUILTINS_ENVVAR = 'CS_DEBUG_BUILTINS'
94
+
95
+ # white list of allowed builtin names
96
+ CS_DEBUG_BUILTINS_NAMES = ('X', 'pformat', 'pprint', 's', 'r', 'trace')
97
+
98
+ # @DEBUG dispatches a thread to monitor function elapsed time.
99
+ # This is how often it polls for function completion.
100
+ DEBUG_POLL_RATE = 0.25
101
+
102
+ @ALL
103
+ class TimingOutLock(object):
104
+ ''' A `Lock` replacement which times out, used for locating deadlock points.
105
+ '''
106
+
107
+ def __init__(self, deadlock_timeout=20.0, recursive=False):
108
+ self._lock = threading_RLock() if recursive else threading_Lock()
109
+ self._deadlock_timeout = deadlock_timeout
110
+
111
+ def acquire(self, blocking=True, timeout=-1, name=None):
112
+ if timeout < 0:
113
+ timeout = self._deadlock_timeout
114
+ else:
115
+ timeout = min(timeout, self._deadlock_timeout)
116
+ ok = (
117
+ self._lock.acquire(timeout=timeout)
118
+ if blocking else self._lock.acquire(blocking=blocking)
119
+ )
120
+ if not ok:
121
+ raise RuntimeError(
122
+ "TIMEOUT acquiring lock held by %s:%r" %
123
+ (self.owner, self.owner_name)
124
+ )
125
+ self.owner = caller()
126
+ self.owner_name = name
127
+ return True
128
+
129
+ def release(self):
130
+ return self._lock.release()
131
+
132
+ def __enter__(self):
133
+ self.acquire()
134
+ self.owner = caller()
135
+ return True
136
+
137
+ def __exit__(self, *a):
138
+ return self._lock.__exit__(*a)
139
+
140
+ class TraceSuite(object):
141
+ ''' Context manager to trace start and end of a code suite.
142
+ '''
143
+
144
+ def __init__(self, msg, *a):
145
+ if a:
146
+ msg = msg % a
147
+ self.msg = msg
148
+
149
+ def __enter__(self):
150
+ X("TraceSuite ENTER %s", self.msg)
151
+
152
+ def __exit__(self, exc_type, exc_value, exc_tb):
153
+ X("TraceSuite LEAVE %s: exc_value=%s", self.msg, exc_value)
154
+
155
+ def Thread(*a, **kw):
156
+ if not ifdebug():
157
+ return threading_Thread(*a, **kw)
158
+ filename, lineno = inspect.stack()[1][1:3]
159
+ return DebuggingThread({'filename': filename, 'lineno': lineno}, *a, **kw)
160
+
161
+ @ALL
162
+ def thread_dump(Ts=None, fp=None):
163
+ ''' Write thread identifiers and stack traces to the file `fp`.
164
+
165
+ Parameters:
166
+ * `Ts`: the `Thread`s to dump; if unspecified use `threading.enumerate()`.
167
+ * `fp`: the file to which to write; if unspecified use `sys.stderr`.
168
+ '''
169
+ if Ts is None:
170
+ Ts = enumerate_threads()
171
+ if fp is None:
172
+ fp = sys.stderr
173
+ with Pfx("thread_dump"):
174
+ frames = sys._current_frames()
175
+ for T in Ts:
176
+ try:
177
+ frame = frames[T.ident]
178
+ except KeyError:
179
+ warning("no frame for Thread.ident=%s", T.ident)
180
+ continue
181
+ print("Thread", T.ident, T.name, T, file=fp)
182
+ traceback.print_stack(frame, None, fp)
183
+ print(file=fp)
184
+
185
+ @ALL
186
+ def stack_dump(stack=None, limit=None, logger=None, log_level=None):
187
+ ''' Dump a stack trace to a logger.
188
+
189
+ Parameters:
190
+ * `stack`: a stack list as returned by `traceback.extract_stack`.
191
+ If missing or `None`, use the result of `traceback.extract_stack()`.
192
+ If `stack` has a `.tb_frame` or `.__traceback__` attribute,
193
+ extract the stack from that (this covers traceback objects and exceptions).
194
+ * `limit`: a limit to the number of stack entries to dump.
195
+ If missing or `None`, dump all entries.
196
+ * `logger`: a `logger.Logger` ducktype or the name of a logger.
197
+ If missing or `None`, obtain a logger from `logging.getLogger()`.
198
+ * `log_level`: the logging level for the dump.
199
+ If missing or `None`, use `cs.logutils.loginfo.level`.
200
+ '''
201
+ stack = frames(stack, limit=limit)
202
+ if logger is None:
203
+ logger = logging.getLogger()
204
+ elif isinstance(logger, str):
205
+ logger = logging.getLogger(logger)
206
+ if log_level is None:
207
+ log_level = getattr(loginfo, 'level', logging.WARNING)
208
+ for text in traceback.format_list(stack):
209
+ for line in text.splitlines():
210
+ logger.log(log_level, line.rstrip())
211
+
212
+ def DEBUG(f, force=False):
213
+ ''' Decorator to wrap functions in timing and value debuggers.
214
+ '''
215
+ from cs.result import Result
216
+
217
+ def inner(*a, **kw):
218
+ if not force and not ifdebug():
219
+ return f(*a, **kw)
220
+ filename, lineno = inspect.stack()[1][1:3]
221
+ n = seq()
222
+ R = Result()
223
+ T = threading_Thread(
224
+ target=_debug_watcher, args=(filename, lineno, n, f.__name__, R)
225
+ )
226
+ T.daemon = True
227
+ T.start()
228
+ debug(
229
+ "%s:%d: [%d] call %s(*%r, **%r)", filename, lineno, n, f.__name__, a,
230
+ kw
231
+ )
232
+ start = time.time()
233
+ try:
234
+ retval = f(*a, **kw)
235
+ except Exception as e:
236
+ error("EXCEPTION from %s(*%s, **%s): %s", f, a, kw, e)
237
+ raise
238
+ end = time.time()
239
+ debug(
240
+ "%s:%d: [%d] called %s, elapsed %gs, got %r", filename, lineno, n,
241
+ f.__name__, end - start, retval
242
+ )
243
+ R.put(retval)
244
+ return retval
245
+
246
+ return inner
247
+
248
+ def _debug_watcher(filename, lineno, n, funcname, R):
249
+ slow = 2
250
+ sofar = 0
251
+ slowness = 0
252
+ while not R.ready:
253
+ if slowness >= slow:
254
+ debug(
255
+ "%s:%d: [%d] calling %s, %gs elapsed so far...", filename, lineno, n,
256
+ funcname, sofar
257
+ )
258
+ # reset report time and complain more slowly next time
259
+ slowness = 0
260
+ slow += 1
261
+ time.sleep(DEBUG_POLL_RATE)
262
+ sofar += DEBUG_POLL_RATE
263
+ slowness += DEBUG_POLL_RATE
264
+
265
+ def DF(func, *a, **kw):
266
+ ''' Wrapper for a function call to debug its use.
267
+
268
+ This requires rewriting the call from `f(*a,*kw)` to `DF(f,*a,**kw)`.
269
+ Alternatively one could rewrite as `DEBUG(f)(*a,**kw)`.
270
+ '''
271
+ return DEBUG(func, force=True)(*a, **kw)
272
+
273
+ class DebugWrapper(NS):
274
+ ''' Base class for classes presenting debugging wrappers.
275
+ '''
276
+
277
+ def debug(self, msg, *a):
278
+ if a:
279
+ msg = msg % a
280
+ cs.logutils.debug(': '.join((self.debug_label, msg)))
281
+
282
+ @property
283
+ def debug_label(self):
284
+ info = '%s:%d' % (self.filename, self.lineno)
285
+ try:
286
+ context = self.context
287
+ except AttributeError:
288
+ pass
289
+ else:
290
+ info = ':'.join(info, str(context))
291
+ label = '%s-%d[%s]' % (self.__class__.__name__, id(self), info)
292
+ return label
293
+
294
+ class DebuggingLock(DebugWrapper):
295
+ ''' Wrapper class for `threading.Lock` to trace creation and use.
296
+
297
+ `cs.threads.Lock()` returns one of these in debug mode or a raw
298
+ `threading.Lock` otherwise.
299
+ '''
300
+
301
+ def __init__(self, *, slow=2, **dkw):
302
+ DebugWrapper.__init__(self, **dkw)
303
+ self.debug("__init__(slow=%r)", slow)
304
+ if slow <= 0:
305
+ raise ValueError("slow must be positive, received: %r" % (slow,))
306
+ self.slow = slow
307
+ self.lock = threading_Lock()
308
+ self.held = None
309
+
310
+ def __enter__(self):
311
+ ##self.lock.__enter__()
312
+ self.acquire()
313
+ return self
314
+
315
+ def __exit__(self, *a):
316
+ ##return self.lock.__exit__(*a)
317
+ self.release()
318
+ return False
319
+
320
+ def acquire(self, *a):
321
+ ''' Acquire the lock.
322
+ '''
323
+ # quietly support Python 3 arguments after blocking parameter
324
+ blocking = True
325
+ if a:
326
+ blocking = a[0]
327
+ a = a[1:]
328
+ filename, lineno = inspect.stack()[1][1:3]
329
+ debug("%s:%d: acquire(blocking=%s)", filename, lineno, blocking)
330
+ if blocking:
331
+ # blocking
332
+ # try non-blocking first
333
+ # if successful, good
334
+ # otherwise spawn a monitoring thread to report on slow acquisition
335
+ # and block
336
+ taken = self.lock.acquire(False)
337
+ if not taken:
338
+ Q = Queue()
339
+ T = Thread(target=self._timed_acquire, args=(Q, filename, lineno))
340
+ T.daemon = True
341
+ T.start()
342
+ taken = self.lock.acquire(blocking, *a)
343
+ Q.put(taken)
344
+ else:
345
+ # non-blocking: do ordinary lock acquisition
346
+ taken = self.lock.acquire(blocking, *a)
347
+ if taken:
348
+ self.held = (filename, lineno)
349
+ return taken
350
+
351
+ def release(self):
352
+ ''' Release the lock.
353
+ '''
354
+ filename, lineno = inspect.stack()[0][1:3]
355
+ debug("%s:%d: release()", filename, lineno)
356
+ self.held = None
357
+ self.lock.release()
358
+
359
+ def _timed_acquire(self, Q, filename, lineno):
360
+ ''' Block waiting for lock acquisition.
361
+ Report slow acquisition.
362
+
363
+ This would be inline above except that Python 2 `Lock`s do
364
+ not have a timeout parameter, hence this thread.
365
+ This probably scales VERY badly if there is a lot of `Lock`
366
+ contention.
367
+ '''
368
+ slow = self.slow
369
+ sofar = 0
370
+ slowness = 0
371
+ while True:
372
+ # block until lock acquired
373
+ try:
374
+ Q.get(True, 1)
375
+ except Queue_Empty:
376
+ sofar += 1
377
+ slowness += 1
378
+ if slowness >= slow:
379
+ self.debug(
380
+ "from %s:%d: acquire: after %gs, held by %s", filename, lineno,
381
+ sofar, self.held
382
+ )
383
+ # complain more slowly next time
384
+ slowness = 0
385
+ slow += 1
386
+ else:
387
+ break
388
+
389
+ class DebuggingRLock(DebugWrapper):
390
+ ''' Wrapper class for threading.RLock to trace creation and use.
391
+
392
+ `cs.threads.RLock()` returns on of these in debug mode or a raw
393
+ `threading.RLock` otherwise.
394
+ '''
395
+
396
+ def __init__(self, owner=None, **dkw):
397
+ if owner is None:
398
+ owner = caller()
399
+ DebugWrapper.__init__(
400
+ self, filename=owner.filename, lineno=owner.lineno, **dkw
401
+ )
402
+ self.debug('__init__')
403
+ self.lock = threading_RLock()
404
+ self.stack = []
405
+
406
+ def __str__(self):
407
+ return "%s[%s:%s]%s" % (
408
+ type(self).__name__,
409
+ shortpath(self.filename),
410
+ self.lineno,
411
+ "->".join(
412
+ ["%s:%s" % filename_lineno for filename_lineno in self.stack]
413
+ ),
414
+ )
415
+
416
+ def __enter__(self, locker=None):
417
+ if locker is None:
418
+ locker = caller()
419
+ filename_lineno = locker.filename, locker.lineno
420
+ self.debug('from %s:%d: __enter__ ...', locker.filename, locker.lineno)
421
+ entry = self.lock.__enter__()
422
+ self.stack.append(filename_lineno)
423
+ return entry
424
+
425
+ def __exit__(self, *a, exiter=None):
426
+ if exiter is None:
427
+ exiter = caller()
428
+ self.debug('%s:%d: __exit__(*%s) ...', exiter.filename, exiter.lineno, a)
429
+ exited = self.lock.__exit__(*a)
430
+ self.stack.pop()
431
+ return exited
432
+
433
+ def acquire(self, blocking=True, timeout=-1, acquirer=None):
434
+ if acquirer is None:
435
+ acquirer = caller()
436
+ self.debug(
437
+ '%s:%d: acquire(blocking=%s)', acquirer.filename, acquirer.lineno,
438
+ blocking
439
+ )
440
+ if timeout < 0:
441
+ ret = self.lock.acquire(blocking)
442
+ else:
443
+ ret = self.lock.acquire(blocking, timeout)
444
+ if ret:
445
+ self.stack.append((acquirer.filename, acquirer.lineno))
446
+ return ret
447
+
448
+ def release(self, releaser=None):
449
+ if releaser is None:
450
+ releaser = caller()
451
+ self.debug('%s:%d: release()', releaser.filename, releaser.lineno)
452
+ self.lock.release()
453
+ self.stack.pop()
454
+
455
+ Lock = DebuggingLock
456
+ RLock = DebuggingRLock
457
+
458
+ _debug_threads = set()
459
+
460
+ def dump_debug_threads():
461
+ D("dump_debug_threads:")
462
+ for T in _debug_threads:
463
+ D("dump_debug_threads: thread %r: %r", T.name, T.debug_label)
464
+ D("dump_debug_threads done")
465
+
466
+ class DebuggingThread(threading_Thread, DebugWrapper):
467
+
468
+ def __init__(self, dkw, *a, **kw):
469
+ DebugWrapper.__init__(self, **dkw)
470
+ self.debug("NEW THREAD(*%r, **%r)", a, kw)
471
+ _debug_threads.add(self)
472
+ threading_Thread.__init__(self, *a, **kw)
473
+
474
+ @DEBUG
475
+ def join(self, timeout=None):
476
+ self.debug("join(timeout=%r)...", timeout)
477
+ retval = threading_Thread.join(self, timeout=timeout)
478
+ self.debug("join(timeout=%r) completed", timeout)
479
+ _debug_threads.discard(self)
480
+ return retval
481
+
482
+ def trace_caller(func):
483
+ ''' Decorator to report the caller of a function when called.
484
+ '''
485
+
486
+ def subfunc(*a, **kw):
487
+ frame = caller()
488
+ D(
489
+ "CALL %s()<%s:%d> FROM %s()<%s:%d>",
490
+ func.__name__,
491
+ func.__code__.co_filename,
492
+ func.__code__.co_firstlineno,
493
+ frame.name,
494
+ frame.filename,
495
+ frame.lineno,
496
+ )
497
+ return func(*a, **kw)
498
+
499
+ subfunc.__name__ = "trace_caller/subfunc/" + func.__name__
500
+ return subfunc
501
+
502
+ class TracingObject(Proxy):
503
+
504
+ def __init__(self, other):
505
+ Proxy.__init__(self, other)
506
+ self.__attr_map = {}
507
+
508
+ def __getattribute__(self, attr):
509
+ X("TracingObject.__getattribute__(attr=%r)", attr)
510
+ _proxied = Proxy.__getattribute__(self, '_proxied')
511
+ try:
512
+ value = object.__getattribute__(_proxied, attr)
513
+ except AttributeError:
514
+ X("no .%s attribute", attr)
515
+ raise
516
+ else:
517
+ X("getattr .%s", attr)
518
+ return TracingObject(value)
519
+
520
+ def __call__(self, *a, **kw):
521
+ _proxied = Proxy.__getattribute__(self, '_proxied')
522
+ X("call %s(*%r, **%r)", _proxied, a, kw)
523
+ return _proxied(*a, **kw)
524
+
525
+ class DummyMap(object):
526
+
527
+ def __init__(self, label, d=None):
528
+ X("new DummyMap labelled %r, d=%r", label, d)
529
+ self.__label = label
530
+ self.__map = {}
531
+ if d:
532
+ self.__map.update(d)
533
+
534
+ def __str__(self):
535
+ return self.__label
536
+
537
+ def items(self):
538
+ X("%s.items", self)
539
+ return []
540
+
541
+ def __getitem__(self, key):
542
+ v = self.__map.get(key)
543
+ X("%s[%r] => %r", self, key, v)
544
+ return v
545
+
546
+ def openfiles(substr=None, pid=None):
547
+ ''' Run lsof(8) against process `pid`
548
+ returning paths of open files whose paths contain `substr`.
549
+
550
+ Parameters:
551
+ * `substr`: default substring to select by; default returns all paths.
552
+ * `pid`: process to examine; default from `os.getpid()`.
553
+ '''
554
+ if pid is None:
555
+ pid = os.getpid()
556
+ paths = []
557
+ P = Popen(['lsof', '-p', str(pid)], stdout=PIPE)
558
+ for lsof in P.stdout:
559
+ lsof = lsof.decode()
560
+ fields = lsof.split()
561
+ if len(fields) >= 9:
562
+ if fields[4] == 'REG':
563
+ if substr is None or substr in fields[8]:
564
+ paths.append(fields[8])
565
+ P.wait()
566
+ return paths
567
+
568
+ class DebugShell(Cmd):
569
+ ''' An interactive prompt for python statements, attached to `/dev/tty` by default.
570
+ '''
571
+
572
+ def __init__(self, var_dict, stdin=None, stdout=None):
573
+ if stdin is None:
574
+ stdin = open('/dev/tty', 'r')
575
+ if stdout is None:
576
+ stdout = open('/dev/tty', 'a')
577
+ self.stdin = stdin
578
+ self.stdout = stdout
579
+ Cmd.__init__(self, stdin=stdin, stdout=stdout)
580
+ self.vars = var_dict
581
+
582
+ def default(self, line):
583
+ ''' Default command action.
584
+ '''
585
+ if line == 'EOF':
586
+ return True
587
+ try:
588
+ exec_code(line, globals(), self.vars)
589
+ except Exception as e:
590
+ X("Exception: %s", e)
591
+ self.stdout.flush()
592
+ return False
593
+
594
+ def debug_object_shell(o, prompt=None):
595
+ ''' Interactive prompt for inspecting variables.
596
+ '''
597
+ if prompt is None:
598
+ prompt = str(o) + '> '
599
+ v = o.__dict__
600
+ C = DebugShell(v)
601
+ intro = '\n\n'
602
+ for k in sorted(v.keys()):
603
+ intro += '\n %s = %r' % (k, v[k])
604
+ intro += '\n'
605
+ C.prompt = prompt
606
+ C.cmdloop(intro)
607
+
608
+ _trace_state = ThreadState(indent='')
609
+
610
+ def log_via_print(msg, *a, file=None):
611
+ ''' Logging style message using `cs.upd.print`.
612
+ '''
613
+ if a:
614
+ msg = msg % a
615
+ if file is None:
616
+ file = sys.stdout
617
+ print(msg, file=file, flush=True)
618
+
619
+ @ALL
620
+ @decorator
621
+ def abrk(func, exceptions=(AssertionError, NameError, RuntimeError)):
622
+ ''' A decorator to intercept certain exceptions
623
+ (by default `AssertionError`, `NameError`, `RuntimeError`)
624
+ and call `breakpoint()`.
625
+ The breakpoint frame contains:
626
+ - `func`: the wrapper function
627
+ - `func_a`, `func_kw`: the function positional and keyword arguments
628
+ '''
629
+
630
+ def cs_debug_abrk_wrapper(*func_a, **func_kw):
631
+ try:
632
+ return func(*func_a, **func_kw)
633
+ except exceptions as e:
634
+ warning(
635
+ "%s: %s\n func = %s\n func_a = %r\nfunc_kw = %r",
636
+ funccite(func),
637
+ e,
638
+ funccite(func),
639
+ func_a,
640
+ func_kw,
641
+ )
642
+ breakpoint()
643
+ raise
644
+
645
+ return cs_debug_abrk_wrapper
646
+
647
+ @ALL
648
+ @decorator
649
+ # pylint: disable=too-many-arguments
650
+ def trace(
651
+ func,
652
+ call=True,
653
+ retval=False,
654
+ exception=True,
655
+ use_pformat=False,
656
+ with_caller=False,
657
+ with_pfx=False,
658
+ xlog=None,
659
+ ):
660
+ ''' Decorator to report the call and return of a function.
661
+
662
+ Decorator parameters:
663
+ * `call`: trace the call, default `True`
664
+ * `retval`: trace the return, default `False`
665
+ * `exception`: trace raised exceptions, default `True`
666
+ * `use_pformat`: present the return value using
667
+ `pformat` instead of `repr`, default `False`
668
+ * `with_caller`: include the caller if this function, default `False`
669
+ * `with_pfx`: include the current `Pfx` prefix, default `False`
670
+ '''
671
+
672
+ citation = funcname(func) ## funccite(func)
673
+
674
+ def traced_function_wrapper(*a, **kw):
675
+ ''' Wrapper for `func` to trace call and return.
676
+ '''
677
+ global _trace_state # pylint: disable=global-statement
678
+ if with_pfx:
679
+ # late import so that we can use this in modules we import
680
+ # pylint: disable=import-outside-toplevel
681
+ try:
682
+ from cs.pfx import XP as xlog
683
+ except ImportError:
684
+ xlog = X
685
+ else:
686
+ xlog = X
687
+ log_cite = citation
688
+ if with_caller:
689
+ log_cite = log_cite + "from[%s]" % (caller(),)
690
+ if call:
691
+ fmt, av = func_a_kw_fmt(log_cite, *a, **kw)
692
+ xlog("%sCALL " + fmt, _trace_state.indent, *av)
693
+ old_indent = _trace_state.indent
694
+ _trace_state.indent += ' '
695
+ start_time = time.time()
696
+ try:
697
+ result = func(*a, **kw)
698
+ except Exception as e:
699
+ end_time = time.time()
700
+ if exception:
701
+ xlog_kw = {}
702
+ if xlog is X:
703
+ xlog_kw['colour'] = 'red'
704
+ xlog(
705
+ "%sCALL %s %gs RAISE %r",
706
+ _trace_state.indent,
707
+ log_cite,
708
+ end_time - start_time,
709
+ e,
710
+ **xlog_kw,
711
+ )
712
+ _trace_state.indent = old_indent
713
+ raise
714
+ else:
715
+ end_time = time.time()
716
+ if retval:
717
+ xlog(
718
+ "%sCALL %s %gs RETURN %s",
719
+ _trace_state.indent,
720
+ log_cite,
721
+ end_time - start_time,
722
+ (pformat if use_pformat else repr)(result),
723
+ )
724
+ if inspect.isgeneratorfunction(func):
725
+ iterator = result
726
+
727
+ def traced_generator():
728
+ while True:
729
+ next_time = time.time()
730
+ if call:
731
+ xlog(
732
+ "%sNEXT %s %gs ...",
733
+ _trace_state.indent,
734
+ log_cite,
735
+ next_time - start_time,
736
+ )
737
+ try:
738
+ item = next(iterator)
739
+ except StopIteration:
740
+ yield_time = time.time()
741
+ xlog(
742
+ "%sDONE %s %gs ...",
743
+ _trace_state.indent,
744
+ log_cite,
745
+ yield_time - next_time,
746
+ )
747
+ break
748
+ except Exception as e:
749
+ end_time = time.time()
750
+ if exception:
751
+ xlog_kw = {}
752
+ if xlog is X:
753
+ xlog_kw['colour'] = 'red'
754
+ xlog(
755
+ "%sCALL %s %gs RAISE %r",
756
+ _trace_state.indent,
757
+ log_cite,
758
+ end_time - start_time,
759
+ e,
760
+ **xlog_kw,
761
+ )
762
+ _trace_state.indent = old_indent
763
+ raise
764
+ else:
765
+ yield_time = time.time()
766
+ xlog(
767
+ "%sYIELD %gs %s <= %s",
768
+ _trace_state.indent,
769
+ yield_time - next_time,
770
+ s(item),
771
+ log_cite,
772
+ )
773
+ yield item
774
+
775
+ result = traced_generator()
776
+ else:
777
+ ##xlog("%sRETURN %s <= %s", _trace_state.indent, type(result), log_cite)
778
+ if retval:
779
+ xlog(
780
+ "%sRETURN %gs %s <= %s",
781
+ _trace_state.indent,
782
+ end_time - start_time,
783
+ s(result),
784
+ log_cite,
785
+ )
786
+ _trace_state.indent = old_indent
787
+ return result
788
+
789
+ traced_function_wrapper.__name__ = "@trace(%s)" % (citation,)
790
+ traced_function_wrapper.__doc__ = "@trace(%s)\n\n" + (func.__doc__ or '')
791
+ return traced_function_wrapper
792
+
793
+ def trace_DEBUG(debug_spec=None):
794
+ ''' Apply the `@trace` decorator to functions specified by `debug_spec`,
795
+ default from the environment variable `$DEBUG`.
796
+ '''
797
+ with Pfx("trace_DEBUG"):
798
+ try:
799
+ import importlib
800
+ except ImportError as e:
801
+ warning("trace_DEBUG: cannot import importlib, no applying: %s", e)
802
+ return
803
+ if debug_spec is None:
804
+ debug_spec = os.environ.get('DEBUG', '')
805
+ if isinstance(debug_spec, str):
806
+ debug_spec = debug_spec.split(',')
807
+ with Pfx("%r", debug_spec):
808
+ module_names = []
809
+ function_names = []
810
+ for spec in debug_spec:
811
+ with Pfx(spec):
812
+ if is_dotted_identifier(spec):
813
+ module_names.append(spec)
814
+ elif ':' in spec:
815
+ # module:funcname
816
+ module_name, func_name = spec.split(':', 1)
817
+ if (is_dotted_identifier(module_name)
818
+ and is_dotted_identifier(func_name)):
819
+ function_names.append((module_name, func_name))
820
+ for module_name in module_names:
821
+ with Pfx("module %s", module_name):
822
+ try:
823
+ M = importlib.import_module(module_name)
824
+ except ImportError as e:
825
+ warning("cannot import: %s", e)
826
+ continue
827
+ M.DEBUG = True
828
+ for module_name, func_name in function_names:
829
+ with Pfx("function %s:%s", module_name, func_name):
830
+ try:
831
+ M = importlib.import_module(module_name)
832
+ except ImportError as e:
833
+ warning("cannot import: %s", e)
834
+ continue
835
+ try:
836
+ F = getattr(M, func_name)
837
+ except AttributeError as e:
838
+ warning("function %s not found: %s", e)
839
+ continue
840
+ if callable(F):
841
+ setattr(M, func_name, trace(F))
842
+
843
+ def selftest(module_name, defaultTest=None, argv=None):
844
+ ''' Called by my unit tests.
845
+ '''
846
+ # pylint: disable=import-outside-toplevel
847
+ if argv is None:
848
+ argv = sys.argv
849
+ import importlib
850
+ importlib.import_module(module_name)
851
+ import signal
852
+ signal.signal(signal.SIGHUP, lambda sig, frame: thread_dump())
853
+ signal.signal(signal.SIGINT, lambda sig, frame: sys.exit(thread_dump()))
854
+ import unittest
855
+ return unittest.main(module=module_name, defaultTest=defaultTest, argv=argv)
856
+
857
+ builtin_names_s = os.environ.get(CS_DEBUG_BUILTINS_ENVVAR, '')
858
+ if builtin_names_s:
859
+ try:
860
+ import builtins # pylint: disable=unused-import
861
+ except ImportError:
862
+ warning(
863
+ "$%s=%r but connot import builtins for monkey patching",
864
+ CS_DEBUG_BUILTINS_ENVVAR, builtin_names_s
865
+ )
866
+ else:
867
+ vs = vars()
868
+ for builtin_name in (__all__ if builtin_names_s == "1" else
869
+ builtin_names_s.split(',')):
870
+ if not builtin_name:
871
+ continue
872
+ if builtin_name not in __all__:
873
+ warning(
874
+ "$%s: ignoring %r, not in cs.debug.__all__:%r",
875
+ CS_DEBUG_BUILTINS_ENVVAR, builtin_name, __all__
876
+ )
877
+ continue
878
+ if builtin_name in ('breakpoint',):
879
+ # breakpoint doesn't work right if wrapped, gets the wrong frame
880
+ continue
881
+ if not is_identifier(builtin_name):
882
+ warning(
883
+ "$%s: ignoring %r, not an identifier", CS_DEBUG_BUILTINS_ENVVAR,
884
+ builtin_name
885
+ )
886
+ continue
887
+ setattr(builtins, builtin_name, vs[builtin_name])
888
+
889
+ # honour the $DEBUG trace flags
890
+ trace_DEBUG()