Upgrading to 1.5.0
Six changes are worth reading before you upgrade. Three can turn a passing build red, and two change what a working application observes when it cancels a consumer or closes a channel.
pika.Connection is new, and is the connection to use
1.5.0 introduces pika.Connection and its Channel, a connection that runs its own IOLoop on a background thread and offers a thread-safe blocking API that does not stall heartbeats while your code processes a message. There is nothing to migrate from: these classes are new in this release, and pika.Connection is the documented import.
from pika.adapters.thread_safe_connection import Connection also works, though the module moves in 2.0 (see #1681). Channel is not exported from pika, because channels come from Connection.channel().
If you are starting new code, use pika.Connection. If you have existing code on one of the adapters below, it keeps working in 1.5.0 and the deprecation notice tells you where it is going.
The other adapters now emit a DeprecationWarning, which may fail your tests
BlockingConnection, TornadoConnection, TwistedProtocolConnection and GeventConnection each warn from their constructor that they will be removed in Pika 2.0. They all still work.
The upgrade risk is that a DeprecationWarning raised from a constructor is a hard error for anyone running with warnings-as-errors, which is a common test configuration. It fires at construction, before any I/O, so it does not depend on reaching a broker:
E DeprecationWarning: BlockingConnection is deprecated and will be removed in Pika 2.0. ...
pika/adapters/blocking_connection.py:367: DeprecationWarning
The same happens under python -W error and warnings.simplefilter('error'). BlockingConnection is pika's most widely used entry point, so this is both likely and likely to be misread as pika being broken rather than as an intentional notice. Outside a test runner it is usually invisible, because Python's default filters hide DeprecationWarning unless it is triggered from __main__; pytest is what turns it back on.
Either filter it or migrate. The warning uses stacklevel=2, so it is reported against the line that constructs the connection rather than against pika, and a filter scoped to the pika module does not match. Match on the message instead:
# pyproject.toml, for pytest
filterwarnings = [
"ignore:.*will be removed in Pika 2\\.0:DeprecationWarning",
]python -W 'ignore:BlockingConnection is deprecated' your_app.pyor migrate to Connection, which runs its own IOLoop on a background thread and provides a thread-safe blocking API that does not stall heartbeats on slow message processing.
Cancelling a consumer no longer rejects the messages still arriving for it
A Basic.Cancel does not stop the broker immediately: the specification allows it to keep dispatching until it sends Basic.CancelOk, and pika used to reject every delivery that arrived in that window, or discard it outright for an auto_ack consumer. Discarding loses the message, because an auto_ack delivery has no copy left at the broker, and rejecting is a decision pika was making on your behalf that no other client makes. Measured on RabbitMQ 4.3.6, closing a channel with an auto_ack consumer half a second after its first delivery, 400 messages, work_queue_maxsize=50 and a 50 ms callback. At 8 KiB bodies with close(timeout=10): 339 of 400 lost before this change, 179 to 189 across five runs after it. close(timeout=None) loses none after the change, at either body size, and lost the same 339 before it, because that loss was the cancel window rather than the budget. At 64 bytes the deliveries still queued when a bounded close returns fit in the work queue, so the worker finishes them and nothing is lost; at 8 KiB they do not fit and the excess is discarded when the pool shuts. Quote the whole configuration with any figure from this area, because the message count, body size, queue size, callback cost and timeout all move it. utils/measure_close_loss.py in the repository reproduces each of them.
Those deliveries are now passed to your callback instead, which is what the Java client does, as long as the channel is still open.
One thing follows for existing code: a consumer callback can now run for a message that arrives after you called basic_cancel, so a callback that assumed cancellation was immediate may see further deliveries. BlockingChannel.basic_cancel still rejects them for an auto_ack=False consumer, so they return to the queue at once as before, and its return value is unchanged; the channel no longer does that rejecting, the wrapper does. The generator-style BlockingChannel.cancel discards them instead, which #1767 covers.
channel.close() on an asynchronous adapter still discards those deliveries
A channel that is closing discards them, because a callback could not acknowledge one anyway and the attempt would abort the connection; the Java client discards them at the same point. Channel.close cancels each consumer without waiting for the broker to confirm, so this is what a bare channel.close() does on SelectConnection, AsyncioConnection, TornadoConnection and GeventConnection. A discarded delivery is redelivered to the next consumer with auto_ack=False and lost with auto_ack=True. Pika now logs a warning naming the count as the channel closes.
pika.Connection and BlockingConnection are unaffected and keep every delivery across a close, since both cancel their consumers and wait for the broker to confirm before closing anything. To get the same guarantee from an asynchronous adapter, cancel each consumer with a callback and close the channel from the last Basic.CancelOk; the channel documentation has the pattern. Pika 2.0 removes these adapters, which leaves Connection and its Channel as the only close path and so retires the gap rather than fixing it.
pika.compat is gone
The module is removed. It held Python 2 compatibility shims and platform checks, was never documented as public API, and nothing in pika imports it any more. It had no leading underscore, though, so it has been importable for years and an upgrade turns import pika.compat into ModuleNotFoundError.
Twelve of its names moved to pika._utils: ON_LINUX, ON_OSX, ON_WINDOWS, LINUX_VERSION, SOCKET_ERROR, HAVE_SIGNAL, RE_NUM, time_now(), as_bytes(), to_digit(), get_linux_version() and nonblocking_socketpair(). That module is private, so it is not a migration target; copy what you need instead. The four that are gone were Python 2 artefacts, and the standard library covers each: byte() (a bytes literal), long (int), AbstractBase (abc.ABC), str_or_bytes ((str, bytes)). pika.spec.str, which was str = bytes, goes with them.
pika.spec is now annotated, which may fail your type checker
Pika ships a py.typed marker, so a type checker reads pika's own annotations. pika.spec is generated code and was previously excluded from pika's mypy configuration, which left every field of a decoded frame typed as Any. Any silences all checking, so a downstream mypy run that was clean can go red on upgrade without any change on your end. The most common line in every pika tutorial is affected:
error: Argument "delivery_tag" to "basic_ack" of "BlockingChannel" has incompatible type "int | None"; expected "int" [arg-type]
error: Argument "routing_key" to "basic_publish" of "BlockingChannel" has incompatible type "str | bytes | None"; expected "str" [arg-type]
Two properties of the generated code account for it. Every generated constructor defaults its arguments to None, so fields are optional even though a frame off the wire always carries them. And AMQP shortstr has no declared encoding, so pika falls back to raw bytes on invalid UTF-8, making routing_key, exchange and consumer_tag all str | bytes | None.
The errors are accurate and the annotations are an improvement, so nothing here should be reverted. The FAQ covers the narrowing idioms: https://pika.github.io/pika/latest/faq/
pyright users see little change, since it reported most of these already.
The same notes are in the changelog, which also carries the full version history: https://pika.github.io/pika/latest/changelog/
What's Changed
Implemented enhancements
- Build the release dist once and add a TestPyPI test mode by @lukebakken in #1656
- Check
pika/spec.pyagainstcodegen.pyoutput in CI by @alonfaraj in #1695 - Redirect the retired ReadTheDocs site to GitHub Pages by @lukebakken in #1720
- Reply with
Basic.CancelOkto brokers that accept it by @suchitd in #1722 - Adopt writing guidance for agents, and trim AGENTS.md by @lukebakken in #1739
- Trigger the release from a pushed tag, not a push to
mainby @lukebakken in #1729 - Build the changelog entry from the milestone, not by crawling history by @lukebakken in #1732
Fixed bugs
- fix: Stop the getaddrinfo acceptance tests from depending on live DNS by @alonfaraj in #1667
- Make
CallbackManager.removehonor its remove-all docstring by @lukebakken in #1693 - Reject out-of-range integers and pre-epoch timestamps on encode by @eeshsaxena in #1718
- Reject bool for port and socket/stack timeouts by @Pitchfork-and-Torch in #1716
- Let a pre-release be promoted to its final version by @lukebakken in #1723
- Give the big-message acceptance test its own timeout budget by @lukebakken in #1725
- Stop the preview leg reporting an unexpanded check name by @lukebakken in #1726
- Annotate the return values of the blocking RPC wrappers by @lukebakken in #1772
- Render every documentation cross-reference, and check it by @lukebakken in #1753
- Contain any exception a
_BoundedWorkPoolwork item raises by @lukebakken in #1756 - Report a crashed IOLoop through
is_openandis_closedby @lukebakken in #1745 - Identify a pool worker by a stamp, not by the channel list by @lukebakken in #1764
- Drain the callback pools before the connection closes by @lukebakken in #1768
Documentation
- Migrate Docs to MkDocs by @alonfaraj in #1576
- Clarify connection_is_idle single-interval threshold is by design by @lukebakken in #1648
- Correct stale file names and CI details in
AGENTS.mdby @alonfaraj in #1665 - Rename ThreadSafeConnection/Channel to Connection/Channel by @lukebakken in #1682
- Deploy versioned docs to GitHub Pages via mike by @alonfaraj in #1614
- Add the 1.5.0 upgrade notes, in both places a reader looks by @lukebakken in #1727
- Remove the duplicated TLS example and rework the TLS consumer by @lukebakken in #1734
- Fix the defects the 1.5.0 documentation review found by @lukebakken in #1744
- Unwrap the hard-wrapped markdown, and broaden the rule by @lukebakken in #1758
- Add a script that reproduces the close-loss figures by @lukebakken in #1771
- Document that no pika connection survives os.fork() by @lukebakken in #1766
- Narrow the empty-intensifier rule, and clear the real cases by @lukebakken in #1760
- Smoke-test the connection 1.5.0 documents, and note
pika.compatby @lukebakken in #1776 - Document what happens when a consumer callback raises by @lukebakken in #1774
- Make
RELEASE.mdthe procedure, and move the reasoning out by @lukebakken in #1778
Other changes
- Fix mypy error caused by twisted by @alonfaraj in #1589
- fix: examples with rabbitmq 4.3 need durable queues by @dadodimauro in #1585
- Update PULL_REQUEST_TEMPLATE.md for clarity and structure by @alonfaraj in #1588
- CI: Add macOS by @alonfaraj in #1587
- Remove Python 2 compatibility code and modernize for Python 3 by @alonfaraj in #1581
- Enable expanded Ruff rules by @alonfaraj in #1590
- fix: handle channel closure in BlockingConnection.process_data_events by @dadodimauro in #1586
- feat: remove pylint suppression comments and .pylintrc by @dadodimauro in #1584
- feat: Inline comments fixes by @dadodimauro in #1583
- feat(docs): add DeliveryMode enum documentation by @alonfaraj in #1592
- tests: Expand unit tests for
DeliveryModeand enhanceExchangeTypeandexceptionsby @alonfaraj in #1593 - fix: codecov coverage upload by @alonfaraj in #1594
- Expands Ruff lint coverage by @alonfaraj in #1591
- tests: fix invisible tests and improve coverage by @alonfaraj in #1595
- Fix
Channel.close()for channels with multiple consumers by @gbenson in #1596 - chore: upgrade RabbitMQ to 4.3.1 and pin Linux CI Docker image by @alonfaraj in #1599
- Fix
DeliveryModeto inherit fromIntEnumby @lukebakken in #1604 - Reorganize CI under .ci/, drop testdata/versions.json, always-latest by @lukebakken in #1606
- feat(CI): improved version release flow by @alonfaraj in #1598
- Add ThreadSafeConnection: fix _tx_buffers race and thread-safety bugs by @Dheeraj4103 in #1582
- Run tests in parallel via pytest-xdist by default by @alonfaraj in #1610
- feat(CI): Add TLS to Linux and macOS by @alonfaraj in #1609
- Add codecov.yml to fix premature coverage reporting by @alonfaraj in #1613
- Fix flaky test_concurrent_publishes_all_scheduled by @alonfaraj in #1616
- Deprecate Tornado, Twisted, Blocking, and Gevent connection adapters by @alonfaraj in #1615
- Remove type: ignore comments from the codebase by @lukebakken in #1619
- Bump codecov/codecov-action from 6 to 7 in the github-actions group by @dependabot[bot] in #1622
- Fix: Wide typehints and docstrings by @alonfaraj in #1621
- Chore: remove redundant
rtypeby @alonfaraj in #1626 - Add docformatter to enforce PEP 257 docstring style by @alonfaraj in #1625
- Bump actions/checkout from 6 to 7 in the github-actions group by @dependabot[bot] in #1627
- feat: Callback removal API by @alonfaraj in #1628
- Chore: re-apply redundant
rtyperemoval lost in #1625 merge by @alonfaraj in #1629 Channel.confirm_delivery: append ack/nack callback instead replace by @alonfaraj in #1630- Include py.typed marker in distribution by @alonfaraj in #1632
- Chore: expand ruff rules by @alonfaraj in #1633
- Bump actions/cache from 5 to 6 in the github-actions group by @dependabot[bot] in #1636
- Chore: general typing fixes by @alonfaraj in #1638
- Add ThreadSafe examples for publisher confirms and long-running publisher by @alonfaraj in #1637
- Include hostname and port in connection error reporting by @lukebakken in #1635
- Add
FAruff rule and apply fixes by @alonfaraj in #1643 - Bump the github-actions group with 4 updates by @dependabot[bot] in #1640
- Stop mutating global asyncio event loop policy on import by @lukebakken in #1641
- Bump the github-actions group with 3 updates by @dependabot[bot] in #1644
- Chore: guard type-only imports and tighten mypy by @alonfaraj in #1646
- Remove dead capabilities-stripping code from Connection._set_server_information by @alonfaraj in #1647
- Fix: address pyright issues by @alonfaraj in #1649
- Bump the github-actions group across 1 directory with 6 updates by @dependabot[bot] in #1657
- Optimization: Speed up field-table codec by @alonfaraj in #1655
- Optimization: Eliminate quadratic copying in inbound frame buffer by @alonfaraj in #1653
- Fix: pyright errors by @alonfaraj in #1652
- Fix ruff 0.16.0
RUF036andISC004violations by @lukebakken in #1659 - Upload coverage to Codecov once after all tests finish by @alonfaraj in #1664
- Add Ruff quote style (
Q) by @alonfaraj in #1662 - Bound work queues in
ThreadSafe*adapters. by @jholaj in #1651 - ruff: extend the default rule set and fix the fallout by @alonfaraj in #1666
- README: drop unsupported
maxAgeparameter from downloads badge by @alonfaraj in #1668 - Remove six
type: ignorecomments from three adapters by @lukebakken in #1670 - Drop the six tractable Twisted
type: ignorecomments by @lukebakken in #1672 - Type-annotate the generated
spec.pyand drop its mypy exemption by @alonfaraj in #1674 - Make
AbstractSelectorIOLoopa structuralProtocolby @lukebakken in #1671 - Revert "Fix
callLatertyping by passing keyword arguments" by @jeremycline in #1678 - Perf: codec hot paths by @alonfaraj in #1676
- Fix: gh-1679 (Reject work scheduled on a closed ThreadSafeConnection) by @alonfaraj in #1680
- Fix: gh-1684 (Release each blocking RPC's channel callbacks) by @alonfaraj in #1685
- Drop each closed channel from the connection's tracking list by @alonfaraj in #1689
- build(deps): bump the github-actions group with 3 updates by @dependabot[bot] in #1696
- Contain exceptions from the basic_publish on_publish callback by @aryansk in #1690
- Encode Python
floatvalues as the AMQPdfield type by @dylanpulver in #1705 - Reject decimal.Decimal values with no AMQP wire representation by @afonsojanu in #1704
- Stop
StreamLostErrorfrom hiding the original traceback by @jholaj in #1703 - build(deps): bump the github-actions group with 3 updates by @dependabot[bot] in #1702
- Resolve "Drop the versionless License :: classifier now that license is a valid SPDX expression by @btschwertfeger in #1701
- Stop an out-of-range
timestampheader from closing the connection by @alonfaraj in #1699 - Fix
Connection.close()self-deadlock from a consumer callback by @zanarellidev in #1697 - Fix: Stop
ThreadSafeChannelconfirm tags drifting from the broker by @alonfaraj in #1694 - Bound how long a channel retains nowait-cancelled consumer tags by @alonfaraj in #1692
- CI: Test against pre-release Python 3.15 in a non-blocking leg by @alonfaraj in #1707
- CI: run macOS on one Python version in a minimal non-blocking leg by @lukebakken in #1709
- build(deps): bump the github-actions group with 3 updates by @dependabot[bot] in #1710
- build(deps): bump the github-actions group with 4 updates by @dependabot[bot] in #1715
- build(deps): bump the github-actions group with 4 updates by @dependabot[bot] in #1733
- Add Python 3.15 official support by @alonfaraj in #1741
- build(deps): bump the github-actions group with 5 updates by @dependabot[bot] in #1746
- Accept an AMQP URL with a username but no password by @eeshsaxena in #1747
- Honor connection close deadlines from callback pool workers by @Grivn in #1748
- Validate user callbacks before wrapping channel operations by @ac1982 in #1749
- Share one connection harness between the five test classes by @lukebakken in #1775
- Enable mypy's
warn_return_anyand make the returns true by @alonfaraj in #1777 - pika 1.5.0rc1 by @lukebakken in #1779
New Contributors
- @gbenson made their first contribution in #1596
- @Dheeraj4103 made their first contribution in #1582
- @jholaj made their first contribution in #1651
- @aryansk made their first contribution in #1690
- @dylanpulver made their first contribution in #1705
- @afonsojanu made their first contribution in #1704
- @btschwertfeger made their first contribution in #1701
- @zanarellidev made their first contribution in #1697
- @eeshsaxena made their first contribution in #1718
- @Pitchfork-and-Torch made their first contribution in #1716
- @Grivn made their first contribution in #1748
- @ac1982 made their first contribution in #1749
Full Changelog: 1.4.0...1.5.0rc1