docs: def get_signature(func):
      """
          :type func: Callable
          :rtype: str
      """
      try:
          return object.__getattribute__(func, '__tri_declarative_signature')
      except AttributeError:
          pass
  
      try:
          if sys.version_info[0] < 3:  # pragma: no mutate
              names, _, varkw, defaults = inspect.getargspec(func)  # pragma: no mutate
          else:
              names, _, varkw, defaults, _, _, _ = inspect.getfullargspec(func)  # pragma: no covererage
      except TypeError:
          return None
  
      first_arg_index = 1 if inspect.ismethod(func) else 0  # Skip self argument on methods
  
      number_of_defaults = len(defaults) if defaults else 0
      if number_of_defaults > 0:
          required = ','.join(sorted(names[first_arg_index:-number_of_defaults]))
          optional = ','.join(sorted(names[-number_of_defaults:]))
      else:
          required = ','.join(sorted(names[first_arg_index:]))
          optional = ''
      wildcard = '*' if varkw is not None else ''
  
      signature = '|'.join((required, optional, wildcard))
      try:
          object.__setattr__(func, '__tri_declarative_signature', signature)
      except TypeError:
          # For classes
          type.__setattr__(func, '__tri_declarative_signature', signature)
      except AttributeError:
          pass
      return signature