' + _("Written by Damien Elmes, with patches, translation,\
- testing and design from:
%(cont)s") % {'cont': """Aaron Harsh, Ádám Szegi,
Alex Fraser, Andreas Klauer, Andrew Wright, Bernhard Ibertsberger, C. van Rooyen, Charlene Barina,
Christian Krause, Christian Rusche, David Smith, Dave Druelinger, Dotan Cohen,
Emilio Wuerges, Emmanuel Jarri, Frank Harper, Gregor Skumavc, H. Mijail,
diff --git a/aqt/addcards.py b/aqt/addcards.py
index 40064b6da..306bcdd3b 100644
--- a/aqt/addcards.py
+++ b/aqt/addcards.py
@@ -69,7 +69,7 @@ class AddCards(QDialog):
self.connect(self.helpButton, SIGNAL("clicked()"), self.helpRequested)
# history
b = bb.addButton(
- _("History")+ u" "+downArrow(), ar)
+ _("History")+ " "+downArrow(), ar)
if isMac:
sc = "Ctrl+Shift+H"
else:
@@ -90,8 +90,8 @@ class AddCards(QDialog):
oldNote = self.editor.note
note = self.setupNewNote(set=False)
if oldNote:
- oldFields = oldNote.keys()
- newFields = note.keys()
+ oldFields = list(oldNote.keys())
+ newFields = list(note.keys())
for n, f in enumerate(note.model()['flds']):
fieldName = f['name']
try:
diff --git a/aqt/addons.py b/aqt/addons.py
index 559d3e0c7..82183045b 100644
--- a/aqt/addons.py
+++ b/aqt/addons.py
@@ -3,7 +3,7 @@
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
import sys, os, traceback
-from cStringIO import StringIO
+from io import StringIO
import zipfile
from aqt.qt import *
from aqt.utils import showInfo, openFolder, isWin, openLink, \
@@ -73,7 +73,7 @@ class AddonManager(object):
frm = aqt.forms.editaddon.Ui_Dialog()
frm.setupUi(d)
d.setWindowTitle(os.path.basename(path))
- frm.text.setPlainText(unicode(open(path).read(), "utf8"))
+ frm.text.setPlainText(open(path).read())
d.connect(frm.buttonBox, SIGNAL("accepted()"),
lambda: self.onAcceptEdit(path, frm))
d.exec_()
diff --git a/aqt/browser.py b/aqt/browser.py
index ca94c26ce..7c7e4e3f7 100644
--- a/aqt/browser.py
+++ b/aqt/browser.py
@@ -278,10 +278,10 @@ class DataModel(QAbstractTableModel):
return a
def formatQA(self, txt):
- s = txt.replace("
", u" ")
- s = s.replace("
", u" ")
- s = s.replace("
", u" ")
- s = s.replace("\n", u" ")
+ s = txt.replace("
", " ")
+ s = s.replace("
", " ")
+ s = s.replace("
", " ")
+ s = s.replace("\n", " ")
s = re.sub("\[sound:[^]]+\]", "", s)
s = re.sub("\[\[type:[^]]+\]\]", "", s)
s = stripHTMLMedia(s)
@@ -518,7 +518,7 @@ class Browser(QMainWindow):
def onSearch(self, reset=True):
"Careful: if reset is true, the current note is saved."
- txt = unicode(self.form.searchEdit.lineEdit().text()).strip()
+ txt = str(self.form.searchEdit.lineEdit().text()).strip()
prompt = _("")
sh = self.mw.pm.profile['searchHistory']
# update search history
@@ -788,12 +788,12 @@ by clicking on one on the left."""))
if self.mw.app.keyboardModifiers() & Qt.AltModifier:
txt = "-"+txt
if self.mw.app.keyboardModifiers() & Qt.ControlModifier:
- cur = unicode(self.form.searchEdit.lineEdit().text())
+ cur = str(self.form.searchEdit.lineEdit().text())
if cur and cur != \
_(""):
txt = cur + " " + txt
elif self.mw.app.keyboardModifiers() & Qt.ShiftModifier:
- cur = unicode(self.form.searchEdit.lineEdit().text())
+ cur = str(self.form.searchEdit.lineEdit().text())
if cur:
txt = cur + " or " + txt
self.form.searchEdit.lineEdit().setText(txt)
@@ -1365,8 +1365,8 @@ update cards set usn=?, mod=?, did=? where id in """ + scids,
self.model.beginReset()
try:
changed = self.col.findReplace(sf,
- unicode(frm.find.text()),
- unicode(frm.replace.text()),
+ str(frm.find.text()),
+ str(frm.replace.text()),
frm.re.isChecked(),
field,
frm.ignoreCase.isChecked())
@@ -1679,7 +1679,7 @@ class ChangeModel(QDialog):
# check maps
fmap = self.getFieldMap()
cmap = self.getTemplateMap()
- if any(True for c in cmap.values() if c is None):
+ if any(True for c in list(cmap.values()) if c is None):
if not askUser(_("""\
Any cards mapped to nothing will be deleted. \
If a note has no remaining cards, it will be lost. \
@@ -1786,7 +1786,7 @@ class FavouritesLineEdit(QLineEdit):
self.mw = mw
self.browser = browser
# add conf if missing
- if not self.mw.col.conf.has_key('savedFilters'):
+ if 'savedFilters' not in self.mw.col.conf:
self.mw.col.conf['savedFilters'] = {}
self.button = QToolButton(self)
self.button.setStyleSheet('border: 0px;')
@@ -1818,8 +1818,8 @@ class FavouritesLineEdit(QLineEdit):
def updateButton(self, reset=True):
# If search text is a saved query, switch to the delete button.
# Otherwise show save button.
- txt = unicode(self.text()).strip()
- for key, value in self.mw.col.conf['savedFilters'].items():
+ txt = str(self.text()).strip()
+ for key, value in list(self.mw.col.conf['savedFilters'].items()):
if txt == value:
self.doSave = False
self.name = key
@@ -1835,7 +1835,7 @@ class FavouritesLineEdit(QLineEdit):
self.deleteClicked()
def saveClicked(self):
- txt = unicode(self.text()).strip()
+ txt = str(self.text()).strip()
dlg = QInputDialog(self)
dlg.setInputMode(QInputDialog.TextInput)
dlg.setLabelText(_("The current search terms will be added as a new "
diff --git a/aqt/clayout.py b/aqt/clayout.py
index 6fff4cb9e..19e427c53 100644
--- a/aqt/clayout.py
+++ b/aqt/clayout.py
@@ -32,7 +32,7 @@ class CardLayout(QDialog):
if addMode:
# save it to DB temporarily
self.emptyFields = []
- for name, val in note.items():
+ for name, val in list(note.items()):
if val.strip():
continue
self.emptyFields.append(name)
@@ -90,10 +90,10 @@ class CardLayout(QDialog):
# template area
tform = aqt.forms.template.Ui_Form()
tform.setupUi(left)
- tform.label1.setText(u" →")
- tform.label2.setText(u" →")
- tform.labelc1.setText(u" ↗")
- tform.labelc2.setText(u" ↘")
+ tform.label1.setText(" →")
+ tform.label2.setText(" →")
+ tform.labelc1.setText(" ↗")
+ tform.labelc2.setText(" ↘")
if self.style().objectName() == "gtk+":
# gtk+ requires margins in inner layout
tform.tlayout1.setContentsMargins(0, 11, 0, 0)
@@ -167,7 +167,7 @@ Please create a new card type first."""))
flip.setAutoDefault(False)
l.addWidget(flip)
c(flip, SIGNAL("clicked()"), self.onFlip)
- more = QPushButton(_("More") + u" "+downArrow())
+ more = QPushButton(_("More") + " "+downArrow())
more.setAutoDefault(False)
l.addWidget(more)
c(more, SIGNAL("clicked()"), lambda: self.onMore(more))
@@ -251,7 +251,7 @@ Please create a new card type first."""))
txt = txt.replace("
", "")
hadHR = origLen != len(txt)
def answerRepl(match):
- res = self.mw.reviewer.correct(u"exomple", u"an example")
+ res = self.mw.reviewer.correct("exomple", "an example")
if hadHR:
res = "
" + res
return res
diff --git a/aqt/deckbrowser.py b/aqt/deckbrowser.py
index acf8d24c3..e145928e6 100644
--- a/aqt/deckbrowser.py
+++ b/aqt/deckbrowser.py
@@ -63,7 +63,7 @@ class DeckBrowser(object):
def _keyHandler(self, evt):
# currently does nothing
- key = unicode(evt.text())
+ key = str(evt.text())
def _selDeck(self, did):
self.scrollPos = self.web.page().mainFrame().scrollPosition()
@@ -287,7 +287,7 @@ where id > ?""", (self.mw.col.sched.dayCutoff-86400)*1000)
return
try:
self.mw.col.decks.rename(deck, newName)
- except DeckRenameError, e:
+ except DeckRenameError as e:
return showWarning(e.description)
self.show()
@@ -304,7 +304,7 @@ where id > ?""", (self.mw.col.sched.dayCutoff-86400)*1000)
def _dragDeckOnto(self, draggedDeckDid, ontoDeckDid):
try:
self.mw.col.decks.renameForDragAndDrop(draggedDeckDid, ontoDeckDid)
- except DeckRenameError, e:
+ except DeckRenameError as e:
return showWarning(e.description)
self.show()
diff --git a/aqt/deckconf.py b/aqt/deckconf.py
index 7cd38f8ea..b595c8b3e 100644
--- a/aqt/deckconf.py
+++ b/aqt/deckconf.py
@@ -42,7 +42,7 @@ class DeckConf(QDialog):
def setupCombos(self):
import anki.consts as cs
f = self.form
- f.newOrder.addItems(cs.newCardOrderLabels().values())
+ f.newOrder.addItems(list(cs.newCardOrderLabels().values()))
self.connect(f.newOrder, SIGNAL("currentIndexChanged(int)"),
self.onNewOrderChanged)
@@ -230,7 +230,7 @@ class DeckConf(QDialog):
##################################################
def updateList(self, conf, key, w, minSize=1):
- items = unicode(w.text()).split(" ")
+ items = str(w.text()).split(" ")
ret = []
for i in items:
if not i:
diff --git a/aqt/downloader.py b/aqt/downloader.py
index b797fcb1e..f09c1f6c4 100644
--- a/aqt/downloader.py
+++ b/aqt/downloader.py
@@ -65,12 +65,12 @@ class Downloader(QThread):
try:
resp, cont = con.request(
aqt.appShared + "download/%d" % self.code)
- except Exception, e:
+ except Exception as e:
exc = traceback.format_exc()
try:
- self.error = unicode(e[0], "utf8", "ignore")
+ self.error = str(e[0])
except:
- self.error = unicode(exc, "utf8", "ignore")
+ self.error = str(exc)
return
finally:
remHook("httpRecv", recvEvent)
diff --git a/aqt/dyndeckconf.py b/aqt/dyndeckconf.py
index 1b782e44a..fce28821c 100644
--- a/aqt/dyndeckconf.py
+++ b/aqt/dyndeckconf.py
@@ -38,7 +38,7 @@ class DeckConf(QDialog):
def setupOrder(self):
import anki.consts as cs
- self.form.order.addItems(cs.dynOrderLabels().values())
+ self.form.order.addItems(list(cs.dynOrderLabels().values()))
def loadConf(self):
f = self.form
@@ -94,7 +94,7 @@ it?""")):
return " ".join([str(x) for x in l])
def userToList(self, w, minSize=1):
- items = unicode(w.text()).split(" ")
+ items = str(w.text()).split(" ")
ret = []
for i in items:
if not i:
diff --git a/aqt/editor.py b/aqt/editor.py
index 8ad50f7ce..62bd2ae01 100644
--- a/aqt/editor.py
+++ b/aqt/editor.py
@@ -3,9 +3,9 @@
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
import re
import os
-import urllib2
+import urllib.request, urllib.error, urllib.parse
import ctypes
-import urllib
+import urllib.request, urllib.parse, urllib.error
from anki.lang import _
from aqt.qt import *
@@ -18,7 +18,7 @@ from aqt.utils import shortcut, showInfo, showWarning, getBase, getFile, \
openHelp, tooltip, downArrow
import aqt
import anki.js
-from BeautifulSoup import BeautifulSoup
+from bs4 import BeautifulSoup
pics = ("jpg", "jpeg", "png", "tif", "tiff", "gif", "svg", "webp")
audio = ("wav", "mp3", "ogg", "flac", "mp4", "swf", "mov", "mpeg", "mkv", "m4a", "3gp", "spx", "oga")
@@ -407,7 +407,7 @@ class Editor(object):
runHook("setupEditorButtons", self)
def enableButtons(self, val=True):
- for b in self._buttons.values():
+ for b in list(self._buttons.values()):
b.setEnabled(val)
def disableButtons(self):
@@ -491,7 +491,7 @@ class Editor(object):
elif str.startswith("dupes"):
self.showDupes()
else:
- print str
+ print(str)
def mungeHTML(self, txt):
if txt == "
":
@@ -536,7 +536,7 @@ class Editor(object):
# will be loaded when page is ready
return
data = []
- for fld, val in self.note.items():
+ for fld, val in list(self.note.items()):
data.append((fld, self.mw.col.media.escapeImages(val)))
self.web.eval("setFields(%s, %d);" % (
json.dumps(data), field))
@@ -614,7 +614,7 @@ class Editor(object):
html = form.textEdit.toPlainText()
# filter html through beautifulsoup so we can strip out things like a
# leading
- html = unicode(BeautifulSoup(html))
+ html = str(BeautifulSoup(html, "html.parser"))
self.note.fields[self.currentField] = html
self.loadNote()
# focus field so it's saved
@@ -702,7 +702,7 @@ to a cloze type first, via Edit>Change Note Type."""))
return
# find the highest existing cloze
highest = 0
- for name, val in self.note.items():
+ for name, val in list(self.note.items()):
m = re.findall("\{\{c(\d+)::", val)
if m:
highest = max(highest, sorted([int(x) for x in m])[-1])
@@ -785,7 +785,7 @@ to a cloze type first, via Edit>Change Note Type."""))
def onRecSound(self):
try:
file = getAudio(self.widget)
- except Exception, e:
+ except Exception as e:
showWarning(_(
"Couldn't record audio. Have you installed lame and sox?") +
"\n\n" + repr(str(e)))
@@ -804,7 +804,7 @@ to a cloze type first, via Edit>Change Note Type."""))
def fnameToLink(self, fname):
ext = fname.split(".")[-1].lower()
if ext in pics:
- name = urllib.quote(fname.encode("utf8"))
+ name = urllib.parse.quote(fname.encode("utf8"))
return '

' % name
else:
anki.sound.play(fname)
@@ -837,22 +837,22 @@ to a cloze type first, via Edit>Change Note Type."""))
self.mw.progress.start(
immediate=True, parent=self.parentWindow)
try:
- req = urllib2.Request(url, None, {
+ req = urllib.request.Request(url, None, {
'User-Agent': 'Mozilla/5.0 (compatible; Anki)'})
- filecontents = urllib2.urlopen(req).read()
- except urllib2.URLError, e:
+ filecontents = urllib.request.urlopen(req).read()
+ except urllib.error.URLError as e:
showWarning(_("An error occurred while opening %s") % e)
return
finally:
self.mw.progress.finish()
- path = unicode(urllib2.unquote(url.encode("utf8")), "utf8")
+ path = urllib.parse.unquote(url)
return self.mw.col.media.writeData(path, filecontents)
# HTML filtering
######################################################################
def _filterHTML(self, html, localize=False):
- doc = BeautifulSoup(html)
+ doc = BeautifulSoup(html, "html.parser")
# remove implicit regular font style from outermost element
if doc.span:
try:
@@ -919,7 +919,7 @@ to a cloze type first, via Edit>Change Note Type."""))
for elem in "html", "head", "body", "meta":
for tag in doc(elem):
tag.replaceWithChildren()
- html = unicode(doc)
+ html = str(doc)
return html
# Advanced menu
@@ -1136,7 +1136,7 @@ class EditorWebView(AnkiWebView):
# be URL-encoded, and shouldn't be a file:// url unless they're browsing
# locally, which we don't support
def _processText(self, mime):
- txt = unicode(mime.text())
+ txt = str(mime.text())
html = None
# if the user is pasting an image or sound link, convert it to local
if self.editor.isURL(txt):
diff --git a/aqt/errors.py b/aqt/errors.py
index 1ce1421e6..d92099e07 100644
--- a/aqt/errors.py
+++ b/aqt/errors.py
@@ -21,11 +21,8 @@ class ErrorHandler(QObject):
sys.stderr = self
def write(self, data):
- # make sure we have unicode
- if not isinstance(data, unicode):
- data = unicode(data, "utf8", "replace")
# dump to stdout
- sys.stdout.write(data.encode("utf-8"))
+ sys.stdout.write(data)
# save in buffer
self.pool += data
# and update timer
diff --git a/aqt/exporting.py b/aqt/exporting.py
index 69e39e659..16caba3e8 100644
--- a/aqt/exporting.py
+++ b/aqt/exporting.py
@@ -88,7 +88,7 @@ class ExportDialog(QDialog):
deck_name = self.decks[self.frm.deck.currentIndex()]
deck_name = re.sub('[\\\\/?<>:*|"^]', '_', deck_name)
filename = os.path.join(aqt.mw.pm.base,
- u'{0}{1}'.format(deck_name, self.exporter.ext))
+ '{0}{1}'.format(deck_name, self.exporter.ext))
while 1:
file = getSaveFile(self, _("Export"), "export",
self.exporter.key, self.exporter.ext,
@@ -104,8 +104,8 @@ class ExportDialog(QDialog):
try:
f = open(file, "wb")
f.close()
- except (OSError, IOError), e:
- showWarning(_("Couldn't save file: %s") % unicode(e))
+ except (OSError, IOError) as e:
+ showWarning(_("Couldn't save file: %s") % str(e))
else:
os.unlink(file)
exportedMedia = lambda cnt: self.mw.progress.update(
diff --git a/aqt/importing.py b/aqt/importing.py
index 05a286f55..b9de69451 100644
--- a/aqt/importing.py
+++ b/aqt/importing.py
@@ -138,7 +138,7 @@ you can enter it here. Use \\t to represent tab."""),
elif d == ":":
d = _("Colon")
else:
- d = `d`
+ d = repr(d)
txt = _("Fields separated by: %s") % d
self.frm.autoDetect.setText(txt)
@@ -164,7 +164,7 @@ you can enter it here. Use \\t to represent tab."""),
except UnicodeDecodeError:
showUnicodeWarning()
return
- except Exception, e:
+ except Exception as e:
msg = _("Import failed.\n")
err = repr(str(e))
if "1-character string" in err:
@@ -172,7 +172,7 @@ you can enter it here. Use \\t to represent tab."""),
elif "invalidTempFolder" in err:
msg += self.mw.errorHandler.tempFolderMsg()
else:
- msg += unicode(traceback.format_exc(), "ascii", "replace")
+ msg += str(traceback.format_exc(), "ascii", "replace")
showText(msg)
return
finally:
@@ -268,7 +268,7 @@ def onImport(mw):
filter=filt)
if not file:
return
- file = unicode(file)
+ file = str(file)
importFile(mw, file)
def importFile(mw, file):
@@ -295,7 +295,7 @@ def importFile(mw, file):
except UnicodeDecodeError:
showUnicodeWarning()
return
- except Exception, e:
+ except Exception as e:
msg = repr(str(e))
if msg == "'unknownFormat'":
if file.endswith(".anki2"):
@@ -306,7 +306,7 @@ backup, please see the 'Backups' section of the user manual."""))
showWarning(_("Unknown file format."))
else:
msg = _("Import failed. Debugging info:\n")
- msg += unicode(traceback.format_exc(), "ascii", "replace")
+ msg += str(traceback.format_exc())
showText(msg)
return
finally:
@@ -329,7 +329,7 @@ backup, please see the 'Backups' section of the user manual."""))
importer.run()
except zipfile.BadZipfile:
showWarning(invalidZipMsg())
- except Exception, e:
+ except Exception as e:
err = repr(str(e))
if "invalidFile" in err:
msg = _("""\
@@ -342,7 +342,7 @@ Invalid file. Please restore from backup.""")
Unable to import from a read-only file."""))
else:
msg = _("Import failed.\n")
- msg += unicode(traceback.format_exc(), "ascii", "replace")
+ msg += str(traceback.format_exc())
showText(msg)
else:
log = "\n".join(importer.log)
diff --git a/aqt/main.py b/aqt/main.py
index ec4ead54c..def4f8ec4 100644
--- a/aqt/main.py
+++ b/aqt/main.py
@@ -56,7 +56,7 @@ class AnkiQt(QMainWindow):
"syncing and add-on loading."))
# were we given a file to import?
if args and args[0]:
- self.onAppMsg(unicode(args[0], sys.getfilesystemencoding(), "ignore"))
+ self.onAppMsg(args[0])
# Load profile in a timer so we can let the window finish init and not
# close on profile load error.
if isMac and qtmajor >= 5:
@@ -272,7 +272,7 @@ see the manual for how to restore from an automatic backup.
Debug info:
""")+traceback.format_exc())
self.unloadProfile()
- except Exception, e:
+ except Exception as e:
# the custom exception handler won't catch this if we immediately
# unload, so we have to manually handle it
if "invalidTempFolder" in repr(str(e)):
@@ -627,7 +627,7 @@ title="%s">%s''' % (
# run standard handler
QMainWindow.keyPressEvent(self, evt)
# check global keys
- key = unicode(evt.text())
+ key = str(evt.text())
if key == "d":
self.moveToState("deckBrowser")
elif key == "s":
@@ -1060,7 +1060,7 @@ will be lost. Continue?"""))
pp = pprint.pprint
self._captureOutput(True)
try:
- exec text
+ exec(text)
except:
self._output += traceback.format_exc()
self._captureOutput(False)
@@ -1109,7 +1109,7 @@ will be lost. Continue?"""))
return
tgt = tgt or self
for action in tgt.findChildren(QAction):
- txt = unicode(action.text())
+ txt = str(action.text())
m = re.match("^(.+)\(&.+\)(.+)?", txt)
if m:
action.setText(m.group(1) + (m.group(2) or ""))
@@ -1157,7 +1157,4 @@ Please ensure a profile is open and Anki is not busy, then try again."""),
if buf == "raise":
return
# import
- if not isinstance(buf, unicode):
- buf = unicode(buf, "utf8", "ignore")
-
self.handleImport(buf)
diff --git a/aqt/models.py b/aqt/models.py
index a3c71afa9..329a39336 100644
--- a/aqt/models.py
+++ b/aqt/models.py
@@ -7,6 +7,7 @@ from aqt.utils import showInfo, askUser, getText, maybeHideClose, openHelp
import aqt.modelchooser, aqt.clayout
from anki import stdmodels
from aqt.utils import saveGeom, restoreGeom
+import collections
class Models(QDialog):
def __init__(self, mw, parent=None, fromMain=False):
@@ -118,8 +119,8 @@ class Models(QDialog):
restoreGeom(d, "modelopts")
d.exec_()
saveGeom(d, "modelopts")
- self.model['latexPre'] = unicode(frm.latexHeader.toPlainText())
- self.model['latexPost'] = unicode(frm.latexFooter.toPlainText())
+ self.model['latexPre'] = str(frm.latexHeader.toPlainText())
+ self.model['latexPost'] = str(frm.latexFooter.toPlainText())
def saveModel(self):
self.mm.save(self.model)
@@ -127,7 +128,7 @@ class Models(QDialog):
def _tmpNote(self):
self.mm.setCurrent(self.model)
n = self.col.newNote(forDeck=False)
- for name in n.keys():
+ for name in list(n.keys()):
n[name] = "("+name+")"
try:
if "{{cloze:Text}}" in self.model['tmpls'][0]['qfmt']:
@@ -171,7 +172,7 @@ class AddModel(QDialog):
# standard models
self.models = []
for (name, func) in stdmodels.models:
- if callable(name):
+ if isinstance(name, collections.Callable):
name = name()
item = QListWidgetItem(_("Add: %s") % name)
self.dialog.models.addItem(item)
diff --git a/aqt/overview.py b/aqt/overview.py
index 2da65b55b..294c3df15 100644
--- a/aqt/overview.py
+++ b/aqt/overview.py
@@ -38,7 +38,7 @@ class Overview(object):
if self.mw.state == "overview":
tooltip(_("No cards are due yet."))
elif url == "anki":
- print "anki menu"
+ print("anki menu")
elif url == "opts":
self.mw.onDeckConf()
elif url == "cram":
@@ -64,7 +64,7 @@ class Overview(object):
def _keyHandler(self, evt):
cram = self.mw.col.decks.current()['dyn']
- key = unicode(evt.text())
+ key = str(evt.text())
if key == "o":
self.mw.onDeckConf()
if key == "r" and cram:
diff --git a/aqt/preferences.py b/aqt/preferences.py
index 379b82937..87f6df017 100644
--- a/aqt/preferences.py
+++ b/aqt/preferences.py
@@ -80,7 +80,7 @@ class Preferences(QDialog):
f.timeLimit.setValue(qc['timeLim']/60.0)
f.showEstimates.setChecked(qc['estTimes'])
f.showProgress.setChecked(qc['dueCounts'])
- f.newSpread.addItems(c.newCardSchedulingLabels().values())
+ f.newSpread.addItems(list(c.newCardSchedulingLabels().values()))
f.newSpread.setCurrentIndex(qc['newSpread'])
f.useCurrent.setCurrentIndex(int(not qc.get("addToCur", True)))
diff --git a/aqt/profiles.py b/aqt/profiles.py
index b49ef2661..900f86338 100644
--- a/aqt/profiles.py
+++ b/aqt/profiles.py
@@ -8,7 +8,7 @@
import os
import random
-import cPickle
+import pickle
import locale
import re
@@ -21,7 +21,6 @@ from aqt import appHelpSite
import aqt.forms
from send2trash import send2trash
-
metaConf = dict(
ver=0,
updates=True,
@@ -103,12 +102,13 @@ a flash drive.""" % self.base)
def profiles(self):
return sorted(x for x in
- self.db.list("select name from profiles")
- if x != "_global")
+ self.db.list("select name from profiles")
+ if x != "_global")
def load(self, name, passwd=None):
data = self.db.scalar("select cast(data as blob) from profiles where name = ?", name)
- prof = cPickle.loads(str(data))
+ # some profiles created in python2 may not decode properly
+ prof = pickle.loads(data, errors="ignore")
if prof['key'] and prof['key'] != self._pwhash(passwd):
self.name = None
return False
@@ -119,14 +119,14 @@ a flash drive.""" % self.base)
def save(self):
sql = "update profiles set data = ? where name = ?"
- self.db.execute(sql, buffer(cPickle.dumps(self.profile)), self.name)
- self.db.execute(sql, buffer(cPickle.dumps(self.meta)), "_global")
+ self.db.execute(sql, pickle.dumps(self.profile), self.name)
+ self.db.execute(sql, pickle.dumps(self.meta), "_global")
self.db.commit()
def create(self, name):
prof = profileConf.copy()
self.db.execute("insert into profiles values (?, ?)",
- name, buffer(cPickle.dumps(prof)))
+ name, pickle.dumps(prof))
self.db.commit()
def remove(self, name):
@@ -262,9 +262,9 @@ create table if not exists profiles
if not new:
# load previously created
try:
- data = self.db.scalar(
- "select cast(data as blob) from profiles where name = '_global'")
- self.meta = cPickle.loads(str(data))
+ self.meta = pickle.loads(
+ self.db.scalar(
+ "select cast(data as blob) from profiles where name = '_global'"))
return
except:
recover()
@@ -272,7 +272,7 @@ create table if not exists profiles
# create a default global profile
self.meta = metaConf.copy()
self.db.execute("insert or replace into profiles values ('_global', ?)",
- buffer(cPickle.dumps(metaConf)))
+ pickle.dumps(metaConf))
self._setDefaultLang()
return True
@@ -281,16 +281,16 @@ create table if not exists profiles
if self.firstRun:
self.create(_("User 1"))
p = os.path.join(self.base, "README.txt")
- open(p, "w").write((_("""\
+ open(p, "w").write(_("""\
This folder stores all of your Anki data in a single location,
to make backups easy. To tell Anki to use a different location,
please see:
%s
-""") % (appHelpSite + "#startupopts")).encode("utf8"))
+""") % (appHelpSite + "#startupopts"))
def _pwhash(self, passwd):
- return checksum(unicode(self.meta['id'])+unicode(passwd))
+ return checksum(str(self.meta['id'])+str(passwd))
# Default language
######################################################################
@@ -299,8 +299,8 @@ please see:
def _setDefaultLang(self):
# the dialog expects _ to be defined, but we're running before
# setupLang() has been called. so we create a dummy op for now
- import __builtin__
- __builtin__.__dict__['_'] = lambda x: x
+ import builtins
+ builtins.__dict__['_'] = lambda x: x
# create dialog
class NoCloseDiag(QDialog):
def reject(self):
@@ -350,6 +350,6 @@ please see:
def setLang(self, code):
self.meta['defaultLang'] = code
sql = "update profiles set data = ? where name = ?"
- self.db.execute(sql, buffer(cPickle.dumps(self.meta)), "_global")
+ self.db.execute(sql, pickle.dumps(self.meta), "_global")
self.db.commit()
anki.lang.setLang(code, local=False)
diff --git a/aqt/progress.py b/aqt/progress.py
index b14b50aab..b50c2647a 100644
--- a/aqt/progress.py
+++ b/aqt/progress.py
@@ -32,8 +32,8 @@ class ProgressManager(object):
try:
db.set_progress_handler(self._dbProgress, 10000)
except:
- print """\
-Your pysqlite2 is too old. Anki will appear frozen during long operations."""
+ print("""\
+Your pysqlite2 is too old. Anki will appear frozen during long operations.""")
def _dbProgress(self):
"Called from SQLite."
diff --git a/aqt/reviewer.py b/aqt/reviewer.py
index 9bc84a252..8eeec0bab 100644
--- a/aqt/reviewer.py
+++ b/aqt/reviewer.py
@@ -2,12 +2,11 @@
# Copyright: Damien Elmes
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
-from __future__ import division
import difflib
import re
import cgi
import unicodedata as ucd
-import HTMLParser
+import html.parser
from anki.lang import _, ngettext
from aqt.qt import *
@@ -285,7 +284,7 @@ The front of this card is empty. Please run Tools>Empty Cards.""")
self.bottom.web.eval("py.link('ans');")
def _keyHandler(self, evt):
- key = unicode(evt.text())
+ key = str(evt.text())
if key == "e":
self.mw.onEditCurrent()
elif (key == " " or evt.key() in (Qt.Key_Return, Qt.Key_Enter)):
@@ -409,12 +408,12 @@ Please run Tools>Empty Cards""")
buf = buf.replace("
", "")
hadHR = len(buf) != origSize
# munge correct value
- parser = HTMLParser.HTMLParser()
+ parser = html.parser.HTMLParser()
cor = stripHTML(self.mw.col.media.strip(self.typeCorrect))
# ensure we don't chomp multiple whitespace
cor = cor.replace(" ", " ")
cor = parser.unescape(cor)
- cor = cor.replace(u"\xa0", " ")
+ cor = cor.replace("\xa0", " ")
given = self.typedAnswer
# compare with typed answer
res = self.correct(given, cor, showBad=False)
diff --git a/aqt/sync.py b/aqt/sync.py
index 13696360d..691f113f3 100644
--- a/aqt/sync.py
+++ b/aqt/sync.py
@@ -1,7 +1,6 @@
# Copyright: Damien Elmes
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
-from __future__ import division
import socket
import time
import traceback
@@ -324,8 +323,6 @@ class SyncThread(QThread):
self._sync()
except:
err = traceback.format_exc()
- if not isinstance(err, unicode):
- err = unicode(err, "utf8", "replace")
self.fireEvent("error", err)
finally:
# don't bump mod time unless we explicitly save
@@ -348,7 +345,7 @@ class SyncThread(QThread):
# run sync and check state
try:
ret = self.client.sync()
- except Exception, e:
+ except Exception as e:
log = traceback.format_exc()
err = repr(str(e))
if ("Unable to find the server" in err or
@@ -357,8 +354,6 @@ class SyncThread(QThread):
else:
if not err:
err = log
- if not isinstance(err, unicode):
- err = unicode(err, "utf8", "replace")
self.fireEvent("error", err)
return
if ret == "badAuth":
@@ -429,22 +424,21 @@ class SyncThread(QThread):
######################################################################
CHUNK_SIZE = 65536
-import httplib, httplib2
-from cStringIO import StringIO
+import http.client, httplib2
+from io import StringIO
from anki.hooks import runHook
# sending in httplib
def _incrementalSend(self, data):
+ print("fixme: _incrementalSend needs updating for python3")
"""Send `data' to the server."""
if self.sock is None:
if self.auto_open:
self.connect()
else:
- raise httplib.NotConnected()
+ raise http.client.NotConnected()
# if it's not a file object, make it one
if not hasattr(data, 'read'):
- if isinstance(data, unicode):
- data = data.encode("utf8")
data = StringIO(data)
while 1:
block = data.read(CHUNK_SIZE)
@@ -453,7 +447,7 @@ def _incrementalSend(self, data):
self.sock.sendall(block)
runHook("httpSend", len(block))
-httplib.HTTPConnection.send = _incrementalSend
+http.client.HTTPConnection.send = _incrementalSend
# receiving in httplib2
# this is an augmented version of httplib's request routine that:
@@ -461,6 +455,7 @@ httplib.HTTPConnection.send = _incrementalSend
# - calls a hook for each chunk of data so we can update the gui
# - retries only when keep-alive connection is closed
def _conn_request(self, conn, request_uri, method, body, headers):
+ print("fixme: _conn_request updating for python3")
for i in range(2):
try:
if conn.sock is None:
@@ -475,20 +470,20 @@ def _conn_request(self, conn, request_uri, method, body, headers):
except httplib2.ssl_SSLError:
conn.close()
raise
- except socket.error, e:
+ except socket.error as e:
conn.close()
raise
- except httplib.HTTPException:
+ except http.client.HTTPException:
conn.close()
raise
try:
response = conn.getresponse()
- except httplib.BadStatusLine:
- print "retry bad line"
+ except http.client.BadStatusLine:
+ print("retry bad line")
conn.close()
conn.connect()
continue
- except (socket.error, httplib.HTTPException):
+ except (socket.error, http.client.HTTPException):
raise
else:
content = ""
diff --git a/aqt/tagedit.py b/aqt/tagedit.py
index c6987714f..836730543 100644
--- a/aqt/tagedit.py
+++ b/aqt/tagedit.py
@@ -68,10 +68,10 @@ class TagCompleter(QCompleter):
self.cursor = None
def splitPath(self, str):
- str = unicode(str).strip()
+ str = str(str).strip()
str = re.sub(" +", " ", str)
self.tags = self.edit.col.tags.split(str)
- self.tags.append(u"")
+ self.tags.append("")
p = self.edit.cursorPosition()
self.cursor = str.count(" ", 0, p)
return [self.tags[self.cursor]]
@@ -80,9 +80,9 @@ class TagCompleter(QCompleter):
if self.cursor is None:
return self.edit.text()
ret = QCompleter.pathFromIndex(self, idx)
- self.tags[self.cursor] = unicode(ret)
+ self.tags[self.cursor] = str(ret)
try:
- self.tags.remove(u"")
+ self.tags.remove("")
except ValueError:
pass
return " ".join(self.tags)
diff --git a/aqt/update.py b/aqt/update.py
index b31fd452c..7a32f0989 100644
--- a/aqt/update.py
+++ b/aqt/update.py
@@ -1,8 +1,8 @@
# Copyright: Damien Elmes
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
-import urllib
-import urllib2
+import urllib.request, urllib.parse, urllib.error
+import urllib.request, urllib.error, urllib.parse
import time
from aqt.qt import *
@@ -32,9 +32,9 @@ class LatestVersionFinder(QThread):
return
d = self._data()
d['proto'] = 1
- d = urllib.urlencode(d)
+ d = urllib.parse.urlencode(d)
try:
- f = urllib2.urlopen(aqt.appUpdate, d)
+ f = urllib.request.urlopen(aqt.appUpdate, d)
resp = f.read()
if not resp:
return
diff --git a/aqt/utils.py b/aqt/utils.py
index 0f73514fa..e193b6af0 100644
--- a/aqt/utils.py
+++ b/aqt/utils.py
@@ -3,7 +3,7 @@
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
from aqt.qt import *
-import re, os, sys, urllib, subprocess
+import re, os, sys, urllib.request, urllib.parse, urllib.error, subprocess
import aqt
from anki.sound import stripSounds
from anki.utils import isWin, isMac, invalidFilename
@@ -143,7 +143,7 @@ def askUserDialog(text, buttons, parent=None, help="", title="Anki"):
class GetTextDialog(QDialog):
- def __init__(self, parent, question, help=None, edit=None, default=u"", \
+ def __init__(self, parent, question, help=None, edit=None, default="", \
title="Anki", minWidth=400):
QDialog.__init__(self, parent)
self.setWindowTitle(title)
@@ -183,21 +183,21 @@ class GetTextDialog(QDialog):
def helpRequested(self):
openHelp(self.help)
-def getText(prompt, parent=None, help=None, edit=None, default=u"", title="Anki"):
+def getText(prompt, parent=None, help=None, edit=None, default="", title="Anki"):
if not parent:
parent = aqt.mw.app.activeWindow() or aqt.mw
d = GetTextDialog(parent, prompt, help=help, edit=edit,
default=default, title=title)
d.setWindowModality(Qt.WindowModal)
ret = d.exec_()
- return (unicode(d.l.text()), ret)
+ return (str(d.l.text()), ret)
def getOnlyText(*args, **kwargs):
(s, r) = getText(*args, **kwargs)
if r:
return s
else:
- return u""
+ return ""
# fixme: these utilities could be combined into a single base class
def chooseList(prompt, choices, startrow=0, parent=None):
@@ -251,7 +251,7 @@ def getFile(parent, title, cb, filter="*.*", dir=None, key=None):
def accept():
# work around an osx crash
#aqt.mw.app.processEvents()
- file = unicode(list(d.selectedFiles())[0])
+ file = str(list(d.selectedFiles())[0])
if dirkey:
dir = os.path.dirname(file)
aqt.mw.pm.profile[dirkey] = dir
@@ -268,8 +268,8 @@ def getSaveFile(parent, title, dir_description, key, ext, fname=None):
config_key = dir_description + 'Directory'
base = aqt.mw.pm.profile.get(config_key, aqt.mw.pm.base)
path = os.path.join(base, fname)
- file = unicode(QFileDialog.getSaveFileName(
- parent, title, path, u"{0} (*{1})".format(key, ext),
+ file = str(QFileDialog.getSaveFileName(
+ parent, title, path, "{0} (*{1})".format(key, ext),
options=QFileDialog.DontConfirmOverwrite))
if file:
# add extension
@@ -350,18 +350,16 @@ def getBase(col):
base = None
mdir = col.media.dir()
if isWin and not mdir.startswith("\\\\"):
- prefix = u"file:///"
+ prefix = "file:///"
else:
- prefix = u"file://"
+ prefix = "file://"
mdir = mdir.replace("\\", "/")
- base = prefix + unicode(
- urllib.quote(mdir.encode("utf-8")),
- "utf-8") + "/"
+ base = prefix + urllib.parse.quote(mdir) + "/"
return '' % base
def openFolder(path):
if isWin:
- if isinstance(path, unicode):
+ if isinstance(path, str):
path = path.encode(sys.getfilesystemencoding())
subprocess.Popen(["explorer", path])
else:
@@ -387,9 +385,9 @@ def addCloseShortcut(widg):
def downArrow():
if isWin:
- return u"▼"
+ return "▼"
# windows 10 is lacking the smaller arrow on English installs
- return u"▾"
+ return "▾"
# Tooltips
######################################################################
diff --git a/aqt/webview.py b/aqt/webview.py
index 1cb7810d0..c9f40ff71 100644
--- a/aqt/webview.py
+++ b/aqt/webview.py
@@ -15,10 +15,10 @@ import anki.js
class Bridge(QObject):
@pyqtSlot(str, result=str)
def run(self, str):
- return unicode(self._bridge(unicode(str)))
+ return self._bridge(str)
@pyqtSlot(str)
def link(self, str):
- self._linkHandler(unicode(str))
+ self._linkHandler(str)
def setBridge(self, func):
self._bridge = func
def setLinkHandler(self, func):
@@ -146,7 +146,7 @@ button {
def _jsErr(self, msg, line, srcID):
sys.stdout.write(
(_("JS error on line %(a)d: %(b)s") %
- dict(a=line, b=msg+"\n")).encode("utf8"))
+ dict(a=line, b=msg+"\n")))
def _linkHandler(self, url):
self.linkHandler(url.toString())
diff --git a/tests/shared.py b/tests/shared.py
index b081b0cbe..e9b269c98 100644
--- a/tests/shared.py
+++ b/tests/shared.py
@@ -37,6 +37,6 @@ def getUpgradeDeckPath(name="anki12.anki"):
src = os.path.join(testDir, "support", name)
(fd, dst) = tempfile.mkstemp(suffix=".anki2")
shutil.copy(src, dst)
- return unicode(dst, "utf8")
+ return dst
testDir = os.path.dirname(__file__)
diff --git a/tests/test_cards.py b/tests/test_cards.py
index 16a47982a..5731228fd 100644
--- a/tests/test_cards.py
+++ b/tests/test_cards.py
@@ -5,8 +5,8 @@ from tests.shared import getEmptyCol
def test_previewCards():
deck = getEmptyCol()
f = deck.newNote()
- f['Front'] = u'1'
- f['Back'] = u'2'
+ f['Front'] = '1'
+ f['Back'] = '2'
# non-empty and active
cards = deck.previewCards(f, 0)
assert len(cards) == 1
@@ -25,8 +25,8 @@ def test_previewCards():
def test_delete():
deck = getEmptyCol()
f = deck.newNote()
- f['Front'] = u'1'
- f['Back'] = u'2'
+ f['Front'] = '1'
+ f['Back'] = '2'
deck.addNote(f)
cid = f.cards()[0].id
deck.reset()
@@ -41,8 +41,8 @@ def test_delete():
def test_misc():
d = getEmptyCol()
f = d.newNote()
- f['Front'] = u'1'
- f['Back'] = u'2'
+ f['Front'] = '1'
+ f['Back'] = '2'
d.addNote(f)
c = f.cards()[0]
id = d.models.current()['id']
@@ -51,8 +51,8 @@ def test_misc():
def test_genrem():
d = getEmptyCol()
f = d.newNote()
- f['Front'] = u'1'
- f['Back'] = u''
+ f['Front'] = '1'
+ f['Back'] = ''
d.addNote(f)
assert len(f.cards()) == 1
m = d.models.current()
@@ -80,7 +80,7 @@ def test_gendeck():
cloze = d.models.byName("Cloze")
d.models.setCurrent(cloze)
f = d.newNote()
- f['Text'] = u'{{c1::one}}'
+ f['Text'] = '{{c1::one}}'
d.addNote(f)
assert d.cardCount() == 1
assert f.cards()[0].did == 1
@@ -89,11 +89,11 @@ def test_gendeck():
cloze['did'] = newId
d.models.save(cloze)
# a newly generated card should share the first card's deck
- f['Text'] += u'{{c2::two}}'
+ f['Text'] += '{{c2::two}}'
f.flush()
assert f.cards()[1].did == 1
# and same with multiple cards
- f['Text'] += u'{{c3::three}}'
+ f['Text'] += '{{c3::three}}'
f.flush()
assert f.cards()[2].did == 1
# if one of the cards is in a different deck, it should revert to the
@@ -101,7 +101,7 @@ def test_gendeck():
c = f.cards()[1]
c.did = newId
c.flush()
- f['Text'] += u'{{c4::four}}'
+ f['Text'] += '{{c4::four}}'
f.flush()
assert f.cards()[3].did == newId
diff --git a/tests/test_collection.py b/tests/test_collection.py
index b1db640ef..2b6415dec 100644
--- a/tests/test_collection.py
+++ b/tests/test_collection.py
@@ -37,14 +37,14 @@ def test_openReadOnly():
os.chmod(newPath, 0)
assertException(Exception,
lambda: aopen(newPath))
- os.chmod(newPath, 0666)
+ os.chmod(newPath, 0o666)
os.unlink(newPath)
def test_noteAddDelete():
deck = getEmptyCol()
# add a note
f = deck.newNote()
- f['Front'] = u"one"; f['Back'] = u"two"
+ f['Front'] = "one"; f['Back'] = "two"
n = deck.addNote(f)
assert n == 1
# test multiple cards - add another template
@@ -62,7 +62,7 @@ def test_noteAddDelete():
assert deck.cardCount() == 2
# creating new notes should use both cards
f = deck.newNote()
- f['Front'] = u"three"; f['Back'] = u"four"
+ f['Front'] = "three"; f['Back'] = "four"
n = deck.addNote(f)
assert n == 2
assert deck.cardCount() == 4
@@ -73,7 +73,7 @@ def test_noteAddDelete():
assert not f.dupeOrEmpty()
# now let's make a duplicate
f2 = deck.newNote()
- f2['Front'] = u"one"; f2['Back'] = u""
+ f2['Front'] = "one"; f2['Back'] = ""
assert f2.dupeOrEmpty()
# empty first field should not be permitted either
f2['Front'] = " "
@@ -82,12 +82,12 @@ def test_noteAddDelete():
def test_fieldChecksum():
deck = getEmptyCol()
f = deck.newNote()
- f['Front'] = u"new"; f['Back'] = u"new2"
+ f['Front'] = "new"; f['Back'] = "new2"
deck.addNote(f)
assert deck.db.scalar(
"select csum from notes") == int("c2a6b03f", 16)
# changing the val should change the checksum
- f['Front'] = u"newx"
+ f['Front'] = "newx"
f.flush()
assert deck.db.scalar(
"select csum from notes") == int("302811ae", 16)
@@ -95,10 +95,10 @@ def test_fieldChecksum():
def test_addDelTags():
deck = getEmptyCol()
f = deck.newNote()
- f['Front'] = u"1"
+ f['Front'] = "1"
deck.addNote(f)
f2 = deck.newNote()
- f2['Front'] = u"2"
+ f2['Front'] = "2"
deck.addNote(f2)
# adding for a given id
deck.tags.bulkAdd([f.id], "foo")
diff --git a/tests/test_decks.py b/tests/test_decks.py
index efe027e2f..f127688f4 100644
--- a/tests/test_decks.py
+++ b/tests/test_decks.py
@@ -46,7 +46,7 @@ def test_remove():
# create a new deck, and add a note/card to it
g1 = deck.decks.id("g1")
f = deck.newNote()
- f['Front'] = u"1"
+ f['Front'] = "1"
f.model()['did'] = g1
deck.addNote(f)
c = f.cards()[0]
@@ -92,7 +92,7 @@ def test_renameForDragAndDrop():
d = getEmptyCol()
def deckNames():
- return [ name for name in sorted(d.decks.allNames()) if name <> u'Default' ]
+ return [ name for name in sorted(d.decks.allNames()) if name != 'Default' ]
languages_did = d.decks.id('Languages')
chinese_did = d.decks.id('Chinese')
diff --git a/tests/test_exporting.py b/tests/test_exporting.py
index 63d950513..61c417356 100644
--- a/tests/test_exporting.py
+++ b/tests/test_exporting.py
@@ -4,7 +4,7 @@ import nose, os, tempfile
from anki import Collection as aopen
from anki.exporting import *
from anki.importing import Anki2Importer
-from shared import getEmptyCol
+from .shared import getEmptyCol
deck = None
ds = None
@@ -14,11 +14,11 @@ def setup1():
global deck
deck = getEmptyCol()
f = deck.newNote()
- f['Front'] = u"foo"; f['Back'] = u"bar"; f.tags = ["tag", "tag2"]
+ f['Front'] = "foo"; f['Back'] = "bar"; f.tags = ["tag", "tag2"]
deck.addNote(f)
# with a different deck
f = deck.newNote()
- f['Front'] = u"baz"; f['Back'] = u"qux"
+ f['Front'] = "baz"; f['Back'] = "qux"
f.model()['did'] = deck.decks.id("new deck")
deck.addNote(f)
@@ -37,7 +37,7 @@ def test_export_anki():
# export
e = AnkiExporter(deck)
fd, newname = tempfile.mkstemp(prefix="ankitest", suffix=".anki2")
- newname = unicode(newname)
+ newname = str(newname)
os.close(fd)
os.unlink(newname)
e.exportInto(newname)
@@ -57,7 +57,7 @@ def test_export_anki():
assert dobj['conf'] == 1
# try again, limited to a deck
fd, newname = tempfile.mkstemp(prefix="ankitest", suffix=".anki2")
- newname = unicode(newname)
+ newname = str(newname)
os.close(fd)
os.unlink(newname)
e.did = 1
@@ -68,13 +68,13 @@ def test_export_anki():
@nose.with_setup(setup1)
def test_export_ankipkg():
# add a test file to the media folder
- open(os.path.join(deck.media.dir(), u"今日.mp3"), "w").write("test")
+ open(os.path.join(deck.media.dir(), "今日.mp3"), "w").write("test")
n = deck.newNote()
- n['Front'] = u'[sound:今日.mp3]'
+ n['Front'] = '[sound:今日.mp3]'
deck.addNote(n)
e = AnkiPackageExporter(deck)
fd, newname = tempfile.mkstemp(prefix="ankitest", suffix=".apkg")
- newname = unicode(newname)
+ newname = str(newname)
os.close(fd)
os.unlink(newname)
e.exportInto(newname)
@@ -83,7 +83,7 @@ def test_export_ankipkg():
def test_export_anki_due():
deck = getEmptyCol()
f = deck.newNote()
- f['Front'] = u"foo"
+ f['Front'] = "foo"
deck.addNote(f)
deck.crt -= 86400*10
deck.sched.reset()
@@ -99,7 +99,7 @@ def test_export_anki_due():
e = AnkiExporter(deck)
e.includeSched = True
fd, newname = tempfile.mkstemp(prefix="ankitest", suffix=".anki2")
- newname = unicode(newname)
+ newname = str(newname)
os.close(fd)
os.unlink(newname)
e.exportInto(newname)
@@ -124,7 +124,7 @@ def test_export_anki_due():
def test_export_textnote():
e = TextNoteExporter(deck)
fd, f = tempfile.mkstemp(prefix="ankitest")
- f = unicode(f)
+ f = str(f)
os.close(fd)
os.unlink(f)
e.exportInto(f)
diff --git a/tests/test_find.py b/tests/test_find.py
index 42058f3f1..8a1539bb0 100644
--- a/tests/test_find.py
+++ b/tests/test_find.py
@@ -22,21 +22,21 @@ def test_parse():
def test_findCards():
deck = getEmptyCol()
f = deck.newNote()
- f['Front'] = u'dog'
- f['Back'] = u'cat'
- f.tags.append(u"monkey")
+ f['Front'] = 'dog'
+ f['Back'] = 'cat'
+ f.tags.append("monkey")
f1id = f.id
deck.addNote(f)
firstCardId = f.cards()[0].id
f = deck.newNote()
- f['Front'] = u'goats are fun'
- f['Back'] = u'sheep'
- f.tags.append(u"sheep goat horse")
+ f['Front'] = 'goats are fun'
+ f['Back'] = 'sheep'
+ f.tags.append("sheep goat horse")
deck.addNote(f)
f2id = f.id
f = deck.newNote()
- f['Front'] = u'cat'
- f['Back'] = u'sheep'
+ f['Front'] = 'cat'
+ f['Back'] = 'sheep'
deck.addNote(f)
catCard = f.cards()[0]
m = deck.models.current(); mm = deck.models
@@ -46,8 +46,8 @@ def test_findCards():
mm.addTemplate(m, t)
mm.save(m)
f = deck.newNote()
- f['Front'] = u'test'
- f['Back'] = u'foo bar'
+ f['Front'] = 'test'
+ f['Back'] = 'foo bar'
deck.addNote(f)
latestCardIds = [c.id for c in f.cards()]
# tag searches
@@ -131,8 +131,8 @@ def test_findCards():
assert len(deck.findCards("deck:*cefault")) == 0
# full search
f = deck.newNote()
- f['Front'] = u'helloworld'
- f['Back'] = u'abc'
+ f['Front'] = 'helloworld'
+ f['Back'] = 'abc'
deck.addNote(f)
# as it's the sort field, it matches
assert len(deck.findCards("helloworld")) == 2
@@ -195,8 +195,8 @@ def test_findCards():
# empty field
assert len(deck.findCards("front:")) == 0
f = deck.newNote()
- f['Front'] = u''
- f['Back'] = u'abc2'
+ f['Front'] = ''
+ f['Back'] = 'abc2'
assert deck.addNote(f) == 1
assert len(deck.findCards("front:")) == 1
# OR searches and nesting
@@ -218,12 +218,12 @@ def test_findCards():
def test_findReplace():
deck = getEmptyCol()
f = deck.newNote()
- f['Front'] = u'foo'
- f['Back'] = u'bar'
+ f['Front'] = 'foo'
+ f['Back'] = 'bar'
deck.addNote(f)
f2 = deck.newNote()
- f2['Front'] = u'baz'
- f2['Back'] = u'foo'
+ f2['Front'] = 'baz'
+ f2['Back'] = 'foo'
deck.addNote(f2)
nids = [f.id, f2.id]
# should do nothing
@@ -245,20 +245,20 @@ def test_findReplace():
def test_findDupes():
deck = getEmptyCol()
f = deck.newNote()
- f['Front'] = u'foo'
- f['Back'] = u'bar'
+ f['Front'] = 'foo'
+ f['Back'] = 'bar'
deck.addNote(f)
f2 = deck.newNote()
- f2['Front'] = u'baz'
- f2['Back'] = u'bar'
+ f2['Front'] = 'baz'
+ f2['Back'] = 'bar'
deck.addNote(f2)
f3 = deck.newNote()
- f3['Front'] = u'quux'
- f3['Back'] = u'bar'
+ f3['Front'] = 'quux'
+ f3['Back'] = 'bar'
deck.addNote(f3)
f4 = deck.newNote()
- f4['Front'] = u'quuux'
- f4['Back'] = u'nope'
+ f4['Front'] = 'quuux'
+ f4['Back'] = 'nope'
deck.addNote(f4)
r = deck.findDupes("Back")
assert r[0][0] == "bar"
diff --git a/tests/test_importing.py b/tests/test_importing.py
index 4f7bf77f1..15eb7d0fb 100644
--- a/tests/test_importing.py
+++ b/tests/test_importing.py
@@ -2,9 +2,8 @@
import os
from tests.shared import getUpgradeDeckPath, getEmptyCol
-from anki.upgrade import Upgrader
from anki.utils import ids2str
-from anki.importing import Anki1Importer, Anki2Importer, TextImporter, \
+from anki.importing import Anki2Importer, TextImporter, \
SupermemoXmlImporter, MnemosyneImporter, AnkiPackageImporter
testDir = os.path.dirname(__file__)
@@ -12,43 +11,6 @@ testDir = os.path.dirname(__file__)
srcNotes=None
srcCards=None
-def test_anki2():
- global srcNotes, srcCards
- # get the deck to import
- tmp = getUpgradeDeckPath()
- u = Upgrader()
- u.check(tmp)
- src = u.upgrade()
- srcpath = src.path
- srcNotes = src.noteCount()
- srcCards = src.cardCount()
- srcRev = src.db.scalar("select count() from revlog")
- # add a media file for testing
- open(os.path.join(src.media.dir(), "_foo.jpg"), "w").write("foo")
- src.close()
- # create a new empty deck
- dst = getEmptyCol()
- # import src into dst
- imp = Anki2Importer(dst, srcpath)
- imp.run()
- def check():
- assert dst.noteCount() == srcNotes
- assert dst.cardCount() == srcCards
- assert srcRev == dst.db.scalar("select count() from revlog")
- mids = [int(x) for x in dst.models.models.keys()]
- assert not dst.db.scalar(
- "select count() from notes where mid not in "+ids2str(mids))
- assert not dst.db.scalar(
- "select count() from cards where nid not in (select id from notes)")
- assert not dst.db.scalar(
- "select count() from revlog where cid not in (select id from cards)")
- assert dst.fixIntegrity()[0].startswith("Database rebuilt")
- check()
- # importing should be idempotent
- imp.run()
- check()
- assert len(os.listdir(dst.media.dir())) == 1
-
def test_anki2_mediadupes():
tmp = getEmptyCol()
# add a note that references a sound
@@ -96,7 +58,7 @@ def test_anki2_mediadupes():
def test_apkg():
tmp = getEmptyCol()
- apkg = unicode(os.path.join(testDir, "support/media.apkg"))
+ apkg = str(os.path.join(testDir, "support/media.apkg"))
imp = AnkiPackageImporter(tmp, apkg)
assert os.listdir(tmp.media.dir()) == []
imp.run()
@@ -113,65 +75,6 @@ def test_apkg():
imp.run()
assert len(os.listdir(tmp.media.dir())) == 2
-def test_anki1():
- # get the deck path to import
- tmp = getUpgradeDeckPath()
- # make sure media is imported properly through the upgrade
- mdir = tmp.replace(".anki2", ".media")
- if not os.path.exists(mdir):
- os.mkdir(mdir)
- open(os.path.join(mdir, "_foo.jpg"), "w").write("foo")
- # create a new empty deck
- dst = getEmptyCol()
- # import src into dst
- imp = Anki1Importer(dst, tmp)
- imp.run()
- def check():
- assert dst.noteCount() == srcNotes
- assert dst.cardCount() == srcCards
- assert len(os.listdir(dst.media.dir())) == 1
- check()
- # importing should be idempotent
- imp = Anki1Importer(dst, tmp)
- imp.run()
- check()
-
-def test_anki1_diffmodels():
- # create a new empty deck
- dst = getEmptyCol()
- # import the 1 card version of the model
- tmp = getUpgradeDeckPath("diffmodels1.anki")
- imp = Anki1Importer(dst, tmp)
- imp.run()
- before = dst.noteCount()
- # repeating the process should do nothing
- imp = Anki1Importer(dst, tmp)
- imp.run()
- assert before == dst.noteCount()
- # then the 2 card version
- tmp = getUpgradeDeckPath("diffmodels2.anki")
- imp = Anki1Importer(dst, tmp)
- imp.run()
- after = dst.noteCount()
- # as the model schemas differ, should have been imported as new model
- assert after == before + 1
- # repeating the process should do nothing
- beforeModels = len(dst.models.all())
- imp = Anki1Importer(dst, tmp)
- imp.run()
- after = dst.noteCount()
- assert after == before + 1
- assert beforeModels == len(dst.models.all())
-
-def test_suspended():
- # create a new empty deck
- dst = getEmptyCol()
- # import the 1 card version of the model
- tmp = getUpgradeDeckPath("suspended12.anki")
- imp = Anki1Importer(dst, tmp)
- imp.run()
- assert dst.db.scalar("select due from cards") < 0
-
def test_anki2_diffmodels():
# create a new empty deck
dst = getEmptyCol()
@@ -254,7 +157,7 @@ def test_anki2_updates():
def test_csv():
deck = getEmptyCol()
- file = unicode(os.path.join(testDir, "support/text-2fields.txt"))
+ file = str(os.path.join(testDir, "support/text-2fields.txt"))
i = TextImporter(deck, file)
i.initMapping()
i.run()
@@ -299,7 +202,7 @@ def test_csv2():
n['Three'] = "3"
deck.addNote(n)
# an update with unmapped fields should not clobber those fields
- file = unicode(os.path.join(testDir, "support/text-update.txt"))
+ file = str(os.path.join(testDir, "support/text-update.txt"))
i = TextImporter(deck, file)
i.initMapping()
i.run()
@@ -311,7 +214,7 @@ def test_csv2():
def test_supermemo_xml_01_unicode():
deck = getEmptyCol()
- file = unicode(os.path.join(testDir, "support/supermemo1.xml"))
+ file = str(os.path.join(testDir, "support/supermemo1.xml"))
i = SupermemoXmlImporter(deck, file)
#i.META.logToStdOutput = True
i.run()
@@ -325,7 +228,7 @@ def test_supermemo_xml_01_unicode():
def test_mnemo():
deck = getEmptyCol()
- file = unicode(os.path.join(testDir, "support/mnemo.db"))
+ file = str(os.path.join(testDir, "support/mnemo.db"))
i = MnemosyneImporter(deck, file)
i.run()
assert deck.cardCount() == 7
diff --git a/tests/test_latex.py b/tests/test_latex.py
index cb723461f..e762f0fab 100644
--- a/tests/test_latex.py
+++ b/tests/test_latex.py
@@ -1,6 +1,9 @@
# coding: utf-8
import os
+
+import shutil
+
from tests.shared import getEmptyCol
from anki.utils import stripHTML
@@ -11,7 +14,7 @@ def test_latex():
anki.latex.latexCmds[0][0] = "nolatex"
# add a note with latex
f = d.newNote()
- f['Front'] = u"[latex]hello[/latex]"
+ f['Front'] = "[latex]hello[/latex]"
d.addNote(f)
# but since latex couldn't run, there's nothing there
assert len(os.listdir(d.media.dir())) == 0
@@ -20,11 +23,9 @@ def test_latex():
assert "executing nolatex" in msg
assert "installed" in msg
# check if we have latex installed, and abort test if we don't
- for cmd in ("latex", "dvipng"):
- if (not os.path.exists("/usr/bin/"+cmd) and
- not os.path.exists("/usr/texbin/"+cmd)):
- print "aborting test; %s is not installed" % cmd
- return
+ if not shutil.which("latex") or not shutil.which("dvipng"):
+ print("aborting test; %s is not installed" % cmd)
+ return
# fix path
anki.latex.latexCmds[0][0] = "latex"
# check media db should cause latex to be generated
@@ -33,13 +34,13 @@ def test_latex():
assert ".png" in f.cards()[0].q()
# adding new notes should cause generation on question display
f = d.newNote()
- f['Front'] = u"[latex]world[/latex]"
+ f['Front'] = "[latex]world[/latex]"
d.addNote(f)
f.cards()[0].q()
assert len(os.listdir(d.media.dir())) == 2
# another note with the same media should reuse
f = d.newNote()
- f['Front'] = u" [latex]world[/latex]"
+ f['Front'] = " [latex]world[/latex]"
d.addNote(f)
assert len(os.listdir(d.media.dir())) == 2
oldcard = f.cards()[0]
@@ -48,7 +49,7 @@ def test_latex():
# missing media will show the latex
anki.latex.build = False
f = d.newNote()
- f['Front'] = u"[latex]foo[/latex]"
+ f['Front'] = "[latex]foo[/latex]"
d.addNote(f)
assert len(os.listdir(d.media.dir())) == 2
assert stripHTML(f.cards()[0].q()) == "[latex]foo[/latex]"
@@ -107,7 +108,7 @@ def test_good_latex_command_works():
def _test_includes_bad_command(bad):
d = getEmptyCol()
f = d.newNote()
- f['Front'] = u'[latex]%s[/latex]' % bad;
+ f['Front'] = '[latex]%s[/latex]' % bad;
d.addNote(f)
q = f.cards()[0].q()
return ("'%s' is not allowed on cards" % bad in q, "Card content: %s" % q)
\ No newline at end of file
diff --git a/tests/test_media.py b/tests/test_media.py
index b18969b5f..7a1c391c5 100644
--- a/tests/test_media.py
+++ b/tests/test_media.py
@@ -4,14 +4,14 @@ import tempfile
import os
import time
-from shared import getEmptyCol, testDir
+from .shared import getEmptyCol, testDir
# copying files to media folder
def test_add():
d = getEmptyCol()
dir = tempfile.mkdtemp(prefix="anki")
- path = os.path.join(dir, u"foo.jpg")
+ path = os.path.join(dir, "foo.jpg")
open(path, "w").write("hello")
# new file, should preserve name
assert d.media.addFile(path) == "foo.jpg"
@@ -24,7 +24,7 @@ def test_add():
def test_strings():
d = getEmptyCol()
mf = d.media.filesInStr
- mid = d.models.models.keys()[0]
+ mid = list(d.models.models.keys())[0]
assert mf(mid, "aoeu") == []
assert mf(mid, "aoeu
ao") == ["foo.jpg"]
assert mf(mid, "aoeu
ao") == ["foo.jpg"]
@@ -50,18 +50,18 @@ def test_deckIntegration():
# create a media dir
d.media.dir()
# put a file into it
- file = unicode(os.path.join(testDir, "support/fake.png"))
+ file = str(os.path.join(testDir, "support/fake.png"))
d.media.addFile(file)
# add a note which references it
f = d.newNote()
- f['Front'] = u"one"; f['Back'] = u"
"
+ f['Front'] = "one"; f['Back'] = "
"
d.addNote(f)
# and one which references a non-existent file
f = d.newNote()
- f['Front'] = u"one"; f['Back'] = u"
"
+ f['Front'] = "one"; f['Back'] = "
"
d.addNote(f)
# and add another file which isn't used
- open(os.path.join(d.media.dir(), "foo.jpg"), "wb").write("test")
+ open(os.path.join(d.media.dir(), "foo.jpg"), "w").write("test")
# check media
ret = d.media.check()
assert ret[0] == ["fake2.png"]
@@ -78,7 +78,7 @@ def test_changes():
assert not list(removed())
# add a file
dir = tempfile.mkdtemp(prefix="anki")
- path = os.path.join(dir, u"foo.jpg")
+ path = os.path.join(dir, "foo.jpg")
open(path, "w").write("hello")
time.sleep(1)
path = d.media.addFile(path)
@@ -106,8 +106,8 @@ def test_changes():
def test_illegal():
d = getEmptyCol()
- aString = u"a:b|cd\\e/f\0g*h"
- good = u"abcdefgh"
+ aString = "a:b|cd\\e/f\0g*h"
+ good = "abcdefgh"
assert d.media.stripIllegal(aString) == good
for c in aString:
bad = d.media.hasIllegal("somestring"+c+"morestring")
diff --git a/tests/test_models.py b/tests/test_models.py
index aa03b47b5..7eb43ccb3 100644
--- a/tests/test_models.py
+++ b/tests/test_models.py
@@ -6,8 +6,8 @@ from anki.utils import stripHTML, joinFields
def test_modelDelete():
deck = getEmptyCol()
f = deck.newNote()
- f['Front'] = u'1'
- f['Back'] = u'2'
+ f['Front'] = '1'
+ f['Back'] = '2'
deck.addNote(f)
assert deck.cardCount() == 1
deck.models.rem(deck.models.current())
@@ -29,8 +29,8 @@ def test_modelCopy():
def test_fields():
d = getEmptyCol()
f = d.newNote()
- f['Front'] = u'1'
- f['Back'] = u'2'
+ f['Front'] = '1'
+ f['Back'] = '2'
d.addNote(f)
m = d.models.current()
# make sure renaming a field updates the templates
@@ -82,8 +82,8 @@ def test_templates():
mm.addTemplate(m, t)
mm.save(m)
f = d.newNote()
- f['Front'] = u'1'
- f['Back'] = u'2'
+ f['Front'] = '1'
+ f['Back'] = '2'
d.addNote(f)
assert d.cardCount() == 2
(c, c2) = f.cards()
@@ -121,7 +121,7 @@ def test_cloze_ordinals():
d.models.remTemplate(m, m['tmpls'][0])
f = d.newNote()
- f['Text'] = u'{{c1::firstQ::firstA}}{{c2::secondQ::secondA}}'
+ f['Text'] = '{{c1::firstQ::firstA}}{{c2::secondQ::secondA}}'
d.addNote(f)
assert d.cardCount() == 2
(c, c2) = f.cards()
@@ -136,7 +136,7 @@ def test_text():
m['tmpls'][0]['qfmt'] = "{{text:Front}}"
d.models.save(m)
f = d.newNote()
- f['Front'] = u'helloworld'
+ f['Front'] = 'helloworld'
d.addNote(f)
assert "helloworld" in f.cards()[0].q()
@@ -146,7 +146,7 @@ def test_cloze():
f = d.newNote()
assert f.model()['name'] == "Cloze"
# a cloze model with no clozes is not empty
- f['Text'] = u'nothing'
+ f['Text'] = 'nothing'
assert d.addNote(f)
# try with one cloze
f = d.newNote()
@@ -221,8 +221,8 @@ def test_modelChange():
mm.addTemplate(m, t)
mm.save(m)
f = deck.newNote()
- f['Front'] = u'f'
- f['Back'] = u'b123'
+ f['Front'] = 'f'
+ f['Back'] = 'b123'
deck.addNote(f)
# switch fields
map = {0: 1, 1: 0}
@@ -267,8 +267,8 @@ def test_modelChange():
assert f['Back'] == 'f'
# another note to try model conversion
f = deck.newNote()
- f['Front'] = u'f2'
- f['Back'] = u'b2'
+ f['Front'] = 'f2'
+ f['Back'] = 'b2'
deck.addNote(f)
assert deck.models.useCount(basic) == 2
assert deck.models.useCount(cloze) == 0
diff --git a/tests/test_sched.py b/tests/test_sched.py
index f3b62240c..e1742c64a 100644
--- a/tests/test_sched.py
+++ b/tests/test_sched.py
@@ -28,7 +28,7 @@ def test_new():
assert d.sched.newCount == 0
# add a note
f = d.newNote()
- f['Front'] = u"one"; f['Back'] = u"two"
+ f['Front'] = "one"; f['Back'] = "two"
d.addNote(f)
d.reset()
assert d.sched.newCount == 1
@@ -99,7 +99,7 @@ def test_newLimits():
def test_newBoxes():
d = getEmptyCol()
f = d.newNote()
- f['Front'] = u"one"
+ f['Front'] = "one"
d.addNote(f)
d.reset()
c = d.sched.getCard()
@@ -113,7 +113,7 @@ def test_learn():
d = getEmptyCol()
# add a note
f = d.newNote()
- f['Front'] = u"one"; f['Back'] = u"two"
+ f['Front'] = "one"; f['Back'] = "two"
f = d.addNote(f)
# set as a learn card and rebuild queues
d.db.execute("update cards set queue=0, type=0")
@@ -126,7 +126,7 @@ def test_learn():
d.sched.answerCard(c, 1)
# it should have three reps left to graduation
assert c.left%1000 == 3
- assert c.left/1000 == 3
+ assert c.left//1000 == 3
# it should by due in 30 seconds
t = round(c.due - time.time())
assert t >= 25 and t <= 40
@@ -135,7 +135,7 @@ def test_learn():
# it should by due in 3 minutes
assert round(c.due - time.time()) in (179, 180)
assert c.left%1000 == 2
- assert c.left/1000 == 2
+ assert c.left//1000 == 2
# check log is accurate
log = d.db.first("select * from revlog order by id desc")
assert log[3] == 2
@@ -146,7 +146,7 @@ def test_learn():
# it should by due in 10 minutes
assert round(c.due - time.time()) in (599, 600)
assert c.left%1000 == 1
- assert c.left/1000 == 1
+ assert c.left//1000 == 1
# the next pass should graduate the card
assert c.queue == 1
assert c.type == 1
@@ -187,10 +187,10 @@ def test_learn_collapsed():
d = getEmptyCol()
# add 2 notes
f = d.newNote()
- f['Front'] = u"1"
+ f['Front'] = "1"
f = d.addNote(f)
f = d.newNote()
- f['Front'] = u"2"
+ f['Front'] = "2"
f = d.addNote(f)
# set as a learn card and rebuild queues
d.db.execute("update cards set queue=0, type=0")
@@ -213,7 +213,7 @@ def test_learn_day():
d = getEmptyCol()
# add a note
f = d.newNote()
- f['Front'] = u"one"
+ f['Front'] = "one"
f = d.addNote(f)
d.sched.reset()
c = d.sched.getCard()
@@ -222,7 +222,7 @@ def test_learn_day():
d.sched.answerCard(c, 2)
# two reps to graduate, 1 more today
assert c.left%1000 == 3
- assert c.left/1000 == 1
+ assert c.left//1000 == 1
assert d.sched.counts() == (0, 1, 0)
c = d.sched.getCard()
ni = d.sched.nextIvl
@@ -271,7 +271,7 @@ def test_reviews():
d = getEmptyCol()
# add a note
f = d.newNote()
- f['Front'] = u"one"; f['Back'] = u"two"
+ f['Front'] = "one"; f['Back'] = "two"
d.addNote(f)
# set the card up as a review card, due 8 days ago
c = f.cards()[0]
@@ -362,7 +362,7 @@ def test_reviews():
def test_button_spacing():
d = getEmptyCol()
f = d.newNote()
- f['Front'] = u"one"
+ f['Front'] = "one"
d.addNote(f)
# 1 day ivl review card due now
c = f.cards()[0]
@@ -385,7 +385,7 @@ def test_overdue_lapse():
d = getEmptyCol()
# add a note
f = d.newNote()
- f['Front'] = u"one"
+ f['Front'] = "one"
d.addNote(f)
# simulate a review that was lapsed and is now due for its normal review
c = f.cards()[0]
@@ -420,7 +420,7 @@ def test_finished():
assert "Congratulations" in d.sched.finishedMsg()
assert "limit" not in d.sched.finishedMsg()
f = d.newNote()
- f['Front'] = u"one"; f['Back'] = u"two"
+ f['Front'] = "one"; f['Back'] = "two"
d.addNote(f)
# have a new card
assert "new cards available" in d.sched.finishedMsg()
@@ -436,7 +436,7 @@ def test_finished():
def test_nextIvl():
d = getEmptyCol()
f = d.newNote()
- f['Front'] = u"one"; f['Back'] = u"two"
+ f['Front'] = "one"; f['Back'] = "two"
d.addNote(f)
d.reset()
conf = d.decks.confForDid(1)
@@ -492,7 +492,7 @@ def test_nextIvl():
def test_misc():
d = getEmptyCol()
f = d.newNote()
- f['Front'] = u"one"
+ f['Front'] = "one"
d.addNote(f)
c = f.cards()[0]
# burying
@@ -506,7 +506,7 @@ def test_misc():
def test_suspend():
d = getEmptyCol()
f = d.newNote()
- f['Front'] = u"one"
+ f['Front'] = "one"
d.addNote(f)
c = f.cards()[0]
# suspending
@@ -549,7 +549,7 @@ def test_suspend():
def test_cram():
d = getEmptyCol()
f = d.newNote()
- f['Front'] = u"one"
+ f['Front'] = "one"
d.addNote(f)
c = f.cards()[0]
c.ivl = 100
@@ -657,7 +657,7 @@ def test_cram():
def test_cram_rem():
d = getEmptyCol()
f = d.newNote()
- f['Front'] = u"one"
+ f['Front'] = "one"
d.addNote(f)
oldDue = f.cards()[0].due
did = d.decks.newDyn("Cram")
@@ -678,7 +678,7 @@ def test_cram_resched():
# add card
d = getEmptyCol()
f = d.newNote()
- f['Front'] = u"one"
+ f['Front'] = "one"
d.addNote(f)
# cram deck
did = d.decks.newDyn("Cram")
@@ -806,7 +806,7 @@ def test_ordcycle():
def test_counts_idx():
d = getEmptyCol()
f = d.newNote()
- f['Front'] = u"one"; f['Back'] = u"two"
+ f['Front'] = "one"; f['Back'] = "two"
d.addNote(f)
d.reset()
assert d.sched.counts() == (1, 0, 0)
@@ -828,7 +828,7 @@ def test_counts_idx():
def test_repCounts():
d = getEmptyCol()
f = d.newNote()
- f['Front'] = u"one"
+ f['Front'] = "one"
d.addNote(f)
d.reset()
# lrnReps should be accurate on pass/fail
@@ -846,7 +846,7 @@ def test_repCounts():
d.sched.answerCard(d.sched.getCard(), 2)
assert d.sched.counts() == (0, 0, 0)
f = d.newNote()
- f['Front'] = u"two"
+ f['Front'] = "two"
d.addNote(f)
d.reset()
# initial pass should be correct too
@@ -858,14 +858,14 @@ def test_repCounts():
assert d.sched.counts() == (0, 0, 0)
# immediate graduate should work
f = d.newNote()
- f['Front'] = u"three"
+ f['Front'] = "three"
d.addNote(f)
d.reset()
d.sched.answerCard(d.sched.getCard(), 3)
assert d.sched.counts() == (0, 0, 0)
# and failing a review should too
f = d.newNote()
- f['Front'] = u"three"
+ f['Front'] = "three"
d.addNote(f)
c = f.cards()[0]
c.type = 2
@@ -907,7 +907,7 @@ def test_collapse():
d = getEmptyCol()
# add a note
f = d.newNote()
- f['Front'] = u"one"
+ f['Front'] = "one"
d.addNote(f)
d.reset()
# test collapsing
@@ -921,11 +921,11 @@ def test_deckDue():
d = getEmptyCol()
# add a note with default deck
f = d.newNote()
- f['Front'] = u"one"
+ f['Front'] = "one"
d.addNote(f)
# and one that's a child
f = d.newNote()
- f['Front'] = u"two"
+ f['Front'] = "two"
default1 = f.model()['did'] = d.decks.id("Default::1")
d.addNote(f)
# make it a review card
@@ -935,12 +935,12 @@ def test_deckDue():
c.flush()
# add one more with a new deck
f = d.newNote()
- f['Front'] = u"two"
+ f['Front'] = "two"
foobar = f.model()['did'] = d.decks.id("foo::bar")
d.addNote(f)
# and one that's a sibling
f = d.newNote()
- f['Front'] = u"three"
+ f['Front'] = "three"
foobaz = f.model()['did'] = d.decks.id("foo::baz")
d.addNote(f)
d.reset()
@@ -980,16 +980,16 @@ def test_deckFlow():
d = getEmptyCol()
# add a note with default deck
f = d.newNote()
- f['Front'] = u"one"
+ f['Front'] = "one"
d.addNote(f)
# and one that's a child
f = d.newNote()
- f['Front'] = u"two"
+ f['Front'] = "two"
default1 = f.model()['did'] = d.decks.id("Default::2")
d.addNote(f)
# and another that's higher up
f = d.newNote()
- f['Front'] = u"three"
+ f['Front'] = "three"
default1 = f.model()['did'] = d.decks.id("Default::1")
d.addNote(f)
# should get top level one first, then ::1, then ::2
@@ -1004,10 +1004,10 @@ def test_reorder():
d = getEmptyCol()
# add a note with default deck
f = d.newNote()
- f['Front'] = u"one"
+ f['Front'] = "one"
d.addNote(f)
f2 = d.newNote()
- f2['Front'] = u"two"
+ f2['Front'] = "two"
d.addNote(f2)
assert f2.cards()[0].due == 2
found=False
@@ -1022,10 +1022,10 @@ def test_reorder():
assert f.cards()[0].due == 1
# shifting
f3 = d.newNote()
- f3['Front'] = u"three"
+ f3['Front'] = "three"
d.addNote(f3)
f4 = d.newNote()
- f4['Front'] = u"four"
+ f4['Front'] = "four"
d.addNote(f4)
assert f.cards()[0].due == 1
assert f2.cards()[0].due == 2
@@ -1041,7 +1041,7 @@ def test_reorder():
def test_forget():
d = getEmptyCol()
f = d.newNote()
- f['Front'] = u"one"
+ f['Front'] = "one"
d.addNote(f)
c = f.cards()[0]
c.queue = 2; c.type = 2; c.ivl = 100; c.due = 0
@@ -1055,7 +1055,7 @@ def test_forget():
def test_resched():
d = getEmptyCol()
f = d.newNote()
- f['Front'] = u"one"
+ f['Front'] = "one"
d.addNote(f)
c = f.cards()[0]
d.sched.reschedCards([c.id], 0, 0)
@@ -1072,7 +1072,7 @@ def test_norelearn():
d = getEmptyCol()
# add a note
f = d.newNote()
- f['Front'] = u"one"
+ f['Front'] = "one"
d.addNote(f)
c = f.cards()[0]
c.type = 2
@@ -1092,7 +1092,7 @@ def test_norelearn():
def test_failmult():
d = getEmptyCol()
f = d.newNote()
- f['Front'] = u"one"; f['Back'] = u"two"
+ f['Front'] = "one"; f['Back'] = "two"
d.addNote(f)
c = f.cards()[0]
c.type = 2
diff --git a/tests/test_sync.py b/tests/test_sync.py
index 5e35a41e5..3ea85238c 100644
--- a/tests/test_sync.py
+++ b/tests/test_sync.py
@@ -21,14 +21,14 @@ def setup_basic():
deck1 = getEmptyCol()
# add a note to deck 1
f = deck1.newNote()
- f['Front'] = u"foo"; f['Back'] = u"bar"; f.tags = [u"foo"]
+ f['Front'] = "foo"; f['Back'] = "bar"; f.tags = ["foo"]
deck1.addNote(f)
# answer it
deck1.reset(); deck1.sched.answerCard(deck1.sched.getCard(), 4)
# repeat for deck2
deck2 = getEmptyDeckWith(server=True)
f = deck2.newNote()
- f['Front'] = u"bar"; f['Back'] = u"bar"; f.tags = [u"bar"]
+ f['Front'] = "bar"; f['Back'] = "bar"; f.tags = ["bar"]
deck2.addNote(f)
deck2.reset(); deck2.sched.answerCard(deck2.sched.getCard(), 4)
# start with same schema and sync time
@@ -223,7 +223,7 @@ def test_threeway():
# client 1 adds a card at time 1
time.sleep(1)
f = deck1.newNote()
- f['Front'] = u"1";
+ f['Front'] = "1";
deck1.addNote(f)
deck1.save()
# at time 2, client 2 syncs to server
@@ -249,7 +249,7 @@ def test_threeway2():
# create collection 1 with a single note
c1 = getEmptyCol()
f = c1.newNote()
- f['Front'] = u"startingpoint"
+ f['Front'] = "startingpoint"
nid = f.id
c1.addNote(f)
cid = f.cards()[0].id
@@ -329,9 +329,9 @@ def _test_speed():
deck1.scm = deck2.scm = 0
server = LocalServer(deck2)
client = Syncer(deck1, server)
- print "load %d" % ((time.time() - t)*1000); t = time.time()
+ print("load %d" % ((time.time() - t)*1000)); t = time.time()
assert client.sync() == "success"
- print "sync %d" % ((time.time() - t)*1000); t = time.time()
+ print("sync %d" % ((time.time() - t)*1000)); t = time.time()
@nose.with_setup(setup_modified)
def test_filtered_delete():
diff --git a/tests/test_undo.py b/tests/test_undo.py
index c329c424e..a9b06b34c 100644
--- a/tests/test_undo.py
+++ b/tests/test_undo.py
@@ -27,7 +27,7 @@ def test_op():
# and a review will, too
d.save("add")
f = d.newNote()
- f['Front'] = u"one"
+ f['Front'] = "one"
d.addNote(f)
d.reset()
assert d.undoName() == "add"
@@ -39,7 +39,7 @@ def test_review():
d = getEmptyCol()
d.conf['counts'] = COUNT_REMAINING
f = d.newNote()
- f['Front'] = u"one"
+ f['Front'] = "one"
d.addNote(f)
d.reset()
assert not d.undoName()
@@ -62,7 +62,7 @@ def test_review():
assert not d.undoName()
# we should be able to undo multiple answers too
f = d.newNote()
- f['Front'] = u"two"
+ f['Front'] = "two"
d.addNote(f)
d.reset()
assert d.sched.counts() == (2, 0, 0)
diff --git a/tools/tests.sh b/tools/tests.sh
index 1e189760a..a73184f8e 100755
--- a/tools/tests.sh
+++ b/tools/tests.sh
@@ -22,5 +22,5 @@ else
args=""
echo "Call with coverage=1 to run coverage tests"
fi
-(cd $dir && nosetests -vs $lim $args --cover-package=anki)
+(cd $dir && nosetests3 -vs $lim $args --cover-package=anki)