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.') |