From 2815f317e5a1a202be4d9d0f36faec81f687317f Mon Sep 17 00:00:00 2001 From: Peter Conerly Date: Sat, 12 Nov 2016 13:49:46 -0800 Subject: [PATCH 1/9] Allow plugins to have [env:name] and find plugins relatively based on env's PYTHONPATH --- circus/arbiter.py | 10 +++- circus/config.py | 32 ++++++++-- circus/plugins/__init__.py | 58 ++++++++++++------- .../config/find_plugin_in_pythonpath.ini | 9 +++ .../config/plugins_uniquename/__init__.py | 0 .../config/plugins_uniquename/my_plugin.py | 7 +++ circus/tests/test_arbiter.py | 57 ++++++++++++++++-- circus/tests/test_config.py | 8 +++ circus/tests/test_plugins.py | 30 ++++++++++ docs/source/for-devs/writing-plugins.rst | 15 +++++ 10 files changed, 195 insertions(+), 31 deletions(-) create mode 100644 circus/tests/config/find_plugin_in_pythonpath.ini create mode 100644 circus/tests/config/plugins_uniquename/__init__.py create mode 100644 circus/tests/config/plugins_uniquename/my_plugin.py create mode 100644 circus/tests/test_plugins.py diff --git a/circus/arbiter.py b/circus/arbiter.py index 115d04344..e8dade831 100644 --- a/circus/arbiter.py +++ b/circus/arbiter.py @@ -20,7 +20,7 @@ from circus.util import DictDiffer, synchronized, tornado_sleep, papa from circus.util import IS_WINDOWS from circus.config import get_config -from circus.plugins import get_plugin_cmd +from circus.plugins import CircusPlugin, get_plugin_cmd from circus.sockets import CircusSocket, CircusSockets @@ -429,6 +429,10 @@ def load_from_config(cls, config_file, loop=None): for socket_ in cfg.get('sockets', []): sockets.append(CircusSocket.load_from_config(socket_)) + plugins = [] + for plugin in cfg.get('plugins', []): + plugins.append(CircusPlugin.load_from_config(plugin)) + httpd = cfg.get('httpd', False) if httpd: # controlling that we have what it takes to run the web UI @@ -446,7 +450,9 @@ def load_from_config(cls, config_file, loop=None): statsd=cfg.get('statsd', False), stats_endpoint=cfg.get('stats_endpoint'), multicast_endpoint=cfg.get('multicast_endpoint'), - plugins=cfg.get('plugins'), sockets=sockets, + plugins=plugins, + # plugins=cfg.get('plugins'), + sockets=sockets, warmup_delay=cfg.get('warmup_delay', 0), httpd=httpd, loop=loop, diff --git a/circus/config.py b/circus/config.py index 095b550ed..18bb2811e 100644 --- a/circus/config.py +++ b/circus/config.py @@ -47,6 +47,15 @@ def watcher_defaults(): 'use_papa': False} +def plugin_defaults(): + return { + 'name': '', + 'stderr_stream': dict(), + 'stdout_stream': dict(), + 'priority': 0, + } + + class DefaultConfigParser(StrictConfigParser): def __init__(self, *args, **kw): @@ -201,10 +210,25 @@ def get_config(config_file): sockets.append(sock) if section.startswith("plugin:"): - plugin = section_items - plugin['name'] = section - if 'priority' in plugin: - plugin['priority'] = int(plugin['priority']) + # plugin = section_items + plugin = plugin_defaults() + plugin.update(section_items) + plugin['name'] = section.split('plugin:', 1)[1] + + # create watcher options + for opt, val in cfg.items(section, noreplace=True): + if opt == 'priority': + plugin['priority'] = int(plugin['priority']) + elif opt.startswith('stderr_stream') or \ + opt.startswith('stdout_stream'): + stream_name, stream_opt = opt.split(".", 1) + plugin[stream_name][stream_opt] = val + + if plugin.get('copy_env'): + plugin['env'] = dict(global_env) + else: + plugin['env'] = dict(local_env) + plugins.append(plugin) if section.startswith("watcher:"): diff --git a/circus/plugins/__init__.py b/circus/plugins/__init__.py index 9423a7e52..dc197137e 100644 --- a/circus/plugins/__init__.py +++ b/circus/plugins/__init__.py @@ -1,9 +1,12 @@ """ Base class to create Circus subscribers plugins. """ -import sys +import argparse +import copy import errno +import os +import site +import sys import uuid -import argparse import zmq import zmq.utils.jsonapi as json @@ -12,9 +15,10 @@ from circus import logger, __version__ from circus.client import make_message, cast_message from circus.py3compat import b, s +from circus.stream import get_stream from circus.util import (debuglog, to_bool, resolve_name, configure_logger, DEFAULT_ENDPOINT_DEALER, DEFAULT_ENDPOINT_SUB, - get_connection) + get_connection, get_python_version, parse_env_dict) class CircusPlugin(object): @@ -30,7 +34,8 @@ class CircusPlugin(object): """ name = '' - def __init__(self, endpoint, pubsub_endpoint, check_delay, ssh_server=None, + def __init__(self, endpoint, pubsub_endpoint, check_delay, + ssh_server=None, stdout_stream=None, stderr_stream=None, **config): self.daemon = True self.config = config @@ -41,6 +46,12 @@ def __init__(self, endpoint, pubsub_endpoint, check_delay, ssh_server=None, self.ssh_server = ssh_server self._id = b(uuid.uuid4().hex) self.running = False + + self.stdout_stream_conf = copy.copy(stdout_stream) + self.stderr_stream_conf = copy.copy(stderr_stream) + self.stdout_stream = get_stream(self.stdout_stream_conf) + self.stderr_stream = get_stream(self.stderr_stream_conf) + self.loop = ioloop.IOLoop() @debuglog @@ -157,31 +168,28 @@ def split_data(data): def load_message(msg): return json.loads(msg) + @classmethod + def load_from_config(cls, config): + if 'env' in config: + config['env'] = parse_env_dict(config['env']) + return config + def _cfg2str(cfg): - return ':::'.join(['%s:%s' % (key, val) for key, val in cfg.items()]) + json_cfg = json.dumps(cfg, separators=(',', ':')) + if get_python_version() < (3, 0, 0): + return json_cfg.encode('unicode-escape').replace(b'"', b'\\"') + else: + return json_cfg.encode('string-escape').replace('"', '\\"') def _str2cfg(data): - cfg = {} - if data is None: - return cfg - - for item in data.split(':::'): - item = item.split(':', 1) - if len(item) != 2: - continue - key, value = item - cfg[key.strip()] = value.strip() - - return cfg + return json.loads(data) def get_plugin_cmd(config, endpoint, pubsub, check_delay, ssh_server, debug=False, loglevel=None, logoutput=None): fqn = config['use'] - # makes sure the name exists - resolve_name(fqn) # we're good, serializing the config del config['use'] @@ -245,6 +253,16 @@ def main(): parser.print_usage() sys.exit(0) + cfg = _str2cfg(args.config) + + # load directories in PYTHONPATH if provided + # so if a hook is there, it can be loaded + if 'env' in cfg and 'PYTHONPATH' in cfg['env']: + for path in cfg['env']['PYTHONPATH'].split(os.pathsep): + if path in sys.path: + continue + site.addsitedir(path) + factory = resolve_name(args.plugin) # configure the logger @@ -256,7 +274,7 @@ def main(): logger.info('Pub/sub: %r' % args.pubsub) plugin = factory(args.endpoint, args.pubsub, args.check_delay, args.ssh, - **_str2cfg(args.config)) + **cfg) logger.info('Starting') try: plugin.start() diff --git a/circus/tests/config/find_plugin_in_pythonpath.ini b/circus/tests/config/find_plugin_in_pythonpath.ini new file mode 100644 index 000000000..91e75c88b --- /dev/null +++ b/circus/tests/config/find_plugin_in_pythonpath.ini @@ -0,0 +1,9 @@ +[plugin:relative_plugin] +priority = 20 +use = plugins_uniquename.my_plugin.MyPlugin +stdout_stream.class = StdoutStream +stderr_stream.class = StdoutStream +# copy_env = True + +[env:relative_plugin] +PYTHONPATH = $PWD/circus/tests/config diff --git a/circus/tests/config/plugins_uniquename/__init__.py b/circus/tests/config/plugins_uniquename/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/circus/tests/config/plugins_uniquename/my_plugin.py b/circus/tests/config/plugins_uniquename/my_plugin.py new file mode 100644 index 000000000..c7db1bf1a --- /dev/null +++ b/circus/tests/config/plugins_uniquename/my_plugin.py @@ -0,0 +1,7 @@ +from circus.tests.test_arbiter import EventLoggingTestPlugin + + +# Plugin is the same as the EventLoggingTestPlugin, +# just in a directory outside of the circus modules. +class MyPlugin(EventLoggingTestPlugin): + pass diff --git a/circus/tests/test_arbiter.py b/circus/tests/test_arbiter.py index 3e890648b..8b5ab86d8 100644 --- a/circus/tests/test_arbiter.py +++ b/circus/tests/test_arbiter.py @@ -18,7 +18,7 @@ EasyTestSuite, skipIf, get_ioloop, SLEEP, PYTHON) from circus.util import (DEFAULT_ENDPOINT_DEALER, DEFAULT_ENDPOINT_MULTICAST, - DEFAULT_ENDPOINT_SUB) + DEFAULT_ENDPOINT_SUB, parse_env_dict) from circus.tests.support import (MockWatcher, has_circusweb, poll_for_callable, get_available_port) from circus import watcher as watcher_mod @@ -28,11 +28,12 @@ _GENERIC = os.path.join(os.path.dirname(__file__), 'generic.py') -class Plugin(CircusPlugin): +class EventLoggingTestPlugin(CircusPlugin): name = 'dummy' def __init__(self, *args, **kwargs): - super(Plugin, self).__init__(*args, **kwargs) + super(EventLoggingTestPlugin, self).__init__(*args, **kwargs) + self.name = kwargs.get('name') with open(self.config['file'], 'a+') as f: f.write('PLUGIN STARTED') @@ -492,10 +493,9 @@ def test_stop_watchers3(self): @tornado.testing.gen_test def test_plugins(self): fd, datafile = mkstemp() - os.close(fd) # setting up a circusd with a plugin - plugin = 'circus.tests.test_arbiter.Plugin' + plugin = 'circus.tests.test_arbiter.EventLoggingTestPlugin' plugins = [{'use': plugin, 'file': datafile}] yield self.start_arbiter(graceful_timeout=0, plugins=plugins, @@ -531,6 +531,53 @@ def incr_processes(cli): os.remove(datafile) yield self.stop_arbiter() + @tornado.testing.gen_test + def test_relative_plugin(self): + fd, datafile = mkstemp() + + # setting up a circusd with a plugin + plugin = 'plugins_uniquename.my_plugin.MyPlugin' + plugins = [{ + 'use': plugin, + 'file': datafile, + 'env': parse_env_dict({ + 'PYTHONPATH': '$PWD/circus/tests/config' + }), + }] + + yield self.start_arbiter(graceful_timeout=0, plugins=plugins, + loop=get_ioloop()) + + def incr_processes(cli): + return cli.send_message('incr', name='test') + + # wait for the plugin to be started + res = yield async_poll_for(datafile, 'PLUGIN STARTED') + self.assertTrue(res) + + cli = AsyncCircusClient(endpoint=self.arbiter.endpoint) + + res = yield cli.send_message('list', name='test') + self.assertEqual(len(res.get('pids')), 1) + + incr_processes(cli) + res = yield cli.send_message('list', name='test') + self.assertEqual(len(res.get('pids')), 2) + # wait for the plugin to receive the signal + res = yield async_poll_for(datafile, 'test:spawn') + self.assertTrue(res) + truncate_file(datafile) + + incr_processes(cli) + res = yield cli.send_message('list', name='test') + self.assertEqual(len(res.get('pids')), 3) + + # wait for the plugin to receive the signal + res = yield async_poll_for(datafile, 'test:spawn') + self.assertTrue(res) + os.remove(datafile) + yield self.stop_arbiter() + @tornado.testing.gen_test def test_singleton(self): # yield self._stop_runners() diff --git a/circus/tests/test_config.py b/circus/tests/test_config.py index 84ecc76ac..db716ae8f 100644 --- a/circus/tests/test_config.py +++ b/circus/tests/test_config.py @@ -25,6 +25,8 @@ 'hooks': os.path.join(CONFIG_DIR, 'hooks.ini'), 'find_hook_in_pythonpath': os.path.join(CONFIG_DIR, 'find_hook_in_pythonpath.ini'), + 'find_plugin_in_pythonpath': os.path.join(CONFIG_DIR, + 'find_plugin_in_pythonpath.ini'), 'env_var': os.path.join(CONFIG_DIR, 'env_var.ini'), 'env_section': os.path.join(CONFIG_DIR, 'env_section.ini'), 'multiple_wildcard': os.path.join(CONFIG_DIR, 'multiple_wildcard.ini'), @@ -230,6 +232,12 @@ def test_watcher_env_var(self): self.assertEqual("%s:/bin" % os.getenv('PATH'), watcher.env['PATH']) watcher.stop() + def test_find_plugin_in_pythonpath(self): + # this tests that the config was loaded, NOT that the config works. + arbiter = Arbiter.load_from_config(_CONF['find_plugin_in_pythonpath']) + watchers = arbiter.iter_watchers() + self.assertEqual(watchers[0].name, 'plugin:relative_plugin') + def test_env_section(self): conf = get_config(_CONF['env_section']) watchers_conf = {} diff --git a/circus/tests/test_plugins.py b/circus/tests/test_plugins.py new file mode 100644 index 000000000..5f101cd5e --- /dev/null +++ b/circus/tests/test_plugins.py @@ -0,0 +1,30 @@ +from circus.plugins import _cfg2str, _str2cfg +from circus.util import get_python_version +from circus.tests.support import TestCase, EasyTestSuite + + +class TestPluginUtils(TestCase): + + def test_cfg_str(self): + data_obj = { + 'use': 'derp', + 'copy_env': True, + 'number': 1234, + 'env': { + 'PYTHONPATH': 'dot.path.to.whereever', + 'more_truth': True, + }, + } + data_strung = _cfg2str(data_obj) + + # need to decode, like what would happen automatically when + # passing an arg into and out of the commandline + if get_python_version() < (3, 0, 0): + return data_strung.decode('unicode-escape') + else: + return data_strung.decode('string-escape') + data_unstrung = _str2cfg(data_strung) + self.assertEqual(data_obj, data_unstrung) + + +test_suite = EasyTestSuite(__name__) diff --git a/docs/source/for-devs/writing-plugins.rst b/docs/source/for-devs/writing-plugins.rst index b0f701578..79d747cb9 100644 --- a/docs/source/for-devs/writing-plugins.rst +++ b/docs/source/for-devs/writing-plugins.rst @@ -91,6 +91,21 @@ by Python. For example, :class:`Logger` may be found in a *plugins* module within a *myproject* package. +If you'd like to use a plugin that is defined in a relative python module +(as opposed to a globally installed module) then you need to define the +PYTHONPATH in *[env:pluginname]*. + +.. code-block:: ini + + [plugin:foo] + use = 'myplugin.MyPlugin' + + [env:foo] + PYTHONPATH = $PYTHONPATH:$PWD + +You can use environment variables like *$PWD* in the *PYTHONPATH*. + + Async requests -------------- From 7b20a3f962c939ef8532f76bfacf6fee61e75e70 Mon Sep 17 00:00:00 2001 From: Peter Conerly Date: Sat, 12 Nov 2016 14:22:31 -0800 Subject: [PATCH 2/9] Fix tests --- circus/config.py | 2 +- circus/plugins/__init__.py | 7 ++++--- circus/tests/test_plugins.py | 2 -- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/circus/config.py b/circus/config.py index 18bb2811e..b0e885f9a 100644 --- a/circus/config.py +++ b/circus/config.py @@ -213,7 +213,7 @@ def get_config(config_file): # plugin = section_items plugin = plugin_defaults() plugin.update(section_items) - plugin['name'] = section.split('plugin:', 1)[1] + plugin['name'] = section # create watcher options for opt, val in cfg.items(section, noreplace=True): diff --git a/circus/plugins/__init__.py b/circus/plugins/__init__.py index dc197137e..4f075708f 100644 --- a/circus/plugins/__init__.py +++ b/circus/plugins/__init__.py @@ -178,9 +178,10 @@ def load_from_config(cls, config): def _cfg2str(cfg): json_cfg = json.dumps(cfg, separators=(',', ':')) if get_python_version() < (3, 0, 0): - return json_cfg.encode('unicode-escape').replace(b'"', b'\\"') + return json_cfg.encode('unicode-escape') else: - return json_cfg.encode('string-escape').replace('"', '\\"') + # zmq in py3 returns bytes + return json_cfg.decode("utf-8") def _str2cfg(data): @@ -200,7 +201,7 @@ def get_plugin_cmd(config, endpoint, pubsub, check_delay, ssh_server, if ssh_server is not None: cmd += ' --ssh %s' % ssh_server if len(config) > 0: - cmd += ' --config %s' % config + cmd += ' --config %r' % config if debug: cmd += ' --log-level DEBUG' elif loglevel: diff --git a/circus/tests/test_plugins.py b/circus/tests/test_plugins.py index 5f101cd5e..6116505fb 100644 --- a/circus/tests/test_plugins.py +++ b/circus/tests/test_plugins.py @@ -21,8 +21,6 @@ def test_cfg_str(self): # passing an arg into and out of the commandline if get_python_version() < (3, 0, 0): return data_strung.decode('unicode-escape') - else: - return data_strung.decode('string-escape') data_unstrung = _str2cfg(data_strung) self.assertEqual(data_obj, data_unstrung) From 3b3399c533d3807d2c7c7e7dada5dc29db2c1471 Mon Sep 17 00:00:00 2001 From: Peter Conerly Date: Tue, 15 Nov 2016 16:27:30 -0800 Subject: [PATCH 3/9] Close FD if they're not needed --- circus/tests/test_arbiter.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/circus/tests/test_arbiter.py b/circus/tests/test_arbiter.py index 8b5ab86d8..4bd81882d 100644 --- a/circus/tests/test_arbiter.py +++ b/circus/tests/test_arbiter.py @@ -493,6 +493,7 @@ def test_stop_watchers3(self): @tornado.testing.gen_test def test_plugins(self): fd, datafile = mkstemp() + os.close(fd) # setting up a circusd with a plugin plugin = 'circus.tests.test_arbiter.EventLoggingTestPlugin' @@ -534,6 +535,7 @@ def incr_processes(cli): @tornado.testing.gen_test def test_relative_plugin(self): fd, datafile = mkstemp() + os.close(fd) # setting up a circusd with a plugin plugin = 'plugins_uniquename.my_plugin.MyPlugin' From 9879cb60175cb8cf4287daa9d3a9cb297b83cb11 Mon Sep 17 00:00:00 2001 From: Peter Conerly Date: Tue, 15 Nov 2016 16:27:43 -0800 Subject: [PATCH 4/9] Tear down arbiter in this test --- circus/tests/test_reloadconfig.py | 1 + 1 file changed, 1 insertion(+) diff --git a/circus/tests/test_reloadconfig.py b/circus/tests/test_reloadconfig.py index fe0c5bbef..730d1afa3 100644 --- a/circus/tests/test_reloadconfig.py +++ b/circus/tests/test_reloadconfig.py @@ -168,5 +168,6 @@ def test_reload_ignorearbiterwatchers(self): statsd = a.get_watcher('circusd-stats') yield a.reload_from_config(_CONF['reload_statsd']) self.assertEqual(statsd, a.get_watcher('circusd-stats')) + yield self._tear_down_arbiter(a) test_suite = EasyTestSuite(__name__) From dd8d0ec5a97096f448417a4f4f877910062dff12 Mon Sep 17 00:00:00 2001 From: Peter Conerly Date: Tue, 15 Nov 2016 16:38:06 -0800 Subject: [PATCH 5/9] Add Peter Conerly to contributors --- CONTRIBUTORS.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt index 000a753d9..d67baa5dd 100644 --- a/CONTRIBUTORS.txt +++ b/CONTRIBUTORS.txt @@ -90,3 +90,4 @@ List of contributors: - Mike Dunn - Yannick PEROUX - David Douard +- Peter Conerly From ac47356e78fb660292f61a2ee89a78e7e1f25028 Mon Sep 17 00:00:00 2001 From: Peter Conerly Date: Tue, 9 Jan 2018 00:46:58 -0800 Subject: [PATCH 6/9] Fix merge --- circus/arbiter.py | 1 - circus/plugins/__init__.py | 11 +---------- circus/tests/test_plugins.py | 8 ++++---- 3 files changed, 5 insertions(+), 15 deletions(-) diff --git a/circus/arbiter.py b/circus/arbiter.py index 6db2802cf..c78c3d3f2 100644 --- a/circus/arbiter.py +++ b/circus/arbiter.py @@ -450,7 +450,6 @@ def load_from_config(cls, config_file, loop=None): stats_endpoint=cfg.get('stats_endpoint'), multicast_endpoint=cfg.get('multicast_endpoint'), plugins=plugins, - # plugins=cfg.get('plugins'), sockets=sockets, warmup_delay=cfg.get('warmup_delay', 0), httpd=httpd, diff --git a/circus/plugins/__init__.py b/circus/plugins/__init__.py index 1e8162c76..9d7d2f9e1 100644 --- a/circus/plugins/__init__.py +++ b/circus/plugins/__init__.py @@ -174,8 +174,7 @@ def load_from_config(cls, config): config['env'] = parse_env_dict(config['env']) return config - -def DERP_cfg2str(cfg): +def _cfg2str(cfg): json_cfg = json.dumps(cfg, separators=(',', ':')) if get_python_version() < (3, 0, 0): return json_cfg.encode('unicode-escape') @@ -183,17 +182,9 @@ def DERP_cfg2str(cfg): # zmq in py3 returns bytes return json_cfg.decode("utf-8") - -def _cfg2str(cfg): - return ':::'.join([ - '%s:%s' % (key, val) for key, val in sorted(cfg.items()) - ]) - - def _str2cfg(data): return json.loads(data) - def get_plugin_cmd(config, endpoint, pubsub, check_delay, ssh_server, debug=False, loglevel=None, logoutput=None): fqn = config['use'] diff --git a/circus/tests/test_plugins.py b/circus/tests/test_plugins.py index 6116505fb..717a2fa61 100644 --- a/circus/tests/test_plugins.py +++ b/circus/tests/test_plugins.py @@ -15,14 +15,14 @@ def test_cfg_str(self): 'more_truth': True, }, } - data_strung = _cfg2str(data_obj) + data_string = _cfg2str(data_obj) # need to decode, like what would happen automatically when # passing an arg into and out of the commandline if get_python_version() < (3, 0, 0): - return data_strung.decode('unicode-escape') - data_unstrung = _str2cfg(data_strung) - self.assertEqual(data_obj, data_unstrung) + return data_string.decode('unicode-escape') + data_unstrung = _str2cfg(data_string) + self.assertEqual(data_obj, data_string) test_suite = EasyTestSuite(__name__) From b3ebc9699ed196757d7e4e2013537982fe8f744e Mon Sep 17 00:00:00 2001 From: Peter Conerly Date: Tue, 9 Jan 2018 00:52:01 -0800 Subject: [PATCH 7/9] self-review --- circus/config.py | 1 - 1 file changed, 1 deletion(-) diff --git a/circus/config.py b/circus/config.py index c4e57f43c..d4d2954a2 100644 --- a/circus/config.py +++ b/circus/config.py @@ -212,7 +212,6 @@ def get_config(config_file): sockets.append(sock) if section.startswith("plugin:"): - # plugin = section_items plugin = plugin_defaults() plugin.update(section_items) plugin['name'] = section From 0c71666d75328fb520146d6860433f40f4d226ee Mon Sep 17 00:00:00 2001 From: Peter Conerly Date: Tue, 9 Jan 2018 01:31:05 -0800 Subject: [PATCH 8/9] Fix test_cfg_str --- circus/tests/test_plugins.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/circus/tests/test_plugins.py b/circus/tests/test_plugins.py index 717a2fa61..30d935374 100644 --- a/circus/tests/test_plugins.py +++ b/circus/tests/test_plugins.py @@ -20,9 +20,9 @@ def test_cfg_str(self): # need to decode, like what would happen automatically when # passing an arg into and out of the commandline if get_python_version() < (3, 0, 0): - return data_string.decode('unicode-escape') + data_string = data_string.decode('unicode-escape') data_unstrung = _str2cfg(data_string) - self.assertEqual(data_obj, data_string) + self.assertEqual(data_obj, data_unstrung) test_suite = EasyTestSuite(__name__) From 0b282ba18f4c5ae9dfaf626cd5e629bd8f2c7317 Mon Sep 17 00:00:00 2001 From: Peter Conerly Date: Tue, 9 Jan 2018 01:54:58 -0800 Subject: [PATCH 9/9] Flake --- circus/plugins/__init__.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/circus/plugins/__init__.py b/circus/plugins/__init__.py index 9d7d2f9e1..4f075708f 100644 --- a/circus/plugins/__init__.py +++ b/circus/plugins/__init__.py @@ -174,6 +174,7 @@ def load_from_config(cls, config): config['env'] = parse_env_dict(config['env']) return config + def _cfg2str(cfg): json_cfg = json.dumps(cfg, separators=(',', ':')) if get_python_version() < (3, 0, 0): @@ -182,9 +183,11 @@ def _cfg2str(cfg): # zmq in py3 returns bytes return json_cfg.decode("utf-8") + def _str2cfg(data): return json.loads(data) + def get_plugin_cmd(config, endpoint, pubsub, check_delay, ssh_server, debug=False, loglevel=None, logoutput=None): fqn = config['use']