Skip to content

cdm_data_loaders.converters.facades.pydantic_to_pyarrow

Convert Pydantic v2 BaseModel schemas to PyArrow schemas.

PydanticToPyArrowError

Bases: ConversionError

Raised when a Pydantic model construct cannot be converted to a PyArrow schema.

Subclasses raised by :func:generate_pyarrow_schema carry a stable specification code and a short title; their docstrings state why the construct is rejected. The converter specification document is generated from these attributes.

Source code in src/cdm_data_loaders/converters/facades/pydantic_to_pyarrow.py
class PydanticToPyArrowError(ConversionError):
    """Raised when a Pydantic model construct cannot be converted to a PyArrow schema.

    Subclasses raised by :func:`generate_pyarrow_schema` carry a stable specification ``code`` and a short
    ``title``; their docstrings state why the construct is rejected. The converter specification document is
    generated from these attributes.
    """

    code: ClassVar[str] = ""
    title: ClassVar[str] = ""

InvalidPydanticModelError

Bases: PydanticToPyArrowError

Converter requires a concrete Pydantic BaseModel subclass as input.

Source code in src/cdm_data_loaders/converters/facades/pydantic_to_pyarrow.py
class InvalidPydanticModelError(PydanticToPyArrowError):
    """Converter requires a concrete Pydantic `BaseModel` subclass as input."""

    code = "E1"
    title = "Input is not a concrete Pydantic `BaseModel` subclass"

NonStructRootError

Bases: PydanticToPyArrowError

Root of a PyArrow schema must be a struct (a table has named columns, not a bare scalar, list, or map).

Source code in src/cdm_data_loaders/converters/facades/pydantic_to_pyarrow.py
class NonStructRootError(PydanticToPyArrowError):
    """Root of a PyArrow schema must be a `struct` (a table has named columns, not a bare scalar, list, or map)."""

    code = "E2"
    title = "Non-struct `RootModel`"

EmptyModelError

Bases: PydanticToPyArrowError

Parquet cannot persist a struct with zero child fields.

Source code in src/cdm_data_loaders/converters/facades/pydantic_to_pyarrow.py
class EmptyModelError(PydanticToPyArrowError):
    """Parquet cannot persist a `struct` with zero child fields."""

    code = "E3"
    title = "Empty model or tuple (zero fields)"

UnconstrainedFieldError

Bases: PydanticToPyArrowError

PyArrow requires a concrete static column type and has no dynamic any or untyped container column.

Source code in src/cdm_data_loaders/converters/facades/pydantic_to_pyarrow.py
class UnconstrainedFieldError(PydanticToPyArrowError):
    """PyArrow requires a concrete static column type and has no dynamic `any` or untyped container column."""

    code = "E4"
    title = "Unconstrained field (`Any`, `object`, untyped container, or unbound `TypeVar`)"

OpenTupleError

Bases: PydanticToPyArrowError

Positional tuple structs require a fixed element count so every position maps to a deterministic field.

Source code in src/cdm_data_loaders/converters/facades/pydantic_to_pyarrow.py
class OpenTupleError(PydanticToPyArrowError):
    """Positional tuple structs require a fixed element count so every position maps to a deterministic field."""

    code = "E5"
    title = "Open tuple mixing positional and variadic elements"

NonStringMapKeyError

Bases: PydanticToPyArrowError

Fixed-field structs cannot mix open extra='allow' maps, and Arrow maps require string keys for JSONL round-tripping.

Source code in src/cdm_data_loaders/converters/facades/pydantic_to_pyarrow.py
class NonStringMapKeyError(PydanticToPyArrowError):
    """Fixed-field structs cannot mix open `extra='allow'` maps, and Arrow maps require string keys for JSONL round-tripping."""

    code = "E6"
    title = "Open `extra='allow'` model or non-string mapping key type"

UnsupportedFieldTypeError

Bases: PydanticToPyArrowError

Non-serializable Python runtime types and null-only fields have no valid Arrow column representation.

Source code in src/cdm_data_loaders/converters/facades/pydantic_to_pyarrow.py
class UnsupportedFieldTypeError(PydanticToPyArrowError):
    """Non-serializable Python runtime types and null-only fields have no valid Arrow column representation."""

    code = "E7"
    title = "Unsupported field type or null-only field"

IncompatibleUnionBranchesError

Bases: PydanticToPyArrowError

PyArrow cannot merge lists, maps, and structs into a single column without an opaque union type.

Source code in src/cdm_data_loaders/converters/facades/pydantic_to_pyarrow.py
class IncompatibleUnionBranchesError(PydanticToPyArrowError):
    """PyArrow cannot merge lists, maps, and structs into a single column without an opaque union type."""

    code = "E8"
    title = "Incompatible structural union branches"

UnresolvedForwardRefError

Bases: PydanticToPyArrowError

Schema generation requires all forward type annotations to be resolved to concrete types.

Source code in src/cdm_data_loaders/converters/facades/pydantic_to_pyarrow.py
class UnresolvedForwardRefError(PydanticToPyArrowError):
    """Schema generation requires all forward type annotations to be resolved to concrete types."""

    code = "E9"
    title = "Unresolvable forward reference"

RecursiveNonJsonTypeError

Bases: PydanticToPyArrowError

Truncated recursive subtrees are serialized with json.dumps, which cannot encode datetime.date or bytes; cyclic type aliases without a BaseModel boundary cannot be finitely unrolled.

Source code in src/cdm_data_loaders/converters/facades/pydantic_to_pyarrow.py
class RecursiveNonJsonTypeError(PydanticToPyArrowError):
    """Truncated recursive subtrees are serialized with `json.dumps`, which cannot encode `datetime.date` or `bytes`; cyclic type aliases without a `BaseModel` boundary cannot be finitely unrolled."""

    code = "E10"
    title = "Non-JSON-serializable type in a recursive model cycle or cyclic type alias"

InvalidDecimalConstraintsError

Bases: PydanticToPyArrowError

Arrow decimal128 requires 1 <= max_digits <= 38 and 0 <= decimal_places <= max_digits.

Source code in src/cdm_data_loaders/converters/facades/pydantic_to_pyarrow.py
class InvalidDecimalConstraintsError(PydanticToPyArrowError):
    """Arrow `decimal128` requires `1 <= max_digits <= 38` and `0 <= decimal_places <= max_digits`."""

    code = "E11"
    title = "Invalid or out-of-range `Decimal` precision/scale constraints"

ValueFieldCollisionError

Bases: PydanticToPyArrowError

The synthesized _value field for scalar union branches would collide with the model's declared _value field.

Source code in src/cdm_data_loaders/converters/facades/pydantic_to_pyarrow.py
class ValueFieldCollisionError(PydanticToPyArrowError):
    """The synthesized `_value` field for scalar union branches would collide with the model's declared `_value` field."""

    code = "E12"
    title = "`_value` collision in a scalar + model union"

PydanticToPyArrow pydantic-model

Bases: BaseModel

Facade converting Pydantic v2 BaseModel classes to PyArrow schemas and empty tables.

Show JSON schema:
{
  "description": "Facade converting Pydantic v2 ``BaseModel`` classes to PyArrow schemas and empty tables.",
  "properties": {
    "max_recursion_depth": {
      "default": 2,
      "exclusiveMinimum": 0,
      "title": "Max Recursion Depth",
      "type": "integer"
    },
    "by_alias": {
      "default": false,
      "title": "By Alias",
      "type": "boolean"
    }
  },
  "title": "PydanticToPyArrow",
  "type": "object"
}

Config:

  • arbitrary_types_allowed: True
  • frozen: True

Fields:

  • max_recursion_depth (PositiveInt)
  • by_alias (bool)
Source code in src/cdm_data_loaders/converters/facades/pydantic_to_pyarrow.py
class PydanticToPyArrow(BaseModel):
    """Facade converting Pydantic v2 ``BaseModel`` classes to PyArrow schemas and empty tables."""

    model_config = ConfigDict(arbitrary_types_allowed=True, frozen=True)

    max_recursion_depth: PositiveInt = Field(default=DEFAULT_MAX_RECURSION_DEPTH)
    by_alias: bool = False

    def _read(self, model: type[BaseModel]) -> SchemaDocument:
        """Read ``model`` into a converter IR ``SchemaDocument``.

        :param model: Concrete Pydantic ``BaseModel`` subclass.
        :type model: type[BaseModel]
        :return: Parsed ``SchemaDocument``.
        :rtype: SchemaDocument
        """
        return PydanticReader(
            max_recursion_depth=self.max_recursion_depth,
            by_alias=self.by_alias,
            error_type=PydanticToPyArrowError,
            error_by_code=ERROR_BY_CODE,
        ).read(model)

    def convert(self, model: type[BaseModel]) -> pa.Schema:
        """Convert a Pydantic ``BaseModel`` subclass to a ``pa.Schema``.

        :param model: concrete Pydantic ``BaseModel`` subclass
        :type model: type[BaseModel]
        :return: converted PyArrow schema
        :rtype: pa.Schema
        """
        return DEFAULT_EMITTER.emit(self._read(model))

    def convert_to_table(self, model: type[BaseModel]) -> pa.Table:
        """Convert a Pydantic ``BaseModel`` subclass to a zero-row ``pa.Table``.

        :param model: concrete Pydantic ``BaseModel`` subclass
        :type model: type[BaseModel]
        :return: zero-row PyArrow table with the converted schema
        :rtype: pa.Table
        """
        return DEFAULT_EMITTER.emit_table(self._read(model))

    def convert_to_python(self, model: type[BaseModel], *, docstring: str | None = None) -> str:
        """Convert a Pydantic ``BaseModel`` subclass to a static Python schema module.

        :param model: concrete Pydantic ``BaseModel`` subclass
        :type model: type[BaseModel]
        :param docstring: optional module docstring header
        :type docstring: str | None
        :return: formatted Python source code defining ``*_STRUCT``, ``*_SCHEMA``, ``SCHEMA``, and ``SCHEMAS``
        :rtype: str
        """
        return DEFAULT_EMITTER.emit_python(self._read(model), docstring=docstring)

    def convert_to_python_file(
        self,
        model: type[BaseModel],
        output: str | Path,
        *,
        docstring: str | None = None,
    ) -> Path:
        """Convert a Pydantic ``BaseModel`` subclass and write a static Python schema module to ``output``.

        :param model: concrete Pydantic ``BaseModel`` subclass
        :type model: type[BaseModel]
        :param output: destination file path for the generated Python module
        :type output: str | Path
        :param docstring: optional module docstring header
        :type docstring: str | None
        :return: resolved output ``Path``
        :rtype: Path
        """
        return DEFAULT_EMITTER.emit_python_file(self._read(model), output, docstring=docstring)

convert

convert(model: type[BaseModel]) -> pa.Schema

Convert a Pydantic BaseModel subclass to a pa.Schema.

Parameters:

Name Type Description Default
model type[BaseModel]

concrete Pydantic BaseModel subclass

required

Returns:

Type Description
pa.Schema

converted PyArrow schema

Source code in src/cdm_data_loaders/converters/facades/pydantic_to_pyarrow.py
def convert(self, model: type[BaseModel]) -> pa.Schema:
    """Convert a Pydantic ``BaseModel`` subclass to a ``pa.Schema``.

    :param model: concrete Pydantic ``BaseModel`` subclass
    :type model: type[BaseModel]
    :return: converted PyArrow schema
    :rtype: pa.Schema
    """
    return DEFAULT_EMITTER.emit(self._read(model))

convert_to_table

convert_to_table(model: type[BaseModel]) -> pa.Table

Convert a Pydantic BaseModel subclass to a zero-row pa.Table.

Parameters:

Name Type Description Default
model type[BaseModel]

concrete Pydantic BaseModel subclass

required

Returns:

Type Description
pa.Table

zero-row PyArrow table with the converted schema

Source code in src/cdm_data_loaders/converters/facades/pydantic_to_pyarrow.py
def convert_to_table(self, model: type[BaseModel]) -> pa.Table:
    """Convert a Pydantic ``BaseModel`` subclass to a zero-row ``pa.Table``.

    :param model: concrete Pydantic ``BaseModel`` subclass
    :type model: type[BaseModel]
    :return: zero-row PyArrow table with the converted schema
    :rtype: pa.Table
    """
    return DEFAULT_EMITTER.emit_table(self._read(model))

convert_to_python

convert_to_python(
    model: type[BaseModel], *, docstring: str | None = None
) -> str

Convert a Pydantic BaseModel subclass to a static Python schema module.

Parameters:

Name Type Description Default
model type[BaseModel]

concrete Pydantic BaseModel subclass

required
docstring str | None

optional module docstring header

None

Returns:

Type Description
str

formatted Python source code defining *_STRUCT, *_SCHEMA, SCHEMA, and SCHEMAS

Source code in src/cdm_data_loaders/converters/facades/pydantic_to_pyarrow.py
def convert_to_python(self, model: type[BaseModel], *, docstring: str | None = None) -> str:
    """Convert a Pydantic ``BaseModel`` subclass to a static Python schema module.

    :param model: concrete Pydantic ``BaseModel`` subclass
    :type model: type[BaseModel]
    :param docstring: optional module docstring header
    :type docstring: str | None
    :return: formatted Python source code defining ``*_STRUCT``, ``*_SCHEMA``, ``SCHEMA``, and ``SCHEMAS``
    :rtype: str
    """
    return DEFAULT_EMITTER.emit_python(self._read(model), docstring=docstring)

convert_to_python_file

convert_to_python_file(
    model: type[BaseModel],
    output: str | Path,
    *,
    docstring: str | None = None
) -> Path

Convert a Pydantic BaseModel subclass and write a static Python schema module to output.

Parameters:

Name Type Description Default
model type[BaseModel]

concrete Pydantic BaseModel subclass

required
output str | Path

destination file path for the generated Python module

required
docstring str | None

optional module docstring header

None

Returns:

Type Description
Path

resolved output Path

Source code in src/cdm_data_loaders/converters/facades/pydantic_to_pyarrow.py
def convert_to_python_file(
    self,
    model: type[BaseModel],
    output: str | Path,
    *,
    docstring: str | None = None,
) -> Path:
    """Convert a Pydantic ``BaseModel`` subclass and write a static Python schema module to ``output``.

    :param model: concrete Pydantic ``BaseModel`` subclass
    :type model: type[BaseModel]
    :param output: destination file path for the generated Python module
    :type output: str | Path
    :param docstring: optional module docstring header
    :type docstring: str | None
    :return: resolved output ``Path``
    :rtype: Path
    """
    return DEFAULT_EMITTER.emit_python_file(self._read(model), output, docstring=docstring)

resolve_scalar_type

resolve_scalar_type(
    annotation: object,
) -> tuple[pa.DataType, str]

Map a scalar Python / Pydantic type annotation to its PyArrow DataType and pydantic_type label.

Parameters:

Name Type Description Default
annotation object

scalar type annotation (e.g. int, AwareDatetime, Annotated[Decimal, Field(...)])

required

Returns:

Type Description
tuple[pa.DataType, str]

(pa.DataType, pydantic_type)

Raises:

Type Description
PydanticToPyArrowError

if annotation is not a supported scalar type

Source code in src/cdm_data_loaders/converters/facades/pydantic_to_pyarrow.py
def resolve_scalar_type(annotation: object) -> tuple[pa.DataType, str]:
    """Map a scalar Python / Pydantic type annotation to its PyArrow DataType and ``pydantic_type`` label.

    :param annotation: scalar type annotation (e.g. ``int``, ``AwareDatetime``, ``Annotated[Decimal, Field(...)]``)
    :type annotation: object
    :return: ``(pa.DataType, pydantic_type)``
    :rtype: tuple[pa.DataType, str]
    :raises PydanticToPyArrowError: if ``annotation`` is not a supported scalar type
    """
    node = PydanticReader(
        error_type=PydanticToPyArrowError,
        error_by_code=ERROR_BY_CODE,
    ).read_scalar_annotation(annotation)
    dtype = DEFAULT_EMITTER.emit_node(node)
    pyd_type = str(node.provenance.metadata["pydantic_type"]) if node.provenance else ""
    return dtype, pyd_type

generate_pyarrow_schema

generate_pyarrow_schema(
    model: type[BaseModel],
    *,
    max_recursion_depth: int = DEFAULT_MAX_RECURSION_DEPTH,
    by_alias: bool = False
) -> pa.Schema

Generate a deterministic PyArrow Schema from a Pydantic v2 BaseModel subclass.

Parameters:

Name Type Description Default
model type[BaseModel]

concrete Pydantic BaseModel subclass

required
max_recursion_depth int

maximum expansion depth for recursive BaseModel references

DEFAULT_MAX_RECURSION_DEPTH
by_alias bool

when True, use serialization_alias or alias for field names

False

Returns:

Type Description
pa.Schema

PyArrow schema with top-level fields corresponding to model fields

Raises:

Type Description
PydanticToPyArrowError

if model is invalid or uses an unsupported construct

Source code in src/cdm_data_loaders/converters/facades/pydantic_to_pyarrow.py
def generate_pyarrow_schema(
    model: type[BaseModel],
    *,
    max_recursion_depth: int = DEFAULT_MAX_RECURSION_DEPTH,
    by_alias: bool = False,
) -> pa.Schema:
    """Generate a deterministic PyArrow Schema from a Pydantic v2 ``BaseModel`` subclass.

    :param model: concrete Pydantic ``BaseModel`` subclass
    :type model: type[BaseModel]
    :param max_recursion_depth: maximum expansion depth for recursive ``BaseModel`` references
    :type max_recursion_depth: int
    :param by_alias: when ``True``, use ``serialization_alias`` or ``alias`` for field names
    :type by_alias: bool
    :return: PyArrow schema with top-level fields corresponding to model fields
    :rtype: pa.Schema
    :raises PydanticToPyArrowError: if ``model`` is invalid or uses an unsupported construct
    """
    doc = PydanticReader(
        max_recursion_depth=max_recursion_depth,
        by_alias=by_alias,
        error_type=PydanticToPyArrowError,
        error_by_code=ERROR_BY_CODE,
    ).read(model)
    return DEFAULT_EMITTER.emit(doc)