in compiler/cpp/src/thrift/generate/t_py_generator.cc [804:1067]
void t_py_generator::generate_py_struct_definition(ostream& out,
t_struct* tstruct,
bool is_exception) {
const vector<t_field*>& members = tstruct->get_members();
const vector<t_field*>& sorted_members = tstruct->get_sorted_members();
vector<t_field*>::const_iterator m_iter;
out << '\n' << '\n' << "class " << tstruct->get_name();
if (is_exception) {
if (gen_dynamic_) {
if (is_immutable(tstruct)) {
out << "(" << gen_dynbaseclass_frozen_exc_ << ")";
} else {
out << "(" << gen_dynbaseclass_exc_ << ")";
}
} else {
out << "(TException)";
}
} else if (gen_dynamic_) {
if (is_immutable(tstruct)) {
out << "(" << gen_dynbaseclass_frozen_ << ")";
} else {
out << "(" << gen_dynbaseclass_ << ")";
}
} else if (gen_newstyle_) {
out << "(object)";
}
out << ":" << '\n';
indent_up();
generate_python_docstring(out, tstruct);
std::string thrift_spec_type = gen_type_hints_ ? ": typing.Any" : "";
out << indent() << "thrift_spec" << thrift_spec_type << " = None" << '\n';
out << '\n';
/*
Here we generate the structure specification for the fastbinary codec.
These specifications have the following structure:
thrift_spec -> tuple of item_spec
item_spec -> None | (tag, type_enum, name, spec_args, default)
tag -> integer
type_enum -> TType.I32 | TType.STRING | TType.STRUCT | ...
name -> string_literal
default -> None # Handled by __init__
spec_args -> None # For simple types
| (type_enum, spec_args) # Value type for list/set
| (type_enum, spec_args, type_enum, spec_args)
# Key and value for map
| (class_name, spec_args_ptr) # For struct/exception
class_name -> identifier # Basically a pointer to the class
spec_args_ptr -> expression # just class_name.spec_args
TODO(dreiss): Consider making this work for structs with negative tags.
*/
if (gen_slots_) {
indent(out) << "__slots__ = (" << '\n';
indent_up();
for (m_iter = sorted_members.begin(); m_iter != sorted_members.end(); ++m_iter) {
indent(out) << "'" << (*m_iter)->get_name() << "'," << '\n';
}
indent_down();
indent(out) << ")" << '\n' << '\n';
}
// TODO(dreiss): Look into generating an empty tuple instead of None
// for structures with no members.
// TODO(dreiss): Test encoding of structs where some inner structs
// don't have thrift_spec.
if (members.size() > 0) {
out << '\n';
out << indent() << "def __init__(self,";
for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) {
out << " " << declare_argument(*m_iter) << ",";
}
out << "):" << '\n';
indent_up();
for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) {
// Initialize fields
t_type* type = (*m_iter)->get_type();
if (!type->is_base_type() && !type->is_enum() && (*m_iter)->get_value() != nullptr) {
indent(out) << "if " << (*m_iter)->get_name() << " is "
<< "self.thrift_spec[" << (*m_iter)->get_key() << "][4]:" << '\n';
indent_up();
indent(out) << (*m_iter)->get_name() << " = " << render_field_default_value(*m_iter)
<< '\n';
indent_down();
}
if (is_immutable(tstruct)) {
if (gen_enum_ && type->is_enum()) {
indent(out) << "super(" << tstruct->get_name() << ", self).__setattr__('"
<< (*m_iter)->get_name() << "', " << (*m_iter)->get_name()
<< " if hasattr(" << (*m_iter)->get_name() << ", 'value') else "
<< type_name(type) << ".__members__.get(" << (*m_iter)->get_name() << "))" << '\n';
} else if (gen_newstyle_ || gen_dynamic_) {
indent(out) << "super(" << tstruct->get_name() << ", self).__setattr__('"
<< (*m_iter)->get_name() << "', " << (*m_iter)->get_name() << ")" << '\n';
} else {
indent(out) << "self.__dict__['" << (*m_iter)->get_name()
<< "'] = " << (*m_iter)->get_name() << '\n';
}
} else {
indent(out) << "self." << (*m_iter)->get_name()
<< member_hint((*m_iter)->get_type(), (*m_iter)->get_req()) << " = "
<< (*m_iter)->get_name() << '\n';
}
}
indent_down();
}
if (is_immutable(tstruct)) {
out << '\n';
out << indent() << "def __setattr__(self, *args):" << '\n';
indent_up();
// Not user-provided fields should be editable so that the Python Standard Library can edit
// internal fields of std library base classes. For example, in Python 3.11 ContextManager
// edits the `__traceback__` field on Exceptions. Allowing this to work with `__slots__` is
// trivial because we know which fields are user-provided, without slots we need to build a
// way to know which fields are user-provided.
if (gen_slots_ && !gen_dynamic_) {
out << indent() << "if args[0] not in self.__slots__:" << '\n';
indent_up();
out << indent() << "super().__setattr__(*args)" << '\n'
<< indent() << "return" << '\n';
indent_down();
}
out << indent() << "raise TypeError(\"can't modify immutable instance\")" << '\n';
indent_down();
out << '\n';
out << indent() << "def __delattr__(self, *args):" << '\n';
indent_up();
// Not user-provided fields should be editable so that the Python Standard Library can edit
// internal fields of std library base classes. For example, in Python 3.11 ContextManager
// edits the `__traceback__` field on Exceptions. Allowing this to work with `__slots__` is
// trivial because we know which fields are user-provided, without slots we need to build a
// way to know which fields are user-provided.
if (gen_slots_ && !gen_dynamic_) {
out << indent() << "if args[0] not in self.__slots__:" << '\n';
indent_up();
out << indent() << "super().__delattr__(*args)" << '\n'
<< indent() << "return" << '\n';
indent_down();
}
out << indent() << "raise TypeError(\"can't modify immutable instance\")" << '\n';
indent_down();
out << '\n';
// Hash all of the members in order, and also hash in the class
// to avoid collisions for stuff like single-field structures.
out << indent() << "def __hash__(self):" << '\n'
<< indent() << indent_str() << "return hash(self.__class__) ^ hash((";
for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) {
out << "self." << (*m_iter)->get_name() << ", ";
}
out << "))" << '\n';
} else if (gen_enum_) {
bool has_enum = false;
for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) {
t_type* type = (*m_iter)->get_type();
if (type->is_enum()) {
has_enum = true;
break;
}
}
if (has_enum) {
out << '\n';
indent(out) << "def __setattr__(self, name, value):" << '\n';
indent_up();
for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) {
t_type* type = (*m_iter)->get_type();
if (type->is_enum()) {
out << indent() << "if name == \"" << (*m_iter)->get_name() << "\":" << '\n'
<< indent() << indent_str() << "super().__setattr__(name, value if hasattr(value, 'value') else "
<< type_name(type) << ".__members__.get(value))" << '\n'
<< indent() << indent_str() << "return" << '\n';
}
}
indent(out) << "super().__setattr__(name, value)" << '\n' << '\n';
indent_down();
}
}
if (!gen_dynamic_) {
out << '\n';
generate_py_struct_reader(out, tstruct);
generate_py_struct_writer(out, tstruct);
}
// For exceptions only, generate a __str__ method. This is
// because when raised exceptions are printed to the console, __repr__
// isn't used. See python bug #5882
if (is_exception) {
out << '\n';
out << indent() << "def __str__(self):" << '\n'
<< indent() << indent_str() << "return repr(self)" << '\n';
}
if (!gen_slots_) {
out << '\n';
// Printing utilities so that on the command line thrift
// structs look pretty like dictionaries
indent(out) << "def __repr__(self):" << '\n';
indent_up();
out << indent() << "L = ['%s=%r' % (key, value)" << '\n'
<< indent() << " for key, value in self.__dict__.items()]" << '\n'
<< indent() << "return '%s(%s)' % (self.__class__.__name__, ', '.join(L))" << '\n'
<< '\n';
indent_down();
// Equality and inequality methods that compare by value
out << indent() << "def __eq__(self, other):" << '\n';
indent_up();
out << indent() << "return isinstance(other, self.__class__) and "
"self.__dict__ == other.__dict__" << '\n';
indent_down();
out << '\n';
out << indent() << "def __ne__(self, other):" << '\n';
indent_up();
out << indent() << "return not (self == other)" << '\n';
indent_down();
} else if (!gen_dynamic_) {
out << '\n';
// no base class available to implement __eq__ and __repr__ and __ne__ for us
// so we must provide one that uses __slots__
indent(out) << "def __repr__(self):" << '\n';
indent_up();
out << indent() << "L = ['%s=%r' % (key, getattr(self, key))" << '\n'
<< indent() << " for key in self.__slots__]" << '\n'
<< indent() << "return '%s(%s)' % (self.__class__.__name__, ', '.join(L))" << '\n'
<< '\n';
indent_down();
// Equality method that compares each attribute by value and type, walking __slots__
out << indent() << "def __eq__(self, other):" << '\n';
indent_up();
out << indent() << "if not isinstance(other, self.__class__):" << '\n'
<< indent() << indent_str() << "return False" << '\n'
<< indent() << "for attr in self.__slots__:" << '\n'
<< indent() << indent_str() << "my_val = getattr(self, attr)" << '\n'
<< indent() << indent_str() << "other_val = getattr(other, attr)" << '\n'
<< indent() << indent_str() << "if my_val != other_val:" << '\n'
<< indent() << indent_str() << indent_str() << "return False" << '\n'
<< indent() << "return True" << '\n'
<< '\n';
indent_down();
out << indent() << "def __ne__(self, other):" << '\n'
<< indent() << indent_str() << "return not (self == other)" << '\n';
}
indent_down();
}