django-explain-errors 0.9.0__tar.gz → 0.9.1__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.
- {django_explain_errors-0.9.0/django_explain_errors.egg-info → django_explain_errors-0.9.1}/PKG-INFO +29 -36
- {django_explain_errors-0.9.0 → django_explain_errors-0.9.1}/README.md +28 -35
- {django_explain_errors-0.9.0 → django_explain_errors-0.9.1/django_explain_errors.egg-info}/PKG-INFO +29 -36
- {django_explain_errors-0.9.0 → django_explain_errors-0.9.1}/explain_errors/rag/indexer.py +14 -8
- {django_explain_errors-0.9.0 → django_explain_errors-0.9.1}/setup.py +1 -1
- {django_explain_errors-0.9.0 → django_explain_errors-0.9.1}/tests/test_rag.py +143 -0
- {django_explain_errors-0.9.0 → django_explain_errors-0.9.1}/LICENSE +0 -0
- {django_explain_errors-0.9.0 → django_explain_errors-0.9.1}/MANIFEST.in +0 -0
- {django_explain_errors-0.9.0 → django_explain_errors-0.9.1}/django_explain_errors.egg-info/SOURCES.txt +0 -0
- {django_explain_errors-0.9.0 → django_explain_errors-0.9.1}/django_explain_errors.egg-info/dependency_links.txt +0 -0
- {django_explain_errors-0.9.0 → django_explain_errors-0.9.1}/django_explain_errors.egg-info/requires.txt +0 -0
- {django_explain_errors-0.9.0 → django_explain_errors-0.9.1}/django_explain_errors.egg-info/top_level.txt +0 -0
- {django_explain_errors-0.9.0 → django_explain_errors-0.9.1}/explain_errors/__init__.py +0 -0
- {django_explain_errors-0.9.0 → django_explain_errors-0.9.1}/explain_errors/client.py +0 -0
- {django_explain_errors-0.9.0 → django_explain_errors-0.9.1}/explain_errors/debug_page.py +0 -0
- {django_explain_errors-0.9.0 → django_explain_errors-0.9.1}/explain_errors/management/__init__.py +0 -0
- {django_explain_errors-0.9.0 → django_explain_errors-0.9.1}/explain_errors/management/commands/__init__.py +0 -0
- {django_explain_errors-0.9.0 → django_explain_errors-0.9.1}/explain_errors/management/commands/build_error_index.py +0 -0
- {django_explain_errors-0.9.0 → django_explain_errors-0.9.1}/explain_errors/middleware.py +0 -0
- {django_explain_errors-0.9.0 → django_explain_errors-0.9.1}/explain_errors/rag/__init__.py +0 -0
- {django_explain_errors-0.9.0 → django_explain_errors-0.9.1}/explain_errors/rag/retriever.py +0 -0
- {django_explain_errors-0.9.0 → django_explain_errors-0.9.1}/explain_errors/rag/store.py +0 -0
- {django_explain_errors-0.9.0 → django_explain_errors-0.9.1}/explain_errors/sanitize.py +0 -0
- {django_explain_errors-0.9.0 → django_explain_errors-0.9.1}/explain_errors/throttle.py +0 -0
- {django_explain_errors-0.9.0 → django_explain_errors-0.9.1}/explain_errors/tracebacks.py +0 -0
- {django_explain_errors-0.9.0 → django_explain_errors-0.9.1}/setup.cfg +0 -0
- {django_explain_errors-0.9.0 → django_explain_errors-0.9.1}/tests/test_build_error_index.py +0 -0
- {django_explain_errors-0.9.0 → django_explain_errors-0.9.1}/tests/test_client.py +0 -0
- {django_explain_errors-0.9.0 → django_explain_errors-0.9.1}/tests/test_debug_page.py +0 -0
- {django_explain_errors-0.9.0 → django_explain_errors-0.9.1}/tests/test_eval_fixtures.py +0 -0
- {django_explain_errors-0.9.0 → django_explain_errors-0.9.1}/tests/test_eval_judge.py +0 -0
- {django_explain_errors-0.9.0 → django_explain_errors-0.9.1}/tests/test_eval_run.py +0 -0
- {django_explain_errors-0.9.0 → django_explain_errors-0.9.1}/tests/test_language.py +0 -0
- {django_explain_errors-0.9.0 → django_explain_errors-0.9.1}/tests/test_middleware.py +0 -0
- {django_explain_errors-0.9.0 → django_explain_errors-0.9.1}/tests/test_print_stdout.py +0 -0
- {django_explain_errors-0.9.0 → django_explain_errors-0.9.1}/tests/test_sanitize.py +0 -0
- {django_explain_errors-0.9.0 → django_explain_errors-0.9.1}/tests/test_signals.py +0 -0
- {django_explain_errors-0.9.0 → django_explain_errors-0.9.1}/tests/test_throttle.py +0 -0
- {django_explain_errors-0.9.0 → django_explain_errors-0.9.1}/tests/test_tracebacks.py +0 -0
- {django_explain_errors-0.9.0 → django_explain_errors-0.9.1}/tests/test_truncation.py +0 -0
{django_explain_errors-0.9.0/django_explain_errors.egg-info → django_explain_errors-0.9.1}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: django-explain-errors
|
|
3
|
-
Version: 0.9.
|
|
3
|
+
Version: 0.9.1
|
|
4
4
|
Summary: Django middleware that explains unhandled exceptions in DEBUG using an LLM, optionally grounded in your own project source via a local vector index. Works with OpenAI, Claude, or any OpenAI-compatible endpoint including local models.
|
|
5
5
|
Home-page: https://github.com/topunix/django-explain-errors
|
|
6
6
|
Author: topunix
|
|
@@ -42,37 +42,31 @@ Dynamic: requires-dist
|
|
|
42
42
|
Dynamic: requires-python
|
|
43
43
|
Dynamic: summary
|
|
44
44
|
|
|
45
|
-
# Django Explain Errors
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
rate limited.
|
|
71
|
-
|
|
72
|
-

|
|
73
|
-
|
|
74
|
-
*Real `gpt-4o-mini` output with RAG enabled, recorded by the eval harness
|
|
75
|
-
on its `missing_fk` fixture.*
|
|
45
|
+
# Django Explain Errors
|
|
46
|
+
|
|
47
|
+
Explains Django exceptions on the debug page, pointing at the real fix in your own source code.
|
|
48
|
+
|
|
49
|
+
<p align="center">
|
|
50
|
+
<a href="https://pypi.org/project/django-explain-errors/"><img src="https://img.shields.io/pypi/v/django-explain-errors" alt="PyPI version"></a>
|
|
51
|
+
<a href="https://pypi.org/project/django-explain-errors/"><img src="https://img.shields.io/pypi/pyversions/django-explain-errors" alt="Python versions"></a>
|
|
52
|
+
<a href="https://github.com/topunix/django-explain-errors/actions/workflows/test.yml"><img src="https://github.com/topunix/django-explain-errors/actions/workflows/test.yml/badge.svg?branch=main" alt="Tests"></a>
|
|
53
|
+
<a href="https://github.com/topunix/django-explain-errors/blob/main/LICENSE"><img src="https://img.shields.io/pypi/l/django-explain-errors" alt="License"></a>
|
|
54
|
+
</p>
|
|
55
|
+
|
|
56
|
+
<p align="center">
|
|
57
|
+
<img src="https://raw.githubusercontent.com/topunix/django-explain-errors/main/docs/images/debug-page-banner.png" width="800"
|
|
58
|
+
alt="Django debug page with the explanation banner under the exception headline">
|
|
59
|
+
</p>
|
|
60
|
+
|
|
61
|
+
*The traceback held only Django and SQLite internals. With RAG enabled, the explanation names the
|
|
62
|
+
failing function (`clone_latest_post`) and gives the one-line fix. Real `gpt-4o-mini` output from
|
|
63
|
+
the eval harness.*
|
|
64
|
+
|
|
65
|
+
- **Strongly recommended:** enable RAG over your project, so explanations name the actual file and function instead of guessing ([measured in the eval harness](#does-rag-actually-help)). It is off by default only because [setup](#codebase-aware-explanations-rag) takes three steps.
|
|
66
|
+
- Works with OpenAI, Claude, or any OpenAI-compatible endpoint, including local models via Ollama
|
|
67
|
+
- Explanations in your language via `EXPLAIN_ERRORS_LANGUAGE`, with code and identifiers kept in English
|
|
68
|
+
- Sync and async views, traceback redaction, rate limiting
|
|
69
|
+
- Local development only: requires `DEBUG = True`
|
|
76
70
|
|
|
77
71
|
## Scope
|
|
78
72
|
|
|
@@ -85,8 +79,6 @@ If a coding agent is doing the debugging, it does not need this. Agents read tra
|
|
|
85
79
|
and tools that expose live runtime state (debugger-over-MCP servers, `mcp-django`) serve that case
|
|
86
80
|
better. This package is not trying to compete there.
|
|
87
81
|
|
|
88
|
-
Local development only. It requires `DEBUG = True` and is inert otherwise.
|
|
89
|
-
|
|
90
82
|
## Features
|
|
91
83
|
|
|
92
84
|
- Captures Django errors and exceptions
|
|
@@ -98,8 +90,9 @@ Local development only. It requires `DEBUG = True` and is inert otherwise.
|
|
|
98
90
|
(`EXPLAIN_ERRORS_PRINT_STDOUT`, on by default)
|
|
99
91
|
- Optional JSON 500 response instead of the debug page
|
|
100
92
|
(`EXPLAIN_ERRORS_PRESERVE_DEBUG_PAGE = False`)
|
|
101
|
-
- Explains errors using OpenAI, Anthropic's Claude models, or any other
|
|
102
|
-
OpenAI-compatible endpoint (Ollama, LM Studio, Azure, gateways) via `OPENAI_BASE_URL
|
|
93
|
+
- Explains errors using OpenAI out of the box, Anthropic's Claude models, or any other
|
|
94
|
+
OpenAI-compatible endpoint (Ollama, LM Studio, Azure, gateways) via `OPENAI_BASE_URL`,
|
|
95
|
+
including fully local models, so no code has to leave your machine
|
|
103
96
|
- Codebase-aware explanations via RAG over a local sqlite-vec index
|
|
104
97
|
(recommended; see "Codebase-aware explanations" below)
|
|
105
98
|
- Explanations in your language via `EXPLAIN_ERRORS_LANGUAGE`, with exception names,
|
|
@@ -1,34 +1,28 @@
|
|
|
1
|
-
# Django Explain Errors
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
rate limited.
|
|
27
|
-
|
|
28
|
-

|
|
29
|
-
|
|
30
|
-
*Real `gpt-4o-mini` output with RAG enabled, recorded by the eval harness
|
|
31
|
-
on its `missing_fk` fixture.*
|
|
1
|
+
# Django Explain Errors
|
|
2
|
+
|
|
3
|
+
Explains Django exceptions on the debug page, pointing at the real fix in your own source code.
|
|
4
|
+
|
|
5
|
+
<p align="center">
|
|
6
|
+
<a href="https://pypi.org/project/django-explain-errors/"><img src="https://img.shields.io/pypi/v/django-explain-errors" alt="PyPI version"></a>
|
|
7
|
+
<a href="https://pypi.org/project/django-explain-errors/"><img src="https://img.shields.io/pypi/pyversions/django-explain-errors" alt="Python versions"></a>
|
|
8
|
+
<a href="https://github.com/topunix/django-explain-errors/actions/workflows/test.yml"><img src="https://github.com/topunix/django-explain-errors/actions/workflows/test.yml/badge.svg?branch=main" alt="Tests"></a>
|
|
9
|
+
<a href="https://github.com/topunix/django-explain-errors/blob/main/LICENSE"><img src="https://img.shields.io/pypi/l/django-explain-errors" alt="License"></a>
|
|
10
|
+
</p>
|
|
11
|
+
|
|
12
|
+
<p align="center">
|
|
13
|
+
<img src="https://raw.githubusercontent.com/topunix/django-explain-errors/main/docs/images/debug-page-banner.png" width="800"
|
|
14
|
+
alt="Django debug page with the explanation banner under the exception headline">
|
|
15
|
+
</p>
|
|
16
|
+
|
|
17
|
+
*The traceback held only Django and SQLite internals. With RAG enabled, the explanation names the
|
|
18
|
+
failing function (`clone_latest_post`) and gives the one-line fix. Real `gpt-4o-mini` output from
|
|
19
|
+
the eval harness.*
|
|
20
|
+
|
|
21
|
+
- **Strongly recommended:** enable RAG over your project, so explanations name the actual file and function instead of guessing ([measured in the eval harness](#does-rag-actually-help)). It is off by default only because [setup](#codebase-aware-explanations-rag) takes three steps.
|
|
22
|
+
- Works with OpenAI, Claude, or any OpenAI-compatible endpoint, including local models via Ollama
|
|
23
|
+
- Explanations in your language via `EXPLAIN_ERRORS_LANGUAGE`, with code and identifiers kept in English
|
|
24
|
+
- Sync and async views, traceback redaction, rate limiting
|
|
25
|
+
- Local development only: requires `DEBUG = True`
|
|
32
26
|
|
|
33
27
|
## Scope
|
|
34
28
|
|
|
@@ -41,8 +35,6 @@ If a coding agent is doing the debugging, it does not need this. Agents read tra
|
|
|
41
35
|
and tools that expose live runtime state (debugger-over-MCP servers, `mcp-django`) serve that case
|
|
42
36
|
better. This package is not trying to compete there.
|
|
43
37
|
|
|
44
|
-
Local development only. It requires `DEBUG = True` and is inert otherwise.
|
|
45
|
-
|
|
46
38
|
## Features
|
|
47
39
|
|
|
48
40
|
- Captures Django errors and exceptions
|
|
@@ -54,8 +46,9 @@ Local development only. It requires `DEBUG = True` and is inert otherwise.
|
|
|
54
46
|
(`EXPLAIN_ERRORS_PRINT_STDOUT`, on by default)
|
|
55
47
|
- Optional JSON 500 response instead of the debug page
|
|
56
48
|
(`EXPLAIN_ERRORS_PRESERVE_DEBUG_PAGE = False`)
|
|
57
|
-
- Explains errors using OpenAI, Anthropic's Claude models, or any other
|
|
58
|
-
OpenAI-compatible endpoint (Ollama, LM Studio, Azure, gateways) via `OPENAI_BASE_URL
|
|
49
|
+
- Explains errors using OpenAI out of the box, Anthropic's Claude models, or any other
|
|
50
|
+
OpenAI-compatible endpoint (Ollama, LM Studio, Azure, gateways) via `OPENAI_BASE_URL`,
|
|
51
|
+
including fully local models, so no code has to leave your machine
|
|
59
52
|
- Codebase-aware explanations via RAG over a local sqlite-vec index
|
|
60
53
|
(recommended; see "Codebase-aware explanations" below)
|
|
61
54
|
- Explanations in your language via `EXPLAIN_ERRORS_LANGUAGE`, with exception names,
|
{django_explain_errors-0.9.0 → django_explain_errors-0.9.1/django_explain_errors.egg-info}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: django-explain-errors
|
|
3
|
-
Version: 0.9.
|
|
3
|
+
Version: 0.9.1
|
|
4
4
|
Summary: Django middleware that explains unhandled exceptions in DEBUG using an LLM, optionally grounded in your own project source via a local vector index. Works with OpenAI, Claude, or any OpenAI-compatible endpoint including local models.
|
|
5
5
|
Home-page: https://github.com/topunix/django-explain-errors
|
|
6
6
|
Author: topunix
|
|
@@ -42,37 +42,31 @@ Dynamic: requires-dist
|
|
|
42
42
|
Dynamic: requires-python
|
|
43
43
|
Dynamic: summary
|
|
44
44
|
|
|
45
|
-
# Django Explain Errors
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
rate limited.
|
|
71
|
-
|
|
72
|
-

|
|
73
|
-
|
|
74
|
-
*Real `gpt-4o-mini` output with RAG enabled, recorded by the eval harness
|
|
75
|
-
on its `missing_fk` fixture.*
|
|
45
|
+
# Django Explain Errors
|
|
46
|
+
|
|
47
|
+
Explains Django exceptions on the debug page, pointing at the real fix in your own source code.
|
|
48
|
+
|
|
49
|
+
<p align="center">
|
|
50
|
+
<a href="https://pypi.org/project/django-explain-errors/"><img src="https://img.shields.io/pypi/v/django-explain-errors" alt="PyPI version"></a>
|
|
51
|
+
<a href="https://pypi.org/project/django-explain-errors/"><img src="https://img.shields.io/pypi/pyversions/django-explain-errors" alt="Python versions"></a>
|
|
52
|
+
<a href="https://github.com/topunix/django-explain-errors/actions/workflows/test.yml"><img src="https://github.com/topunix/django-explain-errors/actions/workflows/test.yml/badge.svg?branch=main" alt="Tests"></a>
|
|
53
|
+
<a href="https://github.com/topunix/django-explain-errors/blob/main/LICENSE"><img src="https://img.shields.io/pypi/l/django-explain-errors" alt="License"></a>
|
|
54
|
+
</p>
|
|
55
|
+
|
|
56
|
+
<p align="center">
|
|
57
|
+
<img src="https://raw.githubusercontent.com/topunix/django-explain-errors/main/docs/images/debug-page-banner.png" width="800"
|
|
58
|
+
alt="Django debug page with the explanation banner under the exception headline">
|
|
59
|
+
</p>
|
|
60
|
+
|
|
61
|
+
*The traceback held only Django and SQLite internals. With RAG enabled, the explanation names the
|
|
62
|
+
failing function (`clone_latest_post`) and gives the one-line fix. Real `gpt-4o-mini` output from
|
|
63
|
+
the eval harness.*
|
|
64
|
+
|
|
65
|
+
- **Strongly recommended:** enable RAG over your project, so explanations name the actual file and function instead of guessing ([measured in the eval harness](#does-rag-actually-help)). It is off by default only because [setup](#codebase-aware-explanations-rag) takes three steps.
|
|
66
|
+
- Works with OpenAI, Claude, or any OpenAI-compatible endpoint, including local models via Ollama
|
|
67
|
+
- Explanations in your language via `EXPLAIN_ERRORS_LANGUAGE`, with code and identifiers kept in English
|
|
68
|
+
- Sync and async views, traceback redaction, rate limiting
|
|
69
|
+
- Local development only: requires `DEBUG = True`
|
|
76
70
|
|
|
77
71
|
## Scope
|
|
78
72
|
|
|
@@ -85,8 +79,6 @@ If a coding agent is doing the debugging, it does not need this. Agents read tra
|
|
|
85
79
|
and tools that expose live runtime state (debugger-over-MCP servers, `mcp-django`) serve that case
|
|
86
80
|
better. This package is not trying to compete there.
|
|
87
81
|
|
|
88
|
-
Local development only. It requires `DEBUG = True` and is inert otherwise.
|
|
89
|
-
|
|
90
82
|
## Features
|
|
91
83
|
|
|
92
84
|
- Captures Django errors and exceptions
|
|
@@ -98,8 +90,9 @@ Local development only. It requires `DEBUG = True` and is inert otherwise.
|
|
|
98
90
|
(`EXPLAIN_ERRORS_PRINT_STDOUT`, on by default)
|
|
99
91
|
- Optional JSON 500 response instead of the debug page
|
|
100
92
|
(`EXPLAIN_ERRORS_PRESERVE_DEBUG_PAGE = False`)
|
|
101
|
-
- Explains errors using OpenAI, Anthropic's Claude models, or any other
|
|
102
|
-
OpenAI-compatible endpoint (Ollama, LM Studio, Azure, gateways) via `OPENAI_BASE_URL
|
|
93
|
+
- Explains errors using OpenAI out of the box, Anthropic's Claude models, or any other
|
|
94
|
+
OpenAI-compatible endpoint (Ollama, LM Studio, Azure, gateways) via `OPENAI_BASE_URL`,
|
|
95
|
+
including fully local models, so no code has to leave your machine
|
|
103
96
|
- Codebase-aware explanations via RAG over a local sqlite-vec index
|
|
104
97
|
(recommended; see "Codebase-aware explanations" below)
|
|
105
98
|
- Explanations in your language via `EXPLAIN_ERRORS_LANGUAGE`, with exception names,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"""File discovery, chunking, and embedding for the RAG source index."""
|
|
2
2
|
import ast
|
|
3
|
+
import contextlib
|
|
3
4
|
import os
|
|
4
5
|
|
|
5
6
|
from django.conf import settings
|
|
@@ -167,16 +168,21 @@ def build_index():
|
|
|
167
168
|
os.remove(tmp_path)
|
|
168
169
|
|
|
169
170
|
dimensions = len(embeddings[0]) if embeddings else 1536
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
(
|
|
174
|
-
|
|
175
|
-
|
|
171
|
+
try:
|
|
172
|
+
with VectorStore(tmp_path) as store:
|
|
173
|
+
store.create(dimensions)
|
|
174
|
+
store.add(
|
|
175
|
+
(file_path, start_line, end_line, sanitized_text, embedding)
|
|
176
|
+
for (file_path, start_line, end_line, _text), sanitized_text, embedding in zip(
|
|
177
|
+
raw_chunks, sanitized_texts, embeddings
|
|
178
|
+
)
|
|
176
179
|
)
|
|
177
|
-
)
|
|
178
180
|
|
|
179
|
-
|
|
181
|
+
os.replace(tmp_path, index_path)
|
|
182
|
+
except BaseException:
|
|
183
|
+
with contextlib.suppress(OSError):
|
|
184
|
+
os.remove(tmp_path)
|
|
185
|
+
raise
|
|
180
186
|
|
|
181
187
|
return {
|
|
182
188
|
"files_scanned": len(files),
|
|
@@ -10,7 +10,7 @@ with open('README.md', encoding='utf-8') as f:
|
|
|
10
10
|
|
|
11
11
|
setup(
|
|
12
12
|
name='django-explain-errors',
|
|
13
|
-
version='0.9.
|
|
13
|
+
version='0.9.1',
|
|
14
14
|
packages=find_packages(exclude=['tests', 'tests.*', 'evals', 'evals.*']),
|
|
15
15
|
description='Django middleware that explains unhandled exceptions in DEBUG using an LLM, optionally grounded in your own project source via a local vector index. Works with OpenAI, Claude, or any OpenAI-compatible endpoint including local models.',
|
|
16
16
|
long_description_content_type='text/markdown',
|
|
@@ -174,6 +174,76 @@ class IndexerBuildTest(SimpleTestCase):
|
|
|
174
174
|
]
|
|
175
175
|
self.assertEqual(leftovers, [])
|
|
176
176
|
|
|
177
|
+
def test_failed_rebuild_keeps_previous_index_and_removes_temp_file(self):
|
|
178
|
+
with tempfile.TemporaryDirectory() as tmp:
|
|
179
|
+
with open(os.path.join(tmp, "app.py"), "w") as f:
|
|
180
|
+
f.write(SAMPLE_MODULE)
|
|
181
|
+
|
|
182
|
+
index_path = os.path.join(tmp, "index.db")
|
|
183
|
+
|
|
184
|
+
with override_settings(
|
|
185
|
+
EXPLAIN_ERRORS_RAG_INCLUDE=[tmp],
|
|
186
|
+
EXPLAIN_ERRORS_RAG_INDEX_PATH=index_path,
|
|
187
|
+
):
|
|
188
|
+
with patch(
|
|
189
|
+
"explain_errors.rag.indexer.get_openai_client",
|
|
190
|
+
return_value=_mock_embed_client([0.1, 0.2, 0.3]),
|
|
191
|
+
):
|
|
192
|
+
build_index()
|
|
193
|
+
|
|
194
|
+
with open(index_path, "rb") as f:
|
|
195
|
+
original_bytes = f.read()
|
|
196
|
+
with VectorStore(index_path) as store:
|
|
197
|
+
original_count = store.count()
|
|
198
|
+
self.assertGreater(original_count, 0)
|
|
199
|
+
|
|
200
|
+
with patch.object(
|
|
201
|
+
VectorStore, "add", side_effect=RuntimeError("add failed")
|
|
202
|
+
):
|
|
203
|
+
with self.assertRaisesRegex(RuntimeError, "add failed"):
|
|
204
|
+
build_index()
|
|
205
|
+
|
|
206
|
+
with open(index_path, "rb") as f:
|
|
207
|
+
self.assertEqual(f.read(), original_bytes)
|
|
208
|
+
with VectorStore(index_path) as store:
|
|
209
|
+
self.assertEqual(store.count(), original_count)
|
|
210
|
+
|
|
211
|
+
leftovers = [
|
|
212
|
+
name for name in os.listdir(tmp) if name.startswith("index.db.tmp-")
|
|
213
|
+
]
|
|
214
|
+
self.assertEqual(leftovers, [])
|
|
215
|
+
|
|
216
|
+
def test_build_index_redacts_secrets_in_stored_chunk_text(self):
|
|
217
|
+
with tempfile.TemporaryDirectory() as tmp:
|
|
218
|
+
src = os.path.join(tmp, "src")
|
|
219
|
+
os.makedirs(src)
|
|
220
|
+
with open(os.path.join(src, "config.py"), "w") as f:
|
|
221
|
+
f.write(
|
|
222
|
+
"def load():\n"
|
|
223
|
+
" api_key = 'sk-index-secret-value'\n"
|
|
224
|
+
" return api_key\n"
|
|
225
|
+
)
|
|
226
|
+
|
|
227
|
+
index_path = os.path.join(tmp, "index.db")
|
|
228
|
+
embed_client = _mock_embed_client([0.1, 0.2, 0.3])
|
|
229
|
+
|
|
230
|
+
with override_settings(
|
|
231
|
+
EXPLAIN_ERRORS_RAG_INCLUDE=[src],
|
|
232
|
+
EXPLAIN_ERRORS_RAG_INDEX_PATH=index_path,
|
|
233
|
+
):
|
|
234
|
+
with patch(
|
|
235
|
+
"explain_errors.rag.indexer.get_openai_client",
|
|
236
|
+
return_value=embed_client,
|
|
237
|
+
):
|
|
238
|
+
build_index()
|
|
239
|
+
|
|
240
|
+
with VectorStore(index_path) as store:
|
|
241
|
+
rows = store.query([0.1, 0.2, 0.3], 10)
|
|
242
|
+
|
|
243
|
+
self.assertEqual(len(rows), 1)
|
|
244
|
+
self.assertNotIn("sk-index-secret-value", rows[0]["chunk_text"])
|
|
245
|
+
self.assertIn("[REDACTED]", rows[0]["chunk_text"])
|
|
246
|
+
|
|
177
247
|
|
|
178
248
|
def _raise_json_error():
|
|
179
249
|
return json.loads("{not valid json")
|
|
@@ -192,6 +262,35 @@ class RetrieverFrameExtractionTest(SimpleTestCase):
|
|
|
192
262
|
self.assertTrue(any(name.endswith("test_rag.py") for name in filenames))
|
|
193
263
|
self.assertFalse(any("json" in os.path.basename(name) for name in filenames))
|
|
194
264
|
|
|
265
|
+
def test_extract_project_frames_excludes_site_packages_frame(self):
|
|
266
|
+
with tempfile.TemporaryDirectory() as tmp:
|
|
267
|
+
site_dir = os.path.join(tmp, "venv-lib", "site-packages")
|
|
268
|
+
os.makedirs(site_dir)
|
|
269
|
+
lib_path = os.path.join(site_dir, "thirdparty.py")
|
|
270
|
+
app_path = os.path.join(tmp, "app.py")
|
|
271
|
+
with open(lib_path, "w") as f:
|
|
272
|
+
f.write("def boom():\n raise ValueError('lib failure')\n")
|
|
273
|
+
with open(app_path, "w") as f:
|
|
274
|
+
f.write("def call(fn):\n return fn()\n")
|
|
275
|
+
|
|
276
|
+
namespaces = {}
|
|
277
|
+
for path in (lib_path, app_path):
|
|
278
|
+
ns = {}
|
|
279
|
+
with open(path) as f:
|
|
280
|
+
exec(compile(f.read(), path, "exec"), ns)
|
|
281
|
+
namespaces[path] = ns
|
|
282
|
+
|
|
283
|
+
# site-packages sits inside the include dir, so only the
|
|
284
|
+
# site-packages rule can exclude it.
|
|
285
|
+
with override_settings(EXPLAIN_ERRORS_RAG_INCLUDE=[tmp]):
|
|
286
|
+
try:
|
|
287
|
+
namespaces[app_path]["call"](namespaces[lib_path]["boom"])
|
|
288
|
+
except ValueError as exc:
|
|
289
|
+
frames = extract_project_frames(exc)
|
|
290
|
+
|
|
291
|
+
filenames = [frame.filename for frame in frames]
|
|
292
|
+
self.assertEqual(filenames, [app_path])
|
|
293
|
+
|
|
195
294
|
def test_extract_project_frames_empty_without_traceback(self):
|
|
196
295
|
self.assertEqual(extract_project_frames(ValueError("no traceback")), [])
|
|
197
296
|
|
|
@@ -229,6 +328,50 @@ class RetrieverTopKTest(SimpleTestCase):
|
|
|
229
328
|
self.assertEqual(retrieve_chunks(ValueError("boom")), [])
|
|
230
329
|
|
|
231
330
|
|
|
331
|
+
class RetrieverQuerySanitizationTest(SimpleTestCase):
|
|
332
|
+
|
|
333
|
+
@requires_sqlite_vec
|
|
334
|
+
def test_query_text_is_sanitized_before_embedding(self):
|
|
335
|
+
with tempfile.TemporaryDirectory() as tmp:
|
|
336
|
+
module_path = os.path.join(tmp, "leaky.py")
|
|
337
|
+
with open(module_path, "w") as f:
|
|
338
|
+
f.write(
|
|
339
|
+
"def fail():\n"
|
|
340
|
+
" password = 'hunter2-source-secret'; raise ValueError(\n"
|
|
341
|
+
" 'token=msg-secret-value')\n"
|
|
342
|
+
)
|
|
343
|
+
|
|
344
|
+
index_path = os.path.join(tmp, "index.db")
|
|
345
|
+
with VectorStore(index_path) as store:
|
|
346
|
+
store.create(3)
|
|
347
|
+
store.add([("leaky.py", 1, 3, "chunk", [1.0, 0.0, 0.0])])
|
|
348
|
+
|
|
349
|
+
namespace = {}
|
|
350
|
+
exec(compile(open(module_path).read(), module_path, "exec"), namespace)
|
|
351
|
+
try:
|
|
352
|
+
namespace["fail"]()
|
|
353
|
+
except ValueError as exc:
|
|
354
|
+
captured = exc
|
|
355
|
+
|
|
356
|
+
embed_client = _mock_embed_client([1.0, 0.0, 0.0])
|
|
357
|
+
with override_settings(
|
|
358
|
+
EXPLAIN_ERRORS_RAG_ENABLED=True,
|
|
359
|
+
EXPLAIN_ERRORS_RAG_INDEX_PATH=index_path,
|
|
360
|
+
EXPLAIN_ERRORS_RAG_INCLUDE=[tmp],
|
|
361
|
+
):
|
|
362
|
+
with patch(
|
|
363
|
+
"explain_errors.rag.retriever.get_openai_client",
|
|
364
|
+
return_value=embed_client,
|
|
365
|
+
):
|
|
366
|
+
retrieve_chunks(captured)
|
|
367
|
+
|
|
368
|
+
embed_client.embeddings.create.assert_called_once()
|
|
369
|
+
(query_text,) = embed_client.embeddings.create.call_args.kwargs["input"]
|
|
370
|
+
self.assertNotIn("msg-secret-value", query_text)
|
|
371
|
+
self.assertNotIn("hunter2-source-secret", query_text)
|
|
372
|
+
self.assertIn("[REDACTED]", query_text)
|
|
373
|
+
|
|
374
|
+
|
|
232
375
|
@override_settings(DEBUG=True, OPENAI_API_KEY="test-key")
|
|
233
376
|
class MiddlewareRagIntegrationTest(SimpleTestCase):
|
|
234
377
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{django_explain_errors-0.9.0 → django_explain_errors-0.9.1}/explain_errors/management/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|