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

Cannot use built-in callable as argument to PlainSerializer #8967

Closed
1 task done
Tracked by #8993
aphedges opened this issue Mar 6, 2024 · 1 comment · Fixed by #9031
Closed
1 task done
Tracked by #8993

Cannot use built-in callable as argument to PlainSerializer #8967

aphedges opened this issue Mar 6, 2024 · 1 comment · Fixed by #9031
Labels
bug V2 Bug related to Pydantic V2 good first issue help wanted Pull Request welcome

Comments

@aphedges
Copy link
Contributor

aphedges commented Mar 6, 2024

Initial Checks

  • I confirm that I'm using Pydantic V2

Description

PlainSerializer does not accept the built-in object str as its func argument. The parameter's type annotation includes Callable[[Any], Any], which str is an instance of. Wrapping str within a lambda expression or a def statement allows the code to work, but that shouldn't be necessary. The problem seems to specifically be with built-ins. The problem isn't classes because a custom class works fine, and the problem isn't restricted to built-in classes because hash() also doesn't work without wrapping.

I have included a stack trace of the thrown exception below:

Stack trace
Traceback (most recent call last):
  File "/Users/ahedges/Downloads/demo.py", line 6, in <module>
    class Demo(BaseModel):
  File "/Users/ahedges/.pyenv/versions/biomed/lib/python3.11/site-packages/pydantic/_internal/_model_construction.py", line 183, in __new__
    complete_model_class(
  File "/Users/ahedges/.pyenv/versions/biomed/lib/python3.11/site-packages/pydantic/_internal/_model_construction.py", line 517, in complete_model_class
    schema = cls.__get_pydantic_core_schema__(cls, handler)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/ahedges/.pyenv/versions/biomed/lib/python3.11/site-packages/pydantic/main.py", line 584, in __get_pydantic_core_schema__
    return __handler(__source)
           ^^^^^^^^^^^^^^^^^^^
  File "/Users/ahedges/.pyenv/versions/biomed/lib/python3.11/site-packages/pydantic/_internal/_schema_generation_shared.py", line 82, in __call__
    schema = self._handler(__source_type)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/ahedges/.pyenv/versions/biomed/lib/python3.11/site-packages/pydantic/_internal/_generate_schema.py", line 499, in generate_schema
    schema = self._generate_schema(obj)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/ahedges/.pyenv/versions/biomed/lib/python3.11/site-packages/pydantic/_internal/_generate_schema.py", line 737, in _generate_schema
    schema = self._post_process_generated_schema(self._generate_schema_inner(obj))
                                                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/ahedges/.pyenv/versions/biomed/lib/python3.11/site-packages/pydantic/_internal/_generate_schema.py", line 759, in _generate_schema_inner
    return self._model_schema(obj)
           ^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/ahedges/.pyenv/versions/biomed/lib/python3.11/site-packages/pydantic/_internal/_generate_schema.py", line 572, in _model_schema
    {k: self._generate_md_field_schema(k, v, decorators) for k, v in fields.items()},
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/ahedges/.pyenv/versions/biomed/lib/python3.11/site-packages/pydantic/_internal/_generate_schema.py", line 572, in <dictcomp>
    {k: self._generate_md_field_schema(k, v, decorators) for k, v in fields.items()},
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/ahedges/.pyenv/versions/biomed/lib/python3.11/site-packages/pydantic/_internal/_generate_schema.py", line 923, in _generate_md_field_schema
    common_field = self._common_field_schema(name, field_info, decorators)
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/ahedges/.pyenv/versions/biomed/lib/python3.11/site-packages/pydantic/_internal/_generate_schema.py", line 1045, in _common_field_schema
    schema = self._apply_annotations(
             ^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/ahedges/.pyenv/versions/biomed/lib/python3.11/site-packages/pydantic/_internal/_generate_schema.py", line 1747, in _apply_annotations
    schema = get_inner_schema(source_type)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/ahedges/.pyenv/versions/biomed/lib/python3.11/site-packages/pydantic/_internal/_schema_generation_shared.py", line 82, in __call__
    schema = self._handler(__source_type)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/ahedges/.pyenv/versions/biomed/lib/python3.11/site-packages/pydantic/_internal/_generate_schema.py", line 1829, in new_handler
    schema = metadata_get_schema(source, get_inner_schema)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/ahedges/.pyenv/versions/biomed/lib/python3.11/site-packages/pydantic/functional_serializers.py", line 66, in __get_pydantic_core_schema__
    return_type = _decorators.get_function_return_type(
                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/ahedges/.pyenv/versions/biomed/lib/python3.11/site-packages/pydantic/_internal/_decorators.py", line 763, in get_function_return_type
    hints = get_function_type_hints(
            ^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/ahedges/.pyenv/versions/biomed/lib/python3.11/site-packages/pydantic/_internal/_typing_extra.py", line 273, in get_function_type_hints
    annotations = function.__annotations__
                  ^^^^^^^^^^^^^^^^^^^^^^^^
AttributeError: type object 'str' has no attribute '__annotations__'. Did you mean: '__contains__'?

Example Code

from typing import Annotated

from pydantic import BaseModel, HttpUrl, PlainSerializer


class Demo(BaseModel):
    # Commented-out line below works
    # url: Annotated[HttpUrl, PlainSerializer(lambda x: str(x))]
    url: Annotated[HttpUrl, PlainSerializer(str)]


input_json = {"url": "https://github.com/pydantic/pydantic"}
obj = Demo.parse_obj(input_json)
assert isinstance(obj.model_dump()["url"], str), obj.model_dump()["url"]
print(f'{obj.model_dump()["url"] = }')

Python, Pydantic & OS Version

             pydantic version: 2.6.3
        pydantic-core version: 2.16.3
          pydantic-core build: profile=release pgo=false
                 install path: /Users/ahedges/.pyenv/versions/3.11.8/envs/biomed/lib/python3.11/site-packages/pydantic
               python version: 3.11.8 (main, Feb 14 2024, 14:49:59) [Clang 15.0.0 (clang-1500.1.0.2.5)]
                     platform: macOS-13.6.4-x86_64-i386-64bit
             related packages: typing_extensions-4.10.0 mypy-1.8.0
                       commit: unknown
@aphedges aphedges added bug V2 Bug related to Pydantic V2 pending Awaiting a response / confirmation labels Mar 6, 2024
@sydney-runkle
Copy link
Member

@aphedges,

Thanks for the report. This is definitely something we do want to support. Might be a pretty good first issue to pick up if someone is interested.

As shown in the traceback, the issue is that str doesn't have an __annotations__ attribute like almost all other functions that we use. Not sure exactly what workaround we should use for those stdlib callable types, though.

@sydney-runkle sydney-runkle added good first issue help wanted Pull Request welcome and removed pending Awaiting a response / confirmation labels Mar 12, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug V2 Bug related to Pydantic V2 good first issue help wanted Pull Request welcome
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants