#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); } }