Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/sqlalchemy/engine/row.py: 42%

206 statements  

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

1# engine/row.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 

8"""Define row constructs including :class:`.Row`.""" 

9 

10 

11import operator 

12 

13from .. import util 

14from ..sql import util as sql_util 

15from ..util.compat import collections_abc 

16 

17MD_INDEX = 0 # integer index in cursor.description 

18 

19# This reconstructor is necessary so that pickles with the C extension or 

20# without use the same Binary format. 

21try: 

22 # We need a different reconstructor on the C extension so that we can 

23 # add extra checks that fields have correctly been initialized by 

24 # __setstate__. 

25 from sqlalchemy.cresultproxy import safe_rowproxy_reconstructor 

26 

27 # The extra function embedding is needed so that the 

28 # reconstructor function has the same signature whether or not 

29 # the extension is present. 

30 def rowproxy_reconstructor(cls, state): 

31 return safe_rowproxy_reconstructor(cls, state) 

32 

33 

34except ImportError: 

35 

36 def rowproxy_reconstructor(cls, state): 

37 obj = cls.__new__(cls) 

38 obj.__setstate__(state) 

39 return obj 

40 

41 

42KEY_INTEGER_ONLY = 0 

43"""__getitem__ only allows integer values, raises TypeError otherwise""" 

44 

45KEY_OBJECTS_ONLY = 1 

46"""__getitem__ only allows string/object values, raises TypeError otherwise""" 

47 

48KEY_OBJECTS_BUT_WARN = 2 

49"""__getitem__ allows integer or string/object values, but emits a 2.0 

50deprecation warning if string/object is passed""" 

51 

52KEY_OBJECTS_NO_WARN = 3 

53"""__getitem__ allows integer or string/object values with no warnings 

54or errors.""" 

55 

56try: 

57 from sqlalchemy.cresultproxy import BaseRow 

58 

59 _baserow_usecext = True 

60except ImportError: 

61 _baserow_usecext = False 

62 

63 class BaseRow(object): 

64 __slots__ = ("_parent", "_data", "_keymap", "_key_style") 

65 

66 def __init__(self, parent, processors, keymap, key_style, data): 

67 """Row objects are constructed by CursorResult objects.""" 

68 

69 object.__setattr__(self, "_parent", parent) 

70 

71 if processors: 

72 object.__setattr__( 

73 self, 

74 "_data", 

75 tuple( 

76 [ 

77 proc(value) if proc else value 

78 for proc, value in zip(processors, data) 

79 ] 

80 ), 

81 ) 

82 else: 

83 object.__setattr__(self, "_data", tuple(data)) 

84 

85 object.__setattr__(self, "_keymap", keymap) 

86 

87 object.__setattr__(self, "_key_style", key_style) 

88 

89 def __reduce__(self): 

90 return ( 

91 rowproxy_reconstructor, 

92 (self.__class__, self.__getstate__()), 

93 ) 

94 

95 def _filter_on_values(self, filters): 

96 return Row( 

97 self._parent, 

98 filters, 

99 self._keymap, 

100 self._key_style, 

101 self._data, 

102 ) 

103 

104 def _values_impl(self): 

105 return list(self) 

106 

107 def __iter__(self): 

108 return iter(self._data) 

109 

110 def __len__(self): 

111 return len(self._data) 

112 

113 def __hash__(self): 

114 return hash(self._data) 

115 

116 def _get_by_int_impl(self, key): 

117 return self._data[key] 

118 

119 def _get_by_key_impl(self, key): 

120 if int in key.__class__.__mro__: 

121 return self._data[key] 

122 

123 if self._key_style == KEY_INTEGER_ONLY: 

124 self._parent._raise_for_nonint(key) 

125 

126 # the following is all LegacyRow support. none of this 

127 # should be called if not LegacyRow 

128 # assert isinstance(self, LegacyRow) 

129 

130 try: 

131 rec = self._keymap[key] 

132 except KeyError as ke: 

133 rec = self._parent._key_fallback(key, ke) 

134 except TypeError: 

135 if isinstance(key, slice): 

136 return tuple(self._data[key]) 

137 else: 

138 raise 

139 

140 mdindex = rec[MD_INDEX] 

141 if mdindex is None: 

142 self._parent._raise_for_ambiguous_column_name(rec) 

143 

144 elif self._key_style == KEY_OBJECTS_BUT_WARN and mdindex != key: 

145 self._parent._warn_for_nonint(key) 

146 

147 return self._data[mdindex] 

148 

149 # The original 1.4 plan was that Row would not allow row["str"] 

150 # access, however as the C extensions were inadvertently allowing 

151 # this coupled with the fact that orm Session sets future=True, 

152 # this allows a softer upgrade path. see #6218 

153 __getitem__ = _get_by_key_impl 

154 

155 def _get_by_key_impl_mapping(self, key): 

156 try: 

157 rec = self._keymap[key] 

158 except KeyError as ke: 

159 rec = self._parent._key_fallback(key, ke) 

160 

161 mdindex = rec[MD_INDEX] 

162 if mdindex is None: 

163 self._parent._raise_for_ambiguous_column_name(rec) 

164 elif ( 

165 self._key_style == KEY_OBJECTS_ONLY 

166 and int in key.__class__.__mro__ 

167 ): 

168 raise KeyError(key) 

169 

170 return self._data[mdindex] 

171 

172 def __getattr__(self, name): 

173 try: 

174 return self._get_by_key_impl_mapping(name) 

175 except KeyError as e: 

176 util.raise_(AttributeError(e.args[0]), replace_context=e) 

177 

178 

179class Row(BaseRow, collections_abc.Sequence): 

180 """Represent a single result row. 

181 

182 The :class:`.Row` object represents a row of a database result. It is 

183 typically associated in the 1.x series of SQLAlchemy with the 

184 :class:`_engine.CursorResult` object, however is also used by the ORM for 

185 tuple-like results as of SQLAlchemy 1.4. 

186 

187 The :class:`.Row` object seeks to act as much like a Python named 

188 tuple as possible. For mapping (i.e. dictionary) behavior on a row, 

189 such as testing for containment of keys, refer to the :attr:`.Row._mapping` 

190 attribute. 

191 

192 .. seealso:: 

193 

194 :ref:`tutorial_selecting_data` - includes examples of selecting 

195 rows from SELECT statements. 

196 

197 :class:`.LegacyRow` - Compatibility interface introduced in SQLAlchemy 

198 1.4. 

199 

200 .. versionchanged:: 1.4 

201 

202 Renamed ``RowProxy`` to :class:`.Row`. :class:`.Row` is no longer a 

203 "proxy" object in that it contains the final form of data within it, 

204 and now acts mostly like a named tuple. Mapping-like functionality is 

205 moved to the :attr:`.Row._mapping` attribute, but will remain available 

206 in SQLAlchemy 1.x series via the :class:`.LegacyRow` class that is used 

207 by :class:`_engine.LegacyCursorResult`. 

208 See :ref:`change_4710_core` for background 

209 on this change. 

210 

211 """ 

212 

213 __slots__ = () 

214 

215 # in 2.0, this should be KEY_INTEGER_ONLY 

216 _default_key_style = KEY_OBJECTS_BUT_WARN 

217 

218 def __setattr__(self, name, value): 

219 raise AttributeError("can't set attribute") 

220 

221 def __delattr__(self, name): 

222 raise AttributeError("can't delete attribute") 

223 

224 @property 

225 def _mapping(self): 

226 """Return a :class:`.RowMapping` for this :class:`.Row`. 

227 

228 This object provides a consistent Python mapping (i.e. dictionary) 

229 interface for the data contained within the row. The :class:`.Row` 

230 by itself behaves like a named tuple, however in the 1.4 series of 

231 SQLAlchemy, the :class:`.LegacyRow` class is still used by Core which 

232 continues to have mapping-like behaviors against the row object 

233 itself. 

234 

235 .. seealso:: 

236 

237 :attr:`.Row._fields` 

238 

239 .. versionadded:: 1.4 

240 

241 """ 

242 return RowMapping( 

243 self._parent, 

244 None, 

245 self._keymap, 

246 RowMapping._default_key_style, 

247 self._data, 

248 ) 

249 

250 def _special_name_accessor(name): 

251 """Handle ambiguous names such as "count" and "index" """ 

252 

253 @property 

254 def go(self): 

255 if self._parent._has_key(name): 

256 return self.__getattr__(name) 

257 else: 

258 

259 def meth(*arg, **kw): 

260 return getattr(collections_abc.Sequence, name)( 

261 self, *arg, **kw 

262 ) 

263 

264 return meth 

265 

266 return go 

267 

268 count = _special_name_accessor("count") 

269 index = _special_name_accessor("index") 

270 

271 def __contains__(self, key): 

272 return key in self._data 

273 

274 def __getstate__(self): 

275 return { 

276 "_parent": self._parent, 

277 "_data": self._data, 

278 "_key_style": self._key_style, 

279 } 

280 

281 def __setstate__(self, state): 

282 parent = state["_parent"] 

283 object.__setattr__(self, "_parent", parent) 

284 object.__setattr__(self, "_data", state["_data"]) 

285 object.__setattr__(self, "_keymap", parent._keymap) 

286 object.__setattr__(self, "_key_style", state["_key_style"]) 

287 

288 def _op(self, other, op): 

289 return ( 

290 op(tuple(self), tuple(other)) 

291 if isinstance(other, Row) 

292 else op(tuple(self), other) 

293 ) 

294 

295 __hash__ = BaseRow.__hash__ 

296 

297 def __lt__(self, other): 

298 return self._op(other, operator.lt) 

299 

300 def __le__(self, other): 

301 return self._op(other, operator.le) 

302 

303 def __ge__(self, other): 

304 return self._op(other, operator.ge) 

305 

306 def __gt__(self, other): 

307 return self._op(other, operator.gt) 

308 

309 def __eq__(self, other): 

310 return self._op(other, operator.eq) 

311 

312 def __ne__(self, other): 

313 return self._op(other, operator.ne) 

314 

315 def __repr__(self): 

316 return repr(sql_util._repr_row(self)) 

317 

318 @util.deprecated_20( 

319 ":meth:`.Row.keys`", 

320 alternative="Use the namedtuple standard accessor " 

321 ":attr:`.Row._fields`, or for full mapping behavior use " 

322 "row._mapping.keys() ", 

323 ) 

324 def keys(self): 

325 """Return the list of keys as strings represented by this 

326 :class:`.Row`. 

327 

328 The keys can represent the labels of the columns returned by a core 

329 statement or the names of the orm classes returned by an orm 

330 execution. 

331 

332 This method is analogous to the Python dictionary ``.keys()`` method, 

333 except that it returns a list, not an iterator. 

334 

335 .. seealso:: 

336 

337 :attr:`.Row._fields` 

338 

339 :attr:`.Row._mapping` 

340 

341 """ 

342 return self._parent.keys 

343 

344 @property 

345 def _fields(self): 

346 """Return a tuple of string keys as represented by this 

347 :class:`.Row`. 

348 

349 The keys can represent the labels of the columns returned by a core 

350 statement or the names of the orm classes returned by an orm 

351 execution. 

352 

353 This attribute is analogous to the Python named tuple ``._fields`` 

354 attribute. 

355 

356 .. versionadded:: 1.4 

357 

358 .. seealso:: 

359 

360 :attr:`.Row._mapping` 

361 

362 """ 

363 return tuple([k for k in self._parent.keys if k is not None]) 

364 

365 def _asdict(self): 

366 """Return a new dict which maps field names to their corresponding 

367 values. 

368 

369 This method is analogous to the Python named tuple ``._asdict()`` 

370 method, and works by applying the ``dict()`` constructor to the 

371 :attr:`.Row._mapping` attribute. 

372 

373 .. versionadded:: 1.4 

374 

375 .. seealso:: 

376 

377 :attr:`.Row._mapping` 

378 

379 """ 

380 return dict(self._mapping) 

381 

382 def _replace(self): 

383 raise NotImplementedError() 

384 

385 @property 

386 def _field_defaults(self): 

387 raise NotImplementedError() 

388 

389 

390class LegacyRow(Row): 

391 """A subclass of :class:`.Row` that delivers 1.x SQLAlchemy behaviors 

392 for Core. 

393 

394 The :class:`.LegacyRow` class is where most of the Python mapping 

395 (i.e. dictionary-like) 

396 behaviors are implemented for the row object. The mapping behavior 

397 of :class:`.Row` going forward is accessible via the :class:`.Row._mapping` 

398 attribute. 

399 

400 .. versionadded:: 1.4 - added :class:`.LegacyRow` which encapsulates most 

401 of the deprecated behaviors of :class:`.Row`. 

402 

403 """ 

404 

405 __slots__ = () 

406 

407 if util.SQLALCHEMY_WARN_20: 

408 _default_key_style = KEY_OBJECTS_BUT_WARN 

409 else: 

410 _default_key_style = KEY_OBJECTS_NO_WARN 

411 

412 def __contains__(self, key): 

413 return self._parent._contains(key, self) 

414 

415 # prior to #6218, LegacyRow would redirect the behavior of __getitem__ 

416 # for the non C version of BaseRow. This is now set up by Python BaseRow 

417 # in all cases 

418 # if not _baserow_usecext: 

419 # __getitem__ = BaseRow._get_by_key_impl 

420 

421 @util.deprecated( 

422 "1.4", 

423 "The :meth:`.LegacyRow.has_key` method is deprecated and will be " 

424 "removed in a future release. To test for key membership, use " 

425 "the :attr:`Row._mapping` attribute, i.e. 'key in row._mapping`.", 

426 ) 

427 def has_key(self, key): 

428 """Return True if this :class:`.LegacyRow` contains the given key. 

429 

430 Through the SQLAlchemy 1.x series, the ``__contains__()`` method of 

431 :class:`.Row` (or :class:`.LegacyRow` as of SQLAlchemy 1.4) also links 

432 to :meth:`.Row.has_key`, in that an expression such as :: 

433 

434 "some_col" in row 

435 

436 Will return True if the row contains a column named ``"some_col"``, 

437 in the way that a Python mapping works. 

438 

439 However, it is planned that the 2.0 series of SQLAlchemy will reverse 

440 this behavior so that ``__contains__()`` will refer to a value being 

441 present in the row, in the way that a Python tuple works. 

442 

443 .. seealso:: 

444 

445 :ref:`change_4710_core` 

446 

447 """ 

448 

449 return self._parent._has_key(key) 

450 

451 @util.deprecated( 

452 "1.4", 

453 "The :meth:`.LegacyRow.items` method is deprecated and will be " 

454 "removed in a future release. Use the :attr:`Row._mapping` " 

455 "attribute, i.e., 'row._mapping.items()'.", 

456 ) 

457 def items(self): 

458 """Return a list of tuples, each tuple containing a key/value pair. 

459 

460 This method is analogous to the Python dictionary ``.items()`` method, 

461 except that it returns a list, not an iterator. 

462 

463 """ 

464 

465 return [(key, self[key]) for key in self.keys()] 

466 

467 @util.deprecated( 

468 "1.4", 

469 "The :meth:`.LegacyRow.iterkeys` method is deprecated and will be " 

470 "removed in a future release. Use the :attr:`Row._mapping` " 

471 "attribute, i.e., 'row._mapping.keys()'.", 

472 ) 

473 def iterkeys(self): 

474 """Return a an iterator against the :meth:`.Row.keys` method. 

475 

476 This method is analogous to the Python-2-only dictionary 

477 ``.iterkeys()`` method. 

478 

479 """ 

480 return iter(self._parent.keys) 

481 

482 @util.deprecated( 

483 "1.4", 

484 "The :meth:`.LegacyRow.itervalues` method is deprecated and will be " 

485 "removed in a future release. Use the :attr:`Row._mapping` " 

486 "attribute, i.e., 'row._mapping.values()'.", 

487 ) 

488 def itervalues(self): 

489 """Return a an iterator against the :meth:`.Row.values` method. 

490 

491 This method is analogous to the Python-2-only dictionary 

492 ``.itervalues()`` method. 

493 

494 """ 

495 return iter(self) 

496 

497 @util.deprecated( 

498 "1.4", 

499 "The :meth:`.LegacyRow.values` method is deprecated and will be " 

500 "removed in a future release. Use the :attr:`Row._mapping` " 

501 "attribute, i.e., 'row._mapping.values()'.", 

502 ) 

503 def values(self): 

504 """Return the values represented by this :class:`.Row` as a list. 

505 

506 This method is analogous to the Python dictionary ``.values()`` method, 

507 except that it returns a list, not an iterator. 

508 

509 """ 

510 

511 return self._values_impl() 

512 

513 

514BaseRowProxy = BaseRow 

515RowProxy = Row 

516 

517 

518class ROMappingView( 

519 collections_abc.KeysView, 

520 collections_abc.ValuesView, 

521 collections_abc.ItemsView, 

522): 

523 __slots__ = ( 

524 "_mapping", 

525 "_items", 

526 ) 

527 

528 def __init__(self, mapping, items): 

529 self._mapping = mapping 

530 self._items = items 

531 

532 def __len__(self): 

533 return len(self._items) 

534 

535 def __repr__(self): 

536 return "{0.__class__.__name__}({0._mapping!r})".format(self) 

537 

538 def __iter__(self): 

539 return iter(self._items) 

540 

541 def __contains__(self, item): 

542 return item in self._items 

543 

544 def __eq__(self, other): 

545 return list(other) == list(self) 

546 

547 def __ne__(self, other): 

548 return list(other) != list(self) 

549 

550 

551class RowMapping(BaseRow, collections_abc.Mapping): 

552 """A ``Mapping`` that maps column names and objects to :class:`.Row` 

553 values. 

554 

555 The :class:`.RowMapping` is available from a :class:`.Row` via the 

556 :attr:`.Row._mapping` attribute, as well as from the iterable interface 

557 provided by the :class:`.MappingResult` object returned by the 

558 :meth:`_engine.Result.mappings` method. 

559 

560 :class:`.RowMapping` supplies Python mapping (i.e. dictionary) access to 

561 the contents of the row. This includes support for testing of 

562 containment of specific keys (string column names or objects), as well 

563 as iteration of keys, values, and items:: 

564 

565 for row in result: 

566 if 'a' in row._mapping: 

567 print("Column 'a': %s" % row._mapping['a']) 

568 

569 print("Column b: %s" % row._mapping[table.c.b]) 

570 

571 

572 .. versionadded:: 1.4 The :class:`.RowMapping` object replaces the 

573 mapping-like access previously provided by a database result row, 

574 which now seeks to behave mostly like a named tuple. 

575 

576 """ 

577 

578 __slots__ = () 

579 

580 _default_key_style = KEY_OBJECTS_ONLY 

581 

582 if not _baserow_usecext: 

583 

584 __getitem__ = BaseRow._get_by_key_impl_mapping 

585 

586 def _values_impl(self): 

587 return list(self._data) 

588 

589 def __iter__(self): 

590 return (k for k in self._parent.keys if k is not None) 

591 

592 def __len__(self): 

593 return len(self._data) 

594 

595 def __contains__(self, key): 

596 return self._parent._has_key(key) 

597 

598 def __repr__(self): 

599 return repr(dict(self)) 

600 

601 def items(self): 

602 """Return a view of key/value tuples for the elements in the 

603 underlying :class:`.Row`. 

604 

605 """ 

606 return ROMappingView(self, [(key, self[key]) for key in self.keys()]) 

607 

608 def keys(self): 

609 """Return a view of 'keys' for string column names represented 

610 by the underlying :class:`.Row`. 

611 

612 """ 

613 

614 return self._parent.keys 

615 

616 def values(self): 

617 """Return a view of values for the values represented in the 

618 underlying :class:`.Row`. 

619 

620 """ 

621 return ROMappingView(self, self._values_impl())