-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
setup_notification command #2
Merged
Merged
Changes from all commits
Commits
Show all changes
14 commits
Select commit
Hold shift + click to select a range
de81826
:sparkles: [#1] first draft of setup_notification command
annashamray a68eabb
:green_heart: [#1] fix gh actions
annashamray fdba99b
:green_heart: [#1] fix doc build
annashamray 3ac524b
:bug: [#1] fix management command
annashamray 261a73e
:green_heart: [#1] comment out non-existent links in the docs
annashamray 2cc9bae
:sparkles: [#1] small improvement of the package code and description
annashamray 6c047d7
:white_check_mark: [#1] a create testapp and add tests
annashamray bda38bb
:memo: [#1] update docs
annashamray a47235e
:sparkles: [#1] add option to disable selftests in the command
annashamray 16d3330
Apply suggestions from code review
annashamray 67dabde
:ok_hand: [#1] proccess PR feedback
annashamray 674ea6a
:ok_hand: [#1] explicitly list invalid values for required settings
annashamray 723a014
:wrench: [#1] support Django 4.2
annashamray c17c48a
:wrench: [#1] fix gh action name
annashamray File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -30,4 +30,4 @@ jobs: | |
run: pip install tox | ||
- run: tox | ||
env: | ||
TOXENV: $ | ||
TOXENV: ${{ matrix.toxenv }} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
from abc import ABC, abstractmethod | ||
|
||
from django.conf import settings | ||
|
||
from .exceptions import PrerequisiteFailed | ||
|
||
|
||
class BaseConfigurationStep(ABC): | ||
verbose_name: str | ||
required_settings: list[str] = [] | ||
enable_setting: str = "" | ||
|
||
def __repr__(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 getattr(settings, var, None) in [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. | ||
""" | ||
... |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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): | ||
""" | ||
Raises an error when the configuration step can't be started | ||
""" | ||
|
||
|
||
class ConfigurationRunFailed(ConfigurationException): | ||
""" | ||
Raises an error when the configuration process was faulty | ||
""" | ||
|
||
|
||
class SelfTestFailed(ConfigurationException): | ||
""" | ||
Raises an error for failed configuration self-tests. | ||
""" |
Empty file.
Empty file.
115 changes: 115 additions & 0 deletions
115
django_setup_configuration/management/commands/setup_configuration.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
from django.conf import settings | ||
from django.core.management import BaseCommand, CommandError | ||
from django.db import transaction | ||
from django.utils.module_loading import import_string | ||
|
||
from ...configuration import BaseConfigurationStep | ||
from ...exceptions import ConfigurationRunFailed, PrerequisiteFailed, SelfTestFailed | ||
|
||
|
||
class ErrorDict(dict): | ||
""" | ||
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 configuration of the application. " | ||
"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." | ||
), | ||
) | ||
parser.add_argument( | ||
"--no-selftest", | ||
action="store_true", | ||
dest="skip_selftest", | ||
help=( | ||
"Skip checking if configuration is successful. Use it if you " | ||
"run this command in the init container before the web app is started" | ||
), | ||
) | ||
|
||
@transaction.atomic | ||
def handle(self, **options): | ||
overwrite: bool = options["overwrite"] | ||
skip_selftest: bool = options["skip_selftest"] | ||
|
||
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()] | ||
|
||
if not enabled_steps: | ||
self.stdout.write( | ||
"There are no enabled configuration steps. " | ||
"Configuration can't be set up" | ||
) | ||
return | ||
|
||
self.stdout.write( | ||
f"Configuration will 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 | ||
if skip_selftest: | ||
self.stdout.write("Selftest is skipped.") | ||
|
||
else: | ||
for step in configured_steps: | ||
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.")) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,16 +5,17 @@ build-backend = "setuptools.build_meta" | |
[project] | ||
name = "django_setup_configuration" | ||
version = "0.1.0" | ||
description = "TODO" | ||
description = "Pluggable configuration setup used with the django management command" | ||
authors = [ | ||
{name = "Maykin Media", email = "[email protected]"} | ||
] | ||
readme = "README.rst" | ||
license = {file = "LICENSE"} | ||
keywords = ["TODO"] | ||
keywords = ["Django", "Configuration"] | ||
classifiers = [ | ||
"Development Status :: 3 - Alpha", | ||
"Framework :: Django", | ||
"Framework :: Django :: 3.2", | ||
"Framework :: Django :: 4.2", | ||
"Intended Audience :: Developers", | ||
"Operating System :: Unix", | ||
|
@@ -27,19 +28,21 @@ classifiers = [ | |
] | ||
requires-python = ">=3.10" | ||
dependencies = [ | ||
"django>=4.2" | ||
"django>=3.2" | ||
] | ||
|
||
[project.urls] | ||
Homepage = "https://github.com/maykinmedia/django_setup_configuration" | ||
Documentation = "http://django_setup_configuration.readthedocs.io/en/latest/" | ||
"Bug Tracker" = "https://github.com/maykinmedia/django_setup_configuration/issues" | ||
"Source Code" = "https://github.com/maykinmedia/django_setup_configuration" | ||
Homepage = "https://github.com/maykinmedia/django-setup-configuration" | ||
Documentation = "http://django-setup-configuration.readthedocs.io/en/latest/" | ||
"Bug Tracker" = "https://github.com/maykinmedia/django-setup-configuration/issues" | ||
"Source Code" = "https://github.com/maykinmedia/django-setup-configuration" | ||
|
||
[project.optional-dependencies] | ||
tests = [ | ||
"pytest", | ||
"pytest-django", | ||
"pytest-mock", | ||
"furl", | ||
"tox", | ||
"isort", | ||
"black", | ||
|
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we really need to do this via a Django setting step? Why can't we directly point to the name of an envvar and skip having to define a Django setting that reads the envvar?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I use django settings to set defaults when it's possible. For example we can have a default for
SELECTIELIJST_API_ROOT