Generate code to encode and decode nested flatbuffers in Python. (#6354)
authorRichard A Hofer <rofer@google.com>
Mon, 4 Jan 2021 20:18:19 +0000 (15:18 -0500)
committerGitHub <noreply@github.com>
Mon, 4 Jan 2021 20:18:19 +0000 (12:18 -0800)
* Generate code to encode and decode nested flatbuffers in Python.

* Delete accidental trailing whitespace.

* Fully delete trailing whitespace.

src/idl_gen_python.cpp
tests/MyGame/Example/Monster.py
tests/py_test.py

index 43290d3..c342650 100644 (file)
@@ -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);
       }
     }
 
index 0ac5cd4..f50aa8b 100644 (file)
@@ -206,6 +206,14 @@ class Monster(object):
         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))
         if o != 0:
@@ -735,6 +743,14 @@ class Monster(object):
         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))
         if o != 0:
@@ -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
index 4fe17ac..4ea8fe5 100644 (file)
@@ -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)