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