dqlite-dbapi 0.2.2__tar.gz → 0.4.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {dqlite_dbapi-0.2.2 → dqlite_dbapi-0.4.0}/DEVELOPMENT.md +28 -3
- dqlite_dbapi-0.4.0/PKG-INFO +133 -0
- dqlite_dbapi-0.4.0/README.md +99 -0
- dqlite_dbapi-0.4.0/docs/architecture.md +169 -0
- dqlite_dbapi-0.4.0/docs/differences-from-aiosqlite.md +42 -0
- dqlite_dbapi-0.4.0/docs/differences-from-sqlite3.md +108 -0
- dqlite_dbapi-0.4.0/docs/transactions.md +96 -0
- {dqlite_dbapi-0.2.2 → dqlite_dbapi-0.4.0}/pyproject.toml +3 -3
- dqlite_dbapi-0.4.0/src/dqlitedbapi/__init__.py +156 -0
- dqlite_dbapi-0.4.0/src/dqlitedbapi/_busy.py +57 -0
- dqlite_dbapi-0.4.0/src/dqlitedbapi/_loop.py +105 -0
- dqlite_dbapi-0.4.0/src/dqlitedbapi/_module.py +35 -0
- dqlite_dbapi-0.4.0/src/dqlitedbapi/_sql.py +204 -0
- dqlite_dbapi-0.4.0/src/dqlitedbapi/_stubs.py +103 -0
- dqlite_dbapi-0.4.0/src/dqlitedbapi/aio/__init__.py +204 -0
- dqlite_dbapi-0.4.0/src/dqlitedbapi/aio/connection.py +549 -0
- dqlite_dbapi-0.4.0/src/dqlitedbapi/aio/cursor.py +480 -0
- dqlite_dbapi-0.4.0/src/dqlitedbapi/connection.py +300 -0
- dqlite_dbapi-0.4.0/src/dqlitedbapi/cursor.py +151 -0
- dqlite_dbapi-0.4.0/src/dqlitedbapi/exceptions.py +282 -0
- dqlite_dbapi-0.4.0/src/dqlitedbapi/row.py +56 -0
- dqlite_dbapi-0.4.0/src/dqlitedbapi/types.py +364 -0
- dqlite_dbapi-0.4.0/tests/aio/test_async_iter_protocol_invariants.py +28 -0
- dqlite_dbapi-0.4.0/tests/conftest.py +29 -0
- {dqlite_dbapi-0.2.2 → dqlite_dbapi-0.4.0}/tests/integration/conftest.py +0 -1
- dqlite_dbapi-0.4.0/tests/integration/test_arraysize_zero_fetchmany.py +43 -0
- {dqlite_dbapi-0.2.2 → dqlite_dbapi-0.4.0}/tests/integration/test_async_concurrent_executemany_serialized.py +3 -24
- {dqlite_dbapi-0.2.2 → dqlite_dbapi-0.4.0}/tests/integration/test_async_cursor_concurrent_execute_rejected.py +4 -21
- {dqlite_dbapi-0.2.2 → dqlite_dbapi-0.4.0}/tests/integration/test_async_cursor_edge_branches.py +5 -25
- dqlite_dbapi-0.4.0/tests/integration/test_async_executemany_param_count.py +96 -0
- dqlite_dbapi-0.4.0/tests/integration/test_async_fetchmany_cancel.py +59 -0
- {dqlite_dbapi-0.2.2 → dqlite_dbapi-0.4.0}/tests/integration/test_async_returning_zero_rows.py +2 -6
- {dqlite_dbapi-0.2.2 → dqlite_dbapi-0.4.0}/tests/integration/test_async_transaction_ctxmgr_rejects_stray_commit.py +4 -23
- dqlite_dbapi-0.4.0/tests/integration/test_boolean_conversion.py +62 -0
- {dqlite_dbapi-0.2.2 → dqlite_dbapi-0.4.0}/tests/integration/test_close_implicit_rollback_pep249.py +5 -25
- {dqlite_dbapi-0.2.2 → dqlite_dbapi-0.4.0}/tests/integration/test_concurrent_cursors_visibility.py +2 -5
- {dqlite_dbapi-0.2.2 → dqlite_dbapi-0.4.0}/tests/integration/test_constraint_extended_codes.py +3 -18
- {dqlite_dbapi-0.2.2 → dqlite_dbapi-0.4.0}/tests/integration/test_context_manager.py +0 -6
- dqlite_dbapi-0.4.0/tests/integration/test_cte_prefixed_dml_metadata.py +50 -0
- {dqlite_dbapi-0.2.2 → dqlite_dbapi-0.4.0}/tests/integration/test_cursor_fetch_after_non_row_execute.py +2 -22
- {dqlite_dbapi-0.2.2 → dqlite_dbapi-0.4.0}/tests/integration/test_cursor_misc_invariant_pins.py +6 -24
- {dqlite_dbapi-0.2.2 → dqlite_dbapi-0.4.0}/tests/integration/test_cursor_row_index_after_fetchall.py +4 -23
- {dqlite_dbapi-0.2.2 → dqlite_dbapi-0.4.0}/tests/integration/test_datetime_conversion.py +79 -64
- {dqlite_dbapi-0.2.2 → dqlite_dbapi-0.4.0}/tests/integration/test_description_after_ddl_pragma.py +2 -18
- {dqlite_dbapi-0.2.2 → dqlite_dbapi-0.4.0}/tests/integration/test_description_survives_empty_fetchall.py +2 -15
- {dqlite_dbapi-0.2.2 → dqlite_dbapi-0.4.0}/tests/integration/test_executemany_cancel_mid_batch.py +14 -45
- {dqlite_dbapi-0.2.2 → dqlite_dbapi-0.4.0}/tests/integration/test_executemany_completed_iterations_observability.py +5 -14
- {dqlite_dbapi-0.2.2 → dqlite_dbapi-0.4.0}/tests/integration/test_executemany_cte_dml.py +3 -11
- {dqlite_dbapi-0.2.2 → dqlite_dbapi-0.4.0}/tests/integration/test_executemany_failure_resets_rowcount.py +16 -28
- {dqlite_dbapi-0.2.2 → dqlite_dbapi-0.4.0}/tests/integration/test_executemany_generator.py +2 -6
- dqlite_dbapi-0.4.0/tests/integration/test_executemany_preserves_lastrowid.py +43 -0
- {dqlite_dbapi-0.2.2 → dqlite_dbapi-0.4.0}/tests/integration/test_executemany_returning.py +0 -1
- dqlite_dbapi-0.4.0/tests/integration/test_force_close_unblocks_run_sync.py +55 -0
- {dqlite_dbapi-0.2.2 → dqlite_dbapi-0.4.0}/tests/integration/test_lastrowid_autoincrement.py +3 -12
- {dqlite_dbapi-0.2.2 → dqlite_dbapi-0.4.0}/tests/integration/test_lastrowid_cursor_scoped.py +1 -17
- {dqlite_dbapi-0.2.2 → dqlite_dbapi-0.4.0}/tests/integration/test_lastrowid_returning.py +5 -26
- {dqlite_dbapi-0.2.2 → dqlite_dbapi-0.4.0}/tests/integration/test_lastrowid_survives_cross_cursor_rollback.py +1 -13
- {dqlite_dbapi-0.2.2 → dqlite_dbapi-0.4.0}/tests/integration/test_leader_redirect_live.py +8 -43
- {dqlite_dbapi-0.2.2 → dqlite_dbapi-0.4.0}/tests/integration/test_misc_coverage.py +47 -114
- {dqlite_dbapi-0.2.2 → dqlite_dbapi-0.4.0}/tests/integration/test_multiline_returning_rows.py +3 -7
- {dqlite_dbapi-0.2.2 → dqlite_dbapi-0.4.0}/tests/integration/test_nan_inf_float.py +6 -25
- {dqlite_dbapi-0.2.2 → dqlite_dbapi-0.4.0}/tests/integration/test_negative_zero_and_subnormal_float.py +5 -34
- {dqlite_dbapi-0.2.2 → dqlite_dbapi-0.4.0}/tests/integration/test_no_transaction_error_wording.py +6 -17
- {dqlite_dbapi-0.2.2 → dqlite_dbapi-0.4.0}/tests/integration/test_null_vs_empty_text_blob_three_way.py +5 -26
- dqlite_dbapi-0.4.0/tests/integration/test_pragma_deny_list.py +85 -0
- dqlite_dbapi-0.4.0/tests/integration/test_pragma_read_rowcount_minus_one.py +46 -0
- {dqlite_dbapi-0.2.2 → dqlite_dbapi-0.4.0}/tests/integration/test_query_detection.py +0 -4
- {dqlite_dbapi-0.2.2 → dqlite_dbapi-0.4.0}/tests/integration/test_returning.py +3 -18
- {dqlite_dbapi-0.2.2 → dqlite_dbapi-0.4.0}/tests/integration/test_savepoint_dbapi.py +3 -25
- {dqlite_dbapi-0.2.2 → dqlite_dbapi-0.4.0}/tests/integration/test_savepoint_failure_responses.py +4 -18
- {dqlite_dbapi-0.2.2 → dqlite_dbapi-0.4.0}/tests/integration/test_session_mode_read_only.py +11 -30
- {dqlite_dbapi-0.2.2 → dqlite_dbapi-0.4.0}/tests/integration/test_sqlite_version_pin.py +5 -23
- {dqlite_dbapi-0.2.2 → dqlite_dbapi-0.4.0}/tests/integration/test_sync_transaction_ctxmgr_rejects_stray_commit.py +9 -22
- dqlite_dbapi-0.4.0/tests/test_aconn_execute_with_parameters.py +40 -0
- dqlite_dbapi-0.4.0/tests/test_aconn_factory_getters.py +34 -0
- {dqlite_dbapi-0.2.2 → dqlite_dbapi-0.4.0}/tests/test_aio_description_tuple_exported.py +1 -8
- dqlite_dbapi-0.4.0/tests/test_aio_register_adapter_export.py +35 -0
- {dqlite_dbapi-0.2.2 → dqlite_dbapi-0.4.0}/tests/test_aio_register_converter_stubs.py +2 -10
- {dqlite_dbapi-0.2.2 → dqlite_dbapi-0.4.0}/tests/test_async_backup_not_async_def.py +2 -12
- {dqlite_dbapi-0.2.2 → dqlite_dbapi-0.4.0}/tests/test_async_cursor_aclose_alias.py +7 -33
- dqlite_dbapi-0.4.0/tests/test_async_cursor_aiter_aenter_pep249_pins.py +59 -0
- dqlite_dbapi-0.4.0/tests/test_async_execute_busy_retry_releases_op_lock_between_attempts.py +55 -0
- {dqlite_dbapi-0.2.2 → dqlite_dbapi-0.4.0}/tests/test_async_executescript_stub_raises_on_call.py +6 -16
- dqlite_dbapi-0.4.0/tests/test_autocommit_doc_no_round_trip.py +36 -0
- {dqlite_dbapi-0.2.2 → dqlite_dbapi-0.4.0}/tests/test_autocommit_legacy_transaction_control.py +5 -16
- {dqlite_dbapi-0.2.2 → dqlite_dbapi-0.4.0}/tests/test_binary_typeerror_stdlib_parity.py +3 -11
- {dqlite_dbapi-0.2.2 → dqlite_dbapi-0.4.0}/tests/test_classify_caller_sql_hardening.py +14 -55
- dqlite_dbapi-0.4.0/tests/test_commit_no_spurious_connect.py +18 -0
- {dqlite_dbapi-0.2.2 → dqlite_dbapi-0.4.0}/tests/test_conform_adapter_non_primitive_rejected_at_dbapi_layer.py +9 -22
- {dqlite_dbapi-0.2.2 → dqlite_dbapi-0.4.0}/tests/test_conftest_adapter_registry_isolation.py +4 -7
- {dqlite_dbapi-0.2.2 → dqlite_dbapi-0.4.0}/tests/test_connect_closed_precedes_thread_affinity.py +7 -20
- {dqlite_dbapi-0.2.2 → dqlite_dbapi-0.4.0}/tests/test_connect_isolation_level_autocommit_kwargs_symmetric.py +8 -41
- dqlite_dbapi-0.4.0/tests/test_connection_cursor_class_attribute.py +38 -0
- {dqlite_dbapi-0.2.2 → dqlite_dbapi-0.4.0}/tests/test_connection_eager_address_validation.py +6 -10
- {dqlite_dbapi-0.2.2 → dqlite_dbapi-0.4.0}/tests/test_connection_executemany_outer_shape_check.py +4 -26
- dqlite_dbapi-0.4.0/tests/test_convert_bind_param_conform_exception_propagates.py +50 -0
- {dqlite_dbapi-0.2.2 → dqlite_dbapi-0.4.0}/tests/test_cursor_after_external_close.py +7 -23
- dqlite_dbapi-0.4.0/tests/test_cursor_close_preserves_body_exception_on_messages_strip.py +56 -0
- {dqlite_dbapi-0.2.2 → dqlite_dbapi-0.4.0}/tests/test_cursor_closed_after_connection_gc_pep249.py +8 -34
- {dqlite_dbapi-0.2.2 → dqlite_dbapi-0.4.0}/tests/test_cursor_execute_non_str_operation.py +4 -13
- {dqlite_dbapi-0.2.2 → dqlite_dbapi-0.4.0}/tests/test_cursor_executemany_non_str_operation.py +4 -16
- {dqlite_dbapi-0.2.2 → dqlite_dbapi-0.4.0}/tests/test_cursor_factory_kwarg_rejected.py +3 -10
- dqlite_dbapi-0.4.0/tests/test_date_widens_to_datetime.py +25 -0
- {dqlite_dbapi-0.2.2 → dqlite_dbapi-0.4.0}/tests/test_datetime_time_round_trip.py +7 -14
- {dqlite_dbapi-0.2.2 → dqlite_dbapi-0.4.0}/tests/test_dbapi_default_timeouts_use_client_constants.py +1 -15
- dqlite_dbapi-0.4.0/tests/test_dbapi_exception_message_arg_cap.py +87 -0
- {dqlite_dbapi-0.2.2 → dqlite_dbapi-0.4.0}/tests/test_dbapi_exception_raw_message_cap.py +3 -13
- {dqlite_dbapi-0.2.2 → dqlite_dbapi-0.4.0}/tests/test_dbapi_type_hash_consistency.py +16 -31
- {dqlite_dbapi-0.2.2 → dqlite_dbapi-0.4.0}/tests/test_description_tuple_public_alias.py +3 -15
- dqlite_dbapi-0.4.0/tests/test_driver_rules.py +242 -0
- {dqlite_dbapi-0.2.2 → dqlite_dbapi-0.4.0}/tests/test_error_pickle_cause_either_or.py +1 -10
- {dqlite_dbapi-0.2.2 → dqlite_dbapi-0.4.0}/tests/test_error_pickle_preserves_raw_message.py +7 -11
- {dqlite_dbapi-0.2.2 → dqlite_dbapi-0.4.0}/tests/test_exception_repr_code.py +4 -13
- {dqlite_dbapi-0.2.2 → dqlite_dbapi-0.4.0}/tests/test_exceptions.py +0 -2
- {dqlite_dbapi-0.2.2 → dqlite_dbapi-0.4.0}/tests/test_exceptions_all_symmetry.py +4 -13
- {dqlite_dbapi-0.2.2 → dqlite_dbapi-0.4.0}/tests/test_execute_executemany_positional_only.py +2 -10
- dqlite_dbapi-0.4.0/tests/test_force_close_observe_callback_coverage.py +49 -0
- {dqlite_dbapi-0.2.2 → dqlite_dbapi-0.4.0}/tests/test_fromticks_tz_policy.py +5 -14
- dqlite_dbapi-0.4.0/tests/test_in_transaction_closed_divergence_doc.py +13 -0
- {dqlite_dbapi-0.2.2 → dqlite_dbapi-0.4.0}/tests/test_iso8601_aware_microseconds_round_trip.py +10 -21
- dqlite_dbapi-0.4.0/tests/test_iso8601_decoder_leap_seconds_rejected.py +28 -0
- dqlite_dbapi-0.4.0/tests/test_iso8601_decoder_subsecond_truncation.py +45 -0
- dqlite_dbapi-0.4.0/tests/test_iso8601_encoder.py +369 -0
- dqlite_dbapi-0.4.0/tests/test_iso8601_encoder_fold.py +48 -0
- {dqlite_dbapi-0.2.2 → dqlite_dbapi-0.4.0}/tests/test_iso8601_encoder_utcoffset_exception_wrap.py +16 -39
- {dqlite_dbapi-0.2.2 → dqlite_dbapi-0.4.0}/tests/test_iso8601_parse_error_truncation.py +10 -24
- {dqlite_dbapi-0.2.2 → dqlite_dbapi-0.4.0}/tests/test_iso8601_tzinfo_exception_classes_wrapped.py +7 -20
- {dqlite_dbapi-0.2.2 → dqlite_dbapi-0.4.0}/tests/test_isolation_level_stdlib_accept_set.py +12 -20
- dqlite_dbapi-0.4.0/tests/test_lastrowid_docstring_stdlib_parity_claim.py +20 -0
- dqlite_dbapi-0.4.0/tests/test_leader_error_codes_identity.py +26 -0
- {dqlite_dbapi-0.2.2 → dqlite_dbapi-0.4.0}/tests/test_legacy_transaction_control_constant.py +2 -10
- dqlite_dbapi-0.4.0/tests/test_no_workflow_references_in_source.py +38 -0
- dqlite_dbapi-0.4.0/tests/test_number_includes_boolean_documented.py +10 -0
- dqlite_dbapi-0.4.0/tests/test_paramstyle_qmark_only_pin.py +52 -0
- {dqlite_dbapi-0.2.2 → dqlite_dbapi-0.4.0}/tests/test_parse_decltypes_colnames_constants.py +3 -10
- {dqlite_dbapi-0.2.2 → dqlite_dbapi-0.4.0}/tests/test_pep249_exception_aliases.py +4 -16
- {dqlite_dbapi-0.2.2 → dqlite_dbapi-0.4.0}/tests/test_pep249_stub_hasattr_divergence.py +8 -32
- dqlite_dbapi-0.4.0/tests/test_pep249_type_constructors_vs_singletons.py +60 -0
- {dqlite_dbapi-0.2.2 → dqlite_dbapi-0.4.0}/tests/test_pep249_type_constructors_wrap_valueerror.py +2 -12
- {dqlite_dbapi-0.2.2 → dqlite_dbapi-0.4.0}/tests/test_pep249_type_objects_final_annotated.py +2 -16
- {dqlite_dbapi-0.2.2 → dqlite_dbapi-0.4.0}/tests/test_pickle_guard.py +5 -21
- dqlite_dbapi-0.4.0/tests/test_prepare_protocol_in_types_all.py +24 -0
- {dqlite_dbapi-0.2.2 → dqlite_dbapi-0.4.0}/tests/test_pyproject_dependency_graph.py +3 -15
- {dqlite_dbapi-0.2.2 → dqlite_dbapi-0.4.0}/tests/test_readme_truthfulness_pins.py +6 -27
- dqlite_dbapi-0.4.0/tests/test_register_adapter_conform_fallback.py +126 -0
- dqlite_dbapi-0.4.0/tests/test_register_adapter_global_sync_aio_symmetry.py +49 -0
- {dqlite_dbapi-0.2.2 → dqlite_dbapi-0.4.0}/tests/test_register_adapter_positional_only_signature.py +2 -7
- {dqlite_dbapi-0.2.2 → dqlite_dbapi-0.4.0}/tests/test_repr.py +3 -8
- {dqlite_dbapi-0.2.2 → dqlite_dbapi-0.4.0}/tests/test_row_factory_annotation.py +4 -22
- dqlite_dbapi-0.4.0/tests/test_row_factory_sqlite3_equivalent.py +188 -0
- dqlite_dbapi-0.4.0/tests/test_row_hash_eq_empty.py +36 -0
- dqlite_dbapi-0.4.0/tests/test_rowid_number_overlap_documented.py +25 -0
- {dqlite_dbapi-0.2.2 → dqlite_dbapi-0.4.0}/tests/test_setinputsizes_accepts_none.py +7 -13
- dqlite_dbapi-0.4.0/tests/test_setinputsizes_non_sequence_iterable.py +58 -0
- {dqlite_dbapi-0.2.2 → dqlite_dbapi-0.4.0}/tests/test_setinputsizes_sequence_acceptance.py +3 -17
- {dqlite_dbapi-0.2.2 → dqlite_dbapi-0.4.0}/tests/test_setoutputsize_accepts_none.py +6 -12
- {dqlite_dbapi-0.2.2 → dqlite_dbapi-0.4.0}/tests/test_sqlite_errorname_property.py +22 -19
- {dqlite_dbapi-0.2.2 → dqlite_dbapi-0.4.0}/tests/test_sqlite_version_not_public_attribute.py +1 -16
- {dqlite_dbapi-0.2.2 → dqlite_dbapi-0.4.0}/tests/test_stub_signatures_normalize_to_dbapi_error.py +21 -47
- {dqlite_dbapi-0.2.2 → dqlite_dbapi-0.4.0}/tests/test_sync_connection_execute_shortcut.py +4 -16
- {dqlite_dbapi-0.2.2 → dqlite_dbapi-0.4.0}/tests/test_sync_connection_executemany_shortcut.py +3 -14
- dqlite_dbapi-0.4.0/tests/test_text_bind_nul_byte_rejected_with_blob_hint.py +61 -0
- {dqlite_dbapi-0.2.2 → dqlite_dbapi-0.4.0}/tests/test_timeout_validation.py +4 -16
- {dqlite_dbapi-0.2.2 → dqlite_dbapi-0.4.0}/tests/test_total_changes_hasattr_safe.py +5 -23
- {dqlite_dbapi-0.2.2 → dqlite_dbapi-0.4.0}/tests/test_transitive_wire_import.py +2 -8
- dqlite_dbapi-0.4.0/tests/test_type_coercion_documented_asymmetries.py +138 -0
- {dqlite_dbapi-0.2.2 → dqlite_dbapi-0.4.0}/tests/test_types_dbapi_compare.py +16 -89
- dqlite_dbapi-0.4.0/tests/test_types_validator_branches.py +84 -0
- {dqlite_dbapi-0.2.2 → dqlite_dbapi-0.4.0}/tests/test_unregister_adapter.py +6 -25
- dqlite_dbapi-0.4.0/tests/test_unregister_adapter_rejects_built_in_datetime_types.py +65 -0
- {dqlite_dbapi-0.2.2 → dqlite_dbapi-0.4.0}/tests/test_validate_ticks.py +2 -12
- dqlite_dbapi-0.4.0/tests/test_validate_ticks_and_unixtime_reject_bool_and_non_numeric.py +74 -0
- dqlite_dbapi-0.4.0/tests/test_with_stub_client.py +607 -0
- {dqlite_dbapi-0.2.2 → dqlite_dbapi-0.4.0}/uv.lock +3 -3
- dqlite_dbapi-0.2.2/PKG-INFO +0 -377
- dqlite_dbapi-0.2.2/README.md +0 -343
- dqlite_dbapi-0.2.2/src/dqlitedbapi/__init__.py +0 -514
- dqlite_dbapi-0.2.2/src/dqlitedbapi/_busy_retry.py +0 -297
- dqlite_dbapi-0.2.2/src/dqlitedbapi/_constants.py +0 -88
- dqlite_dbapi-0.2.2/src/dqlitedbapi/_pragma_intercept.py +0 -264
- dqlite_dbapi-0.2.2/src/dqlitedbapi/aio/__init__.py +0 -602
- dqlite_dbapi-0.2.2/src/dqlitedbapi/aio/connection.py +0 -2974
- dqlite_dbapi-0.2.2/src/dqlitedbapi/aio/cursor.py +0 -1647
- dqlite_dbapi-0.2.2/src/dqlitedbapi/connection.py +0 -4432
- dqlite_dbapi-0.2.2/src/dqlitedbapi/cursor.py +0 -2934
- dqlite_dbapi-0.2.2/src/dqlitedbapi/exceptions.py +0 -460
- dqlite_dbapi-0.2.2/src/dqlitedbapi/row.py +0 -93
- dqlite_dbapi-0.2.2/src/dqlitedbapi/types.py +0 -1157
- dqlite_dbapi-0.2.2/tests/aio/test_aio_version_final_annotated.py +0 -26
- dqlite_dbapi-0.2.2/tests/aio/test_async_close_underlying_close_failed_logged.py +0 -144
- dqlite_dbapi-0.2.2/tests/aio/test_async_connection_fork_guard.py +0 -136
- dqlite_dbapi-0.2.2/tests/aio/test_async_iter_protocol_invariants.py +0 -41
- dqlite_dbapi-0.2.2/tests/aio/test_async_unclosed_warning_skips_in_fork_child.py +0 -181
- dqlite_dbapi-0.2.2/tests/aio/test_close_cancel_between_cursor_cascade_and_lock.py +0 -71
- dqlite_dbapi-0.2.2/tests/aio/test_fetchmany_cancel_atomic.py +0 -87
- dqlite_dbapi-0.2.2/tests/aio/test_messages_clear_on_closed.py +0 -56
- dqlite_dbapi-0.2.2/tests/conftest.py +0 -60
- dqlite_dbapi-0.2.2/tests/integration/test_async_fetchmany_cancel.py +0 -87
- dqlite_dbapi-0.2.2/tests/integration/test_pragma_deny_list.py +0 -122
- dqlite_dbapi-0.2.2/tests/test_aconn_execute_with_parameters.py +0 -59
- dqlite_dbapi-0.2.2/tests/test_aconn_factory_getters.py +0 -54
- dqlite_dbapi-0.2.2/tests/test_aconnect_cleanup_close_shielded_against_outer_timeout.py +0 -91
- dqlite_dbapi-0.2.2/tests/test_aconnect_cleanup_on_connect_failure.py +0 -91
- dqlite_dbapi-0.2.2/tests/test_adapter_exceptions_wrapped_as_data_error.py +0 -175
- dqlite_dbapi-0.2.2/tests/test_aenter_cleanup_nulls_locks_under_concurrent_close.py +0 -136
- dqlite_dbapi-0.2.2/tests/test_aenter_cleanup_preserves_original_under_outer_cancel.py +0 -87
- dqlite_dbapi-0.2.2/tests/test_aexit_behavioural_no_close.py +0 -90
- dqlite_dbapi-0.2.2/tests/test_aexit_commit_cancel_debug_log.py +0 -120
- dqlite_dbapi-0.2.2/tests/test_aexit_commit_cancel_force_closes_transport.py +0 -137
- dqlite_dbapi-0.2.2/tests/test_aexit_rollback_cancel_body_context_pin.py +0 -97
- dqlite_dbapi-0.2.2/tests/test_aexit_rollback_debug_log.py +0 -79
- dqlite_dbapi-0.2.2/tests/test_aio_commit_rollback_no_tx.py +0 -76
- dqlite_dbapi-0.2.2/tests/test_aio_commit_rollback_no_tx_code_matrix.py +0 -101
- dqlite_dbapi-0.2.2/tests/test_aio_commit_rollback_wrapping.py +0 -149
- dqlite_dbapi-0.2.2/tests/test_aio_connection_finalizer_detached_on_cancel_close.py +0 -75
- dqlite_dbapi-0.2.2/tests/test_aio_connection_finalizer_detached_on_close.py +0 -79
- dqlite_dbapi-0.2.2/tests/test_aio_cursor_drain_rows.py +0 -91
- dqlite_dbapi-0.2.2/tests/test_aio_cursor_row_factory_typeerror_wrap.py +0 -113
- dqlite_dbapi-0.2.2/tests/test_aio_execute_unlocked_silent_drop_scrubs_state.py +0 -126
- dqlite_dbapi-0.2.2/tests/test_aio_executemany_cursor_close_cascade.py +0 -36
- dqlite_dbapi-0.2.2/tests/test_aio_force_close_foreign_thread_cursor_closed_pre_cascade.py +0 -123
- dqlite_dbapi-0.2.2/tests/test_aio_force_close_transport_cursor_cascade.py +0 -102
- dqlite_dbapi-0.2.2/tests/test_aio_init_reexports_diagnostic_prefixes.py +0 -40
- dqlite_dbapi-0.2.2/tests/test_aio_loop_affinity_exc_class.py +0 -70
- dqlite_dbapi-0.2.2/tests/test_aio_module_attributes.py +0 -83
- dqlite_dbapi-0.2.2/tests/test_aio_register_adapter_export.py +0 -47
- dqlite_dbapi-0.2.2/tests/test_aio_row_factory_setter_loop_binding.py +0 -109
- dqlite_dbapi-0.2.2/tests/test_aio_setters_clear_messages_pep249.py +0 -45
- dqlite_dbapi-0.2.2/tests/test_aio_transaction_recheck_after_ensure_connection.py +0 -96
- dqlite_dbapi-0.2.2/tests/test_arraysize_validation.py +0 -109
- dqlite_dbapi-0.2.2/tests/test_async_check_loop_only_pid_before_loop.py +0 -87
- dqlite_dbapi-0.2.2/tests/test_async_close_cancel_arm_cancels_inner_pending_drain.py +0 -121
- dqlite_dbapi-0.2.2/tests/test_async_close_ensure_locks_guard.py +0 -45
- dqlite_dbapi-0.2.2/tests/test_async_close_finally_propagates_cancel.py +0 -163
- dqlite_dbapi-0.2.2/tests/test_async_close_op_lock_bounded.py +0 -147
- dqlite_dbapi-0.2.2/tests/test_async_close_outer_cancel_force_closes_transport.py +0 -119
- dqlite_dbapi-0.2.2/tests/test_async_close_race.py +0 -120
- dqlite_dbapi-0.2.2/tests/test_async_commit_rollback_force_close_on_outer_cancel.py +0 -125
- dqlite_dbapi-0.2.2/tests/test_async_commit_rollback_in_transaction_under_lock.py +0 -102
- dqlite_dbapi-0.2.2/tests/test_async_commit_rollback_loop_binding_first.py +0 -88
- dqlite_dbapi-0.2.2/tests/test_async_commit_rollback_messages_under_lock.py +0 -87
- dqlite_dbapi-0.2.2/tests/test_async_commit_rollback_op_lock_bounded.py +0 -67
- dqlite_dbapi-0.2.2/tests/test_async_commit_rollback_phase_budget.py +0 -109
- dqlite_dbapi-0.2.2/tests/test_async_commit_rollback_retry_after_invalidate.py +0 -143
- dqlite_dbapi-0.2.2/tests/test_async_commit_rollback_timeout_outer_scope_hint.py +0 -131
- dqlite_dbapi-0.2.2/tests/test_async_commit_rollback_timeout_phase_aware.py +0 -118
- dqlite_dbapi-0.2.2/tests/test_async_connect_cross_loop_diagnostic_on_fast_path.py +0 -65
- dqlite_dbapi-0.2.2/tests/test_async_connection.py +0 -82
- dqlite_dbapi-0.2.2/tests/test_async_connection_close_during_connect.py +0 -72
- dqlite_dbapi-0.2.2/tests/test_async_connection_cursor_fork_guard.py +0 -82
- dqlite_dbapi-0.2.2/tests/test_async_connection_ensure_connection_post_yield_close_shielded.py +0 -188
- dqlite_dbapi-0.2.2/tests/test_async_connection_race.py +0 -44
- dqlite_dbapi-0.2.2/tests/test_async_connection_setters_loop_binding.py +0 -68
- dqlite_dbapi-0.2.2/tests/test_async_connection_transaction.py +0 -59
- dqlite_dbapi-0.2.2/tests/test_async_cursor.py +0 -270
- dqlite_dbapi-0.2.2/tests/test_async_cursor_add_races_cascade_clear.py +0 -151
- dqlite_dbapi-0.2.2/tests/test_async_cursor_aenter_cross_task_executing_guard.py +0 -80
- dqlite_dbapi-0.2.2/tests/test_async_cursor_aenter_gc_parent_pep249.py +0 -32
- dqlite_dbapi-0.2.2/tests/test_async_cursor_aiter_aenter_pep249_pins.py +0 -98
- dqlite_dbapi-0.2.2/tests/test_async_cursor_aiter_cross_loop_fail_fast.py +0 -68
- dqlite_dbapi-0.2.2/tests/test_async_cursor_arraysize_row_factory_loop_binding.py +0 -79
- dqlite_dbapi-0.2.2/tests/test_async_cursor_callproc_nextset_scroll_loop_binding.py +0 -91
- dqlite_dbapi-0.2.2/tests/test_async_cursor_close_clears_executing_task.py +0 -80
- dqlite_dbapi-0.2.2/tests/test_async_cursor_close_is_sync_and_race_safe.py +0 -137
- dqlite_dbapi-0.2.2/tests/test_async_cursor_close_lock_free.py +0 -75
- dqlite_dbapi-0.2.2/tests/test_async_cursor_closed_state_pep249.py +0 -110
- dqlite_dbapi-0.2.2/tests/test_async_cursor_execute_executing_task_inside_try_frame.py +0 -63
- dqlite_dbapi-0.2.2/tests/test_async_cursor_execute_messages_clear.py +0 -82
- dqlite_dbapi-0.2.2/tests/test_async_cursor_executemany_validation_clears_executing_task.py +0 -118
- dqlite_dbapi-0.2.2/tests/test_async_cursor_fetch_loop_binding.py +0 -146
- dqlite_dbapi-0.2.2/tests/test_async_cursor_finally_clear_executing_task_unconditional.py +0 -48
- dqlite_dbapi-0.2.2/tests/test_async_cursor_late_add_proxy_swap.py +0 -52
- dqlite_dbapi-0.2.2/tests/test_async_cursor_loop_binding.py +0 -61
- dqlite_dbapi-0.2.2/tests/test_async_cursor_rownumber_closed_cross_loop.py +0 -68
- dqlite_dbapi-0.2.2/tests/test_async_cursor_setinputsizes_loop_binding.py +0 -54
- dqlite_dbapi-0.2.2/tests/test_async_executemany_atomic.py +0 -63
- dqlite_dbapi-0.2.2/tests/test_async_executemany_close_race.py +0 -72
- dqlite_dbapi-0.2.2/tests/test_async_fetch_does_not_lazy_bind_loop.py +0 -89
- dqlite_dbapi-0.2.2/tests/test_async_fetchall_row_factory_happy_path.py +0 -65
- dqlite_dbapi-0.2.2/tests/test_async_force_close_transport.py +0 -200
- dqlite_dbapi-0.2.2/tests/test_async_in_transaction_closed_foreign_loop.py +0 -75
- dqlite_dbapi-0.2.2/tests/test_async_in_transaction_loop_binding.py +0 -80
- dqlite_dbapi-0.2.2/tests/test_async_invalidated_property.py +0 -58
- dqlite_dbapi-0.2.2/tests/test_async_lock_binding.py +0 -143
- dqlite_dbapi-0.2.2/tests/test_async_resource_warning_gate.py +0 -144
- dqlite_dbapi-0.2.2/tests/test_async_resource_warning_sanitize_parity.py +0 -83
- dqlite_dbapi-0.2.2/tests/test_async_secondary_methods_clear_messages_before_guards.py +0 -144
- dqlite_dbapi-0.2.2/tests/test_async_transaction_owner_set_inside_try_frame.py +0 -185
- dqlite_dbapi-0.2.2/tests/test_async_with_exit_does_not_close.py +0 -83
- dqlite_dbapi-0.2.2/tests/test_audit_2026_05_coverage_gaps.py +0 -302
- dqlite_dbapi-0.2.2/tests/test_audit_2026_05_dbapi_coverage.py +0 -421
- dqlite_dbapi-0.2.2/tests/test_autocommit_doc_no_round_trip.py +0 -69
- dqlite_dbapi-0.2.2/tests/test_autocommit_property.py +0 -83
- dqlite_dbapi-0.2.2/tests/test_autocommit_setter_exact_int_gate.py +0 -114
- dqlite_dbapi-0.2.2/tests/test_begin_rewrite_session_mode.py +0 -210
- dqlite_dbapi-0.2.2/tests/test_bind_typeerror_as_dataerror.py +0 -53
- dqlite_dbapi-0.2.2/tests/test_build_and_connect_base_exception_group_wrap.py +0 -123
- dqlite_dbapi-0.2.2/tests/test_build_and_connect_resolve_leader_oserror_wrap.py +0 -84
- dqlite_dbapi-0.2.2/tests/test_build_and_connect_wire_encode_error_translation.py +0 -100
- dqlite_dbapi-0.2.2/tests/test_busy_retry_async_wiring.py +0 -159
- dqlite_dbapi-0.2.2/tests/test_busy_retry_classifier.py +0 -97
- dqlite_dbapi-0.2.2/tests/test_busy_retry_curve.py +0 -149
- dqlite_dbapi-0.2.2/tests/test_busy_retry_docstring_pins_cross_thread_caveat.py +0 -61
- dqlite_dbapi-0.2.2/tests/test_busy_retry_sync_wiring.py +0 -140
- dqlite_dbapi-0.2.2/tests/test_call_client_base_exception_group_wrap.py +0 -91
- dqlite_dbapi-0.2.2/tests/test_call_client_baseexceptiongroup_cancel_split.py +0 -110
- dqlite_dbapi-0.2.2/tests/test_call_client_docstring_mapping_table.py +0 -77
- dqlite_dbapi-0.2.2/tests/test_call_client_mapping.py +0 -155
- dqlite_dbapi-0.2.2/tests/test_call_client_typeerror_message_does_not_blame_bind.py +0 -43
- dqlite_dbapi-0.2.2/tests/test_call_client_wire_encode_error.py +0 -36
- dqlite_dbapi-0.2.2/tests/test_callproc_noreturn_annotation.py +0 -44
- dqlite_dbapi-0.2.2/tests/test_callproc_scroll_clear_messages.py +0 -88
- dqlite_dbapi-0.2.2/tests/test_cascade_cursors_clears_messages.py +0 -98
- dqlite_dbapi-0.2.2/tests/test_check_same_thread_kwarg.py +0 -264
- dqlite_dbapi-0.2.2/tests/test_classify_caller_sql.py +0 -99
- dqlite_dbapi-0.2.2/tests/test_classify_caller_sql_structural_reject_precedence.py +0 -100
- dqlite_dbapi-0.2.2/tests/test_classify_operational_code_zero.py +0 -29
- dqlite_dbapi-0.2.2/tests/test_cleanup_loop_thread_disarms_inner_and_reaps_drain.py +0 -304
- dqlite_dbapi-0.2.2/tests/test_cleanup_loop_thread_finalizer_fork_safe.py +0 -191
- dqlite_dbapi-0.2.2/tests/test_close_bypass_path_releases_latched_op_lock.py +0 -76
- dqlite_dbapi-0.2.2/tests/test_close_resource_leak.py +0 -35
- dqlite_dbapi-0.2.2/tests/test_close_signal_handler_reentry.py +0 -182
- dqlite_dbapi-0.2.2/tests/test_close_surfaces_op_lock_contention.py +0 -96
- dqlite_dbapi-0.2.2/tests/test_close_thread_join_uses_close_timeout.py +0 -186
- dqlite_dbapi-0.2.2/tests/test_close_timeout_floor_enforcement.py +0 -59
- dqlite_dbapi-0.2.2/tests/test_close_timeout_plumbing.py +0 -113
- dqlite_dbapi-0.2.2/tests/test_closed_property.py +0 -104
- dqlite_dbapi-0.2.2/tests/test_cluster_policy_rejection_prefix_constant.py +0 -67
- dqlite_dbapi-0.2.2/tests/test_code_to_exception_wire_ssot.py +0 -58
- dqlite_dbapi-0.2.2/tests/test_collated_coverage_gaps.py +0 -193
- dqlite_dbapi-0.2.2/tests/test_commit_after_external_close.py +0 -83
- dqlite_dbapi-0.2.2/tests/test_commit_leader_flip_ambiguous_commit_error.py +0 -82
- dqlite_dbapi-0.2.2/tests/test_commit_leader_flip_propagates.py +0 -136
- dqlite_dbapi-0.2.2/tests/test_commit_no_spurious_connect.py +0 -72
- dqlite_dbapi-0.2.2/tests/test_commit_rollback_shortcircuit_uses_in_transaction.py +0 -182
- dqlite_dbapi-0.2.2/tests/test_connect_baseexceptiongroup_cancel_split.py +0 -108
- dqlite_dbapi-0.2.2/tests/test_connect_busy_timeout_kwarg.py +0 -125
- dqlite_dbapi-0.2.2/tests/test_connect_clears_messages.py +0 -46
- dqlite_dbapi-0.2.2/tests/test_connect_docstring_parity.py +0 -36
- dqlite_dbapi-0.2.2/tests/test_connect_iso_autocommit_round_trip.py +0 -115
- dqlite_dbapi-0.2.2/tests/test_connect_preserves_code.py +0 -87
- dqlite_dbapi-0.2.2/tests/test_connection.py +0 -44
- dqlite_dbapi-0.2.2/tests/test_connection_after_fork_raises.py +0 -157
- dqlite_dbapi-0.2.2/tests/test_connection_autocommit_isolation_level_closed_state.py +0 -95
- dqlite_dbapi-0.2.2/tests/test_connection_close_cascades_cursors.py +0 -76
- dqlite_dbapi-0.2.2/tests/test_connection_close_cursor_cascade_atomicity.py +0 -74
- dqlite_dbapi-0.2.2/tests/test_connection_close_idempotent_cross_thread.py +0 -72
- dqlite_dbapi-0.2.2/tests/test_connection_closed_checks.py +0 -293
- dqlite_dbapi-0.2.2/tests/test_connection_cursor_class_attribute.py +0 -61
- dqlite_dbapi-0.2.2/tests/test_connection_cursor_closed_state_precedes_unknown_kwarg.py +0 -43
- dqlite_dbapi-0.2.2/tests/test_connection_database_empty_string_rejected.py +0 -56
- dqlite_dbapi-0.2.2/tests/test_connection_database_kwarg_isinstance.py +0 -45
- dqlite_dbapi-0.2.2/tests/test_connection_docstring_pins_check_same_thread_contract.py +0 -106
- dqlite_dbapi-0.2.2/tests/test_connection_enter_eager.py +0 -44
- dqlite_dbapi-0.2.2/tests/test_connection_exit_commit_ki_breadcrumb.py +0 -103
- dqlite_dbapi-0.2.2/tests/test_connection_exit_short_circuits_on_closed.py +0 -104
- dqlite_dbapi-0.2.2/tests/test_connection_messages_cleared_by_cursor_methods.py +0 -65
- dqlite_dbapi-0.2.2/tests/test_connection_row_factory_setter_closed_check.py +0 -57
- dqlite_dbapi-0.2.2/tests/test_context_manager_exit.py +0 -103
- dqlite_dbapi-0.2.2/tests/test_convert_bind_param_conform_exception_propagates.py +0 -77
- dqlite_dbapi-0.2.2/tests/test_convert_params_conform_raise_propagates_unwrapped.py +0 -81
- dqlite_dbapi-0.2.2/tests/test_cte_prefix_stripper_edges.py +0 -117
- dqlite_dbapi-0.2.2/tests/test_cursor.py +0 -234
- dqlite_dbapi-0.2.2/tests/test_cursor_arraysize_row_factory_setter_thread.py +0 -129
- dqlite_dbapi-0.2.2/tests/test_cursor_close_clears_messages.py +0 -38
- dqlite_dbapi-0.2.2/tests/test_cursor_close_cross_thread.py +0 -48
- dqlite_dbapi-0.2.2/tests/test_cursor_close_preserves_body_exception_on_messages_strip.py +0 -82
- dqlite_dbapi-0.2.2/tests/test_cursor_close_releases_connection_pin.py +0 -64
- dqlite_dbapi-0.2.2/tests/test_cursor_closed_state_pep249.py +0 -79
- dqlite_dbapi-0.2.2/tests/test_cursor_completed_iterations_resets_on_single_execute.py +0 -100
- dqlite_dbapi-0.2.2/tests/test_cursor_connection_partial_init_attributeerror.py +0 -52
- dqlite_dbapi-0.2.2/tests/test_cursor_connection_property_after_gc_pep249.py +0 -60
- dqlite_dbapi-0.2.2/tests/test_cursor_execute_state_reset_on_failure.py +0 -411
- dqlite_dbapi-0.2.2/tests/test_cursor_executemany_outer_shape_check.py +0 -82
- dqlite_dbapi-0.2.2/tests/test_cursor_force_close_race.py +0 -127
- dqlite_dbapi-0.2.2/tests/test_cursor_iter_clears_messages_pep249.py +0 -96
- dqlite_dbapi-0.2.2/tests/test_cursor_iterator_reset.py +0 -104
- dqlite_dbapi-0.2.2/tests/test_cursor_kwargs_rejection_precedence.py +0 -76
- dqlite_dbapi-0.2.2/tests/test_cursor_next_row_factory_raise_replays_row.py +0 -142
- dqlite_dbapi-0.2.2/tests/test_cursor_per_row_types.py +0 -177
- dqlite_dbapi-0.2.2/tests/test_cursor_post_close_property_reads.py +0 -121
- dqlite_dbapi-0.2.2/tests/test_cursor_protocol_check.py +0 -53
- dqlite_dbapi-0.2.2/tests/test_cursor_rejects_generator_params.py +0 -53
- dqlite_dbapi-0.2.2/tests/test_cursor_repr_with_gced_parent_connection.py +0 -85
- dqlite_dbapi-0.2.2/tests/test_cursor_row_factory_rejecting_setter.py +0 -104
- dqlite_dbapi-0.2.2/tests/test_cursor_row_factory_subclass_inheritance.py +0 -90
- dqlite_dbapi-0.2.2/tests/test_cursor_row_index_reset.py +0 -89
- dqlite_dbapi-0.2.2/tests/test_cursor_rowcount_after_ddl_stdlib_parity.py +0 -269
- dqlite_dbapi-0.2.2/tests/test_cursor_rownumber_affinity.py +0 -63
- dqlite_dbapi-0.2.2/tests/test_cursor_rownumber_post_close.py +0 -80
- dqlite_dbapi-0.2.2/tests/test_cursor_scroll_mode_validation.py +0 -157
- dqlite_dbapi-0.2.2/tests/test_cursor_secondary_methods_positional_only.py +0 -140
- dqlite_dbapi-0.2.2/tests/test_cursor_setinputsizes_affinity_precedes_shape.py +0 -168
- dqlite_dbapi-0.2.2/tests/test_cursor_setinputsizes_setoutputsize_closed_state_precedes_validation.py +0 -104
- dqlite_dbapi-0.2.2/tests/test_cursor_setters_clear_messages_pep249.py +0 -56
- dqlite_dbapi-0.2.2/tests/test_cursor_stop_iteration_contract.py +0 -90
- dqlite_dbapi-0.2.2/tests/test_cursor_uses_run_protocol.py +0 -63
- dqlite_dbapi-0.2.2/tests/test_cursors_weakset_state_lock.py +0 -142
- dqlite_dbapi-0.2.2/tests/test_date_widens_to_datetime.py +0 -34
- dqlite_dbapi-0.2.2/tests/test_datetime_from_iso8601_docstring_two_step_fallback.py +0 -47
- dqlite_dbapi-0.2.2/tests/test_dbapi_commit_deferred_fk.py +0 -39
- dqlite_dbapi-0.2.2/tests/test_dbapi_exception_message_arg_cap.py +0 -115
- dqlite_dbapi-0.2.2/tests/test_dbapi_fork_guard_cluster.py +0 -246
- dqlite_dbapi-0.2.2/tests/test_dbapi_hardening.py +0 -548
- dqlite_dbapi-0.2.2/tests/test_description_null_first_row_resolves.py +0 -131
- dqlite_dbapi-0.2.2/tests/test_description_null_type_code_to_none.py +0 -110
- dqlite_dbapi-0.2.2/tests/test_description_strict_type_codes.py +0 -59
- dqlite_dbapi-0.2.2/tests/test_description_type_code_unknown_sentinel.py +0 -69
- dqlite_dbapi-0.2.2/tests/test_diagnostic_messages.py +0 -140
- dqlite_dbapi-0.2.2/tests/test_dial_attempt_timeout_propagation.py +0 -117
- dqlite_dbapi-0.2.2/tests/test_dial_func_propagation.py +0 -82
- dqlite_dbapi-0.2.2/tests/test_dial_func_propagation_to_leader_resolve.py +0 -180
- dqlite_dbapi-0.2.2/tests/test_doc_class_and_description_drift.py +0 -68
- dqlite_dbapi-0.2.2/tests/test_dqlite_namespace_error_classification.py +0 -52
- dqlite_dbapi-0.2.2/tests/test_ensure_loop_fast_path_snapshot_dcl_defence.py +0 -100
- dqlite_dbapi-0.2.2/tests/test_ensure_loop_post_fork_pid_guard.py +0 -133
- dqlite_dbapi-0.2.2/tests/test_error_code_mapping.py +0 -120
- dqlite_dbapi-0.2.2/tests/test_exception_coded_mixin.py +0 -112
- dqlite_dbapi-0.2.2/tests/test_exception_mapping.py +0 -79
- dqlite_dbapi-0.2.2/tests/test_exception_wrapping.py +0 -131
- dqlite_dbapi-0.2.2/tests/test_execute_caller_param_shape_preserves_prior_state.py +0 -111
- dqlite_dbapi-0.2.2/tests/test_execute_multi_statement_rejection.py +0 -114
- dqlite_dbapi-0.2.2/tests/test_execute_reject_scrubs_prior_result_state.py +0 -212
- dqlite_dbapi-0.2.2/tests/test_executemany_accumulator_semantics.py +0 -110
- dqlite_dbapi-0.2.2/tests/test_executemany_basecaught_state_reset.py +0 -230
- dqlite_dbapi-0.2.2/tests/test_executemany_cancel_restores_pre_batch_lastrowid.py +0 -167
- dqlite_dbapi-0.2.2/tests/test_executemany_cancellederror_restores_pre_batch_lastrowid.py +0 -105
- dqlite_dbapi-0.2.2/tests/test_executemany_classifier_resets_state_first.py +0 -120
- dqlite_dbapi-0.2.2/tests/test_executemany_classify_caller_sql_hoisted.py +0 -61
- dqlite_dbapi-0.2.2/tests/test_executemany_completed_iterations_snapshot_conditional_restore.py +0 -160
- dqlite_dbapi-0.2.2/tests/test_executemany_dict_subclass_accepted.py +0 -80
- dqlite_dbapi-0.2.2/tests/test_executemany_empty.py +0 -112
- dqlite_dbapi-0.2.2/tests/test_executemany_empty_batch_preserves_lastrowid.py +0 -65
- dqlite_dbapi-0.2.2/tests/test_executemany_lastrowid_docstring_modern_stdlib.py +0 -41
- dqlite_dbapi-0.2.2/tests/test_executemany_lastrowid_mid_batch_preserves_in_batch.py +0 -167
- dqlite_dbapi-0.2.2/tests/test_executemany_none_seq_rejected.py +0 -78
- dqlite_dbapi-0.2.2/tests/test_executemany_per_iter_structural_rejects.py +0 -122
- dqlite_dbapi-0.2.2/tests/test_executemany_pragma_with_leading_comment.py +0 -75
- dqlite_dbapi-0.2.2/tests/test_executemany_push_skips_cascade_zeroed_cursor.py +0 -77
- dqlite_dbapi-0.2.2/tests/test_executemany_reject_scrubs_prior_result_state.py +0 -265
- dqlite_dbapi-0.2.2/tests/test_executemany_rejects_pure_queries.py +0 -103
- dqlite_dbapi-0.2.2/tests/test_executemany_rejects_transaction_verbs.py +0 -288
- dqlite_dbapi-0.2.2/tests/test_executemany_returning_cap.py +0 -69
- dqlite_dbapi-0.2.2/tests/test_exit_logs_rollback_failure.py +0 -102
- dqlite_dbapi-0.2.2/tests/test_fetchall_row_factory_raise_replay.py +0 -147
- dqlite_dbapi-0.2.2/tests/test_fetchmany_edges.py +0 -73
- dqlite_dbapi-0.2.2/tests/test_fetchmany_negative_size_rejected.py +0 -91
- dqlite_dbapi-0.2.2/tests/test_fetchmany_non_int_validation.py +0 -78
- dqlite_dbapi-0.2.2/tests/test_fetchmany_one_clear_per_call_pep249.py +0 -111
- dqlite_dbapi-0.2.2/tests/test_fetchmany_row_factory_raise_replay.py +0 -128
- dqlite_dbapi-0.2.2/tests/test_fetchmany_zero_docstring_stdlib_divergence.py +0 -60
- dqlite_dbapi-0.2.2/tests/test_finalizer_strict_warnings.py +0 -53
- dqlite_dbapi-0.2.2/tests/test_force_close_cancel_and_observe_narrow_suppress.py +0 -35
- dqlite_dbapi-0.2.2/tests/test_force_close_observe_ast_pin.py +0 -82
- dqlite_dbapi-0.2.2/tests/test_force_close_observe_callback_coverage.py +0 -71
- dqlite_dbapi-0.2.2/tests/test_force_close_transport_cleanup_tail_unconditional.py +0 -103
- dqlite_dbapi-0.2.2/tests/test_force_close_transport_clears_messages.py +0 -95
- dqlite_dbapi-0.2.2/tests/test_force_close_transport_cross_thread_cancel_threadsafe.py +0 -129
- dqlite_dbapi-0.2.2/tests/test_force_close_transport_cursor_cascade_threadsafe.py +0 -161
- dqlite_dbapi-0.2.2/tests/test_force_close_transport_defensive_arms.py +0 -123
- dqlite_dbapi-0.2.2/tests/test_force_close_transport_disarms_inner_finalizer.py +0 -85
- dqlite_dbapi-0.2.2/tests/test_force_close_transport_pending_drain_resnapshot.py +0 -143
- dqlite_dbapi-0.2.2/tests/test_force_close_transport_writer_cross_thread.py +0 -130
- dqlite_dbapi-0.2.2/tests/test_fork_diagnostic_includes_pids.py +0 -48
- dqlite_dbapi-0.2.2/tests/test_format_utc_offset_non_timedelta_guard.py +0 -62
- dqlite_dbapi-0.2.2/tests/test_gc_cleanup.py +0 -55
- dqlite_dbapi-0.2.2/tests/test_get_async_connection_force_close_race.py +0 -148
- dqlite_dbapi-0.2.2/tests/test_get_resolve_leader_cluster_no_loop_raises_runtime_error.py +0 -37
- dqlite_dbapi-0.2.2/tests/test_in_transaction_after_close.py +0 -113
- dqlite_dbapi-0.2.2/tests/test_in_transaction_closed_divergence_doc.py +0 -55
- dqlite_dbapi-0.2.2/tests/test_in_transaction_closed_foreign_thread.py +0 -107
- dqlite_dbapi-0.2.2/tests/test_in_transaction_property.py +0 -39
- dqlite_dbapi-0.2.2/tests/test_in_transaction_property_under_check_same_thread.py +0 -141
- dqlite_dbapi-0.2.2/tests/test_init_docstring_fromticks_three_impls.py +0 -64
- dqlite_dbapi-0.2.2/tests/test_is_multi_statement_walker.py +0 -61
- dqlite_dbapi-0.2.2/tests/test_is_no_transaction_error.py +0 -162
- dqlite_dbapi-0.2.2/tests/test_is_row_returning_multiline_whitespace.py +0 -112
- dqlite_dbapi-0.2.2/tests/test_is_row_returning_sql_noise.py +0 -76
- dqlite_dbapi-0.2.2/tests/test_iso8601_decoder_leap_seconds_rejected.py +0 -39
- dqlite_dbapi-0.2.2/tests/test_iso8601_decoder_subsecond_truncation.py +0 -92
- dqlite_dbapi-0.2.2/tests/test_iso8601_encoder.py +0 -471
- dqlite_dbapi-0.2.2/tests/test_iso8601_encoder_fold.py +0 -73
- dqlite_dbapi-0.2.2/tests/test_iterdump_stub_signature_3_13_filter_kwarg.py +0 -69
- dqlite_dbapi-0.2.2/tests/test_lastrowid_docstring_stdlib_parity_claim.py +0 -66
- dqlite_dbapi-0.2.2/tests/test_lastrowid_insert_only.py +0 -72
- dqlite_dbapi-0.2.2/tests/test_lastrowid_signed_int64_cast.py +0 -59
- dqlite_dbapi-0.2.2/tests/test_leader_error_codes_identity.py +0 -45
- dqlite_dbapi-0.2.2/tests/test_leader_redirect_on_connect.py +0 -279
- dqlite_dbapi-0.2.2/tests/test_max_continuation_frames_upper_bound.py +0 -65
- dqlite_dbapi-0.2.2/tests/test_max_message_size_round_trip.py +0 -82
- dqlite_dbapi-0.2.2/tests/test_max_total_rows_plumbing.py +0 -70
- dqlite_dbapi-0.2.2/tests/test_max_total_rows_validation.py +0 -56
- dqlite_dbapi-0.2.2/tests/test_messages_attribute.py +0 -34
- dqlite_dbapi-0.2.2/tests/test_messages_clear_on_ctxmgr_entry.py +0 -84
- dqlite_dbapi-0.2.2/tests/test_module_attributes.py +0 -258
- dqlite_dbapi-0.2.2/tests/test_module_level_stubs.py +0 -126
- dqlite_dbapi-0.2.2/tests/test_nextset_noreturn_annotation.py +0 -43
- dqlite_dbapi-0.2.2/tests/test_no_transaction_substrings_source_of_truth.py +0 -31
- dqlite_dbapi-0.2.2/tests/test_no_tx_primary_codes_invariant.py +0 -65
- dqlite_dbapi-0.2.2/tests/test_number_includes_boolean_documented.py +0 -34
- dqlite_dbapi-0.2.2/tests/test_op_lock_bounded_acquire.py +0 -87
- dqlite_dbapi-0.2.2/tests/test_operational_error_classification.py +0 -76
- dqlite_dbapi-0.2.2/tests/test_operational_error_raw_message.py +0 -81
- dqlite_dbapi-0.2.2/tests/test_param_conversion.py +0 -71
- dqlite_dbapi-0.2.2/tests/test_param_validation.py +0 -48
- dqlite_dbapi-0.2.2/tests/test_paramstyle_qmark_only_pin.py +0 -75
- dqlite_dbapi-0.2.2/tests/test_pep249_database_error_routing.py +0 -55
- dqlite_dbapi-0.2.2/tests/test_pep249_messages_clearing.py +0 -256
- dqlite_dbapi-0.2.2/tests/test_pep249_messages_independence.py +0 -88
- dqlite_dbapi-0.2.2/tests/test_pep249_misc_pins.py +0 -181
- dqlite_dbapi-0.2.2/tests/test_pep249_type_constructors_vs_singletons.py +0 -81
- dqlite_dbapi-0.2.2/tests/test_pragma_busy_timeout_intercept.py +0 -201
- dqlite_dbapi-0.2.2/tests/test_pragma_write_description_none.py +0 -60
- dqlite_dbapi-0.2.2/tests/test_prepare_protocol_in_types_all.py +0 -38
- dqlite_dbapi-0.2.2/tests/test_property_round_trip_and_fork_guard.py +0 -255
- dqlite_dbapi-0.2.2/tests/test_property_setters_do_not_mirror_to_inner_async_conn.py +0 -107
- dqlite_dbapi-0.2.2/tests/test_protocol_serialization.py +0 -99
- dqlite_dbapi-0.2.2/tests/test_register_adapter_conform_fallback.py +0 -162
- dqlite_dbapi-0.2.2/tests/test_register_adapter_global_sync_aio_symmetry.py +0 -73
- dqlite_dbapi-0.2.2/tests/test_repr_sanitizes_address.py +0 -162
- dqlite_dbapi-0.2.2/tests/test_resolve_leader_cache_dial_func_id_recycle.py +0 -178
- dqlite_dbapi-0.2.2/tests/test_resolve_leader_cache_lock_atfork_hook.py +0 -73
- dqlite_dbapi-0.2.2/tests/test_resolve_leader_cluster_cache.py +0 -124
- dqlite_dbapi-0.2.2/tests/test_resolve_leader_cross_loop_isolation.py +0 -233
- dqlite_dbapi-0.2.2/tests/test_row_factory_sqlite3_equivalent.py +0 -110
- dqlite_dbapi-0.2.2/tests/test_row_factory_sqlite3_row_surfaces_dataerror.py +0 -94
- dqlite_dbapi-0.2.2/tests/test_rowcount_select_divergence_documented.py +0 -47
- dqlite_dbapi-0.2.2/tests/test_rowid_number_overlap_documented.py +0 -43
- dqlite_dbapi-0.2.2/tests/test_rownumber_closed_cursor_returns_none_documented.py +0 -87
- dqlite_dbapi-0.2.2/tests/test_run_sync_ensure_loop_raise_closes_coroutine.py +0 -105
- dqlite_dbapi-0.2.2/tests/test_run_sync_keyboard_interrupt.py +0 -373
- dqlite_dbapi-0.2.2/tests/test_run_sync_keyboard_interrupt_cleanup_paths.py +0 -162
- dqlite_dbapi-0.2.2/tests/test_run_sync_ki_post_schedule_pre_await.py +0 -122
- dqlite_dbapi-0.2.2/tests/test_run_sync_ki_race_recovery.py +0 -198
- dqlite_dbapi-0.2.2/tests/test_run_sync_loop_closed_during_schedule.py +0 -143
- dqlite_dbapi-0.2.2/tests/test_run_sync_other_runtimeerror_propagates.py +0 -113
- dqlite_dbapi-0.2.2/tests/test_run_sync_timeout.py +0 -412
- dqlite_dbapi-0.2.2/tests/test_run_sync_timeout_recovery_preserves_conn.py +0 -111
- dqlite_dbapi-0.2.2/tests/test_secondary_methods_clear_messages_before_thread_check.py +0 -99
- dqlite_dbapi-0.2.2/tests/test_setinputsizes_clears_messages_per_pep249.py +0 -55
- dqlite_dbapi-0.2.2/tests/test_setinputsizes_non_sequence_iterable.py +0 -87
- dqlite_dbapi-0.2.2/tests/test_setinputsizes_short_circuit_on_closed_connection.py +0 -112
- dqlite_dbapi-0.2.2/tests/test_setter_closed_state_precedes_thread.py +0 -78
- dqlite_dbapi-0.2.2/tests/test_strip_leading_comments_bom.py +0 -70
- dqlite_dbapi-0.2.2/tests/test_strip_leading_comments_parity.py +0 -96
- dqlite_dbapi-0.2.2/tests/test_strip_sql_noise_backtick_identifier.py +0 -73
- dqlite_dbapi-0.2.2/tests/test_submodule_all.py +0 -39
- dqlite_dbapi-0.2.2/tests/test_sync_close_op_lock_owner_aware_probe.py +0 -100
- dqlite_dbapi-0.2.2/tests/test_sync_commit_rollback_closed_precedes_thread.py +0 -47
- dqlite_dbapi-0.2.2/tests/test_sync_commit_rollback_inside_ctxmgr_guard.py +0 -125
- dqlite_dbapi-0.2.2/tests/test_sync_commit_rollback_messages_under_lock.py +0 -78
- dqlite_dbapi-0.2.2/tests/test_sync_commit_rollback_retry_after_invalidate.py +0 -87
- dqlite_dbapi-0.2.2/tests/test_sync_commit_rollback_snapshot_inner_for_tier2.py +0 -53
- dqlite_dbapi-0.2.2/tests/test_sync_cursor_post_add_closed_recheck.py +0 -151
- dqlite_dbapi-0.2.2/tests/test_sync_exit_signal_mid_rollback.py +0 -75
- dqlite_dbapi-0.2.2/tests/test_sync_fetchmany_cancel_atomic.py +0 -84
- dqlite_dbapi-0.2.2/tests/test_sync_force_close_transport.py +0 -192
- dqlite_dbapi-0.2.2/tests/test_sync_force_close_transport_pending_drain_reap.py +0 -185
- dqlite_dbapi-0.2.2/tests/test_sync_invalidated_property.py +0 -59
- dqlite_dbapi-0.2.2/tests/test_sync_run_sync_multiphase_timeout.py +0 -127
- dqlite_dbapi-0.2.2/tests/test_sync_transaction_ctxmgr_owner_slot_busy_sentinel.py +0 -55
- dqlite_dbapi-0.2.2/tests/test_sync_transaction_owner_token_bookkeeping.py +0 -189
- dqlite_dbapi-0.2.2/tests/test_text_bind_nul_byte_rejected_with_blob_hint.py +0 -92
- dqlite_dbapi-0.2.2/tests/test_thread_safety_enforcement.py +0 -370
- dqlite_dbapi-0.2.2/tests/test_transaction_ctxmgr_state_lock.py +0 -235
- dqlite_dbapi-0.2.2/tests/test_type_coercion_documented_asymmetries.py +0 -183
- dqlite_dbapi-0.2.2/tests/test_types_validator_branches.py +0 -125
- dqlite_dbapi-0.2.2/tests/test_unregister_adapter_rejects_built_in_datetime_types.py +0 -87
- dqlite_dbapi-0.2.2/tests/test_unsupported_method_stubs.py +0 -403
- dqlite_dbapi-0.2.2/tests/test_validate_positive_int_pep249_wrap.py +0 -103
- dqlite_dbapi-0.2.2/tests/test_validate_ticks_and_unixtime_reject_bool_and_non_numeric.py +0 -107
- {dqlite_dbapi-0.2.2 → dqlite_dbapi-0.4.0}/.github/workflows/publish-to-pypi.yml +0 -0
- {dqlite_dbapi-0.2.2 → dqlite_dbapi-0.4.0}/.gitignore +0 -0
- {dqlite_dbapi-0.2.2 → dqlite_dbapi-0.4.0}/LICENSE.md +0 -0
- {dqlite_dbapi-0.2.2 → dqlite_dbapi-0.4.0}/src/dqlitedbapi/py.typed +0 -0
- {dqlite_dbapi-0.2.2 → dqlite_dbapi-0.4.0}/tests/__init__.py +0 -0
- {dqlite_dbapi-0.2.2 → dqlite_dbapi-0.4.0}/tests/integration/__init__.py +0 -0
- {dqlite_dbapi-0.2.2 → dqlite_dbapi-0.4.0}/tests/integration/test_integrity_error.py +0 -0
- {dqlite_dbapi-0.2.2 → dqlite_dbapi-0.4.0}/tests/test_top_level_logger_has_null_handler.py +0 -0
- {dqlite_dbapi-0.2.2 → dqlite_dbapi-0.4.0}/tests/test_version.py +0 -0
- {dqlite_dbapi-0.2.2 → dqlite_dbapi-0.4.0}/tests/test_version_export.py +0 -0
|
@@ -33,8 +33,8 @@ uv sync --extra dev
|
|
|
33
33
|
# Run unit tests only
|
|
34
34
|
.venv/bin/pytest tests/ --ignore=tests/integration
|
|
35
35
|
|
|
36
|
-
# Run all tests (requires Docker cluster)
|
|
37
|
-
cd ../dqlite-
|
|
36
|
+
# Run all tests (requires the Docker cluster from the sibling python-dqlite-dev checkout)
|
|
37
|
+
(cd ../python-dqlite-dev/cluster && docker compose up -d)
|
|
38
38
|
.venv/bin/pytest tests/
|
|
39
39
|
```
|
|
40
40
|
|
|
@@ -57,6 +57,31 @@ cd ../dqlite-test-cluster && docker compose up -d
|
|
|
57
57
|
.venv/bin/mypy src
|
|
58
58
|
```
|
|
59
59
|
|
|
60
|
+
## Comments and docstrings
|
|
61
|
+
|
|
62
|
+
Keep comments and docstrings to a minimum — the code should be clear
|
|
63
|
+
enough to stand on its own. Prefer renaming a function or variable over
|
|
64
|
+
writing a comment to explain an unclear one.
|
|
65
|
+
|
|
66
|
+
- Write a comment only when it captures something genuinely non-obvious
|
|
67
|
+
that the code cannot: a subtle invariant, a security caveat, a
|
|
68
|
+
workaround for an upstream bug, or a "why" a reader would otherwise
|
|
69
|
+
get wrong. Delete comments that merely restate what the code does.
|
|
70
|
+
- Most functions need no docstring. Add a one-line docstring only when
|
|
71
|
+
it tells a reader something the name and signature do not. Avoid
|
|
72
|
+
multi-paragraph essays, param-by-param prose, and "Notes/Divergence"
|
|
73
|
+
sections.
|
|
74
|
+
- Do not record rationale, history, or decision logs in comments — that
|
|
75
|
+
context belongs in the commit message and git history (`git blame`,
|
|
76
|
+
`git log -p`), where it stays attached to the change instead of aging
|
|
77
|
+
in the source.
|
|
78
|
+
- Tooling directives (`# type:`, `# noqa`, `# pragma:`) are exempt —
|
|
79
|
+
keep them.
|
|
80
|
+
|
|
81
|
+
When in doubt, leave it out: a missing explanation is a `git blame`
|
|
82
|
+
away; a redundant or stale comment is noise every future reader pays
|
|
83
|
+
for.
|
|
84
|
+
|
|
60
85
|
## Pre-commit Workflow
|
|
61
86
|
|
|
62
87
|
```bash
|
|
@@ -71,5 +96,5 @@ cd ../dqlite-test-cluster && docker compose up -d
|
|
|
71
96
|
This package implements the DB-API 2.0 specification (PEP 249):
|
|
72
97
|
|
|
73
98
|
- `apilevel = "2.0"`
|
|
74
|
-
- `threadsafety =
|
|
99
|
+
- `threadsafety = 2` (threads may share module and connections, not cursors)
|
|
75
100
|
- `paramstyle = "qmark"` (question mark placeholders)
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: dqlite-dbapi
|
|
3
|
+
Version: 0.4.0
|
|
4
|
+
Summary: PEP 249 (DB-API 2.0) compliant interface for dqlite
|
|
5
|
+
Project-URL: Homepage, https://github.com/letsdiscodev/python-dqlite-dbapi
|
|
6
|
+
Project-URL: Repository, https://github.com/letsdiscodev/python-dqlite-dbapi
|
|
7
|
+
Project-URL: Issues, https://github.com/letsdiscodev/python-dqlite-dbapi/issues
|
|
8
|
+
Author-email: Antoine Leclair <antoineleclair@gmail.com>
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
License-File: LICENSE.md
|
|
11
|
+
Keywords: database,dbapi,distributed,dqlite,pep249,sqlite
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Framework :: AsyncIO
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
+
Classifier: Operating System :: OS Independent
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
20
|
+
Classifier: Topic :: Database
|
|
21
|
+
Classifier: Topic :: Database :: Database Engines/Servers
|
|
22
|
+
Classifier: Topic :: Database :: Front-Ends
|
|
23
|
+
Classifier: Typing :: Typed
|
|
24
|
+
Requires-Python: >=3.13
|
|
25
|
+
Requires-Dist: dqlite-client<0.5.0,>=0.4.0
|
|
26
|
+
Requires-Dist: dqlite-wire<0.5.0,>=0.4.0
|
|
27
|
+
Provides-Extra: dev
|
|
28
|
+
Requires-Dist: mypy>=1.0; extra == 'dev'
|
|
29
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
|
|
30
|
+
Requires-Dist: pytest-cov>=4.0; extra == 'dev'
|
|
31
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
32
|
+
Requires-Dist: ruff>=0.4; extra == 'dev'
|
|
33
|
+
Description-Content-Type: text/markdown
|
|
34
|
+
|
|
35
|
+
# dqlite-dbapi
|
|
36
|
+
|
|
37
|
+
A [PEP 249](https://peps.python.org/pep-0249/) (DB-API 2.0) driver for
|
|
38
|
+
[dqlite](https://dqlite.io/), Canonical's distributed SQLite. If you have
|
|
39
|
+
used Python's standard `sqlite3` module, this will feel familiar — same
|
|
40
|
+
`connect()` / `cursor()` / `execute()` / `fetchone()` shape — but it talks
|
|
41
|
+
to a replicated dqlite cluster over the network instead of a local file.
|
|
42
|
+
|
|
43
|
+
It comes in two flavors from one package: a **synchronous** API
|
|
44
|
+
(`dqlitedbapi`) and an **async** API (`dqlitedbapi.aio`).
|
|
45
|
+
|
|
46
|
+
## Is this the package you want?
|
|
47
|
+
|
|
48
|
+
Yes, if you want a drop-in-style database driver for dqlite — directly,
|
|
49
|
+
or under a tool that expects a DB-API 2.0 driver. For SQLAlchemy
|
|
50
|
+
specifically, use [sqlalchemy-dqlite](https://github.com/letsdiscodev/sqlalchemy-dqlite)
|
|
51
|
+
(it builds on this package).
|
|
52
|
+
|
|
53
|
+
## Installation
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
pip install dqlite-dbapi
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Requires Python 3.13+.
|
|
60
|
+
|
|
61
|
+
## Usage
|
|
62
|
+
|
|
63
|
+
### Sync
|
|
64
|
+
|
|
65
|
+
```python
|
|
66
|
+
import dqlitedbapi
|
|
67
|
+
|
|
68
|
+
conn = dqlitedbapi.connect("localhost:9001")
|
|
69
|
+
cur = conn.cursor()
|
|
70
|
+
cur.execute("SELECT 1")
|
|
71
|
+
print(cur.fetchone())
|
|
72
|
+
conn.close()
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### Async
|
|
76
|
+
|
|
77
|
+
```python
|
|
78
|
+
import asyncio
|
|
79
|
+
from dqlitedbapi.aio import aconnect
|
|
80
|
+
|
|
81
|
+
async def main():
|
|
82
|
+
conn = await aconnect("localhost:9001")
|
|
83
|
+
cur = conn.cursor()
|
|
84
|
+
await cur.execute("SELECT 1")
|
|
85
|
+
print(await cur.fetchone())
|
|
86
|
+
await conn.close()
|
|
87
|
+
|
|
88
|
+
asyncio.run(main())
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## Key facts
|
|
92
|
+
|
|
93
|
+
- `apilevel = "2.0"`, `paramstyle = "qmark"` (`?` placeholders),
|
|
94
|
+
`threadsafety = 2`.
|
|
95
|
+
- **Autocommit by default.** Unlike stdlib `sqlite3`, the driver does not
|
|
96
|
+
auto-`BEGIN` before a statement — wrap writes in an explicit transaction
|
|
97
|
+
when you need atomic multi-statement semantics. See
|
|
98
|
+
[Transactions](docs/transactions.md).
|
|
99
|
+
- **Isolation is always SERIALIZABLE** — every write goes through Raft
|
|
100
|
+
consensus, every read is serializable.
|
|
101
|
+
|
|
102
|
+
## The dqlite Python stack
|
|
103
|
+
|
|
104
|
+
This is one of four layered packages. Each builds on the one below:
|
|
105
|
+
|
|
106
|
+
| Package | Role |
|
|
107
|
+
| --- | --- |
|
|
108
|
+
| [sqlalchemy-dqlite](https://github.com/letsdiscodev/sqlalchemy-dqlite) | SQLAlchemy 2.0 dialect |
|
|
109
|
+
| **dqlite-dbapi** — this package | PEP 249 (DB-API 2.0) driver — sync & async |
|
|
110
|
+
| [dqlite-client](https://github.com/letsdiscodev/python-dqlite-client) | Async wire client — pooling, leader discovery |
|
|
111
|
+
| [dqlite-wire](https://github.com/letsdiscodev/python-dqlite-wire) | Wire-protocol codec |
|
|
112
|
+
|
|
113
|
+
Using SQLAlchemy or an ORM? Prefer
|
|
114
|
+
[sqlalchemy-dqlite](https://github.com/letsdiscodev/sqlalchemy-dqlite).
|
|
115
|
+
|
|
116
|
+
## Documentation
|
|
117
|
+
|
|
118
|
+
- [Transactions](docs/transactions.md) — the autocommit-by-default model and how to group statements.
|
|
119
|
+
- [Differences from `sqlite3`](docs/differences-from-sqlite3.md) — what to
|
|
120
|
+
expect when porting from the stdlib module (and a server-version NULL
|
|
121
|
+
gotcha).
|
|
122
|
+
- [Differences from `aiosqlite`](docs/differences-from-aiosqlite.md) — for
|
|
123
|
+
the async surface.
|
|
124
|
+
- [Architecture](docs/architecture.md) — how the sync surface wraps the
|
|
125
|
+
async core, and the threading, lifecycle, and error-mapping policies.
|
|
126
|
+
|
|
127
|
+
## Development
|
|
128
|
+
|
|
129
|
+
See [DEVELOPMENT.md](DEVELOPMENT.md) for setup and contribution guidelines.
|
|
130
|
+
|
|
131
|
+
## License
|
|
132
|
+
|
|
133
|
+
MIT
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# dqlite-dbapi
|
|
2
|
+
|
|
3
|
+
A [PEP 249](https://peps.python.org/pep-0249/) (DB-API 2.0) driver for
|
|
4
|
+
[dqlite](https://dqlite.io/), Canonical's distributed SQLite. If you have
|
|
5
|
+
used Python's standard `sqlite3` module, this will feel familiar — same
|
|
6
|
+
`connect()` / `cursor()` / `execute()` / `fetchone()` shape — but it talks
|
|
7
|
+
to a replicated dqlite cluster over the network instead of a local file.
|
|
8
|
+
|
|
9
|
+
It comes in two flavors from one package: a **synchronous** API
|
|
10
|
+
(`dqlitedbapi`) and an **async** API (`dqlitedbapi.aio`).
|
|
11
|
+
|
|
12
|
+
## Is this the package you want?
|
|
13
|
+
|
|
14
|
+
Yes, if you want a drop-in-style database driver for dqlite — directly,
|
|
15
|
+
or under a tool that expects a DB-API 2.0 driver. For SQLAlchemy
|
|
16
|
+
specifically, use [sqlalchemy-dqlite](https://github.com/letsdiscodev/sqlalchemy-dqlite)
|
|
17
|
+
(it builds on this package).
|
|
18
|
+
|
|
19
|
+
## Installation
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
pip install dqlite-dbapi
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Requires Python 3.13+.
|
|
26
|
+
|
|
27
|
+
## Usage
|
|
28
|
+
|
|
29
|
+
### Sync
|
|
30
|
+
|
|
31
|
+
```python
|
|
32
|
+
import dqlitedbapi
|
|
33
|
+
|
|
34
|
+
conn = dqlitedbapi.connect("localhost:9001")
|
|
35
|
+
cur = conn.cursor()
|
|
36
|
+
cur.execute("SELECT 1")
|
|
37
|
+
print(cur.fetchone())
|
|
38
|
+
conn.close()
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### Async
|
|
42
|
+
|
|
43
|
+
```python
|
|
44
|
+
import asyncio
|
|
45
|
+
from dqlitedbapi.aio import aconnect
|
|
46
|
+
|
|
47
|
+
async def main():
|
|
48
|
+
conn = await aconnect("localhost:9001")
|
|
49
|
+
cur = conn.cursor()
|
|
50
|
+
await cur.execute("SELECT 1")
|
|
51
|
+
print(await cur.fetchone())
|
|
52
|
+
await conn.close()
|
|
53
|
+
|
|
54
|
+
asyncio.run(main())
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Key facts
|
|
58
|
+
|
|
59
|
+
- `apilevel = "2.0"`, `paramstyle = "qmark"` (`?` placeholders),
|
|
60
|
+
`threadsafety = 2`.
|
|
61
|
+
- **Autocommit by default.** Unlike stdlib `sqlite3`, the driver does not
|
|
62
|
+
auto-`BEGIN` before a statement — wrap writes in an explicit transaction
|
|
63
|
+
when you need atomic multi-statement semantics. See
|
|
64
|
+
[Transactions](docs/transactions.md).
|
|
65
|
+
- **Isolation is always SERIALIZABLE** — every write goes through Raft
|
|
66
|
+
consensus, every read is serializable.
|
|
67
|
+
|
|
68
|
+
## The dqlite Python stack
|
|
69
|
+
|
|
70
|
+
This is one of four layered packages. Each builds on the one below:
|
|
71
|
+
|
|
72
|
+
| Package | Role |
|
|
73
|
+
| --- | --- |
|
|
74
|
+
| [sqlalchemy-dqlite](https://github.com/letsdiscodev/sqlalchemy-dqlite) | SQLAlchemy 2.0 dialect |
|
|
75
|
+
| **dqlite-dbapi** — this package | PEP 249 (DB-API 2.0) driver — sync & async |
|
|
76
|
+
| [dqlite-client](https://github.com/letsdiscodev/python-dqlite-client) | Async wire client — pooling, leader discovery |
|
|
77
|
+
| [dqlite-wire](https://github.com/letsdiscodev/python-dqlite-wire) | Wire-protocol codec |
|
|
78
|
+
|
|
79
|
+
Using SQLAlchemy or an ORM? Prefer
|
|
80
|
+
[sqlalchemy-dqlite](https://github.com/letsdiscodev/sqlalchemy-dqlite).
|
|
81
|
+
|
|
82
|
+
## Documentation
|
|
83
|
+
|
|
84
|
+
- [Transactions](docs/transactions.md) — the autocommit-by-default model and how to group statements.
|
|
85
|
+
- [Differences from `sqlite3`](docs/differences-from-sqlite3.md) — what to
|
|
86
|
+
expect when porting from the stdlib module (and a server-version NULL
|
|
87
|
+
gotcha).
|
|
88
|
+
- [Differences from `aiosqlite`](docs/differences-from-aiosqlite.md) — for
|
|
89
|
+
the async surface.
|
|
90
|
+
- [Architecture](docs/architecture.md) — how the sync surface wraps the
|
|
91
|
+
async core, and the threading, lifecycle, and error-mapping policies.
|
|
92
|
+
|
|
93
|
+
## Development
|
|
94
|
+
|
|
95
|
+
See [DEVELOPMENT.md](DEVELOPMENT.md) for setup and contribution guidelines.
|
|
96
|
+
|
|
97
|
+
## License
|
|
98
|
+
|
|
99
|
+
MIT
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
# Architecture
|
|
2
|
+
|
|
3
|
+
This page records the design decisions behind `dqlite-dbapi`. The user-facing
|
|
4
|
+
behaviour they produce is documented in [Transactions](transactions.md) and
|
|
5
|
+
[Differences from `sqlite3`](differences-from-sqlite3.md).
|
|
6
|
+
|
|
7
|
+
## Layering
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
dqlitedbapi sync PEP 249 surface: Connection, Cursor
|
|
11
|
+
│ one daemon thread + asyncio loop per Connection
|
|
12
|
+
dqlitedbapi.aio async surface: AsyncConnection, AsyncCursor ← all driver logic
|
|
13
|
+
│
|
|
14
|
+
dqliteclient DqliteConnection (wire session), ClusterClient (leader discovery)
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
All driver logic lives in `dqlitedbapi.aio`. The sync package is a thin
|
|
18
|
+
adapter: a sync `Connection` owns one `AsyncConnection` and one background
|
|
19
|
+
thread running an asyncio event loop; every sync method submits the matching
|
|
20
|
+
coroutine to that loop and blocks on the result. Fetch methods never hop to
|
|
21
|
+
the loop: results are fully buffered at execute time, so the sync cursor reads
|
|
22
|
+
the buffer directly.
|
|
23
|
+
|
|
24
|
+
Modules:
|
|
25
|
+
|
|
26
|
+
| Module | Responsibility |
|
|
27
|
+
| --- | --- |
|
|
28
|
+
| `aio/connection.py` | `AsyncConnection`: lifecycle, transactions, session mode, operation lock |
|
|
29
|
+
| `aio/cursor.py` | `AsyncCursor`: execute / executemany, result buffering, description |
|
|
30
|
+
| `connection.py`, `cursor.py` | sync adapters over the async classes |
|
|
31
|
+
| `_loop.py` | the background loop thread used by the sync adapter |
|
|
32
|
+
| `_sql.py` | statement classification (row-returning, DML verbs, BEGIN rewrite, PRAGMA intercept) |
|
|
33
|
+
| `_busy.py` | `SQLITE_BUSY` retry curve |
|
|
34
|
+
| `exceptions.py` | PEP 249 hierarchy and the client-error to dbapi-error mapping |
|
|
35
|
+
| `types.py`, `row.py` | type objects, constructors, adapters, datetime codecs, `Row` |
|
|
36
|
+
|
|
37
|
+
Lexical SQL helpers (comment stripping, top-level statement splitting,
|
|
38
|
+
literal blanking, leading keyword) come from `dqliteclient.sql`, which the
|
|
39
|
+
client also uses for its own transaction tracking. `_sql.py` holds only the
|
|
40
|
+
semantic rules the driver adds on top.
|
|
41
|
+
|
|
42
|
+
## Connection lifecycle
|
|
43
|
+
|
|
44
|
+
- Constructing a connection validates its arguments and does no I/O.
|
|
45
|
+
- The first operation (or an explicit `connect()` / `aconnect()`) resolves
|
|
46
|
+
the cluster leader from the seed address through `ClusterClient`, opens the
|
|
47
|
+
wire session against the leader, and applies the session mode.
|
|
48
|
+
- **An invalidated connection is a closed connection.** When the underlying
|
|
49
|
+
wire session is lost (transport error, leader change, cancellation or
|
|
50
|
+
interrupt during an operation), `closed` becomes `True`, `invalidated`
|
|
51
|
+
becomes `True`, and every further operation raises `InterfaceError`. The
|
|
52
|
+
driver never reconnects silently: a reconnect would drop transaction state
|
|
53
|
+
the caller believes is still open. Pools (SQLAlchemy's included) discard the
|
|
54
|
+
connection and open a fresh one.
|
|
55
|
+
- `close()` is idempotent and does not roll back (stdlib `sqlite3` parity).
|
|
56
|
+
- `force_close_transport()` is synchronous, thread-safe, never raises, and
|
|
57
|
+
tears the socket down without waiting for an in-flight operation. It exists
|
|
58
|
+
for pool `terminate` paths and for shutting down from a thread that is not
|
|
59
|
+
the connection's owner.
|
|
60
|
+
|
|
61
|
+
## Threading and concurrency
|
|
62
|
+
|
|
63
|
+
### Sync surface
|
|
64
|
+
|
|
65
|
+
- `check_same_thread=True` (default): every method except
|
|
66
|
+
`force_close_transport()` must be called from the thread that created the
|
|
67
|
+
connection; other threads get `ProgrammingError`.
|
|
68
|
+
- `check_same_thread=False`: any thread may call the connection. Calls are
|
|
69
|
+
serialised by a lock. Cursors are still single-thread objects, as in
|
|
70
|
+
`sqlite3`: share the connection, give each thread its own cursor.
|
|
71
|
+
- **Interrupts close the connection.** A `KeyboardInterrupt` or `SystemExit`
|
|
72
|
+
delivered while a call is blocked cancels the in-flight operation,
|
|
73
|
+
force-closes the connection, and propagates. The driver must not be
|
|
74
|
+
re-entered from a signal handler; that case is out of contract.
|
|
75
|
+
- The sync side imposes no deadline of its own. Every await in the async
|
|
76
|
+
layer is already bounded by the client's per-RPC timeouts, so a blocked sync
|
|
77
|
+
call ends when those fire or when `force_close_transport()` is called from
|
|
78
|
+
another thread.
|
|
79
|
+
|
|
80
|
+
### Async surface
|
|
81
|
+
|
|
82
|
+
- An `AsyncConnection` binds to the event loop it first runs on. Using it
|
|
83
|
+
from another loop raises `InterfaceError`; it cannot be shared across
|
|
84
|
+
`asyncio.run()` calls or threads.
|
|
85
|
+
- One operation at a time: an `asyncio.Lock` serialises execute, commit and
|
|
86
|
+
rollback across tasks on the same loop. Waiting tasks queue rather than
|
|
87
|
+
fail. A cursor is a single-task object.
|
|
88
|
+
- Cancellation during a wire round-trip invalidates the connection (see
|
|
89
|
+
above), because the server-side effect of the interrupted statement is
|
|
90
|
+
unknown.
|
|
91
|
+
|
|
92
|
+
## Statements
|
|
93
|
+
|
|
94
|
+
- One statement per `execute()`; a second statement after `;` raises
|
|
95
|
+
`ProgrammingError` before any network round-trip. `CREATE TRIGGER` bodies
|
|
96
|
+
are understood, so their inner `;` do not count.
|
|
97
|
+
- Empty, comment-only, and NUL-containing SQL raise `ProgrammingError`.
|
|
98
|
+
- A `?` count that does not match the parameter count raises
|
|
99
|
+
`ProgrammingError` client-side.
|
|
100
|
+
- Row-returning statements (`SELECT`, `VALUES`, `PRAGMA`, `EXPLAIN`, a
|
|
101
|
+
`WITH` whose final statement is one of those, and any DML with
|
|
102
|
+
`RETURNING`) go through the query path and populate `description` and the
|
|
103
|
+
row buffer. Everything else goes through the exec path and populates
|
|
104
|
+
`rowcount` (INSERT/UPDATE/DELETE/REPLACE only, else -1) and `lastrowid`
|
|
105
|
+
(INSERT/REPLACE only, sticky across other statements).
|
|
106
|
+
- `PRAGMA busy_timeout` is answered locally: the server rejects it, and the
|
|
107
|
+
driver owns the busy budget.
|
|
108
|
+
- A bare `BEGIN` / `BEGIN TRANSACTION` is rewritten according to the session
|
|
109
|
+
mode: `immediate` → `BEGIN IMMEDIATE`, `exclusive` → `BEGIN EXCLUSIVE`,
|
|
110
|
+
`deferred` and `read_only` → unchanged. An explicit qualifier is never
|
|
111
|
+
rewritten.
|
|
112
|
+
- `SQLITE_BUSY` is retried on SQLite's default busy-callback curve until the
|
|
113
|
+
connection's `busy_timeout` budget is spent. Each `executemany` iteration
|
|
114
|
+
retries independently.
|
|
115
|
+
- `description` carries `(name, type_code, None, None, None, None, None)`;
|
|
116
|
+
the type code is the wire `ValueType` of the first non-NULL cell in the
|
|
117
|
+
column, or the `UNKNOWN` sentinel when there is none.
|
|
118
|
+
- `ISO8601` and `UNIXTIME` wire cells decode to `datetime` objects; every
|
|
119
|
+
other cell is passed through as the wire primitive.
|
|
120
|
+
|
|
121
|
+
## Transactions
|
|
122
|
+
|
|
123
|
+
- Autocommit by default: no implicit `BEGIN`.
|
|
124
|
+
- `commit()` / `rollback()` are no-ops before the first connection and when no
|
|
125
|
+
transaction is open. A server reply of "no transaction is active" is
|
|
126
|
+
swallowed. Every other failure propagates.
|
|
127
|
+
- A `COMMIT` that fails with a leadership-lost code raises
|
|
128
|
+
`AmbiguousCommitError` (an `OperationalError`): the write may or may not
|
|
129
|
+
have been applied.
|
|
130
|
+
- `transaction()` is a context manager issuing `BEGIN` / `COMMIT` /
|
|
131
|
+
`ROLLBACK`. Calling `commit()` or `rollback()` inside the block raises
|
|
132
|
+
`InterfaceError`, because the block owns the boundaries.
|
|
133
|
+
- `with conn:` / `async with conn:` commit on success and roll back on
|
|
134
|
+
exception; neither closes the connection.
|
|
135
|
+
- `session_mode` is read-only; `set_session_mode()` changes it, emitting
|
|
136
|
+
`PRAGMA query_only` when crossing the `read_only` boundary. It may not be
|
|
137
|
+
called inside a transaction.
|
|
138
|
+
|
|
139
|
+
## Errors
|
|
140
|
+
|
|
141
|
+
Client exceptions are translated at the boundary by `exceptions.translate`:
|
|
142
|
+
|
|
143
|
+
| Client exception | dbapi exception |
|
|
144
|
+
| --- | --- |
|
|
145
|
+
| `OperationalError` with code | class chosen from the SQLite primary code (constraint → `IntegrityError`, internal/nomem/notfound → `InternalError`, toobig → `DataError`, range/misuse → `InterfaceError`, corrupt/format/notadb → bare `DatabaseError`, dqlite parse/notfound → `ProgrammingError`, else `OperationalError`) |
|
|
146
|
+
| `DqliteConnectionError`, `ClusterError`, `ProtocolError` | `OperationalError` with `code=None` |
|
|
147
|
+
| `ClusterPolicyError` | `InterfaceError` prefixed `Cluster policy rejection` (never retried) |
|
|
148
|
+
| `DataError`, wire `EncodeError` | `DataError` |
|
|
149
|
+
| `InterfaceError` | `InterfaceError` |
|
|
150
|
+
| any other `DqliteError` | `DatabaseError` |
|
|
151
|
+
|
|
152
|
+
Every raised error keeps `code` and `raw_message` (the untruncated server
|
|
153
|
+
text). Connect-time failures are prefixed `Failed to connect: `. These
|
|
154
|
+
prefixes and the `Connection is closed` / `Cursor is closed` /
|
|
155
|
+
`invalidated (id=` wordings are load-bearing: `sqlalchemy-dqlite` classifies
|
|
156
|
+
disconnects on them.
|
|
157
|
+
|
|
158
|
+
## Contract with sqlalchemy-dqlite
|
|
159
|
+
|
|
160
|
+
The dialect relies on exactly this dbapi surface, beyond PEP 249 itself:
|
|
161
|
+
|
|
162
|
+
- module: `connect`, exception classes, `paramstyle`, `sqlite_version_info`,
|
|
163
|
+
`FAILED_TO_CONNECT_PREFIX`, `CLUSTER_POLICY_REJECTION_PREFIX`;
|
|
164
|
+
- connection: `cursor()`, `commit()`, `rollback()`, `close()`,
|
|
165
|
+
`force_close_transport()`, `connect()` (async), `session_mode`,
|
|
166
|
+
`default_session_mode`, `set_session_mode()`, `in_transaction`;
|
|
167
|
+
- cursor: `execute()`, `executemany()`, `description`, `rowcount`,
|
|
168
|
+
`lastrowid`, `drain_rows()` (async), `close()`;
|
|
169
|
+
- `types.format_utc_offset()` for the dialect's datetime processors.
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# Differences from `aiosqlite`
|
|
2
|
+
|
|
3
|
+
The async surface lives under `dqlitedbapi.aio` and mirrors the sync
|
|
4
|
+
`dqlitedbapi` surface, plus async-specific `aconnect` / `AsyncConnection` /
|
|
5
|
+
`AsyncCursor`.
|
|
6
|
+
|
|
7
|
+
For easy porting from [aiosqlite](https://github.com/omnilib/aiosqlite), the
|
|
8
|
+
full PEP 249 module surface is re-exported under `dqlitedbapi.aio` from one
|
|
9
|
+
namespace: the module attributes (`apilevel`, `threadsafety`, `paramstyle`,
|
|
10
|
+
`sqlite_version`, `sqlite_version_info`), the exception hierarchy (`Warning`,
|
|
11
|
+
`Error`, `InterfaceError`, `DatabaseError`, `DataError`, `OperationalError`,
|
|
12
|
+
`IntegrityError`, `InternalError`, `ProgrammingError`, `NotSupportedError`),
|
|
13
|
+
the type constructors (`Date`, `Time`, `Timestamp`, `DateFromTicks`,
|
|
14
|
+
`TimeFromTicks`, `TimestampFromTicks`, `Binary`), the type sentinels
|
|
15
|
+
(`STRING`, `BINARY`, `NUMBER`, `DATETIME`, `ROWID`, `UNKNOWN`), the `Row`
|
|
16
|
+
factory and `DescriptionTuple`, `register_adapter` / `unregister_adapter`, and
|
|
17
|
+
the `NotSupportedError` stubs for stdlib-only APIs.
|
|
18
|
+
|
|
19
|
+
## The one difference to watch
|
|
20
|
+
|
|
21
|
+
**`AsyncConnection.__aexit__` does not close the connection.** It commits or
|
|
22
|
+
rolls back per the block outcome and leaves the connection reusable — matching
|
|
23
|
+
stdlib `sqlite3.Connection.__exit__` and PEP 249 §7. `aiosqlite`'s
|
|
24
|
+
`__aexit__` *does* close (it tears down its proxy thread and sqlite3
|
|
25
|
+
connection).
|
|
26
|
+
|
|
27
|
+
So when porting from aiosqlite, add an explicit `await conn.close()` (or use a
|
|
28
|
+
pool) where you relied on close-on-exit:
|
|
29
|
+
|
|
30
|
+
```python
|
|
31
|
+
# aiosqlite — closes on aexit:
|
|
32
|
+
async with aiosqlite.connect(":memory:") as conn:
|
|
33
|
+
...
|
|
34
|
+
|
|
35
|
+
# dqlitedbapi.aio — does NOT close on aexit (matches stdlib sqlite3):
|
|
36
|
+
async with await dqlitedbapi.aio.aconnect(...) as conn:
|
|
37
|
+
...
|
|
38
|
+
await conn.close() # explicit
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Everything else under [Differences from `sqlite3`](differences-from-sqlite3.md)
|
|
42
|
+
applies to the async surface too.
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# Differences from `sqlite3`
|
|
2
|
+
|
|
3
|
+
`dqlite-dbapi` is shaped like the stdlib `sqlite3` module, but it talks to a
|
|
4
|
+
replicated cluster over the network rather than a local file. This page
|
|
5
|
+
covers the differences you are most likely to notice when porting code.
|
|
6
|
+
|
|
7
|
+
## Things you'll likely notice
|
|
8
|
+
|
|
9
|
+
- **Autocommit by default.** No implicit `BEGIN` before DML — the opposite
|
|
10
|
+
of stdlib's implicit-transaction model. See
|
|
11
|
+
[Transactions](transactions.md).
|
|
12
|
+
- **One statement per `execute()`.** `cursor.execute("SELECT 1; SELECT 2;")`
|
|
13
|
+
raises `ProgrammingError` (client-side, before any network round-trip).
|
|
14
|
+
Split into separate calls. There is no `executescript()` — it raises
|
|
15
|
+
`NotSupportedError`.
|
|
16
|
+
- **`qmark` parameters only.** Use `?` placeholders. Stdlib also accepts
|
|
17
|
+
`:name`; this driver advertises only `qmark` and rejects mappings with
|
|
18
|
+
`ProgrammingError`.
|
|
19
|
+
- **Foreign keys are enforced by default.** dqlite defaults every fresh
|
|
20
|
+
connection to `PRAGMA foreign_keys = ON` — the opposite of stdlib/pysqlite,
|
|
21
|
+
which default `OFF`. Issue `PRAGMA foreign_keys = OFF` per connection for
|
|
22
|
+
SQLite's legacy unenforced behavior.
|
|
23
|
+
- **SERIALIZABLE isolation only.** Every statement is ordered by Raft;
|
|
24
|
+
weaker isolation levels are not exposed.
|
|
25
|
+
- **`connect()` timeouts differ from stdlib.** stdlib's `timeout` is the
|
|
26
|
+
busy (lock-wait) budget; here `timeout` is a per-RPC-phase deadline and
|
|
27
|
+
the busy budget is the separate `busy_timeout` argument. `connect()` also
|
|
28
|
+
exposes safety caps — `max_message_size`, `max_continuation_frames`,
|
|
29
|
+
`max_total_rows` — and `trust_server_heartbeat`.
|
|
30
|
+
- **Result sets are fully materialized at `execute()` time.** Stdlib streams
|
|
31
|
+
rows lazily from the C engine; dqlite drains every continuation frame into
|
|
32
|
+
the cursor's in-memory buffer before `execute()` returns, and `fetchone()`
|
|
33
|
+
/ `fetchmany()` / `fetchall()` iterate that buffer. For large queries, cap
|
|
34
|
+
memory with `max_total_rows` (forwarded from `connect()`) or shape queries
|
|
35
|
+
with an explicit `LIMIT`.
|
|
36
|
+
- **No SQLite extension APIs.** `create_function`, `create_aggregate`,
|
|
37
|
+
`create_window_function`, `iterdump`, `backup`, `set_authorizer`,
|
|
38
|
+
`serialize`, `blobopen`, `executescript`, and `register_converter` have no
|
|
39
|
+
server-side counterpart in dqlite; their stubs raise `NotSupportedError`.
|
|
40
|
+
- **A lost connection stays lost.** When the wire session drops (transport
|
|
41
|
+
error, leader change, or an interrupted call), `closed` becomes `True`,
|
|
42
|
+
`invalidated` becomes `True`, and every further call raises
|
|
43
|
+
`InterfaceError`. The driver never reconnects behind your back, because a
|
|
44
|
+
silent reconnect would drop transaction state; open a new connection (a
|
|
45
|
+
pool does this for you).
|
|
46
|
+
- **Bad `connect()` arguments raise `ProgrammingError`**, not `TypeError` /
|
|
47
|
+
`ValueError`, so configuration mistakes stay inside the `dbapi.Error`
|
|
48
|
+
hierarchy.
|
|
49
|
+
- **`rowcount` after SELECT is `len(rows)`.** Stdlib returns `-1` for queries.
|
|
50
|
+
dqlite knows the full result at execute time, so it reports the count.
|
|
51
|
+
`rowcount` stays `-1` for non-result paths, for all PRAGMA statements (read
|
|
52
|
+
or write — matching stdlib), and for a never-executed cursor.
|
|
53
|
+
|
|
54
|
+
## NULL in BOOLEAN/DATETIME columns depends on the server version
|
|
55
|
+
|
|
56
|
+
A 2026 dqlite server change (upstream commit `f30fc99`) changed how a NULL
|
|
57
|
+
cell is encoded in columns declared `BOOLEAN`, `DATE`, `DATETIME`, or
|
|
58
|
+
`TIMESTAMP`:
|
|
59
|
+
|
|
60
|
+
- **Before**: a NULL came back as `False` (for `BOOLEAN`) or `""` (for
|
|
61
|
+
datetime types) — indistinguishable from a real value.
|
|
62
|
+
- **After**: a NULL comes back as `None`.
|
|
63
|
+
|
|
64
|
+
There is no handshake that distinguishes the two server versions, and the
|
|
65
|
+
driver decodes faithfully whatever the server sends. Code like
|
|
66
|
+
`if row[0] is None:` will silently miss rows on an old cluster and start
|
|
67
|
+
matching after an upgrade. Check your dqlite cluster version before relying
|
|
68
|
+
on `is None` for these columns.
|
|
69
|
+
|
|
70
|
+
## Minor / edge-case differences
|
|
71
|
+
|
|
72
|
+
You can usually ignore these, but they are here so nothing surprises you:
|
|
73
|
+
|
|
74
|
+
- **`lastrowid` is `None` (not `0`) before the first INSERT.** After the
|
|
75
|
+
first INSERT both drivers agree and keep the value sticky across
|
|
76
|
+
UPDATE/DELETE/DDL.
|
|
77
|
+
- **`fetchmany(0)` returns `[]`** (PEP 249 read literally). Pass `None` or
|
|
78
|
+
omit the argument to default to `arraysize`. `fetchmany(negative)` raises
|
|
79
|
+
`ProgrammingError`.
|
|
80
|
+
- **`execute("")` raises `ProgrammingError`** (empty/whitespace/comment-only
|
|
81
|
+
SQL). Stdlib treats it as a no-op.
|
|
82
|
+
- **Empty result sets report the `UNKNOWN` type code** in
|
|
83
|
+
`description[i][1]` (importable from `dqlitedbapi`). Test it with
|
|
84
|
+
`type_code == UNKNOWN`; for column types on an empty result, query
|
|
85
|
+
`PRAGMA table_info(...)`.
|
|
86
|
+
- **CTE-prefixed DML** (`WITH ... INSERT/UPDATE/DELETE`) sets `lastrowid` and
|
|
87
|
+
reports `rowcount` as the affected-row count. That `rowcount` is a dqlite
|
|
88
|
+
extra: stdlib sqlite3 returns `-1` for CTE-prefixed DML (it counts only
|
|
89
|
+
non-CTE DML). `WITH ... SELECT` and `WITH ... RETURNING` return rows as usual.
|
|
90
|
+
|
|
91
|
+
### Type-code sentinels: compare with `==`, not `in {}`
|
|
92
|
+
|
|
93
|
+
The PEP 249 type sentinels (`STRING`, `BINARY`, `NUMBER`, `DATETIME`,
|
|
94
|
+
`ROWID`) match a `description` type code through `__eq__`, and each wraps
|
|
95
|
+
several wire types (`NUMBER` covers INTEGER+FLOAT+BOOLEAN; `DATETIME` covers
|
|
96
|
+
DATE+TIMESTAMP+ISO8601). A bare wire-int type code does not *hash* equal to a
|
|
97
|
+
sentinel, so set/dict membership silently returns `False`:
|
|
98
|
+
|
|
99
|
+
```python
|
|
100
|
+
type_code = cur.description[i][1]
|
|
101
|
+
if type_code == STRING or type_code == NUMBER: # correct
|
|
102
|
+
...
|
|
103
|
+
if type_code in {STRING, NUMBER}: # WRONG: silently False
|
|
104
|
+
...
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Stdlib `sqlite3` doesn't export these sentinels at all, so the
|
|
108
|
+
chained-equality form is also the cross-driver-portable idiom.
|