skip to Main Content

I have the following python snippet that is generating MyPy "problems" (in vscode).

my_struct = MyStruct()    
#! set mutable flag to true to place data in our object.
fcntl.ioctl( dev_hand.fileno(), my_ioctl_id, my_struct, True )

The error is:

Argument 3 to "ioctl" has incompatible type "my_struct"; expected "Union[int, str]"

MyStruct is a ctypes structure. All the examples for using ioctl() with ctypes structures show passing the instance to ioctl(). Indeed this does work, except now MyPy is complaining.

I’d prefer not to convert to bytes and manually pack/unpack with the struct module (which I presume is one solution).

I’m using Python 3.7.3 on Linux (Debian Buster), with mypy 0.782

Thanks, Brendan.


NOTE: I forgot to mention that my code is targeting Python 2.7, as it is legacy from a Debian Jessie target system. I am using the --py2 switch for mypy (which must run on Python 3).

The ioctl() function has the following signature, which seems to come from the vscode server (remote ssh) ms-python …. typeshed/stdlib/3/fcntl.pyi`

def ioctl(fd: _AnyFile,
          request: int,
          arg: Union[int, bytes] = ...,
          mutate_flag: bool = ...) -> Any: ...

Here is a more complete code example.

from typing import ( BinaryIO, )

import ioctl
import fcntl

from ctypes import ( c_uint32, Structure, addressof )

class Point ( Structure ) :
    _fields_ = [ ( 'x', c_uint32 ), ( 'y', c_uint32 ) ]

def ioctl_get_point (
        dev_hand,
        ) :
    point = Point()
    fcntl.ioctl( dev_hand, 0x12345678, point, True )   #! ** MyPy does NOT complain at all **

def ioctl_get_point_2 (
        dev_hand,               # type: BinaryIO
        ) :
    point = Point()
    fcntl.ioctl( dev_hand, 0x12345678, point, True )   #! ** MyPy complains about arg 3 **
    return point

def ioctl_get_point_3 (
        dev_hand,
        ) :                     # type: (...) -> Point
    point = Point()
    fcntl.ioctl( dev_hand, 0x12345678, point, True )   #! ** MyPy complains about arg 3 **
    return point

def ioctl_get_point_4 (
        dev_hand,               # type: BinaryIO
        ) :                     # type: (...) -> Point
    point = Point()
    fcntl.ioctl( dev_hand, 0x12345678, point, True )   #! ** MyPy complains about arg 3 **
    return point

def ioctl_get_point_5 (
        dev_hand,               # type: BinaryIO
        ) :                     # type: (...) -> Point
    point = Point()
    fcntl.ioctl( dev_hand, 0x12345678, addressof( point ), True )   #! ** MyPy does NOT complain at all **
    return point

To me, it seems like using the ctypes.addressof() function, that @CristiFati suggested, is the simplest solution.

Unfortunately that doesn’t work. The ioctl() function needs to know the size of the object.

Thanks, Brendan.

2

Answers


  1. mypy follows the specs of the fnctl.ioctl function here:

    The parameter arg can be one of an integer, an object supporting the read-only buffer interface (like bytes) or an object supporting the read-write buffer interface (like bytearray).

    The complaint is thus a legitimate one.

    I’d prefer not to convert to bytes and manually pack/unpack with the struct module

    With the help of the TYPE_CHECKING constant, you can introduce a local stub with a type hint for fnctl.ioctl that will override stdlib’s type hint:

    import ctypes
    from typing import TYPE_CHECKING
    
    
    class MyStruct(ctypes.Structure):
        _fields_ = [...]
    
    
    if TYPE_CHECKING:  # this is only processed by mypy
        from typing import Protocol, Union, TypeVar
    
        class HasFileno(Protocol):
            def fileno(self) -> int: ...
    
        FileDescriptorLike = Union[int, HasFileno]
    
        _S = TypeVar('_S', bound=ctypes.Structure)
    
        def ioctl(__fd: FileDescriptorLike, __request: int, __arg: Union[int, bytes, _S] = ..., __mutate_flag: bool = ...) -> int: ...
    
    else:  # this will be executed at runtime and ignored by mypy
        from fcntl import ioctl
    
    
    my_struct = MyStruct(...)
    my_ioctl_id = ...
    dev_hand = ...
    
    ioctl(dev_hand.fileno(), my_ioctl_id, my_struct, True)  # mypy won't complain here anymore
    
    Login or Signup to reply.
  2. First, that error message looks like a Python 2 mypy error message, not a Python 3 mypy error message. The stubs for Python 3 have a declaration of fcntl.ioctl that doesn’t match that error message. (You’d still get an error message with Python 3 mypy, but it’d be a different message.)

    Second, fcntl.ioctl accepts any object that supports the buffer interface (including your struct), but mypy has no idea what the buffer interface even is. There is no annotation for an object that supports the buffer interface, and no way to statically recognize objects that support the buffer interface. It is currently impossible to correctly annotate functions like fcntl.ioctl. There are open issues about this, but no resolution in sight.

    Your best bet may be to slap a # type: ignore comment on that line.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search