diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 000000000..fe7159848 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "python.linting.pylintEnabled": false +} \ No newline at end of file diff --git a/bin/compile b/bin/compile index ff01f8b36..08117af52 100755 --- a/bin/compile +++ b/bin/compile @@ -205,12 +205,19 @@ fi export CACHED_PYTHON_STACK # Pipenv Python version support. -# Detect the version of Python requested from a Pipfile (e.g. python_version or python_full_version). +# Detect the version of Python requested from a Pipfile or Pipfile.lock file. # Convert it to a runtime.txt file. # shellcheck source=bin/steps/pipenv-python-version source "$BIN_DIR/steps/pipenv-python-version" +# Poetry Python version support. +# Detect the version of Python requested from a pyproject.toml or poetry.lock file. +# Convert it to a runtime.txt file. + +# shellcheck source=bin/steps/poetry-python-version +source "$BIN_DIR/steps/poetry-python-version" + # If no runtime was provided by the user, assume the default Python runtime version. if [ ! -f runtime.txt ]; then echo "$DEFAULT_PYTHON_VERSION" > runtime.txt @@ -240,10 +247,14 @@ fi source "$BIN_DIR/steps/python" mtime "python.install.time" "${start}" -# Install Pipenv dependencies, if a Pipfile was provided. +# Install Pipenv dependencies if Pipenv managed project. # shellcheck source=bin/steps/pipenv source "$BIN_DIR/steps/pipenv" +# Install Poetry dependencies if Poetry managed project. +# shellcheck source=bin/steps/poetry +source "$BIN_DIR/steps/poetry" + # Uninstall removed dependencies with Pip. # The buildpack will automatically remove any declared dependencies (in requirements.txt) # that were explicitly removed. This machinery is a bit complex, but it is not complicated. @@ -256,7 +267,7 @@ mtime "pip.uninstall.time" "${start}" # This allows for people to ship a setup.py application to Heroku # (which is rare, but I vouch that it should work!) -if [ ! -f requirements.txt ] && [ ! -f Pipfile ]; then +if [ ! -f requirements.txt ] && [ ! -f Pipfile ] && [ ! -f Pipfile.lock ] && [ ! -f pyproject.toml ] && [ ! -f poetry.lock ]; then echo "-e ." > requirements.txt fi diff --git a/bin/detect b/bin/detect index eeb965b0f..13ab74ce4 100755 --- a/bin/detect +++ b/bin/detect @@ -15,8 +15,8 @@ BUILD_DIR=$1 # Exit early if app is clearly not Python. -if [ ! -f "$BUILD_DIR/requirements.txt" ] && [ ! -f "$BUILD_DIR/setup.py" ] && [ ! -f "$BUILD_DIR/Pipfile" ]; then +if [ -f "$BUILD_DIR/requirements.txt" ] || [ -f "$BUILD_DIR/setup.py" ] || [ -f "$BUILD_DIR/Pipfile" ] || [ -f "$BUILD_DIR/Pipfile.lock"] || [ -f "$BUILD_DIR/pyproject.py"] || [ -f "$BUILD_DIR/poetry.lock"]; then + echo Python Project Detected... +else exit 1 -fi - -echo Python +fi \ No newline at end of file diff --git a/bin/steps/pipenv b/bin/steps/pipenv index 119062121..db6d339fe 100755 --- a/bin/steps/pipenv +++ b/bin/steps/pipenv @@ -6,81 +6,70 @@ source "$BIN_DIR/utils" set -e -if [[ -f Pipfile.lock ]]; then - if [[ -f .heroku/python/Pipfile.lock.sha256 ]]; then - if [[ $(openssl dgst -sha256 Pipfile.lock) == $(cat .heroku/python/Pipfile.lock.sha256) ]]; then - # Measure that we're using Pipenv. - mcount "tool.pipenv" +if [[ -f Pipfile || -f Pipfile.lock ]]; then + mcount "tool.pipenv" - # Don't skip installation of there are git deps. - if ! grep -q 'git' Pipfile.lock; then - echo "Skipping installation, as Pipfile.lock hasn't changed since last deploy." | indent - - mcount "tool.pipenv" - export SKIP_PIPENV_INSTALL=1 - export SKIP_PIP_INSTALL=1 - fi - fi + # Set Pip env vars + # This reads certain environment variables set on the Heroku app config + # and makes them accessible to the pip install process. + # PIP_EXTRA_INDEX_URL allows for an alternate pypi URL to be used. + if [[ -r "$ENV_DIR/PIP_EXTRA_INDEX_URL" ]]; then + PIP_EXTRA_INDEX_URL="$(cat "$ENV_DIR/PIP_EXTRA_INDEX_URL")" + export PIP_EXTRA_INDEX_URL + mcount "buildvar.PIP_EXTRA_INDEX_URL" fi -fi - -if [ ! "$SKIP_PIPENV_INSTALL" ]; then - # Pipenv support (Generate requriements.txt with pipenv). - if [[ -f Pipfile ]]; then - # Measure that we're using Pipenv. - mcount "tool.pipenv" + # Set SLUGIFY_USES_TEXT_UNIDECODE, required for Airflow versions >=1.10 + if [[ -r "$ENV_DIR/SLUGIFY_USES_TEXT_UNIDECODE" ]]; then + SLUGIFY_USES_TEXT_UNIDECODE="$(cat "$ENV_DIR/SLUGIFY_USES_TEXT_UNIDECODE")" + export SLUGIFY_USES_TEXT_UNIDECODE + mcount "buildvar.SLUGIFY_USES_TEXT_UNIDECODE" + fi - # Skip pip install, later. - export SKIP_PIP_INSTALL=1 + # Set PIPEN_VERSION implicitly + if [[ -r "$ENV_DIR/PIPENV_VERSION" ]]; then + PIPENV_VERSION="$(cat "$ENV_DIR/PIPENV_VERSION")" + export PIPENV_VERSION + mcount "buildvar.PIPENV_VERSION" + else + export PIPENV_VERSION="2018.5.18" + fi - # Set Pip env vars - # This reads certain environment variables set on the Heroku app config - # and makes them accessible to the pip install process. - # - # PIP_EXTRA_INDEX_URL allows for an alternate pypi URL to be used. - if [[ -r "$ENV_DIR/PIP_EXTRA_INDEX_URL" ]]; then - PIP_EXTRA_INDEX_URL="$(cat "$ENV_DIR/PIP_EXTRA_INDEX_URL")" - export PIP_EXTRA_INDEX_URL - mcount "buildvar.PIP_EXTRA_INDEX_URL" - fi + # Install pipenv. + # Due to weird old pip behavior and pipenv behavior, pipenv upgrades pip + # to latest if only --upgrade is specified. Specify upgrade strategy to + # avoid this eager behavior. + /app/.heroku/python/bin/pip install pipenv==$PIPENV_VERSION --upgrade --upgrade-strategy only-if-needed &> /dev/null - # Set SLUGIFY_USES_TEXT_UNIDECODE, required for Airflow versions >=1.10 - if [[ -r "$ENV_DIR/SLUGIFY_USES_TEXT_UNIDECODE" ]]; then - SLUGIFY_USES_TEXT_UNIDECODE="$(cat "$ENV_DIR/SLUGIFY_USES_TEXT_UNIDECODE")" - export SLUGIFY_USES_TEXT_UNIDECODE - mcount "buildvar.SLUGIFY_USES_TEXT_UNIDECODE" - fi + # Install the dependencies. + if [[ ! -f Pipfile.lock ]]; then + + /app/.heroku/python/bin/pipenv lock 2>&1 | cleanup | indent - export PIPENV_VERSION="2018.5.18" - - # Install pipenv. - # Due to weird old pip behavior and pipenv behavior, pipenv upgrades pip - # to latest if only --upgrade is specified. Specify upgrade strategy to - # avoid this eager behavior. - /app/.heroku/python/bin/pip install pipenv==$PIPENV_VERSION --upgrade --upgrade-strategy only-if-needed &> /dev/null + fi - # Install the dependencies. + # Install the dependencies. if [[ ! -f Pipfile.lock ]]; then - puts-step "Installing dependencies with Pipenv $PIPENV_VERSION…" - /app/.heroku/python/bin/pipenv install --system --skip-lock 2>&1 | indent + if [ "$INSTALL_TEST" ]; then + puts-step "Installing default and development dependencies with Pipenv (Pipfile) $PIPENV_VERSION…" + /app/.heroku/python/bin/pipenv install --system --skip-lock --dev 2>&1 | cleanup | indent + else + puts-step "Installing default dependencies with Pipenv (Pipfile) $PIPENV_VERSION…" + /app/.heroku/python/bin/pipenv install --system --skip-lock 2>&1 | cleanup | indent + fi else - pipenv-to-pip Pipfile.lock > requirements.txt - "$BIN_DIR/steps/pip-uninstall" - cp requirements.txt .heroku/python/requirements-declared.txt - openssl dgst -sha256 Pipfile.lock > .heroku/python/Pipfile.lock.sha256 - - puts-step "Installing dependencies with Pipenv $PIPENV_VERSION…" - /app/.heroku/python/bin/pipenv install --system --deploy 2>&1 | indent - fi - - # Install the test dependencies, for CI. - if [ "$INSTALL_TEST" ]; then - puts-step "Installing test dependencies…" - /app/.heroku/python/bin/pipenv install --dev --system --deploy 2>&1 | cleanup | indent + if [ "$INSTALL_TEST" ]; then + puts-step "Installing default and development dependencies with Pipenv (Pipfile.lock) $PIPENV_VERSION…" + /app/.heroku/python/bin/pipenv install --system --deploy --dev 2>&1 | cleanup | indent + else + puts-step "Installing default dependencies with Pipenv (Pipfile.lock) $PIPENV_VERSION…" + /app/.heroku/python/bin/pipenv install --system --deploy 2>&1 | cleanup | indent + fi fi - fi -else + + # Skip pip install later on. export SKIP_PIP_INSTALL=1 - pipenv-to-pip Pipfile.lock > requirements.txt + fi +set +e + diff --git a/bin/steps/pipenv-python-version b/bin/steps/pipenv-python-version index dcdf3c261..11075e345 100755 --- a/bin/steps/pipenv-python-version +++ b/bin/steps/pipenv-python-version @@ -1,35 +1,60 @@ #!/usr/bin/env bash - -# Detect Python-version with Pipenv. - -if [[ -f $BUILD_DIR/Pipfile ]]; then +if [[ -f $BUILD_DIR/Pipfile || -f $BUILD_DIR/Pipfile.lock ]]; then if [[ ! -f $BUILD_DIR/runtime.txt ]]; then if [[ ! -f $BUILD_DIR/Pipfile.lock ]]; then puts-warn "No 'Pipfile.lock' found! We recommend you commit this into your repository." - fi - if [[ -f $BUILD_DIR/Pipfile.lock ]]; then set +e - PYTHON=$(jq -r '._meta.requires.python_full_version' "$BUILD_DIR/Pipfile.lock") - if [[ "$PYTHON" != "null" ]]; then + PYTHON=$(sed -nr "/^\[requires\]/ { :l /^python_full_version[ ]*=/ { s/.*=[ ]*//; p; q;}; n; b l;}" "$BUILD_DIR/Pipfile" | tr -d '"') + if ! [ -z "$PYTHON"]; then echo "python-$PYTHON" > "$BUILD_DIR/runtime.txt" + else + PYTHON=$(sed -nr "/^\[requires\]/ { :l /^python_version[ ]*=/ { s/.*=[ ]*//; p; q;}; n; b l;}" "$BUILD_DIR/Pipfile" | tr -d '"') + if ! [ -z "$PYTHON"]; then + if [ "$PYTHON" = 2.7 ]; then + echo "$LATEST_27" > "$BUILD_DIR/runtime.txt" + fi + if [ "$PYTHON" = 3.4 ]; then + echo "$LATEST_34" > "$BUILD_DIR/runtime.txt" + fi + if [ "$PYTHON" = 3.5 ]; then + echo "$LATEST_35" > "$BUILD_DIR/runtime.txt" + fi + if [ "$PYTHON" = 3.6 ]; then + echo "$LATEST_36" > "$BUILD_DIR/runtime.txt" + fi + if [ "$PYTHON" = 3.7 ]; then + echo "$LATEST_37" > "$BUILD_DIR/runtime.txt" + fi + fi fi set -e - - if [[ "$PYTHON" == "null" ]]; then + else + set +e + PYTHON=$(jq -r '._meta.requires.python_full_version' "$BUILD_DIR/Pipfile.lock") + if [[ "$PYTHON" != "null" ]]; then + echo "python-$PYTHON" > "$BUILD_DIR/runtime.txt" + else PYTHON=$(jq -r '._meta.requires.python_version' "$BUILD_DIR/Pipfile.lock") - if [ "$PYTHON" = 2.7 ]; then - echo "$LATEST_27" > "$BUILD_DIR/runtime.txt" - fi - if [ "$PYTHON" = 3.6 ]; then - echo "$LATEST_36" > "$BUILD_DIR/runtime.txt" - fi - if [ "$PYTHON" = 3.7 ]; then - echo "$LATEST_37" > "$BUILD_DIR/runtime.txt" + if [[ "$PYTHON" != "null" ]]; then + if [ "$PYTHON" = 2.7 ]; then + echo "$LATEST_27" > "$BUILD_DIR/runtime.txt" + fi + if [ "$PYTHON" = 3.4 ]; then + echo "$LATEST_34" > "$BUILD_DIR/runtime.txt" + fi + if [ "$PYTHON" = 3.5 ]; then + echo "$LATEST_35" > "$BUILD_DIR/runtime.txt" + fi + if [ "$PYTHON" = 3.6 ]; then + echo "$LATEST_36" > "$BUILD_DIR/runtime.txt" + fi + if [ "$PYTHON" = 3.7 ]; then + echo "$LATEST_37" > "$BUILD_DIR/runtime.txt" + fi fi fi - - + set -e fi fi fi diff --git a/bin/steps/poetry b/bin/steps/poetry new file mode 100644 index 000000000..4b998e264 --- /dev/null +++ b/bin/steps/poetry @@ -0,0 +1,55 @@ +#!/usr/bin/env bash + +# shellcheck source=bin/utils +source "$BIN_DIR/utils" +set -e + +if [[ -f pyproject.toml || -f poetry.lock ]]; then + mcount "tool.poetry" + + # Set Pip env varsz + # This reads certain environment variables set on the Heroku app config + # and makes them accessible to the pip install process. + # PIP_EXTRA_INDEX_URL allows for an alternate pypi URL to be used. + if [[ -r "$ENV_DIR/PIP_EXTRA_INDEX_URL" ]]; then + PIP_EXTRA_INDEX_URL="$(cat "$ENV_DIR/PIP_EXTRA_INDEX_URL")" + export PIP_EXTRA_INDEX_URL + mcount "buildvar.PIP_EXTRA_INDEX_URL" + fi + + # Set SLUGIFY_USES_TEXT_UNIDECODE, required for Airflow versions >=1.10 + if [[ -r "$ENV_DIR/SLUGIFY_USES_TEXT_UNIDECODE" ]]; then + SLUGIFY_USES_TEXT_UNIDECODE="$(cat "$ENV_DIR/SLUGIFY_USES_TEXT_UNIDECODE")" + export SLUGIFY_USES_TEXT_UNIDECODE + mcount "buildvar.SLUGIFY_USES_TEXT_UNIDECODE" + fi + + # # Get Poetry Version from file + if [[ -r "$ENV_DIR/POETRY_VERSION" ]]; then + POETRY_VERSION="$(cat "$ENV_DIR/POETRY_VERSION")" + export POETRY_VERSION + mcount "buildvar.POETRY_VERSION" + else + export POETRY_VERSION="0.12.11" + fi + + # Install poetry. + /app/.heroku/python/bin/pip install poetry==$POETRY_VERSION 2>&1 | cleanup | indent + + # Install the dependencies. + if [ "$INSTALL_TEST" ]; then + puts-step "Installing default and development dependencies with Poetry $POETRY_VERSION…" + /app/.heroku/python/bin/poetry install 2>&1 | cleanup | indent + else + puts-step "Installing default dependencies with Poetry $POETRY_VERSION…" + /app/.heroku/python/bin/poetry install --no-dev 2>&1 | cleanup | indent + fi + + /app/.heroku/python/bin/poetry run pip freeze > requirements.txt + + # Skip pip install later on. + export SKIP_PIP_INSTALL=1 + +fi +set +e + diff --git a/bin/steps/poetry-python-version b/bin/steps/poetry-python-version new file mode 100644 index 000000000..0c3d30307 --- /dev/null +++ b/bin/steps/poetry-python-version @@ -0,0 +1,49 @@ +#!/usr/bin/env bash + +set +e +if [[ -f $BUILD_DIR/pyproject.toml || -f $BUILD_DIR/poetry.lock ]]; then + + if [[ ! -f $BUILD_DIR/runtime.txt ]]; then + + if [[ ! -f $BUILD_DIR/poetry.lock ]]; then + + puts-warn "No 'poetry.lock' found! We recommend you commit this into your repository." + PYTHON=$(sed -nr "/^\[tool.poetry.dependencies\]/ { :l /^python[ ]*=/ { s/.*=[ ]*//; p; q;}; n; b l;}" "$BUILD_DIR/pyproject.toml" | tr -d '"^') + echo $PYTHON + if ! [ -z $PYTHON]; then + if [[ "$PYTHON" == +([0-9]).+([0-9]).+([0-9]) ]]; then + echo "python-$PYTHON" > "$BUILD_DIR/runtime.txt" + elif [ $PYTHON = 2.7 ]; then + echo "$LATEST_27" > "$BUILD_DIR/runtime.txt" + elif [ "$PYTHON" = 3.4 ]; then + echo "$LATEST_34" > "$BUILD_DIR/runtime.txt" + elif [ "$PYTHON" = 3.5 ]; then + echo "$LATEST_35" > "$BUILD_DIR/runtime.txt" + elif [ "$PYTHON" = 3.6 ]; then + echo "$LATEST_36" > "$BUILD_DIR/runtime.txt" + elif [ "$PYTHON" = 3.7 ]; then + echo "$LATEST_37" > "$BUILD_DIR/runtime.txt" + fi + fi + else + PYTHON=$(sed -nr "/^\[metadata\]/ { :l /^python-versions[ ]*=/ { s/.*=[ ]*//; p; q;}; n; b l;}" "$BUILD_DIR/poetry.lock" | tr -d '"^') + if ! [ -z $PYTHON]; then + if [[ "$PYTHON" == +([0-9]).+([0-9]).+([0-9]) ]]; then + echo "python-$PYTHON" > "$BUILD_DIR/runtime.txt" + elif [ $PYTHON = 2.7 ]; then + echo "$LATEST_27" > "$BUILD_DIR/runtime.txt" + elif [ "$PYTHON" = 3.4 ]; then + echo "$LATEST_34" > "$BUILD_DIR/runtime.txt" + elif [ "$PYTHON" = 3.5 ]; then + echo "$LATEST_35" > "$BUILD_DIR/runtime.txt" + elif [ "$PYTHON" = 3.6 ]; then + echo "$LATEST_36" > "$BUILD_DIR/runtime.txt" + elif [ "$PYTHON" = 3.7 ]; then + echo "$LATEST_37" > "$BUILD_DIR/runtime.txt" + fi + fi + fi + fi +fi + +set -e diff --git a/bin/utils b/bin/utils index f625645df..4a7440561 100755 --- a/bin/utils +++ b/bin/utils @@ -20,7 +20,6 @@ indent() { sed "s/^/ /" } - # Clean up pip output cleanup() { sed -e 's/\.\.\.\+/.../g' | sed -e '/already satisfied/Id' | sed -e '/No files were found to uninstall/Id' | sed -e '/Overwriting/Id' | sed -e '/python executable/Id' | sed -e '/no previously-included files/Id' diff --git a/test/fixtures/collectstatic-poetry/.gitignore b/test/fixtures/collectstatic-poetry/.gitignore new file mode 100644 index 000000000..f95d164a6 --- /dev/null +++ b/test/fixtures/collectstatic-poetry/.gitignore @@ -0,0 +1,5 @@ +venv +*.pyc +staticfiles +.env +db.sqlite3 diff --git a/test/fixtures/collectstatic-poetry/Procfile b/test/fixtures/collectstatic-poetry/Procfile new file mode 100644 index 000000000..b6f89307b --- /dev/null +++ b/test/fixtures/collectstatic-poetry/Procfile @@ -0,0 +1 @@ +web: gunicorn gettingstarted.wsgi diff --git a/test/fixtures/collectstatic-poetry/Procfile.windows b/test/fixtures/collectstatic-poetry/Procfile.windows new file mode 100644 index 000000000..69789e554 --- /dev/null +++ b/test/fixtures/collectstatic-poetry/Procfile.windows @@ -0,0 +1 @@ +web: python manage.py runserver 0.0.0.0:5000 diff --git a/test/fixtures/collectstatic-poetry/README.md b/test/fixtures/collectstatic-poetry/README.md new file mode 100644 index 000000000..23ff5a077 --- /dev/null +++ b/test/fixtures/collectstatic-poetry/README.md @@ -0,0 +1,44 @@ +# Python: Getting Started + +A barebones Django app, which can easily be deployed to Heroku. + +This application supports the [Getting Started with Python on Heroku](https://devcenter.heroku.com/articles/getting-started-with-python) article - check it out. + +## Running Locally + +Make sure you have Python [installed properly](http://install.python-guide.org). Also, install the [Heroku CLI](https://devcenter.heroku.com/articles/heroku-cli) and [Postgres](https://devcenter.heroku.com/articles/heroku-postgresql#local-setup). + +```sh +$ git clone git@github.com:heroku/python-getting-started.git +$ cd python-getting-started + +$ pipenv install + +$ createdb python_getting_started + +$ python manage.py migrate +$ python manage.py collectstatic + +$ heroku local +``` + +Your app should now be running on [localhost:5000](http://localhost:5000/). + +## Deploying to Heroku + +```sh +$ heroku create +$ git push heroku master + +$ heroku run python manage.py migrate +$ heroku open +``` +or + +[![Deploy](https://www.herokucdn.com/deploy/button.svg)](https://heroku.com/deploy) + +## Documentation + +For more information about using Python on Heroku, see these Dev Center articles: + +- [Python on Heroku](https://devcenter.heroku.com/categories/python) diff --git a/test/fixtures/collectstatic-poetry/app.json b/test/fixtures/collectstatic-poetry/app.json new file mode 100644 index 000000000..8721c4994 --- /dev/null +++ b/test/fixtures/collectstatic-poetry/app.json @@ -0,0 +1,22 @@ +{ + "name": "Start on Heroku: Python", + "description": "A barebones Python app, which can easily be deployed to Heroku.", + "image": "heroku/python", + "repository": "https://github.com/heroku/python-getting-started", + "keywords": ["python", "django" ], + "addons": [ "heroku-postgresql" ], + "env": { + "SECRET_KEY": { + "description": "The secret key for the Django application.", + "generator": "secret" + } + }, + "environments": { + "test": { + "scripts": { + "test-setup": "python manage.py collectstatic --noinput", + "test": "python manage.py test" + } + } + } +} diff --git a/test/fixtures/collectstatic-poetry/gettingstarted/__init__.py b/test/fixtures/collectstatic-poetry/gettingstarted/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/test/fixtures/collectstatic-poetry/gettingstarted/settings.py b/test/fixtures/collectstatic-poetry/gettingstarted/settings.py new file mode 100644 index 000000000..b23e28bc2 --- /dev/null +++ b/test/fixtures/collectstatic-poetry/gettingstarted/settings.py @@ -0,0 +1,125 @@ +""" +Django settings for gettingstarted project. + +Generated by 'django-admin startproject' using Django 2.0. + +For more information on this file, see +https://docs.djangoproject.com/en/2.0/topics/settings/ + +For the full list of settings and their values, see +https://docs.djangoproject.com/en/2.0/ref/settings/ +""" + +import os +import django_heroku + + +# Build paths inside the project like this: os.path.join(BASE_DIR, ...) +BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + + +# Quick-start development settings - unsuitable for production +# See https://docs.djangoproject.com/en/2.0/howto/deployment/checklist/ + +# SECURITY WARNING: keep the secret key used in production secret! +SECRET_KEY = 'CHANGE_ME!!!! (P.S. the SECRET_KEY environment variable will be used, if set, instead).' + +# SECURITY WARNING: don't run with debug turned on in production! +DEBUG = True + +ALLOWED_HOSTS = [] + + +# Application definition + +INSTALLED_APPS = [ + 'django.contrib.admin', + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.messages', + 'django.contrib.staticfiles', + 'hello' +] + +MIDDLEWARE = [ + 'django.middleware.security.SecurityMiddleware', + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.middleware.common.CommonMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', + 'django.middleware.clickjacking.XFrameOptionsMiddleware', +] + +ROOT_URLCONF = 'gettingstarted.urls' + +TEMPLATES = [ + { + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + 'DIRS': [], + 'APP_DIRS': True, + 'OPTIONS': { + 'context_processors': [ + 'django.template.context_processors.debug', + 'django.template.context_processors.request', + 'django.contrib.auth.context_processors.auth', + 'django.contrib.messages.context_processors.messages', + ], + }, + }, +] + +WSGI_APPLICATION = 'gettingstarted.wsgi.application' + + +# Database +# https://docs.djangoproject.com/en/2.0/ref/settings/#databases + +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.sqlite3', + 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), + } +} + + +# Password validation +# https://docs.djangoproject.com/en/2.0/ref/settings/#auth-password-validators + +AUTH_PASSWORD_VALIDATORS = [ + { + 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', + }, +] + + +# Internationalization +# https://docs.djangoproject.com/en/2.0/topics/i18n/ + +LANGUAGE_CODE = 'en-us' + +TIME_ZONE = 'UTC' + +USE_I18N = True + +USE_L10N = True + +USE_TZ = True + + +# Static files (CSS, JavaScript, Images) +# https://docs.djangoproject.com/en/2.0/howto/static-files/ + +STATIC_URL = '/static/' + +django_heroku.settings(locals()) \ No newline at end of file diff --git a/test/fixtures/collectstatic-poetry/gettingstarted/static/humans.txt b/test/fixtures/collectstatic-poetry/gettingstarted/static/humans.txt new file mode 100644 index 000000000..e69de29bb diff --git a/test/fixtures/collectstatic-poetry/gettingstarted/urls.py b/test/fixtures/collectstatic-poetry/gettingstarted/urls.py new file mode 100644 index 000000000..36492ed3c --- /dev/null +++ b/test/fixtures/collectstatic-poetry/gettingstarted/urls.py @@ -0,0 +1,17 @@ +from django.conf.urls import include, url +from django.urls import path + +from django.contrib import admin +admin.autodiscover() + +import hello.views + +# Examples: +# url(r'^$', 'gettingstarted.views.home', name='home'), +# url(r'^blog/', include('blog.urls')), + +urlpatterns = [ + url(r'^$', hello.views.index, name='index'), + url(r'^db', hello.views.db, name='db'), + path('admin/', admin.site.urls), +] diff --git a/test/fixtures/collectstatic-poetry/gettingstarted/wsgi.py b/test/fixtures/collectstatic-poetry/gettingstarted/wsgi.py new file mode 100644 index 000000000..feb0af46f --- /dev/null +++ b/test/fixtures/collectstatic-poetry/gettingstarted/wsgi.py @@ -0,0 +1,15 @@ +""" +WSGI config for gettingstarted project. + +It exposes the WSGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/1.6/howto/deployment/wsgi/ +""" + +import os +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "gettingstarted.settings") + +from django.core.wsgi import get_wsgi_application + +application = get_wsgi_application() diff --git a/test/fixtures/collectstatic-poetry/hello/__init__.py b/test/fixtures/collectstatic-poetry/hello/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/test/fixtures/collectstatic-poetry/hello/admin.py b/test/fixtures/collectstatic-poetry/hello/admin.py new file mode 100644 index 000000000..8c38f3f3d --- /dev/null +++ b/test/fixtures/collectstatic-poetry/hello/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/test/fixtures/collectstatic-poetry/hello/migrations/0001_initial.py b/test/fixtures/collectstatic-poetry/hello/migrations/0001_initial.py new file mode 100644 index 000000000..99c4a82a5 --- /dev/null +++ b/test/fixtures/collectstatic-poetry/hello/migrations/0001_initial.py @@ -0,0 +1,23 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.1 on 2016-01-27 21:54 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='Greeting', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('when', models.DateTimeField(auto_now_add=True, verbose_name=b'date created')), + ], + ), + ] diff --git a/test/fixtures/collectstatic-poetry/hello/migrations/__init__.py b/test/fixtures/collectstatic-poetry/hello/migrations/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/test/fixtures/collectstatic-poetry/hello/models.py b/test/fixtures/collectstatic-poetry/hello/models.py new file mode 100644 index 000000000..89d76fd1b --- /dev/null +++ b/test/fixtures/collectstatic-poetry/hello/models.py @@ -0,0 +1,5 @@ +from django.db import models + +# Create your models here. +class Greeting(models.Model): + when = models.DateTimeField('date created', auto_now_add=True) diff --git a/test/fixtures/collectstatic-poetry/hello/static/lang-logo.png b/test/fixtures/collectstatic-poetry/hello/static/lang-logo.png new file mode 100644 index 000000000..f04aff1e3 Binary files /dev/null and b/test/fixtures/collectstatic-poetry/hello/static/lang-logo.png differ diff --git a/test/fixtures/collectstatic-poetry/hello/templates/base.html b/test/fixtures/collectstatic-poetry/hello/templates/base.html new file mode 100644 index 000000000..97ec07386 --- /dev/null +++ b/test/fixtures/collectstatic-poetry/hello/templates/base.html @@ -0,0 +1,83 @@ + + + +Python Getting Started on Heroku + + + + + + + + +{% block content %}{% endblock %} + + + diff --git a/test/fixtures/collectstatic-poetry/hello/templates/db.html b/test/fixtures/collectstatic-poetry/hello/templates/db.html new file mode 100644 index 000000000..e0e03d4c6 --- /dev/null +++ b/test/fixtures/collectstatic-poetry/hello/templates/db.html @@ -0,0 +1,21 @@ +{% extends "base.html" %} +{% load staticfiles %} + +{% block content %} +
+ + +

Page View Report

+ + + + +
+ +{% endblock %} \ No newline at end of file diff --git a/test/fixtures/collectstatic-poetry/hello/templates/index.html b/test/fixtures/collectstatic-poetry/hello/templates/index.html new file mode 100644 index 000000000..8af29028b --- /dev/null +++ b/test/fixtures/collectstatic-poetry/hello/templates/index.html @@ -0,0 +1,58 @@ +{% extends "base.html" %} +{% load staticfiles %} + +{% block content %} + +
+
+ +

Getting Started with Python on Heroku

+

This is a sample Python application deployed to Heroku. It's a reasonably simple app - but a good foundation for understanding how to get the most out of the Heroku platform.

+ Getting Started with Python + Source on GitHub +
+
+
+ +
+
+
+

How this sample app works

+
    +
  • This app was deployed to Heroku, either using Git or by using Heroku Button on the repository.
  • + +
  • When Heroku received the source code, it fetched all the dependencies in the Pipfile, creating a deployable slug.
  • +
  • The platform then spins up a dyno, a lightweight container that provides an isolated environment in which the slug can be mounted and executed.
  • +
  • You can scale your app, manage it, and deploy over 150 add-on services, from the Dashboard or CLI.
  • +
+
+
+

Next Steps

+
    +
  • If you are following the Getting Started guide, then please head back to the tutorial and follow the next steps!
  • +
  • If you deployed this app by deploying the Heroku Button, then in a command line shell, run:
  • +
      +
    • git clone https://github.com/heroku/python-getting-started.git - this will create a local copy of the source code for the app
    • +
    • cd python-getting-started - change directory into the local source code repository
    • +
    • heroku git:remote -a <your-app-name> - associate the Heroku app with the repository
    • +
    • You'll now be set up to run the app locally, or deploy changes to Heroku
    • +
    +
+

Helpful Links

+ +
+
+ +
+{% endblock %} diff --git a/test/fixtures/collectstatic-poetry/hello/tests.py b/test/fixtures/collectstatic-poetry/hello/tests.py new file mode 100644 index 000000000..1d69113fe --- /dev/null +++ b/test/fixtures/collectstatic-poetry/hello/tests.py @@ -0,0 +1,18 @@ +from django.contrib.auth.models import AnonymousUser, User +from django.test import TestCase, RequestFactory + +from .views import index + +class SimpleTest(TestCase): + def setUp(self): + # Every test needs access to the request factory. + self.factory = RequestFactory() + + def test_details(self): + # Create an instance of a GET request. + request = self.factory.get('/') + request.user = AnonymousUser() + + # Test my_view() as if it were deployed at /customer/details + response = index(request) + self.assertEqual(response.status_code, 200) \ No newline at end of file diff --git a/test/fixtures/collectstatic-poetry/hello/views.py b/test/fixtures/collectstatic-poetry/hello/views.py new file mode 100644 index 000000000..8558d8451 --- /dev/null +++ b/test/fixtures/collectstatic-poetry/hello/views.py @@ -0,0 +1,20 @@ +from django.shortcuts import render +from django.http import HttpResponse + +from .models import Greeting + +# Create your views here. +def index(request): + # return HttpResponse('Hello from Python!') + return render(request, 'index.html') + + +def db(request): + + greeting = Greeting() + greeting.save() + + greetings = Greeting.objects.all() + + return render(request, 'db.html', {'greetings': greetings}) + diff --git a/test/fixtures/collectstatic-poetry/manage.py b/test/fixtures/collectstatic-poetry/manage.py new file mode 100644 index 000000000..eb3ab8af5 --- /dev/null +++ b/test/fixtures/collectstatic-poetry/manage.py @@ -0,0 +1,10 @@ +#!/usr/bin/env python +import os +import sys + +if __name__ == "__main__": + os.environ.setdefault("DJANGO_SETTINGS_MODULE", "gettingstarted.settings") + + from django.core.management import execute_from_command_line + + execute_from_command_line(sys.argv) diff --git a/test/fixtures/collectstatic-poetry/poetry.lock b/test/fixtures/collectstatic-poetry/poetry.lock new file mode 100644 index 000000000..378dd12c6 --- /dev/null +++ b/test/fixtures/collectstatic-poetry/poetry.lock @@ -0,0 +1,77 @@ +[[package]] +category = "main" +description = "Use Database URLs in your Django Application." +name = "dj-database-url" +optional = false +python-versions = "*" +version = "0.5.0" + +[[package]] +category = "main" +description = "A high-level Python Web framework that encourages rapid development and clean, pragmatic design." +name = "django" +optional = false +python-versions = ">=3.5" +version = "2.1.7" + +[package.dependencies] +pytz = "*" + +[[package]] +category = "main" +description = "This is a Django library for Heroku apps." +name = "django-heroku" +optional = false +python-versions = "*" +version = "0.3.1" + +[package.dependencies] +dj-database-url = ">=0.5.0" +django = "*" +psycopg2 = "*" +whitenoise = "*" + +[[package]] +category = "main" +description = "WSGI HTTP Server for UNIX" +name = "gunicorn" +optional = false +python-versions = ">=2.6, !=3.0.*, !=3.1.*" +version = "19.9.0" + +[[package]] +category = "main" +description = "psycopg2 - Python-PostgreSQL Database Adapter" +name = "psycopg2" +optional = false +python-versions = "*" +version = "2.7.7" + +[[package]] +category = "main" +description = "World timezone definitions, modern and historical" +name = "pytz" +optional = false +python-versions = "*" +version = "2018.9" + +[[package]] +category = "main" +description = "Radically simplified static file serving for WSGI applications" +name = "whitenoise" +optional = false +python-versions = "*" +version = "4.1.2" + +[metadata] +content-hash = "d820e2fc1577f590b409eefbedb667d8a994abf823d3661295f19c9b890c4a98" +python-versions = "^3.6" + +[metadata.hashes] +dj-database-url = ["4aeaeb1f573c74835b0686a2b46b85990571159ffc21aa57ecd4d1e1cb334163", "851785365761ebe4994a921b433062309eb882fedd318e1b0fcecc607ed02da9"] +django = ["275bec66fd2588dd517ada59b8bfb23d4a9abc5a362349139ddda3c7ff6f5ade", "939652e9d34d7d53d74d5d8ef82a19e5f8bb2de75618f7e5360691b6e9667963"] +django-heroku = ["2bc690aab89eedbe01311752320a9a12e7548e3b0ed102681acc5736a41a4762", "6af4bc3ae4a9b55eaad6dbe5164918982d2762661aebc9f83d9fa49f6009514e"] +gunicorn = ["aa8e0b40b4157b36a5df5e599f45c9c76d6af43845ba3b3b0efe2c70473c2471", "fa2662097c66f920f53f70621c6c58ca4a3c4d3434205e608e121b5b3b71f4f3"] +psycopg2 = ["02445ebbb3a11a3fe8202c413d5e6faf38bb75b4e336203ee144ca2c46529f94", "0e9873e60f98f0c52339abf8f0339d1e22bfe5aae0bcf7aabd40c055175035ec", "1148a5eb29073280bf9057c7fc45468592c1bb75a28f6df1591adb93c8cb63d0", "259a8324e109d4922b0fcd046e223e289830e2568d6f4132a3702439e5fd532b", "28dffa9ed4595429e61bacac41d3f9671bb613d1442ff43bcbec63d4f73ed5e8", "314a74302d4737a3865d40ea50e430ce1543c921ba10f39d562e807cfe2edf2a", "36b60201b6d215d7658a71493fdf6bd5e60ad9a0cffed39906627ff9f4f3afd3", "3f9d532bce54c4234161176ff3b8688ff337575ca441ea27597e112dfcd0ee0c", "5d222983847b40af989ad96c07fc3f07e47925e463baa5de716be8f805b41d9b", "6757a6d2fc58f7d8f5d471ad180a0bd7b4dd3c7d681f051504fbea7ae29c8d6f", "6a0e0f1e74edb0ab57d89680e59e7bfefad2bfbdf7c80eb38304d897d43674bb", "6ca703ccdf734e886a1cf53eb702261110f6a8b0ed74bcad15f1399f74d3f189", "8513b953d8f443c446aa79a4cc8a898bd415fc5e29349054f03a7d696d495542", "9262a5ce2038570cb81b4d6413720484cb1bc52c064b2f36228d735b1f98b794", "97441f851d862a0c844d981cbee7ee62566c322ebb3d68f86d66aa99d483985b", "a07feade155eb8e69b54dd6774cf6acf2d936660c61d8123b8b6b1f9247b67d6", "a9b9c02c91b1e3ec1f1886b2d0a90a0ea07cc529cb7e6e472b556bc20ce658f3", "ae88216f94728d691b945983140bf40d51a1ff6c7fe57def93949bf9339ed54a", "b360ffd17659491f1a6ad7c928350e229c7b7bd83a2b922b6ee541245c7a776f", "b4221957ceccf14b2abdabef42d806e791350be10e21b260d7c9ce49012cc19e", "b90758e49d5e6b152a460d10b92f8a6ccf318fcc0ee814dcf53f3a6fc5328789", "c669ea986190ed05fb289d0c100cc88064351f2b85177cbfd3564c4f4847d18c", "d1b61999d15c79cf7f4f7cc9021477aef35277fc52452cf50fd13b713c84424d", "de7bb043d1adaaf46e38d47e7a5f703bb3dab01376111e522b07d25e1a79c1e1", "e393568e288d884b94d263f2669215197840d097c7e5b0acd1a51c1ea7d1aba8", "ed7e0849337bd37d89f2c2b0216a0de863399ee5d363d31b1e5330a99044737b", "f153f71c3164665d269a5d03c7fa76ba675c7a8de9dc09a4e2c2cdc9936a7b41", "f1fb5a8427af099beb7f65093cbdb52e021b8e6dbdfaf020402a623f4181baf5", "f36b333e9f86a2fba960c72b90c34be6ca71819e300f7b1fc3d2b0f0b2c546cd", "f4526d078aedd5187d0508aa5f9a01eae6a48a470ed678406da94b4cd6524b7e"] +pytz = ["32b0891edff07e28efe91284ed9c31e123d84bea3fd98e1f72be2508f43ef8d9", "d5f05e487007e29e03409f9398d074e158d920d36eb82eaf66fb1136b0c5374c"] +whitenoise = ["118ab3e5f815d380171b100b05b76de2a07612f422368a201a9ffdeefb2251c1", "42133ddd5229eeb6a0c9899496bdbe56c292394bf8666da77deeb27454c0456a"] diff --git a/test/fixtures/collectstatic-poetry/pyproject.toml b/test/fixtures/collectstatic-poetry/pyproject.toml new file mode 100644 index 000000000..061018eb5 --- /dev/null +++ b/test/fixtures/collectstatic-poetry/pyproject.toml @@ -0,0 +1,17 @@ +[tool.poetry] +name = "collectstatic-poetry" +version = "0.1.0" +description = "" +authors = ["Your Name "] + +[tool.poetry.dependencies] +python = "^3.6" +django = "^2.1" +gunicorn = "^19.9" +django-heroku = "^0.3.1" + +[tool.poetry.dev-dependencies] + +[build-system] +requires = ["poetry>=0.12"] +build-backend = "poetry.masonry.api" diff --git a/test/fixtures/poetry-full-version/pyproject.toml b/test/fixtures/poetry-full-version/pyproject.toml new file mode 100644 index 000000000..fc946f95c --- /dev/null +++ b/test/fixtures/poetry-full-version/pyproject.toml @@ -0,0 +1,14 @@ +[tool.poetry] +name = "poetry-full-version" +version = "0.1.0" +description = "" +authors = ["Your Name "] + +[tool.poetry.dependencies] +python = "3.6.3" + +[tool.poetry.dev-dependencies] + +[build-system] +requires = ["poetry>=0.12"] +build-backend = "poetry.masonry.api" diff --git a/test/fixtures/poetry-full-version2/pyproject.toml b/test/fixtures/poetry-full-version2/pyproject.toml new file mode 100644 index 000000000..5c5e32d38 --- /dev/null +++ b/test/fixtures/poetry-full-version2/pyproject.toml @@ -0,0 +1,14 @@ +[tool.poetry] +name = "poetry-full-version2" +version = "0.1.0" +description = "" +authors = ["Your Name "] + +[tool.poetry.dependencies] +python = "2.7.15" + +[tool.poetry.dev-dependencies] + +[build-system] +requires = ["poetry>=0.12"] +build-backend = "poetry.masonry.api" diff --git a/test/fixtures/poetry-lock/pyproject.toml b/test/fixtures/poetry-lock/pyproject.toml new file mode 100644 index 000000000..b3fc59b12 --- /dev/null +++ b/test/fixtures/poetry-lock/pyproject.toml @@ -0,0 +1,14 @@ +[tool.poetry] +name = "poetry-lock" +version = "0.1.0" +description = "" +authors = ["Your Name "] + +[tool.poetry.dependencies] +python = "^3.6" + +[tool.poetry.dev-dependencies] + +[build-system] +requires = ["poetry>=0.12"] +build-backend = "poetry.masonry.api" diff --git a/test/fixtures/poetry-version2/pyproject.toml b/test/fixtures/poetry-version2/pyproject.toml new file mode 100644 index 000000000..5bb78f814 --- /dev/null +++ b/test/fixtures/poetry-version2/pyproject.toml @@ -0,0 +1,14 @@ +[tool.poetry] +name = "poetry-version2" +version = "0.1.0" +description = "" +authors = ["Your Name "] + +[tool.poetry.dependencies] +python = "^2.7" + +[tool.poetry.dev-dependencies] + +[build-system] +requires = ["poetry>=0.12"] +build-backend = "poetry.masonry.api" diff --git a/test/fixtures/poetry/pyproject.toml b/test/fixtures/poetry/pyproject.toml new file mode 100644 index 000000000..959896da6 --- /dev/null +++ b/test/fixtures/poetry/pyproject.toml @@ -0,0 +1,14 @@ +[tool.poetry] +name = "poetry" +version = "0.1.0" +description = "" +authors = ["Your Name "] + +[tool.poetry.dependencies] +python = "^3.6" + +[tool.poetry.dev-dependencies] + +[build-system] +requires = ["poetry>=0.12"] +build-backend = "poetry.masonry.api" diff --git a/test/run b/test/run index df9bacb59..05d2bed36 100755 --- a/test/run +++ b/test/run @@ -28,12 +28,36 @@ testPipenvVersion2() { assertCaptured "2.7.15" assertCapturedSuccess } + testPipenvFullVersion() { compile "pipenv-full-version" assertCaptured "3.6.3" assertCapturedSuccess } +testPoetry(){ + compile "poetry" + assertCapturedSuccess +} + +testPoetryVersion2(){ + compile "poetry-version2" + assertCaptured "2.7.15" + assertCapturedSuccess +} + +testPoetryFullVersion(){ + compile "poetry-full-version" + assertCaptured "3.6.3" + assertCapturedSuccess +} + +testPoetryFullVersion2(){ + compile "poetry-full-version2" + assertCaptured "2.7.15" + assertCapturedSuccess +} + testNoRequirements() { compile "no-requirements" assertCapturedError @@ -44,6 +68,11 @@ testCollectstatic() { assertCaptured "collectstatic" } +testCollectstaticPoetry() { + compile "collectstatic-poetry" + assertCaptured "collectstatic" +} + testGEOS() { export BUILD_WITH_GEO_LIBRARIES=1 compile "geos"