From 24dd85fd28b002d1dc6bca331b3e9516ea55c8cf Mon Sep 17 00:00:00 2001 From: Richard A Hofer Date: Mon, 4 Jan 2021 15:18:19 -0500 Subject: [PATCH] Generate code to encode and decode nested flatbuffers in Python. (#6354) * Generate code to encode and decode nested flatbuffers in Python. * Delete accidental trailing whitespace. * Fully delete trailing whitespace. --- src/idl_gen_python.cpp | 72 +++++++++++++++++++++++++++++++++ tests/MyGame/Example/Monster.py | 26 ++++++++++++ tests/py_test.py | 29 +++++++++++++ 3 files changed, 127 insertions(+) diff --git a/src/idl_gen_python.cpp b/src/idl_gen_python.cpp index 43290d32f..c342650bb 100644 --- a/src/idl_gen_python.cpp +++ b/src/idl_gen_python.cpp @@ -408,6 +408,39 @@ class PythonGenerator : public BaseGenerator { code += "\n"; } + // Returns a nested flatbuffer as itself. + void GetVectorAsNestedFlatbuffer(const StructDef &struct_def, + const FieldDef &field, + std::string *code_ptr) { + auto nested = field.attributes.Lookup("nested_flatbuffer"); + if (!nested) { return; } // There is no nested flatbuffer. + + std::string unqualified_name = nested->constant; + std::string qualified_name = nested->constant; + auto nested_root = parser_.LookupStruct(nested->constant); + if (nested_root == nullptr) { + qualified_name = parser_.current_namespace_->GetFullyQualifiedName( + nested->constant); + nested_root = parser_.LookupStruct(qualified_name); + } + FLATBUFFERS_ASSERT(nested_root); // Guaranteed to exist by parser. + (void)nested_root; + + auto &code = *code_ptr; + GenReceiver(struct_def, code_ptr); + code += MakeCamel(NormalizedName(field)) + "NestedRoot(self):"; + + code += OffsetPrefix(field); + + code += Indent + Indent + Indent; + code += "from " + qualified_name + " import " + unqualified_name + "\n"; + code += Indent + Indent + Indent + "return " + unqualified_name; + code += ".GetRootAs" + unqualified_name; + code += "(self._tab.Bytes, self._tab.Vector(o))\n"; + code += Indent + Indent + "return 0\n"; + code += "\n"; + } + // Begin the creator function signature. void BeginBuilderArgs(const StructDef &struct_def, std::string *code_ptr) { auto &code = *code_ptr; @@ -561,6 +594,43 @@ class PythonGenerator : public BaseGenerator { code += ")\n"; } + // Set the value of one of the members of a table's vector and fills in the + // elements from a bytearray. This is for simplifying the use of nested + // flatbuffers. + void BuildVectorOfTableFromBytes(const StructDef &struct_def, + const FieldDef &field, + std::string *code_ptr) { + auto nested = field.attributes.Lookup("nested_flatbuffer"); + if (!nested) { return; } // There is no nested flatbuffer. + + std::string unqualified_name = nested->constant; + std::string qualified_name = nested->constant; + auto nested_root = parser_.LookupStruct(nested->constant); + if (nested_root == nullptr) { + qualified_name = + parser_.current_namespace_->GetFullyQualifiedName(nested->constant); + nested_root = parser_.LookupStruct(qualified_name); + } + FLATBUFFERS_ASSERT(nested_root); // Guaranteed to exist by parser. + (void)nested_root; + + auto &code = *code_ptr; + code += "def " + NormalizedName(struct_def) + "Make"; + code += MakeCamel(NormalizedName(field)); + code += "VectorFromBytes(builder, bytes):\n"; + code += Indent + "builder.StartVector("; + auto vector_type = field.value.type.VectorType(); + auto alignment = InlineAlignment(vector_type); + auto elem_size = InlineSize(vector_type); + code += NumToString(elem_size); + code += ", len(bytes), " + NumToString(alignment); + code += ")\n"; + code += Indent + "builder.head = builder.head - len(bytes)\n"; + code += Indent + "builder.Bytes[builder.head : builder.head + len(bytes)]"; + code += " = bytes\n"; + code += Indent + "return builder.EndVector(len(bytes))\n"; + } + // Get the offset of the end of a table. void GetEndOffsetOnTable(const StructDef &struct_def, std::string *code_ptr) { auto &code = *code_ptr; @@ -607,6 +677,7 @@ class PythonGenerator : public BaseGenerator { } else { GetMemberOfVectorOfNonStruct(struct_def, field, code_ptr); GetVectorOfNonStructAsNumpy(struct_def, field, code_ptr); + GetVectorAsNestedFlatbuffer(struct_def, field, code_ptr); } break; } @@ -643,6 +714,7 @@ class PythonGenerator : public BaseGenerator { BuildFieldOfTable(struct_def, field, offset, code_ptr); if (IsVector(field.value.type)) { BuildVectorOfTable(struct_def, field, code_ptr); + BuildVectorOfTableFromBytes(struct_def, field, code_ptr); } } diff --git a/tests/MyGame/Example/Monster.py b/tests/MyGame/Example/Monster.py index 0ac5cd46d..f50aa8beb 100644 --- a/tests/MyGame/Example/Monster.py +++ b/tests/MyGame/Example/Monster.py @@ -205,6 +205,14 @@ class Monster(object): return self._tab.GetVectorAsNumpy(flatbuffers.number_types.Uint8Flags, o) return 0 + # Monster + def TestnestedflatbufferNestedRoot(self): + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(30)) + if o != 0: + from MyGame.Example.Monster import Monster + return Monster.GetRootAsMonster(self._tab.Bytes, self._tab.Vector(o)) + return 0 + # Monster def TestnestedflatbufferLength(self): o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(30)) @@ -734,6 +742,14 @@ class Monster(object): return self._tab.GetVectorAsNumpy(flatbuffers.number_types.Uint8Flags, o) return 0 + # Monster + def TestrequirednestedflatbufferNestedRoot(self): + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(102)) + if o != 0: + from MyGame.Example.Monster import Monster + return Monster.GetRootAsMonster(self._tab.Bytes, self._tab.Vector(o)) + return 0 + # Monster def TestrequirednestedflatbufferLength(self): o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(102)) @@ -765,6 +781,11 @@ def MonsterStartTestarrayoftablesVector(builder, numElems): return builder.Start def MonsterAddEnemy(builder, enemy): builder.PrependUOffsetTRelativeSlot(12, flatbuffers.number_types.UOffsetTFlags.py_type(enemy), 0) def MonsterAddTestnestedflatbuffer(builder, testnestedflatbuffer): builder.PrependUOffsetTRelativeSlot(13, flatbuffers.number_types.UOffsetTFlags.py_type(testnestedflatbuffer), 0) def MonsterStartTestnestedflatbufferVector(builder, numElems): return builder.StartVector(1, numElems, 1) +def MonsterMakeTestnestedflatbufferVectorFromBytes(builder, bytes): + builder.StartVector(1, len(bytes), 1) + builder.head = builder.head - len(bytes) + builder.Bytes[builder.head : builder.head + len(bytes)] = bytes + return builder.EndVector(len(bytes)) def MonsterAddTestempty(builder, testempty): builder.PrependUOffsetTRelativeSlot(14, flatbuffers.number_types.UOffsetTFlags.py_type(testempty), 0) def MonsterAddTestbool(builder, testbool): builder.PrependBoolSlot(15, testbool, 0) def MonsterAddTesthashs32Fnv1(builder, testhashs32Fnv1): builder.PrependInt32Slot(16, testhashs32Fnv1, 0) @@ -815,6 +836,11 @@ def MonsterStartVectorOfEnumsVector(builder, numElems): return builder.StartVect def MonsterAddSignedEnum(builder, signedEnum): builder.PrependInt8Slot(48, signedEnum, -1) def MonsterAddTestrequirednestedflatbuffer(builder, testrequirednestedflatbuffer): builder.PrependUOffsetTRelativeSlot(49, flatbuffers.number_types.UOffsetTFlags.py_type(testrequirednestedflatbuffer), 0) def MonsterStartTestrequirednestedflatbufferVector(builder, numElems): return builder.StartVector(1, numElems, 1) +def MonsterMakeTestrequirednestedflatbufferVectorFromBytes(builder, bytes): + builder.StartVector(1, len(bytes), 1) + builder.head = builder.head - len(bytes) + builder.Bytes[builder.head : builder.head + len(bytes)] = bytes + return builder.EndVector(len(bytes)) def MonsterEnd(builder): return builder.EndObject() import MyGame.Example.Ability diff --git a/tests/py_test.py b/tests/py_test.py index 4fe17ac80..4ea8fe516 100644 --- a/tests/py_test.py +++ b/tests/py_test.py @@ -1874,6 +1874,35 @@ class TestAllCodePathsOfExampleSchema(unittest.TestCase): lambda: mon2.TestnestedflatbufferAsNumpy(), NumpyRequiredForThisFeature) + def test_nested_monster_testnestedflatbuffer(self): + b = flatbuffers.Builder(0) + + # build another monster to nest inside testnestedflatbuffer + nestedB = flatbuffers.Builder(0) + nameStr = nestedB.CreateString("Nested Monster") + MyGame.Example.Monster.MonsterStart(nestedB) + MyGame.Example.Monster.MonsterAddHp(nestedB, 30) + MyGame.Example.Monster.MonsterAddName(nestedB, nameStr) + nestedMon = MyGame.Example.Monster.MonsterEnd(nestedB) + nestedB.Finish(nestedMon) + + # write the nested FB bytes + sub_buf = MyGame.Example.Monster.MonsterMakeTestnestedflatbufferVectorFromBytes( + b, nestedB.Output()) + + # make the parent monster and include the bytes of the nested monster + MyGame.Example.Monster.MonsterStart(b) + MyGame.Example.Monster.MonsterAddTestnestedflatbuffer(b, sub_buf) + mon = MyGame.Example.Monster.MonsterEnd(b) + b.Finish(mon) + + # inspect the resulting data: + mon2 = MyGame.Example.Monster.Monster.GetRootAsMonster(b.Bytes, + b.Head()) + nestedMon2 = mon2.TestnestedflatbufferNestedRoot() + self.assertEqual(b"Nested Monster", nestedMon2.Name()) + self.assertEqual(30, nestedMon2.Hp()) + def test_nondefault_monster_testempty(self): b = flatbuffers.Builder(0)