dqlite-dbapi 0.2.2__tar.gz → 0.4.0__tar.gz

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