diff --git a/configurations/errors.py b/configurations/errors.py index cfca60e..1f4a984 100644 --- a/configurations/errors.py +++ b/configurations/errors.py @@ -26,6 +26,9 @@ def extract_explanation_lines_from_value(value_instance: 'Value') -> List[str]: result.append(f"{value_instance.destination_name} is taken from the environment variable " f"{value_instance.full_environ_name} as a {type(value_instance).__name__}") + if value_instance.example_generator is not None: + result.append(f"Example value: '{value_instance.example_generator()}'") + return result diff --git a/configurations/example_generators.py b/configurations/example_generators.py new file mode 100644 index 0000000..e52983c --- /dev/null +++ b/configurations/example_generators.py @@ -0,0 +1,47 @@ +from typing import Callable +import secrets +import base64 +from django.core.management.utils import get_random_secret_key +from django.utils.crypto import get_random_string, RANDOM_STRING_CHARS + + +def gen_django_secret_key() -> str: + """ + Generate a cryptographically secure random string that can safely be used as a SECRET_KEY in django + """ + return get_random_secret_key() + + +def gen_random_string(length: int, allowed_chars: str = RANDOM_STRING_CHARS) -> Callable[[], str]: + """ + Create a parameterized generator which generates a cryptographically secure random string of the given length + containing the given characters. + """ + def generate() -> str: + return get_random_string(length, allowed_chars) + return generate + + +def gen_bytes(length: int, encoding: str) -> Callable[[], str]: + """ + Create a parameterized generator which generates a cryptographically secure random assortments of bytes of the given + length and encoded in the given format + + :param length: How many bytes should be generated. Not how long the encoded string will be. + :param encoding: How the generated bytes should be encoded. + Accepted values are "base64", "base64_urlsafe" and "hex" (case is ignored) + """ + encoding = encoding.lower() + if encoding not in ("base64", "base64_urlsafe", "hex"): + raise ValueError(f"Cannot gen_bytes with encoding '{encoding}'. Valid encodings are 'base64', 'base64_urlsafe'" + f" and 'hex'") + + def generate() -> str: + b = secrets.token_bytes(length) + if encoding == "base64": + return base64.standard_b64encode(b).decode("ASCII") + elif encoding == "base64_urlsafe": + return base64.urlsafe_b64encode(b).decode("ASCII") + elif encoding == "hex": + return b.hex().upper() + return generate diff --git a/configurations/values.py b/configurations/values.py index 73d2046..98d3c26 100644 --- a/configurations/values.py +++ b/configurations/values.py @@ -58,8 +58,8 @@ class Value: return instance def __init__(self, default=None, environ=True, environ_name=None, - environ_prefix='DJANGO', environ_required=False, help_text=None, - help_reference=None, *args, **kwargs): + environ_prefix='DJANGO', environ_required=False, example_generator=None, + help_text=None, help_reference=None, *args, **kwargs): if isinstance(default, Value) and default.default is not None: self.default = copy.copy(default.default) else: @@ -73,6 +73,7 @@ class Value: self.destination_name = None self.help_text = help_text self.help_reference = help_reference + self.example_generator = example_generator def __str__(self): return str(self.value)