Skip to content

Commit

Permalink
Add support for external configuration json
Browse files Browse the repository at this point in the history
  • Loading branch information
yakky committed Dec 27, 2020
1 parent 7a0fe6e commit b213802
Show file tree
Hide file tree
Showing 9 changed files with 152 additions and 18 deletions.
20 changes: 19 additions & 1 deletion app_enabler/cli.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import os
import sys
from pathlib import Path
from subprocess import CalledProcessError
from typing import List

import click

from .enable import enable as enable_fun
from .enable import apply_configuration_set, enable_application as enable_fun
from .errors import messages
from .install import get_application_from_package, install as install_fun

Expand Down Expand Up @@ -38,6 +40,22 @@ def enable(context: click.core.Context, application: str):
enable_fun(application, verbose=context.obj["verbose"])


@cli.command()
@click.argument("config_set", nargs=-1)
@click.pass_context
def apply(context: click.core.Context, config_set: List[str]):
"""
Apply configuration stored in one or more json files.
CONFIG_SET: Path to configuration files
\f
:param click.core.Context context: Click context
:param list config_set: list of paths to addon configuration to load and apply
"""
apply_configuration_set([Path(config) for config in config_set], verbose=context.obj["verbose"])


@cli.command()
@click.argument("package")
@click.option("--pip-options", default="", help="Additional options passed as is to pip")
Expand Down
52 changes: 42 additions & 10 deletions app_enabler/enable.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import json
import sys
from importlib import import_module
from pathlib import Path
from types import ModuleType
from typing import Any, Dict
from typing import Any, Dict, List

import django.conf

Expand All @@ -17,7 +19,7 @@ def _verify_settings(imported: ModuleType, application_config: Dict[str, Any]) -
:param dict application_config: addon configuration
"""
test_passed = True
for app in application_config["installed-apps"]:
for app in application_config.get("installed-apps", []):
test_passed = test_passed and app in imported.INSTALLED_APPS
for key, value in application_config.get("settings", {}).items():
if isinstance(value, list):
Expand Down Expand Up @@ -85,21 +87,51 @@ def output_message(message: str):
sys.stdout.write(message)


def enable(application: str, verbose: bool = False):
def apply_configuration(application_config: Dict[str, Any]):
"""
Enable django application in the current project
:param dict application_config: addon configuration
"""

setting_file = get_settings_path(django.conf.settings)
urlconf_file = get_urlconf_path(django.conf.settings)
update_setting(setting_file, application_config)
update_urlconf(urlconf_file, application_config)
if verify_installation(django.conf.settings, application_config):
output_message(application_config.get("message", ""))


def enable_application(application: str, verbose: bool = False):
"""
Enable django application in the current project
:param str application: python module name to enable. It must be the name of a Django application.
:param bool verbose: Verbose output (currently unused)
"""

setup_django()

setting_file = get_settings_path(django.conf.settings)
urlconf_file = get_urlconf_path(django.conf.settings)
application_config = load_addon(application)
if application_config:
update_setting(setting_file, application_config)
update_urlconf(urlconf_file, application_config)
if verify_installation(django.conf.settings, application_config):
output_message(application_config.get("message", ""))
apply_configuration(application_config)


def apply_configuration_set(config_set: List[Path], verbose: bool = False):
"""
Apply settings from the list of input files.
:param list config_set: list of paths to addon configuration to load and apply
:param bool verbose: Verbose output (currently unused)
"""
setup_django()

for config_path in config_set:
try:
config_data = json.loads(config_path.read_text())
except OSError:
config_data = []
if config_data:
if not isinstance(config_data, list):
config_data = [config_data]
for item in config_data:
apply_configuration(item)
2 changes: 1 addition & 1 deletion app_enabler/patcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ def update_setting(project_setting: str, config: Dict[str, Any]):
parsed = astor.parse_file(project_setting)
existing_setting = []
addon_settings = config.get("settings", {})
addon_installed_apps = config["installed-apps"]
addon_installed_apps = config.get("installed-apps", [])
for node in parsed.body:
if isinstance(node, ast.Assign) and node.targets[0].id == "INSTALLED_APPS":
installed_apps = [name.s for name in node.value.elts]
Expand Down
1 change: 1 addition & 0 deletions changes/9.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add support for external configuration json
2 changes: 0 additions & 2 deletions docs/todo.rst
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ Planned features
* Support extra-requirements `issue-6`_
* Support Django settings split in multiple files `issue-7`_
* Support Django urlconf split in multiple files `issue-8`_
* Add support for external addon.json `issue-9`_



Expand All @@ -17,4 +16,3 @@ Planned features
.. _issue-6: https://github.com/nephila/django-app-enabler/issues/6
.. _issue-7: https://github.com/nephila/django-app-enabler/issues/7
.. _issue-8: https://github.com/nephila/django-app-enabler/issues/8
.. _issue-9: https://github.com/nephila/django-app-enabler/issues/9
25 changes: 25 additions & 0 deletions tests/sample/config/1.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
[
{
"installed-apps": [
"taggit"
],
"settings": {
"MY_SETTING_A": "some_value"
},
"urls": [
[
"",
"djangocms_blog.taggit_urls"
]
],
"message": "json1-a"
},
{
"installed-apps": [
],
"settings": {
"MY_SETTING_B": "some_value"
},
"message": "json1-b"
}
]
6 changes: 6 additions & 0 deletions tests/sample/config/2.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"settings": {
"MY_SETTING_2": "some_value"
},
"message": "json2"
}
20 changes: 20 additions & 0 deletions tests/test_cli.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import os
import sys
from pathlib import Path
from subprocess import CalledProcessError
from unittest.mock import call, patch

Expand Down Expand Up @@ -121,6 +122,25 @@ def test_cli_enable(verbose: bool):
assert enable_fun.call_args_list == [call("djangocms_blog", verbose=verbose)]


@pytest.mark.parametrize("verbose", (True, False))
def test_cli_apply(verbose: bool):
"""Running apply command calls the business functions with the correct arguments."""
with patch("app_enabler.cli.apply_configuration_set") as apply_configuration_set:
runner = CliRunner()
if verbose:
args = ["--verbose"]
else:
args = []

configs = ("/path/config1.json", "/path/config2.json")
args.extend(("apply", *configs))
result = runner.invoke(cli, args)
assert result.exit_code == 0

apply_configuration_set.assert_called_once()
assert apply_configuration_set.call_args_list == [call([Path(config) for config in configs], verbose=verbose)]


@pytest.mark.parametrize("verbose", (True, False))
def test_cli_function(verbose: bool):
"""Running cli without commands return info message."""
Expand Down
42 changes: 38 additions & 4 deletions tests/test_enable.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import json
import os
import sys
from importlib import import_module
from types import ModuleType
from unittest.mock import patch

from app_enabler.enable import _verify_settings, _verify_urlconf, enable
from app_enabler.enable import _verify_settings, _verify_urlconf, apply_configuration_set, enable_application
from tests.utils import working_directory


Expand All @@ -15,7 +16,7 @@ def test_enable(capsys, pytester, project_dir, addon_config, teardown_django):
load_addon.return_value = addon_config
os.environ["DJANGO_SETTINGS_MODULE"] = "test_project.settings"

enable("djangocms_blog")
enable_application("djangocms_blog")

captured = capsys.readouterr()
assert addon_config["message"] in captured.out
Expand All @@ -37,7 +38,7 @@ def test_enable_minimal(capsys, pytester, project_dir, addon_config_minimal, tea
load_addon.return_value = addon_config_minimal
os.environ["DJANGO_SETTINGS_MODULE"] = "test_project.settings"

enable("djangocms_blog")
enable_application("djangocms_blog")

captured = capsys.readouterr()
assert not captured.out
Expand All @@ -59,7 +60,7 @@ def test_enable_no_application(pytester, project_dir, addon_config, teardown_dja
load_addon.return_value = None
os.environ["DJANGO_SETTINGS_MODULE"] = "test_project.settings"

enable("djangocms_blog")
enable_application("djangocms_blog")
if os.environ["DJANGO_SETTINGS_MODULE"] in sys.modules:
del sys.modules[os.environ["DJANGO_SETTINGS_MODULE"]]
if "test_project.urls" in sys.modules:
Expand All @@ -78,3 +79,36 @@ def test_enable_no_application(pytester, project_dir, addon_config, teardown_dja
):
urlpattern_patched = True
assert not urlpattern_patched


def test_apply_configuration_set(capsys, pytester, project_dir, teardown_django):
"""Applying configurations from a list of json files update the project settings and urlconf."""

with working_directory(project_dir):
sample_config_set = [
project_dir / "config" / "1.json",
project_dir / "config" / "2.json",
project_dir / "config" / "no_file.json",
]
os.environ["DJANGO_SETTINGS_MODULE"] = "test_project.settings"

json_configs = [json.loads(path.read_text()) for path in sample_config_set if path.exists()]

apply_configuration_set(sample_config_set)

captured = capsys.readouterr()
assert "json1-a" in captured.out
assert "json1-b" in captured.out
assert "json2" in captured.out
if os.environ["DJANGO_SETTINGS_MODULE"] in sys.modules:
del sys.modules[os.environ["DJANGO_SETTINGS_MODULE"]]
if "test_project.urls" in sys.modules:
del sys.modules["test_project.urls"]
imported_settings = import_module(os.environ["DJANGO_SETTINGS_MODULE"])
imported_urls = import_module("test_project.urls")
for config in json_configs:
if not isinstance(config, list):
config = [config]
for item in config:
assert _verify_settings(imported_settings, item)
assert _verify_urlconf(imported_urls, item)

0 comments on commit b213802

Please sign in to comment.