blob: e96ffd2023324ec17a0c2cf32c88c8ce50d28535 [file]
"""Utilities for reading values from pyproject.toml."""
load("@toml.bzl", "toml")
load(":version.bzl", "version")
def read_pyproject(module_ctx, pyproject):
"""Read a pyproject.toml file and return the relevant fields.
The file is parsed with a pure-Starlark TOML decoder; no Python
interpreter is required. The raw `requires-python` value is returned
as-is so that callers can decide how to interpret it.
Args:
module_ctx: the module extension context (needs `path` and `read`).
pyproject: {type}`Label` pointing at the pyproject.toml file.
Returns:
{type}`struct` with the attributes:
* `requires_python`: {type}`str | None` the raw `requires-python`
value (e.g. `"==3.13.9"`), or `None` if it is not set.
"""
data = toml.decode(module_ctx.read(module_ctx.path(pyproject), watch = "yes"))
return struct(
requires_python = data.get("project", {}).get("requires-python"),
)
def version_from_requires_python(requires_python):
"""Derive a concrete Python version from a `requires-python` value.
Currently only an exact `==X.Y.Z` specifier is supported. The value is
validated and normalized via {obj}`//python/private:version.bzl` so that
malformed input fails in a consistent way. Broader specifier support
(e.g. `>=`, `X.Y`) can be layered on here in the future.
Args:
requires_python: {type}`str` the raw `requires-python` value.
Returns:
{type}`str` the normalized version string (e.g. `"3.13.9"`).
"""
if not requires_python:
fail("`requires-python` must be specified")
if not requires_python.startswith("=="):
fail("`requires-python` must pin an exact version with `==`, got: {}".format(requires_python))
bare_version = requires_python[len("=="):].strip()
# Parse strictly so malformed versions fail cleanly, then normalize.
version.parse(bare_version, strict = True)
return version.normalize(bare_version)