Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

mypy 1.11.0 Could not find builtin symbol 'TypeAliasType' #17554

Closed
claudiu-muresan-pfa opened this issue Jul 22, 2024 · 3 comments · Fixed by #17558
Closed

mypy 1.11.0 Could not find builtin symbol 'TypeAliasType' #17554

claudiu-muresan-pfa opened this issue Jul 22, 2024 · 3 comments · Fixed by #17558
Assignees
Labels
bug mypy got something wrong topic-type-alias TypeAlias and other type alias issues

Comments

@claudiu-muresan-pfa
Copy link

/Users/claudiumuresan/opt/anaconda3/envs/waylay_query/lib/python3.11/site-packages/waylay/sdk/api/_models.py:196: error: INTERNAL ERROR -- Please try using mypy master on GitHub:
https://mypy.readthedocs.io/en/stable/common_issues.html#using-a-development-mypy-build
Please report a bug at https:/python/mypy/issues
version: 1.11.0
Traceback (most recent call last):
  File "mypy/checkexpr.py", line 5830, in accept
  File "mypy/nodes.py", line 2714, in accept
  File "mypy/checkexpr.py", line 4730, in visit_type_alias_expr
  File "mypy/checkexpr.py", line 4749, in alias_type_in_runtime_context
  File "mypy/checkexpr.py", line 5863, in named_type
  File "mypy/checker.py", line 6924, in named_type
  File "mypy/checker.py", line 7041, in lookup_qualified
KeyError: "Could not find builtin symbol 'TypeAliasType' (If you are running a test case, use a fixture that defines this symbol)"
/Users/claudiumuresan/opt/anaconda3/envs/waylay_query/lib/python3.11/site-packages/waylay/sdk/api/_models.py:196: : note: use --pdb to drop into pdb
make: *** [typecheck] Error 1
@claudiu-muresan-pfa claudiu-muresan-pfa added the bug mypy got something wrong label Jul 22, 2024
@claudiu-muresan-pfa claudiu-muresan-pfa changed the title mypi 1.1.0 Could not find builtin symbol 'TypeAliasType' mypi 1.11.0 Could not find builtin symbol 'TypeAliasType' Jul 22, 2024
@claudiu-muresan-pfa claudiu-muresan-pfa changed the title mypi 1.11.0 Could not find builtin symbol 'TypeAliasType' mypy 1.11.0 Could not find builtin symbol 'TypeAliasType' Jul 22, 2024
@claudiu-muresan-pfa
Copy link
Author

claudiu-muresan-pfa commented Jul 22, 2024

Here is _models.py:

from __future__ import annotations

import contextlib
from abc import ABC
from copy import deepcopy
from datetime import date, datetime
from decimal import Decimal
from inspect import isclass
from typing import (
    TYPE_CHECKING,
    Annotated,
    Any,
    Callable,
    Dict,
    List,
    Union,
    get_type_hints,
)

from typing_extensions import (
    Self,  # >=3.12
    TypeAliasType,  # >=3.12
)

if TYPE_CHECKING:
    from typing_extensions import TypeAlias  # >= Python 3.10

from pydantic import (
    BaseModel as PydanticBaseModel,
)
from pydantic import (
    ConfigDict,
    SerializationInfo,
    StrictStr,
    TypeAdapter,
    ValidationError,
    ValidationInfo,
    ValidatorFunctionWrapHandler,
    field_validator,
    model_serializer,
    model_validator,
)
from pydantic.functional_validators import ModelWrapValidatorHandler


class BaseModel(PydanticBaseModel, ABC):
    """Waylay base model class that adds additional methods to Pydantic's `BaseModel`.

    Includes a custom validator and serializer.
    """

    @model_serializer(mode="wrap")
    def _model_serializer(
        self, handler: Callable, info: SerializationInfo
    ) -> Dict[StrictStr, Any]:
        """Get the default serializer of the model.

        * Excludes `None` fields that were not set at model initialization.
        """
        model_dict = handler(self, info)
        return {
            k: v
            for k, v in model_dict.items()
            if v is not None or k in self.model_fields_set
        }

    @model_validator(mode="wrap")  # type: ignore[arg-type]
    def _model_validator(
        cls, value: Any, handler: ModelWrapValidatorHandler, info: ValidationInfo
    ):
        """Get the default validator of the model.

        When validation is called with a `skip_validation=True` context
        (e.g. `cls.model_validate(data, context={"skip_validation": True})`),
        the model is constructed without validation.
        Any fields with a `Model` type will be constructed
        from their dict representation recursively.
        """
        context = info.context or {}
        try:
            return handler(value)
        except ValidationError:
            context = info.context or {}
            if context.get("skip_validation", False):
                model = cls.model_construct(**value)
                if not model.model_fields_set:
                    raise

                # set missing fields to None
                model_fields_set = deepcopy(model.model_fields_set)
                model_fields_missing = [
                    field_name
                    for [field_name, field_info] in model.model_fields.items()
                    if field_name not in model_fields_set and field_info.is_required()
                ]
                for field_name in model_fields_missing:
                    with contextlib.suppress(ValueError, TypeError):
                        setattr(model, field_name, None)

                model.__pydantic_fields_set__ = model_fields_set
                # recursively validate set fields
                for field_name in model_fields_set:
                    field_value = getattr(model, field_name)
                    strict = (info.config or {}).get("strict")
                    with contextlib.suppress(BaseException):
                        cls.__pydantic_validator__.validate_assignment(
                            model,
                            field_name,
                            field_value,
                            strict=strict,
                            context=context,
                        )
                return model
            else:
                raise

    @field_validator("*", mode="wrap")
    def _field_validator(
        cls, value: Any, handler: ValidatorFunctionWrapHandler, info: ValidationInfo
    ):
        """Get the default field validator of the model.

        When validation is called with a `skip_validation=True` context,
        the field is assigned without validation.
        If the field is a `Model` type, the model will be constructed
        from its dict representation recursively.
        """
        context = info.context or {}
        try:
            return handler(value)
        except ValidationError:
            context = info.context or {}
            if context.get("skip_validation", False):
                if info.field_name:
                    field_type = get_type_hints(cls).get(info.field_name, Any)
                    try:
                        config = (
                            ConfigDict(arbitrary_types_allowed=True, strict=False)
                            if not isclass(field_type)
                            or not issubclass(field_type, PydanticBaseModel)
                            else None
                        )
                        return TypeAdapter(field_type, config=config).validate_python(
                            value, strict=False, context=context
                        )
                    except ValidationError:
                        return value
            else:
                raise

    def to_dict(self) -> Dict[str, Any]:
        """Convert the model instance to dict."""
        return self.model_dump(by_alias=True, exclude_none=True)

    def to_json(self) -> str:
        """Convert the model instance to a JSON-encoded string."""
        return self.model_dump_json(by_alias=True, exclude_unset=True)

    @classmethod
    def from_dict(cls, obj: dict) -> Self:
        """Create a model instance from a dict."""
        return cls.model_validate(obj)

    @classmethod
    def from_json(cls, json_data: str | bytes | bytearray) -> Self:
        """Create a model instance from a JSON-encoded string."""
        return cls.model_validate_json(json_data)


class _Model(BaseModel):
    """A simple model that allows all additional attributes."""

    model_config = ConfigDict(extra="allow")

    def __init__(self, /, **kwargs):
        super().__init__(**kwargs)
        for field in self.model_fields_set:  # pylint: disable=not-an-iterable
            setattr(
                self, field, _Model.__model_construct_recursive(getattr(self, field))
            )

    @classmethod
    def __model_construct_recursive(cls, obj: Any):
        if isinstance(obj, list):
            return [cls.__model_construct_recursive(inner) for inner in obj]
        elif isinstance(obj, dict):
            model = _Model(**obj)
            return model
        else:
            return obj


Primitive: TypeAlias = Union[
    str, bool, int, float, Decimal, bytes, datetime, date, object, None
]
Model: TypeAlias = TypeAliasType(  # type: ignore[misc]  #(https:/python/mypy/issues/16614)
    "Model",
    Annotated[
        Union[List["Model"], "_Model", Primitive],  # type: ignore[misc]
        "A basic model that acts like a `simpleNamespace` "
        "or a collection over such models.",
    ],

and line 196 corresponds to:

Model: TypeAlias = TypeAliasType(  # type: ignore[misc]  #(https:/python/mypy/issues/16614)

I'm using typing_extensions v4.12.2. I had no such issues when using mypy` v1.10.1.

@sobolevn
Copy link
Member

This is enough to reproduce on main:

from typing_extensions import TypeAliasType

Model = TypeAliasType("Model", int)

@sobolevn sobolevn added the topic-type-alias TypeAlias and other type alias issues label Jul 22, 2024
@sobolevn sobolevn self-assigned this Jul 22, 2024
@sobolevn
Copy link
Member

Ok, here's the problem:

mypy/mypy/checkexpr.py

Lines 4672 to 4682 in 6aa46f0

def visit_type_application(self, tapp: TypeApplication) -> Type:
"""Type check a type application (expr[type, ...]).
There are two different options here, depending on whether expr refers
to a type alias or directly to a generic class. In the first case we need
to use a dedicated function typeanal.instantiate_type_alias(). This
is due to slight differences in how type arguments are applied and checked.
"""
if isinstance(tapp.expr, RefExpr) and isinstance(tapp.expr.node, TypeAlias):
if tapp.expr.node.python_3_12_type_alias:
return self.named_type("typing.TypeAliasType")

I have an idea about how to fix it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug mypy got something wrong topic-type-alias TypeAlias and other type alias issues
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants