dqlite-dbapi 0.1.2__tar.gz → 0.2.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.1.2 → dqlite_dbapi-0.2.0}/DEVELOPMENT.md +2 -3
- dqlite_dbapi-0.2.0/PKG-INFO +369 -0
- dqlite_dbapi-0.2.0/README.md +335 -0
- dqlite_dbapi-0.2.0/pyproject.toml +114 -0
- dqlite_dbapi-0.2.0/src/dqlitedbapi/__init__.py +451 -0
- dqlite_dbapi-0.2.0/src/dqlitedbapi/_constants.py +88 -0
- dqlite_dbapi-0.2.0/src/dqlitedbapi/aio/__init__.py +523 -0
- dqlite_dbapi-0.2.0/src/dqlitedbapi/aio/connection.py +2782 -0
- dqlite_dbapi-0.2.0/src/dqlitedbapi/aio/cursor.py +1533 -0
- dqlite_dbapi-0.2.0/src/dqlitedbapi/connection.py +3948 -0
- dqlite_dbapi-0.2.0/src/dqlitedbapi/cursor.py +2741 -0
- dqlite_dbapi-0.2.0/src/dqlitedbapi/exceptions.py +433 -0
- dqlite_dbapi-0.2.0/src/dqlitedbapi/types.py +1008 -0
- dqlite_dbapi-0.2.0/tests/aio/test_aio_version_final_annotated.py +26 -0
- dqlite_dbapi-0.2.0/tests/aio/test_async_close_underlying_close_failed_logged.py +144 -0
- dqlite_dbapi-0.2.0/tests/aio/test_async_connection_fork_guard.py +136 -0
- dqlite_dbapi-0.2.0/tests/aio/test_async_iter_protocol_invariants.py +41 -0
- dqlite_dbapi-0.2.0/tests/aio/test_async_unclosed_warning_skips_in_fork_child.py +181 -0
- dqlite_dbapi-0.2.0/tests/aio/test_close_cancel_between_cursor_cascade_and_lock.py +71 -0
- dqlite_dbapi-0.2.0/tests/aio/test_fetchmany_cancel_atomic.py +87 -0
- dqlite_dbapi-0.2.0/tests/aio/test_messages_clear_on_closed.py +56 -0
- dqlite_dbapi-0.2.0/tests/conftest.py +60 -0
- dqlite_dbapi-0.2.0/tests/integration/test_async_concurrent_executemany_serialized.py +95 -0
- dqlite_dbapi-0.2.0/tests/integration/test_async_cursor_concurrent_execute_rejected.py +74 -0
- dqlite_dbapi-0.2.0/tests/integration/test_async_cursor_edge_branches.py +81 -0
- dqlite_dbapi-0.2.0/tests/integration/test_async_fetchmany_cancel.py +87 -0
- dqlite_dbapi-0.2.0/tests/integration/test_async_returning_zero_rows.py +43 -0
- dqlite_dbapi-0.2.0/tests/integration/test_async_transaction_ctxmgr_rejects_stray_commit.py +71 -0
- dqlite_dbapi-0.2.0/tests/integration/test_close_implicit_rollback_pep249.py +148 -0
- dqlite_dbapi-0.2.0/tests/integration/test_concurrent_cursors_visibility.py +52 -0
- dqlite_dbapi-0.2.0/tests/integration/test_constraint_extended_codes.py +104 -0
- dqlite_dbapi-0.2.0/tests/integration/test_context_manager.py +56 -0
- dqlite_dbapi-0.2.0/tests/integration/test_cursor_fetch_after_non_row_execute.py +113 -0
- dqlite_dbapi-0.2.0/tests/integration/test_cursor_misc_invariant_pins.py +90 -0
- dqlite_dbapi-0.2.0/tests/integration/test_cursor_row_index_after_fetchall.py +110 -0
- dqlite_dbapi-0.2.0/tests/integration/test_datetime_conversion.py +300 -0
- dqlite_dbapi-0.2.0/tests/integration/test_description_after_ddl_pragma.py +107 -0
- dqlite_dbapi-0.2.0/tests/integration/test_description_survives_empty_fetchall.py +126 -0
- dqlite_dbapi-0.2.0/tests/integration/test_executemany_cancel_mid_batch.py +115 -0
- dqlite_dbapi-0.2.0/tests/integration/test_executemany_completed_iterations_observability.py +69 -0
- dqlite_dbapi-0.2.0/tests/integration/test_executemany_cte_dml.py +74 -0
- dqlite_dbapi-0.2.0/tests/integration/test_executemany_failure_resets_rowcount.py +76 -0
- dqlite_dbapi-0.2.0/tests/integration/test_executemany_generator.py +51 -0
- dqlite_dbapi-0.2.0/tests/integration/test_executemany_returning.py +43 -0
- dqlite_dbapi-0.2.0/tests/integration/test_integrity_error.py +49 -0
- dqlite_dbapi-0.2.0/tests/integration/test_lastrowid_autoincrement.py +91 -0
- dqlite_dbapi-0.2.0/tests/integration/test_lastrowid_cursor_scoped.py +89 -0
- dqlite_dbapi-0.2.0/tests/integration/test_lastrowid_returning.py +65 -0
- dqlite_dbapi-0.2.0/tests/integration/test_lastrowid_survives_cross_cursor_rollback.py +129 -0
- dqlite_dbapi-0.2.0/tests/integration/test_leader_redirect_live.py +142 -0
- dqlite_dbapi-0.2.0/tests/integration/test_misc_coverage.py +401 -0
- dqlite_dbapi-0.2.0/tests/integration/test_multiline_returning_rows.py +73 -0
- dqlite_dbapi-0.2.0/tests/integration/test_nan_inf_float.py +69 -0
- dqlite_dbapi-0.2.0/tests/integration/test_negative_zero_and_subnormal_float.py +97 -0
- dqlite_dbapi-0.2.0/tests/integration/test_no_transaction_error_wording.py +127 -0
- dqlite_dbapi-0.2.0/tests/integration/test_null_vs_empty_text_blob_three_way.py +91 -0
- dqlite_dbapi-0.2.0/tests/integration/test_pragma_deny_list.py +60 -0
- dqlite_dbapi-0.2.0/tests/integration/test_query_detection.py +66 -0
- dqlite_dbapi-0.2.0/tests/integration/test_returning.py +98 -0
- dqlite_dbapi-0.2.0/tests/integration/test_savepoint_dbapi.py +155 -0
- dqlite_dbapi-0.2.0/tests/integration/test_savepoint_failure_responses.py +95 -0
- dqlite_dbapi-0.2.0/tests/integration/test_sqlite_version_pin.py +73 -0
- dqlite_dbapi-0.2.0/tests/integration/test_sync_transaction_ctxmgr_rejects_stray_commit.py +120 -0
- dqlite_dbapi-0.2.0/tests/test_aconn_execute_with_parameters.py +59 -0
- dqlite_dbapi-0.2.0/tests/test_aconn_factory_getters.py +54 -0
- dqlite_dbapi-0.2.0/tests/test_aconnect_cleanup_close_shielded_against_outer_timeout.py +91 -0
- dqlite_dbapi-0.2.0/tests/test_aconnect_cleanup_on_connect_failure.py +91 -0
- dqlite_dbapi-0.2.0/tests/test_adapter_exceptions_wrapped_as_data_error.py +175 -0
- dqlite_dbapi-0.2.0/tests/test_aenter_cleanup_preserves_original_under_outer_cancel.py +87 -0
- dqlite_dbapi-0.2.0/tests/test_aexit_behavioural_no_close.py +90 -0
- dqlite_dbapi-0.2.0/tests/test_aexit_commit_cancel_debug_log.py +112 -0
- dqlite_dbapi-0.2.0/tests/test_aexit_rollback_cancel_body_context_pin.py +97 -0
- dqlite_dbapi-0.2.0/tests/test_aexit_rollback_debug_log.py +71 -0
- dqlite_dbapi-0.2.0/tests/test_aio_commit_rollback_no_tx.py +76 -0
- dqlite_dbapi-0.2.0/tests/test_aio_commit_rollback_no_tx_code_matrix.py +101 -0
- dqlite_dbapi-0.2.0/tests/test_aio_commit_rollback_wrapping.py +149 -0
- dqlite_dbapi-0.2.0/tests/test_aio_connection_finalizer_detached_on_cancel_close.py +75 -0
- dqlite_dbapi-0.2.0/tests/test_aio_connection_finalizer_detached_on_close.py +79 -0
- dqlite_dbapi-0.2.0/tests/test_aio_cursor_drain_rows.py +91 -0
- dqlite_dbapi-0.2.0/tests/test_aio_cursor_row_factory_typeerror_wrap.py +113 -0
- dqlite_dbapi-0.2.0/tests/test_aio_description_tuple_exported.py +23 -0
- dqlite_dbapi-0.2.0/tests/test_aio_executemany_cursor_close_cascade.py +36 -0
- dqlite_dbapi-0.2.0/tests/test_aio_force_close_transport_cursor_cascade.py +102 -0
- dqlite_dbapi-0.2.0/tests/test_aio_init_reexports_diagnostic_prefixes.py +40 -0
- dqlite_dbapi-0.2.0/tests/test_aio_loop_affinity_exc_class.py +70 -0
- dqlite_dbapi-0.2.0/tests/test_aio_module_attributes.py +80 -0
- dqlite_dbapi-0.2.0/tests/test_aio_register_adapter_export.py +47 -0
- dqlite_dbapi-0.2.0/tests/test_aio_register_converter_stubs.py +36 -0
- dqlite_dbapi-0.2.0/tests/test_aio_row_factory_setter_loop_binding.py +109 -0
- dqlite_dbapi-0.2.0/tests/test_aio_setters_clear_messages_pep249.py +45 -0
- dqlite_dbapi-0.2.0/tests/test_arraysize_validation.py +109 -0
- dqlite_dbapi-0.2.0/tests/test_async_backup_not_async_def.py +34 -0
- dqlite_dbapi-0.2.0/tests/test_async_check_loop_only_pid_before_loop.py +87 -0
- dqlite_dbapi-0.2.0/tests/test_async_close_cancel_arm_cancels_inner_pending_drain.py +121 -0
- dqlite_dbapi-0.2.0/tests/test_async_close_ensure_locks_guard.py +45 -0
- dqlite_dbapi-0.2.0/tests/test_async_close_finally_propagates_cancel.py +163 -0
- dqlite_dbapi-0.2.0/tests/test_async_close_op_lock_bounded.py +147 -0
- dqlite_dbapi-0.2.0/tests/test_async_close_outer_cancel_force_closes_transport.py +119 -0
- dqlite_dbapi-0.2.0/tests/test_async_close_race.py +120 -0
- dqlite_dbapi-0.2.0/tests/test_async_commit_rollback_force_close_on_outer_cancel.py +125 -0
- dqlite_dbapi-0.2.0/tests/test_async_commit_rollback_in_transaction_under_lock.py +102 -0
- dqlite_dbapi-0.2.0/tests/test_async_commit_rollback_loop_binding_first.py +88 -0
- dqlite_dbapi-0.2.0/tests/test_async_commit_rollback_messages_under_lock.py +87 -0
- dqlite_dbapi-0.2.0/tests/test_async_commit_rollback_op_lock_bounded.py +67 -0
- dqlite_dbapi-0.2.0/tests/test_async_commit_rollback_phase_budget.py +109 -0
- dqlite_dbapi-0.2.0/tests/test_async_commit_rollback_retry_after_invalidate.py +143 -0
- dqlite_dbapi-0.2.0/tests/test_async_commit_rollback_timeout_phase_aware.py +118 -0
- dqlite_dbapi-0.2.0/tests/test_async_connect_cross_loop_diagnostic_on_fast_path.py +65 -0
- dqlite_dbapi-0.2.0/tests/test_async_connection.py +82 -0
- dqlite_dbapi-0.2.0/tests/test_async_connection_close_during_connect.py +72 -0
- dqlite_dbapi-0.2.0/tests/test_async_connection_cursor_fork_guard.py +82 -0
- dqlite_dbapi-0.2.0/tests/test_async_connection_ensure_connection_post_yield_close_shielded.py +188 -0
- dqlite_dbapi-0.2.0/tests/test_async_connection_race.py +44 -0
- dqlite_dbapi-0.2.0/tests/test_async_connection_setters_loop_binding.py +68 -0
- dqlite_dbapi-0.2.0/tests/test_async_connection_transaction.py +59 -0
- dqlite_dbapi-0.2.0/tests/test_async_cursor.py +270 -0
- dqlite_dbapi-0.2.0/tests/test_async_cursor_aclose_alias.py +98 -0
- dqlite_dbapi-0.2.0/tests/test_async_cursor_add_races_cascade_clear.py +151 -0
- dqlite_dbapi-0.2.0/tests/test_async_cursor_aenter_cross_task_executing_guard.py +80 -0
- dqlite_dbapi-0.2.0/tests/test_async_cursor_aenter_gc_parent_pep249.py +32 -0
- dqlite_dbapi-0.2.0/tests/test_async_cursor_aiter_aenter_pep249_pins.py +98 -0
- dqlite_dbapi-0.2.0/tests/test_async_cursor_aiter_cross_loop_fail_fast.py +68 -0
- dqlite_dbapi-0.2.0/tests/test_async_cursor_arraysize_row_factory_loop_binding.py +79 -0
- dqlite_dbapi-0.2.0/tests/test_async_cursor_callproc_nextset_scroll_loop_binding.py +91 -0
- dqlite_dbapi-0.2.0/tests/test_async_cursor_close_clears_executing_task.py +80 -0
- dqlite_dbapi-0.2.0/tests/test_async_cursor_close_is_sync_and_race_safe.py +137 -0
- dqlite_dbapi-0.2.0/tests/test_async_cursor_close_lock_free.py +75 -0
- dqlite_dbapi-0.2.0/tests/test_async_cursor_closed_state_pep249.py +110 -0
- dqlite_dbapi-0.2.0/tests/test_async_cursor_execute_executing_task_inside_try_frame.py +63 -0
- dqlite_dbapi-0.2.0/tests/test_async_cursor_execute_messages_clear.py +82 -0
- dqlite_dbapi-0.2.0/tests/test_async_cursor_executemany_validation_clears_executing_task.py +118 -0
- dqlite_dbapi-0.2.0/tests/test_async_cursor_fetch_loop_binding.py +146 -0
- dqlite_dbapi-0.2.0/tests/test_async_cursor_finally_clear_executing_task_unconditional.py +48 -0
- dqlite_dbapi-0.2.0/tests/test_async_cursor_late_add_proxy_swap.py +52 -0
- dqlite_dbapi-0.2.0/tests/test_async_cursor_loop_binding.py +61 -0
- dqlite_dbapi-0.2.0/tests/test_async_cursor_rownumber_closed_cross_loop.py +68 -0
- dqlite_dbapi-0.2.0/tests/test_async_cursor_setinputsizes_loop_binding.py +54 -0
- dqlite_dbapi-0.2.0/tests/test_async_executemany_atomic.py +63 -0
- dqlite_dbapi-0.2.0/tests/test_async_executemany_close_race.py +72 -0
- dqlite_dbapi-0.2.0/tests/test_async_executescript_stub_raises_on_call.py +61 -0
- dqlite_dbapi-0.2.0/tests/test_async_fetch_does_not_lazy_bind_loop.py +89 -0
- dqlite_dbapi-0.2.0/tests/test_async_fetchall_row_factory_happy_path.py +65 -0
- dqlite_dbapi-0.2.0/tests/test_async_force_close_transport.py +200 -0
- dqlite_dbapi-0.2.0/tests/test_async_in_transaction_closed_foreign_loop.py +75 -0
- dqlite_dbapi-0.2.0/tests/test_async_in_transaction_loop_binding.py +80 -0
- dqlite_dbapi-0.2.0/tests/test_async_invalidated_property.py +58 -0
- dqlite_dbapi-0.2.0/tests/test_async_lock_binding.py +143 -0
- dqlite_dbapi-0.2.0/tests/test_async_resource_warning_gate.py +144 -0
- dqlite_dbapi-0.2.0/tests/test_async_secondary_methods_clear_messages_before_guards.py +144 -0
- dqlite_dbapi-0.2.0/tests/test_async_transaction_owner_set_inside_try_frame.py +185 -0
- dqlite_dbapi-0.2.0/tests/test_async_with_exit_does_not_close.py +83 -0
- dqlite_dbapi-0.2.0/tests/test_audit_2026_05_coverage_gaps.py +293 -0
- dqlite_dbapi-0.2.0/tests/test_audit_2026_05_dbapi_coverage.py +421 -0
- dqlite_dbapi-0.2.0/tests/test_autocommit_doc_no_round_trip.py +69 -0
- dqlite_dbapi-0.2.0/tests/test_autocommit_legacy_transaction_control.py +68 -0
- dqlite_dbapi-0.2.0/tests/test_autocommit_property.py +83 -0
- dqlite_dbapi-0.2.0/tests/test_autocommit_setter_exact_int_gate.py +114 -0
- dqlite_dbapi-0.2.0/tests/test_binary_typeerror_stdlib_parity.py +53 -0
- dqlite_dbapi-0.2.0/tests/test_bind_typeerror_as_dataerror.py +53 -0
- dqlite_dbapi-0.2.0/tests/test_build_and_connect_base_exception_group_wrap.py +123 -0
- dqlite_dbapi-0.2.0/tests/test_build_and_connect_resolve_leader_oserror_wrap.py +84 -0
- dqlite_dbapi-0.2.0/tests/test_build_and_connect_wire_encode_error_translation.py +100 -0
- dqlite_dbapi-0.2.0/tests/test_call_client_base_exception_group_wrap.py +91 -0
- dqlite_dbapi-0.2.0/tests/test_call_client_baseexceptiongroup_cancel_split.py +110 -0
- dqlite_dbapi-0.2.0/tests/test_call_client_docstring_mapping_table.py +77 -0
- dqlite_dbapi-0.2.0/tests/test_call_client_mapping.py +155 -0
- dqlite_dbapi-0.2.0/tests/test_call_client_typeerror_message_does_not_blame_bind.py +43 -0
- dqlite_dbapi-0.2.0/tests/test_call_client_wire_encode_error.py +36 -0
- dqlite_dbapi-0.2.0/tests/test_callproc_noreturn_annotation.py +44 -0
- dqlite_dbapi-0.2.0/tests/test_callproc_scroll_clear_messages.py +88 -0
- dqlite_dbapi-0.2.0/tests/test_cascade_cursors_clears_messages.py +98 -0
- dqlite_dbapi-0.2.0/tests/test_classify_caller_sql.py +99 -0
- dqlite_dbapi-0.2.0/tests/test_classify_caller_sql_hardening.py +142 -0
- dqlite_dbapi-0.2.0/tests/test_classify_caller_sql_structural_reject_precedence.py +100 -0
- dqlite_dbapi-0.2.0/tests/test_classify_operational_code_zero.py +29 -0
- dqlite_dbapi-0.2.0/tests/test_cleanup_loop_thread_disarms_inner_and_reaps_drain.py +304 -0
- dqlite_dbapi-0.2.0/tests/test_cleanup_loop_thread_finalizer_fork_safe.py +191 -0
- dqlite_dbapi-0.2.0/tests/test_close_bypass_path_releases_latched_op_lock.py +72 -0
- dqlite_dbapi-0.2.0/tests/test_close_resource_leak.py +35 -0
- dqlite_dbapi-0.2.0/tests/test_close_signal_handler_reentry.py +177 -0
- dqlite_dbapi-0.2.0/tests/test_close_surfaces_op_lock_contention.py +96 -0
- dqlite_dbapi-0.2.0/tests/test_close_thread_join_uses_close_timeout.py +186 -0
- dqlite_dbapi-0.2.0/tests/test_close_timeout_floor_enforcement.py +59 -0
- dqlite_dbapi-0.2.0/tests/test_close_timeout_plumbing.py +113 -0
- dqlite_dbapi-0.2.0/tests/test_closed_property.py +104 -0
- dqlite_dbapi-0.2.0/tests/test_cluster_policy_rejection_prefix_constant.py +67 -0
- dqlite_dbapi-0.2.0/tests/test_code_to_exception_wire_ssot.py +58 -0
- dqlite_dbapi-0.2.0/tests/test_collated_coverage_gaps.py +193 -0
- dqlite_dbapi-0.2.0/tests/test_commit_after_external_close.py +83 -0
- dqlite_dbapi-0.2.0/tests/test_commit_leader_flip_ambiguous_commit_error.py +82 -0
- dqlite_dbapi-0.2.0/tests/test_commit_leader_flip_propagates.py +136 -0
- dqlite_dbapi-0.2.0/tests/test_commit_no_spurious_connect.py +72 -0
- dqlite_dbapi-0.2.0/tests/test_commit_rollback_shortcircuit_uses_in_transaction.py +182 -0
- dqlite_dbapi-0.2.0/tests/test_conform_adapter_non_primitive_rejected_at_dbapi_layer.py +83 -0
- dqlite_dbapi-0.2.0/tests/test_conftest_adapter_registry_isolation.py +31 -0
- dqlite_dbapi-0.2.0/tests/test_connect_baseexceptiongroup_cancel_split.py +108 -0
- dqlite_dbapi-0.2.0/tests/test_connect_clears_messages.py +46 -0
- dqlite_dbapi-0.2.0/tests/test_connect_closed_precedes_thread_affinity.py +82 -0
- dqlite_dbapi-0.2.0/tests/test_connect_docstring_parity.py +36 -0
- dqlite_dbapi-0.2.0/tests/test_connect_iso_autocommit_round_trip.py +115 -0
- dqlite_dbapi-0.2.0/tests/test_connect_isolation_level_autocommit_kwargs_symmetric.py +141 -0
- dqlite_dbapi-0.2.0/tests/test_connect_preserves_code.py +87 -0
- {dqlite_dbapi-0.1.2 → dqlite_dbapi-0.2.0}/tests/test_connection.py +12 -3
- dqlite_dbapi-0.2.0/tests/test_connection_after_fork_raises.py +157 -0
- dqlite_dbapi-0.2.0/tests/test_connection_autocommit_isolation_level_closed_state.py +95 -0
- dqlite_dbapi-0.2.0/tests/test_connection_close_cascades_cursors.py +76 -0
- dqlite_dbapi-0.2.0/tests/test_connection_close_cursor_cascade_atomicity.py +74 -0
- dqlite_dbapi-0.2.0/tests/test_connection_close_idempotent_cross_thread.py +72 -0
- dqlite_dbapi-0.2.0/tests/test_connection_closed_checks.py +293 -0
- dqlite_dbapi-0.2.0/tests/test_connection_cursor_class_attribute.py +61 -0
- dqlite_dbapi-0.2.0/tests/test_connection_cursor_closed_state_precedes_unknown_kwarg.py +43 -0
- dqlite_dbapi-0.2.0/tests/test_connection_database_empty_string_rejected.py +56 -0
- dqlite_dbapi-0.2.0/tests/test_connection_database_kwarg_isinstance.py +45 -0
- dqlite_dbapi-0.2.0/tests/test_connection_eager_address_validation.py +52 -0
- dqlite_dbapi-0.2.0/tests/test_connection_enter_eager.py +44 -0
- dqlite_dbapi-0.2.0/tests/test_connection_executemany_outer_shape_check.py +214 -0
- dqlite_dbapi-0.2.0/tests/test_connection_exit_commit_ki_breadcrumb.py +103 -0
- dqlite_dbapi-0.2.0/tests/test_connection_exit_short_circuits_on_closed.py +104 -0
- dqlite_dbapi-0.2.0/tests/test_connection_messages_cleared_by_cursor_methods.py +65 -0
- dqlite_dbapi-0.2.0/tests/test_connection_row_factory_setter_closed_check.py +57 -0
- dqlite_dbapi-0.2.0/tests/test_context_manager_exit.py +103 -0
- dqlite_dbapi-0.2.0/tests/test_convert_bind_param_conform_exception_propagates.py +77 -0
- dqlite_dbapi-0.2.0/tests/test_cte_prefix_stripper_edges.py +117 -0
- dqlite_dbapi-0.2.0/tests/test_cursor.py +234 -0
- dqlite_dbapi-0.2.0/tests/test_cursor_after_external_close.py +63 -0
- dqlite_dbapi-0.2.0/tests/test_cursor_close_clears_messages.py +38 -0
- dqlite_dbapi-0.2.0/tests/test_cursor_close_cross_thread.py +48 -0
- dqlite_dbapi-0.2.0/tests/test_cursor_close_preserves_body_exception_on_messages_strip.py +82 -0
- dqlite_dbapi-0.2.0/tests/test_cursor_close_releases_connection_pin.py +64 -0
- dqlite_dbapi-0.2.0/tests/test_cursor_closed_after_connection_gc_pep249.py +116 -0
- dqlite_dbapi-0.2.0/tests/test_cursor_closed_state_pep249.py +79 -0
- dqlite_dbapi-0.2.0/tests/test_cursor_completed_iterations_resets_on_single_execute.py +100 -0
- dqlite_dbapi-0.2.0/tests/test_cursor_connection_partial_init_attributeerror.py +52 -0
- dqlite_dbapi-0.2.0/tests/test_cursor_connection_property_after_gc_pep249.py +60 -0
- dqlite_dbapi-0.2.0/tests/test_cursor_execute_non_str_operation.py +45 -0
- dqlite_dbapi-0.2.0/tests/test_cursor_execute_state_reset_on_failure.py +411 -0
- dqlite_dbapi-0.2.0/tests/test_cursor_executemany_non_str_operation.py +80 -0
- dqlite_dbapi-0.2.0/tests/test_cursor_executemany_outer_shape_check.py +82 -0
- dqlite_dbapi-0.2.0/tests/test_cursor_factory_kwarg_rejected.py +56 -0
- dqlite_dbapi-0.2.0/tests/test_cursor_force_close_race.py +127 -0
- dqlite_dbapi-0.2.0/tests/test_cursor_iter_clears_messages_pep249.py +96 -0
- dqlite_dbapi-0.2.0/tests/test_cursor_iterator_reset.py +104 -0
- dqlite_dbapi-0.2.0/tests/test_cursor_kwargs_rejection_precedence.py +76 -0
- dqlite_dbapi-0.2.0/tests/test_cursor_next_row_factory_raise_replays_row.py +142 -0
- dqlite_dbapi-0.2.0/tests/test_cursor_per_row_types.py +177 -0
- dqlite_dbapi-0.2.0/tests/test_cursor_post_close_property_reads.py +121 -0
- dqlite_dbapi-0.2.0/tests/test_cursor_protocol_check.py +53 -0
- dqlite_dbapi-0.2.0/tests/test_cursor_rejects_generator_params.py +53 -0
- dqlite_dbapi-0.2.0/tests/test_cursor_repr_with_gced_parent_connection.py +85 -0
- dqlite_dbapi-0.2.0/tests/test_cursor_row_factory_rejecting_setter.py +104 -0
- dqlite_dbapi-0.2.0/tests/test_cursor_row_factory_subclass_inheritance.py +90 -0
- dqlite_dbapi-0.2.0/tests/test_cursor_row_index_reset.py +89 -0
- dqlite_dbapi-0.2.0/tests/test_cursor_rowcount_after_ddl_stdlib_parity.py +269 -0
- dqlite_dbapi-0.2.0/tests/test_cursor_rownumber_affinity.py +63 -0
- dqlite_dbapi-0.2.0/tests/test_cursor_rownumber_post_close.py +80 -0
- dqlite_dbapi-0.2.0/tests/test_cursor_scroll_mode_validation.py +157 -0
- dqlite_dbapi-0.2.0/tests/test_cursor_secondary_methods_positional_only.py +140 -0
- dqlite_dbapi-0.2.0/tests/test_cursor_setinputsizes_affinity_precedes_shape.py +168 -0
- dqlite_dbapi-0.2.0/tests/test_cursor_setinputsizes_setoutputsize_closed_state_precedes_validation.py +104 -0
- dqlite_dbapi-0.2.0/tests/test_cursor_setters_clear_messages_pep249.py +56 -0
- dqlite_dbapi-0.2.0/tests/test_cursor_stop_iteration_contract.py +90 -0
- dqlite_dbapi-0.2.0/tests/test_cursor_uses_run_protocol.py +63 -0
- dqlite_dbapi-0.2.0/tests/test_date_widens_to_datetime.py +34 -0
- dqlite_dbapi-0.2.0/tests/test_datetime_from_iso8601_docstring_two_step_fallback.py +47 -0
- dqlite_dbapi-0.2.0/tests/test_datetime_time_round_trip.py +54 -0
- dqlite_dbapi-0.2.0/tests/test_dbapi_commit_deferred_fk.py +39 -0
- dqlite_dbapi-0.2.0/tests/test_dbapi_default_timeouts_use_client_constants.py +57 -0
- dqlite_dbapi-0.2.0/tests/test_dbapi_exception_message_arg_cap.py +115 -0
- dqlite_dbapi-0.2.0/tests/test_dbapi_exception_raw_message_cap.py +77 -0
- dqlite_dbapi-0.2.0/tests/test_dbapi_fork_guard_cluster.py +246 -0
- dqlite_dbapi-0.2.0/tests/test_dbapi_hardening.py +548 -0
- dqlite_dbapi-0.2.0/tests/test_dbapi_type_hash_consistency.py +77 -0
- dqlite_dbapi-0.2.0/tests/test_description_null_first_row_resolves.py +125 -0
- dqlite_dbapi-0.2.0/tests/test_description_null_type_code_to_none.py +101 -0
- dqlite_dbapi-0.2.0/tests/test_description_strict_type_codes.py +59 -0
- dqlite_dbapi-0.2.0/tests/test_description_tuple_public_alias.py +44 -0
- dqlite_dbapi-0.2.0/tests/test_diagnostic_messages.py +140 -0
- dqlite_dbapi-0.2.0/tests/test_dial_attempt_timeout_propagation.py +117 -0
- dqlite_dbapi-0.2.0/tests/test_dial_func_propagation.py +82 -0
- dqlite_dbapi-0.2.0/tests/test_dial_func_propagation_to_leader_resolve.py +180 -0
- dqlite_dbapi-0.2.0/tests/test_doc_class_and_description_drift.py +68 -0
- dqlite_dbapi-0.2.0/tests/test_dqlite_namespace_error_classification.py +52 -0
- dqlite_dbapi-0.2.0/tests/test_ensure_loop_fast_path_snapshot_dcl_defence.py +100 -0
- dqlite_dbapi-0.2.0/tests/test_ensure_loop_post_fork_pid_guard.py +133 -0
- dqlite_dbapi-0.2.0/tests/test_error_code_mapping.py +120 -0
- dqlite_dbapi-0.2.0/tests/test_error_pickle_cause_either_or.py +50 -0
- dqlite_dbapi-0.2.0/tests/test_error_pickle_preserves_raw_message.py +78 -0
- dqlite_dbapi-0.2.0/tests/test_exception_coded_mixin.py +112 -0
- dqlite_dbapi-0.2.0/tests/test_exception_mapping.py +79 -0
- dqlite_dbapi-0.2.0/tests/test_exception_repr_code.py +55 -0
- dqlite_dbapi-0.2.0/tests/test_exception_wrapping.py +131 -0
- dqlite_dbapi-0.2.0/tests/test_execute_caller_param_shape_preserves_prior_state.py +111 -0
- dqlite_dbapi-0.2.0/tests/test_execute_executemany_positional_only.py +77 -0
- dqlite_dbapi-0.2.0/tests/test_execute_multi_statement_rejection.py +114 -0
- dqlite_dbapi-0.2.0/tests/test_execute_reject_scrubs_prior_result_state.py +212 -0
- dqlite_dbapi-0.2.0/tests/test_executemany_accumulator_semantics.py +110 -0
- dqlite_dbapi-0.2.0/tests/test_executemany_basecaught_state_reset.py +230 -0
- dqlite_dbapi-0.2.0/tests/test_executemany_cancel_restores_pre_batch_lastrowid.py +162 -0
- dqlite_dbapi-0.2.0/tests/test_executemany_cancellederror_restores_pre_batch_lastrowid.py +104 -0
- dqlite_dbapi-0.2.0/tests/test_executemany_classifier_resets_state_first.py +120 -0
- dqlite_dbapi-0.2.0/tests/test_executemany_classify_caller_sql_hoisted.py +61 -0
- dqlite_dbapi-0.2.0/tests/test_executemany_completed_iterations_snapshot_conditional_restore.py +160 -0
- dqlite_dbapi-0.2.0/tests/test_executemany_dict_subclass_accepted.py +80 -0
- dqlite_dbapi-0.2.0/tests/test_executemany_empty.py +112 -0
- dqlite_dbapi-0.2.0/tests/test_executemany_empty_batch_preserves_lastrowid.py +65 -0
- dqlite_dbapi-0.2.0/tests/test_executemany_none_seq_rejected.py +78 -0
- dqlite_dbapi-0.2.0/tests/test_executemany_per_iter_structural_rejects.py +122 -0
- dqlite_dbapi-0.2.0/tests/test_executemany_pragma_with_leading_comment.py +75 -0
- dqlite_dbapi-0.2.0/tests/test_executemany_reject_scrubs_prior_result_state.py +265 -0
- dqlite_dbapi-0.2.0/tests/test_executemany_rejects_pure_queries.py +103 -0
- dqlite_dbapi-0.2.0/tests/test_executemany_rejects_transaction_verbs.py +288 -0
- dqlite_dbapi-0.2.0/tests/test_executemany_returning_cap.py +69 -0
- dqlite_dbapi-0.2.0/tests/test_exit_logs_rollback_failure.py +102 -0
- dqlite_dbapi-0.2.0/tests/test_fetchall_row_factory_raise_replay.py +147 -0
- dqlite_dbapi-0.2.0/tests/test_fetchmany_edges.py +73 -0
- dqlite_dbapi-0.2.0/tests/test_fetchmany_negative_size_rejected.py +91 -0
- dqlite_dbapi-0.2.0/tests/test_fetchmany_non_int_validation.py +78 -0
- dqlite_dbapi-0.2.0/tests/test_fetchmany_one_clear_per_call_pep249.py +111 -0
- dqlite_dbapi-0.2.0/tests/test_fetchmany_row_factory_raise_replay.py +128 -0
- dqlite_dbapi-0.2.0/tests/test_fetchmany_zero_docstring_stdlib_divergence.py +60 -0
- dqlite_dbapi-0.2.0/tests/test_finalizer_strict_warnings.py +53 -0
- dqlite_dbapi-0.2.0/tests/test_force_close_cancel_and_observe_narrow_suppress.py +35 -0
- dqlite_dbapi-0.2.0/tests/test_force_close_observe_ast_pin.py +82 -0
- dqlite_dbapi-0.2.0/tests/test_force_close_observe_callback_coverage.py +71 -0
- dqlite_dbapi-0.2.0/tests/test_force_close_transport_cleanup_tail_unconditional.py +103 -0
- dqlite_dbapi-0.2.0/tests/test_force_close_transport_clears_messages.py +95 -0
- dqlite_dbapi-0.2.0/tests/test_force_close_transport_cross_thread_cancel_threadsafe.py +129 -0
- dqlite_dbapi-0.2.0/tests/test_force_close_transport_cursor_cascade_threadsafe.py +161 -0
- dqlite_dbapi-0.2.0/tests/test_force_close_transport_defensive_arms.py +123 -0
- dqlite_dbapi-0.2.0/tests/test_force_close_transport_disarms_inner_finalizer.py +85 -0
- dqlite_dbapi-0.2.0/tests/test_force_close_transport_pending_drain_resnapshot.py +143 -0
- dqlite_dbapi-0.2.0/tests/test_force_close_transport_writer_cross_thread.py +130 -0
- dqlite_dbapi-0.2.0/tests/test_fork_diagnostic_includes_pids.py +48 -0
- dqlite_dbapi-0.2.0/tests/test_fromticks_tz_policy.py +44 -0
- dqlite_dbapi-0.2.0/tests/test_gc_cleanup.py +55 -0
- dqlite_dbapi-0.2.0/tests/test_get_resolve_leader_cluster_no_loop_raises_runtime_error.py +37 -0
- dqlite_dbapi-0.2.0/tests/test_in_transaction_after_close.py +113 -0
- dqlite_dbapi-0.2.0/tests/test_in_transaction_closed_divergence_doc.py +55 -0
- dqlite_dbapi-0.2.0/tests/test_in_transaction_closed_foreign_thread.py +107 -0
- dqlite_dbapi-0.2.0/tests/test_in_transaction_property.py +39 -0
- dqlite_dbapi-0.2.0/tests/test_init_docstring_fromticks_three_impls.py +64 -0
- dqlite_dbapi-0.2.0/tests/test_is_multi_statement_walker.py +61 -0
- dqlite_dbapi-0.2.0/tests/test_is_no_transaction_error.py +162 -0
- dqlite_dbapi-0.2.0/tests/test_is_row_returning_multiline_whitespace.py +112 -0
- dqlite_dbapi-0.2.0/tests/test_is_row_returning_sql_noise.py +76 -0
- dqlite_dbapi-0.2.0/tests/test_iso8601_aware_microseconds_round_trip.py +72 -0
- dqlite_dbapi-0.2.0/tests/test_iso8601_decoder_leap_seconds_rejected.py +39 -0
- dqlite_dbapi-0.2.0/tests/test_iso8601_decoder_subsecond_truncation.py +92 -0
- dqlite_dbapi-0.2.0/tests/test_iso8601_encoder.py +471 -0
- dqlite_dbapi-0.2.0/tests/test_iso8601_encoder_fold.py +73 -0
- dqlite_dbapi-0.2.0/tests/test_iso8601_encoder_utcoffset_exception_wrap.py +133 -0
- dqlite_dbapi-0.2.0/tests/test_iso8601_parse_error_truncation.py +67 -0
- dqlite_dbapi-0.2.0/tests/test_iso8601_tzinfo_exception_classes_wrapped.py +83 -0
- dqlite_dbapi-0.2.0/tests/test_isolation_level_stdlib_accept_set.py +83 -0
- dqlite_dbapi-0.2.0/tests/test_iterdump_stub_signature_3_13_filter_kwarg.py +69 -0
- dqlite_dbapi-0.2.0/tests/test_lastrowid_docstring_stdlib_parity_claim.py +66 -0
- dqlite_dbapi-0.2.0/tests/test_lastrowid_insert_only.py +72 -0
- dqlite_dbapi-0.2.0/tests/test_lastrowid_signed_int64_cast.py +59 -0
- dqlite_dbapi-0.2.0/tests/test_leader_error_codes_identity.py +45 -0
- dqlite_dbapi-0.2.0/tests/test_leader_redirect_on_connect.py +279 -0
- dqlite_dbapi-0.2.0/tests/test_legacy_transaction_control_constant.py +44 -0
- dqlite_dbapi-0.2.0/tests/test_max_continuation_frames_upper_bound.py +65 -0
- dqlite_dbapi-0.2.0/tests/test_max_message_size_round_trip.py +82 -0
- dqlite_dbapi-0.2.0/tests/test_max_total_rows_plumbing.py +70 -0
- dqlite_dbapi-0.2.0/tests/test_max_total_rows_validation.py +56 -0
- dqlite_dbapi-0.2.0/tests/test_messages_attribute.py +34 -0
- dqlite_dbapi-0.2.0/tests/test_messages_clear_on_ctxmgr_entry.py +84 -0
- dqlite_dbapi-0.2.0/tests/test_module_attributes.py +243 -0
- dqlite_dbapi-0.2.0/tests/test_module_level_stubs.py +107 -0
- dqlite_dbapi-0.2.0/tests/test_nextset_noreturn_annotation.py +43 -0
- dqlite_dbapi-0.2.0/tests/test_no_transaction_substrings_source_of_truth.py +31 -0
- dqlite_dbapi-0.2.0/tests/test_no_tx_primary_codes_invariant.py +65 -0
- dqlite_dbapi-0.2.0/tests/test_op_lock_bounded_acquire.py +87 -0
- dqlite_dbapi-0.2.0/tests/test_operational_error_classification.py +76 -0
- dqlite_dbapi-0.2.0/tests/test_operational_error_raw_message.py +81 -0
- dqlite_dbapi-0.2.0/tests/test_param_conversion.py +71 -0
- dqlite_dbapi-0.2.0/tests/test_param_validation.py +48 -0
- dqlite_dbapi-0.2.0/tests/test_paramstyle_qmark_only_pin.py +75 -0
- dqlite_dbapi-0.2.0/tests/test_parse_decltypes_colnames_constants.py +65 -0
- dqlite_dbapi-0.2.0/tests/test_pep249_database_error_routing.py +55 -0
- dqlite_dbapi-0.2.0/tests/test_pep249_exception_aliases.py +80 -0
- dqlite_dbapi-0.2.0/tests/test_pep249_messages_clearing.py +256 -0
- dqlite_dbapi-0.2.0/tests/test_pep249_messages_independence.py +88 -0
- dqlite_dbapi-0.2.0/tests/test_pep249_misc_pins.py +181 -0
- dqlite_dbapi-0.2.0/tests/test_pep249_stub_hasattr_divergence.py +103 -0
- dqlite_dbapi-0.2.0/tests/test_pep249_type_constructors_vs_singletons.py +81 -0
- dqlite_dbapi-0.2.0/tests/test_pep249_type_constructors_wrap_valueerror.py +49 -0
- dqlite_dbapi-0.2.0/tests/test_pep249_type_objects_final_annotated.py +42 -0
- dqlite_dbapi-0.2.0/tests/test_pickle_guard.py +80 -0
- dqlite_dbapi-0.2.0/tests/test_pragma_write_description_none.py +60 -0
- dqlite_dbapi-0.2.0/tests/test_prepare_protocol_in_types_all.py +38 -0
- dqlite_dbapi-0.2.0/tests/test_property_round_trip_and_fork_guard.py +255 -0
- dqlite_dbapi-0.2.0/tests/test_property_setters_do_not_mirror_to_inner_async_conn.py +107 -0
- dqlite_dbapi-0.2.0/tests/test_protocol_serialization.py +99 -0
- dqlite_dbapi-0.2.0/tests/test_pyproject_dependency_graph.py +52 -0
- dqlite_dbapi-0.2.0/tests/test_readme_truthfulness_pins.py +73 -0
- dqlite_dbapi-0.2.0/tests/test_register_adapter_conform_fallback.py +162 -0
- dqlite_dbapi-0.2.0/tests/test_register_adapter_global_sync_aio_symmetry.py +73 -0
- dqlite_dbapi-0.2.0/tests/test_repr.py +66 -0
- dqlite_dbapi-0.2.0/tests/test_repr_sanitizes_address.py +162 -0
- dqlite_dbapi-0.2.0/tests/test_resolve_leader_cache_dial_func_id_recycle.py +178 -0
- dqlite_dbapi-0.2.0/tests/test_resolve_leader_cache_lock_atfork_hook.py +73 -0
- dqlite_dbapi-0.2.0/tests/test_resolve_leader_cluster_cache.py +124 -0
- dqlite_dbapi-0.2.0/tests/test_resolve_leader_cross_loop_isolation.py +233 -0
- dqlite_dbapi-0.2.0/tests/test_row_factory_annotation.py +67 -0
- dqlite_dbapi-0.2.0/tests/test_row_factory_sqlite3_row_surfaces_dataerror.py +94 -0
- dqlite_dbapi-0.2.0/tests/test_rowid_number_overlap_documented.py +43 -0
- dqlite_dbapi-0.2.0/tests/test_rownumber_closed_cursor_returns_none_documented.py +87 -0
- dqlite_dbapi-0.2.0/tests/test_run_sync_ensure_loop_raise_closes_coroutine.py +105 -0
- dqlite_dbapi-0.2.0/tests/test_run_sync_keyboard_interrupt.py +373 -0
- dqlite_dbapi-0.2.0/tests/test_run_sync_keyboard_interrupt_cleanup_paths.py +162 -0
- dqlite_dbapi-0.2.0/tests/test_run_sync_ki_post_schedule_pre_await.py +122 -0
- dqlite_dbapi-0.2.0/tests/test_run_sync_ki_race_recovery.py +198 -0
- dqlite_dbapi-0.2.0/tests/test_run_sync_loop_closed_during_schedule.py +143 -0
- dqlite_dbapi-0.2.0/tests/test_run_sync_other_runtimeerror_propagates.py +113 -0
- dqlite_dbapi-0.2.0/tests/test_run_sync_timeout.py +412 -0
- dqlite_dbapi-0.2.0/tests/test_run_sync_timeout_recovery_preserves_conn.py +111 -0
- dqlite_dbapi-0.2.0/tests/test_secondary_methods_clear_messages_before_thread_check.py +99 -0
- dqlite_dbapi-0.2.0/tests/test_setinputsizes_accepts_none.py +60 -0
- dqlite_dbapi-0.2.0/tests/test_setinputsizes_clears_messages_per_pep249.py +55 -0
- dqlite_dbapi-0.2.0/tests/test_setinputsizes_non_sequence_iterable.py +87 -0
- dqlite_dbapi-0.2.0/tests/test_setinputsizes_sequence_acceptance.py +80 -0
- dqlite_dbapi-0.2.0/tests/test_setinputsizes_short_circuit_on_closed_connection.py +112 -0
- dqlite_dbapi-0.2.0/tests/test_setoutputsize_accepts_none.py +61 -0
- dqlite_dbapi-0.2.0/tests/test_setter_closed_state_precedes_thread.py +78 -0
- dqlite_dbapi-0.2.0/tests/test_sqlite_errorname_property.py +96 -0
- dqlite_dbapi-0.2.0/tests/test_sqlite_version_not_public_attribute.py +52 -0
- dqlite_dbapi-0.2.0/tests/test_strip_leading_comments_bom.py +70 -0
- dqlite_dbapi-0.2.0/tests/test_strip_leading_comments_parity.py +96 -0
- dqlite_dbapi-0.2.0/tests/test_strip_sql_noise_backtick_identifier.py +73 -0
- dqlite_dbapi-0.2.0/tests/test_stub_signatures_normalize_to_dbapi_error.py +161 -0
- dqlite_dbapi-0.2.0/tests/test_submodule_all.py +39 -0
- dqlite_dbapi-0.2.0/tests/test_sync_commit_rollback_closed_precedes_thread.py +47 -0
- dqlite_dbapi-0.2.0/tests/test_sync_commit_rollback_inside_ctxmgr_guard.py +125 -0
- dqlite_dbapi-0.2.0/tests/test_sync_commit_rollback_messages_under_lock.py +78 -0
- dqlite_dbapi-0.2.0/tests/test_sync_commit_rollback_retry_after_invalidate.py +87 -0
- dqlite_dbapi-0.2.0/tests/test_sync_connection_execute_shortcut.py +67 -0
- dqlite_dbapi-0.2.0/tests/test_sync_connection_executemany_shortcut.py +43 -0
- dqlite_dbapi-0.2.0/tests/test_sync_cursor_post_add_closed_recheck.py +151 -0
- dqlite_dbapi-0.2.0/tests/test_sync_exit_signal_mid_rollback.py +75 -0
- dqlite_dbapi-0.2.0/tests/test_sync_fetchmany_cancel_atomic.py +84 -0
- dqlite_dbapi-0.2.0/tests/test_sync_force_close_transport.py +192 -0
- dqlite_dbapi-0.2.0/tests/test_sync_force_close_transport_pending_drain_reap.py +185 -0
- dqlite_dbapi-0.2.0/tests/test_sync_invalidated_property.py +59 -0
- dqlite_dbapi-0.2.0/tests/test_sync_run_sync_multiphase_timeout.py +127 -0
- dqlite_dbapi-0.2.0/tests/test_sync_transaction_owner_token_bookkeeping.py +170 -0
- dqlite_dbapi-0.2.0/tests/test_thread_safety_enforcement.py +359 -0
- dqlite_dbapi-0.2.0/tests/test_timeout_validation.py +74 -0
- dqlite_dbapi-0.2.0/tests/test_top_level_logger_has_null_handler.py +28 -0
- dqlite_dbapi-0.2.0/tests/test_total_changes_hasattr_safe.py +62 -0
- dqlite_dbapi-0.2.0/tests/test_transitive_wire_import.py +52 -0
- dqlite_dbapi-0.2.0/tests/test_type_coercion_documented_asymmetries.py +183 -0
- dqlite_dbapi-0.2.0/tests/test_types_dbapi_compare.py +263 -0
- dqlite_dbapi-0.2.0/tests/test_types_validator_branches.py +125 -0
- dqlite_dbapi-0.2.0/tests/test_unregister_adapter.py +50 -0
- dqlite_dbapi-0.2.0/tests/test_unregister_adapter_rejects_built_in_datetime_types.py +87 -0
- dqlite_dbapi-0.2.0/tests/test_unsupported_method_stubs.py +403 -0
- dqlite_dbapi-0.2.0/tests/test_validate_positive_int_pep249_wrap.py +103 -0
- dqlite_dbapi-0.2.0/tests/test_validate_ticks.py +86 -0
- dqlite_dbapi-0.2.0/tests/test_validate_ticks_and_unixtime_reject_bool_and_non_numeric.py +79 -0
- dqlite_dbapi-0.2.0/tests/test_version.py +13 -0
- dqlite_dbapi-0.2.0/tests/test_version_export.py +19 -0
- dqlite_dbapi-0.2.0/uv.lock +360 -0
- dqlite_dbapi-0.1.2/PKG-INFO +0 -82
- dqlite_dbapi-0.1.2/README.md +0 -51
- dqlite_dbapi-0.1.2/pyproject.toml +0 -63
- dqlite_dbapi-0.1.2/src/dqlitedbapi/__init__.py +0 -98
- dqlite_dbapi-0.1.2/src/dqlitedbapi/aio/__init__.py +0 -43
- dqlite_dbapi-0.1.2/src/dqlitedbapi/aio/connection.py +0 -86
- dqlite_dbapi-0.1.2/src/dqlitedbapi/aio/cursor.py +0 -166
- dqlite_dbapi-0.1.2/src/dqlitedbapi/connection.py +0 -128
- dqlite_dbapi-0.1.2/src/dqlitedbapi/cursor.py +0 -177
- dqlite_dbapi-0.1.2/src/dqlitedbapi/exceptions.py +0 -61
- dqlite_dbapi-0.1.2/src/dqlitedbapi/types.py +0 -64
- dqlite_dbapi-0.1.2/tests/conftest.py +0 -1
- dqlite_dbapi-0.1.2/tests/integration/test_returning.py +0 -64
- dqlite_dbapi-0.1.2/tests/test_cursor.py +0 -88
- dqlite_dbapi-0.1.2/tests/test_module_attributes.py +0 -92
- {dqlite_dbapi-0.1.2 → dqlite_dbapi-0.2.0}/.github/workflows/publish-to-pypi.yml +0 -0
- {dqlite_dbapi-0.1.2 → dqlite_dbapi-0.2.0}/.gitignore +0 -0
- {dqlite_dbapi-0.1.2 → dqlite_dbapi-0.2.0}/LICENSE.md +0 -0
- {dqlite_dbapi-0.1.2 → dqlite_dbapi-0.2.0}/src/dqlitedbapi/py.typed +0 -0
- {dqlite_dbapi-0.1.2 → dqlite_dbapi-0.2.0}/tests/__init__.py +0 -0
- {dqlite_dbapi-0.1.2 → dqlite_dbapi-0.2.0}/tests/integration/__init__.py +0 -0
- {dqlite_dbapi-0.1.2 → dqlite_dbapi-0.2.0}/tests/integration/conftest.py +0 -0
- {dqlite_dbapi-0.1.2 → dqlite_dbapi-0.2.0}/tests/test_exceptions.py +0 -0
|
@@ -8,15 +8,14 @@
|
|
|
8
8
|
|
|
9
9
|
## Setup
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
Clone [dqlite-wire](https://github.com/letsdiscodev/python-dqlite-wire) and [dqlite-client](https://github.com/letsdiscodev/python-dqlite-client) alongside this checkout; `[tool.uv.sources]` in `pyproject.toml` points the sibling packages at `../python-dqlite-wire` and `../python-dqlite-client`, so `uv sync` picks up in-tree changes automatically.
|
|
12
12
|
|
|
13
13
|
```bash
|
|
14
14
|
# Install uv (if not already installed)
|
|
15
15
|
curl -LsSf https://astral.sh/uv/install.sh | sh
|
|
16
16
|
|
|
17
17
|
# Create virtual environment and install dependencies
|
|
18
|
-
uv
|
|
19
|
-
uv pip install -e "../python-dqlite-wire" -e "../python-dqlite-client" -e ".[dev]"
|
|
18
|
+
uv sync --extra dev
|
|
20
19
|
```
|
|
21
20
|
|
|
22
21
|
## Development Tools
|
|
@@ -0,0 +1,369 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: dqlite-dbapi
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: PEP 249 (DB-API 2.0) compliant interface for dqlite
|
|
5
|
+
Project-URL: Homepage, https://github.com/letsdiscodev/python-dqlite-dbapi
|
|
6
|
+
Project-URL: Repository, https://github.com/letsdiscodev/python-dqlite-dbapi
|
|
7
|
+
Project-URL: Issues, https://github.com/letsdiscodev/python-dqlite-dbapi/issues
|
|
8
|
+
Author-email: Antoine Leclair <antoineleclair@gmail.com>
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
License-File: LICENSE.md
|
|
11
|
+
Keywords: database,dbapi,distributed,dqlite,pep249,sqlite
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Framework :: AsyncIO
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
+
Classifier: Operating System :: OS Independent
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
20
|
+
Classifier: Topic :: Database
|
|
21
|
+
Classifier: Topic :: Database :: Database Engines/Servers
|
|
22
|
+
Classifier: Topic :: Database :: Front-Ends
|
|
23
|
+
Classifier: Typing :: Typed
|
|
24
|
+
Requires-Python: >=3.13
|
|
25
|
+
Requires-Dist: dqlite-client<0.3.0,>=0.2.0
|
|
26
|
+
Requires-Dist: dqlite-wire<0.3.0,>=0.2.0
|
|
27
|
+
Provides-Extra: dev
|
|
28
|
+
Requires-Dist: mypy>=1.0; extra == 'dev'
|
|
29
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
|
|
30
|
+
Requires-Dist: pytest-cov>=4.0; extra == 'dev'
|
|
31
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
32
|
+
Requires-Dist: ruff>=0.4; extra == 'dev'
|
|
33
|
+
Description-Content-Type: text/markdown
|
|
34
|
+
|
|
35
|
+
# dqlite-dbapi
|
|
36
|
+
|
|
37
|
+
PEP 249 compliant interface for [dqlite](https://dqlite.io/).
|
|
38
|
+
|
|
39
|
+
## Installation
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
pip install dqlite-dbapi
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Sync Usage
|
|
46
|
+
|
|
47
|
+
```python
|
|
48
|
+
import dqlitedbapi
|
|
49
|
+
|
|
50
|
+
conn = dqlitedbapi.connect("localhost:9001")
|
|
51
|
+
cursor = conn.cursor()
|
|
52
|
+
cursor.execute("SELECT 1")
|
|
53
|
+
print(cursor.fetchone())
|
|
54
|
+
conn.close()
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Async Usage
|
|
58
|
+
|
|
59
|
+
```python
|
|
60
|
+
import asyncio
|
|
61
|
+
from dqlitedbapi.aio import aconnect
|
|
62
|
+
|
|
63
|
+
async def main():
|
|
64
|
+
conn = await aconnect("localhost:9001")
|
|
65
|
+
cursor = conn.cursor()
|
|
66
|
+
await cursor.execute("SELECT 1")
|
|
67
|
+
print(await cursor.fetchone())
|
|
68
|
+
await conn.close()
|
|
69
|
+
|
|
70
|
+
asyncio.run(main())
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## PEP 249 Compliance
|
|
74
|
+
|
|
75
|
+
- `apilevel = "2.0"`
|
|
76
|
+
- `threadsafety = 1`
|
|
77
|
+
- `paramstyle = "qmark"`
|
|
78
|
+
|
|
79
|
+
## Transactions
|
|
80
|
+
|
|
81
|
+
`dqlite-dbapi` does **not** issue implicit `BEGIN` before DML — each
|
|
82
|
+
statement runs in the underlying SQLite engine's autocommit mode unless
|
|
83
|
+
the caller has explicitly opened a transaction. This deviates from
|
|
84
|
+
PEP 249 §6's prescribed implicit-transaction model and from stdlib
|
|
85
|
+
`sqlite3` / `psycopg`, both of which auto-BEGIN on the first DML; users
|
|
86
|
+
porting from those drivers will see different behaviour and must add
|
|
87
|
+
explicit `BEGIN` calls (see below) to recover atomic multi-statement
|
|
88
|
+
semantics. The dqlite C and Go reference clients have the same opt-in
|
|
89
|
+
contract; this driver matches them rather than stdlib.
|
|
90
|
+
|
|
91
|
+
If you use SQLAlchemy via `sqlalchemy-dqlite`, the dialect emits
|
|
92
|
+
`BEGIN` for every `engine.begin()` / `connection.begin()` block — no
|
|
93
|
+
explicit `BEGIN` needed; the autocommit-by-default gap below applies
|
|
94
|
+
only to direct dbapi users.
|
|
95
|
+
|
|
96
|
+
Every write still goes through Raft consensus and every read is
|
|
97
|
+
serializable; **isolation is always SERIALIZABLE**, but transaction
|
|
98
|
+
*grouping* is opt-in.
|
|
99
|
+
|
|
100
|
+
To group statements into a transaction, issue an explicit `BEGIN`
|
|
101
|
+
through a cursor (or use `dqliteclient`'s `transaction()` async
|
|
102
|
+
context manager from the layer below):
|
|
103
|
+
|
|
104
|
+
```python
|
|
105
|
+
cur = conn.cursor()
|
|
106
|
+
cur.execute("BEGIN")
|
|
107
|
+
cur.execute("INSERT INTO t VALUES (?)", (1,))
|
|
108
|
+
cur.execute("INSERT INTO t VALUES (?)", (2,))
|
|
109
|
+
conn.commit() # COMMIT
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
The bare `BEGIN` SQL is the SQLite default (`BEGIN DEFERRED`). dqlite's
|
|
113
|
+
Raft FSM serializes the transaction across the cluster regardless of
|
|
114
|
+
the `DEFERRED` / `IMMEDIATE` / `EXCLUSIVE` qualifier, so the qualifier
|
|
115
|
+
does not change isolation semantics — only the lock-acquisition timing
|
|
116
|
+
on the leader. The `Connection.isolation_level` attribute exists for
|
|
117
|
+
pre-3.12 stdlib parity: the getter returns `None`, the setter accepts
|
|
118
|
+
only `None` (no-op) and rejects every other value (`""`, `"DEFERRED"`,
|
|
119
|
+
`"IMMEDIATE"`, `"EXCLUSIVE"`, `"SERIALIZABLE"`, `"AUTOCOMMIT"`, etc.)
|
|
120
|
+
with `NotSupportedError`. dqlite is autocommit-by-default at the dbapi
|
|
121
|
+
layer; explicit transactions are managed via `conn.commit()` /
|
|
122
|
+
`conn.rollback()` per PEP 249. The SQLAlchemy dialect rejects
|
|
123
|
+
`AUTOCOMMIT` on the same grounds.
|
|
124
|
+
|
|
125
|
+
Connection-level `commit()` / `rollback()` semantics:
|
|
126
|
+
|
|
127
|
+
- Calling `commit()` / `rollback()` before any query has run is a silent
|
|
128
|
+
no-op (preserves the "no spurious connect" contract — we don't open
|
|
129
|
+
a TCP connection just to send COMMIT).
|
|
130
|
+
- Calling `commit()` / `rollback()` with no active transaction
|
|
131
|
+
(e.g. right after a DDL statement) is also silently successful, matching
|
|
132
|
+
stdlib `sqlite3` semantics.
|
|
133
|
+
- The context-manager exit (`with conn: ...`) commits on clean exit and
|
|
134
|
+
attempts rollback on exception. **Commit failures propagate** —
|
|
135
|
+
silent swallowing was a footgun that could hide data loss.
|
|
136
|
+
- Operational caveat: a leader-flip during COMMIT raises
|
|
137
|
+
`OperationalError` with a code in `dqlitewire.LEADER_ERROR_CODES`. The
|
|
138
|
+
write may or may not have been persisted (Raft may already have
|
|
139
|
+
replicated the commit log entry before the flip). Use idempotent DML
|
|
140
|
+
(`INSERT OR REPLACE`, `UPDATE` on a unique key) or an out-of-band
|
|
141
|
+
state-check before retrying.
|
|
142
|
+
|
|
143
|
+
## Differences from `aiosqlite`
|
|
144
|
+
|
|
145
|
+
This driver is PEP 249-shaped (sync) and exposes an async surface
|
|
146
|
+
under `dqlitedbapi.aio` that mirrors the sync `dqlitedbapi` surface
|
|
147
|
+
plus async-specific `aconnect` / `AsyncConnection` / `AsyncCursor`.
|
|
148
|
+
|
|
149
|
+
The full PEP 249 module attributes (`apilevel`, `threadsafety`,
|
|
150
|
+
`paramstyle`, `sqlite_version`, `sqlite_version_info`), exception
|
|
151
|
+
hierarchy (`Warning`, `Error`, `InterfaceError`, `DatabaseError`,
|
|
152
|
+
`DataError`, `OperationalError`, `IntegrityError`, `InternalError`,
|
|
153
|
+
`ProgrammingError`, `NotSupportedError`), type constructors
|
|
154
|
+
(`Date`, `Time`, `Timestamp`, `DateFromTicks`, `TimeFromTicks`,
|
|
155
|
+
`TimestampFromTicks`, `Binary`), type sentinels (`STRING`, `BINARY`,
|
|
156
|
+
`NUMBER`, `DATETIME`, `ROWID`), the working `register_adapter` /
|
|
157
|
+
`unregister_adapter` (process-global; mirrors stdlib's pre-3.12
|
|
158
|
+
behavior), and stdlib-sqlite3-parity stubs that raise
|
|
159
|
+
`NotSupportedError` (`register_converter`, `complete_statement`,
|
|
160
|
+
`enable_callback_tracebacks`) are all re-exported under
|
|
161
|
+
`dqlitedbapi.aio` so cross-driver code porting from aiosqlite
|
|
162
|
+
imports them from one namespace. One notable deviation:
|
|
163
|
+
|
|
164
|
+
- **`AsyncConnection.__aexit__` does NOT close the connection.** It
|
|
165
|
+
commits / rolls back per the body outcome and leaves the
|
|
166
|
+
connection reusable, matching stdlib `sqlite3.Connection.__exit__`
|
|
167
|
+
and PEP 249 §7. `aiosqlite.Connection.__aexit__` DOES close (closes
|
|
168
|
+
its proxy thread + sqlite3 connection). Cross-driver code porting
|
|
169
|
+
from `aiosqlite` must add an explicit `await conn.close()` (or
|
|
170
|
+
switch to a pool) for eager-close semantics.
|
|
171
|
+
|
|
172
|
+
```python
|
|
173
|
+
# aiosqlite-style (closes on aexit):
|
|
174
|
+
async with aiosqlite.connect(":memory:") as conn:
|
|
175
|
+
...
|
|
176
|
+
|
|
177
|
+
# dqlitedbapi.aio-style (does NOT close on aexit, matches stdlib sqlite3):
|
|
178
|
+
async with await dqlitedbapi.aio.aconnect(...) as conn:
|
|
179
|
+
...
|
|
180
|
+
await conn.close() # explicit
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
## Layering (pool ownership)
|
|
184
|
+
|
|
185
|
+
Each `dqlitedbapi.Connection` (sync or async) owns exactly one
|
|
186
|
+
underlying `dqliteclient.DqliteConnection`. The dbapi layer does NOT
|
|
187
|
+
itself pool client connections — pooling lives one layer up:
|
|
188
|
+
|
|
189
|
+
- **SQLAlchemy users**: SA's `QueuePool` (and async siblings) over
|
|
190
|
+
dbapi `Connection` objects is the production pool.
|
|
191
|
+
- **Direct dbapi users**: a custom pool over dbapi `Connection`
|
|
192
|
+
objects is the supported pattern.
|
|
193
|
+
- `dqliteclient.ConnectionPool` is for direct-client usage and is
|
|
194
|
+
unused by dqlitedbapi.
|
|
195
|
+
|
|
196
|
+
A dbapi `Connection.close()` always closes the underlying client
|
|
197
|
+
transport — not "return to a pool" — because the dbapi never
|
|
198
|
+
borrowed from one.
|
|
199
|
+
|
|
200
|
+
## Limitations vs. stdlib `sqlite3`
|
|
201
|
+
|
|
202
|
+
- **Multi-statement SQL is rejected.** `cursor.execute("SELECT 1;
|
|
203
|
+
SELECT 2;")` raises `ProgrammingError("You can only execute one
|
|
204
|
+
statement at a time.")` (stdlib's wording, but client-side, before
|
|
205
|
+
any wire round-trip). Split into separate `execute()` calls; there
|
|
206
|
+
is no `executescript` (it raises `NotSupportedError`).
|
|
207
|
+
- **`paramstyle = "qmark"` only.** stdlib accepts both `?` (qmark)
|
|
208
|
+
AND `:name` (named) within a single connection; this driver
|
|
209
|
+
advertises only `qmark` and rejects mappings with `ProgrammingError`.
|
|
210
|
+
- **Autocommit-by-default at the server.** Opposite of stdlib
|
|
211
|
+
`sqlite3`'s implicit-transaction model — see Transactions above.
|
|
212
|
+
- **`Cursor.rowcount = len(rows)` after SELECT.** stdlib returns `-1`
|
|
213
|
+
(rowcount is undefined for queries). dqlite knows the full result
|
|
214
|
+
set at execute time, so the literal-PEP-249 reading is honoured.
|
|
215
|
+
`rowcount = -1` is preserved for true non-result paths (PRAGMA
|
|
216
|
+
write, never-executed cursor).
|
|
217
|
+
- **`Cursor.executemany` clears `lastrowid` to `None`.** stdlib leaves
|
|
218
|
+
the prior INSERT's rowid sticky across executemany. The driver
|
|
219
|
+
diverges deliberately (which row's rowid is "the" rowid for a batch
|
|
220
|
+
of N inserts is ambiguous).
|
|
221
|
+
- **`Cursor.close()` scrubs `description` / `lastrowid` / `_rows`.**
|
|
222
|
+
stdlib preserves `description` and `lastrowid` post-close; dqlite
|
|
223
|
+
scrubs to `None` to enforce the closed-cursor "no operation
|
|
224
|
+
performed" surface.
|
|
225
|
+
- **`Cursor.lastrowid` returns `None` after a fresh CREATE TABLE.**
|
|
226
|
+
stdlib `sqlite3` returns `0` for a never-INSERTed cursor; dqlite
|
|
227
|
+
returns `None`. After the first INSERT/REPLACE both drivers agree
|
|
228
|
+
on the sticky value through subsequent UPDATE / DELETE / DDL.
|
|
229
|
+
- **`Cursor.fetchmany(0)` returns `[]`.** stdlib (verified 3.12.3) and
|
|
230
|
+
psycopg3 both treat `size=0` as "fetch arraysize" or "fetch all
|
|
231
|
+
remaining"; dqlite reads PEP 249 literally (size=0 → 0 rows). Pass
|
|
232
|
+
`None` or omit the argument to default to `arraysize`.
|
|
233
|
+
- **`Cursor.fetchmany(negative)` raises `ProgrammingError`.** Stdlib
|
|
234
|
+
`sqlite3.Cursor.fetchmany` on Python 3.13+ rejects negative sizes
|
|
235
|
+
with `ValueError`; older stdlib drained "fetch all remaining". The
|
|
236
|
+
dbapi follows current stdlib but wraps the rejection in
|
|
237
|
+
`ProgrammingError` to keep it in the dbapi.Error hierarchy per
|
|
238
|
+
PEP 249 §7.
|
|
239
|
+
- **`cursor.execute("")` raises `ProgrammingError("empty statement")`.**
|
|
240
|
+
stdlib silently accepts empty / whitespace-only / comment-only SQL
|
|
241
|
+
as a no-op; the driver pre-flight rejects per PEP 249 §7 to surface
|
|
242
|
+
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.
|
|
247
|
+
- **No `executescript` / `create_function` / `create_aggregate` /
|
|
248
|
+
`create_window_function` / `iterdump` / `backup` / `set_authorizer`
|
|
249
|
+
/ `serialize` / `blobopen`.** stdlib-specific APIs that have no
|
|
250
|
+
server-side counterpart in dqlite. Stubs raise `NotSupportedError`.
|
|
251
|
+
- **SERIALIZABLE isolation only.** Every statement is ordered by Raft;
|
|
252
|
+
weaker isolation levels aren't exposed.
|
|
253
|
+
- **PEP 249 type sentinels (`STRING`, `BINARY`, `NUMBER`, `DATETIME`,
|
|
254
|
+
`ROWID`) are unhashable.** Use chained equality against
|
|
255
|
+
`description[i][1]`, NOT set/dict membership:
|
|
256
|
+
|
|
257
|
+
```python
|
|
258
|
+
type_code = cur.description[i][1]
|
|
259
|
+
if type_code == STRING or type_code == NUMBER: # OK
|
|
260
|
+
...
|
|
261
|
+
if type_code in {STRING, NUMBER}: # raises TypeError
|
|
262
|
+
...
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
The sentinels wrap multiple wire type codes (`NUMBER` covers
|
|
266
|
+
INTEGER+FLOAT+BOOLEAN, `DATETIME` covers DATE+TIMESTAMP+ISO8601),
|
|
267
|
+
so no canonical hash can satisfy the Python hash-eq invariant.
|
|
268
|
+
Stdlib `sqlite3` doesn't export these sentinels at all, so the
|
|
269
|
+
chained-equality form is the cross-driver-portable idiom.
|
|
270
|
+
|
|
271
|
+
- **`Binary(value)` leaks bare `TypeError` on bad input.** `Binary` is
|
|
272
|
+
the stdlib `sqlite3.Binary = memoryview` alias, kept as a direct
|
|
273
|
+
alias so `isinstance(Binary(b), memoryview)` holds for cross-driver
|
|
274
|
+
porting code. Bad input (`Binary("not bytes")`, `Binary(123)`,
|
|
275
|
+
`Binary(None)`) raises bare `TypeError` from the underlying
|
|
276
|
+
`memoryview` constructor, *outside* the `dqlitedbapi.Error`
|
|
277
|
+
hierarchy. The sibling `Date`/`Time`/`Timestamp`/`*FromTicks`
|
|
278
|
+
constructors all wrap as `DataError`; `Binary` deliberately does
|
|
279
|
+
not. Callers who want PEP 249 §7 hierarchy purity for binary input
|
|
280
|
+
should wrap their own `try`/`except (TypeError, ValueError)` and
|
|
281
|
+
re-raise as `dqlitedbapi.DataError`.
|
|
282
|
+
|
|
283
|
+
- **`WITH ... INSERT/UPDATE/DELETE` (CTE-prefixed pure DML) reports
|
|
284
|
+
zero `rowcount` and no `lastrowid`.** The driver dispatches between
|
|
285
|
+
the row-returning and execute paths via a prefix-based heuristic;
|
|
286
|
+
CTE-prefixed pure DML is misclassified as row-returning and the
|
|
287
|
+
server's actual count / id is dropped. Stdlib `sqlite3` handles
|
|
288
|
+
this correctly because it dispatches at the SQLite engine level.
|
|
289
|
+
Workaround: rewrite as plain DML, or use `INSERT ... RETURNING id`
|
|
290
|
+
to get the id back through the row-returning path. (PEP 249
|
|
291
|
+
doesn't mandate `lastrowid` correctness for INSERT-via-CTE.)
|
|
292
|
+
|
|
293
|
+
- **`description[i][1]` is `None` on empty result sets.** PEP 249 §6.1.2
|
|
294
|
+
requires `type_code` to compare equal to one of the Type Objects
|
|
295
|
+
(`STRING` / `NUMBER` / `BINARY` / `DATETIME` / `ROWID`). On an empty
|
|
296
|
+
result (zero rows AND zero per-column type tags — the wire layer
|
|
297
|
+
derives types from the first row's header), the per-column affinity
|
|
298
|
+
is genuinely unrecoverable from the wire; the driver emits `None`
|
|
299
|
+
rather than synthesise a misleading default. Cross-driver
|
|
300
|
+
`type_code == STRING` tests silently fail to match. Callers needing
|
|
301
|
+
column types on empty result sets should issue
|
|
302
|
+
`PRAGMA table_info(<table>)` separately. The sibling NULL-first-row
|
|
303
|
+
case is handled by a row-scan rescue (see `Cursor.description`
|
|
304
|
+
docstring).
|
|
305
|
+
|
|
306
|
+
- **Result sets are fully materialised at `execute()` time.** Stdlib
|
|
307
|
+
`sqlite3` streams rows from the C engine via `Cursor.fetchone`,
|
|
308
|
+
yielding one row per call. dqlite drains every continuation frame
|
|
309
|
+
inside `Cursor.execute()` before returning, materialising the full
|
|
310
|
+
result into the cursor's in-memory `_rows`. The `fetchone` /
|
|
311
|
+
`fetchmany` / `fetchall` surface then iterates the in-memory
|
|
312
|
+
buffer; `Cursor.close()` is purely in-memory and does not send
|
|
313
|
+
`INTERRUPT` to the server. For large queries, set `max_total_rows`
|
|
314
|
+
(forwarded from `connect()`) to cap memory at the wire layer, or
|
|
315
|
+
shape queries with explicit `LIMIT` to control batch size. This
|
|
316
|
+
matches the dqlite wire protocol (continuation frames are
|
|
317
|
+
server-pushed) but diverges from go-dqlite's lazy `Rows.Next`.
|
|
318
|
+
|
|
319
|
+
- **`SQLITE_TOOBIG` maps to `DataError`, not `DatabaseError`.** CPython
|
|
320
|
+
`Modules/_sqlite/util.c::get_exception_class` maps `SQLITE_TOOBIG`
|
|
321
|
+
(code 18, "string or BLOB exceeds size limit") to the generic
|
|
322
|
+
`DatabaseError`. The dqlite dbapi maps it to the more specific
|
|
323
|
+
`DataError` since the error is unambiguously a value-size violation.
|
|
324
|
+
Cross-driver code catching `DatabaseError` keeps working
|
|
325
|
+
(`DataError ⊂ DatabaseError`); code that distinguishes on the
|
|
326
|
+
specific subclass catches more here than against stdlib.
|
|
327
|
+
|
|
328
|
+
## Cross-version semantic shift: NULL in BOOLEAN/DATETIME columns
|
|
329
|
+
|
|
330
|
+
Upstream dqlite commit `f30fc99` (`query: preserve SQLITE_NULL type
|
|
331
|
+
for NULL values`, 2026-01-25) changed the wire encoding of NULL cells
|
|
332
|
+
in columns declared `BOOLEAN`, `DATE`, `DATETIME`, or `TIMESTAMP`:
|
|
333
|
+
|
|
334
|
+
- **Before** `f30fc99`: a NULL cell was emitted with the column's
|
|
335
|
+
coerced type — `BOOLEAN(0)` (decodes to `False`) or `ISO8601("")`
|
|
336
|
+
(decodes to `""`), indistinguishable from a real `FALSE` / empty
|
|
337
|
+
string.
|
|
338
|
+
- **After** `f30fc99`: a NULL cell is emitted with `SQLITE_NULL` and
|
|
339
|
+
decodes to `None`.
|
|
340
|
+
|
|
341
|
+
Code that does `if cur.fetchone()[0] is None:` against an old-server
|
|
342
|
+
cluster will silently miss NULL rows. After a cluster upgrade past
|
|
343
|
+
`f30fc99`, the same code starts firing where it previously read
|
|
344
|
+
`False` / `""`. There is no driver-level handshake distinguishing the
|
|
345
|
+
two server versions — check your dqlite cluster version before relying
|
|
346
|
+
on `is None` for `BOOLEAN` / `DATETIME` columns.
|
|
347
|
+
|
|
348
|
+
## Layering
|
|
349
|
+
|
|
350
|
+
Three related packages play different roles:
|
|
351
|
+
|
|
352
|
+
- `dqliteclient.DqliteConnection` — the low-level async wire client.
|
|
353
|
+
Directly speaks the dqlite wire protocol.
|
|
354
|
+
- `dqlitedbapi.Connection` — a sync PEP 249 wrapper built on top, runs a
|
|
355
|
+
dedicated event-loop thread so sync code can use the async client
|
|
356
|
+
transparently.
|
|
357
|
+
- `dqlitedbapi.aio.AsyncConnection` — the PEP 249–shaped async
|
|
358
|
+
counterpart for code already running inside an event loop.
|
|
359
|
+
|
|
360
|
+
Use `dqlitedbapi` (sync or async) unless you specifically need
|
|
361
|
+
wire-level control.
|
|
362
|
+
|
|
363
|
+
## Development
|
|
364
|
+
|
|
365
|
+
See [DEVELOPMENT.md](DEVELOPMENT.md) for setup and contribution guidelines.
|
|
366
|
+
|
|
367
|
+
## License
|
|
368
|
+
|
|
369
|
+
MIT
|