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