diff options
-rw-r--r-- | pydanclick/core.py | 41 | ||||
-rw-r--r-- | pydanclick/decorators.py | 60 | ||||
-rw-r--r-- | pydanclick/examples/__main__.py | 18 |
3 files changed, 90 insertions, 29 deletions
diff --git a/pydanclick/core.py b/pydanclick/core.py index 25ac9bb..1da29a1 100644 --- a/pydanclick/core.py +++ b/pydanclick/core.py @@ -1,10 +1,9 @@ from __future__ import annotations import click -import functools import inspect -from typing import Any +from typing import Any, Union from pydantic import BaseModel from typing import Callable, Type @@ -35,29 +34,6 @@ class Command(click.Command): ctx.invoke(self.callback, **arguments) -def command(name: str = None, cls: Type = Command, **attrs: Any) -> Callable: - """ - Pydantic arguments to click command. - - >>> @command() - ... def entrypoint(): - ... pass - >>> entrypoint - <Command entrypoint> - """ - - def wrapper(entrypoint: Callable) -> Callable: - - @functools.wraps(entrypoint) - def _(*args, **kwargs): - entrypoint(*args, **kwargs) - - options = generate_cli_options(**attrs)(_) - return click.command(options) - - return wrapper - - def is_valid_schema_annotation(annotation: Type) -> bool: """ Filter an annotation with typing/pydantic implementation. @@ -147,6 +123,21 @@ def get_option_arguments( return arguments +def get_parameters_from_arguments( + entrypoint: Callable, + *args: Any, + **kwargs: Any +) -> dict[str, Any]: + """ + Re-construct pydantic object from click kwargs and entrypoint annotation. + """ + final_arguments = {} + for name, parameter in get_processable_arguments(entrypoint): + final_arguments[name] = parameter.annotation(**kwargs) + + return final_arguments + + def generate_cli_option( schema: CliSchema, title: str, diff --git a/pydanclick/decorators.py b/pydanclick/decorators.py new file mode 100644 index 0000000..eddb50e --- /dev/null +++ b/pydanclick/decorators.py @@ -0,0 +1,60 @@ +import functools +import click + +from typing import Any, Callable +from pydanclick.core import ( + Command, + generate_cli_options, + get_processable_arguments, + get_parameters_from_arguments, +) + +def command( + name: str = None, + cls: type = Command, + **attrs: Any +) -> Callable: + """ + Pydantic arguments to click command. + + >>> @command() + ... def entrypoint(): + ... pass + >>> entrypoint + <Command entrypoint> + """ + + def wrapper(entrypoint: Callable) -> Callable: + + @functools.wraps(entrypoint) + def _(*args, **kwargs): + entrypoint( + **get_parameters_from_arguments(entrypoint, *args, **kwargs) + ) + + options = generate_cli_options(**attrs)(_) + return click.command(options) + + return wrapper + + +def group( + name: str = None, + cls: type = Command, + **attrs: Any +) -> Callable[[Callable[..., Any]], click.Group]: + """ + Decorator: pydantic arguments to click options for group. + """ + def wrapper(entrypoint: Callable) -> click.Group: + + @functools.wraps(entrypoint) + def _(*args, **kwargs): + entrypoint( + **get_parameters_from_arguments(entrypoint, *args, **kwargs) + ) + + options = generate_cli_options(**attrs)(_) + return click.group(options) + + return wrapper diff --git a/pydanclick/examples/__main__.py b/pydanclick/examples/__main__.py index fdaa34f..012f110 100644 --- a/pydanclick/examples/__main__.py +++ b/pydanclick/examples/__main__.py @@ -1,9 +1,7 @@ -# from click import command from enum import Enum from pydantic import BaseModel, Field -from pydanclick.core import Command, generate_cli_options -from pydanclick.core import command +from pydanclick.decorators import command, group class MainArguments(BaseModel): @@ -26,10 +24,22 @@ class MainArguments(BaseModel): network_type: NetworkEnum -@command(key_prefix="", a="c") +class ShowArguments(BaseModel): + verbose: bool = Field(description="Verbose output") + + +@group() def main(parameters: MainArguments) -> None: print(vars(parameters)) +@command() +def show(parameters: ShowArguments) -> None: + print(vars(parameters)) + + +main.add_command(show) + + if __name__ == "__main__": main() |