diff --git a/CHANGELOG.md b/CHANGELOG.md index 271a0f1..c05daeb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,10 +5,17 @@ chronological order. Releases follow [semantic versioning](https://semver.org/) releases are available on [PyPI](https://pypi.org/project/pytask-stata) and [Anaconda.org](https://anaconda.org/conda-forge/pytask-stata). -## Unreleased +## 0.5.1 - 2026-06-14 - {pull}`50` drops support for Python 3.8 and 3.9 and adds support for Python 3.14. - {pull}`51` updates pre-commit hooks. +- {pull}`58` removes unnecessary pytest markers. +- {pull}`59` updates installation documentation and typing. +- {pull}`76` hardens GitHub Actions workflows with zizmor. +- {pull}`77` uses uv in the PyPI publish workflow. +- {pull}`104` updates the README for current pytask syntax. +- {pull}`105` adds a mock Stata runtime for CI tests. +- {pull}`110` fixes dependencies for Stata tasks. ## 0.5.0 - 2025-07-26 diff --git a/README.md b/README.md index cd7b08e..c9f7621 100644 --- a/README.md +++ b/README.md @@ -61,6 +61,41 @@ Dependencies and products can be added as with a normal pytask task using the ta function signature as explained in this [tutorial](https://pytask-dev.readthedocs.io/en/stable/tutorials/defining_dependencies_products.html). +Here is a task with one dependency and one product. + +```python +from pathlib import Path + +from pytask import mark + + +@mark.stata(script=Path("script.do")) +def task_run_do_file( + depends_on: Path = Path("input.dta"), + produces: Path = Path("auto.dta"), +): + pass +``` + +pytask uses `input.dta` and `auto.dta` to decide whether the task needs to run. If +`input.dta` changes or `auto.dta` is missing, the Stata task is executed again. + +You can also use `@task(kwargs=...)` to define dependencies which are not part of the +function signature. + +```python +from pathlib import Path + +from pytask import mark +from pytask import task + + +@task(kwargs={"depends_on": Path("input.dta")}) +@mark.stata(script=Path("script.do")) +def task_run_do_file(produces: Path = Path("auto.dta")): + pass +``` + ### Accessing dependencies and products in the script Dependencies and products registered in the task function signature are used by pytask @@ -68,7 +103,7 @@ to order tasks and track whether they are up-to-date. They are not automatically to the Stata script. Use the `options` argument of the decorator to pass paths or other values as command line arguments to your Stata executable. -For example, pass the path of the product with +For example, pass paths for the dependency and product with ```python from pathlib import Path @@ -76,16 +111,19 @@ from pathlib import Path from pytask import mark -@mark.stata(script=Path("script.do"), options=Path("auto.dta")) -def task_run_do_file(produces: Path = Path("auto.dta")): +@mark.stata(script=Path("script.do"), options=[Path("input.dta"), Path("auto.dta")]) +def task_run_do_file( + depends_on: Path = Path("input.dta"), + produces: Path = Path("auto.dta"), +): pass ``` And in your `script.do`, you can intercept the value with ```do -* Intercept command line argument and save to macro named 'produces'. -args produces +* Intercept command line arguments and save them to macros. +args depends_on produces sysuse auto, clear save "`produces'" diff --git a/src/pytask_stata/collect.py b/src/pytask_stata/collect.py index 0041849..04c4e13 100644 --- a/src/pytask_stata/collect.py +++ b/src/pytask_stata/collect.py @@ -34,6 +34,7 @@ def run_stata_script( _options: list[str], _log_name: str, _cwd: Path, + **_kwargs: Any, ) -> None: """Run an R script.""" cmd = [_executable, "-e", "do", _script.as_posix(), *_options, f"-{_log_name}"] diff --git a/tests/test_execute.py b/tests/test_execute.py index 15c5158..d622ad0 100644 --- a/tests/test_execute.py +++ b/tests/test_execute.py @@ -100,6 +100,58 @@ def run_do_file(produces=Path("auto.dta")): assert tmp_path.joinpath("script.log").exists() +@needs_stata +def test_run_do_file_with_dependency(runner, tmp_path): + task_source = """ + import pytask + from pathlib import Path + + @pytask.mark.stata(script="script.do") + def task_run_do_file(depends_on=Path("input.dta"), produces=Path("auto.dta")): + pass + """ + tmp_path.joinpath("task_example.py").write_text(textwrap.dedent(task_source)) + tmp_path.joinpath("input.dta").write_text("content") + + do_file = """ + sysuse auto, clear + save auto + """ + tmp_path.joinpath("script.do").write_text(textwrap.dedent(do_file)) + + result = runner.invoke(cli, [tmp_path.as_posix(), "--stata-keep-log"]) + + assert result.exit_code == ExitCode.OK + assert tmp_path.joinpath("auto.dta").exists() + + +@needs_stata +def test_run_do_file_with_dependency_from_task_kwargs(runner, tmp_path): + task_source = """ + import pytask + from pathlib import Path + from pytask import task + + @task(kwargs={"depends_on": Path("input.dta")}) + @pytask.mark.stata(script="script.do") + def task_run_do_file(produces=Path("auto.dta")): + pass + """ + tmp_path.joinpath("task_example.py").write_text(textwrap.dedent(task_source)) + tmp_path.joinpath("input.dta").write_text("content") + + do_file = """ + sysuse auto, clear + save auto + """ + tmp_path.joinpath("script.do").write_text(textwrap.dedent(do_file)) + + result = runner.invoke(cli, [tmp_path.as_posix(), "--stata-keep-log"]) + + assert result.exit_code == ExitCode.OK + assert tmp_path.joinpath("auto.dta").exists() + + def test_raise_error_if_stata_is_not_found(tmp_path, monkeypatch): task_source = """ from pytask import mark, task