From 9ea4ce8d61c28a95b164c655477bd8719c0527bf Mon Sep 17 00:00:00 2001 From: Dmitry Sorokin <40151847+DimedS@users.noreply.github.com> Date: Wed, 22 Nov 2023 20:07:20 +0000 Subject: [PATCH] Add an option to get an example pipeline (#3295) Add an option to get an example pipeline, fix and add new tests --------- Signed-off-by: Dmitry Sorokin --- MANIFEST.in | 2 +- RELEASE.md | 2 +- features/steps/cli_steps.py | 1 + kedro/framework/cli/starters.py | 88 ++++++++--- kedro/templates/project/cookiecutter.json | 3 +- .../project/hooks/post_gen_project.py | 3 +- kedro/templates/project/hooks/utils.py | 23 ++- kedro/templates/project/prompts.yml | 11 ++ tests/framework/cli/test_starters.py | 140 ++++++++++++++++-- tests/tools/test_cli.py | 2 + 10 files changed, 221 insertions(+), 54 deletions(-) diff --git a/MANIFEST.in b/MANIFEST.in index ad41ac26a3..3aaf04a960 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -3,4 +3,4 @@ include LICENSE.md include kedro/framework/project/default_logging.yml include kedro/ipython/*.png include kedro/ipython/*.svg -recursive-include templates * +recursive-include kedro/templates * diff --git a/RELEASE.md b/RELEASE.md index 7937293234..e02cb44dfc 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -2,7 +2,7 @@ ## Major features and improvements * Dropped Python 3.7 support. -* Introduced add-ons to the `kedro new` CLI flow. +* Introduced add-ons and example to the `kedro new` CLI flow. * The new spaceflights starters, `spaceflights-pandas`, `spaceflights-pandas-viz`, `spaceflights-pyspark`, and `spaceflights-pyspark-viz` can be used with the `kedro new` command with the `--starter` flag. * Added the `--conf-source` option to `%reload_kedro`, allowing users to specify a source for project configuration. * Added the functionality to choose a merging strategy for config files loaded with `OmegaConfigLoader`. diff --git a/features/steps/cli_steps.py b/features/steps/cli_steps.py index 22b26a02a8..355a745702 100644 --- a/features/steps/cli_steps.py +++ b/features/steps/cli_steps.py @@ -153,6 +153,7 @@ def create_config_file(context): config = { "add_ons": "1-5", "project_name": context.project_name, + "example_pipeline": "no", "repo_name": context.project_name, "output_dir": str(context.temp_dir), "python_package": context.package_name, diff --git a/kedro/framework/cli/starters.py b/kedro/framework/cli/starters.py index 48d1b4fc2d..1ae253431c 100644 --- a/kedro/framework/cli/starters.py +++ b/kedro/framework/cli/starters.py @@ -63,6 +63,7 @@ This can be the path to a local directory, a URL to a remote VCS repository supported by `cookiecutter` or one of the aliases listed in ``kedro starter list``. """ +EXAMPLE_ARG_HELP = "Enter y to enable, n to disable the example pipeline." @define(order=True) @@ -125,6 +126,27 @@ class KedroStarterSpec: # noqa: too-few-public-methods "7": "Kedro Viz", } +VALIDATION_PATTERNS = { + "yes_no": { + "regex": r"(?i)^\s*(y|yes|n|no)\s*$", + "error_message": "|It must contain only y, n, YES, NO, case insensitive.", + } +} + + +def _validate_regex(pattern_name, text): + if not re.match(VALIDATION_PATTERNS[pattern_name]["regex"], text): + click.secho( + VALIDATION_PATTERNS[pattern_name]["error_message"], + fg="red", + err=True, + ) + sys.exit(1) + + +def _parse_yes_no_to_bool(value): + return value.strip().lower() in ["y", "yes"] if value is not None else None + # noqa: missing-function-docstring @click.group(context_settings=CONTEXT_SETTINGS, name="Kedro") @@ -150,6 +172,7 @@ def starter(): @click.option("--directory", help=DIRECTORY_ARG_HELP) @click.option("--addons", "-a", "selected_add_ons_flag", help=ADDON_ARG_HELP) @click.option("--name", "-n", "project_name", help=NAME_ARG_HELP) +@click.option("--example", "-e", "example_pipeline", help=EXAMPLE_ARG_HELP) def new( # noqa: PLR0913 config_path, starter_alias, @@ -157,6 +180,7 @@ def new( # noqa: PLR0913 project_name, checkout, directory, + example_pipeline, # This will be True or False **kwargs, ): """Create a new kedro project.""" @@ -198,7 +222,7 @@ def new( # noqa: PLR0913 # Select which prompts will be displayed to the user based on which flags were selected. prompts_required = _select_prompts_to_display( - prompts_required, selected_add_ons_flag, project_name + prompts_required, selected_add_ons_flag, project_name, example_pipeline ) # We only need to make cookiecutter_context if interactive prompts are needed. @@ -221,6 +245,7 @@ def new( # noqa: PLR0913 cookiecutter_context=cookiecutter_context, selected_add_ons_flag=selected_add_ons_flag, project_name=project_name, + example_pipeline=example_pipeline, ) cookiecutter_args = _make_cookiecutter_args( @@ -370,12 +395,13 @@ def _get_starters_dict() -> dict[str, KedroStarterSpec]: return starter_specs -def _get_extra_context( +def _get_extra_context( # noqa: PLR0913 prompts_required: dict, config_path: str, cookiecutter_context: OrderedDict, selected_add_ons_flag: str | None, project_name: str | None, + example_pipeline: str | None, ) -> dict[str, str]: """Generates a config dictionary that will be passed to cookiecutter as `extra_context`, based on CLI flags, user prompts, or a configuration file. @@ -429,6 +455,14 @@ def _get_extra_context( ] extra_context["add_ons"] = str(extra_context["add_ons"]) + extra_context["example_pipeline"] = ( + _parse_yes_no_to_bool( + example_pipeline + if example_pipeline is not None + else extra_context.get("example_pipeline", "no") + ) # type: ignore + ) + return extra_context @@ -457,7 +491,10 @@ def _convert_addon_names_to_numbers(selected_add_ons_flag: str | None) -> str | def _select_prompts_to_display( - prompts_required: dict, selected_add_ons_flag: str, project_name: str + prompts_required: dict, + selected_add_ons_flag: str, + project_name: str, + example_pipeline: str, ) -> dict: """Selects which prompts an user will receive when creating a new Kedro project, based on what information was already made available @@ -470,6 +507,8 @@ def _select_prompts_to_display( or None in case the flag wasn't used. project_name: a string containing the value for the --name flag, or None in case the flag wasn't used. + example_pipeline: "Yes" or "No" for --example flag, or + None in case the flag wasn't used. Returns: the prompts_required dictionary, with all the redundant information removed. @@ -505,6 +544,10 @@ def _select_prompts_to_display( sys.exit(1) del prompts_required["project_name"] + if example_pipeline is not None: + _validate_regex("yes_no", example_pipeline) + del prompts_required["example_pipeline"] + return prompts_required @@ -577,23 +620,25 @@ def _fetch_config_from_user_prompts( def fetch_template_based_on_add_ons(template_path, cookiecutter_args: dict[str, Any]): extra_context = cookiecutter_args["extra_context"] - add_ons = extra_context.get("add_ons") + # If 'add_ons' or 'example_pipeline' are not specified in prompts.yml and not prompted in 'kedro new' options, + # default options will be used instead + add_ons = extra_context.get("add_ons", []) + example_pipeline = extra_context.get("example_pipeline", False) starter_path = "git+https://github.com/kedro-org/kedro-starters.git" - if add_ons: - if "Pyspark" in add_ons and "Kedro Viz" in add_ons: - # Use the spaceflights-pyspark-viz starter if both Pyspark and Kedro Viz are chosen. - cookiecutter_args["directory"] = "spaceflights-pyspark-viz" - elif "Pyspark" in add_ons: - # Use the spaceflights-pyspark starter if only Pyspark is chosen. - cookiecutter_args["directory"] = "spaceflights-pyspark" - elif "Kedro Viz" in add_ons: - # Use the spaceflights-pandas-viz starter if only Kedro Viz is chosen. - cookiecutter_args["directory"] = "spaceflights-pandas-viz" - else: - # Use the default template path for any other combinations or if "none" is chosen. - starter_path = template_path + if "Pyspark" in add_ons and "Kedro Viz" in add_ons: + # Use the spaceflights-pyspark-viz starter if both Pyspark and Kedro Viz are chosen. + cookiecutter_args["directory"] = "spaceflights-pyspark-viz" + elif "Pyspark" in add_ons: + # Use the spaceflights-pyspark starter if only Pyspark is chosen. + cookiecutter_args["directory"] = "spaceflights-pyspark" + elif "Kedro Viz" in add_ons: + # Use the spaceflights-pandas-viz starter if only Kedro Viz is chosen. + cookiecutter_args["directory"] = "spaceflights-pandas-viz" + elif example_pipeline: + # Use spaceflights-pandas starter if example was selected, but PySpark or Viz wasn't + cookiecutter_args["directory"] = "spaceflights-pandas" else: - # Use the default template path if add_ons is None, which can occur if there is no prompts.yml or its empty. + # Use the default template path for non Pyspark, Viz or example options: starter_path = template_path return starter_path @@ -706,6 +751,7 @@ def _validate_config_file_inputs(config: dict[str, str]): selected_add_ons = _parse_add_ons_input(input_add_ons) _validate_selection(selected_add_ons) + _validate_regex("yes_no", config.get("example_pipeline", "no")) def _validate_selection(add_ons: list[str]): @@ -789,10 +835,8 @@ def _create_project(template_path: str, cookiecutter_args: dict[str, Any]): ) add_ons = extra_context.get("add_ons") - # Only core template and spaceflight starters have configurable add-ons - if template_path == str(TEMPLATE_PATH) or ( - add_ons and ("Pyspark" in add_ons or "Kedro Viz" in add_ons) - ): + # we can use starters without add_ons: + if add_ons is not None: if add_ons == "[]": # TODO: This should be a list click.secho("\nYou have selected no add-ons") else: diff --git a/kedro/templates/project/cookiecutter.json b/kedro/templates/project/cookiecutter.json index ea3c62ea2e..61c308af96 100644 --- a/kedro/templates/project/cookiecutter.json +++ b/kedro/templates/project/cookiecutter.json @@ -3,5 +3,6 @@ "repo_name": "{{ cookiecutter.project_name.strip().replace(' ', '-').replace('_', '-').lower() }}", "python_package": "{{ cookiecutter.project_name.strip().replace(' ', '_').replace('-', '_').lower() }}", "kedro_version": "{{ cookiecutter.kedro_version }}", - "add_ons": "none" + "add_ons": "none", + "example_pipeline": "no" } diff --git a/kedro/templates/project/hooks/post_gen_project.py b/kedro/templates/project/hooks/post_gen_project.py index 4475c2f8f1..22e1629be2 100644 --- a/kedro/templates/project/hooks/post_gen_project.py +++ b/kedro/templates/project/hooks/post_gen_project.py @@ -15,9 +15,10 @@ def main(): # Get the selected add-ons from cookiecutter selected_add_ons = "{{ cookiecutter.add_ons }}" + example_pipeline = "{{ cookiecutter.example_pipeline }}" # Handle template directories and requirements according to selected add-ons - setup_template_add_ons(selected_add_ons, requirements_file_path, pyproject_file_path, python_package_name) + setup_template_add_ons(selected_add_ons, requirements_file_path, pyproject_file_path, python_package_name, example_pipeline) # Sort requirements.txt file in alphabetical order sort_requirements(requirements_file_path) diff --git a/kedro/templates/project/hooks/utils.py b/kedro/templates/project/hooks/utils.py index 77ca9a36b4..3ea4db22fe 100644 --- a/kedro/templates/project/hooks/utils.py +++ b/kedro/templates/project/hooks/utils.py @@ -105,11 +105,11 @@ def _remove_file(path: Path) -> None: path.unlink() -def _handle_starter_setup(selected_add_ons_list: str, python_package_name: str) -> None: +def _remove_pyspark_viz_starter_files(is_viz: bool, python_package_name: str) -> None: """Clean up the unnecessary files in the starters template. Args: - selected_add_ons_list (str): A string contains the selected add-ons. + is_viz (bool): if Viz included in starter, then need to remove "reporting" folder. python_package_name (str): The name of the python package. """ # Remove all .csv and .xlsx files from data/01_raw/ @@ -129,11 +129,8 @@ def _handle_starter_setup(selected_add_ons_list: str, python_package_name: str) for param_file in conf_base_path.glob(pattern): _remove_file(param_file) - # Remove the pipelines subdirectories - if "Kedro Viz" in selected_add_ons_list: # Remove reporting if Kedro Viz is selected - pipelines_to_remove = ["data_science", "data_processing", "reporting"] - else: - pipelines_to_remove = ["data_science", "data_processing"] + # Remove the pipelines subdirectories, if Viz - also "reporting" folder + pipelines_to_remove = ["data_science", "data_processing"] + (["reporting"] if is_viz else []) pipelines_path = current_dir / f"src/{python_package_name}/pipelines/" for pipeline_subdir in pipelines_to_remove: @@ -144,7 +141,7 @@ def _handle_starter_setup(selected_add_ons_list: str, python_package_name: str) _remove_file(test_pipeline_path) -def setup_template_add_ons(selected_add_ons_list: str, requirements_file_path: str, pyproject_file_path: str, python_package_name: str) -> None: +def setup_template_add_ons(selected_add_ons_list: str, requirements_file_path: str, pyproject_file_path: str, python_package_name: str, example_pipeline: str) -> None: """Setup the templates according to the choice of add-ons. Args: @@ -152,6 +149,7 @@ def setup_template_add_ons(selected_add_ons_list: str, requirements_file_path: s requirements_file_path (str): The path of the `requiremenets.txt` in the template. pyproject_file_path (str): The path of the `pyproject.toml` in the template python_package_name (str): The name of the python package. + example_pipeline (str): 'True' if example pipeline was selected """ if "Linting" not in selected_add_ons_list: _remove_from_file(requirements_file_path, lint_requirements) @@ -169,14 +167,11 @@ def setup_template_add_ons(selected_add_ons_list: str, requirements_file_path: s _remove_from_toml(pyproject_file_path, docs_pyproject_requirements) _remove_dir(current_dir / "docs") - if "Data Structure" not in selected_add_ons_list: + if "Data Structure" not in selected_add_ons_list and example_pipeline != "True": _remove_dir(current_dir / "data") - if "Pyspark" in selected_add_ons_list: - _handle_starter_setup(selected_add_ons_list, python_package_name) - - if "Kedro Viz" in selected_add_ons_list: - _handle_starter_setup(selected_add_ons_list, python_package_name) + if ("Pyspark" in selected_add_ons_list or "Kedro Viz" in selected_add_ons_list) and example_pipeline != "True": + _remove_pyspark_viz_starter_files("Kedro Viz" in selected_add_ons_list, python_package_name) def sort_requirements(requirements_file_path: Path) -> None: diff --git a/kedro/templates/project/prompts.yml b/kedro/templates/project/prompts.yml index f779fce1d3..9c9fa67faa 100644 --- a/kedro/templates/project/prompts.yml +++ b/kedro/templates/project/prompts.yml @@ -27,3 +27,14 @@ project_name: error_message: | It must contain only alphanumeric symbols, spaces, underscores and hyphens and be at least 2 characters long. + +example_pipeline: + title: "Example Pipeline" + text: | + Select whether you would like an example spaceflights pipeline included in your project. + To skip this step in the future use --example=y/n + To read more about how examples work visit: kedro.org/ + Would you like to include an example pipeline? \[y/N]: + regex_validator: "(?i)^(y|yes|n|no)$" + error_message: | + It must contain only y, n, YES, NO, case insensitive. diff --git a/tests/framework/cli/test_starters.py b/tests/framework/cli/test_starters.py index 87ebcc226a..cc73c0e022 100644 --- a/tests/framework/cli/test_starters.py +++ b/tests/framework/cli/test_starters.py @@ -18,6 +18,7 @@ KedroStarterSpec, _convert_addon_names_to_numbers, _parse_add_ons_input, + _parse_yes_no_to_bool, _validate_selection, ) @@ -54,9 +55,15 @@ def _write_yaml(filepath: Path, config: dict): def _make_cli_prompt_input( - add_ons="none", project_name="", repo_name="", python_package="" + add_ons="none", + project_name="", + example_pipeline="no", + repo_name="", + python_package="", ): - return "\n".join([add_ons, project_name, repo_name, python_package]) + return "\n".join( + [add_ons, project_name, example_pipeline, repo_name, python_package] + ) def _make_cli_prompt_input_without_addons( @@ -71,7 +78,7 @@ def _make_cli_prompt_input_without_name( return "\n".join([add_ons, repo_name, python_package]) -def _get_expected_files(add_ons: str): +def _get_expected_files(add_ons: str, example_pipeline: str): add_ons_template_files = { "1": 0, # Linting does not add any files "2": 3, # If Testing is selected, we add 2 init.py files and 1 test_run.py @@ -82,10 +89,27 @@ def _get_expected_files(add_ons: str): "7": 0, # Kedro Viz does not add any files } # files added to template by each add-on add_ons_list = _parse_add_ons_input(add_ons) + example_pipeline_bool = _parse_yes_no_to_bool(example_pipeline) expected_files = FILES_IN_TEMPLATE_WITH_NO_ADD_ONS for add_on in add_ons_list: expected_files = expected_files + add_ons_template_files[add_on] + # If example pipeline was chosen we don't need to delete /data folder + if example_pipeline_bool and "5" not in add_ons_list: + expected_files += add_ons_template_files["5"] + example_files_count = [ + 3, # Raw data files + 2, # Parameters_ .yml files + 6, # .py files in pipelines folder + ] + if example_pipeline_bool: # If example option is chosen + expected_files += sum(example_files_count) + expected_files += ( + 4 if "7" in add_ons_list else 0 + ) # add 3 .py and 1 parameters files in reporting for Viz + expected_files += ( + 1 if "2" in add_ons_list else 0 + ) # add 1 test file if tests is chosen in add_ons return expected_files @@ -179,6 +203,7 @@ def _assert_template_ok( result, add_ons="none", project_name="New Kedro Project", + example_pipeline="no", repo_name="new-kedro-project", python_package="new_kedro_project", kedro_version=version, @@ -192,7 +217,7 @@ def _assert_template_ok( p for p in full_path.rglob("*") if p.is_file() and p.name != ".DS_Store" ] - assert len(generated_files) == _get_expected_files(add_ons) + assert len(generated_files) == _get_expected_files(add_ons, example_pipeline) assert full_path.exists() assert (full_path / ".gitignore").is_file() assert project_name in (full_path / "README.md").read_text(encoding="utf-8") @@ -488,6 +513,7 @@ def test_required_keys_only(self, fake_kedro_cli): config = { "add_ons": "none", "project_name": "My Project", + "example_pipeline": "no", "repo_name": "my-project", "python_package": "my_project", } @@ -503,6 +529,7 @@ def test_custom_required_keys(self, fake_kedro_cli): config = { "add_ons": "none", "project_name": "Project X", + "example_pipeline": "no", "repo_name": "projectx", "python_package": "proj_x", } @@ -518,6 +545,7 @@ def test_custom_kedro_version(self, fake_kedro_cli): config = { "add_ons": "none", "project_name": "My Project", + "example_pipeline": "no", "repo_name": "my-project", "python_package": "my_project", "kedro_version": "my_version", @@ -534,6 +562,7 @@ def test_custom_output_dir(self, fake_kedro_cli): config = { "add_ons": "none", "project_name": "My Project", + "example_pipeline": "no", "repo_name": "my-project", "python_package": "my_project", "output_dir": "my_output_dir", @@ -551,6 +580,7 @@ def test_extra_keys_allowed(self, fake_kedro_cli): config = { "add_ons": "none", "project_name": "My Project", + "example_pipeline": "no", "repo_name": "my-project", "python_package": "my_project", } @@ -599,6 +629,7 @@ def test_output_dir_does_not_exist(self, fake_kedro_cli): config = { "add_ons": "none", "project_name": "My Project", + "example_pipeline": "no", "repo_name": "my-project", "python_package": "my_project", "output_dir": "does_not_exist", @@ -612,6 +643,7 @@ def test_config_missing_key(self, fake_kedro_cli): """Check the error if keys are missing from config file.""" config = { "add_ons": "none", + "example_pipeline": "no", "python_package": "my_project", "repo_name": "my-project", } @@ -644,6 +676,7 @@ def test_invalid_project_name_special_characters(self, fake_kedro_cli): config = { "add_ons": "none", "project_name": "My $Project!", + "example_pipeline": "no", "repo_name": "my-project", "python_package": "my_project", } @@ -662,6 +695,7 @@ def test_invalid_project_name_too_short(self, fake_kedro_cli): config = { "add_ons": "none", "project_name": "P", + "example_pipeline": "no", "repo_name": "my-project", "python_package": "my_project", } @@ -874,7 +908,7 @@ def test_directory_flag_with_starter_alias(self, fake_kedro_cli): @pytest.mark.usefixtures("chdir_to_tmp") -class TestAddOnsFromUserPrompts: +class TestAddOnsAndExampleFromUserPrompts: @pytest.mark.parametrize( "add_ons", [ @@ -894,14 +928,17 @@ class TestAddOnsFromUserPrompts: "ALL", ], ) - def test_valid_add_ons(self, fake_kedro_cli, add_ons): + @pytest.mark.parametrize("example_pipeline", ["Yes", "No"]) + def test_valid_add_ons_and_example(self, fake_kedro_cli, add_ons, example_pipeline): result = CliRunner().invoke( fake_kedro_cli, ["new"], - input=_make_cli_prompt_input(add_ons=add_ons), + input=_make_cli_prompt_input( + add_ons=add_ons, example_pipeline=example_pipeline + ), ) - _assert_template_ok(result, add_ons=add_ons) + _assert_template_ok(result, add_ons=add_ons, example_pipeline=example_pipeline) _assert_requirements_ok(result, add_ons=add_ons) _clean_up_project(Path("./new-kedro-project")) @@ -953,9 +990,37 @@ def test_invalid_add_ons_range(self, fake_kedro_cli, input): message = f"'{input}' is an invalid range for project add-ons.\nPlease ensure range values go from smaller to larger." assert message in result.output + @pytest.mark.parametrize("example_pipeline", ["y", "n", "N", "YEs", " yeS "]) + def test_valid_example(self, fake_kedro_cli, example_pipeline): + result = CliRunner().invoke( + fake_kedro_cli, + ["new"], + input=_make_cli_prompt_input(example_pipeline=example_pipeline), + ) + + _assert_template_ok(result, example_pipeline=example_pipeline) + _clean_up_project(Path("./new-kedro-project")) + + @pytest.mark.parametrize( + "bad_input", + ["bad input", "Not", "ye", "True", "t"], + ) + def test_invalid_example(self, fake_kedro_cli, bad_input): + result = CliRunner().invoke( + fake_kedro_cli, + ["new"], + input=_make_cli_prompt_input(example_pipeline=bad_input), + ) + + assert result.exit_code != 0 + assert "is an invalid value for example pipeline." in result.output + assert ( + "It must contain only y, n, YES, NO, case insensitive.\n" in result.output + ) + @pytest.mark.usefixtures("chdir_to_tmp") -class TestAddOnsFromConfigFile: +class TestAddOnsAndExampleFromConfigFile: @pytest.mark.parametrize( "add_ons", [ @@ -975,11 +1040,13 @@ class TestAddOnsFromConfigFile: "ALL", ], ) - def test_valid_add_ons(self, fake_kedro_cli, add_ons): + @pytest.mark.parametrize("example_pipeline", ["Yes", "No"]) + def test_valid_add_ons_and_example(self, fake_kedro_cli, add_ons, example_pipeline): """Test project created from config.""" config = { "add_ons": add_ons, "project_name": "New Kedro Project", + "example_pipeline": example_pipeline, "repo_name": "new-kedro-project", "python_package": "new_kedro_project", } @@ -1001,6 +1068,7 @@ def test_invalid_add_ons(self, fake_kedro_cli, bad_input): config = { "add_ons": bad_input, "project_name": "My Project", + "example_pipeline": "no", "repo_name": "my-project", "python_package": "my_project", } @@ -1024,6 +1092,7 @@ def test_invalid_add_ons_selection(self, fake_kedro_cli, input, last_invalid): config = { "add_ons": input, "project_name": "My Project", + "example_pipeline": "no", "repo_name": "my-project", "python_package": "my_project", } @@ -1044,6 +1113,7 @@ def test_invalid_add_ons_range(self, fake_kedro_cli, input): config = { "add_ons": input, "project_name": "My Project", + "example_pipeline": "no", "repo_name": "my-project", "python_package": "my_project", } @@ -1056,9 +1126,50 @@ def test_invalid_add_ons_range(self, fake_kedro_cli, input): message = f"'{input}' is an invalid range for project add-ons.\nPlease ensure range values go from smaller to larger." assert message in result.output + @pytest.mark.parametrize("example_pipeline", ["y", "n", "N", "YEs", " yeS "]) + def test_valid_example(self, fake_kedro_cli, example_pipeline): + """Test project created from config.""" + config = { + "add_ons": "none", + "project_name": "New Kedro Project", + "example_pipeline": example_pipeline, + "repo_name": "new-kedro-project", + "python_package": "new_kedro_project", + } + _write_yaml(Path("config.yml"), config) + result = CliRunner().invoke( + fake_kedro_cli, ["new", "-v", "--config", "config.yml"] + ) + + _assert_template_ok(result, **config) + _clean_up_project(Path("./new-kedro-project")) + + @pytest.mark.parametrize( + "bad_input", + ["bad input", "Not", "ye", "True", "t"], + ) + def test_invalid_example(self, fake_kedro_cli, bad_input): + """Test project created from config.""" + config = { + "add_ons": "none", + "project_name": "My Project", + "example_pipeline": bad_input, + "repo_name": "my-project", + "python_package": "my_project", + } + _write_yaml(Path("config.yml"), config) + result = CliRunner().invoke( + fake_kedro_cli, ["new", "-v", "--config", "config.yml"] + ) + + assert result.exit_code != 0 + assert ( + "It must contain only y, n, YES, NO, case insensitive.\n" in result.output + ) + @pytest.mark.usefixtures("chdir_to_tmp") -class TestAddOnsFromCLI: +class TestAddOnsAndExampleFromCLI: @pytest.mark.parametrize( "add_ons", [ @@ -1083,14 +1194,15 @@ class TestAddOnsFromCLI: "test, DATA, liNt", ], ) - def test_valid_add_ons_flag(self, fake_kedro_cli, add_ons): + @pytest.mark.parametrize("example_pipeline", ["Yes", "No"]) + def test_valid_add_ons_flag(self, fake_kedro_cli, add_ons, example_pipeline): result = CliRunner().invoke( fake_kedro_cli, - ["new", "--addons", add_ons], + ["new", "--addons", add_ons, "--example", example_pipeline], input=_make_cli_prompt_input_without_addons(), ) add_ons = _convert_addon_names_to_numbers(selected_add_ons_flag=add_ons) - _assert_template_ok(result, add_ons=add_ons) + _assert_template_ok(result, add_ons=add_ons, example_pipeline=example_pipeline) _assert_requirements_ok(result, add_ons=add_ons, repo_name="new-kedro-project") _clean_up_project(Path("./new-kedro-project")) diff --git a/tests/tools/test_cli.py b/tests/tools/test_cli.py index 5dbb4b740a..da01af4ee0 100644 --- a/tests/tools/test_cli.py +++ b/tests/tools/test_cli.py @@ -103,6 +103,8 @@ def test_get_cli_structure_depth(self, mocker, fake_metadata): "-c", "--addons", "-a", + "--example", + "-e", "--starter", "-s", "--name",