#pragma once
#define PY_SSIZE_T_CLEAN
#include <Python.h>
#include <structmember.h>
#include "typeattrs.h"
#include "exceptions.h"
#include "module.h"
namespace NPyBind {
void RegisterJSONBridge();
namespace NPrivate {
template <typename>
class TUnboundClosureHolder;
template <typename>
class TUnboundClosure;
}
// TTraits should be derived from TPythonType
template <typename TObjectHolder, typename TObject, typename TTraits>
class TPythonType {
private:
TPythonType(const TPythonType&);
TPythonType& operator=(const TPythonType&);
private:
typedef typename TPythonTypeAttributes<TObject>::TGetterPtr TGetterPtr;
typedef typename TPythonTypeAttributes<TObject>::TSetterPtr TSetterPtr;
typedef typename TPythonTypeAttributes<TObject>::TCallerPtr TCallerPtr;
struct TProxy {
PyObject_HEAD
TObjectHolder* Holder;
};
static PyTypeObject PyType;
static PyMappingMethods MappingMethods;
static PyObject* PyTypeObjPtr;
protected:
static PyTypeObject* GetPyTypePtr() {
return &PyType;
}
private:
TPythonTypeAttributes<TObject> Attributes;
static int InitObject(PyObject* s, PyObject* args, PyObject* kwargs) {
try {
TProxy* self = reinterpret_cast<TProxy*>(s);
auto str = NameFromString("__properties__");
if (kwargs && PyDict_Check(kwargs) && PyDict_Contains(kwargs, str.Get())) {
TPyObjectPtr props(PyDict_GetItem(kwargs, str.Get()));
TVector<TString> properties;
FromPyObject(props.Get(), properties);
self->Holder = TTraits::DoInitPureObject(properties);
} else {
self->Holder = (args || kwargs) ? TTraits::DoInitObject(args, kwargs) : nullptr;
}
if (PyErr_Occurred())
return -1;
return 0;
} catch (const std::exception& ex) {
PyErr_SetString(TExceptionsHolder::Instance().ToPyException(ex).Get(), ex.what());
} catch (...) {
PyErr_SetString(PyExc_RuntimeError, "Unknown error occurred while trying to init object");
}
return -1;
}
static void DeallocObject(TProxy* self) {
delete self->Holder;
Py_TYPE(self)->tp_free(reinterpret_cast<PyObject*>(self));
}
static PyObject* GetObjectAttr(PyObject* pyObj, char* attr);
static int SetObjectAttr(PyObject* pyObj, char* attr, PyObject* value);
static PyObject* GetStr(PyObject*);
static PyObject* GetRepr(PyObject*);
static PyObject* GetIter(PyObject*);
static PyObject* GetNext(PyObject*);
// Fill class __dict__ with functions to make sure methods names will get to dir()
void FillClassDict() const {
TVector<TString> names;
Attributes.GetMethodCallers().GetAllMethodsNames(names);
for (const auto& name : names) {
TPyObjectPtr callable = NPrivate::TUnboundClosure<TObject>::Instance().CreatePyObject(new NPrivate::TUnboundClosureHolder<TObject>(&PyType, name));
PyDict_SetItemString(PyType.tp_dict, name.c_str(), callable.Get());
}
}
void InitCommonAttributes() {
static bool was = false;
if (was)
return;
was = true;
Attributes.InitCommonAttributes();
FillClassDict();
}
protected:
TPythonType(const char* pyTypeName, const char* typeDescr, PyTypeObject* parentType = nullptr)
: Attributes(GetObjectAttr, SetObjectAttr)
{
PyType.tp_name = pyTypeName;
PyType.tp_doc = typeDescr;
Py_INCREF(PyTypeObjPtr);
if (parentType) {
Py_INCREF(parentType);
PyType.tp_base = parentType;
}
PyType_Ready(&PyType);
TExceptionsHolder::Instance();
RegisterJSONBridge();
}
~TPythonType() {
}
static TObjectHolder* DoInitObject(PyObject*, PyObject*) {
return nullptr;
}
static TObjectHolder* DoInitPureObject(const TVector<TString>&) {
return nullptr;
}
static void SetClosure(PyObject* (*call)(PyObject*, PyObject*, PyObject*)) {
PyType.tp_call = call;
}
public:
void AddGetter(const TString& attr, TGetterPtr getter) {
Attributes.AddGetter(attr, getter);
}
void AddSetter(const TString& attr, TSetterPtr setter) {
Attributes.AddSetter(attr, setter);
}
void AddCaller(const TString& name, TCallerPtr caller) {
Attributes.AddCaller(name, caller);
if (name == "__iter__") {
PyType.tp_iter = GetIter;
}
if (name == "next") {
PyType.tp_iternext = GetNext;
}
}
void SetIter(getiterfunc tp_iter) {
PyType.tp_iter = tp_iter;
}
void SetIterNext(iternextfunc tp_iternext) {
PyType.tp_iternext = tp_iternext;
}
void SetDestructor(destructor tp_dealloc) {
PyType.tp_dealloc = tp_dealloc;
}
void SetLengthFunction(lenfunc mp_length) {
PyType.tp_as_mapping->mp_length = mp_length;
}
void SetSubscriptFunction(binaryfunc mp_subscript) {
PyType.tp_as_mapping->mp_subscript = mp_subscript;
}
void SetAssSubscriptFunction(objobjargproc mp_ass_subscript) {
PyType.tp_as_mapping->mp_ass_subscript = mp_ass_subscript;
}
typedef TObject TObjectType;
static TPythonType& Instance() {
static TTraits Traits;
Traits.InitCommonAttributes();
return Traits;
}
void Register(PyObject* module, const char* typeName) {
Py_INCREF(PyTypeObjPtr);
if (0 != PyModule_AddObject(module, typeName, PyTypeObjPtr))
ythrow yexception() << "can't register type \"" << typeName << "\"";
}
void Register(PyObject* module, const char* objName, TObjectHolder* hld) {
if (0 != PyModule_AddObject(module, objName, CreatePyObject(hld).RefGet()))
ythrow yexception() << "can't register object \"" << objName << "\"";
}
void Register(TPyObjectPtr module, const TString& typeName) {
Register(module.Get(), typeName.c_str());
}
void Register(TPyObjectPtr module, const TString& objName, TObjectHolder* hld) {
Register(module.Get(), objName.c_str(), hld);
}
static TObjectHolder* CastToObjectHolder(PyObject* obj) {
// Call Instance() to make sure PyTypeObjPtr is already created at this point
Instance();
if (!PyObject_IsInstance(obj, PyTypeObjPtr))
return nullptr;
TProxy* prx = reinterpret_cast<TProxy*>(obj);
return prx ? prx->Holder : nullptr;
}
static TObject* CastToObject(PyObject* obj) {
TObjectHolder* hld = CastToObjectHolder(obj);
return hld ? TTraits::GetObject(*hld) : nullptr;
}
static TPyObjectPtr CreatePyObject(TObjectHolder* hld) {
TPyObjectPtr r(_PyObject_New(&PyType), true);
TProxy* prx = reinterpret_cast<TProxy*>(r.Get());
if (prx)
prx->Holder = hld;
return r;
}
};
template <typename TObjectHolder, typename TObject, typename TTraits>
PyMappingMethods TPythonType<TObjectHolder, TObject, TTraits>::MappingMethods = {nullptr, nullptr, nullptr};
template <typename TObjectHolder, typename TObject, typename TTraits>
PyTypeObject TPythonType<TObjectHolder, TObject, TTraits>::PyType = {
PyVarObject_HEAD_INIT(nullptr, 0) "", sizeof(TProxy), 0, (destructor)&DeallocObject
#if PY_VERSION_HEX < 0x030800b4
, nullptr, /*tp_print*/
#endif
#if PY_VERSION_HEX >= 0x030800b4
, 0, /*tp_vectorcall_offset*/
#endif
&GetObjectAttr, &SetObjectAttr, nullptr, &GetRepr, nullptr, nullptr, &MappingMethods, nullptr, nullptr, &GetStr, nullptr, nullptr, nullptr,
Py_TPFLAGS_DEFAULT, "", nullptr, nullptr, nullptr, 0, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, 0, InitObject, PyType_GenericAlloc, PyType_GenericNew, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, 0
#if PY_MAJOR_VERSION >= 3
, nullptr
#endif
#if PY_VERSION_HEX >= 0x030800b1
, nullptr /*tp_vectorcall*/
#endif
#if PY_VERSION_HEX >= 0x030800b4 && PY_VERSION_HEX < 0x03090000
, nullptr /*tp_print*/
#endif
};
template <typename TObjectHolder, typename TObject, typename TTraits>
PyObject* TPythonType<TObjectHolder, TObject, TTraits>::PyTypeObjPtr =
reinterpret_cast<PyObject*>(&TPythonType<TObjectHolder, TObject, TTraits>::PyType);
namespace NPrivate {
template <typename TObject>
class TUnboundClosureHolder {
private:
THolder<PyTypeObject> Holder;
TString Method;
public:
TUnboundClosureHolder(PyTypeObject* ptr, const TString& meth)
: Holder(ptr)
, Method(meth)
{
}
PyTypeObject* GetObject() const {
return Holder.Get();
}
const TString GetMethod() const {
return Method;
}
PyObject* Call(PyObject* obj, PyObject* args, PyObject*) const {
TPyObjectPtr callable(PyObject_GetAttrString(obj, Method.c_str()), true);
if (!callable.Get())
ythrow yexception() << "PyBind can't call method '" << Method << "'";
TPyObjectPtr res(PyObject_CallObject(callable.Get(), args), true);
if (!res.Get() && !PyErr_Occurred())
ythrow yexception() << "PyBind can't call method '" << Method << "'";
return res.RefGet();
}
};
template <typename TObject>
class TUnboundClosure: public NPyBind::TPythonType<TUnboundClosureHolder<TObject>, PyTypeObject, TUnboundClosure<TObject>> {
private:
typedef class NPyBind::TPythonType<TUnboundClosureHolder<TObject>, PyTypeObject, TUnboundClosure<TObject>> TParent;
friend class NPyBind::TPythonType<TUnboundClosureHolder<TObject>, PyTypeObject, TUnboundClosure<TObject>>;
class TReprMethodCaller: public TBaseMethodCaller<PyTypeObject> {
public:
bool CallMethod(PyObject* closure, PyTypeObject*, PyObject*, PyObject*, PyObject*& res) const override {
TUnboundClosureHolder<TObject>* hld = TParent::CastToObjectHolder(closure);
TPyObjectPtr type((PyObject*)hld->GetObject());
TString nameStr;
TPyObjectPtr name(PyObject_GetAttrString(type.Get(), "__name__"), true);
if (!name.Get() || !FromPyObject(name.Get(), nameStr))
ythrow yexception() << "Could not get name of object";
TString methodName(hld->GetMethod());
TString message = "<unbound method " + nameStr + "." + methodName + ">";
res = ReturnString(message);
return (res != nullptr);
}
};
private:
TUnboundClosure()
: TParent("", "")
{
TParent::AddCaller("__repr__", new TReprMethodCaller());
TParent::AddCaller("__str__", new TReprMethodCaller());
TParent::SetClosure(&Call);
}
static PyObject* Call(PyObject* closure, PyObject* args, PyObject* kwargs) {
try {
TUnboundClosureHolder<TObject>* hld = TParent::CastToObjectHolder(closure);
if (!hld)
ythrow yexception() << "Can't cast object to TypeHolder";
size_t size = 0;
if (!PyTuple_Check(args) || (size = PyTuple_Size(args)) < 1)
ythrow yexception() << "Can't parse first argument: it should be valid object";
--size;
TPyObjectPtr obj(PyTuple_GetItem(args, 0));
TPyObjectPtr newArgs(PyTuple_New(size), true);
for (size_t i = 0; i < size; ++i) {
TPyObjectPtr item(PyTuple_GetItem(args, i + 1));
PyTuple_SetItem(newArgs.Get(), i, item.RefGet());
}
return hld->Call(obj.Get(), newArgs.Get(), kwargs);
} catch (const std::exception& ex) {
PyErr_SetString(::NPyBind::TExceptionsHolder::Instance().ToPyException(ex).Get(), ex.what());
} catch (...) {
PyErr_SetString(PyExc_RuntimeError, "Unknown error occurred while trying to call method");
}
return nullptr;
}
static PyTypeObject* GetObject(TUnboundClosureHolder<TObject>& obj) {
return obj.GetObject();
}
};
template <typename TObject>
class TBoundClosureHolder {
private:
TPyObjectPtr Ptr;
TObject* Object;
TString Method;
const TMethodCallers<TObject>& MethodCallers;
public:
TBoundClosureHolder(PyObject* ptr, TObject* obj, const TString& meth, const TMethodCallers<TObject>& callers)
: Ptr(ptr)
, Object(obj)
, Method(meth)
, MethodCallers(callers)
{
}
TPyObjectPtr GetObjectPtr() const {
return Ptr;
}
TObject* GetObject() const {
return Object;
}
const TString GetMethod() const {
return Method;
}
PyObject* Call(PyObject* args, PyObject* kwargs) const {
PyObject* res = MethodCallers.CallMethod(Ptr.Get(), Object, args, kwargs, Method);
if (res == nullptr && !PyErr_Occurred())
ythrow yexception() << "PyBind can't call method '" << Method << "'";
return res;
}
};
template <typename TObject>
class TBoundClosure: public TPythonType<TBoundClosureHolder<TObject>, TObject, TBoundClosure<TObject>> {
private:
typedef TPythonType<TBoundClosureHolder<TObject>, TObject, TBoundClosure<TObject>> TMyParent;
class TReprMethodCaller: public TBaseMethodCaller<TObject> {
public:
bool CallMethod(PyObject* closure, TObject*, PyObject*, PyObject*, PyObject*& res) const override {
TBoundClosureHolder<TObject>* hld = TMyParent::CastToObjectHolder(closure);
TPyObjectPtr obj(hld->GetObjectPtr());
TPyObjectPtr type(PyObject_Type(obj.Get()), true);
TString reprStr;
TPyObjectPtr repr(PyObject_Repr(obj.Get()), true);
if (!repr.Get() || !FromPyObject(repr.Get(), reprStr))
ythrow yexception() << "Could not get repr of object";
TString nameStr;
TPyObjectPtr name(PyObject_GetAttrString(type.Get(), "__name__"), true);
if (!name.Get() || !FromPyObject(name.Get(), nameStr))
ythrow yexception() << "Could not get name of object";
TString methodName(hld->GetMethod());
TString message = "<bound method " + nameStr + "." + methodName + " of " + reprStr + ">";
res = ReturnString(message);
return (res != nullptr);
}
};
private:
static PyObject* Call(PyObject* closure, PyObject* args, PyObject* kwargs) {
try {
TBoundClosureHolder<TObject>* hld = TMyParent::CastToObjectHolder(closure);
if (!hld)
ythrow yexception() << "Can't cast object to ClosureHolder";
return hld->Call(args, kwargs);
} catch (const std::exception& ex) {
PyErr_SetString(::NPyBind::TExceptionsHolder::Instance().ToPyException(ex).Get(), ex.what());
} catch (...) {
PyErr_SetString(PyExc_RuntimeError, "Unknown error occurred while trying to call method");
}
return nullptr;
}
public:
TBoundClosure()
: TMyParent("", "")
{
TMyParent::AddCaller("__repr__", new TReprMethodCaller());
TMyParent::AddCaller("__str__", new TReprMethodCaller());
TMyParent::SetClosure(&Call);
}
static TObject* GetObject(const TBoundClosureHolder<TObject>& closure) {
return closure.GetObject();
}
};
}
template <typename TObjectHolder, typename TObject, typename TTraits>
PyObject* TPythonType<TObjectHolder, TObject, TTraits>::GetObjectAttr(PyObject* pyObj, char* attr) {
try {
TObject* obj = CastToObject(pyObj);
PyObject* res = obj ? Instance().Attributes.GetAttrGetters().GetAttr(pyObj, *obj, attr) : nullptr;
if (res == nullptr && Instance().Attributes.GetMethodCallers().HasMethod(pyObj, obj, attr)) {
TPyObjectPtr r = NPrivate::TBoundClosure<TObject>::Instance().CreatePyObject(new NPrivate::TBoundClosureHolder<TObject>(pyObj, obj, attr, Instance().Attributes.GetMethodCallers()));
res = r.RefGet();
}
if (res == nullptr && !PyErr_Occurred())
ythrow TPyErr(PyExc_AttributeError) << "PyBind can't get attribute '" << attr << "'";
return res;
} catch (const std::exception& ex) {
PyErr_SetString(TExceptionsHolder::Instance().ToPyException(ex).Get(), ex.what());
} catch (...) {
PyErr_SetString(PyExc_RuntimeError, (TString("Unknown error occurred while trying to get attribute '") + attr + "'").c_str());
}
return nullptr;
}
template <typename TObjectHolder, typename TObject, typename TTraits>
int TPythonType<TObjectHolder, TObject, TTraits>::SetObjectAttr(PyObject* pyObj, char* attr, PyObject* value) {
try {
TObject* obj = CastToObject(pyObj);
bool res = obj ? Instance().Attributes.GetAttrSetters().SetAttr(pyObj, *obj, attr, value) : false;
if (!res && !PyErr_Occurred())
ythrow yexception() << "PyBind can't set attribute '" << attr << "'";
return res ? 0 : -1;
} catch (const std::exception& ex) {
PyErr_SetString(TExceptionsHolder::Instance().ToPyException(ex).Get(), ex.what());
} catch (...) {
PyErr_SetString(PyExc_RuntimeError, (TString("Unknown error occurred while trying to set attribute '") + attr + "'").c_str());
}
return -1;
}
template <typename TObjectHolder, typename TObject, typename TTraits>
PyObject* TPythonType<TObjectHolder, TObject, TTraits>::GetStr(PyObject* obj) {
try {
TObject* self = CastToObject(obj);
return Instance().Attributes.GetMethodCallers().CallMethod(obj, self, nullptr, nullptr, "__str__");
} catch (const std::exception& ex) {
PyErr_SetString(TExceptionsHolder::Instance().ToPyException(ex).Get(), ex.what());
} catch (...) {
PyErr_SetString(PyExc_RuntimeError, (TString("Unknown error occurred while trying to call '__str__'").c_str()));
}
return nullptr;
}
template <typename TObjectHolder, typename TObject, typename TTraits>
PyObject* TPythonType<TObjectHolder, TObject, TTraits>::GetIter(PyObject* obj) {
try {
TObject* self = CastToObject(obj);
return Instance().Attributes.GetMethodCallers().CallMethod(obj, self, nullptr, nullptr, "__iter__");
} catch (const std::exception& ex) {
PyErr_SetString(TExceptionsHolder::Instance().ToPyException(ex).Get(), ex.what());
} catch (...) {
PyErr_SetString(PyExc_RuntimeError, (TString("Unknown error occurred while trying to call '__iter__'").c_str()));
}
return nullptr;
}
template <typename TObjectHolder, typename TObject, typename TTraits>
PyObject* TPythonType<TObjectHolder, TObject, TTraits>::GetNext(PyObject* obj) {
try {
TObject* self = CastToObject(obj);
return Instance().Attributes.GetMethodCallers().CallMethod(obj, self, nullptr, nullptr, "next");
} catch (const std::exception& ex) {
PyErr_SetString(TExceptionsHolder::Instance().ToPyException(ex).Get(), ex.what());
} catch (...) {
PyErr_SetString(PyExc_RuntimeError, (TString("Unknown error occurred while trying to call 'next'").c_str()));
}
return nullptr;
}
template <typename TObjectHolder, typename TObject, typename TTraits>
PyObject* TPythonType<TObjectHolder, TObject, TTraits>::GetRepr(PyObject* obj) {
try {
TObject* self = CastToObject(obj);
return Instance().Attributes.GetMethodCallers().CallMethod(obj, self, nullptr, nullptr, "__repr__");
} catch (const std::exception& ex) {
PyErr_SetString(TExceptionsHolder::Instance().ToPyException(ex).Get(), ex.what());
} catch (...) {
PyErr_SetString(PyExc_RuntimeError, (TString("Unknown error occurred while trying to call '__repr__'").c_str()));
}
return nullptr;
}
}