Python project
The Python project in the project directory: its manifest and build backend, the Python version it asks for, the requirements it declares and which of them are pinned, and the lock files committed beside them.
| Facts key | python |
| Version | v1 |
| Script | python.py |
| Timeout | 30 seconds |
Inputs
| Input | Description | Default | Environment |
|---|---|---|---|
projectDir | Directory holding the project, relative to the directory the CLI runs in. | . | GUARDRAIL_INPUT_PROJECTDIR |
requirements | Comma separated globs naming the requirements files to read, relative to projectDir. | requirements.txt,requirements-dev.txt,requirements/*.txt | GUARDRAIL_INPUT_REQUIREMENTS |
A guardrail that declares an input of the same name passes it through, so the guardrail's configuration in buildnote.json is what decides these values.
A guardrail asks for these facts by name, and reads them back the same way:
{
"collect": ["python"]
}python = guardrail.facts("python")Facts
The document python collects. A path carrying [] is an entry of the list before it, and one carrying [path] is a key of the object before it.
| Fact | Meaning |
|---|---|
directory | The projectDir input, as the guardrail configured it. |
exists | Whether that directory is there. Nothing else is collected when it is not. |
manifest | Path of the primary manifest, relative to projectDir: pyproject.toml when there is one, otherwise setup.cfg, setup.py or the first requirements file found, in that order. |
sources | Every manifest that was read and understood, in the order they were read. A manifest the reader could not handle is in unparsed instead. |
declared | The Python version the project asks for, or null when it asks for none. .python-version wins over requires-python, because it is what a developer's shell reads. |
declared.version | Constraint exactly as written, such as >=3.11 or 3.12.2. |
declared.source | File declaring it, relative to projectDir. |
declared.pinned | Whether the constraint names one exact version rather than a range. A >=, a ~=, a ^, a * or a comma separated range is not pinned. |
backend | The build backend [build-system] names, such as hatchling.build or poetry.core.masonry.api, or null when the project declares none and setuptools is implied. |
packaging | How the project declares its dependencies: pep621 for [project], poetry for [tool.poetry], setuptools for setup.cfg or setup.py, requirements when only requirements files were found. |
projects | Every package in the build: the root, followed by every member a [tool.uv.workspace] or [tool.poetry.group] path names. |
projects[].path | Directory of the package, relative to projectDir, . for the project directory itself. |
projects[].manifest | That package's pyproject.toml, relative to projectDir. |
projects[].name | Name the manifest declares, or null when it declares none. |
dependencies | What the manifests declare, split by whether the project asks for it itself. |
dependencies.direct | Every requirement any manifest declares, in manifest order. |
dependencies.direct[].name | Distribution name, lower cased with runs of _, . and - normalised to -, which is how the index compares two names. |
dependencies.direct[].version | Constraint exactly as written, such as >=0.27, or null when the requirement names none. |
dependencies.direct[].scopes | Which group declares it: default, an extra's own name, a Poetry group's name, or the requirements file's stem. |
dependencies.direct[].source | Manifest declaring it, relative to projectDir. |
dependencies.direct[].pinned | Whether the constraint names one exact version with ==, which is the only operator that does. |
dependencies.direct[].marker | Environment marker the requirement carries, such as python_version < "3.12", or null when it carries none. A marked requirement is not installed everywhere. |
dependencies.transitive | Always empty. What a lock file resolves is not read: lockfiles says whether one is committed. |
requirements | Every requirements file that was read, relative to projectDir. |
lockfiles | Every lock file committed, relative to projectDir. More than one means two installers each believe they own the environment. |
unparsed | Every manifest the reader could not handle, so a guardrail can tell a project that declares nothing from one nobody could read. |
unparsed[].path | Path of that manifest, relative to projectDir. |
unparsed[].reason | What stopped the reader. |
A collector that cannot finish prints what it had along with an incomplete key saying why, so the facts below the point it stopped are absent. A check that cares reads incomplete before the rest.
Example facts
The example facts collected by python collector:
{
"directory": ".",
"exists": true,
"manifest": "pyproject.toml",
"sources": [
"pyproject.toml"
],
"declared": {
"version": "3.12.2",
"source": ".python-version",
"pinned": true
},
"backend": "hatchling.build",
"packaging": "pep621",
"projects": [
{
"path": ".",
"manifest": "pyproject.toml",
"name": "widget"
}
],
"dependencies": {
"direct": [
{
"name": "httpx",
"version": ">=0.27",
"scopes": [
"default"
],
"source": "pyproject.toml",
"pinned": false,
"marker": null
},
{
"name": "pydantic",
"version": "~=2.6",
"scopes": [
"default"
],
"source": "pyproject.toml",
"pinned": false,
"marker": null
},
{
"name": "tomli",
"version": "==2.0.1",
"scopes": [
"default"
],
"source": "pyproject.toml",
"pinned": true,
"marker": "python_version < \"3.11\""
},
{
"name": "pytest",
"version": ">=8.0",
"scopes": [
"dev"
],
"source": "pyproject.toml",
"pinned": false,
"marker": null
},
{
"name": "ruff",
"version": "==0.4.4",
"scopes": [
"dev"
],
"source": "pyproject.toml",
"pinned": true,
"marker": null
}
],
"transitive": []
},
"requirements": [],
"lockfiles": [
"uv.lock"
],
"unparsed": []
}Collected for
| Guardrail | Category | Inputs |
|---|---|---|
python/build-backend-declared | python | projectDir |
python/lockfile-committed | python | projectDir |
python/single-lockfile | python | projectDir |
python/version-declared | python | projectDir |