docs: def request(*types, **options):
      """Decorator for request handler methods.
  
      The method being decorated should take a req argument followed
      by arguments matching the list of types. The decorator will
      unpack the request message into the arguments.
  
      Parameters
      ----------
      types : list of kattypes
          The types of the request message parameters (in order). A type
          with multiple=True has to be the last type.
  
      Keyword Arguments
      -----------------
      include_msg : bool, optional
          Pass the request message as the third parameter to the decorated
          request handler function (default is False).
      major : int, optional
          Major version of KATCP to use when interpreting types.
          Defaults to latest implemented KATCP version.
  
      Examples
      --------
      >>> class MyDevice(DeviceServer):
      ...     @request(Int(), Float(), Bool())
      ...     @return_reply(Int(), Float())
      ...     def request_myreq(self, req, my_int, my_float, my_bool):
      ...         '''?myreq my_int my_float my_bool'''
      ...         return ("ok", my_int + 1, my_float / 2.0)
      ...
      ...     @request(Int(), include_msg=True)
      ...     @return_reply(Bool())
      ...     def request_is_odd(self, req, msg, my_int):
                  '''?is-odd <my_int>, reply '1' if <my_int> is odd, else 0'''
      ...         req.inform('Checking oddity of %d' % my_int)
      ...         return ("ok", my_int % 2)
      ...
  
      """
      include_msg = options.pop('include_msg', False)
      has_req = options.pop('has_req', True)
      major = options.pop('major', DEFAULT_KATCP_MAJOR)
      check_req = options.pop('_check_req', True)
      if len(options) > 0:
          raise TypeError('does not take keyword argument(s) %r.'
                          % options.keys())
      # Check that only the last type has multiple=True
      if len(types) > 1:
          for type_ in types[:-1]:
              if type_._multiple:
                  raise TypeError('Only the last parameter type '
                                  'can accept multiple arguments.')
  
      def decorator(handler):
          argnames = []
  
          # If this decorator is on the outside, get the parameter names which
          # have been preserved by the other decorator
          all_argnames = getattr(handler, "_orig_argnames", None)
  
          if all_argnames is None:
              # We must be on the inside. Introspect the parameter names.
              all_argnames = inspect.getargspec(handler)[0]
  
          params_start = 1        # Skip 'self' parameter
          if has_req:         # Skip 'req' parameter
              params_start += 1
          if include_msg:
              params_start += 1
          # Get other parameter names
          argnames = all_argnames[params_start:]
  
          if has_req and include_msg:
              def raw_handler(self, req, msg):
                  new_args = unpack_types(types, msg.arguments, argnames, major)
                  return handler(self, req, msg, *new_args)
  
          elif has_req and not include_msg:
              def raw_handler(self, req, msg):
                  new_args = unpack_types(types, msg.arguments, argnames, major)
                  return handler(self, req, *new_args)
  
          elif not has_req and include_msg:
              def raw_handler(self, msg):
                  new_args = unpack_types(types, msg.arguments, argnames, major)
                  return handler(self, msg, *new_args)
  
          elif not has_req and not include_msg:
              def raw_handler(self, msg):
                  new_args = unpack_types(types, msg.arguments, argnames, major)
                  return handler(self, *new_args)
  
          update_wrapper(raw_handler, handler)
          # explicitly note that this decorator has been run, so that
          # return_reply can know if it's on the outside.
          raw_handler._request_decorated = True
          return raw_handler
  
      return decorator