From 6a2a0c32b37d2d4fc70e87fa322af247c5358236 Mon Sep 17 00:00:00 2001 From: Steve Kirkland Date: Thu, 18 Dec 2025 10:16:24 +0000 Subject: [PATCH 1/5] Amend secondary instance URL to bugsnag.smartbear.com --- CHANGELOG.md | 2 ++ bugsnag/configuration.py | 16 ++++++++-------- bugsnag/delivery.py | 8 ++++---- tests/test_configuration.py | 8 ++++---- 4 files changed, 18 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3495a5ba..2c96f5b6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,8 @@ Changelog [#394](https://github.com/bugsnag/bugsnag-python/pull/394). * Set default endpoints based on API key [#399](https://github.com/bugsnag/bugsnag-php/pull/399) +* Amend secondary instance URL to bugsnag.smartbear.com + [#400](https://github.com/bugsnag/bugsnag-php/pull/399) ## v4.7.1 (2024-05-22) diff --git a/bugsnag/configuration.py b/bugsnag/configuration.py index 20125937..954e564e 100644 --- a/bugsnag/configuration.py +++ b/bugsnag/configuration.py @@ -33,8 +33,8 @@ from bugsnag.delivery import (create_default_delivery, DEFAULT_ENDPOINT, DEFAULT_SESSIONS_ENDPOINT, - HUB_ENDPOINT, - HUB_SESSIONS_ENDPOINT) + SECONDARY_ENDPOINT, + SECONDARY_SESSIONS_ENDPOINT) from bugsnag.uwsgi import warn_if_running_uwsgi_without_threads from bugsnag.error import Error @@ -58,9 +58,9 @@ _sentinel = object() -def _is_hub_api_key(api_key: str) -> bool: - hub_prefix = "00000" - return api_key is not None and api_key.startswith(hub_prefix) +def _is_secondary_api_key(api_key: str) -> bool: + secondary_prefix = "00000" + return api_key is not None and api_key.startswith(secondary_prefix) class Configuration: @@ -599,9 +599,9 @@ def _initialize_endpoints(self, endpoint, session_endpoint, api_key): self.endpoint is None and self.session_endpoint is None ): - if _is_hub_api_key(api_key): - self.endpoint = HUB_ENDPOINT - self.session_endpoint = HUB_SESSIONS_ENDPOINT + if _is_secondary_api_key(api_key): + self.endpoint = SECONDARY_ENDPOINT + self.session_endpoint = SECONDARY_SESSIONS_ENDPOINT else: self.endpoint = DEFAULT_ENDPOINT self.session_endpoint = DEFAULT_SESSIONS_ENDPOINT diff --git a/bugsnag/delivery.py b/bugsnag/delivery.py index 1a8f03cb..758542d8 100644 --- a/bugsnag/delivery.py +++ b/bugsnag/delivery.py @@ -28,8 +28,8 @@ DEFAULT_ENDPOINT = 'https://notify.bugsnag.com' DEFAULT_SESSIONS_ENDPOINT = 'https://sessions.bugsnag.com' -HUB_ENDPOINT = 'https://notify.insighthub.smartbear.com' -HUB_SESSIONS_ENDPOINT = 'https://sessions.insighthub.smartbear.com' +SECONDARY_ENDPOINT = 'https://notify.bugsnag.smartbear.com' +SECONDARY_SESSIONS_ENDPOINT = 'https://sessions.bugsnag.smartbear.com' __all__ = ('default_headers', 'Delivery') @@ -86,8 +86,8 @@ def deliver_sessions(self, config, payload: Any, options=None): """ if ((config.endpoint != DEFAULT_ENDPOINT and config.session_endpoint == DEFAULT_SESSIONS_ENDPOINT) or - (config.endpoint != HUB_ENDPOINT and - config.session_endpoint == HUB_SESSIONS_ENDPOINT)): + (config.endpoint != SECONDARY_ENDPOINT and + config.session_endpoint == SECONDARY_SESSIONS_ENDPOINT)): if not self.sent_session_warning: warnings.warn('The session endpoint has not been configured. ' 'No sessions will be sent to Bugsnag.') diff --git a/tests/test_configuration.py b/tests/test_configuration.py index 57dfe03e..47103b1d 100644 --- a/tests/test_configuration.py +++ b/tests/test_configuration.py @@ -119,10 +119,10 @@ def test_validate_endpoint_bugsnag_api_key(self): c.configure(api_key='12312312312312312312312312321312') assert c.endpoint == 'https://notify.bugsnag.com' - def test_validate_endpoint_hub_api_key(self): + def test_validate_endpoint_secondary_api_key(self): c = Configuration() c.configure(api_key='00000312312312312312312312321312') - assert c.endpoint == 'https://notify.insighthub.smartbear.com' + assert c.endpoint == 'https://notify.bugsnag.smartbear.com' def test_validate_app_type(self): c = Configuration() @@ -425,11 +425,11 @@ def test_validate_session_endpoint_bugsnag_api_key(self): c.configure(api_key='12312312312312312312312312321312') assert c.session_endpoint == 'https://sessions.bugsnag.com' - def test_validate_session_endpoint_hub_api_key(self): + def test_validate_session_endpoint_secondary_api_key(self): c = Configuration() c.configure(api_key='00000312312312312312312312321312') assert (c.session_endpoint == - 'https://sessions.insighthub.smartbear.com') + 'https://sessions.bugsnag.smartbear.com') def test_validate_traceback_exclude_modules(self): c = Configuration() From ad2b73b730d9a5c59587e67d1c44d3bab66872f3 Mon Sep 17 00:00:00 2001 From: Aleksander Grzegorzewski Date: Wed, 14 Jan 2026 16:24:21 +0100 Subject: [PATCH 2/5] added python 3.14 to tests --- .github/workflows/maze-runner.yml | 2 +- .github/workflows/python-package.yml | 11 +---------- features/aws-lambda/handled.feature | 6 +++--- features/aws-lambda/sessions.feature | 3 +-- features/aws-lambda/unhandled.feature | 3 +-- features/celery.feature | 12 ++++++------ tox.ini | 11 ++++++----- 7 files changed, 19 insertions(+), 29 deletions(-) diff --git a/.github/workflows/maze-runner.yml b/.github/workflows/maze-runner.yml index ac3d3e7c..1fe130bd 100644 --- a/.github/workflows/maze-runner.yml +++ b/.github/workflows/maze-runner.yml @@ -9,7 +9,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: ['3.5', '3.6', '3.7', '3.8', '3.9', '3.10', '3.11', '3.12', '3.13'] + python-version: ['3.5', '3.6', '3.7', '3.8', '3.9', '3.10', '3.11', '3.12', '3.13', '3.14'] steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index 1e1cb2a7..ee790e55 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -9,15 +9,9 @@ jobs: strategy: fail-fast: false matrix: - python-version: ['3.8', '3.9', '3.10', '3.11', '3.12', '3.13'] + python-version: ['3.8', '3.9', '3.10', '3.11', '3.12', '3.13', '3.14'] os: ['ubuntu-latest'] include: -# Python 3.5 and 3.6 tests skipped pending PLAT-14414 -# - python-version: '3.5' -# os: 'ubuntu-22.04' -# pip-trusted-host: 'pypi.python.org pypi.org files.pythonhosted.org' -# - python-version: '3.6' -# os: 'ubuntu-22.04' - python-version: '3.7' os: 'ubuntu-22.04' @@ -28,8 +22,6 @@ jobs: uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} - env: - PIP_TRUSTED_HOST: ${{ matrix.pip-trusted-host }} - name: Install dependencies run: | @@ -44,7 +36,6 @@ jobs: - name: Upload code coverage data env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - COVERALLS_FLAG_NAME: ${{ matrix.test-name }} COVERALLS_PARALLEL: true run: | coverage combine diff --git a/features/aws-lambda/handled.feature b/features/aws-lambda/handled.feature index 5c3b22ad..ebb20a92 100644 --- a/features/aws-lambda/handled.feature +++ b/features/aws-lambda/handled.feature @@ -1,7 +1,8 @@ +# 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 Feature: Handled exceptions in AWS Lambda -# 3.9 is currently the minimum python version with a lambda runtime -@not-python-3.5 @not-python-3.6 @not-python-3.7 @not-python-3.8 Scenario: Handled exceptions are delivered in an AWS Lambda app Given I run the lambda handler "handled" with the "event.json" event When I wait to receive an error @@ -22,7 +23,6 @@ Scenario: Handled exceptions are delivered in an AWS Lambda app And the session payload has a valid sessions array And the sessionCount "sessionsStarted" equals 1 -@not-python-3.5 @not-python-3.6 @not-python-3.7 @not-python-3.8 Scenario: Handled exceptions are delivered in an AWS Lambda app when auto_notify is False Given I run the lambda handler "handled_no_auto_notify" with the "event.json" event When I wait to receive an error diff --git a/features/aws-lambda/sessions.feature b/features/aws-lambda/sessions.feature index 7c90e0ab..3e85cf99 100644 --- a/features/aws-lambda/sessions.feature +++ b/features/aws-lambda/sessions.feature @@ -1,6 +1,6 @@ +@not-python-3.5 @not-python-3.6 @not-python-3.7 @not-python-3.8 @not-python-3.14 Feature: Sessions in AWS Lambda -@not-python-3.5 @not-python-3.6 @not-python-3.7 @not-python-3.8 Scenario: Manually started sessions are delivered in an AWS Lambda app when auto_capture_sessions is True Given I run the lambda handler "manual_session" with the "event.json" event When I wait to receive a session @@ -9,7 +9,6 @@ Scenario: Manually started sessions are delivered in an AWS Lambda app when auto And the sessionCount "sessionsStarted" equals 2 And I should receive no errors -@not-python-3.5 @not-python-3.6 @not-python-3.7 @not-python-3.8 Scenario: Manually started sessions are delivered in an AWS Lambda app when auto_capture_sessions is False Given I run the lambda handler "manual_session_no_auto_capture_sessions" with the "event.json" event When I wait to receive a session diff --git a/features/aws-lambda/unhandled.feature b/features/aws-lambda/unhandled.feature index a3216521..c21d43f1 100644 --- a/features/aws-lambda/unhandled.feature +++ b/features/aws-lambda/unhandled.feature @@ -1,6 +1,6 @@ +@not-python-3.5 @not-python-3.6 @not-python-3.7 @not-python-3.8 @not-python-3.14 Feature: Unhandled exceptions in AWS Lambda -@not-python-3.5 @not-python-3.6 @not-python-3.7 @not-python-3.8 Scenario: Unhandled exceptions are delivered in an AWS Lambda app Given I run the lambda handler "unhandled" with the "event.json" event When I wait to receive an error @@ -21,7 +21,6 @@ Scenario: Unhandled exceptions are delivered in an AWS Lambda app And the session payload has a valid sessions array And the sessionCount "sessionsStarted" equals 1 -@not-python-3.5 @not-python-3.6 @not-python-3.7 @not-python-3.8 Scenario: Unhandled exceptions are not delivered in an AWS Lambda app when auto_detect_errors is False Given I run the lambda handler "unhandled_no_auto_notify" with the "event.json" event When I wait to receive a session diff --git a/features/celery.feature b/features/celery.feature index 85ca139e..3efe5ecc 100644 --- a/features/celery.feature +++ b/features/celery.feature @@ -12,7 +12,7 @@ Scenario Outline: Handled exceptions are delivered in Celery And the event "severityReason.type" equals "handledException" And the event "device.runtimeVersions.celery" matches "\.\d+\.\d+" - @not-python-3.11 @not-python-3.12 @not-python-3.13 + @not-python-3.11 @not-python-3.12 @not-python-3.13 @not-python-3.14 Examples: | celery-version | | 4 | @@ -40,7 +40,7 @@ Scenario Outline: Unhandled exceptions are delivered in Celery And the event "metaData.extra_data.args" string is empty And the event "metaData.extra_data.kwargs" string is empty - @not-python-3.11 @not-python-3.12 @not-python-3.13 + @not-python-3.11 @not-python-3.12 @not-python-3.13 @not-python-3.14 Examples: | celery-version | | 4 | @@ -72,7 +72,7 @@ Scenario Outline: Task arguments are added to metadata in Celery And the event "metaData.extra_data.args.1" equals "0" And the event "metaData.extra_data.kwargs" string is empty - @not-python-3.11 @not-python-3.12 @not-python-3.13 + @not-python-3.11 @not-python-3.12 @not-python-3.13 @not-python-3.14 Examples: | celery-version | | 4 | @@ -116,7 +116,7 @@ Scenario Outline: Successful tasks do not report errors in Celery " Then I should receive no errors - @not-python-3.11 @not-python-3.12 @not-python-3.13 + @not-python-3.11 @not-python-3.12 @not-python-3.13 @not-python-3.14 Examples: | celery-version | | 4 | @@ -131,7 +131,7 @@ Scenario Outline: Successful shared tasks do not report errors in Celery " Then I should receive no errors - @not-python-3.11 @not-python-3.12 @not-python-3.13 + @not-python-3.11 @not-python-3.12 @not-python-3.13 @not-python-3.14 Examples: | celery-version | | 4 | diff --git a/tox.ini b/tox.ini index e5135fe7..08061af7 100644 --- a/tox.ini +++ b/tox.ini @@ -1,15 +1,15 @@ [tox] envlist= - py{35,36,37,38,39,310,311,312,313}-{test,requests,flask,tornado,wsgi,bottle} - py{36,37,38,39,310,311,312,313}-asgi + py{35,36,37,38,39,310,311,312,313,314}-{test,requests,flask,tornado,wsgi,bottle} + py{36,37,38,39,310,311,312,313,314}-asgi py{35,36,37}-django{18,19,110,111} py{35,36,37,38,39}-django20 py{35,36,37,38,39,310}-django{21,22} py{36,37,38,39,310,311,312,313}-django3 py{38,39,310,311,312,313}-django4 - py{38,39,310,311,312,313}-{asynctest,threadtest} - py{37,38,39,310,311,312,313}-exceptiongroup - py{35,313}-{lint} + py{38,39,310,311,312,313,314}-{asynctest,threadtest} + py{37,38,39,310,311,312,313,314}-exceptiongroup + py{35,313,314}-{lint} [pytest] testpaths = tests @@ -33,6 +33,7 @@ basepython = py311: python3.11 py312: python3.12 py313: python3.13 + py314: python3.14 whitelist_externals= {toxinidir}/scripts/lint.sh deps= From b51db1c35f6fb4a0d182354c9a338402e58f2181 Mon Sep 17 00:00:00 2001 From: Aleksander Grzegorzewski Date: Mon, 19 Jan 2026 13:07:54 +0100 Subject: [PATCH 3/5] added django 5&6 to tests --- .gitignore | 6 + tests/fixtures/django1/.gitignore | 2 - tests/fixtures/django1/manage.py | 10 -- tests/fixtures/django1/notes/urls.py | 13 -- tests/fixtures/django1/todo/settings.py | 102 -------------- tests/fixtures/django5/.gitignore | 1 + tests/fixtures/django5/manage.py | 22 +++ .../{django1 => django5}/notes/__init__.py | 0 tests/fixtures/django5/notes/apps.py | 5 + tests/fixtures/django5/notes/models.py | 9 ++ .../notes}/templates/notes/broken.html | 0 .../django5/notes/templates/notes/index.html | 16 +++ .../notes/templates/notes/note_detail.html | 3 + tests/fixtures/django5/notes/urls.py | 15 ++ .../{django1 => django5}/notes/views.py | 36 ++--- .../{django1 => django5}/todo/__init__.py | 0 tests/fixtures/django5/todo/asgi.py | 16 +++ tests/fixtures/django5/todo/settings.py | 129 ++++++++++++++++++ .../{django1 => django5}/todo/urls.py | 4 +- .../{django1 => django5}/todo/wsgi.py | 6 +- tests/fixtures/django6/.gitignore | 1 + tests/fixtures/django6/manage.py | 22 +++ tests/fixtures/django6/notes/__init__.py | 0 tests/fixtures/django6/notes/apps.py | 5 + tests/fixtures/django6/notes/models.py | 9 ++ .../django6/notes/templates/notes/broken.html | 3 + .../django6/notes/templates/notes/index.html | 16 +++ .../notes/templates/notes/note_detail.html | 3 + tests/fixtures/django6/notes/urls.py | 15 ++ tests/fixtures/django6/notes/views.py | 72 ++++++++++ tests/fixtures/django6/todo/__init__.py | 0 tests/fixtures/django6/todo/asgi.py | 16 +++ tests/fixtures/django6/todo/settings.py | 129 ++++++++++++++++++ tests/fixtures/django6/todo/urls.py | 16 +++ tests/fixtures/django6/todo/wsgi.py | 16 +++ tox.ini | 25 ++-- 36 files changed, 578 insertions(+), 165 deletions(-) delete mode 100644 tests/fixtures/django1/.gitignore delete mode 100755 tests/fixtures/django1/manage.py delete mode 100644 tests/fixtures/django1/notes/urls.py delete mode 100644 tests/fixtures/django1/todo/settings.py create mode 100644 tests/fixtures/django5/.gitignore create mode 100644 tests/fixtures/django5/manage.py rename tests/fixtures/{django1 => django5}/notes/__init__.py (100%) create mode 100644 tests/fixtures/django5/notes/apps.py create mode 100644 tests/fixtures/django5/notes/models.py rename tests/fixtures/{django1 => django5/notes}/templates/notes/broken.html (100%) create mode 100644 tests/fixtures/django5/notes/templates/notes/index.html create mode 100644 tests/fixtures/django5/notes/templates/notes/note_detail.html create mode 100644 tests/fixtures/django5/notes/urls.py rename tests/fixtures/{django1 => django5}/notes/views.py (59%) rename tests/fixtures/{django1 => django5}/todo/__init__.py (100%) create mode 100644 tests/fixtures/django5/todo/asgi.py create mode 100644 tests/fixtures/django5/todo/settings.py rename tests/fixtures/{django1 => django5}/todo/urls.py (80%) rename tests/fixtures/{django1 => django5}/todo/wsgi.py (67%) create mode 100644 tests/fixtures/django6/.gitignore create mode 100644 tests/fixtures/django6/manage.py create mode 100644 tests/fixtures/django6/notes/__init__.py create mode 100644 tests/fixtures/django6/notes/apps.py create mode 100644 tests/fixtures/django6/notes/models.py create mode 100644 tests/fixtures/django6/notes/templates/notes/broken.html create mode 100644 tests/fixtures/django6/notes/templates/notes/index.html create mode 100644 tests/fixtures/django6/notes/templates/notes/note_detail.html create mode 100644 tests/fixtures/django6/notes/urls.py create mode 100644 tests/fixtures/django6/notes/views.py create mode 100644 tests/fixtures/django6/todo/__init__.py create mode 100644 tests/fixtures/django6/todo/asgi.py create mode 100644 tests/fixtures/django6/todo/settings.py create mode 100644 tests/fixtures/django6/todo/urls.py create mode 100644 tests/fixtures/django6/todo/wsgi.py diff --git a/.gitignore b/.gitignore index 9a828436..a0aa2a33 100644 --- a/.gitignore +++ b/.gitignore @@ -39,3 +39,9 @@ my_env venv .idea *.iml + +#venv +bin +include +lib +pyvenv.cfg diff --git a/tests/fixtures/django1/.gitignore b/tests/fixtures/django1/.gitignore deleted file mode 100644 index c5cdd769..00000000 --- a/tests/fixtures/django1/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -*.pyc -*.sqlite3 \ No newline at end of file diff --git a/tests/fixtures/django1/manage.py b/tests/fixtures/django1/manage.py deleted file mode 100755 index ed799175..00000000 --- a/tests/fixtures/django1/manage.py +++ /dev/null @@ -1,10 +0,0 @@ -#!/usr/bin/env python -import os -import sys - -if __name__ == "__main__": - os.environ.setdefault("DJANGO_SETTINGS_MODULE", "todo.settings") - - from django.core.management import execute_from_command_line - - execute_from_command_line(sys.argv) diff --git a/tests/fixtures/django1/notes/urls.py b/tests/fixtures/django1/notes/urls.py deleted file mode 100644 index af6c750d..00000000 --- a/tests/fixtures/django1/notes/urls.py +++ /dev/null @@ -1,13 +0,0 @@ -from django.conf.urls import url -from . import views - -urlpatterns = [ - url(r'^$', views.index), - url(r'unhandled-crash/', views.unhandled_crash, name='crash'), - url(r'unhandled-crash-chain/', views.unhandled_crash_chain), - url(r'unhandled-template-crash/', - views.unhandled_crash_in_template), - url(r'handled-exception/', views.handle_notify), - url(r'handled-exception-custom/', views.handle_notify_custom_info), - url(r'crash-with-callback/', views.handle_crash_callback), -] diff --git a/tests/fixtures/django1/todo/settings.py b/tests/fixtures/django1/todo/settings.py deleted file mode 100644 index c44050bd..00000000 --- a/tests/fixtures/django1/todo/settings.py +++ /dev/null @@ -1,102 +0,0 @@ -""" -Django settings for todo project. - -For more information on this file, see -https://docs.djangoproject.com/en/1.6/topics/settings/ - -For the full list of settings and their values, see -https://docs.djangoproject.com/en/1.6/ref/settings/ -""" - -# Build paths inside the project like this: os.path.join(BASE_DIR, ...) -import os -from os.path import dirname, abspath, join - -import django - -BASE_DIR = dirname(dirname(abspath(__file__))) - - -# Quick-start development settings - unsuitable for production -# See https://docs.djangoproject.com/en/1.6/howto/deployment/checklist/ - -# SECURITY WARNING: keep the secret key used in production secret! -SECRET_KEY = '3n3h7r@tpqnwqtt8#avxh_t75k_6zf3x)@6cg!u(&xmz79(26h' - -# SECURITY WARNING: don't run with debug turned on in production! -DEBUG = False - -TEMPLATE_DEBUG = True - -ALLOWED_HOSTS = ['0.0.0.0', 'localhost'] - - -# Application definition - -INSTALLED_APPS = ( - 'django.contrib.admin', - 'django.contrib.auth', - 'django.contrib.contenttypes', - 'django.contrib.sessions', - 'django.contrib.messages', - 'django.contrib.staticfiles', -) - -_MIDDLEWARE = ( - "bugsnag.django.middleware.BugsnagMiddleware", - 'django.contrib.sessions.middleware.SessionMiddleware', - 'django.middleware.common.CommonMiddleware', - 'django.middleware.csrf.CsrfViewMiddleware', - 'django.contrib.auth.middleware.AuthenticationMiddleware', - 'django.contrib.messages.middleware.MessageMiddleware', - 'django.middleware.clickjacking.XFrameOptionsMiddleware' -) - -if django.VERSION >= (1, 10): - MIDDLEWARE = _MIDDLEWARE -else: - MIDDLEWARE_CLASSES = _MIDDLEWARE - -ROOT_URLCONF = 'todo.urls' - -WSGI_APPLICATION = 'todo.wsgi.application' - - -# Database -# https://docs.djangoproject.com/en/1.6/ref/settings/#databases - -DATABASES = { - 'default': { - 'ENGINE': 'django.db.backends.sqlite3', - 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), - } -} - -# Internationalization -# https://docs.djangoproject.com/en/1.6/topics/i18n/ - -LANGUAGE_CODE = 'en-us' - -TIME_ZONE = 'UTC' - -USE_I18N = True - -USE_L10N = True - -USE_TZ = True - - -# Static files (CSS, JavaScript, Images) -# https://docs.djangoproject.com/en/1.6/howto/static-files/ - -STATIC_URL = '/static/' - -TEMPLATES = [{ - 'BACKEND': 'django.template.backends.django.DjangoTemplates', - 'DIRS': [join(BASE_DIR, 'templates')] -}] - -BUGSNAG = { - 'api_key': 'a05afff2bd2ffaf0ab0f52715bbdcffd', - 'project_root': BASE_DIR, -} diff --git a/tests/fixtures/django5/.gitignore b/tests/fixtures/django5/.gitignore new file mode 100644 index 00000000..60615839 --- /dev/null +++ b/tests/fixtures/django5/.gitignore @@ -0,0 +1 @@ +*.sqlite3 diff --git a/tests/fixtures/django5/manage.py b/tests/fixtures/django5/manage.py new file mode 100644 index 00000000..244b6144 --- /dev/null +++ b/tests/fixtures/django5/manage.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python +"""Django's command-line utility for administrative tasks.""" +import os +import sys + + +def main(): + """Run administrative tasks.""" + os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'todo.settings') + try: + from django.core.management import execute_from_command_line + except ImportError as exc: + raise ImportError( + "Couldn't import Django. Are you sure it's installed and " + "available on your PYTHONPATH environment variable? Did you " + "forget to activate a virtual environment?" + ) from exc + execute_from_command_line(sys.argv) + + +if __name__ == '__main__': + main() diff --git a/tests/fixtures/django1/notes/__init__.py b/tests/fixtures/django5/notes/__init__.py similarity index 100% rename from tests/fixtures/django1/notes/__init__.py rename to tests/fixtures/django5/notes/__init__.py diff --git a/tests/fixtures/django5/notes/apps.py b/tests/fixtures/django5/notes/apps.py new file mode 100644 index 00000000..b6155aca --- /dev/null +++ b/tests/fixtures/django5/notes/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class NotesConfig(AppConfig): + name = 'notes' diff --git a/tests/fixtures/django5/notes/models.py b/tests/fixtures/django5/notes/models.py new file mode 100644 index 00000000..d884b150 --- /dev/null +++ b/tests/fixtures/django5/notes/models.py @@ -0,0 +1,9 @@ +from django.db import models + + +class Note(models.Model): + note_text = models.CharField(max_length=200) + create_date = models.DateTimeField('date created') + + def __str__(self): + return self.note_text diff --git a/tests/fixtures/django1/templates/notes/broken.html b/tests/fixtures/django5/notes/templates/notes/broken.html similarity index 100% rename from tests/fixtures/django1/templates/notes/broken.html rename to tests/fixtures/django5/notes/templates/notes/broken.html diff --git a/tests/fixtures/django5/notes/templates/notes/index.html b/tests/fixtures/django5/notes/templates/notes/index.html new file mode 100644 index 00000000..fbcbeba3 --- /dev/null +++ b/tests/fixtures/django5/notes/templates/notes/index.html @@ -0,0 +1,16 @@ +{% if latest_notes_list %} + +{% else %} +

No notes available

+{% endif %} + +
+{% csrf_token %} + + + +
diff --git a/tests/fixtures/django5/notes/templates/notes/note_detail.html b/tests/fixtures/django5/notes/templates/notes/note_detail.html new file mode 100644 index 00000000..b89113ee --- /dev/null +++ b/tests/fixtures/django5/notes/templates/notes/note_detail.html @@ -0,0 +1,3 @@ +

{{ note.id }}

+

{{ note.note_text }}

+Back to list diff --git a/tests/fixtures/django5/notes/urls.py b/tests/fixtures/django5/notes/urls.py new file mode 100644 index 00000000..cc50423e --- /dev/null +++ b/tests/fixtures/django5/notes/urls.py @@ -0,0 +1,15 @@ +from django.urls import path +from . import views + + +urlpatterns = [ + path('', views.IndexView.as_view(), name='index'), + path('/', views.DetailView.as_view(), name='detail'), + path('add/', views.add_note, name='add'), + path('unhandled-crash/', views.unhandled_crash, name='crash'), + path('unhandled-crash-chain/', views.unhandled_crash_chain), + path('unhandled-template-crash/', views.unhandled_crash_in_template), + path('handled-exception/', views.handle_notify), + path('crash-with-callback/', views.handle_crash_callback), + path('handled-exception-custom/', views.handle_notify_custom_info), +] diff --git a/tests/fixtures/django1/notes/views.py b/tests/fixtures/django5/notes/views.py similarity index 59% rename from tests/fixtures/django1/notes/views.py rename to tests/fixtures/django5/notes/views.py index e28c2d88..72a49a2b 100644 --- a/tests/fixtures/django1/notes/views.py +++ b/tests/fixtures/django5/notes/views.py @@ -1,31 +1,31 @@ import bugsnag -from django.http import HttpResponse from django.shortcuts import render +from django.http import HttpResponseRedirect, HttpResponse +from django.views import generic +from django.utils import timezone +from django.urls import reverse +from .models import Note -def index(request): - return HttpResponse(b'Some content!') +class IndexView(generic.ListView): + template_name = 'notes/index.html' + context_object_name = 'latest_notes_list' -""" -(some nonsense goes here) + def get_queryset(self): + return Note.objects.order_by('-create_date')[:5] +class DetailView(generic.DetailView): + model = Note - - - - - - - - - - - -""" +def add_note(request): + note_text = request.POST['note_text'] + note = Note(note_text=note_text, create_date=timezone.now()) + note.save() + return HttpResponseRedirect(reverse('detail', args=(note.id,))) def unhandled_crash(request): @@ -43,7 +43,7 @@ def handle_notify(request): except KeyError as e: bugsnag.notify(e, unhappy='nonexistent-file') - return HttpResponse(b'everything is fine!', content_type='text/plain') + return HttpResponse('everything is fine!', content_type='text/plain') def handle_notify_custom_info(request): diff --git a/tests/fixtures/django1/todo/__init__.py b/tests/fixtures/django5/todo/__init__.py similarity index 100% rename from tests/fixtures/django1/todo/__init__.py rename to tests/fixtures/django5/todo/__init__.py diff --git a/tests/fixtures/django5/todo/asgi.py b/tests/fixtures/django5/todo/asgi.py new file mode 100644 index 00000000..4351678e --- /dev/null +++ b/tests/fixtures/django5/todo/asgi.py @@ -0,0 +1,16 @@ +""" +ASGI config for todo project. + +It exposes the ASGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/5.0/howto/deployment/asgi/ +""" + +import os + +from django.core.asgi import get_asgi_application + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'todo.settings') + +application = get_asgi_application() diff --git a/tests/fixtures/django5/todo/settings.py b/tests/fixtures/django5/todo/settings.py new file mode 100644 index 00000000..378ee386 --- /dev/null +++ b/tests/fixtures/django5/todo/settings.py @@ -0,0 +1,129 @@ +# flake8: noqa +""" +Django settings for todo project. + +Generated by 'django-admin startproject' using Django 5.0. + +For more information on this file, see +https://docs.djangoproject.com/en/5.0/topics/settings/ + +For the full list of settings and their values, see +https://docs.djangoproject.com/en/5.0/ref/settings/ +""" + +from pathlib import Path + +# Build paths inside the project like this: BASE_DIR / 'subdir'. +BASE_DIR = Path(__file__).resolve().parent.parent + + +# Quick-start development settings - unsuitable for production +# See https://docs.djangoproject.com/en/5.0/howto/deployment/checklist/ + +# SECURITY WARNING: keep the secret key used in production secret! +SECRET_KEY = 'django-insecure-z++&i7gx^m*qqq_1(xjqo=7%gg)^&fcq6fa3gen+10(zn5756z' + +# SECURITY WARNING: don't run with debug turned on in production! +DEBUG = False + +ALLOWED_HOSTS = ['localhost', '0.0.0.0'] +BUGSNAG = { + 'api_key': 'a05afff2bd2ffaf0ab0f52715bbdcffd', + 'project_root': str(BASE_DIR), +} + +# Application definition + +INSTALLED_APPS = [ + 'notes.apps.NotesConfig', + 'django.contrib.admin', + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.messages', + 'django.contrib.staticfiles', +] + +MIDDLEWARE = [ + 'bugsnag.django.middleware.BugsnagMiddleware', + 'django.middleware.security.SecurityMiddleware', + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.middleware.common.CommonMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', + 'django.middleware.clickjacking.XFrameOptionsMiddleware', +] + +ROOT_URLCONF = 'todo.urls' + +TEMPLATES = [ + { + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + 'DIRS': [], + 'APP_DIRS': True, + 'OPTIONS': { + 'context_processors': [ + 'django.template.context_processors.debug', + 'django.template.context_processors.request', + 'django.contrib.auth.context_processors.auth', + 'django.contrib.messages.context_processors.messages', + ], + }, + }, +] + +WSGI_APPLICATION = 'todo.wsgi.application' + + +# Database +# https://docs.djangoproject.com/en/5.0/ref/settings/#databases + +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.sqlite3', + 'NAME': BASE_DIR / 'db.sqlite3', + } +} + + +# Password validation +# https://docs.djangoproject.com/en/5.0/ref/settings/#auth-password-validators + +AUTH_PASSWORD_VALIDATORS = [ + { + 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', + }, +] + + +# Internationalization +# https://docs.djangoproject.com/en/5.0/topics/i18n/ + +LANGUAGE_CODE = 'en-us' + +TIME_ZONE = 'UTC' + +USE_I18N = True + +USE_TZ = True + + +# Static files (CSS, JavaScript, Images) +# https://docs.djangoproject.com/en/5.0/howto/static-files/ + +STATIC_URL = 'static/' + +# Default primary key field type +# https://docs.djangoproject.com/en/5.0/ref/settings/#default-auto-field + +DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' diff --git a/tests/fixtures/django1/todo/urls.py b/tests/fixtures/django5/todo/urls.py similarity index 80% rename from tests/fixtures/django1/todo/urls.py rename to tests/fixtures/django5/todo/urls.py index c54db331..abc0aab4 100644 --- a/tests/fixtures/django1/todo/urls.py +++ b/tests/fixtures/django5/todo/urls.py @@ -1,8 +1,8 @@ -from django.conf.urls import include, url +from django.urls import include, path from django.http import HttpResponseNotFound urlpatterns = [ - url(r'^notes/', include('notes.urls')) + path('notes/', include('notes.urls')) ] diff --git a/tests/fixtures/django1/todo/wsgi.py b/tests/fixtures/django5/todo/wsgi.py similarity index 67% rename from tests/fixtures/django1/todo/wsgi.py rename to tests/fixtures/django5/todo/wsgi.py index 48538c40..ad14c29a 100644 --- a/tests/fixtures/django1/todo/wsgi.py +++ b/tests/fixtures/django5/todo/wsgi.py @@ -4,11 +4,13 @@ It exposes the WSGI callable as a module-level variable named ``application``. For more information on this file, see -https://docs.djangoproject.com/en/1.6/howto/deployment/wsgi/ +https://docs.djangoproject.com/en/5.0/howto/deployment/wsgi/ """ import os from django.core.wsgi import get_wsgi_application -os.environ.setdefault("DJANGO_SETTINGS_MODULE", "todo.settings") + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'todo.settings') + application = get_wsgi_application() diff --git a/tests/fixtures/django6/.gitignore b/tests/fixtures/django6/.gitignore new file mode 100644 index 00000000..60615839 --- /dev/null +++ b/tests/fixtures/django6/.gitignore @@ -0,0 +1 @@ +*.sqlite3 diff --git a/tests/fixtures/django6/manage.py b/tests/fixtures/django6/manage.py new file mode 100644 index 00000000..244b6144 --- /dev/null +++ b/tests/fixtures/django6/manage.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python +"""Django's command-line utility for administrative tasks.""" +import os +import sys + + +def main(): + """Run administrative tasks.""" + os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'todo.settings') + try: + from django.core.management import execute_from_command_line + except ImportError as exc: + raise ImportError( + "Couldn't import Django. Are you sure it's installed and " + "available on your PYTHONPATH environment variable? Did you " + "forget to activate a virtual environment?" + ) from exc + execute_from_command_line(sys.argv) + + +if __name__ == '__main__': + main() diff --git a/tests/fixtures/django6/notes/__init__.py b/tests/fixtures/django6/notes/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/fixtures/django6/notes/apps.py b/tests/fixtures/django6/notes/apps.py new file mode 100644 index 00000000..b6155aca --- /dev/null +++ b/tests/fixtures/django6/notes/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class NotesConfig(AppConfig): + name = 'notes' diff --git a/tests/fixtures/django6/notes/models.py b/tests/fixtures/django6/notes/models.py new file mode 100644 index 00000000..d884b150 --- /dev/null +++ b/tests/fixtures/django6/notes/models.py @@ -0,0 +1,9 @@ +from django.db import models + + +class Note(models.Model): + note_text = models.CharField(max_length=200) + create_date = models.DateTimeField('date created') + + def __str__(self): + return self.note_text diff --git a/tests/fixtures/django6/notes/templates/notes/broken.html b/tests/fixtures/django6/notes/templates/notes/broken.html new file mode 100644 index 00000000..d97a253b --- /dev/null +++ b/tests/fixtures/django6/notes/templates/notes/broken.html @@ -0,0 +1,3 @@ +

A broken template

+ +

{% idunno() %}

diff --git a/tests/fixtures/django6/notes/templates/notes/index.html b/tests/fixtures/django6/notes/templates/notes/index.html new file mode 100644 index 00000000..fbcbeba3 --- /dev/null +++ b/tests/fixtures/django6/notes/templates/notes/index.html @@ -0,0 +1,16 @@ +{% if latest_notes_list %} + +{% else %} +

No notes available

+{% endif %} + +
+{% csrf_token %} + + + +
diff --git a/tests/fixtures/django6/notes/templates/notes/note_detail.html b/tests/fixtures/django6/notes/templates/notes/note_detail.html new file mode 100644 index 00000000..b89113ee --- /dev/null +++ b/tests/fixtures/django6/notes/templates/notes/note_detail.html @@ -0,0 +1,3 @@ +

{{ note.id }}

+

{{ note.note_text }}

+Back to list diff --git a/tests/fixtures/django6/notes/urls.py b/tests/fixtures/django6/notes/urls.py new file mode 100644 index 00000000..cc50423e --- /dev/null +++ b/tests/fixtures/django6/notes/urls.py @@ -0,0 +1,15 @@ +from django.urls import path +from . import views + + +urlpatterns = [ + path('', views.IndexView.as_view(), name='index'), + path('/', views.DetailView.as_view(), name='detail'), + path('add/', views.add_note, name='add'), + path('unhandled-crash/', views.unhandled_crash, name='crash'), + path('unhandled-crash-chain/', views.unhandled_crash_chain), + path('unhandled-template-crash/', views.unhandled_crash_in_template), + path('handled-exception/', views.handle_notify), + path('crash-with-callback/', views.handle_crash_callback), + path('handled-exception-custom/', views.handle_notify_custom_info), +] diff --git a/tests/fixtures/django6/notes/views.py b/tests/fixtures/django6/notes/views.py new file mode 100644 index 00000000..72a49a2b --- /dev/null +++ b/tests/fixtures/django6/notes/views.py @@ -0,0 +1,72 @@ +import bugsnag + +from django.shortcuts import render +from django.http import HttpResponseRedirect, HttpResponse +from django.views import generic +from django.utils import timezone +from django.urls import reverse + +from .models import Note + + +class IndexView(generic.ListView): + template_name = 'notes/index.html' + context_object_name = 'latest_notes_list' + + def get_queryset(self): + return Note.objects.order_by('-create_date')[:5] + + +class DetailView(generic.DetailView): + model = Note + + +def add_note(request): + note_text = request.POST['note_text'] + note = Note(note_text=note_text, create_date=timezone.now()) + note.save() + return HttpResponseRedirect(reverse('detail', args=(note.id,))) + + +def unhandled_crash(request): + raise RuntimeError('failed to return in time') + + +def unhandled_crash_in_template(request): + return render(request, 'notes/broken.html') + + +def handle_notify(request): + items = {} + try: + print("item: {}" % items["nonexistent-item"]) + except KeyError as e: + bugsnag.notify(e, unhappy='nonexistent-file') + + return HttpResponse('everything is fine!', content_type='text/plain') + + +def handle_notify_custom_info(request): + bugsnag.notify(Exception('something bad happened'), severity='info', + context='custom_info') + return HttpResponse('nothing to see here', content_type='text/plain') + + +def request_inspection(event): + event.context = event.request.GET['user_id'] + + +def handle_crash_callback(request): + bugsnag.before_notify(request_inspection) + terrible_event() + + +def terrible_event(): + raise RuntimeError('I did something wrong') + + +def unhandled_crash_chain(request): + try: + unhandled_crash(request) + except RuntimeError as e: + raise Exception('corrupt timeline detected') from e diff --git a/tests/fixtures/django6/todo/__init__.py b/tests/fixtures/django6/todo/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/fixtures/django6/todo/asgi.py b/tests/fixtures/django6/todo/asgi.py new file mode 100644 index 00000000..8e4e2cc4 --- /dev/null +++ b/tests/fixtures/django6/todo/asgi.py @@ -0,0 +1,16 @@ +""" +ASGI config for todo project. + +It exposes the ASGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/6.0/howto/deployment/asgi/ +""" + +import os + +from django.core.asgi import get_asgi_application + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'todo.settings') + +application = get_asgi_application() diff --git a/tests/fixtures/django6/todo/settings.py b/tests/fixtures/django6/todo/settings.py new file mode 100644 index 00000000..c0cee231 --- /dev/null +++ b/tests/fixtures/django6/todo/settings.py @@ -0,0 +1,129 @@ +# flake8: noqa +""" +Django settings for todo project. + +Generated by 'django-admin startproject' using Django 6.0. + +For more information on this file, see +https://docs.djangoproject.com/en/6.0/topics/settings/ + +For the full list of settings and their values, see +https://docs.djangoproject.com/en/6.0/ref/settings/ +""" + +from pathlib import Path + +# Build paths inside the project like this: BASE_DIR / 'subdir'. +BASE_DIR = Path(__file__).resolve().parent.parent + + +# Quick-start development settings - unsuitable for production +# See https://docs.djangoproject.com/en/6.0/howto/deployment/checklist/ + +# SECURITY WARNING: keep the secret key used in production secret! +SECRET_KEY = 'django-insecure-z++&i7gx^m*qqq_1(xjqo=7%gg)^&fcq6fa3gen+10(zn5756z' + +# SECURITY WARNING: don't run with debug turned on in production! +DEBUG = False + +ALLOWED_HOSTS = ['localhost', '0.0.0.0'] +BUGSNAG = { + 'api_key': 'a05afff2bd2ffaf0ab0f52715bbdcffd', + 'project_root': str(BASE_DIR), +} + +# Application definition + +INSTALLED_APPS = [ + 'notes.apps.NotesConfig', + 'django.contrib.admin', + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.messages', + 'django.contrib.staticfiles', +] + +MIDDLEWARE = [ + 'bugsnag.django.middleware.BugsnagMiddleware', + 'django.middleware.security.SecurityMiddleware', + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.middleware.common.CommonMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', + 'django.middleware.clickjacking.XFrameOptionsMiddleware', +] + +ROOT_URLCONF = 'todo.urls' + +TEMPLATES = [ + { + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + 'DIRS': [], + 'APP_DIRS': True, + 'OPTIONS': { + 'context_processors': [ + 'django.template.context_processors.debug', + 'django.template.context_processors.request', + 'django.contrib.auth.context_processors.auth', + 'django.contrib.messages.context_processors.messages', + ], + }, + }, +] + +WSGI_APPLICATION = 'todo.wsgi.application' + + +# Database +# https://docs.djangoproject.com/en/6.0/ref/settings/#databases + +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.sqlite3', + 'NAME': BASE_DIR / 'db.sqlite3', + } +} + + +# Password validation +# https://docs.djangoproject.com/en/6.0/ref/settings/#auth-password-validators + +AUTH_PASSWORD_VALIDATORS = [ + { + 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', + }, +] + + +# Internationalization +# https://docs.djangoproject.com/en/6.0/topics/i18n/ + +LANGUAGE_CODE = 'en-us' + +TIME_ZONE = 'UTC' + +USE_I18N = True + +USE_TZ = True + + +# Static files (CSS, JavaScript, Images) +# https://docs.djangoproject.com/en/6.0/howto/static-files/ + +STATIC_URL = 'static/' + +# Default primary key field type +# https://docs.djangoproject.com/en/6.0/ref/settings/#default-auto-field + +DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' diff --git a/tests/fixtures/django6/todo/urls.py b/tests/fixtures/django6/todo/urls.py new file mode 100644 index 00000000..abc0aab4 --- /dev/null +++ b/tests/fixtures/django6/todo/urls.py @@ -0,0 +1,16 @@ +from django.urls import include, path +from django.http import HttpResponseNotFound + +urlpatterns = [ + path('notes/', include('notes.urls')) +] + + +def handler404(request, *args, **kwargs): + if 'poorly-handled-404' in request.path: + raise Exception('nah') + + response = HttpResponseNotFound('Terrible happenings!', + content_type='text/plain') + response.status_code = 404 + return response diff --git a/tests/fixtures/django6/todo/wsgi.py b/tests/fixtures/django6/todo/wsgi.py new file mode 100644 index 00000000..2d71f073 --- /dev/null +++ b/tests/fixtures/django6/todo/wsgi.py @@ -0,0 +1,16 @@ +""" +WSGI config for todo project. + +It exposes the WSGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/6.0/howto/deployment/wsgi/ +""" + +import os + +from django.core.wsgi import get_wsgi_application + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'todo.settings') + +application = get_wsgi_application() diff --git a/tox.ini b/tox.ini index 08061af7..20ec3042 100644 --- a/tox.ini +++ b/tox.ini @@ -2,11 +2,10 @@ envlist= py{35,36,37,38,39,310,311,312,313,314}-{test,requests,flask,tornado,wsgi,bottle} py{36,37,38,39,310,311,312,313,314}-asgi - py{35,36,37}-django{18,19,110,111} - py{35,36,37,38,39}-django20 - py{35,36,37,38,39,310}-django{21,22} py{36,37,38,39,310,311,312,313}-django3 py{38,39,310,311,312,313}-django4 + py{310,311,312,313,314}-django5 + py{312,313,314}-django6 py{38,39,310,311,312,313,314}-{asynctest,threadtest} py{37,38,39,310,311,312,313,314}-exceptiongroup py{35,313,314}-{lint} @@ -19,10 +18,11 @@ addopts = --cov=bugsnag --cov-report html --cov-append --cov-report term setenv = PYTHONPATH = {toxinidir} COVERAGE_FILE=.coverage.{envname} - django{18,19,110,111,20,21,22}: PYTHONPATH=tests/fixtures/django1{:}{toxinidir} django3: PYTHONPATH=tests/fixtures/django30{:}{toxinidir} django4: PYTHONPATH=tests/fixtures/django4{:}{toxinidir} - django{18,19,110,111,20,21,22,3,4}: DJANGO_SETTINGS_MODULE=todo.settings + django5: PYTHONPATH=tests/fixtures/django5{:}{toxinidir} + django6: PYTHONPATH=tests/fixtures/django6{:}{toxinidir} + django{3,4,5,6}: DJANGO_SETTINGS_MODULE=todo.settings basepython = py35: python3.5 py36: python3.6 @@ -51,18 +51,11 @@ deps= flask: blinker tornado: tornado tornado: pytest<8.2 - django18: Django>=1.8,<1.9 - django19: Django>=1.9,<1.10 - django110: Django>=1.10,<1.11 - django111: Django>=1.11,<2.0 - django20: Django>=2.0,<2.1 - django21: Django>=2.1,<2.2 - django22: Django>=2.2,<3.0 django3: Django>=3.0,<4.0 django4: Django>=4.0,<5.0 - django{18,19,110,111,20,21}: six - django{18,19,110,111,20,21}: pytest-django<4.0 - django{22,3,4}: pytest-django + django5: Django>=5.0,<6.0 + django6: Django>=6.0,<7.0 + django{3,4,5,6}: pytest-django exceptiongroup: exceptiongroup lint: flake8 lint: mypy @@ -82,7 +75,7 @@ commands = wsgi: pytest tests/integrations/test_wsgi.py asgi: pytest tests/integrations/test_asgi.py flask: pytest tests/integrations/test_flask.py - django{18,19,110,111,20,21,22,3,4}: pytest tests/integrations/test_django.py + django{3,4,5,6}: pytest tests/integrations/test_django.py tornado: pytest tests/integrations/test_tornado.py exceptiongroup: pytest tests/test_exception_groups.py lint: flake8 bugsnag tests example --exclude 'venv*' From a9aaf879b9827ca69b7182f4fa8a2ea8963f46f9 Mon Sep 17 00:00:00 2001 From: Steve Kirkland Date: Thu, 22 Jan 2026 17:05:08 +0000 Subject: [PATCH 4/5] Release v4.8.1 --- CHANGELOG.md | 13 +++++++++---- setup.py | 2 +- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 330bdc27..a3a08bc9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,16 +1,21 @@ Changelog ========= -## v4.8.0 (2024-07-08) +## v4.8.1 (2026-01-22) + +### Changes + +* Amend secondary instance URL to bugsnag.smartbear.com + [#400](https://github.com/bugsnag/bugsnag-python/pull/400) + +## v4.8.0 (2025-07-08) ### Enhancements * Remove deprecated `datetime.utcnow()` method call from utils class [#394](https://github.com/bugsnag/bugsnag-python/pull/394). * Set default endpoints based on API key - [#399](https://github.com/bugsnag/bugsnag-php/pull/399) -* Amend secondary instance URL to bugsnag.smartbear.com - [#400](https://github.com/bugsnag/bugsnag-php/pull/399) + [#399](https://github.com/bugsnag/bugsnag-python/pull/399) ## v4.7.1 (2024-05-22) diff --git a/setup.py b/setup.py index 7c0b5e2e..223c70f6 100755 --- a/setup.py +++ b/setup.py @@ -14,7 +14,7 @@ setup( name='bugsnag', - version='4.8.0', + version='4.8.1', description='Automatic error monitoring for django, flask, etc.', long_description=__doc__, author='Simon Maynard', From 695e5664f7e4230245c46892981c98d6e435c2a5 Mon Sep 17 00:00:00 2001 From: Steve Kirkland Date: Fri, 23 Jan 2026 11:16:20 +0000 Subject: [PATCH 5/5] Bump date --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a3a08bc9..d90b47a7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,7 @@ Changelog ========= -## v4.8.1 (2026-01-22) +## v4.8.1 (2026-01-23) ### Changes