dqlite-dbapi 0.2.0__tar.gz → 0.2.2__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.0 → dqlite_dbapi-0.2.2}/PKG-INFO +16 -8
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/README.md +13 -5
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/pyproject.toml +3 -3
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/src/dqlitedbapi/__init__.py +76 -13
- dqlite_dbapi-0.2.2/src/dqlitedbapi/_busy_retry.py +297 -0
- dqlite_dbapi-0.2.2/src/dqlitedbapi/_pragma_intercept.py +264 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/src/dqlitedbapi/aio/__init__.py +84 -5
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/src/dqlitedbapi/aio/connection.py +199 -7
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/src/dqlitedbapi/aio/cursor.py +134 -20
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/src/dqlitedbapi/connection.py +551 -67
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/src/dqlitedbapi/cursor.py +243 -50
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/src/dqlitedbapi/exceptions.py +27 -0
- dqlite_dbapi-0.2.2/src/dqlitedbapi/row.py +93 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/src/dqlitedbapi/types.py +169 -20
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/integration/test_executemany_failure_resets_rowcount.py +11 -9
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/integration/test_misc_coverage.py +23 -15
- dqlite_dbapi-0.2.2/tests/integration/test_pragma_deny_list.py +122 -0
- dqlite_dbapi-0.2.2/tests/integration/test_session_mode_read_only.py +128 -0
- dqlite_dbapi-0.2.2/tests/test_aenter_cleanup_nulls_locks_under_concurrent_close.py +136 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_aexit_commit_cancel_debug_log.py +8 -0
- dqlite_dbapi-0.2.2/tests/test_aexit_commit_cancel_force_closes_transport.py +137 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_aexit_rollback_debug_log.py +8 -0
- dqlite_dbapi-0.2.2/tests/test_aio_execute_unlocked_silent_drop_scrubs_state.py +126 -0
- dqlite_dbapi-0.2.2/tests/test_aio_force_close_foreign_thread_cursor_closed_pre_cascade.py +123 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_aio_module_attributes.py +4 -1
- dqlite_dbapi-0.2.2/tests/test_aio_transaction_recheck_after_ensure_connection.py +96 -0
- dqlite_dbapi-0.2.2/tests/test_async_commit_rollback_timeout_outer_scope_hint.py +131 -0
- dqlite_dbapi-0.2.2/tests/test_async_resource_warning_sanitize_parity.py +83 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_audit_2026_05_coverage_gaps.py +15 -6
- dqlite_dbapi-0.2.2/tests/test_begin_rewrite_session_mode.py +210 -0
- dqlite_dbapi-0.2.2/tests/test_busy_retry_async_wiring.py +159 -0
- dqlite_dbapi-0.2.2/tests/test_busy_retry_classifier.py +97 -0
- dqlite_dbapi-0.2.2/tests/test_busy_retry_curve.py +149 -0
- dqlite_dbapi-0.2.2/tests/test_busy_retry_docstring_pins_cross_thread_caveat.py +61 -0
- dqlite_dbapi-0.2.2/tests/test_busy_retry_sync_wiring.py +140 -0
- dqlite_dbapi-0.2.2/tests/test_check_same_thread_kwarg.py +264 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_close_bypass_path_releases_latched_op_lock.py +5 -1
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_close_signal_handler_reentry.py +7 -2
- dqlite_dbapi-0.2.2/tests/test_connect_busy_timeout_kwarg.py +125 -0
- dqlite_dbapi-0.2.2/tests/test_connection_docstring_pins_check_same_thread_contract.py +106 -0
- dqlite_dbapi-0.2.2/tests/test_convert_params_conform_raise_propagates_unwrapped.py +81 -0
- dqlite_dbapi-0.2.2/tests/test_cursor_arraysize_row_factory_setter_thread.py +129 -0
- dqlite_dbapi-0.2.2/tests/test_cursors_weakset_state_lock.py +142 -0
- dqlite_dbapi-0.2.2/tests/test_dbapi_type_hash_consistency.py +87 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_description_null_first_row_resolves.py +9 -3
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_description_null_type_code_to_none.py +19 -10
- dqlite_dbapi-0.2.2/tests/test_description_type_code_unknown_sentinel.py +69 -0
- dqlite_dbapi-0.2.2/tests/test_exceptions_all_symmetry.py +43 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_executemany_cancel_restores_pre_batch_lastrowid.py +31 -26
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_executemany_cancellederror_restores_pre_batch_lastrowid.py +13 -12
- dqlite_dbapi-0.2.2/tests/test_executemany_lastrowid_docstring_modern_stdlib.py +41 -0
- dqlite_dbapi-0.2.2/tests/test_executemany_lastrowid_mid_batch_preserves_in_batch.py +167 -0
- dqlite_dbapi-0.2.2/tests/test_executemany_push_skips_cascade_zeroed_cursor.py +77 -0
- dqlite_dbapi-0.2.2/tests/test_format_utc_offset_non_timedelta_guard.py +62 -0
- dqlite_dbapi-0.2.2/tests/test_get_async_connection_force_close_race.py +148 -0
- dqlite_dbapi-0.2.2/tests/test_in_transaction_property_under_check_same_thread.py +141 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_module_attributes.py +17 -2
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_module_level_stubs.py +20 -1
- dqlite_dbapi-0.2.2/tests/test_number_includes_boolean_documented.py +34 -0
- dqlite_dbapi-0.2.2/tests/test_pragma_busy_timeout_intercept.py +201 -0
- dqlite_dbapi-0.2.2/tests/test_register_adapter_positional_only_signature.py +38 -0
- dqlite_dbapi-0.2.2/tests/test_row_factory_sqlite3_equivalent.py +110 -0
- dqlite_dbapi-0.2.2/tests/test_rowcount_select_divergence_documented.py +47 -0
- dqlite_dbapi-0.2.2/tests/test_sync_close_op_lock_owner_aware_probe.py +100 -0
- dqlite_dbapi-0.2.2/tests/test_sync_commit_rollback_snapshot_inner_for_tier2.py +53 -0
- dqlite_dbapi-0.2.2/tests/test_sync_transaction_ctxmgr_owner_slot_busy_sentinel.py +55 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_sync_transaction_owner_token_bookkeeping.py +35 -16
- dqlite_dbapi-0.2.2/tests/test_text_bind_nul_byte_rejected_with_blob_hint.py +92 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_thread_safety_enforcement.py +19 -8
- dqlite_dbapi-0.2.2/tests/test_transaction_ctxmgr_state_lock.py +235 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_types_dbapi_compare.py +22 -21
- dqlite_dbapi-0.2.2/tests/test_unregister_adapter.py +93 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_validate_ticks_and_unixtime_reject_bool_and_non_numeric.py +28 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/uv.lock +3 -3
- dqlite_dbapi-0.2.0/tests/integration/test_pragma_deny_list.py +0 -60
- dqlite_dbapi-0.2.0/tests/test_dbapi_type_hash_consistency.py +0 -77
- dqlite_dbapi-0.2.0/tests/test_unregister_adapter.py +0 -50
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/.github/workflows/publish-to-pypi.yml +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/.gitignore +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/DEVELOPMENT.md +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/LICENSE.md +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/src/dqlitedbapi/_constants.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/src/dqlitedbapi/py.typed +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/__init__.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/aio/test_aio_version_final_annotated.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/aio/test_async_close_underlying_close_failed_logged.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/aio/test_async_connection_fork_guard.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/aio/test_async_iter_protocol_invariants.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/aio/test_async_unclosed_warning_skips_in_fork_child.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/aio/test_close_cancel_between_cursor_cascade_and_lock.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/aio/test_fetchmany_cancel_atomic.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/aio/test_messages_clear_on_closed.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/conftest.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/integration/__init__.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/integration/conftest.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/integration/test_async_concurrent_executemany_serialized.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/integration/test_async_cursor_concurrent_execute_rejected.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/integration/test_async_cursor_edge_branches.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/integration/test_async_fetchmany_cancel.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/integration/test_async_returning_zero_rows.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/integration/test_async_transaction_ctxmgr_rejects_stray_commit.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/integration/test_close_implicit_rollback_pep249.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/integration/test_concurrent_cursors_visibility.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/integration/test_constraint_extended_codes.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/integration/test_context_manager.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/integration/test_cursor_fetch_after_non_row_execute.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/integration/test_cursor_misc_invariant_pins.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/integration/test_cursor_row_index_after_fetchall.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/integration/test_datetime_conversion.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/integration/test_description_after_ddl_pragma.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/integration/test_description_survives_empty_fetchall.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/integration/test_executemany_cancel_mid_batch.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/integration/test_executemany_completed_iterations_observability.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/integration/test_executemany_cte_dml.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/integration/test_executemany_generator.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/integration/test_executemany_returning.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/integration/test_integrity_error.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/integration/test_lastrowid_autoincrement.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/integration/test_lastrowid_cursor_scoped.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/integration/test_lastrowid_returning.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/integration/test_lastrowid_survives_cross_cursor_rollback.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/integration/test_leader_redirect_live.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/integration/test_multiline_returning_rows.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/integration/test_nan_inf_float.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/integration/test_negative_zero_and_subnormal_float.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/integration/test_no_transaction_error_wording.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/integration/test_null_vs_empty_text_blob_three_way.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/integration/test_query_detection.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/integration/test_returning.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/integration/test_savepoint_dbapi.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/integration/test_savepoint_failure_responses.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/integration/test_sqlite_version_pin.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/integration/test_sync_transaction_ctxmgr_rejects_stray_commit.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_aconn_execute_with_parameters.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_aconn_factory_getters.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_aconnect_cleanup_close_shielded_against_outer_timeout.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_aconnect_cleanup_on_connect_failure.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_adapter_exceptions_wrapped_as_data_error.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_aenter_cleanup_preserves_original_under_outer_cancel.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_aexit_behavioural_no_close.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_aexit_rollback_cancel_body_context_pin.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_aio_commit_rollback_no_tx.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_aio_commit_rollback_no_tx_code_matrix.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_aio_commit_rollback_wrapping.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_aio_connection_finalizer_detached_on_cancel_close.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_aio_connection_finalizer_detached_on_close.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_aio_cursor_drain_rows.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_aio_cursor_row_factory_typeerror_wrap.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_aio_description_tuple_exported.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_aio_executemany_cursor_close_cascade.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_aio_force_close_transport_cursor_cascade.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_aio_init_reexports_diagnostic_prefixes.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_aio_loop_affinity_exc_class.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_aio_register_adapter_export.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_aio_register_converter_stubs.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_aio_row_factory_setter_loop_binding.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_aio_setters_clear_messages_pep249.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_arraysize_validation.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_async_backup_not_async_def.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_async_check_loop_only_pid_before_loop.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_async_close_cancel_arm_cancels_inner_pending_drain.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_async_close_ensure_locks_guard.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_async_close_finally_propagates_cancel.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_async_close_op_lock_bounded.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_async_close_outer_cancel_force_closes_transport.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_async_close_race.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_async_commit_rollback_force_close_on_outer_cancel.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_async_commit_rollback_in_transaction_under_lock.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_async_commit_rollback_loop_binding_first.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_async_commit_rollback_messages_under_lock.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_async_commit_rollback_op_lock_bounded.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_async_commit_rollback_phase_budget.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_async_commit_rollback_retry_after_invalidate.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_async_commit_rollback_timeout_phase_aware.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_async_connect_cross_loop_diagnostic_on_fast_path.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_async_connection.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_async_connection_close_during_connect.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_async_connection_cursor_fork_guard.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_async_connection_ensure_connection_post_yield_close_shielded.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_async_connection_race.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_async_connection_setters_loop_binding.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_async_connection_transaction.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_async_cursor.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_async_cursor_aclose_alias.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_async_cursor_add_races_cascade_clear.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_async_cursor_aenter_cross_task_executing_guard.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_async_cursor_aenter_gc_parent_pep249.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_async_cursor_aiter_aenter_pep249_pins.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_async_cursor_aiter_cross_loop_fail_fast.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_async_cursor_arraysize_row_factory_loop_binding.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_async_cursor_callproc_nextset_scroll_loop_binding.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_async_cursor_close_clears_executing_task.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_async_cursor_close_is_sync_and_race_safe.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_async_cursor_close_lock_free.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_async_cursor_closed_state_pep249.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_async_cursor_execute_executing_task_inside_try_frame.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_async_cursor_execute_messages_clear.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_async_cursor_executemany_validation_clears_executing_task.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_async_cursor_fetch_loop_binding.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_async_cursor_finally_clear_executing_task_unconditional.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_async_cursor_late_add_proxy_swap.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_async_cursor_loop_binding.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_async_cursor_rownumber_closed_cross_loop.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_async_cursor_setinputsizes_loop_binding.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_async_executemany_atomic.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_async_executemany_close_race.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_async_executescript_stub_raises_on_call.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_async_fetch_does_not_lazy_bind_loop.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_async_fetchall_row_factory_happy_path.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_async_force_close_transport.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_async_in_transaction_closed_foreign_loop.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_async_in_transaction_loop_binding.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_async_invalidated_property.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_async_lock_binding.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_async_resource_warning_gate.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_async_secondary_methods_clear_messages_before_guards.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_async_transaction_owner_set_inside_try_frame.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_async_with_exit_does_not_close.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_audit_2026_05_dbapi_coverage.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_autocommit_doc_no_round_trip.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_autocommit_legacy_transaction_control.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_autocommit_property.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_autocommit_setter_exact_int_gate.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_binary_typeerror_stdlib_parity.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_bind_typeerror_as_dataerror.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_build_and_connect_base_exception_group_wrap.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_build_and_connect_resolve_leader_oserror_wrap.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_build_and_connect_wire_encode_error_translation.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_call_client_base_exception_group_wrap.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_call_client_baseexceptiongroup_cancel_split.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_call_client_docstring_mapping_table.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_call_client_mapping.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_call_client_typeerror_message_does_not_blame_bind.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_call_client_wire_encode_error.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_callproc_noreturn_annotation.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_callproc_scroll_clear_messages.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_cascade_cursors_clears_messages.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_classify_caller_sql.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_classify_caller_sql_hardening.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_classify_caller_sql_structural_reject_precedence.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_classify_operational_code_zero.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_cleanup_loop_thread_disarms_inner_and_reaps_drain.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_cleanup_loop_thread_finalizer_fork_safe.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_close_resource_leak.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_close_surfaces_op_lock_contention.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_close_thread_join_uses_close_timeout.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_close_timeout_floor_enforcement.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_close_timeout_plumbing.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_closed_property.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_cluster_policy_rejection_prefix_constant.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_code_to_exception_wire_ssot.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_collated_coverage_gaps.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_commit_after_external_close.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_commit_leader_flip_ambiguous_commit_error.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_commit_leader_flip_propagates.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_commit_no_spurious_connect.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_commit_rollback_shortcircuit_uses_in_transaction.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_conform_adapter_non_primitive_rejected_at_dbapi_layer.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_conftest_adapter_registry_isolation.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_connect_baseexceptiongroup_cancel_split.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_connect_clears_messages.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_connect_closed_precedes_thread_affinity.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_connect_docstring_parity.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_connect_iso_autocommit_round_trip.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_connect_isolation_level_autocommit_kwargs_symmetric.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_connect_preserves_code.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_connection.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_connection_after_fork_raises.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_connection_autocommit_isolation_level_closed_state.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_connection_close_cascades_cursors.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_connection_close_cursor_cascade_atomicity.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_connection_close_idempotent_cross_thread.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_connection_closed_checks.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_connection_cursor_class_attribute.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_connection_cursor_closed_state_precedes_unknown_kwarg.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_connection_database_empty_string_rejected.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_connection_database_kwarg_isinstance.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_connection_eager_address_validation.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_connection_enter_eager.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_connection_executemany_outer_shape_check.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_connection_exit_commit_ki_breadcrumb.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_connection_exit_short_circuits_on_closed.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_connection_messages_cleared_by_cursor_methods.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_connection_row_factory_setter_closed_check.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_context_manager_exit.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_convert_bind_param_conform_exception_propagates.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_cte_prefix_stripper_edges.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_cursor.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_cursor_after_external_close.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_cursor_close_clears_messages.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_cursor_close_cross_thread.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_cursor_close_preserves_body_exception_on_messages_strip.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_cursor_close_releases_connection_pin.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_cursor_closed_after_connection_gc_pep249.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_cursor_closed_state_pep249.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_cursor_completed_iterations_resets_on_single_execute.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_cursor_connection_partial_init_attributeerror.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_cursor_connection_property_after_gc_pep249.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_cursor_execute_non_str_operation.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_cursor_execute_state_reset_on_failure.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_cursor_executemany_non_str_operation.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_cursor_executemany_outer_shape_check.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_cursor_factory_kwarg_rejected.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_cursor_force_close_race.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_cursor_iter_clears_messages_pep249.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_cursor_iterator_reset.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_cursor_kwargs_rejection_precedence.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_cursor_next_row_factory_raise_replays_row.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_cursor_per_row_types.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_cursor_post_close_property_reads.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_cursor_protocol_check.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_cursor_rejects_generator_params.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_cursor_repr_with_gced_parent_connection.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_cursor_row_factory_rejecting_setter.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_cursor_row_factory_subclass_inheritance.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_cursor_row_index_reset.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_cursor_rowcount_after_ddl_stdlib_parity.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_cursor_rownumber_affinity.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_cursor_rownumber_post_close.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_cursor_scroll_mode_validation.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_cursor_secondary_methods_positional_only.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_cursor_setinputsizes_affinity_precedes_shape.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_cursor_setinputsizes_setoutputsize_closed_state_precedes_validation.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_cursor_setters_clear_messages_pep249.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_cursor_stop_iteration_contract.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_cursor_uses_run_protocol.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_date_widens_to_datetime.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_datetime_from_iso8601_docstring_two_step_fallback.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_datetime_time_round_trip.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_dbapi_commit_deferred_fk.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_dbapi_default_timeouts_use_client_constants.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_dbapi_exception_message_arg_cap.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_dbapi_exception_raw_message_cap.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_dbapi_fork_guard_cluster.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_dbapi_hardening.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_description_strict_type_codes.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_description_tuple_public_alias.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_diagnostic_messages.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_dial_attempt_timeout_propagation.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_dial_func_propagation.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_dial_func_propagation_to_leader_resolve.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_doc_class_and_description_drift.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_dqlite_namespace_error_classification.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_ensure_loop_fast_path_snapshot_dcl_defence.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_ensure_loop_post_fork_pid_guard.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_error_code_mapping.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_error_pickle_cause_either_or.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_error_pickle_preserves_raw_message.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_exception_coded_mixin.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_exception_mapping.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_exception_repr_code.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_exception_wrapping.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_exceptions.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_execute_caller_param_shape_preserves_prior_state.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_execute_executemany_positional_only.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_execute_multi_statement_rejection.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_execute_reject_scrubs_prior_result_state.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_executemany_accumulator_semantics.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_executemany_basecaught_state_reset.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_executemany_classifier_resets_state_first.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_executemany_classify_caller_sql_hoisted.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_executemany_completed_iterations_snapshot_conditional_restore.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_executemany_dict_subclass_accepted.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_executemany_empty.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_executemany_empty_batch_preserves_lastrowid.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_executemany_none_seq_rejected.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_executemany_per_iter_structural_rejects.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_executemany_pragma_with_leading_comment.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_executemany_reject_scrubs_prior_result_state.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_executemany_rejects_pure_queries.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_executemany_rejects_transaction_verbs.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_executemany_returning_cap.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_exit_logs_rollback_failure.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_fetchall_row_factory_raise_replay.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_fetchmany_edges.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_fetchmany_negative_size_rejected.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_fetchmany_non_int_validation.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_fetchmany_one_clear_per_call_pep249.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_fetchmany_row_factory_raise_replay.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_fetchmany_zero_docstring_stdlib_divergence.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_finalizer_strict_warnings.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_force_close_cancel_and_observe_narrow_suppress.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_force_close_observe_ast_pin.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_force_close_observe_callback_coverage.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_force_close_transport_cleanup_tail_unconditional.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_force_close_transport_clears_messages.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_force_close_transport_cross_thread_cancel_threadsafe.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_force_close_transport_cursor_cascade_threadsafe.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_force_close_transport_defensive_arms.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_force_close_transport_disarms_inner_finalizer.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_force_close_transport_pending_drain_resnapshot.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_force_close_transport_writer_cross_thread.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_fork_diagnostic_includes_pids.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_fromticks_tz_policy.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_gc_cleanup.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_get_resolve_leader_cluster_no_loop_raises_runtime_error.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_in_transaction_after_close.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_in_transaction_closed_divergence_doc.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_in_transaction_closed_foreign_thread.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_in_transaction_property.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_init_docstring_fromticks_three_impls.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_is_multi_statement_walker.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_is_no_transaction_error.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_is_row_returning_multiline_whitespace.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_is_row_returning_sql_noise.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_iso8601_aware_microseconds_round_trip.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_iso8601_decoder_leap_seconds_rejected.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_iso8601_decoder_subsecond_truncation.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_iso8601_encoder.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_iso8601_encoder_fold.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_iso8601_encoder_utcoffset_exception_wrap.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_iso8601_parse_error_truncation.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_iso8601_tzinfo_exception_classes_wrapped.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_isolation_level_stdlib_accept_set.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_iterdump_stub_signature_3_13_filter_kwarg.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_lastrowid_docstring_stdlib_parity_claim.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_lastrowid_insert_only.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_lastrowid_signed_int64_cast.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_leader_error_codes_identity.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_leader_redirect_on_connect.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_legacy_transaction_control_constant.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_max_continuation_frames_upper_bound.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_max_message_size_round_trip.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_max_total_rows_plumbing.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_max_total_rows_validation.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_messages_attribute.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_messages_clear_on_ctxmgr_entry.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_nextset_noreturn_annotation.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_no_transaction_substrings_source_of_truth.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_no_tx_primary_codes_invariant.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_op_lock_bounded_acquire.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_operational_error_classification.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_operational_error_raw_message.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_param_conversion.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_param_validation.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_paramstyle_qmark_only_pin.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_parse_decltypes_colnames_constants.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_pep249_database_error_routing.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_pep249_exception_aliases.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_pep249_messages_clearing.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_pep249_messages_independence.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_pep249_misc_pins.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_pep249_stub_hasattr_divergence.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_pep249_type_constructors_vs_singletons.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_pep249_type_constructors_wrap_valueerror.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_pep249_type_objects_final_annotated.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_pickle_guard.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_pragma_write_description_none.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_prepare_protocol_in_types_all.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_property_round_trip_and_fork_guard.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_property_setters_do_not_mirror_to_inner_async_conn.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_protocol_serialization.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_pyproject_dependency_graph.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_readme_truthfulness_pins.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_register_adapter_conform_fallback.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_register_adapter_global_sync_aio_symmetry.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_repr.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_repr_sanitizes_address.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_resolve_leader_cache_dial_func_id_recycle.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_resolve_leader_cache_lock_atfork_hook.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_resolve_leader_cluster_cache.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_resolve_leader_cross_loop_isolation.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_row_factory_annotation.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_row_factory_sqlite3_row_surfaces_dataerror.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_rowid_number_overlap_documented.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_rownumber_closed_cursor_returns_none_documented.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_run_sync_ensure_loop_raise_closes_coroutine.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_run_sync_keyboard_interrupt.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_run_sync_keyboard_interrupt_cleanup_paths.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_run_sync_ki_post_schedule_pre_await.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_run_sync_ki_race_recovery.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_run_sync_loop_closed_during_schedule.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_run_sync_other_runtimeerror_propagates.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_run_sync_timeout.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_run_sync_timeout_recovery_preserves_conn.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_secondary_methods_clear_messages_before_thread_check.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_setinputsizes_accepts_none.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_setinputsizes_clears_messages_per_pep249.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_setinputsizes_non_sequence_iterable.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_setinputsizes_sequence_acceptance.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_setinputsizes_short_circuit_on_closed_connection.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_setoutputsize_accepts_none.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_setter_closed_state_precedes_thread.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_sqlite_errorname_property.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_sqlite_version_not_public_attribute.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_strip_leading_comments_bom.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_strip_leading_comments_parity.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_strip_sql_noise_backtick_identifier.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_stub_signatures_normalize_to_dbapi_error.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_submodule_all.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_sync_commit_rollback_closed_precedes_thread.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_sync_commit_rollback_inside_ctxmgr_guard.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_sync_commit_rollback_messages_under_lock.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_sync_commit_rollback_retry_after_invalidate.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_sync_connection_execute_shortcut.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_sync_connection_executemany_shortcut.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_sync_cursor_post_add_closed_recheck.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_sync_exit_signal_mid_rollback.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_sync_fetchmany_cancel_atomic.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_sync_force_close_transport.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_sync_force_close_transport_pending_drain_reap.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_sync_invalidated_property.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_sync_run_sync_multiphase_timeout.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_timeout_validation.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_top_level_logger_has_null_handler.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_total_changes_hasattr_safe.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_transitive_wire_import.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_type_coercion_documented_asymmetries.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_types_validator_branches.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_unregister_adapter_rejects_built_in_datetime_types.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_unsupported_method_stubs.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_validate_positive_int_pep249_wrap.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_validate_ticks.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_version.py +0 -0
- {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.2}/tests/test_version_export.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dqlite-dbapi
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.2
|
|
4
4
|
Summary: PEP 249 (DB-API 2.0) compliant interface for dqlite
|
|
5
5
|
Project-URL: Homepage, https://github.com/letsdiscodev/python-dqlite-dbapi
|
|
6
6
|
Project-URL: Repository, https://github.com/letsdiscodev/python-dqlite-dbapi
|
|
@@ -22,8 +22,8 @@ Classifier: Topic :: Database :: Database Engines/Servers
|
|
|
22
22
|
Classifier: Topic :: Database :: Front-Ends
|
|
23
23
|
Classifier: Typing :: Typed
|
|
24
24
|
Requires-Python: >=3.13
|
|
25
|
-
Requires-Dist: dqlite-client<0.3.0,>=0.2.
|
|
26
|
-
Requires-Dist: dqlite-wire<0.3.0,>=0.2.
|
|
25
|
+
Requires-Dist: dqlite-client<0.3.0,>=0.2.2
|
|
26
|
+
Requires-Dist: dqlite-wire<0.3.0,>=0.2.2
|
|
27
27
|
Provides-Extra: dev
|
|
28
28
|
Requires-Dist: mypy>=1.0; extra == 'dev'
|
|
29
29
|
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
|
|
@@ -73,7 +73,9 @@ asyncio.run(main())
|
|
|
73
73
|
## PEP 249 Compliance
|
|
74
74
|
|
|
75
75
|
- `apilevel = "2.0"`
|
|
76
|
-
- `threadsafety =
|
|
76
|
+
- `threadsafety = 2` (capability ceiling; default `check_same_thread=True`
|
|
77
|
+
enforces strict per-thread — matches stdlib sqlite3's "advertise
|
|
78
|
+
ceiling, default to floor" pattern)
|
|
77
79
|
- `paramstyle = "qmark"`
|
|
78
80
|
|
|
79
81
|
## Transactions
|
|
@@ -240,10 +242,16 @@ borrowed from one.
|
|
|
240
242
|
stdlib silently accepts empty / whitespace-only / comment-only SQL
|
|
241
243
|
as a no-op; the driver pre-flight rejects per PEP 249 §7 to surface
|
|
242
244
|
caller bugs at the call site.
|
|
243
|
-
- **`threadsafety =
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
245
|
+
- **`threadsafety = 2`** (stdlib reports `3` — we don't claim Tier 3
|
|
246
|
+
because cursors aren't shareable per the documented "share
|
|
247
|
+
connections, not cursors" contract). Tier 2 = "threads may share
|
|
248
|
+
the module AND connections"; matches the dbapi's
|
|
249
|
+
``check_same_thread=False`` opt-in behaviour. Default
|
|
250
|
+
``check_same_thread=True`` enforces strict per-thread; methods
|
|
251
|
+
called from a foreign thread on a default-mode Connection raise
|
|
252
|
+
``ProgrammingError``. Tier 3 (cursor sharing) is tracked as
|
|
253
|
+
future work in
|
|
254
|
+
``issues/dbapi-threadsafety-tier-3-cursor-sharing-stdlib-parity.md``.
|
|
247
255
|
- **No `executescript` / `create_function` / `create_aggregate` /
|
|
248
256
|
`create_window_function` / `iterdump` / `backup` / `set_authorizer`
|
|
249
257
|
/ `serialize` / `blobopen`.** stdlib-specific APIs that have no
|
|
@@ -39,7 +39,9 @@ asyncio.run(main())
|
|
|
39
39
|
## PEP 249 Compliance
|
|
40
40
|
|
|
41
41
|
- `apilevel = "2.0"`
|
|
42
|
-
- `threadsafety =
|
|
42
|
+
- `threadsafety = 2` (capability ceiling; default `check_same_thread=True`
|
|
43
|
+
enforces strict per-thread — matches stdlib sqlite3's "advertise
|
|
44
|
+
ceiling, default to floor" pattern)
|
|
43
45
|
- `paramstyle = "qmark"`
|
|
44
46
|
|
|
45
47
|
## Transactions
|
|
@@ -206,10 +208,16 @@ borrowed from one.
|
|
|
206
208
|
stdlib silently accepts empty / whitespace-only / comment-only SQL
|
|
207
209
|
as a no-op; the driver pre-flight rejects per PEP 249 §7 to surface
|
|
208
210
|
caller bugs at the call site.
|
|
209
|
-
- **`threadsafety =
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
211
|
+
- **`threadsafety = 2`** (stdlib reports `3` — we don't claim Tier 3
|
|
212
|
+
because cursors aren't shareable per the documented "share
|
|
213
|
+
connections, not cursors" contract). Tier 2 = "threads may share
|
|
214
|
+
the module AND connections"; matches the dbapi's
|
|
215
|
+
``check_same_thread=False`` opt-in behaviour. Default
|
|
216
|
+
``check_same_thread=True`` enforces strict per-thread; methods
|
|
217
|
+
called from a foreign thread on a default-mode Connection raise
|
|
218
|
+
``ProgrammingError``. Tier 3 (cursor sharing) is tracked as
|
|
219
|
+
future work in
|
|
220
|
+
``issues/dbapi-threadsafety-tier-3-cursor-sharing-stdlib-parity.md``.
|
|
213
221
|
- **No `executescript` / `create_function` / `create_aggregate` /
|
|
214
222
|
`create_window_function` / `iterdump` / `backup` / `set_authorizer`
|
|
215
223
|
/ `serialize` / `blobopen`.** stdlib-specific APIs that have no
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "dqlite-dbapi"
|
|
7
|
-
version = "0.2.
|
|
7
|
+
version = "0.2.2"
|
|
8
8
|
description = "PEP 249 (DB-API 2.0) compliant interface for dqlite"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.13"
|
|
@@ -26,8 +26,8 @@ classifiers = [
|
|
|
26
26
|
"Typing :: Typed",
|
|
27
27
|
]
|
|
28
28
|
dependencies = [
|
|
29
|
-
"dqlite-client>=0.2.
|
|
30
|
-
"dqlite-wire>=0.2.
|
|
29
|
+
"dqlite-client>=0.2.2,<0.3.0",
|
|
30
|
+
"dqlite-wire>=0.2.2,<0.3.0",
|
|
31
31
|
]
|
|
32
32
|
|
|
33
33
|
[tool.uv.sources]
|
|
@@ -71,6 +71,7 @@ from dqlitedbapi.connection import (
|
|
|
71
71
|
)
|
|
72
72
|
from dqlitedbapi.cursor import Cursor
|
|
73
73
|
from dqlitedbapi.exceptions import (
|
|
74
|
+
AdapterLookupError,
|
|
74
75
|
AmbiguousCommitError,
|
|
75
76
|
DatabaseError,
|
|
76
77
|
DataError,
|
|
@@ -83,12 +84,14 @@ from dqlitedbapi.exceptions import (
|
|
|
83
84
|
ProgrammingError,
|
|
84
85
|
Warning,
|
|
85
86
|
)
|
|
87
|
+
from dqlitedbapi.row import Row
|
|
86
88
|
from dqlitedbapi.types import (
|
|
87
89
|
BINARY,
|
|
88
90
|
DATETIME,
|
|
89
91
|
NUMBER,
|
|
90
92
|
ROWID,
|
|
91
93
|
STRING,
|
|
94
|
+
UNKNOWN,
|
|
92
95
|
Binary,
|
|
93
96
|
Date,
|
|
94
97
|
DateFromTicks,
|
|
@@ -107,14 +110,38 @@ from dqlitewire import (
|
|
|
107
110
|
|
|
108
111
|
# PEP 249 module-level attributes
|
|
109
112
|
apilevel: _Final[_Literal["2.0"]] = "2.0"
|
|
110
|
-
# PEP 249 value
|
|
113
|
+
# PEP 249 value 2: threads may share the module and connections.
|
|
111
114
|
#
|
|
112
|
-
# This
|
|
113
|
-
#
|
|
114
|
-
#
|
|
115
|
-
#
|
|
116
|
-
#
|
|
117
|
-
|
|
115
|
+
# This declares the driver's CAPABILITY ceiling: connections are
|
|
116
|
+
# safe to share across threads once the user opts in via
|
|
117
|
+
# ``check_same_thread=False`` at ``connect()`` (matches stdlib
|
|
118
|
+
# sqlite3's convention — stdlib advertises ``threadsafety = 3``
|
|
119
|
+
# while defaulting ``check_same_thread=True`` to enforce strict
|
|
120
|
+
# per-thread at the Python layer). Cursors are explicitly NOT
|
|
121
|
+
# shareable across threads — the documented contract is "share
|
|
122
|
+
# connections, not cursors" (see ``Connection`` docstring); the
|
|
123
|
+
# ceiling stays at PEP 249 tier 2 for that reason. Tier 3 (cursors
|
|
124
|
+
# shareable too) is tracked as future work in
|
|
125
|
+
# ``issues/dbapi-threadsafety-tier-3-cursor-sharing-stdlib-parity.md``.
|
|
126
|
+
#
|
|
127
|
+
# DEFAULT enforcement is still per-thread: ``check_same_thread``
|
|
128
|
+
# defaults to ``True`` and ``Connection._check_thread()`` raises
|
|
129
|
+
# ``ProgrammingError`` on cross-thread method calls. Pass
|
|
130
|
+
# ``check_same_thread=False`` to use the connection sharing
|
|
131
|
+
# advertised here. The wire is always serialised by ``_op_lock``;
|
|
132
|
+
# the Phase 2 hardening (``_state_lock`` for transaction owner
|
|
133
|
+
# read-check-reserve, ``_cursors`` WeakSet add/discard) closes
|
|
134
|
+
# the cross-thread correctness gaps for the connection-shared
|
|
135
|
+
# path.
|
|
136
|
+
#
|
|
137
|
+
# This declaration uses the "advertise ceiling, default to floor"
|
|
138
|
+
# pattern stdlib sqlite3 established since CPython 3.11
|
|
139
|
+
# (bpo-45613 / gh-31464 made ``sqlite3.threadsafety`` dynamic via
|
|
140
|
+
# ``SQLITE_THREADSAFE`` mode; serialized = tier 3). The pattern
|
|
141
|
+
# describes capability rather than default enforcement;
|
|
142
|
+
# capability-detection libraries reading this value learn the
|
|
143
|
+
# safe-with-opt-in ceiling.
|
|
144
|
+
threadsafety: _Final[_Literal[2]] = 2
|
|
118
145
|
paramstyle: _Final[_Literal["qmark"]] = "qmark" # Question mark style: WHERE name=?
|
|
119
146
|
|
|
120
147
|
# SQLite compatibility attributes (for SQLAlchemy).
|
|
@@ -147,7 +174,7 @@ LEGACY_TRANSACTION_CONTROL: _Final[int] = -1
|
|
|
147
174
|
PARSE_DECLTYPES: _Final[int] = 1
|
|
148
175
|
PARSE_COLNAMES: _Final[int] = 2
|
|
149
176
|
|
|
150
|
-
__version__: _Final[str] = "0.2.
|
|
177
|
+
__version__: _Final[str] = "0.2.2"
|
|
151
178
|
|
|
152
179
|
__all__ = [ # grouped by PEP 249 section, not alphabetical
|
|
153
180
|
# Module attributes
|
|
@@ -190,6 +217,7 @@ __all__ = [ # grouped by PEP 249 section, not alphabetical
|
|
|
190
217
|
"InternalError",
|
|
191
218
|
"ProgrammingError",
|
|
192
219
|
"NotSupportedError",
|
|
220
|
+
"AdapterLookupError",
|
|
193
221
|
# Type constructors
|
|
194
222
|
"Date",
|
|
195
223
|
"Time",
|
|
@@ -204,6 +232,9 @@ __all__ = [ # grouped by PEP 249 section, not alphabetical
|
|
|
204
232
|
"NUMBER",
|
|
205
233
|
"DATETIME",
|
|
206
234
|
"ROWID",
|
|
235
|
+
"UNKNOWN",
|
|
236
|
+
# Row factory (sqlite3.Row equivalent)
|
|
237
|
+
"Row",
|
|
207
238
|
# Type aliases
|
|
208
239
|
"DescriptionTuple",
|
|
209
240
|
]
|
|
@@ -222,6 +253,9 @@ def connect(
|
|
|
222
253
|
dial_timeout: float | None = None,
|
|
223
254
|
attempt_timeout: float | None = None,
|
|
224
255
|
dial_func: DialFunc | None = None,
|
|
256
|
+
busy_timeout: float = 5.0,
|
|
257
|
+
check_same_thread: bool = True,
|
|
258
|
+
session_mode: str | None = None,
|
|
225
259
|
**unknown_kwargs: object,
|
|
226
260
|
) -> Connection:
|
|
227
261
|
"""Connect to a dqlite database.
|
|
@@ -269,6 +303,33 @@ def connect(
|
|
|
269
303
|
``asyncio.open_connection`` path. Forwarded to the
|
|
270
304
|
underlying :class:`Connection`. See
|
|
271
305
|
:data:`dqliteclient.DialFunc` for the protocol.
|
|
306
|
+
busy_timeout: Maximum cumulative seconds to spend retrying
|
|
307
|
+
BUSY responses before raising. Default ``5.0`` matches
|
|
308
|
+
stdlib ``sqlite3.connect(timeout=5.0)``. Retries follow
|
|
309
|
+
SQLite's deterministic ``sqliteDefaultBusyCallback``
|
|
310
|
+
curve (1, 2, 5, 10, 15, 20, 25, 25, 25, 50, 50, 100 ms
|
|
311
|
+
then flat 100 ms). ``0`` disables retry (first BUSY
|
|
312
|
+
raises immediately — stdlib parity). dqlite's VFS
|
|
313
|
+
authorizer rejects the canonical ``PRAGMA busy_timeout``
|
|
314
|
+
on the server side; this driver intercepts the PRAGMA at
|
|
315
|
+
the Cursor layer so ``cur.execute("PRAGMA busy_timeout
|
|
316
|
+
= 30000")`` works transparently and updates the same
|
|
317
|
+
backing field as the kwarg.
|
|
318
|
+
check_same_thread: When ``True`` (default), every method
|
|
319
|
+
call on the returned Connection must come from the
|
|
320
|
+
thread that called ``connect()``; cross-thread calls
|
|
321
|
+
raise ``ProgrammingError``. When ``False``, the cross-
|
|
322
|
+
thread check is gated off and the Connection can be
|
|
323
|
+
shared across threads — the wire is already serialised
|
|
324
|
+
by the per-Connection op-lock regardless of caller
|
|
325
|
+
thread. Per-cursor result state is NOT thread-safe; the
|
|
326
|
+
documented pattern is "share connections, not cursors."
|
|
327
|
+
Matches stdlib ``sqlite3.connect(check_same_thread=
|
|
328
|
+
False)``. The fork check is NEVER relaxed; cross-process
|
|
329
|
+
Connection use raises ``InterfaceError`` in both modes.
|
|
330
|
+
See ``dqlitedbapi.Connection`` docstring for the
|
|
331
|
+
cursor / messages / close() contracts under the
|
|
332
|
+
relaxation.
|
|
272
333
|
|
|
273
334
|
Returns:
|
|
274
335
|
A Connection object
|
|
@@ -323,11 +384,10 @@ def connect(
|
|
|
323
384
|
f"(stdlib LEGACY_TRANSACTION_CONTROL) only; got {autoc!r}"
|
|
324
385
|
)
|
|
325
386
|
|
|
326
|
-
# Reject stdlib ``sqlite3.connect`` kwargs that this driver
|
|
327
|
-
# cannot honour (``detect_types``, ``
|
|
328
|
-
# ``
|
|
329
|
-
# ``
|
|
330
|
-
# protocol leak ``TypeError`` (which escapes ``except
|
|
387
|
+
# Reject other stdlib ``sqlite3.connect`` kwargs that this driver
|
|
388
|
+
# cannot honour (``detect_types``, ``factory``, ``cached_statements``,
|
|
389
|
+
# ``uri``) with ``NotSupportedError`` rather than letting Python's
|
|
390
|
+
# call-protocol leak ``TypeError`` (which escapes ``except
|
|
331
391
|
# dbapi.Error:``). Cross-driver code that passes stdlib kwargs
|
|
332
392
|
# through should be able to catch the rejection inside the
|
|
333
393
|
# dbapi error hierarchy.
|
|
@@ -352,6 +412,9 @@ def connect(
|
|
|
352
412
|
dial_timeout=dial_timeout,
|
|
353
413
|
attempt_timeout=attempt_timeout,
|
|
354
414
|
dial_func=dial_func,
|
|
415
|
+
busy_timeout=busy_timeout,
|
|
416
|
+
check_same_thread=check_same_thread,
|
|
417
|
+
session_mode=session_mode,
|
|
355
418
|
)
|
|
356
419
|
# Apply the validated ``isolation_level`` / ``autocommit`` kwargs
|
|
357
420
|
# via the setters on the freshly-constructed connection. Stdlib
|
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
"""Server-BUSY retry mechanism — stdlib ``sqlite3`` parity.
|
|
2
|
+
|
|
3
|
+
Stdlib ``sqlite3.connect(timeout=N)`` defaults to a 5-second
|
|
4
|
+
``busy_timeout`` at the SQLite C library level. When a write hits
|
|
5
|
+
contention the C library transparently sleeps and retries via a
|
|
6
|
+
deterministic curve (``main.c::sqliteDefaultBusyCallback``). dqlite's
|
|
7
|
+
server-side VFS authorizer denies ``PRAGMA busy_timeout`` (see
|
|
8
|
+
``done/dbapi-pragma-deny-list-no-regression-pin.md``), so callers
|
|
9
|
+
cannot tune the retry behaviour through the canonical SQLite escape
|
|
10
|
+
hatch. Without an in-driver retry, application code that worked
|
|
11
|
+
against stdlib ``sqlite3`` silently regresses on dqlite — two
|
|
12
|
+
concurrent writers contending for the dqlite write lock produce
|
|
13
|
+
``OperationalError: database is locked to <addr>`` surfacing as HTTP
|
|
14
|
+
500s.
|
|
15
|
+
|
|
16
|
+
This module hosts the SQLite-equivalent retry curve and the
|
|
17
|
+
``OperationalError`` ↔ BUSY classifier so the sync and async surfaces
|
|
18
|
+
can share both. The actual retry-loop helpers live on the
|
|
19
|
+
``Connection`` / ``AsyncConnection`` classes (they need access to the
|
|
20
|
+
per-connection ``busy_timeout`` and the run-sync / run-async
|
|
21
|
+
bridges).
|
|
22
|
+
|
|
23
|
+
Design notes:
|
|
24
|
+
|
|
25
|
+
- The curve is SQLite's own (``delays`` / ``totals`` arrays from
|
|
26
|
+
``main.c``). Battle-tested. No jitter — BUSY is local contention,
|
|
27
|
+
not distributed-systems flapping; jitter would only smear the
|
|
28
|
+
retry burst across siblings without changing the convergence shape.
|
|
29
|
+
|
|
30
|
+
- The classifier gates on ``code == SQLITE_BUSY`` not on a substring
|
|
31
|
+
scan. The user-supplied brief referenced
|
|
32
|
+
``dqliteclient/connection.py:_RAFT_BUSY_MESSAGE_FRAGMENTS`` but
|
|
33
|
+
that tuple is narrow (``("checkpoint in progress",)``) and is the
|
|
34
|
+
classifier for the SEPARATE "checkpoint-BUSY rewrap as
|
|
35
|
+
``DqliteConnectionError``" path — NOT the RAFT-BUSY-"is locked"
|
|
36
|
+
family this retry targets. Code-based gating catches both engine-
|
|
37
|
+
BUSY and RAFT-BUSY shapes per the existing comment at
|
|
38
|
+
``dqliteclient/connection.py:2683-2698``.
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
from __future__ import annotations
|
|
42
|
+
|
|
43
|
+
import asyncio
|
|
44
|
+
import logging
|
|
45
|
+
import time
|
|
46
|
+
from collections.abc import Awaitable, Callable, Coroutine
|
|
47
|
+
from typing import Any, Final
|
|
48
|
+
|
|
49
|
+
from dqlitedbapi.exceptions import OperationalError as _OperationalError
|
|
50
|
+
from dqlitewire import SQLITE_BUSY as _SQLITE_BUSY
|
|
51
|
+
|
|
52
|
+
logger = logging.getLogger(__name__)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _resolve_busy_timeout_seconds(connection: object) -> float:
|
|
56
|
+
"""Read ``connection._busy_timeout`` with a MagicMock-safe
|
|
57
|
+
fallback. Test fixtures construct ``Cursor`` instances with
|
|
58
|
+
``MagicMock`` connections whose attributes auto-spawn — without
|
|
59
|
+
this guard the retry curve's ``int(busy_timeout * 1000)`` would
|
|
60
|
+
crash on a non-numeric MagicMock. ``isinstance`` check on the
|
|
61
|
+
return value falls through to ``0.0`` (no retry — preserves the
|
|
62
|
+
pre-feature test semantics)."""
|
|
63
|
+
value = getattr(connection, "_busy_timeout", 0.0)
|
|
64
|
+
# ``bool`` is ``int`` to ``isinstance`` but must be rejected
|
|
65
|
+
# explicitly; production code rejects bool at validation. A
|
|
66
|
+
# MagicMock attribute is not a real number — fall through.
|
|
67
|
+
if not isinstance(value, (int, float)) or isinstance(value, bool):
|
|
68
|
+
return 0.0
|
|
69
|
+
return float(value)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
# SQLite's busy-callback curve (``main.c::sqliteDefaultBusyCallback``).
|
|
73
|
+
# ``delays[i]`` is the milliseconds to sleep before retry attempt
|
|
74
|
+
# ``i+1``; ``totals[i]`` is the cumulative sleep BEFORE attempt
|
|
75
|
+
# ``i`` (so ``totals[i+1] == totals[i] + delays[i]``). After
|
|
76
|
+
# ``len(delays)`` attempts the curve flattens at the last
|
|
77
|
+
# ``delays`` value (100 ms) — see ``next_busy_delay_ms`` below.
|
|
78
|
+
#
|
|
79
|
+
# Pinned to the canonical SQLite values (verified internally
|
|
80
|
+
# consistent via the totals[i+1] = totals[i] + delays[i] check
|
|
81
|
+
# in ``tests/test_busy_retry_curve.py``). The constant 25 appears
|
|
82
|
+
# three times in a row at positions 6, 7, 8 — this is intentional
|
|
83
|
+
# in the upstream curve, not an editing mistake.
|
|
84
|
+
_BUSY_DELAYS_MS: Final[tuple[int, ...]] = (1, 2, 5, 10, 15, 20, 25, 25, 25, 50, 50, 100)
|
|
85
|
+
_BUSY_TOTALS_MS: Final[tuple[int, ...]] = (0, 1, 3, 8, 18, 33, 53, 78, 103, 128, 178, 228)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def next_busy_delay_ms(count: int, busy_timeout_ms: int) -> int | None:
|
|
89
|
+
"""Return the sleep duration (ms) before retry attempt ``count+1``,
|
|
90
|
+
or ``None`` when the cumulative budget is exhausted.
|
|
91
|
+
|
|
92
|
+
Mirrors SQLite's ``sqliteDefaultBusyCallback`` exactly:
|
|
93
|
+
|
|
94
|
+
- For ``count < len(_BUSY_DELAYS_MS)``, return the next entry from
|
|
95
|
+
the curve.
|
|
96
|
+
- For ``count >= len(_BUSY_DELAYS_MS)``, return the flat tail
|
|
97
|
+
value (100 ms).
|
|
98
|
+
- If the resulting ``prior + delay`` would overrun
|
|
99
|
+
``busy_timeout_ms``, clamp ``delay`` to fit; if the clamp would
|
|
100
|
+
produce ``delay <= 0``, return ``None`` (caller raises the
|
|
101
|
+
original BUSY).
|
|
102
|
+
|
|
103
|
+
``busy_timeout_ms <= 0`` short-circuits ``None`` regardless of
|
|
104
|
+
``count`` — stdlib parity for ``timeout=0`` meaning "no wait."
|
|
105
|
+
"""
|
|
106
|
+
if busy_timeout_ms <= 0:
|
|
107
|
+
return None
|
|
108
|
+
n_curve = len(_BUSY_DELAYS_MS)
|
|
109
|
+
if count < n_curve:
|
|
110
|
+
delay = _BUSY_DELAYS_MS[count]
|
|
111
|
+
prior = _BUSY_TOTALS_MS[count]
|
|
112
|
+
else:
|
|
113
|
+
delay = _BUSY_DELAYS_MS[-1]
|
|
114
|
+
prior = _BUSY_TOTALS_MS[-1] + delay * (count - (n_curve - 1))
|
|
115
|
+
if prior + delay > busy_timeout_ms:
|
|
116
|
+
delay = busy_timeout_ms - prior
|
|
117
|
+
if delay <= 0:
|
|
118
|
+
return None
|
|
119
|
+
return delay
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def is_busy_exception(exc: BaseException) -> bool:
|
|
123
|
+
"""Classify an exception as a retryable BUSY.
|
|
124
|
+
|
|
125
|
+
The retry mechanism gates on ``OperationalError`` whose ``code`` is
|
|
126
|
+
``SQLITE_BUSY (5)``. This covers both engine-BUSY (the SQLite-side
|
|
127
|
+
BUSY where the user can retry the failing statement and continue
|
|
128
|
+
the same tx) AND RAFT-BUSY translated by the server's gateway
|
|
129
|
+
(the in-flight write was not accepted; the server-side tx state
|
|
130
|
+
is the same as engine-BUSY from the client's perspective).
|
|
131
|
+
|
|
132
|
+
The two shapes are indistinguishable at the Python layer per the
|
|
133
|
+
existing comment at ``dqliteclient/connection.py:2683-2698``
|
|
134
|
+
("users must retry explicitly"). This retry IS that explicit
|
|
135
|
+
retry, applied uniformly across both BUSY origins.
|
|
136
|
+
|
|
137
|
+
Specifically NOT retried:
|
|
138
|
+
|
|
139
|
+
- ``DqliteConnectionError`` (the checkpoint-BUSY rewrap at
|
|
140
|
+
``dqliteclient/connection.py:2717``) — bypasses this classifier
|
|
141
|
+
because it's not an ``OperationalError`` subclass. The
|
|
142
|
+
checkpoint-BUSY case already triggers SA pool invalidation by
|
|
143
|
+
design (the connection's tx state was reset; retry on the same
|
|
144
|
+
connection is wrong).
|
|
145
|
+
- Other ``OperationalError`` codes (LEADER_ERROR_CODES,
|
|
146
|
+
INTERFACE_ERROR_CODES, etc.) — leader-flip and transport
|
|
147
|
+
faults have their own retry semantics elsewhere; this gate
|
|
148
|
+
narrows to BUSY only.
|
|
149
|
+
"""
|
|
150
|
+
if not isinstance(exc, _OperationalError):
|
|
151
|
+
return False
|
|
152
|
+
return getattr(exc, "code", None) == _SQLITE_BUSY
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def retry_sync_on_busy[T](
|
|
156
|
+
busy_timeout: float,
|
|
157
|
+
run_sync: Callable[[Coroutine[Any, Any, T]], T],
|
|
158
|
+
coro_factory: Callable[[], Coroutine[Any, Any, T]],
|
|
159
|
+
) -> T:
|
|
160
|
+
"""Module-level sync retry loop.
|
|
161
|
+
|
|
162
|
+
Caller provides ``run_sync`` (the function that drives the
|
|
163
|
+
coroutine on the connection's daemon loop — typically
|
|
164
|
+
``connection._run_sync``) and ``coro_factory`` (a callable that
|
|
165
|
+
returns a fresh coroutine each time the retry needs to re-submit).
|
|
166
|
+
Coroutines are single-use so the factory shape is mandatory.
|
|
167
|
+
|
|
168
|
+
Same retry shape as the inline-on-connection variants: SQLite-curve
|
|
169
|
+
sleeps, BUSY-only classification (``OperationalError(code=
|
|
170
|
+
SQLITE_BUSY)``), propagate non-BUSY exceptions unchanged.
|
|
171
|
+
``KeyboardInterrupt`` / ``SystemExit`` during ``time.sleep``
|
|
172
|
+
propagate (those are ``BaseException``, not caught by ``except
|
|
173
|
+
OperationalError``).
|
|
174
|
+
|
|
175
|
+
``busy_timeout <= 0`` short-circuits to a single ``run_sync``
|
|
176
|
+
call with no retry (stdlib parity for ``timeout=0``).
|
|
177
|
+
|
|
178
|
+
**Cross-thread caveat under** ``check_same_thread=False``:
|
|
179
|
+
|
|
180
|
+
The retry loop releases the connection's ``_op_lock`` between
|
|
181
|
+
attempts. ``run_sync`` acquires the lock at the START of each
|
|
182
|
+
attempt and releases it before returning; the ``time.sleep``
|
|
183
|
+
that follows runs OUTSIDE the lock so the sleep doesn't starve
|
|
184
|
+
sibling threads. By design — holding ``_op_lock`` across a
|
|
185
|
+
100ms sleep would defeat cross-thread sharing.
|
|
186
|
+
|
|
187
|
+
On a Connection shared across threads via
|
|
188
|
+
``check_same_thread=False``, a BUSY-retried statement may
|
|
189
|
+
observe interleaved writes from sibling threads between retry
|
|
190
|
+
attempts. Example:
|
|
191
|
+
|
|
192
|
+
Thread A: cur.execute("UPDATE t SET v=v+1 WHERE k=?") -> BUSY
|
|
193
|
+
Thread A: sleeps for SQLite-curve delay (op_lock released)
|
|
194
|
+
Thread B: cur.execute("UPDATE t SET v=v+1 WHERE k=?") -> succeeds
|
|
195
|
+
Thread A: retries -> sees B's update; may succeed against
|
|
196
|
+
a different row state than the original attempt.
|
|
197
|
+
|
|
198
|
+
This is the correct semantic for retry-on-contention (each
|
|
199
|
+
retry sees current state, the same as it would on a fresh
|
|
200
|
+
connection). Callers who need to retry against an atomic
|
|
201
|
+
snapshot must wrap in an explicit transaction:
|
|
202
|
+
|
|
203
|
+
with conn.transaction():
|
|
204
|
+
cur.execute("UPDATE t SET v=v+1 WHERE k=?", (k,))
|
|
205
|
+
|
|
206
|
+
The transaction holds the wire lock across the entire body, so
|
|
207
|
+
sibling threads cannot interleave statements between the retry
|
|
208
|
+
attempts inside that body.
|
|
209
|
+
"""
|
|
210
|
+
busy_timeout_ms = int(busy_timeout * 1000)
|
|
211
|
+
if busy_timeout_ms <= 0:
|
|
212
|
+
return run_sync(coro_factory())
|
|
213
|
+
count = 0
|
|
214
|
+
while True:
|
|
215
|
+
try:
|
|
216
|
+
return run_sync(coro_factory())
|
|
217
|
+
except _OperationalError as exc:
|
|
218
|
+
if not is_busy_exception(exc):
|
|
219
|
+
raise
|
|
220
|
+
delay_ms = next_busy_delay_ms(count, busy_timeout_ms)
|
|
221
|
+
if delay_ms is None:
|
|
222
|
+
logger.warning(
|
|
223
|
+
"busy_timeout budget %.3fs exhausted after %d retries; raising original BUSY",
|
|
224
|
+
busy_timeout,
|
|
225
|
+
count,
|
|
226
|
+
)
|
|
227
|
+
raise
|
|
228
|
+
logger.debug(
|
|
229
|
+
"BUSY retry attempt %d: sleeping %dms (budget %.3fs)",
|
|
230
|
+
count,
|
|
231
|
+
delay_ms,
|
|
232
|
+
busy_timeout,
|
|
233
|
+
)
|
|
234
|
+
time.sleep(delay_ms / 1000.0)
|
|
235
|
+
count += 1
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
async def retry_async_on_busy[T](
|
|
239
|
+
busy_timeout: float,
|
|
240
|
+
coro_factory: Callable[[], Awaitable[T]],
|
|
241
|
+
) -> T:
|
|
242
|
+
"""Module-level async retry loop. Used by the per-iteration
|
|
243
|
+
executemany path that cannot route through
|
|
244
|
+
``Connection._await_with_busy_retry`` because test fixtures
|
|
245
|
+
construct ``Cursor`` instances with a ``MagicMock`` connection
|
|
246
|
+
that does not have that method.
|
|
247
|
+
|
|
248
|
+
Same retry shape as the connection-method variants: SQLite-curve
|
|
249
|
+
sleeps, BUSY-only classification, propagate non-BUSY unchanged.
|
|
250
|
+
``CancelledError`` propagates through ``await asyncio.sleep``
|
|
251
|
+
because the ``except OperationalError`` arm does not catch
|
|
252
|
+
``BaseException``.
|
|
253
|
+
|
|
254
|
+
``busy_timeout <= 0`` short-circuits to a single awaitable call
|
|
255
|
+
with no retry (stdlib parity).
|
|
256
|
+
|
|
257
|
+
**Cross-task caveat**:
|
|
258
|
+
|
|
259
|
+
Mirrors the sync sibling's cross-thread caveat: the retry loop
|
|
260
|
+
releases any wire-level lock (the inner ``DqliteConnection``'s
|
|
261
|
+
op_lock or the AsyncConnection's op_lock) between attempts.
|
|
262
|
+
``await asyncio.sleep`` runs OUTSIDE the lock so siblings can
|
|
263
|
+
make progress; by design.
|
|
264
|
+
|
|
265
|
+
On an AsyncConnection shared across asyncio tasks on the same
|
|
266
|
+
loop, a BUSY-retried statement may observe interleaved writes
|
|
267
|
+
from sibling tasks between retry attempts. Same shape as the
|
|
268
|
+
sync sibling: each retry sees current state. Callers who need
|
|
269
|
+
to retry against an atomic snapshot must wrap in an explicit
|
|
270
|
+
``async with conn.transaction():`` block.
|
|
271
|
+
"""
|
|
272
|
+
busy_timeout_ms = int(busy_timeout * 1000)
|
|
273
|
+
if busy_timeout_ms <= 0:
|
|
274
|
+
return await coro_factory()
|
|
275
|
+
count = 0
|
|
276
|
+
while True:
|
|
277
|
+
try:
|
|
278
|
+
return await coro_factory()
|
|
279
|
+
except _OperationalError as exc:
|
|
280
|
+
if not is_busy_exception(exc):
|
|
281
|
+
raise
|
|
282
|
+
delay_ms = next_busy_delay_ms(count, busy_timeout_ms)
|
|
283
|
+
if delay_ms is None:
|
|
284
|
+
logger.warning(
|
|
285
|
+
"busy_timeout budget %.3fs exhausted after %d retries; raising original BUSY",
|
|
286
|
+
busy_timeout,
|
|
287
|
+
count,
|
|
288
|
+
)
|
|
289
|
+
raise
|
|
290
|
+
logger.debug(
|
|
291
|
+
"BUSY retry attempt %d: sleeping %dms (budget %.3fs)",
|
|
292
|
+
count,
|
|
293
|
+
delay_ms,
|
|
294
|
+
busy_timeout,
|
|
295
|
+
)
|
|
296
|
+
await asyncio.sleep(delay_ms / 1000.0)
|
|
297
|
+
count += 1
|