ripple-down-rules 0.2.4__tar.gz → 0.3.0__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.2.4 → ripple_down_rules-0.3.0}/PKG-INFO +1 -1
- {ripple_down_rules-0.2.4 → ripple_down_rules-0.3.0}/pyproject.toml +1 -1
- {ripple_down_rules-0.2.4 → ripple_down_rules-0.3.0}/src/ripple_down_rules/datasets.py +66 -6
- {ripple_down_rules-0.2.4 → ripple_down_rules-0.3.0}/src/ripple_down_rules/datastructures/callable_expression.py +13 -5
- {ripple_down_rules-0.2.4 → ripple_down_rules-0.3.0}/src/ripple_down_rules/datastructures/case.py +33 -5
- {ripple_down_rules-0.2.4 → ripple_down_rules-0.3.0}/src/ripple_down_rules/datastructures/dataclasses.py +30 -8
- {ripple_down_rules-0.2.4 → ripple_down_rules-0.3.0}/src/ripple_down_rules/datastructures/enums.py +30 -1
- {ripple_down_rules-0.2.4 → ripple_down_rules-0.3.0}/src/ripple_down_rules/experts.py +2 -1
- {ripple_down_rules-0.2.4 → ripple_down_rules-0.3.0}/src/ripple_down_rules/prompt.py +215 -109
- {ripple_down_rules-0.2.4 → ripple_down_rules-0.3.0}/src/ripple_down_rules/rdr.py +7 -5
- ripple_down_rules-0.3.0/src/ripple_down_rules/rdr_decorators.py +139 -0
- {ripple_down_rules-0.2.4 → ripple_down_rules-0.3.0}/src/ripple_down_rules/utils.py +162 -18
- {ripple_down_rules-0.2.4 → ripple_down_rules-0.3.0}/src/ripple_down_rules.egg-info/PKG-INFO +1 -1
- {ripple_down_rules-0.2.4 → ripple_down_rules-0.3.0}/src/ripple_down_rules.egg-info/SOURCES.txt +1 -0
- {ripple_down_rules-0.2.4 → ripple_down_rules-0.3.0}/test/test_rdr_alchemy.py +6 -6
- ripple_down_rules-0.3.0/test/test_rdr_decorators.py +27 -0
- {ripple_down_rules-0.2.4 → ripple_down_rules-0.3.0}/test/test_relational_rdr.py +6 -39
- {ripple_down_rules-0.2.4 → ripple_down_rules-0.3.0}/test/test_relational_rdr_alchemy.py +15 -16
- {ripple_down_rules-0.2.4 → ripple_down_rules-0.3.0}/test/test_sql_model.py +4 -4
- ripple_down_rules-0.2.4/src/ripple_down_rules/rdr_decorators.py +0 -55
- {ripple_down_rules-0.2.4 → ripple_down_rules-0.3.0}/LICENSE +0 -0
- {ripple_down_rules-0.2.4 → ripple_down_rules-0.3.0}/README.md +0 -0
- {ripple_down_rules-0.2.4 → ripple_down_rules-0.3.0}/setup.cfg +0 -0
- {ripple_down_rules-0.2.4 → ripple_down_rules-0.3.0}/src/ripple_down_rules/__init__.py +0 -0
- {ripple_down_rules-0.2.4 → ripple_down_rules-0.3.0}/src/ripple_down_rules/datastructures/__init__.py +0 -0
- {ripple_down_rules-0.2.4 → ripple_down_rules-0.3.0}/src/ripple_down_rules/failures.py +0 -0
- {ripple_down_rules-0.2.4 → ripple_down_rules-0.3.0}/src/ripple_down_rules/helpers.py +0 -0
- {ripple_down_rules-0.2.4 → ripple_down_rules-0.3.0}/src/ripple_down_rules/rules.py +0 -0
- {ripple_down_rules-0.2.4 → ripple_down_rules-0.3.0}/src/ripple_down_rules.egg-info/dependency_links.txt +0 -0
- {ripple_down_rules-0.2.4 → ripple_down_rules-0.3.0}/src/ripple_down_rules.egg-info/top_level.txt +0 -0
- {ripple_down_rules-0.2.4 → ripple_down_rules-0.3.0}/test/test_json_serialization.py +0 -0
- {ripple_down_rules-0.2.4 → ripple_down_rules-0.3.0}/test/test_on_mutagenic.py +0 -0
- {ripple_down_rules-0.2.4 → ripple_down_rules-0.3.0}/test/test_rdr.py +0 -0
- {ripple_down_rules-0.2.4 → ripple_down_rules-0.3.0}/test/test_rdr_world.py +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: ripple_down_rules
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.3.0
|
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
|
@@ -6,7 +6,7 @@ build-backend = "setuptools.build_meta"
|
|
6
6
|
|
7
7
|
[project]
|
8
8
|
name = "ripple_down_rules"
|
9
|
-
version = "0.
|
9
|
+
version = "0.3.0"
|
10
10
|
description = "Implements the various versions of Ripple Down Rules (RDR) for knowledge representation and reasoning."
|
11
11
|
readme = "README.md"
|
12
12
|
authors = [{ name = "Abdelrhman Bassiouny", email = "abassiou@uni-bremen.de" }]
|
@@ -2,15 +2,17 @@ from __future__ import annotations
|
|
2
2
|
|
3
3
|
import os
|
4
4
|
import pickle
|
5
|
+
from dataclasses import dataclass, field
|
5
6
|
|
6
7
|
import sqlalchemy
|
7
8
|
from sqlalchemy import ForeignKey
|
8
|
-
from sqlalchemy.orm import MappedAsDataclass, Mapped, mapped_column, relationship
|
9
|
-
from typing_extensions import Tuple, List, Set, Optional
|
9
|
+
from sqlalchemy.orm import MappedAsDataclass, Mapped, mapped_column, relationship, MappedColumn
|
10
|
+
from typing_extensions import Tuple, List, Set, Optional, Self
|
10
11
|
from ucimlrepo import fetch_ucirepo
|
11
12
|
|
12
13
|
from .datastructures.case import Case, create_cases_from_dataframe
|
13
14
|
from .datastructures.enums import Category
|
15
|
+
from .rdr_decorators import RDRDecorator
|
14
16
|
|
15
17
|
|
16
18
|
def load_cached_dataset(cache_file):
|
@@ -106,8 +108,65 @@ class Habitat(Category):
|
|
106
108
|
air = "air"
|
107
109
|
|
108
110
|
|
109
|
-
|
110
|
-
|
111
|
+
class PhysicalObject:
|
112
|
+
"""
|
113
|
+
A physical object is an object that can be contained in a container.
|
114
|
+
"""
|
115
|
+
_rdr_json_dir: str = os.path.join(os.path.dirname(__file__), "../../test/test_results")
|
116
|
+
"""
|
117
|
+
The directory where the RDR serialized JSON files are stored.
|
118
|
+
"""
|
119
|
+
_rdr_python_dir: str = os.path.join(os.path.dirname(__file__), "../../test/test_generated_rdrs")
|
120
|
+
"""
|
121
|
+
The directory where the RDR generated Python files are stored.
|
122
|
+
"""
|
123
|
+
_is_a_robot_rdr: RDRDecorator = RDRDecorator(_rdr_json_dir, (bool,), True,
|
124
|
+
python_dir=_rdr_python_dir)
|
125
|
+
"""
|
126
|
+
The RDR decorator that is used to determine if the object is a robot or not.
|
127
|
+
"""
|
128
|
+
_select_parts_rdr: RDRDecorator = RDRDecorator(_rdr_json_dir, (Self,), False,
|
129
|
+
python_dir=_rdr_python_dir)
|
130
|
+
"""
|
131
|
+
The RDR decorator that is used to determine if the object is a robot or not.
|
132
|
+
"""
|
133
|
+
|
134
|
+
def __init__(self, name: str, contained_objects: Optional[List[PhysicalObject]] = None):
|
135
|
+
self.name: str = name
|
136
|
+
self._contained_objects: List[PhysicalObject] = contained_objects or []
|
137
|
+
|
138
|
+
@property
|
139
|
+
def contained_objects(self) -> List[PhysicalObject]:
|
140
|
+
return self._contained_objects
|
141
|
+
|
142
|
+
@contained_objects.setter
|
143
|
+
def contained_objects(self, value: List[PhysicalObject]):
|
144
|
+
self._contained_objects = value
|
145
|
+
|
146
|
+
@_is_a_robot_rdr.decorator
|
147
|
+
def is_a_robot(self) -> bool:
|
148
|
+
pass
|
149
|
+
|
150
|
+
@_select_parts_rdr.decorator
|
151
|
+
def select_objects_that_are_parts_of_robot(self, objects: List[PhysicalObject], robot: Robot) -> List[PhysicalObject]:
|
152
|
+
pass
|
153
|
+
|
154
|
+
def __str__(self):
|
155
|
+
return self.name
|
156
|
+
|
157
|
+
def __repr__(self):
|
158
|
+
return self.name
|
159
|
+
|
160
|
+
|
161
|
+
class Part(PhysicalObject):
|
162
|
+
...
|
163
|
+
|
164
|
+
|
165
|
+
class Robot(PhysicalObject):
|
166
|
+
|
167
|
+
def __init__(self, name: str, parts: Optional[List[Part]] = None):
|
168
|
+
super().__init__(name)
|
169
|
+
self.parts: List[Part] = parts if parts else []
|
111
170
|
|
112
171
|
|
113
172
|
class Base(sqlalchemy.orm.DeclarativeBase):
|
@@ -119,7 +178,7 @@ class HabitatTable(MappedAsDataclass, Base):
|
|
119
178
|
|
120
179
|
id: Mapped[int] = mapped_column(init=False, primary_key=True, autoincrement=True)
|
121
180
|
habitat: Mapped[Habitat]
|
122
|
-
animal_id = mapped_column(ForeignKey("Animal.id"), init=False)
|
181
|
+
animal_id: MappedColumn = mapped_column(ForeignKey("Animal.id"), init=False)
|
123
182
|
|
124
183
|
def __hash__(self):
|
125
184
|
return hash(self.habitat)
|
@@ -131,7 +190,7 @@ class HabitatTable(MappedAsDataclass, Base):
|
|
131
190
|
return self.__str__()
|
132
191
|
|
133
192
|
|
134
|
-
class
|
193
|
+
class MappedAnimal(MappedAsDataclass, Base):
|
135
194
|
__tablename__ = "Animal"
|
136
195
|
|
137
196
|
id: Mapped[int] = mapped_column(init=False, primary_key=True, autoincrement=True)
|
@@ -155,3 +214,4 @@ class Animal(MappedAsDataclass, Base):
|
|
155
214
|
species: Mapped[Species] = mapped_column(nullable=True)
|
156
215
|
|
157
216
|
habitats: Mapped[Set[HabitatTable]] = relationship(default_factory=set)
|
217
|
+
|
@@ -93,9 +93,12 @@ class CallableExpression(SubclassJSONSerializer):
|
|
93
93
|
"""
|
94
94
|
encapsulating_function: str = "def _get_value(case):"
|
95
95
|
|
96
|
-
def __init__(self, user_input: Optional[str] = None,
|
96
|
+
def __init__(self, user_input: Optional[str] = None,
|
97
|
+
conclusion_type: Optional[Tuple[Type]] = None,
|
97
98
|
expression_tree: Optional[AST] = None,
|
98
|
-
scope: Optional[Dict[str, Any]] = None,
|
99
|
+
scope: Optional[Dict[str, Any]] = None,
|
100
|
+
conclusion: Optional[Any] = None,
|
101
|
+
mutually_exclusive: bool = True):
|
99
102
|
"""
|
100
103
|
Create a callable expression.
|
101
104
|
|
@@ -104,6 +107,8 @@ class CallableExpression(SubclassJSONSerializer):
|
|
104
107
|
:param expression_tree: The AST tree parsed from the user input.
|
105
108
|
:param scope: The scope to use for the callable expression.
|
106
109
|
:param conclusion: The conclusion to use for the callable expression.
|
110
|
+
:param mutually_exclusive: If True, the conclusion is mutually exclusive, i.e. the callable expression can only
|
111
|
+
return one conclusion. If False, the callable expression can return multiple conclusions.
|
107
112
|
"""
|
108
113
|
if user_input is None and conclusion is None:
|
109
114
|
raise ValueError("Either user_input or conclusion must be provided.")
|
@@ -123,6 +128,7 @@ class CallableExpression(SubclassJSONSerializer):
|
|
123
128
|
self.code = compile_expression_to_code(self.expression_tree)
|
124
129
|
self.visitor = VariableVisitor()
|
125
130
|
self.visitor.visit(self.expression_tree)
|
131
|
+
self.mutually_exclusive: bool = mutually_exclusive
|
126
132
|
|
127
133
|
def __call__(self, case: Any, **kwargs) -> Any:
|
128
134
|
try:
|
@@ -134,8 +140,8 @@ class CallableExpression(SubclassJSONSerializer):
|
|
134
140
|
if output is None:
|
135
141
|
output = scope['_get_value'](case)
|
136
142
|
if self.conclusion_type is not None:
|
137
|
-
if
|
138
|
-
raise ValueError(f"
|
143
|
+
if self.mutually_exclusive and issubclass(type(output), (list, set)):
|
144
|
+
raise ValueError(f"Mutually exclusive types cannot be lists or sets, got {type(output)}")
|
139
145
|
output_types = {type(o) for o in make_list(output)}
|
140
146
|
output_types.add(type(output))
|
141
147
|
if not are_results_subclass_of_types(output_types, self.conclusion_type):
|
@@ -229,6 +235,7 @@ class CallableExpression(SubclassJSONSerializer):
|
|
229
235
|
"scope": {k: get_full_class_name(v) for k, v in self.scope.items()
|
230
236
|
if hasattr(v, '__module__') and hasattr(v, '__name__')},
|
231
237
|
"conclusion": conclusion_to_json(self.conclusion),
|
238
|
+
"mutually_exclusive": self.mutually_exclusive,
|
232
239
|
}
|
233
240
|
|
234
241
|
@classmethod
|
@@ -237,7 +244,8 @@ class CallableExpression(SubclassJSONSerializer):
|
|
237
244
|
conclusion_type=tuple(get_type_from_string(t) for t in data["conclusion_type"])
|
238
245
|
if data["conclusion_type"] else None,
|
239
246
|
scope={k: get_type_from_string(v) for k, v in data["scope"].items()},
|
240
|
-
conclusion=SubclassJSONSerializer.from_json(data["conclusion"])
|
247
|
+
conclusion=SubclassJSONSerializer.from_json(data["conclusion"]),
|
248
|
+
mutually_exclusive=data["mutually_exclusive"])
|
241
249
|
|
242
250
|
|
243
251
|
def compile_expression_to_code(expression_tree: AST) -> Any:
|
{ripple_down_rules-0.2.4 → ripple_down_rules-0.3.0}/src/ripple_down_rules/datastructures/case.py
RENAMED
@@ -11,7 +11,7 @@ from sqlalchemy.orm import DeclarativeBase as SQLTable, MappedColumn as SQLColum
|
|
11
11
|
from typing_extensions import Any, Optional, Dict, Type, Set, Hashable, Union, List, TYPE_CHECKING
|
12
12
|
|
13
13
|
from ..utils import make_set, row_to_dict, table_rows_as_str, get_value_type_from_type_hint, SubclassJSONSerializer, \
|
14
|
-
get_full_class_name, get_type_from_string, make_list, is_iterable, serialize_dataclass, dataclass_to_dict
|
14
|
+
get_full_class_name, get_type_from_string, make_list, is_iterable, serialize_dataclass, dataclass_to_dict, copy_case
|
15
15
|
|
16
16
|
if TYPE_CHECKING:
|
17
17
|
from ripple_down_rules.rules import Rule
|
@@ -65,7 +65,7 @@ class Case(UserDict, SubclassJSONSerializer):
|
|
65
65
|
new_list.extend(make_list(value))
|
66
66
|
super().__setitem__(name, new_list)
|
67
67
|
else:
|
68
|
-
super().__setitem__(name,
|
68
|
+
super().__setitem__(name, value)
|
69
69
|
else:
|
70
70
|
super().__setitem__(name, value)
|
71
71
|
setattr(self, name, self[name])
|
@@ -102,6 +102,29 @@ class Case(UserDict, SubclassJSONSerializer):
|
|
102
102
|
data[k] = SubclassJSONSerializer.from_json(v)
|
103
103
|
return cls(_obj_type=obj_type, _id=id_, _name=name, **data)
|
104
104
|
|
105
|
+
def __deepcopy__(self, memo: Dict[Hashable, Any]) -> Case:
|
106
|
+
"""
|
107
|
+
Create a deep copy of the case.
|
108
|
+
|
109
|
+
:param memo: A dictionary to keep track of objects that have already been copied.
|
110
|
+
:return: A deep copy of the case.
|
111
|
+
"""
|
112
|
+
new_case = Case(self._obj_type, _id=self._id, _name=self._name, original_object=self._original_object)
|
113
|
+
for k, v in self.items():
|
114
|
+
new_case[k] = deepcopy(v)
|
115
|
+
return new_case
|
116
|
+
|
117
|
+
def __copy__(self) -> Case:
|
118
|
+
"""
|
119
|
+
Create a shallow copy of the case.
|
120
|
+
|
121
|
+
:return: A shallow copy of the case.
|
122
|
+
"""
|
123
|
+
new_case = Case(self._obj_type, _id=self._id, _name=self._name, original_object=self._original_object)
|
124
|
+
for k, v in self.items():
|
125
|
+
new_case[k] = copy(v)
|
126
|
+
return new_case
|
127
|
+
|
105
128
|
|
106
129
|
@dataclass
|
107
130
|
class CaseAttributeValue(SubclassJSONSerializer):
|
@@ -220,11 +243,16 @@ def create_case(obj: Any, recursion_idx: int = 0, max_recursion_idx: int = 0,
|
|
220
243
|
return create_cases_from_dataframe(obj, obj_name)
|
221
244
|
if isinstance(obj, Case) or (is_dataclass(obj) and not isinstance(obj, SQLTable)):
|
222
245
|
return obj
|
223
|
-
if ((recursion_idx > max_recursion_idx)
|
246
|
+
if ((recursion_idx > max_recursion_idx)
|
247
|
+
or (obj.__class__.__module__ == "builtins" and not isinstance(obj, (list, set, dict)))
|
224
248
|
or (obj.__class__ in [MetaData, registry])):
|
225
249
|
return Case(type(obj), _id=id(obj), _name=obj_name, original_object=obj,
|
226
250
|
**{obj_name or obj.__class__.__name__: make_list(obj) if parent_is_iterable else obj})
|
227
251
|
case = Case(type(obj), _id=id(obj), _name=obj_name, original_object=obj)
|
252
|
+
if isinstance(obj, dict):
|
253
|
+
for k, v in obj.items():
|
254
|
+
case = create_or_update_case_from_attribute(v, k, obj, obj_name, recursion_idx,
|
255
|
+
max_recursion_idx, parent_is_iterable, case)
|
228
256
|
for attr in dir(obj):
|
229
257
|
if attr.startswith("_") or callable(getattr(obj, attr)):
|
230
258
|
continue
|
@@ -322,9 +350,9 @@ def show_current_and_corner_cases(case: Any, targets: Optional[Dict[str, Any]] =
|
|
322
350
|
if last_evaluated_rule and last_evaluated_rule.fired:
|
323
351
|
corner_row_dict = dataclass_to_dict(last_evaluated_rule.corner_case)
|
324
352
|
else:
|
325
|
-
case_dict = case
|
353
|
+
case_dict = copy_case(case)
|
326
354
|
if last_evaluated_rule and last_evaluated_rule.fired:
|
327
|
-
corner_row_dict = corner_case
|
355
|
+
corner_row_dict = copy_case(corner_case)
|
328
356
|
|
329
357
|
if corner_row_dict:
|
330
358
|
corner_conclusion = last_evaluated_rule.conclusion(case)
|
@@ -1,15 +1,17 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
3
|
import inspect
|
4
|
+
import typing
|
4
5
|
from dataclasses import dataclass, field
|
5
6
|
|
6
7
|
import typing_extensions
|
7
8
|
from sqlalchemy.orm import DeclarativeBase as SQLTable
|
8
|
-
from typing_extensions import Any, Optional, Dict, Type, Tuple, Union, List
|
9
|
+
from typing_extensions import Any, Optional, Dict, Type, Tuple, Union, List, get_origin, Set
|
9
10
|
|
10
11
|
from .callable_expression import CallableExpression
|
11
12
|
from .case import create_case, Case
|
12
|
-
from ..utils import copy_case, make_list, make_set
|
13
|
+
from ..utils import copy_case, make_list, make_set, get_origin_and_args_from_type_hint, get_value_type_from_type_hint, \
|
14
|
+
typing_to_python_type
|
13
15
|
|
14
16
|
|
15
17
|
@dataclass
|
@@ -61,6 +63,15 @@ class CaseQuery:
|
|
61
63
|
"""
|
62
64
|
The conditions that must be satisfied for the target value to be valid.
|
63
65
|
"""
|
66
|
+
is_function: bool = False
|
67
|
+
"""
|
68
|
+
Whether the case is a dict representing the arguments of an actual function or not,
|
69
|
+
most likely means it came from RDRDecorator, the the rdr takes function arguments and outputs the function output.
|
70
|
+
"""
|
71
|
+
function_args_type_hints: Optional[Dict[str, Type]] = None
|
72
|
+
"""
|
73
|
+
The type hints of the function arguments. This is used to recreate the function signature.
|
74
|
+
"""
|
64
75
|
|
65
76
|
@property
|
66
77
|
def case_type(self) -> Type:
|
@@ -117,10 +128,20 @@ class CaseQuery:
|
|
117
128
|
"""
|
118
129
|
:return: The type of the attribute.
|
119
130
|
"""
|
120
|
-
if not
|
121
|
-
self._attribute_types = tuple(
|
122
|
-
|
123
|
-
|
131
|
+
if not isinstance(self._attribute_types, tuple):
|
132
|
+
self._attribute_types = tuple(make_set(self._attribute_types))
|
133
|
+
origin, args = get_origin_and_args_from_type_hint(self._attribute_types)
|
134
|
+
if origin is not None:
|
135
|
+
att_types = make_set(origin)
|
136
|
+
if origin in (list, set, tuple, List, Set, Union, Tuple):
|
137
|
+
att_types.update(make_set(args))
|
138
|
+
elif origin in (dict, Dict):
|
139
|
+
# ignore the key type
|
140
|
+
if args and len(args) > 1:
|
141
|
+
att_types.update(make_set(args[1]))
|
142
|
+
self._attribute_types = tuple(att_types)
|
143
|
+
if not self.mutually_exclusive and (list not in self._attribute_types):
|
144
|
+
self._attribute_types = tuple(make_list(self._attribute_types) + [set, list])
|
124
145
|
return self._attribute_types
|
125
146
|
|
126
147
|
@attribute_type.setter
|
@@ -151,7 +172,7 @@ class CaseQuery:
|
|
151
172
|
"""
|
152
173
|
if (self._target is not None) and (not isinstance(self._target, CallableExpression)):
|
153
174
|
self._target = CallableExpression(conclusion=self._target, conclusion_type=self.attribute_type,
|
154
|
-
scope=self.scope)
|
175
|
+
scope=self.scope, mutually_exclusive=self.mutually_exclusive)
|
155
176
|
return self._target
|
156
177
|
|
157
178
|
@target.setter
|
@@ -195,4 +216,5 @@ class CaseQuery:
|
|
195
216
|
return CaseQuery(self.original_case, self.attribute_name, self.attribute_type,
|
196
217
|
self.mutually_exclusive, _target=self.target, default_value=self.default_value,
|
197
218
|
scope=self.scope, _case=copy_case(self.case), _target_value=self.target_value,
|
198
|
-
conditions=self.conditions
|
219
|
+
conditions=self.conditions, is_function=self.is_function,
|
220
|
+
function_args_type_hints=self.function_args_type_hints)
|
{ripple_down_rules-0.2.4 → ripple_down_rules-0.3.0}/src/ripple_down_rules/datastructures/enums.py
RENAMED
@@ -2,11 +2,40 @@ from __future__ import annotations
|
|
2
2
|
|
3
3
|
from enum import auto, Enum
|
4
4
|
|
5
|
-
from typing_extensions import List, Dict, Any
|
5
|
+
from typing_extensions import List, Dict, Any, Type
|
6
6
|
|
7
7
|
from ripple_down_rules.utils import SubclassJSONSerializer
|
8
8
|
|
9
9
|
|
10
|
+
class Editor(str, Enum):
|
11
|
+
"""
|
12
|
+
The editor that is used to edit the rules.
|
13
|
+
"""
|
14
|
+
Pycharm = "pycharm"
|
15
|
+
"""
|
16
|
+
PyCharm editor.
|
17
|
+
"""
|
18
|
+
Code = "code"
|
19
|
+
"""
|
20
|
+
Visual Studio Code editor.
|
21
|
+
"""
|
22
|
+
CodeServer = "code-server"
|
23
|
+
"""
|
24
|
+
Visual Studio Code server editor.
|
25
|
+
"""
|
26
|
+
@classmethod
|
27
|
+
def from_str(cls, editor: str) -> Editor:
|
28
|
+
"""
|
29
|
+
Convert a string value to an Editor enum.
|
30
|
+
|
31
|
+
:param editor: The string that represents the editor name.
|
32
|
+
:return: The Editor enum.
|
33
|
+
"""
|
34
|
+
if editor not in cls._value2member_map_:
|
35
|
+
raise ValueError(f"Editor {editor} is not supported.")
|
36
|
+
return cls._value2member_map_[editor]
|
37
|
+
|
38
|
+
|
10
39
|
class Category(str, SubclassJSONSerializer, Enum):
|
11
40
|
|
12
41
|
@classmethod
|
@@ -138,7 +138,8 @@ class Human(Expert):
|
|
138
138
|
expert_input = self.all_expert_answers.pop(0)
|
139
139
|
if expert_input is not None:
|
140
140
|
expression = CallableExpression(expert_input, case_query.attribute_type,
|
141
|
-
scope=case_query.scope
|
141
|
+
scope=case_query.scope,
|
142
|
+
mutually_exclusive=case_query.mutually_exclusive)
|
142
143
|
else:
|
143
144
|
show_current_and_corner_cases(case_query.case)
|
144
145
|
expert_input, expression = prompt_user_for_expression(case_query, PromptFor.Conclusion)
|