ripple-down-rules 0.5.5__py3-none-any.whl → 0.5.8__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 +16 -9
- ripple_down_rules/datastructures/case.py +10 -4
- ripple_down_rules/datastructures/dataclasses.py +62 -3
- ripple_down_rules/experts.py +12 -2
- ripple_down_rules/helpers.py +55 -9
- ripple_down_rules/rdr.py +269 -180
- ripple_down_rules/rdr_decorators.py +60 -31
- ripple_down_rules/rules.py +69 -13
- ripple_down_rules/user_interface/gui.py +10 -7
- ripple_down_rules/user_interface/ipython_custom_shell.py +1 -1
- ripple_down_rules/user_interface/object_diagram.py +9 -1
- ripple_down_rules/user_interface/template_file_creator.py +25 -25
- ripple_down_rules/utils.py +330 -79
- {ripple_down_rules-0.5.5.dist-info → ripple_down_rules-0.5.8.dist-info}/METADATA +2 -1
- ripple_down_rules-0.5.8.dist-info/RECORD +24 -0
- ripple_down_rules-0.5.5.dist-info/RECORD +0 -24
- {ripple_down_rules-0.5.5.dist-info → ripple_down_rules-0.5.8.dist-info}/WHEEL +0 -0
- {ripple_down_rules-0.5.5.dist-info → ripple_down_rules-0.5.8.dist-info}/licenses/LICENSE +0 -0
- {ripple_down_rules-0.5.5.dist-info → ripple_down_rules-0.5.8.dist-info}/top_level.txt +0 -0
ripple_down_rules/utils.py
CHANGED
@@ -8,15 +8,20 @@ import json
|
|
8
8
|
import logging
|
9
9
|
import os
|
10
10
|
import re
|
11
|
+
import sys
|
11
12
|
import threading
|
12
13
|
import uuid
|
13
|
-
from collections import UserDict
|
14
|
+
from collections import UserDict, defaultdict
|
14
15
|
from copy import deepcopy, copy
|
15
16
|
from dataclasses import is_dataclass, fields
|
16
17
|
from enum import Enum
|
18
|
+
from os.path import dirname
|
19
|
+
from pathlib import Path
|
17
20
|
from textwrap import dedent
|
18
21
|
from types import NoneType
|
19
|
-
|
22
|
+
|
23
|
+
from sqlalchemy.exc import NoInspectionAvailable
|
24
|
+
|
20
25
|
|
21
26
|
try:
|
22
27
|
import matplotlib
|
@@ -41,8 +46,7 @@ from sqlalchemy import MetaData, inspect
|
|
41
46
|
from sqlalchemy.orm import Mapped, registry, class_mapper, DeclarativeBase as SQLTable, Session
|
42
47
|
from tabulate import tabulate
|
43
48
|
from typing_extensions import Callable, Set, Any, Type, Dict, TYPE_CHECKING, get_type_hints, \
|
44
|
-
get_origin, get_args, Tuple, Optional, List, Union, Self
|
45
|
-
|
49
|
+
get_origin, get_args, Tuple, Optional, List, Union, Self, ForwardRef, Sequence, Iterable
|
46
50
|
|
47
51
|
if TYPE_CHECKING:
|
48
52
|
from .datastructures.case import Case
|
@@ -81,7 +85,7 @@ def are_results_subclass_of_types(result_types: List[Any], types_: List[Type]) -
|
|
81
85
|
return True
|
82
86
|
|
83
87
|
|
84
|
-
def
|
88
|
+
def _get_imports_from_types(types: List[Type]) -> List[str]:
|
85
89
|
"""
|
86
90
|
Get the import statements for a list of types.
|
87
91
|
|
@@ -114,13 +118,21 @@ def get_imports_from_scope(scope: Dict[str, Any]) -> List[str]:
|
|
114
118
|
"""
|
115
119
|
imports = []
|
116
120
|
for k, v in scope.items():
|
117
|
-
if not hasattr(v, "__module__") or not hasattr(v, "__name__"):
|
121
|
+
if not hasattr(v, "__module__") or not hasattr(v, "__name__") or v.__module__ is None:
|
118
122
|
continue
|
119
123
|
imports.append(f"from {v.__module__} import {v.__name__}")
|
120
124
|
return imports
|
121
125
|
|
122
126
|
|
123
|
-
def extract_imports(file_path: Optional[str] = None, tree: Optional[ast.AST] = None
|
127
|
+
def extract_imports(file_path: Optional[str] = None, tree: Optional[ast.AST] = None,
|
128
|
+
package_name: Optional[str] = None) -> Dict[str, Any]:
|
129
|
+
"""
|
130
|
+
Extract imports from a Python file or an AST tree.
|
131
|
+
|
132
|
+
:param file_path: The path to the Python file to extract imports from.
|
133
|
+
:param tree: An AST tree to extract imports from. If provided, file_path is ignored.
|
134
|
+
:param package_name: The name of the package to use for relative imports.
|
135
|
+
"""
|
124
136
|
if tree is None:
|
125
137
|
if file_path is None:
|
126
138
|
raise ValueError("Either file_path or tree must be provided")
|
@@ -135,7 +147,7 @@ def extract_imports(file_path: Optional[str] = None, tree: Optional[ast.AST] = N
|
|
135
147
|
module_name = alias.name
|
136
148
|
asname = alias.asname or alias.name
|
137
149
|
try:
|
138
|
-
scope[asname] = importlib.import_module(module_name)
|
150
|
+
scope[asname] = importlib.import_module(module_name, package=package_name)
|
139
151
|
except ImportError as e:
|
140
152
|
print(f"Could not import {module_name}: {e}")
|
141
153
|
elif isinstance(node, ast.ImportFrom):
|
@@ -144,7 +156,12 @@ def extract_imports(file_path: Optional[str] = None, tree: Optional[ast.AST] = N
|
|
144
156
|
name = alias.name
|
145
157
|
asname = alias.asname or name
|
146
158
|
try:
|
147
|
-
|
159
|
+
if package_name is not None and node.level > 0: # Handle relative imports
|
160
|
+
module_rel_path = Path(os.path.join(file_path, *['..'] * node.level, module_name)).resolve()
|
161
|
+
idx = str(module_rel_path).rfind(package_name)
|
162
|
+
if idx != -1:
|
163
|
+
module_name = str(module_rel_path)[idx:].replace(os.path.sep, '.')
|
164
|
+
module = importlib.import_module(module_name, package=package_name)
|
148
165
|
scope[asname] = getattr(module, name)
|
149
166
|
except (ImportError, AttributeError) as e:
|
150
167
|
logging.warning(f"Could not import {module_name}: {e} while extracting imports from {file_path}")
|
@@ -157,7 +174,7 @@ def extract_function_source(file_path: str,
|
|
157
174
|
return_line_numbers: bool = False,
|
158
175
|
include_signature: bool = True) \
|
159
176
|
-> Union[Dict[str, Union[str, List[str]]],
|
160
|
-
Tuple[Dict[str, Union[str, List[str]]],
|
177
|
+
Tuple[Dict[str, Union[str, List[str]]], Dict[str, Tuple[int, int]]]]:
|
161
178
|
"""
|
162
179
|
Extract the source code of a function from a file.
|
163
180
|
|
@@ -176,7 +193,7 @@ def extract_function_source(file_path: str,
|
|
176
193
|
tree = ast.parse(source)
|
177
194
|
function_names = make_list(function_names)
|
178
195
|
functions_source: Dict[str, Union[str, List[str]]] = {}
|
179
|
-
line_numbers
|
196
|
+
line_numbers: Dict[str, Tuple[int, int]] = {}
|
180
197
|
for node in tree.body:
|
181
198
|
if isinstance(node, ast.FunctionDef) and (node.name in function_names or len(function_names) == 0):
|
182
199
|
# Get the line numbers of the function
|
@@ -184,7 +201,7 @@ def extract_function_source(file_path: str,
|
|
184
201
|
func_lines = lines[node.lineno - 1:node.end_lineno]
|
185
202
|
if not include_signature:
|
186
203
|
func_lines = func_lines[1:]
|
187
|
-
line_numbers.
|
204
|
+
line_numbers[node.name] = (node.lineno, node.end_lineno)
|
188
205
|
functions_source[node.name] = dedent("\n".join(func_lines)) if join_lines else func_lines
|
189
206
|
if (len(functions_source) >= len(function_names)) and (not len(function_names) == 0):
|
190
207
|
break
|
@@ -496,16 +513,8 @@ def serialize_dataclass(obj: Any, seen=None) -> Any:
|
|
496
513
|
value = getattr(obj, f.name)
|
497
514
|
result['fields'][f.name] = serialize_dataclass(value, seen)
|
498
515
|
return result
|
499
|
-
elif isinstance(obj, list):
|
500
|
-
return [serialize_dataclass(v, seen) for v in obj]
|
501
|
-
elif isinstance(obj, dict):
|
502
|
-
return {k: serialize_dataclass(v, seen) for k, v in obj.items()}
|
503
516
|
else:
|
504
|
-
|
505
|
-
json.dumps(obj) # Check if the object is JSON serializable
|
506
|
-
return obj
|
507
|
-
except TypeError:
|
508
|
-
return None
|
517
|
+
return SubclassJSONSerializer.to_json_static(obj, seen)
|
509
518
|
|
510
519
|
|
511
520
|
def deserialize_dataclass(data: Any, refs: Optional[Dict[str, Any]] = None) -> Any:
|
@@ -667,56 +676,257 @@ def get_func_rdr_model_name(func: Callable, include_file_name: bool = False) ->
|
|
667
676
|
return model_name
|
668
677
|
|
669
678
|
|
670
|
-
def
|
671
|
-
"""
|
672
|
-
|
673
|
-
|
674
|
-
|
675
|
-
|
676
|
-
|
677
|
-
|
678
|
-
|
679
|
-
|
680
|
-
|
681
|
-
|
682
|
-
|
683
|
-
|
684
|
-
|
685
|
-
|
686
|
-
|
687
|
-
|
688
|
-
|
689
|
-
|
690
|
-
|
691
|
-
|
692
|
-
|
693
|
-
|
694
|
-
|
695
|
-
|
696
|
-
|
697
|
-
|
698
|
-
|
699
|
-
|
700
|
-
|
701
|
-
|
702
|
-
|
703
|
-
|
704
|
-
|
705
|
-
|
706
|
-
|
707
|
-
|
708
|
-
|
709
|
-
|
710
|
-
|
711
|
-
|
712
|
-
|
713
|
-
|
714
|
-
|
715
|
-
|
716
|
-
|
679
|
+
def stringify_hint(tp):
|
680
|
+
"""Recursively convert a type hint to a string."""
|
681
|
+
if isinstance(tp, str):
|
682
|
+
return tp
|
683
|
+
|
684
|
+
# Handle ForwardRef (string annotations not yet evaluated)
|
685
|
+
if isinstance(tp, ForwardRef):
|
686
|
+
return tp.__forward_arg__
|
687
|
+
|
688
|
+
# Handle typing generics like List[int], Dict[str, List[int]], etc.
|
689
|
+
origin = get_origin(tp)
|
690
|
+
args = get_args(tp)
|
691
|
+
|
692
|
+
if origin is not None:
|
693
|
+
origin_str = getattr(origin, '__name__', str(origin)).capitalize()
|
694
|
+
args_str = ", ".join(stringify_hint(arg) for arg in args)
|
695
|
+
return f"{origin_str}[{args_str}]"
|
696
|
+
|
697
|
+
# Handle built-in types like int, str, etc.
|
698
|
+
if isinstance(tp, type):
|
699
|
+
if tp.__module__ == 'builtins':
|
700
|
+
return tp.__name__
|
701
|
+
return f"{tp.__qualname__}"
|
702
|
+
|
703
|
+
return str(tp)
|
704
|
+
|
705
|
+
|
706
|
+
def is_builtin_type(tp):
|
707
|
+
return isinstance(tp, type) and tp.__module__ == "builtins"
|
708
|
+
|
709
|
+
|
710
|
+
def is_typing_type(tp):
|
711
|
+
return tp.__module__ == "typing"
|
712
|
+
|
713
|
+
origin_type_to_hint = {
|
714
|
+
list: List,
|
715
|
+
set: Set,
|
716
|
+
dict: Dict,
|
717
|
+
tuple: Tuple,
|
718
|
+
}
|
719
|
+
|
720
|
+
def extract_types(tp, seen: Set = None) -> Set[type]:
|
721
|
+
"""Recursively extract all base types from a type hint."""
|
722
|
+
if seen is None:
|
723
|
+
seen = set()
|
724
|
+
|
725
|
+
if tp in seen or isinstance(tp, str):
|
726
|
+
return seen
|
727
|
+
|
728
|
+
# seen.add(tp)
|
729
|
+
|
730
|
+
if isinstance(tp, ForwardRef):
|
731
|
+
# Can't resolve until evaluated
|
732
|
+
return seen
|
733
|
+
|
734
|
+
origin = get_origin(tp)
|
735
|
+
args = get_args(tp)
|
736
|
+
|
737
|
+
if origin:
|
738
|
+
if origin in origin_type_to_hint:
|
739
|
+
seen.add(origin_type_to_hint[origin])
|
740
|
+
else:
|
741
|
+
seen.add(origin)
|
742
|
+
for arg in args:
|
743
|
+
extract_types(arg, seen)
|
744
|
+
|
745
|
+
elif isinstance(tp, type):
|
746
|
+
seen.add(tp)
|
747
|
+
|
748
|
+
return seen
|
749
|
+
|
750
|
+
|
751
|
+
def get_types_to_import_from_func_type_hints(func: Callable) -> Set[Type]:
|
752
|
+
"""
|
753
|
+
Extract importable types from a function's annotations.
|
754
|
+
|
755
|
+
:param func: The function to extract type hints from.
|
756
|
+
"""
|
757
|
+
hints = get_type_hints(func)
|
758
|
+
|
759
|
+
sig = inspect.signature(func)
|
760
|
+
all_hints = list(hints.values())
|
761
|
+
if sig.return_annotation != inspect.Signature.empty:
|
762
|
+
all_hints.append(sig.return_annotation)
|
763
|
+
|
764
|
+
for param in sig.parameters.values():
|
765
|
+
if param.annotation != inspect.Parameter.empty:
|
766
|
+
all_hints.append(param.annotation)
|
767
|
+
|
768
|
+
return get_types_to_import_from_type_hints(all_hints)
|
769
|
+
|
770
|
+
|
771
|
+
def get_types_to_import_from_type_hints(hints: List[Type]) -> Set[Type]:
|
772
|
+
"""
|
773
|
+
Extract importable types from a list of type hints.
|
774
|
+
|
775
|
+
:param hints: A list of type hints to extract types from.
|
776
|
+
:return: A set of types that need to be imported.
|
777
|
+
"""
|
778
|
+
seen_types = set()
|
779
|
+
for hint in hints:
|
780
|
+
extract_types(hint, seen_types)
|
781
|
+
|
782
|
+
# Filter out built-in and internal types
|
783
|
+
to_import = set()
|
784
|
+
for tp in seen_types:
|
785
|
+
if isinstance(tp, ForwardRef) or isinstance(tp, str):
|
786
|
+
continue
|
787
|
+
if not is_builtin_type(tp):
|
788
|
+
to_import.add(tp)
|
789
|
+
|
790
|
+
return to_import
|
791
|
+
|
792
|
+
|
793
|
+
def get_import_path_from_path(path: str) -> Optional[str]:
|
794
|
+
"""
|
795
|
+
Convert a file system path to a Python import path.
|
796
|
+
|
797
|
+
:param path: The file system path to convert.
|
798
|
+
:return: The Python import path.
|
799
|
+
"""
|
800
|
+
package_name = os.path.abspath(path)
|
801
|
+
formated_package_name = package_name.strip('./').replace('/', '.')
|
802
|
+
parent_package_idx = 0
|
803
|
+
packages = formated_package_name.split('.')
|
804
|
+
for i, possible_pacakge in enumerate(reversed(packages)):
|
805
|
+
if i == 0:
|
806
|
+
current_path = package_name
|
807
|
+
else:
|
808
|
+
current_path = '/' + '/'.join(packages[:-i])
|
809
|
+
if os.path.exists(os.path.join(current_path, '__init__.py')):
|
810
|
+
parent_package_idx -= 1
|
811
|
+
else:
|
812
|
+
break
|
813
|
+
package_name = '.'.join(packages[parent_package_idx:]) if parent_package_idx < 0 else None
|
814
|
+
return package_name
|
815
|
+
|
816
|
+
|
817
|
+
def get_function_import_data(func: Callable) -> Tuple[str, str]:
|
818
|
+
"""
|
819
|
+
Get the import path of a function.
|
820
|
+
|
821
|
+
:param func: The function to get the import path for.
|
822
|
+
:return: The import path of the function.
|
823
|
+
"""
|
824
|
+
func_name = get_method_name(func)
|
825
|
+
func_class_name = get_method_class_name_if_exists(func)
|
826
|
+
func_file_path = get_method_file_name(func)
|
827
|
+
func_file_name = func_file_path.split('/')[-1].split('.')[0] # Get the file name without extension
|
828
|
+
func_import_path = get_import_path_from_path(dirname(func_file_path))
|
829
|
+
func_import_path = f"{func_import_path}.{func_file_name}" if func_import_path else func_file_name
|
830
|
+
if func_class_name and func_class_name != func_name:
|
831
|
+
func_import_name = func_class_name
|
717
832
|
else:
|
718
|
-
|
719
|
-
return
|
833
|
+
func_import_name = func_name
|
834
|
+
return func_import_path, func_import_name
|
835
|
+
|
836
|
+
|
837
|
+
def get_function_representation(func: Callable) -> str:
|
838
|
+
"""
|
839
|
+
Get a string representation of a function, including its module and class if applicable.
|
840
|
+
|
841
|
+
:param func: The function to represent.
|
842
|
+
:return: A string representation of the function.
|
843
|
+
"""
|
844
|
+
func_name = get_method_name(func)
|
845
|
+
func_class_name = get_method_class_name_if_exists(func)
|
846
|
+
if func_class_name and func_class_name != func_name:
|
847
|
+
return f"{func_class_name}.{func_name}"
|
848
|
+
return func_name
|
849
|
+
|
850
|
+
|
851
|
+
def get_relative_import(target_file_path, imported_module_path: Optional[str] = None,
|
852
|
+
module: Optional[str] = None) -> str:
|
853
|
+
"""
|
854
|
+
Get a relative import path from the target file to the imported module.
|
855
|
+
|
856
|
+
:param target_file_path: The file path of the target file.
|
857
|
+
:param imported_module_path: The file path of the module being imported.
|
858
|
+
:param module: The module name, if available.
|
859
|
+
:return: A relative import path as a string.
|
860
|
+
"""
|
861
|
+
# Convert to absolute paths
|
862
|
+
if module is not None:
|
863
|
+
imported_module_path = sys.modules[module].__file__
|
864
|
+
if imported_module_path is None:
|
865
|
+
raise ValueError("Either imported_module_path or module must be provided")
|
866
|
+
target_path = Path(target_file_path).resolve()
|
867
|
+
imported_path = Path(imported_module_path).resolve()
|
868
|
+
|
869
|
+
# Compute relative path from target to imported module
|
870
|
+
rel_path = os.path.relpath(imported_path.parent, target_path.parent)
|
871
|
+
|
872
|
+
# Convert path to Python import format
|
873
|
+
rel_parts = [part.replace('..', '.') for part in Path(rel_path).parts]
|
874
|
+
rel_parts = rel_parts if rel_parts else ['']
|
875
|
+
|
876
|
+
# Join the parts and add the module name
|
877
|
+
joined_parts = "".join(rel_parts) + f".{imported_path.stem}"
|
878
|
+
joined_parts = f".{joined_parts}" if not joined_parts.startswith(".") else joined_parts
|
879
|
+
|
880
|
+
return joined_parts
|
881
|
+
|
882
|
+
|
883
|
+
def get_imports_from_types(type_objs: Iterable[Type],
|
884
|
+
target_file_path: Optional[str] = None,
|
885
|
+
package_name: Optional[str] = None) -> List[str]:
|
886
|
+
"""
|
887
|
+
Format import lines from type objects.
|
888
|
+
|
889
|
+
:param type_objs: A list of type objects to format.
|
890
|
+
:param target_file_path: The file path to which the imports should be relative.
|
891
|
+
:param package_name: The name of the package to use for relative imports.
|
892
|
+
"""
|
893
|
+
|
894
|
+
module_to_types = defaultdict(list)
|
895
|
+
module_to_path = {}
|
896
|
+
other_imports = []
|
897
|
+
for tp in type_objs:
|
898
|
+
try:
|
899
|
+
if isinstance(tp, type) or is_typing_type(tp):
|
900
|
+
module = tp.__module__
|
901
|
+
file = getattr(tp, '__file__', None)
|
902
|
+
name = tp.__qualname__
|
903
|
+
elif callable(tp):
|
904
|
+
module, name = get_function_import_data(tp)
|
905
|
+
file = get_method_file_name(tp)
|
906
|
+
elif hasattr(type(tp), "__module__"):
|
907
|
+
module = type(tp).__module__
|
908
|
+
file = getattr(tp, '__file__', None)
|
909
|
+
name = type(tp).__qualname__
|
910
|
+
else:
|
911
|
+
continue
|
912
|
+
if module is None or module == 'builtins' or module.startswith('_'):
|
913
|
+
continue
|
914
|
+
module_to_types[module].append(name)
|
915
|
+
if file:
|
916
|
+
module_to_path[module] = file
|
917
|
+
except AttributeError:
|
918
|
+
continue
|
919
|
+
|
920
|
+
lines = []
|
921
|
+
for module, names in module_to_types.items():
|
922
|
+
joined = ", ".join(sorted(set(names)))
|
923
|
+
import_path = module
|
924
|
+
if (target_file_path is not None) and (package_name is not None) and (package_name in module):
|
925
|
+
import_path = get_relative_import(target_file_path, module=module)
|
926
|
+
lines.append(f"from {import_path} import {joined}")
|
927
|
+
if other_imports:
|
928
|
+
lines.extend(other_imports)
|
929
|
+
return sorted(lines)
|
720
930
|
|
721
931
|
|
722
932
|
def get_method_args_as_dict(method: Callable, *args, **kwargs) -> Dict[str, Any]:
|
@@ -753,7 +963,9 @@ def get_method_class_name_if_exists(method: Callable) -> Optional[str]:
|
|
753
963
|
:return: The class name of the method.
|
754
964
|
"""
|
755
965
|
if hasattr(method, "__self__"):
|
756
|
-
if hasattr(method.__self__, "
|
966
|
+
if hasattr(method.__self__, "__name__"):
|
967
|
+
return method.__self__.__name__
|
968
|
+
elif hasattr(method.__self__, "__class__"):
|
757
969
|
return method.__self__.__class__.__name__
|
758
970
|
return method.__qualname__.split('.')[0] if hasattr(method, "__qualname__") else None
|
759
971
|
|
@@ -869,10 +1081,28 @@ class SubclassJSONSerializer:
|
|
869
1081
|
return data
|
870
1082
|
|
871
1083
|
@staticmethod
|
872
|
-
def to_json_static(obj) ->
|
873
|
-
if
|
874
|
-
return
|
875
|
-
|
1084
|
+
def to_json_static(obj, seen=None) -> Any:
|
1085
|
+
if isinstance(obj, SubclassJSONSerializer):
|
1086
|
+
return {"_type": get_full_class_name(obj.__class__), **obj._to_json()}
|
1087
|
+
elif isinstance(obj, type):
|
1088
|
+
return {"_type": get_full_class_name(obj)}
|
1089
|
+
elif is_dataclass(obj):
|
1090
|
+
return serialize_dataclass(obj, seen)
|
1091
|
+
elif isinstance(obj, list):
|
1092
|
+
return [SubclassJSONSerializer.to_json_static(v, seen) for v in obj]
|
1093
|
+
elif isinstance(obj, dict):
|
1094
|
+
serialized_dict = {}
|
1095
|
+
for k, v in obj.items():
|
1096
|
+
if not isinstance(k, (str, int, bool, float, type(None))):
|
1097
|
+
continue
|
1098
|
+
serialized_dict[k] = SubclassJSONSerializer.to_json_static(v, seen)
|
1099
|
+
return serialized_dict
|
1100
|
+
else:
|
1101
|
+
try:
|
1102
|
+
json.dumps(obj) # Check if the object is JSON serializable
|
1103
|
+
return obj
|
1104
|
+
except TypeError:
|
1105
|
+
return None
|
876
1106
|
|
877
1107
|
def to_json(self) -> Dict[str, Any]:
|
878
1108
|
return self.to_json_static(self)
|
@@ -1008,13 +1238,20 @@ def copy_orm_instance(instance: SQLTable) -> SQLTable:
|
|
1008
1238
|
:param instance: The instance to copy.
|
1009
1239
|
:return: The copied instance.
|
1010
1240
|
"""
|
1011
|
-
|
1241
|
+
try:
|
1242
|
+
session: Session = inspect(instance).session
|
1243
|
+
except NoInspectionAvailable:
|
1244
|
+
session = None
|
1012
1245
|
if session is not None:
|
1013
1246
|
session.expunge(instance)
|
1014
1247
|
new_instance = deepcopy(instance)
|
1015
1248
|
session.add(instance)
|
1016
1249
|
else:
|
1017
|
-
|
1250
|
+
try:
|
1251
|
+
new_instance = deepcopy(instance)
|
1252
|
+
except Exception as e:
|
1253
|
+
logging.debug(e)
|
1254
|
+
new_instance = instance
|
1018
1255
|
return new_instance
|
1019
1256
|
|
1020
1257
|
|
@@ -1028,8 +1265,12 @@ def copy_orm_instance_with_relationships(instance: SQLTable) -> SQLTable:
|
|
1028
1265
|
instance_cp = copy_orm_instance(instance)
|
1029
1266
|
for rel in class_mapper(instance.__class__).relationships:
|
1030
1267
|
related_obj = getattr(instance, rel.key)
|
1268
|
+
related_obj_cp = copy_orm_instance(related_obj)
|
1031
1269
|
if related_obj is not None:
|
1032
|
-
|
1270
|
+
try:
|
1271
|
+
setattr(instance_cp, rel.key, related_obj_cp)
|
1272
|
+
except Exception as e:
|
1273
|
+
logging.debug(e)
|
1033
1274
|
return instance_cp
|
1034
1275
|
|
1035
1276
|
|
@@ -1040,7 +1281,17 @@ def get_value_type_from_type_hint(attr_name: str, obj: Any) -> Type:
|
|
1040
1281
|
:param attr_name: The name of the attribute.
|
1041
1282
|
:param obj: The object to get the attributes from.
|
1042
1283
|
"""
|
1043
|
-
|
1284
|
+
# check first if obj is a function object
|
1285
|
+
if hasattr(obj, '__code__'):
|
1286
|
+
func_type_hints = get_type_hints(obj)
|
1287
|
+
if attr_name in func_type_hints:
|
1288
|
+
hint = func_type_hints[attr_name]
|
1289
|
+
origin = get_origin(hint)
|
1290
|
+
args = get_args(hint)
|
1291
|
+
else:
|
1292
|
+
raise ValueError(f"Unknown type hint: {attr_name}")
|
1293
|
+
else:
|
1294
|
+
hint, origin, args = get_hint_for_attribute(attr_name, obj)
|
1044
1295
|
if not origin and not hint:
|
1045
1296
|
if hasattr(obj, attr_name):
|
1046
1297
|
attr_value = getattr(obj, attr_name)
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: ripple_down_rules
|
3
|
-
Version: 0.5.
|
3
|
+
Version: 0.5.8
|
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
|
@@ -694,6 +694,7 @@ Requires-Dist: pygments
|
|
694
694
|
Requires-Dist: sqlalchemy
|
695
695
|
Requires-Dist: pandas
|
696
696
|
Requires-Dist: pyparsing>=3.2.3
|
697
|
+
Requires-Dist: omegaconf
|
697
698
|
Provides-Extra: viz
|
698
699
|
Requires-Dist: networkx>=3.1; extra == "viz"
|
699
700
|
Requires-Dist: matplotlib>=3.7.5; extra == "viz"
|
@@ -0,0 +1,24 @@
|
|
1
|
+
ripple_down_rules/__init__.py,sha256=cwOHhynJm6Y-t455Unt4AXAxXv4yPEGHMxCLjDfdT_o,99
|
2
|
+
ripple_down_rules/experts.py,sha256=bwozulI1rv0uyaMZQqEgapDO-s8wvW0D6Jqxmvu5fik,12610
|
3
|
+
ripple_down_rules/helpers.py,sha256=v4oE7C5PfQUVJfSUs1FfLHEwrJXEHJLn4vJhJMvyCR8,4453
|
4
|
+
ripple_down_rules/rdr.py,sha256=2t04Qj931dh6UypLBy4RxJ5hFIui0ejFgvm7D5P8b-E,55118
|
5
|
+
ripple_down_rules/rdr_decorators.py,sha256=0sk7izDB53lTKSB9fm33vQahmY_05FyCOWljyQOMB0U,9072
|
6
|
+
ripple_down_rules/rules.py,sha256=iVevv6iZ-6L2IPI0ZYbBjxBymXEQMmJGRFhiKUS-NmA,20352
|
7
|
+
ripple_down_rules/start-code-server.sh,sha256=otClk7VmDgBOX2TS_cjws6K0UwvgAUJhoA0ugkPCLqQ,949
|
8
|
+
ripple_down_rules/utils.py,sha256=mObBszTruGrRvD4MgD8tS1AnMtoyKrPl4RCciinhzY4,60132
|
9
|
+
ripple_down_rules/datastructures/__init__.py,sha256=V2aNgf5C96Y5-IGghra3n9uiefpoIm_QdT7cc_C8cxQ,111
|
10
|
+
ripple_down_rules/datastructures/callable_expression.py,sha256=f3wUPTrLa1INO-1qfgVz87ryrCABronfyq0_JKWoZCs,12800
|
11
|
+
ripple_down_rules/datastructures/case.py,sha256=1zSaXUljaH6z3SgMGzYPoDyjotNam791KpYgvxuMh90,15463
|
12
|
+
ripple_down_rules/datastructures/dataclasses.py,sha256=qoTFHV8Hi-X8VtfC9VdvH4tif73YjF3dUe8dyHXTYts,10993
|
13
|
+
ripple_down_rules/datastructures/enums.py,sha256=ce7tqS0otfSTNAOwsnXlhsvIn4iW_Y_N3TNebF3YoZs,5700
|
14
|
+
ripple_down_rules/user_interface/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
15
|
+
ripple_down_rules/user_interface/gui.py,sha256=_lgZAUXxxaBUFQJAHjA5TBPp6XEvJ62t-kSN8sPsocE,27379
|
16
|
+
ripple_down_rules/user_interface/ipython_custom_shell.py,sha256=Jrf7NxOdlrwGXH0Xyz3vzQprY-PNx9etfePOTpm2Gu8,6479
|
17
|
+
ripple_down_rules/user_interface/object_diagram.py,sha256=FEa2HaYR9QmTE6NsOwBvZ0jqmu3DKyg6mig2VE5ZP4Y,4956
|
18
|
+
ripple_down_rules/user_interface/prompt.py,sha256=AkkltdDIaioN43lkRKDPKSjJcmdSSGZDMYz7AL7X9lE,8082
|
19
|
+
ripple_down_rules/user_interface/template_file_creator.py,sha256=xWUt-RHRqrvoMo74o-bMLo8xNxil68wgbOZAMADZp2A,13570
|
20
|
+
ripple_down_rules-0.5.8.dist-info/licenses/LICENSE,sha256=ixuiBLtpoK3iv89l7ylKkg9rs2GzF9ukPH7ynZYzK5s,35148
|
21
|
+
ripple_down_rules-0.5.8.dist-info/METADATA,sha256=bY7Ex1Qpazl71BzyF1sfOdNB3Tny73pQRGBte2oareU,48213
|
22
|
+
ripple_down_rules-0.5.8.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
23
|
+
ripple_down_rules-0.5.8.dist-info/top_level.txt,sha256=VeoLhEhyK46M1OHwoPbCQLI1EifLjChqGzhQ6WEUqeM,18
|
24
|
+
ripple_down_rules-0.5.8.dist-info/RECORD,,
|
@@ -1,24 +0,0 @@
|
|
1
|
-
ripple_down_rules/__init__.py,sha256=OV_GxE_svT43w1V5gHpc0YaGCqmA-cm_U7P8UmX4Ox4,99
|
2
|
-
ripple_down_rules/experts.py,sha256=rPRJU2py5wCopZADVWOP3Vzp077KL6ArDlkxUQ0Mh6w,12130
|
3
|
-
ripple_down_rules/helpers.py,sha256=TvTJU0BA3dPcAyzvZFvAu7jZqsp8Lu0HAAwvuizlGjg,2018
|
4
|
-
ripple_down_rules/rdr.py,sha256=FJYuRXgpUYSSK1pYrp2yeXb_ZZ2xjPED31tzxofokL4,48865
|
5
|
-
ripple_down_rules/rdr_decorators.py,sha256=pYCKLgMKgQ6x_252WQtF2t4ZNjWPBxnaWtJ6TpGdcc0,7820
|
6
|
-
ripple_down_rules/rules.py,sha256=TPNVMqW9T-_46BS4WemrspLg5uG8kP6tsPvWWBAzJxg,17515
|
7
|
-
ripple_down_rules/start-code-server.sh,sha256=otClk7VmDgBOX2TS_cjws6K0UwvgAUJhoA0ugkPCLqQ,949
|
8
|
-
ripple_down_rules/utils.py,sha256=4xQ5gmIeMsY-mA9g-4cedKt2WJq4oZAMs5XEKeplWPM,51127
|
9
|
-
ripple_down_rules/datastructures/__init__.py,sha256=V2aNgf5C96Y5-IGghra3n9uiefpoIm_QdT7cc_C8cxQ,111
|
10
|
-
ripple_down_rules/datastructures/callable_expression.py,sha256=D2KD1RdShzxYZPAERgywZ5ZPE4ar8WmMtXINqvYo_Tc,12497
|
11
|
-
ripple_down_rules/datastructures/case.py,sha256=r8kjL9xP_wk84ThXusspgPMrAoed2bGQmKi54fzhmH8,15258
|
12
|
-
ripple_down_rules/datastructures/dataclasses.py,sha256=PuD-7zWqWT2p4FnGvnihHvZlZKg9A1ctnFgVYf2cs-8,8554
|
13
|
-
ripple_down_rules/datastructures/enums.py,sha256=ce7tqS0otfSTNAOwsnXlhsvIn4iW_Y_N3TNebF3YoZs,5700
|
14
|
-
ripple_down_rules/user_interface/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
15
|
-
ripple_down_rules/user_interface/gui.py,sha256=SB0gUhgReJ3yx-NEHRPMGVuNRLPRUwW8-qup-Kd4Cfo,27182
|
16
|
-
ripple_down_rules/user_interface/ipython_custom_shell.py,sha256=24MIFwqnAhC6ofObEO6x5xRWRnyQmPpPmTvxbCKBrzM,6514
|
17
|
-
ripple_down_rules/user_interface/object_diagram.py,sha256=tsB6iuLNEbHxp5lR2WjyejjWbnAX_nHF9xS8jNPOQVk,4548
|
18
|
-
ripple_down_rules/user_interface/prompt.py,sha256=AkkltdDIaioN43lkRKDPKSjJcmdSSGZDMYz7AL7X9lE,8082
|
19
|
-
ripple_down_rules/user_interface/template_file_creator.py,sha256=FGtLfYBfr4310c7Dfa9b2qiOWLNzHk1q3kdhD70Ilg4,13804
|
20
|
-
ripple_down_rules-0.5.5.dist-info/licenses/LICENSE,sha256=ixuiBLtpoK3iv89l7ylKkg9rs2GzF9ukPH7ynZYzK5s,35148
|
21
|
-
ripple_down_rules-0.5.5.dist-info/METADATA,sha256=irk6Cn48hjgyNiE9XyuRsSFP1svBynaDMTkN1ijbvRc,48188
|
22
|
-
ripple_down_rules-0.5.5.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
23
|
-
ripple_down_rules-0.5.5.dist-info/top_level.txt,sha256=VeoLhEhyK46M1OHwoPbCQLI1EifLjChqGzhQ6WEUqeM,18
|
24
|
-
ripple_down_rules-0.5.5.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|