From 20e6e83183bb4dd1fec54cdc50e462f15c8133cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Romain=20Gon=C3=A7alves?= Date: Tue, 4 Oct 2022 23:17:21 +0200 Subject: wip: externalize generation of option arguments --- pydanclick/core.py | 70 ++++++++++++++++++++++++----------------- pydanclick/examples/__main__.py | 13 ++++++-- pydanclick/schemas.py | 22 +++++++++---- tests/conftest.py | 5 --- tests/test_core.py | 4 +++ 5 files changed, 72 insertions(+), 42 deletions(-) diff --git a/pydanclick/core.py b/pydanclick/core.py index 9ccdda9..25ac9bb 100644 --- a/pydanclick/core.py +++ b/pydanclick/core.py @@ -8,7 +8,7 @@ from typing import Any from pydantic import BaseModel from typing import Callable, Type -from pydanclick.schemas import CliSchema +from pydanclick.schemas import CliSchema, OptionArgumentsSchema class Command(click.Command): @@ -38,6 +38,12 @@ class Command(click.Command): def command(name: str = None, cls: Type = Command, **attrs: Any) -> Callable: """ Pydantic arguments to click command. + + >>> @command() + ... def entrypoint(): + ... pass + >>> entrypoint + """ def wrapper(entrypoint: Callable) -> Callable: @@ -102,52 +108,60 @@ def get_processable_arguments( )) -def generate_cli_option( +def get_option_arguments( schema: CliSchema, - title: str, parameter: CliSchema.PropertySchema, - *, - key_prefix: None | str = "" -) -> Callable: + *args, + **kwargs +) -> OptionArgumentsSchema: """ - Generate the option information of a click.Command. + Generate the option information only of a click.Command. """ - is_required = True if title in schema.required else False - is_flag = False - option_type: None | Type | object = None - option_title = title.lower().replace("_", "-") + + arguments = OptionArgumentsSchema(help=parameter.description) option_min = parameter.minLength or parameter.exclusiveMinimum option_max = parameter.maxLength or parameter.exlusiveMaximum - # option_extra = {} match parameter.type: case "integer": - option_type = int + arguments.type = int if option_min is not None or option_max is not None: - option_type = click.IntRange(option_min, option_max) + arguments.type = click.IntRange(option_min, option_max) case "number": - option_type = float + arguments.type = float case "string": - option_type = str + arguments.type = str + if parameter.enum: + arguments.type = click.Choice(parameter.enum) case "boolean": - option_type = bool - is_flag = True + arguments.type = bool + arguments.is_flag = True case None: if parameter.ref: - typ = schema.get_definition_from_ref(parameter.ref) - print(typ) - option_type = click.Choice(typ.enum) + definition = schema.get_definition_from_ref(parameter.ref) + arguments = get_option_arguments( + schema, definition, *args, **kwargs + ) + + return arguments + + +def generate_cli_option( + schema: CliSchema, + title: str, + parameter: CliSchema.PropertySchema, + *, + key_prefix: None | str = "" +) -> Callable: + """ + Generate the option object of a click.Command. + """ + option_title = title.lower().replace("_", "-") return click.option( f"--{key_prefix}{option_title}", - help=parameter.description, - required=is_required, - is_flag=is_flag, - type=option_type, - show_default=True, - show_envvar=True, - # **option_extra, + **dict(get_option_arguments(schema, parameter)) ) diff --git a/pydanclick/examples/__main__.py b/pydanclick/examples/__main__.py index 8a6a9cd..fdaa34f 100644 --- a/pydanclick/examples/__main__.py +++ b/pydanclick/examples/__main__.py @@ -13,9 +13,16 @@ class MainArguments(BaseModel): static = "static" disconnected = "disconnected" - filename: str = Field(min_length=10) - minimum_version: int = Field(gt=0) - force_download: bool = Field(default=False) + filename: str = Field( + min_length=10, description="Filename to be used for configuration" + ) + minimum_version: int = Field( + gt=0, description="Minimum supported API version." + ) + force_download: bool = Field( + default=False, + description="Force the download on a different API version" + ) network_type: NetworkEnum diff --git a/pydanclick/schemas.py b/pydanclick/schemas.py index 5601b2b..7fb79ce 100644 --- a/pydanclick/schemas.py +++ b/pydanclick/schemas.py @@ -4,10 +4,6 @@ from pydantic import BaseModel, Field class CliSchema(BaseModel): - class DefinitionSchema(BaseModel): - title: str - description: str - enum: list[str] class PropertySchema(BaseModel): title: None | str @@ -17,6 +13,7 @@ class CliSchema(BaseModel): exlusiveMaximum: None | int minLength: None | int maxLength: None | int + enum: None | list[str] ref: None | str = Field(alias="$ref") class Config: @@ -26,10 +23,23 @@ class CliSchema(BaseModel): properties: dict[str, CliSchema.PropertySchema] required: list[str] description: None | str - definitions: None | dict[str, CliSchema.DefinitionSchema] + definitions: None | dict[str, CliSchema.PropertySchema] - def get_definition_from_ref(self, ref: str) -> CliSchema.DefinitionSchema: + def get_definition_from_ref(self, ref: str) -> CliSchema.PropertySchema: if not self.definitions: raise RuntimeError return self.definitions[ref.split("/")[-1]] + + +class OptionArgumentsSchema(BaseModel): + """ + Externalised and typed click.Option arguments. + """ + + type: None | type | object + help: None | str + required: None | bool + show_default: None | bool + show_envvar: None | bool + is_flag: None | bool diff --git a/tests/conftest.py b/tests/conftest.py index 9282809..5871ed8 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,6 +1 @@ import pytest - - -@pytest.fixture(autouse=True) -def add_np(doctest_namespace): - doctest_namespace["np"] = numpy diff --git a/tests/test_core.py b/tests/test_core.py index b755a52..10e3b6c 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -1,3 +1,7 @@ import pytest from pydanclick import core + + +def test_generate_cli_option_ok(): + assert True -- cgit v1.2.3