Skip to content

Commit d7684df

Browse files
authored
feat: Add pip package management samples (aws-deadline#251)
Add samples for managing Python dependencies with pip, mirroring the existing Conda samples: - queue_environments/pip_queue_env.yaml: a queue environment that builds a Python virtual environment with venv and installs the PipPackages parameter into it, with optional PipIndexUrl/PipExtraIndexUrls for private indexes. No-ops when PipPackages is empty. - job_bundles/pip_package_job: a job bundle that relies on the pip queue environment to provide its dependencies. - job_bundles/pip_self_contained_job: a job bundle that manages its own pip virtual environment inline via a jobEnvironment, needing no queue environment. The pip self-upgrade and package install both pass the index options so a private PipIndexUrl works on workers that cannot reach public PyPI. Both bundles were verified end-to-end on a Deadline Cloud service-managed Linux fleet, and locally with the Open Job Description CLI. Signed-off-by: Stephen Crowe <6042774+crowecawcaw@users.noreply.github.com>
1 parent d2a2683 commit d7684df

6 files changed

Lines changed: 512 additions & 1 deletion

File tree

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
# Pip Package Job
2+
3+
This job bundle demonstrates providing a job's Python dependencies with
4+
[pip](https://pip.pypa.io/) through a **queue environment**. The job itself only
5+
declares the `PipPackages`, `PipIndexUrl`, and `PipExtraIndexUrls` parameters —
6+
the [`pip_queue_env.yaml`](../../queue_environments/pip_queue_env.yaml) queue
7+
environment reads those parameters, builds a Python virtual environment, installs
8+
the packages, and puts the environment on the `PATH` before the step runs.
9+
10+
This is the pip analogue of the Conda samples that pass `CondaPackages` to a
11+
Conda queue environment (see [`monte_carlo_simulation`](../monte_carlo_simulation)).
12+
Use this style when you want to define the pip environment once and share it
13+
across many jobs on a queue.
14+
15+
## Prerequisites
16+
17+
- A queue with the [`pip_queue_env.yaml`](../../queue_environments/pip_queue_env.yaml)
18+
queue environment added to it. See the
19+
[queue_environments README](../../queue_environments/README.md) for how to
20+
create a queue environment.
21+
- A Linux worker with `python3` available on the `PATH`. Deadline Cloud
22+
service-managed fleets satisfy this.
23+
24+
## Submit the job
25+
26+
```bash
27+
deadline bundle submit job_bundles/pip_package_job
28+
```
29+
30+
By default it installs the [`cowsay`](https://pypi.org/project/cowsay/) package
31+
and prints a message. Override the parameters to install your own packages, for
32+
example:
33+
34+
```bash
35+
deadline bundle submit job_bundles/pip_package_job \
36+
-p PipPackages="requests rich" \
37+
-p Message="Hello from pip"
38+
```
39+
40+
To install from a private index such as AWS CodeArtifact, set `PipIndexUrl` (and
41+
optionally `PipExtraIndexUrls`) to the index endpoint.
42+
43+
## Test it locally
44+
45+
You can run the job with the queue environment locally using the
46+
[Open Job Description CLI](https://github.com/OpenJobDescription/openjd-cli):
47+
48+
```bash
49+
openjd run job_bundles/pip_package_job/template.yaml \
50+
--step SayHello \
51+
--environment queue_environments/pip_queue_env.yaml \
52+
--job-param PipPackages=cowsay
53+
```
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
specificationVersion: 'jobtemplate-2023-09'
2+
name: Pip Package Job
3+
4+
description: |
5+
Demonstrates running a job whose Python dependencies are provided by pip through
6+
a queue environment. The job declares the PipPackages and PipIndexUrl parameters
7+
that the pip_queue_env.yaml queue environment consumes to build a Python virtual
8+
environment before the step runs.
9+
10+
This mirrors how the Conda samples pass CondaPackages to a Conda queue environment.
11+
Add the queue_environments/pip_queue_env.yaml queue environment to your queue for
12+
this job to work.
13+
14+
parameterDefinitions:
15+
- name: Message
16+
type: STRING
17+
default: "Hello from a pip-managed virtual environment!"
18+
description: "The message the cowsay package prints."
19+
userInterface:
20+
control: LINE_EDIT
21+
label: Message
22+
- name: PipPackages
23+
type: STRING
24+
default: "cowsay"
25+
description: "A space-separated list of pip requirement specifiers the queue environment installs."
26+
userInterface:
27+
control: LINE_EDIT
28+
label: Pip Packages
29+
groupLabel: Software Environment
30+
- name: PipIndexUrl
31+
type: STRING
32+
default: ""
33+
description: "The base Python package index URL. Leave empty to use the default PyPI index."
34+
userInterface:
35+
control: LINE_EDIT
36+
label: Pip Index URL
37+
groupLabel: Software Environment
38+
- name: PipExtraIndexUrls
39+
type: STRING
40+
default: ""
41+
description: "A space-separated list of additional package index URLs."
42+
userInterface:
43+
control: LINE_EDIT
44+
label: Pip Extra Index URLs
45+
groupLabel: Software Environment
46+
47+
steps:
48+
- name: SayHello
49+
hostRequirements:
50+
attributes:
51+
- name: attr.worker.os.family
52+
anyOf: ["linux"]
53+
script:
54+
actions:
55+
onRun:
56+
command: python
57+
args: ['{{Task.File.Run}}']
58+
embeddedFiles:
59+
- name: Run
60+
type: TEXT
61+
data: |
62+
# cowsay is provided by the pip queue environment's virtual environment,
63+
# which is on the PATH for this step.
64+
import cowsay
65+
66+
cowsay.cow("{{Param.Message}}")
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# Pip Self-Contained Job
2+
3+
This job bundle demonstrates managing [pip](https://pip.pypa.io/) packages
4+
entirely **within the job bundle**, with no queue environment required. The
5+
template defines its own Open Job Description
6+
[job environment](https://github.com/OpenJobDescription/openjd-specifications/wiki/2023-09-Template-Schemas#4-environment)
7+
that creates a Python virtual environment, installs the requested packages into
8+
it, and adds it to the `PATH` for all steps of the job.
9+
10+
Use this style when you want a self-contained bundle that runs on any Linux
11+
worker with `python3` available, without configuring anything on the queue. If
12+
you would rather define the pip environment once and share it across many jobs,
13+
see the [`pip_package_job`](../pip_package_job) sample and the
14+
[`pip_queue_env.yaml`](../../queue_environments/pip_queue_env.yaml) queue
15+
environment instead.
16+
17+
This is the pip analogue of an inline job environment; compare it with the
18+
[`job_env_with_new_command`](../job_env_with_new_command) sample.
19+
20+
## Prerequisites
21+
22+
- A Linux worker with `python3` available on the `PATH`. Deadline Cloud
23+
service-managed fleets satisfy this.
24+
25+
## Submit the job
26+
27+
```bash
28+
deadline bundle submit job_bundles/pip_self_contained_job
29+
```
30+
31+
By default it installs the [`cowsay`](https://pypi.org/project/cowsay/) package
32+
and prints a message. Override the parameters to install your own packages:
33+
34+
```bash
35+
deadline bundle submit job_bundles/pip_self_contained_job \
36+
-p PipPackages="requests rich" \
37+
-p Message="Hello from pip"
38+
```
39+
40+
## Test it locally
41+
42+
You can run the job locally using the
43+
[Open Job Description CLI](https://github.com/OpenJobDescription/openjd-cli):
44+
45+
```bash
46+
openjd run job_bundles/pip_self_contained_job/template.yaml --step SayHello
47+
```
Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
specificationVersion: 'jobtemplate-2023-09'
2+
name: Pip Self-Contained Job
3+
4+
description: |
5+
Demonstrates managing pip packages entirely within a job bundle, without
6+
relying on a queue environment. The job defines its own Open Job Description
7+
job environment that creates a Python virtual environment, installs the
8+
requested pip packages into it, and adds it to the PATH for all steps.
9+
10+
Use this style when you want a self-contained job bundle that runs on any
11+
Linux worker with python3 available, and you do not want to configure a
12+
queue environment. If you would rather share one pip environment definition
13+
across many jobs, see the pip_package_job sample and the
14+
queue_environments/pip_queue_env.yaml queue environment instead.
15+
16+
parameterDefinitions:
17+
- name: Message
18+
type: STRING
19+
default: "Hello from a self-contained pip virtual environment!"
20+
description: "The message the cowsay package prints."
21+
userInterface:
22+
control: LINE_EDIT
23+
label: Message
24+
- name: PipPackages
25+
type: STRING
26+
default: "cowsay"
27+
description: "A space-separated list of pip requirement specifiers to install into the job's virtual environment."
28+
userInterface:
29+
control: LINE_EDIT
30+
label: Pip Packages
31+
groupLabel: Software Environment
32+
- name: PipIndexUrl
33+
type: STRING
34+
default: ""
35+
description: "The base Python package index URL. Leave empty to use the default PyPI index."
36+
userInterface:
37+
control: LINE_EDIT
38+
label: Pip Index URL
39+
groupLabel: Software Environment
40+
41+
jobEnvironments:
42+
- name: PipVirtualEnv
43+
description: Creates a Python virtual environment and installs the PipPackages into it.
44+
script:
45+
actions:
46+
onEnter:
47+
command: bash
48+
args:
49+
- "{{Env.File.Enter}}"
50+
embeddedFiles:
51+
- name: Enter
52+
type: TEXT
53+
data: |
54+
#!/bin/env bash
55+
set -euo pipefail
56+
57+
if [ -z '{{Param.PipPackages}}' ]; then
58+
echo "Skipping pip virtual environment as PipPackages parameter was empty."
59+
exit 0
60+
fi
61+
62+
PIP_PACKAGES='{{Param.PipPackages}}'
63+
64+
INDEX_OPTS=""
65+
if [ -n '{{Param.PipIndexUrl}}' ]; then
66+
INDEX_OPTS="--index-url {{Param.PipIndexUrl}}"
67+
fi
68+
69+
# Find a Python interpreter. Prefer python3, but fall back to python.
70+
if command -v python3 > /dev/null 2>&1; then
71+
PYTHON=python3
72+
else
73+
PYTHON=python
74+
fi
75+
76+
echo "Creating a Python virtual environment in the session directory..."
77+
78+
# Create the virtual environment in the session working directory so it
79+
# is cleaned up automatically when the session ends.
80+
ENV_DIR="$(mktemp -d '{{Session.WorkingDirectory}}/.venv-XXXXX')"
81+
"$PYTHON" -m venv "$ENV_DIR"
82+
83+
# A venv puts its executables in bin/ on Linux and macOS, and in
84+
# Scripts/ on Windows.
85+
if [ -d "$ENV_DIR/bin" ]; then
86+
VENV_BIN="$ENV_DIR/bin"
87+
else
88+
VENV_BIN="$ENV_DIR/Scripts"
89+
fi
90+
91+
# Capture the environment variables before activating the virtual environment
92+
"$PYTHON" '{{Env.File.OpenJDVarsStart}}' .vars
93+
94+
# Activate the virtual environment.
95+
set +u
96+
source "$VENV_BIN/activate"
97+
set -u
98+
99+
# Pass the index options to both commands so a private PipIndexUrl also
100+
# works on workers that cannot reach public PyPI.
101+
pip install $INDEX_OPTS --quiet --upgrade pip
102+
pip install $INDEX_OPTS $PIP_PACKAGES
103+
104+
# Export the environment variables set by activation to all steps
105+
"$VENV_BIN/python" '{{Env.File.OpenJDVarsCapture}}' .vars
106+
107+
echo "Virtual environment created at $ENV_DIR"
108+
pip list
109+
- name: OpenJDVarsStart
110+
filename: openjd-vars-start.py
111+
type: TEXT
112+
data: |
113+
import json
114+
import os
115+
import sys
116+
117+
# Exclude the env var "_" as it has special meaning to shells
118+
before = dict(os.environ)
119+
if "_" in before:
120+
del before["_"]
121+
122+
with open(sys.argv[1], "w", encoding="utf8") as f:
123+
json.dump(before, f)
124+
- name: OpenJDVarsCapture
125+
filename: openjd-vars-capture.py
126+
type: TEXT
127+
data: |
128+
import json
129+
import os
130+
import sys
131+
132+
# Get the snapshot from `openjd-vars-start.py`, and the current environment state.
133+
with open(sys.argv[1], "r", encoding="utf8") as f:
134+
before = json.load(f)
135+
after = dict(os.environ)
136+
# Exclude the env var "_" as it has special meaning to shells
137+
if "_" in after:
138+
del after["_"]
139+
140+
# Identify the modified and deleted environment variables
141+
vars_to_put = {k: v for k, v in after.items() if v != before.get(k)}
142+
vars_to_delete = {k for k in before if k not in after}
143+
144+
# Print the env var changes following the Open Job Description specification
145+
for k, v in vars_to_put.items():
146+
kv = json.dumps(f"{k}={v}", ensure_ascii=True)
147+
print(f"openjd_env: {kv}")
148+
for k in vars_to_delete:
149+
print(f"openjd_unset_env: {k}")
150+
151+
steps:
152+
- name: SayHello
153+
hostRequirements:
154+
attributes:
155+
- name: attr.worker.os.family
156+
anyOf: ["linux"]
157+
script:
158+
actions:
159+
onRun:
160+
command: python
161+
args: ['{{Task.File.Run}}']
162+
embeddedFiles:
163+
- name: Run
164+
type: TEXT
165+
data: |
166+
# cowsay is provided by the PipVirtualEnv job environment, which put its
167+
# virtual environment on the PATH for this step.
168+
import cowsay
169+
170+
cowsay.cow("{{Param.Message}}")

queue_environments/README.md

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ follow the environment template specification from
88

99
The Conda and Rez queue environments let you provide software applications to jobs in your
1010
Deadline Cloud queue, so each job only needs a parameter value for `CondaPackages` or `RezPackages`
11-
to tell it the list of packages to use.
11+
to tell it the list of packages to use. The pip queue environment does the same for Python
12+
packages, so a job only needs to provide a value for `PipPackages`.
1213

1314
## Create a queue environment for your queue
1415

@@ -210,6 +211,26 @@ jobs. The default environment name uses the hash of the Conda channels and packa
210211
set the name in the job. It also includes a parameter for how long to use an environment without running a package
211212
update, so that most of the time it will take seconds to activate an environment that's being reused.
212213

214+
### Pip queue environment
215+
216+
The file [pip_queue_env.yaml](pip_queue_env.yaml) lets you provide Python packages to jobs using
217+
[pip](https://pip.pypa.io/) and the standard library [venv](https://docs.python.org/3/library/venv.html)
218+
module, rather than a package manager like Conda or Rez. When a job provides a `PipPackages` parameter
219+
value, the queue environment creates a Python virtual environment in the session working directory,
220+
installs the requested packages into it with pip, and activates it so subsequent steps run with those
221+
packages available. If `PipPackages` is empty, the queue environment does nothing, so it is safe to add
222+
to a queue that also runs jobs which do not use it.
223+
224+
The `PipIndexUrl` and `PipExtraIndexUrls` parameters let jobs install from a private package index, such
225+
as an [AWS CodeArtifact](https://docs.aws.amazon.com/codeartifact/) repository, instead of the default
226+
[PyPI](https://pypi.org/) index.
227+
228+
Unlike Conda and Rez, pip and venv are included with Python itself, so worker hosts only need a `python3`
229+
(or `python`) interpreter on the `PATH`. Deadline Cloud service-managed fleets provide one. The
230+
[pip_package_job](../job_bundles/pip_package_job) job bundle shows how to submit a job that uses this
231+
queue environment, and [pip_self_contained_job](../job_bundles/pip_self_contained_job) shows the same
232+
pip environment defined inline in a job bundle when you do not want to configure a queue environment.
233+
213234
### Disconnect UBL queue environment
214235

215236
The file [disconnect_ubl_queue_env.yaml](disconnect_ubl_queue_env.yaml) unsets Deadline Cloud Usage Based

0 commit comments

Comments
 (0)