diff --git a/docs/source/changes.md b/docs/source/changes.md index ab7a8e23..7f826fec 100644 --- a/docs/source/changes.md +++ b/docs/source/changes.md @@ -18,6 +18,7 @@ releases are available on [PyPI](https://pypi.org/project/pytask) and - {pull}`555` uses new-style hook wrappers and requires pluggy 1.3 for typing. - {pull}`557` fixes an issue with `@task(after=...)` in notebooks and terminals. - {pull}`566` makes universal-pathlib an official dependency. +- {pull}`568` restricts `task_files` to a list of patterns and raises a better error. ## 0.4.5 - 2024-01-09 diff --git a/docs/source/reference_guides/configuration.md b/docs/source/reference_guides/configuration.md index 9564298f..da8ece2b 100644 --- a/docs/source/reference_guides/configuration.md +++ b/docs/source/reference_guides/configuration.md @@ -242,7 +242,7 @@ strict_markers = true Change the pattern which identify task files. ```toml -task_files = "task_*.py" # default +task_files = ["task_*.py"] # default task_files = ["task_*.py", "tasks_*.py"] ``` diff --git a/src/_pytask/build.py b/src/_pytask/build.py index fe0c2c1e..5c598705 100644 --- a/src/_pytask/build.py +++ b/src/_pytask/build.py @@ -95,7 +95,7 @@ def build( # noqa: C901, PLR0912, PLR0913, PLR0915 stop_after_first_failure: bool = False, strict_markers: bool = False, tasks: Callable[..., Any] | PTask | Iterable[Callable[..., Any] | PTask] = (), - task_files: str | Iterable[str] = "task_*.py", + task_files: Iterable[str] = ("task_*.py",), trace: bool = False, verbose: int = 1, **kwargs: Any, diff --git a/src/_pytask/config.py b/src/_pytask/config.py index ac4775da..86423ac3 100644 --- a/src/_pytask/config.py +++ b/src/_pytask/config.py @@ -92,7 +92,13 @@ def pytask_parse_config(config: dict[str, Any]) -> None: + IGNORED_TEMPORARY_FILES_AND_FOLDERS ) - config["task_files"] = to_list(config.get("task_files", "task_*.py")) + value = config.get("task_files", ["task_*.py"]) + if not isinstance(value, (list, tuple)) or not all( + isinstance(p, str) for p in value + ): + msg = "'task_files' must be a list of patterns." + raise ValueError(msg) + config["task_files"] = value if config["stop_after_first_failure"]: config["max_failures"] = 1 diff --git a/tests/test_collect.py b/tests/test_collect.py index 5ef6f6c0..d6f3c139 100644 --- a/tests/test_collect.py +++ b/tests/test_collect.py @@ -103,10 +103,10 @@ def test_collect_same_task_different_ways(tmp_path, path_extension): @pytest.mark.parametrize( ("task_files", "pattern", "expected_collected_tasks"), [ - (["example_task.py"], "'*_task.py'", 1), - (["tasks_example.py"], "'tasks_*'", 1), - (["example_tasks.py"], "'*_tasks.py'", 1), - (["task_module.py", "tasks_example.py"], "'tasks_*.py'", 1), + (["example_task.py"], "['*_task.py']", 1), + (["tasks_example.py"], "['tasks_*']", 1), + (["example_tasks.py"], "['*_tasks.py']", 1), + (["task_module.py", "tasks_example.py"], "['tasks_*.py']", 1), (["task_module.py", "tasks_example.py"], "['task_*.py', 'tasks_*.py']", 2), ], ) @@ -117,8 +117,8 @@ def test_collect_files_w_custom_file_name_pattern( f"[tool.pytask.ini_options]\ntask_files = {pattern}" ) - for file in task_files: - tmp_path.joinpath(file).write_text("def task_example(): pass") + for file_ in task_files: + tmp_path.joinpath(file_).write_text("def task_example(): pass") session = build(paths=tmp_path) @@ -126,6 +126,21 @@ def test_collect_files_w_custom_file_name_pattern( assert len(session.tasks) == expected_collected_tasks +def test_error_with_invalid_file_name_pattern(runner, tmp_path): + tmp_path.joinpath("pyproject.toml").write_text( + "[tool.pytask.ini_options]\ntask_files = 'asds'" + ) + + result = runner.invoke(cli, [tmp_path.as_posix()]) + assert result.exit_code == ExitCode.CONFIGURATION_FAILED + assert "'task_files' must be a list of patterns." in result.output + + +def test_error_with_invalid_file_name_pattern_(tmp_path): + session = build(paths=tmp_path, task_files=[1]) + assert session.exit_code == ExitCode.CONFIGURATION_FAILED + + @pytest.mark.unit() @pytest.mark.parametrize( ("session", "path", "node_info", "expected"),