From 3591c9e66d29bf908b774664231b6891abc36cdb Mon Sep 17 00:00:00 2001 From: Ivan Ogasawara Date: Mon, 27 May 2024 16:22:44 -0400 Subject: [PATCH] feat: Add initial mechanism for conditional question (#291) --- src/scicookie/profile.py | 2 +- src/scicookie/ui.py | 15 +- tests/profiles/test-depends-on.yaml | 242 ++++++++++++++++++++++++++++ tests/test_cli.py | 67 +++++++- 4 files changed, 315 insertions(+), 11 deletions(-) create mode 100644 tests/profiles/test-depends-on.yaml diff --git a/src/scicookie/profile.py b/src/scicookie/profile.py index 1ec217f7..d90c2c69 100644 --- a/src/scicookie/profile.py +++ b/src/scicookie/profile.py @@ -25,7 +25,7 @@ def __init__(self, profile_name: str): if profile_name not in self.profiles_available: SciCookieLogs.raise_error( - "The given profiles is not available.", + f"The given profile ({profile_name}) is not available.", SciCookieErrorType.SCICOOKIE_INVALID_PARAMETER, ) diff --git a/src/scicookie/ui.py b/src/scicookie/ui.py index ed2dc942..bd69f2c2 100644 --- a/src/scicookie/ui.py +++ b/src/scicookie/ui.py @@ -69,12 +69,17 @@ def _create_question( def check_dependencies_satisfied( question: dict[str, Any], answers: dict[str, str] ) -> bool: - """ - Check if dependencies are satisfied. + """Check if dependencies are satisfied.""" + if "depends_on" not in question: + return True - Note: Not implemented yet. - """ - return True + depends_satisfied = True + for dep_key, dep_value in question.get("depends_on", {}).items(): + if answers[dep_key] != dep_value: + depends_satisfied = False + break + + return depends_satisfied def sanitize_package_slug(package_slug: str) -> str: diff --git a/tests/profiles/test-depends-on.yaml b/tests/profiles/test-depends-on.yaml new file mode 100644 index 00000000..b4d8c76e --- /dev/null +++ b/tests/profiles/test-depends-on.yaml @@ -0,0 +1,242 @@ +author_full_name: + message: Type the author's name + help: https://osl-incubator.github.io/scicookie/guide/#information-about-the-project + type: text + default: Roronoa Zoro + visible: true + +author_email: + message: Type the author's email + help: https://osl-incubator.github.io/scicookie/guide/#information-about-the-project + type: text + default: zoro@one.piece + visible: true + +project_name: + message: Type the project's title + help: https://osl-incubator.github.io/scicookie/guide/#information-about-the-project + type: text + default: OSL Python package + visible: true + +project_short_description: + message: Type a short description about the project + help: https://osl-incubator.github.io/scicookie/guide/#information-about-the-project + type: text + default: This Project aims to ... + visible: true + +project_slug: + message: Type the code name for your project (e.g. the repository name) + help: https://osl-incubator.github.io/scicookie/guide/#information-about-the-project + type: text + default: "myprojectslug" + visible: true + +package_slug: + message: Type the code name for your package (the name used to import your package) + help: https://osl-incubator.github.io/scicookie/guide/#information-about-the-project + type: text + default: "mypackageslug" + visible: true + +project_version: + message: Type the project version + help: https://osl-incubator.github.io/scicookie/guide/#information-about-the-project + type: text + default: 0.1.0 + visible: true + depends_on: + package_slug: mypackageslug + +project_url: + message: Type the project URL + help: https://osl-incubator.github.io/scicookie/guide/#information-about-the-project + type: text + default: "myproject.com" + visible: true + depends_on: + project_version: 1.0.0 + +project_license: + message: Select one option for the project license + help: https://osl-incubator.github.io/scicookie/guide/#information-about-the-project + type: single-choice + default: BSD 3 Clause + # first choice is the default for the UI + choices: + - BSD 3 Clause + - MIT + - ISC license + - Apache Software License 2.0 + - GNU General Public License v3 + - Other + visible: true + +project_layout: + message: Select one option for the project layout + help: "For more information, check:\n https://osl-incubator.github.io/scicookie/guide/#project-layout" + type: single-choice + default: src + # first choice is the default for the UI + choices: + - src + - flat + visible: true + +build_system: + message: Select one option for the build system + help: "For more information, check:\n https://osl-incubator.github.io/scicookie/guide/#build-system" + type: single-choice + default: poetry + # first choice is the default for the UI + choices: + - poetry + - flit + - mesonpy + - setuptools + - pdm + - hatch + - maturin + - scikit-build-core + - pybind11 + visible: true + +command_line_interface: + message: Select one option for Command Line Interface (CLI) + help: "For more information, check:\n https://osl-incubator.github.io/scicookie/guide/#command-line-interfaces-clis" + type: single-choice + default: None + # first choice is the default for the UI + choices: + - None + - Click + - Argparse + visible: true + +documentation_engine: + message: Select one option for the Documentation Engine + help: "For more information, check:\n https://osl-incubator.github.io/scicookie/guide/#documentation-engine" + type: single-choice + default: mkdocs + # first choice is the default for the UI + choices: + - mkdocs + - sphinx + - jupyter-book + - quarto + visible: true + +documentation_url: + message: Type the documentation URL + help: The URL for the documentation page. + type: text + default: "docs.myproject.com" + visible: true + +use_tools: + message: Select all the initial tools you want to add to your project + help: "For more information, check:\n https://osl-incubator.github.io/scicookie/guide/#project-tools" + type: multiple-choices + choices: + - bandit + - black + - conda + - coverage + - flake8 + - hypothesis + - isort + - make + - makim + - mccabe + - mypy + - pre-commit + - prettier + - pydocstyle + - pytest + - ruff + - shellcheck + - vulture + visible: true + +use_containers: + message: Select one option for the container technology for this project + help: "For more information, check:\n https://osl-incubator.github.io/scicookie/guide/#integration-with-devops-tools" + type: single-choice + default: None + # first choice is the default for the UI + choices: + - None + - Docker + - Podman + visible: true + +# doc_template: +# message: Select the template for the Documentation Engine +# help: "" +# default: Material + +code_of_conduct: + message: Select one option for the Code of Conduct + help: "For more information, check:\n https://osl-incubator.github.io/scicookie/guide/#code-of-conduct" + type: single-choice + default: None + # first choice is the default for the UI + choices: + - None + - contributor-covenant + - citizen-code-of-conduct + - numfocus-adapted-coc + - python-adapted-coc + visible: true + +governance_document: + message: Select one option for a governance document template + help: "For more information, check:\n https://osl-incubator.github.io/scicookie/guide/#governance-document" + type: single-choice + default: None + # first choice is the default for the UI + choices: + - None + - numpy-governance + - sciml-governance + visible: true + +roadmap_document: + message: Select one option for a Roadmap document template + help: "For more information, check:\n https://osl-incubator.github.io/scicookie/guide/#roadmap-document" + type: single-choice + default: None + # first choice is the default for the UI + choices: + - None + - pytorch-ignite-roadmap + visible: true + +git_username: + message: Type the GIT username + help: https://osl-incubator.github.io/scicookie/guide/#control-version + type: "text" + default: "" + visible: true + +git_https_origin: + message: Type the GIT HTTPS origin URL + help: https://osl-incubator.github.io/scicookie/guide/#control-version + type: "text" + default: "" + visible: true + +git_https_upstream: + message: Type the GIT HTTPS upstream URL + help: https://osl-incubator.github.io/scicookie/guide/#control-version + type: "text" + default: "" + visible: true + +git_main_branch: + message: Type the GIT main branch + help: https://osl-incubator.github.io/scicookie/guide/#control-version + type: "text" + default: main + visible: true diff --git a/tests/test_cli.py b/tests/test_cli.py index b40c8467..7580aa8a 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -31,10 +31,11 @@ def tmp_dir() -> str: @pytest.mark.skipif(not sys.platform.startswith("linux"), reason="Linux only") -class TestMain: +class BaseCLITestProfile: """Tests for test-main.yaml.""" - profile_path: str + profile_path: str = "" + profile_filename: str = "" @classmethod def setup_class(cls): @@ -42,8 +43,8 @@ def setup_class(cls): test_dir = Path(__file__).parent src_dir = test_dir.parent / "src" / "scicookie" - profile_src_path = test_dir / "profiles" / "test-main.yaml" - cls.profile_path = src_dir / "profiles" / "test-main.yaml" + profile_src_path = test_dir / "profiles" / cls.profile_filename + cls.profile_path = src_dir / "profiles" / cls.profile_filename shutil.copy(profile_src_path, cls.profile_path) @@ -52,9 +53,16 @@ def teardown_class(cls): """Cleanup after test.""" cls.profile_path.unlink() + +@pytest.mark.skipif(not sys.platform.startswith("linux"), reason="Linux only") +class TestMain(BaseCLITestProfile): + """Tests for test-main.yaml.""" + + profile_filename: str = "test-main.yaml" + def test_cli(self, tmp_dir: str) -> None: """Test with test-main.yaml.""" - all_questions = get_all_questions("test-main.yaml") + all_questions = get_all_questions(self.profile_filename) child = pexpect.spawn( "scicookie --profile test-main", @@ -76,3 +84,52 @@ def test_cli(self, tmp_dir: str) -> None: child.expect(pexpect.EOF) output = child.before assert "Traceback" not in output, output + + +@pytest.mark.skipif(not sys.platform.startswith("linux"), reason="Linux only") +class TestDependsOn(BaseCLITestProfile): + """Tests for test-main.yaml.""" + + profile_filename: str = "test-depends-on.yaml" + + def test_cli(self, tmp_dir: str) -> None: + """Test with test-main.yaml.""" + all_questions = get_all_questions(self.profile_filename) + + child = pexpect.spawn( + "scicookie --profile test-depends-on", + cwd=tmp_dir, + encoding="utf-8", + timeout=10, + ) + + answers = {} + + for key, value in all_questions.items(): + depends_on = value.get("depends_on", {}) + depends_on_satisfied = True + + for dep_key, dep_val in depends_on.items(): + if answers.get(dep_key, "") != dep_val: + depends_on_satisfied = False + break + + if not depends_on_satisfied: + continue + + prompt = value.get("message") + + if not prompt: + continue + + # Escape special characters and allow any whitespace after + regex_prompt = re.escape(prompt) + r"\s*" + # Use regex for matching the prompt + child.expect(regex_prompt, timeout=10) + response = value.get("default", "") + answers[key] = response + child.sendline(response) + + child.expect(pexpect.EOF) + output = child.before + assert "Traceback" not in output, output