Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/sqlalchemy/ext/declarative/extensions.py: 37%

104 statements  

« prev     ^ index     » next       coverage.py v7.0.1, created at 2022-12-25 06:11 +0000

1# ext/declarative/extensions.py 

2# Copyright (C) 2005-2022 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"""Public API functions and helpers for declarative.""" 

8 

9 

10from ... import inspection 

11from ... import util 

12from ...orm import exc as orm_exc 

13from ...orm import registry 

14from ...orm import relationships 

15from ...orm.base import _mapper_or_none 

16from ...orm.clsregistry import _resolver 

17from ...orm.decl_base import _DeferredMapperConfig 

18from ...orm.util import polymorphic_union 

19from ...schema import Table 

20from ...util import OrderedDict 

21 

22 

23@util.deprecated( 

24 "2.0", 

25 "the instrument_declarative function is deprecated " 

26 "and will be removed in SQLAlhcemy 2.0. Please use " 

27 ":meth:`_orm.registry.map_declaratively", 

28) 

29def instrument_declarative(cls, cls_registry, metadata): 

30 """Given a class, configure the class declaratively, 

31 using the given registry, which can be any dictionary, and 

32 MetaData object. 

33 

34 """ 

35 registry(metadata=metadata, class_registry=cls_registry).map_declaratively( 

36 cls 

37 ) 

38 

39 

40class ConcreteBase(object): 

41 """A helper class for 'concrete' declarative mappings. 

42 

43 :class:`.ConcreteBase` will use the :func:`.polymorphic_union` 

44 function automatically, against all tables mapped as a subclass 

45 to this class. The function is called via the 

46 ``__declare_last__()`` function, which is essentially 

47 a hook for the :meth:`.after_configured` event. 

48 

49 :class:`.ConcreteBase` produces a mapped 

50 table for the class itself. Compare to :class:`.AbstractConcreteBase`, 

51 which does not. 

52 

53 Example:: 

54 

55 from sqlalchemy.ext.declarative import ConcreteBase 

56 

57 class Employee(ConcreteBase, Base): 

58 __tablename__ = 'employee' 

59 employee_id = Column(Integer, primary_key=True) 

60 name = Column(String(50)) 

61 __mapper_args__ = { 

62 'polymorphic_identity':'employee', 

63 'concrete':True} 

64 

65 class Manager(Employee): 

66 __tablename__ = 'manager' 

67 employee_id = Column(Integer, primary_key=True) 

68 name = Column(String(50)) 

69 manager_data = Column(String(40)) 

70 __mapper_args__ = { 

71 'polymorphic_identity':'manager', 

72 'concrete':True} 

73 

74 

75 The name of the discriminator column used by :func:`.polymorphic_union` 

76 defaults to the name ``type``. To suit the use case of a mapping where an 

77 actual column in a mapped table is already named ``type``, the 

78 discriminator name can be configured by setting the 

79 ``_concrete_discriminator_name`` attribute:: 

80 

81 class Employee(ConcreteBase, Base): 

82 _concrete_discriminator_name = '_concrete_discriminator' 

83 

84 .. versionadded:: 1.3.19 Added the ``_concrete_discriminator_name`` 

85 attribute to :class:`_declarative.ConcreteBase` so that the 

86 virtual discriminator column name can be customized. 

87 

88 .. versionchanged:: 1.4.2 The ``_concrete_discriminator_name`` attribute 

89 need only be placed on the basemost class to take correct effect for 

90 all subclasses. An explicit error message is now raised if the 

91 mapped column names conflict with the discriminator name, whereas 

92 in the 1.3.x series there would be some warnings and then a non-useful 

93 query would be generated. 

94 

95 .. seealso:: 

96 

97 :class:`.AbstractConcreteBase` 

98 

99 :ref:`concrete_inheritance` 

100 

101 

102 """ 

103 

104 @classmethod 

105 def _create_polymorphic_union(cls, mappers, discriminator_name): 

106 return polymorphic_union( 

107 OrderedDict( 

108 (mp.polymorphic_identity, mp.local_table) for mp in mappers 

109 ), 

110 discriminator_name, 

111 "pjoin", 

112 ) 

113 

114 @classmethod 

115 def __declare_first__(cls): 

116 m = cls.__mapper__ 

117 if m.with_polymorphic: 

118 return 

119 

120 discriminator_name = ( 

121 getattr(cls, "_concrete_discriminator_name", None) or "type" 

122 ) 

123 

124 mappers = list(m.self_and_descendants) 

125 pjoin = cls._create_polymorphic_union(mappers, discriminator_name) 

126 m._set_with_polymorphic(("*", pjoin)) 

127 m._set_polymorphic_on(pjoin.c[discriminator_name]) 

128 

129 

130class AbstractConcreteBase(ConcreteBase): 

131 """A helper class for 'concrete' declarative mappings. 

132 

133 :class:`.AbstractConcreteBase` will use the :func:`.polymorphic_union` 

134 function automatically, against all tables mapped as a subclass 

135 to this class. The function is called via the 

136 ``__declare_last__()`` function, which is essentially 

137 a hook for the :meth:`.after_configured` event. 

138 

139 :class:`.AbstractConcreteBase` does produce a mapped class 

140 for the base class, however it is not persisted to any table; it 

141 is instead mapped directly to the "polymorphic" selectable directly 

142 and is only used for selecting. Compare to :class:`.ConcreteBase`, 

143 which does create a persisted table for the base class. 

144 

145 .. note:: 

146 

147 The :class:`.AbstractConcreteBase` delays the mapper creation of the 

148 base class until all the subclasses have been defined, 

149 as it needs to create a mapping against a selectable that will include 

150 all subclass tables. In order to achieve this, it waits for the 

151 **mapper configuration event** to occur, at which point it scans 

152 through all the configured subclasses and sets up a mapping that will 

153 query against all subclasses at once. 

154 

155 While this event is normally invoked automatically, in the case of 

156 :class:`.AbstractConcreteBase`, it may be necessary to invoke it 

157 explicitly after **all** subclass mappings are defined, if the first 

158 operation is to be a query against this base class. To do so, once all 

159 the desired classes have been configured, the 

160 :meth:`_orm.registry.configure` method on the :class:`_orm.registry` 

161 in use can be invoked, which is available in relation to a particular 

162 declarative base class:: 

163 

164 Base.registry.configure() 

165 

166 Example:: 

167 

168 from sqlalchemy.ext.declarative import AbstractConcreteBase 

169 from sqlalchemy.orm import declarative_base 

170 

171 Base = declarative_base() 

172 

173 class Employee(AbstractConcreteBase, Base): 

174 pass 

175 

176 class Manager(Employee): 

177 __tablename__ = 'manager' 

178 employee_id = Column(Integer, primary_key=True) 

179 name = Column(String(50)) 

180 manager_data = Column(String(40)) 

181 

182 __mapper_args__ = { 

183 'polymorphic_identity':'manager', 

184 'concrete':True 

185 } 

186 

187 Base.registry.configure() 

188 

189 The abstract base class is handled by declarative in a special way; 

190 at class configuration time, it behaves like a declarative mixin 

191 or an ``__abstract__`` base class. Once classes are configured 

192 and mappings are produced, it then gets mapped itself, but 

193 after all of its descendants. This is a very unique system of mapping 

194 not found in any other SQLAlchemy system. 

195 

196 Using this approach, we can specify columns and properties 

197 that will take place on mapped subclasses, in the way that 

198 we normally do as in :ref:`declarative_mixins`:: 

199 

200 class Company(Base): 

201 __tablename__ = 'company' 

202 id = Column(Integer, primary_key=True) 

203 

204 class Employee(AbstractConcreteBase, Base): 

205 employee_id = Column(Integer, primary_key=True) 

206 

207 @declared_attr 

208 def company_id(cls): 

209 return Column(ForeignKey('company.id')) 

210 

211 @declared_attr 

212 def company(cls): 

213 return relationship("Company") 

214 

215 class Manager(Employee): 

216 __tablename__ = 'manager' 

217 

218 name = Column(String(50)) 

219 manager_data = Column(String(40)) 

220 

221 __mapper_args__ = { 

222 'polymorphic_identity':'manager', 

223 'concrete':True 

224 } 

225 

226 Base.registry.configure() 

227 

228 When we make use of our mappings however, both ``Manager`` and 

229 ``Employee`` will have an independently usable ``.company`` attribute:: 

230 

231 session.execute( 

232 select(Employee).filter(Employee.company.has(id=5)) 

233 ) 

234 

235 .. seealso:: 

236 

237 :class:`.ConcreteBase` 

238 

239 :ref:`concrete_inheritance` 

240 

241 :ref:`abstract_concrete_base` 

242 

243 """ 

244 

245 __no_table__ = True 

246 

247 @classmethod 

248 def __declare_first__(cls): 

249 cls._sa_decl_prepare_nocascade() 

250 

251 @classmethod 

252 def _sa_decl_prepare_nocascade(cls): 

253 if getattr(cls, "__mapper__", None): 

254 return 

255 

256 to_map = _DeferredMapperConfig.config_for_cls(cls) 

257 

258 # can't rely on 'self_and_descendants' here 

259 # since technically an immediate subclass 

260 # might not be mapped, but a subclass 

261 # may be. 

262 mappers = [] 

263 stack = list(cls.__subclasses__()) 

264 while stack: 

265 klass = stack.pop() 

266 stack.extend(klass.__subclasses__()) 

267 mn = _mapper_or_none(klass) 

268 if mn is not None: 

269 mappers.append(mn) 

270 

271 discriminator_name = ( 

272 getattr(cls, "_concrete_discriminator_name", None) or "type" 

273 ) 

274 pjoin = cls._create_polymorphic_union(mappers, discriminator_name) 

275 

276 # For columns that were declared on the class, these 

277 # are normally ignored with the "__no_table__" mapping, 

278 # unless they have a different attribute key vs. col name 

279 # and are in the properties argument. 

280 # In that case, ensure we update the properties entry 

281 # to the correct column from the pjoin target table. 

282 declared_cols = set(to_map.declared_columns) 

283 for k, v in list(to_map.properties.items()): 

284 if v in declared_cols: 

285 to_map.properties[k] = pjoin.c[v.key] 

286 

287 to_map.local_table = pjoin 

288 

289 m_args = to_map.mapper_args_fn or dict 

290 

291 def mapper_args(): 

292 args = m_args() 

293 args["polymorphic_on"] = pjoin.c[discriminator_name] 

294 return args 

295 

296 to_map.mapper_args_fn = mapper_args 

297 

298 m = to_map.map() 

299 

300 for scls in cls.__subclasses__(): 

301 sm = _mapper_or_none(scls) 

302 if sm and sm.concrete and cls in scls.__bases__: 

303 sm._set_concrete_base(m) 

304 

305 @classmethod 

306 def _sa_raise_deferred_config(cls): 

307 raise orm_exc.UnmappedClassError( 

308 cls, 

309 msg="Class %s is a subclass of AbstractConcreteBase and " 

310 "has a mapping pending until all subclasses are defined. " 

311 "Call the sqlalchemy.orm.configure_mappers() function after " 

312 "all subclasses have been defined to " 

313 "complete the mapping of this class." 

314 % orm_exc._safe_cls_name(cls), 

315 ) 

316 

317 

318class DeferredReflection(object): 

319 """A helper class for construction of mappings based on 

320 a deferred reflection step. 

321 

322 Normally, declarative can be used with reflection by 

323 setting a :class:`_schema.Table` object using autoload_with=engine 

324 as the ``__table__`` attribute on a declarative class. 

325 The caveat is that the :class:`_schema.Table` must be fully 

326 reflected, or at the very least have a primary key column, 

327 at the point at which a normal declarative mapping is 

328 constructed, meaning the :class:`_engine.Engine` must be available 

329 at class declaration time. 

330 

331 The :class:`.DeferredReflection` mixin moves the construction 

332 of mappers to be at a later point, after a specific 

333 method is called which first reflects all :class:`_schema.Table` 

334 objects created so far. Classes can define it as such:: 

335 

336 from sqlalchemy.ext.declarative import declarative_base 

337 from sqlalchemy.ext.declarative import DeferredReflection 

338 Base = declarative_base() 

339 

340 class MyClass(DeferredReflection, Base): 

341 __tablename__ = 'mytable' 

342 

343 Above, ``MyClass`` is not yet mapped. After a series of 

344 classes have been defined in the above fashion, all tables 

345 can be reflected and mappings created using 

346 :meth:`.prepare`:: 

347 

348 engine = create_engine("someengine://...") 

349 DeferredReflection.prepare(engine) 

350 

351 The :class:`.DeferredReflection` mixin can be applied to individual 

352 classes, used as the base for the declarative base itself, 

353 or used in a custom abstract class. Using an abstract base 

354 allows that only a subset of classes to be prepared for a 

355 particular prepare step, which is necessary for applications 

356 that use more than one engine. For example, if an application 

357 has two engines, you might use two bases, and prepare each 

358 separately, e.g.:: 

359 

360 class ReflectedOne(DeferredReflection, Base): 

361 __abstract__ = True 

362 

363 class ReflectedTwo(DeferredReflection, Base): 

364 __abstract__ = True 

365 

366 class MyClass(ReflectedOne): 

367 __tablename__ = 'mytable' 

368 

369 class MyOtherClass(ReflectedOne): 

370 __tablename__ = 'myothertable' 

371 

372 class YetAnotherClass(ReflectedTwo): 

373 __tablename__ = 'yetanothertable' 

374 

375 # ... etc. 

376 

377 Above, the class hierarchies for ``ReflectedOne`` and 

378 ``ReflectedTwo`` can be configured separately:: 

379 

380 ReflectedOne.prepare(engine_one) 

381 ReflectedTwo.prepare(engine_two) 

382 

383 .. seealso:: 

384 

385 :ref:`orm_declarative_reflected_deferred_reflection` - in the 

386 :ref:`orm_declarative_table_config_toplevel` section. 

387 

388 """ 

389 

390 @classmethod 

391 def prepare(cls, engine): 

392 """Reflect all :class:`_schema.Table` objects for all current 

393 :class:`.DeferredReflection` subclasses""" 

394 

395 to_map = _DeferredMapperConfig.classes_for_base(cls) 

396 

397 with inspection.inspect(engine)._inspection_context() as insp: 

398 for thingy in to_map: 

399 cls._sa_decl_prepare(thingy.local_table, insp) 

400 thingy.map() 

401 mapper = thingy.cls.__mapper__ 

402 metadata = mapper.class_.metadata 

403 for rel in mapper._props.values(): 

404 if ( 

405 isinstance(rel, relationships.RelationshipProperty) 

406 and rel.secondary is not None 

407 ): 

408 if isinstance(rel.secondary, Table): 

409 cls._reflect_table(rel.secondary, insp) 

410 elif isinstance(rel.secondary, str): 

411 

412 _, resolve_arg = _resolver(rel.parent.class_, rel) 

413 

414 rel.secondary = resolve_arg(rel.secondary) 

415 rel.secondary._resolvers += ( 

416 cls._sa_deferred_table_resolver( 

417 insp, metadata 

418 ), 

419 ) 

420 

421 # controversy! do we resolve it here? or leave 

422 # it deferred? I think doing it here is necessary 

423 # so the connection does not leak. 

424 rel.secondary = rel.secondary() 

425 

426 @classmethod 

427 def _sa_deferred_table_resolver(cls, inspector, metadata): 

428 def _resolve(key): 

429 t1 = Table(key, metadata) 

430 cls._reflect_table(t1, inspector) 

431 return t1 

432 

433 return _resolve 

434 

435 @classmethod 

436 def _sa_decl_prepare(cls, local_table, inspector): 

437 # autoload Table, which is already 

438 # present in the metadata. This 

439 # will fill in db-loaded columns 

440 # into the existing Table object. 

441 if local_table is not None: 

442 cls._reflect_table(local_table, inspector) 

443 

444 @classmethod 

445 def _sa_raise_deferred_config(cls): 

446 raise orm_exc.UnmappedClassError( 

447 cls, 

448 msg="Class %s is a subclass of DeferredReflection. " 

449 "Mappings are not produced until the .prepare() " 

450 "method is called on the class hierarchy." 

451 % orm_exc._safe_cls_name(cls), 

452 ) 

453 

454 @classmethod 

455 def _reflect_table(cls, table, inspector): 

456 Table( 

457 table.name, 

458 table.metadata, 

459 extend_existing=True, 

460 autoload_replace=False, 

461 autoload_with=inspector, 

462 schema=table.schema, 

463 )