-
Notifications
You must be signed in to change notification settings - Fork 3.4k
[Python] generate enums as IntEnum or IntFlag type when requested #8782
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from 6 commits
bdbc00d
9ef9ca6
e31c02e
6a34e34
95e948b
7a35b59
a1092bf
3ab6995
64aa094
03c31bd
b30a2bf
c968250
d9e65fc
2c18899
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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) { | ||
jtdavis777 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| if (enum_def->attributes.Lookup("big_flags")) { | ||
| imports->Import("enum", "IntFlag"); | ||
| stub << "(IntFlag)"; | ||
| } else { | ||
| imports->Import("enum", "IntEnum"); | ||
| stub << "(IntEnum)"; | ||
| } | ||
| } else { | ||
| stub << "(object)"; | ||
| } | ||
|
|
@@ -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. | ||
|
|
@@ -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); | ||
| } | ||
|
|
||
|
|
@@ -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. | ||
|
|
@@ -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"); | ||
|
|
@@ -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"; | ||
|
|
@@ -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) { | ||
|
|
@@ -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; | ||
|
|
@@ -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); | ||
|
|
||
|
|
@@ -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 | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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.
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 = | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍 nice
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
<3
There was a problem hiding this comment.
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?