summaryrefslogtreecommitdiffstats
path: root/yql/essentials/udfs/common/python
diff options
context:
space:
mode:
authorvvvv <[email protected]>2025-10-10 09:49:53 +0300
committervvvv <[email protected]>2025-10-10 10:04:09 +0300
commitc62bab8ab3141ff460f885bf2dafb922e0c19d38 (patch)
treeb37257fe1cd06a87b589992db93124d456f39152 /yql/essentials/udfs/common/python
parent172bf557598ad5d2a67c1d18ff9d4857a6b40722 (diff)
YQL-20339 Python UDF support
init commit_hash:2a30a1b920f341e1f9250df382dd951604a0894f
Diffstat (limited to 'yql/essentials/udfs/common/python')
-rw-r--r--yql/essentials/udfs/common/python/bindings/py_cast.cpp31
-rw-r--r--yql/essentials/udfs/common/python/bindings/py_linear.cpp226
-rw-r--r--yql/essentials/udfs/common/python/bindings/py_linear.h17
-rw-r--r--yql/essentials/udfs/common/python/bindings/ya.make3
-rw-r--r--yql/essentials/udfs/common/python/python3_small/test/canondata/result.json20
-rw-r--r--yql/essentials/udfs/common/python/python3_small/test/canondata/test.test_LinearDynamicFail1_/extracted12
-rw-r--r--yql/essentials/udfs/common/python/python3_small/test/canondata/test.test_LinearDynamicFail2_/extracted12
-rw-r--r--yql/essentials/udfs/common/python/python3_small/test/canondata/test.test_LinearDynamic_/results.txt28
-rw-r--r--yql/essentials/udfs/common/python/python3_small/test/canondata/test.test_Linear_/results.txt28
-rw-r--r--yql/essentials/udfs/common/python/python3_small/test/cases/Linear.cfg2
-rw-r--r--yql/essentials/udfs/common/python/python3_small/test/cases/Linear.in0
-rw-r--r--yql/essentials/udfs/common/python/python3_small/test/cases/Linear.sql15
-rw-r--r--yql/essentials/udfs/common/python/python3_small/test/cases/LinearDynamic.cfg2
-rw-r--r--yql/essentials/udfs/common/python/python3_small/test/cases/LinearDynamic.in0
-rw-r--r--yql/essentials/udfs/common/python/python3_small/test/cases/LinearDynamic.sql27
-rw-r--r--yql/essentials/udfs/common/python/python3_small/test/cases/LinearDynamicFail1.cfg3
-rw-r--r--yql/essentials/udfs/common/python/python3_small/test/cases/LinearDynamicFail1.in0
-rw-r--r--yql/essentials/udfs/common/python/python3_small/test/cases/LinearDynamicFail1.sql27
-rw-r--r--yql/essentials/udfs/common/python/python3_small/test/cases/LinearDynamicFail2.cfg3
-rw-r--r--yql/essentials/udfs/common/python/python3_small/test/cases/LinearDynamicFail2.in0
-rw-r--r--yql/essentials/udfs/common/python/python3_small/test/cases/LinearDynamicFail2.sql28
-rw-r--r--yql/essentials/udfs/common/python/python_udf/ya.make2
22 files changed, 484 insertions, 2 deletions
diff --git a/yql/essentials/udfs/common/python/bindings/py_cast.cpp b/yql/essentials/udfs/common/python/bindings/py_cast.cpp
index 42237428bb3..67524eb7890 100644
--- a/yql/essentials/udfs/common/python/bindings/py_cast.cpp
+++ b/yql/essentials/udfs/common/python/bindings/py_cast.cpp
@@ -7,6 +7,7 @@
#include "py_gil.h"
#include "py_utils.h"
#include "py_void.h"
+#include "py_linear.h"
#include "py_resource.h"
#include "py_stream.h"
#include "py_struct.h"
@@ -899,6 +900,32 @@ NUdf::TUnboxedValue FromPyNull(
throw yexception() << "Can't cast " << PyObjectRepr(value) << " to null.";
}
+TPyObjectPtr ToPyLinear(
+ const TPyCastContext::TPtr& ctx,
+ const NUdf::TType* type,
+ const NUdf::TUnboxedValuePod& value)
+{
+ const NUdf::TLinearTypeInspector inspector(*ctx->PyCtx->TypeInfoHelper, type);
+ if (inspector.IsDynamic()) {
+ return ToPyDynamicLinear(ctx, inspector.GetItemType(), value);
+ }
+
+ return ToPyObject(ctx, inspector.GetItemType(), value);
+}
+
+NUdf::TUnboxedValue FromPyLinear(
+ const TPyCastContext::TPtr& ctx,
+ const NUdf::TType* type, PyObject* value)
+{
+ const NUdf::TLinearTypeInspector inspector(*ctx->PyCtx->TypeInfoHelper, type);
+ if (inspector.IsDynamic()) {
+ TPyObjectPtr valuePtr(value, TPyObjectPtr::ADD_REF);
+ return FromPyDynamicLinear(ctx, inspector.GetItemType(), valuePtr);
+ }
+
+ return FromPyObject(ctx, inspector.GetItemType(), value);
+}
+
} // namespace
TPyObjectPtr ToPyObject(
@@ -932,6 +959,8 @@ TPyObjectPtr ToPyObject(
return ToPyVariant(ctx, type, value);
case NUdf::ETypeKind::Null:
return ToPyNull(ctx, type, value);
+ case NUdf::ETypeKind::Linear:
+ return ToPyLinear(ctx, type, value);
default: {
::TStringBuilder sb;
sb << "Failed to export: ";
@@ -972,6 +1001,8 @@ NUdf::TUnboxedValue FromPyObject(
return FromPyVariant(ctx, type, value);
case NUdf::ETypeKind::Null:
return FromPyNull(ctx, type, value);
+ case NUdf::ETypeKind::Linear:
+ return FromPyLinear(ctx, type, value);
default: {
::TStringBuilder sb;
sb << "Failed to import: ";
diff --git a/yql/essentials/udfs/common/python/bindings/py_linear.cpp b/yql/essentials/udfs/common/python/bindings/py_linear.cpp
new file mode 100644
index 00000000000..cfad0695f6a
--- /dev/null
+++ b/yql/essentials/udfs/common/python/bindings/py_linear.cpp
@@ -0,0 +1,226 @@
+#include "py_linear.h"
+#include "py_cast.h"
+#include "py_errors.h"
+#include "py_utils.h"
+#include "py_gil.h"
+
+#include <yql/essentials/public/udf/udf_value.h>
+#include <yql/essentials/public/udf/udf_value_builder.h>
+#include <yql/essentials/public/udf/udf_type_inspection.h>
+
+#include <util/string/builder.h>
+
+using namespace NKikimr;
+
+namespace NPython {
+
+//////////////////////////////////////////////////////////////////////////////
+// TPyDynamicLinear interface
+//////////////////////////////////////////////////////////////////////////////
+struct TPyDynamicLinear {
+ using TPtr = NUdf::TRefCountedPtr<TPyDynamicLinear, TPyPtrOps<TPyDynamicLinear>>;
+
+ PyObject_HEAD;
+ TPyCastContext::TPtr CastCtx;
+ const NUdf::TType* ItemType;
+ TPyCleanupListItem<NUdf::IBoxedValuePtr> Value;
+
+ inline static TPyDynamicLinear* Cast(PyObject* o) {
+ return reinterpret_cast<TPyDynamicLinear*>(o);
+ }
+
+ inline static void Dealloc(PyObject* self) {
+ delete Cast(self);
+ }
+
+ static PyObject* New(
+ const TPyCastContext::TPtr& castCtx,
+ const NUdf::TType* itemType,
+ NUdf::IBoxedValuePtr value);
+
+ static PyObject* Repr(PyObject* self);
+ static PyObject* Extract(PyObject* self, PyObject* /* arg */);
+};
+
+static PyMethodDef TPyDynamicLinearMethods[] = {
+ {"extract", TPyDynamicLinear::Extract, METH_NOARGS, nullptr},
+ {nullptr, nullptr, 0, nullptr} /* sentinel */
+};
+
+#if PY_MAJOR_VERSION >= 3
+ #define Py_TPFLAGS_HAVE_ITER 0 // NOLINT(readability-identifier-naming)
+#endif
+
+PyTypeObject PyDynamicLinearType = {
+ PyVarObject_HEAD_INIT(&PyType_Type, 0)
+ // clang-format off
+ INIT_MEMBER(tp_name, "yql.TDynamicLinear"),
+ // clang-format on
+ INIT_MEMBER(tp_basicsize, sizeof(TPyDynamicLinear)),
+ INIT_MEMBER(tp_itemsize, 0),
+ INIT_MEMBER(tp_dealloc, TPyDynamicLinear::Dealloc),
+#if PY_VERSION_HEX < 0x030800b4
+ INIT_MEMBER(tp_print, nullptr),
+#else
+ INIT_MEMBER(tp_vectorcall_offset, 0),
+#endif
+ INIT_MEMBER(tp_getattr, nullptr),
+ INIT_MEMBER(tp_setattr, nullptr),
+#if PY_MAJOR_VERSION >= 3
+ INIT_MEMBER(tp_as_async, nullptr),
+#else
+ INIT_MEMBER(tp_compare, nullptr),
+#endif
+ INIT_MEMBER(tp_repr, TPyDynamicLinear::Repr),
+ INIT_MEMBER(tp_as_number, nullptr),
+ INIT_MEMBER(tp_as_sequence, nullptr),
+ INIT_MEMBER(tp_as_mapping, nullptr),
+ INIT_MEMBER(tp_hash, nullptr),
+ INIT_MEMBER(tp_call, nullptr),
+ INIT_MEMBER(tp_str, nullptr),
+ INIT_MEMBER(tp_getattro, nullptr),
+ INIT_MEMBER(tp_setattro, nullptr),
+ INIT_MEMBER(tp_as_buffer, nullptr),
+ INIT_MEMBER(tp_flags, 0),
+ INIT_MEMBER(tp_doc, "yql.TDynamicLinear object"),
+ INIT_MEMBER(tp_traverse, nullptr),
+ INIT_MEMBER(tp_clear, nullptr),
+ INIT_MEMBER(tp_richcompare, nullptr),
+ INIT_MEMBER(tp_weaklistoffset, 0),
+ INIT_MEMBER(tp_iter, nullptr),
+ INIT_MEMBER(tp_iternext, nullptr),
+ INIT_MEMBER(tp_methods, TPyDynamicLinearMethods),
+ INIT_MEMBER(tp_members, nullptr),
+ INIT_MEMBER(tp_getset, nullptr),
+ INIT_MEMBER(tp_base, nullptr),
+ INIT_MEMBER(tp_dict, nullptr),
+ INIT_MEMBER(tp_descr_get, nullptr),
+ INIT_MEMBER(tp_descr_set, nullptr),
+ INIT_MEMBER(tp_dictoffset, 0),
+ INIT_MEMBER(tp_init, nullptr),
+ INIT_MEMBER(tp_alloc, nullptr),
+ INIT_MEMBER(tp_new, nullptr),
+ INIT_MEMBER(tp_free, nullptr),
+ INIT_MEMBER(tp_is_gc, nullptr),
+ INIT_MEMBER(tp_bases, nullptr),
+ INIT_MEMBER(tp_mro, nullptr),
+ INIT_MEMBER(tp_cache, nullptr),
+ INIT_MEMBER(tp_subclasses, nullptr),
+ INIT_MEMBER(tp_weaklist, nullptr),
+ INIT_MEMBER(tp_del, nullptr),
+ INIT_MEMBER(tp_version_tag, 0),
+#if PY_MAJOR_VERSION >= 3
+ INIT_MEMBER(tp_finalize, nullptr),
+#endif
+#if PY_VERSION_HEX >= 0x030800b1
+ INIT_MEMBER(tp_vectorcall, nullptr),
+#endif
+#if PY_VERSION_HEX >= 0x030800b4 && PY_VERSION_HEX < 0x03090000
+ INIT_MEMBER(tp_print, nullptr),
+#endif
+};
+
+PyObject* TPyDynamicLinear::New(
+ const TPyCastContext::TPtr& castCtx,
+ const NUdf::TType* itemType,
+ NUdf::IBoxedValuePtr value)
+{
+ TPyDynamicLinear* linear = new TPyDynamicLinear;
+ PyObject_INIT(linear, &PyDynamicLinearType);
+
+ linear->CastCtx = castCtx;
+ linear->ItemType = itemType;
+ linear->Value.Set(castCtx->PyCtx, value);
+
+ return reinterpret_cast<PyObject*>(linear);
+}
+
+PyObject* TPyDynamicLinear::Repr(PyObject*)
+{
+ return PyRepr("<yql.TDynamicLinear>").Release();
+}
+
+PyObject* TPyDynamicLinear::Extract(PyObject* self, PyObject* /* arg */)
+{
+ PY_TRY {
+ TPyDynamicLinear* linear = Cast(self);
+ NUdf::TUnboxedValue res;
+ if (NUdf::TBoxedValueAccessor::Next(*linear->Value.Get(), res)) {
+ return ToPyObject(linear->CastCtx, linear->ItemType, res).Release();
+ }
+
+ PyErr_SetString(PyExc_ValueError, "The linear value has already been used");
+ return nullptr;
+ }
+ PY_CATCH(nullptr)
+}
+
+class TDynamicLinearProxy: public NUdf::TBoxedValue {
+public:
+ TDynamicLinearProxy(const TPyCastContext::TPtr& castCtx, const NUdf::TType* itemType, TPyObjectPtr&& pyObject)
+ : CastCtx_(castCtx)
+ , ItemType_(itemType)
+ , PyObject_(std::move(pyObject))
+ {
+ }
+
+ ~TDynamicLinearProxy() {
+ const TPyGilLocker lock;
+ PyObject_.Reset();
+ }
+
+ bool Next(NUdf::TUnboxedValue& value) override try {
+ const TPyGilLocker lock;
+ if (Consumed_) {
+ return false;
+ }
+
+ TPyObjectPtr function(PyObject_GetAttrString(PyObject_.Get(), "extract"));
+ if (!function) {
+ throw yexception() << "Missing 'extract' attribute";
+ }
+
+ if (!PyCallable_Check(function.Get())) {
+ throw yexception() << "'extract' attribute should be a callable";
+ }
+
+ TPyObjectPtr resultObj = PyObject_CallObject(function.Get(), nullptr);
+ if (!resultObj) {
+ throw yexception() << "Failed to execute:\n"
+ << GetLastErrorAsString();
+ }
+
+ Consumed_ = true;
+ value = FromPyObject(CastCtx_, ItemType_, resultObj.Get());
+ return true;
+ } catch (const yexception& e) {
+ UdfTerminate((TStringBuilder() << CastCtx_->PyCtx->Pos << e.what()).c_str());
+ }
+
+private:
+ const TPyCastContext::TPtr CastCtx_;
+ const NUdf::TType* ItemType_;
+ TPyObjectPtr PyObject_;
+ bool Consumed_ = false;
+};
+
+//////////////////////////////////////////////////////////////////////////////
+// public functions
+//////////////////////////////////////////////////////////////////////////////
+TPyObjectPtr ToPyDynamicLinear(
+ const TPyCastContext::TPtr& castCtx,
+ const NUdf::TType* itemType,
+ const NUdf::TUnboxedValuePod& value)
+{
+ return TPyDynamicLinear::New(castCtx, itemType, value.AsBoxed());
+}
+
+NUdf::TUnboxedValue FromPyDynamicLinear(
+ const TPyCastContext::TPtr& castCtx,
+ const NUdf::TType* itemType,
+ TPyObjectPtr value)
+{
+ return NUdf::TUnboxedValuePod(new TDynamicLinearProxy(castCtx, itemType, std::move(value)));
+}
+
+} // namespace NPython
diff --git a/yql/essentials/udfs/common/python/bindings/py_linear.h b/yql/essentials/udfs/common/python/bindings/py_linear.h
new file mode 100644
index 00000000000..3ce528b7586
--- /dev/null
+++ b/yql/essentials/udfs/common/python/bindings/py_linear.h
@@ -0,0 +1,17 @@
+#pragma once
+
+#include "py_ctx.h"
+
+namespace NPython {
+
+TPyObjectPtr ToPyDynamicLinear(
+ const TPyCastContext::TPtr& castCtx,
+ const NKikimr::NUdf::TType* itemType,
+ const NKikimr::NUdf::TUnboxedValuePod& value);
+
+NKikimr::NUdf::TUnboxedValue FromPyDynamicLinear(
+ const TPyCastContext::TPtr& castCtx,
+ const NKikimr::NUdf::TType* itemType,
+ TPyObjectPtr value);
+
+} // namespace NPython
diff --git a/yql/essentials/udfs/common/python/bindings/ya.make b/yql/essentials/udfs/common/python/bindings/ya.make
index 29dca847ee9..1e5eb4cd0b0 100644
--- a/yql/essentials/udfs/common/python/bindings/ya.make
+++ b/yql/essentials/udfs/common/python/bindings/ya.make
@@ -1,6 +1,6 @@
PY23_NATIVE_LIBRARY()
-YQL_ABI_VERSION(2 27 0)
+YQL_ABI_VERSION(2 44 0)
ENABLE(YQL_STYLE_CPP)
@@ -13,6 +13,7 @@ SRCS(
py_list.cpp
py_lazy_mkql_dict.cpp
py_lazy_mkql_list.cpp
+ py_linear.cpp
py_iterator.cpp
py_resource.cpp
py_stream.cpp
diff --git a/yql/essentials/udfs/common/python/python3_small/test/canondata/result.json b/yql/essentials/udfs/common/python/python3_small/test/canondata/result.json
index 5db4c2b5055..cd5bbbcf26f 100644
--- a/yql/essentials/udfs/common/python/python3_small/test/canondata/result.json
+++ b/yql/essentials/udfs/common/python/python3_small/test/canondata/result.json
@@ -46,6 +46,26 @@
"uri": "https://{canondata_backend}/995452/085d43bbd16f44afc51d6cafed42465a3d20215c/resource.tar.gz#test.test_GreedyInputContainers_/results.txt"
}
],
+ "test.test[LinearDynamicFail1]": [
+ {
+ "uri": "file://test.test_LinearDynamicFail1_/extracted"
+ }
+ ],
+ "test.test[LinearDynamicFail2]": [
+ {
+ "uri": "file://test.test_LinearDynamicFail2_/extracted"
+ }
+ ],
+ "test.test[LinearDynamic]": [
+ {
+ "uri": "file://test.test_LinearDynamic_/results.txt"
+ }
+ ],
+ "test.test[Linear]": [
+ {
+ "uri": "file://test.test_Linear_/results.txt"
+ }
+ ],
"test.test[OptionalNested]": [
{
"uri": "file://test.test_OptionalNested_/extracted"
diff --git a/yql/essentials/udfs/common/python/python3_small/test/canondata/test.test_LinearDynamicFail1_/extracted b/yql/essentials/udfs/common/python/python3_small/test/canondata/test.test_LinearDynamicFail1_/extracted
new file mode 100644
index 00000000000..05e09cd7eda
--- /dev/null
+++ b/yql/essentials/udfs/common/python/python3_small/test/canondata/test.test_LinearDynamicFail1_/extracted
@@ -0,0 +1,12 @@
+<tmp_path>/program.sql:<main>: Error: Execution
+
+ <tmp_path>/program.sql:<main>:50:1: Error: Execution of node: Result
+ select $c($p(1));
+ ^
+ <tmp_path>/program.sql:<main>:46:14: Error: Failed to execute:
+Traceback (most recent call last):
+ File "embedded:g", line 35, in g
+ValueError: The linear value has already been used
+
+ $c = Python::g(Callable<($l)->Int32>, $s);
+ ^ \ No newline at end of file
diff --git a/yql/essentials/udfs/common/python/python3_small/test/canondata/test.test_LinearDynamicFail2_/extracted b/yql/essentials/udfs/common/python/python3_small/test/canondata/test.test_LinearDynamicFail2_/extracted
new file mode 100644
index 00000000000..462b4b5332d
--- /dev/null
+++ b/yql/essentials/udfs/common/python/python3_small/test/canondata/test.test_LinearDynamicFail2_/extracted
@@ -0,0 +1,12 @@
+<tmp_path>/program.sql:<main>: Error: Execution
+
+ <tmp_path>/program.sql:<main>:52:1: Error: Execution of node: Result
+ select $c($a,0),$c($a,1);
+ ^
+ <tmp_path>/program.sql:<main>:46:14: Error: Failed to execute:
+Traceback (most recent call last):
+ File "embedded:g", line 35, in g
+ValueError: The linear value has already been used
+
+ $c = Python::g(Callable<($l,Int32)->Int32>, $s);
+ ^ \ No newline at end of file
diff --git a/yql/essentials/udfs/common/python/python3_small/test/canondata/test.test_LinearDynamic_/results.txt b/yql/essentials/udfs/common/python/python3_small/test/canondata/test.test_LinearDynamic_/results.txt
new file mode 100644
index 00000000000..40dd4232253
--- /dev/null
+++ b/yql/essentials/udfs/common/python/python3_small/test/canondata/test.test_LinearDynamic_/results.txt
@@ -0,0 +1,28 @@
+[
+ {
+ "Write" = [
+ {
+ "Type" = [
+ "ListType";
+ [
+ "StructType";
+ [
+ [
+ "column0";
+ [
+ "DataType";
+ "Int32"
+ ]
+ ]
+ ]
+ ]
+ ];
+ "Data" = [
+ [
+ "4"
+ ]
+ ]
+ }
+ ]
+ }
+] \ No newline at end of file
diff --git a/yql/essentials/udfs/common/python/python3_small/test/canondata/test.test_Linear_/results.txt b/yql/essentials/udfs/common/python/python3_small/test/canondata/test.test_Linear_/results.txt
new file mode 100644
index 00000000000..40dd4232253
--- /dev/null
+++ b/yql/essentials/udfs/common/python/python3_small/test/canondata/test.test_Linear_/results.txt
@@ -0,0 +1,28 @@
+[
+ {
+ "Write" = [
+ {
+ "Type" = [
+ "ListType";
+ [
+ "StructType";
+ [
+ [
+ "column0";
+ [
+ "DataType";
+ "Int32"
+ ]
+ ]
+ ]
+ ]
+ ];
+ "Data" = [
+ [
+ "4"
+ ]
+ ]
+ }
+ ]
+ }
+] \ No newline at end of file
diff --git a/yql/essentials/udfs/common/python/python3_small/test/cases/Linear.cfg b/yql/essentials/udfs/common/python/python3_small/test/cases/Linear.cfg
new file mode 100644
index 00000000000..7e882f1da85
--- /dev/null
+++ b/yql/essentials/udfs/common/python/python3_small/test/cases/Linear.cfg
@@ -0,0 +1,2 @@
+langver 2025.04
+
diff --git a/yql/essentials/udfs/common/python/python3_small/test/cases/Linear.in b/yql/essentials/udfs/common/python/python3_small/test/cases/Linear.in
new file mode 100644
index 00000000000..e69de29bb2d
--- /dev/null
+++ b/yql/essentials/udfs/common/python/python3_small/test/cases/Linear.in
diff --git a/yql/essentials/udfs/common/python/python3_small/test/cases/Linear.sql b/yql/essentials/udfs/common/python/python3_small/test/cases/Linear.sql
new file mode 100644
index 00000000000..ef4aa9f0132
--- /dev/null
+++ b/yql/essentials/udfs/common/python/python3_small/test/cases/Linear.sql
@@ -0,0 +1,15 @@
+$s = @@
+def f(x):
+ return x + 1
+
+def g(x):
+ return x * 2
+@@;
+
+$l = LinearType(Int32);
+$p = Python::f(Callable<(Int32)->$l>, $s);
+$c = Python::g(Callable<($l)->Int32>, $s);
+
+select $c($p(1));
+
+
diff --git a/yql/essentials/udfs/common/python/python3_small/test/cases/LinearDynamic.cfg b/yql/essentials/udfs/common/python/python3_small/test/cases/LinearDynamic.cfg
new file mode 100644
index 00000000000..7e882f1da85
--- /dev/null
+++ b/yql/essentials/udfs/common/python/python3_small/test/cases/LinearDynamic.cfg
@@ -0,0 +1,2 @@
+langver 2025.04
+
diff --git a/yql/essentials/udfs/common/python/python3_small/test/cases/LinearDynamic.in b/yql/essentials/udfs/common/python/python3_small/test/cases/LinearDynamic.in
new file mode 100644
index 00000000000..e69de29bb2d
--- /dev/null
+++ b/yql/essentials/udfs/common/python/python3_small/test/cases/LinearDynamic.in
diff --git a/yql/essentials/udfs/common/python/python3_small/test/cases/LinearDynamic.sql b/yql/essentials/udfs/common/python/python3_small/test/cases/LinearDynamic.sql
new file mode 100644
index 00000000000..58b4a649a05
--- /dev/null
+++ b/yql/essentials/udfs/common/python/python3_small/test/cases/LinearDynamic.sql
@@ -0,0 +1,27 @@
+$s = @@
+def f(x):
+ class Once:
+ def __init__(self, v):
+ self.v = v
+ self.extracted = False
+
+ def extract(self):
+ assert not self.extracted
+ self.extracted = True
+ ret = self.v
+ self.v = None
+ return ret
+
+ return Once(x + 1)
+
+def g(x):
+ return x.extract() * 2
+@@;
+
+$l = DynamicLinearType(Int32);
+$p = Python::f(Callable<(Int32)->$l>, $s);
+$c = Python::g(Callable<($l)->Int32>, $s);
+
+select $c($p(1));
+
+
diff --git a/yql/essentials/udfs/common/python/python3_small/test/cases/LinearDynamicFail1.cfg b/yql/essentials/udfs/common/python/python3_small/test/cases/LinearDynamicFail1.cfg
new file mode 100644
index 00000000000..57e3ac59f66
--- /dev/null
+++ b/yql/essentials/udfs/common/python/python3_small/test/cases/LinearDynamicFail1.cfg
@@ -0,0 +1,3 @@
+langver 2025.04
+xfail
+
diff --git a/yql/essentials/udfs/common/python/python3_small/test/cases/LinearDynamicFail1.in b/yql/essentials/udfs/common/python/python3_small/test/cases/LinearDynamicFail1.in
new file mode 100644
index 00000000000..e69de29bb2d
--- /dev/null
+++ b/yql/essentials/udfs/common/python/python3_small/test/cases/LinearDynamicFail1.in
diff --git a/yql/essentials/udfs/common/python/python3_small/test/cases/LinearDynamicFail1.sql b/yql/essentials/udfs/common/python/python3_small/test/cases/LinearDynamicFail1.sql
new file mode 100644
index 00000000000..5a91c16589f
--- /dev/null
+++ b/yql/essentials/udfs/common/python/python3_small/test/cases/LinearDynamicFail1.sql
@@ -0,0 +1,27 @@
+$s = @@
+def f(x):
+ class Once:
+ def __init__(self, v):
+ self.v = v
+ self.extracted = False
+
+ def extract(self):
+ assert not self.extracted
+ self.extracted = True
+ ret = self.v
+ self.v = None
+ return ret
+
+ return Once(x + 1)
+
+def g(x):
+ return x.extract() * x.extract()
+@@;
+
+$l = DynamicLinearType(Int32);
+$p = Python::f(Callable<(Int32)->$l>, $s);
+$c = Python::g(Callable<($l)->Int32>, $s);
+
+select $c($p(1));
+
+
diff --git a/yql/essentials/udfs/common/python/python3_small/test/cases/LinearDynamicFail2.cfg b/yql/essentials/udfs/common/python/python3_small/test/cases/LinearDynamicFail2.cfg
new file mode 100644
index 00000000000..57e3ac59f66
--- /dev/null
+++ b/yql/essentials/udfs/common/python/python3_small/test/cases/LinearDynamicFail2.cfg
@@ -0,0 +1,3 @@
+langver 2025.04
+xfail
+
diff --git a/yql/essentials/udfs/common/python/python3_small/test/cases/LinearDynamicFail2.in b/yql/essentials/udfs/common/python/python3_small/test/cases/LinearDynamicFail2.in
new file mode 100644
index 00000000000..e69de29bb2d
--- /dev/null
+++ b/yql/essentials/udfs/common/python/python3_small/test/cases/LinearDynamicFail2.in
diff --git a/yql/essentials/udfs/common/python/python3_small/test/cases/LinearDynamicFail2.sql b/yql/essentials/udfs/common/python/python3_small/test/cases/LinearDynamicFail2.sql
new file mode 100644
index 00000000000..09610de9aa3
--- /dev/null
+++ b/yql/essentials/udfs/common/python/python3_small/test/cases/LinearDynamicFail2.sql
@@ -0,0 +1,28 @@
+$s = @@
+def f(x):
+ class Once:
+ def __init__(self, v):
+ self.v = v
+ self.extracted = False
+
+ def extract(self):
+ assert not self.extracted
+ self.extracted = True
+ ret = self.v
+ self.v = None
+ return ret
+
+ return Once(x + 1)
+
+def g(x,n):
+ return x.extract() + n
+@@;
+
+$l = DynamicLinearType(Int32);
+$p = Python::f(Callable<(Int32)->$l>, $s);
+$c = Python::g(Callable<($l,Int32)->Int32>, $s);
+
+$a = $p(1);
+select $c($a,0),$c($a,1);
+
+
diff --git a/yql/essentials/udfs/common/python/python_udf/ya.make b/yql/essentials/udfs/common/python/python_udf/ya.make
index 124f075c904..41f4514dfd0 100644
--- a/yql/essentials/udfs/common/python/python_udf/ya.make
+++ b/yql/essentials/udfs/common/python/python_udf/ya.make
@@ -1,6 +1,6 @@
PY23_NATIVE_LIBRARY()
-YQL_ABI_VERSION(2 27 0)
+YQL_ABI_VERSION(2 44 0)
ENABLE(YQL_STYLE_CPP)