Skip to content

Commit

Permalink
✨ [#1] first draft of setup_notification command
Browse files Browse the repository at this point in the history
  • Loading branch information
annashamray committed Feb 9, 2024
1 parent 662a8b4 commit de81826
Show file tree
Hide file tree
Showing 7 changed files with 191 additions and 3 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ jobs:
strategy:
matrix:
python: ['3.10', '3.11', '3.12']
django: ['4.2']
django: ['3.2']

name: Run the test suite (Python $, Django $)

Expand Down
70 changes: 70 additions & 0 deletions django_setup_configuration/configuration.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import logging
from abc import ABC, abstractmethod

from django.conf import settings

from .exceptions import PrerequisiteFailed

logger = logging.getLogger(__name__)


class BaseConfigurationStep(ABC):
verbose_name: str
required_settings: list[str] = []
enable_setting: str = ""

def __str__(self):
return self.verbose_name

def validate_requirements(self) -> None:
"""
check prerequisites of the configuration
:raises: :class: `django_setup_configuration.exceptions.PrerequisiteFailed`
if prerequisites are missing
"""
missing = [
var for var in self.required_settings if not getattr(settings, var, None)
]
if missing:
raise PrerequisiteFailed(
f"{', '.join(missing)} settings should be provided"
)

def is_enabled(self) -> bool:
"""
Hook to switch on and off the configuration step from env vars
By default all steps are enabled
"""
if not self.enable_setting:
return True

return getattr(settings, self.enable_setting, True)

@abstractmethod
def is_configured(self) -> bool:
"""
Check that the configuration is already done with current env variables
"""
...

@abstractmethod
def configure(self) -> None:
"""
Run the configuration step.
:raises: :class: `django_setup_configuration.exceptions.ConfigurationRunFailed`
if the configuration has an error
"""
...

@abstractmethod
def test_configuration(self) -> None:
"""
Test that the configuration works as expected
:raises: :class:`openzaak.config.bootstrap.exceptions.SelfTestFailure`
if the configuration aspect was found to be faulty.
"""
...
22 changes: 22 additions & 0 deletions django_setup_configuration/exceptions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
class ConfigurationException(Exception):
"""
Base exception for configuration steps
"""


class PrerequisiteFailed(ConfigurationException):
"""
Raise an error then configuration step can't be started
"""


class ConfigurationRunFailed(ConfigurationException):
"""
Raise an error then configuration process was faulty
"""


class SelfTestFailed(ConfigurationException):
"""
Raise an error for failed configuration self-tests.
"""
Empty file.
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
from collections import OrderedDict

from django.conf import settings
from django.core.management import BaseCommand, CommandError
from django.utils.module_loading import import_string

from ...configuration import BaseConfigurationStep
from ...exceptions import ConfigurationRunFailed, PrerequisiteFailed, SelfTestFailed


class ErrorDict(OrderedDict):
"""
small helper to display errors
"""

def as_text(self) -> str:
output = [f"{k}: {v}" for k, v in self.items()]
return "\n".join(output)


class Command(BaseCommand):
help = (
"Bootstrap the initial Open Zaak configuration. "
"This command is run only in non-interactive mode with settings "
"configured mainly via environment variables."
)
output_transaction = True

def add_arguments(self, parser):
parser.add_argument(
"--overwrite",
action="store_true",
help=(
"Overwrite the existing configuration. Should be used if some "
"of the env variables have been changed."
),
)

def handle(self, **options):
overwrite: bool = options["overwrite"]

# todo transaction atomic
errors = ErrorDict()
steps: list[BaseConfigurationStep] = [
import_string(path) for path in settings.SETUP_CONFIGURATION_STEPS
]
enabled_steps = [step for step in steps if step.is_enabled()]

self.stdout.write(
f"Configuration would be set up with following steps: {enabled_steps}"
)

# 1. Check prerequisites of all steps
for step in enabled_steps:
try:
step.validate_requirements()
except PrerequisiteFailed as exc:
errors[step] = exc

if errors:
raise CommandError(
f"Prerequisites for configuration are not fulfilled: {errors.as_text()}"
)

# 2. Configure steps
configured_steps = []
for step in enabled_steps:
if not overwrite and step.is_configured():
self.stdout.write(
f"Step {step} is skipped, because the configuration already exists."
)
continue
else:
self.stdout.write(f"Configuring {step}...")
try:
step.configure()
except ConfigurationRunFailed as exc:
raise CommandError(f"Could not configure step {step}") from exc
else:
self.stdout.write(f"{step} is successfully configured")
configured_steps.append(step)

# 3. Test configuration
for step in configured_steps:
# todo global env to turn off self tests?
try:
step.test_configuration()
except SelfTestFailed as exc:
errors[step] = exc

if errors:
raise CommandError(
f"Configuration test failed with errors: {errors.as_text()}"
)

self.stdout.write(self.style.SUCCESS("Instance configuration completed."))
4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ keywords = ["TODO"]
classifiers = [
"Development Status :: 3 - Alpha",
"Framework :: Django",
"Framework :: Django :: 4.2",
"Framework :: Django :: 3.2",
"Intended Audience :: Developers",
"Operating System :: Unix",
"Operating System :: MacOS",
Expand All @@ -27,7 +27,7 @@ classifiers = [
]
requires-python = ">=3.10"
dependencies = [
"django>=4.2"
"django>=3.2"
]

[project.urls]
Expand Down

0 comments on commit de81826

Please sign in to comment.