#pragma once
#include
// ssize_t is non-standard, for MSVC we need to define it
#if defined(_MSC_VER)
#include
using ssize_t = SSIZE_T;
#endif
namespace pythonx::python {
// We only use the Limited C API, which stays compatible across Python
// minor versions. At the moment we assume Python 3.10+, the list of
// available functions can be found in [1].
//
// Other than the C API functions, we also use Python standard library
// functions via PyObject_Call. This is fine as long as the functions
// are present in the lowest supported Python version and seem stable
// (in particular, they are not deprecated). Keep in mind that the
// C API should be preferred when possible, since invoking lower level
// operations directly should be more efficient in most cases.
//
// Note that we manage the library manually with `dlopen`, instead of
// linking at compile time and loading together with the NIF lib. This
// makes the development slightly more annoying, since we need to
// declare and load the individual symbols ourselves, however it makes
// the overall setup more flexible:
//
// 1. We want the Python library location to be configured by the
// user. We could do that by: (a) making sure the NIF library
// depends on a relative libpython.so/python.dll; (b) at runtime,
// symlink or copy the configured Python library at that relative
// location; (c) load the NIF only after symlink is in place,
// rather than upfront. Step (a) requires patching the .so/.dll
// differently on each platform; on Windows this requires compiling
// and attaching and extra manifest to the .dll. With `dlopen` we
// can simply use the configured path.
//
// 2. It prevents from accidentally using symbols out of Limited API.
// Technically setting Py_LIMITED_API hides a subset of function
// declarations, but there are at least certain macros that stay
// visible. By adding the symbols manually, we can make sure they
// are indeed part of the Limited API.
//
// 3. If necessary, we could conditionally use functions added to
// Limited API in later versions and have fallback implementations.
//
// 4. Compiling the NIF does not require Python at all, which makes
// it simpler. Though the main tradeoff is not being able to use
// the Python library headers.
//
// 5. After uninitializing Python, we should be able to unload the
// library, though there may not be an actual use case to make
// this relevant. It is also worth noting that, while in principle
// it should be possible to reinitialize Python, it can lead to
// issues in practice. For example, doing so while using numpy
// simply does not work, see [2] for discussion points.
//
// [1]: https://docs.python.org/3.10/c-api/stable.html#stable-abi-list
// [2]: https://bugs.python.org/issue34309
// Opaque types
using PyInterpreterStatePtr = void *;
using PyObjectPtr = void *;
using PyThreadStatePtr = void *;
using Py_ssize_t = ssize_t;
// Functions
extern PyObjectPtr (*PyBool_FromLong)(long int);
extern int (*PyBytes_AsStringAndSize)(PyObjectPtr, char **, Py_ssize_t *);
extern PyObjectPtr (*PyBytes_FromStringAndSize)(const char *, Py_ssize_t);
extern PyObjectPtr (*PyDict_Copy)(PyObjectPtr);
extern PyObjectPtr (*PyDict_GetItem)(PyObjectPtr, PyObjectPtr);
extern PyObjectPtr (*PyDict_GetItemString)(PyObjectPtr, const char *);
extern PyObjectPtr (*PyDict_New)();
extern int (*PyDict_Next)(PyObjectPtr, Py_ssize_t *, PyObjectPtr *,
PyObjectPtr *);
extern int (*PyDict_SetItem)(PyObjectPtr, PyObjectPtr, PyObjectPtr);
extern int (*PyDict_SetItemString)(PyObjectPtr, const char *, PyObjectPtr);
extern Py_ssize_t (*PyDict_Size)(PyObjectPtr);
extern void (*PyErr_Fetch)(PyObjectPtr *, PyObjectPtr *, PyObjectPtr *);
extern PyObjectPtr (*PyErr_Occurred)();
extern PyObjectPtr (*PyEval_GetBuiltins)();
extern PyObjectPtr (*PyEval_EvalCode)(PyObjectPtr, PyObjectPtr, PyObjectPtr);
extern void (*PyEval_RestoreThread)(PyThreadStatePtr);
extern PyThreadStatePtr (*PyEval_SaveThread)();
extern double (*PyFloat_AsDouble)(PyObjectPtr);
extern PyObjectPtr (*PyFloat_FromDouble)(double);
extern PyObjectPtr (*PyImport_AddModule)(const char *);
extern PyObjectPtr (*PyImport_ImportModule)(const char *);
extern PyInterpreterStatePtr (*PyInterpreterState_Get)();
extern PyObjectPtr (*PyIter_Next)(PyObjectPtr);
extern int (*PyList_Append)(PyObjectPtr, PyObjectPtr);
extern PyObjectPtr (*PyList_GetItem)(PyObjectPtr, Py_ssize_t);
extern PyObjectPtr (*PyList_New)(Py_ssize_t);
extern Py_ssize_t (*PyList_Size)(PyObjectPtr);
extern int (*PyList_SetItem)(PyObjectPtr, Py_ssize_t, PyObjectPtr);
extern long long (*PyLong_AsLongLongAndOverflow)(PyObjectPtr, int *);
extern PyObjectPtr (*PyLong_FromLongLong)(long long);
extern PyObjectPtr (*PyLong_FromString)(const char *, char **, int);
extern PyObjectPtr (*PyLong_FromUnsignedLongLong)(unsigned long long);
extern PyObjectPtr (*PyModule_GetDict)(PyObjectPtr);
extern PyObjectPtr (*PyObject_Call)(PyObjectPtr, PyObjectPtr, PyObjectPtr);
extern PyObjectPtr (*PyObject_CallNoArgs)(PyObjectPtr);
extern PyObjectPtr (*PyObject_GetAttrString)(PyObjectPtr, const char *);
extern PyObjectPtr (*PyObject_GetIter)(PyObjectPtr);
extern int (*PyObject_IsInstance)(PyObjectPtr, PyObjectPtr);
extern PyObjectPtr (*PyObject_Repr)(PyObjectPtr);
extern int (*PyObject_SetAttrString)(PyObjectPtr, const char *, PyObjectPtr);
extern PyObjectPtr (*PyObject_Str)(PyObjectPtr);
extern int (*PySet_Add)(PyObjectPtr, PyObjectPtr);
extern PyObjectPtr (*PySet_New)(PyObjectPtr);
extern Py_ssize_t (*PySet_Size)(PyObjectPtr);
extern PyThreadStatePtr (*PyThreadState_New)(PyInterpreterStatePtr);
extern PyObjectPtr (*PyTuple_GetItem)(PyObjectPtr, Py_ssize_t);
extern PyObjectPtr (*PyTuple_New)(Py_ssize_t);
extern PyObjectPtr (*PyTuple_Pack)(Py_ssize_t, ...);
extern int (*PyTuple_SetItem)(PyObjectPtr, Py_ssize_t, PyObjectPtr);
extern Py_ssize_t (*PyTuple_Size)(PyObjectPtr);
extern const char *(*PyUnicode_AsUTF8AndSize)(PyObjectPtr, Py_ssize_t *);
extern PyObjectPtr (*PyUnicode_FromStringAndSize)(const char *, Py_ssize_t);
extern PyObjectPtr (*Py_BuildValue)(const char *, ...);
extern PyObjectPtr (*Py_CompileString)(const char *, const char *, int);
extern void (*Py_DecRef)(PyObjectPtr);
extern void (*Py_IncRef)(PyObjectPtr);
extern void (*Py_InitializeEx)(int);
extern int (*Py_IsFalse)(PyObjectPtr);
extern int (*Py_IsNone)(PyObjectPtr);
extern int (*Py_IsTrue)(PyObjectPtr);
extern void (*Py_SetPythonHome)(const wchar_t *);
extern void (*Py_SetProgramName)(const wchar_t *);
// Opens Python dynamic library at the given path and looks up all
// relevant symbols.
//
// Raises `std::runtime_error` if loading fails.
void load_python_library(std::string path);
// Closes the Python library.
void unload_python_library();
} // namespace pythonx::python