diff --git a/kedro/framework/cli/starters.py b/kedro/framework/cli/starters.py index 1442a86224..c38d554038 100644 --- a/kedro/framework/cli/starters.py +++ b/kedro/framework/cli/starters.py @@ -108,7 +108,8 @@ class KedroStarterSpec: # noqa: too-few-public-methods 3) Custom Logging: Provides more logging options\n 4) Documentation: Basic documentation setup with Sphinx\n 5) Data Structure: Provides a directory structure for storing data\n -6) Pyspark: Provides a basic PySpark set up\n +6) Pyspark: Provides set up configuration for working with PySpark\n +7) Kedro Viz: Provides Kedro's native visualisation tool \n Example usage:\n kedro new --addons=lint,test,log,docs,data,pyspark (or any subset of these options)\n @@ -123,6 +124,7 @@ class KedroStarterSpec: # noqa: too-few-public-methods "docs": "4", "data": "5", "pyspark": "6", + "viz": "7", } NUMBER_TO_ADD_ONS_NAME = { "1": "Linting", @@ -131,6 +133,7 @@ class KedroStarterSpec: # noqa: too-few-public-methods "4": "Documentation", "5": "Data Structure", "6": "Pyspark", + "7": "Kedro Viz", } @@ -223,7 +226,7 @@ def _validate_range(start, end): def _validate_selection(add_ons: list[str]): for add_on in add_ons: if add_on not in NUMBER_TO_ADD_ONS_NAME: - message = f"'{add_on}' is not a valid selection.\nPlease select from the available add-ons: 1, 2, 3, 4, 5, 6." # nosec + message = f"'{add_on}' is not a valid selection.\nPlease select from the available add-ons: 1, 2, 3, 4, 5, 6, 7." # nosec click.secho(message, fg="red", err=True) sys.exit(1) @@ -475,7 +478,7 @@ def _select_prompts_to_display( for addon in addons: if addon not in valid_addons: click.secho( - "Please select from the available add-ons: lint, test, log, docs, data, pyspark, all, none", + "Please select from the available add-ons: lint, test, log, docs, data, pyspark, viz, all, none", fg="red", err=True, ) @@ -581,11 +584,24 @@ def _make_cookiecutter_args( 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 and "Pyspark" in add_ons: - cookiecutter_args["directory"] = "spaceflights-pyspark" - pyspark_path = "git+https://github.com/kedro-org/kedro-starters.git" - return pyspark_path - return template_path + 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 + else: + # Use the default template path if add_ons is None, which can occur if there is no prompts.yml or its empty. + starter_path = template_path + return starter_path def _create_project(template_path: str, cookiecutter_args: dict[str, Any]): @@ -619,11 +635,9 @@ def _create_project(template_path: str, cookiecutter_args: dict[str, Any]): ) add_ons = extra_context.get("add_ons") - # Only core template and spaceflights-pyspark have configurable add-ons - if ( - template_path == str(TEMPLATE_PATH) - or add_ons is not None - and "Pyspark" in 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) ): if add_ons == "[]": # TODO: This should be a list click.secho("\nYou have selected no add-ons") diff --git a/kedro/templates/project/hooks/utils.py b/kedro/templates/project/hooks/utils.py index 00a232bfa0..77ca9a36b4 100644 --- a/kedro/templates/project/hooks/utils.py +++ b/kedro/templates/project/hooks/utils.py @@ -1,140 +1,189 @@ from pathlib import Path import shutil -import sys -import click +import toml current_dir = Path.cwd() -lint_requirements = "black~=22.12.0\nruff~=0.0.290\n" -lint_pyproject_requirements = """ -[tool.ruff] -select = [ - "F", # Pyflakes - "E", # Pycodestyle - "W", # Pycodestyle - "UP", # pyupgrade - "I", # isort - "PL", # Pylint -] -ignore = ["E501"] # Black takes care of line-too-long -""" - -test_requirements = "pytest-cov~=3.0\npytest-mock>=1.7.1, <2.0\npytest~=7.2" -test_pyproject_requirements = """ -[tool.pytest.ini_options] -addopts = \"\"\" ---cov-report term-missing \\ ---cov src/{{ cookiecutter.python_package }} -ra -\"\"\" - -[tool.coverage.report] -fail_under = 0 -show_missing = true -exclude_lines = ["pragma: no cover", "raise NotImplementedError"] -""" - -docs_pyproject_requirements = """ -[project.optional-dependencies] -docs = [ - "docutils<0.18.0", - "sphinx~=3.4.3", - "sphinx_rtd_theme==0.5.1", - "nbsphinx==0.8.1", - "sphinx-autodoc-typehints==1.11.1", - "sphinx_copybutton==0.3.1", - "ipykernel>=5.3, <7.0", - "Jinja2<3.1.0", - "myst-parser~=0.17.2", -] -""" - - -def setup_template_add_ons(selected_add_ons_list, requirements_file_path, pyproject_file_path, python_package_name): - """Removes directories and files related to unwanted addons from - a Kedro project template. Adds the necessary requirements for - the addons that were selected. +# Requirements for linting tools +lint_requirements = "black~=22.0\nruff~=0.0.290\n" # For requirements.txt +lint_pyproject_requirements = ["tool.ruff"] # For pyproject.toml + +# Requirements and configurations for testing tools and coverage reporting +test_requirements = "pytest-cov~=3.0\npytest-mock>=1.7.1, <2.0\npytest~=7.2" # For requirements.txt +test_pyproject_requirements = ["tool.pytest.ini_options", "tool.coverage.report"] # For pyproject.toml + +# Configuration key for documentation dependencies +docs_pyproject_requirements = ["project.optional-dependencies"] # For pyproject.toml + + +# Helper Functions +def _remove_from_file(file_path: Path, content_to_remove: str) -> None: + """Remove specified content from the file. Args: - selected_add_ons_list: a list containing numbers from 1 to 5, - representing specific add-ons. - requirements_file_path: the path to the requirements.txt file. - pyproject_file_path: the path to the pyproject.toml file - located on the the root of the template. + file_path (Path): The path of the file from which to remove content. + content_to_remove (str): The content to be removed from the file. """ - if "Linting" not in selected_add_ons_list: - pass + with open(file_path, 'r') as file: + lines = file.readlines() + + # Split the content to remove into lines and remove trailing whitespaces/newlines + content_to_remove_lines = [line.strip() for line in content_to_remove.split('\n')] + + # Keep lines that are not in content_to_remove + lines = [line for line in lines if line.strip() not in content_to_remove_lines] + + with open(file_path, 'w') as file: + file.writelines(lines) + + +def _remove_nested_section(data: dict, nested_key: str) -> None: + """Remove a nested section from a dictionary representing a TOML file. + + Args: + data (dict): The dictionary from which to remove the section. + nested_key (str): The dotted path key representing the nested section to remove. + """ + keys = nested_key.split('.') + current_data = data + # Look for Parent section + for key in keys[:-1]: # Iterate over all but last element + if key in current_data: + current_data = current_data[key] + else: + return # Parent section not found, nothing to remove + + # Remove the nested section and any empty parent sections + current_data.pop(keys[-1], None) # Remove last element otherwise return None + for key in reversed(keys[:-1]): + parent_section = data + for key_part in keys[:keys.index(key)]: + parent_section = parent_section[key_part] + if not current_data: # If the section is empty, remove it + parent_section.pop(key, None) + current_data = parent_section + else: + break # If the section is not empty, stop removing + + +def _remove_from_toml(file_path: Path, sections_to_remove: list) -> None: + """Remove specified sections from a TOML file. + + Args: + file_path (Path): The path to the TOML file. + sections_to_remove (list): A list of section keys to remove from the TOML file. + """ + # Load the TOML file + with open(file_path, 'r') as file: + data = toml.load(file) + + # Remove the specified sections + for section in sections_to_remove: + _remove_nested_section(data, section) + + with open(file_path, 'w') as file: + toml.dump(data, file) + + +def _remove_dir(path: Path) -> None: + """Remove a directory if it exists. + + Args: + path (Path): The path of the directory to remove. + """ + if path.exists(): + shutil.rmtree(str(path)) + + +def _remove_file(path: Path) -> None: + """Remove a file if it exists. + + Args: + path (Path): The path of the file to remove. + """ + if path.exists(): + path.unlink() + + +def _handle_starter_setup(selected_add_ons_list: str, 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. + python_package_name (str): The name of the python package. + """ + # Remove all .csv and .xlsx files from data/01_raw/ + raw_data_path = current_dir / "data/01_raw/" + for file_path in raw_data_path.glob("*.*"): + if file_path.suffix in [".csv", ".xlsx"]: + file_path.unlink() + + # Empty the contents of conf/base/catalog.yml + catalog_yml_path = current_dir / "conf/base/catalog.yml" + if catalog_yml_path.exists(): + catalog_yml_path.write_text('') + # Remove parameter files from conf/base + conf_base_path = current_dir / "conf/base/" + parameter_file_patterns = ["parameters_*.yml", "parameters/*.yml"] + for pattern in parameter_file_patterns: + 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: - with open(requirements_file_path, 'a') as file: - file.write(lint_requirements) - with open(pyproject_file_path, 'a') as file: - file.write(lint_pyproject_requirements) + pipelines_to_remove = ["data_science", "data_processing"] + + pipelines_path = current_dir / f"src/{python_package_name}/pipelines/" + for pipeline_subdir in pipelines_to_remove: + _remove_dir(pipelines_path / pipeline_subdir) + + # Remove all test files from tests/pipelines/ + test_pipeline_path = current_dir / "tests/pipelines/test_data_science.py" + _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: + """Setup the templates according to the choice of add-ons. + + Args: + selected_add_ons_list (str): A string contains the selected add-ons. + 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. + """ + if "Linting" not in selected_add_ons_list: + _remove_from_file(requirements_file_path, lint_requirements) + _remove_from_toml(pyproject_file_path, lint_pyproject_requirements) if "Testing" not in selected_add_ons_list: - tests_path = current_dir / "tests" - if tests_path.exists(): - shutil.rmtree(str(tests_path)) - else: - with open(requirements_file_path, 'a') as file: - file.write(test_requirements) - with open(pyproject_file_path, 'a') as file: - file.write(test_pyproject_requirements) + _remove_from_file(requirements_file_path, test_requirements) + _remove_from_toml(pyproject_file_path, test_pyproject_requirements) + _remove_dir(current_dir / "tests") if "Logging" not in selected_add_ons_list: - logging_yml_path = current_dir / "conf/logging.yml" - if logging_yml_path.exists(): - logging_yml_path.unlink() + _remove_file(current_dir / "conf/logging.yml") if "Documentation" not in selected_add_ons_list: - docs_path = current_dir / "docs" - if docs_path.exists(): - shutil.rmtree(str(docs_path)) - else: - with open(pyproject_file_path, 'a') as file: - file.write(docs_pyproject_requirements) + _remove_from_toml(pyproject_file_path, docs_pyproject_requirements) + _remove_dir(current_dir / "docs") if "Data Structure" not in selected_add_ons_list: - data_path = current_dir / "data" - if data_path.exists(): - shutil.rmtree(str(data_path)) - - if "Pyspark" not in selected_add_ons_list: # If PySpark not selected - pass - else: # Use spaceflights-pyspark to create pyspark template - # Remove all .csv and .xlsx files from data/01_raw/ - raw_data_path = current_dir / "data/01_raw/" - if raw_data_path.exists() and raw_data_path.is_dir(): - for file_path in raw_data_path.glob("*.*"): - if file_path.suffix in [".csv", ".xlsx"]: - file_path.unlink() - - # Remove parameter files from conf/base/ - param_files = [ - "parameters_data_processing.yml", - "parameters_data_science.yml", - ] - conf_base_path = current_dir / "conf/base/" - if conf_base_path.exists() and conf_base_path.is_dir(): - for param_file in param_files: - file_path = conf_base_path / param_file - if file_path.exists(): - file_path.unlink() - - # Remove specific pipeline subdirectories - pipelines_path = current_dir / f"src/{python_package_name}/pipelines/" - for pipeline_subdir in ["data_science", "data_processing"]: - shutil.rmtree(pipelines_path / pipeline_subdir, ignore_errors=True) - - # Remove all test file from tests/pipelines/ - test_pipeline_path = current_dir / "tests/pipelines/test_data_science.py" - if test_pipeline_path.exists(): - test_pipeline_path.unlink() - - -def sort_requirements(requirements_file_path): - """Sort the requirements.txt file in alphabetical order. + _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) + + +def sort_requirements(requirements_file_path: Path) -> None: + """Sort the requirements.txt file alphabetically and write it back to the file. Args: - requirements_file_path: the path to the requirements.txt file. + requirements_file_path (Path): The path to the `requirements.txt` file. """ with open(requirements_file_path, 'r') as requirements: lines = requirements.readlines() diff --git a/kedro/templates/project/prompts.yml b/kedro/templates/project/prompts.yml index 1cee3def2b..3889b03c05 100644 --- a/kedro/templates/project/prompts.yml +++ b/kedro/templates/project/prompts.yml @@ -10,9 +10,10 @@ add_ons: 3) Custom Logging : Provides more logging options 4) Documentation: Provides basic documentations setup with Sphinx 5) Data Structure: Provides a directory structure for storing data - 6) PySpark : Provides a basic PySpark set up + 6) Pyspark: Provides set up configuration for working with PySpark + 7) Kedro Viz: Provides Kedro's native visualisation tool - Which add-ons would you like to include in your project? [1-6/1,3/all/none]: + Which add-ons would you like to include in your project? [1-7/1,3/all/none]: regex_validator: "^(all|none|(\\d(,\\d)*|(\\d-\\d)))$" error_message: | Invalid input. Please select valid options for add-ons using comma-separated values, ranges, or 'all/none'. diff --git a/kedro/templates/project/{{ cookiecutter.repo_name }}/conf/local/.gitkeep b/kedro/templates/project/{{ cookiecutter.repo_name }}/conf/local/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/kedro/templates/project/{{ cookiecutter.repo_name }}/pyproject.toml b/kedro/templates/project/{{ cookiecutter.repo_name }}/pyproject.toml index e4c007e3a4..92810db841 100644 --- a/kedro/templates/project/{{ cookiecutter.repo_name }}/pyproject.toml +++ b/kedro/templates/project/{{ cookiecutter.repo_name }}/pyproject.toml @@ -12,6 +12,19 @@ dynamic = ["dependencies", "version"] [project.entry-points."kedro.hooks"] +[project.optional-dependencies] +docs = [ + "docutils<0.18.0", + "sphinx~=3.4.3", + "sphinx_rtd_theme==0.5.1", + "nbsphinx==0.8.1", + "sphinx-autodoc-typehints==1.11.1", + "sphinx_copybutton==0.3.1", + "ipykernel>=5.3, <7.0", + "Jinja2<3.1.0", + "myst-parser~=0.17.2", +] + [tool.setuptools.dynamic] dependencies = {file = "requirements.txt"} version = {attr = "{{ cookiecutter.python_package }}.__version__"} @@ -24,4 +37,28 @@ namespaces = false package_name = "{{ cookiecutter.python_package }}" project_name = "{{ cookiecutter.project_name }}" kedro_init_version = "{{ cookiecutter.kedro_version }}" -add_ons = {{ cookiecutter.add_ons | string | replace("\'", "\"") }} +add_ons = "{{ cookiecutter.add_ons | default('') | string | replace('\"', '\\\"') }}" + +[tool.pytest.ini_options] +addopts = """ +--cov-report term-missing \ +--cov src/{{ cookiecutter.python_package }} -ra""" + +[tool.coverage.report] +fail_under = 0 +show_missing = true +exclude_lines = ["pragma: no cover", "raise NotImplementedError"] + +[tool.ruff] +line-length = 88 +show-fixes = true +select = [ + "F", # Pyflakes + "W", # pycodestyle + "E", # pycodestyle + "I", # isort + "UP", # pyupgrade + "PL", # Pylint + "T201", # Print Statement +] +ignore = ["E501"] # Black takes care of line-too-long diff --git a/kedro/templates/project/{{ cookiecutter.repo_name }}/requirements.txt b/kedro/templates/project/{{ cookiecutter.repo_name }}/requirements.txt index e1bf2945eb..ea1a34549c 100644 --- a/kedro/templates/project/{{ cookiecutter.repo_name }}/requirements.txt +++ b/kedro/templates/project/{{ cookiecutter.repo_name }}/requirements.txt @@ -1,9 +1,13 @@ -ipython>=7.31.1, <8.0; python_version < '3.8' +black~=22.0 ipython~=8.10; python_version >= '3.8' jupyter~=1.0 jupyterlab_server>=2.11.1, <2.16.0 jupyterlab~=3.0, <3.6.0 kedro~={{ cookiecutter.kedro_version }} kedro-telemetry~=0.2.0 +pytest-cov~=3.0 +pytest-mock>=1.7.1, <2.0 +pytest~=7.2 +ruff~=0.0.290 # Pin problematic traitlets release - https://github.com/jupyter/notebook/issues/7048 traitlets<5.10.0 diff --git a/tests/framework/cli/test_starters.py b/tests/framework/cli/test_starters.py index b1893d1d8a..9f2e6ecb08 100644 --- a/tests/framework/cli/test_starters.py +++ b/tests/framework/cli/test_starters.py @@ -6,6 +6,7 @@ from pathlib import Path import pytest +import toml import yaml from click.testing import CliRunner from cookiecutter.exceptions import RepositoryCloneFailed @@ -19,7 +20,7 @@ _parse_add_ons_input, ) -FILES_IN_TEMPLATE_WITH_NO_ADD_ONS = 14 +FILES_IN_TEMPLATE_WITH_NO_ADD_ONS = 15 @pytest.fixture @@ -71,12 +72,13 @@ def _make_cli_prompt_input_without_name( def _get_expected_files(add_ons: str): add_ons_template_files = { - "1": 0, - "2": 3, - "3": 1, - "4": 2, - "5": 8, - "6": 3, + "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 + "3": 1, # If Logging is selected, we add logging.py + "4": 2, # If Documentation is selected, we add conf.py and index.rst + "5": 8, # If Data Structure is selected, we add 8 .gitkeep files + "6": 2, # If Pyspark is selected, we add spark.yml and hooks.py + "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) @@ -94,7 +96,6 @@ def _assert_requirements_ok( repo_name="new-kedro-project", output_dir=".", ): - assert result.exit_code == 0, result.output assert "Change directory to the project generated in" in result.output @@ -111,26 +112,18 @@ def _assert_requirements_ok( assert "black" in requirements assert "ruff" in requirements - with open(pyproject_file_path) as pyproject_file: - requirements = pyproject_file.read() - - assert ( - ( - """ -[tool.ruff] -select = [ - "F", # Pyflakes - "E", # Pycodestyle - "W", # Pycodestyle - "UP", # pyupgrade - "I", # isort - "PL", # Pylint -] -ignore = ["E501"] # Black takes care of line-too-long -""" - ) - in requirements - ) + pyproject_config = toml.load(pyproject_file_path) + expected = { + "tool": { + "ruff": { + "line-length": 88, + "show-fixes": True, + "select": ["F", "W", "E", "I", "UP", "PL", "T201"], + "ignore": ["E501"], + } + } + } + assert expected["tool"]["ruff"] == pyproject_config["tool"]["ruff"] if "2" in add_ons_list: with open(requirements_file_path) as requirements_file: @@ -140,48 +133,44 @@ def _assert_requirements_ok( assert "pytest-mock>=1.7.1, <2.0" in requirements assert "pytest~=7.2" in requirements - with open(pyproject_file_path) as pyproject_file: - requirements = pyproject_file.read() - - assert ( - ( - """ -[tool.pytest.ini_options] -addopts = \"\"\" ---cov-report term-missing \\ ---cov src/{{ cookiecutter.python_package }} -ra -\"\"\" - -[tool.coverage.report] -fail_under = 0 -show_missing = true -exclude_lines = ["pragma: no cover", "raise NotImplementedError"] -""" - ) - in requirements - ) + pyproject_config = toml.load(pyproject_file_path) + expected = { + "pytest": { + "ini_options": { + "addopts": "--cov-report term-missing --cov src/new_kedro_project -ra" + } + }, + "coverage": { + "report": { + "fail_under": 0, + "show_missing": True, + "exclude_lines": ["pragma: no cover", "raise NotImplementedError"], + } + }, + } + assert expected["pytest"] == pyproject_config["tool"]["pytest"] + assert expected["coverage"] == pyproject_config["tool"]["coverage"] if "4" in add_ons_list: - with open(pyproject_file_path) as pyproject_file: - requirements = pyproject_file.read() - + pyproject_config = toml.load(pyproject_file_path) + expected = { + "optional-dependencies": { + "docs": [ + "docutils<0.18.0", + "sphinx~=3.4.3", + "sphinx_rtd_theme==0.5.1", + "nbsphinx==0.8.1", + "sphinx-autodoc-typehints==1.11.1", + "sphinx_copybutton==0.3.1", + "ipykernel>=5.3, <7.0", + "Jinja2<3.1.0", + "myst-parser~=0.17.2", + ] + } + } assert ( - ( - """ -docs = [ - "docutils<0.18.0", - "sphinx~=3.4.3", - "sphinx_rtd_theme==0.5.1", - "nbsphinx==0.8.1", - "sphinx-autodoc-typehints==1.11.1", - "sphinx_copybutton==0.3.1", - "ipykernel>=5.3, <7.0", - "Jinja2<3.1.0", - "myst-parser~=0.17.2", -] -""" - ) - in requirements + expected["optional-dependencies"]["docs"] + == pyproject_config["project"]["optional-dependencies"]["docs"] ) @@ -195,7 +184,6 @@ def _assert_template_ok( kedro_version=version, output_dir=".", ): - assert result.exit_code == 0, result.output assert "Change directory to the project generated in" in result.output @@ -276,7 +264,7 @@ def test_starter_list_with_invalid_starter_plugin( ("1,2,3", ["1", "2", "3"]), ("2-4", ["2", "3", "4"]), ("3-3", ["3"]), - ("all", ["1", "2", "3", "4", "5", "6"]), + ("all", ["1", "2", "3", "4", "5", "6", "7"]), ("none", []), ], ) @@ -298,12 +286,12 @@ def test_parse_add_ons_invalid_range(input, capsys): @pytest.mark.parametrize( "input,first_invalid", - [("0,3,5", "0"), ("1,3,7", "7"), ("0-4", "0"), ("3-7", "7")], + [("0,3,5", "0"), ("1,3,8", "8"), ("0-4", "0"), ("3-8", "8")], ) def test_parse_add_ons_invalid_selection(input, first_invalid, capsys): with pytest.raises(SystemExit): _parse_add_ons_input(input) - message = f"'{first_invalid}' is not a valid selection.\nPlease select from the available add-ons: 1, 2, 3, 4, 5, 6." + message = f"'{first_invalid}' is not a valid selection.\nPlease select from the available add-ons: 1, 2, 3, 4, 5, 6, 7." assert message in capsys.readouterr().err @@ -888,7 +876,7 @@ def test_directory_flag_with_starter_alias(self, fake_kedro_cli): class TestAddOnsFromUserPrompts: @pytest.mark.parametrize( "add_ons", - ["1", "2", "3", "4", "5", "6", "none", "2,3,4", "3-5", "all"], + ["1", "2", "3", "4", "5", "6", "7", "none", "2,3,4", "3-5", "all"], ) def test_valid_add_ons(self, fake_kedro_cli, add_ons): result = CliRunner().invoke( @@ -920,15 +908,15 @@ def test_invalid_add_ons(self, fake_kedro_cli): class TestAddOnsFromConfigFile: @pytest.mark.parametrize( "add_ons", - ["1", "2", "3", "4", "5", "6", "none", "2,3,4", "3-5", "all"], + ["1", "2", "3", "4", "5", "6", "7", "none", "2,3,4", "3-5", "all"], ) def test_valid_add_ons(self, fake_kedro_cli, add_ons): """Test project created from config.""" config = { "add_ons": add_ons, - "project_name": "My Project", - "repo_name": "my-project", - "python_package": "my_project", + "project_name": "New Kedro Project", + "repo_name": "new-kedro-project", + "python_package": "new_kedro_project", } _write_yaml(Path("config.yml"), config) result = CliRunner().invoke( @@ -936,8 +924,8 @@ def test_valid_add_ons(self, fake_kedro_cli, add_ons): ) _assert_template_ok(result, **config) - _assert_requirements_ok(result, add_ons=add_ons, repo_name="my-project") - _clean_up_project(Path("./my-project")) + _assert_requirements_ok(result, add_ons=add_ons, repo_name="new-kedro-project") + _clean_up_project(Path("./new-kedro-project")) def test_invalid_add_ons(self, fake_kedro_cli): """Test project created from config.""" @@ -971,6 +959,7 @@ class TestAddOnsFromCLI: "docs", "data", "pyspark", + "viz", "none", "test,log,docs", "test,data,lint", @@ -1000,7 +989,7 @@ def test_invalid_add_ons(self, fake_kedro_cli): assert result.exit_code != 0 assert ( - "Please select from the available add-ons: lint, test, log, docs, data, pyspark, all, none" + "Please select from the available add-ons: lint, test, log, docs, data, pyspark, viz, all, none" in result.output )