mrkeyoor.com_
Sun 20 Sept 11:47 UTC
PyPIWeb Backendupdated 20 Sept 2026

pyzmq review

pyzmq 27.2.0 is the Python binding for ZeroMQ, exposing contexts, sockets, polling, multipart messages, CURVE security, monitoring, and asyncio-compatible sockets. ZeroMQ connects peers directly with patterns such as request/reply, publish/subscribe, push/pull, and router/dealer; pyzmq does not add a broker or durable queue. Version 27.2 expands type coverage, adds python -m zmq.curve_keygen, raises the minimum Python to 3.9, and updates wheels and builds for newer CPython, Cython, and Visual Studio releases.

Verdict

pyzmq 27.2.0 installed as one 4 MB package in 0.2 seconds, imported in 0.21 seconds, and produced zero pip-audit findings in our sandbox. Use it for direct cross-language messaging when your team will own delivery rules; choose a broker when persistence and redelivery belong in infrastructure.

We installed it

Lab card: what happened when we installed pyzmqScreenshot of pyzmq documentation
Install✓ · 0.2s1 package on disk · 4 MB
Importimport zmq in 0.21s · compiled extensions · py.typed · requires Python >=3.9
Known vulns0(pip-audit)

Answers from our run

Does pyzmq install cleanly?

Yes. In a fresh container with an empty cache, pip install pyzmq finished in 0.2s, leaving 1 package and 4 MB on disk. pip-audit reported no known vulnerabilities.

What does pyzmq need to run?

Python >=3.9, and a platform wheel with compiled extensions. In our run import zmq succeeded in 0.21s, and the package ships py.typed for type checkers.

pyzmq or pynng: which should you use?

pynng: Choose it for NNG messaging patterns when that C library and its dialer or listener model fit the deployment. pyzmq 27.2.0 installed as one 4 MB package in 0.2 seconds, imported in 0.21 seconds, and produced zero pip-audit findings in our sandbox.

When should you not use pyzmq?

Messages must survive every process restart or be replayed by consumer offset; ZeroMQ has no built-in durable broker or log

API stability5/5pyzmq 27.2.0 still centers on Context, Socket, Poller, socket options, and the long-standing ZeroMQ messaging patterns. The 27.0 major made no code or API breaks and changed the supported Linux wheel baselines instead. Version 27.2 raises the Python floor from 3.8 to 3.9 and adjusts free-threaded wheel coverage, which matters for deployment, while ordinary send, receive, poll, and asyncio code remains familiar.
Docs5/5The pyzmq 27.2 documentation separates the binding API from practical guidance on threads, context managers, serialization, zero-copy buffers, asyncio, devices, CURVE, SSH tunnels, and source builds. It states plainly that sockets are not thread-safe and that pickle deserialization can execute arbitrary code. The material assumes readers will also learn ZeroMQ's socket state machines and delivery behavior from the ZeroMQ Guide, so the Python reference alone is insufficient for protocol design.
Maintenance5/5zeromq/pyzmq published 27.2.0 on 2026-08-20, the same date as the repository's latest recorded push. The project is unarchived, has 4,164 stars, and GitHub showed 51 open issues and pull requests combined. The release updates typing, Python 3.15 free-threaded wheels, Visual Studio 2026 builds, and future Cython compatibility. That is current maintenance across Python packaging and the native layer it binds.
Ecosystem5/5The supplied usage figure is about 24.3 million downloads per week, and the zeromq/pyzmq repository has 4,164 stars. pyzmq speaks the established ZeroMQ protocol and patterns used by peers in many languages; Jupyter is a prominent Python ecosystem consumer. The trade is operational: its ecosystem provides bindings and protocol recipes, while durable storage, broker dashboards, redelivery, and consumer offsets remain outside the package.

Use it if

  • Processes you control need low-latency messaging over inproc, IPC, or TCP without operating a separate broker
  • The protocol naturally matches PUB/SUB broadcasts, PUSH/PULL work distribution, REQ/REP calls, or ROUTER/DEALER routing
  • A Python service needs to communicate with ZeroMQ peers written in C, C++, Rust, Java, or another supported language
  • The team can define framing, retries, authentication, queue limits, and shutdown behavior as application protocol
Skip it if

Setup reality

Our pyzmq 27.2.0 install completed in 0.2 seconds in a clean Python 3.12 Bookworm sandbox. One package used 4 MB, pip-audit found zero known vulnerabilities, and import zmq succeeded in 0.21 seconds. The distribution reports 1 direct dependency, requires Python 3.9 or newer, includes compiled .so extensions, and ships py.typed. The lab metadata did not identify a license.

That fast path came from a compatible wheel. Unsupported systems may compile pyzmq and bundled libzmq through CMake, or link against an existing libzmq. Version 27.2 adds a CPython 3.15 free-threaded wheel and stops publishing the CPython 3.13 free-threaded wheel, so free-threaded deployments need exact wheel checks. Windows wheels leave IPC disabled because the project documents crashes in that transport.

A Context may be shared among threads, while each ordinary socket belongs to one thread. Create fresh sockets after a process fork as well. Socket types have state machines: a REQ socket must send before it receives, SUB sees only subscribed prefixes, and PUB can drop early messages before subscription handshakes finish. Set RCVTIMEO, SNDTIMEO, and high-water marks instead of allowing every call and queue to wait forever.

Shutdown needs an explicit policy. Unsent frames can hold close or context termination according to LINGER, so use a finite value and close sockets before terminating their context. send_pyobj and recv_pyobj use pickle; the pyzmq docs warn that an untrusted sender can execute code during deserialization. Prefer bytes, JSON, or a validated codec. CURVE encrypts and authenticates transport, but client authorization still requires an authenticator and a managed allowlist of public keys.

Patterns

Exchange one request and one reply request-reply

# server.py
import zmq
ctx = zmq.Context()
rep = ctx.socket(zmq.REP)
rep.bind('tcp://127.0.0.1:5555')
while True:
    request = rep.recv_json()
    rep.send_json({'id': request['id'], 'ok': True})

# client.py
req = ctx.socket(zmq.REQ)
req.connect('tcp://127.0.0.1:5555')
req.send_json({'id': 42})
reply = req.recv_json()

REQ and REP enforce alternating send and receive calls. A timeout does not reset that state machine; recreate or relax the pattern when retries are required.

Publish multipart messages by topic publish-topics

# publisher
pub = ctx.socket(zmq.PUB)
pub.bind('tcp://*:5556')
pub.send_multipart([b'orders.created', b'{"id":42}'])

# subscriber
sub = ctx.socket(zmq.SUB)
sub.connect('tcp://127.0.0.1:5556')
sub.subscribe(b'orders.')
topic, payload = sub.recv_multipart()

SUB filters by byte prefix and receives nothing without a subscription. Messages sent before the subscription handshake may be lost.

Fan jobs out with PUSH and PULL distribute-work

# producer
push = ctx.socket(zmq.PUSH)
push.bind('tcp://*:5557')
for job_id in range(100):
    push.send_json({'job_id': job_id})

# worker
pull = ctx.socket(zmq.PULL)
pull.connect('tcp://127.0.0.1:5557')
while True:
    job = pull.recv_json()
    process(job)

PUSH balances messages among connected PULL sockets. It supplies no acknowledgement or automatic redelivery when a worker dies after receiving a job.

Address DEALER peers through a ROUTER route-by-identity

# broker
router = ctx.socket(zmq.ROUTER)
router.bind('tcp://*:5558')
peer_id, payload = router.recv_multipart()
router.send_multipart([peer_id, b'accepted:' + payload])

# worker
dealer = ctx.socket(zmq.DEALER)
dealer.setsockopt(zmq.IDENTITY, b'worker-7')
dealer.connect('tcp://127.0.0.1:5558')
dealer.send(b'job-42')
reply = dealer.recv()

ROUTER adds the sender identity as the first frame and requires that identity on the reply. Mixed REQ and DEALER peers may use different envelope frames.

Wait on several sockets with a deadline poll-sockets

poller = zmq.Poller()
poller.register(sub, zmq.POLLIN)
poller.register(pull, zmq.POLLIN)

events = dict(poller.poll(timeout=1000))
if sub in events:
    handle_event(sub.recv_multipart())
if pull in events:
    handle_job(pull.recv_json())
if not events:
    send_heartbeat()

Poller timeouts are measured in milliseconds. A quiet interval returns an empty event list and does not indicate a disconnected peer.

Receive ZeroMQ messages with asyncio await-messages

import asyncio
import zmq
import zmq.asyncio

async def consume():
    ctx = zmq.asyncio.Context()
    sock = ctx.socket(zmq.PULL)
    sock.bind('tcp://127.0.0.1:5559')
    try:
        while True:
            message = await sock.recv_multipart()
            await handle(message)
    finally:
        sock.close(linger=0)
        ctx.term()

asyncio.run(consume())

Use zmq.asyncio sockets for Future-returning operations. Keep one receive loop per socket instead of racing several coroutines on recv().

Stop send and receive from waiting forever bound-wait-time

sock.setsockopt(zmq.RCVTIMEO, 2000)
sock.setsockopt(zmq.SNDTIMEO, 2000)

try:
    sock.send_json(payload)
    reply = sock.recv_json()
except zmq.Again:
    record_timeout()

Both timeout options use milliseconds and raise zmq.Again. A timed-out REQ socket remains in its request state and cannot simply send again.

Apply send and receive high-water marks limit-queued-messages

sock.setsockopt(zmq.SNDHWM, 5000)
sock.setsockopt(zmq.RCVHWM, 5000)

try:
    sock.send_multipart(frames, flags=zmq.NOBLOCK)
except zmq.Again:
    metrics.increment('zmq.queue_full')

Set high-water marks before bind or connect. Some socket types block or raise at the limit, while PUB can drop messages for slow subscribers.

Send structured data without pickle serialize-safe-payload

import json

payload = json.dumps({'id': 42, 'ok': True}).encode('utf-8')
sock.send(payload)
message = json.loads(sock.recv().decode('utf-8'))

recv_pyobj calls pickle.loads and can execute attacker-controlled code. Authenticate the peer and prefer a constrained codec for untrusted boundaries.

Create CURVE credentials from the command line generate-curve-keys

python -m zmq.curve_keygen --json

Version 27.2 adds this entry point. Store secret keys with restricted permissions; the public server key must be distributed to clients through a trusted channel.

Configure a CURVE client and server secure-curve-transport

server_public, server_secret = zmq.curve_keypair()
client_public, client_secret = zmq.curve_keypair()

server = ctx.socket(zmq.REP)
server.curve_secretkey = server_secret
server.curve_publickey = server_public
server.curve_server = True
server.bind('tcp://*:5560')

client = ctx.socket(zmq.REQ)
client.curve_publickey = client_public
client.curve_secretkey = client_secret
client.curve_serverkey = server_public
client.connect('tcp://127.0.0.1:5560')

CURVE options must be set before the endpoint connects. Use a zmq.auth authenticator when the server must allow only named client public keys.

Shut sockets down with finite linger close-with-deadline

ctx = zmq.Context()
sock = ctx.socket(zmq.PUSH)
sock.setsockopt(zmq.LINGER, 1000)
sock.connect('tcp://127.0.0.1:5557')
try:
    sock.send_json({'job_id': 42})
finally:
    sock.close()
    ctx.term()

A 1000 ms linger gives pending sends a bounded drain period. linger=0 drops queued frames immediately and is appropriate only when that loss is accepted.

Alternatives

PackageRegistryPick it when
pynngPyPIChoose it for NNG messaging patterns when that C library and its dialer or listener model fit the deployment
aiozmqPyPIChoose it only when maintaining an older asyncio integration that already depends on its stream and RPC abstractions
pikaPyPIChoose it with RabbitMQ when durable queues, acknowledgements, routing exchanges, and broker operations are requirements
nats-pyPyPIChoose it when a NATS server can own subject routing, reconnect behavior, and optional JetStream durability

More web backend guides

urllib3 · requests · ws · anyio · httpx · undici · the whole shelf →

How this guide is made: grounded in the library's documentation, release notes, changelog, and issue history, on a fixed rubric — not a hands-on install of every release. The 50 most-downloaded entries are additionally install-verified in clean containers. Corrections: contact the desk.