ripple-down-rules 0.4.7__tar.gz → 0.4.8__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.
Files changed (43) hide show
  1. {ripple_down_rules-0.4.7 → ripple_down_rules-0.4.8}/PKG-INFO +1 -1
  2. {ripple_down_rules-0.4.7 → ripple_down_rules-0.4.8}/pyproject.toml +1 -1
  3. {ripple_down_rules-0.4.7 → ripple_down_rules-0.4.8}/src/ripple_down_rules/rules.py +2 -2
  4. {ripple_down_rules-0.4.7 → ripple_down_rules-0.4.8}/src/ripple_down_rules/user_interface/gui.py +4 -1
  5. {ripple_down_rules-0.4.7 → ripple_down_rules-0.4.8}/src/ripple_down_rules/user_interface/ipython_custom_shell.py +4 -1
  6. {ripple_down_rules-0.4.7 → ripple_down_rules-0.4.8}/src/ripple_down_rules/user_interface/template_file_creator.py +50 -26
  7. {ripple_down_rules-0.4.7 → ripple_down_rules-0.4.8}/src/ripple_down_rules/utils.py +7 -5
  8. {ripple_down_rules-0.4.7 → ripple_down_rules-0.4.8}/src/ripple_down_rules.egg-info/PKG-INFO +1 -1
  9. ripple_down_rules-0.4.8/test/test_template_file_creator.py +57 -0
  10. ripple_down_rules-0.4.7/test/test_template_file_creator.py +0 -42
  11. {ripple_down_rules-0.4.7 → ripple_down_rules-0.4.8}/LICENSE +0 -0
  12. {ripple_down_rules-0.4.7 → ripple_down_rules-0.4.8}/README.md +0 -0
  13. {ripple_down_rules-0.4.7 → ripple_down_rules-0.4.8}/setup.cfg +0 -0
  14. {ripple_down_rules-0.4.7 → ripple_down_rules-0.4.8}/src/ripple_down_rules/__init__.py +0 -0
  15. {ripple_down_rules-0.4.7 → ripple_down_rules-0.4.8}/src/ripple_down_rules/datasets.py +0 -0
  16. {ripple_down_rules-0.4.7 → ripple_down_rules-0.4.8}/src/ripple_down_rules/datastructures/__init__.py +0 -0
  17. {ripple_down_rules-0.4.7 → ripple_down_rules-0.4.8}/src/ripple_down_rules/datastructures/callable_expression.py +0 -0
  18. {ripple_down_rules-0.4.7 → ripple_down_rules-0.4.8}/src/ripple_down_rules/datastructures/case.py +0 -0
  19. {ripple_down_rules-0.4.7 → ripple_down_rules-0.4.8}/src/ripple_down_rules/datastructures/dataclasses.py +0 -0
  20. {ripple_down_rules-0.4.7 → ripple_down_rules-0.4.8}/src/ripple_down_rules/datastructures/enums.py +0 -0
  21. {ripple_down_rules-0.4.7 → ripple_down_rules-0.4.8}/src/ripple_down_rules/experts.py +0 -0
  22. {ripple_down_rules-0.4.7 → ripple_down_rules-0.4.8}/src/ripple_down_rules/failures.py +0 -0
  23. {ripple_down_rules-0.4.7 → ripple_down_rules-0.4.8}/src/ripple_down_rules/helpers.py +0 -0
  24. {ripple_down_rules-0.4.7 → ripple_down_rules-0.4.8}/src/ripple_down_rules/rdr.py +0 -0
  25. {ripple_down_rules-0.4.7 → ripple_down_rules-0.4.8}/src/ripple_down_rules/rdr_decorators.py +0 -0
  26. {ripple_down_rules-0.4.7 → ripple_down_rules-0.4.8}/src/ripple_down_rules/user_interface/__init__.py +0 -0
  27. {ripple_down_rules-0.4.7 → ripple_down_rules-0.4.8}/src/ripple_down_rules/user_interface/object_diagram.py +0 -0
  28. {ripple_down_rules-0.4.7 → ripple_down_rules-0.4.8}/src/ripple_down_rules/user_interface/prompt.py +0 -0
  29. {ripple_down_rules-0.4.7 → ripple_down_rules-0.4.8}/src/ripple_down_rules.egg-info/SOURCES.txt +0 -0
  30. {ripple_down_rules-0.4.7 → ripple_down_rules-0.4.8}/src/ripple_down_rules.egg-info/dependency_links.txt +0 -0
  31. {ripple_down_rules-0.4.7 → ripple_down_rules-0.4.8}/src/ripple_down_rules.egg-info/requires.txt +0 -0
  32. {ripple_down_rules-0.4.7 → ripple_down_rules-0.4.8}/src/ripple_down_rules.egg-info/top_level.txt +0 -0
  33. {ripple_down_rules-0.4.7 → ripple_down_rules-0.4.8}/test/test_json_serialization.py +0 -0
  34. {ripple_down_rules-0.4.7 → ripple_down_rules-0.4.8}/test/test_object_diagram.py +0 -0
  35. {ripple_down_rules-0.4.7 → ripple_down_rules-0.4.8}/test/test_on_mutagenic.py +0 -0
  36. {ripple_down_rules-0.4.7 → ripple_down_rules-0.4.8}/test/test_rdr.py +0 -0
  37. {ripple_down_rules-0.4.7 → ripple_down_rules-0.4.8}/test/test_rdr_alchemy.py +0 -0
  38. {ripple_down_rules-0.4.7 → ripple_down_rules-0.4.8}/test/test_rdr_decorators.py +0 -0
  39. {ripple_down_rules-0.4.7 → ripple_down_rules-0.4.8}/test/test_rdr_world.py +0 -0
  40. {ripple_down_rules-0.4.7 → ripple_down_rules-0.4.8}/test/test_relational_rdr.py +0 -0
  41. {ripple_down_rules-0.4.7 → ripple_down_rules-0.4.8}/test/test_relational_rdr_alchemy.py +0 -0
  42. {ripple_down_rules-0.4.7 → ripple_down_rules-0.4.8}/test/test_sql_model.py +0 -0
  43. {ripple_down_rules-0.4.7 → ripple_down_rules-0.4.8}/test/test_utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ripple_down_rules
3
- Version: 0.4.7
3
+ Version: 0.4.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
@@ -6,7 +6,7 @@ build-backend = "setuptools.build_meta"
6
6
 
7
7
  [project]
8
8
  name = "ripple_down_rules"
9
- version = "0.4.7"
9
+ version = "0.4.8"
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" }]
@@ -1,11 +1,11 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import logging
3
4
  import re
4
5
  from abc import ABC, abstractmethod
5
6
  from uuid import uuid4
6
7
 
7
8
  from anytree import NodeMixin
8
- from rospy import logwarn, logdebug
9
9
  from sqlalchemy.orm import DeclarativeBase as SQLTable
10
10
  from typing_extensions import List, Optional, Self, Union, Dict, Any, Tuple
11
11
 
@@ -164,7 +164,7 @@ class Rule(NodeMixin, SubclassJSONSerializer, ABC):
164
164
  try:
165
165
  corner_case = Case.from_json(data["corner_case"])
166
166
  except Exception as e:
167
- logdebug("Failed to load corner case from json, setting it to None.")
167
+ logging.debug("Failed to load corner case from json, setting it to None.")
168
168
  corner_case = None
169
169
  loaded_rule = cls(conditions=CallableExpression.from_json(data["conditions"]),
170
170
  conclusion=CallableExpression.from_json(data["conclusion"]),
@@ -475,7 +475,10 @@ class RDRCaseViewer(QMainWindow):
475
475
  def _load(self):
476
476
  if not self.template_file_creator:
477
477
  return
478
- self.code_lines = self.template_file_creator.load()
478
+ self.code_lines, updates = self.template_file_creator.load(self.template_file_creator.temp_file_path,
479
+ self.template_file_creator.func_name,
480
+ self.template_file_creator.print_func)
481
+ self.ipython_console.kernel.shell.user_ns.update(updates)
479
482
  if self.code_lines is not None:
480
483
  self.user_input = encapsulate_code_lines_into_a_function(
481
484
  self.code_lines, self.template_file_creator.func_name,
@@ -30,7 +30,10 @@ class MyMagics(Magics):
30
30
 
31
31
  @line_magic
32
32
  def load(self, line):
33
- self.all_code_lines = self.rule_editor.load()
33
+ self.all_code_lines, updates = self.rule_editor.load(self.rule_editor.temp_file_path,
34
+ self.rule_editor.func_name,
35
+ self.rule_editor.print_func)
36
+ self.shell.user_ns.update(updates)
34
37
 
35
38
  @line_magic
36
39
  def help(self, line):
@@ -9,13 +9,13 @@ from textwrap import indent, dedent
9
9
 
10
10
  from colorama import Fore, Style
11
11
  from ipykernel.inprocess.ipkernel import InProcessInteractiveShell
12
- from typing_extensions import Optional, Type, List, Callable
12
+ from typing_extensions import Optional, Type, List, Callable, Tuple, Dict
13
13
 
14
14
  from ..datastructures.case import Case
15
15
  from ..datastructures.dataclasses import CaseQuery
16
16
  from ..datastructures.enums import Editor, PromptFor
17
17
  from ..utils import str_to_snake_case, get_imports_from_scope, make_list, typing_hint_to_str, \
18
- get_imports_from_types, extract_function_source
18
+ get_imports_from_types, extract_function_source, extract_imports
19
19
 
20
20
 
21
21
  def detect_available_editor() -> Optional[Editor]:
@@ -49,6 +49,9 @@ def start_code_server(workspace):
49
49
  stderr=subprocess.PIPE, text=True)
50
50
 
51
51
 
52
+ FunctionData = Tuple[Optional[List[str]], Optional[Dict[str, Callable]]]
53
+
54
+
52
55
  class TemplateFileCreator:
53
56
  """
54
57
  A class to create a rule template file for a given case and prompt for the user to edit it.
@@ -71,8 +74,8 @@ class TemplateFileCreator:
71
74
  """
72
75
 
73
76
  def __init__(self, shell: InProcessInteractiveShell, case_query: CaseQuery, prompt_for: PromptFor,
74
- code_to_modify: Optional[str] = None, print_func: Optional[Callable[[str], None]] = None):
75
- self.print_func = print_func if print_func else print
77
+ code_to_modify: Optional[str] = None, print_func: Callable[[str], None] = print):
78
+ self.print_func = print_func
76
79
  self.shell = shell
77
80
  self.code_to_modify = code_to_modify
78
81
  self.prompt_for = prompt_for
@@ -228,15 +231,27 @@ class TemplateFileCreator:
228
231
  imports = set(imports)
229
232
  return '\n'.join(imports)
230
233
 
234
+ @staticmethod
235
+ def get_core_attribute_types(case_query: CaseQuery) -> List[Type]:
236
+ """
237
+ Get the core attribute types of the case query.
238
+
239
+ :return: A list of core attribute types.
240
+ """
241
+ attr_types = [t for t in case_query.core_attribute_type if t.__module__ != "builtins" and t is not None
242
+ and t is not type(None)]
243
+ return attr_types
244
+
231
245
  def get_func_doc(self) -> Optional[str]:
232
246
  """
233
247
  :return: A string containing the function docstring.
234
248
  """
249
+ type_data = f" of type {' or '.join(map(lambda c: c.__name__, self.get_core_attribute_types(self.case_query)))}"
235
250
  if self.prompt_for == PromptFor.Conditions:
236
251
  return (f"Get conditions on whether it's possible to conclude a value"
237
- f" for {self.case_query.name}")
252
+ f" for {self.case_query.name} {type_data}.")
238
253
  else:
239
- return f"Get possible value(s) for {self.case_query.name}"
254
+ return f"Get possible value(s) for {self.case_query.name} {type_data}."
240
255
 
241
256
  @staticmethod
242
257
  def get_func_name(prompt_for, case_query) -> Optional[str]:
@@ -248,9 +263,8 @@ class TemplateFileCreator:
248
263
  # convert any CamelCase word into snake_case by adding _ before each capital letter
249
264
  case_name = case_name.replace(f"_{case_query.attribute_name}", "")
250
265
  func_name += case_name
251
- attr_types = [t for t in case_query.core_attribute_type if t.__module__ != "builtins" and t is not None
252
- and t is not type(None)]
253
- func_name += f"_of_type_{'_or_'.join(map(lambda c: c.__name__, attr_types))}"
266
+ func_name += f"_of_type_{'_or_'.join(map(lambda c: c.__name__,
267
+ TemplateFileCreator.get_core_attribute_types(case_query)))}"
254
268
  return str_to_snake_case(func_name)
255
269
 
256
270
  @cached_property
@@ -263,33 +277,43 @@ class TemplateFileCreator:
263
277
  case = self.case_query.scope['case']
264
278
  return case._obj_type if isinstance(case, Case) else type(case)
265
279
 
266
- def load(self) -> Optional[List[str]]:
267
- if not self.temp_file_path:
268
- self.print_func(f"{Fore.RED}ERROR:: No file to load. Run %edit first.{Style.RESET_ALL}")
269
- return None
280
+ @staticmethod
281
+ def load(file_path: str, func_name: str, print_func: Callable = print) -> FunctionData:
282
+ """
283
+ Load the function from the given file path.
284
+
285
+ :param file_path: The path to the file to load.
286
+ :param func_name: The name of the function to load.
287
+ :param print_func: The function to use for printing messages.
288
+ :return: A tuple containing the function source code and the function object as a dictionary
289
+ with the function name as the key and the function object as the value.
290
+ """
291
+ if not file_path:
292
+ print_func(f"{Fore.RED}ERROR:: No file to load. Run %edit first.{Style.RESET_ALL}")
293
+ return None, None
270
294
 
271
- with open(self.temp_file_path, 'r') as f:
295
+ with open(file_path, 'r') as f:
272
296
  source = f.read()
273
297
 
274
298
  tree = ast.parse(source)
275
299
  updates = {}
276
300
  for node in tree.body:
277
- if isinstance(node, ast.FunctionDef) and node.name == self.func_name:
301
+ if isinstance(node, ast.FunctionDef) and node.name == func_name:
278
302
  exec_globals = {}
279
- exec(source, self.case_query.scope, exec_globals)
280
- user_function = exec_globals[self.func_name]
281
- updates[self.func_name] = user_function
282
- self.print_func(f"{Fore.BLUE}Loaded `{self.func_name}` function into user namespace.{Style.RESET_ALL}")
303
+ scope = extract_imports(tree=tree)
304
+ exec(source, scope, exec_globals)
305
+ user_function = exec_globals[func_name]
306
+ updates[func_name] = user_function
307
+ print_func(f"{Fore.BLUE}Loaded `{func_name}` function into user namespace.{Style.RESET_ALL}")
283
308
  break
284
309
  if updates:
285
- self.shell.user_ns.update(updates)
286
- self.all_code_lines = extract_function_source(self.temp_file_path,
287
- [self.func_name],
288
- join_lines=False)[self.func_name]
289
- return self.all_code_lines
310
+ all_code_lines = extract_function_source(file_path,
311
+ [func_name],
312
+ join_lines=False)[func_name]
313
+ return all_code_lines, updates
290
314
  else:
291
- self.print_func(f"{Fore.RED}ERROR:: Function `{self.func_name}` not found.{Style.RESET_ALL}")
292
- return None
315
+ print_func(f"{Fore.RED}ERROR:: Function `{func_name}` not found.{Style.RESET_ALL}")
316
+ return None, None
293
317
 
294
318
  def __del__(self):
295
319
  if hasattr(self, 'process') and self.process is not None and self.process.poll() is None:
@@ -23,7 +23,6 @@ import requests
23
23
  from anytree import Node, RenderTree
24
24
  from anytree.exporter import DotExporter
25
25
  from matplotlib import pyplot as plt
26
- from rospy import logwarn
27
26
  from sqlalchemy import MetaData, inspect
28
27
  from sqlalchemy.orm import Mapped, registry, class_mapper, DeclarativeBase as SQLTable, Session
29
28
  from tabulate import tabulate
@@ -107,9 +106,12 @@ def get_imports_from_scope(scope: Dict[str, Any]) -> List[str]:
107
106
  return imports
108
107
 
109
108
 
110
- def extract_imports(file_path):
111
- with open(file_path, "r") as f:
112
- tree = ast.parse(f.read(), filename=file_path)
109
+ def extract_imports(file_path: Optional[str] = None, tree: Optional[ast.AST] = None) -> Dict[str, Any]:
110
+ if tree is None:
111
+ if file_path is None:
112
+ raise ValueError("Either file_path or tree must be provided")
113
+ with open(file_path, "r") as f:
114
+ tree = ast.parse(f.read(), filename=file_path)
113
115
 
114
116
  scope = {}
115
117
 
@@ -131,7 +133,7 @@ def extract_imports(file_path):
131
133
  module = importlib.import_module(module_name)
132
134
  scope[asname] = getattr(module, name)
133
135
  except (ImportError, AttributeError) as e:
134
- logwarn(f"Could not import {module_name}: {e} while extracting imports from {file_path}")
136
+ logging.warning(f"Could not import {module_name}: {e} while extracting imports from {file_path}")
135
137
 
136
138
  return scope
137
139
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ripple_down_rules
3
- Version: 0.4.7
3
+ Version: 0.4.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
@@ -0,0 +1,57 @@
1
+ import os
2
+ from textwrap import dedent
3
+
4
+ from ripple_down_rules.datastructures.dataclasses import CaseQuery
5
+ from ripple_down_rules.datastructures.enums import PromptFor
6
+ from ripple_down_rules.user_interface.template_file_creator import TemplateFileCreator
7
+ from test_rdr_world import World, Handle, Container
8
+
9
+
10
+
11
+ def test_func_name_with_one_type():
12
+ # Test the function name
13
+ world = World()
14
+ case_query: CaseQuery = CaseQuery(world, "views", (Handle,), False)
15
+
16
+ func_name = TemplateFileCreator.get_func_name(PromptFor.Conclusion, case_query)
17
+ assert func_name == "world_views_of_type_handle"
18
+
19
+ func_name = TemplateFileCreator.get_func_name(PromptFor.Conditions, case_query)
20
+ assert func_name == "conditions_for_world_views_of_type_handle"
21
+
22
+ def test_func_name_with_two_type():
23
+ # Test the function name
24
+ world = World()
25
+ case_query: CaseQuery = CaseQuery(world, "views", (Handle, Container), False)
26
+
27
+ func_name = TemplateFileCreator.get_func_name(PromptFor.Conclusion, case_query)
28
+ assert func_name == "world_views_of_type_handle_or_container"
29
+
30
+ func_name = TemplateFileCreator.get_func_name(PromptFor.Conditions, case_query)
31
+ assert func_name == "conditions_for_world_views_of_type_handle_or_container"
32
+
33
+ def test_func_name_with_not_needed_types():
34
+ # Test the function name
35
+ world = World()
36
+ case_query: CaseQuery = CaseQuery(world, "views", (Handle, list, bool, type(None)), False)
37
+
38
+ func_name = TemplateFileCreator.get_func_name(PromptFor.Conclusion, case_query)
39
+ assert func_name == "world_views_of_type_handle"
40
+
41
+ func_name = TemplateFileCreator.get_func_name(PromptFor.Conditions, case_query)
42
+ assert func_name == "conditions_for_world_views_of_type_handle"
43
+
44
+ def test_load():
45
+ # Test the load function
46
+ world = World()
47
+ imports = "from test_rdr_world import World\n\n\n"
48
+ func_code = "def test_func(case):\n return case"
49
+ source_code = f"{imports}{func_code}\n"
50
+ source_code = dedent(source_code)
51
+ with open("test.py", "w") as f:
52
+ f.write(source_code)
53
+ code_lines, updates = TemplateFileCreator.load("test.py", "test_func")
54
+ assert code_lines == func_code.splitlines()
55
+ assert list(updates.keys()) == ["test_func"]
56
+ assert updates["test_func"](world) == world
57
+ os.remove("test.py")
@@ -1,42 +0,0 @@
1
- from unittest import TestCase
2
-
3
- from ripple_down_rules.datastructures.dataclasses import CaseQuery
4
- from ripple_down_rules.datastructures.enums import PromptFor
5
- from ripple_down_rules.user_interface.template_file_creator import TemplateFileCreator
6
- from test_rdr_world import World, Handle, Container
7
-
8
-
9
- class TestTemplateFileCreator(TestCase):
10
-
11
- def test_func_name_with_one_type(self):
12
- # Test the function name
13
- world = World()
14
- case_query: CaseQuery = CaseQuery(world, "views", (Handle,), False)
15
-
16
- func_name = TemplateFileCreator.get_func_name(PromptFor.Conclusion, case_query)
17
- self.assertEqual(func_name, "world_views_of_type_handle")
18
-
19
- func_name = TemplateFileCreator.get_func_name(PromptFor.Conditions, case_query)
20
- self.assertEqual(func_name, "conditions_for_world_views_of_type_handle")
21
-
22
- def test_func_name_with_two_type(self):
23
- # Test the function name
24
- world = World()
25
- case_query: CaseQuery = CaseQuery(world, "views", (Handle, Container), False)
26
-
27
- func_name = TemplateFileCreator.get_func_name(PromptFor.Conclusion, case_query)
28
- self.assertEqual(func_name, "world_views_of_type_handle_or_container")
29
-
30
- func_name = TemplateFileCreator.get_func_name(PromptFor.Conditions, case_query)
31
- self.assertEqual(func_name, "conditions_for_world_views_of_type_handle_or_container")
32
-
33
- def test_func_name_with_not_needed_types(self):
34
- # Test the function name
35
- world = World()
36
- case_query: CaseQuery = CaseQuery(world, "views", (Handle, list, bool, type(None)), False)
37
-
38
- func_name = TemplateFileCreator.get_func_name(PromptFor.Conclusion, case_query)
39
- self.assertEqual(func_name, "world_views_of_type_handle")
40
-
41
- func_name = TemplateFileCreator.get_func_name(PromptFor.Conditions, case_query)
42
- self.assertEqual(func_name, "conditions_for_world_views_of_type_handle")