""" The `pydantic_extra_types.coordinate` module provides the [`Latitude`][pydantic_extra_types.coordinate.Latitude], [`Longitude`][pydantic_extra_types.coordinate.Longitude], and [`Coordinate`][pydantic_extra_types.coordinate.Coordinate] data types. """ from dataclasses import dataclass from typing import Any, ClassVar, Tuple, Type from pydantic import GetCoreSchemaHandler from pydantic._internal import _repr from pydantic_core import ArgsKwargs, PydanticCustomError, core_schema class Latitude(float): """Latitude value should be between -90 and 90, inclusive. ```py from pydantic import BaseModel from pydantic_extra_types.coordinate import Latitude class Location(BaseModel): latitude: Latitude location = Location(latitude=41.40338) print(location) #> latitude=41.40338 ``` """ min: ClassVar[float] = -90.00 max: ClassVar[float] = 90.00 @classmethod def __get_pydantic_core_schema__(cls, source: Type[Any], handler: GetCoreSchemaHandler) -> core_schema.CoreSchema: return core_schema.float_schema(ge=cls.min, le=cls.max) class Longitude(float): """Longitude value should be between -180 and 180, inclusive. ```py from pydantic import BaseModel from pydantic_extra_types.coordinate import Longitude class Location(BaseModel): longitude: Longitude location = Location(longitude=2.17403) print(location) #> longitude=2.17403 ``` """ min: ClassVar[float] = -180.00 max: ClassVar[float] = 180.00 @classmethod def __get_pydantic_core_schema__(cls, source: Type[Any], handler: GetCoreSchemaHandler) -> core_schema.CoreSchema: return core_schema.float_schema(ge=cls.min, le=cls.max) @dataclass class Coordinate(_repr.Representation): """Coordinate parses Latitude and Longitude. You can use the `Coordinate` data type for storing coordinates. Coordinates can be defined using one of the following formats: 1. Tuple: `(Latitude, Longitude)`. For example: `(41.40338, 2.17403)`. 2. `Coordinate` instance: `Coordinate(latitude=Latitude, longitude=Longitude)`. ```py from pydantic import BaseModel from pydantic_extra_types.coordinate import Coordinate class Location(BaseModel): coordinate: Coordinate location = Location(coordinate=(41.40338, 2.17403)) #> coordinate=Coordinate(latitude=41.40338, longitude=2.17403) ``` """ _NULL_ISLAND: ClassVar[Tuple[float, float]] = (0.0, 0.0) latitude: Latitude longitude: Longitude @classmethod def __get_pydantic_core_schema__(cls, source: Type[Any], handler: GetCoreSchemaHandler) -> core_schema.CoreSchema: schema_chain = [ core_schema.no_info_wrap_validator_function(cls._parse_str, core_schema.str_schema()), core_schema.no_info_wrap_validator_function( cls._parse_tuple, handler.generate_schema(Tuple[float, float]), ), handler(source), ] chain_length = len(schema_chain) chain_schemas = [core_schema.chain_schema(schema_chain[x:]) for x in range(chain_length - 1, -1, -1)] return core_schema.no_info_wrap_validator_function( cls._parse_args, core_schema.union_schema(chain_schemas), # type: ignore[arg-type] ) @classmethod def _parse_args(cls, value: Any, handler: core_schema.ValidatorFunctionWrapHandler) -> Any: if isinstance(value, ArgsKwargs) and not value.kwargs: n_args = len(value.args) if n_args == 0: value = cls._NULL_ISLAND elif n_args == 1: value = value.args[0] return handler(value) @classmethod def _parse_str(cls, value: Any, handler: core_schema.ValidatorFunctionWrapHandler) -> Any: if not isinstance(value, str): return value try: value = tuple(float(x) for x in value.split(',')) except ValueError: raise PydanticCustomError( 'coordinate_error', 'value is not a valid coordinate: string is not recognized as a valid coordinate', ) return ArgsKwargs(args=value) @classmethod def _parse_tuple(cls, value: Any, handler: core_schema.ValidatorFunctionWrapHandler) -> Any: if not isinstance(value, tuple): return value return ArgsKwargs(args=handler(value)) def __str__(self) -> str: return f'{self.latitude},{self.longitude}' def __eq__(self, other: Any) -> bool: return isinstance(other, Coordinate) and self.latitude == other.latitude and self.longitude == other.longitude def __hash__(self) -> int: return hash((self.latitude, self.longitude))