Skip to content

Commit

Permalink
add: ability to create conda env from yml file
Browse files Browse the repository at this point in the history
  • Loading branch information
jakubk committed Oct 1, 2017
1 parent 215d865 commit 6143e87
Show file tree
Hide file tree
Showing 3 changed files with 60 additions and 7 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ Valid options for each software package are the keyword arguments for the class
| | use_installer | If true, use FSL's Python installer. Only valid on CentOS images. |
| **MINC** | version* | 1.9.15 |
| **Miniconda** | env_name* | Name of this conda environment. |
| | yaml_file | Environment specification file. Can be path on host or URL. |
| | conda_install | Packages to install with conda. e.g., `conda_install="python=3.6 numpy traits"` |
| | pip_install | Packages to install with pip. |
| | conda_opts | Command-line options to pass to [`conda create`](https://conda.io/docs/commands/conda-create.html). e.g., `conda_opts="-c vida-nyu"` |
Expand Down
54 changes: 48 additions & 6 deletions neurodocker/interfaces/miniconda.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import logging
import posixpath

from neurodocker.utils import _indent_pkgs, check_url, indent
from neurodocker.utils import _indent_pkgs, check_url, indent, is_url

logger = logging.getLogger(__name__)

Expand All @@ -23,6 +23,8 @@ class Miniconda(object):
Name to give this environment.
pkg_manager : {'apt', 'yum'}
Linux package manager.
yaml_file : path-like or url-like
Conda environment specification file.
conda_install : str or list or tuple
Packages to install using `conda`, including Python. Follow the syntax
for `conda install`. For example, the input ['numpy=1.12', 'scipy'] is
Expand Down Expand Up @@ -55,10 +57,12 @@ class Miniconda(object):
INSTALLED = False
INSTALL_PATH = "/opt/conda"

def __init__(self, env_name, pkg_manager, conda_install=None,
pip_install=None, conda_opts=None, pip_opts=None,
add_to_path=False, miniconda_verion='latest', check_urls=True):
def __init__(self, env_name, pkg_manager, yaml_file=None,
conda_install=None, pip_install=None, conda_opts=None,
pip_opts=None, add_to_path=False, miniconda_verion='latest',
check_urls=True):
self.env_name = env_name
self.yaml_file = yaml_file
self.pkg_manager = pkg_manager
self.conda_install = conda_install
self.pip_install = pip_install
Expand All @@ -68,8 +72,15 @@ def __init__(self, env_name, pkg_manager, conda_install=None,
self.miniconda_verion = miniconda_verion
self.check_urls = check_urls

self._check_args()
self.cmd = self._create_cmd()

def _check_args(self):
if self.yaml_file and (self.conda_install is not None
or self.pip_install is not None):
raise ValueError("Packages cannot be installed while creating an"
" environment from a yaml file.")

def _create_cmd(self):
cmds = []
comment = ("#------------------"
Expand All @@ -80,13 +91,16 @@ def _create_cmd(self):
cmds.append(self.install_miniconda())
cmds.append('')

create = not (self.env_name in Miniconda.created_envs)
create = self.env_name not in Miniconda.created_envs
_comment_base = "Create" if create else "Update"
comment = ("#-------------------------"
"\n# {} conda environment"
"\n#-------------------------").format(_comment_base)
cmds.append(comment)
cmds.append(self.conda_and_pip_install(create=create))
if self.yaml_file is not None:
cmds.append(self.create_from_yaml())
else:
cmds.append(self.conda_and_pip_install(create=create))

return "\n".join(cmds)

Expand Down Expand Up @@ -120,6 +134,34 @@ def install_miniconda(self):

return "\n".join((env_cmd, cmd))

def create_from_yaml(self):
"""Return Dockerfile instructions to create conda environment from
a YAML file.
"""
tmp_yml = "/tmp/environment.yml"
cmd = ("conda env create -q --name {n} --file {tmp}"
"\n&& rm -f {tmp}")

if is_url(self.yaml_file):
get_file = "curl -sSL {f} > {tmp}"
cmd = get_file + "\n&& " + cmd
if self.check_urls:
check_url(self.yaml_file)
cmd = indent("RUN", cmd)
else:
get_file = 'COPY ["{f}", "{tmp}"]'
cmd = indent("RUN", cmd)
cmd = "\n".join((get_file, cmd))

cmd = cmd.format(n=self.env_name, f=self.yaml_file, tmp=tmp_yml)

if self.add_to_path:
bin_path = posixpath.join(Miniconda.INSTALL_PATH, 'envs',
self.env_name, 'bin')
env_cmd = "ENV PATH={}:$PATH".format(bin_path)
return "\n".join((cmd, env_cmd))
return cmd

def conda_and_pip_install(self, create=True):
"""Return Dockerfile instructions to create conda environment with
desired version of Python and desired conda and pip packages.
Expand Down
12 changes: 11 additions & 1 deletion neurodocker/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,11 +112,21 @@ def _namespace_to_specs(namespace):

specs = {'pkg_manager': namespace.pkg_manager,
'check_urls': namespace.check_urls,
'instructions': instructions,}
'instructions': instructions, }

return specs


def is_url(string):
try:
from urllib.parse import urlparse # Python 3
except ImportError:
from urlparse import urlparse # Python 2

result = urlparse(string)
return (result.scheme and result.netloc)


def check_url(url, timeout=5, **kwargs):
"""Return true if `url` is returns a status code < 400. Otherwise, raise an
error. `kwargs` are arguments for `requests.head()`.
Expand Down

0 comments on commit 6143e87

Please sign in to comment.