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.
Files changed (43) hide show
  1. {django_explain_errors-0.10.0/django_explain_errors.egg-info → django_explain_errors-0.10.1}/PKG-INFO +1 -1
  2. {django_explain_errors-0.10.0 → django_explain_errors-0.10.1/django_explain_errors.egg-info}/PKG-INFO +1 -1
  3. {django_explain_errors-0.10.0 → django_explain_errors-0.10.1}/django_explain_errors.egg-info/SOURCES.txt +1 -0
  4. {django_explain_errors-0.10.0 → django_explain_errors-0.10.1}/explain_errors/middleware.py +5 -1
  5. {django_explain_errors-0.10.0 → django_explain_errors-0.10.1}/setup.py +1 -1
  6. django_explain_errors-0.10.1/tests/test_async_marking.py +82 -0
  7. {django_explain_errors-0.10.0 → django_explain_errors-0.10.1}/LICENSE +0 -0
  8. {django_explain_errors-0.10.0 → django_explain_errors-0.10.1}/MANIFEST.in +0 -0
  9. {django_explain_errors-0.10.0 → django_explain_errors-0.10.1}/README.md +0 -0
  10. {django_explain_errors-0.10.0 → django_explain_errors-0.10.1}/django_explain_errors.egg-info/dependency_links.txt +0 -0
  11. {django_explain_errors-0.10.0 → django_explain_errors-0.10.1}/django_explain_errors.egg-info/requires.txt +0 -0
  12. {django_explain_errors-0.10.0 → django_explain_errors-0.10.1}/django_explain_errors.egg-info/top_level.txt +0 -0
  13. {django_explain_errors-0.10.0 → django_explain_errors-0.10.1}/explain_errors/__init__.py +0 -0
  14. {django_explain_errors-0.10.0 → django_explain_errors-0.10.1}/explain_errors/client.py +0 -0
  15. {django_explain_errors-0.10.0 → django_explain_errors-0.10.1}/explain_errors/debug_page.py +0 -0
  16. {django_explain_errors-0.10.0 → django_explain_errors-0.10.1}/explain_errors/management/__init__.py +0 -0
  17. {django_explain_errors-0.10.0 → django_explain_errors-0.10.1}/explain_errors/management/commands/__init__.py +0 -0
  18. {django_explain_errors-0.10.0 → django_explain_errors-0.10.1}/explain_errors/management/commands/build_error_index.py +0 -0
  19. {django_explain_errors-0.10.0 → django_explain_errors-0.10.1}/explain_errors/rag/__init__.py +0 -0
  20. {django_explain_errors-0.10.0 → django_explain_errors-0.10.1}/explain_errors/rag/indexer.py +0 -0
  21. {django_explain_errors-0.10.0 → django_explain_errors-0.10.1}/explain_errors/rag/retriever.py +0 -0
  22. {django_explain_errors-0.10.0 → django_explain_errors-0.10.1}/explain_errors/rag/store.py +0 -0
  23. {django_explain_errors-0.10.0 → django_explain_errors-0.10.1}/explain_errors/sanitize.py +0 -0
  24. {django_explain_errors-0.10.0 → django_explain_errors-0.10.1}/explain_errors/throttle.py +0 -0
  25. {django_explain_errors-0.10.0 → django_explain_errors-0.10.1}/explain_errors/tracebacks.py +0 -0
  26. {django_explain_errors-0.10.0 → django_explain_errors-0.10.1}/setup.cfg +0 -0
  27. {django_explain_errors-0.10.0 → django_explain_errors-0.10.1}/tests/test_build_error_index.py +0 -0
  28. {django_explain_errors-0.10.0 → django_explain_errors-0.10.1}/tests/test_client.py +0 -0
  29. {django_explain_errors-0.10.0 → django_explain_errors-0.10.1}/tests/test_debug_page.py +0 -0
  30. {django_explain_errors-0.10.0 → django_explain_errors-0.10.1}/tests/test_dedup.py +0 -0
  31. {django_explain_errors-0.10.0 → django_explain_errors-0.10.1}/tests/test_eval_fixtures.py +0 -0
  32. {django_explain_errors-0.10.0 → django_explain_errors-0.10.1}/tests/test_eval_judge.py +0 -0
  33. {django_explain_errors-0.10.0 → django_explain_errors-0.10.1}/tests/test_eval_run.py +0 -0
  34. {django_explain_errors-0.10.0 → django_explain_errors-0.10.1}/tests/test_language.py +0 -0
  35. {django_explain_errors-0.10.0 → django_explain_errors-0.10.1}/tests/test_middleware.py +0 -0
  36. {django_explain_errors-0.10.0 → django_explain_errors-0.10.1}/tests/test_print_stdout.py +0 -0
  37. {django_explain_errors-0.10.0 → django_explain_errors-0.10.1}/tests/test_process_exception_split.py +0 -0
  38. {django_explain_errors-0.10.0 → django_explain_errors-0.10.1}/tests/test_rag.py +0 -0
  39. {django_explain_errors-0.10.0 → django_explain_errors-0.10.1}/tests/test_sanitize.py +0 -0
  40. {django_explain_errors-0.10.0 → django_explain_errors-0.10.1}/tests/test_signals.py +0 -0
  41. {django_explain_errors-0.10.0 → django_explain_errors-0.10.1}/tests/test_throttle.py +0 -0
  42. {django_explain_errors-0.10.0 → django_explain_errors-0.10.1}/tests/test_tracebacks.py +0 -0
  43. {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.0
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.0
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
@@ -21,6 +21,7 @@ explain_errors/rag/__init__.py
21
21
  explain_errors/rag/indexer.py
22
22
  explain_errors/rag/retriever.py
23
23
  explain_errors/rag/store.py
24
+ tests/test_async_marking.py
24
25
  tests/test_build_error_index.py
25
26
  tests/test_client.py
26
27
  tests/test_debug_page.py
@@ -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.0',
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)))