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