This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
# orm/properties.py
|
||||
# Copyright (C) 2005-2023 the SQLAlchemy authors and contributors
|
||||
# Copyright (C) 2005-2025 the SQLAlchemy authors and contributors
|
||||
# <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
@@ -28,6 +28,7 @@ from typing import TypeVar
|
||||
from typing import Union
|
||||
|
||||
from . import attributes
|
||||
from . import exc as orm_exc
|
||||
from . import strategy_options
|
||||
from .base import _DeclarativeMapped
|
||||
from .base import class_mapper
|
||||
@@ -43,7 +44,6 @@ from .interfaces import PropComparator
|
||||
from .interfaces import StrategizedProperty
|
||||
from .relationships import RelationshipProperty
|
||||
from .util import de_stringify_annotation
|
||||
from .util import de_stringify_union_elements
|
||||
from .. import exc as sa_exc
|
||||
from .. import ForeignKey
|
||||
from .. import log
|
||||
@@ -55,12 +55,13 @@ from ..sql.schema import Column
|
||||
from ..sql.schema import SchemaConst
|
||||
from ..sql.type_api import TypeEngine
|
||||
from ..util.typing import de_optionalize_union_types
|
||||
from ..util.typing import get_args
|
||||
from ..util.typing import includes_none
|
||||
from ..util.typing import is_a_type
|
||||
from ..util.typing import is_fwd_ref
|
||||
from ..util.typing import is_optional_union
|
||||
from ..util.typing import is_pep593
|
||||
from ..util.typing import is_union
|
||||
from ..util.typing import is_pep695
|
||||
from ..util.typing import Self
|
||||
from ..util.typing import typing_get_args
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ._typing import _IdentityKeyType
|
||||
@@ -233,7 +234,7 @@ class ColumnProperty(
|
||||
return self.strategy._have_default_expression # type: ignore
|
||||
|
||||
return ("deferred", True) not in self.strategy_key or (
|
||||
self not in self.parent._readonly_props # type: ignore
|
||||
self not in self.parent._readonly_props
|
||||
)
|
||||
|
||||
@util.preload_module("sqlalchemy.orm.state", "sqlalchemy.orm.strategies")
|
||||
@@ -279,8 +280,8 @@ class ColumnProperty(
|
||||
|
||||
name = Column(String(64))
|
||||
extension = Column(String(8))
|
||||
filename = column_property(name + '.' + extension)
|
||||
path = column_property('C:/' + filename.expression)
|
||||
filename = column_property(name + "." + extension)
|
||||
path = column_property("C:/" + filename.expression)
|
||||
|
||||
.. seealso::
|
||||
|
||||
@@ -429,8 +430,7 @@ class ColumnProperty(
|
||||
|
||||
if TYPE_CHECKING:
|
||||
|
||||
def __clause_element__(self) -> NamedColumn[_PT]:
|
||||
...
|
||||
def __clause_element__(self) -> NamedColumn[_PT]: ...
|
||||
|
||||
def _memoized_method___clause_element__(
|
||||
self,
|
||||
@@ -636,9 +636,11 @@ class MappedColumn(
|
||||
return [
|
||||
(
|
||||
self.column,
|
||||
self._sort_order
|
||||
if self._sort_order is not _NoArg.NO_ARG
|
||||
else 0,
|
||||
(
|
||||
self._sort_order
|
||||
if self._sort_order is not _NoArg.NO_ARG
|
||||
else 0
|
||||
),
|
||||
)
|
||||
]
|
||||
|
||||
@@ -661,6 +663,31 @@ class MappedColumn(
|
||||
# Column will be merged into it in _init_column_for_annotation().
|
||||
return MappedColumn()
|
||||
|
||||
def _adjust_for_existing_column(
|
||||
self,
|
||||
decl_scan: _ClassScanMapperConfig,
|
||||
key: str,
|
||||
given_column: Column[_T],
|
||||
) -> Column[_T]:
|
||||
if (
|
||||
self._use_existing_column
|
||||
and decl_scan.inherits
|
||||
and decl_scan.single
|
||||
):
|
||||
if decl_scan.is_deferred:
|
||||
raise sa_exc.ArgumentError(
|
||||
"Can't use use_existing_column with deferred mappers"
|
||||
)
|
||||
supercls_mapper = class_mapper(decl_scan.inherits, False)
|
||||
|
||||
colname = (
|
||||
given_column.name if given_column.name is not None else key
|
||||
)
|
||||
given_column = supercls_mapper.local_table.c.get( # type: ignore[assignment] # noqa: E501
|
||||
colname, given_column
|
||||
)
|
||||
return given_column
|
||||
|
||||
def declarative_scan(
|
||||
self,
|
||||
decl_scan: _ClassScanMapperConfig,
|
||||
@@ -675,21 +702,9 @@ class MappedColumn(
|
||||
) -> None:
|
||||
column = self.column
|
||||
|
||||
if (
|
||||
self._use_existing_column
|
||||
and decl_scan.inherits
|
||||
and decl_scan.single
|
||||
):
|
||||
if decl_scan.is_deferred:
|
||||
raise sa_exc.ArgumentError(
|
||||
"Can't use use_existing_column with deferred mappers"
|
||||
)
|
||||
supercls_mapper = class_mapper(decl_scan.inherits, False)
|
||||
|
||||
colname = column.name if column.name is not None else key
|
||||
column = self.column = supercls_mapper.local_table.c.get( # type: ignore # noqa: E501
|
||||
colname, column
|
||||
)
|
||||
column = self.column = self._adjust_for_existing_column(
|
||||
decl_scan, key, self.column
|
||||
)
|
||||
|
||||
if column.key is None:
|
||||
column.key = key
|
||||
@@ -706,6 +721,8 @@ class MappedColumn(
|
||||
|
||||
self._init_column_for_annotation(
|
||||
cls,
|
||||
decl_scan,
|
||||
key,
|
||||
registry,
|
||||
extracted_mapped_annotation,
|
||||
originating_module,
|
||||
@@ -714,6 +731,7 @@ class MappedColumn(
|
||||
@util.preload_module("sqlalchemy.orm.decl_base")
|
||||
def declarative_scan_for_composite(
|
||||
self,
|
||||
decl_scan: _ClassScanMapperConfig,
|
||||
registry: _RegistryType,
|
||||
cls: Type[Any],
|
||||
originating_module: Optional[str],
|
||||
@@ -724,61 +742,65 @@ class MappedColumn(
|
||||
decl_base = util.preloaded.orm_decl_base
|
||||
decl_base._undefer_column_name(param_name, self.column)
|
||||
self._init_column_for_annotation(
|
||||
cls, registry, param_annotation, originating_module
|
||||
cls, decl_scan, key, registry, param_annotation, originating_module
|
||||
)
|
||||
|
||||
def _init_column_for_annotation(
|
||||
self,
|
||||
cls: Type[Any],
|
||||
decl_scan: _ClassScanMapperConfig,
|
||||
key: str,
|
||||
registry: _RegistryType,
|
||||
argument: _AnnotationScanType,
|
||||
originating_module: Optional[str],
|
||||
) -> None:
|
||||
sqltype = self.column.type
|
||||
|
||||
if isinstance(argument, str) or is_fwd_ref(
|
||||
argument, check_generic=True
|
||||
if is_fwd_ref(
|
||||
argument, check_generic=True, check_for_plain_string=True
|
||||
):
|
||||
assert originating_module is not None
|
||||
argument = de_stringify_annotation(
|
||||
cls, argument, originating_module, include_generic=True
|
||||
)
|
||||
|
||||
if is_union(argument):
|
||||
assert originating_module is not None
|
||||
argument = de_stringify_union_elements(
|
||||
cls, argument, originating_module
|
||||
)
|
||||
|
||||
nullable = is_optional_union(argument)
|
||||
nullable = includes_none(argument)
|
||||
|
||||
if not self._has_nullable:
|
||||
self.column.nullable = nullable
|
||||
|
||||
our_type = de_optionalize_union_types(argument)
|
||||
|
||||
use_args_from = None
|
||||
find_mapped_in: Tuple[Any, ...] = ()
|
||||
our_type_is_pep593 = False
|
||||
raw_pep_593_type = None
|
||||
|
||||
if is_pep593(our_type):
|
||||
our_type_is_pep593 = True
|
||||
|
||||
pep_593_components = typing_get_args(our_type)
|
||||
pep_593_components = get_args(our_type)
|
||||
raw_pep_593_type = pep_593_components[0]
|
||||
if is_optional_union(raw_pep_593_type):
|
||||
if nullable:
|
||||
raw_pep_593_type = de_optionalize_union_types(raw_pep_593_type)
|
||||
find_mapped_in = pep_593_components[1:]
|
||||
elif is_pep695(argument) and is_pep593(argument.__value__):
|
||||
# do not support nested annotation inside unions ets
|
||||
find_mapped_in = get_args(argument.__value__)[1:]
|
||||
|
||||
nullable = True
|
||||
if not self._has_nullable:
|
||||
self.column.nullable = nullable
|
||||
for elem in pep_593_components[1:]:
|
||||
if isinstance(elem, MappedColumn):
|
||||
use_args_from = elem
|
||||
break
|
||||
use_args_from: Optional[MappedColumn[Any]]
|
||||
for elem in find_mapped_in:
|
||||
if isinstance(elem, MappedColumn):
|
||||
use_args_from = elem
|
||||
break
|
||||
else:
|
||||
our_type_is_pep593 = False
|
||||
raw_pep_593_type = None
|
||||
use_args_from = None
|
||||
|
||||
if use_args_from is not None:
|
||||
|
||||
self.column = use_args_from._adjust_for_existing_column(
|
||||
decl_scan, key, self.column
|
||||
)
|
||||
|
||||
if (
|
||||
not self._has_insert_default
|
||||
and use_args_from.column.default is not None
|
||||
@@ -848,8 +870,7 @@ class MappedColumn(
|
||||
)
|
||||
|
||||
if sqltype._isnull and not self.column.foreign_keys:
|
||||
new_sqltype = None
|
||||
|
||||
checks: List[Any]
|
||||
if our_type_is_pep593:
|
||||
checks = [our_type, raw_pep_593_type]
|
||||
else:
|
||||
@@ -864,16 +885,23 @@ class MappedColumn(
|
||||
isinstance(our_type, type)
|
||||
and issubclass(our_type, TypeEngine)
|
||||
):
|
||||
raise sa_exc.ArgumentError(
|
||||
raise orm_exc.MappedAnnotationError(
|
||||
f"The type provided inside the {self.column.key!r} "
|
||||
"attribute Mapped annotation is the SQLAlchemy type "
|
||||
f"{our_type}. Expected a Python type instead"
|
||||
)
|
||||
else:
|
||||
raise sa_exc.ArgumentError(
|
||||
elif is_a_type(our_type):
|
||||
raise orm_exc.MappedAnnotationError(
|
||||
"Could not locate SQLAlchemy Core type for Python "
|
||||
f"type {our_type} inside the {self.column.key!r} "
|
||||
"attribute Mapped annotation"
|
||||
)
|
||||
else:
|
||||
raise orm_exc.MappedAnnotationError(
|
||||
f"The object provided inside the {self.column.key!r} "
|
||||
"attribute Mapped annotation is not a Python type, "
|
||||
f"it's the object {our_type!r}. Expected a Python "
|
||||
"type."
|
||||
)
|
||||
|
||||
self.column._set_type(new_sqltype)
|
||||
|
||||
Reference in New Issue
Block a user