Repository: PrefectHQ/prefect

Issue:
Using a class argument in a flow-decorated function returns an error
### First check

- [X] I added a descriptive title to this issue.
- [X] I used the GitHub search to find a similar issue and didn't find it.
- [X] I searched the Prefect documentation for this issue.
- [X] I checked that this issue is related to Prefect and not one of its dependencies.

### Bug summary

I am trying to pass a class object as the only positional argument to a flow-decorated function, and I'm getting back a `TypeError` when trying to define the function itself (before calling it on any arguments). It works fine if I remove the type-hint. This issue happens with other object-based classes beyond the example below.

### Reproduction
After `pip install ase` using the `Atoms` object described [here](https://wiki.fysik.dtu.dk/ase/_modules/ase/atoms.html#Atoms):

```python3
from prefect import flow
from ase.atoms import Atoms

@flow
def MyFlow(atoms: Atoms):
    print(atoms)
```

To confirm it's not something weird with `ase.atoms.Atoms`, here's another code. After `pip install pymatgen` using the `Structure` object described [here](https://pymatgen.org/pymatgen.core.structure.html#pymatgen.core.structure.Structure):

```python3
from prefect import flow
from pymatgen.core import Structure

@flow
def MyFlow(atoms: Structure):
    print(atoms)
```

Same error in both cases.

### Error

```python3
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[36], line 5
      1 from prefect import flow
      2 from ase.atoms import Atoms
      4 @flow
----> 5 def MyFlow(atoms: Atoms):
      6     print(atoms)

File [c:\Users\asros\miniconda3\envs\prefect\lib\site-packages\prefect\flows.py:659](file:///C:/Users/asros/miniconda3/envs/prefect/lib/site-packages/prefect/flows.py:659), in flow(__fn, name, version, flow_run_name, retries, retry_delay_seconds, task_runner, description, timeout_seconds, validate_parameters, persist_result, result_storage, result_serializer, cache_result_in_memory, log_prints, on_completion, on_failure)
    569 """
    570 Decorator to designate a function as a Prefect workflow.
    571 
   (...)
    654     >>>     pass
    655 """
    656 if __fn:
    657     return cast(
    658         Flow[P, R],
--> 659         Flow(
    660             fn=__fn,
    661             name=name,
    662             version=version,
    663             flow_run_name=flow_run_name,
    664             task_runner=task_runner,
...
    121 def __subclasscheck__(cls, subclass):
    122     """Override for issubclass(subclass, cls)."""
--> 123     return _abc_subclasscheck(cls, subclass)

TypeError: issubclass() arg 1 must be a class
```


### Versions

```Text
Version:             2.10.4
API version:         0.8.4
Python version:      3.9.16
Git commit:          b6d0433a
Built:               Thu, Apr 13, 2023 5:34 PM
OS/Arch:             win32/AMD64
Profile:             default
Server type:         cloud
```


### Additional context

I am very new to Prefect, so my apologies if this is quite basic. I see the docs state "Flow parameters must be serializable by Pydantic." Perhaps that is it? That said, it does run successfully without the type-hint.


Discussion:
Thanks for the report. I can reproduce a similar problem with

```python
from prefect import flow

class Test:
    pass

@flow
def foo(x: Test):
   print(x)
```

```
Traceback (most recent call last):
  File "/Users/mz/eng/src/prefecthq/prefect/example.py", line 7, in <module>
    def foo(x: Test):
  File "/Users/mz/eng/src/prefecthq/prefect/src/prefect/flows.py", line 659, in flow
    Flow(
  File "/Users/mz/eng/src/prefecthq/prefect/src/prefect/context.py", line 178, in __register_init__
    __init__(__self__, *args, **kwargs)
  File "/Users/mz/eng/src/prefecthq/prefect/src/prefect/flows.py", line 192, in __init__
    ValidatedFunction(self.fn, config=None)
  File "pydantic/decorator.py", line 126, in pydantic.decorator.ValidatedFunction.__init__
    try:
  File "pydantic/decorator.py", line 264, in pydantic.decorator.ValidatedFunction.create_model
    A version of "decorator" implemented via "exec" and not via the
  File "pydantic/main.py", line 1026, in pydantic.main.create_model
  File "pydantic/main.py", line 198, in pydantic.main.ModelMetaclass.__new__
  File "pydantic/fields.py", line 506, in pydantic.fields.ModelField.infer
  File "pydantic/fields.py", line 436, in pydantic.fields.ModelField.__init__
  File "pydantic/fields.py", line 557, in pydantic.fields.ModelField.prepare
  File "pydantic/fields.py", line 831, in pydantic.fields.ModelField.populate_validators
  File "pydantic/validators.py", line 765, in find_validators
RuntimeError: no validator found for <class '__main__.Test'>, see `arbitrary_types_allowed` in Config
```

I'm a little confused by the subclass check error — is there more traceback somewhere?

You can bypass this behavior with `@flow(validate_parameters=False)` — we use Pydantic to coerce parameters sent via the API into the annotated type.
Thanks for confirming! For some reason, I can no longer reproduce that traceback! Very bizarre. I'm now getting the same as you. Sorry for the inconsistency there.

```python
RuntimeError                             Traceback (most recent call last)Cell In[1], line 5
      1 from prefect import flow
      2 from ase.atoms import Atoms
      4 @flow
----> 5 def MyFlow(atoms: Atoms):
      6     print(atoms)

File ~\miniconda3\envs\prefect\lib\site-packages\prefect\flows.py:659, in flow(__fn, name, version, flow_run_name, retries, retry_delay_seconds, task_runner, description, timeout_seconds, validate_parameters, persist_result, result_storage, result_serializer, cache_result_in_memory, log_prints, on_completion, on_failure)
    569 """
    570 Decorator to designate a function as a Prefect workflow.
    571
   (...)
    654     >>>     pass
    655 """
    656 if __fn:
    657     return cast(
    658         Flow[P, R],
--> 659         Flow(
    660             fn=__fn,
    661             name=name,
    662             version=version,
    663             flow_run_name=flow_run_name,
    664             task_runner=task_runner,
    665             description=description,
    666             timeout_seconds=timeout_seconds,
    667             validate_parameters=validate_parameters,
    668             retries=retries,
    669             retry_delay_seconds=retry_delay_seconds,
    670             persist_result=persist_result,
    671             result_storage=result_storage,
    672             result_serializer=result_serializer,
    673             cache_result_in_memory=cache_result_in_memory,
    674             log_prints=log_prints,
    675             on_completion=on_completion,
    676             on_failure=on_failure,
    677         ),
    678     )
    679 else:
    680     return cast(
    681         Callable[[Callable[P, R]], Flow[P, R]],
    682         partial(
   (...)
    700         ),
    701     )

File ~\miniconda3\envs\prefect\lib\site-packages\prefect\context.py:178, in PrefectObjectRegistry.register_instances.<locals>.__register_init__(__self__, *args, **kwargs)
    176 registry = cls.get()
    177 try:
--> 178     __init__(__self__, *args, **kwargs)
    179 except Exception as exc:
    180     if not registry or not registry.capture_failures:

File ~\miniconda3\envs\prefect\lib\site-packages\prefect\flows.py:192, in Flow.__init__(self, fn, name, version, flow_run_name, retries, retry_delay_seconds, task_runner, description, timeout_seconds, validate_parameters, persist_result, result_storage, result_serializer, cache_result_in_memory, log_prints, on_completion, on_failure)
    186 if self.should_validate_parameters:
    187     # Try to create the validated function now so that incompatibility can be
    188     # raised at declaration time rather than at runtime
    189     # We cannot, however, store the validated function on the flow because it
    190     # is not picklable in some environments
    191     try:
--> 192         ValidatedFunction(self.fn, config=None)
    193     except pydantic.ConfigError as exc:
    194         raise ValueError(
    195             "Flow function is not compatible with `validate_parameters`. "
    196             "Disable validation or change the argument names."
    197         ) from exc

File ~\miniconda3\envs\prefect\lib\site-packages\pydantic\decorator.py:126, in pydantic.decorator.ValidatedFunction.__init__()

File ~\miniconda3\envs\prefect\lib\site-packages\pydantic\decorator.py:264, in pydantic.decorator.ValidatedFunction.create_model()

File ~\miniconda3\envs\prefect\lib\site-packages\pydantic\main.py:1026, in pydantic.main.create_model()

File ~\miniconda3\envs\prefect\lib\site-packages\pydantic\main.py:197, in pydantic.main.ModelMetaclass.__new__()

File ~\miniconda3\envs\prefect\lib\site-packages\pydantic\fields.py:506, in pydantic.fields.ModelField.infer()

File ~\miniconda3\envs\prefect\lib\site-packages\pydantic\fields.py:436, in pydantic.fields.ModelField.__init__()

File ~\miniconda3\envs\prefect\lib\site-packages\pydantic\fields.py:557, in pydantic.fields.ModelField.prepare()

File ~\miniconda3\envs\prefect\lib\site-packages\pydantic\fields.py:831, in pydantic.fields.ModelField.populate_validators()

File ~\miniconda3\envs\prefect\lib\site-packages\pydantic\validators.py:765, in find_validators()

RuntimeError: no validator found for <class 'ase.atoms.Atoms'>, see `arbitrary_types_allowed` in Config
```
I'm getting this as well... I don't really understand what's going on so I don't have much other useful feedback to give.
Actually, I'm seeing this if I annotate a flow parameter with `collections.abc.Sequence[Any]` and when I use `from __future__ import annotations`. This seems like a bug in Pydantic.
I got the same traceback as before. Here's a more complete version. The error goes away if I don't use the class (`Atoms`) as a type hint. Strangely, setting `validate_parameters=False` had no effect.

```python
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[13], line 4
      2 from ase.build import bulk
      3 from quacc.recipes.emt.core import relax_job
----> 4 from quacc.recipes.emt.prefect.slabs import bulk_to_slabs_flow
      6 @flow
      7 def workflow(atoms):
      8     relaxed_bulk_future = task(relax_job).submit(atoms)

File /mnt/c/Users/asros/github/quacc/quacc/recipes/emt/prefect/slabs.py:12
      7 from quacc.recipes.emt.core import relax_job, static_job
      8 from quacc.util.slabs import make_max_slabs_from_bulk
     11 @flow
---> 12 def bulk_to_slabs_flow(
     13     atoms: Atoms,
     14     slabgen_kwargs: dict | None = None,
     15     slab_relax_task: Task = task(relax_job),
     16     slab_static_task: Task | None = task(static_job),
     17     slab_relax_kwargs: dict | None = None,
     18     slab_static_kwargs: dict | None = None,
     19 ) -> Flow:
     20     """
     21     Workflow consisting of:
     22 
   (...)
     47         A Prefect Flow
     48     """
     50     slab_relax_kwargs = slab_relax_kwargs or {}

File [~/software/miniconda/envs/quacc/lib/python3.10/site-packages/prefect/flows.py:722](https://vscode-remote+wsl-002bubuntu.vscode-resource.vscode-cdn.net/home/rosen/prefect/~/software/miniconda/envs/quacc/lib/python3.10/site-packages/prefect/flows.py:722), in flow(__fn, name, version, flow_run_name, retries, retry_delay_seconds, task_runner, description, timeout_seconds, validate_parameters, persist_result, result_storage, result_serializer, cache_result_in_memory, log_prints, on_completion, on_failure, on_cancellation, on_crashed)
    620 """
    621 Decorator to designate a function as a Prefect workflow.
    622 
   (...)
    717     >>>     pass
    718 """
    719 if __fn:
    720     return cast(
    721         Flow[P, R],
--> 722         Flow(
    723             fn=__fn,
    724             name=name,
    725             version=version,
    726             flow_run_name=flow_run_name,
    727             task_runner=task_runner,
    728             description=description,
    729             timeout_seconds=timeout_seconds,
    730             validate_parameters=validate_parameters,
    731             retries=retries,
    732             retry_delay_seconds=retry_delay_seconds,
    733             persist_result=persist_result,
    734             result_storage=result_storage,
    735             result_serializer=result_serializer,
    736             cache_result_in_memory=cache_result_in_memory,
    737             log_prints=log_prints,
    738             on_completion=on_completion,
    739             on_failure=on_failure,
    740             on_cancellation=on_cancellation,
    741             on_crashed=on_crashed,
    742         ),
    743     )
    744 else:
    745     return cast(
    746         Callable[[Callable[P, R]], Flow[P, R]],
    747         partial(
   (...)
    767         ),
    768     )

File [~/software/miniconda/envs/quacc/lib/python3.10/site-packages/prefect/context.py:179](https://vscode-remote+wsl-002bubuntu.vscode-resource.vscode-cdn.net/home/rosen/prefect/~/software/miniconda/envs/quacc/lib/python3.10/site-packages/prefect/context.py:179), in PrefectObjectRegistry.register_instances..__register_init__(__self__, *args, **kwargs)
    177 registry = cls.get()
    178 try:
--> 179     __init__(__self__, *args, **kwargs)
    180 except Exception as exc:
    181     if not registry or not registry.capture_failures:

File [~/software/miniconda/envs/quacc/lib/python3.10/site-packages/prefect/flows.py:213](https://vscode-remote+wsl-002bubuntu.vscode-resource.vscode-cdn.net/home/rosen/prefect/~/software/miniconda/envs/quacc/lib/python3.10/site-packages/prefect/flows.py:213), in Flow.__init__(self, fn, name, version, flow_run_name, retries, retry_delay_seconds, task_runner, description, timeout_seconds, validate_parameters, persist_result, result_storage, result_serializer, cache_result_in_memory, log_prints, on_completion, on_failure, on_cancellation, on_crashed)
    203 self.retries = (
    204     retries if retries is not None else PREFECT_FLOW_DEFAULT_RETRIES.value()
    205 )
    207 self.retry_delay_seconds = (
    208     retry_delay_seconds
    209     if retry_delay_seconds is not None
    210     else PREFECT_FLOW_DEFAULT_RETRY_DELAY_SECONDS.value()
    211 )
--> 213 self.parameters = parameter_schema(self.fn)
    214 self.should_validate_parameters = validate_parameters
    216 if self.should_validate_parameters:
    217     # Try to create the validated function now so that incompatibility can be
    218     # raised at declaration time rather than at runtime
    219     # We cannot, however, store the validated function on the flow because it
    220     # is not picklable in some environments

File [~/software/miniconda/envs/quacc/lib/python3.10/site-packages/prefect/utilities/callables.py:296](https://vscode-remote+wsl-002bubuntu.vscode-resource.vscode-cdn.net/home/rosen/prefect/~/software/miniconda/envs/quacc/lib/python3.10/site-packages/prefect/utilities/callables.py:296), in parameter_schema(fn)
    293 # Generate a Pydantic model at each step so we can check if this parameter
    294 # type is supported schema generation
    295 try:
--> 296     pydantic.create_model(
    297         "CheckParameter", __config__=ModelConfig, **{name: (type_, field)}
    298     ).schema(by_alias=True)
    299 except ValueError:
    300     # This field's type is not valid for schema creation, update it to `Any`
    301     type_ = Any

File [~/software/miniconda/envs/quacc/lib/python3.10/site-packages/pydantic/main.py:664](https://vscode-remote+wsl-002bubuntu.vscode-resource.vscode-cdn.net/home/rosen/prefect/~/software/miniconda/envs/quacc/lib/python3.10/site-packages/pydantic/main.py:664), in pydantic.main.BaseModel.schema()

File [~/software/miniconda/envs/quacc/lib/python3.10/site-packages/pydantic/schema.py:186](https://vscode-remote+wsl-002bubuntu.vscode-resource.vscode-cdn.net/home/rosen/prefect/~/software/miniconda/envs/quacc/lib/python3.10/site-packages/pydantic/schema.py:186), in pydantic.schema.model_schema()

File [~/software/miniconda/envs/quacc/lib/python3.10/site-packages/pydantic/schema.py:580](https://vscode-remote+wsl-002bubuntu.vscode-resource.vscode-cdn.net/home/rosen/prefect/~/software/miniconda/envs/quacc/lib/python3.10/site-packages/pydantic/schema.py:580), in pydantic.schema.model_process_schema()

File [~/software/miniconda/envs/quacc/lib/python3.10/site-packages/pydantic/schema.py:621](https://vscode-remote+wsl-002bubuntu.vscode-resource.vscode-cdn.net/home/rosen/prefect/~/software/miniconda/envs/quacc/lib/python3.10/site-packages/pydantic/schema.py:621), in pydantic.schema.model_type_schema()

File [~/software/miniconda/envs/quacc/lib/python3.10/site-packages/pydantic/schema.py:254](https://vscode-remote+wsl-002bubuntu.vscode-resource.vscode-cdn.net/home/rosen/prefect/~/software/miniconda/envs/quacc/lib/python3.10/site-packages/pydantic/schema.py:254), in pydantic.schema.field_schema()

File [~/software/miniconda/envs/quacc/lib/python3.10/site-packages/pydantic/schema.py:526](https://vscode-remote+wsl-002bubuntu.vscode-resource.vscode-cdn.net/home/rosen/prefect/~/software/miniconda/envs/quacc/lib/python3.10/site-packages/pydantic/schema.py:526), in pydantic.schema.field_type_schema()

File [~/software/miniconda/envs/quacc/lib/python3.10/site-packages/pydantic/schema.py:921](https://vscode-remote+wsl-002bubuntu.vscode-resource.vscode-cdn.net/home/rosen/prefect/~/software/miniconda/envs/quacc/lib/python3.10/site-packages/pydantic/schema.py:921), in pydantic.schema.field_singleton_schema()

File [~/software/miniconda/envs/quacc/lib/python3.10/abc.py:123](https://vscode-remote+wsl-002bubuntu.vscode-resource.vscode-cdn.net/home/rosen/prefect/~/software/miniconda/envs/quacc/lib/python3.10/abc.py:123), in ABCMeta.__subclasscheck__(cls, subclass)
    121 def __subclasscheck__(cls, subclass):
    122     """Override for issubclass(subclass, cls)."""
--> 123     return _abc_subclasscheck(cls, subclass)

TypeError: issubclass() arg 1 must be a class
```
I can confirm that the error goes away if you use `validate_parameters=False`. My comment before about a remaining error was due to an outdated version of Pydantic. Still, it'd be nice to be able to support class parameters.
@zanieb: Seems potentially related: https://github.com/pydantic/pydantic/issues/182#issuecomment-511522161

Resolving patch:
diff --git a/src/prefect/flows.py b/src/prefect/flows.py
--- a/src/prefect/flows.py
+++ b/src/prefect/flows.py
@@ -279,7 +279,7 @@ def __init__(
             # We cannot, however, store the validated function on the flow because it
             # is not picklable in some environments
             try:
-                ValidatedFunction(self.fn, config=None)
+                ValidatedFunction(self.fn, config={"arbitrary_types_allowed": True})
             except pydantic.ConfigError as exc:
                 raise ValueError(
                     "Flow function is not compatible with `validate_parameters`. "
@@ -443,7 +443,9 @@ def validate_parameters(self, parameters: Dict[str, Any]) -> Dict[str, Any]:
         Raises:
             ParameterTypeError: if the provided parameters are not valid
         """
-        validated_fn = ValidatedFunction(self.fn, config=None)
+        validated_fn = ValidatedFunction(
+            self.fn, config={"arbitrary_types_allowed": True}
+        )
         args, kwargs = parameters_to_args_kwargs(self.fn, parameters)
 
         try:


Test patch:
diff --git a/tests/test_flows.py b/tests/test_flows.py
--- a/tests/test_flows.py
+++ b/tests/test_flows.py
@@ -1367,6 +1367,17 @@ def my_flow(x):
         # See #1638.
         assert my_flow(data) == data
 
+    def test_flow_parameter_annotations_can_be_non_pydantic_classes(self):
+        class Test:
+            pass
+
+        @flow
+        def my_flow(instance: Test):
+            return instance
+
+        instance = my_flow(Test())
+        assert isinstance(instance, Test)
+
     def test_subflow_parameters_can_be_pydantic_types(self):
         @flow
         def my_flow():
@@ -1410,6 +1421,21 @@ def my_subflow(x):
 
         assert my_flow() == ParameterTestModel(data=1)
 
+    def test_subflow_parameter_annotations_can_be_non_pydantic_classes(self):
+        class Test:
+            pass
+
+        @flow
+        def my_flow(i: Test):
+            return my_subflow(i)
+
+        @flow
+        def my_subflow(i: Test):
+            return i
+
+        instance = my_flow(Test())
+        assert isinstance(instance, Test)
+
 
 class TestSubflowTaskInputs:
     async def test_subflow_with_one_upstream_task_future(self, prefect_client):
diff --git a/tests/test_tasks.py b/tests/test_tasks.py
--- a/tests/test_tasks.py
+++ b/tests/test_tasks.py
@@ -3393,6 +3393,22 @@ def my_flow(name):
     assert generate_task_run_name.call_count == 2
 
 
+def test_task_parameter_annotations_can_be_non_pydantic_classes():
+    class Test:
+        pass
+
+    @task
+    def my_task(instance: Test):
+        return instance
+
+    @flow
+    def my_flow(instance: Test):
+        return my_task(instance)
+
+    instance = my_flow(Test())
+    assert isinstance(instance, Test)
+
+
 def create_hook(mock_obj):
     def my_hook(task, task_run, state):
         mock_obj()