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
« 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.
4Defines both the interfaces as well as a registery decorator to register classes as cyphers.
6```python
7from kryptic_cypher.cypher import register_cypher, Cypher, CypherWithKey
9@register_cypher
10class MyCypher(Cypher):
11 def encode(self, text: str) -> str:
12 # Do my encryption here
13 return text
15 def decode(self, text: str) -> str:
16 # Do my decryption here
17 return text
20@register_cypher
21class MyCypherWithKey(CypherWithKey):
22 def encode(self, text: str, key: str) -> str:
23 # Do my encryption here
24 return text
26 def decode(self, text: str, key: str) -> str:
27 # Do my decryption here
28 return text
29```
31You can use this if you want to import and leverage any existing cyphers that have been registered with us.
33If you would like to explore the existing cyphers you can find them as submodules for the `kryptic_cypher.cypher` module.
34"""
36from .base import *
39import importlib
40import inspect
41import pkgutil
42from typing import Type
44logger = getLogger(__name__)
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 = []
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
58 # 2. Check the root package file itself for classes
59 _extract_classes_from_module(root_package, base_class, found_classes)
61 # 3. Guard against single-file modules that have no sub-packages
62 if not hasattr(root_package, "__path__"):
63 return found_classes
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
77 return found_classes
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)
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)
96 for cypher in all_to_register:
97 register_cypher(cypher)