Skip to content

Commit 241be4a

Browse files
author
QuantLab
authored
Merge pull request #1 from quantlabio/quantlab
0.2.0
2 parents 639e904 + 8414dd0 commit 241be4a

14 files changed

Lines changed: 1013 additions & 49 deletions

.travis.yml

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ python:
55
sudo: false
66
install:
77
- pip install setuptools pip --upgrade
8-
- pip install -e .
8+
- pip install -v -e ".[test]"
99
script:
10-
- cd
11-
- python -c "import quantlab_launcher"
10+
- py.test

quantlab_launcher/_version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
version_info = (0, 1, 2)
1+
version_info = (0, 2, 0)
22
__version__ = ".".join(map(str, version_info))

quantlab_launcher/app.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ class QuantLabLauncherApp(NotebookApp):
1818

1919
def start(self):
2020
add_handlers(self.web_app, self.quantlab_config)
21-
super.start()
21+
NotebookApp.start(self)
2222

2323

2424
main = QuantLabLauncherApp.launch_instance

quantlab_launcher/handlers.py

Lines changed: 83 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,9 @@
1010
from notebook.base.handlers import IPythonHandler, FileFindHandler
1111
from jinja2 import FileSystemLoader
1212
from notebook.utils import url_path_join as ujoin
13-
from traitlets import HasTraits, Unicode, Bool
13+
from traitlets import HasTraits, Unicode, Bool, List
1414

15+
from .settings_handler import SettingsHandler
1516

1617
#-----------------------------------------------------------------------------
1718
# Module globals
@@ -20,6 +21,11 @@
2021
HERE = os.path.dirname(__file__)
2122
FILE_LOADER = FileSystemLoader(HERE)
2223

24+
# The default paths for the application.
25+
default_static_path = r'quantlab/static/'
26+
default_settings_path = r'quantlab/api/settings/'
27+
default_themes_path = r'/quantlab/api/themes/'
28+
2329

2430
class QuantLabHandler(IPythonHandler):
2531
"""Render the QuantLab View."""
@@ -35,19 +41,19 @@ def get(self):
3541
assets_dir = config.assets_dir
3642

3743
base_url = self.settings['base_url']
38-
url = ujoin(base_url, config.page_url, '/static/')
39-
40-
bundle_files = []
41-
css_files = []
42-
for entry in ['main']:
43-
css_file = entry + '.css'
44-
if os.path.isfile(os.path.join(assets_dir, css_file)):
45-
css_files.append(ujoin(url, css_file))
46-
bundle_file = entry + '.bundle.js'
47-
if os.path.isfile(os.path.join(assets_dir, bundle_file)):
48-
bundle_files.append(ujoin(url, bundle_file))
49-
50-
if not bundle_files:
44+
js_files = set(config.static_js_urls)
45+
css_files = set(config.static_css_urls)
46+
47+
if config.assets_dir:
48+
for entry in ['main']:
49+
css_file = entry + '.css'
50+
if os.path.isfile(os.path.join(assets_dir, css_file)):
51+
css_files.add(ujoin(config.static_url, css_file))
52+
bundle_file = entry + '.bundle.js'
53+
if os.path.isfile(os.path.join(assets_dir, bundle_file)):
54+
js_files.add(ujoin(config.static_url, bundle_file))
55+
56+
if not js_files:
5157
msg = ('%s build artifacts not detected in "%s".\n' +
5258
'Please see README for build instructions.')
5359
msg = msg % (config.name, config.assets_dir)
@@ -69,6 +75,8 @@ def get(self):
6975
page_config.setdefault('appVersion', config.version)
7076
page_config.setdefault('appNamespace', config.namespace)
7177
page_config.setdefault('devMode', config.dev_mode)
78+
page_config.setdefault('settingsPath', config.settings_url)
79+
page_config.setdefault('themePath', config.themes_url);
7280
page_config.setdefault(
7381
'settingsDir', config.settings_dir.replace(os.sep, '/')
7482
)
@@ -91,9 +99,9 @@ def get(self):
9199
mathjax_url=self.mathjax_url,
92100
mathjax_config=mathjax_config,
93101
css_files=css_files,
94-
bundle_files=bundle_files,
102+
js_files=js_files,
95103
page_config=page_config,
96-
public_url=url
104+
public_url=config.static_url
97105
)
98106
self.write(self.render_template('index.html', **config))
99107

@@ -105,10 +113,10 @@ class QuantLabConfig(HasTraits):
105113
"""The quantlab application configuration object.
106114
"""
107115
settings_dir = Unicode('',
108-
help='The settings directory')
116+
help='The application settings directory')
109117

110118
assets_dir = Unicode('',
111-
help='The assets directory')
119+
help='The location of the static assets directory')
112120

113121
name = Unicode('',
114122
help='The name of the application')
@@ -125,41 +133,74 @@ class QuantLabConfig(HasTraits):
125133
page_url = Unicode('/quantlab',
126134
help='The url for the application')
127135

136+
static_url = Unicode('',
137+
help='The optional override static url for the application')
138+
139+
static_js_urls = List(Unicode(),
140+
help='The optional list of static JavaScript urls to serve')
141+
142+
static_css_urls = List(Unicode(),
143+
help='The optional list of static CSS urls to serve')
144+
128145
dev_mode = Bool(False,
129146
help='Whether the application is in dev mode')
130147

148+
settings_url = Unicode('',
149+
help='The optional override url of the settings handler')
150+
151+
schemas_dir = Unicode('',
152+
help='The location of the settings schemas directory')
153+
154+
user_settings_dir = Unicode('',
155+
help='The location of the user settings directory')
156+
157+
themes_url = Unicode('',
158+
help='The optional theme override url')
159+
160+
themes_dir = Unicode('',
161+
help='The location of the themes directory')
162+
131163

132164
def add_handlers(web_app, config):
133165
"""Add the appropriate handlers to the web app.
134166
"""
167+
# Set up the main page handler.
135168
base_url = web_app.settings['base_url']
136-
url = ujoin(base_url, config.page_url)
137-
assets_dir = config.assets_dir
138-
139-
package_file = os.path.join(assets_dir, 'package.json')
140-
with open(package_file) as fid:
141-
data = json.load(fid)
142-
143-
config.version = (config.version or data['quantlab']['version'] or
144-
data['version'])
145-
config.name = config.name or data['quantlab']['name']
146-
147169
handlers = [
148-
(url + r'/?', QuantLabHandler, {
170+
(ujoin(base_url, config.page_url, r'/?'), QuantLabHandler, {
149171
'quantlab_config': config
150-
}),
151-
(url + r"/static/(.*)", FileFindHandler, {
152-
'path': assets_dir
153-
}),
154-
172+
})
155173
]
156174

157-
# Backward compatibility.
158-
if 'publicPath' in data['quantlab']:
159-
handlers.append(
160-
(data['quantlab']['publicPath'] + r"/(.*)", FileFindHandler, {
161-
'path': assets_dir
162-
})
163-
)
175+
# Handle the static assets.
176+
if config.assets_dir and not config.static_url:
177+
config.static_url = ujoin(base_url, default_static_path)
178+
handlers.append((config.static_url + "(.*)", FileFindHandler, {
179+
'path': config.assets_dir
180+
}))
181+
182+
package_file = os.path.join(config.assets_dir, 'package.json')
183+
with open(package_file) as fid:
184+
data = json.load(fid)
185+
186+
config.version = (config.version or data['quantlab']['version'] or
187+
data['version'])
188+
config.name = config.name or data['quantlab']['name']
189+
190+
# Handle the settings.
191+
if config.schemas_dir and not config.settings_url:
192+
config.settings_url = ujoin(base_url, default_settings_path)
193+
settings_path = config.settings_url + '(?P<section_name>[\w.-]+)'
194+
handlers.append((settings_path, SettingsHandler, {
195+
'schemas_dir': config.schemas_dir,
196+
'settings_dir': config.user_settings_dir
197+
}))
198+
199+
# Handle the themes.
200+
if config.themes_dir and not config.themes_url:
201+
config.themes_url = ujoin(base_url, default_themes_path)
202+
handlers.append((ujoin(config.themes_url, "(.*)"), FileFindHandler, {
203+
'path': config.themes_dir
204+
}))
164205

165206
web_app.add_handlers(".*$", handlers)

quantlab_launcher/index.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,8 @@
2727

2828
{% block favicon %}<link rel="shortcut icon" type="image/x-icon" href="{{ base_url }}static/base/images/favicon.ico">{% endblock %}
2929

30-
{% for bundle_file in bundle_files %}
31-
<script src="{{ bundle_file }}" type="text/javascript" charset="utf-8"></script>
30+
{% for js_file in js_files %}
31+
<script src="{{ js_file }}" type="text/javascript" charset="utf-8"></script>
3232
{% endfor %}
3333

3434
{% if mathjax_url %}
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
"""Tornado handlers for frontend config storage."""
2+
3+
# Copyright (c) Jupyter Development Team.
4+
# Distributed under the terms of the Modified BSD License.
5+
import json
6+
import os
7+
from tornado import web
8+
9+
from notebook.base.handlers import APIHandler, json_errors
10+
11+
try:
12+
from jsonschema import ValidationError
13+
from jsonschema import Draft4Validator as Validator
14+
except ImportError:
15+
Validator = None
16+
17+
18+
class SettingsHandler(APIHandler):
19+
20+
def initialize(self, schemas_dir, settings_dir):
21+
self.schemas_dir = schemas_dir
22+
self.settings_dir = settings_dir
23+
24+
@json_errors
25+
@web.authenticated
26+
def get(self, section_name):
27+
self.set_header('Content-Type', "application/json")
28+
path = os.path.join(self.schemas_dir, section_name + ".json")
29+
30+
if not os.path.exists(path):
31+
raise web.HTTPError(404, "Schema not found: %r" % section_name)
32+
with open(path) as fid:
33+
# Attempt to load the schema file.
34+
try:
35+
schema = json.load(fid)
36+
except Exception as e:
37+
name = section_name
38+
message = "Failed parsing schema ({}): {}".format(name, str(e))
39+
raise web.HTTPError(500, message)
40+
41+
path = os.path.join(self.settings_dir, section_name + '.json')
42+
settings = dict()
43+
if os.path.exists(path):
44+
with open(path) as fid:
45+
# Attempt to load the settings file.
46+
try:
47+
settings = json.load(fid)
48+
except Exception as e:
49+
self.log.warn(str(e))
50+
51+
# Validate the data against the schema.
52+
if Validator is not None and len(settings):
53+
validator = Validator(schema)
54+
try:
55+
validator.validate(settings)
56+
except ValidationError as e:
57+
self.log.warn(str(e))
58+
settings = dict()
59+
60+
resp = dict(id=section_name, data=dict(user=settings), schema=schema)
61+
self.finish(json.dumps(resp))
62+
63+
@json_errors
64+
@web.authenticated
65+
def patch(self, section_name):
66+
if not self.settings_dir:
67+
raise web.HTTPError(404, "No current settings directory")
68+
69+
path = os.path.join(self.schemas_dir, section_name + '.json')
70+
71+
if not os.path.exists(path):
72+
raise web.HTTPError(404, "Schema not found for: %r" % section_name)
73+
74+
data = self.get_json_body() # Will raise 400 if content is not valid JSON
75+
76+
# Validate the data against the schema.
77+
if Validator is not None:
78+
with open(path) as fid:
79+
schema = json.load(fid)
80+
validator = Validator(schema)
81+
try:
82+
validator.validate(data)
83+
except ValidationError as e:
84+
raise web.HTTPError(400, str(e))
85+
86+
# Create the settings dir as needed.
87+
if not os.path.exists(self.settings_dir):
88+
os.makedirs(self.settings_dir)
89+
90+
path = os.path.join(self.settings_dir, section_name + '.json')
91+
92+
with open(path, 'w') as fid:
93+
json.dump(data, fid)
94+
95+
self.set_status(204)

quantlab_launcher/tests/__init__.py

Whitespace-only changes.

0 commit comments

Comments
 (0)