ripple-down-rules 0.5.63__py3-none-any.whl → 0.5.71__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.
@@ -10,13 +10,15 @@ import os
10
10
  import re
11
11
  import threading
12
12
  import uuid
13
- from collections import UserDict
13
+ from collections import UserDict, defaultdict
14
14
  from copy import deepcopy, copy
15
15
  from dataclasses import is_dataclass, fields
16
16
  from enum import Enum
17
+ from os.path import dirname
17
18
  from textwrap import dedent
18
19
  from types import NoneType
19
- from typing import List
20
+
21
+ from sqlalchemy.exc import NoInspectionAvailable
20
22
 
21
23
 
22
24
  try:
@@ -42,8 +44,7 @@ from sqlalchemy import MetaData, inspect
42
44
  from sqlalchemy.orm import Mapped, registry, class_mapper, DeclarativeBase as SQLTable, Session
43
45
  from tabulate import tabulate
44
46
  from typing_extensions import Callable, Set, Any, Type, Dict, TYPE_CHECKING, get_type_hints, \
45
- get_origin, get_args, Tuple, Optional, List, Union, Self
46
-
47
+ get_origin, get_args, Tuple, Optional, List, Union, Self, ForwardRef
47
48
 
48
49
  if TYPE_CHECKING:
49
50
  from .datastructures.case import Case
@@ -82,7 +83,7 @@ def are_results_subclass_of_types(result_types: List[Any], types_: List[Type]) -
82
83
  return True
83
84
 
84
85
 
85
- def get_imports_from_types(types: List[Type]) -> List[str]:
86
+ def _get_imports_from_types(types: List[Type]) -> List[str]:
86
87
  """
87
88
  Get the import statements for a list of types.
88
89
 
@@ -158,7 +159,7 @@ def extract_function_source(file_path: str,
158
159
  return_line_numbers: bool = False,
159
160
  include_signature: bool = True) \
160
161
  -> Union[Dict[str, Union[str, List[str]]],
161
- Tuple[Dict[str, Union[str, List[str]]], List[Tuple[int, int]]]]:
162
+ Tuple[Dict[str, Union[str, List[str]]], Dict[str, Tuple[int, int]]]]:
162
163
  """
163
164
  Extract the source code of a function from a file.
164
165
 
@@ -177,7 +178,7 @@ def extract_function_source(file_path: str,
177
178
  tree = ast.parse(source)
178
179
  function_names = make_list(function_names)
179
180
  functions_source: Dict[str, Union[str, List[str]]] = {}
180
- line_numbers = []
181
+ line_numbers: Dict[str, Tuple[int, int]] = {}
181
182
  for node in tree.body:
182
183
  if isinstance(node, ast.FunctionDef) and (node.name in function_names or len(function_names) == 0):
183
184
  # Get the line numbers of the function
@@ -185,7 +186,7 @@ def extract_function_source(file_path: str,
185
186
  func_lines = lines[node.lineno - 1:node.end_lineno]
186
187
  if not include_signature:
187
188
  func_lines = func_lines[1:]
188
- line_numbers.append((node.lineno, node.end_lineno))
189
+ line_numbers[node.name] = (node.lineno, node.end_lineno)
189
190
  functions_source[node.name] = dedent("\n".join(func_lines)) if join_lines else func_lines
190
191
  if (len(functions_source) >= len(function_names)) and (not len(function_names) == 0):
191
192
  break
@@ -660,56 +661,203 @@ def get_func_rdr_model_name(func: Callable, include_file_name: bool = False) ->
660
661
  return model_name
661
662
 
662
663
 
663
- def extract_bracket_arguments(val: str) -> List[str]:
664
- """
665
- Extract arguments inside brackets into a list.
666
-
667
- :param val: The string containing brackets.
668
- :return: List of arguments inside brackets.
669
- """
670
- if '[' not in val:
671
- return [val]
672
- args_start = val.find('[')
673
- args_end = val.rfind(']')
674
- if args_end == -1:
675
- return [val]
676
- base_type = val[:args_start]
677
- args = val[args_start + 1:args_end].split(',')
678
- args = [arg.strip() for arg in args]
679
- return [base_type] + args
680
-
681
-
682
- def typing_hint_to_str(type_hint: Any) -> Tuple[str, List[str]]:
683
- """
684
- Convert a typing hint to a string.
685
-
686
- :param type_hint: The typing hint to convert.
687
- :return: The string representation of the typing hint.
688
- """
689
- val = (str(type_hint).strip("<>")
690
- .replace("class ", "")
691
- # .replace("typing.", "")
692
- .replace("'", ""))
693
- all_args = []
694
- if '[' in val:
695
- args = extract_bracket_arguments(val)
696
- args_with_brackets = [arg for arg in args if '[' in arg]
697
- all_args.extend([arg for arg in args if '[' not in arg])
698
- while args_with_brackets:
699
- for arg in args:
700
- if '[' in arg:
701
- sub_args = extract_bracket_arguments(arg)
702
- args_with_brackets.remove(arg)
703
- all_args.extend([sarg for sarg in sub_args if '[' not in sarg])
704
- args_with_brackets.extend([sarg for sarg in sub_args if '[' in sarg])
705
- elif arg not in all_args:
706
- all_args.append(arg)
707
- args = args_with_brackets
708
- for arg in all_args:
709
- val = val.replace(arg, arg.split('.')[-1])
664
+ def stringify_hint(tp):
665
+ """Recursively convert a type hint to a string."""
666
+ if isinstance(tp, str):
667
+ return tp
668
+
669
+ # Handle ForwardRef (string annotations not yet evaluated)
670
+ if isinstance(tp, ForwardRef):
671
+ return tp.__forward_arg__
672
+
673
+ # Handle typing generics like List[int], Dict[str, List[int]], etc.
674
+ origin = get_origin(tp)
675
+ args = get_args(tp)
676
+
677
+ if origin is not None:
678
+ origin_str = getattr(origin, '__name__', str(origin)).capitalize()
679
+ args_str = ", ".join(stringify_hint(arg) for arg in args)
680
+ return f"{origin_str}[{args_str}]"
681
+
682
+ # Handle built-in types like int, str, etc.
683
+ if isinstance(tp, type):
684
+ if tp.__module__ == 'builtins':
685
+ return tp.__name__
686
+ return f"{tp.__qualname__}"
687
+
688
+ return str(tp)
689
+
690
+
691
+ def is_builtin_type(tp):
692
+ return isinstance(tp, type) and tp.__module__ == "builtins"
693
+
694
+
695
+ def is_typing_type(tp):
696
+ return tp.__module__ == "typing"
697
+
698
+ origin_type_to_hint = {
699
+ list: List,
700
+ set: Set,
701
+ dict: Dict,
702
+ tuple: Tuple,
703
+ }
704
+
705
+ def extract_types(tp, seen: Set = None) -> Set[type]:
706
+ """Recursively extract all base types from a type hint."""
707
+ if seen is None:
708
+ seen = set()
709
+
710
+ if tp in seen or isinstance(tp, str):
711
+ return seen
712
+
713
+ # seen.add(tp)
714
+
715
+ if isinstance(tp, ForwardRef):
716
+ # Can't resolve until evaluated
717
+ return seen
718
+
719
+ origin = get_origin(tp)
720
+ args = get_args(tp)
721
+
722
+ if origin:
723
+ if origin in origin_type_to_hint:
724
+ seen.add(origin_type_to_hint[origin])
725
+ else:
726
+ seen.add(origin)
727
+ for arg in args:
728
+ extract_types(arg, seen)
729
+
730
+ elif isinstance(tp, type):
731
+ seen.add(tp)
732
+
733
+ return seen
734
+
735
+
736
+ def get_types_to_import_from_func_type_hints(func: Callable) -> Set[Type]:
737
+ """
738
+ Extract importable types from a function's annotations.
739
+
740
+ :param func: The function to extract type hints from.
741
+ """
742
+ hints = get_type_hints(func)
743
+
744
+ sig = inspect.signature(func)
745
+ all_hints = list(hints.values())
746
+ if sig.return_annotation != inspect.Signature.empty:
747
+ all_hints.append(sig.return_annotation)
748
+
749
+ for param in sig.parameters.values():
750
+ if param.annotation != inspect.Parameter.empty:
751
+ all_hints.append(param.annotation)
752
+
753
+ return get_types_to_import_from_type_hints(all_hints)
754
+
755
+
756
+ def get_types_to_import_from_type_hints(hints: List[Type]) -> Set[Type]:
757
+ """
758
+ Extract importable types from a list of type hints.
759
+
760
+ :param hints: A list of type hints to extract types from.
761
+ :return: A set of types that need to be imported.
762
+ """
763
+ seen_types = set()
764
+ for hint in hints:
765
+ extract_types(hint, seen_types)
766
+
767
+ # Filter out built-in and internal types
768
+ to_import = set()
769
+ for tp in seen_types:
770
+ if isinstance(tp, ForwardRef) or isinstance(tp, str):
771
+ continue
772
+ if not is_builtin_type(tp):
773
+ to_import.add(tp)
774
+
775
+ return to_import
776
+
777
+
778
+ def get_import_path_from_path(path: str) -> Optional[str]:
779
+ """
780
+ Convert a file system path to a Python import path.
781
+
782
+ :param path: The file system path to convert.
783
+ :return: The Python import path.
784
+ """
785
+ package_name = os.path.abspath(path)
786
+ formated_package_name = package_name.strip('./').replace('/', '.')
787
+ parent_package_idx = 0
788
+ packages = formated_package_name.split('.')
789
+ for i, possible_pacakge in enumerate(reversed(packages)):
790
+ if i == 0:
791
+ current_path = package_name
792
+ else:
793
+ current_path = '/' + '/'.join(packages[:-i])
794
+ if os.path.exists(os.path.join(current_path, '__init__.py')):
795
+ parent_package_idx -= 1
796
+ else:
797
+ break
798
+ package_name = '.'.join(packages[parent_package_idx:]) if parent_package_idx < 0 else None
799
+ return package_name
800
+
801
+
802
+ def get_function_import_path_and_representation(func: Callable) -> Tuple[str, str]:
803
+ """
804
+ Get the import path of a function.
805
+
806
+ :param func: The function to get the import path for.
807
+ :return: The import path of the function.
808
+ """
809
+ func_name = get_method_name(func)
810
+ func_class_name = get_method_class_name_if_exists(func)
811
+ func_file_path = get_method_file_name(func)
812
+ func_file_name = func_file_path.split('/')[-1].split('.')[0] # Get the file name without extension
813
+ func_import_path = get_import_path_from_path(dirname(func_file_path))
814
+ func_import_path = f"{func_import_path}.{func_file_name}" if func_import_path else func_file_name
815
+ if func_class_name and func_class_name != func_name:
816
+ import_path = f"from {func_import_path} import {func_class_name}"
817
+ func_representation = f"{func_class_name}.{func_name}"
710
818
  else:
711
- val = val.split('.')[-1]
712
- return val, all_args
819
+ import_path = f"from {func_import_path} import {func_name}"
820
+ func_representation = func_name
821
+ return import_path, func_representation
822
+
823
+
824
+ def get_imports_from_types(type_objs: List[Type]) -> List[str]:
825
+ """
826
+ Format import lines from type objects.
827
+
828
+ :param type_objs: A list of type objects to format.
829
+ """
830
+
831
+ module_to_types = defaultdict(list)
832
+ other_imports = []
833
+ for tp in type_objs:
834
+ try:
835
+ if isinstance(tp, type) or is_typing_type(tp):
836
+ module = tp.__module__
837
+ name = tp.__qualname__
838
+ elif callable(tp):
839
+ import_, _ = get_function_import_path_and_representation(tp)
840
+ if import_ is not None:
841
+ other_imports.append(import_)
842
+ module = None
843
+ elif hasattr(type(tp), "__module__"):
844
+ module = type(tp).__module__
845
+ name = type(tp).__qualname__
846
+ else:
847
+ continue
848
+ if module is None or module == 'builtins' or module.startswith('_'):
849
+ continue
850
+ module_to_types[module].append(name)
851
+ except AttributeError:
852
+ continue
853
+
854
+ lines = []
855
+ for module, names in module_to_types.items():
856
+ joined = ", ".join(sorted(set(names)))
857
+ lines.append(f"from {module} import {joined}")
858
+ if other_imports:
859
+ lines.extend(other_imports)
860
+ return sorted(lines)
713
861
 
714
862
 
715
863
  def get_method_args_as_dict(method: Callable, *args, **kwargs) -> Dict[str, Any]:
@@ -746,7 +894,9 @@ def get_method_class_name_if_exists(method: Callable) -> Optional[str]:
746
894
  :return: The class name of the method.
747
895
  """
748
896
  if hasattr(method, "__self__"):
749
- if hasattr(method.__self__, "__class__"):
897
+ if hasattr(method.__self__, "__name__"):
898
+ return method.__self__.__name__
899
+ elif hasattr(method.__self__, "__class__"):
750
900
  return method.__self__.__class__.__name__
751
901
  return method.__qualname__.split('.')[0] if hasattr(method, "__qualname__") else None
752
902
 
@@ -865,6 +1015,8 @@ class SubclassJSONSerializer:
865
1015
  def to_json_static(obj, seen=None) -> Any:
866
1016
  if isinstance(obj, SubclassJSONSerializer):
867
1017
  return {"_type": get_full_class_name(obj.__class__), **obj._to_json()}
1018
+ elif isinstance(obj, type):
1019
+ return {"_type": get_full_class_name(obj)}
868
1020
  elif is_dataclass(obj):
869
1021
  return serialize_dataclass(obj, seen)
870
1022
  elif isinstance(obj, list):
@@ -1017,13 +1169,20 @@ def copy_orm_instance(instance: SQLTable) -> SQLTable:
1017
1169
  :param instance: The instance to copy.
1018
1170
  :return: The copied instance.
1019
1171
  """
1020
- session: Session = inspect(instance).session
1172
+ try:
1173
+ session: Session = inspect(instance).session
1174
+ except NoInspectionAvailable:
1175
+ session = None
1021
1176
  if session is not None:
1022
1177
  session.expunge(instance)
1023
1178
  new_instance = deepcopy(instance)
1024
1179
  session.add(instance)
1025
1180
  else:
1026
- new_instance = instance
1181
+ try:
1182
+ new_instance = deepcopy(instance)
1183
+ except Exception as e:
1184
+ logging.debug(e)
1185
+ new_instance = instance
1027
1186
  return new_instance
1028
1187
 
1029
1188
 
@@ -1037,8 +1196,12 @@ def copy_orm_instance_with_relationships(instance: SQLTable) -> SQLTable:
1037
1196
  instance_cp = copy_orm_instance(instance)
1038
1197
  for rel in class_mapper(instance.__class__).relationships:
1039
1198
  related_obj = getattr(instance, rel.key)
1199
+ related_obj_cp = copy_orm_instance(related_obj)
1040
1200
  if related_obj is not None:
1041
- setattr(instance_cp, rel.key, related_obj)
1201
+ try:
1202
+ setattr(instance_cp, rel.key, related_obj_cp)
1203
+ except Exception as e:
1204
+ logging.debug(e)
1042
1205
  return instance_cp
1043
1206
 
1044
1207
 
@@ -1049,7 +1212,17 @@ def get_value_type_from_type_hint(attr_name: str, obj: Any) -> Type:
1049
1212
  :param attr_name: The name of the attribute.
1050
1213
  :param obj: The object to get the attributes from.
1051
1214
  """
1052
- hint, origin, args = get_hint_for_attribute(attr_name, obj)
1215
+ # check first if obj is a function object
1216
+ if hasattr(obj, '__code__'):
1217
+ func_type_hints = get_type_hints(obj)
1218
+ if attr_name in func_type_hints:
1219
+ hint = func_type_hints[attr_name]
1220
+ origin = get_origin(hint)
1221
+ args = get_args(hint)
1222
+ else:
1223
+ raise ValueError(f"Unknown type hint: {attr_name}")
1224
+ else:
1225
+ hint, origin, args = get_hint_for_attribute(attr_name, obj)
1053
1226
  if not origin and not hint:
1054
1227
  if hasattr(obj, attr_name):
1055
1228
  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.63
3
+ Version: 0.5.71
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=PBN_Kv8LIfx9OpzrltJZONTiFxTieXefSOjX0uYtV0E,100
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=5VeA6YT16SL-sZK3VYiZ6vqazqZzCj_Uhk7xKYyqwVA,51354
5
+ ripple_down_rules/rdr_decorators.py,sha256=0sk7izDB53lTKSB9fm33vQahmY_05FyCOWljyQOMB0U,9072
6
+ ripple_down_rules/rules.py,sha256=rIFE5OIJpuXrquTPAXXOTuatWBD7CkWqRMnDmItLwUc,20176
7
+ ripple_down_rules/start-code-server.sh,sha256=otClk7VmDgBOX2TS_cjws6K0UwvgAUJhoA0ugkPCLqQ,949
8
+ ripple_down_rules/utils.py,sha256=yeSW2KvnxHTL2FMi-95-K6RmcMdzubJBc-jHeMPLL0g,56961
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=vlMmlsFJs3s71fwE8d79x_f8z6ZNcLGC5gjSAqabWxY,11021
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=VLS9Nxg6gPNa-YYliJ_VNsTvLPlZ003EVkJ2t8zuDgE,13563
20
+ ripple_down_rules-0.5.71.dist-info/licenses/LICENSE,sha256=ixuiBLtpoK3iv89l7ylKkg9rs2GzF9ukPH7ynZYzK5s,35148
21
+ ripple_down_rules-0.5.71.dist-info/METADATA,sha256=yfIhc979M1q7zytxPiuINjHj4DHe7g7QSVTnaCgx2sA,48214
22
+ ripple_down_rules-0.5.71.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
23
+ ripple_down_rules-0.5.71.dist-info/top_level.txt,sha256=VeoLhEhyK46M1OHwoPbCQLI1EifLjChqGzhQ6WEUqeM,18
24
+ ripple_down_rules-0.5.71.dist-info/RECORD,,
@@ -1,24 +0,0 @@
1
- ripple_down_rules/__init__.py,sha256=YhwbkV_b1omD9-l5j4yNuD_J1TW_-QiK-vnUMHGzxk4,100
2
- ripple_down_rules/experts.py,sha256=bwozulI1rv0uyaMZQqEgapDO-s8wvW0D6Jqxmvu5fik,12610
3
- ripple_down_rules/helpers.py,sha256=TvTJU0BA3dPcAyzvZFvAu7jZqsp8Lu0HAAwvuizlGjg,2018
4
- ripple_down_rules/rdr.py,sha256=4iobla4XmMwAOQsn_JZaZe2tWU0aMMvqgzP5WavIagI,49280
5
- ripple_down_rules/rdr_decorators.py,sha256=bmn4h4a7xujTVxu-ofECe71cM_6iiqZhLVFosEItid4,7602
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=N5Rgz7wb9oKrVLZiJG2P-irnsjhy7VR3Vqyggf4Mq7I,51564
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=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=jRRyQxgU_RK2e_wgi2gPag_FB8UCYOAXicRTk8_JWgo,27232
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=xw0NIFILMA4rvEqvOznefRqLOpS-UoWa3c9b-HfhVWQ,14129
20
- ripple_down_rules-0.5.63.dist-info/licenses/LICENSE,sha256=ixuiBLtpoK3iv89l7ylKkg9rs2GzF9ukPH7ynZYzK5s,35148
21
- ripple_down_rules-0.5.63.dist-info/METADATA,sha256=iS7z9EkWKtPgrDISnU-4OMcqvp0EsZeoskZK1Da5UuI,48189
22
- ripple_down_rules-0.5.63.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
23
- ripple_down_rules-0.5.63.dist-info/top_level.txt,sha256=VeoLhEhyK46M1OHwoPbCQLI1EifLjChqGzhQ6WEUqeM,18
24
- ripple_down_rules-0.5.63.dist-info/RECORD,,