Skip to content
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

fix: fix compatibility with typing-extensions 0.4.12 #649

Merged
merged 15 commits into from
Jun 4, 2024

Conversation

tlambert03
Copy link
Member

python/typing_extensions#392 seems to be resulting in test errors:

>       @magicgui

tests/test_widgets.py:932: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
/opt/hostedtoolcache/Python/3.11.9/x64/lib/python3.11/site-packages/magicgui/type_map/_magicgui.py:190: in magicgui
    return _magicgui(
/opt/hostedtoolcache/Python/3.11.9/x64/lib/python3.11/site-packages/magicgui/type_map/_magicgui.py:530: in _magicgui
    return inner_func if function is None else inner_func(function)
/opt/hostedtoolcache/Python/3.11.9/x64/lib/python3.11/site-packages/magicgui/type_map/_magicgui.py:528: in inner_func
    return cast(FunctionGui, magic_class(func, **kwargs))
/opt/hostedtoolcache/Python/3.11.9/x64/lib/python3.11/site-packages/magicgui/widgets/_function_gui.py:172: in __init__
    sig = magic_signature(
/opt/hostedtoolcache/Python/3.11.9/x64/lib/python3.11/site-packages/magicgui/signature.py:319: in magic_signature
    return MagicSignature.from_signature(
/opt/hostedtoolcache/Python/3.11.9/x64/lib/python3.11/site-packages/magicgui/signature.py:224: in from_signature
    return cls(
/opt/hostedtoolcache/Python/3.11.9/x64/lib/python3.11/site-packages/magicgui/signature.py:204: in __init__
    params = [
/opt/hostedtoolcache/Python/3.11.9/x64/lib/python3.11/site-packages/magicgui/signature.py:205: in <listcomp>
    MagicParameter.from_parameter(
/opt/hostedtoolcache/Python/3.11.9/x64/lib/python3.11/site-packages/magicgui/signature.py:169: in from_parameter
    return cls(
/opt/hostedtoolcache/Python/3.11.9/x64/lib/python3.11/site-packages/magicgui/signature.py:109: in __init__
    _annotation = make_annotated(annotation, gui_options)
/opt/hostedtoolcache/Python/3.11.9/x64/lib/python3.11/site-packages/magicgui/signature.py:72: in make_annotated
    return Annotated[annotation, _options]
/opt/hostedtoolcache/Python/3.11.9/x64/lib/python3.11/typing.py:2239: in __class_getitem__
    return cls._class_getitem_inner(cls, *params)
/opt/hostedtoolcache/Python/3.11.9/x64/lib/python3.11/typing.py:379: in inner
    return func(*args, **kwds)
/opt/hostedtoolcache/Python/3.11.9/x64/lib/python3.11/typing.py:2253: in _class_getitem_inner
    return _AnnotatedAlias(origin, metadata)
/opt/hostedtoolcache/Python/3.11.9/x64/lib/python3.11/typing.py:2151: in __init__
    super().__init__(origin, origin)
/opt/hostedtoolcache/Python/3.11.9/x64/lib/python3.11/typing.py:1383: in __init__
    self.__parameters__ = _collect_parameters(args)
/opt/hostedtoolcache/Python/3.11.9/x64/lib/python3.11/site-packages/typing_extensions.py:3028: in _collect_parameters
    enforce_default_ordering = _has_generic_or_protocol_as_origin()
/opt/hostedtoolcache/Python/3.11.9/x64/lib/python3.11/site-packages/typing_extensions.py:2955: in _has_generic_or_protocol_as_origin
    return frame.f_locals.get("origin") in {
/opt/hostedtoolcache/Python/3.11.9/x64/lib/python3.11/typing.py:1395: in __hash__
    return hash((self.__origin__, self.__args__))
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = typing.Annotated[int, {'max': 3}]

    def __hash__(self):
>       return hash((self.__origin__, self.__metadata__))
E       TypeError: unhashable type: 'dict'

this attempts to fix it

Copy link

codecov bot commented May 27, 2024

Codecov Report

Attention: Patch coverage is 86.11111% with 5 lines in your changes missing coverage. Please review.

Project coverage is 89.12%. Comparing base (f50a8dc) to head (a08205c).

Files Patch % Lines
src/magicgui/signature.py 86.11% 5 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #649      +/-   ##
==========================================
- Coverage   89.14%   89.12%   -0.03%     
==========================================
  Files          39       39              
  Lines        4682     4718      +36     
==========================================
+ Hits         4174     4205      +31     
- Misses        508      513       +5     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@tlambert03
Copy link
Member Author

tlambert03 commented May 28, 2024

@hanjinliu just a head up... in attempting to fix stuff broken by typing-extensions 0.4.12, I need to make all of the items inside of Annotated hashable. And in doing so, I tried to make dicts immutable... however I ran into an issue with magicclass _create_gui_method, that apparently mutates these options after their creation:

>       ui = A()

tests/test_wraps.py:146: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
magicclass/core.py:210: in __init__
    self._convert_attributes_into_widgets()
magicclass/_gui/class_gui.py:274: in _convert_attributes_into_widgets
    format_error(e, _hist, name, attr)
magicclass/_gui/utils.py:89: in format_error
    raise construction_err from None
magicclass/_gui/class_gui.py:228: in _convert_attributes_into_widgets
    widget = self._create_widget_from_method(widget)
magicclass/_gui/_base.py:707: in _create_widget_from_method
    func = _create_gui_method(self, obj)
magicclass/_gui/_base.py:1088: in _create_gui_method
    _param.options["bind"] = _arg_bind.as_remote_getter(self)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = {'bind': MagicValueField(value=..., name='a', widget_type=None, record=True, options={}), 'widget_type': <class 'magicgui.widgets.EmptyWidget'>}
key = 'bind'
value = <function MagicField.as_remote_getter.<locals>._func at 0x7f2e117167a0>

    def __setitem__(self, key: Any, value: Any) -> None:  # noqa: D105
>       raise TypeError("hashabledict is immutable")
E       magicclass._exceptions.MagicClassConstructionError: 
E       
E       	a <class 'magicclass.fields._fields.MagicValueField'> -> SpinBox
E       	B <class 'magicclass._gui._base._MagicTemplateMeta'> -> B
E       		fb2 (<class 'function'>) <--- Error
E       
E       TypeError: hashabledict is immutable

i'll undo that restriction for now, but it would probably be best if you tried not to rely on dicts in Annotations as being mutable objects that you can modify after creation.

@tlambert03
Copy link
Member Author

I allowed metadata to be mutable again, but now hit another issue with magicclass. can you check out this branch and see if you can determine what has broken in magic class?

tests/test_types.py ....F                                                [ 70%]
tests/test_types.py:71 test_nested_annotated_in_optional - Attributetests/test_types.py .............                                        [ 75%]
tests/test_undo.py ..........                                            [ 78%]
tests/test_widget_types.py ................                              [ 84%]
tests/test_worker.py .............                                       [ 88%]
tests/test_wrappers.py .........................                         [ 97%]
tests/test_wraps.py ........                                             [100%]

=================================== FAILURES ===================================
______________________ test_nested_annotated_in_optional _______________________

    @pytest.mark.skipif(sys.version_info < (3, 9), reason="requires python3.9+")
    def test_nested_annotated_in_optional():
        @magicclass
        class A:
            def f(
                self,
                x: Optional[list[Annotated[int, {"text": "T", "options": {"min": -1}}]]] = None,
            ):
                x.sort()
    
        ui = A()
>       ui["f"].changed()
E       AttributeError: 'function' object has no attribute 'changed'

tests/test_types.py:83: AttributeError

@hanjinliu
Copy link
Contributor

Hi @tlambert03 , thank you for notifying me.
Making dict hashable does not break my implementation, and I agree that immutable options will be better (I remember I got the same unhashable error and fixed it with a dirty hack before 😞).

Regarding to the AttributeError, I found it originated from the error trying to write __args__ of generic classes.

from magicgui.signature import _make_hashable
_make_hashable(list[int])
File ~\mambaforge\envs\py312\Lib\site-packages\magicgui\signature.py:122, in _make_hashable(obj)
    120     return frozenset(_make_hashable(v) for v in obj)
    121 if args := getattr(obj, "__args__", None):
--> 122     obj.__args__ = _make_hashable(args)
    123 if meta := getattr(obj, "__metadata__", None):
    124     obj.__metadata__ = _make_hashable(meta)

AttributeError: readonly attribute

This happens when I pass generic classes to the "annotation" GUI option. The example below has redundant "annotation" option but it shows how the AttributeError happens.

@magicgui
def f(a: Annotated[list[int], {"annotation": list[int]}]):
    pass

Is it possible to fix this from the _make_hashable side? (...or maybe I should be careful to not use generic classes in annotation option)

@tlambert03 tlambert03 merged commit 3fc6823 into pyapp-kit:main Jun 4, 2024
36 checks passed
@tlambert03 tlambert03 deleted the fix-typing-ext branch June 4, 2024 13:11
@tlambert03 tlambert03 added the bug Something isn't working label Jun 4, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants