From d61027fa3e153daf0e89a13db6f8802e02ce9081 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Z=C3=BClke?= Date: Wed, 28 Aug 2024 18:05:32 -0400 Subject: [PATCH 01/31] Add viewport meta tag to HTML head (#234) Much better page dimensions and scaling on mobile devices; this tag is standard practice for HTML these days. Also see https://github.com/heroku/php-getting-started/pull/74 --- hello/templates/base.html | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/hello/templates/base.html b/hello/templates/base.html index 97ec07386..73977e0b2 100644 --- a/hello/templates/base.html +++ b/hello/templates/base.html @@ -1,7 +1,8 @@ -Python Getting Started on Heroku + Python Getting Started on Heroku + From 1da36000e9a38658396d111139d6953a10eec05b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 2 Sep 2024 08:49:44 +0100 Subject: [PATCH 02/31] Update gunicorn requirement from <23,>=22 to >=23,<24 (#235) * Update gunicorn requirement from <23,>=22 to >=22,<24 Updates the requirements on [gunicorn](https://github.com/benoitc/gunicorn) to permit the latest version. - [Release notes](https://github.com/benoitc/gunicorn/releases) - [Commits](https://github.com/benoitc/gunicorn/compare/22.0.0...23.0.0) --- updated-dependencies: - dependency-name: gunicorn dependency-type: direct:production ... Signed-off-by: dependabot[bot] * Bump lower bound --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Ed Morley <501702+edmorley@users.noreply.github.com> --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index e80db5cab..c420fa54c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ django>=5.1,<5.2 -gunicorn>=22,<23 +gunicorn>=23,<24 dj-database-url>=2,<3 whitenoise[brotli]>=6,<7 From 295f6efb4be6a0c472cb24ab80f8264298438b18 Mon Sep 17 00:00:00 2001 From: Ed Morley <501702+edmorley@users.noreply.github.com> Date: Sat, 7 Sep 2024 11:26:20 +0000 Subject: [PATCH 03/31] Update to Python 3.12.6 (#236) https://docs.python.org/release/3.12.6/whatsnew/changelog.html#python-3-12-6-final GUS-W-16690292. --- runtime.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runtime.txt b/runtime.txt index 8a7cba521..32bcba665 100644 --- a/runtime.txt +++ b/runtime.txt @@ -1 +1 @@ -python-3.12.5 +python-3.12.6 From 60d9aa2cf600edaa7d801e7f6784463ade9811d5 Mon Sep 17 00:00:00 2001 From: Ed Morley <501702+edmorley@users.noreply.github.com> Date: Tue, 1 Oct 2024 06:31:44 +0000 Subject: [PATCH 04/31] Update to Python 3.12.7 (#237) https://www.python.org/downloads/release/python-3127/ GUS-W-16867262. --- runtime.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runtime.txt b/runtime.txt index 32bcba665..32905d6e0 100644 --- a/runtime.txt +++ b/runtime.txt @@ -1 +1 @@ -python-3.12.6 +python-3.12.7 From 4d9fb79404f61312c39de4cc4815cf115fe017d8 Mon Sep 17 00:00:00 2001 From: Ed Morley <501702+edmorley@users.noreply.github.com> Date: Thu, 10 Oct 2024 20:59:22 +0000 Subject: [PATCH 05/31] Update to Python 3.13.0 (#238) Release announcement: https://blog.python.org/2024/10/python-3130-final-released.html https://www.python.org/downloads/release/python-3130/ What's new in Python 3.13: https://docs.python.org/3.13/whatsnew/3.13.html GUS-W-14846869. --- runtime.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runtime.txt b/runtime.txt index 32905d6e0..dae7fec92 100644 --- a/runtime.txt +++ b/runtime.txt @@ -1 +1 @@ -python-3.12.7 +python-3.13.0 From c9a165d7f7fa08dedcefd76ff83dcc782514198b Mon Sep 17 00:00:00 2001 From: Ed Morley <501702+edmorley@users.noreply.github.com> Date: Mon, 14 Oct 2024 18:24:58 +0000 Subject: [PATCH 06/31] Switch from `runtime.txt` to `.python-version` (#239) Since use of the `runtime.txt` file is now deprecated in favour of the `.python-version` file (since the latter is more widely supported in the Python ecosystem). This also switches to specifying only the major Python version (which automatically uses the latest patch release), in order to: 1. Save us from having to bump the version in this repo every time a new Python patch version is released. 2. Ensure that anyone who closes this repo and uses it as the basis for their app is then always using an up to date patch version if they don't know/remember to bump the version themselves. GUS-W-16971647. --- .python-version | 1 + runtime.txt | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 .python-version delete mode 100644 runtime.txt diff --git a/.python-version b/.python-version new file mode 100644 index 000000000..24ee5b1be --- /dev/null +++ b/.python-version @@ -0,0 +1 @@ +3.13 diff --git a/runtime.txt b/runtime.txt deleted file mode 100644 index dae7fec92..000000000 --- a/runtime.txt +++ /dev/null @@ -1 +0,0 @@ -python-3.13.0 From 0c83b7c202ab0108e50fe95428dd9780424c0653 Mon Sep 17 00:00:00 2001 From: Ed Morley <501702+edmorley@users.noreply.github.com> Date: Wed, 20 Nov 2024 09:52:03 +0000 Subject: [PATCH 07/31] Disable unused Django features (#240) The example app isn't currently using the admin, auth, sessions and messages features. The config has been commented out rather than removed, to make it easier for users to re-enable if needed. (Particularly since some of the config is order dependant, such as `MIDDLEWARE`.) --- gettingstarted/settings.py | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/gettingstarted/settings.py b/gettingstarted/settings.py index c722f202c..80043c60f 100644 --- a/gettingstarted/settings.py +++ b/gettingstarted/settings.py @@ -58,16 +58,22 @@ # Application definition +# Several optional Django features that are present in the default `startproject` template have +# been disabled since they are not used by this example app. To use them, uncomment the relevant +# entries in `INSTALLED_APPS`, `MIDDLEWARE`, `TEMPLATES` and `urls.py`. See: +# https://docs.djangoproject.com/en/5.1/ref/contrib/admin/ +# https://docs.djangoproject.com/en/5.1/topics/auth/ +# https://docs.djangoproject.com/en/5.1/ref/contrib/contenttypes/ +# https://docs.djangoproject.com/en/5.1/topics/http/sessions/ +# https://docs.djangoproject.com/en/5.1/ref/contrib/messages/ INSTALLED_APPS = [ # Use WhiteNoise's runserver implementation instead of the Django default, for dev-prod parity. "whitenoise.runserver_nostatic", - # Uncomment this and the entry in `urls.py` if you wish to use the Django admin feature: - # https://docs.djangoproject.com/en/5.1/ref/contrib/admin/ # "django.contrib.admin", - "django.contrib.auth", - "django.contrib.contenttypes", - "django.contrib.sessions", - "django.contrib.messages", + # "django.contrib.auth", + # "django.contrib.contenttypes", + # "django.contrib.sessions", + # "django.contrib.messages", "django.contrib.staticfiles", "hello", ] @@ -79,11 +85,11 @@ # after Django's `SecurityMiddleware` so that security redirects are still performed. # See: https://whitenoise.readthedocs.io "whitenoise.middleware.WhiteNoiseMiddleware", - "django.contrib.sessions.middleware.SessionMiddleware", + # "django.contrib.sessions.middleware.SessionMiddleware", "django.middleware.common.CommonMiddleware", "django.middleware.csrf.CsrfViewMiddleware", - "django.contrib.auth.middleware.AuthenticationMiddleware", - "django.contrib.messages.middleware.MessageMiddleware", + # "django.contrib.auth.middleware.AuthenticationMiddleware", + # "django.contrib.messages.middleware.MessageMiddleware", "django.middleware.clickjacking.XFrameOptionsMiddleware", ] @@ -98,8 +104,8 @@ "context_processors": [ "django.template.context_processors.debug", "django.template.context_processors.request", - "django.contrib.auth.context_processors.auth", - "django.contrib.messages.context_processors.messages", + # "django.contrib.auth.context_processors.auth", + # "django.contrib.messages.context_processors.messages", ], }, }, From 962e79875f43b5c32a9109b416a827023d6f87cd Mon Sep 17 00:00:00 2001 From: Ed Morley <501702+edmorley@users.noreply.github.com> Date: Wed, 20 Nov 2024 17:54:27 +0000 Subject: [PATCH 08/31] Configure gunicorn to bind to the IPv6 interface (#241) By default when the `PORT` env var is set, gunicorn binds to the IPv4 interface `0.0.0.0:$PORT`: https://docs.gunicorn.org/en/stable/settings.html#bind Now, we configure it to bind to the IPv6 interface `[::]:$PORT` instead, allowing the app to work in IPv6-only environments. We don't have it bind to both the IPv4 and IPv6 interfaces, since otherwise on Linux by default gunicorn would fail to boot with: ``` [ERROR] connection to ('::', 5006) failed: [Errno 98] Address already in use ``` ...and when binding to the IPv6 interface IPv4 connections will still work (so long as `IPV6_V6ONLY` hasn't been enabled). If this strategy causes issues, we could always start using the `reuse_port` option, however, that makes the DX worse for the local development use-case (it's then possible to leave a stale gunicorn process running, which will serve requests and give the impression code changes aren't taking effect). I've added a `gunicorn.conf.py` config file rather than passing CLI args to the `gunicorn` command, since it allows for more flexibility with the configuration (and there are other settings we'll be adding soon). See: https://docs.gunicorn.org/en/stable/configure.html GUS-W-17280180. --- Procfile | 2 +- gettingstarted/settings.py | 2 +- gunicorn.conf.py | 16 ++++++++++++++++ 3 files changed, 18 insertions(+), 2 deletions(-) create mode 100644 gunicorn.conf.py diff --git a/Procfile b/Procfile index 470556bd9..c4512ab32 100644 --- a/Procfile +++ b/Procfile @@ -1,4 +1,4 @@ -web: gunicorn gettingstarted.wsgi +web: gunicorn --config gunicorn.conf.py gettingstarted.wsgi # Uncomment this `release` process if you are using a database, so that Django's model # migrations are run as part of app deployment, using Heroku's Release Phase feature: diff --git a/gettingstarted/settings.py b/gettingstarted/settings.py index 80043c60f..087b3a776 100644 --- a/gettingstarted/settings.py +++ b/gettingstarted/settings.py @@ -53,7 +53,7 @@ if IS_HEROKU_APP: ALLOWED_HOSTS = ["*"] else: - ALLOWED_HOSTS = [".localhost", "127.0.0.1", "[::1]", "0.0.0.0"] + ALLOWED_HOSTS = [".localhost", "127.0.0.1", "[::1]", "0.0.0.0", "[::]"] # Application definition diff --git a/gunicorn.conf.py b/gunicorn.conf.py new file mode 100644 index 000000000..1db1755e0 --- /dev/null +++ b/gunicorn.conf.py @@ -0,0 +1,16 @@ +# Gunicorn configuration file: +# https://docs.gunicorn.org/en/stable/configure.html +# https://docs.gunicorn.org/en/stable/settings.html +# Note: The classic Python buildpack currently sets a few gunicorn settings automatically via +# the `GUNICORN_CMD_ARGS` env var (which take priority over the settings in this file): +# https://github.com/heroku/heroku-buildpack-python/blob/main/vendor/python.gunicorn.sh + +import os + +# The `PORT` env var is set automatically for web dynos and when using `heroku local`: +# https://devcenter.heroku.com/articles/dyno-startup-behavior#port-binding-of-web-dynos +# https://devcenter.heroku.com/articles/heroku-local +_port = os.environ.get("PORT", 5006) +# Bind to the IPv6 interface instead of the gunicorn default of IPv4, so the app works in IPv6-only +# environments. IPv4 connections will still work so long as `IPV6_V6ONLY` hasn't been enabled. +bind = [f"[::]:{_port}"] From a538e3c0c0ad3f0dab8784fc6abd37dbf0d18034 Mon Sep 17 00:00:00 2001 From: Ed Morley <501702+edmorley@users.noreply.github.com> Date: Thu, 21 Nov 2024 00:57:10 +0000 Subject: [PATCH 09/31] Switch to gunicorn's `gthread` worker type (#242) gunicorn's default worker type is the `sync` worker, which is more suited to CPU/network-bandwidth bound workloads. As such this switches to the thread-based `gthread` worker for improved request throughput performance for blocking I/O workloads - given that it's common for Heroku apps to make blocking requests to external APIs/datastores etc. The threads count of 5 is somewhat arbitrary, but seemed to work well enough with rough benchmarking against a few different dyno sizes (where the process count will vary according to `WEB_CONCURRENCY`) and simulated workload types. The `preload_app` option has also been enabled, which makes gunicorn load the app before the worker processes are forked, to reduce memory usage and boot times. See: https://docs.gunicorn.org/en/stable/design.html#server-model https://docs.gunicorn.org/en/stable/settings.html#worker-class GUS-W-17236632. --- gunicorn.conf.py | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/gunicorn.conf.py b/gunicorn.conf.py index 1db1755e0..22a2fab6c 100644 --- a/gunicorn.conf.py +++ b/gunicorn.conf.py @@ -14,3 +14,37 @@ # Bind to the IPv6 interface instead of the gunicorn default of IPv4, so the app works in IPv6-only # environments. IPv4 connections will still work so long as `IPV6_V6ONLY` hasn't been enabled. bind = [f"[::]:{_port}"] + +# The default `sync` worker is more suited to CPU/network-bandwidth bound workloads, so we +# instead use the thread based worker type for improved support of blocking I/O workloads: +# https://docs.gunicorn.org/en/stable/design.html#server-model +# +# If you need to further improve the performance of blocking I/O workloads, you may want to +# try the `gevent` worker type, though you will need to disable `preload_app`, enable DB +# connecting pooling, and be aware that gevent's monkey patching can break some packages. +# +# Note: When changing the number of dynos/workers/threads you will want to make sure you +# do not exceed the maximum number of connections to external services such as DBs: +# https://devcenter.heroku.com/articles/python-concurrency-and-database-connections +worker_class = "gthread" + +# gunicorn will start this many worker processes. The Python buildpack automatically sets a +# default for WEB_CONCURRENCY at dyno boot, based on the number of CPUs and available RAM: +# https://devcenter.heroku.com/articles/python-concurrency +workers = os.environ.get("WEB_CONCURRENCY", 1) + +# Each `gthread` worker process will use a pool of this many threads. +threads = 5 + +# Load the app before the worker processes are forked, to reduce memory usage and boot times. +preload_app = True + +# Workers silent for more than this many seconds are killed and restarted. +# Note: This only affects the maximum request time when using the `sync` worker. +# For all other worker types it acts only as a worker heartbeat timeout. +timeout = 20 + +# After receiving a restart signal, workers have this much time to finish serving requests. +# This should be set to a value less than the 30 second Heroku dyno shutdown timeout: +# https://devcenter.heroku.com/articles/dyno-shutdown-behavior +graceful_timeout = 20 From 4795062876a634b260fdc69aab907a66267b0112 Mon Sep 17 00:00:00 2001 From: Ed Morley <501702+edmorley@users.noreply.github.com> Date: Thu, 21 Nov 2024 16:08:23 +0000 Subject: [PATCH 10/31] Update app.json metadata (#243) * Remove the `image` field, since the `heroku.yml` languages feature was sunset some time ago, and the language docker images no longer exist. * Add the `website` field so the Heroku Button deploy form links to the Getting started guide. See: https://devcenter.heroku.com/articles/heroku-button https://devcenter.heroku.com/articles/app-json-schema --- app.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app.json b/app.json index 42c60c66b..c093460de 100644 --- a/app.json +++ b/app.json @@ -1,8 +1,8 @@ { "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", + "website": "https://devcenter.heroku.com/articles/getting-started-with-python", "keywords": ["python", "django"], "env": { "DJANGO_SECRET_KEY": { From 3d141db2535d117c0021e5fc04448d5de1523986 Mon Sep 17 00:00:00 2001 From: Ed Morley <501702+edmorley@users.noreply.github.com> Date: Wed, 27 Nov 2024 15:17:24 +0000 Subject: [PATCH 11/31] Clean up the gunicorn `bind` config (#244) So that it's on one line and easier to include via the rundoc template for the getting started guide. --- gunicorn.conf.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/gunicorn.conf.py b/gunicorn.conf.py index 22a2fab6c..9332468fd 100644 --- a/gunicorn.conf.py +++ b/gunicorn.conf.py @@ -7,13 +7,15 @@ import os -# The `PORT` env var is set automatically for web dynos and when using `heroku local`: +# On Heroku, web dynos must bind to the port number specified via the `PORT` env var. This +# env var is set automatically for web dynos and also when using `heroku local` locally: # https://devcenter.heroku.com/articles/dyno-startup-behavior#port-binding-of-web-dynos # https://devcenter.heroku.com/articles/heroku-local -_port = os.environ.get("PORT", 5006) -# Bind to the IPv6 interface instead of the gunicorn default of IPv4, so the app works in IPv6-only -# environments. IPv4 connections will still work so long as `IPV6_V6ONLY` hasn't been enabled. -bind = [f"[::]:{_port}"] +# Gunicorn will automatically use the `PORT` env var, however, by default it will bind to the +# port using the IPv4 interface (`0.0.0.0`). We configure the binding manually to make it bind +# to the IPv6 interface (`::`) instead, so that the app works in IPv6-only environments too. +# (IPv4 connections will still work so long as `IPV6_V6ONLY` hasn't been enabled.) +bind = ["[::]:{}".format(os.environ.get("PORT", 5006))] # The default `sync` worker is more suited to CPU/network-bandwidth bound workloads, so we # instead use the thread based worker type for improved support of blocking I/O workloads: From 65c908c6ac36428a0be3643a1edb3ce638df51ac Mon Sep 17 00:00:00 2001 From: Richard Schneeman Date: Sun, 8 Dec 2024 08:11:30 -0600 Subject: [PATCH 12/31] Update for Fir (#245) Update the README and app's templates for the release of Fir. Co-authored-by: Ed Morley <501702+edmorley@users.noreply.github.com> --- README.md | 36 +++++++++++++++++++++++++------ hello/templates/base.html | 36 +++++++++++++++++++++++-------- hello/templates/index.html | 44 ++++++++++++++++++++------------------ 3 files changed, 80 insertions(+), 36 deletions(-) diff --git a/README.md b/README.md index 0eab837d0..0806f3220 100644 --- a/README.md +++ b/README.md @@ -1,20 +1,44 @@ -# Python: Getting Started +# Python Getting Started A barebones Django app, which can easily be deployed to Heroku. +This application supports the tutorials for both the [Cedar and Fir generations](https://devcenter.heroku.com/articles/generations) of the Heroku platform. You can check them out here: + +- [Getting Started on Heroku with Python](https://devcenter.heroku.com/articles/getting-started-with-python) +- [Getting Started on Heroku Fir with Python](https://devcenter.heroku.com/articles/getting-started-with-python-fir) + ## Deploying to Heroku Using resources for this example app counts towards your usage. [Delete your app](https://devcenter.heroku.com/articles/heroku-cli-commands#heroku-apps-destroy) and [database](https://devcenter.heroku.com/articles/heroku-postgresql#removing-the-add-on) as soon as you are done experimenting to control costs. +### Deploy on Heroku [Cedar](https://devcenter.heroku.com/articles/generations#cedar) + By default, apps use Eco dynos if you are subscribed to Eco. Otherwise, it defaults to Basic dynos. The Eco dynos plan is shared across all Eco dynos in your account and is recommended if you plan on deploying many small apps to Heroku. Learn more about our low-cost plans [here](https://blog.heroku.com/new-low-cost-plans). Eligible students can apply for platform credits through our new [Heroku for GitHub Students program](https://blog.heroku.com/github-student-developer-program). -This application supports the [Getting Started with Python on Heroku](https://devcenter.heroku.com/articles/getting-started-with-python) article - check it out for instructions on how to deploy this app to Heroku and also run it locally. - -Alternatively, you can deploy it using this Heroku Button: - -[![Deploy](https://www.herokucdn.com/deploy/button.svg)](https://www.heroku.com/deploy?template=https://github.com/heroku/python-getting-started) +```term +$ git clone https://github.com/heroku/python-getting-started +$ cd python-getting-started +$ heroku create +$ git push heroku main +$ heroku open +``` + +### Deploy on Heroku [Fir](https://devcenter.heroku.com/articles/generations#fir) + +By default, apps on [Fir](https://devcenter.heroku.com/articles/generations#fir) use 1X-Classic dynos. To create an app on [Fir](https://devcenter.heroku.com/articles/generations#fir) you'll need to +[create a private space](https://devcenter.heroku.com/articles/working-with-private-spaces#create-a-private-space) +first. + +```term +$ git clone https://github.com/heroku/python-getting-started +$ cd python-getting-started +$ heroku create --space +$ git push heroku main +$ heroku ps:wait +$ heroku open +``` For more information about using Python on Heroku, see these Dev Center articles: diff --git a/hello/templates/base.html b/hello/templates/base.html index 73977e0b2..4a9450c99 100644 --- a/hello/templates/base.html +++ b/hello/templates/base.html @@ -55,20 +55,38 @@ How Heroku Works +