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

Prefer user signature over Hera-added overloads in @script typing #1181

Merged
merged 5 commits into from
Aug 30, 2024

Conversation

alicederyn
Copy link
Collaborator

Pull Request Checklist

Description of PR
Address review feedback in #1180 missed due to early merge.

  • If we take a scenario where a user is adding the Hera library to their existing code and adds the @script decorator to a function, type checking should not be affected. We are an optional wrapper and should be treating the code as such.
  • Preserve any docstring provided by the user for the overload which invokes the underlying code.
  • Improve documentation for remaining overloads.

The overload using the type signature of the decorated function had a docstring; removing it causes VS Code to pick up the docstring of the decorated function instead.

Signed-off-by: Alice Purcell <alicederyn@gmail.com>
If an invocation matches both the decorated function signature and a Hera-added signature, assume the user intended to invoke the decorated function rather than Hera. Users can always fall back to invoking the Hera constructors (e.g. `Task(template=my_function)`) directly if the type checker picks the wrong signature.

Signed-off-by: Alice Purcell <alicederyn@gmail.com>
Signed-off-by: Alice Purcell <alicederyn@gmail.com>
Frame _ScriptDecoratedFunction docstrings in terms of what the call does, not what the call is.

Signed-off-by: Alice Purcell <alicederyn@gmail.com>
@alicederyn alicederyn added semver:patch A change requiring a patch version bump type:bug A general bug type:enhancement A general enhancement and removed type:bug A general bug labels Aug 29, 2024
Copy link

codecov bot commented Aug 29, 2024

Codecov Report

Attention: Patch coverage is 66.66667% with 1 line in your changes missing coverage. Please review.

Project coverage is 81.6%. Comparing base (d553546) to head (547340b).
Report is 133 commits behind head on main.

Files with missing lines Patch % Lines
src/hera/workflows/script.py 66.6% 1 Missing ⚠️
Additional details and impacted files
@@           Coverage Diff           @@
##            main   #1181     +/-   ##
=======================================
- Coverage   81.7%   81.6%   -0.2%     
=======================================
  Files         54      60      +6     
  Lines       4208    4768    +560     
  Branches     889    1020    +131     
=======================================
+ Hits        3439    3891    +452     
- Misses       574     647     +73     
- Partials     195     230     +35     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@alicederyn alicederyn marked this pull request as ready for review August 29, 2024 12:20
Copy link
Collaborator

@elliotgunton elliotgunton left a comment

Choose a reason for hiding this comment

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

Minor doc string suggestions, but I would also update the name/argument types

src/hera/workflows/script.py Outdated Show resolved Hide resolved
src/hera/workflows/script.py Outdated Show resolved Hide resolved
@@ -654,13 +666,16 @@ def __call__( # type: ignore [overload-overlap]
with_param: Optional[Any] = ...,
Copy link
Collaborator

Choose a reason for hiding this comment

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

Could we update arguments in both overloads to Optional? We could also update name to be Optional as it technically is, within the magic syntax call.

Copy link
Collaborator Author

@alicederyn alicederyn Aug 29, 2024

Choose a reason for hiding this comment

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

  • ArgumentsT is already Optional:
    ArgumentsT = Optional[
    Union[
    ModelArguments,
    OneOrMany[Union[Parameter, ModelParameter, Artifact, ModelArtifact, Dict[str, Any]]],
    ]
    ]
  • I don't think name is Optional; if you pass in None, the default will be overwritten:
    if "name" in script_kwargs:
    # take the client-provided `name` if it is submitted, pop the name for otherwise there will be two
    # kwargs called `name`
    name = script_kwargs.pop("name")
    else:
    # otherwise populate the `name` from the function name
    name = source.__name__.replace("_", "-")

Copy link
Collaborator

@elliotgunton elliotgunton Aug 29, 2024

Choose a reason for hiding this comment

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

ArgumentsT is already Optional

🤦Oops sorry! 😂

For the name, your linked code is for the @script part, i.e. users can do @script(name=None) which is allowed but would be a bit weird - it's allowed (name is optional) for the very niche use case of inline templates (they are kind of broken/unreliable and we tend to avoid using them though).

The Step/Task name comes from here:

if "name" not in kwargs:
kwargs["name"] = self.name # type: ignore

But names for Steps/Tasks are mandatory, so my point is if you have a callable function doing my_function(arguments={...}), which is allowed (as we infer the name from the _meta_mixins.py lines above), wouldn't/shouldn't that raise a linting error?

For completeness, if you do my_function(name=None,...) you'd get:

examples/workflows/steps/callable_steps.py:14: in <module>
    hello(name=None, arguments={"name": "hello-1-{{inputs.parameters.my-step-input}}"})
src/hera/workflows/script.py:771: in task_wrapper
    return s.__call__(*args, **kwargs)
src/hera/workflows/_meta_mixins.py:378: in __call__
    return Step(template=self, **kwargs)

...

>           raise validation_error
E           pydantic.v1.error_wrappers.ValidationError: 1 validation error for Step
E           name
E             none is not an allowed value (type=type_error.none.not_allowed)

Which we should fix if we make it name: Optional = None

Copy link
Collaborator Author

@alicederyn alicederyn Aug 29, 2024

Choose a reason for hiding this comment

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

Whoops! Yep, your code snippet was what I was trying to link to. Let me restate my understanding to check we're on the same page. Right now, at runtime:

  1. If name is provided and is a string, all is good.
  2. If name is provided and None, there's a runtime error.
  3. If name is not provided, it defaults to the name of the Script.

I expressed that as name: str = .... i.e. it must be a string but can be left at the default. Declaring it as Optional would only let the user pass in None, which doesn't match current behaviour.

We could change the implementation to allow None and then mark it as Optional in the overloads (I avoided any implementation changes), just for consistency. Is that the ask?

Copy link
Collaborator Author

@alicederyn alicederyn Aug 29, 2024

Choose a reason for hiding this comment

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

if you have a callable function doing my_function(arguments={...}), which is allowed (as we infer the name from the _meta_mixins.py lines above), wouldn't that raise a linting error?

Not with how I've written the type hints right now, no. test_optional_step_and_task_arguments validates this.

Copy link
Collaborator Author

@alicederyn alicederyn Aug 29, 2024

Choose a reason for hiding this comment

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

Just to further check we're on the same page, Optional doesn't let you leave off an argument, it lets you pass in None. = ... lets you leave off the argument (and has no effect on whether you can pass in None). The ... is a Protocol convention to avoid duplicating (or getting wrong) the default value.

Copy link
Collaborator

Choose a reason for hiding this comment

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

I actually didn't know about the ... protocol behaviour for defaults, but it seems like the best way to describe "there is a default, but it will be set at runtime (so we can't give an accurate type hint)." (Unless we can capture that? Not fussed right now though.)

I think the code is fine then, as I was just confused by the intellisense suggesting name is required because I didn't understand what the = ... type hint actually meant :shipit:

Co-authored-by: Elliot Gunton <elliotgunton@gmail.com>
Signed-off-by: Alice Purcell <alicederyn@gmail.com>
Copy link
Collaborator

@elliotgunton elliotgunton left a comment

Choose a reason for hiding this comment

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

Good stuff, thank you! 🚀

@samj1912 samj1912 merged commit f84bfc8 into argoproj-labs:main Aug 30, 2024
18 of 20 checks passed
@alicederyn alicederyn deleted the script.decorator.peer.review branch August 30, 2024 11:17
@elliotgunton elliotgunton added type:bug A general bug and removed type:enhancement A general enhancement labels Aug 30, 2024
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.

3 participants