django-explain-errors 0.10.0__tar.gz → 0.10.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.10.0/django_explain_errors.egg-info → django_explain_errors-0.10.1}/PKG-INFO +1 -1
- {django_explain_errors-0.10.0 → django_explain_errors-0.10.1/django_explain_errors.egg-info}/PKG-INFO +1 -1
- {django_explain_errors-0.10.0 → django_explain_errors-0.10.1}/django_explain_errors.egg-info/SOURCES.txt +1 -0
- {django_explain_errors-0.10.0 → django_explain_errors-0.10.1}/explain_errors/middleware.py +5 -1
- {django_explain_errors-0.10.0 → django_explain_errors-0.10.1}/setup.py +1 -1
- django_explain_errors-0.10.1/tests/test_async_marking.py +82 -0
- {django_explain_errors-0.10.0 → django_explain_errors-0.10.1}/LICENSE +0 -0
- {django_explain_errors-0.10.0 → django_explain_errors-0.10.1}/MANIFEST.in +0 -0
- {django_explain_errors-0.10.0 → django_explain_errors-0.10.1}/README.md +0 -0
- {django_explain_errors-0.10.0 → django_explain_errors-0.10.1}/django_explain_errors.egg-info/dependency_links.txt +0 -0
- {django_explain_errors-0.10.0 → django_explain_errors-0.10.1}/django_explain_errors.egg-info/requires.txt +0 -0
- {django_explain_errors-0.10.0 → django_explain_errors-0.10.1}/django_explain_errors.egg-info/top_level.txt +0 -0
- {django_explain_errors-0.10.0 → django_explain_errors-0.10.1}/explain_errors/__init__.py +0 -0
- {django_explain_errors-0.10.0 → django_explain_errors-0.10.1}/explain_errors/client.py +0 -0
- {django_explain_errors-0.10.0 → django_explain_errors-0.10.1}/explain_errors/debug_page.py +0 -0
- {django_explain_errors-0.10.0 → django_explain_errors-0.10.1}/explain_errors/management/__init__.py +0 -0
- {django_explain_errors-0.10.0 → django_explain_errors-0.10.1}/explain_errors/management/commands/__init__.py +0 -0
- {django_explain_errors-0.10.0 → django_explain_errors-0.10.1}/explain_errors/management/commands/build_error_index.py +0 -0
- {django_explain_errors-0.10.0 → django_explain_errors-0.10.1}/explain_errors/rag/__init__.py +0 -0
- {django_explain_errors-0.10.0 → django_explain_errors-0.10.1}/explain_errors/rag/indexer.py +0 -0
- {django_explain_errors-0.10.0 → django_explain_errors-0.10.1}/explain_errors/rag/retriever.py +0 -0
- {django_explain_errors-0.10.0 → django_explain_errors-0.10.1}/explain_errors/rag/store.py +0 -0
- {django_explain_errors-0.10.0 → django_explain_errors-0.10.1}/explain_errors/sanitize.py +0 -0
- {django_explain_errors-0.10.0 → django_explain_errors-0.10.1}/explain_errors/throttle.py +0 -0
- {django_explain_errors-0.10.0 → django_explain_errors-0.10.1}/explain_errors/tracebacks.py +0 -0
- {django_explain_errors-0.10.0 → django_explain_errors-0.10.1}/setup.cfg +0 -0
- {django_explain_errors-0.10.0 → django_explain_errors-0.10.1}/tests/test_build_error_index.py +0 -0
- {django_explain_errors-0.10.0 → django_explain_errors-0.10.1}/tests/test_client.py +0 -0
- {django_explain_errors-0.10.0 → django_explain_errors-0.10.1}/tests/test_debug_page.py +0 -0
- {django_explain_errors-0.10.0 → django_explain_errors-0.10.1}/tests/test_dedup.py +0 -0
- {django_explain_errors-0.10.0 → django_explain_errors-0.10.1}/tests/test_eval_fixtures.py +0 -0
- {django_explain_errors-0.10.0 → django_explain_errors-0.10.1}/tests/test_eval_judge.py +0 -0
- {django_explain_errors-0.10.0 → django_explain_errors-0.10.1}/tests/test_eval_run.py +0 -0
- {django_explain_errors-0.10.0 → django_explain_errors-0.10.1}/tests/test_language.py +0 -0
- {django_explain_errors-0.10.0 → django_explain_errors-0.10.1}/tests/test_middleware.py +0 -0
- {django_explain_errors-0.10.0 → django_explain_errors-0.10.1}/tests/test_print_stdout.py +0 -0
- {django_explain_errors-0.10.0 → django_explain_errors-0.10.1}/tests/test_process_exception_split.py +0 -0
- {django_explain_errors-0.10.0 → django_explain_errors-0.10.1}/tests/test_rag.py +0 -0
- {django_explain_errors-0.10.0 → django_explain_errors-0.10.1}/tests/test_sanitize.py +0 -0
- {django_explain_errors-0.10.0 → django_explain_errors-0.10.1}/tests/test_signals.py +0 -0
- {django_explain_errors-0.10.0 → django_explain_errors-0.10.1}/tests/test_throttle.py +0 -0
- {django_explain_errors-0.10.0 → django_explain_errors-0.10.1}/tests/test_tracebacks.py +0 -0
- {django_explain_errors-0.10.0 → django_explain_errors-0.10.1}/tests/test_truncation.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: django-explain-errors
|
|
3
|
-
Version: 0.10.
|
|
3
|
+
Version: 0.10.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
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: django-explain-errors
|
|
3
|
-
Version: 0.10.
|
|
3
|
+
Version: 0.10.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
|
|
@@ -5,7 +5,7 @@ from collections import OrderedDict
|
|
|
5
5
|
|
|
6
6
|
from django.conf import settings
|
|
7
7
|
from django.http import JsonResponse
|
|
8
|
-
from asgiref.sync import sync_to_async
|
|
8
|
+
from asgiref.sync import markcoroutinefunction, sync_to_async
|
|
9
9
|
|
|
10
10
|
from .client import get_openai_client
|
|
11
11
|
from .debug_page import ExplainErrorsExceptionReporter
|
|
@@ -71,6 +71,10 @@ class ExplainErrorsMiddleware:
|
|
|
71
71
|
def __init__(self, get_response):
|
|
72
72
|
self.get_response = get_response
|
|
73
73
|
self._is_async = asyncio.iscoroutinefunction(get_response)
|
|
74
|
+
if self._is_async:
|
|
75
|
+
# Without this, iscoroutinefunction(self) is False and middleware
|
|
76
|
+
# above this one runs in sync mode and receives a coroutine.
|
|
77
|
+
markcoroutinefunction(self)
|
|
74
78
|
self.openai_client = None
|
|
75
79
|
self.throttle = None
|
|
76
80
|
self.dedup_enabled = False
|
|
@@ -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.10.
|
|
13
|
+
version='0.10.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',
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
from unittest.mock import MagicMock, patch
|
|
2
|
+
|
|
3
|
+
from asgiref.sync import iscoroutinefunction
|
|
4
|
+
from django.http import HttpResponse
|
|
5
|
+
from django.test import AsyncClient, Client, SimpleTestCase, override_settings
|
|
6
|
+
|
|
7
|
+
from explain_errors.middleware import ExplainErrorsMiddleware
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def _mock_openai():
|
|
11
|
+
patcher = patch("openai.OpenAI")
|
|
12
|
+
mock_cls = patcher.start()
|
|
13
|
+
client = MagicMock()
|
|
14
|
+
client.chat.completions.create.return_value = MagicMock(
|
|
15
|
+
choices=[MagicMock(message=MagicMock(content="Mocked explanation."))]
|
|
16
|
+
)
|
|
17
|
+
mock_cls.return_value = client
|
|
18
|
+
return patcher
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@override_settings(
|
|
22
|
+
DEBUG=True,
|
|
23
|
+
OPENAI_API_KEY="test-key",
|
|
24
|
+
ROOT_URLCONF="tests.urls_debug_page",
|
|
25
|
+
MIDDLEWARE=[
|
|
26
|
+
"django.middleware.common.CommonMiddleware",
|
|
27
|
+
"explain_errors.middleware.ExplainErrorsMiddleware",
|
|
28
|
+
],
|
|
29
|
+
)
|
|
30
|
+
class AsyncCoroutineMarkingRequestCycleTest(SimpleTestCase):
|
|
31
|
+
"""Through the real handler chain, with an async-capable middleware
|
|
32
|
+
above ExplainErrorsMiddleware, as in a default project."""
|
|
33
|
+
|
|
34
|
+
def setUp(self):
|
|
35
|
+
patcher = _mock_openai()
|
|
36
|
+
self.addCleanup(patcher.stop)
|
|
37
|
+
|
|
38
|
+
def _assert_banner_500(self, response):
|
|
39
|
+
self.assertEqual(response.status_code, 500)
|
|
40
|
+
content = response.content.decode()
|
|
41
|
+
self.assertIn('id="explain-errors"', content)
|
|
42
|
+
self.assertIn("Mocked explanation.", content)
|
|
43
|
+
|
|
44
|
+
async def test_async_client_sync_view(self):
|
|
45
|
+
response = await AsyncClient(raise_request_exception=False).get("/boom/")
|
|
46
|
+
self._assert_banner_500(response)
|
|
47
|
+
|
|
48
|
+
async def test_async_client_async_view(self):
|
|
49
|
+
response = await AsyncClient(raise_request_exception=False).get("/boom-async/")
|
|
50
|
+
self._assert_banner_500(response)
|
|
51
|
+
|
|
52
|
+
async def test_async_client_async_view_no_exception(self):
|
|
53
|
+
response = await AsyncClient().get("/ok-async/")
|
|
54
|
+
self.assertEqual(response.status_code, 200)
|
|
55
|
+
self.assertEqual(response.content, b"ok")
|
|
56
|
+
|
|
57
|
+
def test_sync_client_sync_view(self):
|
|
58
|
+
response = Client(raise_request_exception=False).get("/boom/")
|
|
59
|
+
self._assert_banner_500(response)
|
|
60
|
+
|
|
61
|
+
def test_sync_client_async_view(self):
|
|
62
|
+
response = Client(raise_request_exception=False).get("/boom-async/")
|
|
63
|
+
self._assert_banner_500(response)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
@override_settings(DEBUG=True, OPENAI_API_KEY="test-key")
|
|
67
|
+
class AsyncCoroutineMarkingInstanceTest(SimpleTestCase):
|
|
68
|
+
def setUp(self):
|
|
69
|
+
patcher = _mock_openai()
|
|
70
|
+
self.addCleanup(patcher.stop)
|
|
71
|
+
|
|
72
|
+
def test_marked_as_coroutine_function_when_get_response_is_async(self):
|
|
73
|
+
async def get_response(request):
|
|
74
|
+
return HttpResponse("ok")
|
|
75
|
+
|
|
76
|
+
self.assertTrue(iscoroutinefunction(ExplainErrorsMiddleware(get_response)))
|
|
77
|
+
|
|
78
|
+
def test_not_marked_when_get_response_is_sync(self):
|
|
79
|
+
def get_response(request):
|
|
80
|
+
return HttpResponse("ok")
|
|
81
|
+
|
|
82
|
+
self.assertFalse(iscoroutinefunction(ExplainErrorsMiddleware(get_response)))
|
|
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.10.0 → django_explain_errors-0.10.1}/explain_errors/management/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{django_explain_errors-0.10.0 → django_explain_errors-0.10.1}/explain_errors/rag/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{django_explain_errors-0.10.0 → django_explain_errors-0.10.1}/explain_errors/rag/retriever.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{django_explain_errors-0.10.0 → django_explain_errors-0.10.1}/tests/test_build_error_index.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
|
{django_explain_errors-0.10.0 → django_explain_errors-0.10.1}/tests/test_process_exception_split.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|