Skip to content
Open
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions docs/source/flatc.md
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,8 @@ list of `FILES...`.

- `--python-typing` : Generate Python type annotations

- `--python-enum` : Generated enumerations inherit from `IntEnum` or `IntFlag` instead of `object`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 nice

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

<3

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You said it was compatible.. do we need this flag at all? Can't we always do the better thing?


- `--python-decode-obj-api-strings` : Decode bytes automaticaly with utf-8

Additional gRPC options:
Expand Down
2 changes: 2 additions & 0 deletions include/flatbuffers/idl.h
Original file line number Diff line number Diff line change
Expand Up @@ -712,6 +712,7 @@ struct IDLOptions {
/********************************** Python **********************************/
bool python_no_type_prefix_suffix;
bool python_typing;
bool python_enum;
bool python_decode_obj_api_strings = false;

// The target Python version. Can be one of the following:
Expand Down Expand Up @@ -855,6 +856,7 @@ struct IDLOptions {
keep_proto_id(false),
python_no_type_prefix_suffix(false),
python_typing(false),
python_enum(false),
python_gen_numpy(true),
ts_omit_entrypoint(false),
proto_id_gap_action(ProtoIdGapAction::WARNING),
Expand Down
10 changes: 10 additions & 0 deletions src/flatc.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,9 @@ const static FlatCOption flatc_options[] = {
{"", "python-no-type-prefix-suffix", "",
"Skip emission of Python functions that are prefixed with typenames"},
{"", "python-typing", "", "Generate Python type annotations"},
{"", "python-enum", "",
"Generate enum types as IntEnum and IntFlag (assumes python-version >= "
"3"},
{"", "python-version", "", "Generate code for the given Python version."},
{"", "python-decode-obj-api-strings", "",
"Decode bytes to strings for the Python Object API"},
Expand Down Expand Up @@ -694,6 +697,8 @@ FlatCOptions FlatCompiler::ParseFromCommandLineArguments(int argc,
opts.python_no_type_prefix_suffix = true;
} else if (arg == "--python-typing") {
opts.python_typing = true;
} else if (arg == "--python-enum") {
opts.python_enum = true;
} else if (arg.rfind("--python-version=", 0) == 0) {
opts.python_version =
arg.substr(std::string("--python-version=").size());
Expand Down Expand Up @@ -800,6 +805,11 @@ void FlatCompiler::ValidateOptions(const FlatCOptions& options) {
Error("no options: specify at least one generator.", true);
}

if (opts.python_enum &&
(opts.python_version.empty() || opts.python_version[0] != '3')) {
Error("--python-enum requires --python-version >= 3");
}

if (opts.cs_gen_json_serializer && !opts.generate_object_based_api) {
Error(
"--cs-gen-json-serializer requires --gen-object-api to be set as "
Expand Down
114 changes: 85 additions & 29 deletions src/idl_gen_python.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -612,9 +612,14 @@ class PythonStubGenerator {

imports->Import("typing", "cast");

if (version_.major == 3) {
imports->Import("enum", "IntEnum");
stub << "(IntEnum)";
if (parser_.opts.python_enum) {
if (enum_def->attributes.Lookup("big_flags")) {
imports->Import("enum", "IntFlag");
stub << "(IntFlag)";
} else {
imports->Import("enum", "IntEnum");
stub << "(IntEnum)";
}
} else {
stub << "(object)";
}
Expand Down Expand Up @@ -721,7 +726,20 @@ class PythonGenerator : public BaseGenerator {
// Begin enum code with a class declaration.
void BeginEnum(const EnumDef& enum_def, std::string* code_ptr) const {
auto& code = *code_ptr;
code += "class " + namer_.Type(enum_def) + "(object):\n";

code += "class " + namer_.Type(enum_def);

if (parser_.opts.python_enum) {
if (enum_def.attributes.Lookup("bit_flags")) {
code += "(IntFlag)";
} else {
code += "(IntEnum)";
}
} else {
code += "(object)";
}

code += ":\n";
}

// Starts a new line and then indents.
Expand Down Expand Up @@ -1685,6 +1703,12 @@ class PythonGenerator : public BaseGenerator {
auto& field = **it;
if (field.deprecated) continue;

// include import for enum type if used in this struct
if (IsEnum(field.value.type)) {
imports.insert(ImportMapEntry{GenPackageReference(field.value.type),
namer_.Type(*field.value.type.enum_def)});
}

GenStructAccessor(struct_def, field, code_ptr, imports);
}

Expand Down Expand Up @@ -1739,6 +1763,11 @@ class PythonGenerator : public BaseGenerator {
} else if (IsFloat(base_type)) {
return float_const_gen_.GenFloatConstant(field);
} else if (IsInteger(base_type)) {
// wrap the default value in the enum constructor to aid type hinting
if (parser_.opts.python_enum && IsEnum(field.value.type)) {
auto enum_type = namer_.Type(*field.value.type.enum_def);
return enum_type + "(" + field.value.constant + ")";
}
return field.value.constant;
} else {
// For string, struct, and table.
Expand Down Expand Up @@ -1865,7 +1894,7 @@ class PythonGenerator : public BaseGenerator {
break;
}
default:
// Scalar or sting fields.
// Scalar or string fields.
field_type = GetBasePythonTypeForScalarAndString(base_type);
if (field.IsScalarOptional()) {
import_typing_list.insert("Optional");
Expand Down Expand Up @@ -2647,6 +2676,11 @@ class PythonGenerator : public BaseGenerator {

std::string GenFieldTy(const FieldDef& field) const {
if (IsScalar(field.value.type.base_type) || IsArray(field.value.type)) {
if (parser_.opts.python_enum) {
if (IsEnum(field.value.type)) {
return namer_.Type(*field.value.type.enum_def);
}
}
const std::string ty = GenTypeBasic(field.value.type);
if (ty.find("int") != std::string::npos) {
return "int";
Expand Down Expand Up @@ -2761,7 +2795,8 @@ class PythonGenerator : public BaseGenerator {
bool generate() {
std::string one_file_code;
ImportMap one_file_imports;
if (!generateEnums(&one_file_code)) return false;

if (!generateEnums(&one_file_code, one_file_imports)) return false;
if (!generateStructs(&one_file_code, one_file_imports)) return false;

if (parser_.opts.one_file) {
Expand All @@ -2776,7 +2811,8 @@ class PythonGenerator : public BaseGenerator {
}

private:
bool generateEnums(std::string* one_file_code) const {
bool generateEnums(std::string* one_file_code,
ImportMap& one_file_imports) const {
for (auto it = parser_.enums_.vec.begin(); it != parser_.enums_.vec.end();
++it) {
auto& enum_def = **it;
Expand All @@ -2787,9 +2823,26 @@ class PythonGenerator : public BaseGenerator {
}

if (parser_.opts.one_file && !enumcode.empty()) {
if (parser_.opts.python_enum) {
if (enum_def.attributes.Lookup("bit_flags")) {
one_file_imports.insert({"enum", "IntFlag"});
} else {
one_file_imports.insert({"enum", "IntEnum"});
}
}

*one_file_code += enumcode + "\n\n";
} else {
ImportMap imports;

if (parser_.opts.python_enum) {
if (enum_def.attributes.Lookup("bit_flags")) {
imports.insert({"enum", "IntFlag"});
} else {
imports.insert({"enum", "IntEnum"});
}
}

const std::string mod =
namer_.File(enum_def, SkipFile::SuffixAndExtension);

Expand Down Expand Up @@ -2835,49 +2888,52 @@ class PythonGenerator : public BaseGenerator {
}

// Begin by declaring namespace and imports.
void BeginFile(const std::string& name_space_name, const bool needs_imports,
std::string* code_ptr, const std::string& mod,
const ImportMap& imports) const {
void BeginFile(const std::string& name_space_name,
const bool needs_default_imports, std::string* code_ptr,
const std::string& mod, const ImportMap& imports) const {
auto& code = *code_ptr;
code = code + "# " + FlatBuffersGeneratedWarning() + "\n\n";
code += "# namespace: " + name_space_name + "\n\n";

if (needs_imports) {
const std::string local_import = "." + mod;

if (needs_default_imports) {
code += "import flatbuffers\n";
if (parser_.opts.python_gen_numpy) {
code += "from flatbuffers.compat import import_numpy\n";
}
if (parser_.opts.python_typing) {
code += "from typing import Any\n";

for (auto import_entry : imports) {
// If we have a file called, say, "MyType.py" and in it we have a
// class "MyType", we can generate imports -- usually when we
// have a type that contains arrays of itself -- of the type
// "from .MyType import MyType", which Python can't resolve. So
// if we are trying to import ourself, we skip.
if (import_entry.first != local_import) {
code += "from " + import_entry.first + " import " +
import_entry.second + "\n";
}
}
}
if (parser_.opts.python_gen_numpy) {
code += "np = import_numpy()\n\n";
}
for (auto import_entry : imports) {
const std::string local_import = "." + mod;

// If we have a file called, say, "MyType.py" and in it we have a
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems a separate fix? Usually good to keep those in separate PRs, though no biggie keeping it here for now.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh I think this is totally unrelated -- just a result of my poor branch management it seems. will remove.

// class "MyType", we can generate imports -- usually when we
// have a type that contains arrays of itself -- of the type
// "from .MyType import MyType", which Python can't resolve. So
// if we are trying to import ourself, we skip.
if (import_entry.first != local_import) {
code += "from " + import_entry.first + " import " +
import_entry.second + "\n";
}
}

code += "\n";

if (needs_default_imports && parser_.opts.python_gen_numpy) {
code += "np = import_numpy()\n\n";
}
}

// Save out the generated code for a Python Table type.
bool SaveType(const std::string& defname, const Namespace& ns,
const std::string& classcode, const ImportMap& imports,
const std::string& mod, bool needs_imports) const {
const std::string& mod, bool needs_default_imports) const {
if (classcode.empty()) return true;

std::string code = "";
BeginFile(LastNamespacePart(ns), needs_imports, &code, mod, imports);
BeginFile(LastNamespacePart(ns), needs_default_imports, &code, mod,
imports);
code += classcode;

const std::string directories =
Expand Down
7 changes: 7 additions & 0 deletions tests/PythonTest.sh
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,18 @@ runtime_library_dir=${test_dir}/../python

# 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 --python-enum --python-version 3
${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 --python-typing --gen-compare
${test_dir}/../flatc -p -o ${gen_code_path} -I include_test monster_extra.fbs --gen-object-api --python-typing --gen-compare --python-enum --python-version 3
${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 arrays_test.fbs --gen-object-api --python-enum --python-version 3
${test_dir}/../flatc -p -o ${gen_code_path} -I include_test arrays_test.fbs --gen-object-api --python-enum --python-typing --python-version 3
${test_dir}/../flatc -p -o ${gen_code_path} -I include_test arrays_test.fbs --gen-object-api --python-enum --python-typing --gen-onefile --python-version 3
${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 nested_union_test.fbs --gen-object-api --python-typing --python-decode-obj-api-strings --python-enum --python-version 3
${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} -I include_test service_test.fbs --grpc --grpc-python-typed-handlers --python-typing --python-enum --no-python-gen-numpy --gen-onefile --python-version 3

# Syntax: run_tests <interpreter> <benchmark vtable dedupes>
# <benchmark read count> <benchmark build count>
Expand Down