Skip to content
This repository was archived by the owner on Jun 7, 2023. It is now read-only.

Commit 751ce9d

Browse files
author
Brad Miller
committed
Put DB updates inside one transaction
- Mark questions with 'from_text' flag on build as they are the ones that should count towards progress
1 parent 7657240 commit 751ce9d

4 files changed

Lines changed: 105 additions & 68 deletions

File tree

runestone/__init__.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,6 @@ def runestone_extensions():
4242
basedir = os.path.dirname(__file__)
4343
module_paths = [ x for x in os.listdir(basedir) if os.path.isdir(os.path.join(basedir,x))]
4444
modules = [ 'runestone.{}'.format(x) for x in module_paths if os.path.exists('{}/__init__.py'.format(os.path.join(basedir,x)))]
45-
modules.remove('runestone.server')
4645
# Place ``runestone.common`` first, so it can run init code needed by all other modules. This assumes that the first module in the list is run first. An alternative to this to guarantee this ordering is to call ``app.setup_extension('runestone.common')`` in every extension.
4746
modules.insert(0, modules.pop(modules.index('runestone.common')))
4847
return modules

runestone/chapterdb/dbchapterinfo.py

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
from collections import OrderedDict
2222
import docutils
2323
from sqlalchemy import Table, select, and_, or_
24-
from runestone.server.componentdb import engine, meta
24+
from runestone.server.componentdb import engine, meta, sess
2525
from sphinx.util import logging
2626

2727
logger = logging.getLogger(__name__)
@@ -42,7 +42,7 @@ def update_database(chaptitles, subtitles, skips, app):
4242
When the build is completely finished output the information gathered about
4343
chapters and subchapters into the database.
4444
"""
45-
if not engine:
45+
if not sess:
4646
logger.info("You need to install a DBAPI module - psycopg2 for Postgres")
4747
logger.info("Or perhaps you have not set your DBURL environment variable")
4848
return
@@ -58,7 +58,7 @@ def update_database(chaptitles, subtitles, skips, app):
5858
cname = app.env.config.html_context.get('course_id', "unknown")
5959

6060
logger.info("Cleaning up old chapters info for {}".format(cname))
61-
engine.execute(chapters.delete().where(chapters.c.course_id == basecourse))
61+
sess.execute(chapters.delete().where(chapters.c.course_id == basecourse))
6262

6363

6464
logger.info("Populating the database with Chapter information")
@@ -70,7 +70,7 @@ def update_database(chaptitles, subtitles, skips, app):
7070
ins = chapters.insert().values(chapter_name=chaptitles.get(chap, chap),
7171
course_id=cname, chapter_label=chap,
7272
chapter_num=chapnum)
73-
res = engine.execute(ins)
73+
res = sess.execute(ins)
7474
currentRowId = res.inserted_primary_key[0]
7575
for subchapnum, sub in enumerate(subtitles[chap], start=1):
7676
if (chap,sub) in skips:
@@ -85,7 +85,7 @@ def update_database(chaptitles, subtitles, skips, app):
8585
sub_chapter_label=sub,
8686
skipreading=skipreading,
8787
sub_chapter_num=subchapnum)
88-
engine.execute(ins)
88+
sess.execute(ins)
8989
# Three possibilities:
9090
# 1) The chapter and subchapter labels match existing, but the q_name doesn't match; because you changed
9191
# heading in a file.
@@ -100,21 +100,23 @@ def update_database(chaptitles, subtitles, skips, app):
100100
questions.c.question_type == 'page',
101101
questions.c.base_course == basecourse))
102102
)
103-
res = engine.execute(sel).first()
103+
res = sess.execute(sel).first()
104104
if res and ((res.name != q_name) or (res.chapter != chap) or (res.subchapter !=sub)):
105105
# Something changed
106106
upd = questions.update().where(questions.c.id == res['id']).values(name=q_name,
107107
chapter = chap,
108+
from_source='T',
108109
subchapter = sub)
109-
engine.execute(upd)
110+
sess.execute(upd)
110111
if not res:
111112
# this is a new subchapter
112113
ins = questions.insert().values(chapter=chap, subchapter=sub,
113114
question_type='page',
115+
from_source='T',
114116
name=q_name,
115117
timestamp=datetime.datetime.now(),
116118
base_course=basecourse)
117-
engine.execute(ins)
119+
sess.execute(ins)
118120

119121

120122

runestone/server/__init__.py

Lines changed: 1 addition & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1 @@
1-
from os import environ
2-
import re
3-
4-
def get_dburl(outer={}):
5-
"""
6-
Return a nicely formatted database connection URL
7-
This function should not be used to configure a DAL db_uri for web2py that will already
8-
be configured in settings.
9-
10-
:param outer: pass locals from the calling environment
11-
:return: string
12-
"""
13-
# outer may contain the locals from the calling function
14-
# nonlocal env, settings # Python 3 only
15-
16-
if 'DBURL' in environ:
17-
return environ['DBURL']
18-
19-
if 'options' in outer:
20-
return outer['options'].build.template_args['dburl']
21-
22-
if 'env' in outer:
23-
return outer['env'].config.html_context['dburl']
24-
25-
if 'env' in globals():
26-
return globals()['env'].config.html_context['dburl']
27-
28-
ret = None
29-
if 'settings' in outer:
30-
ret = outer['settings'].database_uri
31-
32-
if 'settings' in globals():
33-
ret = globals()['settings'].database_uri.replace('postgres:','postgresql:')
34-
35-
if ret:
36-
return re.sub(r'postgres:.*/', 'postgresql:/', ret)
37-
38-
raise RuntimeError("Cannot configure a Database URL!")
1+
from .componentdb import *

runestone/server/componentdb.py

Lines changed: 94 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -20,19 +20,71 @@
2020
__author__ = 'bmiller'
2121

2222
import os
23+
from os import environ
24+
import re
2325
from sqlalchemy import create_engine, Table, MetaData, select, and_
24-
from . import get_dburl
26+
from sqlalchemy.orm.session import sessionmaker
2527
from runestone.common.runestonedirective import RunestoneDirective
2628

29+
def get_dburl(outer={}):
30+
"""
31+
Return a nicely formatted database connection URL
32+
This function should not be used to configure a DAL db_uri for web2py that will already
33+
be configured in settings.
34+
35+
:param outer: pass locals from the calling environment
36+
:return: string
37+
"""
38+
# outer may contain the locals from the calling function
39+
# nonlocal env, settings # Python 3 only
40+
41+
if 'WEB2PY_CONFIG' in environ:
42+
w2py_config = environ['WEB2PY_CONFIG']
43+
if w2py_config == 'development':
44+
return environ['DEV_DBURL']
45+
46+
if w2py_config == 'production':
47+
return environ['DBURL']
48+
49+
if w2py_config == 'test':
50+
return environ['TEST_DBURL']
51+
52+
if 'options' in outer:
53+
return outer['options'].build.template_args['dburl']
54+
55+
if 'env' in outer:
56+
return outer['env'].config.html_context['dburl']
57+
58+
if 'env' in globals():
59+
return globals()['env'].config.html_context['dburl']
60+
61+
ret = None
62+
if 'settings' in outer:
63+
ret = outer['settings'].database_uri
64+
65+
if 'settings' in globals():
66+
ret = globals()['settings'].database_uri.replace('postgres:','postgresql:')
67+
68+
if ret:
69+
return re.sub(r'postgres:.*/', 'postgresql:/', ret)
70+
71+
raise RuntimeError("Cannot configure a Database URL!")
72+
73+
2774
# create a global DB query engine to share for the rest of the file
2875
try:
2976
dburl = get_dburl()
3077
engine = create_engine(dburl, client_encoding='utf8', convert_unicode=True)
78+
Session = sessionmaker()
3179
engine.connect()
80+
Session.configure(bind=engine)
81+
sess = Session()
3282
except Exception as e: # psycopg2.OperationalError
3383
dburl = None
3484
engine = None
3585
meta = None
86+
sess = None
87+
print(e)
3688
print("Skipping all DB operations because environment variables not set up")
3789
else:
3890
# If no exceptions are raised, then set up the database.
@@ -41,6 +93,27 @@
4193
assignment_questions = Table('assignment_questions', meta, autoload=True, autoload_with=engine)
4294
courses = Table('courses', meta, autoload=True, autoload_with=engine)
4395

96+
97+
def setup(app):
98+
app.connect('env-before-read-docs', reset_questions)
99+
app.connect('build-finished', finalize_updates)
100+
101+
102+
def reset_questions(app, env, docnames):
103+
if sess:
104+
basecourse = env.config.html_context.get('basecourse')
105+
stmt = questions.update().where(and_(questions.c.base_course == basecourse,
106+
questions.c.question_type != 'page')).values(from_source='F')
107+
sess.execute(stmt)
108+
109+
def finalize_updates(app, excpt):
110+
if sess:
111+
if excpt is None:
112+
sess.commit()
113+
else:
114+
sess.rollback()
115+
116+
44117
def logSource(self):
45118
sourcelog = self.state.document.settings.env.config.html_context.get('dsource', None)
46119
if sourcelog:
@@ -87,23 +160,23 @@ def addQuestionToDB(self):
87160
id_ = self.options['divid']
88161
sel = select([questions]).where(and_(questions.c.name == id_,
89162
questions.c.base_course == basecourse))
90-
res = engine.execute(sel).first()
163+
res = sess.execute(sel).first()
91164
try:
92165
if res:
93-
stmt = questions.update().where(questions.c.id == res['id']).values(question = self.block_text, timestamp=last_changed, is_private='F', question_type=self.name, subchapter=self.subchapter, autograde=autograde, author=author,difficulty=difficulty,chapter=self.chapter, practice=practice, topic=topics)
94-
engine.execute(stmt)
166+
stmt = questions.update().where(questions.c.id == res['id']).values(question = self.block_text, timestamp=last_changed, is_private='F', question_type=self.name, subchapter=self.subchapter, autograde=autograde, author=author,difficulty=difficulty,chapter=self.chapter, practice=practice, topic=topics, from_source='T')
167+
sess.execute(stmt)
95168
else:
96-
ins = questions.insert().values(base_course=basecourse, name=id_, question=self.block_text, timestamp=last_changed, is_private='F', question_type=self.name, subchapter=self.subchapter, autograde=autograde, author=author,difficulty=difficulty,chapter=self.chapter, practice=practice, topic=topics)
169+
ins = questions.insert().values(base_course=basecourse, name=id_, question=self.block_text, timestamp=last_changed, is_private='F', question_type=self.name, subchapter=self.subchapter, autograde=autograde, author=author,difficulty=difficulty,chapter=self.chapter, practice=practice, topic=topics, from_source='T')
97170

98-
engine.execute(ins)
171+
sess.execute(ins)
99172
except UnicodeEncodeError:
100173
raise self.severe("Bad character in directive {} in {}/{}. This will not be saved to the DB".format(id_, self.chapter, self.subchapter))
101174

102175
def getQuestionID(base_course, name):
103176

104177
sel = select([questions]).where(and_(questions.c.name == name,
105178
questions.c.base_course == base_course))
106-
res = engine.execute(sel).first()
179+
res = sess.execute(sel).first()
107180
if res:
108181
return res['id']
109182
else:
@@ -114,7 +187,7 @@ def getOrInsertQuestionForPage(base_course=None, name=None, is_private='F', ques
114187

115188
sel = select([questions]).where(and_(questions.c.name == name,
116189
questions.c.base_course == base_course))
117-
res = engine.execute(sel).first()
190+
res = sess.execute(sel).first()
118191

119192
if res:
120193
id = res['id']
@@ -126,7 +199,7 @@ def getOrInsertQuestionForPage(base_course=None, name=None, is_private='F', ques
126199
author=author,
127200
difficulty=difficulty,
128201
chapter=chapter)
129-
res = engine.execute(stmt)
202+
res = sess.execute(stmt)
130203
return id
131204
else:
132205
ins = questions.insert().values(
@@ -139,14 +212,14 @@ def getOrInsertQuestionForPage(base_course=None, name=None, is_private='F', ques
139212
author=author,
140213
difficulty=difficulty,
141214
chapter=chapter)
142-
res = engine.execute(ins)
215+
res = sess.execute(ins)
143216
return res.inserted_primary_key[0]
144217

145218
def addAssignmentQuestionToDB(question_id, assignment_id, points, activities_required = 0, autograde=None, which_to_grade = None, reading_assignment=None, sorting_priority=0):
146219
# now insert or update the assignment_questions row
147220
sel = select([assignment_questions]).where(and_(assignment_questions.c.assignment_id == assignment_id,
148221
assignment_questions.c.question_id == question_id))
149-
res = engine.execute(sel).first()
222+
res = sess.execute(sel).first()
150223
vals = dict(
151224
assignment_id = assignment_id,
152225
question_id = question_id,
@@ -159,35 +232,35 @@ def addAssignmentQuestionToDB(question_id, assignment_id, points, activities_req
159232
if res:
160233
#update
161234
stmt = assignment_questions.update().where(assignment_questions.c.id == res['id']).values(**vals)
162-
engine.execute(stmt)
235+
sess.execute(stmt)
163236
else:
164237
#insert
165238
ins = assignment_questions.insert().values(**vals)
166-
engine.execute(ins)
239+
sess.execute(ins)
167240

168241
def getCourseID(coursename):
169242
sel = select([courses]).where(courses.c.course_name == coursename)
170-
res = engine.execute(sel).first()
243+
res = sess.execute(sel).first()
171244
return res['id']
172245

173246
def addAssignmentToDB(name = None, course_id = None, assignment_type_id = None, deadline = None, points = None):
174247

175248
last_changed = datetime.now()
176249
sel = select([assignments]).where(and_(assignments.c.name == name,
177250
assignments.c.course == course_id))
178-
res = engine.execute(sel).first()
251+
res = sess.execute(sel).first()
179252
if res:
180253
stmt = assignments.update().where(assignments.c.id == res['id']).values(
181254
assignment_type = assignment_type_id,
182255
duedate = deadline,
183256
points = points
184257
)
185-
engine.execute(stmt)
258+
sess.execute(stmt)
186259
a_id = res['id']
187260
# delete all existing AssignmentQuestions, so that you don't have any leftovers
188261
# this is safe because grades and comments are associated with div_ids and course_names, not assignment_questions rows.
189262
stmt2 = assignment_questions.delete().where(assignment_questions.c.assignment_id == a_id)
190-
engine.execute(stmt2)
263+
sess.execute(stmt2)
191264

192265
else:
193266
ins = assignments.insert().values(
@@ -196,7 +269,7 @@ def addAssignmentToDB(name = None, course_id = None, assignment_type_id = None,
196269
assignment_type = assignment_type_id,
197270
duedate = deadline,
198271
points = points)
199-
res = engine.execute(ins)
272+
res = sess.execute(ins)
200273
a_id = res.inserted_primary_key[0]
201274

202275
return a_id
@@ -206,12 +279,12 @@ def addHTMLToDB(divid, basecourse, htmlsrc, feedback=None):
206279
last_changed = datetime.now()
207280
sel = select([questions]).where(and_(questions.c.name == divid,
208281
questions.c.base_course == basecourse))
209-
res = engine.execute(sel).first()
282+
res = sess.execute(sel).first()
210283
try:
211284
if res:
212285
if res['htmlsrc'] != htmlsrc or res['feedback'] != feedback:
213286
stmt = questions.update().where(questions.c.id == res['id']).values(htmlsrc = htmlsrc, feedback=feedback, timestamp=last_changed)
214-
engine.execute(stmt)
287+
sess.execute(stmt)
215288
except UnicodeEncodeError:
216289
print("Bad character in directive {}".format(divid))
217290
except:
@@ -220,7 +293,7 @@ def addHTMLToDB(divid, basecourse, htmlsrc, feedback=None):
220293
def get_HTML_from_DB(divid, basecourse):
221294
sel = select([questions]).where(and_(questions.c.name == divid,
222295
questions.c.base_course == basecourse))
223-
res = engine.execute(sel).first()
296+
res = sess.execute(sel).first()
224297
if res:
225298
return res['htmlsrc']
226299
else:

0 commit comments

Comments
 (0)