Skip to content

Commit 5396ff7

Browse files
committed
Seed repo
0 parents  commit 5396ff7

10 files changed

+292
-0
lines changed

.gitattributes

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
* text=auto eol=lf

.gitignore

+95
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
# Byte-compiled / optimized / DLL files
2+
__pycache__/
3+
*.py[cod]
4+
5+
# C extensions
6+
*.so
7+
8+
# Distribution / packaging
9+
.Python
10+
env/
11+
bin/
12+
build/
13+
develop-eggs/
14+
dist/
15+
eggs/
16+
lib/
17+
lib64/
18+
parts/
19+
sdist/
20+
var/
21+
*.egg-info/
22+
.installed.cfg
23+
*.egg
24+
doc/_build
25+
26+
# Installer logs
27+
pip-log.txt
28+
pip-delete-this-directory.txt
29+
30+
# Unit test / coverage reports
31+
htmlcov/
32+
.tox/
33+
cover/
34+
.coverage
35+
.cache
36+
nosetests.xml
37+
coverage.xml
38+
cover/
39+
40+
# Translations
41+
*.mo
42+
43+
# Mr Developer
44+
.mr.developer.cfg
45+
.project
46+
.pydevproject
47+
48+
# Rope
49+
.ropeproject
50+
51+
# Django stuff:
52+
*.log
53+
*.pot
54+
55+
# Sphinx documentation
56+
docs/_build/
57+
58+
#mac
59+
.DS_Store
60+
*~
61+
62+
#pycharm
63+
.idea/*
64+
65+
#Dolphin browser files
66+
.directory/
67+
.directory
68+
69+
#Binary data files
70+
*.volume
71+
*.am
72+
*.tiff
73+
*.tif
74+
*.dat
75+
*.DAT
76+
77+
#generated documntation files
78+
generated/
79+
80+
#ipython notebook
81+
.ipynb_checkpoints/
82+
83+
#vim
84+
*.swp
85+
86+
#data files
87+
*.zip
88+
*.jpg
89+
90+
# ctags
91+
.tags*
92+
93+
# Emacs temp and backup files
94+
*~
95+
\#*\#

.travis.yml

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
language: python
2+
python:
3+
- 3.6
4+
sudo: required
5+
services:
6+
- docker
7+
install:
8+
- make test-env
9+
script:
10+
- make build
11+
- make test

Dockerfile

+46
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
ARG BASE_CONTAINER=jupyter/minimal-notebook
2+
FROM $BASE_CONTAINER
3+
4+
USER root
5+
6+
# Sage pre-requisites and jq for manipulating json
7+
RUN apt-get update && \
8+
apt-get install -y --no-install-recommends \
9+
dvipng \
10+
ffmpeg \
11+
imagemagick \
12+
texlive \
13+
tk tk-dev \
14+
jq && \
15+
rm -rf /var/lib/apt/lists/*
16+
17+
18+
USER $NB_UID
19+
20+
# Install Sage conda environment
21+
RUN conda create --quiet --yes --name sage \
22+
sage python=2.7 --channel conda-forge && \
23+
conda clean --all -f -y && \
24+
npm cache clean --force && \
25+
fix-permissions $CONDA_DIR && \
26+
fix-permissions /home/$NB_USER
27+
28+
# Install sagemath kernel and extensions using conda run:
29+
# Create jupyter directories if they are missing
30+
# Add "conda run -n sage" to sage kernal using jq
31+
# Symbolically link all available kernels and extensions
32+
# but ignore errors caused by pre-existent kernel or extension conflicts
33+
RUN mkdir -p $CONDA_DIR/share/jupyter/kernels/sagemath && \
34+
mkdir -p $CONDA_DIR/share/jupyter/nbextensions && \
35+
echo ' \
36+
ln -s $SAGE_LOCAL/share/jupyter/kernels/sagemath/* $CONDA_DIR/share/jupyter/kernels/sagemath/ && \
37+
rm $CONDA_DIR/share/jupyter/kernels/sagemath/kernel.json && \
38+
cat $SAGE_LOCAL/share/jupyter/kernels/sagemath/kernel.json | \
39+
jq '"'"'.argv=[env.CONDA_DIR+"/bin/conda", "run", "-n", "sage"] + .argv '"'"' > \
40+
$CONDA_DIR/share/jupyter/kernels/sagemath/kernel.json && \
41+
sh -c "ln -s $SAGE_LOCAL/share/jupyter/kernels/*/ $CONDA_DIR/share/jupyter/kernels/ ; exit 0" && \
42+
sh -c "ln -s $SAGE_LOCAL/share/jupyter/nbextensions/*/ $CONDA_DIR/share/jupyter/nbextensions/ ; exit 0" \
43+
' | conda run -n sage sh && \
44+
fix-permissions $CONDA_DIR && \
45+
fix-permissions /home/$NB_USER
46+

Makefile

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
.PHONY: help build dev test test-env
2+
3+
# Docker image name and tag
4+
IMAGE:=sharptrick/sage-notebook
5+
TAG?=latest
6+
# Shell that make should use
7+
SHELL:=bash
8+
9+
help:
10+
# http://marmelab.com/blog/2016/02/29/auto-documented-makefile.html
11+
@grep -E '^[a-zA-Z0-9_%/-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'
12+
13+
build: DARGS?=
14+
build: ## Make the latest build of the image
15+
docker build $(DARGS) --rm --force-rm -t $(IMAGE):$(TAG) .
16+
17+
dev: ARGS?=
18+
dev: DARGS?=
19+
dev: PORT?=8888
20+
dev: ## Make a container from a tagged image image
21+
docker run -it --rm -p $(PORT):8888 $(DARGS) $(REPO) $(ARGS)
22+
23+
test: ## Make a test run against the latest image
24+
pytest tests
25+
26+
test-env: ## Make a test environment by installing test dependencies with pip
27+
pip install -r requirements-test.txt

README.md

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# sage-notebook
2+
3+
sage-notebook is a community maintained Jupyter Docker Stack image which includes the sagemath kernel.

hooks/post_push

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
#!/bin/bash
2+
3+
# Tag the latest build with the short git sha. Push the tag in addition
4+
# to the "latest" tag already pushed.
5+
GIT_SHA_TAG=${SOURCE_COMMIT:0:12}
6+
docker tag $IMAGE_NAME $DOCKER_REPO:$GIT_SHA_TAG
7+
docker push $DOCKER_REPO:$GIT_SHA_TAG

requirements-test.txt

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
docker
2+
pytest
3+
requests

tests/conftest.py

+93
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
import os
2+
3+
import docker
4+
import pytest
5+
import requests
6+
7+
from requests.packages.urllib3.util.retry import Retry
8+
from requests.adapters import HTTPAdapter
9+
10+
11+
@pytest.fixture(scope='session')
12+
def http_client():
13+
"""Requests session with retries and backoff."""
14+
s = requests.Session()
15+
retries = Retry(total=5, backoff_factor=1)
16+
s.mount('http://', HTTPAdapter(max_retries=retries))
17+
s.mount('https://', HTTPAdapter(max_retries=retries))
18+
return s
19+
20+
21+
@pytest.fixture(scope='session')
22+
def docker_client():
23+
"""Docker client configured based on the host environment"""
24+
return docker.from_env()
25+
26+
27+
@pytest.fixture(scope='session')
28+
def image_name():
29+
"""Image name to test"""
30+
return 'sharptrick/sage-notebook'
31+
32+
33+
class TrackedContainer(object):
34+
"""Wrapper that collects docker container configuration and delays
35+
container creation/execution.
36+
Parameters
37+
----------
38+
docker_client: docker.DockerClient
39+
Docker client instance
40+
image_name: str
41+
Name of the docker image to launch
42+
**kwargs: dict, optional
43+
Default keyword arguments to pass to docker.DockerClient.containers.run
44+
"""
45+
def __init__(self, docker_client, image_name, **kwargs):
46+
self.container = None
47+
self.docker_client = docker_client
48+
self.image_name = image_name
49+
self.kwargs = kwargs
50+
51+
def run(self, **kwargs):
52+
"""Runs a docker container using the preconfigured image name
53+
and a mix of the preconfigured container options and those passed
54+
to this method.
55+
Keeps track of the docker.Container instance spawned to kill it
56+
later.
57+
Parameters
58+
----------
59+
**kwargs: dict, optional
60+
Keyword arguments to pass to docker.DockerClient.containers.run
61+
extending and/or overriding key/value pairs passed to the constructor
62+
Returns
63+
-------
64+
docker.Container
65+
"""
66+
all_kwargs = {}
67+
all_kwargs.update(self.kwargs)
68+
all_kwargs.update(kwargs)
69+
self.container = self.docker_client.containers.run(self.image_name, **all_kwargs)
70+
return self.container
71+
72+
def remove(self):
73+
"""Kills and removes the tracked docker container."""
74+
if self.container:
75+
self.container.remove(force=True)
76+
77+
78+
@pytest.fixture(scope='function')
79+
def container(docker_client, image_name):
80+
"""Notebook container with initial configuration appropriate for testing
81+
(e.g., HTTP port exposed to the host for HTTP calls).
82+
Yields the container instance and kills it when the caller is done with it.
83+
"""
84+
container = TrackedContainer(
85+
docker_client,
86+
image_name,
87+
detach=True,
88+
ports={
89+
'8888/tcp': 8888
90+
}
91+
)
92+
yield container
93+
container.remove()

tests/test_job.py

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
def test_secured_server(container, http_client):
2+
"""Notebook server should eventually request user login."""
3+
container.run()
4+
resp = http_client.get('http://localhost:8888')
5+
resp.raise_for_status()
6+
assert 'login_submit' in resp.text

0 commit comments

Comments
 (0)