Source code for fancytypes.ctypes

# -*- coding: utf-8 -*-
"""
Structures and unions
=====================

User defined types are created by **decorating a class that stores the 
corresponding member annotations**. Each of the types, :code:`struct` and 
:code:`union`, has its own decorator function, :py:func:`@cstruct<cstruct>` and 
:py:func:`@cunion<cunion>`, respectively.

.. autodecorator:: cstruct
.. autodecorator:: cunion

Structure and union types with :code:`wrapped=True` have a corresponding 
:code:`numpy.dtype` and **can be used as a datatype in NumPy arrays**. Pointer 
types are first converted to :code:`ctypes.c_void_p`, which NumPy interprets as 
unsigned integers. This conversion does not affect the actual structure or 
union type.
"""



from ctypes import (c_float, c_double, pointer as pointer_, cast as cast_,
                    Structure, Union, c_void_p)
import functools


from numpy import dtype


from fancytypes._types import FancyType



def _numpy_type(typ):
    '''Handle types to create NumPy compliant structures.
    '''
    
    # It is a pointer type, NumPy sees uint64 (64-bit machines)
    if hasattr(typ, 'contents'):
        return c_void_p
    
    # Is a structured type, recursively do it just in case it has pointers
    elif hasattr(typ, '_fields_'):
        numpy_fields = [(var_, _numpy_type(typ_))
                                                for var_, typ_ in typ._fields_]
        return dtype(numpy_fields, align=True)
    
    # Is a basic type, just return it
    else:
        return typ
    

def _build_structured_type(cls, base, alias, wrapped):
    '''Wrap custom structured types. Make them NumPy friendly as well by 
    declaring all pointer types as void pointers, which NumPy interprets as 
    integers of machine address size.
    '''
    
    fields = [(var, typ._ctype_)
                          if hasattr(typ, '_ctype_') else (var, typ)
                                  for var, typ in cls.__annotations__.items()]
    
    ctypes_type = type('struct', (base, *cls.__bases__), {'_fields_' : fields})
    functools.update_wrapper(ctypes_type, cls, updated=())
    
    if not wrapped:
        return ctypes_type
    
    type_name = f'{cls.__name__}_{base.__name__}'
    
    numpy_fields = [(var, _numpy_type(typ))
                                        for var, typ in ctypes_type._fields_]
    
    dict_ = {
        '_ctype_' : ctypes_type,
        '_numpy_' : dtype(numpy_fields, align=True),
        '_alias_' : alias if alias else f'{base.__name__}({repr(fields)})',
        }
    
    new_type = type(type_name, (FancyType,), dict_)
    
    return new_type
    


[docs] def cstruct(cls=None, /, *, alias=None, wrapped=True): '''Class decorator that returns a C struct class that inherits from :py:class:`ctypes.Structure`. The decorated class must **annotate the struct members with valid types**. .. code-block:: <name> : <type> The :code:`type` field takes all types from the :ref:`basic types <basic>` section, plus all pointer and array types derived from them. User defined types are also valid, together with `ctypes types <https://docs.python.org/3/library/ctypes.html#fundamental-data-types>`_. .. code-block:: python import fancytypes as ft # 128-bit complex number as defined by the C99 standard @ft.cstruct class complex128: real : ft.real64 # Real part imag : ft.real64 # Imaginary part # Sample struct that can store an N-dimensional array @ft.cstruct class ndarray: data : ft.pointer(ft.real64) # Pointer to the first data array element dim : ft.pointer(ft.int32) # Pointer to the first dimension array element ndim : ft.int32 # Number of dimensions :param alias: User defined alias for the type, default is an explicit list with the fields :type alias: str, optional :param wrapped: Flag to wrap the type or return a :code:`ctypes` class, default is :code:`True` :type wrapped: bool, optional ''' def build(cls): return _build_structured_type(cls, Structure, alias, wrapped) if cls is None: return build return build(cls)
[docs] def cunion(cls=None, /, *, alias=None, wrapped=True): '''Class decorator that returns a C union class that inherits from :py:class:`ctypes.Union`. Union members are annotated following the same rules described in :py:func:`@cstruct<cstruct>`. :param alias: User defined alias for the type, default is an explicit list with the fields :type alias: str, optional :param wrapped: Flag to wrap the type or return a :code:`ctypes` class, default is :code:`True` :type wrapped: bool, optional ''' def build(cls): return _build_structured_type(cls, Union, alias, wrapped) if cls is None: return build return build(cls)
@cstruct(wrapped=False) class single_complex: '''Single precision complex number. ''' real : c_float imag : c_float @cstruct(wrapped=False) class double_complex: '''Double precision complex number. ''' real : c_double imag : c_double def cpointer(var): '''Get a pointer to a :py:mod:`ctypes` instance. :param var: Instance of ctypes type :type var: :py:mod:`ctypes` instance :return: Pointer to a ctypes instance. :rtype: :py:mod:`ctypes` instance of pointer type ''' return pointer_(var) def cast(var, typ): '''Cast pointer instance into another pointer type. :param var: Pointer instance of ctypes type :type var: :py:mod:`ctypes` instance :param typ: Target pointer class :type typ: :py:mod:`fancytypes` type :return: Pointer to a ctypes instance :rtype: :py:mod:`ctypes` instance of pointer type ''' if not hasattr(typ, '_ctype_'): errmsg = 'fancytypes.cast must receive a fancytypes type' raise TypeError(errmsg) new_var = cast_(var, typ._ctype_) return new_var