Skip to content

Converters

A type override replaces a column’s Python type, but it converts by calling that type (Preferences(value)), which cannot express JSON or model (de)serialization. A converter names two of your own functions instead, used whenever the column is read from or written to the database.

Specification

A converter is declared under converters and referenced by an override:

FieldDescription
nameUnique name, referenced by an override’s converter.
py_typeThe Python type the converter produces and accepts (import/package/type).
to_dbDotted path to a function turning the Python value into the column’s wire type.
from_dbDotted path to a function turning the wire value into the Python type.
options:
  converters:
    - name: prefs
      py_type:
        import: myapp.models
        package: Preferences
        type: Preferences
      to_db: myapp.converters.encode_preferences
      from_db: myapp.converters.decode_preferences
  overrides:
    - db_type: jsonb            # every jsonb column
      converter: prefs
    - column: users.preferences # or one specific column
      converter: prefs

Because the converter is referenced by an override, it applies to whichever columns that override matches, and converter replaces py_type on the override.

Generated code

The column is typed with your py_type, and the plugin inserts calls to your functions on both sides:

# read - from_db on each column
return models.User(id_=row[0], preferences=myapp.converters.decode_preferences(row[1]))

# write - to_db on each parameter
await conn.execute(CREATE_USER, id_, myapp.converters.encode_preferences(preferences))

Nullable columns are guarded, so your functions never receive None:

maybe_prefs=myapp.converters.decode_preferences(row[2]) if row[2] is not None else None

and list columns convert element-wise:

[myapp.converters.encode_label(v) for v in labels]

Rules

  • Both directions are required. to_db and from_db are dotted paths to module-level functions; the module is imported and the function called fully qualified, so the names can never collide with generated code.
  • Wire type. to_db must return the type the column would have had without the override (jsonb is a str, bytea a memoryview - see type mappings), and from_db receives that same type. For an unrecognised SQL type there is no such type, so to_db must return a type the driver accepts.
  • Your functions never see None. A SQL NULL stays None without calling the converter.
  • List columns convert element-wise.
To reach an ANY($1::type[]) array parameter, match the override with db_type rather than column - sqlc does not link column overrides to those parameters.

Next

Converters are most often reached for with JSON columns. See Working with JSON for a complete walkthrough using msgspec structs.