exdrf-qt 0.1.17__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.
- exdrf_qt-0.1.17/PKG-INFO +74 -0
- exdrf_qt-0.1.17/README.md +26 -0
- exdrf_qt-0.1.17/exdrf_qt/__init__.py +0 -0
- exdrf_qt-0.1.17/exdrf_qt/__version__.py +24 -0
- exdrf_qt-0.1.17/exdrf_qt/assets/__init__.py +0 -0
- exdrf_qt-0.1.17/exdrf_qt/comparator/__init__.py +0 -0
- exdrf_qt-0.1.17/exdrf_qt/comparator/logic/__init__.py +0 -0
- exdrf_qt-0.1.17/exdrf_qt/comparator/logic/adapter.py +81 -0
- exdrf_qt-0.1.17/exdrf_qt/comparator/logic/manager.py +322 -0
- exdrf_qt-0.1.17/exdrf_qt/comparator/logic/merge.py +246 -0
- exdrf_qt-0.1.17/exdrf_qt/comparator/logic/nodes.py +250 -0
- exdrf_qt-0.1.17/exdrf_qt/comparator/models/__init__.py +0 -0
- exdrf_qt-0.1.17/exdrf_qt/comparator/models/tree.py +390 -0
- exdrf_qt-0.1.17/exdrf_qt/comparator/widgets/__init__.py +0 -0
- exdrf_qt-0.1.17/exdrf_qt/comparator/widgets/cmp_result_preview_pane.py +97 -0
- exdrf_qt-0.1.17/exdrf_qt/comparator/widgets/field_aware_record_adapter.py +70 -0
- exdrf_qt-0.1.17/exdrf_qt/comparator/widgets/merge_delegate.py +197 -0
- exdrf_qt-0.1.17/exdrf_qt/comparator/widgets/record_cmp_base.py +32 -0
- exdrf_qt-0.1.17/exdrf_qt/comparator/widgets/record_comparator_base.py +210 -0
- exdrf_qt-0.1.17/exdrf_qt/comparator/widgets/record_to_node_adapter.py +94 -0
- exdrf_qt-0.1.17/exdrf_qt/comparator/widgets/tree.py +525 -0
- exdrf_qt-0.1.17/exdrf_qt/comparator/widgets/webview.py +282 -0
- exdrf_qt-0.1.17/exdrf_qt/context.py +567 -0
- exdrf_qt-0.1.17/exdrf_qt/context_use.py +109 -0
- exdrf_qt-0.1.17/exdrf_qt/controls/__init__.py +9 -0
- exdrf_qt-0.1.17/exdrf_qt/controls/base_editor.py +901 -0
- exdrf_qt-0.1.17/exdrf_qt/controls/checkable_combo.py +134 -0
- exdrf_qt-0.1.17/exdrf_qt/controls/checks/__init__.py +0 -0
- exdrf_qt-0.1.17/exdrf_qt/controls/checks/available_delegate.py +203 -0
- exdrf_qt-0.1.17/exdrf_qt/controls/checks/available_model.py +218 -0
- exdrf_qt-0.1.17/exdrf_qt/controls/checks/check_manager.py +1807 -0
- exdrf_qt-0.1.17/exdrf_qt/controls/checks/check_manager_ui.py +281 -0
- exdrf_qt-0.1.17/exdrf_qt/controls/checks/checks_model_base.py +396 -0
- exdrf_qt-0.1.17/exdrf_qt/controls/checks/mp_executor.py +303 -0
- exdrf_qt-0.1.17/exdrf_qt/controls/checks/results_model.py +468 -0
- exdrf_qt-0.1.17/exdrf_qt/controls/checks/selected_delegate.py +132 -0
- exdrf_qt-0.1.17/exdrf_qt/controls/checks/selected_model.py +332 -0
- exdrf_qt-0.1.17/exdrf_qt/controls/column_sel/__init__.py +0 -0
- exdrf_qt-0.1.17/exdrf_qt/controls/column_sel/column_sel.py +115 -0
- exdrf_qt-0.1.17/exdrf_qt/controls/column_sel/column_sel_ui.py +113 -0
- exdrf_qt-0.1.17/exdrf_qt/controls/command_palette/__init__.py +2 -0
- exdrf_qt-0.1.17/exdrf_qt/controls/command_palette/constants.py +39 -0
- exdrf_qt-0.1.17/exdrf_qt/controls/command_palette/delegate.py +217 -0
- exdrf_qt-0.1.17/exdrf_qt/controls/command_palette/engine.py +190 -0
- exdrf_qt-0.1.17/exdrf_qt/controls/command_palette/line_edit.py +377 -0
- exdrf_qt-0.1.17/exdrf_qt/controls/command_palette/model.py +137 -0
- exdrf_qt-0.1.17/exdrf_qt/controls/constraints/__init__.py +0 -0
- exdrf_qt-0.1.17/exdrf_qt/controls/constraints/base.py +106 -0
- exdrf_qt-0.1.17/exdrf_qt/controls/constraints/concept_base.py +26 -0
- exdrf_qt-0.1.17/exdrf_qt/controls/crud_actions.py +405 -0
- exdrf_qt-0.1.17/exdrf_qt/controls/filter_dlg/__init__.py +1 -0
- exdrf_qt-0.1.17/exdrf_qt/controls/filter_dlg/filter_dlg.py +105 -0
- exdrf_qt-0.1.17/exdrf_qt/controls/filter_dlg/filter_dlg_ui.py +108 -0
- exdrf_qt-0.1.17/exdrf_qt/controls/filter_dlg/filter_editor.py +884 -0
- exdrf_qt-0.1.17/exdrf_qt/controls/filter_header.py +289 -0
- exdrf_qt-0.1.17/exdrf_qt/controls/json_editor/__init__.py +0 -0
- exdrf_qt-0.1.17/exdrf_qt/controls/json_editor/delegate.py +134 -0
- exdrf_qt-0.1.17/exdrf_qt/controls/json_editor/dialog.py +91 -0
- exdrf_qt-0.1.17/exdrf_qt/controls/json_editor/editor.py +101 -0
- exdrf_qt-0.1.17/exdrf_qt/controls/json_editor/item.py +129 -0
- exdrf_qt-0.1.17/exdrf_qt/controls/json_editor/model.py +322 -0
- exdrf_qt-0.1.17/exdrf_qt/controls/json_editor/tree.py +195 -0
- exdrf_qt-0.1.17/exdrf_qt/controls/new_search_line.py +146 -0
- exdrf_qt-0.1.17/exdrf_qt/controls/param_controls.py +503 -0
- exdrf_qt-0.1.17/exdrf_qt/controls/pdf_viewer/__init__.py +3 -0
- exdrf_qt-0.1.17/exdrf_qt/controls/pdf_viewer/image_graphics_view.py +295 -0
- exdrf_qt-0.1.17/exdrf_qt/controls/pdf_viewer/pdf_image_splitter.py +649 -0
- exdrf_qt-0.1.17/exdrf_qt/controls/pdf_viewer/pdf_image_viewer.py +1543 -0
- exdrf_qt-0.1.17/exdrf_qt/controls/pdf_viewer/pdf_render_worker.py +108 -0
- exdrf_qt-0.1.17/exdrf_qt/controls/pdf_viewer/pdf_viewer.py +71 -0
- exdrf_qt-0.1.17/exdrf_qt/controls/pdf_viewer/rotation_editor_dialog.py +105 -0
- exdrf_qt-0.1.17/exdrf_qt/controls/pdf_viewer/split_entry.py +14 -0
- exdrf_qt-0.1.17/exdrf_qt/controls/pdf_viewer/split_plan_panel.py +1176 -0
- exdrf_qt-0.1.17/exdrf_qt/controls/pdf_viewer/split_preview_window.py +205 -0
- exdrf_qt-0.1.17/exdrf_qt/controls/popup_list.py +272 -0
- exdrf_qt-0.1.17/exdrf_qt/controls/record_cmp_base.py +15 -0
- exdrf_qt-0.1.17/exdrf_qt/controls/search_line.py +237 -0
- exdrf_qt-0.1.17/exdrf_qt/controls/search_lines/__init__.py +0 -0
- exdrf_qt-0.1.17/exdrf_qt/controls/search_lines/base.py +404 -0
- exdrf_qt-0.1.17/exdrf_qt/controls/search_lines/model_settings.py +144 -0
- exdrf_qt-0.1.17/exdrf_qt/controls/search_lines/with_model.py +49 -0
- exdrf_qt-0.1.17/exdrf_qt/controls/seldb/__init__.py +0 -0
- exdrf_qt-0.1.17/exdrf_qt/controls/seldb/choose_db.py +37 -0
- exdrf_qt-0.1.17/exdrf_qt/controls/seldb/db_config_delegate.py +249 -0
- exdrf_qt-0.1.17/exdrf_qt/controls/seldb/db_config_model.py +222 -0
- exdrf_qt-0.1.17/exdrf_qt/controls/seldb/db_version_worker.py +197 -0
- exdrf_qt-0.1.17/exdrf_qt/controls/seldb/manage_model.py +1069 -0
- exdrf_qt-0.1.17/exdrf_qt/controls/seldb/sel_db.py +649 -0
- exdrf_qt-0.1.17/exdrf_qt/controls/seldb/sel_db_ui.py +255 -0
- exdrf_qt-0.1.17/exdrf_qt/controls/seldb/utils.py +27 -0
- exdrf_qt-0.1.17/exdrf_qt/controls/table_list.py +1092 -0
- exdrf_qt-0.1.17/exdrf_qt/controls/table_viewer/__init__.py +21 -0
- exdrf_qt-0.1.17/exdrf_qt/controls/table_viewer/column_filter_proxy.py +131 -0
- exdrf_qt-0.1.17/exdrf_qt/controls/table_viewer/column_visibility_dialog.py +132 -0
- exdrf_qt-0.1.17/exdrf_qt/controls/table_viewer/db_viewer.py +825 -0
- exdrf_qt-0.1.17/exdrf_qt/controls/table_viewer/sql_column_delegate.py +525 -0
- exdrf_qt-0.1.17/exdrf_qt/controls/table_viewer/sql_table_model.py +521 -0
- exdrf_qt-0.1.17/exdrf_qt/controls/table_viewer/table_view_ctx.py +60 -0
- exdrf_qt-0.1.17/exdrf_qt/controls/table_viewer/table_viewer.py +826 -0
- exdrf_qt-0.1.17/exdrf_qt/controls/table_viewer/viewer_plugin.py +65 -0
- exdrf_qt-0.1.17/exdrf_qt/controls/task_runner/__init__.py +42 -0
- exdrf_qt-0.1.17/exdrf_qt/controls/task_runner/blob_param.py +123 -0
- exdrf_qt-0.1.17/exdrf_qt/controls/task_runner/bool_param.py +72 -0
- exdrf_qt-0.1.17/exdrf_qt/controls/task_runner/date_param.py +134 -0
- exdrf_qt-0.1.17/exdrf_qt/controls/task_runner/datetime_param.py +144 -0
- exdrf_qt-0.1.17/exdrf_qt/controls/task_runner/duration_param.py +114 -0
- exdrf_qt-0.1.17/exdrf_qt/controls/task_runner/enum_param.py +99 -0
- exdrf_qt-0.1.17/exdrf_qt/controls/task_runner/float_list_param.py +178 -0
- exdrf_qt-0.1.17/exdrf_qt/controls/task_runner/float_param.py +162 -0
- exdrf_qt-0.1.17/exdrf_qt/controls/task_runner/formatted_param.py +83 -0
- exdrf_qt-0.1.17/exdrf_qt/controls/task_runner/int_list_param.py +176 -0
- exdrf_qt-0.1.17/exdrf_qt/controls/task_runner/int_param.py +147 -0
- exdrf_qt-0.1.17/exdrf_qt/controls/task_runner/param_widget.py +43 -0
- exdrf_qt-0.1.17/exdrf_qt/controls/task_runner/ref_many_param.py +92 -0
- exdrf_qt-0.1.17/exdrf_qt/controls/task_runner/ref_one_param.py +108 -0
- exdrf_qt-0.1.17/exdrf_qt/controls/task_runner/str_list_param.py +123 -0
- exdrf_qt-0.1.17/exdrf_qt/controls/task_runner/str_param.py +163 -0
- exdrf_qt-0.1.17/exdrf_qt/controls/task_runner/task_runner.py +373 -0
- exdrf_qt-0.1.17/exdrf_qt/controls/task_runner/task_runner_ui.py +162 -0
- exdrf_qt-0.1.17/exdrf_qt/controls/task_runner/time_param.py +126 -0
- exdrf_qt-0.1.17/exdrf_qt/controls/templ_viewer/__init__.py +0 -0
- exdrf_qt-0.1.17/exdrf_qt/controls/templ_viewer/add_var_dlg.py +465 -0
- exdrf_qt-0.1.17/exdrf_qt/controls/templ_viewer/assets/__init__.py +0 -0
- exdrf_qt-0.1.17/exdrf_qt/controls/templ_viewer/code_text_edit.py +513 -0
- exdrf_qt-0.1.17/exdrf_qt/controls/templ_viewer/delegate.py +590 -0
- exdrf_qt-0.1.17/exdrf_qt/controls/templ_viewer/header.py +109 -0
- exdrf_qt-0.1.17/exdrf_qt/controls/templ_viewer/html_to_docx/__init__.py +0 -0
- exdrf_qt-0.1.17/exdrf_qt/controls/templ_viewer/html_to_docx/main.py +1399 -0
- exdrf_qt-0.1.17/exdrf_qt/controls/templ_viewer/html_to_docx/screen_grabber.py +243 -0
- exdrf_qt-0.1.17/exdrf_qt/controls/templ_viewer/html_to_docx/tables.py +468 -0
- exdrf_qt-0.1.17/exdrf_qt/controls/templ_viewer/model.py +420 -0
- exdrf_qt-0.1.17/exdrf_qt/controls/templ_viewer/save_pdf_dlg.py +200 -0
- exdrf_qt-0.1.17/exdrf_qt/controls/templ_viewer/templ_viewer.py +2216 -0
- exdrf_qt-0.1.17/exdrf_qt/controls/templ_viewer/templ_viewer_ui.py +134 -0
- exdrf_qt-0.1.17/exdrf_qt/controls/templ_viewer/view_page.py +396 -0
- exdrf_qt-0.1.17/exdrf_qt/controls/templ_viewer/view_widget.py +163 -0
- exdrf_qt-0.1.17/exdrf_qt/controls/toast.py +426 -0
- exdrf_qt-0.1.17/exdrf_qt/controls/transfer/__init__.py +5 -0
- exdrf_qt-0.1.17/exdrf_qt/controls/transfer/numeric_sort_proxy.py +79 -0
- exdrf_qt-0.1.17/exdrf_qt/controls/transfer/tables_model.py +578 -0
- exdrf_qt-0.1.17/exdrf_qt/controls/transfer/tbl_row.py +19 -0
- exdrf_qt-0.1.17/exdrf_qt/controls/transfer/transfer_rows_worker.py +148 -0
- exdrf_qt-0.1.17/exdrf_qt/controls/transfer/transfer_selected_plugin.py +183 -0
- exdrf_qt-0.1.17/exdrf_qt/controls/transfer/transfer_widget.py +1239 -0
- exdrf_qt-0.1.17/exdrf_qt/controls/transfer/transfer_worker.py +207 -0
- exdrf_qt-0.1.17/exdrf_qt/controls/tree_header.py +695 -0
- exdrf_qt-0.1.17/exdrf_qt/controls/tree_list.py +102 -0
- exdrf_qt-0.1.17/exdrf_qt/field_ed/__init__.py +0 -0
- exdrf_qt-0.1.17/exdrf_qt/field_ed/api.py +12 -0
- exdrf_qt-0.1.17/exdrf_qt/field_ed/base.py +376 -0
- exdrf_qt-0.1.17/exdrf_qt/field_ed/base_date.py +196 -0
- exdrf_qt-0.1.17/exdrf_qt/field_ed/base_drop.py +80 -0
- exdrf_qt-0.1.17/exdrf_qt/field_ed/base_line.py +476 -0
- exdrf_qt-0.1.17/exdrf_qt/field_ed/base_number.py +228 -0
- exdrf_qt-0.1.17/exdrf_qt/field_ed/choices_mixin.py +95 -0
- exdrf_qt-0.1.17/exdrf_qt/field_ed/fed_blob.py +173 -0
- exdrf_qt-0.1.17/exdrf_qt/field_ed/fed_bool.py +172 -0
- exdrf_qt-0.1.17/exdrf_qt/field_ed/fed_date.py +101 -0
- exdrf_qt-0.1.17/exdrf_qt/field_ed/fed_dt.py +112 -0
- exdrf_qt-0.1.17/exdrf_qt/field_ed/fed_enum.py +440 -0
- exdrf_qt-0.1.17/exdrf_qt/field_ed/fed_int.py +77 -0
- exdrf_qt-0.1.17/exdrf_qt/field_ed/fed_m_text.py +230 -0
- exdrf_qt-0.1.17/exdrf_qt/field_ed/fed_real.py +111 -0
- exdrf_qt-0.1.17/exdrf_qt/field_ed/fed_related/__init__.py +5 -0
- exdrf_qt-0.1.17/exdrf_qt/field_ed/fed_related/base_adapter.py +62 -0
- exdrf_qt-0.1.17/exdrf_qt/field_ed/fed_related/bridge_adapter.py +297 -0
- exdrf_qt-0.1.17/exdrf_qt/field_ed/fed_related/fed_related.py +460 -0
- exdrf_qt-0.1.17/exdrf_qt/field_ed/fed_related/simple_adapter.py +179 -0
- exdrf_qt-0.1.17/exdrf_qt/field_ed/fed_s_text.py +162 -0
- exdrf_qt-0.1.17/exdrf_qt/field_ed/fed_sel_multi.py +289 -0
- exdrf_qt-0.1.17/exdrf_qt/field_ed/fed_sel_one.py +982 -0
- exdrf_qt-0.1.17/exdrf_qt/field_ed/fed_time.py +113 -0
- exdrf_qt-0.1.17/exdrf_qt/local_settings.py +456 -0
- exdrf_qt-0.1.17/exdrf_qt/menus.py +510 -0
- exdrf_qt-0.1.17/exdrf_qt/models/__init__.py +2 -0
- exdrf_qt-0.1.17/exdrf_qt/models/cache.py +212 -0
- exdrf_qt-0.1.17/exdrf_qt/models/fi_item.py +8 -0
- exdrf_qt-0.1.17/exdrf_qt/models/fi_op.py +49 -0
- exdrf_qt-0.1.17/exdrf_qt/models/field.py +476 -0
- exdrf_qt-0.1.17/exdrf_qt/models/field_list.py +338 -0
- exdrf_qt-0.1.17/exdrf_qt/models/fields.py +1516 -0
- exdrf_qt-0.1.17/exdrf_qt/models/model.py +2700 -0
- exdrf_qt-0.1.17/exdrf_qt/models/proxy.py +238 -0
- exdrf_qt-0.1.17/exdrf_qt/models/record.py +241 -0
- exdrf_qt-0.1.17/exdrf_qt/models/requests.py +186 -0
- exdrf_qt-0.1.17/exdrf_qt/models/selector.py +354 -0
- exdrf_qt-0.1.17/exdrf_qt/plugins.py +98 -0
- exdrf_qt-0.1.17/exdrf_qt/py.typed +0 -0
- exdrf_qt-0.1.17/exdrf_qt/scripts/__init__.py +0 -0
- exdrf_qt-0.1.17/exdrf_qt/scripts/gen_ui_file.py +503 -0
- exdrf_qt-0.1.17/exdrf_qt/utils/__init__.py +0 -0
- exdrf_qt-0.1.17/exdrf_qt/utils/attr_dict.py +109 -0
- exdrf_qt-0.1.17/exdrf_qt/utils/del_actions.py +108 -0
- exdrf_qt-0.1.17/exdrf_qt/utils/examine_threads.py +1483 -0
- exdrf_qt-0.1.17/exdrf_qt/utils/flt_acts.py +93 -0
- exdrf_qt-0.1.17/exdrf_qt/utils/html2docx.py +243 -0
- exdrf_qt-0.1.17/exdrf_qt/utils/native_threads.py +176 -0
- exdrf_qt-0.1.17/exdrf_qt/utils/plugins.py +135 -0
- exdrf_qt-0.1.17/exdrf_qt/utils/reload_module.py +867 -0
- exdrf_qt-0.1.17/exdrf_qt/utils/router.py +296 -0
- exdrf_qt-0.1.17/exdrf_qt/utils/screen_grabber.py +207 -0
- exdrf_qt-0.1.17/exdrf_qt/utils/search_actions.py +116 -0
- exdrf_qt-0.1.17/exdrf_qt/utils/stay_open_menu.py +76 -0
- exdrf_qt-0.1.17/exdrf_qt/utils/t_collector.py +59 -0
- exdrf_qt-0.1.17/exdrf_qt/utils/tlh.py +141 -0
- exdrf_qt-0.1.17/exdrf_qt/worker.py +615 -0
- exdrf_qt-0.1.17/exdrf_qt.egg-info/PKG-INFO +74 -0
- exdrf_qt-0.1.17/exdrf_qt.egg-info/SOURCES.txt +242 -0
- exdrf_qt-0.1.17/exdrf_qt.egg-info/dependency_links.txt +1 -0
- exdrf_qt-0.1.17/exdrf_qt.egg-info/requires.txt +36 -0
- exdrf_qt-0.1.17/exdrf_qt.egg-info/top_level.txt +3 -0
- exdrf_qt-0.1.17/exdrf_qt_tests/__init__.py +0 -0
- exdrf_qt-0.1.17/exdrf_qt_tests/comparator/conftest.py +108 -0
- exdrf_qt-0.1.17/exdrf_qt_tests/comparator/test_adapter.py +10 -0
- exdrf_qt-0.1.17/exdrf_qt_tests/comparator/test_manager.py +49 -0
- exdrf_qt-0.1.17/exdrf_qt_tests/comparator/test_merge_delegate_test.py +66 -0
- exdrf_qt-0.1.17/exdrf_qt_tests/comparator/test_merge_logic_test.py +137 -0
- exdrf_qt-0.1.17/exdrf_qt_tests/comparator/test_nodes.py +51 -0
- exdrf_qt-0.1.17/exdrf_qt_tests/comparator/test_tree_model.py +138 -0
- exdrf_qt-0.1.17/exdrf_qt_tests/comparator/test_tree_view.py +79 -0
- exdrf_qt-0.1.17/exdrf_qt_tests/comparator/test_webview.py +119 -0
- exdrf_qt-0.1.17/exdrf_qt_tests/conftests.py +46 -0
- exdrf_qt-0.1.17/exdrf_qt_tests/controls/conftest.py +30 -0
- exdrf_qt-0.1.17/exdrf_qt_tests/controls/test_record_cmp_base_test.py +106 -0
- exdrf_qt-0.1.17/exdrf_qt_tests/models/__init__.py +0 -0
- exdrf_qt-0.1.17/exdrf_qt_tests/models/model/__init__.py +0 -0
- exdrf_qt-0.1.17/exdrf_qt_tests/models/model/test_trim_request.py +255 -0
- exdrf_qt-0.1.17/exdrf_qt_tests/models/test_cache.py +354 -0
- exdrf_qt-0.1.17/exdrf_qt_tests/models/test_fi_item.py +72 -0
- exdrf_qt-0.1.17/exdrf_qt_tests/models/test_fi_item_shim.py +17 -0
- exdrf_qt-0.1.17/exdrf_qt_tests/models/test_fi_op.py +352 -0
- exdrf_qt-0.1.17/exdrf_qt_tests/models/test_fi_op_shim.py +14 -0
- exdrf_qt-0.1.17/exdrf_qt_tests/models/test_field.py +281 -0
- exdrf_qt-0.1.17/exdrf_qt_tests/models/test_field_list.py +405 -0
- exdrf_qt-0.1.17/exdrf_qt_tests/models/test_model.py +500 -0
- exdrf_qt-0.1.17/exdrf_qt_tests/models/test_proxy.py +369 -0
- exdrf_qt-0.1.17/exdrf_qt_tests/models/test_record.py +430 -0
- exdrf_qt-0.1.17/exdrf_qt_tests/models/test_requests.py +313 -0
- exdrf_qt-0.1.17/exdrf_qt_tests/models/test_selector.py +500 -0
- exdrf_qt-0.1.17/exdrf_qt_tests/test_local_settings.py +139 -0
- exdrf_qt-0.1.17/exdrf_qt_tests/test_worker.py +107 -0
- exdrf_qt-0.1.17/pyproject.toml +95 -0
- exdrf_qt-0.1.17/setup.cfg +4 -0
- exdrf_qt-0.1.17/setup.py +6 -0
exdrf_qt-0.1.17/PKG-INFO
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: exdrf-qt
|
|
3
|
+
Version: 0.1.17
|
|
4
|
+
Summary: Use Qt5 with Ex-DRF.
|
|
5
|
+
Author-email: Nicu Tofan <nicu.tofan@gmail.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Classifier: Operating System :: OS Independent
|
|
8
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
9
|
+
Classifier: Development Status :: 3 - Alpha
|
|
10
|
+
Classifier: Typing :: Typed
|
|
11
|
+
Requires-Python: >=3.12.2
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
Requires-Dist: exdrf
|
|
14
|
+
Requires-Dist: appdirs>=1.4.4
|
|
15
|
+
Requires-Dist: cairosvg>=2.7.1
|
|
16
|
+
Requires-Dist: PyQt5>=5.15.11
|
|
17
|
+
Requires-Dist: python-dateutil>=2.9.0
|
|
18
|
+
Requires-Dist: humanize>=4.12.3
|
|
19
|
+
Requires-Dist: html-for-docx>=1.0.6
|
|
20
|
+
Requires-Dist: PyQtWebEngine>=5.15.7
|
|
21
|
+
Requires-Dist: PyQtWebEngine-Qt5>=5.15.2
|
|
22
|
+
Requires-Dist: attrs>=24.2.0
|
|
23
|
+
Requires-Dist: parse>=1.20.2
|
|
24
|
+
Requires-Dist: minify_html>=0.16.4
|
|
25
|
+
Requires-Dist: PyQtWebEngine==5.15.7
|
|
26
|
+
Requires-Dist: pluggy>=1.6.0
|
|
27
|
+
Requires-Dist: pyrsistent>=0.20.0
|
|
28
|
+
Requires-Dist: exdrf-al
|
|
29
|
+
Requires-Dist: SQLAlchemy>=2.0.38
|
|
30
|
+
Requires-Dist: sqlparse>=0.5.3
|
|
31
|
+
Requires-Dist: unidecode
|
|
32
|
+
Requires-Dist: filelock>=3.16.0
|
|
33
|
+
Provides-Extra: dev
|
|
34
|
+
Requires-Dist: autoflake; extra == "dev"
|
|
35
|
+
Requires-Dist: black==25.1.0; extra == "dev"
|
|
36
|
+
Requires-Dist: build; extra == "dev"
|
|
37
|
+
Requires-Dist: flake8; extra == "dev"
|
|
38
|
+
Requires-Dist: isort; extra == "dev"
|
|
39
|
+
Requires-Dist: mypy; extra == "dev"
|
|
40
|
+
Requires-Dist: pre-commit; extra == "dev"
|
|
41
|
+
Requires-Dist: pyproject-flake8; extra == "dev"
|
|
42
|
+
Requires-Dist: pytest-cov; extra == "dev"
|
|
43
|
+
Requires-Dist: pytest-mock; extra == "dev"
|
|
44
|
+
Requires-Dist: pytest; extra == "dev"
|
|
45
|
+
Requires-Dist: twine; extra == "dev"
|
|
46
|
+
Requires-Dist: wheel; extra == "dev"
|
|
47
|
+
Requires-Dist: click<8.2.0,>=8.1.8; extra == "dev"
|
|
48
|
+
|
|
49
|
+
# Qt5 components for Ex-DRF
|
|
50
|
+
|
|
51
|
+
**exdrf-qt** supplies **PyQt5** building blocks—models, editors, lists,
|
|
52
|
+
selectors, plugins, and HTML-backed viewers—that align with **exdrf** field
|
|
53
|
+
types and **exdrf-al**-derived datasets. Generated desktop UIs from
|
|
54
|
+
**exdrf-gen-al2qt** import this package for base classes and integration glue.
|
|
55
|
+
|
|
56
|
+
## Scope
|
|
57
|
+
|
|
58
|
+
The library is opinionated toward **desktop** workflows: it brings in **PyQt5**,
|
|
59
|
+
**PyQtWebEngine**, SVG and HTML helpers, and SQLAlchemy-related utilities for
|
|
60
|
+
data-bound widgets. It is heavier than **exdrf** alone; use it when you ship a
|
|
61
|
+
Qt client or run codegen that targets Qt.
|
|
62
|
+
|
|
63
|
+
## Dependencies
|
|
64
|
+
|
|
65
|
+
See `pyproject.toml`: **exdrf**, **exdrf-al**, **SQLAlchemy**, **PyQt5**,
|
|
66
|
+
**PyQtWebEngine**, and several small helpers (attrs, parse, filelock, etc.).
|
|
67
|
+
Python **3.12.2+** is required.
|
|
68
|
+
|
|
69
|
+
## Related packages
|
|
70
|
+
|
|
71
|
+
- **exdrf-gen-al2qt** — generates menus, routers, per-resource widgets, and
|
|
72
|
+
field classes on top of **exdrf-qt**.
|
|
73
|
+
- **exdrf-dev** (in the same monorepo) — sample app and widgets that exercise
|
|
74
|
+
the stack end to end.
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# Qt5 components for Ex-DRF
|
|
2
|
+
|
|
3
|
+
**exdrf-qt** supplies **PyQt5** building blocks—models, editors, lists,
|
|
4
|
+
selectors, plugins, and HTML-backed viewers—that align with **exdrf** field
|
|
5
|
+
types and **exdrf-al**-derived datasets. Generated desktop UIs from
|
|
6
|
+
**exdrf-gen-al2qt** import this package for base classes and integration glue.
|
|
7
|
+
|
|
8
|
+
## Scope
|
|
9
|
+
|
|
10
|
+
The library is opinionated toward **desktop** workflows: it brings in **PyQt5**,
|
|
11
|
+
**PyQtWebEngine**, SVG and HTML helpers, and SQLAlchemy-related utilities for
|
|
12
|
+
data-bound widgets. It is heavier than **exdrf** alone; use it when you ship a
|
|
13
|
+
Qt client or run codegen that targets Qt.
|
|
14
|
+
|
|
15
|
+
## Dependencies
|
|
16
|
+
|
|
17
|
+
See `pyproject.toml`: **exdrf**, **exdrf-al**, **SQLAlchemy**, **PyQt5**,
|
|
18
|
+
**PyQtWebEngine**, and several small helpers (attrs, parse, filelock, etc.).
|
|
19
|
+
Python **3.12.2+** is required.
|
|
20
|
+
|
|
21
|
+
## Related packages
|
|
22
|
+
|
|
23
|
+
- **exdrf-gen-al2qt** — generates menus, routers, per-resource widgets, and
|
|
24
|
+
field classes on top of **exdrf-qt**.
|
|
25
|
+
- **exdrf-dev** (in the same monorepo) — sample app and widgets that exercise
|
|
26
|
+
the stack end to end.
|
|
File without changes
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"""Package version from PEP 621 or installed metadata."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
from exdrf.pep621_version import distribution_version, version_tuple_from_string
|
|
8
|
+
|
|
9
|
+
_PYPROJECT = Path(__file__).resolve().parents[1] / "pyproject.toml"
|
|
10
|
+
_DIST_NAME = "exdrf-qt"
|
|
11
|
+
|
|
12
|
+
__version__ = version = distribution_version(_DIST_NAME, _PYPROJECT)
|
|
13
|
+
__version_tuple__ = version_tuple = version_tuple_from_string(__version__)
|
|
14
|
+
|
|
15
|
+
__commit_id__ = commit_id = None
|
|
16
|
+
|
|
17
|
+
__all__ = [
|
|
18
|
+
"__version__",
|
|
19
|
+
"__version_tuple__",
|
|
20
|
+
"version",
|
|
21
|
+
"version_tuple",
|
|
22
|
+
"__commit_id__",
|
|
23
|
+
"commit_id",
|
|
24
|
+
]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
from typing import TYPE_CHECKING, Any, List, Optional
|
|
2
|
+
|
|
3
|
+
if TYPE_CHECKING:
|
|
4
|
+
from exdrf_qt.comparator.logic.manager import ComparatorManager
|
|
5
|
+
from exdrf_qt.comparator.logic.merge import (
|
|
6
|
+
LeafMergeState,
|
|
7
|
+
MergeContext,
|
|
8
|
+
MergeMethodOption,
|
|
9
|
+
)
|
|
10
|
+
from exdrf_qt.comparator.logic.nodes import LeafNode, ParentNode
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class ComparatorAdapter:
|
|
14
|
+
"""Defines the interface that needs to be implemented so that data can be
|
|
15
|
+
extracted from a source. Optional merge hooks allow per-source or
|
|
16
|
+
per-property overrides when merge mode is enabled.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
def get_compare_data(self, mng: "ComparatorManager") -> "ParentNode":
|
|
20
|
+
"""Get the data that will be used for comparison.
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
mng: The manager that this adapter belongs to.
|
|
24
|
+
|
|
25
|
+
Returns:
|
|
26
|
+
A single parent node that will not be used in the comparison but
|
|
27
|
+
will be used only as a container for the data.
|
|
28
|
+
"""
|
|
29
|
+
raise NotImplementedError("get_compare_data")
|
|
30
|
+
|
|
31
|
+
# Optional merge hooks (return None to use strategy default).
|
|
32
|
+
# -------------------------------------------------------------------------
|
|
33
|
+
|
|
34
|
+
def get_merge_item_label(
|
|
35
|
+
self, mng: "ComparatorManager", source_index: int
|
|
36
|
+
) -> Optional[str]:
|
|
37
|
+
"""Return the display label for one item/source in merge method list.
|
|
38
|
+
|
|
39
|
+
Args:
|
|
40
|
+
mng: The comparator manager.
|
|
41
|
+
source_index: Zero-based index of the source (0 = Item 1, etc.).
|
|
42
|
+
|
|
43
|
+
Returns:
|
|
44
|
+
Label string for this source, or None to use strategy default
|
|
45
|
+
(e.g. "Item 1", "Item 2").
|
|
46
|
+
"""
|
|
47
|
+
return None
|
|
48
|
+
|
|
49
|
+
def get_available_merge_methods_for_leaf(
|
|
50
|
+
self, mng: "ComparatorManager", leaf: "LeafNode"
|
|
51
|
+
) -> Optional[List["MergeMethodOption"]]:
|
|
52
|
+
"""Return merge methods allowed for this leaf, or None to use strategy.
|
|
53
|
+
|
|
54
|
+
Args:
|
|
55
|
+
mng: The comparator manager.
|
|
56
|
+
leaf: The leaf node (key/label identify the property).
|
|
57
|
+
|
|
58
|
+
Returns:
|
|
59
|
+
List of method options, or None to use manager strategy default.
|
|
60
|
+
"""
|
|
61
|
+
return None
|
|
62
|
+
|
|
63
|
+
def create_merge_editor(
|
|
64
|
+
self,
|
|
65
|
+
parent: Any,
|
|
66
|
+
context: "MergeContext",
|
|
67
|
+
state: "LeafMergeState",
|
|
68
|
+
current_value: Any,
|
|
69
|
+
) -> Optional[Any]:
|
|
70
|
+
"""Create a custom editor widget for the merge result cell, or None.
|
|
71
|
+
|
|
72
|
+
Args:
|
|
73
|
+
parent: Parent Qt widget for the editor.
|
|
74
|
+
context: Merge context for the leaf.
|
|
75
|
+
state: Current merge state.
|
|
76
|
+
current_value: Current value to show in the editor.
|
|
77
|
+
|
|
78
|
+
Returns:
|
|
79
|
+
Editor widget, or None to use default (e.g. line edit for manual).
|
|
80
|
+
"""
|
|
81
|
+
return None
|
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
from typing import TYPE_CHECKING, Any, Dict, List, Optional, cast
|
|
2
|
+
|
|
3
|
+
from attrs import define, field
|
|
4
|
+
|
|
5
|
+
from exdrf_qt.comparator.logic.adapter import ComparatorAdapter
|
|
6
|
+
|
|
7
|
+
if TYPE_CHECKING:
|
|
8
|
+
from exdrf_qt.comparator.logic.merge import (
|
|
9
|
+
MergeContext,
|
|
10
|
+
MergeMethodOption,
|
|
11
|
+
MergeStrategy,
|
|
12
|
+
)
|
|
13
|
+
from exdrf_qt.comparator.logic.nodes import BaseNode, LeafNode, ParentNode
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@define(eq=False)
|
|
17
|
+
class ComparatorManager:
|
|
18
|
+
"""Manages the comparison between two or more items.
|
|
19
|
+
|
|
20
|
+
When merge mode is enabled, merge_strategy and adapter hooks control
|
|
21
|
+
method labels, available methods, and value resolution.
|
|
22
|
+
|
|
23
|
+
Attributes:
|
|
24
|
+
sources: List of sources that will be queried for comparison.
|
|
25
|
+
root: Unified root node after compare().
|
|
26
|
+
data: Per-source roots from get_compare_data().
|
|
27
|
+
merge_strategy: Optional merge strategy; None uses default behavior.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
sources: List["ComparatorAdapter"] = field(factory=list)
|
|
31
|
+
root: "ParentNode" = field(default=None, repr=False)
|
|
32
|
+
data: List["BaseNode"] = field(factory=list)
|
|
33
|
+
merge_strategy: Optional["MergeStrategy"] = field(default=None)
|
|
34
|
+
|
|
35
|
+
def __attrs_post_init__(self) -> None:
|
|
36
|
+
"""Post-initialization hook."""
|
|
37
|
+
from exdrf_qt.comparator.logic.nodes import ParentNode
|
|
38
|
+
|
|
39
|
+
self.root = ParentNode(manager=self)
|
|
40
|
+
|
|
41
|
+
def get_compare_data(self) -> List["BaseNode"]:
|
|
42
|
+
"""Get the data that will be used for comparison."""
|
|
43
|
+
self.data = [adapter.get_compare_data(self) for adapter in self.sources]
|
|
44
|
+
return self.data
|
|
45
|
+
|
|
46
|
+
def compare(self) -> None:
|
|
47
|
+
from exdrf_qt.comparator.logic.nodes import LeafNode, ParentNode, Value
|
|
48
|
+
|
|
49
|
+
stack = [
|
|
50
|
+
{
|
|
51
|
+
"src": self.data,
|
|
52
|
+
"dst": self.root,
|
|
53
|
+
}
|
|
54
|
+
]
|
|
55
|
+
|
|
56
|
+
while stack:
|
|
57
|
+
frame = stack.pop()
|
|
58
|
+
|
|
59
|
+
sources = cast(List["ParentNode | None"], frame["src"])
|
|
60
|
+
destination = cast("ParentNode", frame["dst"])
|
|
61
|
+
|
|
62
|
+
children: List[Dict[int, "BaseNode"]] = []
|
|
63
|
+
is_first = True
|
|
64
|
+
for s_i, source in enumerate(sources):
|
|
65
|
+
if source is None:
|
|
66
|
+
continue
|
|
67
|
+
|
|
68
|
+
if is_first:
|
|
69
|
+
# The first source simply adds all its children to the
|
|
70
|
+
# destination.
|
|
71
|
+
is_first = False
|
|
72
|
+
|
|
73
|
+
for child in source.children:
|
|
74
|
+
children.append({s_i: child})
|
|
75
|
+
else:
|
|
76
|
+
# Other sources must either match one of the existing
|
|
77
|
+
# children or create a new child.
|
|
78
|
+
|
|
79
|
+
# We start by creating a matrix of scores, one for each
|
|
80
|
+
# child and source.
|
|
81
|
+
len_c = len(children)
|
|
82
|
+
scores = [[0] * len_c for _ in range(len(source.children))]
|
|
83
|
+
for o_i, other_data in enumerate(children):
|
|
84
|
+
# Get the data from the first source that provided data
|
|
85
|
+
# for this child.
|
|
86
|
+
other = other_data[min(other_data.keys())]
|
|
87
|
+
# Now go through the current source's children and
|
|
88
|
+
# compare them to the other child.
|
|
89
|
+
for crt_i, current in enumerate(source.children):
|
|
90
|
+
scores[crt_i][o_i] = (
|
|
91
|
+
0
|
|
92
|
+
if (other.is_leaf != current.is_leaf)
|
|
93
|
+
else source.compare(other, current)
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
# Now we need to find the best match for each child in the
|
|
97
|
+
# order of the score, while making sure that we don't match
|
|
98
|
+
# the same child to multiple sources. -1 means a perfect
|
|
99
|
+
# match and is evaluated first, 0 means never match,
|
|
100
|
+
# others are evaluated in order of similarity but the
|
|
101
|
+
# order is absolute across the board., so the highest
|
|
102
|
+
# score left on the board should be pulled, make that
|
|
103
|
+
# association and repeat until there are no more matches.
|
|
104
|
+
|
|
105
|
+
# Prepare greedy matching with priority:
|
|
106
|
+
# 1) Perfect matches (-1) first
|
|
107
|
+
# 2) Then scores in descending order
|
|
108
|
+
row_count = len(source.children)
|
|
109
|
+
col_count = len_c
|
|
110
|
+
|
|
111
|
+
# Track used rows/cols to avoid duplicate matches.
|
|
112
|
+
used_rows: set[int] = set()
|
|
113
|
+
used_cols: set[int] = set()
|
|
114
|
+
|
|
115
|
+
# First, take all perfect matches.
|
|
116
|
+
for r in range(row_count):
|
|
117
|
+
for c in range(col_count):
|
|
118
|
+
if (
|
|
119
|
+
scores[r][c] == -1
|
|
120
|
+
and r not in used_rows
|
|
121
|
+
and c not in used_cols
|
|
122
|
+
):
|
|
123
|
+
# Assign this perfect pair.
|
|
124
|
+
children[c][s_i] = source.children[r]
|
|
125
|
+
used_rows.add(r)
|
|
126
|
+
used_cols.add(c)
|
|
127
|
+
|
|
128
|
+
# Next, consider all scores and pick highest first.
|
|
129
|
+
candidates: List[tuple[int, int, int]] = []
|
|
130
|
+
for r in range(row_count):
|
|
131
|
+
for c in range(col_count):
|
|
132
|
+
sc = scores[r][c]
|
|
133
|
+
if sc != 0 and sc != -1:
|
|
134
|
+
candidates.append((sc, r, c))
|
|
135
|
+
|
|
136
|
+
# Sort by score descending.
|
|
137
|
+
candidates.sort(key=lambda t: t[0], reverse=True)
|
|
138
|
+
|
|
139
|
+
for sc, r, c in candidates:
|
|
140
|
+
if r in used_rows or c in used_cols:
|
|
141
|
+
continue
|
|
142
|
+
children[c][s_i] = source.children[r]
|
|
143
|
+
used_rows.add(r)
|
|
144
|
+
used_cols.add(c)
|
|
145
|
+
|
|
146
|
+
# Finally, any row not matched creates a new child entry.
|
|
147
|
+
for r in range(row_count):
|
|
148
|
+
if r not in used_rows:
|
|
149
|
+
children.append({s_i: source.children[r]})
|
|
150
|
+
assert not is_first, "At least one source must be provided."
|
|
151
|
+
|
|
152
|
+
# At this point for each children all values are either leafs or
|
|
153
|
+
# parents or not set. Each entry will have at least one member.
|
|
154
|
+
|
|
155
|
+
for c_data in children:
|
|
156
|
+
sample = c_data[min(c_data.keys())]
|
|
157
|
+
|
|
158
|
+
new_node: "BaseNode"
|
|
159
|
+
if sample.is_leaf:
|
|
160
|
+
new_node = LeafNode(
|
|
161
|
+
manager=self,
|
|
162
|
+
key=sample.key,
|
|
163
|
+
label=sample.label,
|
|
164
|
+
parent=destination,
|
|
165
|
+
)
|
|
166
|
+
for s_i, source in enumerate(sources):
|
|
167
|
+
adapter = self.sources[s_i]
|
|
168
|
+
buddy = cast("LeafNode", c_data.get(s_i, None))
|
|
169
|
+
if buddy is not None:
|
|
170
|
+
if len(buddy.values) > 0 and buddy.values[0].exists:
|
|
171
|
+
new_node.values.append(
|
|
172
|
+
Value(
|
|
173
|
+
exists=True,
|
|
174
|
+
value=buddy.values[0].value,
|
|
175
|
+
node=new_node,
|
|
176
|
+
source=adapter,
|
|
177
|
+
)
|
|
178
|
+
)
|
|
179
|
+
continue
|
|
180
|
+
|
|
181
|
+
new_node.values.append(
|
|
182
|
+
Value(
|
|
183
|
+
exists=False,
|
|
184
|
+
value=None,
|
|
185
|
+
node=new_node,
|
|
186
|
+
source=adapter,
|
|
187
|
+
)
|
|
188
|
+
)
|
|
189
|
+
else:
|
|
190
|
+
new_node = ParentNode(
|
|
191
|
+
manager=self,
|
|
192
|
+
key=sample.key,
|
|
193
|
+
label=sample.label,
|
|
194
|
+
parent=destination,
|
|
195
|
+
)
|
|
196
|
+
new_sources: List["BaseNode | None"] = []
|
|
197
|
+
for s_i, source in enumerate(sources):
|
|
198
|
+
if s_i in c_data:
|
|
199
|
+
new_sources.append(c_data[s_i])
|
|
200
|
+
else:
|
|
201
|
+
new_sources.append(None)
|
|
202
|
+
|
|
203
|
+
stack.append(
|
|
204
|
+
{
|
|
205
|
+
"src": new_sources,
|
|
206
|
+
"dst": new_node,
|
|
207
|
+
}
|
|
208
|
+
)
|
|
209
|
+
destination.add_child(new_node)
|
|
210
|
+
|
|
211
|
+
# Merge mode helpers (no-op if merge not used).
|
|
212
|
+
# -------------------------------------------------------------------------
|
|
213
|
+
|
|
214
|
+
def _get_strategy(self) -> "MergeStrategy":
|
|
215
|
+
"""Return the effective merge strategy (never None)."""
|
|
216
|
+
from exdrf_qt.comparator.logic.merge import DefaultMergeStrategy
|
|
217
|
+
|
|
218
|
+
if self.merge_strategy is not None:
|
|
219
|
+
return self.merge_strategy
|
|
220
|
+
return DefaultMergeStrategy()
|
|
221
|
+
|
|
222
|
+
def get_merge_context(self, leaf: "LeafNode") -> "MergeContext":
|
|
223
|
+
"""Build merge context for a leaf (values and source labels).
|
|
224
|
+
|
|
225
|
+
Args:
|
|
226
|
+
leaf: The leaf node.
|
|
227
|
+
|
|
228
|
+
Returns:
|
|
229
|
+
MergeContext with values and source_labels (from adapters or
|
|
230
|
+
strategy).
|
|
231
|
+
"""
|
|
232
|
+
from exdrf_qt.comparator.logic.merge import MergeContext
|
|
233
|
+
|
|
234
|
+
strategy = self._get_strategy()
|
|
235
|
+
num_sources = len(self.sources)
|
|
236
|
+
source_labels = []
|
|
237
|
+
for i in range(num_sources):
|
|
238
|
+
label = None
|
|
239
|
+
if i < len(self.sources):
|
|
240
|
+
adapter = self.sources[i]
|
|
241
|
+
label = adapter.get_merge_item_label(self, i)
|
|
242
|
+
if label is None:
|
|
243
|
+
ctx = MergeContext(leaf=leaf, manager=self, values=leaf.values)
|
|
244
|
+
labels = strategy.get_item_labels(ctx, num_sources)
|
|
245
|
+
label = labels[i] if i < len(labels) else "Item %d" % (i + 1)
|
|
246
|
+
source_labels.append(label)
|
|
247
|
+
return MergeContext(
|
|
248
|
+
leaf=leaf,
|
|
249
|
+
manager=self,
|
|
250
|
+
values=list(leaf.values),
|
|
251
|
+
source_labels=source_labels,
|
|
252
|
+
)
|
|
253
|
+
|
|
254
|
+
def get_available_merge_methods(
|
|
255
|
+
self, leaf: "LeafNode"
|
|
256
|
+
) -> List["MergeMethodOption"]:
|
|
257
|
+
"""Return merge methods for this leaf (adapter override or strategy).
|
|
258
|
+
|
|
259
|
+
Args:
|
|
260
|
+
leaf: The leaf node.
|
|
261
|
+
|
|
262
|
+
Returns:
|
|
263
|
+
List of method options.
|
|
264
|
+
"""
|
|
265
|
+
for adapter in self.sources:
|
|
266
|
+
opts = adapter.get_available_merge_methods_for_leaf(self, leaf)
|
|
267
|
+
if opts is not None:
|
|
268
|
+
return opts
|
|
269
|
+
context = self.get_merge_context(leaf)
|
|
270
|
+
return self._get_strategy().get_available_methods(context)
|
|
271
|
+
|
|
272
|
+
def resolve_merge_value(self, leaf: "LeafNode") -> Any:
|
|
273
|
+
"""Resolve the merged value for a leaf from its merge state.
|
|
274
|
+
|
|
275
|
+
Args:
|
|
276
|
+
leaf: The leaf node (merge_state set when merge mode used).
|
|
277
|
+
|
|
278
|
+
Returns:
|
|
279
|
+
Resolved value (may be None).
|
|
280
|
+
"""
|
|
281
|
+
from exdrf_qt.comparator.logic.merge import LeafMergeState
|
|
282
|
+
|
|
283
|
+
state = getattr(leaf, "merge_state", None)
|
|
284
|
+
if state is None:
|
|
285
|
+
state = LeafMergeState()
|
|
286
|
+
context = self.get_merge_context(leaf)
|
|
287
|
+
resolved = self._get_strategy().resolve_value(context, state)
|
|
288
|
+
ms = getattr(leaf, "merge_state", None)
|
|
289
|
+
if ms is not None:
|
|
290
|
+
ms.resolved_value = resolved
|
|
291
|
+
return resolved
|
|
292
|
+
|
|
293
|
+
def get_merged_payload(self) -> Dict[str, Any]:
|
|
294
|
+
"""Walk all leaves and return flat key-path -> resolved value map.
|
|
295
|
+
|
|
296
|
+
Keys are dotted paths from root (e.g. "grp.nested_equal"). Only leaf
|
|
297
|
+
nodes are included. Merge state is initialized on leaves by the tree
|
|
298
|
+
model in merge mode.
|
|
299
|
+
|
|
300
|
+
Returns:
|
|
301
|
+
Dict mapping dotted key path to resolved value.
|
|
302
|
+
"""
|
|
303
|
+
from exdrf_qt.comparator.logic.nodes import LeafNode, ParentNode
|
|
304
|
+
|
|
305
|
+
result: Dict[str, Any] = {}
|
|
306
|
+
|
|
307
|
+
def walk(node: "BaseNode", path_prefix: List[str]) -> None:
|
|
308
|
+
if isinstance(node, LeafNode):
|
|
309
|
+
key_path = ".".join(path_prefix) if path_prefix else node.key
|
|
310
|
+
result[key_path] = self.resolve_merge_value(node)
|
|
311
|
+
elif isinstance(node, ParentNode):
|
|
312
|
+
for child in node.children:
|
|
313
|
+
child_path = (
|
|
314
|
+
path_prefix + [child.key] if child.key else path_prefix
|
|
315
|
+
)
|
|
316
|
+
walk(child, child_path)
|
|
317
|
+
|
|
318
|
+
if self.root is not None:
|
|
319
|
+
for child in self.root.children:
|
|
320
|
+
walk(child, [child.key] if child.key else [])
|
|
321
|
+
|
|
322
|
+
return result
|