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.
- {splink-5.0.0.dev4 → splink-5.0.0.dev5}/.gitignore +6 -1
- {splink-5.0.0.dev4 → splink-5.0.0.dev5}/PKG-INFO +6 -5
- {splink-5.0.0.dev4 → splink-5.0.0.dev5}/pyproject.toml +8 -15
- {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/__init__.py +1 -1
- {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/backends/duckdb.py +2 -1
- {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/accuracy.py +2 -2
- {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/blocking_analysis.py +8 -21
- {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/blocking_rule_creator_utils.py +4 -3
- {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/blocking_rule_library.py +4 -3
- {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/cache_dict_with_logging.py +4 -4
- {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/charts.py +46 -78
- {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/cluster_studio.py +13 -18
- {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/column_expression.py +8 -6
- {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/comparison.py +4 -4
- {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/comparison_creator.py +8 -6
- {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/comparison_level.py +28 -24
- {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/comparison_level_composition.py +1 -1
- {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/comparison_level_library.py +25 -10
- {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/comparison_library.py +13 -33
- {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/completeness.py +4 -4
- {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/database_api.py +2 -4
- {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/databricks/enable_splink.py +2 -2
- {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/datasets/utils.py +1 -1
- {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/dialects.py +43 -12
- splink-5.0.0.dev5/splink/internals/duckdb/database_api.py +178 -0
- splink-5.0.0.dev5/splink/internals/duckdb/database_api_with_profiling.py +157 -0
- {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/duckdb/dataframe.py +3 -3
- splink-5.0.0.dev5/splink/internals/duckdb/parquet_materialisation.py +102 -0
- splink-5.0.0.dev5/splink/internals/duckdb/parquet_write_options.py +36 -0
- splink-5.0.0.dev5/splink/internals/duckdb/pruned_prediction.py +140 -0
- {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/em_training_session.py +4 -4
- splink-5.0.0.dev5/splink/internals/files/chart_defs/.gitignore +1 -0
- splink-5.0.0.dev5/splink/internals/files/chart_defs/profile_data_outer.json +5 -0
- 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
- 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
- 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
- splink-5.0.0.dev5/splink/internals/files/templates/chart_fragment.html +67 -0
- splink-5.0.0.dev5/splink/internals/files/templates/single_chart_template.html +15 -0
- splink-5.0.0.dev5/splink/internals/html_utils.py +86 -0
- {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/input_column.py +22 -11
- {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/labelling_tool.py +46 -10
- {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/linker_components/blocking_analysis.py +9 -15
- {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/linker_components/clustering.py +1 -1
- {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/linker_components/evaluation.py +1 -1
- {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/linker_components/inference.py +57 -36
- {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/linker_components/visualisations.py +13 -7
- {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/misc.py +35 -13
- {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/postgres/database_api.py +4 -1
- {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/predict.py +4 -4
- {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/profile_data.py +103 -70
- {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/settings.py +1 -1
- {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/spark/dataframe.py +2 -2
- {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/splink_comparison_viewer.py +80 -10
- {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/splink_dataframe.py +1 -1
- {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/sql_transform.py +1 -1
- {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/vertically_concatenate.py +1 -1
- splink-5.0.0.dev4/splink/internals/duckdb/database_api.py +0 -98
- splink-5.0.0.dev4/splink/internals/duckdb/database_api_with_profiling.py +0 -71
- splink-5.0.0.dev4/splink/internals/files/chart_defs/comparator_score_chart.json +0 -169
- splink-5.0.0.dev4/splink/internals/files/chart_defs/comparator_score_threshold_chart.json +0 -196
- splink-5.0.0.dev4/splink/internals/files/chart_defs/phonetic_match_chart.json +0 -94
- splink-5.0.0.dev4/splink/internals/files/templates/single_chart_template.html +0 -31
- splink-5.0.0.dev4/splink/internals/similarity_analysis.py +0 -318
- {splink-5.0.0.dev4 → splink-5.0.0.dev5}/LICENSE +0 -0
- {splink-5.0.0.dev4 → splink-5.0.0.dev5}/README.md +0 -0
- {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/backends/postgres.py +0 -0
- {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/backends/spark.py +0 -0
- {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/backends/sqlite.py +0 -0
- {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/blocking_analysis.py +0 -0
- {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/blocking_rule_library.py +0 -0
- {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/clustering.py +0 -0
- {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/comparison_level_library.py +0 -0
- {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/comparison_library.py +0 -0
- {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/datasets.py +0 -0
- {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/exploratory.py +0 -0
- {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/__init__.py +0 -0
- {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/block_from_labels.py +0 -0
- {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/blocking.py +0 -0
- {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/blocking_rule_creator.py +0 -0
- {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/chunking.py +0 -0
- {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/clustering.py +0 -0
- {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/comparison_level_creator.py +0 -0
- {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/comparison_level_sql.py +0 -0
- {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/comparison_vector_distribution.py +0 -0
- {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/comparison_vector_values.py +0 -0
- {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/connected_components.py +0 -0
- {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/constants.py +0 -0
- {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/databricks/__init__.py +0 -0
- {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/datasets/__init__.py +0 -0
- {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/datasets/metadata.py +0 -0
- {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/datasets/splink_datasets.py +0 -0
- {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/duckdb/__init__.py +0 -0
- {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/duckdb/duckdb_helpers/__init__.py +0 -0
- {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/duckdb/duckdb_helpers/duckdb_helpers.py +0 -0
- {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/edge_metrics.py +0 -0
- {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/em_sampling.py +0 -0
- {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/estimate_u.py +0 -0
- {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/exceptions.py +0 -0
- {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/expectation_maximisation.py +0 -0
- {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/files/DEPENDENCY_LICENSES.txt +0 -0
- {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/files/chart_defs/accuracy_chart.json +0 -0
- {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/files/chart_defs/blocking_rule_generated_comparisons.json +0 -0
- {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/files/chart_defs/completeness.json +0 -0
- {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/files/chart_defs/m_u_parameters_interactive_history.json +0 -0
- {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/files/chart_defs/match_weight_histogram.json +0 -0
- {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/files/chart_defs/match_weights_interactive_history.json +0 -0
- {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/files/chart_defs/match_weights_waterfall.json +0 -0
- {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/files/chart_defs/parameter_estimate_comparisons.json +0 -0
- {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/files/chart_defs/precision_recall.json +0 -0
- {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/files/chart_defs/probability_two_random_records_match_iteration.json +0 -0
- {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/files/chart_defs/profile_data.json +0 -0
- {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/files/chart_defs/roc.json +0 -0
- {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/files/chart_defs/tf_adjustment_chart.json +0 -0
- {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/files/chart_defs/threshold_selection_tool.json +0 -0
- {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/files/chart_defs/unlinkables_chart_def.json +0 -0
- {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/files/external_js/d3@7.8.5 +0 -0
- {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/files/external_js/stdlib.js@5.8.3 +0 -0
- {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/files/external_js/vega-embed@6.20.2 +0 -0
- {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/files/external_js/vega-lite@5.2.0 +0 -0
- {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/files/external_js/vega@5.31.0 +0 -0
- {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/files/labelling_tool/slt.js +0 -0
- {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
- {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
- {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
- {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/files/splink_cluster_studio/custom.css +0 -0
- {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/files/splink_comparison_viewer/custom.css +0 -0
- {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/files/splink_vis_utils/splink_vis_utils.js +0 -0
- {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/find_matches_to_new_records.py +0 -0
- {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/graph_metrics.py +0 -0
- {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/linker.py +0 -0
- {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/linker_components/__init__.py +0 -0
- {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/linker_components/misc.py +0 -0
- {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/linker_components/table_management.py +0 -0
- {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/linker_components/training.py +0 -0
- {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/logging_messages.py +0 -0
- {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/lower_id_on_lhs.py +0 -0
- {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/m_from_labels.py +0 -0
- {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/m_training.py +0 -0
- {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/m_u_records_to_parameters.py +0 -0
- {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/match_weights_histogram.py +0 -0
- {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/one_to_one_clustering.py +0 -0
- {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/parse_sql.py +0 -0
- {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/pipeline.py +0 -0
- {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/postgres/__init__.py +0 -0
- {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/postgres/dataframe.py +0 -0
- {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/realtime.py +0 -0
- {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/settings_creator.py +0 -0
- {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/settings_validation/__init__.py +0 -0
- {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/settings_validation/log_invalid_columns.py +0 -0
- {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/settings_validation/settings_column_cleaner.py +0 -0
- {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/settings_validation/settings_validation_log_strings.py +0 -0
- {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/settings_validation/valid_types.py +0 -0
- {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/spark/__init__.py +0 -0
- {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/spark/database_api.py +0 -0
- {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/spark/database_api_with_profiling.py +0 -0
- {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/spark/jar_location.py +0 -0
- {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/spark/spark_helpers/__init__.py +0 -0
- {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/spark/spark_helpers/custom_spark_dialect.py +0 -0
- {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/spark/spark_helpers/version.py +0 -0
- {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/splink_logging.py +0 -0
- {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/splinkdataframe_utils.py +0 -0
- {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/sqlite/__init__.py +0 -0
- {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/sqlite/database_api.py +0 -0
- {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/sqlite/dataframe.py +0 -0
- {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/term_frequencies.py +0 -0
- {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/testing.py +0 -0
- {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/unique_id_concat.py +0 -0
- {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/unlinkables.py +0 -0
- {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/internals/waterfall_chart.py +0 -0
- {splink-5.0.0.dev4 → splink-5.0.0.dev5}/splink/logging.py +0 -0
- {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.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: splink
|
|
3
|
-
Version: 5.0.0.
|
|
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.
|
|
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"
|
|
@@ -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:
|
|
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
|
-
|
|
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:
|
|
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:
|
|
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
|
-
|
|
18
|
-
|
|
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,
|
|
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,
|
|
30
|
-
if not isinstance(
|
|
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,
|
|
33
|
+
super().__setitem__(key, item)
|
|
34
34
|
|
|
35
35
|
logger.log(
|
|
36
|
-
1, f"Setting cache for {key} with 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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
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.
|
|
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":
|
|
388
|
-
"raw_node_data":
|
|
389
|
-
"raw_clusters_data":
|
|
390
|
-
"splink_settings":
|
|
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":
|
|
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"] =
|
|
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
|
-
|
|
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(
|
|
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,
|
|
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,
|
|
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:
|
|
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
|
|
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
|
|
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
|