Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/sqlalchemy/orm/decl_base.py: 22%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

914 statements  

1# orm/decl_base.py 

2# Copyright (C) 2005-2026 the SQLAlchemy authors and contributors 

3# <see AUTHORS file> 

4# 

5# This module is part of SQLAlchemy and is released under 

6# the MIT License: https://www.opensource.org/licenses/mit-license.php 

7 

8"""Internal implementation for declarative.""" 

9 

10from __future__ import annotations 

11 

12import collections 

13import dataclasses 

14import itertools 

15import re 

16from typing import Any 

17from typing import Callable 

18from typing import cast 

19from typing import Dict 

20from typing import get_args 

21from typing import Iterable 

22from typing import List 

23from typing import Mapping 

24from typing import NamedTuple 

25from typing import NoReturn 

26from typing import Optional 

27from typing import Protocol 

28from typing import Sequence 

29from typing import Tuple 

30from typing import Type 

31from typing import TYPE_CHECKING 

32from typing import TypeVar 

33from typing import Union 

34import weakref 

35 

36from . import attributes 

37from . import clsregistry 

38from . import exc as orm_exc 

39from . import instrumentation 

40from . import mapperlib 

41from ._typing import _O 

42from ._typing import attr_is_internal_proxy 

43from .attributes import InstrumentedAttribute 

44from .attributes import QueryableAttribute 

45from .base import _is_mapped_class 

46from .base import InspectionAttr 

47from .descriptor_props import CompositeProperty 

48from .descriptor_props import SynonymProperty 

49from .interfaces import _AttributeOptions 

50from .interfaces import _DataclassArguments 

51from .interfaces import _DCAttributeOptions 

52from .interfaces import _IntrospectsAnnotations 

53from .interfaces import _MappedAttribute 

54from .interfaces import _MapsColumns 

55from .interfaces import MapperProperty 

56from .mapper import Mapper 

57from .properties import ColumnProperty 

58from .properties import MappedColumn 

59from .util import _extract_mapped_subtype 

60from .util import _is_mapped_annotation 

61from .util import _metadata_for_cls as _metadata_for_cls_fn 

62from .util import class_mapper 

63from .util import de_stringify_annotation 

64from .. import event 

65from .. import exc 

66from .. import util 

67from ..sql import expression 

68from ..sql._annotated_cols import TypedColumns 

69from ..sql.base import _NoArg 

70from ..sql.schema import Column 

71from ..sql.schema import Table 

72from ..util import topological 

73from ..util.typing import _AnnotationScanType 

74from ..util.typing import is_fwd_ref 

75from ..util.typing import is_literal 

76 

77if TYPE_CHECKING: 

78 from ._typing import _ClassDict 

79 from ._typing import _RegistryType 

80 from .base import Mapped 

81 from .decl_api import declared_attr 

82 from .instrumentation import ClassManager 

83 from ..sql.elements import NamedColumn 

84 from ..sql.schema import MetaData 

85 from ..sql.selectable import FromClause 

86 

87_T = TypeVar("_T", bound=Any) 

88 

89_MapperKwArgs = Mapping[str, Any] 

90_TableArgsType = Union[Tuple[Any, ...], Dict[str, Any]] 

91 

92 

93class MappedClassProtocol(Protocol[_O]): 

94 """A protocol representing a SQLAlchemy mapped class. 

95 

96 The protocol is generic on the type of class, use 

97 ``MappedClassProtocol[Any]`` to allow any mapped class. 

98 """ 

99 

100 __name__: str 

101 __mapper__: Mapper[_O] 

102 __table__: FromClause 

103 

104 def __call__(self, **kw: Any) -> _O: ... 

105 

106 

107class _DeclMappedClassProtocol(MappedClassProtocol[_O], Protocol): 

108 "Internal more detailed version of ``MappedClassProtocol``." 

109 

110 metadata: MetaData 

111 __tablename__: str 

112 __mapper_args__: _MapperKwArgs 

113 __table_args__: Optional[_TableArgsType] 

114 

115 _sa_apply_dc_transforms: Optional[_DataclassArguments] 

116 

117 def __declare_first__(self) -> None: ... 

118 

119 def __declare_last__(self) -> None: ... 

120 

121 

122def _declared_mapping_info( 

123 cls: Type[Any], 

124) -> Optional[Union[_DeferredDeclarativeConfig, Mapper[Any]]]: 

125 # deferred mapping 

126 if _DeferredDeclarativeConfig.has_cls(cls): 

127 return _DeferredDeclarativeConfig.config_for_cls(cls) 

128 # regular mapping 

129 elif _is_mapped_class(cls): 

130 return class_mapper(cls, configure=False) 

131 else: 

132 return None 

133 

134 

135def _is_supercls_for_inherits(cls: Type[Any]) -> bool: 

136 """return True if this class will be used as a superclass to set in 

137 'inherits'. 

138 

139 This includes deferred mapper configs that aren't mapped yet, however does 

140 not include classes with _sa_decl_prepare_nocascade (e.g. 

141 ``AbstractConcreteBase``); these concrete-only classes are not set up as 

142 "inherits" until after mappers are configured using 

143 mapper._set_concrete_base() 

144 

145 """ 

146 if _DeferredDeclarativeConfig.has_cls(cls): 

147 return not _get_immediate_cls_attr( 

148 cls, "_sa_decl_prepare_nocascade", strict=True 

149 ) 

150 # regular mapping 

151 elif _is_mapped_class(cls): 

152 return True 

153 else: 

154 return False 

155 

156 

157def _resolve_for_abstract_or_classical(cls: Type[Any]) -> Optional[Type[Any]]: 

158 if cls is object: 

159 return None 

160 

161 sup: Optional[Type[Any]] 

162 

163 if cls.__dict__.get("__abstract__", False): 

164 for base_ in cls.__bases__: 

165 sup = _resolve_for_abstract_or_classical(base_) 

166 if sup is not None: 

167 return sup 

168 else: 

169 return None 

170 else: 

171 clsmanager = _dive_for_cls_manager(cls) 

172 

173 if clsmanager: 

174 return clsmanager.class_ 

175 else: 

176 return cls 

177 

178 

179def _get_immediate_cls_attr( 

180 cls: Type[Any], attrname: str, strict: bool = False 

181) -> Optional[Any]: 

182 """return an attribute of the class that is either present directly 

183 on the class, e.g. not on a superclass, or is from a superclass but 

184 this superclass is a non-mapped mixin, that is, not a descendant of 

185 the declarative base and is also not classically mapped. 

186 

187 This is used to detect attributes that indicate something about 

188 a mapped class independently from any mapped classes that it may 

189 inherit from. 

190 

191 """ 

192 

193 # the rules are different for this name than others, 

194 # make sure we've moved it out. transitional 

195 assert attrname != "__abstract__" 

196 

197 if not issubclass(cls, object): 

198 return None 

199 

200 if attrname in cls.__dict__: 

201 return getattr(cls, attrname) 

202 

203 for base in cls.__mro__[1:]: 

204 _is_classical_inherits = _dive_for_cls_manager(base) is not None 

205 

206 if attrname in base.__dict__ and ( 

207 base is cls 

208 or ( 

209 (base in cls.__bases__ if strict else True) 

210 and not _is_classical_inherits 

211 ) 

212 ): 

213 return getattr(base, attrname) 

214 else: 

215 return None 

216 

217 

218def _dive_for_cls_manager(cls: Type[_O]) -> Optional[ClassManager[_O]]: 

219 # because the class manager registration is pluggable, 

220 # we need to do the search for every class in the hierarchy, 

221 # rather than just a simple "cls._sa_class_manager" 

222 

223 for base in cls.__mro__: 

224 manager: Optional[ClassManager[_O]] = attributes.opt_manager_of_class( 

225 base 

226 ) 

227 if manager: 

228 return manager 

229 return None 

230 

231 

232@util.preload_module("sqlalchemy.orm.decl_api") 

233def _is_declarative_props(obj: Any) -> bool: 

234 _declared_attr_common = util.preloaded.orm_decl_api._declared_attr_common 

235 

236 return isinstance(obj, (_declared_attr_common, util.classproperty)) 

237 

238 

239def _check_declared_props_nocascade( 

240 obj: Any, name: str, cls: Type[_O] 

241) -> bool: 

242 if _is_declarative_props(obj): 

243 if getattr(obj, "_cascading", False): 

244 util.warn( 

245 "@declared_attr.cascading is not supported on the %s " 

246 "attribute on class %s. This attribute invokes for " 

247 "subclasses in any case." % (name, cls) 

248 ) 

249 return True 

250 else: 

251 return False 

252 

253 

254class _ORMClassConfigurator: 

255 """Object that configures a class that's potentially going to be 

256 mapped, and/or turned into an ORM dataclass. 

257 

258 This is the base class for all the configurator objects. 

259 

260 """ 

261 

262 __slots__ = ("cls", "classname", "__weakref__") 

263 

264 cls: Type[Any] 

265 classname: str 

266 

267 def __init__(self, cls_: Type[Any]): 

268 self.cls = util.assert_arg_type(cls_, type, "cls_") 

269 self.classname = cls_.__name__ 

270 

271 @classmethod 

272 def _as_declarative( 

273 cls, registry: _RegistryType, cls_: Type[Any], dict_: _ClassDict 

274 ) -> Optional[_MapperConfig]: 

275 manager = attributes.opt_manager_of_class(cls_) 

276 if manager and manager.class_ is cls_: 

277 raise exc.InvalidRequestError( 

278 f"Class {cls_!r} already has been instrumented declaratively" 

279 ) 

280 

281 # allow subclassing an orm class with typed columns without 

282 # generating an orm class 

283 if cls_.__dict__.get("__abstract__", False) or issubclass( 

284 cls_, TypedColumns 

285 ): 

286 return None 

287 

288 defer_map = _get_immediate_cls_attr( 

289 cls_, "_sa_decl_prepare_nocascade", strict=True 

290 ) or hasattr(cls_, "_sa_decl_prepare") 

291 

292 if defer_map: 

293 return _DeferredDeclarativeConfig(registry, cls_, dict_) 

294 else: 

295 return _DeclarativeMapperConfig(registry, cls_, dict_) 

296 

297 @classmethod 

298 def _as_unmapped_dataclass( 

299 cls, cls_: Type[Any], dict_: _ClassDict 

300 ) -> _UnmappedDataclassConfig: 

301 return _UnmappedDataclassConfig(cls_, dict_) 

302 

303 @classmethod 

304 def _mapper( 

305 cls, 

306 registry: _RegistryType, 

307 cls_: Type[_O], 

308 table: Optional[FromClause], 

309 mapper_kw: _MapperKwArgs, 

310 ) -> Mapper[_O]: 

311 _ImperativeMapperConfig(registry, cls_, table, mapper_kw) 

312 return cast("MappedClassProtocol[_O]", cls_).__mapper__ 

313 

314 

315class _MapperConfig(_ORMClassConfigurator): 

316 """Configurator that configures a class that's potentially going to be 

317 mapped, and optionally turned into a dataclass as well.""" 

318 

319 __slots__ = ( 

320 "properties", 

321 "declared_attr_reg", 

322 ) 

323 

324 properties: util.OrderedDict[ 

325 str, 

326 Union[ 

327 Sequence[NamedColumn[Any]], NamedColumn[Any], MapperProperty[Any] 

328 ], 

329 ] 

330 declared_attr_reg: Dict[declared_attr[Any], Any] 

331 

332 def __init__( 

333 self, 

334 registry: _RegistryType, 

335 cls_: Type[Any], 

336 ): 

337 super().__init__(cls_) 

338 self.properties = util.OrderedDict() 

339 self.declared_attr_reg = {} 

340 

341 instrumentation.register_class( 

342 self.cls, 

343 finalize=False, 

344 registry=registry, 

345 declarative_scan=self, 

346 init_method=registry.constructor, 

347 ) 

348 

349 def set_cls_attribute(self, attrname: str, value: _T) -> _T: 

350 manager = instrumentation.manager_of_class(self.cls) 

351 manager.install_member(attrname, value) 

352 return value 

353 

354 def map(self, mapper_kw: _MapperKwArgs) -> Mapper[Any]: 

355 raise NotImplementedError() 

356 

357 def _early_mapping(self, mapper_kw: _MapperKwArgs) -> None: 

358 self.map(mapper_kw) 

359 

360 

361class _ImperativeMapperConfig(_MapperConfig): 

362 """Configurator that configures a class for an imperative mapping.""" 

363 

364 __slots__ = ("local_table", "inherits") 

365 

366 def __init__( 

367 self, 

368 registry: _RegistryType, 

369 cls_: Type[_O], 

370 table: Optional[FromClause], 

371 mapper_kw: _MapperKwArgs, 

372 ): 

373 super().__init__(registry, cls_) 

374 

375 self.local_table = self.set_cls_attribute("__table__", table) 

376 

377 with mapperlib._CONFIGURE_MUTEX: 

378 clsregistry._add_class( 

379 self.classname, self.cls, registry._class_registry 

380 ) 

381 

382 self._setup_inheritance(mapper_kw) 

383 

384 self._early_mapping(mapper_kw) 

385 

386 def map(self, mapper_kw: _MapperKwArgs = util.EMPTY_DICT) -> Mapper[Any]: 

387 mapper_cls = Mapper 

388 

389 return self.set_cls_attribute( 

390 "__mapper__", 

391 mapper_cls(self.cls, self.local_table, **mapper_kw), 

392 ) 

393 

394 def _setup_inheritance(self, mapper_kw: _MapperKwArgs) -> None: 

395 cls = self.cls 

396 

397 inherits = None 

398 inherits_search = [] 

399 

400 # since we search for classical mappings now, search for 

401 # multiple mapped bases as well and raise an error. 

402 for base_ in cls.__bases__: 

403 c = _resolve_for_abstract_or_classical(base_) 

404 if c is None: 

405 continue 

406 

407 if _is_supercls_for_inherits(c) and c not in inherits_search: 

408 inherits_search.append(c) 

409 

410 if inherits_search: 

411 if len(inherits_search) > 1: 

412 raise exc.InvalidRequestError( 

413 "Class %s has multiple mapped bases: %r" 

414 % (cls, inherits_search) 

415 ) 

416 inherits = inherits_search[0] 

417 

418 self.inherits = inherits 

419 

420 

421class _CollectedAnnotation(NamedTuple): 

422 raw_annotation: _AnnotationScanType 

423 mapped_container: Optional[Type[Mapped[Any]]] 

424 extracted_mapped_annotation: Union[_AnnotationScanType, str] 

425 is_dataclass: bool 

426 attr_value: Any 

427 originating_module: str 

428 originating_class: Type[Any] 

429 

430 

431class _ClassScanAbstractConfig(_ORMClassConfigurator): 

432 """Abstract base for a configurator that configures a class for a 

433 declarative mapping, or an unmapped ORM dataclass. 

434 

435 Defines scanning of pep-484 annotations as well as ORM dataclass 

436 applicators 

437 

438 """ 

439 

440 __slots__ = () 

441 

442 clsdict_view: _ClassDict 

443 collected_annotations: Dict[str, _CollectedAnnotation] 

444 collected_attributes: Dict[str, Any] 

445 

446 is_dataclass_prior_to_mapping: bool 

447 allow_unmapped_annotations: bool 

448 

449 dataclass_setup_arguments: Optional[_DataclassArguments] 

450 """if the class has SQLAlchemy native dataclass parameters, where 

451 we will turn the class into a dataclass within the declarative mapping 

452 process. 

453 

454 """ 

455 

456 allow_dataclass_fields: bool 

457 """if true, look for dataclass-processed Field objects on the target 

458 class as well as superclasses and extract ORM mapping directives from 

459 the "metadata" attribute of each Field. 

460 

461 if False, dataclass fields can still be used, however they won't be 

462 mapped. 

463 

464 """ 

465 

466 _include_dunders = { 

467 "__table__", 

468 "__mapper_args__", 

469 "__tablename__", 

470 "__table_args__", 

471 } 

472 

473 _match_exclude_dunders = re.compile(r"^(?:_sa_|__)") 

474 

475 def _scan_attributes(self) -> None: 

476 raise NotImplementedError() 

477 

478 def _setup_dataclasses_transforms( 

479 self, *, enable_descriptor_defaults: bool, revert: bool = False 

480 ) -> None: 

481 dataclass_setup_arguments = self.dataclass_setup_arguments 

482 if not dataclass_setup_arguments: 

483 return 

484 

485 # can't use is_dataclass since it uses hasattr 

486 if "__dataclass_fields__" in self.cls.__dict__: 

487 raise exc.InvalidRequestError( 

488 f"Class {self.cls} is already a dataclass; ensure that " 

489 "base classes / decorator styles of establishing dataclasses " 

490 "are not being mixed. " 

491 "This can happen if a class that inherits from " 

492 "'MappedAsDataclass', even indirectly, is been mapped with " 

493 "'@registry.mapped_as_dataclass'" 

494 ) 

495 

496 # can't create a dataclass if __table__ is already there. This would 

497 # fail an assertion when calling _get_arguments_for_make_dataclass: 

498 # assert False, "Mapped[] received without a mapping declaration" 

499 if "__table__" in self.cls.__dict__: 

500 raise exc.InvalidRequestError( 

501 f"Class {self.cls} already defines a '__table__'. " 

502 "ORM Annotated Dataclasses do not support a pre-existing " 

503 "'__table__' element" 

504 ) 

505 

506 raise_for_non_dc_attrs = collections.defaultdict(list) 

507 

508 def _allow_dataclass_field( 

509 key: str, originating_class: Type[Any] 

510 ) -> bool: 

511 if ( 

512 originating_class is not self.cls 

513 and "__dataclass_fields__" not in originating_class.__dict__ 

514 ): 

515 raise_for_non_dc_attrs[originating_class].append(key) 

516 

517 return True 

518 

519 field_list = [ 

520 _AttributeOptions._get_arguments_for_make_dataclass( 

521 self, 

522 key, 

523 anno, 

524 mapped_container, 

525 self.collected_attributes.get(key, _NoArg.NO_ARG), 

526 dataclass_setup_arguments, 

527 enable_descriptor_defaults, 

528 ) 

529 for key, anno, mapped_container in ( 

530 ( 

531 key, 

532 raw_anno, 

533 mapped_container, 

534 ) 

535 for key, ( 

536 raw_anno, 

537 mapped_container, 

538 mapped_anno, 

539 is_dc, 

540 attr_value, 

541 originating_module, 

542 originating_class, 

543 ) in self.collected_annotations.items() 

544 if _allow_dataclass_field(key, originating_class) 

545 and ( 

546 key not in self.collected_attributes 

547 # issue #9226; check for attributes that we've collected 

548 # which are already instrumented, which we would assume 

549 # mean we are in an ORM inheritance mapping and this 

550 # attribute is already mapped on the superclass. Under 

551 # no circumstance should any QueryableAttribute be sent to 

552 # the dataclass() function; anything that's mapped should 

553 # be Field and that's it 

554 or not isinstance( 

555 self.collected_attributes[key], QueryableAttribute 

556 ) 

557 ) 

558 ) 

559 ] 

560 if raise_for_non_dc_attrs: 

561 for ( 

562 originating_class, 

563 non_dc_attrs, 

564 ) in raise_for_non_dc_attrs.items(): 

565 raise exc.InvalidRequestError( 

566 f"When transforming {self.cls} to a dataclass, " 

567 f"attribute(s) " 

568 f"{', '.join(repr(key) for key in non_dc_attrs)} " 

569 f"originates from superclass " 

570 f"{originating_class}, which is not a dataclass. When " 

571 f"declaring SQLAlchemy Declarative " 

572 f"Dataclasses, ensure that all mixin classes and other " 

573 f"superclasses which include attributes are also a " 

574 f"subclass of MappedAsDataclass or make use of the " 

575 f"@unmapped_dataclass decorator.", 

576 code="dcmx", 

577 ) 

578 

579 if revert: 

580 # the "revert" case is used only by an unmapped mixin class 

581 # that is nonetheless using Mapped construct and needs to 

582 # itself be a dataclass 

583 revert_dict = { 

584 name: self.cls.__dict__[name] 

585 for name in (item[0] for item in field_list) 

586 if name in self.cls.__dict__ 

587 } 

588 else: 

589 revert_dict = None 

590 

591 # get original annotations using ForwardRef for symbols that 

592 # are unresolvable 

593 orig_annotations = util.get_annotations(self.cls) 

594 

595 # build a new __annotations__ dict from the fields we have. 

596 # this has to be done carefully since we have to maintain 

597 # the correct order! wow 

598 swap_annotations = {} 

599 defaults = {} 

600 

601 for item in field_list: 

602 if len(item) == 2: 

603 name, tp = item 

604 elif len(item) == 3: 

605 name, tp, spec = item 

606 defaults[name] = spec 

607 else: 

608 assert False 

609 

610 # add the annotation to the new dict we are creating. 

611 # note that if name is in orig_annotations, we expect 

612 # tp and orig_annotations[name] to be identical. 

613 swap_annotations[name] = orig_annotations.get(name, tp) 

614 

615 for k, v in defaults.items(): 

616 setattr(self.cls, k, v) 

617 

618 self._assert_dc_arguments(dataclass_setup_arguments) 

619 

620 dataclass_callable = dataclass_setup_arguments["dataclass_callable"] 

621 if dataclass_callable is _NoArg.NO_ARG: 

622 dataclass_callable = dataclasses.dataclass 

623 

624 # create a merged __annotations__ dictionary, maintaining order 

625 # as best we can: 

626 

627 # 1. merge all keys in orig_annotations that occur before 

628 # we see any of our mapped fields (this can be attributes like 

629 # __table_args__ etc.) 

630 new_annotations = { 

631 k: orig_annotations[k] 

632 for k in itertools.takewhile( 

633 lambda k: k not in swap_annotations, orig_annotations 

634 ) 

635 } 

636 

637 # 2. then put in all the dataclass annotations we have 

638 new_annotations |= swap_annotations 

639 

640 # 3. them merge all of orig_annotations which will add remaining 

641 # keys 

642 new_annotations |= orig_annotations 

643 

644 # 4. this becomes the new class annotations. 

645 restore_anno = util.restore_annotations(self.cls, new_annotations) 

646 

647 try: 

648 dataclass_callable( # type: ignore[call-overload] 

649 self.cls, 

650 **{ # type: ignore[call-overload,unused-ignore] 

651 k: v 

652 for k, v in dataclass_setup_arguments.items() 

653 if v is not _NoArg.NO_ARG 

654 and k not in ("dataclass_callable",) 

655 }, 

656 ) 

657 except (TypeError, ValueError) as ex: 

658 raise exc.InvalidRequestError( 

659 f"Python dataclasses error encountered when creating " 

660 f"dataclass for {self.cls.__name__!r}: " 

661 f"{ex!r}. Please refer to Python dataclasses " 

662 "documentation for additional information.", 

663 code="dcte", 

664 ) from ex 

665 finally: 

666 if revert and revert_dict: 

667 # used for mixin dataclasses; we have to restore the 

668 # mapped_column(), relationship() etc. to the class so these 

669 # take place for a mapped class scan 

670 for k, v in revert_dict.items(): 

671 setattr(self.cls, k, v) 

672 

673 restore_anno() 

674 

675 def _collect_annotation( 

676 self, 

677 name: str, 

678 raw_annotation: _AnnotationScanType, 

679 originating_class: Type[Any], 

680 expect_mapped: Optional[bool], 

681 attr_value: Any, 

682 ) -> Optional[_CollectedAnnotation]: 

683 if name in self.collected_annotations: 

684 return self.collected_annotations[name] 

685 

686 if raw_annotation is None: 

687 return None 

688 

689 is_dataclass = self.is_dataclass_prior_to_mapping 

690 allow_unmapped = self.allow_unmapped_annotations 

691 

692 if expect_mapped is None: 

693 is_dataclass_field = isinstance(attr_value, dataclasses.Field) 

694 expect_mapped = ( 

695 not is_dataclass_field 

696 and not allow_unmapped 

697 and ( 

698 attr_value is None 

699 or isinstance(attr_value, _MappedAttribute) 

700 ) 

701 ) 

702 

703 is_dataclass_field = False 

704 extracted = _extract_mapped_subtype( 

705 raw_annotation, 

706 self.cls, 

707 originating_class.__module__, 

708 name, 

709 type(attr_value), 

710 required=False, 

711 is_dataclass_field=is_dataclass_field, 

712 expect_mapped=expect_mapped and not is_dataclass, 

713 ) 

714 if extracted is None: 

715 # ClassVar can come out here 

716 return None 

717 

718 extracted_mapped_annotation, mapped_container = extracted 

719 

720 if attr_value is None and not is_literal(extracted_mapped_annotation): 

721 for elem in get_args(extracted_mapped_annotation): 

722 if is_fwd_ref( 

723 elem, check_generic=True, check_for_plain_string=True 

724 ): 

725 elem = de_stringify_annotation( 

726 self.cls, 

727 elem, 

728 originating_class.__module__, 

729 include_generic=True, 

730 ) 

731 # look in Annotated[...] for an ORM construct, 

732 # such as Annotated[int, mapped_column(primary_key=True)] 

733 if isinstance(elem, _IntrospectsAnnotations): 

734 attr_value = elem.found_in_pep593_annotated() 

735 

736 self.collected_annotations[name] = ca = _CollectedAnnotation( 

737 raw_annotation, 

738 mapped_container, 

739 extracted_mapped_annotation, 

740 is_dataclass, 

741 attr_value, 

742 originating_class.__module__, 

743 originating_class, 

744 ) 

745 return ca 

746 

747 @classmethod 

748 def _assert_dc_arguments(cls, arguments: _DataclassArguments) -> None: 

749 allowed = { 

750 "init", 

751 "repr", 

752 "order", 

753 "eq", 

754 "unsafe_hash", 

755 "kw_only", 

756 "match_args", 

757 "dataclass_callable", 

758 } 

759 disallowed_args = set(arguments).difference(allowed) 

760 if disallowed_args: 

761 msg = ", ".join(f"{arg!r}" for arg in sorted(disallowed_args)) 

762 raise exc.ArgumentError( 

763 f"Dataclass argument(s) {msg} are not accepted" 

764 ) 

765 

766 def _cls_attr_override_checker( 

767 self, cls: Type[_O] 

768 ) -> Callable[[str, Any], bool]: 

769 """Produce a function that checks if a class has overridden an 

770 attribute, taking SQLAlchemy-enabled dataclass fields into account. 

771 

772 """ 

773 

774 if self.allow_dataclass_fields: 

775 sa_dataclass_metadata_key = _get_immediate_cls_attr( 

776 cls, "__sa_dataclass_metadata_key__" 

777 ) 

778 else: 

779 sa_dataclass_metadata_key = None 

780 

781 if not sa_dataclass_metadata_key: 

782 

783 def attribute_is_overridden(key: str, obj: Any) -> bool: 

784 return getattr(cls, key, obj) is not obj 

785 

786 else: 

787 all_datacls_fields = { 

788 f.name: f.metadata[sa_dataclass_metadata_key] 

789 for f in util.dataclass_fields(cls) 

790 if sa_dataclass_metadata_key in f.metadata 

791 } 

792 local_datacls_fields = { 

793 f.name: f.metadata[sa_dataclass_metadata_key] 

794 for f in util.local_dataclass_fields(cls) 

795 if sa_dataclass_metadata_key in f.metadata 

796 } 

797 

798 absent = object() 

799 

800 def attribute_is_overridden(key: str, obj: Any) -> bool: 

801 if _is_declarative_props(obj): 

802 obj = obj.fget 

803 

804 # this function likely has some failure modes still if 

805 # someone is doing a deep mixing of the same attribute 

806 # name as plain Python attribute vs. dataclass field. 

807 

808 ret = local_datacls_fields.get(key, absent) 

809 if _is_declarative_props(ret): 

810 ret = ret.fget 

811 

812 if ret is obj: 

813 return False 

814 elif ret is not absent: 

815 return True 

816 

817 all_field = all_datacls_fields.get(key, absent) 

818 

819 ret = getattr(cls, key, obj) 

820 

821 if ret is obj: 

822 return False 

823 

824 # for dataclasses, this could be the 

825 # 'default' of the field. so filter more specifically 

826 # for an already-mapped InstrumentedAttribute 

827 if ret is not absent and isinstance( 

828 ret, InstrumentedAttribute 

829 ): 

830 return True 

831 

832 if all_field is obj: 

833 return False 

834 elif all_field is not absent: 

835 return True 

836 

837 # can't find another attribute 

838 return False 

839 

840 return attribute_is_overridden 

841 

842 def _cls_attr_resolver( 

843 self, cls: Type[Any] 

844 ) -> Callable[[], Iterable[Tuple[str, Any, Any, bool]]]: 

845 """produce a function to iterate the "attributes" of a class 

846 which we want to consider for mapping, adjusting for SQLAlchemy fields 

847 embedded in dataclass fields. 

848 

849 """ 

850 cls_annotations = util.get_annotations(cls) 

851 

852 cls_vars = vars(cls) 

853 

854 _include_dunders = self._include_dunders 

855 _match_exclude_dunders = self._match_exclude_dunders 

856 

857 names = [ 

858 n 

859 for n in util.merge_lists_w_ordering( 

860 list(cls_vars), list(cls_annotations) 

861 ) 

862 if not _match_exclude_dunders.match(n) or n in _include_dunders 

863 ] 

864 

865 if self.allow_dataclass_fields: 

866 sa_dataclass_metadata_key: Optional[str] = _get_immediate_cls_attr( 

867 cls, "__sa_dataclass_metadata_key__" 

868 ) 

869 else: 

870 sa_dataclass_metadata_key = None 

871 

872 if not sa_dataclass_metadata_key: 

873 

874 def local_attributes_for_class() -> ( 

875 Iterable[Tuple[str, Any, Any, bool]] 

876 ): 

877 return ( 

878 ( 

879 name, 

880 cls_vars.get(name), 

881 cls_annotations.get(name), 

882 False, 

883 ) 

884 for name in names 

885 ) 

886 

887 else: 

888 dataclass_fields = { 

889 field.name: field for field in util.local_dataclass_fields(cls) 

890 } 

891 

892 fixed_sa_dataclass_metadata_key = sa_dataclass_metadata_key 

893 

894 def local_attributes_for_class() -> ( 

895 Iterable[Tuple[str, Any, Any, bool]] 

896 ): 

897 for name in names: 

898 field = dataclass_fields.get(name, None) 

899 if field and sa_dataclass_metadata_key in field.metadata: 

900 yield field.name, _as_dc_declaredattr( 

901 field.metadata, fixed_sa_dataclass_metadata_key 

902 ), cls_annotations.get(field.name), True 

903 else: 

904 yield name, cls_vars.get(name), cls_annotations.get( 

905 name 

906 ), False 

907 

908 return local_attributes_for_class 

909 

910 

911class _DeclarativeMapperConfig(_MapperConfig, _ClassScanAbstractConfig): 

912 """Configurator that will produce a declarative mapped class""" 

913 

914 __slots__ = ( 

915 "registry", 

916 "local_table", 

917 "persist_selectable", 

918 "declared_columns", 

919 "column_ordering", 

920 "column_copies", 

921 "table_args", 

922 "tablename", 

923 "mapper_args", 

924 "mapper_args_fn", 

925 "table_fn", 

926 "inherits", 

927 "single", 

928 "clsdict_view", 

929 "collected_attributes", 

930 "collected_annotations", 

931 "allow_dataclass_fields", 

932 "dataclass_setup_arguments", 

933 "is_dataclass_prior_to_mapping", 

934 "allow_unmapped_annotations", 

935 ) 

936 

937 is_deferred = False 

938 registry: _RegistryType 

939 local_table: Optional[FromClause] 

940 persist_selectable: Optional[FromClause] 

941 declared_columns: util.OrderedSet[Column[Any]] 

942 column_ordering: Dict[Column[Any], int] 

943 column_copies: Dict[ 

944 Union[MappedColumn[Any], Column[Any]], 

945 Union[MappedColumn[Any], Column[Any]], 

946 ] 

947 tablename: Optional[str] 

948 mapper_args: Mapping[str, Any] 

949 table_args: Optional[_TableArgsType] 

950 mapper_args_fn: Optional[Callable[[], Dict[str, Any]]] 

951 inherits: Optional[Type[Any]] 

952 single: bool 

953 

954 def __init__( 

955 self, 

956 registry: _RegistryType, 

957 cls_: Type[_O], 

958 dict_: _ClassDict, 

959 ): 

960 # grab class dict before the instrumentation manager has been added. 

961 # reduces cycles 

962 self.clsdict_view = ( 

963 util.immutabledict(dict_) if dict_ else util.EMPTY_DICT 

964 ) 

965 super().__init__(registry, cls_) 

966 self.registry = registry 

967 self.persist_selectable = None 

968 

969 self.collected_attributes = {} 

970 self.collected_annotations = {} 

971 self.declared_columns = util.OrderedSet() 

972 self.column_ordering = {} 

973 self.column_copies = {} 

974 self.single = False 

975 self.dataclass_setup_arguments = dca = getattr( 

976 self.cls, "_sa_apply_dc_transforms", None 

977 ) 

978 

979 self.allow_unmapped_annotations = getattr( 

980 self.cls, "__allow_unmapped__", False 

981 ) or bool(self.dataclass_setup_arguments) 

982 

983 self.is_dataclass_prior_to_mapping = cld = dataclasses.is_dataclass( 

984 cls_ 

985 ) 

986 

987 sdk = _get_immediate_cls_attr(cls_, "__sa_dataclass_metadata_key__") 

988 

989 # we don't want to consume Field objects from a not-already-dataclass. 

990 # the Field objects won't have their "name" or "type" populated, 

991 # and while it seems like we could just set these on Field as we 

992 # read them, Field is documented as "user read only" and we need to 

993 # stay far away from any off-label use of dataclasses APIs. 

994 if (not cld or dca) and sdk: 

995 raise exc.InvalidRequestError( 

996 "SQLAlchemy mapped dataclasses can't consume mapping " 

997 "information from dataclass.Field() objects if the immediate " 

998 "class is not already a dataclass." 

999 ) 

1000 

1001 # if already a dataclass, and __sa_dataclass_metadata_key__ present, 

1002 # then also look inside of dataclass.Field() objects yielded by 

1003 # dataclasses.get_fields(cls) when scanning for attributes 

1004 self.allow_dataclass_fields = bool(sdk and cld) 

1005 

1006 self._setup_declared_events() 

1007 

1008 self._scan_attributes() 

1009 

1010 self._setup_dataclasses_transforms(enable_descriptor_defaults=True) 

1011 

1012 with mapperlib._CONFIGURE_MUTEX: 

1013 clsregistry._add_class( 

1014 self.classname, self.cls, registry._class_registry 

1015 ) 

1016 

1017 self._setup_inheriting_mapper() 

1018 

1019 self._extract_mappable_attributes() 

1020 

1021 self._extract_declared_columns() 

1022 

1023 self._setup_table() 

1024 

1025 self._setup_inheriting_columns() 

1026 

1027 self._early_mapping(util.EMPTY_DICT) 

1028 

1029 def _setup_declared_events(self) -> None: 

1030 if _get_immediate_cls_attr(self.cls, "__declare_last__"): 

1031 

1032 @event.listens_for(Mapper, "after_configured") 

1033 def after_configured() -> None: 

1034 cast( 

1035 "_DeclMappedClassProtocol[Any]", self.cls 

1036 ).__declare_last__() 

1037 

1038 if _get_immediate_cls_attr(self.cls, "__declare_first__"): 

1039 

1040 @event.listens_for(Mapper, "before_configured") 

1041 def before_configured() -> None: 

1042 cast( 

1043 "_DeclMappedClassProtocol[Any]", self.cls 

1044 ).__declare_first__() 

1045 

1046 def _scan_attributes(self) -> None: 

1047 cls = self.cls 

1048 

1049 cls_as_Decl = cast("_DeclMappedClassProtocol[Any]", cls) 

1050 

1051 clsdict_view = self.clsdict_view 

1052 collected_attributes = self.collected_attributes 

1053 column_copies = self.column_copies 

1054 _include_dunders = self._include_dunders 

1055 mapper_args_fn = None 

1056 table_args = inherited_table_args = None 

1057 table_fn = None 

1058 tablename = None 

1059 fixed_table = "__table__" in clsdict_view 

1060 

1061 attribute_is_overridden = self._cls_attr_override_checker(self.cls) 

1062 

1063 bases = [] 

1064 

1065 for base in cls.__mro__: 

1066 # collect bases and make sure standalone columns are copied 

1067 # to be the column they will ultimately be on the class, 

1068 # so that declared_attr functions use the right columns. 

1069 # need to do this all the way up the hierarchy first 

1070 # (see #8190) 

1071 

1072 class_mapped = base is not cls and _is_supercls_for_inherits(base) 

1073 

1074 local_attributes_for_class = self._cls_attr_resolver(base) 

1075 

1076 if not class_mapped and base is not cls: 

1077 locally_collected_columns = self._produce_column_copies( 

1078 local_attributes_for_class, 

1079 attribute_is_overridden, 

1080 fixed_table, 

1081 base, 

1082 ) 

1083 else: 

1084 locally_collected_columns = {} 

1085 

1086 bases.append( 

1087 ( 

1088 base, 

1089 class_mapped, 

1090 local_attributes_for_class, 

1091 locally_collected_columns, 

1092 ) 

1093 ) 

1094 

1095 for ( 

1096 base, 

1097 class_mapped, 

1098 local_attributes_for_class, 

1099 locally_collected_columns, 

1100 ) in bases: 

1101 # this transfer can also take place as we scan each name 

1102 # for finer-grained control of how collected_attributes is 

1103 # populated, as this is what impacts column ordering. 

1104 # however it's simpler to get it out of the way here. 

1105 collected_attributes.update(locally_collected_columns) 

1106 

1107 for ( 

1108 name, 

1109 obj, 

1110 annotation, 

1111 is_dataclass_field, 

1112 ) in local_attributes_for_class(): 

1113 if name in _include_dunders: 

1114 if name == "__mapper_args__": 

1115 check_decl = _check_declared_props_nocascade( 

1116 obj, name, cls 

1117 ) 

1118 if not mapper_args_fn and ( 

1119 not class_mapped or check_decl 

1120 ): 

1121 # don't even invoke __mapper_args__ until 

1122 # after we've determined everything about the 

1123 # mapped table. 

1124 # make a copy of it so a class-level dictionary 

1125 # is not overwritten when we update column-based 

1126 # arguments. 

1127 def _mapper_args_fn() -> Dict[str, Any]: 

1128 return dict(cls_as_Decl.__mapper_args__) 

1129 

1130 mapper_args_fn = _mapper_args_fn 

1131 

1132 elif name == "__tablename__": 

1133 check_decl = _check_declared_props_nocascade( 

1134 obj, name, cls 

1135 ) 

1136 if not tablename and (not class_mapped or check_decl): 

1137 tablename = cls_as_Decl.__tablename__ 

1138 elif name == "__table__": 

1139 check_decl = _check_declared_props_nocascade( 

1140 obj, name, cls 

1141 ) 

1142 # if a @declared_attr using "__table__" is detected, 

1143 # wrap up a callable to look for "__table__" from 

1144 # the final concrete class when we set up a table. 

1145 # this was fixed by 

1146 # #11509, regression in 2.0 from version 1.4. 

1147 if check_decl and not table_fn: 

1148 # don't even invoke __table__ until we're ready 

1149 def _table_fn() -> FromClause: 

1150 return cls_as_Decl.__table__ 

1151 

1152 table_fn = _table_fn 

1153 

1154 elif name == "__table_args__": 

1155 check_decl = _check_declared_props_nocascade( 

1156 obj, name, cls 

1157 ) 

1158 if not table_args and (not class_mapped or check_decl): 

1159 table_args = cls_as_Decl.__table_args__ 

1160 if not isinstance( 

1161 table_args, (tuple, dict, type(None)) 

1162 ): 

1163 raise exc.ArgumentError( 

1164 "__table_args__ value must be a tuple, " 

1165 "dict, or None" 

1166 ) 

1167 if base is not cls: 

1168 inherited_table_args = True 

1169 else: 

1170 # any other dunder names; should not be here 

1171 # as we have tested for all four names in 

1172 # _include_dunders 

1173 assert False 

1174 elif class_mapped: 

1175 if _is_declarative_props(obj) and not obj._quiet: 

1176 util.warn( 

1177 "Regular (i.e. not __special__) " 

1178 "attribute '%s.%s' uses @declared_attr, " 

1179 "but owning class %s is mapped - " 

1180 "not applying to subclass %s." 

1181 % (base.__name__, name, base, cls) 

1182 ) 

1183 

1184 continue 

1185 elif base is not cls: 

1186 # we're a mixin, abstract base, or something that is 

1187 # acting like that for now. 

1188 

1189 if isinstance(obj, (Column, MappedColumn)): 

1190 # already copied columns to the mapped class. 

1191 continue 

1192 elif isinstance(obj, MapperProperty): 

1193 raise exc.InvalidRequestError( 

1194 "Mapper properties (i.e. deferred," 

1195 "column_property(), relationship(), etc.) must " 

1196 "be declared as @declared_attr callables " 

1197 "on declarative mixin classes. For dataclass " 

1198 "field() objects, use a lambda:" 

1199 ) 

1200 elif _is_declarative_props(obj): 

1201 # tried to get overloads to tell this to 

1202 # pylance, no luck 

1203 assert obj is not None 

1204 

1205 if obj._cascading: 

1206 if name in clsdict_view: 

1207 # unfortunately, while we can use the user- 

1208 # defined attribute here to allow a clean 

1209 # override, if there's another 

1210 # subclass below then it still tries to use 

1211 # this. not sure if there is enough 

1212 # information here to add this as a feature 

1213 # later on. 

1214 util.warn( 

1215 "Attribute '%s' on class %s cannot be " 

1216 "processed due to " 

1217 "@declared_attr.cascading; " 

1218 "skipping" % (name, cls) 

1219 ) 

1220 collected_attributes[name] = column_copies[obj] = ( 

1221 ret 

1222 ) = obj.__get__(obj, cls) 

1223 setattr(cls, name, ret) 

1224 else: 

1225 if is_dataclass_field: 

1226 # access attribute using normal class access 

1227 # first, to see if it's been mapped on a 

1228 # superclass. note if the dataclasses.field() 

1229 # has "default", this value can be anything. 

1230 ret = getattr(cls, name, None) 

1231 

1232 # so, if it's anything that's not ORM 

1233 # mapped, assume we should invoke the 

1234 # declared_attr 

1235 if not isinstance(ret, InspectionAttr): 

1236 ret = obj.fget() 

1237 else: 

1238 # access attribute using normal class access. 

1239 # if the declared attr already took place 

1240 # on a superclass that is mapped, then 

1241 # this is no longer a declared_attr, it will 

1242 # be the InstrumentedAttribute 

1243 ret = getattr(cls, name) 

1244 

1245 # correct for proxies created from hybrid_property 

1246 # or similar. note there is no known case that 

1247 # produces nested proxies, so we are only 

1248 # looking one level deep right now. 

1249 

1250 if ( 

1251 isinstance(ret, InspectionAttr) 

1252 and attr_is_internal_proxy(ret) 

1253 and not isinstance( 

1254 ret.original_property, MapperProperty 

1255 ) 

1256 ): 

1257 ret = ret.descriptor 

1258 

1259 collected_attributes[name] = column_copies[obj] = ( 

1260 ret 

1261 ) 

1262 

1263 if ( 

1264 isinstance(ret, (Column, MapperProperty)) 

1265 and ret.doc is None 

1266 ): 

1267 ret.doc = obj.__doc__ 

1268 

1269 self._collect_annotation( 

1270 name, 

1271 obj._collect_return_annotation(), 

1272 base, 

1273 True, 

1274 obj, 

1275 ) 

1276 elif _is_mapped_annotation(annotation, cls, base): 

1277 # Mapped annotation without any object. 

1278 # product_column_copies should have handled this. 

1279 # if future support for other MapperProperty, 

1280 # then test if this name is already handled and 

1281 # otherwise proceed to generate. 

1282 if not fixed_table: 

1283 assert ( 

1284 name in collected_attributes 

1285 or attribute_is_overridden(name, None) 

1286 ) 

1287 continue 

1288 else: 

1289 # here, the attribute is some other kind of 

1290 # property that we assume is not part of the 

1291 # declarative mapping. however, check for some 

1292 # more common mistakes 

1293 self._warn_for_decl_attributes(base, name, obj) 

1294 elif is_dataclass_field and ( 

1295 name not in clsdict_view or clsdict_view[name] is not obj 

1296 ): 

1297 # here, we are definitely looking at the target class 

1298 # and not a superclass. this is currently a 

1299 # dataclass-only path. if the name is only 

1300 # a dataclass field and isn't in local cls.__dict__, 

1301 # put the object there. 

1302 # assert that the dataclass-enabled resolver agrees 

1303 # with what we are seeing 

1304 

1305 assert not attribute_is_overridden(name, obj) 

1306 

1307 if _is_declarative_props(obj): 

1308 obj = obj.fget() 

1309 

1310 collected_attributes[name] = obj 

1311 self._collect_annotation( 

1312 name, annotation, base, False, obj 

1313 ) 

1314 else: 

1315 collected_annotation = self._collect_annotation( 

1316 name, annotation, base, None, obj 

1317 ) 

1318 is_mapped = ( 

1319 collected_annotation is not None 

1320 and collected_annotation.mapped_container is not None 

1321 ) 

1322 generated_obj = ( 

1323 collected_annotation.attr_value 

1324 if collected_annotation is not None 

1325 else obj 

1326 ) 

1327 if obj is None and not fixed_table and is_mapped: 

1328 collected_attributes[name] = ( 

1329 generated_obj 

1330 if generated_obj is not None 

1331 else MappedColumn() 

1332 ) 

1333 elif name in clsdict_view: 

1334 collected_attributes[name] = obj 

1335 # else if the name is not in the cls.__dict__, 

1336 # don't collect it as an attribute. 

1337 # we will see the annotation only, which is meaningful 

1338 # both for mapping and dataclasses setup 

1339 

1340 if inherited_table_args and not tablename: 

1341 table_args = None 

1342 

1343 self.table_args = table_args 

1344 self.tablename = tablename 

1345 self.mapper_args_fn = mapper_args_fn 

1346 self.table_fn = table_fn 

1347 

1348 @classmethod 

1349 def _update_annotations_for_non_mapped_class( 

1350 cls, klass: Type[_O] 

1351 ) -> Mapping[str, _AnnotationScanType]: 

1352 cls_annotations = util.get_annotations(klass) 

1353 

1354 new_anno = {} 

1355 for name, annotation in cls_annotations.items(): 

1356 if _is_mapped_annotation(annotation, klass, klass): 

1357 extracted = _extract_mapped_subtype( 

1358 annotation, 

1359 klass, 

1360 klass.__module__, 

1361 name, 

1362 type(None), 

1363 required=False, 

1364 is_dataclass_field=False, 

1365 expect_mapped=False, 

1366 ) 

1367 if extracted: 

1368 inner, _ = extracted 

1369 new_anno[name] = inner 

1370 else: 

1371 new_anno[name] = annotation 

1372 return new_anno 

1373 

1374 def _warn_for_decl_attributes( 

1375 self, cls: Type[Any], key: str, c: Any 

1376 ) -> None: 

1377 if isinstance(c, expression.ColumnElement): 

1378 util.warn( 

1379 f"Attribute '{key}' on class {cls} appears to " 

1380 "be a non-schema SQLAlchemy expression " 

1381 "object; this won't be part of the declarative mapping. " 

1382 "To map arbitrary expressions, use ``column_property()`` " 

1383 "or a similar function such as ``deferred()``, " 

1384 "``query_expression()`` etc. " 

1385 ) 

1386 

1387 def _produce_column_copies( 

1388 self, 

1389 attributes_for_class: Callable[ 

1390 [], Iterable[Tuple[str, Any, Any, bool]] 

1391 ], 

1392 attribute_is_overridden: Callable[[str, Any], bool], 

1393 fixed_table: bool, 

1394 originating_class: Type[Any], 

1395 ) -> Dict[str, Union[Column[Any], MappedColumn[Any]]]: 

1396 cls = self.cls 

1397 dict_ = self.clsdict_view 

1398 locally_collected_attributes = {} 

1399 column_copies = self.column_copies 

1400 # copy mixin columns to the mapped class 

1401 

1402 for name, obj, annotation, is_dataclass in attributes_for_class(): 

1403 if ( 

1404 not fixed_table 

1405 and obj is None 

1406 and _is_mapped_annotation(annotation, cls, originating_class) 

1407 ): 

1408 # obj is None means this is the annotation only path 

1409 

1410 if attribute_is_overridden(name, obj): 

1411 # perform same "overridden" check as we do for 

1412 # Column/MappedColumn, this is how a mixin col is not 

1413 # applied to an inherited subclass that does not have 

1414 # the mixin. the anno-only path added here for 

1415 # #9564 

1416 continue 

1417 

1418 collected_annotation = self._collect_annotation( 

1419 name, annotation, originating_class, True, obj 

1420 ) 

1421 obj = ( 

1422 collected_annotation.attr_value 

1423 if collected_annotation is not None 

1424 else obj 

1425 ) 

1426 if obj is None: 

1427 obj = MappedColumn() 

1428 

1429 locally_collected_attributes[name] = obj 

1430 setattr(cls, name, obj) 

1431 

1432 elif isinstance(obj, (Column, MappedColumn)): 

1433 if attribute_is_overridden(name, obj): 

1434 # if column has been overridden 

1435 # (like by the InstrumentedAttribute of the 

1436 # superclass), skip. don't collect the annotation 

1437 # either (issue #8718) 

1438 continue 

1439 

1440 collected_annotation = self._collect_annotation( 

1441 name, annotation, originating_class, True, obj 

1442 ) 

1443 obj = ( 

1444 collected_annotation.attr_value 

1445 if collected_annotation is not None 

1446 else obj 

1447 ) 

1448 

1449 if name not in dict_ and not ( 

1450 "__table__" in dict_ 

1451 and (getattr(obj, "name", None) or name) 

1452 in dict_["__table__"].c 

1453 ): 

1454 if obj.foreign_keys: 

1455 for fk in obj.foreign_keys: 

1456 if ( 

1457 fk._table_column is not None 

1458 and fk._table_column.table is None 

1459 ): 

1460 raise exc.InvalidRequestError( 

1461 "Columns with foreign keys to " 

1462 "non-table-bound " 

1463 "columns must be declared as " 

1464 "@declared_attr callables " 

1465 "on declarative mixin classes. " 

1466 "For dataclass " 

1467 "field() objects, use a lambda:." 

1468 ) 

1469 

1470 column_copies[obj] = copy_ = obj._copy() 

1471 

1472 locally_collected_attributes[name] = copy_ 

1473 setattr(cls, name, copy_) 

1474 

1475 return locally_collected_attributes 

1476 

1477 def _extract_mappable_attributes(self) -> None: 

1478 cls = self.cls 

1479 collected_attributes = self.collected_attributes 

1480 

1481 our_stuff = self.properties 

1482 

1483 _include_dunders = self._include_dunders 

1484 

1485 late_mapped = _get_immediate_cls_attr( 

1486 cls, "_sa_decl_prepare_nocascade", strict=True 

1487 ) 

1488 

1489 allow_unmapped_annotations = self.allow_unmapped_annotations 

1490 expect_annotations_wo_mapped = ( 

1491 allow_unmapped_annotations or self.is_dataclass_prior_to_mapping 

1492 ) 

1493 

1494 look_for_dataclass_things = bool(self.dataclass_setup_arguments) 

1495 

1496 for k in list(collected_attributes): 

1497 if k in _include_dunders: 

1498 continue 

1499 

1500 value = collected_attributes[k] 

1501 

1502 if _is_declarative_props(value): 

1503 # @declared_attr in collected_attributes only occurs here for a 

1504 # @declared_attr that's directly on the mapped class; 

1505 # for a mixin, these have already been evaluated 

1506 if value._cascading: 

1507 util.warn( 

1508 "Use of @declared_attr.cascading only applies to " 

1509 "Declarative 'mixin' and 'abstract' classes. " 

1510 "Currently, this flag is ignored on mapped class " 

1511 "%s" % self.cls 

1512 ) 

1513 

1514 value = getattr(cls, k) 

1515 

1516 elif ( 

1517 isinstance(value, QueryableAttribute) 

1518 and value.class_ is not cls 

1519 and value.key != k 

1520 ): 

1521 # detect a QueryableAttribute that's already mapped being 

1522 # assigned elsewhere in userland, turn into a synonym() 

1523 value = SynonymProperty(value.key) 

1524 setattr(cls, k, value) 

1525 

1526 if k in ("metadata", "registry"): 

1527 noun = { 

1528 "metadata": "MetaData instance", 

1529 "registry": "Declarative class registry", 

1530 } 

1531 util.warn( 

1532 f"Attribute name '{k}' should be left reserved for the " 

1533 f"{noun[k]} when using a Declarative class." 

1534 ) 

1535 

1536 if ( 

1537 isinstance(value, tuple) 

1538 and len(value) == 1 

1539 and isinstance(value[0], (Column, _MappedAttribute)) 

1540 ): 

1541 util.warn( 

1542 "Ignoring declarative-like tuple value of attribute " 

1543 "'%s': possibly a copy-and-paste error with a comma " 

1544 "accidentally placed at the end of the line?" % k 

1545 ) 

1546 continue 

1547 elif look_for_dataclass_things and isinstance( 

1548 value, dataclasses.Field 

1549 ): 

1550 # we collected a dataclass Field; dataclasses would have 

1551 # set up the correct state on the class 

1552 continue 

1553 elif not isinstance(value, (Column, _DCAttributeOptions)): 

1554 # using @declared_attr for some object that 

1555 # isn't Column/MapperProperty/_DCAttributeOptions; remove 

1556 # from the clsdict_view 

1557 # and place the evaluated value onto the class. 

1558 collected_attributes.pop(k) 

1559 self._warn_for_decl_attributes(cls, k, value) 

1560 if not late_mapped: 

1561 setattr(cls, k, value) 

1562 continue 

1563 elif isinstance(value, Column): 

1564 _undefer_column_name( 

1565 k, self.column_copies.get(value, value) # type: ignore 

1566 ) 

1567 else: 

1568 if isinstance(value, _IntrospectsAnnotations): 

1569 ( 

1570 annotation, 

1571 mapped_container, 

1572 extracted_mapped_annotation, 

1573 is_dataclass, 

1574 attr_value, 

1575 originating_module, 

1576 originating_class, 

1577 ) = self.collected_annotations.get( 

1578 k, (None, None, None, False, None, None, None) 

1579 ) 

1580 

1581 # issue #8692 - don't do any annotation interpretation if 

1582 # an annotation were present and a container such as 

1583 # Mapped[] etc. were not used. If annotation is None, 

1584 # do declarative_scan so that the property can raise 

1585 # for required 

1586 if ( 

1587 mapped_container is not None 

1588 or annotation is None 

1589 # issue #10516: need to do declarative_scan even with 

1590 # a non-Mapped annotation if we are doing 

1591 # __allow_unmapped__, for things like col.name 

1592 # assignment 

1593 or allow_unmapped_annotations 

1594 ): 

1595 try: 

1596 value.declarative_scan( 

1597 self, 

1598 self.registry, 

1599 cls, 

1600 originating_module, 

1601 k, 

1602 mapped_container, 

1603 annotation, 

1604 extracted_mapped_annotation, 

1605 is_dataclass, 

1606 ) 

1607 except NameError as ne: 

1608 raise orm_exc.MappedAnnotationError( 

1609 f"Could not resolve all types within mapped " 

1610 f'annotation: "{annotation}". Ensure all ' 

1611 f"types are written correctly and are " 

1612 f"imported within the module in use." 

1613 ) from ne 

1614 else: 

1615 # assert that we were expecting annotations 

1616 # without Mapped[] were going to be passed. 

1617 # otherwise an error should have been raised 

1618 # by util._extract_mapped_subtype before we got here. 

1619 assert expect_annotations_wo_mapped 

1620 

1621 if isinstance(value, _DCAttributeOptions): 

1622 if ( 

1623 value._has_dataclass_arguments 

1624 and not look_for_dataclass_things 

1625 ): 

1626 if isinstance(value, MapperProperty): 

1627 argnames = [ 

1628 "init", 

1629 "default_factory", 

1630 "repr", 

1631 "default", 

1632 "dataclass_metadata", 

1633 ] 

1634 else: 

1635 argnames = [ 

1636 "init", 

1637 "default_factory", 

1638 "repr", 

1639 "dataclass_metadata", 

1640 ] 

1641 

1642 args = { 

1643 a 

1644 for a in argnames 

1645 if getattr( 

1646 value._attribute_options, f"dataclasses_{a}" 

1647 ) 

1648 is not _NoArg.NO_ARG 

1649 } 

1650 

1651 raise exc.ArgumentError( 

1652 f"Attribute '{k}' on class {cls} includes " 

1653 f"dataclasses argument(s): " 

1654 f"{', '.join(sorted(repr(a) for a in args))} but " 

1655 f"class does not specify " 

1656 "SQLAlchemy native dataclass configuration." 

1657 ) 

1658 

1659 if not isinstance(value, (MapperProperty, _MapsColumns)): 

1660 # filter for _DCAttributeOptions objects that aren't 

1661 # MapperProperty / mapped_column(). Currently this 

1662 # includes AssociationProxy. pop it from the things 

1663 # we're going to map and set it up as a descriptor 

1664 # on the class. 

1665 collected_attributes.pop(k) 

1666 

1667 # Assoc Prox (or other descriptor object that may 

1668 # use _DCAttributeOptions) is usually here, except if 

1669 # 1. we're a 

1670 # dataclass, dataclasses would have removed the 

1671 # attr here or 2. assoc proxy is coming from a 

1672 # superclass, we want it to be direct here so it 

1673 # tracks state or 3. assoc prox comes from 

1674 # declared_attr, uncommon case 

1675 setattr(cls, k, value) 

1676 continue 

1677 

1678 our_stuff[k] = value 

1679 

1680 def _extract_declared_columns(self) -> None: 

1681 our_stuff = self.properties 

1682 

1683 # extract columns from the class dict 

1684 declared_columns = self.declared_columns 

1685 column_ordering = self.column_ordering 

1686 name_to_prop_key = collections.defaultdict(set) 

1687 

1688 for key, c in list(our_stuff.items()): 

1689 if isinstance(c, _MapsColumns): 

1690 mp_to_assign = c.mapper_property_to_assign 

1691 if mp_to_assign: 

1692 our_stuff[key] = mp_to_assign 

1693 else: 

1694 # if no mapper property to assign, this currently means 

1695 # this is a MappedColumn that will produce a Column for us 

1696 del our_stuff[key] 

1697 

1698 for col, sort_order in c.columns_to_assign: 

1699 if not isinstance(c, CompositeProperty): 

1700 name_to_prop_key[col.name].add(key) 

1701 declared_columns.add(col) 

1702 

1703 # we would assert this, however we want the below 

1704 # warning to take effect instead. See #9630 

1705 # assert col not in column_ordering 

1706 

1707 column_ordering[col] = sort_order 

1708 

1709 # if this is a MappedColumn and the attribute key we 

1710 # have is not what the column has for its key, map the 

1711 # Column explicitly under the attribute key name. 

1712 # otherwise, Mapper will map it under the column key. 

1713 if mp_to_assign is None and key != col.key: 

1714 our_stuff[key] = col 

1715 elif isinstance(c, Column): 

1716 # undefer previously occurred here, and now occurs earlier. 

1717 # ensure every column we get here has been named 

1718 assert c.name is not None 

1719 name_to_prop_key[c.name].add(key) 

1720 declared_columns.add(c) 

1721 # if the column is the same name as the key, 

1722 # remove it from the explicit properties dict. 

1723 # the normal rules for assigning column-based properties 

1724 # will take over, including precedence of columns 

1725 # in multi-column ColumnProperties. 

1726 if key == c.key: 

1727 del our_stuff[key] 

1728 

1729 for name, keys in name_to_prop_key.items(): 

1730 if len(keys) > 1: 

1731 util.warn( 

1732 "On class %r, Column object %r named " 

1733 "directly multiple times, " 

1734 "only one will be used: %s. " 

1735 "Consider using orm.synonym instead" 

1736 % (self.classname, name, (", ".join(sorted(keys)))) 

1737 ) 

1738 

1739 def _setup_table(self, table: Optional[FromClause] = None) -> None: 

1740 cls = self.cls 

1741 cls_as_Decl = cast("MappedClassProtocol[Any]", cls) 

1742 

1743 tablename = self.tablename 

1744 table_args = self.table_args 

1745 clsdict_view = self.clsdict_view 

1746 declared_columns = self.declared_columns 

1747 column_ordering = self.column_ordering 

1748 

1749 manager = attributes.manager_of_class(cls) 

1750 

1751 if ( 

1752 self.table_fn is None 

1753 and "__table__" not in clsdict_view 

1754 and table is None 

1755 ): 

1756 if hasattr(cls, "__table_cls__"): 

1757 table_cls = cast( 

1758 Type[Table], 

1759 util.unbound_method_to_callable(cls.__table_cls__), # type: ignore # noqa: E501 

1760 ) 

1761 else: 

1762 table_cls = Table 

1763 

1764 if tablename is not None: 

1765 args: Tuple[Any, ...] = () 

1766 table_kw: Dict[str, Any] = {} 

1767 

1768 if table_args: 

1769 if isinstance(table_args, dict): 

1770 table_kw = table_args 

1771 elif isinstance(table_args, tuple): 

1772 if isinstance(table_args[-1], dict): 

1773 args, table_kw = table_args[0:-1], table_args[-1] 

1774 else: 

1775 args = table_args 

1776 

1777 autoload_with = clsdict_view.get("__autoload_with__") 

1778 if autoload_with: 

1779 table_kw["autoload_with"] = autoload_with 

1780 

1781 autoload = clsdict_view.get("__autoload__") 

1782 if autoload: 

1783 table_kw["autoload"] = True 

1784 

1785 sorted_columns = sorted( 

1786 declared_columns, 

1787 key=lambda c: column_ordering.get(c, 0), 

1788 ) 

1789 table = self.set_cls_attribute( 

1790 "__table__", 

1791 table_cls( 

1792 tablename, 

1793 self._metadata_for_cls(manager), 

1794 *sorted_columns, 

1795 *args, 

1796 **table_kw, 

1797 ), 

1798 ) 

1799 else: 

1800 if table is None: 

1801 if self.table_fn: 

1802 table = self.set_cls_attribute( 

1803 "__table__", self.table_fn() 

1804 ) 

1805 else: 

1806 table = cls_as_Decl.__table__ 

1807 if declared_columns: 

1808 for c in declared_columns: 

1809 if not table.c.contains_column(c): 

1810 raise exc.ArgumentError( 

1811 "Can't add additional column %r when " 

1812 "specifying __table__" % c.key 

1813 ) 

1814 

1815 self.local_table = table 

1816 

1817 def _metadata_for_cls(self, manager: ClassManager[Any]) -> MetaData: 

1818 return _metadata_for_cls_fn(self.cls, manager.registry) 

1819 

1820 def _setup_inheriting_mapper(self) -> None: 

1821 cls = self.cls 

1822 

1823 inherits = None 

1824 

1825 if inherits is None: 

1826 # since we search for classical mappings now, search for 

1827 # multiple mapped bases as well and raise an error. 

1828 inherits_search = [] 

1829 for base_ in cls.__bases__: 

1830 c = _resolve_for_abstract_or_classical(base_) 

1831 if c is None: 

1832 continue 

1833 

1834 if _is_supercls_for_inherits(c) and c not in inherits_search: 

1835 inherits_search.append(c) 

1836 

1837 if inherits_search: 

1838 if len(inherits_search) > 1: 

1839 raise exc.InvalidRequestError( 

1840 "Class %s has multiple mapped bases: %r" 

1841 % (cls, inherits_search) 

1842 ) 

1843 inherits = inherits_search[0] 

1844 elif isinstance(inherits, Mapper): 

1845 inherits = inherits.class_ 

1846 

1847 self.inherits = inherits 

1848 

1849 clsdict_view = self.clsdict_view 

1850 if "__table__" not in clsdict_view and self.tablename is None: 

1851 self.single = True 

1852 

1853 def _setup_inheriting_columns(self) -> None: 

1854 table = self.local_table 

1855 cls = self.cls 

1856 table_args = self.table_args 

1857 declared_columns = self.declared_columns 

1858 

1859 if ( 

1860 table is None 

1861 and self.inherits is None 

1862 and not _get_immediate_cls_attr(cls, "__no_table__") 

1863 ): 

1864 raise exc.InvalidRequestError( 

1865 "Class %r does not have a __table__ or __tablename__ " 

1866 "specified and does not inherit from an existing " 

1867 "table-mapped class." % cls 

1868 ) 

1869 elif self.inherits: 

1870 inherited_mapper_or_config = _declared_mapping_info(self.inherits) 

1871 assert inherited_mapper_or_config is not None 

1872 inherited_table = inherited_mapper_or_config.local_table 

1873 inherited_persist_selectable = ( 

1874 inherited_mapper_or_config.persist_selectable 

1875 ) 

1876 

1877 if table is None: 

1878 # single table inheritance. 

1879 # ensure no table args 

1880 if table_args: 

1881 raise exc.ArgumentError( 

1882 "Can't place __table_args__ on an inherited class " 

1883 "with no table." 

1884 ) 

1885 

1886 # add any columns declared here to the inherited table. 

1887 if declared_columns and not isinstance(inherited_table, Table): 

1888 raise exc.ArgumentError( 

1889 f"Can't declare columns on single-table-inherited " 

1890 f"subclass {self.cls}; superclass {self.inherits} " 

1891 "is not mapped to a Table" 

1892 ) 

1893 

1894 for col in declared_columns: 

1895 assert inherited_table is not None 

1896 if col.name in inherited_table.c: 

1897 if inherited_table.c[col.name] is col: 

1898 continue 

1899 raise exc.ArgumentError( 

1900 f"Column '{col}' on class {cls.__name__} " 

1901 f"conflicts with existing column " 

1902 f"'{inherited_table.c[col.name]}'. If using " 

1903 f"Declarative, consider using the " 

1904 "use_existing_column parameter of mapped_column() " 

1905 "to resolve conflicts." 

1906 ) 

1907 if col.primary_key: 

1908 raise exc.ArgumentError( 

1909 "Can't place primary key columns on an inherited " 

1910 "class with no table." 

1911 ) 

1912 

1913 if TYPE_CHECKING: 

1914 assert isinstance(inherited_table, Table) 

1915 

1916 inherited_table.append_column(col) 

1917 if ( 

1918 inherited_persist_selectable is not None 

1919 and inherited_persist_selectable is not inherited_table 

1920 ): 

1921 inherited_persist_selectable._refresh_for_new_column( 

1922 col 

1923 ) 

1924 

1925 def _prepare_mapper_arguments(self, mapper_kw: _MapperKwArgs) -> None: 

1926 properties = self.properties 

1927 

1928 if self.mapper_args_fn: 

1929 mapper_args = self.mapper_args_fn() 

1930 else: 

1931 mapper_args = {} 

1932 

1933 if mapper_kw: 

1934 mapper_args.update(mapper_kw) 

1935 

1936 if "properties" in mapper_args: 

1937 properties = dict(properties) 

1938 properties.update(mapper_args["properties"]) 

1939 

1940 # make sure that column copies are used rather 

1941 # than the original columns from any mixins 

1942 for k in ("version_id_col", "polymorphic_on"): 

1943 if k in mapper_args: 

1944 v = mapper_args[k] 

1945 mapper_args[k] = self.column_copies.get(v, v) 

1946 

1947 if "primary_key" in mapper_args: 

1948 mapper_args["primary_key"] = [ 

1949 self.column_copies.get(v, v) 

1950 for v in util.to_list(mapper_args["primary_key"]) 

1951 ] 

1952 

1953 if "inherits" in mapper_args: 

1954 inherits_arg = mapper_args["inherits"] 

1955 if isinstance(inherits_arg, Mapper): 

1956 inherits_arg = inherits_arg.class_ 

1957 

1958 if inherits_arg is not self.inherits: 

1959 raise exc.InvalidRequestError( 

1960 "mapper inherits argument given for non-inheriting " 

1961 "class %s" % (mapper_args["inherits"]) 

1962 ) 

1963 

1964 if self.inherits: 

1965 mapper_args["inherits"] = self.inherits 

1966 

1967 if self.inherits and not mapper_args.get("concrete", False): 

1968 # note the superclass is expected to have a Mapper assigned and 

1969 # not be a deferred config, as this is called within map() 

1970 inherited_mapper = class_mapper(self.inherits, False) 

1971 inherited_table = inherited_mapper.local_table 

1972 

1973 # single or joined inheritance 

1974 # exclude any cols on the inherited table which are 

1975 # not mapped on the parent class, to avoid 

1976 # mapping columns specific to sibling/nephew classes 

1977 if "exclude_properties" not in mapper_args: 

1978 mapper_args["exclude_properties"] = exclude_properties = { 

1979 c.key 

1980 for c in inherited_table.c 

1981 if c not in inherited_mapper._columntoproperty 

1982 }.union(inherited_mapper.exclude_properties or ()) 

1983 exclude_properties.difference_update( 

1984 [c.key for c in self.declared_columns] 

1985 ) 

1986 

1987 # look through columns in the current mapper that 

1988 # are keyed to a propname different than the colname 

1989 # (if names were the same, we'd have popped it out above, 

1990 # in which case the mapper makes this combination). 

1991 # See if the superclass has a similar column property. 

1992 # If so, join them together. 

1993 for k, col in list(properties.items()): 

1994 if not isinstance(col, expression.ColumnElement): 

1995 continue 

1996 if k in inherited_mapper._props: 

1997 p = inherited_mapper._props[k] 

1998 if isinstance(p, ColumnProperty): 

1999 # note here we place the subclass column 

2000 # first. See [ticket:1892] for background. 

2001 properties[k] = [col] + p.columns 

2002 result_mapper_args = mapper_args.copy() 

2003 result_mapper_args["properties"] = properties 

2004 self.mapper_args = result_mapper_args 

2005 

2006 def map(self, mapper_kw: _MapperKwArgs = util.EMPTY_DICT) -> Mapper[Any]: 

2007 self._prepare_mapper_arguments(mapper_kw) 

2008 if hasattr(self.cls, "__mapper_cls__"): 

2009 mapper_cls = cast( 

2010 "Type[Mapper[Any]]", 

2011 util.unbound_method_to_callable( 

2012 self.cls.__mapper_cls__ # type: ignore 

2013 ), 

2014 ) 

2015 else: 

2016 mapper_cls = Mapper 

2017 

2018 return self.set_cls_attribute( 

2019 "__mapper__", 

2020 mapper_cls(self.cls, self.local_table, **self.mapper_args), 

2021 ) 

2022 

2023 

2024class _UnmappedDataclassConfig(_ClassScanAbstractConfig): 

2025 """Configurator that will produce an unmapped dataclass.""" 

2026 

2027 __slots__ = ( 

2028 "clsdict_view", 

2029 "collected_attributes", 

2030 "collected_annotations", 

2031 "allow_dataclass_fields", 

2032 "dataclass_setup_arguments", 

2033 "is_dataclass_prior_to_mapping", 

2034 "allow_unmapped_annotations", 

2035 ) 

2036 

2037 def __init__( 

2038 self, 

2039 cls_: Type[_O], 

2040 dict_: _ClassDict, 

2041 ): 

2042 super().__init__(cls_) 

2043 self.clsdict_view = ( 

2044 util.immutabledict(dict_) if dict_ else util.EMPTY_DICT 

2045 ) 

2046 self.dataclass_setup_arguments = getattr( 

2047 self.cls, "_sa_apply_dc_transforms", None 

2048 ) 

2049 

2050 self.is_dataclass_prior_to_mapping = dataclasses.is_dataclass(cls_) 

2051 self.allow_dataclass_fields = False 

2052 self.allow_unmapped_annotations = True 

2053 self.collected_attributes = {} 

2054 self.collected_annotations = {} 

2055 

2056 self._scan_attributes() 

2057 

2058 self._setup_dataclasses_transforms( 

2059 enable_descriptor_defaults=False, revert=True 

2060 ) 

2061 

2062 def _scan_attributes(self) -> None: 

2063 cls = self.cls 

2064 

2065 clsdict_view = self.clsdict_view 

2066 collected_attributes = self.collected_attributes 

2067 _include_dunders = self._include_dunders 

2068 

2069 attribute_is_overridden = self._cls_attr_override_checker(self.cls) 

2070 

2071 local_attributes_for_class = self._cls_attr_resolver(cls) 

2072 for ( 

2073 name, 

2074 obj, 

2075 annotation, 

2076 is_dataclass_field, 

2077 ) in local_attributes_for_class(): 

2078 if name in _include_dunders: 

2079 continue 

2080 elif is_dataclass_field and ( 

2081 name not in clsdict_view or clsdict_view[name] is not obj 

2082 ): 

2083 # here, we are definitely looking at the target class 

2084 # and not a superclass. this is currently a 

2085 # dataclass-only path. if the name is only 

2086 # a dataclass field and isn't in local cls.__dict__, 

2087 # put the object there. 

2088 # assert that the dataclass-enabled resolver agrees 

2089 # with what we are seeing 

2090 

2091 assert not attribute_is_overridden(name, obj) 

2092 

2093 if _is_declarative_props(obj): 

2094 obj = obj.fget() 

2095 

2096 collected_attributes[name] = obj 

2097 self._collect_annotation(name, annotation, cls, False, obj) 

2098 else: 

2099 self._collect_annotation(name, annotation, cls, None, obj) 

2100 if name in clsdict_view: 

2101 collected_attributes[name] = obj 

2102 

2103 

2104@util.preload_module("sqlalchemy.orm.decl_api") 

2105def _as_dc_declaredattr( 

2106 field_metadata: Mapping[str, Any], sa_dataclass_metadata_key: str 

2107) -> Any: 

2108 # wrap lambdas inside dataclass fields inside an ad-hoc declared_attr. 

2109 # we can't write it because field.metadata is immutable :( so we have 

2110 # to go through extra trouble to compare these 

2111 decl_api = util.preloaded.orm_decl_api 

2112 obj = field_metadata[sa_dataclass_metadata_key] 

2113 if callable(obj) and not isinstance(obj, decl_api.declared_attr): 

2114 return decl_api.declared_attr(obj) 

2115 else: 

2116 return obj 

2117 

2118 

2119class _DeferredDeclarativeConfig(_DeclarativeMapperConfig): 

2120 """Configurator that extends _DeclarativeMapperConfig to add a 

2121 "deferred" step, to allow extensions like AbstractConcreteBase, 

2122 DeferredMapping to partially set up a mapping that is "prepared" 

2123 when table metadata is ready. 

2124 

2125 """ 

2126 

2127 _cls: weakref.ref[Type[Any]] 

2128 

2129 is_deferred = True 

2130 

2131 _configs: util.OrderedDict[ 

2132 weakref.ref[Type[Any]], _DeferredDeclarativeConfig 

2133 ] = util.OrderedDict() 

2134 

2135 def _early_mapping(self, mapper_kw: _MapperKwArgs) -> None: 

2136 pass 

2137 

2138 @property 

2139 def cls(self) -> Type[Any]: 

2140 return self._cls() # type: ignore 

2141 

2142 @cls.setter 

2143 def cls(self, class_: Type[Any]) -> None: 

2144 self._cls = weakref.ref(class_, self._remove_config_cls) 

2145 self._configs[self._cls] = self 

2146 

2147 @classmethod 

2148 def _remove_config_cls(cls, ref: weakref.ref[Type[Any]]) -> None: 

2149 cls._configs.pop(ref, None) 

2150 

2151 @classmethod 

2152 def has_cls(cls, class_: Type[Any]) -> bool: 

2153 # 2.6 fails on weakref if class_ is an old style class 

2154 return isinstance(class_, type) and weakref.ref(class_) in cls._configs 

2155 

2156 @classmethod 

2157 def raise_unmapped_for_cls(cls, class_: Type[Any]) -> NoReturn: 

2158 if hasattr(class_, "_sa_raise_deferred_config"): 

2159 class_._sa_raise_deferred_config() 

2160 

2161 raise orm_exc.UnmappedClassError( 

2162 class_, 

2163 msg=( 

2164 f"Class {orm_exc._safe_cls_name(class_)} has a deferred " 

2165 "mapping on it. It is not yet usable as a mapped class." 

2166 ), 

2167 ) 

2168 

2169 @classmethod 

2170 def config_for_cls(cls, class_: Type[Any]) -> _DeferredDeclarativeConfig: 

2171 return cls._configs[weakref.ref(class_)] 

2172 

2173 @classmethod 

2174 def classes_for_base( 

2175 cls, base_cls: Type[Any], sort: bool = True 

2176 ) -> List[_DeferredDeclarativeConfig]: 

2177 classes_for_base = [ 

2178 m 

2179 for m, cls_ in [(m, m.cls) for m in cls._configs.values()] 

2180 if cls_ is not None and issubclass(cls_, base_cls) 

2181 ] 

2182 

2183 if not sort: 

2184 return classes_for_base 

2185 

2186 all_m_by_cls = {m.cls: m for m in classes_for_base} 

2187 

2188 tuples: List[ 

2189 Tuple[_DeferredDeclarativeConfig, _DeferredDeclarativeConfig] 

2190 ] = [] 

2191 for m_cls in all_m_by_cls: 

2192 tuples.extend( 

2193 (all_m_by_cls[base_cls], all_m_by_cls[m_cls]) 

2194 for base_cls in m_cls.__bases__ 

2195 if base_cls in all_m_by_cls 

2196 ) 

2197 return list(topological.sort(tuples, classes_for_base)) 

2198 

2199 def map(self, mapper_kw: _MapperKwArgs = util.EMPTY_DICT) -> Mapper[Any]: 

2200 self._configs.pop(self._cls, None) 

2201 return super().map(mapper_kw) 

2202 

2203 

2204def _add_attribute( 

2205 cls: Type[Any], key: str, value: MapperProperty[Any] 

2206) -> None: 

2207 """add an attribute to an existing declarative class. 

2208 

2209 This runs through the logic to determine MapperProperty, 

2210 adds it to the Mapper, adds a column to the mapped Table, etc. 

2211 

2212 """ 

2213 

2214 if "__mapper__" in cls.__dict__: 

2215 mapped_cls = cast("MappedClassProtocol[Any]", cls) 

2216 

2217 def _table_or_raise(mc: MappedClassProtocol[Any]) -> Table: 

2218 if isinstance(mc.__table__, Table): 

2219 return mc.__table__ 

2220 raise exc.InvalidRequestError( 

2221 f"Cannot add a new attribute to mapped class {mc.__name__!r} " 

2222 "because it's not mapped against a table." 

2223 ) 

2224 

2225 if isinstance(value, Column): 

2226 _undefer_column_name(key, value) 

2227 _table_or_raise(mapped_cls).append_column( 

2228 value, replace_existing=True 

2229 ) 

2230 mapped_cls.__mapper__.add_property(key, value) 

2231 elif isinstance(value, _MapsColumns): 

2232 mp = value.mapper_property_to_assign 

2233 for col, _ in value.columns_to_assign: 

2234 _undefer_column_name(key, col) 

2235 _table_or_raise(mapped_cls).append_column( 

2236 col, replace_existing=True 

2237 ) 

2238 if not mp: 

2239 mapped_cls.__mapper__.add_property(key, col) 

2240 if mp: 

2241 mapped_cls.__mapper__.add_property(key, mp) 

2242 elif isinstance(value, MapperProperty): 

2243 mapped_cls.__mapper__.add_property(key, value) 

2244 elif isinstance(value, QueryableAttribute) and value.key != key: 

2245 # detect a QueryableAttribute that's already mapped being 

2246 # assigned elsewhere in userland, turn into a synonym() 

2247 value = SynonymProperty(value.key) 

2248 mapped_cls.__mapper__.add_property(key, value) 

2249 else: 

2250 type.__setattr__(cls, key, value) 

2251 mapped_cls.__mapper__._expire_memoizations() 

2252 else: 

2253 type.__setattr__(cls, key, value) 

2254 

2255 

2256def _del_attribute(cls: Type[Any], key: str) -> None: 

2257 if ( 

2258 "__mapper__" in cls.__dict__ 

2259 and key in cls.__dict__ 

2260 and not cast( 

2261 "MappedClassProtocol[Any]", cls 

2262 ).__mapper__._dispose_called 

2263 ): 

2264 value = cls.__dict__[key] 

2265 if isinstance( 

2266 value, (Column, _MapsColumns, MapperProperty, QueryableAttribute) 

2267 ): 

2268 raise NotImplementedError( 

2269 "Can't un-map individual mapped attributes on a mapped class." 

2270 ) 

2271 else: 

2272 type.__delattr__(cls, key) 

2273 cast( 

2274 "MappedClassProtocol[Any]", cls 

2275 ).__mapper__._expire_memoizations() 

2276 else: 

2277 type.__delattr__(cls, key) 

2278 

2279 

2280def _declarative_constructor(self: Any, **kwargs: Any) -> None: 

2281 """A simple constructor that allows initialization from kwargs. 

2282 

2283 Sets attributes on the constructed instance using the names and 

2284 values in ``kwargs``. 

2285 

2286 Only keys that are present as 

2287 attributes of the instance's class are allowed. These could be, 

2288 for example, any mapped columns or relationships. 

2289 """ 

2290 cls_ = type(self) 

2291 for k in kwargs: 

2292 if not hasattr(cls_, k): 

2293 raise TypeError( 

2294 "%r is an invalid keyword argument for %s" % (k, cls_.__name__) 

2295 ) 

2296 setattr(self, k, kwargs[k]) 

2297 

2298 

2299_declarative_constructor.__name__ = "__init__" 

2300 

2301 

2302def _undefer_column_name(key: str, column: Column[Any]) -> None: 

2303 if column.key is None: 

2304 column.key = key 

2305 if column.name is None: 

2306 column.name = key