implement example generators

Example generators are an addition to the 'Value' classes that allow the error handler to display
example values for the failed value.
For example if no django secret key is found to be defined in the environment, an example (secure)
value for such a secret key can now be generated on the fly and be suggested to the user
This commit is contained in:
Finn-Thorben Sell 2022-03-24 14:25:28 +01:00
parent fd1b571594
commit 5cd3a86106
No known key found for this signature in database
GPG key ID: A78A03C25A3A3825
3 changed files with 53 additions and 2 deletions

View file

@ -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

View file

@ -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

View file

@ -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)