excdump 0.1.0__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,13 @@
1
+ # Python-generated files
2
+ __pycache__/
3
+ *.py[oc]
4
+ build/
5
+ dist/
6
+ wheels/
7
+ *.egg-info
8
+
9
+ # Virtual environments
10
+ .venv
11
+
12
+ # Captured exception dumps
13
+ .exception_dumps/
excdump-0.1.0/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Alireza Hariri
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR OTHER DEALINGS IN THE SOFTWARE.
excdump-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,235 @@
1
+ Metadata-Version: 2.5
2
+ Name: excdump
3
+ Version: 0.1.0
4
+ Summary: Capture a full exception snapshot and debug it later, offline.
5
+ Project-URL: Homepage, https://github.com/alireza-hariri/excdump
6
+ Project-URL: Repository, https://github.com/alireza-hariri/excdump
7
+ Project-URL: Issues, https://github.com/alireza-hariri/excdump/issues
8
+ Author-email: Alireza Hariri <ali.r.hariri@gmail.com>
9
+ License-Expression: MIT
10
+ License-File: LICENSE
11
+ Keywords: debugging,exceptions,pdb,postmortem,traceback
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Topic :: Software Development :: Debuggers
16
+ Classifier: Typing :: Typed
17
+ Requires-Python: >=3.11
18
+ Requires-Dist: dill>=0.4.1
19
+ Requires-Dist: prompt-toolkit>=3.0.53
20
+ Requires-Dist: pydantic>=2.13.4
21
+ Description-Content-Type: text/markdown
22
+
23
+ # excdump
24
+
25
+ Capture everything about an exception the moment it happens, then debug it later
26
+ on your own machine — with the frames, the locals, related globals, and the source as they were.
27
+
28
+ A traceback tells you where a program failed. It does not tell you what `order`
29
+ held, what `rate` had been computed to, or what the file looked like before you
30
+ edited it. `excdump` writes all of that to a file at the moment of failure, and
31
+ gives you an offline debugger to walk it afterwards.
32
+
33
+ ```python
34
+ from excdump import dump_exception
35
+
36
+ try:
37
+ handle_checkout()
38
+ except Exception:
39
+ trace_id = dump_exception() # returns the id -- log it
40
+ raise
41
+ ```
42
+
43
+ ```console
44
+ $ python -m excdump inspect <trace-id>
45
+ ```
46
+
47
+ ## Install
48
+
49
+ Requires Python 3.14+.
50
+
51
+ ```console
52
+ $ uv sync # or: pip install -e .
53
+ ```
54
+
55
+ Try it without writing any code:
56
+
57
+ ```console
58
+ $ python -m excdump demo
59
+ $ python -m excdump inspect
60
+ ```
61
+
62
+ ## Capturing
63
+
64
+ Two ways in. `dump_exception()` is called from an `except` block and returns the
65
+ trace id, which is what you log:
66
+
67
+ ```python
68
+ except Exception:
69
+ logger.error("checkout failed", extra={"trace_id": dump_exception()})
70
+ raise
71
+ ```
72
+
73
+ `@dump_on_exception` wraps a function and captures anything that escapes it,
74
+ then re-raises unchanged. It takes options, or none at all:
75
+
76
+ ```python
77
+ from excdump import dump_on_exception
78
+
79
+ @dump_on_exception
80
+ def handle_checkout(cart):
81
+ ...
82
+
83
+ @dump_on_exception(on_dump=report_to_sentry, n_depth_up=2)
84
+ def calculate_tax(order):
85
+ ...
86
+ ```
87
+
88
+ The decorated function is the pivot the session opens on, with the decorator's
89
+ own frames left out, so `up` reaches its real caller.
90
+
91
+ Both capture the frames of the traceback plus a configurable number of caller
92
+ frames above it, every exception in a `raise ... from ...` chain, and the source
93
+ of each file involved.
94
+
95
+ ## Inspecting
96
+
97
+ ```console
98
+ $ python -m excdump list # what the store holds, grouped by failure
99
+ $ python -m excdump inspect # the most recent dump
100
+ $ python -m excdump inspect <trace-id> # a specific one; a unique prefix works
101
+ $ python -m excdump inspect <path-id> # newest dump of one failure
102
+ $ python -m excdump gc # reclaim paths nothing hits any more
103
+ ```
104
+
105
+ `inspect` opens a `pdb`-like session over the dump. Frames, locals and globals
106
+ are all there; `--plain` forces the readline prompt instead of the TUI.
107
+
108
+ Nothing about the session touches the process that crashed — it no longer
109
+ exists. That is the point: you get to look around at your leisure, on a
110
+ different machine, days later.
111
+
112
+ ## How dumps are filed
113
+
114
+ A dump is filed under its **exception path** — the `(filename, lineno)` list of
115
+ its traceback, hashed. One bug hit a million times shares one path, so retention
116
+ applies per failure and a hot loop cannot push a rarer failure out of the store.
117
+
118
+ ```
119
+ .exception_dumps/
120
+ .gc when the last sweep ran
121
+ <path_id>/
122
+ path.json the (filename, lineno) list, for humans
123
+ sources/<hash>.json.gz full text of a captured file
124
+ <trace_id>.dump one capture
125
+ ```
126
+
127
+ Nothing is ever read to decide where a dump goes, so many processes can write
128
+ into one store with no coordination between them.
129
+
130
+ Three things bound the size:
131
+
132
+ | | bound by | when |
133
+ |---|---|---|
134
+ | dumps within a path | count, `max_dumps_per_path` | every capture |
135
+ | whole paths | age, `max_path_age_days` | hourly during capture, and `gc` |
136
+ | abandoned temporaries | age, one hour | with the sweep above |
137
+
138
+ Paths need collecting because their ids churn: the id hashes line numbers, so a
139
+ deploy that shifts a line puts every traceback through that file under a new id
140
+ and leaves the old one unreachable. Age tells those apart from failures that
141
+ simply have not recurred yet — and needs no deploy hook to do it.
142
+
143
+ Removing a path removes everything it held. No rule here reads anything but file
144
+ names, which is why the sweep can run during capture without loading a thing.
145
+
146
+ ## Source is stored, not re-read
147
+
148
+ Each captured file's full text is written into the path's `sources/` directory,
149
+ named by the hash of its contents. The inspector reads *that*, not the file on
150
+ disk.
151
+
152
+ This matters more than it sounds. Read the live file back and every dump written
153
+ before your last edit points its arrow at the wrong line — silently, and most
154
+ confusingly for the old dumps you most want to trust. Content addressing also
155
+ means a file edited between two dumps of one path simply lands in a second blob,
156
+ and two processes writing the same text write the same bytes to the same name.
157
+
158
+ ## What gets stored for a value
159
+
160
+ Each captured value is stored the cheapest way that keeps it, decided per value:
161
+
162
+ - **plain pickle** if pickle can take it and the result will resolve wherever
163
+ the dump is read;
164
+ - otherwise **dill**, which handles functions, classes and closures pickle
165
+ refuses. Everything dill takes goes into one shared stream per dump, so ten
166
+ functions from one module carry that module's namespace once between them.
167
+ Both of dill's encodings are tried and the smaller kept: neither wins in
168
+ general, and the one that lost by 8× on a batch of functions from one module
169
+ won by 3× on a single decorated one.
170
+ - **modules** are stored by name and re-imported on load, rather than pickled
171
+ whole;
172
+ - anything left becomes a capped `repr`, visible as a placeholder rather than a
173
+ failed load.
174
+
175
+ A dump that cannot be fully reconstructed still opens. A class this machine
176
+ cannot import shows as `<Unavailable pkg.Thing>` and everything around it still
177
+ reads.
178
+
179
+ Set `serializer` to `"dill"` to send everything through dill (much larger), or
180
+ `"pickle"` to drop whatever pickle will not take.
181
+
182
+ ## Configuration
183
+
184
+ `configure()` validates as it sets, so a typo fails at startup rather than
185
+ mid-exception:
186
+
187
+ ```python
188
+ from excdump import configure
189
+
190
+ configure(store_dir="/var/log/exception_dumps", max_dumps_per_path=500)
191
+ ```
192
+
193
+ Every field also reads `EXCDUMP_<NAME>` from the environment, so a deployment
194
+ can tune capture without touching code. A malformed value is ignored rather than
195
+ raised — a bad environment variable must not stop an app from starting.
196
+
197
+ | option | default | meaning |
198
+ |---|---|---|
199
+ | `store_dir` | `./.exception_dumps` | root of the dump store |
200
+ | `max_dumps_per_path` | 200 | dumps kept per failure; 0 disables pruning |
201
+ | `max_path_age_days` | 14 | age at which a whole path is dropped; 0 keeps forever |
202
+ | `gc_interval_seconds` | 3600 | how often capture sweeps; 0 leaves it to `gc` |
203
+ | `n_depth_up` | 5 | caller frames captured above the handling frame |
204
+ | `n_depth_down` | 5 | traceback frames captured below it |
205
+ | `serializer` | `"auto"` | `"auto"`, `"dill"` or `"pickle"` |
206
+ | `source_radius` | 5 | lines kept either side of each captured line |
207
+ | `max_repr_chars` | 2000 | cap on a stored `repr` |
208
+ | `max_dill_bytes` | 65536 | backstop on one dill-serialized value |
209
+ | `max_source_bytes` | 1000000 | cap on a captured file's stored text |
210
+ | `enabled` | `True` | master switch |
211
+ | `on_dump` | `None` | callback given each trace id |
212
+ | `verbose` | `False` | print the dump path to stderr |
213
+
214
+ ## Capture never breaks the caller
215
+
216
+ Everything on the capture path is written to fail quietly. A store that cannot
217
+ be written, a value that cannot be serialized, a sweep that cannot run — none of
218
+ them raise, because all of it happens with an exception already in flight and
219
+ losing the original failure is always worse.
220
+
221
+ ## Layout
222
+
223
+ | module | holds |
224
+ |---|---|
225
+ | `config.py` | `Config`, `configure()`, environment overrides |
226
+ | `paths.py` | trace and path ids, file-name conventions |
227
+ | `model.py` | `ExceptionDump`, `ExceptionRecord`, `FrameSnapshot` |
228
+ | `sources.py` | captured source and the per-path sidecar |
229
+ | `values.py` | which serializer each value gets |
230
+ | `capture.py` | `dump_exception`, `dump_on_exception` |
231
+ | `store.py` | on-disk layout, retention, collection, lookup |
232
+ | `loading.py` | reading a dump back, tolerantly |
233
+ | `session.py` | the offline debugging session |
234
+ | `cli.py` | commands and the readline inspector |
235
+ | `tui.py` | the full-screen inspector |
@@ -0,0 +1,213 @@
1
+ # excdump
2
+
3
+ Capture everything about an exception the moment it happens, then debug it later
4
+ on your own machine — with the frames, the locals, related globals, and the source as they were.
5
+
6
+ A traceback tells you where a program failed. It does not tell you what `order`
7
+ held, what `rate` had been computed to, or what the file looked like before you
8
+ edited it. `excdump` writes all of that to a file at the moment of failure, and
9
+ gives you an offline debugger to walk it afterwards.
10
+
11
+ ```python
12
+ from excdump import dump_exception
13
+
14
+ try:
15
+ handle_checkout()
16
+ except Exception:
17
+ trace_id = dump_exception() # returns the id -- log it
18
+ raise
19
+ ```
20
+
21
+ ```console
22
+ $ python -m excdump inspect <trace-id>
23
+ ```
24
+
25
+ ## Install
26
+
27
+ Requires Python 3.14+.
28
+
29
+ ```console
30
+ $ uv sync # or: pip install -e .
31
+ ```
32
+
33
+ Try it without writing any code:
34
+
35
+ ```console
36
+ $ python -m excdump demo
37
+ $ python -m excdump inspect
38
+ ```
39
+
40
+ ## Capturing
41
+
42
+ Two ways in. `dump_exception()` is called from an `except` block and returns the
43
+ trace id, which is what you log:
44
+
45
+ ```python
46
+ except Exception:
47
+ logger.error("checkout failed", extra={"trace_id": dump_exception()})
48
+ raise
49
+ ```
50
+
51
+ `@dump_on_exception` wraps a function and captures anything that escapes it,
52
+ then re-raises unchanged. It takes options, or none at all:
53
+
54
+ ```python
55
+ from excdump import dump_on_exception
56
+
57
+ @dump_on_exception
58
+ def handle_checkout(cart):
59
+ ...
60
+
61
+ @dump_on_exception(on_dump=report_to_sentry, n_depth_up=2)
62
+ def calculate_tax(order):
63
+ ...
64
+ ```
65
+
66
+ The decorated function is the pivot the session opens on, with the decorator's
67
+ own frames left out, so `up` reaches its real caller.
68
+
69
+ Both capture the frames of the traceback plus a configurable number of caller
70
+ frames above it, every exception in a `raise ... from ...` chain, and the source
71
+ of each file involved.
72
+
73
+ ## Inspecting
74
+
75
+ ```console
76
+ $ python -m excdump list # what the store holds, grouped by failure
77
+ $ python -m excdump inspect # the most recent dump
78
+ $ python -m excdump inspect <trace-id> # a specific one; a unique prefix works
79
+ $ python -m excdump inspect <path-id> # newest dump of one failure
80
+ $ python -m excdump gc # reclaim paths nothing hits any more
81
+ ```
82
+
83
+ `inspect` opens a `pdb`-like session over the dump. Frames, locals and globals
84
+ are all there; `--plain` forces the readline prompt instead of the TUI.
85
+
86
+ Nothing about the session touches the process that crashed — it no longer
87
+ exists. That is the point: you get to look around at your leisure, on a
88
+ different machine, days later.
89
+
90
+ ## How dumps are filed
91
+
92
+ A dump is filed under its **exception path** — the `(filename, lineno)` list of
93
+ its traceback, hashed. One bug hit a million times shares one path, so retention
94
+ applies per failure and a hot loop cannot push a rarer failure out of the store.
95
+
96
+ ```
97
+ .exception_dumps/
98
+ .gc when the last sweep ran
99
+ <path_id>/
100
+ path.json the (filename, lineno) list, for humans
101
+ sources/<hash>.json.gz full text of a captured file
102
+ <trace_id>.dump one capture
103
+ ```
104
+
105
+ Nothing is ever read to decide where a dump goes, so many processes can write
106
+ into one store with no coordination between them.
107
+
108
+ Three things bound the size:
109
+
110
+ | | bound by | when |
111
+ |---|---|---|
112
+ | dumps within a path | count, `max_dumps_per_path` | every capture |
113
+ | whole paths | age, `max_path_age_days` | hourly during capture, and `gc` |
114
+ | abandoned temporaries | age, one hour | with the sweep above |
115
+
116
+ Paths need collecting because their ids churn: the id hashes line numbers, so a
117
+ deploy that shifts a line puts every traceback through that file under a new id
118
+ and leaves the old one unreachable. Age tells those apart from failures that
119
+ simply have not recurred yet — and needs no deploy hook to do it.
120
+
121
+ Removing a path removes everything it held. No rule here reads anything but file
122
+ names, which is why the sweep can run during capture without loading a thing.
123
+
124
+ ## Source is stored, not re-read
125
+
126
+ Each captured file's full text is written into the path's `sources/` directory,
127
+ named by the hash of its contents. The inspector reads *that*, not the file on
128
+ disk.
129
+
130
+ This matters more than it sounds. Read the live file back and every dump written
131
+ before your last edit points its arrow at the wrong line — silently, and most
132
+ confusingly for the old dumps you most want to trust. Content addressing also
133
+ means a file edited between two dumps of one path simply lands in a second blob,
134
+ and two processes writing the same text write the same bytes to the same name.
135
+
136
+ ## What gets stored for a value
137
+
138
+ Each captured value is stored the cheapest way that keeps it, decided per value:
139
+
140
+ - **plain pickle** if pickle can take it and the result will resolve wherever
141
+ the dump is read;
142
+ - otherwise **dill**, which handles functions, classes and closures pickle
143
+ refuses. Everything dill takes goes into one shared stream per dump, so ten
144
+ functions from one module carry that module's namespace once between them.
145
+ Both of dill's encodings are tried and the smaller kept: neither wins in
146
+ general, and the one that lost by 8× on a batch of functions from one module
147
+ won by 3× on a single decorated one.
148
+ - **modules** are stored by name and re-imported on load, rather than pickled
149
+ whole;
150
+ - anything left becomes a capped `repr`, visible as a placeholder rather than a
151
+ failed load.
152
+
153
+ A dump that cannot be fully reconstructed still opens. A class this machine
154
+ cannot import shows as `<Unavailable pkg.Thing>` and everything around it still
155
+ reads.
156
+
157
+ Set `serializer` to `"dill"` to send everything through dill (much larger), or
158
+ `"pickle"` to drop whatever pickle will not take.
159
+
160
+ ## Configuration
161
+
162
+ `configure()` validates as it sets, so a typo fails at startup rather than
163
+ mid-exception:
164
+
165
+ ```python
166
+ from excdump import configure
167
+
168
+ configure(store_dir="/var/log/exception_dumps", max_dumps_per_path=500)
169
+ ```
170
+
171
+ Every field also reads `EXCDUMP_<NAME>` from the environment, so a deployment
172
+ can tune capture without touching code. A malformed value is ignored rather than
173
+ raised — a bad environment variable must not stop an app from starting.
174
+
175
+ | option | default | meaning |
176
+ |---|---|---|
177
+ | `store_dir` | `./.exception_dumps` | root of the dump store |
178
+ | `max_dumps_per_path` | 200 | dumps kept per failure; 0 disables pruning |
179
+ | `max_path_age_days` | 14 | age at which a whole path is dropped; 0 keeps forever |
180
+ | `gc_interval_seconds` | 3600 | how often capture sweeps; 0 leaves it to `gc` |
181
+ | `n_depth_up` | 5 | caller frames captured above the handling frame |
182
+ | `n_depth_down` | 5 | traceback frames captured below it |
183
+ | `serializer` | `"auto"` | `"auto"`, `"dill"` or `"pickle"` |
184
+ | `source_radius` | 5 | lines kept either side of each captured line |
185
+ | `max_repr_chars` | 2000 | cap on a stored `repr` |
186
+ | `max_dill_bytes` | 65536 | backstop on one dill-serialized value |
187
+ | `max_source_bytes` | 1000000 | cap on a captured file's stored text |
188
+ | `enabled` | `True` | master switch |
189
+ | `on_dump` | `None` | callback given each trace id |
190
+ | `verbose` | `False` | print the dump path to stderr |
191
+
192
+ ## Capture never breaks the caller
193
+
194
+ Everything on the capture path is written to fail quietly. A store that cannot
195
+ be written, a value that cannot be serialized, a sweep that cannot run — none of
196
+ them raise, because all of it happens with an exception already in flight and
197
+ losing the original failure is always worse.
198
+
199
+ ## Layout
200
+
201
+ | module | holds |
202
+ |---|---|
203
+ | `config.py` | `Config`, `configure()`, environment overrides |
204
+ | `paths.py` | trace and path ids, file-name conventions |
205
+ | `model.py` | `ExceptionDump`, `ExceptionRecord`, `FrameSnapshot` |
206
+ | `sources.py` | captured source and the per-path sidecar |
207
+ | `values.py` | which serializer each value gets |
208
+ | `capture.py` | `dump_exception`, `dump_on_exception` |
209
+ | `store.py` | on-disk layout, retention, collection, lookup |
210
+ | `loading.py` | reading a dump back, tolerantly |
211
+ | `session.py` | the offline debugging session |
212
+ | `cli.py` | commands and the readline inspector |
213
+ | `tui.py` | the full-screen inspector |
@@ -0,0 +1,120 @@
1
+ """Capture rich exception snapshots and inspect them offline.
2
+
3
+ The dump stores the whole exception chain (``__cause__`` / ``__context__``), so
4
+ the inspector can walk between chained exceptions the way modern pdb does with
5
+ its ``exceptions`` command, in addition to walking frames within one exception.
6
+
7
+ Dumps are kept small: source is stored once per file (as merged line windows
8
+ keyed by a path relative to the capture root, not per frame), each object is
9
+ serialized once no matter how many frames reference it, the whole file is
10
+ gzipped, and each value is stored with the cheapest serializer that can hold
11
+ it -- plain pickle for almost everything, dill only where pickle fails.
12
+ The full text of every captured file is written once per exception
13
+ path, beside the dumps, and referenced by content hash -- the inspector reads
14
+ that instead of the file on disk, so line numbers still line up after the code
15
+ has moved on. :func:`set_serializer` overrides that per-value choice with strict ``dill``
16
+ or strict ``pickle``.
17
+
18
+ In production, capture is configured once and then needs no arguments::
19
+
20
+ configure(store_dir="/var/log/exception_dumps", max_dumps_per_path=1000,
21
+ on_dump=lambda trace_id: log.error("dump %s", trace_id))
22
+
23
+ try:
24
+ ...
25
+ except Exception:
26
+ trace_id = dump_exception() # returns the id to log
27
+
28
+ Dumps are filed by *exception path* -- the ``(filename, lineno)`` list of the
29
+ traceback -- and each path keeps only its most recent
30
+ ``CONFIG.max_dumps_per_path`` dumps, so a hot failure loop cannot fill the disk
31
+ and cannot push other, rarer failures out of the store. Paths themselves are
32
+ reclaimed by age -- see ``python -m excdump gc``.
33
+
34
+ The implementation is split by responsibility -- :mod:`~excdump.config`,
35
+ :mod:`~excdump.paths`, :mod:`~excdump.model`, :mod:`~excdump.sources`,
36
+ :mod:`~excdump.values`, :mod:`~excdump.capture`, :mod:`~excdump.store`,
37
+ :mod:`~excdump.loading`, :mod:`~excdump.session`, :mod:`~excdump.cli` --
38
+ and everything a caller needs is re-exported here.
39
+ """
40
+
41
+ from .capture import dump_exception, dump_on_exception
42
+ from .cli import (
43
+ COMMANDS,
44
+ OfflinePdb,
45
+ dispatch,
46
+ gc_command,
47
+ help_text,
48
+ list_command,
49
+ load_and_debug,
50
+ main,
51
+ plain_loop,
52
+ )
53
+ from .config import (
54
+ CONFIG,
55
+ SERIALIZERS,
56
+ Config,
57
+ SerializerName,
58
+ Unset,
59
+ UNSET,
60
+ configure,
61
+ get_serializer,
62
+ logger,
63
+ set_serializer,
64
+ )
65
+ from .loading import load_dump
66
+ from .model import (
67
+ ExceptionDump,
68
+ ExceptionRecord,
69
+ FrameSnapshot,
70
+ MissingRef,
71
+ )
72
+ from .paths import (
73
+ DUMP_SUFFIX,
74
+ PATH_META,
75
+ SOURCE_DIR,
76
+ SOURCE_SUFFIX,
77
+ exception_path,
78
+ path_id,
79
+ relative_path,
80
+ trace_path_id,
81
+ )
82
+ from .session import DebuggerSession
83
+ from .sources import SourceFile, SourceStore
84
+ from .store import DumpStore, default_store, resolve_dump
85
+ from .values import _DillRef, _ModuleRef, _ValueFilter
86
+
87
+ __all__ = [
88
+ "CONFIG",
89
+ "COMMANDS",
90
+ "Config",
91
+ "DebuggerSession",
92
+ "DumpStore",
93
+ "ExceptionDump",
94
+ "ExceptionRecord",
95
+ "FrameSnapshot",
96
+ "MissingRef",
97
+ "OfflinePdb",
98
+ "SerializerName",
99
+ "SourceFile",
100
+ "SourceStore",
101
+ "configure",
102
+ "default_store",
103
+ "dispatch",
104
+ "dump_exception",
105
+ "dump_on_exception",
106
+ "exception_path",
107
+ "gc_command",
108
+ "get_serializer",
109
+ "help_text",
110
+ "list_command",
111
+ "load_and_debug",
112
+ "load_dump",
113
+ "main",
114
+ "path_id",
115
+ "plain_loop",
116
+ "relative_path",
117
+ "resolve_dump",
118
+ "set_serializer",
119
+ "trace_path_id",
120
+ ]
@@ -0,0 +1,7 @@
1
+ """``python -m excdump ...`` -- same commands as the CLI."""
2
+
3
+ import sys
4
+
5
+ from .cli import main
6
+
7
+ raise SystemExit(main(sys.argv))