Source code for gfw.common.bigquery.schema
"""Utilities for converting BigQuery schemas to other formats."""
from __future__ import annotations
import json
from pathlib import Path
from typing import Any, Dict
import pyarrow as pa
from google.cloud import bigquery
_BIGQUERY_TO_PYARROW_TYPE_MAPPING: dict[str, pa.DataType] = {
"STRING": pa.string(),
"BYTES": pa.binary(),
"INTEGER": pa.int64(),
"INT64": pa.int64(),
"FLOAT": pa.float64(),
"FLOAT64": pa.float64(),
"BOOLEAN": pa.bool_(),
"BOOL": pa.bool_(),
"TIMESTAMP": pa.timestamp("s"),
"DATE": pa.date32(),
"DATETIME": pa.timestamp("s"),
"TIME": pa.time32("s"),
"NUMERIC": pa.decimal128(38, 9),
"BIGNUMERIC": pa.decimal256(76, 38),
"JSON": pa.string(),
}
def _convert_schema_field(field: bigquery.SchemaField) -> pa.Field:
if field.field_type == "RECORD":
pa_type: pa.DataType = pa.struct([_convert_schema_field(f) for f in field.fields])
elif field.field_type in _BIGQUERY_TO_PYARROW_TYPE_MAPPING:
pa_type = _BIGQUERY_TO_PYARROW_TYPE_MAPPING[field.field_type]
else:
raise ValueError(f"Unsupported BigQuery type: {field.field_type!r}")
if field.mode == "REPEATED":
pa_type = pa.list_(pa_type)
return pa.field(field.name, pa_type, nullable=field.mode != "REQUIRED")
[docs]
class Schema:
"""A BigQuery schema with conversion utilities.
Wraps a list of :class:`~google.cloud.bigquery.SchemaField` objects and provides
methods to convert to other formats.
Args:
fields:
List of BigQuery schema fields.
"""
def __init__(self, fields: list[bigquery.SchemaField]) -> None:
self._fields = fields
[docs]
@classmethod
def from_dicts(cls, dicts: list[dict[str, Any]]) -> Schema:
"""Construct a :class:`Schema` from a list of BigQuery field descriptor dicts.
Args:
dicts:
List of BigQuery field descriptors, each with at minimum ``name``
and ``type`` keys. Supports optional ``mode`` and ``fields`` for
nested ``RECORD`` types.
Returns:
A :class:`Schema` wrapping the parsed fields.
"""
return cls([bigquery.SchemaField.from_api_repr(f) for f in dicts])
[docs]
@classmethod
def from_json(cls, path: str | Path) -> Schema:
"""Load a BigQuery JSON schema file and return a :class:`Schema`.
Args:
path:
Path to a JSON file containing a list of BigQuery field descriptors.
Returns:
A :class:`Schema` wrapping the parsed fields.
"""
return cls.from_dicts(json.loads(Path(path).read_text()))
@property
def fields(self) -> list[bigquery.SchemaField]:
"""The raw BigQuery :class:`~google.cloud.bigquery.SchemaField` list."""
return self._fields
[docs]
def as_dicts(self) -> list[Dict[str, Any]]:
"""Return the schema as a list of BigQuery field descriptor dicts.
Useful when passing a schema to interfaces that expect the JSON dict
format (e.g. :class:`~gfw.common.beam.transforms.bigquery.WriteToBigQueryWrapper`).
"""
return [f.to_api_repr() for f in self._fields]
[docs]
def as_pyarrow(self) -> pa.Schema:
"""Convert to a :class:`pyarrow.Schema`."""
return pa.schema([_convert_schema_field(f) for f in self._fields])