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/base.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"""Internal implementation for declarative.""" 

8 

9import collections 

10import weakref 

11 

12from sqlalchemy.orm import instrumentation 

13from . import clsregistry 

14from ... import event 

15from ... import exc 

16from ... import util 

17from ...orm import class_mapper 

18from ...orm import exc as orm_exc 

19from ...orm import mapper 

20from ...orm import mapperlib 

21from ...orm import synonym 

22from ...orm.attributes import QueryableAttribute 

23from ...orm.base import _is_mapped_class 

24from ...orm.base import InspectionAttr 

25from ...orm.interfaces import MapperProperty 

26from ...orm.properties import ColumnProperty 

27from ...orm.properties import CompositeProperty 

28from ...schema import Column 

29from ...schema import Table 

30from ...sql import expression 

31from ...util import topological 

32 

33 

34declared_attr = declarative_props = None 

35 

36 

37def _declared_mapping_info(cls): 

38 # deferred mapping 

39 if _DeferredMapperConfig.has_cls(cls): 

40 return _DeferredMapperConfig.config_for_cls(cls) 

41 # regular mapping 

42 elif _is_mapped_class(cls): 

43 return class_mapper(cls, configure=False) 

44 else: 

45 return None 

46 

47 

48def _resolve_for_abstract_or_classical(cls): 

49 if cls is object: 

50 return None 

51 

52 if _get_immediate_cls_attr(cls, "__abstract__", strict=True): 

53 for sup in cls.__bases__: 

54 sup = _resolve_for_abstract_or_classical(sup) 

55 if sup is not None: 

56 return sup 

57 else: 

58 return None 

59 else: 

60 classical = _dive_for_classically_mapped_class(cls) 

61 if classical is not None: 

62 return classical 

63 else: 

64 return cls 

65 

66 

67def _dive_for_classically_mapped_class(cls): 

68 # support issue #4321 

69 

70 # if we are within a base hierarchy, don't 

71 # search at all for classical mappings 

72 if hasattr(cls, "_decl_class_registry"): 

73 return None 

74 

75 manager = instrumentation.manager_of_class(cls) 

76 if manager is not None: 

77 return cls 

78 else: 

79 for sup in cls.__bases__: 

80 mapper = _dive_for_classically_mapped_class(sup) 

81 if mapper is not None: 

82 return sup 

83 else: 

84 return None 

85 

86 

87def _get_immediate_cls_attr(cls, attrname, strict=False): 

88 """return an attribute of the class that is either present directly 

89 on the class, e.g. not on a superclass, or is from a superclass but 

90 this superclass is a non-mapped mixin, that is, not a descendant of 

91 the declarative base and is also not classically mapped. 

92 

93 This is used to detect attributes that indicate something about 

94 a mapped class independently from any mapped classes that it may 

95 inherit from. 

96 

97 """ 

98 if not issubclass(cls, object): 

99 return None 

100 

101 for base in cls.__mro__: 

102 _is_declarative_inherits = hasattr(base, "_decl_class_registry") 

103 _is_classicial_inherits = ( 

104 not _is_declarative_inherits 

105 and _dive_for_classically_mapped_class(base) is not None 

106 ) 

107 

108 if attrname in base.__dict__ and ( 

109 base is cls 

110 or ( 

111 (base in cls.__bases__ if strict else True) 

112 and not _is_declarative_inherits 

113 and not _is_classicial_inherits 

114 ) 

115 ): 

116 return getattr(base, attrname) 

117 else: 

118 return None 

119 

120 

121def _as_declarative(cls, classname, dict_): 

122 global declared_attr, declarative_props 

123 if declared_attr is None: 

124 from .api import declared_attr 

125 

126 declarative_props = (declared_attr, util.classproperty) 

127 

128 if _get_immediate_cls_attr(cls, "__abstract__", strict=True): 

129 return 

130 

131 _MapperConfig.setup_mapping(cls, classname, dict_) 

132 

133 

134def _check_declared_props_nocascade(obj, name, cls): 

135 

136 if isinstance(obj, declarative_props): 

137 if getattr(obj, "_cascading", False): 

138 util.warn( 

139 "@declared_attr.cascading is not supported on the %s " 

140 "attribute on class %s. This attribute invokes for " 

141 "subclasses in any case." % (name, cls) 

142 ) 

143 return True 

144 else: 

145 return False 

146 

147 

148class _MapperConfig(object): 

149 @classmethod 

150 def setup_mapping(cls, cls_, classname, dict_): 

151 defer_map = _get_immediate_cls_attr( 

152 cls_, "_sa_decl_prepare_nocascade", strict=True 

153 ) or hasattr(cls_, "_sa_decl_prepare") 

154 

155 if defer_map: 

156 cfg_cls = _DeferredMapperConfig 

157 else: 

158 cfg_cls = _MapperConfig 

159 

160 cfg_cls(cls_, classname, dict_) 

161 

162 def __init__(self, cls_, classname, dict_): 

163 

164 self.cls = cls_ 

165 

166 # dict_ will be a dictproxy, which we can't write to, and we need to! 

167 self.dict_ = dict(dict_) 

168 self.classname = classname 

169 self.persist_selectable = None 

170 self.properties = util.OrderedDict() 

171 self.declared_columns = set() 

172 self.column_copies = {} 

173 self._setup_declared_events() 

174 

175 # temporary registry. While early 1.0 versions 

176 # set up the ClassManager here, by API contract 

177 # we can't do that until there's a mapper. 

178 self.cls._sa_declared_attr_reg = {} 

179 

180 self._scan_attributes() 

181 

182 mapperlib._CONFIGURE_MUTEX.acquire() 

183 try: 

184 clsregistry.add_class(self.classname, self.cls) 

185 

186 self._extract_mappable_attributes() 

187 

188 self._extract_declared_columns() 

189 

190 self._setup_table() 

191 

192 self._setup_inheritance() 

193 

194 self._early_mapping() 

195 finally: 

196 mapperlib._CONFIGURE_MUTEX.release() 

197 

198 def _early_mapping(self): 

199 self.map() 

200 

201 def _setup_declared_events(self): 

202 if _get_immediate_cls_attr(self.cls, "__declare_last__"): 

203 

204 @event.listens_for(mapper, "after_configured") 

205 def after_configured(): 

206 self.cls.__declare_last__() 

207 

208 if _get_immediate_cls_attr(self.cls, "__declare_first__"): 

209 

210 @event.listens_for(mapper, "before_configured") 

211 def before_configured(): 

212 self.cls.__declare_first__() 

213 

214 def _scan_attributes(self): 

215 cls = self.cls 

216 dict_ = self.dict_ 

217 column_copies = self.column_copies 

218 mapper_args_fn = None 

219 table_args = inherited_table_args = None 

220 tablename = None 

221 

222 for base in cls.__mro__: 

223 class_mapped = ( 

224 base is not cls 

225 and _declared_mapping_info(base) is not None 

226 and not _get_immediate_cls_attr( 

227 base, "_sa_decl_prepare_nocascade", strict=True 

228 ) 

229 ) 

230 

231 if not class_mapped and base is not cls: 

232 self._produce_column_copies(base) 

233 

234 for name, obj in vars(base).items(): 

235 if name == "__mapper_args__": 

236 check_decl = _check_declared_props_nocascade( 

237 obj, name, cls 

238 ) 

239 if not mapper_args_fn and (not class_mapped or check_decl): 

240 # don't even invoke __mapper_args__ until 

241 # after we've determined everything about the 

242 # mapped table. 

243 # make a copy of it so a class-level dictionary 

244 # is not overwritten when we update column-based 

245 # arguments. 

246 def mapper_args_fn(): 

247 return dict(cls.__mapper_args__) 

248 

249 elif name == "__tablename__": 

250 check_decl = _check_declared_props_nocascade( 

251 obj, name, cls 

252 ) 

253 if not tablename and (not class_mapped or check_decl): 

254 tablename = cls.__tablename__ 

255 elif name == "__table_args__": 

256 check_decl = _check_declared_props_nocascade( 

257 obj, name, cls 

258 ) 

259 if not table_args and (not class_mapped or check_decl): 

260 table_args = cls.__table_args__ 

261 if not isinstance( 

262 table_args, (tuple, dict, type(None)) 

263 ): 

264 raise exc.ArgumentError( 

265 "__table_args__ value must be a tuple, " 

266 "dict, or None" 

267 ) 

268 if base is not cls: 

269 inherited_table_args = True 

270 elif class_mapped: 

271 if isinstance(obj, declarative_props): 

272 util.warn( 

273 "Regular (i.e. not __special__) " 

274 "attribute '%s.%s' uses @declared_attr, " 

275 "but owning class %s is mapped - " 

276 "not applying to subclass %s." 

277 % (base.__name__, name, base, cls) 

278 ) 

279 continue 

280 elif base is not cls: 

281 # we're a mixin, abstract base, or something that is 

282 # acting like that for now. 

283 if isinstance(obj, Column): 

284 # already copied columns to the mapped class. 

285 continue 

286 elif isinstance(obj, MapperProperty): 

287 raise exc.InvalidRequestError( 

288 "Mapper properties (i.e. deferred," 

289 "column_property(), relationship(), etc.) must " 

290 "be declared as @declared_attr callables " 

291 "on declarative mixin classes." 

292 ) 

293 elif isinstance(obj, declarative_props): 

294 oldclassprop = isinstance(obj, util.classproperty) 

295 if not oldclassprop and obj._cascading: 

296 if name in dict_: 

297 # unfortunately, while we can use the user- 

298 # defined attribute here to allow a clean 

299 # override, if there's another 

300 # subclass below then it still tries to use 

301 # this. not sure if there is enough 

302 # information here to add this as a feature 

303 # later on. 

304 util.warn( 

305 "Attribute '%s' on class %s cannot be " 

306 "processed due to " 

307 "@declared_attr.cascading; " 

308 "skipping" % (name, cls) 

309 ) 

310 dict_[name] = column_copies[ 

311 obj 

312 ] = ret = obj.__get__(obj, cls) 

313 setattr(cls, name, ret) 

314 else: 

315 if oldclassprop: 

316 util.warn_deprecated( 

317 "Use of sqlalchemy.util.classproperty on " 

318 "declarative classes is deprecated." 

319 ) 

320 # access attribute using normal class access 

321 ret = getattr(cls, name) 

322 

323 # correct for proxies created from hybrid_property 

324 # or similar. note there is no known case that 

325 # produces nested proxies, so we are only 

326 # looking one level deep right now. 

327 if ( 

328 isinstance(ret, InspectionAttr) 

329 and ret._is_internal_proxy 

330 and not isinstance( 

331 ret.original_property, MapperProperty 

332 ) 

333 ): 

334 ret = ret.descriptor 

335 

336 dict_[name] = column_copies[obj] = ret 

337 if ( 

338 isinstance(ret, (Column, MapperProperty)) 

339 and ret.doc is None 

340 ): 

341 ret.doc = obj.__doc__ 

342 # here, the attribute is some other kind of property that 

343 # we assume is not part of the declarative mapping. 

344 # however, check for some more common mistakes 

345 else: 

346 self._warn_for_decl_attributes(base, name, obj) 

347 

348 if inherited_table_args and not tablename: 

349 table_args = None 

350 

351 self.table_args = table_args 

352 self.tablename = tablename 

353 self.mapper_args_fn = mapper_args_fn 

354 

355 def _warn_for_decl_attributes(self, cls, key, c): 

356 if isinstance(c, expression.ColumnClause): 

357 util.warn( 

358 "Attribute '%s' on class %s appears to be a non-schema " 

359 "'sqlalchemy.sql.column()' " 

360 "object; this won't be part of the declarative mapping" 

361 % (key, cls) 

362 ) 

363 

364 def _produce_column_copies(self, base): 

365 cls = self.cls 

366 dict_ = self.dict_ 

367 column_copies = self.column_copies 

368 # copy mixin columns to the mapped class 

369 for name, obj in vars(base).items(): 

370 if isinstance(obj, Column): 

371 if getattr(cls, name) is not obj: 

372 # if column has been overridden 

373 # (like by the InstrumentedAttribute of the 

374 # superclass), skip 

375 continue 

376 elif obj.foreign_keys: 

377 raise exc.InvalidRequestError( 

378 "Columns with foreign keys to other columns " 

379 "must be declared as @declared_attr callables " 

380 "on declarative mixin classes. " 

381 ) 

382 elif name not in dict_ and not ( 

383 "__table__" in dict_ 

384 and (obj.name or name) in dict_["__table__"].c 

385 ): 

386 column_copies[obj] = copy_ = obj.copy() 

387 copy_._creation_order = obj._creation_order 

388 setattr(cls, name, copy_) 

389 dict_[name] = copy_ 

390 

391 def _extract_mappable_attributes(self): 

392 cls = self.cls 

393 dict_ = self.dict_ 

394 

395 our_stuff = self.properties 

396 

397 late_mapped = _get_immediate_cls_attr( 

398 cls, "_sa_decl_prepare_nocascade", strict=True 

399 ) 

400 

401 for k in list(dict_): 

402 

403 if k in ("__table__", "__tablename__", "__mapper_args__"): 

404 continue 

405 

406 value = dict_[k] 

407 if isinstance(value, declarative_props): 

408 if isinstance(value, declared_attr) and value._cascading: 

409 util.warn( 

410 "Use of @declared_attr.cascading only applies to " 

411 "Declarative 'mixin' and 'abstract' classes. " 

412 "Currently, this flag is ignored on mapped class " 

413 "%s" % self.cls 

414 ) 

415 

416 value = getattr(cls, k) 

417 

418 elif ( 

419 isinstance(value, QueryableAttribute) 

420 and value.class_ is not cls 

421 and value.key != k 

422 ): 

423 # detect a QueryableAttribute that's already mapped being 

424 # assigned elsewhere in userland, turn into a synonym() 

425 value = synonym(value.key) 

426 setattr(cls, k, value) 

427 

428 if ( 

429 isinstance(value, tuple) 

430 and len(value) == 1 

431 and isinstance(value[0], (Column, MapperProperty)) 

432 ): 

433 util.warn( 

434 "Ignoring declarative-like tuple value of attribute " 

435 "'%s': possibly a copy-and-paste error with a comma " 

436 "accidentally placed at the end of the line?" % k 

437 ) 

438 continue 

439 elif not isinstance(value, (Column, MapperProperty)): 

440 # using @declared_attr for some object that 

441 # isn't Column/MapperProperty; remove from the dict_ 

442 # and place the evaluated value onto the class. 

443 if not k.startswith("__"): 

444 dict_.pop(k) 

445 self._warn_for_decl_attributes(cls, k, value) 

446 if not late_mapped: 

447 setattr(cls, k, value) 

448 continue 

449 # we expect to see the name 'metadata' in some valid cases; 

450 # however at this point we see it's assigned to something trying 

451 # to be mapped, so raise for that. 

452 elif k == "metadata": 

453 raise exc.InvalidRequestError( 

454 "Attribute name 'metadata' is reserved " 

455 "for the MetaData instance when using a " 

456 "declarative base class." 

457 ) 

458 prop = clsregistry._deferred_relationship(cls, value) 

459 our_stuff[k] = prop 

460 

461 def _extract_declared_columns(self): 

462 our_stuff = self.properties 

463 

464 # set up attributes in the order they were created 

465 our_stuff.sort(key=lambda key: our_stuff[key]._creation_order) 

466 

467 # extract columns from the class dict 

468 declared_columns = self.declared_columns 

469 name_to_prop_key = collections.defaultdict(set) 

470 for key, c in list(our_stuff.items()): 

471 if isinstance(c, (ColumnProperty, CompositeProperty)): 

472 for col in c.columns: 

473 if isinstance(col, Column) and col.table is None: 

474 _undefer_column_name(key, col) 

475 if not isinstance(c, CompositeProperty): 

476 name_to_prop_key[col.name].add(key) 

477 declared_columns.add(col) 

478 elif isinstance(c, Column): 

479 _undefer_column_name(key, c) 

480 name_to_prop_key[c.name].add(key) 

481 declared_columns.add(c) 

482 # if the column is the same name as the key, 

483 # remove it from the explicit properties dict. 

484 # the normal rules for assigning column-based properties 

485 # will take over, including precedence of columns 

486 # in multi-column ColumnProperties. 

487 if key == c.key: 

488 del our_stuff[key] 

489 

490 for name, keys in name_to_prop_key.items(): 

491 if len(keys) > 1: 

492 util.warn( 

493 "On class %r, Column object %r named " 

494 "directly multiple times, " 

495 "only one will be used: %s. " 

496 "Consider using orm.synonym instead" 

497 % (self.classname, name, (", ".join(sorted(keys)))) 

498 ) 

499 

500 def _setup_table(self): 

501 cls = self.cls 

502 tablename = self.tablename 

503 table_args = self.table_args 

504 dict_ = self.dict_ 

505 declared_columns = self.declared_columns 

506 

507 declared_columns = self.declared_columns = sorted( 

508 declared_columns, key=lambda c: c._creation_order 

509 ) 

510 table = None 

511 

512 if hasattr(cls, "__table_cls__"): 

513 table_cls = util.unbound_method_to_callable(cls.__table_cls__) 

514 else: 

515 table_cls = Table 

516 

517 if "__table__" not in dict_: 

518 if tablename is not None: 

519 

520 args, table_kw = (), {} 

521 if table_args: 

522 if isinstance(table_args, dict): 

523 table_kw = table_args 

524 elif isinstance(table_args, tuple): 

525 if isinstance(table_args[-1], dict): 

526 args, table_kw = table_args[0:-1], table_args[-1] 

527 else: 

528 args = table_args 

529 

530 autoload = dict_.get("__autoload__") 

531 if autoload: 

532 table_kw["autoload"] = True 

533 

534 cls.__table__ = table = table_cls( 

535 tablename, 

536 cls.metadata, 

537 *(tuple(declared_columns) + tuple(args)), 

538 **table_kw 

539 ) 

540 else: 

541 table = cls.__table__ 

542 if declared_columns: 

543 for c in declared_columns: 

544 if not table.c.contains_column(c): 

545 raise exc.ArgumentError( 

546 "Can't add additional column %r when " 

547 "specifying __table__" % c.key 

548 ) 

549 self.local_table = table 

550 

551 def _setup_inheritance(self): 

552 table = self.local_table 

553 cls = self.cls 

554 table_args = self.table_args 

555 declared_columns = self.declared_columns 

556 

557 # since we search for classical mappings now, search for 

558 # multiple mapped bases as well and raise an error. 

559 inherits = [] 

560 for c in cls.__bases__: 

561 c = _resolve_for_abstract_or_classical(c) 

562 if c is None: 

563 continue 

564 if _declared_mapping_info( 

565 c 

566 ) is not None and not _get_immediate_cls_attr( 

567 c, "_sa_decl_prepare_nocascade", strict=True 

568 ): 

569 inherits.append(c) 

570 

571 if inherits: 

572 if len(inherits) > 1: 

573 raise exc.InvalidRequestError( 

574 "Class %s has multiple mapped bases: %r" % (cls, inherits) 

575 ) 

576 self.inherits = inherits[0] 

577 else: 

578 self.inherits = None 

579 

580 if ( 

581 table is None 

582 and self.inherits is None 

583 and not _get_immediate_cls_attr(cls, "__no_table__") 

584 ): 

585 

586 raise exc.InvalidRequestError( 

587 "Class %r does not have a __table__ or __tablename__ " 

588 "specified and does not inherit from an existing " 

589 "table-mapped class." % cls 

590 ) 

591 elif self.inherits: 

592 inherited_mapper = _declared_mapping_info(self.inherits) 

593 inherited_table = inherited_mapper.local_table 

594 inherited_persist_selectable = inherited_mapper.persist_selectable 

595 

596 if table is None: 

597 # single table inheritance. 

598 # ensure no table args 

599 if table_args: 

600 raise exc.ArgumentError( 

601 "Can't place __table_args__ on an inherited class " 

602 "with no table." 

603 ) 

604 # add any columns declared here to the inherited table. 

605 for c in declared_columns: 

606 if c.name in inherited_table.c: 

607 if inherited_table.c[c.name] is c: 

608 continue 

609 raise exc.ArgumentError( 

610 "Column '%s' on class %s conflicts with " 

611 "existing column '%s'" 

612 % (c, cls, inherited_table.c[c.name]) 

613 ) 

614 if c.primary_key: 

615 raise exc.ArgumentError( 

616 "Can't place primary key columns on an inherited " 

617 "class with no table." 

618 ) 

619 inherited_table.append_column(c) 

620 if ( 

621 inherited_persist_selectable is not None 

622 and inherited_persist_selectable is not inherited_table 

623 ): 

624 inherited_persist_selectable._refresh_for_new_column(c) 

625 

626 def _prepare_mapper_arguments(self): 

627 properties = self.properties 

628 if self.mapper_args_fn: 

629 mapper_args = self.mapper_args_fn() 

630 else: 

631 mapper_args = {} 

632 

633 # make sure that column copies are used rather 

634 # than the original columns from any mixins 

635 for k in ("version_id_col", "polymorphic_on"): 

636 if k in mapper_args: 

637 v = mapper_args[k] 

638 mapper_args[k] = self.column_copies.get(v, v) 

639 

640 assert ( 

641 "inherits" not in mapper_args 

642 ), "Can't specify 'inherits' explicitly with declarative mappings" 

643 

644 if self.inherits: 

645 mapper_args["inherits"] = self.inherits 

646 

647 if self.inherits and not mapper_args.get("concrete", False): 

648 # single or joined inheritance 

649 # exclude any cols on the inherited table which are 

650 # not mapped on the parent class, to avoid 

651 # mapping columns specific to sibling/nephew classes 

652 inherited_mapper = _declared_mapping_info(self.inherits) 

653 inherited_table = inherited_mapper.local_table 

654 

655 if "exclude_properties" not in mapper_args: 

656 mapper_args["exclude_properties"] = exclude_properties = set( 

657 [ 

658 c.key 

659 for c in inherited_table.c 

660 if c not in inherited_mapper._columntoproperty 

661 ] 

662 ).union(inherited_mapper.exclude_properties or ()) 

663 exclude_properties.difference_update( 

664 [c.key for c in self.declared_columns] 

665 ) 

666 

667 # look through columns in the current mapper that 

668 # are keyed to a propname different than the colname 

669 # (if names were the same, we'd have popped it out above, 

670 # in which case the mapper makes this combination). 

671 # See if the superclass has a similar column property. 

672 # If so, join them together. 

673 for k, col in list(properties.items()): 

674 if not isinstance(col, expression.ColumnElement): 

675 continue 

676 if k in inherited_mapper._props: 

677 p = inherited_mapper._props[k] 

678 if isinstance(p, ColumnProperty): 

679 # note here we place the subclass column 

680 # first. See [ticket:1892] for background. 

681 properties[k] = [col] + p.columns 

682 result_mapper_args = mapper_args.copy() 

683 result_mapper_args["properties"] = properties 

684 self.mapper_args = result_mapper_args 

685 

686 def map(self): 

687 self._prepare_mapper_arguments() 

688 if hasattr(self.cls, "__mapper_cls__"): 

689 mapper_cls = util.unbound_method_to_callable( 

690 self.cls.__mapper_cls__ 

691 ) 

692 else: 

693 mapper_cls = mapper 

694 

695 self.cls.__mapper__ = mp_ = mapper_cls( 

696 self.cls, self.local_table, **self.mapper_args 

697 ) 

698 del self.cls._sa_declared_attr_reg 

699 return mp_ 

700 

701 

702class _DeferredMapperConfig(_MapperConfig): 

703 _configs = util.OrderedDict() 

704 

705 def _early_mapping(self): 

706 pass 

707 

708 @property 

709 def cls(self): 

710 return self._cls() 

711 

712 @cls.setter 

713 def cls(self, class_): 

714 self._cls = weakref.ref(class_, self._remove_config_cls) 

715 self._configs[self._cls] = self 

716 

717 @classmethod 

718 def _remove_config_cls(cls, ref): 

719 cls._configs.pop(ref, None) 

720 

721 @classmethod 

722 def has_cls(cls, class_): 

723 # 2.6 fails on weakref if class_ is an old style class 

724 return isinstance(class_, type) and weakref.ref(class_) in cls._configs 

725 

726 @classmethod 

727 def raise_unmapped_for_cls(cls, class_): 

728 if hasattr(class_, "_sa_raise_deferred_config"): 

729 class_._sa_raise_deferred_config() 

730 

731 raise orm_exc.UnmappedClassError( 

732 class_, 

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

734 "usable as a mapped class." % orm_exc._safe_cls_name(class_), 

735 ) 

736 

737 @classmethod 

738 def config_for_cls(cls, class_): 

739 return cls._configs[weakref.ref(class_)] 

740 

741 @classmethod 

742 def classes_for_base(cls, base_cls, sort=True): 

743 classes_for_base = [ 

744 m 

745 for m, cls_ in [(m, m.cls) for m in cls._configs.values()] 

746 if cls_ is not None and issubclass(cls_, base_cls) 

747 ] 

748 

749 if not sort: 

750 return classes_for_base 

751 

752 all_m_by_cls = dict((m.cls, m) for m in classes_for_base) 

753 

754 tuples = [] 

755 for m_cls in all_m_by_cls: 

756 tuples.extend( 

757 (all_m_by_cls[base_cls], all_m_by_cls[m_cls]) 

758 for base_cls in m_cls.__bases__ 

759 if base_cls in all_m_by_cls 

760 ) 

761 return list(topological.sort(tuples, classes_for_base)) 

762 

763 def map(self): 

764 self._configs.pop(self._cls, None) 

765 return super(_DeferredMapperConfig, self).map() 

766 

767 

768def _add_attribute(cls, key, value): 

769 """add an attribute to an existing declarative class. 

770 

771 This runs through the logic to determine MapperProperty, 

772 adds it to the Mapper, adds a column to the mapped Table, etc. 

773 

774 """ 

775 

776 if "__mapper__" in cls.__dict__: 

777 if isinstance(value, Column): 

778 _undefer_column_name(key, value) 

779 cls.__table__.append_column(value) 

780 cls.__mapper__.add_property(key, value) 

781 elif isinstance(value, ColumnProperty): 

782 for col in value.columns: 

783 if isinstance(col, Column) and col.table is None: 

784 _undefer_column_name(key, col) 

785 cls.__table__.append_column(col) 

786 cls.__mapper__.add_property(key, value) 

787 elif isinstance(value, MapperProperty): 

788 cls.__mapper__.add_property( 

789 key, clsregistry._deferred_relationship(cls, value) 

790 ) 

791 elif isinstance(value, QueryableAttribute) and value.key != key: 

792 # detect a QueryableAttribute that's already mapped being 

793 # assigned elsewhere in userland, turn into a synonym() 

794 value = synonym(value.key) 

795 cls.__mapper__.add_property( 

796 key, clsregistry._deferred_relationship(cls, value) 

797 ) 

798 else: 

799 type.__setattr__(cls, key, value) 

800 cls.__mapper__._expire_memoizations() 

801 else: 

802 type.__setattr__(cls, key, value) 

803 

804 

805def _del_attribute(cls, key): 

806 

807 if ( 

808 "__mapper__" in cls.__dict__ 

809 and key in cls.__dict__ 

810 and not cls.__mapper__._dispose_called 

811 ): 

812 value = cls.__dict__[key] 

813 if isinstance( 

814 value, (Column, ColumnProperty, MapperProperty, QueryableAttribute) 

815 ): 

816 raise NotImplementedError( 

817 "Can't un-map individual mapped attributes on a mapped class." 

818 ) 

819 else: 

820 type.__delattr__(cls, key) 

821 cls.__mapper__._expire_memoizations() 

822 else: 

823 type.__delattr__(cls, key) 

824 

825 

826def _declarative_constructor(self, **kwargs): 

827 """A simple constructor that allows initialization from kwargs. 

828 

829 Sets attributes on the constructed instance using the names and 

830 values in ``kwargs``. 

831 

832 Only keys that are present as 

833 attributes of the instance's class are allowed. These could be, 

834 for example, any mapped columns or relationships. 

835 """ 

836 cls_ = type(self) 

837 for k in kwargs: 

838 if not hasattr(cls_, k): 

839 raise TypeError( 

840 "%r is an invalid keyword argument for %s" % (k, cls_.__name__) 

841 ) 

842 setattr(self, k, kwargs[k]) 

843 

844 

845_declarative_constructor.__name__ = "__init__" 

846 

847 

848def _undefer_column_name(key, column): 

849 if column.key is None: 

850 column.key = key 

851 if column.name is None: 

852 column.name = key