Repository: PrefectHQ/prefect

Issue:
Create `Task.delay`
Wrap `Task.apply_async` with a more convenient method that works like a normal method call. Instead of `my_task.apply_async(args=("my string arg",))` a user should be able to write `my_task.delay("my string arg")`. Then if they need to, when we offer some config options through `apply_async`, they can drop down to `apply_async`.


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
@@ -1128,6 +1128,46 @@ def apply_async(
         )
         return PrefectDistributedFuture(task_run_id=task_run.id)
 
+    def delay(self, *args: P.args, **kwargs: P.kwargs) -> PrefectDistributedFuture:
+        """
+        An alias for `apply_async` with simpler calling semantics.
+
+        Avoids having to use explicit "args" and "kwargs" arguments. Arguments
+        will pass through as-is to the task.
+
+        Examples:
+
+                Define a task
+
+                >>> from prefect import task
+                >>> @task
+                >>> def my_task(name: str = "world"):
+                >>>     return f"hello {name}"
+
+                Create a pending task run for the task
+
+                >>> from prefect import flow
+                >>> @flow
+                >>> def my_flow():
+                >>>     my_task.delay("marvin")
+
+                Wait for a task to finish
+
+                >>> @flow
+                >>> def my_flow():
+                >>>     my_task.delay("marvin").wait()
+
+                Use the result from a task in a flow
+
+                >>> @flow
+                >>> def my_flow():
+                >>>     print(my_task.delay("marvin").result())
+                >>>
+                >>> my_flow()
+                hello marvin
+        """
+        return self.apply_async(args=args, kwargs=kwargs)
+
     def serve(self, task_runner: Optional["BaseTaskRunner"] = None) -> "Task":
         """Serve the task using the provided task runner. This method is used to
         establish a websocket connection with the Prefect server and listen 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
@@ -4233,3 +4233,104 @@ def add_em_up(*args, **kwargs):
         assert await self.get_background_task_run_parameters(
             add_em_up, task_run.state.state_details.task_parameters_id
         ) == {"args": (42,), "kwargs": {"y": 42}}
+
+
+class TestDelay:
+    async def get_background_task_run_parameters(self, task, parameters_id):
+        factory = await ResultFactory.from_autonomous_task(task)
+        return await factory.read_parameters(parameters_id)
+
+    @pytest.mark.parametrize(
+        "args, kwargs",
+        [
+            ((42, 42), {}),
+            ([42, 42], {}),
+            ((), {"x": 42, "y": 42}),
+            ([42], {"y": 42}),
+        ],
+    )
+    async def test_delay_with_args_kwargs(self, args, kwargs):
+        @task
+        def multiply(x, y):
+            return x * y
+
+        task_run = multiply.delay(*args, **kwargs)
+
+        assert await self.get_background_task_run_parameters(
+            multiply, task_run.state.state_details.task_parameters_id
+        ) == {"x": 42, "y": 42}
+
+    def test_delay_with_duplicate_values(self):
+        @task
+        def add(x, y):
+            return x + y
+
+        with pytest.raises(
+            ParameterBindError, match="multiple values for argument 'x'"
+        ):
+            add.delay(42, x=42)
+
+    def test_delay_missing_values(self):
+        @task
+        def add(x, y):
+            return x + y
+
+        with pytest.raises(
+            ParameterBindError, match="missing a required argument: 'y'"
+        ):
+            add.delay(42)
+
+    async def test_delay_handles_default_values(self):
+        @task
+        def add(x, y=42):
+            return x + y
+
+        task_run = add.delay(42)
+
+        assert await self.get_background_task_run_parameters(
+            add, task_run.state.state_details.task_parameters_id
+        ) == {"x": 42, "y": 42}
+
+    async def test_delay_overrides_defaults(self):
+        @task
+        def add(x, y=42):
+            return x + y
+
+        task_run = add.delay(42, y=100)
+
+        assert await self.get_background_task_run_parameters(
+            add, task_run.state.state_details.task_parameters_id
+        ) == {"x": 42, "y": 100}
+
+    async def test_delay_with_variadic_args(self):
+        @task
+        def add_em_up(*args):
+            return sum(args)
+
+        task_run = add_em_up.delay(42, 42)
+
+        assert await self.get_background_task_run_parameters(
+            add_em_up, task_run.state.state_details.task_parameters_id
+        ) == {"args": (42, 42)}
+
+    async def test_delay_with_variadic_kwargs(self):
+        @task
+        def add_em_up(**kwargs):
+            return sum(kwargs.values())
+
+        task_run = add_em_up.delay(x=42, y=42)
+
+        assert await self.get_background_task_run_parameters(
+            add_em_up, task_run.state.state_details.task_parameters_id
+        ) == {"kwargs": {"x": 42, "y": 42}}
+
+    async def test_delay_with_variadic_args_and_kwargs(self):
+        @task
+        def add_em_up(*args, **kwargs):
+            return sum(args) + sum(kwargs.values())
+
+        task_run = add_em_up.delay(42, y=42)
+
+        assert await self.get_background_task_run_parameters(
+            add_em_up, task_run.state.state_details.task_parameters_id
+        ) == {"args": (42,), "kwargs": {"y": 42}}