Skip to content

Commit 8f9801a

Browse files
miraculixxomegaml
authored andcommitted
enable jupyterhub, jupyterlab, dashserve (#60)
updates
1 parent 3558495 commit 8f9801a

File tree

23 files changed

+1160
-47
lines changed

23 files changed

+1160
-47
lines changed

Procfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
worker: celery worker --app omegaml.celeryapp -E -B --loglevel=DEBUG
2-
notebook: jupyter notebook --allow-root --ip 0.0.0.0 --no-browser $JUPYTER_PARAM
2+
notebook: jupyter notebook --allow-root --ip 0.0.0.0 --no-browser $JUPYTER_PARAM --port $JUPYTER_PORT
33
restapi: python -m omegaml.restapi

config-example.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# example user config file
2+
#OMEGA_USER_EXTENSIONS:
3+
# OMEGA_STORE_BACKENDS:
4+
# anything: omegaml.example.userext.mymodule.MyBackend
5+

docker-compose.yml

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,19 +5,47 @@ services:
55
image: omegaml/omegaml:latest
66
hostname: omegaml
77
ports:
8-
- "8899:8888"
98
- "5000:5000"
9+
- "8899:8899"
1010
links:
1111
- rabbitmq
1212
- mongodb
1313
working_dir: /app
14-
command: honcho start notebook restapi
14+
command: honcho start restapi notebook
1515
environment:
1616
- APP=omegaml
1717
- OMEGA_MONGO_URL=mongodb://mongodb/omega
1818
- OMEGA_BROKER=amqp://rabbitmq:5672//
1919
- OMEGA_FRAMEWORKS=scikit-learn,keras,tensorflow
2020
- JUPYTER_PASSWORD=sha1:24fa20fec60f:c7cd7e46afa507d484c59abeadbefa05022583b8
21+
- JUPYTER_PORT=8899
22+
- PYTHONPATH=/app/pylib
23+
volumes:
24+
- pythonlib:/app/pylib
25+
jyhub:
26+
build:
27+
context: scripts/docker/jyhub
28+
args:
29+
- pypi
30+
image: omegaml/jyhub:latest
31+
hostname: jyhub
32+
ports:
33+
- "8000:8000"
34+
- "8050-8060:8050-8060"
35+
links:
36+
- omegaml
37+
- rabbitmq
38+
- mongodb
39+
command: jupyterhub
40+
environment:
41+
- APP=omegaml
42+
- OMEGA_MONGO_URL=mongodb://mongodb/omega
43+
- OMEGA_BROKER=amqp://rabbitmq:5672//
44+
- OMEGA_FRAMEWORKS=scikit-learn,keras,tensorflow
45+
- JUPYTER_PASSWORD=sha1:24fa20fec60f:c7cd7e46afa507d484c59abeadbefa05022583b8
46+
- PYTHONPATH=/app/pylib
47+
volumes:
48+
- pythonlib:/app/pylib
2149
worker:
2250
image: omegaml/omegaml:latest
2351
hostname: worker
@@ -30,10 +58,18 @@ services:
3058
- OMEGA_MONGO_URL=mongodb://mongodb/omega
3159
- OMEGA_BROKER=amqp://rabbitmq:5672//
3260
- OMEGA_FRAMEWORKS=scikit-learn,keras,tensorflow
61+
- JY_CONTENTS_MANAGER=
3362
- C_FORCE_ROOT=yes
3463
rabbitmq:
3564
image: rabbitmq:3.7.17
3665
hostname: rabbitmq
66+
ports:
67+
- 5672:5672
3768
mongodb:
3869
image: mongo:3.6.8-stretch
3970
hostname: mongodb
71+
ports:
72+
- 27017:27017
73+
74+
volumes:
75+
pythonlib:

omegaml/client/cli/jobs.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,8 @@ def schedule(self):
145145
self.logger.info("Given this new interval, next {next_n} times would be:".format(**locals()))
146146
for time in om.jobs.Schedule.from_cron(cron_sched).next_times(int(next_n)):
147147
self.logger.info(" {}".format(time))
148-
answer = self.ask("Do you want to schedule {name} at {human_sched}?", options="Y/n", default='y')
148+
text = "Do you want to schedule {name} at {human_sched}?".format(**locals())
149+
answer = self.ask(text, options="Y/n", default='y')
149150
if answer.lower().startswith('n'):
150151
self.logger.info('Ok, not scheduled. Try again.')
151152
return

omegaml/documents.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
from omegaml.util import settings
1111

12+
1213
# default kinds of objects
1314
class MDREGISTRY:
1415
PANDAS_DFROWS = 'pandas.dfrows' # dataframe
@@ -63,6 +64,8 @@ class Metadata(Document):
6364
uri = StringField()
6465
#: created datetime
6566
created = DateTimeField(default=datetime.datetime.now)
67+
#: created datetime
68+
modified = DateTimeField(default=datetime.datetime.now)
6669
# the actual db is defined in settings, OMEGA_MONGO_URL
6770
meta = {
6871
'db_alias': 'omega',
@@ -84,6 +87,10 @@ def __unicode__(self):
8487
for k in self._fields.keys() if k in fields)
8588
return u"Metadata(%s)" % ','.join(kwargs)
8689

90+
def save(self, *args, **kwargs):
91+
self.modified = datetime.datetime.now()
92+
return super().save(*args, **kwargs)
93+
8794
return Metadata
8895

8996

omegaml/notebook/jupyter/jupyter_notebook_config.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -621,7 +621,7 @@ def setup():
621621

622622
# omegaml setup
623623
default_contents_manager = 'omegaml.notebook.omegacontentsmgr.OmegaStoreContentsManager'
624-
contents_manager = os.environ.get('JY_CONTENTS_MANAGER', default_contents_manager)
624+
contents_manager = os.environ.get('JY_CONTENTS_MANAGER') or default_contents_manager
625625
c.NotebookApp.contents_manager_class = contents_manager
626626

627627
import logging

omegaml/notebook/omegacontentsmgr.py

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,13 @@
22
from urllib.parse import unquote
33

44
import nbformat
5+
from datetime import datetime
56
from notebook.services.contents.manager import ContentsManager
67
from tornado import web
78

89
from omegaml.notebook.checkpoints import NoOpCheckpoints
910

11+
1012
class OmegaStoreContentsManager(ContentsManager):
1113
"""
1214
Jupyter notebook storage manager for omegaml
@@ -124,7 +126,7 @@ def delete_file(self, path):
124126

125127
def rename_file(self, old_path, new_path):
126128
"""
127-
rename a file
129+
rename a file
128130
129131
this is called by the contents engine to rename an entry
130132
"""
@@ -136,8 +138,9 @@ def rename_file(self, old_path, new_path):
136138
elif self.dir_exists(new_path):
137139
raise web.HTTPError(409, u'Directory already exists: %s' % new_path)
138140
# do the renaming
139-
if self.dir_exists(old_path):
140-
meta = self.omega.jobs.metadata(old_path + '/' + self._dir_placeholder)
141+
dirname = old_path + '/' + self._dir_placeholder
142+
if self.dir_exists(dirname):
143+
meta = self.omega.jobs.metadata(dirname)
141144
meta.name = new_path + '/' + self._dir_placeholder
142145
elif self.file_exists(old_path):
143146
meta = self.omega.jobs.metadata(old_path)
@@ -150,7 +153,7 @@ def exists(self, path):
150153
Does a file or dir exist at the given collection in gridFS?
151154
We do not have dir so dir_exists returns true.
152155
153-
:param path: (str) The relative path to the file's directory
156+
:param path: (str) The relative path to the file's directory
154157
(with '/' as separator)
155158
:returns exists: (boo) The relative path to the file's directory (with '/' as separator)
156159
"""
@@ -190,22 +193,22 @@ def _notebook_model(self, path, content=True):
190193
model['content'] = nb
191194
model['format'] = 'json'
192195
self.validate_notebook_model(model)
193-
# if exists already fake last modified and created timestamps
194-
# otherwise jupyter notebook will claim a newer version "on disk"
195-
if self.exists(path):
196-
from IPython.utils import tz
197-
# FIXME get actual datetime
198-
model['last_modified'] = tz.datetime(1970, 1, 1)
199-
model['created'] = tz.datetime(1970, 1, 1)
196+
# always add accurate created and modified
197+
meta = self.omega.jobs.metadata(path)
198+
if meta is not None:
199+
model['created'] = meta.created
200+
model['last_modified'] = meta.modified
201+
else:
202+
model['last_modified'] = datetime.utcnow()
203+
model['created'] = datetime.utcnow()
200204
return model
201205

202206
def _base_model(self, path, kind=None):
203207
"""Build the common base of a contents model"""
204208
# http://jupyter-notebook.readthedocs.io/en/stable/extending/contents.html
205-
from IPython.utils import tz
206209
path = unquote(path).strip('/')
207-
last_modified = tz.utcnow()
208-
created = tz.utcnow()
210+
last_modified = datetime.utcnow()
211+
created = last_modified
209212
# Create the base model.
210213
model = {}
211214
model['name'] = os.path.basename(path)

omegaml/notebook/tasks.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -86,10 +86,9 @@ def execute_scripts(self, **kwargs):
8686
# run pending jobs
8787
pending = (trigger for trigger in triggers
8888
if trigger['event-kind'] == 'scheduled' and trigger['status'] == 'PENDING')
89-
logger.debug("***** {}".format(pending))
9089
for trigger in pending:
9190
run_at = trigger['run-at']
92-
logger.info("***** {} {}".format(now, run_at))
91+
logger.info("***** now={} run_at={}".format(now, run_at))
9392
if now >= run_at:
9493
om.runtime.job(job_meta.name).run(event=trigger['event'])
9594
# immediately schedule for next time

omegaml/restapi/__main__.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1+
import os
2+
13
from omegaml.restapi.app import app
24

35
if __name__ == '__main__':
4-
app.run(host='0.0.0.0', port=5000, debug=True)
6+
port = os.environ.get('OMEGA_PORT')
7+
app.run(host='0.0.0.0', port=port, debug=True)

omegaml/tests/features/steps/notebook.py

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,16 @@ def open_jupyter(ctx):
1010
br = ctx.browser
1111
br.visit(ctx.feature.jynb_url)
1212
nb = Notebook(br)
13-
login_required = br.is_text_present('Password', wait_time=2)
14-
login_required |= br.is_text_present('token', wait_time=2)
15-
if login_required:
16-
nb.login()
13+
if br.is_text_present('JupyterHub', wait_time=10):
14+
login_required = br.is_element_present_by_id('username_input', wait_time=30)
15+
if login_required:
16+
nb.login_hub()
17+
else:
18+
# fallback to juypter notebook
19+
login_required = br.is_text_present('Password', wait_time=2)
20+
login_required |= br.is_text_present('token', wait_time=2)
21+
if login_required:
22+
nb.login_nb()
1723
nb.jupyter_home
1824

1925

@@ -57,7 +63,7 @@ def list_datasets(ctx):
5763
sleep(10)
5864
current = nb.current_cell_output()
5965
expected = "['sample']"
60-
assert current == expected, "Expected {expected}, got {current}".format(**locals)
66+
assert current == expected, "Expected {expected}, got {current}".format(**locals())
6167

6268

6369
@then(u'we can add a notebook in the folder')

0 commit comments

Comments
 (0)