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