-
Notifications
You must be signed in to change notification settings - Fork 105
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
base: main
Are you sure you want to change the base?
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]>
# as the default value ones are captured by the automatically generated `Parameter` fields for positional | ||
# kwargs. Otherwise, we assume that the user sets the value of the parameter via the `with_param` field | ||
io = construct_io_from_annotation(p.name, p.annotation) | ||
if isinstance(io, Parameter) and io.default is None and not io.output: |
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 wasn't sure whether it made sense to include Artifact
s here or not; if this was the wrong decision, LMK and I'll fix it.
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 me it looks reasonable while reading callee function (it is _get_deduped_params_from_source
, and returns List[Parameter])
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.
Perhaps we should be raising an error instead?
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.
We only want to inspect parameters in this function. We shouldn't raise an error (as Artifact defaults work differently)
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #1236 +/- ##
=======================================
- Coverage 45.9% 45.8% -0.1%
=======================================
Files 60 60
Lines 4082 4094 +12
Branches 857 858 +1
=======================================
+ Hits 1875 1877 +2
- Misses 2175 2185 +10
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
# as the default value ones are captured by the automatically generated `Parameter` fields for positional | ||
# kwargs. Otherwise, we assume that the user sets the value of the parameter via the `with_param` field | ||
io = construct_io_from_annotation(p.name, p.annotation) | ||
if isinstance(io, Parameter) and io.default is None and not io.output: |
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 me it looks reasonable while reading callee function (it is _get_deduped_params_from_source
, and returns List[Parameter])
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 :)
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
@@ -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) |
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