class DataType(Annotable, Comparable): """Base class for all data types. [`DataType`][ibis.expr.datatypes.DataType] instances are immutable. """ nullable = optional(instance_of(bool), default=True) def __call__(self, nullable: bool = True) -> DataType: if nullable is not True and nullable is not False: raise TypeError( "__call__ only accepts the 'nullable' argument. " "Please construct a new instance of the type to change the " "values of the attributes." ) kwargs = dict(zip(self.argnames, self.args)) kwargs["nullable"] = nullable return self.__class__(**kwargs) @property def _pretty_piece(self) -> str: return "" @property def name(self) -> str: """Return the name of the data type.""" return self.__class__.__name__ def __str__(self) -> str: prefix = "!" * (not self.nullable) return f"{prefix}{self.name.lower()}{self._pretty_piece}" def __equals__( self, other: typing.Any, ) -> bool: return self.args == other.args def equals(self, other): if not isinstance(other, DataType): raise TypeError( "invalid equality comparison between DataType and " f"{type(other)}" ) return super().__cached_equals__(other) def castable(self, target, **kwargs): """Return whether this data type is castable to `target`.""" return castable(self, target, **kwargs) def cast(self, target, **kwargs): """Cast this data type to `target`.""" return cast(self, target, **kwargs)
class GeoSpatial(DataType): """Geospatial values.""" geotype = optional(isin({"geography", "geometry"})) """The specific geospatial type""" srid = optional(instance_of(int)) """The spatial reference identifier.""" column = ir.GeoSpatialColumn scalar = ir.GeoSpatialScalar @property def _pretty_piece(self) -> str: piece = "" if self.geotype is not None: piece += f":{self.geotype}" if self.srid is not None: piece += f";{self.srid}" return piece
class Timestamp(DataType): """Timestamp values.""" timezone = optional(instance_of(str)) """The timezone of values of this type.""" scalar = ir.TimestampScalar column = ir.TimestampColumn @property def _pretty_piece(self) -> str: if (timezone := self.timezone) is not None: return f"({timezone!r})" return ""
class Category(DataType): cardinality = optional(instance_of(int)) scalar = ir.CategoryScalar column = ir.CategoryColumn def __repr__(self): if self.cardinality is not None: cardinality = repr(self.cardinality) else: cardinality = "unknown" return f"{self.name}(cardinality={cardinality})" def to_integer_type(self): if self.cardinality is None: return int64 else: return infer(self.cardinality)
class Interval(DataType): """Interval values.""" unit = optional( map_to( { 'days': 'D', 'hours': 'h', 'minutes': 'm', 'seconds': 's', 'milliseconds': 'ms', 'microseconds': 'us', 'nanoseconds': 'ns', 'Y': 'Y', 'Q': 'Q', 'M': 'M', 'W': 'W', 'D': 'D', 'h': 'h', 'm': 'm', 's': 's', 'ms': 'ms', 'us': 'us', 'ns': 'ns', } ), default="s", ) """The time unit of the interval.""" value_type = optional( compose_of([datatype, instance_of(Integer)]), default=Int32() ) """The underlying type of the stored values.""" scalar = ir.IntervalScalar column = ir.IntervalColumn # based on numpy's units _units = { 'Y': 'year', 'Q': 'quarter', 'M': 'month', 'W': 'week', 'D': 'day', 'h': 'hour', 'm': 'minute', 's': 'second', 'ms': 'millisecond', 'us': 'microsecond', 'ns': 'nanosecond', } # TODO(kszucs): assert that the nullability if the value_type is equal # to the interval's nullability @property def bounds(self): return self.value_type.bounds @property def resolution(self): """The interval unit's name.""" return self._units[self.unit] @property def _pretty_piece(self) -> str: return f"<{self.value_type}>(unit={self.unit!r})"
class Decimal(DataType): """Fixed-precision decimal values.""" precision = optional(instance_of(int)) """The number of decimal places values of this type can hold.""" scale = optional(instance_of(int)) """The number of values after the decimal point.""" scalar = ir.DecimalScalar column = ir.DecimalColumn def __init__( self, precision: int | None = None, scale: int | None = None, **kwargs: Any, ) -> None: if precision is not None: if not isinstance(precision, numbers.Integral): raise TypeError( "Decimal type precision must be an integer; " f"got {type(precision)}" ) if precision < 0: raise ValueError('Decimal type precision cannot be negative') if not precision: raise ValueError('Decimal type precision cannot be zero') if scale is not None: if not isinstance(scale, numbers.Integral): raise TypeError('Decimal type scale must be an integer') if scale < 0: raise ValueError('Decimal type scale cannot be negative') if precision is not None and precision < scale: raise ValueError( 'Decimal type precision must be greater than or equal to ' 'scale. Got precision={:d} and scale={:d}'.format( precision, scale ) ) super().__init__(precision=precision, scale=scale, **kwargs) @property def largest(self): """Return the largest type of decimal.""" return self.__class__( precision=max(self.precision, 38) if self.precision is not None else None, scale=max(self.scale, 2) if self.scale is not None else None, ) @property def _pretty_piece(self) -> str: args = [] if (precision := self.precision) is not None: args.append(f"prec={precision:d}") if (scale := self.scale) is not None: args.append(f"scale={scale:d}")