Coverage for kryptic_cypher/cypher.py: 88%
26 statements
« prev ^ index » next coverage.py v7.6.7, created at 2024-11-23 22:56 +0000
« prev ^ index » next coverage.py v7.6.7, created at 2024-11-23 22:56 +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.
32"""
34from abc import ABC, abstractmethod
35from logging import getLogger
36from typing import IO
39logger = getLogger(__name__)
42class ValidationResult:
43 def __init__(self, success: bool, messages: list[str]) -> None:
44 self.success = success
45 self.messages = messages
47 @classmethod
48 def ok(cls):
49 return cls(True, [])
51 @classmethod
52 def fail(cls, messages: list[str]):
53 return cls(False, messages)
56class Cypher(ABC):
57 """A Cypher that does not require a key to encode/decode."""
59 @abstractmethod
60 def encode(self, text: str) -> IO: # pragma: no cover
61 """Encode the given text using the given key.
63 **Parameters**
64 - text (str): The text to encode
66 **Returns**
67 - IO: The encoded text
68 """
69 pass
71 @abstractmethod
72 def decode(self, text: str) -> IO: # pragma: no cover
73 """Decode the given text using the given key.
75 **Parameters**
76 - text (str): The text to decode
78 **Returns**
79 - IO: The decoded text
80 """
81 pass
84class CypherWithKey(ABC):
85 """A Cypher that requires a key to encode/decode."""
87 @classmethod
88 @abstractmethod
89 def validate_key(cls, key: str) -> ValidationResult: # pragma: no cover
90 """
91 Validate the key for the Cypher. If the key is invalid, it should return a ValidationResult
92 with a message explaining why the key is invalid.
94 **Parameters**
95 - key (str): The key to validate
97 **Returns**
98 - ValidationResult: ValidationResult indicating if the key is valid or not
99 """
100 pass
102 @abstractmethod
103 def encode(self, text: str, key: str) -> IO: # pragma: no cover
104 """Encode the given text using the given key.
106 **Parameters**
107 - text (str): The text to encode
108 - key (str): The key to use
110 **Returns**
111 - IO: The encoded text
112 """
113 pass
115 @abstractmethod
116 def decode(self, text: str, key: str) -> IO: # pragma: no cover
117 """Decode the given text using the given key.
119 **Parameters**
120 - text (str): The text to decode
121 - key (str): The key to use
123 **Returns**
124 - IO: The decoded text
125 """
126 pass
129registered_cyphers: dict[str, Cypher | CypherWithKey] = {}
132def register_cypher(cypher: type) -> None:
133 """Class Decorator to register type as cypher.
134 Must Implment Cypher or CypherWithKey and must have a constructor that takes no args.
136 **Parameters**
137 - cypher (type): Type that implements Cypher or CypherWithKey
139 **Raises**
140 - ValueError: If cypher does not implement Cypher or CypherWithKey
141 """
142 if not issubclass(cypher, Cypher) and not issubclass(cypher, CypherWithKey):
143 raise ValueError(f"Invalid cypher Class: {cypher}")
144 name = cypher.__name__
145 if name in registered_cyphers:
146 logger.warning(f"Duplicate cypher: {name}")
147 return
148 registered_cyphers[name] = cypher()
149 logger.debug(f"Registered cypher: {name}")