Skip to content

Commit da99b88

Browse files
authored
Merge pull request #28 from taskbadger/sk/celery-upgrade
fix celery serialization bug
2 parents 27a3e88 + 3bc2659 commit da99b88

8 files changed

Lines changed: 776 additions & 385 deletions

File tree

.github/workflows/tests.yml

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,27 @@ on:
88
jobs:
99
python-tests:
1010
runs-on: ubuntu-latest
11+
services:
12+
redis:
13+
image: redis
14+
options: >-
15+
--health-cmd "redis-cli ping"
16+
--health-interval 10s
17+
--health-timeout 5s
18+
--health-retries 5
19+
ports:
20+
- 6379:6379
1121
strategy:
1222
max-parallel: 4
1323
matrix:
1424
python-version:
1525
- "3.9"
1626
- "3.10"
27+
- "3.11"
28+
- "3.12"
29+
celery-version:
30+
- ">=5.3,<5.4"
31+
- ">=5.4"
1732
steps:
1833
- uses: actions/checkout@v3
1934
- name: Set up Python ${{ matrix.python-version }}
@@ -31,6 +46,8 @@ jobs:
3146
poetry --version
3247
poetry check --no-interaction
3348
- name: Install project
34-
run: poetry install --no-interaction
49+
run: |
50+
poetry install --no-interaction
51+
pip install celery"${{ matrix.celery-version }}"
3552
- name: Run tests
3653
run: poetry run pytest -v

poetry.lock

Lines changed: 686 additions & 372 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ packages = [
1313
include = ["CHANGELOG.md", "taskbadger/internal/py.typed"]
1414

1515
homepage = "https://taskbadger.net/"
16-
repository = "https://github.com/taskbadger/taskbadger-docs"
16+
repository = "https://github.com/taskbadger/taskbadger-python"
1717
documentation = "https://docs.taskbadger.net/"
1818
classifiers = [
1919
"Development Status :: 4 - Beta",
@@ -30,13 +30,16 @@ classifiers = [
3030
"Topic :: Software Development :: Libraries :: Python Modules",
3131
]
3232

33+
[tool.poetry.urls]
34+
"Changelog" = "https://github.com/taskbadger/taskbadger-python/releases"
35+
3336
[tool.poetry.dependencies]
3437
python = "^3.8"
35-
httpx = ">=0.20.0,<0.25.0"
38+
httpx = ">=0.20.0,<0.28.0"
3639
attrs = ">=21.3.0"
3740
python-dateutil = "^2.8.0"
38-
typer = {extras = ["all"], version = "^0.9.0"}
39-
tomlkit = "^0.11.6"
41+
typer = {extras = ["all"], version = "<0.10.0"}
42+
tomlkit = "^0.12.5"
4043
importlib-metadata = {version = "^1.0", python = "<3.8"}
4144
typing-extensions = {version = "^4.7.1", python = "3.9"}
4245
celery = {version = ">=4.0.0,<6.0.0", optional = true}
@@ -52,7 +55,8 @@ black = "^23.1.0"
5255
pre-commit = "^3.0.2"
5356
pytest-httpx = "^0.21.3"
5457
invoke = "^2.0.0"
55-
pytest-celery = "^0.0.0"
58+
pytest-celery = ">0.0.0"
59+
redis = "^5.0.4"
5660

5761
[tool.poetry.scripts]
5862
taskbadger = "taskbadger.cli_main:app"

taskbadger/celery.py

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -103,11 +103,13 @@ def scrape_urls(self, urls):
103103
def apply_async(self, *args, **kwargs):
104104
headers = kwargs.setdefault("headers", {})
105105
headers["taskbadger_track"] = True
106-
tb_kwargs = kwargs.pop(TB_KWARGS_ARG, {})
107-
for name in list(kwargs):
108-
if name.startswith(KWARG_PREFIX):
109-
val = kwargs.pop(name)
110-
tb_kwargs[name.removeprefix(KWARG_PREFIX)] = val
106+
tb_kwargs = self._get_tb_kwargs(kwargs)
107+
if kwargs.get("kwargs"):
108+
# extract taskbadger options from task kwargs when supplied as keyword argument
109+
tb_kwargs.update(self._get_tb_kwargs(kwargs["kwargs"]))
110+
elif len(args) > 1 and isinstance(args[1], dict):
111+
# extract taskbadger options from task kwargs when supplied as positional argument
112+
tb_kwargs.update(self._get_tb_kwargs(args[1]))
111113
headers[TB_KWARGS_ARG] = tb_kwargs
112114
result = super().apply_async(*args, **kwargs)
113115

@@ -119,6 +121,14 @@ def apply_async(self, *args, **kwargs):
119121

120122
return result
121123

124+
def _get_tb_kwargs(self, kwargs):
125+
tb_kwargs = kwargs.pop(TB_KWARGS_ARG, {})
126+
for name in list(kwargs):
127+
if name.startswith(KWARG_PREFIX):
128+
val = kwargs.pop(name)
129+
tb_kwargs[name.removeprefix(KWARG_PREFIX)] = val
130+
return tb_kwargs
131+
122132
@property
123133
def taskbadger_task_id(self):
124134
return _get_taskbadger_task_id(self.request)
@@ -146,6 +156,7 @@ def task_publish_handler(sender=None, headers=None, body=None, **kwargs):
146156
celery_system = Badger.current.settings.get_system_by_id("celery")
147157
auto_track = celery_system and celery_system.track_task(sender)
148158
manual_track = headers.get("taskbadger_track")
159+
header_kwargs = headers.pop(TB_KWARGS_ARG, {})
149160
if not manual_track and not auto_track:
150161
return
151162

@@ -158,7 +169,7 @@ def task_publish_handler(sender=None, headers=None, body=None, **kwargs):
158169
kwargs[attr.removeprefix(KWARG_PREFIX)] = getattr(ctask, attr)
159170

160171
# get kwargs from the task headers (set via apply_async)
161-
kwargs.update(headers.get(TB_KWARGS_ARG, {}))
172+
kwargs.update(header_kwargs)
162173
kwargs["status"] = StatusEnum.PENDING
163174
name = kwargs.pop("name", headers["task"])
164175

taskbadger/sdk.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import logging
12
import os
23
from typing import Any, List
34

@@ -15,6 +16,8 @@
1516
from taskbadger.mug import Badger, Session, Settings
1617
from taskbadger.systems import System
1718

19+
log = logging.getLogger("taskbadger")
20+
1821
_TB_HOST = "https://taskbadger.net"
1922

2023

@@ -338,6 +341,12 @@ def data(self):
338341
def __getattr__(self, item):
339342
return getattr(self._task, item)
340343

344+
def safe_update(self, **kwargs):
345+
try:
346+
self.update(**kwargs)
347+
except Exception as e:
348+
log.exception("Error updating task '%s'", self._task.id)
349+
341350

342351
def _none_to_unset(value):
343352
return UNSET if value is None else value

tests/conftest.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,12 @@ def bind_settings():
88
Badger.current.bind(Settings("https://taskbadger.net", "token", "org", "proj"))
99
yield
1010
Badger.current.bind(None)
11+
12+
13+
@pytest.fixture(scope="session", autouse=True)
14+
def celery_config():
15+
"""Test against Redis to ensure serialization works"""
16+
return {
17+
"broker_url": "redis://localhost:6379",
18+
"result_backend": "redis://localhost:6379",
19+
}

tests/test_celery.py

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
import celery
1414
import pytest
1515

16-
from taskbadger import StatusEnum
16+
from taskbadger import Action, EmailIntegration, StatusEnum
1717
from taskbadger.celery import Task
1818
from taskbadger.mug import Badger
1919
from tests.utils import task_for_test
@@ -75,6 +75,32 @@ def add_with_task_args(self, a, b):
7575
create.assert_called_once_with("new_name", value_max=10, data={"foo": "bar"}, status=StatusEnum.PENDING)
7676

7777

78+
def test_celery_task_with_kwargs(celery_session_app, celery_session_worker, bind_settings):
79+
@celery_session_app.task(bind=True, base=Task)
80+
def add_with_task_args(self, a, b):
81+
assert self.taskbadger_task is not None
82+
return a + b
83+
84+
celery_session_worker.reload()
85+
86+
with mock.patch("taskbadger.celery.create_task_safe") as create, mock.patch(
87+
"taskbadger.celery.update_task_safe"
88+
) as update, mock.patch("taskbadger.celery.get_task") as get_task:
89+
create.return_value = task_for_test()
90+
91+
actions = [Action("stale", integration=EmailIntegration(to="test@test.com"))]
92+
result = add_with_task_args.delay(
93+
2,
94+
2,
95+
taskbadger_name="new_name",
96+
taskbadger_value_max=10,
97+
taskbadger_kwargs={"actions": actions},
98+
)
99+
assert result.get(timeout=10, propagate=True) == 4
100+
101+
create.assert_called_once_with("new_name", value_max=10, actions=actions, status=StatusEnum.PENDING)
102+
103+
78104
def test_celery_task_with_args_in_decorator(celery_session_app, celery_session_worker, bind_settings):
79105
@celery_session_app.task(bind=True, base=Task, taskbadger_value_max=10, taskbadger_kwargs={"monitor_id": "123"})
80106
def add_with_task_args_in_decorator(self, a, b):

tests/test_celery_error.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ def add_error(self, a, b):
2121
"taskbadger.celery.update_task_safe"
2222
) as update, mock.patch("taskbadger.celery.get_task") as get_task:
2323
task = task_for_test()
24+
create.return_value = task
2425
get_task.return_value = task
2526
update.return_value = task
2627
result = add_error.delay(2, 2)

0 commit comments

Comments
 (0)