Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
48 changes: 43 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,31 +61,69 @@ 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
to order tasks and track whether they are up-to-date. They are not automatically passed
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

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'"
Expand Down
1 change: 1 addition & 0 deletions src/pytask_stata/collect.py
Original file line number Diff line number Diff line change
Expand Up @@ -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}"]
Expand Down
52 changes: 52 additions & 0 deletions tests/test_execute.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading