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