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

fix: fetch correct type in pydantic #222

Closed
wants to merge 9 commits into from

Conversation

phi-friday
Copy link
Contributor

related: #206, #207, python/cpython#113702

sample code:

from __future__ import annotations

from pprint import pprint
from typing import Annotated, Any, TypeAlias, get_type_hints

from pydantic import BaseModel
from pydantic.annotated_handlers import GetCoreSchemaHandler
from pydantic_core import core_schema

import expression as ex


T: TypeAlias = "Annotated[str, 1]"
T2: TypeAlias = Annotated[str, 1]


class Username(str):
    @classmethod
    def __get_pydantic_core_schema__(cls, source_type: Any, handler: GetCoreSchemaHandler) -> Any:
        # https://github.com/dbrattli/Expression/issues/206
        return core_schema.no_info_after_validator_function(cls, handler(str))


class Sample(BaseModel):
    a: T = "foo"
    b: T2 = "bar"
    c: str | None = None
    d: Annotated[Annotated[Annotated[T, 1], 2], 3] = "baz"
    e: Username = Username("spam")
    f: Annotated[Annotated[Username, 1], 2] = Username("eggs")
    options_a: ex.Option[Username] = ex.option.Nothing
    options_b: ex.Option[Annotated[Annotated[Username, 1], 2]]
    options_c: ex.Option[Annotated[Annotated[Username, 1], 2]]
    result_a: ex.Result[Username, Any]
    result_b: ex.Result[Annotated[Annotated[Username, 1], 2], Any]
    result_c: ex.Result[Annotated[Annotated[Username, 1], 2], Any]


hints = get_type_hints(Sample)
pprint(hints)
schema = Sample.model_json_schema()
pprint(schema)

before:

❯ poetry run python sample.py
Traceback (most recent call last):
  File "/Users/***/git/python/private/Expression/sample.py", line 24, in <module>
    class Sample(BaseModel):
  File "/Users/***/git/python/private/Expression/.venv/lib/python3.10/site-packages/pydantic/_internal/_model_construction.py", line 205, in __new__
    complete_model_class(
  File "/Users/***/git/python/private/Expression/.venv/lib/python3.10/site-packages/pydantic/_internal/_model_construction.py", line 552, in complete_model_class
    cls.__pydantic_validator__ = create_schema_validator(
  File "/Users/***/git/python/private/Expression/.venv/lib/python3.10/site-packages/pydantic/plugin/_schema_validator.py", line 50, in create_schema_validator
    return SchemaValidator(schema, config)
pydantic_core._pydantic_core.SchemaError: Error building "model" validator:
  SchemaError: Error building "model-fields" validator:
  SchemaError: Field "options_b":
  SchemaError: Error building "json-or-python" validator:
  SchemaError: Error building "chain" validator:
  SchemaError: Error building "function-before" validator:
  SchemaError: Error building "union" validator:
  SchemaError: Error building "chain" validator:
  SchemaError: Error building "is-instance" validator:
  SchemaError: 'cls' must be valid as the first argument to 'isinstance'

after:

❯ poetry run python sample.py
{'a': <class 'str'>,
 'b': <class 'str'>,
 'c': str | None,
 'd': <class 'str'>,
 'e': <class '__main__.Username'>,
 'f': <class '__main__.Username'>,
 'options_a': expression.core.option.Option[__main__.Username],
 'options_b': expression.core.option.Option[__main__.Username],
 'options_c': expression.core.option.Option[__main__.Username],
 'result_a': expression.core.result.Result[__main__.Username, typing.Any],
 'result_b': expression.core.result.Result[__main__.Username, typing.Any],
 'result_c': expression.core.result.Result[__main__.Username, typing.Any]}
{'properties': {'a': {'default': 'foo', 'title': 'A', 'type': 'string'},
                'b': {'default': 'bar', 'title': 'B', 'type': 'string'},
                'c': {'anyOf': [{'type': 'string'}, {'type': 'null'}],
                      'default': None,
                      'title': 'C'},
                'd': {'default': 'baz', 'title': 'D', 'type': 'string'},
                'e': {'default': 'spam', 'title': 'E', 'type': 'string'},
                'f': {'default': 'eggs', 'title': 'F', 'type': 'string'},
                'options_a': {'default': {'none': None, 'tag': 'none'},
                              'title': 'Options A'},
                'options_b': {'title': 'Options B'},
                'options_c': {'title': 'Options C'},
                'result_a': {'anyOf': [{'properties': {'ok': {'title': 'Ok',
                                                              'type': 'string'},
                                                       'tag': {'title': 'Tag',
                                                               'type': 'string'}},
                                        'required': ['tag', 'ok'],
                                        'type': 'object'},
                                       {'properties': {'error': {'title': 'Error'},
                                                       'tag': {'title': 'Tag',
                                                               'type': 'string'}},
                                        'required': ['tag', 'error'],
                                        'type': 'object'}],
                             'title': 'Result A'},
                'result_b': {'anyOf': [{'properties': {'ok': {'title': 'Ok',
                                                              'type': 'string'},
                                                       'tag': {'title': 'Tag',
                                                               'type': 'string'}},
                                        'required': ['tag', 'ok'],
                                        'type': 'object'},
                                       {'properties': {'error': {'title': 'Error'},
                                                       'tag': {'title': 'Tag',
                                                               'type': 'string'}},
                                        'required': ['tag', 'error'],
                                        'type': 'object'}],
                             'title': 'Result B'},
                'result_c': {'anyOf': [{'properties': {'ok': {'title': 'Ok',
                                                              'type': 'string'},
                                                       'tag': {'title': 'Tag',
                                                               'type': 'string'}},
                                        'required': ['tag', 'ok'],
                                        'type': 'object'},
                                       {'properties': {'error': {'title': 'Error'},
                                                       'tag': {'title': 'Tag',
                                                               'type': 'string'}},
                                        'required': ['tag', 'error'],
                                        'type': 'object'}],
                             'title': 'Result C'}},
 'required': ['options_b', 'options_c', 'result_a', 'result_b', 'result_c'],
 'title': 'Sample',
 'type': 'object'}

@phi-friday phi-friday closed this Aug 13, 2024
@phi-friday phi-friday deleted the feat-fetch-correct-type branch August 13, 2024 14:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant