Skip to content

Honor the delivery_mode argument in Exchange.Message - #2570

Merged
auvipy merged 1 commit into
celery:mainfrom
uttam12331:fix-exchange-message-delivery-mode
Jul 25, 2026
Merged

Honor the delivery_mode argument in Exchange.Message#2570
auvipy merged 1 commit into
celery:mainfrom
uttam12331:fix-exchange-message-delivery-mode

Conversation

@uttam12331

Copy link
Copy Markdown
Contributor

Exchange.Message accepts a delivery_mode argument and documents it as:

delivery_mode (bool): Set custom delivery mode. Defaults to :attr:delivery_mode.

but the method body never uses it — it always calls maybe_delivery_mode(self.delivery_mode), so the caller's value is silently discarded:

>>> ex = Exchange('ex', 'direct', delivery_mode='persistent')
>>> ex.Message(body, delivery_mode='transient')['properties']['delivery_mode']
2      # expected 1
>>> Exchange('e2', 'direct', delivery_mode='transient').Message(body, delivery_mode='persistent')['properties']['delivery_mode']
1      # expected 2

So a caller asking for a transient message on a persistent exchange still gets delivery_mode=2 (RabbitMQ fsyncs and persists it), and one asking to persist can silently end up transient.

The sibling Producer._delivery_details already handles this correctly:

return exchange.name, maybe_delivery_mode(
    delivery_mode or exchange.delivery_mode,
)

This change makes Exchange.Message use the same idiom, falling back to the exchange default only when no delivery mode is passed. Exchange.publish() calls self.Message(message) with no delivery_mode, so None or self.delivery_mode reproduces the current behaviour exactly — nothing changes for existing callers.

Added t/unit/test_entity.py::test_Exchange::test_create_message_delivery_mode, which fails before this change and passes after (the argument overrides the exchange default; without it the default is used).

pytest t/unit/test_entity.py t/unit/test_messaging.py → 128 passed; flake8 (max-line-length 117) clean.

Exchange.Message accepts a delivery_mode argument and documents it as
'Set custom delivery mode. Defaults to :attr:delivery_mode', but the
body always used self.delivery_mode, so the caller's value was
discarded. Asking for a transient message on a persistent exchange
still produced delivery_mode=2, and vice versa.

Fall back to the exchange default only when no delivery mode is given,
matching Producer._delivery_details, which already does
'delivery_mode or exchange.delivery_mode'. Exchange.publish() passes no
delivery mode, so its behaviour is unchanged.
@codecov

codecov Bot commented Jul 25, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 82.81%. Comparing base (4281680) to head (bf5f25d).
✅ All tests successful. No failed tests found.

Additional details and impacted files
@@           Coverage Diff           @@
##             main    #2570   +/-   ##
=======================================
  Coverage   82.81%   82.81%           
=======================================
  Files          79       79           
  Lines       10333    10333           
  Branches     1187     1187           
=======================================
  Hits         8557     8557           
  Misses       1575     1575           
  Partials      201      201           

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

@auvipy auvipy added this to the 5.7.0 milestone Jul 25, 2026
@auvipy
auvipy self-requested a review July 25, 2026 14:03
@auvipy
auvipy merged commit a96f06e into celery:main Jul 25, 2026
41 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants