Skip to content

TypedDict with generic function #9827

Closed

Description

@achimnol

Bug Report

Using a TypedDict with a generic function gives a strange type error with the same "expected" and "actual" types.
I also wonder that if we could use TypeVar('TD', bound=TypedDict) as well.
(NOTE: This is a follow-up to #9820)

To Reproduce

Given

import sys
from typing import Any, Mapping, Type, TypeVar, cast

import typeguard

TD = TypeVar('TD')

def check_typed_dict(value: Mapping[Any, Any], expected_type: Type[TD]) -> TD:
    """
    Validates the given dict against the given TypedDict class, and wraps the value as the given TypedDict type.

    This is a shortcut to :func:`typeguard.check_typed_dict()` function to fill extra information
    """
    assert issubclass(expected_type, dict) and hasattr(expected_type, '__annotations__'), \
           f"expected_type ({type(expected_type)}) must be a TypedDict class"
    frame = sys._getframe(1)
    _globals = frame.f_globals
    _locals = frame.f_locals
    memo = typeguard._TypeCheckMemo(_globals, _locals)
    typeguard.check_typed_dict('value', value, expected_type, memo)
    # Here we passed the check, so return it after casting.
    return cast(TD, value)

and

from typing import Dict, Literal, Optional, TypedDict

class HardwareMetadata(TypedDict):
    status: Literal["healthy", "degraded", "offline", "unavailable"]
    status_info: Optional[str]
    metadata: Dict[str, str]

,
running mypy against the following code snippet

    async def gather_agent_hwinfo(self, instance_id: AgentId) -> Mapping[str, HardwareMetadata]:
        agent = await self.get_instance(instance_id, agents.c.addr)
        async with RPCContext(agent['addr'], None) as rpc:
            result = await rpc.call.gather_hwinfo()
            return {
                k: check_typed_dict(v, HardwareMetadata)  # L259
                for k, v in result.items()
            }

    async def gather_storage_hwinfo(self, vfolder_host: str) -> HardwareMetadata:
        proxy_name, volume_name = self.storage_manager.split_host(vfolder_host)
        async with self.storage_manager.request(
            proxy_name, 'GET', 'volumes/hwinfo',
            query={'volume': volume_name},
            raise_for_status=True,
        ) as (_, storage_resp):
            return check_typed_dict(await storage_resp.json(), HardwareMetadata)  # L270

reports

src/ai/backend/manager/registry.py:259: error: Value expression in dictionary comprehension has incompatible type "HardwareMetadata"; expected type "HardwareMetadata"
src/ai/backend/manager/registry.py:270: error: Incompatible return value type (got "HardwareMetadata", expected "HardwareMetadata")

Expected Behavior

It should pass the type check, or give a more understandable error message.

Actual Behavior

It's reporting a type error with the same "expected" and "actual" types.

Your Environment

  • Mypy version used: 0.790
  • Mypy command-line flags: python -m mypy src/ai/backend tests
  • Mypy configuration options from mypy.ini (and other config files):
[mypy]
ignore_missing_imports = true
namespace_packages = true
  • Python version used: 3.8.5
  • Operating system and version: Ubuntu 20.04

Activity

  1. achimnol commented on Jan 27, 2021

    @achimnol
    ContributorAuthor

    This is still happening with v0.800 release.
    Note that my config has been updated:

    [mypy]
    ignore_missing_imports = true
    mypy_path = src
    namespace_packages = true
    explicit_package_bases = true
    
  2. brendon-boldt commented on Feb 2, 2021

    @brendon-boldt

    For what it's worth I have a minimal example here:

    from typing import TypedDict, TypeVar, Type, cast
    
    class Alpha(TypedDict):
        pass
    
    class Beta:
        pass
    
    _T = TypeVar("_T")
    
    def foo(td: Type[_T]) -> _T:
        pass
    
    def baz_alpha() -> Alpha:
        return foo(Alpha)  # Incompatible return value type (got "Alpha", expected "Alpha")
    
    def baz_alpha_cast() -> Alpha:
        return cast(Alpha, foo(Alpha))  # no error, no warning about a redundant cast
    
    def baz_beta() -> Beta:
        return foo(Beta)  # no error
    
    def baz_int() -> int:
        return foo(int)  # no error

    Python 3.9.1 and mypy 0.800.

    Incidentally, I am also unable to use TypedDicts as the upper bound of the TypeVar.

  3. erictraut commented on Aug 12, 2023

    @erictraut

    I think this bug has been fixed. I'm not able to repro it with the latest version of mypy.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions