ripple-down-rules 0.6.28__py3-none-any.whl → 0.6.30__py3-none-any.whl
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/__init__.py +1 -1
- ripple_down_rules/datastructures/callable_expression.py +4 -2
- ripple_down_rules/datastructures/case.py +12 -11
- ripple_down_rules/datastructures/dataclasses.py +87 -9
- ripple_down_rules/experts.py +137 -44
- ripple_down_rules/rdr.py +44 -29
- ripple_down_rules/rules.py +15 -1
- ripple_down_rules/user_interface/gui.py +59 -40
- ripple_down_rules/user_interface/ipython_custom_shell.py +36 -7
- ripple_down_rules/user_interface/prompt.py +41 -26
- ripple_down_rules/user_interface/template_file_creator.py +10 -8
- ripple_down_rules/utils.py +77 -15
- {ripple_down_rules-0.6.28.dist-info → ripple_down_rules-0.6.30.dist-info}/METADATA +1 -1
- ripple_down_rules-0.6.30.dist-info/RECORD +24 -0
- ripple_down_rules-0.6.28.dist-info/RECORD +0 -24
- {ripple_down_rules-0.6.28.dist-info → ripple_down_rules-0.6.30.dist-info}/WHEEL +0 -0
- {ripple_down_rules-0.6.28.dist-info → ripple_down_rules-0.6.30.dist-info}/licenses/LICENSE +0 -0
- {ripple_down_rules-0.6.28.dist-info → ripple_down_rules-0.6.30.dist-info}/top_level.txt +0 -0
@@ -1,7 +1,10 @@
|
|
1
1
|
import ast
|
2
2
|
import logging
|
3
|
+
import sys
|
3
4
|
from _ast import AST
|
4
5
|
|
6
|
+
from .. import logger
|
7
|
+
|
5
8
|
try:
|
6
9
|
from PyQt6.QtWidgets import QApplication
|
7
10
|
from .gui import RDRCaseViewer, style
|
@@ -29,7 +32,7 @@ class UserPrompt:
|
|
29
32
|
"""
|
30
33
|
shell_lock: RLock = RLock() # To ensure that only one thread can access the shell at a time
|
31
34
|
|
32
|
-
def __init__(self):
|
35
|
+
def __init__(self, prompt_user: bool = True):
|
33
36
|
"""
|
34
37
|
Initialize the UserPrompt class.
|
35
38
|
"""
|
@@ -51,18 +54,19 @@ class UserPrompt:
|
|
51
54
|
callable_expression: Optional[CallableExpression] = None
|
52
55
|
while True:
|
53
56
|
with self.shell_lock:
|
54
|
-
user_input, expression_tree = self.prompt_user_about_case(case_query, prompt_for, prompt_str,
|
57
|
+
user_input, expression_tree = self.prompt_user_about_case(case_query, prompt_for, prompt_str,
|
58
|
+
code_to_modify=prev_user_input)
|
55
59
|
if user_input is None:
|
56
60
|
if prompt_for == PromptFor.Conclusion:
|
57
|
-
self.print_func(f"{Fore.YELLOW}No conclusion provided. Exiting.{Style.RESET_ALL}")
|
61
|
+
self.print_func(f"\n{Fore.YELLOW}No conclusion provided. Exiting.{Style.RESET_ALL}")
|
58
62
|
return None, None
|
59
63
|
else:
|
60
|
-
self.print_func(f"{Fore.RED}Conditions must be provided. Please try again.{Style.RESET_ALL}")
|
64
|
+
self.print_func(f"\n{Fore.RED}Conditions must be provided. Please try again.{Style.RESET_ALL}")
|
61
65
|
continue
|
62
|
-
elif user_input
|
63
|
-
self.print_func(f"{Fore.YELLOW}Exiting.{Style.RESET_ALL}")
|
66
|
+
elif user_input in ["exit", 'quit']:
|
67
|
+
self.print_func(f"\n{Fore.YELLOW}Exiting.{Style.RESET_ALL}")
|
64
68
|
return user_input, None
|
65
|
-
|
69
|
+
|
66
70
|
prev_user_input = '\n'.join(user_input.split('\n')[2:-1])
|
67
71
|
conclusion_type = bool if prompt_for == PromptFor.Conditions else case_query.attribute_type
|
68
72
|
callable_expression = CallableExpression(user_input, conclusion_type, expression_tree=expression_tree,
|
@@ -73,8 +77,9 @@ class UserPrompt:
|
|
73
77
|
if len(make_list(result)) == 0 and (user_input_to_modify is not None
|
74
78
|
and (prev_user_input != user_input_to_modify)):
|
75
79
|
user_input_to_modify = prev_user_input
|
76
|
-
self.print_func(
|
77
|
-
|
80
|
+
self.print_func(
|
81
|
+
f"{Fore.YELLOW}The given expression gave an empty result for case {case_query.name}."
|
82
|
+
f" Please accept or modify!{Style.RESET_ALL}")
|
78
83
|
continue
|
79
84
|
break
|
80
85
|
except Exception as e:
|
@@ -82,7 +87,6 @@ class UserPrompt:
|
|
82
87
|
self.print_func(f"{Fore.RED}{e}{Style.RESET_ALL}")
|
83
88
|
return user_input, callable_expression
|
84
89
|
|
85
|
-
|
86
90
|
def prompt_user_about_case(self, case_query: CaseQuery, prompt_for: PromptFor,
|
87
91
|
prompt_str: Optional[str] = None,
|
88
92
|
code_to_modify: Optional[str] = None) -> Tuple[Optional[str], Optional[AST]]:
|
@@ -95,7 +99,7 @@ class UserPrompt:
|
|
95
99
|
:param code_to_modify: The code to modify. If given will be used as a start for user to modify.
|
96
100
|
:return: The user input, and the executable expression that was parsed from the user input.
|
97
101
|
"""
|
98
|
-
|
102
|
+
logger.debug("Entered shell")
|
99
103
|
initial_prompt_str = f"{prompt_str}\n" if prompt_str is not None else ''
|
100
104
|
if prompt_for == PromptFor.Conclusion:
|
101
105
|
prompt_for_str = f"Give possible value(s) for:"
|
@@ -103,20 +107,32 @@ class UserPrompt:
|
|
103
107
|
prompt_for_str = f"Give conditions for:"
|
104
108
|
case_query.scope.update({'case': case_query.case})
|
105
109
|
shell = None
|
110
|
+
|
106
111
|
if self.viewer is None:
|
112
|
+
prompt_for_str = prompt_for_str.replace(":", f" {case_query.name}:")
|
107
113
|
prompt_str = f"{Fore.WHITE}{initial_prompt_str}{Fore.MAGENTA}{prompt_for_str}"
|
108
114
|
prompt_str = self.construct_prompt_str_for_shell(case_query, prompt_for, prompt_str)
|
109
115
|
shell = IPythonShell(header=prompt_str, prompt_for=prompt_for, case_query=case_query,
|
110
|
-
|
116
|
+
code_to_modify=code_to_modify)
|
111
117
|
else:
|
112
|
-
prompt_str =
|
113
|
-
self.viewer.update_for_case_query(case_query,
|
114
|
-
|
118
|
+
prompt_str = case_query.current_value_str
|
119
|
+
self.viewer.update_for_case_query(case_query, prompt_for=prompt_for, code_to_modify=code_to_modify,
|
120
|
+
title=prompt_for_str, prompt_str=prompt_str)
|
115
121
|
user_input, expression_tree = self.prompt_user_input_and_parse_to_expression(shell=shell)
|
116
|
-
|
122
|
+
logger.debug("Exited shell")
|
117
123
|
return user_input, expression_tree
|
118
124
|
|
119
|
-
|
125
|
+
def build_prompt_str_for_ai(self, case_query: CaseQuery, prompt_for: PromptFor,
|
126
|
+
initial_prompt_str: Optional[str] = None) -> str:
|
127
|
+
initial_prompt_str = f"{initial_prompt_str}\n" if initial_prompt_str is not None else ''
|
128
|
+
if prompt_for == PromptFor.Conclusion:
|
129
|
+
prompt_for_str = f"Give possible value(s) for:"
|
130
|
+
else:
|
131
|
+
prompt_for_str = f"Give conditions for:"
|
132
|
+
prompt_for_str = prompt_for_str.replace(":", f" {case_query.name}:")
|
133
|
+
prompt_str = f"{Fore.WHITE}{initial_prompt_str}{Fore.MAGENTA}{prompt_for_str}"
|
134
|
+
prompt_str += '\n' + case_query.current_value_str
|
135
|
+
return prompt_str
|
120
136
|
|
121
137
|
def construct_prompt_str_for_shell(self, case_query: CaseQuery, prompt_for: PromptFor,
|
122
138
|
prompt_str: Optional[str] = None) -> str:
|
@@ -127,8 +143,7 @@ class UserPrompt:
|
|
127
143
|
:param prompt_for: The type of information the user should provide for the given case.
|
128
144
|
:param prompt_str: The prompt string to display to the user.
|
129
145
|
"""
|
130
|
-
prompt_str +=
|
131
|
-
f"{Fore.CYAN}({', '.join(map(lambda x: x.__name__, case_query.core_attribute_type))}){Fore.MAGENTA}")
|
146
|
+
prompt_str += '\n' + case_query.current_value_str
|
132
147
|
if prompt_for == PromptFor.Conditions:
|
133
148
|
prompt_str += (f"\ne.g. `{Fore.GREEN}return {Fore.BLUE}len{Fore.RESET}(case.attribute) > {Fore.BLUE}0` "
|
134
149
|
f"{Fore.MAGENTA}\nOR `{Fore.GREEN}return {Fore.YELLOW}True`{Fore.MAGENTA} (If you want the"
|
@@ -138,7 +153,6 @@ class UserPrompt:
|
|
138
153
|
prompt_str = f"{Fore.MAGENTA}{prompt_str}{Fore.YELLOW}\n(Write %help for guide){Fore.RESET}\n"
|
139
154
|
return prompt_str
|
140
155
|
|
141
|
-
|
142
156
|
def prompt_user_input_and_parse_to_expression(self, shell: Optional[IPythonShell] = None,
|
143
157
|
user_input: Optional[str] = None) \
|
144
158
|
-> Tuple[Optional[str], Optional[ast.AST]]:
|
@@ -152,17 +166,18 @@ class UserPrompt:
|
|
152
166
|
while True:
|
153
167
|
if user_input is None:
|
154
168
|
user_input = self.start_shell_and_get_user_input(shell=shell)
|
155
|
-
if user_input is None or user_input
|
169
|
+
if user_input is None or user_input in ['exit', 'quit']:
|
156
170
|
return user_input, None
|
157
|
-
|
158
|
-
|
159
|
-
|
171
|
+
if logger.level <= logging.DEBUG:
|
172
|
+
self.print_func(f"\n{Fore.GREEN}Captured User input: {Style.RESET_ALL}")
|
173
|
+
highlighted_code = highlight(user_input, PythonLexer(), TerminalFormatter())
|
174
|
+
self.print_func(highlighted_code)
|
160
175
|
try:
|
161
176
|
return user_input, parse_string_to_expression(user_input)
|
162
177
|
except Exception as e:
|
163
178
|
msg = f"Error parsing expression: {e}"
|
164
179
|
logging.error(msg)
|
165
|
-
self.print_func(f"{Fore.RED}{msg}{Style.RESET_ALL}")
|
180
|
+
self.print_func(f"\n{Fore.RED}{msg}{Style.RESET_ALL}")
|
166
181
|
user_input = None
|
167
182
|
|
168
183
|
def start_shell_and_get_user_input(self, shell: Optional[IPythonShell] = None) -> Optional[str]:
|
@@ -185,6 +200,6 @@ class UserPrompt:
|
|
185
200
|
self.viewer.show()
|
186
201
|
app.exec()
|
187
202
|
if self.viewer.exit_status == ExitStatus.CLOSE:
|
188
|
-
exit(
|
203
|
+
sys.exit()
|
189
204
|
user_input = self.viewer.user_input
|
190
205
|
return user_input
|
@@ -109,20 +109,21 @@ class TemplateFileCreator:
|
|
109
109
|
|
110
110
|
self.open_file_in_editor()
|
111
111
|
|
112
|
-
def open_file_in_editor(self):
|
112
|
+
def open_file_in_editor(self, file_path: Optional[str] = None):
|
113
113
|
"""
|
114
114
|
Open the file in the available editor.
|
115
115
|
"""
|
116
|
+
file_path = file_path or self.temp_file_path
|
116
117
|
if self.editor_cmd is not None:
|
117
|
-
subprocess.Popen([self.editor_cmd,
|
118
|
+
subprocess.Popen([self.editor_cmd, file_path],
|
118
119
|
stdout=subprocess.DEVNULL,
|
119
120
|
stderr=subprocess.DEVNULL)
|
120
121
|
elif self.editor == Editor.Pycharm:
|
121
|
-
subprocess.Popen(["pycharm", "--line", str(self.user_edit_line),
|
122
|
+
subprocess.Popen(["pycharm", "--line", str(self.user_edit_line), file_path],
|
122
123
|
stdout=subprocess.DEVNULL,
|
123
124
|
stderr=subprocess.DEVNULL)
|
124
125
|
elif self.editor == Editor.Code:
|
125
|
-
subprocess.Popen(["code",
|
126
|
+
subprocess.Popen(["code", file_path])
|
126
127
|
elif self.editor == Editor.CodeServer:
|
127
128
|
try:
|
128
129
|
subprocess.check_output(["pgrep", "-f", "code-server"])
|
@@ -134,7 +135,8 @@ class TemplateFileCreator:
|
|
134
135
|
except (subprocess.CalledProcessError, ValueError) as e:
|
135
136
|
self.process = start_code_server(self.workspace)
|
136
137
|
self.print_func(f"Open code-server in your browser at http://localhost:{self.port}?folder={self.workspace}")
|
137
|
-
|
138
|
+
if file_path.endswith('.py'):
|
139
|
+
self.print_func(f"Edit the file: {Fore.MAGENTA}{file_path}")
|
138
140
|
|
139
141
|
def build_boilerplate_code(self):
|
140
142
|
imports = self.get_imports()
|
@@ -183,7 +185,7 @@ class TemplateFileCreator:
|
|
183
185
|
func_args = ', '.join([f"{k}: {v}" if str(v) not in ["NoneType", "None"] else str(k)
|
184
186
|
for k, v in func_args.items()])
|
185
187
|
else:
|
186
|
-
func_args = f"case: {self.case_type.__name__}"
|
188
|
+
func_args = f"case: {self.case_query.case_type.__name__}"
|
187
189
|
return func_args
|
188
190
|
|
189
191
|
def write_to_file(self, code: str):
|
@@ -212,7 +214,7 @@ class TemplateFileCreator:
|
|
212
214
|
else:
|
213
215
|
case_type_imports.append(v)
|
214
216
|
else:
|
215
|
-
case_type_imports.append(self.case_type)
|
217
|
+
case_type_imports.append(self.case_query.case_type)
|
216
218
|
if self.output_type is None:
|
217
219
|
output_type_imports = [Any]
|
218
220
|
else:
|
@@ -302,7 +304,7 @@ class TemplateFileCreator:
|
|
302
304
|
exec(source, scope, exec_globals)
|
303
305
|
user_function = exec_globals[func_name]
|
304
306
|
updates[func_name] = user_function
|
305
|
-
print_func(f"{Fore.
|
307
|
+
print_func(f"\n{Fore.WHITE}Loaded the following function into user namespace:\n{Fore.GREEN}{func_name}{Style.RESET_ALL}")
|
306
308
|
break
|
307
309
|
if updates:
|
308
310
|
all_code_lines = extract_function_source(file_path,
|
ripple_down_rules/utils.py
CHANGED
@@ -21,8 +21,10 @@ from subprocess import check_call
|
|
21
21
|
from tempfile import NamedTemporaryFile
|
22
22
|
from textwrap import dedent
|
23
23
|
from types import NoneType
|
24
|
+
import inspect
|
24
25
|
|
25
26
|
import six
|
27
|
+
from graphviz import Source
|
26
28
|
from sqlalchemy.exc import NoInspectionAvailable
|
27
29
|
from . import logger
|
28
30
|
|
@@ -45,7 +47,7 @@ except ImportError as e:
|
|
45
47
|
|
46
48
|
import requests
|
47
49
|
from anytree import Node, RenderTree, PreOrderIter
|
48
|
-
from sqlalchemy import MetaData, inspect
|
50
|
+
from sqlalchemy import MetaData, inspect as sql_inspect
|
49
51
|
from sqlalchemy.orm import Mapped, registry, class_mapper, DeclarativeBase as SQLTable, Session
|
50
52
|
from tabulate import tabulate
|
51
53
|
from typing_extensions import Callable, Set, Any, Type, Dict, TYPE_CHECKING, get_type_hints, \
|
@@ -146,7 +148,9 @@ def extract_imports(file_path: Optional[str] = None, tree: Optional[ast.AST] = N
|
|
146
148
|
def extract_function_source(file_path: str,
|
147
149
|
function_names: List[str], join_lines: bool = True,
|
148
150
|
return_line_numbers: bool = False,
|
149
|
-
include_signature: bool = True
|
151
|
+
include_signature: bool = True,
|
152
|
+
as_list: bool = False,
|
153
|
+
is_class: bool = False) \
|
150
154
|
-> Union[Dict[str, Union[str, List[str]]],
|
151
155
|
Tuple[Dict[str, Union[str, List[str]]], Dict[str, Tuple[int, int]]]]:
|
152
156
|
"""
|
@@ -157,6 +161,9 @@ def extract_function_source(file_path: str,
|
|
157
161
|
:param join_lines: Whether to join the lines of the function.
|
158
162
|
:param return_line_numbers: Whether to return the line numbers of the function.
|
159
163
|
:param include_signature: Whether to include the function signature in the source code.
|
164
|
+
:param as_list: Whether to return a list of function sources instead of dict (useful when there is multiple
|
165
|
+
functions with same name).
|
166
|
+
:param is_class: Whether to also look for class definitions
|
160
167
|
:return: A dictionary mapping function names to their source code as a string if join_lines is True,
|
161
168
|
otherwise as a list of strings.
|
162
169
|
"""
|
@@ -167,24 +174,39 @@ def extract_function_source(file_path: str,
|
|
167
174
|
tree = ast.parse(source)
|
168
175
|
function_names = make_list(function_names)
|
169
176
|
functions_source: Dict[str, Union[str, List[str]]] = {}
|
177
|
+
functions_source_list: List[Union[str, List[str]]] = []
|
170
178
|
line_numbers: Dict[str, Tuple[int, int]] = {}
|
179
|
+
line_numbers_list: List[Tuple[int, int]] = []
|
180
|
+
if is_class:
|
181
|
+
look_for_type = ast.ClassDef
|
182
|
+
else:
|
183
|
+
look_for_type = ast.FunctionDef
|
184
|
+
|
171
185
|
for node in tree.body:
|
172
|
-
if isinstance(node,
|
186
|
+
if isinstance(node, look_for_type) and (node.name in function_names or len(function_names) == 0):
|
173
187
|
# Get the line numbers of the function
|
174
188
|
lines = source.splitlines()
|
175
189
|
func_lines = lines[node.lineno - 1:node.end_lineno]
|
176
190
|
if not include_signature:
|
177
191
|
func_lines = func_lines[1:]
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
192
|
+
if as_list:
|
193
|
+
line_numbers_list.append((node.lineno, node.end_lineno))
|
194
|
+
else:
|
195
|
+
line_numbers[node.name] = (node.lineno, node.end_lineno)
|
196
|
+
parsed_function = dedent("\n".join(func_lines)) if join_lines else func_lines
|
197
|
+
if as_list:
|
198
|
+
functions_source_list.append(parsed_function)
|
199
|
+
else:
|
200
|
+
functions_source[node.name] = parsed_function
|
201
|
+
if len(function_names) > 0:
|
202
|
+
if len(functions_source) >= len(function_names) or len(functions_source_list) >= len(function_names):
|
203
|
+
break
|
204
|
+
if len(functions_source) < len(function_names) and len(functions_source_list) < len(function_names):
|
183
205
|
logger.warning(f"Could not find all functions in {file_path}: {function_names} not found, "
|
184
206
|
f"functions not found: {set(function_names) - set(functions_source.keys())}")
|
185
207
|
if return_line_numbers:
|
186
|
-
return functions_source, line_numbers
|
187
|
-
return functions_source
|
208
|
+
return functions_source if not as_list else functions_source_list, line_numbers if not as_list else line_numbers_list
|
209
|
+
return functions_source if not as_list else functions_source_list
|
188
210
|
|
189
211
|
|
190
212
|
def encapsulate_user_input(user_input: str, func_signature: str, func_doc: Optional[str] = None) -> str:
|
@@ -791,6 +813,13 @@ def get_import_path_from_path(path: str) -> Optional[str]:
|
|
791
813
|
return package_name
|
792
814
|
|
793
815
|
|
816
|
+
def get_class_file_path(cls):
|
817
|
+
"""
|
818
|
+
Get the file path of a class.
|
819
|
+
"""
|
820
|
+
return os.path.abspath(inspect.getfile(cls))
|
821
|
+
|
822
|
+
|
794
823
|
def get_function_import_data(func: Callable) -> Tuple[str, str]:
|
795
824
|
"""
|
796
825
|
Get the import path of a function.
|
@@ -1281,7 +1310,7 @@ def copy_orm_instance(instance: SQLTable) -> SQLTable:
|
|
1281
1310
|
:return: The copied instance.
|
1282
1311
|
"""
|
1283
1312
|
try:
|
1284
|
-
session: Session =
|
1313
|
+
session: Session = sql_inspect(instance).session
|
1285
1314
|
except NoInspectionAvailable:
|
1286
1315
|
session = None
|
1287
1316
|
if session is not None:
|
@@ -1833,6 +1862,24 @@ class FilteredDotExporter(object):
|
|
1833
1862
|
yield node
|
1834
1863
|
for edge in self.__iter_edges(indent, nodenamefunc, edgeattrfunc, edgetypefunc):
|
1835
1864
|
yield edge
|
1865
|
+
legend_dot_graph = """
|
1866
|
+
// Color legend as a subgraph
|
1867
|
+
subgraph cluster_legend {
|
1868
|
+
label = "Legend";
|
1869
|
+
style = dashed;
|
1870
|
+
color = gray;
|
1871
|
+
|
1872
|
+
legend_green [label="Fired->Query Related Value", shape=box, style=filled, fillcolor=green, fontcolor=black, size=0.5];
|
1873
|
+
legend_yellow [label="Fired->Some Value", shape=box, style=filled, fillcolor=yellow, fontcolor=black, size=0.5];
|
1874
|
+
legend_orange [label="Fired->Empty Value", shape=box, style=filled, fillcolor=orange, fontcolor=black, size=0.5];
|
1875
|
+
legend_red [label="Evaluated->Not Fired", shape=box, style=filled, fillcolor=red, fontcolor=black, size=0.5];
|
1876
|
+
legend_white [label="Not Evaluated", shape=box, style=filled, fillcolor=white, fontcolor=black, size=0.5];
|
1877
|
+
|
1878
|
+
// Invisible edges to arrange legend vertically
|
1879
|
+
legend_white -> legend_red -> legend_orange -> legend_yellow -> legend_green [style=invis];
|
1880
|
+
}"""
|
1881
|
+
for line in legend_dot_graph.splitlines():
|
1882
|
+
yield "%s" % (line.strip())
|
1836
1883
|
yield "}"
|
1837
1884
|
|
1838
1885
|
def __iter_options(self, indent):
|
@@ -1915,6 +1962,12 @@ class FilteredDotExporter(object):
|
|
1915
1962
|
msg = 'Could not remove temporary file %s' % dotfilename
|
1916
1963
|
logger.warning(msg)
|
1917
1964
|
|
1965
|
+
def to_source(self) -> Source:
|
1966
|
+
"""
|
1967
|
+
Return the source code of the graph as a Source object.
|
1968
|
+
"""
|
1969
|
+
return Source("\n".join(self), filename=self.name)
|
1970
|
+
|
1918
1971
|
@staticmethod
|
1919
1972
|
def esc(value):
|
1920
1973
|
"""Escape Strings."""
|
@@ -1922,7 +1975,9 @@ class FilteredDotExporter(object):
|
|
1922
1975
|
|
1923
1976
|
|
1924
1977
|
def render_tree(root: Node, use_dot_exporter: bool = False,
|
1925
|
-
filename: str = "scrdr", only_nodes: List[Node] = None, show_in_console: bool = False
|
1978
|
+
filename: str = "scrdr", only_nodes: List[Node] = None, show_in_console: bool = False,
|
1979
|
+
color_map: Optional[Callable[[Node], str]] = None,
|
1980
|
+
view: bool = False) -> None:
|
1926
1981
|
"""
|
1927
1982
|
Render the tree using the console and optionally export it to a dot file.
|
1928
1983
|
|
@@ -1931,6 +1986,8 @@ def render_tree(root: Node, use_dot_exporter: bool = False,
|
|
1931
1986
|
:param filename: The name of the file to export the tree to.
|
1932
1987
|
:param only_nodes: A list of nodes to include in the dot export.
|
1933
1988
|
:param show_in_console: Whether to print the tree to the console.
|
1989
|
+
:param color_map: A function that returns a color for certain nodes.
|
1990
|
+
:param view: Whether to view the dot file in a viewer.
|
1934
1991
|
"""
|
1935
1992
|
if not root:
|
1936
1993
|
logger.warning("No rules to render")
|
@@ -1947,10 +2004,15 @@ def render_tree(root: Node, use_dot_exporter: bool = False,
|
|
1947
2004
|
include_nodes=only_nodes,
|
1948
2005
|
nodenamefunc=unique_node_names,
|
1949
2006
|
edgeattrfunc=edge_attr_setter,
|
1950
|
-
nodeattrfunc=lambda node: f'style=filled,
|
2007
|
+
nodeattrfunc=lambda node: f'style=filled,'
|
2008
|
+
f' fillcolor={color_map(node) if color_map else node.color}',
|
1951
2009
|
)
|
1952
|
-
|
1953
|
-
|
2010
|
+
if view:
|
2011
|
+
de.to_source().view()
|
2012
|
+
else:
|
2013
|
+
filename = filename or "rule_tree"
|
2014
|
+
de.to_dotfile(f"{filename}{'.dot'}")
|
2015
|
+
de.to_picture(f"{filename}{'.svg'}")
|
1954
2016
|
|
1955
2017
|
|
1956
2018
|
def draw_tree(root: Node, fig: Figure):
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: ripple_down_rules
|
3
|
-
Version: 0.6.
|
3
|
+
Version: 0.6.30
|
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
|
@@ -0,0 +1,24 @@
|
|
1
|
+
ripple_down_rules/__init__.py,sha256=97mtCfVEw1HwkdrprDGcbF2yNl3fqkWb_6oSEj-rOFo,99
|
2
|
+
ripple_down_rules/experts.py,sha256=KXwWCmDrCffu9HW3yNewqWc1e5rnPI5Rnc981w_5M7U,17896
|
3
|
+
ripple_down_rules/helpers.py,sha256=X1psHOqrb4_xYN4ssQNB8S9aRKKsqgihAyWJurN0dqk,5499
|
4
|
+
ripple_down_rules/rdr.py,sha256=KsZbAbOs8U2PL19YOjFqSer8coXkSMDL3ztIrWHmTCA,62833
|
5
|
+
ripple_down_rules/rdr_decorators.py,sha256=xoBGsIJMkJYUdsrsEaPZqoAsGuXkuVZAKCoP-xD2Iv8,11668
|
6
|
+
ripple_down_rules/rules.py,sha256=W-y4aGKrGn2Xc7fbn1PNyz_jUzufFMtYwhLYiST28Ww,29213
|
7
|
+
ripple_down_rules/start-code-server.sh,sha256=otClk7VmDgBOX2TS_cjws6K0UwvgAUJhoA0ugkPCLqQ,949
|
8
|
+
ripple_down_rules/utils.py,sha256=1fiSF4MOaOUrxlMz8sZA_e10258sMWuX5fG9WDawd2o,76674
|
9
|
+
ripple_down_rules/datastructures/__init__.py,sha256=V2aNgf5C96Y5-IGghra3n9uiefpoIm_QdT7cc_C8cxQ,111
|
10
|
+
ripple_down_rules/datastructures/callable_expression.py,sha256=IrlnufVsKrUDLVkc2owoFQ05oSOby3HiGuNXoFVj4Dw,13494
|
11
|
+
ripple_down_rules/datastructures/case.py,sha256=dfLnrjsHIVF2bgbz-4ID7OdQvw68V71btCeTK372P-g,15667
|
12
|
+
ripple_down_rules/datastructures/dataclasses.py,sha256=3vX52WrAHgVyw0LUSgSBOVFaQNTSxU8hQpdr7cW-tSg,13278
|
13
|
+
ripple_down_rules/datastructures/enums.py,sha256=CvcROl8fE7A6uTbMfs2lLpyxwS_ZFtFcQlBDDKFfoHc,6059
|
14
|
+
ripple_down_rules/user_interface/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
15
|
+
ripple_down_rules/user_interface/gui.py,sha256=K6cvA9TOXIDpk0quGCamrWqDRlvz0QruDaTj4Y4PWWI,28544
|
16
|
+
ripple_down_rules/user_interface/ipython_custom_shell.py,sha256=RLdPqPxx-a0Sh74UZWyRBuxS_OKeXJnzoDfms4i-Aus,7710
|
17
|
+
ripple_down_rules/user_interface/object_diagram.py,sha256=FEa2HaYR9QmTE6NsOwBvZ0jqmu3DKyg6mig2VE5ZP4Y,4956
|
18
|
+
ripple_down_rules/user_interface/prompt.py,sha256=WPbw_8_-8SpF2ISyRZRuFwPKBEuGC4HaX3lbCPFHhh8,10314
|
19
|
+
ripple_down_rules/user_interface/template_file_creator.py,sha256=uSbosZS15MOR3Nv7M3MrFuoiKXyP4cBId-EK3I6stHM,13660
|
20
|
+
ripple_down_rules-0.6.30.dist-info/licenses/LICENSE,sha256=ixuiBLtpoK3iv89l7ylKkg9rs2GzF9ukPH7ynZYzK5s,35148
|
21
|
+
ripple_down_rules-0.6.30.dist-info/METADATA,sha256=5y9rt2-t0p_N1BJt2_fMrLzYk0KZl5W8uDDB27LHiWw,48294
|
22
|
+
ripple_down_rules-0.6.30.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
23
|
+
ripple_down_rules-0.6.30.dist-info/top_level.txt,sha256=VeoLhEhyK46M1OHwoPbCQLI1EifLjChqGzhQ6WEUqeM,18
|
24
|
+
ripple_down_rules-0.6.30.dist-info/RECORD,,
|
@@ -1,24 +0,0 @@
|
|
1
|
-
ripple_down_rules/__init__.py,sha256=XvJJjrncdmKjrvKVVILZq7uuTOYiohoP3iLBN_oKp_M,99
|
2
|
-
ripple_down_rules/experts.py,sha256=bQfDB7RZE_CANVkpBMb9bi8ZWFwk5p7hn1AzjpaO5ow,12877
|
3
|
-
ripple_down_rules/helpers.py,sha256=X1psHOqrb4_xYN4ssQNB8S9aRKKsqgihAyWJurN0dqk,5499
|
4
|
-
ripple_down_rules/rdr.py,sha256=hVqfCRrru6TUnqzY7yGMjiNnxUIFjfL2U7txmGNjeGI,61661
|
5
|
-
ripple_down_rules/rdr_decorators.py,sha256=xoBGsIJMkJYUdsrsEaPZqoAsGuXkuVZAKCoP-xD2Iv8,11668
|
6
|
-
ripple_down_rules/rules.py,sha256=N4dEx-xyqxGZpoEYzRd9P5u97_DcDEVLY_UiNhZ4E7g,28726
|
7
|
-
ripple_down_rules/start-code-server.sh,sha256=otClk7VmDgBOX2TS_cjws6K0UwvgAUJhoA0ugkPCLqQ,949
|
8
|
-
ripple_down_rules/utils.py,sha256=9xW0N2cB7X4taVANtLg-kVTPS-6ajWZylKkTqw2PKw4,73825
|
9
|
-
ripple_down_rules/datastructures/__init__.py,sha256=V2aNgf5C96Y5-IGghra3n9uiefpoIm_QdT7cc_C8cxQ,111
|
10
|
-
ripple_down_rules/datastructures/callable_expression.py,sha256=P3o-z54Jt4rtIczeFWiuHFTNqMzYEOm94OyOP535D6Q,13378
|
11
|
-
ripple_down_rules/datastructures/case.py,sha256=PJ7_-AdxYic6BO5z816piFODj6nU5J6Jt1YzTFH-dds,15510
|
12
|
-
ripple_down_rules/datastructures/dataclasses.py,sha256=kI3Kv8GiVR8igMgA_BlKN6djUYxC2mLecvyh19pqQQA,10998
|
13
|
-
ripple_down_rules/datastructures/enums.py,sha256=CvcROl8fE7A6uTbMfs2lLpyxwS_ZFtFcQlBDDKFfoHc,6059
|
14
|
-
ripple_down_rules/user_interface/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
15
|
-
ripple_down_rules/user_interface/gui.py,sha256=JCz6vA2kWVvRD6CupoUfMbtGz39cvUlmVnGnw_fY6OA,27635
|
16
|
-
ripple_down_rules/user_interface/ipython_custom_shell.py,sha256=yp-F8YRWGhj1PLB33HE6vJkdYWFN5Zn2244S2DUWRTM,6576
|
17
|
-
ripple_down_rules/user_interface/object_diagram.py,sha256=FEa2HaYR9QmTE6NsOwBvZ0jqmu3DKyg6mig2VE5ZP4Y,4956
|
18
|
-
ripple_down_rules/user_interface/prompt.py,sha256=nLIAviClSmVCY80vQgTazDPs4a1AYmNQmT7sksLDJpE,9449
|
19
|
-
ripple_down_rules/user_interface/template_file_creator.py,sha256=kwBbFLyN6Yx2NTIHPSwOoytWgbJDYhgrUOVFw_jkDQ4,13522
|
20
|
-
ripple_down_rules-0.6.28.dist-info/licenses/LICENSE,sha256=ixuiBLtpoK3iv89l7ylKkg9rs2GzF9ukPH7ynZYzK5s,35148
|
21
|
-
ripple_down_rules-0.6.28.dist-info/METADATA,sha256=fPfcVvFD7eEuG5FKX26HRRWPJkZatA0mfWdarnTqPa8,48294
|
22
|
-
ripple_down_rules-0.6.28.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
23
|
-
ripple_down_rules-0.6.28.dist-info/top_level.txt,sha256=VeoLhEhyK46M1OHwoPbCQLI1EifLjChqGzhQ6WEUqeM,18
|
24
|
-
ripple_down_rules-0.6.28.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|