Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1# ext/declarative/api.py 

2# Copyright (C) 2005-2020 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: http://www.opensource.org/licenses/mit-license.php 

7"""Public API functions and helpers for declarative.""" 

8 

9 

10import re 

11import weakref 

12 

13from .base import _add_attribute 

14from .base import _as_declarative 

15from .base import _declarative_constructor 

16from .base import _DeferredMapperConfig 

17from .base import _del_attribute 

18from .clsregistry import _class_resolver 

19from ... import exc 

20from ... import inspection 

21from ... import util 

22from ...orm import attributes 

23from ...orm import comparable_property 

24from ...orm import exc as orm_exc 

25from ...orm import interfaces 

26from ...orm import properties 

27from ...orm import synonym as _orm_synonym 

28from ...orm.base import _inspect_mapped_class 

29from ...orm.base import _mapper_or_none 

30from ...orm.util import polymorphic_union 

31from ...schema import MetaData 

32from ...schema import Table 

33from ...util import hybridmethod 

34from ...util import hybridproperty 

35from ...util import OrderedDict 

36 

37 

38def instrument_declarative(cls, registry, metadata): 

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

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

41 MetaData object. 

42 

43 """ 

44 if "_decl_class_registry" in cls.__dict__: 

45 raise exc.InvalidRequestError( 

46 "Class %r already has been " "instrumented declaratively" % cls 

47 ) 

48 cls._decl_class_registry = registry 

49 cls.metadata = metadata 

50 _as_declarative(cls, cls.__name__, cls.__dict__) 

51 

52 

53def has_inherited_table(cls): 

54 """Given a class, return True if any of the classes it inherits from has a 

55 mapped table, otherwise return False. 

56 

57 This is used in declarative mixins to build attributes that behave 

58 differently for the base class vs. a subclass in an inheritance 

59 hierarchy. 

60 

61 .. seealso:: 

62 

63 :ref:`decl_mixin_inheritance` 

64 

65 """ 

66 for class_ in cls.__mro__[1:]: 

67 if getattr(class_, "__table__", None) is not None: 

68 return True 

69 return False 

70 

71 

72class DeclarativeMeta(type): 

73 def __init__(cls, classname, bases, dict_): 

74 if "_decl_class_registry" not in cls.__dict__: 

75 _as_declarative(cls, classname, cls.__dict__) 

76 type.__init__(cls, classname, bases, dict_) 

77 

78 def __setattr__(cls, key, value): 

79 _add_attribute(cls, key, value) 

80 

81 def __delattr__(cls, key): 

82 _del_attribute(cls, key) 

83 

84 

85def synonym_for(name, map_column=False): 

86 """Decorator that produces an :func:`_orm.synonym` 

87 attribute in conjunction 

88 with a Python descriptor. 

89 

90 The function being decorated is passed to :func:`_orm.synonym` as the 

91 :paramref:`.orm.synonym.descriptor` parameter:: 

92 

93 class MyClass(Base): 

94 __tablename__ = 'my_table' 

95 

96 id = Column(Integer, primary_key=True) 

97 _job_status = Column("job_status", String(50)) 

98 

99 @synonym_for("job_status") 

100 @property 

101 def job_status(self): 

102 return "Status: %s" % self._job_status 

103 

104 The :ref:`hybrid properties <mapper_hybrids>` feature of SQLAlchemy 

105 is typically preferred instead of synonyms, which is a more legacy 

106 feature. 

107 

108 .. seealso:: 

109 

110 :ref:`synonyms` - Overview of synonyms 

111 

112 :func:`_orm.synonym` - the mapper-level function 

113 

114 :ref:`mapper_hybrids` - The Hybrid Attribute extension provides an 

115 updated approach to augmenting attribute behavior more flexibly than 

116 can be achieved with synonyms. 

117 

118 """ 

119 

120 def decorate(fn): 

121 return _orm_synonym(name, map_column=map_column, descriptor=fn) 

122 

123 return decorate 

124 

125 

126def comparable_using(comparator_factory): 

127 """Decorator, allow a Python @property to be used in query criteria. 

128 

129 This is a decorator front end to 

130 :func:`~sqlalchemy.orm.comparable_property` that passes 

131 through the comparator_factory and the function being decorated:: 

132 

133 @comparable_using(MyComparatorType) 

134 @property 

135 def prop(self): 

136 return 'special sauce' 

137 

138 The regular ``comparable_property()`` is also usable directly in a 

139 declarative setting and may be convenient for read/write properties:: 

140 

141 prop = comparable_property(MyComparatorType) 

142 

143 """ 

144 

145 def decorate(fn): 

146 return comparable_property(comparator_factory, fn) 

147 

148 return decorate 

149 

150 

151class declared_attr(interfaces._MappedAttribute, property): 

152 """Mark a class-level method as representing the definition of 

153 a mapped property or special declarative member name. 

154 

155 @declared_attr turns the attribute into a scalar-like 

156 property that can be invoked from the uninstantiated class. 

157 Declarative treats attributes specifically marked with 

158 @declared_attr as returning a construct that is specific 

159 to mapping or declarative table configuration. The name 

160 of the attribute is that of what the non-dynamic version 

161 of the attribute would be. 

162 

163 @declared_attr is more often than not applicable to mixins, 

164 to define relationships that are to be applied to different 

165 implementors of the class:: 

166 

167 class ProvidesUser(object): 

168 "A mixin that adds a 'user' relationship to classes." 

169 

170 @declared_attr 

171 def user(self): 

172 return relationship("User") 

173 

174 It also can be applied to mapped classes, such as to provide 

175 a "polymorphic" scheme for inheritance:: 

176 

177 class Employee(Base): 

178 id = Column(Integer, primary_key=True) 

179 type = Column(String(50), nullable=False) 

180 

181 @declared_attr 

182 def __tablename__(cls): 

183 return cls.__name__.lower() 

184 

185 @declared_attr 

186 def __mapper_args__(cls): 

187 if cls.__name__ == 'Employee': 

188 return { 

189 "polymorphic_on":cls.type, 

190 "polymorphic_identity":"Employee" 

191 } 

192 else: 

193 return {"polymorphic_identity":cls.__name__} 

194 

195 """ 

196 

197 def __init__(self, fget, cascading=False): 

198 super(declared_attr, self).__init__(fget) 

199 self.__doc__ = fget.__doc__ 

200 self._cascading = cascading 

201 

202 def __get__(desc, self, cls): 

203 reg = cls.__dict__.get("_sa_declared_attr_reg", None) 

204 if reg is None: 

205 if ( 

206 not re.match(r"^__.+__$", desc.fget.__name__) 

207 and attributes.manager_of_class(cls) is None 

208 ): 

209 util.warn( 

210 "Unmanaged access of declarative attribute %s from " 

211 "non-mapped class %s" % (desc.fget.__name__, cls.__name__) 

212 ) 

213 return desc.fget(cls) 

214 elif desc in reg: 

215 return reg[desc] 

216 else: 

217 reg[desc] = obj = desc.fget(cls) 

218 return obj 

219 

220 @hybridmethod 

221 def _stateful(cls, **kw): 

222 return _stateful_declared_attr(**kw) 

223 

224 @hybridproperty 

225 def cascading(cls): 

226 """Mark a :class:`.declared_attr` as cascading. 

227 

228 This is a special-use modifier which indicates that a column 

229 or MapperProperty-based declared attribute should be configured 

230 distinctly per mapped subclass, within a mapped-inheritance scenario. 

231 

232 .. warning:: 

233 

234 The :attr:`.declared_attr.cascading` modifier has several 

235 limitations: 

236 

237 * The flag **only** applies to the use of :class:`.declared_attr` 

238 on declarative mixin classes and ``__abstract__`` classes; it 

239 currently has no effect when used on a mapped class directly. 

240 

241 * The flag **only** applies to normally-named attributes, e.g. 

242 not any special underscore attributes such as ``__tablename__``. 

243 On these attributes it has **no** effect. 

244 

245 * The flag currently **does not allow further overrides** down 

246 the class hierarchy; if a subclass tries to override the 

247 attribute, a warning is emitted and the overridden attribute 

248 is skipped. This is a limitation that it is hoped will be 

249 resolved at some point. 

250 

251 Below, both MyClass as well as MySubClass will have a distinct 

252 ``id`` Column object established:: 

253 

254 class HasIdMixin(object): 

255 @declared_attr.cascading 

256 def id(cls): 

257 if has_inherited_table(cls): 

258 return Column( 

259 ForeignKey('myclass.id'), primary_key=True) 

260 else: 

261 return Column(Integer, primary_key=True) 

262 

263 class MyClass(HasIdMixin, Base): 

264 __tablename__ = 'myclass' 

265 # ... 

266 

267 class MySubClass(MyClass): 

268 "" 

269 # ... 

270 

271 The behavior of the above configuration is that ``MySubClass`` 

272 will refer to both its own ``id`` column as well as that of 

273 ``MyClass`` underneath the attribute named ``some_id``. 

274 

275 .. seealso:: 

276 

277 :ref:`declarative_inheritance` 

278 

279 :ref:`mixin_inheritance_columns` 

280 

281 

282 """ 

283 return cls._stateful(cascading=True) 

284 

285 

286class _stateful_declared_attr(declared_attr): 

287 def __init__(self, **kw): 

288 self.kw = kw 

289 

290 def _stateful(self, **kw): 

291 new_kw = self.kw.copy() 

292 new_kw.update(kw) 

293 return _stateful_declared_attr(**new_kw) 

294 

295 def __call__(self, fn): 

296 return declared_attr(fn, **self.kw) 

297 

298 

299def declarative_base( 

300 bind=None, 

301 metadata=None, 

302 mapper=None, 

303 cls=object, 

304 name="Base", 

305 constructor=_declarative_constructor, 

306 class_registry=None, 

307 metaclass=DeclarativeMeta, 

308): 

309 r"""Construct a base class for declarative class definitions. 

310 

311 The new base class will be given a metaclass that produces 

312 appropriate :class:`~sqlalchemy.schema.Table` objects and makes 

313 the appropriate :func:`~sqlalchemy.orm.mapper` calls based on the 

314 information provided declaratively in the class and any subclasses 

315 of the class. 

316 

317 :param bind: An optional 

318 :class:`~sqlalchemy.engine.Connectable`, will be assigned 

319 the ``bind`` attribute on the :class:`~sqlalchemy.schema.MetaData` 

320 instance. 

321 

322 :param metadata: 

323 An optional :class:`~sqlalchemy.schema.MetaData` instance. All 

324 :class:`~sqlalchemy.schema.Table` objects implicitly declared by 

325 subclasses of the base will share this MetaData. A MetaData instance 

326 will be created if none is provided. The 

327 :class:`~sqlalchemy.schema.MetaData` instance will be available via the 

328 `metadata` attribute of the generated declarative base class. 

329 

330 :param mapper: 

331 An optional callable, defaults to :func:`~sqlalchemy.orm.mapper`. Will 

332 be used to map subclasses to their Tables. 

333 

334 :param cls: 

335 Defaults to :class:`object`. A type to use as the base for the generated 

336 declarative base class. May be a class or tuple of classes. 

337 

338 :param name: 

339 Defaults to ``Base``. The display name for the generated 

340 class. Customizing this is not required, but can improve clarity in 

341 tracebacks and debugging. 

342 

343 :param constructor: 

344 Defaults to 

345 :func:`~sqlalchemy.ext.declarative.base._declarative_constructor`, an 

346 __init__ implementation that assigns \**kwargs for declared 

347 fields and relationships to an instance. If ``None`` is supplied, 

348 no __init__ will be provided and construction will fall back to 

349 cls.__init__ by way of the normal Python semantics. 

350 

351 :param class_registry: optional dictionary that will serve as the 

352 registry of class names-> mapped classes when string names 

353 are used to identify classes inside of :func:`_orm.relationship` 

354 and others. Allows two or more declarative base classes 

355 to share the same registry of class names for simplified 

356 inter-base relationships. 

357 

358 :param metaclass: 

359 Defaults to :class:`.DeclarativeMeta`. A metaclass or __metaclass__ 

360 compatible callable to use as the meta type of the generated 

361 declarative base class. 

362 

363 .. versionchanged:: 1.1 if :paramref:`.declarative_base.cls` is a 

364 single class (rather than a tuple), the constructed base class will 

365 inherit its docstring. 

366 

367 .. seealso:: 

368 

369 :func:`.as_declarative` 

370 

371 """ 

372 lcl_metadata = metadata or MetaData() 

373 if bind: 

374 lcl_metadata.bind = bind 

375 

376 if class_registry is None: 

377 class_registry = weakref.WeakValueDictionary() 

378 

379 bases = not isinstance(cls, tuple) and (cls,) or cls 

380 class_dict = dict( 

381 _decl_class_registry=class_registry, metadata=lcl_metadata 

382 ) 

383 

384 if isinstance(cls, type): 

385 class_dict["__doc__"] = cls.__doc__ 

386 

387 if constructor: 

388 class_dict["__init__"] = constructor 

389 if mapper: 

390 class_dict["__mapper_cls__"] = mapper 

391 

392 return metaclass(name, bases, class_dict) 

393 

394 

395def as_declarative(**kw): 

396 """ 

397 Class decorator for :func:`.declarative_base`. 

398 

399 Provides a syntactical shortcut to the ``cls`` argument 

400 sent to :func:`.declarative_base`, allowing the base class 

401 to be converted in-place to a "declarative" base:: 

402 

403 from sqlalchemy.ext.declarative import as_declarative 

404 

405 @as_declarative() 

406 class Base(object): 

407 @declared_attr 

408 def __tablename__(cls): 

409 return cls.__name__.lower() 

410 id = Column(Integer, primary_key=True) 

411 

412 class MyMappedClass(Base): 

413 # ... 

414 

415 All keyword arguments passed to :func:`.as_declarative` are passed 

416 along to :func:`.declarative_base`. 

417 

418 .. seealso:: 

419 

420 :func:`.declarative_base` 

421 

422 """ 

423 

424 def decorate(cls): 

425 kw["cls"] = cls 

426 kw["name"] = cls.__name__ 

427 return declarative_base(**kw) 

428 

429 return decorate 

430 

431 

432class ConcreteBase(object): 

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

434 

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

436 function automatically, against all tables mapped as a subclass 

437 to this class. The function is called via the 

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

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

440 

441 :class:`.ConcreteBase` produces a mapped 

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

443 which does not. 

444 

445 Example:: 

446 

447 from sqlalchemy.ext.declarative import ConcreteBase 

448 

449 class Employee(ConcreteBase, Base): 

450 __tablename__ = 'employee' 

451 employee_id = Column(Integer, primary_key=True) 

452 name = Column(String(50)) 

453 __mapper_args__ = { 

454 'polymorphic_identity':'employee', 

455 'concrete':True} 

456 

457 class Manager(Employee): 

458 __tablename__ = 'manager' 

459 employee_id = Column(Integer, primary_key=True) 

460 name = Column(String(50)) 

461 manager_data = Column(String(40)) 

462 __mapper_args__ = { 

463 'polymorphic_identity':'manager', 

464 'concrete':True} 

465 

466 .. seealso:: 

467 

468 :class:`.AbstractConcreteBase` 

469 

470 :ref:`concrete_inheritance` 

471 

472 

473 """ 

474 

475 @classmethod 

476 def _create_polymorphic_union(cls, mappers): 

477 return polymorphic_union( 

478 OrderedDict( 

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

480 ), 

481 "type", 

482 "pjoin", 

483 ) 

484 

485 @classmethod 

486 def __declare_first__(cls): 

487 m = cls.__mapper__ 

488 if m.with_polymorphic: 

489 return 

490 

491 mappers = list(m.self_and_descendants) 

492 pjoin = cls._create_polymorphic_union(mappers) 

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

494 m._set_polymorphic_on(pjoin.c.type) 

495 

496 

497class AbstractConcreteBase(ConcreteBase): 

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

499 

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

501 function automatically, against all tables mapped as a subclass 

502 to this class. The function is called via the 

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

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

505 

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

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

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

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

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

511 

512 .. note:: 

513 

514 The :class:`.AbstractConcreteBase` class does not intend to set up the 

515 mapping for the base class until all the subclasses have been defined, 

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

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

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

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

520 query against all subclasses at once. 

521 

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

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

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

525 operation is to be a query against this base class. To do so, invoke 

526 :func:`.configure_mappers` once all the desired classes have been 

527 configured:: 

528 

529 from sqlalchemy.orm import configure_mappers 

530 

531 configure_mappers() 

532 

533 .. seealso:: 

534 

535 :func:`_orm.configure_mappers` 

536 

537 

538 Example:: 

539 

540 from sqlalchemy.ext.declarative import AbstractConcreteBase 

541 

542 class Employee(AbstractConcreteBase, Base): 

543 pass 

544 

545 class Manager(Employee): 

546 __tablename__ = 'manager' 

547 employee_id = Column(Integer, primary_key=True) 

548 name = Column(String(50)) 

549 manager_data = Column(String(40)) 

550 

551 __mapper_args__ = { 

552 'polymorphic_identity':'manager', 

553 'concrete':True} 

554 

555 configure_mappers() 

556 

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

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

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

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

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

562 not found in any other SQLAlchemy system. 

563 

564 Using this approach, we can specify columns and properties 

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

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

567 

568 class Company(Base): 

569 __tablename__ = 'company' 

570 id = Column(Integer, primary_key=True) 

571 

572 class Employee(AbstractConcreteBase, Base): 

573 employee_id = Column(Integer, primary_key=True) 

574 

575 @declared_attr 

576 def company_id(cls): 

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

578 

579 @declared_attr 

580 def company(cls): 

581 return relationship("Company") 

582 

583 class Manager(Employee): 

584 __tablename__ = 'manager' 

585 

586 name = Column(String(50)) 

587 manager_data = Column(String(40)) 

588 

589 __mapper_args__ = { 

590 'polymorphic_identity':'manager', 

591 'concrete':True} 

592 

593 configure_mappers() 

594 

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

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

597 

598 session.query(Employee).filter(Employee.company.has(id=5)) 

599 

600 .. versionchanged:: 1.0.0 - The mechanics of :class:`.AbstractConcreteBase` 

601 have been reworked to support relationships established directly 

602 on the abstract base, without any special configurational steps. 

603 

604 .. seealso:: 

605 

606 :class:`.ConcreteBase` 

607 

608 :ref:`concrete_inheritance` 

609 

610 """ 

611 

612 __no_table__ = True 

613 

614 @classmethod 

615 def __declare_first__(cls): 

616 cls._sa_decl_prepare_nocascade() 

617 

618 @classmethod 

619 def _sa_decl_prepare_nocascade(cls): 

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

621 return 

622 

623 to_map = _DeferredMapperConfig.config_for_cls(cls) 

624 

625 # can't rely on 'self_and_descendants' here 

626 # since technically an immediate subclass 

627 # might not be mapped, but a subclass 

628 # may be. 

629 mappers = [] 

630 stack = list(cls.__subclasses__()) 

631 while stack: 

632 klass = stack.pop() 

633 stack.extend(klass.__subclasses__()) 

634 mn = _mapper_or_none(klass) 

635 if mn is not None: 

636 mappers.append(mn) 

637 pjoin = cls._create_polymorphic_union(mappers) 

638 

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

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

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

642 # and are in the properties argument. 

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

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

645 declared_cols = set(to_map.declared_columns) 

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

647 if v in declared_cols: 

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

649 

650 to_map.local_table = pjoin 

651 

652 m_args = to_map.mapper_args_fn or dict 

653 

654 def mapper_args(): 

655 args = m_args() 

656 args["polymorphic_on"] = pjoin.c.type 

657 return args 

658 

659 to_map.mapper_args_fn = mapper_args 

660 

661 m = to_map.map() 

662 

663 for scls in cls.__subclasses__(): 

664 sm = _mapper_or_none(scls) 

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

666 sm._set_concrete_base(m) 

667 

668 @classmethod 

669 def _sa_raise_deferred_config(cls): 

670 raise orm_exc.UnmappedClassError( 

671 cls, 

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

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

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

675 "all subclasses have been defined to " 

676 "complete the mapping of this class." 

677 % orm_exc._safe_cls_name(cls), 

678 ) 

679 

680 

681class DeferredReflection(object): 

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

683 a deferred reflection step. 

684 

685 Normally, declarative can be used with reflection by 

686 setting a :class:`_schema.Table` object using autoload=True 

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

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

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

690 at the point at which a normal declarative mapping is 

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

692 at class declaration time. 

693 

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

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

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

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

698 

699 from sqlalchemy.ext.declarative import declarative_base 

700 from sqlalchemy.ext.declarative import DeferredReflection 

701 Base = declarative_base() 

702 

703 class MyClass(DeferredReflection, Base): 

704 __tablename__ = 'mytable' 

705 

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

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

708 can be reflected and mappings created using 

709 :meth:`.prepare`:: 

710 

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

712 DeferredReflection.prepare(engine) 

713 

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

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

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

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

718 particular prepare step, which is necessary for applications 

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

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

721 separately, e.g.:: 

722 

723 class ReflectedOne(DeferredReflection, Base): 

724 __abstract__ = True 

725 

726 class ReflectedTwo(DeferredReflection, Base): 

727 __abstract__ = True 

728 

729 class MyClass(ReflectedOne): 

730 __tablename__ = 'mytable' 

731 

732 class MyOtherClass(ReflectedOne): 

733 __tablename__ = 'myothertable' 

734 

735 class YetAnotherClass(ReflectedTwo): 

736 __tablename__ = 'yetanothertable' 

737 

738 # ... etc. 

739 

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

741 ``ReflectedTwo`` can be configured separately:: 

742 

743 ReflectedOne.prepare(engine_one) 

744 ReflectedTwo.prepare(engine_two) 

745 

746 """ 

747 

748 @classmethod 

749 def prepare(cls, engine): 

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

751 :class:`.DeferredReflection` subclasses""" 

752 

753 to_map = _DeferredMapperConfig.classes_for_base(cls) 

754 for thingy in to_map: 

755 cls._sa_decl_prepare(thingy.local_table, engine) 

756 thingy.map() 

757 mapper = thingy.cls.__mapper__ 

758 metadata = mapper.class_.metadata 

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

760 if ( 

761 isinstance(rel, properties.RelationshipProperty) 

762 and rel.secondary is not None 

763 ): 

764 if isinstance(rel.secondary, Table): 

765 cls._reflect_table(rel.secondary, engine) 

766 elif isinstance(rel.secondary, _class_resolver): 

767 rel.secondary._resolvers += ( 

768 cls._sa_deferred_table_resolver(engine, metadata), 

769 ) 

770 

771 @classmethod 

772 def _sa_deferred_table_resolver(cls, engine, metadata): 

773 def _resolve(key): 

774 t1 = Table(key, metadata) 

775 cls._reflect_table(t1, engine) 

776 return t1 

777 

778 return _resolve 

779 

780 @classmethod 

781 def _sa_decl_prepare(cls, local_table, engine): 

782 # autoload Table, which is already 

783 # present in the metadata. This 

784 # will fill in db-loaded columns 

785 # into the existing Table object. 

786 if local_table is not None: 

787 cls._reflect_table(local_table, engine) 

788 

789 @classmethod 

790 def _sa_raise_deferred_config(cls): 

791 raise orm_exc.UnmappedClassError( 

792 cls, 

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

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

795 "method is called on the class hierarchy." 

796 % orm_exc._safe_cls_name(cls), 

797 ) 

798 

799 @classmethod 

800 def _reflect_table(cls, table, engine): 

801 Table( 

802 table.name, 

803 table.metadata, 

804 extend_existing=True, 

805 autoload_replace=False, 

806 autoload=True, 

807 autoload_with=engine, 

808 schema=table.schema, 

809 ) 

810 

811 

812@inspection._inspects(DeclarativeMeta) 

813def _inspect_decl_meta(cls): 

814 mp = _inspect_mapped_class(cls) 

815 if mp is None: 

816 if _DeferredMapperConfig.has_cls(cls): 

817 _DeferredMapperConfig.raise_unmapped_for_cls(cls) 

818 raise orm_exc.UnmappedClassError( 

819 cls, 

820 msg="Class %s has a deferred mapping on it. It is not yet " 

821 "usable as a mapped class." % orm_exc._safe_cls_name(cls), 

822 ) 

823 return mp