From 602289343c1f02de3fdbe74e805fd889e7d25e12 Mon Sep 17 00:00:00 2001 From: 9seconds Date: Thu, 24 Mar 2016 21:06:02 +0300 Subject: [PATCH 01/15] Bump version to 0.2 --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index c72ad1b..e1ed5d2 100644 --- a/setup.cfg +++ b/setup.cfg @@ -11,7 +11,7 @@ python-tag = py34 [metadata] name = concierge -version = 0.1.2 +version = 0.2 description-file = README.rst summary = Maintainable SSH config author = Sergey Arkhipov From 179bbab6f7b636d1650a53f143cc4a0586b1e909 Mon Sep 17 00:00:00 2001 From: 9seconds Date: Thu, 24 Mar 2016 21:31:06 +0300 Subject: [PATCH 02/15] Break backward compatibility --- concierge/endpoints/cli.py | 15 +++++++-------- concierge/templater.py | 36 ++++++++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+), 8 deletions(-) create mode 100644 concierge/templater.py diff --git a/concierge/endpoints/cli.py b/concierge/endpoints/cli.py index 0d88f09..833bb6f 100644 --- a/concierge/endpoints/cli.py +++ b/concierge/endpoints/cli.py @@ -4,6 +4,7 @@ import argparse import concierge +import concierge.templater def create_parser(): @@ -40,13 +41,11 @@ def create_parser(): "DESTINATION_PATH is stdout, then this option is set to false."), action="store_true", default=None) - - if concierge.EXTRAS["templater"].name: - parser.add_argument( - "-t", "--no-templater", - help="Do not use {} templater for SOURCE_PATH.".format( - concierge.EXTRAS["templater"].name), - action="store_true", - default=False) + parser.add_argument( + "-t", "--templater", + help=("Use following templater for config file. If nothing is set, " + "then no templater will be used."), + choices=concierge.all_templaters().keys(), + default=None) return parser diff --git a/concierge/templater.py b/concierge/templater.py new file mode 100644 index 0000000..1e5110a --- /dev/null +++ b/concierge/templater.py @@ -0,0 +1,36 @@ +# -*- coding: utf-8 -*- + + +import pkg_resources + + +TEMPLATER_NAMESPACE = "concierge.templater" + + +def all_templaters(): + templaters = {} + + for plugin in pkg_resources.iter_entry_points(group=TEMPLATER_NAMESPACE): + templaters[plugin.name] = plugin.load() + + return templaters + + +def get_templater(code): + if code is None: + return Templater() + + return all_templaters()[code] + + + +class Templater: + + code = None + """Code for the templater to refer to.""" + + name = "None" + """The name of the templater to show.""" + + def render(self, content): + return content From ba6af09ac925d5fdb436b7f22b20d1896b6299ea Mon Sep 17 00:00:00 2001 From: 9seconds Date: Sat, 26 Mar 2016 17:22:44 +0300 Subject: [PATCH 03/15] Attach new templater model --- concierge/__init__.py | 25 ------------------------- concierge/endpoints/cli.py | 13 ++++++++++--- concierge/endpoints/common.py | 8 +++++--- concierge/templater.py | 14 +++++++++----- 4 files changed, 24 insertions(+), 36 deletions(-) diff --git a/concierge/__init__.py b/concierge/__init__.py index c136901..83fad66 100644 --- a/concierge/__init__.py +++ b/concierge/__init__.py @@ -1,34 +1,9 @@ # -*- coding: utf-8 -*- -import collections import os.path -Templater = collections.namedtuple("Templater", ["name", "render"]) - -EXTRAS = {"templater": Templater(None, lambda content: content)} HOME_DIR = os.path.expanduser("~") DEFAULT_RC = os.path.join(HOME_DIR, ".conciergerc") DEFAULT_SSHCONFIG = os.path.join(HOME_DIR, ".ssh", "config") - - -try: - import mako.template -except ImportError: - pass -else: - EXTRAS["templater"] = Templater( - "Mako", - lambda content: mako.template.Template(content).render_unicode()) - - -if EXTRAS["templater"].name is None: - try: - import jinja2 - except ImportError: - pass - else: - EXTRAS["templater"] = Templater( - "Jinja2", - lambda content: jinja2.Environment().from_string(content).render()) diff --git a/concierge/endpoints/cli.py b/concierge/endpoints/cli.py index 833bb6f..d9a1958 100644 --- a/concierge/endpoints/cli.py +++ b/concierge/endpoints/cli.py @@ -42,10 +42,17 @@ def create_parser(): action="store_true", default=None) parser.add_argument( - "-t", "--templater", + "-u", "--use-templater", help=("Use following templater for config file. If nothing is set, " - "then no templater will be used."), - choices=concierge.all_templaters().keys(), + "then default template resolve chain will be " + "used (Mako -> Jinja -> Nothing)"), + choices=concierge.templater.all_templaters().keys(), default=None) + parser.add_argument( + "-t", "--no-templater", + help=("Do not use any templater. Please be noticed that newer " + "version of concierge will change that behavior."), + action="store_true", + default=False) return parser diff --git a/concierge/endpoints/common.py b/concierge/endpoints/common.py index 4d774fd..74389c3 100644 --- a/concierge/endpoints/common.py +++ b/concierge/endpoints/common.py @@ -7,6 +7,7 @@ import concierge.core.processor import concierge.endpoints.cli import concierge.endpoints.templates +import concierge.templater import concierge.utils @@ -25,6 +26,8 @@ def __init__(self, options): self.boring_syntax = options.boring_syntax self.add_header = options.add_header self.no_templater = getattr(options, "no_templater", False) + self.templater = concierge.templater.resolve_templater( + options.use_templater) if self.add_header is None: self.add_header = options.destination_path is not None @@ -91,11 +94,10 @@ def apply_template(self, content): LOG.info("Applying templater to content of %s.", self.source_path) try: - content = concierge.EXTRAS["templater"].render(content) + content = self.templater.render(content) except Exception as exc: LOG.error("Cannot process template (%s) in source file %s.", - self.source_path, concierge.EXTRAS["templater"].name, - exc) + self.source_path, self.templater.name, exc) raise LOG.info("Templated content of %s:\n%s", self.source_path, content) diff --git a/concierge/templater.py b/concierge/templater.py index 1e5110a..bec3379 100644 --- a/concierge/templater.py +++ b/concierge/templater.py @@ -5,10 +5,11 @@ TEMPLATER_NAMESPACE = "concierge.templater" +DEFAULT_RESOLVE_SEQ = "mako", "jinja2", None def all_templaters(): - templaters = {} + templaters = {Templater.code: Templater} for plugin in pkg_resources.iter_entry_points(group=TEMPLATER_NAMESPACE): templaters[plugin.name] = plugin.load() @@ -16,12 +17,15 @@ def all_templaters(): return templaters -def get_templater(code): - if code is None: - return Templater() +def resolve_templater(choose=None): + templaters = all_templaters() - return all_templaters()[code] + if choose: + return templaters[choose] + for code in DEFAULT_RESOLVE_SEQ: + if code in templaters: + return templaters[code] class Templater: From c67058a9ffd499c2f4d1862d9234534690f75f0b Mon Sep 17 00:00:00 2001 From: 9seconds Date: Sat, 26 Mar 2016 17:55:56 +0300 Subject: [PATCH 04/15] Fix tests --- concierge/endpoints/common.py | 9 +++++-- concierge/templater.py | 8 +++++-- tests/conftest.py | 33 ++++++++------------------ tests/test_endpoints_app.py | 43 +++++++++++++--------------------- tests/test_endpoints_check.py | 2 +- tests/test_endpoints_cli.py | 7 +----- tests/test_endpoints_daemon.py | 8 +++---- 7 files changed, 45 insertions(+), 65 deletions(-) diff --git a/concierge/endpoints/common.py b/concierge/endpoints/common.py index 74389c3..2caaab5 100644 --- a/concierge/endpoints/common.py +++ b/concierge/endpoints/common.py @@ -26,8 +26,13 @@ def __init__(self, options): self.boring_syntax = options.boring_syntax self.add_header = options.add_header self.no_templater = getattr(options, "no_templater", False) - self.templater = concierge.templater.resolve_templater( - options.use_templater) + + try: + self.templater = concierge.templater.resolve_templater( + options.use_templater) + except KeyError: + raise ValueError( + "Cannot find templater for {0}".format(options.use_templater)) if self.add_header is None: self.add_header = options.destination_path is not None diff --git a/concierge/templater.py b/concierge/templater.py index bec3379..7f4f848 100644 --- a/concierge/templater.py +++ b/concierge/templater.py @@ -19,13 +19,17 @@ def all_templaters(): def resolve_templater(choose=None): templaters = all_templaters() + found = None if choose: - return templaters[choose] + found = templaters[choose] for code in DEFAULT_RESOLVE_SEQ: if code in templaters: - return templaters[code] + found = templaters[code] + + if found: + return found() class Templater: diff --git a/tests/conftest.py b/tests/conftest.py index 1e762ae..7d168c9 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -8,20 +8,11 @@ import unittest.mock import concierge +import concierge.templater import inotify_simple import pytest -class FakeTemplater(object): - - @staticmethod - def render(content): - return content - - def __init__(self, name): - self.name = name - - def have_mocked(request, *mock_args, **mock_kwargs): if len(mock_args) > 1: method = unittest.mock.patch.object @@ -80,15 +71,6 @@ def sysargv(monkeypatch): return argv -@pytest.fixture(params=(None, "Fake")) -def templater(request, monkeypatch): - templater = FakeTemplater(request.param) - - monkeypatch.setitem(concierge.EXTRAS, "templater", templater) - - return templater - - @pytest.fixture def inotifier(request): mock = have_mocked(request, "inotify_simple.INotify") @@ -110,6 +92,11 @@ def side_effect(): return mock +@pytest.fixture +def template_render(request): + return have_mocked(request, concierge.templater.Templater, "render") + + @pytest.fixture(params=(None, "-d", "--debug")) def cliparam_debug(request): return request.param @@ -161,7 +148,7 @@ def cliargs_default(sysargv): @pytest.fixture -def cliargs_fullset(sysargv, templater, cliparam_debug, cliparam_verbose, +def cliargs_fullset(sysargv, cliparam_debug, cliparam_verbose, cliparam_source_path, cliparam_destination_path, cliparam_boring_syntax, cliparam_add_header, cliparam_no_templater): @@ -188,7 +175,7 @@ def cliargs_fullset(sysargv, templater, cliparam_debug, cliparam_verbose, sysargv.append(param) sysargv.append("/path/to") - if templater.name and cliparam_no_templater: + if cliparam_no_templater: sysargv.append(cliparam_no_templater) return sysargv, options @@ -210,7 +197,7 @@ def cliargs_concierge_fullset(cliargs_fullset, cliparam_systemd, @pytest.fixture -def mock_mainfunc(cliargs_default, mock_get_content, templater, inotifier): +def mock_mainfunc(cliargs_default, mock_get_content, inotifier): mock_get_content.return_value = """\ Compression yes @@ -221,4 +208,4 @@ def mock_mainfunc(cliargs_default, mock_get_content, templater, inotifier): HostName lalala """ - return cliargs_default, mock_get_content, templater, inotifier + return cliargs_default, mock_get_content, inotifier diff --git a/tests/test_endpoints_app.py b/tests/test_endpoints_app.py index 18e3b23..4e6dcd5 100644 --- a/tests/test_endpoints_app.py +++ b/tests/test_endpoints_app.py @@ -25,16 +25,14 @@ def do(self): return self.output() -def test_fetch_content_ok(cliargs_default, templater, - mock_get_content): +def test_fetch_content_ok(cliargs_default, mock_get_content): mock_get_content.return_value = "Content" app = get_app() assert app.fetch_content() == mock_get_content.return_value -def test_fetch_content_exception(cliargs_default, templater, - mock_get_content): +def test_fetch_content_exception(cliargs_default, mock_get_content): mock_get_content.side_effect = Exception app = get_app() @@ -42,18 +40,16 @@ def test_fetch_content_exception(cliargs_default, templater, app.fetch_content() -def test_apply_content_ok(monkeypatch, cliargs_default, templater): - monkeypatch.setattr(templater, "render", lambda param: param.upper()) +def test_apply_content_ok(monkeypatch, cliargs_default, template_render): + template_render.side_effect = lambda param: param.upper() app = get_app() assert app.apply_template("hello") == "HELLO" -def test_apply_content_exception(monkeypatch, cliargs_default, templater): - def badfunc(content): - raise Exception - - monkeypatch.setattr(templater, "render", badfunc) +def test_apply_content_exception(monkeypatch, cliargs_default, + template_render): + template_render.side_effect = Exception app = get_app() with pytest.raises(Exception): @@ -93,10 +89,10 @@ def test_attach_header(cliargs_default): @pytest.mark.parametrize( "add_header", ( True, False)) -def test_get_new_config(monkeypatch, cliargs_default, templater, +def test_get_new_config(monkeypatch, cliargs_default, template_render, mock_get_content, no_templater, boring_syntax, add_header): - monkeypatch.setattr(templater, "render", lambda param: param.upper()) + template_render.side_effect = lambda param: param.upper() mock_get_content.return_value = """\ Compression yes @@ -134,8 +130,7 @@ def test_get_new_config(monkeypatch, cliargs_default, templater, assert not result.startswith("#") -def test_output_stdout(capfd, monkeypatch, cliargs_default, templater, - mock_get_content): +def test_output_stdout(capfd, monkeypatch, cliargs_default, mock_get_content): mock_get_content.return_value = """\ Compression yes @@ -166,7 +161,7 @@ def test_output_stdout(capfd, monkeypatch, cliargs_default, templater, assert not err -def test_output_file(cliargs_default, ptmpdir, templater, mock_get_content): +def test_output_file(cliargs_default, ptmpdir, mock_get_content): mock_get_content.return_value = """\ Compression yes @@ -187,7 +182,7 @@ def test_output_file(cliargs_default, ptmpdir, templater, mock_get_content): def test_output_file_exception(monkeypatch, cliargs_default, ptmpdir, - templater, mock_get_content): + mock_get_content): def write_fail(*args, **kwargs): raise Exception @@ -210,7 +205,7 @@ def write_fail(*args, **kwargs): @pytest.mark.longrun -def test_create_app(cliargs_fullset, templater, mock_log_configuration): +def test_create_app(cliargs_fullset, mock_log_configuration): _, options = cliargs_fullset parser = cli.create_parser() @@ -235,15 +230,10 @@ def test_create_app(cliargs_fullset, templater, mock_log_configuration): else: assert app.add_header == (options["destination_path"] is not None) - if templater.name: - assert app.no_templater == bool(options["no_templater"]) - else: - assert not app.no_templater - assert mock_log_configuration.called -def test_mainfunc_ok(cliargs_default, templater, mock_get_content): +def test_mainfunc_ok(cliargs_default, mock_get_content): mock_get_content.return_value = """\ Compression yes @@ -260,7 +250,7 @@ def test_mainfunc_ok(cliargs_default, templater, mock_get_content): assert result is None or result == os.EX_OK -def test_mainfunc_exception(cliargs_default, templater, mock_get_content): +def test_mainfunc_exception(cliargs_default, mock_get_content): mock_get_content.side_effect = Exception main = concierge.endpoints.common.main(SimpleApp) @@ -268,8 +258,7 @@ def test_mainfunc_exception(cliargs_default, templater, mock_get_content): assert main() != os.EX_OK -def test_mainfunc_keyboardinterrupt(cliargs_default, templater, - mock_get_content): +def test_mainfunc_keyboardinterrupt(cliargs_default, mock_get_content): mock_get_content.side_effect = KeyboardInterrupt main = concierge.endpoints.common.main(SimpleApp) diff --git a/tests/test_endpoints_check.py b/tests/test_endpoints_check.py index 8c2ec18..5bd1c4b 100644 --- a/tests/test_endpoints_check.py +++ b/tests/test_endpoints_check.py @@ -14,7 +14,7 @@ def test_mainfunc_ok(mock_mainfunc): def test_mainfunc_exception(mock_mainfunc): - _, mock_get_content, _, _ = mock_mainfunc + _, mock_get_content, _ = mock_mainfunc mock_get_content.side_effect = Exception main = concierge.endpoints.common.main(concierge.endpoints.check.CheckApp) diff --git a/tests/test_endpoints_cli.py b/tests/test_endpoints_cli.py index f9542f6..b81cc83 100644 --- a/tests/test_endpoints_cli.py +++ b/tests/test_endpoints_cli.py @@ -5,7 +5,7 @@ import concierge.endpoints.cli as cli -def test_parser_default(cliargs_default, templater): +def test_parser_default(cliargs_default): parser = cli.create_parser() parsed = parser.parse_args() @@ -15,8 +15,3 @@ def test_parser_default(cliargs_default, templater): assert parsed.destination_path is None assert not parsed.boring_syntax assert parsed.add_header is None - - if templater.name: - assert not parsed.no_templater - else: - assert not hasattr(parsed, "no_templater") diff --git a/tests/test_endpoints_daemon.py b/tests/test_endpoints_daemon.py index 92d1231..aac9ccb 100644 --- a/tests/test_endpoints_daemon.py +++ b/tests/test_endpoints_daemon.py @@ -57,7 +57,7 @@ def test_print_help(capfd, cliargs_default, cliparam_curlsh): "main_method", ( True, False)) def test_work(mock_mainfunc, ptmpdir, main_method): - _, _, _, inotifier = mock_mainfunc + _, _, inotifier = mock_mainfunc app = get_app() app.destination_path = ptmpdir.join("filename").strpath @@ -76,7 +76,7 @@ def test_work(mock_mainfunc, ptmpdir, main_method): def test_track_no_our_events(no_sleep, mock_mainfunc, ptmpdir): - _, _, _, inotifier = mock_mainfunc + _, _, inotifier = mock_mainfunc inotifier.v.clear() inotifier.v.extend([inotify_simple.Event(0, 0, 0, "Fake")] * 3) @@ -89,7 +89,7 @@ def test_track_no_our_events(no_sleep, mock_mainfunc, ptmpdir): def test_track_cannot_read(no_sleep, mock_mainfunc, ptmpdir): - _, _, _, inotifier = mock_mainfunc + _, _, inotifier = mock_mainfunc def add_watch(*args, **kwargs): exc = IOError("Hello?") @@ -138,7 +138,7 @@ def test_mainfunc_ok(mock_mainfunc): def test_mainfunc_exception(mock_mainfunc): - _, _, _, inotifier = mock_mainfunc + _, _, inotifier = mock_mainfunc inotifier.read.side_effect = Exception result = daemon.main() From bf1fbee655390eef05f754b289126e992da4fe4e Mon Sep 17 00:00:00 2001 From: 9seconds Date: Sat, 26 Mar 2016 19:01:40 +0300 Subject: [PATCH 05/15] Add tests for templater --- concierge/templater.py | 19 ++++------ tests/test_templater.py | 83 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 91 insertions(+), 11 deletions(-) create mode 100644 tests/test_templater.py diff --git a/concierge/templater.py b/concierge/templater.py index 7f4f848..3a22235 100644 --- a/concierge/templater.py +++ b/concierge/templater.py @@ -9,7 +9,7 @@ def all_templaters(): - templaters = {Templater.code: Templater} + templaters = dict.fromkeys(("dummy", None), Templater) for plugin in pkg_resources.iter_entry_points(group=TEMPLATER_NAMESPACE): templaters[plugin.name] = plugin.load() @@ -23,21 +23,18 @@ def resolve_templater(choose=None): if choose: found = templaters[choose] + else: + for code in DEFAULT_RESOLVE_SEQ: + if code in templaters: + found = templaters[code] + break - for code in DEFAULT_RESOLVE_SEQ: - if code in templaters: - found = templaters[code] - - if found: - return found() + return found() class Templater: - code = None - """Code for the templater to refer to.""" - - name = "None" + name = "dummy" """The name of the templater to show.""" def render(self, content): diff --git a/tests/test_templater.py b/tests/test_templater.py new file mode 100644 index 0000000..d9b9353 --- /dev/null +++ b/tests/test_templater.py @@ -0,0 +1,83 @@ +# -*- coding: utf-8 -*- + + +import collections + +import pytest + +import concierge.templater as templater + + +class Plugin: + + def __init__(self, tpl): + self.templater = tpl + + @property + def name(self): + return self.templater.name + + def load(self): + return self.templater + + +def create_templater(name_): + class Fake(templater.Templater): + name = name_ + + def render(self, content): + return self.name + " " + content + + return Fake + + +@pytest.fixture +def mock_plugins(request, monkeypatch): + templaters = [ + Plugin(create_templater(name)) + for name in ("mako", "jinja2")] + + monkeypatch.setattr( + "pkg_resources.iter_entry_points", + lambda *args, **kwargs: templaters) + + return templaters + + +def test_all_templaters(mock_plugins): + tpls = templater.all_templaters() + + assert len(tpls) == 4 + assert tpls[None] is templater.Templater + assert tpls["dummy"] is templater.Templater + assert tpls["mako"]().render("q") == "mako q" + assert tpls["jinja2"]().render("q") == "jinja2 q" + + +def test_resolve_templater_none(mock_plugins): + tpls = templater.all_templaters() + tpl = templater.resolve_templater("dummy") + + assert isinstance(tpl, templater.Templater) + assert tpl.name == "dummy" + + +def test_resolve_templater_default(mock_plugins): + assert templater.resolve_templater(None).name == "mako" + del mock_plugins[0] + + assert templater.resolve_templater(None).name == "jinja2" + del mock_plugins[0] + + assert templater.resolve_templater(None).name == "dummy" + + +@pytest.mark.parametrize("code", ("mako", "jinja2", "dummy")) +def test_resolve_templater_known(mock_plugins, code): + assert templater.resolve_templater(code).name == code + + +def test_render_dummy_templater(): + tpl = templater.Templater() + + assert tpl.render("lalala") == "lalala" From a3db75d4b975da7923c4171e48bf33b621d8aa67 Mon Sep 17 00:00:00 2001 From: 9seconds Date: Sat, 26 Mar 2016 19:01:55 +0300 Subject: [PATCH 06/15] Add dummy plugin --- setup.cfg | 2 ++ 1 file changed, 2 insertions(+) diff --git a/setup.cfg b/setup.cfg index e1ed5d2..bb6450d 100644 --- a/setup.cfg +++ b/setup.cfg @@ -42,6 +42,8 @@ jinja = console_scripts = concierge-check=concierge.endpoints.check:main concierge=concierge.endpoints.daemon:main +concierge.templater = + dummy = concierge.templater:Templater [global] setup-hooks = From 4100ab6bdeb26b052d6d90b6e6a361514ad11dce Mon Sep 17 00:00:00 2001 From: 9seconds Date: Sat, 26 Mar 2016 19:05:37 +0300 Subject: [PATCH 07/15] Remove excess extras --- setup.cfg | 7 ------- 1 file changed, 7 deletions(-) diff --git a/setup.cfg b/setup.cfg index bb6450d..5c200eb 100644 --- a/setup.cfg +++ b/setup.cfg @@ -31,13 +31,6 @@ classifier = packages = concierge -[extras] -mako = - mako - -jinja = - jinja2 - [entry_points] console_scripts = concierge-check=concierge.endpoints.check:main From 3ca83c4963ea3fe26f0ba6956f785cc9c27f26af Mon Sep 17 00:00:00 2001 From: 9seconds Date: Sat, 26 Mar 2016 19:36:25 +0300 Subject: [PATCH 08/15] More templater tests --- concierge/__init__.py | 4 ++++ concierge/endpoints/cli.py | 3 ++- concierge/endpoints/common.py | 14 ++++++++++++++ concierge/templater.py | 7 +++++-- tests/test_templater.py | 3 +-- 5 files changed, 26 insertions(+), 5 deletions(-) diff --git a/concierge/__init__.py b/concierge/__init__.py index 83fad66..44f8be5 100644 --- a/concierge/__init__.py +++ b/concierge/__init__.py @@ -2,8 +2,12 @@ import os.path +import warnings HOME_DIR = os.path.expanduser("~") DEFAULT_RC = os.path.join(HOME_DIR, ".conciergerc") DEFAULT_SSHCONFIG = os.path.join(HOME_DIR, ".ssh", "config") + + +warnings.simplefilter('always', DeprecationWarning) diff --git a/concierge/endpoints/cli.py b/concierge/endpoints/cli.py index d9a1958..cd3e034 100644 --- a/concierge/endpoints/cli.py +++ b/concierge/endpoints/cli.py @@ -45,7 +45,8 @@ def create_parser(): "-u", "--use-templater", help=("Use following templater for config file. If nothing is set, " "then default template resolve chain will be " - "used (Mako -> Jinja -> Nothing)"), + "used (Mako -> Jinja -> Nothing). Dummy templater means that " + "no templater is actually used."), choices=concierge.templater.all_templaters().keys(), default=None) parser.add_argument( diff --git a/concierge/endpoints/common.py b/concierge/endpoints/common.py index 2caaab5..44b7fed 100644 --- a/concierge/endpoints/common.py +++ b/concierge/endpoints/common.py @@ -3,6 +3,7 @@ import abc import os +import warnings import concierge.core.processor import concierge.endpoints.cli @@ -21,6 +22,19 @@ def specify_parser(cls, parser): return parser def __init__(self, options): + if options.use_templater is None: + warnings.warn( + "--use-templater flag and therefore implicit templater " + "autoresolve are deprecated. Please use explicit " + "templater in both concierge-check and concierge.", + FutureWarning) + + if options.no_templater: + warnings.warn( + "Flag --no-templater is deprecated. " + "Please use 'dummy' templater instead.", + DeprecationWarning) + self.source_path = options.source_path self.destination_path = options.destination_path self.boring_syntax = options.boring_syntax diff --git a/concierge/templater.py b/concierge/templater.py index 3a22235..bdec1dd 100644 --- a/concierge/templater.py +++ b/concierge/templater.py @@ -2,14 +2,15 @@ import pkg_resources +import warnings TEMPLATER_NAMESPACE = "concierge.templater" -DEFAULT_RESOLVE_SEQ = "mako", "jinja2", None +DEFAULT_RESOLVE_SEQ = "mako", "jinja2" def all_templaters(): - templaters = dict.fromkeys(("dummy", None), Templater) + templaters = {"dummy": Templater} for plugin in pkg_resources.iter_entry_points(group=TEMPLATER_NAMESPACE): templaters[plugin.name] = plugin.load() @@ -28,6 +29,8 @@ def resolve_templater(choose=None): if code in templaters: found = templaters[code] break + else: + found = Templater return found() diff --git a/tests/test_templater.py b/tests/test_templater.py index d9b9353..5927bae 100644 --- a/tests/test_templater.py +++ b/tests/test_templater.py @@ -47,8 +47,7 @@ def mock_plugins(request, monkeypatch): def test_all_templaters(mock_plugins): tpls = templater.all_templaters() - assert len(tpls) == 4 - assert tpls[None] is templater.Templater + assert len(tpls) == 3 assert tpls["dummy"] is templater.Templater assert tpls["mako"]().render("q") == "mako q" assert tpls["jinja2"]().render("q") == "jinja2 q" From 7a18c02eef3be3a74e20726496077e5703defda0 Mon Sep 17 00:00:00 2001 From: 9seconds Date: Sat, 26 Mar 2016 19:39:30 +0300 Subject: [PATCH 09/15] Add templater to systemd config --- concierge/endpoints/daemon.py | 3 ++- concierge/endpoints/templates.py | 7 ++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/concierge/endpoints/daemon.py b/concierge/endpoints/daemon.py index 5d2dfaa..9a38cc9 100755 --- a/concierge/endpoints/daemon.py +++ b/concierge/endpoints/daemon.py @@ -65,7 +65,8 @@ def do(self): if not self.systemd: return self.track() - script = concierge.endpoints.templates.make_systemd_script() + script = concierge.endpoints.templates.make_systemd_script( + self.templater) if not self.curlsh: script = [ diff --git a/concierge/endpoints/templates.py b/concierge/endpoints/templates.py index 62ce784..5b8856c 100644 --- a/concierge/endpoints/templates.py +++ b/concierge/endpoints/templates.py @@ -25,7 +25,7 @@ After=syslog.target [Service] -ExecStart={command} -o {sshconfig} +ExecStart={command} -u {templater} -o {sshconfig} Restart=on-failure [Install] @@ -52,14 +52,15 @@ def make_header(**kwargs): rc_file=kwargs.get("rc_file", "???")) -def make_systemd_script(): +def make_systemd_script(templater): systemd_user_path = os.path.join(concierge.HOME_DIR, ".config", "systemd", "user") systemd_user_service_path = os.path.join(systemd_user_path, SYSTEMD_SERVICE_NAME) systemd_config = SYSTEMD_CONFIG.format( command=distutils.spawn.find_executable(sys.argv[0]), - sshconfig=concierge.DEFAULT_SSHCONFIG) + sshconfig=concierge.DEFAULT_SSHCONFIG, + templater=templater.name.lower()) yield 'mkdir -p "{0}" || true'.format(systemd_user_path) yield 'cat > "{0}" < Date: Sat, 26 Mar 2016 19:44:03 +0300 Subject: [PATCH 10/15] Lint fixes --- concierge/templater.py | 3 +-- tests/test_endpoints_templates.py | 3 ++- tests/test_templater.py | 5 +---- 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/concierge/templater.py b/concierge/templater.py index bdec1dd..eaa7770 100644 --- a/concierge/templater.py +++ b/concierge/templater.py @@ -2,7 +2,6 @@ import pkg_resources -import warnings TEMPLATER_NAMESPACE = "concierge.templater" @@ -35,7 +34,7 @@ def resolve_templater(choose=None): return found() -class Templater: +class Templater(object): name = "dummy" """The name of the templater to show.""" diff --git a/tests/test_endpoints_templates.py b/tests/test_endpoints_templates.py index 655277e..04951df 100644 --- a/tests/test_endpoints_templates.py +++ b/tests/test_endpoints_templates.py @@ -4,6 +4,7 @@ import pytest import concierge.endpoints.templates as templates +import concierge.templater @pytest.mark.parametrize( @@ -32,4 +33,4 @@ def test_make_header(filename, date): def test_make_systemd_script(): - list(templates.make_systemd_script()) # have no idea how to test that + list(templates.make_systemd_script(concierge.templater.Templater)) diff --git a/tests/test_templater.py b/tests/test_templater.py index 5927bae..6e993ce 100644 --- a/tests/test_templater.py +++ b/tests/test_templater.py @@ -1,14 +1,12 @@ # -*- coding: utf-8 -*- -import collections - import pytest import concierge.templater as templater -class Plugin: +class Plugin(object): def __init__(self, tpl): self.templater = tpl @@ -54,7 +52,6 @@ def test_all_templaters(mock_plugins): def test_resolve_templater_none(mock_plugins): - tpls = templater.all_templaters() tpl = templater.resolve_templater("dummy") assert isinstance(tpl, templater.Templater) From 4e6f4396e8f28067b0a7ec03657558b49a45bb5b Mon Sep 17 00:00:00 2001 From: 9seconds Date: Sun, 27 Mar 2016 00:10:24 +0300 Subject: [PATCH 11/15] Add information about templaters to README --- README.rst | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/README.rst b/README.rst index 2fb8ff3..8617141 100644 --- a/README.rst +++ b/README.rst @@ -97,6 +97,28 @@ After installation, 2 utilities will be available: * ``concierge`` +Templaters +---------- + +``concierge`` comes with support of additional templaters, you may plug +them in installing the packages from PyPI. At the time of writing, +support of following templaters was done: + +* `concierge-mako `_ - + support of Mako_ templates +* `concierge-jinja `_ - + support of Jinja2_ templates + +To install them just do + +.. code-block:: shell + + $ pip install concierge-mako + +And ``concierge`` will automatically recognizes support of Mako and now +one may use ``concierge -u mako`` for her ``~/.conciergerc``. + + concierge-check --------------- From 7de738af4aae0d7d0f9b260e790365ee3462b569 Mon Sep 17 00:00:00 2001 From: 9seconds Date: Sat, 2 Apr 2016 15:49:59 +0300 Subject: [PATCH 12/15] Implement support of libnotify --- concierge/endpoints/cli.py | 5 +++++ concierge/endpoints/common.py | 26 ++++++++++++++++++-------- concierge/notifications.py | 34 ++++++++++++++++++++++++++++++++++ setup.cfg | 4 ++++ 4 files changed, 61 insertions(+), 8 deletions(-) create mode 100644 concierge/notifications.py diff --git a/concierge/endpoints/cli.py b/concierge/endpoints/cli.py index cd3e034..365c402 100644 --- a/concierge/endpoints/cli.py +++ b/concierge/endpoints/cli.py @@ -55,5 +55,10 @@ def create_parser(): "version of concierge will change that behavior."), action="store_true", default=False) + parser.add_argument( + "-n", "--notify", + help="Notify user on problems.", + action="store_true", + default=False) return parser diff --git a/concierge/endpoints/common.py b/concierge/endpoints/common.py index 44b7fed..2a71f4f 100644 --- a/concierge/endpoints/common.py +++ b/concierge/endpoints/common.py @@ -8,6 +8,7 @@ import concierge.core.processor import concierge.endpoints.cli import concierge.endpoints.templates +import concierge.notifications import concierge.templater import concierge.utils @@ -41,6 +42,11 @@ def __init__(self, options): self.add_header = options.add_header self.no_templater = getattr(options, "no_templater", False) + if options.notify: + self.notificator = concierge.notifications.notifier + else: + self.notificator = concierge.notifications.dummy_notifier + try: self.templater = concierge.templater.resolve_templater( options.use_templater) @@ -71,8 +77,8 @@ def output(self): with concierge.utils.topen(self.destination_path, True) as destfp: destfp.write(content) except Exception as exc: - LOG.error("Cannot write to file %s: %s", - self.destination_path, exc) + self.log_error("Cannot write to file %s: %s", + self.destination_path, exc) raise def get_new_config(self): @@ -101,8 +107,8 @@ def fetch_content(self): try: content = concierge.utils.get_content(self.source_path) except Exception as exc: - LOG.error("Cannot fetch content from %s: %s", - self.source_path, exc) + self.log_error("Cannot fetch content from %s: %s", + self.source_path, exc) raise LOG.info("Original content of %s:\n%s", self.source_path, content) @@ -115,8 +121,8 @@ def apply_template(self, content): try: content = self.templater.render(content) except Exception as exc: - LOG.error("Cannot process template (%s) in source file %s.", - self.source_path, self.templater.name, exc) + self.log_error("Cannot process template (%s) in source file %s.", + self.source_path, self.templater.name, exc) raise LOG.info("Templated content of %s:\n%s", self.source_path, content) @@ -127,8 +133,8 @@ def process_syntax(self, content): try: return concierge.core.processor.process(content) except Exception as exc: - LOG.error("Cannot parse content of source file %s: %s", - self.source_path, exc) + self.log_error("Cannot parse content of source file %s: %s", + self.source_path, exc) raise def attach_header(self, content): @@ -138,6 +144,10 @@ def attach_header(self, content): return content + def log_error(self, template, *args): + LOG.error(template, *args) + self.notificator(template % args) + def main(app_class): def main_func(): diff --git a/concierge/notifications.py b/concierge/notifications.py new file mode 100644 index 0000000..d56e224 --- /dev/null +++ b/concierge/notifications.py @@ -0,0 +1,34 @@ +# -*- coding: utf-8 -*- + + +import atexit + + +try: + import pgi +except ImportError: + pgi = None +else: + pgi.install_as_gi() + + import gi + + gi.require_version("Notify", "0.7") + + from gi.repository import Notify + + Notify.init("concierge") + atexit.register(Notify.uninit) + + +def dummy_notifier(_): + pass + + +notifier = dummy_notifier + +if pgi: + def libnotify_notifier(problem): + Notify.Notification.new("concierge", problem, None).show() + + notifier = libnotify_notifier diff --git a/setup.cfg b/setup.cfg index 5c200eb..85e52ea 100644 --- a/setup.cfg +++ b/setup.cfg @@ -31,6 +31,10 @@ classifier = packages = concierge +[extras] +libnotify = + pgi >=0.0.11,<0.1 + [entry_points] console_scripts = concierge-check=concierge.endpoints.check:main From ce2803a70fd951aa9b768371db5e66cf0afc328b Mon Sep 17 00:00:00 2001 From: 9seconds Date: Sat, 2 Apr 2016 16:11:47 +0300 Subject: [PATCH 13/15] Test modifications --- tests/conftest.py | 29 ++++++++++++++++++++++++----- tests/test_endpoints_app.py | 10 ++++++++++ 2 files changed, 34 insertions(+), 5 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 7d168c9..0db8908 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -7,11 +7,13 @@ import sys import unittest.mock -import concierge -import concierge.templater import inotify_simple import pytest +import concierge +import concierge.notifications +import concierge.templater + def have_mocked(request, *mock_args, **mock_kwargs): if len(mock_args) > 1: @@ -52,6 +54,17 @@ def mock_log_configuration(request): return have_mocked(request, "concierge.utils.configure_logging") +@pytest.fixture(autouse=True) +def mock_notificatior(request, monkeypatch): + marker = request.node.get_marker("no_mock_notificatior") + + if not marker: + monkeypatch.setattr( + concierge.notifications, + "notifier", + concierge.notifications.dummy_notifier) + + @pytest.fixture def ptmpdir(request, tmpdir): for key in "TMP", "TEMPDIR", "TEMP": @@ -142,6 +155,11 @@ def cliparam_curlsh(request): return request.param +@pytest.fixture(params=(None, "-n", "--notify")) +def cliparam_notify(request): + return request.param + + @pytest.fixture def cliargs_default(sysargv): return sysargv @@ -151,7 +169,7 @@ def cliargs_default(sysargv): def cliargs_fullset(sysargv, cliparam_debug, cliparam_verbose, cliparam_source_path, cliparam_destination_path, cliparam_boring_syntax, cliparam_add_header, - cliparam_no_templater): + cliparam_no_templater, cliparam_notify): options = { "debug": cliparam_debug, "verbose": cliparam_verbose, @@ -159,10 +177,11 @@ def cliargs_fullset(sysargv, cliparam_debug, cliparam_verbose, "destination_path": cliparam_destination_path, "add_header": cliparam_add_header, "boring_syntax": cliparam_boring_syntax, - "no_templater": cliparam_no_templater} + "no_templater": cliparam_no_templater, + "notify": cliparam_notify} bool_params = ( cliparam_debug, cliparam_verbose, cliparam_boring_syntax, - cliparam_add_header) + cliparam_add_header, cliparam_notify) value_params = ( cliparam_source_path, cliparam_destination_path) diff --git a/tests/test_endpoints_app.py b/tests/test_endpoints_app.py index 4e6dcd5..5a9e7a3 100644 --- a/tests/test_endpoints_app.py +++ b/tests/test_endpoints_app.py @@ -25,6 +25,16 @@ def do(self): return self.output() +def test_resolve_templater_unknown(cliargs_default, monkeypatch): + def boom(*args, **kwargs): + raise KeyError + + monkeypatch.setattr("concierge.templater.resolve_templater", boom) + + with pytest.raises(ValueError): + get_app() + + def test_fetch_content_ok(cliargs_default, mock_get_content): mock_get_content.return_value = "Content" From 9d83ff785821f1d298bcfd220b4dbe9f3a407034 Mon Sep 17 00:00:00 2001 From: 9seconds Date: Sat, 2 Apr 2016 16:26:09 +0300 Subject: [PATCH 14/15] Documentation update --- README.rst | 19 +++++++++---------- concierge/endpoints/cli.py | 4 ++-- concierge/endpoints/common.py | 6 +++--- tests/conftest.py | 10 +++++----- 4 files changed, 19 insertions(+), 20 deletions(-) diff --git a/README.rst b/README.rst index 8617141..bc1a7ee 100644 --- a/README.rst +++ b/README.rst @@ -62,21 +62,20 @@ or if you want to install it manually, do following: $ cd concierge $ python setup.py install -By default, no template support will be installed. If you want to use -Mako_ or Jinja2_, please do following: +By default, no template support is going to be installed. If you want to +use Mako_ or Jinja2_, please refer to `Templaters`_ section. -.. code-block:: shell - - $ pip install concierge[mako] - -or +Also, it is possible to install support of `libnotify +`_. Please install tool like +this: .. code-block:: shell - $ pip install concierge[jinja] - + $ pip install concierge[libnotify] -If you already have them installed, then you are good. +In that case, you will have a desktop notifications about any problem +with parsing of your ``~/.conciergerc``. Yep, these Ubuntu popups on the +right top of the screen. If you have a problems with Pip installation (with modifiers, for example), please update your pip and setuptools first. diff --git a/concierge/endpoints/cli.py b/concierge/endpoints/cli.py index 365c402..d182e89 100644 --- a/concierge/endpoints/cli.py +++ b/concierge/endpoints/cli.py @@ -56,8 +56,8 @@ def create_parser(): action="store_true", default=False) parser.add_argument( - "-n", "--notify", - help="Notify user on problems.", + "-n", "--no-desktop-notifications", + help="Do not show desktop notifications on problems.", action="store_true", default=False) diff --git a/concierge/endpoints/common.py b/concierge/endpoints/common.py index 2a71f4f..64a4110 100644 --- a/concierge/endpoints/common.py +++ b/concierge/endpoints/common.py @@ -42,10 +42,10 @@ def __init__(self, options): self.add_header = options.add_header self.no_templater = getattr(options, "no_templater", False) - if options.notify: - self.notificator = concierge.notifications.notifier - else: + if options.no_desktop_notifications: self.notificator = concierge.notifications.dummy_notifier + else: + self.notificator = concierge.notifications.notifier try: self.templater = concierge.templater.resolve_templater( diff --git a/tests/conftest.py b/tests/conftest.py index 0db8908..f2de231 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -155,8 +155,8 @@ def cliparam_curlsh(request): return request.param -@pytest.fixture(params=(None, "-n", "--notify")) -def cliparam_notify(request): +@pytest.fixture(params=(None, "-n", "--no-desktop-notifications")) +def cliparam_no_desktop_notifications(request): return request.param @@ -169,7 +169,7 @@ def cliargs_default(sysargv): def cliargs_fullset(sysargv, cliparam_debug, cliparam_verbose, cliparam_source_path, cliparam_destination_path, cliparam_boring_syntax, cliparam_add_header, - cliparam_no_templater, cliparam_notify): + cliparam_no_templater, cliparam_no_desktop_notifications): options = { "debug": cliparam_debug, "verbose": cliparam_verbose, @@ -178,10 +178,10 @@ def cliargs_fullset(sysargv, cliparam_debug, cliparam_verbose, "add_header": cliparam_add_header, "boring_syntax": cliparam_boring_syntax, "no_templater": cliparam_no_templater, - "notify": cliparam_notify} + "no_desktop_notifications": cliparam_no_desktop_notifications} bool_params = ( cliparam_debug, cliparam_verbose, cliparam_boring_syntax, - cliparam_add_header, cliparam_notify) + cliparam_add_header, cliparam_no_desktop_notifications) value_params = ( cliparam_source_path, cliparam_destination_path) From 4cde98c284c4178fa8e39e08b6d99eac68248078 Mon Sep 17 00:00:00 2001 From: 9seconds Date: Sun, 3 Apr 2016 13:35:12 +0300 Subject: [PATCH 15/15] Fix for systemd script --- concierge/endpoints/templates.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/concierge/endpoints/templates.py b/concierge/endpoints/templates.py index 5b8856c..bf208fa 100644 --- a/concierge/endpoints/templates.py +++ b/concierge/endpoints/templates.py @@ -22,14 +22,13 @@ SYSTEMD_CONFIG = """ [Unit] Description=Daemon for converting ~/.concierge to ~/.ssh/config -After=syslog.target [Service] ExecStart={command} -u {templater} -o {sshconfig} Restart=on-failure [Install] -WantedBy=multi-user.target +WantedBy=default.target """.strip() SYSTEMD_SERVICE_NAME = "concierge.service"