From c98af69d1b20f3398b3b1ab7ad9ddb9e750749ba Mon Sep 17 00:00:00 2001 From: Jan Breuer Date: Fri, 20 Mar 2026 15:21:54 +0100 Subject: [PATCH 1/7] Add multiple C-API version infrastructure and C-API v1.1.0a.1 --- cadet/cadet_dll.py | 202 +++++++++++++++++++++++++++------------------ 1 file changed, 122 insertions(+), 80 deletions(-) diff --git a/cadet/cadet_dll.py b/cadet/cadet_dll.py index 162e87f..2f84341 100644 --- a/cadet/cadet_dll.py +++ b/cadet/cadet_dll.py @@ -4,6 +4,7 @@ from pathlib import Path from typing import Any, Optional +from typing import Union import addict import numpy @@ -26,7 +27,7 @@ _CDT_DATA_NOT_STORED = -3 -class CADETAPIV010000_DATA: +class CADET_API_V1_SIGNATURES: """ Definition of CADET-C-API v1.0 function signatures and type mappings. @@ -42,65 +43,68 @@ class CADETAPIV010000_DATA: # API function signatures # Note, order is important, it has to match the cdtAPIv010000 struct of the C-API - signatures = {} - - signatures['getFileFormat'] = ('return', 'fileFormat') - - signatures['createDriver'] = ('drv',) - signatures['deleteDriver'] = (None, 'drv') - signatures['runSimulation'] = ('return', 'drv', 'parameterProvider') - - signatures['getNumUnitOp'] = ('return', 'drv', 'nUnits') - signatures['getNumParTypes'] = ('return', 'drv', 'unitOpId', 'nParTypes') - signatures['getNumSensitivities'] = ('return', 'drv', 'nSens') - - signatures['getSolutionInlet'] = ('return', 'drv', 'unitOpId', 'time', 'data', 'nTime', 'nPort', 'nComp') - signatures['getSolutionOutlet'] = ('return', 'drv', 'unitOpId', 'time', 'data', 'nTime', 'nPort', 'nComp') - signatures['getSolutionBulk'] = ('return', 'drv', 'unitOpId', 'time', 'data', 'nTime', 'nAxialCells', 'nRadialCells', 'nComp', 'keepAxialSingletonDimension') - signatures['getSolutionParticle'] = ('return', 'drv', 'unitOpId', 'parType', 'time', 'data', 'nTime', 'nAxialCells', 'nRadialCells', 'nParShells', 'nComp', 'keepAxialSingletonDimension', 'keepParticleSingletonDimension') - signatures['getSolutionSolid'] = ('return', 'drv', 'unitOpId', 'parType', 'time', 'data', 'nTime', 'nAxialCells', 'nRadialCells', 'nParShells', 'nBound', 'keepAxialSingletonDimension', 'keepParticleSingletonDimension') - signatures['getSolutionFlux'] = ('return', 'drv', 'unitOpId', 'time', 'data', 'nTime', 'nAxialCells', 'nRadialCells', 'nParTypes', 'nComp', 'keepAxialSingletonDimension') - signatures['getSolutionVolume'] = ('return', 'drv', 'unitOpId', 'time', 'data', 'nTime') - - signatures['getSolutionDerivativeInlet'] = ('return', 'drv', 'unitOpId', 'time', 'data', 'nTime', 'nPort', 'nComp') - signatures['getSolutionDerivativeOutlet'] = ('return', 'drv', 'unitOpId', 'time', 'data', 'nTime', 'nPort', 'nComp') - signatures['getSolutionDerivativeBulk'] = ('return', 'drv', 'unitOpId', 'time', 'data', 'nTime', 'nAxialCells', 'nRadialCells', 'nComp', 'keepAxialSingletonDimension') - signatures['getSolutionDerivativeParticle'] = ('return', 'drv', 'unitOpId', 'parType', 'time', 'data', 'nTime', 'nAxialCells', 'nRadialCells', 'nParShells', 'nComp', 'keepAxialSingletonDimension', 'keepParticleSingletonDimension') - signatures['getSolutionDerivativeSolid'] = ('return', 'drv', 'unitOpId', 'parType', 'time', 'data', 'nTime', 'nAxialCells', 'nRadialCells', 'nParShells', 'nBound', 'keepAxialSingletonDimension', 'keepParticleSingletonDimension') - signatures['getSolutionDerivativeFlux'] = ('return', 'drv', 'unitOpId', 'time', 'data', 'nTime', 'nAxialCells', 'nRadialCells', 'nParTypes', 'nComp', 'keepAxialSingletonDimension') - signatures['getSolutionDerivativeVolume'] = ('return', 'drv', 'unitOpId', 'time', 'data', 'nTime') - - signatures['getSensitivityInlet'] = ('return', 'drv', 'unitOpId', 'sensIdx', 'time', 'data', 'nTime', 'nPort', 'nComp') - signatures['getSensitivityOutlet'] = ('return', 'drv', 'unitOpId', 'sensIdx', 'time', 'data', 'nTime', 'nPort', 'nComp') - signatures['getSensitivityBulk'] = ('return', 'drv', 'unitOpId', 'sensIdx', 'time', 'data', 'nTime', 'nAxialCells', 'nRadialCells', 'nComp', 'keepAxialSingletonDimension') - signatures['getSensitivityParticle'] = ('return', 'drv', 'unitOpId', 'sensIdx', 'parType', 'time', 'data', 'nTime', 'nAxialCells', 'nRadialCells', 'nParShells', 'nComp', 'keepAxialSingletonDimension', 'keepParticleSingletonDimension') - signatures['getSensitivitySolid'] = ('return', 'drv', 'unitOpId', 'sensIdx', 'parType', 'time', 'data', 'nTime', 'nAxialCells', 'nRadialCells', 'nParShells', 'nBound', 'keepAxialSingletonDimension', 'keepParticleSingletonDimension') - signatures['getSensitivityFlux'] = ('return', 'drv', 'unitOpId', 'sensIdx', 'time', 'data', 'nTime', 'nAxialCells', 'nRadialCells', 'nParTypes', 'nComp', 'keepAxialSingletonDimension') - signatures['getSensitivityVolume'] = ('return', 'drv', 'unitOpId', 'sensIdx', 'time', 'data', 'nTime') - - signatures['getSensitivityDerivativeInlet'] = ('return', 'drv', 'unitOpId', 'sensIdx', 'time', 'data', 'nTime', 'nPort', 'nComp') - signatures['getSensitivityDerivativeOutlet'] = ('return', 'drv', 'unitOpId', 'sensIdx', 'time', 'data', 'nTime', 'nPort', 'nComp') - signatures['getSensitivityDerivativeBulk'] = ('return', 'drv', 'unitOpId', 'sensIdx', 'time', 'data', 'nTime', 'nAxialCells', 'nRadialCells', 'nComp', 'keepAxialSingletonDimension') - signatures['getSensitivityDerivativeParticle'] = ('return', 'drv', 'unitOpId', 'sensIdx', 'parType', 'time', 'data', 'nTime', 'nAxialCells', 'nRadialCells', 'nParShells', 'nComp', 'keepAxialSingletonDimension', 'keepParticleSingletonDimension') - signatures['getSensitivityDerivativeSolid'] = ('return', 'drv', 'unitOpId', 'sensIdx', 'parType', 'time', 'data', 'nTime', 'nAxialCells', 'nRadialCells', 'nParShells', 'nBound', 'keepAxialSingletonDimension', 'keepParticleSingletonDimension') - signatures['getSensitivityDerivativeFlux'] = ('return', 'drv', 'unitOpId', 'sensIdx', 'time', 'data', 'nTime', 'nAxialCells', 'nRadialCells', 'nParTypes', 'nComp', 'keepAxialSingletonDimension') - signatures['getSensitivityDerivativeVolume'] = ('return', 'drv', 'unitOpId', 'sensIdx', 'time', 'data', 'nTime') - - signatures['getLastState'] = ('return', 'drv', 'state', 'nStates') - signatures['getLastStateTimeDerivative'] = ('return', 'drv', 'state', 'nStates') - signatures['getLastUnitState'] = ('return', 'drv', 'unitOpId', 'state', 'nStates') - signatures['getLastUnitStateTimeDerivative'] = ('return', 'drv', 'unitOpId', 'state', 'nStates') - signatures['getLastSensitivityState'] = ('return', 'drv', 'sensIdx', 'state', 'nStates') - signatures['getLastSensitivityStateTimeDerivative'] = ('return', 'drv', 'sensIdx', 'state', 'nStates') - signatures['getLastSensitivityUnitState'] = ('return', 'drv', 'sensIdx', 'unitOpId', 'state', 'nStates') - signatures['getLastSensitivityUnitStateTimeDerivative'] = ('return', 'drv', 'sensIdx', 'unitOpId', 'state', 'nStates') - - signatures['getPrimaryCoordinates'] = ('return', 'drv', 'unitOpId', 'coords', 'nCoords') - signatures['getSecondaryCoordinates'] = ('return', 'drv', 'unitOpId', 'coords', 'nCoords') - signatures['getParticleCoordinates'] = ('return', 'drv', 'unitOpId', 'parType', 'coords', 'nCoords') - signatures['getSolutionTimes'] = ('return', 'drv', 'time', 'nTime') - - signatures['getTimeSim'] = ('return', 'drv', 'timeSim') + signatures_1_0_0 = {} + + signatures_1_0_0['getFileFormat'] = ('return', 'fileFormat') + + signatures_1_0_0['createDriver'] = ('drv',) + signatures_1_0_0['deleteDriver'] = (None, 'drv') + signatures_1_0_0['runSimulation'] = ('return', 'drv', 'parameterProvider') + + signatures_1_0_0['getNumUnitOp'] = ('return', 'drv', 'nUnits') + signatures_1_0_0['getNumParTypes'] = ('return', 'drv', 'unitOpId', 'nParTypes') + signatures_1_0_0['getNumSensitivities'] = ('return', 'drv', 'nSens') + + signatures_1_0_0['getSolutionInlet'] = ('return', 'drv', 'unitOpId', 'time', 'data', 'nTime', 'nPort', 'nComp') + signatures_1_0_0['getSolutionOutlet'] = ('return', 'drv', 'unitOpId', 'time', 'data', 'nTime', 'nPort', 'nComp') + signatures_1_0_0['getSolutionBulk'] = ('return', 'drv', 'unitOpId', 'time', 'data', 'nTime', 'nAxialCells', 'nRadialCells', 'nComp', 'keepAxialSingletonDimension') + signatures_1_0_0['getSolutionParticle'] = ('return', 'drv', 'unitOpId', 'parType', 'time', 'data', 'nTime', 'nAxialCells', 'nRadialCells', 'nParShells', 'nComp', 'keepAxialSingletonDimension', 'keepParticleSingletonDimension') + signatures_1_0_0['getSolutionSolid'] = ('return', 'drv', 'unitOpId', 'parType', 'time', 'data', 'nTime', 'nAxialCells', 'nRadialCells', 'nParShells', 'nBound', 'keepAxialSingletonDimension', 'keepParticleSingletonDimension') + signatures_1_0_0['getSolutionFlux'] = ('return', 'drv', 'unitOpId', 'time', 'data', 'nTime', 'nAxialCells', 'nRadialCells', 'nParTypes', 'nComp', 'keepAxialSingletonDimension') + signatures_1_0_0['getSolutionVolume'] = ('return', 'drv', 'unitOpId', 'time', 'data', 'nTime') + + signatures_1_0_0['getSolutionDerivativeInlet'] = ('return', 'drv', 'unitOpId', 'time', 'data', 'nTime', 'nPort', 'nComp') + signatures_1_0_0['getSolutionDerivativeOutlet'] = ('return', 'drv', 'unitOpId', 'time', 'data', 'nTime', 'nPort', 'nComp') + signatures_1_0_0['getSolutionDerivativeBulk'] = ('return', 'drv', 'unitOpId', 'time', 'data', 'nTime', 'nAxialCells', 'nRadialCells', 'nComp', 'keepAxialSingletonDimension') + signatures_1_0_0['getSolutionDerivativeParticle'] = ('return', 'drv', 'unitOpId', 'parType', 'time', 'data', 'nTime', 'nAxialCells', 'nRadialCells', 'nParShells', 'nComp', 'keepAxialSingletonDimension', 'keepParticleSingletonDimension') + signatures_1_0_0['getSolutionDerivativeSolid'] = ('return', 'drv', 'unitOpId', 'parType', 'time', 'data', 'nTime', 'nAxialCells', 'nRadialCells', 'nParShells', 'nBound', 'keepAxialSingletonDimension', 'keepParticleSingletonDimension') + signatures_1_0_0['getSolutionDerivativeFlux'] = ('return', 'drv', 'unitOpId', 'time', 'data', 'nTime', 'nAxialCells', 'nRadialCells', 'nParTypes', 'nComp', 'keepAxialSingletonDimension') + signatures_1_0_0['getSolutionDerivativeVolume'] = ('return', 'drv', 'unitOpId', 'time', 'data', 'nTime') + + signatures_1_0_0['getSensitivityInlet'] = ('return', 'drv', 'unitOpId', 'sensIdx', 'time', 'data', 'nTime', 'nPort', 'nComp') + signatures_1_0_0['getSensitivityOutlet'] = ('return', 'drv', 'unitOpId', 'sensIdx', 'time', 'data', 'nTime', 'nPort', 'nComp') + signatures_1_0_0['getSensitivityBulk'] = ('return', 'drv', 'unitOpId', 'sensIdx', 'time', 'data', 'nTime', 'nAxialCells', 'nRadialCells', 'nComp', 'keepAxialSingletonDimension') + signatures_1_0_0['getSensitivityParticle'] = ('return', 'drv', 'unitOpId', 'sensIdx', 'parType', 'time', 'data', 'nTime', 'nAxialCells', 'nRadialCells', 'nParShells', 'nComp', 'keepAxialSingletonDimension', 'keepParticleSingletonDimension') + signatures_1_0_0['getSensitivitySolid'] = ('return', 'drv', 'unitOpId', 'sensIdx', 'parType', 'time', 'data', 'nTime', 'nAxialCells', 'nRadialCells', 'nParShells', 'nBound', 'keepAxialSingletonDimension', 'keepParticleSingletonDimension') + signatures_1_0_0['getSensitivityFlux'] = ('return', 'drv', 'unitOpId', 'sensIdx', 'time', 'data', 'nTime', 'nAxialCells', 'nRadialCells', 'nParTypes', 'nComp', 'keepAxialSingletonDimension') + signatures_1_0_0['getSensitivityVolume'] = ('return', 'drv', 'unitOpId', 'sensIdx', 'time', 'data', 'nTime') + + signatures_1_0_0['getSensitivityDerivativeInlet'] = ('return', 'drv', 'unitOpId', 'sensIdx', 'time', 'data', 'nTime', 'nPort', 'nComp') + signatures_1_0_0['getSensitivityDerivativeOutlet'] = ('return', 'drv', 'unitOpId', 'sensIdx', 'time', 'data', 'nTime', 'nPort', 'nComp') + signatures_1_0_0['getSensitivityDerivativeBulk'] = ('return', 'drv', 'unitOpId', 'sensIdx', 'time', 'data', 'nTime', 'nAxialCells', 'nRadialCells', 'nComp', 'keepAxialSingletonDimension') + signatures_1_0_0['getSensitivityDerivativeParticle'] = ('return', 'drv', 'unitOpId', 'sensIdx', 'parType', 'time', 'data', 'nTime', 'nAxialCells', 'nRadialCells', 'nParShells', 'nComp', 'keepAxialSingletonDimension', 'keepParticleSingletonDimension') + signatures_1_0_0['getSensitivityDerivativeSolid'] = ('return', 'drv', 'unitOpId', 'sensIdx', 'parType', 'time', 'data', 'nTime', 'nAxialCells', 'nRadialCells', 'nParShells', 'nBound', 'keepAxialSingletonDimension', 'keepParticleSingletonDimension') + signatures_1_0_0['getSensitivityDerivativeFlux'] = ('return', 'drv', 'unitOpId', 'sensIdx', 'time', 'data', 'nTime', 'nAxialCells', 'nRadialCells', 'nParTypes', 'nComp', 'keepAxialSingletonDimension') + signatures_1_0_0['getSensitivityDerivativeVolume'] = ('return', 'drv', 'unitOpId', 'sensIdx', 'time', 'data', 'nTime') + + signatures_1_0_0['getLastState'] = ('return', 'drv', 'state', 'nStates') + signatures_1_0_0['getLastStateTimeDerivative'] = ('return', 'drv', 'state', 'nStates') + signatures_1_0_0['getLastUnitState'] = ('return', 'drv', 'unitOpId', 'state', 'nStates') + signatures_1_0_0['getLastUnitStateTimeDerivative'] = ('return', 'drv', 'unitOpId', 'state', 'nStates') + signatures_1_0_0['getLastSensitivityState'] = ('return', 'drv', 'sensIdx', 'state', 'nStates') + signatures_1_0_0['getLastSensitivityStateTimeDerivative'] = ('return', 'drv', 'sensIdx', 'state', 'nStates') + signatures_1_0_0['getLastSensitivityUnitState'] = ('return', 'drv', 'sensIdx', 'unitOpId', 'state', 'nStates') + signatures_1_0_0['getLastSensitivityUnitStateTimeDerivative'] = ('return', 'drv', 'sensIdx', 'unitOpId', 'state', 'nStates') + + signatures_1_0_0['getPrimaryCoordinates'] = ('return', 'drv', 'unitOpId', 'coords', 'nCoords') + signatures_1_0_0['getSecondaryCoordinates'] = ('return', 'drv', 'unitOpId', 'coords', 'nCoords') + signatures_1_0_0['getParticleCoordinates'] = ('return', 'drv', 'unitOpId', 'parType', 'coords', 'nCoords') + signatures_1_0_0['getSolutionTimes'] = ('return', 'drv', 'time', 'nTime') + + signatures_1_0_0['getTimeSim'] = ('return', 'drv', 'timeSim') + + signatures_1_1_0a_1 = {} + signatures_1_1_0a_1['timeout'] = ('return', 'drv', 'timeout') # Mappings for common ctypes parameters lookup_prototype = { @@ -131,6 +135,7 @@ class CADETAPIV010000_DATA: 'keepAxialSingletonDimension': point_bool, 'keepParticleSingletonDimension': point_bool, 'timeSim': point_double, + 'timeout': point_double, } lookup_output_argument_type = { @@ -154,31 +159,57 @@ class CADETAPIV010000_DATA: 'keepAxialSingletonDimension': ctypes.c_bool, 'keepParticleSingletonDimension': ctypes.c_bool, 'timeSim': ctypes.c_double, + 'timeout': ctypes.c_double, } - -def _setup_api() -> list[tuple[str, ctypes.CFUNCTYPE]]: +def _get_api_signatures(api: Any) -> dict[str, tuple[str, ...]]: + if isinstance(api, CADETAPI_V1_1_0a_1): + sigs = dict(CADET_API_V1_SIGNATURES.signatures_1_0_0) + sigs.update(CADET_API_V1_SIGNATURES.signatures_1_1_0a_1) + return sigs + elif isinstance(api, CADETAPI_V1_0_0): + return CADET_API_V1_SIGNATURES.signatures_1_0_0 + else: + raise TypeError(f"Unsupported API type: {type(api).__name__}") + +def _setup_api(signatures: dict[str, tuple[str, ...]]) -> list[tuple[str, ctypes.CFUNCTYPE]]: """ - Set up the API function prototypes for CADETAPIV010000. - - Returns - ------- - list of tuple - List of function names and corresponding ctypes function prototypes. + Set up the API function prototypes for a given CADET API signature table. """ - _fields_ = [] - for key, value in CADETAPIV010000_DATA.signatures.items(): - args = tuple(CADETAPIV010000_DATA.lookup_prototype[key] for key in value) - _fields_.append((key, ctypes.CFUNCTYPE(*args))) + fields = [] + + for name, value in signatures.items(): + args = tuple(CADET_API_V1_SIGNATURES.lookup_prototype[arg_name] for arg_name in value) + fields.append((name, ctypes.CFUNCTYPE(*args))) + + return fields + +SIGNATURES_V1_0_0 = dict(CADET_API_V1_SIGNATURES.signatures_1_0_0) + +SIGNATURES_V1_1_0A_1 = dict(CADET_API_V1_SIGNATURES.signatures_1_0_0) +SIGNATURES_V1_1_0A_1.update(CADET_API_V1_SIGNATURES.signatures_1_1_0a_1) - return _fields_ +class CADETAPI_V1_0_0(ctypes.Structure): + """Mimic cdtAPIv1.0.0 struct of CADET C-API in ctypes.""" + _signatures_ = SIGNATURES_V1_0_0 + _fields_ = _setup_api(_signatures_) -class CADETAPIV010000(ctypes.Structure): - """Mimic cdtAPIv010000 struct of CADET C-API in ctypes.""" - _fields_ = _setup_api() +CADETAPIV010000 = CADETAPI_V1_0_0 + +class CADETAPI_V1_1_0a_1(ctypes.Structure): + """Mimic cdtAPIv1.1.0a.1 struct of CADET C-API in ctypes.""" + _signatures_ = SIGNATURES_V1_1_0A_1 + _fields_ = _setup_api(_signatures_) + + +class CADETAPI_V1_0_0(ctypes.Structure): + """Mimic cdtAPIv1.0.0 struct of CADET C-API in ctypes.""" + _fields_ = _setup_api("1.0.0") + + class SimulationResult: """ Handles reading results from a CADET simulation. @@ -192,7 +223,10 @@ class SimulationResult: """ - def __init__(self, api: CADETAPIV010000, driver: CadetDriver) -> None: + def __init__( + self, + api: Union[CADETAPI_V1_0_0, CADETAPI_V1_1_0a_1], driver: CadetDriver + ) -> None: self._api = api self._driver = driver @@ -228,21 +262,29 @@ def _load_data( call_args = [] call_outputs = {} + signatures = _get_api_signatures(self._api) + # Construct API call function arguments - for key in CADETAPIV010000_DATA.signatures[get_solution_str]: + for key in signatures[get_solution_str]: if key == 'return': # Skip, this is the actual return value of the API function continue elif key == 'drv': call_args.append(self._driver) - elif key == 'unitOpId' and unitOpId is not None: + elif key == 'unitOpId': + if unitOpId is None: + raise ValueError(f"{get_solution_str} requires unitOpId") call_args.append(unitOpId) elif key == 'sensIdx': + if sensIdx is None: + raise ValueError(f"{get_solution_str} requires sensIdx") call_args.append(sensIdx) elif key == 'parType': + if parType is None: + raise ValueError(f"{get_solution_str} requires parType") call_args.append(parType) else: - _obj = CADETAPIV010000_DATA.lookup_output_argument_type[key]() + _obj = CADET_API_V1_SIGNATURES.lookup_output_argument_type[key]() call_outputs[key] = _obj call_args.append(ctypes.byref(_obj)) From a71a6948b812e7d4761406a8b0a26af982c4edb0 Mon Sep 17 00:00:00 2001 From: AntoniaBerger Date: Mon, 30 Mar 2026 17:09:21 +0200 Subject: [PATCH 2/7] Add VERSION_SIGNATURES and update init --- cadet/cadet_dll.py | 66 ++++++++++++++++++++++------------------------ 1 file changed, 32 insertions(+), 34 deletions(-) diff --git a/cadet/cadet_dll.py b/cadet/cadet_dll.py index 2f84341..86c7d63 100644 --- a/cadet/cadet_dll.py +++ b/cadet/cadet_dll.py @@ -162,20 +162,22 @@ class CADET_API_V1_SIGNATURES: 'timeout': ctypes.c_double, } +_VERSION_SIGNATURES: dict[Version, dict] = {} +_VERSION_SIGNATURES[Version("1.0.0")] = dict(CADET_API_V1_SIGNATURES.signatures_1_0_0) + +_sigs_1_1_0a_1 = dict(CADET_API_V1_SIGNATURES.signatures_1_0_0) +_sigs_1_1_0a_1.update(CADET_API_V1_SIGNATURES.signatures_1_1_0a_1) +_VERSION_SIGNATURES[Version("1.1.0a1")] = _sigs_1_1_0a_1 + def _get_api_signatures(api: Any) -> dict[str, tuple[str, ...]]: - if isinstance(api, CADETAPI_V1_1_0a_1): - sigs = dict(CADET_API_V1_SIGNATURES.signatures_1_0_0) - sigs.update(CADET_API_V1_SIGNATURES.signatures_1_1_0a_1) - return sigs - elif isinstance(api, CADETAPI_V1_0_0): - return CADET_API_V1_SIGNATURES.signatures_1_0_0 - else: - raise TypeError(f"Unsupported API type: {type(api).__name__}") + return _VERSION_SIGNATURES[api._version] -def _setup_api(signatures: dict[str, tuple[str, ...]]) -> list[tuple[str, ctypes.CFUNCTYPE]]: +def _setup_api(version: Version) -> list[tuple[str, ctypes.CFUNCTYPE]]: """ Set up the API function prototypes for a given CADET API signature table. """ + + signatures = _VERSION_SIGNATURES[version] fields = [] for name, value in signatures.items(): @@ -184,30 +186,19 @@ def _setup_api(signatures: dict[str, tuple[str, ...]]) -> list[tuple[str, ctypes return fields -SIGNATURES_V1_0_0 = dict(CADET_API_V1_SIGNATURES.signatures_1_0_0) - -SIGNATURES_V1_1_0A_1 = dict(CADET_API_V1_SIGNATURES.signatures_1_0_0) -SIGNATURES_V1_1_0A_1.update(CADET_API_V1_SIGNATURES.signatures_1_1_0a_1) - class CADETAPI_V1_0_0(ctypes.Structure): """Mimic cdtAPIv1.0.0 struct of CADET C-API in ctypes.""" - _signatures_ = SIGNATURES_V1_0_0 - _fields_ = _setup_api(_signatures_) - + _version = Version("1.0.0") + _fields_ = _setup_api(_version) CADETAPIV010000 = CADETAPI_V1_0_0 class CADETAPI_V1_1_0a_1(ctypes.Structure): """Mimic cdtAPIv1.1.0a.1 struct of CADET C-API in ctypes.""" - _signatures_ = SIGNATURES_V1_1_0A_1 - _fields_ = _setup_api(_signatures_) - - -class CADETAPI_V1_0_0(ctypes.Structure): - """Mimic cdtAPIv1.0.0 struct of CADET C-API in ctypes.""" - _fields_ = _setup_api("1.0.0") + _version = Version("1.1.0a1") + _fields_ = _setup_api(_version) class SimulationResult: @@ -1714,23 +1705,35 @@ def _initialize_dll(self): # Query API try: - cdtGetLatestCAPIVersion = self._lib.cdtGetLatestCAPIVersion# + cdtGetLatestCAPIVersion = self._lib.cdtGetLatestCAPIVersion except AttributeError: raise ValueError( "CADET-Python does not support CADET-CAPI at all." ) cdtGetLatestCAPIVersion.restype = ctypes.c_char_p - self._cadet_capi_version = cdtGetLatestCAPIVersion().decode('utf-8') + self._cadet_capi_version = Version(cdtGetLatestCAPIVersion().decode('utf-8')) # Check which C-API is provided by CADET (given the current install path) - if self._cadet_capi_version == "1.0.0": - cdtGetAPIv010000 = self._lib.cdtGetAPIv010000 + #current supported versions are: 1.0.0 and 1.1.0a1 + #TODO: write in developer guide to update LATEST_CAPI_VERSION in VersionInfo.cpp.in (Cadet-Core) + if self._cadet_capi_version == Version("1.1.0a1"): + cdtGetAPIv1_1_0a_1 = self._lib.cdtGetAPIv1_1_0a1 + cdtGetAPIv1_1_0a_1.argtypes = [ctypes.POINTER(CADETAPI_V1_1_0a_1)] + cdtGetAPIv1_1_0a_1.restype = c_cadet_result + self._api = CADETAPI_V1_1_0a_1() + cdtGetAPIv1_1_0a_1(ctypes.byref(self._api)) + elif self._cadet_capi_version < Version("1.1.0a1"): + cdtGetAPIv010000 = self._lib.cdtGetAPIv1_0_0 cdtGetAPIv010000.argtypes = [ctypes.POINTER(CADETAPIV010000)] cdtGetAPIv010000.restype = c_cadet_result self._api = CADETAPIV010000() cdtGetAPIv010000(ctypes.byref(self._api)) + #TODO if version 1.1.0 is realeased + #raise Warning ( + # "A newer C-API version is available you are using the fallback version 1.0.0" + #) else: - raise ValueError( + raise TypeError( "CADET-Python does not support CADET-CAPI version " f"({self._cadet_capi_version})." ) @@ -2017,11 +2020,6 @@ def load_state(self, sim: "Cadet") -> None: soldot_last_unit = self.res.last_state_ydot_unit(unit) solution[unit_index]['last_state_ydot'] = soldot_last_unit - @staticmethod - def _get_index_string(prefix: str, index: int) -> str: - """Get a formatted string index (e.g., ('unit', 0) -> 'unit_000').""" - return f'{prefix}_{index:03d}' - def _checks_if_write_is_true(func): """Decorator to check if unit operation solution should be written out.""" def wrapper(self, sim, unitOpId, solution_str, *args, **kwargs): From 59130ee0f1b19508f42f69f20738d39bc4657cb2 Mon Sep 17 00:00:00 2001 From: AntoniaBerger Date: Mon, 30 Mar 2026 17:10:08 +0200 Subject: [PATCH 3/7] Add time out warnings and change type of timeout from int to float --- cadet/cadet.py | 8 ++++---- cadet/cadet_dll.py | 12 +++++++++--- cadet/runner.py | 7 ++++--- 3 files changed, 17 insertions(+), 10 deletions(-) diff --git a/cadet/cadet.py b/cadet/cadet.py index 5a63636..e814198 100644 --- a/cadet/cadet.py +++ b/cadet/cadet.py @@ -518,7 +518,7 @@ def run_load( def run_simulation( self, - timeout: Optional[int] = None, + timeout: Optional[float] = None, clear: bool = True ) -> ReturnInformation: """ @@ -526,7 +526,7 @@ def run_simulation( Parameters ---------- - timeout : Optional[int] + timeout : Optional[float] Maximum time allowed for the simulation to run, in seconds. clear : bool If True, clear the simulation results from the current runner instance. @@ -551,14 +551,14 @@ def run_simulation( def run( self, - timeout: Optional[int] = None, + timeout: Optional[float] = None, ) -> ReturnInformation: """ Run the CADET simulation. Parameters ---------- - timeout : Optional[int] + timeout : Optional[float] Maximum time allowed for the simulation to run, in seconds. Returns diff --git a/cadet/cadet_dll.py b/cadet/cadet_dll.py index 86c7d63..aad359e 100644 --- a/cadet/cadet_dll.py +++ b/cadet/cadet_dll.py @@ -2,9 +2,9 @@ import io import os from pathlib import Path -from typing import Any, Optional +from typing import Any, Optional, Union -from typing import Union +from packaging.version import Version import addict import numpy @@ -1811,7 +1811,7 @@ def log_handler(file, func, line, level, level_name, message): def run( self, simulation: Optional["Cadet"] = None, - timeout: Optional[int] = None, + timeout: Optional[float] = None, ) -> ReturnInformation: """ Run a CADET simulation using the DLL interface. @@ -1828,6 +1828,12 @@ def run( RuntimeError If the simulation process returns a non-zero exit code. """ + if timeout is not None: + if(self._cadet_capi_version < Version("1.1.0a1")): + raise TypeError( + "timeout is not support CADET-CAPI version: " + f"({self._cadet_capi_version})." + ) pp = cadet_dll_parameterprovider.PARAMETERPROVIDER(simulation) log_buffer = self.setup_log_buffer() diff --git a/cadet/runner.py b/cadet/runner.py index 602e681..ec0afa6 100644 --- a/cadet/runner.py +++ b/cadet/runner.py @@ -6,6 +6,7 @@ from dataclasses import dataclass from pathlib import Path from typing import Optional +from packaging.version import Version @dataclass @@ -38,7 +39,7 @@ class CadetRunnerBase(ABC): def run( self, simulation: "Cadet", - timeout: Optional[int] = None, + timeout: Optional[float] = None, ) -> ReturnInformation: """ Run a CADET simulation. @@ -126,7 +127,7 @@ def __init__(self, cadet_path: str | os.PathLike) -> None: def run( self, simulation: "Cadet", - timeout: Optional[int] = None, + timeout: Optional[float] = None, ) -> ReturnInformation: """ Run a CADET simulation using the CLI executable. @@ -135,7 +136,7 @@ def run( ---------- simulation : Cadet Not used in this runner. - timeout : Optional[int] + timeout : Optional[float] Maximum time allowed for the simulation to run, in seconds. Raises From 95c3ddb77adb4813e0998e435ffe6ef5ac3ffcd1 Mon Sep 17 00:00:00 2001 From: Jan Breuer Date: Thu, 9 Apr 2026 13:44:44 +0200 Subject: [PATCH 4/7] fixup! --- cadet/cadet_dll.py | 46 +++++++++++++++++++++++++++++----------------- 1 file changed, 29 insertions(+), 17 deletions(-) diff --git a/cadet/cadet_dll.py b/cadet/cadet_dll.py index aad359e..8fe34ad 100644 --- a/cadet/cadet_dll.py +++ b/cadet/cadet_dll.py @@ -1713,29 +1713,41 @@ def _initialize_dll(self): cdtGetLatestCAPIVersion.restype = ctypes.c_char_p self._cadet_capi_version = Version(cdtGetLatestCAPIVersion().decode('utf-8')) - # Check which C-API is provided by CADET (given the current install path) - #current supported versions are: 1.0.0 and 1.1.0a1 - #TODO: write in developer guide to update LATEST_CAPI_VERSION in VersionInfo.cpp.in (Cadet-Core) - if self._cadet_capi_version == Version("1.1.0a1"): + # Use the latest supported C-API if applicable, i.e. + # - unsupported major version -> error + # - minor/patch versions later than the last supported version fall back to the last supported version + if self._cadet_capi_version < Version("1.0.0") or self._cadet_capi_version >= Version("2.0.0"): + + raise TypeError( + "This version of CADET-Python does not support CADET-CAPI version " + f"({self._cadet_capi_version})." + ) + + elif self._cadet_capi_version >= Version("1.1.0a1"): cdtGetAPIv1_1_0a_1 = self._lib.cdtGetAPIv1_1_0a1 cdtGetAPIv1_1_0a_1.argtypes = [ctypes.POINTER(CADETAPI_V1_1_0a_1)] cdtGetAPIv1_1_0a_1.restype = c_cadet_result self._api = CADETAPI_V1_1_0a_1() cdtGetAPIv1_1_0a_1(ctypes.byref(self._api)) - elif self._cadet_capi_version < Version("1.1.0a1"): - cdtGetAPIv010000 = self._lib.cdtGetAPIv1_0_0 - cdtGetAPIv010000.argtypes = [ctypes.POINTER(CADETAPIV010000)] - cdtGetAPIv010000.restype = c_cadet_result - self._api = CADETAPIV010000() - cdtGetAPIv010000(ctypes.byref(self._api)) - #TODO if version 1.1.0 is realeased - #raise Warning ( - # "A newer C-API version is available you are using the fallback version 1.0.0" - #) - else: + + elif self._cadet_capi_version == Version("1.0.0"): + + # Support of old CAPI version semantic + if Version(self._cadet_version) < Version("6.0.0a3"): + cdtGetAPIv1_0_0 = self._lib.cdtGetAPIv010000 + else: + cdtGetAPIv1_0_0 = self._lib.cdtGetAPIv1_0_0 + + cdtGetAPIv1_0_0.argtypes = [ctypes.POINTER(CADETAPI_V1_0_0)] + cdtGetAPIv1_0_0.restype = c_cadet_result + self._api = CADETAPI_V1_0_0() + cdtGetAPIv1_0_0(ctypes.byref(self._api)) + + else: # this case must not happen and if it happens, the above logic is incomplete. + raise TypeError( - "CADET-Python does not support CADET-CAPI version " - f"({self._cadet_capi_version})." + "This version of CADET-Python does not support CADET-CAPI version " + f"({self._cadet_capi_version}). Check CADET-Python implementation of CAPI support logic." ) self._driver = self._api.createDriver() From 583212598ec6974216129fef9a426155560e7ec8 Mon Sep 17 00:00:00 2001 From: Jan Breuer Date: Thu, 9 Apr 2026 15:22:24 +0200 Subject: [PATCH 5/7] fixup! pre-release version name syntax PEP 440 --- cadet/cadet_dll.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/cadet/cadet_dll.py b/cadet/cadet_dll.py index 8fe34ad..5ae50b1 100644 --- a/cadet/cadet_dll.py +++ b/cadet/cadet_dll.py @@ -103,8 +103,8 @@ class CADET_API_V1_SIGNATURES: signatures_1_0_0['getTimeSim'] = ('return', 'drv', 'timeSim') - signatures_1_1_0a_1 = {} - signatures_1_1_0a_1['timeout'] = ('return', 'drv', 'timeout') + signatures_1_1_0a1 = {} + signatures_1_1_0a1['timeout'] = ('return', 'drv', 'timeout') # Mappings for common ctypes parameters lookup_prototype = { @@ -165,9 +165,9 @@ class CADET_API_V1_SIGNATURES: _VERSION_SIGNATURES: dict[Version, dict] = {} _VERSION_SIGNATURES[Version("1.0.0")] = dict(CADET_API_V1_SIGNATURES.signatures_1_0_0) -_sigs_1_1_0a_1 = dict(CADET_API_V1_SIGNATURES.signatures_1_0_0) -_sigs_1_1_0a_1.update(CADET_API_V1_SIGNATURES.signatures_1_1_0a_1) -_VERSION_SIGNATURES[Version("1.1.0a1")] = _sigs_1_1_0a_1 +_sigs_1_1_0a1 = dict(CADET_API_V1_SIGNATURES.signatures_1_0_0) +_sigs_1_1_0a1.update(CADET_API_V1_SIGNATURES.signatures_1_1_0a1) +_VERSION_SIGNATURES[Version("1.1.0a1")] = _sigs_1_1_0a1 def _get_api_signatures(api: Any) -> dict[str, tuple[str, ...]]: return _VERSION_SIGNATURES[api._version] @@ -195,7 +195,7 @@ class CADETAPI_V1_0_0(ctypes.Structure): CADETAPIV010000 = CADETAPI_V1_0_0 -class CADETAPI_V1_1_0a_1(ctypes.Structure): +class CADETAPI_V1_1_0a1(ctypes.Structure): """Mimic cdtAPIv1.1.0a.1 struct of CADET C-API in ctypes.""" _version = Version("1.1.0a1") _fields_ = _setup_api(_version) @@ -216,7 +216,7 @@ class SimulationResult: def __init__( self, - api: Union[CADETAPI_V1_0_0, CADETAPI_V1_1_0a_1], driver: CadetDriver + api: Union[CADETAPI_V1_0_0, CADETAPI_V1_1_0a1], driver: CadetDriver ) -> None: self._api = api self._driver = driver @@ -1724,11 +1724,11 @@ def _initialize_dll(self): ) elif self._cadet_capi_version >= Version("1.1.0a1"): - cdtGetAPIv1_1_0a_1 = self._lib.cdtGetAPIv1_1_0a1 - cdtGetAPIv1_1_0a_1.argtypes = [ctypes.POINTER(CADETAPI_V1_1_0a_1)] - cdtGetAPIv1_1_0a_1.restype = c_cadet_result - self._api = CADETAPI_V1_1_0a_1() - cdtGetAPIv1_1_0a_1(ctypes.byref(self._api)) + cdtGetAPIv1_1_0a1 = self._lib.cdtGetAPIv1_1_0a1 + cdtGetAPIv1_1_0a1.argtypes = [ctypes.POINTER(CADETAPI_V1_1_0a1)] + cdtGetAPIv1_1_0a1.restype = c_cadet_result + self._api = CADETAPI_V1_1_0a1() + cdtGetAPIv1_1_0a1(ctypes.byref(self._api)) elif self._cadet_capi_version == Version("1.0.0"): From 364679d16588f89048f7792ebe2ce7098f6dcae6 Mon Sep 17 00:00:00 2001 From: Jan Breuer Date: Thu, 9 Apr 2026 17:34:49 +0200 Subject: [PATCH 6/7] wip add tests --- tests/test_capi_versioning.py | 169 ++++++++++++++++++++++++++++++++++ 1 file changed, 169 insertions(+) create mode 100644 tests/test_capi_versioning.py diff --git a/tests/test_capi_versioning.py b/tests/test_capi_versioning.py new file mode 100644 index 0000000..c46d79e --- /dev/null +++ b/tests/test_capi_versioning.py @@ -0,0 +1,169 @@ +""" +Unit tests for CADET-C-API versioning support in cadet_dll.py. + +Tests the following aspects of CAPI versioning: +1. Support for CAPI version 1.0.0 +2. Support for CAPI version 1.1.0a1 and its timeout function +3. Error handling for unsupported versions +4. Old CAPI version semantic (cdtGetAPIv010000 vs cdtGetAPIv1_0_0) +""" + +import pytest +from unittest.mock import Mock, MagicMock, patch +from pathlib import Path +from packaging.version import Version + +from cadet.cadet_dll import ( + CADETAPI_V1_0_0, + CADETAPI_V1_1_0a1, + _get_api_signatures, + _setup_api, + CadetDLLRunner, +) + + +# Tests for ctypes Structure Definitions + +def test_v1_0_0_structure_version(): + """Test that CADETAPI_V1_0_0 has correct version.""" + assert CADETAPI_V1_0_0._version == Version("1.0.0") + + +def test_v1_1_0a1_structure_version(): + """Test that CADETAPI_V1_1_0a1 has correct version.""" + assert CADETAPI_V1_1_0a1._version == Version("1.1.0a1") + + +def test_v1_0_0_structure_has_fields(): + """Test that CADETAPI_V1_0_0 structure has fields defined.""" + assert len(CADETAPI_V1_0_0._fields_) > 0 + + +def test_v1_1_0a1_structure_has_fields(): + """Test that CADETAPI_V1_1_0a1 structure has fields defined.""" + assert len(CADETAPI_V1_1_0a1._fields_) > 0 + + +def test_v1_1_0a1_has_timeout_field(): + """Test that CADETAPI_V1_1_0a1 has timeout field.""" + field_names = [field[0] for field in CADETAPI_V1_1_0a1._fields_] + assert 'timeout' in field_names + + +def test_v1_0_0_does_not_have_timeout_field(): + """Test that CADETAPI_V1_0_0 does not have timeout field.""" + field_names = [field[0] for field in CADETAPI_V1_0_0._fields_] + assert 'timeout' not in field_names + + +def test_v1_0_0_fields_are_subset_of_v1_1_0a1(): + """Test that v1.0.0 fields are a subset of v1.1.0a1 fields.""" + v1_0_0_fields = {field[0] for field in CADETAPI_V1_0_0._fields_} + v1_1_0a1_fields = {field[0] for field in CADETAPI_V1_1_0a1._fields_} + + assert v1_0_0_fields.issubset(v1_1_0a1_fields) + + +# Tests for _setup_api Function + +def test_setup_api_v1_0_0(): + """Test _setup_api correctly sets up v1.0.0 API fields.""" + fields = _setup_api(Version("1.0.0")) + field_names = [field[0] for field in fields] + + assert 'createDriver' in field_names + assert 'deleteDriver' in field_names + assert 'runSimulation' in field_names + assert 'timeout' not in field_names + + +def test_setup_api_v1_1_0a1(): + """Test _setup_api correctly sets up v1.1.0a1 API fields.""" + fields = _setup_api(Version("1.1.0a1")) + field_names = [field[0] for field in fields] + + assert 'createDriver' in field_names + assert 'deleteDriver' in field_names + assert 'runSimulation' in field_names + assert 'timeout' in field_names + + +def test_setup_api_fields_are_cfunctype(): + """Test that all setup API fields are CFUNCTYPE.""" + import ctypes + + fields = _setup_api(Version("1.0.0")) + + for field_name, field_type in fields: + assert callable(field_type) + + +# Tests for _initialize_dll Method and Version Handling + +@patch('cadet.cadet_dll.ctypes.cdll.LoadLibrary') +def test_unsupported_version_error_below_1_0_0(mock_load): + """Test that versions below 1.0.0 raise TypeError.""" + mock_lib = MagicMock() + mock_load.return_value = mock_lib + + mock_lib.cdtGetLibraryVersion.return_value = b"5.0.0" + mock_lib.cdtGetLibraryCommitHash.return_value = b"abc123" + mock_lib.cdtGetLibraryBranchRefspec.return_value = b"main" + mock_lib.cdtGetLibraryBuildType.return_value = b"Release" + mock_lib.cdtGetLatestCAPIVersion.return_value = b"0.9.0" + + runner = CadetDLLRunner.__new__(CadetDLLRunner) + runner._cadet_path = Path("fake_path.so") + + with pytest.raises(TypeError) as exc_info: + runner._initialize_dll() + + assert "does not support CADET-CAPI version" in str(exc_info.value) + assert "0.9.0" in str(exc_info.value) + + +@patch('cadet.cadet_dll.ctypes.cdll.LoadLibrary') +def test_unsupported_version_error_above_2_0_0(mock_load): + """Test that versions 2.0.0 and above raise TypeError.""" + mock_lib = MagicMock() + mock_load.return_value = mock_lib + + mock_lib.cdtGetLibraryVersion.return_value = b"5.0.0" + mock_lib.cdtGetLibraryCommitHash.return_value = b"abc123" + mock_lib.cdtGetLibraryBranchRefspec.return_value = b"main" + mock_lib.cdtGetLibraryBuildType.return_value = b"Release" + mock_lib.cdtGetLatestCAPIVersion.return_value = b"2.0.0" + + runner = CadetDLLRunner.__new__(CadetDLLRunner) + runner._cadet_path = Path("fake_path.so") + + with pytest.raises(TypeError) as exc_info: + runner._initialize_dll() + + assert "does not support CADET-CAPI version" in str(exc_info.value) + assert "2.0.0" in str(exc_info.value) + + +@patch('cadet.cadet_dll.ctypes.cdll.LoadLibrary') +def test_no_capi_function_error(mock_load): + """Test that missing cdtGetLatestCAPIVersion raises ValueError.""" + mock_lib = MagicMock() + mock_load.return_value = mock_lib + + mock_lib.cdtGetLibraryVersion.return_value = b"5.0.0" + mock_lib.cdtGetLibraryCommitHash.return_value = b"abc123" + mock_lib.cdtGetLibraryBranchRefspec.return_value = b"main" + mock_lib.cdtGetLibraryBuildType.return_value = b"Release" + del mock_lib.cdtGetLatestCAPIVersion + + runner = CadetDLLRunner.__new__(CadetDLLRunner) + runner._cadet_path = Path("fake_path.so") + + with pytest.raises(ValueError) as exc_info: + runner._initialize_dll() + + assert "does not support CADET-CAPI" in str(exc_info.value) + + +if __name__ == '__main__': + pytest.main([__file__]) From 15a33f624ca5dbb8b10a69bf15f07e78f0c20315 Mon Sep 17 00:00:00 2001 From: Jan Breuer Date: Mon, 13 Apr 2026 15:59:27 +0200 Subject: [PATCH 7/7] fixup! tests --- tests/test_capi_versioning.py | 31 +++++-------------------------- 1 file changed, 5 insertions(+), 26 deletions(-) diff --git a/tests/test_capi_versioning.py b/tests/test_capi_versioning.py index c46d79e..d1f3663 100644 --- a/tests/test_capi_versioning.py +++ b/tests/test_capi_versioning.py @@ -9,14 +9,13 @@ """ import pytest -from unittest.mock import Mock, MagicMock, patch +from unittest.mock import MagicMock, patch from pathlib import Path from packaging.version import Version from cadet.cadet_dll import ( CADETAPI_V1_0_0, CADETAPI_V1_1_0a1, - _get_api_signatures, _setup_api, CadetDLLRunner, ) @@ -34,33 +33,14 @@ def test_v1_1_0a1_structure_version(): assert CADETAPI_V1_1_0a1._version == Version("1.1.0a1") -def test_v1_0_0_structure_has_fields(): - """Test that CADETAPI_V1_0_0 structure has fields defined.""" - assert len(CADETAPI_V1_0_0._fields_) > 0 - - -def test_v1_1_0a1_structure_has_fields(): - """Test that CADETAPI_V1_1_0a1 structure has fields defined.""" - assert len(CADETAPI_V1_1_0a1._fields_) > 0 - - -def test_v1_1_0a1_has_timeout_field(): - """Test that CADETAPI_V1_1_0a1 has timeout field.""" - field_names = [field[0] for field in CADETAPI_V1_1_0a1._fields_] - assert 'timeout' in field_names - - -def test_v1_0_0_does_not_have_timeout_field(): - """Test that CADETAPI_V1_0_0 does not have timeout field.""" - field_names = [field[0] for field in CADETAPI_V1_0_0._fields_] - assert 'timeout' not in field_names - - -def test_v1_0_0_fields_are_subset_of_v1_1_0a1(): +def test_v1_0_0_v1_0_0a1_fields(): """Test that v1.0.0 fields are a subset of v1.1.0a1 fields.""" v1_0_0_fields = {field[0] for field in CADETAPI_V1_0_0._fields_} v1_1_0a1_fields = {field[0] for field in CADETAPI_V1_1_0a1._fields_} + assert 'timeout' in v1_1_0a1_fields + assert 'timeout' not in v1_0_0_fields + assert v1_0_0_fields.issubset(v1_1_0a1_fields) @@ -90,7 +70,6 @@ def test_setup_api_v1_1_0a1(): def test_setup_api_fields_are_cfunctype(): """Test that all setup API fields are CFUNCTYPE.""" - import ctypes fields = _setup_api(Version("1.0.0"))