ripple-down-rules 0.6.47__py3-none-any.whl → 0.6.48__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.
@@ -1,5 +1,17 @@
1
- __version__ = "0.6.47"
1
+ __version__ = "0.6.48"
2
2
 
3
3
  import logging
4
+ import sys
5
+
4
6
  logger = logging.Logger("rdr")
5
- logger.setLevel(logging.INFO)
7
+ logger.setLevel(logging.INFO)
8
+
9
+ try:
10
+ from PyQt6.QtWidgets import QApplication
11
+ app = QApplication(sys.argv)
12
+ except ImportError:
13
+ pass
14
+
15
+ from .datastructures.dataclasses import CaseQuery
16
+ from .rdr_decorators import RDRDecorator
17
+ from .rdr import MultiClassRDR, SingleClassRDR, GeneralRDR
@@ -1,6 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import os
4
+ import sys
4
5
  from types import ModuleType
5
6
  from typing import Tuple
6
7
 
@@ -90,7 +91,7 @@ def load_or_create_func_rdr_model(func, model_dir: str, rdr_type: Type[RippleDow
90
91
  :param rdr_kwargs: Additional arguments to pass to the RDR constructor in the case of a new model.
91
92
  """
92
93
  model_name = get_func_rdr_model_name(func)
93
- model_path = os.path.join(model_dir, model_name, "rdr_metadata", f"{model_name}.json")
94
+ model_path = os.path.join(model_dir, model_name, f"{model_name}.py")
94
95
  if os.path.exists(model_path):
95
96
  rdr = rdr_type.load(load_dir=model_dir, model_name=model_name)
96
97
  else:
@@ -116,3 +117,13 @@ def get_an_updated_case_copy(case: Case, conclusion: Callable, attribute_name: s
116
117
  output = {attribute_name: output}
117
118
  update_case(temp_case_query, output)
118
119
  return case_cp
120
+
121
+ def enable_gui():
122
+ """
123
+ Enable the GUI for Ripple Down Rules if available.
124
+ """
125
+ try:
126
+ from .user_interface.gui import RDRCaseViewer
127
+ viewer = RDRCaseViewer()
128
+ except ImportError:
129
+ pass
ripple_down_rules/rdr.py CHANGED
@@ -46,7 +46,8 @@ from .utils import draw_tree, make_set, SubclassJSONSerializer, make_list, get_t
46
46
  is_value_conflicting, extract_function_source, extract_imports, get_full_class_name, \
47
47
  is_iterable, str_to_snake_case, get_import_path_from_path, get_imports_from_types, render_tree, \
48
48
  get_types_to_import_from_func_type_hints, get_function_return_type, get_file_that_ends_with, \
49
- get_and_import_python_module, get_and_import_python_modules_in_a_package, get_type_from_type_hint
49
+ get_and_import_python_module, get_and_import_python_modules_in_a_package, get_type_from_type_hint, \
50
+ are_results_subclass_of_types
50
51
 
51
52
 
52
53
  class RippleDownRules(SubclassJSONSerializer, ABC):
@@ -103,6 +104,16 @@ class RippleDownRules(SubclassJSONSerializer, ABC):
103
104
  self.viewer: Optional[RDRCaseViewer] = RDRCaseViewer.instances[0]\
104
105
  if RDRCaseViewer and any(RDRCaseViewer.instances) else None
105
106
  self.input_node: Optional[Rule] = None
107
+ self.update_model()
108
+
109
+ def update_model(self):
110
+ """
111
+ Update the model from the model directory if it exists.
112
+ """
113
+ if self.save_dir is not None and self.model_name is not None:
114
+ model_path = os.path.join(self.save_dir, self.model_name)
115
+ if os.path.exists(model_path):
116
+ self.update_from_python(model_path, update_rule_tree=True)
106
117
 
107
118
  def write_rdr_metadata_to_pyton_file(self, file: TextIOWrapper):
108
119
  """
@@ -221,7 +232,7 @@ class RippleDownRules(SubclassJSONSerializer, ABC):
221
232
  if rdr is None:
222
233
  rdr = cls.from_python(model_dir, parent_package_name=package_name)
223
234
  else:
224
- rdr.update_from_python(model_dir, package_name=package_name)
235
+ rdr.update_from_python(model_dir, parent_package_name=package_name)
225
236
  rdr.to_json_file(json_file)
226
237
  except (FileNotFoundError, ValueError, SyntaxError, ModuleNotFoundError) as e:
227
238
  logger.warning(f"Could not load the python file for the model {model_name} from {model_dir}. "
@@ -234,18 +245,22 @@ class RippleDownRules(SubclassJSONSerializer, ABC):
234
245
  return rdr
235
246
 
236
247
  @classmethod
237
- @abstractmethod
238
- def from_python(cls, model_dir: str, python_file_path: Optional[str] = None,
248
+ def from_python(cls, model_path: str,
249
+ python_file_path: Optional[str] = None,
239
250
  parent_package_name: Optional[str] = None) -> Self:
240
251
  """
241
- Load the classifier from a python file.
252
+ Load the RDR classifier from a generated python file.
242
253
 
243
- :param model_dir: The path to the directory where the generated python file is located.
244
- :param python_file_path: The path to the python file to load the classifier from.
254
+ :param model_path: The directory where the generated python file is located.
255
+ :param python_file_path: The path to the generated python file that contains the RDR classifier function.
245
256
  :param parent_package_name: The name of the package that contains the RDR classifier function, this
246
257
  is required in case of relative imports in the generated python file.
258
+ :return: An instance of the RDR classifier.
247
259
  """
248
- pass
260
+ rdr = cls()
261
+ rdr.update_from_python(model_path, parent_package_name=parent_package_name, python_file_path=python_file_path,
262
+ update_rule_tree=True)
263
+ return rdr
249
264
 
250
265
  @abstractmethod
251
266
  def _write_to_python(self, model_dir: str, package_name: Optional[str] = None):
@@ -555,13 +570,17 @@ class RippleDownRules(SubclassJSONSerializer, ABC):
555
570
  pass
556
571
 
557
572
  @abstractmethod
558
- def update_from_python(self, model_dir: str, package_name: Optional[str] = None):
573
+ def update_from_python(self, model_dir: str, parent_package_name: Optional[str] = None,
574
+ python_file_path: Optional[str] = None,
575
+ update_rule_tree: bool = False):
559
576
  """
560
577
  Update the rules from the generated python file, that might have been modified by the user.
561
578
 
562
579
  :param model_dir: The directory where the generated python file is located.
563
- :param package_name: The name of the package that contains the RDR classifier function, this
580
+ :param parent_package_name: The name of the package that contains the RDR classifier function, this
564
581
  is required in case of relative imports in the generated python file.
582
+ :param python_file_path: The path to the generated python file that contains the RDR classifier function.
583
+ :param update_rule_tree: Whether to update the rule tree from the python file or not.
565
584
  """
566
585
  pass
567
586
 
@@ -802,24 +821,6 @@ class MultiClassTreeBuilder(TreeBuilder):
802
821
 
803
822
  class RDRWithCodeWriter(RippleDownRules, ABC):
804
823
 
805
- @classmethod
806
- def from_python(cls, model_path: str,
807
- python_file_path: Optional[str] = None,
808
- parent_package_name: Optional[str] = None) -> Self:
809
- """
810
- Load the RDR classifier from a generated python file.
811
-
812
- :param model_path: The directory where the generated python file is located.
813
- :param python_file_path: The path to the generated python file that contains the RDR classifier function.
814
- :param parent_package_name: The name of the package that contains the RDR classifier function, this
815
- is required in case of relative imports in the generated python file.
816
- :return: An instance of the RDR classifier.
817
- """
818
- rule_tree_root = cls.read_rule_tree_from_python(model_path, python_file_path=python_file_path)
819
- rdr = cls(start_rule=rule_tree_root)
820
- rdr.update_from_python(model_path, package_name=parent_package_name, python_file_path=python_file_path)
821
- return rdr
822
-
823
824
  @classmethod
824
825
  def read_rule_tree_from_python(cls, model_path: str, python_file_path: Optional[str] = None) -> Rule:
825
826
  """
@@ -861,16 +862,19 @@ class RDRWithCodeWriter(RippleDownRules, ABC):
861
862
  return []
862
863
  return [r for r in [self.start_rule] + list(self.start_rule.descendants) if r.conditions is not None]
863
864
 
864
- def update_from_python(self, model_dir: str, package_name: Optional[str] = None,
865
- python_file_path: Optional[str] = None):
865
+ def update_from_python(self, model_dir: str, parent_package_name: Optional[str] = None,
866
+ python_file_path: Optional[str] = None, update_rule_tree: bool = False):
866
867
  """
867
868
  Update the rules from the generated python file, that might have been modified by the user.
868
869
 
869
870
  :param model_dir: The directory where the generated python file is located.
870
- :param package_name: The name of the package that contains the RDR classifier function, this
871
+ :param parent_package_name: The name of the package that contains the RDR classifier function, this
871
872
  is required in case of relative imports in the generated python file.
872
873
  :param python_file_path: The path to the generated python file that contains the RDR classifier function.
874
+ :param update_rule_tree: Whether to update the rule tree from the python file or not.
873
875
  """
876
+ if update_rule_tree:
877
+ self.start_rule = self.read_rule_tree_from_python(model_dir, python_file_path=python_file_path)
874
878
  all_rules = self.all_rules
875
879
  condition_func_names = [rule.generated_conditions_function_name for rule in all_rules]
876
880
  conclusion_func_names = [rule.generated_conclusion_function_name for rule in all_rules
@@ -880,24 +884,24 @@ class RDRWithCodeWriter(RippleDownRules, ABC):
880
884
  main_module, defs_module, cases_module = self.get_and_import_model_python_modules(
881
885
  model_dir,
882
886
  python_file_path=python_file_path,
883
- parent_package_name=package_name)
887
+ parent_package_name=parent_package_name)
884
888
  self.generated_python_file_name = Path(main_module.__file__).name.replace(".py", "")
885
889
 
886
890
  self.update_rdr_metadata_from_python(main_module)
887
891
 
888
892
  functions_source = extract_function_source(defs_module.__file__,
889
893
  all_func_names, include_signature=True)
890
- scope = extract_imports(defs_module.__file__, package_name=package_name)
894
+ scope = extract_imports(defs_module.__file__, package_name=parent_package_name)
891
895
 
892
896
  cases_source, cases_scope = None, None
893
897
  if cases_module:
894
898
  with open(cases_module.__file__, "r") as f:
895
899
  cases_source = f.read()
896
- cases_scope = extract_imports(cases_module.__file__, package_name=package_name)
900
+ cases_scope = extract_imports(cases_module.__file__, package_name=parent_package_name)
897
901
 
898
902
  with open(main_module.__file__, "r") as f:
899
903
  main_source = f.read()
900
- main_scope = extract_imports(main_module.__file__, package_name=package_name)
904
+ main_scope = extract_imports(main_module.__file__, package_name=parent_package_name)
901
905
  attribute_name_line = [l for l in main_source.split('\n') if "attribute_name = " in l]
902
906
  conclusion_name = None
903
907
  if len(attribute_name_line) > 0:
@@ -1325,7 +1329,7 @@ class MultiClassRDR(RDRWithCodeWriter):
1325
1329
  evaluated_rule.last_conclusion = rule_conclusion
1326
1330
  if case_query is not None:
1327
1331
  rule_conclusion_types = set(map(type, make_list(rule_conclusion)))
1328
- if any(rule_conclusion_types.intersection(set(case_query.core_attribute_type))):
1332
+ if are_results_subclass_of_types(rule_conclusion_types, case_query.core_attribute_type):
1329
1333
  evaluated_rule.contributed_to_case_query = True
1330
1334
  self.add_conclusion(rule_conclusion)
1331
1335
  evaluated_rule = next_rule
@@ -1568,21 +1572,9 @@ class GeneralRDR(RippleDownRules):
1568
1572
  is required in case of relative imports in the generated python file.
1569
1573
  :return: An instance of the class.
1570
1574
  """
1571
- if python_file_path is None:
1572
- main_python_file_path = cls.get_generated_python_file_path(model_dir)
1573
- else:
1574
- main_python_file_path = python_file_path
1575
- main_module = get_and_import_python_module(main_python_file_path, parent_package_name=parent_package_name)
1576
- classifiers_dict = main_module.classifiers_dict
1577
- start_rules_dict = {}
1578
- for rdr_name, rdr_module in classifiers_dict.items():
1579
- rdr_acronym = rdr_module.__name__.split('_')[-1]
1580
- rdr_type = cls.get_rdr_type_from_acronym(rdr_acronym)
1581
- rdr_model_path = main_python_file_path.replace('_rdr.py', f'_{rdr_name}_{rdr_acronym}.py')
1582
- rdr = rdr_type.from_python(model_dir, python_file_path=rdr_model_path, parent_package_name=parent_package_name)
1583
- start_rules_dict[rdr_name] = rdr
1584
- grdr = cls(category_rdr_map=start_rules_dict)
1585
- grdr.update_rdr_metadata_from_python(main_module)
1575
+ grdr = cls()
1576
+ grdr.update_from_python(model_dir, parent_package_name=parent_package_name, python_file_path=python_file_path,
1577
+ update_rule_tree=True)
1586
1578
  return grdr
1587
1579
 
1588
1580
  @classmethod
@@ -1706,16 +1698,38 @@ class GeneralRDR(RippleDownRules):
1706
1698
  new_rdr.case_name = data["case_name"]
1707
1699
  return new_rdr
1708
1700
 
1709
- def update_from_python(self, model_dir: str, package_name: Optional[str] = None) -> None:
1701
+ def update_from_python(self, model_dir: str, parent_package_name: Optional[str] = None,
1702
+ python_file_path: Optional[str] = None,
1703
+ update_rule_tree: bool = False) -> None:
1710
1704
  """
1711
1705
  Update the rules from the generated python file, that might have been modified by the user.
1712
1706
 
1713
1707
  :param model_dir: The directory where the model is stored.
1714
- :param package_name: The name of the package that contains the RDR classifier function, this
1708
+ :param parent_package_name: The name of the package that contains the RDR classifier function, this
1715
1709
  is required in case of relative imports in the generated python file.
1710
+ :param python_file_path: The path to the python file, if not provided, it will be generated from the model_dir.
1711
+ :param update_rule_tree: Whether to update the rule tree from the python file or not.
1716
1712
  """
1717
- for rdr in self.start_rules_dict.values():
1718
- rdr.update_from_python(model_dir, package_name=package_name)
1713
+ if update_rule_tree:
1714
+ if python_file_path is None:
1715
+ main_python_file_path = self.get_generated_python_file_path(model_dir)
1716
+ else:
1717
+ main_python_file_path = python_file_path
1718
+ main_module = get_and_import_python_module(main_python_file_path, parent_package_name=parent_package_name)
1719
+ classifiers_dict = main_module.classifiers_dict
1720
+ self.start_rules_dict = {}
1721
+ for rdr_name, rdr_module in classifiers_dict.items():
1722
+ rdr_acronym = rdr_module.__name__.split('_')[-1]
1723
+ rdr_type = self.get_rdr_type_from_acronym(rdr_acronym)
1724
+ rdr_model_path = main_python_file_path.replace('_rdr.py', f'_{rdr_name}_{rdr_acronym}.py')
1725
+ rdr = rdr_type.from_python(model_dir, python_file_path=rdr_model_path,
1726
+ parent_package_name=parent_package_name)
1727
+ self.start_rules_dict[rdr_name] = rdr
1728
+
1729
+ self.update_rdr_metadata_from_python(main_module)
1730
+ else:
1731
+ for rdr in self.start_rules_dict.values():
1732
+ rdr.update_from_python(model_dir, parent_package_name=parent_package_name)
1719
1733
 
1720
1734
  def _write_to_python(self, model_dir: str, package_name: Optional[str] = None) -> None:
1721
1735
  """
@@ -5,9 +5,11 @@ of the RDRs.
5
5
  """
6
6
  import os.path
7
7
  from functools import wraps
8
+ from typing import get_origin
8
9
 
9
10
  from typing_extensions import Callable, Optional, Type, Tuple, Dict, Any, Self, get_type_hints, List, Union, Sequence
10
11
 
12
+ from .utils import get_type_from_type_hint
11
13
  from .datastructures.case import Case
12
14
  from .datastructures.dataclasses import CaseQuery
13
15
  from .experts import Expert, Human
@@ -18,7 +20,7 @@ try:
18
20
  except ImportError:
19
21
  RDRCaseViewer = None
20
22
  from .utils import get_method_args_as_dict, get_func_rdr_model_name, make_set, \
21
- get_method_class_if_exists, str_to_snake_case
23
+ get_method_class_if_exists, str_to_snake_case, make_list
22
24
 
23
25
 
24
26
  class RDRDecorator:
@@ -77,7 +79,7 @@ class RDRDecorator:
77
79
  lambda f: f # Default to no fitting decorator
78
80
  self.generate_dot_file = generate_dot_file
79
81
  self.not_none_output_found: bool = False
80
- self.load()
82
+ self.origin_type: Optional[Type] = None
81
83
 
82
84
  def decorator(self, func: Callable) -> Callable:
83
85
 
@@ -86,6 +88,10 @@ class RDRDecorator:
86
88
 
87
89
  if self.model_name is None:
88
90
  self.initialize_rdr_model_name_and_load(func)
91
+ if self.origin_type is None and not self.mutual_exclusive:
92
+ self.origin_type = get_origin(get_type_hints(func)['return'])
93
+ if self.origin_type:
94
+ self.origin_type = get_type_from_type_hint(self.origin_type)
89
95
 
90
96
  func_output = {self.output_name: func(*args, **kwargs)}
91
97
 
@@ -125,6 +131,8 @@ class RDRDecorator:
125
131
  self.not_none_output_found = True
126
132
 
127
133
  if self.output_name in output:
134
+ if self.origin_type == list:
135
+ return make_list(output[self.output_name])
128
136
  return output[self.output_name]
129
137
  else:
130
138
  return func_output[self.output_name]
@@ -211,14 +219,10 @@ class RDRDecorator:
211
219
  """
212
220
  Load the RDR model from the specified directory.
213
221
  """
214
- self.rdr = None
215
- if self.model_name is not None:
216
- self.rdr = GeneralRDR.load(self.rdr_models_dir, self.model_name, package_name=self.package_name)
217
- if self.rdr is None:
218
- self.rdr = GeneralRDR(save_dir=self.rdr_models_dir, model_name=self.model_name)
222
+ self.rdr = GeneralRDR(save_dir=self.rdr_models_dir, model_name=self.model_name)
219
223
 
220
224
  def update_from_python(self):
221
225
  """
222
226
  Update the RDR model from a python file.
223
227
  """
224
- self.rdr.update_from_python(self.rdr_models_dir, package_name=self.package_name)
228
+ self.rdr.update_from_python(self.rdr_models_dir, parent_package_name=self.package_name)
@@ -165,7 +165,7 @@ class IPythonShell:
165
165
  """
166
166
  if self.shell.all_lines[-1] in ['quit', 'exit']:
167
167
  self.user_input = 'exit'
168
- elif self.shell.all_lines[0].replace('return', '').strip() == '':
168
+ elif self.shell.all_lines[-1].replace('return', '').strip() == '':
169
169
  self.user_input = None
170
170
  else:
171
171
  self.all_code_lines = extract_dependencies(self.shell.all_lines)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ripple_down_rules
3
- Version: 0.6.47
3
+ Version: 0.6.48
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
@@ -1,9 +1,9 @@
1
- ripple_down_rules/__init__.py,sha256=eA36n1YXRokHKFHwQGjTQGszPb5BH0eP82R866uCMOA,99
1
+ ripple_down_rules/__init__.py,sha256=IIYp1vwC-NyqQshOebhowV9ynnc5PEZ-NFCbXeL057w,375
2
2
  ripple_down_rules/experts.py,sha256=KXwWCmDrCffu9HW3yNewqWc1e5rnPI5Rnc981w_5M7U,17896
3
3
  ripple_down_rules/failures.py,sha256=ZPypPwKjyVcJkY9YG6p538ld8ZzpdW6CrC1RmJi44Ek,42
4
- ripple_down_rules/helpers.py,sha256=X1psHOqrb4_xYN4ssQNB8S9aRKKsqgihAyWJurN0dqk,5499
5
- ripple_down_rules/rdr.py,sha256=pG7Sn3WeKVrCwoBuroO31_d9R2vVSCw6BfbtYU1H6wE,83228
6
- ripple_down_rules/rdr_decorators.py,sha256=ehK9YFYCh21fnbuzOX000yBwVDXL-QqYuEfKoWNYyDU,11468
4
+ ripple_down_rules/helpers.py,sha256=sVU1Q7HPbMh57X8zDYP6vj1maDoaVAvnXHpUYIbr_As,5715
5
+ ripple_down_rules/rdr.py,sha256=M7CEjHIegJWiDZtn-B8Qf9CpMvplAkrFX3HiiyGQC7M,84292
6
+ ripple_down_rules/rdr_decorators.py,sha256=sbrbo5qfhnY9dIge4sFlkn4p8yPNBWWQ1Nq-NvMDx20,11756
7
7
  ripple_down_rules/rules.py,sha256=L4Ws-x3g5ljE0GrDt4LAib2qtR1C1_Ra4cRcIB-IQDI,28702
8
8
  ripple_down_rules/start-code-server.sh,sha256=otClk7VmDgBOX2TS_cjws6K0UwvgAUJhoA0ugkPCLqQ,949
9
9
  ripple_down_rules/utils.py,sha256=5UL4h-m5Q4l7RyCYqmUmDDxUAkFqPYpFa3QldDYoJUE,80752
@@ -14,12 +14,12 @@ ripple_down_rules/datastructures/dataclasses.py,sha256=3vX52WrAHgVyw0LUSgSBOVFaQ
14
14
  ripple_down_rules/datastructures/enums.py,sha256=CvcROl8fE7A6uTbMfs2lLpyxwS_ZFtFcQlBDDKFfoHc,6059
15
15
  ripple_down_rules/user_interface/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
16
16
  ripple_down_rules/user_interface/gui.py,sha256=K6cvA9TOXIDpk0quGCamrWqDRlvz0QruDaTj4Y4PWWI,28544
17
- ripple_down_rules/user_interface/ipython_custom_shell.py,sha256=RLdPqPxx-a0Sh74UZWyRBuxS_OKeXJnzoDfms4i-Aus,7710
17
+ ripple_down_rules/user_interface/ipython_custom_shell.py,sha256=GQf5Je5szkPH17g42zHQ3cKy_MATywXV2bCpwSKKsZE,7711
18
18
  ripple_down_rules/user_interface/object_diagram.py,sha256=FEa2HaYR9QmTE6NsOwBvZ0jqmu3DKyg6mig2VE5ZP4Y,4956
19
19
  ripple_down_rules/user_interface/prompt.py,sha256=WPbw_8_-8SpF2ISyRZRuFwPKBEuGC4HaX3lbCPFHhh8,10314
20
20
  ripple_down_rules/user_interface/template_file_creator.py,sha256=uSbosZS15MOR3Nv7M3MrFuoiKXyP4cBId-EK3I6stHM,13660
21
- ripple_down_rules-0.6.47.dist-info/licenses/LICENSE,sha256=ixuiBLtpoK3iv89l7ylKkg9rs2GzF9ukPH7ynZYzK5s,35148
22
- ripple_down_rules-0.6.47.dist-info/METADATA,sha256=8kqMXzC6_vZ_vwdXOWFaV4L9AIhKIMJ3D9o8e6r5ur4,48294
23
- ripple_down_rules-0.6.47.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
24
- ripple_down_rules-0.6.47.dist-info/top_level.txt,sha256=VeoLhEhyK46M1OHwoPbCQLI1EifLjChqGzhQ6WEUqeM,18
25
- ripple_down_rules-0.6.47.dist-info/RECORD,,
21
+ ripple_down_rules-0.6.48.dist-info/licenses/LICENSE,sha256=ixuiBLtpoK3iv89l7ylKkg9rs2GzF9ukPH7ynZYzK5s,35148
22
+ ripple_down_rules-0.6.48.dist-info/METADATA,sha256=rkNl0nbm5W3NGX6q5io1EOkLHYgY0mtWdE_F0Y0u7Jk,48294
23
+ ripple_down_rules-0.6.48.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
24
+ ripple_down_rules-0.6.48.dist-info/top_level.txt,sha256=VeoLhEhyK46M1OHwoPbCQLI1EifLjChqGzhQ6WEUqeM,18
25
+ ripple_down_rules-0.6.48.dist-info/RECORD,,