splink 5.0.0.dev4__tar.gz → 5.0.0.dev5__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 (171) hide show
  1. {splink-5.0.0.dev4 → splink-5.0.0.dev5}/.gitignore +6 -1
  2. {splink-5.0.0.dev4 → splink-5.0.0.dev5}/PKG-INFO +6 -5
  3. {splink-5.0.0.dev4 → splink-5.0.0.dev5}/pyproject.toml +8 -15
  4. {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/__init__.py +1 -1
  5. {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/backends/duckdb.py +2 -1
  6. {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/accuracy.py +2 -2
  7. {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/blocking_analysis.py +8 -21
  8. {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/blocking_rule_creator_utils.py +4 -3
  9. {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/blocking_rule_library.py +4 -3
  10. {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/cache_dict_with_logging.py +4 -4
  11. {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/charts.py +46 -78
  12. {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/cluster_studio.py +13 -18
  13. {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/column_expression.py +8 -6
  14. {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/comparison.py +4 -4
  15. {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/comparison_creator.py +8 -6
  16. {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/comparison_level.py +28 -24
  17. {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/comparison_level_composition.py +1 -1
  18. {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/comparison_level_library.py +25 -10
  19. {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/comparison_library.py +13 -33
  20. {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/completeness.py +4 -4
  21. {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/database_api.py +2 -4
  22. {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/databricks/enable_splink.py +2 -2
  23. {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/datasets/utils.py +1 -1
  24. {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/dialects.py +43 -12
  25. splink-5.0.0.dev5/splink/internals/duckdb/database_api.py +178 -0
  26. splink-5.0.0.dev5/splink/internals/duckdb/database_api_with_profiling.py +157 -0
  27. {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/duckdb/dataframe.py +3 -3
  28. splink-5.0.0.dev5/splink/internals/duckdb/parquet_materialisation.py +102 -0
  29. splink-5.0.0.dev5/splink/internals/duckdb/parquet_write_options.py +36 -0
  30. splink-5.0.0.dev5/splink/internals/duckdb/pruned_prediction.py +140 -0
  31. {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/em_training_session.py +4 -4
  32. splink-5.0.0.dev5/splink/internals/files/chart_defs/.gitignore +1 -0
  33. splink-5.0.0.dev5/splink/internals/files/chart_defs/profile_data_outer.json +5 -0
  34. splink-5.0.0.dev4/splink/internals/files/labelling_tool/template.j2 → splink-5.0.0.dev5/splink/internals/files/labelling_tool/template.html +32 -40
  35. splink-5.0.0.dev4/splink/internals/files/splink_cluster_studio/cluster_template.j2 → splink-5.0.0.dev5/splink/internals/files/splink_cluster_studio/cluster_template.html +36 -40
  36. splink-5.0.0.dev4/splink/internals/files/splink_comparison_viewer/template.j2 → splink-5.0.0.dev5/splink/internals/files/splink_comparison_viewer/template.html +29 -28
  37. splink-5.0.0.dev5/splink/internals/files/templates/chart_fragment.html +67 -0
  38. splink-5.0.0.dev5/splink/internals/files/templates/single_chart_template.html +15 -0
  39. splink-5.0.0.dev5/splink/internals/html_utils.py +86 -0
  40. {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/input_column.py +22 -11
  41. {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/labelling_tool.py +46 -10
  42. {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/linker_components/blocking_analysis.py +9 -15
  43. {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/linker_components/clustering.py +1 -1
  44. {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/linker_components/evaluation.py +1 -1
  45. {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/linker_components/inference.py +57 -36
  46. {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/linker_components/visualisations.py +13 -7
  47. {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/misc.py +35 -13
  48. {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/postgres/database_api.py +4 -1
  49. {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/predict.py +4 -4
  50. {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/profile_data.py +103 -70
  51. {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/settings.py +1 -1
  52. {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/spark/dataframe.py +2 -2
  53. {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/splink_comparison_viewer.py +80 -10
  54. {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/splink_dataframe.py +1 -1
  55. {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/sql_transform.py +1 -1
  56. {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/vertically_concatenate.py +1 -1
  57. splink-5.0.0.dev4/splink/internals/duckdb/database_api.py +0 -98
  58. splink-5.0.0.dev4/splink/internals/duckdb/database_api_with_profiling.py +0 -71
  59. splink-5.0.0.dev4/splink/internals/files/chart_defs/comparator_score_chart.json +0 -169
  60. splink-5.0.0.dev4/splink/internals/files/chart_defs/comparator_score_threshold_chart.json +0 -196
  61. splink-5.0.0.dev4/splink/internals/files/chart_defs/phonetic_match_chart.json +0 -94
  62. splink-5.0.0.dev4/splink/internals/files/templates/single_chart_template.html +0 -31
  63. splink-5.0.0.dev4/splink/internals/similarity_analysis.py +0 -318
  64. {splink-5.0.0.dev4 → splink-5.0.0.dev5}/LICENSE +0 -0
  65. {splink-5.0.0.dev4 → splink-5.0.0.dev5}/README.md +0 -0
  66. {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/backends/postgres.py +0 -0
  67. {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/backends/spark.py +0 -0
  68. {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/backends/sqlite.py +0 -0
  69. {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/blocking_analysis.py +0 -0
  70. {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/blocking_rule_library.py +0 -0
  71. {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/clustering.py +0 -0
  72. {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/comparison_level_library.py +0 -0
  73. {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/comparison_library.py +0 -0
  74. {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/datasets.py +0 -0
  75. {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/exploratory.py +0 -0
  76. {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/__init__.py +0 -0
  77. {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/block_from_labels.py +0 -0
  78. {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/blocking.py +0 -0
  79. {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/blocking_rule_creator.py +0 -0
  80. {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/chunking.py +0 -0
  81. {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/clustering.py +0 -0
  82. {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/comparison_level_creator.py +0 -0
  83. {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/comparison_level_sql.py +0 -0
  84. {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/comparison_vector_distribution.py +0 -0
  85. {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/comparison_vector_values.py +0 -0
  86. {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/connected_components.py +0 -0
  87. {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/constants.py +0 -0
  88. {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/databricks/__init__.py +0 -0
  89. {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/datasets/__init__.py +0 -0
  90. {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/datasets/metadata.py +0 -0
  91. {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/datasets/splink_datasets.py +0 -0
  92. {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/duckdb/__init__.py +0 -0
  93. {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/duckdb/duckdb_helpers/__init__.py +0 -0
  94. {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/duckdb/duckdb_helpers/duckdb_helpers.py +0 -0
  95. {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/edge_metrics.py +0 -0
  96. {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/em_sampling.py +0 -0
  97. {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/estimate_u.py +0 -0
  98. {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/exceptions.py +0 -0
  99. {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/expectation_maximisation.py +0 -0
  100. {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/files/DEPENDENCY_LICENSES.txt +0 -0
  101. {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/files/chart_defs/accuracy_chart.json +0 -0
  102. {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/files/chart_defs/blocking_rule_generated_comparisons.json +0 -0
  103. {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/files/chart_defs/completeness.json +0 -0
  104. {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/files/chart_defs/m_u_parameters_interactive_history.json +0 -0
  105. {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/files/chart_defs/match_weight_histogram.json +0 -0
  106. {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/files/chart_defs/match_weights_interactive_history.json +0 -0
  107. {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/files/chart_defs/match_weights_waterfall.json +0 -0
  108. {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/files/chart_defs/parameter_estimate_comparisons.json +0 -0
  109. {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/files/chart_defs/precision_recall.json +0 -0
  110. {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/files/chart_defs/probability_two_random_records_match_iteration.json +0 -0
  111. {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/files/chart_defs/profile_data.json +0 -0
  112. {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/files/chart_defs/roc.json +0 -0
  113. {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/files/chart_defs/tf_adjustment_chart.json +0 -0
  114. {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/files/chart_defs/threshold_selection_tool.json +0 -0
  115. {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/files/chart_defs/unlinkables_chart_def.json +0 -0
  116. {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/files/external_js/d3@7.8.5 +0 -0
  117. {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/files/external_js/stdlib.js@5.8.3 +0 -0
  118. {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/files/external_js/vega-embed@6.20.2 +0 -0
  119. {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/files/external_js/vega-lite@5.2.0 +0 -0
  120. {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/files/external_js/vega@5.31.0 +0 -0
  121. {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/files/labelling_tool/slt.js +0 -0
  122. {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/files/spark_jars/scala-udf-similarity-0.1.2_spark3.x.jar +0 -0
  123. {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/files/spark_jars/scala-udf-similarity-0.2.0_spark4.x.jar +0 -0
  124. {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/files/spark_jars/scala-udf-similarity-0.2.1_spark4.x.jar +0 -0
  125. {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/files/splink_cluster_studio/custom.css +0 -0
  126. {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/files/splink_comparison_viewer/custom.css +0 -0
  127. {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/files/splink_vis_utils/splink_vis_utils.js +0 -0
  128. {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/find_matches_to_new_records.py +0 -0
  129. {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/graph_metrics.py +0 -0
  130. {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/linker.py +0 -0
  131. {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/linker_components/__init__.py +0 -0
  132. {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/linker_components/misc.py +0 -0
  133. {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/linker_components/table_management.py +0 -0
  134. {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/linker_components/training.py +0 -0
  135. {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/logging_messages.py +0 -0
  136. {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/lower_id_on_lhs.py +0 -0
  137. {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/m_from_labels.py +0 -0
  138. {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/m_training.py +0 -0
  139. {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/m_u_records_to_parameters.py +0 -0
  140. {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/match_weights_histogram.py +0 -0
  141. {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/one_to_one_clustering.py +0 -0
  142. {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/parse_sql.py +0 -0
  143. {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/pipeline.py +0 -0
  144. {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/postgres/__init__.py +0 -0
  145. {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/postgres/dataframe.py +0 -0
  146. {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/realtime.py +0 -0
  147. {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/settings_creator.py +0 -0
  148. {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/settings_validation/__init__.py +0 -0
  149. {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/settings_validation/log_invalid_columns.py +0 -0
  150. {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/settings_validation/settings_column_cleaner.py +0 -0
  151. {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/settings_validation/settings_validation_log_strings.py +0 -0
  152. {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/settings_validation/valid_types.py +0 -0
  153. {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/spark/__init__.py +0 -0
  154. {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/spark/database_api.py +0 -0
  155. {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/spark/database_api_with_profiling.py +0 -0
  156. {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/spark/jar_location.py +0 -0
  157. {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/spark/spark_helpers/__init__.py +0 -0
  158. {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/spark/spark_helpers/custom_spark_dialect.py +0 -0
  159. {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/spark/spark_helpers/version.py +0 -0
  160. {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/splink_logging.py +0 -0
  161. {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/splinkdataframe_utils.py +0 -0
  162. {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/sqlite/__init__.py +0 -0
  163. {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/sqlite/database_api.py +0 -0
  164. {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/sqlite/dataframe.py +0 -0
  165. {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/term_frequencies.py +0 -0
  166. {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/testing.py +0 -0
  167. {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/unique_id_concat.py +0 -0
  168. {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/unlinkables.py +0 -0
  169. {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/waterfall_chart.py +0 -0
  170. {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/logging.py +0 -0
  171. {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/py.typed +0 -0
@@ -169,7 +169,12 @@ cython_debug/
169
169
  # html files opt-in only
170
170
  *.html
171
171
  !splink/**/templates/**/*.html
172
- !docs/**/*.html
172
+ !docs/demos/examples/**/*.html
173
+ !docs/demos/tutorials/**/*.html
174
+ !docs/overrides/**/*.html
175
+
176
+ # don't store ipynb here
177
+ *.ipynb
173
178
 
174
179
  .DS_Store
175
180
 
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.5
2
2
  Name: splink
3
- Version: 5.0.0.dev4
3
+ Version: 5.0.0.dev5
4
4
  Summary: Fast probabilistic data linkage at scale
5
5
  Project-URL: Homepage, https://github.com/moj-analytical-services/splink
6
6
  Project-URL: Repository, https://github.com/moj-analytical-services/splink
@@ -9,11 +9,12 @@ Author-email: Robin Linacre <robinlinacre@hotmail.com>
9
9
  License-Expression: MIT
10
10
  License-File: LICENSE
11
11
  Requires-Python: <4.0.0,>=3.10.0
12
- Requires-Dist: altair>=5.0.1
13
12
  Requires-Dist: duckdb>=0.9.2
14
- Requires-Dist: igraph>=0.11.2
15
- Requires-Dist: jinja2>=3.0.3
16
13
  Requires-Dist: sqlglot>=17.6.0
14
+ Provides-Extra: altair
15
+ Requires-Dist: altair>=5.0.1; extra == 'altair'
16
+ Provides-Extra: igraph
17
+ Requires-Dist: igraph>=0.11.2; extra == 'igraph'
17
18
  Provides-Extra: postgres
18
19
  Requires-Dist: psycopg2-binary>=2.9.0; extra == 'postgres'
19
20
  Requires-Dist: sqlalchemy>=2.0.0; extra == 'postgres'
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "splink"
3
- version = "5.0.0.dev4"
3
+ version = "5.0.0.dev5"
4
4
  description = "Fast probabilistic data linkage at scale"
5
5
  authors = [
6
6
  { name = "Robin Linacre", email = "robinlinacre@hotmail.com" },
@@ -16,12 +16,11 @@ license = "MIT"
16
16
  dependencies = [
17
17
  "duckdb>=0.9.2",
18
18
  "sqlglot>=17.6.0",
19
- "altair>=5.0.1",
20
- "Jinja2>=3.0.3",
21
- "igraph>=0.11.2",
22
19
  ]
23
20
 
24
21
  [project.optional-dependencies]
22
+ altair = ["altair>=5.0.1"]
23
+ igraph = ["igraph>=0.11.2"]
25
24
  pyspark = ["pyspark>=3.5.0"]
26
25
  spark = ["pyspark>=3.5.0"]
27
26
  # sqlite doesn't _require_ this functionality, but it's used if we want to use fuzzy levels
@@ -38,14 +37,6 @@ Repository = "https://github.com/moj-analytical-services/splink"
38
37
  # allows us to easily check package functionality with just these installed
39
38
  # (and not the rest of our required dependencies)
40
39
  core = ["sqlglot", "duckdb"]
41
- demos = [
42
- "ipywidgets>=8.1.7",
43
- "jupytext>=1.18.1",
44
- "nbmake>=1.5.5",
45
- "pseudopeople>=1.2.6",
46
- "pytest-xdist>=3.6.1",
47
- { "include-group" = "pandas" },
48
- ]
49
40
  # dev deliberately doesn't include spark, so we can opt in to different spark versions
50
41
  dev = [
51
42
  { "include-group" = "linting" },
@@ -57,6 +48,7 @@ spark-3 = [
57
48
  ]
58
49
  spark-4 = ["pyspark>=4.0.0"]
59
50
  docs = [
51
+ "altair>=5.0.1",
60
52
  "griffe>=1.4.0",
61
53
  # keep jinja2 relaxed so it doesn't constrain us in rest of dev
62
54
  "Jinja2>=3.1.6",
@@ -84,6 +76,7 @@ sqlite = [
84
76
  "rapidfuzz>=3.10.0",
85
77
  ]
86
78
  testing-core = [
79
+ "igraph>=0.11.2",
87
80
  "pytest>=7.3",
88
81
  "pyarrow>=15.0.0",
89
82
  "networkx>=2.5.1",
@@ -181,9 +174,6 @@ markers = [
181
174
  [tool.mypy]
182
175
  python_version = "3.10"
183
176
  packages = "splink"
184
- # for now at least allow implicit optionals
185
- # to cut down on noise. Easy to fix.
186
- implicit_optional = true
187
177
  # for now, ignore missing imports
188
178
  # can remove later and install stubs, where existent
189
179
  ignore_missing_imports = true
@@ -205,3 +195,6 @@ disallow_any_generics = true
205
195
  # disallow_untyped_calls = true
206
196
  disallow_incomplete_defs = true
207
197
  # disallow_untyped_defs = true
198
+
199
+ [tool.ty.rules]
200
+ unresolved-import = "ignore"
@@ -56,7 +56,7 @@ def __getattr__(name):
56
56
  raise AttributeError(f"module 'splink' has no attribute '{name}'") from None
57
57
 
58
58
 
59
- __version__ = "5.0.0.dev4"
59
+ __version__ = "5.0.0.dev5"
60
60
 
61
61
 
62
62
  __all__ = [
@@ -2,5 +2,6 @@ from splink.internals.duckdb.database_api import DuckDBAPI
2
2
  from splink.internals.duckdb.database_api_with_profiling import (
3
3
  DuckDBAPIWithProfiling,
4
4
  )
5
+ from splink.internals.duckdb.parquet_write_options import ParquetWriteOptions
5
6
 
6
- __all__ = ["DuckDBAPI", "DuckDBAPIWithProfiling"]
7
+ __all__ = ["DuckDBAPI", "DuckDBAPIWithProfiling", "ParquetWriteOptions"]
@@ -26,7 +26,7 @@ if TYPE_CHECKING:
26
26
  def truth_space_table_from_labels_with_predictions_sqls(
27
27
  threshold_actual: float = 0.5,
28
28
  match_weight_round_to_nearest: float | None = None,
29
- total_labels: int = None,
29
+ total_labels: int | None = None,
30
30
  positives_not_captured_by_blocking_rules_scored_as_zero: bool = True,
31
31
  ) -> list[dict[str, str]]:
32
32
  """
@@ -336,7 +336,7 @@ def truth_space_table_from_labels_column(
336
336
  linker: "Linker",
337
337
  label_colname: str,
338
338
  threshold_actual: float = 0.5,
339
- match_weight_round_to_nearest: float = None,
339
+ match_weight_round_to_nearest: float | None = None,
340
340
  positives_not_captured_by_blocking_rules_scored_as_zero: bool = True,
341
341
  ) -> SplinkDataFrame:
342
342
  # First we need to calculate the number of implicit true negatives
@@ -40,6 +40,7 @@ from splink.internals.em_sampling import (
40
40
  from splink.internals.input_column import InputColumn
41
41
  from splink.internals.misc import (
42
42
  calculate_cartesian,
43
+ ensure_is_list,
43
44
  )
44
45
  from splink.internals.pipeline import CTEPipeline
45
46
  from splink.internals.splink_dataframe import SplinkDataFrame
@@ -594,16 +595,13 @@ def _cumulative_comparisons_to_be_scored_from_blocking_rules(
594
595
  return counts_data
595
596
 
596
597
 
598
+ BlockingRuleLike = BlockingRuleCreator | BlockingRule | str | dict[str, Any]
599
+
600
+
597
601
  def count_comparisons_from_blocking_rules(
598
602
  splink_dataframe_or_dataframes: SplinkDataFrame | Sequence[SplinkDataFrame],
599
603
  *,
600
- blocking_rules: Union[
601
- BlockingRuleCreator,
602
- BlockingRule,
603
- str,
604
- Dict[str, Any],
605
- Iterable[Union[BlockingRuleCreator, BlockingRule, str, Dict[str, Any]]],
606
- ],
604
+ blocking_rules: BlockingRuleLike | Iterable[BlockingRuleLike],
607
605
  link_type: user_input_link_type_options,
608
606
  unique_id_column_name: str = "unique_id",
609
607
  source_dataset_column_name: Optional[str] = None,
@@ -651,14 +649,9 @@ def count_comparisons_from_blocking_rules(
651
649
  # Allow either a single blocking rule or an iterable of them. A single rule
652
650
  # may be a dict, which is itself iterable, so we must detect the single-rule
653
651
  # types explicitly rather than relying on iterability.
654
- if isinstance(blocking_rules, (str, dict, BlockingRuleCreator, BlockingRule)):
655
- blocking_rules_iterable: Iterable[
656
- Union[BlockingRuleCreator, BlockingRule, str, Dict[str, Any]]
657
- ] = [blocking_rules]
658
- else:
659
- blocking_rules_iterable = list(blocking_rules)
652
+ blocking_rules_iterable: Iterable[BlockingRuleLike] = ensure_is_list(blocking_rules)
660
653
 
661
- blocking_rules_as_br: List[BlockingRule] = []
654
+ blocking_rules_as_br: list[BlockingRule] = []
662
655
  for br_input in blocking_rules_iterable:
663
656
  blocking_rules_as_br.append(
664
657
  _as_blocking_rule(br_input, db_api.sql_dialect.sql_dialect_str)
@@ -686,13 +679,7 @@ def count_comparisons_from_blocking_rules(
686
679
  def chart_comparisons_from_blocking_rules(
687
680
  splink_dataframe_or_dataframes: SplinkDataFrame | Sequence[SplinkDataFrame],
688
681
  *,
689
- blocking_rules: Union[
690
- BlockingRuleCreator,
691
- BlockingRule,
692
- str,
693
- Dict[str, Any],
694
- Iterable[Union[BlockingRuleCreator, BlockingRule, str, Dict[str, Any]]],
695
- ],
682
+ blocking_rules: BlockingRuleLike | Iterable[BlockingRuleLike],
696
683
  link_type: user_input_link_type_options,
697
684
  unique_id_column_name: str = "unique_id",
698
685
  source_dataset_column_name: Optional[str] = None,
@@ -12,11 +12,12 @@ from .blocking_rule_library import CustomRule
12
12
  def to_blocking_rule_creator(
13
13
  blocking_rule_creator: Union[dict[str, Any], str, BlockingRuleCreator],
14
14
  ) -> BlockingRuleCreator:
15
+ if isinstance(blocking_rule_creator, BlockingRuleCreator):
16
+ return blocking_rule_creator
15
17
  if isinstance(blocking_rule_creator, dict):
16
18
  return CustomRule(**blocking_rule_creator)
17
- if isinstance(blocking_rule_creator, str):
18
- return CustomRule(blocking_rule_creator)
19
- return blocking_rule_creator
19
+ # assume it is a string
20
+ return CustomRule(blocking_rule_creator)
20
21
 
21
22
 
22
23
  def blocking_rule_args_to_list_of_blocking_rules(
@@ -12,7 +12,7 @@ from splink.internals.dialects import SplinkDialect
12
12
  def _translate_sql_string(
13
13
  sqlglot_base_dialect_sql: str,
14
14
  to_sqlglot_dialect: str,
15
- from_sqlglot_dialect: str = None,
15
+ from_sqlglot_dialect: str | None = None,
16
16
  ) -> str:
17
17
  tree = parse_one(sqlglot_base_dialect_sql, read=from_sqlglot_dialect)
18
18
 
@@ -38,7 +38,7 @@ class CustomRule(BlockingRuleCreator):
38
38
  def __init__(
39
39
  self,
40
40
  blocking_rule: str,
41
- sql_dialect: str = None,
41
+ sql_dialect: str | None = None,
42
42
  arrays_to_explode: list[str] | None = None,
43
43
  ):
44
44
  """
@@ -115,7 +115,8 @@ class _Merge(BlockingRuleCreator):
115
115
  f"Must provide at least one blocking rule to {type(self)}()"
116
116
  )
117
117
  blocking_rule_creators = [
118
- CustomRule(**br) if isinstance(br, dict) else br for br in blocking_rules
118
+ CustomRule(**br) if not isinstance(br, BlockingRuleCreator) else br
119
+ for br in blocking_rules
119
120
  ]
120
121
  self.blocking_rules = blocking_rule_creators
121
122
 
@@ -26,14 +26,14 @@ class CacheDictWithLogging(TypedUserDict):
26
26
  # without modifying the version in the cache
27
27
  return copy(splink_dataframe)
28
28
 
29
- def __setitem__(self, key, value):
30
- if not isinstance(value, SplinkDataFrame):
29
+ def __setitem__(self, key, item):
30
+ if not isinstance(item, SplinkDataFrame):
31
31
  raise TypeError("Cached items must be of type SplinkDataFrame")
32
32
 
33
- super().__setitem__(key, value)
33
+ super().__setitem__(key, item)
34
34
 
35
35
  logger.log(
36
- 1, f"Setting cache for {key} with physical name {value.physical_name}"
36
+ 1, f"Setting cache for {key} with physical name {item.physical_name}"
37
37
  )
38
38
 
39
39
  def invalidate_cache(self):
@@ -13,28 +13,22 @@ from typing import (
13
13
  Protocol,
14
14
  Sequence,
15
15
  TypeVar,
16
- Union,
17
16
  cast,
18
17
  )
19
18
 
19
+ from splink.internals.html_utils import chart_html, load_chart_libraries
20
20
  from splink.internals.misc import read_resource
21
21
 
22
22
  if TYPE_CHECKING:
23
- from altair import SchemaBase
24
-
25
23
  from splink.internals.comparison_level import ComparisonLevelDetailedRecord
26
24
  from splink.internals.em_training_session import (
27
25
  ModelParameterIterationDetailedRecord,
28
26
  )
29
27
  from splink.internals.settings import ModelParameterDetailedRecord
30
28
  else:
31
- SchemaBase = None
32
-
33
29
  ComparisonLevelDetailedRecord = None
34
30
  ModelParameterDetailedRecord = None
35
31
  ModelParameterIterationDetailedRecord = None
36
- # type alias:
37
- ChartReturnType = Union[dict[Any, Any], SchemaBase]
38
32
 
39
33
 
40
34
  def load_chart_definition(filename):
@@ -43,23 +37,7 @@ def load_chart_definition(filename):
43
37
 
44
38
 
45
39
  def _load_external_libs():
46
- to_load = {
47
- "vega-embed": "internals/files/external_js/vega-embed@6.20.2",
48
- "vega-lite": "internals/files/external_js/vega-lite@5.2.0",
49
- "vega": "internals/files/external_js/vega@5.31.0",
50
- }
51
- return {k: read_resource(v) for k, v in to_load.items()}
52
-
53
-
54
- def altair_or_json(
55
- chart_dict: dict[Any, Any], as_dict: bool = False
56
- ) -> ChartReturnType:
57
- from altair import Chart
58
-
59
- if not as_dict:
60
- return Chart.from_dict(chart_dict)
61
-
62
- return chart_dict
40
+ return load_chart_libraries()
63
41
 
64
42
 
65
43
  class AsDictable(Protocol):
@@ -121,7 +99,16 @@ class SplinkChart(ABC, Generic[T]):
121
99
 
122
100
  @property
123
101
  def altair_chart(self):
124
- from altair import Chart
102
+ try:
103
+ from altair import Chart
104
+ except ModuleNotFoundError as exc:
105
+ if exc.name != "altair":
106
+ raise
107
+ raise ModuleNotFoundError(
108
+ "`altair_chart` requires the optional Altair dependency. "
109
+ "Notebook display and basic HTML export do not require Altair.",
110
+ name="altair",
111
+ ) from exc
125
112
 
126
113
  return Chart.from_dict(self.chart_dict)
127
114
 
@@ -152,8 +139,27 @@ class SplinkChart(ABC, Generic[T]):
152
139
  self.height = height
153
140
  # TODO: return self?
154
141
 
142
+ def to_html(self, *, inline: bool = False) -> str:
143
+ """Return a complete HTML document, with CDN or bundled (inline) libraries."""
144
+ return chart_html(self.chart_dict, inline=inline, fullhtml=True)
145
+
155
146
  def save(self, *args, **kwargs):
156
- self.altair_chart.save(*args, **kwargs)
147
+ """Save basic HTML natively; delegate advanced requests to optional Altair."""
148
+ target = args[0] if args else kwargs.get("fp")
149
+ basic = len(args) <= 1 and not (args and "fp" in kwargs)
150
+ basic = basic and not (kwargs.keys() - {"fp", "format", "inline"})
151
+ fmt = kwargs.get("format")
152
+ if fmt is None and isinstance(target, (str, os.PathLike)):
153
+ fmt = os.path.splitext(os.fspath(target))[1][1:].lower()
154
+ if basic and fmt in ("html", "htm") and target is not None:
155
+ content = self.to_html(inline=kwargs.get("inline", False))
156
+ if hasattr(target, "write"):
157
+ target.write(content)
158
+ else:
159
+ with open(target, "w", encoding="utf-8") as stream:
160
+ stream.write(content)
161
+ return
162
+ return self.altair_chart.save(*args, **kwargs)
157
163
 
158
164
  def save_offline_chart(
159
165
  self,
@@ -185,23 +191,26 @@ class SplinkChart(ABC, Generic[T]):
185
191
  f"or set overwrite=True to overwrite."
186
192
  )
187
193
 
188
- template = read_resource("internals/files/templates/single_chart_template.html")
189
-
190
- fmt_dict = _load_external_libs()
191
-
192
- fmt_dict["mychart"] = json.dumps(self.chart_dict)
193
-
194
194
  with open(filename, "w", encoding="utf-8") as f:
195
- f.write(template.format(**fmt_dict))
195
+ f.write(self.to_html(inline=True))
196
196
 
197
197
  if print_msg:
198
198
  print(f"Chart saved to {filename}") # noqa: T201
199
199
  print(iframe_message.format(filename=filename)) # noqa: T201
200
200
 
201
- # allows rich representation of altair chart in IPython environments: https://ipython.readthedocs.io/en/stable/config/integrating.html
202
- def _repr_mimebundle_(self, *args, **kwargs):
203
- # let altair handle the display for us
204
- return self.altair_chart._repr_mimebundle_(*args, **kwargs)
201
+ def _repr_mimebundle_(self, include=None, exclude=None):
202
+ requested = {"text/html", "text/plain"}
203
+ if include is not None:
204
+ requested.intersection_update(include)
205
+ requested.difference_update(exclude or ())
206
+ result = {}
207
+ if "text/plain" in requested:
208
+ result["text/plain"] = (
209
+ "SplinkChart (use .to_html() or .save() to export HTML)"
210
+ )
211
+ if "text/html" in requested:
212
+ result["text/html"] = chart_html(self.chart_dict)
213
+ return result
205
214
 
206
215
 
207
216
  class MatchWeightsChart(SplinkChart[ComparisonLevelDetailedRecord]):
@@ -702,44 +711,3 @@ class TFAdjustmentChart(SplinkChart[ChartRecord]):
702
711
  chart_spec["config"]["params"][0]["bind"]["options"] = self.tf_levels
703
712
  chart_spec["config"]["params"][0]["bind"]["labels"] = self.labels
704
713
  return chart_spec
705
-
706
-
707
- def _comparator_score_chart(similarity_records, distance_records, as_dict=False):
708
- chart_path = "comparator_score_chart.json"
709
- chart = load_chart_definition(chart_path)
710
-
711
- chart["datasets"]["data-similarity"] = similarity_records
712
- chart["datasets"]["data-distance"] = distance_records
713
-
714
- return altair_or_json(chart, as_dict=as_dict)
715
-
716
-
717
- def _comparator_score_threshold_chart(
718
- similarity_records,
719
- distance_records,
720
- similarity_threshold,
721
- distance_threshold,
722
- as_dict=False,
723
- ):
724
- chart_path = "comparator_score_threshold_chart.json"
725
- chart = load_chart_definition(chart_path)
726
-
727
- chart["params"][0]["value"] = similarity_threshold
728
- chart["params"][1]["value"] = distance_threshold
729
-
730
- chart["hconcat"][0]["layer"][0]["title"]["subtitle"] = f">= {similarity_threshold}"
731
- chart["hconcat"][1]["layer"][0]["title"]["subtitle"] = f"<= {distance_threshold}"
732
-
733
- chart["datasets"]["data-similarity"] = similarity_records
734
- chart["datasets"]["data-distance"] = distance_records
735
-
736
- return altair_or_json(chart, as_dict=as_dict)
737
-
738
-
739
- def _phonetic_match_chart(records, as_dict=False):
740
- chart_path = "phonetic_match_chart.json"
741
- chart = load_chart_definition(chart_path)
742
-
743
- chart["datasets"]["data-phonetic"] = records
744
-
745
- return altair_or_json(chart, as_dict=as_dict)
@@ -1,11 +1,11 @@
1
1
  from __future__ import annotations
2
2
 
3
- import json
4
3
  import os
5
4
  import random
6
5
  from typing import TYPE_CHECKING, Any, Literal, Optional
7
6
 
8
7
  from splink.internals.exceptions import SplinkException
8
+ from splink.internals.html_utils import json_for_html, render_html_template
9
9
  from splink.internals.misc import EverythingEncoder, read_resource
10
10
  from splink.internals.pipeline import CTEPipeline
11
11
  from splink.internals.splink_dataframe import SplinkDataFrame
@@ -351,14 +351,11 @@ def render_splink_cluster_studio_html(
351
351
  sampling_method: SamplingMethods = "random",
352
352
  sample_size: int = 10,
353
353
  sample_seed: int | None = None,
354
- cluster_ids: list[str] = None,
355
- cluster_names: list[str] = None,
354
+ cluster_ids: list[str] | None = None,
355
+ cluster_names: list[str] | None = None,
356
356
  overwrite: bool = False,
357
- _df_cluster_metrics: SplinkDataFrame = None,
357
+ _df_cluster_metrics: SplinkDataFrame | None = None,
358
358
  ) -> str:
359
- from jinja2 import Template
360
-
361
- bundle_observable_notebook = True
362
359
 
363
360
  svu_options = {
364
361
  "cluster_colname": "cluster_id",
@@ -380,23 +377,23 @@ def render_splink_cluster_studio_html(
380
377
  edges_recs = df_edges_as_records(linker, df_predicted_edges, df_nodes)
381
378
 
382
379
  # Render template with cluster, nodes and edges
383
- template_path = "internals/files/splink_cluster_studio/cluster_template.j2"
384
- template = Template(read_resource(template_path))
380
+ template_path = "internals/files/splink_cluster_studio/cluster_template.html"
385
381
 
386
382
  template_data: dict[str, Any] = {
387
- "raw_edge_data": json.dumps(edges_recs, cls=EverythingEncoder),
388
- "raw_node_data": json.dumps(nodes_recs, cls=EverythingEncoder),
389
- "raw_clusters_data": json.dumps(cluster_recs, cls=EverythingEncoder),
390
- "splink_settings": json.dumps(
383
+ "raw_edge_data": json_for_html(edges_recs, cls=EverythingEncoder),
384
+ "raw_node_data": json_for_html(nodes_recs, cls=EverythingEncoder),
385
+ "raw_clusters_data": json_for_html(cluster_recs, cls=EverythingEncoder),
386
+ "splink_settings": json_for_html(
391
387
  linker._settings_obj._as_completed_dict(), cls=EverythingEncoder
392
388
  ),
393
- "svu_options": json.dumps(svu_options, cls=EverythingEncoder),
389
+ "svu_options": json_for_html(svu_options, cls=EverythingEncoder),
394
390
  }
395
391
 
392
+ template_data["named_clusters"] = "null"
396
393
  if cluster_names:
397
394
  named_clusters_dict = dict(zip(cluster_ids, cluster_names))
398
395
 
399
- template_data["named_clusters"] = json.dumps(
396
+ template_data["named_clusters"] = json_for_html(
400
397
  named_clusters_dict, cls=EverythingEncoder
401
398
  )
402
399
 
@@ -411,9 +408,7 @@ def render_splink_cluster_studio_html(
411
408
  for k, v in files.items():
412
409
  template_data[k] = read_resource(v)
413
410
 
414
- template_data["bundle_observable_notebook"] = bundle_observable_notebook
415
-
416
- rendered = template.render(**template_data)
411
+ rendered = render_html_template(template_path, template_data)
417
412
 
418
413
  if os.path.isfile(out_path) and not overwrite:
419
414
  raise ValueError(
@@ -17,7 +17,7 @@ from splink.internals.sql_transform import (
17
17
 
18
18
 
19
19
  class ColumnExpressionOperation(Protocol):
20
- def __call__(self, name: str, sql_dialect: SplinkDialect) -> str: ...
20
+ def __call__(self, name: str, *, sql_dialect: SplinkDialect) -> str: ...
21
21
 
22
22
 
23
23
  class ColumnExpression:
@@ -44,7 +44,7 @@ class ColumnExpression:
44
44
  level creator into a `Linker`.
45
45
  """
46
46
 
47
- def __init__(self, sql_expression: str, sql_dialect: SplinkDialect = None):
47
+ def __init__(self, sql_expression: str, sql_dialect: SplinkDialect | None = None):
48
48
  self.raw_sql_expression = sql_expression
49
49
  self.operations: list[ColumnExpressionOperation] = []
50
50
  if sql_dialect is not None:
@@ -233,11 +233,11 @@ class ColumnExpression:
233
233
  self,
234
234
  name: str,
235
235
  sql_dialect: SplinkDialect,
236
- date_format: str = None,
236
+ date_format: str | None = None,
237
237
  ) -> str:
238
238
  return sql_dialect.try_parse_date(name, date_format=date_format)
239
239
 
240
- def try_parse_date(self, date_format: str = None) -> "ColumnExpression":
240
+ def try_parse_date(self, date_format: str | None = None) -> "ColumnExpression":
241
241
  """Applies a 'try parse date' transform to the input expression.
242
242
 
243
243
  Args:
@@ -257,11 +257,13 @@ class ColumnExpression:
257
257
  self,
258
258
  name: str,
259
259
  sql_dialect: SplinkDialect,
260
- timestamp_format: str = None,
260
+ timestamp_format: str | None = None,
261
261
  ) -> str:
262
262
  return sql_dialect.try_parse_timestamp(name, timestamp_format=timestamp_format)
263
263
 
264
- def try_parse_timestamp(self, timestamp_format: str = None) -> "ColumnExpression":
264
+ def try_parse_timestamp(
265
+ self, timestamp_format: str | None = None
266
+ ) -> "ColumnExpression":
265
267
  """Applies a 'try parse timestamp' transform to the input expression.
266
268
 
267
269
  Args:
@@ -71,14 +71,14 @@ class Comparison:
71
71
  self,
72
72
  comparison_levels: List[ComparisonLevel | dict[str, Any]],
73
73
  sqlglot_dialect: str,
74
- output_column_name: str = None,
75
- comparison_description: str = None,
76
- column_info_settings: ColumnInfoSettings = None,
74
+ output_column_name: str | None = None,
75
+ comparison_description: str | None = None,
76
+ column_info_settings: ColumnInfoSettings | None = None,
77
77
  ):
78
78
  sql_dialect = SplinkDialect.from_string(sqlglot_dialect)
79
79
  comparison_levels_as_objs: list[ComparisonLevel] = [
80
80
  ComparisonLevel(**cl, sql_dialect=sql_dialect)
81
- if isinstance(cl, dict)
81
+ if not isinstance(cl, ComparisonLevel)
82
82
  else cl
83
83
  for cl in comparison_levels
84
84
  ]
@@ -1,7 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from abc import ABC, abstractmethod
4
- from typing import Any, Dict, List, Optional, Union, final
4
+ from typing import Any, List, Optional, cast, final
5
5
 
6
6
  from splink.internals.column_expression import ColumnExpression
7
7
  from splink.internals.exceptions import SplinkException
@@ -19,9 +19,7 @@ class ComparisonCreator(ABC):
19
19
 
20
20
  def __init__(
21
21
  self,
22
- col_name_or_names: Union[
23
- Dict[str, Union[str, ColumnExpression]], Union[str, ColumnExpression]
24
- ],
22
+ col_name_or_names: dict[str, str | ColumnExpression] | str | ColumnExpression,
25
23
  ):
26
24
  """
27
25
  Class to author Comparisons
@@ -30,7 +28,7 @@ class ComparisonCreator(ABC):
30
28
  Can be a single item or a dict.
31
29
  """
32
30
  # if it's not a dict, assume it is a single expression-like
33
- if not isinstance(col_name_or_names, dict):
31
+ if isinstance(col_name_or_names, (str, ColumnExpression)):
34
32
  cols = {self.DEFAULT_COL_EXP_KEY: col_name_or_names}
35
33
  else:
36
34
  cols = col_name_or_names
@@ -80,9 +78,13 @@ class ComparisonCreator(ABC):
80
78
 
81
79
  if self.term_frequency_adjustments:
82
80
  for cl in comparison_levels:
81
+ # we can fix the cast with TypeIs, but requires typing_extensions
82
+ # before python 3.13
83
83
  if (
84
84
  hasattr(cl, "col_expression")
85
- and cl.col_expression.is_pure_column_or_column_reference
85
+ and cast(
86
+ ColumnExpression, cl.col_expression
87
+ ).is_pure_column_or_column_reference
86
88
  and cl.is_exact_match_level
87
89
  ):
88
90
  cl.term_frequency_adjustments = True