Skip to content

Commit

Permalink
Merge pull request #70 from mpkocher/use-pydantic-repr
Browse files Browse the repository at this point in the history
Use FieldInfo repr. This non-trivially changes how the output is displayed.
  • Loading branch information
mpkocher authored Sep 20, 2024
2 parents 5df8a90 + 13b3a4b commit b343dc3
Show file tree
Hide file tree
Showing 3 changed files with 11 additions and 58 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/pre-commit.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,6 @@ jobs:
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.12'
- uses: pre-commit/[email protected]
65 changes: 8 additions & 57 deletions pydantic_cli/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@
import traceback
import logging
import typing
from copy import deepcopy
from typing import overload
from typing import Any, Mapping, Callable


import pydantic
from pydantic import BaseModel
from pydantic.fields import FieldInfo
from pydantic_core import PydanticUndefined

from ._version import __version__

Expand Down Expand Up @@ -61,7 +61,6 @@ def run(self) -> None: ...
SubCmdKlassT = Mapping[str, CmdKlassT]
CmdOrSubCmdKlassT = CmdKlassT | SubCmdKlassT
NOT_PROVIDED = ...
NONE_TYPE = type(None)


def _is_sequence(annotation: Any) -> bool:
Expand All @@ -76,54 +75,6 @@ def _is_sequence(annotation: Any) -> bool:
return getattr(annotation, "__origin__", "NOTFOUND") in ALL_SEQ


def __try_to_pretty_type(field_type: Any) -> str:
"""
This is a marginal improvement to get the types to be
displayed in slightly better format.
FIXME. This needs to be display Union types better.
"""

args = typing.get_args(field_type)
if args:
if len(args) == 1:
name = field_type.__name__
else:
name = "|".join(map(lambda x: x.__name__, args))
else:
try:
name = field_type.__name__
except AttributeError:
name = repr(field_type)

return f"type:{name}"


def __to_type_description(
default_value: Any = NOT_PROVIDED,
field_type: Any = NOT_PROVIDED,
allow_none: bool = False,
is_required: bool = False,
) -> str:
t = "" if field_type is NOT_PROVIDED else __try_to_pretty_type(field_type)

# avoid using in with a Set to avoid assumptions that default_value is hashable
allowed_defaults: list[Any] = (
[NOT_PROVIDED, PydanticUndefined]
if allow_none
else [NOT_PROVIDED, PydanticUndefined, None, type(None)]
)
v = (
""
if any((default_value is x) for x in allowed_defaults)
else f"default:{default_value}"
)
required = " *required*" if is_required else ""
sep = " " if v else ""
xs = sep.join([t, v]) + required
return xs


@pydantic.validate_call
def __process_tuple(tuple_one_or_two: Tuple1or2Type, long_arg: str) -> Tuple1or2Type:
"""
Expand Down Expand Up @@ -180,7 +131,6 @@ def _add_pydantic_field_to_parser(
"""

default_long_arg = "".join([long_prefix, field_id])
description = field_info.description
# there's mypy type issues here
cli_custom_: Tuple1or2Type = (
(default_long_arg,)
Expand All @@ -190,7 +140,6 @@ def _add_pydantic_field_to_parser(
cli_short_long: Tuple1or2Type = __process_tuple(cli_custom_, default_long_arg)

is_required = field_info.is_required()
is_nullable = type(None) in typing.get_args(field_info.annotation)
default_value = field_info.default
is_sequence = _is_sequence(field_info.annotation)

Expand All @@ -200,19 +149,21 @@ def _add_pydantic_field_to_parser(
default_value = override_value
is_required = False

type_desc = __to_type_description(
default_value, field_info.annotation, is_nullable, is_required
)
# Delete cli and json_schema_extras metadata isn't in FieldInfo and won't be displayed
# Not sure if this is the correct, or expected behavior.
cfield_info = deepcopy(field_info)
cfield_info.json_schema_extra = None
# write this to keep backward compat with 3.10
help_ = "".join(["Field(", field_info.__repr_str__(", "), ")"])

# log.debug(f"Creating Argument Field={field_id} opts:{cli_short_long}, allow_none={field.allow_none} default={default_value} type={field.type_} required={is_required} dest={field_id} desc={description}")

# MK. I don't think there's any point trying to fight with argparse to get
# the types correct here. It's just a mess from a type standpoint.
shape_kw = {"nargs": "+"} if is_sequence else {}
desc = description or ""
parser.add_argument(
*cli_short_long,
help=f"{desc} ({type_desc})",
help=help_,
default=default_value,
dest=field_id,
required=is_required,
Expand Down
2 changes: 1 addition & 1 deletion pydantic_cli/_version.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = "9.0.0"
__version__ = "9.1.0"

0 comments on commit b343dc3

Please sign in to comment.