From 93adb0b832e9d064b7fdfbd9cc28584f21526f9e Mon Sep 17 00:00:00 2001 From: steadytao Date: Fri, 3 Apr 2026 15:33:51 +1000 Subject: [PATCH] Fix Python object API packing with no type prefixes Make Python object API Pack generation respect --python-no-type-prefix-suffix for table start, add, end, and vector helper calls. Add a regression schema and Python test covering the nested-table Pack path reported in #8715. Fixes #8715 --- src/idl_gen_python.cpp | 79 +++++++++++++-------- tests/PythonTest.sh | 1 + tests/py_test.py | 19 +++++ tests/python_no_type_prefix_suffix_test.fbs | 10 +++ 4 files changed, 80 insertions(+), 29 deletions(-) create mode 100644 tests/python_no_type_prefix_suffix_test.fbs diff --git a/src/idl_gen_python.cpp b/src/idl_gen_python.cpp index fa3787088b..72ac76a58b 100644 --- a/src/idl_gen_python.cpp +++ b/src/idl_gen_python.cpp @@ -2320,6 +2320,33 @@ class PythonGenerator : public BaseGenerator { code += ")\n"; } + std::string TableStartFunctionName(const StructDef& struct_def) const { + return parser_.opts.python_no_type_prefix_suffix + ? "Start" + : namer_.Type(struct_def) + "Start"; + } + + std::string TableAddFunctionName(const StructDef& struct_def, + const FieldDef& field) const { + return parser_.opts.python_no_type_prefix_suffix + ? "Add" + namer_.Method(field) + : namer_.Type(struct_def) + "Add" + namer_.Method(field); + } + + std::string TableStartVectorFunctionName(const StructDef& struct_def, + const FieldDef& field) const { + return parser_.opts.python_no_type_prefix_suffix + ? "Start" + namer_.Method(field) + "Vector" + : namer_.Type(struct_def) + "Start" + namer_.Method(field) + + "Vector"; + } + + std::string TableEndFunctionName(const StructDef& struct_def) const { + return parser_.opts.python_no_type_prefix_suffix + ? "End" + : namer_.Type(struct_def) + "End"; + } + void GenPackForStructVectorField(const StructDef& struct_def, const FieldDef& field, std::string* code_prefix_ptr, @@ -2327,14 +2354,14 @@ class PythonGenerator : public BaseGenerator { auto& code_prefix = *code_prefix_ptr; auto& code = *code_ptr; const auto field_field = namer_.Field(field); - const auto struct_type = namer_.Type(struct_def); - const auto field_method = namer_.Method(field); + const auto start_vector = TableStartVectorFunctionName(struct_def, field); + const auto add_field = TableAddFunctionName(struct_def, field); // Creates the field. code_prefix += GenIndents(2) + "if self." + field_field + " is not None:"; if (field.value.type.struct_def->fixed) { - code_prefix += GenIndents(3) + struct_type + "Start" + field_method + - "Vector(builder, len(self." + field_field + "))"; + code_prefix += GenIndents(3) + start_vector + "(builder, len(self." + + field_field + "))"; code_prefix += GenIndents(3) + "for i in reversed(range(len(self." + field_field + "))):"; code_prefix += @@ -2349,8 +2376,8 @@ class PythonGenerator : public BaseGenerator { code_prefix += GenIndents(4) + field_field + "list.append(self." + field_field + "[i].Pack(builder))"; - code_prefix += GenIndents(3) + struct_type + "Start" + field_method + - "Vector(builder, len(self." + field_field + "))"; + code_prefix += GenIndents(3) + start_vector + "(builder, len(self." + + field_field + "))"; code_prefix += GenIndents(3) + "for i in reversed(range(len(self." + field_field + "))):"; code_prefix += GenIndents(4) + "builder.PrependUOffsetTRelative" + "(" + @@ -2360,8 +2387,7 @@ class PythonGenerator : public BaseGenerator { // Adds the field into the struct. code += GenIndents(2) + "if self." + field_field + " is not None:"; - code += GenIndents(3) + struct_type + "Add" + field_method + "(builder, " + - field_field + ")"; + code += GenIndents(3) + add_field + "(builder, " + field_field + ")"; } void GenPackForScalarVectorFieldHelper(const StructDef& struct_def, @@ -2370,12 +2396,11 @@ class PythonGenerator : public BaseGenerator { int indents) const { auto& code = *code_ptr; const auto field_field = namer_.Field(field); - const auto field_method = namer_.Method(field); - const auto struct_type = namer_.Type(struct_def); + const auto start_vector = TableStartVectorFunctionName(struct_def, field); const auto vectortype = field.value.type.VectorType(); - code += GenIndents(indents) + struct_type + "Start" + field_method + - "Vector(builder, len(self." + field_field + "))"; + code += GenIndents(indents) + start_vector + "(builder, len(self." + + field_field + "))"; code += GenIndents(indents) + "for i in reversed(range(len(self." + field_field + "))):"; code += GenIndents(indents + 1) + "builder.Prepend"; @@ -2433,12 +2458,11 @@ class PythonGenerator : public BaseGenerator { auto& code_prefix = *code_prefix_ptr; const auto field_field = namer_.Field(field); const auto field_method = namer_.Method(field); - const auto struct_type = namer_.Type(struct_def); + const auto add_field = TableAddFunctionName(struct_def, field); // Adds the field into the struct. code += GenIndents(2) + "if self." + field_field + " is not None:"; - code += GenIndents(3) + struct_type + "Add" + field_method + "(builder, " + - field_field + ")"; + code += GenIndents(3) + add_field + "(builder, " + field_field + ")"; // Creates the field. code_prefix += GenIndents(2) + "if self." + field_field + " is not None:"; @@ -2481,8 +2505,7 @@ class PythonGenerator : public BaseGenerator { auto& code_prefix = *code_prefix_ptr; auto& code = *code_ptr; const auto field_field = namer_.Field(field); - const auto field_method = namer_.Method(field); - const auto struct_type = namer_.Type(struct_def); + const auto add_field = TableAddFunctionName(struct_def, field); if (field.value.type.struct_def->fixed) { // Pure struct fields need to be created along with their parent @@ -2498,8 +2521,7 @@ class PythonGenerator : public BaseGenerator { code += GenIndents(2) + "if self." + field_field + " is not None:"; } - code += GenIndents(3) + struct_type + "Add" + field_method + "(builder, " + - field_field + ")"; + code += GenIndents(3) + add_field + "(builder, " + field_field + ")"; } void GenPackForUnionField(const StructDef& struct_def, const FieldDef& field, @@ -2508,16 +2530,14 @@ class PythonGenerator : public BaseGenerator { auto& code_prefix = *code_prefix_ptr; auto& code = *code_ptr; const auto field_field = namer_.Field(field); - const auto field_method = namer_.Method(field); - const auto struct_type = namer_.Type(struct_def); + const auto add_field = TableAddFunctionName(struct_def, field); // TODO(luwa): TypeT should be moved under the None check as well. code_prefix += GenIndents(2) + "if self." + field_field + " is not None:"; code_prefix += GenIndents(3) + field_field + " = self." + field_field + ".Pack(builder)"; code += GenIndents(2) + "if self." + field_field + " is not None:"; - code += GenIndents(3) + struct_type + "Add" + field_method + "(builder, " + - field_field + ")"; + code += GenIndents(3) + add_field + "(builder, " + field_field + ")"; } void GenPackForTable(const StructDef& struct_def, @@ -2525,11 +2545,12 @@ class PythonGenerator : public BaseGenerator { auto& code_base = *code_ptr; std::string code, code_prefix; const auto struct_var = namer_.Variable(struct_def); - const auto struct_type = namer_.Type(struct_def); + const auto start_table = TableStartFunctionName(struct_def); + const auto end_table = TableEndFunctionName(struct_def); GenReceiverForObjectAPI(struct_def, code_ptr); code_base += "Pack(self, builder):"; - code += GenIndents(2) + struct_type + "Start(builder)"; + code += GenIndents(2) + start_table + "(builder)"; for (auto it = struct_def.fields.vec.begin(); it != struct_def.fields.vec.end(); ++it) { auto& field = **it; @@ -2558,26 +2579,26 @@ class PythonGenerator : public BaseGenerator { break; } case BASE_TYPE_STRING: { + const auto add_field = TableAddFunctionName(struct_def, field); code_prefix += GenIndents(2) + "if self." + field_field + " is not None:"; code_prefix += GenIndents(3) + field_field + " = builder.CreateString(self." + field_field + ")"; code += GenIndents(2) + "if self." + field_field + " is not None:"; - code += GenIndents(3) + struct_type + "Add" + field_method + - "(builder, " + field_field + ")"; + code += GenIndents(3) + add_field + "(builder, " + field_field + ")"; break; } default: // Generates code for scalar values. If the value equals to the // default value, builder will automatically ignore it. So we don't // need to check the value ahead. - code += GenIndents(2) + struct_type + "Add" + field_method + + code += GenIndents(2) + TableAddFunctionName(struct_def, field) + "(builder, self." + field_field + ")"; break; } } - code += GenIndents(2) + struct_var + " = " + struct_type + "End(builder)"; + code += GenIndents(2) + struct_var + " = " + end_table + "(builder)"; code += GenIndents(2) + "return " + struct_var; code_base += code_prefix + code; diff --git a/tests/PythonTest.sh b/tests/PythonTest.sh index e7806bd429..1f411ec4f2 100755 --- a/tests/PythonTest.sh +++ b/tests/PythonTest.sh @@ -29,6 +29,7 @@ ${test_dir}/../flatc -p -o ${gen_code_path} -I include_test arrays_test.fbs --ge ${test_dir}/../flatc -p -o ${gen_code_path} -I include_test nested_union_test.fbs --gen-object-api --python-typing --python-decode-obj-api-strings ${test_dir}/../flatc -p -o ${gen_code_path} -I include_test service_test.fbs --grpc --grpc-python-typed-handlers --python-typing --no-python-gen-numpy --gen-onefile ${test_dir}/../flatc -p -o ${gen_code_path} union_name_test.fbs --gen-object-api +${test_dir}/../flatc -p -o ${gen_code_path} python_no_type_prefix_suffix_test.fbs --gen-object-api --python-no-type-prefix-suffix # Syntax: run_tests # diff --git a/tests/py_test.py b/tests/py_test.py index 0be6177855..17177b348d 100644 --- a/tests/py_test.py +++ b/tests/py_test.py @@ -55,6 +55,8 @@ import monster_test_generated # the one-file version import optional_scalars import optional_scalars.ScalarStuff +import TableBoo +import TableFoo import union_name_test.Container # refers to generated code import union_name_test.Foo # refers to generated code import union_name_test.Bar # refers to generated code @@ -340,6 +342,23 @@ def test_optional_scalars_with_pack_and_unpack(self): self.assertTrue(optsT2.maybeEnum is None) self.assertTrue(optsT2.defaultU64 == 42) + def test_python_no_type_prefix_suffix_with_pack(self): + fooT = TableFoo.TableFooT() + fooT.field1 = 7 + fooT.field2 = 'hello' + + booT = TableBoo.TableBooT() + booT.table1 = fooT + + builder = flatbuffers.Builder(0) + builder.Finish(booT.Pack(builder)) + + boo = TableBoo.TableBoo.GetRootAs(builder.Bytes, builder.Head()) + foo = boo.Table1() + + self.assertEqual(foo.Field1(), 7) + self.assertEqual(foo.Field2().decode('utf-8'), 'hello') + class TestAllMutableCodePathsOfExampleSchema(unittest.TestCase): """Tests the object API generated for monster_test.fbs for mutation diff --git a/tests/python_no_type_prefix_suffix_test.fbs b/tests/python_no_type_prefix_suffix_test.fbs new file mode 100644 index 0000000000..effab11a03 --- /dev/null +++ b/tests/python_no_type_prefix_suffix_test.fbs @@ -0,0 +1,10 @@ +table TableFoo { + field1:uint8; + field2:string; +} + +table TableBoo { + table1:TableFoo; +} + +root_type TableBoo;