From affbc86cdff15e1c5b83af2487031ee211142d01 Mon Sep 17 00:00:00 2001 From: Jesus Cabrera Date: Tue, 8 Sep 2020 18:55:23 -0400 Subject: [PATCH 01/13] Add type-name and language commands to cfn init --- src/rpdk/core/init.py | 46 ++++++++++++++---- src/rpdk/core/plugin_registry.py | 7 +++ tests/test_init.py | 81 ++++++++++++++++++++++++++++++-- 3 files changed, 119 insertions(+), 15 deletions(-) diff --git a/src/rpdk/core/init.py b/src/rpdk/core/init.py index 28bca5d4..82dc97b0 100644 --- a/src/rpdk/core/init.py +++ b/src/rpdk/core/init.py @@ -2,13 +2,12 @@ """ import logging import re -from argparse import SUPPRESS from functools import wraps from colorama import Fore, Style from .exceptions import WizardAbortError, WizardValidationError -from .plugin_registry import PLUGIN_CHOICES +from .plugin_registry import get_plugin_choices from .project import Project LOG = logging.getLogger(__name__) @@ -42,7 +41,7 @@ def validate_type_name(value): return value LOG.debug("'%s' did not match '%s'", value, TYPE_NAME_REGEX) raise WizardValidationError( - "Please enter a value matching '{}'".format(TYPE_NAME_REGEX) + "Please enter a resource type name matching '{}'".format(TYPE_NAME_REGEX) ) @@ -77,7 +76,7 @@ def __call__(self, value): validate_plugin_choice = ValidatePluginChoice( # pylint: disable=invalid-name - PLUGIN_CHOICES + get_plugin_choices() ) @@ -139,10 +138,26 @@ def init(args): check_for_existing_project(project) - type_name = input_typename() + if args.type_name: + try: + type_name = validate_type_name(args.type_name) + except WizardValidationError as error: + print(Style.BRIGHT, Fore.RED, str(error), Style.RESET_ALL, sep="") + type_name = input_typename() + else: + type_name = input_typename() + if args.language: - language = args.language - LOG.warning("Language plugin '%s' selected non-interactively", language) + language = args.language.lower() + if language not in get_plugin_choices(): + print( + Style.BRIGHT, + Fore.RED, + "The plugin for {} is not installed.".format(language), + Style.RESET_ALL, + sep="", + ) + language = input_language() else: language = input_language() @@ -172,7 +187,18 @@ def setup_subparser(subparsers, parents): parser.set_defaults(command=ignore_abort(init)) parser.add_argument( - "--force", action="store_true", help="Force files to be overwritten." + "--force", + action="store_true", + help="Force files to be overwritten.", + ) + + parser.add_argument( + "--language", + help="""Select a language for code generation. + The language plugin needs to be installed.""", + ) + + parser.add_argument( + "--type-name", + help="Selects the name of the resource type.", ) - # this is mainly for CI, so suppress it to keep it simple - parser.add_argument("--language", help=SUPPRESS) diff --git a/src/rpdk/core/plugin_registry.py b/src/rpdk/core/plugin_registry.py index 391a4a0b..b9d9a5c4 100644 --- a/src/rpdk/core/plugin_registry.py +++ b/src/rpdk/core/plugin_registry.py @@ -8,5 +8,12 @@ PLUGIN_CHOICES = sorted(PLUGIN_REGISTRY.keys()) +def get_plugin_choices(): + return [ + entry_point.name + for entry_point in pkg_resources.iter_entry_points("rpdk.v1.languages") + ] + + def load_plugin(language): return PLUGIN_REGISTRY[language]()() diff --git a/tests/test_init.py b/tests/test_init.py index b79c098d..cf341e8c 100644 --- a/tests/test_init.py +++ b/tests/test_init.py @@ -1,6 +1,7 @@ from pathlib import Path from unittest.mock import ANY, Mock, PropertyMock, patch +import pkg_resources import pytest from rpdk.core.exceptions import WizardAbortError, WizardValidationError @@ -21,13 +22,25 @@ ERROR = "TUJFEL" -def test_init_method_interactive_language(): +def add_dummy_language_plugin(): + distribution = pkg_resources.Distribution(__file__) + entry_point = pkg_resources.EntryPoint.parse( + "dummy = rpdk.dummy:DummyLanguagePlugin", dist=distribution + ) + distribution._ep_map = { # pylint: disable=protected-access + "rpdk.v1.languages": {"dummy": entry_point} + } + pkg_resources.working_set.add(distribution) + + +def test_init_method_interactive(): type_name = object() language = object() - args = Mock(spec_set=["force", "language"]) + args = Mock(spec_set=["force", "language", "type_name"]) args.force = False args.language = None + args.type_name = None mock_project = Mock(spec=Project) mock_project.load_settings.side_effect = FileNotFoundError @@ -49,12 +62,42 @@ def test_init_method_interactive_language(): mock_project.generate.assert_called_once_with() -def test_init_method_noninteractive_language(): +def test_init_method_noninteractive(): + add_dummy_language_plugin() + + args = Mock(spec_set=["force", "language", "type_name"]) + args.force = False + args.language = "dummy" + args.type_name = "Test::Test::Test" + + mock_project = Mock(spec=Project) + mock_project.load_settings.side_effect = FileNotFoundError + mock_project.settings_path = "" + mock_project.root = Path(".") + + patch_project = patch("rpdk.core.init.Project", return_value=mock_project) + patch_tn = patch("rpdk.core.init.input_typename") + patch_l = patch("rpdk.core.init.input_language") + + with patch_project, patch_tn as mock_tn, patch_l as mock_l: + init(args) + + mock_tn.assert_not_called() + mock_l.assert_not_called() + + mock_project.load_settings.assert_called_once_with() + mock_project.init.assert_called_once_with(args.type_name, args.language) + mock_project.generate.assert_called_once_with() + + +def test_init_method_noninteractive_invalid_type_name(): + add_dummy_language_plugin() type_name = object() - args = Mock(spec_set=["force", "language"]) + args = Mock(spec_set=["force", "language", "type_name"]) args.force = False - args.language = "rust1.39" + args.language = "dummy" + args.type_name = "Test" mock_project = Mock(spec=Project) mock_project.load_settings.side_effect = FileNotFoundError @@ -76,6 +119,34 @@ def test_init_method_noninteractive_language(): mock_project.generate.assert_called_once_with() +def test_init_method_noninteractive_invalid_language(): + language = object() + + args = Mock(spec_set=["force", "language", "type_name"]) + args.force = False + args.language = "fortran" + args.type_name = "Test::Test::Test" + + mock_project = Mock(spec=Project) + mock_project.load_settings.side_effect = FileNotFoundError + mock_project.settings_path = "" + mock_project.root = Path(".") + + patch_project = patch("rpdk.core.init.Project", return_value=mock_project) + patch_tn = patch("rpdk.core.init.input_typename") + patch_l = patch("rpdk.core.init.input_language", return_value=language) + + with patch_project, patch_tn as mock_tn, patch_l as mock_l: + init(args) + + mock_tn.assert_not_called() + mock_l.assert_called_once_with() + + mock_project.load_settings.assert_called_once_with() + mock_project.init.assert_called_once_with(args.type_name, language) + mock_project.generate.assert_called_once_with() + + def test_input_with_validation_valid_first_try(capsys): sentinel1 = object() sentinel2 = object() From a82e90fb5725bca9d00e74768fb847f828facec6 Mon Sep 17 00:00:00 2001 From: Jesus Cabrera Date: Wed, 9 Sep 2020 19:03:03 -0400 Subject: [PATCH 02/13] Add use_docker flag to cfn init --- src/rpdk/core/init.py | 14 ++++++- tests/test_init.py | 87 ++++++++++++++++++++++--------------------- 2 files changed, 57 insertions(+), 44 deletions(-) diff --git a/src/rpdk/core/init.py b/src/rpdk/core/init.py index 82dc97b0..1a98c9ca 100644 --- a/src/rpdk/core/init.py +++ b/src/rpdk/core/init.py @@ -161,7 +161,11 @@ def init(args): else: language = input_language() - project.init(type_name, language) + project.init( + type_name, + language, + {"use_docker": args.use_docker}, + ) project.generate() project.generate_docs() @@ -202,3 +206,11 @@ def setup_subparser(subparsers, parents): "--type-name", help="Selects the name of the resource type.", ) + + parser.add_argument( + "--use-docker", + action="store_true", + help="""Use docker for python platform-independent packaging. + This is highly recommended unless you are experienced + with cross-platform Python packaging.""", + ) diff --git a/tests/test_init.py b/tests/test_init.py index cf341e8c..0dc0055a 100644 --- a/tests/test_init.py +++ b/tests/test_init.py @@ -33,21 +33,38 @@ def add_dummy_language_plugin(): pkg_resources.working_set.add(distribution) -def test_init_method_interactive(): - type_name = object() - language = object() - - args = Mock(spec_set=["force", "language", "type_name"]) - args.force = False - args.language = None - args.type_name = None - +def get_mock_project(): mock_project = Mock(spec=Project) mock_project.load_settings.side_effect = FileNotFoundError mock_project.settings_path = "" mock_project.root = Path(".") patch_project = patch("rpdk.core.init.Project", return_value=mock_project) + + return (mock_project, patch_project) + + +def get_args(interactive=False, language=True, type_name=True): + args = Mock(spec_set=["force", "language", "type_name", "use_docker"]) + args.force = False + args.language = ( + None if interactive else ("dummy" if language else "invalid_language") + ) + args.type_name = ( + None if interactive else ("Test::Test::Test" if type_name else "Test") + ) + args.use_docker = True + + return args + + +def test_init_method_interactive(): + type_name = object() + language = object() + + args = get_args(interactive=True) + mock_project, patch_project = get_mock_project() + patch_tn = patch("rpdk.core.init.input_typename", return_value=type_name) patch_l = patch("rpdk.core.init.input_language", return_value=language) @@ -58,24 +75,18 @@ def test_init_method_interactive(): mock_l.assert_called_once_with() mock_project.load_settings.assert_called_once_with() - mock_project.init.assert_called_once_with(type_name, language) + mock_project.init.assert_called_once_with( + type_name, language, {"use_docker": args.use_docker} + ) mock_project.generate.assert_called_once_with() def test_init_method_noninteractive(): add_dummy_language_plugin() - args = Mock(spec_set=["force", "language", "type_name"]) - args.force = False - args.language = "dummy" - args.type_name = "Test::Test::Test" + args = get_args() + mock_project, patch_project = get_mock_project() - mock_project = Mock(spec=Project) - mock_project.load_settings.side_effect = FileNotFoundError - mock_project.settings_path = "" - mock_project.root = Path(".") - - patch_project = patch("rpdk.core.init.Project", return_value=mock_project) patch_tn = patch("rpdk.core.init.input_typename") patch_l = patch("rpdk.core.init.input_language") @@ -86,7 +97,9 @@ def test_init_method_noninteractive(): mock_l.assert_not_called() mock_project.load_settings.assert_called_once_with() - mock_project.init.assert_called_once_with(args.type_name, args.language) + mock_project.init.assert_called_once_with( + args.type_name, args.language, {"use_docker": args.use_docker} + ) mock_project.generate.assert_called_once_with() @@ -94,17 +107,9 @@ def test_init_method_noninteractive_invalid_type_name(): add_dummy_language_plugin() type_name = object() - args = Mock(spec_set=["force", "language", "type_name"]) - args.force = False - args.language = "dummy" - args.type_name = "Test" - - mock_project = Mock(spec=Project) - mock_project.load_settings.side_effect = FileNotFoundError - mock_project.settings_path = "" - mock_project.root = Path(".") + args = get_args(type_name=False) + mock_project, patch_project = get_mock_project() - patch_project = patch("rpdk.core.init.Project", return_value=mock_project) patch_tn = patch("rpdk.core.init.input_typename", return_value=type_name) patch_l = patch("rpdk.core.init.input_language") @@ -115,24 +120,18 @@ def test_init_method_noninteractive_invalid_type_name(): mock_l.assert_not_called() mock_project.load_settings.assert_called_once_with() - mock_project.init.assert_called_once_with(type_name, args.language) + mock_project.init.assert_called_once_with( + type_name, args.language, {"use_docker": args.use_docker} + ) mock_project.generate.assert_called_once_with() def test_init_method_noninteractive_invalid_language(): language = object() - args = Mock(spec_set=["force", "language", "type_name"]) - args.force = False - args.language = "fortran" - args.type_name = "Test::Test::Test" + args = get_args(language=False) + mock_project, patch_project = get_mock_project() - mock_project = Mock(spec=Project) - mock_project.load_settings.side_effect = FileNotFoundError - mock_project.settings_path = "" - mock_project.root = Path(".") - - patch_project = patch("rpdk.core.init.Project", return_value=mock_project) patch_tn = patch("rpdk.core.init.input_typename") patch_l = patch("rpdk.core.init.input_language", return_value=language) @@ -143,7 +142,9 @@ def test_init_method_noninteractive_invalid_language(): mock_l.assert_called_once_with() mock_project.load_settings.assert_called_once_with() - mock_project.init.assert_called_once_with(args.type_name, language) + mock_project.init.assert_called_once_with( + args.type_name, language, {"use_docker": args.use_docker} + ) mock_project.generate.assert_called_once_with() From bd16519d6da5fed34396ab1fd94361130df0eeef Mon Sep 17 00:00:00 2001 From: Jesus Cabrera Date: Fri, 11 Sep 2020 12:20:24 -0400 Subject: [PATCH 03/13] Add --namespace flag for creation of Java projects --- src/rpdk/core/init.py | 12 ++++++++++-- tests/test_init.py | 19 ++++++++++++++----- 2 files changed, 24 insertions(+), 7 deletions(-) diff --git a/src/rpdk/core/init.py b/src/rpdk/core/init.py index 1a98c9ca..c665a350 100644 --- a/src/rpdk/core/init.py +++ b/src/rpdk/core/init.py @@ -164,7 +164,7 @@ def init(args): project.init( type_name, language, - {"use_docker": args.use_docker}, + {"use_docker": args.use_docker, "namespace": args.namespace}, ) project.generate() project.generate_docs() @@ -204,7 +204,7 @@ def setup_subparser(subparsers, parents): parser.add_argument( "--type-name", - help="Selects the name of the resource type.", + help="Select the name of the resource type.", ) parser.add_argument( @@ -214,3 +214,11 @@ def setup_subparser(subparsers, parents): This is highly recommended unless you are experienced with cross-platform Python packaging.""", ) + + parser.add_argument( + "--namespace", + nargs="?", + const="default", + help="""Select the name of the Java namespace. + Passing the flag without argument select the default namespace.""", + ) diff --git a/tests/test_init.py b/tests/test_init.py index 0dc0055a..716464cf 100644 --- a/tests/test_init.py +++ b/tests/test_init.py @@ -45,7 +45,7 @@ def get_mock_project(): def get_args(interactive=False, language=True, type_name=True): - args = Mock(spec_set=["force", "language", "type_name", "use_docker"]) + args = Mock(spec_set=["force", "language", "type_name", "use_docker", "namespace"]) args.force = False args.language = ( None if interactive else ("dummy" if language else "invalid_language") @@ -54,6 +54,7 @@ def get_args(interactive=False, language=True, type_name=True): None if interactive else ("Test::Test::Test" if type_name else "Test") ) args.use_docker = True + args.namespace = "tested.by.the.plugin" return args @@ -76,7 +77,9 @@ def test_init_method_interactive(): mock_project.load_settings.assert_called_once_with() mock_project.init.assert_called_once_with( - type_name, language, {"use_docker": args.use_docker} + type_name, + language, + {"use_docker": args.use_docker, "namespace": args.namespace}, ) mock_project.generate.assert_called_once_with() @@ -98,7 +101,9 @@ def test_init_method_noninteractive(): mock_project.load_settings.assert_called_once_with() mock_project.init.assert_called_once_with( - args.type_name, args.language, {"use_docker": args.use_docker} + args.type_name, + args.language, + {"use_docker": args.use_docker, "namespace": args.namespace}, ) mock_project.generate.assert_called_once_with() @@ -121,7 +126,9 @@ def test_init_method_noninteractive_invalid_type_name(): mock_project.load_settings.assert_called_once_with() mock_project.init.assert_called_once_with( - type_name, args.language, {"use_docker": args.use_docker} + type_name, + args.language, + {"use_docker": args.use_docker, "namespace": args.namespace}, ) mock_project.generate.assert_called_once_with() @@ -143,7 +150,9 @@ def test_init_method_noninteractive_invalid_language(): mock_project.load_settings.assert_called_once_with() mock_project.init.assert_called_once_with( - args.type_name, language, {"use_docker": args.use_docker} + args.type_name, + language, + {"use_docker": args.use_docker, "namespace": args.namespace}, ) mock_project.generate.assert_called_once_with() From 9df29ad17072d46349ab1ff758dae9a1a66a6e15 Mon Sep 17 00:00:00 2001 From: Jesus Cabrera Date: Fri, 11 Sep 2020 14:20:37 -0400 Subject: [PATCH 04/13] Add --codegen_model flag for creation of Java projects --- src/rpdk/core/init.py | 12 +++++++++++- tests/test_init.py | 38 ++++++++++++++++++++++++++++++++------ 2 files changed, 43 insertions(+), 7 deletions(-) diff --git a/src/rpdk/core/init.py b/src/rpdk/core/init.py index c665a350..9559cef0 100644 --- a/src/rpdk/core/init.py +++ b/src/rpdk/core/init.py @@ -164,7 +164,11 @@ def init(args): project.init( type_name, language, - {"use_docker": args.use_docker, "namespace": args.namespace}, + { + "use_docker": args.use_docker, + "namespace": args.namespace, + "codegen_template_path": args.codegen_model, + }, ) project.generate() project.generate_docs() @@ -222,3 +226,9 @@ def setup_subparser(subparsers, parents): help="""Select the name of the Java namespace. Passing the flag without argument select the default namespace.""", ) + + parser.add_argument( + "--codegen-model", + choices=["default", "guided_aws"], + help="Select a codegen model.", + ) diff --git a/tests/test_init.py b/tests/test_init.py index 716464cf..73952f99 100644 --- a/tests/test_init.py +++ b/tests/test_init.py @@ -45,7 +45,16 @@ def get_mock_project(): def get_args(interactive=False, language=True, type_name=True): - args = Mock(spec_set=["force", "language", "type_name", "use_docker", "namespace"]) + args = Mock( + spec_set=[ + "force", + "language", + "type_name", + "use_docker", + "namespace", + "codegen_model", + ] + ) args.force = False args.language = ( None if interactive else ("dummy" if language else "invalid_language") @@ -53,8 +62,9 @@ def get_args(interactive=False, language=True, type_name=True): args.type_name = ( None if interactive else ("Test::Test::Test" if type_name else "Test") ) - args.use_docker = True + args.use_docker = None args.namespace = "tested.by.the.plugin" + args.codegen_model = None return args @@ -79,7 +89,11 @@ def test_init_method_interactive(): mock_project.init.assert_called_once_with( type_name, language, - {"use_docker": args.use_docker, "namespace": args.namespace}, + { + "use_docker": args.use_docker, + "namespace": args.namespace, + "codegen_template_path": args.codegen_model, + }, ) mock_project.generate.assert_called_once_with() @@ -103,7 +117,11 @@ def test_init_method_noninteractive(): mock_project.init.assert_called_once_with( args.type_name, args.language, - {"use_docker": args.use_docker, "namespace": args.namespace}, + { + "use_docker": args.use_docker, + "namespace": args.namespace, + "codegen_template_path": args.codegen_model, + }, ) mock_project.generate.assert_called_once_with() @@ -128,7 +146,11 @@ def test_init_method_noninteractive_invalid_type_name(): mock_project.init.assert_called_once_with( type_name, args.language, - {"use_docker": args.use_docker, "namespace": args.namespace}, + { + "use_docker": args.use_docker, + "namespace": args.namespace, + "codegen_template_path": args.codegen_model, + }, ) mock_project.generate.assert_called_once_with() @@ -152,7 +174,11 @@ def test_init_method_noninteractive_invalid_language(): mock_project.init.assert_called_once_with( args.type_name, language, - {"use_docker": args.use_docker, "namespace": args.namespace}, + { + "use_docker": args.use_docker, + "namespace": args.namespace, + "codegen_template_path": args.codegen_model, + }, ) mock_project.generate.assert_called_once_with() From 250a5f891d42c19e50744e744bc477d9a169d7ce Mon Sep 17 00:00:00 2001 From: Jesus Cabrera Date: Fri, 11 Sep 2020 17:19:49 -0400 Subject: [PATCH 05/13] Add --import-path for creation of Go projects --- src/rpdk/core/init.py | 6 ++++++ tests/test_init.py | 8 ++++++++ 2 files changed, 14 insertions(+) diff --git a/src/rpdk/core/init.py b/src/rpdk/core/init.py index 9559cef0..e328d33d 100644 --- a/src/rpdk/core/init.py +++ b/src/rpdk/core/init.py @@ -168,6 +168,7 @@ def init(args): "use_docker": args.use_docker, "namespace": args.namespace, "codegen_template_path": args.codegen_model, + "importpath": args.import_path, }, ) project.generate() @@ -232,3 +233,8 @@ def setup_subparser(subparsers, parents): choices=["default", "guided_aws"], help="Select a codegen model.", ) + + parser.add_argument( + "--import-path", + help="Select the go language import path.", + ) diff --git a/tests/test_init.py b/tests/test_init.py index 73952f99..09eb0591 100644 --- a/tests/test_init.py +++ b/tests/test_init.py @@ -53,6 +53,7 @@ def get_args(interactive=False, language=True, type_name=True): "use_docker", "namespace", "codegen_model", + "import_path", ] ) args.force = False @@ -62,9 +63,12 @@ def get_args(interactive=False, language=True, type_name=True): args.type_name = ( None if interactive else ("Test::Test::Test" if type_name else "Test") ) + + # The arguments below will only be tested by the plugins. args.use_docker = None args.namespace = "tested.by.the.plugin" args.codegen_model = None + args.import_path = None return args @@ -93,6 +97,7 @@ def test_init_method_interactive(): "use_docker": args.use_docker, "namespace": args.namespace, "codegen_template_path": args.codegen_model, + "importpath": args.import_path, }, ) mock_project.generate.assert_called_once_with() @@ -121,6 +126,7 @@ def test_init_method_noninteractive(): "use_docker": args.use_docker, "namespace": args.namespace, "codegen_template_path": args.codegen_model, + "importpath": args.import_path, }, ) mock_project.generate.assert_called_once_with() @@ -150,6 +156,7 @@ def test_init_method_noninteractive_invalid_type_name(): "use_docker": args.use_docker, "namespace": args.namespace, "codegen_template_path": args.codegen_model, + "importpath": args.import_path, }, ) mock_project.generate.assert_called_once_with() @@ -178,6 +185,7 @@ def test_init_method_noninteractive_invalid_language(): "use_docker": args.use_docker, "namespace": args.namespace, "codegen_template_path": args.codegen_model, + "importpath": args.import_path, }, ) mock_project.generate.assert_called_once_with() From 08ecbd2937da59ee629f621f3cfa27bccfd8ca1a Mon Sep 17 00:00:00 2001 From: Jesus Cabrera Date: Fri, 11 Sep 2020 18:17:09 -0400 Subject: [PATCH 06/13] Clean up code --- src/rpdk/core/init.py | 7 ++++ src/rpdk/core/plugin_registry.py | 5 ++- tests/test_init.py | 55 ++---------------------------- tests/utils.py | 57 ++++++++++++++++++++++++++++++++ 4 files changed, 68 insertions(+), 56 deletions(-) diff --git a/src/rpdk/core/init.py b/src/rpdk/core/init.py index e328d33d..d7a7d9ca 100644 --- a/src/rpdk/core/init.py +++ b/src/rpdk/core/init.py @@ -196,23 +196,27 @@ def setup_subparser(subparsers, parents): parser.set_defaults(command=ignore_abort(init)) parser.add_argument( + "-f", "--force", action="store_true", help="Force files to be overwritten.", ) parser.add_argument( + "-l", "--language", help="""Select a language for code generation. The language plugin needs to be installed.""", ) parser.add_argument( + "-t", "--type-name", help="Select the name of the resource type.", ) parser.add_argument( + "-d", "--use-docker", action="store_true", help="""Use docker for python platform-independent packaging. @@ -221,6 +225,7 @@ def setup_subparser(subparsers, parents): ) parser.add_argument( + "-n", "--namespace", nargs="?", const="default", @@ -229,12 +234,14 @@ def setup_subparser(subparsers, parents): ) parser.add_argument( + "-c", "--codegen-model", choices=["default", "guided_aws"], help="Select a codegen model.", ) parser.add_argument( + "-p", "--import-path", help="Select the go language import path.", ) diff --git a/src/rpdk/core/plugin_registry.py b/src/rpdk/core/plugin_registry.py index b9d9a5c4..92d6c88a 100644 --- a/src/rpdk/core/plugin_registry.py +++ b/src/rpdk/core/plugin_registry.py @@ -5,14 +5,13 @@ for entry_point in pkg_resources.iter_entry_points("rpdk.v1.languages") } -PLUGIN_CHOICES = sorted(PLUGIN_REGISTRY.keys()) - def get_plugin_choices(): - return [ + plugin_choices = [ entry_point.name for entry_point in pkg_resources.iter_entry_points("rpdk.v1.languages") ] + return sorted(plugin_choices) def load_plugin(language): diff --git a/tests/test_init.py b/tests/test_init.py index 09eb0591..208b2f46 100644 --- a/tests/test_init.py +++ b/tests/test_init.py @@ -1,7 +1,5 @@ -from pathlib import Path from unittest.mock import ANY, Mock, PropertyMock, patch -import pkg_resources import pytest from rpdk.core.exceptions import WizardAbortError, WizardValidationError @@ -18,61 +16,12 @@ ) from rpdk.core.project import Project +from .utils import add_dummy_language_plugin, get_args, get_mock_project + PROMPT = "MECVGD" ERROR = "TUJFEL" -def add_dummy_language_plugin(): - distribution = pkg_resources.Distribution(__file__) - entry_point = pkg_resources.EntryPoint.parse( - "dummy = rpdk.dummy:DummyLanguagePlugin", dist=distribution - ) - distribution._ep_map = { # pylint: disable=protected-access - "rpdk.v1.languages": {"dummy": entry_point} - } - pkg_resources.working_set.add(distribution) - - -def get_mock_project(): - mock_project = Mock(spec=Project) - mock_project.load_settings.side_effect = FileNotFoundError - mock_project.settings_path = "" - mock_project.root = Path(".") - - patch_project = patch("rpdk.core.init.Project", return_value=mock_project) - - return (mock_project, patch_project) - - -def get_args(interactive=False, language=True, type_name=True): - args = Mock( - spec_set=[ - "force", - "language", - "type_name", - "use_docker", - "namespace", - "codegen_model", - "import_path", - ] - ) - args.force = False - args.language = ( - None if interactive else ("dummy" if language else "invalid_language") - ) - args.type_name = ( - None if interactive else ("Test::Test::Test" if type_name else "Test") - ) - - # The arguments below will only be tested by the plugins. - args.use_docker = None - args.namespace = "tested.by.the.plugin" - args.codegen_model = None - args.import_path = None - - return args - - def test_init_method_interactive(): type_name = object() language = object() diff --git a/tests/utils.py b/tests/utils.py index 97823f00..946d3de3 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -1,7 +1,13 @@ import os from contextlib import contextmanager from io import BytesIO +from pathlib import Path from random import sample +from unittest.mock import Mock, patch + +import pkg_resources + +from rpdk.core.project import Project CONTENTS_UTF8 = "💣" @@ -67,6 +73,57 @@ def chdir(path): os.chdir(old) +def add_dummy_language_plugin(): + distribution = pkg_resources.Distribution(__file__) + entry_point = pkg_resources.EntryPoint.parse( + "dummy = rpdk.dummy:DummyLanguagePlugin", dist=distribution + ) + distribution._ep_map = { # pylint: disable=protected-access + "rpdk.v1.languages": {"dummy": entry_point} + } + pkg_resources.working_set.add(distribution) + + +def get_mock_project(): + mock_project = Mock(spec=Project) + mock_project.load_settings.side_effect = FileNotFoundError + mock_project.settings_path = "" + mock_project.root = Path(".") + + patch_project = patch("rpdk.core.init.Project", return_value=mock_project) + + return (mock_project, patch_project) + + +def get_args(interactive=False, language=True, type_name=True): + args = Mock( + spec_set=[ + "force", + "language", + "type_name", + "use_docker", + "namespace", + "codegen_model", + "import_path", + ] + ) + args.force = False + args.language = ( + None if interactive else ("dummy" if language else "invalid_language") + ) + args.type_name = ( + None if interactive else ("Test::Test::Test" if type_name else "Test") + ) + + # The arguments below will only be tested by the plugins. + args.use_docker = None + args.namespace = "tested.by.the.plugin" + args.codegen_model = None + args.import_path = None + + return args + + class UnclosingBytesIO(BytesIO): _was_closed = False From ff8604298e8af8e71dc53447626d5687f894b076 Mon Sep 17 00:00:00 2001 From: Jesus Cabrera Date: Mon, 21 Sep 2020 13:49:15 -0400 Subject: [PATCH 07/13] Subparser from entry point proof of concept --- src/rpdk/core/init.py | 96 ++++++++-------- src/rpdk/core/plugin_registry.py | 5 + tests/test_init.py | 182 +++++++++++++++++-------------- tests/utils.py | 16 +-- 4 files changed, 159 insertions(+), 140 deletions(-) diff --git a/src/rpdk/core/init.py b/src/rpdk/core/init.py index d7a7d9ca..1abf94f2 100644 --- a/src/rpdk/core/init.py +++ b/src/rpdk/core/init.py @@ -1,5 +1,6 @@ """This sub command generates IDE and build files for a given language. """ +import argparse import logging import re from functools import wraps @@ -7,7 +8,7 @@ from colorama import Fore, Style from .exceptions import WizardAbortError, WizardValidationError -from .plugin_registry import get_plugin_choices +from .plugin_registry import PARSER_REGISTRY, get_plugin_choices from .project import Project LOG = logging.getLogger(__name__) @@ -161,16 +162,16 @@ def init(args): else: language = input_language() - project.init( - type_name, - language, - { - "use_docker": args.use_docker, - "namespace": args.namespace, - "codegen_template_path": args.codegen_model, - "importpath": args.import_path, - }, - ) + settings = { + arg: getattr(args, arg) + for arg in vars(args) + if not callable(getattr(args, arg)) + } + + print("--------------------\n", settings, "\n-----------------------") + + project.init(type_name, language, settings) + project.generate() project.generate_docs() @@ -195,6 +196,12 @@ def setup_subparser(subparsers, parents): parser = subparsers.add_parser("init", description=__doc__, parents=parents) parser.set_defaults(command=ignore_abort(init)) + language_subparsers = parser.add_subparsers(dest="subparser_name") + base_subparser = argparse.ArgumentParser(add_help=False) + for language_setup_subparser in PARSER_REGISTRY.values(): + language_parser = language_setup_subparser()(language_subparsers, [base_subparser]) + print(vars(language_parser.parse_args([]))) + parser.add_argument( "-f", "--force", @@ -202,46 +209,39 @@ def setup_subparser(subparsers, parents): help="Force files to be overwritten.", ) - parser.add_argument( - "-l", - "--language", - help="""Select a language for code generation. - The language plugin needs to be installed.""", - ) - parser.add_argument( "-t", "--type-name", help="Select the name of the resource type.", ) - parser.add_argument( - "-d", - "--use-docker", - action="store_true", - help="""Use docker for python platform-independent packaging. - This is highly recommended unless you are experienced - with cross-platform Python packaging.""", - ) - - parser.add_argument( - "-n", - "--namespace", - nargs="?", - const="default", - help="""Select the name of the Java namespace. - Passing the flag without argument select the default namespace.""", - ) - - parser.add_argument( - "-c", - "--codegen-model", - choices=["default", "guided_aws"], - help="Select a codegen model.", - ) - - parser.add_argument( - "-p", - "--import-path", - help="Select the go language import path.", - ) + # parser.add_argument( + # "-d", + # "--use-docker", + # action="store_true", + # help="""Use docker for python platform-independent packaging. + # This is highly recommended unless you are experienced + # with cross-platform Python packaging.""", + # ) + + # parser.add_argument( + # "-n", + # "--namespace", + # nargs="?", + # const="default", + # help="""Select the name of the Java namespace. + # Passing the flag without argument select the default namespace.""", + # ) + + # parser.add_argument( + # "-c", + # "--codegen-model", + # choices=["default", "guided_aws"], + # help="Select a codegen model.", + # ) + + # parser.add_argument( + # "-p", + # "--import-path", + # help="Select the go language import path.", + # ) diff --git a/src/rpdk/core/plugin_registry.py b/src/rpdk/core/plugin_registry.py index 92d6c88a..e8509ce6 100644 --- a/src/rpdk/core/plugin_registry.py +++ b/src/rpdk/core/plugin_registry.py @@ -5,6 +5,11 @@ for entry_point in pkg_resources.iter_entry_points("rpdk.v1.languages") } +PARSER_REGISTRY = { + entry_point.name: entry_point.load + for entry_point in pkg_resources.iter_entry_points("rpdk.v1.parsers") +} + def get_plugin_choices(): plugin_choices = [ diff --git a/tests/test_init.py b/tests/test_init.py index 208b2f46..64a4f6b2 100644 --- a/tests/test_init.py +++ b/tests/test_init.py @@ -22,34 +22,34 @@ ERROR = "TUJFEL" -def test_init_method_interactive(): - type_name = object() - language = object() +# def test_init_method_interactive(): +# type_name = object() +# language = object() - args = get_args(interactive=True) - mock_project, patch_project = get_mock_project() +# args = get_args(interactive=True) +# mock_project, patch_project = get_mock_project() - patch_tn = patch("rpdk.core.init.input_typename", return_value=type_name) - patch_l = patch("rpdk.core.init.input_language", return_value=language) +# patch_tn = patch("rpdk.core.init.input_typename", return_value=type_name) +# patch_l = patch("rpdk.core.init.input_language", return_value=language) - with patch_project, patch_tn as mock_tn, patch_l as mock_l: - init(args) +# with patch_project, patch_tn as mock_tn, patch_l as mock_l: +# init(args) - mock_tn.assert_called_once_with() - mock_l.assert_called_once_with() +# mock_tn.assert_called_once_with() +# mock_l.assert_called_once_with() - mock_project.load_settings.assert_called_once_with() - mock_project.init.assert_called_once_with( - type_name, - language, - { - "use_docker": args.use_docker, - "namespace": args.namespace, - "codegen_template_path": args.codegen_model, - "importpath": args.import_path, - }, - ) - mock_project.generate.assert_called_once_with() +# mock_project.load_settings.assert_called_once_with() +# mock_project.init.assert_called_once_with( +# type_name, +# language, +# { +# "use_docker": args.use_docker, +# "namespace": args.namespace, +# "codegen_template_path": args.codegen_model, +# "importpath": args.import_path, +# }, +# ) +# mock_project.generate.assert_called_once_with() def test_init_method_noninteractive(): @@ -58,6 +58,8 @@ def test_init_method_noninteractive(): args = get_args() mock_project, patch_project = get_mock_project() + print(args) + patch_tn = patch("rpdk.core.init.input_typename") patch_l = patch("rpdk.core.init.input_language") @@ -72,72 +74,84 @@ def test_init_method_noninteractive(): args.type_name, args.language, { - "use_docker": args.use_docker, - "namespace": args.namespace, - "codegen_template_path": args.codegen_model, - "importpath": args.import_path, + # "use_docker": args.use_docker, + # "namespace": args.namespace, + # "codegen_template_path": args.codegen_model, + # "importpath": args.import_path, + # "version": args.version, + # "subparser_name": args.subparser_name, + # "verbose": args.verbose, + # "force": args.force, + # "type_name": args.type_name, + # "language": args.language, }, ) mock_project.generate.assert_called_once_with() -def test_init_method_noninteractive_invalid_type_name(): - add_dummy_language_plugin() - type_name = object() - - args = get_args(type_name=False) - mock_project, patch_project = get_mock_project() - - patch_tn = patch("rpdk.core.init.input_typename", return_value=type_name) - patch_l = patch("rpdk.core.init.input_language") - - with patch_project, patch_tn as mock_tn, patch_l as mock_l: - init(args) - - mock_tn.assert_called_once_with() - mock_l.assert_not_called() - - mock_project.load_settings.assert_called_once_with() - mock_project.init.assert_called_once_with( - type_name, - args.language, - { - "use_docker": args.use_docker, - "namespace": args.namespace, - "codegen_template_path": args.codegen_model, - "importpath": args.import_path, - }, - ) - mock_project.generate.assert_called_once_with() - - -def test_init_method_noninteractive_invalid_language(): - language = object() - - args = get_args(language=False) - mock_project, patch_project = get_mock_project() - - patch_tn = patch("rpdk.core.init.input_typename") - patch_l = patch("rpdk.core.init.input_language", return_value=language) - - with patch_project, patch_tn as mock_tn, patch_l as mock_l: - init(args) - - mock_tn.assert_not_called() - mock_l.assert_called_once_with() - - mock_project.load_settings.assert_called_once_with() - mock_project.init.assert_called_once_with( - args.type_name, - language, - { - "use_docker": args.use_docker, - "namespace": args.namespace, - "codegen_template_path": args.codegen_model, - "importpath": args.import_path, - }, - ) - mock_project.generate.assert_called_once_with() +# def test_init_method_noninteractive_invalid_type_name(): +# add_dummy_language_plugin() +# type_name = object() + +# args = get_args(type_name=False) +# mock_project, patch_project = get_mock_project() + +# patch_tn = patch("rpdk.core.init.input_typename", return_value=type_name) +# patch_l = patch("rpdk.core.init.input_language") + +# with patch_project, patch_tn as mock_tn, patch_l as mock_l: +# init(args) + +# mock_tn.assert_called_once_with() +# mock_l.assert_not_called() + +# mock_project.load_settings.assert_called_once_with() +# mock_project.init.assert_called_once_with( +# type_name, +# args.language, +# { +# "use_docker": args.use_docker, +# "namespace": args.namespace, +# "codegen_template_path": args.codegen_model, +# "importpath": args.import_path, +# }, +# ) +# mock_project.generate.assert_called_once_with() + + +# def test_init_method_noninteractive_invalid_language(): +# language = object() + +# args = get_args(language=False) +# mock_project, patch_project = get_mock_project() + +# patch_tn = patch("rpdk.core.init.input_typename") +# patch_l = patch("rpdk.core.init.input_language", return_value=language) + +# with patch_project, patch_tn as mock_tn, patch_l as mock_l: +# init(args) + +# mock_tn.assert_not_called() +# mock_l.assert_called_once_with() + +# mock_project.load_settings.assert_called_once_with() +# mock_project.init.assert_called_once_with( +# args.type_name, +# language, +# { +# "use_docker": args.use_docker, +# # "namespace": args.namespace, +# # "codegen_template_path": args.codegen_model, +# # "importpath": args.import_path, +# "version": False, +# "subparser_name": "python37", +# "verbose": 0, +# "force": False, +# "type_name": "TT::TT::TT", +# "language": "python37", +# }, +# ) +# mock_project.generate.assert_called_once_with() def test_input_with_validation_valid_first_try(capsys): diff --git a/tests/utils.py b/tests/utils.py index 946d3de3..da159773 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -101,10 +101,10 @@ def get_args(interactive=False, language=True, type_name=True): "force", "language", "type_name", - "use_docker", - "namespace", - "codegen_model", - "import_path", + # "use_docker", + # "namespace", + # "codegen_model", + # "import_path", ] ) args.force = False @@ -116,10 +116,10 @@ def get_args(interactive=False, language=True, type_name=True): ) # The arguments below will only be tested by the plugins. - args.use_docker = None - args.namespace = "tested.by.the.plugin" - args.codegen_model = None - args.import_path = None + # args.use_docker = None + # args.namespace = "tested.by.the.plugin" + # args.codegen_model = None + # args.import_path = None return args From ebceecfa4bbd36debde9a774cfac413b962c0218 Mon Sep 17 00:00:00 2001 From: Jesus Cabrera Date: Fri, 25 Sep 2020 12:35:41 -0400 Subject: [PATCH 08/13] Add plugins subparsers --- src/rpdk/core/init.py | 51 +--------- src/rpdk/core/plugin_registry.py | 14 ++- tests/test_init.py | 162 +++++++++++-------------------- tests/utils.py | 44 ++++++--- 4 files changed, 103 insertions(+), 168 deletions(-) diff --git a/src/rpdk/core/init.py b/src/rpdk/core/init.py index 1abf94f2..73d360ea 100644 --- a/src/rpdk/core/init.py +++ b/src/rpdk/core/init.py @@ -8,7 +8,7 @@ from colorama import Fore, Style from .exceptions import WizardAbortError, WizardValidationError -from .plugin_registry import PARSER_REGISTRY, get_plugin_choices +from .plugin_registry import get_parsers, get_plugin_choices from .project import Project LOG = logging.getLogger(__name__) @@ -148,17 +148,8 @@ def init(args): else: type_name = input_typename() - if args.language: + if "language" in vars(args): language = args.language.lower() - if language not in get_plugin_choices(): - print( - Style.BRIGHT, - Fore.RED, - "The plugin for {} is not installed.".format(language), - Style.RESET_ALL, - sep="", - ) - language = input_language() else: language = input_language() @@ -168,8 +159,6 @@ def init(args): if not callable(getattr(args, arg)) } - print("--------------------\n", settings, "\n-----------------------") - project.init(type_name, language, settings) project.generate() @@ -198,9 +187,8 @@ def setup_subparser(subparsers, parents): language_subparsers = parser.add_subparsers(dest="subparser_name") base_subparser = argparse.ArgumentParser(add_help=False) - for language_setup_subparser in PARSER_REGISTRY.values(): - language_parser = language_setup_subparser()(language_subparsers, [base_subparser]) - print(vars(language_parser.parse_args([]))) + for language_setup_subparser in get_parsers().values(): + language_setup_subparser()(language_subparsers, [base_subparser]) parser.add_argument( "-f", @@ -214,34 +202,3 @@ def setup_subparser(subparsers, parents): "--type-name", help="Select the name of the resource type.", ) - - # parser.add_argument( - # "-d", - # "--use-docker", - # action="store_true", - # help="""Use docker for python platform-independent packaging. - # This is highly recommended unless you are experienced - # with cross-platform Python packaging.""", - # ) - - # parser.add_argument( - # "-n", - # "--namespace", - # nargs="?", - # const="default", - # help="""Select the name of the Java namespace. - # Passing the flag without argument select the default namespace.""", - # ) - - # parser.add_argument( - # "-c", - # "--codegen-model", - # choices=["default", "guided_aws"], - # help="Select a codegen model.", - # ) - - # parser.add_argument( - # "-p", - # "--import-path", - # help="Select the go language import path.", - # ) diff --git a/src/rpdk/core/plugin_registry.py b/src/rpdk/core/plugin_registry.py index e8509ce6..8862ca4a 100644 --- a/src/rpdk/core/plugin_registry.py +++ b/src/rpdk/core/plugin_registry.py @@ -5,11 +5,6 @@ for entry_point in pkg_resources.iter_entry_points("rpdk.v1.languages") } -PARSER_REGISTRY = { - entry_point.name: entry_point.load - for entry_point in pkg_resources.iter_entry_points("rpdk.v1.parsers") -} - def get_plugin_choices(): plugin_choices = [ @@ -19,5 +14,14 @@ def get_plugin_choices(): return sorted(plugin_choices) +def get_parsers(): + parsers = { + entry_point.name: entry_point.load + for entry_point in pkg_resources.iter_entry_points("rpdk.v1.parsers") + } + + return parsers + + def load_plugin(language): return PLUGIN_REGISTRY[language]()() diff --git a/tests/test_init.py b/tests/test_init.py index 64a4f6b2..92d48805 100644 --- a/tests/test_init.py +++ b/tests/test_init.py @@ -2,12 +2,12 @@ import pytest +from rpdk.core.cli import main from rpdk.core.exceptions import WizardAbortError, WizardValidationError -from rpdk.core.init import ( +from rpdk.core.init import ( # init, ValidatePluginChoice, check_for_existing_project, ignore_abort, - init, input_language, input_typename, input_with_validation, @@ -16,40 +16,35 @@ ) from rpdk.core.project import Project -from .utils import add_dummy_language_plugin, get_args, get_mock_project +from .utils import add_dummy_language_plugin, dummy_parser, get_args, get_mock_project PROMPT = "MECVGD" ERROR = "TUJFEL" -# def test_init_method_interactive(): -# type_name = object() -# language = object() +def test_init_method_interactive(): + type_name = object() + language = object() -# args = get_args(interactive=True) -# mock_project, patch_project = get_mock_project() + args = get_args(interactive=True) + mock_project, patch_project = get_mock_project() -# patch_tn = patch("rpdk.core.init.input_typename", return_value=type_name) -# patch_l = patch("rpdk.core.init.input_language", return_value=language) + patch_tn = patch("rpdk.core.init.input_typename", return_value=type_name) + patch_l = patch("rpdk.core.init.input_language", return_value=language) -# with patch_project, patch_tn as mock_tn, patch_l as mock_l: -# init(args) + with patch_project, patch_tn as mock_tn, patch_l as mock_l: + main(args_in=["init"]) -# mock_tn.assert_called_once_with() -# mock_l.assert_called_once_with() + mock_tn.assert_called_once_with() + mock_l.assert_called_once_with() -# mock_project.load_settings.assert_called_once_with() -# mock_project.init.assert_called_once_with( -# type_name, -# language, -# { -# "use_docker": args.use_docker, -# "namespace": args.namespace, -# "codegen_template_path": args.codegen_model, -# "importpath": args.import_path, -# }, -# ) -# mock_project.generate.assert_called_once_with() + mock_project.load_settings.assert_called_once_with() + mock_project.init.assert_called_once_with( + type_name, + language, + args.settings, + ) + mock_project.generate.assert_called_once_with() def test_init_method_noninteractive(): @@ -58,100 +53,57 @@ def test_init_method_noninteractive(): args = get_args() mock_project, patch_project = get_mock_project() - print(args) - patch_tn = patch("rpdk.core.init.input_typename") patch_l = patch("rpdk.core.init.input_language") + patch_get_parser = patch( + "rpdk.core.init.get_parsers", return_value={"dummy": dummy_parser} + ) - with patch_project, patch_tn as mock_tn, patch_l as mock_l: - init(args) + # pylint: disable=C0301 + with patch_project, patch_tn as mock_tn, patch_l as mock_l, patch_get_parser as mock_parser: # noqa: B950 + main(args_in=["init", "--type-name", args.type_name, args.language, "--dummy"]) mock_tn.assert_not_called() mock_l.assert_not_called() + mock_parser.assert_called_once() mock_project.load_settings.assert_called_once_with() mock_project.init.assert_called_once_with( args.type_name, args.language, - { - # "use_docker": args.use_docker, - # "namespace": args.namespace, - # "codegen_template_path": args.codegen_model, - # "importpath": args.import_path, - # "version": args.version, - # "subparser_name": args.subparser_name, - # "verbose": args.verbose, - # "force": args.force, - # "type_name": args.type_name, - # "language": args.language, - }, + args.settings, ) mock_project.generate.assert_called_once_with() -# def test_init_method_noninteractive_invalid_type_name(): -# add_dummy_language_plugin() -# type_name = object() - -# args = get_args(type_name=False) -# mock_project, patch_project = get_mock_project() - -# patch_tn = patch("rpdk.core.init.input_typename", return_value=type_name) -# patch_l = patch("rpdk.core.init.input_language") - -# with patch_project, patch_tn as mock_tn, patch_l as mock_l: -# init(args) - -# mock_tn.assert_called_once_with() -# mock_l.assert_not_called() - -# mock_project.load_settings.assert_called_once_with() -# mock_project.init.assert_called_once_with( -# type_name, -# args.language, -# { -# "use_docker": args.use_docker, -# "namespace": args.namespace, -# "codegen_template_path": args.codegen_model, -# "importpath": args.import_path, -# }, -# ) -# mock_project.generate.assert_called_once_with() - - -# def test_init_method_noninteractive_invalid_language(): -# language = object() - -# args = get_args(language=False) -# mock_project, patch_project = get_mock_project() - -# patch_tn = patch("rpdk.core.init.input_typename") -# patch_l = patch("rpdk.core.init.input_language", return_value=language) - -# with patch_project, patch_tn as mock_tn, patch_l as mock_l: -# init(args) - -# mock_tn.assert_not_called() -# mock_l.assert_called_once_with() - -# mock_project.load_settings.assert_called_once_with() -# mock_project.init.assert_called_once_with( -# args.type_name, -# language, -# { -# "use_docker": args.use_docker, -# # "namespace": args.namespace, -# # "codegen_template_path": args.codegen_model, -# # "importpath": args.import_path, -# "version": False, -# "subparser_name": "python37", -# "verbose": 0, -# "force": False, -# "type_name": "TT::TT::TT", -# "language": "python37", -# }, -# ) -# mock_project.generate.assert_called_once_with() +def test_init_method_noninteractive_invalid_type_name(): + add_dummy_language_plugin() + type_name = object() + + args = get_args(type_name=False) + mock_project, patch_project = get_mock_project() + + patch_tn = patch("rpdk.core.init.input_typename", return_value=type_name) + patch_l = patch("rpdk.core.init.input_language") + patch_get_parser = patch( + "rpdk.core.init.get_parsers", return_value={"dummy": dummy_parser} + ) + + # pylint: disable=C0301 + with patch_project, patch_tn as mock_tn, patch_l as mock_l, patch_get_parser as mock_parser: # noqa: B950 + main(args_in=["init", "--type-name", args.type_name, args.language, "--dummy"]) + + mock_tn.assert_called_once_with() + mock_l.assert_not_called() + mock_parser.assert_called_once() + + mock_project.load_settings.assert_called_once_with() + mock_project.init.assert_called_once_with( + type_name, + args.language, + args.settings, + ) + mock_project.generate.assert_called_once_with() def test_input_with_validation_valid_first_try(capsys): diff --git a/tests/utils.py b/tests/utils.py index da159773..f11100f0 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -98,32 +98,54 @@ def get_mock_project(): def get_args(interactive=False, language=True, type_name=True): args = Mock( spec_set=[ - "force", "language", "type_name", - # "use_docker", - # "namespace", - # "codegen_model", - # "import_path", + "settings", ] ) - args.force = False + args.language = ( None if interactive else ("dummy" if language else "invalid_language") ) args.type_name = ( None if interactive else ("Test::Test::Test" if type_name else "Test") ) + args.settings = { + "version": False, + "subparser_name": None if interactive else ("dummy" if language else None), + "verbose": 0, + "force": False, + "type_name": args.type_name, + } - # The arguments below will only be tested by the plugins. - # args.use_docker = None - # args.namespace = "tested.by.the.plugin" - # args.codegen_model = None - # args.import_path = None + if language and not interactive: + args.settings["dummy"] = True + args.settings["language"] = args.language return args +def dummy_parser(): + def dummy_subparser(subparsers, parents): + parser = subparsers.add_parser( + "dummy", + description="""This sub command generates IDE and build + files for the Dummy plugin""", + parents=parents, + ) + parser.set_defaults(language="dummy") + + parser.add_argument( + "-d", + "--dummy", + action="store_true", + help="Dummy boolean to test if parser is loaded correctly", + ) + return parser + + return dummy_subparser + + class UnclosingBytesIO(BytesIO): _was_closed = False From f7f54e62df865467f239ae30da0bc9fcbacf9877 Mon Sep 17 00:00:00 2001 From: Jesus Cabrera Date: Fri, 25 Sep 2020 15:21:05 -0400 Subject: [PATCH 09/13] Clean up code --- src/rpdk/core/init.py | 10 ++- test/inputs/inputs_1_create.json | 11 ++++ test/inputs/inputs_1_invalid.json | 11 ++++ test/inputs/inputs_1_update.json | 11 ++++ test/tt-tt-tt.json | 105 ++++++++++++++++++++++++++++++ 5 files changed, 145 insertions(+), 3 deletions(-) create mode 100644 test/inputs/inputs_1_create.json create mode 100644 test/inputs/inputs_1_invalid.json create mode 100644 test/inputs/inputs_1_update.json create mode 100644 test/tt-tt-tt.json diff --git a/src/rpdk/core/init.py b/src/rpdk/core/init.py index 73d360ea..bb758977 100644 --- a/src/rpdk/core/init.py +++ b/src/rpdk/core/init.py @@ -17,6 +17,10 @@ TYPE_NAME_REGEX = r"^[a-zA-Z0-9]{2,64}::[a-zA-Z0-9]{2,64}::[a-zA-Z0-9]{2,64}$" +def print_error(error): + print(Style.BRIGHT, Fore.RED, str(error), Style.RESET_ALL, sep="") + + def input_with_validation(prompt, validate, description=""): while True: print( @@ -32,8 +36,8 @@ def input_with_validation(prompt, validate, description=""): response = input() try: return validate(response) - except WizardValidationError as e: - print(Style.BRIGHT, Fore.RED, str(e), Style.RESET_ALL, sep="") + except WizardValidationError as error: + print_error(error) def validate_type_name(value): @@ -143,7 +147,7 @@ def init(args): try: type_name = validate_type_name(args.type_name) except WizardValidationError as error: - print(Style.BRIGHT, Fore.RED, str(error), Style.RESET_ALL, sep="") + print_error(error) type_name = input_typename() else: type_name = input_typename() diff --git a/test/inputs/inputs_1_create.json b/test/inputs/inputs_1_create.json new file mode 100644 index 00000000..b31224ca --- /dev/null +++ b/test/inputs/inputs_1_create.json @@ -0,0 +1,11 @@ +{ + "TPSCode": "...", + "Title": "...", + "CoverSheetIncluded": "...", + "DueDate": "...", + "ApprovalDate": "...", + "Memo": "...", + "SecondCopyOfMemo": "...", + "TestCode": "...", + "Authors": "..." +} diff --git a/test/inputs/inputs_1_invalid.json b/test/inputs/inputs_1_invalid.json new file mode 100644 index 00000000..b31224ca --- /dev/null +++ b/test/inputs/inputs_1_invalid.json @@ -0,0 +1,11 @@ +{ + "TPSCode": "...", + "Title": "...", + "CoverSheetIncluded": "...", + "DueDate": "...", + "ApprovalDate": "...", + "Memo": "...", + "SecondCopyOfMemo": "...", + "TestCode": "...", + "Authors": "..." +} diff --git a/test/inputs/inputs_1_update.json b/test/inputs/inputs_1_update.json new file mode 100644 index 00000000..b31224ca --- /dev/null +++ b/test/inputs/inputs_1_update.json @@ -0,0 +1,11 @@ +{ + "TPSCode": "...", + "Title": "...", + "CoverSheetIncluded": "...", + "DueDate": "...", + "ApprovalDate": "...", + "Memo": "...", + "SecondCopyOfMemo": "...", + "TestCode": "...", + "Authors": "..." +} diff --git a/test/tt-tt-tt.json b/test/tt-tt-tt.json new file mode 100644 index 00000000..32d52fe5 --- /dev/null +++ b/test/tt-tt-tt.json @@ -0,0 +1,105 @@ +{ + "typeName": "TT::TT::TT", + "description": "An example resource schema demonstrating some basic constructs and validation rules.", + "sourceUrl": "https://github.com/aws-cloudformation/aws-cloudformation-rpdk.git", + "definitions": { + "InitechDateFormat": { + "$comment": "Use the `definitions` block to provide shared resource property schemas", + "type": "string", + "format": "date-time" + }, + "Memo": { + "type": "object", + "properties": { + "Heading": { + "type": "string" + }, + "Body": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "properties": { + "TPSCode": { + "description": "A TPS Code is automatically generated on creation and assigned as the unique identifier.", + "type": "string", + "pattern": "^[A-Z]{3,5}[0-9]{8}-[0-9]{4}$" + }, + "Title": { + "description": "The title of the TPS report is a mandatory element.", + "type": "string", + "minLength": 20, + "maxLength": 250 + }, + "CoverSheetIncluded": { + "description": "Required for all TPS Reports submitted after 2/19/1999", + "type": "boolean" + }, + "DueDate": { + "$ref": "#/definitions/InitechDateFormat" + }, + "ApprovalDate": { + "$ref": "#/definitions/InitechDateFormat" + }, + "Memo": { + "$ref": "#/definitions/Memo" + }, + "SecondCopyOfMemo": { + "description": "In case you didn't get the first one.", + "$ref": "#/definitions/Memo" + }, + "TestCode": { + "type": "string", + "enum": [ + "NOT_STARTED", + "CANCELLED" + ] + }, + "Authors": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "additionalProperties": false, + "required": [ + "TestCode", + "Title" + ], + "readOnlyProperties": [ + "/properties/TPSCode" + ], + "primaryIdentifier": [ + "/properties/TPSCode" + ], + "handlers": { + "create": { + "permissions": [ + "initech:CreateReport" + ] + }, + "read": { + "permissions": [ + "initech:DescribeReport" + ] + }, + "update": { + "permissions": [ + "initech:UpdateReport" + ] + }, + "delete": { + "permissions": [ + "initech:DeleteReport" + ] + }, + "list": { + "permissions": [ + "initech:ListReports" + ] + } + } +} From 5d8058661ccc967783e13ba5d9475efe9214507f Mon Sep 17 00:00:00 2001 From: Jesus Cabrera Date: Fri, 25 Sep 2020 15:23:38 -0400 Subject: [PATCH 10/13] Remove test files --- test/inputs/inputs_1_create.json | 11 ---- test/inputs/inputs_1_invalid.json | 11 ---- test/inputs/inputs_1_update.json | 11 ---- test/tt-tt-tt.json | 105 ------------------------------ 4 files changed, 138 deletions(-) delete mode 100644 test/inputs/inputs_1_create.json delete mode 100644 test/inputs/inputs_1_invalid.json delete mode 100644 test/inputs/inputs_1_update.json delete mode 100644 test/tt-tt-tt.json diff --git a/test/inputs/inputs_1_create.json b/test/inputs/inputs_1_create.json deleted file mode 100644 index b31224ca..00000000 --- a/test/inputs/inputs_1_create.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "TPSCode": "...", - "Title": "...", - "CoverSheetIncluded": "...", - "DueDate": "...", - "ApprovalDate": "...", - "Memo": "...", - "SecondCopyOfMemo": "...", - "TestCode": "...", - "Authors": "..." -} diff --git a/test/inputs/inputs_1_invalid.json b/test/inputs/inputs_1_invalid.json deleted file mode 100644 index b31224ca..00000000 --- a/test/inputs/inputs_1_invalid.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "TPSCode": "...", - "Title": "...", - "CoverSheetIncluded": "...", - "DueDate": "...", - "ApprovalDate": "...", - "Memo": "...", - "SecondCopyOfMemo": "...", - "TestCode": "...", - "Authors": "..." -} diff --git a/test/inputs/inputs_1_update.json b/test/inputs/inputs_1_update.json deleted file mode 100644 index b31224ca..00000000 --- a/test/inputs/inputs_1_update.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "TPSCode": "...", - "Title": "...", - "CoverSheetIncluded": "...", - "DueDate": "...", - "ApprovalDate": "...", - "Memo": "...", - "SecondCopyOfMemo": "...", - "TestCode": "...", - "Authors": "..." -} diff --git a/test/tt-tt-tt.json b/test/tt-tt-tt.json deleted file mode 100644 index 32d52fe5..00000000 --- a/test/tt-tt-tt.json +++ /dev/null @@ -1,105 +0,0 @@ -{ - "typeName": "TT::TT::TT", - "description": "An example resource schema demonstrating some basic constructs and validation rules.", - "sourceUrl": "https://github.com/aws-cloudformation/aws-cloudformation-rpdk.git", - "definitions": { - "InitechDateFormat": { - "$comment": "Use the `definitions` block to provide shared resource property schemas", - "type": "string", - "format": "date-time" - }, - "Memo": { - "type": "object", - "properties": { - "Heading": { - "type": "string" - }, - "Body": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "properties": { - "TPSCode": { - "description": "A TPS Code is automatically generated on creation and assigned as the unique identifier.", - "type": "string", - "pattern": "^[A-Z]{3,5}[0-9]{8}-[0-9]{4}$" - }, - "Title": { - "description": "The title of the TPS report is a mandatory element.", - "type": "string", - "minLength": 20, - "maxLength": 250 - }, - "CoverSheetIncluded": { - "description": "Required for all TPS Reports submitted after 2/19/1999", - "type": "boolean" - }, - "DueDate": { - "$ref": "#/definitions/InitechDateFormat" - }, - "ApprovalDate": { - "$ref": "#/definitions/InitechDateFormat" - }, - "Memo": { - "$ref": "#/definitions/Memo" - }, - "SecondCopyOfMemo": { - "description": "In case you didn't get the first one.", - "$ref": "#/definitions/Memo" - }, - "TestCode": { - "type": "string", - "enum": [ - "NOT_STARTED", - "CANCELLED" - ] - }, - "Authors": { - "type": "array", - "items": { - "type": "string" - } - } - }, - "additionalProperties": false, - "required": [ - "TestCode", - "Title" - ], - "readOnlyProperties": [ - "/properties/TPSCode" - ], - "primaryIdentifier": [ - "/properties/TPSCode" - ], - "handlers": { - "create": { - "permissions": [ - "initech:CreateReport" - ] - }, - "read": { - "permissions": [ - "initech:DescribeReport" - ] - }, - "update": { - "permissions": [ - "initech:UpdateReport" - ] - }, - "delete": { - "permissions": [ - "initech:DeleteReport" - ] - }, - "list": { - "permissions": [ - "initech:ListReports" - ] - } - } -} From 906b63057e8185d03f93d127ee13881cc49d88dd Mon Sep 17 00:00:00 2001 From: Jesus Cabrera Date: Fri, 25 Sep 2020 15:27:48 -0400 Subject: [PATCH 11/13] Clean up code --- tests/test_init.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_init.py b/tests/test_init.py index 92d48805..10a0fa96 100644 --- a/tests/test_init.py +++ b/tests/test_init.py @@ -4,7 +4,7 @@ from rpdk.core.cli import main from rpdk.core.exceptions import WizardAbortError, WizardValidationError -from rpdk.core.init import ( # init, +from rpdk.core.init import ( ValidatePluginChoice, check_for_existing_project, ignore_abort, From f4381b55759c93039c75c6fd8215e5306c1a642b Mon Sep 17 00:00:00 2001 From: Jesus Cabrera Date: Tue, 29 Sep 2020 17:21:40 -0400 Subject: [PATCH 12/13] Clean up testing --- tests/test_init.py | 48 +++++++++++++++++++++++++++++----------------- tests/utils.py | 21 +++----------------- 2 files changed, 33 insertions(+), 36 deletions(-) diff --git a/tests/test_init.py b/tests/test_init.py index 10a0fa96..9fbf2db8 100644 --- a/tests/test_init.py +++ b/tests/test_init.py @@ -26,9 +26,7 @@ def test_init_method_interactive(): type_name = object() language = object() - args = get_args(interactive=True) mock_project, patch_project = get_mock_project() - patch_tn = patch("rpdk.core.init.input_typename", return_value=type_name) patch_l = patch("rpdk.core.init.input_language", return_value=language) @@ -42,7 +40,13 @@ def test_init_method_interactive(): mock_project.init.assert_called_once_with( type_name, language, - args.settings, + { + "version": False, + "subparser_name": None, + "verbose": 0, + "force": False, + "type_name": None, + }, ) mock_project.generate.assert_called_once_with() @@ -50,28 +54,31 @@ def test_init_method_interactive(): def test_init_method_noninteractive(): add_dummy_language_plugin() - args = get_args() + args = get_args("dummy", "Test::Test::Test") mock_project, patch_project = get_mock_project() - patch_tn = patch("rpdk.core.init.input_typename") - patch_l = patch("rpdk.core.init.input_language") patch_get_parser = patch( "rpdk.core.init.get_parsers", return_value={"dummy": dummy_parser} ) - # pylint: disable=C0301 - with patch_project, patch_tn as mock_tn, patch_l as mock_l, patch_get_parser as mock_parser: # noqa: B950 + with patch_project, patch_get_parser as mock_parser: main(args_in=["init", "--type-name", args.type_name, args.language, "--dummy"]) - mock_tn.assert_not_called() - mock_l.assert_not_called() mock_parser.assert_called_once() mock_project.load_settings.assert_called_once_with() mock_project.init.assert_called_once_with( args.type_name, args.language, - args.settings, + { + "version": False, + "subparser_name": args.language, + "verbose": 0, + "force": False, + "type_name": args.type_name, + "language": args.language, + "dummy": True, + }, ) mock_project.generate.assert_called_once_with() @@ -80,28 +87,33 @@ def test_init_method_noninteractive_invalid_type_name(): add_dummy_language_plugin() type_name = object() - args = get_args(type_name=False) + args = get_args("dummy", "invalid_type_name") mock_project, patch_project = get_mock_project() patch_tn = patch("rpdk.core.init.input_typename", return_value=type_name) - patch_l = patch("rpdk.core.init.input_language") patch_get_parser = patch( "rpdk.core.init.get_parsers", return_value={"dummy": dummy_parser} ) - # pylint: disable=C0301 - with patch_project, patch_tn as mock_tn, patch_l as mock_l, patch_get_parser as mock_parser: # noqa: B950 - main(args_in=["init", "--type-name", args.type_name, args.language, "--dummy"]) + with patch_project, patch_tn as mock_tn, patch_get_parser as mock_parser: + main(args_in=["init", "-t", args.type_name, args.language, "--dummy"]) mock_tn.assert_called_once_with() - mock_l.assert_not_called() mock_parser.assert_called_once() mock_project.load_settings.assert_called_once_with() mock_project.init.assert_called_once_with( type_name, args.language, - args.settings, + { + "version": False, + "subparser_name": args.language, + "verbose": 0, + "force": False, + "type_name": args.type_name, + "language": args.language, + "dummy": True, + }, ) mock_project.generate.assert_called_once_with() diff --git a/tests/utils.py b/tests/utils.py index f11100f0..59025460 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -95,7 +95,7 @@ def get_mock_project(): return (mock_project, patch_project) -def get_args(interactive=False, language=True, type_name=True): +def get_args(language=None, type_name=None): args = Mock( spec_set=[ "language", @@ -104,23 +104,8 @@ def get_args(interactive=False, language=True, type_name=True): ] ) - args.language = ( - None if interactive else ("dummy" if language else "invalid_language") - ) - args.type_name = ( - None if interactive else ("Test::Test::Test" if type_name else "Test") - ) - args.settings = { - "version": False, - "subparser_name": None if interactive else ("dummy" if language else None), - "verbose": 0, - "force": False, - "type_name": args.type_name, - } - - if language and not interactive: - args.settings["dummy"] = True - args.settings["language"] = args.language + args.language = language + args.type_name = type_name return args From 90a5393e33ef9e3580b1a83dc3c9a2fc31be8efc Mon Sep 17 00:00:00 2001 From: Jesus Cabrera Date: Tue, 29 Sep 2020 17:25:24 -0400 Subject: [PATCH 13/13] Clean up testing --- tests/utils.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/utils.py b/tests/utils.py index 59025460..79d63d59 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -100,7 +100,6 @@ def get_args(language=None, type_name=None): spec_set=[ "language", "type_name", - "settings", ] )