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( 

788 value # type: ignore[arg-type] 

789 ) 

790 else: 

791 raise sa_exc.ArgumentError( 

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

793 % (self.prop, value) 

794 ) 

795 

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

797 

798 @util.memoized_property 

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

800 if self._adapt_to_entity: 

801 return [ 

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

803 for prop in self.prop._comparable_elements 

804 ] 

805 else: 

806 return self.prop._comparable_elements 

807 

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

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

810 

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

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

813 

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

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

816 

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

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

819 

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

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

822 

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

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

825 

826 # what might be interesting would be if we create 

827 # an instance of the composite class itself with 

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

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

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

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

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

833 def _compare( 

834 self, operator: OperatorType, other: Any 

835 ) -> ColumnElement[bool]: 

836 values: Sequence[Any] 

837 if other is None: 

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

839 else: 

840 values = self.prop._composite_values_from_instance(other) 

841 comparisons = [ 

842 operator(a, b) 

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

844 ] 

845 if self._adapt_to_entity: 

846 assert self.adapter is not None 

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

848 return sql.and_(*comparisons) 

849 

850 def __str__(self) -> str: 

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

852 

853 

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

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

856 class. 

857 

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

859 

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

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

862 

863 .. seealso:: 

864 

865 :ref:`mapper_composite` 

866 

867 """ 

868 

869 inherit_cache = True 

870 """:meta private:""" 

871 

872 

873class ConcreteInheritedProperty(DescriptorProperty[_T]): 

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

875 an attribute on a concrete subclass that is only present 

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

877 

878 Cases where this occurs include: 

879 

880 * When the superclass mapper is mapped against a 

881 "polymorphic union", which includes all attributes from 

882 all subclasses. 

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

884 but not on the subclass mapper. Concrete mappers require 

885 that relationship() is configured explicitly on each 

886 subclass. 

887 

888 """ 

889 

890 def _comparator_factory( 

891 self, mapper: Mapper[Any] 

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

893 comparator_callable = None 

894 

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

896 p = m._props[self.key] 

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

898 comparator_callable = p.comparator_factory 

899 break 

900 assert comparator_callable is not None 

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

902 

903 def __init__(self) -> None: 

904 super().__init__() 

905 

906 def warn() -> NoReturn: 

907 raise AttributeError( 

908 "Concrete %s does not implement " 

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

910 "this property explicitly to %s." 

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

912 ) 

913 

914 class NoninheritedConcreteProp: 

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

916 warn() 

917 

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

919 warn() 

920 

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

922 if obj is None: 

923 return self.descriptor 

924 warn() 

925 

926 self.descriptor = NoninheritedConcreteProp() 

927 

928 

929class SynonymProperty(DescriptorProperty[_T]): 

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

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

932 of another attribute. 

933 

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

935 function. 

936 

937 .. seealso:: 

938 

939 :ref:`synonyms` - Overview of synonyms 

940 

941 """ 

942 

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

944 

945 def __init__( 

946 self, 

947 name: str, 

948 map_column: Optional[bool] = None, 

949 descriptor: Optional[Any] = None, 

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

951 attribute_options: Optional[_AttributeOptions] = None, 

952 info: Optional[_InfoType] = None, 

953 doc: Optional[str] = None, 

954 ): 

955 super().__init__(attribute_options=attribute_options) 

956 

957 self.name = name 

958 self.map_column = map_column 

959 self.descriptor = descriptor 

960 self.comparator_factory = comparator_factory 

961 if doc: 

962 self.doc = doc 

963 elif descriptor and descriptor.__doc__: 

964 self.doc = descriptor.__doc__ 

965 else: 

966 self.doc = None 

967 if info: 

968 self.info.update(info) 

969 

970 util.set_creation_order(self) 

971 

972 if not TYPE_CHECKING: 

973 

974 @property 

975 def uses_objects(self) -> bool: 

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

977 

978 # TODO: when initialized, check _proxied_object, 

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

980 

981 @util.memoized_property 

982 def _proxied_object( 

983 self, 

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

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

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

987 attr.property, MapperProperty 

988 ): 

989 # attribute is a non-MapperProprerty proxy such as 

990 # hybrid or association proxy 

991 if isinstance(attr, attributes.QueryableAttribute): 

992 return attr.comparator 

993 elif isinstance(attr, SQLORMOperations): 

994 # assocaition proxy comes here 

995 return attr 

996 

997 raise sa_exc.InvalidRequestError( 

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

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

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

1001 ) 

1002 return attr.property 

1003 

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

1005 prop = self._proxied_object 

1006 

1007 if isinstance(prop, MapperProperty): 

1008 if self.comparator_factory: 

1009 comp = self.comparator_factory(prop, mapper) 

1010 else: 

1011 comp = prop.comparator_factory(prop, mapper) 

1012 return comp 

1013 else: 

1014 return prop 

1015 

1016 def get_history( 

1017 self, 

1018 state: InstanceState[Any], 

1019 dict_: _InstanceDict, 

1020 passive: PassiveFlag = PassiveFlag.PASSIVE_OFF, 

1021 ) -> History: 

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

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

1024 

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

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

1027 properties = util.preloaded.orm_properties 

1028 

1029 if self.map_column: 

1030 # implement the 'map_column' option. 

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

1032 raise sa_exc.ArgumentError( 

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

1034 "'%s' named '%s'" 

1035 % ( 

1036 self.name, 

1037 parent.persist_selectable.description, 

1038 self.key, 

1039 ) 

1040 ) 

1041 elif ( 

1042 parent.persist_selectable.c[self.key] 

1043 in parent._columntoproperty 

1044 and parent._columntoproperty[ 

1045 parent.persist_selectable.c[self.key] 

1046 ].key 

1047 == self.name 

1048 ): 

1049 raise sa_exc.ArgumentError( 

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

1051 "a ColumnProperty already exists keyed to the name " 

1052 "%r for column %r" 

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

1054 ) 

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

1056 parent.persist_selectable.c[self.key] 

1057 ) 

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

1059 p._mapped_by_synonym = self.key 

1060 

1061 self.parent = parent 

1062 

1063 

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

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

1066 

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

1068 

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

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

1071 

1072 .. seealso:: 

1073 

1074 :ref:`synonyms` - Overview of synonyms 

1075 

1076 """ 

1077 

1078 inherit_cache = True 

1079 """:meta private:"""