#pragma once #include #include #include #include #include #include #include #include namespace NPyBind { #define DEFINE_CONVERTERS_IMPL(TClass) \ PyObject* BuildPyObject(typename TClass::TBase&& base) { \ return TClass::BuildPyObject(std::move(base)); \ } \ PyObject* BuildPyObject(const typename TClass::TBase& base) { \ return TClass::BuildPyObject(base); \ } #define DEFINE_CONVERTERS(function) DEFINE_CONVERTERS_IMPL(TFunctionResult) #define DEFINE_TRANSFORMERS_IMPL(TClass) \ template <> \ bool ::NPyBind::FromPyObject(PyObject * obj, typename TClass::TBase * &res) { \ res = TClass::CastToObject(obj); \ return res != nullptr; \ } \ template <> \ bool ::NPyBind::FromPyObject(PyObject * obj, typename TClass::TBase const*& res) { \ res = TClass::CastToObject(obj); \ return res != nullptr; \ } #define DEFINE_TRANSFORMERS(function) DEFINE_TRANSFORMERS_IMPL(TFunctionResult) namespace Detail { struct IGetContextBase { virtual ~IGetContextBase() = default; }; } //Detail struct TPyModuleDefinition { static void InitModule(const TString& name); static TPyModuleDefinition& GetModule(); TString Name; NPyBind::TPyObjectPtr M; THashMap ClassName2Type; THashMap Class2ContextGetter; }; namespace Detail { // Manages modules lifecycle // IMPORTANT!!! Don't use it in PyBind v1 environment, it will lead to inconsistent state of v1 module // UnnamedModule-> new unnamed module stub, this stub become current module. In this case you can add functions to it // InitModuleWithName -> convert unnamed module into named one, now you can switch to it in switch, this module remains current // SwitchToModule switches to the particular module in registry, this module becomes current. class TPyModuleRegistry { private: TPyModuleRegistry(); TPyModuleRegistry(const TPyModuleRegistry&) = delete; TPyModuleRegistry& operator=(TPyModuleRegistry&) = delete; public: static TPyModuleRegistry& Get() { static TPyModuleRegistry registry; return registry; } TPyModuleDefinition& GetCurrentModule() { if (!CurrentModule) { GetUnnamedModule(); } return *CurrentModule; } TPyModuleDefinition& GetUnnamedModule() { if (!UnnamedModule) { UnnamedModule = TPyModuleDefinition(); CurrentModule = const_cast(UnnamedModule.Get()); } return *UnnamedModule; } TPyModuleDefinition& InitModuleWithName(const TString& name) { if (!UnnamedModule) { GetUnnamedModule(); } Name2Def[name] = *UnnamedModule; UnnamedModule.Clear(); CurrentModule = &Name2Def[name]; return *CurrentModule; } TPyModuleDefinition& SwitchToModuleByName(const TString& name) { Y_ENSURE(Name2Def.contains(name)); Y_ENSURE(UnnamedModule.Empty()); CurrentModule = &Name2Def[name]; return *CurrentModule; } const THashMap& GetCurrentModules() const { return Name2Def; } private: TPyModuleDefinition* CurrentModule = nullptr; TMaybe UnnamedModule;// THashMap Name2Def; }; }//Detail inline void TPyModuleDefinition::InitModule(const TString& name) { Detail::TPyModuleRegistry::Get().GetUnnamedModule() = TPyModuleDefinition{name, TModuleHolder::Instance().InitModule(name), {}, {}}; Detail::TPyModuleRegistry::Get().InitModuleWithName(name); } inline TPyModuleDefinition& TPyModuleDefinition::GetModule() { return Detail::TPyModuleRegistry::Get().GetCurrentModule(); } namespace Detail { template struct TNameCtx { TString ClassShortName; static TNameCtx& GetNameCtx() { static TNameCtx result; return result; } }; struct TParentData { PyTypeObject* ParentType; IGetContextBase* ParentContext; }; TVector GetParentTypes(const TVector& parentsData); template struct TContextImpl { TVector ParentsData; TString ClassShortName; TString ClassFullName; TString ClassDescription; TVector::TCallerPtr>> ListCallers; TVector::TGetterPtr>> ListGetters; TVector::TSetterPtr>> ListSetters; }; template struct IGetContext: public IGetContextBase { virtual ~IGetContext() = default; virtual const TContextImpl& GetContext() const = 0; }; template >> THolderClass* DoInitPureObject(const TVector&) { ythrow yexception() << "Can't create this object in pure mode from python"; } template >, typename=void> THolderClass* DoInitPureObject(const TVector&) { return new THolderClass(MakeHolder()); } using TParentClassResolver = std::function&)>; TString DefaultParentResolver(const TString&, const THashSet& parentModules); template void InitializeBasesArray(TVector& parentsData, const TParentClassResolver& parentResolver) { auto shortName = Detail::TNameCtx>::GetNameCtx().ClassShortName; THashMap module2TParentData; THashSet parentModules; for (const auto& [_, module] : TPyModuleRegistry::Get().GetCurrentModules()) { auto it = module.ClassName2Type.find(shortName); if (it != module.ClassName2Type.end()) { module2TParentData[module.Name].ParentType = it->second; module2TParentData[module.Name].ParentContext = module.Class2ContextGetter.at(shortName); parentModules.insert(module.Name); } } if (parentModules.empty()) { ythrow yexception() << "Can't find registrated PyClass for parent class"; } if (parentModules.size() == 1) { parentsData[Ind] = module2TParentData.begin()->second; return; } TString resolvedModule = parentResolver(shortName, parentModules); parentsData[Ind] = module2TParentData[resolvedModule]; } template TVector GetParentsData(const TParentClassResolver& parentResolver) { constexpr int nTypes = std::tuple_size_v; if constexpr (nTypes == 0) { return {}; } TVector parentsData(nTypes); [&parentsData, &parentResolver] (std::index_sequence) { (InitializeBasesArray(parentsData, parentResolver), ...); }(std::make_index_sequence{}); return parentsData; } template void UpdateClassNamesInModule(TPyModuleDefinition& M, const TString& name, PyTypeObject* pythonType); template void UpdateGetContextInModule(TPyModuleDefinition& M, const TString& name, IGetContextBase* base); } template struct TPyParentClassTraits { using TParentPyClasses = std::tuple; }; template struct TPyClassConfigTraits: public TPyParentClassTraits { constexpr static bool InitEnabled = InitEnabled_; constexpr static bool RawInit = false; }; template struct TPyClassRawInitConfigTraits: public TPyParentClassTraits { constexpr static bool InitEnabled = true; constexpr static bool RawInit = true; }; template class TPyClass { public: using TBase = TBaseClass; private: using TThisClass = TPyClass; using TContext = Detail::TContextImpl; struct THolder { ::THolder Holder; THolder(::THolder&& right) : Holder(std::move(right)) { } THolder(TBase&& right) : Holder(MakeHolder(std::move(right))) { } }; class TSelectedTraits: public NPyBind::TPythonType { private: using TParent = NPyBind::TPythonType; friend TParent; public: TSelectedTraits() : TParent(TThisClass::GetContext().ClassFullName.data(), TThisClass::GetContext().ClassDescription.data(), TThisClass::GetContext().ParentsData.empty() ? nullptr : TThisClass::GetContext().ParentsData[0].ParentType, Detail::GetParentTypes(TThisClass::GetContext().ParentsData)) { for (const auto& caller : TThisClass::GetContext().ListCallers) { TParent::AddCaller(caller.first, caller.second); } for (const auto& getter : TThisClass::GetContext().ListGetters) { TParent::AddGetter(getter.first, getter.second); } for (const auto& setter : TThisClass::GetContext().ListSetters) { TParent::AddSetter(setter.first, setter.second); } } static TBase* GetObject(const THolder& holder) { return holder.Holder.Get(); } static THolder* DoInitObject(PyObject* args, PyObject* kwargs) { if constexpr (TPyClassConfigTraits::InitEnabled) { if constexpr (TPyClassConfigTraits::RawInit) { static_assert(sizeof...(ConstructorArgs) == 0, "Do not pass construction args if use RawInit."); return new THolder(::MakeHolder(args, kwargs)); } else { if (args && (!PyTuple_Check(args) || PyTuple_Size(args) != sizeof...(ConstructorArgs))) { ythrow yexception() << "Method takes " << sizeof...(ConstructorArgs) << " arguments, " << PyTuple_Size(args) << " provided"; } ::THolder basePtr{std::apply([](auto&&... unpackedArgs) {return new TBase(std::forward(unpackedArgs)...); }, GetArguments(args))}; return new THolder(std::move(basePtr)); } } else { ythrow yexception() << "Can't create this object from python"; } } static THolder* DoInitPureObject(const TVector& properties) { return Detail::DoInitPureObject(properties); } static TBase* CastToObject(PyObject* obj) { return TParent::CastToObject(obj); } static PyTypeObject* GetType() { return TParent::GetPyTypePtr(); } }; class TContextHolder: public Detail::IGetContext { public: static TContextHolder& GetContextHolder() { static TContextHolder holder; return holder; } TContext& GetContext() { return Context; } const TContext& GetContext() const override { return Context; } private: TContext Context; }; template class TCallerWrapper: public TBaseMethodCaller { public: explicit TCallerWrapper(TSimpleSharedPtr> baseCaller) : BaseCaller(baseCaller) { Y_ENSURE(BaseCaller); } bool CallMethod(PyObject* owner, TDerivedClass* self, PyObject* args, PyObject* kwargs, PyObject*& res) const override { return BaseCaller->CallMethod(owner, static_cast(self), args, kwargs, res); } private: TSimpleSharedPtr> BaseCaller; }; template class TSetterWrapper: public TBaseAttrSetter { public: explicit TSetterWrapper(TSimpleSharedPtr> baseSetter) : BaseSetter(baseSetter) { Y_ENSURE(BaseSetter); } bool SetAttr(PyObject* owner, TDerivedClass& self, const TString& attr, PyObject* val) override { return BaseSetter->SetAttr(owner, static_cast(self), attr, val); } private: TSimpleSharedPtr> BaseSetter; }; template class TGetterWrapper: public TBaseAttrGetter { public: explicit TGetterWrapper(TSimpleSharedPtr> baseGetter) : BaseGetter(baseGetter) { Y_ENSURE(BaseGetter); } bool GetAttr(PyObject* owner, const TDerivedClass& self, const TString& attr, PyObject*& res) const override { return BaseGetter->GetAttr(owner, static_cast(self), attr, res); } private: TSimpleSharedPtr> BaseGetter; }; template >> void ReloadAttrsFromBase() { auto callerBasePtr = GetContext().ParentsData[Ind].ParentContext; if (auto getContextPtr = dynamic_cast*>(callerBasePtr)) { auto& ctx = getContextPtr->GetContext(); auto getUniqueNames = [](const auto& collection) { THashSet uniqueNames; for (const auto& elem : collection) { uniqueNames.insert(elem.first); } return uniqueNames; }; auto uniqueCallerNames = getUniqueNames(GetContext().ListCallers); using TConcreteCallerWrapper = TCallerWrapper; for (const auto& caller : ctx.ListCallers) { if (uniqueCallerNames.contains(caller.first)) { continue; } GetContext().ListCallers.push_back(std::make_pair(caller.first, MakeSimpleShared(caller.second))); } auto uniqueGettersNames = getUniqueNames(GetContext().ListGetters); using TConcreteGetterWrapper = TGetterWrapper; for (const auto& getter : ctx.ListGetters) { if (uniqueGettersNames.contains(getter.first)) { continue; } GetContext().ListGetters.push_back(std::make_pair(getter.first, MakeSimpleShared(getter.second))); } auto uniqueSetterNames = getUniqueNames(GetContext().ListSetters); using TConcreteSetterWrapper = TSetterWrapper; for (auto& setter : ctx.ListSetters) { if (uniqueSetterNames.contains(setter.first)) { continue; } GetContext().ListSetters.push_back(std::make_pair(setter.first, MakeSimpleShared(setter.second))); } } } template >, typename=void> void ReloadAttrsFromBase() { } template void ReloadAttrsFromBases() { [&] (std::index_sequence) { (ReloadAttrsFromBase, Ind>(), ...); }(std::make_index_sequence>{}); } void CompleteImpl() { ReloadAttrsFromBases(); TSelectedTraits::Instance().Register(M.M, GetContext().ClassShortName); } static TContext& GetContext() { return TContextHolder::GetContextHolder().GetContext(); } friend struct Detail::TContextImpl;//instead of context friend struct THolder; friend class TSelectedTraits; using TCallerFunc = std::function; class TFuncCallerWrapper: public TBaseMethodCaller { public: explicit TFuncCallerWrapper(TCallerFunc func) : Func(func) { Y_ENSURE(func); } bool CallMethod(PyObject* owner, TBaseClass* self, PyObject* args, PyObject* kwargs, PyObject*& res) const override { return Func(owner, self, args, kwargs, res); } private: mutable TCallerFunc Func; }; public: TPyClass(const TString& name, const TString& descr = "", const Detail::TParentClassResolver& parentResolver = &Detail::DefaultParentResolver) : M(TPyModuleDefinition::GetModule()) { Detail::UpdateClassNamesInModule(M, name, TSelectedTraits::GetType()); Detail::UpdateGetContextInModule(M, name, &TContextHolder::GetContextHolder()); GetContext().ClassFullName = TString::Join(M.Name, ".", name); GetContext().ClassShortName = name; GetContext().ClassDescription = descr; GetContext().ParentsData = Detail::GetParentsData(parentResolver); Detail::TNameCtx::GetNameCtx().ClassShortName = name; } template >, typename=std::enable_if_t::value>> TThisClass& Def(const TString& name, TMemberFuction t) { GetContext().ListCallers.push_back(std::make_pair(name, CreateMethodCaller(t))); return *this; } template >, typename=std::enable_if_t::value>, typename=void> TThisClass& Def(const TString& name, TMemberFuction t) { GetContext().ListCallers.push_back(std::make_pair(name, CreateConstMethodCaller(t))); return *this; } template >> TThisClass& Def(const TString& name, TMemberObject t) { GetContext().ListGetters.push_back(std::make_pair(name, CreateAttrGetter(t))); GetContext().ListSetters.push_back(std::make_pair(name, CreateAttrSetter(t))); return *this; } template TThisClass& DefByFunc(const TString& name, std::function func) { GetContext().ListCallers.push_back(std::make_pair(name, CreateFunctorCaller(func))); return *this; } TThisClass& DefByFunc(const TString& name, TCallerFunc origFunc) { GetContext().ListCallers.push_back(std::make_pair(name, MakeSimpleShared(origFunc))); return *this; } template TThisClass& DefReadonly(const TString& name, TMemberObject t, std::enable_if_t::value>* = nullptr) { GetContext().ListGetters.push_back(std::make_pair(name, CreateAttrGetter(t))); return *this; } template && std::is_member_function_pointer_v>> TThisClass& AsProperty(const TString& name, TMethodGetter getter, TMethodSetter setter) { GetContext().ListGetters.push_back(std::make_pair(name, CreateMethodAttrGetter(getter))); GetContext().ListSetters.push_back(std::make_pair(name, CreateMethodAttrSetter(setter))); return *this; } template && !std::is_member_function_pointer_v>> TThisClass& AsPropertyByFunc(const TString& name, TMethodGetter getter, TMethodSetter setter) { GetContext().ListGetters.push_back(std::make_pair(name, CreateFunctorAttrGetter(getter))); GetContext().ListSetters.push_back(std::make_pair(name, CreateFunctorAttrSetter(setter))); return *this; } template >> TThisClass& AsProperty(const TString& name, TMethodGetter getter) { GetContext().ListGetters.push_back(std::make_pair(name, CreateMethodAttrGetter(getter))); return *this; } template TThisClass& AsPropertyByFunc(const TString& name, TMethodGetter getter) { GetContext().ListGetters.push_back(std::make_pair(name, CreateFunctorAttrGetter(getter))); return *this; } TThisClass& Complete() { if (!Completed) { CompleteImpl(); Completed = true; } return *this; } public: static PyObject* BuildPyObject(TBase&& base) { return NPyBind::BuildPyObject(TSelectedTraits::Instance().CreatePyObject(new THolder(std::move(base)))); } static PyObject* BuildPyObject(const TBase& base) { return NPyBind::BuildPyObject(TSelectedTraits::Instance().CreatePyObject(new THolder(TBase(base)))); // WARN - copy } static TBase* CastToObject(PyObject* obj) { return TSelectedTraits::CastToObject(obj); } private: TPyModuleDefinition& M; bool Completed = false; }; template void DefImpl(const TString& name, const TString& descr = "") { NPyBind::TModuleHolder::Instance().AddModuleMethod::Call>(name, descr); } template void DefRawImpl(const TString& name, const TString& descr = "") { NPyBind::TModuleHolder::Instance().AddModuleMethod<[](PyObject*, PyObject* args, PyObject* kwargs) -> PyObject* { return BuildPyObject(function(args, kwargs)); }>(name, descr); } #define DefFunc(NAME, FUNC) NPyBind::DefImpl(NAME) #define DefFuncDescr(NAME, FUNC, DESCR) NPyBind::DefImpl(NAME, DESCR) #define DefRawFunc(NAME, FUNC) NPyBind::DefRawImpl(NAME) #define DefRawFuncDescr(NAME, FUNC, DESCR) NPyBind::DefRawImpl(NAME, DESCR) };