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