Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Removed

- Removed support for Python 3.9. ([#488](https://github.com/heroku/buildpacks-python/pull/488))

## [3.0.2] - 2026-01-05

### Changed
Expand Down
24 changes: 0 additions & 24 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -94,30 +94,6 @@ impl Buildpack for PythonBuildpack {
}

if let RequestedPythonVersion {
major: 3,
minor: 9,
origin,
..
} = &requested_python_version
{
log_warning(
"Support for Python 3.9 is ending soon",
formatdoc! {"
Python 3.9 reached its upstream end-of-life on 31st October 2025,
and so no longer receives security updates:
https://devguide.python.org/versions/#supported-versions

As such, support for Python 3.9 will be removed from this
buildpack on 7th January 2026.

Upgrade to a newer Python version as soon as possible, by
changing the version in your {origin} file.

For more information, see:
https://devcenter.heroku.com/articles/python-support#supported-python-versions
"},
);
} else if let RequestedPythonVersion {
major: 3,
minor: 10,
origin,
Expand Down
8 changes: 3 additions & 5 deletions src/python_version.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,11 @@ pub(crate) const DEFAULT_PYTHON_VERSION: RequestedPythonVersion = RequestedPytho
#[cfg(test)]
pub(crate) const DEFAULT_PYTHON_FULL_VERSION: PythonVersion = LATEST_PYTHON_3_14;

pub(crate) const OLDEST_SUPPORTED_PYTHON_3_MINOR_VERSION: u16 = 9;
pub(crate) const OLDEST_SUPPORTED_PYTHON_3_MINOR_VERSION: u16 = 10;
pub(crate) const NEWEST_SUPPORTED_PYTHON_3_MINOR_VERSION: u16 = 14;
pub(crate) const NEXT_UNRELEASED_PYTHON_3_MINOR_VERSION: u16 =
NEWEST_SUPPORTED_PYTHON_3_MINOR_VERSION + 1;

pub(crate) const LATEST_PYTHON_3_9: PythonVersion = PythonVersion::new(3, 9, 25);
pub(crate) const LATEST_PYTHON_3_10: PythonVersion = PythonVersion::new(3, 10, 19);
pub(crate) const LATEST_PYTHON_3_11: PythonVersion = PythonVersion::new(3, 11, 14);
pub(crate) const LATEST_PYTHON_3_12: PythonVersion = PythonVersion::new(3, 12, 12);
Expand Down Expand Up @@ -173,7 +172,6 @@ pub(crate) fn resolve_python_version(
(3, NEXT_UNRELEASED_PYTHON_3_MINOR_VERSION.., _) | (4.., _, _) => Err(
ResolvePythonVersionError::UnknownVersion(requested_python_version.clone()),
),
(3, 9, None) => Ok(LATEST_PYTHON_3_9),
(3, 10, None) => Ok(LATEST_PYTHON_3_10),
(3, 11, None) => Ok(LATEST_PYTHON_3_11),
(3, 12, None) => Ok(LATEST_PYTHON_3_12),
Expand Down Expand Up @@ -284,13 +282,13 @@ mod tests {
fn read_requested_python_version_python_version_file() {
assert_eq!(
read_requested_python_version(
Path::new("tests/fixtures/python_3.9"),
Path::new("tests/fixtures/python_3.13"),
PackageManager::Pip
)
.unwrap(),
RequestedPythonVersion {
major: 3,
minor: 9,
minor: 13,
patch: None,
origin: PythonVersionOrigin::PythonVersionFile,
}
Expand Down
14 changes: 6 additions & 8 deletions tests/django_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,7 @@ fn django_staticfiles_latest_django() {
);
}

// This tests the oldest Django version that works on Python 3.9 (which is the
// oldest Python that is available on all of our supported builders).
// This tests the oldest Django version that works on Python 3.10 (our oldest supported Python version).
#[test]
#[ignore = "integration test"]
fn django_staticfiles_legacy_django() {
Expand All @@ -63,7 +62,7 @@ fn django_staticfiles_legacy_django() {
indoc! {"
[Generating Django static files]
Running 'manage.py collectstatic'
{'CPATH': '/layers/heroku_python/venv/include:/layers/heroku_python/python/include/python3.9:/layers/heroku_python/python/include:/invalid',
{'CPATH': '/layers/heroku_python/venv/include:/layers/heroku_python/python/include/python3.10:/layers/heroku_python/python/include:/invalid',
'DJANGO_SETTINGS_MODULE': 'testproject.settings',
'LC_CTYPE': 'C.UTF-8',
'LD_LIBRARY_PATH': '/layers/heroku_python/venv/lib:/layers/heroku_python/pip/lib:/layers/heroku_python/python/lib:/invalid',
Expand All @@ -81,11 +80,10 @@ fn django_staticfiles_legacy_django() {

['/workspace',
'/invalid',
'/layers/heroku_python/python/lib/python39.zip',
'/layers/heroku_python/python/lib/python3.9',
'/layers/heroku_python/python/lib/python3.9/lib-dynload',
'/layers/heroku_python/venv/lib/python3.9/site-packages']
Copying '/workspace/testapp/static/robots.txt'
'/layers/heroku_python/python/lib/python310.zip',
'/layers/heroku_python/python/lib/python3.10',
'/layers/heroku_python/python/lib/python3.10/lib-dynload',
'/layers/heroku_python/venv/lib/python3.10/site-packages']

1 static file copied to '/workspace/staticfiles'.
"}
Expand Down
Original file line number Diff line number Diff line change
@@ -1 +1 @@
3.9
3.10
Original file line number Diff line number Diff line change
@@ -1,3 +1,2 @@
# This is the oldest Django version that works on Python 3.9 (which is the
# oldest Python that is available on all of our supported builders).
Django==1.8.19
# This is the oldest Django version that works on Python 3.10 (our oldest supported Python version).
Django==2.1.15
2 changes: 1 addition & 1 deletion tests/fixtures/pip_oldest_python/.python-version
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
# This uses the .0 patch version of the oldest major Python version we support,
# so we can check that the pip bootstrapping works with the oldest bundled pip.
3.9.0
3.10.0
2 changes: 1 addition & 1 deletion tests/fixtures/poetry_oldest_python/.python-version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
3.9.0
3.10.0
4 changes: 2 additions & 2 deletions tests/fixtures/poetry_oldest_python/poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion tests/fixtures/poetry_oldest_python/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@
package-mode = false

[tool.poetry.dependencies]
python = "^3.9"
python = "^3.10"
typing-extensions = "*"
1 change: 0 additions & 1 deletion tests/fixtures/python_3.9/.python-version

This file was deleted.

Empty file.
2 changes: 1 addition & 1 deletion tests/fixtures/python_version_eol/.python-version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
3.8
3.9
Original file line number Diff line number Diff line change
@@ -1 +0,0 @@
python-3.9.0
2 changes: 1 addition & 1 deletion tests/fixtures/uv_oldest_python/.python-version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
3.9.0
3.10.0
2 changes: 1 addition & 1 deletion tests/fixtures/uv_oldest_python/pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[project]
name = "uv-oldest-python"
version = "0.0.0"
requires-python = "==3.9.*"
requires-python = "==3.10.*"
dependencies = [
"typing-extensions",
]
2 changes: 1 addition & 1 deletion tests/fixtures/uv_oldest_python/uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 7 additions & 7 deletions tests/pip_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -237,15 +237,15 @@ fn pip_oldest_python() {
context.pack_stdout,
indoc! {"
[Determining Python version]
Using Python version 3.9.0 specified in .python-version
Using Python version 3.10.0 specified in .python-version

[Warning: Support for Python 3.9 is ending soon]
Python 3.9 reached its upstream end-of-life on 31st October 2025,
and so no longer receives security updates:
[Warning: Support for Python 3.10 is deprecated]
Python 3.10 will reach its upstream end-of-life in October 2026,
at which point it will no longer receive security updates:
https://devguide.python.org/versions/#supported-versions

As such, support for Python 3.9 will be removed from this
buildpack on 7th January 2026.
As such, support for Python 3.10 will be removed from this
buildpack on 6th January 2027.

Upgrade to a newer Python version as soon as possible, by
changing the version in your .python-version file.
Expand All @@ -255,7 +255,7 @@ fn pip_oldest_python() {


[Installing Python]
Installing Python 3.9.0
Installing Python 3.10.0
"}
);
});
Expand Down
14 changes: 7 additions & 7 deletions tests/poetry_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -241,15 +241,15 @@ fn poetry_oldest_python() {
context.pack_stdout,
&formatdoc! {"
[Determining Python version]
Using Python version 3.9.0 specified in .python-version
Using Python version 3.10.0 specified in .python-version

[Warning: Support for Python 3.9 is ending soon]
Python 3.9 reached its upstream end-of-life on 31st October 2025,
and so no longer receives security updates:
[Warning: Support for Python 3.10 is deprecated]
Python 3.10 will reach its upstream end-of-life in October 2026,
at which point it will no longer receive security updates:
https://devguide.python.org/versions/#supported-versions

As such, support for Python 3.9 will be removed from this
buildpack on 7th January 2026.
As such, support for Python 3.10 will be removed from this
buildpack on 6th January 2027.

Upgrade to a newer Python version as soon as possible, by
changing the version in your .python-version file.
Expand All @@ -259,7 +259,7 @@ fn poetry_oldest_python() {


[Installing Python]
Installing Python 3.9.0
Installing Python 3.10.0

[Installing Poetry]
Installing Poetry {POETRY_VERSION}
Expand Down
47 changes: 7 additions & 40 deletions tests/python_version_test.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::python_version::{
DEFAULT_PYTHON_FULL_VERSION, DEFAULT_PYTHON_VERSION, LATEST_PYTHON_3_9, LATEST_PYTHON_3_10,
LATEST_PYTHON_3_11, LATEST_PYTHON_3_12, LATEST_PYTHON_3_13, LATEST_PYTHON_3_14,
DEFAULT_PYTHON_FULL_VERSION, DEFAULT_PYTHON_VERSION, LATEST_PYTHON_3_10, LATEST_PYTHON_3_11,
LATEST_PYTHON_3_12, LATEST_PYTHON_3_13, LATEST_PYTHON_3_14,
NEWEST_SUPPORTED_PYTHON_3_MINOR_VERSION, PythonVersion,
};
use crate::tests::default_build_config;
Expand Down Expand Up @@ -29,12 +29,6 @@ fn python_version_unspecified() {
});
}

#[test]
#[ignore = "integration test"]
fn python_3_9() {
builds_with_python_version("tests/fixtures/python_3.9", &LATEST_PYTHON_3_9);
}

#[test]
#[ignore = "integration test"]
fn python_3_10() {
Expand Down Expand Up @@ -75,33 +69,7 @@ fn builds_with_python_version(fixture_path: &str, python_version: &PythonVersion
TestRunner::default().build(default_build_config(fixture_path), |context| {
assert_empty!(context.pack_stderr);

if major == 3 && minor == 9 {
assert_contains!(
context.pack_stdout,
&formatdoc! {"
[Determining Python version]
Using Python version {major}.{minor} specified in .python-version

[Warning: Support for Python 3.9 is ending soon]
Python 3.9 reached its upstream end-of-life on 31st October 2025,
and so no longer receives security updates:
https://devguide.python.org/versions/#supported-versions

As such, support for Python 3.9 will be removed from this
buildpack on 7th January 2026.

Upgrade to a newer Python version as soon as possible, by
changing the version in your .python-version file.

For more information, see:
https://devcenter.heroku.com/articles/python-support#supported-python-versions


[Installing Python]
Installing Python {major}.{minor}.{patch}
"}
);
} else if major == 3 && minor == 10 {
if major == 3 && minor == 10 {
assert_contains!(
context.pack_stdout,
&formatdoc! {"
Expand Down Expand Up @@ -154,9 +122,8 @@ fn builds_with_python_version(fixture_path: &str, python_version: &PythonVersion

# Check that the Python binary is using its own 'libpython' and not the system one:
# https://github.com/docker-library/python/issues/784
# Note: This has to handle Python 3.9 and older not being built in shared library mode.
libpython_path=$(ldd /layers/heroku_python/python/bin/python | grep libpython || true)
if [[ -n "${libpython_path}" && "${libpython_path}" != *"=> /layers/"* ]]; then
libpython_path=$(ldd /layers/heroku_python/python/bin/python | grep libpython)
if [[ "${libpython_path}" != *"=> /layers/"* ]]; then
echo "The Python binary is not using the correct libpython!"
echo "${libpython_path}"
exit 1
Expand Down Expand Up @@ -326,14 +293,14 @@ fn python_version_eol() {
[Determining Python version]

[Error: The requested Python version has reached end-of-life]
Python 3.8 has reached its upstream end-of-life, and is
Python 3.9 has reached its upstream end-of-life, and is
therefore no longer receiving security updates:
https://devguide.python.org/versions/#supported-versions

As such, it's no longer supported by this buildpack:
https://devcenter.heroku.com/articles/python-support#supported-python-versions

Please upgrade to at least Python 3.9 by changing the
Please upgrade to at least Python 3.10 by changing the
version in your .python-version file.

If possible, we recommend upgrading all the way to Python {DEFAULT_PYTHON_VERSION},
Expand Down
14 changes: 7 additions & 7 deletions tests/uv_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -242,15 +242,15 @@ fn uv_oldest_python() {
context.pack_stdout,
&formatdoc! {"
\\[Determining Python version\\]
Using Python version 3.9.0 specified in .python-version
Using Python version 3.10.0 specified in .python-version

\\[Warning: Support for Python 3.9 is ending soon\\]
Python 3.9 reached its upstream end-of-life on 31st October 2025,
and so no longer receives security updates:
\\[Warning: Support for Python 3.10 is deprecated\\]
Python 3.10 will reach its upstream end-of-life in October 2026,
at which point it will no longer receive security updates:
https://devguide.python.org/versions/#supported-versions

As such, support for Python 3.9 will be removed from this
buildpack on 7th January 2026.
As such, support for Python 3.10 will be removed from this
buildpack on 6th January 2027.

Upgrade to a newer Python version as soon as possible, by
changing the version in your .python-version file.
Expand All @@ -260,7 +260,7 @@ fn uv_oldest_python() {


\\[Installing Python\\]
Installing Python 3.9.0
Installing Python 3.10.0

\\[Installing uv\\]
Installing uv {UV_VERSION}
Expand Down