Show More
@@ -17,12 +17,15 b' import atexit' | |||
|
17 | 17 | import datetime |
|
18 | 18 | import os |
|
19 | 19 | import re |
|
20 | import sqlite3 | |
|
20 | try: | |
|
21 | import sqlite3 | |
|
22 | except ImportError: | |
|
23 | sqlite3 = None | |
|
21 | 24 | import threading |
|
22 | 25 | |
|
23 | 26 | # Our own packages |
|
24 | 27 | from IPython.config.configurable import Configurable |
|
25 | ||
|
28 | from IPython.external.decorator import decorator | |
|
26 | 29 | from IPython.testing.skipdoctest import skip_doctest |
|
27 | 30 | from IPython.utils import io |
|
28 | 31 | from IPython.utils.path import locate_profile |
@@ -33,6 +36,30 b' from IPython.utils.warn import warn' | |||
|
33 | 36 | # Classes and functions |
|
34 | 37 | #----------------------------------------------------------------------------- |
|
35 | 38 | |
|
39 | class DummyDB(object): | |
|
40 | """Dummy DB that will act as a black hole for history. | |
|
41 | ||
|
42 | Only used in the absence of sqlite""" | |
|
43 | def execute(*args, **kwargs): | |
|
44 | return [] | |
|
45 | ||
|
46 | def commit(self, *args, **kwargs): | |
|
47 | pass | |
|
48 | ||
|
49 | def __enter__(self, *args, **kwargs): | |
|
50 | pass | |
|
51 | ||
|
52 | def __exit__(self, *args, **kwargs): | |
|
53 | pass | |
|
54 | ||
|
55 | @decorator | |
|
56 | def needs_sqlite(f,*a,**kw): | |
|
57 | """return an empty list in the absence of sqlite""" | |
|
58 | if sqlite3 is None: | |
|
59 | return [] | |
|
60 | else: | |
|
61 | return f(*a,**kw) | |
|
62 | ||
|
36 | 63 | class HistoryAccessor(Configurable): |
|
37 | 64 | """Access the history database without adding to it. |
|
38 | 65 | |
@@ -42,7 +69,10 b' class HistoryAccessor(Configurable):' | |||
|
42 | 69 | hist_file = Unicode(config=True) |
|
43 | 70 | |
|
44 | 71 | # The SQLite database |
|
45 | db = Instance(sqlite3.Connection) | |
|
72 | if sqlite3: | |
|
73 | db = Instance(sqlite3.Connection) | |
|
74 | else: | |
|
75 | db = Instance(DummyDB) | |
|
46 | 76 | |
|
47 | 77 | def __init__(self, profile='default', hist_file=u'', shell=None, config=None, **traits): |
|
48 | 78 | """Create a new history accessor. |
@@ -67,6 +97,11 b' class HistoryAccessor(Configurable):' | |||
|
67 | 97 | # No one has set the hist_file, yet. |
|
68 | 98 | self.hist_file = self._get_hist_file_name(profile) |
|
69 | 99 | |
|
100 | if sqlite3 is None: | |
|
101 | warn("IPython History requires SQLite, your history will not be saved\n") | |
|
102 | self.db = DummyDB() | |
|
103 | return | |
|
104 | ||
|
70 | 105 | try: |
|
71 | 106 | self.init_db() |
|
72 | 107 | except sqlite3.DatabaseError: |
@@ -146,6 +181,7 b' class HistoryAccessor(Configurable):' | |||
|
146 | 181 | return ((ses, lin, (inp, out)) for ses, lin, inp, out in cur) |
|
147 | 182 | return cur |
|
148 | 183 | |
|
184 | @needs_sqlite | |
|
149 | 185 | def get_session_info(self, session=0): |
|
150 | 186 | """get info about a session |
|
151 | 187 | |
@@ -351,7 +387,7 b' class HistoryManager(HistoryAccessor):' | |||
|
351 | 387 | self.save_thread.start() |
|
352 | 388 | |
|
353 | 389 | self.new_session() |
|
354 | ||
|
390 | ||
|
355 | 391 | def _get_hist_file_name(self, profile=None): |
|
356 | 392 | """Get default history file name based on the Shell's profile. |
|
357 | 393 | |
@@ -360,6 +396,7 b' class HistoryManager(HistoryAccessor):' | |||
|
360 | 396 | profile_dir = self.shell.profile_dir.location |
|
361 | 397 | return os.path.join(profile_dir, 'history.sqlite') |
|
362 | 398 | |
|
399 | @needs_sqlite | |
|
363 | 400 | def new_session(self, conn=None): |
|
364 | 401 | """Get a new session number.""" |
|
365 | 402 | if conn is None: |
@@ -537,6 +574,7 b' class HistoryManager(HistoryAccessor):' | |||
|
537 | 574 | conn.execute("INSERT INTO output_history VALUES (?, ?, ?)", |
|
538 | 575 | (self.session_number,)+line) |
|
539 | 576 | |
|
577 | @needs_sqlite | |
|
540 | 578 | def writeout_cache(self, conn=None): |
|
541 | 579 | """Write any entries in the cache to the database.""" |
|
542 | 580 | if conn is None: |
@@ -581,6 +619,7 b' class HistorySavingThread(threading.Thread):' | |||
|
581 | 619 | self.history_manager = history_manager |
|
582 | 620 | atexit.register(self.stop) |
|
583 | 621 | |
|
622 | @needs_sqlite | |
|
584 | 623 | def run(self): |
|
585 | 624 | # We need a separate db connection per thread: |
|
586 | 625 | try: |
@@ -55,7 +55,8 b' def test_magic_parse_options():' | |||
|
55 | 55 | expected = path |
|
56 | 56 | nt.assert_equals(opts['f'], expected) |
|
57 | 57 | |
|
58 | ||
|
58 | ||
|
59 | @dec.skip_without('sqlite3') | |
|
59 | 60 | def doctest_hist_f(): |
|
60 | 61 | """Test %hist -f with temporary filename. |
|
61 | 62 | |
@@ -69,6 +70,7 b' def doctest_hist_f():' | |||
|
69 | 70 | """ |
|
70 | 71 | |
|
71 | 72 | |
|
73 | @dec.skip_without('sqlite3') | |
|
72 | 74 | def doctest_hist_r(): |
|
73 | 75 | """Test %hist -r |
|
74 | 76 | |
@@ -86,6 +88,8 b' def doctest_hist_r():' | |||
|
86 | 88 | %hist -r 2 |
|
87 | 89 | """ |
|
88 | 90 | |
|
91 | ||
|
92 | @dec.skip_without('sqlite3') | |
|
89 | 93 | def doctest_hist_op(): |
|
90 | 94 | """Test %hist -op |
|
91 | 95 | |
@@ -151,7 +155,9 b' def doctest_hist_op():' | |||
|
151 | 155 | 's' |
|
152 | 156 | >>> |
|
153 | 157 | """ |
|
154 | ||
|
158 | ||
|
159 | ||
|
160 | @dec.skip_without('sqlite3') | |
|
155 | 161 | def test_macro(): |
|
156 | 162 | ip = get_ipython() |
|
157 | 163 | ip.history_manager.reset() # Clear any existing history. |
@@ -164,6 +170,8 b' def test_macro():' | |||
|
164 | 170 | # List macros. |
|
165 | 171 | assert "test" in ip.magic("macro") |
|
166 | 172 | |
|
173 | ||
|
174 | @dec.skip_without('sqlite3') | |
|
167 | 175 | def test_macro_run(): |
|
168 | 176 | """Test that we can run a multi-line macro successfully.""" |
|
169 | 177 | ip = get_ipython() |
@@ -169,9 +169,13 b' class TestMagicRunSimple(tt.TempFileMixin):' | |||
|
169 | 169 | " print 'object A deleted'\n" |
|
170 | 170 | "a = A()\n") |
|
171 | 171 | self.mktmp(py3compat.doctest_refactor_print(src)) |
|
172 | tt.ipexec_validate(self.fname, 'object A deleted') | |
|
173 | ||
|
174 | @dec.skip_known_failure | |
|
172 | if dec.module_not_available('sqlite3'): | |
|
173 | err = 'WARNING: IPython History requires SQLite, your history will not be saved\n' | |
|
174 | else: | |
|
175 | err = None | |
|
176 | tt.ipexec_validate(self.fname, 'object A deleted', err) | |
|
177 | ||
|
178 | @dec.skip_known_failure | |
|
175 | 179 | def test_aggressive_namespace_cleanup(self): |
|
176 | 180 | """Test that namespace cleanup is not too aggressive GH-238 |
|
177 | 181 | |
@@ -207,4 +211,8 b" ARGV 1-: ['C-third']" | |||
|
207 | 211 | tclass.py: deleting object: C-second |
|
208 | 212 | tclass.py: deleting object: C-third |
|
209 | 213 | """ |
|
210 | tt.ipexec_validate(self.fname, out) | |
|
214 | if dec.module_not_available('sqlite3'): | |
|
215 | err = 'WARNING: IPython History requires SQLite, your history will not be saved\n' | |
|
216 | else: | |
|
217 | err = None | |
|
218 | tt.ipexec_validate(self.fname, out, err) |
@@ -16,7 +16,10 b' import os' | |||
|
16 | 16 | import cPickle as pickle |
|
17 | 17 | from datetime import datetime |
|
18 | 18 | |
|
19 | import sqlite3 | |
|
19 | try: | |
|
20 | import sqlite3 | |
|
21 | except ImportError: | |
|
22 | sqlite3 = None | |
|
20 | 23 | |
|
21 | 24 | from zmq.eventloop import ioloop |
|
22 | 25 | |
@@ -99,7 +102,10 b' class SQLiteDB(BaseDB):' | |||
|
99 | 102 | in tasks from previous sessions being available via Clients' db_query and |
|
100 | 103 | get_result methods.""") |
|
101 | 104 | |
|
102 | _db = Instance('sqlite3.Connection') | |
|
105 | if sqlite3 is not None: | |
|
106 | _db = Instance('sqlite3.Connection') | |
|
107 | else: | |
|
108 | _db = None | |
|
103 | 109 | # the ordered list of column names |
|
104 | 110 | _keys = List(['msg_id' , |
|
105 | 111 | 'header' , |
@@ -145,6 +151,8 b' class SQLiteDB(BaseDB):' | |||
|
145 | 151 | |
|
146 | 152 | def __init__(self, **kwargs): |
|
147 | 153 | super(SQLiteDB, self).__init__(**kwargs) |
|
154 | if sqlite3 is None: | |
|
155 | raise ImportError("SQLiteDB requires sqlite3") | |
|
148 | 156 | if not self.table: |
|
149 | 157 | # use session, and prefix _, since starting with # is illegal |
|
150 | 158 | self.table = '_'+self.session.replace('-','_') |
@@ -24,13 +24,12 b' import time' | |||
|
24 | 24 | from datetime import datetime, timedelta |
|
25 | 25 | from unittest import TestCase |
|
26 | 26 | |
|
27 | from nose import SkipTest | |
|
28 | ||
|
29 | 27 | from IPython.parallel import error |
|
30 | 28 | from IPython.parallel.controller.dictdb import DictDB |
|
31 | 29 | from IPython.parallel.controller.sqlitedb import SQLiteDB |
|
32 | 30 | from IPython.parallel.controller.hub import init_record, empty_record |
|
33 | 31 | |
|
32 | from IPython.testing import decorators as dec | |
|
34 | 33 | from IPython.zmq.session import Session |
|
35 | 34 | |
|
36 | 35 | |
@@ -171,8 +170,11 b' class TestDictBackend(TestCase):' | |||
|
171 | 170 | self.db.drop_matching_records(query) |
|
172 | 171 | recs = self.db.find_records(query) |
|
173 | 172 | self.assertEquals(len(recs), 0) |
|
174 | ||
|
173 | ||
|
174 | ||
|
175 | 175 | class TestSQLiteBackend(TestDictBackend): |
|
176 | ||
|
177 | @dec.skip_without('sqlite3') | |
|
176 | 178 | def create_db(self): |
|
177 | 179 | return SQLiteDB(location=tempfile.gettempdir()) |
|
178 | 180 |
@@ -128,6 +128,7 b" have['pymongo'] = test_for('pymongo')" | |||
|
128 | 128 | have['wx'] = test_for('wx') |
|
129 | 129 | have['wx.aui'] = test_for('wx.aui') |
|
130 | 130 | have['qt'] = test_for('IPython.external.qt') |
|
131 | have['sqlite3'] = test_for('sqlite3') | |
|
131 | 132 | |
|
132 | 133 | have['tornado'] = test_for('tornado.version_info', (2,1,0), callback=None) |
|
133 | 134 | |
@@ -204,7 +205,9 b' def make_exclude():' | |||
|
204 | 205 | ipjoin('config', 'default'), |
|
205 | 206 | ipjoin('config', 'profile'), |
|
206 | 207 | ] |
|
207 | ||
|
208 | if not have['sqlite3']: | |
|
209 | exclusions.append(ipjoin('core', 'tests', 'test_history')) | |
|
210 | exclusions.append(ipjoin('core', 'history')) | |
|
208 | 211 | if not have['wx']: |
|
209 | 212 | exclusions.append(ipjoin('lib', 'inputhookwx')) |
|
210 | 213 |
General Comments 0
You need to be logged in to leave comments.
Login now