py_struct.cpp 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188
  1. #include "py_struct.h"
  2. #include "py_cast.h"
  3. #include "py_errors.h"
  4. #include "py_gil.h"
  5. #include "py_utils.h"
  6. #include <yql/essentials/public/udf/udf_value.h>
  7. #include <yql/essentials/public/udf/udf_value_builder.h>
  8. #include <yql/essentials/public/udf/udf_type_inspection.h>
  9. #include <yql/essentials/public/udf/udf_terminator.h>
  10. #include <util/string/cast.h>
  11. #include <util/string/join.h>
  12. #include <util/string/builder.h>
  13. using namespace NKikimr;
  14. namespace NPython {
  15. namespace {
  16. TPyObjectPtr CreateNewStrucInstance(const TPyCastContext::TPtr& ctx, const NKikimr::NUdf::TType* type, const NUdf::TStructTypeInspector& inspector)
  17. {
  18. const auto it = ctx->StructTypes.emplace(type, TPyObjectPtr());
  19. if (it.second) {
  20. #if PY_MAJOR_VERSION >= 3
  21. std::vector<PyStructSequence_Field> fields(inspector.GetMembersCount() + 1U);
  22. for (ui32 i = 0U; i < inspector.GetMembersCount(); ++i) {
  23. fields[i] = {const_cast<char*>(inspector.GetMemberName(i).Data()), nullptr};
  24. }
  25. fields.back() = {nullptr, nullptr};
  26. PyStructSequence_Desc desc = {
  27. INIT_MEMBER(name, "yql.Struct"),
  28. INIT_MEMBER(doc, nullptr),
  29. INIT_MEMBER(fields, fields.data()),
  30. INIT_MEMBER(n_in_sequence, int(inspector.GetMembersCount()))
  31. };
  32. const auto typeObject = new PyTypeObject();
  33. if (0 > PyStructSequence_InitType2(typeObject, &desc)) {
  34. throw yexception() << "can't create struct type: " << GetLastErrorAsString();
  35. }
  36. it.first->second.ResetSteal(reinterpret_cast<PyObject*>(typeObject));
  37. }
  38. const TPyObjectPtr object = PyStructSequence_New(it.first->second.GetAs<PyTypeObject>());
  39. #else
  40. const auto className = TString("yql.Struct_") += ToString(ctx->StructTypes.size());
  41. PyObject* metaclass = (PyObject *) &PyClass_Type;
  42. const TPyObjectPtr name = PyRepr(TStringBuf(className));
  43. const TPyObjectPtr bases = PyTuple_New(0);
  44. const TPyObjectPtr dict = PyDict_New();
  45. TPyObjectPtr newClass = PyObject_CallFunctionObjArgs(
  46. metaclass, name.Get(), bases.Get(), dict.Get(),
  47. nullptr);
  48. if (!newClass) {
  49. throw yexception() << "can't create new type: " << GetLastErrorAsString();
  50. }
  51. it.first->second = std::move(newClass);
  52. }
  53. Y_UNUSED(inspector);
  54. const TPyObjectPtr object = PyInstance_New(it.first->second.Get(), nullptr, nullptr);
  55. #endif
  56. if (!object) {
  57. throw yexception() << "can't struct instance: " << GetLastErrorAsString();
  58. }
  59. return object;
  60. }
  61. }
  62. TPyObjectPtr ToPyStruct(const TPyCastContext::TPtr& ctx, const NUdf::TType* type, const NUdf::TUnboxedValuePod& value)
  63. {
  64. const NUdf::TStructTypeInspector inspector(*ctx->PyCtx->TypeInfoHelper, type);
  65. const TPyObjectPtr object = CreateNewStrucInstance(ctx, type, inspector);
  66. const auto membersCount = inspector.GetMembersCount();
  67. if (auto ptr = value.GetElements()) {
  68. for (Py_ssize_t i = 0; i < membersCount; ++i) {
  69. #if PY_MAJOR_VERSION >= 3
  70. auto item = ToPyObject(ctx, inspector.GetMemberType(i), *ptr++);
  71. PyStructSequence_SetItem(object.Get(), i, item.Release());
  72. #else
  73. const TStringBuf name = inspector.GetMemberName(i);
  74. const auto item = ToPyObject(ctx, inspector.GetMemberType(i), *ptr++);
  75. if (0 > PyObject_SetAttrString(object.Get(), name.data(), item.Get())) {
  76. throw yexception()
  77. << "Can't set attr '" << name << "' to python object: "
  78. << GetLastErrorAsString();
  79. }
  80. #endif
  81. }
  82. } else {
  83. for (Py_ssize_t i = 0; i < membersCount; ++i) {
  84. #if PY_MAJOR_VERSION >= 3
  85. auto item = ToPyObject(ctx, inspector.GetMemberType(i), value.GetElement(i));
  86. PyStructSequence_SetItem(object.Get(), i, item.Release());
  87. #else
  88. const TStringBuf name = inspector.GetMemberName(i);
  89. const auto item = ToPyObject(ctx, inspector.GetMemberType(i), value.GetElement(i));
  90. if (0 > PyObject_SetAttrString(object.Get(), name.data(), item.Get())) {
  91. throw yexception()
  92. << "Can't set attr '" << name << "' to python object: "
  93. << GetLastErrorAsString();
  94. }
  95. #endif
  96. }
  97. }
  98. return object;
  99. }
  100. NUdf::TUnboxedValue FromPyStruct(const TPyCastContext::TPtr& ctx, const NUdf::TType* type, PyObject* value)
  101. {
  102. NUdf::TUnboxedValue* items = nullptr;
  103. const NUdf::TStructTypeInspector inspector(*ctx->PyCtx->TypeInfoHelper, type);
  104. const auto membersCount = inspector.GetMembersCount();
  105. auto mkqlStruct = ctx->ValueBuilder->NewArray(membersCount, items);
  106. TVector<TString> errors;
  107. if (PyDict_Check(value)) {
  108. for (ui32 i = 0; i < membersCount; i++) {
  109. TStringBuf memberName = inspector.GetMemberName(i);
  110. auto memberType = inspector.GetMemberType(i);
  111. // borrowed reference - no need to manage ownership
  112. PyObject* item = PyDict_GetItemString(value, memberName.data());
  113. if (!item) {
  114. TPyObjectPtr bytesMemberName = PyBytes_FromStringAndSize(memberName.data(), memberName.size());
  115. item = PyDict_GetItem(value, bytesMemberName.Get());
  116. }
  117. if (!item) {
  118. if (ctx->PyCtx->TypeInfoHelper->GetTypeKind(memberType) == NUdf::ETypeKind::Optional) {
  119. items[i] = NUdf::TUnboxedValue();
  120. continue;
  121. }
  122. errors.push_back(TStringBuilder() << "Dict has no item '" << memberName << "'");
  123. continue;
  124. }
  125. try {
  126. items[i] = FromPyObject(ctx, inspector.GetMemberType(i), item);
  127. } catch (const yexception& e) {
  128. errors.push_back(TStringBuilder() << "Failed to convert dict item '" << memberName << "' - " << e.what());
  129. }
  130. }
  131. if (!errors.empty()) {
  132. throw yexception() << "Failed to convert dict to struct\n" << JoinSeq("\n", errors) << "\nDict repr: " << PyObjectRepr(value);
  133. }
  134. } else {
  135. for (ui32 i = 0; i < membersCount; i++) {
  136. TStringBuf memberName = inspector.GetMemberName(i);
  137. auto memberType = inspector.GetMemberType(i);
  138. TPyObjectPtr attr = PyObject_GetAttrString(value, memberName.data());
  139. if (!attr) {
  140. if (ctx->PyCtx->TypeInfoHelper->GetTypeKind(memberType) == NUdf::ETypeKind::Optional &&
  141. PyErr_ExceptionMatches(PyExc_AttributeError)) {
  142. PyErr_Clear();
  143. items[i] = NUdf::TUnboxedValue();
  144. continue;
  145. }
  146. errors.push_back(TStringBuilder() << "Object has no attr '" << memberName << "' , error: " << GetLastErrorAsString());
  147. continue;
  148. }
  149. try {
  150. items[i] = FromPyObject(ctx, memberType, attr.Get());
  151. } catch (const yexception& e) {
  152. errors.push_back(TStringBuilder() << "Failed to convert object attr '" << memberName << "' - " << e.what());
  153. }
  154. }
  155. if (!errors.empty()) {
  156. throw yexception() << "Failed to convert object to struct\n" << JoinSeq("\n", errors) << "\nObject repr: " << PyObjectRepr(value);
  157. }
  158. }
  159. return mkqlStruct;
  160. }
  161. }