<commit_before>"""Command line interface for chalice.

Contains commands for deploying chalice.

"""
import logging
import os
import sys
import tempfile
import shutil
import traceback
import functools

import botocore.exceptions
import click
from typing import Dict, Any, Optional  # noqa

from chalice import __version__ as chalice_version
from chalice.app import Chalice  # noqa
from chalice.awsclient import TypedAWSClient
from chalice.cli.factory import CLIFactory
from chalice.config import Config  # noqa
from chalice.logs import display_logs
from chalice.utils import create_zip_file
from chalice.deploy.validate import validate_routes, validate_python_version
from chalice.utils import getting_started_prompt, UI, serialize_to_json
from chalice.constants import CONFIG_VERSION, TEMPLATE_APP, GITIGNORE
from chalice.constants import DEFAULT_STAGE_NAME
from chalice.constants import DEFAULT_APIGATEWAY_STAGE_NAME
from chalice.local import LocalDevServer  # noqa


def create_new_project_skeleton(project_name, profile=None):
    # type: (str, Optional[str]) -> None
    chalice_dir = os.path.join(project_name, '.chalice')
    os.makedirs(chalice_dir)
    config = os.path.join(project_name, '.chalice', 'config.json')
    cfg = {
        'version': CONFIG_VERSION,
        'app_name': project_name,
        'stages': {
            DEFAULT_STAGE_NAME: {
                'api_gateway_stage': DEFAULT_APIGATEWAY_STAGE_NAME,
            }
        }
    }
    if profile is not None:
        cfg['profile'] = profile
    with open(config, 'w') as f:
        f.write(serialize_to_json(cfg))
    with open(os.path.join(project_name, 'requirements.txt'), 'w'):
        pass
    with open(os.path.join(project_name, 'app.py'), 'w') as f:
        f.write(TEMPLATE_APP % project_name)
    with open(os.path.join(project_name, '.gitignore'), 'w') as f:
        f.write(GITIGNORE)


@click.group()
@click.version_option(version=chalice_version, message='%(prog)s %(version)s')
@click.option('--project-dir',
              help='The project directory.  Defaults to CWD')
@click.option('--debug/--no-debug',
              default=False,
              help='Print debug logs to stderr.')
@click.pass_context
def cli(ctx, project_dir, debug=False):
    # type: (click.Context, str, bool) -> None
    if project_dir is None:
        project_dir = os.getcwd()
    ctx.obj['project_dir'] = project_dir
    ctx.obj['debug'] = debug
    ctx.obj['factory'] = CLIFactory(project_dir, debug, environ=os.environ)
    os.chdir(project_dir)


@cli.command()
@click.option('--host', default='127.0.0.1')
@click.option('--port', default=8000, type=click.INT)
@click.option('--stage', default=DEFAULT_STAGE_NAME,
              help='Name of the Chalice stage for the local server to use.')
@click.option('--autoreload/--no-autoreload',
              default=True,
              help='Automatically restart server when code changes.')
@click.pass_context
def local(ctx, host='127.0.0.1', port=8000, stage=DEFAULT_STAGE_NAME,
          autoreload=True):
    # type: (click.Context, str, int, str, bool) -> None
    factory = ctx.obj['factory']  # type: CLIFactory
    from chalice.cli import reloader
    # We don't create the server here because that will bind the
    # socket and we only want to do this in the worker process.
    server_factory = functools.partial(
        create_local_server, factory, host, port, stage)
    # When running `chalice local`, a stdout logger is configured
    # so you'll see the same stdout logging as you would when
    # running in lambda.  This is configuring the root logger.
    # The app-specific logger (app.log) will still continue
    # to work.
    logging.basicConfig(
        stream=sys.stdout, level=logging.INFO, format='%(message)s')
    if autoreload:
        project_dir = factory.create_config_obj(
            chalice_stage_name=stage).project_dir
        rc = reloader.run_with_reloader(
            server_factory, os.environ, project_dir)
        # Click doesn't sys.exit() with the RC this function.  The
        # recommended way to do this is to use sys.exit() directly,
        # see: https://github.com/pallets/click/issues/747
        sys.exit(rc)
    run_local_server(factory, host, port, stage)


def create_local_server(factory, host, port, stage):
    # type: (CLIFactory, str, int, str) -> LocalDevServer
    config = factory.create_config_obj(
        chalice_stage_name=stage
    )
    app_obj = config.chalice_app
    # Check that `chalice deploy` would let us deploy these routes, otherwise
    # there is no point in testing locally.
    routes = config.chalice_app.routes
    validate_routes(routes)
    server = factory.create_local_server(app_obj, config, host, port)
    return server


def run_local_server(factory, host, port, stage):
    # type: (CLIFactory, str, int, str) -> None
    server = create_local_server(factory, host, port, stage)
    server.serve_forever()


@cli.command()
@click.option('--autogen-policy/--no-autogen-policy',
              default=None,
              help='Automatically generate IAM policy for app code.')
@click.option('--profile', help='Override profile at deploy time.')
@click.option('--api-gateway-stage',
              help='Name of the API gateway stage to deploy to.')
@click.option('--stage', default=DEFAULT_STAGE_NAME,
              help=('Name of the Chalice stage to deploy to. '
                    'Specifying a new chalice stage will create '
                    'an entirely new set of AWS resources.'))
@click.option('--connection-timeout',
              type=int,
              help=('Overrides the default botocore connection '
                    'timeout.'))
@click.pass_context
def deploy(ctx, autogen_policy, profile, api_gateway_stage, stage,
           connection_timeout):
    # type: (click.Context, Optional[bool], str, str, str, int) -> None
    factory = ctx.obj['factory']  # type: CLIFactory
    factory.profile = profile
    config = factory.create_config_obj(
        chalice_stage_name=stage, autogen_policy=autogen_policy,
        api_gateway_stage=api_gateway_stage,
    )
    session = factory.create_botocore_session(
        connection_timeout=connection_timeout)
    ui = UI()
    d = factory.create_default_deployer(session=session,
                                        config=config,
                                        ui=ui)
    deployed_values = d.deploy(config, chalice_stage_name=stage)
    reporter = factory.create_deployment_reporter(ui=ui)
    reporter.display_report(deployed_values)


@cli.command('delete')
@click.option('--profile', help='Override profile at deploy time.')
@click.option('--stage', default=DEFAULT_STAGE_NAME,
              help='Name of the Chalice stage to delete.')
@click.pass_context
def delete(ctx, profile, stage):
    # type: (click.Context, str, str) -> None
    factory = ctx.obj['factory']  # type: CLIFactory
    factory.profile = profile
    config = factory.create_config_obj(chalice_stage_name=stage)
    session = factory.create_botocore_session()
    d = factory.create_deletion_deployer(session=session, ui=UI())
    d.deploy(config, chalice_stage_name=stage)


@cli.command()
@click.option('--num-entries', default=None, type=int,
              help='Max number of log entries to show.')
@click.option('--include-lambda-messages/--no-include-lambda-messages',
              default=False,
              help='Controls whether or not lambda log messages are included.')
@click.option('--stage', default=DEFAULT_STAGE_NAME)
@click.option('-n', '--name',
              help='The name of the lambda function to retrieve logs from.',
              default='api_handler')
@click.option('--profile', help='The profile to use for fetching logs.')
@click.pass_context
def logs(ctx, num_entries, include_lambda_messages, stage, name, profile):
    # type: (click.Context, int, bool, str, str, str) -> None
    factory = ctx.obj['factory']  # type: CLIFactory
    factory.profile = profile
    config = factory.create_config_obj(stage, False)
    deployed = config.deployed_resources(stage)
    if name in deployed.resource_names():
        lambda_arn = deployed.resource_values(name)['lambda_arn']
        session = factory.create_botocore_session()
        retriever = factory.create_log_retriever(
            session, lambda_arn)
        display_logs(retriever, num_entries, include_lambda_messages,
                     sys.stdout)


@cli.command('gen-policy')
@click.option('--filename',
              help='The filename to analyze.  Otherwise app.py is assumed.')<commit_msg>Use constant for default API handler name<commit_after>"""Command line interface for chalice.

Contains commands for deploying chalice.

"""
import logging
import os
import sys
import tempfile
import shutil
import traceback
import functools

import botocore.exceptions
import click
from typing import Dict, Any, Optional  # noqa

from chalice import __version__ as chalice_version
from chalice.app import Chalice  # noqa
from chalice.awsclient import TypedAWSClient
from chalice.cli.factory import CLIFactory
from chalice.config import Config  # noqa
from chalice.logs import display_logs
from chalice.utils import create_zip_file
from chalice.deploy.validate import validate_routes, validate_python_version
from chalice.utils import getting_started_prompt, UI, serialize_to_json
from chalice.constants import CONFIG_VERSION, TEMPLATE_APP, GITIGNORE
from chalice.constants import DEFAULT_STAGE_NAME
from chalice.constants import DEFAULT_APIGATEWAY_STAGE_NAME
from chalice.local import LocalDevServer  # noqa
from chalice.constants import DEFAULT_HANDLER_NAME


def create_new_project_skeleton(project_name, profile=None):
    # type: (str, Optional[str]) -> None
    chalice_dir = os.path.join(project_name, '.chalice')
    os.makedirs(chalice_dir)
    config = os.path.join(project_name, '.chalice', 'config.json')
    cfg = {
        'version': CONFIG_VERSION,
        'app_name': project_name,
        'stages': {
            DEFAULT_STAGE_NAME: {
                'api_gateway_stage': DEFAULT_APIGATEWAY_STAGE_NAME,
            }
        }
    }
    if profile is not None:
        cfg['profile'] = profile
    with open(config, 'w') as f:
        f.write(serialize_to_json(cfg))
    with open(os.path.join(project_name, 'requirements.txt'), 'w'):
        pass
    with open(os.path.join(project_name, 'app.py'), 'w') as f:
        f.write(TEMPLATE_APP % project_name)
    with open(os.path.join(project_name, '.gitignore'), 'w') as f:
        f.write(GITIGNORE)


@click.group()
@click.version_option(version=chalice_version, message='%(prog)s %(version)s')
@click.option('--project-dir',
              help='The project directory.  Defaults to CWD')
@click.option('--debug/--no-debug',
              default=False,
              help='Print debug logs to stderr.')
@click.pass_context
def cli(ctx, project_dir, debug=False):
    # type: (click.Context, str, bool) -> None
    if project_dir is None:
        project_dir = os.getcwd()
    ctx.obj['project_dir'] = project_dir
    ctx.obj['debug'] = debug
    ctx.obj['factory'] = CLIFactory(project_dir, debug, environ=os.environ)
    os.chdir(project_dir)


@cli.command()
@click.option('--host', default='127.0.0.1')
@click.option('--port', default=8000, type=click.INT)
@click.option('--stage', default=DEFAULT_STAGE_NAME,
              help='Name of the Chalice stage for the local server to use.')
@click.option('--autoreload/--no-autoreload',
              default=True,
              help='Automatically restart server when code changes.')
@click.pass_context
def local(ctx, host='127.0.0.1', port=8000, stage=DEFAULT_STAGE_NAME,
          autoreload=True):
    # type: (click.Context, str, int, str, bool) -> None
    factory = ctx.obj['factory']  # type: CLIFactory
    from chalice.cli import reloader
    # We don't create the server here because that will bind the
    # socket and we only want to do this in the worker process.
    server_factory = functools.partial(
        create_local_server, factory, host, port, stage)
    # When running `chalice local`, a stdout logger is configured
    # so you'll see the same stdout logging as you would when
    # running in lambda.  This is configuring the root logger.
    # The app-specific logger (app.log) will still continue
    # to work.
    logging.basicConfig(
        stream=sys.stdout, level=logging.INFO, format='%(message)s')
    if autoreload:
        project_dir = factory.create_config_obj(
            chalice_stage_name=stage).project_dir
        rc = reloader.run_with_reloader(
            server_factory, os.environ, project_dir)
        # Click doesn't sys.exit() with the RC this function.  The
        # recommended way to do this is to use sys.exit() directly,
        # see: https://github.com/pallets/click/issues/747
        sys.exit(rc)
    run_local_server(factory, host, port, stage)


def create_local_server(factory, host, port, stage):
    # type: (CLIFactory, str, int, str) -> LocalDevServer
    config = factory.create_config_obj(
        chalice_stage_name=stage
    )
    app_obj = config.chalice_app
    # Check that `chalice deploy` would let us deploy these routes, otherwise
    # there is no point in testing locally.
    routes = config.chalice_app.routes
    validate_routes(routes)
    server = factory.create_local_server(app_obj, config, host, port)
    return server


def run_local_server(factory, host, port, stage):
    # type: (CLIFactory, str, int, str) -> None
    server = create_local_server(factory, host, port, stage)
    server.serve_forever()


@cli.command()
@click.option('--autogen-policy/--no-autogen-policy',
              default=None,
              help='Automatically generate IAM policy for app code.')
@click.option('--profile', help='Override profile at deploy time.')
@click.option('--api-gateway-stage',
              help='Name of the API gateway stage to deploy to.')
@click.option('--stage', default=DEFAULT_STAGE_NAME,
              help=('Name of the Chalice stage to deploy to. '
                    'Specifying a new chalice stage will create '
                    'an entirely new set of AWS resources.'))
@click.option('--connection-timeout',
              type=int,
              help=('Overrides the default botocore connection '
                    'timeout.'))
@click.pass_context
def deploy(ctx, autogen_policy, profile, api_gateway_stage, stage,
           connection_timeout):
    # type: (click.Context, Optional[bool], str, str, str, int) -> None
    factory = ctx.obj['factory']  # type: CLIFactory
    factory.profile = profile
    config = factory.create_config_obj(
        chalice_stage_name=stage, autogen_policy=autogen_policy,
        api_gateway_stage=api_gateway_stage,
    )
    session = factory.create_botocore_session(
        connection_timeout=connection_timeout)
    ui = UI()
    d = factory.create_default_deployer(session=session,
                                        config=config,
                                        ui=ui)
    deployed_values = d.deploy(config, chalice_stage_name=stage)
    reporter = factory.create_deployment_reporter(ui=ui)
    reporter.display_report(deployed_values)


@cli.command('delete')
@click.option('--profile', help='Override profile at deploy time.')
@click.option('--stage', default=DEFAULT_STAGE_NAME,
              help='Name of the Chalice stage to delete.')
@click.pass_context
def delete(ctx, profile, stage):
    # type: (click.Context, str, str) -> None
    factory = ctx.obj['factory']  # type: CLIFactory
    factory.profile = profile
    config = factory.create_config_obj(chalice_stage_name=stage)
    session = factory.create_botocore_session()
    d = factory.create_deletion_deployer(session=session, ui=UI())
    d.deploy(config, chalice_stage_name=stage)


@cli.command()
@click.option('--num-entries', default=None, type=int,
              help='Max number of log entries to show.')
@click.option('--include-lambda-messages/--no-include-lambda-messages',
              default=False,
              help='Controls whether or not lambda log messages are included.')
@click.option('--stage', default=DEFAULT_STAGE_NAME)
@click.option('-n', '--name',
              help='The name of the lambda function to retrieve logs from.',
              default=DEFAULT_HANDLER_NAME)
@click.option('--profile', help='The profile to use for fetching logs.')
@click.pass_context
def logs(ctx, num_entries, include_lambda_messages, stage, name, profile):
    # type: (click.Context, int, bool, str, str, str) -> None
    factory = ctx.obj['factory']  # type: CLIFactory
    factory.profile = profile
    config = factory.create_config_obj(stage, False)
    deployed = config.deployed_resources(stage)
    if name in deployed.resource_names():
        lambda_arn = deployed.resource_values(name)['lambda_arn']
        session = factory.create_botocore_session()
        retriever = factory.create_log_retriever(
            session, lambda_arn)
        display_logs(retriever, num_entries, include_lambda_messages,
                     sys.stdout)


@cli.command('gen-policy')
@click.option('--filename',
              help='The filename to analyze.  Otherwise app.py is assumed.')
{'fraction_non_alphanumeric': 0.11058809855880986, 'fraction_numerical': 0.002673175267317527, 'mean_word_length': 3.2596534653465348, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 19, 'https://': 2, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 4, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 4, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '735113', 'n_tokens_mistral': 5252, 'n_tokens_neox': 5163, 'n_words': 1317}