pydantic-core icon indicating copy to clipboard operation
pydantic-core copied to clipboard

Can't generate schema when `typing.Never` is involved

Open nbraud opened this issue 10 months ago • 3 comments

pydantic fails to generate a schema for typing.Never; I ran into this in a model generic over a TypeVar whose bound involved Never.

Error encountered

pydantic.errors.PydanticSchemaGenerationError: Unable to generate pydantic-core schema for typing.Never. Set arbitrary_types_allowed=True in the model_config to ignore this error or implement __get_pydantic_core_schema__ on your type to fully support it.

Test case

from pydantic import Field
from pydantic.dataclasses import dataclass
from typing import Generic, Never, TypeVar


T = TypeVar('T', None, Never)

@dataclass(frozen=True, kw_only=True)
class GenericConfig(Generic[T]):
    foo: str
    bar: str | T = Field(None)

ParsedConfig = GenericConfig[None]
ResolvedConfig = GenericConfig[Never]

Workarounds

In this particular case, removing the bounds on T works, but is highly undesirable: one could then instantiate things like GenericConfig[int] without type-checking error.

I attempted to provide the schema manually, but couldn't get that to work:

from pydantic_core import core_schema
from pydantic import GetPydanticSchema

from typing import Annotated, Never as _Never
Never = Annotated[
    _Never,
    GetPydanticSchema(
        lambda tp, handler: core_schema.no_info_after_validator_function(
                lambda _: False, handler(tp)
        )
    ),
]

I'm assuming the handler(tp) call is the problem, but I couldn't find a way to directly construct a CoreSchema.

nbraud avatar Oct 26 '23 18:10 nbraud

I don't think typing.Never makes sense in the context of a field. It only really makes sense in the context of a function return / control flow.

adriangb avatar Oct 26 '23 19:10 adriangb

@adriangb Not sure what you mean there: Never is the bottom type, and it's generally-useful for tracking (at the type-level) situations that cannot happen (like someObj.bar being something else than a str).

I even believe this kind of use was intended given that:

  • Never is semantically-equivalent to NoReturn, but the former was added in Py3.11 to have a more-sensible name in usecases other than a function's return type;
  • mypy type-checks that kind of code without issue.

Note: if the field's type was T (rather than bar: str | T here), the type GenericConfig[Never] would indeed be uninhabited; however, it's common to have inhabited types defined by expressions that happen to contain Never.

nbraud avatar Oct 26 '23 21:10 nbraud

I can see how Never might appear in generic code. I think it would be straightforward for us to add a never_schema which can never succeed validation. For example even list[Never] is sort of reasonable to me as a typing concept; it would mean a list which cannot ever contain a value.

davidhewitt avatar Oct 27 '23 08:10 davidhewitt

Going to close this as a duplicate of https://github.com/pydantic/pydantic/issues/9731. I think that issue is more likely to get views / potential support from community members.

sydney-runkle avatar Aug 16 '24 17:08 sydney-runkle