deploy.py
def deploy(target):
    """Deploys the package and documentation.

    Proceeds in the following steps:

    1. Ensures proper environment variables are set and checks that we are on Circle CI
    2. Tags the repository with the new version
    3. Creates a standard distribution and a wheel
    4. Updates version.py to have the proper version
    5. Commits the ChangeLog, AUTHORS, and version.py file
    6. Pushes to PyPI
    7. Pushes the tags and newly committed files

    Raises:
        `EnvironmentError`:
            - Not running on CircleCI
            - `*_PYPI_USERNAME` and/or `*_PYPI_PASSWORD` environment variables
               are missing
            - Attempting to deploy to production from a branch that isn't master
    """
    # Ensure proper environment
    if not os.getenv(CIRCLECI_ENV_VAR):     # pragma: no cover
        raise EnvironmentError('Must be on CircleCI to run this script')

    current_branch = os.getenv('CIRCLE_BRANCH')
    if (target == 'PROD') and (current_branch != 'master'):
        raise EnvironmentError((
            'Refusing to deploy to production from branch {current_branch!r}. '
            'Production deploys can only be made from master.'
        ).format(current_branch=current_branch))

    if target in ('PROD', 'TEST'):
        pypi_username = os.getenv('{target}_PYPI_USERNAME'.format(target=target))
        pypi_password = os.getenv('{target}_PYPI_PASSWORD'.format(target=target))
    else:
        raise ValueError(
            "Deploy target must be 'PROD' or 'TEST', got {target!r}.".format(target=target))

    if not (pypi_username and pypi_password):  # pragma: no cover
        raise EnvironmentError((
            "Missing '{target}_PYPI_USERNAME' and/or '{target}_PYPI_PASSWORD' "
            "environment variables. These are required to push to PyPI."
        ).format(target=target))

    # Twine requires these environment variables to be set. Subprocesses will
    # inherit these when we invoke them, so no need to pass them on the command
    # line. We want to avoid that in case something's logging each command run.
    os.environ['TWINE_USERNAME'] = pypi_username
    os.environ['TWINE_PASSWORD'] = pypi_password

    # Set up git on circle to push to the current branch
    _shell('git config --global user.email "oss@cloverhealth.com"')
    _shell('git config --global user.name "Circle CI"')
    _shell('git config push.default current')

    # Obtain the version to deploy
    ret = _shell('make version', stdout=subprocess.PIPE)
    version = ret.stdout.decode('utf-8').strip()

    print('Deploying version {version!r}...'.format(version=version))

    # Tag the version
    _shell('git tag -f -a {version} -m "Version {version}"'.format(version=version))

    # Update the version
    _shell(
        'sed -i.bak "s/^__version__ = .*/__version__ = {version!r}/" */version.py'.format(
            version=version))

    # Create a standard distribution and a wheel
    _shell('python setup.py sdist bdist_wheel')

    # Add the updated ChangeLog and AUTHORS
    _shell('git add ChangeLog AUTHORS */version.py')

    # Start the commit message with "Merge" so that PBR will ignore it in the
    # ChangeLog. Use [skip ci] to ensure CircleCI doesn't recursively deploy.
    _shell('git commit --no-verify -m "Merge autogenerated files [skip ci]"')

    # Push the distributions to PyPI.
    _pypi_push('dist')

    # Push the tag and AUTHORS / ChangeLog after successful PyPI deploy
    _shell('git push --follow-tags')

    print('Deployment complete. Latest version is {version}.'.format(version=version))
def deploy(target):
    """Deploys the package and documentation.

    Proceeds in the following steps:

    1. Ensures proper environment variables are set and checks that we are on Circle CI
    2. Tags the repository with the new version
    3. Creates a standard distribution and a wheel
    4. Updates version.py to have the proper version
    5. Commits the ChangeLog, AUTHORS, and version.py file
    6. Pushes to PyPI
    7. Pushes the tags and newly committed files

    Raises:
        `EnvironmentError`:
            - Not running on CircleCI
            - `*_PYPI_USERNAME` and/or `*_PYPI_PASSWORD` environment variables
               are missing
            - Attempting to deploy to production from a branch that isn't master
    """
    # Ensure proper environment
    if not os.getenv(CIRCLECI_ENV_VAR):     # pragma: no cover
        raise EnvironmentError('Must be on CircleCI to run this script')

    current_branch = os.getenv('CIRCLE_BRANCH')
    if (target == 'PROD') and (current_branch != 'master'):
        raise EnvironmentError((
            'Refusing to deploy to production from branch {current_branch!r}. '
            'Production deploys can only be made from master.'
        ).format(current_branch=current_branch))

    if target in ('PROD', 'TEST'):
        pypi_username = os.getenv('{target}_PYPI_USERNAME'.format(target=target))
        pypi_password = os.getenv('{target}_PYPI_PASSWORD'.format(target=target))
    else:
        raise ValueError(
            "Deploy target must be 'PROD' or 'TEST', got {target!r}.".format(target=target))

    if not (pypi_username and pypi_password):  # pragma: no cover
        raise EnvironmentError((
            "Missing '{target}_PYPI_USERNAME' and/or '{target}_PYPI_PASSWORD' "
            "environment variables. These are required to push to PyPI."
        ).format(target=target))

    # Twine requires these environment variables to be set. Subprocesses will
    # inherit these when we invoke them, so no need to pass them on the command
    # line. We want to avoid that in case something's logging each command run.
    os.environ['TWINE_USERNAME'] = pypi_username
    os.environ['TWINE_PASSWORD'] = pypi_password

    # Set up git on circle to push to the current branch
    _shell('git config --global user.email "oss@cloverhealth.com"')
    _shell('git config --global user.name "Circle CI"')
    _shell('git config push.default current')

    # Obtain the version to deploy
    ret = _shell('make version', stdout=subprocess.PIPE)
    version = ret.stdout.decode('utf-8').strip()

    print('Deploying version {version!r}...'.format(version=version))

    # Tag the version
    _shell('git tag -f -a {version} -m "Version {version}"'.format(version=version))

    # Update the version
    _shell(
        'sed -i.bak "s/^__version__ = .*/__version__ = {version!r}/" */version.py'.format(
            version=version))

    # Create a standard distribution and a wheel
    _shell('python setup.py sdist bdist_wheel')

    # Add the updated ChangeLog and AUTHORS
    _shell('git add ChangeLog AUTHORS */version.py')

    # Start the commit message with "Merge" so that PBR will ignore it in the
    # ChangeLog. Use [skip ci] to ensure CircleCI doesn't recursively deploy.
    _shell('git commit --no-verify -m "Merge autogenerated files [skip ci]"')

    # Push the distributions to PyPI.
    _pypi_push('dist')

    # Push the tag and AUTHORS / ChangeLog after successful PyPI deploy
    _shell('git push --follow-tags')

    print('Deployment complete. Latest version is {version}.'.format(version=version))
Deploys the package and documentation.

    Proceeds in the following steps:

    1. Ensures proper environment variables are set and checks that we are on Circle CI
    2. Tags the repository with the new version
    3. Creates a standard distribution and a wheel
    4. Updates version.py to have the proper version
    5. Commits the ChangeLog, AUTHORS, and version.py file
    6. Pushes to PyPI
    7. Pushes the tags and newly committed files

    Raises:
        `EnvironmentError`:
            - Not running on CircleCI
            - `*_PYPI_USERNAME` and/or `*_PYPI_PASSWORD` environment variables
               are missing
            - Attempting to deploy to production from a branch that isn't master