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

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

208 statements  

1# engine/row.py 

2# Copyright (C) 2005-2024 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 if isinstance(key, slice): 

134 return tuple(self._data[key]) 

135 else: 

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

137 except TypeError: 

138 if isinstance(key, slice): 

139 return tuple(self._data[key]) 

140 else: 

141 raise 

142 

143 mdindex = rec[MD_INDEX] 

144 if mdindex is None: 

145 self._parent._raise_for_ambiguous_column_name(rec) 

146 

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

148 self._parent._warn_for_nonint(key) 

149 

150 return self._data[mdindex] 

151 

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

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

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

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

156 __getitem__ = _get_by_key_impl 

157 

158 def _get_by_key_impl_mapping(self, key): 

159 try: 

160 rec = self._keymap[key] 

161 except KeyError as ke: 

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

163 

164 mdindex = rec[MD_INDEX] 

165 if mdindex is None: 

166 self._parent._raise_for_ambiguous_column_name(rec) 

167 elif ( 

168 self._key_style == KEY_OBJECTS_ONLY 

169 and int in key.__class__.__mro__ 

170 ): 

171 raise KeyError(key) 

172 

173 return self._data[mdindex] 

174 

175 def __getattr__(self, name): 

176 try: 

177 return self._get_by_key_impl_mapping(name) 

178 except KeyError as e: 

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

180 

181 

182class Row(BaseRow, collections_abc.Sequence): 

183 """Represent a single result row. 

184 

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

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

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

188 tuple-like results as of SQLAlchemy 1.4. 

189 

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

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

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

193 attribute. 

194 

195 .. seealso:: 

196 

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

198 rows from SELECT statements. 

199 

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

201 1.4. 

202 

203 .. versionchanged:: 1.4 

204 

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

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

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

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

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

210 by :class:`_engine.LegacyCursorResult`. 

211 See :ref:`change_4710_core` for background 

212 on this change. 

213 

214 """ 

215 

216 __slots__ = () 

217 

218 # in 2.0, this should be KEY_INTEGER_ONLY 

219 _default_key_style = KEY_OBJECTS_BUT_WARN 

220 

221 def __setattr__(self, name, value): 

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

223 

224 def __delattr__(self, name): 

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

226 

227 @property 

228 def _mapping(self): 

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

230 

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

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

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

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

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

236 itself. 

237 

238 .. seealso:: 

239 

240 :attr:`.Row._fields` 

241 

242 .. versionadded:: 1.4 

243 

244 """ 

245 return RowMapping( 

246 self._parent, 

247 None, 

248 self._keymap, 

249 RowMapping._default_key_style, 

250 self._data, 

251 ) 

252 

253 def _special_name_accessor(name): 

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

255 

256 @property 

257 def go(self): 

258 if self._parent._has_key(name): 

259 return self.__getattr__(name) 

260 else: 

261 

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

263 return getattr(collections_abc.Sequence, name)( 

264 self, *arg, **kw 

265 ) 

266 

267 return meth 

268 

269 return go 

270 

271 count = _special_name_accessor("count") 

272 index = _special_name_accessor("index") 

273 

274 def __contains__(self, key): 

275 return key in self._data 

276 

277 def __getstate__(self): 

278 return { 

279 "_parent": self._parent, 

280 "_data": self._data, 

281 "_key_style": self._key_style, 

282 } 

283 

284 def __setstate__(self, state): 

285 parent = state["_parent"] 

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

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

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

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

290 

291 def _op(self, other, op): 

292 return ( 

293 op(tuple(self), tuple(other)) 

294 if isinstance(other, Row) 

295 else op(tuple(self), other) 

296 ) 

297 

298 __hash__ = BaseRow.__hash__ 

299 

300 def __lt__(self, other): 

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

302 

303 def __le__(self, other): 

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

305 

306 def __ge__(self, other): 

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

308 

309 def __gt__(self, other): 

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

311 

312 def __eq__(self, other): 

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

314 

315 def __ne__(self, other): 

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

317 

318 def __repr__(self): 

319 return repr(sql_util._repr_row(self)) 

320 

321 @util.deprecated_20( 

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

323 alternative="Use the namedtuple standard accessor " 

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

325 "row._mapping.keys() ", 

326 ) 

327 def keys(self): 

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

329 :class:`.Row`. 

330 

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

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

333 execution. 

334 

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

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

337 

338 .. seealso:: 

339 

340 :attr:`.Row._fields` 

341 

342 :attr:`.Row._mapping` 

343 

344 """ 

345 return self._parent.keys 

346 

347 @property 

348 def _fields(self): 

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

350 :class:`.Row`. 

351 

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

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

354 execution. 

355 

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

357 attribute. 

358 

359 .. versionadded:: 1.4 

360 

361 .. seealso:: 

362 

363 :attr:`.Row._mapping` 

364 

365 """ 

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

367 

368 def _asdict(self): 

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

370 values. 

371 

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

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

374 :attr:`.Row._mapping` attribute. 

375 

376 .. versionadded:: 1.4 

377 

378 .. seealso:: 

379 

380 :attr:`.Row._mapping` 

381 

382 """ 

383 return dict(self._mapping) 

384 

385 def _replace(self): 

386 raise NotImplementedError() 

387 

388 @property 

389 def _field_defaults(self): 

390 raise NotImplementedError() 

391 

392 

393class LegacyRow(Row): 

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

395 for Core. 

396 

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

398 (i.e. dictionary-like) 

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

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

401 attribute. 

402 

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

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

405 

406 """ 

407 

408 __slots__ = () 

409 

410 if util.SQLALCHEMY_WARN_20: 

411 _default_key_style = KEY_OBJECTS_BUT_WARN 

412 else: 

413 _default_key_style = KEY_OBJECTS_NO_WARN 

414 

415 def __contains__(self, key): 

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

417 

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

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

420 # in all cases 

421 # if not _baserow_usecext: 

422 # __getitem__ = BaseRow._get_by_key_impl 

423 

424 @util.deprecated( 

425 "1.4", 

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

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

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

429 ) 

430 def has_key(self, key): 

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

432 

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

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

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

436 

437 "some_col" in row 

438 

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

440 in the way that a Python mapping works. 

441 

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

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

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

445 

446 .. seealso:: 

447 

448 :ref:`change_4710_core` 

449 

450 """ 

451 

452 return self._parent._has_key(key) 

453 

454 @util.deprecated( 

455 "1.4", 

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

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

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

459 ) 

460 def items(self): 

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

462 

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

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

465 

466 """ 

467 

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

469 

470 @util.deprecated( 

471 "1.4", 

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

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

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

475 ) 

476 def iterkeys(self): 

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

478 

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

480 ``.iterkeys()`` method. 

481 

482 """ 

483 return iter(self._parent.keys) 

484 

485 @util.deprecated( 

486 "1.4", 

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

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

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

490 ) 

491 def itervalues(self): 

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

493 

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

495 ``.itervalues()`` method. 

496 

497 """ 

498 return iter(self) 

499 

500 @util.deprecated( 

501 "1.4", 

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

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

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

505 ) 

506 def values(self): 

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

508 

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

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

511 

512 """ 

513 

514 return self._values_impl() 

515 

516 

517BaseRowProxy = BaseRow 

518RowProxy = Row 

519 

520 

521class ROMappingView( 

522 collections_abc.KeysView, 

523 collections_abc.ValuesView, 

524 collections_abc.ItemsView, 

525): 

526 __slots__ = ( 

527 "_mapping", 

528 "_items", 

529 ) 

530 

531 def __init__(self, mapping, items): 

532 self._mapping = mapping 

533 self._items = items 

534 

535 def __len__(self): 

536 return len(self._items) 

537 

538 def __repr__(self): 

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

540 

541 def __iter__(self): 

542 return iter(self._items) 

543 

544 def __contains__(self, item): 

545 return item in self._items 

546 

547 def __eq__(self, other): 

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

549 

550 def __ne__(self, other): 

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

552 

553 

554class RowMapping(BaseRow, collections_abc.Mapping): 

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

556 values. 

557 

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

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

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

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

562 

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

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

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

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

567 

568 for row in result: 

569 if 'a' in row._mapping: 

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

571 

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

573 

574 

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

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

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

578 

579 """ 

580 

581 __slots__ = () 

582 

583 _default_key_style = KEY_OBJECTS_ONLY 

584 

585 if not _baserow_usecext: 

586 

587 __getitem__ = BaseRow._get_by_key_impl_mapping 

588 

589 def _values_impl(self): 

590 return list(self._data) 

591 

592 def __iter__(self): 

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

594 

595 def __len__(self): 

596 return len(self._data) 

597 

598 def __contains__(self, key): 

599 return self._parent._has_key(key) 

600 

601 def __repr__(self): 

602 return repr(dict(self)) 

603 

604 def items(self): 

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

606 underlying :class:`.Row`. 

607 

608 """ 

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

610 

611 def keys(self): 

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

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

614 

615 """ 

616 

617 return self._parent.keys 

618 

619 def values(self): 

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

621 underlying :class:`.Row`. 

622 

623 """ 

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