diff --git a/CHANGELOG.md b/CHANGELOG.md index d90b47a7..1fda530e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,13 @@ Changelog ========= +## v4.9.0 (2026-04-21) + +### Enhancements + +* Add `bugsnag.__version__` attribute for programmatic version checking as per PEP 396 specification + [#409](https://github.com/bugsnag/bugsnag-python/pull/409) + ## v4.8.1 (2026-01-23) ### Changes diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 94378b8d..e0072bd2 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -3,9 +3,10 @@ Contributing ------------ - [Fork](https://help.github.com/articles/fork-a-repo) the [notifier on github](https://github.com/bugsnag/bugsnag-python) +- Create a new branch from `next` - Commit and push until you are happy with your contribution - Run the tests -- [Make a pull request](https://help.github.com/articles/using-pull-requests) +- [Make a pull request](https://help.github.com/articles/using-pull-requests) into the `next` branch - Thanks! Running the tests @@ -90,15 +91,27 @@ If you're on the core team, you can release Bugsnag as follows: ## Making a release -* Create branch for the release +* Create branch for the release from `next` ``` git checkout -b release/v4.x.x ``` -* Update the version number in [`setup.py`](./setup.py) and `bugsnag/notifier.py`(./bugsnag/notifier.py) -* Update the CHANGELOG.md and README.md if necessary +* Update the version number using the Makefile + + ``` + make VERSION=4.x.x bump + ``` + +* Update the CHANGELOG.md (add version and date) and README.md if necessary * Commit and open a pull request into `master` + + ``` + git add bugsnag/__init__.py setup.py bugsnag/notifier.py CHANGELOG.md + git commit -m "Release v4.x.x" + git push origin release/v4.x.x + ``` + * Merge the PR when it's been reviewed * Create a release on GitHub, tagging the new version `v4.x.x` * Push the release to PyPI @@ -108,6 +121,7 @@ If you're on the core team, you can release Bugsnag as follows: python setup.py sdist bdist_wheel twine upload dist/* ``` +* After the release, create a PR to merge `master` back into `next` to keep the branches in sync. ## Update docs.bugsnag.com diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..7a2e6c4b --- /dev/null +++ b/Makefile @@ -0,0 +1,12 @@ +.PHONY: bump + +bump: ## Bump the version numbers to $VERSION +ifeq ($(VERSION),) + @$(error VERSION is not defined. Run with `make VERSION=number bump`) +endif + @echo Bumping the version number to $(VERSION) + @sed -i.bak "s/__version__ = '.*'/__version__ = '$(VERSION)'/" bugsnag/__init__.py && rm bugsnag/__init__.py.bak + @sed -i.bak "s/version='.*',/version='$(VERSION)',/" setup.py && rm setup.py.bak + @sed -i.bak "s/'version': '.*'/'version': '$(VERSION)'/" bugsnag/notifier.py && rm bugsnag/notifier.py.bak + @echo "Successfully bumped version to $(VERSION)" + @echo "Updated files: bugsnag/__init__.py, setup.py, bugsnag/notifier.py" diff --git a/bugsnag/__init__.py b/bugsnag/__init__.py index 9c023b2f..e9d93e08 100644 --- a/bugsnag/__init__.py +++ b/bugsnag/__init__.py @@ -18,6 +18,8 @@ clear_feature_flag, clear_feature_flags, aws_lambda_handler) +__version__ = '4.9.0' + __all__ = ('Client', 'Event', 'Configuration', 'RequestConfiguration', 'configuration', 'configure', 'configure_request', 'add_metadata_tab', 'clear_request_config', 'notify', @@ -27,4 +29,4 @@ 'OnBreadcrumbCallback', 'leave_breadcrumb', 'add_on_breadcrumb', 'remove_on_breadcrumb', 'FeatureFlag', 'add_feature_flag', 'add_feature_flags', 'clear_feature_flag', 'clear_feature_flags', - 'aws_lambda_handler') + 'aws_lambda_handler', '__version__') diff --git a/bugsnag/notifier.py b/bugsnag/notifier.py index c9b3b212..426ab721 100644 --- a/bugsnag/notifier.py +++ b/bugsnag/notifier.py @@ -1,5 +1,5 @@ _NOTIFIER_INFORMATION = { 'name': 'Python Bugsnag Notifier', 'url': 'https://github.com/bugsnag/bugsnag-python', - 'version': '4.8.0' + 'version': '4.9.0' } diff --git a/features/aws-lambda/handled.feature b/features/aws-lambda/handled.feature index ebb20a92..0060a70c 100644 --- a/features/aws-lambda/handled.feature +++ b/features/aws-lambda/handled.feature @@ -1,6 +1,5 @@ # 3.9 is currently the minimum python version with a lambda runtime -# 3.14 is not supported by the AWS `sam` CLI at the moment -@not-python-3.5 @not-python-3.6 @not-python-3.7 @not-python-3.8 @not-python-3.14 +@not-python-3.5 @not-python-3.6 @not-python-3.7 @not-python-3.8 Feature: Handled exceptions in AWS Lambda Scenario: Handled exceptions are delivered in an AWS Lambda app diff --git a/features/aws-lambda/sessions.feature b/features/aws-lambda/sessions.feature index 3e85cf99..ca3739ca 100644 --- a/features/aws-lambda/sessions.feature +++ b/features/aws-lambda/sessions.feature @@ -1,4 +1,4 @@ -@not-python-3.5 @not-python-3.6 @not-python-3.7 @not-python-3.8 @not-python-3.14 +@not-python-3.5 @not-python-3.6 @not-python-3.7 @not-python-3.8 Feature: Sessions in AWS Lambda Scenario: Manually started sessions are delivered in an AWS Lambda app when auto_capture_sessions is True diff --git a/features/aws-lambda/unhandled.feature b/features/aws-lambda/unhandled.feature index c21d43f1..f3c0acb6 100644 --- a/features/aws-lambda/unhandled.feature +++ b/features/aws-lambda/unhandled.feature @@ -1,4 +1,4 @@ -@not-python-3.5 @not-python-3.6 @not-python-3.7 @not-python-3.8 @not-python-3.14 +@not-python-3.5 @not-python-3.6 @not-python-3.7 @not-python-3.8 Feature: Unhandled exceptions in AWS Lambda Scenario: Unhandled exceptions are delivered in an AWS Lambda app diff --git a/features/support/env.rb b/features/support/env.rb index 9f3ae675..1fb69453 100644 --- a/features/support/env.rb +++ b/features/support/env.rb @@ -15,12 +15,12 @@ def current_ip Maze.config.file_log = false Maze.config.log_requests = true - # don't wait so long for requests/not to receive requests - Maze.config.receive_requests_wait = 10 - Maze.config.receive_no_requests_wait = 10 + # Increased timeout to 20s for AWS Lambda cold starts (especially Python 3.12+) + Maze.config.receive_requests_wait = 20 + Maze.config.receive_no_requests_wait = 20 - # warn if a test takes more than 5 seconds to send a request - Maze.config.receive_requests_slow_threshold = 5 + # warn if a test takes more than 10 seconds to send a request + Maze.config.receive_requests_slow_threshold = 10 # bugsnag-python doesn't need to send the integrity header Maze.config.enforce_bugsnag_integrity = false diff --git a/setup.py b/setup.py index 223c70f6..68d2c745 100755 --- a/setup.py +++ b/setup.py @@ -14,7 +14,7 @@ setup( name='bugsnag', - version='4.8.1', + version='4.9.0', description='Automatic error monitoring for django, flask, etc.', long_description=__doc__, author='Simon Maynard', @@ -49,5 +49,5 @@ install_requires=['webob'], extras_require={ 'flask': ['flask', 'blinker'] - }, + } ) diff --git a/tests/integrations/test_asgi.py b/tests/integrations/test_asgi.py index e998f32f..bb5bed88 100644 --- a/tests/integrations/test_asgi.py +++ b/tests/integrations/test_asgi.py @@ -39,11 +39,12 @@ def test_routing_crash(self): async def other_func(): raise ScaryException('fell winds!') - @app.route('/') async def index(req): await other_func() return PlainTextResponse('pineapple') + app.add_route('/', index) + app = TestClient(BugsnagMiddleware(app)) self.assertRaises(ScaryException, lambda: app.get('/')) @@ -77,11 +78,12 @@ def test_enable_environment(self): async def other_func(): raise ScaryException('fell winds!') - @app.route('/') async def index(req): await other_func() return PlainTextResponse('pineapple') + app.add_route('/', index) + app = TestClient(BugsnagMiddleware(app)) self.assertRaises(ScaryException, lambda: app.get('/')) @@ -106,11 +108,12 @@ def test_headers_are_filtered(self): async def other_func(): raise ScaryException('fell winds!') - @app.route('/') async def index(req): await other_func() return PlainTextResponse('pineapple') + app.add_route('/', index) + app = TestClient(BugsnagMiddleware(app)) self.assertRaises( @@ -159,11 +162,12 @@ async def next_func(): bugsnag.configure_request(metadata={'wave': {'size': '35b'}}) raise ScaryException('fell winds!') - @app.route('/') async def index(req): await next_func() return PlainTextResponse('pineapple') + app.add_route('/', index) + app = TestClient(BugsnagMiddleware(app)) self.assertRaises(ScaryException, lambda: app.get('/')) @@ -229,10 +233,11 @@ async def app(scope, receive, send): def test_url_components(self): app = Starlette() - @app.route('/path') async def index(req): raise ScaryException('forgot the map') + app.add_route('/path', index) + app = TestClient(BugsnagMiddleware(app)) self.assertRaises( @@ -261,11 +266,12 @@ def test_breadcrumb_records_the_referer_header(self): async def other_func(): raise ScaryException('fell winds!') - @app.route('/') async def index(req): await other_func() return PlainTextResponse('pineapple') + app.add_route('/', index) + app = TestClient(BugsnagMiddleware(app)) headers = {'referer': 'http://testserver/abc/xyz?password=hunter2'} @@ -305,13 +311,14 @@ def test_chained_exceptions(self): async def other_func(): raise ScaryException('fell winds!') - @app.route('/') async def index(req): try: await other_func() except ScaryException as scary: raise Exception('disconcerting breeze.') from scary + app.add_route('/', index) + app = TestClient(BugsnagMiddleware(app)) with pytest.raises(Exception): @@ -351,7 +358,6 @@ async def other_func(): raise ScaryException('fell winds!') - @app.route('/') async def index(req): nonlocal count count += 1 @@ -360,6 +366,8 @@ async def index(req): await other_func() return PlainTextResponse('pineapple') + app.add_route('/', index) + app = TestClient(BugsnagMiddleware(app)) with pytest.raises(Exception):