Coverage for kryptic_cypher/cypher/__init__.py: 82%

34 statements  

« prev     ^ index     » next       coverage.py v7.14.2, created at 2026-06-20 18:52 +0000

1""" 

2Module that contains the code for cyphers in our system. 

3 

4Defines both the interfaces as well as a registery decorator to register classes as cyphers. 

5 

6```python 

7from kryptic_cypher.cypher import register_cypher, Cypher, CypherWithKey 

8 

9@register_cypher 

10class MyCypher(Cypher): 

11 def encode(self, text: str) -> str: 

12 # Do my encryption here 

13 return text 

14 

15 def decode(self, text: str) -> str: 

16 # Do my decryption here 

17 return text 

18 

19 

20@register_cypher 

21class MyCypherWithKey(CypherWithKey): 

22 def encode(self, text: str, key: str) -> str: 

23 # Do my encryption here 

24 return text 

25 

26 def decode(self, text: str, key: str) -> str: 

27 # Do my decryption here 

28 return text 

29``` 

30 

31You can use this if you want to import and leverage any existing cyphers that have been registered with us. 

32 

33If you would like to explore the existing cyphers you can find them as submodules for the `kryptic_cypher.cypher` module. 

34""" 

35 

36from .base import * 

37 

38 

39import importlib 

40import inspect 

41import pkgutil 

42from typing import Type 

43 

44logger = getLogger(__name__) 

45 

46 

47def find_subclasses_recursively(package_name: str, base_class: Type) -> list[Type]: 

48 """Recursively walks a package to find all classes implementing base_class.""" 

49 found_classes = [] 

50 

51 try: 

52 # 1. Load the root package 

53 root_package = importlib.import_module(package_name) 

54 except ImportError: 

55 print(f"Error: Root package '{package_name}' could not be imported.") 

56 return found_classes 

57 

58 # 2. Check the root package file itself for classes 

59 _extract_classes_from_module(root_package, base_class, found_classes) 

60 

61 # 3. Guard against single-file modules that have no sub-packages 

62 if not hasattr(root_package, "__path__"): 

63 return found_classes 

64 

65 # 4. Recursively walk through all submodules and sub-packages 

66 for module_info in pkgutil.walk_packages( 

67 root_package.__path__, root_package.__name__ + "." 

68 ): 

69 try: 

70 # Dynamically import each discovered submodule 

71 submodule = importlib.import_module(module_info.name) 

72 _extract_classes_from_module(submodule, base_class, found_classes) 

73 except ImportError: 

74 # Skip submodules that fail to import due to missing dependencies 

75 continue 

76 

77 return found_classes 

78 

79 

80def _extract_classes_from_module(module, base_class: Type, target_list: list[Type]): 

81 """Helper to find matching classes inside a specific module instance.""" 

82 for name, obj in inspect.getmembers(module, inspect.isclass): 

83 # Ensure it is a subclass, isn't the base itself, and was defined in this module 

84 if issubclass(obj, base_class) and obj is not base_class: 

85 if obj.__module__ == module.__name__: 

86 if obj not in target_list: 

87 target_list.append(obj) 

88 

89 

90def register_all_cyphers(): 

91 logger.info("Registering all cyphers...") 

92 all_to_register = find_subclasses_recursively( 

93 "kryptic_cypher.cypher", Cypher 

94 ) + find_subclasses_recursively("kryptic_cypher.cypher", CypherWithKey) 

95 

96 for cypher in all_to_register: 

97 register_cypher(cypher)