Prettier Struct Definitions for Python CTypes

I was writing some Python code that uses [ctypes][] for interfacing with the [Windows ToolHelp API]( Specifically, I had to [define a Python equivalent][structs] for the [PROCESSENTRY32]( struct. Now, the [ctypes][] [struct definitions][structs] are a little annoying because they do not give you type hints. You can add the type hints _on top_ of the _fields_ attribute but that looks a little silly because you _literally_ [write everything twice][wet]. In Python 3.7+1, you can solve this using a metaclass, and one stigma of metaclasses is that they have very few applications, so I decided to blog about this one:
class FieldsFromTypeHints(type(ctypes.Structure)):
    def __new__(cls, name, bases, namespace):
        from typing import get_type_hints
        class AnnotationDummy:
            __annotations__ = namespace.get('__annotations__', {})
        annotations = get_type_hints(AnnotationDummy)
        namespace['_fields_'] = list(annotations.items())
        return type(ctypes.Structure).__new__(cls, name, bases, namespace)
and now you can write:
class PROCESSENTRY32(ctypes.Structure, metaclass=FieldsFromTypeHints):
    dwSize              : ctypes.c_uint32
    cntUsage            : ctypes.c_uint32
    th32ProcessID       : ctypes.c_uint32
    th32DefaultHeapID   : ctypes.POINTER(ctypes.c_ulong)
    th32ModuleID        : ctypes.c_uint32
    cntThreads          : ctypes.c_uint32
    th32ParentProcessID : ctypes.c_uint32
    pcPriClassBase      : ctypes.c_long
    dwFlags             : ctypes.c_uint32
    szExeFile           : ctypes.c_char * 260
The metaclass simply deduces the _fields_ attribute of the new class for you before the class is _even created_. It might make sense to think of metaclasses as "class creation hooks", i.e. you get to modify what you have written before the class is actually being defined. The following bit is just to be compatible with [PEP-563](
        from typing import get_type_hints
        class AnnotationDummy:
            __annotations__ = namespace.get('__annotations__', {})
        annotations = get_type_hints(AnnotationDummy)
And then, the following is the actual magic line:
        namespace['_fields_'] = list(annotations.items())
This adds the attribute _fields_ before the class is created.

The Code

Since you clicked, I’ll share with you the code that comes next:

def get_parent_processes():
    k32 = ctypes.windll.kernel32
    entry = PROCESSENTRY32()
    entry.dwSize = ctypes.sizeof(PROCESSENTRY32)
    snap = k32.CreateToolhelp32Snapshot(2, 0)
    if not snap:
        raise RuntimeError('could not create snapshot')
        if not k32.Process32First(snap, ctypes.byref(entry)):
            raise RuntimeError('could not iterate processes')
        processes = {
            entry.th32ProcessID: (
            ) for _ in iter(
                functools.partial(k32.Process32Next, snap, ctypes.byref(entry)), 0)
    pid = os.getpid()
    while pid in processes:
        pid, path = processes[pid]
        yield path

This code gives me a list of the executable names of the process ancestors of the current Python process. I needed this because I had to figure out whether I am running inside PowerShell or inside a Command Prompt. Why, you ask? In essence, because #1908 and some other shortcomings are turning my binary refinery toolkit into a complete shit show under PowerShell, and I am currently looking for a workaround that I can implement on my end.

  1. Starting in Python 3.7, dictionaries keep the insertion order.

Article Link: Prettier Struct Definitions for Python CTypes – nullteilerfrei