Skip to content

Commit

Permalink
Merge pull request #376 from davidszotten/refactor_commands
Browse files Browse the repository at this point in the history
refactor cli commands to avoid eventlet.monkey_patch
  • Loading branch information
davidszotten committed Nov 6, 2016
2 parents 608cc5b + b2f2d47 commit d5e34aa
Show file tree
Hide file tree
Showing 9 changed files with 145 additions and 88 deletions.
16 changes: 0 additions & 16 deletions nameko/cli/backdoor.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,9 @@
"""Connect to a nameko backdoor.
If a backdoor is running this will connect to a remote shell. The
runner is generally available as `runner`.
"""
from __future__ import print_function

import os
from subprocess import call

from nameko.exceptions import CommandError
from .actions import FlagAction


def main(args):
Expand Down Expand Up @@ -44,13 +38,3 @@ def main(args):
print()
if choice == 'telnet' and rlwrap:
call(['reset'])


def init_parser(parser):
parser.add_argument(
'target', metavar='[host:]port', help="(host and) port to connect to")
parser.add_argument(
'--rlwrap', dest='rlwrap', action=FlagAction,
help='Use rlwrap')
parser.set_defaults(feature=True)
return parser
120 changes: 120 additions & 0 deletions nameko/cli/commands.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
"""Commands are defined in here, with imports inline, to avoid triggering
imports from other subcommands (e.g. `run` will cause an eventlet monkey-patch
which we don't want for `shell`)
"""
from .actions import FlagAction


class Command(object):
name = None

@staticmethod
def init_parser(parser):
raise NotImplementedError # pragma: no cover

@staticmethod
def main(args):
# import inline to avoid triggering imports from other subcommands
raise NotImplementedError # pragma: no cover


class Backdoor(Command):
"""Connect to a nameko backdoor.
If a backdoor is running this will connect to a remote shell. The
runner is generally available as `runner`.
"""

name = 'backdoor'

@staticmethod
def init_parser(parser):
parser.add_argument(
'target', metavar='[host:]port',
help="(host and) port to connect to",
)
parser.add_argument(
'--rlwrap', dest='rlwrap', action=FlagAction,
help='Use rlwrap')
parser.set_defaults(feature=True)
return parser

@staticmethod
def main(args):
from .backdoor import main
main(args)


class Run(Command):
"""Run nameko services. Given a python path to a module containing one or
more nameko services, will host and run them. By default this will try to
find classes that look like services (anything with nameko entrypoints),
but a specific service can be specified via
``nameko run module:ServiceClass``.
"""

name = 'run'

@staticmethod
def init_parser(parser):
parser.add_argument(
'services', nargs='+',
metavar='module[:service class]',
help='python path to one or more service classes to run')

parser.add_argument(
'--config', default='',
help='The YAML configuration file')

parser.add_argument(
'--broker', default='amqp://guest:guest@localhost',
help='RabbitMQ broker url')

parser.add_argument(
'--backdoor-port', type=int,
help='Specify a port number to host a backdoor, which can be'
' connected to for an interactive interpreter within the running'
' service process using `nameko backdoor`.')

return parser

@staticmethod
def main(args):
from .run import main
main(args)


class Shell(Command):
"""Launch an interactive python shell for working with remote nameko
services.
This is a regular interactive interpreter, with a special module ``n``
added to the built-in namespace, providing ``n.rpc`` and
``n.dispatch_event``.
"""

name = 'shell'

SHELLS = ['bpython', 'ipython', 'plain']

@classmethod
def init_parser(cls, parser):
parser.add_argument(
'--broker', default='amqp://guest:guest@localhost',
help='RabbitMQ broker url')
parser.add_argument(
'--interface', choices=cls.SHELLS,
help='Specify an interactive interpreter interface.')
parser.add_argument(
'--config', default='',
help='The YAML configuration file')
return parser

@staticmethod
def main(args):
from .shell import main
main(args)


commands = Command.__subclasses__() # pylint: disable=E1101
13 changes: 6 additions & 7 deletions nameko/cli/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import yaml

from nameko.exceptions import CommandError, ConfigurationError
from . import backdoor, run, shell
from . import commands

ENV_VAR_MATCHER = re.compile(
r"""
Expand All @@ -31,12 +31,11 @@ def setup_parser():
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers()

for module in [backdoor, run, shell]:
name = module.__name__.split('.')[-1]
module_parser = subparsers.add_parser(
name, description=module.__doc__)
module.init_parser(module_parser)
module_parser.set_defaults(main=module.main)
for command in commands.commands:
command_parser = subparsers.add_parser(
command.name, description=command.__doc__)
command.init_parser(command_parser)
command_parser.set_defaults(main=command.main)
return parser


Expand Down
28 changes: 0 additions & 28 deletions nameko/cli/run.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,3 @@
"""Run nameko services. Given a python path to a module containing one or more
nameko services, will host and run them. By default this will try to find
classes that look like services (anything with nameko entrypoints), but a
specific service can be specified via ``nameko run module:ServiceClass``. """

from __future__ import print_function

import eventlet
Expand Down Expand Up @@ -183,26 +178,3 @@ def main(args):
)

run(services, config, backdoor_port=args.backdoor_port)


def init_parser(parser):
parser.add_argument(
'services', nargs='+',
metavar='module[:service class]',
help='python path to one or more service classes to run')

parser.add_argument(
'--config', default='',
help='The YAML configuration file')

parser.add_argument(
'--broker', default='amqp://guest:guest@localhost',
help='RabbitMQ broker url')

parser.add_argument(
'--backdoor-port', type=int,
help='Specify a port number to host a backdoor, which can be connected'
' to for an interactive interpreter within the running service'
' process using `nameko backdoor`.')

return parser
23 changes: 2 additions & 21 deletions nameko/cli/shell.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,3 @@
"""Launch an interactive python shell for working with remote nameko services.
This is a regular interactive interpreter, with a special module ``n`` added
to the built-in namespace, providing ``n.rpc`` and ``n.dispatch_event``.
"""
import code
import os
import sys
Expand All @@ -13,8 +8,7 @@
from nameko.standalone.rpc import ClusterRpcProxy
from nameko.standalone.events import event_dispatcher


SHELLS = ['bpython', 'ipython', 'plain']
from .commands import Shell


class ShellRunner(object):
Expand All @@ -35,7 +29,7 @@ def plain(self):
code.interact(banner=self.banner, local=self.local)

def start_shell(self, name):
available_shells = [name] if name else SHELLS
available_shells = [name] if name else Shell.SHELLS

# Support the regular Python interpreter startup script if someone
# is using it.
Expand All @@ -53,19 +47,6 @@ def start_shell(self, name):
self.plain()


def init_parser(parser):
parser.add_argument(
'--broker', default='amqp://guest:guest@localhost',
help='RabbitMQ broker url')
parser.add_argument(
'--interface', choices=SHELLS,
help='Specify an interactive interpreter interface.')
parser.add_argument(
'--config', default='',
help='The YAML configuration file')
return parser


def make_nameko_helper(config):
"""Create a fake module that provides some convenient access to nameko
standalone functionality for interactive shell usage.
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
packages=find_packages(exclude=['test', 'test.*']),
install_requires=[
"eventlet>=0.16.1",
"kombu>=3.0.1",
"kombu>=3.0.1,<4",
"mock>=1.2",
"path.py>=6.2",
"pyyaml>=3.10",
Expand Down
12 changes: 6 additions & 6 deletions test/cli/test_backdoor.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from mock import patch, DEFAULT
import pytest

from nameko.cli.backdoor import main
from nameko.cli.commands import Backdoor
from nameko.cli.main import setup_parser
from nameko.cli.run import setup_backdoor
from nameko.exceptions import CommandError
Expand All @@ -23,7 +23,7 @@ def test_no_telnet():
with patch('nameko.cli.backdoor.os') as mock_os:
mock_os.system.return_value = -1
with pytest.raises(CommandError) as exc:
main(args)
Backdoor.main(args)
assert 'Could not find an installed telnet' in str(exc)


Expand All @@ -36,7 +36,7 @@ def test_no_running_backdoor():
mocks['os'].system.return_value = 0
mocks['call'].return_value = -1
with pytest.raises(CommandError) as exc:
main(args)
Backdoor.main(args)
assert 'Backdoor unreachable' in str(exc)


Expand All @@ -50,7 +50,7 @@ def test_basic(running_backdoor):
mock_call = mocks['call']
mocks['os'].system.return_value = 0
mock_call.return_value = 0
main(args)
Backdoor.main(args)
(cmd, ), _ = mock_call.call_args
expected = (
['rlwrap', 'netcat'] +
Expand All @@ -70,7 +70,7 @@ def test_default_host(running_backdoor):
mock_call = mocks['call']
mocks['os'].system.return_value = 0
mock_call.return_value = 0
main(args)
Backdoor.main(args)
(cmd, ), _ = mock_call.call_args
expected = ['rlwrap', 'netcat', 'localhost'] + [str(port)] + ['--close']
assert cmd == expected
Expand All @@ -86,4 +86,4 @@ def test_stop(running_backdoor):
# choose telnet (skip nc and netcat) and find rlwrap
mocks['os'].system.side_effect = [-1, -1, 0, 0]
mocks['call'].side_effect = [KeyboardInterrupt, 0]
main(args)
Backdoor.main(args)
4 changes: 2 additions & 2 deletions test/cli/test_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ def fake_argv():


def test_run():
with patch('nameko.cli.main.run.main') as run:
with patch('nameko.cli.run.main') as run:
main()
assert run.call_count == 1
(args,), _ = run.call_args
Expand All @@ -35,7 +35,7 @@ def test_run():

@pytest.mark.parametrize('exception', (CommandError, ConfigurationError))
def test_error(exception, capsys):
with patch('nameko.cli.main.run.main') as run:
with patch('nameko.cli.run.main') as run:
run.side_effect = exception('boom')
main()
out, _ = capsys.readouterr()
Expand Down

0 comments on commit d5e34aa

Please sign in to comment.