ripple-down-rules 0.6.1__py3-none-any.whl → 0.6.6__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 +21 -1
- ripple_down_rules/datastructures/callable_expression.py +24 -7
- ripple_down_rules/datastructures/case.py +12 -11
- ripple_down_rules/datastructures/dataclasses.py +135 -14
- ripple_down_rules/datastructures/enums.py +29 -86
- ripple_down_rules/datastructures/field_info.py +177 -0
- ripple_down_rules/datastructures/tracked_object.py +208 -0
- ripple_down_rules/experts.py +141 -50
- ripple_down_rules/failures.py +4 -0
- ripple_down_rules/helpers.py +75 -8
- ripple_down_rules/predicates.py +97 -0
- ripple_down_rules/rdr.py +712 -96
- ripple_down_rules/rdr_decorators.py +164 -112
- ripple_down_rules/rules.py +351 -114
- ripple_down_rules/user_interface/gui.py +66 -41
- ripple_down_rules/user_interface/ipython_custom_shell.py +46 -9
- ripple_down_rules/user_interface/prompt.py +80 -60
- ripple_down_rules/user_interface/template_file_creator.py +13 -8
- ripple_down_rules/utils.py +537 -53
- {ripple_down_rules-0.6.1.dist-info → ripple_down_rules-0.6.6.dist-info}/METADATA +4 -1
- ripple_down_rules-0.6.6.dist-info/RECORD +28 -0
- ripple_down_rules-0.6.1.dist-info/RECORD +0 -24
- {ripple_down_rules-0.6.1.dist-info → ripple_down_rules-0.6.6.dist-info}/WHEEL +0 -0
- {ripple_down_rules-0.6.1.dist-info → ripple_down_rules-0.6.6.dist-info}/licenses/LICENSE +0 -0
- {ripple_down_rules-0.6.1.dist-info → ripple_down_rules-0.6.6.dist-info}/top_level.txt +0 -0
ripple_down_rules/__init__.py
CHANGED
@@ -1,5 +1,25 @@
|
|
1
|
-
__version__ = "0.6.
|
1
|
+
__version__ = "0.6.6"
|
2
2
|
|
3
3
|
import logging
|
4
|
+
import sys
|
5
|
+
|
4
6
|
logger = logging.Logger("rdr")
|
5
7
|
logger.setLevel(logging.INFO)
|
8
|
+
|
9
|
+
try:
|
10
|
+
from PyQt6.QtWidgets import QApplication
|
11
|
+
app = QApplication(sys.argv)
|
12
|
+
except ImportError:
|
13
|
+
pass
|
14
|
+
|
15
|
+
|
16
|
+
# Trigger patch
|
17
|
+
try:
|
18
|
+
from .predicates import *
|
19
|
+
from .datastructures.tracked_object import TrackedObjectMixin
|
20
|
+
from .datastructures.dataclasses import CaseQuery
|
21
|
+
from .rdr_decorators import RDRDecorator
|
22
|
+
from .rdr import MultiClassRDR, SingleClassRDR, GeneralRDR
|
23
|
+
import ripple_down_rules_meta._apply_overrides
|
24
|
+
except ImportError:
|
25
|
+
pass
|
@@ -95,7 +95,7 @@ class CallableExpression(SubclassJSONSerializer):
|
|
95
95
|
encapsulating_function_name: str = "_get_value"
|
96
96
|
|
97
97
|
def __init__(self, user_input: Optional[str] = None,
|
98
|
-
conclusion_type: Optional[Tuple[Type]] = None,
|
98
|
+
conclusion_type: Optional[Tuple[Type, ...]] = None,
|
99
99
|
expression_tree: Optional[AST] = None,
|
100
100
|
scope: Optional[Dict[str, Any]] = None,
|
101
101
|
conclusion: Optional[Any] = None,
|
@@ -116,13 +116,24 @@ class CallableExpression(SubclassJSONSerializer):
|
|
116
116
|
if user_input is None:
|
117
117
|
user_input = build_user_input_from_conclusion(conclusion)
|
118
118
|
self.conclusion: Optional[Any] = conclusion
|
119
|
-
|
119
|
+
if "def " in user_input:
|
120
|
+
self.user_defined_name = user_input.split('(')[0].replace('def ', '')
|
121
|
+
else:
|
122
|
+
self.user_defined_name = user_input
|
123
|
+
if f"def {self.encapsulating_function_name}" not in user_input:
|
124
|
+
user_input = encapsulate_user_input(user_input, self.get_encapsulating_function())
|
125
|
+
self._user_input: str = user_input
|
120
126
|
if conclusion_type is not None:
|
121
127
|
if is_iterable(conclusion_type):
|
122
128
|
conclusion_type = tuple(conclusion_type)
|
123
129
|
else:
|
124
130
|
conclusion_type = (conclusion_type,)
|
125
131
|
self.conclusion_type = conclusion_type
|
132
|
+
self.expected_types: Set[Type] = set(conclusion_type) if conclusion_type is not None else 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)
|
126
137
|
self.scope: Optional[Dict[str, Any]] = scope if scope is not None else {}
|
127
138
|
self.scope = get_used_scope(self.user_input, self.scope)
|
128
139
|
self.expression_tree: AST = expression_tree if expression_tree else parse_string_to_expression(self.user_input)
|
@@ -140,6 +151,8 @@ class CallableExpression(SubclassJSONSerializer):
|
|
140
151
|
|
141
152
|
def __call__(self, case: Any, **kwargs) -> Any:
|
142
153
|
try:
|
154
|
+
# if not self.mutually_exclusive:
|
155
|
+
# self.expected_types.update({list, set})
|
143
156
|
if self.user_input is not None:
|
144
157
|
if not isinstance(case, Case):
|
145
158
|
case = create_case(case, max_recursion_idx=3)
|
@@ -151,8 +164,7 @@ class CallableExpression(SubclassJSONSerializer):
|
|
151
164
|
if self.mutually_exclusive and issubclass(type(output), (list, set)):
|
152
165
|
raise ValueError(f"Mutually exclusive types cannot be lists or sets, got {type(output)}")
|
153
166
|
output_types = {type(o) for o in make_list(output)}
|
154
|
-
output_types.
|
155
|
-
if not are_results_subclass_of_types(output_types, self.conclusion_type):
|
167
|
+
if not are_results_subclass_of_types(output_types, self.expected_types):
|
156
168
|
raise ValueError(f"Not all result types {output_types} are subclasses of expected types"
|
157
169
|
f" {self.conclusion_type}")
|
158
170
|
return output
|
@@ -161,7 +173,7 @@ class CallableExpression(SubclassJSONSerializer):
|
|
161
173
|
else:
|
162
174
|
raise ValueError("Either user_input or conclusion must be provided.")
|
163
175
|
except Exception as e:
|
164
|
-
raise ValueError(f"Error during evaluation: {e}")
|
176
|
+
raise ValueError(f"Error during evaluation: {e}, user_input: {self.user_input}")
|
165
177
|
|
166
178
|
def combine_with(self, other: 'CallableExpression') -> 'CallableExpression':
|
167
179
|
"""
|
@@ -172,7 +184,8 @@ class CallableExpression(SubclassJSONSerializer):
|
|
172
184
|
new_user_input = (f"{cond1_user_input}\n"
|
173
185
|
f"{cond2_user_input}\n"
|
174
186
|
f"return _cond1(case) and _cond2(case)")
|
175
|
-
return CallableExpression(new_user_input, conclusion_type=self.conclusion_type
|
187
|
+
return CallableExpression(new_user_input, conclusion_type=self.conclusion_type,
|
188
|
+
mutually_exclusive=self.mutually_exclusive)
|
176
189
|
|
177
190
|
def update_user_input_from_file(self, file_path: str, function_name: str):
|
178
191
|
"""
|
@@ -214,6 +227,10 @@ class CallableExpression(SubclassJSONSerializer):
|
|
214
227
|
Set the user input.
|
215
228
|
"""
|
216
229
|
if value is not None:
|
230
|
+
if "def " in value:
|
231
|
+
self.user_defined_name = value.split('(')[0].replace('def ', '')
|
232
|
+
else:
|
233
|
+
self.user_defined_name = value
|
217
234
|
self._user_input = encapsulate_user_input(value, self.get_encapsulating_function())
|
218
235
|
self.scope = get_used_scope(self.user_input, self.scope)
|
219
236
|
self.expression_tree = parse_string_to_expression(self.user_input)
|
@@ -299,7 +316,7 @@ def parse_string_to_expression(expression_str: str) -> AST:
|
|
299
316
|
:param expression_str: The string which will be parsed.
|
300
317
|
:return: The parsed expression.
|
301
318
|
"""
|
302
|
-
if not expression_str.startswith(CallableExpression.
|
319
|
+
if not expression_str.startswith(f"def {CallableExpression.encapsulating_function_name}"):
|
303
320
|
expression_str = encapsulate_user_input(expression_str, CallableExpression.get_encapsulating_function())
|
304
321
|
mode = 'exec' if expression_str.startswith('def') else 'eval'
|
305
322
|
tree = ast.parse(expression_str, mode=mode)
|
@@ -20,19 +20,20 @@ if TYPE_CHECKING:
|
|
20
20
|
|
21
21
|
class Case(UserDict, SubclassJSONSerializer):
|
22
22
|
"""
|
23
|
-
A collection of attributes that represents a set of
|
24
|
-
the names of the attributes and the values are the attributes. All are stored in lower case
|
23
|
+
A collection of attributes that represents a set of attributes of a case. This is a dictionary where the keys are
|
24
|
+
the names of the attributes and the values are the attributes. All are stored in lower case, and can be accessed
|
25
|
+
using the dot notation as well as the dictionary access notation.
|
25
26
|
"""
|
26
27
|
|
27
28
|
def __init__(self, _obj_type: Type, _id: Optional[Hashable] = None,
|
28
29
|
_name: Optional[str] = None, original_object: Optional[Any] = None, **kwargs):
|
29
30
|
"""
|
30
|
-
Create a new
|
31
|
+
Create a new case.
|
31
32
|
|
32
|
-
:param _obj_type: The type of the object that the
|
33
|
-
:param _id: The id of the
|
34
|
-
:param _name: The semantic name that describes the
|
35
|
-
:param kwargs: The attributes of the
|
33
|
+
:param _obj_type: The original type of the object that the case represents.
|
34
|
+
:param _id: The id of the case.
|
35
|
+
:param _name: The semantic name that describes the case.
|
36
|
+
:param kwargs: The attributes of the case.
|
36
37
|
"""
|
37
38
|
super().__init__(kwargs)
|
38
39
|
self._original_object = original_object
|
@@ -43,12 +44,12 @@ class Case(UserDict, SubclassJSONSerializer):
|
|
43
44
|
@classmethod
|
44
45
|
def from_obj(cls, obj: Any, obj_name: Optional[str] = None, max_recursion_idx: int = 3) -> Case:
|
45
46
|
"""
|
46
|
-
Create a
|
47
|
+
Create a case from an object.
|
47
48
|
|
48
|
-
:param obj: The object to create a
|
49
|
+
:param obj: The object to create a case from.
|
49
50
|
:param max_recursion_idx: The maximum recursion index to prevent infinite recursion.
|
50
51
|
:param obj_name: The name of the object.
|
51
|
-
:return: The
|
52
|
+
:return: The case that represents the object.
|
52
53
|
"""
|
53
54
|
return create_case(obj, max_recursion_idx=max_recursion_idx, obj_name=obj_name)
|
54
55
|
|
@@ -129,7 +130,7 @@ class Case(UserDict, SubclassJSONSerializer):
|
|
129
130
|
@dataclass
|
130
131
|
class CaseAttributeValue(SubclassJSONSerializer):
|
131
132
|
"""
|
132
|
-
|
133
|
+
Encapsulates a single value of a case attribute, it adds an id to the value.
|
133
134
|
"""
|
134
135
|
id: Hashable
|
135
136
|
"""
|
@@ -1,19 +1,22 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
3
|
import inspect
|
4
|
-
import
|
4
|
+
import uuid
|
5
5
|
from dataclasses import dataclass, field
|
6
6
|
|
7
|
-
import
|
7
|
+
from colorama import Fore, Style
|
8
8
|
from omegaconf import MISSING
|
9
9
|
from sqlalchemy.orm import DeclarativeBase as SQLTable
|
10
|
-
from typing_extensions import Any, Optional, Dict, Type, Tuple, Union, List,
|
10
|
+
from typing_extensions import Any, Optional, Dict, Type, Tuple, Union, List, Set, Callable, TYPE_CHECKING
|
11
11
|
|
12
|
-
from ..utils import get_method_name, get_function_import_data, get_function_representation
|
13
12
|
from .callable_expression import CallableExpression
|
14
13
|
from .case import create_case, Case
|
15
|
-
from ..utils import copy_case, make_list, make_set, get_origin_and_args_from_type_hint,
|
16
|
-
|
14
|
+
from ..utils import copy_case, make_list, make_set, get_origin_and_args_from_type_hint, render_tree, \
|
15
|
+
get_function_representation, get_method_object_from_pytest_request
|
16
|
+
|
17
|
+
if TYPE_CHECKING:
|
18
|
+
from ..rdr import RippleDownRules
|
19
|
+
from ..rules import Rule
|
17
20
|
|
18
21
|
|
19
22
|
@dataclass
|
@@ -31,7 +34,7 @@ class CaseQuery:
|
|
31
34
|
"""
|
32
35
|
The name of the attribute.
|
33
36
|
"""
|
34
|
-
_attribute_types: Tuple[Type]
|
37
|
+
_attribute_types: Tuple[Type, ...]
|
35
38
|
"""
|
36
39
|
The type(s) of the attribute.
|
37
40
|
"""
|
@@ -57,9 +60,14 @@ class CaseQuery:
|
|
57
60
|
The executable scenario is the root callable that recreates the situation that the case is
|
58
61
|
created in, for example, when the case is created from a test function, this would be the test function itself.
|
59
62
|
"""
|
63
|
+
this_case_target_value: Optional[Any] = None
|
64
|
+
"""
|
65
|
+
The non relational case query instance target value.
|
66
|
+
"""
|
60
67
|
_target: Optional[CallableExpression] = None
|
61
68
|
"""
|
62
|
-
The target
|
69
|
+
The relational target (the evaluatable conclusion of the rule) which is a callable expression that varies with
|
70
|
+
the case.
|
63
71
|
"""
|
64
72
|
default_value: Optional[Any] = None
|
65
73
|
"""
|
@@ -92,6 +100,38 @@ class CaseQuery:
|
|
92
100
|
"""
|
93
101
|
The type hints of the function arguments. This is used to recreate the function signature.
|
94
102
|
"""
|
103
|
+
rdr: Optional[RippleDownRules] = None
|
104
|
+
"""
|
105
|
+
The Ripple Down Rules that was used to answer the case query.
|
106
|
+
"""
|
107
|
+
|
108
|
+
def render_rule_tree(self, filepath: Optional[str] = None, view: bool = False):
|
109
|
+
if self.rdr is None:
|
110
|
+
return
|
111
|
+
render_tree(self.rdr.start_rule, use_dot_exporter=True, filename=filepath, view=view)
|
112
|
+
|
113
|
+
@property
|
114
|
+
def current_value_str(self):
|
115
|
+
return (f"{Fore.MAGENTA}Current value of {Fore.CYAN}{self.name}{Fore.MAGENTA} of type(s) "
|
116
|
+
f"{Fore.CYAN}({self.core_attribute_type_str}){Fore.MAGENTA}: "
|
117
|
+
f"{Fore.WHITE}{self.current_value}{Style.RESET_ALL}")
|
118
|
+
|
119
|
+
@property
|
120
|
+
def current_value(self) -> Any:
|
121
|
+
"""
|
122
|
+
:return: The current value of the attribute.
|
123
|
+
"""
|
124
|
+
if not hasattr(self.case, self.attribute_name):
|
125
|
+
return None
|
126
|
+
|
127
|
+
attr_value = getattr(self.case, self.attribute_name)
|
128
|
+
|
129
|
+
if attr_value is None:
|
130
|
+
return attr_value
|
131
|
+
elif self.mutually_exclusive:
|
132
|
+
return attr_value
|
133
|
+
else:
|
134
|
+
return list({v for v in make_list(attr_value) if isinstance(v, self.core_attribute_type)})
|
95
135
|
|
96
136
|
@property
|
97
137
|
def case_type(self) -> Type:
|
@@ -139,13 +179,20 @@ class CaseQuery:
|
|
139
179
|
attribute_types_str = f"Union[{', '.join([t.__name__ for t in self.core_attribute_type])}]"
|
140
180
|
else:
|
141
181
|
attribute_types_str = self.core_attribute_type[0].__name__
|
142
|
-
if
|
182
|
+
if not self.mutually_exclusive:
|
143
183
|
return f"List[{attribute_types_str}]"
|
144
184
|
else:
|
145
185
|
return attribute_types_str
|
146
186
|
|
147
187
|
@property
|
148
|
-
def
|
188
|
+
def core_attribute_type_str(self) -> str:
|
189
|
+
"""
|
190
|
+
:return: The names of the core types of the attribute.
|
191
|
+
"""
|
192
|
+
return ','.join([t.__name__ for t in self.core_attribute_type])
|
193
|
+
|
194
|
+
@property
|
195
|
+
def core_attribute_type(self) -> Tuple[Type, ...]:
|
149
196
|
"""
|
150
197
|
:return: The core type of the attribute.
|
151
198
|
"""
|
@@ -247,7 +294,7 @@ class CaseQuery:
|
|
247
294
|
conditions=self.conditions, is_function=self.is_function,
|
248
295
|
function_args_type_hints=self.function_args_type_hints,
|
249
296
|
case_factory=self.case_factory, case_factory_idx=self.case_factory_idx,
|
250
|
-
case_conf=self.case_conf, scenario=self.scenario)
|
297
|
+
case_conf=self.case_conf, scenario=self.scenario, rdr=self.rdr)
|
251
298
|
|
252
299
|
|
253
300
|
@dataclass
|
@@ -264,6 +311,12 @@ class CaseFactoryMetaData:
|
|
264
311
|
factory_idx: Optional[int] = None
|
265
312
|
case_conf: Optional[CaseConf] = None
|
266
313
|
scenario: Optional[Callable] = None
|
314
|
+
pytest_request: Optional[Callable] = field(hash=False, compare=False, default=None)
|
315
|
+
this_case_target_value: Optional[Any] = None
|
316
|
+
|
317
|
+
def __post_init__(self):
|
318
|
+
if self.pytest_request is not None and self.scenario is None:
|
319
|
+
self.scenario = get_method_object_from_pytest_request(self.pytest_request)
|
267
320
|
|
268
321
|
@classmethod
|
269
322
|
def from_case_query(cls, case_query: CaseQuery) -> CaseFactoryMetaData:
|
@@ -280,8 +333,76 @@ class CaseFactoryMetaData:
|
|
280
333
|
return (f"CaseFactoryMetaData("
|
281
334
|
f"factory_method={factory_method_repr}, "
|
282
335
|
f"factory_idx={self.factory_idx}, "
|
283
|
-
f"case_conf={self.case_conf},"
|
284
|
-
f"
|
336
|
+
f"case_conf={self.case_conf}, "
|
337
|
+
f"scenario={scenario_repr}, "
|
338
|
+
f"this_case_target_value={self.this_case_target_value})")
|
285
339
|
|
286
340
|
def __str__(self):
|
287
|
-
return self.__repr__()
|
341
|
+
return self.__repr__()
|
342
|
+
|
343
|
+
|
344
|
+
@dataclass
|
345
|
+
class RDRConclusion:
|
346
|
+
"""
|
347
|
+
This dataclass represents a conclusion of a Ripple Down Rule.
|
348
|
+
It contains the conclusion expression, the type of the conclusion, and the scope in which it is evaluated.
|
349
|
+
"""
|
350
|
+
_conclusion: Any
|
351
|
+
"""
|
352
|
+
The conclusion value.
|
353
|
+
"""
|
354
|
+
_frozen_case: Any
|
355
|
+
"""
|
356
|
+
The frozen case that the conclusion was made for.
|
357
|
+
"""
|
358
|
+
_rule: Rule
|
359
|
+
"""
|
360
|
+
The rule that gave this conclusion.
|
361
|
+
"""
|
362
|
+
_rdr: RippleDownRules
|
363
|
+
"""
|
364
|
+
The Ripple Down Rules that classified the case and produced this conclusion.
|
365
|
+
"""
|
366
|
+
_id: int = field(default_factory=lambda: uuid.uuid4().int)
|
367
|
+
"""
|
368
|
+
The unique identifier of the conclusion.
|
369
|
+
"""
|
370
|
+
def __getattribute__(self, name: str) -> Any:
|
371
|
+
if name.startswith('_'):
|
372
|
+
return object.__getattribute__(self, name)
|
373
|
+
else:
|
374
|
+
conclusion = object.__getattribute__(self, "_conclusion")
|
375
|
+
|
376
|
+
value = getattr(conclusion, name)
|
377
|
+
|
378
|
+
self._record_dependency(name)
|
379
|
+
|
380
|
+
return value
|
381
|
+
|
382
|
+
def __setattr__(self, name, value):
|
383
|
+
if name.startswith('_'):
|
384
|
+
object.__setattr__(self, name, value)
|
385
|
+
else:
|
386
|
+
setattr(self._wrapped, name, value)
|
387
|
+
|
388
|
+
def _record_dependency(self, attr_name):
|
389
|
+
# Inspect stack to find instance of CallableExpression
|
390
|
+
for frame_info in inspect.stack():
|
391
|
+
func_name = frame_info.function
|
392
|
+
local_self = frame_info.frame.f_locals.get("self", None)
|
393
|
+
if (
|
394
|
+
func_name == "__call__" and
|
395
|
+
local_self is not None and
|
396
|
+
type(local_self) is CallableExpression
|
397
|
+
):
|
398
|
+
self._used_in_tracker = True
|
399
|
+
print("RDRConclusion used inside CallableExpression")
|
400
|
+
break
|
401
|
+
|
402
|
+
def __hash__(self):
|
403
|
+
return hash(self.id)
|
404
|
+
|
405
|
+
def __eq__(self, other):
|
406
|
+
if not isinstance(other, RDRConclusion):
|
407
|
+
return False
|
408
|
+
return self.id == other.id
|
@@ -7,6 +7,19 @@ from typing_extensions import List, Dict, Any, Type
|
|
7
7
|
from ripple_down_rules.utils import SubclassJSONSerializer
|
8
8
|
|
9
9
|
|
10
|
+
class ExitStatus(Enum):
|
11
|
+
"""
|
12
|
+
Describes the status at exit of the user interface.
|
13
|
+
"""
|
14
|
+
CLOSE = auto()
|
15
|
+
"""
|
16
|
+
The user wants to stop the program.
|
17
|
+
"""
|
18
|
+
SUCCESS = auto()
|
19
|
+
"""
|
20
|
+
The user completed the task successfully.
|
21
|
+
"""
|
22
|
+
|
10
23
|
class InteractionMode(Enum):
|
11
24
|
"""
|
12
25
|
The interaction mode of the RDR.
|
@@ -80,20 +93,6 @@ class Stop(Category):
|
|
80
93
|
stop = "stop"
|
81
94
|
|
82
95
|
|
83
|
-
class ExpressionParser(Enum):
|
84
|
-
"""
|
85
|
-
Parsers for expressions to evaluate and encapsulate the expression into a callable function.
|
86
|
-
"""
|
87
|
-
ASTVisitor: int = auto()
|
88
|
-
"""
|
89
|
-
Generic python Abstract Syntax Tree that detects variables, attributes, binary/boolean expressions , ...etc.
|
90
|
-
"""
|
91
|
-
SQLAlchemy: int = auto()
|
92
|
-
"""
|
93
|
-
Specific for SQLAlchemy expressions on ORM Tables.
|
94
|
-
"""
|
95
|
-
|
96
|
-
|
97
96
|
class PromptFor(Enum):
|
98
97
|
"""
|
99
98
|
The reason of the prompt. (e.g. get conditions, conclusions, or affirmation).
|
@@ -118,51 +117,6 @@ class PromptFor(Enum):
|
|
118
117
|
return self.__str__()
|
119
118
|
|
120
119
|
|
121
|
-
class CategoricalValue(Enum):
|
122
|
-
"""
|
123
|
-
A categorical value is a value that is a category.
|
124
|
-
"""
|
125
|
-
|
126
|
-
def __eq__(self, other):
|
127
|
-
if isinstance(other, CategoricalValue):
|
128
|
-
return self.name == other.name
|
129
|
-
elif isinstance(other, str):
|
130
|
-
return self.name == other
|
131
|
-
return self.name == other
|
132
|
-
|
133
|
-
def __hash__(self):
|
134
|
-
return hash(self.name)
|
135
|
-
|
136
|
-
@classmethod
|
137
|
-
def to_list(cls):
|
138
|
-
return list(cls._value2member_map_.keys())
|
139
|
-
|
140
|
-
@classmethod
|
141
|
-
def from_str(cls, category: str):
|
142
|
-
return cls[category.lower()]
|
143
|
-
|
144
|
-
@classmethod
|
145
|
-
def from_strs(cls, categories: List[str]):
|
146
|
-
return [cls.from_str(c) for c in categories]
|
147
|
-
|
148
|
-
def __str__(self):
|
149
|
-
return self.name
|
150
|
-
|
151
|
-
def __repr__(self):
|
152
|
-
return self.__str__()
|
153
|
-
|
154
|
-
|
155
|
-
class RDRMode(Enum):
|
156
|
-
Propositional = auto()
|
157
|
-
"""
|
158
|
-
Propositional mode, the mode where the rules are propositional.
|
159
|
-
"""
|
160
|
-
Relational = auto()
|
161
|
-
"""
|
162
|
-
Relational mode, the mode where the rules are relational.
|
163
|
-
"""
|
164
|
-
|
165
|
-
|
166
120
|
class MCRDRMode(Enum):
|
167
121
|
"""
|
168
122
|
The modes of the MultiClassRDR.
|
@@ -196,34 +150,23 @@ class RDREdge(Enum):
|
|
196
150
|
"""
|
197
151
|
Next edge, the edge that represents the next rule to be evaluated.
|
198
152
|
"""
|
199
|
-
|
200
|
-
|
201
|
-
class ValueType(Enum):
|
202
|
-
Unary = auto()
|
203
|
-
"""
|
204
|
-
Unary value type (eg. null).
|
153
|
+
Filter = "filter if"
|
205
154
|
"""
|
206
|
-
|
155
|
+
Filter edge, the edge that represents the filter condition.
|
207
156
|
"""
|
208
|
-
|
157
|
+
Empty = ""
|
209
158
|
"""
|
210
|
-
|
211
|
-
"""
|
212
|
-
Discrete value type (eg. 1, 2, 3).
|
213
|
-
"""
|
214
|
-
Continuous = auto()
|
215
|
-
"""
|
216
|
-
Continuous value type (eg. 1.0, 2.5, 3.4).
|
217
|
-
"""
|
218
|
-
Nominal = auto()
|
219
|
-
"""
|
220
|
-
Nominal value type (eg. red, blue, green), categories where the values have no natural order.
|
221
|
-
"""
|
222
|
-
Ordinal = auto()
|
223
|
-
"""
|
224
|
-
Ordinal value type (eg. low, medium, high), categories where the values have a natural order.
|
225
|
-
"""
|
226
|
-
Iterable = auto()
|
227
|
-
"""
|
228
|
-
Iterable value type (eg. [1, 2, 3]).
|
159
|
+
Empty edge, used for example for the root/input node of the tree.
|
229
160
|
"""
|
161
|
+
|
162
|
+
@classmethod
|
163
|
+
def from_value(cls, value: str) -> RDREdge:
|
164
|
+
"""
|
165
|
+
Convert a string value to an RDREdge enum.
|
166
|
+
|
167
|
+
:param value: The string that represents the edge type.
|
168
|
+
:return: The RDREdge enum.
|
169
|
+
"""
|
170
|
+
if value not in cls._value2member_map_:
|
171
|
+
raise ValueError(f"RDREdge {value} is not supported.")
|
172
|
+
return cls._value2member_map_[value]
|