phillipdupuis / pydantic-to-typescript

CLI Tool for converting pydantic models into typescript definitions
MIT License
285 stars 48 forks source link

Error when TypeAlias is used #34

Open khalo-sa opened 1 year ago

khalo-sa commented 1 year ago

Hi and thank you for this package!

I would like to report a bug with version 1.0.10 and Python version 3.10.

Assume we have this Python file:

# test.py
from typing import TypeAlias
from pydantic import BaseModel

CustomType: TypeAlias = list[str]

class Test(BaseModel):
    x: int

Now if I run pydantic2ts --module test.py --output ./test.ts I get the following error:

Traceback (most recent call last):
  File "/Users/user/miniforge3/envs/py310/bin/pydantic2ts", line 8, in <module>
    sys.exit(main())
  File "/Users/user/miniforge3/envs/py310/lib/python3.10/site-packages/pydantic2ts/cli/script.py", line 274, in main
    return generate_typescript_defs(
  File "/Users/user/miniforge3/envs/py310/lib/python3.10/site-packages/pydantic2ts/cli/script.py", line 201, in generate_typescript_defs
    models = extract_pydantic_models(import_module(module))
  File "/Users/user/miniforge3/envs/py310/lib/python3.10/site-packages/pydantic2ts/cli/script.py", line 81, in extract_pydantic_models
    for _, model in inspect.getmembers(module, is_concrete_pydantic_model):
  File "/Users/user/miniforge3/envs/py310/lib/python3.10/inspect.py", line 482, in getmembers
    if not predicate or predicate(value):
  File "/Users/user/miniforge3/envs/py310/lib/python3.10/site-packages/pydantic2ts/cli/script.py", line 68, in is_concrete_pydantic_model
    elif GenericModel and issubclass(obj, GenericModel):
  File "/Users/user/miniforge3/envs/py310/lib/python3.10/abc.py", line 123, in __subclasscheck__
    return _abc_subclasscheck(cls, subclass)
TypeError: issubclass() arg 1 must be a class

Note that CustomType is not even used in the PydanticModel. Still, if I comment out the definition of CustomType, the error disappears.

H-Plus-Time commented 1 year ago

CustomType is (since version 3.9 at least) actually an instance of type GenericAlias. Despite it being the first argument (which is meant to work properly), calling issubclass with anything based on ABCMeta (which BaseModel and GenericModel are) results in a call to _abc_subclasscheck with the subclass last (which must not be a generic).

The pydantic maintainers encountered this early 2021 and fixed it in their pydantic.utils.lenient_issubclass function pydantic/pydantic#2399. Swapping out the two calls to issubclass in is_concrete_pydantic_model to that utility function solves it.

EricWebsmith commented 1 year ago

I modified the function to

def is_concrete_pydantic_model(obj) -> bool:
    """
    Return true if an object is a concrete subclass of pydantic's BaseModel.
    'concrete' meaning that it's not a GenericModel.
    """
    return isinstance(obj, ModelMetaclass)

and I can generate typescript.