ripple-down-rules 0.1.67__py3-none-any.whl → 0.1.68__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/datastructures/case.py +7 -4
- ripple_down_rules/rdr.py +45 -29
- ripple_down_rules/rules.py +3 -3
- ripple_down_rules/utils.py +143 -38
- {ripple_down_rules-0.1.67.dist-info → ripple_down_rules-0.1.68.dist-info}/METADATA +1 -1
- {ripple_down_rules-0.1.67.dist-info → ripple_down_rules-0.1.68.dist-info}/RECORD +9 -9
- {ripple_down_rules-0.1.67.dist-info → ripple_down_rules-0.1.68.dist-info}/WHEEL +0 -0
- {ripple_down_rules-0.1.67.dist-info → ripple_down_rules-0.1.68.dist-info}/licenses/LICENSE +0 -0
- {ripple_down_rules-0.1.67.dist-info → ripple_down_rules-0.1.68.dist-info}/top_level.txt +0 -0
@@ -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
|
14
|
+
get_full_class_name, get_type_from_string, make_list, is_iterable, serialize_dataclass, dataclass_to_dict
|
15
15
|
|
16
16
|
if TYPE_CHECKING:
|
17
17
|
from ripple_down_rules.rules import Rule
|
@@ -99,8 +99,7 @@ class Case(UserDict, SubclassJSONSerializer):
|
|
99
99
|
obj_type = get_type_from_string(data.pop("_obj_type"))
|
100
100
|
name = data.pop("_name")
|
101
101
|
for k, v in data.items():
|
102
|
-
|
103
|
-
data[k] = SubclassJSONSerializer.from_json(v)
|
102
|
+
data[k] = SubclassJSONSerializer.from_json(v)
|
104
103
|
return cls(_obj_type=obj_type, _id=id_, _name=name, **data)
|
105
104
|
|
106
105
|
|
@@ -219,7 +218,7 @@ def create_case(obj: Any, recursion_idx: int = 0, max_recursion_idx: int = 0,
|
|
219
218
|
obj_name = obj_name or obj.__class__.__name__
|
220
219
|
if isinstance(obj, DataFrame):
|
221
220
|
return create_cases_from_dataframe(obj, obj_name)
|
222
|
-
if isinstance(obj, Case):
|
221
|
+
if isinstance(obj, Case) or (is_dataclass(obj) and not isinstance(obj, SQLTable)):
|
223
222
|
return obj
|
224
223
|
if ((recursion_idx > max_recursion_idx) or (obj.__class__.__module__ == "builtins")
|
225
224
|
or (obj.__class__ in [MetaData, registry])):
|
@@ -318,6 +317,10 @@ def show_current_and_corner_cases(case: Any, targets: Optional[Dict[str, Any]] =
|
|
318
317
|
case_dict = row_to_dict(case)
|
319
318
|
if last_evaluated_rule and last_evaluated_rule.fired:
|
320
319
|
corner_row_dict = row_to_dict(last_evaluated_rule.corner_case)
|
320
|
+
elif is_dataclass(case):
|
321
|
+
case_dict = dataclass_to_dict(case)
|
322
|
+
if last_evaluated_rule and last_evaluated_rule.fired:
|
323
|
+
corner_row_dict = dataclass_to_dict(last_evaluated_rule.corner_case)
|
321
324
|
else:
|
322
325
|
case_dict = case
|
323
326
|
if last_evaluated_rule and last_evaluated_rule.fired:
|
ripple_down_rules/rdr.py
CHANGED
@@ -211,17 +211,17 @@ class RDRWithCodeWriter(RippleDownRules, ABC):
|
|
211
211
|
func_def = f"def classify(case: {self.case_type.__name__}) -> {self.conclusion_type_hint}:\n"
|
212
212
|
file_name = file_path + f"/{self.generated_python_file_name}.py"
|
213
213
|
defs_file_name = file_path + f"/{self.generated_python_defs_file_name}.py"
|
214
|
-
imports = self._get_imports()
|
214
|
+
imports, defs_imports = self._get_imports()
|
215
215
|
# clear the files first
|
216
216
|
with open(defs_file_name, "w") as f:
|
217
|
-
f.write(
|
217
|
+
f.write(defs_imports + "\n\n")
|
218
218
|
with open(file_name, "w") as f:
|
219
219
|
imports += f"from .{self.generated_python_defs_file_name} import *\n"
|
220
220
|
imports += f"from ripple_down_rules.rdr import {self.__class__.__name__}\n"
|
221
221
|
f.write(imports + "\n\n")
|
222
|
-
f.write(f"conclusion_type = ({', '.join([ct.__name__ for ct in self.conclusion_type])},)\n
|
223
|
-
f.write(f"type_ = {self.__class__.__name__}\n
|
224
|
-
f.write(func_def)
|
222
|
+
f.write(f"conclusion_type = ({', '.join([ct.__name__ for ct in self.conclusion_type])},)\n")
|
223
|
+
f.write(f"type_ = {self.__class__.__name__}\n")
|
224
|
+
f.write(f"\n\n{func_def}")
|
225
225
|
f.write(f"{' ' * 4}if not isinstance(case, Case):\n"
|
226
226
|
f"{' ' * 4} case = create_case(case, max_recursion_idx=3)\n""")
|
227
227
|
self.write_rules_as_source_code_to_file(self.start_rule, f, " " * 4, defs_file=defs_file_name)
|
@@ -234,17 +234,11 @@ class RDRWithCodeWriter(RippleDownRules, ABC):
|
|
234
234
|
"""
|
235
235
|
pass
|
236
236
|
|
237
|
-
def _get_imports(self) -> str:
|
237
|
+
def _get_imports(self) -> Tuple[str, str]:
|
238
238
|
"""
|
239
239
|
:return: The imports for the generated python file of the RDR as a string.
|
240
240
|
"""
|
241
|
-
|
242
|
-
if self.case_type.__module__ != "builtins":
|
243
|
-
imports += f"from {self.case_type.__module__} import {self.case_type.__name__}\n"
|
244
|
-
for conclusion_type in self.conclusion_type:
|
245
|
-
if conclusion_type.__module__ != "builtins":
|
246
|
-
imports += f"from {conclusion_type.__module__} import {conclusion_type.__name__}\n"
|
247
|
-
imports += "from ripple_down_rules.datastructures.case import Case, create_case\n"
|
241
|
+
defs_imports = ""
|
248
242
|
for rule in [self.start_rule] + list(self.start_rule.descendants):
|
249
243
|
if not rule.conditions:
|
250
244
|
continue
|
@@ -255,10 +249,21 @@ class RDRWithCodeWriter(RippleDownRules, ABC):
|
|
255
249
|
if not hasattr(v, "__module__") or not hasattr(v, "__name__"):
|
256
250
|
continue
|
257
251
|
new_imports = f"from {v.__module__} import {v.__name__}\n"
|
258
|
-
if new_imports in
|
252
|
+
if new_imports in defs_imports:
|
259
253
|
continue
|
260
|
-
|
261
|
-
|
254
|
+
defs_imports += new_imports
|
255
|
+
imports = ""
|
256
|
+
if self.case_type.__module__ != "builtins":
|
257
|
+
new_import = f"from {self.case_type.__module__} import {self.case_type.__name__}\n"
|
258
|
+
if new_import not in defs_imports:
|
259
|
+
imports += new_import
|
260
|
+
for conclusion_type in self.conclusion_type:
|
261
|
+
if conclusion_type.__module__ != "builtins":
|
262
|
+
new_import = f"from {conclusion_type.__module__} import {conclusion_type.__name__}\n"
|
263
|
+
if new_import not in defs_imports:
|
264
|
+
imports += new_import
|
265
|
+
imports += "from ripple_down_rules.datastructures.case import Case, create_case\n"
|
266
|
+
return imports, defs_imports
|
262
267
|
|
263
268
|
def get_rdr_classifier_from_python_file(self, package_name: str) -> Callable[[Any], Any]:
|
264
269
|
"""
|
@@ -293,7 +298,11 @@ class RDRWithCodeWriter(RippleDownRules, ABC):
|
|
293
298
|
"""
|
294
299
|
:return: The default generated python file name.
|
295
300
|
"""
|
296
|
-
|
301
|
+
if isinstance(self.start_rule.corner_case, Case):
|
302
|
+
name = self.start_rule.corner_case._name
|
303
|
+
else:
|
304
|
+
name = self.start_rule.corner_case.__class__.__name__
|
305
|
+
return f"{name.lower()}_{self.attribute_name}_{self.acronym.lower()}"
|
297
306
|
|
298
307
|
@property
|
299
308
|
def generated_python_defs_file_name(self) -> str:
|
@@ -539,13 +548,21 @@ class MultiClassRDR(RDRWithCodeWriter):
|
|
539
548
|
|
540
549
|
@property
|
541
550
|
def conclusion_type_hint(self) -> str:
|
542
|
-
|
551
|
+
conclusion_types = [ct.__name__ for ct in self.conclusion_type if ct not in [list, set]]
|
552
|
+
if len(conclusion_types) == 1:
|
553
|
+
return f"Set[{conclusion_types[0]}]"
|
554
|
+
else:
|
555
|
+
return f"Set[Union[{', '.join(conclusion_types)}]]"
|
543
556
|
|
544
|
-
def _get_imports(self) -> str:
|
545
|
-
imports = super()._get_imports()
|
546
|
-
|
557
|
+
def _get_imports(self) -> Tuple[str, str]:
|
558
|
+
imports, defs_imports = super()._get_imports()
|
559
|
+
conclusion_types = [ct for ct in self.conclusion_type if ct not in [list, set]]
|
560
|
+
if len(conclusion_types) == 1:
|
561
|
+
imports += f"from typing_extensions import Set\n"
|
562
|
+
else:
|
563
|
+
imports += "from typing_extensions import Set, Union\n"
|
547
564
|
imports += "from ripple_down_rules.utils import make_set\n"
|
548
|
-
return imports
|
565
|
+
return imports, defs_imports
|
549
566
|
|
550
567
|
def update_start_rule(self, case_query: CaseQuery, expert: Expert):
|
551
568
|
"""
|
@@ -867,7 +884,11 @@ class GeneralRDR(RippleDownRules):
|
|
867
884
|
"""
|
868
885
|
:return: The default generated python file name.
|
869
886
|
"""
|
870
|
-
|
887
|
+
if isinstance(self.start_rule.corner_case, Case):
|
888
|
+
name = self.start_rule.corner_case._name
|
889
|
+
else:
|
890
|
+
name = self.start_rule.corner_case.__class__.__name__
|
891
|
+
return f"{name}_rdr".lower()
|
871
892
|
|
872
893
|
@property
|
873
894
|
def conclusion_type_hint(self) -> str:
|
@@ -882,17 +903,12 @@ class GeneralRDR(RippleDownRules):
|
|
882
903
|
"""
|
883
904
|
imports = ""
|
884
905
|
# add type hints
|
885
|
-
imports += f"from typing_extensions import Dict, Any
|
906
|
+
imports += f"from typing_extensions import Dict, Any\n"
|
886
907
|
# import rdr type
|
887
908
|
imports += f"from ripple_down_rules.rdr import GeneralRDR\n"
|
888
909
|
# add case type
|
889
910
|
imports += f"from ripple_down_rules.datastructures.case import Case, create_case\n"
|
890
911
|
imports += f"from {self.case_type.__module__} import {self.case_type.__name__}\n"
|
891
|
-
# add conclusion type imports
|
892
|
-
for rdr in self.start_rules_dict.values():
|
893
|
-
for conclusion_type in rdr.conclusion_type:
|
894
|
-
if conclusion_type.__module__ != "builtins":
|
895
|
-
imports += f"from {conclusion_type.__module__} import {conclusion_type.__name__}\n"
|
896
912
|
# add rdr python generated functions.
|
897
913
|
for rdr_key, rdr in self.start_rules_dict.items():
|
898
914
|
imports += (f"from {file_path.strip('./')}"
|
ripple_down_rules/rules.py
CHANGED
@@ -93,7 +93,7 @@ class Rule(NodeMixin, SubclassJSONSerializer, ABC):
|
|
93
93
|
conclusion_func, conclusion_func_call = self._conclusion_source_code(conclusion, parent_indent=parent_indent)
|
94
94
|
if conclusion_func is not None:
|
95
95
|
with open(defs_file, 'a') as f:
|
96
|
-
f.write(conclusion_func + "\n\n")
|
96
|
+
f.write(conclusion_func.strip() + "\n\n\n")
|
97
97
|
return conclusion_func_call
|
98
98
|
|
99
99
|
@abstractmethod
|
@@ -120,7 +120,7 @@ class Rule(NodeMixin, SubclassJSONSerializer, ABC):
|
|
120
120
|
conditions_lines[0] = re.sub(r"def (\w+)", new_function_name, conditions_lines[0])
|
121
121
|
def_code = "\n".join(conditions_lines)
|
122
122
|
with open(defs_file, 'a') as f:
|
123
|
-
f.write(def_code + "\n\n")
|
123
|
+
f.write(def_code.strip() + "\n\n\n")
|
124
124
|
return f"\n{parent_indent}{if_clause} {new_function_name.replace('def ', '')}(case):\n"
|
125
125
|
|
126
126
|
@abstractmethod
|
@@ -131,7 +131,7 @@ class Rule(NodeMixin, SubclassJSONSerializer, ABC):
|
|
131
131
|
json_serialization = {"conditions": self.conditions.to_json(),
|
132
132
|
"conclusion": conclusion_to_json(self.conclusion),
|
133
133
|
"parent": self.parent.json_serialization if self.parent else None,
|
134
|
-
"corner_case":
|
134
|
+
"corner_case": SubclassJSONSerializer.to_json_static(self.corner_case),
|
135
135
|
"conclusion_name": self.conclusion_name,
|
136
136
|
"weight": self.weight}
|
137
137
|
return json_serialization
|
ripple_down_rules/utils.py
CHANGED
@@ -7,6 +7,7 @@ import json
|
|
7
7
|
import logging
|
8
8
|
import os
|
9
9
|
import re
|
10
|
+
import uuid
|
10
11
|
from collections import UserDict
|
11
12
|
from copy import deepcopy
|
12
13
|
from dataclasses import is_dataclass, fields
|
@@ -322,7 +323,7 @@ def extract_dependencies(code_lines):
|
|
322
323
|
return required_lines
|
323
324
|
|
324
325
|
|
325
|
-
def serialize_dataclass(obj: Any) ->
|
326
|
+
def serialize_dataclass(obj: Any, seen=None) -> Any:
|
326
327
|
"""
|
327
328
|
Recursively serialize a dataclass to a dictionary. If the dataclass contains any nested dataclasses, they will be
|
328
329
|
serialized as well. If the object is not a dataclass, it will be returned as is.
|
@@ -330,24 +331,44 @@ def serialize_dataclass(obj: Any) -> Union[Dict, Any]:
|
|
330
331
|
:param obj: The dataclass to serialize.
|
331
332
|
:return: The serialized dataclass as a dictionary or the object itself if it is not a dataclass.
|
332
333
|
"""
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
334
|
+
if seen is None:
|
335
|
+
seen = {}
|
336
|
+
|
337
|
+
obj_id = id(obj)
|
338
|
+
if obj_id in seen:
|
339
|
+
return {'$ref': seen[obj_id]}
|
340
|
+
|
341
|
+
if is_dataclass(obj):
|
342
|
+
uid = str(uuid.uuid4())
|
343
|
+
seen[obj_id] = uid
|
344
|
+
result = {
|
345
|
+
'$id': uid,
|
346
|
+
"__dataclass__": f"{obj.__class__.__module__}.{obj.__class__.__qualname__}",
|
347
|
+
'fields': {}
|
348
|
+
}
|
349
|
+
for f in fields(obj):
|
350
|
+
value = getattr(obj, f.name)
|
351
|
+
result['fields'][f.name] = serialize_dataclass(value, seen)
|
352
|
+
return result
|
353
|
+
elif isinstance(obj, list):
|
354
|
+
return [serialize_dataclass(v, seen) for v in obj]
|
355
|
+
elif isinstance(obj, dict):
|
356
|
+
return {k: serialize_dataclass(v, seen) for k, v in obj.items()}
|
357
|
+
else:
|
358
|
+
try:
|
359
|
+
json.dumps(obj) # Check if the object is JSON serializable
|
345
360
|
return obj
|
361
|
+
except TypeError:
|
362
|
+
return None
|
346
363
|
|
347
|
-
return recursive_convert(obj)
|
348
364
|
|
365
|
+
def deserialize_dataclass(data: Any, refs: Optional[Dict[str, Any]] = None) -> Any:
|
366
|
+
refs = {} if refs is None else refs
|
367
|
+
preloaded = preload_serialized_objects(data, refs)
|
368
|
+
return resolve_refs(preloaded, refs)
|
349
369
|
|
350
|
-
|
370
|
+
|
371
|
+
def preload_serialized_objects(data: Any, refs: Dict[str, Any] = None) -> Any:
|
351
372
|
"""
|
352
373
|
Recursively deserialize a dataclass from a dictionary, if the dictionary contains a key "__dataclass__" (Most likely
|
353
374
|
created by the serialize_dataclass function), it will be treated as a dataclass and deserialized accordingly,
|
@@ -356,25 +377,81 @@ def deserialize_dataclass(data: dict) -> Any:
|
|
356
377
|
:param data: The dictionary to deserialize.
|
357
378
|
:return: The deserialized dataclass.
|
358
379
|
"""
|
380
|
+
if refs is None:
|
381
|
+
refs = {}
|
382
|
+
|
383
|
+
if isinstance(data, dict):
|
384
|
+
|
385
|
+
if '$ref' in data:
|
386
|
+
ref_id = data['$ref']
|
387
|
+
if ref_id not in refs:
|
388
|
+
return {'$ref': data['$ref']}
|
389
|
+
return refs[ref_id]
|
390
|
+
|
391
|
+
elif '$id' in data and '__dataclass__' in data and 'fields' in data:
|
392
|
+
cls_path = data['__dataclass__']
|
393
|
+
module_name, class_name = cls_path.rsplit('.', 1)
|
394
|
+
cls = getattr(importlib.import_module(module_name), class_name)
|
395
|
+
|
396
|
+
dummy_instance = cls.__new__(cls) # Don't call __init__ yet
|
397
|
+
refs[data['$id']] = dummy_instance
|
398
|
+
|
399
|
+
for f in fields(cls):
|
400
|
+
raw_value = data['fields'].get(f.name)
|
401
|
+
value = preload_serialized_objects(raw_value, refs)
|
402
|
+
setattr(dummy_instance, f.name, value)
|
403
|
+
|
404
|
+
return dummy_instance
|
359
405
|
|
360
|
-
def recursive_load(obj):
|
361
|
-
if isinstance(obj, dict) and "__dataclass__" in obj:
|
362
|
-
module_name, class_name = obj["__dataclass__"].rsplit(".", 1)
|
363
|
-
module = importlib.import_module(module_name)
|
364
|
-
cls: Type = getattr(module, class_name)
|
365
|
-
field_values = {
|
366
|
-
k: recursive_load(v)
|
367
|
-
for k, v in obj["fields"].items()
|
368
|
-
}
|
369
|
-
return cls(**field_values)
|
370
|
-
elif isinstance(obj, list):
|
371
|
-
return [recursive_load(item) for item in obj]
|
372
|
-
elif isinstance(obj, dict):
|
373
|
-
return {k: recursive_load(v) for k, v in obj.items()}
|
374
406
|
else:
|
375
|
-
return
|
407
|
+
return {k: preload_serialized_objects(v, refs) for k, v in data.items()}
|
408
|
+
|
409
|
+
elif isinstance(data, list):
|
410
|
+
return [preload_serialized_objects(item, refs) for item in data]
|
411
|
+
elif isinstance(data, dict):
|
412
|
+
return {k: preload_serialized_objects(v, refs) for k, v in data.items()}
|
413
|
+
|
414
|
+
return data # Primitive
|
376
415
|
|
377
|
-
|
416
|
+
|
417
|
+
def resolve_refs(obj, refs, seen=None):
|
418
|
+
if seen is None:
|
419
|
+
seen = {}
|
420
|
+
|
421
|
+
obj_id = id(obj)
|
422
|
+
if obj_id in seen:
|
423
|
+
return seen[obj_id]
|
424
|
+
|
425
|
+
# Resolve if dict with $ref
|
426
|
+
if isinstance(obj, dict) and '$ref' in obj:
|
427
|
+
ref_id = obj['$ref']
|
428
|
+
if ref_id not in refs:
|
429
|
+
raise KeyError(f"$ref to unknown ID: {ref_id}")
|
430
|
+
return refs[ref_id]
|
431
|
+
|
432
|
+
elif is_dataclass(obj):
|
433
|
+
seen[obj_id] = obj # Mark before diving deeper
|
434
|
+
for f in fields(obj):
|
435
|
+
val = getattr(obj, f.name)
|
436
|
+
resolved = resolve_refs(val, refs, seen)
|
437
|
+
setattr(obj, f.name, resolved)
|
438
|
+
return obj
|
439
|
+
|
440
|
+
elif isinstance(obj, list):
|
441
|
+
resolved_list = []
|
442
|
+
seen[obj_id] = resolved_list
|
443
|
+
for item in obj:
|
444
|
+
resolved_list.append(resolve_refs(item, refs, seen))
|
445
|
+
return resolved_list
|
446
|
+
|
447
|
+
elif isinstance(obj, dict):
|
448
|
+
resolved_dict = {}
|
449
|
+
seen[obj_id] = resolved_dict
|
450
|
+
for k, v in obj.items():
|
451
|
+
resolved_dict[k] = resolve_refs(v, refs, seen)
|
452
|
+
return resolved_dict
|
453
|
+
|
454
|
+
return obj # Primitive
|
378
455
|
|
379
456
|
|
380
457
|
def typing_to_python_type(typing_hint: Type) -> Type:
|
@@ -547,6 +624,7 @@ class SubclassJSONSerializer:
|
|
547
624
|
Classes that inherit from this class can be serialized and deserialized automatically by calling this classes
|
548
625
|
'from_json' method.
|
549
626
|
"""
|
627
|
+
data_class_refs = {}
|
550
628
|
|
551
629
|
def to_json_file(self, filename: str):
|
552
630
|
"""
|
@@ -560,8 +638,14 @@ class SubclassJSONSerializer:
|
|
560
638
|
json.dump(data, f, indent=4)
|
561
639
|
return data
|
562
640
|
|
641
|
+
@staticmethod
|
642
|
+
def to_json_static(obj) -> Dict[str, Any]:
|
643
|
+
if is_dataclass(obj):
|
644
|
+
return serialize_dataclass(obj)
|
645
|
+
return {"_type": get_full_class_name(obj.__class__), **obj._to_json()}
|
646
|
+
|
563
647
|
def to_json(self) -> Dict[str, Any]:
|
564
|
-
return
|
648
|
+
return self.to_json_static(self)
|
565
649
|
|
566
650
|
def _to_json(self) -> Dict[str, Any]:
|
567
651
|
"""
|
@@ -582,7 +666,7 @@ class SubclassJSONSerializer:
|
|
582
666
|
raise NotImplementedError()
|
583
667
|
|
584
668
|
@classmethod
|
585
|
-
def from_json_file(cls, filename: str):
|
669
|
+
def from_json_file(cls, filename: str) -> Any:
|
586
670
|
"""
|
587
671
|
Create an instance of the subclass from the data in the given json file.
|
588
672
|
|
@@ -592,7 +676,9 @@ class SubclassJSONSerializer:
|
|
592
676
|
filename += ".json"
|
593
677
|
with open(filename, "r") as f:
|
594
678
|
scrdr_json = json.load(f)
|
595
|
-
|
679
|
+
deserialized_obj = cls.from_json(scrdr_json)
|
680
|
+
cls.data_class_refs.clear()
|
681
|
+
return deserialized_obj
|
596
682
|
|
597
683
|
@classmethod
|
598
684
|
def from_json(cls, data: Dict[str, Any]) -> Self:
|
@@ -604,11 +690,17 @@ class SubclassJSONSerializer:
|
|
604
690
|
"""
|
605
691
|
if data is None:
|
606
692
|
return None
|
607
|
-
if
|
693
|
+
if isinstance(data, list):
|
694
|
+
# if the data is a list, deserialize it
|
695
|
+
return [cls.from_json(d) for d in data]
|
696
|
+
elif isinstance(data, dict):
|
697
|
+
if '__dataclass__' in data:
|
698
|
+
# if the data is a dataclass, deserialize it
|
699
|
+
return deserialize_dataclass(data, cls.data_class_refs)
|
700
|
+
elif '_type' not in data:
|
701
|
+
return {k: cls.from_json(v) for k, v in data.items()}
|
702
|
+
elif not isinstance(data, dict):
|
608
703
|
return data
|
609
|
-
if '__dataclass__' in data:
|
610
|
-
# if the data is a dataclass, deserialize it
|
611
|
-
return deserialize_dataclass(data)
|
612
704
|
|
613
705
|
# check if type module is builtins
|
614
706
|
data_type = get_type_from_string(data["_type"])
|
@@ -757,6 +849,19 @@ def row_to_dict(obj):
|
|
757
849
|
}
|
758
850
|
|
759
851
|
|
852
|
+
def dataclass_to_dict(obj):
|
853
|
+
"""
|
854
|
+
Convert a dataclass to a dictionary.
|
855
|
+
|
856
|
+
:param obj: The dataclass to convert.
|
857
|
+
:return: The dictionary representation of the dataclass.
|
858
|
+
"""
|
859
|
+
if is_dataclass(obj):
|
860
|
+
return {f.name: getattr(obj, f.name) for f in fields(obj) if not f.name.startswith("_")}
|
861
|
+
else:
|
862
|
+
raise ValueError(f"Object {obj} is not a dataclass.")
|
863
|
+
|
864
|
+
|
760
865
|
def get_attribute_name(obj: Any, attribute: Optional[Any] = None, attribute_type: Optional[Type] = None,
|
761
866
|
possible_value: Optional[Any] = None) -> Optional[str]:
|
762
867
|
"""
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: ripple_down_rules
|
3
|
-
Version: 0.1.
|
3
|
+
Version: 0.1.68
|
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
|
@@ -4,17 +4,17 @@ ripple_down_rules/experts.py,sha256=JGVvSNiWhm4FpRpg76f98tl8Ii_C7x_aWD9FxD-JDLQ,
|
|
4
4
|
ripple_down_rules/failures.py,sha256=E6ajDUsw3Blom8eVLbA7d_Qnov2conhtZ0UmpQ9ZtSE,302
|
5
5
|
ripple_down_rules/helpers.py,sha256=TvTJU0BA3dPcAyzvZFvAu7jZqsp8Lu0HAAwvuizlGjg,2018
|
6
6
|
ripple_down_rules/prompt.py,sha256=gdLV2qhq-1kbmlJhVmkCwGGFhIeDxV0p5u2hwrbGUpk,6370
|
7
|
-
ripple_down_rules/rdr.py,sha256=
|
7
|
+
ripple_down_rules/rdr.py,sha256=E8YXSU_VMd3e_LJItjzbBjr0g1KXhhU_HL4FMAgdczc,42203
|
8
8
|
ripple_down_rules/rdr_decorators.py,sha256=8SclpceI3EtrsbuukWJu8HGLh7Q1ZCgYGLX-RPlG-w0,2018
|
9
|
-
ripple_down_rules/rules.py,sha256=
|
10
|
-
ripple_down_rules/utils.py,sha256=
|
9
|
+
ripple_down_rules/rules.py,sha256=WfMWzgfI_5Tqv8a2k7jkpGdvwu-zYCG36EmpCl7GiEQ,16576
|
10
|
+
ripple_down_rules/utils.py,sha256=INGRVIUH-SgssUW2T9gUMRf-XyJ3rxY_kuJUxfbsyOg,39683
|
11
11
|
ripple_down_rules/datastructures/__init__.py,sha256=V2aNgf5C96Y5-IGghra3n9uiefpoIm_QdT7cc_C8cxQ,111
|
12
12
|
ripple_down_rules/datastructures/callable_expression.py,sha256=4AxguiVeSTxzYti5AyOWHdqWzS6KdSPWqf0unR9ZuhA,9636
|
13
|
-
ripple_down_rules/datastructures/case.py,sha256=
|
13
|
+
ripple_down_rules/datastructures/case.py,sha256=nJDKOjyhGLx4gt0sHxJNxBLdy9X2SLcDn89_SmKzwoc,14035
|
14
14
|
ripple_down_rules/datastructures/dataclasses.py,sha256=TAOAeEvh0BeTis3rEHu8rpCeqNNhU0vK3to0JaBwTio,5961
|
15
15
|
ripple_down_rules/datastructures/enums.py,sha256=RdyPUp9Ls1QuLmkcMMkBbCWrmXIZI4xWuM-cLPYZhR0,4666
|
16
|
-
ripple_down_rules-0.1.
|
17
|
-
ripple_down_rules-0.1.
|
18
|
-
ripple_down_rules-0.1.
|
19
|
-
ripple_down_rules-0.1.
|
20
|
-
ripple_down_rules-0.1.
|
16
|
+
ripple_down_rules-0.1.68.dist-info/licenses/LICENSE,sha256=ixuiBLtpoK3iv89l7ylKkg9rs2GzF9ukPH7ynZYzK5s,35148
|
17
|
+
ripple_down_rules-0.1.68.dist-info/METADATA,sha256=Aj_H4IgkoJAhQ3k7dufJsSnXEq3PQkN97CZbrhazT4w,42576
|
18
|
+
ripple_down_rules-0.1.68.dist-info/WHEEL,sha256=wXxTzcEDnjrTwFYjLPcsW_7_XihufBwmpiBeiXNBGEA,91
|
19
|
+
ripple_down_rules-0.1.68.dist-info/top_level.txt,sha256=VeoLhEhyK46M1OHwoPbCQLI1EifLjChqGzhQ6WEUqeM,18
|
20
|
+
ripple_down_rules-0.1.68.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|