Skip to content

Commit c224fda

Browse files
authored
Release v4.3.0
2 parents cef9a23 + 13dd9c3 commit c224fda

11 files changed

Lines changed: 155 additions & 30 deletions

File tree

CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,16 @@
11
Changelog
22
=========
33

4+
## v4.3.0 (2022-11-02)
5+
6+
### Enhancements
7+
8+
* Allow preventing an exception from being reported to Bugsnag by setting the `skip_bugsnag` attr to `True`
9+
[#325](https://github.com/bugsnag/bugsnag-python/pull/325)
10+
11+
* Prevent duplicate events from being notified in the Django integration
12+
[#326](https://github.com/bugsnag/bugsnag-python/pull/326)
13+
414
## v4.2.1 (2022-05-16)
515

616
### Bug fixes

bugsnag/configuration.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,11 @@
1515
OnBreadcrumbCallback
1616
)
1717
from bugsnag.sessiontracker import SessionMiddleware
18-
from bugsnag.middleware import DefaultMiddleware, MiddlewareStack
18+
from bugsnag.middleware import (
19+
DefaultMiddleware,
20+
MiddlewareStack,
21+
skip_bugsnag_middleware
22+
)
1923
from bugsnag.utils import (fully_qualified_class_name, validate_str_setter,
2024
validate_bool_setter, validate_iterable_setter,
2125
validate_required_str_setter, validate_int_setter)
@@ -72,6 +76,7 @@ def __init__(self, logger=_sentinel):
7276
self.middleware = MiddlewareStack()
7377

7478
self.internal_middleware = MiddlewareStack()
79+
self.internal_middleware.before_notify(skip_bugsnag_middleware)
7580
self.internal_middleware.append(DefaultMiddleware)
7681
self.internal_middleware.append(SessionMiddleware)
7782

bugsnag/django/__init__.py

Lines changed: 13 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -90,10 +90,7 @@ def configure():
9090
config.runtime_versions['django'] = django.__version__
9191

9292
request_started.connect(__track_session)
93-
got_request_exception.connect(
94-
__handle_request_exception(config.logger),
95-
weak=False
96-
)
93+
got_request_exception.connect(__handle_request_exception)
9794

9895
return config
9996

@@ -103,19 +100,17 @@ def __track_session(sender, **extra):
103100
bugsnag.start_session()
104101

105102

106-
def __handle_request_exception(logger):
107-
def inner(sender, **kwargs):
108-
request = kwargs.get('request', None)
103+
def __handle_request_exception(sender, **kwargs):
104+
request = kwargs.get('request', None)
109105

110-
if request is not None:
111-
bugsnag.configure_request(django_request=request)
106+
if request is not None:
107+
bugsnag.configure_request(django_request=request)
112108

113-
try:
114-
bugsnag.auto_notify_exc_info(severity_reason={
115-
"type": "unhandledExceptionMiddleware",
116-
"attributes": {"framework": "Django"}
117-
})
118-
except Exception:
119-
logger.exception("Error in exception middleware")
120-
121-
return inner
109+
try:
110+
bugsnag.auto_notify_exc_info(severity_reason={
111+
"type": "unhandledExceptionMiddleware",
112+
"attributes": {"framework": "Django"}
113+
})
114+
115+
except Exception:
116+
bugsnag.configure().logger.exception("Error in exception middleware")

bugsnag/django/middleware.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,10 @@ def process_exception(self, request, exception):
4848
}
4949
)
5050

51+
# ensure the 'got_request_exception' signal doesn't also notify
52+
# this exception
53+
setattr(exception, 'skip_bugsnag', True)
54+
5155
except Exception:
5256
self.config.logger.exception("Error in exception middleware")
5357

bugsnag/middleware.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,15 @@ def __call__(self, event: Event):
6464
self.bugsnag(event)
6565

6666

67+
def skip_bugsnag_middleware(event: Event):
68+
"""
69+
A callback-based middleware that prevents notifying an event where the
70+
'original_error' has a 'skip_bugsnag' attr set to 'True'.
71+
"""
72+
if getattr(event.original_error, 'skip_bugsnag', False) is True:
73+
return False
74+
75+
6776
class MiddlewareStack:
6877
"""
6978
Manages a stack of Bugsnag middleware.

setup.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414

1515
setup(
1616
name='bugsnag',
17-
version='4.2.1',
17+
version='4.3.0',
1818
description='Automatic error monitoring for django, flask, etc.',
1919
long_description=__doc__,
2020
author='Simon Maynard',
@@ -37,9 +37,11 @@
3737
'Programming Language :: Python :: 3.7',
3838
'Programming Language :: Python :: 3.8',
3939
'Programming Language :: Python :: 3.9',
40+
'Programming Language :: Python :: 3.10',
41+
'Programming Language :: Python :: 3.11',
4042
'Topic :: Software Development'
4143
],
42-
package_data = {
44+
package_data={
4345
'bugsnag': ['py.typed'],
4446
},
4547
test_suite='tests',

tests/integrations/test_django.py

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,9 @@ def test_notify(bugsnag_server, django_client):
5252
assert response.status_code == 200
5353

5454
bugsnag_server.wait_for_request()
55+
56+
assert bugsnag_server.sent_report_count == 1
57+
5558
payload = bugsnag_server.received[0]['json_body']
5659
event = payload['events'][0]
5760
exception = event['exceptions'][0]
@@ -102,6 +105,9 @@ def test_enable_environment(bugsnag_server, django_client):
102105
assert response.status_code == 200
103106

104107
bugsnag_server.wait_for_request()
108+
109+
assert bugsnag_server.sent_report_count == 1
110+
105111
payload = bugsnag_server.received[0]['json_body']
106112
event = payload['events'][0]
107113
assert event['metaData']['environment']['REQUEST_METHOD'] == 'GET'
@@ -110,6 +116,9 @@ def test_enable_environment(bugsnag_server, django_client):
110116
def test_notify_custom_info(bugsnag_server, django_client):
111117
django_client.get('/notes/handled-exception-custom/')
112118
bugsnag_server.wait_for_request()
119+
120+
assert bugsnag_server.sent_report_count == 1
121+
113122
payload = bugsnag_server.received[0]['json_body']
114123
event = payload['events'][0]
115124

@@ -126,6 +135,9 @@ def test_notify_post_body(bugsnag_server, django_client):
126135
assert response.status_code == 200
127136

128137
bugsnag_server.wait_for_request()
138+
139+
assert bugsnag_server.sent_report_count == 1
140+
129141
payload = bugsnag_server.received[0]['json_body']
130142
event = payload['events'][0]
131143
exception = event['exceptions'][0]
@@ -168,6 +180,9 @@ def test_unhandled_exception(bugsnag_server, django_client):
168180
django_client.get('/notes/unhandled-crash/')
169181

170182
bugsnag_server.wait_for_request()
183+
184+
assert bugsnag_server.sent_report_count == 1
185+
171186
payload = bugsnag_server.received[0]['json_body']
172187
event = payload['events'][0]
173188
exception = event['exceptions'][0]
@@ -213,6 +228,9 @@ def test_unhandled_exception_chain(bugsnag_server, django_client):
213228
django_client.get('/notes/unhandled-crash-chain/')
214229

215230
bugsnag_server.wait_for_request()
231+
232+
assert bugsnag_server.sent_report_count == 1
233+
216234
payload = bugsnag_server.received[0]['json_body']
217235
event = payload['events'][0]
218236
exception = event['exceptions'][0]
@@ -258,6 +276,9 @@ def test_unhandled_exception_in_template(bugsnag_server, django_client):
258276
django_client.get('/notes/unhandled-template-crash/')
259277

260278
bugsnag_server.wait_for_request()
279+
280+
assert bugsnag_server.sent_report_count == 1
281+
261282
payload = bugsnag_server.received[0]['json_body']
262283
event = payload['events'][0]
263284
exception = event['exceptions'][0]
@@ -289,14 +310,17 @@ def test_ignores_http404(bugsnag_server, django_client):
289310
with pytest.raises(MissingRequestError):
290311
bugsnag_server.wait_for_request()
291312

292-
assert len(bugsnag_server.received) == 0
313+
assert bugsnag_server.sent_report_count == 0
293314

294315

295316
def test_report_error_from_http404handler(bugsnag_server, django_client):
296317
with pytest.raises(Exception):
297318
django_client.get('/notes/poorly-handled-404')
298319

299320
bugsnag_server.wait_for_request()
321+
322+
assert bugsnag_server.sent_report_count == 1
323+
300324
payload = bugsnag_server.received[0]['json_body']
301325
event = payload['events'][0]
302326
exception = event['exceptions'][0]
@@ -342,6 +366,9 @@ def test_notify_appends_user_data(bugsnag_server, django_client):
342366
assert response.status_code == 200
343367

344368
bugsnag_server.wait_for_request()
369+
370+
assert bugsnag_server.sent_report_count == 1
371+
345372
payload = bugsnag_server.received[0]['json_body']
346373
event = payload['events'][0]
347374
exception = event['exceptions'][0]
@@ -386,6 +413,9 @@ def test_crash_appends_user_data(bugsnag_server, django_client):
386413
django_client.get('/notes/unhandled-crash/')
387414

388415
bugsnag_server.wait_for_request()
416+
417+
assert bugsnag_server.sent_report_count == 1
418+
389419
payload = bugsnag_server.received[0]['json_body']
390420
event = payload['events'][0]
391421
exception = event['exceptions'][0]
@@ -431,6 +461,9 @@ def test_read_request_in_callback(bugsnag_server, django_client):
431461
django_client.get('/notes/crash-with-callback/?user_id=foo')
432462

433463
bugsnag_server.wait_for_request()
464+
465+
assert bugsnag_server.sent_report_count == 1
466+
434467
payload = bugsnag_server.received[0]['json_body']
435468
event = payload['events'][0]
436469
assert event['context'] == 'foo'
@@ -448,6 +481,9 @@ def test_bugsnag_middleware_leaves_breadcrumb_with_referer(
448481
assert response.status_code == 200
449482

450483
bugsnag_server.wait_for_request()
484+
485+
assert bugsnag_server.sent_report_count == 1
486+
451487
payload = bugsnag_server.received[0]['json_body']
452488
event = payload['events'][0]
453489
exception = event['exceptions'][0]

tests/test_client.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1456,6 +1456,43 @@ def test_ignore_classes_has_no_exception_chain_with_no_cause(self):
14561456

14571457
assert self.sent_report_count == 1
14581458

1459+
def test_skip_bugsnag_attr_prevents_notify_when_true(self):
1460+
exception = Exception('Testing Notify')
1461+
self.client.notify(exception)
1462+
1463+
assert self.sent_report_count == 1
1464+
1465+
exception.skip_bugsnag = True
1466+
self.client.notify(exception)
1467+
1468+
assert self.sent_report_count == 1
1469+
1470+
def test_setting_skip_bugsnag_attr_to_false_allows_notify(self):
1471+
exception = Exception('Testing Notify')
1472+
exception.skip_bugsnag = True
1473+
1474+
self.client.notify(exception)
1475+
1476+
assert self.sent_report_count == 0
1477+
1478+
exception.skip_bugsnag = False
1479+
self.client.notify(exception)
1480+
1481+
assert self.sent_report_count == 1
1482+
1483+
def test_deleting_skip_bugsnag_attr_allows_notify(self):
1484+
exception = Exception('Testing Notify')
1485+
exception.skip_bugsnag = True
1486+
1487+
self.client.notify(exception)
1488+
1489+
assert self.sent_report_count == 0
1490+
1491+
delattr(exception, 'skip_bugsnag')
1492+
self.client.notify(exception)
1493+
1494+
assert self.sent_report_count == 1
1495+
14591496

14601497
@pytest.mark.parametrize("metadata,type", [
14611498
(1234, 'int'),

tests/test_configuration.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
from bugsnag.breadcrumbs import BreadcrumbType
1212
from bugsnag.configuration import Configuration
1313
from bugsnag.error import Error
14-
from bugsnag.middleware import DefaultMiddleware
14+
from bugsnag.middleware import DefaultMiddleware, SimpleMiddleware
1515
from bugsnag.sessiontracker import SessionMiddleware
1616

1717
import pytest
@@ -85,9 +85,13 @@ def test_session_tracking_defaults(self):
8585

8686
def test_default_middleware_location(self):
8787
c = Configuration()
88-
self.assertEqual(c.internal_middleware.stack,
89-
[DefaultMiddleware, SessionMiddleware])
90-
self.assertEqual(len(c.middleware.stack), 0)
88+
89+
assert len(c.internal_middleware.stack) == 3
90+
assert isinstance(c.internal_middleware.stack[0], SimpleMiddleware)
91+
assert c.internal_middleware.stack[1] is DefaultMiddleware
92+
assert c.internal_middleware.stack[2] is SessionMiddleware
93+
94+
assert len(c.middleware.stack) == 0
9195

9296
def test_validate_api_key(self):
9397
c = Configuration()

tests/test_middleware.py

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import unittest
22

33
import bugsnag
4-
from bugsnag.middleware import MiddlewareStack
4+
from bugsnag.middleware import MiddlewareStack, skip_bugsnag_middleware
55
from bugsnag.configuration import RequestConfiguration
66

77

@@ -41,9 +41,9 @@ def __call__(self, item):
4141
return
4242

4343

44-
def create_event() -> bugsnag.Event:
44+
def create_event(exception=None) -> bugsnag.Event:
4545
return bugsnag.Event(
46-
RuntimeError('oh no!'),
46+
exception or RuntimeError('oh no!'),
4747
bugsnag.configure(),
4848
RequestConfiguration.get_instance()
4949
)
@@ -153,3 +153,17 @@ def test_callback_not_run_if_middleware_returns(self):
153153
m.run(a, lambda: a.append('Callback'))
154154

155155
self.assertEqual(a, ['A'])
156+
157+
def test_skip_bugsnag_middleware_returns_false_when_attr_is_present(self):
158+
exception = Exception('oh no')
159+
exception.skip_bugsnag = True
160+
161+
event = create_event(exception)
162+
163+
assert skip_bugsnag_middleware(event) is False
164+
165+
def test_skip_bugsnag_middleware_returns_none_when_attr_is_missing(self):
166+
exception = Exception('oh no')
167+
event = create_event(exception)
168+
169+
assert skip_bugsnag_middleware(event) is None

0 commit comments

Comments
 (0)