| 360 | |
| 361 | |
| 362 | class ForeachDefinition: |
| 363 | def __init__( |
| 364 | self, |
| 365 | resolver: DataResolver, |
| 366 | context: Context, |
| 367 | name: str, |
| 368 | definition: "DictStrAny", |
| 369 | where: str = STAGES_KWD, |
| 370 | ): |
| 371 | self.resolver = resolver |
| 372 | self.relpath = self.resolver.relpath |
| 373 | self.context = context |
| 374 | self.name = name |
| 375 | |
| 376 | assert DO_KWD in definition |
| 377 | assert MATRIX_KWD not in definition |
| 378 | self.foreach_data = definition[FOREACH_KWD] |
| 379 | self._template = definition[DO_KWD] |
| 380 | |
| 381 | self.pair = IterationPair() |
| 382 | self.where = where |
| 383 | |
| 384 | @cached_property |
| 385 | def template(self): |
| 386 | # optimization: check for syntax errors only once for `foreach` stages |
| 387 | check_syntax_errors(self._template, self.name, self.relpath) |
| 388 | return self._template |
| 389 | |
| 390 | @cached_property |
| 391 | def resolved_iterable(self): |
| 392 | return self._resolve_foreach_data() |
| 393 | |
| 394 | def _resolve_foreach_data(self) -> "SeqOrMap": |
| 395 | try: |
| 396 | iterable = self.context.resolve(self.foreach_data, unwrap=False) |
| 397 | except (ContextError, ParseError) as exc: |
| 398 | format_and_raise(exc, f"'{self.where}.{self.name}.foreach'", self.relpath) |
| 399 | |
| 400 | # foreach data can be a resolved dictionary/list. |
| 401 | self._check_is_map_or_seq(iterable) |
| 402 | # foreach stages will have `item` and `key` added to the context |
| 403 | # so, we better warn them if they have them already in the context |
| 404 | # from the global vars. We could add them in `set_temporarily`, but |
| 405 | # that'd make it display for each iteration. |
| 406 | self._warn_if_overwriting(self._inserted_keys(iterable)) |
| 407 | return iterable |
| 408 | |
| 409 | def _check_is_map_or_seq(self, iterable): |
| 410 | if not is_map_or_seq(iterable): |
| 411 | node = iterable.value if isinstance(iterable, Node) else iterable |
| 412 | typ = type(node).__name__ |
| 413 | raise ResolveError( |
| 414 | f"failed to resolve '{self.where}.{self.name}.foreach'" |
| 415 | f" in '{self.relpath}': expected list/dictionary, got " + typ |
| 416 | ) |
| 417 | |
| 418 | def _warn_if_overwriting(self, keys: list[str]): |
| 419 | warn_for = [k for k in keys if k in self.context] |
no outgoing calls