hunk.code_window:             'subject': 'sendgrid-send-email unit test'}
           self.personalization_custom_args = {'arg1': 'val1', 'arg2': 'val2'}
           self.categories = ['cat1', 'cat2']
           self.expected_mail_data_extras = copy.deepcopy(self.expected_mail_data)
           self.expected_mail_data_extras['personalizations'][0]['custom_args'] = \
               self.personalization_custom_args
           self.expected_mail_data_extras['categories'] = self.categories
  
hunk.labels: keep keep add keep keep keep keep
hunk.after_edit:         # extras
  
hunk.file_path: tests/contrib/utils/test_sendgrid.py
hunk.type: add
file: # -*- coding: utf-8 -*-
  #
  # Licensed to the Apache Software Foundation (ASF) under one
  # or more contributor license agreements.  See the NOTICE file
  # distributed with this work for additional information
  # regarding copyright ownership.  The ASF licenses this file
  # to you under the Apache License, Version 2.0 (the
  # "License"); you may not use this file except in compliance
  # with the License.  You may obtain a copy of the License at
  # 
  #   http://www.apache.org/licenses/LICENSE-2.0
  # 
  # Unless required by applicable law or agreed to in writing,
  # software distributed under the License is distributed on an
  # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
  # KIND, either express or implied.  See the License for the
  # specific language governing permissions and limitations
  # under the License.
  #
  from __future__ import absolute_import
  from __future__ import division
  from __future__ import print_function
  from __future__ import unicode_literals
  
  import logging
  import sys
  import warnings
  
  import six
  
  from builtins import object
  from contextlib import contextmanager
  from logging import Handler, StreamHandler
  
  
  class LoggingMixin(object):
      """
      Convenience super-class to have a logger configured with the class name
      """
      def __init__(self, context=None):
          self._set_context(context)
  
      # We want to deprecate the logger property in Airflow 2.0
      # The log property is the de facto standard in most programming languages
      @property
      def logger(self):
          warnings.warn(
              'Initializing logger for {} using logger(), which will '
              'be replaced by .log in Airflow 2.0'.format(
                  self.__class__.__module__ + '.' + self.__class__.__name__
              ),
              DeprecationWarning
          )
          return self.log
  
      @property
      def log(self):
          try:
              return self._log
          except AttributeError:
              self._log = logging.root.getChild(
                  self.__class__.__module__ + '.' + self.__class__.__name__
              )
              return self._log
  
      def _set_context(self, context):
          if context is not None:
              set_context(self.log, context)
  
  
  class StreamLogWriter(object):
      encoding = False
  
      """
      Allows to redirect stdout and stderr to logger
      """
      def __init__(self, logger, level):
          """
          :param log: The log level method to write to, ie. log.debug, log.warning
          :return:
          """
          self.logger = logger
          self.level = level
          self._buffer = str()
  
      def write(self, message):
          """
          Do whatever it takes to actually log the specified logging record
          :param message: message to log
          """
          if not message.endswith("\n"):
              self._buffer += message
          else:
              self._buffer += message
              self.logger.log(self.level, self._buffer)
              self._buffer = str()
  
      def flush(self):
          """
          Ensure all logging output has been flushed
          """
          if len(self._buffer) > 0:
              self.logger.log(self.level, self._buffer)
              self._buffer = str()
  
      def isatty(self):
          """
          Returns False to indicate the fd is not connected to a tty(-like) device.
          For compatibility reasons.
          """
          return False
  
  
  class RedirectStdHandler(StreamHandler):
      """
      This class is like a StreamHandler using sys.stderr/stdout, but always uses
      whatever sys.stderr/stderr is currently set to rather than the value of
      sys.stderr/stdout at handler construction time.
      """
      def __init__(self, stream):
          if not isinstance(stream, six.string_types):
              raise Exception("Cannot use file like objects. Use 'stdout' or 'stderr'"
                              " as a str and without 'ext://'.")
  
          self._use_stderr = True
          if 'stdout' in stream:
              self._use_stderr = False
  
          # StreamHandler tries to set self.stream
          Handler.__init__(self)
  
      @property
      def stream(self):
          if self._use_stderr:
              return sys.stderr
  
          return sys.stdout
  
  
  @contextmanager
  def redirect_stdout(logger, level):
      writer = StreamLogWriter(logger, level)
      try:
          sys.stdout = writer
          yield
      finally:
          sys.stdout = sys.__stdout__
  
  
  @contextmanager
  def redirect_stderr(logger, level):
      writer = StreamLogWriter(logger, level)
      try:
          sys.stderr = writer
          yield
      finally:
          sys.stderr = sys.__stderr__
  
  
  def set_context(logger, value):
      """
      Walks the tree of loggers and tries to set the context for each handler
      :param logger: logger
      :param value: value to set
      """
      _logger = logger
      while _logger:
          for handler in _logger.handlers:
              try:
                  handler.set_context(value)
              except AttributeError:
                  # Not all handlers need to have context passed in so we ignore
                  # the error when handlers do not have set_context defined.
                  pass
          if _logger.propagate is True:
              _logger = _logger.parent
          else:
              _logger = None
  