Source code for pydsstools.core.gridinfo.factory

"""Factory function for creating grid metadata objects.

This module provides the GridInfoCreate factory function which automatically
selects and instantiates the appropriate grid info class based on grid_type.
"""

from typing import Union

try: # python 3.10+
    from typing import Annotated
except ImportError:
    from typing_extensions import Annotated

from pydantic import Field, TypeAdapter

from .undefined import GridInfo
from .hrap import HrapInfo
from .albers import AlbersInfo
from .specified import SpecifiedInfo
from .base import GridInfoBase

__all__ = ["GridInfoCreate", "GridInfoUnionType"]


# Type union for discriminated validation
GridInfoUnionType = Annotated[
    Union[GridInfo, HrapInfo, AlbersInfo, SpecifiedInfo],
    Field(discriminator="grid_type"),
]


[docs] def GridInfoCreate(**kwargs) -> GridInfoBase: """Factory function to create appropriate GridInfo subclass. Automatically selects the correct GridInfo subclass (GridInfo, HrapInfo, AlbersInfo, or SpecifiedInfo) based on the grid_type parameter. This is the recommended way to create grid metadata objects as it: - Provides type validation - Ensures correct class selection - Validates all required fields - Applies default values Parameters ---------- **kwargs Keyword arguments for grid metadata. Must include 'grid_type'. Other required/optional parameters depend on the grid type. Returns ------- GridInfoBase Instance of GridInfo, HrapInfo, AlbersInfo, or SpecifiedInfo. Raises ------ TypeError If no keyword arguments provided. ValidationError If required fields are missing or invalid. Examples -------- Create HRAP grid metadata: >>> from pydsstools.core.gridinfo import GridInfoCreate, GridType, DataType >>> info = GridInfoCreate( ... grid_type=GridType.hrap, ... data_type=DataType.per_aver, ... shape=(100, 150), ... cell_size=4762.5, ... data_units="MM", ... data_source="NEXRAD" ... ) >>> type(info).__name__ 'HrapInfo' Create Albers/SHG grid metadata: >>> info = GridInfoCreate( ... grid_type=GridType.albers, ... data_type=DataType.inst_val, ... shape=(500, 700), ... cell_size=2000.0, ... proj_datum=Datum.nad83, ... lat_0=23.0, ... lat_1=29.5, ... lat_2=45.5, ... lon_0=-96.0, ... min_xy=(-1500000, 500000), ... data_units="M" ... ) >>> type(info).__name__ 'AlbersInfo' Create specified grid with custom CRS: >>> info = GridInfoCreate( ... grid_type=GridType.specified, ... data_type=DataType.per_aver, ... shape=(200, 300), ... cell_size=1000.0, ... crs="EPSG:32610", ... crs_name="WGS84 / UTM zone 10N", ... nodata=-9999.0, ... min_xy=(500000, 4000000), ... data_units="MM" ... ) >>> type(info).__name__ 'SpecifiedInfo' Create undefined/basic grid: >>> info = GridInfoCreate( ... grid_type=GridType.undefined, ... data_type=DataType.inst_val, ... shape=(100, 100), ... cell_size=1000.0, ... data_units="M" ... ) >>> type(info).__name__ 'GridInfo' With extra fields: >>> info = GridInfoCreate( ... grid_type=GridType.hrap, ... data_type=DataType.per_aver, ... shape=(100, 150), ... cell_size=4762.5, ... data_units="MM", ... custom_field="custom_value", # Goes to extra dict ... another_field=123 ... ) >>> info.extra_info['custom_field'] 'custom_value' Notes ----- The factory uses Pydantic's discriminated union feature to automatically select the correct class based on grid_type. This provides: - Better error messages for invalid fields - Type-specific validation - Clear separation of grid type concerns Grid Type Mapping: - GridType.undefined or undefined_time → GridInfo - GridType.hrap or hrap_time → HrapInfo - GridType.albers or albers_time → AlbersInfo - GridType.specified or specified_time → SpecifiedInfo """ if not kwargs: raise TypeError( "Provide keyword arguments to create GridInfo " "(e.g., grid_type=GridType.hrap, ...)" ) # Use Pydantic TypeAdapter for discriminated validation adapter = TypeAdapter(GridInfoUnionType) return adapter.validate_python(kwargs)