Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion aiocli/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
"""Simple and lightweight async console runner."""

__version__ = '1.6.0'
__version__ = '1.6.1'
10 changes: 9 additions & 1 deletion aiocli/commander.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,20 @@
from asyncio.events import AbstractEventLoop
from typing import Any, Awaitable, Callable, List, Optional, Set, Union, cast

from aiocli.commander_app import Application, Command, Depends, State, command
from aiocli.commander_app import (
Application,
Command,
CommandArgument,
Depends,
State,
command,
)

__all__ = (
# commander_app
'State',
'Depends',
'CommandArgument',
'Command',
'command',
'Application',
Expand Down
107 changes: 93 additions & 14 deletions aiocli/commander_app.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
from argparse import ArgumentParser, RawTextHelpFormatter
from argparse import Action, ArgumentParser, RawTextHelpFormatter
from inspect import signature
from typing import (
Any,
Awaitable,
Callable,
Container,
Coroutine,
Dict,
List,
NamedTuple,
Optional,
Sequence,
Tuple,
Expand All @@ -18,13 +20,13 @@
# commander_app
'State',
'Depends',
'CommandArgument',
'Command',
'command',
'CommandHandler',
'Application',
)


from .helpers import resolve_function
from .logger import logger

Expand All @@ -35,6 +37,7 @@ class State(dict):
pass


# dataclass
class _Depends:
def __init__(self, dependency: Callable[..., Any], cache: bool) -> None:
self.dependency = dependency
Expand All @@ -45,41 +48,96 @@ def Depends(dependency: Callable[..., Any], cache: bool = True) -> Any:
return _Depends(dependency=dependency, cache=cache)


# https://docs.python.org/3/library/argparse.html#the-add-argument-method
class CommandArgument(NamedTuple):
name_or_flags: Union[str, List[str]]
action: Optional[Union[str, Action]] = None
nargs: Optional[Union[int, str]] = None
const: Any = None
default: Any = None
type: Union[Type[Any], Callable[[str], Any]] = str
choices: Optional[Container[Any]] = None
required: Optional[bool] = None
help: Optional[str] = None
metavar: Optional[str] = None
dest: Optional[str] = None


# dataclass
class Command:
name: str
handler: CommandHandler
positionals: List[Tuple[str, Dict[str, Any]]]
optionals: List[Tuple[str, Dict[str, Any]]]
positionals: List[
Union[
Tuple[str, Dict[str, Any]],
CommandArgument,
]
]
optionals: List[
Union[
Tuple[str, Dict[str, Any]],
CommandArgument,
]
]
deprecated: Optional[bool]
description: Optional[str]

def __init__(
self,
name: str,
handler: CommandHandler,
positionals: List[Tuple[str, Dict[str, Any]]],
optionals: List[Tuple[str, Dict[str, Any]]],
positionals: List[
Union[
Tuple[str, Dict[str, Any]],
CommandArgument,
]
],
optionals: List[
Union[
Tuple[str, Dict[str, Any]],
CommandArgument,
]
],
deprecated: Optional[bool] = None,
description: Optional[str] = None,
) -> None:
self.name = name
self.handler = handler # type: ignore
self.positionals = positionals
self.optionals = optionals
self.deprecated = deprecated
self.description = description


def command(
name: str,
handler: CommandHandler,
positionals: Optional[List[Tuple[str, Dict[str, Any]]]] = None,
optionals: Optional[List[Tuple[str, Dict[str, Any]]]] = None,
positionals: Optional[
List[
Union[
Tuple[str, Dict[str, Any]],
CommandArgument,
]
]
] = None,
optionals: Optional[
List[
Union[
Tuple[str, Dict[str, Any]],
CommandArgument,
]
]
] = None,
deprecated: Optional[bool] = None,
description: Optional[str] = None,
) -> Command:
return Command(
name=name,
handler=handler,
positionals=positionals or [],
optionals=optionals or [],
deprecated=deprecated,
description=description,
)


Expand All @@ -91,6 +149,12 @@ def command(

CommandHook = Callable[['Application'], Union[None, Awaitable[None]]]

ArgumentState = Union[
State,
Dict[str, Any],
Callable[[], Union[State, Dict[str, Any]]],
]


class Application:
_parser: ArgumentParser
Expand Down Expand Up @@ -122,7 +186,7 @@ def __init__(
on_shutdown: Optional[Sequence[CommandHook]] = None,
on_cleanup: Optional[Sequence[CommandHook]] = None,
deprecated: Optional[bool] = None,
state: Optional[Union[State, Dict[str, Any]]] = None,
state: Optional[ArgumentState] = None,
) -> None:
self._parser = ArgumentParser(
description=description,
Expand Down Expand Up @@ -160,7 +224,7 @@ async def self_handler() -> int:
self._on_cleanup = [] if on_cleanup is None else list(on_cleanup)
self._deprecated = bool(deprecated)
self._dependencies_cached = {}
self._state = State(state or {}) if not isinstance(state, State) else state
self._set_state(state or State())

async def __call__(self, args: List[str]) -> int:
exit_code: Optional[int] = self._exit_code
Expand Down Expand Up @@ -219,8 +283,8 @@ def decorator(handler: CommandHandler) -> CommandHandler:
Command(
name=name,
handler=handler,
positionals=positionals or [],
optionals=optionals or [],
positionals=positionals or [], # type: ignore
optionals=optionals or [], # type: ignore
deprecated=deprecated,
)
)
Expand Down Expand Up @@ -305,8 +369,13 @@ def _add_command(self, cmd: Command) -> None:
if cmd.deprecated is None:
cmd.deprecated = self._deprecated
self._commands[cmd.name] = cmd
parser = ArgumentParser(prog=cmd.name)
_ = [parser.add_argument(arg[0], **arg[1]) for arg in cmd.positionals + cmd.optionals]
parser = ArgumentParser(description=cmd.description, prog=cmd.name, prefix_chars='-')
args = cmd.positionals + cmd.optionals
for arg in args:
if isinstance(arg, CommandArgument):
arg = (arg.name_or_flags, arg._asdict()) # type: ignore
del arg[1]['name_or_flags'] # type: ignore
parser.add_argument(arg[0], **arg[1]) # type: ignore
self._parsers[cmd.name] = parser
self._update_parser_description(cmd.name)

Expand Down Expand Up @@ -432,6 +501,16 @@ async def _execute_command_exception_handler(
)
return await resolve_function(exception_handler, err, cmd, kwargs)

def _set_state(self, state: ArgumentState) -> None:
if isinstance(state, State):
self._state = state
elif isinstance(state, dict):
self._state = State(state)
elif callable(state):
self._state = state() # type: ignore
else:
self._state = State()

def _log(self, msg: str) -> None:
if self._debug:
# print(msg)
Expand Down
10 changes: 5 additions & 5 deletions aiocli/helpers.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import asyncio
import functools
import sys
from functools import partial
from sys import version_info
from typing import Any, Callable, Optional, Set


Expand All @@ -9,17 +9,17 @@ def all_tasks(loop: Optional[asyncio.AbstractEventLoop] = None) -> Set["asyncio.
return {t for t in tasks if not t.done()}


if sys.version_info >= (3, 7):
if version_info >= (3, 7):
all_tasks = getattr(asyncio, 'all_tasks')


def iscoroutinefunction(func: Callable[..., Any]) -> bool:
while isinstance(func, functools.partial):
while isinstance(func, partial):
func = func.func
return asyncio.iscoroutinefunction(func)


if sys.version_info >= (3, 8):
if version_info >= (3, 8):
iscoroutinefunction = asyncio.iscoroutinefunction


Expand Down
18 changes: 16 additions & 2 deletions docs/404.html
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@

<li class="md-select__item">
<a href="/es/" hreflang="" class="md-select__link">
es - español
es - Spanish
</a>
</li>

Expand Down Expand Up @@ -421,6 +421,20 @@








<li class="md-nav__item">
<a href="/python-aiocli/advanced/state/" class="md-nav__link">
State
</a>
</li>




</ul>
</nav>
</li>
Expand Down Expand Up @@ -468,7 +482,7 @@ <h1>404 - Not found</h1>
<div class="md-copyright">

<div class="md-copyright__highlight">
Copyright &copy; 2020 - 2021
Copyright &copy; 2020 - 2022
</div>


Expand Down
18 changes: 16 additions & 2 deletions docs/advanced/exception_handler/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@

<li class="md-select__item">
<a href="/es/" hreflang="" class="md-select__link">
es - español
es - Spanish
</a>
</li>

Expand Down Expand Up @@ -433,6 +433,20 @@








<li class="md-nav__item">
<a href="../state/" class="md-nav__link">
State
</a>
</li>




</ul>
</nav>
</li>
Expand Down Expand Up @@ -526,7 +540,7 @@ <h1>Exception handler</h1>
<div class="md-copyright">

<div class="md-copyright__highlight">
Copyright &copy; 2020 - 2021
Copyright &copy; 2020 - 2022
</div>


Expand Down
Loading