Skip to content

Commit

Permalink
Improve validation and error reporting for malformed args config
Browse files Browse the repository at this point in the history
  • Loading branch information
nat-n committed Oct 26, 2024
1 parent 7580b0e commit fc5b0e3
Show file tree
Hide file tree
Showing 2 changed files with 48 additions and 19 deletions.
16 changes: 13 additions & 3 deletions poethepoet/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,10 @@ def resolve_task(self, allow_hidden: bool = False) -> Optional["PoeTask"]:

task = tuple(self.ui["task"])
if not task:
self.print_help(info="No task specified.")
try:
self.print_help(info="No task specified.")
except PoeException as error:
self.print_help(error=error)
return None

task_name = task[0]
Expand Down Expand Up @@ -184,7 +187,12 @@ def run_task_graph(self, task: "PoeTask") -> Optional[int]:
from .task.graph import TaskExecutionGraph

context = self.get_run_context(multistage=True)
graph = TaskExecutionGraph(task, context)
try:
graph = TaskExecutionGraph(task, context)
except PoeException as error:
self.print_help(error=error)
return 1

plan = graph.get_execution_plan()

for stage in plan:
Expand Down Expand Up @@ -240,7 +248,9 @@ def print_help(
task_name: (
(
content.get("help", ""),
PoeTaskArgs.get_help_content(content.get("args")),
PoeTaskArgs.get_help_content(
content.get("args"), task_name, suppress_errors=bool(error)
),
)
if isinstance(content, dict)
else ("", tuple())
Expand Down
51 changes: 35 additions & 16 deletions poethepoet/task/args.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,10 @@ def normalize(cls, args_def: ArgsDef, strict: bool = True):

elif isinstance(args_def, dict):
for name, params in args_def.items():
if not isinstance(params, dict):
raise ConfigValidationError(
f"Invalid configuration for arg {name!r}, expected dict"
)
if strict and "name" in params:
raise ConfigValidationError(
f"Unexpected 'name' option for argument {name!r}"
Expand Down Expand Up @@ -192,22 +196,12 @@ def _parse_args_def(self, args_def: ArgsDef):
try:
return tuple(ArgSpec.parse(args_def))
except ConfigValidationError as error:
if isinstance(error.index, int):
if isinstance(args_def, list):
item = args_def[error.index]
if arg_name := (isinstance(item, dict) and item.get("name")):
arg_ref = arg_name
elif arg_name := tuple(args_def.keys())[error.index]:
arg_ref = arg_name
else:
arg_ref = error.index
error.context = f"Invalid argument {arg_ref!r} declared"
error.task_name = self._task_name
self._enrich_config_error(error, args_def, self._task_name)
raise

@classmethod
def get_help_content(
cls, args_def: Optional[ArgsDef]
cls, args_def: Optional[ArgsDef], task_name: str, suppress_errors: bool = False
) -> List[Tuple[Tuple[str, ...], str, str]]:
if args_def is None:
return []
Expand All @@ -218,10 +212,35 @@ def format_default(arg) -> str:
return f"[default: {default}]"
return ""

return [
(arg["options"], arg.get("help", ""), format_default(arg))
for arg in ArgSpec.normalize(args_def, strict=False)
]
try:
return [
(arg["options"], arg.get("help", ""), format_default(arg))
for arg in ArgSpec.normalize(args_def, strict=False)
]
except ConfigValidationError as error:
if suppress_errors:
return []
else:
cls._enrich_config_error(error, args_def, task_name)
raise

@staticmethod
def _enrich_config_error(
error: ConfigValidationError, args_def: ArgsDef, task_name: str
):
if isinstance(error.index, int):
if isinstance(args_def, list):
item = args_def[error.index]
if arg_name := (isinstance(item, dict) and item.get("name")):
arg_ref = arg_name
else:
arg_ref = error.index
elif arg_name := tuple(args_def.keys())[error.index]:
arg_ref = arg_name
else:
arg_ref = error.index
error.context = f"Invalid argument {arg_ref!r} declared"
error.task_name = task_name

def build_parser(
self, env: "EnvVarsManager", program_name: str
Expand Down

0 comments on commit fc5b0e3

Please sign in to comment.