docs: def _create_attrcontent_class(name, fields, inheritance=(object,), data_structure=None, extra_functions=None, docstring=""):
      '''Helper function that creates a class for attribute contents.
  
      This function creates is a boilerplate to create all the expected methods of
      an attributes. The basic methods work in the same way for all classes.
  
      Once it executes it defines a dynamic class with the methods "__init__",
      "__repr__" and "__eq__" based on the fields passed in the ``fields`` parameter.
      If the ``data_structure`` parameter is present, the classmethod ``get_representation_size``
      and the class variable ``_REPR`` will also be present.
  
      It is also possible to define the inheritance using this method by passing
      a list of classes in the ``inheritance`` parameter.
  
      If the ``extra_functions`` argument is present, they will be added to the
      class.
  
      Note:
          If the ``extra_functions`` has defined any of dinamically created methods,
          they will *replace* the ones created.
  
  
      Args:
          name (str): Name of the class that will be created.
          fields (tuple(str)): The attributes that will be added to the class.
          inherited (tuple(object)): List of objects that will be inherited by
              the new class
          extra_functions (dict(str : function)): A dictionary where the key
              will be the name of the function in the class and the content
              of the key is a function that will be bound to the class
          doctring (str): Class' docstring
  
      Returns:
          A new class with the ``name`` as it's name.
      '''
  
      def create_func_from_str(f_name, args, content, docstring=""):
          '''Helper function to create functions from strings.
  
          To improve performance, the standard functions are created at runtime
          based on the string derived from the content. This way the function, from
          the interpreter point of view, looks like statically defined.
  
          Note:
              This function should be used only for methods that will receive
              ``self`` (instace methods). The ``self`` argument is added automatically.
  
          Args:
              f_name (str): Function name
              args (list(str)): List of extra arguments that the function will receive
              content (str): Content of the function
              docstring (str): Function's docstring
  
          Returns:
              A new function object that can be inserted in the class.
          '''
          exec_namespace = {"__name__" : f"{f_name}"}
          new_args = ", ".join(["self"] + args)
          func_str = f"def {f_name}({new_args}): {content}"
          exec(func_str, exec_namespace)
          func = exec_namespace[f_name]
          func.__doc__ = docstring
  
          return func
  
      #creates the functions necessary for the new class
      slots = fields
  
      init_content = ", ".join([f"self.{field}" for field in fields]) + " = content"
      __init__ = create_func_from_str("__init__", [f"content=(None,)*{len(fields)}"],  init_content)
  
      temp = ", ".join([f"{field}={{self.{field}}}" for field in fields])
      repr = "return " + f"f\'{{self.__class__.__name__}}({temp})\'"
      __repr__ = create_func_from_str("__repr__", [],  repr)
  
      temp = " and ".join([f"self.{field} == other.{field}" for field in fields])
      eq = f"return {temp} if isinstance(other, {name}) else False"
      __eq__ = create_func_from_str("__eq__", ["other"], eq)
  
      @classmethod
      def get_representation_size(cls):
          return cls._REPR.size
  
      #adapted from namedtuple code
      # Modify function metadata to help with introspection and debugging
      for method in (__init__, get_representation_size.__func__, __eq__,
                     __repr__):
          method.__qualname__ = f'{name}.{method.__name__}'
  
      #map class namespace for the class creation
      namespace = {"__slots__" : slots,
                   "__init__" : __init__,
                   "__repr__" : __repr__,
                   "__eq__" : __eq__
                   }
      if data_structure is not None:
          namespace["_REPR"] = struct.Struct(data_structure)
          namespace["get_representation_size"] = get_representation_size
      if docstring:
          namespace["__doc__"] = docstring
      #some new mappings can be set or overload the ones defined
      if extra_functions is not None:
          for method in extra_functions.values():
              try:
                  method.__qualname__ = f'{name}.{method.__name__}'
              except AttributeError:
                  try:
                      method.__func__.__qualname__ = f'{name}.{method.__func__.__name__}'
                  except AttributeError:
                      #if we got here, it is not a method or classmethod, must be an attribute
                      #TODO feels like a hack, change it
                      #TODO design a test for this
                      pass
          namespace = {**namespace, **extra_functions}
  
      #TODO check if docstring was provided, issue a warning
  
      new_class = type(name, inheritance, namespace)
  
      # adapted from namedtuple code
      # For pickling to work, the __module__ variable needs to be set to the frame
      # where the named tuple is created.  Bypass this step in environments where
      # sys._getframe is not defined (Jython for example) or sys._getframe is not
      # defined for arguments greater than 0 (IronPython), or where the user has
      # specified a particular module.
      try:
          new_class.__module__ = _sys._getframe(1).f_globals.get('__name__', '__main__')
      except (AttributeError, ValueError):
          pass
  
      return new_class