PEP 723 Metadata¶
Groundhog uses PEP 723 to embed configuration and dependencies directly in your Python scripts.
So what?
Leaning into PEP 723 means that your code and its environment are defined in the same file. Tightly coupling the code-iteration loop to the environment-iteration loop means that you don't fix your code, then wait in the submission queue, then fix your hand-crafted virtual environment, then wait in the queue again, then realize your local environment doesn't match, etc etc. Just declare what and where your code needs to run, Groundhog takes care of the rest.
What is PEP 723?¶
PEP 723 defines a standard for inline script metadata (like a pyproject.toml for scripts). It allows Python scripts to declare:
- Python version requirements
- Package dependencies
- Tool-specific configuration
The metadata lives in a comment block at the top of your script:
# /// script
# requires-python = ">=3.12"
# dependencies = [
# "numpy",
# "scipy>=1.11.0",
# ]
#
# [tool.hog.anvil]
# endpoint = "5aafb4c1-27b2-40d8-a038-a0277611868f"
# account = "my-account"
# ///
import groundhog_hpc as hog
# ... rest of your script
The # /// delimiters mark the start and end of the metadata block. Inside, you write TOML configuration.
Why Inline Metadata?¶
PEP 723 solves a specific problem: standalone scripts need dependency information without requiring a pyproject.toml or complex project structure.
Before PEP 723, you had two bad options:
- Import and fail: Write
import numpyand let the script crash if numpy isn't installed - Create a project: Add
pyproject.toml, virtual environment setup, and package tooling for a single script
PEP 723 provides a third option: declare dependencies inline. Tools like uv read the metadata and install packages automatically before running the script.
How uv Uses PEP 723¶
When you run a script with uv:
uv reads PEP 723 metadata (if present), creates an ephemeral virtual environment, and executes the script with the requested dependencies. You don't need to manage any virtual environments yourself.
How Groundhog Uses PEP 723¶
Groundhog extends PEP 723 with HPC-specific configuration in [tool.hog] tables:
# /// script
# requires-python = ">=3.12"
# dependencies = ["numpy"]
#
# [tool.hog.anvil]
# endpoint = "5aafb4c1-27b2-40d8-a038-a0277611868f"
# account = "my-account"
# walltime = "00:30:00"
# ///
When you call .remote(), Groundhog:
- Reads your script's PEP 723 metadata
- Templates a shell command that includes the metadata
- Submits the command to your HPC cluster via Globus Compute
- On the remote node,
uvreads the metadata and sets up the environment - Your function executes in the configured environment
This ensures the remote environment exactly matches your script's requirements.
Standard vs Tool-Specific Sections¶
PEP 723 defines standard fields:
requires-python- Python version (e.g.,">=3.12,<3.13")dependencies- Package list (e.g.,["numpy", "scipy>=1.11"])
Tools can add their own sections under [tool.*]:
[tool.uv]- uv package manager configuration (see below)[tool.hog.*]- Groundhog endpoint configurations
Standard fields control the Python environment. Tool-specific fields configure behavior.
Configuring uv via [tool.uv]¶
Groundhog uses uv to manage Python environments on remote endpoints. You can configure uv's behavior through the [tool.uv] section in your PEP 723 metadata.
Common [tool.uv] settings example:¶
# /// script
# requires-python = ">=3.11"
# dependencies = ["numpy", "torch"]
#
# [tool.uv]
# exclude-newer = "2025-12-19T00:00:00Z" # Lock packages to a point in time
# python-preference = "managed" # Use uv-managed Python
# extra-index-url = [ # Additional package indexes
# "https://download.pytorch.org/whl/cpu"
# ]
# ///
See also: Any uv settings can be used in [tool.uv] - the configuration is passed through to uv when creating the remote environment.
Custom package sources with [tool.uv.sources]¶
For finer control over where specific packages come from, use [tool.uv.sources]:
# /// script
# requires-python = ">=3.11"
# dependencies = ["torch==2.5.1", "my-internal-lib", "my-github-dependency"]
#
# [[tool.uv.index]]
# name = "pytorch-cpu"
# url = "https://download.pytorch.org/whl/cpu"
#
# [[tool.uv.index]]
# name = "facility-pypi"
# url = "https://pypi.facility.gov/simple"
#
# [tool.uv.sources] (1)
# torch = { index = "pytorch-cpu" }
# my-internal-lib = { index = "facility-pypi" }
# my-github-dependency = { git = "https://github.com/some-org/my-github-dependency", tag = "1.0.0" }
# ///
- See also:
uvsources documentation
This is useful for:
- Installing PyTorch CPU/CUDA variants from PyTorch's custom wheel server
- Using private package registries for internal packages
- Pulling specific packages from Git repositories or local paths
See the PyTorch Custom Index Example for a complete example.
Configuration precedence¶
uv reads configuration with precedence: Environment variables > [tool.uv] in script
This means:
- Settings in
[tool.uv]become the baseline for your script - Environment variables like
UV_INDEX_URLcan override them (useful for endpoint-specific configuration)
You can use environment variables to override [tool.uv] settings per endpoint:
[tool.hog.cpu_cluster]
endpoint = "..."
worker_init = """
export UV_EXTRA_INDEX_URL=https://download.pytorch.org/whl/cpu
"""
[tool.hog.gpu_cluster]
endpoint = "..."
worker_init = """
export UV_EXTRA_INDEX_URL=https://download.pytorch.org/whl/cu121
"""
This lets the same script work on both CPU and GPU clusters without code changes.
Next Steps¶
- Dependencies Example - Add and use packages
- Configuration Example - What
[tool.hog.*]config blocks do uvScripts Guide - Officialuvreference for PEP 723 scripts