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