dqlite-dbapi 0.2.0__tar.gz → 0.2.1__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.
Files changed (487) hide show
  1. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/PKG-INFO +16 -8
  2. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/README.md +13 -5
  3. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/pyproject.toml +3 -3
  4. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/src/dqlitedbapi/__init__.py +67 -13
  5. dqlite_dbapi-0.2.1/src/dqlitedbapi/_busy_retry.py +297 -0
  6. dqlite_dbapi-0.2.1/src/dqlitedbapi/_pragma_intercept.py +106 -0
  7. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/src/dqlitedbapi/aio/__init__.py +58 -5
  8. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/src/dqlitedbapi/aio/connection.py +57 -5
  9. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/src/dqlitedbapi/aio/cursor.py +53 -3
  10. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/src/dqlitedbapi/connection.py +332 -39
  11. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/src/dqlitedbapi/cursor.py +65 -3
  12. dqlite_dbapi-0.2.1/tests/integration/test_pragma_deny_list.py +122 -0
  13. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_aio_module_attributes.py +4 -1
  14. dqlite_dbapi-0.2.1/tests/test_async_resource_warning_sanitize_parity.py +83 -0
  15. dqlite_dbapi-0.2.1/tests/test_busy_retry_async_wiring.py +159 -0
  16. dqlite_dbapi-0.2.1/tests/test_busy_retry_classifier.py +97 -0
  17. dqlite_dbapi-0.2.1/tests/test_busy_retry_curve.py +149 -0
  18. dqlite_dbapi-0.2.1/tests/test_busy_retry_docstring_pins_cross_thread_caveat.py +61 -0
  19. dqlite_dbapi-0.2.1/tests/test_busy_retry_sync_wiring.py +140 -0
  20. dqlite_dbapi-0.2.1/tests/test_check_same_thread_kwarg.py +264 -0
  21. dqlite_dbapi-0.2.1/tests/test_connect_busy_timeout_kwarg.py +125 -0
  22. dqlite_dbapi-0.2.1/tests/test_connection_docstring_pins_check_same_thread_contract.py +106 -0
  23. dqlite_dbapi-0.2.1/tests/test_cursors_weakset_state_lock.py +142 -0
  24. dqlite_dbapi-0.2.1/tests/test_in_transaction_property_under_check_same_thread.py +141 -0
  25. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_module_attributes.py +14 -2
  26. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_module_level_stubs.py +20 -1
  27. dqlite_dbapi-0.2.1/tests/test_pragma_busy_timeout_intercept.py +201 -0
  28. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_sync_transaction_owner_token_bookkeeping.py +24 -10
  29. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_thread_safety_enforcement.py +19 -8
  30. dqlite_dbapi-0.2.1/tests/test_transaction_ctxmgr_state_lock.py +227 -0
  31. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/uv.lock +3 -3
  32. dqlite_dbapi-0.2.0/tests/integration/test_pragma_deny_list.py +0 -60
  33. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/.github/workflows/publish-to-pypi.yml +0 -0
  34. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/.gitignore +0 -0
  35. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/DEVELOPMENT.md +0 -0
  36. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/LICENSE.md +0 -0
  37. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/src/dqlitedbapi/_constants.py +0 -0
  38. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/src/dqlitedbapi/exceptions.py +0 -0
  39. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/src/dqlitedbapi/py.typed +0 -0
  40. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/src/dqlitedbapi/types.py +0 -0
  41. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/__init__.py +0 -0
  42. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/aio/test_aio_version_final_annotated.py +0 -0
  43. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/aio/test_async_close_underlying_close_failed_logged.py +0 -0
  44. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/aio/test_async_connection_fork_guard.py +0 -0
  45. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/aio/test_async_iter_protocol_invariants.py +0 -0
  46. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/aio/test_async_unclosed_warning_skips_in_fork_child.py +0 -0
  47. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/aio/test_close_cancel_between_cursor_cascade_and_lock.py +0 -0
  48. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/aio/test_fetchmany_cancel_atomic.py +0 -0
  49. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/aio/test_messages_clear_on_closed.py +0 -0
  50. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/conftest.py +0 -0
  51. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/integration/__init__.py +0 -0
  52. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/integration/conftest.py +0 -0
  53. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/integration/test_async_concurrent_executemany_serialized.py +0 -0
  54. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/integration/test_async_cursor_concurrent_execute_rejected.py +0 -0
  55. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/integration/test_async_cursor_edge_branches.py +0 -0
  56. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/integration/test_async_fetchmany_cancel.py +0 -0
  57. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/integration/test_async_returning_zero_rows.py +0 -0
  58. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/integration/test_async_transaction_ctxmgr_rejects_stray_commit.py +0 -0
  59. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/integration/test_close_implicit_rollback_pep249.py +0 -0
  60. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/integration/test_concurrent_cursors_visibility.py +0 -0
  61. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/integration/test_constraint_extended_codes.py +0 -0
  62. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/integration/test_context_manager.py +0 -0
  63. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/integration/test_cursor_fetch_after_non_row_execute.py +0 -0
  64. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/integration/test_cursor_misc_invariant_pins.py +0 -0
  65. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/integration/test_cursor_row_index_after_fetchall.py +0 -0
  66. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/integration/test_datetime_conversion.py +0 -0
  67. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/integration/test_description_after_ddl_pragma.py +0 -0
  68. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/integration/test_description_survives_empty_fetchall.py +0 -0
  69. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/integration/test_executemany_cancel_mid_batch.py +0 -0
  70. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/integration/test_executemany_completed_iterations_observability.py +0 -0
  71. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/integration/test_executemany_cte_dml.py +0 -0
  72. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/integration/test_executemany_failure_resets_rowcount.py +0 -0
  73. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/integration/test_executemany_generator.py +0 -0
  74. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/integration/test_executemany_returning.py +0 -0
  75. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/integration/test_integrity_error.py +0 -0
  76. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/integration/test_lastrowid_autoincrement.py +0 -0
  77. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/integration/test_lastrowid_cursor_scoped.py +0 -0
  78. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/integration/test_lastrowid_returning.py +0 -0
  79. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/integration/test_lastrowid_survives_cross_cursor_rollback.py +0 -0
  80. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/integration/test_leader_redirect_live.py +0 -0
  81. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/integration/test_misc_coverage.py +0 -0
  82. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/integration/test_multiline_returning_rows.py +0 -0
  83. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/integration/test_nan_inf_float.py +0 -0
  84. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/integration/test_negative_zero_and_subnormal_float.py +0 -0
  85. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/integration/test_no_transaction_error_wording.py +0 -0
  86. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/integration/test_null_vs_empty_text_blob_three_way.py +0 -0
  87. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/integration/test_query_detection.py +0 -0
  88. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/integration/test_returning.py +0 -0
  89. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/integration/test_savepoint_dbapi.py +0 -0
  90. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/integration/test_savepoint_failure_responses.py +0 -0
  91. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/integration/test_sqlite_version_pin.py +0 -0
  92. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/integration/test_sync_transaction_ctxmgr_rejects_stray_commit.py +0 -0
  93. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_aconn_execute_with_parameters.py +0 -0
  94. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_aconn_factory_getters.py +0 -0
  95. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_aconnect_cleanup_close_shielded_against_outer_timeout.py +0 -0
  96. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_aconnect_cleanup_on_connect_failure.py +0 -0
  97. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_adapter_exceptions_wrapped_as_data_error.py +0 -0
  98. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_aenter_cleanup_preserves_original_under_outer_cancel.py +0 -0
  99. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_aexit_behavioural_no_close.py +0 -0
  100. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_aexit_commit_cancel_debug_log.py +0 -0
  101. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_aexit_rollback_cancel_body_context_pin.py +0 -0
  102. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_aexit_rollback_debug_log.py +0 -0
  103. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_aio_commit_rollback_no_tx.py +0 -0
  104. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_aio_commit_rollback_no_tx_code_matrix.py +0 -0
  105. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_aio_commit_rollback_wrapping.py +0 -0
  106. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_aio_connection_finalizer_detached_on_cancel_close.py +0 -0
  107. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_aio_connection_finalizer_detached_on_close.py +0 -0
  108. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_aio_cursor_drain_rows.py +0 -0
  109. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_aio_cursor_row_factory_typeerror_wrap.py +0 -0
  110. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_aio_description_tuple_exported.py +0 -0
  111. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_aio_executemany_cursor_close_cascade.py +0 -0
  112. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_aio_force_close_transport_cursor_cascade.py +0 -0
  113. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_aio_init_reexports_diagnostic_prefixes.py +0 -0
  114. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_aio_loop_affinity_exc_class.py +0 -0
  115. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_aio_register_adapter_export.py +0 -0
  116. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_aio_register_converter_stubs.py +0 -0
  117. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_aio_row_factory_setter_loop_binding.py +0 -0
  118. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_aio_setters_clear_messages_pep249.py +0 -0
  119. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_arraysize_validation.py +0 -0
  120. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_async_backup_not_async_def.py +0 -0
  121. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_async_check_loop_only_pid_before_loop.py +0 -0
  122. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_async_close_cancel_arm_cancels_inner_pending_drain.py +0 -0
  123. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_async_close_ensure_locks_guard.py +0 -0
  124. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_async_close_finally_propagates_cancel.py +0 -0
  125. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_async_close_op_lock_bounded.py +0 -0
  126. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_async_close_outer_cancel_force_closes_transport.py +0 -0
  127. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_async_close_race.py +0 -0
  128. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_async_commit_rollback_force_close_on_outer_cancel.py +0 -0
  129. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_async_commit_rollback_in_transaction_under_lock.py +0 -0
  130. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_async_commit_rollback_loop_binding_first.py +0 -0
  131. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_async_commit_rollback_messages_under_lock.py +0 -0
  132. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_async_commit_rollback_op_lock_bounded.py +0 -0
  133. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_async_commit_rollback_phase_budget.py +0 -0
  134. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_async_commit_rollback_retry_after_invalidate.py +0 -0
  135. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_async_commit_rollback_timeout_phase_aware.py +0 -0
  136. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_async_connect_cross_loop_diagnostic_on_fast_path.py +0 -0
  137. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_async_connection.py +0 -0
  138. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_async_connection_close_during_connect.py +0 -0
  139. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_async_connection_cursor_fork_guard.py +0 -0
  140. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_async_connection_ensure_connection_post_yield_close_shielded.py +0 -0
  141. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_async_connection_race.py +0 -0
  142. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_async_connection_setters_loop_binding.py +0 -0
  143. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_async_connection_transaction.py +0 -0
  144. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_async_cursor.py +0 -0
  145. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_async_cursor_aclose_alias.py +0 -0
  146. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_async_cursor_add_races_cascade_clear.py +0 -0
  147. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_async_cursor_aenter_cross_task_executing_guard.py +0 -0
  148. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_async_cursor_aenter_gc_parent_pep249.py +0 -0
  149. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_async_cursor_aiter_aenter_pep249_pins.py +0 -0
  150. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_async_cursor_aiter_cross_loop_fail_fast.py +0 -0
  151. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_async_cursor_arraysize_row_factory_loop_binding.py +0 -0
  152. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_async_cursor_callproc_nextset_scroll_loop_binding.py +0 -0
  153. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_async_cursor_close_clears_executing_task.py +0 -0
  154. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_async_cursor_close_is_sync_and_race_safe.py +0 -0
  155. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_async_cursor_close_lock_free.py +0 -0
  156. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_async_cursor_closed_state_pep249.py +0 -0
  157. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_async_cursor_execute_executing_task_inside_try_frame.py +0 -0
  158. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_async_cursor_execute_messages_clear.py +0 -0
  159. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_async_cursor_executemany_validation_clears_executing_task.py +0 -0
  160. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_async_cursor_fetch_loop_binding.py +0 -0
  161. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_async_cursor_finally_clear_executing_task_unconditional.py +0 -0
  162. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_async_cursor_late_add_proxy_swap.py +0 -0
  163. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_async_cursor_loop_binding.py +0 -0
  164. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_async_cursor_rownumber_closed_cross_loop.py +0 -0
  165. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_async_cursor_setinputsizes_loop_binding.py +0 -0
  166. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_async_executemany_atomic.py +0 -0
  167. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_async_executemany_close_race.py +0 -0
  168. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_async_executescript_stub_raises_on_call.py +0 -0
  169. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_async_fetch_does_not_lazy_bind_loop.py +0 -0
  170. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_async_fetchall_row_factory_happy_path.py +0 -0
  171. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_async_force_close_transport.py +0 -0
  172. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_async_in_transaction_closed_foreign_loop.py +0 -0
  173. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_async_in_transaction_loop_binding.py +0 -0
  174. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_async_invalidated_property.py +0 -0
  175. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_async_lock_binding.py +0 -0
  176. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_async_resource_warning_gate.py +0 -0
  177. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_async_secondary_methods_clear_messages_before_guards.py +0 -0
  178. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_async_transaction_owner_set_inside_try_frame.py +0 -0
  179. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_async_with_exit_does_not_close.py +0 -0
  180. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_audit_2026_05_coverage_gaps.py +0 -0
  181. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_audit_2026_05_dbapi_coverage.py +0 -0
  182. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_autocommit_doc_no_round_trip.py +0 -0
  183. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_autocommit_legacy_transaction_control.py +0 -0
  184. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_autocommit_property.py +0 -0
  185. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_autocommit_setter_exact_int_gate.py +0 -0
  186. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_binary_typeerror_stdlib_parity.py +0 -0
  187. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_bind_typeerror_as_dataerror.py +0 -0
  188. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_build_and_connect_base_exception_group_wrap.py +0 -0
  189. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_build_and_connect_resolve_leader_oserror_wrap.py +0 -0
  190. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_build_and_connect_wire_encode_error_translation.py +0 -0
  191. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_call_client_base_exception_group_wrap.py +0 -0
  192. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_call_client_baseexceptiongroup_cancel_split.py +0 -0
  193. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_call_client_docstring_mapping_table.py +0 -0
  194. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_call_client_mapping.py +0 -0
  195. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_call_client_typeerror_message_does_not_blame_bind.py +0 -0
  196. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_call_client_wire_encode_error.py +0 -0
  197. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_callproc_noreturn_annotation.py +0 -0
  198. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_callproc_scroll_clear_messages.py +0 -0
  199. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_cascade_cursors_clears_messages.py +0 -0
  200. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_classify_caller_sql.py +0 -0
  201. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_classify_caller_sql_hardening.py +0 -0
  202. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_classify_caller_sql_structural_reject_precedence.py +0 -0
  203. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_classify_operational_code_zero.py +0 -0
  204. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_cleanup_loop_thread_disarms_inner_and_reaps_drain.py +0 -0
  205. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_cleanup_loop_thread_finalizer_fork_safe.py +0 -0
  206. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_close_bypass_path_releases_latched_op_lock.py +0 -0
  207. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_close_resource_leak.py +0 -0
  208. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_close_signal_handler_reentry.py +0 -0
  209. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_close_surfaces_op_lock_contention.py +0 -0
  210. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_close_thread_join_uses_close_timeout.py +0 -0
  211. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_close_timeout_floor_enforcement.py +0 -0
  212. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_close_timeout_plumbing.py +0 -0
  213. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_closed_property.py +0 -0
  214. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_cluster_policy_rejection_prefix_constant.py +0 -0
  215. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_code_to_exception_wire_ssot.py +0 -0
  216. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_collated_coverage_gaps.py +0 -0
  217. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_commit_after_external_close.py +0 -0
  218. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_commit_leader_flip_ambiguous_commit_error.py +0 -0
  219. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_commit_leader_flip_propagates.py +0 -0
  220. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_commit_no_spurious_connect.py +0 -0
  221. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_commit_rollback_shortcircuit_uses_in_transaction.py +0 -0
  222. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_conform_adapter_non_primitive_rejected_at_dbapi_layer.py +0 -0
  223. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_conftest_adapter_registry_isolation.py +0 -0
  224. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_connect_baseexceptiongroup_cancel_split.py +0 -0
  225. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_connect_clears_messages.py +0 -0
  226. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_connect_closed_precedes_thread_affinity.py +0 -0
  227. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_connect_docstring_parity.py +0 -0
  228. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_connect_iso_autocommit_round_trip.py +0 -0
  229. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_connect_isolation_level_autocommit_kwargs_symmetric.py +0 -0
  230. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_connect_preserves_code.py +0 -0
  231. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_connection.py +0 -0
  232. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_connection_after_fork_raises.py +0 -0
  233. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_connection_autocommit_isolation_level_closed_state.py +0 -0
  234. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_connection_close_cascades_cursors.py +0 -0
  235. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_connection_close_cursor_cascade_atomicity.py +0 -0
  236. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_connection_close_idempotent_cross_thread.py +0 -0
  237. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_connection_closed_checks.py +0 -0
  238. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_connection_cursor_class_attribute.py +0 -0
  239. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_connection_cursor_closed_state_precedes_unknown_kwarg.py +0 -0
  240. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_connection_database_empty_string_rejected.py +0 -0
  241. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_connection_database_kwarg_isinstance.py +0 -0
  242. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_connection_eager_address_validation.py +0 -0
  243. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_connection_enter_eager.py +0 -0
  244. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_connection_executemany_outer_shape_check.py +0 -0
  245. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_connection_exit_commit_ki_breadcrumb.py +0 -0
  246. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_connection_exit_short_circuits_on_closed.py +0 -0
  247. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_connection_messages_cleared_by_cursor_methods.py +0 -0
  248. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_connection_row_factory_setter_closed_check.py +0 -0
  249. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_context_manager_exit.py +0 -0
  250. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_convert_bind_param_conform_exception_propagates.py +0 -0
  251. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_cte_prefix_stripper_edges.py +0 -0
  252. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_cursor.py +0 -0
  253. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_cursor_after_external_close.py +0 -0
  254. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_cursor_close_clears_messages.py +0 -0
  255. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_cursor_close_cross_thread.py +0 -0
  256. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_cursor_close_preserves_body_exception_on_messages_strip.py +0 -0
  257. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_cursor_close_releases_connection_pin.py +0 -0
  258. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_cursor_closed_after_connection_gc_pep249.py +0 -0
  259. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_cursor_closed_state_pep249.py +0 -0
  260. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_cursor_completed_iterations_resets_on_single_execute.py +0 -0
  261. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_cursor_connection_partial_init_attributeerror.py +0 -0
  262. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_cursor_connection_property_after_gc_pep249.py +0 -0
  263. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_cursor_execute_non_str_operation.py +0 -0
  264. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_cursor_execute_state_reset_on_failure.py +0 -0
  265. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_cursor_executemany_non_str_operation.py +0 -0
  266. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_cursor_executemany_outer_shape_check.py +0 -0
  267. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_cursor_factory_kwarg_rejected.py +0 -0
  268. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_cursor_force_close_race.py +0 -0
  269. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_cursor_iter_clears_messages_pep249.py +0 -0
  270. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_cursor_iterator_reset.py +0 -0
  271. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_cursor_kwargs_rejection_precedence.py +0 -0
  272. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_cursor_next_row_factory_raise_replays_row.py +0 -0
  273. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_cursor_per_row_types.py +0 -0
  274. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_cursor_post_close_property_reads.py +0 -0
  275. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_cursor_protocol_check.py +0 -0
  276. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_cursor_rejects_generator_params.py +0 -0
  277. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_cursor_repr_with_gced_parent_connection.py +0 -0
  278. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_cursor_row_factory_rejecting_setter.py +0 -0
  279. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_cursor_row_factory_subclass_inheritance.py +0 -0
  280. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_cursor_row_index_reset.py +0 -0
  281. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_cursor_rowcount_after_ddl_stdlib_parity.py +0 -0
  282. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_cursor_rownumber_affinity.py +0 -0
  283. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_cursor_rownumber_post_close.py +0 -0
  284. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_cursor_scroll_mode_validation.py +0 -0
  285. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_cursor_secondary_methods_positional_only.py +0 -0
  286. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_cursor_setinputsizes_affinity_precedes_shape.py +0 -0
  287. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_cursor_setinputsizes_setoutputsize_closed_state_precedes_validation.py +0 -0
  288. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_cursor_setters_clear_messages_pep249.py +0 -0
  289. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_cursor_stop_iteration_contract.py +0 -0
  290. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_cursor_uses_run_protocol.py +0 -0
  291. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_date_widens_to_datetime.py +0 -0
  292. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_datetime_from_iso8601_docstring_two_step_fallback.py +0 -0
  293. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_datetime_time_round_trip.py +0 -0
  294. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_dbapi_commit_deferred_fk.py +0 -0
  295. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_dbapi_default_timeouts_use_client_constants.py +0 -0
  296. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_dbapi_exception_message_arg_cap.py +0 -0
  297. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_dbapi_exception_raw_message_cap.py +0 -0
  298. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_dbapi_fork_guard_cluster.py +0 -0
  299. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_dbapi_hardening.py +0 -0
  300. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_dbapi_type_hash_consistency.py +0 -0
  301. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_description_null_first_row_resolves.py +0 -0
  302. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_description_null_type_code_to_none.py +0 -0
  303. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_description_strict_type_codes.py +0 -0
  304. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_description_tuple_public_alias.py +0 -0
  305. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_diagnostic_messages.py +0 -0
  306. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_dial_attempt_timeout_propagation.py +0 -0
  307. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_dial_func_propagation.py +0 -0
  308. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_dial_func_propagation_to_leader_resolve.py +0 -0
  309. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_doc_class_and_description_drift.py +0 -0
  310. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_dqlite_namespace_error_classification.py +0 -0
  311. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_ensure_loop_fast_path_snapshot_dcl_defence.py +0 -0
  312. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_ensure_loop_post_fork_pid_guard.py +0 -0
  313. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_error_code_mapping.py +0 -0
  314. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_error_pickle_cause_either_or.py +0 -0
  315. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_error_pickle_preserves_raw_message.py +0 -0
  316. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_exception_coded_mixin.py +0 -0
  317. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_exception_mapping.py +0 -0
  318. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_exception_repr_code.py +0 -0
  319. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_exception_wrapping.py +0 -0
  320. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_exceptions.py +0 -0
  321. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_execute_caller_param_shape_preserves_prior_state.py +0 -0
  322. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_execute_executemany_positional_only.py +0 -0
  323. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_execute_multi_statement_rejection.py +0 -0
  324. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_execute_reject_scrubs_prior_result_state.py +0 -0
  325. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_executemany_accumulator_semantics.py +0 -0
  326. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_executemany_basecaught_state_reset.py +0 -0
  327. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_executemany_cancel_restores_pre_batch_lastrowid.py +0 -0
  328. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_executemany_cancellederror_restores_pre_batch_lastrowid.py +0 -0
  329. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_executemany_classifier_resets_state_first.py +0 -0
  330. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_executemany_classify_caller_sql_hoisted.py +0 -0
  331. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_executemany_completed_iterations_snapshot_conditional_restore.py +0 -0
  332. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_executemany_dict_subclass_accepted.py +0 -0
  333. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_executemany_empty.py +0 -0
  334. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_executemany_empty_batch_preserves_lastrowid.py +0 -0
  335. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_executemany_none_seq_rejected.py +0 -0
  336. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_executemany_per_iter_structural_rejects.py +0 -0
  337. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_executemany_pragma_with_leading_comment.py +0 -0
  338. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_executemany_reject_scrubs_prior_result_state.py +0 -0
  339. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_executemany_rejects_pure_queries.py +0 -0
  340. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_executemany_rejects_transaction_verbs.py +0 -0
  341. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_executemany_returning_cap.py +0 -0
  342. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_exit_logs_rollback_failure.py +0 -0
  343. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_fetchall_row_factory_raise_replay.py +0 -0
  344. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_fetchmany_edges.py +0 -0
  345. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_fetchmany_negative_size_rejected.py +0 -0
  346. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_fetchmany_non_int_validation.py +0 -0
  347. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_fetchmany_one_clear_per_call_pep249.py +0 -0
  348. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_fetchmany_row_factory_raise_replay.py +0 -0
  349. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_fetchmany_zero_docstring_stdlib_divergence.py +0 -0
  350. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_finalizer_strict_warnings.py +0 -0
  351. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_force_close_cancel_and_observe_narrow_suppress.py +0 -0
  352. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_force_close_observe_ast_pin.py +0 -0
  353. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_force_close_observe_callback_coverage.py +0 -0
  354. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_force_close_transport_cleanup_tail_unconditional.py +0 -0
  355. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_force_close_transport_clears_messages.py +0 -0
  356. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_force_close_transport_cross_thread_cancel_threadsafe.py +0 -0
  357. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_force_close_transport_cursor_cascade_threadsafe.py +0 -0
  358. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_force_close_transport_defensive_arms.py +0 -0
  359. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_force_close_transport_disarms_inner_finalizer.py +0 -0
  360. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_force_close_transport_pending_drain_resnapshot.py +0 -0
  361. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_force_close_transport_writer_cross_thread.py +0 -0
  362. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_fork_diagnostic_includes_pids.py +0 -0
  363. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_fromticks_tz_policy.py +0 -0
  364. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_gc_cleanup.py +0 -0
  365. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_get_resolve_leader_cluster_no_loop_raises_runtime_error.py +0 -0
  366. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_in_transaction_after_close.py +0 -0
  367. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_in_transaction_closed_divergence_doc.py +0 -0
  368. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_in_transaction_closed_foreign_thread.py +0 -0
  369. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_in_transaction_property.py +0 -0
  370. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_init_docstring_fromticks_three_impls.py +0 -0
  371. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_is_multi_statement_walker.py +0 -0
  372. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_is_no_transaction_error.py +0 -0
  373. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_is_row_returning_multiline_whitespace.py +0 -0
  374. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_is_row_returning_sql_noise.py +0 -0
  375. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_iso8601_aware_microseconds_round_trip.py +0 -0
  376. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_iso8601_decoder_leap_seconds_rejected.py +0 -0
  377. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_iso8601_decoder_subsecond_truncation.py +0 -0
  378. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_iso8601_encoder.py +0 -0
  379. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_iso8601_encoder_fold.py +0 -0
  380. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_iso8601_encoder_utcoffset_exception_wrap.py +0 -0
  381. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_iso8601_parse_error_truncation.py +0 -0
  382. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_iso8601_tzinfo_exception_classes_wrapped.py +0 -0
  383. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_isolation_level_stdlib_accept_set.py +0 -0
  384. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_iterdump_stub_signature_3_13_filter_kwarg.py +0 -0
  385. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_lastrowid_docstring_stdlib_parity_claim.py +0 -0
  386. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_lastrowid_insert_only.py +0 -0
  387. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_lastrowid_signed_int64_cast.py +0 -0
  388. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_leader_error_codes_identity.py +0 -0
  389. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_leader_redirect_on_connect.py +0 -0
  390. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_legacy_transaction_control_constant.py +0 -0
  391. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_max_continuation_frames_upper_bound.py +0 -0
  392. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_max_message_size_round_trip.py +0 -0
  393. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_max_total_rows_plumbing.py +0 -0
  394. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_max_total_rows_validation.py +0 -0
  395. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_messages_attribute.py +0 -0
  396. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_messages_clear_on_ctxmgr_entry.py +0 -0
  397. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_nextset_noreturn_annotation.py +0 -0
  398. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_no_transaction_substrings_source_of_truth.py +0 -0
  399. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_no_tx_primary_codes_invariant.py +0 -0
  400. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_op_lock_bounded_acquire.py +0 -0
  401. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_operational_error_classification.py +0 -0
  402. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_operational_error_raw_message.py +0 -0
  403. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_param_conversion.py +0 -0
  404. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_param_validation.py +0 -0
  405. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_paramstyle_qmark_only_pin.py +0 -0
  406. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_parse_decltypes_colnames_constants.py +0 -0
  407. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_pep249_database_error_routing.py +0 -0
  408. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_pep249_exception_aliases.py +0 -0
  409. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_pep249_messages_clearing.py +0 -0
  410. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_pep249_messages_independence.py +0 -0
  411. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_pep249_misc_pins.py +0 -0
  412. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_pep249_stub_hasattr_divergence.py +0 -0
  413. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_pep249_type_constructors_vs_singletons.py +0 -0
  414. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_pep249_type_constructors_wrap_valueerror.py +0 -0
  415. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_pep249_type_objects_final_annotated.py +0 -0
  416. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_pickle_guard.py +0 -0
  417. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_pragma_write_description_none.py +0 -0
  418. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_prepare_protocol_in_types_all.py +0 -0
  419. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_property_round_trip_and_fork_guard.py +0 -0
  420. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_property_setters_do_not_mirror_to_inner_async_conn.py +0 -0
  421. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_protocol_serialization.py +0 -0
  422. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_pyproject_dependency_graph.py +0 -0
  423. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_readme_truthfulness_pins.py +0 -0
  424. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_register_adapter_conform_fallback.py +0 -0
  425. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_register_adapter_global_sync_aio_symmetry.py +0 -0
  426. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_repr.py +0 -0
  427. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_repr_sanitizes_address.py +0 -0
  428. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_resolve_leader_cache_dial_func_id_recycle.py +0 -0
  429. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_resolve_leader_cache_lock_atfork_hook.py +0 -0
  430. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_resolve_leader_cluster_cache.py +0 -0
  431. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_resolve_leader_cross_loop_isolation.py +0 -0
  432. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_row_factory_annotation.py +0 -0
  433. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_row_factory_sqlite3_row_surfaces_dataerror.py +0 -0
  434. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_rowid_number_overlap_documented.py +0 -0
  435. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_rownumber_closed_cursor_returns_none_documented.py +0 -0
  436. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_run_sync_ensure_loop_raise_closes_coroutine.py +0 -0
  437. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_run_sync_keyboard_interrupt.py +0 -0
  438. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_run_sync_keyboard_interrupt_cleanup_paths.py +0 -0
  439. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_run_sync_ki_post_schedule_pre_await.py +0 -0
  440. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_run_sync_ki_race_recovery.py +0 -0
  441. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_run_sync_loop_closed_during_schedule.py +0 -0
  442. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_run_sync_other_runtimeerror_propagates.py +0 -0
  443. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_run_sync_timeout.py +0 -0
  444. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_run_sync_timeout_recovery_preserves_conn.py +0 -0
  445. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_secondary_methods_clear_messages_before_thread_check.py +0 -0
  446. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_setinputsizes_accepts_none.py +0 -0
  447. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_setinputsizes_clears_messages_per_pep249.py +0 -0
  448. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_setinputsizes_non_sequence_iterable.py +0 -0
  449. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_setinputsizes_sequence_acceptance.py +0 -0
  450. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_setinputsizes_short_circuit_on_closed_connection.py +0 -0
  451. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_setoutputsize_accepts_none.py +0 -0
  452. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_setter_closed_state_precedes_thread.py +0 -0
  453. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_sqlite_errorname_property.py +0 -0
  454. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_sqlite_version_not_public_attribute.py +0 -0
  455. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_strip_leading_comments_bom.py +0 -0
  456. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_strip_leading_comments_parity.py +0 -0
  457. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_strip_sql_noise_backtick_identifier.py +0 -0
  458. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_stub_signatures_normalize_to_dbapi_error.py +0 -0
  459. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_submodule_all.py +0 -0
  460. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_sync_commit_rollback_closed_precedes_thread.py +0 -0
  461. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_sync_commit_rollback_inside_ctxmgr_guard.py +0 -0
  462. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_sync_commit_rollback_messages_under_lock.py +0 -0
  463. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_sync_commit_rollback_retry_after_invalidate.py +0 -0
  464. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_sync_connection_execute_shortcut.py +0 -0
  465. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_sync_connection_executemany_shortcut.py +0 -0
  466. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_sync_cursor_post_add_closed_recheck.py +0 -0
  467. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_sync_exit_signal_mid_rollback.py +0 -0
  468. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_sync_fetchmany_cancel_atomic.py +0 -0
  469. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_sync_force_close_transport.py +0 -0
  470. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_sync_force_close_transport_pending_drain_reap.py +0 -0
  471. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_sync_invalidated_property.py +0 -0
  472. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_sync_run_sync_multiphase_timeout.py +0 -0
  473. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_timeout_validation.py +0 -0
  474. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_top_level_logger_has_null_handler.py +0 -0
  475. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_total_changes_hasattr_safe.py +0 -0
  476. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_transitive_wire_import.py +0 -0
  477. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_type_coercion_documented_asymmetries.py +0 -0
  478. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_types_dbapi_compare.py +0 -0
  479. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_types_validator_branches.py +0 -0
  480. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_unregister_adapter.py +0 -0
  481. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_unregister_adapter_rejects_built_in_datetime_types.py +0 -0
  482. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_unsupported_method_stubs.py +0 -0
  483. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_validate_positive_int_pep249_wrap.py +0 -0
  484. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_validate_ticks.py +0 -0
  485. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_validate_ticks_and_unixtime_reject_bool_and_non_numeric.py +0 -0
  486. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/tests/test_version.py +0 -0
  487. {dqlite_dbapi-0.2.0 → dqlite_dbapi-0.2.1}/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.0
3
+ Version: 0.2.1
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.0
26
- Requires-Dist: dqlite-wire<0.3.0,>=0.2.0
25
+ Requires-Dist: dqlite-client<0.3.0,>=0.2.1
26
+ Requires-Dist: dqlite-wire<0.3.0,>=0.2.1
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 = 1`
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 = 1`** (stdlib reports `3`). Each Connection is
244
- thread-affine: methods called from a foreign thread raise
245
- `ProgrammingError`. Use one Connection per thread or use the async
246
- surface for a single-thread-per-loop model.
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 = 1`
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 = 1`** (stdlib reports `3`). Each Connection is
210
- thread-affine: methods called from a foreign thread raise
211
- `ProgrammingError`. Use one Connection per thread or use the async
212
- surface for a single-thread-per-loop model.
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.0"
7
+ version = "0.2.1"
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.0,<0.3.0",
30
- "dqlite-wire>=0.2.0,<0.3.0",
29
+ "dqlite-client>=0.2.1,<0.3.0",
30
+ "dqlite-wire>=0.2.1,<0.3.0",
31
31
  ]
32
32
 
33
33
  [tool.uv.sources]
@@ -107,14 +107,38 @@ from dqlitewire import (
107
107
 
108
108
  # PEP 249 module-level attributes
109
109
  apilevel: _Final[_Literal["2.0"]] = "2.0"
110
- # PEP 249 value 1: threads may share the module.
110
+ # PEP 249 value 2: threads may share the module and connections.
111
111
  #
112
- # This driver is stricter than the PEP minimum: each Connection is
113
- # bound to the thread that created it. Any method call from a
114
- # different thread raises ProgrammingError. Use one Connection per
115
- # thread, or use the async API (dqlitedbapi.aio.aconnect) for a
116
- # single-thread-per-loop model.
117
- threadsafety: _Final[_Literal[1]] = 1
112
+ # This declares the driver's CAPABILITY ceiling: connections are
113
+ # safe to share across threads once the user opts in via
114
+ # ``check_same_thread=False`` at ``connect()`` (matches stdlib
115
+ # sqlite3's convention — stdlib advertises ``threadsafety = 3``
116
+ # while defaulting ``check_same_thread=True`` to enforce strict
117
+ # per-thread at the Python layer). Cursors are explicitly NOT
118
+ # shareable across threads — the documented contract is "share
119
+ # connections, not cursors" (see ``Connection`` docstring); the
120
+ # ceiling stays at PEP 249 tier 2 for that reason. Tier 3 (cursors
121
+ # shareable too) is tracked as future work in
122
+ # ``issues/dbapi-threadsafety-tier-3-cursor-sharing-stdlib-parity.md``.
123
+ #
124
+ # DEFAULT enforcement is still per-thread: ``check_same_thread``
125
+ # defaults to ``True`` and ``Connection._check_thread()`` raises
126
+ # ``ProgrammingError`` on cross-thread method calls. Pass
127
+ # ``check_same_thread=False`` to use the connection sharing
128
+ # advertised here. The wire is always serialised by ``_op_lock``;
129
+ # the Phase 2 hardening (``_state_lock`` for transaction owner
130
+ # read-check-reserve, ``_cursors`` WeakSet add/discard) closes
131
+ # the cross-thread correctness gaps for the connection-shared
132
+ # path.
133
+ #
134
+ # This declaration uses the "advertise ceiling, default to floor"
135
+ # pattern stdlib sqlite3 established since CPython 3.11
136
+ # (bpo-45613 / gh-31464 made ``sqlite3.threadsafety`` dynamic via
137
+ # ``SQLITE_THREADSAFE`` mode; serialized = tier 3). The pattern
138
+ # describes capability rather than default enforcement;
139
+ # capability-detection libraries reading this value learn the
140
+ # safe-with-opt-in ceiling.
141
+ threadsafety: _Final[_Literal[2]] = 2
118
142
  paramstyle: _Final[_Literal["qmark"]] = "qmark" # Question mark style: WHERE name=?
119
143
 
120
144
  # SQLite compatibility attributes (for SQLAlchemy).
@@ -147,7 +171,7 @@ LEGACY_TRANSACTION_CONTROL: _Final[int] = -1
147
171
  PARSE_DECLTYPES: _Final[int] = 1
148
172
  PARSE_COLNAMES: _Final[int] = 2
149
173
 
150
- __version__: _Final[str] = "0.2.0"
174
+ __version__: _Final[str] = "0.2.1"
151
175
 
152
176
  __all__ = [ # grouped by PEP 249 section, not alphabetical
153
177
  # Module attributes
@@ -222,6 +246,8 @@ def connect(
222
246
  dial_timeout: float | None = None,
223
247
  attempt_timeout: float | None = None,
224
248
  dial_func: DialFunc | None = None,
249
+ busy_timeout: float = 5.0,
250
+ check_same_thread: bool = True,
225
251
  **unknown_kwargs: object,
226
252
  ) -> Connection:
227
253
  """Connect to a dqlite database.
@@ -269,6 +295,33 @@ def connect(
269
295
  ``asyncio.open_connection`` path. Forwarded to the
270
296
  underlying :class:`Connection`. See
271
297
  :data:`dqliteclient.DialFunc` for the protocol.
298
+ busy_timeout: Maximum cumulative seconds to spend retrying
299
+ BUSY responses before raising. Default ``5.0`` matches
300
+ stdlib ``sqlite3.connect(timeout=5.0)``. Retries follow
301
+ SQLite's deterministic ``sqliteDefaultBusyCallback``
302
+ curve (1, 2, 5, 10, 15, 20, 25, 25, 25, 50, 50, 100 ms
303
+ then flat 100 ms). ``0`` disables retry (first BUSY
304
+ raises immediately — stdlib parity). dqlite's VFS
305
+ authorizer rejects the canonical ``PRAGMA busy_timeout``
306
+ on the server side; this driver intercepts the PRAGMA at
307
+ the Cursor layer so ``cur.execute("PRAGMA busy_timeout
308
+ = 30000")`` works transparently and updates the same
309
+ backing field as the kwarg.
310
+ check_same_thread: When ``True`` (default), every method
311
+ call on the returned Connection must come from the
312
+ thread that called ``connect()``; cross-thread calls
313
+ raise ``ProgrammingError``. When ``False``, the cross-
314
+ thread check is gated off and the Connection can be
315
+ shared across threads — the wire is already serialised
316
+ by the per-Connection op-lock regardless of caller
317
+ thread. Per-cursor result state is NOT thread-safe; the
318
+ documented pattern is "share connections, not cursors."
319
+ Matches stdlib ``sqlite3.connect(check_same_thread=
320
+ False)``. The fork check is NEVER relaxed; cross-process
321
+ Connection use raises ``InterfaceError`` in both modes.
322
+ See ``dqlitedbapi.Connection`` docstring for the
323
+ cursor / messages / close() contracts under the
324
+ relaxation.
272
325
 
273
326
  Returns:
274
327
  A Connection object
@@ -323,11 +376,10 @@ def connect(
323
376
  f"(stdlib LEGACY_TRANSACTION_CONTROL) only; got {autoc!r}"
324
377
  )
325
378
 
326
- # Reject stdlib ``sqlite3.connect`` kwargs that this driver
327
- # cannot honour (``detect_types``, ``check_same_thread``,
328
- # ``factory``, ``cached_statements``, ``uri``) with
329
- # ``NotSupportedError`` rather than letting Python's call-
330
- # protocol leak ``TypeError`` (which escapes ``except
379
+ # Reject other stdlib ``sqlite3.connect`` kwargs that this driver
380
+ # cannot honour (``detect_types``, ``factory``, ``cached_statements``,
381
+ # ``uri``) with ``NotSupportedError`` rather than letting Python's
382
+ # call-protocol leak ``TypeError`` (which escapes ``except
331
383
  # dbapi.Error:``). Cross-driver code that passes stdlib kwargs
332
384
  # through should be able to catch the rejection inside the
333
385
  # dbapi error hierarchy.
@@ -352,6 +404,8 @@ def connect(
352
404
  dial_timeout=dial_timeout,
353
405
  attempt_timeout=attempt_timeout,
354
406
  dial_func=dial_func,
407
+ busy_timeout=busy_timeout,
408
+ check_same_thread=check_same_thread,
355
409
  )
356
410
  # Apply the validated ``isolation_level`` / ``autocommit`` kwargs
357
411
  # 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
@@ -0,0 +1,106 @@
1
+ """``PRAGMA busy_timeout`` interception at the dbapi cursor layer.
2
+
3
+ dqlite's C VFS authorizer rejects ``PRAGMA busy_timeout`` (see prior
4
+ issue ``done/dbapi-pragma-deny-list-no-regression-pin.md``), so the
5
+ canonical SQLite escape hatch — ``PRAGMA busy_timeout = N`` — would
6
+ otherwise raise ``DatabaseError("not authorized")`` on the dqlite
7
+ server. To preserve stdlib parity (and to give callers a single
8
+ canonical knob), this module intercepts the PRAGMA at the dbapi
9
+ cursor layer BEFORE the wire round-trip:
10
+
11
+ - Setter (``PRAGMA busy_timeout = N`` or ``PRAGMA busy_timeout(N)``)
12
+ updates ``connection._busy_timeout`` in seconds (N is ms per
13
+ SQLite convention; we divide by 1000) and emits the new value as
14
+ a single-row result, matching stdlib's PRAGMA result-set shape.
15
+ - Getter (``PRAGMA busy_timeout``) emits the current value as a
16
+ single-row result.
17
+
18
+ Case-insensitive and whitespace-tolerant per SQLite parser
19
+ conventions. Multi-statement strings ARE rejected (only a SINGLE
20
+ ``PRAGMA busy_timeout`` statement is intercepted; multi-statement
21
+ inputs surface to the wire where the existing multi-statement
22
+ classifier rejects them with ``ProgrammingError`` — that path is
23
+ unchanged).
24
+
25
+ The interception writes the cursor's result state directly:
26
+
27
+ - ``_description = (("busy_timeout", None, None, None, None, None, None),)``
28
+ - ``_rows = [(N_ms_clamped,)]``
29
+ - ``_rowcount = -1`` (PRAGMA does not have a meaningful row count)
30
+ - ``_row_index = 0``
31
+
32
+ Then returns True so the caller short-circuits before the wire
33
+ round-trip.
34
+ """
35
+
36
+ from __future__ import annotations
37
+
38
+ import re
39
+ from typing import TYPE_CHECKING
40
+
41
+ if TYPE_CHECKING:
42
+ from dqlitedbapi.cursor import Cursor
43
+
44
+ # Match ``PRAGMA busy_timeout`` with optional setter forms:
45
+ #
46
+ # PRAGMA busy_timeout
47
+ # PRAGMA busy_timeout = 5000
48
+ # PRAGMA busy_timeout=5000
49
+ # pragma BUSY_TIMEOUT = 5000
50
+ # PRAGMA busy_timeout(5000)
51
+ #
52
+ # The trailing ``;?`` accommodates the canonical single-statement
53
+ # terminator. A semicolon followed by additional content (multi-
54
+ # statement) does NOT match — those route to the wire where the
55
+ # existing multi-statement classifier rejects them.
56
+ _PRAGMA_BUSY_TIMEOUT_RE = re.compile(
57
+ r"^\s*PRAGMA\s+busy_timeout\s*"
58
+ r"(?:=\s*(?P<setter_eq>-?\d+)|\(\s*(?P<setter_paren>-?\d+)\s*\))?"
59
+ r"\s*;?\s*$",
60
+ re.IGNORECASE,
61
+ )
62
+
63
+
64
+ def try_intercept_busy_timeout(
65
+ cursor: Cursor,
66
+ statement: str,
67
+ parameters: object,
68
+ ) -> bool:
69
+ """Return True if the statement is ``PRAGMA busy_timeout`` (any
70
+ form) and the cursor's result state has been populated.
71
+
72
+ When True, the caller MUST short-circuit BEFORE the wire round-
73
+ trip (the server would reject the PRAGMA via the VFS authorizer
74
+ if it reached the wire, defeating the interception's purpose).
75
+
76
+ ``parameters`` is accepted for caller-signature parity but
77
+ PRAGMA does not take parameters. If parameters are non-empty
78
+ we still match the PRAGMA and ignore them — the engine would
79
+ do the same. (A future refinement could raise a friendlier
80
+ error here, but stdlib silently accepts the same shape.)
81
+ """
82
+ if not isinstance(statement, str):
83
+ return False
84
+ match = _PRAGMA_BUSY_TIMEOUT_RE.match(statement)
85
+ if match is None:
86
+ return False
87
+ setter_eq = match.group("setter_eq")
88
+ setter_paren = match.group("setter_paren")
89
+ setter_value = setter_eq if setter_eq is not None else setter_paren
90
+ connection = cursor._connection
91
+ if setter_value is not None:
92
+ # Setter form: update connection._busy_timeout.
93
+ # SQLite clamps negative values to 0 (no wait) — stdlib parity.
94
+ new_ms = max(0, int(setter_value))
95
+ connection._busy_timeout = new_ms / 1000.0
96
+ # Both setter AND getter forms emit the (possibly updated) value
97
+ # as a single-row result. Matches SQLite's PRAGMA convention:
98
+ # ``PRAGMA busy_timeout = 5000`` returns ``(5000,)``,
99
+ # ``PRAGMA busy_timeout`` (getter) also returns ``(5000,)``.
100
+ current_ms = int(connection._busy_timeout * 1000)
101
+ cursor._description = (("busy_timeout", None, None, None, None, None, None),)
102
+ cursor._rows = [(current_ms,)]
103
+ cursor._rowcount = -1
104
+ cursor._row_index = 0
105
+ # Match stdlib: PRAGMA does not set lastrowid.
106
+ return True