void t_py_generator::generate_py_struct_definition()

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();
}