Skip to content
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

Open
wants to merge 7 commits into
base: main
Choose a base branch
from

Conversation

alicederyn
Copy link
Collaborator

Pull Request Checklist

Description 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 experimental script_annotations and script_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:

  • pulling the parameters from a Pydantic Input type instead, if present
  • respecting the name in the Parameter annotation, if present
  • ignoring output=True annotations
  • ignoring Artifact annotations

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:
Copy link
Collaborator Author

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 Artifacts here or not; if this was the wrong decision, LMK and I'll fix it.

Copy link
Collaborator

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])

Copy link
Collaborator Author

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?

Copy link
Collaborator

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)

@alicederyn alicederyn added semver:patch A change requiring a patch version bump type:bug A general bug labels Oct 10, 2024
Copy link

codecov bot commented Oct 10, 2024

Codecov Report

Attention: Patch coverage is 12.24490% with 43 lines in your changes missing coverage. Please review.

Project coverage is 45.8%. Comparing base (a08f744) to head (e5708fe).

Files with missing lines Patch % Lines
src/hera/workflows/_meta_mixins.py 8.6% 21 Missing ⚠️
src/hera/workflows/script.py 7.1% 13 Missing ⚠️
src/hera/shared/_type_util.py 16.6% 5 Missing ⚠️
src/hera/workflows/_mixins.py 25.0% 3 Missing ⚠️
src/hera/workflows/io/_io_mixins.py 50.0% 1 Missing ⚠️
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.
📢 Have feedback on the report? Share it here.

@alicederyn
Copy link
Collaborator Author

It looks like the code coverage is broken, because it's complaining about lines not being tested that definitely are.

@alicederyn alicederyn marked this pull request as ready for review October 10, 2024 08:35
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.
Copy link
Collaborator Author

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]>
Copy link
Collaborator

@jeongukjae jeongukjae left a 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

src/hera/shared/_type_util.py Outdated Show resolved Hide resolved
# 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:
Copy link
Collaborator

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])
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

Comment on lines 441 to +442
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.

Co-authored-by: Ukjae Jeong <[email protected]>
Signed-off-by: Alice <[email protected]>
Copy link
Collaborator

@jeongukjae jeongukjae left a 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 :)

Comment on lines +305 to +306
for param in non_default_parameters:
param.value = "{{" + ("item" if len(non_default_parameters) == 1 else f"item.{param.name}") + "}}"
Copy link
Collaborator

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 @@
"""
Copy link
Collaborator

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.

Copy link
Collaborator Author

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?

Copy link
Collaborator

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

workflow_dict = workflow.to_dict()
assert workflow == Workflow.from_dict(workflow_dict)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
semver:patch A change requiring a patch version bump type:bug A general bug
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Issue using with_param and Pydantic IO Script annotation inputs doesn't play well with with_param
3 participants