Skip to content

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.

from pygwire import BackendMessageDecoder

decoder = BackendMessageDecoder()

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.

from pygwire import FrontendMessageDecoder

decoder = FrontendMessageDecoder()

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__:

decoder.feed(raw_bytes)
for msg in decoder:
    print(type(msg).__name__)

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:

  1. STARTUP phase: messages have no identifier byte (length + payload only)
  2. SSL_NEGOTIATION/GSS_NEGOTIATION: single-byte responses
  3. 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:

decoder.clear()