common cases are a bit faster overall
Python version
| | python opt | python main | py opt / main |
| -------------------- | ---------- | ----------- | ------------- |
| base_row_new | 0.84959 | 0.86417 | 0.
9831283197 |
| row_new | 0.85698 | 0.85543 | 1.
001811954 |
| base_row_new_proc | 2.77489 | 2.81795 | 0.
9847193882 |
| row_new_proc | 2.77881 | 2.86206 | 0.
9709125595 |
| brow_new_proc_none | 1.6311 | 1.65773 | 0.
9839358641 |
| row_new_proc_none | 1.65048 | 1.69896 | 0.
9714648962 |
| row_dumps | 0.15279 | 0.14519 | 1.
052345203 |
| row_loads | 0.91471 | 0.91358 | 1.
001236892 |
| row_values_impl | 0.1818 | 0.17773 | 1.
022899904 |
| row_iter | 0.44042 | 0.4554 | 0.
967105841 |
| row_len | 0.13858 | 0.13079 | 1.
059561129 |
| row_hash | 0.28614 | 0.29172 | 0.
9808720691 |
| getitem | 0.23159 | 0.23138 | 1.
000907598 |
| getitem_slice | 0.38393 | 0.38765 | 0.
9904037147 |
| get_by_key | 0.33692 | 0.33913 | 0.
993483325 |
| get_by_key2 | 0.35105 | 0.33915 | 1.
035087719 |
| getattr | 0.46809 | 0.46911 | 0.
9978256699 |
| get_by_key_recreate | 1.08137 | 1.18224 | 0.
9146789146 |
| get_by_key_recreate2 | 0.98543 | 0.99272 | 0.
9926565396 |
| getattr_recreate | 0.74 | 0.7407 | 0.
999054948 |
| contains | 0.49312 | 0.74477 | 0.
6621104502 |
Cython version
| | cython opt | cython main | cy opt / main |
| -------------------- | ---------- | ----------- | ------------- |
| base_row_new | 0.12933 | 0.1347 | 0.
9601336303 |
| row_new | 0.15755 | 0.16489 | 0.
9554854752 |
| base_row_new_proc | 0.87398 | 1.08483 | 0.
8056377497 |
| row_new_proc | 0.88592 | 1.13255 | 0.
7822347799 |
| brow_new_proc_none | 0.2664 | 0.41703 | 0.
6388029638 |
| row_new_proc_none | 0.27526 | 0.42483 | 0.
6479297601 |
| row_dumps | 0.37526 | 0.20093 | 1.
867615588 |
| row_loads | 0.53037 | 0.3607 | 1.
470390907 |
| row_values_impl | 0.36886 | 0.22218 | 1.
660185435 |
| row_iter | 0.25249 | 0.25553 | 0.
9881031581 |
| row_len | 0.05194 | 0.05412 | 0.
9597191426 |
| row_hash | 0.18864 | 0.18726 | 1.
007369433 |
| getitem | 0.13876 | 0.13934 | 0.
9958375197 |
| getitem_slice | 0.23181 | 0.2318 | 1.
000043141 |
| get_by_key | 0.17031 | 0.17857 | 0.
9537436299 |
| get_by_key2 | 0.58172 | 0.31651 | 1.
837919813 |
| getattr | 0.21275 | 1.16669 | 0.
1823534958 |
| get_by_key_recreate | 0.80161 | 0.8836 | 0.
9072091444 |
| get_by_key_recreate2 | 1.14649 | 0.98709 | 1.
161484768 |
| getattr_recreate | 0.55927 | 1.09921 | 0.
5087926784 |
| contains | 0.35338 | 0.5494 | 0.
6432107754 |
Closes: #12919
Pull-request: https://github.com/sqlalchemy/sqlalchemy/pull/12919
Pull-request-sha:
b13204b7416083ccff09a45291b11fd606ff0fbd
Change-Id: I5ff2b951f9840e99abdad3ae286ecb8becc70e4b
#
# This module is part of SQLAlchemy and is released under
# the MIT License: https://www.opensource.org/licenses/mit-license.php
-# mypy: disable-error-code="misc"
+# mypy: disable-error-code="misc,no-redef,valid-type,no-untyped-call"
+# mypy: disable-error-code="index,no-any-return,arg-type,assignment"
from __future__ import annotations
from typing import Any
from typing import Dict
from typing import Iterator
from typing import List
+from typing import NoReturn
from typing import Optional
from typing import Sequence
from typing import Tuple
data: Sequence[Any],
) -> None:
"""Row objects are constructed by CursorResult objects."""
-
- data_tuple: Tuple[Any, ...] = (
- _apply_processors(processors, data)
- if processors is not None
- else tuple(data)
+ self._set_attrs(
+ parent,
+ key_to_index,
+ (
+ _apply_processors(processors, data)
+ if processors is not None
+ else data if isinstance(data, tuple) else tuple(data)
+ ),
)
- self._set_attrs(parent, key_to_index, data_tuple)
@cython.cfunc
@cython.inline
return self._get_by_key_impl(key, False)
@cython.cfunc
+ @cython.inline
def _get_by_key_impl(self, key: _KeyType, attr_err: cython.bint) -> object:
- index: Optional[int] = self._key_to_index.get(key)
+ # NOTE: don't type index since there is no advantage in making cython
+ # do a type check
+ index = self._key_to_index.get(key)
if index is not None:
return self._data[index]
self._parent._key_not_found(key, attr_err)
+ if cython.compiled:
+
+ @cython.annotation_typing(False)
+ def __getattribute__(self, name: str) -> Any:
+ # this optimizes getattr access on cython, that's otherwise
+ # quite slow compared with python. The assumption is that
+ # most columns will not start with _. If they do they will
+ # fallback on __getattr__ in any case.
+ if name != "" and name[0] != "_":
+ # inline of _get_by_key_impl. Attribute on the class
+ # take precedence over column names.
+ index = self._key_to_index.get(name)
+ if index is not None and not hasattr(type(self), name):
+ return self._data[index]
+
+ return object.__getattribute__(self, name)
+
@cython.annotation_typing(False)
def __getattr__(self, name: str) -> Any:
return self._get_by_key_impl(name, True)
+ def __setattr__(self, name: str, value: Any) -> NoReturn:
+ raise AttributeError("can't set attribute")
+
+ def __delattr__(self, name: str) -> NoReturn:
+ raise AttributeError("can't delete attribute")
+
def _to_tuple_instance(self) -> Tuple[Any, ...]:
return self._data
+ def __contains__(self, key: Any) -> cython.bint:
+ return key in self._data
+
+
+if cython.compiled:
-@cython.inline
-@cython.cfunc
-def _apply_processors(
- proc: _ProcessorsType, data: Sequence[Any]
-) -> Tuple[Any, ...]:
- res: List[Any] = list(data)
- proc_size: cython.Py_ssize_t = len(proc)
- # TODO: would be nice to do this only on the fist row
- assert len(res) == proc_size
- for i in range(proc_size):
- p = proc[i]
- if p is not None:
- res[i] = p(res[i])
- return tuple(res)
+ from cython.cimports.cpython import PyTuple_New
+ from cython.cimports.cpython import Py_INCREF
+ from cython.cimports.cpython import PyTuple_SET_ITEM
+
+ @cython.inline
+ @cython.cfunc
+ @cython.wraparound(False)
+ @cython.boundscheck(False)
+ @cython.locals(
+ res=tuple,
+ proc_size=cython.Py_ssize_t,
+ i=cython.Py_ssize_t,
+ p=object,
+ value=object,
+ )
+ def _apply_processors(
+ proc: Sequence[Any], data: Sequence[Any]
+ ) -> Tuple[Any, ...]:
+ proc_size = len(proc)
+ # TODO: would be nice to do this only on the fist row
+ assert len(data) == proc_size
+ res = PyTuple_New(proc_size)
+ for i in range(proc_size):
+ p = proc[i]
+ if p is not None:
+ value = p(data[i])
+ else:
+ value = data[i]
+ Py_INCREF(value)
+ PyTuple_SET_ITEM(res, i, value)
+ return res
+
+else:
+
+ def _apply_processors(
+ proc: _ProcessorsType, data: Sequence[Any]
+ ) -> Tuple[Any, ...]:
+ res: List[Any] = list(data)
+ proc_size = len(proc)
+ # TODO: would be nice to do this only on the fist row
+ assert len(res) == proc_size
+ for i in range(proc_size):
+ p = proc[i]
+ if p is not None:
+ res[i] = p(res[i])
+ return tuple(res)
# This reconstructor is necessary so that pickles with the Cy extension or
from typing import Dict
from typing import Generic
from typing import Iterator
-from typing import List
from typing import Mapping
-from typing import NoReturn
from typing import Optional
from typing import Sequence
from typing import Tuple
__slots__ = ()
- def __setattr__(self, name: str, value: Any) -> NoReturn:
- raise AttributeError("can't set attribute")
-
- def __delattr__(self, name: str) -> NoReturn:
- raise AttributeError("can't delete attribute")
-
@deprecated(
"2.1.0",
"The :meth:`.Row._tuple` method is deprecated, :class:`.Row` "
count = _special_name_accessor("count")
index = _special_name_accessor("index")
- def __contains__(self, key: Any) -> bool:
- return key in self._data
-
def _op(self, other: Any, op: Callable[[Any, Any], bool]) -> bool:
return (
op(self._to_tuple_instance(), other._to_tuple_instance())
else:
__getitem__ = BaseRow._get_by_key_impl_mapping
- def _values_impl(self) -> List[Any]:
- return list(self._data)
-
def __iter__(self) -> Iterator[str]:
return (k for k in self._parent.keys if k is not None)
- def __len__(self) -> int:
- return len(self._data)
-
def __contains__(self, key: object) -> bool:
return self._parent._has_key(key)
float = float # noqa: A001
double = float
void = Any
+NULL = None
# functions
def cast(type_: Type[_T], value: Any, *, typecheck: bool = False) -> _T:
return value # type: ignore[no-any-return]
+
+
+def returns(_: type) -> _NO_OP[_T]:
+ return _no_op
+
+
+def locals(**kwargs: Any) -> _NO_OP[_T]: # noqa: A001
+ return _no_op
+
+
+def wraparound(value: bool) -> _NO_OP[_T]:
+ return _no_op
+
+
+def boundscheck(value: bool) -> _NO_OP[_T]:
+ return _no_op
if HAS_CYTHON and IS_CPYTHON and not DISABLE_EXTENSION:
assert _cy_Extension is not None
assert _cy_build_ext is not None
+ from Cython.Compiler import Options
+
+ Options.docstrings = False
+ Options.clear_to_none = False
cython_directives: Dict[str, Any] = {
"language_level": "3",
+ # "initializedcheck": False,
}
if sys.version_info >= (3, 13):
def __iter__(self):
return iter(self.data)
+ def __len__(self):
+ return len(self.data)
+
self._test_getitem_value_refcounts_new(CustomSeq)
class Row(self.impl):
pass
+ class RowMap(self.impl):
+ __getitem__ = self.impl._get_by_key_impl_mapping
+
self.Row = Row
- self.row_sub = Row(*self.row_args)
+ self.row_map = RowMap(*self.row_args)
+ self.row_long_map = RowMap(*self.row_long_args)
self.row_state = self.row.__getstate__()
self.row_long_state = self.row_long.__getstate__()
@test_case
def get_by_key(self):
+ self.row_map["a"]
+ self.row_map["b"]
+ self.row_long_map["s"]
+ self.row_long_map["a"]
+
+ @test_case
+ def get_by_key2(self):
+ # NOTE: this is not representative of real usage
self.row._get_by_key_impl_mapping("a")
self.row._get_by_key_impl_mapping("b")
self.row_long._get_by_key_impl_mapping("s")
self.row_long.x
self.row_long.y
- @test_case(number=25_000)
+ @test_case(number=15_000)
def get_by_key_recreate(self):
+ self.init_objects()
+ row = self.row_map
+ for _ in range(25):
+ row["a"]
+ l_row = self.row_long_map
+ for _ in range(25):
+ l_row["f"]
+ l_row["o"]
+ l_row["r"]
+ l_row["t"]
+ l_row["y"]
+ l_row["t"]
+ l_row["w"]
+ l_row["o"]
+
+ @test_case(number=15_000)
+ def get_by_key_recreate2(self):
+ # NOTE: this is not representative of real usage
self.init_objects()
row = self.row
for _ in range(25):
l_row.t
l_row.w
l_row.o
+
+ @test_case
+ def contains(self):
+ 1 in self.row
+ -1 in self.row
+ 1 in self.row_long
+ -1 in self.row_long
test.aaa_profiling.test_resultset.ResultSetTest.test_fetch_by_key_mappings x86_64_linux_cpython_3.13_postgresql_psycopg2_dbapiunicode_cextensions 1632
test.aaa_profiling.test_resultset.ResultSetTest.test_fetch_by_key_mappings x86_64_linux_cpython_3.13_postgresql_psycopg2_dbapiunicode_nocextensions 35643
test.aaa_profiling.test_resultset.ResultSetTest.test_fetch_by_key_mappings x86_64_linux_cpython_3.13_sqlite_pysqlite_dbapiunicode_cextensions 1598
-test.aaa_profiling.test_resultset.ResultSetTest.test_fetch_by_key_mappings x86_64_linux_cpython_3.13_sqlite_pysqlite_dbapiunicode_nocextensions 35609
+test.aaa_profiling.test_resultset.ResultSetTest.test_fetch_by_key_mappings x86_64_linux_cpython_3.13_sqlite_pysqlite_dbapiunicode_nocextensions 37599
test.aaa_profiling.test_resultset.ResultSetTest.test_fetch_by_key_mappings x86_64_linux_cpython_3.14_mariadb_mysqldb_dbapiunicode_cextensions 1648
test.aaa_profiling.test_resultset.ResultSetTest.test_fetch_by_key_mappings x86_64_linux_cpython_3.14_mariadb_mysqldb_dbapiunicode_nocextensions 35650
test.aaa_profiling.test_resultset.ResultSetTest.test_fetch_by_key_mappings x86_64_linux_cpython_3.14_mssql_pyodbc_dbapiunicode_cextensions 1652
test.aaa_profiling.test_resultset.ResultSetTest.test_fetch_by_key_mappings x86_64_linux_cpython_3.14_postgresql_psycopg2_dbapiunicode_cextensions 1632
test.aaa_profiling.test_resultset.ResultSetTest.test_fetch_by_key_mappings x86_64_linux_cpython_3.14_postgresql_psycopg2_dbapiunicode_nocextensions 35634
test.aaa_profiling.test_resultset.ResultSetTest.test_fetch_by_key_mappings x86_64_linux_cpython_3.14_sqlite_pysqlite_dbapiunicode_cextensions 1598
-test.aaa_profiling.test_resultset.ResultSetTest.test_fetch_by_key_mappings x86_64_linux_cpython_3.14_sqlite_pysqlite_dbapiunicode_nocextensions 35609
+test.aaa_profiling.test_resultset.ResultSetTest.test_fetch_by_key_mappings x86_64_linux_cpython_3.14_sqlite_pysqlite_dbapiunicode_nocextensions 37598
# TEST: test.aaa_profiling.test_resultset.ResultSetTest.test_one_or_none[False-0]
test.aaa_profiling.test_resultset.ResultSetTest.test_one_or_none[False-1] x86_64_linux_cpython_3.13_postgresql_psycopg2_dbapiunicode_cextensions 14
test.aaa_profiling.test_resultset.ResultSetTest.test_one_or_none[False-1] x86_64_linux_cpython_3.13_postgresql_psycopg2_dbapiunicode_nocextensions 16
test.aaa_profiling.test_resultset.ResultSetTest.test_one_or_none[False-1] x86_64_linux_cpython_3.13_sqlite_pysqlite_dbapiunicode_cextensions 14
-test.aaa_profiling.test_resultset.ResultSetTest.test_one_or_none[False-1] x86_64_linux_cpython_3.13_sqlite_pysqlite_dbapiunicode_nocextensions 16
+test.aaa_profiling.test_resultset.ResultSetTest.test_one_or_none[False-1] x86_64_linux_cpython_3.13_sqlite_pysqlite_dbapiunicode_nocextensions 17
test.aaa_profiling.test_resultset.ResultSetTest.test_one_or_none[False-1] x86_64_linux_cpython_3.14_mariadb_mysqldb_dbapiunicode_cextensions 18
test.aaa_profiling.test_resultset.ResultSetTest.test_one_or_none[False-1] x86_64_linux_cpython_3.14_mariadb_mysqldb_dbapiunicode_nocextensions 20
test.aaa_profiling.test_resultset.ResultSetTest.test_one_or_none[False-1] x86_64_linux_cpython_3.14_mssql_pyodbc_dbapiunicode_cextensions 14
test.aaa_profiling.test_resultset.ResultSetTest.test_one_or_none[False-1] x86_64_linux_cpython_3.14_postgresql_psycopg2_dbapiunicode_cextensions 14
test.aaa_profiling.test_resultset.ResultSetTest.test_one_or_none[False-1] x86_64_linux_cpython_3.14_postgresql_psycopg2_dbapiunicode_nocextensions 16
test.aaa_profiling.test_resultset.ResultSetTest.test_one_or_none[False-1] x86_64_linux_cpython_3.14_sqlite_pysqlite_dbapiunicode_cextensions 14
-test.aaa_profiling.test_resultset.ResultSetTest.test_one_or_none[False-1] x86_64_linux_cpython_3.14_sqlite_pysqlite_dbapiunicode_nocextensions 16
+test.aaa_profiling.test_resultset.ResultSetTest.test_one_or_none[False-1] x86_64_linux_cpython_3.14_sqlite_pysqlite_dbapiunicode_nocextensions 17
# TEST: test.aaa_profiling.test_resultset.ResultSetTest.test_one_or_none[False-2]
test.aaa_profiling.test_resultset.ResultSetTest.test_one_or_none[False-2] x86_64_linux_cpython_3.13_postgresql_psycopg2_dbapiunicode_cextensions 14
test.aaa_profiling.test_resultset.ResultSetTest.test_one_or_none[False-2] x86_64_linux_cpython_3.13_postgresql_psycopg2_dbapiunicode_nocextensions 16
test.aaa_profiling.test_resultset.ResultSetTest.test_one_or_none[False-2] x86_64_linux_cpython_3.13_sqlite_pysqlite_dbapiunicode_cextensions 14
-test.aaa_profiling.test_resultset.ResultSetTest.test_one_or_none[False-2] x86_64_linux_cpython_3.13_sqlite_pysqlite_dbapiunicode_nocextensions 16
+test.aaa_profiling.test_resultset.ResultSetTest.test_one_or_none[False-2] x86_64_linux_cpython_3.13_sqlite_pysqlite_dbapiunicode_nocextensions 17
test.aaa_profiling.test_resultset.ResultSetTest.test_one_or_none[False-2] x86_64_linux_cpython_3.14_mariadb_mysqldb_dbapiunicode_cextensions 18
test.aaa_profiling.test_resultset.ResultSetTest.test_one_or_none[False-2] x86_64_linux_cpython_3.14_mariadb_mysqldb_dbapiunicode_nocextensions 20
test.aaa_profiling.test_resultset.ResultSetTest.test_one_or_none[False-2] x86_64_linux_cpython_3.14_mssql_pyodbc_dbapiunicode_cextensions 14
test.aaa_profiling.test_resultset.ResultSetTest.test_one_or_none[False-2] x86_64_linux_cpython_3.14_postgresql_psycopg2_dbapiunicode_cextensions 14
test.aaa_profiling.test_resultset.ResultSetTest.test_one_or_none[False-2] x86_64_linux_cpython_3.14_postgresql_psycopg2_dbapiunicode_nocextensions 16
test.aaa_profiling.test_resultset.ResultSetTest.test_one_or_none[False-2] x86_64_linux_cpython_3.14_sqlite_pysqlite_dbapiunicode_cextensions 14
-test.aaa_profiling.test_resultset.ResultSetTest.test_one_or_none[False-2] x86_64_linux_cpython_3.14_sqlite_pysqlite_dbapiunicode_nocextensions 16
+test.aaa_profiling.test_resultset.ResultSetTest.test_one_or_none[False-2] x86_64_linux_cpython_3.14_sqlite_pysqlite_dbapiunicode_nocextensions 17
# TEST: test.aaa_profiling.test_resultset.ResultSetTest.test_one_or_none[True-1]
test.aaa_profiling.test_resultset.ResultSetTest.test_raw_string x86_64_linux_cpython_3.14_postgresql_psycopg2_dbapiunicode_cextensions 299
test.aaa_profiling.test_resultset.ResultSetTest.test_raw_string x86_64_linux_cpython_3.14_postgresql_psycopg2_dbapiunicode_nocextensions 4301
test.aaa_profiling.test_resultset.ResultSetTest.test_raw_string x86_64_linux_cpython_3.14_sqlite_pysqlite_dbapiunicode_cextensions 271
-test.aaa_profiling.test_resultset.ResultSetTest.test_raw_string x86_64_linux_cpython_3.14_sqlite_pysqlite_dbapiunicode_nocextensions 4273
+test.aaa_profiling.test_resultset.ResultSetTest.test_raw_string x86_64_linux_cpython_3.14_sqlite_pysqlite_dbapiunicode_nocextensions 5264
# TEST: test.aaa_profiling.test_resultset.ResultSetTest.test_raw_unicode
test.aaa_profiling.test_resultset.ResultSetTest.test_raw_unicode x86_64_linux_cpython_3.14_postgresql_psycopg2_dbapiunicode_cextensions 299
test.aaa_profiling.test_resultset.ResultSetTest.test_raw_unicode x86_64_linux_cpython_3.14_postgresql_psycopg2_dbapiunicode_nocextensions 4301
test.aaa_profiling.test_resultset.ResultSetTest.test_raw_unicode x86_64_linux_cpython_3.14_sqlite_pysqlite_dbapiunicode_cextensions 271
-test.aaa_profiling.test_resultset.ResultSetTest.test_raw_unicode x86_64_linux_cpython_3.14_sqlite_pysqlite_dbapiunicode_nocextensions 4273
+test.aaa_profiling.test_resultset.ResultSetTest.test_raw_unicode x86_64_linux_cpython_3.14_sqlite_pysqlite_dbapiunicode_nocextensions 5264
# TEST: test.aaa_profiling.test_resultset.ResultSetTest.test_string
test.aaa_profiling.test_resultset.ResultSetTest.test_string x86_64_linux_cpython_3.14_postgresql_psycopg2_dbapiunicode_cextensions 623
test.aaa_profiling.test_resultset.ResultSetTest.test_string x86_64_linux_cpython_3.14_postgresql_psycopg2_dbapiunicode_nocextensions 4625
test.aaa_profiling.test_resultset.ResultSetTest.test_string x86_64_linux_cpython_3.14_sqlite_pysqlite_dbapiunicode_cextensions 589
-test.aaa_profiling.test_resultset.ResultSetTest.test_string x86_64_linux_cpython_3.14_sqlite_pysqlite_dbapiunicode_nocextensions 4600
+test.aaa_profiling.test_resultset.ResultSetTest.test_string x86_64_linux_cpython_3.14_sqlite_pysqlite_dbapiunicode_nocextensions 5591
# TEST: test.aaa_profiling.test_resultset.ResultSetTest.test_unicode
test.aaa_profiling.test_resultset.ResultSetTest.test_unicode x86_64_linux_cpython_3.14_postgresql_psycopg2_dbapiunicode_cextensions 623
test.aaa_profiling.test_resultset.ResultSetTest.test_unicode x86_64_linux_cpython_3.14_postgresql_psycopg2_dbapiunicode_nocextensions 4625
test.aaa_profiling.test_resultset.ResultSetTest.test_unicode x86_64_linux_cpython_3.14_sqlite_pysqlite_dbapiunicode_cextensions 589
-test.aaa_profiling.test_resultset.ResultSetTest.test_unicode x86_64_linux_cpython_3.14_sqlite_pysqlite_dbapiunicode_nocextensions 4600
+test.aaa_profiling.test_resultset.ResultSetTest.test_unicode x86_64_linux_cpython_3.14_sqlite_pysqlite_dbapiunicode_nocextensions 5591
eq_(row.key, "kv")
eq_(row.count, "cv")
eq_(row.index, "iv")
+ eq_(row.foo, "f")
eq_(row._mapping["foo"], "f")
eq_(row._mapping["count"], "cv")
eq_(row.count("cv"), 1)
eq_(row.count("x"), 0)
+ def test_row_precedence_normal_names(self):
+ f = ("_fields", "_asdict", "_mapping", "as_tuple")
+ v = ["ff", "ad", "mm", "at"]
+ metadata = SimpleResultMetaData(f)
+
+ class SubRow(Row):
+ # use subclass to ensure there is always a public method
+ @property
+ def as_tuple(self):
+ return tuple(self)
+
+ row = SubRow(metadata, None, metadata._key_to_index, v)
+
+ eq_(row._fields, f)
+ eq_(row._asdict(), dict(zip(f, v)))
+ eq_(row._mapping, dict(zip(f, v)))
+ eq_(row.as_tuple, tuple(v))
+
+ with expect_raises(AttributeError):
+ getattr(row, "") # test cython getattr edge case
+
def test_new_row_no_dict_behaviors(self):
"""This mode is not used currently but will be once we are in 2.0."""
metadata = SimpleResultMetaData(["a", "b", "count"])