text: # -*- coding: utf-8 -*-
  # Copyright (C) 2006-2020 Mag. Christian Tanzer. All rights reserved
  # Glasauergasse 32, A--1130 Wien, Austria. tanzer@swing.co.at
  # ****************************************************************************
  #
  # This module is licensed under the terms of the BSD 3-Clause License
  # <http://www.c-tanzer.at/license/bsd_3c.html>.
  # ****************************************************************************
  #
  #++
  # Name
  #    TFL.Decorator
  #
  # Purpose
  #    Provide a decorator for defining well-behaved decorators
  #
  # Revision Dates
  #    14-Apr-2006 (CT)  Creation
  #    19-Apr-2006 (CT)  Set `__module__`, too
  #    26-Sep-2006 (PGO) `wrapper` fixed to work with builtin functions, too
  #    24-Jan-2008 (CT)  `Add_Method` and `Add_New_Method` added
  #    26-Mar-2008 (CT)  `Add_Method` changed to use `_Added_Method_Descriptor_`
  #    28-Mar-2008 (CT)  `_Added_Method_Descriptor_.__get__` corrected (`obj`
  #                      needs to be passed as `head_args`)
  #     1-Apr-2008 (CT)  `_Added_Method_Descriptor_` changed to be meta-class
  #                      compatible, too
  #     4-Apr-2008 (CT)  Set `_globals` (unfortunately, both `__globals__` and
  #                      `func_globals` are readonly)
  #     4-Apr-2008 (CT)  `Contextmanager` added
  #    17-Apr-2008 (CT)  `_Added_Method_Descriptor_` complexity revoked (didn't
  #                      work for replacing a method that's overriden by a
  #                      descendent of the class for which it is overriden)
  #    17-Apr-2008 (CT)  `Override_Method` added (allows access to `orig`, but
  #                      no multiple classes); `Add_Method` changed to not
  #                      provide `orig`
  #    18-Apr-2008 (CT)  `Decorator` decorator removed from `Add_Method` and
  #                      `Override_Method`
  #    19-Jun-2008 (CT)  `Attributed` added
  #    23-Aug-2008 (CT)  `Annotated` added
  #    12-Oct-2009 (CT)  `Add_To_Class` added
  #     4-Nov-2009 (CT)  `decorator` keyword argument added to `Add_Method` and
  #                      `Add_New_Method`
  #    13-Mar-2010 (CT)  `decorator` keyword argument added to `Add_To_Class`, too
  #     9-Aug-2012 (CT)  Add `getattr_safe`
  #    22-Feb-2013 (CT)  Use `TFL.Undef ()` not `object ()`
  #    28-May-2013 (CT)  Add `subclass_responsibility`
  #    28-May-2013 (CT)  Change `Decorator` to properly support `classmethod`
  #    29-May-2013 (CT)  Kludge around Python2.6 lack of classmethod introspection
  #    18-Sep-2013 (CT)  Add `dict_from_class`
  #    21-Aug-2014 (CT)  Add `Add_To_Object`
  #    16-Oct-2014 (CT)  Change `dict_from_class` to return real `dict`,
  #                      remove thunder properties from its result
  #     5-Feb-2015 (CT)  Add `default` to `getattr_safe`; factor `_update_wrapper`
  #    16-Jul-2015 (CT)  Use `expect_except` in doc-tests
  #    15-Aug-2015 (CT)  Add `eval_function_body`
  #    16-Oct-2015 (CT)  Add `__future__` imports
  #    19-Apr-2020 (CT)  Use `functools.update_wrapper`
  #    ««revision-date»»···
  #--
  
  from   _TFL         import TFL
  from   _TFL.pyk     import pyk
  
  import _TFL.Undef
  
  import functools
  import logging
  
  def _update_wrapper (wrapper, wrapped) :
      functools.update_wrapper (wrapper, wrapped)
      wrapper._globals = getattr \
          (wrapped, "_globals", getattr (wrapped, "__globals__", {}))
  # end def _update_wrapper
  
  def Decorator (decorator) :
      """Decorate `decorator` so that `__name__`, `__doc__`, and `__dict__` of
         decorated functions/methods are preserved.
  
         ::
  
             >>> def deco (f) :
             ...     def wrapper () :
             ...         "Wrapper around decorated function"
             ...         return f ()
             ...     return wrapper
             ...
             >>> @deco
             ... def foo () :
             ...     "Function to test decoration"
             ...     pass
             ...
             >>> print (foo.__name__, ":", foo.__doc__)
             wrapper : Wrapper around decorated function
  
             >>> @Decorator
             ... def deco (f) :
             ...     def wrapper () :
             ...         "Wrapper around decorated function"
             ...         return f ()
             ...     return wrapper
             ...
             >>> @deco
             ... def foo () :
             ...     "Function to test decoration"
             ...     pass
             ...
             >>> print (foo.__name__, ":", foo.__doc__)
             foo : Function to test decoration
      """
      def wrapper (f) :
          if isinstance (f, (classmethod, staticmethod)) :
              cors = f.__class__
              try :
                  orig_f = f.__func__
              except AttributeError :
                  ### Python 2.6 doesn't support `__func__` for classmethod
                  orig_f = f
              else :
                  orig_f.is_classmethod = cors is classmethod
              wrapper    = decorator (orig_f)
              decorated  = cors (wrapper)
              _update_wrapper (wrapper, orig_f)
          else :
              decorated = decorator (f)
              _update_wrapper (decorated, f)
          return decorated
      _update_wrapper (wrapper, decorator)
      return wrapper
  # end def Decorator
  
  _AR_undefined = TFL.Undef ("return-value")
  
  def Annotated (RETURN = _AR_undefined, ** kw) :
      """Add dictionary `func_annotations` containing elements of `kw` and
         value of `RETURN` bound to key `return` as proposed by
         http://www.python.org/dev/peps/pep-3107/.
  
         Each key of `kw` must be the name of an argument of the function to be
         annotated::
  
             >>> from _TFL.portable_repr import portable_repr
             >>> @TFL.Annotated (bar = "Arg 1", baz = 42)
             ... def foo (bar, baz) : pass
             ...
             >>> portable_repr (foo.func_annotations)
             "{'bar' : 'Arg 1', 'baz' : 42}"
  
             >>> @TFL.Annotated (bar = "Arg 1", baz = 42, RETURN = None)
             ... def foo (bar, baz) : pass
             ...
             >>> portable_repr (foo.func_annotations)
             "{'bar' : 'Arg 1', 'baz' : 42, 'return' : None}"
  
             >>> with expect_except (TypeError) :
             ...     @TFL.Annotated (bar = "Arg 1", baz = 42, qux = None)
             ...     def foo (bar, baz) : pass
             ...
             TypeError: Function `foo` doesn't have an argument named `qux`
      """
      def decorator (f) :
          from inspect import getargspec
          f.func_annotations             = fa = {}
          args, varargs, varkw, defaults = getargspec (f)
          if varargs : args.append (varargs)
          if varkw   : args.append (varkw)
          arg_set = set (args)
          for k, v in kw.items () :
              if k in arg_set :
                  fa [k] = v
              else :
                  raise TypeError \
                      ( "Function `%s` doesn't have an argument named `%s`"
                      % (f.__name__, k)
                      )
          if RETURN is not _AR_undefined :
              fa ["return"] = RETURN
          return f
      return decorator
  # end def Annotated
  
  def Attributed (** kw) :
      """Add all elements of `kw` as function attribute to decorated function.
  
         ::
  
             >>> from _TFL.portable_repr import portable_repr
             >>> @Attributed (foo = 1, bar = 42)
             ... def f () :
             ...     pass
             ...
             >>> portable_repr (f.__dict__)
             "{'bar' : 42, 'foo' : 1}"
  
             >>> @Attributed (a = "WTF", b = 137)
             ... def g () :
             ...     "Test `Attributed` decorator"
             ...
             >>> portable_repr (g.__dict__)
             "{'a' : 'WTF', 'b' : 137}"
  
             >>> print (g.__doc__)
             Test `Attributed` decorator
      """
      def decorator (f) :
          for k, v in pyk.iteritems (kw) :
              setattr (f, k, v)
          return f
      return decorator
  # end def Attributed
  
  @Decorator
  def Contextmanager (f) :
      """Decorate `f` so that it's usable as a contextmanager in `with`
         statements.
      """
      from contextlib import contextmanager
      return contextmanager (f)
  # end def Contextmanager
  
  def Add_Method (* classes, ** kw) :
      """Adds decorated function to `classes` (won't complain if any class
         already contains a function of that name, but the original function
         isn't available to the decorated function for chaining up to).
      """
      def decorator (f) :
          name = f.__name__
          deco = kw.get ("decorator")
          if deco :
              f = deco (f)
          for cls in classes :
              setattr (cls, name, f)
          return f
      return decorator
  # end def Add_Method
  
  def Add_New_Method (* classes, ** kw) :
      """Adds decorated function to `classes` (complains if any class already
         contains a function of that name).
      """
      def decorator (f) :
          name = f.__name__
          deco = kw.get ("decorator")
          if deco :
              f = deco (f)
          for cls in classes :
              if hasattr (cls, name) :
                  raise TypeError \
                      ("%s already has a property named `%s`" % (cls, name))
              setattr (cls, name, f)
          return f
      return decorator
  # end def Add_New_Method
  
  def Add_To_Class (name, * classes, ** kw) :
      """Adds decorated function/class to `classes` using `name`.
      """
      def decorator (x) :
          deco = kw.get ("decorator")
          if deco :
              x = deco (x)
          for cls in classes :
              setattr (cls, name, x)
          return x
      return decorator
  # end def Add_To_Class
  
  def Add_To_Object (* objects, ** kw) :
      """Adds decorated function/class as attribute to `objects`.
  
         If `name` is passed as keyword argument, it is used as name for the
         added attribute, otherwise the `__name__` of the decorated
         function/class is used.
      """
      def decorator (x) :
          name = kw.get ("name", x.__name__)
          for o in objects :
              setattr (o, name, x)
          return x
      return decorator
  # end def Add_To_Object
  
  def dict_from_class (cls) :
      """Return `cls.__dict__`."""
      return dict \
          ( (k, v) for k, v in pyk.iteritems (cls.__dict__)
          if not k.startswith ("__")
          )
  # end def dict_from_class
  
  def eval_function_body (f) :
      """Call the decorated function. The function itself is not returned!"""
      return f ()
  # end def eval_function_body
  
  def getattr_safe (f = None, default = None) :
      """Protect `f` from raising `AttributeError` (to avoid `__getattr__`)."""
      if f is None :
          return lambda f : getattr_safe (f, default = default)
      def _ (* args, ** kw) :
          try :
              return f (* args, ** kw)
          except AttributeError as exc :
              logging.exception \
                  ( "Property %s [module %s] triggered AttributeError"
                  , f.__name__, f.__module__
                  )
              return default
      _update_wrapper (_, f)
      return _
  # end def getattr_safe
  
  @Decorator
  def Override_Method (cls) :
      """Adds decorated function to `cls` (original method if any is available
         inside the decorated function as function attribute `.orig`).
      """
      def decorator (f) :
          name = f.__name__
          if hasattr (cls, name) :
              setattr (f, "orig", getattr (cls, name))
          setattr (cls, name, f)
          return f
      return decorator
  # end def Override_Method
  
  @Decorator
  def subclass_responsibility (f) :
      """Raise a NotImplementedError unless the method is redefined in
         descendents.
  
         For classmethods, use `subclass_responsibility` as first decorator::
  
             @subclass_responsibility
             @classmethod
             def meth (cls, ...) :
                 ...
  
      """
      ### For methods decorated with `@classmethod`, `Decorator` passes the
      ### original method but marks it with `is_classmethod`
      is_classmethod = getattr (f, "is_classmethod", False)
      fmt = "%s must implement method %r"
      if isinstance (f, classmethod) or is_classmethod :
          if is_classmethod :
              name = f.__name__
          else :
              try :
                  name = f.__func__.__name__
              except AttributeError :
                  ### Python 2.6 doesn't support `__func__` nor `__name__`
                  ### for classmethod
                  name = "the classmethod"
          def _ (cls, * args, ** kw) :
              raise NotImplementedError (fmt % (cls.__name__, name))
      else :
          name = f.__name__
          def _ (self, * args, ** kw) :
              raise NotImplementedError (fmt % (self.__class__.__name__, name))
      return _
  # end def subclass_responsibility
  
  __doc__ = """
  Library of decorator functions.
  
  .. moduleauthor:: Christian Tanzer <tanzer@swing.co.at>
  
  """
  
  if __name__ != "__main__" :
      TFL._Export ("*")
  ### __END__ TFL.Decorator
  