##// END OF EJS Templates
Addressed issues: renamed ZMQHistoryManager (appears to do need to do more than just an Accessor) in IPython/terminal/console/zmqhistory.py; added abc methods; rewrote access methods to load the history from the kernel client; works with 'ipython console' and with 'ipython console --kernel'
Doug Blank -
Show More
@@ -0,0 +1,111 b''
1 """ ZMQ Kernel History accessor and manager. """
2 #-----------------------------------------------------------------------------
3 # Copyright (C) 2010-2011 The IPython Development Team.
4 #
5 # Distributed under the terms of the BSD License.
6 #
7 # The full license is in the file COPYING.txt, distributed with this software.
8 #-----------------------------------------------------------------------------
9
10 #-----------------------------------------------------------------------------
11 # Imports
12 #-----------------------------------------------------------------------------
13
14 from IPython.core.history import HistoryAccessorBase
15 from IPython.utils import py3compat
16 from IPython.utils.traitlets import Dict, List
17
18 try:
19 from queue import Empty # Py 3
20 except ImportError:
21 from Queue import Empty # Py 2
22
23 class ZMQHistoryManager(HistoryAccessorBase):
24 """History accessor and manager for ZMQ-based kernels"""
25 input_hist_parsed = List([""])
26 output_hist = Dict()
27 dir_hist = List()
28 output_hist_reprs = Dict()
29
30 def __init__(self, client):
31 """
32 Class to load the command-line history from a ZMQ-based kernel,
33 and access the history.
34
35 Parameters
36 ----------
37
38 client: `IPython.kernel.KernelClient`
39 The kernel client in order to request the history.
40 """
41 self.client = client
42
43 def _load_history(self, raw=True, output=False, hist_access_type='range',
44 **kwargs):
45 """
46 Load the history over ZMQ from the kernel. Wraps the history
47 messaging with loop to wait to get history results.
48 """
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,
53 hist_access_type=hist_access_type,
54 **kwargs)
55 history = []
56 while True:
57 try:
58 reply = self.client.get_shell_msg(timeout=1)
59 except Empty:
60 break
61 else:
62 if reply['parent_header'].get('msg_id') == msg_id:
63 history = reply['content'].get('history', [])
64 break
65 return history
66
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):
74 return self._load_history(hist_access_type='tail', n=n, raw=raw,
75 output=output)
76
77 def search(self, pattern="*", raw=True, search_raw=True,
78 output=False, n=None, unique=False):
79 return self._load_history(hist_access_type='search', pattern=pattern,
80 raw=raw, search_raw=search_raw,
81 output=output, n=n, unique=unique)
82
83 def get_range(self, session, start=1, stop=None, raw=True,output=False):
84 return self._load_history(hist_access_type='range', raw=raw,
85 output=output, start=start, stop=stop,
86 session=session)
87
88 def get_range_by_str(self, rangestr, raw=True, output=False):
89 return self._load_history(hist_access_type='range', raw=raw,
90 output=output, rangestr=rangetr)
91
92 def end_session(self):
93 """
94 Nothing to do for ZMQ-based histories.
95 """
96 pass
97
98 def reset(self, new_session=True):
99 """Clear the session history, releasing all object references, and
100 optionally open a new session."""
101 self.output_hist.clear()
102 # The directory history can't be completely empty
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
@@ -1,955 +1,875 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
18 import os
19 import os
19 import re
20 import re
20 try:
21 try:
21 import sqlite3
22 import sqlite3
22 except ImportError:
23 except ImportError:
23 try:
24 try:
24 from pysqlite2 import dbapi2 as sqlite3
25 from pysqlite2 import dbapi2 as sqlite3
25 except ImportError:
26 except ImportError:
26 sqlite3 = None
27 sqlite3 = None
27 import threading
28 import threading
28
29
29 try:
30 from queue import Empty # Py 3
31 except ImportError:
32 from Queue import Empty # Py 2
33
34 # Our own packages
30 # Our own packages
35 from IPython.config.configurable import Configurable
31 from IPython.config.configurable import Configurable
36 from IPython.external.decorator import decorator
32 from IPython.external.decorator import decorator
37 from IPython.utils.decorators import undoc
33 from IPython.utils.decorators import undoc
38 from IPython.utils.path import locate_profile
34 from IPython.utils.path import locate_profile
39 from IPython.utils import py3compat
35 from IPython.utils import py3compat
36 from IPython.utils.py3compat import with_metaclass
40 from IPython.utils.traitlets import (
37 from IPython.utils.traitlets import (
41 Any, Bool, Dict, Instance, Integer, List, Unicode, TraitError,
38 Any, Bool, Dict, Instance, Integer, List, Unicode, TraitError,
42 )
39 )
43 from IPython.utils.warn import warn
40 from IPython.utils.warn import warn
44
41
45 #-----------------------------------------------------------------------------
42 #-----------------------------------------------------------------------------
46 # Classes and functions
43 # Classes and functions
47 #-----------------------------------------------------------------------------
44 #-----------------------------------------------------------------------------
48
45
49 @undoc
46 @undoc
50 class DummyDB(object):
47 class DummyDB(object):
51 """Dummy DB that will act as a black hole for history.
48 """Dummy DB that will act as a black hole for history.
52
49
53 Only used in the absence of sqlite"""
50 Only used in the absence of sqlite"""
54 def execute(*args, **kwargs):
51 def execute(*args, **kwargs):
55 return []
52 return []
56
53
57 def commit(self, *args, **kwargs):
54 def commit(self, *args, **kwargs):
58 pass
55 pass
59
56
60 def __enter__(self, *args, **kwargs):
57 def __enter__(self, *args, **kwargs):
61 pass
58 pass
62
59
63 def __exit__(self, *args, **kwargs):
60 def __exit__(self, *args, **kwargs):
64 pass
61 pass
65
62
66
63
67 @decorator
64 @decorator
68 def needs_sqlite(f, self, *a, **kw):
65 def needs_sqlite(f, self, *a, **kw):
69 """Decorator: return an empty list in the absence of sqlite."""
66 """Decorator: return an empty list in the absence of sqlite."""
70 if sqlite3 is None or not self.enabled:
67 if sqlite3 is None or not self.enabled:
71 return []
68 return []
72 else:
69 else:
73 return f(self, *a, **kw)
70 return f(self, *a, **kw)
74
71
75
72
76 if sqlite3 is not None:
73 if sqlite3 is not None:
77 DatabaseError = sqlite3.DatabaseError
74 DatabaseError = sqlite3.DatabaseError
78 else:
75 else:
79 @undoc
76 @undoc
80 class DatabaseError(Exception):
77 class DatabaseError(Exception):
81 "Dummy exception when sqlite could not be imported. Should never occur."
78 "Dummy exception when sqlite could not be imported. Should never occur."
82
79
83 @decorator
80 @decorator
84 def catch_corrupt_db(f, self, *a, **kw):
81 def catch_corrupt_db(f, self, *a, **kw):
85 """A decorator which wraps HistoryAccessor method calls to catch errors from
82 """A decorator which wraps HistoryAccessor method calls to catch errors from
86 a corrupt SQLite database, move the old database out of the way, and create
83 a corrupt SQLite database, move the old database out of the way, and create
87 a new one.
84 a new one.
88 """
85 """
89 try:
86 try:
90 return f(self, *a, **kw)
87 return f(self, *a, **kw)
91 except DatabaseError:
88 except DatabaseError:
92 if os.path.isfile(self.hist_file):
89 if os.path.isfile(self.hist_file):
93 # Try to move the file out of the way
90 # Try to move the file out of the way
94 base,ext = os.path.splitext(self.hist_file)
91 base,ext = os.path.splitext(self.hist_file)
95 newpath = base + '-corrupt' + ext
92 newpath = base + '-corrupt' + ext
96 os.rename(self.hist_file, newpath)
93 os.rename(self.hist_file, newpath)
97 self.init_db()
94 self.init_db()
98 print("ERROR! History file wasn't a valid SQLite database.",
95 print("ERROR! History file wasn't a valid SQLite database.",
99 "It was moved to %s" % newpath, "and a new file created.")
96 "It was moved to %s" % newpath, "and a new file created.")
100 return []
97 return []
101
98
102 else:
99 else:
103 # The hist_file is probably :memory: or something else.
100 # The hist_file is probably :memory: or something else.
104 raise
101 raise
105
102
106 class HistoryAccessorBase(Configurable):
103 class HistoryAccessorBase(Configurable):
107 input_hist_parsed = List([""])
104 """An abstract class for History Accessors """
108 input_hist_raw = List([""])
109 output_hist = Dict()
110 dir_hist = List()
111 output_hist_reprs = Dict()
112
113 def end_session(self):
114 pass
115
116 def reset(self, new_session=True):
117 """Clear the session history, releasing all object references, and
118 optionally open a new session."""
119 self.output_hist.clear()
120 # The directory history can't be completely empty
121 self.dir_hist[:] = [py3compat.getcwd()]
122
123 if new_session:
124 if self.session_number:
125 self.end_session()
126 self.input_hist_parsed[:] = [""]
127 self.input_hist_raw[:] = [""]
128 self.new_session()
129
130 def new_session(self, conn=None):
131 pass
132
133 def writeout_cache(self):
134 pass
135
105
106 @abc.abstractmethod
136 def get_tail(self, n=10, raw=True, output=False, include_latest=False):
107 def get_tail(self, n=10, raw=True, output=False, include_latest=False):
137 return []
108 pass
138
109
110 @abc.abstractmethod
139 def search(self, pattern="*", raw=True, search_raw=True,
111 def search(self, pattern="*", raw=True, search_raw=True,
140 output=False, n=None, unique=False):
112 output=False, n=None, unique=False):
141 return []
113 pass
142
114
115 @abc.abstractmethod
143 def get_range(self, session, start=1, stop=None, raw=True,output=False):
116 def get_range(self, session, start=1, stop=None, raw=True,output=False):
144 return []
145
146 def get_range_by_str(self, rangestr, raw=True, output=False):
147 return []
148
149 def store_inputs(self, line_num, source, source_raw=None):
150 pass
117 pass
151
118
152 def store_output(self, line_num):
119 @abc.abstractmethod
120 def get_range_by_str(self, rangestr, raw=True, output=False):
153 pass
121 pass
154
122
155 class HistoryAccessor(HistoryAccessorBase):
123 class HistoryAccessor(HistoryAccessorBase):
156 """Access the history database without adding to it.
124 """Access the history database without adding to it.
157
125
158 This is intended for use by standalone history tools. IPython shells use
126 This is intended for use by standalone history tools. IPython shells use
159 HistoryManager, below, which is a subclass of this."""
127 HistoryManager, below, which is a subclass of this."""
160
128
161 # String holding the path to the history file
129 # String holding the path to the history file
162 hist_file = Unicode(config=True,
130 hist_file = Unicode(config=True,
163 help="""Path to file to use for SQLite history database.
131 help="""Path to file to use for SQLite history database.
164
132
165 By default, IPython will put the history database in the IPython
133 By default, IPython will put the history database in the IPython
166 profile directory. If you would rather share one history among
134 profile directory. If you would rather share one history among
167 profiles, you can set this value in each, so that they are consistent.
135 profiles, you can set this value in each, so that they are consistent.
168
136
169 Due to an issue with fcntl, SQLite is known to misbehave on some NFS
137 Due to an issue with fcntl, SQLite is known to misbehave on some NFS
170 mounts. If you see IPython hanging, try setting this to something on a
138 mounts. If you see IPython hanging, try setting this to something on a
171 local disk, e.g::
139 local disk, e.g::
172
140
173 ipython --HistoryManager.hist_file=/tmp/ipython_hist.sqlite
141 ipython --HistoryManager.hist_file=/tmp/ipython_hist.sqlite
174
142
175 """)
143 """)
176
144
177 enabled = Bool(True, config=True,
145 enabled = Bool(True, config=True,
178 help="""enable the SQLite history
146 help="""enable the SQLite history
179
147
180 set enabled=False to disable the SQLite history,
148 set enabled=False to disable the SQLite history,
181 in which case there will be no stored history, no SQLite connection,
149 in which case there will be no stored history, no SQLite connection,
182 and no background saving thread. This may be necessary in some
150 and no background saving thread. This may be necessary in some
183 threaded environments where IPython is embedded.
151 threaded environments where IPython is embedded.
184 """
152 """
185 )
153 )
186
154
187 connection_options = Dict(config=True,
155 connection_options = Dict(config=True,
188 help="""Options for configuring the SQLite connection
156 help="""Options for configuring the SQLite connection
189
157
190 These options are passed as keyword args to sqlite3.connect
158 These options are passed as keyword args to sqlite3.connect
191 when establishing database conenctions.
159 when establishing database conenctions.
192 """
160 """
193 )
161 )
194
162
195 # The SQLite database
163 # The SQLite database
196 db = Any()
164 db = Any()
197 def _db_changed(self, name, old, new):
165 def _db_changed(self, name, old, new):
198 """validate the db, since it can be an Instance of two different types"""
166 """validate the db, since it can be an Instance of two different types"""
199 connection_types = (DummyDB,)
167 connection_types = (DummyDB,)
200 if sqlite3 is not None:
168 if sqlite3 is not None:
201 connection_types = (DummyDB, sqlite3.Connection)
169 connection_types = (DummyDB, sqlite3.Connection)
202 if not isinstance(new, connection_types):
170 if not isinstance(new, connection_types):
203 msg = "%s.db must be sqlite3 Connection or DummyDB, not %r" % \
171 msg = "%s.db must be sqlite3 Connection or DummyDB, not %r" % \
204 (self.__class__.__name__, new)
172 (self.__class__.__name__, new)
205 raise TraitError(msg)
173 raise TraitError(msg)
206
174
207 def __init__(self, profile='default', hist_file=u'', **traits):
175 def __init__(self, profile='default', hist_file=u'', **traits):
208 """Create a new history accessor.
176 """Create a new history accessor.
209
177
210 Parameters
178 Parameters
211 ----------
179 ----------
212 profile : str
180 profile : str
213 The name of the profile from which to open history.
181 The name of the profile from which to open history.
214 hist_file : str
182 hist_file : str
215 Path to an SQLite history database stored by IPython. If specified,
183 Path to an SQLite history database stored by IPython. If specified,
216 hist_file overrides profile.
184 hist_file overrides profile.
217 config : :class:`~IPython.config.loader.Config`
185 config : :class:`~IPython.config.loader.Config`
218 Config object. hist_file can also be set through this.
186 Config object. hist_file can also be set through this.
219 """
187 """
220 # We need a pointer back to the shell for various tasks.
188 # We need a pointer back to the shell for various tasks.
221 super(HistoryAccessor, self).__init__(**traits)
189 super(HistoryAccessor, self).__init__(**traits)
222 # defer setting hist_file from kwarg until after init,
190 # defer setting hist_file from kwarg until after init,
223 # otherwise the default kwarg value would clobber any value
191 # otherwise the default kwarg value would clobber any value
224 # set by config
192 # set by config
225 if hist_file:
193 if hist_file:
226 self.hist_file = hist_file
194 self.hist_file = hist_file
227
195
228 if self.hist_file == u'':
196 if self.hist_file == u'':
229 # No one has set the hist_file, yet.
197 # No one has set the hist_file, yet.
230 self.hist_file = self._get_hist_file_name(profile)
198 self.hist_file = self._get_hist_file_name(profile)
231
199
232 if sqlite3 is None and self.enabled:
200 if sqlite3 is None and self.enabled:
233 warn("IPython History requires SQLite, your history will not be saved")
201 warn("IPython History requires SQLite, your history will not be saved")
234 self.enabled = False
202 self.enabled = False
235
203
236 self.init_db()
204 self.init_db()
237
205
238 def _get_hist_file_name(self, profile='default'):
206 def _get_hist_file_name(self, profile='default'):
239 """Find the history file for the given profile name.
207 """Find the history file for the given profile name.
240
208
241 This is overridden by the HistoryManager subclass, to use the shell's
209 This is overridden by the HistoryManager subclass, to use the shell's
242 active profile.
210 active profile.
243
211
244 Parameters
212 Parameters
245 ----------
213 ----------
246 profile : str
214 profile : str
247 The name of a profile which has a history file.
215 The name of a profile which has a history file.
248 """
216 """
249 return os.path.join(locate_profile(profile), 'history.sqlite')
217 return os.path.join(locate_profile(profile), 'history.sqlite')
250
218
251 @catch_corrupt_db
219 @catch_corrupt_db
252 def init_db(self):
220 def init_db(self):
253 """Connect to the database, and create tables if necessary."""
221 """Connect to the database, and create tables if necessary."""
254 if not self.enabled:
222 if not self.enabled:
255 self.db = DummyDB()
223 self.db = DummyDB()
256 return
224 return
257
225
258 # use detect_types so that timestamps return datetime objects
226 # use detect_types so that timestamps return datetime objects
259 kwargs = dict(detect_types=sqlite3.PARSE_DECLTYPES|sqlite3.PARSE_COLNAMES)
227 kwargs = dict(detect_types=sqlite3.PARSE_DECLTYPES|sqlite3.PARSE_COLNAMES)
260 kwargs.update(self.connection_options)
228 kwargs.update(self.connection_options)
261 self.db = sqlite3.connect(self.hist_file, **kwargs)
229 self.db = sqlite3.connect(self.hist_file, **kwargs)
262 self.db.execute("""CREATE TABLE IF NOT EXISTS sessions (session integer
230 self.db.execute("""CREATE TABLE IF NOT EXISTS sessions (session integer
263 primary key autoincrement, start timestamp,
231 primary key autoincrement, start timestamp,
264 end timestamp, num_cmds integer, remark text)""")
232 end timestamp, num_cmds integer, remark text)""")
265 self.db.execute("""CREATE TABLE IF NOT EXISTS history
233 self.db.execute("""CREATE TABLE IF NOT EXISTS history
266 (session integer, line integer, source text, source_raw text,
234 (session integer, line integer, source text, source_raw text,
267 PRIMARY KEY (session, line))""")
235 PRIMARY KEY (session, line))""")
268 # Output history is optional, but ensure the table's there so it can be
236 # Output history is optional, but ensure the table's there so it can be
269 # enabled later.
237 # enabled later.
270 self.db.execute("""CREATE TABLE IF NOT EXISTS output_history
238 self.db.execute("""CREATE TABLE IF NOT EXISTS output_history
271 (session integer, line integer, output text,
239 (session integer, line integer, output text,
272 PRIMARY KEY (session, line))""")
240 PRIMARY KEY (session, line))""")
273 self.db.commit()
241 self.db.commit()
274
242
275 def writeout_cache(self):
243 def writeout_cache(self):
276 """Overridden by HistoryManager to dump the cache before certain
244 """Overridden by HistoryManager to dump the cache before certain
277 database lookups."""
245 database lookups."""
278 pass
246 pass
279
247
280 ## -------------------------------
248 ## -------------------------------
281 ## Methods for retrieving history:
249 ## Methods for retrieving history:
282 ## -------------------------------
250 ## -------------------------------
283 def _run_sql(self, sql, params, raw=True, output=False):
251 def _run_sql(self, sql, params, raw=True, output=False):
284 """Prepares and runs an SQL query for the history database.
252 """Prepares and runs an SQL query for the history database.
285
253
286 Parameters
254 Parameters
287 ----------
255 ----------
288 sql : str
256 sql : str
289 Any filtering expressions to go after SELECT ... FROM ...
257 Any filtering expressions to go after SELECT ... FROM ...
290 params : tuple
258 params : tuple
291 Parameters passed to the SQL query (to replace "?")
259 Parameters passed to the SQL query (to replace "?")
292 raw, output : bool
260 raw, output : bool
293 See :meth:`get_range`
261 See :meth:`get_range`
294
262
295 Returns
263 Returns
296 -------
264 -------
297 Tuples as :meth:`get_range`
265 Tuples as :meth:`get_range`
298 """
266 """
299 toget = 'source_raw' if raw else 'source'
267 toget = 'source_raw' if raw else 'source'
300 sqlfrom = "history"
268 sqlfrom = "history"
301 if output:
269 if output:
302 sqlfrom = "history LEFT JOIN output_history USING (session, line)"
270 sqlfrom = "history LEFT JOIN output_history USING (session, line)"
303 toget = "history.%s, output_history.output" % toget
271 toget = "history.%s, output_history.output" % toget
304 cur = self.db.execute("SELECT session, line, %s FROM %s " %\
272 cur = self.db.execute("SELECT session, line, %s FROM %s " %\
305 (toget, sqlfrom) + sql, params)
273 (toget, sqlfrom) + sql, params)
306 if output: # Regroup into 3-tuples, and parse JSON
274 if output: # Regroup into 3-tuples, and parse JSON
307 return ((ses, lin, (inp, out)) for ses, lin, inp, out in cur)
275 return ((ses, lin, (inp, out)) for ses, lin, inp, out in cur)
308 return cur
276 return cur
309
277
310 @needs_sqlite
278 @needs_sqlite
311 @catch_corrupt_db
279 @catch_corrupt_db
312 def get_session_info(self, session):
280 def get_session_info(self, session):
313 """Get info about a session.
281 """Get info about a session.
314
282
315 Parameters
283 Parameters
316 ----------
284 ----------
317
285
318 session : int
286 session : int
319 Session number to retrieve.
287 Session number to retrieve.
320
288
321 Returns
289 Returns
322 -------
290 -------
323
291
324 session_id : int
292 session_id : int
325 Session ID number
293 Session ID number
326 start : datetime
294 start : datetime
327 Timestamp for the start of the session.
295 Timestamp for the start of the session.
328 end : datetime
296 end : datetime
329 Timestamp for the end of the session, or None if IPython crashed.
297 Timestamp for the end of the session, or None if IPython crashed.
330 num_cmds : int
298 num_cmds : int
331 Number of commands run, or None if IPython crashed.
299 Number of commands run, or None if IPython crashed.
332 remark : unicode
300 remark : unicode
333 A manually set description.
301 A manually set description.
334 """
302 """
335 query = "SELECT * from sessions where session == ?"
303 query = "SELECT * from sessions where session == ?"
336 return self.db.execute(query, (session,)).fetchone()
304 return self.db.execute(query, (session,)).fetchone()
337
305
338 @catch_corrupt_db
306 @catch_corrupt_db
339 def get_last_session_id(self):
307 def get_last_session_id(self):
340 """Get the last session ID currently in the database.
308 """Get the last session ID currently in the database.
341
309
342 Within IPython, this should be the same as the value stored in
310 Within IPython, this should be the same as the value stored in
343 :attr:`HistoryManager.session_number`.
311 :attr:`HistoryManager.session_number`.
344 """
312 """
345 for record in self.get_tail(n=1, include_latest=True):
313 for record in self.get_tail(n=1, include_latest=True):
346 return record[0]
314 return record[0]
347
315
348 @catch_corrupt_db
316 @catch_corrupt_db
349 def get_tail(self, n=10, raw=True, output=False, include_latest=False):
317 def get_tail(self, n=10, raw=True, output=False, include_latest=False):
350 """Get the last n lines from the history database.
318 """Get the last n lines from the history database.
351
319
352 Parameters
320 Parameters
353 ----------
321 ----------
354 n : int
322 n : int
355 The number of lines to get
323 The number of lines to get
356 raw, output : bool
324 raw, output : bool
357 See :meth:`get_range`
325 See :meth:`get_range`
358 include_latest : bool
326 include_latest : bool
359 If False (default), n+1 lines are fetched, and the latest one
327 If False (default), n+1 lines are fetched, and the latest one
360 is discarded. This is intended to be used where the function
328 is discarded. This is intended to be used where the function
361 is called by a user command, which it should not return.
329 is called by a user command, which it should not return.
362
330
363 Returns
331 Returns
364 -------
332 -------
365 Tuples as :meth:`get_range`
333 Tuples as :meth:`get_range`
366 """
334 """
367 self.writeout_cache()
335 self.writeout_cache()
368 if not include_latest:
336 if not include_latest:
369 n += 1
337 n += 1
370 cur = self._run_sql("ORDER BY session DESC, line DESC LIMIT ?",
338 cur = self._run_sql("ORDER BY session DESC, line DESC LIMIT ?",
371 (n,), raw=raw, output=output)
339 (n,), raw=raw, output=output)
372 if not include_latest:
340 if not include_latest:
373 return reversed(list(cur)[1:])
341 return reversed(list(cur)[1:])
374 return reversed(list(cur))
342 return reversed(list(cur))
375
343
376 @catch_corrupt_db
344 @catch_corrupt_db
377 def search(self, pattern="*", raw=True, search_raw=True,
345 def search(self, pattern="*", raw=True, search_raw=True,
378 output=False, n=None, unique=False):
346 output=False, n=None, unique=False):
379 """Search the database using unix glob-style matching (wildcards
347 """Search the database using unix glob-style matching (wildcards
380 * and ?).
348 * and ?).
381
349
382 Parameters
350 Parameters
383 ----------
351 ----------
384 pattern : str
352 pattern : str
385 The wildcarded pattern to match when searching
353 The wildcarded pattern to match when searching
386 search_raw : bool
354 search_raw : bool
387 If True, search the raw input, otherwise, the parsed input
355 If True, search the raw input, otherwise, the parsed input
388 raw, output : bool
356 raw, output : bool
389 See :meth:`get_range`
357 See :meth:`get_range`
390 n : None or int
358 n : None or int
391 If an integer is given, it defines the limit of
359 If an integer is given, it defines the limit of
392 returned entries.
360 returned entries.
393 unique : bool
361 unique : bool
394 When it is true, return only unique entries.
362 When it is true, return only unique entries.
395
363
396 Returns
364 Returns
397 -------
365 -------
398 Tuples as :meth:`get_range`
366 Tuples as :meth:`get_range`
399 """
367 """
400 tosearch = "source_raw" if search_raw else "source"
368 tosearch = "source_raw" if search_raw else "source"
401 if output:
369 if output:
402 tosearch = "history." + tosearch
370 tosearch = "history." + tosearch
403 self.writeout_cache()
371 self.writeout_cache()
404 sqlform = "WHERE %s GLOB ?" % tosearch
372 sqlform = "WHERE %s GLOB ?" % tosearch
405 params = (pattern,)
373 params = (pattern,)
406 if unique:
374 if unique:
407 sqlform += ' GROUP BY {0}'.format(tosearch)
375 sqlform += ' GROUP BY {0}'.format(tosearch)
408 if n is not None:
376 if n is not None:
409 sqlform += " ORDER BY session DESC, line DESC LIMIT ?"
377 sqlform += " ORDER BY session DESC, line DESC LIMIT ?"
410 params += (n,)
378 params += (n,)
411 elif unique:
379 elif unique:
412 sqlform += " ORDER BY session, line"
380 sqlform += " ORDER BY session, line"
413 cur = self._run_sql(sqlform, params, raw=raw, output=output)
381 cur = self._run_sql(sqlform, params, raw=raw, output=output)
414 if n is not None:
382 if n is not None:
415 return reversed(list(cur))
383 return reversed(list(cur))
416 return cur
384 return cur
417
385
418 @catch_corrupt_db
386 @catch_corrupt_db
419 def get_range(self, session, start=1, stop=None, raw=True,output=False):
387 def get_range(self, session, start=1, stop=None, raw=True,output=False):
420 """Retrieve input by session.
388 """Retrieve input by session.
421
389
422 Parameters
390 Parameters
423 ----------
391 ----------
424 session : int
392 session : int
425 Session number to retrieve.
393 Session number to retrieve.
426 start : int
394 start : int
427 First line to retrieve.
395 First line to retrieve.
428 stop : int
396 stop : int
429 End of line range (excluded from output itself). If None, retrieve
397 End of line range (excluded from output itself). If None, retrieve
430 to the end of the session.
398 to the end of the session.
431 raw : bool
399 raw : bool
432 If True, return untranslated input
400 If True, return untranslated input
433 output : bool
401 output : bool
434 If True, attempt to include output. This will be 'real' Python
402 If True, attempt to include output. This will be 'real' Python
435 objects for the current session, or text reprs from previous
403 objects for the current session, or text reprs from previous
436 sessions if db_log_output was enabled at the time. Where no output
404 sessions if db_log_output was enabled at the time. Where no output
437 is found, None is used.
405 is found, None is used.
438
406
439 Returns
407 Returns
440 -------
408 -------
441 entries
409 entries
442 An iterator over the desired lines. Each line is a 3-tuple, either
410 An iterator over the desired lines. Each line is a 3-tuple, either
443 (session, line, input) if output is False, or
411 (session, line, input) if output is False, or
444 (session, line, (input, output)) if output is True.
412 (session, line, (input, output)) if output is True.
445 """
413 """
446 if stop:
414 if stop:
447 lineclause = "line >= ? AND line < ?"
415 lineclause = "line >= ? AND line < ?"
448 params = (session, start, stop)
416 params = (session, start, stop)
449 else:
417 else:
450 lineclause = "line>=?"
418 lineclause = "line>=?"
451 params = (session, start)
419 params = (session, start)
452
420
453 return self._run_sql("WHERE session==? AND %s" % lineclause,
421 return self._run_sql("WHERE session==? AND %s" % lineclause,
454 params, raw=raw, output=output)
422 params, raw=raw, output=output)
455
423
456 def get_range_by_str(self, rangestr, raw=True, output=False):
424 def get_range_by_str(self, rangestr, raw=True, output=False):
457 """Get lines of history from a string of ranges, as used by magic
425 """Get lines of history from a string of ranges, as used by magic
458 commands %hist, %save, %macro, etc.
426 commands %hist, %save, %macro, etc.
459
427
460 Parameters
428 Parameters
461 ----------
429 ----------
462 rangestr : str
430 rangestr : str
463 A string specifying ranges, e.g. "5 ~2/1-4". See
431 A string specifying ranges, e.g. "5 ~2/1-4". See
464 :func:`magic_history` for full details.
432 :func:`magic_history` for full details.
465 raw, output : bool
433 raw, output : bool
466 As :meth:`get_range`
434 As :meth:`get_range`
467
435
468 Returns
436 Returns
469 -------
437 -------
470 Tuples as :meth:`get_range`
438 Tuples as :meth:`get_range`
471 """
439 """
472 for sess, s, e in extract_hist_ranges(rangestr):
440 for sess, s, e in extract_hist_ranges(rangestr):
473 for line in self.get_range(sess, s, e, raw=raw, output=output):
441 for line in self.get_range(sess, s, e, raw=raw, output=output):
474 yield line
442 yield line
475
443
476
444
477 class HistoryManager(HistoryAccessor):
445 class HistoryManager(HistoryAccessor):
478 """A class to organize all history-related functionality in one place.
446 """A class to organize all history-related functionality in one place.
479 """
447 """
480 # Public interface
448 # Public interface
481
449
482 # An instance of the IPython shell we are attached to
450 # An instance of the IPython shell we are attached to
483 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC')
451 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC')
484 # Lists to hold processed and raw history. These start with a blank entry
452 # Lists to hold processed and raw history. These start with a blank entry
485 # so that we can index them starting from 1
453 # so that we can index them starting from 1
486 input_hist_parsed = List([""])
454 input_hist_parsed = List([""])
487 input_hist_raw = List([""])
455 input_hist_raw = List([""])
488 # A list of directories visited during session
456 # A list of directories visited during session
489 dir_hist = List()
457 dir_hist = List()
490 def _dir_hist_default(self):
458 def _dir_hist_default(self):
491 try:
459 try:
492 return [py3compat.getcwd()]
460 return [py3compat.getcwd()]
493 except OSError:
461 except OSError:
494 return []
462 return []
495
463
496 # A dict of output history, keyed with ints from the shell's
464 # A dict of output history, keyed with ints from the shell's
497 # execution count.
465 # execution count.
498 output_hist = Dict()
466 output_hist = Dict()
499 # The text/plain repr of outputs.
467 # The text/plain repr of outputs.
500 output_hist_reprs = Dict()
468 output_hist_reprs = Dict()
501
469
502 # The number of the current session in the history database
470 # The number of the current session in the history database
503 session_number = Integer()
471 session_number = Integer()
504
472
505 db_log_output = Bool(False, config=True,
473 db_log_output = Bool(False, config=True,
506 help="Should the history database include output? (default: no)"
474 help="Should the history database include output? (default: no)"
507 )
475 )
508 db_cache_size = Integer(0, config=True,
476 db_cache_size = Integer(0, config=True,
509 help="Write to database every x commands (higher values save disk access & power).\n"
477 help="Write to database every x commands (higher values save disk access & power).\n"
510 "Values of 1 or less effectively disable caching."
478 "Values of 1 or less effectively disable caching."
511 )
479 )
512 # The input and output caches
480 # The input and output caches
513 db_input_cache = List()
481 db_input_cache = List()
514 db_output_cache = List()
482 db_output_cache = List()
515
483
516 # History saving in separate thread
484 # History saving in separate thread
517 save_thread = Instance('IPython.core.history.HistorySavingThread')
485 save_thread = Instance('IPython.core.history.HistorySavingThread')
518 try: # Event is a function returning an instance of _Event...
486 try: # Event is a function returning an instance of _Event...
519 save_flag = Instance(threading._Event)
487 save_flag = Instance(threading._Event)
520 except AttributeError: # ...until Python 3.3, when it's a class.
488 except AttributeError: # ...until Python 3.3, when it's a class.
521 save_flag = Instance(threading.Event)
489 save_flag = Instance(threading.Event)
522
490
523 # Private interface
491 # Private interface
524 # Variables used to store the three last inputs from the user. On each new
492 # Variables used to store the three last inputs from the user. On each new
525 # history update, we populate the user's namespace with these, shifted as
493 # history update, we populate the user's namespace with these, shifted as
526 # necessary.
494 # necessary.
527 _i00 = Unicode(u'')
495 _i00 = Unicode(u'')
528 _i = Unicode(u'')
496 _i = Unicode(u'')
529 _ii = Unicode(u'')
497 _ii = Unicode(u'')
530 _iii = Unicode(u'')
498 _iii = Unicode(u'')
531
499
532 # A regex matching all forms of the exit command, so that we don't store
500 # A regex matching all forms of the exit command, so that we don't store
533 # them in the history (it's annoying to rewind the first entry and land on
501 # them in the history (it's annoying to rewind the first entry and land on
534 # an exit call).
502 # an exit call).
535 _exit_re = re.compile(r"(exit|quit)(\s*\(.*\))?$")
503 _exit_re = re.compile(r"(exit|quit)(\s*\(.*\))?$")
536
504
537 def __init__(self, shell=None, config=None, **traits):
505 def __init__(self, shell=None, config=None, **traits):
538 """Create a new history manager associated with a shell instance.
506 """Create a new history manager associated with a shell instance.
539 """
507 """
540 # We need a pointer back to the shell for various tasks.
508 # We need a pointer back to the shell for various tasks.
541 super(HistoryManager, self).__init__(shell=shell, config=config,
509 super(HistoryManager, self).__init__(shell=shell, config=config,
542 **traits)
510 **traits)
543 self.save_flag = threading.Event()
511 self.save_flag = threading.Event()
544 self.db_input_cache_lock = threading.Lock()
512 self.db_input_cache_lock = threading.Lock()
545 self.db_output_cache_lock = threading.Lock()
513 self.db_output_cache_lock = threading.Lock()
546 if self.enabled and self.hist_file != ':memory:':
514 if self.enabled and self.hist_file != ':memory:':
547 self.save_thread = HistorySavingThread(self)
515 self.save_thread = HistorySavingThread(self)
548 self.save_thread.start()
516 self.save_thread.start()
549
517
550 self.new_session()
518 self.new_session()
551
519
552 def _get_hist_file_name(self, profile=None):
520 def _get_hist_file_name(self, profile=None):
553 """Get default history file name based on the Shell's profile.
521 """Get default history file name based on the Shell's profile.
554
522
555 The profile parameter is ignored, but must exist for compatibility with
523 The profile parameter is ignored, but must exist for compatibility with
556 the parent class."""
524 the parent class."""
557 profile_dir = self.shell.profile_dir.location
525 profile_dir = self.shell.profile_dir.location
558 return os.path.join(profile_dir, 'history.sqlite')
526 return os.path.join(profile_dir, 'history.sqlite')
559
527
560 @needs_sqlite
528 @needs_sqlite
561 def new_session(self, conn=None):
529 def new_session(self, conn=None):
562 """Get a new session number."""
530 """Get a new session number."""
563 if conn is None:
531 if conn is None:
564 conn = self.db
532 conn = self.db
565
533
566 with conn:
534 with conn:
567 cur = conn.execute("""INSERT INTO sessions VALUES (NULL, ?, NULL,
535 cur = conn.execute("""INSERT INTO sessions VALUES (NULL, ?, NULL,
568 NULL, "") """, (datetime.datetime.now(),))
536 NULL, "") """, (datetime.datetime.now(),))
569 self.session_number = cur.lastrowid
537 self.session_number = cur.lastrowid
570
538
571 def end_session(self):
539 def end_session(self):
572 """Close the database session, filling in the end time and line count."""
540 """Close the database session, filling in the end time and line count."""
573 self.writeout_cache()
541 self.writeout_cache()
574 with self.db:
542 with self.db:
575 self.db.execute("""UPDATE sessions SET end=?, num_cmds=? WHERE
543 self.db.execute("""UPDATE sessions SET end=?, num_cmds=? WHERE
576 session==?""", (datetime.datetime.now(),
544 session==?""", (datetime.datetime.now(),
577 len(self.input_hist_parsed)-1, self.session_number))
545 len(self.input_hist_parsed)-1, self.session_number))
578 self.session_number = 0
546 self.session_number = 0
579
547
580 def name_session(self, name):
548 def name_session(self, name):
581 """Give the current session a name in the history database."""
549 """Give the current session a name in the history database."""
582 with self.db:
550 with self.db:
583 self.db.execute("UPDATE sessions SET remark=? WHERE session==?",
551 self.db.execute("UPDATE sessions SET remark=? WHERE session==?",
584 (name, self.session_number))
552 (name, self.session_number))
585
553
586 def reset(self, new_session=True):
554 def reset(self, new_session=True):
587 """Clear the session history, releasing all object references, and
555 """Clear the session history, releasing all object references, and
588 optionally open a new session."""
556 optionally open a new session."""
589 self.output_hist.clear()
557 self.output_hist.clear()
590 # The directory history can't be completely empty
558 # The directory history can't be completely empty
591 self.dir_hist[:] = [py3compat.getcwd()]
559 self.dir_hist[:] = [py3compat.getcwd()]
592
560
593 if new_session:
561 if new_session:
594 if self.session_number:
562 if self.session_number:
595 self.end_session()
563 self.end_session()
596 self.input_hist_parsed[:] = [""]
564 self.input_hist_parsed[:] = [""]
597 self.input_hist_raw[:] = [""]
565 self.input_hist_raw[:] = [""]
598 self.new_session()
566 self.new_session()
599
567
600 # ------------------------------
568 # ------------------------------
601 # Methods for retrieving history
569 # Methods for retrieving history
602 # ------------------------------
570 # ------------------------------
603 def get_session_info(self, session=0):
571 def get_session_info(self, session=0):
604 """Get info about a session.
572 """Get info about a session.
605
573
606 Parameters
574 Parameters
607 ----------
575 ----------
608
576
609 session : int
577 session : int
610 Session number to retrieve. The current session is 0, and negative
578 Session number to retrieve. The current session is 0, and negative
611 numbers count back from current session, so -1 is the previous session.
579 numbers count back from current session, so -1 is the previous session.
612
580
613 Returns
581 Returns
614 -------
582 -------
615
583
616 session_id : int
584 session_id : int
617 Session ID number
585 Session ID number
618 start : datetime
586 start : datetime
619 Timestamp for the start of the session.
587 Timestamp for the start of the session.
620 end : datetime
588 end : datetime
621 Timestamp for the end of the session, or None if IPython crashed.
589 Timestamp for the end of the session, or None if IPython crashed.
622 num_cmds : int
590 num_cmds : int
623 Number of commands run, or None if IPython crashed.
591 Number of commands run, or None if IPython crashed.
624 remark : unicode
592 remark : unicode
625 A manually set description.
593 A manually set description.
626 """
594 """
627 if session <= 0:
595 if session <= 0:
628 session += self.session_number
596 session += self.session_number
629
597
630 return super(HistoryManager, self).get_session_info(session=session)
598 return super(HistoryManager, self).get_session_info(session=session)
631
599
632 def _get_range_session(self, start=1, stop=None, raw=True, output=False):
600 def _get_range_session(self, start=1, stop=None, raw=True, output=False):
633 """Get input and output history from the current session. Called by
601 """Get input and output history from the current session. Called by
634 get_range, and takes similar parameters."""
602 get_range, and takes similar parameters."""
635 input_hist = self.input_hist_raw if raw else self.input_hist_parsed
603 input_hist = self.input_hist_raw if raw else self.input_hist_parsed
636
604
637 n = len(input_hist)
605 n = len(input_hist)
638 if start < 0:
606 if start < 0:
639 start += n
607 start += n
640 if not stop or (stop > n):
608 if not stop or (stop > n):
641 stop = n
609 stop = n
642 elif stop < 0:
610 elif stop < 0:
643 stop += n
611 stop += n
644
612
645 for i in range(start, stop):
613 for i in range(start, stop):
646 if output:
614 if output:
647 line = (input_hist[i], self.output_hist_reprs.get(i))
615 line = (input_hist[i], self.output_hist_reprs.get(i))
648 else:
616 else:
649 line = input_hist[i]
617 line = input_hist[i]
650 yield (0, i, line)
618 yield (0, i, line)
651
619
652 def get_range(self, session=0, start=1, stop=None, raw=True,output=False):
620 def get_range(self, session=0, start=1, stop=None, raw=True,output=False):
653 """Retrieve input by session.
621 """Retrieve input by session.
654
622
655 Parameters
623 Parameters
656 ----------
624 ----------
657 session : int
625 session : int
658 Session number to retrieve. The current session is 0, and negative
626 Session number to retrieve. The current session is 0, and negative
659 numbers count back from current session, so -1 is previous session.
627 numbers count back from current session, so -1 is previous session.
660 start : int
628 start : int
661 First line to retrieve.
629 First line to retrieve.
662 stop : int
630 stop : int
663 End of line range (excluded from output itself). If None, retrieve
631 End of line range (excluded from output itself). If None, retrieve
664 to the end of the session.
632 to the end of the session.
665 raw : bool
633 raw : bool
666 If True, return untranslated input
634 If True, return untranslated input
667 output : bool
635 output : bool
668 If True, attempt to include output. This will be 'real' Python
636 If True, attempt to include output. This will be 'real' Python
669 objects for the current session, or text reprs from previous
637 objects for the current session, or text reprs from previous
670 sessions if db_log_output was enabled at the time. Where no output
638 sessions if db_log_output was enabled at the time. Where no output
671 is found, None is used.
639 is found, None is used.
672
640
673 Returns
641 Returns
674 -------
642 -------
675 entries
643 entries
676 An iterator over the desired lines. Each line is a 3-tuple, either
644 An iterator over the desired lines. Each line is a 3-tuple, either
677 (session, line, input) if output is False, or
645 (session, line, input) if output is False, or
678 (session, line, (input, output)) if output is True.
646 (session, line, (input, output)) if output is True.
679 """
647 """
680 if session <= 0:
648 if session <= 0:
681 session += self.session_number
649 session += self.session_number
682 if session==self.session_number: # Current session
650 if session==self.session_number: # Current session
683 return self._get_range_session(start, stop, raw, output)
651 return self._get_range_session(start, stop, raw, output)
684 return super(HistoryManager, self).get_range(session, start, stop, raw,
652 return super(HistoryManager, self).get_range(session, start, stop, raw,
685 output)
653 output)
686
654
687 ## ----------------------------
655 ## ----------------------------
688 ## Methods for storing history:
656 ## Methods for storing history:
689 ## ----------------------------
657 ## ----------------------------
690 def store_inputs(self, line_num, source, source_raw=None):
658 def store_inputs(self, line_num, source, source_raw=None):
691 """Store source and raw input in history and create input cache
659 """Store source and raw input in history and create input cache
692 variables ``_i*``.
660 variables ``_i*``.
693
661
694 Parameters
662 Parameters
695 ----------
663 ----------
696 line_num : int
664 line_num : int
697 The prompt number of this input.
665 The prompt number of this input.
698
666
699 source : str
667 source : str
700 Python input.
668 Python input.
701
669
702 source_raw : str, optional
670 source_raw : str, optional
703 If given, this is the raw input without any IPython transformations
671 If given, this is the raw input without any IPython transformations
704 applied to it. If not given, ``source`` is used.
672 applied to it. If not given, ``source`` is used.
705 """
673 """
706 if source_raw is None:
674 if source_raw is None:
707 source_raw = source
675 source_raw = source
708 source = source.rstrip('\n')
676 source = source.rstrip('\n')
709 source_raw = source_raw.rstrip('\n')
677 source_raw = source_raw.rstrip('\n')
710
678
711 # do not store exit/quit commands
679 # do not store exit/quit commands
712 if self._exit_re.match(source_raw.strip()):
680 if self._exit_re.match(source_raw.strip()):
713 return
681 return
714
682
715 self.input_hist_parsed.append(source)
683 self.input_hist_parsed.append(source)
716 self.input_hist_raw.append(source_raw)
684 self.input_hist_raw.append(source_raw)
717
685
718 with self.db_input_cache_lock:
686 with self.db_input_cache_lock:
719 self.db_input_cache.append((line_num, source, source_raw))
687 self.db_input_cache.append((line_num, source, source_raw))
720 # Trigger to flush cache and write to DB.
688 # Trigger to flush cache and write to DB.
721 if len(self.db_input_cache) >= self.db_cache_size:
689 if len(self.db_input_cache) >= self.db_cache_size:
722 self.save_flag.set()
690 self.save_flag.set()
723
691
724 # update the auto _i variables
692 # update the auto _i variables
725 self._iii = self._ii
693 self._iii = self._ii
726 self._ii = self._i
694 self._ii = self._i
727 self._i = self._i00
695 self._i = self._i00
728 self._i00 = source_raw
696 self._i00 = source_raw
729
697
730 # hackish access to user namespace to create _i1,_i2... dynamically
698 # hackish access to user namespace to create _i1,_i2... dynamically
731 new_i = '_i%s' % line_num
699 new_i = '_i%s' % line_num
732 to_main = {'_i': self._i,
700 to_main = {'_i': self._i,
733 '_ii': self._ii,
701 '_ii': self._ii,
734 '_iii': self._iii,
702 '_iii': self._iii,
735 new_i : self._i00 }
703 new_i : self._i00 }
736
704
737 if self.shell is not None:
705 if self.shell is not None:
738 self.shell.push(to_main, interactive=False)
706 self.shell.push(to_main, interactive=False)
739
707
740 def store_output(self, line_num):
708 def store_output(self, line_num):
741 """If database output logging is enabled, this saves all the
709 """If database output logging is enabled, this saves all the
742 outputs from the indicated prompt number to the database. It's
710 outputs from the indicated prompt number to the database. It's
743 called by run_cell after code has been executed.
711 called by run_cell after code has been executed.
744
712
745 Parameters
713 Parameters
746 ----------
714 ----------
747 line_num : int
715 line_num : int
748 The line number from which to save outputs
716 The line number from which to save outputs
749 """
717 """
750 if (not self.db_log_output) or (line_num not in self.output_hist_reprs):
718 if (not self.db_log_output) or (line_num not in self.output_hist_reprs):
751 return
719 return
752 output = self.output_hist_reprs[line_num]
720 output = self.output_hist_reprs[line_num]
753
721
754 with self.db_output_cache_lock:
722 with self.db_output_cache_lock:
755 self.db_output_cache.append((line_num, output))
723 self.db_output_cache.append((line_num, output))
756 if self.db_cache_size <= 1:
724 if self.db_cache_size <= 1:
757 self.save_flag.set()
725 self.save_flag.set()
758
726
759 def _writeout_input_cache(self, conn):
727 def _writeout_input_cache(self, conn):
760 with conn:
728 with conn:
761 for line in self.db_input_cache:
729 for line in self.db_input_cache:
762 conn.execute("INSERT INTO history VALUES (?, ?, ?, ?)",
730 conn.execute("INSERT INTO history VALUES (?, ?, ?, ?)",
763 (self.session_number,)+line)
731 (self.session_number,)+line)
764
732
765 def _writeout_output_cache(self, conn):
733 def _writeout_output_cache(self, conn):
766 with conn:
734 with conn:
767 for line in self.db_output_cache:
735 for line in self.db_output_cache:
768 conn.execute("INSERT INTO output_history VALUES (?, ?, ?)",
736 conn.execute("INSERT INTO output_history VALUES (?, ?, ?)",
769 (self.session_number,)+line)
737 (self.session_number,)+line)
770
738
771 @needs_sqlite
739 @needs_sqlite
772 def writeout_cache(self, conn=None):
740 def writeout_cache(self, conn=None):
773 """Write any entries in the cache to the database."""
741 """Write any entries in the cache to the database."""
774 if conn is None:
742 if conn is None:
775 conn = self.db
743 conn = self.db
776
744
777 with self.db_input_cache_lock:
745 with self.db_input_cache_lock:
778 try:
746 try:
779 self._writeout_input_cache(conn)
747 self._writeout_input_cache(conn)
780 except sqlite3.IntegrityError:
748 except sqlite3.IntegrityError:
781 self.new_session(conn)
749 self.new_session(conn)
782 print("ERROR! Session/line number was not unique in",
750 print("ERROR! Session/line number was not unique in",
783 "database. History logging moved to new session",
751 "database. History logging moved to new session",
784 self.session_number)
752 self.session_number)
785 try:
753 try:
786 # Try writing to the new session. If this fails, don't
754 # Try writing to the new session. If this fails, don't
787 # recurse
755 # recurse
788 self._writeout_input_cache(conn)
756 self._writeout_input_cache(conn)
789 except sqlite3.IntegrityError:
757 except sqlite3.IntegrityError:
790 pass
758 pass
791 finally:
759 finally:
792 self.db_input_cache = []
760 self.db_input_cache = []
793
761
794 with self.db_output_cache_lock:
762 with self.db_output_cache_lock:
795 try:
763 try:
796 self._writeout_output_cache(conn)
764 self._writeout_output_cache(conn)
797 except sqlite3.IntegrityError:
765 except sqlite3.IntegrityError:
798 print("!! Session/line number for output was not unique",
766 print("!! Session/line number for output was not unique",
799 "in database. Output will not be stored.")
767 "in database. Output will not be stored.")
800 finally:
768 finally:
801 self.db_output_cache = []
769 self.db_output_cache = []
802
770
803 class KernelHistoryManager(HistoryAccessorBase):
804 def __init__(self, client):
805 self.client = client
806 self._load_history()
807
808 def _load_history(self):
809 msg_id = self.client.history()
810 while True:
811 try:
812 reply = self.client.get_shell_msg(timeout=1)
813 except Empty:
814 break
815 else:
816 if reply['parent_header'].get('msg_id') == msg_id:
817 history = reply['content'].get('history', [])
818 break
819 self.history = history
820 print("_load_history:", self.history)
821
822 def writeout_cache(self):
823 """dump cache before certain database lookups."""
824 print("write_cache")
825
826 def get_tail(self, n=10, raw=True, output=False, include_latest=False):
827 print("get_tail: ", n)
828 return self.history[-n:]
829
830 def search(self, pattern="*", raw=True, search_raw=True,
831 output=False, n=None, unique=False):
832 print("search: ", pattern)
833 return []
834
835 def get_range(self, session, start=1, stop=None, raw=True,output=False):
836 print("get_range: ", start, stop)
837 if stop is None:
838 stop = -1
839 return self.history[start:stop]
840
841 def get_range_by_str(self, rangestr, raw=True, output=False):
842 print("get_range_by_str: " + rangestr)
843 return []
844
845 def store_inputs(self, line_num, source, source_raw=None):
846 print("store_inputs")
847
848 def store_output(self, line_num):
849 print("store_output")
850
851
771
852 class HistorySavingThread(threading.Thread):
772 class HistorySavingThread(threading.Thread):
853 """This thread takes care of writing history to the database, so that
773 """This thread takes care of writing history to the database, so that
854 the UI isn't held up while that happens.
774 the UI isn't held up while that happens.
855
775
856 It waits for the HistoryManager's save_flag to be set, then writes out
776 It waits for the HistoryManager's save_flag to be set, then writes out
857 the history cache. The main thread is responsible for setting the flag when
777 the history cache. The main thread is responsible for setting the flag when
858 the cache size reaches a defined threshold."""
778 the cache size reaches a defined threshold."""
859 daemon = True
779 daemon = True
860 stop_now = False
780 stop_now = False
861 enabled = True
781 enabled = True
862 def __init__(self, history_manager):
782 def __init__(self, history_manager):
863 super(HistorySavingThread, self).__init__(name="IPythonHistorySavingThread")
783 super(HistorySavingThread, self).__init__(name="IPythonHistorySavingThread")
864 self.history_manager = history_manager
784 self.history_manager = history_manager
865 self.enabled = history_manager.enabled
785 self.enabled = history_manager.enabled
866 atexit.register(self.stop)
786 atexit.register(self.stop)
867
787
868 @needs_sqlite
788 @needs_sqlite
869 def run(self):
789 def run(self):
870 # We need a separate db connection per thread:
790 # We need a separate db connection per thread:
871 try:
791 try:
872 self.db = sqlite3.connect(self.history_manager.hist_file,
792 self.db = sqlite3.connect(self.history_manager.hist_file,
873 **self.history_manager.connection_options
793 **self.history_manager.connection_options
874 )
794 )
875 while True:
795 while True:
876 self.history_manager.save_flag.wait()
796 self.history_manager.save_flag.wait()
877 if self.stop_now:
797 if self.stop_now:
878 self.db.close()
798 self.db.close()
879 return
799 return
880 self.history_manager.save_flag.clear()
800 self.history_manager.save_flag.clear()
881 self.history_manager.writeout_cache(self.db)
801 self.history_manager.writeout_cache(self.db)
882 except Exception as e:
802 except Exception as e:
883 print(("The history saving thread hit an unexpected error (%s)."
803 print(("The history saving thread hit an unexpected error (%s)."
884 "History will not be written to the database.") % repr(e))
804 "History will not be written to the database.") % repr(e))
885
805
886 def stop(self):
806 def stop(self):
887 """This can be called from the main thread to safely stop this thread.
807 """This can be called from the main thread to safely stop this thread.
888
808
889 Note that it does not attempt to write out remaining history before
809 Note that it does not attempt to write out remaining history before
890 exiting. That should be done by calling the HistoryManager's
810 exiting. That should be done by calling the HistoryManager's
891 end_session method."""
811 end_session method."""
892 self.stop_now = True
812 self.stop_now = True
893 self.history_manager.save_flag.set()
813 self.history_manager.save_flag.set()
894 self.join()
814 self.join()
895
815
896
816
897 # To match, e.g. ~5/8-~2/3
817 # To match, e.g. ~5/8-~2/3
898 range_re = re.compile(r"""
818 range_re = re.compile(r"""
899 ((?P<startsess>~?\d+)/)?
819 ((?P<startsess>~?\d+)/)?
900 (?P<start>\d+)?
820 (?P<start>\d+)?
901 ((?P<sep>[\-:])
821 ((?P<sep>[\-:])
902 ((?P<endsess>~?\d+)/)?
822 ((?P<endsess>~?\d+)/)?
903 (?P<end>\d+))?
823 (?P<end>\d+))?
904 $""", re.VERBOSE)
824 $""", re.VERBOSE)
905
825
906
826
907 def extract_hist_ranges(ranges_str):
827 def extract_hist_ranges(ranges_str):
908 """Turn a string of history ranges into 3-tuples of (session, start, stop).
828 """Turn a string of history ranges into 3-tuples of (session, start, stop).
909
829
910 Examples
830 Examples
911 --------
831 --------
912 >>> list(extract_hist_ranges("~8/5-~7/4 2"))
832 >>> list(extract_hist_ranges("~8/5-~7/4 2"))
913 [(-8, 5, None), (-7, 1, 5), (0, 2, 3)]
833 [(-8, 5, None), (-7, 1, 5), (0, 2, 3)]
914 """
834 """
915 for range_str in ranges_str.split():
835 for range_str in ranges_str.split():
916 rmatch = range_re.match(range_str)
836 rmatch = range_re.match(range_str)
917 if not rmatch:
837 if not rmatch:
918 continue
838 continue
919 start = rmatch.group("start")
839 start = rmatch.group("start")
920 if start:
840 if start:
921 start = int(start)
841 start = int(start)
922 end = rmatch.group("end")
842 end = rmatch.group("end")
923 # If no end specified, get (a, a + 1)
843 # If no end specified, get (a, a + 1)
924 end = int(end) if end else start + 1
844 end = int(end) if end else start + 1
925 else: # start not specified
845 else: # start not specified
926 if not rmatch.group('startsess'): # no startsess
846 if not rmatch.group('startsess'): # no startsess
927 continue
847 continue
928 start = 1
848 start = 1
929 end = None # provide the entire session hist
849 end = None # provide the entire session hist
930
850
931 if rmatch.group("sep") == "-": # 1-3 == 1:4 --> [1, 2, 3]
851 if rmatch.group("sep") == "-": # 1-3 == 1:4 --> [1, 2, 3]
932 end += 1
852 end += 1
933 startsess = rmatch.group("startsess") or "0"
853 startsess = rmatch.group("startsess") or "0"
934 endsess = rmatch.group("endsess") or startsess
854 endsess = rmatch.group("endsess") or startsess
935 startsess = int(startsess.replace("~","-"))
855 startsess = int(startsess.replace("~","-"))
936 endsess = int(endsess.replace("~","-"))
856 endsess = int(endsess.replace("~","-"))
937 assert endsess >= startsess, "start session must be earlier than end session"
857 assert endsess >= startsess, "start session must be earlier than end session"
938
858
939 if endsess == startsess:
859 if endsess == startsess:
940 yield (startsess, start, end)
860 yield (startsess, start, end)
941 continue
861 continue
942 # Multiple sessions in one range:
862 # Multiple sessions in one range:
943 yield (startsess, start, None)
863 yield (startsess, start, None)
944 for sess in range(startsess+1, endsess):
864 for sess in range(startsess+1, endsess):
945 yield (sess, 1, None)
865 yield (sess, 1, None)
946 yield (endsess, 1, end)
866 yield (endsess, 1, end)
947
867
948
868
949 def _format_lineno(session, line):
869 def _format_lineno(session, line):
950 """Helper function to format line numbers properly."""
870 """Helper function to format line numbers properly."""
951 if session == 0:
871 if session == 0:
952 return str(line)
872 return str(line)
953 return "%s#%s" % (session, line)
873 return "%s#%s" % (session, line)
954
874
955
875
@@ -1,578 +1,578 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """terminal client to the IPython kernel"""
2 """terminal client to the IPython kernel"""
3
3
4 # Copyright (c) IPython Development Team.
4 # Copyright (c) IPython Development Team.
5 # Distributed under the terms of the Modified BSD License.
5 # Distributed under the terms of the Modified BSD License.
6
6
7 from __future__ import print_function
7 from __future__ import print_function
8
8
9 import base64
9 import base64
10 import bdb
10 import bdb
11 import signal
11 import signal
12 import os
12 import os
13 import sys
13 import sys
14 import time
14 import time
15 import subprocess
15 import subprocess
16 from getpass import getpass
16 from getpass import getpass
17 from io import BytesIO
17 from io import BytesIO
18
18
19 try:
19 try:
20 from queue import Empty # Py 3
20 from queue import Empty # Py 3
21 except ImportError:
21 except ImportError:
22 from Queue import Empty # Py 2
22 from Queue import Empty # Py 2
23
23
24 from IPython.core import page
24 from IPython.core import page
25 from IPython.core import release
25 from IPython.core import release
26 from IPython.core.history import KernelHistoryManager
26 from IPython.terminal.console.zmqhistory import ZMQHistoryManager
27 from IPython.utils.warn import warn, error
27 from IPython.utils.warn import warn, error
28 from IPython.utils import io
28 from IPython.utils import io
29 from IPython.utils.py3compat import string_types, input
29 from IPython.utils.py3compat import string_types, input
30 from IPython.utils.traitlets import List, Enum, Any, Instance, Unicode, Float, Bool
30 from IPython.utils.traitlets import List, Enum, Any, Instance, Unicode, Float, Bool
31 from IPython.utils.tempdir import NamedFileInTemporaryDirectory
31 from IPython.utils.tempdir import NamedFileInTemporaryDirectory
32
32
33 from IPython.terminal.interactiveshell import TerminalInteractiveShell
33 from IPython.terminal.interactiveshell import TerminalInteractiveShell
34 from IPython.terminal.console.completer import ZMQCompleter
34 from IPython.terminal.console.completer import ZMQCompleter
35
35
36 class ZMQTerminalInteractiveShell(TerminalInteractiveShell):
36 class ZMQTerminalInteractiveShell(TerminalInteractiveShell):
37 """A subclass of TerminalInteractiveShell that uses the 0MQ kernel"""
37 """A subclass of TerminalInteractiveShell that uses the 0MQ kernel"""
38 _executing = False
38 _executing = False
39 _execution_state = Unicode('')
39 _execution_state = Unicode('')
40 _pending_clearoutput = False
40 _pending_clearoutput = False
41 kernel_banner = Unicode('')
41 kernel_banner = Unicode('')
42 kernel_timeout = Float(60, config=True,
42 kernel_timeout = Float(60, config=True,
43 help="""Timeout for giving up on a kernel (in seconds).
43 help="""Timeout for giving up on a kernel (in seconds).
44
44
45 On first connect and restart, the console tests whether the
45 On first connect and restart, the console tests whether the
46 kernel is running and responsive by sending kernel_info_requests.
46 kernel is running and responsive by sending kernel_info_requests.
47 This sets the timeout in seconds for how long the kernel can take
47 This sets the timeout in seconds for how long the kernel can take
48 before being presumed dead.
48 before being presumed dead.
49 """
49 """
50 )
50 )
51
51
52 image_handler = Enum(('PIL', 'stream', 'tempfile', 'callable'),
52 image_handler = Enum(('PIL', 'stream', 'tempfile', 'callable'),
53 config=True, help=
53 config=True, help=
54 """
54 """
55 Handler for image type output. This is useful, for example,
55 Handler for image type output. This is useful, for example,
56 when connecting to the kernel in which pylab inline backend is
56 when connecting to the kernel in which pylab inline backend is
57 activated. There are four handlers defined. 'PIL': Use
57 activated. There are four handlers defined. 'PIL': Use
58 Python Imaging Library to popup image; 'stream': Use an
58 Python Imaging Library to popup image; 'stream': Use an
59 external program to show the image. Image will be fed into
59 external program to show the image. Image will be fed into
60 the STDIN of the program. You will need to configure
60 the STDIN of the program. You will need to configure
61 `stream_image_handler`; 'tempfile': Use an external program to
61 `stream_image_handler`; 'tempfile': Use an external program to
62 show the image. Image will be saved in a temporally file and
62 show the image. Image will be saved in a temporally file and
63 the program is called with the temporally file. You will need
63 the program is called with the temporally file. You will need
64 to configure `tempfile_image_handler`; 'callable': You can set
64 to configure `tempfile_image_handler`; 'callable': You can set
65 any Python callable which is called with the image data. You
65 any Python callable which is called with the image data. You
66 will need to configure `callable_image_handler`.
66 will need to configure `callable_image_handler`.
67 """
67 """
68 )
68 )
69
69
70 stream_image_handler = List(config=True, help=
70 stream_image_handler = List(config=True, help=
71 """
71 """
72 Command to invoke an image viewer program when you are using
72 Command to invoke an image viewer program when you are using
73 'stream' image handler. This option is a list of string where
73 'stream' image handler. This option is a list of string where
74 the first element is the command itself and reminders are the
74 the first element is the command itself and reminders are the
75 options for the command. Raw image data is given as STDIN to
75 options for the command. Raw image data is given as STDIN to
76 the program.
76 the program.
77 """
77 """
78 )
78 )
79
79
80 tempfile_image_handler = List(config=True, help=
80 tempfile_image_handler = List(config=True, help=
81 """
81 """
82 Command to invoke an image viewer program when you are using
82 Command to invoke an image viewer program when you are using
83 'tempfile' image handler. This option is a list of string
83 'tempfile' image handler. This option is a list of string
84 where the first element is the command itself and reminders
84 where the first element is the command itself and reminders
85 are the options for the command. You can use {file} and
85 are the options for the command. You can use {file} and
86 {format} in the string to represent the location of the
86 {format} in the string to represent the location of the
87 generated image file and image format.
87 generated image file and image format.
88 """
88 """
89 )
89 )
90
90
91 callable_image_handler = Any(config=True, help=
91 callable_image_handler = Any(config=True, help=
92 """
92 """
93 Callable object called via 'callable' image handler with one
93 Callable object called via 'callable' image handler with one
94 argument, `data`, which is `msg["content"]["data"]` where
94 argument, `data`, which is `msg["content"]["data"]` where
95 `msg` is the message from iopub channel. For exmaple, you can
95 `msg` is the message from iopub channel. For exmaple, you can
96 find base64 encoded PNG data as `data['image/png']`.
96 find base64 encoded PNG data as `data['image/png']`.
97 """
97 """
98 )
98 )
99
99
100 mime_preference = List(
100 mime_preference = List(
101 default_value=['image/png', 'image/jpeg', 'image/svg+xml'],
101 default_value=['image/png', 'image/jpeg', 'image/svg+xml'],
102 config=True, allow_none=False, help=
102 config=True, allow_none=False, help=
103 """
103 """
104 Preferred object representation MIME type in order. First
104 Preferred object representation MIME type in order. First
105 matched MIME type will be used.
105 matched MIME type will be used.
106 """
106 """
107 )
107 )
108
108
109 manager = Instance('IPython.kernel.KernelManager')
109 manager = Instance('IPython.kernel.KernelManager')
110 client = Instance('IPython.kernel.KernelClient')
110 client = Instance('IPython.kernel.KernelClient')
111 def _client_changed(self, name, old, new):
111 def _client_changed(self, name, old, new):
112 self.session_id = new.session.session
112 self.session_id = new.session.session
113 session_id = Unicode()
113 session_id = Unicode()
114
114
115 def init_completer(self):
115 def init_completer(self):
116 """Initialize the completion machinery.
116 """Initialize the completion machinery.
117
117
118 This creates completion machinery that can be used by client code,
118 This creates completion machinery that can be used by client code,
119 either interactively in-process (typically triggered by the readline
119 either interactively in-process (typically triggered by the readline
120 library), programmatically (such as in test suites) or out-of-process
120 library), programmatically (such as in test suites) or out-of-process
121 (typically over the network by remote frontends).
121 (typically over the network by remote frontends).
122 """
122 """
123 from IPython.core.completerlib import (module_completer,
123 from IPython.core.completerlib import (module_completer,
124 magic_run_completer, cd_completer)
124 magic_run_completer, cd_completer)
125
125
126 self.Completer = ZMQCompleter(self, self.client, config=self.config)
126 self.Completer = ZMQCompleter(self, self.client, config=self.config)
127
127
128
128
129 self.set_hook('complete_command', module_completer, str_key = 'import')
129 self.set_hook('complete_command', module_completer, str_key = 'import')
130 self.set_hook('complete_command', module_completer, str_key = 'from')
130 self.set_hook('complete_command', module_completer, str_key = 'from')
131 self.set_hook('complete_command', magic_run_completer, str_key = '%run')
131 self.set_hook('complete_command', magic_run_completer, str_key = '%run')
132 self.set_hook('complete_command', cd_completer, str_key = '%cd')
132 self.set_hook('complete_command', cd_completer, str_key = '%cd')
133
133
134 # Only configure readline if we truly are using readline. IPython can
134 # Only configure readline if we truly are using readline. IPython can
135 # do tab-completion over the network, in GUIs, etc, where readline
135 # do tab-completion over the network, in GUIs, etc, where readline
136 # itself may be absent
136 # itself may be absent
137 if self.has_readline:
137 if self.has_readline:
138 self.set_readline_completer()
138 self.set_readline_completer()
139
139
140 def run_cell(self, cell, store_history=True):
140 def run_cell(self, cell, store_history=True):
141 """Run a complete IPython cell.
141 """Run a complete IPython cell.
142
142
143 Parameters
143 Parameters
144 ----------
144 ----------
145 cell : str
145 cell : str
146 The code (including IPython code such as %magic functions) to run.
146 The code (including IPython code such as %magic functions) to run.
147 store_history : bool
147 store_history : bool
148 If True, the raw and translated cell will be stored in IPython's
148 If True, the raw and translated cell will be stored in IPython's
149 history. For user code calling back into IPython's machinery, this
149 history. For user code calling back into IPython's machinery, this
150 should be set to False.
150 should be set to False.
151 """
151 """
152 if (not cell) or cell.isspace():
152 if (not cell) or cell.isspace():
153 # pressing enter flushes any pending display
153 # pressing enter flushes any pending display
154 self.handle_iopub()
154 self.handle_iopub()
155 return
155 return
156
156
157 # flush stale replies, which could have been ignored, due to missed heartbeats
157 # flush stale replies, which could have been ignored, due to missed heartbeats
158 while self.client.shell_channel.msg_ready():
158 while self.client.shell_channel.msg_ready():
159 self.client.shell_channel.get_msg()
159 self.client.shell_channel.get_msg()
160 # shell_channel.execute takes 'hidden', which is the inverse of store_hist
160 # shell_channel.execute takes 'hidden', which is the inverse of store_hist
161 msg_id = self.client.shell_channel.execute(cell, not store_history)
161 msg_id = self.client.shell_channel.execute(cell, not store_history)
162
162
163 # first thing is wait for any side effects (output, stdin, etc.)
163 # first thing is wait for any side effects (output, stdin, etc.)
164 self._executing = True
164 self._executing = True
165 self._execution_state = "busy"
165 self._execution_state = "busy"
166 while self._execution_state != 'idle' and self.client.is_alive():
166 while self._execution_state != 'idle' and self.client.is_alive():
167 try:
167 try:
168 self.handle_input_request(msg_id, timeout=0.05)
168 self.handle_input_request(msg_id, timeout=0.05)
169 except Empty:
169 except Empty:
170 # display intermediate print statements, etc.
170 # display intermediate print statements, etc.
171 self.handle_iopub(msg_id)
171 self.handle_iopub(msg_id)
172
172
173 # after all of that is done, wait for the execute reply
173 # after all of that is done, wait for the execute reply
174 while self.client.is_alive():
174 while self.client.is_alive():
175 try:
175 try:
176 self.handle_execute_reply(msg_id, timeout=0.05)
176 self.handle_execute_reply(msg_id, timeout=0.05)
177 except Empty:
177 except Empty:
178 pass
178 pass
179 else:
179 else:
180 break
180 break
181 self._executing = False
181 self._executing = False
182
182
183 #-----------------
183 #-----------------
184 # message handlers
184 # message handlers
185 #-----------------
185 #-----------------
186
186
187 def handle_execute_reply(self, msg_id, timeout=None):
187 def handle_execute_reply(self, msg_id, timeout=None):
188 msg = self.client.shell_channel.get_msg(block=False, timeout=timeout)
188 msg = self.client.shell_channel.get_msg(block=False, timeout=timeout)
189 if msg["parent_header"].get("msg_id", None) == msg_id:
189 if msg["parent_header"].get("msg_id", None) == msg_id:
190
190
191 self.handle_iopub(msg_id)
191 self.handle_iopub(msg_id)
192
192
193 content = msg["content"]
193 content = msg["content"]
194 status = content['status']
194 status = content['status']
195
195
196 if status == 'aborted':
196 if status == 'aborted':
197 self.write('Aborted\n')
197 self.write('Aborted\n')
198 return
198 return
199 elif status == 'ok':
199 elif status == 'ok':
200 # handle payloads
200 # handle payloads
201 for item in content["payload"]:
201 for item in content["payload"]:
202 source = item['source']
202 source = item['source']
203 if source == 'page':
203 if source == 'page':
204 page.page(item['data']['text/plain'])
204 page.page(item['data']['text/plain'])
205 elif source == 'set_next_input':
205 elif source == 'set_next_input':
206 self.set_next_input(item['text'])
206 self.set_next_input(item['text'])
207 elif source == 'ask_exit':
207 elif source == 'ask_exit':
208 self.ask_exit()
208 self.ask_exit()
209
209
210 elif status == 'error':
210 elif status == 'error':
211 for frame in content["traceback"]:
211 for frame in content["traceback"]:
212 print(frame, file=io.stderr)
212 print(frame, file=io.stderr)
213
213
214 self.execution_count = int(content["execution_count"] + 1)
214 self.execution_count = int(content["execution_count"] + 1)
215
215
216 include_other_output = Bool(False, config=True,
216 include_other_output = Bool(False, config=True,
217 help="""Whether to include output from clients
217 help="""Whether to include output from clients
218 other than this one sharing the same kernel.
218 other than this one sharing the same kernel.
219
219
220 Outputs are not displayed until enter is pressed.
220 Outputs are not displayed until enter is pressed.
221 """
221 """
222 )
222 )
223 other_output_prefix = Unicode("[remote] ", config=True,
223 other_output_prefix = Unicode("[remote] ", config=True,
224 help="""Prefix to add to outputs coming from clients other than this one.
224 help="""Prefix to add to outputs coming from clients other than this one.
225
225
226 Only relevant if include_other_output is True.
226 Only relevant if include_other_output is True.
227 """
227 """
228 )
228 )
229
229
230 def from_here(self, msg):
230 def from_here(self, msg):
231 """Return whether a message is from this session"""
231 """Return whether a message is from this session"""
232 return msg['parent_header'].get("session", self.session_id) == self.session_id
232 return msg['parent_header'].get("session", self.session_id) == self.session_id
233
233
234 def include_output(self, msg):
234 def include_output(self, msg):
235 """Return whether we should include a given output message"""
235 """Return whether we should include a given output message"""
236 from_here = self.from_here(msg)
236 from_here = self.from_here(msg)
237 if msg['msg_type'] == 'execute_input':
237 if msg['msg_type'] == 'execute_input':
238 # only echo inputs not from here
238 # only echo inputs not from here
239 return self.include_other_output and not from_here
239 return self.include_other_output and not from_here
240
240
241 if self.include_other_output:
241 if self.include_other_output:
242 return True
242 return True
243 else:
243 else:
244 return from_here
244 return from_here
245
245
246 def handle_iopub(self, msg_id=''):
246 def handle_iopub(self, msg_id=''):
247 """Process messages on the IOPub channel
247 """Process messages on the IOPub channel
248
248
249 This method consumes and processes messages on the IOPub channel,
249 This method consumes and processes messages on the IOPub channel,
250 such as stdout, stderr, execute_result and status.
250 such as stdout, stderr, execute_result and status.
251
251
252 It only displays output that is caused by this session.
252 It only displays output that is caused by this session.
253 """
253 """
254 while self.client.iopub_channel.msg_ready():
254 while self.client.iopub_channel.msg_ready():
255 sub_msg = self.client.iopub_channel.get_msg()
255 sub_msg = self.client.iopub_channel.get_msg()
256 msg_type = sub_msg['header']['msg_type']
256 msg_type = sub_msg['header']['msg_type']
257 parent = sub_msg["parent_header"]
257 parent = sub_msg["parent_header"]
258
258
259 if self.include_output(sub_msg):
259 if self.include_output(sub_msg):
260 if msg_type == 'status':
260 if msg_type == 'status':
261 self._execution_state = sub_msg["content"]["execution_state"]
261 self._execution_state = sub_msg["content"]["execution_state"]
262 elif msg_type == 'stream':
262 elif msg_type == 'stream':
263 if sub_msg["content"]["name"] == "stdout":
263 if sub_msg["content"]["name"] == "stdout":
264 if self._pending_clearoutput:
264 if self._pending_clearoutput:
265 print("\r", file=io.stdout, end="")
265 print("\r", file=io.stdout, end="")
266 self._pending_clearoutput = False
266 self._pending_clearoutput = False
267 print(sub_msg["content"]["text"], file=io.stdout, end="")
267 print(sub_msg["content"]["text"], file=io.stdout, end="")
268 io.stdout.flush()
268 io.stdout.flush()
269 elif sub_msg["content"]["name"] == "stderr":
269 elif sub_msg["content"]["name"] == "stderr":
270 if self._pending_clearoutput:
270 if self._pending_clearoutput:
271 print("\r", file=io.stderr, end="")
271 print("\r", file=io.stderr, end="")
272 self._pending_clearoutput = False
272 self._pending_clearoutput = False
273 print(sub_msg["content"]["text"], file=io.stderr, end="")
273 print(sub_msg["content"]["text"], file=io.stderr, end="")
274 io.stderr.flush()
274 io.stderr.flush()
275
275
276 elif msg_type == 'execute_result':
276 elif msg_type == 'execute_result':
277 if self._pending_clearoutput:
277 if self._pending_clearoutput:
278 print("\r", file=io.stdout, end="")
278 print("\r", file=io.stdout, end="")
279 self._pending_clearoutput = False
279 self._pending_clearoutput = False
280 self.execution_count = int(sub_msg["content"]["execution_count"])
280 self.execution_count = int(sub_msg["content"]["execution_count"])
281 if not self.from_here(sub_msg):
281 if not self.from_here(sub_msg):
282 sys.stdout.write(self.other_output_prefix)
282 sys.stdout.write(self.other_output_prefix)
283 format_dict = sub_msg["content"]["data"]
283 format_dict = sub_msg["content"]["data"]
284 self.handle_rich_data(format_dict)
284 self.handle_rich_data(format_dict)
285
285
286 # taken from DisplayHook.__call__:
286 # taken from DisplayHook.__call__:
287 hook = self.displayhook
287 hook = self.displayhook
288 hook.start_displayhook()
288 hook.start_displayhook()
289 hook.write_output_prompt()
289 hook.write_output_prompt()
290 hook.write_format_data(format_dict)
290 hook.write_format_data(format_dict)
291 hook.log_output(format_dict)
291 hook.log_output(format_dict)
292 hook.finish_displayhook()
292 hook.finish_displayhook()
293
293
294 elif msg_type == 'display_data':
294 elif msg_type == 'display_data':
295 data = sub_msg["content"]["data"]
295 data = sub_msg["content"]["data"]
296 handled = self.handle_rich_data(data)
296 handled = self.handle_rich_data(data)
297 if not handled:
297 if not handled:
298 if not self.from_here(sub_msg):
298 if not self.from_here(sub_msg):
299 sys.stdout.write(self.other_output_prefix)
299 sys.stdout.write(self.other_output_prefix)
300 # if it was an image, we handled it by now
300 # if it was an image, we handled it by now
301 if 'text/plain' in data:
301 if 'text/plain' in data:
302 print(data['text/plain'])
302 print(data['text/plain'])
303
303
304 elif msg_type == 'execute_input':
304 elif msg_type == 'execute_input':
305 content = sub_msg['content']
305 content = sub_msg['content']
306 self.execution_count = content['execution_count']
306 self.execution_count = content['execution_count']
307 if not self.from_here(sub_msg):
307 if not self.from_here(sub_msg):
308 sys.stdout.write(self.other_output_prefix)
308 sys.stdout.write(self.other_output_prefix)
309 sys.stdout.write(self.prompt_manager.render('in'))
309 sys.stdout.write(self.prompt_manager.render('in'))
310 sys.stdout.write(content['code'])
310 sys.stdout.write(content['code'])
311
311
312 elif msg_type == 'clear_output':
312 elif msg_type == 'clear_output':
313 if sub_msg["content"]["wait"]:
313 if sub_msg["content"]["wait"]:
314 self._pending_clearoutput = True
314 self._pending_clearoutput = True
315 else:
315 else:
316 print("\r", file=io.stdout, end="")
316 print("\r", file=io.stdout, end="")
317
317
318 _imagemime = {
318 _imagemime = {
319 'image/png': 'png',
319 'image/png': 'png',
320 'image/jpeg': 'jpeg',
320 'image/jpeg': 'jpeg',
321 'image/svg+xml': 'svg',
321 'image/svg+xml': 'svg',
322 }
322 }
323
323
324 def handle_rich_data(self, data):
324 def handle_rich_data(self, data):
325 for mime in self.mime_preference:
325 for mime in self.mime_preference:
326 if mime in data and mime in self._imagemime:
326 if mime in data and mime in self._imagemime:
327 self.handle_image(data, mime)
327 self.handle_image(data, mime)
328 return True
328 return True
329
329
330 def handle_image(self, data, mime):
330 def handle_image(self, data, mime):
331 handler = getattr(
331 handler = getattr(
332 self, 'handle_image_{0}'.format(self.image_handler), None)
332 self, 'handle_image_{0}'.format(self.image_handler), None)
333 if handler:
333 if handler:
334 handler(data, mime)
334 handler(data, mime)
335
335
336 def handle_image_PIL(self, data, mime):
336 def handle_image_PIL(self, data, mime):
337 if mime not in ('image/png', 'image/jpeg'):
337 if mime not in ('image/png', 'image/jpeg'):
338 return
338 return
339 import PIL.Image
339 import PIL.Image
340 raw = base64.decodestring(data[mime].encode('ascii'))
340 raw = base64.decodestring(data[mime].encode('ascii'))
341 img = PIL.Image.open(BytesIO(raw))
341 img = PIL.Image.open(BytesIO(raw))
342 img.show()
342 img.show()
343
343
344 def handle_image_stream(self, data, mime):
344 def handle_image_stream(self, data, mime):
345 raw = base64.decodestring(data[mime].encode('ascii'))
345 raw = base64.decodestring(data[mime].encode('ascii'))
346 imageformat = self._imagemime[mime]
346 imageformat = self._imagemime[mime]
347 fmt = dict(format=imageformat)
347 fmt = dict(format=imageformat)
348 args = [s.format(**fmt) for s in self.stream_image_handler]
348 args = [s.format(**fmt) for s in self.stream_image_handler]
349 with open(os.devnull, 'w') as devnull:
349 with open(os.devnull, 'w') as devnull:
350 proc = subprocess.Popen(
350 proc = subprocess.Popen(
351 args, stdin=subprocess.PIPE,
351 args, stdin=subprocess.PIPE,
352 stdout=devnull, stderr=devnull)
352 stdout=devnull, stderr=devnull)
353 proc.communicate(raw)
353 proc.communicate(raw)
354
354
355 def handle_image_tempfile(self, data, mime):
355 def handle_image_tempfile(self, data, mime):
356 raw = base64.decodestring(data[mime].encode('ascii'))
356 raw = base64.decodestring(data[mime].encode('ascii'))
357 imageformat = self._imagemime[mime]
357 imageformat = self._imagemime[mime]
358 filename = 'tmp.{0}'.format(imageformat)
358 filename = 'tmp.{0}'.format(imageformat)
359 with NamedFileInTemporaryDirectory(filename) as f, \
359 with NamedFileInTemporaryDirectory(filename) as f, \
360 open(os.devnull, 'w') as devnull:
360 open(os.devnull, 'w') as devnull:
361 f.write(raw)
361 f.write(raw)
362 f.flush()
362 f.flush()
363 fmt = dict(file=f.name, format=imageformat)
363 fmt = dict(file=f.name, format=imageformat)
364 args = [s.format(**fmt) for s in self.tempfile_image_handler]
364 args = [s.format(**fmt) for s in self.tempfile_image_handler]
365 subprocess.call(args, stdout=devnull, stderr=devnull)
365 subprocess.call(args, stdout=devnull, stderr=devnull)
366
366
367 def handle_image_callable(self, data, mime):
367 def handle_image_callable(self, data, mime):
368 self.callable_image_handler(data)
368 self.callable_image_handler(data)
369
369
370 def handle_input_request(self, msg_id, timeout=0.1):
370 def handle_input_request(self, msg_id, timeout=0.1):
371 """ Method to capture raw_input
371 """ Method to capture raw_input
372 """
372 """
373 req = self.client.stdin_channel.get_msg(timeout=timeout)
373 req = self.client.stdin_channel.get_msg(timeout=timeout)
374 # in case any iopub came while we were waiting:
374 # in case any iopub came while we were waiting:
375 self.handle_iopub(msg_id)
375 self.handle_iopub(msg_id)
376 if msg_id == req["parent_header"].get("msg_id"):
376 if msg_id == req["parent_header"].get("msg_id"):
377 # wrap SIGINT handler
377 # wrap SIGINT handler
378 real_handler = signal.getsignal(signal.SIGINT)
378 real_handler = signal.getsignal(signal.SIGINT)
379 def double_int(sig,frame):
379 def double_int(sig,frame):
380 # call real handler (forwards sigint to kernel),
380 # call real handler (forwards sigint to kernel),
381 # then raise local interrupt, stopping local raw_input
381 # then raise local interrupt, stopping local raw_input
382 real_handler(sig,frame)
382 real_handler(sig,frame)
383 raise KeyboardInterrupt
383 raise KeyboardInterrupt
384 signal.signal(signal.SIGINT, double_int)
384 signal.signal(signal.SIGINT, double_int)
385 content = req['content']
385 content = req['content']
386 read = getpass if content.get('password', False) else input
386 read = getpass if content.get('password', False) else input
387 try:
387 try:
388 raw_data = read(content["prompt"])
388 raw_data = read(content["prompt"])
389 except EOFError:
389 except EOFError:
390 # turn EOFError into EOF character
390 # turn EOFError into EOF character
391 raw_data = '\x04'
391 raw_data = '\x04'
392 except KeyboardInterrupt:
392 except KeyboardInterrupt:
393 sys.stdout.write('\n')
393 sys.stdout.write('\n')
394 return
394 return
395 finally:
395 finally:
396 # restore SIGINT handler
396 # restore SIGINT handler
397 signal.signal(signal.SIGINT, real_handler)
397 signal.signal(signal.SIGINT, real_handler)
398
398
399 # only send stdin reply if there *was not* another request
399 # only send stdin reply if there *was not* another request
400 # or execution finished while we were reading.
400 # or execution finished while we were reading.
401 if not (self.client.stdin_channel.msg_ready() or self.client.shell_channel.msg_ready()):
401 if not (self.client.stdin_channel.msg_ready() or self.client.shell_channel.msg_ready()):
402 self.client.stdin_channel.input(raw_data)
402 self.client.stdin_channel.input(raw_data)
403
403
404 def mainloop(self, display_banner=False):
404 def mainloop(self, display_banner=False):
405 while True:
405 while True:
406 try:
406 try:
407 self.interact(display_banner=display_banner)
407 self.interact(display_banner=display_banner)
408 #self.interact_with_readline()
408 #self.interact_with_readline()
409 # XXX for testing of a readline-decoupled repl loop, call
409 # XXX for testing of a readline-decoupled repl loop, call
410 # interact_with_readline above
410 # interact_with_readline above
411 break
411 break
412 except KeyboardInterrupt:
412 except KeyboardInterrupt:
413 # this should not be necessary, but KeyboardInterrupt
413 # this should not be necessary, but KeyboardInterrupt
414 # handling seems rather unpredictable...
414 # handling seems rather unpredictable...
415 self.write("\nKeyboardInterrupt in interact()\n")
415 self.write("\nKeyboardInterrupt in interact()\n")
416
416
417 self.client.shell_channel.shutdown()
417 self.client.shell_channel.shutdown()
418
418
419 def _banner1_default(self):
419 def _banner1_default(self):
420 return "IPython Console {version}\n".format(version=release.version)
420 return "IPython Console {version}\n".format(version=release.version)
421
421
422 def compute_banner(self):
422 def compute_banner(self):
423 super(ZMQTerminalInteractiveShell, self).compute_banner()
423 super(ZMQTerminalInteractiveShell, self).compute_banner()
424 if self.client and not self.kernel_banner:
424 if self.client and not self.kernel_banner:
425 msg_id = self.client.kernel_info()
425 msg_id = self.client.kernel_info()
426 while True:
426 while True:
427 try:
427 try:
428 reply = self.client.get_shell_msg(timeout=1)
428 reply = self.client.get_shell_msg(timeout=1)
429 except Empty:
429 except Empty:
430 break
430 break
431 else:
431 else:
432 if reply['parent_header'].get('msg_id') == msg_id:
432 if reply['parent_header'].get('msg_id') == msg_id:
433 self.kernel_banner = reply['content'].get('banner', '')
433 self.kernel_banner = reply['content'].get('banner', '')
434 break
434 break
435 self.banner += self.kernel_banner
435 self.banner += self.kernel_banner
436
436
437 def wait_for_kernel(self, timeout=None):
437 def wait_for_kernel(self, timeout=None):
438 """method to wait for a kernel to be ready"""
438 """method to wait for a kernel to be ready"""
439 tic = time.time()
439 tic = time.time()
440 self.client.hb_channel.unpause()
440 self.client.hb_channel.unpause()
441 while True:
441 while True:
442 msg_id = self.client.kernel_info()
442 msg_id = self.client.kernel_info()
443 reply = None
443 reply = None
444 while True:
444 while True:
445 try:
445 try:
446 reply = self.client.get_shell_msg(timeout=1)
446 reply = self.client.get_shell_msg(timeout=1)
447 except Empty:
447 except Empty:
448 break
448 break
449 else:
449 else:
450 if reply['parent_header'].get('msg_id') == msg_id:
450 if reply['parent_header'].get('msg_id') == msg_id:
451 return True
451 return True
452 if timeout is not None \
452 if timeout is not None \
453 and (time.time() - tic) > timeout \
453 and (time.time() - tic) > timeout \
454 and not self.client.hb_channel.is_beating():
454 and not self.client.hb_channel.is_beating():
455 # heart failed
455 # heart failed
456 return False
456 return False
457 return True
457 return True
458
458
459 def interact(self, display_banner=None):
459 def interact(self, display_banner=None):
460 """Closely emulate the interactive Python console."""
460 """Closely emulate the interactive Python console."""
461
461
462 # batch run -> do not interact
462 # batch run -> do not interact
463 if self.exit_now:
463 if self.exit_now:
464 return
464 return
465
465
466 if display_banner is None:
466 if display_banner is None:
467 display_banner = self.display_banner
467 display_banner = self.display_banner
468
468
469 if isinstance(display_banner, string_types):
469 if isinstance(display_banner, string_types):
470 self.show_banner(display_banner)
470 self.show_banner(display_banner)
471 elif display_banner:
471 elif display_banner:
472 self.show_banner()
472 self.show_banner()
473
473
474 more = False
474 more = False
475
475
476 # run a non-empty no-op, so that we don't get a prompt until
476 # run a non-empty no-op, so that we don't get a prompt until
477 # we know the kernel is ready. This keeps the connection
477 # we know the kernel is ready. This keeps the connection
478 # message above the first prompt.
478 # message above the first prompt.
479 if not self.wait_for_kernel(self.kernel_timeout):
479 if not self.wait_for_kernel(self.kernel_timeout):
480 error("Kernel did not respond\n")
480 error("Kernel did not respond\n")
481 return
481 return
482
482
483 if self.has_readline:
483 if self.has_readline:
484 self.readline_startup_hook(self.pre_readline)
484 self.readline_startup_hook(self.pre_readline)
485 hlen_b4_cell = self.readline.get_current_history_length()
485 hlen_b4_cell = self.readline.get_current_history_length()
486 else:
486 else:
487 hlen_b4_cell = 0
487 hlen_b4_cell = 0
488 # exit_now is set by a call to %Exit or %Quit, through the
488 # exit_now is set by a call to %Exit or %Quit, through the
489 # ask_exit callback.
489 # ask_exit callback.
490
490
491 while not self.exit_now:
491 while not self.exit_now:
492 if not self.client.is_alive():
492 if not self.client.is_alive():
493 # kernel died, prompt for action or exit
493 # kernel died, prompt for action or exit
494
494
495 action = "restart" if self.manager else "wait for restart"
495 action = "restart" if self.manager else "wait for restart"
496 ans = self.ask_yes_no("kernel died, %s ([y]/n)?" % action, default='y')
496 ans = self.ask_yes_no("kernel died, %s ([y]/n)?" % action, default='y')
497 if ans:
497 if ans:
498 if self.manager:
498 if self.manager:
499 self.manager.restart_kernel(True)
499 self.manager.restart_kernel(True)
500 self.wait_for_kernel(self.kernel_timeout)
500 self.wait_for_kernel(self.kernel_timeout)
501 else:
501 else:
502 self.exit_now = True
502 self.exit_now = True
503 continue
503 continue
504 try:
504 try:
505 # protect prompt block from KeyboardInterrupt
505 # protect prompt block from KeyboardInterrupt
506 # when sitting on ctrl-C
506 # when sitting on ctrl-C
507 self.hooks.pre_prompt_hook()
507 self.hooks.pre_prompt_hook()
508 if more:
508 if more:
509 try:
509 try:
510 prompt = self.prompt_manager.render('in2')
510 prompt = self.prompt_manager.render('in2')
511 except Exception:
511 except Exception:
512 self.showtraceback()
512 self.showtraceback()
513 if self.autoindent:
513 if self.autoindent:
514 self.rl_do_indent = True
514 self.rl_do_indent = True
515
515
516 else:
516 else:
517 try:
517 try:
518 prompt = self.separate_in + self.prompt_manager.render('in')
518 prompt = self.separate_in + self.prompt_manager.render('in')
519 except Exception:
519 except Exception:
520 self.showtraceback()
520 self.showtraceback()
521
521
522 line = self.raw_input(prompt)
522 line = self.raw_input(prompt)
523 if self.exit_now:
523 if self.exit_now:
524 # quick exit on sys.std[in|out] close
524 # quick exit on sys.std[in|out] close
525 break
525 break
526 if self.autoindent:
526 if self.autoindent:
527 self.rl_do_indent = False
527 self.rl_do_indent = False
528
528
529 except KeyboardInterrupt:
529 except KeyboardInterrupt:
530 #double-guard against keyboardinterrupts during kbdint handling
530 #double-guard against keyboardinterrupts during kbdint handling
531 try:
531 try:
532 self.write('\nKeyboardInterrupt\n')
532 self.write('\nKeyboardInterrupt\n')
533 source_raw = self.input_splitter.raw_reset()
533 source_raw = self.input_splitter.raw_reset()
534 hlen_b4_cell = self._replace_rlhist_multiline(source_raw, hlen_b4_cell)
534 hlen_b4_cell = self._replace_rlhist_multiline(source_raw, hlen_b4_cell)
535 more = False
535 more = False
536 except KeyboardInterrupt:
536 except KeyboardInterrupt:
537 pass
537 pass
538 except EOFError:
538 except EOFError:
539 if self.autoindent:
539 if self.autoindent:
540 self.rl_do_indent = False
540 self.rl_do_indent = False
541 if self.has_readline:
541 if self.has_readline:
542 self.readline_startup_hook(None)
542 self.readline_startup_hook(None)
543 self.write('\n')
543 self.write('\n')
544 self.exit()
544 self.exit()
545 except bdb.BdbQuit:
545 except bdb.BdbQuit:
546 warn('The Python debugger has exited with a BdbQuit exception.\n'
546 warn('The Python debugger has exited with a BdbQuit exception.\n'
547 'Because of how pdb handles the stack, it is impossible\n'
547 'Because of how pdb handles the stack, it is impossible\n'
548 'for IPython to properly format this particular exception.\n'
548 'for IPython to properly format this particular exception.\n'
549 'IPython will resume normal operation.')
549 'IPython will resume normal operation.')
550 except:
550 except:
551 # exceptions here are VERY RARE, but they can be triggered
551 # exceptions here are VERY RARE, but they can be triggered
552 # asynchronously by signal handlers, for example.
552 # asynchronously by signal handlers, for example.
553 self.showtraceback()
553 self.showtraceback()
554 else:
554 else:
555 try:
555 try:
556 self.input_splitter.push(line)
556 self.input_splitter.push(line)
557 more = self.input_splitter.push_accepts_more()
557 more = self.input_splitter.push_accepts_more()
558 except SyntaxError:
558 except SyntaxError:
559 # Run the code directly - run_cell takes care of displaying
559 # Run the code directly - run_cell takes care of displaying
560 # the exception.
560 # the exception.
561 more = False
561 more = False
562 if (self.SyntaxTB.last_syntax_error and
562 if (self.SyntaxTB.last_syntax_error and
563 self.autoedit_syntax):
563 self.autoedit_syntax):
564 self.edit_syntax_error()
564 self.edit_syntax_error()
565 if not more:
565 if not more:
566 source_raw = self.input_splitter.raw_reset()
566 source_raw = self.input_splitter.raw_reset()
567 hlen_b4_cell = self._replace_rlhist_multiline(source_raw, hlen_b4_cell)
567 hlen_b4_cell = self._replace_rlhist_multiline(source_raw, hlen_b4_cell)
568 self.run_cell(source_raw)
568 self.run_cell(source_raw)
569
569
570
570
571 # Turn off the exit flag, so the mainloop can be restarted if desired
571 # Turn off the exit flag, so the mainloop can be restarted if desired
572 self.exit_now = False
572 self.exit_now = False
573
573
574 def init_history(self):
574 def init_history(self):
575 """Sets up the command history. """
575 """Sets up the command history. """
576 self.history_manager = KernelHistoryManager(client=self.client)
576 self.history_manager = ZMQHistoryManager(client=self.client)
577 self.configurables.append(self.history_manager)
577 self.configurables.append(self.history_manager)
578
578
General Comments 0
You need to be logged in to leave comments. Login now