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.
Files changed (244) hide show
  1. exdrf_qt-0.1.17/PKG-INFO +74 -0
  2. exdrf_qt-0.1.17/README.md +26 -0
  3. exdrf_qt-0.1.17/exdrf_qt/__init__.py +0 -0
  4. exdrf_qt-0.1.17/exdrf_qt/__version__.py +24 -0
  5. exdrf_qt-0.1.17/exdrf_qt/assets/__init__.py +0 -0
  6. exdrf_qt-0.1.17/exdrf_qt/comparator/__init__.py +0 -0
  7. exdrf_qt-0.1.17/exdrf_qt/comparator/logic/__init__.py +0 -0
  8. exdrf_qt-0.1.17/exdrf_qt/comparator/logic/adapter.py +81 -0
  9. exdrf_qt-0.1.17/exdrf_qt/comparator/logic/manager.py +322 -0
  10. exdrf_qt-0.1.17/exdrf_qt/comparator/logic/merge.py +246 -0
  11. exdrf_qt-0.1.17/exdrf_qt/comparator/logic/nodes.py +250 -0
  12. exdrf_qt-0.1.17/exdrf_qt/comparator/models/__init__.py +0 -0
  13. exdrf_qt-0.1.17/exdrf_qt/comparator/models/tree.py +390 -0
  14. exdrf_qt-0.1.17/exdrf_qt/comparator/widgets/__init__.py +0 -0
  15. exdrf_qt-0.1.17/exdrf_qt/comparator/widgets/cmp_result_preview_pane.py +97 -0
  16. exdrf_qt-0.1.17/exdrf_qt/comparator/widgets/field_aware_record_adapter.py +70 -0
  17. exdrf_qt-0.1.17/exdrf_qt/comparator/widgets/merge_delegate.py +197 -0
  18. exdrf_qt-0.1.17/exdrf_qt/comparator/widgets/record_cmp_base.py +32 -0
  19. exdrf_qt-0.1.17/exdrf_qt/comparator/widgets/record_comparator_base.py +210 -0
  20. exdrf_qt-0.1.17/exdrf_qt/comparator/widgets/record_to_node_adapter.py +94 -0
  21. exdrf_qt-0.1.17/exdrf_qt/comparator/widgets/tree.py +525 -0
  22. exdrf_qt-0.1.17/exdrf_qt/comparator/widgets/webview.py +282 -0
  23. exdrf_qt-0.1.17/exdrf_qt/context.py +567 -0
  24. exdrf_qt-0.1.17/exdrf_qt/context_use.py +109 -0
  25. exdrf_qt-0.1.17/exdrf_qt/controls/__init__.py +9 -0
  26. exdrf_qt-0.1.17/exdrf_qt/controls/base_editor.py +901 -0
  27. exdrf_qt-0.1.17/exdrf_qt/controls/checkable_combo.py +134 -0
  28. exdrf_qt-0.1.17/exdrf_qt/controls/checks/__init__.py +0 -0
  29. exdrf_qt-0.1.17/exdrf_qt/controls/checks/available_delegate.py +203 -0
  30. exdrf_qt-0.1.17/exdrf_qt/controls/checks/available_model.py +218 -0
  31. exdrf_qt-0.1.17/exdrf_qt/controls/checks/check_manager.py +1807 -0
  32. exdrf_qt-0.1.17/exdrf_qt/controls/checks/check_manager_ui.py +281 -0
  33. exdrf_qt-0.1.17/exdrf_qt/controls/checks/checks_model_base.py +396 -0
  34. exdrf_qt-0.1.17/exdrf_qt/controls/checks/mp_executor.py +303 -0
  35. exdrf_qt-0.1.17/exdrf_qt/controls/checks/results_model.py +468 -0
  36. exdrf_qt-0.1.17/exdrf_qt/controls/checks/selected_delegate.py +132 -0
  37. exdrf_qt-0.1.17/exdrf_qt/controls/checks/selected_model.py +332 -0
  38. exdrf_qt-0.1.17/exdrf_qt/controls/column_sel/__init__.py +0 -0
  39. exdrf_qt-0.1.17/exdrf_qt/controls/column_sel/column_sel.py +115 -0
  40. exdrf_qt-0.1.17/exdrf_qt/controls/column_sel/column_sel_ui.py +113 -0
  41. exdrf_qt-0.1.17/exdrf_qt/controls/command_palette/__init__.py +2 -0
  42. exdrf_qt-0.1.17/exdrf_qt/controls/command_palette/constants.py +39 -0
  43. exdrf_qt-0.1.17/exdrf_qt/controls/command_palette/delegate.py +217 -0
  44. exdrf_qt-0.1.17/exdrf_qt/controls/command_palette/engine.py +190 -0
  45. exdrf_qt-0.1.17/exdrf_qt/controls/command_palette/line_edit.py +377 -0
  46. exdrf_qt-0.1.17/exdrf_qt/controls/command_palette/model.py +137 -0
  47. exdrf_qt-0.1.17/exdrf_qt/controls/constraints/__init__.py +0 -0
  48. exdrf_qt-0.1.17/exdrf_qt/controls/constraints/base.py +106 -0
  49. exdrf_qt-0.1.17/exdrf_qt/controls/constraints/concept_base.py +26 -0
  50. exdrf_qt-0.1.17/exdrf_qt/controls/crud_actions.py +405 -0
  51. exdrf_qt-0.1.17/exdrf_qt/controls/filter_dlg/__init__.py +1 -0
  52. exdrf_qt-0.1.17/exdrf_qt/controls/filter_dlg/filter_dlg.py +105 -0
  53. exdrf_qt-0.1.17/exdrf_qt/controls/filter_dlg/filter_dlg_ui.py +108 -0
  54. exdrf_qt-0.1.17/exdrf_qt/controls/filter_dlg/filter_editor.py +884 -0
  55. exdrf_qt-0.1.17/exdrf_qt/controls/filter_header.py +289 -0
  56. exdrf_qt-0.1.17/exdrf_qt/controls/json_editor/__init__.py +0 -0
  57. exdrf_qt-0.1.17/exdrf_qt/controls/json_editor/delegate.py +134 -0
  58. exdrf_qt-0.1.17/exdrf_qt/controls/json_editor/dialog.py +91 -0
  59. exdrf_qt-0.1.17/exdrf_qt/controls/json_editor/editor.py +101 -0
  60. exdrf_qt-0.1.17/exdrf_qt/controls/json_editor/item.py +129 -0
  61. exdrf_qt-0.1.17/exdrf_qt/controls/json_editor/model.py +322 -0
  62. exdrf_qt-0.1.17/exdrf_qt/controls/json_editor/tree.py +195 -0
  63. exdrf_qt-0.1.17/exdrf_qt/controls/new_search_line.py +146 -0
  64. exdrf_qt-0.1.17/exdrf_qt/controls/param_controls.py +503 -0
  65. exdrf_qt-0.1.17/exdrf_qt/controls/pdf_viewer/__init__.py +3 -0
  66. exdrf_qt-0.1.17/exdrf_qt/controls/pdf_viewer/image_graphics_view.py +295 -0
  67. exdrf_qt-0.1.17/exdrf_qt/controls/pdf_viewer/pdf_image_splitter.py +649 -0
  68. exdrf_qt-0.1.17/exdrf_qt/controls/pdf_viewer/pdf_image_viewer.py +1543 -0
  69. exdrf_qt-0.1.17/exdrf_qt/controls/pdf_viewer/pdf_render_worker.py +108 -0
  70. exdrf_qt-0.1.17/exdrf_qt/controls/pdf_viewer/pdf_viewer.py +71 -0
  71. exdrf_qt-0.1.17/exdrf_qt/controls/pdf_viewer/rotation_editor_dialog.py +105 -0
  72. exdrf_qt-0.1.17/exdrf_qt/controls/pdf_viewer/split_entry.py +14 -0
  73. exdrf_qt-0.1.17/exdrf_qt/controls/pdf_viewer/split_plan_panel.py +1176 -0
  74. exdrf_qt-0.1.17/exdrf_qt/controls/pdf_viewer/split_preview_window.py +205 -0
  75. exdrf_qt-0.1.17/exdrf_qt/controls/popup_list.py +272 -0
  76. exdrf_qt-0.1.17/exdrf_qt/controls/record_cmp_base.py +15 -0
  77. exdrf_qt-0.1.17/exdrf_qt/controls/search_line.py +237 -0
  78. exdrf_qt-0.1.17/exdrf_qt/controls/search_lines/__init__.py +0 -0
  79. exdrf_qt-0.1.17/exdrf_qt/controls/search_lines/base.py +404 -0
  80. exdrf_qt-0.1.17/exdrf_qt/controls/search_lines/model_settings.py +144 -0
  81. exdrf_qt-0.1.17/exdrf_qt/controls/search_lines/with_model.py +49 -0
  82. exdrf_qt-0.1.17/exdrf_qt/controls/seldb/__init__.py +0 -0
  83. exdrf_qt-0.1.17/exdrf_qt/controls/seldb/choose_db.py +37 -0
  84. exdrf_qt-0.1.17/exdrf_qt/controls/seldb/db_config_delegate.py +249 -0
  85. exdrf_qt-0.1.17/exdrf_qt/controls/seldb/db_config_model.py +222 -0
  86. exdrf_qt-0.1.17/exdrf_qt/controls/seldb/db_version_worker.py +197 -0
  87. exdrf_qt-0.1.17/exdrf_qt/controls/seldb/manage_model.py +1069 -0
  88. exdrf_qt-0.1.17/exdrf_qt/controls/seldb/sel_db.py +649 -0
  89. exdrf_qt-0.1.17/exdrf_qt/controls/seldb/sel_db_ui.py +255 -0
  90. exdrf_qt-0.1.17/exdrf_qt/controls/seldb/utils.py +27 -0
  91. exdrf_qt-0.1.17/exdrf_qt/controls/table_list.py +1092 -0
  92. exdrf_qt-0.1.17/exdrf_qt/controls/table_viewer/__init__.py +21 -0
  93. exdrf_qt-0.1.17/exdrf_qt/controls/table_viewer/column_filter_proxy.py +131 -0
  94. exdrf_qt-0.1.17/exdrf_qt/controls/table_viewer/column_visibility_dialog.py +132 -0
  95. exdrf_qt-0.1.17/exdrf_qt/controls/table_viewer/db_viewer.py +825 -0
  96. exdrf_qt-0.1.17/exdrf_qt/controls/table_viewer/sql_column_delegate.py +525 -0
  97. exdrf_qt-0.1.17/exdrf_qt/controls/table_viewer/sql_table_model.py +521 -0
  98. exdrf_qt-0.1.17/exdrf_qt/controls/table_viewer/table_view_ctx.py +60 -0
  99. exdrf_qt-0.1.17/exdrf_qt/controls/table_viewer/table_viewer.py +826 -0
  100. exdrf_qt-0.1.17/exdrf_qt/controls/table_viewer/viewer_plugin.py +65 -0
  101. exdrf_qt-0.1.17/exdrf_qt/controls/task_runner/__init__.py +42 -0
  102. exdrf_qt-0.1.17/exdrf_qt/controls/task_runner/blob_param.py +123 -0
  103. exdrf_qt-0.1.17/exdrf_qt/controls/task_runner/bool_param.py +72 -0
  104. exdrf_qt-0.1.17/exdrf_qt/controls/task_runner/date_param.py +134 -0
  105. exdrf_qt-0.1.17/exdrf_qt/controls/task_runner/datetime_param.py +144 -0
  106. exdrf_qt-0.1.17/exdrf_qt/controls/task_runner/duration_param.py +114 -0
  107. exdrf_qt-0.1.17/exdrf_qt/controls/task_runner/enum_param.py +99 -0
  108. exdrf_qt-0.1.17/exdrf_qt/controls/task_runner/float_list_param.py +178 -0
  109. exdrf_qt-0.1.17/exdrf_qt/controls/task_runner/float_param.py +162 -0
  110. exdrf_qt-0.1.17/exdrf_qt/controls/task_runner/formatted_param.py +83 -0
  111. exdrf_qt-0.1.17/exdrf_qt/controls/task_runner/int_list_param.py +176 -0
  112. exdrf_qt-0.1.17/exdrf_qt/controls/task_runner/int_param.py +147 -0
  113. exdrf_qt-0.1.17/exdrf_qt/controls/task_runner/param_widget.py +43 -0
  114. exdrf_qt-0.1.17/exdrf_qt/controls/task_runner/ref_many_param.py +92 -0
  115. exdrf_qt-0.1.17/exdrf_qt/controls/task_runner/ref_one_param.py +108 -0
  116. exdrf_qt-0.1.17/exdrf_qt/controls/task_runner/str_list_param.py +123 -0
  117. exdrf_qt-0.1.17/exdrf_qt/controls/task_runner/str_param.py +163 -0
  118. exdrf_qt-0.1.17/exdrf_qt/controls/task_runner/task_runner.py +373 -0
  119. exdrf_qt-0.1.17/exdrf_qt/controls/task_runner/task_runner_ui.py +162 -0
  120. exdrf_qt-0.1.17/exdrf_qt/controls/task_runner/time_param.py +126 -0
  121. exdrf_qt-0.1.17/exdrf_qt/controls/templ_viewer/__init__.py +0 -0
  122. exdrf_qt-0.1.17/exdrf_qt/controls/templ_viewer/add_var_dlg.py +465 -0
  123. exdrf_qt-0.1.17/exdrf_qt/controls/templ_viewer/assets/__init__.py +0 -0
  124. exdrf_qt-0.1.17/exdrf_qt/controls/templ_viewer/code_text_edit.py +513 -0
  125. exdrf_qt-0.1.17/exdrf_qt/controls/templ_viewer/delegate.py +590 -0
  126. exdrf_qt-0.1.17/exdrf_qt/controls/templ_viewer/header.py +109 -0
  127. exdrf_qt-0.1.17/exdrf_qt/controls/templ_viewer/html_to_docx/__init__.py +0 -0
  128. exdrf_qt-0.1.17/exdrf_qt/controls/templ_viewer/html_to_docx/main.py +1399 -0
  129. exdrf_qt-0.1.17/exdrf_qt/controls/templ_viewer/html_to_docx/screen_grabber.py +243 -0
  130. exdrf_qt-0.1.17/exdrf_qt/controls/templ_viewer/html_to_docx/tables.py +468 -0
  131. exdrf_qt-0.1.17/exdrf_qt/controls/templ_viewer/model.py +420 -0
  132. exdrf_qt-0.1.17/exdrf_qt/controls/templ_viewer/save_pdf_dlg.py +200 -0
  133. exdrf_qt-0.1.17/exdrf_qt/controls/templ_viewer/templ_viewer.py +2216 -0
  134. exdrf_qt-0.1.17/exdrf_qt/controls/templ_viewer/templ_viewer_ui.py +134 -0
  135. exdrf_qt-0.1.17/exdrf_qt/controls/templ_viewer/view_page.py +396 -0
  136. exdrf_qt-0.1.17/exdrf_qt/controls/templ_viewer/view_widget.py +163 -0
  137. exdrf_qt-0.1.17/exdrf_qt/controls/toast.py +426 -0
  138. exdrf_qt-0.1.17/exdrf_qt/controls/transfer/__init__.py +5 -0
  139. exdrf_qt-0.1.17/exdrf_qt/controls/transfer/numeric_sort_proxy.py +79 -0
  140. exdrf_qt-0.1.17/exdrf_qt/controls/transfer/tables_model.py +578 -0
  141. exdrf_qt-0.1.17/exdrf_qt/controls/transfer/tbl_row.py +19 -0
  142. exdrf_qt-0.1.17/exdrf_qt/controls/transfer/transfer_rows_worker.py +148 -0
  143. exdrf_qt-0.1.17/exdrf_qt/controls/transfer/transfer_selected_plugin.py +183 -0
  144. exdrf_qt-0.1.17/exdrf_qt/controls/transfer/transfer_widget.py +1239 -0
  145. exdrf_qt-0.1.17/exdrf_qt/controls/transfer/transfer_worker.py +207 -0
  146. exdrf_qt-0.1.17/exdrf_qt/controls/tree_header.py +695 -0
  147. exdrf_qt-0.1.17/exdrf_qt/controls/tree_list.py +102 -0
  148. exdrf_qt-0.1.17/exdrf_qt/field_ed/__init__.py +0 -0
  149. exdrf_qt-0.1.17/exdrf_qt/field_ed/api.py +12 -0
  150. exdrf_qt-0.1.17/exdrf_qt/field_ed/base.py +376 -0
  151. exdrf_qt-0.1.17/exdrf_qt/field_ed/base_date.py +196 -0
  152. exdrf_qt-0.1.17/exdrf_qt/field_ed/base_drop.py +80 -0
  153. exdrf_qt-0.1.17/exdrf_qt/field_ed/base_line.py +476 -0
  154. exdrf_qt-0.1.17/exdrf_qt/field_ed/base_number.py +228 -0
  155. exdrf_qt-0.1.17/exdrf_qt/field_ed/choices_mixin.py +95 -0
  156. exdrf_qt-0.1.17/exdrf_qt/field_ed/fed_blob.py +173 -0
  157. exdrf_qt-0.1.17/exdrf_qt/field_ed/fed_bool.py +172 -0
  158. exdrf_qt-0.1.17/exdrf_qt/field_ed/fed_date.py +101 -0
  159. exdrf_qt-0.1.17/exdrf_qt/field_ed/fed_dt.py +112 -0
  160. exdrf_qt-0.1.17/exdrf_qt/field_ed/fed_enum.py +440 -0
  161. exdrf_qt-0.1.17/exdrf_qt/field_ed/fed_int.py +77 -0
  162. exdrf_qt-0.1.17/exdrf_qt/field_ed/fed_m_text.py +230 -0
  163. exdrf_qt-0.1.17/exdrf_qt/field_ed/fed_real.py +111 -0
  164. exdrf_qt-0.1.17/exdrf_qt/field_ed/fed_related/__init__.py +5 -0
  165. exdrf_qt-0.1.17/exdrf_qt/field_ed/fed_related/base_adapter.py +62 -0
  166. exdrf_qt-0.1.17/exdrf_qt/field_ed/fed_related/bridge_adapter.py +297 -0
  167. exdrf_qt-0.1.17/exdrf_qt/field_ed/fed_related/fed_related.py +460 -0
  168. exdrf_qt-0.1.17/exdrf_qt/field_ed/fed_related/simple_adapter.py +179 -0
  169. exdrf_qt-0.1.17/exdrf_qt/field_ed/fed_s_text.py +162 -0
  170. exdrf_qt-0.1.17/exdrf_qt/field_ed/fed_sel_multi.py +289 -0
  171. exdrf_qt-0.1.17/exdrf_qt/field_ed/fed_sel_one.py +982 -0
  172. exdrf_qt-0.1.17/exdrf_qt/field_ed/fed_time.py +113 -0
  173. exdrf_qt-0.1.17/exdrf_qt/local_settings.py +456 -0
  174. exdrf_qt-0.1.17/exdrf_qt/menus.py +510 -0
  175. exdrf_qt-0.1.17/exdrf_qt/models/__init__.py +2 -0
  176. exdrf_qt-0.1.17/exdrf_qt/models/cache.py +212 -0
  177. exdrf_qt-0.1.17/exdrf_qt/models/fi_item.py +8 -0
  178. exdrf_qt-0.1.17/exdrf_qt/models/fi_op.py +49 -0
  179. exdrf_qt-0.1.17/exdrf_qt/models/field.py +476 -0
  180. exdrf_qt-0.1.17/exdrf_qt/models/field_list.py +338 -0
  181. exdrf_qt-0.1.17/exdrf_qt/models/fields.py +1516 -0
  182. exdrf_qt-0.1.17/exdrf_qt/models/model.py +2700 -0
  183. exdrf_qt-0.1.17/exdrf_qt/models/proxy.py +238 -0
  184. exdrf_qt-0.1.17/exdrf_qt/models/record.py +241 -0
  185. exdrf_qt-0.1.17/exdrf_qt/models/requests.py +186 -0
  186. exdrf_qt-0.1.17/exdrf_qt/models/selector.py +354 -0
  187. exdrf_qt-0.1.17/exdrf_qt/plugins.py +98 -0
  188. exdrf_qt-0.1.17/exdrf_qt/py.typed +0 -0
  189. exdrf_qt-0.1.17/exdrf_qt/scripts/__init__.py +0 -0
  190. exdrf_qt-0.1.17/exdrf_qt/scripts/gen_ui_file.py +503 -0
  191. exdrf_qt-0.1.17/exdrf_qt/utils/__init__.py +0 -0
  192. exdrf_qt-0.1.17/exdrf_qt/utils/attr_dict.py +109 -0
  193. exdrf_qt-0.1.17/exdrf_qt/utils/del_actions.py +108 -0
  194. exdrf_qt-0.1.17/exdrf_qt/utils/examine_threads.py +1483 -0
  195. exdrf_qt-0.1.17/exdrf_qt/utils/flt_acts.py +93 -0
  196. exdrf_qt-0.1.17/exdrf_qt/utils/html2docx.py +243 -0
  197. exdrf_qt-0.1.17/exdrf_qt/utils/native_threads.py +176 -0
  198. exdrf_qt-0.1.17/exdrf_qt/utils/plugins.py +135 -0
  199. exdrf_qt-0.1.17/exdrf_qt/utils/reload_module.py +867 -0
  200. exdrf_qt-0.1.17/exdrf_qt/utils/router.py +296 -0
  201. exdrf_qt-0.1.17/exdrf_qt/utils/screen_grabber.py +207 -0
  202. exdrf_qt-0.1.17/exdrf_qt/utils/search_actions.py +116 -0
  203. exdrf_qt-0.1.17/exdrf_qt/utils/stay_open_menu.py +76 -0
  204. exdrf_qt-0.1.17/exdrf_qt/utils/t_collector.py +59 -0
  205. exdrf_qt-0.1.17/exdrf_qt/utils/tlh.py +141 -0
  206. exdrf_qt-0.1.17/exdrf_qt/worker.py +615 -0
  207. exdrf_qt-0.1.17/exdrf_qt.egg-info/PKG-INFO +74 -0
  208. exdrf_qt-0.1.17/exdrf_qt.egg-info/SOURCES.txt +242 -0
  209. exdrf_qt-0.1.17/exdrf_qt.egg-info/dependency_links.txt +1 -0
  210. exdrf_qt-0.1.17/exdrf_qt.egg-info/requires.txt +36 -0
  211. exdrf_qt-0.1.17/exdrf_qt.egg-info/top_level.txt +3 -0
  212. exdrf_qt-0.1.17/exdrf_qt_tests/__init__.py +0 -0
  213. exdrf_qt-0.1.17/exdrf_qt_tests/comparator/conftest.py +108 -0
  214. exdrf_qt-0.1.17/exdrf_qt_tests/comparator/test_adapter.py +10 -0
  215. exdrf_qt-0.1.17/exdrf_qt_tests/comparator/test_manager.py +49 -0
  216. exdrf_qt-0.1.17/exdrf_qt_tests/comparator/test_merge_delegate_test.py +66 -0
  217. exdrf_qt-0.1.17/exdrf_qt_tests/comparator/test_merge_logic_test.py +137 -0
  218. exdrf_qt-0.1.17/exdrf_qt_tests/comparator/test_nodes.py +51 -0
  219. exdrf_qt-0.1.17/exdrf_qt_tests/comparator/test_tree_model.py +138 -0
  220. exdrf_qt-0.1.17/exdrf_qt_tests/comparator/test_tree_view.py +79 -0
  221. exdrf_qt-0.1.17/exdrf_qt_tests/comparator/test_webview.py +119 -0
  222. exdrf_qt-0.1.17/exdrf_qt_tests/conftests.py +46 -0
  223. exdrf_qt-0.1.17/exdrf_qt_tests/controls/conftest.py +30 -0
  224. exdrf_qt-0.1.17/exdrf_qt_tests/controls/test_record_cmp_base_test.py +106 -0
  225. exdrf_qt-0.1.17/exdrf_qt_tests/models/__init__.py +0 -0
  226. exdrf_qt-0.1.17/exdrf_qt_tests/models/model/__init__.py +0 -0
  227. exdrf_qt-0.1.17/exdrf_qt_tests/models/model/test_trim_request.py +255 -0
  228. exdrf_qt-0.1.17/exdrf_qt_tests/models/test_cache.py +354 -0
  229. exdrf_qt-0.1.17/exdrf_qt_tests/models/test_fi_item.py +72 -0
  230. exdrf_qt-0.1.17/exdrf_qt_tests/models/test_fi_item_shim.py +17 -0
  231. exdrf_qt-0.1.17/exdrf_qt_tests/models/test_fi_op.py +352 -0
  232. exdrf_qt-0.1.17/exdrf_qt_tests/models/test_fi_op_shim.py +14 -0
  233. exdrf_qt-0.1.17/exdrf_qt_tests/models/test_field.py +281 -0
  234. exdrf_qt-0.1.17/exdrf_qt_tests/models/test_field_list.py +405 -0
  235. exdrf_qt-0.1.17/exdrf_qt_tests/models/test_model.py +500 -0
  236. exdrf_qt-0.1.17/exdrf_qt_tests/models/test_proxy.py +369 -0
  237. exdrf_qt-0.1.17/exdrf_qt_tests/models/test_record.py +430 -0
  238. exdrf_qt-0.1.17/exdrf_qt_tests/models/test_requests.py +313 -0
  239. exdrf_qt-0.1.17/exdrf_qt_tests/models/test_selector.py +500 -0
  240. exdrf_qt-0.1.17/exdrf_qt_tests/test_local_settings.py +139 -0
  241. exdrf_qt-0.1.17/exdrf_qt_tests/test_worker.py +107 -0
  242. exdrf_qt-0.1.17/pyproject.toml +95 -0
  243. exdrf_qt-0.1.17/setup.cfg +4 -0
  244. exdrf_qt-0.1.17/setup.py +6 -0
@@ -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