Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/sqlalchemy/orm/descriptor_props.py: 29%

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

480 statements  

1# orm/descriptor_props.py 

2# Copyright (C) 2005-2024 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"""Descriptor properties are more "auxiliary" properties 

9that exist as configurational elements, but don't participate 

10as actively in the load/persist ORM loop. 

11 

12""" 

13from __future__ import annotations 

14 

15from dataclasses import is_dataclass 

16import inspect 

17import itertools 

18import operator 

19import typing 

20from typing import Any 

21from typing import Callable 

22from typing import Dict 

23from typing import List 

24from typing import NoReturn 

25from typing import Optional 

26from typing import Sequence 

27from typing import Tuple 

28from typing import Type 

29from typing import TYPE_CHECKING 

30from typing import TypeVar 

31from typing import Union 

32import weakref 

33 

34from . import attributes 

35from . import util as orm_util 

36from .base import _DeclarativeMapped 

37from .base import LoaderCallableStatus 

38from .base import Mapped 

39from .base import PassiveFlag 

40from .base import SQLORMOperations 

41from .interfaces import _AttributeOptions 

42from .interfaces import _IntrospectsAnnotations 

43from .interfaces import _MapsColumns 

44from .interfaces import MapperProperty 

45from .interfaces import PropComparator 

46from .util import _none_set 

47from .util import de_stringify_annotation 

48from .. import event 

49from .. import exc as sa_exc 

50from .. import schema 

51from .. import sql 

52from .. import util 

53from ..sql import expression 

54from ..sql import operators 

55from ..sql.elements import BindParameter 

56from ..util.typing import is_fwd_ref 

57from ..util.typing import is_pep593 

58from ..util.typing import TupleAny 

59from ..util.typing import typing_get_args 

60from ..util.typing import Unpack 

61 

62 

63if typing.TYPE_CHECKING: 

64 from ._typing import _InstanceDict 

65 from ._typing import _RegistryType 

66 from .attributes import History 

67 from .attributes import InstrumentedAttribute 

68 from .attributes import QueryableAttribute 

69 from .context import ORMCompileState 

70 from .decl_base import _ClassScanMapperConfig 

71 from .mapper import Mapper 

72 from .properties import ColumnProperty 

73 from .properties import MappedColumn 

74 from .state import InstanceState 

75 from ..engine.base import Connection 

76 from ..engine.row import Row 

77 from ..sql._typing import _DMLColumnArgument 

78 from ..sql._typing import _InfoType 

79 from ..sql.elements import ClauseList 

80 from ..sql.elements import ColumnElement 

81 from ..sql.operators import OperatorType 

82 from ..sql.schema import Column 

83 from ..sql.selectable import Select 

84 from ..util.typing import _AnnotationScanType 

85 from ..util.typing import CallableReference 

86 from ..util.typing import DescriptorReference 

87 from ..util.typing import RODescriptorReference 

88 

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

90_PT = TypeVar("_PT", bound=Any) 

91 

92 

93class DescriptorProperty(MapperProperty[_T]): 

94 """:class:`.MapperProperty` which proxies access to a 

95 user-defined descriptor.""" 

96 

97 doc: Optional[str] = None 

98 

99 uses_objects = False 

100 _links_to_entity = False 

101 

102 descriptor: DescriptorReference[Any] 

103 

104 def get_history( 

105 self, 

106 state: InstanceState[Any], 

107 dict_: _InstanceDict, 

108 passive: PassiveFlag = PassiveFlag.PASSIVE_OFF, 

109 ) -> History: 

110 raise NotImplementedError() 

111 

112 def instrument_class(self, mapper: Mapper[Any]) -> None: 

113 prop = self 

114 

115 class _ProxyImpl(attributes.AttributeImpl): 

116 accepts_scalar_loader = False 

117 load_on_unexpire = True 

118 collection = False 

119 

120 @property 

121 def uses_objects(self) -> bool: # type: ignore 

122 return prop.uses_objects 

123 

124 def __init__(self, key: str): 

125 self.key = key 

126 

127 def get_history( 

128 self, 

129 state: InstanceState[Any], 

130 dict_: _InstanceDict, 

131 passive: PassiveFlag = PassiveFlag.PASSIVE_OFF, 

132 ) -> History: 

133 return prop.get_history(state, dict_, passive) 

134 

135 if self.descriptor is None: 

136 desc = getattr(mapper.class_, self.key, None) 

137 if mapper._is_userland_descriptor(self.key, desc): 

138 self.descriptor = desc 

139 

140 if self.descriptor is None: 

141 

142 def fset(obj: Any, value: Any) -> None: 

143 setattr(obj, self.name, value) 

144 

145 def fdel(obj: Any) -> None: 

146 delattr(obj, self.name) 

147 

148 def fget(obj: Any) -> Any: 

149 return getattr(obj, self.name) 

150 

151 self.descriptor = property(fget=fget, fset=fset, fdel=fdel) 

152 

153 proxy_attr = attributes.create_proxied_attribute(self.descriptor)( 

154 self.parent.class_, 

155 self.key, 

156 self.descriptor, 

157 lambda: self._comparator_factory(mapper), 

158 doc=self.doc, 

159 original_property=self, 

160 ) 

161 proxy_attr.impl = _ProxyImpl(self.key) 

162 mapper.class_manager.instrument_attribute(self.key, proxy_attr) 

163 

164 

165_CompositeAttrType = Union[ 

166 str, 

167 "Column[_T]", 

168 "MappedColumn[_T]", 

169 "InstrumentedAttribute[_T]", 

170 "Mapped[_T]", 

171] 

172 

173 

174_CC = TypeVar("_CC", bound=Any) 

175 

176 

177_composite_getters: weakref.WeakKeyDictionary[ 

178 Type[Any], Callable[[Any], Tuple[Any, ...]] 

179] = weakref.WeakKeyDictionary() 

180 

181 

182class CompositeProperty( 

183 _MapsColumns[_CC], _IntrospectsAnnotations, DescriptorProperty[_CC] 

184): 

185 """Defines a "composite" mapped attribute, representing a collection 

186 of columns as one attribute. 

187 

188 :class:`.CompositeProperty` is constructed using the :func:`.composite` 

189 function. 

190 

191 .. seealso:: 

192 

193 :ref:`mapper_composite` 

194 

195 """ 

196 

197 composite_class: Union[Type[_CC], Callable[..., _CC]] 

198 attrs: Tuple[_CompositeAttrType[Any], ...] 

199 

200 _generated_composite_accessor: CallableReference[ 

201 Optional[Callable[[_CC], Tuple[Any, ...]]] 

202 ] 

203 

204 comparator_factory: Type[Comparator[_CC]] 

205 

206 def __init__( 

207 self, 

208 _class_or_attr: Union[ 

209 None, Type[_CC], Callable[..., _CC], _CompositeAttrType[Any] 

210 ] = None, 

211 *attrs: _CompositeAttrType[Any], 

212 attribute_options: Optional[_AttributeOptions] = None, 

213 active_history: bool = False, 

214 deferred: bool = False, 

215 group: Optional[str] = None, 

216 comparator_factory: Optional[Type[Comparator[_CC]]] = None, 

217 info: Optional[_InfoType] = None, 

218 **kwargs: Any, 

219 ): 

220 super().__init__(attribute_options=attribute_options) 

221 

222 if isinstance(_class_or_attr, (Mapped, str, sql.ColumnElement)): 

223 self.attrs = (_class_or_attr,) + attrs 

224 # will initialize within declarative_scan 

225 self.composite_class = None # type: ignore 

226 else: 

227 self.composite_class = _class_or_attr # type: ignore 

228 self.attrs = attrs 

229 

230 self.active_history = active_history 

231 self.deferred = deferred 

232 self.group = group 

233 self.comparator_factory = ( 

234 comparator_factory 

235 if comparator_factory is not None 

236 else self.__class__.Comparator 

237 ) 

238 self._generated_composite_accessor = None 

239 if info is not None: 

240 self.info.update(info) 

241 

242 util.set_creation_order(self) 

243 self._create_descriptor() 

244 self._init_accessor() 

245 

246 def instrument_class(self, mapper: Mapper[Any]) -> None: 

247 super().instrument_class(mapper) 

248 self._setup_event_handlers() 

249 

250 def _composite_values_from_instance(self, value: _CC) -> Tuple[Any, ...]: 

251 if self._generated_composite_accessor: 

252 return self._generated_composite_accessor(value) 

253 else: 

254 try: 

255 accessor = value.__composite_values__ 

256 except AttributeError as ae: 

257 raise sa_exc.InvalidRequestError( 

258 f"Composite class {self.composite_class.__name__} is not " 

259 f"a dataclass and does not define a __composite_values__()" 

260 " method; can't get state" 

261 ) from ae 

262 else: 

263 return accessor() # type: ignore 

264 

265 def do_init(self) -> None: 

266 """Initialization which occurs after the :class:`.Composite` 

267 has been associated with its parent mapper. 

268 

269 """ 

270 self._setup_arguments_on_columns() 

271 

272 _COMPOSITE_FGET = object() 

273 

274 def _create_descriptor(self) -> None: 

275 """Create the Python descriptor that will serve as 

276 the access point on instances of the mapped class. 

277 

278 """ 

279 

280 def fget(instance: Any) -> Any: 

281 dict_ = attributes.instance_dict(instance) 

282 state = attributes.instance_state(instance) 

283 

284 if self.key not in dict_: 

285 # key not present. Iterate through related 

286 # attributes, retrieve their values. This 

287 # ensures they all load. 

288 values = [ 

289 getattr(instance, key) for key in self._attribute_keys 

290 ] 

291 

292 # current expected behavior here is that the composite is 

293 # created on access if the object is persistent or if 

294 # col attributes have non-None. This would be better 

295 # if the composite were created unconditionally, 

296 # but that would be a behavioral change. 

297 if self.key not in dict_ and ( 

298 state.key is not None or not _none_set.issuperset(values) 

299 ): 

300 dict_[self.key] = self.composite_class(*values) 

301 state.manager.dispatch.refresh( 

302 state, self._COMPOSITE_FGET, [self.key] 

303 ) 

304 

305 return dict_.get(self.key, None) 

306 

307 def fset(instance: Any, value: Any) -> None: 

308 dict_ = attributes.instance_dict(instance) 

309 state = attributes.instance_state(instance) 

310 attr = state.manager[self.key] 

311 

312 if attr.dispatch._active_history: 

313 previous = fget(instance) 

314 else: 

315 previous = dict_.get(self.key, LoaderCallableStatus.NO_VALUE) 

316 

317 for fn in attr.dispatch.set: 

318 value = fn(state, value, previous, attr.impl) 

319 dict_[self.key] = value 

320 if value is None: 

321 for key in self._attribute_keys: 

322 setattr(instance, key, None) 

323 else: 

324 for key, value in zip( 

325 self._attribute_keys, 

326 self._composite_values_from_instance(value), 

327 ): 

328 setattr(instance, key, value) 

329 

330 def fdel(instance: Any) -> None: 

331 state = attributes.instance_state(instance) 

332 dict_ = attributes.instance_dict(instance) 

333 attr = state.manager[self.key] 

334 

335 if attr.dispatch._active_history: 

336 previous = fget(instance) 

337 dict_.pop(self.key, None) 

338 else: 

339 previous = dict_.pop(self.key, LoaderCallableStatus.NO_VALUE) 

340 

341 attr = state.manager[self.key] 

342 attr.dispatch.remove(state, previous, attr.impl) 

343 for key in self._attribute_keys: 

344 setattr(instance, key, None) 

345 

346 self.descriptor = property(fget, fset, fdel) 

347 

348 @util.preload_module("sqlalchemy.orm.properties") 

349 def declarative_scan( 

350 self, 

351 decl_scan: _ClassScanMapperConfig, 

352 registry: _RegistryType, 

353 cls: Type[Any], 

354 originating_module: Optional[str], 

355 key: str, 

356 mapped_container: Optional[Type[Mapped[Any]]], 

357 annotation: Optional[_AnnotationScanType], 

358 extracted_mapped_annotation: Optional[_AnnotationScanType], 

359 is_dataclass_field: bool, 

360 ) -> None: 

361 MappedColumn = util.preloaded.orm_properties.MappedColumn 

362 if ( 

363 self.composite_class is None 

364 and extracted_mapped_annotation is None 

365 ): 

366 self._raise_for_required(key, cls) 

367 argument = extracted_mapped_annotation 

368 

369 if is_pep593(argument): 

370 argument = typing_get_args(argument)[0] 

371 

372 if argument and self.composite_class is None: 

373 if isinstance(argument, str) or is_fwd_ref( 

374 argument, check_generic=True 

375 ): 

376 if originating_module is None: 

377 str_arg = ( 

378 argument.__forward_arg__ 

379 if hasattr(argument, "__forward_arg__") 

380 else str(argument) 

381 ) 

382 raise sa_exc.ArgumentError( 

383 f"Can't use forward ref {argument} for composite " 

384 f"class argument; set up the type as Mapped[{str_arg}]" 

385 ) 

386 argument = de_stringify_annotation( 

387 cls, argument, originating_module, include_generic=True 

388 ) 

389 

390 self.composite_class = argument 

391 

392 if is_dataclass(self.composite_class): 

393 self._setup_for_dataclass(registry, cls, originating_module, key) 

394 else: 

395 for attr in self.attrs: 

396 if ( 

397 isinstance(attr, (MappedColumn, schema.Column)) 

398 and attr.name is None 

399 ): 

400 raise sa_exc.ArgumentError( 

401 "Composite class column arguments must be named " 

402 "unless a dataclass is used" 

403 ) 

404 self._init_accessor() 

405 

406 def _init_accessor(self) -> None: 

407 if is_dataclass(self.composite_class) and not hasattr( 

408 self.composite_class, "__composite_values__" 

409 ): 

410 insp = inspect.signature(self.composite_class) 

411 getter = operator.attrgetter( 

412 *[p.name for p in insp.parameters.values()] 

413 ) 

414 if len(insp.parameters) == 1: 

415 self._generated_composite_accessor = lambda obj: (getter(obj),) 

416 else: 

417 self._generated_composite_accessor = getter 

418 

419 if ( 

420 self.composite_class is not None 

421 and isinstance(self.composite_class, type) 

422 and self.composite_class not in _composite_getters 

423 ): 

424 if self._generated_composite_accessor is not None: 

425 _composite_getters[self.composite_class] = ( 

426 self._generated_composite_accessor 

427 ) 

428 elif hasattr(self.composite_class, "__composite_values__"): 

429 _composite_getters[self.composite_class] = ( 

430 lambda obj: obj.__composite_values__() 

431 ) 

432 

433 @util.preload_module("sqlalchemy.orm.properties") 

434 @util.preload_module("sqlalchemy.orm.decl_base") 

435 def _setup_for_dataclass( 

436 self, 

437 registry: _RegistryType, 

438 cls: Type[Any], 

439 originating_module: Optional[str], 

440 key: str, 

441 ) -> None: 

442 MappedColumn = util.preloaded.orm_properties.MappedColumn 

443 

444 decl_base = util.preloaded.orm_decl_base 

445 

446 insp = inspect.signature(self.composite_class) 

447 for param, attr in itertools.zip_longest( 

448 insp.parameters.values(), self.attrs 

449 ): 

450 if param is None: 

451 raise sa_exc.ArgumentError( 

452 f"number of composite attributes " 

453 f"{len(self.attrs)} exceeds " 

454 f"that of the number of attributes in class " 

455 f"{self.composite_class.__name__} {len(insp.parameters)}" 

456 ) 

457 if attr is None: 

458 # fill in missing attr spots with empty MappedColumn 

459 attr = MappedColumn() 

460 self.attrs += (attr,) 

461 

462 if isinstance(attr, MappedColumn): 

463 attr.declarative_scan_for_composite( 

464 registry, 

465 cls, 

466 originating_module, 

467 key, 

468 param.name, 

469 param.annotation, 

470 ) 

471 elif isinstance(attr, schema.Column): 

472 decl_base._undefer_column_name(param.name, attr) 

473 

474 @util.memoized_property 

475 def _comparable_elements(self) -> Sequence[QueryableAttribute[Any]]: 

476 return [getattr(self.parent.class_, prop.key) for prop in self.props] 

477 

478 @util.memoized_property 

479 @util.preload_module("orm.properties") 

480 def props(self) -> Sequence[MapperProperty[Any]]: 

481 props = [] 

482 MappedColumn = util.preloaded.orm_properties.MappedColumn 

483 

484 for attr in self.attrs: 

485 if isinstance(attr, str): 

486 prop = self.parent.get_property(attr, _configure_mappers=False) 

487 elif isinstance(attr, schema.Column): 

488 prop = self.parent._columntoproperty[attr] 

489 elif isinstance(attr, MappedColumn): 

490 prop = self.parent._columntoproperty[attr.column] 

491 elif isinstance(attr, attributes.InstrumentedAttribute): 

492 prop = attr.property 

493 else: 

494 prop = None 

495 

496 if not isinstance(prop, MapperProperty): 

497 raise sa_exc.ArgumentError( 

498 "Composite expects Column objects or mapped " 

499 f"attributes/attribute names as arguments, got: {attr!r}" 

500 ) 

501 

502 props.append(prop) 

503 return props 

504 

505 @util.non_memoized_property 

506 @util.preload_module("orm.properties") 

507 def columns(self) -> Sequence[Column[Any]]: 

508 MappedColumn = util.preloaded.orm_properties.MappedColumn 

509 return [ 

510 a.column if isinstance(a, MappedColumn) else a 

511 for a in self.attrs 

512 if isinstance(a, (schema.Column, MappedColumn)) 

513 ] 

514 

515 @property 

516 def mapper_property_to_assign(self) -> Optional[MapperProperty[_CC]]: 

517 return self 

518 

519 @property 

520 def columns_to_assign(self) -> List[Tuple[schema.Column[Any], int]]: 

521 return [(c, 0) for c in self.columns if c.table is None] 

522 

523 @util.preload_module("orm.properties") 

524 def _setup_arguments_on_columns(self) -> None: 

525 """Propagate configuration arguments made on this composite 

526 to the target columns, for those that apply. 

527 

528 """ 

529 ColumnProperty = util.preloaded.orm_properties.ColumnProperty 

530 

531 for prop in self.props: 

532 if not isinstance(prop, ColumnProperty): 

533 continue 

534 else: 

535 cprop = prop 

536 

537 cprop.active_history = self.active_history 

538 if self.deferred: 

539 cprop.deferred = self.deferred 

540 cprop.strategy_key = (("deferred", True), ("instrument", True)) 

541 cprop.group = self.group 

542 

543 def _setup_event_handlers(self) -> None: 

544 """Establish events that populate/expire the composite attribute.""" 

545 

546 def load_handler( 

547 state: InstanceState[Any], context: ORMCompileState 

548 ) -> None: 

549 _load_refresh_handler(state, context, None, is_refresh=False) 

550 

551 def refresh_handler( 

552 state: InstanceState[Any], 

553 context: ORMCompileState, 

554 to_load: Optional[Sequence[str]], 

555 ) -> None: 

556 # note this corresponds to sqlalchemy.ext.mutable load_attrs() 

557 

558 if not to_load or ( 

559 {self.key}.union(self._attribute_keys) 

560 ).intersection(to_load): 

561 _load_refresh_handler(state, context, to_load, is_refresh=True) 

562 

563 def _load_refresh_handler( 

564 state: InstanceState[Any], 

565 context: ORMCompileState, 

566 to_load: Optional[Sequence[str]], 

567 is_refresh: bool, 

568 ) -> None: 

569 dict_ = state.dict 

570 

571 # if context indicates we are coming from the 

572 # fget() handler, this already set the value; skip the 

573 # handler here. (other handlers like mutablecomposite will still 

574 # want to catch it) 

575 # there's an insufficiency here in that the fget() handler 

576 # really should not be using the refresh event and there should 

577 # be some other event that mutablecomposite can subscribe 

578 # towards for this. 

579 

580 if ( 

581 not is_refresh or context is self._COMPOSITE_FGET 

582 ) and self.key in dict_: 

583 return 

584 

585 # if column elements aren't loaded, skip. 

586 # __get__() will initiate a load for those 

587 # columns 

588 for k in self._attribute_keys: 

589 if k not in dict_: 

590 return 

591 

592 dict_[self.key] = self.composite_class( 

593 *[state.dict[key] for key in self._attribute_keys] 

594 ) 

595 

596 def expire_handler( 

597 state: InstanceState[Any], keys: Optional[Sequence[str]] 

598 ) -> None: 

599 if keys is None or set(self._attribute_keys).intersection(keys): 

600 state.dict.pop(self.key, None) 

601 

602 def insert_update_handler( 

603 mapper: Mapper[Any], 

604 connection: Connection, 

605 state: InstanceState[Any], 

606 ) -> None: 

607 """After an insert or update, some columns may be expired due 

608 to server side defaults, or re-populated due to client side 

609 defaults. Pop out the composite value here so that it 

610 recreates. 

611 

612 """ 

613 

614 state.dict.pop(self.key, None) 

615 

616 event.listen( 

617 self.parent, "after_insert", insert_update_handler, raw=True 

618 ) 

619 event.listen( 

620 self.parent, "after_update", insert_update_handler, raw=True 

621 ) 

622 event.listen( 

623 self.parent, "load", load_handler, raw=True, propagate=True 

624 ) 

625 event.listen( 

626 self.parent, "refresh", refresh_handler, raw=True, propagate=True 

627 ) 

628 event.listen( 

629 self.parent, "expire", expire_handler, raw=True, propagate=True 

630 ) 

631 

632 proxy_attr = self.parent.class_manager[self.key] 

633 proxy_attr.impl.dispatch = proxy_attr.dispatch # type: ignore 

634 proxy_attr.impl.dispatch._active_history = self.active_history 

635 

636 # TODO: need a deserialize hook here 

637 

638 @util.memoized_property 

639 def _attribute_keys(self) -> Sequence[str]: 

640 return [prop.key for prop in self.props] 

641 

642 def _populate_composite_bulk_save_mappings_fn( 

643 self, 

644 ) -> Callable[[Dict[str, Any]], None]: 

645 if self._generated_composite_accessor: 

646 get_values = self._generated_composite_accessor 

647 else: 

648 

649 def get_values(val: Any) -> Tuple[Any]: 

650 return val.__composite_values__() # type: ignore 

651 

652 attrs = [prop.key for prop in self.props] 

653 

654 def populate(dest_dict: Dict[str, Any]) -> None: 

655 dest_dict.update( 

656 { 

657 key: val 

658 for key, val in zip( 

659 attrs, get_values(dest_dict.pop(self.key)) 

660 ) 

661 } 

662 ) 

663 

664 return populate 

665 

666 def get_history( 

667 self, 

668 state: InstanceState[Any], 

669 dict_: _InstanceDict, 

670 passive: PassiveFlag = PassiveFlag.PASSIVE_OFF, 

671 ) -> History: 

672 """Provided for userland code that uses attributes.get_history().""" 

673 

674 added: List[Any] = [] 

675 deleted: List[Any] = [] 

676 

677 has_history = False 

678 for prop in self.props: 

679 key = prop.key 

680 hist = state.manager[key].impl.get_history(state, dict_) 

681 if hist.has_changes(): 

682 has_history = True 

683 

684 non_deleted = hist.non_deleted() 

685 if non_deleted: 

686 added.extend(non_deleted) 

687 else: 

688 added.append(None) 

689 if hist.deleted: 

690 deleted.extend(hist.deleted) 

691 else: 

692 deleted.append(None) 

693 

694 if has_history: 

695 return attributes.History( 

696 [self.composite_class(*added)], 

697 (), 

698 [self.composite_class(*deleted)], 

699 ) 

700 else: 

701 return attributes.History((), [self.composite_class(*added)], ()) 

702 

703 def _comparator_factory( 

704 self, mapper: Mapper[Any] 

705 ) -> Composite.Comparator[_CC]: 

706 return self.comparator_factory(self, mapper) 

707 

708 class CompositeBundle(orm_util.Bundle[_T]): 

709 def __init__( 

710 self, 

711 property_: Composite[_T], 

712 expr: ClauseList, 

713 ): 

714 self.property = property_ 

715 super().__init__(property_.key, *expr) 

716 

717 def create_row_processor( 

718 self, 

719 query: Select[Unpack[TupleAny]], 

720 procs: Sequence[Callable[[Row[Unpack[TupleAny]]], Any]], 

721 labels: Sequence[str], 

722 ) -> Callable[[Row[Unpack[TupleAny]]], Any]: 

723 def proc(row: Row[Unpack[TupleAny]]) -> Any: 

724 return self.property.composite_class( 

725 *[proc(row) for proc in procs] 

726 ) 

727 

728 return proc 

729 

730 class Comparator(PropComparator[_PT]): 

731 """Produce boolean, comparison, and other operators for 

732 :class:`.Composite` attributes. 

733 

734 See the example in :ref:`composite_operations` for an overview 

735 of usage , as well as the documentation for :class:`.PropComparator`. 

736 

737 .. seealso:: 

738 

739 :class:`.PropComparator` 

740 

741 :class:`.ColumnOperators` 

742 

743 :ref:`types_operators` 

744 

745 :attr:`.TypeEngine.comparator_factory` 

746 

747 """ 

748 

749 # https://github.com/python/mypy/issues/4266 

750 __hash__ = None # type: ignore 

751 

752 prop: RODescriptorReference[Composite[_PT]] 

753 

754 @util.memoized_property 

755 def clauses(self) -> ClauseList: 

756 return expression.ClauseList( 

757 group=False, *self._comparable_elements 

758 ) 

759 

760 def __clause_element__(self) -> CompositeProperty.CompositeBundle[_PT]: 

761 return self.expression 

762 

763 @util.memoized_property 

764 def expression(self) -> CompositeProperty.CompositeBundle[_PT]: 

765 clauses = self.clauses._annotate( 

766 { 

767 "parententity": self._parententity, 

768 "parentmapper": self._parententity, 

769 "proxy_key": self.prop.key, 

770 } 

771 ) 

772 return CompositeProperty.CompositeBundle(self.prop, clauses) 

773 

774 def _bulk_update_tuples( 

775 self, value: Any 

776 ) -> Sequence[Tuple[_DMLColumnArgument, Any]]: 

777 if isinstance(value, BindParameter): 

778 value = value.value 

779 

780 values: Sequence[Any] 

781 

782 if value is None: 

783 values = [None for key in self.prop._attribute_keys] 

784 elif isinstance(self.prop.composite_class, type) and isinstance( 

785 value, self.prop.composite_class 

786 ): 

787 values = self.prop._composite_values_from_instance(value) 

788 else: 

789 raise sa_exc.ArgumentError( 

790 "Can't UPDATE composite attribute %s to %r" 

791 % (self.prop, value) 

792 ) 

793 

794 return list(zip(self._comparable_elements, values)) 

795 

796 @util.memoized_property 

797 def _comparable_elements(self) -> Sequence[QueryableAttribute[Any]]: 

798 if self._adapt_to_entity: 

799 return [ 

800 getattr(self._adapt_to_entity.entity, prop.key) 

801 for prop in self.prop._comparable_elements 

802 ] 

803 else: 

804 return self.prop._comparable_elements 

805 

806 def __eq__(self, other: Any) -> ColumnElement[bool]: # type: ignore[override] # noqa: E501 

807 return self._compare(operators.eq, other) 

808 

809 def __ne__(self, other: Any) -> ColumnElement[bool]: # type: ignore[override] # noqa: E501 

810 return self._compare(operators.ne, other) 

811 

812 def __lt__(self, other: Any) -> ColumnElement[bool]: 

813 return self._compare(operators.lt, other) 

814 

815 def __gt__(self, other: Any) -> ColumnElement[bool]: 

816 return self._compare(operators.gt, other) 

817 

818 def __le__(self, other: Any) -> ColumnElement[bool]: 

819 return self._compare(operators.le, other) 

820 

821 def __ge__(self, other: Any) -> ColumnElement[bool]: 

822 return self._compare(operators.ge, other) 

823 

824 # what might be interesting would be if we create 

825 # an instance of the composite class itself with 

826 # the columns as data members, then use "hybrid style" comparison 

827 # to create these comparisons. then your Point.__eq__() method could 

828 # be where comparison behavior is defined for SQL also. Likely 

829 # not a good choice for default behavior though, not clear how it would 

830 # work w/ dataclasses, etc. also no demand for any of this anyway. 

831 def _compare( 

832 self, operator: OperatorType, other: Any 

833 ) -> ColumnElement[bool]: 

834 values: Sequence[Any] 

835 if other is None: 

836 values = [None] * len(self.prop._comparable_elements) 

837 else: 

838 values = self.prop._composite_values_from_instance(other) 

839 comparisons = [ 

840 operator(a, b) 

841 for a, b in zip(self.prop._comparable_elements, values) 

842 ] 

843 if self._adapt_to_entity: 

844 assert self.adapter is not None 

845 comparisons = [self.adapter(x) for x in comparisons] 

846 return sql.and_(*comparisons) 

847 

848 def __str__(self) -> str: 

849 return str(self.parent.class_.__name__) + "." + self.key 

850 

851 

852class Composite(CompositeProperty[_T], _DeclarativeMapped[_T]): 

853 """Declarative-compatible front-end for the :class:`.CompositeProperty` 

854 class. 

855 

856 Public constructor is the :func:`_orm.composite` function. 

857 

858 .. versionchanged:: 2.0 Added :class:`_orm.Composite` as a Declarative 

859 compatible subclass of :class:`_orm.CompositeProperty`. 

860 

861 .. seealso:: 

862 

863 :ref:`mapper_composite` 

864 

865 """ 

866 

867 inherit_cache = True 

868 """:meta private:""" 

869 

870 

871class ConcreteInheritedProperty(DescriptorProperty[_T]): 

872 """A 'do nothing' :class:`.MapperProperty` that disables 

873 an attribute on a concrete subclass that is only present 

874 on the inherited mapper, not the concrete classes' mapper. 

875 

876 Cases where this occurs include: 

877 

878 * When the superclass mapper is mapped against a 

879 "polymorphic union", which includes all attributes from 

880 all subclasses. 

881 * When a relationship() is configured on an inherited mapper, 

882 but not on the subclass mapper. Concrete mappers require 

883 that relationship() is configured explicitly on each 

884 subclass. 

885 

886 """ 

887 

888 def _comparator_factory( 

889 self, mapper: Mapper[Any] 

890 ) -> Type[PropComparator[_T]]: 

891 comparator_callable = None 

892 

893 for m in self.parent.iterate_to_root(): 

894 p = m._props[self.key] 

895 if getattr(p, "comparator_factory", None) is not None: 

896 comparator_callable = p.comparator_factory 

897 break 

898 assert comparator_callable is not None 

899 return comparator_callable(p, mapper) # type: ignore 

900 

901 def __init__(self) -> None: 

902 super().__init__() 

903 

904 def warn() -> NoReturn: 

905 raise AttributeError( 

906 "Concrete %s does not implement " 

907 "attribute %r at the instance level. Add " 

908 "this property explicitly to %s." 

909 % (self.parent, self.key, self.parent) 

910 ) 

911 

912 class NoninheritedConcreteProp: 

913 def __set__(s: Any, obj: Any, value: Any) -> NoReturn: 

914 warn() 

915 

916 def __delete__(s: Any, obj: Any) -> NoReturn: 

917 warn() 

918 

919 def __get__(s: Any, obj: Any, owner: Any) -> Any: 

920 if obj is None: 

921 return self.descriptor 

922 warn() 

923 

924 self.descriptor = NoninheritedConcreteProp() 

925 

926 

927class SynonymProperty(DescriptorProperty[_T]): 

928 """Denote an attribute name as a synonym to a mapped property, 

929 in that the attribute will mirror the value and expression behavior 

930 of another attribute. 

931 

932 :class:`.Synonym` is constructed using the :func:`_orm.synonym` 

933 function. 

934 

935 .. seealso:: 

936 

937 :ref:`synonyms` - Overview of synonyms 

938 

939 """ 

940 

941 comparator_factory: Optional[Type[PropComparator[_T]]] 

942 

943 def __init__( 

944 self, 

945 name: str, 

946 map_column: Optional[bool] = None, 

947 descriptor: Optional[Any] = None, 

948 comparator_factory: Optional[Type[PropComparator[_T]]] = None, 

949 attribute_options: Optional[_AttributeOptions] = None, 

950 info: Optional[_InfoType] = None, 

951 doc: Optional[str] = None, 

952 ): 

953 super().__init__(attribute_options=attribute_options) 

954 

955 self.name = name 

956 self.map_column = map_column 

957 self.descriptor = descriptor 

958 self.comparator_factory = comparator_factory 

959 if doc: 

960 self.doc = doc 

961 elif descriptor and descriptor.__doc__: 

962 self.doc = descriptor.__doc__ 

963 else: 

964 self.doc = None 

965 if info: 

966 self.info.update(info) 

967 

968 util.set_creation_order(self) 

969 

970 if not TYPE_CHECKING: 

971 

972 @property 

973 def uses_objects(self) -> bool: 

974 return getattr(self.parent.class_, self.name).impl.uses_objects 

975 

976 # TODO: when initialized, check _proxied_object, 

977 # emit a warning if its not a column-based property 

978 

979 @util.memoized_property 

980 def _proxied_object( 

981 self, 

982 ) -> Union[MapperProperty[_T], SQLORMOperations[_T]]: 

983 attr = getattr(self.parent.class_, self.name) 

984 if not hasattr(attr, "property") or not isinstance( 

985 attr.property, MapperProperty 

986 ): 

987 # attribute is a non-MapperProprerty proxy such as 

988 # hybrid or association proxy 

989 if isinstance(attr, attributes.QueryableAttribute): 

990 return attr.comparator 

991 elif isinstance(attr, SQLORMOperations): 

992 # assocaition proxy comes here 

993 return attr 

994 

995 raise sa_exc.InvalidRequestError( 

996 """synonym() attribute "%s.%s" only supports """ 

997 """ORM mapped attributes, got %r""" 

998 % (self.parent.class_.__name__, self.name, attr) 

999 ) 

1000 return attr.property 

1001 

1002 def _comparator_factory(self, mapper: Mapper[Any]) -> SQLORMOperations[_T]: 

1003 prop = self._proxied_object 

1004 

1005 if isinstance(prop, MapperProperty): 

1006 if self.comparator_factory: 

1007 comp = self.comparator_factory(prop, mapper) 

1008 else: 

1009 comp = prop.comparator_factory(prop, mapper) 

1010 return comp 

1011 else: 

1012 return prop 

1013 

1014 def get_history( 

1015 self, 

1016 state: InstanceState[Any], 

1017 dict_: _InstanceDict, 

1018 passive: PassiveFlag = PassiveFlag.PASSIVE_OFF, 

1019 ) -> History: 

1020 attr: QueryableAttribute[Any] = getattr(self.parent.class_, self.name) 

1021 return attr.impl.get_history(state, dict_, passive=passive) 

1022 

1023 @util.preload_module("sqlalchemy.orm.properties") 

1024 def set_parent(self, parent: Mapper[Any], init: bool) -> None: 

1025 properties = util.preloaded.orm_properties 

1026 

1027 if self.map_column: 

1028 # implement the 'map_column' option. 

1029 if self.key not in parent.persist_selectable.c: 

1030 raise sa_exc.ArgumentError( 

1031 "Can't compile synonym '%s': no column on table " 

1032 "'%s' named '%s'" 

1033 % ( 

1034 self.name, 

1035 parent.persist_selectable.description, 

1036 self.key, 

1037 ) 

1038 ) 

1039 elif ( 

1040 parent.persist_selectable.c[self.key] 

1041 in parent._columntoproperty 

1042 and parent._columntoproperty[ 

1043 parent.persist_selectable.c[self.key] 

1044 ].key 

1045 == self.name 

1046 ): 

1047 raise sa_exc.ArgumentError( 

1048 "Can't call map_column=True for synonym %r=%r, " 

1049 "a ColumnProperty already exists keyed to the name " 

1050 "%r for column %r" 

1051 % (self.key, self.name, self.name, self.key) 

1052 ) 

1053 p: ColumnProperty[Any] = properties.ColumnProperty( 

1054 parent.persist_selectable.c[self.key] 

1055 ) 

1056 parent._configure_property(self.name, p, init=init, setparent=True) 

1057 p._mapped_by_synonym = self.key 

1058 

1059 self.parent = parent 

1060 

1061 

1062class Synonym(SynonymProperty[_T], _DeclarativeMapped[_T]): 

1063 """Declarative front-end for the :class:`.SynonymProperty` class. 

1064 

1065 Public constructor is the :func:`_orm.synonym` function. 

1066 

1067 .. versionchanged:: 2.0 Added :class:`_orm.Synonym` as a Declarative 

1068 compatible subclass for :class:`_orm.SynonymProperty` 

1069 

1070 .. seealso:: 

1071 

1072 :ref:`synonyms` - Overview of synonyms 

1073 

1074 """ 

1075 

1076 inherit_cache = True 

1077 """:meta private:"""