-
Notifications
You must be signed in to change notification settings - Fork 110
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add optional support for pydantic v1 models when v2 is installed (#897)
**Pull Request Checklist** - [ ] Fixes #<!--issue number goes here--> - [x] Tests added - [x] Documentation/examples added - [x] [Good commit messages](https://cbea.ms/git-commit/) and/or PR title **Description of PR** Currently, when pydantic v2 is used, users can only use v2 models. This PR adds the ability to optionally provide v1 models to scripts. Signed-off-by: Sambhav Kothari <[email protected]>
- Loading branch information
Showing
7 changed files
with
307 additions
and
14 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,140 @@ | ||
apiVersion: argoproj.io/v1alpha1 | ||
kind: Workflow | ||
metadata: | ||
name: my-workflow | ||
spec: | ||
templates: | ||
- name: my-steps | ||
steps: | ||
- - arguments: | ||
parameters: | ||
- name: input | ||
value: '{"a": 2, "b": "bar", "c": 42}' | ||
name: my-function | ||
template: my-function | ||
- - arguments: | ||
parameters: | ||
- name: input | ||
value: '{"a": 2, "b": "bar", "c": 42}' | ||
name: str-function | ||
template: str-function | ||
- - arguments: | ||
parameters: | ||
- name: inputs | ||
value: '[{"a": 2, "b": "bar", "c": 42}, {"a": 2, "b": "bar", "c": 42.0}]' | ||
name: another-function | ||
template: another-function | ||
- - arguments: | ||
parameters: | ||
- name: a-but-kebab | ||
value: '3' | ||
- name: b-but-kebab | ||
value: bar | ||
name: function-kebab | ||
template: function-kebab | ||
- - arguments: | ||
parameters: | ||
- name: input-value | ||
value: '{"a": 3, "b": "bar", "c": "42"}' | ||
name: function-kebab-object | ||
template: function-kebab-object | ||
- inputs: | ||
parameters: | ||
- name: input | ||
name: my-function | ||
script: | ||
args: | ||
- -m | ||
- hera.workflows.runner | ||
- -e | ||
- examples.workflows.scripts.callable_script_v1:my_function | ||
command: | ||
- python | ||
env: | ||
- name: hera__script_annotations | ||
value: '' | ||
- name: hera__pydantic_mode | ||
value: '1' | ||
image: my-image-with-python-source-code-and-dependencies | ||
source: '{{inputs.parameters}}' | ||
- inputs: | ||
parameters: | ||
- name: input | ||
name: str-function | ||
script: | ||
args: | ||
- -m | ||
- hera.workflows.runner | ||
- -e | ||
- examples.workflows.scripts.callable_script_v1:str_function | ||
command: | ||
- python | ||
env: | ||
- name: hera__script_annotations | ||
value: '' | ||
- name: hera__pydantic_mode | ||
value: '1' | ||
image: my-image-with-python-source-code-and-dependencies | ||
source: '{{inputs.parameters}}' | ||
- inputs: | ||
parameters: | ||
- name: inputs | ||
name: another-function | ||
script: | ||
args: | ||
- -m | ||
- hera.workflows.runner | ||
- -e | ||
- examples.workflows.scripts.callable_script_v1:another_function | ||
command: | ||
- python | ||
env: | ||
- name: hera__script_annotations | ||
value: '' | ||
- name: hera__pydantic_mode | ||
value: '1' | ||
image: my-image-with-python-source-code-and-dependencies | ||
source: '{{inputs.parameters}}' | ||
- inputs: | ||
parameters: | ||
- default: '2' | ||
name: a-but-kebab | ||
- default: foo | ||
name: b-but-kebab | ||
- default: '42.0' | ||
name: c-but-kebab | ||
name: function-kebab | ||
script: | ||
args: | ||
- -m | ||
- hera.workflows.runner | ||
- -e | ||
- examples.workflows.scripts.callable_script_v1:function_kebab | ||
command: | ||
- python | ||
env: | ||
- name: hera__script_annotations | ||
value: '' | ||
- name: hera__pydantic_mode | ||
value: '1' | ||
image: my-image-with-python-source-code-and-dependencies | ||
source: '{{inputs.parameters}}' | ||
- inputs: | ||
parameters: | ||
- name: input-value | ||
name: function-kebab-object | ||
script: | ||
args: | ||
- -m | ||
- hera.workflows.runner | ||
- -e | ||
- examples.workflows.scripts.callable_script_v1:function_kebab_object | ||
command: | ||
- python | ||
env: | ||
- name: hera__script_annotations | ||
value: '' | ||
- name: hera__pydantic_mode | ||
value: '1' | ||
image: my-image-with-python-source-code-and-dependencies | ||
source: '{{inputs.parameters}}' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,100 @@ | ||
from typing import List, Union | ||
|
||
from hera.shared.serialization import serialize | ||
|
||
try: | ||
from typing import Annotated # type: ignore | ||
except ImportError: | ||
from typing_extensions import Annotated # type: ignore | ||
try: | ||
from pydantic.v1 import BaseModel | ||
except (ImportError, ModuleNotFoundError): | ||
from pydantic import BaseModel | ||
|
||
|
||
from hera.shared import global_config | ||
from hera.workflows import Parameter, RunnerScriptConstructor, Script, Steps, Workflow, script | ||
|
||
# Note, setting constructor to runner is only possible if the source code is available | ||
# along with dependencies include hera in the image. | ||
# Callable is a robust mode that allows you to run any python function | ||
# and is compatible with pydantic. It automatically parses the input | ||
# and serializes the output. | ||
global_config.image = "my-image-with-python-source-code-and-dependencies" | ||
global_config.set_class_defaults(Script, constructor=RunnerScriptConstructor(pydantic_mode=1)) | ||
# Script annotations is still an experimental feature and we need to explicitly opt in to it | ||
# Note that experimental features are subject to breaking changes in future releases of the same major version | ||
global_config.experimental_features["script_annotations"] = True | ||
|
||
|
||
# An optional pydantic input type | ||
# hera can automatically de-serialize argo | ||
# arguments into types denoted by your function's signature | ||
# as long as they are de-serializable by pydantic | ||
# This provides auto-magic input parsing with validation | ||
# provided by pydantic. | ||
class Input(BaseModel): | ||
a: int | ||
b: str = "foo" | ||
c: Union[str, int, float] | ||
|
||
class Config: | ||
smart_union = True | ||
|
||
|
||
# An optional pydantic output type | ||
# hera can automatically serialize the output | ||
# of your function into a json string | ||
# as long as they are serializable by pydantic or json serializable | ||
# This provides auto-magic output serialization with validation | ||
# provided by pydantic. | ||
class Output(BaseModel): | ||
output: List[Input] | ||
|
||
|
||
@script() | ||
def my_function(input: Input) -> Output: | ||
return Output(output=[input]) | ||
|
||
|
||
# Note that the input type is a list of Input | ||
# hera can also automatically de-serialize | ||
# composite types like lists and dicts | ||
@script() | ||
def another_function(inputs: List[Input]) -> Output: | ||
return Output(output=inputs) | ||
|
||
|
||
# it also works with raw json strings | ||
# but those must be explicitly marked as | ||
# a string type | ||
@script() | ||
def str_function(input: str) -> Output: | ||
# Example function to ensure we are not json parsing | ||
# string types before passing it to the function | ||
return Output(output=[Input.parse_raw(input)]) | ||
|
||
|
||
# Use the script_annotations feature to seamlessly enable aliased kebab-case names | ||
# as your template interface, while using regular snake_case in the Python code | ||
@script() | ||
def function_kebab( | ||
a_but_kebab: Annotated[int, Parameter(name="a-but-kebab")] = 2, | ||
b_but_kebab: Annotated[str, Parameter(name="b-but-kebab")] = "foo", | ||
c_but_kebab: Annotated[float, Parameter(name="c-but-kebab")] = 42.0, | ||
) -> Output: | ||
return Output(output=[Input(a=a_but_kebab, b=b_but_kebab, c=c_but_kebab)]) | ||
|
||
|
||
@script() | ||
def function_kebab_object(annotated_input_value: Annotated[Input, Parameter(name="input-value")]) -> Output: | ||
return Output(output=[annotated_input_value]) | ||
|
||
|
||
with Workflow(name="my-workflow") as w: | ||
with Steps(name="my-steps") as s: | ||
my_function(arguments={"input": Input(a=2, b="bar", c=42)}) | ||
str_function(arguments={"input": serialize(Input(a=2, b="bar", c=42))}) | ||
another_function(arguments={"inputs": [Input(a=2, b="bar", c=42), Input(a=2, b="bar", c=42.0)]}) | ||
function_kebab(arguments={"a-but-kebab": 3, "b-but-kebab": "bar"}) | ||
function_kebab_object(arguments={"input-value": Input(a=3, b="bar", c="42")}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.