Skip to content

Commit 4dbd3ce

Browse files
Improved install (#94)
* Requirements file for AWE Workbench * Non-working commit / fixing requirements to work with setup.py / still need to fix -e * Subdirectories work in req.txt * awe_requirements * Missing init file. Why did this ever work? * lo_dash_react_components, hashes in setup.py * lo_dash_react_components now works after one install * Writing observer modules * Module has standard place for 3rd party libs * wo_common_student_errors copies assets to virtualenv * Dash integration is using pkg_resources, albeit not completely correctly * We now handle pip installed modules with assets * Reasonable error handling in dash_integration.py * Better error on roster data * Better auth error message * Update to supporting latest bootstrap * Writing Observer requirements in format usable from github * Extra requires for writing observer. Might not be stable. * added other common assets to downloads and addressed runtime to json encoding error * Fixed typo in module.py --------- Co-authored-by: Piotr Mitros <piotr@mitros.org>
1 parent af05118 commit 4dbd3ce

File tree

29 files changed

+268
-328
lines changed

29 files changed

+268
-328
lines changed

awe_requirements.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
AWE_SpellCorrect @ git+https://github.com/ETS-Next-Gen/AWE_SpellCorrect.git
2+
AWE_Components @ git+https://github.com/ETS-Next-Gen/AWE_Components.git
3+
AWE_Lexica @ git+https://github.com/ETS-Next-Gen/AWE_Lexica.git
4+
AWE_LanguageTool @ git+https://github.com/ETS-Next-Gen/AWE_LanguageTool.git
5+
AWE_Workbench @ git+https://github.com/ETS-Next-Gen/AWE_Workbench.git

gitserve/gitserve/__init__.py

Whitespace-only changes.

learning_observer/learning_observer/admin.py

Lines changed: 1 addition & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,16 @@
55
Views for monitoring overall system operation, and eventually, for
66
administering the system.
77
'''
8-
import copy
9-
import numbers
108
import psutil
119
import sys
1210

1311
import aiohttp
1412
import aiohttp.web
1513

16-
import dash.development.base_component
1714

1815
import learning_observer.module_loader
1916
from learning_observer.log_event import debug_log
17+
from learning_observer.util import clean_json
2018

2119
from learning_observer.auth.utils import admin
2220

@@ -72,34 +70,6 @@ def routes(app):
7270
resources.append(sinfo)
7371
return resources
7472

75-
def clean_json(json_object):
76-
'''
77-
* Deep copy a JSON object
78-
* Convert list-like objects to lists
79-
* Convert dictionary-like objects to dicts
80-
* Convert functions to string representations
81-
'''
82-
if isinstance(json_object, str):
83-
return str(json_object)
84-
if isinstance(json_object, numbers.Number):
85-
return json_object
86-
if isinstance(json_object, dict):
87-
return {key: clean_json(value) for key, value in json_object.items()}
88-
if isinstance(json_object, list) or isinstance(json_object, tuple):
89-
return [clean_json(i) for i in json_object]
90-
if isinstance(json_object, learning_observer.stream_analytics.fields.Scope):
91-
# We could make a nicer representation....
92-
return str(json_object)
93-
if callable(json_object):
94-
return str(json_object)
95-
if json_object is None:
96-
return json_object
97-
if str(type(json_object)) == "<class 'module'>":
98-
return str(json_object)
99-
if isinstance(json_object, dash.development.base_component.Component):
100-
return f"Dash Component {json_object}"
101-
raise ValueError("We don't yet handle this type in clean_json: {} (object: {})".format(type(json_object), json_object))
102-
10373
status = {
10474
"status": "Alive!",
10575
"resources": machine_resources(),

learning_observer/learning_observer/auth/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,5 +106,6 @@ def verify_auth_precheck():
106106
"mirrors the one here.\n" + \
107107
"\n" + \
108108
"If you are not planning to use Google auth (which is the case for most dev\n" + \
109-
"settings), please disable Google authentication in creds.yaml"
109+
"settings), please disable Google authentication in creds.yaml by\n" + \
110+
"removing the google_auth section under auth."
110111
raise learning_observer.prestartup.StartupCheck(error)

learning_observer/learning_observer/communication_protocol/executor.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
import learning_observer.settings
1818
import learning_observer.stream_analytics.fields
1919
import learning_observer.stream_analytics.helpers
20-
from learning_observer.util import get_nested_dict_value
20+
from learning_observer.util import get_nested_dict_value, clean_json
2121
from learning_observer.communication_protocol.exception import DAGExecutionException
2222

2323
dispatch = learning_observer.communication_protocol.query.dispatch
@@ -647,10 +647,10 @@ async def visit(node_name):
647647

648648
# Include execution history in output if operating in development settings
649649
if learning_observer.settings.RUN_MODE == learning_observer.settings.RUN_MODES.DEV:
650-
return {e: await visit(e) for e in target_nodes}
650+
return {e: clean_json(await visit(e)) for e in target_nodes}
651651

652652
# Remove execution history if in deployed settings, with data flowing back to teacher dashboards
653-
return {e: strip_provenance(await visit(e)) for e in target_nodes}
653+
return {e: clean_json(strip_provenance(await visit(e))) for e in target_nodes}
654654

655655

656656
if __name__ == "__main__":

learning_observer/learning_observer/dash_integration.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@
1313
import os.path
1414
import shutil
1515

16+
from pkg_resources import resource_filename, resource_listdir
17+
18+
1619
import dash
1720
from dash import Dash, html, clientside_callback, Output, Input
1821

@@ -121,7 +124,19 @@ def asset_paths():
121124
module = modules[m]
122125
for layout in module:
123126
if 'ASSETS' in layout:
124-
asset_path = os.path.join(layout['_BASE_PATH'], layout['ASSETS'])
127+
try:
128+
# This is a hack. We should be using resource_dir and then resource_filename.
129+
asset_path = resource_filename(m, layout['ASSETS'])
130+
except TypeError:
131+
print(
132+
f'\n\n'
133+
f'If you are seeing an odd exception in posixpath.py, then module {m}\n'
134+
'is probably not set up correctly. It is defining assets in module.py,\n'
135+
'but I cannot find them. It is probably either missing __init__.py in\n'
136+
'a directory, a MANIFEST.in for static assets, or a package_data field\n'
137+
'in setup.py (or the equivalent).\n\n')
138+
raise
139+
# OLD: os.path.join(layout['_BASE_PATH'], layout['ASSETS'])
125140
yield asset_path
126141

127142
def copy_files(source, destination):
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
'''This file contains default versions of components.
2+
3+
This is handy for:
4+
- Making changes in one place
5+
- Keeping everyone on the same version
6+
7+
URL is where we grab the component from. Note that DBC components
8+
change frequently.
9+
10+
Hashes allow us to verify file integrity / avoid man-in-the-middle
11+
attacks
12+
13+
The name is currently not used, but it'd be handy to be able to
14+
include components and component sets as lists (rather than
15+
dictionaries) and with common names.
16+
17+
tested_versions is also currently not used (although a few modules
18+
have a hacked-up version of something similar. The idea is we should
19+
be following LATEST in dev, but on servers, flag if we're running a
20+
version which we have not yet tested.
21+
'''
22+
23+
import dash_bootstrap_components as dbc
24+
import os
25+
26+
REQUIRE_JS = {
27+
"url": "https://requirejs.org/docs/release/2.3.6/comments/require.js",
28+
"hash": "d1e7687c1b2990966131bc25a761f03d6de83115512c9ce85d72e4b9819fb"
29+
"8733463fa0d93ca31e2e42ebee6e425d811e3420a788a0fc95f745aa349e3b01901",
30+
"name": "require.js" # <-- We should switch to something like this
31+
}
32+
TEXT_JS = {
33+
"url": "https://raw.githubusercontent.com/requirejs/text/"
34+
"3f9d4c19b3a1a3c6f35650c5788cbea1db93197a/text.js",
35+
"hash": "fb8974f1633f261f77220329c7070ff214241ebd33a1434f2738572608efc"
36+
"8eb6699961734285e9500bbbd60990794883981fb113319503208822e6706bca0b8"
37+
}
38+
R_JS = {
39+
"url": "https://requirejs.org/docs/release/2.3.6/r.js",
40+
"hash": "52300a8371df306f45e981fd224b10cc586365d5637a19a24e710a2fa566f"
41+
"88450b8a3920e7af47ba7197ffefa707a179bc82a407f05c08508248e6b5084f457"
42+
}
43+
BULMA_CSS = {
44+
"url": "https://cdnjs.cloudflare.com/ajax/libs/bulma/0.9.0/css/"
45+
"bulma.min.css",
46+
"hash": "ec7342883fdb6fbd4db80d7b44938951c3903d2132fc3e4bf7363c6e6dc52"
47+
"95a478c930856177ac6257c32e1d1e10a4132c6c51d194b3174dc670ab8d116b362"
48+
}
49+
FONTAWESOME_JS = {
50+
"url": "https://use.fontawesome.com/releases/v5.3.1/js/all.js",
51+
"hash": "83e7b36f1545d5abe63bea9cd3505596998aea272dd05dee624b9a2c72f96"
52+
"62618d4bff6e51fafa25d41cb59bd97f3ebd72fd94ebd09a52c17c4c23fdca3962b"
53+
}
54+
SHOWDOWN_JS = {
55+
"url": "https://rawgit.com/showdownjs/showdown/1.9.1/dist/showdown.js",
56+
"hash": "4fe14f17c2a1d0275d44e06d7e68d2b177779196c6d0c562d082eb5435eec"
57+
"4e710a625be524767aef3d9a1f6a5b88f912ddd71821f4a9df12ff7dd66d6fbb3c9"
58+
}
59+
SHOWDOWN_JS_MAP = {
60+
"url": "https://rawgit.com/showdownjs/showdown/1.9.1/dist/showdown.js.map",
61+
"hash": "74690aa3cea07fd075942ba9e98cf7297752994b93930acb3a1baa2d3042a"
62+
"62b5523d3da83177f63e6c02fe2a09c8414af9e1774dad892a303e15a86dbeb29ba"
63+
}
64+
MUSTACHE_JS = {
65+
"url": "http://cdnjs.cloudflare.com/ajax/libs/mustache.js/3.1.0/"
66+
"mustache.min.js",
67+
"hash": "e7c446dc9ac2da9396cf401774efd9bd063d25920343eaed7bee9ad878840"
68+
"e846d48204d62755aede6f51ae6f169dcc9455f45c1b86ba1b42980ccf8f241af25"
69+
}
70+
D3_V5_JS = {
71+
"url": "https://d3js.org/d3.v5.min.js",
72+
"hash": "466fe57816d719048885357cccc91a082d8e5d3796f227f88a988bf36a5c2"
73+
"ceb7a4d25842f5f3c327a0151d682e648cd9623bfdcc7a18a70ac05cfd0ec434463"
74+
}
75+
BULMA_TOOLTIP_CSS = {
76+
"url": "https://cdn.jsdelivr.net/npm/@creativebulma/bulma-tooltip@1.2.0/"
77+
"dist/bulma-tooltip.min.css",
78+
"hash": "fc37b25fa75664a6aa91627a7b1298a09025c136085f99ba31b1861f073a0"
79+
"696c4756cb156531ccf5c630154d66f3059b6b589617bd6bd711ef665079f879405"
80+
}
81+
BOOTSTRAP_MIN_CSS = {
82+
"url": dbc.themes.MINTY,
83+
"hash": {
84+
"old": "b361dc857ee7c817afa9c3370f1d317db2c4be5572dd5ec3171caeb812281"
85+
"cf900a5a9141e5d6c7069408e2615df612fbcd31094223996154e16f2f80a348532",
86+
"5.1.3": "c03f5bfd8deb11ad6cec84a6201f4327f28a640e693e56466fd80d983ed54"
87+
"16deff1548a0f6bbad013ec278b9750d1d253bd9c5bd1f53c85fcd62adba5eedc59",
88+
"5.3.1": "d099dac0135309466dc6208aaa973584843a3efbb40b2c96eb7c179f5f20f"
89+
"80def35bbc1a7a0b08c9d5bdbed6b8e780ba7d013d18e4019e04fd82a19c076a1f8"
90+
},
91+
"tested_versions": [
92+
"https://cdn.jsdelivr.net/npm/bootswatch@5.3.1/dist/minty/bootstrap.min.css",
93+
'https://cdn.jsdelivr.net/npm/bootswatch@5.1.3/dist/minty/bootstrap.min.css',
94+
]
95+
}
96+
FONTAWESOME_CSS = {
97+
"url": dbc.icons.FONT_AWESOME,
98+
"hash": {
99+
"6.1.1": "535a5f3e40bc8ddf475b56c1a39a5406052b524413dea331c4e683ca99e39"
100+
"6dbbc11fdce1f8355730a73c52ac6a1062de1938406c6af8e4361fd346106acb6b0",
101+
"6.3.0": "1496214e7421773324f4b332127ea77bec822fc6739292ebb19c6abcc22a5"
102+
"6248e0634b4e0ca0c2fcac14dc10b8d01fa17febaa35f46731201d1ffd0ab482dd7"
103+
},
104+
"tested_versions": [
105+
"https://use.fontawesome.com/releases/v6.3.0/css/all.css",
106+
"https://use.fontawesome.com/releases/v6.1.1/css/all.css"
107+
]
108+
}
109+
FONTAWESOME_WOFF2 = {
110+
"url": os.path.dirname(os.path.dirname(dbc.icons.FONT_AWESOME)) + "/webfonts/fa-solid-900.woff2",
111+
"hash": {
112+
"6.1.1": "6d3fe769cc40a5790ea2e09fb775f1bd3b130d2fdae1dd552f69559e7ca4c"
113+
"a047862f795da0024737e59e3bcc7446f6eec1bab173758aef0b97ba89d722ffbde",
114+
"6.3.0": "d50c68cd4b3312f50deb66ac8ab5c37b2d4161f4e00ea077"
115+
"326ae76769dac650dd19e65dee8d698ba2f86a69537f38cf4010ff45227211cee8b382d9b567257a"
116+
}
117+
}
118+
FONTAWESOME_TTF = {
119+
"url": os.path.dirname(os.path.dirname(dbc.icons.FONT_AWESOME)) + "/webfonts/fa-solid-900.ttf",
120+
"hash": {
121+
"6.1.1": "0fdd341671021d04304186c197001cf2e888d3028baaf9a5dec0f0e496959"
122+
"666e8a2e34aae8e79904f8e9b4c0ccae40249897cce5f5ae58d12cc1b3985e588d6",
123+
"6.3.0": "5a2c2b010a2496e4ed832ede8620f3bbfa9374778f3d63e4"
124+
"5a4aab041e174dafd9fffd3229b8b36f259cf2ef46ae7bf5cb041e280f2939884652788fc1e8ce58"
125+
}
126+
}
127+
128+
if (dbc.themes.MINTY not in BOOTSTRAP_MIN_CSS["tested_versions"]):
129+
print("WARN:: Unrecognized Minty URL detected: {}".format(dbc.themes.MINTY))
130+
print("You will need to update dash bootstrap components hash value.\n")
131+
132+
if (dbc.icons.FONT_AWESOME not in FONTAWESOME_CSS["tested_versions"]):
133+
print("WARN:: Unrecognized Fontawesome URL detected: {}".format(dbc.icons.FONT_AWESOME))
134+
print("You will need to update the FontAwesome bootstrap components hash value.\n")

learning_observer/learning_observer/rosters.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -374,11 +374,13 @@ def init():
374374
raise learning_observer.prestartup.StartupCheck(
375375
"Missing course roster files!\n"
376376
"The following are required:\t{paths}\n\n"
377-
"Please run:\n"
377+
"If you plan to use rosters from local files, please run:\n"
378378
"{commands}\n\n"
379379
"(And ideally, they'll be populated with\n"
380380
"a list of courses, and of students for\n"
381-
"those courses)".format(
381+
"those courses)\n\n"
382+
"If you plan to use other sources of roster,\n"
383+
"data please change creds.yaml instead".format(
382384
paths=", ".join(r_paths),
383385
commands="\n".join(["mkdir {path}".format(path=path) for path in r_paths])
384386
)

learning_observer/learning_observer/util.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,18 @@
99
before doing so.
1010
'''
1111

12+
import dash.development.base_component
1213
import datetime
1314
import enum
1415
import hashlib
1516
import math
17+
import numbers
1618
import re
1719
import socket
1820
from dateutil import parser
1921

22+
import learning_observer
23+
2024

2125
def paginate(data_list, nrows):
2226
'''
@@ -152,6 +156,39 @@ def get_nested_dict_value(d, key_str=None, default=MissingType.Missing):
152156
return d
153157

154158

159+
def clean_json(json_object):
160+
'''
161+
* Deep copy a JSON object
162+
* Convert list-like objects to lists
163+
* Convert dictionary-like objects to dicts
164+
* Convert functions to string representations
165+
'''
166+
if isinstance(json_object, str):
167+
return str(json_object)
168+
if isinstance(json_object, numbers.Number):
169+
return json_object
170+
if isinstance(json_object, dict):
171+
return {key: clean_json(value) for key, value in json_object.items()}
172+
if isinstance(json_object, list) or isinstance(json_object, tuple):
173+
return [clean_json(i) for i in json_object]
174+
if isinstance(json_object, learning_observer.stream_analytics.fields.Scope):
175+
# We could make a nicer representation....
176+
return str(json_object)
177+
if callable(json_object):
178+
return str(json_object)
179+
if json_object is None:
180+
return json_object
181+
if str(type(json_object)) == "<class 'module'>":
182+
return str(json_object)
183+
if str(type(json_object)) == "<class 'learning_observer.runtime.Runtime'>":
184+
return str(json_object)
185+
if isinstance(json_object, dash.development.base_component.Component):
186+
return f"Dash Component {json_object}"
187+
if isinstance(json_object, KeyError):
188+
return str(json_object)
189+
raise ValueError("We don't yet handle this type in clean_json: {} (object: {})".format(type(json_object), json_object))
190+
191+
155192
def timestamp():
156193
"""
157194
Return a timestamp string in ISO 8601 format

learning_observer/setup.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,24 @@
66
'''
77

88
from setuptools import setup
9+
import os
10+
11+
my_path = os.path.dirname(os.path.realpath(__file__))
12+
parent_path = os.path.abspath(os.path.join(my_path, os.pardir))
13+
req_path = os.path.join(parent_path, 'requirements.txt')
14+
awe_path = os.path.join(parent_path, 'awe_requirements.txt')
15+
wo_path = os.path.join(parent_path, 'wo_requirements.txt')
16+
17+
def clean_requirements(filename):
18+
file_path = os.path.join(parent_path, filename)
19+
requirements = [s.strip() for s in open(file_path).readlines() if len(s)>1]
20+
return requirements
21+
922

1023
setup(
24+
install_requires = clean_requirements(req_path),
25+
extras_require = {
26+
"wo": clean_requirements(wo_path),
27+
"awe": clean_requirements(awe_path)
28+
}
1129
)

0 commit comments

Comments
 (0)