-
Notifications
You must be signed in to change notification settings - Fork 106
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix issues with with_param #1236
Fix issues with with_param #1236
Conversation
Many places need to inspect a parameter or field to construct a Parameter or Artifact based on the name and other annotations. Extract the single-field logic from _construct_io_from_fields to simplify many callsites that were using get_workflow_annotation and stitching in the name and default Parameter themselves. This will also allow us to reliably provide more features in future, such as inferring an enum parameter for a Literal. Signed-off-by: Alice Purcell <[email protected]>
Add three unit tests of the working current behaviour to prevent regression. Signed-off-by: Alice Purcell <[email protected]>
Support Parameter/Artifact annotations in _get_param_items_from_source. If a Parameter annotation is provided, the name will override the name of the Python parameter; additionally, if it is an output parameter, or if there is an Artifact annotation, it will be skipped. Signed-off-by: Alice Purcell <[email protected]>
Support Pydantic Input subclasses in _get_param_items_from_source. Signed-off-by: Alice Purcell <[email protected]>
Add examples reproducing issues argoproj-labs#861 (using with_param with an annotated input) and argoproj-labs#1234 (using with_param with a Pydantic Input type). Signed-off-by: Alice Purcell <[email protected]>
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #1236 +/- ##
=======================================
- Coverage 45.9% 45.8% -0.2%
=======================================
Files 60 60
Lines 4096 4110 +14
Branches 861 863 +2
=======================================
+ Hits 1884 1886 +2
- Misses 2180 2192 +12
Partials 32 32 ☔ View full report in Codecov by Sentry. |
It looks like the code coverage is broken, because it's complaining about lines not being tested that definitely are. |
If a field has a Parameter or Artifact annotation, a copy will be returned, with name added if missing. | ||
Otherwise, a Parameter object will be constructed. | ||
If a field has a Parameter or Artifact annotation, a copy will be returned, with missing | ||
fields filled out based on other metadata. Otherwise, a Parameter object will be constructed. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note: I changed this docstring because the core logic is now in a different function, so it will be easy to miss changes in future and bitrot. I intend to change the behaviour as part of fixing #1173, for instance, to set the enum field for Literals.
Signed-off-by: Alice Purcell <[email protected]>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
other things looks good
if annotation := get_workflow_annotation(annotations[field]): | ||
# Copy so as to not modify the fields themselves | ||
annotation_copy = annotation.copy() | ||
annotation_copy.name = annotation.name or field | ||
yield field, field_info, annotation_copy | ||
else: | ||
yield field, field_info, Parameter(name=field) | ||
yield field, field_info, construct_io_from_annotation(field, annotations[field]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
for name, p in inspect.signature(source).parameters.items(): | ||
annotation = get_workflow_annotation(p.annotation) | ||
if not annotation or not annotation.output: | ||
annotation = construct_io_from_annotation(name, p.annotation) |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
Co-authored-by: Ukjae Jeong <[email protected]> Signed-off-by: Alice <[email protected]>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
looks good to me :)
src/hera/workflows/_meta_mixins.py
Outdated
for param in non_default_parameters: | ||
param.value = "{{" + ("item" if len(non_default_parameters) == 1 else f"item.{param.name}") + "}}" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems harder to read/understand IMO
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It can't be as succinct as before because we're doing a side-effect. A couple of options spring to mind, which do you prefer?
# Just move the if/else out into a separate line
for param in non_default_parameters:
param_label = "item" if len(non_default_parameters) == 1 else f"item.{param.name}"
param.value = "{{" + param_label + "}}"
# Move the if/else back out to the top
if len(non_default_parameters) == 1:
non_default_parameters[0].value = "{{item}}"
else:
for param in non_default_parameters:
param.value = "{{{item." + param.name + "))
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The second please! 🙏 Makes it much more obvious that we're doing a very different thing if len == 1
@@ -0,0 +1,38 @@ | |||
""" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think we need to add these two examples - they are not demonstrating something unique - script annotations/pydantic IO have their own examples, and the syntax for with_param
is more simply shown in the loops examples, where the syntax in the DAG construction is the same.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fair, I added them to exercise the code rather than as examples per se. Is there a better place to put this kind of end-to-end YAML output test?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You could do a roundtrip to_dict
/from_dict
like in the test_script_annotations
tests
hera/tests/test_script_annotations.py
Lines 373 to 374 in a08f744
workflow_dict = workflow.to_dict() | |
assert workflow == Workflow.from_dict(workflow_dict) |
6f32dce
to
4508194
Compare
Signed-off-by: Alice Purcell <[email protected]>
Signed-off-by: Alice Purcell <[email protected]>
Signed-off-by: Alice Purcell <[email protected]>
fd989e9
to
12cfa63
Compare
Signed-off-by: Alice Purcell <[email protected]>
Pull Request Checklist
with_param
#861 and fixes Issue using with_param and Pydantic IO #1234Description of PR
Currently, the logic implementing
with_param
assumes that Argo Parameters exactly match script function parameters, including the Python parameter name. This does not support the experimentalscript_annotations
andscript_pydantic_io
features.This PR extracts common logic for creating an IO object from parameter/field metadata into a
construct_io_from_annotation
function, which copies any IO annotation it finds, sets the name if missing, and defaults to a Parameter if no annotation is found. It then uses this to generalize_get_param_items_from_source
, including:output=True
annotationsArtifact
annotations