Skip to content

Mind inheritance when inferring TypedDict-based class #2876

Closed

Description

@Melevir

This one continues #2872.

Here is a reproducible example:

from typing import TypedDict, List
from hypothesis import given, strategies as st

class SomeData(TypedDict):
    a: str
    b: int

class SomeMoreData(SomeData):
    c: List[int]

st.register_type_strategy(
    SomeData, st.builds(SomeData, a=st.just("string from strategy"), b=st.just(42))
)

@given(st.from_type(SomeMoreData))
def test_some_data(item):
    assert item["a"] == "string from strategy"
    assert item["b"] == 42

class EvenMoreData(SomeMoreData):
    b: bool  # tricky case - should *not* use SomeData strategy for this

print(SomeData.__annotations__)
print(SomeMoreData.__annotations__)
print(EvenMoreData.__annotations__)

For now, the test will fail, but it make sense to mind inheritance when inferring TypedDicts types.

If the proposition (or bug, whatever) is approved, I'll try to implement the thing.

Activity

  1. added
    enhancementit's not broken, but we want it to be better
    on Feb 25, 2021
  2. Zac-HD commented on Feb 25, 2021

    @Zac-HD
    Member

    I'll call this an "enhancement", and would be happy to have the feature.

    But be careful! You'll need to traverse the MRO, use the closest registered supertype for all fields defined on that type (handling the case where the registered strategy doesn't return a dict...), and then use our current logic to resolve the fields added by subclasses of whatever was registered down to the type we're actually constructing.

    Tricky, but doable 😁

  3. sobolevn commented on Mar 1, 2021

    @sobolevn
    Member

    There's an ongoing PEP discussion that might be related: https://mail.python.org/archives/list/typing-sig@python.org/thread/53XVOD5ZUKJ263MWA6AUPEA6J7LBBLNV/ But, since Guido is the PEP's sponsor - it is a matter of time.

    Maybe there are some things to consider from it in the current implementation.

  4. Zac-HD commented on Mar 1, 2021

    @Zac-HD
    Member

    Gosh... looks kinda ugly to me, but then I've never felt that TypedDict is really idiomatic for statically-typed-Python. If you're going to this much trouble to annotate your dicts, maybe just use a dataclass?

    In any case, I'd hope that at runtime this will just use the existing attributes for lists of optional and required keys (see python/cpython#17214); in which case it will "just work" with Hypothesis.

  5. gwerbin commented on Mar 17, 2021

    @gwerbin
  6. self-assigned this
    on Dec 12, 2021
  7. Zac-HD commented on Dec 12, 2021

    @Zac-HD
    Member

    Unfortunately, it turns out that the TypedDict implementation simply doesn't retain inheritance information at runtime, so we won't be able to support this feature.

    I was keen enough to try parsing, but inspect.getsource() also fails!

  8. sobolevn commented on Dec 12, 2021

    @sobolevn
    Member

    @Zac-HD I think it is worth filling a BPO issue. Maybe even two:

    • inspect.getsource() should not fail
    • getting base classes should also be somehow possible, otherwise it totally kills introspection

    I've seen lots of bugs with TypedDict in terms of its creation / metaclass usage / etc. Many related PRs were accepted.

  9. Zac-HD commented on Dec 12, 2021

    @Zac-HD
    Member

    Yeah, probably... I just don't have the energy for another long engagement with python-dev at the moment, and once I do it'll be about getting PBT running in their CI.

  10. sobolevn commented on Dec 12, 2021

    @sobolevn
    Member

    Yes, I've seen this thread about hypothesis in CPython's CI. Sending hopes and prayers, really cool feature 🙏

    I will open them in this case 🙂

  11. sobolevn commented on Dec 26, 2021

    @sobolevn
    Member

    I am filling a BPO issue right now and I found that the original proposal by @Melevir is not what I would expect from hypothesis.

    Let's see how regular classes work on this case:

    from typing import List
    from hypothesis import given, strategies as st
    from dataclasses import dataclass
    
    @dataclass
    class SomeClass:
        a: str
        b: int
    
    @dataclass
    class SomeMoreClass(SomeClass):
        c: List[int]
    
    st.register_type_strategy(
        SomeClass, st.builds(SomeClass, a=st.just("string from strategy"), b=st.just(42))
    )
    
    @given(st.from_type(SomeMoreClass))
    def test_some_class(item):
        assert item.a == "string from strategy"
        assert item.b == 42

    This will fail with:

    __________________________________ test_some_class ___________________________________
    
        @given(st.from_type(SomeMoreClass))
    >   def test_some_class(item):
    
    ex.py:35: 
    _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
    
    item = SomeMoreClass(a='', b=0, c=[])
    
        @given(st.from_type(SomeMoreClass))
        def test_some_class(item):
    >       assert item.a == "string from strategy"
    E       AssertionError: assert '' == 'string from strategy'
    E         - string from strategy
    
    ex.py:36: AssertionError
    -------------------------------- Captured stdout call --------------------------------
    Falsifying example: test_some_class(
        item=SomeMoreClass(a='', b=0, c=[]),
    )
    

    Why the same should work for TypedDict?

  12. sobolevn commented on Dec 26, 2021

    @sobolevn
    Member

    @Zac-HD I was also not successful in reproducing inspect.getsource failure.

    This works for me:

    from typing import TypedDict
    import inspect
    
    class SomeData(TypedDict):
        a: str
        b: int
    
    class SomeExtraData(SomeData):
        c: bool
    
    print(inspect.getsource(SomeData))
    print(inspect.getsource(SomeExtraData))

    Снимок экрана 2021-12-26 в 13 58 43

    Is there anything that I am missing?

  13. Zac-HD commented on Dec 27, 2021

    @Zac-HD
    Member

    Huh, looks like inspect.getsource() works for TypedDict on Python 3.8 and later! (I'm still not keen to rely on this though...)

    I think that TypedDict is a bit special / different to standard classes here, but on reflection that you're also right that we probably don't want this feature - it's pretty hard to say why e.g. dataclasses should follow either standard classes or typeddicts, and in that case we should default to treating them all the same (i.e. status quo).

    And also, thanks again for following this up! It's fantastic to have your independent investigations and design work on this stuff 😍

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

Metadata

Metadata

Assignees

Labels

enhancementit's not broken, but we want it to be better

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions