Merge pull request #298 from minrk/always-conda-python

Always install Python with conda
pull/306/head
Tim Head 2018-05-08 10:33:03 +02:00 zatwierdzone przez GitHub
commit f73db1121e
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 4AEE18F83AFDEB23
13 zmienionych plików z 72 dodań i 373 usunięć

Wyświetl plik

@ -29,7 +29,7 @@ from traitlets.config import Application
from . import __version__ from . import __version__
from .buildpacks import ( from .buildpacks import (
PythonBuildPack, DockerBuildPack, LegacyBinderDockerBuildPack, PythonBuildPack, DockerBuildPack, LegacyBinderDockerBuildPack,
CondaBuildPack, JuliaBuildPack, Python2BuildPack, BaseImage, CondaBuildPack, JuliaBuildPack, BaseImage,
RBuildPack RBuildPack
) )
from .utils import ( from .utils import (
@ -67,7 +67,6 @@ class Repo2Docker(Application):
DockerBuildPack, DockerBuildPack,
JuliaBuildPack, JuliaBuildPack,
CondaBuildPack, CondaBuildPack,
Python2BuildPack,
RBuildPack, RBuildPack,
PythonBuildPack, PythonBuildPack,
], ],

Wyświetl plik

@ -1,5 +1,5 @@
from .base import BuildPack, BaseImage from .base import BuildPack, BaseImage
from .python import Python2BuildPack, PythonBuildPack from .python import PythonBuildPack
from .conda import CondaBuildPack from .conda import CondaBuildPack
from .julia import JuliaBuildPack from .julia import JuliaBuildPack
from .docker import DockerBuildPack from .docker import DockerBuildPack

Wyświetl plik

@ -25,17 +25,26 @@ class CondaBuildPack(BaseImage):
the `NB_PYTHON_PREFIX` to the location of the jupyter binary. the `NB_PYTHON_PREFIX` to the location of the jupyter binary.
""" """
return super().get_env() + [ env = super().get_env() + [
('CONDA_DIR', '${APP_BASE}/conda'), ('CONDA_DIR', '${APP_BASE}/conda'),
('NB_PYTHON_PREFIX', '${CONDA_DIR}'), ('NB_PYTHON_PREFIX', '${CONDA_DIR}'),
] ]
if self.py2:
env.append(('KERNEL_PYTHON_PREFIX', '${CONDA_DIR}/envs/kernel'))
else:
env.append(('KERNEL_PYTHON_PREFIX', '${NB_PYTHON_PREFIX}'))
return env
def get_path(self): def get_path(self):
"""Return paths (including conda environment path) to be added to """Return paths (including conda environment path) to be added to
the PATH environment variable. the PATH environment variable.
""" """
return super().get_path() + ['${CONDA_DIR}/bin'] path = super().get_path()
if self.py2:
path.insert(0, '${KERNEL_PYTHON_PREFIX}/bin')
path.insert(0, '${CONDA_DIR}/bin')
return path
def get_build_scripts(self): def get_build_scripts(self):
""" """

Wyświetl plik

@ -1,139 +1,70 @@
"""Generates Dockerfiles based on an input matrix based on Python.""" """Generates Dockerfiles based on an input matrix based on Python."""
import os import os
from ..base import BaseImage from ..conda import CondaBuildPack
class PythonBuildPack(BaseImage): class PythonBuildPack(CondaBuildPack):
"""Setup Python 3 for use with a repository.""" """Setup Python for use with a repository."""
def get_packages(self):
"""Return a list of the Python 3 core language packages to be installed
via apt-get for this BuildPack.
Note: The packages specified here are for the core Python3 language. @property
Third party libraries are specified in other configuration files. def python_version(self):
if hasattr(self, '_python_version'):
return self._python_version
""" try:
return super().get_packages().union({ with open(self.binder_path('runtime.txt')) as f:
'python3', runtime = f.read().strip()
'python3-venv', except FileNotFoundError:
'python3-dev', runtime = ''
})
def get_env(self): if not runtime.startswith('python-'):
""" # not a Python runtime (e.g. R, which subclasses this)
Return environment variables to be set. # use the default Python
self._python_version = self.major_pythons['3']
return self._python_version
We set `VENV_PATH` to the virtual environment location and py_version_info = runtime.split('-', 1)[1].split('.')
the `NB_PYTHON_PREFIX` to the location of the jupyter binary. py_version = ''
if len(py_version_info) == 1:
""" py_version = self.major_pythons[py_version_info[0]]
return super().get_env() + [ else:
("VENV_PATH", "${APP_BASE}/venv"), # get major.minor
# Prefix to use for installing kernels and finding jupyter binary py_version = '.'.join(py_version_info[:2])
("NB_PYTHON_PREFIX", "${VENV_PATH}"), self._python_version = py_version
] return self._python_version
def get_path(self):
"""Return paths (including virtual environment path) to be added to
the PATH environment variable.
"""
return super().get_path() + [
"${VENV_PATH}/bin"
]
def get_build_script_files(self):
"""
Dict of files to be copied to the container image for use in building.
This is copied before the `build_scripts` & `assemble_scripts` are
run, so can be executed from either of them.
It's a dictionary where the key is the source file path in the host
system, and the value is the destination file path inside the
container image.
This currently adds a frozen set of Python 3 requirements to the dict
of files.
"""
files = {
'python/requirements.frozen.txt': '/tmp/requirements.frozen.txt',
}
files.update(super().get_build_script_files())
return files
def get_build_scripts(self):
"""
Return series of build-steps common to all Python 3 repositories.
All scripts here should be independent of contents of the repository.
This sets up:
- a directory for the virtual environment and its ownership by the
notebook user
- a Python 3 interpreter for the virtual environement
- a Python 3 jupyter kernel including a base set of requirements
- support for Jupyter widgets
- support for JupyterLab
- support for nteract
"""
return super().get_build_scripts() + [
(
"root",
r"""
mkdir -p ${VENV_PATH} && \
chown -R ${NB_USER}:${NB_USER} ${VENV_PATH}
"""
),
(
"${NB_USER}",
r"""
python3 -m venv ${VENV_PATH}
"""
),
(
"${NB_USER}",
r"""
pip install --no-cache-dir -r /tmp/requirements.frozen.txt && \
jupyter nbextension enable --py widgetsnbextension --sys-prefix && \
jupyter serverextension enable --py jupyterlab --sys-prefix && \
jupyter serverextension enable nteract_on_jupyter --sys-prefix
"""
)
]
def get_assemble_scripts(self): def get_assemble_scripts(self):
"""Return series of build-steps specific to this repository. """Return series of build-steps specific to this repository.
""" """
# If we have a runtime.txt & that's set to python-2.7, # If we have a runtime.txt & that's set to python-2.7,
# we will *not* install requirements.txt but will find & # requirements.txt will be installed in the *kernel* env
# install a requirements3.txt file if it exists. # and requirements3.txt (if it exists)
# This way, when using python2 venv, requirements.txt will # will be installed in the python 3 notebook server env.
# be installed in the python2 venv, and requirements3.txt
# will be installed in python3 venv. This is less of a
# surprise than requiring python2 to be requirements2.txt tho.
assemble_scripts = super().get_assemble_scripts() assemble_scripts = super().get_assemble_scripts()
setup_py = 'setup.py' setup_py = 'setup.py'
try: # KERNEL_PYTHON_PREFIX is the env with the kernel,
with open(self.binder_path('runtime.txt')) as f: # whether it's distinct from the notebook or the same.
runtime = f.read().strip() pip = '${KERNEL_PYTHON_PREFIX}/bin/pip'
except FileNotFoundError: if self.py2:
runtime = 'python-3.5' # using python 2 kernel,
if runtime == 'python-2.7': # requirements3.txt allows installation in the notebook server env
pip = "pip2" nb_requirements_file = self.binder_path('requirements3.txt')
requirements_file = self.binder_path('requirements3.txt') if os.path.exists(nb_requirements_file):
else: assemble_scripts.append((
pip = "pip3" '${NB_USER}',
'${NB_PYTHON_PREFIX}/bin/pip install --no-cache-dir -r "{}"'.format(nb_requirements_file)
))
# install requirements.txt in the kernel env
requirements_file = self.binder_path('requirements.txt') requirements_file = self.binder_path('requirements.txt')
if os.path.exists(requirements_file): if os.path.exists(requirements_file):
assemble_scripts.append(( assemble_scripts.append((
'${NB_USER}', '${NB_USER}',
'pip3 install --no-cache-dir -r "{}"'.format(requirements_file) '{} install --no-cache-dir -r "{}"'.format(pip, requirements_file)
)) ))
# setup.py exists *and* binder dir is not used
if not os.path.exists('binder') and os.path.exists(setup_py): if not os.path.exists('binder') and os.path.exists(setup_py):
assemble_scripts.append(( assemble_scripts.append((
'${NB_USER}', '${NB_USER}',
@ -142,7 +73,7 @@ class PythonBuildPack(BaseImage):
return assemble_scripts return assemble_scripts
def detect(self): def detect(self):
"""Check if current repo should be built with the Python 3 Build pack. """Check if current repo should be built with the Python buildpack.
""" """
requirements_txt = self.binder_path('requirements.txt') requirements_txt = self.binder_path('requirements.txt')
runtime_txt = self.binder_path('runtime.txt') runtime_txt = self.binder_path('runtime.txt')
@ -151,136 +82,10 @@ class PythonBuildPack(BaseImage):
if os.path.exists(runtime_txt): if os.path.exists(runtime_txt):
with open(runtime_txt) as f: with open(runtime_txt) as f:
runtime = f.read().strip() runtime = f.read().strip()
if runtime.startswith("python-3"): if runtime.startswith("python-"):
return True return True
else: else:
return False return False
if not os.path.exists('binder') and os.path.exists(setup_py): if not os.path.exists('binder') and os.path.exists(setup_py):
return True return True
return os.path.exists(requirements_txt) return os.path.exists(requirements_txt)
class Python2BuildPack(PythonBuildPack):
"""Setup Python 2 for use with a repository."""
def get_packages(self):
"""Return a list of the Python 2 core language packages to be installed
via apt-get for this BuildPack.
Note: The packages specified here are for the core Python2 language.
Third party libraries are specified in other configuration files.
"""
return super().get_packages().union({
'python',
'python-dev',
'virtualenv'
})
def get_env(self):
"""
Return environment variables to be set.
We set `VENV_PATH` to the virtual environment location containing
Python 2.
"""
return super().get_env() + [
('VENV2_PATH', '${APP_BASE}/venv2')
]
def get_path(self):
"""Return paths (including virtual environment path) to be added to
the PATH environment variable.
"""
return super().get_path() + [
"${VENV2_PATH}/bin"
]
def get_build_script_files(self):
"""
Dict of files to be copied to the container image for use in building.
This is copied before the `build_scripts` & `assemble_scripts` are
run, so can be executed from either of them.
It's a dictionary where the key is the source file path in the host
system, and the value is the destination file path inside the
container image.
This currently adds a frozen set of Python 2 requirements to the dict
of files.
"""
files = {
'python/requirements2.frozen.txt': '/tmp/requirements2.frozen.txt',
}
files.update(super().get_build_script_files())
return files
def get_build_scripts(self):
"""
Return series of build-steps common to all Python 2 repositories.
All scripts here should be independent of contents of the repository.
This sets up:
- a directory for the virtual environment and its ownership by the
notebook user
- a Python 2 interpreter for the virtual environement
- a Python 2 jupyter kernel
"""
return super().get_build_scripts() + [
(
"root",
r"""
mkdir -p ${VENV2_PATH} && \
chown -R ${NB_USER}:${NB_USER} ${VENV2_PATH}
"""
),
(
"${NB_USER}",
r"""
virtualenv -p python2 ${VENV2_PATH}
"""
),
(
"${NB_USER}",
r"""
pip2 install --no-cache-dir -r /tmp/requirements2.frozen.txt && \
python2 -m ipykernel install --prefix=${NB_PYTHON_PREFIX}
"""
)
]
def get_assemble_scripts(self):
"""Return series of build-steps specific to this repository.
"""
requirements_txt = self.binder_path('requirements.txt')
assemble_scripts = super().get_assemble_scripts()
if os.path.exists(requirements_txt):
assemble_scripts.insert(0, (
'${NB_USER}',
'pip2 install --no-cache-dir -r "{}"'.format(requirements_txt)
))
return assemble_scripts
def detect(self):
"""Check if current repo should be built with the Python 2 Build pack.
"""
runtime_txt = self.binder_path('runtime.txt')
if os.path.exists(runtime_txt):
with open(runtime_txt) as f:
runtime = f.read().strip()
if runtime == 'python-2.7':
return True
elif runtime.startswith('python-2'):
raise ValueError(
"Only python-2.7 or python-3.x is supported in "
"runtime.txt, not '{}'".format(runtime_txt))
else:
return False
return False

Wyświetl plik

@ -1,39 +0,0 @@
#!/bin/bash
set -euo pipefail
# Freeze requirements.txt into requirements.frozen.txt, pinning all dependent library versions to
# versions that are resolved at time of freezing.
# Does the same for requirements2.txt to requirements2.frozen.txt...
# cd to the directory where the freeze script is located
if [[ ! -z "$(which realpath 2>/dev/null)" ]]; then
realpath=realpath
else
realpath="readlink -f"
fi
cd $(dirname "$($realpath "$0")")
function freeze-requirements {
# Freeze a requirements file $2 into a frozen requirements file $3
# Requires that a completely empty venv of appropriate version exist in $1
PYTHON_VERSION="$1"
REQUIREMENTS_FILE="$2"
FROZEN_FILE="$3"
if [[ $(echo ${PYTHON_VERSION} | cut -d. -f 1) == "2" ]]; then
VENV=virtualenv
else
VENV=venv
fi
echo "# AUTO GENERATED FROM ${REQUIREMENTS_FILE}, DO NOT MANUALLY MODIFY" > ${FROZEN_FILE}
echo "# Frozen on $(date -u)" >> ${FROZEN_FILE}
docker run --rm -v $PWD:/python -it python:${PYTHON_VERSION} \
sh -c "
python -m $VENV /venv
/venv/bin/pip install -r /python/${REQUIREMENTS_FILE} &&
/venv/bin/pip freeze | sort --ignore-case >> /python/${FROZEN_FILE}"
}
freeze-requirements 3.5 requirements.txt requirements.frozen.txt
freeze-requirements 2.7 requirements2.txt requirements2.frozen.txt

Wyświetl plik

@ -1,42 +0,0 @@
# AUTO GENERATED FROM requirements.txt, DO NOT MANUALLY MODIFY
# Frozen on Mon Apr 2 07:17:48 UTC 2018
bleach==2.1.3
decorator==4.2.1
entrypoints==0.2.3
html5lib==1.0.1
ipykernel==4.8.2
ipython-genutils==0.2.0
ipython==6.2.1
ipywidgets==7.1.1
jedi==0.11.1
Jinja2==2.10
jsonschema==2.6.0
jupyter-client==5.2.3
jupyter-core==4.4.0
jupyterlab-launcher==0.10.5
jupyterlab==0.31.5
MarkupSafe==1.0
mistune==0.8.3
nbconvert==5.3.1
nbformat==4.4.0
notebook==5.4.1
nteract-on-jupyter==1.6.0
pandocfilters==1.4.2
parso==0.1.1
pexpect==4.4.0
pickleshare==0.7.4
prompt-toolkit==1.0.15
ptyprocess==0.5.2
Pygments==2.2.0
python-dateutil==2.7.2
pyzmq==17.0.0
Send2Trash==1.5.0
simplegeneric==0.8.1
six==1.11.0
terminado==0.8.1
testpath==0.3.1
tornado==4.5.3
traitlets==4.3.2
wcwidth==0.1.7
webencodings==0.5.1
widgetsnbextension==3.1.4

Wyświetl plik

@ -1,5 +0,0 @@
notebook==5.4.1
tornado==4.5.3
ipywidgets==7.1.1
jupyterlab==0.31.5
nteract_on_jupyter==1.6.0

Wyświetl plik

@ -1,27 +0,0 @@
# AUTO GENERATED FROM requirements2.txt, DO NOT MANUALLY MODIFY
# Frozen on Mon Apr 2 07:19:00 UTC 2018
backports-abc==0.5
backports.shutil-get-terminal-size==1.0.0
certifi==2018.1.18
decorator==4.2.1
enum34==1.1.6
ipykernel==4.8.2
ipython-genutils==0.2.0
ipython==5.5.0
jupyter-client==5.2.3
jupyter-core==4.4.0
pathlib2==2.3.0
pexpect==4.4.0
pickleshare==0.7.4
prompt-toolkit==1.0.15
ptyprocess==0.5.2
Pygments==2.2.0
python-dateutil==2.7.2
pyzmq==17.0.0
scandir==1.7
simplegeneric==0.8.1
singledispatch==3.4.0.3
six==1.11.0
tornado==4.5.3
traitlets==4.3.2
wcwidth==0.1.7

Wyświetl plik

@ -1,2 +0,0 @@
ipykernel==4.8.2
tornado==4.5.3

Wyświetl plik

@ -3,3 +3,7 @@ System - Specifying runtime environments
You can specify runtime environments (such as Python 2 or 3) with a You can specify runtime environments (such as Python 2 or 3) with a
``runtime.txt`` file. ``runtime.txt`` file.
You can specify just the major version,
such as `python-2` or `python-3`,
or a minor version, such as `python-3.5`.

Wyświetl plik

@ -1,9 +0,0 @@
System - Specifying runtime environments
----------------------------------------
You can specify runtime environments (such as Python 2 or 3) with a
``runtime.txt`` file.
This is an example that selects Python 3. Currently you can not use
this to select a sepcific version of Python 3 (e.g. 3.4 vs 3.6). If you
need this level of control we recommend you use a `environment.yml`.

Wyświetl plik

@ -0,0 +1 @@
python-3.5

Wyświetl plik

@ -0,0 +1,5 @@
#!/usr/bin/env python3
import sys
print(sys.version_info)
assert sys.version_info[:2] == (3, 5), sys.version