Skip to content

Commit

Permalink
Deprecate support for the runtime.txt file (#325)
Browse files Browse the repository at this point in the history
The `runtime.txt` file is a classic Heroku Python buildpack invention
that's not widely supported in the Python ecosystem. Instead, most
other tooling (pyenv, package managers, GitHub Actions, dependency
update bots etc) support/use the `.python-version` file.

As such, we recently added `.python-version` support to both the Python
CNB and the classic Python buildpack, and updated all documentation and
guides to use it instead of the `runtime.txt` file. eg:
https://devcenter.heroku.com/articles/python-runtimes

We would prefer apps use the new file, since it helps ensure their
deployed app is using the same Python version used locally (via eg
pyenv or uv) or in CI.

As such this adds a deprecation warning for apps using `runtime.txt`,
which will be made an error in the CNB in near future (likely before
Fir GA). We'll also be adding a deprecation warning to the classic
Python buildpack, however, won't be making that an error any time
soon (if at all).

Towards #275.
GUS-W-16878239.
  • Loading branch information
edmorley authored Feb 4, 2025
1 parent 79bb4d1 commit 4435b42
Show file tree
Hide file tree
Showing 5 changed files with 149 additions and 58 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Added `runtime.txt` support for the `python-3.X` major Python version form. ([#322](https://github.com/heroku/buildpacks-python/pull/322))
- Enabled `libcnb`'s `trace` feature. ([#320](https://github.com/heroku/buildpacks-python/pull/320))

### Changed

- Deprecated support for the `runtime.txt` file. ([#325](https://github.com/heroku/buildpacks-python/pull/325))
- Improved the error messages shown when the `.python-version` file contents aren't valid. ([#325](https://github.com/heroku/buildpacks-python/pull/325))

## [0.23.0] - 2025-01-13

### Changed
Expand Down
66 changes: 41 additions & 25 deletions src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -156,71 +156,87 @@ fn on_requested_python_version_error(error: RequestedPythonVersionError) {
ParsePythonVersionFileError::InvalidVersion(version) => log_error(
"Invalid Python version in .python-version",
formatdoc! {"
The Python version specified in '.python-version' is not in the correct format.
The Python version specified in your .python-version file
isn't in the correct format.
The following version was found:
{version}
However, the version must be specified as either:
1. '<major>.<minor>' (recommended, for automatic security updates)
2. '<major>.<minor>.<patch>' (to pin to an exact Python version)
However, the Python version must be specified as either:
1. The major version only: 3.X (recommended)
2. An exact patch version: 3.X.Y
Do not include quotes or a 'python-' prefix. To include comments, add them
on their own line, prefixed with '#'.
Don't include quotes or a 'python-' prefix. To include
comments, add them on their own line, prefixed with '#'.
For example, to request the latest version of Python {DEFAULT_PYTHON_VERSION},
update the '.python-version' file so it contains:
update your .python-version file so it contains:
{DEFAULT_PYTHON_VERSION}
We strongly recommend that you use the major version form
instead of pinning to an exact version, since it will allow
your app to receive Python security updates.
"},
),
ParsePythonVersionFileError::MultipleVersions(versions) => {
let version_list = versions.join("\n");
log_error(
"Invalid Python version in .python-version",
formatdoc! {"
Multiple Python versions were found in '.python-version':
Multiple versions were found in your .python-version file:
{version_list}
Update the file so it contains only one Python version.
If the additional versions are actually comments, prefix those lines with '#'.
If you have added comments to the file, make sure that those
lines begin with a '#', so that they are ignored.
"},
);
}
ParsePythonVersionFileError::NoVersion => log_error(
"Invalid Python version in .python-version",
formatdoc! {"
No Python version was found in the '.python-version' file.
No Python version was found in your .python-version file.
Update the file so that it contain a valid Python version (such as '{DEFAULT_PYTHON_VERSION}'),
or else delete the file to use the default version (currently Python {DEFAULT_PYTHON_VERSION}).
Update the file so that it contains a valid Python version.
For example, to request the latest version of Python {DEFAULT_PYTHON_VERSION},
update your .python-version file so it contains:
{DEFAULT_PYTHON_VERSION}
If the file already contains a version, check the line is not prefixed by
a '#', since otherwise it will be treated as a comment.
If the file already contains a version, check the line doesn't
begin with a '#', otherwise it will be treated as a comment.
"},
),
},
RequestedPythonVersionError::ParseRuntimeTxt(ParseRuntimeTxtError { cleaned_contents }) => {
log_error(
"Invalid Python version in runtime.txt",
formatdoc! {"
The Python version specified in 'runtime.txt' isn't in
the correct format.
The Python version specified in your runtime.txt file isn't
in the correct format.
The following file contents were found:
The following file contents were found, which aren't valid:
{cleaned_contents}
However, the version must be specified as either:
1. 'python-<major>.<minor>' (recommended, for automatic updates)
2. 'python-<major>.<minor>.<patch>' (to pin to an exact version)
However, the runtime.txt file is deprecated since it has
been replaced by the .python-version file. As such, we
recommend that you switch to using a .python-version file
instead of fixing your runtime.txt file.
Remember to include the 'python-' prefix. Comments aren't
supported.
Please delete your runtime.txt file and create a new file named:
.python-version
For example, to request the latest version of Python {DEFAULT_PYTHON_VERSION},
update the 'runtime.txt' file so it contains:
python-{DEFAULT_PYTHON_VERSION}
Make sure to include the '.' at the start of the filename.
In the new file, specify your app's Python version without
quotes or a 'python-' prefix. For example:
{DEFAULT_PYTHON_VERSION}
We strongly recommend that you use the major version form
instead of pinning to an exact version, since it will allow
your app to receive Python security updates.
"},
);
}
Expand Down
36 changes: 31 additions & 5 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,11 +85,37 @@ impl Buildpack for PythonBuildpack {
PythonVersionOrigin::PythonVersionFile => log_info(format!(
"Using Python version {requested_python_version} specified in .python-version"
)),
// TODO: Add a deprecation message for runtime.txt once .python-version support has been
// released for both the CNB and the classic buildpack.
PythonVersionOrigin::RuntimeTxt => log_info(format!(
"Using Python version {requested_python_version} specified in runtime.txt"
)),
PythonVersionOrigin::RuntimeTxt => {
log_info(format!(
"Using Python version {requested_python_version} specified in runtime.txt"
));
log_warning(
"The runtime.txt file is deprecated",
formatdoc! {"
The runtime.txt file is deprecated since it has been replaced
by the more widely supported .python-version file.
Please delete your runtime.txt file and create a new file named:
.python-version
Make sure to include the '.' at the start of the filename.
In the new file, specify your app's Python version without
quotes or a 'python-' prefix. For example:
{major}.{minor}
We strongly recommend that you use the major version form
instead of pinning to an exact version, since it will allow
your app to receive Python security updates.
In the near future support for runtime.txt will be removed
and this warning will be made an error.
",
major = requested_python_version.major,
minor = requested_python_version.minor,
},
);
}
}

if let RequestedPythonVersion {
Expand Down
Original file line number Diff line number Diff line change
@@ -1 +1,10 @@
an.invalid.version
# Comments are supported.
# Even when indented
#
# So are empty lines, and leading/trailing whitespace.


3.12.0invalid


# 2.7.18
89 changes: 62 additions & 27 deletions tests/python_version_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -171,21 +171,26 @@ fn python_version_file_invalid_version() {
context.pack_stderr,
&formatdoc! {"
[Error: Invalid Python version in .python-version]
The Python version specified in '.python-version' is not in the correct format.
The Python version specified in your .python-version file
isn't in the correct format.
The following version was found:
an.invalid.version
3.12.0invalid
However, the version must be specified as either:
1. '<major>.<minor>' (recommended, for automatic security updates)
2. '<major>.<minor>.<patch>' (to pin to an exact Python version)
However, the Python version must be specified as either:
1. The major version only: 3.X (recommended)
2. An exact patch version: 3.X.Y
Do not include quotes or a 'python-' prefix. To include comments, add them
on their own line, prefixed with '#'.
Don't include quotes or a 'python-' prefix. To include
comments, add them on their own line, prefixed with '#'.
For example, to request the latest version of Python {DEFAULT_PYTHON_VERSION},
update the '.python-version' file so it contains:
update your .python-version file so it contains:
{DEFAULT_PYTHON_VERSION}
We strongly recommend that you use the major version form
instead of pinning to an exact version, since it will allow
your app to receive Python security updates.
"}
);
});
Expand All @@ -202,15 +207,16 @@ fn python_version_file_multiple_versions() {
context.pack_stderr,
indoc! {"
[Error: Invalid Python version in .python-version]
Multiple Python versions were found in '.python-version':
Multiple versions were found in your .python-version file:
// invalid comment
3.12
2.7
Update the file so it contains only one Python version.
If the additional versions are actually comments, prefix those lines with '#'.
If you have added comments to the file, make sure that those
lines begin with a '#', so that they are ignored.
"}
);
});
Expand All @@ -227,13 +233,13 @@ fn python_version_file_no_version() {
context.pack_stderr,
&formatdoc! {"
[Error: Invalid Python version in .python-version]
No Python version was found in the '.python-version' file.
No Python version was found in your .python-version file.
Update the file so that it contain a valid Python version (such as '{DEFAULT_PYTHON_VERSION}'),
or else delete the file to use the default version (currently Python {DEFAULT_PYTHON_VERSION}).
Update the file so that it contains a valid Python version.
If the file already contains a version, check the line is not prefixed by
a '#', since otherwise it will be treated as a comment.
For example, to request the latest version of Python {DEFAULT_PYTHON_VERSION},
update your .python-version file so it contains:
{DEFAULT_PYTHON_VERSION}
"}
);
});
Expand Down Expand Up @@ -320,7 +326,8 @@ fn python_version_non_existent_minor() {
}

// This tests that:
// - The Python version can be specified using runtime.txt
// - The Python version can be specified using runtime.txt.
// - A runtime.txt deprecation warning is shown.
// - pip works with the oldest currently supported Python version (3.9.0).
// - The Python 3.9 deprecation warning correctly lists the origin as runtime.txt.
#[test]
Expand All @@ -332,6 +339,27 @@ fn runtime_txt() {
assert_eq!(
context.pack_stderr,
indoc! {"
[Warning: The runtime.txt file is deprecated]
The runtime.txt file is deprecated since it has been replaced
by the more widely supported .python-version file.
Please delete your runtime.txt file and create a new file named:
.python-version
Make sure to include the '.' at the start of the filename.
In the new file, specify your app's Python version without
quotes or a 'python-' prefix. For example:
3.9
We strongly recommend that you use the major version form
instead of pinning to an exact version, since it will allow
your app to receive Python security updates.
In the near future support for runtime.txt will be removed
and this warning will be made an error.
[Warning: Support for Python 3.9 is deprecated]
Python 3.9 will reach its upstream end-of-life in October 2025,
Expand Down Expand Up @@ -392,22 +420,29 @@ fn runtime_txt_invalid_version() {
context.pack_stderr,
&formatdoc! {"
[Error: Invalid Python version in runtime.txt]
The Python version specified in 'runtime.txt' isn't in
the correct format.
The Python version specified in your runtime.txt file isn't
in the correct format.
The following file contents were found:
The following file contents were found, which aren't valid:
python-an.invalid.version
However, the version must be specified as either:
1. 'python-<major>.<minor>' (recommended, for automatic updates)
2. 'python-<major>.<minor>.<patch>' (to pin to an exact version)
However, the runtime.txt file is deprecated since it has
been replaced by the .python-version file. As such, we
recommend that you switch to using a .python-version file
instead of fixing your runtime.txt file.
Remember to include the 'python-' prefix. Comments aren't
supported.
Please delete your runtime.txt file and create a new file named:
.python-version
For example, to request the latest version of Python {DEFAULT_PYTHON_VERSION},
update the 'runtime.txt' file so it contains:
python-{DEFAULT_PYTHON_VERSION}
Make sure to include the '.' at the start of the filename.
In the new file, specify your app's Python version without
quotes or a 'python-' prefix. For example:
{DEFAULT_PYTHON_VERSION}
We strongly recommend that you use the major version form
instead of pinning to an exact version, since it will allow
your app to receive Python security updates.
"}
);
});
Expand Down

0 comments on commit 4435b42

Please sign in to comment.