Skip to content

TypedDict with generic function #9827

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

Closed
achimnol opened this issue Dec 20, 2020 · 3 comments
Closed

TypedDict with generic function #9827

achimnol opened this issue Dec 20, 2020 · 3 comments
Labels
bug mypy got something wrong topic-type-form TypeForm might fix this topic-typed-dict

Comments

@achimnol
Copy link
Contributor

achimnol commented Dec 20, 2020

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
@achimnol achimnol added the bug mypy got something wrong label Dec 20, 2020
achimnol added a commit to lablup/backend.ai-manager that referenced this issue Dec 20, 2020
@achimnol
Copy link
Contributor Author

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

@brendon-boldt
Copy link

brendon-boldt commented Feb 2, 2021

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.

@erictraut
Copy link

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
Labels
bug mypy got something wrong topic-type-form TypeForm might fix this topic-typed-dict
Projects
None yet
Development

Successfully merging a pull request may close this issue.

6 participants