Skip to content

f-strings break the workflow generator #584

@sergiynesterenko90

Description

@sergiynesterenko90

Hi, I'm trying to use an f-string in my script and am running into issues. I think the presence of curly brackets confuses the workflow compiler somehow. I'm using hera 5.1.3.

For example, I can run the basic example script just fine. But if I make a small change to include an f-string:

from hera.workflows import Steps, Workflow, script


@script()
def echo(message: str):
    print(f"message: {message}")


with Workflow(
    generate_name="single-script-",
    entrypoint="steps",
) as w:
    with Steps(name="steps"):
        echo(arguments={"message": "A"})

w.create()

I get an error:

---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
Cell In[5], line 18
     15     with Steps(name="steps"):
     16         echo(arguments={"message": "A"})
---> 18 wf = {"Workflow": w.to_dict()}
     20 # submit the workflow to Argo
     21 argo.create_workflow(wf)

File [~/anaconda3/envs/quilt/lib/python3.8/site-packages/hera/workflows/workflow.py:319](https://vscode-remote+wsl-002bubuntu.vscode-resource.vscode-cdn.net/home/sergiy/git/quilt/quilt/notebooks/sergiy/~/anaconda3/envs/quilt/lib/python3.8/site-packages/hera/workflows/workflow.py:319), in Workflow.to_dict(self)
    317 def to_dict(self) -> Any:
    318     """Builds the Workflow as an Argo schema Workflow object and returns it as a dictionary."""
--> 319     return self.build().dict(exclude_none=True, by_alias=True)

File [~/anaconda3/envs/quilt/lib/python3.8/site-packages/hera/workflows/workflow.py:212](https://vscode-remote+wsl-002bubuntu.vscode-resource.vscode-cdn.net/home/sergiy/git/quilt/quilt/notebooks/sergiy/~/anaconda3/envs/quilt/lib/python3.8/site-packages/hera/workflows/workflow.py:212), in Workflow.build(self)
    209     template = template._dispatch_hooks()
    211 if isinstance(template, Templatable):
--> 212     templates.append(template._build_template())
    213 elif isinstance(template, get_args(TTemplate)):
    214     templates.append(template)

File [~/anaconda3/envs/quilt/lib/python3.8/site-packages/hera/workflows/script.py:172](https://vscode-remote+wsl-002bubuntu.vscode-resource.vscode-cdn.net/home/sergiy/git/quilt/quilt/notebooks/sergiy/~/anaconda3/envs/quilt/lib/python3.8/site-packages/hera/workflows/script.py:172), in Script._build_template(self)
    144 def _build_template(self) -> _ModelTemplate:
    145     assert isinstance(self.constructor, ScriptConstructor)
    146     return self.constructor.transform_template_post_build(
    147         self,
    148         _ModelTemplate(
    149             active_deadline_seconds=self.active_deadline_seconds,
    150             affinity=self.affinity,
    151             archive_location=self.archive_location,
    152             automount_service_account_token=self.automount_service_account_token,
    153             daemon=self.daemon,
    154             executor=self.executor,
    155             fail_fast=self.fail_fast,
    156             host_aliases=self.host_aliases,
    157             init_containers=self.init_containers,
    158             inputs=self._build_inputs(),
    159             memoize=self.memoize,
    160             metadata=self._build_metadata(),
    161             metrics=self.metrics,
    162             name=self.name,
    163             node_selector=self.node_selector,
    164             outputs=self._build_outputs(),
    165             parallelism=self.parallelism,
    166             plugin=self.plugin,
    167             pod_spec_patch=self.pod_spec_patch,
    168             priority=self.priority,
    169             priority_class_name=self.priority_class_name,
    170             retry_strategy=self.retry_strategy,
    171             scheduler_name=self.scheduler_name,
--> 172             script=self._build_script(),
    173             security_context=self.pod_security_context,
    174             service_account_name=self.service_account_name,
    175             sidecars=self.sidecars,
    176             synchronization=self.synchronization,
    177             timeout=self.timeout,
    178             tolerations=self.tolerations,
    179             volumes=self._build_volumes(),
    180         ),
    181     )

File [~/anaconda3/envs/quilt/lib/python3.8/site-packages/hera/workflows/script.py:201](https://vscode-remote+wsl-002bubuntu.vscode-resource.vscode-cdn.net/home/sergiy/git/quilt/quilt/notebooks/sergiy/~/anaconda3/envs/quilt/lib/python3.8/site-packages/hera/workflows/script.py:201), in Script._build_script(self)
    183 def _build_script(self) -> _ModelScriptTemplate:
    184     assert isinstance(self.constructor, ScriptConstructor)
    185     return self.constructor.transform_script_template_post_build(
    186         self,
    187         _ModelScriptTemplate(
    188             args=self.args,
    189             command=self.command,
    190             env=self._build_env(),
    191             env_from=self._build_env_from(),
    192             image=self.image,
    193             image_pull_policy=self._build_image_pull_policy(),
    194             lifecycle=self.lifecycle,
    195             liveness_probe=self.liveness_probe,
    196             name=self.container_name,
    197             ports=self.ports,
    198             readiness_probe=self.readiness_probe,
    199             resources=self._build_resources(),
    200             security_context=self.security_context,
--> 201             source=self.constructor.generate_source(self),
    202             startup_probe=self.startup_probe,
    203             stdin=self.stdin,
    204             stdin_once=self.stdin_once,
    205             termination_message_path=self.termination_message_path,
    206             termination_message_policy=self.termination_message_policy,
    207             tty=self.tty,
    208             volume_devices=self.volume_devices,
    209             volume_mounts=self._build_volume_mounts(),
    210             working_dir=self.working_dir,
    211         ),
    212     )

File [~/anaconda3/envs/quilt/lib/python3.8/site-packages/hera/workflows/script.py:336](https://vscode-remote+wsl-002bubuntu.vscode-resource.vscode-cdn.net/home/sergiy/git/quilt/quilt/notebooks/sergiy/~/anaconda3/envs/quilt/lib/python3.8/site-packages/hera/workflows/script.py:336), in InlineScriptConstructor.generate_source(self, instance)
    330     script += "\n"
    332 # We use ast parse/unparse to get the source code of the function
    333 # in order to have consistent looking functions and getting rid of any comments
    334 # parsing issues.
    335 # See https://github.com/argoproj-labs/hera/issues/572
--> 336 content = roundtrip(inspect.getsource(instance.source)).splitlines()
    337 for i, line in enumerate(content):
    338     if line.startswith("def") or line.startswith("async def"):

File [~/anaconda3/envs/quilt/lib/python3.8/site-packages/hera/workflows/_unparse.py:949](https://vscode-remote+wsl-002bubuntu.vscode-resource.vscode-cdn.net/home/sergiy/git/quilt/quilt/notebooks/sergiy/~/anaconda3/envs/quilt/lib/python3.8/site-packages/hera/workflows/_unparse.py:949), in roundtrip(source)
    947 if hasattr(ast, "unparse"):
    948     return ast.unparse(tree)
--> 949 return unparse(tree)

File [~/anaconda3/envs/quilt/lib/python3.8/site-packages/hera/workflows/_unparse.py:942](https://vscode-remote+wsl-002bubuntu.vscode-resource.vscode-cdn.net/home/sergiy/git/quilt/quilt/notebooks/sergiy/~/anaconda3/envs/quilt/lib/python3.8/site-packages/hera/workflows/_unparse.py:942), in unparse(ast_obj)
    940 def unparse(ast_obj):
    941     unparser = _Unparser()
--> 942     return unparser(ast_obj)

File [~/anaconda3/envs/quilt/lib/python3.8/site-packages/hera/workflows/_unparse.py:70](https://vscode-remote+wsl-002bubuntu.vscode-resource.vscode-cdn.net/home/sergiy/git/quilt/quilt/notebooks/sergiy/~/anaconda3/envs/quilt/lib/python3.8/site-packages/hera/workflows/_unparse.py:70), in _Unparser.__call__(self, node)
     67 """Outputs a source code string that, if converted back to an ast
     68 (using ast.parse) will generate an AST equivalent to *node*"""
     69 self._source = []
---> 70 self.traverse(node)
     71 return "".join(self._source)

File [~/anaconda3/envs/quilt/lib/python3.8/site-packages/hera/workflows/_unparse.py:183](https://vscode-remote+wsl-002bubuntu.vscode-resource.vscode-cdn.net/home/sergiy/git/quilt/quilt/notebooks/sergiy/~/anaconda3/envs/quilt/lib/python3.8/site-packages/hera/workflows/_unparse.py:183), in _Unparser.traverse(self, node)
    181         self.traverse(item)
    182 else:
--> 183     super().visit(node)

File [~/anaconda3/envs/quilt/lib/python3.8/ast.py:371](https://vscode-remote+wsl-002bubuntu.vscode-resource.vscode-cdn.net/home/sergiy/git/quilt/quilt/notebooks/sergiy/~/anaconda3/envs/quilt/lib/python3.8/ast.py:371), in NodeVisitor.visit(self, node)
    369 method = 'visit_' + node.__class__.__name__
    370 visitor = getattr(self, method, self.generic_visit)
--> 371 return visitor(node)

File [~/anaconda3/envs/quilt/lib/python3.8/site-packages/hera/workflows/_unparse.py:194](https://vscode-remote+wsl-002bubuntu.vscode-resource.vscode-cdn.net/home/sergiy/git/quilt/quilt/notebooks/sergiy/~/anaconda3/envs/quilt/lib/python3.8/site-packages/hera/workflows/_unparse.py:194), in _Unparser.visit_Module(self, node)
    192 def visit_Module(self, node):
    193     self._type_ignores = {ignore.lineno: f"ignore{ignore.tag}" for ignore in node.type_ignores}
--> 194     self._write_docstring_and_traverse_body(node)
    195     self._type_ignores.clear()

File [~/anaconda3/envs/quilt/lib/python3.8/site-packages/hera/workflows/_unparse.py:190](https://vscode-remote+wsl-002bubuntu.vscode-resource.vscode-cdn.net/home/sergiy/git/quilt/quilt/notebooks/sergiy/~/anaconda3/envs/quilt/lib/python3.8/site-packages/hera/workflows/_unparse.py:190), in _Unparser._write_docstring_and_traverse_body(self, node)
    188     self.traverse(node.body[1:])
    189 else:
--> 190     self.traverse(node.body)

File [~/anaconda3/envs/quilt/lib/python3.8/site-packages/hera/workflows/_unparse.py:181](https://vscode-remote+wsl-002bubuntu.vscode-resource.vscode-cdn.net/home/sergiy/git/quilt/quilt/notebooks/sergiy/~/anaconda3/envs/quilt/lib/python3.8/site-packages/hera/workflows/_unparse.py:181), in _Unparser.traverse(self, node)
    179 if isinstance(node, list):
    180     for item in node:
--> 181         self.traverse(item)
    182 else:
    183     super().visit(node)

File [~/anaconda3/envs/quilt/lib/python3.8/site-packages/hera/workflows/_unparse.py:183](https://vscode-remote+wsl-002bubuntu.vscode-resource.vscode-cdn.net/home/sergiy/git/quilt/quilt/notebooks/sergiy/~/anaconda3/envs/quilt/lib/python3.8/site-packages/hera/workflows/_unparse.py:183), in _Unparser.traverse(self, node)
    181         self.traverse(item)
    182 else:
--> 183     super().visit(node)

File [~/anaconda3/envs/quilt/lib/python3.8/ast.py:371](https://vscode-remote+wsl-002bubuntu.vscode-resource.vscode-cdn.net/home/sergiy/git/quilt/quilt/notebooks/sergiy/~/anaconda3/envs/quilt/lib/python3.8/ast.py:371), in NodeVisitor.visit(self, node)
    369 method = 'visit_' + node.__class__.__name__
    370 visitor = getattr(self, method, self.generic_visit)
--> 371 return visitor(node)

File [~/anaconda3/envs/quilt/lib/python3.8/site-packages/hera/workflows/_unparse.py:374](https://vscode-remote+wsl-002bubuntu.vscode-resource.vscode-cdn.net/home/sergiy/git/quilt/quilt/notebooks/sergiy/~/anaconda3/envs/quilt/lib/python3.8/site-packages/hera/workflows/_unparse.py:374), in _Unparser.visit_FunctionDef(self, node)
    373 def visit_FunctionDef(self, node):
--> 374     self._function_helper(node, "def")

File [~/anaconda3/envs/quilt/lib/python3.8/site-packages/hera/workflows/_unparse.py:392](https://vscode-remote+wsl-002bubuntu.vscode-resource.vscode-cdn.net/home/sergiy/git/quilt/quilt/notebooks/sergiy/~/anaconda3/envs/quilt/lib/python3.8/site-packages/hera/workflows/_unparse.py:392), in _Unparser._function_helper(self, node, fill_suffix)
    390     self.traverse(node.returns)
    391 with self.block(extra=self.get_type_comment(node)):
--> 392     self._write_docstring_and_traverse_body(node)

File [~/anaconda3/envs/quilt/lib/python3.8/site-packages/hera/workflows/_unparse.py:190](https://vscode-remote+wsl-002bubuntu.vscode-resource.vscode-cdn.net/home/sergiy/git/quilt/quilt/notebooks/sergiy/~/anaconda3/envs/quilt/lib/python3.8/site-packages/hera/workflows/_unparse.py:190), in _Unparser._write_docstring_and_traverse_body(self, node)
    188     self.traverse(node.body[1:])
    189 else:
--> 190     self.traverse(node.body)

File [~/anaconda3/envs/quilt/lib/python3.8/site-packages/hera/workflows/_unparse.py:181](https://vscode-remote+wsl-002bubuntu.vscode-resource.vscode-cdn.net/home/sergiy/git/quilt/quilt/notebooks/sergiy/~/anaconda3/envs/quilt/lib/python3.8/site-packages/hera/workflows/_unparse.py:181), in _Unparser.traverse(self, node)
    179 if isinstance(node, list):
    180     for item in node:
--> 181         self.traverse(item)
    182 else:
    183     super().visit(node)

File [~/anaconda3/envs/quilt/lib/python3.8/site-packages/hera/workflows/_unparse.py:183](https://vscode-remote+wsl-002bubuntu.vscode-resource.vscode-cdn.net/home/sergiy/git/quilt/quilt/notebooks/sergiy/~/anaconda3/envs/quilt/lib/python3.8/site-packages/hera/workflows/_unparse.py:183), in _Unparser.traverse(self, node)
    181         self.traverse(item)
    182 else:
--> 183     super().visit(node)

File [~/anaconda3/envs/quilt/lib/python3.8/ast.py:371](https://vscode-remote+wsl-002bubuntu.vscode-resource.vscode-cdn.net/home/sergiy/git/quilt/quilt/notebooks/sergiy/~/anaconda3/envs/quilt/lib/python3.8/ast.py:371), in NodeVisitor.visit(self, node)
    369 method = 'visit_' + node.__class__.__name__
    370 visitor = getattr(self, method, self.generic_visit)
--> 371 return visitor(node)

File [~/anaconda3/envs/quilt/lib/python3.8/site-packages/hera/workflows/_unparse.py:233](https://vscode-remote+wsl-002bubuntu.vscode-resource.vscode-cdn.net/home/sergiy/git/quilt/quilt/notebooks/sergiy/~/anaconda3/envs/quilt/lib/python3.8/site-packages/hera/workflows/_unparse.py:233), in _Unparser.visit_Assign(self, node)
    231     self.traverse(target)
    232     self.write(" = ")
--> 233 self.traverse(node.value)
    234 if type_comment := self.get_type_comment(node):
    235     self.write(type_comment)

File [~/anaconda3/envs/quilt/lib/python3.8/site-packages/hera/workflows/_unparse.py:183](https://vscode-remote+wsl-002bubuntu.vscode-resource.vscode-cdn.net/home/sergiy/git/quilt/quilt/notebooks/sergiy/~/anaconda3/envs/quilt/lib/python3.8/site-packages/hera/workflows/_unparse.py:183), in _Unparser.traverse(self, node)
    181         self.traverse(item)
    182 else:
--> 183     super().visit(node)

File [~/anaconda3/envs/quilt/lib/python3.8/ast.py:371](https://vscode-remote+wsl-002bubuntu.vscode-resource.vscode-cdn.net/home/sergiy/git/quilt/quilt/notebooks/sergiy/~/anaconda3/envs/quilt/lib/python3.8/ast.py:371), in NodeVisitor.visit(self, node)
    369 method = 'visit_' + node.__class__.__name__
    370 visitor = getattr(self, method, self.generic_visit)
--> 371 return visitor(node)

File [~/anaconda3/envs/quilt/lib/python3.8/site-packages/hera/workflows/_unparse.py:511](https://vscode-remote+wsl-002bubuntu.vscode-resource.vscode-cdn.net/home/sergiy/git/quilt/quilt/notebooks/sergiy/~/anaconda3/envs/quilt/lib/python3.8/site-packages/hera/workflows/_unparse.py:511), in _Unparser.visit_JoinedStr(self, node)
    509 for value in node.values:
    510     meth = getattr(self, "_fstring_" + type(value).__name__)
--> 511     meth(value, self.buffer_writer)
    512     buffer.append((self.buffer, isinstance(value, Constant)))
    513 new_buffer = []

File [~/anaconda3/envs/quilt/lib/python3.8/site-packages/hera/workflows/_unparse.py:546](https://vscode-remote+wsl-002bubuntu.vscode-resource.vscode-cdn.net/home/sergiy/git/quilt/quilt/notebooks/sergiy/~/anaconda3/envs/quilt/lib/python3.8/site-packages/hera/workflows/_unparse.py:546), in _Unparser._fstring_FormattedValue(self, node, write)
    544 unparser.set_precedence(_Precedence.TEST.next(), node.value)
    545 expr = unparser.visit(node.value)
--> 546 if expr.startswith("{"):
    547     write(" ")  # Separate pair of opening brackets as "{ {"
    548 if "\\" in expr:

AttributeError: 'NoneType' object has no attribute 'startswith'

Metadata

Metadata

Assignees

No one assigned

    Labels

    semver:patchA change requiring a patch version bumptype:bugA general bug

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions