nat-n / poethepoet

A task runner that works well with poetry.
https://poethepoet.natn.io/
MIT License
1.47k stars 59 forks source link

Proposal: conditional task execution #12

Open nat-n opened 4 years ago

nat-n commented 4 years ago

The goal of this project is to be the obvious choice of task runner for python projects, with the simplicity of npm scripts for simple use cases, but also with powerful features comparable to make (at least as far any python project is likely to need).

One key feature of make is being able to skip build targets that already exist.

I need to analyse this problem a bit more but so far I’m thinking an appropriate comparable feature would be for all tasks to support a condition option, which can either represent a relative path to a file or directory (with globbing?), the existence of which would make the condition false, or it could reference a python function to evaluate.

Open questions:

howeaj commented 3 years ago

use case:

[tool.poe.tasks.test]
help = "Run unit tests with coverage"
cmd = "pytest --cov-report=html"

[tool.poe.tasks.cov]
help = "Open HTML test coverage report"
shell = "start htmlcov/index.html"

Ideally, I'd want the cov task to conditionally run the test task first if index.html doesn't exist.

abi-jey commented 2 years ago

Hi this is definitely something that I have been looking for. Imagine you need to install some thing or run tests after install for your local dev but not in prod. If we use poetry than we can run only poetry install and be done with it. 🥹

[tool.poe.poetry hooks]
post_install = "post-install"

[tool.poe.tasks.post-install]
Sequence = [
{She'll="apt install something"},
{She'll="Poe test", condition="${dev}" },
]
Args=[{name=dev, default=True, type=bool}]
nat-n commented 1 year ago

Here's what I have in mind for this feature so far. Comments welcome.

There are two distinct mechanisms for making a task conditional.

The check option specifies another task (defaults to the expr task type with assert = true) which is executed first. If it returns non-zero then the main task execution is skipped.

[tool.poe.tasks.install-deps]
cmd   = "apt install something"
check = "${ENV} != 'prod'"

The other mechanism is similar to how dependencies work in gnu make, by building on top of the existing deps mechanism. a task can declare a target option that points to a file. If a task has a target declared then it will only execute if either that file does not exist, or it has as a dependency on a task with a target file that is newer that its own.

For example here package will run build, but build will only execute if there's no build artefact. So package will always execute if build does, or if build has otherwise executed more recently than it has (judging from the last updated dates of their respective target files).

[tool.poe.tasks.clean]
cmd   = "rm -rf build"

[tool.poe.tasks.build]
cmd   = "apt install something"
target = "build/app"

[tool.poe.tasks.package]
cmd   = "..."
target = "app.zip"
deps = ["build"]

So then one way to interpret the scenario from @howeaj would be to make cov depend on test, and configure test with another new option called watch, which specifies files equivalent to a target on a dependency but without having to have another task to depend on.

[tool.poe.tasks.test]
help = "Run unit tests with coverage"
cmd = "pytest --cov-report=html"
watch = ["src/**/*.py", "tests/**/*.py"]
target = "index.html"
deps = ["_src_changed"]

[tool.poe.tasks.cov]
help = "Open HTML test coverage report"
shell = "start htmlcov/index.html"
deps = ["test"]

Although I'm not sure this is really what you'd want?

I'm open to suggestions for a more elegant or powerful approach to this problem.