Skip to content

Conversation

@lencshu
Copy link

@lencshu lencshu commented Nov 5, 2025

Please using Annotated[Union] with Python 3.14

Describe the bug

When casting:

cast(CachedDiscriminatorType, union).__discriminator__ = details

With union being:

anthropic.types.raw_message_start_event.RawMessageStartEvent | anthropic.types.raw_message_delta_event.RawMessageDeltaEvent | anthropic.types.raw_message_stop_event.RawMessageStopEvent | anthropic.types.raw_content_block_start_event.RawContentBlockStartEvent | anthropic.types.raw_content_block_delta_event.RawContentBlockDeltaEvent | anthropic.types.raw_content_block_stop_event.RawContentBlockStopEvent

To Reproduce

I’m getting an error when running the MCP tool call

  File "/app/.venv/lib/python3.14/site-packages/anthropic/_streaming.py", line 185, in __aiter__
    async for item in self._iterator:
        yield item
  File "/app/.venv/lib/python3.14/site-packages/anthropic/_streaming.py", line 214, in __stream__
    yield process_data(data=data, cast_to=cast_to, response=response)
          ~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/app/.venv/lib/python3.14/site-packages/anthropic/_base_client.py", line 641, in _process_response_data
    return cast(ResponseT, construct_type(type_=cast_to, value=data))
                           ~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/app/.venv/lib/python3.14/site-packages/anthropic/_models.py", line 518, in construct_type
    discriminator = _build_discriminated_union_meta(union=type_, meta_annotations=meta)
  File "/app/.venv/lib/python3.14/site-packages/anthropic/_models.py", line 688, in _build_discriminated_union_meta
    cast(CachedDiscriminatorType, union).__discriminator__ = details
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
AttributeError: 'typing.Union' object has no attribute '__discriminator__' and no __dict__ for setting new attributes

CachedDiscriminatorType should be an Annotated rather than Union directly

OS
macos

Python version
Python v3.14.0

Library version
anthropic v0.72.0

Update _models.py to handle Python 3.14
@lencshu lencshu requested a review from a team as a code owner November 5, 2025 14:28
@Natim
Copy link

Natim commented Nov 5, 2025

Refs openai/openai-python#2705

@Natim
Copy link

Natim commented Nov 5, 2025

🧩 The Root Cause

Starting with Python 3.14, typing.Union (and also types.UnionType for | syntax) is now fully immutable — you can no longer attach arbitrary attributes to them (they have no __dict__).
Hence this line fails with:

cast(CachedDiscriminatorType, union).__discriminator__ = details

With the error:

AttributeError: 'typing.Union' object has no attribute '__discriminator__' and no __dict__ for setting new attributes

This behavior worked in ≤ 3.13 because Union objects allowed dynamic attributes, but that’s gone.

@Natim
Copy link

Natim commented Nov 5, 2025

✅ The Correct Fix

We can’t store metadata on the Union type itself anymore.
Instead, we can keep a sidecar cache (a WeakKeyDictionary) keyed by the union type.

Here’s how you can safely patch both OpenAI and Anthropic implementations:

Step 1 – Create a global cache

Add this near the top of the module (just after CachedDiscriminatorType is defined):

import weakref

# Global cache since typing.Union objects are now immutable (Python 3.14+)
_DISCRIMINATOR_CACHE: "weakref.WeakKeyDictionary[type, DiscriminatorDetails]" = weakref.WeakKeyDictionary()

Step 2 – Update _build_discriminated_union_meta

Replace

if isinstance(union, CachedDiscriminatorType):
    return union.__discriminator__

With

cached = _DISCRIMINATOR_CACHE.get(union)
if cached is not None:
    return cached

Step 3 - Update the ___discriminator__ set

Replace

cast(CachedDiscriminatorType, union).__discriminator__ = details
return details

With

_DISCRIMINATOR_CACHE[union] = details
return details

@Natim
Copy link

Natim commented Nov 5, 2025

🧠 Why This Works

  • WeakKeyDictionary lets cached entries disappear when their type objects are garbage-collected.
  • Works across all supported Python versions.
  • Preserves existing “caching” semantics without mutating Union objects.

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.

2 participants