[tor-commits] [stem/master] Provide a connection with stem.manual.database

atagar at torproject.org atagar at torproject.org
Sat Aug 26 22:51:43 UTC 2017

commit 17ab84ac5ebf896a77d1ce67bee54bb00d3c0bfd
Author: Damian Johnson <atagar at torproject.org>
Date:   Sat Aug 26 14:59:08 2017 -0700

    Provide a connection with stem.manual.database
    In dabbling with this more I've been finding the context manager enforced by
    stem.manual.database() annoying. When on an interactive prompt I want a method
    to quickly get a no-hassle database connection - not a cursor that's only
    usable within a 'with' block.
    Reason I provided a cursor was to ensure callers had read-only access.
    However, just checked and when the database file is read-only (as our
    cached_tor_manual.sqlite should be when installed) attempting to write
    to it behaves how I'd expect...
      >>> import stem.manual
      >>> conn = stem.manual.database()
      >>> conn.execute('CREATE TABLE new_table(name TEXT PRIMARY KEY, description TEXT)')
      Traceback (most recent call last):
        File "<stdin>", line 1, in <module>
      sqlite3.OperationalError: attempt to write a readonly database
 stem/manual.py      | 18 ++++++++++++------
 test/unit/manual.py |  8 +++++---
 2 files changed, 17 insertions(+), 9 deletions(-)

diff --git a/stem/manual.py b/stem/manual.py
index fc648cc9..e78f917c 100644
--- a/stem/manual.py
+++ b/stem/manual.py
@@ -47,7 +47,6 @@ us what our torrc options do...
 .. versionadded:: 1.5.0
-import contextlib
 import os
 import shutil
 import sqlite3
@@ -95,26 +94,33 @@ CATEGORY_SECTIONS = OrderedDict((
- at contextlib.contextmanager
 def database(path = None):
-  Provides a database cursor for a sqlite cache.
+  Provides database connection for our sqlite cache. This database should be
+  treated as being read-only, with this restriction being enforced in the
+  future.
   .. versionadded:: 1.6.0
   :param str path: cached manual content to read, if not provided this uses
     the bundled manual information
-  :returns: :class:`sqlite3.Cursor` for the database cache
+  :returns: :class:`sqlite3.Connection` for the database cache
   :raises: **IOError** if a **path** was provided and we were unable to read it
   if path is None:
     path = CACHE_PATH
+  elif not os.path.exists(path):
+    raise IOError("%s doesn't exist" % path)
-  with sqlite3.connect(path) as conn:
-    yield conn.cursor()
+  # TODO: When we only support python 3.4+ we can use sqlite's uri argument to
+  # get a read-only connection...
+  #
+  #   https://docs.python.org/3/library/sqlite3.html#sqlite3.connect
+  return sqlite3.connect(path)
 class ConfigOption(object):
diff --git a/test/unit/manual.py b/test/unit/manual.py
index eab9bdfc..eaca3629 100644
--- a/test/unit/manual.py
+++ b/test/unit/manual.py
@@ -103,9 +103,11 @@ def _cached_manual():
 class TestManual(unittest.TestCase):
   def test_database(self):
-    with stem.manual.database() as cursor:
-      cursor.execute('SELECT description FROM torrc WHERE name="CookieAuthFile"')
-      self.assertEqual("If set, this option overrides the default location and file name for Tor's cookie file. (See CookieAuthentication above.)", cursor.fetchone()[0])
+    with stem.manual.database() as conn:
+      self.assertEqual("If set, this option overrides the default location and file name for Tor's cookie file. (See CookieAuthentication above.)", conn.execute('SELECT description FROM torrc WHERE name="CookieAuthFile"').fetchone()[0])
+  def test_missing_database(self):
+    self.assertRaisesRegexp(IOError, "/no/such/path doesn't exist", stem.manual.database, '/no/such/path')
   def test_has_all_summaries(self):

More information about the tor-commits mailing list