#pragma once
#define PY_SSIZE_T_CLEAN
#include <Python.h>
#include <util/generic/string.h>
#include <util/generic/map.h>
#include <util/generic/set.h>
#include <util/generic/vector.h>
#include <util/generic/ptr.h>
#include <util/generic/typetraits.h>
#include <util/generic/function.h>
#include "cast.h"
namespace NPyBind {
template <typename TObjType>
class TBaseMethodCaller {
public:
virtual ~TBaseMethodCaller() {
}
virtual bool CallMethod(PyObject* owner, TObjType* self, PyObject* args, PyObject* kwargs, PyObject*& res) const = 0;
virtual bool HasMethod(PyObject*, TObjType*, const TString&, const TSet<TString>&) {
return true;
}
};
template <typename TObjType>
class TIsACaller;
template <typename TObjType>
class TMethodCallers {
private:
typedef TSimpleSharedPtr<TBaseMethodCaller<TObjType>> TCallerPtr;
typedef TVector<TCallerPtr> TCallerList;
typedef TMap<TString, TCallerList> TCallerMap;
const TSet<TString>& HiddenAttrNames;
TCallerMap Callers;
public:
TMethodCallers(const TSet<TString>& hiddenNames)
: HiddenAttrNames(hiddenNames)
{
}
void AddCaller(const TString& name, TCallerPtr caller) {
Callers[name].push_back(caller);
}
bool HasCaller(const TString& name) const {
return Callers.has(name);
}
PyObject* CallMethod(PyObject* owner, TObjType* self, PyObject* args, PyObject* kwargs, const TString& name) const {
const TCallerList* lst = Callers.FindPtr(name);
if (!lst)
return nullptr;
for (const auto& caller : *lst) {
PyObject* res = nullptr;
PyErr_Clear();
if (caller->CallMethod(owner, self, args, kwargs, res))
return res;
}
return nullptr;
}
bool HasMethod(PyObject* owner, TObjType* self, const TString& name) const {
const TCallerList* lst = Callers.FindPtr(name);
if (!lst)
return false;
for (const auto& caller : *lst) {
if (caller->HasMethod(owner, self, name, HiddenAttrNames))
return true;
}
return false;
}
void GetMethodsNames(PyObject* owner, TObjType* self, TVector<TString>& resultNames) const {
for (const auto& it : Callers) {
if (HasMethod(owner, self, it.first) && !HiddenAttrNames.contains(it.first))
resultNames.push_back(it.first);
}
}
void GetAllMethodsNames(TVector<TString>& resultNames) const {
for (const auto& it : Callers) {
resultNames.push_back(it.first);
}
}
void GetPropertiesNames(PyObject*, TObjType* self, TVector<TString>& resultNames) const {
const TCallerList* lst = Callers.FindPtr("IsA");
if (!lst)
return;
for (const auto& caller : *lst) {
TIsACaller<TObjType>* isACaller = dynamic_cast<TIsACaller<TObjType>*>(caller.Get());
if (isACaller) {
resultNames = isACaller->GetPropertiesNames(self);
return;
}
}
}
};
template <typename TObjType>
class TIsACaller: public TBaseMethodCaller<TObjType> {
private:
class TIsAChecker {
public:
virtual ~TIsAChecker() {
}
virtual bool Check(const TObjType* obj) const = 0;
};
template <typename TConcrete>
class TIsAConcreteChecker: public TIsAChecker {
public:
bool Check(const TObjType* obj) const override {
return dynamic_cast<const TConcrete*>(obj) != nullptr;
}
};
typedef TSimpleSharedPtr<TIsAChecker> TCheckerPtr;
typedef TMap<TString, TCheckerPtr> TCheckersMap;
TCheckersMap Checkers;
bool Check(const TString& name, const TObjType* obj) const {
const TCheckerPtr* checker = Checkers.FindPtr(name);
if (!checker) {
PyErr_Format(PyExc_KeyError, "unknown class name: %s", name.data());
return false;
}
return (*checker)->Check(obj);
}
protected:
TIsACaller() {
}
template <typename TConcrete>
void AddChecker(const TString& name) {
Checkers[name] = new TIsAConcreteChecker<TConcrete>;
}
public:
bool CallMethod(PyObject*, TObjType* self, PyObject* args, PyObject*, PyObject*& res) const override {
if (args == nullptr || !PyTuple_Check(args))
return false;
size_t cnt = PyTuple_Size(args);
bool result = true;
for (size_t i = 0; i < cnt; ++i) {
result = result && Check(
#if PY_MAJOR_VERSION >= 3
PyUnicode_AsUTF8(
#else
PyString_AsString(
#endif
PyTuple_GetItem(args, i)), self);
}
if (PyErr_Occurred()) {
return false;
}
res = BuildPyObject(result);
return true;
}
TVector<TString> GetPropertiesNames(const TObjType* obj) const {
TVector<TString> names;
for (const auto& it : Checkers) {
if (it.second->Check(obj)) {
names.push_back(it.first);
}
}
return names;
}
};
template <typename TObjType>
class TGenericMethodCaller: public TBaseMethodCaller<TObjType> {
private:
TString AttrName;
public:
TGenericMethodCaller(const TString& attrName)
: AttrName(attrName)
{
}
bool CallMethod(PyObject* obj, TObjType*, PyObject* args, PyObject*, PyObject*& res) const override {
auto str = NameFromString(AttrName);
PyObject* attr = PyObject_GenericGetAttr(obj, str.Get());
if (!attr)
ythrow yexception() << "Can't get generic attribute '" << AttrName << "'";
res = PyObject_CallObject(attr, args);
return res != nullptr;
}
};
template <typename TObjType, typename TSubObject>
class TSubObjectChecker: public TBaseMethodCaller<TObjType> {
public:
~TSubObjectChecker() override {
}
bool HasMethod(PyObject*, TObjType* self, const TString&, const TSet<TString>&) override {
return dynamic_cast<const TSubObject*>(self) != nullptr;
}
};
template <typename TFunctor, typename Tuple, typename ResType, typename=std::enable_if_t<!std::is_same_v<ResType, void>>>
void ApplyFunctor(TFunctor functor, Tuple resultArgs, PyObject*& res) {
res = BuildPyObject(std::move(Apply(functor, resultArgs)));
}
template <typename TFunctor, typename Tuple, typename ResType, typename=std::enable_if_t<std::is_same_v<ResType, void>>, typename=void>
void ApplyFunctor(TFunctor functor, Tuple resultArgs, PyObject*& res) {
Py_INCREF(Py_None);
res = Py_None;
Apply(functor, resultArgs);
}
template <typename TObjType, typename TResType, typename... Args>
class TFunctorCaller: public TBaseMethodCaller<TObjType> {
using TFunctor = std::function<TResType(TObjType&,Args...)>;
TFunctor Functor;
public:
explicit TFunctorCaller(TFunctor functor):
Functor(functor){}
bool CallMethod(PyObject*, TObjType* self, PyObject* args, PyObject*, PyObject*& res) const {
auto methodArgsTuple = GetArguments<Args...>(args);
auto resultArgs = std::tuple_cat(std::tie(*self), methodArgsTuple);
ApplyFunctor<TFunctor, decltype(resultArgs), TResType>(Functor, resultArgs, res);
return true;
}
};
template <typename TObjType, typename TRealType>
class TGetStateCaller: public TSubObjectChecker<TObjType, TRealType> {
protected:
TPyObjectPtr AddFromCaller(PyObject* obj, const TString& methodName) const {
PyObject* res = PyObject_CallMethod(obj, const_cast<char*>(methodName.c_str()), const_cast<char*>(""));
if (!res) {
PyErr_Clear();
return TPyObjectPtr(Py_None);
}
return TPyObjectPtr(res, true);
}
void GetStandartAttrsDictionary(PyObject* obj, TRealType*, TMap<TString, TPyObjectPtr>& dict) const {
TPyObjectPtr attrsDict(PyObject_GetAttrString(obj, "__dict__"), true);
TMap<TString, TPyObjectPtr> attrs;
if (!FromPyObject(attrsDict.Get(), attrs))
ythrow yexception() << "Can't get '__dict__' attribute";
dict.insert(attrs.begin(), attrs.end());
}
virtual void GetAttrsDictionary(PyObject* obj, TRealType* self, TMap<TString, TPyObjectPtr>& dict) const = 0;
public:
bool CallMethod(PyObject* obj, TObjType* self, PyObject* args, PyObject*, PyObject*& res) const override {
if (!ExtractArgs(args))
ythrow yexception() << "Can't parse arguments: it should be none";
TRealType* rself = dynamic_cast<TRealType*>(self);
if (!rself)
return false;
TMap<TString, TPyObjectPtr> dict;
GetAttrsDictionary(obj, rself, dict);
res = BuildPyObject(dict);
return true;
}
};
template <typename TObjType, typename TRealType>
class TSetStateCaller: public TSubObjectChecker<TObjType, TRealType> {
protected:
void SetStandartAttrsDictionary(PyObject* obj, TRealType*, TMap<TString, TPyObjectPtr>& dict) const {
TPyObjectPtr value(BuildPyObject(dict), true);
PyObject_SetAttrString(obj, "__dict__", value.Get());
}
virtual void SetAttrsDictionary(PyObject* obj, TRealType* self, TMap<TString, TPyObjectPtr>& dict) const = 0;
public:
bool CallMethod(PyObject* obj, TObjType* self, PyObject* args, PyObject*, PyObject*& res) const override {
TMap<TString, TPyObjectPtr> dict;
if (!ExtractArgs(args, dict))
ythrow yexception() << "Can't parse arguments: it should be one dictionary";
TRealType* rself = dynamic_cast<TRealType*>(self);
if (!rself)
return false;
SetAttrsDictionary(obj, rself, dict);
Py_INCREF(Py_None);
res = Py_None;
return true;
}
};
template <typename TObjType, typename TResult, typename TSubObject, typename TMethod, typename... Args>
class TAnyParameterMethodCaller: public TSubObjectChecker<TObjType, TSubObject> {
private:
TMethod Method;
public:
TAnyParameterMethodCaller(TMethod method)
: Method(method)
{
}
public:
bool CallMethod(PyObject*, TObjType* self, PyObject* args, PyObject*, PyObject*& res) const override {
TSubObject* sub = dynamic_cast<TSubObject*>(self);
if (sub == nullptr)
return false;
if (args && (!PyTuple_Check(args) || PyTuple_Size(args) != TFunctionArgs<TMethod>::Length)) {
//ythrow yexception() << "Method takes " << (size_t)(TFunctionArgs<TMethod>::Length) << " arguments, " << PyTuple_Size(args) << " provided";
return false;
}
try {
class Applicant {
public:
TResult operator()(Args... theArgs) {
return (Sub->*Method)(theArgs...);
}
TSubObject* Sub;
TMethod Method;
};
res = BuildPyObject(std::move(Apply(Applicant{sub, Method}, GetArguments<Args...>(args))));
} catch (cast_exception) {
return false;
} catch (...) {
if (PyExc_StopIteration == PyErr_Occurred()) {
// NB: it's replacement for geo_boost::python::throw_error_already_set();
return true;
}
PyErr_SetString(PyExc_RuntimeError, CurrentExceptionMessage().data());
return true;
}
return true;
}
};
template <typename TObjType, typename TSubObject, typename TMethod, typename... Args>
class TAnyParameterMethodCaller<TObjType, void, TSubObject, TMethod, Args...>: public TSubObjectChecker<TObjType, TSubObject> {
private:
TMethod Method;
public:
TAnyParameterMethodCaller(TMethod method)
: Method(method)
{
}
public:
bool CallMethod(PyObject*, TObjType* self, PyObject* args, PyObject*, PyObject*& res) const override {
TSubObject* sub = dynamic_cast<TSubObject*>(self);
if (sub == nullptr) {
return false;
}
if (args && (!PyTuple_Check(args) || PyTuple_Size(args) != TFunctionArgs<TMethod>::Length)) {
return false;
}
try {
class Applicant {
public:
void operator()(Args... theArgs) {
(Sub->*Method)(theArgs...);
}
TSubObject* Sub;
TMethod Method;
};
Apply(Applicant{sub, Method}, GetArguments<Args...>(args));
Py_INCREF(Py_None);
res = Py_None;
} catch (cast_exception) {
return false;
} catch (...) {
PyErr_SetString(PyExc_RuntimeError, CurrentExceptionMessage().data());
return true;
}
return true;
}
};
template <typename TResult, typename TSubObject, typename... Args>
struct TConstTraits {
typedef TResult (TSubObject::*TMethod)(Args... args) const;
};
template <typename TResult, typename TSubObject, typename... Args>
struct TNonConstTraits {
typedef TResult (TSubObject::*TMethod)(Args... args);
};
template <typename TObjType, typename TResult, typename TSubObject, typename TMethod, typename... Args>
class TConstMethodCaller: public TAnyParameterMethodCaller<TObjType, TResult, const TSubObject, typename TConstTraits<TResult, TSubObject, Args...>::TMethod, Args...> {
public:
TConstMethodCaller(typename TConstTraits<TResult, TSubObject, Args...>::TMethod method)
: TAnyParameterMethodCaller<TObjType, TResult, const TSubObject, typename TConstTraits<TResult, TSubObject, Args...>::TMethod, Args...>(method)
{
}
};
template <typename TObjType, typename TResult, typename TSubObject, typename... Args>
TSimpleSharedPtr<TBaseMethodCaller<TObjType>> CreateConstMethodCaller(TResult (TSubObject::*method)(Args...) const) {
return new TConstMethodCaller<TObjType, TResult, TSubObject, TResult (TSubObject::*)(Args...) const, Args...>(method);
}
template <typename TObjType, typename TResType, typename... Args>
TSimpleSharedPtr<TBaseMethodCaller<TObjType>> CreateFunctorCaller(std::function<TResType(TObjType&, Args...)> functor) {
return new TFunctorCaller<TObjType, TResType, Args...>(functor);
}
template <typename TObjType, typename TResult, typename TSubObject, typename TMethod, typename... Args>
class TMethodCaller: public TAnyParameterMethodCaller<TObjType, TResult, TSubObject, typename TNonConstTraits<TResult, TSubObject, Args...>::TMethod, Args...> {
public:
TMethodCaller(typename TNonConstTraits<TResult, TSubObject, Args...>::TMethod method)
: TAnyParameterMethodCaller<TObjType, TResult, TSubObject, typename TNonConstTraits<TResult, TSubObject, Args...>::TMethod, Args...>(method)
{
}
};
template <typename TObjType, typename TResult, typename TSubObject, typename... Args>
TSimpleSharedPtr<TBaseMethodCaller<TObjType>> CreateMethodCaller(TResult (TSubObject::*method)(Args...)) {
return new TMethodCaller<TObjType, TResult, TSubObject, TResult (TSubObject::*)(Args...), Args...>(method);
}
}