From 29bd0a4cdc6a2c1f286c79648042b20ee05ecd65 Mon Sep 17 00:00:00 2001 From: Ryan Peach Date: Fri, 24 Sep 2021 14:16:58 -0400 Subject: [PATCH 1/6] Added async_command function. --- typer/main.py | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/typer/main.py b/typer/main.py index d2a45849f8..0e709c8cae 100644 --- a/typer/main.py +++ b/typer/main.py @@ -1,7 +1,7 @@ import inspect from datetime import datetime from enum import Enum -from functools import update_wrapper +from functools import update_wrapper, wraps from pathlib import Path from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Type, Union from uuid import UUID @@ -165,7 +165,33 @@ def decorator(f: CommandFunctionType) -> CommandFunctionType: return f return decorator + + + def async_command(self, *args: Any, **kwargs: Any) -> Callable[[CommandFunctionType], CommandFunctionType]: + """Same arguments as command but works with async functions.""" + + def decorator(async_func: CommandFunctionType) -> CommandFunctionType: + # Now we make a function that turns the async + # function into a synchronous function. + # By wrapping async_func we preserve the + # meta characteristics typer needs to create + # a good interface, such as the description and + # argument type hints + @wraps(async_func) + def sync_func(*_args, **_kwargs): + return run(async_func(*_args, **_kwargs)) + + # Now use app.command as normal to register the + # synchronous function + self.command(*args, **kwargs)(sync_func) + + # We return the async function unmodifed, + # so its library functionality is preserved + return async_func + return decorator + + def add_typer( self, typer_instance: "Typer", From c578a49ef1515d0cbce1f277437d68b3b5888941 Mon Sep 17 00:00:00 2001 From: Ryan Peach Date: Fri, 24 Sep 2021 14:20:27 -0400 Subject: [PATCH 2/6] Forgot asyncio --- typer/main.py | 1 + 1 file changed, 1 insertion(+) diff --git a/typer/main.py b/typer/main.py index 0e709c8cae..b562c39111 100644 --- a/typer/main.py +++ b/typer/main.py @@ -5,6 +5,7 @@ from pathlib import Path from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Type, Union from uuid import UUID +from asyncio import run import click From 7b295126e763db3c0dadaf24c257c357ae5073b5 Mon Sep 17 00:00:00 2001 From: Ryan Date: Fri, 24 Sep 2021 15:06:29 -0400 Subject: [PATCH 3/6] Added anyio --- pyproject.toml | 3 +++ typer/main.py | 14 ++++++++++---- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index bdf673841b..5d530cde5a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -56,7 +56,10 @@ dev = [ "autoflake >=1.3.1,<2.0.0", "flake8 >=3.8.3,<4.0.0", ] +# TODO: Add a version number for anyio +async = ["anyio"] all = [ + "anyio", "colorama >=0.4.3,<0.5.0", "shellingham >=1.3.0,<2.0.0" ] diff --git a/typer/main.py b/typer/main.py index b562c39111..ed3e2556dd 100644 --- a/typer/main.py +++ b/typer/main.py @@ -5,7 +5,6 @@ from pathlib import Path from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Type, Union from uuid import UUID -from asyncio import run import click @@ -168,9 +167,14 @@ def decorator(f: CommandFunctionType) -> CommandFunctionType: return decorator - def async_command(self, *args: Any, **kwargs: Any) -> Callable[[CommandFunctionType], CommandFunctionType]: + def async_command(self, *args: Any, backend=None, **kwargs: Any) -> Callable[[CommandFunctionType], CommandFunctionType]: """Same arguments as command but works with async functions.""" - + # Dynamically import either anyio or asyncio + if backend is not None: + from anyio import run + else: + from asyncio import run + def decorator(async_func: CommandFunctionType) -> CommandFunctionType: # Now we make a function that turns the async # function into a synchronous function. @@ -180,9 +184,11 @@ def decorator(async_func: CommandFunctionType) -> CommandFunctionType: # argument type hints @wraps(async_func) def sync_func(*_args, **_kwargs): + if backend is not None: + return run(async_func(*_args, **_kwargs), backend=backend) return run(async_func(*_args, **_kwargs)) - # Now use app.command as normal to register the + # Now use self.command as normal to register the # synchronous function self.command(*args, **kwargs)(sync_func) From af392ee826214e3643d50269b946b9244ec3e467 Mon Sep 17 00:00:00 2001 From: Ryan Date: Mon, 4 Oct 2021 09:46:01 -0400 Subject: [PATCH 4/6] Made mypy compatible and removed need for external libraries. --- pyproject.toml | 3 --- typer/main.py | 55 +++++++++++++++++++++++++++++++++++-------------- typer/models.py | 3 ++- 3 files changed, 41 insertions(+), 20 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 5d530cde5a..bdf673841b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -56,10 +56,7 @@ dev = [ "autoflake >=1.3.1,<2.0.0", "flake8 >=3.8.3,<4.0.0", ] -# TODO: Add a version number for anyio -async = ["anyio"] all = [ - "anyio", "colorama >=0.4.3,<0.5.0", "shellingham >=1.3.0,<2.0.0" ] diff --git a/typer/main.py b/typer/main.py index ed3e2556dd..21a38f3c21 100644 --- a/typer/main.py +++ b/typer/main.py @@ -5,6 +5,7 @@ from pathlib import Path from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Type, Union from uuid import UUID +import asyncio import click @@ -14,6 +15,7 @@ AnyType, ArgumentInfo, CommandFunctionType, + AsyncCommandFunctionType, CommandInfo, Default, DefaultPlaceholder, @@ -165,40 +167,61 @@ def decorator(f: CommandFunctionType) -> CommandFunctionType: return f return decorator - - def async_command(self, *args: Any, backend=None, **kwargs: Any) -> Callable[[CommandFunctionType], CommandFunctionType]: + + def async_command( + self, + name: Optional[str] = None, + *, + run_func: Callable[[AsyncCommandFunctionType, Tuple[Any, ...], Dict[str, Any]], Any] = lambda f, args, kwargs: asyncio.run(f(*args, **kwargs)), + cls: Optional[Type[click.Command]] = None, + context_settings: Optional[Dict[Any, Any]] = None, + help: Optional[str] = None, + epilog: Optional[str] = None, + short_help: Optional[str] = None, + options_metavar: str = "[OPTIONS]", + add_help_option: bool = True, + no_args_is_help: bool = False, + hidden: bool = False, + deprecated: bool = False, + ) -> Callable[[AsyncCommandFunctionType], AsyncCommandFunctionType]: """Same arguments as command but works with async functions.""" # Dynamically import either anyio or asyncio - if backend is not None: - from anyio import run - else: - from asyncio import run - - def decorator(async_func: CommandFunctionType) -> CommandFunctionType: + + def decorator(async_func: AsyncCommandFunctionType) -> AsyncCommandFunctionType: # Now we make a function that turns the async # function into a synchronous function. # By wrapping async_func we preserve the # meta characteristics typer needs to create - # a good interface, such as the description and + # a good interface, such as the description and # argument type hints @wraps(async_func) - def sync_func(*_args, **_kwargs): - if backend is not None: - return run(async_func(*_args, **_kwargs), backend=backend) - return run(async_func(*_args, **_kwargs)) + def sync_func(*args: Any, **kwargs: Any) -> Any: + return run_func(async_func, args, kwargs) # Now use self.command as normal to register the # synchronous function - self.command(*args, **kwargs)(sync_func) + self.command( + name=name, + cls=cls, + context_settings=context_settings, + help=help, + epilog=epilog, + short_help=short_help, + options_metavar=options_metavar, + add_help_option=add_help_option, + no_args_is_help=no_args_is_help, + hidden=hidden, + deprecated=deprecated + )(sync_func) - # We return the async function unmodifed, + # We return the async function unmodifed, # so its library functionality is preserved return async_func return decorator - + def add_typer( self, typer_instance: "Typer", diff --git a/typer/models.py b/typer/models.py index e1de3a46f8..3031828a8a 100644 --- a/typer/models.py +++ b/typer/models.py @@ -8,6 +8,7 @@ List, Optional, Sequence, + Coroutine, Type, TypeVar, Union, @@ -70,7 +71,7 @@ def __bool__(self) -> bool: DefaultType = TypeVar("DefaultType") CommandFunctionType = TypeVar("CommandFunctionType", bound=Callable[..., Any]) - +AsyncCommandFunctionType = TypeVar("AsyncCommandFunctionType", bound=Callable[..., Coroutine[None, None, Any]]) def Default(value: DefaultType) -> DefaultType: """ From 45a0fc26551ffabc6619466f5637f42dd998cb3d Mon Sep 17 00:00:00 2001 From: Ryan Date: Mon, 4 Oct 2021 10:00:24 -0400 Subject: [PATCH 5/6] Better using a protocol --- typer/main.py | 5 +++-- typer/models.py | 13 +++++++++++++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/typer/main.py b/typer/main.py index 21a38f3c21..7978f0082a 100644 --- a/typer/main.py +++ b/typer/main.py @@ -28,6 +28,7 @@ ParameterInfo, ParamMeta, Required, + RunFunction, TyperInfo, ) from .utils import get_params_from_function @@ -173,7 +174,7 @@ def async_command( self, name: Optional[str] = None, *, - run_func: Callable[[AsyncCommandFunctionType, Tuple[Any, ...], Dict[str, Any]], Any] = lambda f, args, kwargs: asyncio.run(f(*args, **kwargs)), + run_func: RunFunction = lambda f, *args, **kwargs: asyncio.run(f(*args, **kwargs)), cls: Optional[Type[click.Command]] = None, context_settings: Optional[Dict[Any, Any]] = None, help: Optional[str] = None, @@ -197,7 +198,7 @@ def decorator(async_func: AsyncCommandFunctionType) -> AsyncCommandFunctionType: # argument type hints @wraps(async_func) def sync_func(*args: Any, **kwargs: Any) -> Any: - return run_func(async_func, args, kwargs) + return run_func(async_func, *args, **kwargs) # Now use self.command as normal to register the # synchronous function diff --git a/typer/models.py b/typer/models.py index 3031828a8a..6511609974 100644 --- a/typer/models.py +++ b/typer/models.py @@ -12,6 +12,7 @@ Type, TypeVar, Union, + Protocol ) import click @@ -73,6 +74,18 @@ def __bool__(self) -> bool: CommandFunctionType = TypeVar("CommandFunctionType", bound=Callable[..., Any]) AsyncCommandFunctionType = TypeVar("AsyncCommandFunctionType", bound=Callable[..., Coroutine[None, None, Any]]) +class RunFunction(Protocol): + """ + Defines a run function as the following. + + Examples: + asyncio: `run_func = lambda f, *args, **kwargs: asyncio.run(f(*args, **kwargs))` + anyio: `run_func = lambda f, *args, **kwargs: anyio.run(f, *args, **kwargs)` + trio: `run_func = lambda f, *args, **kwargs: trio.run(f, *args, **kwargs)` + """ + def __call__(self, f: AsyncCommandFunctionType, *args: Any, **kwargs: Any) -> Any: ... + + def Default(value: DefaultType) -> DefaultType: """ You shouldn't use this function directly. From 2a255b2e6fb3d8569405e495776ea0e619f4cfbd Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 16 Dec 2022 20:47:18 +0000 Subject: [PATCH 6/6] =?UTF-8?q?=F0=9F=8E=A8=20[pre-commit.ci]=20Auto=20for?= =?UTF-8?q?mat=20from=20pre-commit.com=20hooks?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- typer/main.py | 12 ++++++------ typer/models.py | 13 +++++++++---- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/typer/main.py b/typer/main.py index ee8c9bcd6b..1e8609c1d5 100644 --- a/typer/main.py +++ b/typer/main.py @@ -1,3 +1,4 @@ +import asyncio import inspect import os import sys @@ -10,7 +11,6 @@ from types import TracebackType from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Type, Union from uuid import UUID -import asyncio import click @@ -19,8 +19,8 @@ from .models import ( AnyType, ArgumentInfo, - CommandFunctionType, AsyncCommandFunctionType, + CommandFunctionType, CommandInfo, Default, DefaultPlaceholder, @@ -260,12 +260,13 @@ def decorator(f: CommandFunctionType) -> CommandFunctionType: return decorator - def async_command( self, name: Optional[str] = None, *, - run_func: RunFunction = lambda f, *args, **kwargs: asyncio.run(f(*args, **kwargs)), + run_func: RunFunction = lambda f, *args, **kwargs: asyncio.run( + f(*args, **kwargs) + ), cls: Optional[Type[click.Command]] = None, context_settings: Optional[Dict[Any, Any]] = None, help: Optional[str] = None, @@ -304,7 +305,7 @@ def sync_func(*args: Any, **kwargs: Any) -> Any: add_help_option=add_help_option, no_args_is_help=no_args_is_help, hidden=hidden, - deprecated=deprecated + deprecated=deprecated, )(sync_func) # We return the async function unmodifed, @@ -313,7 +314,6 @@ def sync_func(*args: Any, **kwargs: Any) -> Any: return decorator - def add_typer( self, typer_instance: "Typer", diff --git a/typer/models.py b/typer/models.py index 80903923e1..f71d630961 100644 --- a/typer/models.py +++ b/typer/models.py @@ -4,15 +4,15 @@ TYPE_CHECKING, Any, Callable, + Coroutine, Dict, List, Optional, + Protocol, Sequence, - Coroutine, Type, TypeVar, Union, - Protocol ) import click @@ -76,7 +76,10 @@ def __bool__(self) -> bool: DefaultType = TypeVar("DefaultType") CommandFunctionType = TypeVar("CommandFunctionType", bound=Callable[..., Any]) -AsyncCommandFunctionType = TypeVar("AsyncCommandFunctionType", bound=Callable[..., Coroutine[None, None, Any]]) +AsyncCommandFunctionType = TypeVar( + "AsyncCommandFunctionType", bound=Callable[..., Coroutine[None, None, Any]] +) + class RunFunction(Protocol): """ @@ -87,7 +90,9 @@ class RunFunction(Protocol): anyio: `run_func = lambda f, *args, **kwargs: anyio.run(f, *args, **kwargs)` trio: `run_func = lambda f, *args, **kwargs: trio.run(f, *args, **kwargs)` """ - def __call__(self, f: AsyncCommandFunctionType, *args: Any, **kwargs: Any) -> Any: ... + + def __call__(self, f: AsyncCommandFunctionType, *args: Any, **kwargs: Any) -> Any: + ... def Default(value: DefaultType) -> DefaultType: