ripple-down-rules 0.5.71__tar.gz → 0.5.80__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 (152) hide show
  1. {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/PKG-INFO +1 -1
  2. {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/src/ripple_down_rules/__init__.py +1 -1
  3. {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/src/ripple_down_rules/datastructures/dataclasses.py +3 -3
  4. {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/src/ripple_down_rules/rdr.py +148 -106
  5. {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/src/ripple_down_rules/rules.py +15 -9
  6. {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/src/ripple_down_rules/user_interface/template_file_creator.py +1 -1
  7. {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/src/ripple_down_rules/utils.py +85 -16
  8. {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/src/ripple_down_rules.egg-info/PKG-INFO +1 -1
  9. {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/src/ripple_down_rules.egg-info/SOURCES.txt +1 -0
  10. {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/test/test_results/datasets_physical_object_is_a_robot/physical_object_is_a_robot_output__scrdr_defs.py +1 -2
  11. {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/test/test_results/datasets_physical_object_is_a_robot/physical_object_is_a_robot_rdr.py +2 -4
  12. {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/test/test_results/datasets_physical_object_is_a_robot/rdr_metadata/datasets_physical_object_is_a_robot.json +4 -4
  13. {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/test/test_results/datasets_physical_object_select_objects_that_are_parts_of_robot/physical_object_select_objects_that_are_parts_of_robot_output__mcrdr.py +2 -2
  14. {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/test/test_results/datasets_physical_object_select_objects_that_are_parts_of_robot/physical_object_select_objects_that_are_parts_of_robot_output__mcrdr_defs.py +2 -6
  15. {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/test/test_results/datasets_physical_object_select_objects_that_are_parts_of_robot/physical_object_select_objects_that_are_parts_of_robot_rdr.py +2 -4
  16. {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/test/test_results/datasets_physical_object_select_objects_that_are_parts_of_robot/rdr_metadata/datasets_physical_object_select_objects_that_are_parts_of_robot.json +6 -6
  17. ripple_down_rules-0.5.80/test/test_user_interface/__init__.py +0 -0
  18. ripple_down_rules-0.5.80/test/test_utils.py +78 -0
  19. ripple_down_rules-0.5.71/test/test_utils.py +0 -38
  20. {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/.github/workflows/build_and_deploy_doc.yml +0 -0
  21. {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/.github/workflows/ci.yml +0 -0
  22. {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/.github/workflows/publish-to-test-pypi.yml +0 -0
  23. {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/.idea/shelf/Uncommitted_changes_before_Checkout_at_2_4_25,_6_32_PM_[Changes]/shelved.patch +0 -0
  24. {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/.idea/shelf/Uncommitted_changes_before_Checkout_at_2_4_25,_6_32_PM_[Changes]1/shelved.patch +0 -0
  25. {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/LICENSE +0 -0
  26. {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/README.md +0 -0
  27. {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/doc/_config.yml +0 -0
  28. {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/doc/_toc.yml +0 -0
  29. {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/doc/bibliography.md +0 -0
  30. {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/doc/intro.md +0 -0
  31. {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/doc/references.bib +0 -0
  32. {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/doc/requirements.txt +0 -0
  33. {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/examples/__init__.py +0 -0
  34. {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/examples/animal_species.py +0 -0
  35. {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/examples/part_containment_rdr/__init__.py +0 -0
  36. {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/examples/part_containment_rdr/rdr_metadata/part_containment_rdr.json +0 -0
  37. {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/examples/part_containment_rdr/robot_contained_objects_mcrdr.py +0 -0
  38. {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/examples/part_containment_rdr/robot_contained_objects_mcrdr_defs.py +0 -0
  39. {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/examples/part_containment_rdr/robot_rdr.py +0 -0
  40. {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/examples/relational_example.py +0 -0
  41. {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/images/scrdr.dot +0 -0
  42. {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/images/scrdr.png +0 -0
  43. {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/images/thinking_pr2.jpg +0 -0
  44. {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/pyproject.toml +0 -0
  45. {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/pytest.ini +0 -0
  46. {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/requirements-dev-ci.txt +0 -0
  47. {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/requirements-dev.txt +0 -0
  48. {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/requirements-gui.txt +0 -0
  49. {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/requirements-viz.txt +0 -0
  50. {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/requirements.txt +0 -0
  51. {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/results/complete_mcrdr_extra.dot +0 -0
  52. {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/results/complete_mcrdr_extra.png +0 -0
  53. {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/results/complete_mcrdr_stop_only.dot +0 -0
  54. {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/results/complete_mcrdr_stop_only.png +0 -0
  55. {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/results/complete_mcrdr_stop_plus_rule.dot +0 -0
  56. {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/results/complete_mcrdr_stop_plus_rule.png +0 -0
  57. {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/results/complete_scrdr.dot +0 -0
  58. {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/results/complete_scrdr.png +0 -0
  59. {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/results/complete_scrdr_2.dot +0 -0
  60. {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/results/complete_scrdr_2.png +0 -0
  61. {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/results/complete_scrdr_3.dot +0 -0
  62. {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/results/complete_scrdr_3.png +0 -0
  63. {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/results/grdr_Habitat.dot +0 -0
  64. {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/results/grdr_Habitat.png +0 -0
  65. {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/results/grdr_Species.dot +0 -0
  66. {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/results/grdr_Species.png +0 -0
  67. {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/results/mcrdr_extra.dot +0 -0
  68. {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/results/mcrdr_extra.png +0 -0
  69. {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/results/mcrdr_extra_classify.dot +0 -0
  70. {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/results/mcrdr_extra_classify.png +0 -0
  71. {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/results/mcrdr_stop_plus_rule_combined.dot +0 -0
  72. {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/results/mcrdr_stop_plus_rule_combined.png +0 -0
  73. {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/results/partial_mcrdr_extra.dot +0 -0
  74. {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/results/partial_mcrdr_extra.png +0 -0
  75. {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/results/relational_scrdr_classify.dot +0 -0
  76. {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/results/relational_scrdr_classify.png +0 -0
  77. {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/setup.cfg +0 -0
  78. {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/src/ripple_down_rules/datastructures/__init__.py +0 -0
  79. {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/src/ripple_down_rules/datastructures/callable_expression.py +0 -0
  80. {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/src/ripple_down_rules/datastructures/case.py +0 -0
  81. {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/src/ripple_down_rules/datastructures/enums.py +0 -0
  82. {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/src/ripple_down_rules/experts.py +0 -0
  83. {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/src/ripple_down_rules/helpers.py +0 -0
  84. {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/src/ripple_down_rules/rdr_decorators.py +0 -0
  85. {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/src/ripple_down_rules/start-code-server.sh +0 -0
  86. {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/src/ripple_down_rules/user_interface/__init__.py +0 -0
  87. {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/src/ripple_down_rules/user_interface/gui.py +0 -0
  88. {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/src/ripple_down_rules/user_interface/ipython_custom_shell.py +0 -0
  89. {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/src/ripple_down_rules/user_interface/object_diagram.py +0 -0
  90. {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/src/ripple_down_rules/user_interface/prompt.py +0 -0
  91. {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/src/ripple_down_rules.egg-info/dependency_links.txt +0 -0
  92. {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/src/ripple_down_rules.egg-info/requires.txt +0 -0
  93. {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/src/ripple_down_rules.egg-info/top_level.txt +0 -0
  94. {ripple_down_rules-0.5.71/test/conf → ripple_down_rules-0.5.80/test}/__init__.py +0 -0
  95. {ripple_down_rules-0.5.71/test/conf/world → ripple_down_rules-0.5.80/test/conf}/__init__.py +0 -0
  96. {ripple_down_rules-0.5.71/test/factories → ripple_down_rules-0.5.80/test/conf/world}/__init__.py +0 -0
  97. {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/test/conf/world/base_config.py +0 -0
  98. {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/test/conf/world/handles_and_containers.py +0 -0
  99. {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/test/conftest.py +0 -0
  100. {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/test/datasets.py +0 -0
  101. {ripple_down_rules-0.5.71/test/factories/world → ripple_down_rules-0.5.80/test/factories}/__init__.py +0 -0
  102. {ripple_down_rules-0.5.71/test/test_helpers → ripple_down_rules-0.5.80/test/factories/world}/__init__.py +0 -0
  103. {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/test/factories/world/handles_and_containers.py +0 -0
  104. {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/test/test_expert_answers/correct_drawer_rdr_expert_answers_fit.json +0 -0
  105. {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/test/test_expert_answers/drawer_cabinet_expert_answers_fit.json +0 -0
  106. {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/test/test_expert_answers/grdr_expert_answers_classify.json +0 -0
  107. {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/test/test_expert_answers/grdr_expert_answers_fit.json +0 -0
  108. {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/test/test_expert_answers/grdr_expert_answers_fit_extra.json +0 -0
  109. {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/test/test_expert_answers/grdr_expert_answers_fit_no_targets.json +0 -0
  110. {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/test/test_expert_answers/mcrdr_expert_answers_classify.json +0 -0
  111. {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/test/test_expert_answers/mcrdr_expert_answers_fit_no_targets.json +0 -0
  112. {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/test/test_expert_answers/mcrdr_expert_answers_stop_only_fit.json +0 -0
  113. {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/test/test_expert_answers/mcrdr_extra_expert_answers_classify.json +0 -0
  114. {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/test/test_expert_answers/mcrdr_extra_expert_answers_fit.json +0 -0
  115. {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/test/test_expert_answers/mcrdr_multi_line_expert_answers_fit.json +0 -0
  116. {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/test/test_expert_answers/mcrdr_stop_only_answers_fit.json +0 -0
  117. {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/test/test_expert_answers/mcrdr_stop_plus_rule_answers_fit.json +0 -0
  118. {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/test/test_expert_answers/mcrdr_stop_plus_rule_combined_expert_answers_fit.json +0 -0
  119. {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/test/test_expert_answers/mcrdr_stop_plus_rule_expert_answers_fit.json +0 -0
  120. {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/test/test_expert_answers/mutagenic_expert_answers.json +0 -0
  121. {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/test/test_expert_answers/relational_scrdr_expert_answers_classify.json +0 -0
  122. {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/test/test_expert_answers/scrdr_expert_answers_classify.json +0 -0
  123. {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/test/test_expert_answers/scrdr_expert_answers_fit.json +0 -0
  124. {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/test/test_expert_answers/scrdr_expert_answers_fit_no_targets.json +0 -0
  125. {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/test/test_expert_answers/scrdr_multi_line_expert_answers_fit.json +0 -0
  126. {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/test/test_expert_answers/scrdr_world_expert_answers_fit.json +0 -0
  127. {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/test/test_generated_rdrs/__init__.py +0 -0
  128. {ripple_down_rules-0.5.71/test/test_rdr_world → ripple_down_rules-0.5.80/test/test_helpers}/__init__.py +0 -0
  129. {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/test/test_helpers/helpers.py +0 -0
  130. {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/test/test_helpers/object_diagram_case_query.png +0 -0
  131. {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/test/test_helpers/object_diagram_person.png +0 -0
  132. {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/test/test_json_serialization.py +0 -0
  133. {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/test/test_object_diagram.py +0 -0
  134. {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/test/test_on_mutagenic.py +0 -0
  135. {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/test/test_rdr.py +0 -0
  136. {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/test/test_rdr_alchemy.py +0 -0
  137. {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/test/test_rdr_decorators.py +0 -0
  138. {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/test/test_rdr_helpers_rdrs.py +0 -0
  139. {ripple_down_rules-0.5.71/test/test_user_interface → ripple_down_rules-0.5.80/test/test_rdr_world}/__init__.py +0 -0
  140. {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/test/test_rdr_world/conftest.py +0 -0
  141. {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/test/test_rdr_world/test_rdr_world.py +0 -0
  142. {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/test/test_relational_rdr.py +0 -0
  143. {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/test/test_relational_rdr_alchemy.py +0 -0
  144. {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/test/test_results/datasets_physical_object_is_a_robot/__init__.py +0 -0
  145. {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/test/test_results/datasets_physical_object_is_a_robot/physical_object_is_a_robot_output__scrdr.py +0 -0
  146. {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/test/test_results/datasets_physical_object_select_objects_that_are_parts_of_robot/__init__.py +0 -0
  147. {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/test/test_sql_model.py +0 -0
  148. {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/test/test_template_file_creator.py +0 -0
  149. {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/test/test_user_interface/test_ipython.py +0 -0
  150. {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/test/test_user_interface/test_ipython_copilot.py +0 -0
  151. {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/test/test_user_interface/test_prompt.py +0 -0
  152. {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/test/test_user_interface/test_qt_gui_inline.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ripple_down_rules
3
- Version: 0.5.71
3
+ Version: 0.5.80
4
4
  Summary: Implements the various versions of Ripple Down Rules (RDR) for knowledge representation and reasoning.
5
5
  Author-email: Abdelrhman Bassiouny <abassiou@uni-bremen.de>
6
6
  License: GNU GENERAL PUBLIC LICENSE
@@ -1,4 +1,4 @@
1
- __version__ = "0.5.71"
1
+ __version__ = "0.5.80"
2
2
 
3
3
  import logging
4
4
  logger = logging.Logger("rdr")
@@ -9,7 +9,7 @@ from omegaconf import MISSING
9
9
  from sqlalchemy.orm import DeclarativeBase as SQLTable
10
10
  from typing_extensions import Any, Optional, Dict, Type, Tuple, Union, List, get_origin, Set, Callable
11
11
 
12
- from ..utils import get_method_name, get_function_import_path_and_representation
12
+ from ..utils import get_method_name, get_function_import_data, get_function_representation
13
13
  from .callable_expression import CallableExpression
14
14
  from .case import create_case, Case
15
15
  from ..utils import copy_case, make_list, make_set, get_origin_and_args_from_type_hint, get_value_type_from_type_hint, \
@@ -274,9 +274,9 @@ class CaseFactoryMetaData:
274
274
  factory_method_repr = None
275
275
  scenario_repr = None
276
276
  if self.factory_method is not None:
277
- _, factory_method_repr = get_function_import_path_and_representation(self.factory_method)
277
+ factory_method_repr = get_function_representation(self.factory_method)
278
278
  if self.scenario is not None:
279
- _, scenario_repr = get_function_import_path_and_representation(self.scenario)
279
+ scenario_repr = get_function_representation(self.scenario)
280
280
  return (f"CaseFactoryMetaData("
281
281
  f"factory_method={factory_method_repr}, "
282
282
  f"factory_idx={self.factory_idx}, "
@@ -1,20 +1,16 @@
1
1
  from __future__ import annotations
2
2
 
3
- import copyreg
4
3
  import importlib
5
4
  import os
5
+ from abc import ABC, abstractmethod
6
+ from copy import copy
6
7
 
7
8
  from ripple_down_rules.datastructures.dataclasses import CaseFactoryMetaData
8
-
9
9
  from . import logger
10
- import sys
11
- from abc import ABC, abstractmethod
12
- from copy import copy
13
- from io import TextIOWrapper
14
- from types import ModuleType
15
10
 
16
11
  try:
17
12
  from matplotlib import pyplot as plt
13
+
18
14
  Figure = plt.Figure
19
15
  except ImportError as e:
20
16
  logger.debug(f"{e}: matplotlib is not installed")
@@ -32,14 +28,14 @@ from .datastructures.enums import MCRDRMode
32
28
  from .experts import Expert, Human
33
29
  from .helpers import is_matching, general_rdr_classify
34
30
  from .rules import Rule, SingleClassRule, MultiClassTopRule, MultiClassStopRule
31
+
35
32
  try:
36
33
  from .user_interface.gui import RDRCaseViewer
37
34
  except ImportError as e:
38
35
  RDRCaseViewer = None
39
- from .utils import draw_tree, make_set, copy_case, \
40
- SubclassJSONSerializer, make_list, get_type_from_string, \
41
- is_conflicting, get_imports_from_scope, extract_function_source, extract_imports, get_full_class_name, \
42
- is_iterable, str_to_snake_case, get_import_path_from_path
36
+ from .utils import draw_tree, make_set, SubclassJSONSerializer, make_list, get_type_from_string, \
37
+ is_conflicting, extract_function_source, extract_imports, get_full_class_name, \
38
+ is_iterable, str_to_snake_case, get_import_path_from_path, get_imports_from_types
43
39
 
44
40
 
45
41
  class RippleDownRules(SubclassJSONSerializer, ABC):
@@ -98,13 +94,15 @@ class RippleDownRules(SubclassJSONSerializer, ABC):
98
94
  if self.viewer is not None:
99
95
  self.viewer.set_save_function(self.save)
100
96
 
101
- def save(self, save_dir: Optional[str] = None, model_name: Optional[str] = None) -> str:
97
+ def save(self, save_dir: Optional[str] = None, model_name: Optional[str] = None,
98
+ package_name: Optional[str] = None) -> str:
102
99
  """
103
100
  Save the classifier to a file.
104
101
 
105
102
  :param save_dir: The directory to save the classifier to.
106
103
  :param model_name: The name of the model to save. If None, a default name is generated.
107
- :param postfix: The postfix to add to the file name.
104
+ :param package_name: The name of the package that contains the RDR classifier function, this
105
+ is required in case of relative imports in the generated python file.
108
106
  :return: The name of the saved model.
109
107
  """
110
108
  save_dir = save_dir or self.save_dir
@@ -124,22 +122,25 @@ class RippleDownRules(SubclassJSONSerializer, ABC):
124
122
  json_dir = os.path.join(model_dir, self.metadata_folder)
125
123
  os.makedirs(json_dir, exist_ok=True)
126
124
  self.to_json_file(os.path.join(json_dir, self.model_name))
127
- self._write_to_python(model_dir)
125
+ self._write_to_python(model_dir, package_name=package_name)
128
126
  return self.model_name
129
127
 
130
128
  @classmethod
131
- def load(cls, load_dir: str, model_name: str) -> Self:
129
+ def load(cls, load_dir: str, model_name: str,
130
+ package_name: Optional[str] = None) -> Self:
132
131
  """
133
132
  Load the classifier from a file.
134
133
 
135
134
  :param load_dir: The path to the model directory to load the classifier from.
136
135
  :param model_name: The name of the model to load.
136
+ :param package_name: The name of the package that contains the RDR classifier function, this
137
+ is required in case of relative imports in the generated python file.
137
138
  """
138
139
  model_dir = os.path.join(load_dir, model_name)
139
140
  json_file = os.path.join(model_dir, cls.metadata_folder, model_name)
140
141
  rdr = cls.from_json_file(json_file)
141
142
  try:
142
- rdr.update_from_python(model_dir)
143
+ rdr.update_from_python(model_dir, package_name=package_name)
143
144
  except (FileNotFoundError, ValueError) as e:
144
145
  logger.warning(f"Could not load the python file for the model {model_name} from {model_dir}. "
145
146
  f"Make sure the file exists and is valid.")
@@ -148,11 +149,13 @@ class RippleDownRules(SubclassJSONSerializer, ABC):
148
149
  return rdr
149
150
 
150
151
  @abstractmethod
151
- def _write_to_python(self, model_dir: str):
152
+ def _write_to_python(self, model_dir: str, package_name: Optional[str] = None):
152
153
  """
153
154
  Write the tree of rules as source code to a file.
154
155
 
155
156
  :param model_dir: The path to the directory to write the source code to.
157
+ :param package_name: The name of the package that contains the RDR classifier function, this
158
+ is required in case of relative imports in the generated python file.
156
159
  """
157
160
  pass
158
161
 
@@ -373,11 +376,13 @@ class RippleDownRules(SubclassJSONSerializer, ABC):
373
376
  pass
374
377
 
375
378
  @abstractmethod
376
- def update_from_python(self, model_dir: str):
379
+ def update_from_python(self, model_dir: str, package_name: Optional[str] = None):
377
380
  """
378
381
  Update the rules from the generated python file, that might have been modified by the user.
379
382
 
380
383
  :param model_dir: The directory where the generated python file is located.
384
+ :param package_name: The name of the package that contains the RDR classifier function, this
385
+ is required in case of relative imports in the generated python file.
381
386
  """
382
387
  pass
383
388
 
@@ -401,47 +406,51 @@ class RippleDownRules(SubclassJSONSerializer, ABC):
401
406
  # remove from imports if exists first
402
407
  package_name = get_import_path_from_path(package_name)
403
408
  name = f"{package_name}.{self.generated_python_file_name}" if package_name else self.generated_python_file_name
404
- try:
405
- module = importlib.import_module(name)
406
- del sys.modules[name]
407
- except ModuleNotFoundError:
408
- pass
409
- return importlib.import_module(name).classify
409
+ module = importlib.import_module(name)
410
+ importlib.reload(module)
411
+ return module.classify
410
412
 
411
413
 
412
414
  class RDRWithCodeWriter(RippleDownRules, ABC):
413
415
 
414
- def update_from_python(self, model_dir: str):
416
+ def update_from_python(self, model_dir: str, package_name: Optional[str] = None):
415
417
  """
416
418
  Update the rules from the generated python file, that might have been modified by the user.
417
419
 
418
420
  :param model_dir: The directory where the generated python file is located.
421
+ :param package_name: The name of the package that contains the RDR classifier function, this
422
+ is required in case of relative imports in the generated python file.
419
423
  """
420
- rules_dict = {r.uid: r for r in [self.start_rule] + list(self.start_rule.descendants) if r.conditions is not None}
424
+ rules_dict = {r.uid: r for r in [self.start_rule] + list(self.start_rule.descendants)
425
+ if r.conditions is not None}
421
426
  condition_func_names = [f'conditions_{rid}' for rid in rules_dict.keys()]
422
- conclusion_func_names = [f'conclusion_{rid}' for rid in rules_dict.keys() if not isinstance(rules_dict[rid], MultiClassStopRule)]
427
+ conclusion_func_names = [f'conclusion_{rid}' for rid in rules_dict.keys()
428
+ if not isinstance(rules_dict[rid], MultiClassStopRule)]
423
429
  all_func_names = condition_func_names + conclusion_func_names
424
430
  filepath = f"{model_dir}/{self.generated_python_defs_file_name}.py"
425
431
  cases_path = f"{model_dir}/{self.generated_python_cases_file_name}.py"
426
432
  cases_import_path = get_import_path_from_path(model_dir)
427
- cases_import_path = f"{cases_import_path}.{self.generated_python_cases_file_name}" if cases_import_path\
433
+ cases_import_path = f"{cases_import_path}.{self.generated_python_cases_file_name}" if cases_import_path \
428
434
  else self.generated_python_cases_file_name
429
435
  functions_source = extract_function_source(filepath, all_func_names, include_signature=False)
430
436
  # get the scope from the imports in the file
431
- scope = extract_imports(filepath)
437
+ scope = extract_imports(filepath, package_name=package_name)
432
438
  for rule in [self.start_rule] + list(self.start_rule.descendants):
433
439
  if rule.conditions is not None:
434
440
  rule.conditions.user_input = functions_source[f"conditions_{rule.uid}"]
435
441
  rule.conditions.scope = scope
436
442
  if os.path.exists(cases_path):
437
- rule.corner_case_metadata = importlib.import_module(cases_import_path).__dict__.get(f"corner_case_{rule.uid}", None)
443
+ module = importlib.import_module(cases_import_path, package=package_name)
444
+ importlib.reload(module)
445
+ rule.corner_case_metadata = module.__dict__.get(f"corner_case_{rule.uid}", None)
438
446
  if rule.conclusion is not None and not isinstance(rule, MultiClassStopRule):
439
447
  rule.conclusion.user_input = functions_source[f"conclusion_{rule.uid}"]
440
448
  rule.conclusion.scope = scope
441
449
 
442
450
  @abstractmethod
443
451
  def write_rules_as_source_code_to_file(self, rule: Rule, file, parent_indent: str = "",
444
- defs_file: Optional[str] = None, cases_file: Optional[str] = None):
452
+ defs_file: Optional[str] = None, cases_file: Optional[str] = None,
453
+ package_name: Optional[str] = None):
445
454
  """
446
455
  Write the rules as source code to a file.
447
456
 
@@ -450,40 +459,62 @@ class RDRWithCodeWriter(RippleDownRules, ABC):
450
459
  :param parent_indent: The indentation of the parent rule.
451
460
  :param defs_file: The file to write the definitions to.
452
461
  :param cases_file: The file to write the cases to.
462
+ :param package_name: The name of the package that contains the RDR classifier function, this
463
+ is required in case of relative imports in the generated python file.
453
464
  """
454
465
  pass
455
466
 
456
- def _write_to_python(self, model_dir: str):
467
+ def _write_to_python(self, model_dir: str, package_name: Optional[str] = None):
457
468
  """
458
469
  Write the tree of rules as source code to a file.
459
470
 
460
471
  :param model_dir: The path to the directory to write the source code to.
472
+ :param package_name: The name of the package that contains the RDR classifier function, this
473
+ is required in case of relative imports in the generated python file.
461
474
  """
475
+ # Make sure the model directory exists and create an __init__.py file if it doesn't exist
462
476
  os.makedirs(model_dir, exist_ok=True)
463
477
  if not os.path.exists(model_dir + '/__init__.py'):
464
478
  with open(model_dir + '/__init__.py', 'w') as f:
465
479
  f.write("from . import *\n")
466
- func_def = f"def classify(case: {self.case_type.__name__}, **kwargs) -> {self.conclusion_type_hint}:\n"
480
+
481
+ # Set the file names for the generated python files
467
482
  file_name = model_dir + f"/{self.generated_python_file_name}.py"
468
483
  defs_file_name = model_dir + f"/{self.generated_python_defs_file_name}.py"
469
484
  cases_file_name = model_dir + f"/{self.generated_python_cases_file_name}.py"
470
- imports, defs_imports = self._get_imports()
471
- # clear the files first
485
+
486
+ # Get the required imports for the main file and the defs file
487
+ main_types, defs_types, corner_cases_types = self._get_types_to_import()
488
+ imports = get_imports_from_types(main_types, file_name, package_name)
489
+ defs_imports = get_imports_from_types(defs_types, defs_file_name, package_name)
490
+ corner_cases_imports = get_imports_from_types(corner_cases_types, cases_file_name, package_name)
491
+
492
+ # Add the imports to the defs file
472
493
  with open(defs_file_name, "w") as f:
473
- f.write(defs_imports + "\n\n")
494
+ f.write('\n'.join(defs_imports) + "\n\n\n")
495
+
496
+ # Add the imports to the cases file
497
+ case_factory_import = get_imports_from_types([CaseFactoryMetaData], cases_file_name, package_name)
498
+ corner_cases_imports.extend(case_factory_import)
474
499
  with open(cases_file_name, "w") as cases_f:
475
500
  cases_f.write("# This file contains the corner cases for the rules.\n")
501
+ cases_f.write('\n'.join(corner_cases_imports) + "\n\n\n")
502
+
503
+ # Add the imports, the attributes, and the function definition to the main file
504
+ func_def = f"def classify(case: {self.case_type.__name__}, **kwargs) -> {self.conclusion_type_hint}:\n"
476
505
  with open(file_name, "w") as f:
477
- imports += f"from .{self.generated_python_defs_file_name} import *\n"
478
- f.write(imports + "\n\n")
506
+ imports.append(f"from .{self.generated_python_defs_file_name} import *")
507
+ f.write('\n'.join(imports) + "\n\n\n")
479
508
  f.write(f"attribute_name = '{self.attribute_name}'\n")
480
509
  f.write(f"conclusion_type = ({', '.join([ct.__name__ for ct in self.conclusion_type])},)\n")
481
510
  f.write(f"mutually_exclusive = {self.mutually_exclusive}\n")
482
511
  f.write(f"\n\n{func_def}")
483
512
  f.write(f"{' ' * 4}if not isinstance(case, Case):\n"
484
513
  f"{' ' * 4} case = create_case(case, max_recursion_idx=3)\n""")
485
- self.write_rules_as_source_code_to_file(self.start_rule, f, " " * 4, defs_file=defs_file_name,
486
- cases_file=cases_file_name)
514
+
515
+ # Write the rules as source code to the main file
516
+ self.write_rules_as_source_code_to_file(self.start_rule, file_name, " " * 4, defs_file=defs_file_name,
517
+ cases_file=cases_file_name, package_name=package_name)
487
518
 
488
519
  @property
489
520
  @abstractmethod
@@ -493,31 +524,27 @@ class RDRWithCodeWriter(RippleDownRules, ABC):
493
524
  """
494
525
  pass
495
526
 
496
- def _get_imports(self) -> Tuple[str, str]:
527
+ def _get_types_to_import(self) -> Tuple[Set[Type], Set[Type], Set[Type]]:
497
528
  """
498
- :return: The imports for the generated python file of the RDR as a string.
529
+ :return: The types of the main, defs, and corner cases files of the RDR classifier that will be imported.
499
530
  """
500
- defs_imports_list = []
531
+ defs_types = set()
532
+ cases_types = set()
501
533
  for rule in [self.start_rule] + list(self.start_rule.descendants):
502
534
  if not rule.conditions:
503
535
  continue
504
536
  for scope in [rule.conditions.scope, rule.conclusion.scope]:
505
537
  if scope is None:
506
538
  continue
507
- defs_imports_list.extend(get_imports_from_scope(scope))
508
- if self.case_type.__module__ != "builtins":
509
- defs_imports_list.append(f"from {self.case_type.__module__} import {self.case_type.__name__}")
510
- defs_imports = "\n".join(set(defs_imports_list)) + "\n"
511
- imports = []
512
- if self.case_type.__module__ != "builtins":
513
- imports.append(f"from {self.case_type.__module__} import {self.case_type.__name__}")
514
- for conclusion_type in self.conclusion_type:
515
- if conclusion_type.__module__ != "builtins":
516
- imports.append(f"from {conclusion_type.__module__} import {conclusion_type.__name__}")
517
- imports.append("from ripple_down_rules.datastructures.case import Case, create_case")
518
- imports = set(imports).difference(defs_imports_list)
519
- imports = "\n".join(imports) + "\n"
520
- return imports, defs_imports
539
+ defs_types.update(make_set(scope.values()))
540
+ cases_types.update(rule.get_corner_case_types_to_import())
541
+ defs_types.add(self.case_type)
542
+ main_types = set()
543
+ main_types.add(self.case_type)
544
+ main_types.update(make_set(self.conclusion_type))
545
+ main_types.update({Case, create_case})
546
+ main_types = main_types.difference(defs_types)
547
+ return main_types, defs_types, cases_types
521
548
 
522
549
  @property
523
550
  def _default_generated_python_file_name(self) -> Optional[str]:
@@ -536,7 +563,6 @@ class RDRWithCodeWriter(RippleDownRules, ABC):
536
563
  def generated_python_cases_file_name(self) -> str:
537
564
  return f"{self.generated_python_file_name}_cases"
538
565
 
539
-
540
566
  @property
541
567
  def conclusion_type(self) -> Tuple[Type]:
542
568
  """
@@ -588,7 +614,6 @@ class RDRWithCodeWriter(RippleDownRules, ABC):
588
614
 
589
615
 
590
616
  class SingleClassRDR(RDRWithCodeWriter):
591
-
592
617
  mutually_exclusive: bool = True
593
618
  """
594
619
  The output of the classification of this rdr negates all other possible outputs, there can only be one true value.
@@ -645,7 +670,7 @@ class SingleClassRDR(RDRWithCodeWriter):
645
670
  pred = self.evaluate(case)
646
671
  conclusion = pred.conclusion(case) if pred is not None else None
647
672
  if pred is not None and pred.fired and case_query is not None:
648
- if pred.corner_case_metadata is None and conclusion is not None\
673
+ if pred.corner_case_metadata is None and conclusion is not None \
649
674
  and type(conclusion) in case_query.core_attribute_type:
650
675
  pred.corner_case_metadata = CaseFactoryMetaData.from_case_query(case_query)
651
676
  return conclusion if pred is not None and pred.fired else self.default_conclusion
@@ -657,31 +682,35 @@ class SingleClassRDR(RDRWithCodeWriter):
657
682
  matched_rule = self.start_rule(case) if self.start_rule is not None else None
658
683
  return matched_rule if matched_rule is not None else self.start_rule
659
684
 
660
- def _write_to_python(self, model_dir: str):
661
- super()._write_to_python(model_dir)
685
+ def _write_to_python(self, model_dir: str, package_name: Optional[str] = None):
686
+ super()._write_to_python(model_dir, package_name=package_name)
662
687
  if self.default_conclusion is not None:
663
688
  with open(model_dir + f"/{self.generated_python_file_name}.py", "a") as f:
664
689
  f.write(f"{' ' * 4}else:\n{' ' * 8}return {self.default_conclusion}\n")
665
690
 
666
- def write_rules_as_source_code_to_file(self, rule: SingleClassRule, file: TextIOWrapper, parent_indent: str = "",
667
- defs_file: Optional[str] = None, cases_file: Optional[str] = None):
691
+ def write_rules_as_source_code_to_file(self, rule: SingleClassRule, filename: str, parent_indent: str = "",
692
+ defs_file: Optional[str] = None, cases_file: Optional[str] = None,
693
+ package_name: Optional[str] = None):
668
694
  """
669
695
  Write the rules as source code to a file.
670
696
  """
671
697
  if rule.conditions:
672
- rule.write_corner_case_as_source_code(cases_file)
698
+ rule.write_corner_case_as_source_code(cases_file, package_name=package_name)
673
699
  if_clause = rule.write_condition_as_source_code(parent_indent, defs_file)
674
- file.write(if_clause)
700
+ with open(filename, "a") as file:
701
+ file.write(if_clause)
675
702
  if rule.refinement:
676
- self.write_rules_as_source_code_to_file(rule.refinement, file, parent_indent + " ",
677
- defs_file=defs_file, cases_file=cases_file)
703
+ self.write_rules_as_source_code_to_file(rule.refinement, filename, parent_indent + " ",
704
+ defs_file=defs_file, cases_file=cases_file,
705
+ package_name=package_name)
678
706
 
679
707
  conclusion_call = rule.write_conclusion_as_source_code(parent_indent, defs_file)
680
- file.write(conclusion_call)
708
+ with open(filename, "a") as file:
709
+ file.write(conclusion_call)
681
710
 
682
711
  if rule.alternative:
683
- self.write_rules_as_source_code_to_file(rule.alternative, file, parent_indent, defs_file=defs_file,
684
- cases_file=cases_file)
712
+ self.write_rules_as_source_code_to_file(rule.alternative, filename, parent_indent, defs_file=defs_file,
713
+ cases_file=cases_file, package_name=package_name)
685
714
 
686
715
  @property
687
716
  def conclusion_type_hint(self) -> str:
@@ -742,8 +771,9 @@ class MultiClassRDR(RDRWithCodeWriter):
742
771
  if evaluated_rule.fired:
743
772
  rule_conclusion = evaluated_rule.conclusion(case)
744
773
  if evaluated_rule.corner_case_metadata is None and case_query is not None:
745
- if rule_conclusion is not None and len(make_list(rule_conclusion)) > 0\
746
- and any(ct in case_query.core_attribute_type for ct in map(type, make_list(rule_conclusion))):
774
+ if rule_conclusion is not None and len(make_list(rule_conclusion)) > 0 \
775
+ and any(
776
+ ct in case_query.core_attribute_type for ct in map(type, make_list(rule_conclusion))):
747
777
  evaluated_rule.corner_case_metadata = CaseFactoryMetaData.from_case_query(case_query)
748
778
  self.add_conclusion(rule_conclusion)
749
779
  evaluated_rule = next_rule
@@ -785,27 +815,32 @@ class MultiClassRDR(RDRWithCodeWriter):
785
815
  return self.conclusions
786
816
 
787
817
  def write_rules_as_source_code_to_file(self, rule: Union[MultiClassTopRule, MultiClassStopRule],
788
- file, parent_indent: str = "", defs_file: Optional[str] = None,
789
- cases_file: Optional[str] = None):
818
+ filename: str, parent_indent: str = "", defs_file: Optional[str] = None,
819
+ cases_file: Optional[str] = None, package_name: Optional[str] = None):
790
820
  if rule == self.start_rule:
791
- file.write(f"{parent_indent}conclusions = set()\n")
821
+ with open(filename, "a") as file:
822
+ file.write(f"{parent_indent}conclusions = set()\n")
792
823
  if rule.conditions:
793
- rule.write_corner_case_as_source_code(cases_file)
824
+ rule.write_corner_case_as_source_code(cases_file, package_name=package_name)
794
825
  if_clause = rule.write_condition_as_source_code(parent_indent, defs_file)
795
- file.write(if_clause)
826
+ with open(filename, "a") as file:
827
+ file.write(if_clause)
796
828
  conclusion_indent = parent_indent
797
829
  if hasattr(rule, "refinement") and rule.refinement:
798
- self.write_rules_as_source_code_to_file(rule.refinement, file, parent_indent + " ",
799
- defs_file=defs_file, cases_file=cases_file)
830
+ self.write_rules_as_source_code_to_file(rule.refinement, filename, parent_indent + " ",
831
+ defs_file=defs_file, cases_file=cases_file,
832
+ package_name=package_name)
800
833
  conclusion_indent = parent_indent + " " * 4
801
- file.write(f"{conclusion_indent}else:\n")
834
+ with open(filename, "a") as file:
835
+ file.write(f"{conclusion_indent}else:\n")
802
836
 
803
837
  conclusion_call = rule.write_conclusion_as_source_code(conclusion_indent, defs_file)
804
- file.write(conclusion_call)
838
+ with open(filename, "a") as file:
839
+ file.write(conclusion_call)
805
840
 
806
841
  if rule.alternative:
807
- self.write_rules_as_source_code_to_file(rule.alternative, file, parent_indent, defs_file=defs_file,
808
- cases_file=cases_file)
842
+ self.write_rules_as_source_code_to_file(rule.alternative, filename, parent_indent, defs_file=defs_file,
843
+ cases_file=cases_file, package_name=package_name)
809
844
 
810
845
  @property
811
846
  def conclusion_type_hint(self) -> str:
@@ -815,12 +850,11 @@ class MultiClassRDR(RDRWithCodeWriter):
815
850
  else:
816
851
  return f"Set[Union[{', '.join(conclusion_types)}]]"
817
852
 
818
- def _get_imports(self) -> Tuple[str, str]:
819
- imports, defs_imports = super()._get_imports()
820
- imports += f"from typing_extensions import Set, Union\n"
821
- imports += "from ripple_down_rules.utils import make_set\n"
822
- defs_imports += "from typing_extensions import Union\n"
823
- return imports, defs_imports
853
+ def _get_types_to_import(self) -> Tuple[Set[Type], Set[Type], Set[Type]]:
854
+ main_types, defs_types, cases_types = super()._get_types_to_import()
855
+ main_types.update({Set, Union, make_set})
856
+ defs_types.add(Union)
857
+ return main_types, defs_types, cases_types
824
858
 
825
859
  def update_start_rule(self, case_query: CaseQuery, expert: Expert):
826
860
  """
@@ -1032,7 +1066,7 @@ class GeneralRDR(RippleDownRules):
1032
1066
 
1033
1067
  def _to_json(self) -> Dict[str, Any]:
1034
1068
  return {"start_rules": {name: rdr.to_json() for name, rdr in self.start_rules_dict.items()}
1035
- , "generated_python_file_name": self.generated_python_file_name,
1069
+ , "generated_python_file_name": self.generated_python_file_name,
1036
1070
  "name": self.name,
1037
1071
  "case_type": get_full_class_name(self.case_type) if self.case_type is not None else None,
1038
1072
  "case_name": self.case_name}
@@ -1056,26 +1090,30 @@ class GeneralRDR(RippleDownRules):
1056
1090
  new_rdr.case_name = data["case_name"]
1057
1091
  return new_rdr
1058
1092
 
1059
- def update_from_python(self, model_dir: str) -> None:
1093
+ def update_from_python(self, model_dir: str, package_name: Optional[str] = None) -> None:
1060
1094
  """
1061
1095
  Update the rules from the generated python file, that might have been modified by the user.
1062
1096
 
1063
1097
  :param model_dir: The directory where the model is stored.
1098
+ :param package_name: The name of the package that contains the RDR classifier function, this
1099
+ is required in case of relative imports in the generated python file.
1064
1100
  """
1065
1101
  for rdr in self.start_rules_dict.values():
1066
- rdr.update_from_python(model_dir)
1102
+ rdr.update_from_python(model_dir, package_name=package_name)
1067
1103
 
1068
- def _write_to_python(self, model_dir: str) -> None:
1104
+ def _write_to_python(self, model_dir: str, package_name: Optional[str] = None) -> None:
1069
1105
  """
1070
1106
  Write the tree of rules as source code to a file.
1071
1107
 
1072
1108
  :param model_dir: The directory where the model is stored.
1109
+ :param relative_imports: Whether to use relative imports in the generated python file.
1073
1110
  """
1074
1111
  for rdr in self.start_rules_dict.values():
1075
- rdr._write_to_python(model_dir)
1112
+ rdr._write_to_python(model_dir, package_name=package_name)
1076
1113
  func_def = f"def classify(case: {self.case_type.__name__}, **kwargs) -> {self.conclusion_type_hint}:\n"
1077
- with open(model_dir + f"/{self.generated_python_file_name}.py", "w") as f:
1078
- f.write(self._get_imports() + "\n\n")
1114
+ file_path = model_dir + f"/{self.generated_python_file_name}.py"
1115
+ with open(file_path, "w") as f:
1116
+ f.write(self._get_imports(file_path=file_path, package_name=package_name) + "\n\n")
1079
1117
  f.write("classifiers_dict = dict()\n")
1080
1118
  for rdr_key, rdr in self.start_rules_dict.items():
1081
1119
  f.write(f"classifiers_dict['{rdr_key}'] = {self.rdr_key_to_function_name(rdr_key)}\n")
@@ -1098,25 +1136,29 @@ class GeneralRDR(RippleDownRules):
1098
1136
  def conclusion_type_hint(self) -> str:
1099
1137
  return f"Dict[str, Any]"
1100
1138
 
1101
- def _get_imports(self) -> str:
1139
+ def _get_imports(self, file_path: Optional[str] = None, package_name: Optional[str] = None) -> str:
1102
1140
  """
1103
1141
  Get the imports needed for the generated python file.
1104
1142
 
1143
+ :param file_path: The path to the file where the imports will be written, if None, the imports will be absolute.
1144
+ :param package_name: The name of the package that contains the RDR classifier function, this
1145
+ is required in case of relative imports in the generated python file.
1105
1146
  :return: The imports needed for the generated python file.
1106
1147
  """
1107
- imports = ""
1148
+ all_types = set()
1108
1149
  # add type hints
1109
- imports += f"from typing_extensions import Dict, Any\n"
1150
+ all_types.update({Dict, Any})
1110
1151
  # import rdr type
1111
- imports += f"from ripple_down_rules.helpers import general_rdr_classify\n"
1152
+ all_types.add(general_rdr_classify)
1112
1153
  # add case type
1113
- imports += f"from ripple_down_rules.datastructures.case import Case, create_case\n"
1114
- imports += f"from {self.case_type.__module__} import {self.case_type.__name__}\n"
1154
+ all_types.update({Case, create_case, self.case_type})
1155
+ # get the imports from the types
1156
+ imports = get_imports_from_types(all_types, target_file_path=file_path, package_name=package_name)
1115
1157
  # add rdr python generated functions.
1116
1158
  for rdr_key, rdr in self.start_rules_dict.items():
1117
- imports += (f"from ."
1118
- f" import {rdr.generated_python_file_name} as {self.rdr_key_to_function_name(rdr_key)}\n")
1119
- return imports
1159
+ imports.append(
1160
+ f"from . import {rdr.generated_python_file_name} as {self.rdr_key_to_function_name(rdr_key)}")
1161
+ return '\n'.join(imports)
1120
1162
 
1121
1163
  @staticmethod
1122
1164
  def rdr_key_to_function_name(rdr_key: str) -> str:
@@ -8,11 +8,11 @@ from uuid import uuid4
8
8
 
9
9
  from anytree import NodeMixin
10
10
  from sqlalchemy.orm import DeclarativeBase as SQLTable
11
- from typing_extensions import List, Optional, Self, Union, Dict, Any, Tuple, Callable
11
+ from typing_extensions import List, Optional, Self, Union, Dict, Any, Tuple, Type, Set
12
12
 
13
13
  from .datastructures.callable_expression import CallableExpression
14
14
  from .datastructures.case import Case
15
- from .datastructures.dataclasses import CaseFactoryMetaData, CaseConf, CaseQuery
15
+ from .datastructures.dataclasses import CaseFactoryMetaData, CaseQuery
16
16
  from .datastructures.enums import RDREdge, Stop
17
17
  from .utils import SubclassJSONSerializer, conclusion_to_json, get_full_class_name, get_imports_from_types
18
18
 
@@ -102,11 +102,21 @@ class Rule(NodeMixin, SubclassJSONSerializer, ABC):
102
102
  """
103
103
  pass
104
104
 
105
- def write_corner_case_as_source_code(self, cases_file: Path) -> None:
105
+ def write_corner_case_as_source_code(self, cases_file: str, package_name: Optional[str] = None) -> None:
106
106
  """
107
107
  Write the source code representation of the corner case of the rule to a file.
108
108
 
109
- :param cases_file: The file to write the corner case to if it is a definition.
109
+ :param cases_file: The file to write the corner case to.
110
+ :param package_name: The package name to use for relative imports.
111
+ """
112
+ if self.corner_case_metadata is None:
113
+ return
114
+ with open(cases_file, 'a') as f:
115
+ f.write(f"corner_case_{self.uid} = {self.corner_case_metadata}" + "\n\n\n")
116
+
117
+ def get_corner_case_types_to_import(self) -> Set[Type]:
118
+ """
119
+ Get the types that need to be imported for the corner case of the rule.
110
120
  """
111
121
  if self.corner_case_metadata is None:
112
122
  return
@@ -117,11 +127,7 @@ class Rule(NodeMixin, SubclassJSONSerializer, ABC):
117
127
  types_to_import.add(self.corner_case_metadata.scenario)
118
128
  if self.corner_case_metadata.case_conf is not None:
119
129
  types_to_import.add(self.corner_case_metadata.case_conf)
120
- types_to_import.add(CaseFactoryMetaData)
121
- imports = get_imports_from_types(list(types_to_import))
122
- with open(cases_file, 'a') as f:
123
- f.write("\n".join(imports) + "\n\n\n")
124
- f.write(f"corner_case_{self.uid} = {self.corner_case_metadata}" + "\n\n\n")
130
+ return types_to_import
125
131
 
126
132
  def write_conclusion_as_source_code(self, parent_indent: str = "", defs_file: Optional[str] = None) -> str:
127
133
  """
@@ -198,7 +198,7 @@ class TemplateFileCreator:
198
198
  with open(self.temp_file_path, 'w+') as f:
199
199
  f.write(code)
200
200
 
201
- def get_imports(self):
201
+ def get_imports(self) -> str:
202
202
  """
203
203
  :return: A string containing the imports for the function.
204
204
  """