API refactor
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
2025-10-07 16:25:52 +09:00
parent 76d0d86211
commit 91c7e04474
1171 changed files with 81940 additions and 44117 deletions

View File

@@ -1,4 +1,5 @@
# Copyright (C) 2013-2023 the SQLAlchemy authors and contributors
# dialects/postgresql/ranges.py
# Copyright (C) 2013-2025 the SQLAlchemy authors and contributors
# <see AUTHORS file>
#
# This module is part of SQLAlchemy and is released under
@@ -14,8 +15,10 @@ from decimal import Decimal
from typing import Any
from typing import cast
from typing import Generic
from typing import List
from typing import Optional
from typing import overload
from typing import Sequence
from typing import Tuple
from typing import Type
from typing import TYPE_CHECKING
@@ -151,8 +154,8 @@ class Range(Generic[_T]):
return not self.empty and self.upper is None
@property
def __sa_type_engine__(self) -> AbstractRange[Range[_T]]:
return AbstractRange()
def __sa_type_engine__(self) -> AbstractSingleRange[_T]:
return AbstractSingleRange()
def _contains_value(self, value: _T) -> bool:
"""Return True if this range contains the given value."""
@@ -268,9 +271,9 @@ class Range(Generic[_T]):
value2 += step
value2_inc = False
if value1 < value2: # type: ignore
if value1 < value2:
return -1
elif value1 > value2: # type: ignore
elif value1 > value2:
return 1
elif only_values:
return 0
@@ -357,6 +360,8 @@ class Range(Generic[_T]):
else:
return self._contains_value(value)
__contains__ = contains
def overlaps(self, other: Range[_T]) -> bool:
"Determine whether this range overlaps with `other`."
@@ -707,27 +712,46 @@ class Range(Generic[_T]):
return f"{b0}{l},{r}{b1}"
class AbstractRange(sqltypes.TypeEngine[Range[_T]]):
"""
Base for PostgreSQL RANGE types.
class MultiRange(List[Range[_T]]):
"""Represents a multirange sequence.
This list subclass is an utility to allow automatic type inference of
the proper multi-range SQL type depending on the single range values.
This is useful when operating on literal multi-ranges::
import sqlalchemy as sa
from sqlalchemy.dialects.postgresql import MultiRange, Range
value = literal(MultiRange([Range(2, 4)]))
select(tbl).where(tbl.c.value.op("@")(MultiRange([Range(-3, 7)])))
.. versionadded:: 2.0.26
.. seealso::
`PostgreSQL range functions <https://www.postgresql.org/docs/current/static/functions-range.html>`_
- :ref:`postgresql_multirange_list_use`.
"""
""" # noqa: E501
@property
def __sa_type_engine__(self) -> AbstractMultiRange[_T]:
return AbstractMultiRange()
class AbstractRange(sqltypes.TypeEngine[_T]):
"""Base class for single and multi Range SQL types."""
render_bind_cast = True
__abstract__ = True
@overload
def adapt(self, cls: Type[_TE], **kw: Any) -> _TE:
...
def adapt(self, cls: Type[_TE], **kw: Any) -> _TE: ...
@overload
def adapt(self, cls: Type[TypeEngineMixin], **kw: Any) -> TypeEngine[Any]:
...
def adapt(
self, cls: Type[TypeEngineMixin], **kw: Any
) -> TypeEngine[Any]: ...
def adapt(
self,
@@ -741,7 +765,10 @@ class AbstractRange(sqltypes.TypeEngine[Range[_T]]):
and also render as ``INT4RANGE`` in SQL and DDL.
"""
if issubclass(cls, AbstractRangeImpl) and cls is not self.__class__:
if (
issubclass(cls, (AbstractSingleRangeImpl, AbstractMultiRangeImpl))
and cls is not self.__class__
):
# two ways to do this are: 1. create a new type on the fly
# or 2. have AbstractRangeImpl(visit_name) constructor and a
# visit_abstract_range_impl() method in the PG compiler.
@@ -760,21 +787,6 @@ class AbstractRange(sqltypes.TypeEngine[Range[_T]]):
else:
return super().adapt(cls)
def _resolve_for_literal(self, value: Any) -> Any:
spec = value.lower if value.lower is not None else value.upper
if isinstance(spec, int):
return INT8RANGE()
elif isinstance(spec, (Decimal, float)):
return NUMRANGE()
elif isinstance(spec, datetime):
return TSRANGE() if not spec.tzinfo else TSTZRANGE()
elif isinstance(spec, date):
return DATERANGE()
else:
# empty Range, SQL datatype can't be determined here
return sqltypes.NULLTYPE
class comparator_factory(TypeEngine.Comparator[Range[Any]]):
"""Define comparison operations for range types."""
@@ -856,91 +868,164 @@ class AbstractRange(sqltypes.TypeEngine[Range[_T]]):
return self.expr.operate(operators.mul, other)
class AbstractRangeImpl(AbstractRange[Range[_T]]):
"""Marker for AbstractRange that will apply a subclass-specific
adaptation"""
class AbstractSingleRange(AbstractRange[Range[_T]]):
"""Base for PostgreSQL RANGE types.
These are types that return a single :class:`_postgresql.Range` object.
class AbstractMultiRange(AbstractRange[Range[_T]]):
"""base for PostgreSQL MULTIRANGE types"""
.. seealso::
`PostgreSQL range functions <https://www.postgresql.org/docs/current/static/functions-range.html>`_
""" # noqa: E501
__abstract__ = True
def _resolve_for_literal(self, value: Range[Any]) -> Any:
spec = value.lower if value.lower is not None else value.upper
class AbstractMultiRangeImpl(
AbstractRangeImpl[Range[_T]], AbstractMultiRange[Range[_T]]
):
"""Marker for AbstractRange that will apply a subclass-specific
if isinstance(spec, int):
# pg is unreasonably picky here: the query
# "select 1::INTEGER <@ '[1, 4)'::INT8RANGE" raises
# "operator does not exist: integer <@ int8range" as of pg 16
if _is_int32(value):
return INT4RANGE()
else:
return INT8RANGE()
elif isinstance(spec, (Decimal, float)):
return NUMRANGE()
elif isinstance(spec, datetime):
return TSRANGE() if not spec.tzinfo else TSTZRANGE()
elif isinstance(spec, date):
return DATERANGE()
else:
# empty Range, SQL datatype can't be determined here
return sqltypes.NULLTYPE
class AbstractSingleRangeImpl(AbstractSingleRange[_T]):
"""Marker for AbstractSingleRange that will apply a subclass-specific
adaptation"""
class INT4RANGE(AbstractRange[Range[int]]):
class AbstractMultiRange(AbstractRange[Sequence[Range[_T]]]):
"""Base for PostgreSQL MULTIRANGE types.
these are types that return a sequence of :class:`_postgresql.Range`
objects.
"""
__abstract__ = True
def _resolve_for_literal(self, value: Sequence[Range[Any]]) -> Any:
if not value:
# empty MultiRange, SQL datatype can't be determined here
return sqltypes.NULLTYPE
first = value[0]
spec = first.lower if first.lower is not None else first.upper
if isinstance(spec, int):
# pg is unreasonably picky here: the query
# "select 1::INTEGER <@ '{[1, 4),[6,19)}'::INT8MULTIRANGE" raises
# "operator does not exist: integer <@ int8multirange" as of pg 16
if all(_is_int32(r) for r in value):
return INT4MULTIRANGE()
else:
return INT8MULTIRANGE()
elif isinstance(spec, (Decimal, float)):
return NUMMULTIRANGE()
elif isinstance(spec, datetime):
return TSMULTIRANGE() if not spec.tzinfo else TSTZMULTIRANGE()
elif isinstance(spec, date):
return DATEMULTIRANGE()
else:
# empty Range, SQL datatype can't be determined here
return sqltypes.NULLTYPE
class AbstractMultiRangeImpl(AbstractMultiRange[_T]):
"""Marker for AbstractMultiRange that will apply a subclass-specific
adaptation"""
class INT4RANGE(AbstractSingleRange[int]):
"""Represent the PostgreSQL INT4RANGE type."""
__visit_name__ = "INT4RANGE"
class INT8RANGE(AbstractRange[Range[int]]):
class INT8RANGE(AbstractSingleRange[int]):
"""Represent the PostgreSQL INT8RANGE type."""
__visit_name__ = "INT8RANGE"
class NUMRANGE(AbstractRange[Range[Decimal]]):
class NUMRANGE(AbstractSingleRange[Decimal]):
"""Represent the PostgreSQL NUMRANGE type."""
__visit_name__ = "NUMRANGE"
class DATERANGE(AbstractRange[Range[date]]):
class DATERANGE(AbstractSingleRange[date]):
"""Represent the PostgreSQL DATERANGE type."""
__visit_name__ = "DATERANGE"
class TSRANGE(AbstractRange[Range[datetime]]):
class TSRANGE(AbstractSingleRange[datetime]):
"""Represent the PostgreSQL TSRANGE type."""
__visit_name__ = "TSRANGE"
class TSTZRANGE(AbstractRange[Range[datetime]]):
class TSTZRANGE(AbstractSingleRange[datetime]):
"""Represent the PostgreSQL TSTZRANGE type."""
__visit_name__ = "TSTZRANGE"
class INT4MULTIRANGE(AbstractMultiRange[Range[int]]):
class INT4MULTIRANGE(AbstractMultiRange[int]):
"""Represent the PostgreSQL INT4MULTIRANGE type."""
__visit_name__ = "INT4MULTIRANGE"
class INT8MULTIRANGE(AbstractMultiRange[Range[int]]):
class INT8MULTIRANGE(AbstractMultiRange[int]):
"""Represent the PostgreSQL INT8MULTIRANGE type."""
__visit_name__ = "INT8MULTIRANGE"
class NUMMULTIRANGE(AbstractMultiRange[Range[Decimal]]):
class NUMMULTIRANGE(AbstractMultiRange[Decimal]):
"""Represent the PostgreSQL NUMMULTIRANGE type."""
__visit_name__ = "NUMMULTIRANGE"
class DATEMULTIRANGE(AbstractMultiRange[Range[date]]):
class DATEMULTIRANGE(AbstractMultiRange[date]):
"""Represent the PostgreSQL DATEMULTIRANGE type."""
__visit_name__ = "DATEMULTIRANGE"
class TSMULTIRANGE(AbstractMultiRange[Range[datetime]]):
class TSMULTIRANGE(AbstractMultiRange[datetime]):
"""Represent the PostgreSQL TSRANGE type."""
__visit_name__ = "TSMULTIRANGE"
class TSTZMULTIRANGE(AbstractMultiRange[Range[datetime]]):
class TSTZMULTIRANGE(AbstractMultiRange[datetime]):
"""Represent the PostgreSQL TSTZRANGE type."""
__visit_name__ = "TSTZMULTIRANGE"
_max_int_32 = 2**31 - 1
_min_int_32 = -(2**31)
def _is_int32(r: Range[int]) -> bool:
return (r.lower is None or _min_int_32 <= r.lower <= _max_int_32) and (
r.upper is None or _min_int_32 <= r.upper <= _max_int_32
)