From 8d036e2389f482a88a16ddf931982284ec7e2ed6 2011-10-20 17:36:10 From: MinRK Date: 2011-10-20 17:36:10 Subject: [PATCH] Allow IPython to run without sqlite3 The History will always appear empty, so history magics, etc. don't do anything. Readline history for a single session works fine. Test suite should now pass without sqlite --- diff --git a/IPython/core/history.py b/IPython/core/history.py index 9ae70f6..b10ac42 100644 --- a/IPython/core/history.py +++ b/IPython/core/history.py @@ -17,12 +17,15 @@ import atexit import datetime import os import re -import sqlite3 +try: + import sqlite3 +except ImportError: + sqlite3 = None import threading # Our own packages from IPython.config.configurable import Configurable - +from IPython.external.decorator import decorator from IPython.testing.skipdoctest import skip_doctest from IPython.utils import io from IPython.utils.path import locate_profile @@ -33,6 +36,30 @@ from IPython.utils.warn import warn # Classes and functions #----------------------------------------------------------------------------- +class DummyDB(object): + """Dummy DB that will act as a black hole for history. + + Only used in the absence of sqlite""" + def execute(*args, **kwargs): + return [] + + def commit(self, *args, **kwargs): + pass + + def __enter__(self, *args, **kwargs): + pass + + def __exit__(self, *args, **kwargs): + pass + +@decorator +def needs_sqlite(f,*a,**kw): + """return an empty list in the absence of sqlite""" + if sqlite3 is None: + return [] + else: + return f(*a,**kw) + class HistoryAccessor(Configurable): """Access the history database without adding to it. @@ -42,7 +69,10 @@ class HistoryAccessor(Configurable): hist_file = Unicode(config=True) # The SQLite database - db = Instance(sqlite3.Connection) + if sqlite3: + db = Instance(sqlite3.Connection) + else: + db = Instance(DummyDB) def __init__(self, profile='default', hist_file=u'', shell=None, config=None, **traits): """Create a new history accessor. @@ -67,6 +97,11 @@ class HistoryAccessor(Configurable): # No one has set the hist_file, yet. self.hist_file = self._get_hist_file_name(profile) + if sqlite3 is None: + warn("IPython History requires SQLite, your history will not be saved\n") + self.db = DummyDB() + return + try: self.init_db() except sqlite3.DatabaseError: @@ -146,6 +181,7 @@ class HistoryAccessor(Configurable): return ((ses, lin, (inp, out)) for ses, lin, inp, out in cur) return cur + @needs_sqlite def get_session_info(self, session=0): """get info about a session @@ -351,7 +387,7 @@ class HistoryManager(HistoryAccessor): self.save_thread.start() self.new_session() - + def _get_hist_file_name(self, profile=None): """Get default history file name based on the Shell's profile. @@ -360,6 +396,7 @@ class HistoryManager(HistoryAccessor): profile_dir = self.shell.profile_dir.location return os.path.join(profile_dir, 'history.sqlite') + @needs_sqlite def new_session(self, conn=None): """Get a new session number.""" if conn is None: @@ -537,6 +574,7 @@ class HistoryManager(HistoryAccessor): conn.execute("INSERT INTO output_history VALUES (?, ?, ?)", (self.session_number,)+line) + @needs_sqlite def writeout_cache(self, conn=None): """Write any entries in the cache to the database.""" if conn is None: @@ -581,6 +619,7 @@ class HistorySavingThread(threading.Thread): self.history_manager = history_manager atexit.register(self.stop) + @needs_sqlite def run(self): # We need a separate db connection per thread: try: diff --git a/IPython/core/tests/test_magic.py b/IPython/core/tests/test_magic.py index e4ebde9..0cf5d56 100644 --- a/IPython/core/tests/test_magic.py +++ b/IPython/core/tests/test_magic.py @@ -55,7 +55,8 @@ def test_magic_parse_options(): expected = path nt.assert_equals(opts['f'], expected) - + +@dec.skip_without('sqlite3') def doctest_hist_f(): """Test %hist -f with temporary filename. @@ -69,6 +70,7 @@ def doctest_hist_f(): """ +@dec.skip_without('sqlite3') def doctest_hist_r(): """Test %hist -r @@ -86,6 +88,8 @@ def doctest_hist_r(): %hist -r 2 """ + +@dec.skip_without('sqlite3') def doctest_hist_op(): """Test %hist -op @@ -151,7 +155,9 @@ def doctest_hist_op(): 's' >>> """ - + + +@dec.skip_without('sqlite3') def test_macro(): ip = get_ipython() ip.history_manager.reset() # Clear any existing history. @@ -164,6 +170,8 @@ def test_macro(): # List macros. assert "test" in ip.magic("macro") + +@dec.skip_without('sqlite3') def test_macro_run(): """Test that we can run a multi-line macro successfully.""" ip = get_ipython() diff --git a/IPython/core/tests/test_run.py b/IPython/core/tests/test_run.py index 6a823df..af52125 100644 --- a/IPython/core/tests/test_run.py +++ b/IPython/core/tests/test_run.py @@ -169,9 +169,13 @@ class TestMagicRunSimple(tt.TempFileMixin): " print 'object A deleted'\n" "a = A()\n") self.mktmp(py3compat.doctest_refactor_print(src)) - tt.ipexec_validate(self.fname, 'object A deleted') - - @dec.skip_known_failure + if dec.module_not_available('sqlite3'): + err = 'WARNING: IPython History requires SQLite, your history will not be saved\n' + else: + err = None + tt.ipexec_validate(self.fname, 'object A deleted', err) + + @dec.skip_known_failure def test_aggressive_namespace_cleanup(self): """Test that namespace cleanup is not too aggressive GH-238 @@ -207,4 +211,8 @@ ARGV 1-: ['C-third'] tclass.py: deleting object: C-second tclass.py: deleting object: C-third """ - tt.ipexec_validate(self.fname, out) + if dec.module_not_available('sqlite3'): + err = 'WARNING: IPython History requires SQLite, your history will not be saved\n' + else: + err = None + tt.ipexec_validate(self.fname, out, err) diff --git a/IPython/parallel/controller/sqlitedb.py b/IPython/parallel/controller/sqlitedb.py index 80806e8..8b5d724 100644 --- a/IPython/parallel/controller/sqlitedb.py +++ b/IPython/parallel/controller/sqlitedb.py @@ -16,7 +16,10 @@ import os import cPickle as pickle from datetime import datetime -import sqlite3 +try: + import sqlite3 +except ImportError: + sqlite3 = None from zmq.eventloop import ioloop @@ -99,7 +102,10 @@ class SQLiteDB(BaseDB): in tasks from previous sessions being available via Clients' db_query and get_result methods.""") - _db = Instance('sqlite3.Connection') + if sqlite3 is not None: + _db = Instance('sqlite3.Connection') + else: + _db = None # the ordered list of column names _keys = List(['msg_id' , 'header' , @@ -145,6 +151,8 @@ class SQLiteDB(BaseDB): def __init__(self, **kwargs): super(SQLiteDB, self).__init__(**kwargs) + if sqlite3 is None: + raise ImportError("SQLiteDB requires sqlite3") if not self.table: # use session, and prefix _, since starting with # is illegal self.table = '_'+self.session.replace('-','_') diff --git a/IPython/parallel/tests/test_db.py b/IPython/parallel/tests/test_db.py index 4711d9a..3bdc1e9 100644 --- a/IPython/parallel/tests/test_db.py +++ b/IPython/parallel/tests/test_db.py @@ -24,13 +24,12 @@ import time from datetime import datetime, timedelta from unittest import TestCase -from nose import SkipTest - from IPython.parallel import error from IPython.parallel.controller.dictdb import DictDB from IPython.parallel.controller.sqlitedb import SQLiteDB from IPython.parallel.controller.hub import init_record, empty_record +from IPython.testing import decorators as dec from IPython.zmq.session import Session @@ -171,8 +170,11 @@ class TestDictBackend(TestCase): self.db.drop_matching_records(query) recs = self.db.find_records(query) self.assertEquals(len(recs), 0) - + + class TestSQLiteBackend(TestDictBackend): + + @dec.skip_without('sqlite3') def create_db(self): return SQLiteDB(location=tempfile.gettempdir()) diff --git a/IPython/testing/iptest.py b/IPython/testing/iptest.py index e97ad63..1ce0c37 100644 --- a/IPython/testing/iptest.py +++ b/IPython/testing/iptest.py @@ -128,6 +128,7 @@ have['pymongo'] = test_for('pymongo') have['wx'] = test_for('wx') have['wx.aui'] = test_for('wx.aui') have['qt'] = test_for('IPython.external.qt') +have['sqlite3'] = test_for('sqlite3') have['tornado'] = test_for('tornado.version_info', (2,1,0), callback=None) @@ -204,7 +205,9 @@ def make_exclude(): ipjoin('config', 'default'), ipjoin('config', 'profile'), ] - + if not have['sqlite3']: + exclusions.append(ipjoin('core', 'tests', 'test_history')) + exclusions.append(ipjoin('core', 'history')) if not have['wx']: exclusions.append(ipjoin('lib', 'inputhookwx'))