Commit 5f7d3068059e65ca987be48575dae7ed02db8395
1 parent
84a1054c
Exists in
master
and in
1 other branch
- Initial support for dependency graphs.
Showing
4 changed files
with
46 additions
and
5 deletions
Show diff stats
README.md
@@ -27,7 +27,7 @@ In the end you should be able to run `pip3 --version` and `python3 -c "import sq | @@ -27,7 +27,7 @@ In the end you should be able to run `pip3 --version` and `python3 -c "import sq | ||
27 | 27 | ||
28 | Install additional python packages locally on the user area: | 28 | Install additional python packages locally on the user area: |
29 | 29 | ||
30 | - pip install --user tornado sqlalchemy pyyaml pygments markdown bcrypt | 30 | + pip install --user tornado sqlalchemy pyyaml pygments markdown bcrypt networkx |
31 | 31 | ||
32 | These are usually installed under | 32 | These are usually installed under |
33 | 33 |
app.py
@@ -8,6 +8,8 @@ try: | @@ -8,6 +8,8 @@ try: | ||
8 | import bcrypt | 8 | import bcrypt |
9 | from sqlalchemy import create_engine | 9 | from sqlalchemy import create_engine |
10 | from sqlalchemy.orm import sessionmaker | 10 | from sqlalchemy.orm import sessionmaker |
11 | + import networkx as nx | ||
12 | + import yaml | ||
11 | except ImportError: | 13 | except ImportError: |
12 | logger.critical('Python package missing. See README.md for instructions.') | 14 | logger.critical('Python package missing. See README.md for instructions.') |
13 | sys.exit(1) | 15 | sys.exit(1) |
@@ -24,10 +26,17 @@ logger = logging.getLogger(__name__) | @@ -24,10 +26,17 @@ logger = logging.getLogger(__name__) | ||
24 | # ============================================================================ | 26 | # ============================================================================ |
25 | class LearnApp(object): | 27 | class LearnApp(object): |
26 | def __init__(self): | 28 | def __init__(self): |
29 | + # online students | ||
27 | self.online = {} | 30 | self.online = {} |
28 | 31 | ||
29 | # connect to database and check registered students | 32 | # connect to database and check registered students |
30 | - db = 'students.db' # FIXME | 33 | + self.setup_db('students.db') # FIXME |
34 | + | ||
35 | + # build dependency graph | ||
36 | + self.build_dependency_graph({'uevora/lei'}) # FIXME | ||
37 | + | ||
38 | + # ------------------------------------------------------------------------ | ||
39 | + def setup_db(self, db): | ||
31 | engine = create_engine(f'sqlite:///{db}', echo=False) | 40 | engine = create_engine(f'sqlite:///{db}', echo=False) |
32 | self.Session = sessionmaker(bind=engine) | 41 | self.Session = sessionmaker(bind=engine) |
33 | try: | 42 | try: |
@@ -59,7 +68,6 @@ class LearnApp(object): | @@ -59,7 +68,6 @@ class LearnApp(object): | ||
59 | 'number': student.id, | 68 | 'number': student.id, |
60 | 'knowledge': Knowledge(), # FIXME initial state? | 69 | 'knowledge': Knowledge(), # FIXME initial state? |
61 | } | 70 | } |
62 | - # print(self.online) | ||
63 | return True | 71 | return True |
64 | 72 | ||
65 | # ------------------------------------------------------------------------ | 73 | # ------------------------------------------------------------------------ |
@@ -103,3 +111,37 @@ class LearnApp(object): | @@ -103,3 +111,37 @@ class LearnApp(object): | ||
103 | raise | 111 | raise |
104 | finally: | 112 | finally: |
105 | session.close() | 113 | session.close() |
114 | + | ||
115 | + # ------------------------------------------------------------------------ | ||
116 | + # Receives a set of topics (strings like "math/algebra"), | ||
117 | + # and recursively adds dependencies to the dependency graph | ||
118 | + def build_dependency_graph(self, topics=set()): | ||
119 | + g = nx.DiGraph() | ||
120 | + | ||
121 | + # add nodes "recursively" following the config.yaml files | ||
122 | + while topics: | ||
123 | + t = topics.pop() # take one topic | ||
124 | + if t in g.nodes(): # skip it if already in the graph | ||
125 | + continue | ||
126 | + | ||
127 | + # get configuration dictionary for this topic | ||
128 | + # dictionary has keys: type, title, depends | ||
129 | + try: | ||
130 | + with open(f'topics/{t}/config.yaml', 'r') as f: | ||
131 | + logger.info(f'Loading {t}') | ||
132 | + config = yaml.load(f) | ||
133 | + except FileNotFoundError: | ||
134 | + logger.error(f'Not found: topics/{t}/config.yaml') | ||
135 | + continue | ||
136 | + | ||
137 | + config.setdefault('depends', set()) # make sure 'depends' key exists | ||
138 | + topics.update(config['depends']) | ||
139 | + g.add_node(t, config=config) | ||
140 | + | ||
141 | + # add edges topic -> dependency | ||
142 | + for t in g.nodes(): | ||
143 | + deps = g.node[t]['config']['depends'] | ||
144 | + g.add_edges_from([(t, d) for d in deps]) | ||
145 | + | ||
146 | + self.depgraph = g | ||
147 | + logger.info(f'Graph has {g.number_of_nodes()} nodes and {g.number_of_edges()} edges') |
config/logger.yaml
@@ -5,7 +5,7 @@ formatters: | @@ -5,7 +5,7 @@ formatters: | ||
5 | void: | 5 | void: |
6 | format: '' | 6 | format: '' |
7 | standard: | 7 | standard: |
8 | - format: '%(asctime)s | %(levelname)-8s | %(name)-14s | %(message)s' | 8 | + format: '%(asctime)s | %(levelname)-8s | %(name)-8s | %(message)s' |
9 | 9 | ||
10 | handlers: | 10 | handlers: |
11 | default: | 11 | default: |
serve.py
@@ -82,7 +82,6 @@ class LoginHandler(BaseHandler): | @@ -82,7 +82,6 @@ class LoginHandler(BaseHandler): | ||
82 | def post(self): | 82 | def post(self): |
83 | uid = self.get_body_argument('uid') | 83 | uid = self.get_body_argument('uid') |
84 | pw = self.get_body_argument('pw') | 84 | pw = self.get_body_argument('pw') |
85 | - # print(f'login.post: user={uid}, pw={pw}') | ||
86 | 85 | ||
87 | if self.learn.login(uid, pw): | 86 | if self.learn.login(uid, pw): |
88 | logging.info(f'User "{uid}" login ok.') | 87 | logging.info(f'User "{uid}" login ok.') |