Currently these values (atomic mass, elementary charge etc) are only accessible in extension classes using cimport. It would be valuable to be able to import them in Python files as well so we can single-source constants, for example in tests.
In general it is difficult to properly shadow C types defined in Cython with Python types (cython/cython#3959 (comment)), but in the simple case of a collection of constants all with the same type we can make use of ctypes, module-level __getattr__ and a custom module class (https://docs.python.org/3/reference/datamodel.html#customizing-module-attribute-access).
Proof-of-concept:
import ctypes
import sys
from types import ModuleType
from cherab.core.utility import constants
PyCapsule_GetName = ctypes.pythonapi.PyCapsule_GetName
PyCapsule_GetName.restype = ctypes.c_char_p
PyCapsule_GetName.argtypes = [ctypes.py_object]
PyCapsule_GetPointer = ctypes.pythonapi.PyCapsule_GetPointer
PyCapsule_GetPointer.restype = ctypes.c_void_p
PyCapsule_GetPointer.argtypes = [ctypes.py_object, ctypes.c_char_p]
PyFloat_FromDouble = ctypes.pythonapi.PyFloat_FromDouble
PyFloat_FromDouble.restype = ctypes.py_object
PyFloat_FromDouble.argtypes = [ctypes.c_double]
DoublePtr = ctypes.POINTER(ctypes.c_double)
def __getattr__(name):
if name not in constants.__pyx_capi__:
raise AttributeError()
capsule = constants.__pyx_capi__[name]
capsule_name = PyCapsule_GetName(capsule)
voidptr = PyCapsule_GetPointer(capsule, capsule_name)
doubleptr = ctypes.cast(voidptr, DoublePtr)
val = PyFloat_FromDouble(doubleptr.contents)
return val
def __dir__():
return list(constants.__pyx_capi__)
class ReadOnlyModule(ModuleType):
def __setattr__(self, attr, value):
raise ValueError("Can't add/modify constants.")
sys.modules[__name__].__class__ = ReadOnlyModule
Leads to some nice usability improvements (auto-complete and module help for example):
(cherab-dev) 3jl@fusion3:~$ python
Python 3.10.12 (main, Jun 22 2026, 18:55:27) [GCC 11.4.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import cherab_utility_constants as cuc
>>> cuc.ATOMIC_MASS
1.6605390666e-27
>>> cuc.VACUUM_PERMITTIVITY
8.8541878128e-12
>>> cuc.VACUUM_PERMITTIVITY += 1
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/home/3jl/cherab_utility_constants.py", line 40, in __setattr__
raise ValueError("Can't add/modify constants.")
ValueError: Can't add/modify constants.
>>> cuc.<TAB>
cuc.ATOMIC_MASS cuc.ELEMENTARY_CHARGE cuc.RECIP_4_PI
cuc.BOHR_MAGNETON cuc.HC_EV_NM cuc.RYDBERG_CONSTANT_EV
cuc.DEGREES_TO_RADIANS cuc.PLANCK_CONSTANT cuc.SPEED_OF_LIGHT
cuc.ELECTRON_CLASSICAL_RADIUS cuc.RADIANS_TO_DEGREES cuc.VACUUM_PERMITTIVITY
cuc.ELECTRON_REST_MASS cuc.RECIP_2_PI
>>> help(cuc)
Help on module cherab_utility_constants:
NAME
cherab_utility_constants - Extract cdef double constants from cherab.core.utility for use in Python.
DATA
ATOMIC_MASS = 1.6605390666e-27
BOHR_MAGNETON = 5.78838180123e-05
DEGREES_TO_RADIANS = 0.017453292519943295
ELECTRON_CLASSICAL_RADIUS = 2.8179403262e-15
ELECTRON_REST_MASS = 9.1093837015e-31
ELEMENTARY_CHARGE = 1.602176634e-19
HC_EV_NM = 1239.8419738620933
PLANCK_CONSTANT = 6.62607015e-34
RADIANS_TO_DEGREES = 57.29577951308232
RECIP_2_PI = 0.15915494309189535
RECIP_4_PI = 0.07957747154594767
RYDBERG_CONSTANT_EV = 13.605693122994
SPEED_OF_LIGHT = 299792458.0
VACUUM_PERMITTIVITY = 8.8541878128e-12
FILE
/home/3jl/cherab_utility_constants.py
Implementing __getattr__ and the custom ModuleType class within the pyx file itself would probably also allow the removal of a lot of the ctypes boilerplate, though I haven't tested this.
Currently these values (atomic mass, elementary charge etc) are only accessible in extension classes using
cimport. It would be valuable to be able to import them in Python files as well so we can single-source constants, for example in tests.In general it is difficult to properly shadow C types defined in Cython with Python types (cython/cython#3959 (comment)), but in the simple case of a collection of constants all with the same type we can make use of
ctypes, module-level__getattr__and a custom module class (https://docs.python.org/3/reference/datamodel.html#customizing-module-attribute-access).Proof-of-concept:
Leads to some nice usability improvements (auto-complete and module help for example):
Implementing
__getattr__and the customModuleTypeclass within the pyx file itself would probably also allow the removal of a lot of the ctypes boilerplate, though I haven't tested this.