error-investigator 1.0.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.
- error_investigator-1.0.0/LICENSE +21 -0
- error_investigator-1.0.0/PKG-INFO +681 -0
- error_investigator-1.0.0/README.md +654 -0
- error_investigator-1.0.0/pyproject.toml +51 -0
- error_investigator-1.0.0/setup.cfg +4 -0
- error_investigator-1.0.0/src/error_investigator.egg-info/PKG-INFO +681 -0
- error_investigator-1.0.0/src/error_investigator.egg-info/SOURCES.txt +61 -0
- error_investigator-1.0.0/src/error_investigator.egg-info/dependency_links.txt +1 -0
- error_investigator-1.0.0/src/error_investigator.egg-info/entry_points.txt +2 -0
- error_investigator-1.0.0/src/error_investigator.egg-info/requires.txt +6 -0
- error_investigator-1.0.0/src/error_investigator.egg-info/top_level.txt +1 -0
- error_investigator-1.0.0/src/investigator/__init__.py +17 -0
- error_investigator-1.0.0/src/investigator/cli.py +59 -0
- error_investigator-1.0.0/src/investigator/cli_format.py +123 -0
- error_investigator-1.0.0/src/investigator/error_parser.py +81 -0
- error_investigator-1.0.0/src/investigator/fastapi.py +258 -0
- error_investigator-1.0.0/src/investigator/investigation.py +17 -0
- error_investigator-1.0.0/src/investigator/investigation_result.py +80 -0
- error_investigator-1.0.0/src/investigator/investigator.py +39 -0
- error_investigator-1.0.0/src/investigator/io_utils.py +27 -0
- error_investigator-1.0.0/src/investigator/knowledge/__init__.py +0 -0
- error_investigator-1.0.0/src/investigator/knowledge/httpx_connect_error.yaml +27 -0
- error_investigator-1.0.0/src/investigator/knowledge/json_decode_error.yaml +29 -0
- error_investigator-1.0.0/src/investigator/knowledge/pydantic_validation_error.yaml +28 -0
- error_investigator-1.0.0/src/investigator/knowledge/python_attribute_error.yaml +28 -0
- error_investigator-1.0.0/src/investigator/knowledge/python_connection_error.yaml +27 -0
- error_investigator-1.0.0/src/investigator/knowledge/python_file_not_found_error.yaml +28 -0
- error_investigator-1.0.0/src/investigator/knowledge/python_index_error.yaml +28 -0
- error_investigator-1.0.0/src/investigator/knowledge/python_key_error.yaml +31 -0
- error_investigator-1.0.0/src/investigator/knowledge/python_module_not_found_error.yaml +28 -0
- error_investigator-1.0.0/src/investigator/knowledge/python_permission_error.yaml +28 -0
- error_investigator-1.0.0/src/investigator/knowledge/python_timeout_error.yaml +27 -0
- error_investigator-1.0.0/src/investigator/knowledge/python_type_error.yaml +28 -0
- error_investigator-1.0.0/src/investigator/knowledge/python_value_error.yaml +30 -0
- error_investigator-1.0.0/src/investigator/knowledge/requests_connection_error.yaml +29 -0
- error_investigator-1.0.0/src/investigator/knowledge/requests_timeout_error.yaml +28 -0
- error_investigator-1.0.0/src/investigator/knowledge/sqlalchemy_data_error.yaml +31 -0
- error_investigator-1.0.0/src/investigator/knowledge/sqlalchemy_integrity_error.yaml +29 -0
- error_investigator-1.0.0/src/investigator/knowledge/sqlalchemy_multiple_results_found.yaml +29 -0
- error_investigator-1.0.0/src/investigator/knowledge/sqlalchemy_no_result_found.yaml +29 -0
- error_investigator-1.0.0/src/investigator/knowledge/sqlalchemy_operational_error.yaml +31 -0
- error_investigator-1.0.0/src/investigator/knowledge_loader.py +147 -0
- error_investigator-1.0.0/src/investigator/knowledge_lookup.py +107 -0
- error_investigator-1.0.0/src/investigator/sdk.py +82 -0
- error_investigator-1.0.0/tests/test_cli.py +385 -0
- error_investigator-1.0.0/tests/test_cli_format.py +151 -0
- error_investigator-1.0.0/tests/test_edge_cases.py +295 -0
- error_investigator-1.0.0/tests/test_edge_cases_adversarial.py +407 -0
- error_investigator-1.0.0/tests/test_end_to_end_known.py +138 -0
- error_investigator-1.0.0/tests/test_end_to_end_unknown.py +115 -0
- error_investigator-1.0.0/tests/test_error_parser.py +77 -0
- error_investigator-1.0.0/tests/test_error_parser_contract.py +147 -0
- error_investigator-1.0.0/tests/test_fastapi.py +607 -0
- error_investigator-1.0.0/tests/test_investigation.py +67 -0
- error_investigator-1.0.0/tests/test_investigation_result.py +146 -0
- error_investigator-1.0.0/tests/test_investigator.py +143 -0
- error_investigator-1.0.0/tests/test_io_utils.py +173 -0
- error_investigator-1.0.0/tests/test_kb_collisions.py +151 -0
- error_investigator-1.0.0/tests/test_kb_expansion.py +81 -0
- error_investigator-1.0.0/tests/test_knowledge_loader.py +238 -0
- error_investigator-1.0.0/tests/test_knowledge_lookup.py +152 -0
- error_investigator-1.0.0/tests/test_public_api.py +45 -0
- error_investigator-1.0.0/tests/test_sdk.py +297 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Head2On
|
|
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 THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,681 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: error-investigator
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: A CLI developer tool for investigating API errors
|
|
5
|
+
Author-email: ISHAN KUMAR <ishankumar063@gmail.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Keywords: cli,developer-tools,error-analysis,debugging,python
|
|
8
|
+
Classifier: Development Status :: 4 - Beta
|
|
9
|
+
Classifier: Environment :: Console
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: Topic :: Software Development :: Debuggers
|
|
17
|
+
Classifier: Topic :: Software Development :: Quality Assurance
|
|
18
|
+
Requires-Python: >=3.10
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
License-File: LICENSE
|
|
21
|
+
Requires-Dist: typer
|
|
22
|
+
Requires-Dist: pyyaml
|
|
23
|
+
Provides-Extra: fastapi
|
|
24
|
+
Requires-Dist: fastapi>=0.100.0; extra == "fastapi"
|
|
25
|
+
Requires-Dist: httpx>=0.24.0; extra == "fastapi"
|
|
26
|
+
Dynamic: license-file
|
|
27
|
+
|
|
28
|
+
# Error Investigator
|
|
29
|
+
|
|
30
|
+
[](https://www.python.org/downloads/)
|
|
31
|
+
[](https://opensource.org/licenses/MIT)
|
|
32
|
+
[]()
|
|
33
|
+
[]()
|
|
34
|
+
|
|
35
|
+
A fast, deterministic CLI developer tool that investigates Python and API errors by parsing tracebacks, matching them against a curated knowledge base, and returning structured diagnoses with root causes and actionable fixes.
|
|
36
|
+
|
|
37
|
+
**Status:** Deterministic offline engine — zero external network requests in the shipped tool.
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## Key Features
|
|
42
|
+
|
|
43
|
+
- **Deterministic & Instant:** Powered by an offline, indexed knowledge base. Zero latency, zero external network requests, completely reproducible.
|
|
44
|
+
- **Honest by Design:** If an error is unknown, it returns `Status: UNKNOWN`. The tool **never fabricates** a diagnosis.
|
|
45
|
+
- **Dual Interaction Modes:**
|
|
46
|
+
- **Interactive TTY:** Paste multiline tracebacks directly with early directory validation and simple `DONE` submission.
|
|
47
|
+
- **Piped Mode:** Pipe stderr or log files directly (`python app.py 2>&1 | investigate`) with silent, prompt-free execution.
|
|
48
|
+
- **Extensible Knowledge Base:** Bring custom error catalogs via `--kb-dir <path>` with automatic schema validation and collision detection.
|
|
49
|
+
- **100% Tested:** Comprehensive test suite with 100% statement coverage across all modules.
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## How It Works
|
|
54
|
+
|
|
55
|
+
```text
|
|
56
|
+
Raw Error String / Traceback
|
|
57
|
+
│
|
|
58
|
+
▼
|
|
59
|
+
┌──────────────────────────────────────────────┐
|
|
60
|
+
│ parse_error() │
|
|
61
|
+
│ • Extracts error_type, message, file, line │
|
|
62
|
+
└──────────────────────┬───────────────────────┘
|
|
63
|
+
▼
|
|
64
|
+
ParsedError
|
|
65
|
+
│
|
|
66
|
+
▼
|
|
67
|
+
┌──────────────────────────────────────────────┐
|
|
68
|
+
│ lookup() │
|
|
69
|
+
│ • Matches primary error_type or aliases │
|
|
70
|
+
│ • Validates custom KB directory upfront │
|
|
71
|
+
└──────────────────────┬───────────────────────┘
|
|
72
|
+
▼
|
|
73
|
+
KnowledgeEntry | None
|
|
74
|
+
│
|
|
75
|
+
┌─────────────┴─────────────┐
|
|
76
|
+
▼ ▼
|
|
77
|
+
[Match Found] [No Match]
|
|
78
|
+
Status: KNOWN Status: UNKNOWN
|
|
79
|
+
Structured root causes Honest fallback
|
|
80
|
+
& proven fixes (never fabricated)
|
|
81
|
+
│ │
|
|
82
|
+
└─────────────┬─────────────┘
|
|
83
|
+
▼
|
|
84
|
+
format_result()
|
|
85
|
+
│
|
|
86
|
+
▼
|
|
87
|
+
Terminal Output
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
## Installation
|
|
93
|
+
|
|
94
|
+
### Prerequisites
|
|
95
|
+
|
|
96
|
+
- Python 3.10 or higher
|
|
97
|
+
- Git
|
|
98
|
+
|
|
99
|
+
### Install with pip (Recommended)
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
pip install error-investigator
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
With optional FastAPI middleware support:
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
pip install "error-investigator[fastapi]"
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### Install from Source
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
git clone https://github.com/Head2On/api-error-investigator.git
|
|
115
|
+
cd api-error-investigator
|
|
116
|
+
|
|
117
|
+
# Create and activate virtual environment
|
|
118
|
+
python -m venv .venv
|
|
119
|
+
|
|
120
|
+
# On Linux / macOS:
|
|
121
|
+
source .venv/bin/activate
|
|
122
|
+
|
|
123
|
+
# On Windows (PowerShell):
|
|
124
|
+
.venv\Scripts\Activate.ps1
|
|
125
|
+
|
|
126
|
+
# Install in editable mode
|
|
127
|
+
pip install -e .
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
Verify the installation:
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
investigate --help
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
---
|
|
137
|
+
|
|
138
|
+
## Quickstart & Usage
|
|
139
|
+
|
|
140
|
+
### 1. Interactive Mode
|
|
141
|
+
|
|
142
|
+
Run `investigate` without arguments:
|
|
143
|
+
|
|
144
|
+
```bash
|
|
145
|
+
investigate
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
Paste your traceback, type `DONE` on a new line (case-insensitive), and press **Enter** (or send EOF with `Ctrl+D` / `Ctrl+Z`):
|
|
149
|
+
|
|
150
|
+
```text
|
|
151
|
+
Paste your error below.
|
|
152
|
+
(Type 'DONE' on a new blank line and press Enter to submit):
|
|
153
|
+
|
|
154
|
+
ValueError: invalid literal for int() with base 10: 'abc'
|
|
155
|
+
DONE
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
#### Sample Output (Known Error)
|
|
159
|
+
|
|
160
|
+
```text
|
|
161
|
+
Error: ValueError
|
|
162
|
+
Status: KNOWN
|
|
163
|
+
|
|
164
|
+
Summary:
|
|
165
|
+
A function received a value of the correct type but with an invalid content.
|
|
166
|
+
|
|
167
|
+
Explanation:
|
|
168
|
+
ValueError means the argument had the right type, but the value itself was not
|
|
169
|
+
acceptable to the function. It's not a type error (which would mean the wrong type was
|
|
170
|
+
passed) -- it's a content error. Built-in functions, int(), float(), datetime parsing,
|
|
171
|
+
and many libraries raise it for malformed input.
|
|
172
|
+
|
|
173
|
+
Common Causes:
|
|
174
|
+
- Calling int("abc") or float("12.3.4") on non-numeric strings.
|
|
175
|
+
- Unpacking a sequence with the wrong number of elements.
|
|
176
|
+
- Passing a negative number to a function that requires non-negative.
|
|
177
|
+
- Parsing a malformed date/time or UUID string.
|
|
178
|
+
- Iterating over a closed file or reading from a closed resource.
|
|
179
|
+
|
|
180
|
+
Fixes:
|
|
181
|
+
- Read the error message -- Python's ValueError almost always states what's wrong.
|
|
182
|
+
- Wrap the call in try/except and validate input before passing it in.
|
|
183
|
+
- If parsing user input, use a parser that returns a result type instead (e.g.
|
|
184
|
+
Pydantic).
|
|
185
|
+
- Check the argument at the call site, not deep inside the function.
|
|
186
|
+
- For unpacking errors, print or log the value to see how many items it actually has.
|
|
187
|
+
|
|
188
|
+
Documentation:
|
|
189
|
+
https://docs.python.org/3/library/exceptions.html#ValueError
|
|
190
|
+
|
|
191
|
+
Related Errors:
|
|
192
|
+
- TypeError
|
|
193
|
+
- KeyError
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
#### Sample Output (Unknown Error)
|
|
197
|
+
|
|
198
|
+
```text
|
|
199
|
+
Error: SomeUnregisteredCustomError
|
|
200
|
+
Status: UNKNOWN
|
|
201
|
+
|
|
202
|
+
The Knowledge Base has no matching entry for this error. No diagnosis was produced.
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
---
|
|
206
|
+
|
|
207
|
+
### 2. Piped Mode (Scripts, CI, Logs)
|
|
208
|
+
|
|
209
|
+
In piped mode (when stdin is not a TTY), `investigate` runs silently to EOF without interactive prompts or sentinels:
|
|
210
|
+
|
|
211
|
+
```bash
|
|
212
|
+
# Pipe directly from a failing script
|
|
213
|
+
python script.py 2>&1 | investigate
|
|
214
|
+
|
|
215
|
+
# Pipe from a saved traceback or log file
|
|
216
|
+
cat traceback.log | investigate
|
|
217
|
+
|
|
218
|
+
# Pipe from PowerShell
|
|
219
|
+
Get-Content error.log | investigate
|
|
220
|
+
|
|
221
|
+
# Pipe a single error string directly
|
|
222
|
+
echo "KeyError: 'user_id'" | investigate
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
---
|
|
226
|
+
|
|
227
|
+
### 3. Custom Knowledge Base (`--kb-dir`)
|
|
228
|
+
|
|
229
|
+
Use your team's custom knowledge base directory instead of the built-in catalog:
|
|
230
|
+
|
|
231
|
+
```bash
|
|
232
|
+
investigate --kb-dir ./custom_kb
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
Piped mode works with custom knowledge bases as well:
|
|
236
|
+
|
|
237
|
+
```bash
|
|
238
|
+
python app.py 2>&1 | investigate --kb-dir ./custom_kb
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
> [!NOTE]
|
|
242
|
+
> **Early Validation:** If `--kb-dir` does not exist, is not a directory, contains no YAML files, has malformed YAML, or contains identifier collisions, `investigate` prints an error and exits **before** prompting for input.
|
|
243
|
+
|
|
244
|
+
---
|
|
245
|
+
|
|
246
|
+
## CLI Reference
|
|
247
|
+
|
|
248
|
+
```text
|
|
249
|
+
Usage: investigate [OPTIONS]
|
|
250
|
+
|
|
251
|
+
Error Investigator — a CLI developer tool for investigating Python and API
|
|
252
|
+
errors.
|
|
253
|
+
|
|
254
|
+
╭─ Options ────────────────────────────────────────────────────────────────────╮
|
|
255
|
+
│ --kb-dir <path> Path to a custom knowledge base directory. │
|
|
256
|
+
│ --help Show this message and exit. │
|
|
257
|
+
╰──────────────────────────────────────────────────────────────────────────────╯
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
### Exit Codes
|
|
261
|
+
|
|
262
|
+
| Code | Meaning | Reason |
|
|
263
|
+
| :---: | :--- | :--- |
|
|
264
|
+
| **`0`** | **Success** | Investigation completed normally (for both `KNOWN` and `UNKNOWN` errors). |
|
|
265
|
+
| **`1`** | **Execution Error** | Empty input received, or custom knowledge base failed validation (nonexistent path, empty folder, invalid YAML, or identifier collision). |
|
|
266
|
+
| **`2`** | **Invocation Error** | Invalid CLI usage (e.g. unrecognized options or unexpected positional arguments). |
|
|
267
|
+
|
|
268
|
+
---
|
|
269
|
+
|
|
270
|
+
## Knowledge Base Schema
|
|
271
|
+
|
|
272
|
+
Each knowledge base entry is a YAML file placed in the knowledge directory.
|
|
273
|
+
|
|
274
|
+
### YAML Specification
|
|
275
|
+
|
|
276
|
+
```yaml
|
|
277
|
+
error_type: my_service.errors.UserNotFoundError
|
|
278
|
+
aliases:
|
|
279
|
+
- UserNotFoundError
|
|
280
|
+
- AccountNotFound
|
|
281
|
+
category: database
|
|
282
|
+
subcategory: entity_lookup # optional (string or null)
|
|
283
|
+
summary: Raised when a user record cannot be found in the database.
|
|
284
|
+
explanation: >
|
|
285
|
+
Occurs when querying for a user ID that does not exist in the database,
|
|
286
|
+
or when an account has been soft-deleted and filtered out by active tenant rules.
|
|
287
|
+
common_causes:
|
|
288
|
+
- The requested user_id does not exist in the users table.
|
|
289
|
+
- The record is marked as deleted (deleted_at is not NULL).
|
|
290
|
+
- The query is scoped to the wrong tenant ID.
|
|
291
|
+
fixes:
|
|
292
|
+
- Check if user_id is correct before executing the query.
|
|
293
|
+
- Use get_or_none() or handle UserNotFoundError explicitly.
|
|
294
|
+
- Verify tenant isolation filter context.
|
|
295
|
+
docs_url: https://docs.example.com/errors/user-not-found # optional (string or null)
|
|
296
|
+
related_errors:
|
|
297
|
+
- AuthenticationError
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
### Schema Rules & Invariants
|
|
301
|
+
|
|
302
|
+
1. **Required Fields:**
|
|
303
|
+
- `error_type` *(string)*: Primary fully-qualified error class identifier.
|
|
304
|
+
- `category` *(string)*: High-level domain category (e.g., `database`, `python`, `network`).
|
|
305
|
+
- `summary` *(string)*: High-level summary of the issue.
|
|
306
|
+
- `explanation` *(string)*: Detailed explanation of the error context.
|
|
307
|
+
- `common_causes` *(list of strings)*: Typical root causes.
|
|
308
|
+
- `fixes` *(list of strings)*: Practical, actionable remedies.
|
|
309
|
+
- `aliases` *(list of strings)*: Alternative names (e.g. unqualified class names).
|
|
310
|
+
- `related_errors` *(list of strings)*: Associated error types.
|
|
311
|
+
2. **Optional Fields:**
|
|
312
|
+
- `subcategory` *(string or null)*: Granular subcategory.
|
|
313
|
+
- `docs_url` *(string or null)*: Official documentation link.
|
|
314
|
+
3. **Collision Invariant:**
|
|
315
|
+
No identifier may appear as the `error_type` or `alias` of more than one entry or more than once within a single entry across the entire knowledge base. Any overlap triggers an upfront `KnowledgeEntryError`.
|
|
316
|
+
|
|
317
|
+
---
|
|
318
|
+
|
|
319
|
+
## Built-in Knowledge Base
|
|
320
|
+
|
|
321
|
+
Out of the box, `investigate` includes curated diagnostics for:
|
|
322
|
+
|
|
323
|
+
| Error Type | Category | Aliases |
|
|
324
|
+
|---|---|---|
|
|
325
|
+
| **Python builtins** | | |
|
|
326
|
+
| `ValueError` | `python` | — |
|
|
327
|
+
| `KeyError` | `python` | — |
|
|
328
|
+
| `AttributeError` | `python` | — |
|
|
329
|
+
| `TypeError` | `python` | — |
|
|
330
|
+
| `IndexError` | `python` | — |
|
|
331
|
+
| `ModuleNotFoundError` | `python` | — |
|
|
332
|
+
| **Filesystem** | | |
|
|
333
|
+
| `FileNotFoundError` | `filesystem` | — |
|
|
334
|
+
| `PermissionError` | `filesystem` | — |
|
|
335
|
+
| **Network** | | |
|
|
336
|
+
| `ConnectionError` | `network` | — |
|
|
337
|
+
| `TimeoutError` | `network` | — |
|
|
338
|
+
| `requests.exceptions.ConnectionError` | `network` | `requests.ConnectionError` |
|
|
339
|
+
| `requests.exceptions.Timeout` | `network` | `requests.Timeout` |
|
|
340
|
+
| `httpx.ConnectError` | `network` | — |
|
|
341
|
+
| **Data** | | |
|
|
342
|
+
| `json.JSONDecodeError` | `data` | `JSONDecodeError` |
|
|
343
|
+
| `pydantic.ValidationError` | `data` | — |
|
|
344
|
+
| **Database** | | |
|
|
345
|
+
| `sqlalchemy.exc.IntegrityError` | `database` | `IntegrityError` |
|
|
346
|
+
| `sqlalchemy.exc.OperationalError` | `database` | `OperationalError` |
|
|
347
|
+
| `sqlalchemy.exc.DataError` | `database` | `DataError` |
|
|
348
|
+
| `sqlalchemy.exc.NoResultFound` | `database` | `NoResultFound` |
|
|
349
|
+
| `sqlalchemy.exc.MultipleResultsFound` | `database` | `MultipleResultsFound` |
|
|
350
|
+
|
|
351
|
+
---
|
|
352
|
+
|
|
353
|
+
## Python SDK & Programmatic API
|
|
354
|
+
|
|
355
|
+
Error Investigator provides both high-level runtime utilities in `investigator.sdk` and direct parsing functions in `investigator`.
|
|
356
|
+
|
|
357
|
+
### 1. Investigating Caught Exceptions (`investigate_exception`)
|
|
358
|
+
|
|
359
|
+
Use `investigate_exception` to inspect an active or caught exception instance directly without manually extracting tracebacks.
|
|
360
|
+
|
|
361
|
+
- **Pure Function:** Zero side effects — never prints, logs, or re-raises.
|
|
362
|
+
- **Traceback-Aware:** Analyzes active exception tracebacks, chained exceptions (`from e`), and unraised exceptions (a never-raised exception is formatted as `TypeName: message` and investigated normally).
|
|
363
|
+
- **Custom KB Support:** Supports custom `--kb-dir` catalogs via the `kb_dir` keyword argument.
|
|
364
|
+
|
|
365
|
+
```python
|
|
366
|
+
from investigator.sdk import investigate_exception
|
|
367
|
+
|
|
368
|
+
try:
|
|
369
|
+
int("invalid")
|
|
370
|
+
except ValueError as exc:
|
|
371
|
+
result = investigate_exception(exc)
|
|
372
|
+
|
|
373
|
+
if result.status == "known":
|
|
374
|
+
assert result.investigation is not None
|
|
375
|
+
print(f"Error Type: {result.parsed_error['error_type']}")
|
|
376
|
+
print(f"Summary: {result.investigation.summary}")
|
|
377
|
+
print("Recommended Fixes:")
|
|
378
|
+
for fix in result.investigation.fixes:
|
|
379
|
+
print(f" • {fix}")
|
|
380
|
+
else:
|
|
381
|
+
print("Uncataloged error. No diagnosis produced.")
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
---
|
|
385
|
+
|
|
386
|
+
### 2. Automated Exception Capture (`capture_exception`)
|
|
387
|
+
|
|
388
|
+
`capture_exception` acts as both a **context manager** and a **decorator** to monitor code blocks or functions. When an `Exception` is raised, it automatically investigates the error, outputs formatted diagnosis to `sys.stderr` (or a custom sink), and optionally re-raises.
|
|
389
|
+
|
|
390
|
+
#### As a Context Manager
|
|
391
|
+
|
|
392
|
+
```python
|
|
393
|
+
from investigator.sdk import capture_exception
|
|
394
|
+
|
|
395
|
+
# Default: prints formatted diagnosis to stderr and re-raises the original exception
|
|
396
|
+
with capture_exception():
|
|
397
|
+
data = load_data()
|
|
398
|
+
|
|
399
|
+
# Suppress exception after diagnosing (reraise=False)
|
|
400
|
+
with capture_exception(reraise=False):
|
|
401
|
+
risky_operation()
|
|
402
|
+
|
|
403
|
+
# Route diagnoses to a custom sink (e.g. logger, monitoring, or telemetry)
|
|
404
|
+
def log_sink(result):
|
|
405
|
+
my_logger.error("Investigated failure: %s", result.parsed_error["error_type"])
|
|
406
|
+
if result.investigation:
|
|
407
|
+
my_logger.info("Summary: %s", result.investigation.summary)
|
|
408
|
+
|
|
409
|
+
with capture_exception(sink=log_sink):
|
|
410
|
+
process_order()
|
|
411
|
+
```
|
|
412
|
+
|
|
413
|
+
#### As a Function Decorator
|
|
414
|
+
|
|
415
|
+
Works as either a bare decorator or parameterized with options:
|
|
416
|
+
|
|
417
|
+
```python
|
|
418
|
+
from investigator.sdk import capture_exception
|
|
419
|
+
|
|
420
|
+
# Bare decorator: diagnoses to stderr and re-raises on exception
|
|
421
|
+
@capture_exception
|
|
422
|
+
def query_user(user_id: int):
|
|
423
|
+
return db.query(User).filter_by(id=user_id).one()
|
|
424
|
+
|
|
425
|
+
# Parameterized decorator: custom sink and suppression
|
|
426
|
+
@capture_exception(reraise=False, sink=custom_sink)
|
|
427
|
+
def background_sync():
|
|
428
|
+
sync_remote_records()
|
|
429
|
+
|
|
430
|
+
# With custom knowledge base directory
|
|
431
|
+
@capture_exception(kb_dir="./custom_kb")
|
|
432
|
+
def calculate_metrics():
|
|
433
|
+
compute_heavy_stats()
|
|
434
|
+
```
|
|
435
|
+
|
|
436
|
+
> [!NOTE]
|
|
437
|
+
> **Safe for Repeated Calls:** Decorated functions are reentrant and safe for repeated calls across the application lifecycle.
|
|
438
|
+
|
|
439
|
+
---
|
|
440
|
+
|
|
441
|
+
### 3. Direct Traceback String Investigation (`investigate`)
|
|
442
|
+
|
|
443
|
+
For investigating raw traceback strings from external logs, worker queues, or stdin streams:
|
|
444
|
+
|
|
445
|
+
```python
|
|
446
|
+
from investigator import investigate
|
|
447
|
+
|
|
448
|
+
traceback_text = """
|
|
449
|
+
Traceback (most recent call last):
|
|
450
|
+
File "main.py", line 12, in <module>
|
|
451
|
+
user = users[user_id]
|
|
452
|
+
KeyError: 'user_id'
|
|
453
|
+
"""
|
|
454
|
+
|
|
455
|
+
result = investigate(traceback_text)
|
|
456
|
+
|
|
457
|
+
if result.status == "known":
|
|
458
|
+
assert result.investigation is not None
|
|
459
|
+
print(f"Summary: {result.investigation.summary}")
|
|
460
|
+
```
|
|
461
|
+
|
|
462
|
+
---
|
|
463
|
+
|
|
464
|
+
### 4. Custom Knowledge Base & Error Handling
|
|
465
|
+
|
|
466
|
+
When providing a custom knowledge base directory (`kb_dir`) to `investigate_exception` or `capture_exception`, the directory is validated upfront (checking path existence, directory type, presence of YAML files, schema integrity, and identifier collisions). If validation fails, `KnowledgeEntryError` is raised immediately.
|
|
467
|
+
|
|
468
|
+
```python
|
|
469
|
+
from pathlib import Path
|
|
470
|
+
from investigator.sdk import KnowledgeEntryError, investigate_exception
|
|
471
|
+
|
|
472
|
+
try:
|
|
473
|
+
result = investigate_exception(exc, kb_dir=Path("./invalid_or_missing_kb"))
|
|
474
|
+
except KnowledgeEntryError as err:
|
|
475
|
+
print(f"Custom knowledge base error: {err}")
|
|
476
|
+
```
|
|
477
|
+
|
|
478
|
+
---
|
|
479
|
+
|
|
480
|
+
### 5. SDK Reference & Invariants
|
|
481
|
+
|
|
482
|
+
| Component | Signature / Usage | Description |
|
|
483
|
+
| :--- | :--- | :--- |
|
|
484
|
+
| **`investigate_exception`** | `investigate_exception(exc, *, kb_dir=None) -> InvestigationResult` | Pure function returning structured diagnosis. Propagates `KnowledgeEntryError` on invalid `kb_dir`. |
|
|
485
|
+
| **`capture_exception`** | `@capture_exception`<br>`with capture_exception(*, reraise=True, sink=None, kb_dir=None):` | Context manager & decorator. Formats diagnosis to `sys.stderr` by default and preserves traceback on re-raise. |
|
|
486
|
+
| **`KnowledgeEntryError`** | `from investigator.sdk import KnowledgeEntryError` | Re-exported exception raised when custom knowledge base fails schema or collision validation. |
|
|
487
|
+
|
|
488
|
+
#### Product Invariants
|
|
489
|
+
- **Non-Interference with System Signals:** `KeyboardInterrupt` and `SystemExit` are **never** caught or intercepted by `capture_exception`; they pass through cleanly.
|
|
490
|
+
- **Sink Customization:** Supplying a callable `sink` bypasses `sys.stderr` emission entirely, directing the `InvestigationResult` directly into your logging or telemetry pipeline.
|
|
491
|
+
|
|
492
|
+
---
|
|
493
|
+
|
|
494
|
+
## FastAPI & Starlette Middleware
|
|
495
|
+
|
|
496
|
+
Error Investigator provides `InvestigatorMiddleware` for FastAPI and Starlette applications, enabling automatic error interception, request context extraction, credential sanitization, and structured diagnosis routing.
|
|
497
|
+
|
|
498
|
+
Install with FastAPI support:
|
|
499
|
+
|
|
500
|
+
```bash
|
|
501
|
+
pip install error-investigator[fastapi]
|
|
502
|
+
```
|
|
503
|
+
|
|
504
|
+
### 1. Basic Setup (Default Re-Raise)
|
|
505
|
+
|
|
506
|
+
By default (`reraise=True`), unhandled route exceptions are caught, investigated, rendered to `sys.stderr`, and re-raised for your standard exception handlers:
|
|
507
|
+
|
|
508
|
+
```python
|
|
509
|
+
from fastapi import FastAPI
|
|
510
|
+
from investigator.fastapi import InvestigatorMiddleware
|
|
511
|
+
|
|
512
|
+
app = FastAPI()
|
|
513
|
+
app.add_middleware(InvestigatorMiddleware)
|
|
514
|
+
|
|
515
|
+
@app.get("/users/{user_id}")
|
|
516
|
+
def get_user(user_id: int):
|
|
517
|
+
# If this raises a KeyError or ValueError, it will be automatically diagnosed
|
|
518
|
+
return database[user_id]
|
|
519
|
+
```
|
|
520
|
+
|
|
521
|
+
### 2. Suppressed Mode & Response Exposure (`reraise=False`)
|
|
522
|
+
|
|
523
|
+
To catch unhandled exceptions and return a clean HTTP 500 response without crashing the server process, set `reraise=False`:
|
|
524
|
+
|
|
525
|
+
```python
|
|
526
|
+
app.add_middleware(InvestigatorMiddleware, reraise=False)
|
|
527
|
+
```
|
|
528
|
+
|
|
529
|
+
By default, the returned JSON response is strictly minimal to prevent internal implementation details from leaking to clients:
|
|
530
|
+
|
|
531
|
+
```json
|
|
532
|
+
{
|
|
533
|
+
"error": "InternalServerError"
|
|
534
|
+
}
|
|
535
|
+
```
|
|
536
|
+
Headers:
|
|
537
|
+
`X-Error-Investigated: true`
|
|
538
|
+
`X-Error-Status: known` (or `unknown`)
|
|
539
|
+
|
|
540
|
+
#### Opt-In Diagnostic Body (`expose_diagnosis=True`)
|
|
541
|
+
|
|
542
|
+
For development environments or trusted internal APIs, pass `expose_diagnosis=True` to include the full structured diagnosis directly in the JSON response:
|
|
543
|
+
|
|
544
|
+
```python
|
|
545
|
+
app.add_middleware(InvestigatorMiddleware, reraise=False, expose_diagnosis=True)
|
|
546
|
+
```
|
|
547
|
+
|
|
548
|
+
```json
|
|
549
|
+
{
|
|
550
|
+
"error": "InternalServerError",
|
|
551
|
+
"status": "known",
|
|
552
|
+
"message": "invalid literal for int() with base 10: 'abc'",
|
|
553
|
+
"investigation": {
|
|
554
|
+
"summary": "A function received a value of the correct type but with an invalid content.",
|
|
555
|
+
"explanation": "ValueError means the argument had the right type, but the value itself was not acceptable...",
|
|
556
|
+
"common_causes": [
|
|
557
|
+
"Calling int('abc') on non-numeric strings.",
|
|
558
|
+
"Parsing a malformed date/time string."
|
|
559
|
+
],
|
|
560
|
+
"fixes": [
|
|
561
|
+
"Wrap the call in try/except and validate input before passing it in.",
|
|
562
|
+
"Check the argument at the call site."
|
|
563
|
+
],
|
|
564
|
+
"docs_url": "https://docs.python.org/3/library/exceptions.html#ValueError",
|
|
565
|
+
"related_errors": ["TypeError", "KeyError"]
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
```
|
|
569
|
+
|
|
570
|
+
### 3. Custom Telemetry & Sinks
|
|
571
|
+
|
|
572
|
+
You can route diagnoses and immutable `RequestContext` instances directly to your logging or monitoring pipeline:
|
|
573
|
+
|
|
574
|
+
```python
|
|
575
|
+
def my_sink(result, context):
|
|
576
|
+
logger.error(
|
|
577
|
+
"Request failed: %s %s (status %d) -> %s",
|
|
578
|
+
context.method,
|
|
579
|
+
context.path,
|
|
580
|
+
context.status_code,
|
|
581
|
+
result.parsed_error["error_type"],
|
|
582
|
+
)
|
|
583
|
+
|
|
584
|
+
app.add_middleware(InvestigatorMiddleware, sink=my_sink)
|
|
585
|
+
```
|
|
586
|
+
|
|
587
|
+
The sink dispatcher supports:
|
|
588
|
+
- **1-argument sinks:** `sink(result)`
|
|
589
|
+
- **2-argument sinks:** `sink(result, context)`
|
|
590
|
+
- **Async sinks:** `async def sink(result, context): await send_alert(...)`
|
|
591
|
+
|
|
592
|
+
### 4. Automatic Sanitization (`sanitize=True`)
|
|
593
|
+
|
|
594
|
+
Enabled by default, sanitization protects sensitive data before logging, printing, or sending responses:
|
|
595
|
+
- **Headers Redacted:** `Authorization`, `Cookie`, `X-Api-Key`, and any header containing `auth`, `token`, `secret`, `key`, or `password` are replaced with `"[REDACTED]"`.
|
|
596
|
+
- **Query Parameters Redacted:** Sensitive query values like `?token=...`, `?password=...`, and `?secret=...` are masked.
|
|
597
|
+
- **Traceback Message Scrubbing:** Credentials in exception messages (`password='...'` or `Bearer ...`) are scrubbed before reaching sinks or terminal output.
|
|
598
|
+
|
|
599
|
+
To disable sanitization:
|
|
600
|
+
```python
|
|
601
|
+
app.add_middleware(InvestigatorMiddleware, sanitize=False)
|
|
602
|
+
```
|
|
603
|
+
|
|
604
|
+
### 5. Custom Knowledge Base
|
|
605
|
+
|
|
606
|
+
Teams can supply custom knowledge catalogs via `kb_dir`:
|
|
607
|
+
|
|
608
|
+
```python
|
|
609
|
+
app.add_middleware(InvestigatorMiddleware, kb_dir="./custom_kb")
|
|
610
|
+
```
|
|
611
|
+
|
|
612
|
+
If the custom directory contains invalid schema or identifier collisions, `KnowledgeEntryError` is raised upfront on application startup.
|
|
613
|
+
|
|
614
|
+
---
|
|
615
|
+
|
|
616
|
+
## Architecture & Codebase Layout
|
|
617
|
+
|
|
618
|
+
```text
|
|
619
|
+
src/investigator/
|
|
620
|
+
├── __init__.py # Public API exports (investigate, InvestigationResult, etc.)
|
|
621
|
+
├── cli.py # CLI application and Typer command wiring
|
|
622
|
+
├── cli_format.py # Terminal output presentation (88-col wrap, hanging indents)
|
|
623
|
+
├── error_parser.py # Traceback parser extracting structured ParsedError
|
|
624
|
+
├── fastapi.py # FastAPI / Starlette middleware (InvestigatorMiddleware)
|
|
625
|
+
├── investigation.py # Investigation dataclass
|
|
626
|
+
├── investigation_result.py # Result envelope and status invariants
|
|
627
|
+
├── investigator.py # Pipeline orchestration: parse → lookup → result
|
|
628
|
+
├── io_utils.py # Multiline stdin capture (interactive TTY & pipe support)
|
|
629
|
+
├── knowledge_loader.py # YAML loader, schema validation, and collision detection
|
|
630
|
+
├── knowledge_lookup.py # In-memory index and lookup engine
|
|
631
|
+
├── sdk.py # Programmatic Python SDK (investigate_exception, capture_exception)
|
|
632
|
+
└── knowledge/ # Built-in curated YAML knowledge entries
|
|
633
|
+
```
|
|
634
|
+
|
|
635
|
+
### Layering Boundary
|
|
636
|
+
|
|
637
|
+
The codebase enforces strict, unidirectional module layering:
|
|
638
|
+
- **CLI Layer:** [`cli.py`](src/investigator/cli.py) handles terminal I/O, invokes the loader for upfront `--kb-dir` validation, and triggers the investigator.
|
|
639
|
+
- **Middleware Layer:** [`fastapi.py`](src/investigator/fastapi.py) provides ASGI middleware for web frameworks.
|
|
640
|
+
- **SDK Layer:** [`sdk.py`](src/investigator/sdk.py) provides programmatic developer integration (`investigate_exception`, `capture_exception`).
|
|
641
|
+
- **Orchestration Layer:** [`investigator.py`](src/investigator/investigator.py) coordinates parsing and lookup.
|
|
642
|
+
- **Lookup Layer:** [`knowledge_lookup.py`](src/investigator/knowledge_lookup.py) manages in-memory indexing and query matching.
|
|
643
|
+
- **Loader Layer:** [`knowledge_loader.py`](src/investigator/knowledge_loader.py) reads YAML, validates schema constraints, and detects collisions.
|
|
644
|
+
|
|
645
|
+
---
|
|
646
|
+
|
|
647
|
+
## Development & Testing
|
|
648
|
+
|
|
649
|
+
### Running Tests
|
|
650
|
+
|
|
651
|
+
Run the full pytest suite with coverage analysis:
|
|
652
|
+
|
|
653
|
+
```bash
|
|
654
|
+
pytest --cov=investigator --cov-report=term-missing
|
|
655
|
+
```
|
|
656
|
+
|
|
657
|
+
### Current Status
|
|
658
|
+
|
|
659
|
+
- **296 passed tests**
|
|
660
|
+
- **100% statement coverage** across all source files
|
|
661
|
+
- Strict test isolation (cache resets, interactive TTY simulation, edge-case and adversarial checks)
|
|
662
|
+
|
|
663
|
+
---
|
|
664
|
+
|
|
665
|
+
## Roadmap
|
|
666
|
+
|
|
667
|
+
- **Chunk A** - Core deterministic investigator ✅
|
|
668
|
+
- **Chunk B** - External investigation engine ⏭️ deferred
|
|
669
|
+
- **Chunk C** - Developer integration ✅
|
|
670
|
+
- C1: CLI productization
|
|
671
|
+
- C2: Python SDK
|
|
672
|
+
- C3: FastAPI middleware
|
|
673
|
+
- C4: Knowledge base expansion (20 entries)
|
|
674
|
+
- **Chunks D-G** - Future direction (backend service, infrastructure,
|
|
675
|
+
production hardening, release) - not currently planned
|
|
676
|
+
|
|
677
|
+
---
|
|
678
|
+
|
|
679
|
+
## License
|
|
680
|
+
|
|
681
|
+
This project is licensed under the [MIT License](LICENSE).
|