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