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