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