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鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

PySide2 conflicting with pydantic signatures #2264

Closed
tlambert03 opened this issue Feb 13, 2021 · 1 comment 路 Fixed by #2265
Closed

PySide2 conflicting with pydantic signatures #2264

tlambert03 opened this issue Feb 13, 2021 · 1 comment 路 Fixed by #2265
Labels
bug Something isn't working
Milestone

Comments

@tlambert03
Copy link
Member

tlambert03 commented Feb 13, 2021

馃悰 Bug

I don't know how to explain this one yet... but simply importing napari is changing/breaking something about pydantic.BaseModel. It may be doing many things, but the way I noticed it is that BaseModel signatures are breaking after napari is imported:

update: PySide2 is still not playing well with pydantic (or apparently anything that tries to dynamically update a class __signature__ attribute):

In [1]: from pydantic import BaseModel

In [2]: import inspect

In [3]: class T(BaseModel):
   ...:     x: int
   ...:     y: str = 'sadf'
   ...:

# so far so good
In [4]: inspect.signature(T)
Out[4]: <Signature (*, x: int, y: str = 'sadf') -> None>

In [5]: import PySide2

In [6]: inspect.signature(T)
Out[6]: <Signature (**data: typing.Any) -> None>  # ??

# let's try that again
In [7]: class T(BaseModel):
   ...:     x: int
   ...:     y: str = 'sadf'
   ...:

In [8]: inspect.signature(T)  # ???
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-8-e75fbef4a311> in <module>
----> 1 inspect.signature(T)

~/miniconda3/envs/napdev/lib/python3.8/inspect.py in signature(obj, follow_wrapped)
   3091 def signature(obj, *, follow_wrapped=True):
   3092     """Get a signature object for the passed callable."""
-> 3093     return Signature.from_callable(obj, follow_wrapped=follow_wrapped)
   3094
   3095

~/miniconda3/envs/napdev/lib/python3.8/inspect.py in from_callable(cls, obj, follow_wrapped)
   2840     def from_callable(cls, obj, *, follow_wrapped=True):
   2841         """Constructs Signature for the given callable object."""
-> 2842         return _signature_from_callable(obj, sigcls=cls,
   2843                                         follow_wrapper_chains=follow_wrapped)
   2844

~/miniconda3/envs/napdev/lib/python3.8/inspect.py in _signature_from_callable(obj, follow_wrapper_chains, skip_bound_arg, sigcls)
   2250         if sig is not None:
   2251             if not isinstance(sig, Signature):
-> 2252                 raise TypeError(
   2253                     'unexpected object {!r} in __signature__ '
   2254                     'attribute'.format(sig))

TypeError: unexpected object <pydantic.utils.ClassAttribute object at 0x7fe9b925d4f0> in __signature__ attribute

I say "still" because I thought this bug, was solved in PySide2 5.13.1... which is why we bumped our pyside requirement. This, however, seems to be a slightly different side of that old bug. need to dig deeper

@tlambert03 tlambert03 changed the title napari is somehow breaking pydantic ~napari~ PySide2 conflicting with pydantic signatures Feb 13, 2021
@tlambert03 tlambert03 changed the title ~napari~ PySide2 conflicting with pydantic signatures PySide2 conflicting with pydantic signatures Feb 13, 2021
@tlambert03
Copy link
Member Author

tlambert03 commented Feb 13, 2021

ok, some more details... this is really quite a mess. I'm still only kind of following here. but this patch in PySide2 describes their "fix" as follows:

It turned out that the mock tool of the unittest module wants to
write into a __signature__ attribute. We now allow this by implementing
a writable attribute that memorizes any written value.

I'm not sure, but it kinda seems like they're directly modifying how the __signature__ attribute is retrieved for everything? (seems atrocious, but maybe?)

pydantic, however, doesn't set the __signature__ attribute directly to an instance of inspect.Signature, but rather uses a descriptor object to hide the class signature on instances (they do this to fix the signature on callable instance). Apparently, when python then goes to get the class __signature__... pyside2 seems to have broken that descriptor "getter" and instead returns the descriptor class itself. what a mess.

For us, I think we might be able to work around this by undoing what pydantic does with their signature descriptor, and simply being aware that we might need to modify the signature (if desired) on callable object instances.

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 a pull request may close this issue.

2 participants