Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package python-celery for openSUSE:Factory checked in at 2026-08-21 16:53:17 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-celery (Old) and /work/SRC/openSUSE:Factory/.python-celery.new.1258 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-celery" Fri Aug 21 16:53:17 2026 rev:57 rq:1372557 version:5.6.3 Changes: -------- --- /work/SRC/openSUSE:Factory/python-celery/python-celery.changes 2026-06-15 19:54:02.782093895 +0200 +++ /work/SRC/openSUSE:Factory/.python-celery.new.1258/python-celery.changes 2026-08-21 16:54:28.024390774 +0200 @@ -1,0 +2,5 @@ +Wed Aug 19 14:44:30 UTC 2026 - Nico Krapp <[email protected]> + +- Add tests.patch to fix testsuite (boo#1275668) + +------------------------------------------------------------------- New: ---- tests.patch ----------(New B)---------- New: - Add tests.patch to fix testsuite (boo#1275668) ----------(New E)---------- ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-celery.spec ++++++ --- /var/tmp/diff_new_pack.oJGWkJ/_old 2026-08-21 16:54:29.299436049 +0200 +++ /var/tmp/diff_new_pack.oJGWkJ/_new 2026-08-21 16:54:29.305436262 +0200 @@ -35,6 +35,8 @@ URL: https://celeryproject.org Source: https://files.pythonhosted.org/packages/source/c/celery/celery-%{version}.tar.gz Patch0: move-pytest-configuration-to-conftest.patch +# PATCH-FIX-UPSTREAM tests.patch boo#1275668 gh#celery/celery@bf1cf69 +Patch1: tests.patch BuildRequires: %{python_module base >= 3.9} BuildRequires: %{python_module pip} BuildRequires: %{python_module setuptools} ++++++ tests.patch ++++++ >From bf1cf69e24e3ede7d7053c17d47e381be959880e Mon Sep 17 00:00:00 2001 From: Puneet Dixit <[email protected]> Date: Mon, 25 May 2026 11:01:20 +0530 Subject: [PATCH] Skip empty groups in chains (#10321) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Skip empty groups in chains * Tolerate Click option error wording * Potential fix for pull request finding * Handle generator-backed empty groups * Skip empty groups during chain preparation * Skip known-empty generator groups in chains * Fix chord freeze for empty group bodies Treat an empty group chord body as present so freeze() returns the body's GroupResult instead of None. This keeps callers that traverse chord body results from seeing a missing result when the body exists but is empty. Assisted-by: OpenAI GPT-5 <[email protected]> * Clarify prepared task variable in chain test --------- Co-authored-by: Asif Saif Uddin {"Auvi":"অভি"} <[email protected]> --- celery/canvas.py | 16 ++++++++- t/unit/app/test_preload_cli.py | 3 +- t/unit/tasks/test_canvas.py | 64 ++++++++++++++++++++++++++++++++++ 3 files changed, 81 insertions(+), 2 deletions(-) Index: celery-5.6.3/celery/canvas.py =================================================================== --- celery-5.6.3.orig/celery/canvas.py +++ celery-5.6.3/celery/canvas.py @@ -1187,6 +1187,13 @@ class _chain(Signature): # when groups are nested, they are unrolled - all tasks within # groups should be called in parallel task = maybe_unroll_group(task) + if ( + isinstance(task, group) and + isinstance(task.tasks, (list, tuple)) and + not task.tasks and + (steps or prev_task) + ): + continue # first task gets partial args from chain if clone: @@ -1582,6 +1589,13 @@ class group(Signature): return self.apply_async(partial_args, **options) def __or__(self, other): + if ( + isinstance(other, group) and + not isinstance(other.tasks, _regen) and + isinstance(other.tasks, (list, tuple)) and + not other.tasks + ): + return self # group() | task -> chord return chord(self, body=other, app=self._app) @@ -2083,7 +2097,7 @@ class _chord(Signature): # secondly freeze all tasks in the body: those that should be called after the header body_result = None - if self.body: + if self.body is not None: body_result = self.body.freeze( _id, root_id=root_id, chord=chord, group_id=group_id, group_index=group_index) Index: celery-5.6.3/t/unit/app/test_preload_cli.py =================================================================== --- celery-5.6.3.orig/t/unit/app/test_preload_cli.py +++ celery-5.6.3/t/unit/app/test_preload_cli.py @@ -38,7 +38,8 @@ def test_preload_options(subcommand_with catch_exceptions=False, ) - assert "No such option: --ini" in res_without_preload.output + assert "No such option" in res_without_preload.output + assert "--ini" in res_without_preload.output assert res_without_preload.exit_code == 2 res_with_preload = isolated_cli_runner.invoke( Index: celery-5.6.3/t/unit/tasks/test_canvas.py =================================================================== --- celery-5.6.3.orig/t/unit/tasks/test_canvas.py +++ celery-5.6.3/t/unit/tasks/test_canvas.py @@ -467,6 +467,53 @@ class test_chain(CanvasCase): c = g1 | g2 assert isinstance(c, chord) + def test_empty_groups_are_skipped_in_chain(self): + c = chain( + group([self.add.s(2, 2), self.add.s(4, 4)], app=self.app), + group(app=self.app), + group(app=self.app), + ) + + assert isinstance(c, group) + result = c.apply_async() + assert isinstance(result, GroupResult) + assert len(result.results) == 2 + + def test_empty_group_body_is_skipped_when_chain_upgrades_to_chord(self): + c = self.add.s(1, 1) | group( + [self.add.s(2, 2), self.add.s(3, 3)], + app=self.app, + ) | group(app=self.app) + + result = c.apply_async() + + assert isinstance(result, GroupResult) + assert len(result.results) == 2 + + def test_generator_backed_empty_group_is_not_skipped_when_chained(self): + def tasks(): + yield from () + + c = group([self.add.s(2, 2)], app=self.app) | group(tasks(), app=self.app) + + assert isinstance(c, chord) + + def test_known_empty_generator_backed_group_is_skipped_in_chain(self): + def tasks(): + yield from () + + c = _chain( + group([self.add.s(2, 2)], app=self.app), + group(tasks(), app=self.app), + app=self.app, + ) + + prepared_tasks, results = c.prepare_steps((), {}, c.tasks) + + assert len(prepared_tasks) == 1 + assert prepared_tasks[0].task == self.add.name + assert isinstance(results[0], AsyncResult) + def test_prepare_steps_set_last_task_id_to_chain(self): last_task = self.add.s(2).set(task_id='42') c = self.add.s(4) | last_task @@ -1335,6 +1382,23 @@ class test_chord(CanvasCase): x = chord([], self.add.s(4, 4)) assert x.app is self.add.app + def test_freeze_empty_group_body_returns_result(self): + """An empty group body still exists and should be frozen. + + This is a defensive check for chains that may upgrade a group into a + chord whose body is an empty group. + """ + x = chord( + group(self.add.s(2, 2), app=self.app), + group(app=self.app), + app=self.app, + ) + + result = x.freeze() + + assert isinstance(result, GroupResult) + assert result.parent is not None + @pytest.mark.usefixtures('depends_on_current_app') def test_app_fallback_to_current(self): from celery._state import current_app
