Commit 5f7d3068059e65ca987be48575dae7ed02db8395

Authored by Miguel Barão
1 parent 84a1054c
Exists in master and in 1 other branch dev

- Initial support for dependency graphs.

README.md
... ... @@ -27,7 +27,7 @@ In the end you should be able to run `pip3 --version` and `python3 -c "import sq
27 27  
28 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 32 These are usually installed under
33 33  
... ...
app.py
... ... @@ -8,6 +8,8 @@ try:
8 8 import bcrypt
9 9 from sqlalchemy import create_engine
10 10 from sqlalchemy.orm import sessionmaker
  11 + import networkx as nx
  12 + import yaml
11 13 except ImportError:
12 14 logger.critical('Python package missing. See README.md for instructions.')
13 15 sys.exit(1)
... ... @@ -24,10 +26,17 @@ logger = logging.getLogger(__name__)
24 26 # ============================================================================
25 27 class LearnApp(object):
26 28 def __init__(self):
  29 + # online students
27 30 self.online = {}
28 31  
29 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 40 engine = create_engine(f'sqlite:///{db}', echo=False)
32 41 self.Session = sessionmaker(bind=engine)
33 42 try:
... ... @@ -59,7 +68,6 @@ class LearnApp(object):
59 68 'number': student.id,
60 69 'knowledge': Knowledge(), # FIXME initial state?
61 70 }
62   - # print(self.online)
63 71 return True
64 72  
65 73 # ------------------------------------------------------------------------
... ... @@ -103,3 +111,37 @@ class LearnApp(object):
103 111 raise
104 112 finally:
105 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 5 void:
6 6 format: ''
7 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 10 handlers:
11 11 default:
... ...
serve.py
... ... @@ -82,7 +82,6 @@ class LoginHandler(BaseHandler):
82 82 def post(self):
83 83 uid = self.get_body_argument('uid')
84 84 pw = self.get_body_argument('pw')
85   - # print(f'login.post: user={uid}, pw={pw}')
86 85  
87 86 if self.learn.login(uid, pw):
88 87 logging.info(f'User "{uid}" login ok.')
... ...