Codec¶
The codec module provides incremental stream decoders that parse raw bytes into typed message objects.
Decoders¶
| Decoder | Direction | Use case |
|---|---|---|
BackendMessageDecoder |
Server to Client | Building a client or proxy |
FrontendMessageDecoder |
Client to Server | Building a server or proxy |
Both share the same API.
BackendMessageDecoder¶
Decoder for backend (server to client) messages.
Parameters: None
The decoder automatically uses phase-aware framing based on the current connection phase. When used standalone (without Connection), you must manually update the phase property as the connection state changes.
Note
Backend messages use standard framing (identifier byte + length + payload) except during SSL/GSSAPI negotiation. The decoder handles phase transitions automatically when coordinated with FrontendConnection.
FrontendMessageDecoder¶
Decoder for frontend (client to server) messages.
Parameters: None
The decoder automatically uses phase-aware framing based on the current connection phase. Startup messages (StartupMessage, SSLRequest, etc.) use identifier-less framing, while standard messages use identifier byte + length + payload.
Important
When using the decoder standalone (without BackendConnection), you are responsible for updating the phase property to match connection state transitions. The Connection classes handle this automatically.
Methods¶
| Method | Returns | Description |
|---|---|---|
feed(data) |
None |
Append bytes to the internal buffer and parse complete messages |
read() |
PGMessage \| None |
Return next decoded message, or None |
read_all() |
list[PGMessage] |
Drain and return all decoded messages |
clear() |
None |
Discard all buffered data and pending messages |
Properties¶
| Property | Type | Description |
|---|---|---|
phase |
ConnectionPhase |
Current connection phase (read/write). Update this manually when using decoder standalone. |
buffered |
int |
Number of unprocessed bytes in the buffer |
Iteration¶
Both decoders implement __iter__ and __next__:
Basic usage¶
"""Codec: Basic usage."""
from pygwire import FrontendMessageDecoder
from pygwire.messages import StartupMessage
decoder = FrontendMessageDecoder()
# Feed bytes from your transport layer
# (Using fake data for demonstration)
startup_msg = StartupMessage(params={"user": "postgres", "database": "postgres"})
raw_bytes = startup_msg.to_wire()
decoder.feed(raw_bytes)
# Read messages one at a time
msg = next(decoder)
print(msg)
# Or iterate over all available messages
for msg in decoder:
print(msg)
# Update phase as connection progresses
# (Connection class handles this automatically)
Streaming and partial messages¶
The decoder handles arbitrarily chunked input. Feed one byte at a time or megabytes at once. It buffers internally until a complete message is available:
"""Codec: Streaming and partial messages."""
from pygwire import FrontendMessageDecoder
from pygwire.messages import StartupMessage
decoder = FrontendMessageDecoder()
# Create a startup message and convert to wire format
startup_msg = StartupMessage(params={"user": "postgres", "database": "postgres"})
wire_data = startup_msg.to_wire()
# Split the wire data into three chunks to simulate streaming
chunk_size = len(wire_data) // 3
first_chunk = wire_data[:chunk_size]
second_chunk = wire_data[chunk_size : chunk_size * 2]
third_chunk = wire_data[chunk_size * 2 :]
# Feed chunks one at a time - decoder buffers until complete message
decoder.feed(first_chunk)
decoder.feed(second_chunk)
decoder.feed(third_chunk)
# Now the complete message is available
msg = None
for m in decoder:
msg = m
break
print(f"Decoded: {type(msg).__name__}")
print(f"User: {msg}")
Phase-aware framing¶
The PostgreSQL wire protocol uses different framing formats based on connection phase:
- STARTUP phase: messages have no identifier byte (length + payload only)
- SSL_NEGOTIATION/GSS_NEGOTIATION: single-byte responses
- Standard phases: messages have identifier byte + length + payload
The decoder automatically selects the correct framing based on the phase property:
"""Codec: Phase-aware framing."""
from pygwire import ConnectionPhase, FrontendMessageDecoder
from pygwire.messages import StartupMessage
decoder = FrontendMessageDecoder()
assert decoder.phase == ConnectionPhase.STARTUP # Start in STARTUP
# Simulate client data (would come from socket.recv())
first_data_from_client = StartupMessage(params={"user": "postgres", "database": "mydb"}).to_wire()
decoder.feed(first_data_from_client)
for msg in decoder:
# First message will be StartupMessage, SSLRequest, etc.
print(msg)
# Manually update phase based on message
if isinstance(msg, StartupMessage):
decoder.phase = ConnectionPhase.AUTHENTICATING
print(decoder.phase)
Tip
Use FrontendConnection or BackendConnection to automatically coordinate decoder phase with state machine transitions.
Buffer management¶
The decoder uses memoryview for zero-copy payload slicing. It automatically compacts its internal buffer when consumed data exceeds 4 KB. You do not need to manage the buffer yourself.
To discard all buffered data and pending messages: