Guide¶
A recipe¶
An app ships one TOML file per tool. Every section but [tool] and [locate]
is optional.
[tool]
name = "openbabel"
label = "OpenBabel"
version = "3.1.1" # pinned here; never a user option
prefix = "{tools_home}/{name}/{version}" # the default
[locate]
paths = ["{prefix}/bin/obabel"] # where the install puts it
commands = ["obabel"] # looked up on PATH
check = ["{exe}", "-V"] # {exe} = the candidate
[env] # a conda prefix at {prefix}
conda = ["openbabel={version}"]
pip = []
channels = ["conda-forge"] # the default
[[install]] # plain steps, after [env]
name = "download the model"
argv = ["curl", "-fsSL", "-o", "{prefix}/model.pt", "https://..."]
timeout = 1800
ToolSpec.load(path) reads the file, and spec.recipe(tools_home) binds its
templates to one host. tools_home is a path on that host.
from ms_env import ToolSpec
spec = ToolSpec.load("openbabel.toml")
recipe = spec.recipe("/home/me/.myapp/tools")
recipe() takes three more choices of the user:
| Argument | What it is |
|---|---|
prefix= |
A location in place of the managed one: an environment of their own, or a place to install into |
extra_paths= |
Executables the user registered, tried before anything else |
manager= |
A conda-type program to use on the host in place of micromamba |
Finding a tool¶
locate(recipe, host) returns (READY, executable), or (MISSING, ""), or
(UNREACHABLE, "") when ssh could not connect. status returns the state
alone. The first candidate whose check exits 0 wins, in this order:
- The paths the user registered.
- The managed install under
tools_home. - The commands on
PATH.
Both calls block on a subprocess, so keep them off UI threads.
Installing a tool¶
ensure(recipe, host) returns an Install at once and installs on a daemon
thread. Nothing is installed where the tool is already usable.
from ms_env import ensure
install = ensure(recipe)
install.future.result() # waits; raises when the install failed
print(*install.lines, sep="\n")
install.linesis the live log, and its last line is the error when there is one.- A second
ensureof the same tool on the same host joins the install that is running. [env]is created with micromamba: the one on the host'sPATH, or one downloaded intotools_home.
An app that shows the steps itself takes them from recipe_steps(recipe, host)
and runs them with run_steps(steps, on_line, on_step).
Whose an environment is¶
ensure stamps the prefix it installs. The stamp decides what may happen to it:
| At the prefix | ensure |
remove |
|---|---|---|
| Nothing | Creates it | Nothing to delete |
| A stamp of this recipe | Nothing | Deletes it |
| A stamp of another recipe (the TOML changed) | Creates it again: the update | Deletes it |
| Something without a stamp: the user's own environment | Verifies it, never changes it | Leaves it |
from ms_env import remove, verify
mine = spec.recipe("/home/me/.myapp/tools", prefix="/home/me/envs/babel") # an environment I already have
verify(mine) # [] when it meets the recipe, else what it lacks:
# ["conda: openbabel=3.1.1 is asked for and is not there"]
remove(recipe) # True: ms_env had installed it and it is gone
verify looks up every pinned conda package, every pinned pip package and the
recipe's check, and only reads. An executable registered on its own
(extra_paths=) is not an environment: it only has to pass the check.
While something runs from it¶
An update replaces an install, so an app can refuse it while the install is in
use. ensure takes busy=, a function that returns why the install cannot be
changed now, or an empty string.
ensure(recipe, host, busy=lambda: "2 jobs are running from it")
busy is asked only before an install of ms_env is changed. When it returns
a reason the install fails saying so and the prefix is left as it is. A prefix
that is missing, or already as the recipe asks, never asks.
A host¶
Host() is this machine. Host("me@login.hpc") runs every command through the
system's ssh with BatchMode=yes:
- Users, keys and jumps come from
~/.ssh/config. - The key logs in or the command fails at once; no credential is handled.
- Nothing is left listening on the other side.
from ms_env import Host
host = Host("me@login.hpc")
home = host.home() # the home of the account there
host.run("nproc").decode() # what the command printed; HostError when it fails
A failed login says what to do: ssh-copy-id when the key is not accepted, a
first login by hand when the host key is not known yet.
The environment of an app¶
app_recipe makes the recipe that gives a host the environment an app runs in
here: the same Python and the same versions of what this one has installed.
from ms_env import Host, app_recipe, ensure
host = Host("me@login.hpc")
home = host.home()
recipe = app_recipe(
"myapp",
["myapp", "ms_flow"], # packages: `name` or `name[extras]`
["myapp", "ms_flow"], # modules that have to import there
prefix=f"{home}/.myapp/env", # on the host
tools_home=f"{home}/.myapp/tools", # on the host
cache="/home/me/.myapp/cache/wheels", # on this machine
)
ensure(recipe, host).future.result()
What travels and what is downloaded:
- A package imported from a source checkout goes as a wheel of its code as it is now. The wheel is built once for each state of the code.
- A package that is installed here is asked for in the version it has.
- Everything else the host downloads, pinned to the versions of this environment, so the two machines may be different systems.
- When an index lacks a version that conda installed here, the install tries again pinning only what pip installed.
The environment is updated where it stands for as long as the Python version
is the same; a new Python makes it again. app_recipe builds wheels the first
time and after the code changes, so keep it off UI threads.
The environment manager¶
find_environment_manager() returns the program that creates environments on
this machine, or None when a recipe has to download its own micromamba.
- The variables the app names, in the order given.
MOLSUITE_ENV_MANAGER.micromambaonPATH.
Any of the variables may name a conda or a mamba.