ripple-down-rules 0.6.49__py3-none-any.whl → 0.6.51__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.
@@ -1,4 +1,4 @@
1
- __version__ = "0.6.49"
1
+ __version__ = "0.6.51"
2
2
 
3
3
  import logging
4
4
  import sys
@@ -130,8 +130,10 @@ class CallableExpression(SubclassJSONSerializer):
130
130
  conclusion_type = (conclusion_type,)
131
131
  self.conclusion_type = conclusion_type
132
132
  self.expected_types: Set[Type] = set(conclusion_type) if conclusion_type is not None else set()
133
- if not mutually_exclusive:
134
- self.expected_types.update({list, set})
133
+ if list in self.expected_types:
134
+ self.expected_types.remove(list)
135
+ if set in self.expected_types:
136
+ self.expected_types.add(set)
135
137
  self.scope: Optional[Dict[str, Any]] = scope if scope is not None else {}
136
138
  self.scope = get_used_scope(self.user_input, self.scope)
137
139
  self.expression_tree: AST = expression_tree if expression_tree else parse_string_to_expression(self.user_input)
@@ -149,6 +151,8 @@ class CallableExpression(SubclassJSONSerializer):
149
151
 
150
152
  def __call__(self, case: Any, **kwargs) -> Any:
151
153
  try:
154
+ # if not self.mutually_exclusive:
155
+ # self.expected_types.update({list, set})
152
156
  if self.user_input is not None:
153
157
  if not isinstance(case, Case):
154
158
  case = create_case(case, max_recursion_idx=3)
@@ -160,7 +164,6 @@ class CallableExpression(SubclassJSONSerializer):
160
164
  if self.mutually_exclusive and issubclass(type(output), (list, set)):
161
165
  raise ValueError(f"Mutually exclusive types cannot be lists or sets, got {type(output)}")
162
166
  output_types = {type(o) for o in make_list(output)}
163
- output_types.add(type(output))
164
167
  if not are_results_subclass_of_types(output_types, self.expected_types):
165
168
  raise ValueError(f"Not all result types {output_types} are subclasses of expected types"
166
169
  f" {self.conclusion_type}")
@@ -4,17 +4,18 @@ that can be used with any python function such that this function can benefit fr
4
4
  of the RDRs.
5
5
  """
6
6
  import os.path
7
+ from dataclasses import dataclass, field
7
8
  from functools import wraps
8
9
  from typing import get_origin
9
10
 
10
11
  from typing_extensions import Callable, Optional, Type, Tuple, Dict, Any, Self, get_type_hints, List, Union, Sequence
11
12
 
12
- from .utils import get_type_from_type_hint
13
13
  from .datastructures.case import Case
14
14
  from .datastructures.dataclasses import CaseQuery
15
15
  from .experts import Expert, Human
16
16
  from .rdr import GeneralRDR
17
- from . import logger
17
+ from .utils import get_type_from_type_hint
18
+
18
19
  try:
19
20
  from .user_interface.gui import RDRCaseViewer
20
21
  except ImportError:
@@ -23,63 +24,91 @@ from .utils import get_method_args_as_dict, get_func_rdr_model_name, make_set, \
23
24
  get_method_class_if_exists, str_to_snake_case, make_list
24
25
 
25
26
 
27
+ @dataclass(unsafe_hash=True)
26
28
  class RDRDecorator:
27
- rdr: GeneralRDR
28
-
29
- def __init__(self, models_dir: str,
30
- output_type: Tuple[Type],
31
- mutual_exclusive: bool,
32
- output_name: str = "output_",
33
- fit: bool = True,
34
- expert: Optional[Expert] = None,
35
- update_existing_rules: bool = True,
36
- viewer: Optional[RDRCaseViewer] = None,
37
- package_name: Optional[str] = None,
38
- use_generated_classifier: bool = False,
39
- ask_now: Callable[Dict[str, Any], bool] = lambda _: True,
40
- fitting_decorator: Optional[Callable] = None,
41
- generate_dot_file: bool = False) -> None:
42
- """
43
- :param models_dir: The directory to save/load the RDR models.
44
- :param output_type: The type of the output. This is used to create the RDR model.
45
- :param mutual_exclusive: If True, the output types are mutually exclusive.
46
- If None, the RDR model will not be saved as a python file.
47
- :param output_name: The name of the output. This is used to create the RDR model.
48
- :param fit: If True, the function will be in fit mode. This means that the RDR will prompt the user for the
49
- correct output if the function's output is not in the RDR model. If False, the function will be in
50
- classification mode. This means that the RDR will classify the function's output based on the RDR model.
51
- :param expert: The expert that will be used to prompt the user for the correct output. If None, a Human
52
- expert will be used.
53
- :param update_existing_rules: If True, the function will update the existing RDR rules
54
- even if they gave an output.
55
- :param viewer: The viewer to use for the RDR model. If None, no viewer will be used.
56
- :param package_name: The package name to use for relative imports in the RDR model.
57
- :param use_generated_classifier: If True, the function will use the generated classifier instead of the RDR model.
58
- :param ask_now: A callable that takes the case dictionary and returns True if the user should be asked for
59
- the output, or False if the function should return the output without asking.
60
- :param fitting_decorator: A decorator to use for the fitting function. If None, no decorator will be used.
61
- :param generate_dot_file: If True, the RDR model will generate a dot file for visualization.
62
- :return: A decorator to use a GeneralRDR as a classifier that monitors and modifies the function's output.
63
- """
64
- self.rdr_models_dir = models_dir
65
- self.model_name: Optional[str] = None
66
- self.output_type = output_type
67
- self.parsed_output_type: List[Type] = []
68
- self.mutual_exclusive = mutual_exclusive
69
- self.output_name = output_name
70
- self.fit: bool = fit
71
- self.expert: Optional[Expert] = expert
72
- self.update_existing_rules = update_existing_rules
73
- self.viewer = viewer
74
- self.package_name = package_name
75
- self.use_generated_classifier = use_generated_classifier
76
- self.generated_classifier: Optional[Callable] = None
77
- self.ask_now = ask_now
78
- self.fitting_decorator = fitting_decorator if fitting_decorator is not None else \
79
- lambda f: f # Default to no fitting decorator
80
- self.generate_dot_file = generate_dot_file
81
- self.not_none_output_found: bool = False
82
- self.origin_type: Optional[Type] = None
29
+ models_dir: str
30
+ """
31
+ The directory to save the RDR models in.
32
+ """
33
+ output_type: Tuple[Type, ...]
34
+ """
35
+ The type(s) of the output produced by the RDR model (The type(s) of the queried attribute).
36
+ """
37
+ mutual_exclusive: bool
38
+ """
39
+ Whether the queried attribute is mutually exclusive (i.e. allows for only one possible value) or not.
40
+ """
41
+ fit: bool = field(default=True)
42
+ """
43
+ Whether to run in fitting mode and prompt the expert or just classify using existing rules.
44
+ """
45
+ expert: Optional[Expert] = field(default=None)
46
+ """
47
+ The expert instance, this is used by the rdr to prompt for answers.
48
+ """
49
+ update_existing_rules: bool = field(default=True)
50
+ """
51
+ When in fitting mode, whether to ask for answers for existing rules as well or not.
52
+ """
53
+ package_name: Optional[str] = field(default=None)
54
+ """
55
+ The name of the user python package where the RDR model will be saved, this is useful for generating relative
56
+ imports in the generated rdr model files.
57
+ """
58
+ use_generated_classifier: bool = field(default=False)
59
+ """
60
+ Whether to use the generated classifier files of the rdr model instead of the RDR instance itself, this is useful
61
+ when you want to debug inside the rules.
62
+ """
63
+ ask_now: Callable[Dict[str, Any], bool] = field(default=lambda _: True)
64
+ """
65
+ A user provided callable function that outputs a boolean indicating when to ask the expert for an answer. The input
66
+ to the `ask_now` function is a dictionary with the original function arguments, while arguments like `self` and
67
+ `cls` are passed as a special key `self_` or `cls_` respectively.
68
+ """
69
+ fitting_decorator: Optional[Callable] = field(default=lambda f: f)
70
+ """
71
+ A user provided decorator that wraps the `py:meth:ripple_down_rules.rdr.RippleDownRules.fit_case` method which is
72
+ used when in fitting mode, this is useful when you want special logic pre and post the fitting operation, you can
73
+ for example freeze your system during fitting such that you have a stable state that you can query and use while
74
+ writing and testing your answer/rule.
75
+ """
76
+ generate_dot_file: bool = field(default=False)
77
+ """
78
+ Whether to generate a dynamic dot file representing the state of the rule tree each time the rdr is queried, showing
79
+ which rules fired and which rules didn't get evaluated, ...etc.
80
+ """
81
+ model_name: Optional[str] = field(default=None)
82
+ """
83
+ The name of the rdr model, this gets auto generated from the function signature and the class/file it is contained
84
+ in.
85
+ """
86
+ rdr: GeneralRDR = field(init=False)
87
+ """
88
+ The ripple down rules instance of the decorator class.
89
+ """
90
+ parsed_output_type: List[Type] = field(init=False, default_factory=list)
91
+ """
92
+ The output of a post processing done on the output types, for example converting typing module types
93
+ (i.e. type hints) to python types.
94
+ """
95
+ origin_type: Optional[Type] = field(init=False, default=None)
96
+ """
97
+ The origin of the type hint of the attribute, useful in the case of not mutually exclusive attributes to map the
98
+ result to the specified container type (e.g. a list instead of a set which is the default container type for rdr
99
+ output).
100
+ """
101
+ output_name: str = field(init=False, default='output_')
102
+ """
103
+ This is used to refer to the output value of the decorated function, this is used as part of the case as input to
104
+ the rdr model, but is never used in the rule logic to prevent cycles from happening. The correct way to use the
105
+ output of an rdr is through refinement rules which happens automatically by the rdr prompting for refinements.
106
+ """
107
+ _not_none_output_found: bool = field(init=False, default=False)
108
+ """
109
+ This is a flag that indicates that a not None output for the rdr has been inferred, this is used to update the
110
+ generated dot file if it is set to `True`.
111
+ """
83
112
 
84
113
  def decorator(self, func: Callable) -> Callable:
85
114
 
@@ -102,33 +131,33 @@ class RDRDecorator:
102
131
  if len(self.parsed_output_type) == 0:
103
132
  self.parsed_output_type = self.parse_output_type(func, self.output_type, *args)
104
133
  if self.expert is None:
105
- self.expert = Human(answers_save_path=self.rdr_models_dir + f'/{self.model_name}/expert_answers')
134
+ self.expert = Human(answers_save_path=self.models_dir + f'/{self.model_name}/expert_answers')
106
135
  case_query = self.create_case_query_from_method(func, func_output,
107
136
  self.parsed_output_type,
108
137
  self.mutual_exclusive,
109
138
  case, case_dict,
110
139
  *args, **kwargs)
111
140
  output = self.rdr.fit_case(case_query, expert=self.expert,
112
- update_existing_rules=self.update_existing_rules,
113
- viewer=self.viewer)
141
+ update_existing_rules=self.update_existing_rules)
114
142
  return output
115
-
143
+
116
144
  if self.fit and not self.use_generated_classifier and self.ask_now(case_dict):
117
145
  output = fit()
118
146
  else:
119
147
  if self.use_generated_classifier:
120
148
  if self.generated_classifier is None:
121
- model_path = os.path.join(self.rdr_models_dir, self.model_name)
149
+ model_path = os.path.join(self.models_dir, self.model_name)
122
150
  self.generated_classifier = self.rdr.get_rdr_classifier_from_python_file(model_path)
123
151
  output = self.generated_classifier(case)
124
152
  else:
125
153
  output = self.rdr.classify(case)
126
154
  if self.generate_dot_file:
127
155
  eval_rule_tree = self.rdr.get_evaluated_rule_tree()
128
- if not self.not_none_output_found or (eval_rule_tree and len(eval_rule_tree) > 1):
129
- self.rdr.render_evaluated_rule_tree(self.rdr_models_dir + f'/{self.model_name}', show_full_tree=True)
156
+ if not self._not_none_output_found or (eval_rule_tree and len(eval_rule_tree) > 1):
157
+ self.rdr.render_evaluated_rule_tree(self.models_dir + f'/{self.model_name}',
158
+ show_full_tree=True)
130
159
  if eval_rule_tree and len(eval_rule_tree) > 1:
131
- self.not_none_output_found = True
160
+ self._not_none_output_found = True
132
161
 
133
162
  if self.output_name in output:
134
163
  if self.origin_type == list:
@@ -189,7 +218,6 @@ class RDRDecorator:
189
218
  return Case(dict, id(case_dict), case_name, case_dict, **case_dict), case_dict
190
219
 
191
220
  def initialize_rdr_model_name_and_load(self, func: Callable) -> None:
192
- self.viewer = RDRCaseViewer.instances[0] if RDRCaseViewer is not None and len(RDRCaseViewer.instances) > 0 else self.viewer
193
221
  model_file_name = get_func_rdr_model_name(func, include_file_name=True)
194
222
  self.model_name = str_to_snake_case(model_file_name)
195
223
  self.load()
@@ -209,20 +237,8 @@ class RDRDecorator:
209
237
  parsed_output_type.append(ot)
210
238
  return parsed_output_type
211
239
 
212
- def save(self):
213
- """
214
- Save the RDR model to the specified directory.
215
- """
216
- self.rdr.save(self.rdr_models_dir, self.model_name, package_name=self.package_name)
217
-
218
240
  def load(self):
219
241
  """
220
- Load the RDR model from the specified directory.
221
- """
222
- self.rdr = GeneralRDR(save_dir=self.rdr_models_dir, model_name=self.model_name)
223
-
224
- def update_from_python(self):
225
- """
226
- Update the RDR model from a python file.
242
+ Load the RDR model from the specified directory, otherwise create a new one.
227
243
  """
228
- self.rdr.update_from_python(self.rdr_models_dir, parent_package_name=self.package_name)
244
+ self.rdr = GeneralRDR(save_dir=self.models_dir, model_name=self.model_name)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ripple_down_rules
3
- Version: 0.6.49
3
+ Version: 0.6.51
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,14 +1,14 @@
1
- ripple_down_rules/__init__.py,sha256=GyypMGVzsHCBimVk-W61kWH2zma_ldearwOUdhYFOG4,375
1
+ ripple_down_rules/__init__.py,sha256=CEBeSdz-FJtYgbev1cmm7aT0S9BXptU3COOsvgBLT2M,375
2
2
  ripple_down_rules/experts.py,sha256=KXwWCmDrCffu9HW3yNewqWc1e5rnPI5Rnc981w_5M7U,17896
3
3
  ripple_down_rules/failures.py,sha256=ZPypPwKjyVcJkY9YG6p538ld8ZzpdW6CrC1RmJi44Ek,42
4
4
  ripple_down_rules/helpers.py,sha256=sVU1Q7HPbMh57X8zDYP6vj1maDoaVAvnXHpUYIbr_As,5715
5
5
  ripple_down_rules/rdr.py,sha256=M7CEjHIegJWiDZtn-B8Qf9CpMvplAkrFX3HiiyGQC7M,84292
6
- ripple_down_rules/rdr_decorators.py,sha256=sbrbo5qfhnY9dIge4sFlkn4p8yPNBWWQ1Nq-NvMDx20,11756
6
+ ripple_down_rules/rdr_decorators.py,sha256=mu7pZM6tqjkjoKg1m4f5yXhaRL8RiZu75ABaU275qTY,11751
7
7
  ripple_down_rules/rules.py,sha256=L4Ws-x3g5ljE0GrDt4LAib2qtR1C1_Ra4cRcIB-IQDI,28702
8
8
  ripple_down_rules/start-code-server.sh,sha256=otClk7VmDgBOX2TS_cjws6K0UwvgAUJhoA0ugkPCLqQ,949
9
9
  ripple_down_rules/utils.py,sha256=5UL4h-m5Q4l7RyCYqmUmDDxUAkFqPYpFa3QldDYoJUE,80752
10
10
  ripple_down_rules/datastructures/__init__.py,sha256=V2aNgf5C96Y5-IGghra3n9uiefpoIm_QdT7cc_C8cxQ,111
11
- ripple_down_rules/datastructures/callable_expression.py,sha256=rzMrpD5oztaCRlt3hQ2B_xZ09cSuJNkYOCePndfQJRA,13684
11
+ ripple_down_rules/datastructures/callable_expression.py,sha256=Eq8_9wlFIZex3Y3htwg-AKu-B7053f0Qk6dvMo1Nlts,13815
12
12
  ripple_down_rules/datastructures/case.py,sha256=dfLnrjsHIVF2bgbz-4ID7OdQvw68V71btCeTK372P-g,15667
13
13
  ripple_down_rules/datastructures/dataclasses.py,sha256=3vX52WrAHgVyw0LUSgSBOVFaQNTSxU8hQpdr7cW-tSg,13278
14
14
  ripple_down_rules/datastructures/enums.py,sha256=CvcROl8fE7A6uTbMfs2lLpyxwS_ZFtFcQlBDDKFfoHc,6059
@@ -18,8 +18,8 @@ ripple_down_rules/user_interface/ipython_custom_shell.py,sha256=GQf5Je5szkPH17g4
18
18
  ripple_down_rules/user_interface/object_diagram.py,sha256=FEa2HaYR9QmTE6NsOwBvZ0jqmu3DKyg6mig2VE5ZP4Y,4956
19
19
  ripple_down_rules/user_interface/prompt.py,sha256=WPbw_8_-8SpF2ISyRZRuFwPKBEuGC4HaX3lbCPFHhh8,10314
20
20
  ripple_down_rules/user_interface/template_file_creator.py,sha256=uSbosZS15MOR3Nv7M3MrFuoiKXyP4cBId-EK3I6stHM,13660
21
- ripple_down_rules-0.6.49.dist-info/licenses/LICENSE,sha256=ixuiBLtpoK3iv89l7ylKkg9rs2GzF9ukPH7ynZYzK5s,35148
22
- ripple_down_rules-0.6.49.dist-info/METADATA,sha256=-xhdW2zRfqAdIa_deC_DHETUVV5_8PLWVe0YTDN4VXg,48294
23
- ripple_down_rules-0.6.49.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
24
- ripple_down_rules-0.6.49.dist-info/top_level.txt,sha256=VeoLhEhyK46M1OHwoPbCQLI1EifLjChqGzhQ6WEUqeM,18
25
- ripple_down_rules-0.6.49.dist-info/RECORD,,
21
+ ripple_down_rules-0.6.51.dist-info/licenses/LICENSE,sha256=ixuiBLtpoK3iv89l7ylKkg9rs2GzF9ukPH7ynZYzK5s,35148
22
+ ripple_down_rules-0.6.51.dist-info/METADATA,sha256=uq85MFVRjE4NgR42SGGABOKLLbigoHn776Pa_P74oaU,48294
23
+ ripple_down_rules-0.6.51.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
24
+ ripple_down_rules-0.6.51.dist-info/top_level.txt,sha256=VeoLhEhyK46M1OHwoPbCQLI1EifLjChqGzhQ6WEUqeM,18
25
+ ripple_down_rules-0.6.51.dist-info/RECORD,,