ripple-down-rules 0.2.4__py3-none-any.whl → 0.4.0__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.
@@ -5,51 +5,135 @@ of the RDRs.
5
5
  """
6
6
  import os.path
7
7
  from functools import wraps
8
- from typing import Callable, Optional, Type
8
+ from typing_extensions import Callable, Optional, Type, Tuple, Dict, Any, Self, get_type_hints, List, Union
9
9
 
10
- from sqlalchemy.orm import Session
11
- from typing_extensions import Any
12
-
13
- from ripple_down_rules.datastructures import Case, Category, create_case, CaseQuery
10
+ from ripple_down_rules.datastructures.case import create_case
11
+ from ripple_down_rules.datastructures.dataclasses import CaseQuery
12
+ from ripple_down_rules.datastructures.enums import Category
14
13
  from ripple_down_rules.experts import Expert, Human
14
+ from ripple_down_rules.rdr import GeneralRDR, RippleDownRules
15
+ from ripple_down_rules.utils import get_method_args_as_dict, get_func_rdr_model_name, make_set, \
16
+ get_method_class_if_exists
15
17
 
16
- from ripple_down_rules.rdr import SingleClassRDR, MultiClassRDR, GeneralRDR
17
- from ripple_down_rules.utils import get_method_args_as_dict, get_method_name, get_method_class_name_if_exists, \
18
- get_method_file_name, get_func_rdr_model_path
19
-
20
-
21
- def single_class_rdr(
22
- model_dir: str,
23
- fit: bool = True,
24
- expert: Optional[Expert] = None,
25
- session: Optional[Session] = None,
26
- ) -> Callable:
27
- """
28
- Decorator to use a SingleClassRDR as a classifier.
29
- """
30
- expert = expert if expert else Human(session=session)
31
-
32
- def decorator(func: Callable) -> Callable:
33
- scrdr_model_path = get_func_rdr_model_path(func, model_dir)
34
- if os.path.exists(scrdr_model_path):
35
- scrdr = SingleClassRDR.load(scrdr_model_path)
36
- scrdr.session = session
37
- else:
38
- scrdr = SingleClassRDR(session=session)
18
+
19
+ class RDRDecorator:
20
+ rdr: GeneralRDR
21
+
22
+ def __init__(self, models_dir: str,
23
+ output_type: Tuple[Type],
24
+ mutual_exclusive: bool,
25
+ python_dir: Optional[str] = None,
26
+ output_name: str = "output_",
27
+ fit: bool = True,
28
+ expert: Optional[Expert] = None):
29
+ """
30
+ :param models_dir: The directory to save/load the RDR models.
31
+ :param output_type: The type of the output. This is used to create the RDR model.
32
+ :param mutual_exclusive: If True, the output types are mutually exclusive.
33
+ :param python_dir: The directory to save the RDR model as a python file.
34
+ If None, the RDR model will not be saved as a python file.
35
+ :param output_name: The name of the output. This is used to create the RDR model.
36
+ :param fit: If True, the function will be in fit mode. This means that the RDR will prompt the user for the
37
+ correct output if the function's output is not in the RDR model. If False, the function will be in
38
+ classification mode. This means that the RDR will classify the function's output based on the RDR model.
39
+ :param expert: The expert that will be used to prompt the user for the correct output. If None, a Human
40
+ expert will be used.
41
+ :return: A decorator to use a GeneralRDR as a classifier that monitors and modifies the function's output.
42
+ """
43
+ self.rdr_models_dir = models_dir
44
+ self.output_type = output_type
45
+ self.parsed_output_type: List[Type] = []
46
+ self.mutual_exclusive = mutual_exclusive
47
+ self.rdr_python_path: Optional[str] = python_dir
48
+ self.output_name = output_name
49
+ self.fit: bool = fit
50
+ self.expert = expert if expert else Human()
51
+ self.rdr_model_path: Optional[str] = None
52
+ self.load()
53
+
54
+ def decorator(self, func: Callable) -> Callable:
39
55
 
40
56
  @wraps(func)
41
- def wrapper(*args, **kwargs) -> Category:
57
+ def wrapper(*args, **kwargs) -> Optional[Any]:
58
+ if len(self.parsed_output_type) == 0:
59
+ self.parse_output_type(func, *args)
60
+ if self.rdr_model_path is None:
61
+ self.initialize_rdr_model_path_and_load(func)
42
62
  case_dict = get_method_args_as_dict(func, *args, **kwargs)
43
63
  func_output = func(*args, **kwargs)
44
- if func_output is not None:
45
- case_dict.update({"_output": func_output})
46
- case = create_case(case_dict, recursion_idx=3)
47
- if fit:
48
- output = scrdr.fit_case(CaseQuery(case), expert=expert)
49
- scrdr.save(scrdr_model_path)
50
- return output
64
+ case_dict.update({self.output_name: func_output})
65
+ case = create_case(case_dict, obj_name=get_func_rdr_model_name(func), max_recursion_idx=3)
66
+ if self.fit:
67
+ scope = func.__globals__
68
+ scope.update(case_dict)
69
+ func_args_type_hints = get_type_hints(func)
70
+ func_args_type_hints.update({self.output_name: Union[tuple(self.parsed_output_type)]})
71
+ case_query = CaseQuery(case, self.output_name, Union[tuple(self.parsed_output_type)],
72
+ self.mutual_exclusive,
73
+ scope=scope, is_function=True, function_args_type_hints=func_args_type_hints)
74
+ output = self.rdr.fit_case(case_query, expert=self.expert)
75
+ return output[self.output_name]
51
76
  else:
52
- return scrdr.classify(case)
77
+ return self.rdr.classify(case)[self.output_name]
78
+
53
79
  return wrapper
54
80
 
55
- return decorator
81
+ def initialize_rdr_model_path_and_load(self, func: Callable) -> None:
82
+ model_file_name = get_func_rdr_model_name(func, include_file_name=True)
83
+ model_file_name = (''.join(['_' + c.lower() if c.isupper() else c for c in model_file_name]).lstrip('_')
84
+ .replace('__', '_') + ".json")
85
+ self.rdr_model_path = os.path.join(self.rdr_models_dir, model_file_name)
86
+ self.load()
87
+
88
+ def parse_output_type(self, func: Callable, *args) -> None:
89
+ for ot in make_set(self.output_type):
90
+ if ot is Self:
91
+ func_class = get_method_class_if_exists(func, *args)
92
+ if func_class is not None:
93
+ self.parsed_output_type.append(func_class)
94
+ else:
95
+ raise ValueError(f"The function {func} is not a method of a class,"
96
+ f" and the output type is {Self}.")
97
+ else:
98
+ self.parsed_output_type.append(ot)
99
+
100
+ def save(self):
101
+ """
102
+ Save the RDR model to the specified directory.
103
+ """
104
+ self.rdr.save(self.rdr_model_path)
105
+
106
+ if self.rdr_python_path is not None:
107
+ if not os.path.exists(self.rdr_python_path):
108
+ os.makedirs(self.rdr_python_path)
109
+ if not os.path.exists(os.path.join(self.rdr_python_path, "__init__.py")):
110
+ # add __init__.py file to the directory
111
+ with open(os.path.join(self.rdr_python_path, "__init__.py"), "w") as f:
112
+ f.write("# This is an empty __init__.py file to make the directory a package.")
113
+ # write the RDR model to a python file
114
+ self.rdr.write_to_python_file(self.rdr_python_path)
115
+
116
+ def load(self):
117
+ """
118
+ Load the RDR model from the specified directory.
119
+ """
120
+ if self.rdr_model_path is not None and os.path.exists(self.rdr_model_path):
121
+ self.rdr = GeneralRDR.load(self.rdr_model_path)
122
+ else:
123
+ self.rdr = GeneralRDR()
124
+
125
+ def write_to_python_file(self, package_dir: str, file_name_postfix: str = ""):
126
+ """
127
+ Write the RDR model to a python file.
128
+
129
+ :param package_dir: The path to the directory to write the python file.
130
+ """
131
+ self.rdr.write_to_python_file(package_dir, postfix=file_name_postfix)
132
+
133
+ def update_from_python_file(self, package_dir: str):
134
+ """
135
+ Update the RDR model from a python file.
136
+
137
+ :param package_dir: The directory of the package that contains the generated python file.
138
+ """
139
+ self.rdr.update_from_python_file(package_dir)
File without changes