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.
- {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/PKG-INFO +1 -1
- {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/src/ripple_down_rules/__init__.py +1 -1
- {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/src/ripple_down_rules/datastructures/dataclasses.py +3 -3
- {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/src/ripple_down_rules/rdr.py +148 -106
- {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/src/ripple_down_rules/rules.py +15 -9
- {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/src/ripple_down_rules/user_interface/template_file_creator.py +1 -1
- {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/src/ripple_down_rules/utils.py +85 -16
- {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/src/ripple_down_rules.egg-info/PKG-INFO +1 -1
- {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/src/ripple_down_rules.egg-info/SOURCES.txt +1 -0
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- ripple_down_rules-0.5.80/test/test_user_interface/__init__.py +0 -0
- ripple_down_rules-0.5.80/test/test_utils.py +78 -0
- ripple_down_rules-0.5.71/test/test_utils.py +0 -38
- {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/.github/workflows/build_and_deploy_doc.yml +0 -0
- {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/.github/workflows/ci.yml +0 -0
- {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/.github/workflows/publish-to-test-pypi.yml +0 -0
- {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
- {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
- {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/LICENSE +0 -0
- {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/README.md +0 -0
- {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/doc/_config.yml +0 -0
- {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/doc/_toc.yml +0 -0
- {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/doc/bibliography.md +0 -0
- {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/doc/intro.md +0 -0
- {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/doc/references.bib +0 -0
- {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/doc/requirements.txt +0 -0
- {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/examples/__init__.py +0 -0
- {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/examples/animal_species.py +0 -0
- {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/examples/part_containment_rdr/__init__.py +0 -0
- {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/examples/part_containment_rdr/rdr_metadata/part_containment_rdr.json +0 -0
- {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/examples/part_containment_rdr/robot_contained_objects_mcrdr.py +0 -0
- {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/examples/part_containment_rdr/robot_contained_objects_mcrdr_defs.py +0 -0
- {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/examples/part_containment_rdr/robot_rdr.py +0 -0
- {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/examples/relational_example.py +0 -0
- {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/images/scrdr.dot +0 -0
- {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/images/scrdr.png +0 -0
- {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/images/thinking_pr2.jpg +0 -0
- {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/pyproject.toml +0 -0
- {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/pytest.ini +0 -0
- {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/requirements-dev-ci.txt +0 -0
- {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/requirements-dev.txt +0 -0
- {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/requirements-gui.txt +0 -0
- {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/requirements-viz.txt +0 -0
- {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/requirements.txt +0 -0
- {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/results/complete_mcrdr_extra.dot +0 -0
- {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/results/complete_mcrdr_extra.png +0 -0
- {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/results/complete_mcrdr_stop_only.dot +0 -0
- {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/results/complete_mcrdr_stop_only.png +0 -0
- {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/results/complete_mcrdr_stop_plus_rule.dot +0 -0
- {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/results/complete_mcrdr_stop_plus_rule.png +0 -0
- {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/results/complete_scrdr.dot +0 -0
- {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/results/complete_scrdr.png +0 -0
- {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/results/complete_scrdr_2.dot +0 -0
- {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/results/complete_scrdr_2.png +0 -0
- {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/results/complete_scrdr_3.dot +0 -0
- {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/results/complete_scrdr_3.png +0 -0
- {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/results/grdr_Habitat.dot +0 -0
- {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/results/grdr_Habitat.png +0 -0
- {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/results/grdr_Species.dot +0 -0
- {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/results/grdr_Species.png +0 -0
- {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/results/mcrdr_extra.dot +0 -0
- {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/results/mcrdr_extra.png +0 -0
- {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/results/mcrdr_extra_classify.dot +0 -0
- {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/results/mcrdr_extra_classify.png +0 -0
- {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/results/mcrdr_stop_plus_rule_combined.dot +0 -0
- {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/results/mcrdr_stop_plus_rule_combined.png +0 -0
- {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/results/partial_mcrdr_extra.dot +0 -0
- {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/results/partial_mcrdr_extra.png +0 -0
- {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/results/relational_scrdr_classify.dot +0 -0
- {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/results/relational_scrdr_classify.png +0 -0
- {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/setup.cfg +0 -0
- {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/src/ripple_down_rules/datastructures/__init__.py +0 -0
- {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/src/ripple_down_rules/datastructures/callable_expression.py +0 -0
- {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/src/ripple_down_rules/datastructures/case.py +0 -0
- {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/src/ripple_down_rules/datastructures/enums.py +0 -0
- {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/src/ripple_down_rules/experts.py +0 -0
- {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/src/ripple_down_rules/helpers.py +0 -0
- {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/src/ripple_down_rules/rdr_decorators.py +0 -0
- {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/src/ripple_down_rules/start-code-server.sh +0 -0
- {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/src/ripple_down_rules/user_interface/__init__.py +0 -0
- {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/src/ripple_down_rules/user_interface/gui.py +0 -0
- {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/src/ripple_down_rules/user_interface/ipython_custom_shell.py +0 -0
- {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/src/ripple_down_rules/user_interface/object_diagram.py +0 -0
- {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/src/ripple_down_rules/user_interface/prompt.py +0 -0
- {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/src/ripple_down_rules.egg-info/dependency_links.txt +0 -0
- {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/src/ripple_down_rules.egg-info/requires.txt +0 -0
- {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/src/ripple_down_rules.egg-info/top_level.txt +0 -0
- {ripple_down_rules-0.5.71/test/conf → ripple_down_rules-0.5.80/test}/__init__.py +0 -0
- {ripple_down_rules-0.5.71/test/conf/world → ripple_down_rules-0.5.80/test/conf}/__init__.py +0 -0
- {ripple_down_rules-0.5.71/test/factories → ripple_down_rules-0.5.80/test/conf/world}/__init__.py +0 -0
- {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/test/conf/world/base_config.py +0 -0
- {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/test/conf/world/handles_and_containers.py +0 -0
- {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/test/conftest.py +0 -0
- {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/test/datasets.py +0 -0
- {ripple_down_rules-0.5.71/test/factories/world → ripple_down_rules-0.5.80/test/factories}/__init__.py +0 -0
- {ripple_down_rules-0.5.71/test/test_helpers → ripple_down_rules-0.5.80/test/factories/world}/__init__.py +0 -0
- {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/test/factories/world/handles_and_containers.py +0 -0
- {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
- {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/test/test_expert_answers/drawer_cabinet_expert_answers_fit.json +0 -0
- {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/test/test_expert_answers/grdr_expert_answers_classify.json +0 -0
- {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/test/test_expert_answers/grdr_expert_answers_fit.json +0 -0
- {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/test/test_expert_answers/grdr_expert_answers_fit_extra.json +0 -0
- {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
- {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/test/test_expert_answers/mcrdr_expert_answers_classify.json +0 -0
- {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
- {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
- {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/test/test_expert_answers/mcrdr_extra_expert_answers_classify.json +0 -0
- {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/test/test_expert_answers/mcrdr_extra_expert_answers_fit.json +0 -0
- {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
- {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/test/test_expert_answers/mcrdr_stop_only_answers_fit.json +0 -0
- {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
- {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
- {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
- {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/test/test_expert_answers/mutagenic_expert_answers.json +0 -0
- {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/test/test_expert_answers/relational_scrdr_expert_answers_classify.json +0 -0
- {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/test/test_expert_answers/scrdr_expert_answers_classify.json +0 -0
- {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/test/test_expert_answers/scrdr_expert_answers_fit.json +0 -0
- {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
- {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
- {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/test/test_expert_answers/scrdr_world_expert_answers_fit.json +0 -0
- {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/test/test_generated_rdrs/__init__.py +0 -0
- {ripple_down_rules-0.5.71/test/test_rdr_world → ripple_down_rules-0.5.80/test/test_helpers}/__init__.py +0 -0
- {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/test/test_helpers/helpers.py +0 -0
- {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/test/test_helpers/object_diagram_case_query.png +0 -0
- {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/test/test_helpers/object_diagram_person.png +0 -0
- {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/test/test_json_serialization.py +0 -0
- {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/test/test_object_diagram.py +0 -0
- {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/test/test_on_mutagenic.py +0 -0
- {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/test/test_rdr.py +0 -0
- {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/test/test_rdr_alchemy.py +0 -0
- {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/test/test_rdr_decorators.py +0 -0
- {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/test/test_rdr_helpers_rdrs.py +0 -0
- {ripple_down_rules-0.5.71/test/test_user_interface → ripple_down_rules-0.5.80/test/test_rdr_world}/__init__.py +0 -0
- {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/test/test_rdr_world/conftest.py +0 -0
- {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/test/test_rdr_world/test_rdr_world.py +0 -0
- {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/test/test_relational_rdr.py +0 -0
- {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/test/test_relational_rdr_alchemy.py +0 -0
- {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
- {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
- {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
- {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/test/test_sql_model.py +0 -0
- {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/test/test_template_file_creator.py +0 -0
- {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/test/test_user_interface/test_ipython.py +0 -0
- {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/test/test_user_interface/test_ipython_copilot.py +0 -0
- {ripple_down_rules-0.5.71 → ripple_down_rules-0.5.80}/test/test_user_interface/test_prompt.py +0 -0
- {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.
|
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
|
@@ -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,
|
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
|
-
|
277
|
+
factory_method_repr = get_function_representation(self.factory_method)
|
278
278
|
if self.scenario is not None:
|
279
|
-
|
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,
|
40
|
-
|
41
|
-
|
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
|
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
|
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
|
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
|
-
|
405
|
-
|
406
|
-
|
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)
|
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()
|
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
|
-
|
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
|
-
|
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
|
-
|
471
|
-
#
|
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
|
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
|
-
|
486
|
-
|
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
|
527
|
+
def _get_types_to_import(self) -> Tuple[Set[Type], Set[Type], Set[Type]]:
|
497
528
|
"""
|
498
|
-
:return: The
|
529
|
+
:return: The types of the main, defs, and corner cases files of the RDR classifier that will be imported.
|
499
530
|
"""
|
500
|
-
|
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
|
-
|
508
|
-
|
509
|
-
|
510
|
-
|
511
|
-
|
512
|
-
|
513
|
-
|
514
|
-
|
515
|
-
|
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,
|
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
|
-
|
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,
|
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
|
-
|
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,
|
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(
|
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
|
-
|
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
|
-
|
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
|
-
|
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,
|
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
|
-
|
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
|
-
|
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,
|
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
|
819
|
-
|
820
|
-
|
821
|
-
|
822
|
-
|
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
|
-
|
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
|
-
|
1078
|
-
|
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
|
-
|
1148
|
+
all_types = set()
|
1108
1149
|
# add type hints
|
1109
|
-
|
1150
|
+
all_types.update({Dict, Any})
|
1110
1151
|
# import rdr type
|
1111
|
-
|
1152
|
+
all_types.add(general_rdr_classify)
|
1112
1153
|
# add case type
|
1113
|
-
|
1114
|
-
imports
|
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
|
1118
|
-
|
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,
|
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,
|
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:
|
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
|
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
|
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
|
"""
|