Skip to content

Commit

Permalink
fix: support Python 3.13 in is_annotated
Browse files Browse the repository at this point in the history
In Python 3.13, the `typing.Annotated` type was changed to no longer
support `isinstance` or `issubclass`. Replace the `is_annotated`
implementation with code from Pydantic that checks to see if the
origin of the type matches the `Annotated` type exported from either
`typing` or `typing_extensions`.

Fixes #816
  • Loading branch information
rra committed Dec 11, 2024
1 parent cf2703c commit 2c2188b
Showing 1 changed file with 29 additions and 2 deletions.
31 changes: 29 additions & 2 deletions dataclasses_avroschema/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from datetime import datetime, timezone
from functools import lru_cache

import typing_extensions
from typing_extensions import Annotated, get_origin

from .protocol import ModelProtocol # pragma: no cover
Expand All @@ -22,6 +23,24 @@
faust = None # type: ignore # pragma: no cover


@lru_cache(maxsize=None)
def _get_typing_objects_by_name_of(name: str) -> tuple[typing.Any, ...]:
"""Get the member named `name` from both `typing` and `typing-extensions` (if it exists)."""
result = tuple(getattr(module, name) for module in (typing, typing_extensions) if hasattr(module, name))
if not result:
raise ValueError(f'Neither `typing` nor `typing_extensions` has an object called {name!r}')

Check warning on line 31 in dataclasses_avroschema/utils.py

View check run for this annotation

Codecov / codecov/patch

dataclasses_avroschema/utils.py#L31

Added line #L31 was not covered by tests
return result


def _is_typing_name(obj: object, name: str) -> bool:
"""Return whether `obj` is the member of the typing modules (includes the `typing-extensions` one) named `name`."""
# Using `any()` is slower:
for thing in _get_typing_objects_by_name_of(name):
if obj is thing:
return True
return False


@lru_cache(maxsize=None)
def is_pydantic_model(klass: typing.Type[ModelProtocol]) -> bool:
if pydantic is not None:
Expand Down Expand Up @@ -82,8 +101,16 @@ class User(...)


def is_annotated(a_type: typing.Type) -> bool:
origin = get_origin(a_type)
return origin is not None and isinstance(origin, type) and issubclass(origin, Annotated) # type: ignore[arg-type]
"""
Given a python type, return True if is typing.Annotated, otherwise False
Arguments:
a_type (typing.Any): python type
Returns:
bool
"""
return _is_typing_name(get_origin(a_type), name="Annotated")


def rebuild_annotation(a_type: typing.Type, field_info: FieldInfo) -> typing.Type:
Expand Down

0 comments on commit 2c2188b

Please sign in to comment.