diff --git a/docs/changes/newsfragments/4446.improved b/docs/changes/newsfragments/4446.improved index 1f0e6ea0538..b3ab77e3448 100644 --- a/docs/changes/newsfragments/4446.improved +++ b/docs/changes/newsfragments/4446.improved @@ -1,3 +1,3 @@ Improve performance of ``sqlite3`` converters and adapters used to write and read in the database. -Get rid of irrelevant unpacking from ``sqlite3.Row`` to ``list``. +Get rid of ``sqlite3.Row`` and irrelevant unpacking to ``list``. diff --git a/qcodes/dataset/data_set.py b/qcodes/dataset/data_set.py index b42c7adc9ce..f6287a4f022 100644 --- a/qcodes/dataset/data_set.py +++ b/qcodes/dataset/data_set.py @@ -1174,11 +1174,14 @@ def unsubscribe_all(self) -> None: """ Remove all subscribers """ - sql = "select * from sqlite_master where type = 'trigger';" + sql = """ + SELECT name FROM sqlite_master + WHERE type = 'trigger' + """ triggers = atomic_transaction(self.conn, sql).fetchall() with atomic(self.conn) as conn: - for trigger in triggers: - remove_trigger(conn, trigger['name']) + for (trigger,) in triggers: + remove_trigger(conn, trigger) for sub in self.subscribers.values(): sub.schedule_stop() sub.join() diff --git a/qcodes/dataset/experiment_container.py b/qcodes/dataset/experiment_container.py index 563313b6ad7..c8b99cbfd79 100644 --- a/qcodes/dataset/experiment_container.py +++ b/qcodes/dataset/experiment_container.py @@ -169,8 +169,10 @@ def data_set(self, counter: int) -> DataSet: def data_sets(self) -> list[DataSetProtocol]: """Get all the datasets of this experiment""" - runs = get_runs(self.conn, self.exp_id) - return [load_by_id(run['run_id'], conn=self.conn) for run in runs] + return [ + load_by_id(run_id, conn=self.conn) + for run_id in get_runs(self.conn, self.exp_id) + ] def last_data_set(self) -> DataSetProtocol: """Get the last dataset of this experiment""" @@ -216,8 +218,7 @@ def experiments(conn: ConnectionPlus | None = None) -> list[Experiment]: """ conn = conn_from_dbpath_or_conn(conn=conn, path_to_db=None) log.info(f"loading experiments from {conn.path_to_dbfile}") - rows = get_experiments(conn) - return [load_experiment(row['exp_id'], conn) for row in rows] + return [load_experiment(exp_id, conn) for exp_id in get_experiments(conn)] def new_experiment( diff --git a/qcodes/dataset/sqlite/database.py b/qcodes/dataset/sqlite/database.py index e010cf7464f..cd9431cb37a 100644 --- a/qcodes/dataset/sqlite/database.py +++ b/qcodes/dataset/sqlite/database.py @@ -149,9 +149,6 @@ def connect(name: str | Path, debug: bool = False, version: int = -1) -> Connect f"version of QCoDeS supports up to " f"version {latest_supported_version}") - # sqlite3 options - conn.row_factory = sqlite3.Row - # Make sure numpy ints and floats types are inserted properly for numpy_int in numpy_ints: sqlite3.register_adapter(numpy_int, int) diff --git a/qcodes/dataset/sqlite/db_upgrades/upgrade_2_to_3.py b/qcodes/dataset/sqlite/db_upgrades/upgrade_2_to_3.py index f506952f05e..4620294f075 100644 --- a/qcodes/dataset/sqlite/db_upgrades/upgrade_2_to_3.py +++ b/qcodes/dataset/sqlite/db_upgrades/upgrade_2_to_3.py @@ -16,7 +16,7 @@ atomic_transaction, transaction, ) -from qcodes.dataset.sqlite.query_helpers import one +from qcodes.dataset.sqlite.query_helpers import get_description_map, one log = logging.getLogger(__name__) @@ -29,8 +29,8 @@ def _2to3_get_result_tables(conn: ConnectionPlus) -> dict[int, str]: data = cur.fetchall() cur.close() results = {} - for row in data: - results[row['run_id']] = row['result_table_name'] + for run_id, result_table_name in data: + results[run_id] = result_table_name return results @@ -47,9 +47,7 @@ def _2to3_get_layout_ids(conn: ConnectionPlus) -> DefaultDict[int, list[int]]: results: DefaultDict[int, list[int]] = defaultdict(list) - for row in data: - run_id = row['run_id'] - layout_id = row['layout_id'] + for run_id, layout_id in data: results[run_id].append(layout_id) return results @@ -68,9 +66,7 @@ def _2to3_get_indeps(conn: ConnectionPlus) -> DefaultDict[int, list[int]]: cur.close() results: DefaultDict[int, list[int]] = defaultdict(list) - for row in data: - run_id = row['run_id'] - layout_id = row['layout_id'] + for run_id, layout_id in data: results[run_id].append(layout_id) return results @@ -89,9 +85,7 @@ def _2to3_get_deps(conn: ConnectionPlus) -> DefaultDict[int, list[int]]: cur.close() results: DefaultDict[int, list[int]] = defaultdict(list) - for row in data: - run_id = row['run_id'] - layout_id = row['layout_id'] + for run_id, layout_id in data: results[run_id].append(layout_id) return results @@ -112,9 +106,7 @@ def _2to3_get_dependencies(conn: ConnectionPlus) -> DefaultDict[int, list[int]]: if len(data) == 0: return results - for row in data: - dep = row['dependent'] - indep = row['independent'] + for dep, indep in data: results[dep].append(indep) return results @@ -129,11 +121,8 @@ def _2to3_get_layouts(conn: ConnectionPlus) -> dict[int, tuple[str, str, str, st cur.execute(query) results: dict[int, tuple[str, str, str, str]] = {} - for row in cur.fetchall(): - results[row['layout_id']] = (row['parameter'], - row['label'], - row['unit'], - row['inferred_from']) + for layout_id, parameter, label, unit, inferred_from in cur.fetchall(): + results[layout_id] = (parameter, label, unit, inferred_from) return results @@ -159,10 +148,11 @@ def _2to3_get_paramspecs( # get the data type sql = f'PRAGMA TABLE_INFO("{result_table_name}")' c = transaction(conn, sql) + description = get_description_map(c) paramtype = None for row in c.fetchall(): - if row['name'] == name: - paramtype = row['type'] + if row[description["name"]] == name: + paramtype = row[description["type"]] break if paramtype is None: raise TypeError(f"Could not determine type of {name} during the" diff --git a/qcodes/dataset/sqlite/queries.py b/qcodes/dataset/sqlite/queries.py index b8c630a31be..e8dfef3f430 100644 --- a/qcodes/dataset/sqlite/queries.py +++ b/qcodes/dataset/sqlite/queries.py @@ -33,6 +33,7 @@ from qcodes.dataset.sqlite.query_helpers import ( VALUE, VALUES, + get_description_map, insert_column, insert_values, is_column_in_table, @@ -102,6 +103,7 @@ def is_run_id_in_database(conn: ConnectionPlus, *run_ids: int) -> dict[int, bool return {run_id: (run_id in existing_ids) for run_id in run_ids} +@deprecate("Unused private method to be removed in a future version") def _build_data_query( table_name: str, columns: list[str], @@ -272,6 +274,7 @@ def get_rundescriber_from_result_table_name( return rd +@deprecate("Unused and untested method, to be removed in a future version") def get_interdeps_from_result_table_name(conn: ConnectionPlus, result_table_name: str) -> InterDependencies_: rd = get_rundescriber_from_result_table_name(conn, result_table_name) interdeps = rd.interdeps @@ -470,31 +473,15 @@ def get_parameter_tree_values( if start is not None and end is not None and start > end: limit = 0 - # Note: if we use placeholders for the SELECT part, then we get rows - # back that have "?" as all their keys, making further data extraction - # impossible - # - # Also, placeholders seem to be ignored in the WHERE X IS NOT NULL line - columns = [toplevel_param_name] + list(other_param_names) - columns_for_select = ','.join(columns) - - sql_subquery = f""" - (SELECT {columns_for_select} - FROM "{result_table_name}" - WHERE {toplevel_param_name} IS NOT NULL) - """ sql = f""" - SELECT {columns_for_select} - FROM {sql_subquery} - LIMIT {limit} OFFSET {offset} - """ - + SELECT {','.join(columns)} FROM "{result_table_name}" + WHERE {toplevel_param_name} IS NOT NULL + LIMIT ? OFFSET ? + """ cursor = conn.cursor() - cursor.execute(sql, ()) - res = many_many(cursor, *columns) - - return res + cursor.execute(sql, (limit, offset)) + return many_many(cursor, *columns) @deprecate(alternative="get_parameter_data") @@ -643,7 +630,7 @@ def get_runid_from_guid(conn: ConnectionPlus, guid: str) -> int | None: log.critical(errormssg) raise RuntimeError(errormssg) else: - run_id = int(rows[0]['run_id']) + run_id = int(rows[0][0]) return run_id @@ -715,11 +702,7 @@ def _query_guids_from_run_spec( else: cursor.execute(query) - rows = cursor.fetchall() - results = [] - for r in rows: - results.append(r['guid']) - return results + return [guid for guid, in cursor.fetchall()] @deprecate( @@ -785,7 +768,7 @@ def _get_dependents(conn: ConnectionPlus, run_id: int) -> list[int]: WHERE run_id=? and layout_id in (SELECT dependent FROM dependencies) """ c = atomic_transaction(conn, sql, run_id) - res = [d[0] for d in many_many(c, 'layout_id')] + res = [layout_id for layout_id, in many_many(c, "layout_id")] return res @@ -992,20 +975,18 @@ def get_run_counter(conn: ConnectionPlus, exp_id: int) -> int: return counter -def get_experiments(conn: ConnectionPlus) -> list[sqlite3.Row]: - """ Get a list of experiments - Args: - conn: database connection +def get_experiments(conn: ConnectionPlus) -> list[int]: + """Get a list of experiments + Args: + conn: database connection Returns: list of rows - """ - sql = """ - SELECT * FROM experiments """ + sql = "SELECT exp_id FROM experiments" c = atomic_transaction(conn, sql) - return c.fetchall() + return [exp_id for exp_id, in c.fetchall()] def get_matching_exp_ids(conn: ConnectionPlus, **match_conditions: Any) -> list[int]: @@ -1045,11 +1026,9 @@ def get_matching_exp_ids(conn: ConnectionPlus, **match_conditions: Any) -> list[ query = query.replace("end_time = ?", f"end_time {time_eq} ?") query = query.replace("sample_name = ?", f"sample_name {sample_name_eq} ?") - cursor = conn.cursor() - cursor.execute(query, tuple(match_conditions.values())) - rows = cursor.fetchall() + cursor = conn.execute(query, tuple(match_conditions.values())) - return [row[0] for row in rows] + return [exp_id for exp_id, in cursor.fetchall()] def get_exp_ids_from_run_ids(conn: ConnectionPlus, run_ids: Sequence[int]) -> list[int]: @@ -1087,7 +1066,7 @@ def get_last_experiment(conn: ConnectionPlus) -> int | None: return c.fetchall()[0][0] -def get_runs(conn: ConnectionPlus, exp_id: int | None = None) -> list[sqlite3.Row]: +def get_runs(conn: ConnectionPlus, exp_id: int | None = None) -> list[int]: """Get a list of runs. Args: @@ -1101,17 +1080,15 @@ def get_runs(conn: ConnectionPlus, exp_id: int | None = None) -> list[sqlite3.Ro with atomic(conn) as conn: if exp_id: sql = """ - SELECT * FROM runs - where exp_id = ? + SELECT run_id FROM runs + WHERE exp_id = ? """ c = transaction(conn, sql, exp_id) else: - sql = """ - SELECT * FROM runs - """ + sql = "SELECT run_id FROM runs" c = transaction(conn, sql) - return c.fetchall() + return [run_id for run_id, in c.fetchall()] def get_last_run(conn: ConnectionPlus, exp_id: int | None = None) -> int | None: @@ -1144,7 +1121,7 @@ def get_last_run(conn: ConnectionPlus, exp_id: int | None = None) -> int | None: def run_exists(conn: ConnectionPlus, run_id: int) -> bool: - # the following query always returns a single sqlite3.Row with an integer + # the following query always returns a single tuple with an integer # value of `1` or `0` for existing and non-existing run_id in the database query = """ SELECT EXISTS( @@ -1154,23 +1131,22 @@ def run_exists(conn: ConnectionPlus, run_id: int) -> bool: LIMIT 1 ); """ - res: sqlite3.Row = atomic_transaction(conn, query, run_id).fetchone() + res: tuple[int] = atomic_transaction(conn, query, run_id).fetchone() return bool(res[0]) -def data_sets(conn: ConnectionPlus) -> list[sqlite3.Row]: - """ Get a list of datasets +@deprecate(alternative="get_runs") +def data_sets(conn: ConnectionPlus) -> list[int]: + """Get a list of datasets Args: conn: database connection Returns: list of rows """ - sql = """ - SELECT * FROM runs - """ + sql = "SELECT run_id FROM runs" c = atomic_transaction(conn, sql) - return c.fetchall() + return [run_id for run_id, in c.fetchall()] def format_table_name(fmt_str: str, name: str, exp_id: int, @@ -1353,19 +1329,14 @@ def _get_parameters(conn: ConnectionPlus, run_id: int) -> list[ParamSpec]: """ sql = f""" - SELECT parameter FROM layouts WHERE run_id={run_id} + SELECT parameter FROM layouts + WHERE run_id = ? """ - c = conn.execute(sql) - param_names_temp = many_many(c, 'parameter') - param_names = [p[0] for p in param_names_temp] - param_names = cast(List[str], param_names) - - parspecs = [] - - for param_name in param_names: - parspecs.append(_get_paramspec(conn, run_id, param_name)) - - return parspecs + c = conn.execute(sql, (run_id,)) + return [ + _get_paramspec(conn, run_id, param_name) + for param_name, in many_many(c, "parameter") + ] def _get_paramspec(conn: ConnectionPlus, @@ -1383,31 +1354,33 @@ def _get_paramspec(conn: ConnectionPlus, # get table name sql = f""" - SELECT result_table_name FROM runs WHERE run_id = {run_id} + SELECT result_table_name FROM runs + WHERE run_id = ? """ - c = conn.execute(sql) + c = conn.execute(sql, (run_id,)) result_table_name = one(c, 'result_table_name') # get the data type sql = f""" PRAGMA TABLE_INFO("{result_table_name}") """ - c = conn.execute(sql) + c = c.execute(sql) + description = get_description_map(c) for row in c.fetchall(): - if row['name'] == param_name: - param_type = row['type'] + if row[description["name"]] == param_name: + param_type = row[description["type"]] break # get everything else sql = f""" - SELECT * FROM layouts - WHERE parameter="{param_name}" and run_id={run_id} + SELECT layout_id, run_id, parameter, label, unit, inferred_from FROM layouts + WHERE parameter = ? and run_id = ? """ - c = conn.execute(sql) - resp = many(c, 'layout_id', 'run_id', 'parameter', 'label', 'unit', - 'inferred_from') - (layout_id, _, _, label, unit, inferred_from_string) = resp + c = c.execute(sql, (param_name, run_id)) + (layout_id, _, _, label, unit, inferred_from_string) = many( + c, "layout_id", "run_id", "parameter", "label", "unit", "inferred_from" + ) if inferred_from_string: inferred_from = inferred_from_string.split(', ') @@ -1424,9 +1397,10 @@ def _get_paramspec(conn: ConnectionPlus, depends_on = [] for _, dp in sorted(zip(ax_nums, dps)): sql = f""" - SELECT parameter FROM layouts WHERE layout_id = {dp} + SELECT parameter FROM layouts + WHERE layout_id = ? """ - c = conn.execute(sql) + c = conn.execute(sql, (dp,)) depends_on.append(one(c, 'parameter')) parspec = ParamSpec(param_name, param_type, label, unit, @@ -1833,23 +1807,22 @@ def get_metadata_from_run_id(conn: ConnectionPlus, run_id: int) -> dict[str, Any # first fetch all columns of the runs table query = "PRAGMA table_info(runs)" - cursor = conn.cursor() - for row in cursor.execute(query): - if row['name'] not in non_metadata: - possible_tags.append(row['name']) + cursor = conn.execute(query) + description = get_description_map(cursor) + for row in cursor.fetchall(): + if row[description["name"]] not in non_metadata: + possible_tags.append(row[description["name"]]) # and then fetch whatever metadata the run might have for tag in possible_tags: query = f""" - SELECT "{tag}" - FROM runs - WHERE run_id = ? - AND "{tag}" IS NOT NULL - """ + SELECT "{tag}" FROM runs + WHERE run_id = ? AND "{tag}" IS NOT NULL + """ cursor.execute(query, (run_id,)) row = cursor.fetchall() if row != []: - metadata[tag] = row[0][tag] + metadata[tag] = row[0][0] return metadata @@ -2018,10 +1991,9 @@ def _both_zero( sql = f""" UPDATE runs SET guid = ? - where run_id == {run_id} + WHERE run_id = ? """ - cur = conn.cursor() - cur.execute(sql, (guid_str,)) + conn.execute(sql, (guid_str, run_id)) log.info(f'Succesfully updated run number {run_id}.') @@ -2165,14 +2137,16 @@ def _populate_results_table( target_cursor = target_conn.cursor() for row in source_cursor.execute(get_data_query): - column_names = ",".join(row.keys()[1:]) # the first key is "id" + column_names = ",".join( + d[0] for d in source_cursor.description[1:] + ) # the first key is "id" values = tuple(val for val in row[1:]) value_placeholders = sql_placeholder_string(len(values)) insert_data_query = f""" - INSERT INTO "{target_table_name}" - ({column_names}) - values {value_placeholders} - """ + INSERT INTO "{target_table_name}" + ({column_names}) + values {value_placeholders} + """ target_cursor.execute(insert_data_query, values) diff --git a/qcodes/dataset/sqlite/query_helpers.py b/qcodes/dataset/sqlite/query_helpers.py index 9911f3d23a7..81502b2d23a 100644 --- a/qcodes/dataset/sqlite/query_helpers.py +++ b/qcodes/dataset/sqlite/query_helpers.py @@ -26,6 +26,17 @@ VALUES = Sequence[VALUE] +def get_description_map(curr: sqlite3.Cursor) -> dict[str, int]: + """Get the description of the last query + Args: + curr: last cursor operated on + + Returns: + dictionary mapping column names and their indices + """ + return {c[0]: i for i, c in enumerate(curr.description)} + + def one(curr: sqlite3.Cursor, column: int | str) -> Any: """Get the value of one column from one row Args: @@ -41,7 +52,9 @@ def one(curr: sqlite3.Cursor, column: int | str) -> Any: elif len(res) == 0: raise RuntimeError("Expected one row") else: - return res[0][column] + return res[0][ + column if isinstance(column, int) else get_description_map(curr)[column] + ] def _need_to_select(curr: sqlite3.Cursor, *columns: str) -> bool: @@ -51,13 +64,6 @@ def _need_to_select(curr: sqlite3.Cursor, *columns: str) -> bool: return tuple(c[0] for c in curr.description) != columns -def _select_columns(row: sqlite3.Row, *columns: str) -> tuple[Any, ...]: - """ - sqlite3.Row({key:value, key2:value2}), (key2,) -> [value2] - """ - return tuple(row[c] for c in columns) - - def many(curr: sqlite3.Cursor, *columns: str) -> tuple[Any, ...]: """Get the values of many columns from one row Args: @@ -70,12 +76,13 @@ def many(curr: sqlite3.Cursor, *columns: str) -> tuple[Any, ...]: res = curr.fetchall() if len(res) > 1: raise RuntimeError("Expected only one row") - else: - return ( - _select_columns(res[0], *columns) - if _need_to_select(curr, *columns) - else res[0] + elif _need_to_select(curr, *columns): + raise RuntimeError( + "Expected consistent selection: cursor has columns" + f"{tuple(c[0] for c in curr.description)} but expected {columns}" ) + else: + return res[0] def many_many(curr: sqlite3.Cursor, *columns: str) -> list[tuple[Any, ...]]: @@ -88,11 +95,14 @@ def many_many(curr: sqlite3.Cursor, *columns: str) -> list[tuple[Any, ...]]: list of lists of values """ res = curr.fetchall() - return ( - [_select_columns(r, *columns) for r in res] - if _need_to_select(curr, *columns) - else res - ) + + if _need_to_select(curr, *columns): + raise RuntimeError( + "Expected consistent selection: cursor has columns" + f"{tuple(c[0] for c in curr.description)} but expected {columns}" + ) + + return res def select_one_where( @@ -369,8 +379,9 @@ def insert_column( # and do nothing if it is query = f'PRAGMA TABLE_INFO("{table}");' cur = atomic_transaction(conn, query) - columns = many_many(cur, "name") - if name in [col[0] for col in columns]: + description = get_description_map(cur) + columns = cur.fetchall() + if name in [col[description["name"]] for col in columns]: return with atomic(conn) as conn: @@ -396,8 +407,9 @@ def is_column_in_table(conn: ConnectionPlus, table: str, column: str) -> bool: column: the column name """ cur = atomic_transaction(conn, f"PRAGMA table_info({table})") + description = get_description_map(cur) for row in cur.fetchall(): - if row['name'] == column: + if row[description["name"]] == column: return True return False diff --git a/qcodes/tests/dataset/test_database_creation_and_upgrading.py b/qcodes/tests/dataset/test_database_creation_and_upgrading.py index ef4feded1f5..f5d1195ddf3 100644 --- a/qcodes/tests/dataset/test_database_creation_and_upgrading.py +++ b/qcodes/tests/dataset/test_database_creation_and_upgrading.py @@ -40,7 +40,11 @@ set_user_version, ) from qcodes.dataset.sqlite.queries import get_run_description, update_GUIDs -from qcodes.dataset.sqlite.query_helpers import is_column_in_table, one +from qcodes.dataset.sqlite.query_helpers import ( + get_description_map, + is_column_in_table, + one, +) from qcodes.tests.common import error_caused_by, skip_if_no_fixtures from qcodes.tests.dataset.conftest import temporarily_copied_DB @@ -77,16 +81,19 @@ def test_connect_upgrades_user_version(ver): @pytest.mark.parametrize('version', VERSIONS + (LATEST_VERSION_ARG,)) def test_tables_exist(empty_temp_db, version): - conn = connect(qc.config["core"]["db_location"], - qc.config["core"]["db_debug"], - version=version) - cursor = conn.execute("select sql from sqlite_master" - " where type = 'table'") - expected_tables = ['experiments', 'runs', 'layouts', 'dependencies'] + conn = connect( + qc.config["core"]["db_location"], qc.config["core"]["db_debug"], version=version + ) + query = """ + SELECT sql FROM sqlite_master + WHERE type = 'table' + """ + cursor = conn.execute(query) + expected_tables = ["experiments", "runs", "layouts", "dependencies"] rows = [row for row in cursor] assert len(rows) == len(expected_tables) - for row, expected_table in zip(rows, expected_tables): - assert expected_table in row['sql'] + for (sql,), expected_table in zip(rows, expected_tables): + assert expected_table in sql conn.close() @@ -704,8 +711,10 @@ def test_perform_actual_upgrade_6_to_7(): atomic_transaction(conn, no_of_runs_query), 'max(run_id)') assert no_of_runs == 10 - columns = atomic_transaction(conn, "PRAGMA table_info(runs)").fetchall() - col_names = [col['name'] for col in columns] + c = atomic_transaction(conn, "PRAGMA table_info(runs)") + description = get_description_map(c) + columns = c.fetchall() + col_names = [col[description["name"]] for col in columns] assert 'captured_run_id' in col_names assert 'captured_counter' in col_names diff --git a/qcodes/tests/dataset/test_sqlite_base.py b/qcodes/tests/dataset/test_sqlite_base.py index 7ac3a7c0e02..7218475ea3c 100644 --- a/qcodes/tests/dataset/test_sqlite_base.py +++ b/qcodes/tests/dataset/test_sqlite_base.py @@ -227,9 +227,10 @@ def test_runs_table_columns(empty_temp_db): colnames = list(mut_queries.RUNS_TABLE_COLUMNS) conn = mut_db.connect(get_DB_location()) query = "PRAGMA table_info(runs)" - cursor = conn.cursor() - for row in cursor.execute(query): - colnames.remove(row['name']) + cursor = conn.execute(query) + description = mut_help.get_description_map(cursor) + for row in cursor.fetchall(): + colnames.remove(row[description["name"]]) assert colnames == [] diff --git a/qcodes/tests/dataset/test_sqlite_connection.py b/qcodes/tests/dataset/test_sqlite_connection.py index a4ea0b19af5..1b7ba1c8744 100644 --- a/qcodes/tests/dataset/test_sqlite_connection.py +++ b/qcodes/tests/dataset/test_sqlite_connection.py @@ -332,5 +332,4 @@ def test_connect(): assert isinstance(conn, sqlite3.Connection) assert isinstance(conn, ConnectionPlus) assert False is conn.atomic_in_progress - - assert sqlite3.Row is conn.row_factory + assert None is conn.row_factory