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

123 statements  

« prev     ^ index     » next       coverage.py v7.2.7, created at 2023-06-07 06:35 +0000

1# orm/properties.py 

2# Copyright (C) 2005-2023 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"""MapperProperty implementations. 

9 

10This is a private module which defines the behavior of individual ORM- 

11mapped attributes. 

12 

13""" 

14from __future__ import absolute_import 

15 

16from . import attributes 

17from .descriptor_props import CompositeProperty 

18from .descriptor_props import ConcreteInheritedProperty 

19from .descriptor_props import SynonymProperty 

20from .interfaces import PropComparator 

21from .interfaces import StrategizedProperty 

22from .relationships import RelationshipProperty 

23from .. import log 

24from .. import util 

25from ..sql import coercions 

26from ..sql import roles 

27 

28 

29__all__ = [ 

30 "ColumnProperty", 

31 "CompositeProperty", 

32 "ConcreteInheritedProperty", 

33 "RelationshipProperty", 

34 "SynonymProperty", 

35] 

36 

37 

38@log.class_logger 

39class ColumnProperty(StrategizedProperty): 

40 """Describes an object attribute that corresponds to a table column. 

41 

42 Public constructor is the :func:`_orm.column_property` function. 

43 

44 """ 

45 

46 strategy_wildcard_key = "column" 

47 inherit_cache = True 

48 _links_to_entity = False 

49 

50 __slots__ = ( 

51 "columns", 

52 "group", 

53 "deferred", 

54 "instrument", 

55 "comparator_factory", 

56 "descriptor", 

57 "active_history", 

58 "expire_on_flush", 

59 "info", 

60 "doc", 

61 "strategy_key", 

62 "_creation_order", 

63 "_is_polymorphic_discriminator", 

64 "_mapped_by_synonym", 

65 "_deferred_column_loader", 

66 "_raise_column_loader", 

67 "_renders_in_subqueries", 

68 "raiseload", 

69 ) 

70 

71 def __init__(self, *columns, **kwargs): 

72 r"""Provide a column-level property for use with a mapping. 

73 

74 Column-based properties can normally be applied to the mapper's 

75 ``properties`` dictionary using the :class:`_schema.Column` 

76 element directly. 

77 Use this function when the given column is not directly present within 

78 the mapper's selectable; examples include SQL expressions, functions, 

79 and scalar SELECT queries. 

80 

81 The :func:`_orm.column_property` function returns an instance of 

82 :class:`.ColumnProperty`. 

83 

84 Columns that aren't present in the mapper's selectable won't be 

85 persisted by the mapper and are effectively "read-only" attributes. 

86 

87 :param \*cols: 

88 list of Column objects to be mapped. 

89 

90 :param active_history=False: 

91 When ``True``, indicates that the "previous" value for a 

92 scalar attribute should be loaded when replaced, if not 

93 already loaded. Normally, history tracking logic for 

94 simple non-primary-key scalar values only needs to be 

95 aware of the "new" value in order to perform a flush. This 

96 flag is available for applications that make use of 

97 :func:`.attributes.get_history` or :meth:`.Session.is_modified` 

98 which also need to know 

99 the "previous" value of the attribute. 

100 

101 :param comparator_factory: a class which extends 

102 :class:`.ColumnProperty.Comparator` which provides custom SQL 

103 clause generation for comparison operations. 

104 

105 :param group: 

106 a group name for this property when marked as deferred. 

107 

108 :param deferred: 

109 when True, the column property is "deferred", meaning that 

110 it does not load immediately, and is instead loaded when the 

111 attribute is first accessed on an instance. See also 

112 :func:`~sqlalchemy.orm.deferred`. 

113 

114 :param doc: 

115 optional string that will be applied as the doc on the 

116 class-bound descriptor. 

117 

118 :param expire_on_flush=True: 

119 Disable expiry on flush. A column_property() which refers 

120 to a SQL expression (and not a single table-bound column) 

121 is considered to be a "read only" property; populating it 

122 has no effect on the state of data, and it can only return 

123 database state. For this reason a column_property()'s value 

124 is expired whenever the parent object is involved in a 

125 flush, that is, has any kind of "dirty" state within a flush. 

126 Setting this parameter to ``False`` will have the effect of 

127 leaving any existing value present after the flush proceeds. 

128 Note however that the :class:`.Session` with default expiration 

129 settings still expires 

130 all attributes after a :meth:`.Session.commit` call, however. 

131 

132 :param info: Optional data dictionary which will be populated into the 

133 :attr:`.MapperProperty.info` attribute of this object. 

134 

135 :param raiseload: if True, indicates the column should raise an error 

136 when undeferred, rather than loading the value. This can be 

137 altered at query time by using the :func:`.deferred` option with 

138 raiseload=False. 

139 

140 .. versionadded:: 1.4 

141 

142 .. seealso:: 

143 

144 :ref:`deferred_raiseload` 

145 

146 .. seealso:: 

147 

148 :ref:`column_property_options` - to map columns while including 

149 mapping options 

150 

151 :ref:`mapper_column_property_sql_expressions` - to map SQL 

152 expressions 

153 

154 """ 

155 super(ColumnProperty, self).__init__() 

156 self.columns = [ 

157 coercions.expect(roles.LabeledColumnExprRole, c) for c in columns 

158 ] 

159 self.group = kwargs.pop("group", None) 

160 self.deferred = kwargs.pop("deferred", False) 

161 self.raiseload = kwargs.pop("raiseload", False) 

162 self.instrument = kwargs.pop("_instrument", True) 

163 self.comparator_factory = kwargs.pop( 

164 "comparator_factory", self.__class__.Comparator 

165 ) 

166 self.descriptor = kwargs.pop("descriptor", None) 

167 self.active_history = kwargs.pop("active_history", False) 

168 self.expire_on_flush = kwargs.pop("expire_on_flush", True) 

169 

170 if "info" in kwargs: 

171 self.info = kwargs.pop("info") 

172 

173 if "doc" in kwargs: 

174 self.doc = kwargs.pop("doc") 

175 else: 

176 for col in reversed(self.columns): 

177 doc = getattr(col, "doc", None) 

178 if doc is not None: 

179 self.doc = doc 

180 break 

181 else: 

182 self.doc = None 

183 

184 if kwargs: 

185 raise TypeError( 

186 "%s received unexpected keyword argument(s): %s" 

187 % (self.__class__.__name__, ", ".join(sorted(kwargs.keys()))) 

188 ) 

189 

190 util.set_creation_order(self) 

191 

192 self.strategy_key = ( 

193 ("deferred", self.deferred), 

194 ("instrument", self.instrument), 

195 ) 

196 if self.raiseload: 

197 self.strategy_key += (("raiseload", True),) 

198 

199 def _memoized_attr__renders_in_subqueries(self): 

200 if ("query_expression", True) in self.strategy_key: 

201 return self.strategy._have_default_expression 

202 

203 return ("deferred", True) not in self.strategy_key or ( 

204 self not in self.parent._readonly_props 

205 ) 

206 

207 @util.preload_module("sqlalchemy.orm.state", "sqlalchemy.orm.strategies") 

208 def _memoized_attr__deferred_column_loader(self): 

209 state = util.preloaded.orm_state 

210 strategies = util.preloaded.orm_strategies 

211 return state.InstanceState._instance_level_callable_processor( 

212 self.parent.class_manager, 

213 strategies.LoadDeferredColumns(self.key), 

214 self.key, 

215 ) 

216 

217 @util.preload_module("sqlalchemy.orm.state", "sqlalchemy.orm.strategies") 

218 def _memoized_attr__raise_column_loader(self): 

219 state = util.preloaded.orm_state 

220 strategies = util.preloaded.orm_strategies 

221 return state.InstanceState._instance_level_callable_processor( 

222 self.parent.class_manager, 

223 strategies.LoadDeferredColumns(self.key, True), 

224 self.key, 

225 ) 

226 

227 def __clause_element__(self): 

228 """Allow the ColumnProperty to work in expression before it is turned 

229 into an instrumented attribute. 

230 """ 

231 

232 return self.expression 

233 

234 @property 

235 def expression(self): 

236 """Return the primary column or expression for this ColumnProperty. 

237 

238 E.g.:: 

239 

240 

241 class File(Base): 

242 # ... 

243 

244 name = Column(String(64)) 

245 extension = Column(String(8)) 

246 filename = column_property(name + '.' + extension) 

247 path = column_property('C:/' + filename.expression) 

248 

249 .. seealso:: 

250 

251 :ref:`mapper_column_property_sql_expressions_composed` 

252 

253 """ 

254 return self.columns[0] 

255 

256 def instrument_class(self, mapper): 

257 if not self.instrument: 

258 return 

259 

260 attributes.register_descriptor( 

261 mapper.class_, 

262 self.key, 

263 comparator=self.comparator_factory(self, mapper), 

264 parententity=mapper, 

265 doc=self.doc, 

266 ) 

267 

268 def do_init(self): 

269 super(ColumnProperty, self).do_init() 

270 

271 if len(self.columns) > 1 and set(self.parent.primary_key).issuperset( 

272 self.columns 

273 ): 

274 util.warn( 

275 ( 

276 "On mapper %s, primary key column '%s' is being combined " 

277 "with distinct primary key column '%s' in attribute '%s'. " 

278 "Use explicit properties to give each column its own " 

279 "mapped attribute name." 

280 ) 

281 % (self.parent, self.columns[1], self.columns[0], self.key) 

282 ) 

283 

284 def copy(self): 

285 return ColumnProperty( 

286 deferred=self.deferred, 

287 group=self.group, 

288 active_history=self.active_history, 

289 *self.columns 

290 ) 

291 

292 def _getcommitted( 

293 self, state, dict_, column, passive=attributes.PASSIVE_OFF 

294 ): 

295 return state.get_impl(self.key).get_committed_value( 

296 state, dict_, passive=passive 

297 ) 

298 

299 def merge( 

300 self, 

301 session, 

302 source_state, 

303 source_dict, 

304 dest_state, 

305 dest_dict, 

306 load, 

307 _recursive, 

308 _resolve_conflict_map, 

309 ): 

310 if not self.instrument: 

311 return 

312 elif self.key in source_dict: 

313 value = source_dict[self.key] 

314 

315 if not load: 

316 dest_dict[self.key] = value 

317 else: 

318 impl = dest_state.get_impl(self.key) 

319 impl.set(dest_state, dest_dict, value, None) 

320 elif dest_state.has_identity and self.key not in dest_dict: 

321 dest_state._expire_attributes( 

322 dest_dict, [self.key], no_loader=True 

323 ) 

324 

325 class Comparator(util.MemoizedSlots, PropComparator): 

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

327 :class:`.ColumnProperty` attributes. 

328 

329 See the documentation for :class:`.PropComparator` for a brief 

330 overview. 

331 

332 .. seealso:: 

333 

334 :class:`.PropComparator` 

335 

336 :class:`.ColumnOperators` 

337 

338 :ref:`types_operators` 

339 

340 :attr:`.TypeEngine.comparator_factory` 

341 

342 """ 

343 

344 __slots__ = "__clause_element__", "info", "expressions" 

345 

346 def _orm_annotate_column(self, column): 

347 """annotate and possibly adapt a column to be returned 

348 as the mapped-attribute exposed version of the column. 

349 

350 The column in this context needs to act as much like the 

351 column in an ORM mapped context as possible, so includes 

352 annotations to give hints to various ORM functions as to 

353 the source entity of this column. It also adapts it 

354 to the mapper's with_polymorphic selectable if one is 

355 present. 

356 

357 """ 

358 

359 pe = self._parententity 

360 annotations = { 

361 "entity_namespace": pe, 

362 "parententity": pe, 

363 "parentmapper": pe, 

364 "proxy_key": self.prop.key, 

365 } 

366 

367 col = column 

368 

369 # for a mapper with polymorphic_on and an adapter, return 

370 # the column against the polymorphic selectable. 

371 # see also orm.util._orm_downgrade_polymorphic_columns 

372 # for the reverse operation. 

373 if self._parentmapper._polymorphic_adapter: 

374 mapper_local_col = col 

375 col = self._parentmapper._polymorphic_adapter.traverse(col) 

376 

377 # this is a clue to the ORM Query etc. that this column 

378 # was adapted to the mapper's polymorphic_adapter. the 

379 # ORM uses this hint to know which column its adapting. 

380 annotations["adapt_column"] = mapper_local_col 

381 

382 return col._annotate(annotations)._set_propagate_attrs( 

383 {"compile_state_plugin": "orm", "plugin_subject": pe} 

384 ) 

385 

386 def _memoized_method___clause_element__(self): 

387 if self.adapter: 

388 return self.adapter(self.prop.columns[0], self.prop.key) 

389 else: 

390 return self._orm_annotate_column(self.prop.columns[0]) 

391 

392 def _memoized_attr_info(self): 

393 """The .info dictionary for this attribute.""" 

394 

395 ce = self.__clause_element__() 

396 try: 

397 return ce.info 

398 except AttributeError: 

399 return self.prop.info 

400 

401 def _memoized_attr_expressions(self): 

402 """The full sequence of columns referenced by this 

403 attribute, adjusted for any aliasing in progress. 

404 

405 .. versionadded:: 1.3.17 

406 

407 """ 

408 if self.adapter: 

409 return [ 

410 self.adapter(col, self.prop.key) 

411 for col in self.prop.columns 

412 ] 

413 else: 

414 return [ 

415 self._orm_annotate_column(col) for col in self.prop.columns 

416 ] 

417 

418 def _fallback_getattr(self, key): 

419 """proxy attribute access down to the mapped column. 

420 

421 this allows user-defined comparison methods to be accessed. 

422 """ 

423 return getattr(self.__clause_element__(), key) 

424 

425 def operate(self, op, *other, **kwargs): 

426 return op(self.__clause_element__(), *other, **kwargs) 

427 

428 def reverse_operate(self, op, other, **kwargs): 

429 col = self.__clause_element__() 

430 return op(col._bind_param(op, other), col, **kwargs) 

431 

432 def __str__(self): 

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