Skip to content

Commit

Permalink
Add support for interpolating env vars into task arg default values
Browse files Browse the repository at this point in the history
  • Loading branch information
nat-n committed Dec 26, 2022
1 parent d266433 commit 3c99468
Show file tree
Hide file tree
Showing 7 changed files with 54 additions and 22 deletions.
21 changes: 19 additions & 2 deletions README.rst
Expand Up @@ -641,8 +641,25 @@ Task argument options
Named arguments support the following configuration options:

- **default** : Union[str, int, float, bool]
The value to use if the argument is not provided. This option has no effect if the
required option is set to true.
The value to use if the argument is not provided. This option has no significance if
the required option is set to true.

For string values, environment variables can be referenced using the usual templating
syntax as in the following example.

.. code-block:: toml
[[tool.poe.tasks.deploy.args]]
name = "region"
help = "The region to deploy to"
default = "${AWS_REGION}"
This can be combined with setting an env values on the task with the default
specifier to get the following precendence of values for the arg:

1. the value passed on the command line
2. the value of the variable set on the environment
3. the default value for the environment variable configured on the task

- **help** : str
A short description of the argument to include in the documentation of the task.
Expand Down
8 changes: 7 additions & 1 deletion poethepoet/task/args.py
Expand Up @@ -12,6 +12,8 @@
Union,
)

from ..env.manager import EnvVarsManager

if TYPE_CHECKING:
from ..config import PoeConfig

Expand Down Expand Up @@ -39,9 +41,10 @@
class PoeTaskArgs:
_args: Tuple[ArgParams, ...]

def __init__(self, args_def: ArgsDef, task_name: str):
def __init__(self, args_def: ArgsDef, task_name: str, env: EnvVarsManager):
self._args = self._normalize_args_def(args_def)
self._task_name = task_name
self._env = env

@classmethod
def _normalize_args_def(cls, args_def: ArgsDef) -> Tuple[ArgParams, ...]:
Expand Down Expand Up @@ -244,6 +247,9 @@ def build_parser(self) -> argparse.ArgumentParser:

def _get_argument_params(self, arg: ArgParams):
default = arg.get("default")
if isinstance(default, str):
default = self._env.fill_template(default)

result = {
"default": default,
"help": arg.get("help", ""),
Expand Down
24 changes: 14 additions & 10 deletions poethepoet/task/base.py
Expand Up @@ -8,7 +8,6 @@
Dict,
Iterator,
List,
Mapping,
Optional,
Sequence,
Tuple,
Expand Down Expand Up @@ -54,6 +53,7 @@ class PoeTask(metaclass=MetaPoeTask):
name: str
content: TaskContent
options: Dict[str, Any]
named_args: Optional[Dict[str, str]] = None

__options__: Dict[str, Union[Type, Tuple[Type, ...]]] = {}
__content_type__: Type = str
Expand Down Expand Up @@ -94,7 +94,6 @@ def __init__(
self._config = config
self._is_windows = sys.platform == "win32"
self.invocation = invocation
self.named_args = self._parse_named_args(invocation[1:])

@classmethod
def from_config(
Expand Down Expand Up @@ -202,18 +201,23 @@ def resolve_task_type(

return None

def _parse_named_args(self, extra_args: Sequence[str]) -> Optional[Dict[str, str]]:
def _parse_named_args(
self, extra_args: Sequence[str], env: EnvVarsManager
) -> Optional[Dict[str, str]]:
args_def = self.options.get("args")
if args_def:
return PoeTaskArgs(args_def, self.name).parse(extra_args)
return PoeTaskArgs(args_def, self.name, env).parse(extra_args)
return None

@property
def has_named_args(self):
return bool(self.named_args)
# @property
# def has_named_args(self):
# return bool(self.named_args)

def get_named_arg_values(self) -> Mapping[str, str]:
result = {}
def get_named_arg_values(self, env: EnvVarsManager) -> Dict[str, str]:
result: Dict[str, str] = {}

if self.named_args is None:
self.named_args = self._parse_named_args(self.invocation[1:], env)

if not self.named_args:
return {}
Expand Down Expand Up @@ -279,7 +283,7 @@ def _get_upstream_invocations(self, context: "RunContext"):
env = context.get_task_env(
None, self.options.get("envfile"), self.options.get("env")
)
env.update(self.get_named_arg_values())
env.update(self.get_named_arg_values(env))

self.__upstream_invocations = {
"deps": [
Expand Down
5 changes: 3 additions & 2 deletions poethepoet/task/cmd.py
Expand Up @@ -32,9 +32,10 @@ def _handle_run(
extra_args: Sequence[str],
env: EnvVarsManager,
) -> int:
env.update(self.get_named_arg_values())
named_arg_values = self.get_named_arg_values(env)
env.update(named_arg_values)

if self.has_named_args:
if named_arg_values:
# If named arguments are defined then it doesn't make sense to pass extra
# args to the command, because they've already been parsed
cmd = self._resolve_args(context, env)
Expand Down
5 changes: 3 additions & 2 deletions poethepoet/task/sequence.py
Expand Up @@ -69,9 +69,10 @@ def _handle_run(
extra_args: Sequence[str],
env: EnvVarsManager,
) -> int:
env.update(self.get_named_arg_values())
named_arg_values = self.get_named_arg_values(env)
env.update(named_arg_values)

if not self.has_named_args and any(arg.strip() for arg in extra_args):
if not named_arg_values and any(arg.strip() for arg in extra_args):
raise PoeException(f"Sequence task {self.name!r} does not accept arguments")

if len(self.subtasks) > 1:
Expand Down
5 changes: 3 additions & 2 deletions poethepoet/task/shell.py
Expand Up @@ -38,9 +38,10 @@ def _handle_run(
extra_args: Sequence[str],
env: EnvVarsManager,
) -> int:
env.update(self.get_named_arg_values())
named_arg_values = self.get_named_arg_values(env)
env.update(named_arg_values)

if not self.has_named_args and any(arg.strip() for arg in extra_args):
if not named_arg_values and any(arg.strip() for arg in extra_args):
raise PoeException(f"Shell task {self.name!r} does not accept arguments")

interpreter_cmd = self.resolve_interpreter_cmd()
Expand Down
8 changes: 5 additions & 3 deletions tests/fixtures/scripts_project/pyproject.toml
Expand Up @@ -77,10 +77,11 @@ greet = "pkg:greet"

[tool.poe.tasks.greet-strict]
script = "pkg:greet(greeting, user=name)"
help = "All arguments are required"
help = "All arguments are required"
env = { DOES = "does", STUFF = "matter" }

[tool.poe.tasks.greet-strict.args.greeting]
default = "doesn't matter"
default = "${DOES}n't ${STUFF}"
required = true
help = "this one is required"

Expand All @@ -90,11 +91,12 @@ greet = "pkg:greet"

[tool.poe.tasks.greet-positional]
script = "pkg:greet(greeting, user=username, upper=uppercase)"
env.DEFAULT_GREETING.default = "yo"

[[tool.poe.tasks.greet-positional.args]]
name = "greeting"
positional = true
default = "yo"
default = "${DEFAULT_GREETING}"
help = "this one is required"

[[tool.poe.tasks.greet-positional.args]]
Expand Down

0 comments on commit 3c99468

Please sign in to comment.