Skip to content

Commit

Permalink
Remove Python 3.8 support (#313)
Browse files Browse the repository at this point in the history
Python 3.8 reached upstream EOL on 7th October 2024:
https://devguide.python.org/versions/#supported-versions

The Python version support policy is that supported versions
follows the upstream EOL lifecycle:
https://devcenter.heroku.com/articles/python-support#python-version-support-policy

And Python 3.8 support has been deprecated since December 2023:
https://devcenter.heroku.com/changelog-items/2768

(Plus Python 3.8 binaries have never been available for Heroku-22
or Heroku-24, meaning it was only ever supported on Heroku-20
and older.)

Dropping support for Python 3.8 also unblocks upgrading to Poetry v2
(which has already dropped 3.8 support).

Apps using Python 3.8 that aren't able to upgrade immediately will
need to pin to an older buildpack version temporarily (which will
work until Heroku-20 EOLs).

GUS-W-17472312.
  • Loading branch information
edmorley authored Jan 8, 2025
1 parent 871a132 commit ed10970
Show file tree
Hide file tree
Showing 11 changed files with 111 additions and 103 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Removed

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

### Changed

- Buildpack detection now recognises more Python-related file and directory names. ([#312](https://github.com/heroku/buildpacks-python/pull/312))
- Improved the error messages shown for EOL or unrecognised major Python versions. ([#313](https://github.com/heroku/buildpacks-python/pull/313))

## [0.21.0] - 2024-12-18

Expand Down
39 changes: 23 additions & 16 deletions src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ use crate::layers::python::PythonLayerError;
use crate::package_manager::DeterminePackageManagerError;
use crate::python_version::{
RequestedPythonVersion, RequestedPythonVersionError, ResolvePythonVersionError,
DEFAULT_PYTHON_FULL_VERSION, DEFAULT_PYTHON_VERSION,
DEFAULT_PYTHON_FULL_VERSION, DEFAULT_PYTHON_VERSION, NEWEST_SUPPORTED_PYTHON_3_MINOR_VERSION,
OLDEST_SUPPORTED_PYTHON_3_MINOR_VERSION,
};
use crate::python_version_file::ParsePythonVersionFileError;
use crate::runtime_txt::ParseRuntimeTxtError;
Expand Down Expand Up @@ -231,16 +232,17 @@ fn on_resolve_python_version_error(error: ResolvePythonVersionError) {
..
} = requested_python_version;
log_error(
"Requested Python version has reached end-of-life",
"The requested Python version has reached end-of-life",
formatdoc! {"
The requested Python version {major}.{minor} has reached its upstream end-of-life,
and is therefore no longer receiving security updates:
Python {major}.{minor} 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 is no longer supported by this buildpack.
As such, it's no longer supported by this buildpack:
https://devcenter.heroku.com/articles/python-support#supported-python-versions
Please upgrade to a newer Python version by updating the version
configured via the {origin} file.
Please upgrade to at least Python 3.{OLDEST_SUPPORTED_PYTHON_3_MINOR_VERSION} by changing the
version in your {origin} file.
If possible, we recommend upgrading all the way to Python {DEFAULT_PYTHON_VERSION},
since it contains many performance and usability improvements.
Expand All @@ -255,17 +257,21 @@ fn on_resolve_python_version_error(error: ResolvePythonVersionError) {
..
} = requested_python_version;
log_error(
"Requested Python version is not recognised",
"The requested Python version isn't recognised",
formatdoc! {"
The requested Python version {major}.{minor} is not recognised.
The requested Python version {major}.{minor} isn't recognised.
Check that this Python version has been officially released:
Check that this Python version has been officially released,
and that the Python buildpack has added support for it:
https://devguide.python.org/versions/#supported-versions
https://devcenter.heroku.com/articles/python-support#supported-python-versions
If it has, make sure that you are using the latest version of this buildpack.
If it has, make sure that you are using the latest version
of this buildpack, and haven't pinned to an older release
via a custom buildpack configuration in project.toml.
If it has not, please switch to a supported version (such as Python {DEFAULT_PYTHON_VERSION})
by updating the version configured via the {origin} file.
Otherwise, switch to a supported version (such as Python 3.{NEWEST_SUPPORTED_PYTHON_3_MINOR_VERSION})
by changing the version in your {origin} file.
"},
);
}
Expand Down Expand Up @@ -294,11 +300,12 @@ fn on_python_layer_error(error: PythonLayerError) {
},
// This error will change once the Python version is validated against a manifest.
// TODO: (W-12613425) Write the supported Python versions inline, instead of linking out to Dev Center.
// TODO: Decide how to explain to users how stacks, base images and builder images versions relate to each other.
// TODO: Update this error message to suggest switching to the major version syntax in .python-version,
// which will prevent the error from ever occurring (now that all stacks support the same versions).
PythonLayerError::PythonArchiveNotFound { python_version } => log_error(
"Requested Python version is not available",
"The requested Python version wasn't found",
formatdoc! {"
The requested Python version ({python_version}) is not available for this builder image.
The requested Python version ({python_version}) wasn't found.
Please switch to a supported Python version, or else don't specify a version
and the buildpack will use a default version (currently Python {DEFAULT_PYTHON_VERSION}).
Expand Down
30 changes: 15 additions & 15 deletions src/python_version.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,11 @@ pub(crate) const DEFAULT_PYTHON_VERSION: RequestedPythonVersion = RequestedPytho
};
pub(crate) const DEFAULT_PYTHON_FULL_VERSION: PythonVersion = LATEST_PYTHON_3_13;

pub(crate) const LATEST_PYTHON_3_8: PythonVersion = PythonVersion::new(3, 8, 20);
pub(crate) const OLDEST_SUPPORTED_PYTHON_3_MINOR_VERSION: u16 = 9;
pub(crate) const NEWEST_SUPPORTED_PYTHON_3_MINOR_VERSION: u16 = 13;
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, 21);
pub(crate) const LATEST_PYTHON_3_10: PythonVersion = PythonVersion::new(3, 10, 16);
pub(crate) const LATEST_PYTHON_3_11: PythonVersion = PythonVersion::new(3, 11, 11);
Expand Down Expand Up @@ -156,18 +160,17 @@ pub(crate) fn resolve_python_version(
} = requested_python_version;

match (major, minor, patch) {
(..3, _, _) | (3, ..8, _) => Err(ResolvePythonVersionError::EolVersion(
requested_python_version.clone(),
)),
(3, 8, None) => Ok(LATEST_PYTHON_3_8),
(..3, _, _) | (3, ..OLDEST_SUPPORTED_PYTHON_3_MINOR_VERSION, _) => Err(
ResolvePythonVersionError::EolVersion(requested_python_version.clone()),
),
(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),
(3, 13, None) => Ok(LATEST_PYTHON_3_13),
(3, 14.., _) | (4.., _, _) => Err(ResolvePythonVersionError::UnknownVersion(
requested_python_version.clone(),
)),
(major, minor, Some(patch)) => Ok(PythonVersion::new(major, minor, patch)),
}
}
Expand All @@ -183,9 +186,6 @@ pub(crate) enum ResolvePythonVersionError {
mod tests {
use super::*;

const OLDEST_SUPPORTED_PYTHON_3_MINOR_VERSION: u16 = 8;
const NEWEST_SUPPORTED_PYTHON_3_MINOR_VERSION: u16 = 13;

#[test]
fn python_version_url() {
assert_eq!(
Expand Down Expand Up @@ -239,10 +239,10 @@ mod tests {
#[test]
fn read_requested_python_version_python_version_file() {
assert_eq!(
read_requested_python_version(Path::new("tests/fixtures/python_3.7")).unwrap(),
read_requested_python_version(Path::new("tests/fixtures/python_3.9")).unwrap(),
RequestedPythonVersion {
major: 3,
minor: 7,
minor: 9,
patch: None,
origin: PythonVersionOrigin::PythonVersionFile,
}
Expand Down Expand Up @@ -357,7 +357,7 @@ mod tests {
fn resolve_python_version_unsupported() {
let requested_python_version = RequestedPythonVersion {
major: 3,
minor: NEWEST_SUPPORTED_PYTHON_3_MINOR_VERSION + 1,
minor: NEXT_UNRELEASED_PYTHON_3_MINOR_VERSION,
patch: None,
origin: PythonVersionOrigin::PythonVersionFile,
};
Expand All @@ -370,7 +370,7 @@ mod tests {

let requested_python_version = RequestedPythonVersion {
major: 3,
minor: NEWEST_SUPPORTED_PYTHON_3_MINOR_VERSION + 1,
minor: NEXT_UNRELEASED_PYTHON_3_MINOR_VERSION,
patch: Some(0),
origin: PythonVersionOrigin::PythonVersionFile,
};
Expand Down
1 change: 0 additions & 1 deletion tests/fixtures/python_3.7/.python-version

This file was deleted.

File renamed without changes.
File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
3.12.999
138 changes: 67 additions & 71 deletions tests/python_version_test.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
use crate::python_version::{
PythonVersion, 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_8,
LATEST_PYTHON_3_9,
LATEST_PYTHON_3_11, LATEST_PYTHON_3_12, LATEST_PYTHON_3_13, LATEST_PYTHON_3_9,
NEWEST_SUPPORTED_PYTHON_3_MINOR_VERSION,
};
use crate::tests::{builder, default_build_config};
use crate::tests::default_build_config;
use indoc::{formatdoc, indoc};
use libcnb_test::{assert_contains, assert_empty, PackResult, TestRunner};

Expand All @@ -29,44 +29,6 @@ fn python_version_unspecified() {
});
}

#[test]
#[ignore = "integration test"]
fn python_3_7() {
let mut config = default_build_config("tests/fixtures/python_3.7");
config.expected_pack_result(PackResult::Failure);

TestRunner::default().build(config, |context| {
assert_contains!(
context.pack_stderr,
&formatdoc! {"
[Error: Requested Python version has reached end-of-life]
The requested Python version 3.7 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 is no longer supported by this buildpack.
Please upgrade to a newer Python version by updating the version
configured via the .python-version file.
If possible, we recommend upgrading all the way to Python {DEFAULT_PYTHON_VERSION},
since it contains many performance and usability improvements.
"}
);
});
}

#[test]
#[ignore = "integration test"]
fn python_3_8() {
// Python 3.8 is only available on Heroku-20 and older.
let fixture = "tests/fixtures/python_3.8";
match builder().as_str() {
"heroku/builder:20" => builds_with_python_version(fixture, &LATEST_PYTHON_3_8),
_ => rejects_non_existent_python_version(fixture, &LATEST_PYTHON_3_8),
};
}

#[test]
#[ignore = "integration test"]
fn python_3_9() {
Expand Down Expand Up @@ -154,27 +116,6 @@ fn builds_with_python_version(fixture_path: &str, python_version: &PythonVersion
});
}

fn rejects_non_existent_python_version(fixture_path: &str, python_version: &PythonVersion) {
let mut config = default_build_config(fixture_path);
config.expected_pack_result(PackResult::Failure);

TestRunner::default().build(config, |context| {
assert_contains!(
context.pack_stderr,
&formatdoc! {"
[Error: Requested Python version is not available]
The requested Python version ({python_version}) is not available for this builder image.
Please switch to a supported Python version, or else don't specify a version
and the buildpack will use a default version (currently Python {DEFAULT_PYTHON_VERSION}).
For a list of the supported Python versions, see:
https://devcenter.heroku.com/articles/python-support#supported-runtimes
"}
);
});
}

#[test]
#[ignore = "integration test"]
fn python_version_file_io_error() {
Expand Down Expand Up @@ -275,24 +216,79 @@ fn python_version_file_no_version() {

#[test]
#[ignore = "integration test"]
fn python_version_file_unknown_version() {
let mut config = default_build_config("tests/fixtures/python_version_file_unknown_version");
fn python_version_eol() {
let mut config = default_build_config("tests/fixtures/python_version_eol");
config.expected_pack_result(PackResult::Failure);

TestRunner::default().build(config, |context| {
assert_contains!(
context.pack_stderr,
&formatdoc! {"
[Error: Requested Python version is not recognised]
The requested Python version 3.99 is not recognised.
Check that this Python version has been officially released:
[Error: The requested Python version has reached end-of-life]
Python 3.8 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
version in your .python-version file.
If possible, we recommend upgrading all the way to Python {DEFAULT_PYTHON_VERSION},
since it contains many performance and usability improvements.
"}
);
});
}

#[test]
#[ignore = "integration test"]
fn python_version_non_existent_major() {
let mut config = default_build_config("tests/fixtures/python_version_non_existent_major");
config.expected_pack_result(PackResult::Failure);

TestRunner::default().build(config, |context| {
assert_contains!(
context.pack_stderr,
&formatdoc! {"
[Error: The requested Python version isn't recognised]
The requested Python version 3.99 isn't recognised.
Check that this Python version has been officially released,
and that the Python buildpack has added support for it:
https://devguide.python.org/versions/#supported-versions
https://devcenter.heroku.com/articles/python-support#supported-python-versions
If it has, make sure that you are using the latest version
of this buildpack, and haven't pinned to an older release
via a custom buildpack configuration in project.toml.
Otherwise, switch to a supported version (such as Python 3.{NEWEST_SUPPORTED_PYTHON_3_MINOR_VERSION})
by changing the version in your .python-version file.
"}
);
});
}

#[test]
#[ignore = "integration test"]
fn python_version_non_existent_minor() {
let mut config = default_build_config("tests/fixtures/python_version_non_existent_minor");
config.expected_pack_result(PackResult::Failure);

TestRunner::default().build(config, |context| {
assert_contains!(
context.pack_stderr,
&formatdoc! {"
[Error: The requested Python version wasn't found]
The requested Python version (3.12.999) wasn't found.
If it has, make sure that you are using the latest version of this buildpack.
Please switch to a supported Python version, or else don't specify a version
and the buildpack will use a default version (currently Python {DEFAULT_PYTHON_VERSION}).
If it has not, please switch to a supported version (such as Python {DEFAULT_PYTHON_VERSION})
by updating the version configured via the .python-version file.
For a list of the supported Python versions, see:
https://devcenter.heroku.com/articles/python-support#supported-runtimes
"}
);
});
Expand Down

0 comments on commit ed10970

Please sign in to comment.