Skip to content

Commit

Permalink
✨ Add management command to generate docs for envvars
Browse files Browse the repository at this point in the history
  • Loading branch information
stevenbal committed May 14, 2024
1 parent 985ff2a commit b88866f
Show file tree
Hide file tree
Showing 8 changed files with 120 additions and 3 deletions.
1 change: 1 addition & 0 deletions open_api_framework/conf/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@
"mozilla_django_oidc_db",
"log_outgoing_requests",
"django_setup_configuration",
"open_api_framework",
]

MIDDLEWARE = [
Expand Down
42 changes: 39 additions & 3 deletions open_api_framework/conf/utils.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,42 @@
import sys
from dataclasses import dataclass
from pathlib import Path
from typing import Any
from typing import Any, Optional
from urllib.parse import urlparse

from decouple import Csv, config as _config, undefined
from decouple import Csv, Undefined, config as _config, undefined
from sentry_sdk.integrations import DidNotEnable, django, redis


def config(option: str, default: Any = undefined, *args, **kwargs):
@dataclass
class EnvironmentVariable:
name: str
default: Any
help_text: str
group: Optional[str] = None

def __post_init__(self):
if not self.group:
self.group = (
"Required" if isinstance(self.default, Undefined) else "Optional"
)

def __eq__(self, other):
return isinstance(other, EnvironmentVariable) and self.name == other.name


ENVVAR_REGISTRY = []


def config(
option: str,
default: Any = undefined,
help_text="",
group=None,
add_to_docs=True,
*args,
**kwargs,
):
"""
Pull a config parameter from the environment.
Expand All @@ -17,6 +46,13 @@ def config(option: str, default: Any = undefined, *args, **kwargs):
Pass ``split=True`` to split the comma-separated input into a list.
"""
if add_to_docs:
variable = EnvironmentVariable(
name=option, default=default, help_text=help_text, group=group
)
if variable not in ENVVAR_REGISTRY:
ENVVAR_REGISTRY.append(variable)

if "split" in kwargs:
kwargs.pop("split")
kwargs["cast"] = Csv()
Expand Down
Empty file.
Empty file.
27 changes: 27 additions & 0 deletions open_api_framework/management/commands/generate_envvar_docs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import warnings
from collections import defaultdict

from django.core.management.base import BaseCommand
from django.template import loader

from open_api_framework.conf.utils import EnvironmentVariable


def convert_group_to_rst(variables: set[EnvironmentVariable]) -> str:
template = loader.get_template("open_api_framework/env_config.rst")
grouped_vars = defaultdict(list)
for var in variables:
if not var.help_text:
warnings.warn(f"missing help_text for environment variable {var}")
grouped_vars[var.group].append(var)
return template.render({"vars": grouped_vars.items()})


class Command(BaseCommand):
help = "Generate documentation for all used envvars"

def handle(self, *args, **options):
from open_api_framework.conf.utils import ENVVAR_REGISTRY

with open("docs/rendered.rst", "w") as f:
f.write(convert_group_to_rst(ENVVAR_REGISTRY))
18 changes: 18 additions & 0 deletions open_api_framework/templates/open_api_framework/env_config.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{% load doc_tags %}.. _installation_env_config:

===================================
Environment configuration reference
===================================

{% block intro %}{% endblock %}

Available environment variables
===============================

{% for group_name, vars in vars %}
{{group_name}}
{{group_name|repeat_char:"="}}

{% for var in vars %}* ``{{var.name}}``: {% if var.help_text %}{{var.help_text|safe|ensure_endswith:"."}}{% endif %}{% if not var.default|is_undefined %} Defaults to: ``{{var.default|to_str}}``{% endif %}
{% endfor %}
{% endfor %}
Empty file.
35 changes: 35 additions & 0 deletions open_api_framework/templatetags/doc_tags.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
from django import template

from decouple import Undefined

register = template.Library()


@register.filter(name="repeat_char")
def repeat_char(value, char="-"):
try:
length = len(value)
return char * length
except TypeError:
return ""


@register.filter(name="is_undefined")
def is_undefined(value):
return isinstance(value, Undefined)


@register.filter(name="to_str")
def to_str(value):
if value == "":
return "(empty string)"
return str(value)


@register.filter(name="ensure_endswith")
def ensure_endswith(value, char):
if not isinstance(value, str):
value = str(value)
if not value.endswith(char):
value += char
return value

0 comments on commit b88866f

Please sign in to comment.