##// END OF EJS Templates
Catch sqlite DatabaseErrors in more places when reading the history database....
Thomas Kluyver -
Show More
@@ -1,770 +1,786 b''
1 """ History related magics and functionality """
1 """ History related magics and functionality """
2 #-----------------------------------------------------------------------------
2 #-----------------------------------------------------------------------------
3 # Copyright (C) 2010-2011 The IPython Development Team.
3 # Copyright (C) 2010-2011 The IPython Development Team.
4 #
4 #
5 # Distributed under the terms of the BSD License.
5 # Distributed under the terms of the BSD License.
6 #
6 #
7 # The full license is in the file COPYING.txt, distributed with this software.
7 # The full license is in the file COPYING.txt, distributed with this software.
8 #-----------------------------------------------------------------------------
8 #-----------------------------------------------------------------------------
9
9
10 #-----------------------------------------------------------------------------
10 #-----------------------------------------------------------------------------
11 # Imports
11 # Imports
12 #-----------------------------------------------------------------------------
12 #-----------------------------------------------------------------------------
13 from __future__ import print_function
13 from __future__ import print_function
14
14
15 # Stdlib imports
15 # Stdlib imports
16 import atexit
16 import atexit
17 import datetime
17 import datetime
18 import os
18 import os
19 import re
19 import re
20 try:
20 try:
21 import sqlite3
21 import sqlite3
22 except ImportError:
22 except ImportError:
23 sqlite3 = None
23 sqlite3 = None
24 import threading
24 import threading
25
25
26 # Our own packages
26 # Our own packages
27 from IPython.config.configurable import Configurable
27 from IPython.config.configurable import Configurable
28 from IPython.external.decorator import decorator
28 from IPython.external.decorator import decorator
29 from IPython.utils.path import locate_profile
29 from IPython.utils.path import locate_profile
30 from IPython.utils.traitlets import (
30 from IPython.utils.traitlets import (
31 Any, Bool, Dict, Instance, Integer, List, Unicode, TraitError,
31 Any, Bool, Dict, Instance, Integer, List, Unicode, TraitError,
32 )
32 )
33 from IPython.utils.warn import warn
33 from IPython.utils.warn import warn
34
34
35 #-----------------------------------------------------------------------------
35 #-----------------------------------------------------------------------------
36 # Classes and functions
36 # Classes and functions
37 #-----------------------------------------------------------------------------
37 #-----------------------------------------------------------------------------
38
38
39 class DummyDB(object):
39 class DummyDB(object):
40 """Dummy DB that will act as a black hole for history.
40 """Dummy DB that will act as a black hole for history.
41
41
42 Only used in the absence of sqlite"""
42 Only used in the absence of sqlite"""
43 def execute(*args, **kwargs):
43 def execute(*args, **kwargs):
44 return []
44 return []
45
45
46 def commit(self, *args, **kwargs):
46 def commit(self, *args, **kwargs):
47 pass
47 pass
48
48
49 def __enter__(self, *args, **kwargs):
49 def __enter__(self, *args, **kwargs):
50 pass
50 pass
51
51
52 def __exit__(self, *args, **kwargs):
52 def __exit__(self, *args, **kwargs):
53 pass
53 pass
54
54
55
55
56 @decorator
56 @decorator
57 def needs_sqlite(f, self, *a, **kw):
57 def needs_sqlite(f, self, *a, **kw):
58 """return an empty list in the absence of sqlite"""
58 """return an empty list in the absence of sqlite"""
59 if sqlite3 is None or not self.enabled:
59 if sqlite3 is None or not self.enabled:
60 return []
60 return []
61 else:
61 else:
62 return f(self, *a, **kw)
62 return f(self, *a, **kw)
63
63
64
64
65 if sqlite3 is not None:
66 DatabaseError = sqlite3.DatabaseError
67 else:
68 class DatabaseError(Exception):
69 "Dummy exception when sqlite could not be imported. Should never occur."
70
71 @decorator
72 def catch_corrupt_db(f, self, *a, **kw):
73 """A decorator which wraps HistoryAccessor method calls to catch errors from
74 a corrupt SQLite database, move the old database out of the way, create a
75 new one, and optionally retry the function.
76 """
77 try:
78 return f(self, *a, **kw)
79 except DatabaseError:
80 if os.path.isfile(self.hist_file):
81 # Try to move the file out of the way
82 base,ext = os.path.splitext(self.hist_file)
83 newpath = base + '-corrupt' + ext
84 os.rename(self.hist_file, newpath)
85 self.init_db()
86 print("ERROR! History file wasn't a valid SQLite database.",
87 "It was moved to %s" % newpath, "and a new file created.")
88 return []
89
90 else:
91 # The hist_file is probably :memory: or something else.
92 raise
93
94
95
65 class HistoryAccessor(Configurable):
96 class HistoryAccessor(Configurable):
66 """Access the history database without adding to it.
97 """Access the history database without adding to it.
67
98
68 This is intended for use by standalone history tools. IPython shells use
99 This is intended for use by standalone history tools. IPython shells use
69 HistoryManager, below, which is a subclass of this."""
100 HistoryManager, below, which is a subclass of this."""
70
101
71 # String holding the path to the history file
102 # String holding the path to the history file
72 hist_file = Unicode(config=True,
103 hist_file = Unicode(config=True,
73 help="""Path to file to use for SQLite history database.
104 help="""Path to file to use for SQLite history database.
74
105
75 By default, IPython will put the history database in the IPython
106 By default, IPython will put the history database in the IPython
76 profile directory. If you would rather share one history among
107 profile directory. If you would rather share one history among
77 profiles, you can set this value in each, so that they are consistent.
108 profiles, you can set this value in each, so that they are consistent.
78
109
79 Due to an issue with fcntl, SQLite is known to misbehave on some NFS
110 Due to an issue with fcntl, SQLite is known to misbehave on some NFS
80 mounts. If you see IPython hanging, try setting this to something on a
111 mounts. If you see IPython hanging, try setting this to something on a
81 local disk, e.g::
112 local disk, e.g::
82
113
83 ipython --HistoryManager.hist_file=/tmp/ipython_hist.sqlite
114 ipython --HistoryManager.hist_file=/tmp/ipython_hist.sqlite
84
115
85 """)
116 """)
86
117
87 enabled = Bool(True, config=True,
118 enabled = Bool(True, config=True,
88 help="""enable the SQLite history
119 help="""enable the SQLite history
89
120
90 set enabled=False to disable the SQLite history,
121 set enabled=False to disable the SQLite history,
91 in which case there will be no stored history, no SQLite connection,
122 in which case there will be no stored history, no SQLite connection,
92 and no background saving thread. This may be necessary in some
123 and no background saving thread. This may be necessary in some
93 threaded environments where IPython is embedded.
124 threaded environments where IPython is embedded.
94 """
125 """
95 )
126 )
96
127
97 connection_options = Dict(config=True,
128 connection_options = Dict(config=True,
98 help="""Options for configuring the SQLite connection
129 help="""Options for configuring the SQLite connection
99
130
100 These options are passed as keyword args to sqlite3.connect
131 These options are passed as keyword args to sqlite3.connect
101 when establishing database conenctions.
132 when establishing database conenctions.
102 """
133 """
103 )
134 )
104
135
105 # The SQLite database
136 # The SQLite database
106 db = Any()
137 db = Any()
107 def _db_changed(self, name, old, new):
138 def _db_changed(self, name, old, new):
108 """validate the db, since it can be an Instance of two different types"""
139 """validate the db, since it can be an Instance of two different types"""
109 connection_types = (DummyDB,)
140 connection_types = (DummyDB,)
110 if sqlite3 is not None:
141 if sqlite3 is not None:
111 connection_types = (DummyDB, sqlite3.Connection)
142 connection_types = (DummyDB, sqlite3.Connection)
112 if not isinstance(new, connection_types):
143 if not isinstance(new, connection_types):
113 msg = "%s.db must be sqlite3 Connection or DummyDB, not %r" % \
144 msg = "%s.db must be sqlite3 Connection or DummyDB, not %r" % \
114 (self.__class__.__name__, new)
145 (self.__class__.__name__, new)
115 raise TraitError(msg)
146 raise TraitError(msg)
116
147
117 def __init__(self, profile='default', hist_file=u'', config=None, **traits):
148 def __init__(self, profile='default', hist_file=u'', config=None, **traits):
118 """Create a new history accessor.
149 """Create a new history accessor.
119
150
120 Parameters
151 Parameters
121 ----------
152 ----------
122 profile : str
153 profile : str
123 The name of the profile from which to open history.
154 The name of the profile from which to open history.
124 hist_file : str
155 hist_file : str
125 Path to an SQLite history database stored by IPython. If specified,
156 Path to an SQLite history database stored by IPython. If specified,
126 hist_file overrides profile.
157 hist_file overrides profile.
127 config :
158 config :
128 Config object. hist_file can also be set through this.
159 Config object. hist_file can also be set through this.
129 """
160 """
130 # We need a pointer back to the shell for various tasks.
161 # We need a pointer back to the shell for various tasks.
131 super(HistoryAccessor, self).__init__(config=config, **traits)
162 super(HistoryAccessor, self).__init__(config=config, **traits)
132 # defer setting hist_file from kwarg until after init,
163 # defer setting hist_file from kwarg until after init,
133 # otherwise the default kwarg value would clobber any value
164 # otherwise the default kwarg value would clobber any value
134 # set by config
165 # set by config
135 if hist_file:
166 if hist_file:
136 self.hist_file = hist_file
167 self.hist_file = hist_file
137
168
138 if self.hist_file == u'':
169 if self.hist_file == u'':
139 # No one has set the hist_file, yet.
170 # No one has set the hist_file, yet.
140 self.hist_file = self._get_hist_file_name(profile)
171 self.hist_file = self._get_hist_file_name(profile)
141
172
142 if sqlite3 is None and self.enabled:
173 if sqlite3 is None and self.enabled:
143 warn("IPython History requires SQLite, your history will not be saved\n")
174 warn("IPython History requires SQLite, your history will not be saved\n")
144 self.enabled = False
175 self.enabled = False
145
176
146 if sqlite3 is not None:
177 self.init_db()
147 DatabaseError = sqlite3.DatabaseError
148 else:
149 DatabaseError = Exception
150
151 try:
152 self.init_db()
153 except DatabaseError:
154 if os.path.isfile(self.hist_file):
155 # Try to move the file out of the way
156 base,ext = os.path.splitext(self.hist_file)
157 newpath = base + '-corrupt' + ext
158 os.rename(self.hist_file, newpath)
159 print("ERROR! History file wasn't a valid SQLite database.",
160 "It was moved to %s" % newpath, "and a new file created.")
161 self.init_db()
162 else:
163 # The hist_file is probably :memory: or something else.
164 raise
165
178
166 def _get_hist_file_name(self, profile='default'):
179 def _get_hist_file_name(self, profile='default'):
167 """Find the history file for the given profile name.
180 """Find the history file for the given profile name.
168
181
169 This is overridden by the HistoryManager subclass, to use the shell's
182 This is overridden by the HistoryManager subclass, to use the shell's
170 active profile.
183 active profile.
171
184
172 Parameters
185 Parameters
173 ----------
186 ----------
174 profile : str
187 profile : str
175 The name of a profile which has a history file.
188 The name of a profile which has a history file.
176 """
189 """
177 return os.path.join(locate_profile(profile), 'history.sqlite')
190 return os.path.join(locate_profile(profile), 'history.sqlite')
178
191
192 @catch_corrupt_db
179 def init_db(self):
193 def init_db(self):
180 """Connect to the database, and create tables if necessary."""
194 """Connect to the database, and create tables if necessary."""
181 if not self.enabled:
195 if not self.enabled:
182 self.db = DummyDB()
196 self.db = DummyDB()
183 return
197 return
184
198
185 # use detect_types so that timestamps return datetime objects
199 # use detect_types so that timestamps return datetime objects
186 kwargs = dict(detect_types=sqlite3.PARSE_DECLTYPES|sqlite3.PARSE_COLNAMES)
200 kwargs = dict(detect_types=sqlite3.PARSE_DECLTYPES|sqlite3.PARSE_COLNAMES)
187 kwargs.update(self.connection_options)
201 kwargs.update(self.connection_options)
188 self.db = sqlite3.connect(self.hist_file, **kwargs)
202 self.db = sqlite3.connect(self.hist_file, **kwargs)
189 self.db.execute("""CREATE TABLE IF NOT EXISTS sessions (session integer
203 self.db.execute("""CREATE TABLE IF NOT EXISTS sessions (session integer
190 primary key autoincrement, start timestamp,
204 primary key autoincrement, start timestamp,
191 end timestamp, num_cmds integer, remark text)""")
205 end timestamp, num_cmds integer, remark text)""")
192 self.db.execute("""CREATE TABLE IF NOT EXISTS history
206 self.db.execute("""CREATE TABLE IF NOT EXISTS history
193 (session integer, line integer, source text, source_raw text,
207 (session integer, line integer, source text, source_raw text,
194 PRIMARY KEY (session, line))""")
208 PRIMARY KEY (session, line))""")
195 # Output history is optional, but ensure the table's there so it can be
209 # Output history is optional, but ensure the table's there so it can be
196 # enabled later.
210 # enabled later.
197 self.db.execute("""CREATE TABLE IF NOT EXISTS output_history
211 self.db.execute("""CREATE TABLE IF NOT EXISTS output_history
198 (session integer, line integer, output text,
212 (session integer, line integer, output text,
199 PRIMARY KEY (session, line))""")
213 PRIMARY KEY (session, line))""")
200 self.db.commit()
214 self.db.commit()
201
215
202 def writeout_cache(self):
216 def writeout_cache(self):
203 """Overridden by HistoryManager to dump the cache before certain
217 """Overridden by HistoryManager to dump the cache before certain
204 database lookups."""
218 database lookups."""
205 pass
219 pass
206
220
207 ## -------------------------------
221 ## -------------------------------
208 ## Methods for retrieving history:
222 ## Methods for retrieving history:
209 ## -------------------------------
223 ## -------------------------------
224 @catch_corrupt_db
210 def _run_sql(self, sql, params, raw=True, output=False):
225 def _run_sql(self, sql, params, raw=True, output=False):
211 """Prepares and runs an SQL query for the history database.
226 """Prepares and runs an SQL query for the history database.
212
227
213 Parameters
228 Parameters
214 ----------
229 ----------
215 sql : str
230 sql : str
216 Any filtering expressions to go after SELECT ... FROM ...
231 Any filtering expressions to go after SELECT ... FROM ...
217 params : tuple
232 params : tuple
218 Parameters passed to the SQL query (to replace "?")
233 Parameters passed to the SQL query (to replace "?")
219 raw, output : bool
234 raw, output : bool
220 See :meth:`get_range`
235 See :meth:`get_range`
221
236
222 Returns
237 Returns
223 -------
238 -------
224 Tuples as :meth:`get_range`
239 Tuples as :meth:`get_range`
225 """
240 """
226 toget = 'source_raw' if raw else 'source'
241 toget = 'source_raw' if raw else 'source'
227 sqlfrom = "history"
242 sqlfrom = "history"
228 if output:
243 if output:
229 sqlfrom = "history LEFT JOIN output_history USING (session, line)"
244 sqlfrom = "history LEFT JOIN output_history USING (session, line)"
230 toget = "history.%s, output_history.output" % toget
245 toget = "history.%s, output_history.output" % toget
231 cur = self.db.execute("SELECT session, line, %s FROM %s " %\
246 cur = self.db.execute("SELECT session, line, %s FROM %s " %\
232 (toget, sqlfrom) + sql, params)
247 (toget, sqlfrom) + sql, params)
233 if output: # Regroup into 3-tuples, and parse JSON
248 if output: # Regroup into 3-tuples, and parse JSON
234 return ((ses, lin, (inp, out)) for ses, lin, inp, out in cur)
249 return ((ses, lin, (inp, out)) for ses, lin, inp, out in cur)
235 return cur
250 return cur
236
251
237 @needs_sqlite
252 @needs_sqlite
253 @catch_corrupt_db
238 def get_session_info(self, session=0):
254 def get_session_info(self, session=0):
239 """get info about a session
255 """get info about a session
240
256
241 Parameters
257 Parameters
242 ----------
258 ----------
243
259
244 session : int
260 session : int
245 Session number to retrieve. The current session is 0, and negative
261 Session number to retrieve. The current session is 0, and negative
246 numbers count back from current session, so -1 is previous session.
262 numbers count back from current session, so -1 is previous session.
247
263
248 Returns
264 Returns
249 -------
265 -------
250
266
251 (session_id [int], start [datetime], end [datetime], num_cmds [int],
267 (session_id [int], start [datetime], end [datetime], num_cmds [int],
252 remark [unicode])
268 remark [unicode])
253
269
254 Sessions that are running or did not exit cleanly will have `end=None`
270 Sessions that are running or did not exit cleanly will have `end=None`
255 and `num_cmds=None`.
271 and `num_cmds=None`.
256
272
257 """
273 """
258
274
259 if session <= 0:
275 if session <= 0:
260 session += self.session_number
276 session += self.session_number
261
277
262 query = "SELECT * from sessions where session == ?"
278 query = "SELECT * from sessions where session == ?"
263 return self.db.execute(query, (session,)).fetchone()
279 return self.db.execute(query, (session,)).fetchone()
264
280
265 def get_tail(self, n=10, raw=True, output=False, include_latest=False):
281 def get_tail(self, n=10, raw=True, output=False, include_latest=False):
266 """Get the last n lines from the history database.
282 """Get the last n lines from the history database.
267
283
268 Parameters
284 Parameters
269 ----------
285 ----------
270 n : int
286 n : int
271 The number of lines to get
287 The number of lines to get
272 raw, output : bool
288 raw, output : bool
273 See :meth:`get_range`
289 See :meth:`get_range`
274 include_latest : bool
290 include_latest : bool
275 If False (default), n+1 lines are fetched, and the latest one
291 If False (default), n+1 lines are fetched, and the latest one
276 is discarded. This is intended to be used where the function
292 is discarded. This is intended to be used where the function
277 is called by a user command, which it should not return.
293 is called by a user command, which it should not return.
278
294
279 Returns
295 Returns
280 -------
296 -------
281 Tuples as :meth:`get_range`
297 Tuples as :meth:`get_range`
282 """
298 """
283 self.writeout_cache()
299 self.writeout_cache()
284 if not include_latest:
300 if not include_latest:
285 n += 1
301 n += 1
286 cur = self._run_sql("ORDER BY session DESC, line DESC LIMIT ?",
302 cur = self._run_sql("ORDER BY session DESC, line DESC LIMIT ?",
287 (n,), raw=raw, output=output)
303 (n,), raw=raw, output=output)
288 if not include_latest:
304 if not include_latest:
289 return reversed(list(cur)[1:])
305 return reversed(list(cur)[1:])
290 return reversed(list(cur))
306 return reversed(list(cur))
291
307
292 def search(self, pattern="*", raw=True, search_raw=True,
308 def search(self, pattern="*", raw=True, search_raw=True,
293 output=False, n=None):
309 output=False, n=None):
294 """Search the database using unix glob-style matching (wildcards
310 """Search the database using unix glob-style matching (wildcards
295 * and ?).
311 * and ?).
296
312
297 Parameters
313 Parameters
298 ----------
314 ----------
299 pattern : str
315 pattern : str
300 The wildcarded pattern to match when searching
316 The wildcarded pattern to match when searching
301 search_raw : bool
317 search_raw : bool
302 If True, search the raw input, otherwise, the parsed input
318 If True, search the raw input, otherwise, the parsed input
303 raw, output : bool
319 raw, output : bool
304 See :meth:`get_range`
320 See :meth:`get_range`
305 n : None or int
321 n : None or int
306 If an integer is given, it defines the limit of
322 If an integer is given, it defines the limit of
307 returned entries.
323 returned entries.
308
324
309 Returns
325 Returns
310 -------
326 -------
311 Tuples as :meth:`get_range`
327 Tuples as :meth:`get_range`
312 """
328 """
313 tosearch = "source_raw" if search_raw else "source"
329 tosearch = "source_raw" if search_raw else "source"
314 if output:
330 if output:
315 tosearch = "history." + tosearch
331 tosearch = "history." + tosearch
316 self.writeout_cache()
332 self.writeout_cache()
317 sqlform = "WHERE %s GLOB ?" % tosearch
333 sqlform = "WHERE %s GLOB ?" % tosearch
318 params = (pattern,)
334 params = (pattern,)
319 if n is not None:
335 if n is not None:
320 sqlform += " ORDER BY session DESC, line DESC LIMIT ?"
336 sqlform += " ORDER BY session DESC, line DESC LIMIT ?"
321 params += (n,)
337 params += (n,)
322 cur = self._run_sql(sqlform, params, raw=raw, output=output)
338 cur = self._run_sql(sqlform, params, raw=raw, output=output)
323 if n is not None:
339 if n is not None:
324 return reversed(list(cur))
340 return reversed(list(cur))
325 return cur
341 return cur
326
342
327 def get_range(self, session, start=1, stop=None, raw=True,output=False):
343 def get_range(self, session, start=1, stop=None, raw=True,output=False):
328 """Retrieve input by session.
344 """Retrieve input by session.
329
345
330 Parameters
346 Parameters
331 ----------
347 ----------
332 session : int
348 session : int
333 Session number to retrieve.
349 Session number to retrieve.
334 start : int
350 start : int
335 First line to retrieve.
351 First line to retrieve.
336 stop : int
352 stop : int
337 End of line range (excluded from output itself). If None, retrieve
353 End of line range (excluded from output itself). If None, retrieve
338 to the end of the session.
354 to the end of the session.
339 raw : bool
355 raw : bool
340 If True, return untranslated input
356 If True, return untranslated input
341 output : bool
357 output : bool
342 If True, attempt to include output. This will be 'real' Python
358 If True, attempt to include output. This will be 'real' Python
343 objects for the current session, or text reprs from previous
359 objects for the current session, or text reprs from previous
344 sessions if db_log_output was enabled at the time. Where no output
360 sessions if db_log_output was enabled at the time. Where no output
345 is found, None is used.
361 is found, None is used.
346
362
347 Returns
363 Returns
348 -------
364 -------
349 An iterator over the desired lines. Each line is a 3-tuple, either
365 An iterator over the desired lines. Each line is a 3-tuple, either
350 (session, line, input) if output is False, or
366 (session, line, input) if output is False, or
351 (session, line, (input, output)) if output is True.
367 (session, line, (input, output)) if output is True.
352 """
368 """
353 if stop:
369 if stop:
354 lineclause = "line >= ? AND line < ?"
370 lineclause = "line >= ? AND line < ?"
355 params = (session, start, stop)
371 params = (session, start, stop)
356 else:
372 else:
357 lineclause = "line>=?"
373 lineclause = "line>=?"
358 params = (session, start)
374 params = (session, start)
359
375
360 return self._run_sql("WHERE session==? AND %s" % lineclause,
376 return self._run_sql("WHERE session==? AND %s" % lineclause,
361 params, raw=raw, output=output)
377 params, raw=raw, output=output)
362
378
363 def get_range_by_str(self, rangestr, raw=True, output=False):
379 def get_range_by_str(self, rangestr, raw=True, output=False):
364 """Get lines of history from a string of ranges, as used by magic
380 """Get lines of history from a string of ranges, as used by magic
365 commands %hist, %save, %macro, etc.
381 commands %hist, %save, %macro, etc.
366
382
367 Parameters
383 Parameters
368 ----------
384 ----------
369 rangestr : str
385 rangestr : str
370 A string specifying ranges, e.g. "5 ~2/1-4". See
386 A string specifying ranges, e.g. "5 ~2/1-4". See
371 :func:`magic_history` for full details.
387 :func:`magic_history` for full details.
372 raw, output : bool
388 raw, output : bool
373 As :meth:`get_range`
389 As :meth:`get_range`
374
390
375 Returns
391 Returns
376 -------
392 -------
377 Tuples as :meth:`get_range`
393 Tuples as :meth:`get_range`
378 """
394 """
379 for sess, s, e in extract_hist_ranges(rangestr):
395 for sess, s, e in extract_hist_ranges(rangestr):
380 for line in self.get_range(sess, s, e, raw=raw, output=output):
396 for line in self.get_range(sess, s, e, raw=raw, output=output):
381 yield line
397 yield line
382
398
383
399
384 class HistoryManager(HistoryAccessor):
400 class HistoryManager(HistoryAccessor):
385 """A class to organize all history-related functionality in one place.
401 """A class to organize all history-related functionality in one place.
386 """
402 """
387 # Public interface
403 # Public interface
388
404
389 # An instance of the IPython shell we are attached to
405 # An instance of the IPython shell we are attached to
390 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC')
406 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC')
391 # Lists to hold processed and raw history. These start with a blank entry
407 # Lists to hold processed and raw history. These start with a blank entry
392 # so that we can index them starting from 1
408 # so that we can index them starting from 1
393 input_hist_parsed = List([""])
409 input_hist_parsed = List([""])
394 input_hist_raw = List([""])
410 input_hist_raw = List([""])
395 # A list of directories visited during session
411 # A list of directories visited during session
396 dir_hist = List()
412 dir_hist = List()
397 def _dir_hist_default(self):
413 def _dir_hist_default(self):
398 try:
414 try:
399 return [os.getcwdu()]
415 return [os.getcwdu()]
400 except OSError:
416 except OSError:
401 return []
417 return []
402
418
403 # A dict of output history, keyed with ints from the shell's
419 # A dict of output history, keyed with ints from the shell's
404 # execution count.
420 # execution count.
405 output_hist = Dict()
421 output_hist = Dict()
406 # The text/plain repr of outputs.
422 # The text/plain repr of outputs.
407 output_hist_reprs = Dict()
423 output_hist_reprs = Dict()
408
424
409 # The number of the current session in the history database
425 # The number of the current session in the history database
410 session_number = Integer()
426 session_number = Integer()
411 # Should we log output to the database? (default no)
427 # Should we log output to the database? (default no)
412 db_log_output = Bool(False, config=True)
428 db_log_output = Bool(False, config=True)
413 # Write to database every x commands (higher values save disk access & power)
429 # Write to database every x commands (higher values save disk access & power)
414 # Values of 1 or less effectively disable caching.
430 # Values of 1 or less effectively disable caching.
415 db_cache_size = Integer(0, config=True)
431 db_cache_size = Integer(0, config=True)
416 # The input and output caches
432 # The input and output caches
417 db_input_cache = List()
433 db_input_cache = List()
418 db_output_cache = List()
434 db_output_cache = List()
419
435
420 # History saving in separate thread
436 # History saving in separate thread
421 save_thread = Instance('IPython.core.history.HistorySavingThread')
437 save_thread = Instance('IPython.core.history.HistorySavingThread')
422 try: # Event is a function returning an instance of _Event...
438 try: # Event is a function returning an instance of _Event...
423 save_flag = Instance(threading._Event)
439 save_flag = Instance(threading._Event)
424 except AttributeError: # ...until Python 3.3, when it's a class.
440 except AttributeError: # ...until Python 3.3, when it's a class.
425 save_flag = Instance(threading.Event)
441 save_flag = Instance(threading.Event)
426
442
427 # Private interface
443 # Private interface
428 # Variables used to store the three last inputs from the user. On each new
444 # Variables used to store the three last inputs from the user. On each new
429 # history update, we populate the user's namespace with these, shifted as
445 # history update, we populate the user's namespace with these, shifted as
430 # necessary.
446 # necessary.
431 _i00 = Unicode(u'')
447 _i00 = Unicode(u'')
432 _i = Unicode(u'')
448 _i = Unicode(u'')
433 _ii = Unicode(u'')
449 _ii = Unicode(u'')
434 _iii = Unicode(u'')
450 _iii = Unicode(u'')
435
451
436 # A regex matching all forms of the exit command, so that we don't store
452 # A regex matching all forms of the exit command, so that we don't store
437 # them in the history (it's annoying to rewind the first entry and land on
453 # them in the history (it's annoying to rewind the first entry and land on
438 # an exit call).
454 # an exit call).
439 _exit_re = re.compile(r"(exit|quit)(\s*\(.*\))?$")
455 _exit_re = re.compile(r"(exit|quit)(\s*\(.*\))?$")
440
456
441 def __init__(self, shell=None, config=None, **traits):
457 def __init__(self, shell=None, config=None, **traits):
442 """Create a new history manager associated with a shell instance.
458 """Create a new history manager associated with a shell instance.
443 """
459 """
444 # We need a pointer back to the shell for various tasks.
460 # We need a pointer back to the shell for various tasks.
445 super(HistoryManager, self).__init__(shell=shell, config=config,
461 super(HistoryManager, self).__init__(shell=shell, config=config,
446 **traits)
462 **traits)
447 self.save_flag = threading.Event()
463 self.save_flag = threading.Event()
448 self.db_input_cache_lock = threading.Lock()
464 self.db_input_cache_lock = threading.Lock()
449 self.db_output_cache_lock = threading.Lock()
465 self.db_output_cache_lock = threading.Lock()
450 if self.enabled and self.hist_file != ':memory:':
466 if self.enabled and self.hist_file != ':memory:':
451 self.save_thread = HistorySavingThread(self)
467 self.save_thread = HistorySavingThread(self)
452 self.save_thread.start()
468 self.save_thread.start()
453
469
454 self.new_session()
470 self.new_session()
455
471
456 def _get_hist_file_name(self, profile=None):
472 def _get_hist_file_name(self, profile=None):
457 """Get default history file name based on the Shell's profile.
473 """Get default history file name based on the Shell's profile.
458
474
459 The profile parameter is ignored, but must exist for compatibility with
475 The profile parameter is ignored, but must exist for compatibility with
460 the parent class."""
476 the parent class."""
461 profile_dir = self.shell.profile_dir.location
477 profile_dir = self.shell.profile_dir.location
462 return os.path.join(profile_dir, 'history.sqlite')
478 return os.path.join(profile_dir, 'history.sqlite')
463
479
464 @needs_sqlite
480 @needs_sqlite
465 def new_session(self, conn=None):
481 def new_session(self, conn=None):
466 """Get a new session number."""
482 """Get a new session number."""
467 if conn is None:
483 if conn is None:
468 conn = self.db
484 conn = self.db
469
485
470 with conn:
486 with conn:
471 cur = conn.execute("""INSERT INTO sessions VALUES (NULL, ?, NULL,
487 cur = conn.execute("""INSERT INTO sessions VALUES (NULL, ?, NULL,
472 NULL, "") """, (datetime.datetime.now(),))
488 NULL, "") """, (datetime.datetime.now(),))
473 self.session_number = cur.lastrowid
489 self.session_number = cur.lastrowid
474
490
475 def end_session(self):
491 def end_session(self):
476 """Close the database session, filling in the end time and line count."""
492 """Close the database session, filling in the end time and line count."""
477 self.writeout_cache()
493 self.writeout_cache()
478 with self.db:
494 with self.db:
479 self.db.execute("""UPDATE sessions SET end=?, num_cmds=? WHERE
495 self.db.execute("""UPDATE sessions SET end=?, num_cmds=? WHERE
480 session==?""", (datetime.datetime.now(),
496 session==?""", (datetime.datetime.now(),
481 len(self.input_hist_parsed)-1, self.session_number))
497 len(self.input_hist_parsed)-1, self.session_number))
482 self.session_number = 0
498 self.session_number = 0
483
499
484 def name_session(self, name):
500 def name_session(self, name):
485 """Give the current session a name in the history database."""
501 """Give the current session a name in the history database."""
486 with self.db:
502 with self.db:
487 self.db.execute("UPDATE sessions SET remark=? WHERE session==?",
503 self.db.execute("UPDATE sessions SET remark=? WHERE session==?",
488 (name, self.session_number))
504 (name, self.session_number))
489
505
490 def reset(self, new_session=True):
506 def reset(self, new_session=True):
491 """Clear the session history, releasing all object references, and
507 """Clear the session history, releasing all object references, and
492 optionally open a new session."""
508 optionally open a new session."""
493 self.output_hist.clear()
509 self.output_hist.clear()
494 # The directory history can't be completely empty
510 # The directory history can't be completely empty
495 self.dir_hist[:] = [os.getcwdu()]
511 self.dir_hist[:] = [os.getcwdu()]
496
512
497 if new_session:
513 if new_session:
498 if self.session_number:
514 if self.session_number:
499 self.end_session()
515 self.end_session()
500 self.input_hist_parsed[:] = [""]
516 self.input_hist_parsed[:] = [""]
501 self.input_hist_raw[:] = [""]
517 self.input_hist_raw[:] = [""]
502 self.new_session()
518 self.new_session()
503
519
504 # ------------------------------
520 # ------------------------------
505 # Methods for retrieving history
521 # Methods for retrieving history
506 # ------------------------------
522 # ------------------------------
507 def _get_range_session(self, start=1, stop=None, raw=True, output=False):
523 def _get_range_session(self, start=1, stop=None, raw=True, output=False):
508 """Get input and output history from the current session. Called by
524 """Get input and output history from the current session. Called by
509 get_range, and takes similar parameters."""
525 get_range, and takes similar parameters."""
510 input_hist = self.input_hist_raw if raw else self.input_hist_parsed
526 input_hist = self.input_hist_raw if raw else self.input_hist_parsed
511
527
512 n = len(input_hist)
528 n = len(input_hist)
513 if start < 0:
529 if start < 0:
514 start += n
530 start += n
515 if not stop or (stop > n):
531 if not stop or (stop > n):
516 stop = n
532 stop = n
517 elif stop < 0:
533 elif stop < 0:
518 stop += n
534 stop += n
519
535
520 for i in range(start, stop):
536 for i in range(start, stop):
521 if output:
537 if output:
522 line = (input_hist[i], self.output_hist_reprs.get(i))
538 line = (input_hist[i], self.output_hist_reprs.get(i))
523 else:
539 else:
524 line = input_hist[i]
540 line = input_hist[i]
525 yield (0, i, line)
541 yield (0, i, line)
526
542
527 def get_range(self, session=0, start=1, stop=None, raw=True,output=False):
543 def get_range(self, session=0, start=1, stop=None, raw=True,output=False):
528 """Retrieve input by session.
544 """Retrieve input by session.
529
545
530 Parameters
546 Parameters
531 ----------
547 ----------
532 session : int
548 session : int
533 Session number to retrieve. The current session is 0, and negative
549 Session number to retrieve. The current session is 0, and negative
534 numbers count back from current session, so -1 is previous session.
550 numbers count back from current session, so -1 is previous session.
535 start : int
551 start : int
536 First line to retrieve.
552 First line to retrieve.
537 stop : int
553 stop : int
538 End of line range (excluded from output itself). If None, retrieve
554 End of line range (excluded from output itself). If None, retrieve
539 to the end of the session.
555 to the end of the session.
540 raw : bool
556 raw : bool
541 If True, return untranslated input
557 If True, return untranslated input
542 output : bool
558 output : bool
543 If True, attempt to include output. This will be 'real' Python
559 If True, attempt to include output. This will be 'real' Python
544 objects for the current session, or text reprs from previous
560 objects for the current session, or text reprs from previous
545 sessions if db_log_output was enabled at the time. Where no output
561 sessions if db_log_output was enabled at the time. Where no output
546 is found, None is used.
562 is found, None is used.
547
563
548 Returns
564 Returns
549 -------
565 -------
550 An iterator over the desired lines. Each line is a 3-tuple, either
566 An iterator over the desired lines. Each line is a 3-tuple, either
551 (session, line, input) if output is False, or
567 (session, line, input) if output is False, or
552 (session, line, (input, output)) if output is True.
568 (session, line, (input, output)) if output is True.
553 """
569 """
554 if session <= 0:
570 if session <= 0:
555 session += self.session_number
571 session += self.session_number
556 if session==self.session_number: # Current session
572 if session==self.session_number: # Current session
557 return self._get_range_session(start, stop, raw, output)
573 return self._get_range_session(start, stop, raw, output)
558 return super(HistoryManager, self).get_range(session, start, stop, raw,
574 return super(HistoryManager, self).get_range(session, start, stop, raw,
559 output)
575 output)
560
576
561 ## ----------------------------
577 ## ----------------------------
562 ## Methods for storing history:
578 ## Methods for storing history:
563 ## ----------------------------
579 ## ----------------------------
564 def store_inputs(self, line_num, source, source_raw=None):
580 def store_inputs(self, line_num, source, source_raw=None):
565 """Store source and raw input in history and create input cache
581 """Store source and raw input in history and create input cache
566 variables _i*.
582 variables _i*.
567
583
568 Parameters
584 Parameters
569 ----------
585 ----------
570 line_num : int
586 line_num : int
571 The prompt number of this input.
587 The prompt number of this input.
572
588
573 source : str
589 source : str
574 Python input.
590 Python input.
575
591
576 source_raw : str, optional
592 source_raw : str, optional
577 If given, this is the raw input without any IPython transformations
593 If given, this is the raw input without any IPython transformations
578 applied to it. If not given, ``source`` is used.
594 applied to it. If not given, ``source`` is used.
579 """
595 """
580 if source_raw is None:
596 if source_raw is None:
581 source_raw = source
597 source_raw = source
582 source = source.rstrip('\n')
598 source = source.rstrip('\n')
583 source_raw = source_raw.rstrip('\n')
599 source_raw = source_raw.rstrip('\n')
584
600
585 # do not store exit/quit commands
601 # do not store exit/quit commands
586 if self._exit_re.match(source_raw.strip()):
602 if self._exit_re.match(source_raw.strip()):
587 return
603 return
588
604
589 self.input_hist_parsed.append(source)
605 self.input_hist_parsed.append(source)
590 self.input_hist_raw.append(source_raw)
606 self.input_hist_raw.append(source_raw)
591
607
592 with self.db_input_cache_lock:
608 with self.db_input_cache_lock:
593 self.db_input_cache.append((line_num, source, source_raw))
609 self.db_input_cache.append((line_num, source, source_raw))
594 # Trigger to flush cache and write to DB.
610 # Trigger to flush cache and write to DB.
595 if len(self.db_input_cache) >= self.db_cache_size:
611 if len(self.db_input_cache) >= self.db_cache_size:
596 self.save_flag.set()
612 self.save_flag.set()
597
613
598 # update the auto _i variables
614 # update the auto _i variables
599 self._iii = self._ii
615 self._iii = self._ii
600 self._ii = self._i
616 self._ii = self._i
601 self._i = self._i00
617 self._i = self._i00
602 self._i00 = source_raw
618 self._i00 = source_raw
603
619
604 # hackish access to user namespace to create _i1,_i2... dynamically
620 # hackish access to user namespace to create _i1,_i2... dynamically
605 new_i = '_i%s' % line_num
621 new_i = '_i%s' % line_num
606 to_main = {'_i': self._i,
622 to_main = {'_i': self._i,
607 '_ii': self._ii,
623 '_ii': self._ii,
608 '_iii': self._iii,
624 '_iii': self._iii,
609 new_i : self._i00 }
625 new_i : self._i00 }
610
626
611 self.shell.push(to_main, interactive=False)
627 self.shell.push(to_main, interactive=False)
612
628
613 def store_output(self, line_num):
629 def store_output(self, line_num):
614 """If database output logging is enabled, this saves all the
630 """If database output logging is enabled, this saves all the
615 outputs from the indicated prompt number to the database. It's
631 outputs from the indicated prompt number to the database. It's
616 called by run_cell after code has been executed.
632 called by run_cell after code has been executed.
617
633
618 Parameters
634 Parameters
619 ----------
635 ----------
620 line_num : int
636 line_num : int
621 The line number from which to save outputs
637 The line number from which to save outputs
622 """
638 """
623 if (not self.db_log_output) or (line_num not in self.output_hist_reprs):
639 if (not self.db_log_output) or (line_num not in self.output_hist_reprs):
624 return
640 return
625 output = self.output_hist_reprs[line_num]
641 output = self.output_hist_reprs[line_num]
626
642
627 with self.db_output_cache_lock:
643 with self.db_output_cache_lock:
628 self.db_output_cache.append((line_num, output))
644 self.db_output_cache.append((line_num, output))
629 if self.db_cache_size <= 1:
645 if self.db_cache_size <= 1:
630 self.save_flag.set()
646 self.save_flag.set()
631
647
632 def _writeout_input_cache(self, conn):
648 def _writeout_input_cache(self, conn):
633 with conn:
649 with conn:
634 for line in self.db_input_cache:
650 for line in self.db_input_cache:
635 conn.execute("INSERT INTO history VALUES (?, ?, ?, ?)",
651 conn.execute("INSERT INTO history VALUES (?, ?, ?, ?)",
636 (self.session_number,)+line)
652 (self.session_number,)+line)
637
653
638 def _writeout_output_cache(self, conn):
654 def _writeout_output_cache(self, conn):
639 with conn:
655 with conn:
640 for line in self.db_output_cache:
656 for line in self.db_output_cache:
641 conn.execute("INSERT INTO output_history VALUES (?, ?, ?)",
657 conn.execute("INSERT INTO output_history VALUES (?, ?, ?)",
642 (self.session_number,)+line)
658 (self.session_number,)+line)
643
659
644 @needs_sqlite
660 @needs_sqlite
645 def writeout_cache(self, conn=None):
661 def writeout_cache(self, conn=None):
646 """Write any entries in the cache to the database."""
662 """Write any entries in the cache to the database."""
647 if conn is None:
663 if conn is None:
648 conn = self.db
664 conn = self.db
649
665
650 with self.db_input_cache_lock:
666 with self.db_input_cache_lock:
651 try:
667 try:
652 self._writeout_input_cache(conn)
668 self._writeout_input_cache(conn)
653 except sqlite3.IntegrityError:
669 except sqlite3.IntegrityError:
654 self.new_session(conn)
670 self.new_session(conn)
655 print("ERROR! Session/line number was not unique in",
671 print("ERROR! Session/line number was not unique in",
656 "database. History logging moved to new session",
672 "database. History logging moved to new session",
657 self.session_number)
673 self.session_number)
658 try:
674 try:
659 # Try writing to the new session. If this fails, don't
675 # Try writing to the new session. If this fails, don't
660 # recurse
676 # recurse
661 self._writeout_input_cache(conn)
677 self._writeout_input_cache(conn)
662 except sqlite3.IntegrityError:
678 except sqlite3.IntegrityError:
663 pass
679 pass
664 finally:
680 finally:
665 self.db_input_cache = []
681 self.db_input_cache = []
666
682
667 with self.db_output_cache_lock:
683 with self.db_output_cache_lock:
668 try:
684 try:
669 self._writeout_output_cache(conn)
685 self._writeout_output_cache(conn)
670 except sqlite3.IntegrityError:
686 except sqlite3.IntegrityError:
671 print("!! Session/line number for output was not unique",
687 print("!! Session/line number for output was not unique",
672 "in database. Output will not be stored.")
688 "in database. Output will not be stored.")
673 finally:
689 finally:
674 self.db_output_cache = []
690 self.db_output_cache = []
675
691
676
692
677 class HistorySavingThread(threading.Thread):
693 class HistorySavingThread(threading.Thread):
678 """This thread takes care of writing history to the database, so that
694 """This thread takes care of writing history to the database, so that
679 the UI isn't held up while that happens.
695 the UI isn't held up while that happens.
680
696
681 It waits for the HistoryManager's save_flag to be set, then writes out
697 It waits for the HistoryManager's save_flag to be set, then writes out
682 the history cache. The main thread is responsible for setting the flag when
698 the history cache. The main thread is responsible for setting the flag when
683 the cache size reaches a defined threshold."""
699 the cache size reaches a defined threshold."""
684 daemon = True
700 daemon = True
685 stop_now = False
701 stop_now = False
686 enabled = True
702 enabled = True
687 def __init__(self, history_manager):
703 def __init__(self, history_manager):
688 super(HistorySavingThread, self).__init__()
704 super(HistorySavingThread, self).__init__()
689 self.history_manager = history_manager
705 self.history_manager = history_manager
690 self.enabled = history_manager.enabled
706 self.enabled = history_manager.enabled
691 atexit.register(self.stop)
707 atexit.register(self.stop)
692
708
693 @needs_sqlite
709 @needs_sqlite
694 def run(self):
710 def run(self):
695 # We need a separate db connection per thread:
711 # We need a separate db connection per thread:
696 try:
712 try:
697 self.db = sqlite3.connect(self.history_manager.hist_file,
713 self.db = sqlite3.connect(self.history_manager.hist_file,
698 **self.history_manager.connection_options
714 **self.history_manager.connection_options
699 )
715 )
700 while True:
716 while True:
701 self.history_manager.save_flag.wait()
717 self.history_manager.save_flag.wait()
702 if self.stop_now:
718 if self.stop_now:
703 return
719 return
704 self.history_manager.save_flag.clear()
720 self.history_manager.save_flag.clear()
705 self.history_manager.writeout_cache(self.db)
721 self.history_manager.writeout_cache(self.db)
706 except Exception as e:
722 except Exception as e:
707 print(("The history saving thread hit an unexpected error (%s)."
723 print(("The history saving thread hit an unexpected error (%s)."
708 "History will not be written to the database.") % repr(e))
724 "History will not be written to the database.") % repr(e))
709
725
710 def stop(self):
726 def stop(self):
711 """This can be called from the main thread to safely stop this thread.
727 """This can be called from the main thread to safely stop this thread.
712
728
713 Note that it does not attempt to write out remaining history before
729 Note that it does not attempt to write out remaining history before
714 exiting. That should be done by calling the HistoryManager's
730 exiting. That should be done by calling the HistoryManager's
715 end_session method."""
731 end_session method."""
716 self.stop_now = True
732 self.stop_now = True
717 self.history_manager.save_flag.set()
733 self.history_manager.save_flag.set()
718 self.join()
734 self.join()
719
735
720
736
721 # To match, e.g. ~5/8-~2/3
737 # To match, e.g. ~5/8-~2/3
722 range_re = re.compile(r"""
738 range_re = re.compile(r"""
723 ((?P<startsess>~?\d+)/)?
739 ((?P<startsess>~?\d+)/)?
724 (?P<start>\d+) # Only the start line num is compulsory
740 (?P<start>\d+) # Only the start line num is compulsory
725 ((?P<sep>[\-:])
741 ((?P<sep>[\-:])
726 ((?P<endsess>~?\d+)/)?
742 ((?P<endsess>~?\d+)/)?
727 (?P<end>\d+))?
743 (?P<end>\d+))?
728 $""", re.VERBOSE)
744 $""", re.VERBOSE)
729
745
730
746
731 def extract_hist_ranges(ranges_str):
747 def extract_hist_ranges(ranges_str):
732 """Turn a string of history ranges into 3-tuples of (session, start, stop).
748 """Turn a string of history ranges into 3-tuples of (session, start, stop).
733
749
734 Examples
750 Examples
735 --------
751 --------
736 list(extract_input_ranges("~8/5-~7/4 2"))
752 list(extract_input_ranges("~8/5-~7/4 2"))
737 [(-8, 5, None), (-7, 1, 4), (0, 2, 3)]
753 [(-8, 5, None), (-7, 1, 4), (0, 2, 3)]
738 """
754 """
739 for range_str in ranges_str.split():
755 for range_str in ranges_str.split():
740 rmatch = range_re.match(range_str)
756 rmatch = range_re.match(range_str)
741 if not rmatch:
757 if not rmatch:
742 continue
758 continue
743 start = int(rmatch.group("start"))
759 start = int(rmatch.group("start"))
744 end = rmatch.group("end")
760 end = rmatch.group("end")
745 end = int(end) if end else start+1 # If no end specified, get (a, a+1)
761 end = int(end) if end else start+1 # If no end specified, get (a, a+1)
746 if rmatch.group("sep") == "-": # 1-3 == 1:4 --> [1, 2, 3]
762 if rmatch.group("sep") == "-": # 1-3 == 1:4 --> [1, 2, 3]
747 end += 1
763 end += 1
748 startsess = rmatch.group("startsess") or "0"
764 startsess = rmatch.group("startsess") or "0"
749 endsess = rmatch.group("endsess") or startsess
765 endsess = rmatch.group("endsess") or startsess
750 startsess = int(startsess.replace("~","-"))
766 startsess = int(startsess.replace("~","-"))
751 endsess = int(endsess.replace("~","-"))
767 endsess = int(endsess.replace("~","-"))
752 assert endsess >= startsess
768 assert endsess >= startsess
753
769
754 if endsess == startsess:
770 if endsess == startsess:
755 yield (startsess, start, end)
771 yield (startsess, start, end)
756 continue
772 continue
757 # Multiple sessions in one range:
773 # Multiple sessions in one range:
758 yield (startsess, start, None)
774 yield (startsess, start, None)
759 for sess in range(startsess+1, endsess):
775 for sess in range(startsess+1, endsess):
760 yield (sess, 1, None)
776 yield (sess, 1, None)
761 yield (endsess, 1, end)
777 yield (endsess, 1, end)
762
778
763
779
764 def _format_lineno(session, line):
780 def _format_lineno(session, line):
765 """Helper function to format line numbers properly."""
781 """Helper function to format line numbers properly."""
766 if session == 0:
782 if session == 0:
767 return str(line)
783 return str(line)
768 return "%s#%s" % (session, line)
784 return "%s#%s" % (session, line)
769
785
770
786
General Comments 0
You need to be logged in to leave comments. Login now