docs: def new(self, user_id, tokens=None, user_data=None, valid_until=None,
              client_ip=None, encoding='utf-8'):
          """Creates a new authentication ticket.
  
          Args:
              user_id: User id to store in ticket (stored in plain text)
              tokens: Optional sequence of token strings to store in the ticket
                  (stored in plain text).
              user_data: Optional user data to store in the ticket (string like
                  object stored in plain text)
              valid_until: Expiration time of ticket as a integer (typically
                  time.time() + seconds).
              client_ip: Optional string or ip_address.IPAddress of the client.
              encoding: Optional encoding type that is used when hashing the
                  strings passed to the function
  
          Returns:
              A ticket string that can later be used to identify the user
          """
          if valid_until is None:
              valid_until = int(time.time()) + TicketFactory._DEFAULT_TIMEOUT
          else:
              valid_until = int(valid_until)
  
          # Make sure we dont have any exclamations in the user_id
          user_id = ulp.quote(user_id)
  
          # Create a comma seperated list of tokens
          token_str = ''
          if tokens:
              # Escape characters in our tokens
              token_str = ','.join((ulp.quote(t) for t in tokens))
  
          # Encode our user data (a string)
          user_str = '' if not user_data else ulp.quote(user_data)
  
          # Get our address
          ip = self._DEFAULT_IP if client_ip is None else ip_address(client_ip)
  
          # Create our digest
          data0 = bytes([ip.version]) + ip.packed + pack(">I", valid_until)
          data1 = ('\0'.join((user_id, token_str, user_str))).encode(encoding)
          digest = self._hexdigest(data0, data1)
  
          # digest + timestamp as an eight character hexadecimal + userid
          parts = ('{0}{1:08x}{2}'.format(digest, valid_until, user_id),
                   token_str, user_str)
          return '!'.join(parts)