# Technical Documentation: aws/chalice

> ℹ️ **Provenance:** Multi-Tier Ingestion · [CodeWiki Reference](https://codewiki.google/github.com/aws/chalice)

### Source Api (`docs/source/api.rst`)

Chalice
=======

.. class:: Chalice(app_name)

   This class represents a chalice application.  It provides:

   * The ability to register routes using the :meth:`route` method.
   * Within a view function, the ability to introspect the current
     request using the ``current_request`` attribute which is an instance
     of the :class:`Request` class.

   .. attribute:: app_name

      The name of the Chalice app.  This corresponds to the value provided
      when instantiating a ``Chalice`` object.

   .. attribute:: current_request

      An object of type :class:`Request`.  This value is only set when
      a view function is being called.  This attribute can be used to
      introspect the current HTTP request.

   .. attribute:: api

      An object of type :class:`APIGateway`. This attribute can be used to control
      how apigateway interprets ``Content-Type`` headers in both requests and
      responses.

   .. attribute:: lambda_context

      A Lambda context object that is passed to the invoked view by AWS
      Lambda. You can find out more about this object by reading the
      `lambda context object documentation <https://docs.aws.amazon.com/lambda/latest/dg/python-context-object.html>`_.

      .. note::

       This is only set on ``@app.route`` handlers. For other handlers it
       will be ``None``. Instead the event parameter will have a ``context``
       property. For example :attr:`S3Event.context`.

   .. attribute:: debug

      A boolean value that enables debugging.  By default, this value is
      ``False``.  If debugging is true, then internal errors are returned back
      to the client.  Additionally, debug log messages generated by the
      framework will show up in the cloudwatch logs.  Example usage:

      .. code-block:: python

         from chalice import Chalice

         app = Chalice(app_name="appname")
         app.debug = True

   .. attribute:: websocket_api

      An object of type :class:`WebsocketAPI`. This attribute can be used to
      send messages to websocket clients connected through API Gateway.

   .. method:: route(path, \*\*options)

      Register a view function for a particular URI path.  This method
      is intended to be used as a decorator for a view function.  For example:

      .. code-block:: python

         from chalice import Chalice

         app = Chalice(app_name="appname")

         @app.route('/resource/{value}', methods=['PUT'])
         def viewfunction(value):
             pass


      :param str path: The path to associate with the view function.  The
        ``path`` should only contain ``[a-zA-Z0-9._-]`` chars and curly
        braces for parts of the URL you would like to capture.  The path
        should not end in a trailing slash, otherwise a validation error
        will be raised during deployment.

      :param list methods: Optional parameter that indicates which HTTP methods
        this view function should accept.  By default, only ``GET`` requests
        are supported.  If you only wanted to support ``POST`` requests, you
        would specify ``methods=['POST']``.  If you support multiple HTTP
        methods in a single view function (``methods=['GET', 'POST']``), you
        can check the :attr:`app.current_request.method <Request.method>`
        attribute to see which HTTP method was used when making the request.
        You can provide any HTTP method supported by API Gateway, which
        includes: ``GET``, ``POST``, ``PUT``, ``PATCH``, ``HEAD``,
        ``OPTIONS``, and ``DELETE``.

      :param str name: Optional parameter to specify the name of the view
        function.  You generally do not need to set this value.  The name
        of the view function is used as the default value for the view name.

      :param Authorizer authorizer: Specify an authorizer to use for this
        view.  Can be an instance of :class:`CognitoUserPoolAuthorizer`,
        :class:`CustomAuthorizer` or :class:`IAMAuthorizer`.

      :param str content_types: A list of content types to accept for
        this view.  By default ``application/json`` is accepted.  If
        this value is specified, then chalice will reject any incoming request
        that does not match the provided list of content types with a
        415 Unsupported Media Type response.

      :param boolean api_key_required: Optional parameter to specify whether
        the method required a valid API key.

      :param cors: Specify if CORS is supported for this view.  This can either
        by a boolean value, ``None``, or an instance of :class:`CORSConfig`.
        Setting this value is set to ``True`` gives similar behavior to enabling
        CORS in the AWS Console.  This includes injecting the
        ``Access-Control-Allow-Origin`` header to have a value of ``*`` as well
        as adding an ``OPTIONS`` method to support preflighting requests.  If
        you would like more control over how CORS is configured, you can provide
        an instance of :class:`CORSConfig`.

   .. method:: authorizer(name, \*\*options)

      Register a built-in authorizer.

      .. code-block:: python

         from chalice import Chalice, AuthResponse

         app = Chalice(app_name="appname")

         @app.authorizer(ttl_seconds=30)
         def my_auth(auth_request):
             # Validate auth_request.token, and then:
             return AuthResponse(routes=['/'], principal_id='username')

         @app.route('/', authorizer=my_auth)
         def viewfunction(value):
             pass

      :param ttl_seconds: The number of seconds to cache this response.
        Subsequent requests that require this authorizer will use a
        cached response if available.  The default is 300 seconds.

      :param execution_role: An optional IAM role to specify when invoking
        the Lambda function associated with the built-in authorizer.

      :param header: The header where the auth token will be specified.
        The default is ``Authorization``

   .. method:: schedule(expression, name=None)

      Register a scheduled event that's invoked on a regular schedule.
      This will create a lambda function associated with the decorated
      function.  It will also schedule the lambda function to be invoked
      with a scheduled CloudWatch Event.

      See :ref:`scheduled-events` for more information.

      .. code-block:: python

          @app.schedule('cron(15 10 ? * 6L 2002-2005)')
          def cron_handler(event):
              pass

          @app.schedule('rate(5 minutes)')
          def rate_handler(event):
              pass

          @app.schedule(Rate(5, unit=Rate.MINUTES))
          def rate_obj_handler(event):
              pass

          @app.schedule(Cron(15, 10, '?', '*', '6L', '2002-2005'))
          def cron_obj_handler(event):
              pass


      :param expression: The schedule expression to use for the CloudWatch
        event rule.  This value can either be a string value or an
        instance of type ``ScheduleExpression``, which is either a
        :class:`Cron` or :class:`Rate` object.  If a string value is
        provided, it will be provided directly as the ``ScheduleExpression``
        value in the `PutRule <https://docs.aws.amazon.com/AmazonCloudWatchEvents/latest/APIReference/API_PutRule.html#API_PutRule_RequestSyntax>`__ API
        call.

      :param name: The name of the function to use.  This name is combined
        with the chalice app name as well as the stage name to create the
        entire lambda function name.  This parameter is optional.  If it is
        not provided, the name of the python function will be used.

   .. method:: on_cw_event(pattern, name=None)

      Create a lambda function and configure it to be invoked whenever
      an event that matches the given pattern flows through CloudWatch Events
      or Event Bridge.

      :param pattern: The event pattern to use to filter subscribed events.
        See the CloudWatch Events docs for examples https://amzn.to/2OlqZso

      :param name: The name of the function to create.  This name is combined
        with the chalice app name as well as the stage name to create the
        entire lambda function name.  This parameter is optional.  If it is
        not provided, the name of the python function will be used.

   .. method:: on_s3_event(bucket, events=None, prefix=None, suffix=None, name=None)

      Create a lambda function and configure it to be automatically invoked
      whenever an event happens on an S3 bucket.

      .. warning::

          You can't use the ``chalice package`` command when using the
          ``on_s3_event`` decorator.  This is because CFN does not support
          configuring an existing S3 bucket.

      See :ref:`s3-events` for more information.

      This example shows how you could implement an image resizer that's
      triggered whenever an object is uploaded to the ``images/`` prefix
      of an S3 bucket (e.g ``s3://mybucket/images/house.jpg``).

      .. code-block:: python

          @app.on_s3_event('mybucket', events=['s3:ObjectCreated:Put'],
                           prefix='images/', suffix='.jpg')
          def resize_image(event):
              with tempfile.NamedTemporaryFile('w') as f:
                  s3.download_file(event.bucket, event.key, f.name)
                  resize_image(f.name)
                  s3.upload_file(event.bucket, 'resized/%s' % event.key, f.name)


      :param bucket: The name of the S3 bucket.  This bucket must already exist.

      :param events: A list of strings indicating the events that should trigger
        the lambda function.  See `Supported Event Types <https://docs.aws.amazon.com/AmazonS3/latest/dev/NotificationHowTo.html#supported-notification-event-types>`__
        for the full list of strings you can provide.  If this option is not
        provided, a default of ``['s3:ObjectCreated:*']`` is used, which will
        configure the lambda function to be invoked whenever a new object
        is created in the S3 bucket.

      :param prefix: An optional key prefix.  This specifies that
        the lambda function should only be invoked if the key starts with
        this prefix (e.g. ``prefix='images/'``).  Note that this value
        is not a glob (e.g. ``images/*``), it is a literal string match
        for the start of the key.

      :param suffix: An optional key suffix.  This specifies that the
        lambda function should only be invoked if the key name ends with
        this suffix (e.g. ``suffix='.jpg'``).  Note that this value is
        not a glob (e.g. ``*.txt``), it is a literal string match for
        the end of the key.

      :param name: The name of the function to use.  This name is combined
        with the chalice app name as well as the stage name to create the
        entire lambda function name.  This parameter is optional.  If it is
        not provided, the name of the python function will be used.

   .. method:: on_sns_message(topic, name=None)

      Create a lambda function and configure it to be automatically invoked
      whenever an SNS message is published to the specified topic.

      See :ref:`sns-events` for more information.

      This example prints the subject and the contents of the message whenever
      something publishes to the sns topic of ``mytopic``.  In this example,
      the input parameter is of type :class:`SNSEvent`.

      .. code-block:: python

          app.debug = True

          @app.on_sns_message(topic='mytopic')
          def handler(event):
              app.log.info("SNS subject: %s", event.subject)
              app.log.info("SNS message: %s", event.message)

      :param topic: The name or ARN of the SNS topic you want to subscribe to.

      :param name: The name of the function to use.  This name is combined
        with the chalice app name as well as the stage name to create the
        entire lambda function name.  This parameter is optional.  If it is
        not provided, the name of the python function will be used.

   .. method:: on_sqs_message(queue, batch_size=1, name=None, queue_arn=None, maximum_batching_window_in_seconds=0, maximum_concurrency=None)

      Create a lambda function and configure it to be automatically invoked
      whenever a message is published to the specified SQS queue.

      The lambda function must accept a single parameter which
      is of type :class:`SQSEvent`.

      If the decorated function returns without raising any exceptions
      then Lambda will automatically delete the SQS messages associated
      with the :class:`SQSEvent`.  You don't need to manually delete
      messages.  If any exception is raised, Lambda won't delete any messages,
      and the messages will become available once the visibility timeout
      has been reached.  Note that for batch sizes of more than one, either
      the entire batch succeeds and all the messages in the batch are
      deleted by Lambda, or the entire batch fails.  The default batch size
      is 1.  See the
      `Using AWS Lambda with Amazon SQS <https://docs.aws.amazon.com/lambda/latest/dg/with-sqs.html>`__
      for more information on how Lambda integrates with SQS.

      See the :ref:`sqs-events` topic guide for more information on using SQS
      in Chalice.

      .. code-block:: python

          app.debug = True

          @app.on_sqs_message(queue='myqueue')
          def handler(event):
              app.log.info("Event: %s", event.to_dict())
              for record in event:
                  app.log.info("Message body: %s", record.body)

      :param queue: The name of the SQS queue you want to subscribe to.
        This is the name of the queue, not the ARN or Queue URL.

      :param batch_size: The maximum number of messages to retrieve
        when polling for SQS messages.  The event parameter can have
        multiple SQS messages associated with it.  This is why the
        event parameter passed to the lambda function is iterable.  The
        batch size controls how many messages can be in a single event.

      :param name: The name of the function to use.  This name is combined
        with the chalice app name as well as the stage name to create the
        entire lambda function name.  This parameter is optional.  If it is
        not provided, the name of the python function will be used.

      :param queue_arn: The ARN of the SQS queue you want to subscribe to.
        This argument is mutually exclusive with the ``queue`` parameter.
        This is useful if you already know the exact ARN or when integrating
        with the AWS CDK to create your SQS queue.

      :param maximum_batching_window_in_seconds: The maximum amount of time,
        in seconds, to gather records before invoking the function.

      :param maximum_concurrency: The maximum number of concurrent functions
        that the event source can invoke.

   .. method:: on_kinesis_record(stream, batch_size=100, starting_position='LATEST', name=None, maximum_batching_window_in_seconds=0)

      Create a lambda function and configure it to be automatically invoked
      whenever data is published to the specified Kinesis stream.

      The lambda function must accept a single parameter which
      is of type :class:`KinesisEvent`.

      If the decorated function raises an exception, Lambda retries the
      batch until processing succeeds or the data expires.

      See
      `Using AWS Lambda with Amazon Kinesis <https://docs.aws.amazon.com/lambda/latest/dg/with-kinesis.html>`__
      for more information on how Lambda integrates with Kinesis.

      .. code-block:: python

          app.debug = True

          @app.on_kinesis_record(stream='mystream')
          def handler(event):
              app.log.info("Event: %s", event.to_dict())
              for record in event:
                  app.log.info("Message body: %s", record.data)

      :param stream: The name of the Kinesis stream you want to subscribe to.
        This is the name of the data stream, not the ARN.

      :param batch_size: The maximum number of messages to retrieve
        when polling for Kinesis messages.  The event parameter can have
        multiple Kinesis records associated with it.  This is why the
        event parameter passed to the lambda function is iterable.  The
        batch size controls how many messages can be in a single event.

      :param starting_position: Specifies where to start processing records.
        This can have the following values:

        * ``LATEST`` - Process new records that are added to the stream.
        * ``TRIM_HORIZON`` - Process all records in the stream.

      :param name: The name of the function to use.  This name is combined
        with the chalice app name as well as the stage name to create the
        entire lambda function name.  This parameter is optional.  If it is
        not provided, the name of the python function will be used.

      :param maximum_batching_window_in_seconds: The maximum amount of time,
        in seconds, to gather records before invoking the function.

   .. method:: on_dynamodb_record(stream_arn, batch_size=100, starting_position='LATEST', name=None, maximum_batching_window_in_seconds=0)

      Create a lambda function and configure it to be automatically invoked
      whenever data is written to a DynamoDB stream.

      The lambda function must accept a single parameter which
      is of type :class:`DynamoDBEvent`.

      If the decorated function raises an exception, Lambda retries the
      batch until processing succeeds or the data expires.

      See
      `Using AWS Lambda with Amazon DynamoDB <https://docs.aws.amazon.com/lambda/latest/dg/with-ddb.html>`__
      for more information on how Lambda integrates with DynamoDB Streams.

      .. code-block:: python

          app.debug = True

          @app.on_dynamodb_record(stream_arn='arn:aws:dynamodb:...:stream')
          def handler(event):
              app.log.info("Event: %s", event.to_dict())
              for record in event:
                  app.log.info("New: %s", record.new_image)

      :param stream_arn: The name of the DynamoDB stream ARN you want to
        subscribe to.  Note that, unlike other event handlers that accept
        the resource name, you must provide the stream ARN when subscribing
        to the DynamoDB stream ARN.

      :param batch_size: The maximum number of messages to retrieve
        when polling for DynamoDB messages.  The event parameter can have
        multiple DynamoDB records associated with it.  This is why the
        event parameter passed to the lambda function is iterable.  The
        batch size controls how many messages can be in a single event.

      :param starting_position: Specifies where to start processing records.
        This can have the following values:

        * ``LATEST`` - Process new records that are added to the stream.
        * ``TRIM_HORIZON`` - Process all records in the stream.

      :param name: The name of the function to use.  This name is combined
        with the chalice app name as well as the stage name to create the
        entire lambda function name.  This parameter is optional.  If it is
        not provided, the name of the python function will be used.

      :param maximum_batching_window_in_seconds: The maximum amount of time,
        in seconds, to gather records before invoking the function.

   .. method:: lambda_function(name=None)

      Create a pure lambda function that's not connected to anything.

      See :doc:`topics/purelambda` for more information.

      :param name: The name of the function to use.  This name is combined
        with the chalice app name as well as the stage name to create the
        entire lambda function name.  This parameter is optional.  If it is
        not provided, the name of the python function will be used.

   .. method:: register_blueprint(blueprint, name_prefix=None, url_prefix=None)

      Register a :class:`Blueprint` to a Chalice app.
      See :doc:`topics/blueprints` for more information.

      :param blueprint: The :class:`Blueprint` to register to the app.

      :param name_prefix: An optional name prefix that's added to all the
        resources specified in the blueprint.

      :param url_prefix: An optional url prefix that's added to all the
        routes defined the Blueprint.  This allows you to set the root mount
        point for all URLs in a Blueprint.

   .. method:: on_ws_connect(event)

      Create a Websocket API connect event handler.

      :param event: The :class:`WebsocketEvent` received to indicate a new
         connection has been registered with API Gateway. The identifier of this
         connection is under the :attr:`WebsocketEvent.connection_id` attribute.

      see :doc:`topics/websockets` for more information.

   .. method:: on_ws_message(event)

      Create a Websocket API message event handler.

      :param event: The :class:`WebsocketEvent` received to indicate API Gateway
         received a message from a connected client. The identifier of the
         client that sent the message is under the
         :attr:`WebsocketEvent.connection_id` attribute. The content of the
         message is available in the :attr:`WebsocketEvent.body` attribute.

      see :doc:`topics/websockets` for more information.

   .. method:: on_ws_disconnect(event)

      Create a Websocket API disconnect event handler.

      :param event: The :class:`WebsocketEvent` received to indicate an existing
         connection has been disconnected from API Gateway. The identifier of this
         connection is under the :attr:`WebsocketEvent.connection_id` attribute.

      see :doc:`topics/websockets` for more information.

   .. method:: middleware(event_type='all')

      Register a middleware with a Chalice application.
      This decorator will register a function as Chalice middleware, which
      will be automatically invoked as part of the request/response cycle for
      a Lambda invocation.  You can provide the ``event_type`` argument to
      indicate what type of lambda events you want to register with.  The
      default value, ``all``, indicates that the middleware will be called
      for all Lambda functions defined in your Chalice app.  Supported
      values are:

      * ``all`` - ``Any``
      * ``s3`` - :class:`S3Event`
      * ``sns`` - :class:`SNSEvent`
      * ``sqs`` - :class:`SQSEvent`
      * ``cloudwatch`` - :class:`CloudWatchEvent`
      * ``scheduled`` - :class:`CloudWatchEvent`
      * ``websocket`` - :class:`WebsocketEvent`
      * ``http`` - :class:`Request`
      * ``pure_lambda`` - :class:`LambdaFunctionEvent`

      The decorated function must accept two arguments, ``event`` and
      ``get_response``.  The ``event`` is the input event associated with
      the Lambda invocation, and ``get_response`` is a callable that takes
      an input event and will invoke the next middleware in the chain,
      and eventually the original Lambda handler.  Below is a noop middleware
      that shows the minimum needed to write middleware:

      .. code-block:: python

          @app.middleware('all')
          def mymiddleware(event, get_response):
              return get_response(event)

      See :doc:`topics/middleware` for more information on writing middleware.

   .. method:: register_middleware(func, event_type='all')

      Register a middleware with a Chalice application.  This is the same
      behavior as the :meth:`Chalice.middleware` decorator and is useful
      if you want to register middleware for pre-existing functions:

      .. code-block:: python

          import thirdparty

          app.register_middleware(thirdparty.func, 'all')

.. class:: ConvertToMiddleware(lambda_wrapper)

   This class is used to convert a function that wraps/proxies a Lambda
   function into middleware.  This allows this wrapper to automatically
   be applied to every function in your app.  For example, if you had the
   following logging decorator:

   .. code-block:: python

       def log_invocation(func):
           def wrapper(event, context):
               logger.debug("Before lambda function.")
               response = func(event, context)
               logger.debug("After lambda function.")
           return wrapper

       @app.lambda_function()
       @log_invocation
       def myfunction(event, context):
           logger.debug("In myfunction().")


   Rather than decorate every Lambda function with the ``@log_invocation``
   decorator, you can instead use ``ConvertToMiddleware`` to automatically
   apply this wrapper to every Lambda function in your app.

   .. code-block:: python

       app.register_middleware(ConvertToMiddleware(log_invoation))


Request
=======

.. class:: Request

  A class that represents the current request.  This is mapped to
  the ``app.current_request`` object.

  .. code-block:: python

      @app.route('/objects/{key}', methods=['GET', 'PUT'])
      def myobject(key):
          request = app.current_request  # type: Request
          if request.method == 'PUT':
              # handle PUT request
              pass
          elif request.method == 'GET':
              # handle GET request
              pass


  .. attribute:: path

     The path of the HTTP request.

  .. attribute:: query_params

     A MultiDict of the query params for the request.  This value is ``None``
     if no query params were provided in the request. The MultiDict acts like a
     normal dictionary except that you can call the method ``getlist()`` to get
     multiple keys from the same query string parameter

     .. code-block:: python

          request = app.current_request
          # Raises an exception if key doesn't exist, usual Python behavior.
          single_param = request.query_params['single']

          # None if key doesn't exist, usual Python behavior
          another_param = request.query_params.get('another_param')

          # A List of all parameters named multi_param, Throws an exception if
          # key doesn't exist
          multi_param_list = request.query_params.getlist('multi_param')

  .. attribute:: headers

     A dict of the request headers.

  .. attribute:: uri_params

     A dict of the captured URI params.  This value is ``None`` if no
     URI params were provided in the request.

  .. attribute:: method

     The HTTP method as a string.

  .. attribute:: json_body

     The parsed JSON body (``json.loads(raw_body)``).  This value will only
     be non-None if the Content-Type header is ``application/json``, which
     is the default content type value in chalice.

  .. attribute:: raw_body

     The raw HTTP body as bytes.  This is useful if you need to
     calculate a checksum of the HTTP body.

  .. attribute:: context

     A dict of additional context information.

  .. attribute:: stage_vars

     A dict of configuration for the API Gateway stage.

   .. attribute:: lambda_context

     A Lambda context object that is passed to the invoked view by AWS
     Lambda. You can find out more about this object by reading the
     `lambda context object documentation <https://docs.aws.amazon.com/lambda/latest/dg/python-context-object.html>`_.

  .. method:: to_dict()

     Convert the :class:`Request` object to a dictionary.  This is useful
     for debugging purposes.  This dictionary is guaranteed to be JSON
     serializable so you can return this value from a chalice view.


Response
========

.. class:: Response(body, headers=None, status_code=200)

  A class that represents the response for the view function.  You
  can optionally return an instance of this class from a view function if you
  want complete control over the returned HTTP response.

  .. code-block:: python

      from chalice import Chalice, Response

      app = Chalice(app_name='custom-response')


      @app.route('/')
      def index():
          return Response(body='hello world!',
                          status_code=200,
                          headers={'Content-Type': 'text/plain'})


  .. versionadded:: 0.6.0

  .. attribute:: body

     The HTTP response body to send back.  This value must be a string.

  .. attribute:: headers

     An optional dictionary of HTTP headers to send back.  This is a dictionary
     of header name to header value, e.g ``{'Content-Type': 'text/plain'}``

  .. attribute:: status_code

     The integer HTTP status code to send back in the HTTP response.


Authorization
=============

Each of these classes below can be provided using the ``authorizer`` argument
for an ``@app.route(authorizer=...)`` call:


.. code-block:: python

    authorizer = CognitoUserPoolAuthorizer(
        'MyPool', header='Authorization',
        provider_arns=['arn:aws:cognito:...:userpool/name'])

    @app.route('/user-pools', methods=['GET'], authorizer=authorizer)
    def authenticated():
        return {"secure": True}


.. class:: CognitoUserPoolAuthorizer(name, provider_arns, header='Authorization')

  .. versionadded:: 0.8.1

  .. attribute:: name

     The name of the authorizer.

  .. attribute:: provider_arns

     The Cognito User Pool arns to use.

  .. attribute:: header

     The header where the auth token will be specified.

.. class:: IAMAuthorizer()

  .. versionadded:: 0.8.3

.. class:: CustomAuthorizer(name, authorizer_uri, ttl_seconds, header='Authorization')

  .. versionadded:: 0.8.1

  .. attribute:: name

     The name of the authorizer.

  .. attribute:: authorizer_uri

     The URI of the lambda function to use for the custom authorizer.  This
     usually has the form
     ``arn:aws:apigateway:{region}:lambda:path/2015-03-31/functions/{lambda_arn}/invocations``.

  .. attribute:: ttl_seconds

     The number of seconds to cache the returned policy from a custom
     authorizer.

  .. attribute:: header

     The header where the auth token will be specified.


Built-in Authorizers
--------------------

These classes are used when defining built-in authorizers in Chalice.

.. class:: AuthRequest(auth_type, token, method_arn)

   An instance of this class is passed as the first argument
   to an authorizer defined via ``@app.authorizer()``.  You
   generally do not instantiate this class directly.

   .. attribute:: auth_type

      The type of authentication

   .. attribute:: token

      The authorization token.  This is usually the value of the
      ``Authorization`` header.

   .. attribute:: method_arn

      The ARN of the API gateway being authorized.

.. class:: AuthResponse(routes, principal_id, context=None)

   .. attribute:: routes

      A list of authorized routes.  Each element in the list
      can either by a string route such as `"/foo/bar"` or
      an instance of ``AuthRoute``.  If you specify the URL as
      a string, then all supported HTTP methods will be authorized.
      If you want to specify which HTTP methods are allowed, you
      can use ``AuthRoute``.  If you want to specify that all
      routes and HTTP methods are supported you can use the
      wildcard value of ``"*"``: ``AuthResponse(routes=['*'], ...)``

   .. attribute:: principal_id

      The principal id of the user.

   .. attribute:: context

      An optional dictionary of key value pairs.  This dictionary
      will be accessible in the ``app.current_request.context``
      in all subsequent authorized requests for this user.

.. class:: AuthRoute(path, methods)

   This class be used in the ``routes`` attribute of a
   :class:`AuthResponse` instance to get fine grained control
   over which HTTP methods are allowed for a given route.

   .. attribute:: path

      The allowed route specified as a string

   .. attribute:: methods

      A list of allowed HTTP methods.


APIGateway
==========

.. class:: APIGateway()

   This class is used to control
   how API Gateway interprets ``Content-Type`` headers in both requests and
   responses.

   There is a single instance of this class attached to each
   :class:`Chalice` object under the ``api`` attribute.

   .. attribute:: cors

      Global cors configuration. If a route-level cors configuration is not
      provided, or is ``None`` then this configuration will be used. By
      default it is set to ``False``. This can either be ``True``, ``False``,
      or an instance of the ``CORSConfig`` class. This makes it easy to enable
      CORS for your entire application by setting ``app.api.cors = True``.

      .. versionadded:: 1.12.1


   .. attribute:: default_binary_types

      The value of ``default_binary_types`` are the ``Content-Types`` that are
      considered binary by default. This value should not be changed, instead
      you should modify the ``binary_types`` list to change the behavior of a
      content type. Its value is: ``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``.


   .. attribute:: binary_types

      The value of ``binary_types`` controls how API Gateway interprets
      requests and responses as detailed below.

      If an incoming request has a ``Content-Type`` header value that is
      present in the ``binary_types`` list it will be assumed that its body is
      a sequence of raw bytes. You can access these bytes by accessing the
      ``app.current_request.raw_body`` property.

      If an outgoing response from ``Chalice`` has a header ``Content-Type``
      that matches one of the ``binary_types`` its body must be a ``bytes``
      type object. It is important to note that originating request must have
      the ``Accept`` header for the same type as the ``Content-Type`` on the
      response. Otherwise a ``400`` error will be returned.

      This value can be modified to change what types API Gateway treats as
      binary. The easiest way to do this is to simply append new types to
      the list.

      .. code-block:: python

          app.api.binary_types.append('application/my-binary-data')


      Keep in mind that there can only be a total of 25 binary types at a time
      and Chalice by default has a list of 16 types. It is recommended if you
      are going to make extensive use of binary types to reset the list to
      the exact set of content types you will be using. This can easily be
      done by reassigning the whole list.

      .. code-block:: python

          app.api.binary_types = [
              'application/octet-stream',
              'application/my-binary-data',
          ]


      **Implementation Note**: API Gateway and Lambda communicate through a
      JSON event which is encoded using ``UTF-8``. The raw bytes are
      temporarily encoded using base64 when being passed between API Gateway
      and Lambda. In the worst case this encoding can cause the binary body
      to be inflated up to ``4/3`` its original size. Lambda only accepts an
      event up to ``6mb``, which means even if your binary data was not quite
      at that limit, with the base64 encoding it may exceed that limit. This
      will manifest as a ``502`` Bad Gateway error.


WebsocketAPI
============

.. class:: WebsocketAPI

   This class is used to send messages to websocket clients connected to an API
   Gateway Websocket API.

   .. attribute:: session

      A boto3 Session that will be used to send websocket messages to
      clients. Any custom configuration can be set through a botocore
      ``session``. This **must** be manually set before websocket features can
      be used.

      .. code-block:: python

         import botocore
         from boto3.session import Session
         from chalice import Chalice

         app = Chalice('example')
         session = botocore.session.Session()
         session.set_config_variable('retries', {'max_attempts': 0})
         app.websocket_api.session = Session(botocore_session=session)

   .. method:: configure(domain_name, stage)

      Configure prepares the :class:`WebsocketAPI` to call the :meth:`send`
      method. Without first calling this method calls to :meth:`send` will fail
      with the message ``WebsocketAPI needs to be configured before sending
      messages.``. This is because a boto3 ``apigatewaymanagementapi`` client
      must be created from the :attr:`session` with a custom endpoint in order
      to properly communicate with our API Gateway WebsocketAPI. This method is
      called on your behalf before each of the websocket handlers:
      ``on_ws_connect``, ``on_ws_message``, ``on_ws_disconnect``. This ensures
      that the :meth:`send` method is available in each of those handlers.

.. _websocket-send:

   .. method:: send(connection_id, message)

      *requires* ``boto3>=1.9.91``

      Method to send a message to a client. The ``connection_id`` is the unique
      identifier of the socket to send the ``message`` to. The ``message`` must
      be a utf-8 string.

      If the socket is disconnected it raises a :class:`WebsocketDisconnectedError`
      error.

   .. method:: close(connection_id)

      *requires* ``boto3>=1.9.221``

      Method to close a WebSocket connection. The ``connection_id`` is the
      unique identifier of the socket to close.

      If the socket is already disconnected it raises a
      :class:`WebsocketDisconnectedError` error.

   .. method:: info(connection_id)

      *requires* ``boto3>=1.9.221``

      Method to get info about a WebSocket. The ``connection_id`` is the unique
      identifier of the socket to get info about.

      The following is an example of the format this method returns::

       {
           'ConnectedAt': datetime(2015, 1, 1),
           'Identity': {
               'SourceIp': 'string',
               'UserAgent': 'string'
           },
           'LastActiveAt': datetime(2015, 1, 1)
       }

      If the socket is disconnected it raises a :class:`WebsocketDisconnectedError`
      error.

.. class:: WebsocketDisconnectedError

   An exception raised when a message is sent to a websocket that has disconnected.

   .. attribute:: connection_id

      The unique identifier of the websocket that was disconnected.


CORS
====

.. class:: CORSConfig(allow_origin='*', allow_headers=None, expose_headers=None, max_age=None, allow_credentials=None)

  CORS configuration to attach to a route, or globally on ``app.api.cors``.

  .. code-block:: python

      from chalice import CORSConfig
      cors_config = CORSConfig(
          allow_origin='https://foo.example.com',
          allow_headers=['X-Special-Header'],
          max_age=600,
          expose_headers=['X-Special-Header'],
          allow_credentials=True
      )

      @app.route('/custom_cors', methods=['GET'], cors=cors_config)
      def supports_custom_cors():
          return {'cors': True}

  .. versionadded:: 0.8.1

  .. attribute:: allow_origin

     The value of the ``Access-Control-Allow-Origin`` to send in the response.
     Keep in mind that even though the ``Access-Control-Allow-Origin`` header
     can be set to a string that is a space separated list of origins, this
     behavior does not work on all clients that implement CORS. You should only
     supply a single origin to the ``CORSConfig`` object. If you need to supply
     multiple origins you will need to define a custom handler for it that
     accepts ``OPTIONS`` requests and matches the ``Origin`` header against a
     whitelist of origins.  If the match is successful then return just their
     ``Origin`` back to them in the ``Access-Control-Allow-Origin`` header.

  .. attribute:: allow_headers

     The list of additional allowed headers.  This list is added to list of
     built in allowed headers: ``Content-Type``, ``X-Amz-Date``,
     ``Authorization``, ``X-Api-Key``, ``X-Amz-Security-Token``.

  .. attribute:: expose_headers

     A list of values to return for the ``Access-Control-Expose-Headers``:

  .. attribute:: max_age

     The value for the ``Access-Control-Max-Age``

  .. attribute:: allow_credentials

     A boolean value that sets the value of
     ``Access-Control-Allow-Credentials``.


Event Sources
=============

.. versionadded:: 1.0.0b1

.. class:: Rate(value, unit)

  An instance of this class can be used as the ``expression`` value
  in the :meth:`Chalice.schedule` method:

  .. code-block:: python

     @app.schedule(Rate(5, unit=Rate.MINUTES))
     def handler(event):
         pass

  Examples:

  .. code-block:: python

      # Run every minute.
      Rate(1, unit=Rate.MINUTES)

      # Run every 2 hours.
      Rate(2, unit=Rate.HOURS)

  .. attribute:: value

     An integer value that presents the amount of time to wait
     between invocations of the scheduled event.

  .. attribute:: unit

     The unit of the provided ``value`` attribute.  This can be
     either ``Rate.MINUTES``, ``Rate.HOURS``, or ``Rate.DAYS``.

  .. attribute:: MINUTES, HOURS, DAYS

     These values should be used for the ``unit`` attribute.


.. class:: Cron(minutes, hours, day_of_month, month, day_of_week, year)

  An instance of this class can be used as the ``expression`` value
  in the :meth:`Chalice.schedule` method.

  .. code-block:: python

     @app.schedule(Cron(15, 10, '?', '*', '6L', '2002-2005'))
     def handler(event):
         pass

  It provides more capabilities than the :class:`Rate`
  class.  There are a few limits:

  * You can't specify ``day_of_month`` and ``day_of_week`` fields in
    the same Cron expression.  If you specify a value in one of the
    fields, you must use a ``?`` in the other.
  * Cron expressions that lead to rates faster than 1 minute are not
    supported.

  For more information, see the API
  `docs page <https://docs.aws.amazon.com/AmazonCloudWatch/latest/events/ScheduledEvents.html#CronExpressions>`__.

  Examples:

  .. code-block:: python

      # Run at 10:00am (UTC) every day.
      Cron(0, 10, '*', '*', '?', '*')

      # Run at 12:15pm (UTC) every day.
      Cron(15, 12, '*', '*', '?', '*')

      # Run at 06:00pm (UTC) every Monday through Friday.
      Cron(0, 18, '?', '*', 'MON-FRI', '*')

      # Run at 08:00am (UTC) every 1st day of the month.
      Cron(0, 8, 1, '*', '?', '*')

      # Run every 15 minutes.
      Cron('0/15', '*', '*', '*', '?', '*')

      # Run every 10 minutes Monday through Friday.
      Cron('0/10', '*', '?', '*', 'MON-FRI', '*')

      # Run every 5 minutes Monday through Friday between
      # 08:00am and 5:55pm (UTC).
      Cron('0/5', '8-17', '?', '*', 'MON-FRI', '*')


.. class:: CloudWatchEvent()

   This is the input argument for a scheduled or CloudWatch events.

   .. code-block:: python

      @app.schedule('rate(1 hour)')
      def every_hour(event: CloudWatchEvent):
          pass

   In the code example above, the ``event`` argument is of
   type ``CloudWatchEvent``, which will have the following
   attributes.

   .. attribute:: version

      By default, this is set to 0 (zero) in all events.

   .. attribute:: account

      The 12-digit number identifying an AWS account.

   .. attribute:: region

      Identifies the AWS region where the event originated.

   .. attribute:: detail

      For CloudWatch events this will be the event payload.
      For scheduled events, this will be an empty dictionary.

   .. attribute:: detail_type

      For scheduled events, this value will be ``"Scheduled Event"``.

   .. attribute:: source

      Identifies the service that sourced the event. All events sourced from
      within AWS will begin with "aws." Customer-generated events can have any
      value here as long as it doesn't begin with "aws." We recommend the use
      of java package-name style reverse domain-name strings.

      For scheduled events, this will be ``aws.events``.

   .. attribute:: time

      The event timestamp, which can be specified by the service originating
      the event. If the event spans a time interval, the service might choose
      to report the start time, so this value can be noticeably before the time
      the event is actually received.

   .. attribute:: event_id

      A unique value is generated for every event. This can be helpful in
      tracing events as they move through rules to targets, and are processed.

   .. attribute:: resources

      This JSON array contains ARNs that identify resources that are involved
      in the event. Inclusion of these ARNs is at the discretion of the
      service.

      For scheduled events, this will include the ARN of the CloudWatch
      rule that triggered this event.

   .. attribute:: context

      A `Lambda context object <https://docs.aws.amazon.com/lambda/latest/dg/python-context-object.html>`_
      that is passed to the handler by AWS Lambda. This is useful if you need
      the AWS request ID for tracing, or any other data in the context object.

   .. method:: to_dict()

      Return the original event dictionary provided
      from Lambda.  This is useful if you need direct
      access to the lambda event, for example if a
      new key is added to the lambda event that has not
      been mapped as an attribute to the ``CloudWatchEvent``
      object.  Example::

          {'account': '123457940291',
           'detail': {},
           'detail-type': 'Scheduled Event',
           'id': '12345678-b9f1-4667-9c5e-39f98e9a6113',
           'region': 'us-west-2',
           'resources': ['arn:aws:events:us-west-2:123457940291:rule/testevents-dev-every_minute'],
           'source': 'aws.events',
           'time': '2017-06-30T23:28:38Z',
           'version': '0'}


.. class:: S3Event()

   This is the input argument for an S3 event.

   .. code-block:: python

      @app.on_s3_event(bucket='mybucket')
      def event_handler(event: S3Event):
          app.log.info("Event received for bucket: %s, key %s",
                       event.bucket, event.key)

   In the code example above, the ``event`` argument is of
   type ``S3Event``, which will have the following
   attributes.

   .. attribute:: bucket

      The S3 bucket associated with the event.

   .. attribute:: key

      The S3 key name associated with the event.
      The original key name in the S3 event payload
      is URL encoded.  However, this ``key`` attribute automatically
      URL decodes the key name for you.  If you need
      access to the original URL encoded key name, you can
      access it through the ``to_dict()`` method.

   .. attribute:: context

      A `Lambda context object <https://docs.aws.amazon.com/lambda/latest/dg/python-context-object.html>`_
      that is passed to the handler by AWS Lambda. This is useful if you need
      the AWS request ID for tracing, or any other data in the context object.

   .. method:: to_dict()

      Return the original event dictionary provided
      from Lambda.  This is useful if you need direct
      access to the lambda event, for example if a
      new key is added to the lambda event that has not
      been mapped as an attribute to the ``S3Event``
      object.  Note that this event is not modified in any way.
      This means that the key name of the S3 object is URL
      encoded, which is the way that S3 sends this value
      to Lambda.


.. class:: SNSEvent()

   This is the input argument for an SNS event handler.

   .. code-block:: python

      @app.on_sns_message(topic='mytopic')
      def event_handler(event: SNSEvent):
          app.log.info("Message received with subject: %s, message: %s",
                       event.subject, event.message)

   In the code example above, the ``event`` argument is of
   type ``SNSEvent``, which will have the following
   attributes.

   .. attribute:: subject

      The subject of the SNS message that was published.

   .. attribute:: message

      The string value of the SNS message that was published.

   .. attribute:: context

      A `Lambda context object <https://docs.aws.amazon.com/lambda/latest/dg/python-context-object.html>`_
      that is passed to the handler by AWS Lambda. This is useful if you need
      the AWS request ID for tracing, or any other data in the context object.

   .. method:: to_dict()

      Return the original event dictionary provided
      from Lambda.  This is useful if you need direct
      access to the lambda event, for example if a
      new key is added to the lambda event that has not
      been mapped as an attribute to the ``SNSEvent``
      object.


.. class:: SQSEvent()

   This is the input argument for an SQS event handler.

   .. code-block:: python

      @app.on_sqs_message(queue='myqueue')
      def event_handler(event: SQSEvent):
          app.log.info("Event: %s", event.to_dict())

   In the code example above, the ``event`` argument is of
   type ``SQSEvent``.  An ``SQSEvent`` can have multiple
   sqs messages associated with it.  To access the multiple
   messages, you can iterate over the ``SQSEvent``.

   .. method:: __iter__()

      Iterate over individual SQS messages associated with
      the event.  Each element in the iterable is of type
      :class:`SQSRecord`.

   .. attribute:: context

      A `Lambda context object <https://docs.aws.amazon.com/lambda/latest/dg/python-context-object.html>`_
      that is passed to the handler by AWS Lambda. This is useful if you need
      the AWS request ID for tracing, or any other data in the context object.

   .. method:: to_dict()

      Return the original event dictionary provided
      from Lambda.  This is useful if you need direct
      access to the lambda event, for example if a
      new key is added to the lambda event that has not
      been mapped as an attribute to the ``SQSEvent``
      object.

.. class:: SQSRecord()

   Represents a single SQS record within an :class:`SQSEvent`.

   .. attribute:: body

      The body of the SQS message.

   .. attribute:: receipt_handle

      The receipt handle associated with the message.  This is useful
      if you need to manually delete an SQS message to account for
      partial failures.

   .. attribute:: context

      A `Lambda context object <https://docs.aws.amazon.com/lambda/latest/dg/python-context-object.html>`_
      that is passed to the handler by AWS Lambda.

   .. method:: to_dict()

      Return the original dictionary associated with the given
      message. This is useful if you need direct
      access to the lambda event.


.. class:: KinesisEvent()

   This is the input argument for a Kinesis data stream event handler.

   .. code-block:: python

      @app.on_kinesis_record(stream='mystream')
      def event_handler(event: KinesisEvent):
          app.log.info("Event: %s", event.to_dict())

   In the code example above, the ``event`` argument is of
   type ``KinesisEvent``.  A ``KinesisEvent`` can have multiple
   messages associated with it.  To access the multiple
   messages, you can iterate over the ``KinesisEvent``.

   .. method:: __iter__()

      Iterate over individual Kinesis records associated with
      the event.  Each element in the iterable is of type
      :class:`KinesisRecord`.

   .. attribute:: context

      A `Lambda context object <https://docs.aws.amazon.com/lambda/latest/dg/python-context-object.html>`_
      that is passed to the handler by AWS Lambda. This is useful if you need
      the AWS request ID for tracing, or any other data in the context object.

   .. method:: to_dict()

      Return the original event dictionary provided
      from Lambda.  This is useful if you need direct
      access to the lambda event, for example if a
      new key is added to the lambda event that has not
      been mapped as an attribute to the ``SQSEvent``
      object.

.. class:: KinesisRecord()

   Represents a single Kinesis record within a :class:`KinesisEvent`.

   .. attribute:: data

      The payload data for the Kinesis record.  This data is automatically
      base64 decoded for you and will be a ``bytes`` type.

   .. attribute:: sequence_number

      The unique identifier of the record within its shard.

   .. attribute:: partition_key

      Identifies which shard in the stream the data record is assigned to.

   .. attribute:: schema_version

      Schema version for the record.

   .. attribute:: timestamp

      The approximate time that the record was inserted into the stream.  This
      is automatically converted to a ``datetime.datetime`` object.

   .. attribute:: context

      A `Lambda context object <https://docs.aws.amazon.com/lambda/latest/dg/python-context-object.html>`_
      that is passed to the handler by AWS Lambda.

   .. method:: to_dict()

      Return the original dictionary associated with the given
      message. This is useful if you need direct
      access to the lambda event.


.. class:: DynamoDBEvent()

   This is the input argument for a DynamoDB stream event handler.

   .. code-block:: python

      @app.on_dynamodb_record(stream_arn='arn:aws:us-west-2:.../stream')
      def event_handler(event: DynamoDBEvent):
          app.log.info("Event: %s", event.to_dict())
          for record in event:
              app.log.info(record.to_dict())

   In the code example above, the ``event`` argument is of
   type ``DynamoDBEvent``.  A ``DynamoDBEvent`` can have multiple
   messages associated with it.  To access the multiple
   messages, you can iterate over the ``DynamoDBEvent``.

   .. method:: __iter__()

      Iterate over individual DynamoDB records associated with
      the event.  Each element in the iterable is of type
      :class:`DynamoDBRecord`.

   .. attribute:: context

      A `Lambda context object <https://docs.aws.amazon.com/lambda/latest/dg/python-context-object.html>`_
      that is passed to the handler by AWS Lambda. This is useful if you need
      the AWS request ID for tracing, or any other data in the context object.

   .. method:: to_dict()

      Return the original event dictionary provided
      from Lambda.  This is useful if you need direct
      access to the lambda event, for example if a
      new key is added to the lambda event that has not
      been mapped as an attribute to the ``SQSEvent``
      object.

.. class:: DynamoDBRecord()

   Represents a single DynamoDB record within a :class:`DynamoDBEvent`.

   .. attribute:: timestamp

      The approximate time that the record was the stream record was created.
      This is automatically converted to a ``datetime.datetime`` object.

   .. attribute:: keys

      The primary key attribute(s) for the DynamoDB item that was modified.

   .. attribute:: new_image

      The item in the DynamoDB table as it appeared after it was modified.

   .. attribute:: old_image

      The item in the DynamoDB table as it appeared before it was modified.

   .. attribute:: sequence_number

      The sequence number of the stream record.

   .. attribute:: size_bytes

      The size of the stream record, in bytes.

   .. attribute:: stream_view_type

      The type of data from the modified DynamoDB item.

   .. attribute:: aws_region

      The region associated with the event.

   .. attribute:: event_id

      A unique identifier for the event.

   .. attribute:: event_name

      The type of data modification that was performed on the DynamoDB table.
      This can be: ``INSERT``, ``MODIFY``, or ``DELETE``.

   .. attribute:: event_source_arn

      The ARN of the DynamoDB stream.

   .. attribute:: table_name

      The name of the DynamoDB table associated with the stream.  This value is
      computed from the ``event_source_arn`` parameter and will be an empty string
      if Chalice is unable to parse the table name from the event source ARN.

   .. attribute:: context

      A `Lambda context object <https://docs.aws.amazon.com/lambda/latest/dg/python-context-object.html>`_
      that is passed to the handler by AWS Lambda.

   .. method:: to_dict()

      Return the original dictionary associated with the given
      message. This is useful if you need direct
      access to the lambda event.


.. class:: LambdaFunctionEvent()

   This is the input argument of middleware registered to a
   pure Lambda function (``@app.lambda_function()``).

   .. attribute:: event

      The original input event dictionary.

   .. attribute:: context

      A `Lambda context object <https://docs.aws.amazon.com/lambda/latest/dg/python-context-object.html>`_
      that is passed to the handler by AWS Lambda.

Blueprints
==========

.. class:: Blueprint(import_name)

  An object used for grouping related handlers together.
  This is primarily used as a mechanism for organizing your lambda
  handlers.  Any decorator methods defined in the :class:`Chalice`
  object are also defined on a ``Blueprint`` object.  You can register
  a blueprint to a Chalice app using the :meth:`Chalice.register_blueprint`
  method.

  The ``import_name`` is the module in which the Blueprint is defined.
  It is used to construct the appropriate handler string when creating
  the Lambda functions associated with a Blueprint.  This is typically
  the `__name__` attribute:``mybp = Blueprint(__name__)``.

  See :doc:`topics/blueprints` for more information.

  .. code-block:: python

      # In ./app.py

      from chalice import Chalice
      from chalicelib import myblueprint

      app = Chalice(app_name='blueprints')
      app.register_blueprint(myblueprint)

      # In chalicelib/myblueprint.py

      from chalice import Blueprint

      myblueprint = Blueprint(__name__)

      @myblueprint.route('/')
      def index():
          return {'hello': 'world'}


Websockets
==========
.. _websocket-api:

.. class:: WebsocketEvent()

  Event object event that is passed as the sole argument to any handler
  function decorated with one of the three websocket related handlers:
  ``on_ws_connect``, ``on_ws_disconnect``, ``on_ws_message``.

  .. attribute:: domain_name

     The domain name of the endpoint for the API Gateway Websocket API.

  .. attribute:: stage

     The API Gateway stage of the Websocket API.

  .. attribute:: connection_id

     A handle that uniquely identifies a connection with API Gateway.

  .. attribute:: body

     The message body received. This is only populated on the ``on_ws_message``
     otherwise it will be set to ``None``.

  .. attribute:: json_body

     The parsed JSON body (``json.loads(body)``) of the message. If the body is
     not JSON parsable then using this attribute will raise a ``ValueError``.

  See :doc:`topics/websockets` for more information.


.. _testing-api:

Testing
=======

.. class:: Client(app, stage_name='dev', project_dir='.')

  A test client used to write tests for Chalice apps.  It allows you to
  test Lambda function invocation as well as REST APIs.  Depending
  on what you want to test, you'll access the various attributes
  of this class.  You can use this class as a context manager.
  When entering the context manager, any environment variables
  specified for your function will be set.  The original environment
  variables are put back when the block is exited:

  .. code-block:: python

     from chalice.test import Client

     with Client(app) as client:
         result = client.http.post("/my-data")

  See the :doc:`topics/testing` documentation for more details on testing
  your Chalice app.

  .. attribute:: lambda_

     Returns the Lambda test client :class:`TestLambdaClient`.

  .. attribute:: http

     Returns the test client for REST APIs :class:`TestHTTPClient`.

  .. attribute:: events

     Returns the test client for generating Lambda events
     :class:`TestEventsClient`.

.. class:: TestLambdaClient(import_name)

   Test client for invoking Lambda functions.  This class should not be
   instantiated directly, and instead should be accessed via the
   ``Client.lambda_`` attribute:

   .. code-block:: python

      @app.lambda_function()
      def myfunction(event, context):
          return {"hello": "world"}

      with Client(app) as client:
          result = client.lambda_.invoke("myfunction")
          assert result.payload == {"hello": "world"}

   .. method:: invoke(function_name, payload=None)

      Invoke a Lambda function by name.  The name should match
      the resource name of the function.  This is typically the
      name of the python function unless an explicit ``name=``
      kwarg is provided when registering the function.

      Returns an :class:`InvokeResponse` instance.


.. class:: TestHTTPClient(import_name)

   Test client for REST APIs.  This class should not be
   instantiated directly, and instead should be accessed via the
   ``Client.http`` attribute:

   .. code-block:: python

      with Client(app) as client:
          response = client.http.get("/my-route")

   .. method:: request(method, path, headers=None, body=b'')

      Makes a test HTTP request to your REST API.  Returns
      an :class:`HTTPResponse`.  You can also use the methods below
      to make a request with a specific HTTP method instead of using
      this method directly, e.g. ``client.http.get("/foo")`` instead
      of ``client.http.request("GET", "/foo")``.

   .. method:: get(path, \*\*kwargs)

      Makes an HTTP GET request.

   .. method:: post(path, \*\*kwargs)

      Makes an HTTP POST request.

   .. method:: put(path, \*\*kwargs)

      Makes an HTTP PUT request.

   .. method:: patch(path, \*\*kwargs)

      Makes an HTTP PATCH request.

   .. method:: options(path, \*\*kwargs)

      Makes an HTTP OPTIONS request.

   .. method:: delete(path, \*\*kwargs)

      Makes an HTTP DELETE request.

   .. method:: head(path, \*\*kwargs)

      Makes an HTTP HEAD request.


.. class:: TestEventsClient(import_name)

   Test client for generating Lambda events.  This class should not be
   instantiated directly, and instead should be accessed via the
   ``Client.events`` attribute:

   .. code-block:: python

      with Client(app) as client:
          result = client.lambda_.invoke(
              "my_sns_handler",
              client.events.generate_sns_event("Hello world")
          )

   .. method:: generate_sns_event(message, subject='')

      Generates a sample SNS event.

   .. method:: generate_s3_event(bucket, key, event_name='ObjectCreated:Put')

      Generates a sample S3 event.

   .. method:: generate_sqs_event(message_bodies, queue_name='queue-name')

      Generates a sample SQS event.

   .. method:: generate_cw_event(source, detail_type, detail, resources, region='us-west-2')

      Generates a sample CloudWatch event.

   .. method:: generate_kinesis_event(message_bodies, stream_name='stream-name')

      Generates a Kinesis event.


.. class:: HTTPResponse()

  .. attribute:: body

     The body of the HTTP response, in ``bytes``.

  .. attribute:: headers

     A dictionary of HTTP headers in the response.

  .. attribute:: status_code

     The status code of the HTTP response.

.. class:: InvokeResponse(payload)

  .. attribute:: payload

     The response payload of Lambda invocation.


.. _cdk-api:

AWS CDK
=======

The Chalice CDK construct is available in the ``chalice.cdk`` namespace.
For more details on using the AWS CDK with Chalice, see :doc:`tutorials/cdk`.

.. code-block:: python

   from chalice import cdk

.. class:: cdk.Chalice(scope, id, source_dir, stage_config=None, preserve_logical_ids=True, \*\*kwargs)

   A test client used to write tests for Chalice apps.  It allows you to

   :param scope: The CDK scope that the construct is created within.
   :param str id: The identifier for the construct.  Must be unique within the
       scope in which it's created.
   :param str source_dir: Path to Chalice application source code.
   :param dict stage_config: Chalice stage configuration.
       The configuration object should have the same structure as Chalice
       JSON stage configuration.
   :param bool preserve_logical_ids: Whether the resources should have
       the same logical IDs in the resulting CDK template as they did in
       the original CloudFormation template file. If you're vending a
       Construct using cdk-chalice, make sure to pass this as ``False``.
       Note: regardless of whether this option is true or false, the
       :attr:`sam_template`'s ``get_resource`` and related methods always
       uses the original logical ID of the resource/element, as specified
       in the template file.
   :raises `ChaliceError`: Error packaging the Chalice application.

   .. code-block:: python

      chalice = Chalice(
          self, 'ChaliceApp', source_dir='../runtime',
          stage_config={
              'environment_variables': {
                  'MY_ENV_VAR': 'FOO'
              }
          }
      )


   .. method:: get_resource(resource_name)

      Returns a low-level CfnResource from the resources in a Chalice app with
      the given resource name.  The resource name corresponds to the logical ID
      of the underlying resource in the SAM template.  Any modifications
      performed on that resource will be reflected in the resulting CDK
      template.

      :param str resource_name: The logical ID of the resource in the SAM template.
      :rtype: aws_cdk.cdk.CfnResource

   .. method:: get_role(resource_name)

      Returns an ``IRole`` for an underlying SAM template resource.  This is useful
      if you want to grant additional permissions to an IAM role constructed by Chalice.

      .. code-block:: python

        dynamodb_table = dynamodb.Table(...)
        chalice = Chalice(scope, 'ChaliceApp', ....)
        dynamodb_table.grant_read_write_data(chalice.get_role('DefaultRole'))

      :param str resource_name: The logical ID of the resource in the SAM template.
      :rtype: aws_cdk.aws_iam.IRole

   .. method:: get_function(resource_name)

      Returns an ``IFunction`` for an underlying SAM template resource.

      :param str resource_name: The logical ID of the resource in the SAM template.
      :rtype: aws_cdk.aws_lambda.IFunction

   .. method:: add_environment_variable(key, value, function_name)

      Convenience function to add environment variables to a single Lambda
      function constructed by Chalice.  You can also add environment variables
      to Lambda functions using the ``stage_config`` parameter when creating the
      ``Chalice()`` construct.

      :param str key: The environment variable key name.
      :param str value: The value of the environment variable.
      :param str function_name: The logical ID of the Lambda function resource.

---

### Source Faq (`docs/source/faq.rst`)

Frequently Asked Questions
==========================

**Q: What is AWS Chalice?**

AWS Chalice is a framework for writing serverless apps in python.
It consists of a CLI, a declarative python API for connecting
events to AWS Lambda functions, and a runtime component that
provides APIs accessible to your Lambda functions.

**Q: Why should I use AWS Chalice?**

Chalice is designed for a seamless getting started experience
that can get you up and running quickly.  It handles all the
boilerplate and low level details of creating a serverless
application, allowing you to focus on the business logic
of your application.  It also provides deep integration with
various AWS services allowing you to take advantage of the
features available in each service.

**Q: How does Chalice compare to AWS SAM and the AWS CDK?**

Chalice is designed to work together with AWS SAM.
SAM focus on provisioning the resources needed
for your application, and not necessarily on the application code
itself.  Chalice provides a set of APIs to help you write your
application code, including a routing layer for REST and Websocket
APIs, and decorators to connect various AWS event sources to
Lambda functions.  It then can integrate with AWS SAM by offloading
the deployment to AWS CloudFormation.

**Q: How does Chalice compare to other similar frameworks?**

The biggest difference between Chalice and other frameworks is that Chalice
is focused on using a familiar, decorator-based API to write serverless
python applications that run on AWS Lambda.  Its goal is to make writing and
deploying these types of applications as simple as possible specifically for
Python developers.  It was designed from the ground up to run in a
serverless environment.

To achieve this goal, it has to make certain tradeoffs.  Chalice makes
assumptions about how applications will be deployed, and it has restrictions on
how an application can be structured.  The feature set is purposefully small.

---

### Source Index (`docs/source/index.rst`)

Documentation
=============


Getting Started
---------------

.. toctree::
   :maxdepth: 2

   quickstart


Tutorials
---------

.. toctree::
   :maxdepth: 2

   tutorials/index


Topics
------

.. toctree::
   :maxdepth: 2

   topics/index


API Reference
-------------

.. toctree::
   :maxdepth: 2

   api


Sample Apps
-----------

.. toctree::
   :maxdepth: 2

   samples/index


Upgrade Notes
-------------

.. toctree::
   :maxdepth: 2

   upgrading


FAQ
---

.. toctree::
   :maxdepth: 1

   faq


Indices and tables
==================

* :ref:`genindex`
* :ref:`search`

---

### Source Quickstart (`docs/source/quickstart.rst`)

Quickstart
==========

.. include:: ../../README.rst
  :start-after: quick-start-begin
  :end-before: quick-start-end

Cleaning Up
-----------

If you're done experimenting with Chalice and you'd like to cleanup, you can
use the ``chalice delete`` command, and Chalice will delete all the resources
it created when running the ``chalice deploy`` command.

::

    $ chalice delete
    Deleting Rest API: abcd4kwyl4
    Deleting function arn:aws:lambda:region:123456789012:function:helloworld-dev
    Deleting IAM Role helloworld-dev


Next Steps
----------

At this point, there are several tutorials you can follow based on what
you're interested in:

* :doc:`Creating REST APIs <tutorials/basicrestapi>` - Dive into more detail on
  how to create a REST API using Chalice.  We'll explore URL parameters, error
  messages, content types, CORs, and more.
* :doc:`Event Sources <tutorials/events>` - In this tutorial, we'll focus on
  difference event sources you can connect with a Lambda function other than a
  REST API with Amazon API Gateway.
* :doc:`Websockets <tutorials/wschat>` - In this tutorial, we'll show you
  how to create a websocket API and create a sample chat application.

You can also jump into specific :doc:`topic guides <topics/index>`.  These are
more detailed than the tutorials, and provide more reference style
documentation on specific features of Chalice.

And finally, you can look at the :doc:`API Reference <api>` for detailed API
documentation for Chalice.  This is useful if you know exactly what feature
you're using but need to lookup a specific parameter name or return value.

---

### Source Upgrading (`docs/source/upgrading.rst`)

Upgrade Notes
=============

This document provides additional documentation
on upgrading your version of chalice.  If you're just
interested in the high level changes, see the
`CHANGELOG.md <https://github.com/aws/chalice/blob/master/CHANGELOG.md>`__)
file.

.. _v1-2-0:

1.2.0
-----

This release features a rewrite of the Chalice deployer
(`#604 <https://github.com/aws/chalice/issues/604>`__).
This is a backwards compatible change, and should not have any
noticeable changes with deployments with the exception of
fixing deployer bugs (e.g. https://github.com/aws/chalice/issues/604).
This code path affects the ``chalice deploy``, ``chalice delete``, and
``chalice package`` commands.

While this release is backwards compatible, you will notice several
changes when you upgrade to version 1.2.0.

The output of ``chalice deploy`` has changed in order to give
more details about the resources it creates along with a more detailed
summary at the end::

    $ chalice deploy
    Creating deployment package.
    Creating IAM role: myapp-dev
    Creating lambda function: myapp-dev-foo
    Creating lambda function: myapp-dev
    Creating Rest API
    Resources deployed:
      - Lambda ARN: arn:aws:lambda:us-west-2:12345:function:myapp-dev-foo
      - Lambda ARN: arn:aws:lambda:us-west-2:12345:function:myapp-dev
      - Rest API URL: https://abcd.execute-api.us-west-2.amazonaws.com/api/

Also, the files used to store deployed values has changed.  These files are
used internally by the ``chalice deploy/delete`` commands and you typically
do not interact with these files directly.  It's mentioned here in case
you notice new files in your ``.chalice`` directory.  Note that these files
are *not* part of the public interface of Chalice and are documented here
for completeness and to help with debugging issues.

In versions < 1.2.0, the value of deployed resources was stored in
``.chalice/deployed.json`` and looked like this::

  {
    "dev": {
      "region": "us-west-2",
      "api_handler_name": "demoauth4-dev",
      "api_handler_arn": "arn:aws:lambda:us-west-2:123:function:myapp-dev",
      "rest_api_id": "abcd",
      "lambda_functions": {
        "myapp-dev-foo": {
          "type": "pure_lambda",
          "arn": "arn:aws:lambda:us-west-2:123:function:myapp-dev-foo"
        }
      },
      "chalice_version": "1.1.1",
      "api_gateway_stage": "api",
      "backend": "api"
    },
    "prod": {...}
  }


In version 1.2.0, the deployed resources are split into multiple files, one
file per chalice stage.  These files are in the
``.chalice/deployed/<stage.json>``, so if you had a dev and a prod chalice
stage you'd have ``.chalice/deployed/dev.json`` and
``.chalice/deployed/prod.json``.  The schema has also changed and looks
like this::


  $ cat .chalice/deployed/dev.json
  {
    "schema_version": "2.0",
    "resources": [
      {
        "role_name": "myapp-dev",
        "role_arn": "arn:aws:iam::123:role/myapp-dev",
        "name": "default-role",
        "resource_type": "iam_role"
      },
      {
        "lambda_arn": "arn:aws:lambda:us-west-2:123:function:myapp-dev-foo",
        "name": "foo",
        "resource_type": "lambda_function"
      },
      {
        "lambda_arn": "arn:aws:lambda:us-west-2:123:function:myapp-dev",
        "name": "api_handler",
        "resource_type": "lambda_function"
      },
      {
        "name": "rest_api",
        "rest_api_id": "abcd",
        "rest_api_url": "https://abcd.execute-api.us-west-2.amazonaws.com/api",
        "resource_type": "rest_api"
      }
    ],
    "backend": "api"
  }

When you run ``chalice deploy`` for the first time after upgrading to version
1.2.0, chalice will automatically converted ``.chalice/deployed.json`` over to
the format as you deploy a given stage.

.. warning::

  Once you upgrade to 1.2.0, chalice will only update the new
  ``.chalice/deployed/<stage>.json``.  This means you cannot downgrade
  to earlier versions of chalice unless you manually update
  ``.chalice/deployed.json`` as well.


The ``chalice package`` command has also been updated to use the
deployer.  This results in several changes compared to the previous
version:

* Pure lambdas are supported
* Scheduled events are supported
* Parity between the behavior of ``chalice deploy`` and ``chalice package``

As part of this change, the CFN resource names have been updated
to use ``CamelCase`` names.  Previously, chalice converted your
python function names to CFN resource names by removing all
non alphanumeric characters and appending an md5 checksum,
e.g ``my_function -> myfunction3bfc``.  With this new packager
update, the resource name would be converted as
``my_function -> MyFunction``.  Note, the ``Outputs`` section
renames unchanged in order to preserve backwards compatibility.
In order to fix parity issues with ``chalice deploy`` and
``chalice package``, we now explicitly create an IAM role
resource as part of the default configuration.


.. _v1-0-0b2:

1.0.0b2
-------

The url parameter names and the function argument names must match.
Previously, the routing code would use positional args ``handler(*args)``
to invoke a view function.  In this version, kwargs are now used instead:
``handler(**view_args)``.  For example, this code will no longer work:

.. code-block:: python

    @app.route('/{a}/{b}')
    def myview(first, second)
        return {}


The example above must be updated to:


.. code-block:: python

    @app.route('/{a}/{b}')
    def myview(a, b)
        return {}

Now that functions are invoked with kwargs, the order doesn't matter.  You may
also write the above view function as:


.. code-block:: python

    @app.route('/{a}/{b}')
    def myview(b, a)
        return {}


This was done to have consistent behavior with other web frameworks such as
Flask.

.. _v1-0-0b1:

1.0.0b1
-------

The ``Chalice.define_authorizer`` method has been removed.  This has been
deprecated since v0.8.1.  See :doc:`topics/authorizers` for updated
information on configuring authorizers in Chalice as well as the
original deprecation notice in the :ref:`v0-8-1` upgrade notes.

The optional deprecated positional parameter in the ``chalice deploy`` command
for specifying the API Gateway stage has been removed.  If you want to
specify the API Gateway stage, you can use the ``--api-gateway-stage``
option in the ``chalice deploy`` command::

    # Deprecated and removed in 1.0.0b1
    $ chalice deploy prod

    # Equivalent and updated way to specify an API Gateway stage:
    $ chalice deploy --api-gateway-stage prod


.. _v0-9-0:

0.9.0
-----

The 0.9.0 release changed the type of ``app.current_request.raw_body`` to
always be of type ``bytes()``.  This only affects users that were using
python3.  Previously you would get a type ``str()``, but with the introduction
of `binary content type support
<https://github.com/aws/chalice/issues/348>`__, the ``raw_body`` attribute
was made to consistently be of type ``bytes()``.


.. _v0-8-1:

0.8.1
-----

The 0.8.1 changed the preferred way of specifying authorizers for view
functions.  You now specify either an instance of
``chalice.CognitoUserPoolAuthorizer`` or ``chalice.CustomAuthorizer``
to an ``@app.route()`` function using the ``authorizer`` argument.

Deprecated:

.. code-block:: python

    @app.route('/user-pools', methods=['GET'], authorizer_name='MyPool')
    def authenticated():
        return {"secure": True}

    app.define_authorizer(
        name='MyPool',
        header='Authorization',
        auth_type='cognito_user_pools',
        provider_arns=['arn:aws:cognito:...:userpool/name']
    )

Equivalent, and preferred way

.. code-block:: python

    from chalice import CognitoUserPoolAuthorizer

    authorizer = CognitoUserPoolAuthorizer(
        'MyPool', header='Authorization',
        provider_arns=['arn:aws:cognito:...:userpool/name'])

    @app.route('/user-pools', methods=['GET'], authorizer=authorizer)
    def authenticated():
        return {"secure": True}


The ``define_authorizer`` is still available, but is now deprecated and will
be removed in future versions of chalice.  You can also use the new
``authorizer`` argument to provider a ``CustomAuthorizer``:


.. code-block:: python

    from chalice import CustomAuthorizer

    authorizer = CustomAuthorizer(
        'MyCustomAuth', header='Authorization',
        authorizer_uri=('arn:aws:apigateway:region:lambda:path/2015-03-01'
                        '/functions/arn:aws:lambda:region:account-id:'
                        'function:FunctionName/invocations'))

    @app.route('/custom-auth', methods=['GET'], authorizer=authorizer)
    def authenticated():
        return {"secure": True}


.. _v0-7-0:

0.7.0
-----

The 0.7.0 release adds several major features to chalice.  While the majority
of these features are introduced in a backwards compatible way, there are a few
backwards incompatible changes that were made in order to support these new
major features.

Separate Stages
~~~~~~~~~~~~~~~

Prior to this version, chalice had a notion of a "stage" that corresponded to
an API gateway stage.  You can create and deploy a new API gateway stage by
running ``chalice deploy <stage-name>``.  In 0.7.0, stage support was been
reworked such that a chalice stage is a completely separate set of AWS
resources.  This means that if you have two chalice stages, say ``dev`` and
``prod``, then you will have two separate sets of AWS resources, one set per
stage:

* Two API Gateway Rest APIs
* Two separate Lambda functions
* Two separate IAM roles

The :doc:`topics/stages` doc has more details on the new chalice stages
feature.  This section highlights the key differences between the old stage
behavior and the new chalice stage functionality in 0.7.0.  In order to ease
transition to this new model, the following changes were made:

* A new ``--stage`` argument was added to the ``deploy``, ``logs``, ``url``,
  ``generate-sdk``, and ``package`` commands.  If this value is specified
  and the stage does not exist, a new chalice stage with that name will
  be created for you.
* The existing form ``chalice deploy <stage-name>`` has been deprecated.
  The command will still work in version 0.7.0, but a deprecation warning
  will be printed to stderr.
* If you want the pre-existing behavior of creating a new API gateway stage
  (while using the same Lambda function), you can use the
  ``--api-gateway-stage`` argument.  This is the replacement for the
  deprecated form ``chalice deploy <stage-name>``.
* The default stage if no ``--stage`` option is provided is ``dev``.  By
  defaulting to a ``dev`` stage, the pre-existing behavior of not
  specifying a stage name, e.g ``chalice deploy``, ``chalice url``, etc.
  will still work exactly the same.
* A new ``stages`` key is supported in the ``.chalice/config.json``.  This
  allows you to specify configuration specific to a chalice stage.
  See the :doc:`topics/configfile` doc for more information about stage
  specific configuration.
* Setting ``autogen_policy`` to false will result in chalice looking
  for a IAM policy file named ``.chalice/policy-<stage-name>.json``.
  Previously it would look for a file named ``.chalice/policy.json``.
  You can also explicitly set this value to
  In order to ease transition, chalice will check for a
  ``.chalice/policy.json`` file when deploying to the ``dev`` stage.
  Support for ``.chalice/policy.json`` will be removed in future
  versions of chalice and users are encouraged to switch to the
  stage specific ``.chalice/policy-<stage-name>.json`` files.


See the :doc:`topics/stages` doc for more details on the new chalice stages
feature.

**Note, the AWS resource names it creates now have the form
``<app-name>-<stage-name>``, e.g. ``myapp-dev``, ``myapp-prod``.**

We recommend using the new stage specific resource names.  However, If you
would like to use the existing resource names for a specific stage, you can
create a ``.chalice/deployed.json`` file that specifies the existing values::

  {
    "dev": {
      "backend": "api",
      "api_handler_arn": "lambda-function-arn",
      "api_handler_name": "lambda-function-name",
      "rest_api_id": "your-rest-api-id",
      "api_gateway_stage": "dev",
      "region": "your region (e.g us-west-2)",
      "chalice_version": "0.7.0",
    }
  }


This file is discussed in the next section.

Deployed Values
~~~~~~~~~~~~~~~

In version 0.7.0, the way deployed values are stored and retrieved
has changed.  In prior versions, only the ``lambda_arn`` was saved,
and its value was written to the ``.chalice/config.json`` file.
Any of other deployed values that were needed (for example the
API Gateway rest API id) was dynamically queried by assuming the
resource names matches the app name.  In this version of chalice,
a separate ``.chalice/deployed.json`` file is written on every
deployment which contains all the resources that have been created.
While this should be a transparent change, you may noticed
issues if you run commands such as ``chalice url`` and ``chalice logs``
without first deploying.  To fix this issue, run ``chalice deploy``
and version 0.7.0 of chalice so a ``.chalice/deployed.json`` will
be created for you.


Authorizer Changes
~~~~~~~~~~~~~~~~~~

**The ``authorizer_id`` and ``authorization_type`` args are
no longer supported in ``@app.route(...)`` calls.**


They have been replaced with an ``authorizer_name`` parameter and an
``app.define_authorizer`` method.

This version changed the internals of how an API gateway REST API is created.
Prior to 0.7.0, the AWS SDK for Python was used to make the appropriate service
API calls to API gateway include ``create_rest_api`` and ``put_method /
put_method_response`` for each route.  In version 0.7.0, this internal
mechanism was changed to instead generate a swagger document.  The rest api is
then created or updated by calling ``import_rest_api`` or ``put_rest_api`` and
providing the swagger document.  This simplifies the internals and also unifies
the code base for the newly added ``chalice package`` command (which uses a
swagger document internally).  One consequence of this change is that the
entire REST API must be defined in the swagger document.  With the previous
``authorizer_id`` parameter, you would create/deploy a rest api, create your
authorizer, and then provide that ``authorizer_id`` in your ``@app.route``
calls.  Now they must be defined all at once in the ``app.py`` file:


.. code-block:: python

    app = chalice.Chalice(app_name='demo')

    @app.route('/auth-required', authorizer_name='MyUserPool')
    def foo():
        return {}

    app.define_authorizer(
        name='MyUserPool',
        header='Authorization',
        auth_type='cognito_user_pools',
        provider_arns=['arn:aws:cognito:...:userpool/name']
    )


.. _v0-6-0:

0.6.0
-----

This version changed how the internals of how API gateway resources are created
by chalice.  The integration type changed from ``AWS`` to ``AWS_PROXY``.  This
was to enable additional functionality, notable to allows users to provide
non-JSON HTTP responses and inject arbitrary headers to the HTTP responses.
While this change to the internals is primarily internal, there are several
user-visible changes.


* Uncaught exceptions with ``app.debug = False`` (the default value)
  will result in a more generic ``InternalServerError`` error.  The
  previous behavior was to return a ``ChaliceViewError``.
* When you enabled debug mode via ``app.debug = True``, the HTTP
  response will contain the python stack trace as the entire request
  body.  This is to improve the readability of stack traces.
  For example::

    $ http https://endpoint/dev/
    HTTP/1.1 500 Internal Server Error
    Content-Length: 358
    Content-Type: text/plain

    Traceback (most recent call last):
      File "/var/task/chalice/app.py", line 286, in __call__
        response = view_function(*function_args)
      File "/var/task/app.py", line 12, in index
        return a()
      File "/var/task/app.py", line 16, in a
        return b()
      File "/var/task/app.py", line 19, in b
        raise ValueError("Hello, error!")
    ValueError: Hello, error!

* Content type validation now has error responses that match the same error
  response format used for other chalice built in responses.  Chalice was
  previously relying on API gateway to perform the content type validation.
  As a result of the ``AWS_PROXY`` work, this logic has moved into the chalice
  handler and now has a consistent error response::

    $ http https://endpoint/dev/ 'Content-Type: text/plain'
    HTTP/1.1 415 Unsupported Media Type
    Content-Type: application/json

    {
        "Code": "UnsupportedMediaType",
        "Message": "Unsupported media type: text/plain"
    }
* The keys in the ``app.current_request.to_dict()`` now match the casing used
  by the ``AWS_PPROXY`` lambda integration, which are ``lowerCamelCased``.
  This method is primarily intended for introspection purposes.

---

### Source Samples Index (`docs/source/samples/index.rst`)

Sample Applications
===================

Below are a collection of Chalice sample applications.  They
show you how you can write more real-world serverless applications.
The code for all of these sample applications is available in
the `Chalice repository on GitHub
<https://github.com/aws/chalice/tree/master/docs/source/samples>`__.

For each of these sample apps, we'll cover what is does,
the architecture of the app, how to deploy and test the app,
and we'll walk through the key parts of the application code.

:doc:`todo-app/index`
  This app is a REST API that manages Todo
  items.  These items are stored in an Amazon DynamoDB database.
  The REST API is protected with JWT auth.  We show you how to
  implement auth and login with Chalice's :ref:`builtin-authorizers`.

:doc:`media-query/index`
  This app shows how to create an image
  processing pipeline that can analyze images and videos to detect
  real world objects.  The results of this analysis are then
  stored in a database and exposed through a queryable REST API.


.. toctree::
   :hidden:
   :glob:

   ./*/index

---

### Source Topics Authorizers (`docs/source/topics/authorizers.rst`)

Authorization
=============

Chalice supports multiple mechanisms for authorization.  This topic
covers how you can integrate authorization into your Chalice applications.

In Chalice, all the authorizers are configured per-route and specified
using the ``authorizer`` kwarg to an ``@app.route()`` call.  You
control which type of authorizer to use based on what's passed as the
``authorizer`` kwarg.  You can use the same authorizer instance for
multiple routes.

The first set of authorizers chalice supports cover the scenario where
you have some existing authorization mechanism that you just want your
Chalice app to use.

Chalice also supports built-in authorizers, which allows Chalice to
manage your custom authorizers as part of ``chalice deploy``.  This is
covered in the Built-in Authorizers section.


AWS IAM Authorizer
------------------

The IAM Authorizer allows you to control access to API Gateway with
`IAM permissions`_

To associate an IAM authorizer with a route in chalice, you use the
:class:`IAMAUthorizer` class:

.. code-block:: python

    from chalice import IAMAuthorizer

    authorizer = IAMAuthorizer()

    @app.route('/iam-auth', methods=['GET'], authorizer=authorizer)
    def authenticated():
        return {"success": True}


See the `API Gateway documentation
<https://docs.aws.amazon.com/apigateway/latest/developerguide/permissions.html>`__
for more information on controlling access to API Gateway with IAM permissions.

Amazon Cognito User Pools
-------------------------

In addition to using IAM roles and policies with the :class:`IAMAuthorizer` you
can also use a `Cognito user pools`_ to control who can access your Chalice
app.  A cognito user pool serves as your own identity provider to maintain a
user directory.

To integrate Cognito user pools with Chalice, you'll need to have an existing
cognito user pool configured.


.. code-block:: python

    from chalice import CognitoUserPoolAuthorizer

    authorizer = CognitoUserPoolAuthorizer(
        'MyPool', provider_arns=['arn:aws:cognito:...:userpool/name'])

    @app.route('/user-pools', methods=['GET'], authorizer=authorizer)
    def authenticated():
        return {"success": True}


For more information about using Cognito user pools with API Gateway,
see the `Use Amazon Cognito User Pools documentation
<https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-integrate-with-cognito.html>`__.


Custom Authorizers
------------------

API Gateway also lets you write custom authorizers using a Lambda function.
You can configure a Chalice route to use a pre-existing Lambda function as
a custom authorizer.  If you also want to write and manage your Lambda
authorizer using Chalice, see the next section, Built-in Authorizers.

To connect an existing Lambda function as a custom authorizer in chalice,
you use the ``CustomAuthorizer`` class:

.. code-block:: python

    from chalice import CustomAuthorizer

    authorizer = CustomAuthorizer(
        'MyCustomAuth', header='Authorization',
        authorizer_uri=('arn:aws:apigateway:region:lambda:path/2015-03-31'
                        '/functions/arn:aws:lambda:region:account-id:'
                        'function:FunctionName/invocations'))

    @app.route('/custom-auth', methods=['GET'], authorizer=authorizer)
    def authenticated():
        return {"success": True}


.. _builtin-authorizers:

Built-in Authorizers
--------------------

The ``IAMAuthorizer``, ``CognitoUserPoolAuthorizer``, and the
``CustomAuthorizer`` classes are all for cases where you have existing
resources for managing authorization and you want to wire them together with
your Chalice app.  A Built-in authorizer is used when you'd like to write your
custom authorizer in Chalice, and have the additional Lambda functions managed
when you run ``chalice deploy/delete``.  This section will cover how to use the
built-in authorizers in chalice.

Creating an authorizer in chalice requires you use the ``@app.authorizer``
decorator to a function.  The function must accept a single arg, which will be
an instance of :class:`AuthRequest`.  The function must return a
:class:`AuthResponse`.  As an example, we'll port the example from the `API
Gateway documentation`_.  First, we'll show the code and then walk through it:

.. code-block:: python

    from chalice import Chalice, AuthResponse

    app = Chalice(app_name='demoauth1')


    @app.authorizer()
    def demo_auth(auth_request):
        token = auth_request.token
        # This is just for demo purposes as shown in the API Gateway docs.
        # Normally you'd call an oauth provider, validate the
        # jwt token, etc.
        # In this example, the token is treated as the status for demo
        # purposes.
        if token == 'allow':
            return AuthResponse(routes=['/'], principal_id='user')
        else:
            # By specifying an empty list of routes,
            # we're saying this user is not authorized
            # for any URLs, which will result in an
            # Unauthorized response.
            return AuthResponse(routes=[], principal_id='user')


    @app.route('/', authorizer=demo_auth)
    def index():
        return {'context': app.current_request.context}


In the example above we define a built-in authorizer by decorating
the ``demo_auth`` function with the ``@app.authorizer()`` decorator.
Note you must use ``@app.authorizer()`` and not ``@app.authorizer``.
A built-in authorizer function has this type signature::

    def auth_handler(auth_request: AuthRequest) -> AuthResponse: ...

Within the auth handler you must determine if the request is
authorized or not.  The ``AuthResponse`` contains the allowed
URLs as well as the principal id of the user.  You can optionally
return a dictionary of key value pairs (as the ``context`` kwarg).
This dictionary will be passed through on subsequent requests.
In our example above we're not using the context dictionary.
API Gateway will convert all the values in the ``context``
dictionary to string values.

Now let's deploy our app.  As usual, we just need to run
``chalice deploy`` and chalice will automatically deploy all the
necessary Lambda functions for us.

Now when we try to make a request, we'll get an Unauthorized error::

  $ http https://api.us-west-2.amazonaws.com/api/
  HTTP/1.1 401 Unauthorized

  {
      "message": "Unauthorized"
  }

If we add the appropriate authorization header, we'll see the call succeed::

  $ http https://api.us-west-2.amazonaws.com/api/ 'Authorization: allow'
  HTTP/1.1 200 OK

  {
      "context": {
          "accountId": "12345",
          "apiId": "api",
          "authorizer": {
              "principalId": "user"
          },
          "httpMethod": "GET",
          "identity": {
              "accessKey": null,
              "accountId": null,
              "apiKey": "",
              "caller": null,
              "cognitoAuthenticationProvider": null,
              "cognitoAuthenticationType": null,
              "cognitoIdentityId": null,
              "cognitoIdentityPoolId": null,
              "sourceIp": "1.1.1.1",
              "user": null,
              "userAgent": "HTTPie/0.9.9",
              "userArn": null
          },
          "path": "/api/",
          "requestId": "d35d2063-56be-11e7-9ce1-dd61c24a3668",
          "resourceId": "id",
          "resourcePath": "/",
          "stage": "dev"
      }
  }

The low level API for API Gateway's custom authorizer feature requires
that an IAM policy must be returned.  The :class:`AuthResponse` class we're
using is a wrapper over building the IAM policy ourselves.  If you want
low level control and would prefer to construct the IAM policy yourself
you can return a dictionary of the IAM policy instead of an instance of
:class:`AuthResponse`.  If you do that, the dictionary is returned
without modification back to API Gateway.

For more information on custom authorizers, see the
`Use API Gateway Custom Authorizers
<https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-use-lambda-authorizer.html>`__
page in the API Gateway user guide.


Scopes
-------------------------

OAuth 2.0 and OpenID Connect (OIDC) scopes can be used to implement access
controls in your Chalice app. Scopes are supported when using the Cognito
Authorizer, Custom Authorizers, and Built-In Authorizers.

To integrate Scopes with a Cognito Authorizer in Chalice, you'll need to have
an existing `Cognito user pools`_ and `Cognito resource server`_ configured.
Scopes for Cognito Authorizers need to include the full identifier
which is ``resourceServerIdentifier/scopeName``.

Scopes can be configured per-authorizer using the ``scopes`` attribute.

.. code-block:: python

    from chalice import CognitoUserPoolAuthorizer

    authorizer = CognitoUserPoolAuthorizer(
        'MyPool', provider_arns=['arn:aws:cognito:...:userpool/name'],
        scopes=["https://mychaliceapp.example.com/todos.read"])

    @app.route('/user-pools', methods=['GET'], authorizer=authorizer)
    def authenticated():
        return {"success": True}

Scopes can be configured per-route for an Authorizer using ``with_scopes``.

.. code-block:: python

    from chalice import CognitoUserPoolAuthorizer

    authorizer = CognitoUserPoolAuthorizer(
        'MyPool', provider_arns=['arn:aws:cognito:...:userpool/name'])

    @app.route(
        '/user-pools',
        methods=['GET'],
        authorizer=authorizer.with_scopes(["https://mychaliceapp.example.com/todos.read"]))
    def authenticated():
        return {"success": True}

Scopes can also be used with custom authorizers and built-in authorizers.
These authorizers will need to inspect the access token to determine if access
should be granted based on the scopes configured for the authorizer and route.


.. _IAM permissions: https://docs.aws.amazon.com/IAM/latest/UserGuide/access_controlling.html
.. _Cognito User Pools: https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-identity-pools.html
.. _Cognito Resource Server: https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-pools-define-resource-servers.html
.. _API Gateway documentation: https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-use-lambda-authorizer.html

---

### Source Topics Blueprints (`docs/source/topics/blueprints.rst`)

Blueprints
==========


Chalice blueprints are used to organize your application into logical
components.  Using a blueprint, you define your resources and decorators in
modules outside of your ``app.py``.  You then register a blueprint in your main
``app.py`` file.  Blueprints support any decorator available on an application
object.


.. note::

  The Chalice blueprints are conceptually similar to `Blueprints
  <https://flask.palletsprojects.com/blueprints/>`__ in Flask.  Flask
  blueprints allow you to define a set of URL routes separately from the main
  ``Flask`` object.  This concept is extended to all resources in Chalice.  A
  Chalice blueprint can have Lambda functions, event handlers, built-in
  authorizers, etc. in addition to a collection of routes.


Example
-------

In this example, we'll create a blueprint with part of our routes defined in a
separate file.  First, let's create an application::

    $ chalice new-project blueprint-demo
    $ cd blueprint-demo
    $ mkdir chalicelib
    $ touch chalicelib/__init__.py
    $ touch chalicelib/blueprints.py

Next, we'll open the ``chalicelib/blueprints.py`` file:

.. code-block:: python

    from chalice import Blueprint


    extra_routes = Blueprint(__name__)


    @extra_routes.route('/foo')
    def foo():
        return {'foo': 'bar'}


The ``__name__`` is used to denote the import path of the blueprint.  This name
must match the import name of the module so the function can be properly
imported when running in Lambda.  We'll now import this module in our
``app.py`` and register this blueprint.  We'll also add a route in our
``app.py`` directly:

.. code-block:: python

    from chalice import Chalice
    from chalicelib.blueprints import extra_routes

    app = Chalice(app_name='blueprint-demo')
    app.register_blueprint(extra_routes)


    @app.route('/')
    def index():
        return {'hello': 'world'}

At this point, we've defined two routes.  One route, ``/``, is directly defined
in our ``app.py`` file.  The other route, ``/foo`` is defined in
``chalicelib/blueprints.py``.  It was added to our Chalice app when we
registered it via ``app.register_blueprint(extra_routes)``.

We can deploy our application to verify this works as expected::

    $ chalice deploy
    Creating deployment package.
    Creating IAM role: blueprint-demo-dev
    Creating lambda function: blueprint-demo-dev
    Creating Rest API
    Resources deployed:
      - Lambda ARN: arn:aws:lambda:us-west-2:1234:function:blueprint-demo-dev
      - Rest API URL: https://rest-api.execute-api.us-west-2.amazonaws.com/api/


We should now be able to request the ``/`` and ``/foo`` routes::

    $ http https://rest-api.execute-api.us-west-2.amazonaws.com/api/
    HTTP/1.1 200 OK
    Connection: keep-alive
    Content-Length: 17
    Content-Type: application/json
    Date: Sat, 22 Dec 2018 01:05:48 GMT
    Via: 1.1 5ab5dc09da67e3ea794ec8a82992cc89.cloudfront.net (CloudFront)
    X-Amz-Cf-Id: Cdsow9--fnTH5EdjkjWBMWINCCMD4nGmi4S_3iMYMK0rpc8Mpiymgw==
    X-Amzn-Trace-Id: Root=1-5c1d8dec-f1ef3ee83c7c654ca7fb3a70;Sampled=0
    X-Cache: Miss from cloudfront
    x-amz-apigw-id: SSMc6H_yvHcFcEw=
    x-amzn-RequestId: b7bd0c87-0585-11e9-90cf-59b71c1a1de1

    {
        "hello": "world"
    }

    $ http https://rest-api.execute-api.us-west-2.amazonaws.com/api/foo
    HTTP/1.1 200 OK
    Connection: keep-alive
    Content-Length: 13
    Content-Type: application/json
    Date: Sat, 22 Dec 2018 01:05:51 GMT
    Via: 1.1 95b0ac620fa3a80ee590ecf1cda1c698.cloudfront.net (CloudFront)
    X-Amz-Cf-Id: HX4l1BNdWvYDRXan17PFZya1vaomoJel4rP7d8_stdw2qT50v7Iybg==
    X-Amzn-Trace-Id: Root=1-5c1d8def-214e7f681ff82c00fd81f37a;Sampled=0
    X-Cache: Miss from cloudfront
    x-amz-apigw-id: SSMdXF40vHcF-mg=
    x-amzn-RequestId: b96f77bf-0585-11e9-b229-01305cd40040

    {
        "foo": "bar"
    }


Blueprint Registration
----------------------

The ``app.register_blueprint`` function accepts two optional arguments,
``name_prefix`` and ``url_prefix``.  This allows you to register the resources
in your blueprint at a certain url and name prefix.  If you specify
``url_prefix``, any routes defined in your blueprint will have the
``url_prefix`` prepended to it.  If you specify the ``name_prefix``, any Lambda
functions created will have the ``name_prefix`` prepended to the resource name.

.. note::

  The ``name_prefix`` parameter does not apply to the Lambda function
  associated with API Gateway, which is anything decorated with
  ``@app.route()``.


Advanced Example
----------------

Let's create a more advanced example.  If this application, let's say we want
to organize our application into separate modules for our API and our event
sources.  We can create an app with these files::

    $ ls -la chalicelib/
    __init__.py
    api.py
    events.py


The contents of ``api.py`` are:

.. code-block:: python

    from chalice import Blueprint


    myapi = Blueprint(__name__)


    @myapi.route('/')
    def index():
        return {'hello': 'world'}


    @myapi.route('/foo')
    def index():
        return {'foo': 'bar'}


The contents of ``events.py`` are:

.. code-block:: python

    from chalice import Blueprint


    myevents = Blueprint(__name__)


    @myevents.schedule('rate(5 minutes)')
    def cron(event):
        pass


    @myevents.on_sns_message('MyTopic')
    def handle_sns_message(event):
        pass

In our ``app.py`` we'll register these blueprints:

.. code-block:: python

    from chalice import Chalice
    from chalicelib.events import myevents
    from chalicelib.api import myapi

    app = Chalice(app_name='blueprint-demo')
    app.register_blueprint(myevents)
    app.register_blueprint(myapi)


Now our ``app.py`` only registers the necessary blueprints, and all our
resources are defined in blueprints.

---

### Source Topics Cd (`docs/source/topics/cd.rst`)

===========================
Continuous Deployment (CD)
===========================

Chalice can be used to set up a basic Continuous Deployment pipeline. The
``chalice deploy`` command is good for getting up and running quickly with
Chalice, but in a team environment properly managing permissions and sharing
and updating the ``deployed.json`` file will get messy.

One way to scale up your chalice app is to create a continuous deployment
pipeline. The pipeline can run tests on code changes and, if they pass, promote
the new build to a testing stage. More checks can be put in place to manually
promote a build to production, or you can do so automatically. This model
greatly simplifies managing what resources belong to your Chalice app as they
are all stored in the Continuous Deployment pipeline.

Chalice can generate a CloudFormation template that will create a starter CD
pipeline. By default it contains an AWS CodeCommit repo, an AWS CodeBuild stage
for packaging your chalice app, and an AWS CodePipeline stage to deploy your
application using CloudFormation.

You can also configure a source repository hosted on GitHub instead of
a CodeCommit repository.

Pipeline Template Versions
==========================

This starter pipeline template can be generated using the ``generate-pipeline``
command.  There are two versions of this pipeline.  The older ``v1`` template
is the default (for backwards compatibility reasons), but the newer template
version, ``v2``, is recommended.  The version can be specified using the
``--pipeline-version`` option.  These are the differences between ``v1`` and
``v2`` templates:

* The ``v1`` templates use version ``0.1`` of the CodeBuild buildspec, whereas
  ``v2`` uses ``0.2`` of the CodeBuild buildspec.  Buildspec ``0.2`` is the
  recommended version to use with CodeBuild.  See their
  `documentation <https://docs.aws.amazon.com/codebuild/latest/userguide/build-spec-ref.html>`__
  for more information.
* The ``v2`` template uses `AWS Secrets Manager <https://aws.amazon.com/secrets-manager/>`__
  to configure access to a GitHub repository.
* The ``v2`` buildspec uses `runtime-versions <https://docs.aws.amazon.com/codebuild/latest/userguide/build-spec-ref.html#build-spec.phases.install.runtime-versions>`__
  to configure which version of Python to use instead of a Python
  version specific CodeBuild image.  For ``v2`` templates the
  ``aws/codebuild/amazonlinux2023-x86_64-standard`` image.

**The v2 pipeline template requires Python 3.10 or higher.** If you're using
Python versions less than 3.10 you must use the ``v1`` pipeline template.


Usage example
=============

Setting up the deployment pipeline is a two step process. First use the
``chalice generate-pipeline`` command to generate a base CloudFormation
template. Second use the AWS CLI to deploy the CloudFormation template using
the ``aws cloudformation deploy`` command. Below is an example.

::

   $ chalice generate-pipeline --pipeline-version v2 pipeline.json
   $ aws cloudformation deploy --stack-name mystack
         --template-file pipeline.json --capabilities CAPABILITY_IAM
   Waiting for changeset to be created..
   Waiting for stack create/update to complete
   Successfully created/updated stack - mystack

.. note::
   To configure your Chalice app to use a GitHub repository instead of
   CodeCommit see the :ref:`cicd-github-repo` section below.


Once the CloudFormation template has finished creating the stack, you will have
several new AWS resources that make up a bare bones CD pipeline.

* **CodeCommit Repository** - The `CodeCommit <https://aws.amazon.com/codecommit/>`_
  repository is the entrypoint into the pipeline. Any code you want to deploy
  should be pushed to this remote.
* **CodePipeline Pipeline** - The
  `CodePipeline <https://aws.amazon.com/codepipeline/>`_ is what coordinates
  the build process, and pushes the released code out.
* **CodeBuild Project** - The `CodeBuild <https://aws.amazon.com/codebuild/>`_
  project is where the code bundle is built that will be pushed to Lambda. The
  default CloudFormation template will create a CodeBuild stage that builds
  a package using ``chalice package`` and then uploads those artifacts for
  CodePipeline to deploy.
* **S3 Buckets** - Two S3 buckets are created on your behalf.

  * **artifactbucketstore** - This bucket stores artifacts that are built by
    the CodeBuild project. The only artifact by default is the
    ``transformed.yaml`` created by the ``aws cloudformation package`` command.
  * **applicationbucket** - Stores the application bundle after the Chalice
    application has been packaged in the CodeBuild stage.
* Each resource is created with all the required IAM roles and policies.


CodeCommit repository
---------------------

The CodeCommit repository can be added as a git remote for deployment. This
makes it easy to kick off deployments. The developer doing the deployment only
needs to push the release code up to the CodeCommit repository master branch.
All the developer needs is keys that allow for push access to the CodeCommit
repository. This is a lot easier than managing a set of ``deployed.json``
resources across a repsoitory and manually doing ``chalice deploy`` whenever
a change needs to be deployed.

The default CodeCommit repository that is created is empty, you will have to
populate it with the Chalice application code. Permissions will also need to be
set up, you can find the documentation on how to do that
`here <https://docs.aws.amazon.com/codebuild/latest/userguide/setting-up.html>`_
.

You can retrieve the CodeCommit clone URL by searching for the
``SourceRepoURL`` in the CloudFormation stack output::

    $ aws cloudformation describe-stacks --stack-name mystack \
       --query "Stacks[0].Outputs[?OutputKey=='SourceRepoURL'] | [0].OutputValue"


CodePipeline
------------

CodePipeline is the main coordinator between all the other resources. It
watches for changes on the CodeCommit repository, and triggers builds in the
CodeBuild project. If the build succeeds then it will start a CloudFormation
deployment of the built artifacts to a beta stage. This should be treated as
a starting point, not a fully featured CD system.


CodeBuild build script
----------------------

By default Chalice will create the CodeBuild project with a default buildspec
that does the following.

.. code-block:: yaml

  version: 0.1
  phases:
    install:
      commands:
      - sudo pip install --upgrade awscli
      - aws --version
      - sudo pip install chalice
      - sudo pip install -r requirements.txt
      - chalice package /tmp/packaged
      - aws cloudformation package --template-file
          /tmp/packaged/sam.json --s3-bucket ${APP_S3_BUCKET}
          --output-template-file transformed.yaml
  artifacts:
    type: zip
    files:
      - transformed.yaml

The CodeBuild stage installs both the AWS CLI and Chalice, then creates a
package out of your chalice project, pushing the package to the application
S3 bucket that was created for you. The transformed CloudFormation template
is the only artifact, and can be run by CodePipeline after the build has
succeeded.


Deploying to beta stage
-----------------------

Once the CodeBuild stage has finished building the Chalice package and
creating the ``transformed.yaml``, CodePipeline will take these artifacts and
use them to create or update the beta stage. The ``transformed.yaml``
is a CloudFormation template that CodePipeline will execute, all the code it
references has been uploaded to the application bucket by the AWS CLI in the
CodeBuild stage, so this is the only artifact we need.

Once the CodePipeline beta build stage is finished, the beta version of the app
is deployed and ready for testing.


Extending
---------

It is recommended to use this pipeline as a starting point. The default
template does not run any tests on the Chalice app before deploying to beta.
There is also no mechanism provided by Chalice for a production stage.
Ideally the CodeBuild stage would be used to run unit and functional tests
before deploying to beta. After the beta stage is up, integration tests can be
run against that endpoint, and if they all pass the beta stage could be
promoted to a production stage using the CodePipeline manual approval feature.

.. _cicd-github-repo:

Configuring a GitHub Repository
===============================

You can configure a GitHub repository instead of a CodeCommit repo when
setting up your deployment pipeline by specifying the ``--source github``
option.  When generating a CloudFormation template for a GitHub repository,
there are several parameters that are added to your template that allow
you to configure how to connect your GitHub repository with your CodePipeline.

You must store your OAuth token that enables access to a GitHub repository
in AWS Secrets Manager.  You then specify the secret name/id and the JSON
key name as CloudFormation parameters.  These values default to a secret
name of ``GithubRepoAccess`` and a JSON key name of ``OAuthToken``.

Below is an example of how to configure a GitHub repository as the
source for your deployment pipeline.

First create a `GitHub token <https://docs.github.com/en/github/authenticating-to-github/creating-a-personal-access-token>`__
that can be used in this template.  Next create a secret in AWS Secrets
Manager.  You can either follow the documentation
`here <https://docs.aws.amazon.com/secretsmanager/latest/userguide/manage_create-basic-secret.html>`__
or use the AWS CLI or any AWS SDK.  For this example, we'll use the AWS CLI
to create our secret.  Create a file named ``/tmp/secrets.json`` with these
contents::

    {"OAuthToken": "abcdefghhijklmnop"}

Be sure to replace the value of ``OAuthToken`` with the value of your GitHub
token you created.  Next we can create the secret using this command::

    $ aws secretsmanager create-secret --name GithubRepoAccess \
      --description "Token for Github Repo Access" \
      --secret-string file:///tmp/secrets.json

Now we can generate our deployment pipeline::

    $ chalice generate-pipeline --pipeline-version v2 \
      --source github --buildspec-file buildspec.yml pipeline.json

This will create two files, a ``pipeline.json`` file containing our
deployment pipeline and a ``buildspec.yml`` file.  This buildspec file
lets us update what commands should be run as part of our build process
without having to redeploy our CloudFormation template.

We now add and commit our changes to our repository.

::

    $ git add buildspec.yml pipeline.json
    $ git commit -m "Add deployment pipeline template"
    $ git push

Now we're ready to deploy our CloudFormation template using the AWS CLI.  Be
sure to replace the ``GithubOwner`` and ``GithubRepoName`` with your own
values for your GitHub repository.  You'll also need to specify the
``GithubRepoSecretId`` and ``GithubRepoSecretJSONKey`` if you used values
other than the default values of ``GithubRepoAccess`` and ``OAuthToken`` when
creating your secret in Secrets Manager.

::

    $ aws cloudformation deploy --template-file pipeline.json \
      --stack-name MyChaliceApp --parameter-overrides \
      GithubOwner=repo-owner-name \
      GithubRepoName=repo-name \
      --capabilities CAPABILITY_IAM

We've now created a deployment pipeline that will automatically deploy our
Chalice app whenever we push to our GitHub repository.

---

### Source Topics Cfn (`docs/source/topics/cfn.rst`)

AWS CloudFormation Support
==========================

When you run ``chalice deploy``, chalice will deploy your application using the
`AWS SDK for Python <http://boto3.readthedocs.io/en/docs/>`__).  Chalice also
provides functionality that allows you to manage deployments yourself using
cloudformation.  This is provided via the ``chalice package`` command.

When you run this command, chalice will generate the AWS Lambda deployment
package that contains your application as well as a `Serverless Application
Model (SAM) <https://github.com/awslabs/serverless-application-model>`__
template.  You can then use a tool like the AWS CLI, or any cloudformation
deployment tools you use, to deploy your chalice application.

Considerations
--------------

Using the ``chalice package`` command is useful when you don't want to
use ``chalice deploy`` to manage your deployments.  There's several reasons
why you might want to do this:

* You have pre-existing infrastructure and tooling set up to manage
  cloudformation stacks.
* You want to integrate with other cloudformation stacks to manage
  all your AWS resources, including resources outside of your chalice
  app.
* You'd like to integrate with `AWS CodePipeline
  <https://aws.amazon.com/codepipeline/>`__ to automatically deploy
  changes when you push to a git repo.

Keep in mind that you can't switch between ``chalice deploy`` and
``chalice package`` + CloudFormation for deploying your app.

If you choose to use ``chalice package`` and CloudFormation to deploy
your app, you won't be able to switch back to ``chalice deploy``.
Running ``chalice deploy`` would create an entirely new set of AWS
resources (API Gateway Rest API, AWS Lambda function, etc).

Template Merge
--------------

It's a common use case to need to modify a Chalice generated template
before deployment. Often to inject extra resources, values, or
configurations that are not supported directly by Chalice. It will
always be the case that something on AWS is not supported by Chalice
directly that a consumer may want to interact with.

The package command can now be invoked with the ``--merge-template`` argument::

  $ chalice package --merge-template extras.json out

This extras.json file should be a JSON formatted file which will be
deep-merged on top of the sam.json that is generated by Chalice.

For a simple example lets assume that we have the default new Chalice
project and that extras.json has the following content::

    {
      "Resources" : {
        "MusicTable" : {
          "Type" : "AWS::DynamoDB::Table",
          "Properties" : {
            "TableName" : "MusicData",
            "AttributeDefinitions" : [
              {
                "AttributeName" : "Album",
                "AttributeType" : "S"
              },
              {
                "AttributeName" : "Artist",
                "AttributeType" : "S"
              }
            ],
            "KeySchema" : [
              {
                "AttributeName" : "Album",
                "KeyType" : "HASH"
              },
              {
                "AttributeName" : "Artist",
                "KeyType" : "RANGE"
              }
            ],
            "ProvisionedThroughput" : {
              "ReadCapacityUnits" : "5",
              "WriteCapacityUnits" : "5"
            }
          }
        },
        "APIHandler": {
          "Properties": {
            "Environment": {
              "Variables": {
                "MUSIC_TABLE": {"Ref": "MusicTable"}
              }
            }
          }
        }
      }
    }


The generated template located at out/sam.json will have the DynamoDB table
injected into the resource section, as well as the ``MUSIC_TABLE`` environment
variable added to the ``APIHandler`` Lambda function::

    ...
        "APIHandler": {
          "Type": "AWS::Serverless::Function",
          "Properties": {
            "Runtime": "python3.6",
            "Handler": "app.app",
            "CodeUri": "./deployment.zip",
            "Tags": {
              "aws-chalice": "version=1.10-:stage=dev:app=test"
            },
            "Timeout": 60,
            "MemorySize": 128,
            "Role": {
              "Fn::GetAtt": [
                "DefaultRole",
                "Arn"
              ]
            },
            "Environment": {
              "Variables": {
                "MUSIC_TABLE": "MusicData"
              }
            }
          }
        },
    ...

This gives us the ability to inject arbitrary resources into our Chalice
applications, and reference them from our Chalice-deployed functions. We can
now rely on Chalice's policy auto-generation to generate a policy that allows
DynamoDB access, inject our own policy modifications through the same
extras.json file, or specify a custom policy using the config file.


Example
-------

In this example, we'll create a chalice app and deploy it using
the AWS CLI.

First install the necessary packages::

    $ virtualenv /tmp/venv
    $ . /tmp/venv/bin/activate
    $ pip install chalice awscli
    $ chalice new-project test-cfn-deploy
    $ cd test-cfn-deploy

At this point we've installed chalice and the AWS CLI and we have
a basic app created locally.  Next we'll run the ``package`` command
and look at its contents::

    $ $ chalice package /tmp/packaged-app/
    Creating deployment package.
    $ ls -la /tmp/packaged-app/
    -rw-r--r--   1 j         wheel  3355270 May 25 14:20 deployment.zip
    -rw-r--r--   1 j         wheel     3068 May 25 14:20 sam.json

    $ unzip -l /tmp/packaged-app/deployment.zip  | tail -n 5
        17292  05-25-17 14:19   chalice/app.py
          283  05-25-17 14:19   chalice/__init__.py
          796  05-25-17 14:20   app.py
     --------                   -------
      9826899                   723 files

    $ head < /tmp/packaged-app/sam.json
    {
      "AWSTemplateFormatVersion": "2010-09-09",
      "Outputs": {
        "RestAPIId": {
          "Value": {
            "Ref": "RestAPI"
          }
        },
        "APIHandlerName": {
          "Value": {

As you can see in the above example, the ``package`` command created a
directory that contained two files, a ``deployment.zip`` file, which is the
Lambda deployment package, and a ``sam.json`` file, which is the SAM template
that can be deployed using CloudFormation.  Next we're going to use the AWS CLI
to deploy our app.  To this, we'll first run the ``aws cloudformation package``
command, which will take our deployment.zip file and upload to an S3 bucket
we specify::

    $ aws cloudformation package \
         --template-file /tmp/packaged-app/sam.json \
         --s3-bucket myapp-bucket \
         --output-template-file /tmp/packaged-app/packaged.yaml

Now we can deploy our app using the ``aws cloudformation deploy`` command::

    $ aws cloudformation deploy \
        --template-file /tmp/packaged-app/packaged.yaml \
        --stack-name test-cfn-stack \
        --capabilities CAPABILITY_IAM
    Waiting for changeset to be created..
    Waiting for stack create/update to complete
    Successfully created/updated stack - test-cfn-stack

This will take a few minutes to complete, but once it's done, the endpoint url
will be available as an output::

    $ aws cloudformation describe-stacks --stack-name test-cfn-stack \
      --query "Stacks[].Outputs[?OutputKey=='EndpointURL'][] | [0].OutputValue"
    "https://abc29hkq0i.execute-api.us-west-2.amazonaws.com/api/"

    $ http "https://abc29hkq0i.execute-api.us-west-2.amazonaws.com/api/"
    HTTP/1.1 200 OK
    Connection: keep-alive
    Content-Length: 18
    Content-Type: application/json
    ...

    {
        "hello": "world"
    }

---

### Source Topics Configfile (`docs/source/topics/configfile.rst`)

Configuration File
==================

Whenever you create a new project using
``chalice new-project``, a ``.chalice`` directory is created
for you.  In this directory is a ``config.json`` file that
you can use to control what happens when you ``chalice deploy``::


    $ tree -a
    .
    ├── .chalice
    │   └── config.json
    ├── app.py
    └── requirements.txt

    1 directory, 3 files


.. _stage-config:

Stage Specific Configuration
----------------------------

As of version 0.7.0 of chalice, you can specify configuration
that is specific to a chalice stage as well as configuration that should
be shared across all stages.  See the :doc:`stages` doc for more
information about chalice stages.

* ``stages`` - This value of this key is a mapping of chalice stage
  name to stage configuration.  Chalice assumes a default stage name
  of ``dev``.  If you run the ``chalice new-project`` command on
  chalice 0.7.0 or higher, this key along with the default ``dev``
  key will automatically be created for you.  See the examples
  section below for some stage specific configurations.

The following config values can either be specified per stage config
or as a top level key which is not tied to a specific stage.  Whenever
a stage specific configuration value is needed, the ``stages`` mapping
is checked first.  If no value is found then the top level keys will
be checked.


``api_gateway_endpoint_type``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

The endpoint configuration of the deployed API Gateway which determines how the
API will be accessed, can be EDGE, REGIONAL, PRIVATE. Note this value can only
be set as a top level key and defaults to EDGE. For more information see
https://amzn.to/2LofApt


``api_gateway_endpoint_vpce``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

When configuring a Private API a VPC Endpoint id must be specified to configure
a default resource policy on the API if an explicit policy is not specified.
This value can be a list or a string of endpoint ids.


``api_gateway_policy_file``
~~~~~~~~~~~~~~~~~~~~~~~~~~~

A file pointing to an IAM resource policy for the REST API. If not specified
chalice will autogenerate this policy when endpoint_type is PRIVATE. This
filename is relative to the ``.chalice`` directory.


``api_gateway_stage``
~~~~~~~~~~~~~~~~~~~~~

The name of the API gateway stage.  This will also be the URL prefix for your
API (``https://endpoint/prefix/your-api``).


``autogen_policy``
~~~~~~~~~~~~~~~~~~

A boolean value that indicates if chalice should try to automatically generate
an IAM policy based on analyzing your application source code.  The default
value is ``true``.  If this value is ``false`` then chalice will try to load an
IAM policy from disk at ``.chalice/policy-<stage-name>.json`` instead of
auto-generating a policy from source code analysis. You can change the filename
by providing the ``iam_policy_file`` config option.
See :ref:`iam-role-pol-examples` for examples of how to configure IAM roles
and policies.


``environment_variables``
~~~~~~~~~~~~~~~~~~~~~~~~~

A mapping of key value pairs.  These key value pairs will be set as environment
variables in your application.  All environment variables must be strings.  If
this key is specified in both a stage specific config option as well as a top
level key, the stage specific environment variables will be merged into the top
level keys.  See the :ref:`env-var-examples` section below for a concrete
example.


``iam_policy_file``
~~~~~~~~~~~~~~~~~~~

When ``autogen_policy`` is ``false``, Chalice will try to load an IAM policy
from disk instead of auto-generating one based on source code analysis.  The
default location of this file is ``.chalice/policy-<stage-name>.json``, e.g
``.chalice/policy-dev.json``, ``.chalice/policy-prod.json``, etc.  You can
change the filename by providing this ``iam_policy_file`` config option.  This
filename is relative to the ``.chalice`` directory.  For example, this config
will create an IAM role using the file in ``.chalice/my-policy.json``::

  {
    "version": "2.0",
    "app_name": "app",
    "stages": {
      "dev": {
        "autogen_policy": false,
        "iam_policy_file": "my-policy.json"
      }
    }
  }


See :ref:`iam-role-pol-examples` for more examples of how to configure IAM
roles and policies.


``iam_role_arn``
~~~~~~~~~~~~~~~~

If ``manage_iam_role`` is ``false``, you must specify this value that indicates
which IAM role arn to use when configuration your application.  This value is
only used if ``manage_iam_role`` is ``false``.
See :ref:`iam-role-pol-examples` for examples of how to configure IAM roles
and policies.


``lambda_architecture``
~~~~~~~~~~~~~~~~~~~~~~~

The instruction set architecture of your Lambda functions and automatic
layer.  Valid values are ``x86_64`` and ``arm64``.  The default
``lambda_architecture`` is ``x86_64``.  This value can be set globally or per
stage, but not per function.  When set to ``arm64``, Chalice will package
``manylinux`` wheel files built for ``aarch64``.  See
:ref:`package-3rd-party` for more information on how third party packages are
handled.


``lambda_memory_size``
~~~~~~~~~~~~~~~~~~~~~~

An integer representing the amount of memory, in MB, your Lambda function is
given. AWS Lambda uses this memory size to infer the amount of CPU allocated to
your function. The default ``lambda_memory_size`` value is ``128``. The value
must be a multiple of 64 MB.


``lambda_timeout``
~~~~~~~~~~~~~~~~~~

An integer representing the function execution time, in seconds, at which AWS
Lambda should terminate the function. The default ``lambda_timeout`` is ``60``
seconds.


``layers``
~~~~~~~~~~

A list of Lambda Layers arns. This value can be provided per stage as well as
per Lambda function. See `AWS Lambda Layers Configuration`_.



.. _automatic-layer-option:

``automatic_layer``
~~~~~~~~~~~~~~~~~~~~

A boolean value that indicates whether chalice will automatically construct a
single stage layer for all Lambda functions with requirements.txt libraries and
vendored libraries.  Boolean value defaults to ``false`` if not specified.  See
:ref:`package-3rd-party` for more information.


.. _custom-domain-config-options:

``api_gateway_custom_domain``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

A mapping of key value pairs. The following are required keys when
specifying an ``api_gateway_custom_domain``:

- ``domain_name``: The custom domain name to associated with the REST API
  (api.example.com)
- ``certificate_arn``: the ARN of ACM certificate for the current domain name.
  If you're using a ``REGIONAL`` endpoint type for your API, the ACM
  certificate **must** be in the same region as your API.  If you're using an
  ``EDGE`` endpoint type, the certificate must be in ``us-east-1``.

You can also provide the following optional configuration:

- ``tls_version`` - The Transport Layer Security (TLS) version of the security
  policy for this domain name.  Defaults to ``TLS_1_2``, you can also provide
  ``TLS_1_0`` for REST APIs.
- ``url_prefix`` - A custom domain name plus a url_prefix (BasePathMapping)
  specification identifies a deployed REST API in a given stage. With custom
  domain names, you can set up your API's hostname, and choose a base path (for
  example, `myservice`) to map the alternative URL to your API (for example
  ``https://api.example.com/myservice``).  If you don't set any ``url_prefix``,
  the resulting API's base URL is the same as the custom domain (for example
  ``https://api.example.com/``).
- tags - A dictionary of tags with the keys being the tag key, and the values
  being the value for the tag.

See the :doc:`domainname` documentation for more information on configuring
your Chalice application with a custom domain name.

See `AWS Custom Domain names setup`_ for the API Gateway documentation on
configuring a custom domain name.

.. _custom-domain-ws-config-options:

``websocket_api_custom_domain``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

A mapping of key value pairs. The following are required keys when
specifying a ``websocket_api_custom_domain``:

- ``domain_name``: The custom domain name to associated with the REST API
  (api.example.com)
- ``certificate_arn``: the ARN of ACM certificate for the current domain name.
  If you're using a ``REGIONAL`` endpoint type for your API, the ACM
  certificate **must** be in the same region as your API.  If you're using an
  ``EDGE`` endpoint type, the certificate must be in ``us-east-1``.

You can also provide the following optional configuration:

- ``tls_version`` - The Transport Layer Security (TLS) version of the security
  policy for this domain name.  Defaults to ``TLS_1_2``, you can also provide
  ``TLS_1_0`` for REST APIs.
- ``url_prefix`` - A custom domain name plus a url_prefix (BasePathMapping)
  specification identifies a deployed REST API in a given stage. With custom
  domain names, you can set up your API's hostname, and choose a base path (for
  example, `myservice`) to map the alternative URL to your API (for example
  ``https://api.example.com/myservice``).  If you don't set any ``url_prefix``,
  the resulting API's base URL is the same as the custom domain (for example
  ``https://api.example.com/``).
- tags - A dictionary of tags with the keys being the tag key, and the values
  being the value for the tag.

See the :doc:`domainname` documentation for more information on configuring
your Chalice application with a custom domain name.

See `AWS Custom Domain names setup`_ for the API Gateway documentation on
configuring a custom domain name.

``manage_iam_role``
~~~~~~~~~~~~~~~~~~~

``true``/``false``.  Indicates if you want chalice to create and update the IAM
role used for your application.  By default, this value is ``true``.  However,
if you have a pre-existing role you've created, you can set this value to
``false`` and a role will not be created or updated.  ``"manage_iam_role":
false`` means that you are responsible for managing the role and any associated
policies associated with that role.  If this value is ``false`` you must
specify an ``iam_role_arn``, otherwise an error is raised when you try to run
``chalice deploy``.


``minimum_compression_size``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~

An integer value that indicates the minimum compression size to apply to the
API gateway. If this key is specified in both a stage specific config option as
well as a top level key, the stage specific key will override the top level key
for the given stage. For more information check out the `Service Docs
<https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-gzip-compression-decompression.html>`__


``reserved_concurrency``
~~~~~~~~~~~~~~~~~~~~~~~~

An integer representing each function's reserved concurrency.  This value can
be provided per stage as well as per Lambda function. AWS Lambda reserves this
value of concurrency to each lambda deployed in this stage. If the value is set
to 0, invocations to this function are blocked. If the value is unset, there
will be no reserved concurrency allocations. For more information, see `AWS
Documentation on managing concurrency`_.


``subnet_ids``
~~~~~~~~~~~~~~

A list of subnet ids for VPC configuration.  This value can be provided per
stage as well as per Lambda function.  In order for this value to take effect,
you must also provide the ``security_group_ids`` value.  When both values are
provided and ``autogen_policy`` is True, chalice will automatically update your
IAM role with the necessary permissions to create, describe, and delete ENIs.
If you are managing the IAM role policy yourself, make sure to update your
permissions accordingly, as described in the `AWS Lambda VPC documentation`_.


``security_group_ids``
~~~~~~~~~~~~~~~~~~~~~~

A list of security groups for VPC configuration.  This value can be provided
per stage as well as per Lambda function.  In order for this value to take
effect, you must also provide the ``subnet_ids`` value.


``tags``
~~~~~~~~

A mapping of key value pairs. These key value pairs will be set as the tags on
the resources running your deployed application. All tag keys and values must
be strings. Similar to ``environment_variables``, if a key is specified in both
a stage specific config option as well as a top level key, the stage specific
tags will be merged into the top level keys. By default, all chalice deployed
resources are tagged with the key ``'aws-chalice'`` whose value is
``'version={chalice-version}:stage={stage-name}:app={app-name}'``.  Currently
only the following chalice deployed resources are tagged: Lambda functions.

``xray``
~~~~~~~~

A boolean that turns on AWS XRay's Active tracing configuration.
This will turn on XRay for both Lambda functions and API Gateway stages.


``log_retention_in_days``
~~~~~~~~~~~~~~~~~~~~~~~~~

An integer value that indicates the retention time to be applied to lambda
function log groups. Only certain values are valid, see the `AWS CloudWatch
Logs docs
<https://docs.aws.amazon.com/AmazonCloudWatchLogs/latest/APIReference/API_PutRetentionPolicy.html#API_PutRetentionPolicy_RequestParameters>`__

.. warning::
   If you using the `chalice package` command to generate a CloudFormation template,
   a Log Group resource will be added to your template with the configured
   ``log_retention_in_days``.  This will cause your deployment to fail
   if this Log Group resource already exists (i.e. if the associated
   Lambda function has previously been invoked which results in Lambda
   automatically created a Log Group for the function).  In order to use
   this configuration option, it should be part of the initial deployment
   of the Lambda function.

.. _lambda-config:

Lambda Specific Configuration
-----------------------------

In addition to a chalice stage, there are also some configuration values
that can be specified per Lambda function.  A chalice app can have many
stages, and a stage can have many Lambda functions.

You have the option to specify configuration for a lambda function across
all your stages, or for a lambda function in a specific stage.

To configure per lambda configuration for a specific stage, you add a
``lambda_functions`` key in your stage configuration::

  {
    "version": "2.0",
    "app_name": "app",
    "stages": {
      "dev": {
        "lambda_functions": {
          "foo": {
            "lambda_timeout": 120
          }
        }
      }
    }
  }

To specify per lambda configuration across all stages, you add
a top level ``lambda_functions`` key::

  {
    "version": "2.0",
    "app_name": "app",
    "lambda_functions": {
      "foo": {
        "lambda_timeout": 120
      }
    }
  }


Each key in the ``lambda_functions`` dictionary is the name of a Lambda
function in your app.  The value is a dictionary of configuration that
will be applied to that function.  These are the configuration options
that can be applied per function:

* ``autogen_policy``
* ``environment_variables``
* ``iam_policy_file``
* ``iam_role_arn``
* ``lambda_memory_size``
* ``lambda_timeout``
* ``layers``
* ``manage_iam_role``
* ``reserved_concurrency``
* ``security_group_ids``
* ``subnet_ids``
* ``tags``
* ``log_retention_in_days``


See the :ref:`stage-config` section above for a description
of these config options.

In general, the name of your lambda function will correspond to
the name of the function in your app.  For example:

.. code-block:: python

    @app.lambda_function()
    def foo(event, context):
        pass

To specify configuration for this function, you would use the
key of ``foo`` in the ``lambda_functions`` configuration.

There is one exception to this, which is any python function
decorated with the ``@app.route()`` decorator.  Chalice uses
a single Lambda function for all requests from API gateway,
and this name is ``api_handler``.  So if you have an app
like this:

.. code-block:: python

    @app.route('/')
    def index(): pass

    @app.route('/foo/bar')
    def other_handler(): pass

Then to specify configuration values for the underlying
lambda function, which ``index()`` and ``other_handler()`` share,
you would specify:

.. code-block:: json

   {
      "lambda_functions": {
        "api_handler": {
          "subnet_ids": ["sn-1", "sn-2"],
          "security_group_ids": ["sg-10", "sg-11"],
          "layers": ["layer-arn-1", "layer-arn-2"],
        }
      }
    }


Examples
--------

Below are examples that show how you can configure your chalice app.

Custom Domain Name
~~~~~~~~~~~~~~~~~~

Here's an example for configuring Custom domain name for
dev stage for REST API::

  {
    "version": "2.0",
    "app_name": "app",
    "stages": {
      "dev": {
        "autogen_policy": true,
        "api_gateway_stage": "dev"
        "api_gateway_custom_domain": {
          "domain_name": "api.example.com",
          "security_policy": "TLS 1.2|TLS 1.0",
          "certificate_arn": "arn:aws:acm:example.com",
          "url_prefixes": ["foo", "bar"],
          "tags": {
            "key": "tag1",
            "key1": "tag2"
          }
        },
      },
    }
  }

In this config file we're specifying ``dev`` stage for ApiGateway.
In the ``dev`` stage, chalice will automatically create ``custom domain name``
with specified ``url_prefixes`` that should contain information about
`AWS Api Mapping key`_.

If there is Websocket API ``websocket_api_custom_domain`` should be used
instead of ``api_gateway_custom_domain``.

.. _iam-role-pol-examples:

IAM Roles and Policies
~~~~~~~~~~~~~~~~~~~~~~


Here's an example for configuring IAM policies across stages::

  {
    "version": "2.0",
    "app_name": "app",
    "stages": {
      "dev": {
        "autogen_policy": true,
        "api_gateway_stage": "dev"
      },
      "beta": {
        "autogen_policy": false,
        "iam_policy_file": "beta-app-policy.json"
      },
      "prod": {
        "manage_iam_role": false,
        "iam_role_arn": "arn:aws:iam::...:role/prod-role"
      }
    }
  }

In this config file we're specifying three stages, ``dev``, ``beta``,
and ``prod``.  In the ``dev`` stage, chalice will automatically
generate an IAM policy based on analyzing the application source code.
For the ``beta`` stage, chalice will load the
``.chalice/beta-app-policy.json`` file and use it as the policy to
associate with the IAM role for that stage.  In the ``prod`` stage,
chalice won't modify any IAM roles.  It will just set the IAM role
for the Lambda function to be ``arn:aws:iam::...:role/prod-role``.

Here's an example that shows config precedence::


  {
    "version": "2.0",
    "app_name": "app",
    "api_gateway_stage": "api",
    "stages": {
      "dev": {
      },
      "beta": {
      },
      "prod": {
        "api_gateway_stage": "prod",
        "manage_iam_role": false,
        "iam_role_arn": "arn:aws:iam::...:role/prod-role"
      }
    }
  }

In this config file, both the ``dev`` and ``beta`` stage will
have an API gateway stage name of ``api`` because they will
default to the top level ``api_gateway_stage`` key.
However, the ``prod`` stage will have an API gateway stage
name of ``prod`` because the ``api_gateway_stage`` is specified
in ``{"stages": {"prod": ...}}`` mapping.



.. _env-var-examples:

Environment Variables
~~~~~~~~~~~~~~~~~~~~~


In the following example, environment variables are specified
both as top level keys as well as per stage.  This allows us to
provide environment variables that all stages should have as well
as stage specific environment variables::


  {
    "version": "2.0",
    "app_name": "app",
    "environment_variables": {
      "SHARED_CONFIG": "foo",
      "OTHER_CONFIG": "from-top"
    },
    "stages": {
      "dev": {
        "environment_variables": {
          "TABLE_NAME": "dev-table",
          "OTHER_CONFIG": "dev-value"
        }
      },
      "prod": {
        "environment_variables": {
          "TABLE_NAME": "prod-table",
          "OTHER_CONFIG": "prod-value"
        }
      }
    }
  }

For the above config, the ``dev`` stage will have the
following environment variables set::

  {
    "SHARED_CONFIG": "foo",
    "TABLE_NAME": "dev-table",
    "OTHER_CONFIG": "dev-value",
  }

The ``prod`` stage will have these environment variables set::

  {
    "SHARED_CONFIG": "foo",
    "TABLE_NAME": "prod-table",
    "OTHER_CONFIG": "prod-value",
  }


Per Lambda Examples
~~~~~~~~~~~~~~~~~~~

Suppose we had the following chalice app:

.. code-block:: python

    from chalice import Chalice

    app = Chalice(app_name='demo')

    @app.lambda_function()
    def foo(event, context):
        pass

    @app.lambda_function()
    def bar(event, context):
        pass


Given these two functions, we'd like to configure the functions
as follows:

* Both functions should have an environment variable ``OWNER`` with value
  ``dev-team``.
* The ``foo`` function should have an autogenerated IAM policy managed by
  chalice.
* The ``foo`` function should be run in a VPC with subnet ids ``sn-1`` and
  ``sn-2``, with security groups ``sg-10`` and ``sg-11``.  Chalice should
  also automatically configure the IAM policy with permissions to modify
  EC2 network interfaces.
* The ``foo`` function should have two connected layers as ``layer-arn-1`` and
  ``layer-arn-2``. Chalice should automatically configure the IAM policy.
* The ``bar`` function should use a pre-existing IAM role that was created
  outside of chalice.  Chalice should not perform an IAM role management for
  the ``bar`` function.
* The ``bar`` function should have an environment variable ``TABLE_NAME`` with
  value ``mytable``.

We can accomplish all this with this config file::

  {
    "stages": {
      "dev": {
        "environment_variables": {
          "OWNER": "dev-team"
        }
        "api_gateway_stage": "api",
        "lambda_functions": {
          "foo": {
            "subnet_ids": ["sn-1", "sn-2"],
            "security_group_ids": ["sg-10", "sg-11"],
            "layers": ["layer-arn-1", "layer-arn-2"],
          },
          "bar": {
            "manage_iam_role": false,
            "iam_role_arn": "arn:aws:iam::my-role-name",
            "environment_variables": {"TABLE_NAME": "mytable"}
          }
        }
      }
    },
    "version": "2.0",
    "app_name": "demo"
  }

.. _AWS Lambda VPC documentation: https://docs.aws.amazon.com/lambda/latest/dg/vpc.html#vpc-configuring
.. _AWS Documentation on managing concurrency: https://docs.aws.amazon.com/lambda/latest/dg/concurrent-executions.html
.. _AWS Lambda Layers Configuration: https://docs.aws.amazon.com/lambda/latest/dg/configuration-layers.html
.. _AWS Custom Domain names setup: https://docs.aws.amazon.com/apigateway/latest/developerguide/how-to-custom-domains.html
.. _AWS Api Mapping key:    https://docs.aws.amazon.com/apigatewayv2/latest/api-reference/domainnames-domainname-apimappings.html

---

### Source Topics Domainname (`docs/source/topics/domainname.rst`)

Custom Domain Names
===================

Custom domain names are simpler and more intuitive URLs
that you can provide to your API users.
With custom domain names, you can set up your API's hostname,
and choose a base path to map the alternative URL to your API.

You must have an AWS managed certificate created or imported through
AWS Certificate Manager (ACM) in order to configure a custom domain name
for REST and WebSocket APIs.
See `Get certificate in AWS Certificate Manager <https://docs.aws.amazon.com/apigateway/latest/developerguide/how-to-custom-domains-prerequisites.html>`__
for more information.

Custom domain name can be configured per Chalice stage.

.. note::
    This document describes the configuration option and process
    needed to configure a custom domain with your Chalice application.
    If you'd like a step-by-step example that walks you through configuring
    a custom domain for a Chalice app using Amazon Route53 and ACM, see
    the :doc:`../tutorials/customdomain` tutorial.

There are two steps to configuring a custom domain name.  First you must
configure your Chalice app such that it creates the necessary resources
and configuration when provisioning your REST or WebSocket APIs.  This
is explained in the next two sections below.
Then you must configure your DNS configuration to point your custom domain
name to the domain name created by API Gateway.  This is explained in the
:ref:`dns-last-config` section below.

Configure custom domain name for REST API
-----------------------------------------

To create custom domain name for REST API, add the
``api_gateway_custom_domain`` configuration option to your
``.chalice/config.json`` file.  You must specify the ``certificate_arn``,
which is the ARN of your ACM certificate associated with your domain as
well as your ``domain_name``.  The remaining fields are optional and
may be omitted.  By default TLS 1.2 is used for your endpoint unless
otherwise specified.

Below is an example of all the configuration options you can specify
when configuring a custom domain.  They are explained in the
:ref:`custom-domain-config-options` section of the :doc:`configfile`
documentation.

.. code-block:: json

    {
        "stages": {
            "dev": {
                "api_gateway_stage": "api",
                "api_gateway_custom_domain": {
                    "domain_name": "api.example.com",
                    "tls_version": "TLS_1_2|TLS_1_0",
                    "certificate_arn": "arn:aws:acm:example",
                    "url_prefix": "foo",
                    "tags": {
                        "key": "tag1",
                        "key1": "tag2"
                    }
                }
            }
        }
    }

Configure custom domain name for WebSocket
------------------------------------------

To create custom domain name for WebSocket API, add the
``websocket_api_custom_domain`` configuration option to your
``.chalice/config.json`` file.

Below is an example of all the configuration options you can specify when
configuring a custom domain for a WebSocket API.  They are explained in the
:ref:`custom-domain-ws-config-options` section of the :doc:`configfile`
documentation.

.. code-block:: json

    {
        "stages": {
            "dev": {
                "api_gateway_stage": "api",
                "websocket_api_custom_domain": {
                    "domain_name": "api.example.com",
                    "tls_version": "TLS_1_2|TLS_1_0",
                    "certificate_arn": "arn:aws:acm:example",
                    "url_prefix": "foo",
                    "tags": {
                        "key": "tag1",
                        "key1": "tag2"
                    }
                }
            }
        }
    }


.. _dns-last-config:

DNS Configuration
-----------------

Chalice only configures your API Gateway API with the necessary resources
and configuration so a custom domain can be used.  It does not alter any
existing DNS records you have associated with your domain name.  After you've
deployed your Chalice app with the configuration options described above,
you'll need to modify your DNS records to point to your API Gateway API
using the web interface or API of your domain registrar associated with
your domain name.  When you run ``chalice deploy`` with a custom domain
configured, there will be two new fields in the output::

    $ chalice deploy
    Creating deployment package.
    Updating policy for IAM role: customdomain-dev
    Updating lambda function: customdomain-dev
    Updating rest API
    Creating custom domain name: api.chalice-demo-app.com
    Creating api mapping: /
    Resources deployed:
      - Lambda ARN: arn:aws:lambda:us-west-2:0123456789:function:customdomain-dev
      - Rest API URL: https://qxea58abcd.execute-api.us-west-2.amazonaws.com/api/
      - Custom domain name:
          HostedZoneId: Z1UJRXOUMOOFQ8
          AliasDomainName: d-6vj4cynstd.execute-api.us-west-2.amazonaws.com

If you're using Route53 to manage your hosted zone, you'll need to create
an Alias record using the ``HostedZoneId`` and ``AliasDomainName`` specified
in the output of ``chalice deploy``.  If you're using a third party domain
registrar, you'll need to create a CNAME record to the ``AliasDomainName``.
If you'd like a step-by-step example of how to do this with Route53, see
the :doc:`../tutorials/customdomain` tutorial.

---

### Source Topics Events (`docs/source/topics/events.rst`)

====================
Lambda Event Sources
====================


.. _scheduled-events:

Scheduled Events
================

Chalice has support for `scheduled events`_.  This feature allows you to
periodically invoke a lambda function based on some regular schedule.  You can
specify a fixed rate or a cron expression.

To create a scheduled event in chalice, you use the ``@app.schedule()``
decorator.  Let's look at an example.


.. code-block:: python

    app = chalice.Chalice(app_name='foo')

    @app.schedule('rate(1 hour)')
    def every_hour(event):
        print(event.to_dict())


In this example, we have a single lambda function that we want automatically
invoked every hour.  When you run ``chalice deploy`` Chalice will create a
lambda function as well as the necessary CloudWatch events/rules such that the
``every_hour`` function is invoked every hour.

The :meth:`Chalice.schedule` method accepts either a string or an
instance of :class:`Rate` or :class:`Cron`.  For example:

.. code-block:: python

    app = chalice.Chalice(app_name='foo')

    @app.schedule(Rate(1, unit=Rate.HOURS))
    def every_hour(event):
        print(event.to_dict())


The function you decorate must accept a single argument,
which will be of type :class:`CloudWatchEvent`.

You can use the ``schedule()`` decorator multiple times
in your chalice app.  Each ``schedule()`` decorator will
result in a new lambda function and associated CloudWatch
event rule.  For example:


.. code-block:: python

    app = chalice.Chalice(app_name='foo')

    @app.schedule(Rate(1, unit=Rate.HOURS))
    def every_hour(event):
        print(event.to_dict())


    @app.schedule(Rate(2, unit=Rate.HOURS))
    def every_two_hours(event):
        print(event.to_dict())


In the app above, chalice will create two lambda functions,
and configure ``every_hour`` to be invoked once an hour,
and ``every_two_hours`` to be invoked once every two hours.


.. _cwe-events:

CloudWatch Events
==================

You can configure a lambda function to subscribe to
any `CloudWatch Event <https://amzn.to/2SCgWA6>`__.

To subscribe to a CloudWatch Event in chalice, you use the
``@app.on_cw_event()`` decorator.  Let's look at an example.


.. code-block:: python

    app = chalice.Chalice(app_name='foo')

    @app.on_cw_event({"source": ["aws.codecommit"]})
    def on_code_commit_changes(event):
        print(event.to_dict())

In this example, we have a single lambda function that we subscribe to all
events from the AWS Code Commit service. The first parameter to the decorator
is the event pattern that will be used to filter the events sent to the function.

See the `CloudWatch Event pattern docs <https://amzn.to/2OlqZso>`__
for additional syntax and examples.

The function you decorate must accept a single argument,
which will be of type :class:`CloudWatchEvent`.

.. _s3-events:

S3 Events
=========

You can configure a lambda function to be invoked whenever
certain events happen in an S3 bucket.  This uses the
`event notifications`_ feature provided by Amazon S3.

To configure this, you just tell Chalice the name of an existing
S3 bucket, along with what events should trigger the lambda function.
This is done with the :meth:`Chalice.on_s3_event` decorator.

Here's an example:

.. code-block:: python

    from chalice import Chalice

    app = chalice.Chalice(app_name='s3eventdemo')
    app.debug = True

    @app.on_s3_event(bucket='mybucket-name',
                     events=['s3:ObjectCreated:*'])
    def handle_s3_event(event):
        app.log.debug("Received event for bucket: %s, key: %s",
                      event.bucket, event.key)

In this example above, Chalice connects the S3 bucket to the
``handle_s3_event`` Lambda function such that whenever an object is uploaded
to the ``mybucket-name`` bucket, the Lambda function will be invoked.
This example also uses the ``.bucket`` and ``.key`` attributes from the
``event`` parameter, which is of type :class:`S3Event`.

It will automatically create the appropriate S3 notification configuration
as needed.  Chalice will also leave any existing notification configuration
on the ``mybucket-name`` untouched.  It will only merge in the additional
configuration needed for the ``handle_s3_event`` Lambda function.


.. warning::

  This feature only works when using `chalice deploy`.  Because you
  configure the lambda function with the name of an existing S3 bucket,
  it is not possible to describe this using a CloudFormation/SAM template.
  The ``chalice package`` command will fail.  You will eventually be able
  to request that chalice create a bucket for you, which will support
  the ``chalice package`` command.

The function you decorate must accept a single argument,
which will be of type :class:`S3Event`.

.. _sns-events:

SNS Events
==========

You can configure a lambda function to be automatically invoked whenever
something publishes to an SNS topic.  Chalice will automatically handle
creating the lambda function, subscribing the lambda function to the
SNS topic, and modifying the lambda function policy to allow SNS to invoke
the function.

To configure this, you just need the name of an existing SNS topic you'd
like to subscribe to.  The SNS topic must already exist.

Below is an example of how to set this up.  The example uses boto3 to
create the SNS topic.  If you don't have boto3 installed in your virtual
environment, be sure to install it with::

    $ pip install boto3

First, we'll create an SNS topic using boto3.

::

    $ python
    >>> import boto3
    >>> sns = boto3.client('sns')
    >>> sns.create_topic(Name='my-demo-topic')
    {'TopicArn': 'arn:aws:sns:us-west-2:12345:my-demo-topic',
     'ResponseMetadata': {}}

Next, we'll create our chalice app::

    $ chalice new-project chalice-demo-sns
    $ cd chalice-demo-sns/

We'll update the ``app.py`` file to use the ``on_sns_message`` decorator:

.. code-block:: python

    from chalice import Chalice

    app = Chalice(app_name='chalice-sns-demo')
    app.debug = True

    @app.on_sns_message(topic='my-demo-topic')
    def handle_sns_message(event):
        app.log.debug("Received message with subject: %s, message: %s",
                      event.subject, event.message)

We can now deploy our chalice app::

    $ chalice deploy
    Creating deployment package.
    Creating IAM role: chalice-demo-sns-dev
    Creating lambda function: chalice-demo-sns-dev-handle_sns_message
    Subscribing chalice-demo-sns-dev-handle_sns_message to SNS topic my-demo-topic
    Resources deployed:
      - Lambda ARN: arn:aws:lambda:us-west-2:123:function:...

And now we can test our app by publishing a few SNS messages to our topic.
We'll do this using boto3.  In the example below, we're using ``list_topics()``
to find the ARN associated with our topic name before calling the ``publish()``
method.

::

    $ python
    >>> import boto3
    >>> sns = boto3.client('sns')
    >>> topic_arn = [t['TopicArn'] for t in sns.list_topics()['Topics']
    ...              if t['TopicArn'].endswith(':my-demo-topic')][0]
    >>> sns.publish(Message='TestMessage1', Subject='TestSubject1',
    ...             TopicArn=topic_arn)
    {'MessageId': '12345', 'ResponseMetadata': {}}
    >>> sns.publish(Message='TestMessage2', Subject='TestSubject2',
    ...             TopicArn=topic_arn)
    {'MessageId': '54321', 'ResponseMetadata': {}}

To verify our function was called correctly, we can use the ``chalice logs``
command::

    $ chalice logs -n handle_sns_message
    2018-06-28 17:49:30.513000 547e0f chalice-demo-sns - DEBUG - Received message with subject: TestSubject1, message: TestMessage1
    2018-06-28 17:49:40.391000 547e0f chalice-demo-sns - DEBUG - Received message with subject: TestSubject2, message: TestMessage2

In this example we used the SNS topic name to register our handler, but you can
also use the topic arn. This can be useful if your topic is in another region
or account.


.. _sqs-events:

SQS Events
==========

You can configure a lambda function to be invoked whenever messages are
available on an SQS queue.  To configure this, use the
:meth:`Chalice.on_sqs_message` decorator and provide the name of the SQS queue
and an optional batch size.

The message visibility timeout of your SQS queue must be greater than or
equal to the lambda timeout.  The default message visibility timeout
when you create an SQS queue is 30 seconds, and the default timeout
for a Lambda function is 60 seconds, so you'll need to modify one of these
values in order to successfully connect an SQS queue to a Lambda function.

You can check the visibility timeout of your queue using the
``GetQueueAttributes`` API call.  Using the
`AWS CLI <https://docs.aws.amazon.com/cli/latest/reference/sqs/get-queue-attributes.html>`__,
you can run this command to check the value::

  $ aws sqs get-queue-attributes \
      --queue-url https://us-west-2.queue.amazonaws.com/1/testq \
      --attribute-names VisibilityTimeout
  {
      "Attributes": {
          "VisibilityTimeout": "30"
      }
  }

You can set the visibility timeout of your SQS queue using the
``SetQueueAttributes`` API call.  Again using the AWS CLI you can
run this command::

  $ aws sqs set-queue-attributes \
      --queue-url https://us-west-2.queue.amazonaws.com/1/testq \
      --attributes VisibilityTimeout=60

If you would prefer to change the timeout of your lambda function instead,
you can specify this timeout value using the ``lambda_timeout`` config key
if your ``.chalice/config.json`` file.
See :ref:`lambda-config` for a list of all supported lambda configuration
values in chalice.  In this example below, we're setting the timeout
of our ``handle_sqs_message`` lambda function to 30 seconds::

  $ cat .chalice/config.json
  {
    "stages": {
      "dev": {
        "lambda_functions": {
          "handle_sqs_message": {
            "lambda_timeout": 30
          }
        }
      }
    },
    "version": "2.0",
    "app_name": "chalice-sqs-demo"
  }


In this example below, we're connecting the ``handle_sqs_message`` lambda
function to the ``my-queue`` SQS queue.  Note that we are specifying the
queue name, not the queue URL or queue ARN.  If you are connecting your
lambda function to a FIFO queue, make sure you specify the ``.fifo``
suffix, e.g. ``my-queue.fifo``.

.. code-block:: python

    from chalice import Chalice

    app = chalice.Chalice(app_name='chalice-sqs-demo')
    app.debug = True

    @app.on_sqs_message(queue='my-queue', batch_size=1)
    def handle_sqs_message(event):
        for record in event:
            app.log.debug("Received message with contents: %s", record.body)


Whenever a message is sent to the SQS queue our function will be automatically
invoked.  The function argument is an :class:`SQSEvent` object, and each
``record`` in the example above is of type :class:`SQSRecord`.  Lambda takes
care of automatically scaling your function as needed.  See `Understanding
Scaling Behavior`_ for more information on how Lambda scaling works.

If your lambda function completes without raising an exception, then
Lambda will automatically delete all the messages associated with the
:class:`SQSEvent`.  You don't need to manually call ``sqs.delete_message()``
in your lambda function.  If your lambda function raises an exception, then
Lambda won't delete any messages, and once the visibility timeout has been
reached, the messages will be available again in the SQS queue.  Note that
if you are using a batch size of more than one, the entire batch succeeds or
fails.  This means that it is possible for your lambda function to see
a message multiple times, even if it's successfully processed the message
previously.  There are a few options available to mitigate this:

* Use a batch size of 1 (the default value).
* Use a separate data store to check if you've already processed an SQS
  message.  You can use services such as Amazon DynamoDB or Amazon ElastiCache.
* Manually call ``sqs.delete_message()`` in your Lambda function once you've
  successfully processed a message.

For more information on Lambda and SQS,
see the `AWS documentation`_.

.. _kinesis-events:

Kinesis Events
==============

You can configure a Lambda function to be invoked whenever messages are
published to an Amazon Kinesis data stream.  To configure this, use the
:meth:`Chalice.on_kinesis_record` decorator and provide the name of the
Kinesis stream.

The :class:`KinesisEvent` that is passed in as the ``event`` argument
to the event handler is also iterable.  This allows you to iterate over
all the records in the event.  Additionally, each record has a ``.data``
attribute that is automatically base64 decoded for you.

Here's an example:

.. code-block:: python

    from chalice import Chalice

    app = chalice.Chalice(app_name='kinesiseventdemo')
    app.debug = True

    @app.on_kinesis_record(stream='mystream')
    def handle_kinesis_message(event):
        for record in event:
            # The .data attribute is automatically base64 decoded for you.
            app.log.debug("Received message with contents: %s", record.data)

For more information on using Kinesis and Lambda, see
`Using AWS Lambda with Amazon Kinesis <https://docs.aws.amazon.com/lambda/latest/dg/with-kinesis.html>`__.

.. _dynamodb-events:

DynamoDB Events
===============

You can configure a Lambda function to be invoked whenever messages are
published to an Amazon DynamoDB stream.  To configure this, use the
:meth:`Chalice.on_dynamodb_record` decorator and provide the name of the
DynamoDB stream ARN.

.. note::
   Other event handlers such as :meth:`Chalice.on_kinesis_record`,
   :meth:`Chalice.on_sqs_message`, and :meth:`Chalice.on_sns_message`
   only require the resource name and not the full ARN.  In the case
   of DynamoDB streams, there are auto-generated portions of the
   stream ARN that cannot be computed based on the resource name.  This
   is why Chalice requires that full stream ARN when configuring
   a DynamoDB stream handler.

The :class:`DynamoDBEvent` that is passed in as the ``event`` argument
to the event handler is also iterable.  This allows you to iterate over
all the records in the event.

Here's an example:

.. code-block:: python

    from chalice import Chalice

    app = chalice.Chalice(app_name='ddb-event-demo')
    app.debug = True

    @app.on_dynamodb_record(stream_arn='arn:aws:dynamodb:.../stream/2020')
    def handle_ddb_message(event):
        for record in event:
            app.log.debug("New: %s", record.new_image)


For more information on using Lambda and DynamoDB, see
`Using AWS Lambda with Amazon DynamoDB <https://docs.aws.amazon.com/lambda/latest/dg/with-ddb.html>`__.


.. _event notifications: https://docs.aws.amazon.com/AmazonS3/latest/dev/NotificationHowTo.html
.. _AWS documentation: https://docs.aws.amazon.com/lambda/latest/dg/with-sqs.html
.. _Understanding Scaling Behavior: https://docs.aws.amazon.com/lambda/latest/dg/scaling.html

---

### Source Topics Experimental (`docs/source/topics/experimental.rst`)

Experimental APIs
=================

Chalice maintains backwards compatibility for all features that appear in this
documentation.  Any Chalice application using version 1.x will continue to work
for all future versions of 1.x.

We also believe that Chalice has a lot of potential for new ideas and APIs,
many of which will take several iterations to get right.  We may implement a
new idea and need to make changes based on customer usage and feedback.  This
may include backwards incompatible changes all the way up to the removal of
a feature.

To accommodate these new features, Chalice has support for experimental APIs,
which are features that are added to Chalice on a provisional basis.  Because
these features may include backwards incompatible changes, you must explicitly
opt-in to using these features.  This makes it clear that you are using an
experimental feature that may change.

Opting-in to Experimental APIs
------------------------------

Each experimental feature in chalice has a name associated with it.  To opt-in
to an experimental API, you must have the feature name to the
``experimental_feature_flags`` attribute on your ``app`` object.
This attribute's type is a set of strings.

.. code-block:: python

    from chalice import Chalice

    app = Chalice('myapp')
    app.experimental_feature_flags.update([
        'MYFEATURE1',
        'MYFEATURE2',
    ])


If you use an experimental API without opting-in, you will receive
a message whenever you run a Chalice CLI command.  The error message
tells you which feature flags you need to add::

    $ chalice deploy
    You are using experimental features without explicitly opting in.
    Experimental features do not guarantee backwards compatibility and may be removed in the future.
    If you still like to use these experimental features, you can opt-in by adding this to your app.py file:

    app.experimental_feature_flags.update([
        'FEATURE_FLAG_NAME'
    ])


    See https://aws.github.io/chalice/topics/experimental.rst for more details.

The feature flag only happens when running CLI commands.  There are no runtime
checks for experimental features once your application is deployed.


List of Experimental APIs
-------------------------

In the table below, the "Feature Flag Name" column is the value you
must add to the ``app.experimental_feature_flags`` attribute.
The status of an experimental API can be:

* ``Trial`` - You must explicitly opt-in to use this feature.
* ``Accepted`` - This feature has graduated from an experimental
  feature to a fully supported, backwards compatible feature in Chalice.
  Accepted features still appear in the table for auditing purposes.
* ``Rejected`` - This feature has been removed.


.. list-table:: Experimental APIs
  :header-rows: 1

  * - Feature
    - Feature Flag Name
    - Version Added
    - Version Finalized
    - Status
    - GitHub Issue(s)
  * - :doc:`blueprints`
    - ``BLUEPRINTS``
    - 1.7.0
    - 1.15.0
    - Accepted
    - `#1023 <https://github.com/aws/chalice/pull/1023>`__,
      `#651 <https://github.com/aws/chalice/pull/651>`__
  * - :doc:`websockets`
    - ``WEBSOCKETS``
    - 1.10.0
    - n/a
    - Trial
    - `#1041 <https://github.com/aws/chalice/pull/1041>`__,
      `#1017 <https://github.com/aws/chalice/issues/1017>`__


See the `original discussion <https://github.com/aws/chalice/issues/1019>`__
for more background information and alternative proposals.

---

### Source Topics Index (`docs/source/topics/index.rst`)

Topics
======

.. toctree::
   :maxdepth: 2

   routing
   views
   configfile
   multifile
   logging
   sdks
   stages
   packaging
   pyversion
   cfn
   tf
   authorizers
   events
   purelambda
   blueprints
   websockets
   cd
   domainname
   experimental
   testing
   middleware

---

### Source Topics Logging (`docs/source/topics/logging.rst`)

Logging
=======

You have several options for logging in your
application.  You can use any of the options
available to lambda functions as outlined
in the
`AWS Lambda Docs <https://docs.aws.amazon.com/lambda/latest/dg/python-logging.html>`_.
The simplest option is to just use print statements.
Anything you print will be accessible in cloudwatch logs
as well as in the output of the ``chalice logs`` command.

In addition to using the stdlib ``logging`` module directly,
the framework offers a preconfigured logger designed to work
nicely with Lambda.  This is offered purely as a convenience,
you can use ``print`` or the ``logging`` module directly if you prefer.

You can access this logger via the ``app.log``
attribute, which is a logger specifically for your application.
This attribute is an instance of ``logging.getLogger(your_app_name_)``
that's been preconfigured with reasonable defaults:

* StreamHandler associated with ``sys.stdout``.
* Log level set to ``logging.ERROR`` by default.
  You can also manually set the logging level by setting
  ``app.log.setLevel(logging.DEBUG)``.
* A logging formatter that displays the app name, level name,
  and message.


Examples
--------

In the following application, we're using the application logger
to emit two log messages, one at ``DEBUG`` and one at the ``ERROR``
level:

.. code-block:: python

    from chalice import Chalice

    app = Chalice(app_name='demolog')


    @app.route('/')
    def index():
        app.log.debug("This is a debug statement")
        app.log.error("This is an error statement")
        return {'hello': 'world'}


If we make a request to this endpoint, and then look at
``chalice logs`` we'll see the following log message::

    2016-11-06 20:24:25.490000 9d2a92 demolog - ERROR - This is an error statement

As you can see, only the ``ERROR`` level log is emitted because
the default log level is ``ERROR``.  Also note the log message formatting.
This is the default format that's been automatically configured.
We can make a change to set our log level to debug:


.. code-block:: python

    import logging

    from chalice import Chalice

    app = Chalice(app_name='demolog')
    # Enable DEBUG logs.
    app.log.setLevel(logging.DEBUG)


    @app.route('/')
    def index():
        app.log.debug("This is a debug statement")
        app.log.error("This is an error statement")
        return {'hello': 'world'}

Now if we make a request to the ``/`` URL and look at the
output of ``chalice logs``, we'll see the following log message::

    2016-11-07 12:29:15.714 431786 demolog - DEBUG - This is a debug statement
    2016-11-07 12:29:15.714 431786 demolog - ERROR - This is an error statement


As you can see here, both the debug and error log message are shown.

You can use the ``-n/--name`` option to view the logs for a specific lambda
function.  By default, the logs for the API handler lambda function are shown.
This corresponds to any log statements made within an ``@app.route()`` call.
The name option is the logical name of the lambda function.  This is the
name of the python function by default, or whatever name you provided
as the ``name`` kwarg to the ``@app.lambda_function()`` call.  For example,
given this app:

.. code-block:: python

    from chalice import Chalice

    app = Chalice(app_name='multilog')


    @app.lambda_function()
    def foo(event, context):
        app.log.debug("Invoking from function foo")
        return {'hello': 'world'}


    @app.lambda_function(name='MyFunction)
    def bar(event, context):
        incr_counter()
        app.log.debug("Invoking from function bar")
        return {'hello': 'world'}


You can retrieve logs for the above function by running::

    $ chalice logs --name foo
    $ chalice logs --name MyFunction

---

### Source Topics Middleware (`docs/source/topics/middleware.rst`)

==========
Middleware
==========

Chalice provides numerous features and capabilities right out of the box, but
there are often times where you'll want to customize the behavior of Chalice
for your specific needs.  You can accomplish this by using middleware, which
lets you alter the request and response lifecycle.  Chalice middleware
is a function that you register as part of your application that will
automatically be invoked by Chalice whenever your Lambda functions are called.

Below is an example of Chalice middleware:

.. code-block:: python

    from chalice import Chalice

    app = Chalice(app_name='demo-middleware')

    @app.middleware('all')
    def my_middleware(event, get_response):
        app.log.info("Before calling my main Lambda function.")
        response = get_response(event)
        app.log.info("After calling my main Lambda function.")
        return response

    @app.route('/')
    def index():
        return {'hello': 'world'}

    @app.on_sns_message('mytopic')
    def sns_handler(event):
        pass

In this example, our middleware is emitting a log message before and after
our Lambda function has been invoked.  Because we specified an event type of
``all``, the ``my_middleware`` function will be called when either our REST
API's ``index()`` or our ``sns_handler()`` Lambda function is invoked.


Writing Middleware
==================

Middleware must adhere to these requirements:

* Must be a callable object that accepts two parameters, an ``event``, and
  a ``get_response`` function.  The ``event`` type will depend on what type
  of handlers the middleware has been registered for (see "Registering
  Middleware" below).
* Must return a response.  This will be the response that gets returned back
  to the caller.
* In order to invoke the next middleware in the chain and eventually call the
  actual Lambda handler, it must invoke ``get_response(event)``.
* Middleware can short-circuit the request by returning its own response.
  It does not have to invoke ``get_response(event)`` if not needed.  The
  response type should match the response type of the underlying Lambda
  handler.

Below is the simplest middleware in Chalice that does nothing:

.. code-block:: python

   @app.middleware('all')
   def noop_middleware(event, get_response):
       # The `event` type will depend on what type of
       # Lambda handler is being invoked.
       return get_response(event)


Error Handling
--------------

With the exception of middleware for REST APIs, all middleware follow the same
error handling strategy.  Any exceptions from a Lambda handler are propagated
back to each middleware.  You can then catch these exceptions in your
middleware and process them as needed.  For example:

.. code-block:: python

   @app.middleware('all')
   def handle_errors(event, get_response):
       try:
           return get_response(event)
       except MyCustomError as e:
           # We don't want MyCustomError to propagate, instead
           # we'll convert this to an error response dictionary.
           return {"Error": e.__class__.__name__,
                   "Message": str(e)}

   @app.lambda_function()
   def noop_middleware(event, context):
       raise MyCustomError("Raising an error.")


If an exception is raised in a Lambda handler and no middleware catches the
exception, the exception will be returned back to the client that invoked
the Lambda function.

Rest APIs
~~~~~~~~~

Rest APIs have special error processing for backwards compatibility purposes.
If a chalice view function (decorated via ``@app.route``) raises an exception
Chalice will automatically catch this exception and convert to a ``Response``
object with an appropriately set status code (see :ref:`view-error-handling`).
As a result, middleware for Rest APIs won't see exceptions propagate, they will
instead see a `Response` object as a result of calling ``get_response(event)``.

In the case where you want to allow an exception to propagate out of a view
function, you can raise a ``chalice.ChaliceUnhandledError`` exception.
For example:

.. code-block:: python

   from chalice import ChaliceUnhandledError

   @app.middleware('all')
   def handle_errors(event, get_response):
       try:
           return get_response(event)
       except ChaliceUnhandledError as e:
           return Response(status_code=500, body=str(e),
                           headers={'Content-Type': 'text/plain'})

   @app.route('/')
   def index():
       # The handle_errors middleware will never see this exception.
       # This will automatically be converted to a ``Response`` object
       # with a status code of ``500``.
       raise MyCustomError("Raising an error.")

   @app.route('/error')
   def unhandled_error():
       # The handle_errors middleware will see this exception because it's
       # of type ChaliceUnhandledError.
       raise ChaliceUnhandledError("Raising an error.")


This is useful if you want to have middleware that applies to all event types
that has consistent error handling behavior.  If a
``chalice.ChaliceUnhandledError`` error is raised and no middleware catches
and processes this error, then the standard error processing behavior will
apply (a 500 response is returned back to the user, and if debug mode
is enabled, the traceback is sent as the response body).


Registering Middleware
----------------------

In order to register middleware, you use the ``@app.middleware()`` decorator.
This function accepts a single arg that specifies what type of Lambda function
it wants to be registered for.  This allows you to apply middleware to only
specific type of event handlers, e.g. only for REST APIs, or Websockets, or
S3 event handlers.  To register middleware for all Lambda functions, you can
specify ``all``.  Below are the supported event types along with the
corresponding type of event that will be provided to the middleware:

* ``all`` - ``Any``
* ``s3`` - :class:`S3Event`
* ``sns`` - :class:`SNSEvent`
* ``sqs`` - :class:`SQSEvent`
* ``cloudwatch`` - :class:`CloudWatchEvent`
* ``scheduled`` - :class:`CloudWatchEvent`
* ``websocket`` - :class:`WebsocketEvent`
* ``http`` - :class:`Request`
* ``pure_lambda`` - :class:`LambdaFunctionEvent`

.. note::
   The ``chalice.LambdaFunctionEvent`` is the only case where the
   event type for the middleware does not match the event type of the
   corresponding Lambda handler.  For backwards compatibility reasons,
   the existing signature of the ``@app.lambda_function()`` decorator
   is preserved (it accepts an ``event`` and ``context``) whereas for
   middleware, a consistent signature is needed, which is why the
   ``chalice.LambdaFunctionEvent`` is used.

You can also use the :meth:`Chalice.register_middleware` method, which
has the same behavior as :meth:`Chalice.middleware` except you provide
the middleware function as an argument instead of decorating a function.
This is useful when you want to import third party functions and use
them as middleware.

.. code-block:: python

    import thirdparty

    app.register_middleware(thirdparty.func, 'all')

You can also use the :class:`ConvertToMiddleware` class to convert an
existing Lambda wrapper to middleware.  For example, if you had the
following logging decorator:

.. code-block:: python

    def log_invocation(func):
        def wrapper(event, context):
            logger.debug("Before lambda function.")
            response = func(event, context)
            logger.debug("After lambda function.")
        return wrapper

    @app.lambda_function()
    @log_invocation
    def myfunction(event, context):
        logger.debug("In myfunction().")


Rather than decorate every Lambda function with the ``@log_invocation``
decorator, you can instead use ``ConvertToMiddleware`` to automatically
apply this wrapper to every Lambda function in your app.

.. code-block:: python

    from chalice import ConvertToMiddleware

    app.register_middleware(ConvertToMiddleware(log_invoation))

This is also useful to integrate with existing libraries that provide
Lambda wrappers.  See :ref:`powertools-example` for a more complete
example.

Examples
========

Below are some examples of common middleware patterns.

Short Circuiting a Request
--------------------------

In this example, we want to return a 400 bad response if a specific
header is missing from a request.  Because this is HTTP specific, we only
want to register this handler for our ``http`` event type.

.. code-block:: python

   from chalice import Response

   @app.middleware('http')
   def require_header(event, get_response):
       # From the list above, because this is an ``http`` event
       # type, we know that event will be of type ``chalice.Request``.
       if 'X-Custom-Header' not in event.headers:
           return Response(
               status_code=400,
               body={"Error": "Missing required 'X-Custom-Header'"})
       # If the header exists then we'll defer to our normal request flow.
       return get_response(event)

Modifying a Response
--------------------

In this example, we want to measure the processing time and inject it as
a key in our Lambda response.

.. code-block:: python

   import time

   @app.middleware('pure_lambda')
   def inject_time(event, get_response):
       start = time.time()
       response = get_response(event)
       total = time.time() - start
       response.setdefault('metadata', {})['duration'] = total
       return response


.. _powertools-example:

Integrating with AWS Lambda Powertools
--------------------------------------

`AWS Lambda Powertools
<https://docs.powertools.aws.dev/lambda/python/latest/>`__ is a suite of
utilities for AWS Lambda functions that makes tracing with AWS X-Ray,
structured logging and creating custom metrics asynchronously easier.

You can use Chalice middleware to easily integrate Lambda Powertools with
your Chalice apps.  In this example, we'll use the
`Logger
<https://docs.powertools.aws.dev/lambda/python/latest/core/logger/>`__
and `Tracer <https://docs.powertools.aws.dev/lambda/python/latest/core/tracer/>`__
and convert them to Chalice middleware so they will be automatically applied
to all Lambda functions in our application.


.. code-block:: python

    from chalice import Chalice
    from chalice.app import ConvertToMiddleware

    # First, instead of using Chalice's built in logger, we'll instead use
    # the structured logger from powertools.  In addition to automatically
    # injecting lambda context, let's say we also want to inject which
    # route is being invoked.
    from aws_lambda_powertools import Logger
    from aws_lambda_powertools import Tracer

    app = Chalice(app_name='chalice-powertools')


    logger = Logger(service=app.app_name)
    tracer = Tracer(service=app.app_name)
    # This will automatically convert any decorator on a lambda function
    # into middleware that will be connected to every lambda function
    # in our app.  This lets us avoid decoratoring every lambda function
    # with this behavior, but it also works in cases where we don't control
    # the code (e.g. registering blueprints).
    app.register_middleware(ConvertToMiddleware(logger.inject_lambda_context))
    app.register_middleware(
        ConvertToMiddleware(
            tracer.capture_lambda_handler(capture_response=False))
    )

    # Here we're writing Chalice specific middleware where for any HTTP
    # APIs, we want to add the request path to our structured log message.
    # This shows how we can combine both Chalice-style middleware with
    # other existing tools.
    @app.middleware('http')
    def inject_route_info(event, get_response):
        logger.structure_logs(append=True, request_path=event.path)
        return get_response(event)


    @app.route('/')
    def index():
        logger.info("In index() function, this will have a 'path' key.")
        return {'hello': 'world'}

    @app.route('/foo/bar')
    def foobar():
        logger.info("In foobar() function")
        return {'foo': 'bar'}


    @app.lambda_function()
    def myfunction(event, context):
        logger.info("In myfunction().")
        tracer.put_annotation(key="Status", value="SUCCESS")
        return {}


For a more detailed walkthrough of configuring Chalice with Lambda Powertools,
see
`Following serverless best practices with AWS Chalice and Lambda Powertools <https://aws.amazon.com/blogs/developer/following-serverless-best-practices-with-aws-chalice-and-lambda-powertools/>`__.

---

### Source Topics Multifile (`docs/source/topics/multifile.rst`)

Multifile Support
=================

The ``app.py`` file contains all of your view functions and route
information, but you don't have to keep all of your application
code in your ``app.py`` file.

As your application grows, you may reach out a point where you'd
prefer to structure your application in multiple files.
You can create a ``chalicelib/`` directory, and anything
in that directory is recursively included in the deployment
package.  This means that you can have files besides just
``.py`` files in ``chalicelib/``, including ``.json`` files
for config, or any kind of binary assets.

Let's take a look at a few examples.

Consider the following app directory structure layout::

    .
    ├── app.py
    ├── chalicelib
    │   └── __init__.py
    └── requirements.txt

Where ``chalicelib/__init__.py`` contains:

.. code-block:: python

    MESSAGE = 'world'


and the ``app.py`` file contains:

.. code-block:: python
    :linenos:
    :emphasize-lines: 2

    from chalice import Chalice
    from chalicelib import MESSAGE

    app = Chalice(app_name="multifile")

    @app.route("/")
    def index():
        return {"hello": MESSAGE}


Note in line 2 we're importing the ``MESSAGE`` variable from
the ``chalicelib`` package, which is a top level directory
in our project.  We've created a ``chalicelib/__init__.py``
file which turns the ``chalicelib`` directory into a python
package.

We can also use this directory to store config data.   Consider
this app structure layout::


    .
    ├── app.py
    ├── chalicelib
    │   └── config.json
    └── requirements.txt


With ``chalicelib/config.json`` containing::

    {"message": "world"}


In our ``app.py`` code, we can load and use our config file:

.. code-block:: python
    :linenos:

    import os
    import json

    from chalice import Chalice

    app = Chalice(app_name="multifile")

    filename = os.path.join(
        os.path.dirname(__file__), 'chalicelib', 'config.json')
    with open(filename) as f:
        config = json.load(f)

    @app.route("/")
    def index():
        # We can access ``config`` here if we want.
        return {"hello": config['message']}

---

### Source Topics Packaging (`docs/source/topics/packaging.rst`)

App Packaging
=============

In order to deploy your Chalice app, a zip file is created that
contains your application and all third party packages your application
requires.  This file is used by AWS Lambda and is referred
to as a deployment package.

Chalice will automatically create this deployment package for you, and offers
several features to make this easier to manage.  Chalice allows you to
clearly separate application specific modules and packages you are writing
from 3rd party package dependencies.

By default, Chalice will create a single zip file containing everything
necessary to deploy your application to Lambda.  Chalice also has
the ability to split your code into multiple files to leverage
`AWS Lambda layers <https://docs.aws.amazon.com/lambda/latest/dg/configuration-layers.html>`__.
This is discussed in the :ref:`package-3rd-party` section below.

App Directories
---------------

You have two options to structure application specific code/config:

* **app.py** - This file includes all your route information and is always
  included in the deployment package.
* **chalicelib/** - This directory (if it exists) is included in the
  deployment package.  This is where you can add config files and additional
  application modules if you prefer not to have all your app code in the
  ``app.py`` file.

See :doc:`multifile` for more info on the ``chalicelib/`` directory.  Both the
``app.py`` and the ``chalicelib/`` directory are intended for code that you
write yourself.


.. _package-3rd-party:

3rd Party Packages
------------------

When handling third party packages, you can have Chalice manage
these files as part of the deployment package of your Lambda function,
or as a separate Lambda layer that's shared by all your Lambda functions.
See the :ref:`package-examples` section for examples of how this works.

There are two options for handling python package dependencies:

* **requirements.txt** - During the packaging process, Chalice will
  install any packages it finds or can build compatible wheels for.
  Specifically all pure python packages as well as all packages that upload
  ``manylinux`` wheel files for the architecture of your Lambda functions
  (``x86_64`` by default, or ``aarch64`` when ``lambda_architecture`` is set
  to ``arm64``) will be automatically installable.
* **vendor/** - The *contents* of this directory are automatically added to
  your deployment package and its location in your Lambda functions will
  depend on whether you are using automatic Lambda Layers, described in the
  :ref:`package-auto-layers` section.

Chalice will also check for an optional ``vendor/`` directory in the project
root directory.  The contents of this directory are automatically included in
the top level of the deployment package (see :ref:`package-examples` for
specific examples).  The ``vendor/`` directory is helpful in these scenarios:

* You need to include custom packages or binary content that is not accessible
  via ``pip``.  These may be internal packages that aren't public.
* Wheel files are not available for a package you need from pip.
* A package is installable with ``requirements.txt`` but has optional c
  extensions. Chalice can build the dependency without the c extensions, but
  if you want better performance you can vendor a version that is compiled.

As a general rule of thumb, code that you write goes in either ``app.py`` or
``chalicelib/``, and dependencies are either specified in ``requirements.txt``
or placed in the ``vendor/`` directory.

.. _package-auto-layers:

Automatic Lambda Layers
~~~~~~~~~~~~~~~~~~~~~~~

By default, Chalice will create a single zip file that contains all the
code needed to run your application.

You can set the :ref:`automatic-layer-option` in your ``.chalice/config.json``
file which will instruct Chalice to create your 3rd party packages as a
separate Lambda layer.

There are several benefits to this approach:

* The layer is created once and then shared across all Lambda functions in
  your application.
* When creating or updating a Lambda function, you send the entire contents
  of the zip file that contains your app.  This is repeated for each Lambda
  function.  As a result, there is unnecessary time and network bandwidth
  used to send the same 3rd party dependencies for each Lambda function.
  When using layers, Chalice will specify the layer ARN when creating or
  updating your Lambda function, which cuts down on the time it takes
  to package and deploy your application.
* Saves storage space in Lambda.  There is a 75GB maximum size for all
  your Lambda functions.  If you're not using layers, each Lambda function
  stores its own copies of your 3rd party dependencies.

We recommend setting ``"automatic_layer": true`` in your
``.chalice/config.json`` due to these benefits.

Migrating to ``"automatic_layer": true`` is mostly backwards compatible
with one notable exception: the location of the vendor files is different.

When not using automatic layers, any files placed in ``vendor/`` will be
available in your CWD of your application.  However, when using layers,
these files will be unzipped to ``/opt/python/lib/pythonX.Y/site-packages``.
If you are using the ``vendor/`` directory to include custom built python
packages then this change is transparent as that directory is automatically
added to the python path.  However, if you are trying to read a file from
``vendor/`` directly, then this will no longer work.  For example, if you
have::

    .
    ├── app.py
    └── vendor
        └── myimage.png

And your ``app.py`` attempts to read this file:

.. code-block:: python

   @app.lambda_function()
   def handler(event, context):
       with open('myimage.png') as f:
           do_something(f)


This code will no longer work.  You have two options.  You can either place
static assets in ``chalicelib/`` or you'll have to check both directories for
your file::

'/opt/python/lib/python%s.%s/site-packages' % sys.version_info[:2]

.. code-block:: python

   @app.lambda_function()
   def handler(event, context):
       with open_vendor_file('myimage.png') as f:
           do_something(f)

   def open_vendor_file(filename):
       directories = [
           '.',
           '/opt/python/lib/python%s.%s/site-packages' % sys.version_info[:2]
       ]
       for dirname in directories:
           full_path = os.path.join(dirname, filename)
           if os.path.isfile(full_path):
               return open(full_path)

Environment Variables
---------------------

As part of the packaging and deployment process, Chalice will import your
``app.py`` file.  This will result in any top level module code being
executed.  This can sometimes have undesirable behavior.
When running any Chalice CLI commands, a ``AWS_CHALICE_CLI_MODE`` environment
variable is set.  You can check if this env var is set in your ``app.py``
if you have code that you don't want to run whenever your app is packaged
and deployed.

.. code-block:: python

   import os

   app = Chalice(app_name='testimport')

   expensive_connection = None
   if 'AWS_CHALICE_CLI_MODE' not in os.environ:
       # We're running in Lambda, we want to start up
       # our connection to our DB.
       expensive_connection = ConnectToDB()


Chalice will also set any environment variables specified in your global or
stage specific configuration whenever your app is packaged and deployed.
Per-Lambda function environment variables are not set when importing your app
(this would require importing your application for each Lambda function).  For
example, given the config below you would be able to access the ``STAGE_VAR``
environment variable but not the ``PER_FUNCTION`` variable during the
building/packaging process when Chalice imports your application.  This
can be useful if you want to move configuration or resource names out of
your app.py file.

::

  {
    "stages": {
      "dev": {
        "environment_variables": {
          "STAGE_VAR": "stage-var"
        }
        "api_gateway_stage": "api",
        "lambda_functions": {
          "foo": {
            "environment_variables": {"PER_FUNCTION": "per-function"}
          }
        }
      }
    },
    "version": "2.0",
    "app_name": "demo"
  }

This only applies to the packaging stage.  When the ``foo`` function is invoked
on Lambda, the ``PER_FUNCTION`` environment variable will be set as expected.


.. _package-examples:

Examples
--------

Suppose I have the following app structure::

    .
    ├── app.py
    ├── chalicelib
    │   ├── __init__.py
    │   └── utils.py
    ├── requirements.txt
    └── vendor
        ├── myimage.png
        └── internalpackage
            └── __init__.py

And the ``requirements.txt`` file had one requirement::

    $ cat requirements.txt
    sortedcontainers==1.5.4

With the default behavior of not using layers (``"automatic_layer": false``),
the final deployment package directory structure would look like this::

    deployment.zip
    .
    ├── app.py
    ├── chalicelib
    │   ├── __init__.py
    │   └── utils.py
    ├── myimage.png
    ├── internalpackage
    │   └── __init__.py
    └── sortedcontainers
        └── __init__.py


This directory structure is then zipped up and sent to AWS Lambda during the
deployment process.  Suppose our application had two Lambda functions.
Each Lambda function has its own copy of the application deployment package,
as shown in the architecture diagram below.

.. image:: ../img/no-auto-layer.png
   :width: 50%
   :align: center
   :alt: Default behavior with no layers.


If you are using ``"automatic_layer": true``, then two zip files will be
created. The deployment package used for the Lambda function will be::

    deployment.zip
    .
    ├── app.py
    └── chalicelib
        ├── __init__.py
        └── utils.py

And the zip file for the shared lambda layer will look like this::

    layer-deployment.zip
    .
    └── python
        └── lib
            └── python3.7
                └── site-packages
                    ├── myimage.png
                    ├── internalpackage
                    │   └── __init__.py
                    └── sortedcontainers
                        └── __init__.py


Below is an updated diagram of the same Chalice application using automatic
layers.  Both functions now share the same Lambda layer that contains the third
party packages used by the application.

.. image:: ../img/auto-layer.png
   :width: 80%
   :align: center
   :alt: Shared layer for 3rd party code.


Cryptography Example
--------------------

.. note::
   Since the original version of this example was written, cryptography has
   released version 2.0 which does have manylinux1 wheels available. This
   means if you want to use cryptography in a Chalice app all you need to
   do is add ``cryptography`` or ``cryptography>=2.0`` in your
   requirements.txt file.

   This example will use version 1.9 of Cryptography
   because it is a good example of a library with C extensions and no wheel
   files.

Below shows an example of how to use the
`cryptography 1.9 <https://pypi.org/project/cryptography/1.9/>`__ package
in a Chalice app for the ``python3.6`` lambda environment.

Suppose you are on a Mac or Windows and want to deploy a Chalice app that
depends on the ``cryptography==1.9`` package. If you simply add it to your
``requirements.txt`` file and try to deploy it with ``chalice deploy`` you will
get the following warning during deployment::

  $ cat requirements.txt
  cryptography==1.9
  $ chalice deploy
  Updating IAM policy.
  Updating lambda function...
  Creating deployment package.

  Could not install dependencies:
  cryptography==1.9
  You will have to build these yourself and vendor them in
  the chalice vendor folder.

  Your deployment will continue but may not work correctly
  if missing dependencies are not present. For more information:
  http://aws.github.io/chalice/topics/packaging.html

This happened because the ``cryptography`` version 1.9 does not have wheel
files available on PyPi, and has C extensions. Since we are not on the same
platform as AWS Lambda, the compiled C extensions Chalice built were not
compatible. To get around this we are going to leverage the ``vendor/``
directory, and build the ``cryptography`` package on a compatible linux system.

You can do this yourself by building ``cryptography`` on an Amazon Linux
instance running in EC2. All of the following commands were run inside a
``python 3.6`` virtual environment.

* Download the source first::

    $ pip download cryptography==1.9

  This will download all the requirements into the current working directory.
  The directory should have the following contents:

  * ``asn1crypto-0.22.0-py2.py3-none-any.whl``
  * ``cffi-1.10.0-cp36-cp36m-manylinux1_x86_64.whl``
  * ``cryptography-1.9.tar.gz``
  * ``idna-2.5-py2.py3-none-any.whl``
  * ``pycparser-2.17.tar.gz``
  * ``six-1.10.0-py2.py3-none-any.whl``

  This is a complete set of dependencies required for the cryptography package.
  Most of these packages have wheels that were downloaded, which means they can
  simply be put in the ``requirements.txt`` and Chalice will take care of
  downloading them. That leaves ``cryptography`` itself and ``pycparser`` as
  the only two that did not have a wheel file available for download.

* Next build the ``cryptography`` source package into a wheel file::

    $ pip wheel cryptography-1.9.tar.gz

  This will take a few seconds and build a wheel file for both ``cryptography``
  and ``pycparser``. The directory should now have two additional wheel files:

  * ``cryptography-1.9-cp36-cp36m-linux_x86_64.whl``
  * ``pycparser-2.17-py2.py3-none-any.whl``

  The ``cryptography`` wheel file has been built with a compatible
  architecture for Lambda (``linux_x86_64``) and the ``pycparser`` has been
  built for ``any`` architecture which means it can also be automatically
  packaged by Chalice if it is listed in the ``requirements.txt`` file.

* Download the ``cryptography`` wheel file from the Amazon Linux instance and
  unzip it into the ``vendor/`` directory in the root directory of your Chalice
  app.

  You should now have a project directory that looks like this::

     $ tree
     .
     ├── app.py
     ├── requirements.txt
     └── vendor
         ├── cryptography
         │   ├── ... Lots of files
         │
         └── cryptography-1.9.dist-info
             ├── DESCRIPTION.rst
             ├── METADATA
             ├── RECORD
             ├── WHEEL
             ├── entry_points.txt
             ├── metadata.json
             └── top_level.txt

  The ``requirements.txt`` file should look like this::

    $ cat requirements.txt
    cffi==1.10.0
    six==1.10.0
    asn1crypto==0.22.0
    idna==2.5
    pycparser==2.17

  In your ``app.py`` file you can now import ``cryptography``, and these
  dependencies will all get included when the ``chalice deploy`` command is
  run.

---

### Source Topics Purelambda (`docs/source/topics/purelambda.rst`)

=====================
Pure Lambda Functions
=====================


Chalice provides abstractions over AWS Lambda functions, including:

* An API handler that coordinates with API Gateway for creating rest APIs.
* A custom authorizer that allows you to integrate custom auth logic in your
  rest API.
* A scheduled event that includes managing the CloudWatch Event rules, targets,
  and permissions.

However, chalice also supports managing pure Lambda functions that don't have
any abstractions built on top.  This is useful if you want to create a Lambda
function for something that's not supported by chalice or if you just want to
create Lambda functions but don't want to manage handling dependencies and
deployments yourself.

In order to do this, you can use the :meth:`Chalice.lambda_function` decorator
to denote that this python function is a pure lambda function that should
be invoked as is, without any input or output mapping.  When you use
this function, you must provide a function that maps to the same function
signature expected by AWS Lambda as `defined here`_.

Let's look at an example.

.. code-block:: python

    app = chalice.Chalice(app_name='foo')

    @app.route('/')
    def index():
        return {'hello': 'world'}

    @app.lambda_function()
    def custom_lambda_function(event, context):
        # Anything you want here.
        return {}

    @app.lambda_function(name='MyFunction')
    def other_lambda_function(event, context):
        # Anything you want here.
        return {}

In this example, we've updated the starter hello world app with
two extra Lambda functions.  When you run ``chalice deploy`` Chalice will create
three Lambda functions.  The first lambda function is for the API handler
used by API gateway.  The second and third lambda functions will be pure lambda
functions.  These two additional lambda functions won't be hooked up to anything.
You'll need to manage connecting them to any additional AWS Resources on your
own.


.. _defined here: https://docs.aws.amazon.com/lambda/latest/dg/python-programming-model-handler-types.html

---

### Source Topics Pyversion (`docs/source/topics/pyversion.rst`)

Python Version Support
======================

Chalice supports Python 3.10 through Python 3.15.  You can see the list of
supported python versions for Lambda in their
`docs <https://docs.aws.amazon.com/lambda/latest/dg/lambda-python.html>`__.

Chalice will automatically pick which version of python to use for Lambda
based on the major/minor version of python you are using.  You don't have to
explicitly configure which version of python you want to use. For example::

    $ python --version
    Python 3.10.20
    $ chalice new-project test-versions
    $ cd test-versions
    $ chalice package test-package
    $ grep -C 3 python test-package/sam.json
        "APIHandler": {
          "Type": "AWS::Serverless::Function",
          "Properties": {
            "Runtime": "python3.10",
            "Handler": "app.app",
            "CodeUri": "./deployment.zip",
            "Events": {

    # Similarly, if we were to run "chalice deploy" we'd
    # use python3.10 for the runtime.
    $ chalice --debug deploy
    Initiating first time deployment...
    Deploying to: dev
    ...
    "Runtime":"python3.10"
    ...
    https://rest-api-id.execute-api.us-west-2.amazonaws.com/api/


In the example above, we're using python 3.10.20 so chalice automatically
selects the ``python3.10`` runtime for lambda.  If we were using python 3.15,
chalice would automatically select ``python3.15`` as the runtime.

Chalice will emit a warning if the minor version does not match a python
version supported by Lambda.  Chalice will select the closest Lambda version
in this scenario, as shown in the table below.

We strongly encourage you to develop your application using the same
major/minor version of python you plan on using on AWS Lambda.


Changing Python Runtime Versions
================================

The version of the python runtime to use in AWS Lambda can be reconfigured
whenever you deploy your chalice app.  This allows you to migrate to newer
Python versions in AWS Lambda by creating a new virtual environment that uses
python3.  For example, suppose you have an existing chalice app that uses
Python 3.10 ::

    $ python --version
    Python 3.10.20
    $ chalice deploy
    ...
    https://endpoint/api

To upgrade the application to use Python 3.15, create a python3 virtual
environment and redeploy.

::

    $ deactivate
    $ python3 -m venv /tmp/venv3
    $ source /tmp/venv3/bin/activate
    $ python --version
    Python 3.15.0
    $ chalice deploy
    ...

---

### Source Topics Routing (`docs/source/topics/routing.rst`)

Routing
=======

The :meth:`Chalice.route` method is used to construct which routes
you want to create for your API.  The concept is the same
mechanism used by `Flask <https://palletsprojects.com/p/flask/>`__ and
`bottle <http://bottlepy.org/docs/dev/index.html>`__.
You decorate a function with ``@app.route(...)``, and whenever
a user requests that URL, the function you've decorated is called.
For example, suppose you deployed this app:

.. code-block:: python

    from chalice import Chalice

    app = Chalice(app_name='helloworld')


    @app.route('/')
    def index():
        return {'view': 'index'}

    @app.route('/a')
    def a():
        return {'view': 'a'}

    @app.route('/b')
    def b():
        return {'view': 'b'}


If you go to ``https://endpoint/``, the ``index()`` function would be called.
If you went to ``https://endpoint/a`` and ``https://endpoint/b``, then the
``a()`` and ``b()`` function would be called, respectively.

.. note::

  Do not end your route paths with a trailing slash.  If you do this, the
  ``chalice deploy`` command will raise a validation error.


You can also create a route that captures part of the URL.  This captured value
will then be passed in as arguments to your view function:


.. code-block:: python

    from chalice import Chalice

    app = Chalice(app_name='helloworld')


    @app.route('/users/{name}')
    def users(name):
        return {'name': name}


If you then go to ``https://endpoint/users/james``, then the view function
will be called as: ``users('james')``.  The parameters are passed as
keyword parameters based on the name as they appear in the URL. The argument
names for the view function must match the name of the captured
argument:


.. code-block:: python

    from chalice import Chalice

    app = Chalice(app_name='helloworld')


    @app.route('/a/{first}/b/{second}')
    def users(first, second):
        return {'first': first, 'second': second}


Other Request Metadata
----------------------

The route path can only contain ``[a-zA-Z0-9._-]`` chars and curly braces for
parts of the URL you want to capture.  You do not need to model other parts of
the request you want to capture, including headers and query strings.  Within
a view function, you can introspect the current request using the
:attr:`app.current_request <Chalice.current_request>` attribute.  This also
means you cannot control the routing based on query strings or headers.
Here's an example for accessing query string data in a view function:

.. code-block:: python

    from chalice import Chalice

    app = Chalice(app_name='helloworld')


    @app.route('/users/{name}')
    def users(name):
        result = {'name': name}
        if app.current_request.query_params.get('include-greeting') == 'true':
            result['greeting'] = 'Hello, %s' % name
        return result

In the function above, if the user provides a ``?include-greeting=true`` in the
HTTP request, then an additional ``greeting`` key will be returned::

    $ http https://endpoint/api/users/bob

    {
        "name": "bob"
    }

    $ http https://endpoint/api/users/bob?include-greeting=true

    {
        "greeting": "Hello, bob",
        "name": "bob"
    }

---

### Source Topics Sdks (`docs/source/topics/sdks.rst`)

SDK Generation
==============

The ``@app.route(...)`` information you provide chalice allows
it to create corresponding routes in API Gateway.  One of the benefits of this
approach is that we can leverage API Gateway's SDK generation process.
Chalice offers a ``chalice generate-sdk`` command that will automatically
generate an SDK based on your declared routes.

.. note::
  The only supported language at this time is javascript.

Keep in mind that chalice itself does not have any logic for generating
SDKs.  The SDK generation happens service side in `API Gateway`_, the
``chalice generate-sdk`` is just a high level wrapper around that
functionality.

To generate an SDK for a chalice app, run this command from the project
directory::

    $ chalice generate-sdk /tmp/sdk

You should now have a generated javascript sdk in ``/tmp/sdk``.
API Gateway includes a ``README.md`` as part of its SDK generation
which contains details on how to use the javascript SDK.

Example
-------

Suppose we have the following chalice app:

.. code-block:: python

    from chalice import Chalice

    app = Chalice(app_name='sdktest')

    @app.route('/', cors=True)
    def index():
        return {'hello': 'world'}

    @app.route('/foo', cors=True)
    def foo():
        return {'foo': True}

    @app.route('/hello/{name}', cors=True)
    def hello_name(name):
        return {'hello': name}

    @app.route('/users/{user_id}', methods=['PUT'], cors=True)
    def update_user(user_id):
        return {"msg": "fake updated user", "userId": user_id}


Let's generate a javascript SDK and test it out in the browser.
Run the following command from the project dir::

    $ chalice generate-sdk /tmp/sdkdemo
    $ cd /tmp/sdkdemo
    $ ls -la
    -rw-r--r--   1 jamessar  r  3227 Nov 21 17:06 README.md
    -rw-r--r--   1 jamessar  r  9243 Nov 21 17:06 apigClient.js
    drwxr-xr-x   6 jamessar  r   204 Nov 21 17:06 lib

You should now be able to follow the instructions from API Gateway in the
``README.md`` file. Below is a snippet that shows how the generated
javascript SDK methods correspond to the ``@app.route()`` calls in chalice.

.. code-block:: html

  <script type="text/javascript">
    // Below are examples of how the javascript SDK methods
    // correspond to chalice @app.routes()
    var apigClient = apigClientFactory.newClient();

    // @app.route('/')
    apigClient.rootGet().then(result => {
        document.getElementById('root-get').innerHTML = JSON.stringify(result.data);
    });

    // @app.route('/foo')
    apigClient.fooGet().then(result => {
        document.getElementById('foo-get').innerHTML = JSON.stringify(result.data);
    });

    // @app.route('/hello/{name}')
    apigClient.helloNameGet({name: 'jimmy'}).then(result => {
        document.getElementById('helloname-get').innerHTML = JSON.stringify(result.data);
    });

    // @app.route('/users/{user_id}', methods=['PUT'])
    apigClient.usersUserIdPut({user_id: '123'}, 'body content').then(result => {
        document.getElementById('users-userid-put').innerHTML = JSON.stringify(result.data);
    });
  </script>





Example HTML File
~~~~~~~~~~~~~~~~~

If you want to try out the example above, you can use the following index.html
page to test:

.. code-block:: html

    <!DOCTYPE html>
    <html lang="en">
        <head>
            <title>SDK Test</title>
            <meta charset="UTF-8">
            <meta name="viewport" content="width=device-width, initial-scale=1">
            <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/skeleton/2.0.4/skeleton.min.css">
            <script type="text/javascript" src="lib/axios/dist/axios.standalone.js"></script>
            <script type="text/javascript" src="lib/CryptoJS/rollups/hmac-sha256.js"></script>
            <script type="text/javascript" src="lib/CryptoJS/rollups/sha256.js"></script>
            <script type="text/javascript" src="lib/CryptoJS/components/hmac.js"></script>
            <script type="text/javascript" src="lib/CryptoJS/components/enc-base64.js"></script>
            <script type="text/javascript" src="lib/url-template/url-template.js"></script>
            <script type="text/javascript" src="lib/apiGatewayCore/sigV4Client.js"></script>
            <script type="text/javascript" src="lib/apiGatewayCore/apiGatewayClient.js"></script>
            <script type="text/javascript" src="lib/apiGatewayCore/simpleHttpClient.js"></script>
            <script type="text/javascript" src="lib/apiGatewayCore/utils.js"></script>
            <script type="text/javascript" src="apigClient.js"></script>


            <script type="text/javascript">
              // Below are examples of how the javascript SDK methods
              // correspond to chalice @app.routes()
              var apigClient = apigClientFactory.newClient();

              // @app.route('/')
              apigClient.rootGet().then(result => {
                  document.getElementById('root-get').innerHTML = JSON.stringify(result.data);
              });

              // @app.route('/foo')
              apigClient.fooGet().then(result => {
                  document.getElementById('foo-get').innerHTML = JSON.stringify(result.data);
              });

              // @app.route('/hello/{name}')
              apigClient.helloNameGet({name: 'jimmy'}).then(result => {
                  document.getElementById('helloname-get').innerHTML = JSON.stringify(result.data);
              });

              // @app.route('/users/{user_id}', methods=['PUT'])
              apigClient.usersUserIdPut({user_id: '123'}, 'body content').then(result => {
                  document.getElementById('users-userid-put').innerHTML = JSON.stringify(result.data);
              });
            </script>
        </head>
        <body>
            <div><h5>result of rootGet()</h5><pre id="root-get"></pre></div>
            <div><h5>result of fooGet()</h5><pre id="foo-get"></pre></div>
            <div><h5>result of helloNameGet({name: 'jimmy'})</h5><pre id="helloname-get"></pre></div>
            <div><h5>result of usersUserIdPut({user_id: '123'})</h5><pre id="users-userid-put"></pre></div>
        </body>
    </html>


.. _API Gateway: https://docs.aws.amazon.com/apigateway/latest/developerguide/how-to-generate-sdk.html

---

### Source Topics Stages (`docs/source/topics/stages.rst`)

Chalice Stages
==============

Chalice has the concept of stages, which are completely
separate sets of AWS resources.  When you first create a chalice
project and run commands such as ``chalice deploy`` and ``chalice url``,
you don't have to specify any stage values or stage configuration.
This is because chalice will use a stage named ``dev`` by default.

You may eventually want to have multiple stages of your application.  A
common configuration would be to have a ``dev``, ``beta`` and ``prod``
stage.  A ``dev`` stage would be used by developers to test out new
features.  Completed features would be deployed to ``beta``, and the
``prod`` stage would be used for serving production traffic.

Chalice can help you manage this.

To create a new chalice stage, specify the ``--stage`` argument.
If the stage does not exist yet, it will be created for you::

    $ chalice deploy --stage prod

By creating a new chalice stage, a new API Gateway rest API, Lambda
function, and potentially (depending on config settings) a new IAM role
will be created for you.


Example
-------

Let's say we have a new app::

    $ chalice new-project myapp
    $ cd myapp
    $ chalice deploy
    ...
    https://mmnkdi.execute-api.us-west-2.amazonaws.com/api/

We've just created our first stage, ``dev``.  We can iterate on our
application and continue to run ``chalice deploy`` to deploy our code
to the ``dev`` stage.  Let's say we want to now create a ``prod`` stage.
To do this, we can run::

    $ chalice deploy --stage prod
    ...
    https://wk9fhx.execute-api.us-west-2.amazonaws.com/api/

We now have two completely separate rest APIs::

    $ chalice url --stage dev
    https://mmnkdi.execute-api.us-west-2.amazonaws.com/api/

    $ chalice url --stage prod
    https://wk9fhx.execute-api.us-west-2.amazonaws.com/api/

Additionally, we can see all our deployed values by looking
at the ``.chalice/deployed/dev.json`` or ``.chalice/deployed/prod.json`` files::

    $ cat .chalice/deployed/dev.json
    {
      "resources": [
        {
          "name": "api_handler",
          "resource_type": "lambda_function",
          "lambda_arn": "arn:aws:lambda:...:function:myapp-dev"
        },
        {
          "name": "rest_api",
          "resource_type": "rest_api",
          "rest_api_id": "wk9fhx",
          "rest_api_url": "https://wk9fhx.execute-api.us-west-2.amazonaws.com/api/"
        }
      ],
      "schema_version": "2.0",
      "backend": "api"
    }

    $ cat .chalice/deployed/prod.json
    {
      "resources": [
        {
          "name": "api_handler",
          "resource_type": "lambda_function",
          "lambda_arn": "arn:aws:lambda:...:function:myapp-prod"
        },
        {
          "name": "rest_api",
          "resource_type": "rest_api",
          "rest_api_id": "mmnkdi",
          "rest_api_url": "https://mmnkdi.execute-api.us-west-2.amazonaws.com/api/"
        }
      ],
      "schema_version": "2.0",
      "backend": "api"
    }

---

### Source Topics Testing (`docs/source/topics/testing.rst`)

Testing
=======

Chalice provides a :ref:`test client <testing-api>` in ``chalice.test`` that
you can use to test your Chalice applications.  This client lets you invoke
Lambda function and event handlers directly, as well as test your REST APIs.

.. _testing-lambda-functions:

Lambda Functions
----------------

To test lambda functions, use the
:meth:`Client.lambda_.invoke <TestLambdaClient.invoke>` method.  The
test client is intended to be used as a context manager.  For example,
given this sample app:


.. code-block:: python

   from chalice import Chalice

   app = Chalice(app_name="testclient")

   @app.lambda_function()
   def foo(event, context):
       return {'hello': 'world'}

   @app.lambda_function()
   def bar(event, context):
       return {'event': event}


Here's how you can test these functions with the test client.  In our
example, we'll be using `pytest <https://docs.pytest.org/en/stable/>`__,
but the Chalice test client will work with any testing framework.
We'll create a new ``tests/`` directory and create a ``tests/__init__.py``
and a ``tests/test_app.py`` file.

::

    $ mkdir tests
    $ touch tests/{__init__.py,test_app.py}

The ``tests/test_app.py`` file should have the following contents:

.. code-block:: python

   from chalice.test import Client
   from app import app

   def test_foo_function():
       with Client(app) as client:
           result = client.lambda_.invoke('foo')
           assert result.payload == {'hello': 'world'}

   def test_bar_function():
       with Client(app) as client:
           result = client.lambda_.invoke(
               'bar', {'my': 'event'})
           assert result.payload == {'event': {'my': 'event'}}

Now we can run our tests with ``pytest``::

    $ pip install pytest
    $ py.test tests/test_app.py
    ========================= test session starts ==========================
    platform darwin -- Python 3.7.3, pytest-5.3.1, py-1.5.3, pluggy-0.12.0
    rootdir: /tmp/testclient
    plugins: hypothesis-4.43.1, cov-2.8.1
    collected 2 items

    test_app.py ..                                                            [100%]

    ========================= 2 passed in 0.32s ============================

.. note::
   See the :ref:`testing-pytest-fixtures` section for how to use pytest
   fixtures with the Chalice test client.

For testing Lambda functions that are connected to specific events,
you can use the :attr:`Client.events` attribute to generate
sample events.  For example:

.. code-block:: python

   from chalice import Chalice

   @app.on_sns_message(topic='mytopic')
   def foo(event):
       return {'message': event.message}

   # Test code

   from chalice.test import Client

   def test_sns_handler():
       with Client(app) as client:
           response = client.lambda_.invoke(
               "foo",
               client.events.generate_sns_event(message="hello world")
           )
           assert response.payload == {'message': 'hello world'}


Environment Variables
~~~~~~~~~~~~~~~~~~~~~

The Chalice test client will also configure any environment variables you
have configured with your Lambda functions in your ``.chalice/config.json``
file.  For example, suppose you had these config file:

.. code-block:: json

   {
       "version": "2.0",
       "app_name": "testenv",
       "stages": {
           "prod": {
               "api_gateway_stage": "api",
               "environment_variables": {
                   "MY_ENV_VAR": "TOP LEVEL"
               },
               "lambda_functions": {
                   "bar": {
                       "environment_variables": {
                           "MY_ENV_VAR": "OVERRIDE"
                       }
                   }
               }
           }
       }
   }

These sets a ``MY_ENV_VAR`` environment variable for the ``prod`` stage.
The ``bar`` function overrides this environment variable with its own
custom value.  To test this, we need to specify the ``prod`` stage when
we create our test client:

.. code-block:: python

   from chalice import Chalice

   app = Chalice(app_name="testclient")

   @app.lambda_function()
   def foo(event, context):
       return {'value': os.environ.get('MY_ENV_VAR')}

   @app.lambda_function()
   def bar(event, context):
       return {'value': os.environ.get('MY_ENV_VAR')}

    # Test code
   from chalice.test import Client

   def test_foo_function():
       with Client(app, stage_name='prod') as client:
           result = client.lambda_.invoke('foo')
           assert result.payload == {'value': 'TOP LEVEL'}

   def test_bar_function():
       with Client(app) as client:
           result = client.lambda_.invoke('bar')
           assert result.payload == {'value': 'OVERRIDE'}


REST APIs
---------

You can test your REST API with the Chalice test client using the
:attr:`Client.http` attribute.  For example, given this REST API:


.. code-block:: python

   from chalice import Chalice

   app = Chalice(app_name="testclient")

   @app.route('/')
   def index():
       return {'hello': 'world'}


You can test this route with:

.. code-block:: python

   from chalice.test import Client
   from app import app

    def test_index():
        with Client(app) as client:
            response = client.http.get('/')
            assert response.json_body == {'hello': 'world'}

If you want to access the response body's raw bytes, you can use the
``body`` attribute:

.. code-block:: python

   from chalice.test import Client
   from app import app

    def test_index():
        with Client(app) as client:
            response = client.http.get('/')
            assert response.body == b'{"hello":"world"}'


You can also test other HTTP methods by using the corresponding
``post()``, ``put()``, ``delete()``, etc. method calls.

.. code-block:: python

   import json
   from chalice import Chalice

   app = Chalice(app_name="testclient")

   @app.route('/', methods=['POST'])
   def index()
       return app.current_request.json_body


   def test_index():
      with Client(app) as client:
          response = client.http.post(
              '/myview',
              headers={'Content-Type':'application/json'},
              body=json.dumps({'example':'json'})
          )
          assert response.json_body == {'example': 'json'}


You can also test builtin authorizers with the test client:

.. code-block:: python

   from chalice import Chalice

   app = Chalice(app_name="testclient")

   @app.authorizer()
   def myauth(event)
       if event.token == 'allow':
           return AuthResponse(['*'], principal_id='id')
       return AuthResponse([], principal_id='noone')

   @app.route('/needs-auth', authorizer=myauth)
   def needs_auth()
       return {'success': True}

   #  Test code:
   from chalice.test import Client

    def test_needs_auth():
        with Client(app) as client:
            response = client.http.get(
                '/needs-auth', headers={'Authorization': 'allow'})
            assert response.json_body == {'success': True}
            assert client.http.get(
                '/needs-auth',
                headers={'Authorization': 'deny'}).status_code == 403


.. _testing-boto3-client-calls:

Testing Boto3 Client Calls
--------------------------

If your event handlers are making AWS API calls using boto3 or botocore,
you can use the `botocore stubber
<https://botocore.amazonaws.com/v1/documentation/api/latest/reference/stubber.html>`__
to test your API calls.  For example, suppose we have an app that makes an
API call to Amazon Rekognition whenever an object is uploaded to S3:

.. code-block:: python

   import boto3

   from chalice import Chalice

   app = Chalice(app_name='testclient')
   _REKOGNITION_CLIENT = None


   def get_rekognition_client():
       global _REKOGNITION_CLIENT
       if _REKOGNITION_CLIENT is None:
           _REKOGNITION_CLIENT = boto3.client('rekognition')
       return _REKOGNITION_CLIENT


   @app.on_s3_event(bucket='mybucket',
                    events=['s3:ObjectCreated:*'])
   def handle_object_created(event):
       client = get_rekognition_client()
       response = client.detect_labels(
           Image={
               'S3Object': {
                   'Bucket': event.bucket,
                   'Name': event.key,
               },
           },
           MinConfidence=50.0
       )
       labels = [label['Name'] for label in response['Labels']]
       # In the real app we'd now do something with these labels
       # (e.g. store than in a database so we can query them later).
       return labels

To test this, we'll combine the botocore stubber and the Chalice test client:

.. code-block:: python

   from chalice.test import Client
   import app

   from botocore.stub import Stubber

   def test_calls_rekognition():
       client = app.get_rekognition_client()
       stub = Stubber(client)
       stub.add_response(
           'detect_labels',
           expected_params={
               'Image': {
                   'S3Object': {
                       'Bucket': 'mybucket',
                       'Name': 'mykey',
                   }
               },
               'MinConfidence': 50.0,
           },
           service_response={
               'Labels': [
                   {'Name': 'Dog', 'Confidence': 75.0},
                   {'Name': 'Mountain', 'Confidence': 80.0},
                   {'Name': 'Snow', 'Confidence': 85.0},
               ]
           },
       )
       with stub:
           with Client(app.app) as client:
               event = client.events.generate_s3_event(
                   bucket='mybucket', key='mykey')
               response = client.lambda_.invoke('handle_object_created', event)
               assert response.payload == ['Dog', 'Mountain', 'Snow']
           stub.assert_no_pending_responses()


In the testcase above, we first tell the stubber what API call we're expecting,
along with the parameters we'll send and the response we expect back from the
Rekognition service.  Next we use the ``with stub:`` line to activate our stubs.
This also ensures that when our test exits that we'll deactivate the stubs for
this client.  Now we the ``client.lambda_.invoke`` method is called, our
stubbed client will return the preconfigured response data instead of making
an actual API call to the Rekognition service.


.. _testing-pytest-fixtures:

Pytest Fixtures
---------------

Both the Botocore stubber and the Chalice test client are used within
a context manager.  In our previous example, this resulted in multiple
levels of nesting, which is required for every test we write.  If you're
using pytest as your test framework, you can create
`test fixtures <https://docs.pytest.org/en/stable/fixture.html>`__ to
reduce the boiler plate code.  Let's rewrite several of these tests to use
pytest fixtures.

First we'll create a test fixture for the Chalice test client:

.. code-block:: python

    import app
    from pytest import fixture
    from chalice.test import Client

    @fixture
    def test_client():
        with Client(app.app) as client:
            yield client

Now our original tests for the ``foo`` and ``bar`` Lambda functions
from the :ref:`testing-lambda-functions` section can be rewritten
to use this fixture:

.. code-block:: python

   def test_foo_function(test_client):
       result = test_client.lambda_.invoke('foo')
       assert result.payload == {'hello': 'world'}

   def test_bar_function(test_client):
       result = test_client.lambda_.invoke(
           'bar', {'my': 'event'})
       assert result.payload == {'event': {'my': 'event'}}

We can also create a fixture for the botocore stubber.  This allows us
to rewrite the ``test_calls_rekognition()`` test from the
:ref:`previous section <testing-boto3-client-calls>` in a more simplified
manner.  Below is the entire test file using both the botocore and Chalice
test client fixtures:

.. code-block:: python

    import app
    from pytest import fixture
    from chalice.test import Client


    @fixture
    def test_client():
        with Client(app.app) as client:
            yield client


    @fixture
    def rekognition_stub():
        client = app.get_rekognition_client()
        stub = Stubber(client)
        with stub:
            yield stub


    def test_calls_rekognition(test_client, rekognition_stub):
        rekognition_stub.add_response(
            'detect_labels',
            expected_params={
                'Image': {
                    'S3Object': {
                        'Bucket': 'mybucket',
                        'Name': 'mykey',
                    }
                },
                'MinConfidence': 50.0,
            },
            service_response={
                'Labels': [
                    {'Name': 'Dog', 'Confidence': 75.0},
                    {'Name': 'Mountain', 'Confidence': 80.0},
                    {'Name': 'Snow', 'Confidence': 85.0},
                ]
            },
        )
        event = test_client.events.generate_s3_event(
            bucket='mybucket', key='mykey')
        response = test_client.lambda_.invoke('handle_object_created', event)
        assert response.payload == ['Dog', 'Mountain', 'Snow']
        stub.assert_no_pending_responses()


Next Steps
----------

For reference documentation on the methods and attributes of the Chalice test
client, see the :ref:`test client <testing-api>` section in the API
documentation.

---

### Source Topics Tf (`docs/source/topics/tf.rst`)

Terraform Support
=================

When you run ``chalice deploy``, chalice will deploy your application using the
`AWS SDK for Python <http://boto3.readthedocs.io/en/docs/>`__.  Chalice also
provides functionality that allows you to manage deployments yourself using
terraform.  This is provided via the ``chalice package --pkg-format terraform``
command.

When you run this command, chalice will generate the AWS Lambda
deployment package that contains your application and a `Terraform
<https://www.terraform.io>`__ configuration file. You can then use the
terraform cli to deploy your chalice application.

Considerations
--------------

Using the ``chalice package`` command is useful when you don't want to
use ``chalice deploy`` to manage your deployments.  There's several reasons
why you might want to do this:

* You have pre-existing infrastructure and tooling set up to manage
  Terraform deployments.
* You want to integrate with other Terraform resources to manage all
  your application resources, including resources outside of your
  chalice app.
* You'd like to integrate with `AWS CodePipeline
  <https://aws.amazon.com/codepipeline/>`__ to automatically deploy
  changes when you push to a git repo.

Keep in mind that you can't switch between ``chalice deploy`` and
``chalice package`` + Terraform for deploying your app.

If you choose to use ``chalice package`` and Terraform to deploy
your app, you won't be able to switch back to ``chalice deploy``.
Running ``chalice deploy`` would create an entirely new set of AWS
resources (API Gateway Rest API, AWS Lambda function, etc).

Example
-------

In this example, we'll create a chalice app and deploy it using
the AWS CLI.

First install the necessary packages::

    $ virtualenv /tmp/venv
    $ . /tmp/venv/bin/activate
    $ pip install chalice awscli
    $ chalice new-project test-tf-deploy
    $ cd test-tf-deploy

At this point we've installed chalice and the AWS CLI and we have
a basic app created locally.  Next we'll run the ``package`` command::

    $ chalice package --pkg-format terraform /tmp/packaged-app/
    Creating deployment package.
    $ ls -la /tmp/packaged-app/
    -rw-r--r--   1 j         wheel  3355270 May 25 14:20 deployment.zip
    -rw-r--r--   1 j         wheel     3068 May 25 14:20 chalice.tf.json

    $ unzip -l /tmp/packaged-app/deployment.zip  | tail -n 5
        17292  05-25-17 14:19   chalice/app.py
          283  05-25-17 14:19   chalice/__init__.py
          796  05-25-17 14:20   app.py
     --------                   -------
      9826899                   723 files


As you can see in the above example, the ``package --pkg-format``
command created a directory that contained two files, a
``deployment.zip`` file, which is the Lambda deployment package, and a
``chalice.tf.json`` file, which is the Terraform template that can be
deployed using Terraform.  Next we're going to use the Terraform CLI
to deploy our app.

Note terraform will deploy run against all terraform files in this
directory, so we can add additional resources for our application by
adding terraform additional files here. The Chalice terraform template
includes two static data values (`app` and `stage` names) that we can
optionally use when constructing these additional resources,
ie. `${data.null_data_source.chalice.outputs.app}`

First let's run Terraform init to install the AWS Terraform Provider::

    $ cd /tmp/packaged-app
    $ terraform init

Now we can deploy our app using the ``terraform apply`` command::

  $ terraform apply
  data.aws_region.chalice: Refreshing state...
  data.aws_caller_identity.chalice: Refreshing state...

  An execution plan has been generated and is shown below.
  Resource actions are indicated with the following symbols:
  + create

  ... (omit plan output)

  Plan: 14 to add, 0 to change, 0 to destroy.

  Do you want to perform these actions?
    Terraform will perform the actions described above.
    Only 'yes' will be accepted to approve.
  Enter a value: yes

  ... (omit apply output)

  Apply complete! Resources: 14 added, 0 changed, 0 destroyed.

  Outputs:

  EndpointURL = https://7bnxriulj5.execute-api.us-east-1.amazonaws.com/dev
  RestApiId = 7bnxriulj5

This will take a minute to complete, but once it's done, the endpoint url
will be available as an output which we can then curl::

    $ http "$(terraform output EndpointURL)"
    HTTP/1.1 200 OK
    Connection: keep-alive
    Content-Length: 18
    Content-Type: application/json
    ...

    {
        "hello": "world"
    }

---

### Source Topics Views (`docs/source/topics/views.rst`)

Views
=====

A view function in chalice is the function attached to an
``@app.route()`` decorator.  In the example below, ``index``
is the view function:

.. code-block:: python

    from chalice import Chalice

    app = Chalice(app_name='helloworld')


    @app.route('/')
    def index():
        return {'view': 'index'}


View Function Parameters
------------------------

A view function's parameters correspond to the number of captured
URL parameters specified in the ``@app.route`` call.  In the example above,
the route ``/`` specifies no captured parameters so the ``index`` view
function accepts no parameters.  However, in the view function below,
a single URL parameter, ``{city}`` is specified, so the view function
must accept a single parameter:


.. code-block:: python

    from chalice import Chalice

    app = Chalice(app_name='helloworld')


    @app.route('/cities/{city}')
    def index(city):
        return {'city': city}


This indicates that the value of ``{city}`` is variable, and whatever
value is provided in the URL is passed to the ``index`` view function.
For example::

    GET /cities/seattle   --> index('seattle')
    GET /cities/portland  --> index('portland')


If you want to access any other metadata of the incoming HTTP request,
you can use the ``app.current_request`` property, which is an instance of
the the :class:`Request` class.


View Function Return Values
---------------------------

The response returned back to the client depends on the behavior
of the view function.  There are several options available:

* Returning an instance of :class:`Response`.  This gives you
  complete control over what gets returned back to the customer.
* A ``bytes`` type response body must have a ``Content-Type`` header value
  that is present in the ``app.api.binary_types`` list in order to be handled
  properly.
* Any other return value will be serialized as JSON and sent back
  as the response body with content type ``application/json``.
* Any subclass of ``ChaliceViewError`` will result in an HTTP
  response being returned with the status code associated with that
  response, and a JSON response body containing a ``Code`` and a ``Message``.
  This is discussed in more detail below.
* Any other exception raised will result in a 500 HTTP response.
  The body of that response depends on whether debug mode is enabled.


.. _view-error-handling:

Error Handling
--------------

Chalice provides a built in set of exception classes that map to common
HTTP errors including:

* ``BadRequestError``- returns a status code of 400
* ``UnauthorizedError``- returns a status code of 401
* ``ForbiddenError``- returns a status code of 403
* ``NotFoundError``- returns a status code of 404
* ``ConflictError``- returns a status code of 409
* ``TooManyRequestsError``- returns a status code of 429
* ``ChaliceViewError``- returns a status code of 500

You can raise these anywhere in your view functions and chalice will convert
these to the appropriate HTTP response.  The default chalice error responses
will send the error back as ``application/json`` with the response body
containing a ``Code`` corresponding to the exception class name and a
``Message`` key corresponding to the string provided when the exception
was instantiated.  For example:

.. code-block:: python

    from chalice import Chalice
    from chalice import BadRequestError

    app = Chalice(app_name="badrequest")

    @app.route('/badrequest')
    def badrequest():
        raise BadRequestError("This is a bad request")


This view function will generate the following HTTP response::

    $ http https://endpoint/api/badrequest
    HTTP/1.1 400 Bad Request

    {
        "Code": "BadRequestError",
        "Message": "This is a bad request"
    }


In addition to the built in chalice exceptions, you can use the
:class:`Response` class to customize the HTTP errors if you prefer to
either not have JSON error responses or customize the JSON response body
for errors.  For example:

.. code-block:: python

    from chalice import Chalice, Response

    app = Chalice(app_name="badrequest")

    @app.route('/badrequest')
    def badrequest():
        return Response(body='Plain text error message',
                        headers={'Content-Type': 'text/plain'},
                        status_code=400)



Specifying HTTP Methods
-----------------------

So far, our examples have only allowed GET requests. It's actually possible
to support additional HTTP methods. Here's an example of a view function that
supports PUT:

.. code-block:: python

    @app.route('/resource/{value}', methods=['PUT'])
    def put_test(value):
        return {"value": value}

We can test this method using the ``http`` command::

    $ http PUT https://endpoint/api/resource/foo
    HTTP/1.1 200 OK

    {
        "value": "foo"
    }

Note that the ``methods`` kwarg accepts a list of methods.  Your view function
will be called when any of the HTTP methods you specify are used for the
specified resource.  For example:

.. code-block:: python

    @app.route('/myview', methods=['POST', 'PUT'])
    def myview():
        pass

The above view function will be called when either an HTTP POST or
PUT is sent to ``/myview`` as shown below::

    POST /myview   --> myview()
    PUT /myview  --> myview()

Alternatively if you do not want to share the same view function across
multiple HTTP methods for the same route url, you may define separate view
functions to the same route url but have the view functions differ by
HTTP method. For example:

.. code-block:: python

    @app.route('/myview', methods=['POST'])
    def myview_post():
        pass

    @app.route('/myview', methods=['PUT'])
    def myview_put():
        pass

This setup will route all HTTP POST's to ``/myview`` to the ``myview_post()``
view function and route all HTTP PUT's to ``/myview`` to the ``myview_put()``
view function as shown below::

    POST /myview   --> myview_post()
    PUT /myview  --> myview_put()

If you do chose to use separate view functions for the same route path, it is
important to know:

* View functions that share the same route cannot have the same names.
  For example, two view functions that both share the same route path cannot
  both be named ``view()``.

* View functions that share the same route cannot overlap in supported HTTP
  methods. For example if two view functions both share the same route path,
  they both cannot contain ``'PUT'`` in their route ``methods`` list.

* View functions that share the same route path and have CORS configured cannot
  have differing CORS configuration. For example, if two view functions that
  both share the same route path, the route configuration for one of the
  view functions cannot set ``cors=True`` while having the route
  configuration of the other view function be set to
  ``cors=app.CORSConfig(allow_origin='https://foo.example.com')``.


Binary Content
--------------

Chalice supports binary payloads through its ``app.api.binary_types`` list. Any
type in this list is considered a binary ``Content-Type``. Whenever a request
with a ``Content-Type`` header is encountered that matches an entry in the
``binary_types`` list, its body will be available as a ``bytes`` type on the
property ``app.current_request.raw_body``. Similarly, in order to send binary
data back in a response, simply set your ``Content-Type`` header to something
present in the ``binary_types`` list. Note that you can override the default
types by modifying the ``app.api.binary_types`` list at the module level.

Here is an example app which simply echoes back binary content:

.. code-block:: python

   from chalice import Chalice, Response

   app = Chalice(app_name="binary-response")

   @app.route('/bin-echo', methods=['POST'],
              content_types=['application/octet-stream'])
   def bin_echo():
       raw_request_body = app.current_request.raw_body
       return Response(body=raw_request_body,
                       status_code=200,
                       headers={'Content-Type': 'application/octet-stream'})

You can see this app echo back binary data sent to it::

  $ echo -n -e "\xFE\xED" | http POST $(chalice url)bin-echo \
    Accept:application/octet-stream Content-Type:application/octet-stream | xxd
  0000000: feed                                     ..

Note that both the ``Accept`` and ``Content-Type`` headers are required. If
you fail to set the ``Content-Type`` header on the request will result in a
``415 UnsupportedMediaType`` error. Care must be taken when configuring what
``content_types`` a route accepts, they must all be valid binary types, or they
must all be non-binary types. The ``Accept`` header must also be set if the
data returned is to be the raw binary, if is omitted the call return a ``400``
Bad Request response.

For example, here is the same call as above without the ``Accept`` header::

  $ echo -n -e "\xFE\xED" | http POST  $(chalice url)bin-echo \
    Content-Type:application/octet-stream
  HTTP/1.1 400 Bad Request
  Connection: keep-alive
  Content-Length: 270
  Content-Type: application/json
  Date: Sat, 27 May 2017 07:09:51 GMT

  {
    "Code": "BadRequest",
    "Message": "Request did not specify an Accept header with
      application/octet-stream, The response has a Content-Type of
      application/octet-stream. If a response has a binary Content-Type then
      the request must specify an Accept header that matches."
  }



Usage Recommendations
---------------------

If you want to return a JSON response body, just return the corresponding
python types directly.  You don't need to use the :class:`Response` class.
Chalice will automatically convert this to a JSON HTTP response as a
convenience for you.

Use the :class:`Response` class when you want to return non-JSON content, or
when you want to inject custom HTTP headers to your response.

For errors, raise the built in ``ChaliceViewError`` subclasses (e.g
``BadRequestError``, ``NotFoundError``, ``ConflictError`` etc)  when you
want to return a HTTP error response with a preconfigured JSON body containing
a ``Code`` and ``Message``.

Use the :class:`Response` class when you want to customize the error responses
to either return a different JSON error response body, or to return an HTTP
response that's not ``application/json``.

---

### Source Topics Websockets (`docs/source/topics/websockets.rst`)

Websockets
==========

.. warning::

  Websockets are considered an experimental API.  You'll need to opt-in
  to this feature using the ``WEBSOCKETS`` feature flag:

  .. code-block:: python

    app = Chalice('myapp')
    app.experimental_feature_flags.update([
        'WEBSOCKETS'
    ])

  See :doc:`experimental` for more information.


Chalice supports websockets through integration with an API Gateway Websocket
API. If any of the decorators are present in a Chalice app, then an API
Gateway Websocket API will be deployed and wired to Lambda Functions.


Responding to websocket events
------------------------------

In a Chalice app the websocket API is accessed through the three decorators
``on_ws_connect``, ``on_ws_message``, ``on_ws_disconnect``. These handle a new
websocket connection, an incoming message on an existing connection, and a
connection being cleaned up respectively.

A decorated websocket handler function takes one argument ``event`` with the
type :ref:`WebsocketEvent <websocket-api>`. This class allows easy access to
information about the API Gateway Websocket API, and information about the
particular socket the handler is being invoked to serve.

Below is a simple working example application that prints to CloudWatch Logs
for each of the events.

.. code-block:: python

    from boto3.session import Session
    from chalice import Chalice

    app = Chalice(app_name='test-websockets')
    app.experimental_feature_flags.update([
        'WEBSOCKETS',
    ])
    app.websocket_api.session = Session()


    @app.on_ws_connect()
    def connect(event):
        print('New connection: %s' % event.connection_id)


    @app.on_ws_message()
    def message(event):
        print('%s: %s' % (event.connection_id, event.body))


    @app.on_ws_disconnect()
    def disconnect(event):
        print('%s disconnected' % event.connection_id)


Setting the websocket protocol on new connections
-------------------------------------------------

You can return a dictionary or an instance of :class:`Response` in the
``on_ws_connect`` handler, similar to what you'd do in a Rest API.  Note that
API Gateway does not forward arbitrary headers or a response body back to the
client, so this is primarily used to set a ``Sec-WebSocket-Protocol`` header
value.


.. code-block:: python

    from chalice import Chalice

    app = Chalice(app_name='test-websockets')
    app.experimental_feature_flags.update([
        'WEBSOCKETS',
    ])


    @app.on_ws_connect()
    def connect(event):
        print('New connection: %s' % event.connection_id)
        # We don't need to explicitly set a statusCode.
        return {
          'headers': {'Sec-WebSocket-Protocol': 'My-Protocol'},
        }


You don't need to explicitly set a ``statusCode`` if you return a
dictionary from the ``on_ws_connect`` header, but if want to return one you
should **not** set the status code to ``101``.  API Gateway will automatically
do this for you.  For successful connection handling you should return a
``200`` status code if you want to explicitly set a ``statusCode``.


Sending a message over a websocket
----------------------------------

To send a message to a websocket client Chalice, use the
:ref:`app.websocket_api.send() <websocket-send>` method. This method will work in any
of the decorated functions outlined in the above section.

Two pieces of information are needed to send a message. The identifier of the
websocket, and the contents for the message. Below is a simple example that
when it receives a message, it sends back the message ``"I got your message!"``
over the same socket.

.. code-block:: python

    from boto3.session import Session
    from chalice import Chalice

    app = Chalice(app_name='test-websockets')
    app.experimental_feature_flags.update([
        'WEBSOCKETS',
    ])
    app.websocket_api.session = Session()


    @app.on_ws_message()
    def message(event):
        app.websocket_api.send(event.connection_id, 'I got your message!')


See :ref:`websocket-tutorial` for completely worked example applications.

---

### Source Tutorials Basicrestapi (`docs/source/tutorials/basicrestapi.rst`)

REST API Tutorial
=================

In this tutorial, we create a REST API and explore
Chalice features that help us write REST APIs.

Installation and Configuration
------------------------------

If you haven't already setup and configured Chalice, see the
:doc:`../quickstart` for a step by step guide.  In a nutshell, you can get a
basic Chalice app created with::

    $ python3 --version
    Python 3.10.20
    $ python3 -m venv .venv
    $ . .venv/bin/activate
    $ python3 -m pip install chalice
    $ chalice new-project helloworld
    $ cd helloworld


URL Parameters
--------------

The default template when you run the ``new-project`` generates a sample
REST API for you:

.. code-block:: python

    from chalice import Chalice

    app = Chalice(app_name='helloworld')


    @app.route('/')
    def index():
        return {'hello': 'world'}

We're going to make a few changes to our ``app.py`` file that
demonstrate the capabilities provided by Chalice.

Our application so far has a single view that allows you to make
an HTTP GET request to ``/``.  Now let's suppose we want to capture
parts of the URI:

.. code-block:: python

    from chalice import Chalice

    app = Chalice(app_name='helloworld')

    CITIES_TO_STATE = {
        'seattle': 'WA',
        'portland': 'OR',
    }


    @app.route('/')
    def index():
        return {'hello': 'world'}

    @app.route('/cities/{city}')
    def state_of_city(city):
        return {'state': CITIES_TO_STATE[city]}


In the example above, we've now added a ``state_of_city`` view that allows
a user to specify a city name.  The view function takes the city
name and returns the name of the state the city is in.  Notice that the
``@app.route`` decorator has a URL pattern of ``/cities/{city}``.  This
means that the value of ``{city}`` is captured and passed to the view
function.  You can also see that the ``state_of_city`` takes a single
argument.  This argument is the name of the city provided by the user.
For example::

    GET /cities/seattle   --> state_of_city('seattle')
    GET /cities/portland  --> state_of_city('portland')

Now that we've updated our ``app.py`` file with this new view function,
let's redeploy our application.  You can run ``chalice deploy`` from
the ``helloworld`` directory and it will deploy your application::

    $ chalice deploy

Let's try it out.  Note the examples below use the ``http`` command from the
``httpie`` package.  You can install this using ``pip install httpie``::

    $ http https://endpoint/api/cities/seattle
    HTTP/1.1 200 OK

    {
        "state": "WA"
    }

    $ http https://endpoint/api/cities/portland
    HTTP/1.1 200 OK

    {
        "state": "OR"
    }


Notice what happens if we try to request a city that's not in our
``CITIES_TO_STATE`` map::

    $ http https://endpoint/api/cities/vancouver
    HTTP/1.1 500 Internal Server Error
    Content-Type: application/json
    X-Cache: Error from cloudfront

    {
        "Code": "ChaliceViewError",
        "Message": "An internal server error occurred."
    }


In the next section, we'll see how to fix this and provide better
error messages.


Error Messages
--------------

In the example above, you'll notice that when our app raised
an uncaught exception, a 500 internal server error was returned.

In this section, we're going to show how you can debug and improve
these error messages.

The first thing we're going to look at is how we can debug this
issue.  By default, debugging is turned off, but you can
enable debugging to get more information:

.. code-block:: python

    from chalice import Chalice

    app = Chalice(app_name='helloworld')
    app.debug = True


The ``app.debug = True`` enables debugging for your app.
Save this file and redeploy your changes::

    $ chalice deploy
    ...
    https://endpoint/api/

Now, when you request the same URL that returned an internal
server error, you'll get back the original stack trace::

    $ http https://endpoint/api/cities/vancouver
    Traceback (most recent call last):
      File "/var/task/chalice/app.py", line 304, in _get_view_function_response
        response = view_function(*function_args)
      File "/var/task/app.py", line 18, in state_of_city
        return {'state': CITIES_TO_STATE[city]}
    KeyError: u'vancouver'


We can see that the error is caused from an uncaught ``KeyError`` resulting
from trying to access the ``vancouver`` key.

Now that we know the error, we can fix our code.  What we'd like to do is
catch this exception and instead return a more helpful error message
to the user.  Here's the updated code:

.. code-block:: python

    from chalice import BadRequestError

    @app.route('/cities/{city}')
    def state_of_city(city):
        try:
            return {'state': CITIES_TO_STATE[city]}
        except KeyError:
            raise BadRequestError("Unknown city '%s', valid choices are: %s" % (
                city, ', '.join(CITIES_TO_STATE.keys())))


Save and deploy these changes::

    $ chalice deploy
    $ http https://endpoint/api/cities/vancouver
    HTTP/1.1 400 Bad Request

    {
        "Code": "BadRequestError",
        "Message": "Unknown city 'vancouver', valid choices are: portland, seattle"
    }

We can see now that we have received a ``Code`` and ``Message`` key, with the
message being the value we passed to ``BadRequestError``.  Whenever you raise a
``BadRequestError`` from your view function, the framework will return an HTTP
status code of 400 along with a JSON body with a ``Code`` and ``Message``.
There are a few additional exceptions you can raise from your python code::

* BadRequestError - return a status code of 400
* UnauthorizedError - return a status code of 401
* ForbiddenError - return a status code of 403
* NotFoundError - return a status code of 404
* ConflictError - return a status code of 409
* UnprocessableEntityError - return a status code of 422
* TooManyRequestsError - return a status code of 429
* ChaliceViewError - return a status code of 500

You can import these directly from the ``chalice`` package:

.. code-block:: python

    from chalice import UnauthorizedError


Additional Routing
------------------

So far, our examples have only allowed GET requests.
It's actually possible to support additional HTTP methods.
Here's an example of a view function that supports PUT:

.. code-block:: python

    @app.route('/resource/{value}', methods=['PUT'])
    def put_test(value):
        return {"value": value}

We can test this method using the ``http`` command::

    $ http PUT https://endpoint/api/resource/foo
    HTTP/1.1 200 OK

    {
        "value": "foo"
    }

Note that the ``methods`` kwarg accepts a list of methods.  Your view function
will be called when any of the HTTP methods you specify are used for the
specified resource.  For example:

.. code-block:: python

    @app.route('/myview', methods=['POST', 'PUT'])
    def myview():
        pass

The above view function will be called when either an HTTP POST or
PUT is sent to ``/myview``.

Alternatively if you do not want to share the same view function across
multiple HTTP methods for the same route url, you may define separate view
functions to the same route url but have the view functions differ by
HTTP method. For example:

.. code-block:: python

    @app.route('/myview', methods=['POST'])
    def myview_post():
        pass

    @app.route('/myview', methods=['PUT'])
    def myview_put():
        pass

This setup routes HTTP POST requests to ``/myview`` to the ``myview_post()``
view function and routes HTTP PUT requests to ``/myview`` to the ``myview_put()``
view function. It is also important to note that the view functions
**must** have unique names. For example, both view functions cannot be
named ``myview()``.

In the next section we'll go over how you can introspect the given request
in order to differentiate between various HTTP methods.


Request Metadata
----------------

In the examples above, you saw how to create a view function that supports
an HTTP PUT request as well as a view function that supports both POST and
PUT via the same view function.  However, there's more information we
might need about a given request:

* In a PUT/POST, you frequently send a request body.  We need some
  way of accessing the contents of the request body.
* For view functions that support multiple HTTP methods, we'd like
  to detect which HTTP method was used so we can have different
  code paths for PUTs vs. POSTs.

All of this and more is handled by the current request object that the
``chalice`` library makes available to each view function when it's called.

Let's see an example of this.  Suppose we want to create a view function
that allowed you to PUT data to an object and retrieve that data
via a corresponding GET.  We could accomplish that with the
following view function:

.. code-block:: python

    from chalice import NotFoundError

    OBJECTS = {
    }

    @app.route('/objects/{key}', methods=['GET', 'PUT'])
    def myobject(key):
        request = app.current_request
        if request.method == 'PUT':
            OBJECTS[key] = request.json_body
        elif request.method == 'GET':
            try:
                return {key: OBJECTS[key]}
            except KeyError:
                raise NotFoundError(key)


Save this in your ``app.py`` file and rerun ``chalice deploy``.
Now, you can make a PUT request to ``/objects/your-key`` with a request
body, and retrieve the value of that body by making a subsequent
``GET`` request to the same resource.  Here's an example of its usage::

    # First, trying to retrieve the key will return a 404.
    $ http GET https://endpoint/api/objects/mykey
    HTTP/1.1 404 Not Found

    {
        "Code": "NotFoundError",
        "Message": "mykey"
    }

    # Next, we'll create that key by sending a PUT request.
    $ echo '{"foo": "bar"}' | http PUT https://endpoint/api/objects/mykey
    HTTP/1.1 200 OK

    null

    # And now we no longer get a 404, we instead get the value we previously
    # put.
    $ http GET https://endpoint/api/objects/mykey
    HTTP/1.1 200 OK

    {
        "mykey": {
            "foo": "bar"
        }
    }

You might see a problem with storing the objects in a module level
``OBJECTS`` variable.  We address this in the next section.

The ``app.current_request`` object is an instance of the :class:`Request`
class, which also has the following properties.

* ``current_request.query_params`` - A dict of the query params.
* ``current_request.headers`` - A dict of the request headers.
* ``current_request.uri_params`` - A dict of the captured URI params.
* ``current_request.method`` -  The HTTP method (as a string).
* ``current_request.json_body`` - The parsed JSON body.
* ``current_request.raw_body`` - The raw HTTP body as bytes.
* ``current_request.context`` - A dict of additional context information
* ``current_request.stage_vars`` - Configuration for the API Gateway stage

The ``current_request`` object also has a ``to_dict`` method, which returns all
the information about the current request as a dictionary.  Let's use this
method to write a view function that returns everything it knows about the
request:

.. code-block:: python

    @app.route('/introspect')
    def introspect():
        return app.current_request.to_dict()


Save this to your ``app.py`` file and redeploy with ``chalice deploy``.
Here's an example of hitting the ``/introspect`` URL.  Note how we're
sending a query string as well as a custom ``X-TestHeader`` header::


    $ http 'https://endpoint/api/introspect?query1=value1&query2=value2' 'X-TestHeader: Foo'
    HTTP/1.1 200 OK

    {
        "context": {
            "apiId": "apiId",
            "httpMethod": "GET",
            "identity": {
                "accessKey": null,
                "accountId": null,
                "apiKey": null,
                "caller": null,
                "cognitoAuthenticationProvider": null,
                "cognitoAuthenticationType": null,
                "cognitoIdentityId": null,
                "cognitoIdentityPoolId": null,
                "sourceIp": "1.1.1.1",
                "userAgent": "HTTPie/0.9.3",
                "userArn": null
            },
            "requestId": "request-id",
            "resourceId": "resourceId",
            "resourcePath": "/introspect",
            "stage": "dev"
        },
        "headers": {
            "accept": "*/*",
            ...
            "x-testheader": "Foo"
        },
        "method": "GET",
        "query_params": {
            "query1": "value1",
            "query2": "value2"
        },
        "raw_body": null,
        "stage_vars": null,
        "uri_params": null
    }


Request Content Types
---------------------

The default behavior of a view function supports
a request body of ``application/json``.  When a request is
made with a ``Content-Type`` of ``application/json``, the
``app.current_request.json_body`` attribute is automatically
set for you.  This value is the parsed JSON body.

You can also configure a view function to support other
content types.  You can do this by specifying the
``content_types`` parameter value to your ``app.route``
function.  This parameter is a list of acceptable content
types.  Here's an example of this feature:

.. code-block:: python

    import sys

    from chalice import Chalice
    from urllib.parse import urlparse, parse_qs

    app = Chalice(app_name='helloworld')


    @app.route('/', methods=['POST'],
               content_types=['application/x-www-form-urlencoded'])
    def index():
        parsed = parse_qs(app.current_request.raw_body.decode())
        return {
            'states': parsed.get('states', [])
        }

There's a few things worth noting in this view function.
First, we've specified that we only accept the
``application/x-www-form-urlencoded`` content type.  If we
try to send a request with ``application/json``, we'll now
get a ``415 Unsupported Media Type`` response::

    $ http POST https://endpoint/api/ states=WA states=CA --debug
    ...
    >>> requests.request(**{'allow_redirects': False,
     'headers': {'Accept': 'application/json',
                 'Content-Type': 'application/json',
    ...


    HTTP/1.1 415 Unsupported Media Type

    {
        "message": "Unsupported Media Type"
    }

If we use the ``--form`` argument, we can see the
expected behavior of this view function because ``httpie`` sets the
``Content-Type`` header to ``application/x-www-form-urlencoded``::

    $ http --form POST https://endpoint/api/formtest states=WA states=CA --debug
    ...
    >>> requests.request(**{'allow_redirects': False,
     'headers': {'Content-Type': 'application/x-www-form-urlencoded; charset=utf-8',
    ...

    HTTP/1.1 200 OK
    {
        "states": [
            "WA",
            "CA"
        ]
    }

The second thing worth noting is that ``app.current_request.json_body``
**is only available for the application/json content type.**
In our example above, we used ``app.current_request.raw_body`` to access
the raw body bytes:

.. code-block:: python

    parsed = parse_qs(app.current_request.raw_body)

``app.current_request.json_body`` is set to ``None`` whenever the
``Content-Type`` is not ``application/json``.  This means that
you will need to use ``app.current_request.raw_body`` and parse
the request body as needed.


Customizing the HTTP Response
-----------------------------

The return value from a chalice view function is serialized as JSON as the
response body returned back to the caller.  This makes it easy to create
rest APIs that return JSON response bodies.

Chalice allows you to control this behavior by returning an instance of
a chalice specific ``Response`` class.  This behavior allows you to:

* Specify the status code to return
* Specify custom headers to add to the response
* Specify response bodies that are not ``application/json``

Here's an example of this:

.. code-block:: python

    from chalice import Chalice, Response

    app = Chalice(app_name='custom-response')


    @app.route('/')
    def index():
        return Response(body='hello world!',
                        status_code=200,
                        headers={'Content-Type': 'text/plain'})

This will result in a plain text response body::

    $ http https://endpoint/api/
    HTTP/1.1 200 OK
    Content-Length: 12
    Content-Type: text/plain

    hello world!


GZIP compression for JSON
-------------------------

The return value from a chalice view function is serialized as JSON as the
response body returned back to the caller.  This makes it easy to create
rest APIs that return JSON response bodies.

Chalice allows you to control this behavior by returning an instance of
a chalice specific ``Response`` class.  This behavior allows you to:

* Add ``application/json`` to binary_types
* Specify the status code to return
* Specify custom header ``Content-Type: application/json``
* Specify custom header ``Content-Encoding: gzip``

Here's an example of this:

.. code-block:: python

    import json
    import gzip
    from chalice import Chalice, Response

    app = Chalice(app_name='compress-response')
    app.api.binary_types.append('application/json')

    @app.route('/')
    def index():
        blob = json.dumps({'hello': 'world'}).encode('utf-8')
        payload = gzip.compress(blob)
        custom_headers = {
            'Content-Type': 'application/json',
            'Content-Encoding': 'gzip'
        }
        return Response(body=payload,
                        status_code=200,
                        headers=custom_headers)



CORS Support
------------

You can specify whether a view supports CORS by adding the
``cors=True`` parameter to your ``@app.route()`` call.  By
default this value is ``False``. Global CORS can be set by
setting ``app.api.cors = True``.

.. code-block:: python

    @app.route('/supports-cors', methods=['PUT'], cors=True)
    def supports_cors():
        return {}


Setting ``cors=True`` has similar behavior to enabling CORS
using the AWS Console.  This includes:

* Injecting the ``Access-Control-Allow-Origin: *`` header to your
  responses, including all error responses you can return.
* Automatically adding an ``OPTIONS`` method to support preflighting
  requests.

The preflight request will return a response that includes:

* ``Access-Control-Allow-Origin: *``
* The ``Access-Control-Allow-Methods`` header will return a list of all HTTP
  methods you've called out in your view function.  In the example above,
  this will be ``PUT,OPTIONS``.
* ``Access-Control-Allow-Headers: Content-Type,X-Amz-Date,Authorization,
  X-Api-Key,X-Amz-Security-Token``.

If more fine grained control of the CORS headers is desired, set the ``cors``
parameter to an instance of ``CORSConfig`` instead of ``True``. The
``CORSConfig`` object can be imported from from the ``chalice`` package it's
constructor takes the following keyword arguments that map to CORS headers:

================= ==== ================================
Argument          Type Header
================= ==== ================================
allow_origin      str  Access-Control-Allow-Origin
allow_headers     list Access-Control-Allow-Headers
expose_headers    list Access-Control-Expose-Headers
max_age           int  Access-Control-Max-Age
allow_credentials bool Access-Control-Allow-Credentials
================= ==== ================================

Code sample defining more CORS headers:

.. code-block:: python

    from chalice import CORSConfig
    cors_config = CORSConfig(
        allow_origin='https://foo.example.com',
        allow_headers=['X-Special-Header'],
        max_age=600,
        expose_headers=['X-Special-Header'],
        allow_credentials=True
    )
    @app.route('/custom-cors', methods=['GET'], cors=cors_config)
    def supports_custom_cors():
        return {'cors': True}


There's a couple of things to keep in mind when enabling cors for a view:

* An ``OPTIONS`` method for preflighting is always injected.  Ensure that
  you don't have ``OPTIONS`` in the ``methods=[...]`` list of your
  view function.
* Even though the ``Access-Control-Allow-Origin`` header can be set to a
  string that is a space separated list of origins, this behavior does not
  work on all clients that implement CORS. You should only supply a single
  origin to the ``CORSConfig`` object. If you need to supply multiple origins
  you will need to define a custom handler for it that accepts ``OPTIONS``
  requests and matches the ``Origin`` header against a whitelist of origins.
  If the match is successful then return just their ``Origin`` back to them
  in the ``Access-Control-Allow-Origin`` header.

  Example:

.. code-block:: python

    from chalice import Chalice, Response

    app = Chalice(app_name='multipleorigincors')

    _ALLOWED_ORIGINS = set([
        'http://allowed1.example.com',
        'http://allowed2.example.com',
    ])

    @app.route('/cors_multiple_origins', methods=['GET', 'OPTIONS'])
    def supports_cors_multiple_origins():
        method = app.current_request.method
        if method == 'OPTIONS':
            headers = {
                'Access-Control-Allow-Method': 'GET,OPTIONS',
                'Access-Control-Allow-Origin': ','.join(_ALLOWED_ORIGINS),
                'Access-Control-Allow-Headers': 'X-Some-Header',
            }
            origin = app.current_request.headers.get('origin', '')
            if origin in _ALLOWED_ORIGINS:
                headers.update({'Access-Control-Allow-Origin': origin})
            return Response(
                body=None,
                headers=headers,
            )
        elif method == 'GET':
            return 'Foo'

---

### Source Tutorials Cdk (`docs/source/tutorials/cdk.rst`)

Deploying with the AWS CDK
==========================

In this tutorial, we're going to create a REST API with an Amazon DynamoDB
table as our data store.  We'll be using the `AWS Cloud Development Kit (CDK)
<https://aws.amazon.com/cdk/>`__
to deploy our application, and we'll show how to use the integration between
Chalice and the CDK in order to build and deploy our application.

By combining Chalice and the CDK together, you can use Chalice to
write your application code using its familiar, decorator-based APIs, and
use the CDK and the full breadth of its construct libraries to create the
service infrastructure and resources needed for your application.
We'll also see how we can use the Chalice construct to manipulate our
Chalice application using the CDK APIs as well as take resources from
CDK constructs and map them into our Chalice application.


Installation and Configuration
------------------------------

This tutorial requires that both Chalice and the AWS CDK is installed.
The CDK is written in Typescript and requires node and npm to be installed.
See the `Getting started with the AWS CDK <https://docs.aws.amazon.com/cdk/latest/guide/getting_started.html#getting_started_prerequisites>`__
for more details on install the CDK.

First, we'll install the CDK.

::

  $ npm install aws-cdk

You should now have a ``cdk`` executable you can run.

::

  $ cdk --version
  1.83.0 (build 827c5f4)

Next we'll create a Python virtual environment and install Chalice.  Be sure
to use Python 3.6 or greater.

::

  $ python3 -m venv demo
  $ . demo/bin/activate
  $ python3 -m pip install chalice
  $ chalice --version
  chalice 1.22.0, python 3.7.8, darwin 19.6.0

CDK integration with Chalice is available as an optional package installation.
To install the necessary dependencies run the following command:

::

  $ python3 -m pip install "chalice[cdkv2]"

**Note:** Please use CDK version 2, support for CDK version 1 ended
on June 1, 2023. See
`Working with the AWS CDK in Python Doc <https://docs.aws.amazon.com/cdk/v2/guide/work-with-cdk-python.html>`__
for more information.

You're now ready to create your first Chalice and CDK application.


Project Creation
----------------

To create a new project we'll use the ``chalice new-project`` command with no
arguments.  Enter a name for your project and select
``[CDK] REST API with DynamoDB backend`` for the project type.

::

  $ chalice new-project


     ___  _  _    _    _     ___  ___  ___
    / __|| || |  /_\  | |   |_ _|/ __|| __|
   | (__ | __ | / _ \ | |__  | || (__ | _|
    \___||_||_|/_/ \_\|____||___|\___||___|


  The python serverless microframework for AWS allows
  you to quickly create and deploy applications using
  Amazon API Gateway and AWS Lambda.

  Please enter the project name
  [?] Enter the project name: cdkdemo
  [?] Select your project type: [CDK] REST API with DynamoDB backend
     REST API
     S3 Event Handler
     Lambda Functions only
     Legacy REST API Template
     [CDK] REST API with DynamoDB backend

  Your project has been generated in ./cdkdemo

Next, we'll ``cd`` into the ``cdkdemo`` directory and see what Chalice has
generated.

::

  $ cd cdkdemo
  $ tree
  .
  ├── README.rst
  ├── infrastructure           # CDK Application
  │   ├── app.py
  │   ├── cdk.json
  │   ├── requirements.txt
  │   └── stacks
  │       ├── __init__.py
  │       └── chaliceapp.py
  ├── requirements.txt
  └── runtime                  # Chalice Application
      ├── app.py
      └── requirements.txt


There's two top level directories, ``infrastructure`` and ``runtime``, which
correspond to the CDK application and the Chalice application.  The
``infrastructure`` directory is where we can add additional AWS resources
needed by our application, and the ``runtime`` directory is where we write
our application code for our Lambda functions.  We'll look at these in more
detail, but first we'll deploy our application.

In order to build and deploy our application, we need to install the
dependencies used by our application.  We can do this by installing the
requirements file in the top level directory of our project.

::

  $ python3 -m pip install -r requirements.txt

If this is your first time using the CDK, you'll need to bootstrap your
account, which will deploy an AWS CloudFormation stack that contains
resources needed to store our application.  You can do this by running the
``cdk bootstrap`` command from the ``infrastructure`` directory.


::

  $ cd infrastructure
  $ cdk bootstrap
  Packaging Chalice app for cdkdemo
  Creating deployment package.
  The stack cdkdemo already includes a CDKMetadata resource
   ⏳  Bootstrapping environment aws://12345/us-west-2...
  CDKToolkit: creating CloudFormation changeset...
  [██████████████████████████████████████████████████████████] (3/3)


   ✅  Environment aws://12345/us-west-2 bootstrapped.

We can now deploy our application using the ``cdk deploy`` command.  Make sure
you're still in the ``infrastructure`` directory.


::

  $ cdk deploy
  Packaging Chalice app for cdkdemo
  Creating deployment package.
  Reusing existing deployment package.
  The stack cdkdemo already includes a CDKMetadata resource
  This deployment will make potentially sensitive changes according to your current security approval level (--require-approval broadening).
  Please confirm you intend to make the following modifications:

  ...

  Do you wish to deploy these changes (y/n)? y
  cdkdemo: deploying...
  [0%] start: Publishing abcd:current
  [100%] success: Published abcd:current
  cdkdemo: creating CloudFormation changeset...
  [██████████████████████████████████████████████████████████] (10/10)


   ✅  cdkdemo

  Outputs:
  cdkdemo.APIHandlerArn = arn:aws:lambda:us-west-2:12345:function:cdkdemo-APIHandler-C8OLGQT9YIDO
  cdkdemo.APIHandlerName = cdkdemo-APIHandler-C8OLGQT9YIDO
  cdkdemo.AppTableName = cdkdemo-AppTable815C50BC-1OPGOPFYODZOJ
  cdkdemo.EndpointURL = https://abcd.execute-api.us-west-2.amazonaws.com/api/
  cdkdemo.RestAPIId = abcd

  Stack ARN:
  arn:aws:cloudformation:us-west-2:12345:stack/cdkdemo/574c4850-1d23-11eb-8cae-0aea264da24f

We've now deployed a Chalice application powered by the CDK.  We can now test
our REST API.


.. note::
   If you've Chalice before, you may be familiar with the ``chalice deploy``
   command.  When we use the AWS CDK to deploy our application we no longer
   use ``chalice deploy`` and instead we run ``cdk deploy`` from the
   ``infrastructure/`` directory.  You should not use ``chalice deploy``
   to deploy your application when using Chalice's CDK integration.

Testing
-------

To test our application, we make HTTP requests to our ``EndpointUrl``, which is
shown as the value for ``cdkdemo.EndpointUrl`` in the output section above.
We're using `httpie <https://httpie.io/>`__ to make our HTTP requests from the
command line.

::

  $ python3 -m pip install httpie
  $ http POST https://abcd.execute-api.us-west-2.amazonaws.com/api/users/ username=jamesls name=James
  HTTP/1.1 200 OK
  ...

  {}

  $ http https://abcd.execute-api.us-west-2.amazonaws.com/api/users/jamesls
  HTTP/1.1 200 OK
  Content-Type: application/json
  ...

  {
      "name": "James",
      "username": "jamesls"
  }

Now that we have our sample application up and running, let's walk through the
project code so we can better understand what's happening.


Code Walkthrough
----------------

The ``runtime/`` directory contains code where you define your Lambda event
handlers (e.g. ``@app.route()``, ``@app.on_s3_event()``, etc.).  When you
create a Chalice application without the CDK, this is normally the root
directory for your application.  You should also see your Chalice config file
in ``.chalice/config.json``.  The ``infrastructure/`` directory contains the
definitions for the AWS resources used by your application.  This is the
directory structure that would be generated if you were only using the
CDK and not Chalice.  This is why the combined Chalice/CDK application template
has a new top level directory with separate sub directories for the CDK app
and the Chalice app.

To better understand how the two applications communicate with each other,
we'll examine how the DynamoDB table was added to the application.

First, let’s look at the code for our REST API in ``runtime/app.py``.


.. code-block:: python

  import os
  import boto3
  from chalice import Chalice


  app = Chalice(app_name='cdkdemo')
  dynamodb = boto3.resource('dynamodb')
  dynamodb_table = dynamodb.Table(os.environ.get('APP_TABLE_NAME', ''))


  @app.route('/users', methods=['POST'])
  def create_user():
      ...


  @app.route('/users/{username}', methods=['GET'])
  def get_user(username):
      ...

The name of the DynamoDB table is passed through an environment variable,
``APP_TABLE_NAME``.  We then create a ``dynamodb.Table`` resource given this
name.  This environment variable is generated and mapped in the CDK stack that
Chalice generated for us.  This is located in
``../infrastructure/stacks/chaliceapp.py``.

Let's look at the contents of the ``../infrastructure/stacks/chaliceapp.py``
file now.


.. code-block:: python

  import os

  from aws_cdk import (
      aws_dynamodb as dynamodb,
      core as cdk
  )
  from chalice.cdk import Chalice


  RUNTIME_SOURCE_DIR = os.path.join(
      os.path.dirname(os.path.dirname(__file__)), os.pardir, 'runtime')


  class ChaliceApp(cdk.Stack):

      def __init__(self, scope: cdk.Construct, id: str, **kwargs) -> None:
          super().__init__(scope, id, **kwargs)
          self.dynamodb_table = self._create_ddb_table()
          self.chalice = Chalice(
              self, 'ChaliceApp', source_dir=RUNTIME_SOURCE_DIR,
              stage_config={
                  'environment_variables': {
                      'APP_TABLE_NAME': self.dynamodb_table.table_name
                  }
              }
          )
          self.dynamodb_table.grant_read_write_data(
              self.chalice.get_role('DefaultRole')
          )

      def _create_ddb_table(self):
          dynamodb_table = dynamodb.Table(
              self, 'AppTable',
              partition_key=dynamodb.Attribute(
                  name='PK', type=dynamodb.AttributeType.STRING),
              sort_key=dynamodb.Attribute(
                  name='SK', type=dynamodb.AttributeType.STRING
              ),
              removal_policy=cdk.RemovalPolicy.DESTROY)
          cdk.CfnOutput(self, 'AppTableName',
                        value=dynamodb_table.table_name)
          return dynamodb_table


Our CDK stack is using the Chalice construct from the ``chalice.cdk``
package.  This provides us two benefits.  First, we can generate CDK resources
and pass them into our Chalice application by mapping environment variables.
Second, we can take resources generated in our Chalice application and
reference them with the CDK API.  For example, we’re generating a DynamoDB
table in the ``self._create_ddb_table()`` method, and then mapping it into our
Chalice application by providing a ``stage_config`` override.  This dictionary
is merged with the existing Chalice configuration located in
./runtime/.chalice/config.json.  If we want to pass additional values into our
Chalice application we can update the environment_variables dictionary in our
stage_config.

We’re also able to retrieve references to our resources in our Chalice
application and reference them in our CDK stack.  For example, once we’ve
created our DynamoDB table we also need to grant the IAM role associated with
your Lambda function access to this table.  We do this by using the
``grant_read_write_data`` method on our table resource, and we provide a
reference to the default role that Chalice creates for us by using the
``self.chalice.get_role()`` method.


Next Steps
----------


Feel free to experiment with this sample app.  Add new resources to your
application by updating the ``infrastructure/stacks/chaliceapp.py`` file, map
CDK resources into your Chalice app through environment variables, and
redeploy your application by running ``cdk deploy`` from the
``infrastructure/`` directory.

---

### Source Tutorials Customdomain (`docs/source/tutorials/customdomain.rst`)

Custom Domain Names
===================

In this tutorial, we're going to create a REST API and associate our own custom
domain with this REST API.  This allows us to not use the auto-generated domain
name that API Gateway automatically creates when we deploy our REST API.

Installation and Configuration
------------------------------

If you haven't already setup and configured Chalice, see the
:doc:`../quickstart` for a step by step guide.  You can run these commands
to create a basic Chalice app::

    $ python3 --version
    Python 3.10.20
    $ python3 -m venv .venv
    $ . .venv/bin/activate
    $ python3 -m pip install chalice
    $ chalice new-project customdomain
    $ cd customdomain


Configure and Deploy a REGIONAL Endpoint
----------------------------------------

Before we configure a custom domain for our REST API, we'll deploy
our REST API so we can see the auto-generated domain name that API Gateway
creates for us.

First, we'll change our endpoint type from EDGE (the default) to REGIONAL.
Update your ``.chalice/config.json`` file so it looks like this::

    $ cat .chalice/config.json
    {
      "version": "2.0",
      "app_name": "customdomain",
      "api_gateway_endpoint_type": "REGIONAL",
      "stages": {
        "dev": {
          "api_gateway_stage": "api"
        }
      }
    }

Now we'll deploy our application.  Note the URL that's printed when
your application is deployed.

::

    $ chalice deploy
    Creating deployment package.
    Creating IAM role: customdomain-dev
    Creating lambda function: customdomain-dev
    Creating Rest API
    Resources deployed:
      - Lambda ARN: arn:aws:lambda:us-west-2:12345:function:customdomain-dev
      - Rest API URL: https://qxea58abcd.execute-api.us-west-2.amazonaws.com/api/

You now have an API up and running using API Gateway and Lambda::

    $ curl https://qxea58abcd.execute-api.us-west-2.amazonaws.com/api/
    {"hello": "world"}

The ``qxea58abcd.execute-api.us-west-2.amazonaws.com`` domain name was
auto-generated by API Gateway.  Replacing this domain name with our own
custom domain name allows us to use simpler and more intuitive URLs that
we can provide to our API users.


Configuring a Custom Domain
---------------------------

In this tutorial, we're using Amazon Route53 to manage our DNS configuration.
If you're using a third-party domain registrar, the steps will be similar, but
you will have to create your DNS records using your provider's web interface or
API.

For this tutorial, we'll configure the domain ``chalice-demo-app.com``.  Be
sure to replace this value with your own domain name.

Creating a Hosted Zone
~~~~~~~~~~~~~~~~~~~~~~

First, we'll need to create a hosted zone in Route 53.  If you already have
a hosted zone created for your domain, you can skip this step.

We'll be using the AWS CLI V2 to configure our domain.  You can follow
the
`installation instructions <https://docs.aws.amazon.com/cli/latest/userguide/install-cliv2.html>`__
if you don't have the AWS CLI installed.

::

    $ aws route53 create-hosted-zone --name chalice-demo-app.com --caller-reference 12345
    {
        "Location": "https://route53.amazonaws.com/2013-04-01/hostedzone/ZABCDEFGABCDEFGLDO822",
        "HostedZone": {
            "Id": "/hostedzone/ZABCDEFGABCDEFGLDO822",
            "Name": "chalice-demo-app.com.",
            "CallerReference": "12345",
            "Config": {
                "PrivateZone": false
            },
            "ResourceRecordSetCount": 2
        },
        "ChangeInfo": {
            "Id": "/change/C07395431VDLB0CY65VP",
            "Status": "PENDING",
            "SubmittedAt": "2020-07-21T17:13:54.709000+00:00"
        },
        "DelegationSet": {
            "NameServers": [
                "ns-123.awsdns-31.net",
                "ns-123.awsdns-05.com",
                "ns-123.awsdns-09.org",
                "ns-123.awsdns-40.co.uk"
            ]
        }
    }

You'll need to save the value of the hosted zone id for later.
From the output above the line ``"Id": "/hostedzone/ZABCDEFGABCDEFGLDO822",``
contains our hosted zone id of ``ZABCDEFGABCDEFGLDO822``.  We'll refer
to this value as ``$OUR_HOSTED_ZONE_ID`` later.

You'll now need to register the ``"NameServers"`` shown in the output above
with your domain registrar.


Creating an ACM Certificate
~~~~~~~~~~~~~~~~~~~~~~~~~~~

Now that we have our hosted zone, we'll need to create an ACM certificate
associated with this domain.  This is the SSL/TLS certificate that will be
used when requests are made to our custom domain.  In this example, we'll
create a wildcard certificate for ``*.chalice-demo-app.com``.  Note that
we're creating a ``REGIONAL`` endpoint type for our REST API, which means
that our ACM certificate **must** be in the same region as our REST API.
In this example, we're using ``us-west-2``.  If you're using the default
``EDGE`` endpoint type, the ACM cert must be in ``us-east-1``.  You can
explicitly specify the region using the ``--region`` CLI parameter if needed.

::

    $ aws acm request-certificate --domain-name "*.chalice-demo-app.com" \
        --validation-method DNS --idempotency-token 12345 \
        --options CertificateTransparencyLoggingPreference=DISABLED
    {
        "CertificateArn": "arn:aws:acm:us-west-2:0123456789:certificate/578efbda-6bc7-4ae2-9964-6e6c3f58008b"
    }

Save the value of ``CertificateArn`` shown in the output above.  We'll
need this value when we configure our app to use this custom domain.

Before we can use this certificate, we need to validate this certificate.
This process demonstrates that we own or control the domain name associated
with the certificate.
In the command above, we used the ``--validation-method DNS``, which
requires us to add CNAME records to validate we control our domain name.
ACM supports both `DNS validation <https://docs.aws.amazon.com/acm/latest/userguide/gs-acm-validate-dns.html>`__
as well as `email validation <https://docs.aws.amazon.com/acm/latest/userguide/gs-acm-validate-email.html>`__.

To validate our domain, we'll now create the necessary CNAME records in our
hosted zone using the Route53 API.  First, we need to retrieve the values
for our CNAME record.  Be sure to replace the value of ``--certificate-arn``
with your own certificate ARN in the command below::

    $ aws acm describe-certificate --certificate-arn arn:aws:acm:us-west-2:0123456789:certificate/578efbda-6bc7-4ae2-9964-6e6c3f58008b \
        --query Certificate.DomainValidationOptions[0]
    {
        "DomainName": "*.chalice-demo-app.com",
        "ValidationDomain": "*.chalice-demo-app.com",
        "ValidationStatus": "PENDING_VALIDATION",
        "ResourceRecord": {
            "Name": "_1234567891234567897eb5512d9fb554.chalice-demo-app.com.",
            "Type": "CNAME",
            "Value": "_123456789123456789e7495341c27cd1.jfrzftwwjs.acm-validations.aws."
        },
        "ValidationMethod": "DNS"
    }

Next we'll create a CNAME record for
``_1234567891234567897eb5512d9fb554.chalice-demo-app.com.`` with a value of
``_123456789123456789e7495341c27cd1.jfrzftwwjs.acm-validations.aws.``::

    $ aws route53 change-resource-record-sets \
        --hosted-zone-id $OUR_HOSTED_ZONE_ID --change-batch \
    '{
      "Changes": [
        {
          "Action": "CREATE",
          "ResourceRecordSet": {
            "Name": "_0073e080112eb8de8c7eb5512d9fb554.chalice-demo-app.com.",
            "Type": "CNAME",
            "TTL": 300,
            "ResourceRecords": [{"Value": "_6e560a5a9831aad210e7495341c27cd1.jfrzftwwjs.acm-validations.aws."}]
          }
        }
      ]
    }'

    # Command output:
    {
        "ChangeInfo": {
            "Id": "/change/C0339874QPDDRA8TKT7U",
            "Status": "PENDING",
            "SubmittedAt": "2020-07-21T17:36:39.902000+00:00"
        }
    }

It will take a few minutes before ACM validates your domain.  You can
move on to the next steps, or if you'd like to wait until the domain is
validated you can use the CLI's ``certificate-validated`` waiter, which
will block until the ACM certificate is validated::

    $ aws acm wait certificate-validated \
        --certificate-arn arn:aws:acm:us-west-2:0123456789:certificate/578efbda-6bc7-4ae2-9964-6e6c3f58008b


Chalice App Configuration
~~~~~~~~~~~~~~~~~~~~~~~~~

Now that we have our hosted zone and ACM certificate created, we can configure
our Chalice application with our custom domain.  To do so we need to add
`api_gateway_custom_domain <https://aws.github.io/chalice/topics/configfile.html#api-gateway-custom-domain>`__
configuration option and specify our ACM certificate ARN as well as the our
custom domain name.  You're ``.chalice/config.json`` file should look like
this:

.. code-block:: json

    {
      "version": "2.0",
      "app_name": "customdomain",
      "api_gateway_endpoint_type": "REGIONAL",
      "stages": {
        "dev": {
          "api_gateway_custom_domain": {
            "domain_name": "api.chalice-demo-app.com",
            "certificate_arn": "arn:aws:acm:us-west-2:0123456789:certificate/578efbda-6bc7-4ae2-9964-6e6c3f58008b"
          },
          "api_gateway_stage": "api"
        }
      }
    }

We we rerun the ``chalice deploy`` command you'll notice there's a new
``Custom domain name:`` line in the output::

    $ chalice deploy
    Creating deployment package.
    Updating policy for IAM role: customdomain-dev
    Updating lambda function: customdomain-dev
    Updating rest API
    Creating custom domain name: api.chalice-demo-app.com
    Creating api mapping: /
    Resources deployed:
      - Lambda ARN: arn:aws:lambda:us-west-2:0123456789:function:customdomain-dev
      - Rest API URL: https://qxea58abcd.execute-api.us-west-2.amazonaws.com/api/
      - Custom domain name:
          HostedZoneId: Z1UJRXOUMOOFQ8
          AliasDomainName: d-6vj4cynstd.execute-api.us-west-2.amazonaws.com

Now that we've configured our Chalice app with our custom domain, there's one
step left.  We need to update our DNS configuration to point to our REST API.

To do this, we'll use the values of ``HostedZoneId`` and ``AliasDomainName``
in the output above to create an alias record in our hosted zone.

Alias Record Configuration
~~~~~~~~~~~~~~~~~~~~~~~~~~

You can run the following command to create an alias record to your REST API.
Note that there are two different hosted zone id values here.  The value
specified as the ``--hosted-zone-id`` value is the ID of our hosted zone
ID (``$OUR_HOSTED_ZONE_ID``) that we created earlier in this example.
The value of the ``HostedZoneId`` in the ``AliasTarget`` section is the
value of the ``HostedZoneId`` generated by API Gateway shown in the output
of ``chalice deploy`` above.

::

    $ aws route53 change-resource-record-sets --hosted-zone-id ZABCDEFGABCDEFGLDO822 --change-batch \
    '{
      "Changes": [
        {
          "Action": "CREATE",
          "ResourceRecordSet": {
            "Name": "api.chalice-demo-app.com",
            "Type": "A",
            "AliasTarget": {
              "DNSName": "d-6vj4cynstd.execute-api.us-west-2.amazonaws.com",
              "HostedZoneId": "Z1UJRXOUMOOFQ8",
              "EvaluateTargetHealth": false
            }
          }
        }
      ]
    }'

    # Command output:
    {
        "ChangeInfo": {
            "Id": "/change/C0539657Y0HMX8XBC5EH",
            "Status": "PENDING",
            "SubmittedAt": "2020-07-21T17:52:34.935000+00:00"
        }
    }


Verification
------------

Our Chalice application is now configured to use our custom domain.
We can verify this by making a request to our custom domain.  In this
example, this is ``api.chalice-demo-app.com``::

    $ curl -i https://api.chalice-demo-app.com/
    HTTP/1.1 200 OK
    Date: Tue, 21 Jul 2020 17:56:00 GMT
    Content-Type: application/json
    Content-Length: 17
    Connection: keep-alive
    x-amzn-RequestId: 9f33fbb9-6b10-469e-827f-f287199c9bc5
    x-amz-apigw-id: QCPXoEwPIAMFi8Q=
    X-Amzn-Trace-Id: Root=1-5f172c30-dccc232932a16a539dfc01b9;Sampled=0

    {"hello":"world"}


Next Steps
----------

For more information on configuring custom domains, check out our
:doc:`topic guide <../topics/domainname>` on custom domains as well
as the config file reference for the
:ref:`custom-domain-config-options` and the
:ref:`custom-domain-ws-config-options` options.

---

### Source Tutorials Events (`docs/source/tutorials/events.rst`)

Event Sources Tutorial
======================

In the :doc:`../quickstart` guide, we looked at how to create a
REST API using the ``@app.route()`` decorator.  Chalice also has
additional decorators that connects your code to specific event sources.
This results in your code being invoked when a specific event occurs.

In this tutorial we'll look at a few examples.

Installation and Configuration
------------------------------

If you haven't already setup and configured Chalice, see the
:doc:`../quickstart` for a step by step guide.  In a nutshell, you can get a
basic Chalice app created with::

    $ python3 --version
    Python 3.10.20
    $ python3 -m venv .venv
    $ . .venv/bin/activate
    $ python3 -m pip install chalice
    $ chalice new-project chalice-sns-demo
    $ cd chalice-sns-demo


We'll also be using the AWS CLI in this tutorial.  You can follow
`these instructions <https://docs.aws.amazon.com/cli/latest/userguide/install-cliv2.html>`__
for installing the AWS CLI v2.


Amazon SNS Topics
-----------------

In this first example, we'll create a Chalice application that will
call our Lambda function whenever a message is published to an
`SNS Topic <https://aws.amazon.com/sns/>`__.

First, we'll create an SNS topic.  This is what we'll connect to our
Lambda function::

    $ aws sns create-topic --name MyDemoTopic
    {
        "TopicArn": "arn:aws:sns:us-west-2:12345:MyDemoTopic"
    }

Be sure to save the ``TopicArn`` value for later.  In this example
that would be ``arn:aws:sns:us-west-2:12345:MyDemoTopic``.

Next, we'll update the ``app.py`` to create a lambda function that
connects to an SNS topic:

.. code-block:: python

    from chalice import Chalice

    app = Chalice(app_name='chalice-sns-demo', debug=True)

    @app.on_sns_message(topic='MyDemoTopic')
    def handle_sns_message(event):
        app.log.debug("Received message with subject: %s, message: %s",
                      event.subject, event.message)

In the code above, we're using the ``@app.on_sns_message()`` decorator to
connect the SNS topic named ``MyDemoTopic`` with the ``handle_sns_message``
function.  Note that we're using the name of the topic and not the
``TopicArn``.

Now we can deploy our chalice app::

    $ chalice deploy
    Creating deployment package.
    Creating IAM role: chalice-demo-sns-dev
    Creating lambda function: chalice-demo-sns-dev-handle_sns_message
    Subscribing chalice-demo-sns-dev-handle_sns_message to SNS topic my-demo-topic
    Resources deployed:
      - Lambda ARN: arn:aws:lambda:us-west-2:123:function:...

Now we can test our app by publishing a few SNS messages to our topic.

::

    $ aws sns publish --topic-arn arn:aws:sns:us-west-2:12345:MyDemoTopic \
        --subject TestSubject --message TestMessage
    {
        "MessageId": "abcdefgh-3e56-54bd-a471-72477b5388af"
    }
    $ aws sns publish --topic-arn arn:aws:sns:us-west-2:12345:MyDemoTopic \
        --subject TestSubject2 --message TestMessage2
    {
        "MessageId": "abcdefgh-3e56-54bd-a471-72477b5388ag"
    }

We should now see log messages showing that our Lambda function was invoked.
We can wait for the messages using the ``chalice logs`` command.

::

    $ chalice logs --follow -n handle_sns_message
    ... 217378 chalice-sns-demo - DEBUG - Received message with subject: TestSubject, message: TestMessage
    ... 217378 chalice-sns-demo - DEBUG - Received message with subject: TestSubject2, message: TestMessage2

Next Steps
----------

In addition to SNS, chalice supports other event sources including Amazon S3,
Amazon SQS, as well as scheduled events.  You can check out the topic guide
on :doc:`../topics/events` for more details.

Cleaning Up
-----------

Once you're done experimenting you can clean up by deleting the Chalice
app and deleting the SNS topic::

    $ chalice delete
    Deleting function: arn:aws:lambda:us-west-2:21345:function:chalice-sns-demo...
    Deleting IAM role: chalice-sns-demo-dev
    $ aws sns delete-topic --topic-arn arn:aws:sns:us-west-2:12345:MyDemoTopic

---

### Source Tutorials Index (`docs/source/tutorials/index.rst`)

Tutorials
=========

These step-by-step tutorials show you how to use various features of Chalice.
These are perfect if you're new to Chalice and want to learn what Chalice can
do.  If you want more complete, real-world examples, you can check out
our :doc:`../samples/index`.

Rest API Tutorials
------------------

:doc:`basicrestapi`
  This tutorial walks you through creating a REST API in
  Chalice.  It covers features such as routing, URL parameters, error handling,
  etc.
:doc:`customdomain`
  In this tutorial, we show you how to configure
  a REST API with your own custom domain name.


.. _websocket-tutorial:

Websocket Tutorials
-------------------


:doc:`wsecho`
  Learn the basics of creating websocket APIs in Chalice.  This tutorial
  creates an echo server that echoes back any message that the client sends to
  the websocket server.
:doc:`wschat`
  In this more complete example, learn how to create a basic chat application
  based on websockets.


Event Source Tutorials
----------------------


:doc:`events`
  This tutorial shows you how to create an event handler
  that's triggered whenever a message is published to an SNS topic.

.. toctree::
   :hidden:
   :glob:

   *

AWS CDK Tutorials
-----------------

:doc:`cdk`
  This tutorial walks you through creating a REST API with a DynamoDB data
  store that's deployed using the AWS CDK.  It shows you how you can combine
  the APIs of Chalice with CDK construct APIs

---

### Source Tutorials Wschat (`docs/source/tutorials/wschat.rst`)

Chat Server Example
===================

.. note::

  This example is for illustration purposes and does not represent best
  practices.

A simple chat server example application. This example will walk through
deploying a chat application with separate chat rooms and nicknames. It uses
a DynamoDB table to store state like connection IDs between websocket messages.


First install a copy of Chalice in a fresh environment, create a new project
and cd into the directory::

  $ pip install -U chalice
  $ chalice new-project chalice-chat-example
  $ cd chalice-chat-example


Our Chalice application will need boto3 as a dependency for both DynamoDB
access and in order to communicate back with API Gateway to send websocket
messages. Let's add a boto3 to the ``requirements.txt`` file::

  $ echo "boto3>=1.9.91" > requirements.txt


Now that the requirement has been added. Let's install it locally since our
next script will need it as well::

  $ pip install -r requirements.txt

Unlike our previous example where we used ``chalice deploy``, we will use
``chalice package`` to create a CloudFormation template. The AWS CLI will be
used to deploy the template. To install the AWS CLI run the command::

  $ pip install -U awscli

Starting in Chalice 1.10, the package command has a ``--merge-template``
argument that allows us to merge in a custom JSON file to the generated
CloudFormation template. Since Chalice does not have any built-in support for
DynamoDB currently, we will make a ``resources.json`` file with the DynamoDB
definition. The template file will set the environment variable TABLE in all
our Lambda functions as a CloudFormatiion reference to the DynamoDB table.
Finally, the template will also override our IAM policy with a custom one to
allow all the DynamoDB operations our application will need.

Below is the JSON file that contains all of our custom Cloudformation.

.. code-block:: json
   :caption: resources.json

   {
     "Resources": {
       "ChaliceChatTable": {
         "Type": "AWS::DynamoDB::Table",
         "Properties": {
           "AttributeDefinitions": [
             {
               "AttributeName": "PK",
               "AttributeType": "S"
             },
             {
               "AttributeName": "SK",
               "AttributeType": "S"
             }
           ],
           "KeySchema": [
             {
               "AttributeName": "PK",
               "KeyType": "HASH"
             },
             {
               "AttributeName": "SK",
               "KeyType": "RANGE"
             }
           ],
           "GlobalSecondaryIndexes": [
             {
               "IndexName": "ReverseLookup",
               "KeySchema": [
                 {
                   "AttributeName": "SK",
                   "KeyType": "HASH"
                 },
                 {
                   "AttributeName": "PK",
                   "KeyType": "RANGE"
                 }
               ],
               "Projection": {
                 "ProjectionType": "ALL"
               },
               "ProvisionedThroughput": {
                 "ReadCapacityUnits": 1,
                 "WriteCapacityUnits": 1
               }
             }
           ],
           "ProvisionedThroughput": {
             "ReadCapacityUnits": 1,
             "WriteCapacityUnits": 1
           },
           "TableName": "ChaliceChat"
         }
       },
       "WebsocketConnect": {
         "Properties": {
           "Environment": {
             "Variables": {
               "TABLE": {
                 "Ref": "ChaliceChatTable"
               }
             }
           }
         }
       },
       "WebsocketMessage": {
         "Properties": {
           "Environment": {
             "Variables": {
               "TABLE": {
                 "Ref": "ChaliceChatTable"
               }
             }
           }
         }
       },
       "WebsocketDisconnect": {
         "Properties": {
           "Environment": {
             "Variables": {
               "TABLE": {
                 "Ref": "ChaliceChatTable"
               }
             }
           }
         }
       },
       "DefaultRole": {
         "Type": "AWS::IAM::Role",
         "Properties": {
           "AssumeRolePolicyDocument": {
             "Version": "2012-10-17",
             "Statement": [
               {
                 "Sid": "",
                 "Effect": "Allow",
                 "Principal": {
                   "Service": "lambda.amazonaws.com"
                 },
                 "Action": "sts:AssumeRole"
               }
             ]
           },
           "Policies": [
             {
               "PolicyName": "DefaultRolePolicy",
               "PolicyDocument": {
                 "Version": "2012-10-17",
                 "Statement": [
                   {
                     "Effect": "Allow",
                     "Action": [
                       "logs:CreateLogGroup",
                       "logs:CreateLogStream",
                       "logs:PutLogEvents"
                     ],
                     "Resource": "arn:aws:logs:*:*:*"
                   },
                   {
                     "Effect": "Allow",
                     "Action": [
                       "execute-api:ManageConnections"
                     ],
                     "Resource": "arn:aws:execute-api:*:*:*/@connections/*"
                   },
                   {
                     "Effect": "Allow",
                     "Action": [
                       "dynamodb:DeleteItem",
                       "dynamodb:PutItem",
                       "dynamodb:GetItem",
                       "dynamodb:UpdateItem",
                       "dynamodb:Query",
                       "dynamodb:Scan"
                     ],
                     "Resource": [
                       {
                         "Fn::Sub": "arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/${ChaliceChatTable}"
                       },
                       {
                         "Fn::Sub": "arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/${ChaliceChatTable}/index/ReverseLookup"
                       }
                     ]
                   }
                 ]
               }
             }
           ]
         }
       }
     }
   }

The current directory layout should now look like this::

 $ tree -a .
 .
 ├── .chalice
 │   └── config.json
 ├── .gitignore
 ├── app.py
 ├── resources.json
 └── requirements.txt

 1 directory, 5 files


Next let's fill out the ``app.py`` file since it is pretty simple. Most of this
example code is contained in the ``chalicelib/`` directory.

.. code-block:: python
   :caption: chalice-chat-example/app.py

   from boto3.session import Session

   from chalice import Chalice

   from chalicelib import Storage
   from chalicelib import Sender
   from chalicelib import Handler

   app = Chalice(app_name="chalice-chat-example")
   app.websocket_api.session = Session()
   app.experimental_feature_flags.update([
       'WEBSOCKETS'
   ])

   STORAGE = Storage.from_env()
   SENDER = Sender(app, STORAGE)
   HANDLER = Handler(STORAGE, SENDER)


   @app.on_ws_connect()
   def connect(event):
       STORAGE.create_connection(event.connection_id)


   @app.on_ws_disconnect()
   def disconnect(event):
       STORAGE.delete_connection(event.connection_id)


   @app.on_ws_message()
   def message(event):
       HANDLER.handle(event.connection_id, event.body)


Similar to the previous example. We need to use ``boto3`` to construct a
Session and pass it to ``app.websocket_api.session``. We opt into the
usage of the ``WEBSOCKET`` experimental feature. Most of the actual work is
done in some classes that we import from ``chalicelib/``. These classes are
detailed below, and the various parts are explained in comments and doc
strings. In addition to the previous example, we register a handler for
``on_ws_connect`` and ``on_ws_disconnect`` to handle events from API gateway
when a new socket is trying to connect, or an existing socket is disconnected.

Finally before being able to deploy and test the app out, we need to fill out
the chalicelib directory. This is the bulk of the app and it is explained
inline in comments. Create a new directory called ``chalicelib`` and inside
that directory create an ``__init__.py`` file and fill it out with the
following file.

.. code-block:: python
   :caption: chalice-chat-example/chalicelib/__init__.py

   import os

   import boto3
   from boto3.dynamodb.conditions import Key

   from chalice import WebsocketDisconnectedError


   class Storage(object):
       """An abstraction to interact with the DynamoDB Table."""
       def __init__(self, table):
           """Initialize Storage object

           :param table: A boto3 dynamodb Table resource object.
           """
           self._table = table

       @classmethod
       def from_env(cls):
           """Create table from the environment.

           The environment variable TABLE is present for a deployed application
           since it is set in all of the Lambda functions by a CloudFormation
           reference. We default to '', which will happen when we run
           ``chalice package`` since it loads the application, and no
           environment variable has been set. For local testing, a value should
           be manually set in the environment if '' will not suffice.
           """
           table_name = os.environ.get('TABLE', '')
           table = boto3.resource('dynamodb').Table(table_name)
           return cls(table)

       def create_connection(self, connection_id):
           """Create a new connection object in the dtabase.

           When a new connection is created, we create a stub for
           it in the table. The stub uses a primary key of the
           connection_id and a sort key of username_. This translates
           to a connection with an unset username. The first message
           sent over the wire from the connection is to be used as the
           username, and this entry will be re-written.

           :param connection_id: The connection id to write to
               the table.
           """
           self._table.put_item(
               Item={
                   'PK': connection_id,
                   'SK': 'username_',
               },
           )

       def set_username(self, connection_id, old_name, username):
           """Set the username.

           The SK entry that goes with this connection id that starts
           with username_ is taken to be the username. The previous
           entry needs to be deleted, and a new entry needs to be
           written.

           :param connection_id: Connection id of the user trying to
               change their name.

           :param old_name: The original username. Since this is part of
               the key, it needs to be deleted and re-created rather than
               updated.

           :param username: The new username the user wants.
           """
           self._table.delete_item(
               Key={
                   'PK': connection_id,
                   'SK': 'username_%s' % old_name,
               },
           )
           self._table.put_item(
               Item={
                   'PK': connection_id,
                   'SK': 'username_%s' % username,
               },
           )

       def list_rooms(self):
           """Get a list of all rooms that exist.

           Scan through the table looking for SKs that start with room_
           which indicates a room that a user is in. Collect a unique set
           of those and return them.
           """
           r = self._table.scan()
           rooms = set([item['SK'].split('_', 1)[1] for item in r['Items']
                        if item['SK'].startswith('room_')])
           return rooms

       def set_room(self, connection_id, room):
           """Set the room a user is currently in.

           The room a user is in is in the form of an SK that starts with
           room_ prefix.

           :param connection_id: The connection id to move to a room.

           :param room: The room name to join.
           """
           self._table.put_item(
               Item={
                   'PK': connection_id,
                   'SK': 'room_%s' % room,
               },
           )

       def remove_room(self, connection_id, room):
           """Remove a user from a room.

           The room a user is in is in the form of an SK that starts with
           room_ prefix. To leave a room we need to delete this entry.

           :param connection_id: The connection id to move to a room.

           :param room: The room name to join.
           """
           self._table.delete_item(
               Key={
                   'PK': connection_id,
                   'SK': 'room_%s' % room,
               },
           )

       def get_connection_ids_by_room(self, room):
           """Find all connection ids that go to a room.

           This is needed whenever we broadcast to a room. We collect all
           their connection ids so we can send messages to them. We use a
           ReverseLookup table here which inverts the PK, SK relationship
           creating a partition called room_{room}. Everything in that
           partition is a connection in the room.

           :param room: Room name to get all connection ids from.
           """
           r = self._table.query(
               IndexName='ReverseLookup',
               KeyConditionExpression=(
                   Key('SK').eq('room_%s' % room)
               ),
               Select='ALL_ATTRIBUTES',
           )
           return [item['PK'] for item in r['Items']]

       def delete_connection(self, connection_id):
           """Delete a connection.

           Called when a connection is disconnected and all its entries need
           to be deleted.

           :param connection_id: The connection partition to delete from
               the table.
           """
           try:
               r = self._table.query(
                   KeyConditionExpression=(
                       Key('PK').eq(connection_id)
                   ),
                   Select='ALL_ATTRIBUTES',
               )
               for item in r['Items']:
                   self._table.delete_item(
                       Key={
                           'PK': connection_id,
                           'SK': item['SK'],
                       },
                   )
           except Exception as e:
               print(e)

       def get_record_by_connection(self, connection_id):
           """Get all the properties associated with a connection.

           Each connection_id creates a partition in the table with multiple
           SK entries. Each SK entry is in the format {property}_{value}.
           This method reads all those records from the database and puts them
           all into dictionary and returns it.

           :param connection_id: The connection to get properties for.
           """
           r = self._table.query(
               KeyConditionExpression=(
                   Key('PK').eq(connection_id)
               ),
               Select='ALL_ATTRIBUTES',
           )
           r = {
               entry['SK'].split('_', 1)[0]: entry['SK'].split('_', 1)[1]
               for entry in r['Items']
           }
           return r


   class Sender(object):
       """Class to send messages over websockets."""
       def __init__(self, app, storage):
           """Initialize a sender object.

           :param app: A Chalice application object.

           :param storage: A Storage object.
           """
           self._app = app
           self._storage = storage

       def send(self, connection_id, message):
           """Send a message over a websocket.

           :param connection_id: API Gateway Connection ID to send a
               message to.

           :param message: The message to send to the connection.
           """
           try:
               # Call the chalice websocket api send method
               self._app.websocket_api.send(connection_id, message)
           except WebsocketDisconnectedError as e:
               # If the websocket has been closed, we delete the connection
               # from our database.
               self._storage.delete_connection(e.connection_id)

       def broadcast(self, connection_ids, message):
           """"Send a message to multiple connections.

           :param connection_id: A list of API Gateway Connection IDs to
               send the message to.

           :param message: The message to send to the connections.
           """
           for cid in connection_ids:
               self.send(cid, message)


   class Handler(object):
       """Handler object that handles messages received from a websocket.

       This class implements the bulk of our app behavior.
       """
       def __init__(self, storage, sender):
           """Initialize a Handler object.

           :param storage: Storage object to interact with database.

           :param sender: Sender object to send messages to websockets.
           """
           self._storage = storage
           self._sender = sender
           # Command table to translate a string command name into a
           # method to call.
           self._command_table = {
               'help': self._help,
               'nick': self._nick,
               'join': self._join,
               'room': self._room,
               'quit': self._quit,
               'ls': self._list,
           }

       def handle(self, connection_id, message):
           """Entry point for our application.

           :param connection_id: Connection id that the message came from.

           :param message: Message we got from the connection.
           """
           # First look the user up in the database and get a record for it.
           record = self._storage.get_record_by_connection(connection_id)
           if record['username'] == '':
               # If the user does not have a username, we assume that the message
               # is the username they want and we call _handle_login_message.
               self._handle_login_message(connection_id, message)
           else:
               # Otherwise we assume the user is logged in. So we call
               # a method to handle the message. We pass along the
               # record we loaded from the database so we don't need to
               # again.
               self._handle_message(connection_id, message, record)

       def _handle_login_message(self, connection_id, message):
           """Handle a login message.

           The message is the username to give the user. Re-write the
           database entry for this user to reset their username from ''
           to {message}. Once that is done send a message back to the user
           to confirm the name choice. Also send a /help prompt.
           """
           self._storage.set_username(connection_id, '', message)
           self._sender.send(
               connection_id,
               'Using nickname: %s\nType /help for list of commands.' % message
           )

       def _handle_message(self, connection_id, message, record):
           """"Handle a message from a connected and logged in user.

           If the message starts with a / it's a command. Otherwise its a
           text message to send to all rooms in the room.

           :param connection_id: Connection id that the message came from.

           :param message: Message we got from the connection.

           :param record: A data record about the sender.
           """
           if message.startswith('/'):
               self._handle_command(connection_id, message[1:], record)
           else:
               self._handle_text(connection_id, message, record)

       def _handle_command(self, connection_id, message, record):
           """Handle a command message.

           Check the command name and look it up in our command table.
           If there is an entry, we call that method and pass along
           the connection_id, arguments, and the loaded record.

           :param connection_id: Connection id that the message came from.

           :param message: Message we got from the connection.

           :param record: A data record about the sender.
           """
           args = message.split(' ')
           command_name = args.pop(0).lower()
           command = self._command_table.get(command_name)
           if command:
               command(connection_id, args, record)
           else:
               # If no command method is found, send an error message
               # back to the user.
               self._sender(
                   connection_id, 'Unknown command: %s' % command_name)

       def _handle_text(self, connection_id, message, record):
           """Handle a raw text message.

           :param connection_id: Connection id that the message came from.

           :param message: Message we got from the connection.

           :param record: A data record about the sender.
           """
           if 'room' not in record:
               # If the user is not in a room send them an error message
               # and return early.
               self._sender.send(
                   connection_id, 'Cannot send message if not in chatroom.')
               return
           # Collect a list of connection_ids in the same room as the message
           # sender.
           connection_ids = self._storage.get_connection_ids_by_room(
               record['room'])
           # Prefix the message with the sender's name.
           message = '%s: %s' % (record['username'], message)
           # Broadcast the new message to everyone in the room.
           self._sender.broadcast(connection_ids, message)

       def _help(self, connection_id, _message, _record):
           """Send the help message.

           Build a help message and send back to the same connection.

           :param connection_id: Connection id that the message came from.
           """
           self._sender.send(
               connection_id,
               '\n'.join([
                   'Commands available:',
                   '    /help',
                   '          Display this message.',
                   '    /join {chat_room_name}',
                   '          Join a chatroom named {chat_room_name}.',
                   '    /nick {nickname}',
                   '          Change your name to {nickname}. If no {nickname}',
                   '          is provided then your current name will be printed',
                   '    /room',
                   '          Print out the name of the room you are currently ',
                   '          in.',
                   '    /ls',
                   '          If you are in a room, list all users also in the',
                   '          room. Otherwise, list all rooms.',
                   '    /quit',
                   '          Leave current room.',
                   '',
                   'If you are in a room, raw text messages that do not start ',
                   'with a / will be sent to everyone else in the room.',
               ]),
           )

       def _nick(self, connection_id, args, record):
           """Change or check nickname (username).

           :param connection_id: Connection id that the message came from.

           :param args: Argument list that came after the command.

           :param record: A data record about the sender.
           """
           if not args:
               # If a nickname argument was not provided, we just want to
               # report the current nickname to the user.
               self._sender.send(
                   connection_id, 'Current nickname: %s' % record['username'])
               return
           # The first argument is assumed to be the new desired nickname.
           nick = args[0]
           # Change the username from record['username'] to nick in the storage
           # layer.
           self._storage.set_username(connection_id, record['username'], nick)
           # Send a message to the requestor to confirm the nickname change.
           self._sender.send(connection_id, 'Nickname is: %s' % nick)
           # Get the room the user is in.
           room = record.get('room')
           if room:
               # If the user was in a room, announce to the room they have
               # changed their name. Don't send this me sage to the user since
               # they already got a name change message.
               room_connections = self._storage.get_connection_ids_by_room(room)
               room_connections.remove(connection_id)
               self._sender.broadcast(
                   room_connections,
                   '%s is now known as %s.' % (record['username'], nick))

       def _join(self, connection_id, args, record):
           """Join a chat room.

           :param connection_id: Connection id that the message came from.

           :param args: Argument list. The first argument should be the
              name of the room to join.

           :param record: A data record about the sender.
           """
           # Get the room name to join.
           room = args[0]
           # Call quit to leave the current room we are in if there is any.
           self._quit(connection_id, '', record)
           # Get a list of connections in the target chat room.
           room_connections = self._storage.get_connection_ids_by_room(room)
           # Join the target chat room.
           self._storage.set_room(connection_id, room)
           # Send a message to the requestor that they have joined the room.
           # At the same time send an announcement to everyone who was already
           # in the room to alert them of the new user.
           self._sender.send(
               connection_id, 'Joined chat room "%s"' % room)
           message = '%s joined room.' % record['username']
           self._sender.broadcast(room_connections, message)

       def _room(self, connection_id, _args, record):
           """Report the name of the current room.

           :param connection_id: Connection id that the message came from.

           :param record: A data record about the sender.
           """
           if 'room' in record:
               # If the user is in a room send them the name back.
               self._sender.send(connection_id, record['room'])
           else:
               # If the user is not in a room. Tell them so, and how to
               # join a room.
               self._sender.send(
                   connection_id,
                   'Not currently in a room. Type /join {room_name} to do so.'
               )

       def _quit(self, connection_id, _args, record):
           """Quit from a room.

           :param connection_id: Connection id that the message came from.

           :param record: A data record about the sender.
           """
           if 'room' not in record:
               # If the user is not in a room there is nothing to do.
               return
           # Find the current room name, and delete that entry from
           # the database.
           room_name = record['room']
           self._storage.remove_room(connection_id, room_name)
           # Send a message to the user to inform them they left the room.
           self._sender.send(
               connection_id, 'Left chat room "%s"' % room_name)
           # Tell everyone in the room that the user has left.
           self._sender.broadcast(
               self._storage.get_connection_ids_by_room(room_name),
               '%s left room.' % record['username'],
           )

       def _list(self, connection_id, _args, record):
           """Show a context dependent listing.

           :param connection_id: Connection id that the message came from.

           :param record: A data record about the sender.
           """
           room = record.get('room')
           if room:
               # If the user is in a room, get a listing of everyone
               # in the room.
               result = [
                   self._storage.get_record_by_connection(c_id)['username']
                   for c_id in self._storage.get_connection_ids_by_room(room)
               ]
           else:
               # If they are not in a room. Get a listing of all rooms
               # currently open.
               result = self._storage.list_rooms()
           # Send the result list back to the requestor.
           self._sender.send(connection_id, '\n'.join(result))


The final directory layout should be ::

    $ tree -a .
    .
    ├── .chalice
    │   ├── config.json
    ├── .gitignore
    ├── app.py
    ├── chalicelib
    │   └── __init__.py
    ├── resources.json
    └── requirements.txt

    2 directories, 6 files


Deploying our app with CloudFormation requires 3 steps. First we use Chalice
to package our app into a JSON CloudFormation template::

  $ chalice package --merge-template resources.json out

This will result in a new directory called ``out`` being created, inside which
there is a ``sam.json`` file. This template contains our Chalice app as a
CloudFormation template, merged with our ``resources.json`` template.

Next we use the AWS CLI to package this template, and prepare it for
deployment. In order for this to work you will need to replace ``$BUCKET``
with the name of a bucket you control::

  $ aws cloudformation package  --template-file out/sam.json \
      --s3-bucket $BUCKET --output-template-file out/template.yml

Once this is complete, a new template should be located at ``out/template.yml``
this is the final CloudFormation template which is ready for deployment.
Deploying it with the AWS CLI can be done with the following command::

  $ aws cloudformation deploy --template-file out/template.yml \
      --stack-name ChaliceChat --capabilities CAPABILITY_IAM

This command should wait awhile, and once it exits the app should be ready. To
get the websocket connection URL, we can use the AWS CLI again to check the
stack output ``WebsocketConnectEndpointURL``::

  $ aws cloudformation describe-stacks --stack-name ChaliceChat \
      --query "Stacks[0].Outputs[?OutputKey=='WebsocketConnectEndpointURL'].OutputValue" \
      --output text
  wss://{id}.execute-api.{region}.amazonaws.com/api/


Once deployed we can take the result of the previous command and connect to it
using ``wsdump.py``. Below is a sample of two running clients, the first
message sent to the server is used as the client's username.


.. code-block:: bash
   :caption: client-1

   $ wsdump.py wss://{id}.execute-api.{region}.amazonaws.com/api/
   Press Ctrl+C to quit
   > John
   < Using nickname: John
   Type /help for list of commands.
   > /help
   < Commands available:
       /help
             Display this message.
       /join {chat_room_name}
             Join a chatroom named {chat_room_name}.
       /nick {nickname}
             Change your name to {nickname}. If no {nickname}
             is provided then your current name will be printed
       /room
             Print out the name of the room you are currently
             in.
       /ls
             If you are in a room, list all users also in the
             room. Otherwise, list all rooms.
       /quit
             Leave current room.

   If you are in a room, raw text messages that do not start
   with a / will be sent to everyone else in the room.
   > /join chalice
   < Joined chat room "chalice"
   < Jenny joined room.
   > Hi
   < John: Hi
   < Jenny is now known as JennyJones.
   > /quit
   < Left chat room "chalice"
   > /ls
   < chalice
   > Ctrl-D

.. code-block:: bash
   :caption: client-2

   $ wsdump.py wss://{id}.execute-api.{region}.amazonaws.com/api/
   Press Ctrl+C to quit
   > Jenny
   < Using nickname: Jenny
   Type /help for list of commands.
   > /help
   < Commands available:
       /help
             Display this message.
       /join {chat_room_name}
             Join a chatroom named {chat_room_name}.
       /nick {nickname}
             Change your name to {nickname}. If no {nickname}
             is provided then your current name will be printed
       /room
             Print out the name of the room you are currently
             in.
       /ls
             If you are in a room, list all users also in the
             room. Otherwise, list all rooms.
       /quit
             Leave current room.

   If you are in a room, raw text messages that do not start
   with a / will be sent to everyone else in the room.
   > /join chalice
   < Joined chat room "chalice"
   > /ls
   < John
   Jenny
   < John: Hi
   > /nick JennyJones
   < Nickname is: JennyJones
   < John left room.
   > /ls
   < JennyJones
   > /room
   < chalice
   > /nick
   < Current nickname: JennyJones
   > Ctrl-D


To delete the resources you can run use the AWS CLI to delete the stack::

  $ aws cloudformation delete-stack --stack-name ChaliceChat

---

### Source Tutorials Wsecho (`docs/source/tutorials/wsecho.rst`)

Echo Server Example
===================

An echo server is a simple server that echos any message it receives back to
the client that sent it.

First install a copy of Chalice in a fresh environment, create a new project
and cd into the directory::

  $ pip install -U chalice
  $ chalice new-project echo-server
  $ cd echo-server

Our Chalice application will need boto3 as a dependency for both API Gateway
to send websocket messages. Let's add a boto3 to the ``requirements.txt``
file::

  $ echo "boto3>=1.9.91" > requirements.txt


Now that the requirement has been added. Let's install it locally since our
next script will need it as well::

  $ pip install -r requirements.txt


Next replace the contents of the ``app.py`` file with the code below.

.. code-block:: python
   :caption: app.py
   :linenos:

   from boto3.session import Session

   from chalice import Chalice
   from chalice import WebsocketDisconnectedError

   app = Chalice(app_name="echo-server")
   app.websocket_api.session = Session()
   app.experimental_feature_flags.update([
       'WEBSOCKETS'
   ])


   @app.on_ws_message()
   def message(event):
       try:
           app.websocket_api.send(
               connection_id=event.connection_id,
               message=event.body,
           )
       except WebsocketDisconnectedError as e:
           pass  # Disconnected so we can't send the message back.


Stepping through this app line by line, the first thing to note is that we
need to import and instantiate a boto3 session. This session is manually
assigned to ``app.websocket_api.session``.
This is needed because in order to send websocket responses to API Gateway we
need to construct a boto3 client. Chalice does not take a direct dependency
on boto3 or botocore, so we need to provide the Session ourselves.

.. code-block:: python

   from boto3.session import Session
   app.websocket_api.session = Session()


Next we enable the experimental feature ``WEBSOCKETS``. Websockets are an
experimental feature and are subject to API changes. This includes all aspects
of the Websocket API exposted in Chalice. Including any public members of
``app.websocket_api``, and the three decorators ``on_ws_connect``,
``on_ws_message``, and ``on_ws_disconnect``.

.. code-block:: python

   app.experimental_feature_flags.update([
       'WEBSOCKETS'
   ])


To register a websocket handler, and cause Chalice to deploy an
API Gateway Websocket API we use the ``app.on_ws_message()`` decorator.
The event parameter here is a wrapper object with some convenience
parameters attached. The most useful are ``event.connection_id`` and
``event.body``. The ``connection_id`` is an API Gateway specific identifier
that allows you to refer to the connection that sent the message. The ``body``
is the content of the message.

.. code-block:: python

   @app.on_ws_message()
   def message(event):


Since this is an echo server, the message handler simply reads the content it
received on the socket, and rewrites it back to the same socket. To send a
message to a socket we call ``app.websocket_api.send(connection_id, message)``.
In this case, we just use the same ``connection_id`` we got the message from,
and use the ``body`` we got from the event as the ``message`` to send.

.. code-block:: python

   app.websocket_api.send(
       connection_id=event.connection_id,
       message=event.body,
    )


Finally, we catch the exception ``WebsocketDisconnectedError`` which is raised
by ``app.websocket_api.send`` if the provided ``connection_id`` is not
connected anymore. In our case this doesn't really matter since we don't have
anything tracking our connections. The error has a ``connection_id`` property
that contains the offending connection id.

.. code-block:: python

   except WebsocketDisconnectedError as e:
       pass  # Disconnected so we can't send the message back.


Now that we understand the code, lets deploy it with ``chalice deploy``::

   $ chalice deploy
     Creating deployment package.
     Creating IAM role: echo-server-dev
     Creating lambda function: echo-server-dev-websocket_message
     Creating websocket api: echo-server-dev-websocket-api
     Resources deployed:
       - Lambda ARN: arn:aws:lambda:region:0123456789:function:echo-server-dev-websocket_message
       - Websocket API URL: wss://{websocket_api_id}.execute-api.region.amazonaws.com/api/

To test out the echo server we will use the  ``websocket-client`` package. You
install it from PyPI::

  $ pip install websocket-client


After deploying the Chalice app the output will contain a URL for connecting
to the websocket API labeled: ``- Websocket API URL:``. The
``websocket-client`` package installs a command line tool called ``wsdump.py``
which can be used to test websocket echo server::

  $ wsdump wss://{websocket_api_id}.execute-api.region.amazonaws.com/api/
  Press Ctrl+C to quit
  > foo
  < foo
  > bar
  < bar
  > foo bar baz
  < foo bar baz
  >


Every message sent to the server (lines that start with ``>``) result in a
message sent to us (lines that start with ``<``) with the same content.

If something goes wrong, you can check the chalice error logs using the
following command::

  $ chalice logs -n websocket_message

.. note::
   If you encounter an Internal Server Error here it is likely that you forgot
   to include ``boto3>=1.9.91`` in the ``requirements.txt`` file.

To tear down the example. Just run::

  $ chalice delete
    Deleting Websocket API: {websocket_api_id}
    Deleting function: arn:aws:lambda:us-west-2:0123456789:function:echo-server-dev-websocket_message
    Deleting IAM role: echo-server-dev

Next Steps
----------

In this tutorial, we created an echo server with websockets.
If you'd like to try something more ambitious, you can follow our
tutorial for creating a sample :doc:`Chat application with websocket <wschat>`.

---

### Source Samples Media Query Index (`docs/source/samples/media-query/index.rst`)

=======================
Media Query Application
=======================


.. youtube:: UCZXJpI1dKw
   :width: 75%


This sample application shows how to combine multiple event handlers in Chalice
to create an image processing pipeline.  It takes as input any image or video
and it will identify objects, people, text, scenes, and activities.  This
results of this analysis can then be queried with a REST API.

.. image:: docs/assets/appexample.jpg
  :width: 100%
  :alt: Application Example

There are several components of this application.  The first part is an image
processing pipeline.  The application is registered to automatically process
any media that's uploaded to an Amazon S3 bucket.  The application will then
use Amazon Rekognition to automatically detect labels in either the image
or the video.  The returned labels are then stored in an Amazon DynamoDB
table.

For videos, an asynchronous job is started.  This is because the analysis
for videos takes longer than analyzing images so we don't want our Lambda
function to block until the job is complete.  To handle this asynchronous
job, we subscribe to an Amazon SNS topic.  When the asynchronous job
is finished analyzing our uploaded video, an event handler is called that
will retrieve the results and store the labels in Amazon DynamoDB.

The final component is the REST API.  This allows users to query for
labels associated with the media that has been uploaded.

You can find the full source code for this application in our
`samples directory on GitHub <https://github.com/aws/chalice/tree/master/docs/source/samples/media-query/code/>`__.

::

    $ git clone git://github.com/aws/chalice
    $ cd chalice/docs/source/samples/media-query/code


We'll now walk through the architecture of the application, how to
deploy and use the application, and go over the application code.


.. note::
    This sample application is also available as a `workshop
    <https://chalice-workshop.readthedocs.io/en/latest/media-query/index.html>`__.
    The main difference between the sample apps here and the Chalice workshops
    is that the workshop is a detailed step by step process for how to create
    this application from scratch.  You build the app by gradually adding each
    feature piece by piece.  It takes several hours to work through all the
    workshop material.  In this document we review the architecture,
    the deployment process, then walk through the main sections of the code.


Architecture
============

Below is the architecture for the application.

.. image:: docs/assets/architecture.png
  :width: 100%
  :alt: Architecture diagram

The main components of the application are as follows:

* ``handle_object_created``: A Lambda function that is triggered when an
  object is uploaded to a S3 bucket. If the object is an image, it will
  call Amazon Rekognition's ``DetectLabels`` API to detect objects in the
  image. With the detected objects, the Lambda function will then add the
  object to an Amazon DynamoDB table. If the object is a video, it will call
  Rekognition's ``StartLabelDetection`` API to initiate an asynchronous
  job to detect labels in the video. When the job is completed, a completion
  notification is pushed to an SNS topic.

* ``handle_object_deleted``: A Lambda function that removes the object from
  the DynamoDB table if the object is deleted from the S3 bucket.

* ``add_video_labels``: A Lambda function that is triggered on video label
  detection SNS messages. On invocation, it will call Rekognition's
  ``GetLabelDetection`` API to retrieve all detected objects from the video.
  It then adds the video with its labels to the DynamoDB Table

* ``api_handler``: A Lambda function that is invoked by HTTP requests to
  Amazon API Gateway. On invocation, it will query the database based on the
  received HTTP request and return the results to the user through API Gateway.


Deployment
==========

First, we'll setup our development environment by cloning the Chalice
GitHub repository and copying the sample code in a new directory::

    $ git clone git://github.com/aws/chalice
    $ mkdir /tmp/demo
    $ cp -r chalice/docs/source/samples/media-query/code/ /tmp/demo/media-query
    $ cd /tmp/demo/media-query/

Next configure a virtual environment that uses Python 3.  In this example
we're using Python 3.7.

::
  $ python3 -m venv /tmp/venv37
  $ . /tmp/venv37/bin/activate

To deploy the application, first install the necessary requirements and
install Chalice::

  $ pip install -r requirements.txt
  $ pip install chalice


We'll also be using the AWS CLI to help deploy our application, you can
follow the `installation instructions <https://docs.aws.amazon.com/cli/latest/userguide/install-cliv2.html>`__
if you don't have the AWS CLI installed.

Next, we'll use the AWS CLI to deploy a CloudFormation stack containing the S3
bucket, DynamoDB table, and SNS topic needed to run this application::

  $ aws cloudformation deploy --template-file resources.json \
      --stack-name media-query --capabilities CAPABILITY_IAM


Record the deployed resources as environment variables in the Chalice
application by running the `recordresources.py` script::

  $ python recordresources.py --stack-name media-query

You can see these values by looking at the ``.chalice/config.json`` file.
Once those resources are created and recorded, deploy the Chalice application::

  $ chalice deploy


Using the Application
=====================

Once the application is deployed, use the AWS CLI to fetch the name of the
bucket that is storing the media files::

   $ aws cloudformation describe-stacks --stack-name media-query \
       --query "Stacks[0].Outputs[?OutputKey=='MediaBucketName'].OutputValue" \
       --output text
   media-query-mediabucket-xtrhd3c4b59


Upload some sample media files to your Amazon S3 bucket so the system populates
information about the media files in your DynamoDB table.  If you need sample
media files, you can use the included samples from the corresponding
`Chalice workshop <https://chalice-workshop.readthedocs.io/en/latest/media-query/index.html>`__ assets
`here <https://github.com/aws-samples/chalice-workshop/tree/master/code/media-query/final/assets>`__.

::

   $ aws s3 cp assets/sample.jpg s3://media-query-mediabucket-xtrhd3c4b59/sample.jpg
   $ aws s3 cp assets/sample.mp4 s3://media-query-mediabucket-xtrhd3c4b59/sample.mp4


Wait about a minute for the media files to be populated in the database and
then install HTTPie::

    $ pip install httpie


Then, list out all if the media files using the application's API with HTTPie::

    $ chalice url
    https://qi5hf4djdg.execute-api.us-west-2.amazonaws.com/api/

    $ http https://qi5hf4djdg.execute-api.us-west-2.amazonaws.com/api/
    HTTP/1.1 200 OK
    Connection: keep-alive
    Content-Length: 279
    Content-Type: application/json
    Date: Tue, 10 Jul 2018 17:58:40 GMT
    Via: 1.1 fa751ee53e2bf18781ae98b293ff9375.cloudfront.net (CloudFront)
    X-Amz-Cf-Id: sNnrzvbdvgj1ZraySJvfSUbHthC_fok8l5GJ7glV4QcED_M1c8tlvg==
    X-Amzn-Trace-Id: Root=1-5b44f3d0-4546157e8f5e35a008d06d88;Sampled=0
    X-Cache: Miss from cloudfront
    x-amz-apigw-id: J0sIlHs3vHcFj9g=
    x-amzn-RequestId: e0aaf4e1-846a-11e8-b756-99d52d342d60

    [
        {
            "labels": [
                "Animal",
                "Canine",
                "Dog",
                "German Shepherd",
                "Mammal",
                "Pet",
                "Collie"
            ],
            "name": "sample.jpg",
            "type": "image"
        },
        {
            "labels": [
                "Human",
                "Clothing",
                "Dog",
                "Nest",
                "Person",
                "Footwear",
                "Bird Nest",
                "People",
                "Animal",
                "Husky"
            ],
            "name": "sample.mp4",
            "type": "video"
        }
    ]

You can include query string parameters as well to query all objects based
on what the file name starts with, the type of the media file, and the detected
objects in the media file::

    $ http https://qi5hf4djdg.execute-api.us-west-2.amazonaws.com/api/ startswith==sample.m
    HTTP/1.1 200 OK
    Connection: keep-alive
    Content-Length: 153
    Content-Type: application/json
    Date: Tue, 10 Jul 2018 19:20:02 GMT
    Via: 1.1 aa42484f82c16d99015c599631def20c.cloudfront.net (CloudFront)
    X-Amz-Cf-Id: euqlOlWN5k5V_zKCJy4SL988Vcje6W5jDR88GrWr5uYGH-_ZvN4arg==
    X-Amzn-Trace-Id: Root=1-5b4506e0-db041a3492ee56e8f3d9457c;Sampled=0
    X-Cache: Miss from cloudfront
    x-amz-apigw-id: J04DHE92PHcF--Q=
    x-amzn-RequestId: 3d82319d-8476-11e8-86d9-a1e4585e5c26

    [
        {
            "labels": [
                "Human",
                "Clothing",
                "Dog",
                "Nest",
                "Person",
                "Footwear",
                "Bird Nest",
                "People",
                "Animal",
                "Husky"
            ],
            "name": "sample.mp4",
            "type": "video"
        }
    ]

    $ http https://qi5hf4djdg.execute-api.us-west-2.amazonaws.com/api/ media-type==image
    HTTP/1.1 200 OK
    Connection: keep-alive
    Content-Length: 126
    Content-Type: application/json
    Date: Tue, 10 Jul 2018 19:20:53 GMT
    Via: 1.1 88eb066576c1b47cd896ab0019b9f25f.cloudfront.net (CloudFront)
    X-Amz-Cf-Id: rwuOwzLKDM4KgcSBXFihWeNNsYSpZDYVpc8IXdT0xOu8qz8aA2Pj3w==
    X-Amzn-Trace-Id: Root=1-5b450715-de71cf04ca2900b839ff1194;Sampled=0
    X-Cache: Miss from cloudfront
    x-amz-apigw-id: J04LaE6YPHcF3VA=
    x-amzn-RequestId: 5d29d59a-8476-11e8-a347-ebb5d5f47789

    [
        {
            "labels": [
                "Animal",
                "Canine",
                "Dog",
                "German Shepherd",
                "Mammal",
                "Pet",
                "Collie"
            ],
            "name": "sample.jpg",
            "type": "image"
        }
    ]

    $ http https://qi5hf4djdg.execute-api.us-west-2.amazonaws.com/api/ label==Person
    HTTP/1.1 200 OK
    Connection: keep-alive
    Content-Length: 153
    Content-Type: application/json
    Date: Tue, 10 Jul 2018 19:20:02 GMT
    Via: 1.1 aa42484f82c16d99015c599631def20c.cloudfront.net (CloudFront)
    X-Amz-Cf-Id: euqlOlWN5k5V_zKCJy4SL988Vcje6W5jDR88GrWr5uYGH-_ZvN4arg==
    X-Amzn-Trace-Id: Root=1-5b4506e0-db041a3492ee56e8f3d9457c;Sampled=0
    X-Cache: Miss from cloudfront
    x-amz-apigw-id: J04DHE92PHcF--Q=
    x-amzn-RequestId: 3d82319d-8476-11e8-86d9-a1e4585e5c26

    [
        {
            "labels": [
                "Human",
                "Clothing",
                "Dog",
                "Nest",
                "Person",
                "Footwear",
                "Bird Nest",
                "People",
                "Animal",
                "Husky"
            ],
            "name": "sample.mp4",
            "type": "video"
        }
    ]


You can also query for a specific object::

    $ http https://qi5hf4djdg.execute-api.us-west-2.amazonaws.com/api/sample.jpg
    HTTP/1.1 200 OK
    Connection: keep-alive
    Content-Length: 126
    Content-Type: application/json
    Date: Tue, 10 Jul 2018 19:20:53 GMT
    Via: 1.1 88eb066576c1b47cd896ab0019b9f25f.cloudfront.net (CloudFront)
    X-Amz-Cf-Id: rwuOwzLKDM4KgcSBXFihWeNNsYSpZDYVpc8IXdT0xOu8qz8aA2Pj3w==
    X-Amzn-Trace-Id: Root=1-5b450715-de71cf04ca2900b839ff1194;Sampled=0
    X-Cache: Miss from cloudfront
    x-amz-apigw-id: J04LaE6YPHcF3VA=
    x-amzn-RequestId: 5d29d59a-8476-11e8-a347-ebb5d5f47789

    [
        {
            "labels": [
                "Animal",
                "Canine",
                "Dog",
                "German Shepherd",
                "Mammal",
                "Pet",
                "Collie"
            ],
            "name": "sample.jpg",
            "type": "image"
        }
    ]


Code Walkthrough
================

We'll take a top-down approach with this application and start with the main
entry point, the ``app.py`` file.  The source code for this application is
split between the ``app.py`` as well as supporting code in ``chalicelib/``.

Event Handlers
--------------

In the ``app.py`` we see four different decorator types, each corresponding
to Lambda functions that are triggered by different events.  Note that
the line numbers correspond to the line numbers in the ``app.py`` file.


.. literalinclude:: code/app.py
   :lineno-match:
   :start-after: # Start of Event Handlers
   :end-before: # End of Event Handlers


The first two decorators use ``@app.on_s3_event`` and are specifying
that these two Lambda functions should be invoked when an object is
created or deleted from S3, respectively.  The name of the S3 bucket
is not hardcoded in the ``app.py`` file but instead pulled from the
environment variable ``MEDIA_BUCKET_NAME``.  The ``recordresources.py``
script that was run as part of the deployment process described above
automatically created these resources and updated the Chalice config file
(``.chalice/config.json``) with these values.  If you look at the
contents of your ``.chalice/config.json`` file, it should look something
like this:


.. code-block:: json

    {
      "version": "2.0",
      "app_name": "media-query",
      "stages": {
        "dev": {
          "api_gateway_stage": "api",
          "autogen_policy": false,
          "environment_variables": {
            "MEDIA_TABLE_NAME": "media-query-MediaTable-10QEPR0O8DOT4",
            "MEDIA_BUCKET_NAME": "media-query-mediabucket-fb8oddjbslv1",
            "VIDEO_TOPIC_NAME": "media-query-VideoTopic-KU38EEHIIUV1",
            "VIDEO_ROLE_ARN": "arn:aws:iam::123456789123:role/media-query-VideoRole-1GKK0CA30VCAD",
            "VIDEO_TOPIC_ARN": "arn:aws:sns:us-west-2:123456789123:media-query-VideoTopic-KU38EEHIIUV1"
          }
        }
      }
    }

Next, the ``@app.on_sns_message`` is used to connect an SNS topic to our
Lambda function.  This is only used for video processing with Rekognition.
Because of the longer processing times of video compared to images,
video analysis is performed by first starting a "video label job".  When
you start this asynchronous job, we can specify an SNS topic that
Rekognition will publish to when the job is complete, as shown in the
``_handle_created_video`` function below.


.. literalinclude:: code/app.py
  :linenos:
  :lineno-match:
  :pyobject: _handle_created_video

The ``add_video_file()`` function will then query for the results
of the job (the ``JobId`` is provided as part of the SNS message
that's published) and store the results in the DynamoDB table.

The final two decorators of this app creates a REST API with
Amazon API Gateway and defines two routes: ``/`` and ``/{name}``.

Requesting the root URL of ``/`` is equivalent to a "List" API call that will
return all the media files that have been analyzed so far.  Request
``/{name}``, where ``{name}`` is the name of the media file that was uploaded
to S3 will return the detected labels for that single resource.  This is
equivalent to a "Get" API call.

.. note::
  This sample application returns all analyzed media files in its
  List API call.  In practice, you should paginate your List API calls
  to ensure you don't return unbounded results.


Supporting Files
----------------

The event handlers described in the previous section interact with
Rekognition and DynamoDB through clients that are accessed through
``get_rekognition_client()`` and ``get_rekognition_client()``
respectively.  These clients are high level wrappers to the corresponding
boto3 clients/resources for these services.  The code for these
high level clients are in ``chalicelib/rekognition.py`` and
``chalicelib/db.py``.  If we look at the ``DynamoMediaDB.add_media_file()``
method in the ``chalicelib/db.py`` file, we see that it's a small wrapper
around the ``put_item()`` operation of the underlying DynamoDB API:

.. literalinclude:: code/chalicelib/db.py
  :linenos:
  :lineno-match:
  :pyobject: DynamoMediaDB.add_media_file


We see a similar pattern in ``chalicelib/rekognition.py``.  Here's the
``start_video_label_job`` job that starts the asynchronous processing
discussed in the previous section.

.. literalinclude:: code/chalicelib/rekognition.py
  :linenos:
  :lineno-match:
  :pyobject: RekognitonClient.start_video_label_job

As you can see, it's a small wrapper around the ``start_label_detection``
operation of the underlying Rekognition API.

We encourage you to look through the rest of the ``chalicelib/`` directory
to see how these high level clients are implemented.

Cleaning Up
===========

If you're done experimenting with this sample app, you can run these commands
to delete this app.


1. Delete the chalice application::

    $ chalice delete
    Deleting Rest API: kyfn3gqcf0
    Deleting function: arn:aws:lambda:us-west-2:123456789123:function:media-query-dev
    Deleting IAM role: media-query-dev-api_handler
    Deleting function: arn:aws:lambda:us-west-2:123456789123:function:media-query-dev-add_video_file
    Deleting IAM role: media-query-dev-add_video_file
    Deleting function: arn:aws:lambda:us-west-2:123456789123:function:media-query-dev-handle_object_removed
    Deleting IAM role: media-query-dev-handle_object_removed
    Deleting function: arn:aws:lambda:us-west-2:123456789123:function:media-query-dev-handle_object_created
    Deleting IAM role: media-query-dev-handle_object_created

2. Delete all objects in your S3 bucket::

    $ aws s3 rm s3://$MEDIA_BUCKET_NAME --recursive
    delete: s3://media-query-mediabucket-4b1h8anboxpa/sample.jpg
    delete: s3://media-query-mediabucket-4b1h8anboxpa/sample.mp4

3. Delete the CloudFormation stack containing the additional AWS resources::

    $ aws cloudformation delete-stack --stack-name media-query

---

### Source Samples Todo App Index (`docs/source/samples/todo-app/index.rst`)

================
Todo Application
================

This is a sample application that allows you to manage Todo items.  This
tutorial will walk through creating a serverless web API to create, update,
get, and delete Todos, managing Todos in a database, and adding authorization
with JWT.  AWS services covered include AWS Lambda, Amazon API Gateway, Amazon
DynamoDB, AWS CodeBuild, and AWS Systems Manager.

You can find the full source code for this application in our
`samples directory on GitHub <https://github.com/aws/chalice/tree/master/docs/source/samples/todo-app/code/>`__.

::

    $ git clone git://github.com/aws/chalice
    $ cd chalice/docs/source/samples/todo-app/code

We'll now walk through the architecture of this application,
how to deploy and use the application, and finally we'll go over
the main components of the application code.

.. note::
    This sample application is also available as a `workshop
    <https://chalice-workshop.readthedocs.io/en/latest/todo-app/index.html>`__.
    The main difference between the sample apps here and the Chalice workshops
    is that the workshop is a detailed step by step process for how to create
    this application from scratch.  You build the app by gradually adding each
    feature piece by piece.  In the workshop, we first create a REST API with
    no authentication or data store.  Then we introduce DynamoDB, then JWT
    auth, etc.  The workshop also shows you how to set up a CI/CD pipeline
    to automatically deploy your application whenever you push to your git
    repository.  It takes several hours to work through all the workshop
    material.  In this document we review the architecture, the deployment process,
    then walk through the main sections of the final version of this
    application.

Architecture
============

The main component of this application is a REST API backed by Amazon API
Gateway and AWS Lambda.  The rest API lets you manage a Todo list.  It lets you
create a new Todo list as well as check off existing Todo items.

In order to see a list of your Todo items, you must first log in.  Information
about our users is stored in an Amazon DynamoDB table.  The authentication is
done using a builtin authorizer.  This lets you define a Lambda function to
perform your custom auth process.  For this sample app, we're using JSON Web
Tokens (JWT).

The Todo items are stored in a separate DynamoDB table.  Below is
an architecture diagram of our sample app.  It shows the API Gateway
REST API, along with a Lambda function for our authorizer, a Lambda function
for our REST API, and two DynamoDB tables.

.. image:: docs/assets/architecture.jpg
  :width: 100%
  :alt: Architecture diagram

.. _todo-sample-rest-api:

REST API
--------

The REST API supports the following resources:

* GET    - ``/todos/`` - Gets a list of all todo items
* POST   - ``/todos/`` - Creates a new Todo item
* GET    - ``/todos/{id}`` - Gets a specific todo item
* DELETE - ``/todos/{id}`` - Deletes a specific todo item
* PUT    - ``/todos/{id}`` - Updates the state of a todo item

A todo item has this schema::

  {
    "description": {"type": "str"},
    "uid": {"type: "str"},
    "state": {"type: "str", "enum": ["unstarted", "started", "completed"]},
    "metadata": {
      "type": "object"
    }
  }


Deployment
==========

To run and deploy this application, first create a virtual environment
and install the dependencies.  Python 3.7 is used for this sample app.

::

    $ python3 -m /tmp/venv37
    $ . /tmp/venv37/bin/activate
    $ pip install ./requirements-dev.txt
    $ pip install ./requirements.txt

As part of this application, there are additional resources that
are created that are used by this application, including two DynamoDB
tables as well as an SSM parameter used to store our secret key used
in our JWT auth.  To create these resources, you can run::

    $ python create-resources.py

This will also update your ``.chalice/config.json`` file with environment
variables containing the name of the DynamoDB tables that were created.

At this point, you can either test the application by running ``chalice
local``.  This will start a local HTTP server on port 8000 that emulates API
Gateway so that you can test without having to deploy your application to AWS.
You can also run ``chalice deploy`` to deploy your application to
AWS, which allows you to test on an actual API Gateway REST API::

     $ chalice deploy
     Creating deployment package.
     Creating IAM role: mytodo-dev-api_handler
     Creating lambda function: mytodo-dev
     Creating IAM role: mytodo-dev-jwt_auth
     Creating lambda function: mytodo-dev-jwt_auth
     Creating Rest API
     Resources deployed:
       - Lambda ARN: arn:aws:lambda:us-east-1:12345:function:mytodo-dev
       - Lambda ARN: arn:aws:lambda:us-east-1:12345:function:mytodo-dev-jwt_auth
       - Rest API URL: https://abcd.execute-api.us-west-2.amazonaws.com/api/


Using the Application
=====================

If you've deployed your application using ``chalice deploy``, you can test
the REST API by making requests to the ``Rest API URL``, shown in the output
of ``chalice deploy``, in our example that would be
``https://abcd.execute-api.us-west-2.amazonaws.com/api/``.  If you're using
``chalice local``, you'll make requests to ``http://localhost:8000/``.

Before we can make requests we need to authenticate with the API.  In order
to authenticate with the API we need to create user accounts.

A helper script, ``users.py`` is included in the repository to help you
manage users.  The first thing we'll need to do is create a user::

    $ python users.py --create-user
    Username: myusername
    Password:

This will create a new entry in our users DynamoDB table.
You can then test that the password verification works by running::

    $ python users.py --test-password
    Username: myusername
    Password:
    Password verified.

Once we've created a test user, we can now login by sending a POST
request to the ``/login`` URL::

    $ echo '{"username": "myusername", "password": "mypassword"}' | \
        http POST https://abcd.execute-api.us-west-2.amazonaws.com/api/login/
    {
        "token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJteXVzZXJuYW1lIiwiaWF0IjoxNTk1NDU3Njg5LCJuYmYiOjE1OTU0NTc2ODksImp0aSI6IjMxNjc4YzFkLTdkZjEtNGEzOC04YmZiLTllZjZiMGM1YzAyNyJ9.w46RdtzZdk_P0LAh_St3wjsqgh-k-Hp1ykTpbDqad2k",
    }

.. note::
  We're using the HTTPie command line tool instead of cURL.  You can
  install this tool by running ``pip install httpie``.

Now whenever we make any requests to our REST API, we need to include
the token value in the output above as the value of our ``Authorization``
header.  For example, we can list all of our Todos, which is initially
empty::

    $ http https://abcd.execute-api.us-west-2.amazonaws.com/api/todos/ 'Authorization: my.jwt.token'
    HTTP/1.1 200 OK
    Content-Length: 2
    Content-Type: application/json

    []


If you omit the ``Authorization`` header, you'll see this error response::

    $ http https://abcd.execute-api.us-west-2.amazonaws.com/api/todos/
    HTTP/1.1 401 Unauthorized
    Content-Length: 26
    Content-Type: application/json
    x-amzn-ErrorType: UnauthorizedException

    {
        "message": "Unauthorized"
    }


We can create a new Todo::

    $ echo '{"description": "My first Todo", "metadata": {}}' \
        |  http POST https://abcd.execute-api.us-west-2.amazonaws.com/api/todos/ \
           'Authorization: my.jwt.token'
    HTTP/1.1 200 OK
    Content-Length: 36
    Content-Type: application/json

    e25643f7-0b18-47d2-b124-4e6713ab527c

Now when we list our Todos, we'll see our new entry we created::

    $ http https://abcd.execute-api.us-west-2.amazonaws.com/api/todos/ 'Authorization: my.jwt.token'
    HTTP/1.1 200 OK
    Content-Length: 136
    Content-Type: application/json

    [
        {
            "description": "My first Todo",
            "metadata": {},
            "state": "unstarted",
            "uid": "e25643f7-0b18-47d2-b124-4e6713ab527c",
            "username": "myusername"
        }
    ]

We can update our Todo and mark it completed::


    $ echo '{"state": "completed"}' |  \
        http PUT https://abcd.execute-api.us-west-2.amazonaws.com/api/todos/e25643f7-0b18-47d2-b124-4e6713ab527c \
        'Authorization: my.jwt.token'
    HTTP/1.1 200 OK
    Content-Length: 4
    Content-Type: application/json

    null

And we can now verify that the Todo item shows up as completed::

    $ http https://abcd.execute-api.us-west-2.amazonaws.com/api/todos/e25643f7-0b18-47d2-b124-4e6713ab527c \
        'Authorization: my.jwt.token'
    HTTP/1.1 200 OK
    Content-Length: 134
    Content-Type: application/json

    {
        "description": "My first Todo",
        "metadata": {},
        "state": "completed",
        "uid": "e25643f7-0b18-47d2-b124-4e6713ab527c",
        "username": "myusername"
    }


Code Walkthrough
================


.. _todo-app-rest-api:

Rest API
--------

Below is the code for the five routes defined in the
:ref:`todo-sample-rest-api` section defined in the ``app.py`` file:

.. literalinclude:: code/app.py
   :caption: app.py
   :linenos:
   :lineno-match:
   :lines: 67-105

The first thing all of these routes do is extract the current username from the
request.  This is done by examining the context associated with the current
request.  This will include the ``principalId``, or the current username, which
is discussed in more detail in the :ref:`todo-app-jwt-auth` section below.

Each of these routes then makes a call into the data storage layer,
and either retrieves or updates data in the ``Todo`` DynamoDB table.
This is discussed in the next section on data storage.

The application DB is tracked as a module level variable that is
retrieved through the ``get_app_db()`` function.  The name of the
DynamoDB table is provided through the ``APP_TABLE_NAME`` environment
variable, which is specified in your ``.chalice/config.json`` file.
This was automatically filled in for you when you ran the
``create-resources.py`` script.

User input is extracted from both the URL (the ``uid`` associated with
a Todo item is provided as part of the URL) as well as the JSON
request body.  A key takeaway from these routes is that there's
minimal logic in the route definitions themselves.  They're primarily
about extracting user input and then delegating the heavy lifting to
other objects that are independent of any routing information.


Data Storage
------------

Each route in this sample application app makes a call to the
data storage layer, which is backed by a DynamoDB table.  This interface
is defined by the ``TodoDB`` interface, which is defined in the
``chalicelib/db.py`` file:

.. literalinclude:: code/chalicelib/db.py
  :caption: chalicelib/db.py
  :linenos:
  :lineno-match:
  :pyobject: TodoDB

There are two different implementations of this interface.  The first one,
``InMemoryTodoDB``, is an in-memory implementation of this interface where
all data is stored within the process.  The purpose of this implementation
is for testing purposes when you don't want to work with the real DynamoDB
service.  This allows you to develop your application locally and test
using ``chalice local``.  The other implementation of ``TodoDB`` interface is
``DynamoDBTodo``, which communicates with the actual DynamoDB service
to store and retrieve Todo items.  It uses the Table resource of ``boto3``,
created via ``boto3.resource('dynamodb').Table(TABLE_NAME)``.  This allows
us to use the `high level querying interface of boto3 <https://boto3.amazonaws.com/v1/documentation/api/latest/reference/customizations/dynamodb.html#dynamodb-conditions>`__.
The implementation is shown below.

.. literalinclude:: code/chalicelib/db.py
  :caption: chalicelib/db.py
  :linenos:
  :lineno-match:
  :pyobject: DynamoDBTodo

.. _todo-app-jwt-auth:

JWT Authentication
------------------

.. note::
  This example is for illustration purposes and does not necessarily
  represent best practices.  Its intent is to show how custom
  authentication can be implemented in a Chalice app.

Our REST API for our Todo items requires that you send an appropriate
``Authorization`` header when making HTTP requests.  You can retrieve
a auth token by making a request to the ``/login`` route with your
user name and password.  The underlying mechanism used to handle
our auth functionality is through issuing a
`JWT <https://datatracker.ietf.org/doc/html/rfc7519>`__ when you login.

Users Table
~~~~~~~~~~~

In order to login, we need a way to store and retrieve user information.  This
is done through our ``Users`` DynamoDB table.  This was created when you ran
the ``create-resoureces.py`` file.  Each user record stores their username and
information about their password.  We're using PBKDF2 as our key derivation
function for password hashing, which is available in Python's standard library
through the `hashlib.pbkdf2_hmac
<https://docs.python.org/3/library/hashlib.html#hashlib.pbkdf2_hmac>`__
function.  The parameters needed by ``pbkdf2_hmac`` are stored in each user's
record, including the password hash, salt, number of rounds, and the hash used
for PBKDF2 (sha256 in our example).  These user entries were created and stored
in the ``Users`` DynamoDB table when you ran the ``python users.py
--create-user`` command.
You can see the fields for a specific user by using the ``--get-user`` option
to the ``users.py`` script::


    $ python users.py --get-user myusername
    Entry for user: myusername
      hash      : sha256
      username  : myusername
      hashed    : Hym8Ss6WIArus+aZ6BucZ3sz6Wu5w8Tc3lPUivTuUi4=
      salt      : rXMPBx8ZriKU3SQTh58BlxQQtpcLHfmITTB2tpRs/sM=
      rounds    : 100000


Login Flow
~~~~~~~~~~

Below is the code for the ``/login`` route:

.. literalinclude:: code/app.py
  :caption: app.py
  :linenos:
  :lineno-match:
  :pyobject: login

In this login view, we first lookup the user record fom our users DB,
and then try to generate a JWT token for this entry.  The
``auth.get_jwt_token`` will first verify that the password hash
matches what's stored in our users DB, and then generate a JWT token
for this user as shown in the code below:

.. literalinclude:: code/chalicelib/auth.py
  :caption: chalicelib/auth.py
  :linenos:
  :lineno-match:
  :pyobject: get_jwt_token

The call to ``jwt.encode()`` requires a payload and a secret.
This secret is a value that is only known to our application and is
used in our built-in authorizer to verify the JWT is valid.
This secret value is stored as an SSM parameter.  A random secret
was automatically generated and stored in SSM for you when running the
``create-resources.py`` script.  When we call ``auth.get_jwt_token`` we first
retrieve this value from SSM as shown in the ``get_auth_key()`` function
defined in our ``app.py`` file:

.. literalinclude:: code/app.py
  :caption: app.py
  :linenos:
  :lineno-match:
  :pyobject: get_auth_key

Once we've generated a JWT token, we return the token back to the caller.
They must then provide that same token in the ``Authorization`` header
whenever they make API calls to the REST API.

Custom Authorizer
~~~~~~~~~~~~~~~~~

In order to require that a specific route requires proper authorization,
we must first create an authorizer, and then associate it with any routes
that require auth.  Chalice supports different types of
:doc:`../../topics/authorizers`, and in this example we're using the
:ref:`builtin-authorizers` type provided by Chalice.  This lets us write
our custom authorization logic as part of our Chalice app.  To do this,
we decorate our auth function with the ``@app.authorizer`` decorator.
Our custom authorizer logic takes the JWT token (accessible through the
``auth_request.token`` attribute, and verifies the token is valid
using our secret key retrieved via ``get_auth_key()``.  The custom
authorizer is shown below:

.. literalinclude:: code/app.py
  :caption: app.py
  :linenos:
  :lineno-match:
  :pyobject: jwt_auth

Once we verify that JWT token is valid, we return an ``AuthResponse`` that
specifies what routes the user is allowed to access.  In our example, we're
giving them access to all routes, denoted by a ``*``.

Now that we have our authorizer, we can associate with a route by providing
the function as the value of the ``authorizer=`` parameter.  We saw this in the
:ref:`todo-app-rest-api` section above.  For example, note that the
``@app.route()`` decorator is being provided an ``authorizer`` function:

.. literalinclude:: code/app.py
  :caption: app.py
  :linenos:
  :lineno-match:
  :pyobject: list_todos


Cleaning Up
===========

Once you're finished experimenting with this sample app, you can cleanup your
resources by deleting the Chalice app and deleting any additional resources
associated with this app.  To do this, first delete your Chalice app::

    $ chalice delete
    Deleting Rest API: q7dc49grhk
    Deleting function: arn:aws:lambda:us-west-w:12345:function:mytodo-dev-jwt_auth
    Deleting IAM role: mytodo-dev-jwt_auth
    Deleting function: arn:aws:lambda:us-west-w:12345:function:mytodo-dev
    Deleting IAM role: mytodo-dev-api_handler

Then to cleanup the remaining resources, rerun the
``create-resources.py`` script with the ``--cleanup`` flag.  This will delete
the DynamoDB tables and the SSM parameter, along with any additional resources
created as part of your Chalice app::

    $ python create-resources.py --cleanup
    Deleting table: todo-app-632a558c-8355-4c2d-a46e-24350f371389
    Deleting table: users-app-05b34fa2-1ae6-4d81-95d1-7ced59878a2b
    Deleting SSM param: /todo-sample-app/auth-key
    Resources deleted.  If you haven't already, be sure to run 'chalice delete' to delete your Chalice application.

---

### Source Samples Media Query Code Resources.Json (`docs/source/samples/media-query/code/resources.json`)

{
  "Outputs": {
    "MediaBucketName": {
      "Value": {
        "Ref": "MediaBucket"
      }
    },
    "MediaTableName": {
      "Value": {
        "Ref": "MediaTable"
      }
    },
    "VideoTopicArn": {
      "Value": {
        "Ref": "VideoTopic"
      }
    },
    "VideoTopicName": {
      "Value": {
        "Fn::GetAtt": [
          "VideoTopic",
          "TopicName"
        ]
      }
    },
    "VideoRoleArn": {
      "Value": {
        "Fn::GetAtt": [
          "VideoRole",
          "Arn"
        ]
      }
    }
  },
  "Resources": {
    "MediaBucket": {
      "Type": "AWS::S3::Bucket"
    },
    "MediaTable": {
      "Properties": {
        "AttributeDefinitions": [
          {
            "AttributeName": "name",
            "AttributeType": "S"
          }
        ],
        "KeySchema": [
          {
            "AttributeName": "name",
            "KeyType": "HASH"
          }
        ],
        "ProvisionedThroughput": {
          "ReadCapacityUnits": 5,
          "WriteCapacityUnits": 5
        }
      },
      "Type": "AWS::DynamoDB::Table"
    },
    "VideoTopic": {
      "Type": "AWS::SNS::Topic"
    },
    "VideoRole": {
      "Properties": {
        "AssumeRolePolicyDocument": {
          "Statement": [
            {
              "Effect": "Allow",
              "Action": [
                "sts:AssumeRole"
              ],
              "Principal": {
                "Service": [
                  "rekognition.amazonaws.com"
                ]
              }
            }
          ]
        },
        "Policies": [
          {
            "PolicyName": "RekognitionPublish",
            "PolicyDocument": {
              "Statement": [
                {
                  "Effect": "Allow",
                  "Action": [
                    "sns:Publish"
                  ],
                  "Resource": [
                    {
                      "Ref": "VideoTopic"
                    }
                  ]
                }
              ]
            }
          }
        ]
      },
      "Type": "AWS::IAM::Role"
    }
  }
}

---

### Source Samples Media Query Code .Chalice Config.Json (`docs/source/samples/media-query/code/.chalice/config.json`)

{
  "version": "2.0",
  "app_name": "media-query",
  "stages": {
    "dev": {
      "api_gateway_stage": "api",
      "autogen_policy": false
    }
  }
}

---

### Source Samples Media Query Code .Chalice Policy Dev.Json (`docs/source/samples/media-query/code/.chalice/policy-dev.json`)

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Action": [
        "logs:CreateLogGroup",
        "logs:CreateLogStream",
        "logs:PutLogEvents"
      ],
      "Resource": "arn:aws:logs:*:*:*",
      "Effect": "Allow"
    },
    {
      "Action": [
        "dynamodb:PutItem",
        "dynamodb:DeleteItem",
        "dynamodb:UpdateItem",
        "dynamodb:GetItem",
        "dynamodb:Scan",
        "dynamodb:Query"
      ],
      "Resource": [
        "arn:aws:dynamodb:*:*:table/media-query-*"
      ],
      "Effect": "Allow"
    },
    {
      "Action": [
        "rekognition:DetectLabels",
        "rekognition:StartLabelDetection",
        "rekognition:GetLabelDetection"
      ],
      "Resource": "*",
      "Effect": "Allow"
    },
    {
      "Action": [
         "s3:GetObject"
      ],
      "Resource": "arn:aws:s3:::media-query*",
      "Effect": "Allow"
    },
    {
      "Action": [
         "iam:PassRole"
      ],
      "Resource": "arn:aws:iam::*:role/media-query-*",
      "Effect": "Allow"
    }
  ]
}

---

### Source Samples Todo App Code .Chalice Config.Json (`docs/source/samples/todo-app/code/.chalice/config.json`)

{
  "stages": {
    "dev": {
      "autogen_policy": false,
      "api_gateway_stage": "api"
    }
  },
  "version": "2.0",
  "app_name": "mytodo"
}

---

### Source Samples Todo App Code .Chalice Policy Dev.Json (`docs/source/samples/todo-app/code/.chalice/policy-dev.json`)

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Action": [
        "logs:CreateLogGroup",
        "logs:CreateLogStream",
        "logs:PutLogEvents"
      ],
      "Resource": "arn:aws:logs:*:*:*",
      "Effect": "Allow"
    },
    {
      "Action": [
        "dynamodb:PutItem",
        "dynamodb:DeleteItem",
        "dynamodb:UpdateItem",
        "dynamodb:GetItem",
        "dynamodb:Scan",
        "dynamodb:Query"
      ],
      "Resource": [
        "arn:aws:dynamodb:*:*:table/todo-app-*",
        "arn:aws:dynamodb:*:*:table/users-app-*"
      ],
      "Effect": "Allow"
    },
    {
      "Action": [
        "ssm:GetParameter"
      ],
      "Resource": [
	"arn:aws:ssm:*:*:parameter/todo-sample-app/auth-key"
      ],
      "Effect": "Allow"
    }
  ]
}

---

--- METRICS ---
- Files Extracted: 42
- Estimated Token Budget: ~96937 tokens
- Canonical Reference: https://codewiki.google/github.com/aws/chalice
