[Python] Fix various codegen problems (#8292)

* [Python] Fix various codegen problems.

This includes:

-  escaping keywords happens **after** converting the case:
   - currently, `table ClassT` generate `class = Class()` which is invalid Python;
-  imports in `one_file` mode use the filename rather than the type name when resolving module names;
-  use `filename_suffix` instead of the hardcoded `_generated` one;
-  generate empty files if no structs or enums are available. This makes the set of output files more predictable for Bazel.

* [Python] Fix various codegen problems.

This includes:

-  escaping keywords happens **after** converting the case:
   - currently, `table ClassT` generate `class = Class()` which is invalid Python;
-  imports in `one_file` mode use the filename rather than the type name when resolving module names;
-  use `filename_suffix` instead of the hardcoded `_generated` one;
-  generate empty files if no structs or enums are available. This makes the set of output files more predictable for Bazel.
diff --git a/src/idl_gen_python.cpp b/src/idl_gen_python.cpp
index 9e16275..a6bc0f5 100644
--- a/src/idl_gen_python.cpp
+++ b/src/idl_gen_python.cpp
@@ -18,7 +18,9 @@
 
 #include "idl_gen_python.h"
 
+#include <algorithm>
 #include <cctype>
+#include <cstdio>
 #include <set>
 #include <string>
 #include <unordered_set>
@@ -56,7 +58,7 @@
            /*variable=*/Case::kLowerCamel,
            /*variants=*/Case::kKeep,
            /*enum_variant_seperator=*/".",
-           /*escape_keywords=*/Namer::Config::Escape::BeforeConvertingCase,
+           /*escape_keywords=*/Namer::Config::Escape::AfterConvertingCase,
            /*namespaces=*/Case::kKeep,  // Packages in python.
            /*namespace_seperator=*/".",
            /*object_prefix=*/"",
@@ -424,16 +426,29 @@
     code += Indent + Indent + "return None\n\n";
   }
 
+  template <typename T>
+  std::string ModuleFor(const T *def) const {
+    if (!parser_.opts.one_file) {
+      return namer_.NamespacedType(*def);
+    }
+
+    std::string filename =
+        StripExtension(def->file) + parser_.opts.filename_suffix;
+    if (parser_.file_being_parsed_ == def->file) {
+      return "." + StripPath(filename);  // make it a "local" import
+    }
+
+    std::string module = parser_.opts.include_prefix + filename;
+    std::replace(module.begin(), module.end(), '/', '.');
+    return module;
+  }
+
   // Generate the package reference when importing a struct or enum from its
   // module.
   std::string GenPackageReference(const Type &type) const {
-    if (type.struct_def) {
-      return namer_.NamespacedType(*type.struct_def);
-    } else if (type.enum_def) {
-      return namer_.NamespacedType(*type.enum_def);
-    } else {
-      return "." + GenTypeGet(type);
-    }
+    if (type.struct_def) return ModuleFor(type.struct_def);
+    if (type.enum_def) return ModuleFor(type.enum_def);
+    return "." + GenTypeGet(type);
   }
 
   // Get the value of a vector's struct member.
@@ -2021,7 +2036,7 @@
     if (!generateStructs(&one_file_code, one_file_imports)) return false;
 
     if (parser_.opts.one_file) {
-      const std::string mod = file_name_ + "_generated";
+      const std::string mod = file_name_ + parser_.opts.filename_suffix;
 
       // Legacy file format uses keep casing.
       return SaveType(mod + ".py", *parser_.current_namespace_, one_file_code,
@@ -2122,11 +2137,13 @@
   bool SaveType(const std::string &defname, const Namespace &ns,
                 const std::string &classcode, const ImportMap &imports,
                 const std::string &mod, bool needs_imports) const {
-    if (!classcode.length()) return true;
-
     std::string code = "";
-    BeginFile(LastNamespacePart(ns), needs_imports, &code, mod, imports);
-    code += classcode;
+    if (classcode.empty()) {
+      BeginFile(LastNamespacePart(ns), false, &code, "", {});
+    } else {
+      BeginFile(LastNamespacePart(ns), needs_imports, &code, mod, imports);
+      code += classcode;
+    }
 
     const std::string directories =
         parser_.opts.one_file ? path_ : namer_.Directories(ns.components);
diff --git a/tests/FromInclude.py b/tests/FromInclude.py
new file mode 100644
index 0000000..0476017
--- /dev/null
+++ b/tests/FromInclude.py
@@ -0,0 +1,4 @@
+# automatically generated by the FlatBuffers compiler, do not modify
+
+# namespace: OtherNameSpace
+
diff --git a/tests/MyGame/OtherNameSpace/FromInclude.py b/tests/MyGame/OtherNameSpace/FromInclude.py
new file mode 100644
index 0000000..0476017
--- /dev/null
+++ b/tests/MyGame/OtherNameSpace/FromInclude.py
@@ -0,0 +1,4 @@
+# automatically generated by the FlatBuffers compiler, do not modify
+
+# namespace: OtherNameSpace
+
diff --git a/tests/MyGame/OtherNameSpace/TableB.py b/tests/MyGame/OtherNameSpace/TableB.py
new file mode 100644
index 0000000..0476017
--- /dev/null
+++ b/tests/MyGame/OtherNameSpace/TableB.py
@@ -0,0 +1,4 @@
+# automatically generated by the FlatBuffers compiler, do not modify
+
+# namespace: OtherNameSpace
+
diff --git a/tests/MyGame/OtherNameSpace/Unused.py b/tests/MyGame/OtherNameSpace/Unused.py
new file mode 100644
index 0000000..0476017
--- /dev/null
+++ b/tests/MyGame/OtherNameSpace/Unused.py
@@ -0,0 +1,4 @@
+# automatically generated by the FlatBuffers compiler, do not modify
+
+# namespace: OtherNameSpace
+
diff --git a/tests/MyGame/OtherNameSpace/__init__.py b/tests/MyGame/OtherNameSpace/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tests/MyGame/OtherNameSpace/__init__.py
diff --git a/tests/PythonTest.sh b/tests/PythonTest.sh
index 84e8201..e7199b8 100755
--- a/tests/PythonTest.sh
+++ b/tests/PythonTest.sh
@@ -24,9 +24,9 @@
 # Emit Python code for the example schema in the test dir:
 ${test_dir}/../flatc -p -o ${gen_code_path} -I include_test monster_test.fbs --gen-object-api
 ${test_dir}/../flatc -p -o ${gen_code_path} -I include_test monster_test.fbs --gen-object-api --gen-onefile
-${test_dir}/../flatc -p -o ${gen_code_path} -I include_test monster_extra.fbs --gen-object-api
-${test_dir}/../flatc -p -o ${gen_code_path} -I include_test arrays_test.fbs --gen-object-api
-${test_dir}/../flatc -p -o ${gen_code_path} -I include_test nested_union_test.fbs --gen-object-api
+${test_dir}/../flatc -p -o ${gen_code_path} -I include_test monster_extra.fbs --gen-object-api --python-typing --gen-compare
+${test_dir}/../flatc -p -o ${gen_code_path} -I include_test arrays_test.fbs --gen-object-api --python-typing
+${test_dir}/../flatc -p -o ${gen_code_path} -I include_test nested_union_test.fbs --gen-object-api --python-typing
 
 # Syntax: run_tests <interpreter> <benchmark vtable dedupes>
 #                   <benchmark read count> <benchmark build count>
diff --git a/tests/TableA.py b/tests/TableA.py
new file mode 100644
index 0000000..ca9b83e
--- /dev/null
+++ b/tests/TableA.py
@@ -0,0 +1,4 @@
+# automatically generated by the FlatBuffers compiler, do not modify
+
+# namespace: 
+
diff --git a/tests/__init__.py b/tests/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tests/__init__.py