text: """Chalice app and routing code."""
  # pylint: disable=too-many-lines,ungrouped-imports
  import re
  import sys
  import os
  import logging
  import json
  import traceback
  import decimal
  import base64
  import copy
  import functools
  import datetime
  from collections import defaultdict
  
  
  __version__ = '1.24.1'
  _PARAMS = re.compile(r'{\w+}')
  
  # Implementation note:  This file is intended to be a standalone file
  # that gets copied into the lambda deployment package.  It has no dependencies
  # on other parts of chalice so it can stay small and lightweight, with minimal
  # startup overhead.  This also means we need to handle py2/py3 compat issues
  # directly in this file instead of copying over compat.py
  try:
      from urllib.parse import unquote_plus
      from collections.abc import Mapping
      from collections.abc import MutableMapping
  
      unquote_str = unquote_plus
  
      # In python 3 string and bytes are different so we explicitly check
      # for both.
      _ANY_STRING = (str, bytes)
  except ImportError:
      from urllib import unquote_plus
      from collections import Mapping
      from collections import MutableMapping
  
      # This is borrowed from botocore/compat.py
      def unquote_str(value, encoding='utf-8'):
          # In python2, unquote() gives us a string back that has the urldecoded
          # bits, but not the unicode parts.  We need to decode this manually.
          # unquote has special logic in which if it receives a unicode object it
          # will decode it to latin1.  This is hard coded.  To avoid this, we'll
          # encode the string with the passed in encoding before trying to
          # unquote it.
          byte_string = value.encode(encoding)
          return unquote_plus(byte_string).decode(encoding)
      # In python 2 there is a base class for the string types that we can check
      # for. It was removed in python 3 so it will cause a name error.
      _ANY_STRING = (basestring, bytes)  # noqa pylint: disable=E0602
  
  
  def handle_extra_types(obj):
      # Lambda will automatically serialize decimals so we need
      # to support that as well.
      if isinstance(obj, decimal.Decimal):
          return float(obj)
      # This is added for backwards compatibility.
      # It will keep only the last value for every key as it used to.
      if isinstance(obj, MultiDict):
          return dict(obj)
      raise TypeError('Object of type %s is not JSON serializable'
                      % obj.__class__.__name__)
  
  
  def error_response(message, error_code, http_status_code, headers=None):
      body = {'Code': error_code, 'Message': message}
      response = Response(body=body, status_code=http_status_code,
                          headers=headers)
  
      return response
  
  
  def _matches_content_type(content_type, valid_content_types):
      # If '*/*' is in the Accept header or the valid types,
      # then all content_types match. Otherwise see of there are any common types
      content_type = content_type.lower()
      valid_content_types = [x.lower() for x in valid_content_types]
      return '*/*' in content_type or \
          '*/*' in valid_content_types or \
          _content_type_header_contains(content_type, valid_content_types)
  
  
  def _content_type_header_contains(content_type_header, valid_content_types):
      content_type_header_parts = [
          p.strip() for p in
          re.split('[,;]', content_type_header)
      ]
      valid_parts = set(valid_content_types).intersection(
          content_type_header_parts
      )
      return len(valid_parts) > 0
  
  
  class ChaliceError(Exception):
      pass
  
  
  class WebsocketDisconnectedError(ChaliceError):
      def __init__(self, connection_id):
          self.connection_id = connection_id
  
  
  class ChaliceViewError(ChaliceError):
      STATUS_CODE = 500
  
      def __init__(self, msg=''):
          super(ChaliceViewError, self).__init__(
              self.__class__.__name__ + ': %s' % msg)
  
  
  class ChaliceUnhandledError(ChaliceError):
      """This error is not caught from a Chalice view function.
  
      This exception is allowed to propagate from a view function so
      that middleware handlers can process the exception.
      """
  
  
  class BadRequestError(ChaliceViewError):
      STATUS_CODE = 400
  
  
  class UnauthorizedError(ChaliceViewError):
      STATUS_CODE = 401
  
  
  class ForbiddenError(ChaliceViewError):
      STATUS_CODE = 403
  
  
  class NotFoundError(ChaliceViewError):
      STATUS_CODE = 404
  
  
  class MethodNotAllowedError(ChaliceViewError):
      STATUS_CODE = 405
  
  
  class RequestTimeoutError(ChaliceViewError):
      STATUS_CODE = 408
  
  
  class ConflictError(ChaliceViewError):
      STATUS_CODE = 409
  
  
  class UnprocessableEntityError(ChaliceViewError):
      STATUS_CODE = 422
  
  
  class TooManyRequestsError(ChaliceViewError):
      STATUS_CODE = 429
  
  
  ALL_ERRORS = [
      ChaliceViewError,
      BadRequestError,
      NotFoundError,
      UnauthorizedError,
      ForbiddenError,
      MethodNotAllowedError,
      RequestTimeoutError,
      ConflictError,
      UnprocessableEntityError,
      TooManyRequestsError]
  
  
  class MultiDict(MutableMapping):  # pylint: disable=too-many-ancestors
      """A mapping of key to list of values.
  
      Accessing it in the usual way will return the last value in the list.
      Calling getlist will return a list of all the values associated with
      the same key.
      """
  
      def __init__(self, mapping):
          if mapping is None:
              mapping = {}
  
          self._dict = mapping
  
      def __getitem__(self, k):
          try:
              return self._dict[k][-1]
          except IndexError:
              raise KeyError(k)
  
      def __setitem__(self, k, v):
          self._dict[k] = [v]
  
      def __delitem__(self, k):
          del self._dict[k]
  
      def getlist(self, k):
          return list(self._dict[k])
  
      def __len__(self):
          return len(self._dict)
  
      def __iter__(self):
          return iter(self._dict)
  
      def __repr__(self):
          return 'MultiDict(%s)' % self._dict
  
      def __str__(self):
          return repr(self)
  
  
  class CaseInsensitiveMapping(Mapping):
      """Case insensitive and read-only mapping."""
  
      def __init__(self, mapping):
          mapping = mapping or {}
          self._dict = {k.lower(): v for k, v in mapping.items()}
  
      def __getitem__(self, key):
          return self._dict[key.lower()]
  
      def __iter__(self):
          return iter(self._dict)
  
      def __len__(self):
          return len(self._dict)
  
      def __repr__(self):
          return 'CaseInsensitiveMapping(%s)' % repr(self._dict)
  
  
  class Authorizer(object):
      name = ''
      scopes = []
  
      def to_swagger(self):
          raise NotImplementedError("to_swagger")
  
      def with_scopes(self, scopes):
          raise NotImplementedError("with_scopes")
  
  
  class IAMAuthorizer(Authorizer):
  
      _AUTH_TYPE = 'aws_iam'
  
      def __init__(self):
          self.name = 'sigv4'
          self.scopes = []
  
      def to_swagger(self):
          return {
              'in': 'header',
              'type': 'apiKey',
              'name': 'Authorization',
              'x-amazon-apigateway-authtype': 'awsSigv4',
          }
  
      def with_scopes(self, scopes):
          raise NotImplementedError("with_scopes")
  
  
  class CognitoUserPoolAuthorizer(Authorizer):
  
      _AUTH_TYPE = 'cognito_user_pools'
  
      def __init__(self, name, provider_arns, header='Authorization',
                   scopes=None):
          self.name = name
          self._header = header
          if not isinstance(provider_arns, list):
              # This class is used directly by users so we're
              # adding some validation to help them troubleshoot
              # potential issues.
              raise TypeError(
                  "provider_arns should be a list of ARNs, received: %s"
                  % provider_arns)
          self._provider_arns = provider_arns
          self.scopes = scopes or []
  
      def to_swagger(self):
          return {
              'in': 'header',
              'type': 'apiKey',
              'name': self._header,
              'x-amazon-apigateway-authtype': self._AUTH_TYPE,
              'x-amazon-apigateway-authorizer': {
                  'type': self._AUTH_TYPE,
                  'providerARNs': self._provider_arns,
              }
          }
  
      def with_scopes(self, scopes):
          authorizer_with_scopes = copy.deepcopy(self)
          authorizer_with_scopes.scopes = scopes
          return authorizer_with_scopes
  
  
  class CustomAuthorizer(Authorizer):
  
      _AUTH_TYPE = 'custom'
  
      def __init__(self, name, authorizer_uri, ttl_seconds=300,
                   header='Authorization', invoke_role_arn=None, scopes=None):
          self.name = name
          self._header = header
          self._authorizer_uri = authorizer_uri
          self._ttl_seconds = ttl_seconds
          self._invoke_role_arn = invoke_role_arn
          self.scopes = scopes or []
  
      def to_swagger(self):
          swagger = {
              'in': 'header',
              'type': 'apiKey',
              'name': self._header,
              'x-amazon-apigateway-authtype': self._AUTH_TYPE,
              'x-amazon-apigateway-authorizer': {
                  'type': 'token',
                  'authorizerUri': self._authorizer_uri,
                  'authorizerResultTtlInSeconds': self._ttl_seconds,
              }
          }
          if self._invoke_role_arn is not None:
              swagger['x-amazon-apigateway-authorizer'][
                  'authorizerCredentials'] = self._invoke_role_arn
          return swagger
  
      def with_scopes(self, scopes):
          authorizer_with_scopes = copy.deepcopy(self)
          authorizer_with_scopes.scopes = scopes
          return authorizer_with_scopes
  
  
  class CORSConfig(object):
      """A cors configuration to attach to a route."""
  
      _REQUIRED_HEADERS = ['Content-Type', 'X-Amz-Date', 'Authorization',
                           'X-Api-Key', 'X-Amz-Security-Token']
  
      def __init__(self, allow_origin='*', allow_headers=None,
                   expose_headers=None, max_age=None, allow_credentials=None):
          self.allow_origin = allow_origin
  
          if allow_headers is None:
              allow_headers = set(self._REQUIRED_HEADERS)
          else:
              allow_headers = set(allow_headers + self._REQUIRED_HEADERS)
          self._allow_headers = allow_headers
  
          if expose_headers is None:
              expose_headers = []
          self._expose_headers = expose_headers
  
          self._max_age = max_age
          self._allow_credentials = allow_credentials
  
      @property
      def allow_headers(self):
          return ','.join(sorted(self._allow_headers))
  
      def get_access_control_headers(self):
          headers = {
              'Access-Control-Allow-Origin': self.allow_origin,
              'Access-Control-Allow-Headers': self.allow_headers
          }
          if self._expose_headers:
              headers.update({
                  'Access-Control-Expose-Headers': ','.join(self._expose_headers)
              })
          if self._max_age is not None:
              headers.update({
                  'Access-Control-Max-Age': str(self._max_age)
              })
          if self._allow_credentials is True:
              headers.update({
                  'Access-Control-Allow-Credentials': 'true'
              })
  
          return headers
  
      def __eq__(self, other):
          if isinstance(other, self.__class__):
              return self.get_access_control_headers() == \
                  other.get_access_control_headers()
          return False
  
  
  class Request(object):
      """The current request from API gateway."""
  
      _NON_SERIALIZED_ATTRS = ['lambda_context']
  
      def __init__(self, event_dict, lambda_context=None):
          query_params = event_dict['multiValueQueryStringParameters']
          self.query_params = None if query_params is None \
              else MultiDict(query_params)
          self.headers = CaseInsensitiveMapping(event_dict['headers'])
          self.uri_params = event_dict['pathParameters']
          self.method = event_dict['requestContext']['httpMethod']
          self._is_base64_encoded = event_dict.get('isBase64Encoded', False)
          self._body = event_dict['body']
          #: The parsed JSON from the body.  This value should
          #: only be set if the Content-Type header is application/json,
          #: which is the default content type value in chalice.
          self._json_body = None
          self._raw_body = b''
          self.context = event_dict['requestContext']
          self.stage_vars = event_dict['stageVariables']
          self.path = event_dict['requestContext']['resourcePath']
          self.lambda_context = lambda_context
          self._event_dict = event_dict
  
      def _base64decode(self, encoded):
          if not isinstance(encoded, bytes):
              encoded = encoded.encode('ascii')
          output = base64.b64decode(encoded)
          return output
  
      @property
      def raw_body(self):
          if not self._raw_body and self._body is not None:
              if self._is_base64_encoded:
                  self._raw_body = self._base64decode(self._body)
              elif not isinstance(self._body, bytes):
                  self._raw_body = self._body.encode('utf-8')
              else:
                  self._raw_body = self._body
          return self._raw_body
  
      @property
      def json_body(self):
          if self.headers.get('content-type', '').startswith('application/json'):
              if self._json_body is None:
                  try:
                      self._json_body = json.loads(self.raw_body)
                  except ValueError:
                      raise BadRequestError('Error Parsing JSON')
              return self._json_body
  
      def to_dict(self):
          # Don't copy internal attributes.
          copied = {
              k: v for k, v in self.__dict__.items()
              if not k.startswith('_') and
              k not in self._NON_SERIALIZED_ATTRS
          }
          # We want the output of `to_dict()` to be
          # JSON serializable, so we need to remove the CaseInsensitive dict.
          copied['headers'] = dict(copied['headers'])
          if copied['query_params'] is not None:
              copied['query_params'] = dict(copied['query_params'])
          return copied
  
      def to_original_event(self):
          # To bring consistency with the BaseLambdaEvents, every
          # input event should have access to the original event
          # dictionary as an escape hatch to the underlying data
          # in case something gets added and we haven't mapped it yet.
          # We unfortunately already have a `to_dict()` method which is
          # what other events use so we have to use a different method name.
          return self._event_dict
  
  
  class Response(object):
      def __init__(self, body, headers=None, status_code=200):
          self.body = body
          if headers is None:
              headers = {}
          self.headers = headers
          self.status_code = status_code
  
      def to_dict(self, binary_types=None):
          body = self.body
          if not isinstance(body, _ANY_STRING):
              body = json.dumps(body, separators=(',', ':'),
                                default=handle_extra_types)
          single_headers, multi_headers = self._sort_headers(self.headers)
          response = {
              'headers': single_headers,
              'multiValueHeaders': multi_headers,
              'statusCode': self.status_code,
              'body': body
          }
          if binary_types is not None:
              self._b64encode_body_if_needed(response, binary_types)
          return response
  
      def _sort_headers(self, all_headers):
          multi_headers = {}
          single_headers = {}
          for name, value in all_headers.items():
              if isinstance(value, list):
                  multi_headers[name] = value
              else:
                  single_headers[name] = value
          return single_headers, multi_headers
  
      def _b64encode_body_if_needed(self, response_dict, binary_types):
          response_headers = CaseInsensitiveMapping(response_dict['headers'])
          content_type = response_headers.get('content-type', '')
          body = response_dict['body']
  
          if _matches_content_type(content_type, binary_types):
              if _matches_content_type(content_type, ['application/json']) or \
                      not content_type:
                  # There's a special case when a user configures
                  # ``application/json`` as a binary type.  The default
                  # json serialization results in a string type, but for binary
                  # content types we need a type bytes().  So we need to special
                  # case this scenario and encode the JSON body to bytes().
                  #
                  # If a user does not provide a content type header, which can
                  # happen if they return a python type instead of a ``Response``
                  # type, then we assume the content is application/json.
                  body = body if isinstance(body, bytes) \
                      else body.encode('utf-8')
              body = self._base64encode(body)
              response_dict['isBase64Encoded'] = True
          response_dict['body'] = body
  
      def _base64encode(self, data):
          if not isinstance(data, bytes):
              raise ValueError('Expected bytes type for body with binary '
                               'Content-Type. Got %s type body instead.'
                               % type(data))
          data = base64.b64encode(data)
          return data.decode('ascii')
  
  
  class RouteEntry(object):
  
      def __init__(self, view_function, view_name, path, method,
                   api_key_required=None, content_types=None,
                   cors=False, authorizer=None):
          self.view_function = view_function
          self.view_name = view_name
          self.uri_pattern = path
          self.method = method
          self.api_key_required = api_key_required
          #: A list of names to extract from path:
          #: e.g, '/foo/{bar}/{baz}/qux -> ['bar', 'baz']
          self.view_args = self._parse_view_args()
          self.content_types = content_types
          # cors is passed as either a boolean or a CORSConfig object. If it is a
          # boolean it needs to be replaced with a real CORSConfig object to
          # pass the typechecker. None in this context will not inject any cors
          # headers, otherwise the CORSConfig object will determine which
          # headers are injected.
          if cors is True:
              cors = CORSConfig()
          elif cors is False:
              cors = None
          self.cors = cors
          self.authorizer = authorizer
  
      def _parse_view_args(self):
          if '{' not in self.uri_pattern:
              return []
          # The [1:-1] slice is to remove the braces
          # e.g {foobar} -> foobar
          results = [r[1:-1] for r in _PARAMS.findall(self.uri_pattern)]
          return results
  
      def __eq__(self, other):
          return self.__dict__ == other.__dict__
  
  
  class APIGateway(object):
  
      _DEFAULT_BINARY_TYPES = [
          'application/octet-stream', 'application/x-tar', 'application/zip',
          'audio/basic', 'audio/ogg', 'audio/mp4', 'audio/mpeg', 'audio/wav',
          'audio/webm', 'image/png', 'image/jpg', 'image/jpeg', 'image/gif',
          'video/ogg', 'video/mpeg', 'video/webm',
      ]
  
      def __init__(self):
          self.binary_types = self.default_binary_types
          self.cors = False
  
      @property
      def default_binary_types(self):
          return list(self._DEFAULT_BINARY_TYPES)
  
  
  class WebsocketAPI(object):
      _WEBSOCKET_ENDPOINT_TEMPLATE = 'https://{domain_name}/{stage}'
      _REGION_ENV_VARS = ['AWS_REGION', 'AWS_DEFAULT_REGION']
  
      def __init__(self, env=None):
          self.session = None
          self._endpoint = None
          self._client = None
          if env is None:
              env = os.environ
          self._env = env
  
      def configure(self, domain_name, stage):
          if self._endpoint is not None:
              return
          self._endpoint = self._WEBSOCKET_ENDPOINT_TEMPLATE.format(
              domain_name=domain_name,
              stage=stage,
          )
  
      def configure_from_api_id(self, api_id, stage):
          if self._endpoint is not None:
              return
          region_name = self._get_region()
          domain_name = '{api_id}.execute-api.{region}.amazonaws.com'.format(
              api_id=api_id, region=region_name)
          self.configure(domain_name, stage)
  
      def _get_region(self):
          # Attempt to get the region so we can configure the
          # apigatewaymanagementapi client.  We'll first try
          # retrieving this value from env vars because these should
          # always be set in the Lambda runtime environment.
          for varname in self._REGION_ENV_VARS:
              if varname in self._env:
                  return self._env[varname]
          # As a last attempt we'll try to retrieve the region
          # from the currently configured region.  If the session
          # isn't configured or we can't get the region, we have
          # no choice but to error out.
          if self.session is not None:
              region_name = self.session.region_name
              if region_name is not None:
                  return region_name
          raise ValueError(
              "Unable to retrieve the region name when configuring the "
              "websocket client.  Either set the 'AWS_REGION' environment "
              "variable or assign 'app.websocket_api.session' to a boto3 "
              "session."
          )
  
      def _get_client(self):
          if self.session is None:
              raise ValueError(
                  'Assign app.websocket_api.session to a boto3 session before '
                  'using the WebsocketAPI'
              )
          if self._endpoint is None:
              raise ValueError(
                  'WebsocketAPI.configure must be called before using the '
                  'WebsocketAPI'
              )
          if self._client is None:
              self._client = self.session.client(
                  'apigatewaymanagementapi',
                  endpoint_url=self._endpoint,
              )
          return self._client
  
      def send(self, connection_id, message):
          client = self._get_client()
          try:
              client.post_to_connection(
                  ConnectionId=connection_id,
                  Data=message,
              )
          except client.exceptions.GoneException:
              raise WebsocketDisconnectedError(connection_id)
  
      def close(self, connection_id):
          client = self._get_client()
          try:
              client.delete_connection(
                  ConnectionId=connection_id,
              )
          except client.exceptions.GoneException:
              raise WebsocketDisconnectedError(connection_id)
  
      def info(self, connection_id):
          client = self._get_client()
          try:
              return client.get_connection(
                  ConnectionId=connection_id,
              )
          except client.exceptions.GoneException:
              raise WebsocketDisconnectedError(connection_id)
  
  
  class DecoratorAPI(object):
      def middleware(self, event_type='all'):
          def _middleware_wrapper(func):
              self.register_middleware(func, event_type)
              return func
          return _middleware_wrapper
  
      def authorizer(self, ttl_seconds=None, execution_role=None,
                     name=None, header='Authorization'):
          return self._create_registration_function(
              handler_type='authorizer',
              name=name,
              registration_kwargs={
                  'ttl_seconds': ttl_seconds,
                  'execution_role': execution_role,
                  'header': header
              }
          )
  
      def on_s3_event(self, bucket, events=None,
                      prefix=None, suffix=None, name=None):
          return self._create_registration_function(
              handler_type='on_s3_event',
              name=name,
              registration_kwargs={
                  'bucket': bucket, 'events': events,
                  'prefix': prefix, 'suffix': suffix,
              }
          )
  
      def on_sns_message(self, topic, name=None):
          return self._create_registration_function(
              handler_type='on_sns_message',
              name=name,
              registration_kwargs={'topic': topic}
          )
  
      def on_sqs_message(self, queue=None, batch_size=1,
                         name=None, queue_arn=None):
          return self._create_registration_function(
              handler_type='on_sqs_message',
              name=name,
              registration_kwargs={
                  'queue': queue,
                  'queue_arn': queue_arn,
                  'batch_size': batch_size,
              }
          )
  
      def on_cw_event(self, event_pattern, name=None):
          return self._create_registration_function(
              handler_type='on_cw_event',
              name=name,
              registration_kwargs={'event_pattern': event_pattern}
          )
  
      def schedule(self, expression, name=None, description=''):
          return self._create_registration_function(
              handler_type='schedule',
              name=name,
              registration_kwargs={'expression': expression,
                                   'description': description},
          )
  
      def on_kinesis_record(self, stream, batch_size=100,
                            starting_position='LATEST', name=None):
          return self._create_registration_function(
              handler_type='on_kinesis_record',
              name=name,
              registration_kwargs={'stream': stream,
                                   'batch_size': batch_size,
                                   'starting_position': starting_position},
          )
  
      def on_dynamodb_record(self, stream_arn, batch_size=100,
                             starting_position='LATEST', name=None):
          return self._create_registration_function(
              handler_type='on_dynamodb_record',
              name=name,
              registration_kwargs={'stream_arn': stream_arn,
                                   'batch_size': batch_size,
                                   'starting_position': starting_position},
          )
  
      def route(self, path, **kwargs):
          return self._create_registration_function(
              handler_type='route',
              name=kwargs.pop('name', None),
              # This looks a little weird taking kwargs as a key,
              # but we want to preserve keep the **kwargs signature
              # in the route decorator.
              registration_kwargs={'path': path, 'kwargs': kwargs},
          )
  
      def lambda_function(self, name=None):
          return self._create_registration_function(
              handler_type='lambda_function', name=name)
  
      def on_ws_connect(self, name=None):
          return self._create_registration_function(
              handler_type='on_ws_connect',
              name=name,
              registration_kwargs={'route_key': '$connect'},
          )
  
      def on_ws_disconnect(self, name=None):
          return self._create_registration_function(
              handler_type='on_ws_disconnect',
              name=name,
              registration_kwargs={'route_key': '$disconnect'},
          )
  
      def on_ws_message(self, name=None):
          return self._create_registration_function(
              handler_type='on_ws_message',
              name=name,
              registration_kwargs={'route_key': '$default'},
          )
  
      def _create_registration_function(self, handler_type, name=None,
                                        registration_kwargs=None):
          def _register_handler(user_handler):
              handler_name = name
              if handler_name is None:
                  handler_name = user_handler.__name__
              if registration_kwargs is not None:
                  kwargs = registration_kwargs
              else:
                  kwargs = {}
              wrapped = self._wrap_handler(handler_type, handler_name,
                                           user_handler)
              self._register_handler(handler_type, handler_name,
                                     user_handler, wrapped, kwargs)
              return wrapped
          return _register_handler
  
      def _wrap_handler(self, handler_type, handler_name, user_handler):
          if handler_type in _EVENT_CLASSES:
              if handler_type == 'lambda_function':
                  # We have to wrap existing @app.lambda_function()
                  # handlers for backwards compat reasons so we can
                  # preserve the `def handler(event, context): ...`
                  # interface.  However we need a consistent interface
                  # for middleware so we have to wrap the event
                  # here.
                  user_handler = PureLambdaWrapper(user_handler)
              return EventSourceHandler(
                  user_handler, _EVENT_CLASSES[handler_type],
                  middleware_handlers=self._get_middleware_handlers(
                      event_type=_MIDDLEWARE_MAPPING[handler_type],
                  )
              )
  
          websocket_event_classes = [
              'on_ws_connect',
              'on_ws_message',
              'on_ws_disconnect',
          ]
          if handler_type in websocket_event_classes:
              return WebsocketEventSourceHandler(
                  user_handler, WebsocketEvent,
                  self.websocket_api,  # pylint: disable=no-member
                  middleware_handlers=self._get_middleware_handlers(
                      event_type='websocket')
              )
          if handler_type == 'authorizer':
              # Authorizer is special cased and doesn't quite fit the
              # EventSourceHandler pattern.
              return ChaliceAuthorizer(handler_name, user_handler)
          return user_handler
  
      def _get_middleware_handlers(self, event_type):
          raise NotImplementedError("_get_middleware_handlers")
  
      def _register_handler(self, handler_type, name,
                            user_handler, wrapped_handler, kwargs, options=None):
          raise NotImplementedError("_register_handler")
  
      def register_middleware(self, func, event_type='all'):
          raise NotImplementedError("register_middleware")
  
  
  class _HandlerRegistration(object):
  
      def __init__(self):
          self.routes = defaultdict(dict)
          self.websocket_handlers = {}
          self.builtin_auth_handlers = []
          self.event_sources = []
          self.pure_lambda_functions = []
          self.api = APIGateway()
          self.handler_map = {}
          self.middleware_handlers = []
  
      def register_middleware(self, func, event_type='all'):
          self.middleware_handlers.append((func, event_type))
  
      def _do_register_handler(self, handler_type, name, user_handler,
                               wrapped_handler, kwargs, options=None):
          url_prefix = None
          name_prefix = None
          module_name = 'app'
          if options is not None:
              name_prefix = options.get('name_prefix')
              if name_prefix is not None:
                  name = name_prefix + name
              url_prefix = options.get('url_prefix')
              if url_prefix is not None and handler_type == 'route':
                  # Move url_prefix into kwargs so only the
                  # route() handler gets a url_prefix kwarg.
                  kwargs['url_prefix'] = url_prefix
              # module_name is always provided if options is not None.
              module_name = options['module_name']
          handler_string = '%s.%s' % (module_name, user_handler.__name__)
          getattr(self, '_register_%s' % handler_type)(
              name=name,
              user_handler=user_handler,
              handler_string=handler_string,
              wrapped_handler=wrapped_handler,
              kwargs=kwargs,
          )
          self.handler_map[name] = wrapped_handler
  
      def _attach_websocket_handler(self, handler):
          route_key = handler.route_key_handled
          decorator_name = {
              '$default': 'on_ws_message',
              '$connect': 'on_ws_connect',
              '$disconnect': 'on_ws_disconnect',
          }.get(route_key)
          if route_key in self.websocket_handlers:
              raise ValueError(
                  "Duplicate websocket handler: '%s'. There can only be one "
                  "handler for each websocket decorator." % decorator_name
              )
          self.websocket_handlers[route_key] = handler
  
      def _register_on_ws_connect(self, name, user_handler, handler_string,
                                  kwargs, **unused):
          wrapper = WebsocketConnectConfig(
              name=name,
              handler_string=handler_string,
              user_handler=user_handler,
          )
          self._attach_websocket_handler(wrapper)
  
      def _register_on_ws_message(self, name, user_handler, handler_string,
                                  kwargs, **unused):
          route_key = kwargs['route_key']
          wrapper = WebsocketMessageConfig(
              name=name,
              route_key_handled=route_key,
              handler_string=handler_string,
              user_handler=user_handler,
          )
          self._attach_websocket_handler(wrapper)
          self.websocket_handlers[route_key] = wrapper
  
      def _register_on_ws_disconnect(self, name, user_handler,
                                     handler_string, kwargs, **unused):
          wrapper = WebsocketDisconnectConfig(
              name=name,
              handler_string=handler_string,
              user_handler=user_handler,
          )
          self._attach_websocket_handler(wrapper)
  
      def _register_lambda_function(self, name, user_handler,
                                    handler_string, **unused):
          wrapper = LambdaFunction(
              user_handler, name=name,
              handler_string=handler_string,
          )
          self.pure_lambda_functions.append(wrapper)
  
      def _register_on_s3_event(self, name, handler_string, kwargs, **unused):
          events = kwargs['events']
          if events is None:
              events = ['s3:ObjectCreated:*']
          s3_event = S3EventConfig(
              name=name,
              bucket=kwargs['bucket'],
              events=events,
              prefix=kwargs['prefix'],
              suffix=kwargs['suffix'],
              handler_string=handler_string,
          )
          self.event_sources.append(s3_event)
  
      def _register_on_sns_message(self, name, handler_string, kwargs, **unused):
          sns_config = SNSEventConfig(
              name=name,
              handler_string=handler_string,
              topic=kwargs['topic'],
          )
          self.event_sources.append(sns_config)
  
      def _register_on_sqs_message(self, name, handler_string, kwargs, **unused):
          queue = kwargs.get('queue')
          queue_arn = kwargs.get('queue_arn')
          if not queue and not queue_arn:
              raise ValueError(
                  "Must provide either `queue` or `queue_arn` to the "
                  "`on_sqs_message` decorator."
              )
          sqs_config = SQSEventConfig(
              name=name,
              handler_string=handler_string,
              queue=queue,
              queue_arn=queue_arn,
              batch_size=kwargs['batch_size'],
          )
          self.event_sources.append(sqs_config)
  
      def _register_on_kinesis_record(self, name, handler_string,
                                      kwargs, **unused):
          kinesis_config = KinesisEventConfig(
              name=name,
              handler_string=handler_string,
              stream=kwargs['stream'],
              batch_size=kwargs['batch_size'],
              starting_position=kwargs['starting_position'],
          )
          self.event_sources.append(kinesis_config)
  
      def _register_on_dynamodb_record(self, name, handler_string,
                                       kwargs, **unused):
          ddb_config = DynamoDBEventConfig(
              name=name,
              handler_string=handler_string,
              stream_arn=kwargs['stream_arn'],
              batch_size=kwargs['batch_size'],
              starting_position=kwargs['starting_position'],
          )
          self.event_sources.append(ddb_config)
  
      def _register_on_cw_event(self, name, handler_string, kwargs, **unused):
          event_source = CloudWatchEventConfig(
              name=name,
              event_pattern=kwargs['event_pattern'],
              handler_string=handler_string
          )
          self.event_sources.append(event_source)
  
      def _register_schedule(self, name, handler_string, kwargs, **unused):
          event_source = ScheduledEventConfig(
              name=name,
              schedule_expression=kwargs['expression'],
              description=kwargs["description"],
              handler_string=handler_string,
          )
          self.event_sources.append(event_source)
  
      def _register_authorizer(self, name, handler_string, wrapped_handler,
                               kwargs, **unused):
          actual_kwargs = kwargs.copy()
          ttl_seconds = actual_kwargs.pop('ttl_seconds', None)
          execution_role = actual_kwargs.pop('execution_role', None)
          header = actual_kwargs.pop('header', None)
          if actual_kwargs:
              raise TypeError(
                  'TypeError: authorizer() got unexpected keyword '
                  'arguments: %s' % ', '.join(list(actual_kwargs)))
          auth_config = BuiltinAuthConfig(
              name=name,
              handler_string=handler_string,
              ttl_seconds=ttl_seconds,
              execution_role=execution_role,
              header=header,
          )
          wrapped_handler.config = auth_config
          self.builtin_auth_handlers.append(auth_config)
  
      def _register_route(self, name, user_handler, kwargs, **unused):
          actual_kwargs = kwargs['kwargs']
          path = kwargs['path']
          url_prefix = kwargs.pop('url_prefix', None)
          if url_prefix is not None:
              path = '/'.join([url_prefix.rstrip('/'),
                               path.strip('/')]).rstrip('/')
          methods = actual_kwargs.pop('methods', ['GET'])
          route_kwargs = {
              'authorizer': actual_kwargs.pop('authorizer', None),
              'api_key_required': actual_kwargs.pop('api_key_required', None),
              'content_types': actual_kwargs.pop('content_types',
                                                 ['application/json']),
              'cors': actual_kwargs.pop('cors', self.api.cors),
          }
          if route_kwargs['cors'] is None:
              route_kwargs['cors'] = self.api.cors
          if not isinstance(route_kwargs['content_types'], list):
              raise ValueError(
                  'In view function "%s", the content_types '
                  'value must be a list, not %s: %s' % (
                      name, type(route_kwargs['content_types']),
                      route_kwargs['content_types']))
          if actual_kwargs:
              raise TypeError('TypeError: route() got unexpected keyword '
                              'arguments: %s' % ', '.join(list(actual_kwargs)))
          for method in methods:
              if method in self.routes[path]:
                  raise ValueError(
                      "Duplicate method: '%s' detected for route: '%s'\n"
                      "between view functions: \"%s\" and \"%s\". A specific "
                      "method may only be specified once for "
                      "a particular path." % (
                          method, path, self.routes[path][method].view_name,
                          name)
                  )
              entry = RouteEntry(user_handler, name, path, method,
                                 **route_kwargs)
              self.routes[path][method] = entry
  
  
  class Chalice(_HandlerRegistration, DecoratorAPI):
      FORMAT_STRING = '%(name)s - %(levelname)s - %(message)s'
  
      def __init__(self, app_name, debug=False, configure_logs=True, env=None):
          super(Chalice, self).__init__()
          self.app_name = app_name
          self.websocket_api = WebsocketAPI()
          self.current_request = None
          self.lambda_context = None
          self._debug = debug
          self.configure_logs = configure_logs
          self.log = logging.getLogger(self.app_name)
          if env is None:
              env = os.environ
          self._initialize(env)
          self.experimental_feature_flags = set()
          # This is marked as internal but is intended to be used by
          # any code within Chalice.
          self._features_used = set()
  
      def _initialize(self, env):
          if self.configure_logs:
              self._configure_logging()
          env['AWS_EXECUTION_ENV'] = '%s aws-chalice/%s' % (
              env.get('AWS_EXECUTION_ENV', 'AWS_Lambda'),
              __version__,
          )
  
      @property
      def debug(self):
          return self._debug
  
      @debug.setter
      def debug(self, value):
          self._debug = value
          self._configure_log_level()
  
      def _configure_logging(self):
          if self._already_configured(self.log):
              return
          handler = logging.StreamHandler(sys.stdout)
          # Timestamp is handled by lambda itself so the
          # default FORMAT_STRING doesn't need to include it.
          formatter = logging.Formatter(self.FORMAT_STRING)
          handler.setFormatter(formatter)
          self.log.propagate = False
          self._configure_log_level()
          self.log.addHandler(handler)
  
      def _already_configured(self, log):
          if not log.handlers:
              return False
          for handler in log.handlers:
              if isinstance(handler, logging.StreamHandler):
                  if handler.stream == sys.stdout:
                      return True
          return False
  
      def _configure_log_level(self):
          if self._debug:
              level = logging.DEBUG
          else:
              level = logging.ERROR
          self.log.setLevel(level)
  
      def register_blueprint(self, blueprint, name_prefix=None, url_prefix=None):
          blueprint.register(self, options={'name_prefix': name_prefix,
                                            'url_prefix': url_prefix})
  
      def _register_handler(self, handler_type, name, user_handler,
                            wrapped_handler, kwargs, options=None):
          self._do_register_handler(handler_type, name, user_handler,
                                    wrapped_handler, kwargs, options)
  
      # These are defined here on the Chalice class because we want all the
      # feature flag tracking to live in Chalice and not the DecoratorAPI.
      def _register_on_ws_connect(self, name, user_handler, handler_string,
                                  kwargs, **unused):
          self._features_used.add('WEBSOCKETS')
          super(Chalice, self)._register_on_ws_connect(
              name, user_handler, handler_string, kwargs, **unused)
  
      def _register_on_ws_message(self, name, user_handler, handler_string,
                                  kwargs, **unused):
          self._features_used.add('WEBSOCKETS')
          super(Chalice, self)._register_on_ws_message(
              name, user_handler, handler_string, kwargs, **unused)
  
      def _register_on_ws_disconnect(self, name, user_handler,
                                     handler_string, kwargs, **unused):
          self._features_used.add('WEBSOCKETS')
          super(Chalice, self)._register_on_ws_disconnect(
              name, user_handler, handler_string, kwargs, **unused)
  
      def _get_middleware_handlers(self, event_type):
          # We're returning a generator here because we want to defer the
          # collection of all middleware until as last as possible (when
          # then handler is actually invoked).  This lets us pick up any
          # middleware that's registered after a handler has been defined,
          # which is the behavior you'd expect.
          return (func for func, filter_type in self.middleware_handlers if
                  filter_type in [event_type, 'all'])
  
      def __call__(self, event, context):
          # For legacy reasons, we can't move the Rest API handler entry
          # point away from this Chalice.__call__ method . However, we can
          # try to extract as much as logic as possible to a separate handler
          # class we can call.  That way it's still structured somewhat similar
          # to the other event handlers which makes it more manageable to
          # implement shared functionality (e.g. middleware).
          self.lambda_context = context
          handler = RestAPIEventHandler(
              self.routes, self.api, self.log, self.debug,
              middleware_handlers=self._get_middleware_handlers('http'),
          )
          self.current_request = handler.create_request_object(event, context)
          return handler(event, context)
  
  
  class BuiltinAuthConfig(object):
      def __init__(self, name, handler_string, ttl_seconds=None,
                   execution_role=None, header='Authorization'):
          # We'd also support all the misc config options you can set.
          self.name = name
          self.handler_string = handler_string
          self.ttl_seconds = ttl_seconds
          self.execution_role = execution_role
          self.header = header
  
  
  # ChaliceAuthorizer is unique in that the runtime component (the thing
  # that wraps the decorated function) also needs a reference to the config
  # object (the object the describes how to create the resource).  In
  # most event sources these are separate and don't need to know about
  # each other, but ChaliceAuthorizer does.  This is because the way
  # you associate a builtin authorizer with a view function is by passing
  # a direct reference:
  #
  # @app.authorizer(...)
  # def my_auth_function(...): pass
  #
  # @app.route('/', auth=my_auth_function)
  #
  # The 'route' part needs to know about the auth function for two reasons:
  #
  # 1. We use ``view.authorizer`` to figure out how to deploy the app
  # 2. We need a reference to the runtime handler for the auth in order
  #    to support local mode testing.
  # I *think* we can refactor things to handle both of those issues but
  # we would need more research to know for sure.  For now, this is a
  # special cased runtime class that knows about its config.
  class ChaliceAuthorizer(object):
      def __init__(self, name, func, scopes=None):
          self.name = name
          self.func = func
          self.scopes = scopes or []
          # This is filled in during the @app.authorizer()
          # processing.
          self.config = None
  
      def __call__(self, event, context):
          auth_request = self._transform_event(event)
          result = self.func(auth_request)
          if isinstance(result, AuthResponse):
              return result.to_dict(auth_request)
          return result
  
      def _transform_event(self, event):
          return AuthRequest(event['type'],
                             event['authorizationToken'],
                             event['methodArn'])
  
      def with_scopes(self, scopes):
          authorizer_with_scopes = copy.deepcopy(self)
          authorizer_with_scopes.scopes = scopes
          return authorizer_with_scopes
  
  
  class AuthRequest(object):
      def __init__(self, auth_type, token, method_arn):
          self.auth_type = auth_type
          self.token = token
          self.method_arn = method_arn
  
  
  class AuthResponse(object):
      ALL_HTTP_METHODS = ['DELETE', 'HEAD', 'OPTIONS',
                          'PATCH', 'POST', 'PUT', 'GET']
  
      def __init__(self, routes, principal_id, context=None):
          self.routes = routes
          self.principal_id = principal_id
          # The request is used to generate full qualified ARNs
          # that we need for the resource portion of the returned
          # policy.
          if context is None:
              context = {}
          self.context = context
  
      def to_dict(self, request):
          return {
              'context': self.context,
              'principalId': self.principal_id,
              'policyDocument': self._generate_policy(request),
          }
  
      def _generate_policy(self, request):
          allowed_resources = self._generate_allowed_resources(request)
          return {
              'Version': '2012-10-17',
              'Statement': [
                  {
                      'Action': 'execute-api:Invoke',
                      'Effect': 'Allow',
                      'Resource': allowed_resources,
                  }
              ]
          }
  
      def _generate_allowed_resources(self, request):
          allowed_resources = []
          for route in self.routes:
              if isinstance(route, AuthRoute):
                  methods = route.methods
                  path = route.path
              elif route == '*':
                  # A string route of '*' means that all paths and
                  # all HTTP methods are now allowed.
                  methods = ['*']
                  path = '*'
              else:
                  # If 'route' is just a string, then they've
                  # opted not to use the AuthRoute(), so we'll
                  # generate a policy that allows all HTTP methods.
                  methods = ['*']
                  path = route
              for method in methods:
                  allowed_resources.append(
                      self._generate_arn(path, request, method))
          return allowed_resources
  
      def _generate_arn(self, route, request, method='*'):
          incoming_arn = request.method_arn
          # An incoming_arn would look like this:
          # "arn:aws:execute-api:us-west-2:123:rest-api-id/stage/GET/needs/auth"
          # Then we pull out the rest-api-id and stage, such that:
          #   base = ['rest-api-id', 'stage']
          #
          # We rely on the fact that the first part of the ARN format is fixed
          # as:    arn:<partition>:<service>:<region>:<account-id>:<resource>
          arn_parts = incoming_arn.split(':', 5)
          allowed_resource = arn_parts[-1].split('/')[:2]
          # Now we add in the path components and rejoin everything
          # back together to make a full arn.
          # We're also assuming all HTTP methods (via '*') for now.
          # To support per HTTP method routes the API will need to be updated.
          # We also need to strip off the leading ``/`` so it can be
          # '/'.join(...)'d properly.
          allowed_resource.extend([method, route[1:]])
          last_arn_segment = '/'.join(allowed_resource)
          if route == '*':
              # We also have to handle the '*' case which matches
              # all routes.
              last_arn_segment += route
          arn_parts[-1] = last_arn_segment
          final_arn = ':'.join(arn_parts)
          return final_arn
  
  
  class AuthRoute(object):
      def __init__(self, path, methods):
          self.path = path
          self.methods = methods
  
  
  class LambdaFunction(object):
      def __init__(self, func, name, handler_string):
          self.func = func
          self.name = name
          self.handler_string = handler_string
  
      def __call__(self, event, context):
          return self.func(event, context)
  
  
  class BaseEventSourceConfig(object):
      def __init__(self, name, handler_string):
          self.name = name
          self.handler_string = handler_string
  
  
  class ScheduledEventConfig(BaseEventSourceConfig):
      def __init__(self, name, handler_string, schedule_expression, description):
          super(ScheduledEventConfig, self).__init__(name, handler_string)
          self.schedule_expression = schedule_expression
          self.description = description
  
  
  class CloudWatchEventConfig(BaseEventSourceConfig):
      def __init__(self, name, handler_string, event_pattern):
          super(CloudWatchEventConfig, self).__init__(name, handler_string)
          self.event_pattern = event_pattern
  
  
  class ScheduleExpression(object):
      def to_string(self):
          raise NotImplementedError("to_string")
  
  
  class Rate(ScheduleExpression):
      MINUTES = 'MINUTES'
      HOURS = 'HOURS'
      DAYS = 'DAYS'
  
      def __init__(self, value, unit):
          self.value = value
          self.unit = unit
  
      def to_string(self):
          unit = self.unit.lower()
          if self.value == 1:
              # Remove the 's' from the end if it's singular.
              # This is required by the cloudwatch events API.
              unit = unit[:-1]
          return 'rate(%s %s)' % (self.value, unit)
  
  
  class Cron(ScheduleExpression):
      def __init__(self, minutes, hours, day_of_month, month, day_of_week, year):
          self.minutes = minutes
          self.hours = hours
          self.day_of_month = day_of_month
          self.month = month
          self.day_of_week = day_of_week
          self.year = year
  
      def to_string(self):
          return 'cron(%s %s %s %s %s %s)' % (
              self.minutes,
              self.hours,
              self.day_of_month,
              self.month,
              self.day_of_week,
              self.year,
          )
  
  
  class S3EventConfig(BaseEventSourceConfig):
      def __init__(self, name, bucket, events, prefix, suffix, handler_string):
          super(S3EventConfig, self).__init__(name, handler_string)
          self.bucket = bucket
          self.events = events
          self.prefix = prefix
          self.suffix = suffix
  
  
  class SNSEventConfig(BaseEventSourceConfig):
      def __init__(self, name, handler_string, topic):
          super(SNSEventConfig, self).__init__(name, handler_string)
          self.topic = topic
  
  
  class SQSEventConfig(BaseEventSourceConfig):
      def __init__(self, name, handler_string, queue, queue_arn, batch_size):
          super(SQSEventConfig, self).__init__(name, handler_string)
          self.queue = queue
          self.queue_arn = queue_arn
          self.batch_size = batch_size
  
  
  class KinesisEventConfig(BaseEventSourceConfig):
      def __init__(self, name, handler_string, stream,
                   batch_size, starting_position):
          super(KinesisEventConfig, self).__init__(name, handler_string)
          self.stream = stream
          self.batch_size = batch_size
          self.starting_position = starting_position
  
  
  class DynamoDBEventConfig(BaseEventSourceConfig):
      def __init__(self, name, handler_string, stream_arn,
                   batch_size, starting_position):
          super(DynamoDBEventConfig, self).__init__(name, handler_string)
          self.stream_arn = stream_arn
          self.batch_size = batch_size
          self.starting_position = starting_position
  
  
  class WebsocketConnectConfig(BaseEventSourceConfig):
      CONNECT_ROUTE = '$connect'
  
      def __init__(self, name, handler_string, user_handler):
          super(WebsocketConnectConfig, self).__init__(name, handler_string)
          self.route_key_handled = self.CONNECT_ROUTE
          self.handler_function = user_handler
  
  
  class WebsocketMessageConfig(BaseEventSourceConfig):
      def __init__(self, name, route_key_handled, handler_string, user_handler):
          super(WebsocketMessageConfig, self).__init__(name, handler_string)
          self.route_key_handled = route_key_handled
          self.handler_function = user_handler
  
  
  class WebsocketDisconnectConfig(BaseEventSourceConfig):
      DISCONNECT_ROUTE = '$disconnect'
  
      def __init__(self, name, handler_string, user_handler):
          super(WebsocketDisconnectConfig, self).__init__(name, handler_string)
          self.route_key_handled = self.DISCONNECT_ROUTE
          self.handler_function = user_handler
  
  
  class PureLambdaWrapper(object):
      def __init__(self, original_func):
          self._original_func = original_func
  
      def __call__(self, event):
          # The @app.lambda_function() expects an event dict
          # and a context argument so this class will is used to adapt
          # from the Chalice single-arg style function (which is used
          # in all the event handlers) to the low-level lambda api.
          return self._original_func(event.to_dict(), event.context)
  
  
  class MiddlewareHandler(object):
      def __init__(self, handler, next_handler):
          self.handler = handler
          self.next_handler = next_handler
  
      def __call__(self, request):
          return self.handler(request, self.next_handler)
  
  
  class BaseLambdaHandler(object):
      def __call__(self, event, context):
          pass
  
      def _build_middleware_handlers(self, handlers, original_handler):
          current = original_handler
          for handler in reversed(list(handlers)):
              current = MiddlewareHandler(handler=handler, next_handler=current)
          return current
  
  
  class EventSourceHandler(BaseLambdaHandler):
  
      def __init__(self, func, event_class, middleware_handlers=None):
          self.func = func
          self.event_class = event_class
          if middleware_handlers is None:
              middleware_handlers = []
          self._middleware_handlers = middleware_handlers
          self.handler = None
  
      @property
      def middleware_handlers(self):
          return self._middleware_handlers
  
      @middleware_handlers.setter
      def middleware_handlers(self, value):
          self._middleware_handlers = value
  
      def __call__(self, event, context):
          event_obj = self.event_class(event, context)
          if self.handler is None:
              # Defer creating handlers so we have all middleware configured.
              self.handler = self._build_middleware_handlers(
                  self._middleware_handlers, original_handler=self.func)
          return self.handler(event_obj)
  
  
  class WebsocketEventSourceHandler(EventSourceHandler):
      def __init__(self, func, event_class, websocket_api,
                   middleware_handlers=None):
          self.func = func
          self.event_class = event_class
          self.websocket_api = websocket_api
          if middleware_handlers is None:
              middleware_handlers = []
          self._middleware_handlers = middleware_handlers
          self.handler = None
  
      def __call__(self, event, context):
          self.websocket_api.configure_from_api_id(
              event['requestContext']['apiId'],
              event['requestContext']['stage'],
          )
          super(WebsocketEventSourceHandler, self).__call__(event, context)
          return {'statusCode': 200}
  
  
  class RestAPIEventHandler(BaseLambdaHandler):
      def __init__(self, route_table, api, log, debug, middleware_handlers=None):
          self.routes = route_table
          self.api = api
          self.log = log
          self.debug = debug
          self.current_request = None
          self.lambda_context = None
          if middleware_handlers is None:
              middleware_handlers = []
          self._middleware_handlers = middleware_handlers
  
      def _global_error_handler(self, event, get_response):
          try:
              return get_response(event)
          except Exception:
              return self._unhandled_exception_to_response()
  
      def create_request_object(self, event, context):
          # For legacy reasons, there's some initial validation that takes
          # place before we convert the input event to a python object.
          # We don't do this in event handlers we added later, so we *should*
          # be able to remove this code.  To be safe, we're keeping it in for
          # now to minimize the potential for breaking changes.
          resource_path = event.get('requestContext', {}).get('resourcePath')
          if resource_path is not None:
              self.current_request = Request(event, context)
              return self.current_request
  
      def __call__(self, event, context):
          def wrapped_event(request):
              return self._main_rest_api_handler(event, context)
  
          final_handler = self._build_middleware_handlers(
              [self._global_error_handler] + list(self._middleware_handlers),
              original_handler=wrapped_event,
          )
          response = final_handler(self.current_request)
          return response.to_dict(self.api.binary_types)
  
      def _main_rest_api_handler(self, event, context):
          resource_path = event.get('requestContext', {}).get('resourcePath')
          if resource_path is None:
              return error_response(error_code='InternalServerError',
                                    message='Unknown request.',
                                    http_status_code=500)
          http_method = event['requestContext']['httpMethod']
          if http_method not in self.routes[resource_path]:
              allowed_methods = ', '.join(self.routes[resource_path].keys())
              return error_response(
                  error_code='MethodNotAllowedError',
                  message='Unsupported method: %s' % http_method,
                  http_status_code=405,
                  headers={'Allow': allowed_methods})
          route_entry = self.routes[resource_path][http_method]
          view_function = route_entry.view_function
          function_args = {name: event['pathParameters'][name]
                           for name in route_entry.view_args}
          self.lambda_context = context
          # We're getting the CORS headers before validation to be able to
          # output desired headers with
          cors_headers = None
          if self._cors_enabled_for_route(route_entry):
              cors_headers = self._get_cors_headers(route_entry.cors)
          # We're doing the header validation after creating the request
          # so can leverage the case insensitive dict that the Request class
          # uses for headers.
          if route_entry.content_types:
              content_type = self.current_request.headers.get(
                  'content-type', 'application/json')
              if not _matches_content_type(content_type,
                                           route_entry.content_types):
                  return error_response(
                      error_code='UnsupportedMediaType',
                      message='Unsupported media type: %s' % content_type,
                      http_status_code=415,
                      headers=cors_headers
                  )
          response = self._get_view_function_response(view_function,
                                                      function_args)
          if cors_headers is not None:
              self._add_cors_headers(response, cors_headers)
  
          response_headers = CaseInsensitiveMapping(response.headers)
          if not self._validate_binary_response(
                  self.current_request.headers, response_headers):
              content_type = response_headers.get('content-type', '')
              return error_response(
                  error_code='BadRequest',
                  message=('Request did not specify an Accept header with %s, '
                           'The response has a Content-Type of %s. If a '
                           'response has a binary Content-Type then the request '
                           'must specify an Accept header that matches.'
                           % (content_type, content_type)),
                  http_status_code=400,
                  headers=cors_headers
              )
          return response
  
      def _validate_binary_response(self, request_headers, response_headers):
          # Validates that a response is valid given the request. If the response
          # content-type specifies a binary type, there must be an accept header
          # that is a binary type as well.
          request_accept_header = request_headers.get('accept')
          response_content_type = response_headers.get(
              'content-type', 'application/json')
          response_is_binary = _matches_content_type(response_content_type,
                                                     self.api.binary_types)
          expects_binary_response = False
          if request_accept_header is not None:
              expects_binary_response = _matches_content_type(
                  request_accept_header, self.api.binary_types)
          if response_is_binary and not expects_binary_response:
              return False
          return True
  
      def _get_view_function_response(self, view_function, function_args):
          try:
              response = view_function(**function_args)
              if not isinstance(response, Response):
                  response = Response(body=response)
              self._validate_response(response)
          except ChaliceUnhandledError:
              # Reraise this exception so that middleware has a chance
              # to handle the exception.
              raise
          except ChaliceViewError as e:
              # Any chalice view error should propagate.  These
              # get mapped to various HTTP status codes in API Gateway.
              response = Response(body={'Code': e.__class__.__name__,
                                        'Message': str(e)},
                                  status_code=e.STATUS_CODE)
          except Exception:
              response = self._unhandled_exception_to_response()
          return response
  
      def _unhandled_exception_to_response(self):
          headers = {}
          path = getattr(self.current_request, 'path', 'unknown')
          self.log.error("Caught exception for path %s", path, exc_info=True)
          if self.debug:
              # If the user has turned on debug mode,
              # we'll let the original exception propagate so
              # they get more information about what went wrong.
              stack_trace = ''.join(traceback.format_exc())
              body = stack_trace
              headers['Content-Type'] = 'text/plain'
          else:
              body = {'Code': 'InternalServerError',
                      'Message': 'An internal server error occurred.'}
          response = Response(body=body, headers=headers, status_code=500)
          return response
  
      def _validate_response(self, response):
          for header, value in response.headers.items():
              if '\n' in value:
                  raise ChaliceError("Bad value for header '%s': %r" %
                                     (header, value))
  
      def _cors_enabled_for_route(self, route_entry):
          return route_entry.cors is not None
  
      def _get_cors_headers(self, cors):
          return cors.get_access_control_headers()
  
      def _add_cors_headers(self, response, cors_headers):
          for name, value in cors_headers.items():
              if name not in response.headers:
                  response.headers[name] = value
  
  
  # These classes contain all the event types that are passed
  # in as arguments in the lambda event handlers.  These are
  # part of Chalice's public API and must be backwards compatible.
  
  class BaseLambdaEvent(object):
      def __init__(self, event_dict, context):
          self._event_dict = event_dict
          self.context = context
          self._extract_attributes(event_dict)
  
      def _extract_attributes(self, event_dict):
          raise NotImplementedError("_extract_attributes")
  
      def to_dict(self):
          return self._event_dict
  
  
  # This class is only used for middleware handlers because
  # we can't change the existing interface for @app.lambda_function().
  # This could be a Chalice 2.0 thing where we make all the decorators
  # have a consistent interface that takes a single event arg.
  class LambdaFunctionEvent(BaseLambdaEvent):
      def __init__(self, event_dict, context):
          self.event = event_dict
          self.context = context
  
      def _extract_attributes(self, event_dict):
          pass
  
      def to_dict(self):
          return self.event
  
  
  class CloudWatchEvent(BaseLambdaEvent):
      def _extract_attributes(self, event_dict):
          self.version = event_dict['version']
          self.account = event_dict['account']
          self.region = event_dict['region']
          self.detail = event_dict['detail']
          self.detail_type = event_dict['detail-type']
          self.source = event_dict['source']
          self.time = event_dict['time']
          self.event_id = event_dict['id']
          self.resources = event_dict['resources']
  
  
  class WebsocketEvent(BaseLambdaEvent):
      def __init__(self, event_dict, context):
          super(WebsocketEvent, self).__init__(event_dict, context)
          self._json_body = None
  
      def _extract_attributes(self, event_dict):
          request_context = event_dict['requestContext']
          self.domain_name = request_context['domainName']
          self.stage = request_context['stage']
          self.connection_id = request_context['connectionId']
          self.body = event_dict.get('body')
  
      @property
      def json_body(self):
          if self._json_body is None:
              try:
                  self._json_body = json.loads(self.body)
              except ValueError:
                  raise BadRequestError('Error Parsing JSON')
          return self._json_body
  
  
  class SNSEvent(BaseLambdaEvent):
      def _extract_attributes(self, event_dict):
          first_record = event_dict['Records'][0]
          self.message = first_record['Sns']['Message']
          self.subject = first_record['Sns']['Subject']
  
  
  class S3Event(BaseLambdaEvent):
      def _extract_attributes(self, event_dict):
          s3 = event_dict['Records'][0]['s3']
          self.bucket = s3['bucket']['name']
          self.key = unquote_str(s3['object']['key'])
  
  
  class SQSEvent(BaseLambdaEvent):
      def _extract_attributes(self, event_dict):
          # We don't extract anything off the top level
          # event.
          pass
  
      def __iter__(self):
          for record in self._event_dict['Records']:
              yield SQSRecord(record, self.context)
  
  
  class SQSRecord(BaseLambdaEvent):
      def _extract_attributes(self, event_dict):
          self.body = event_dict['body']
          self.receipt_handle = event_dict['receiptHandle']
  
  
  class KinesisEvent(BaseLambdaEvent):
      def _extract_attributes(self, event_dict):
          pass
  
      def __iter__(self):
          for record in self._event_dict['Records']:
              yield KinesisRecord(record, self.context)
  
  
  class KinesisRecord(BaseLambdaEvent):
      def _extract_attributes(self, event_dict):
          kinesis = event_dict['kinesis']
          encoded_payload = kinesis['data']
          self.data = base64.b64decode(encoded_payload)
          self.sequence_number = kinesis['sequenceNumber']
          self.partition_key = kinesis['partitionKey']
          self.schema_version = kinesis['kinesisSchemaVersion']
          self.timestamp = datetime.datetime.utcfromtimestamp(
              kinesis['approximateArrivalTimestamp'])
  
  
  class DynamoDBEvent(BaseLambdaEvent):
      def _extract_attributes(self, event_dict):
          pass
  
      def __iter__(self):
          for record in self._event_dict['Records']:
              yield DynamoDBRecord(record, self.context)
  
  
  class DynamoDBRecord(BaseLambdaEvent):
      def _extract_attributes(self, event_dict):
          dynamodb = event_dict['dynamodb']
          self.timestamp = datetime.datetime.utcfromtimestamp(
              dynamodb['ApproximateCreationDateTime'])
          self.keys = dynamodb.get('Keys')
          self.new_image = dynamodb.get('NewImage')
          self.old_image = dynamodb.get('OldImage')
          self.sequence_number = dynamodb['SequenceNumber']
          self.size_bytes = dynamodb['SizeBytes']
          self.stream_view_type = dynamodb['StreamViewType']
          # These are from the top level keys in a record.
          self.aws_region = event_dict['awsRegion']
          self.event_id = event_dict['eventID']
          self.event_name = event_dict['eventName']
          self.event_source_arn = event_dict['eventSourceARN']
  
      @property
      def table_name(self):
          # Converts:
          # "arn:aws:dynamodb:us-west-2:12345:table/MyTable/"
          # "stream/2020-09-28T16:49:14.209"
          #
          # into:
          # "MyTable"
          parts = self.event_source_arn.split(':', 5)
          if not len(parts) == 6:
              return ''
          full_name = parts[-1]
          name_parts = full_name.split('/')
          if len(name_parts) >= 2:
              return name_parts[1]
          return ''
  
  
  class Blueprint(DecoratorAPI):
      def __init__(self, import_name):
          self._import_name = import_name
          self._deferred_registrations = []
          self._current_app = None
          self._lambda_context = None
  
      @property
      def log(self):
          if self._current_app is None:
              raise RuntimeError(
                  "Can only access Blueprint.log if it's registered to an app."
              )
          return self._current_app.log
  
      @property
      def current_request(self):
          if self._current_app is None:
              raise RuntimeError(
                  "Can only access Blueprint.current_request if it's registered "
                  "to an app."
              )
          return self._current_app.current_request
  
      @property
      def current_app(self):
          if self._current_app is None:
              raise RuntimeError(
                  "Can only access Blueprint.current_app if it's registered "
                  "to an app."
              )
          return self._current_app
  
      @property
      def lambda_context(self):
          if self._current_app is None:
              raise RuntimeError(
                  "Can only access Blueprint.lambda_context if it's registered "
                  "to an app."
              )
          return self._current_app.lambda_context
  
      def register(self, app, options):
          self._current_app = app
          all_options = options.copy()
          all_options['module_name'] = self._import_name
          for function in self._deferred_registrations:
              function(app, all_options)
  
      # Note on blueprints implementation.  One option we have for implementing
      # blueprints is to copy every decorator in our public API over to the
      # Blueprints class.  Instead what we do is inherit from DecoratorAPI so we
      # get new decorators for free.  The tradeoff is that need to add
      # implementations of the internal methods used to manage handler
      # registration that defer registration until we get an app object. While
      # these methods are not public in the sense that we don't want users to
      # call them, they're available for blueprints to use in order to avoid
      # boilerplate code.
  
      def register_middleware(self, func, event_type='all'):
          self._deferred_registrations.append(
              # pylint: disable=protected-access
              lambda app, options: app.register_middleware(
                  func, event_type
              )
          )
  
      def _register_handler(self, handler_type, name, user_handler,
                            wrapped_handler, kwargs, options=None):
          # If we go through the public API (app.route, app.schedule, etc) then
          # we have to duplicate either the methods or the params in this
          # class.  We're using _register_handler as a tradeoff for cutting
          # down on the duplication.
          def _register_blueprint_handler(app, options):
              if handler_type in _EVENT_CLASSES:
                  # pylint: disable=protected-access
                  wrapped_handler.middleware_handlers = \
                      app._get_middleware_handlers(
                          _MIDDLEWARE_MAPPING[handler_type])
              # pylint: disable=protected-access
              app._register_handler(
                  handler_type, name, user_handler, wrapped_handler,
                  kwargs, options
              )
          self._deferred_registrations.append(_register_blueprint_handler)
  
      def _get_middleware_handlers(self, event_type):
          # This will get filled in later during the registration process.
          return []
  
  
  # This class is used to convert any existing/3rd party decorators
  # that work directly on lambda functions with the original signature
  # of (event, context).  By using ConvertToMiddleware you can automatically
  # apply this decorator to every lambda function in a Chalice app.
  # Example:
  #
  # Before:
  #
  # @third_part.decorator
  # def some_lambda_function(event, context): pass
  #
  # Now:
  #
  # app.register_middleware(ConvertToMiddleware(third_party.decorator))
  #
  #
  class ConvertToMiddleware(object):
      def __init__(self, lambda_wrapper):
          self._wrapper = lambda_wrapper
  
      def __call__(self, event, get_response):
          original_event, context = self._extract_original_param(event)
          @functools.wraps(self._wrapper)
          def wrapped(original_event, context):
              return get_response(event)
          return self._wrapper(wrapped)(original_event, context)
  
      def _extract_original_param(self, event):
          if isinstance(event, Request):
              return event.to_original_event(), event.lambda_context
          return event.to_dict(), event.context
  
  
  _EVENT_CLASSES = {
      'on_s3_event': S3Event,
      'on_sns_message': SNSEvent,
      'on_sqs_message': SQSEvent,
      'on_cw_event': CloudWatchEvent,
      'on_kinesis_record': KinesisEvent,
      'on_dynamodb_record': DynamoDBEvent,
      'schedule': CloudWatchEvent,
      'lambda_function': LambdaFunctionEvent,
  }
  
  
  _MIDDLEWARE_MAPPING = {
      'on_s3_event': 's3',
      'on_sns_message': 'sns',
      'on_sqs_message': 'sqs',
      'on_cw_event': 'cloudwatch',
      'on_kinesis_record': 'kinesis',
      'on_dynamodb_record': 'dynamodb',
      'schedule': 'scheduled',
      'lambda_function': 'pure_lambda',
  }
  