django-explain-errors 0.3.0__tar.gz → 0.3.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.3.0/django_explain_errors.egg-info → django_explain_errors-0.3.1}/PKG-INFO +1 -1
- {django_explain_errors-0.3.0 → django_explain_errors-0.3.1/django_explain_errors.egg-info}/PKG-INFO +1 -1
- {django_explain_errors-0.3.0 → django_explain_errors-0.3.1}/django_explain_errors.egg-info/SOURCES.txt +1 -0
- django_explain_errors-0.3.1/explain_errors/management/commands/build_error_index.py +28 -0
- {django_explain_errors-0.3.0 → django_explain_errors-0.3.1}/setup.py +1 -1
- django_explain_errors-0.3.1/tests/test_build_error_index.py +74 -0
- django_explain_errors-0.3.0/explain_errors/management/commands/build_error_index.py +0 -18
- {django_explain_errors-0.3.0 → django_explain_errors-0.3.1}/LICENSE +0 -0
- {django_explain_errors-0.3.0 → django_explain_errors-0.3.1}/MANIFEST.in +0 -0
- {django_explain_errors-0.3.0 → django_explain_errors-0.3.1}/README.md +0 -0
- {django_explain_errors-0.3.0 → django_explain_errors-0.3.1}/django_explain_errors.egg-info/dependency_links.txt +0 -0
- {django_explain_errors-0.3.0 → django_explain_errors-0.3.1}/django_explain_errors.egg-info/requires.txt +0 -0
- {django_explain_errors-0.3.0 → django_explain_errors-0.3.1}/django_explain_errors.egg-info/top_level.txt +0 -0
- {django_explain_errors-0.3.0 → django_explain_errors-0.3.1}/explain_errors/__init__.py +0 -0
- {django_explain_errors-0.3.0 → django_explain_errors-0.3.1}/explain_errors/management/__init__.py +0 -0
- {django_explain_errors-0.3.0 → django_explain_errors-0.3.1}/explain_errors/management/commands/__init__.py +0 -0
- {django_explain_errors-0.3.0 → django_explain_errors-0.3.1}/explain_errors/middleware.py +0 -0
- {django_explain_errors-0.3.0 → django_explain_errors-0.3.1}/explain_errors/rag/__init__.py +0 -0
- {django_explain_errors-0.3.0 → django_explain_errors-0.3.1}/explain_errors/rag/indexer.py +0 -0
- {django_explain_errors-0.3.0 → django_explain_errors-0.3.1}/explain_errors/rag/retriever.py +0 -0
- {django_explain_errors-0.3.0 → django_explain_errors-0.3.1}/explain_errors/rag/store.py +0 -0
- {django_explain_errors-0.3.0 → django_explain_errors-0.3.1}/explain_errors/sanitize.py +0 -0
- {django_explain_errors-0.3.0 → django_explain_errors-0.3.1}/explain_errors/throttle.py +0 -0
- {django_explain_errors-0.3.0 → django_explain_errors-0.3.1}/setup.cfg +0 -0
- {django_explain_errors-0.3.0 → django_explain_errors-0.3.1}/tests/test_middleware.py +0 -0
- {django_explain_errors-0.3.0 → django_explain_errors-0.3.1}/tests/test_rag.py +0 -0
- {django_explain_errors-0.3.0 → django_explain_errors-0.3.1}/tests/test_sanitize.py +0 -0
- {django_explain_errors-0.3.0 → django_explain_errors-0.3.1}/tests/test_throttle.py +0 -0
{django_explain_errors-0.3.0/django_explain_errors.egg-info → django_explain_errors-0.3.1}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: django-explain-errors
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.1
|
|
4
4
|
Summary: Django middleware that captures errors and exceptions, sends them to OpenAI for a detailed explanation, and prints the explanation to stdout when debug mode is enabled. Supports both sync and async views.
|
|
5
5
|
Home-page: https://github.com/topunix/django-explain-errors
|
|
6
6
|
Author: topunix
|
{django_explain_errors-0.3.0 → django_explain_errors-0.3.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.3.
|
|
3
|
+
Version: 0.3.1
|
|
4
4
|
Summary: Django middleware that captures errors and exceptions, sends them to OpenAI for a detailed explanation, and prints the explanation to stdout when debug mode is enabled. Supports both sync and async views.
|
|
5
5
|
Home-page: https://github.com/topunix/django-explain-errors
|
|
6
6
|
Author: topunix
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
from django.core.management.base import BaseCommand, CommandError
|
|
2
|
+
|
|
3
|
+
from explain_errors.rag.indexer import build_index
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Command(BaseCommand):
|
|
7
|
+
help = (
|
|
8
|
+
"Build (or rebuild) the local vector index used to ground "
|
|
9
|
+
"explain_errors explanations in project source code."
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
def handle(self, *args, **options):
|
|
13
|
+
# Imported lazily to mirror the indexer's optional-dependency pattern.
|
|
14
|
+
from openai import OpenAIError
|
|
15
|
+
|
|
16
|
+
try:
|
|
17
|
+
result = build_index()
|
|
18
|
+
except OpenAIError as exc:
|
|
19
|
+
code = getattr(exc, "code", None) or type(exc).__name__
|
|
20
|
+
raise CommandError(
|
|
21
|
+
f"OpenAI API request failed ({code}). Check your API key and "
|
|
22
|
+
"billing at platform.openai.com. Index not built."
|
|
23
|
+
) from exc
|
|
24
|
+
self.stdout.write(
|
|
25
|
+
f"explain_errors: scanned {result['files_scanned']} files, "
|
|
26
|
+
f"embedded {result['chunks_embedded']} chunks.\n"
|
|
27
|
+
f"Index written to {result['index_path']}"
|
|
28
|
+
)
|
|
@@ -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.3.
|
|
13
|
+
version='0.3.1',
|
|
14
14
|
packages=find_packages(exclude=['tests', 'tests.*']),
|
|
15
15
|
description='Django middleware that captures errors and exceptions, sends them to OpenAI for a detailed explanation, and prints the explanation to stdout when debug mode is enabled. Supports both sync and async views.',
|
|
16
16
|
long_description_content_type='text/markdown',
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
from io import StringIO
|
|
2
|
+
from unittest.mock import patch
|
|
3
|
+
|
|
4
|
+
from django.core.management import call_command
|
|
5
|
+
from django.core.management.base import CommandError
|
|
6
|
+
from django.test import SimpleTestCase
|
|
7
|
+
from openai import APIConnectionError, OpenAIError, RateLimitError
|
|
8
|
+
|
|
9
|
+
COMMAND_BUILD_INDEX = "explain_errors.management.commands.build_error_index.build_index"
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class _FakeAPIError(OpenAIError):
|
|
13
|
+
"""Stands in for a real SDK error. Constructed directly rather than via
|
|
14
|
+
RateLimitError/APIConnectionError, whose signatures require an HTTP client
|
|
15
|
+
Request/Response object that changed libraries in openai 3.0 (httpx ->
|
|
16
|
+
httpx2). The command only cares about the OpenAIError base class and .code,
|
|
17
|
+
so this keeps the suite green across openai majors."""
|
|
18
|
+
|
|
19
|
+
def __init__(self, message, code=None):
|
|
20
|
+
super().__init__(message)
|
|
21
|
+
self.code = code
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class _FakeConnectionError(OpenAIError):
|
|
25
|
+
"""An OpenAIError with no .code, to exercise the class-name fallback."""
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class BuildErrorIndexCommandTest(SimpleTestCase):
|
|
29
|
+
"""The command must fail with a one-line message and nonzero exit when the
|
|
30
|
+
OpenAI API errors, instead of dying with a raw traceback (issue #1)."""
|
|
31
|
+
|
|
32
|
+
def test_real_sdk_errors_are_caught_by_the_base_class(self):
|
|
33
|
+
"""Guards the assumption the fakes rely on."""
|
|
34
|
+
self.assertTrue(issubclass(RateLimitError, OpenAIError))
|
|
35
|
+
self.assertTrue(issubclass(APIConnectionError, OpenAIError))
|
|
36
|
+
|
|
37
|
+
@patch(COMMAND_BUILD_INDEX)
|
|
38
|
+
def test_rate_limit_error_becomes_one_line_command_error(self, mock_build):
|
|
39
|
+
mock_build.side_effect = _FakeAPIError(
|
|
40
|
+
"You exceeded your current quota", code="insufficient_quota"
|
|
41
|
+
)
|
|
42
|
+
with self.assertRaises(CommandError) as ctx:
|
|
43
|
+
call_command("build_error_index")
|
|
44
|
+
msg = str(ctx.exception)
|
|
45
|
+
self.assertIn("OpenAI API request failed", msg)
|
|
46
|
+
self.assertIn("insufficient_quota", msg)
|
|
47
|
+
self.assertIn("Index not built", msg)
|
|
48
|
+
self.assertEqual(ctx.exception.returncode, 1)
|
|
49
|
+
|
|
50
|
+
@patch(COMMAND_BUILD_INDEX)
|
|
51
|
+
def test_error_without_code_falls_back_to_class_name(self, mock_build):
|
|
52
|
+
mock_build.side_effect = _FakeConnectionError("connection failed")
|
|
53
|
+
with self.assertRaises(CommandError) as ctx:
|
|
54
|
+
call_command("build_error_index")
|
|
55
|
+
self.assertIn("_FakeConnectionError", str(ctx.exception))
|
|
56
|
+
|
|
57
|
+
@patch(COMMAND_BUILD_INDEX)
|
|
58
|
+
def test_success_path_unchanged(self, mock_build):
|
|
59
|
+
mock_build.return_value = {
|
|
60
|
+
"files_scanned": 2,
|
|
61
|
+
"chunks_embedded": 5,
|
|
62
|
+
"index_path": "/tmp/index.db",
|
|
63
|
+
}
|
|
64
|
+
out = StringIO()
|
|
65
|
+
call_command("build_error_index", stdout=out)
|
|
66
|
+
self.assertIn("scanned 2 files", out.getvalue())
|
|
67
|
+
self.assertIn("embedded 5 chunks", out.getvalue())
|
|
68
|
+
|
|
69
|
+
@patch(COMMAND_BUILD_INDEX)
|
|
70
|
+
def test_non_openai_errors_still_raise(self, mock_build):
|
|
71
|
+
"""Config errors (e.g. missing BASE_DIR) are not silently reworded."""
|
|
72
|
+
mock_build.side_effect = ValueError("EXPLAIN_ERRORS_RAG_INCLUDE is not set")
|
|
73
|
+
with self.assertRaises(ValueError):
|
|
74
|
+
call_command("build_error_index")
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
from django.core.management.base import BaseCommand
|
|
2
|
-
|
|
3
|
-
from explain_errors.rag.indexer import build_index
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
class Command(BaseCommand):
|
|
7
|
-
help = (
|
|
8
|
-
"Build (or rebuild) the local vector index used to ground "
|
|
9
|
-
"explain_errors explanations in project source code."
|
|
10
|
-
)
|
|
11
|
-
|
|
12
|
-
def handle(self, *args, **options):
|
|
13
|
-
result = build_index()
|
|
14
|
-
self.stdout.write(
|
|
15
|
-
f"explain_errors: scanned {result['files_scanned']} files, "
|
|
16
|
-
f"embedded {result['chunks_embedded']} chunks.\n"
|
|
17
|
-
f"Index written to {result['index_path']}"
|
|
18
|
-
)
|
|
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.3.0 → django_explain_errors-0.3.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
|