Repository: agronholm/typeguard

Issue #479: Protocol checking against `typing_extenstion.Protocol` fails
Created: 2024-08-27T06:16:58Z
Things to check first


 I have searched the existing issues and didn't find my bug already reported there


 I have checked that my bug is still present in the latest release


Typeguard version
4.3.0
Python version
Python 3.10.4
What happened?
Expected typeguard to ignore private attributes of the typing_extension.Protocol so that type checking would pass.

Note: Honestly this was my first intuition, but I realize it might be out of scope as I'm not sure what your stance is with respect to supporting the typing_extensions package. Perhaps this issue is more a feature request to allow configuring custom attributes that typeguard will ignore when typechecking protocols.

How can we reproduce the bug?
import typing_extensions
import builtins
import typeguard

class IDo(typing_extensions.Protocol):
    def do_something(self) -> builtins.str:
        ...

def doer(do: IDo) -> builtins.str:
    typeguard.check_type(value=do, expected_type=IDo)
    return do.do_something()

class DefaultDo:
    def do_something(self) -> builtins.str:
        return "did something"
    
print(doer(DefaultDo()))
Running the above program will result in:
typeguard.TypeCheckError: __main__.DefaultDo is not compatible with the IDo protocol because it has no attribute named '__protocol_attrs__'
Indeed, extending the typing_extensions.Protocol adds the __protocol_attrs__ attribute and causes the mismatch. I did notice however that typeguard has provisions to ignore private attributes of the typing.Protocol class:

  
    
      typeguard/src/typeguard/_checkers.py
    
    
        Lines 668 to 671
      in
      016f813
    
  
  
    

        
          
           ignored_attrs = set(dir(typing.Protocol)) | { 
        

        
          
               "__annotations__", 
        

        
          
               "__non_callable_proto_members__", 
        

        
          
           } 
        
    
  


So I was thinking this might be extended to support typing_extension.Protocol? If not, it would be great if we had some way to inject custom attributes we'd like typeguard to ignore.

Duplicate issue #483: Return value (method) is not compatible with the P protocol because it has no attribute named '__weakref__'
Created: 2024-09-08T00:38:18Z
Things to check first


 I have searched the existing issues and didn't find my bug already reported there


 I have checked that my bug is still present in the latest release


Typeguard version
4.3.0
Python version
3.12.3
What happened?
Traceback (most recent call last):
  File "/.../test.py", line 3, in <module>
    import A
  File "<frozen importlib._bootstrap>", line 1360, in _find_and_load
  File "<frozen importlib._bootstrap>", line 1331, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 935, in _load_unlocked
  File "/home/.../.local/lib/python3.12/site-packages/typeguard/_importhook.py", line 98, in exec_module
    super().exec_module(module)
  File "/.../A.py", line 13, in <module>
    print(A().g()())
          ^^^^^^^
  File "/.../A.py", line 11, in g
    return self.f
  File "/home/.../.local/lib/python3.12/site-packages/typeguard/_functions.py", line 165, in check_return_type
    check_type_internal(retval, annotation, memo)
  File "/home/.../.local/lib/python3.12/site-packages/typeguard/_checkers.py", line 861, in check_type_internal
    checker(value, origin_type, args, memo)
  File "/home/.../.local/lib/python3.12/site-packages/typeguard/_checkers.py", line 738, in check_protocol
    raise TypeCheckError(
typeguard.TypeCheckError: the return value (method) is not compatible with the P protocol because it has no attribute named '__weakref__'

How can we reproduce the bug?
test.py:
from typeguard import install_import_hook
with install_import_hook(('A',)):
    import A
A.py:
from typing import Protocol

class P(Protocol):
    def __call__(self) -> str: ...

class A:
    def f(self) -> str:
        return "OK"

    def g(self) -> P:
        return self.f

print(A().g()())
$ python3 A.py
OK

$ python3 test.py
<crash>

Closing comment (2024-09-08T12:41:17Z): Duplicate of #479.