/ciphey/iface/_registry.py

https://github.com/Ciphey/Ciphey · Python · 172 lines · 135 code · 28 blank · 9 comment · 39 complexity · 1eb530fb0b118e32d956a742193c6e9e MD5 · raw file

  1. from abc import ABC, abstractmethod
  2. from collections import defaultdict
  3. from typing import (
  4. Any,
  5. Callable,
  6. Dict,
  7. Generic,
  8. Optional,
  9. List,
  10. NamedTuple,
  11. TypeVar,
  12. Type,
  13. Union,
  14. Set,
  15. Tuple,
  16. )
  17. import pydoc
  18. try:
  19. from typing import get_origin, get_args
  20. except ImportError:
  21. from typing_inspect import get_origin, get_args
  22. from loguru import logger
  23. from . import _fwd
  24. from ._modules import *
  25. import datetime
  26. class Registry:
  27. # I was planning on using __init_subclass__, but that is incompatible with dynamic type creation when we have
  28. # generic keys
  29. RegElem = Union[List[Type], Dict[Type, "RegElem"]]
  30. _reg: Dict[Type, RegElem] = {}
  31. _names: Dict[str, Tuple[Type, Set[Type]]] = {}
  32. _targets: Dict[str, Dict[Type, List[Type]]] = {}
  33. _modules = {Checker, Cracker, Decoder, ResourceLoader, Searcher, PolymorphicChecker}
  34. def _register_one(self, input_type, module_base, module_args):
  35. if len(module_args) == 0:
  36. self._reg.setdefault(module_base, []).append(input_type)
  37. return
  38. target_reg = self._reg.setdefault(module_base, {})
  39. # Seek to the given type
  40. for subtype in module_args[0:-1]:
  41. target_reg = target_reg.setdefault(subtype, {})
  42. target_reg.setdefault(module_args[-1], []).append(input_type)
  43. def _real_register(self, input_type: type, *args) -> Type:
  44. name = input_type.__name__.lower()
  45. name_target = self._names[name] = (input_type, set())
  46. if issubclass(input_type, Targeted):
  47. target = input_type.getTarget()
  48. else:
  49. target = None
  50. if issubclass(input_type, Searcher):
  51. module_type = module_base = Searcher
  52. module_args = ()
  53. else:
  54. module_type: Optional[Type] = None
  55. module_base = None
  56. # Work out what module type this is
  57. if len(args) == 0 and hasattr(input_type, "__orig_bases__"):
  58. for i in input_type.__orig_bases__:
  59. if module_type is not None:
  60. raise TypeError(
  61. f"Type derived from multiple registrable base classes {i} and {module_type}"
  62. )
  63. module_base = get_origin(i)
  64. if module_base not in self._modules:
  65. continue
  66. module_type = i
  67. else:
  68. for i in self._modules:
  69. if not issubclass(input_type, i):
  70. continue
  71. if module_type is not None:
  72. raise TypeError(
  73. f"Type derived from multiple registrable base classes {i} and {module_type}"
  74. )
  75. module_type = i
  76. if module_type is None:
  77. raise TypeError("No registrable base class")
  78. # Replace input type with polymorphic checker if required
  79. if issubclass(input_type, Checker):
  80. if len(args) == 0:
  81. arg = [get_args(i) for i in input_type.__orig_bases__ if get_origin(i) == Checker][0]
  82. if len(arg) != 1:
  83. raise TypeError(f"No argument for Checker")
  84. input_type = input_type.convert({arg[0]})
  85. else:
  86. input_type = input_type.convert(set(args))
  87. self._register_one(input_type, PolymorphicChecker, [])
  88. # Refresh the names with the new type
  89. name_target = self._names[name] = (input_type, {PolymorphicChecker})
  90. # Now handle the difference between register and register_multi
  91. if len(args) == 0:
  92. if module_type is PolymorphicChecker:
  93. module_base = PolymorphicChecker
  94. elif module_base is None:
  95. raise TypeError("No type argument given")
  96. self._register_one(input_type, module_base, get_args(module_type))
  97. name_target[1].add(module_base)
  98. else:
  99. if module_base is not None:
  100. raise TypeError(f"Redundant type argument for {module_type}")
  101. module_base = module_type
  102. for module_args in args:
  103. # Correct missing brackets
  104. if not isinstance(module_args, tuple):
  105. module_args = (module_args,)
  106. self._register_one(input_type, module_base, module_args)
  107. name_target[1].add(module_type[module_args])
  108. name_target[1].add(module_type)
  109. if target is not None and issubclass(module_base, Targeted):
  110. self._targets.setdefault(target, {}).setdefault(module_type, []).append(
  111. input_type
  112. )
  113. return input_type
  114. def register(self, input_type):
  115. return self._real_register(input_type)
  116. def register_multi(self, *x):
  117. return lambda input_type: self._real_register(input_type, *x)
  118. def __getitem__(self, i: type) -> Optional[Any]:
  119. target_type = get_origin(i)
  120. # Check if this is a non-generic type, and return the whole dict if it is
  121. if target_type is None:
  122. return self._reg[i]
  123. target_subtypes = get_args(i)
  124. target_list = self._reg.setdefault(target_type, {})
  125. for subtype in target_subtypes:
  126. target_list = target_list.setdefault(subtype, {})
  127. return target_list
  128. def get_named(self, name: str, type_constraint: Type = None) -> Any:
  129. ret = self._names[name.lower()]
  130. if type_constraint and type_constraint not in ret[1]:
  131. raise TypeError(f"Type mismatch: wanted {type_constraint}, got {ret[1]}")
  132. return ret[0]
  133. def get_targeted(
  134. self, target: str, type_constraint: Type = None
  135. ) -> Optional[Union[Dict[Type, Set[Type]], Set[Type]]]:
  136. x = self._targets.get(target)
  137. if x is None or type_constraint is None:
  138. return x
  139. return x.get(type_constraint)
  140. def get_all_names(self) -> List[str]:
  141. return list(self._names.keys())
  142. def __str__(self):
  143. return f"ciphey.iface.Registry {{_reg: {self._reg}, _names: {self._names}, _targets: {self._targets}}}"
  144. _fwd.registry = Registry()