ripple-down-rules 0.4.3__py3-none-any.whl → 0.4.7__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.
@@ -103,7 +103,7 @@ class Human(Expert):
103
103
  def ask_for_conditions(self, case_query: CaseQuery,
104
104
  last_evaluated_rule: Optional[Rule] = None) \
105
105
  -> CallableExpression:
106
- if not self.use_loaded_answers:
106
+ if not self.use_loaded_answers and self.user_prompt.viewer is None:
107
107
  show_current_and_corner_cases(case_query.case, {case_query.attribute_name: case_query.target_value},
108
108
  last_evaluated_rule=last_evaluated_rule)
109
109
  return self._get_conditions(case_query)
@@ -148,7 +148,8 @@ class Human(Expert):
148
148
  scope=case_query.scope,
149
149
  mutually_exclusive=case_query.mutually_exclusive)
150
150
  else:
151
- show_current_and_corner_cases(case_query.case)
151
+ if self.user_prompt.viewer is None:
152
+ show_current_and_corner_cases(case_query.case)
152
153
  expert_input, expression = self.user_prompt.prompt_user_for_expression(case_query, PromptFor.Conclusion)
153
154
  self.all_expert_answers.append(expert_input)
154
155
  case_query.target = expression
ripple_down_rules/rdr.py CHANGED
@@ -22,7 +22,7 @@ from .rules import Rule, SingleClassRule, MultiClassTopRule, MultiClassStopRule
22
22
  from .user_interface.gui import RDRCaseViewer
23
23
  from .utils import draw_tree, make_set, copy_case, \
24
24
  SubclassJSONSerializer, make_list, get_type_from_string, \
25
- is_conflicting, update_case, get_imports_from_scope, extract_function_source
25
+ is_conflicting, update_case, get_imports_from_scope, extract_function_source, extract_imports
26
26
 
27
27
 
28
28
  class RippleDownRules(SubclassJSONSerializer, ABC):
@@ -41,6 +41,10 @@ class RippleDownRules(SubclassJSONSerializer, ABC):
41
41
  """
42
42
  The name of the generated python file.
43
43
  """
44
+ name: Optional[str] = None
45
+ """
46
+ The name of the classifier.
47
+ """
44
48
 
45
49
  def __init__(self, start_rule: Optional[Rule] = None, viewer: Optional[RDRCaseViewer] = None):
46
50
  """
@@ -138,6 +142,7 @@ class RippleDownRules(SubclassJSONSerializer, ABC):
138
142
  """
139
143
  if case_query is None:
140
144
  raise ValueError("The case query cannot be None.")
145
+ self.name = case_query.attribute_name if self.name is None else self.name
141
146
  if case_query.target is None:
142
147
  case_query_cp = copy(case_query)
143
148
  self.classify(case_query_cp.case, modify_case=True)
@@ -237,11 +242,15 @@ class RDRWithCodeWriter(RippleDownRules, ABC):
237
242
  all_func_names = condition_func_names + conclusion_func_names
238
243
  filepath = f"{package_dir}/{self.generated_python_defs_file_name}.py"
239
244
  functions_source = extract_function_source(filepath, all_func_names, include_signature=False)
245
+ # get the scope from the imports in the file
246
+ scope = extract_imports(filepath)
240
247
  for rule in [self.start_rule] + list(self.start_rule.descendants):
241
248
  if rule.conditions is not None:
242
249
  rule.conditions.user_input = functions_source[f"conditions_{rule.uid}"]
250
+ rule.conditions.scope = scope
243
251
  if rule.conclusion is not None:
244
252
  rule.conclusion.user_input = functions_source[f"conclusion_{rule.uid}"]
253
+ rule.conclusion.scope = scope
245
254
 
246
255
  @abstractmethod
247
256
  def write_rules_as_source_code_to_file(self, rule: Rule, file, parent_indent: str = "",
@@ -275,6 +284,7 @@ class RDRWithCodeWriter(RippleDownRules, ABC):
275
284
  imports += f"from .{self.generated_python_defs_file_name} import *\n"
276
285
  imports += f"from ripple_down_rules.rdr import {self.__class__.__name__}\n"
277
286
  f.write(imports + "\n\n")
287
+ f.write(f"attribute_name = '{self.attribute_name}'\n")
278
288
  f.write(f"conclusion_type = ({', '.join([ct.__name__ for ct in self.conclusion_type])},)\n")
279
289
  f.write(f"type_ = {self.__class__.__name__}\n")
280
290
  f.write(f"\n\n{func_def}")
@@ -384,6 +394,31 @@ class RDRWithCodeWriter(RippleDownRules, ABC):
384
394
  """
385
395
  return self.start_rule.conclusion_name
386
396
 
397
+ def _to_json(self) -> Dict[str, Any]:
398
+ return {"start_rule": self.start_rule.to_json(), "generated_python_file_name": self.generated_python_file_name,
399
+ "name": self.name}
400
+
401
+ @classmethod
402
+ def _from_json(cls, data: Dict[str, Any]) -> Self:
403
+ """
404
+ Create an instance of the class from a json
405
+ """
406
+ start_rule = cls.start_rule_type().from_json(data["start_rule"])
407
+ new_rdr = cls(start_rule)
408
+ if "generated_python_file_name" in data:
409
+ new_rdr.generated_python_file_name = data["generated_python_file_name"]
410
+ if "name" in data:
411
+ new_rdr.name = data["name"]
412
+ return new_rdr
413
+
414
+ @staticmethod
415
+ @abstractmethod
416
+ def start_rule_type() -> Type[Rule]:
417
+ """
418
+ :return: The type of the starting rule of the RDR classifier.
419
+ """
420
+ pass
421
+
387
422
 
388
423
  class SingleClassRDR(RDRWithCodeWriter):
389
424
 
@@ -478,16 +513,12 @@ class SingleClassRDR(RDRWithCodeWriter):
478
513
  return (type(self.default_conclusion),)
479
514
  return super().conclusion_type
480
515
 
481
- def _to_json(self) -> Dict[str, Any]:
482
- return {"start_rule": self.start_rule.to_json()}
483
-
484
- @classmethod
485
- def _from_json(cls, data: Dict[str, Any]) -> Self:
516
+ @staticmethod
517
+ def start_rule_type() -> Type[Rule]:
486
518
  """
487
- Create an instance of the class from a json
519
+ :return: The type of the starting rule of the RDR classifier.
488
520
  """
489
- start_rule = SingleClassRule.from_json(data["start_rule"])
490
- return cls(start_rule)
521
+ return SingleClassRule
491
522
 
492
523
 
493
524
  class MultiClassRDR(RDRWithCodeWriter):
@@ -693,16 +724,12 @@ class MultiClassRDR(RDRWithCodeWriter):
693
724
  """
694
725
  self.start_rule.alternative = MultiClassTopRule(conditions, conclusion, corner_case=corner_case)
695
726
 
696
- def _to_json(self) -> Dict[str, Any]:
697
- return {"start_rule": self.start_rule.to_json()}
698
-
699
- @classmethod
700
- def _from_json(cls, data: Dict[str, Any]) -> Self:
727
+ @staticmethod
728
+ def start_rule_type() -> Type[Rule]:
701
729
  """
702
- Create an instance of the class from a json
730
+ :return: The type of the starting rule of the RDR classifier.
703
731
  """
704
- start_rule = SingleClassRule.from_json(data["start_rule"])
705
- return cls(start_rule)
732
+ return MultiClassTopRule
706
733
 
707
734
 
708
735
  class GeneralRDR(RippleDownRules):
@@ -733,15 +760,15 @@ class GeneralRDR(RippleDownRules):
733
760
  super(GeneralRDR, self).__init__(**kwargs)
734
761
  self.all_figs: List[plt.Figure] = [sr.fig for sr in self.start_rules_dict.values()]
735
762
 
736
- def add_rdr(self, rdr: Union[SingleClassRDR, MultiClassRDR], attribute_name: Optional[str] = None):
763
+ def add_rdr(self, rdr: Union[SingleClassRDR, MultiClassRDR], case_query: Optional[CaseQuery] = None):
737
764
  """
738
765
  Add a ripple down rules classifier to the map of classifiers.
739
766
 
740
767
  :param rdr: The ripple down rules classifier to add.
741
- :param attribute_name: The name of the attribute that the classifier is classifying.
768
+ :param case_query: The case query to add the classifier for.
742
769
  """
743
- attribute_name = attribute_name if attribute_name else rdr.attribute_name
744
- self.start_rules_dict[attribute_name] = rdr
770
+ name = case_query.attribute_name if case_query else rdr.name
771
+ self.start_rules_dict[name] = rdr
745
772
 
746
773
  @property
747
774
  def start_rule(self) -> Optional[Union[SingleClassRule, MultiClassTopRule]]:
@@ -825,7 +852,6 @@ class GeneralRDR(RippleDownRules):
825
852
  :param expert: The expert to ask for differentiating features as new rule conditions.
826
853
  :return: The categories that the case belongs to.
827
854
  """
828
-
829
855
  case_query_cp = copy(case_query)
830
856
  self.classify(case_query_cp.case, modify_case=True)
831
857
  case_query_cp.update_target_value()
@@ -843,7 +869,7 @@ class GeneralRDR(RippleDownRules):
843
869
  """
844
870
  if case_query.attribute_name not in self.start_rules_dict:
845
871
  new_rdr = self.initialize_new_rdr_for_attribute(case_query)
846
- self.add_rdr(new_rdr, case_query.attribute_name)
872
+ self.add_rdr(new_rdr, case_query)
847
873
 
848
874
  @staticmethod
849
875
  def initialize_new_rdr_for_attribute(case_query: CaseQuery):
@@ -854,7 +880,9 @@ class GeneralRDR(RippleDownRules):
854
880
  else MultiClassRDR()
855
881
 
856
882
  def _to_json(self) -> Dict[str, Any]:
857
- return {"start_rules": {t: rdr.to_json() for t, rdr in self.start_rules_dict.items()}}
883
+ return {"start_rules": {name: rdr.to_json() for name, rdr in self.start_rules_dict.items()}
884
+ , "generated_python_file_name": self.generated_python_file_name,
885
+ "name": self.name}
858
886
 
859
887
  @classmethod
860
888
  def _from_json(cls, data: Dict[str, Any]) -> GeneralRDR:
@@ -864,7 +892,12 @@ class GeneralRDR(RippleDownRules):
864
892
  start_rules_dict = {}
865
893
  for k, v in data["start_rules"].items():
866
894
  start_rules_dict[k] = get_type_from_string(v['_type']).from_json(v)
867
- return cls(start_rules_dict)
895
+ new_rdr = cls(start_rules_dict)
896
+ if "generated_python_file_name" in data:
897
+ new_rdr.generated_python_file_name = data["generated_python_file_name"]
898
+ if "name" in data:
899
+ new_rdr.name = data["name"]
900
+ return new_rdr
868
901
 
869
902
  def update_from_python_file(self, package_dir: str) -> None:
870
903
  """
@@ -921,7 +954,7 @@ class GeneralRDR(RippleDownRules):
921
954
  """
922
955
  :return: The default generated python file name.
923
956
  """
924
- if self.start_rule is None or self.start_rule.corner_case is None:
957
+ if self.start_rule is None or self.start_rule.conclusion is None:
925
958
  return None
926
959
  if isinstance(self.start_rule.corner_case, Case):
927
960
  name = self.start_rule.corner_case._name
@@ -5,6 +5,7 @@ from abc import ABC, abstractmethod
5
5
  from uuid import uuid4
6
6
 
7
7
  from anytree import NodeMixin
8
+ from rospy import logwarn, logdebug
8
9
  from sqlalchemy.orm import DeclarativeBase as SQLTable
9
10
  from typing_extensions import List, Optional, Self, Union, Dict, Any, Tuple
10
11
 
@@ -151,7 +152,8 @@ class Rule(NodeMixin, SubclassJSONSerializer, ABC):
151
152
  json_serialization = {"conditions": self.conditions.to_json(),
152
153
  "conclusion": conclusion_to_json(self.conclusion),
153
154
  "parent": self.parent.json_serialization if self.parent else None,
154
- "corner_case": SubclassJSONSerializer.to_json_static(self.corner_case),
155
+ "corner_case": SubclassJSONSerializer.to_json_static(self.corner_case)
156
+ if self.corner_case else None,
155
157
  "conclusion_name": self.conclusion_name,
156
158
  "weight": self.weight,
157
159
  "uid": self.uid}
@@ -159,10 +161,15 @@ class Rule(NodeMixin, SubclassJSONSerializer, ABC):
159
161
 
160
162
  @classmethod
161
163
  def _from_json(cls, data: Dict[str, Any]) -> Rule:
164
+ try:
165
+ corner_case = Case.from_json(data["corner_case"])
166
+ except Exception as e:
167
+ logdebug("Failed to load corner case from json, setting it to None.")
168
+ corner_case = None
162
169
  loaded_rule = cls(conditions=CallableExpression.from_json(data["conditions"]),
163
170
  conclusion=CallableExpression.from_json(data["conclusion"]),
164
171
  parent=cls.from_json(data["parent"]),
165
- corner_case=Case.from_json(data["corner_case"]),
172
+ corner_case=corner_case,
166
173
  conclusion_name=data["conclusion_name"],
167
174
  weight=data["weight"],
168
175
  uid=data["uid"])
@@ -134,14 +134,14 @@ class UserPrompt:
134
134
  """
135
135
  while True:
136
136
  if user_input is None:
137
- if QApplication.instance() is None:
137
+ if self.viewer is None:
138
138
  shell = IPythonShell() if shell is None else shell
139
139
  shell.run()
140
140
  user_input = shell.user_input
141
141
  else:
142
142
  app = QApplication.instance()
143
143
  if app is None:
144
- app = QApplication([])
144
+ raise RuntimeError("QApplication instance is None. Please run the application first.")
145
145
  self.viewer.show()
146
146
  app.exec()
147
147
  user_input = self.viewer.user_input
@@ -79,7 +79,7 @@ class TemplateFileCreator:
79
79
  self.case_query = case_query
80
80
  self.output_type = self.get_output_type()
81
81
  self.user_edit_line = 0
82
- self.func_name: str = self.get_func_name()
82
+ self.func_name: str = self.get_func_name(self.prompt_for, self.case_query)
83
83
  self.func_doc: str = self.get_func_doc()
84
84
  self.function_signature: str = self.get_function_signature()
85
85
  self.editor: Optional[Editor] = detect_available_editor()
@@ -146,7 +146,7 @@ class TemplateFileCreator:
146
146
 
147
147
  def get_function_signature(self) -> str:
148
148
  if self.func_name is None:
149
- self.func_name = self.get_func_name()
149
+ self.func_name = self.get_func_name(self.prompt_for, self.case_query)
150
150
  output_type_hint = self.get_output_type_hint()
151
151
  func_args = self.get_func_args()
152
152
  return f"def {self.func_name}({func_args}){output_type_hint}:"
@@ -238,15 +238,19 @@ class TemplateFileCreator:
238
238
  else:
239
239
  return f"Get possible value(s) for {self.case_query.name}"
240
240
 
241
- def get_func_name(self) -> Optional[str]:
241
+ @staticmethod
242
+ def get_func_name(prompt_for, case_query) -> Optional[str]:
242
243
  func_name = ""
243
- if self.prompt_for == PromptFor.Conditions:
244
- func_name = f"{self.prompt_for.value.lower()}_for_"
245
- case_name = self.case_query.name.replace(".", "_")
246
- if self.case_query.is_function:
244
+ if prompt_for == PromptFor.Conditions:
245
+ func_name = f"{prompt_for.value.lower()}_for_"
246
+ case_name = case_query.name.replace(".", "_")
247
+ if case_query.is_function:
247
248
  # convert any CamelCase word into snake_case by adding _ before each capital letter
248
- case_name = case_name.replace(f"_{self.case_query.attribute_name}", "")
249
+ case_name = case_name.replace(f"_{case_query.attribute_name}", "")
249
250
  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))}"
250
254
  return str_to_snake_case(func_name)
251
255
 
252
256
  @cached_property
@@ -14,6 +14,7 @@ from collections import UserDict
14
14
  from copy import deepcopy, copy
15
15
  from dataclasses import is_dataclass, fields
16
16
  from enum import Enum
17
+ from textwrap import dedent
17
18
  from types import NoneType
18
19
 
19
20
  import matplotlib
@@ -22,6 +23,7 @@ import requests
22
23
  from anytree import Node, RenderTree
23
24
  from anytree.exporter import DotExporter
24
25
  from matplotlib import pyplot as plt
26
+ from rospy import logwarn
25
27
  from sqlalchemy import MetaData, inspect
26
28
  from sqlalchemy.orm import Mapped, registry, class_mapper, DeclarativeBase as SQLTable, Session
27
29
  from tabulate import tabulate
@@ -129,7 +131,7 @@ def extract_imports(file_path):
129
131
  module = importlib.import_module(module_name)
130
132
  scope[asname] = getattr(module, name)
131
133
  except (ImportError, AttributeError) as e:
132
- print(f"Could not import {name} from {module_name}: {e}")
134
+ logwarn(f"Could not import {module_name}: {e} while extracting imports from {file_path}")
133
135
 
134
136
  return scope
135
137
 
@@ -167,7 +169,7 @@ def extract_function_source(file_path: str,
167
169
  if not include_signature:
168
170
  func_lines = func_lines[1:]
169
171
  line_numbers.append((node.lineno, node.end_lineno))
170
- functions_source[node.name] = "\n".join(func_lines) if join_lines else func_lines
172
+ functions_source[node.name] = dedent("\n".join(func_lines)) if join_lines else func_lines
171
173
  if len(functions_source) == len(function_names):
172
174
  break
173
175
  if len(functions_source) != len(function_names):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ripple_down_rules
3
- Version: 0.4.3
3
+ Version: 0.4.7
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
@@ -677,12 +677,26 @@ License: GNU GENERAL PUBLIC LICENSE
677
677
  the library. If this is what you want to do, use the GNU Lesser General
678
678
  Public License instead of this License. But first, please read
679
679
  <https://www.gnu.org/licenses/why-not-lgpl.html>.
680
- Project-URL: Homepage, https://github.com/AbdelrhmanBassiouny/ripple_down_rules
681
680
  Keywords: robotics,knowledge,reasoning,representation
682
681
  Classifier: Programming Language :: Python :: 3
683
- Requires-Python: >=3.10
684
682
  Description-Content-Type: text/markdown
685
683
  License-File: LICENSE
684
+ Requires-Dist: anytree>=2.8.0
685
+ Requires-Dist: pandas>=2.0.3
686
+ Requires-Dist: networkx>=3.1
687
+ Requires-Dist: ordered_set>=4
688
+ Requires-Dist: pygraphviz>=1.7
689
+ Requires-Dist: ucimlrepo>=0.0.7
690
+ Requires-Dist: typing-extensions>=4.12.2
691
+ Requires-Dist: matplotlib>=3.7.5
692
+ Requires-Dist: sqlalchemy
693
+ Requires-Dist: pyqt6
694
+ Requires-Dist: qtconsole
695
+ Requires-Dist: graphviz
696
+ Requires-Dist: tabulate
697
+ Requires-Dist: ipython
698
+ Requires-Dist: requests
699
+ Requires-Dist: colorama
686
700
  Dynamic: license-file
687
701
 
688
702
  # Ripple Down Rules (RDR)
@@ -1,12 +1,12 @@
1
1
  ripple_down_rules/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
2
  ripple_down_rules/datasets.py,sha256=fJbZ7V-UUYTu5XVVpFinTbuzN3YePCnUB01L3AyZVM8,6837
3
- ripple_down_rules/experts.py,sha256=S-d1EZP0SK0Ab_pMzgkOcLvZFLMdSm8_C0u3AgfouUc,6468
3
+ ripple_down_rules/experts.py,sha256=dEZKv58sdicDByj0T0bu0q6coIBMce6o4t9gJICQy1k,6556
4
4
  ripple_down_rules/failures.py,sha256=E6ajDUsw3Blom8eVLbA7d_Qnov2conhtZ0UmpQ9ZtSE,302
5
5
  ripple_down_rules/helpers.py,sha256=TvTJU0BA3dPcAyzvZFvAu7jZqsp8Lu0HAAwvuizlGjg,2018
6
- ripple_down_rules/rdr.py,sha256=cO5QUtrKDaOk75GUnBwxzq-ZYzFbJhgycX6BKeLEVtM,44258
6
+ ripple_down_rules/rdr.py,sha256=jxMvkyQ_2jdyxa5aKmvGRHvD-IJBKjsyOpxvvJbFLPE,45518
7
7
  ripple_down_rules/rdr_decorators.py,sha256=VdmE0JrE8j89b6Af1R1tLZiKfy3h1VCvhAUefN_FLLQ,6753
8
- ripple_down_rules/rules.py,sha256=QQy7NBG6mKiowxVG_LjQJBxLTDW2hMyx5zAgwUxdCMM,17183
9
- ripple_down_rules/utils.py,sha256=kJEqmKuq3NxM8gY7MrXSps2Xvf1FsYFgTMo3PSYZJY8,49098
8
+ ripple_down_rules/rules.py,sha256=PLNaRLn5tQbq7gNsAe-n9hTjKC80p_7MbfkZWgDxLBg,17474
9
+ ripple_down_rules/utils.py,sha256=3x6BHCoqEewn40lDtUIWOMQLZ_IA0j7WnY9_YDHvs-o,49192
10
10
  ripple_down_rules/datastructures/__init__.py,sha256=V2aNgf5C96Y5-IGghra3n9uiefpoIm_QdT7cc_C8cxQ,111
11
11
  ripple_down_rules/datastructures/callable_expression.py,sha256=jA7424_mWPbOoPICW3eLMX0-ypxnsW6gOqxrJ7JpDbE,11610
12
12
  ripple_down_rules/datastructures/case.py,sha256=oC8OSdhXvHE-Zx1IIQlad-fsKzQQqr6MZBW24c-dbeU,15191
@@ -16,10 +16,10 @@ ripple_down_rules/user_interface/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRk
16
16
  ripple_down_rules/user_interface/gui.py,sha256=GlVUYGpMRikrd-FjYCAamdKFAuTAeiq21ZzQvjVKJ4g,27588
17
17
  ripple_down_rules/user_interface/ipython_custom_shell.py,sha256=tc7ms80iPNElm9AYC6i1FlfMKkqHLT8wmOEXg_k9yAU,6275
18
18
  ripple_down_rules/user_interface/object_diagram.py,sha256=84JlEH0nQmtGmP8Su5iRX3ZfqByYHbVwd0BQYYPuckY,4436
19
- ripple_down_rules/user_interface/prompt.py,sha256=tQyIrRno1YuS3K0c4p48FVTdDJGG0HCE63WHVKpSJ1I,7976
20
- ripple_down_rules/user_interface/template_file_creator.py,sha256=PegwW_g8UScjEF2GGe1eV0VoWUzxPhA27L9ExbL7Y_E,12610
21
- ripple_down_rules-0.4.3.dist-info/licenses/LICENSE,sha256=ixuiBLtpoK3iv89l7ylKkg9rs2GzF9ukPH7ynZYzK5s,35148
22
- ripple_down_rules-0.4.3.dist-info/METADATA,sha256=gT-m0t8DxQkY2a7qqne4CxS-6WnobM0LGBa4dBlMOwo,42875
23
- ripple_down_rules-0.4.3.dist-info/WHEEL,sha256=zaaOINJESkSfm_4HQVc5ssNzHCPXhJm0kEUakpsEHaU,91
24
- ripple_down_rules-0.4.3.dist-info/top_level.txt,sha256=VeoLhEhyK46M1OHwoPbCQLI1EifLjChqGzhQ6WEUqeM,18
25
- ripple_down_rules-0.4.3.dist-info/RECORD,,
19
+ ripple_down_rules/user_interface/prompt.py,sha256=97s5mhmsqrFTQkfmHssYFko0SPLBDxesHoLumqZO0FA,8028
20
+ ripple_down_rules/user_interface/template_file_creator.py,sha256=_CEe8H07q62YpImy1p72vXcPHkJ2_Q0n6t56K6g2gPE,12930
21
+ ripple_down_rules-0.4.7.dist-info/licenses/LICENSE,sha256=ixuiBLtpoK3iv89l7ylKkg9rs2GzF9ukPH7ynZYzK5s,35148
22
+ ripple_down_rules-0.4.7.dist-info/METADATA,sha256=NOxEaLxlUHlfwCGv9lgu_083JZl3ls6fcknejdagcm0,43217
23
+ ripple_down_rules-0.4.7.dist-info/WHEEL,sha256=zaaOINJESkSfm_4HQVc5ssNzHCPXhJm0kEUakpsEHaU,91
24
+ ripple_down_rules-0.4.7.dist-info/top_level.txt,sha256=VeoLhEhyK46M1OHwoPbCQLI1EifLjChqGzhQ6WEUqeM,18
25
+ ripple_down_rules-0.4.7.dist-info/RECORD,,