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

Try to re-add stubs for ctypes #1906

Merged
merged 32 commits into from
Mar 17, 2018
Merged

Conversation

dgelessus
Copy link
Contributor

Two years ago, some stubs for ctypes were added (#454) but removed soon afterwards, because they caused problems with correct code (#475). As I understand it, this was in part because of limitations in mypy at the time. Some of these limitations don't seem to exist anymore, so I tried to make the ctypes stubs work again.

I took the code in its last state before it was removed and tried to fix the issues reported in #475, as well as a few issues that I came across myself when using the stubs with my own code. Details on what exactly I've changed can be found in the individual commit messages. I'm sure there are still issues that I haven't noticed, so if anyone has a ctypes-using codebase and could try out these stubs, I would be happy to get some feedback.

Finally, I'd like to note that I don't have a lot of experience with mypy and writing typing stubs. Previously I've type-annotated some bits of my code and used typeshed through PyCharm's typechecker, but other than that this is my first time properly using mypy and typing. So if there's anything that I'm doing incorrectly or not according to convention, please let me know! 😃

Both Structure's metaclass and Structure itself define a __getattr__.
The former returns field objects (each representing a field definition
in the Structure), while the latter can return anything (the value of
the field in a specific Structure instance).
Structure also defines a __setattr__, while Structure's metaclass
does not.

BigEndianStructure, LittleEndianStructure and Union support the same
operations as Structure does, but the semantics obviously differ.

Depending on the system endianness, exactly one of the EndianStructures
is an alias for Structure, and the other one is a special Structure
subclass. For simplicity, both EndianStructures are considered
subclasses of Structure here, even though this is technically not
always accurate.
The classmethods return *instances* of the class of course, not the
class itself.
It's not possible to specify the correct return type for __mul__ and
__rmul__ here - see comments for explanation.
Copy link
Member

@JelleZijlstra JelleZijlstra left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for adding this, and sorry for the wait before the review!

I left a couple of comments.

Any, Callable, ClassVar, Iterable, List, Mapping, Optional, Sequence, Sized, Tuple, Type,
Generic, TypeVar, overload,
)
from typing import Union as UnionT
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should name this _UnionT so it doesn't get re-exported. (With the current definition, mypy would let users write ctypes.UnionT.)

class _DLL:
def __init__(self, name: str, mode: int = ..., handle: Optional[int] = ...,
use_errno: bool = ..., use_last_error: bool = ...) -> None: ...
def __getattr__(self, name: str) -> Any: ...
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like there's also __getitem__.

class PyDLL(_DLL):
_handle: int = ...
_name: str = ...
def __init__(self, name: str, mode: int = ...,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't the __init__ just inherited from the base class? https://github.com/python/cpython/blob/2.7/Lib/ctypes/__init__.py#L389

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Technically yes, but the documentation lists the constructor as not accepting the use_errno and use_last_error kwargs. I don't know what the correct procedure is in cases like this - is the documentation or the code right?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd generally favor the code.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, I've removed the __init__ from PyDLL, it should now inherit the one from CDLL again.


class LibraryLoader(Generic[_DLLT]):
def __init__(self, dlltype: Type[_DLLT]) -> None: ...
def LoadLibrary(self, name: str) -> _DLLT: ...
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing __getattr__ and __getitem__.

def create_string_buffer(init_or_size: UnionT[int, bytes],
size: Optional[int] = ...) -> Array[c_char]: ...
c_buffer = create_string_buffer
def create_unicode_buffer(init_or_size: UnionT[int, str],
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be Text for Python 2. The actual code checks for (str, unicode).


class c_byte(_SimpleCData[int]): ...

class c_char(_SimpleCData[bytes]): ...
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The __init__ argument here can also be an int.

class c_byte(_SimpleCData[int]): ...

class c_char(_SimpleCData[bytes]): ...
class c_char_p(_PointerLike, _SimpleCData[Optional[bytes]]):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is it Optional? If the value is inaccessible, it just segfaults (as I just found out experimentally).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not here - the value of a null pointer is None.

Python 3.6.4 (v3.6.4:d48ecebad5, Dec 18 2017, 21:07:28) 
[GCC 4.2.1 (Apple Inc. build 5666) (dot 3)] on darwin
>>> import ctypes
>>> ccp = ctypes.c_char_p(None)
>>> ccp.value
>>> cwp = ctypes.c_wchar_p(None)
>>> cwp.value


class c_void_p(_PointerLike, _SimpleCData[Optional[int]]): ...

class c_wchar(_SimpleCData[str]): ...
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be Text?

if sys.platform == 'win32':
class HRESULT(_SimpleCData[Any]): ... # TODO undocumented

class py_object(Generic[_T], _SimpleCData[_T]): ...
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't need the explicit Generic base class.

@dgelessus
Copy link
Contributor Author

OK, should all be fixed (along with a couple of other issues that I noticed/remembered).

@JelleZijlstra JelleZijlstra merged commit 8009a34 into python:master Mar 17, 2018
@classmethod
def from_buffer(cls: Type[_CT], source: bytearray, offset: int = ...) -> _CT: ...
@classmethod
def from_buffer_copy(cls: Type[_CT], source: bytearray, offset: int = ...) -> _CT: ...
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this be bytes not bytearray?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both of the bytearray types are inadequate actually - from_buffer_copy accepts any object that implements the buffer protocol, and similarly from_buffer accepts any writable buffer. bytearray and bytes are common examples of read-write and read-only buffer objects, but there are others like memoryview and array.array. All ctypes data objects (_CData subclasses) are writable buffers as well.

Does typeshed/typing have a type for "anything implementing the buffer protocol"? If not, we probably need to use Unions and list the most common stdlib buffer types.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can define a new protocol. I think there are already many examples in typeshed (grep for Protocol).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, it is C-level, in this case looks like a big union is the only option.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't bytes already treated by mypy as anything implementing the buffer protocol?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is what the typing documentation says:

This type [ByteString] represents the types bytes, bytearray, and memoryview.

As a shorthand for this type, bytes can be used to annotate arguments of any of the types mentioned above.

This should probably be added to PEP 484.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fix here: #2610

dgelessus added a commit to dgelessus/typeshed that referenced this pull request Nov 20, 2018
JelleZijlstra pushed a commit that referenced this pull request Nov 20, 2018
yedpodtrzitko pushed a commit to yedpodtrzitko/typeshed that referenced this pull request Jan 23, 2019
* Convert type comments to variable annotations in ctypes.__init__

* Add missing List import in ctypes.__init__

* Add missing bound kwarg to _CT TypeVar in ctypes.__init__

* Fix ctypes._CData classmethod definitions

* Fix ctypes Structure/Union __getattr__ definitions

Both Structure's metaclass and Structure itself define a __getattr__.
The former returns field objects (each representing a field definition
in the Structure), while the latter can return anything (the value of
the field in a specific Structure instance).
Structure also defines a __setattr__, while Structure's metaclass
does not.

BigEndianStructure, LittleEndianStructure and Union support the same
operations as Structure does, but the semantics obviously differ.

Depending on the system endianness, exactly one of the EndianStructures
is an alias for Structure, and the other one is a special Structure
subclass. For simplicity, both EndianStructures are considered
subclasses of Structure here, even though this is technically not
always accurate.

* Fix swapped parameter types in ctypes._CData.in_dll

* Add limited support for ctypes._CData.__class__.__mul__

It's not possible to specify the correct return type for __mul__ and
__rmul__ here - see comments for explanation.

* Make ctypes._FuncPtr extend ctypes._CData

* Improve typing of ctypes.cast

* Mark class attributes with ClassVar in ctypes.__init__

* Mark ctypes._CData.from_buffer[_copy] offset arg as optional

* Remove trailing whitespace in ctypes.__init__

* Don't export ctypes.UnionT

* Add ctypes._DLL.__getitem__

* Make ctypes._DLL.__get(attr|item)__ return _FuncPtr instead of Any

* Change ctypes DLL inheritance hierarchy to match the real one better

* Add some missing attributes to ctypes.CDLL

* Rename ctypes._FuncPtr so it doesn't conflict with CDLL._FuncPtr

* Fix type of ctypes.CDLL._FuncPtr

* Merge _FuncProto into _FuncPointer

The function pointer types returned by CFUNCTYPE and friends are the
same as those encountered elsewhere, so there's no need to treat them
differently.

* Fix some leftover references to ctypes._DLL

* Simplify definition of ctypes._DLLT

* Add ctypes.LibraryLoader.__get(attr|item)__

* Use Text instead of str where appropriate in ctypes.__init__

* Make ctypes.c_char accept ints

* Make ctypes.c_[w]char_p accept None

* Remove unneeded Generic base from ctypes.py_object

* Make ctypes.cast accept _cparam

* Fix ctypes._PF being declared too late

* Remove incorrect ctypes.PyDLL.__init__ override
yedpodtrzitko pushed a commit to yedpodtrzitko/typeshed that referenced this pull request Jan 23, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants