dqlite-dbapi 0.3.0__tar.gz → 0.4.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {dqlite_dbapi-0.3.0 → dqlite_dbapi-0.4.0}/DEVELOPMENT.md +2 -2
- {dqlite_dbapi-0.3.0 → dqlite_dbapi-0.4.0}/PKG-INFO +6 -4
- {dqlite_dbapi-0.3.0 → dqlite_dbapi-0.4.0}/README.md +2 -0
- dqlite_dbapi-0.4.0/docs/architecture.md +169 -0
- {dqlite_dbapi-0.3.0 → dqlite_dbapi-0.4.0}/docs/differences-from-sqlite3.md +9 -0
- {dqlite_dbapi-0.3.0 → dqlite_dbapi-0.4.0}/docs/transactions.md +24 -12
- {dqlite_dbapi-0.3.0 → dqlite_dbapi-0.4.0}/pyproject.toml +3 -3
- dqlite_dbapi-0.4.0/src/dqlitedbapi/__init__.py +156 -0
- dqlite_dbapi-0.4.0/src/dqlitedbapi/_busy.py +57 -0
- dqlite_dbapi-0.4.0/src/dqlitedbapi/_loop.py +105 -0
- dqlite_dbapi-0.4.0/src/dqlitedbapi/_module.py +35 -0
- dqlite_dbapi-0.4.0/src/dqlitedbapi/_sql.py +204 -0
- dqlite_dbapi-0.4.0/src/dqlitedbapi/_stubs.py +103 -0
- dqlite_dbapi-0.4.0/src/dqlitedbapi/aio/__init__.py +204 -0
- dqlite_dbapi-0.4.0/src/dqlitedbapi/aio/connection.py +549 -0
- dqlite_dbapi-0.4.0/src/dqlitedbapi/aio/cursor.py +480 -0
- dqlite_dbapi-0.4.0/src/dqlitedbapi/connection.py +300 -0
- dqlite_dbapi-0.4.0/src/dqlitedbapi/cursor.py +151 -0
- dqlite_dbapi-0.4.0/src/dqlitedbapi/exceptions.py +282 -0
- {dqlite_dbapi-0.3.0 → dqlite_dbapi-0.4.0}/src/dqlitedbapi/row.py +8 -22
- dqlite_dbapi-0.4.0/src/dqlitedbapi/types.py +364 -0
- dqlite_dbapi-0.4.0/tests/conftest.py +29 -0
- {dqlite_dbapi-0.3.0 → dqlite_dbapi-0.4.0}/tests/integration/test_async_concurrent_executemany_serialized.py +1 -1
- {dqlite_dbapi-0.3.0 → dqlite_dbapi-0.4.0}/tests/integration/test_async_executemany_param_count.py +1 -1
- {dqlite_dbapi-0.3.0 → dqlite_dbapi-0.4.0}/tests/integration/test_datetime_conversion.py +8 -8
- {dqlite_dbapi-0.3.0 → dqlite_dbapi-0.4.0}/tests/integration/test_executemany_cancel_mid_batch.py +4 -4
- {dqlite_dbapi-0.3.0 → dqlite_dbapi-0.4.0}/tests/integration/test_executemany_completed_iterations_observability.py +3 -4
- {dqlite_dbapi-0.3.0 → dqlite_dbapi-0.4.0}/tests/integration/test_executemany_failure_resets_rowcount.py +4 -4
- {dqlite_dbapi-0.3.0 → dqlite_dbapi-0.4.0}/tests/integration/test_force_close_unblocks_run_sync.py +1 -1
- {dqlite_dbapi-0.3.0 → dqlite_dbapi-0.4.0}/tests/integration/test_lastrowid_returning.py +1 -1
- {dqlite_dbapi-0.3.0 → dqlite_dbapi-0.4.0}/tests/integration/test_leader_redirect_live.py +1 -1
- {dqlite_dbapi-0.3.0 → dqlite_dbapi-0.4.0}/tests/integration/test_misc_coverage.py +4 -4
- {dqlite_dbapi-0.3.0 → dqlite_dbapi-0.4.0}/tests/integration/test_pragma_deny_list.py +2 -2
- {dqlite_dbapi-0.3.0 → dqlite_dbapi-0.4.0}/tests/integration/test_session_mode_read_only.py +3 -3
- {dqlite_dbapi-0.3.0 → dqlite_dbapi-0.4.0}/tests/test_async_cursor_aiter_aenter_pep249_pins.py +4 -4
- {dqlite_dbapi-0.3.0 → dqlite_dbapi-0.4.0}/tests/test_autocommit_doc_no_round_trip.py +3 -3
- {dqlite_dbapi-0.3.0 → dqlite_dbapi-0.4.0}/tests/test_autocommit_legacy_transaction_control.py +3 -3
- dqlite_dbapi-0.4.0/tests/test_commit_no_spurious_connect.py +18 -0
- {dqlite_dbapi-0.3.0 → dqlite_dbapi-0.4.0}/tests/test_conform_adapter_non_primitive_rejected_at_dbapi_layer.py +5 -5
- {dqlite_dbapi-0.3.0 → dqlite_dbapi-0.4.0}/tests/test_connect_isolation_level_autocommit_kwargs_symmetric.py +4 -4
- {dqlite_dbapi-0.3.0 → dqlite_dbapi-0.4.0}/tests/test_connection_eager_address_validation.py +5 -5
- {dqlite_dbapi-0.3.0 → dqlite_dbapi-0.4.0}/tests/test_convert_bind_param_conform_exception_propagates.py +5 -5
- {dqlite_dbapi-0.3.0 → dqlite_dbapi-0.4.0}/tests/test_cursor_after_external_close.py +4 -14
- {dqlite_dbapi-0.3.0 → dqlite_dbapi-0.4.0}/tests/test_date_widens_to_datetime.py +5 -5
- {dqlite_dbapi-0.3.0 → dqlite_dbapi-0.4.0}/tests/test_datetime_time_round_trip.py +6 -6
- dqlite_dbapi-0.4.0/tests/test_driver_rules.py +242 -0
- {dqlite_dbapi-0.3.0 → dqlite_dbapi-0.4.0}/tests/test_in_transaction_closed_divergence_doc.py +1 -1
- {dqlite_dbapi-0.3.0 → dqlite_dbapi-0.4.0}/tests/test_iso8601_aware_microseconds_round_trip.py +5 -5
- {dqlite_dbapi-0.3.0 → dqlite_dbapi-0.4.0}/tests/test_iso8601_decoder_leap_seconds_rejected.py +2 -2
- {dqlite_dbapi-0.3.0 → dqlite_dbapi-0.4.0}/tests/test_iso8601_decoder_subsecond_truncation.py +4 -4
- {dqlite_dbapi-0.3.0 → dqlite_dbapi-0.4.0}/tests/test_iso8601_encoder.py +68 -68
- {dqlite_dbapi-0.3.0 → dqlite_dbapi-0.4.0}/tests/test_iso8601_encoder_fold.py +7 -7
- {dqlite_dbapi-0.3.0 → dqlite_dbapi-0.4.0}/tests/test_iso8601_encoder_utcoffset_exception_wrap.py +7 -7
- {dqlite_dbapi-0.3.0 → dqlite_dbapi-0.4.0}/tests/test_iso8601_parse_error_truncation.py +6 -6
- {dqlite_dbapi-0.3.0 → dqlite_dbapi-0.4.0}/tests/test_iso8601_tzinfo_exception_classes_wrapped.py +3 -3
- {dqlite_dbapi-0.3.0 → dqlite_dbapi-0.4.0}/tests/test_isolation_level_stdlib_accept_set.py +6 -6
- {dqlite_dbapi-0.3.0 → dqlite_dbapi-0.4.0}/tests/test_legacy_transaction_control_constant.py +1 -1
- {dqlite_dbapi-0.3.0 → dqlite_dbapi-0.4.0}/tests/test_pep249_stub_hasattr_divergence.py +1 -1
- {dqlite_dbapi-0.3.0 → dqlite_dbapi-0.4.0}/tests/test_register_adapter_conform_fallback.py +9 -9
- {dqlite_dbapi-0.3.0 → dqlite_dbapi-0.4.0}/tests/test_repr.py +2 -3
- {dqlite_dbapi-0.3.0 → dqlite_dbapi-0.4.0}/tests/test_setinputsizes_accepts_none.py +5 -6
- {dqlite_dbapi-0.3.0 → dqlite_dbapi-0.4.0}/tests/test_setinputsizes_non_sequence_iterable.py +6 -8
- {dqlite_dbapi-0.3.0 → dqlite_dbapi-0.4.0}/tests/test_setoutputsize_accepts_none.py +5 -6
- {dqlite_dbapi-0.3.0 → dqlite_dbapi-0.4.0}/tests/test_text_bind_nul_byte_rejected_with_blob_hint.py +11 -11
- {dqlite_dbapi-0.3.0 → dqlite_dbapi-0.4.0}/tests/test_total_changes_hasattr_safe.py +1 -1
- {dqlite_dbapi-0.3.0 → dqlite_dbapi-0.4.0}/tests/test_type_coercion_documented_asymmetries.py +12 -12
- {dqlite_dbapi-0.3.0 → dqlite_dbapi-0.4.0}/tests/test_types_validator_branches.py +9 -9
- {dqlite_dbapi-0.3.0 → dqlite_dbapi-0.4.0}/tests/test_unregister_adapter_rejects_built_in_datetime_types.py +3 -3
- {dqlite_dbapi-0.3.0 → dqlite_dbapi-0.4.0}/tests/test_validate_ticks_and_unixtime_reject_bool_and_non_numeric.py +8 -8
- dqlite_dbapi-0.4.0/tests/test_with_stub_client.py +607 -0
- {dqlite_dbapi-0.3.0 → dqlite_dbapi-0.4.0}/uv.lock +3 -3
- dqlite_dbapi-0.3.0/src/dqlitedbapi/__init__.py +0 -256
- dqlite_dbapi-0.3.0/src/dqlitedbapi/_busy_retry.py +0 -149
- dqlite_dbapi-0.3.0/src/dqlitedbapi/_constants.py +0 -37
- dqlite_dbapi-0.3.0/src/dqlitedbapi/_pragma_intercept.py +0 -130
- dqlite_dbapi-0.3.0/src/dqlitedbapi/aio/__init__.py +0 -373
- dqlite_dbapi-0.3.0/src/dqlitedbapi/aio/connection.py +0 -1697
- dqlite_dbapi-0.3.0/src/dqlitedbapi/aio/cursor.py +0 -970
- dqlite_dbapi-0.3.0/src/dqlitedbapi/connection.py +0 -2574
- dqlite_dbapi-0.3.0/src/dqlitedbapi/cursor.py +0 -1749
- dqlite_dbapi-0.3.0/src/dqlitedbapi/exceptions.py +0 -291
- dqlite_dbapi-0.3.0/src/dqlitedbapi/types.py +0 -590
- dqlite_dbapi-0.3.0/tests/aio/test_aio_version_final_annotated.py +0 -20
- dqlite_dbapi-0.3.0/tests/aio/test_async_close_underlying_close_failed_logged.py +0 -105
- dqlite_dbapi-0.3.0/tests/aio/test_async_connection_fork_guard.py +0 -115
- dqlite_dbapi-0.3.0/tests/aio/test_async_unclosed_warning_skips_in_fork_child.py +0 -143
- dqlite_dbapi-0.3.0/tests/aio/test_close_cancel_between_cursor_cascade_and_lock.py +0 -58
- dqlite_dbapi-0.3.0/tests/aio/test_fetchmany_cancel_atomic.py +0 -57
- dqlite_dbapi-0.3.0/tests/aio/test_messages_clear_on_closed.py +0 -49
- dqlite_dbapi-0.3.0/tests/conftest.py +0 -45
- dqlite_dbapi-0.3.0/tests/test_aconnect_cleanup_close_shielded_against_outer_timeout.py +0 -62
- dqlite_dbapi-0.3.0/tests/test_aconnect_cleanup_on_connect_failure.py +0 -75
- dqlite_dbapi-0.3.0/tests/test_adapter_exceptions_wrapped_as_data_error.py +0 -123
- dqlite_dbapi-0.3.0/tests/test_aenter_cleanup_nulls_locks_under_concurrent_close.py +0 -107
- dqlite_dbapi-0.3.0/tests/test_aenter_cleanup_preserves_original_under_outer_cancel.py +0 -61
- dqlite_dbapi-0.3.0/tests/test_aexit_behavioural_no_close.py +0 -64
- dqlite_dbapi-0.3.0/tests/test_aexit_commit_cancel_debug_log.py +0 -99
- dqlite_dbapi-0.3.0/tests/test_aexit_commit_cancel_force_closes_transport.py +0 -114
- dqlite_dbapi-0.3.0/tests/test_aexit_rollback_cancel_body_context_pin.py +0 -65
- dqlite_dbapi-0.3.0/tests/test_aexit_rollback_debug_log.py +0 -71
- dqlite_dbapi-0.3.0/tests/test_aio_cascade_close_clears_executing_task.py +0 -37
- dqlite_dbapi-0.3.0/tests/test_aio_commit_rollback_no_tx.py +0 -62
- dqlite_dbapi-0.3.0/tests/test_aio_commit_rollback_no_tx_code_matrix.py +0 -75
- dqlite_dbapi-0.3.0/tests/test_aio_commit_rollback_wrapping.py +0 -125
- dqlite_dbapi-0.3.0/tests/test_aio_connection_finalizer_detached_on_close.py +0 -44
- dqlite_dbapi-0.3.0/tests/test_aio_cursor_drain_rows.py +0 -69
- dqlite_dbapi-0.3.0/tests/test_aio_cursor_row_factory_typeerror_wrap.py +0 -80
- dqlite_dbapi-0.3.0/tests/test_aio_execute_unlocked_silent_drop_scrubs_state.py +0 -102
- dqlite_dbapi-0.3.0/tests/test_aio_executemany_cursor_close_cascade.py +0 -23
- dqlite_dbapi-0.3.0/tests/test_aio_force_close_foreign_thread_cursor_closed_pre_cascade.py +0 -91
- dqlite_dbapi-0.3.0/tests/test_aio_force_close_transport_cursor_cascade.py +0 -78
- dqlite_dbapi-0.3.0/tests/test_aio_init_reexports_diagnostic_prefixes.py +0 -31
- dqlite_dbapi-0.3.0/tests/test_aio_loop_affinity_exc_class.py +0 -54
- dqlite_dbapi-0.3.0/tests/test_aio_module_attributes.py +0 -79
- dqlite_dbapi-0.3.0/tests/test_aio_row_factory_setter_loop_binding.py +0 -76
- dqlite_dbapi-0.3.0/tests/test_aio_setters_clear_messages_pep249.py +0 -37
- dqlite_dbapi-0.3.0/tests/test_aio_transaction_recheck_after_ensure_connection.py +0 -79
- dqlite_dbapi-0.3.0/tests/test_arraysize_validation.py +0 -107
- dqlite_dbapi-0.3.0/tests/test_async_check_loop_only_pid_before_loop.py +0 -55
- dqlite_dbapi-0.3.0/tests/test_async_close_cancel_arm_cancels_inner_pending_drain.py +0 -101
- dqlite_dbapi-0.3.0/tests/test_async_close_ensure_locks_guard.py +0 -34
- dqlite_dbapi-0.3.0/tests/test_async_close_finally_propagates_cancel.py +0 -127
- dqlite_dbapi-0.3.0/tests/test_async_close_op_lock_bounded.py +0 -128
- dqlite_dbapi-0.3.0/tests/test_async_close_outer_cancel_force_closes_transport.py +0 -89
- dqlite_dbapi-0.3.0/tests/test_async_close_race.py +0 -106
- dqlite_dbapi-0.3.0/tests/test_async_commit_rollback_force_close_on_outer_cancel.py +0 -101
- dqlite_dbapi-0.3.0/tests/test_async_commit_rollback_in_transaction_under_lock.py +0 -77
- dqlite_dbapi-0.3.0/tests/test_async_commit_rollback_loop_binding_first.py +0 -69
- dqlite_dbapi-0.3.0/tests/test_async_commit_rollback_messages_under_lock.py +0 -69
- dqlite_dbapi-0.3.0/tests/test_async_commit_rollback_op_lock_bounded.py +0 -54
- dqlite_dbapi-0.3.0/tests/test_async_commit_rollback_phase_budget.py +0 -92
- dqlite_dbapi-0.3.0/tests/test_async_commit_rollback_retry_after_invalidate.py +0 -111
- dqlite_dbapi-0.3.0/tests/test_async_commit_rollback_timeout_outer_scope_hint.py +0 -105
- dqlite_dbapi-0.3.0/tests/test_async_commit_rollback_timeout_phase_aware.py +0 -100
- dqlite_dbapi-0.3.0/tests/test_async_connect_cross_loop_diagnostic_on_fast_path.py +0 -47
- dqlite_dbapi-0.3.0/tests/test_async_connection.py +0 -75
- dqlite_dbapi-0.3.0/tests/test_async_connection_close_during_connect.py +0 -51
- dqlite_dbapi-0.3.0/tests/test_async_connection_cursor_fork_guard.py +0 -63
- dqlite_dbapi-0.3.0/tests/test_async_connection_ensure_connection_post_yield_close_shielded.py +0 -139
- dqlite_dbapi-0.3.0/tests/test_async_connection_race.py +0 -40
- dqlite_dbapi-0.3.0/tests/test_async_connection_setters_loop_binding.py +0 -60
- dqlite_dbapi-0.3.0/tests/test_async_connection_transaction.py +0 -48
- dqlite_dbapi-0.3.0/tests/test_async_cursor.py +0 -248
- dqlite_dbapi-0.3.0/tests/test_async_cursor_add_races_cascade_clear.py +0 -94
- dqlite_dbapi-0.3.0/tests/test_async_cursor_aenter_cross_task_executing_guard.py +0 -57
- dqlite_dbapi-0.3.0/tests/test_async_cursor_aenter_gc_parent_pep249.py +0 -23
- dqlite_dbapi-0.3.0/tests/test_async_cursor_aiter_cross_loop_fail_fast.py +0 -50
- dqlite_dbapi-0.3.0/tests/test_async_cursor_anext_cooperative_yield.py +0 -116
- dqlite_dbapi-0.3.0/tests/test_async_cursor_arraysize_row_factory_loop_binding.py +0 -68
- dqlite_dbapi-0.3.0/tests/test_async_cursor_callproc_nextset_scroll_loop_binding.py +0 -78
- dqlite_dbapi-0.3.0/tests/test_async_cursor_close_clears_executing_task.py +0 -59
- dqlite_dbapi-0.3.0/tests/test_async_cursor_close_is_sync_and_race_safe.py +0 -90
- dqlite_dbapi-0.3.0/tests/test_async_cursor_close_lock_free.py +0 -52
- dqlite_dbapi-0.3.0/tests/test_async_cursor_closed_state_pep249.py +0 -92
- dqlite_dbapi-0.3.0/tests/test_async_cursor_execute_executing_task_inside_try_frame.py +0 -47
- dqlite_dbapi-0.3.0/tests/test_async_cursor_execute_messages_clear.py +0 -59
- dqlite_dbapi-0.3.0/tests/test_async_cursor_executemany_validation_clears_executing_task.py +0 -94
- dqlite_dbapi-0.3.0/tests/test_async_cursor_fetch_loop_binding.py +0 -125
- dqlite_dbapi-0.3.0/tests/test_async_cursor_finally_clear_executing_task_unconditional.py +0 -41
- dqlite_dbapi-0.3.0/tests/test_async_cursor_late_add_proxy_swap.py +0 -34
- dqlite_dbapi-0.3.0/tests/test_async_cursor_loop_binding.py +0 -47
- dqlite_dbapi-0.3.0/tests/test_async_cursor_repr_with_gced_parent_connection.py +0 -29
- dqlite_dbapi-0.3.0/tests/test_async_cursor_rownumber_closed_cross_loop.py +0 -55
- dqlite_dbapi-0.3.0/tests/test_async_cursor_setinputsizes_loop_binding.py +0 -46
- dqlite_dbapi-0.3.0/tests/test_async_executemany_atomic.py +0 -58
- dqlite_dbapi-0.3.0/tests/test_async_executemany_close_race.py +0 -56
- dqlite_dbapi-0.3.0/tests/test_async_fetch_does_not_lazy_bind_loop.py +0 -41
- dqlite_dbapi-0.3.0/tests/test_async_fetchall_row_factory_cooperative_yield.py +0 -166
- dqlite_dbapi-0.3.0/tests/test_async_fetchall_row_factory_happy_path.py +0 -46
- dqlite_dbapi-0.3.0/tests/test_async_fetchmany_row_factory_cooperative_yield.py +0 -153
- dqlite_dbapi-0.3.0/tests/test_async_force_close_transport.py +0 -153
- dqlite_dbapi-0.3.0/tests/test_async_in_transaction_closed_foreign_loop.py +0 -58
- dqlite_dbapi-0.3.0/tests/test_async_in_transaction_loop_binding.py +0 -61
- dqlite_dbapi-0.3.0/tests/test_async_invalidated_property.py +0 -48
- dqlite_dbapi-0.3.0/tests/test_async_lock_binding.py +0 -111
- dqlite_dbapi-0.3.0/tests/test_async_resource_warning_gate.py +0 -109
- dqlite_dbapi-0.3.0/tests/test_async_resource_warning_sanitize_parity.py +0 -47
- dqlite_dbapi-0.3.0/tests/test_async_secondary_methods_clear_messages_before_guards.py +0 -117
- dqlite_dbapi-0.3.0/tests/test_async_transaction_owner_set_inside_try_frame.py +0 -78
- dqlite_dbapi-0.3.0/tests/test_async_unclosed_warning_shutdown_safety.py +0 -135
- dqlite_dbapi-0.3.0/tests/test_audit_2026_05_coverage_gaps.py +0 -256
- dqlite_dbapi-0.3.0/tests/test_audit_2026_05_dbapi_coverage.py +0 -332
- dqlite_dbapi-0.3.0/tests/test_autocommit_property.py +0 -64
- dqlite_dbapi-0.3.0/tests/test_autocommit_setter_exact_int_gate.py +0 -101
- dqlite_dbapi-0.3.0/tests/test_begin_rewrite_session_mode.py +0 -179
- dqlite_dbapi-0.3.0/tests/test_bind_typeerror_as_dataerror.py +0 -36
- dqlite_dbapi-0.3.0/tests/test_build_and_connect_base_exception_group_wrap.py +0 -105
- dqlite_dbapi-0.3.0/tests/test_build_and_connect_pragma_shield_observer.py +0 -58
- dqlite_dbapi-0.3.0/tests/test_build_and_connect_resolve_leader_oserror_wrap.py +0 -57
- dqlite_dbapi-0.3.0/tests/test_build_and_connect_wire_encode_error_translation.py +0 -76
- dqlite_dbapi-0.3.0/tests/test_busy_retry_async_wiring.py +0 -138
- dqlite_dbapi-0.3.0/tests/test_busy_retry_classifier.py +0 -69
- dqlite_dbapi-0.3.0/tests/test_busy_retry_curve.py +0 -81
- dqlite_dbapi-0.3.0/tests/test_busy_retry_sync_wiring.py +0 -129
- dqlite_dbapi-0.3.0/tests/test_call_client_base_exception_group_wrap.py +0 -71
- dqlite_dbapi-0.3.0/tests/test_call_client_baseexceptiongroup_cancel_split.py +0 -80
- dqlite_dbapi-0.3.0/tests/test_call_client_mapping.py +0 -105
- dqlite_dbapi-0.3.0/tests/test_call_client_typeerror_message_does_not_blame_bind.py +0 -32
- dqlite_dbapi-0.3.0/tests/test_call_client_wire_encode_error.py +0 -30
- dqlite_dbapi-0.3.0/tests/test_callproc_scroll_clear_messages.py +0 -78
- dqlite_dbapi-0.3.0/tests/test_cascade_cursors_clears_messages.py +0 -79
- dqlite_dbapi-0.3.0/tests/test_check_same_thread_kwarg.py +0 -180
- dqlite_dbapi-0.3.0/tests/test_classify_caller_sql.py +0 -84
- dqlite_dbapi-0.3.0/tests/test_classify_caller_sql_structural_reject_precedence.py +0 -70
- dqlite_dbapi-0.3.0/tests/test_classify_operational_code_zero.py +0 -20
- dqlite_dbapi-0.3.0/tests/test_cleanup_loop_thread_disarms_inner_and_reaps_drain.py +0 -296
- dqlite_dbapi-0.3.0/tests/test_cleanup_loop_thread_finalizer_fork_safe.py +0 -150
- dqlite_dbapi-0.3.0/tests/test_cleanup_loop_thread_foreign_loop_short_floor.py +0 -160
- dqlite_dbapi-0.3.0/tests/test_close_bypass_path_releases_latched_op_lock.py +0 -59
- dqlite_dbapi-0.3.0/tests/test_close_resource_leak.py +0 -31
- dqlite_dbapi-0.3.0/tests/test_close_signal_handler_reentry.py +0 -125
- dqlite_dbapi-0.3.0/tests/test_close_surfaces_op_lock_contention.py +0 -52
- dqlite_dbapi-0.3.0/tests/test_close_thread_join_uses_close_timeout.py +0 -157
- dqlite_dbapi-0.3.0/tests/test_close_timeout_floor_enforcement.py +0 -47
- dqlite_dbapi-0.3.0/tests/test_close_timeout_plumbing.py +0 -101
- dqlite_dbapi-0.3.0/tests/test_closed_property.py +0 -96
- dqlite_dbapi-0.3.0/tests/test_cluster_policy_rejection_prefix_constant.py +0 -53
- dqlite_dbapi-0.3.0/tests/test_code_to_exception_wire_ssot.py +0 -35
- dqlite_dbapi-0.3.0/tests/test_collated_coverage_gaps.py +0 -151
- dqlite_dbapi-0.3.0/tests/test_commit_after_external_close.py +0 -67
- dqlite_dbapi-0.3.0/tests/test_commit_leader_flip_ambiguous_commit_error.py +0 -241
- dqlite_dbapi-0.3.0/tests/test_commit_leader_flip_propagates.py +0 -114
- dqlite_dbapi-0.3.0/tests/test_commit_no_spurious_connect.py +0 -27
- dqlite_dbapi-0.3.0/tests/test_commit_rollback_shortcircuit_uses_in_transaction.py +0 -130
- dqlite_dbapi-0.3.0/tests/test_connect_baseexceptiongroup_cancel_split.py +0 -86
- dqlite_dbapi-0.3.0/tests/test_connect_busy_timeout_kwarg.py +0 -86
- dqlite_dbapi-0.3.0/tests/test_connect_clears_messages.py +0 -41
- dqlite_dbapi-0.3.0/tests/test_connect_iso_autocommit_round_trip.py +0 -97
- dqlite_dbapi-0.3.0/tests/test_connect_preserves_code.py +0 -69
- dqlite_dbapi-0.3.0/tests/test_connection.py +0 -38
- dqlite_dbapi-0.3.0/tests/test_connection_after_fork_raises.py +0 -125
- dqlite_dbapi-0.3.0/tests/test_connection_autocommit_isolation_level_closed_state.py +0 -66
- dqlite_dbapi-0.3.0/tests/test_connection_close_cascades_cursors.py +0 -67
- dqlite_dbapi-0.3.0/tests/test_connection_close_cursor_cascade_atomicity.py +0 -62
- dqlite_dbapi-0.3.0/tests/test_connection_close_idempotent_cross_thread.py +0 -55
- dqlite_dbapi-0.3.0/tests/test_connection_closed_checks.py +0 -205
- dqlite_dbapi-0.3.0/tests/test_connection_cursor_closed_state_precedes_unknown_kwarg.py +0 -35
- dqlite_dbapi-0.3.0/tests/test_connection_database_empty_string_rejected.py +0 -45
- dqlite_dbapi-0.3.0/tests/test_connection_database_kwarg_isinstance.py +0 -38
- dqlite_dbapi-0.3.0/tests/test_connection_enter_eager.py +0 -30
- dqlite_dbapi-0.3.0/tests/test_connection_exit_commit_ki_breadcrumb.py +0 -79
- dqlite_dbapi-0.3.0/tests/test_connection_exit_short_circuits_on_closed.py +0 -70
- dqlite_dbapi-0.3.0/tests/test_connection_messages_cleared_by_cursor_methods.py +0 -53
- dqlite_dbapi-0.3.0/tests/test_connection_row_factory_setter_closed_check.py +0 -50
- dqlite_dbapi-0.3.0/tests/test_context_manager_exit.py +0 -79
- dqlite_dbapi-0.3.0/tests/test_convert_bind_param_unsupported_vs_bad_adapter_message.py +0 -37
- dqlite_dbapi-0.3.0/tests/test_convert_params_async_cooperative_yield.py +0 -121
- dqlite_dbapi-0.3.0/tests/test_convert_params_conform_raise_propagates_unwrapped.py +0 -51
- dqlite_dbapi-0.3.0/tests/test_convert_rows_async_cooperative_yield.py +0 -106
- dqlite_dbapi-0.3.0/tests/test_convert_rows_async_probe_does_not_walk_all_row_types_up_front.py +0 -85
- dqlite_dbapi-0.3.0/tests/test_convert_rows_converter_free_fast_path.py +0 -109
- dqlite_dbapi-0.3.0/tests/test_cte_prefix_stripper_edges.py +0 -71
- dqlite_dbapi-0.3.0/tests/test_cursor.py +0 -215
- dqlite_dbapi-0.3.0/tests/test_cursor_arraysize_row_factory_setter_thread.py +0 -115
- dqlite_dbapi-0.3.0/tests/test_cursor_close_clears_messages.py +0 -32
- dqlite_dbapi-0.3.0/tests/test_cursor_close_cross_thread.py +0 -43
- dqlite_dbapi-0.3.0/tests/test_cursor_close_releases_connection_pin.py +0 -46
- dqlite_dbapi-0.3.0/tests/test_cursor_closed_state_pep249.py +0 -74
- dqlite_dbapi-0.3.0/tests/test_cursor_completed_iterations_resets_on_single_execute.py +0 -73
- dqlite_dbapi-0.3.0/tests/test_cursor_connection_partial_init_attributeerror.py +0 -42
- dqlite_dbapi-0.3.0/tests/test_cursor_connection_property_after_gc_pep249.py +0 -45
- dqlite_dbapi-0.3.0/tests/test_cursor_execute_state_reset_on_failure.py +0 -366
- dqlite_dbapi-0.3.0/tests/test_cursor_executemany_outer_shape_check.py +0 -72
- dqlite_dbapi-0.3.0/tests/test_cursor_force_close_race.py +0 -66
- dqlite_dbapi-0.3.0/tests/test_cursor_iter_clears_messages_pep249.py +0 -71
- dqlite_dbapi-0.3.0/tests/test_cursor_iterator_reset.py +0 -91
- dqlite_dbapi-0.3.0/tests/test_cursor_kwargs_rejection_precedence.py +0 -61
- dqlite_dbapi-0.3.0/tests/test_cursor_next_row_factory_raise_replays_row.py +0 -104
- dqlite_dbapi-0.3.0/tests/test_cursor_per_row_types.py +0 -153
- dqlite_dbapi-0.3.0/tests/test_cursor_post_close_property_reads.py +0 -100
- dqlite_dbapi-0.3.0/tests/test_cursor_protocol_check.py +0 -50
- dqlite_dbapi-0.3.0/tests/test_cursor_rejects_generator_params.py +0 -38
- dqlite_dbapi-0.3.0/tests/test_cursor_repr_with_gced_parent_connection.py +0 -53
- dqlite_dbapi-0.3.0/tests/test_cursor_row_factory_rejecting_setter.py +0 -97
- dqlite_dbapi-0.3.0/tests/test_cursor_row_factory_subclass_inheritance.py +0 -68
- dqlite_dbapi-0.3.0/tests/test_cursor_row_index_reset.py +0 -76
- dqlite_dbapi-0.3.0/tests/test_cursor_rowcount_after_ddl_stdlib_parity.py +0 -195
- dqlite_dbapi-0.3.0/tests/test_cursor_rownumber_affinity.py +0 -54
- dqlite_dbapi-0.3.0/tests/test_cursor_rownumber_post_close.py +0 -64
- dqlite_dbapi-0.3.0/tests/test_cursor_scroll_mode_validation.py +0 -126
- dqlite_dbapi-0.3.0/tests/test_cursor_secondary_methods_positional_only.py +0 -116
- dqlite_dbapi-0.3.0/tests/test_cursor_setinputsizes_affinity_precedes_shape.py +0 -151
- dqlite_dbapi-0.3.0/tests/test_cursor_setinputsizes_setoutputsize_closed_state_precedes_validation.py +0 -90
- dqlite_dbapi-0.3.0/tests/test_cursor_setters_clear_messages_pep249.py +0 -48
- dqlite_dbapi-0.3.0/tests/test_cursor_stop_iteration_contract.py +0 -82
- dqlite_dbapi-0.3.0/tests/test_cursors_weakset_state_lock.py +0 -104
- dqlite_dbapi-0.3.0/tests/test_dbapi_commit_deferred_fk.py +0 -31
- dqlite_dbapi-0.3.0/tests/test_dbapi_fork_guard_cluster.py +0 -204
- dqlite_dbapi-0.3.0/tests/test_dbapi_hardening.py +0 -548
- dqlite_dbapi-0.3.0/tests/test_description_null_first_row_resolves.py +0 -136
- dqlite_dbapi-0.3.0/tests/test_description_null_rescue_scan_yields_cooperatively.py +0 -144
- dqlite_dbapi-0.3.0/tests/test_description_null_type_code_to_none.py +0 -90
- dqlite_dbapi-0.3.0/tests/test_description_strict_type_codes.py +0 -53
- dqlite_dbapi-0.3.0/tests/test_description_type_code_unknown_sentinel.py +0 -42
- dqlite_dbapi-0.3.0/tests/test_diagnostic_messages.py +0 -131
- dqlite_dbapi-0.3.0/tests/test_dial_attempt_timeout_propagation.py +0 -96
- dqlite_dbapi-0.3.0/tests/test_dial_func_propagation.py +0 -67
- dqlite_dbapi-0.3.0/tests/test_dial_func_propagation_to_leader_resolve.py +0 -152
- dqlite_dbapi-0.3.0/tests/test_dqlite_namespace_error_classification.py +0 -37
- dqlite_dbapi-0.3.0/tests/test_ensure_loop_fast_path_snapshot_dcl_defence.py +0 -79
- dqlite_dbapi-0.3.0/tests/test_ensure_loop_post_fork_pid_guard.py +0 -108
- dqlite_dbapi-0.3.0/tests/test_ensure_loop_signal_teardown.py +0 -125
- dqlite_dbapi-0.3.0/tests/test_error_code_mapping.py +0 -91
- dqlite_dbapi-0.3.0/tests/test_exception_coded_mixin.py +0 -90
- dqlite_dbapi-0.3.0/tests/test_exception_mapping.py +0 -65
- dqlite_dbapi-0.3.0/tests/test_exception_wrapping.py +0 -102
- dqlite_dbapi-0.3.0/tests/test_execute_caller_param_shape_preserves_prior_state.py +0 -89
- dqlite_dbapi-0.3.0/tests/test_execute_multi_statement_rejection.py +0 -87
- dqlite_dbapi-0.3.0/tests/test_execute_reject_scrubs_prior_result_state.py +0 -156
- dqlite_dbapi-0.3.0/tests/test_executemany_accumulator_semantics.py +0 -87
- dqlite_dbapi-0.3.0/tests/test_executemany_basecaught_state_reset.py +0 -172
- dqlite_dbapi-0.3.0/tests/test_executemany_cancel_restores_pre_batch_lastrowid.py +0 -132
- dqlite_dbapi-0.3.0/tests/test_executemany_cancellederror_restores_pre_batch_lastrowid.py +0 -101
- dqlite_dbapi-0.3.0/tests/test_executemany_classifier_resets_state_first.py +0 -89
- dqlite_dbapi-0.3.0/tests/test_executemany_completed_iterations_snapshot_conditional_restore.py +0 -128
- dqlite_dbapi-0.3.0/tests/test_executemany_dict_subclass_accepted.py +0 -64
- dqlite_dbapi-0.3.0/tests/test_executemany_empty.py +0 -92
- dqlite_dbapi-0.3.0/tests/test_executemany_empty_batch_preserves_lastrowid.py +0 -52
- dqlite_dbapi-0.3.0/tests/test_executemany_lastrowid_restored_pre_batch_on_raise.py +0 -145
- dqlite_dbapi-0.3.0/tests/test_executemany_none_seq_rejected.py +0 -59
- dqlite_dbapi-0.3.0/tests/test_executemany_per_iter_structural_rejects.py +0 -77
- dqlite_dbapi-0.3.0/tests/test_executemany_pragma_with_leading_comment.py +0 -62
- dqlite_dbapi-0.3.0/tests/test_executemany_push_skips_cascade_zeroed_cursor.py +0 -64
- dqlite_dbapi-0.3.0/tests/test_executemany_reject_scrubs_prior_result_state.py +0 -231
- dqlite_dbapi-0.3.0/tests/test_executemany_rejects_pure_queries.py +0 -93
- dqlite_dbapi-0.3.0/tests/test_executemany_rejects_transaction_verbs.py +0 -235
- dqlite_dbapi-0.3.0/tests/test_executemany_returning_cap.py +0 -61
- dqlite_dbapi-0.3.0/tests/test_exit_logs_rollback_failure.py +0 -76
- dqlite_dbapi-0.3.0/tests/test_fetchall_row_factory_raise_replay.py +0 -125
- dqlite_dbapi-0.3.0/tests/test_fetchmany_edges.py +0 -63
- dqlite_dbapi-0.3.0/tests/test_fetchmany_negative_size_rejected.py +0 -78
- dqlite_dbapi-0.3.0/tests/test_fetchmany_non_int_validation.py +0 -66
- dqlite_dbapi-0.3.0/tests/test_fetchmany_one_clear_per_call_pep249.py +0 -42
- dqlite_dbapi-0.3.0/tests/test_fetchmany_row_factory_raise_replay.py +0 -111
- dqlite_dbapi-0.3.0/tests/test_finalizer_strict_warnings.py +0 -38
- dqlite_dbapi-0.3.0/tests/test_force_close_cancel_and_observe_narrow_suppress.py +0 -19
- dqlite_dbapi-0.3.0/tests/test_force_close_transport_cleanup_tail_unconditional.py +0 -88
- dqlite_dbapi-0.3.0/tests/test_force_close_transport_clears_messages.py +0 -87
- dqlite_dbapi-0.3.0/tests/test_force_close_transport_cross_thread_cancel_threadsafe.py +0 -101
- dqlite_dbapi-0.3.0/tests/test_force_close_transport_cursor_cascade_threadsafe.py +0 -135
- dqlite_dbapi-0.3.0/tests/test_force_close_transport_defensive_arms.py +0 -83
- dqlite_dbapi-0.3.0/tests/test_force_close_transport_disarms_inner_finalizer.py +0 -68
- dqlite_dbapi-0.3.0/tests/test_force_close_transport_nulls_lock_and_loop_slots.py +0 -74
- dqlite_dbapi-0.3.0/tests/test_force_close_transport_pending_drain_resnapshot.py +0 -115
- dqlite_dbapi-0.3.0/tests/test_force_close_transport_writer_cross_thread.py +0 -114
- dqlite_dbapi-0.3.0/tests/test_fork_diagnostic_includes_pids.py +0 -41
- dqlite_dbapi-0.3.0/tests/test_format_utc_offset_non_timedelta_guard.py +0 -50
- dqlite_dbapi-0.3.0/tests/test_gc_cleanup.py +0 -43
- dqlite_dbapi-0.3.0/tests/test_get_async_connection_force_close_race.py +0 -101
- dqlite_dbapi-0.3.0/tests/test_get_resolve_leader_cluster_no_loop_raises_runtime_error.py +0 -24
- dqlite_dbapi-0.3.0/tests/test_in_transaction_after_close.py +0 -95
- dqlite_dbapi-0.3.0/tests/test_in_transaction_closed_foreign_thread.py +0 -85
- dqlite_dbapi-0.3.0/tests/test_in_transaction_property.py +0 -35
- dqlite_dbapi-0.3.0/tests/test_in_transaction_property_under_check_same_thread.py +0 -101
- dqlite_dbapi-0.3.0/tests/test_is_multi_statement_walker.py +0 -40
- dqlite_dbapi-0.3.0/tests/test_is_no_transaction_error.py +0 -113
- dqlite_dbapi-0.3.0/tests/test_is_row_returning_multiline_whitespace.py +0 -88
- dqlite_dbapi-0.3.0/tests/test_is_row_returning_sql_noise.py +0 -70
- dqlite_dbapi-0.3.0/tests/test_iterdump_stub_signature_3_13_filter_kwarg.py +0 -52
- dqlite_dbapi-0.3.0/tests/test_join_budget_helper_shutdown_safety.py +0 -70
- dqlite_dbapi-0.3.0/tests/test_lastrowid_insert_only.py +0 -68
- dqlite_dbapi-0.3.0/tests/test_lastrowid_signed_int64_cast.py +0 -51
- dqlite_dbapi-0.3.0/tests/test_leader_redirect_on_connect.py +0 -207
- dqlite_dbapi-0.3.0/tests/test_max_continuation_frames_upper_bound.py +0 -53
- dqlite_dbapi-0.3.0/tests/test_max_message_size_round_trip.py +0 -51
- dqlite_dbapi-0.3.0/tests/test_max_total_rows_plumbing.py +0 -63
- dqlite_dbapi-0.3.0/tests/test_max_total_rows_validation.py +0 -47
- dqlite_dbapi-0.3.0/tests/test_messages_attribute.py +0 -33
- dqlite_dbapi-0.3.0/tests/test_messages_clear_on_ctxmgr_entry.py +0 -67
- dqlite_dbapi-0.3.0/tests/test_module_attributes.py +0 -223
- dqlite_dbapi-0.3.0/tests/test_module_level_stubs.py +0 -88
- dqlite_dbapi-0.3.0/tests/test_multi_statement_classifier_allows_trigger_bodies.py +0 -66
- dqlite_dbapi-0.3.0/tests/test_no_transaction_substrings_source_of_truth.py +0 -22
- dqlite_dbapi-0.3.0/tests/test_no_tx_primary_codes_invariant.py +0 -41
- dqlite_dbapi-0.3.0/tests/test_op_lock_bounded_acquire.py +0 -69
- dqlite_dbapi-0.3.0/tests/test_operational_error_classification.py +0 -64
- dqlite_dbapi-0.3.0/tests/test_operational_error_raw_message.py +0 -62
- dqlite_dbapi-0.3.0/tests/test_param_conversion.py +0 -59
- dqlite_dbapi-0.3.0/tests/test_param_validation.py +0 -42
- dqlite_dbapi-0.3.0/tests/test_pep249_database_error_routing.py +0 -41
- dqlite_dbapi-0.3.0/tests/test_pep249_messages_clearing.py +0 -233
- dqlite_dbapi-0.3.0/tests/test_pep249_messages_independence.py +0 -73
- dqlite_dbapi-0.3.0/tests/test_pep249_misc_pins.py +0 -124
- dqlite_dbapi-0.3.0/tests/test_pragma_busy_timeout_intercept.py +0 -208
- dqlite_dbapi-0.3.0/tests/test_pragma_write_description_none.py +0 -48
- dqlite_dbapi-0.3.0/tests/test_property_round_trip_and_fork_guard.py +0 -223
- dqlite_dbapi-0.3.0/tests/test_property_setters_do_not_mirror_to_inner_async_conn.py +0 -44
- dqlite_dbapi-0.3.0/tests/test_protocol_serialization.py +0 -79
- dqlite_dbapi-0.3.0/tests/test_repr_sanitizes_address.py +0 -131
- dqlite_dbapi-0.3.0/tests/test_resolve_leader_cache_dial_func_id_recycle.py +0 -130
- dqlite_dbapi-0.3.0/tests/test_resolve_leader_cache_lock_atfork_hook.py +0 -25
- dqlite_dbapi-0.3.0/tests/test_resolve_leader_cluster_cache.py +0 -93
- dqlite_dbapi-0.3.0/tests/test_resolve_leader_cluster_constructs_outside_lock.py +0 -87
- dqlite_dbapi-0.3.0/tests/test_resolve_leader_cross_loop_isolation.py +0 -187
- dqlite_dbapi-0.3.0/tests/test_rollback_busy_retry.py +0 -81
- dqlite_dbapi-0.3.0/tests/test_row_factory_sqlite3_row_surfaces_dataerror.py +0 -53
- dqlite_dbapi-0.3.0/tests/test_rownumber_closed_cursor_returns_none_documented.py +0 -60
- dqlite_dbapi-0.3.0/tests/test_run_sync_ensure_loop_raise_closes_coroutine.py +0 -59
- dqlite_dbapi-0.3.0/tests/test_run_sync_finally_suppresses_double_release.py +0 -83
- dqlite_dbapi-0.3.0/tests/test_run_sync_keyboard_interrupt.py +0 -287
- dqlite_dbapi-0.3.0/tests/test_run_sync_keyboard_interrupt_cleanup_paths.py +0 -133
- dqlite_dbapi-0.3.0/tests/test_run_sync_ki_post_schedule_pre_await.py +0 -71
- dqlite_dbapi-0.3.0/tests/test_run_sync_ki_race_recovery.py +0 -164
- dqlite_dbapi-0.3.0/tests/test_run_sync_loop_closed_during_schedule.py +0 -93
- dqlite_dbapi-0.3.0/tests/test_run_sync_other_runtimeerror_propagates.py +0 -88
- dqlite_dbapi-0.3.0/tests/test_run_sync_owner_stamp_window.py +0 -82
- dqlite_dbapi-0.3.0/tests/test_run_sync_timeout.py +0 -352
- dqlite_dbapi-0.3.0/tests/test_run_sync_timeout_recovery_preserves_conn.py +0 -85
- dqlite_dbapi-0.3.0/tests/test_secondary_methods_clear_messages_before_thread_check.py +0 -75
- dqlite_dbapi-0.3.0/tests/test_setinputsizes_clears_messages_per_pep249.py +0 -45
- dqlite_dbapi-0.3.0/tests/test_setinputsizes_short_circuit_on_closed_connection.py +0 -83
- dqlite_dbapi-0.3.0/tests/test_setter_closed_state_precedes_thread.py +0 -63
- dqlite_dbapi-0.3.0/tests/test_strip_leading_comments_bom.py +0 -52
- dqlite_dbapi-0.3.0/tests/test_strip_leading_comments_parity.py +0 -62
- dqlite_dbapi-0.3.0/tests/test_strip_sql_noise_backtick_identifier.py +0 -43
- dqlite_dbapi-0.3.0/tests/test_submodule_all.py +0 -32
- dqlite_dbapi-0.3.0/tests/test_sync_close_op_lock_owner_aware_probe.py +0 -78
- dqlite_dbapi-0.3.0/tests/test_sync_commit_rollback_closed_precedes_thread.py +0 -38
- dqlite_dbapi-0.3.0/tests/test_sync_commit_rollback_inside_ctxmgr_guard.py +0 -87
- dqlite_dbapi-0.3.0/tests/test_sync_commit_rollback_messages_under_lock.py +0 -64
- dqlite_dbapi-0.3.0/tests/test_sync_commit_rollback_protocol_recheck.py +0 -37
- dqlite_dbapi-0.3.0/tests/test_sync_commit_rollback_retry_after_invalidate.py +0 -68
- dqlite_dbapi-0.3.0/tests/test_sync_cursor_post_add_closed_recheck.py +0 -102
- dqlite_dbapi-0.3.0/tests/test_sync_cursor_rownumber_closed_cross_thread.py +0 -50
- dqlite_dbapi-0.3.0/tests/test_sync_execute_async_pre_wire_close_recheck.py +0 -65
- dqlite_dbapi-0.3.0/tests/test_sync_execute_async_silent_drop_scrubs_state.py +0 -94
- dqlite_dbapi-0.3.0/tests/test_sync_executemany_close_race.py +0 -56
- dqlite_dbapi-0.3.0/tests/test_sync_exit_signal_mid_rollback.py +0 -66
- dqlite_dbapi-0.3.0/tests/test_sync_fetchmany_cancel_atomic.py +0 -60
- dqlite_dbapi-0.3.0/tests/test_sync_force_close_transport.py +0 -147
- dqlite_dbapi-0.3.0/tests/test_sync_force_close_transport_pending_drain_reap.py +0 -145
- dqlite_dbapi-0.3.0/tests/test_sync_invalidated_property.py +0 -47
- dqlite_dbapi-0.3.0/tests/test_sync_run_sync_multiphase_timeout.py +0 -87
- dqlite_dbapi-0.3.0/tests/test_sync_transaction_ctxmgr_owner_slot_busy_sentinel.py +0 -14
- dqlite_dbapi-0.3.0/tests/test_sync_transaction_owner_token_bookkeeping.py +0 -110
- dqlite_dbapi-0.3.0/tests/test_sync_transaction_signal_in_restore_reaps_owner.py +0 -67
- dqlite_dbapi-0.3.0/tests/test_thread_safety_enforcement.py +0 -327
- dqlite_dbapi-0.3.0/tests/test_transaction_ctxmgr_state_lock.py +0 -129
- dqlite_dbapi-0.3.0/tests/test_unsupported_method_stubs.py +0 -352
- dqlite_dbapi-0.3.0/tests/test_validate_positive_int_pep249_wrap.py +0 -87
- {dqlite_dbapi-0.3.0 → dqlite_dbapi-0.4.0}/.github/workflows/publish-to-pypi.yml +0 -0
- {dqlite_dbapi-0.3.0 → dqlite_dbapi-0.4.0}/.gitignore +0 -0
- {dqlite_dbapi-0.3.0 → dqlite_dbapi-0.4.0}/LICENSE.md +0 -0
- {dqlite_dbapi-0.3.0 → dqlite_dbapi-0.4.0}/docs/differences-from-aiosqlite.md +0 -0
- {dqlite_dbapi-0.3.0 → dqlite_dbapi-0.4.0}/src/dqlitedbapi/py.typed +0 -0
- {dqlite_dbapi-0.3.0 → dqlite_dbapi-0.4.0}/tests/__init__.py +0 -0
- {dqlite_dbapi-0.3.0 → dqlite_dbapi-0.4.0}/tests/aio/test_async_iter_protocol_invariants.py +0 -0
- {dqlite_dbapi-0.3.0 → dqlite_dbapi-0.4.0}/tests/integration/__init__.py +0 -0
- {dqlite_dbapi-0.3.0 → dqlite_dbapi-0.4.0}/tests/integration/conftest.py +0 -0
- {dqlite_dbapi-0.3.0 → dqlite_dbapi-0.4.0}/tests/integration/test_arraysize_zero_fetchmany.py +0 -0
- {dqlite_dbapi-0.3.0 → dqlite_dbapi-0.4.0}/tests/integration/test_async_cursor_concurrent_execute_rejected.py +0 -0
- {dqlite_dbapi-0.3.0 → dqlite_dbapi-0.4.0}/tests/integration/test_async_cursor_edge_branches.py +0 -0
- {dqlite_dbapi-0.3.0 → dqlite_dbapi-0.4.0}/tests/integration/test_async_fetchmany_cancel.py +0 -0
- {dqlite_dbapi-0.3.0 → dqlite_dbapi-0.4.0}/tests/integration/test_async_returning_zero_rows.py +0 -0
- {dqlite_dbapi-0.3.0 → dqlite_dbapi-0.4.0}/tests/integration/test_async_transaction_ctxmgr_rejects_stray_commit.py +0 -0
- {dqlite_dbapi-0.3.0 → dqlite_dbapi-0.4.0}/tests/integration/test_boolean_conversion.py +0 -0
- {dqlite_dbapi-0.3.0 → dqlite_dbapi-0.4.0}/tests/integration/test_close_implicit_rollback_pep249.py +0 -0
- {dqlite_dbapi-0.3.0 → dqlite_dbapi-0.4.0}/tests/integration/test_concurrent_cursors_visibility.py +0 -0
- {dqlite_dbapi-0.3.0 → dqlite_dbapi-0.4.0}/tests/integration/test_constraint_extended_codes.py +0 -0
- {dqlite_dbapi-0.3.0 → dqlite_dbapi-0.4.0}/tests/integration/test_context_manager.py +0 -0
- {dqlite_dbapi-0.3.0 → dqlite_dbapi-0.4.0}/tests/integration/test_cte_prefixed_dml_metadata.py +0 -0
- {dqlite_dbapi-0.3.0 → dqlite_dbapi-0.4.0}/tests/integration/test_cursor_fetch_after_non_row_execute.py +0 -0
- {dqlite_dbapi-0.3.0 → dqlite_dbapi-0.4.0}/tests/integration/test_cursor_misc_invariant_pins.py +0 -0
- {dqlite_dbapi-0.3.0 → dqlite_dbapi-0.4.0}/tests/integration/test_cursor_row_index_after_fetchall.py +0 -0
- {dqlite_dbapi-0.3.0 → dqlite_dbapi-0.4.0}/tests/integration/test_description_after_ddl_pragma.py +0 -0
- {dqlite_dbapi-0.3.0 → dqlite_dbapi-0.4.0}/tests/integration/test_description_survives_empty_fetchall.py +0 -0
- {dqlite_dbapi-0.3.0 → dqlite_dbapi-0.4.0}/tests/integration/test_executemany_cte_dml.py +0 -0
- {dqlite_dbapi-0.3.0 → dqlite_dbapi-0.4.0}/tests/integration/test_executemany_generator.py +0 -0
- {dqlite_dbapi-0.3.0 → dqlite_dbapi-0.4.0}/tests/integration/test_executemany_preserves_lastrowid.py +0 -0
- {dqlite_dbapi-0.3.0 → dqlite_dbapi-0.4.0}/tests/integration/test_executemany_returning.py +0 -0
- {dqlite_dbapi-0.3.0 → dqlite_dbapi-0.4.0}/tests/integration/test_integrity_error.py +0 -0
- {dqlite_dbapi-0.3.0 → dqlite_dbapi-0.4.0}/tests/integration/test_lastrowid_autoincrement.py +0 -0
- {dqlite_dbapi-0.3.0 → dqlite_dbapi-0.4.0}/tests/integration/test_lastrowid_cursor_scoped.py +0 -0
- {dqlite_dbapi-0.3.0 → dqlite_dbapi-0.4.0}/tests/integration/test_lastrowid_survives_cross_cursor_rollback.py +0 -0
- {dqlite_dbapi-0.3.0 → dqlite_dbapi-0.4.0}/tests/integration/test_multiline_returning_rows.py +0 -0
- {dqlite_dbapi-0.3.0 → dqlite_dbapi-0.4.0}/tests/integration/test_nan_inf_float.py +0 -0
- {dqlite_dbapi-0.3.0 → dqlite_dbapi-0.4.0}/tests/integration/test_negative_zero_and_subnormal_float.py +0 -0
- {dqlite_dbapi-0.3.0 → dqlite_dbapi-0.4.0}/tests/integration/test_no_transaction_error_wording.py +0 -0
- {dqlite_dbapi-0.3.0 → dqlite_dbapi-0.4.0}/tests/integration/test_null_vs_empty_text_blob_three_way.py +0 -0
- {dqlite_dbapi-0.3.0 → dqlite_dbapi-0.4.0}/tests/integration/test_pragma_read_rowcount_minus_one.py +0 -0
- {dqlite_dbapi-0.3.0 → dqlite_dbapi-0.4.0}/tests/integration/test_query_detection.py +0 -0
- {dqlite_dbapi-0.3.0 → dqlite_dbapi-0.4.0}/tests/integration/test_returning.py +0 -0
- {dqlite_dbapi-0.3.0 → dqlite_dbapi-0.4.0}/tests/integration/test_savepoint_dbapi.py +0 -0
- {dqlite_dbapi-0.3.0 → dqlite_dbapi-0.4.0}/tests/integration/test_savepoint_failure_responses.py +0 -0
- {dqlite_dbapi-0.3.0 → dqlite_dbapi-0.4.0}/tests/integration/test_sqlite_version_pin.py +0 -0
- {dqlite_dbapi-0.3.0 → dqlite_dbapi-0.4.0}/tests/integration/test_sync_transaction_ctxmgr_rejects_stray_commit.py +0 -0
- {dqlite_dbapi-0.3.0 → dqlite_dbapi-0.4.0}/tests/test_aconn_execute_with_parameters.py +0 -0
- {dqlite_dbapi-0.3.0 → dqlite_dbapi-0.4.0}/tests/test_aconn_factory_getters.py +0 -0
- {dqlite_dbapi-0.3.0 → dqlite_dbapi-0.4.0}/tests/test_aio_description_tuple_exported.py +0 -0
- {dqlite_dbapi-0.3.0 → dqlite_dbapi-0.4.0}/tests/test_aio_register_adapter_export.py +0 -0
- {dqlite_dbapi-0.3.0 → dqlite_dbapi-0.4.0}/tests/test_aio_register_converter_stubs.py +0 -0
- {dqlite_dbapi-0.3.0 → dqlite_dbapi-0.4.0}/tests/test_async_backup_not_async_def.py +0 -0
- {dqlite_dbapi-0.3.0 → dqlite_dbapi-0.4.0}/tests/test_async_cursor_aclose_alias.py +0 -0
- {dqlite_dbapi-0.3.0 → dqlite_dbapi-0.4.0}/tests/test_async_execute_busy_retry_releases_op_lock_between_attempts.py +0 -0
- {dqlite_dbapi-0.3.0 → dqlite_dbapi-0.4.0}/tests/test_async_executescript_stub_raises_on_call.py +0 -0
- {dqlite_dbapi-0.3.0 → dqlite_dbapi-0.4.0}/tests/test_binary_typeerror_stdlib_parity.py +0 -0
- {dqlite_dbapi-0.3.0 → dqlite_dbapi-0.4.0}/tests/test_classify_caller_sql_hardening.py +0 -0
- {dqlite_dbapi-0.3.0 → dqlite_dbapi-0.4.0}/tests/test_conftest_adapter_registry_isolation.py +0 -0
- {dqlite_dbapi-0.3.0 → dqlite_dbapi-0.4.0}/tests/test_connect_closed_precedes_thread_affinity.py +0 -0
- {dqlite_dbapi-0.3.0 → dqlite_dbapi-0.4.0}/tests/test_connection_cursor_class_attribute.py +0 -0
- {dqlite_dbapi-0.3.0 → dqlite_dbapi-0.4.0}/tests/test_connection_executemany_outer_shape_check.py +0 -0
- {dqlite_dbapi-0.3.0 → dqlite_dbapi-0.4.0}/tests/test_cursor_close_preserves_body_exception_on_messages_strip.py +0 -0
- {dqlite_dbapi-0.3.0 → dqlite_dbapi-0.4.0}/tests/test_cursor_closed_after_connection_gc_pep249.py +0 -0
- {dqlite_dbapi-0.3.0 → dqlite_dbapi-0.4.0}/tests/test_cursor_execute_non_str_operation.py +0 -0
- {dqlite_dbapi-0.3.0 → dqlite_dbapi-0.4.0}/tests/test_cursor_executemany_non_str_operation.py +0 -0
- {dqlite_dbapi-0.3.0 → dqlite_dbapi-0.4.0}/tests/test_cursor_factory_kwarg_rejected.py +0 -0
- {dqlite_dbapi-0.3.0 → dqlite_dbapi-0.4.0}/tests/test_dbapi_default_timeouts_use_client_constants.py +0 -0
- {dqlite_dbapi-0.3.0 → dqlite_dbapi-0.4.0}/tests/test_dbapi_exception_message_arg_cap.py +0 -0
- {dqlite_dbapi-0.3.0 → dqlite_dbapi-0.4.0}/tests/test_dbapi_exception_raw_message_cap.py +0 -0
- {dqlite_dbapi-0.3.0 → dqlite_dbapi-0.4.0}/tests/test_dbapi_type_hash_consistency.py +0 -0
- {dqlite_dbapi-0.3.0 → dqlite_dbapi-0.4.0}/tests/test_description_tuple_public_alias.py +0 -0
- {dqlite_dbapi-0.3.0 → dqlite_dbapi-0.4.0}/tests/test_error_pickle_cause_either_or.py +0 -0
- {dqlite_dbapi-0.3.0 → dqlite_dbapi-0.4.0}/tests/test_error_pickle_preserves_raw_message.py +0 -0
- {dqlite_dbapi-0.3.0 → dqlite_dbapi-0.4.0}/tests/test_exception_repr_code.py +0 -0
- {dqlite_dbapi-0.3.0 → dqlite_dbapi-0.4.0}/tests/test_exceptions.py +0 -0
- {dqlite_dbapi-0.3.0 → dqlite_dbapi-0.4.0}/tests/test_exceptions_all_symmetry.py +0 -0
- {dqlite_dbapi-0.3.0 → dqlite_dbapi-0.4.0}/tests/test_execute_executemany_positional_only.py +0 -0
- {dqlite_dbapi-0.3.0 → dqlite_dbapi-0.4.0}/tests/test_force_close_observe_callback_coverage.py +0 -0
- {dqlite_dbapi-0.3.0 → dqlite_dbapi-0.4.0}/tests/test_fromticks_tz_policy.py +0 -0
- {dqlite_dbapi-0.3.0 → dqlite_dbapi-0.4.0}/tests/test_lastrowid_docstring_stdlib_parity_claim.py +0 -0
- {dqlite_dbapi-0.3.0 → dqlite_dbapi-0.4.0}/tests/test_leader_error_codes_identity.py +0 -0
- {dqlite_dbapi-0.3.0 → dqlite_dbapi-0.4.0}/tests/test_no_workflow_references_in_source.py +0 -0
- {dqlite_dbapi-0.3.0 → dqlite_dbapi-0.4.0}/tests/test_number_includes_boolean_documented.py +0 -0
- {dqlite_dbapi-0.3.0 → dqlite_dbapi-0.4.0}/tests/test_paramstyle_qmark_only_pin.py +0 -0
- {dqlite_dbapi-0.3.0 → dqlite_dbapi-0.4.0}/tests/test_parse_decltypes_colnames_constants.py +0 -0
- {dqlite_dbapi-0.3.0 → dqlite_dbapi-0.4.0}/tests/test_pep249_exception_aliases.py +0 -0
- {dqlite_dbapi-0.3.0 → dqlite_dbapi-0.4.0}/tests/test_pep249_type_constructors_vs_singletons.py +0 -0
- {dqlite_dbapi-0.3.0 → dqlite_dbapi-0.4.0}/tests/test_pep249_type_constructors_wrap_valueerror.py +0 -0
- {dqlite_dbapi-0.3.0 → dqlite_dbapi-0.4.0}/tests/test_pep249_type_objects_final_annotated.py +0 -0
- {dqlite_dbapi-0.3.0 → dqlite_dbapi-0.4.0}/tests/test_pickle_guard.py +0 -0
- {dqlite_dbapi-0.3.0 → dqlite_dbapi-0.4.0}/tests/test_prepare_protocol_in_types_all.py +0 -0
- {dqlite_dbapi-0.3.0 → dqlite_dbapi-0.4.0}/tests/test_pyproject_dependency_graph.py +0 -0
- {dqlite_dbapi-0.3.0 → dqlite_dbapi-0.4.0}/tests/test_readme_truthfulness_pins.py +0 -0
- {dqlite_dbapi-0.3.0 → dqlite_dbapi-0.4.0}/tests/test_register_adapter_global_sync_aio_symmetry.py +0 -0
- {dqlite_dbapi-0.3.0 → dqlite_dbapi-0.4.0}/tests/test_register_adapter_positional_only_signature.py +0 -0
- {dqlite_dbapi-0.3.0 → dqlite_dbapi-0.4.0}/tests/test_row_factory_annotation.py +0 -0
- {dqlite_dbapi-0.3.0 → dqlite_dbapi-0.4.0}/tests/test_row_factory_sqlite3_equivalent.py +0 -0
- {dqlite_dbapi-0.3.0 → dqlite_dbapi-0.4.0}/tests/test_row_hash_eq_empty.py +0 -0
- {dqlite_dbapi-0.3.0 → dqlite_dbapi-0.4.0}/tests/test_rowid_number_overlap_documented.py +0 -0
- {dqlite_dbapi-0.3.0 → dqlite_dbapi-0.4.0}/tests/test_setinputsizes_sequence_acceptance.py +0 -0
- {dqlite_dbapi-0.3.0 → dqlite_dbapi-0.4.0}/tests/test_sqlite_errorname_property.py +0 -0
- {dqlite_dbapi-0.3.0 → dqlite_dbapi-0.4.0}/tests/test_sqlite_version_not_public_attribute.py +0 -0
- {dqlite_dbapi-0.3.0 → dqlite_dbapi-0.4.0}/tests/test_stub_signatures_normalize_to_dbapi_error.py +0 -0
- {dqlite_dbapi-0.3.0 → dqlite_dbapi-0.4.0}/tests/test_sync_connection_execute_shortcut.py +0 -0
- {dqlite_dbapi-0.3.0 → dqlite_dbapi-0.4.0}/tests/test_sync_connection_executemany_shortcut.py +0 -0
- {dqlite_dbapi-0.3.0 → dqlite_dbapi-0.4.0}/tests/test_timeout_validation.py +0 -0
- {dqlite_dbapi-0.3.0 → dqlite_dbapi-0.4.0}/tests/test_top_level_logger_has_null_handler.py +0 -0
- {dqlite_dbapi-0.3.0 → dqlite_dbapi-0.4.0}/tests/test_transitive_wire_import.py +0 -0
- {dqlite_dbapi-0.3.0 → dqlite_dbapi-0.4.0}/tests/test_types_dbapi_compare.py +0 -0
- {dqlite_dbapi-0.3.0 → dqlite_dbapi-0.4.0}/tests/test_unregister_adapter.py +0 -0
- {dqlite_dbapi-0.3.0 → dqlite_dbapi-0.4.0}/tests/test_validate_ticks.py +0 -0
- {dqlite_dbapi-0.3.0 → dqlite_dbapi-0.4.0}/tests/test_version.py +0 -0
- {dqlite_dbapi-0.3.0 → dqlite_dbapi-0.4.0}/tests/test_version_export.py +0 -0
|
@@ -33,8 +33,8 @@ uv sync --extra dev
|
|
|
33
33
|
# Run unit tests only
|
|
34
34
|
.venv/bin/pytest tests/ --ignore=tests/integration
|
|
35
35
|
|
|
36
|
-
# Run all tests (requires Docker cluster)
|
|
37
|
-
cd ../dqlite-
|
|
36
|
+
# Run all tests (requires the Docker cluster from the sibling python-dqlite-dev checkout)
|
|
37
|
+
(cd ../python-dqlite-dev/cluster && docker compose up -d)
|
|
38
38
|
.venv/bin/pytest tests/
|
|
39
39
|
```
|
|
40
40
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: dqlite-dbapi
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
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.
|
|
26
|
-
Requires-Dist: dqlite-wire<0.
|
|
25
|
+
Requires-Dist: dqlite-client<0.5.0,>=0.4.0
|
|
26
|
+
Requires-Dist: dqlite-wire<0.5.0,>=0.4.0
|
|
27
27
|
Provides-Extra: dev
|
|
28
28
|
Requires-Dist: mypy>=1.0; extra == 'dev'
|
|
29
29
|
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
|
|
@@ -121,6 +121,8 @@ Using SQLAlchemy or an ORM? Prefer
|
|
|
121
121
|
gotcha).
|
|
122
122
|
- [Differences from `aiosqlite`](docs/differences-from-aiosqlite.md) — for
|
|
123
123
|
the async surface.
|
|
124
|
+
- [Architecture](docs/architecture.md) — how the sync surface wraps the
|
|
125
|
+
async core, and the threading, lifecycle, and error-mapping policies.
|
|
124
126
|
|
|
125
127
|
## Development
|
|
126
128
|
|
|
@@ -87,6 +87,8 @@ Using SQLAlchemy or an ORM? Prefer
|
|
|
87
87
|
gotcha).
|
|
88
88
|
- [Differences from `aiosqlite`](docs/differences-from-aiosqlite.md) — for
|
|
89
89
|
the async surface.
|
|
90
|
+
- [Architecture](docs/architecture.md) — how the sync surface wraps the
|
|
91
|
+
async core, and the threading, lifecycle, and error-mapping policies.
|
|
90
92
|
|
|
91
93
|
## Development
|
|
92
94
|
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
# Architecture
|
|
2
|
+
|
|
3
|
+
This page records the design decisions behind `dqlite-dbapi`. The user-facing
|
|
4
|
+
behaviour they produce is documented in [Transactions](transactions.md) and
|
|
5
|
+
[Differences from `sqlite3`](differences-from-sqlite3.md).
|
|
6
|
+
|
|
7
|
+
## Layering
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
dqlitedbapi sync PEP 249 surface: Connection, Cursor
|
|
11
|
+
│ one daemon thread + asyncio loop per Connection
|
|
12
|
+
dqlitedbapi.aio async surface: AsyncConnection, AsyncCursor ← all driver logic
|
|
13
|
+
│
|
|
14
|
+
dqliteclient DqliteConnection (wire session), ClusterClient (leader discovery)
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
All driver logic lives in `dqlitedbapi.aio`. The sync package is a thin
|
|
18
|
+
adapter: a sync `Connection` owns one `AsyncConnection` and one background
|
|
19
|
+
thread running an asyncio event loop; every sync method submits the matching
|
|
20
|
+
coroutine to that loop and blocks on the result. Fetch methods never hop to
|
|
21
|
+
the loop: results are fully buffered at execute time, so the sync cursor reads
|
|
22
|
+
the buffer directly.
|
|
23
|
+
|
|
24
|
+
Modules:
|
|
25
|
+
|
|
26
|
+
| Module | Responsibility |
|
|
27
|
+
| --- | --- |
|
|
28
|
+
| `aio/connection.py` | `AsyncConnection`: lifecycle, transactions, session mode, operation lock |
|
|
29
|
+
| `aio/cursor.py` | `AsyncCursor`: execute / executemany, result buffering, description |
|
|
30
|
+
| `connection.py`, `cursor.py` | sync adapters over the async classes |
|
|
31
|
+
| `_loop.py` | the background loop thread used by the sync adapter |
|
|
32
|
+
| `_sql.py` | statement classification (row-returning, DML verbs, BEGIN rewrite, PRAGMA intercept) |
|
|
33
|
+
| `_busy.py` | `SQLITE_BUSY` retry curve |
|
|
34
|
+
| `exceptions.py` | PEP 249 hierarchy and the client-error to dbapi-error mapping |
|
|
35
|
+
| `types.py`, `row.py` | type objects, constructors, adapters, datetime codecs, `Row` |
|
|
36
|
+
|
|
37
|
+
Lexical SQL helpers (comment stripping, top-level statement splitting,
|
|
38
|
+
literal blanking, leading keyword) come from `dqliteclient.sql`, which the
|
|
39
|
+
client also uses for its own transaction tracking. `_sql.py` holds only the
|
|
40
|
+
semantic rules the driver adds on top.
|
|
41
|
+
|
|
42
|
+
## Connection lifecycle
|
|
43
|
+
|
|
44
|
+
- Constructing a connection validates its arguments and does no I/O.
|
|
45
|
+
- The first operation (or an explicit `connect()` / `aconnect()`) resolves
|
|
46
|
+
the cluster leader from the seed address through `ClusterClient`, opens the
|
|
47
|
+
wire session against the leader, and applies the session mode.
|
|
48
|
+
- **An invalidated connection is a closed connection.** When the underlying
|
|
49
|
+
wire session is lost (transport error, leader change, cancellation or
|
|
50
|
+
interrupt during an operation), `closed` becomes `True`, `invalidated`
|
|
51
|
+
becomes `True`, and every further operation raises `InterfaceError`. The
|
|
52
|
+
driver never reconnects silently: a reconnect would drop transaction state
|
|
53
|
+
the caller believes is still open. Pools (SQLAlchemy's included) discard the
|
|
54
|
+
connection and open a fresh one.
|
|
55
|
+
- `close()` is idempotent and does not roll back (stdlib `sqlite3` parity).
|
|
56
|
+
- `force_close_transport()` is synchronous, thread-safe, never raises, and
|
|
57
|
+
tears the socket down without waiting for an in-flight operation. It exists
|
|
58
|
+
for pool `terminate` paths and for shutting down from a thread that is not
|
|
59
|
+
the connection's owner.
|
|
60
|
+
|
|
61
|
+
## Threading and concurrency
|
|
62
|
+
|
|
63
|
+
### Sync surface
|
|
64
|
+
|
|
65
|
+
- `check_same_thread=True` (default): every method except
|
|
66
|
+
`force_close_transport()` must be called from the thread that created the
|
|
67
|
+
connection; other threads get `ProgrammingError`.
|
|
68
|
+
- `check_same_thread=False`: any thread may call the connection. Calls are
|
|
69
|
+
serialised by a lock. Cursors are still single-thread objects, as in
|
|
70
|
+
`sqlite3`: share the connection, give each thread its own cursor.
|
|
71
|
+
- **Interrupts close the connection.** A `KeyboardInterrupt` or `SystemExit`
|
|
72
|
+
delivered while a call is blocked cancels the in-flight operation,
|
|
73
|
+
force-closes the connection, and propagates. The driver must not be
|
|
74
|
+
re-entered from a signal handler; that case is out of contract.
|
|
75
|
+
- The sync side imposes no deadline of its own. Every await in the async
|
|
76
|
+
layer is already bounded by the client's per-RPC timeouts, so a blocked sync
|
|
77
|
+
call ends when those fire or when `force_close_transport()` is called from
|
|
78
|
+
another thread.
|
|
79
|
+
|
|
80
|
+
### Async surface
|
|
81
|
+
|
|
82
|
+
- An `AsyncConnection` binds to the event loop it first runs on. Using it
|
|
83
|
+
from another loop raises `InterfaceError`; it cannot be shared across
|
|
84
|
+
`asyncio.run()` calls or threads.
|
|
85
|
+
- One operation at a time: an `asyncio.Lock` serialises execute, commit and
|
|
86
|
+
rollback across tasks on the same loop. Waiting tasks queue rather than
|
|
87
|
+
fail. A cursor is a single-task object.
|
|
88
|
+
- Cancellation during a wire round-trip invalidates the connection (see
|
|
89
|
+
above), because the server-side effect of the interrupted statement is
|
|
90
|
+
unknown.
|
|
91
|
+
|
|
92
|
+
## Statements
|
|
93
|
+
|
|
94
|
+
- One statement per `execute()`; a second statement after `;` raises
|
|
95
|
+
`ProgrammingError` before any network round-trip. `CREATE TRIGGER` bodies
|
|
96
|
+
are understood, so their inner `;` do not count.
|
|
97
|
+
- Empty, comment-only, and NUL-containing SQL raise `ProgrammingError`.
|
|
98
|
+
- A `?` count that does not match the parameter count raises
|
|
99
|
+
`ProgrammingError` client-side.
|
|
100
|
+
- Row-returning statements (`SELECT`, `VALUES`, `PRAGMA`, `EXPLAIN`, a
|
|
101
|
+
`WITH` whose final statement is one of those, and any DML with
|
|
102
|
+
`RETURNING`) go through the query path and populate `description` and the
|
|
103
|
+
row buffer. Everything else goes through the exec path and populates
|
|
104
|
+
`rowcount` (INSERT/UPDATE/DELETE/REPLACE only, else -1) and `lastrowid`
|
|
105
|
+
(INSERT/REPLACE only, sticky across other statements).
|
|
106
|
+
- `PRAGMA busy_timeout` is answered locally: the server rejects it, and the
|
|
107
|
+
driver owns the busy budget.
|
|
108
|
+
- A bare `BEGIN` / `BEGIN TRANSACTION` is rewritten according to the session
|
|
109
|
+
mode: `immediate` → `BEGIN IMMEDIATE`, `exclusive` → `BEGIN EXCLUSIVE`,
|
|
110
|
+
`deferred` and `read_only` → unchanged. An explicit qualifier is never
|
|
111
|
+
rewritten.
|
|
112
|
+
- `SQLITE_BUSY` is retried on SQLite's default busy-callback curve until the
|
|
113
|
+
connection's `busy_timeout` budget is spent. Each `executemany` iteration
|
|
114
|
+
retries independently.
|
|
115
|
+
- `description` carries `(name, type_code, None, None, None, None, None)`;
|
|
116
|
+
the type code is the wire `ValueType` of the first non-NULL cell in the
|
|
117
|
+
column, or the `UNKNOWN` sentinel when there is none.
|
|
118
|
+
- `ISO8601` and `UNIXTIME` wire cells decode to `datetime` objects; every
|
|
119
|
+
other cell is passed through as the wire primitive.
|
|
120
|
+
|
|
121
|
+
## Transactions
|
|
122
|
+
|
|
123
|
+
- Autocommit by default: no implicit `BEGIN`.
|
|
124
|
+
- `commit()` / `rollback()` are no-ops before the first connection and when no
|
|
125
|
+
transaction is open. A server reply of "no transaction is active" is
|
|
126
|
+
swallowed. Every other failure propagates.
|
|
127
|
+
- A `COMMIT` that fails with a leadership-lost code raises
|
|
128
|
+
`AmbiguousCommitError` (an `OperationalError`): the write may or may not
|
|
129
|
+
have been applied.
|
|
130
|
+
- `transaction()` is a context manager issuing `BEGIN` / `COMMIT` /
|
|
131
|
+
`ROLLBACK`. Calling `commit()` or `rollback()` inside the block raises
|
|
132
|
+
`InterfaceError`, because the block owns the boundaries.
|
|
133
|
+
- `with conn:` / `async with conn:` commit on success and roll back on
|
|
134
|
+
exception; neither closes the connection.
|
|
135
|
+
- `session_mode` is read-only; `set_session_mode()` changes it, emitting
|
|
136
|
+
`PRAGMA query_only` when crossing the `read_only` boundary. It may not be
|
|
137
|
+
called inside a transaction.
|
|
138
|
+
|
|
139
|
+
## Errors
|
|
140
|
+
|
|
141
|
+
Client exceptions are translated at the boundary by `exceptions.translate`:
|
|
142
|
+
|
|
143
|
+
| Client exception | dbapi exception |
|
|
144
|
+
| --- | --- |
|
|
145
|
+
| `OperationalError` with code | class chosen from the SQLite primary code (constraint → `IntegrityError`, internal/nomem/notfound → `InternalError`, toobig → `DataError`, range/misuse → `InterfaceError`, corrupt/format/notadb → bare `DatabaseError`, dqlite parse/notfound → `ProgrammingError`, else `OperationalError`) |
|
|
146
|
+
| `DqliteConnectionError`, `ClusterError`, `ProtocolError` | `OperationalError` with `code=None` |
|
|
147
|
+
| `ClusterPolicyError` | `InterfaceError` prefixed `Cluster policy rejection` (never retried) |
|
|
148
|
+
| `DataError`, wire `EncodeError` | `DataError` |
|
|
149
|
+
| `InterfaceError` | `InterfaceError` |
|
|
150
|
+
| any other `DqliteError` | `DatabaseError` |
|
|
151
|
+
|
|
152
|
+
Every raised error keeps `code` and `raw_message` (the untruncated server
|
|
153
|
+
text). Connect-time failures are prefixed `Failed to connect: `. These
|
|
154
|
+
prefixes and the `Connection is closed` / `Cursor is closed` /
|
|
155
|
+
`invalidated (id=` wordings are load-bearing: `sqlalchemy-dqlite` classifies
|
|
156
|
+
disconnects on them.
|
|
157
|
+
|
|
158
|
+
## Contract with sqlalchemy-dqlite
|
|
159
|
+
|
|
160
|
+
The dialect relies on exactly this dbapi surface, beyond PEP 249 itself:
|
|
161
|
+
|
|
162
|
+
- module: `connect`, exception classes, `paramstyle`, `sqlite_version_info`,
|
|
163
|
+
`FAILED_TO_CONNECT_PREFIX`, `CLUSTER_POLICY_REJECTION_PREFIX`;
|
|
164
|
+
- connection: `cursor()`, `commit()`, `rollback()`, `close()`,
|
|
165
|
+
`force_close_transport()`, `connect()` (async), `session_mode`,
|
|
166
|
+
`default_session_mode`, `set_session_mode()`, `in_transaction`;
|
|
167
|
+
- cursor: `execute()`, `executemany()`, `description`, `rowcount`,
|
|
168
|
+
`lastrowid`, `drain_rows()` (async), `close()`;
|
|
169
|
+
- `types.format_utc_offset()` for the dialect's datetime processors.
|
|
@@ -37,6 +37,15 @@ covers the differences you are most likely to notice when porting code.
|
|
|
37
37
|
`create_window_function`, `iterdump`, `backup`, `set_authorizer`,
|
|
38
38
|
`serialize`, `blobopen`, `executescript`, and `register_converter` have no
|
|
39
39
|
server-side counterpart in dqlite; their stubs raise `NotSupportedError`.
|
|
40
|
+
- **A lost connection stays lost.** When the wire session drops (transport
|
|
41
|
+
error, leader change, or an interrupted call), `closed` becomes `True`,
|
|
42
|
+
`invalidated` becomes `True`, and every further call raises
|
|
43
|
+
`InterfaceError`. The driver never reconnects behind your back, because a
|
|
44
|
+
silent reconnect would drop transaction state; open a new connection (a
|
|
45
|
+
pool does this for you).
|
|
46
|
+
- **Bad `connect()` arguments raise `ProgrammingError`**, not `TypeError` /
|
|
47
|
+
`ValueError`, so configuration mistakes stay inside the `dbapi.Error`
|
|
48
|
+
hierarchy.
|
|
40
49
|
- **`rowcount` after SELECT is `len(rows)`.** Stdlib returns `-1` for queries.
|
|
41
50
|
dqlite knows the full result at execute time, so it reports the count.
|
|
42
51
|
`rowcount` stays `-1` for non-result paths, for all PRAGMA statements (read
|
|
@@ -29,23 +29,35 @@ cur.execute("INSERT INTO t VALUES (?)", (2,))
|
|
|
29
29
|
conn.commit() # or conn.rollback()
|
|
30
30
|
```
|
|
31
31
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
`
|
|
42
|
-
|
|
43
|
-
`
|
|
32
|
+
A bare `BEGIN` / `BEGIN TRANSACTION` is qualified according to the
|
|
33
|
+
connection's session mode; an explicit `DEFERRED` / `IMMEDIATE` /
|
|
34
|
+
`EXCLUSIVE` qualifier is always passed through unchanged. dqlite's Raft FSM
|
|
35
|
+
serializes the transaction across the cluster regardless of the qualifier, so
|
|
36
|
+
the qualifier changes only lock-acquisition timing on the leader, never
|
|
37
|
+
isolation.
|
|
38
|
+
|
|
39
|
+
| `session_mode` | bare `BEGIN` becomes | notes |
|
|
40
|
+
| --- | --- | --- |
|
|
41
|
+
| `immediate` (default) | `BEGIN IMMEDIATE` | takes the write lock up front, so a read-then-write cannot fail later with `SQLITE_BUSY_SNAPSHOT` |
|
|
42
|
+
| `deferred` | `BEGIN` | SQLite's default deferred transaction |
|
|
43
|
+
| `exclusive` | `BEGIN EXCLUSIVE` | |
|
|
44
|
+
| `read_only` | `BEGIN` | the connection runs with `PRAGMA query_only = 1`; writes raise `OperationalError` |
|
|
45
|
+
|
|
46
|
+
The mode is selected with the `session_mode` connect argument (or the
|
|
47
|
+
`DQLITE_SESSION_MODE` environment variable), read back through the
|
|
48
|
+
`session_mode` property, and changed at runtime with `set_session_mode()`,
|
|
49
|
+
which may not be called inside a transaction. `default_session_mode` is the
|
|
50
|
+
value given at construction.
|
|
44
51
|
|
|
45
52
|
The connection also works as a context manager (matching stdlib `sqlite3`):
|
|
46
53
|
the `with conn:` block commits on clean exit and rolls back on exception.
|
|
47
54
|
It does **not** close the connection — the connection stays reusable.
|
|
48
55
|
|
|
56
|
+
`conn.transaction()` (sync) / `async with conn.transaction():` wraps a block
|
|
57
|
+
in `BEGIN` / `COMMIT` / `ROLLBACK`. Calling `commit()` or `rollback()`
|
|
58
|
+
inside the block raises `InterfaceError`, because the block owns the
|
|
59
|
+
boundaries; use `SAVEPOINT` for nesting.
|
|
60
|
+
|
|
49
61
|
## `commit()` / `rollback()` details
|
|
50
62
|
|
|
51
63
|
- Calling `commit()` / `rollback()` before any query has run is a silent
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "dqlite-dbapi"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.4.0"
|
|
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.
|
|
30
|
-
"dqlite-wire>=0.
|
|
29
|
+
"dqlite-client>=0.4.0,<0.5.0",
|
|
30
|
+
"dqlite-wire>=0.4.0,<0.5.0",
|
|
31
31
|
]
|
|
32
32
|
|
|
33
33
|
[tool.uv.sources]
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
"""PEP 249 (DB-API 2.0) driver for dqlite."""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
|
|
5
|
+
from dqliteclient import DEFAULT_CLOSE_TIMEOUT_SECONDS, DEFAULT_TIMEOUT_SECONDS, DialFunc
|
|
6
|
+
from dqlitedbapi._module import (
|
|
7
|
+
LEGACY_TRANSACTION_CONTROL,
|
|
8
|
+
PARSE_COLNAMES,
|
|
9
|
+
PARSE_DECLTYPES,
|
|
10
|
+
__version__,
|
|
11
|
+
apilevel,
|
|
12
|
+
complete_statement,
|
|
13
|
+
enable_callback_tracebacks,
|
|
14
|
+
paramstyle,
|
|
15
|
+
register_converter,
|
|
16
|
+
sqlite_version,
|
|
17
|
+
sqlite_version_info,
|
|
18
|
+
threadsafety,
|
|
19
|
+
)
|
|
20
|
+
from dqlitedbapi.aio.connection import apply_stdlib_connect_kwargs as _apply_stdlib_connect_kwargs
|
|
21
|
+
from dqlitedbapi.connection import Connection
|
|
22
|
+
from dqlitedbapi.cursor import Cursor
|
|
23
|
+
from dqlitedbapi.exceptions import (
|
|
24
|
+
CLUSTER_POLICY_REJECTION_PREFIX,
|
|
25
|
+
FAILED_TO_CONNECT_PREFIX,
|
|
26
|
+
AdapterLookupError,
|
|
27
|
+
AmbiguousCommitError,
|
|
28
|
+
DatabaseError,
|
|
29
|
+
DataError,
|
|
30
|
+
Error,
|
|
31
|
+
IntegrityError,
|
|
32
|
+
InterfaceError,
|
|
33
|
+
InternalError,
|
|
34
|
+
NotSupportedError,
|
|
35
|
+
OperationalError,
|
|
36
|
+
ProgrammingError,
|
|
37
|
+
Warning,
|
|
38
|
+
)
|
|
39
|
+
from dqlitedbapi.row import Row
|
|
40
|
+
from dqlitedbapi.types import (
|
|
41
|
+
BINARY,
|
|
42
|
+
DATETIME,
|
|
43
|
+
NUMBER,
|
|
44
|
+
ROWID,
|
|
45
|
+
STRING,
|
|
46
|
+
UNKNOWN,
|
|
47
|
+
Binary,
|
|
48
|
+
Date,
|
|
49
|
+
DateFromTicks,
|
|
50
|
+
DescriptionTuple,
|
|
51
|
+
PrepareProtocol,
|
|
52
|
+
Time,
|
|
53
|
+
TimeFromTicks,
|
|
54
|
+
Timestamp,
|
|
55
|
+
TimestampFromTicks,
|
|
56
|
+
register_adapter,
|
|
57
|
+
unregister_adapter,
|
|
58
|
+
)
|
|
59
|
+
from dqlitewire import DEFAULT_MAX_CONTINUATION_FRAMES as _DEFAULT_MAX_CONTINUATION_FRAMES
|
|
60
|
+
from dqlitewire import DEFAULT_MAX_TOTAL_ROWS as _DEFAULT_MAX_TOTAL_ROWS
|
|
61
|
+
|
|
62
|
+
__all__ = [
|
|
63
|
+
"__version__",
|
|
64
|
+
"apilevel",
|
|
65
|
+
"threadsafety",
|
|
66
|
+
"paramstyle",
|
|
67
|
+
"sqlite_version",
|
|
68
|
+
"sqlite_version_info",
|
|
69
|
+
"LEGACY_TRANSACTION_CONTROL",
|
|
70
|
+
"PARSE_DECLTYPES",
|
|
71
|
+
"PARSE_COLNAMES",
|
|
72
|
+
"connect",
|
|
73
|
+
"Connection",
|
|
74
|
+
"Cursor",
|
|
75
|
+
"DialFunc",
|
|
76
|
+
"Warning",
|
|
77
|
+
"Error",
|
|
78
|
+
"InterfaceError",
|
|
79
|
+
"DatabaseError",
|
|
80
|
+
"DataError",
|
|
81
|
+
"OperationalError",
|
|
82
|
+
"AmbiguousCommitError",
|
|
83
|
+
"IntegrityError",
|
|
84
|
+
"InternalError",
|
|
85
|
+
"ProgrammingError",
|
|
86
|
+
"NotSupportedError",
|
|
87
|
+
"AdapterLookupError",
|
|
88
|
+
"Date",
|
|
89
|
+
"Time",
|
|
90
|
+
"Timestamp",
|
|
91
|
+
"DateFromTicks",
|
|
92
|
+
"TimeFromTicks",
|
|
93
|
+
"TimestampFromTicks",
|
|
94
|
+
"Binary",
|
|
95
|
+
"STRING",
|
|
96
|
+
"BINARY",
|
|
97
|
+
"NUMBER",
|
|
98
|
+
"DATETIME",
|
|
99
|
+
"ROWID",
|
|
100
|
+
"UNKNOWN",
|
|
101
|
+
"Row",
|
|
102
|
+
"DescriptionTuple",
|
|
103
|
+
"PrepareProtocol",
|
|
104
|
+
"register_adapter",
|
|
105
|
+
"unregister_adapter",
|
|
106
|
+
"register_converter",
|
|
107
|
+
"complete_statement",
|
|
108
|
+
"enable_callback_tracebacks",
|
|
109
|
+
"CLUSTER_POLICY_REJECTION_PREFIX",
|
|
110
|
+
"FAILED_TO_CONNECT_PREFIX",
|
|
111
|
+
]
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def connect(
|
|
115
|
+
address: str,
|
|
116
|
+
*,
|
|
117
|
+
database: str = "default",
|
|
118
|
+
timeout: float = DEFAULT_TIMEOUT_SECONDS,
|
|
119
|
+
max_total_rows: int | None = _DEFAULT_MAX_TOTAL_ROWS,
|
|
120
|
+
max_continuation_frames: int | None = _DEFAULT_MAX_CONTINUATION_FRAMES,
|
|
121
|
+
max_message_size: int | None = None,
|
|
122
|
+
trust_server_heartbeat: bool = False,
|
|
123
|
+
close_timeout: float = DEFAULT_CLOSE_TIMEOUT_SECONDS,
|
|
124
|
+
dial_timeout: float | None = None,
|
|
125
|
+
attempt_timeout: float | None = None,
|
|
126
|
+
dial_func: DialFunc | None = None,
|
|
127
|
+
busy_timeout: float = 5.0,
|
|
128
|
+
check_same_thread: bool = True,
|
|
129
|
+
session_mode: str | None = None,
|
|
130
|
+
**stdlib_kwargs: object,
|
|
131
|
+
) -> Connection:
|
|
132
|
+
"""Create a connection to the cluster reachable through ``address`` (``host:port``).
|
|
133
|
+
|
|
134
|
+
The wire session opens on first use; ``timeout`` bounds each RPC phase.
|
|
135
|
+
"""
|
|
136
|
+
connection = Connection(
|
|
137
|
+
address,
|
|
138
|
+
database=database,
|
|
139
|
+
timeout=timeout,
|
|
140
|
+
max_total_rows=max_total_rows,
|
|
141
|
+
max_continuation_frames=max_continuation_frames,
|
|
142
|
+
max_message_size=max_message_size,
|
|
143
|
+
trust_server_heartbeat=trust_server_heartbeat,
|
|
144
|
+
close_timeout=close_timeout,
|
|
145
|
+
dial_timeout=dial_timeout,
|
|
146
|
+
attempt_timeout=attempt_timeout,
|
|
147
|
+
dial_func=dial_func,
|
|
148
|
+
busy_timeout=busy_timeout,
|
|
149
|
+
check_same_thread=check_same_thread,
|
|
150
|
+
session_mode=session_mode,
|
|
151
|
+
)
|
|
152
|
+
_apply_stdlib_connect_kwargs(connection._async, stdlib_kwargs)
|
|
153
|
+
return connection
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
logging.getLogger(__name__).addHandler(logging.NullHandler())
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"""Retry on ``SQLITE_BUSY`` following SQLite's default busy-handler curve.
|
|
2
|
+
|
|
3
|
+
dqlite rejects ``PRAGMA busy_timeout`` on the wire, so the driver owns the budget.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import asyncio
|
|
7
|
+
import logging
|
|
8
|
+
from collections.abc import Awaitable, Callable
|
|
9
|
+
from typing import Final
|
|
10
|
+
|
|
11
|
+
from dqlitedbapi.exceptions import OperationalError
|
|
12
|
+
from dqlitewire import SQLITE_BUSY
|
|
13
|
+
|
|
14
|
+
logger = logging.getLogger(__name__)
|
|
15
|
+
|
|
16
|
+
# sqlite3.c ``sqliteDefaultBusyCallback``: sleep before retry i, and cumulative sleep.
|
|
17
|
+
_DELAYS_MS: Final[tuple[int, ...]] = (1, 2, 5, 10, 15, 20, 25, 25, 25, 50, 50, 100)
|
|
18
|
+
_TOTALS_MS: Final[tuple[int, ...]] = (0, 1, 3, 8, 18, 33, 53, 78, 103, 128, 178, 228)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def next_delay_ms(attempt: int, budget_ms: int) -> int | None:
|
|
22
|
+
"""Milliseconds to sleep before retry ``attempt + 1``, or ``None`` when the budget is spent."""
|
|
23
|
+
if budget_ms <= 0:
|
|
24
|
+
return None
|
|
25
|
+
if attempt < len(_DELAYS_MS):
|
|
26
|
+
delay, prior = _DELAYS_MS[attempt], _TOTALS_MS[attempt]
|
|
27
|
+
else:
|
|
28
|
+
delay = _DELAYS_MS[-1]
|
|
29
|
+
prior = _TOTALS_MS[-1] + delay * (attempt - (len(_DELAYS_MS) - 1))
|
|
30
|
+
if prior + delay > budget_ms:
|
|
31
|
+
delay = budget_ms - prior
|
|
32
|
+
return delay if delay > 0 else None
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def is_busy(exc: BaseException) -> bool:
|
|
36
|
+
return isinstance(exc, OperationalError) and exc.code == SQLITE_BUSY
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
async def retry_on_busy[T](busy_timeout: float, attempt: Callable[[], Awaitable[T]]) -> T:
|
|
40
|
+
"""Run ``attempt()`` until it succeeds, fails with a non-BUSY error, or the budget is spent."""
|
|
41
|
+
budget_ms = round(busy_timeout * 1000)
|
|
42
|
+
count = 0
|
|
43
|
+
while True:
|
|
44
|
+
try:
|
|
45
|
+
return await attempt()
|
|
46
|
+
except OperationalError as exc:
|
|
47
|
+
if not is_busy(exc):
|
|
48
|
+
raise
|
|
49
|
+
delay = next_delay_ms(count, budget_ms)
|
|
50
|
+
if delay is None:
|
|
51
|
+
if budget_ms > 0:
|
|
52
|
+
logger.warning(
|
|
53
|
+
"busy_timeout budget %.3fs exhausted after %d retries", busy_timeout, count
|
|
54
|
+
)
|
|
55
|
+
raise
|
|
56
|
+
await asyncio.sleep(delay / 1000)
|
|
57
|
+
count += 1
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
"""Background event-loop thread used by the sync surface."""
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
import concurrent.futures
|
|
5
|
+
import contextlib
|
|
6
|
+
import threading
|
|
7
|
+
from collections.abc import Coroutine
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
from dqlitedbapi.exceptions import InterfaceError
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class LoopThread:
|
|
14
|
+
"""A daemon thread running an asyncio loop; coroutines are submitted and awaited from
|
|
15
|
+
other threads. Started on first use, stopped by :meth:`shutdown`."""
|
|
16
|
+
|
|
17
|
+
def __init__(self, name: str) -> None:
|
|
18
|
+
self._name = name
|
|
19
|
+
self._loop: asyncio.AbstractEventLoop | None = None
|
|
20
|
+
self._thread: threading.Thread | None = None
|
|
21
|
+
self._inflight: set[concurrent.futures.Future[Any]] = set()
|
|
22
|
+
self._guard = threading.Lock()
|
|
23
|
+
|
|
24
|
+
@property
|
|
25
|
+
def loop(self) -> asyncio.AbstractEventLoop | None:
|
|
26
|
+
return self._loop
|
|
27
|
+
|
|
28
|
+
def run[T](self, coro: Coroutine[Any, Any, T]) -> T:
|
|
29
|
+
"""Run ``coro`` on the loop thread and block for its result.
|
|
30
|
+
|
|
31
|
+
A ``KeyboardInterrupt`` or ``SystemExit`` while blocked cancels the coroutine before
|
|
32
|
+
propagating. A cancellation triggered by :meth:`shutdown` surfaces as
|
|
33
|
+
``concurrent.futures.CancelledError``.
|
|
34
|
+
"""
|
|
35
|
+
try:
|
|
36
|
+
loop = self._ensure_running()
|
|
37
|
+
except BaseException:
|
|
38
|
+
coro.close()
|
|
39
|
+
raise
|
|
40
|
+
future = asyncio.run_coroutine_threadsafe(coro, loop)
|
|
41
|
+
with self._guard:
|
|
42
|
+
self._inflight.add(future)
|
|
43
|
+
try:
|
|
44
|
+
return future.result()
|
|
45
|
+
except BaseException:
|
|
46
|
+
future.cancel()
|
|
47
|
+
raise
|
|
48
|
+
finally:
|
|
49
|
+
with self._guard:
|
|
50
|
+
self._inflight.discard(future)
|
|
51
|
+
|
|
52
|
+
def shutdown(self, timeout: float) -> None:
|
|
53
|
+
"""Cancel in-flight work, stop the loop and join the thread. Idempotent, never raises."""
|
|
54
|
+
with self._guard:
|
|
55
|
+
loop, thread = self._loop, self._thread
|
|
56
|
+
self._thread = None
|
|
57
|
+
if loop is None:
|
|
58
|
+
return
|
|
59
|
+
if thread is not None and thread.is_alive():
|
|
60
|
+
stopper = _cancel_all_and_stop(loop, timeout)
|
|
61
|
+
try:
|
|
62
|
+
asyncio.run_coroutine_threadsafe(stopper, loop)
|
|
63
|
+
except RuntimeError:
|
|
64
|
+
stopper.close()
|
|
65
|
+
thread.join(timeout + 1.0)
|
|
66
|
+
with self._guard:
|
|
67
|
+
inflight = list(self._inflight)
|
|
68
|
+
for future in inflight:
|
|
69
|
+
with contextlib.suppress(concurrent.futures.InvalidStateError):
|
|
70
|
+
future.set_exception(
|
|
71
|
+
InterfaceError("Connection closed while an operation was in flight")
|
|
72
|
+
)
|
|
73
|
+
if not loop.is_running():
|
|
74
|
+
with contextlib.suppress(Exception):
|
|
75
|
+
loop.close()
|
|
76
|
+
|
|
77
|
+
def _ensure_running(self) -> asyncio.AbstractEventLoop:
|
|
78
|
+
with self._guard:
|
|
79
|
+
if self._thread is not None and self._thread.is_alive() and self._loop is not None:
|
|
80
|
+
return self._loop
|
|
81
|
+
if self._loop is not None and self._loop.is_closed():
|
|
82
|
+
raise InterfaceError("Connection is closed (event loop thread stopped)")
|
|
83
|
+
loop = asyncio.new_event_loop()
|
|
84
|
+
started = threading.Event()
|
|
85
|
+
|
|
86
|
+
def main() -> None:
|
|
87
|
+
asyncio.set_event_loop(loop)
|
|
88
|
+
loop.call_soon(started.set)
|
|
89
|
+
loop.run_forever()
|
|
90
|
+
|
|
91
|
+
thread = threading.Thread(target=main, name=self._name, daemon=True)
|
|
92
|
+
thread.start()
|
|
93
|
+
started.wait()
|
|
94
|
+
self._loop, self._thread = loop, thread
|
|
95
|
+
return loop
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
async def _cancel_all_and_stop(loop: asyncio.AbstractEventLoop, timeout: float) -> None:
|
|
99
|
+
me = asyncio.current_task()
|
|
100
|
+
tasks = [t for t in asyncio.all_tasks(loop) if t is not me]
|
|
101
|
+
for task in tasks:
|
|
102
|
+
task.cancel()
|
|
103
|
+
if tasks:
|
|
104
|
+
await asyncio.wait(tasks, timeout=timeout)
|
|
105
|
+
loop.stop()
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"""Module-level PEP 249 attributes shared by ``dqlitedbapi`` and ``dqlitedbapi.aio``."""
|
|
2
|
+
|
|
3
|
+
from typing import Final, Literal, NoReturn
|
|
4
|
+
|
|
5
|
+
from dqlitedbapi.exceptions import NotSupportedError
|
|
6
|
+
|
|
7
|
+
__version__: Final[str] = "0.4.0"
|
|
8
|
+
|
|
9
|
+
apilevel: Final[Literal["2.0"]] = "2.0"
|
|
10
|
+
threadsafety: Final[Literal[2]] = 2
|
|
11
|
+
paramstyle: Final[Literal["qmark"]] = "qmark"
|
|
12
|
+
|
|
13
|
+
# The SQLite feature floor every supported dqlite server provides (RETURNING needs 3.35).
|
|
14
|
+
sqlite_version_info: Final[tuple[int, int, int]] = (3, 35, 0)
|
|
15
|
+
sqlite_version: Final[str] = "3.35.0"
|
|
16
|
+
|
|
17
|
+
# stdlib sqlite3 parity constants; ``PARSE_*`` are accepted nowhere and enable nothing.
|
|
18
|
+
LEGACY_TRANSACTION_CONTROL: Final[int] = -1
|
|
19
|
+
PARSE_DECLTYPES: Final[int] = 1
|
|
20
|
+
PARSE_COLNAMES: Final[int] = 2
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def register_converter(*args: object, **kwargs: object) -> NoReturn:
|
|
24
|
+
raise NotSupportedError(
|
|
25
|
+
"register_converter is not supported: the wire carries no declared column types; "
|
|
26
|
+
"use row_factory to post-process rows"
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def complete_statement(*args: object, **kwargs: object) -> NoReturn:
|
|
31
|
+
raise NotSupportedError("complete_statement is not supported")
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def enable_callback_tracebacks(*args: object, **kwargs: object) -> NoReturn:
|
|
35
|
+
raise NotSupportedError("enable_callback_tracebacks is not supported: there are no callbacks")
|