Repository: PrefectHQ/prefect

Issue:
Creating a Task from a callable object fails if the task does not have a name
        @jmg-duarte The following test fails

```python

    def test_task_run_from_callable_object(self):
        class Foo:
            message = "hello"

            def __call__(self, prefix: str, suffix: str) -> Any:
                return prefix + self.message + suffix

        obj = Foo()
        foo = Task(fn=obj)

        @flow
        def bar():
            return foo("a", suffix="b")

        assert bar() == "ahellob"
```
>  `E       AttributeError: 'Foo' object has no attribute '__name__'`

I may have to revert this change. Do you have time to look at a fix?

_Originally posted by @madkinsz in https://github.com/PrefectHQ/prefect/issues/7217#issuecomment-1284169478_
      


Discussion:

Resolving patch:
diff --git a/src/prefect/tasks.py b/src/prefect/tasks.py
--- a/src/prefect/tasks.py
+++ b/src/prefect/tasks.py
@@ -148,7 +148,14 @@ def __init__(
         self.fn = fn
         self.isasync = inspect.iscoroutinefunction(self.fn)
 
-        self.name = name or self.fn.__name__
+        if not name:
+            if not hasattr(self.fn, "__name__"):
+                self.name = type(self.fn).__name__
+            else:
+                self.name = self.fn.__name__
+        else:
+            self.name = name
+
         self.version = version
 
         raise_for_reserved_arguments(self.fn, ["return_state", "wait_for"])


Test patch:
diff --git a/tests/test_tasks.py b/tests/test_tasks.py
--- a/tests/test_tasks.py
+++ b/tests/test_tasks.py
@@ -155,7 +155,7 @@ def bar():
         with pytest.raises(ValueError, match="Test"):
             state.result()
 
-    def test_task_supports_callable_objects(self):
+    def test_task_with_name_supports_callable_objects(self):
         class A:
             def __call__(self, *_args: Any, **_kwargs: Any) -> Any:
                 return "hello"
@@ -164,6 +164,47 @@ def __call__(self, *_args: Any, **_kwargs: Any) -> Any:
         task = Task(fn=a, name="Task")
         assert task.fn is a
 
+    def test_task_supports_callable_objects(self):
+        class A:
+            def __call__(self, *_args: Any, **_kwargs: Any) -> Any:
+                return "hello"
+
+        a = A()
+        task = Task(fn=a)
+        assert task.fn is a
+
+    def test_task_run_with_name_from_callable_object(self):
+        class Foo:
+            message = "hello"
+
+            def __call__(self, prefix: str, suffix: str) -> Any:
+                return prefix + self.message + suffix
+
+        obj = Foo()
+        foo = Task(fn=obj, name="Task")
+
+        @flow
+        def bar():
+            return foo("a", suffix="b")
+
+        assert bar() == "ahellob"
+
+    def test_task_run_from_callable_object(self):
+        class Foo:
+            message = "hello"
+
+            def __call__(self, prefix: str, suffix: str) -> Any:
+                return prefix + self.message + suffix
+
+        obj = Foo()
+        foo = Task(fn=obj)
+
+        @flow
+        def bar():
+            return foo("a", suffix="b")
+
+        assert bar() == "ahellob"
+
 
 class TestTaskRun:
     def test_task_run_outside_flow_raises(self):