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