Skip to content

Commit

Permalink
Release v4.7.1
Browse files Browse the repository at this point in the history
  • Loading branch information
imjoehaines committed May 22, 2024
2 parents 2d4e88e + 8ad3c40 commit 10f7e8d
Show file tree
Hide file tree
Showing 20 changed files with 332 additions and 250 deletions.
14 changes: 8 additions & 6 deletions .github/workflows/license-audit.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,16 @@ on: [push, pull_request]

jobs:
license-audit:
# TODO: a GH action update broke the 'ubuntu-latest' image
# when it's fixed, we should switch back
runs-on: ubuntu-20.04
runs-on: 'ubuntu-latest'

steps:
- uses: actions/checkout@v2

- name: Set up Python
uses: actions/setup-python@v2
with:
# License Finder's Docker image uses Python 3.5
python-version: 3.5
# License Finder's Docker image uses Python 3.10
python-version: '3.10'

- name: Fetch decisions.yml
run: curl https://raw.githubusercontent.com/bugsnag/license-audit/master/config/decision_files/global.yml -o decisions.yml
Expand All @@ -32,6 +30,10 @@ jobs:
run: >
docker run -v $PWD:/scan licensefinder/license_finder /bin/bash -lc "
cd /scan &&
pip3 install -r requirements.txt --quiet &&
apt-get update &&
apt-get install -y python3-venv &&
python3 -m venv .venv &&
source .venv/bin/activate &&
pip3 install -r requirements.txt &&
license_finder --decisions-file decisions.yml --python-version 3 --enabled-package-managers=pip
"
7 changes: 5 additions & 2 deletions .github/workflows/python-package.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,19 @@ jobs:
include:
- python-version: '3.5'
os: 'ubuntu-20.04'
pip-trusted-host: 'pypi.python.org pypi.org files.pythonhosted.org'
- python-version: '3.6'
os: 'ubuntu-20.04'

steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4

- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
env:
PIP_TRUSTED_HOST: ${{ matrix.pip-trusted-host }}

- name: Install dependencies
run: |
Expand Down
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
Changelog
=========

## v4.7.1 (2024-05-22)

### Bug fixes

* Avoid reading `__code__` when setting a custom delivery unless it exists
[#387](https://github.com/bugsnag/bugsnag-python/pull/387)

## v4.7.0 (2024-04-24)

### Enhancements
Expand Down
6 changes: 5 additions & 1 deletion bugsnag/configuration.py
Original file line number Diff line number Diff line change
Expand Up @@ -279,7 +279,11 @@ def delivery(self, value):
# this should be made mandatory in the next major release
if (
hasattr(value, 'deliver_sessions') and
callable(value.deliver_sessions)
callable(value.deliver_sessions) and
# Mock objects don't allow accessing or mocking '__code__' so
# ensure it exists before attempting to read it
# __code__ should always be present in a real delivery object
hasattr(value.deliver_sessions, '__code__')
):
parameter_names = value.deliver_sessions.__code__.co_varnames

Expand Down
21 changes: 14 additions & 7 deletions bugsnag/delivery.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,6 @@ def deliver_sessions(self, config, payload: Any, options=None):
options = {}

options['endpoint'] = config.session_endpoint
options['success'] = 202

self.deliver(config, payload, options)

Expand Down Expand Up @@ -151,10 +150,14 @@ def request():
status = resp.getcode()

if 'success' in options:
success = options['success']
# if an expected status code has been given then it must match
# exactly with the actual status code
success = status == options['success']
else:
success = 200
if status != success:
# warn if we don't get a 2xx status code by default
success = status >= 200 and status < 300

if not success:
config.logger.warning(
'Delivery to %s failed, status %d' % (uri, status)
)
Expand Down Expand Up @@ -184,12 +187,16 @@ def request():

response = requests.post(uri, **req_options)
status = response.status_code

if 'success' in options:
success = options['success']
# if an expected status code has been given then it must match
# exactly with the actual status code
success = status == options['success']
else:
success = requests.codes.ok
# warn if we don't get a 2xx status code by default
success = status >= 200 and status < 300

if status != success:
if not success:
config.logger.warning(
'Delivery to %s failed, status %d' % (uri, status)
)
Expand Down
2 changes: 1 addition & 1 deletion bugsnag/notifier.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
_NOTIFIER_INFORMATION = {
'name': 'Python Bugsnag Notifier',
'url': 'https://github.com/bugsnag/bugsnag-python',
'version': '4.7.0'
'version': '4.7.1'
}
5 changes: 4 additions & 1 deletion bugsnag/sessiontracker.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,10 @@ def __deliver(self, sessions: List[Dict], asynchronous=True):

deliver = self.config.delivery.deliver_sessions

if 'options' in deliver.__code__.co_varnames:
if (
hasattr(deliver, '__code__') and
'options' in deliver.__code__.co_varnames
):
try:
post_delivery_callback = self._request_tracker.new_request()

Expand Down
142 changes: 142 additions & 0 deletions features/celery.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
Feature: Celery

Scenario Outline: Handled exceptions are delivered in Celery <celery-version>
Given I start the service "celery-<celery-version>"
When I execute the command "python bugsnag_celery_test_app/queue_task.py handled" in the service "celery-<celery-version>"
And I wait to receive an error
Then the error is valid for the error reporting API version "4.0" for the "Python Bugsnag Notifier" notifier
And the exception "errorClass" equals "Exception"
And the exception "message" equals "oooh nooo"
And the event "unhandled" is false
And the event "severity" equals "warning"
And the event "severityReason.type" equals "handledException"
And the event "device.runtimeVersions.celery" matches "<celery-version>\.\d+\.\d+"

@not-python-3.11 @not-python-3.12
Examples:
| celery-version |
| 4 |

@not-python-3.5
Examples:
| celery-version |
| 5 |

Scenario Outline: Unhandled exceptions are delivered in Celery <celery-version>
Given I start the service "celery-<celery-version>"
When I execute the command "python bugsnag_celery_test_app/queue_task.py unhandled" in the service "celery-<celery-version>"
And I wait to receive an error
Then the error is valid for the error reporting API version "4.0" for the "Python Bugsnag Notifier" notifier
And the exception "errorClass" equals "KeyError"
And the exception "message" equals "'b'"
And the event "unhandled" is true
And the event "severity" equals "error"
And the event "severityReason.type" equals "unhandledExceptionMiddleware"
And the event "severityReason.attributes.framework" equals "Celery"
And the event "device.runtimeVersions.celery" matches "<celery-version>\.\d+\.\d+"
And the event "context" equals "bugsnag_celery_test_app.tasks.unhandled"
And the event "metaData.extra_data.task_id" is not null
# these aren't strings but the maze runner step works on arrays and hashes
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
Examples:
| celery-version |
| 4 |

@not-python-3.5
Examples:
| celery-version |
| 5 |

Scenario Outline: Task arguments are added to metadata in Celery <celery-version>
Given I start the service "celery-<celery-version>"
When I execute the command "python bugsnag_celery_test_app/queue_task.py add 1 2 3 '4' a=100 b=200" in the service "celery-<celery-version>"
And I wait to receive an error
Then the error is valid for the error reporting API version "4.0" for the "Python Bugsnag Notifier" notifier
And the exception "errorClass" equals "AssertionError"
And the exception "message" equals ""
And the event "unhandled" is true
And the event "severity" equals "error"
And the event "severityReason.type" equals "unhandledExceptionMiddleware"
And the event "severityReason.attributes.framework" equals "Celery"
And the event "device.runtimeVersions.celery" matches "<celery-version>\.\d+\.\d+"
And the event "context" equals "bugsnag_celery_test_app.tasks.add"
And the event "metaData.extra_data.task_id" is not null
And the error payload field "events.0.metaData.extra_data.args" is an array with 4 elements
And the event "metaData.extra_data.args.0" equals "1"
And the event "metaData.extra_data.args.1" equals "2"
And the event "metaData.extra_data.args.2" equals "3"
And the event "metaData.extra_data.args.3" equals "4"
And the event "metaData.extra_data.kwargs.a" equals "100"
And the event "metaData.extra_data.kwargs.b" equals "200"

@not-python-3.11 @not-python-3.12
Examples:
| celery-version |
| 4 |

@not-python-3.5
Examples:
| celery-version |
| 5 |

Scenario Outline: Errors in shared tasks are reported in Celery <celery-version>
Given I start the service "celery-<celery-version>"
When I execute the command "python bugsnag_celery_test_app/queue_task.py divide 10 0" in the service "celery-<celery-version>"
And I wait to receive an error
Then the error is valid for the error reporting API version "4.0" for the "Python Bugsnag Notifier" notifier
And the exception "errorClass" equals "ZeroDivisionError"
And the exception "message" equals "division by zero"
And the event "unhandled" is true
And the event "severity" equals "error"
And the event "severityReason.type" equals "unhandledExceptionMiddleware"
And the event "severityReason.attributes.framework" equals "Celery"
And the event "device.runtimeVersions.celery" matches "<celery-version>\.\d+\.\d+"
And the event "context" equals "bugsnag_celery_test_app.tasks.divide"
And the event "metaData.extra_data.task_id" is not null
And the error payload field "events.0.metaData.extra_data.args" is an array with 2 elements
And the event "metaData.extra_data.args.0" equals "10"
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
Examples:
| celery-version |
| 4 |

@not-python-3.5
Examples:
| celery-version |
| 5 |

Scenario Outline: Successful tasks do not report errors in Celery <celery-version>
Given I start the service "celery-<celery-version>"
When I execute the command "python bugsnag_celery_test_app/queue_task.py add 1 2 3 4 5 6 7 a=8 b=9" in the service "celery-<celery-version>"
Then I should receive no errors

@not-python-3.11 @not-python-3.12
Examples:
| celery-version |
| 4 |

@not-python-3.5
Examples:
| celery-version |
| 5 |

Scenario Outline: Successful shared tasks do not report errors in Celery <celery-version>
Given I start the service "celery-<celery-version>"
When I execute the command "python bugsnag_celery_test_app/queue_task.py divide 10 2" in the service "celery-<celery-version>"
Then I should receive no errors

@not-python-3.11 @not-python-3.12
Examples:
| celery-version |
| 4 |

@not-python-3.5
Examples:
| celery-version |
| 5 |
12 changes: 12 additions & 0 deletions features/fixtures/celery/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
ARG PYTHON_TEST_VERSION
FROM python:$PYTHON_TEST_VERSION

COPY app/ /usr/src/app
COPY temp-bugsnag-python/ /usr/src/bugsnag

WORKDIR /usr/src/app

ARG CELERY_TEST_VERSION
RUN CELERY_TEST_VERSION=$CELERY_TEST_VERSION pip install --no-cache-dir -r requirements.txt

CMD celery --app bugsnag_celery_test_app.main worker -l INFO
Empty file.
24 changes: 24 additions & 0 deletions features/fixtures/celery/app/bugsnag_celery_test_app/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import os
import bugsnag
from celery import Celery
from bugsnag.celery import connect_failure_handler


bugsnag.configure(
api_key=os.environ["BUGSNAG_API_KEY"],
endpoint=os.environ["BUGSNAG_ERROR_ENDPOINT"],
session_endpoint=os.environ["BUGSNAG_SESSION_ENDPOINT"],
)

app = Celery(
'bugsnag_celery_test_app',
broker='redis://redis:6379',
backend='rpc://',
include=['bugsnag_celery_test_app.tasks'],
)

connect_failure_handler()


if __name__ == '__main__':
app.start()
23 changes: 23 additions & 0 deletions features/fixtures/celery/app/bugsnag_celery_test_app/queue_task.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import sys
import json
import bugsnag_celery_test_app.tasks as tasks


if __name__ == '__main__':
task = sys.argv[1]
arguments = []
keyword_arguments = {}

if len(sys.argv) > 2:
raw_arguments = sys.argv[2:]

for argument in raw_arguments:
if '=' in argument:
key, value = argument.split('=')
keyword_arguments[key] = value
else:
arguments.append(argument)

print("~*~ Queueing task '%s' with args: [%s] and kwargs: %s" % (task, ", ".join(arguments), json.dumps(keyword_arguments)))

getattr(tasks, task).delay(*arguments, **keyword_arguments)
34 changes: 34 additions & 0 deletions features/fixtures/celery/app/bugsnag_celery_test_app/tasks.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import bugsnag
from celery import shared_task
from bugsnag_celery_test_app.main import app


@app.task
def handled():
bugsnag.notify(Exception('oooh nooo'))

return 'hello world'


@app.task
def unhandled():
a = {}

return a['b']


@app.task
def add(*args, a, b):
total = int(a) + int(b)

for arg in args:
total += int(arg)

assert total < 100

return total


@shared_task
def divide(a, b):
return int(a) / int(b)
4 changes: 4 additions & 0 deletions features/fixtures/celery/app/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
.
../bugsnag
celery[redis]${CELERY_TEST_VERSION}
importlib-metadata<5.0
3 changes: 3 additions & 0 deletions features/fixtures/celery/app/setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from setuptools import setup

setup(name="bugsnag_celery_test_app", packages=["bugsnag_celery_test_app"])
Loading

0 comments on commit 10f7e8d

Please sign in to comment.