##// END OF EJS Templates
Merge pull request #2609 from tkf/history-unique...
Thomas Kluyver -
r9030:751e3470 merge
parent child Browse files
Show More
@@ -1,788 +1,794 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 os
18 import os
19 import re
19 import re
20 try:
20 try:
21 import sqlite3
21 import sqlite3
22 except ImportError:
22 except ImportError:
23 sqlite3 = None
23 sqlite3 = None
24 import threading
24 import threading
25
25
26 # Our own packages
26 # Our own packages
27 from IPython.config.configurable import Configurable
27 from IPython.config.configurable import Configurable
28 from IPython.external.decorator import decorator
28 from IPython.external.decorator import decorator
29 from IPython.utils.path import locate_profile
29 from IPython.utils.path import locate_profile
30 from IPython.utils.traitlets import (
30 from IPython.utils.traitlets import (
31 Any, Bool, Dict, Instance, Integer, List, Unicode, TraitError,
31 Any, Bool, Dict, Instance, Integer, List, Unicode, TraitError,
32 )
32 )
33 from IPython.utils.warn import warn
33 from IPython.utils.warn import warn
34
34
35 #-----------------------------------------------------------------------------
35 #-----------------------------------------------------------------------------
36 # Classes and functions
36 # Classes and functions
37 #-----------------------------------------------------------------------------
37 #-----------------------------------------------------------------------------
38
38
39 class DummyDB(object):
39 class DummyDB(object):
40 """Dummy DB that will act as a black hole for history.
40 """Dummy DB that will act as a black hole for history.
41
41
42 Only used in the absence of sqlite"""
42 Only used in the absence of sqlite"""
43 def execute(*args, **kwargs):
43 def execute(*args, **kwargs):
44 return []
44 return []
45
45
46 def commit(self, *args, **kwargs):
46 def commit(self, *args, **kwargs):
47 pass
47 pass
48
48
49 def __enter__(self, *args, **kwargs):
49 def __enter__(self, *args, **kwargs):
50 pass
50 pass
51
51
52 def __exit__(self, *args, **kwargs):
52 def __exit__(self, *args, **kwargs):
53 pass
53 pass
54
54
55
55
56 @decorator
56 @decorator
57 def needs_sqlite(f, self, *a, **kw):
57 def needs_sqlite(f, self, *a, **kw):
58 """return an empty list in the absence of sqlite"""
58 """return an empty list in the absence of sqlite"""
59 if sqlite3 is None or not self.enabled:
59 if sqlite3 is None or not self.enabled:
60 return []
60 return []
61 else:
61 else:
62 return f(self, *a, **kw)
62 return f(self, *a, **kw)
63
63
64
64
65 if sqlite3 is not None:
65 if sqlite3 is not None:
66 DatabaseError = sqlite3.DatabaseError
66 DatabaseError = sqlite3.DatabaseError
67 else:
67 else:
68 class DatabaseError(Exception):
68 class DatabaseError(Exception):
69 "Dummy exception when sqlite could not be imported. Should never occur."
69 "Dummy exception when sqlite could not be imported. Should never occur."
70
70
71 @decorator
71 @decorator
72 def catch_corrupt_db(f, self, *a, **kw):
72 def catch_corrupt_db(f, self, *a, **kw):
73 """A decorator which wraps HistoryAccessor method calls to catch errors from
73 """A decorator which wraps HistoryAccessor method calls to catch errors from
74 a corrupt SQLite database, move the old database out of the way, and create
74 a corrupt SQLite database, move the old database out of the way, and create
75 a new one.
75 a new one.
76 """
76 """
77 try:
77 try:
78 return f(self, *a, **kw)
78 return f(self, *a, **kw)
79 except DatabaseError:
79 except DatabaseError:
80 if os.path.isfile(self.hist_file):
80 if os.path.isfile(self.hist_file):
81 # Try to move the file out of the way
81 # Try to move the file out of the way
82 base,ext = os.path.splitext(self.hist_file)
82 base,ext = os.path.splitext(self.hist_file)
83 newpath = base + '-corrupt' + ext
83 newpath = base + '-corrupt' + ext
84 os.rename(self.hist_file, newpath)
84 os.rename(self.hist_file, newpath)
85 self.init_db()
85 self.init_db()
86 print("ERROR! History file wasn't a valid SQLite database.",
86 print("ERROR! History file wasn't a valid SQLite database.",
87 "It was moved to %s" % newpath, "and a new file created.")
87 "It was moved to %s" % newpath, "and a new file created.")
88 return []
88 return []
89
89
90 else:
90 else:
91 # The hist_file is probably :memory: or something else.
91 # The hist_file is probably :memory: or something else.
92 raise
92 raise
93
93
94
94
95
95
96 class HistoryAccessor(Configurable):
96 class HistoryAccessor(Configurable):
97 """Access the history database without adding to it.
97 """Access the history database without adding to it.
98
98
99 This is intended for use by standalone history tools. IPython shells use
99 This is intended for use by standalone history tools. IPython shells use
100 HistoryManager, below, which is a subclass of this."""
100 HistoryManager, below, which is a subclass of this."""
101
101
102 # String holding the path to the history file
102 # String holding the path to the history file
103 hist_file = Unicode(config=True,
103 hist_file = Unicode(config=True,
104 help="""Path to file to use for SQLite history database.
104 help="""Path to file to use for SQLite history database.
105
105
106 By default, IPython will put the history database in the IPython
106 By default, IPython will put the history database in the IPython
107 profile directory. If you would rather share one history among
107 profile directory. If you would rather share one history among
108 profiles, you can set this value in each, so that they are consistent.
108 profiles, you can set this value in each, so that they are consistent.
109
109
110 Due to an issue with fcntl, SQLite is known to misbehave on some NFS
110 Due to an issue with fcntl, SQLite is known to misbehave on some NFS
111 mounts. If you see IPython hanging, try setting this to something on a
111 mounts. If you see IPython hanging, try setting this to something on a
112 local disk, e.g::
112 local disk, e.g::
113
113
114 ipython --HistoryManager.hist_file=/tmp/ipython_hist.sqlite
114 ipython --HistoryManager.hist_file=/tmp/ipython_hist.sqlite
115
115
116 """)
116 """)
117
117
118 enabled = Bool(True, config=True,
118 enabled = Bool(True, config=True,
119 help="""enable the SQLite history
119 help="""enable the SQLite history
120
120
121 set enabled=False to disable the SQLite history,
121 set enabled=False to disable the SQLite history,
122 in which case there will be no stored history, no SQLite connection,
122 in which case there will be no stored history, no SQLite connection,
123 and no background saving thread. This may be necessary in some
123 and no background saving thread. This may be necessary in some
124 threaded environments where IPython is embedded.
124 threaded environments where IPython is embedded.
125 """
125 """
126 )
126 )
127
127
128 connection_options = Dict(config=True,
128 connection_options = Dict(config=True,
129 help="""Options for configuring the SQLite connection
129 help="""Options for configuring the SQLite connection
130
130
131 These options are passed as keyword args to sqlite3.connect
131 These options are passed as keyword args to sqlite3.connect
132 when establishing database conenctions.
132 when establishing database conenctions.
133 """
133 """
134 )
134 )
135
135
136 # The SQLite database
136 # The SQLite database
137 db = Any()
137 db = Any()
138 def _db_changed(self, name, old, new):
138 def _db_changed(self, name, old, new):
139 """validate the db, since it can be an Instance of two different types"""
139 """validate the db, since it can be an Instance of two different types"""
140 connection_types = (DummyDB,)
140 connection_types = (DummyDB,)
141 if sqlite3 is not None:
141 if sqlite3 is not None:
142 connection_types = (DummyDB, sqlite3.Connection)
142 connection_types = (DummyDB, sqlite3.Connection)
143 if not isinstance(new, connection_types):
143 if not isinstance(new, connection_types):
144 msg = "%s.db must be sqlite3 Connection or DummyDB, not %r" % \
144 msg = "%s.db must be sqlite3 Connection or DummyDB, not %r" % \
145 (self.__class__.__name__, new)
145 (self.__class__.__name__, new)
146 raise TraitError(msg)
146 raise TraitError(msg)
147
147
148 def __init__(self, profile='default', hist_file=u'', config=None, **traits):
148 def __init__(self, profile='default', hist_file=u'', config=None, **traits):
149 """Create a new history accessor.
149 """Create a new history accessor.
150
150
151 Parameters
151 Parameters
152 ----------
152 ----------
153 profile : str
153 profile : str
154 The name of the profile from which to open history.
154 The name of the profile from which to open history.
155 hist_file : str
155 hist_file : str
156 Path to an SQLite history database stored by IPython. If specified,
156 Path to an SQLite history database stored by IPython. If specified,
157 hist_file overrides profile.
157 hist_file overrides profile.
158 config :
158 config :
159 Config object. hist_file can also be set through this.
159 Config object. hist_file can also be set through this.
160 """
160 """
161 # We need a pointer back to the shell for various tasks.
161 # We need a pointer back to the shell for various tasks.
162 super(HistoryAccessor, self).__init__(config=config, **traits)
162 super(HistoryAccessor, self).__init__(config=config, **traits)
163 # defer setting hist_file from kwarg until after init,
163 # defer setting hist_file from kwarg until after init,
164 # otherwise the default kwarg value would clobber any value
164 # otherwise the default kwarg value would clobber any value
165 # set by config
165 # set by config
166 if hist_file:
166 if hist_file:
167 self.hist_file = hist_file
167 self.hist_file = hist_file
168
168
169 if self.hist_file == u'':
169 if self.hist_file == u'':
170 # No one has set the hist_file, yet.
170 # No one has set the hist_file, yet.
171 self.hist_file = self._get_hist_file_name(profile)
171 self.hist_file = self._get_hist_file_name(profile)
172
172
173 if sqlite3 is None and self.enabled:
173 if sqlite3 is None and self.enabled:
174 warn("IPython History requires SQLite, your history will not be saved")
174 warn("IPython History requires SQLite, your history will not be saved")
175 self.enabled = False
175 self.enabled = False
176
176
177 self.init_db()
177 self.init_db()
178
178
179 def _get_hist_file_name(self, profile='default'):
179 def _get_hist_file_name(self, profile='default'):
180 """Find the history file for the given profile name.
180 """Find the history file for the given profile name.
181
181
182 This is overridden by the HistoryManager subclass, to use the shell's
182 This is overridden by the HistoryManager subclass, to use the shell's
183 active profile.
183 active profile.
184
184
185 Parameters
185 Parameters
186 ----------
186 ----------
187 profile : str
187 profile : str
188 The name of a profile which has a history file.
188 The name of a profile which has a history file.
189 """
189 """
190 return os.path.join(locate_profile(profile), 'history.sqlite')
190 return os.path.join(locate_profile(profile), 'history.sqlite')
191
191
192 @catch_corrupt_db
192 @catch_corrupt_db
193 def init_db(self):
193 def init_db(self):
194 """Connect to the database, and create tables if necessary."""
194 """Connect to the database, and create tables if necessary."""
195 if not self.enabled:
195 if not self.enabled:
196 self.db = DummyDB()
196 self.db = DummyDB()
197 return
197 return
198
198
199 # use detect_types so that timestamps return datetime objects
199 # use detect_types so that timestamps return datetime objects
200 kwargs = dict(detect_types=sqlite3.PARSE_DECLTYPES|sqlite3.PARSE_COLNAMES)
200 kwargs = dict(detect_types=sqlite3.PARSE_DECLTYPES|sqlite3.PARSE_COLNAMES)
201 kwargs.update(self.connection_options)
201 kwargs.update(self.connection_options)
202 self.db = sqlite3.connect(self.hist_file, **kwargs)
202 self.db = sqlite3.connect(self.hist_file, **kwargs)
203 self.db.execute("""CREATE TABLE IF NOT EXISTS sessions (session integer
203 self.db.execute("""CREATE TABLE IF NOT EXISTS sessions (session integer
204 primary key autoincrement, start timestamp,
204 primary key autoincrement, start timestamp,
205 end timestamp, num_cmds integer, remark text)""")
205 end timestamp, num_cmds integer, remark text)""")
206 self.db.execute("""CREATE TABLE IF NOT EXISTS history
206 self.db.execute("""CREATE TABLE IF NOT EXISTS history
207 (session integer, line integer, source text, source_raw text,
207 (session integer, line integer, source text, source_raw text,
208 PRIMARY KEY (session, line))""")
208 PRIMARY KEY (session, line))""")
209 # Output history is optional, but ensure the table's there so it can be
209 # Output history is optional, but ensure the table's there so it can be
210 # enabled later.
210 # enabled later.
211 self.db.execute("""CREATE TABLE IF NOT EXISTS output_history
211 self.db.execute("""CREATE TABLE IF NOT EXISTS output_history
212 (session integer, line integer, output text,
212 (session integer, line integer, output text,
213 PRIMARY KEY (session, line))""")
213 PRIMARY KEY (session, line))""")
214 self.db.commit()
214 self.db.commit()
215
215
216 def writeout_cache(self):
216 def writeout_cache(self):
217 """Overridden by HistoryManager to dump the cache before certain
217 """Overridden by HistoryManager to dump the cache before certain
218 database lookups."""
218 database lookups."""
219 pass
219 pass
220
220
221 ## -------------------------------
221 ## -------------------------------
222 ## Methods for retrieving history:
222 ## Methods for retrieving history:
223 ## -------------------------------
223 ## -------------------------------
224 def _run_sql(self, sql, params, raw=True, output=False):
224 def _run_sql(self, sql, params, raw=True, output=False):
225 """Prepares and runs an SQL query for the history database.
225 """Prepares and runs an SQL query for the history database.
226
226
227 Parameters
227 Parameters
228 ----------
228 ----------
229 sql : str
229 sql : str
230 Any filtering expressions to go after SELECT ... FROM ...
230 Any filtering expressions to go after SELECT ... FROM ...
231 params : tuple
231 params : tuple
232 Parameters passed to the SQL query (to replace "?")
232 Parameters passed to the SQL query (to replace "?")
233 raw, output : bool
233 raw, output : bool
234 See :meth:`get_range`
234 See :meth:`get_range`
235
235
236 Returns
236 Returns
237 -------
237 -------
238 Tuples as :meth:`get_range`
238 Tuples as :meth:`get_range`
239 """
239 """
240 toget = 'source_raw' if raw else 'source'
240 toget = 'source_raw' if raw else 'source'
241 sqlfrom = "history"
241 sqlfrom = "history"
242 if output:
242 if output:
243 sqlfrom = "history LEFT JOIN output_history USING (session, line)"
243 sqlfrom = "history LEFT JOIN output_history USING (session, line)"
244 toget = "history.%s, output_history.output" % toget
244 toget = "history.%s, output_history.output" % toget
245 cur = self.db.execute("SELECT session, line, %s FROM %s " %\
245 cur = self.db.execute("SELECT session, line, %s FROM %s " %\
246 (toget, sqlfrom) + sql, params)
246 (toget, sqlfrom) + sql, params)
247 if output: # Regroup into 3-tuples, and parse JSON
247 if output: # Regroup into 3-tuples, and parse JSON
248 return ((ses, lin, (inp, out)) for ses, lin, inp, out in cur)
248 return ((ses, lin, (inp, out)) for ses, lin, inp, out in cur)
249 return cur
249 return cur
250
250
251 @needs_sqlite
251 @needs_sqlite
252 @catch_corrupt_db
252 @catch_corrupt_db
253 def get_session_info(self, session=0):
253 def get_session_info(self, session=0):
254 """get info about a session
254 """get info about a session
255
255
256 Parameters
256 Parameters
257 ----------
257 ----------
258
258
259 session : int
259 session : int
260 Session number to retrieve. The current session is 0, and negative
260 Session number to retrieve. The current session is 0, and negative
261 numbers count back from current session, so -1 is previous session.
261 numbers count back from current session, so -1 is previous session.
262
262
263 Returns
263 Returns
264 -------
264 -------
265
265
266 (session_id [int], start [datetime], end [datetime], num_cmds [int],
266 (session_id [int], start [datetime], end [datetime], num_cmds [int],
267 remark [unicode])
267 remark [unicode])
268
268
269 Sessions that are running or did not exit cleanly will have `end=None`
269 Sessions that are running or did not exit cleanly will have `end=None`
270 and `num_cmds=None`.
270 and `num_cmds=None`.
271
271
272 """
272 """
273
273
274 if session <= 0:
274 if session <= 0:
275 session += self.session_number
275 session += self.session_number
276
276
277 query = "SELECT * from sessions where session == ?"
277 query = "SELECT * from sessions where session == ?"
278 return self.db.execute(query, (session,)).fetchone()
278 return self.db.execute(query, (session,)).fetchone()
279
279
280 @catch_corrupt_db
280 @catch_corrupt_db
281 def get_tail(self, n=10, raw=True, output=False, include_latest=False):
281 def get_tail(self, n=10, raw=True, output=False, include_latest=False):
282 """Get the last n lines from the history database.
282 """Get the last n lines from the history database.
283
283
284 Parameters
284 Parameters
285 ----------
285 ----------
286 n : int
286 n : int
287 The number of lines to get
287 The number of lines to get
288 raw, output : bool
288 raw, output : bool
289 See :meth:`get_range`
289 See :meth:`get_range`
290 include_latest : bool
290 include_latest : bool
291 If False (default), n+1 lines are fetched, and the latest one
291 If False (default), n+1 lines are fetched, and the latest one
292 is discarded. This is intended to be used where the function
292 is discarded. This is intended to be used where the function
293 is called by a user command, which it should not return.
293 is called by a user command, which it should not return.
294
294
295 Returns
295 Returns
296 -------
296 -------
297 Tuples as :meth:`get_range`
297 Tuples as :meth:`get_range`
298 """
298 """
299 self.writeout_cache()
299 self.writeout_cache()
300 if not include_latest:
300 if not include_latest:
301 n += 1
301 n += 1
302 cur = self._run_sql("ORDER BY session DESC, line DESC LIMIT ?",
302 cur = self._run_sql("ORDER BY session DESC, line DESC LIMIT ?",
303 (n,), raw=raw, output=output)
303 (n,), raw=raw, output=output)
304 if not include_latest:
304 if not include_latest:
305 return reversed(list(cur)[1:])
305 return reversed(list(cur)[1:])
306 return reversed(list(cur))
306 return reversed(list(cur))
307
307
308 @catch_corrupt_db
308 @catch_corrupt_db
309 def search(self, pattern="*", raw=True, search_raw=True,
309 def search(self, pattern="*", raw=True, search_raw=True,
310 output=False, n=None):
310 output=False, n=None, unique=False):
311 """Search the database using unix glob-style matching (wildcards
311 """Search the database using unix glob-style matching (wildcards
312 * and ?).
312 * and ?).
313
313
314 Parameters
314 Parameters
315 ----------
315 ----------
316 pattern : str
316 pattern : str
317 The wildcarded pattern to match when searching
317 The wildcarded pattern to match when searching
318 search_raw : bool
318 search_raw : bool
319 If True, search the raw input, otherwise, the parsed input
319 If True, search the raw input, otherwise, the parsed input
320 raw, output : bool
320 raw, output : bool
321 See :meth:`get_range`
321 See :meth:`get_range`
322 n : None or int
322 n : None or int
323 If an integer is given, it defines the limit of
323 If an integer is given, it defines the limit of
324 returned entries.
324 returned entries.
325 unique : bool
326 When it is true, return only unique entries.
325
327
326 Returns
328 Returns
327 -------
329 -------
328 Tuples as :meth:`get_range`
330 Tuples as :meth:`get_range`
329 """
331 """
330 tosearch = "source_raw" if search_raw else "source"
332 tosearch = "source_raw" if search_raw else "source"
331 if output:
333 if output:
332 tosearch = "history." + tosearch
334 tosearch = "history." + tosearch
333 self.writeout_cache()
335 self.writeout_cache()
334 sqlform = "WHERE %s GLOB ?" % tosearch
336 sqlform = "WHERE %s GLOB ?" % tosearch
335 params = (pattern,)
337 params = (pattern,)
338 if unique:
339 sqlform += ' GROUP BY {0}'.format(tosearch)
336 if n is not None:
340 if n is not None:
337 sqlform += " ORDER BY session DESC, line DESC LIMIT ?"
341 sqlform += " ORDER BY session DESC, line DESC LIMIT ?"
338 params += (n,)
342 params += (n,)
343 elif unique:
344 sqlform += " ORDER BY session, line"
339 cur = self._run_sql(sqlform, params, raw=raw, output=output)
345 cur = self._run_sql(sqlform, params, raw=raw, output=output)
340 if n is not None:
346 if n is not None:
341 return reversed(list(cur))
347 return reversed(list(cur))
342 return cur
348 return cur
343
349
344 @catch_corrupt_db
350 @catch_corrupt_db
345 def get_range(self, session, start=1, stop=None, raw=True,output=False):
351 def get_range(self, session, start=1, stop=None, raw=True,output=False):
346 """Retrieve input by session.
352 """Retrieve input by session.
347
353
348 Parameters
354 Parameters
349 ----------
355 ----------
350 session : int
356 session : int
351 Session number to retrieve.
357 Session number to retrieve.
352 start : int
358 start : int
353 First line to retrieve.
359 First line to retrieve.
354 stop : int
360 stop : int
355 End of line range (excluded from output itself). If None, retrieve
361 End of line range (excluded from output itself). If None, retrieve
356 to the end of the session.
362 to the end of the session.
357 raw : bool
363 raw : bool
358 If True, return untranslated input
364 If True, return untranslated input
359 output : bool
365 output : bool
360 If True, attempt to include output. This will be 'real' Python
366 If True, attempt to include output. This will be 'real' Python
361 objects for the current session, or text reprs from previous
367 objects for the current session, or text reprs from previous
362 sessions if db_log_output was enabled at the time. Where no output
368 sessions if db_log_output was enabled at the time. Where no output
363 is found, None is used.
369 is found, None is used.
364
370
365 Returns
371 Returns
366 -------
372 -------
367 An iterator over the desired lines. Each line is a 3-tuple, either
373 An iterator over the desired lines. Each line is a 3-tuple, either
368 (session, line, input) if output is False, or
374 (session, line, input) if output is False, or
369 (session, line, (input, output)) if output is True.
375 (session, line, (input, output)) if output is True.
370 """
376 """
371 if stop:
377 if stop:
372 lineclause = "line >= ? AND line < ?"
378 lineclause = "line >= ? AND line < ?"
373 params = (session, start, stop)
379 params = (session, start, stop)
374 else:
380 else:
375 lineclause = "line>=?"
381 lineclause = "line>=?"
376 params = (session, start)
382 params = (session, start)
377
383
378 return self._run_sql("WHERE session==? AND %s" % lineclause,
384 return self._run_sql("WHERE session==? AND %s" % lineclause,
379 params, raw=raw, output=output)
385 params, raw=raw, output=output)
380
386
381 def get_range_by_str(self, rangestr, raw=True, output=False):
387 def get_range_by_str(self, rangestr, raw=True, output=False):
382 """Get lines of history from a string of ranges, as used by magic
388 """Get lines of history from a string of ranges, as used by magic
383 commands %hist, %save, %macro, etc.
389 commands %hist, %save, %macro, etc.
384
390
385 Parameters
391 Parameters
386 ----------
392 ----------
387 rangestr : str
393 rangestr : str
388 A string specifying ranges, e.g. "5 ~2/1-4". See
394 A string specifying ranges, e.g. "5 ~2/1-4". See
389 :func:`magic_history` for full details.
395 :func:`magic_history` for full details.
390 raw, output : bool
396 raw, output : bool
391 As :meth:`get_range`
397 As :meth:`get_range`
392
398
393 Returns
399 Returns
394 -------
400 -------
395 Tuples as :meth:`get_range`
401 Tuples as :meth:`get_range`
396 """
402 """
397 for sess, s, e in extract_hist_ranges(rangestr):
403 for sess, s, e in extract_hist_ranges(rangestr):
398 for line in self.get_range(sess, s, e, raw=raw, output=output):
404 for line in self.get_range(sess, s, e, raw=raw, output=output):
399 yield line
405 yield line
400
406
401
407
402 class HistoryManager(HistoryAccessor):
408 class HistoryManager(HistoryAccessor):
403 """A class to organize all history-related functionality in one place.
409 """A class to organize all history-related functionality in one place.
404 """
410 """
405 # Public interface
411 # Public interface
406
412
407 # An instance of the IPython shell we are attached to
413 # An instance of the IPython shell we are attached to
408 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC')
414 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC')
409 # Lists to hold processed and raw history. These start with a blank entry
415 # Lists to hold processed and raw history. These start with a blank entry
410 # so that we can index them starting from 1
416 # so that we can index them starting from 1
411 input_hist_parsed = List([""])
417 input_hist_parsed = List([""])
412 input_hist_raw = List([""])
418 input_hist_raw = List([""])
413 # A list of directories visited during session
419 # A list of directories visited during session
414 dir_hist = List()
420 dir_hist = List()
415 def _dir_hist_default(self):
421 def _dir_hist_default(self):
416 try:
422 try:
417 return [os.getcwdu()]
423 return [os.getcwdu()]
418 except OSError:
424 except OSError:
419 return []
425 return []
420
426
421 # A dict of output history, keyed with ints from the shell's
427 # A dict of output history, keyed with ints from the shell's
422 # execution count.
428 # execution count.
423 output_hist = Dict()
429 output_hist = Dict()
424 # The text/plain repr of outputs.
430 # The text/plain repr of outputs.
425 output_hist_reprs = Dict()
431 output_hist_reprs = Dict()
426
432
427 # The number of the current session in the history database
433 # The number of the current session in the history database
428 session_number = Integer()
434 session_number = Integer()
429 # Should we log output to the database? (default no)
435 # Should we log output to the database? (default no)
430 db_log_output = Bool(False, config=True)
436 db_log_output = Bool(False, config=True)
431 # Write to database every x commands (higher values save disk access & power)
437 # Write to database every x commands (higher values save disk access & power)
432 # Values of 1 or less effectively disable caching.
438 # Values of 1 or less effectively disable caching.
433 db_cache_size = Integer(0, config=True)
439 db_cache_size = Integer(0, config=True)
434 # The input and output caches
440 # The input and output caches
435 db_input_cache = List()
441 db_input_cache = List()
436 db_output_cache = List()
442 db_output_cache = List()
437
443
438 # History saving in separate thread
444 # History saving in separate thread
439 save_thread = Instance('IPython.core.history.HistorySavingThread')
445 save_thread = Instance('IPython.core.history.HistorySavingThread')
440 try: # Event is a function returning an instance of _Event...
446 try: # Event is a function returning an instance of _Event...
441 save_flag = Instance(threading._Event)
447 save_flag = Instance(threading._Event)
442 except AttributeError: # ...until Python 3.3, when it's a class.
448 except AttributeError: # ...until Python 3.3, when it's a class.
443 save_flag = Instance(threading.Event)
449 save_flag = Instance(threading.Event)
444
450
445 # Private interface
451 # Private interface
446 # Variables used to store the three last inputs from the user. On each new
452 # Variables used to store the three last inputs from the user. On each new
447 # history update, we populate the user's namespace with these, shifted as
453 # history update, we populate the user's namespace with these, shifted as
448 # necessary.
454 # necessary.
449 _i00 = Unicode(u'')
455 _i00 = Unicode(u'')
450 _i = Unicode(u'')
456 _i = Unicode(u'')
451 _ii = Unicode(u'')
457 _ii = Unicode(u'')
452 _iii = Unicode(u'')
458 _iii = Unicode(u'')
453
459
454 # A regex matching all forms of the exit command, so that we don't store
460 # A regex matching all forms of the exit command, so that we don't store
455 # them in the history (it's annoying to rewind the first entry and land on
461 # them in the history (it's annoying to rewind the first entry and land on
456 # an exit call).
462 # an exit call).
457 _exit_re = re.compile(r"(exit|quit)(\s*\(.*\))?$")
463 _exit_re = re.compile(r"(exit|quit)(\s*\(.*\))?$")
458
464
459 def __init__(self, shell=None, config=None, **traits):
465 def __init__(self, shell=None, config=None, **traits):
460 """Create a new history manager associated with a shell instance.
466 """Create a new history manager associated with a shell instance.
461 """
467 """
462 # We need a pointer back to the shell for various tasks.
468 # We need a pointer back to the shell for various tasks.
463 super(HistoryManager, self).__init__(shell=shell, config=config,
469 super(HistoryManager, self).__init__(shell=shell, config=config,
464 **traits)
470 **traits)
465 self.save_flag = threading.Event()
471 self.save_flag = threading.Event()
466 self.db_input_cache_lock = threading.Lock()
472 self.db_input_cache_lock = threading.Lock()
467 self.db_output_cache_lock = threading.Lock()
473 self.db_output_cache_lock = threading.Lock()
468 if self.enabled and self.hist_file != ':memory:':
474 if self.enabled and self.hist_file != ':memory:':
469 self.save_thread = HistorySavingThread(self)
475 self.save_thread = HistorySavingThread(self)
470 self.save_thread.start()
476 self.save_thread.start()
471
477
472 self.new_session()
478 self.new_session()
473
479
474 def _get_hist_file_name(self, profile=None):
480 def _get_hist_file_name(self, profile=None):
475 """Get default history file name based on the Shell's profile.
481 """Get default history file name based on the Shell's profile.
476
482
477 The profile parameter is ignored, but must exist for compatibility with
483 The profile parameter is ignored, but must exist for compatibility with
478 the parent class."""
484 the parent class."""
479 profile_dir = self.shell.profile_dir.location
485 profile_dir = self.shell.profile_dir.location
480 return os.path.join(profile_dir, 'history.sqlite')
486 return os.path.join(profile_dir, 'history.sqlite')
481
487
482 @needs_sqlite
488 @needs_sqlite
483 def new_session(self, conn=None):
489 def new_session(self, conn=None):
484 """Get a new session number."""
490 """Get a new session number."""
485 if conn is None:
491 if conn is None:
486 conn = self.db
492 conn = self.db
487
493
488 with conn:
494 with conn:
489 cur = conn.execute("""INSERT INTO sessions VALUES (NULL, ?, NULL,
495 cur = conn.execute("""INSERT INTO sessions VALUES (NULL, ?, NULL,
490 NULL, "") """, (datetime.datetime.now(),))
496 NULL, "") """, (datetime.datetime.now(),))
491 self.session_number = cur.lastrowid
497 self.session_number = cur.lastrowid
492
498
493 def end_session(self):
499 def end_session(self):
494 """Close the database session, filling in the end time and line count."""
500 """Close the database session, filling in the end time and line count."""
495 self.writeout_cache()
501 self.writeout_cache()
496 with self.db:
502 with self.db:
497 self.db.execute("""UPDATE sessions SET end=?, num_cmds=? WHERE
503 self.db.execute("""UPDATE sessions SET end=?, num_cmds=? WHERE
498 session==?""", (datetime.datetime.now(),
504 session==?""", (datetime.datetime.now(),
499 len(self.input_hist_parsed)-1, self.session_number))
505 len(self.input_hist_parsed)-1, self.session_number))
500 self.session_number = 0
506 self.session_number = 0
501
507
502 def name_session(self, name):
508 def name_session(self, name):
503 """Give the current session a name in the history database."""
509 """Give the current session a name in the history database."""
504 with self.db:
510 with self.db:
505 self.db.execute("UPDATE sessions SET remark=? WHERE session==?",
511 self.db.execute("UPDATE sessions SET remark=? WHERE session==?",
506 (name, self.session_number))
512 (name, self.session_number))
507
513
508 def reset(self, new_session=True):
514 def reset(self, new_session=True):
509 """Clear the session history, releasing all object references, and
515 """Clear the session history, releasing all object references, and
510 optionally open a new session."""
516 optionally open a new session."""
511 self.output_hist.clear()
517 self.output_hist.clear()
512 # The directory history can't be completely empty
518 # The directory history can't be completely empty
513 self.dir_hist[:] = [os.getcwdu()]
519 self.dir_hist[:] = [os.getcwdu()]
514
520
515 if new_session:
521 if new_session:
516 if self.session_number:
522 if self.session_number:
517 self.end_session()
523 self.end_session()
518 self.input_hist_parsed[:] = [""]
524 self.input_hist_parsed[:] = [""]
519 self.input_hist_raw[:] = [""]
525 self.input_hist_raw[:] = [""]
520 self.new_session()
526 self.new_session()
521
527
522 # ------------------------------
528 # ------------------------------
523 # Methods for retrieving history
529 # Methods for retrieving history
524 # ------------------------------
530 # ------------------------------
525 def _get_range_session(self, start=1, stop=None, raw=True, output=False):
531 def _get_range_session(self, start=1, stop=None, raw=True, output=False):
526 """Get input and output history from the current session. Called by
532 """Get input and output history from the current session. Called by
527 get_range, and takes similar parameters."""
533 get_range, and takes similar parameters."""
528 input_hist = self.input_hist_raw if raw else self.input_hist_parsed
534 input_hist = self.input_hist_raw if raw else self.input_hist_parsed
529
535
530 n = len(input_hist)
536 n = len(input_hist)
531 if start < 0:
537 if start < 0:
532 start += n
538 start += n
533 if not stop or (stop > n):
539 if not stop or (stop > n):
534 stop = n
540 stop = n
535 elif stop < 0:
541 elif stop < 0:
536 stop += n
542 stop += n
537
543
538 for i in range(start, stop):
544 for i in range(start, stop):
539 if output:
545 if output:
540 line = (input_hist[i], self.output_hist_reprs.get(i))
546 line = (input_hist[i], self.output_hist_reprs.get(i))
541 else:
547 else:
542 line = input_hist[i]
548 line = input_hist[i]
543 yield (0, i, line)
549 yield (0, i, line)
544
550
545 def get_range(self, session=0, start=1, stop=None, raw=True,output=False):
551 def get_range(self, session=0, start=1, stop=None, raw=True,output=False):
546 """Retrieve input by session.
552 """Retrieve input by session.
547
553
548 Parameters
554 Parameters
549 ----------
555 ----------
550 session : int
556 session : int
551 Session number to retrieve. The current session is 0, and negative
557 Session number to retrieve. The current session is 0, and negative
552 numbers count back from current session, so -1 is previous session.
558 numbers count back from current session, so -1 is previous session.
553 start : int
559 start : int
554 First line to retrieve.
560 First line to retrieve.
555 stop : int
561 stop : int
556 End of line range (excluded from output itself). If None, retrieve
562 End of line range (excluded from output itself). If None, retrieve
557 to the end of the session.
563 to the end of the session.
558 raw : bool
564 raw : bool
559 If True, return untranslated input
565 If True, return untranslated input
560 output : bool
566 output : bool
561 If True, attempt to include output. This will be 'real' Python
567 If True, attempt to include output. This will be 'real' Python
562 objects for the current session, or text reprs from previous
568 objects for the current session, or text reprs from previous
563 sessions if db_log_output was enabled at the time. Where no output
569 sessions if db_log_output was enabled at the time. Where no output
564 is found, None is used.
570 is found, None is used.
565
571
566 Returns
572 Returns
567 -------
573 -------
568 An iterator over the desired lines. Each line is a 3-tuple, either
574 An iterator over the desired lines. Each line is a 3-tuple, either
569 (session, line, input) if output is False, or
575 (session, line, input) if output is False, or
570 (session, line, (input, output)) if output is True.
576 (session, line, (input, output)) if output is True.
571 """
577 """
572 if session <= 0:
578 if session <= 0:
573 session += self.session_number
579 session += self.session_number
574 if session==self.session_number: # Current session
580 if session==self.session_number: # Current session
575 return self._get_range_session(start, stop, raw, output)
581 return self._get_range_session(start, stop, raw, output)
576 return super(HistoryManager, self).get_range(session, start, stop, raw,
582 return super(HistoryManager, self).get_range(session, start, stop, raw,
577 output)
583 output)
578
584
579 ## ----------------------------
585 ## ----------------------------
580 ## Methods for storing history:
586 ## Methods for storing history:
581 ## ----------------------------
587 ## ----------------------------
582 def store_inputs(self, line_num, source, source_raw=None):
588 def store_inputs(self, line_num, source, source_raw=None):
583 """Store source and raw input in history and create input cache
589 """Store source and raw input in history and create input cache
584 variables _i*.
590 variables _i*.
585
591
586 Parameters
592 Parameters
587 ----------
593 ----------
588 line_num : int
594 line_num : int
589 The prompt number of this input.
595 The prompt number of this input.
590
596
591 source : str
597 source : str
592 Python input.
598 Python input.
593
599
594 source_raw : str, optional
600 source_raw : str, optional
595 If given, this is the raw input without any IPython transformations
601 If given, this is the raw input without any IPython transformations
596 applied to it. If not given, ``source`` is used.
602 applied to it. If not given, ``source`` is used.
597 """
603 """
598 if source_raw is None:
604 if source_raw is None:
599 source_raw = source
605 source_raw = source
600 source = source.rstrip('\n')
606 source = source.rstrip('\n')
601 source_raw = source_raw.rstrip('\n')
607 source_raw = source_raw.rstrip('\n')
602
608
603 # do not store exit/quit commands
609 # do not store exit/quit commands
604 if self._exit_re.match(source_raw.strip()):
610 if self._exit_re.match(source_raw.strip()):
605 return
611 return
606
612
607 self.input_hist_parsed.append(source)
613 self.input_hist_parsed.append(source)
608 self.input_hist_raw.append(source_raw)
614 self.input_hist_raw.append(source_raw)
609
615
610 with self.db_input_cache_lock:
616 with self.db_input_cache_lock:
611 self.db_input_cache.append((line_num, source, source_raw))
617 self.db_input_cache.append((line_num, source, source_raw))
612 # Trigger to flush cache and write to DB.
618 # Trigger to flush cache and write to DB.
613 if len(self.db_input_cache) >= self.db_cache_size:
619 if len(self.db_input_cache) >= self.db_cache_size:
614 self.save_flag.set()
620 self.save_flag.set()
615
621
616 # update the auto _i variables
622 # update the auto _i variables
617 self._iii = self._ii
623 self._iii = self._ii
618 self._ii = self._i
624 self._ii = self._i
619 self._i = self._i00
625 self._i = self._i00
620 self._i00 = source_raw
626 self._i00 = source_raw
621
627
622 # hackish access to user namespace to create _i1,_i2... dynamically
628 # hackish access to user namespace to create _i1,_i2... dynamically
623 new_i = '_i%s' % line_num
629 new_i = '_i%s' % line_num
624 to_main = {'_i': self._i,
630 to_main = {'_i': self._i,
625 '_ii': self._ii,
631 '_ii': self._ii,
626 '_iii': self._iii,
632 '_iii': self._iii,
627 new_i : self._i00 }
633 new_i : self._i00 }
628
634
629 self.shell.push(to_main, interactive=False)
635 self.shell.push(to_main, interactive=False)
630
636
631 def store_output(self, line_num):
637 def store_output(self, line_num):
632 """If database output logging is enabled, this saves all the
638 """If database output logging is enabled, this saves all the
633 outputs from the indicated prompt number to the database. It's
639 outputs from the indicated prompt number to the database. It's
634 called by run_cell after code has been executed.
640 called by run_cell after code has been executed.
635
641
636 Parameters
642 Parameters
637 ----------
643 ----------
638 line_num : int
644 line_num : int
639 The line number from which to save outputs
645 The line number from which to save outputs
640 """
646 """
641 if (not self.db_log_output) or (line_num not in self.output_hist_reprs):
647 if (not self.db_log_output) or (line_num not in self.output_hist_reprs):
642 return
648 return
643 output = self.output_hist_reprs[line_num]
649 output = self.output_hist_reprs[line_num]
644
650
645 with self.db_output_cache_lock:
651 with self.db_output_cache_lock:
646 self.db_output_cache.append((line_num, output))
652 self.db_output_cache.append((line_num, output))
647 if self.db_cache_size <= 1:
653 if self.db_cache_size <= 1:
648 self.save_flag.set()
654 self.save_flag.set()
649
655
650 def _writeout_input_cache(self, conn):
656 def _writeout_input_cache(self, conn):
651 with conn:
657 with conn:
652 for line in self.db_input_cache:
658 for line in self.db_input_cache:
653 conn.execute("INSERT INTO history VALUES (?, ?, ?, ?)",
659 conn.execute("INSERT INTO history VALUES (?, ?, ?, ?)",
654 (self.session_number,)+line)
660 (self.session_number,)+line)
655
661
656 def _writeout_output_cache(self, conn):
662 def _writeout_output_cache(self, conn):
657 with conn:
663 with conn:
658 for line in self.db_output_cache:
664 for line in self.db_output_cache:
659 conn.execute("INSERT INTO output_history VALUES (?, ?, ?)",
665 conn.execute("INSERT INTO output_history VALUES (?, ?, ?)",
660 (self.session_number,)+line)
666 (self.session_number,)+line)
661
667
662 @needs_sqlite
668 @needs_sqlite
663 def writeout_cache(self, conn=None):
669 def writeout_cache(self, conn=None):
664 """Write any entries in the cache to the database."""
670 """Write any entries in the cache to the database."""
665 if conn is None:
671 if conn is None:
666 conn = self.db
672 conn = self.db
667
673
668 with self.db_input_cache_lock:
674 with self.db_input_cache_lock:
669 try:
675 try:
670 self._writeout_input_cache(conn)
676 self._writeout_input_cache(conn)
671 except sqlite3.IntegrityError:
677 except sqlite3.IntegrityError:
672 self.new_session(conn)
678 self.new_session(conn)
673 print("ERROR! Session/line number was not unique in",
679 print("ERROR! Session/line number was not unique in",
674 "database. History logging moved to new session",
680 "database. History logging moved to new session",
675 self.session_number)
681 self.session_number)
676 try:
682 try:
677 # Try writing to the new session. If this fails, don't
683 # Try writing to the new session. If this fails, don't
678 # recurse
684 # recurse
679 self._writeout_input_cache(conn)
685 self._writeout_input_cache(conn)
680 except sqlite3.IntegrityError:
686 except sqlite3.IntegrityError:
681 pass
687 pass
682 finally:
688 finally:
683 self.db_input_cache = []
689 self.db_input_cache = []
684
690
685 with self.db_output_cache_lock:
691 with self.db_output_cache_lock:
686 try:
692 try:
687 self._writeout_output_cache(conn)
693 self._writeout_output_cache(conn)
688 except sqlite3.IntegrityError:
694 except sqlite3.IntegrityError:
689 print("!! Session/line number for output was not unique",
695 print("!! Session/line number for output was not unique",
690 "in database. Output will not be stored.")
696 "in database. Output will not be stored.")
691 finally:
697 finally:
692 self.db_output_cache = []
698 self.db_output_cache = []
693
699
694
700
695 class HistorySavingThread(threading.Thread):
701 class HistorySavingThread(threading.Thread):
696 """This thread takes care of writing history to the database, so that
702 """This thread takes care of writing history to the database, so that
697 the UI isn't held up while that happens.
703 the UI isn't held up while that happens.
698
704
699 It waits for the HistoryManager's save_flag to be set, then writes out
705 It waits for the HistoryManager's save_flag to be set, then writes out
700 the history cache. The main thread is responsible for setting the flag when
706 the history cache. The main thread is responsible for setting the flag when
701 the cache size reaches a defined threshold."""
707 the cache size reaches a defined threshold."""
702 daemon = True
708 daemon = True
703 stop_now = False
709 stop_now = False
704 enabled = True
710 enabled = True
705 def __init__(self, history_manager):
711 def __init__(self, history_manager):
706 super(HistorySavingThread, self).__init__()
712 super(HistorySavingThread, self).__init__()
707 self.history_manager = history_manager
713 self.history_manager = history_manager
708 self.enabled = history_manager.enabled
714 self.enabled = history_manager.enabled
709 atexit.register(self.stop)
715 atexit.register(self.stop)
710
716
711 @needs_sqlite
717 @needs_sqlite
712 def run(self):
718 def run(self):
713 # We need a separate db connection per thread:
719 # We need a separate db connection per thread:
714 try:
720 try:
715 self.db = sqlite3.connect(self.history_manager.hist_file,
721 self.db = sqlite3.connect(self.history_manager.hist_file,
716 **self.history_manager.connection_options
722 **self.history_manager.connection_options
717 )
723 )
718 while True:
724 while True:
719 self.history_manager.save_flag.wait()
725 self.history_manager.save_flag.wait()
720 if self.stop_now:
726 if self.stop_now:
721 return
727 return
722 self.history_manager.save_flag.clear()
728 self.history_manager.save_flag.clear()
723 self.history_manager.writeout_cache(self.db)
729 self.history_manager.writeout_cache(self.db)
724 except Exception as e:
730 except Exception as e:
725 print(("The history saving thread hit an unexpected error (%s)."
731 print(("The history saving thread hit an unexpected error (%s)."
726 "History will not be written to the database.") % repr(e))
732 "History will not be written to the database.") % repr(e))
727
733
728 def stop(self):
734 def stop(self):
729 """This can be called from the main thread to safely stop this thread.
735 """This can be called from the main thread to safely stop this thread.
730
736
731 Note that it does not attempt to write out remaining history before
737 Note that it does not attempt to write out remaining history before
732 exiting. That should be done by calling the HistoryManager's
738 exiting. That should be done by calling the HistoryManager's
733 end_session method."""
739 end_session method."""
734 self.stop_now = True
740 self.stop_now = True
735 self.history_manager.save_flag.set()
741 self.history_manager.save_flag.set()
736 self.join()
742 self.join()
737
743
738
744
739 # To match, e.g. ~5/8-~2/3
745 # To match, e.g. ~5/8-~2/3
740 range_re = re.compile(r"""
746 range_re = re.compile(r"""
741 ((?P<startsess>~?\d+)/)?
747 ((?P<startsess>~?\d+)/)?
742 (?P<start>\d+) # Only the start line num is compulsory
748 (?P<start>\d+) # Only the start line num is compulsory
743 ((?P<sep>[\-:])
749 ((?P<sep>[\-:])
744 ((?P<endsess>~?\d+)/)?
750 ((?P<endsess>~?\d+)/)?
745 (?P<end>\d+))?
751 (?P<end>\d+))?
746 $""", re.VERBOSE)
752 $""", re.VERBOSE)
747
753
748
754
749 def extract_hist_ranges(ranges_str):
755 def extract_hist_ranges(ranges_str):
750 """Turn a string of history ranges into 3-tuples of (session, start, stop).
756 """Turn a string of history ranges into 3-tuples of (session, start, stop).
751
757
752 Examples
758 Examples
753 --------
759 --------
754 list(extract_input_ranges("~8/5-~7/4 2"))
760 list(extract_input_ranges("~8/5-~7/4 2"))
755 [(-8, 5, None), (-7, 1, 4), (0, 2, 3)]
761 [(-8, 5, None), (-7, 1, 4), (0, 2, 3)]
756 """
762 """
757 for range_str in ranges_str.split():
763 for range_str in ranges_str.split():
758 rmatch = range_re.match(range_str)
764 rmatch = range_re.match(range_str)
759 if not rmatch:
765 if not rmatch:
760 continue
766 continue
761 start = int(rmatch.group("start"))
767 start = int(rmatch.group("start"))
762 end = rmatch.group("end")
768 end = rmatch.group("end")
763 end = int(end) if end else start+1 # If no end specified, get (a, a+1)
769 end = int(end) if end else start+1 # If no end specified, get (a, a+1)
764 if rmatch.group("sep") == "-": # 1-3 == 1:4 --> [1, 2, 3]
770 if rmatch.group("sep") == "-": # 1-3 == 1:4 --> [1, 2, 3]
765 end += 1
771 end += 1
766 startsess = rmatch.group("startsess") or "0"
772 startsess = rmatch.group("startsess") or "0"
767 endsess = rmatch.group("endsess") or startsess
773 endsess = rmatch.group("endsess") or startsess
768 startsess = int(startsess.replace("~","-"))
774 startsess = int(startsess.replace("~","-"))
769 endsess = int(endsess.replace("~","-"))
775 endsess = int(endsess.replace("~","-"))
770 assert endsess >= startsess
776 assert endsess >= startsess
771
777
772 if endsess == startsess:
778 if endsess == startsess:
773 yield (startsess, start, end)
779 yield (startsess, start, end)
774 continue
780 continue
775 # Multiple sessions in one range:
781 # Multiple sessions in one range:
776 yield (startsess, start, None)
782 yield (startsess, start, None)
777 for sess in range(startsess+1, endsess):
783 for sess in range(startsess+1, endsess):
778 yield (sess, 1, None)
784 yield (sess, 1, None)
779 yield (endsess, 1, end)
785 yield (endsess, 1, end)
780
786
781
787
782 def _format_lineno(session, line):
788 def _format_lineno(session, line):
783 """Helper function to format line numbers properly."""
789 """Helper function to format line numbers properly."""
784 if session == 0:
790 if session == 0:
785 return str(line)
791 return str(line)
786 return "%s#%s" % (session, line)
792 return "%s#%s" % (session, line)
787
793
788
794
@@ -1,307 +1,312 b''
1 """Implementation of magic functions related to History.
1 """Implementation of magic functions related to History.
2 """
2 """
3 #-----------------------------------------------------------------------------
3 #-----------------------------------------------------------------------------
4 # Copyright (c) 2012, IPython Development Team.
4 # Copyright (c) 2012, IPython Development Team.
5 #
5 #
6 # Distributed under the terms of the Modified BSD License.
6 # Distributed under the terms of the Modified BSD License.
7 #
7 #
8 # The full license is in the file COPYING.txt, distributed with this software.
8 # The full license is in the file COPYING.txt, distributed with this software.
9 #-----------------------------------------------------------------------------
9 #-----------------------------------------------------------------------------
10
10
11 #-----------------------------------------------------------------------------
11 #-----------------------------------------------------------------------------
12 # Imports
12 # Imports
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14 from __future__ import print_function
14 from __future__ import print_function
15
15
16 # Stdlib
16 # Stdlib
17 import os
17 import os
18 from io import open as io_open
18 from io import open as io_open
19 from IPython.external.argparse import Action
19 from IPython.external.argparse import Action
20
20
21 # Our own packages
21 # Our own packages
22 from IPython.core.error import StdinNotImplementedError
22 from IPython.core.error import StdinNotImplementedError
23 from IPython.core.magic import Magics, magics_class, line_magic
23 from IPython.core.magic import Magics, magics_class, line_magic
24 from IPython.core.magic_arguments import (argument, magic_arguments,
24 from IPython.core.magic_arguments import (argument, magic_arguments,
25 parse_argstring)
25 parse_argstring)
26 from IPython.testing.skipdoctest import skip_doctest
26 from IPython.testing.skipdoctest import skip_doctest
27 from IPython.utils import io
27 from IPython.utils import io
28
28
29 #-----------------------------------------------------------------------------
29 #-----------------------------------------------------------------------------
30 # Magics class implementation
30 # Magics class implementation
31 #-----------------------------------------------------------------------------
31 #-----------------------------------------------------------------------------
32
32
33
33
34 _unspecified = object()
34 _unspecified = object()
35
35
36
36
37 @magics_class
37 @magics_class
38 class HistoryMagics(Magics):
38 class HistoryMagics(Magics):
39
39
40 @magic_arguments()
40 @magic_arguments()
41 @argument(
41 @argument(
42 '-n', dest='print_nums', action='store_true', default=False,
42 '-n', dest='print_nums', action='store_true', default=False,
43 help="""
43 help="""
44 print line numbers for each input.
44 print line numbers for each input.
45 This feature is only available if numbered prompts are in use.
45 This feature is only available if numbered prompts are in use.
46 """)
46 """)
47 @argument(
47 @argument(
48 '-o', dest='get_output', action='store_true', default=False,
48 '-o', dest='get_output', action='store_true', default=False,
49 help="also print outputs for each input.")
49 help="also print outputs for each input.")
50 @argument(
50 @argument(
51 '-p', dest='pyprompts', action='store_true', default=False,
51 '-p', dest='pyprompts', action='store_true', default=False,
52 help="""
52 help="""
53 print classic '>>>' python prompts before each input.
53 print classic '>>>' python prompts before each input.
54 This is useful for making documentation, and in conjunction
54 This is useful for making documentation, and in conjunction
55 with -o, for producing doctest-ready output.
55 with -o, for producing doctest-ready output.
56 """)
56 """)
57 @argument(
57 @argument(
58 '-t', dest='raw', action='store_false', default=True,
58 '-t', dest='raw', action='store_false', default=True,
59 help="""
59 help="""
60 print the 'translated' history, as IPython understands it.
60 print the 'translated' history, as IPython understands it.
61 IPython filters your input and converts it all into valid Python
61 IPython filters your input and converts it all into valid Python
62 source before executing it (things like magics or aliases are turned
62 source before executing it (things like magics or aliases are turned
63 into function calls, for example). With this option, you'll see the
63 into function calls, for example). With this option, you'll see the
64 native history instead of the user-entered version: '%%cd /' will be
64 native history instead of the user-entered version: '%%cd /' will be
65 seen as 'get_ipython().magic("%%cd /")' instead of '%%cd /'.
65 seen as 'get_ipython().magic("%%cd /")' instead of '%%cd /'.
66 """)
66 """)
67 @argument(
67 @argument(
68 '-f', dest='filename',
68 '-f', dest='filename',
69 help="""
69 help="""
70 FILENAME: instead of printing the output to the screen, redirect
70 FILENAME: instead of printing the output to the screen, redirect
71 it to the given file. The file is always overwritten, though *when
71 it to the given file. The file is always overwritten, though *when
72 it can*, IPython asks for confirmation first. In particular, running
72 it can*, IPython asks for confirmation first. In particular, running
73 the command 'history -f FILENAME' from the IPython Notebook
73 the command 'history -f FILENAME' from the IPython Notebook
74 interface will replace FILENAME even if it already exists *without*
74 interface will replace FILENAME even if it already exists *without*
75 confirmation.
75 confirmation.
76 """)
76 """)
77 @argument(
77 @argument(
78 '-g', dest='pattern', nargs='*', default=None,
78 '-g', dest='pattern', nargs='*', default=None,
79 help="""
79 help="""
80 treat the arg as a glob pattern to search for in (full) history.
80 treat the arg as a glob pattern to search for in (full) history.
81 This includes the saved history (almost all commands ever written).
81 This includes the saved history (almost all commands ever written).
82 The pattern may contain '?' to match one unknown character and '*'
82 The pattern may contain '?' to match one unknown character and '*'
83 to match any number of unknown characters. Use '%%hist -g' to show
83 to match any number of unknown characters. Use '%%hist -g' to show
84 full saved history (may be very long).
84 full saved history (may be very long).
85 """)
85 """)
86 @argument(
86 @argument(
87 '-l', dest='limit', type=int, nargs='?', default=_unspecified,
87 '-l', dest='limit', type=int, nargs='?', default=_unspecified,
88 help="""
88 help="""
89 get the last n lines from all sessions. Specify n as a single
89 get the last n lines from all sessions. Specify n as a single
90 arg, or the default is the last 10 lines.
90 arg, or the default is the last 10 lines.
91 """)
91 """)
92 @argument(
93 '-u', dest='unique', action='store_true',
94 help="""
95 when searching history using `-g`, show only unique history.
96 """)
92 @argument('range', nargs='*')
97 @argument('range', nargs='*')
93 @skip_doctest
98 @skip_doctest
94 @line_magic
99 @line_magic
95 def history(self, parameter_s = ''):
100 def history(self, parameter_s = ''):
96 """Print input history (_i<n> variables), with most recent last.
101 """Print input history (_i<n> variables), with most recent last.
97
102
98 By default, input history is printed without line numbers so it can be
103 By default, input history is printed without line numbers so it can be
99 directly pasted into an editor. Use -n to show them.
104 directly pasted into an editor. Use -n to show them.
100
105
101 By default, all input history from the current session is displayed.
106 By default, all input history from the current session is displayed.
102 Ranges of history can be indicated using the syntax:
107 Ranges of history can be indicated using the syntax:
103 4 : Line 4, current session
108 4 : Line 4, current session
104 4-6 : Lines 4-6, current session
109 4-6 : Lines 4-6, current session
105 243/1-5: Lines 1-5, session 243
110 243/1-5: Lines 1-5, session 243
106 ~2/7 : Line 7, session 2 before current
111 ~2/7 : Line 7, session 2 before current
107 ~8/1-~6/5 : From the first line of 8 sessions ago, to the fifth line
112 ~8/1-~6/5 : From the first line of 8 sessions ago, to the fifth line
108 of 6 sessions ago.
113 of 6 sessions ago.
109 Multiple ranges can be entered, separated by spaces
114 Multiple ranges can be entered, separated by spaces
110
115
111 The same syntax is used by %macro, %save, %edit, %rerun
116 The same syntax is used by %macro, %save, %edit, %rerun
112
117
113 Examples
118 Examples
114 --------
119 --------
115 ::
120 ::
116
121
117 In [6]: %history -n 4-6
122 In [6]: %history -n 4-6
118 4:a = 12
123 4:a = 12
119 5:print a**2
124 5:print a**2
120 6:%history -n 4-6
125 6:%history -n 4-6
121
126
122 """
127 """
123
128
124 args = parse_argstring(self.history, parameter_s)
129 args = parse_argstring(self.history, parameter_s)
125
130
126 # For brevity
131 # For brevity
127 history_manager = self.shell.history_manager
132 history_manager = self.shell.history_manager
128
133
129 def _format_lineno(session, line):
134 def _format_lineno(session, line):
130 """Helper function to format line numbers properly."""
135 """Helper function to format line numbers properly."""
131 if session in (0, history_manager.session_number):
136 if session in (0, history_manager.session_number):
132 return str(line)
137 return str(line)
133 return "%s/%s" % (session, line)
138 return "%s/%s" % (session, line)
134
139
135 # Check if output to specific file was requested.
140 # Check if output to specific file was requested.
136 outfname = args.filename
141 outfname = args.filename
137 if not outfname:
142 if not outfname:
138 outfile = io.stdout # default
143 outfile = io.stdout # default
139 # We don't want to close stdout at the end!
144 # We don't want to close stdout at the end!
140 close_at_end = False
145 close_at_end = False
141 else:
146 else:
142 if os.path.exists(outfname):
147 if os.path.exists(outfname):
143 try:
148 try:
144 ans = io.ask_yes_no("File %r exists. Overwrite?" % outfname)
149 ans = io.ask_yes_no("File %r exists. Overwrite?" % outfname)
145 except StdinNotImplementedError:
150 except StdinNotImplementedError:
146 ans = True
151 ans = True
147 if not ans:
152 if not ans:
148 print('Aborting.')
153 print('Aborting.')
149 return
154 return
150 print("Overwriting file.")
155 print("Overwriting file.")
151 outfile = io_open(outfname, 'w', encoding='utf-8')
156 outfile = io_open(outfname, 'w', encoding='utf-8')
152 close_at_end = True
157 close_at_end = True
153
158
154 print_nums = args.print_nums
159 print_nums = args.print_nums
155 get_output = args.get_output
160 get_output = args.get_output
156 pyprompts = args.pyprompts
161 pyprompts = args.pyprompts
157 raw = args.raw
162 raw = args.raw
158
163
159 pattern = None
164 pattern = None
160 limit = None if args.limit is _unspecified else args.limit
165 limit = None if args.limit is _unspecified else args.limit
161
166
162 if args.pattern is not None:
167 if args.pattern is not None:
163 if args.pattern:
168 if args.pattern:
164 pattern = "*" + " ".join(args.pattern) + "*"
169 pattern = "*" + " ".join(args.pattern) + "*"
165 else:
170 else:
166 pattern = "*"
171 pattern = "*"
167 hist = history_manager.search(pattern, raw=raw, output=get_output,
172 hist = history_manager.search(pattern, raw=raw, output=get_output,
168 n=limit)
173 n=limit, unique=args.unique)
169 print_nums = True
174 print_nums = True
170 elif args.limit is not _unspecified:
175 elif args.limit is not _unspecified:
171 n = 10 if limit is None else limit
176 n = 10 if limit is None else limit
172 hist = history_manager.get_tail(n, raw=raw, output=get_output)
177 hist = history_manager.get_tail(n, raw=raw, output=get_output)
173 else:
178 else:
174 if args.range: # Get history by ranges
179 if args.range: # Get history by ranges
175 hist = history_manager.get_range_by_str(" ".join(args.range),
180 hist = history_manager.get_range_by_str(" ".join(args.range),
176 raw, get_output)
181 raw, get_output)
177 else: # Just get history for the current session
182 else: # Just get history for the current session
178 hist = history_manager.get_range(raw=raw, output=get_output)
183 hist = history_manager.get_range(raw=raw, output=get_output)
179
184
180 # We could be displaying the entire history, so let's not try to pull
185 # We could be displaying the entire history, so let's not try to pull
181 # it into a list in memory. Anything that needs more space will just
186 # it into a list in memory. Anything that needs more space will just
182 # misalign.
187 # misalign.
183 width = 4
188 width = 4
184
189
185 for session, lineno, inline in hist:
190 for session, lineno, inline in hist:
186 # Print user history with tabs expanded to 4 spaces. The GUI
191 # Print user history with tabs expanded to 4 spaces. The GUI
187 # clients use hard tabs for easier usability in auto-indented code,
192 # clients use hard tabs for easier usability in auto-indented code,
188 # but we want to produce PEP-8 compliant history for safe pasting
193 # but we want to produce PEP-8 compliant history for safe pasting
189 # into an editor.
194 # into an editor.
190 if get_output:
195 if get_output:
191 inline, output = inline
196 inline, output = inline
192 inline = inline.expandtabs(4).rstrip()
197 inline = inline.expandtabs(4).rstrip()
193
198
194 multiline = "\n" in inline
199 multiline = "\n" in inline
195 line_sep = '\n' if multiline else ' '
200 line_sep = '\n' if multiline else ' '
196 if print_nums:
201 if print_nums:
197 print(u'%s:%s' % (_format_lineno(session, lineno).rjust(width),
202 print(u'%s:%s' % (_format_lineno(session, lineno).rjust(width),
198 line_sep), file=outfile, end=u'')
203 line_sep), file=outfile, end=u'')
199 if pyprompts:
204 if pyprompts:
200 print(u">>> ", end=u"", file=outfile)
205 print(u">>> ", end=u"", file=outfile)
201 if multiline:
206 if multiline:
202 inline = "\n... ".join(inline.splitlines()) + "\n..."
207 inline = "\n... ".join(inline.splitlines()) + "\n..."
203 print(inline, file=outfile)
208 print(inline, file=outfile)
204 if get_output and output:
209 if get_output and output:
205 print(output, file=outfile)
210 print(output, file=outfile)
206
211
207 if close_at_end:
212 if close_at_end:
208 outfile.close()
213 outfile.close()
209
214
210 @line_magic
215 @line_magic
211 def recall(self, arg):
216 def recall(self, arg):
212 r"""Repeat a command, or get command to input line for editing.
217 r"""Repeat a command, or get command to input line for editing.
213
218
214 %recall and %rep are equivalent.
219 %recall and %rep are equivalent.
215
220
216 - %recall (no arguments):
221 - %recall (no arguments):
217
222
218 Place a string version of last computation result (stored in the
223 Place a string version of last computation result (stored in the
219 special '_' variable) to the next input prompt. Allows you to create
224 special '_' variable) to the next input prompt. Allows you to create
220 elaborate command lines without using copy-paste::
225 elaborate command lines without using copy-paste::
221
226
222 In[1]: l = ["hei", "vaan"]
227 In[1]: l = ["hei", "vaan"]
223 In[2]: "".join(l)
228 In[2]: "".join(l)
224 Out[2]: heivaan
229 Out[2]: heivaan
225 In[3]: %recall
230 In[3]: %recall
226 In[4]: heivaan_ <== cursor blinking
231 In[4]: heivaan_ <== cursor blinking
227
232
228 %recall 45
233 %recall 45
229
234
230 Place history line 45 on the next input prompt. Use %hist to find
235 Place history line 45 on the next input prompt. Use %hist to find
231 out the number.
236 out the number.
232
237
233 %recall 1-4
238 %recall 1-4
234
239
235 Combine the specified lines into one cell, and place it on the next
240 Combine the specified lines into one cell, and place it on the next
236 input prompt. See %history for the slice syntax.
241 input prompt. See %history for the slice syntax.
237
242
238 %recall foo+bar
243 %recall foo+bar
239
244
240 If foo+bar can be evaluated in the user namespace, the result is
245 If foo+bar can be evaluated in the user namespace, the result is
241 placed at the next input prompt. Otherwise, the history is searched
246 placed at the next input prompt. Otherwise, the history is searched
242 for lines which contain that substring, and the most recent one is
247 for lines which contain that substring, and the most recent one is
243 placed at the next input prompt.
248 placed at the next input prompt.
244 """
249 """
245 if not arg: # Last output
250 if not arg: # Last output
246 self.shell.set_next_input(str(self.shell.user_ns["_"]))
251 self.shell.set_next_input(str(self.shell.user_ns["_"]))
247 return
252 return
248 # Get history range
253 # Get history range
249 histlines = self.shell.history_manager.get_range_by_str(arg)
254 histlines = self.shell.history_manager.get_range_by_str(arg)
250 cmd = "\n".join(x[2] for x in histlines)
255 cmd = "\n".join(x[2] for x in histlines)
251 if cmd:
256 if cmd:
252 self.shell.set_next_input(cmd.rstrip())
257 self.shell.set_next_input(cmd.rstrip())
253 return
258 return
254
259
255 try: # Variable in user namespace
260 try: # Variable in user namespace
256 cmd = str(eval(arg, self.shell.user_ns))
261 cmd = str(eval(arg, self.shell.user_ns))
257 except Exception: # Search for term in history
262 except Exception: # Search for term in history
258 histlines = self.shell.history_manager.search("*"+arg+"*")
263 histlines = self.shell.history_manager.search("*"+arg+"*")
259 for h in reversed([x[2] for x in histlines]):
264 for h in reversed([x[2] for x in histlines]):
260 if 'recall' in h or 'rep' in h:
265 if 'recall' in h or 'rep' in h:
261 continue
266 continue
262 self.shell.set_next_input(h.rstrip())
267 self.shell.set_next_input(h.rstrip())
263 return
268 return
264 else:
269 else:
265 self.shell.set_next_input(cmd.rstrip())
270 self.shell.set_next_input(cmd.rstrip())
266 print("Couldn't evaluate or find in history:", arg)
271 print("Couldn't evaluate or find in history:", arg)
267
272
268 @line_magic
273 @line_magic
269 def rerun(self, parameter_s=''):
274 def rerun(self, parameter_s=''):
270 """Re-run previous input
275 """Re-run previous input
271
276
272 By default, you can specify ranges of input history to be repeated
277 By default, you can specify ranges of input history to be repeated
273 (as with %history). With no arguments, it will repeat the last line.
278 (as with %history). With no arguments, it will repeat the last line.
274
279
275 Options:
280 Options:
276
281
277 -l <n> : Repeat the last n lines of input, not including the
282 -l <n> : Repeat the last n lines of input, not including the
278 current command.
283 current command.
279
284
280 -g foo : Repeat the most recent line which contains foo
285 -g foo : Repeat the most recent line which contains foo
281 """
286 """
282 opts, args = self.parse_options(parameter_s, 'l:g:', mode='string')
287 opts, args = self.parse_options(parameter_s, 'l:g:', mode='string')
283 if "l" in opts: # Last n lines
288 if "l" in opts: # Last n lines
284 n = int(opts['l'])
289 n = int(opts['l'])
285 hist = self.shell.history_manager.get_tail(n)
290 hist = self.shell.history_manager.get_tail(n)
286 elif "g" in opts: # Search
291 elif "g" in opts: # Search
287 p = "*"+opts['g']+"*"
292 p = "*"+opts['g']+"*"
288 hist = list(self.shell.history_manager.search(p))
293 hist = list(self.shell.history_manager.search(p))
289 for l in reversed(hist):
294 for l in reversed(hist):
290 if "rerun" not in l[2]:
295 if "rerun" not in l[2]:
291 hist = [l] # The last match which isn't a %rerun
296 hist = [l] # The last match which isn't a %rerun
292 break
297 break
293 else:
298 else:
294 hist = [] # No matches except %rerun
299 hist = [] # No matches except %rerun
295 elif args: # Specify history ranges
300 elif args: # Specify history ranges
296 hist = self.shell.history_manager.get_range_by_str(args)
301 hist = self.shell.history_manager.get_range_by_str(args)
297 else: # Last line
302 else: # Last line
298 hist = self.shell.history_manager.get_tail(1)
303 hist = self.shell.history_manager.get_tail(1)
299 hist = [x[2] for x in hist]
304 hist = [x[2] for x in hist]
300 if not hist:
305 if not hist:
301 print("No lines in history match specification")
306 print("No lines in history match specification")
302 return
307 return
303 histlines = "\n".join(hist)
308 histlines = "\n".join(hist)
304 print("=== Executing: ===")
309 print("=== Executing: ===")
305 print(histlines)
310 print(histlines)
306 print("=== Output: ===")
311 print("=== Output: ===")
307 self.shell.run_cell("\n".join(hist), store_history=False)
312 self.shell.run_cell("\n".join(hist), store_history=False)
@@ -1,163 +1,184 b''
1 # coding: utf-8
1 # coding: utf-8
2 """Tests for the IPython tab-completion machinery.
2 """Tests for the IPython tab-completion machinery.
3 """
3 """
4 #-----------------------------------------------------------------------------
4 #-----------------------------------------------------------------------------
5 # Module imports
5 # Module imports
6 #-----------------------------------------------------------------------------
6 #-----------------------------------------------------------------------------
7
7
8 # stdlib
8 # stdlib
9 import os
9 import os
10 import shutil
10 import shutil
11 import sys
11 import sys
12 import tempfile
12 import tempfile
13 import unittest
13 import unittest
14 from datetime import datetime
14 from datetime import datetime
15
15
16 # third party
16 # third party
17 import nose.tools as nt
17 import nose.tools as nt
18
18
19 # our own packages
19 # our own packages
20 from IPython.config.loader import Config
20 from IPython.config.loader import Config
21 from IPython.utils.tempdir import TemporaryDirectory
21 from IPython.utils.tempdir import TemporaryDirectory
22 from IPython.core.history import HistoryManager, extract_hist_ranges
22 from IPython.core.history import HistoryManager, extract_hist_ranges
23 from IPython.utils import py3compat
23 from IPython.utils import py3compat
24
24
25 def setUp():
25 def setUp():
26 nt.assert_equal(sys.getdefaultencoding(), "utf-8" if py3compat.PY3 else "ascii")
26 nt.assert_equal(sys.getdefaultencoding(), "utf-8" if py3compat.PY3 else "ascii")
27
27
28 def test_history():
28 def test_history():
29 ip = get_ipython()
29 ip = get_ipython()
30 with TemporaryDirectory() as tmpdir:
30 with TemporaryDirectory() as tmpdir:
31 hist_manager_ori = ip.history_manager
31 hist_manager_ori = ip.history_manager
32 hist_file = os.path.join(tmpdir, 'history.sqlite')
32 hist_file = os.path.join(tmpdir, 'history.sqlite')
33 try:
33 try:
34 ip.history_manager = HistoryManager(shell=ip, hist_file=hist_file)
34 ip.history_manager = HistoryManager(shell=ip, hist_file=hist_file)
35 hist = [u'a=1', u'def f():\n test = 1\n return test', u"b='β‚¬Γ†ΒΎΓ·ΓŸ'"]
35 hist = [u'a=1', u'def f():\n test = 1\n return test', u"b='β‚¬Γ†ΒΎΓ·ΓŸ'"]
36 for i, h in enumerate(hist, start=1):
36 for i, h in enumerate(hist, start=1):
37 ip.history_manager.store_inputs(i, h)
37 ip.history_manager.store_inputs(i, h)
38
38
39 ip.history_manager.db_log_output = True
39 ip.history_manager.db_log_output = True
40 # Doesn't match the input, but we'll just check it's stored.
40 # Doesn't match the input, but we'll just check it's stored.
41 ip.history_manager.output_hist_reprs[3] = "spam"
41 ip.history_manager.output_hist_reprs[3] = "spam"
42 ip.history_manager.store_output(3)
42 ip.history_manager.store_output(3)
43
43
44 nt.assert_equal(ip.history_manager.input_hist_raw, [''] + hist)
44 nt.assert_equal(ip.history_manager.input_hist_raw, [''] + hist)
45
45
46 # Detailed tests for _get_range_session
46 # Detailed tests for _get_range_session
47 grs = ip.history_manager._get_range_session
47 grs = ip.history_manager._get_range_session
48 nt.assert_equal(list(grs(start=2,stop=-1)), zip([0], [2], hist[1:-1]))
48 nt.assert_equal(list(grs(start=2,stop=-1)), zip([0], [2], hist[1:-1]))
49 nt.assert_equal(list(grs(start=-2)), zip([0,0], [2,3], hist[-2:]))
49 nt.assert_equal(list(grs(start=-2)), zip([0,0], [2,3], hist[-2:]))
50 nt.assert_equal(list(grs(output=True)), zip([0,0,0], [1,2,3], zip(hist, [None,None,'spam'])))
50 nt.assert_equal(list(grs(output=True)), zip([0,0,0], [1,2,3], zip(hist, [None,None,'spam'])))
51
51
52 # Check whether specifying a range beyond the end of the current
52 # Check whether specifying a range beyond the end of the current
53 # session results in an error (gh-804)
53 # session results in an error (gh-804)
54 ip.magic('%hist 2-500')
54 ip.magic('%hist 2-500')
55
55
56 # Check that we can write non-ascii characters to a file
56 # Check that we can write non-ascii characters to a file
57 ip.magic("%%hist -f %s" % os.path.join(tmpdir, "test1"))
57 ip.magic("%%hist -f %s" % os.path.join(tmpdir, "test1"))
58 ip.magic("%%hist -pf %s" % os.path.join(tmpdir, "test2"))
58 ip.magic("%%hist -pf %s" % os.path.join(tmpdir, "test2"))
59 ip.magic("%%hist -nf %s" % os.path.join(tmpdir, "test3"))
59 ip.magic("%%hist -nf %s" % os.path.join(tmpdir, "test3"))
60 ip.magic("%%save %s 1-10" % os.path.join(tmpdir, "test4"))
60 ip.magic("%%save %s 1-10" % os.path.join(tmpdir, "test4"))
61
61
62 # New session
62 # New session
63 ip.history_manager.reset()
63 ip.history_manager.reset()
64 newcmds = ["z=5","class X(object):\n pass", "k='p'"]
64 newcmds = [u"z=5",
65 u"class X(object):\n pass",
66 u"k='p'",
67 u"z=5"]
65 for i, cmd in enumerate(newcmds, start=1):
68 for i, cmd in enumerate(newcmds, start=1):
66 ip.history_manager.store_inputs(i, cmd)
69 ip.history_manager.store_inputs(i, cmd)
67 gothist = ip.history_manager.get_range(start=1, stop=4)
70 gothist = ip.history_manager.get_range(start=1, stop=4)
68 nt.assert_equal(list(gothist), zip([0,0,0],[1,2,3], newcmds))
71 nt.assert_equal(list(gothist), zip([0,0,0],[1,2,3], newcmds))
69 # Previous session:
72 # Previous session:
70 gothist = ip.history_manager.get_range(-1, 1, 4)
73 gothist = ip.history_manager.get_range(-1, 1, 4)
71 nt.assert_equal(list(gothist), zip([1,1,1],[1,2,3], hist))
74 nt.assert_equal(list(gothist), zip([1,1,1],[1,2,3], hist))
72
75
76 newhist = [(2, i, c) for (i, c) in enumerate(newcmds, 1)]
77
73 # Check get_hist_tail
78 # Check get_hist_tail
74 gothist = ip.history_manager.get_tail(4, output=True,
79 gothist = ip.history_manager.get_tail(5, output=True,
75 include_latest=True)
80 include_latest=True)
76 expected = [(1, 3, (hist[-1], "spam")),
81 expected = [(1, 3, (hist[-1], "spam"))] \
77 (2, 1, (newcmds[0], None)),
82 + [(s, n, (c, None)) for (s, n, c) in newhist]
78 (2, 2, (newcmds[1], None)),
79 (2, 3, (newcmds[2], None)),]
80 nt.assert_equal(list(gothist), expected)
83 nt.assert_equal(list(gothist), expected)
81
84
82 gothist = ip.history_manager.get_tail(2)
85 gothist = ip.history_manager.get_tail(2)
83 expected = [(2, 1, newcmds[0]),
86 expected = newhist[-3:-1]
84 (2, 2, newcmds[1])]
85 nt.assert_equal(list(gothist), expected)
87 nt.assert_equal(list(gothist), expected)
86
88
87 # Check get_hist_search
89 # Check get_hist_search
88 gothist = ip.history_manager.search("*test*")
90 gothist = ip.history_manager.search("*test*")
89 nt.assert_equal(list(gothist), [(1,2,hist[1])] )
91 nt.assert_equal(list(gothist), [(1,2,hist[1])] )
92
90 gothist = ip.history_manager.search("*=*")
93 gothist = ip.history_manager.search("*=*")
91 nt.assert_equal(list(gothist),
94 nt.assert_equal(list(gothist),
92 [(1, 1, hist[0]),
95 [(1, 1, hist[0]),
93 (1, 2, hist[1]),
96 (1, 2, hist[1]),
94 (1, 3, hist[2]),
97 (1, 3, hist[2]),
95 (2, 1, newcmds[0]),
98 newhist[0],
96 (2, 3, newcmds[2])])
99 newhist[2],
97 gothist = ip.history_manager.search("*=*", n=3)
100 newhist[3]])
101
102 gothist = ip.history_manager.search("*=*", n=4)
103 nt.assert_equal(list(gothist),
104 [(1, 3, hist[2]),
105 newhist[0],
106 newhist[2],
107 newhist[3]])
108
109 gothist = ip.history_manager.search("*=*", unique=True)
110 nt.assert_equal(list(gothist),
111 [(1, 1, hist[0]),
112 (1, 2, hist[1]),
113 (1, 3, hist[2]),
114 newhist[2],
115 newhist[3]])
116
117 gothist = ip.history_manager.search("*=*", unique=True, n=3)
98 nt.assert_equal(list(gothist),
118 nt.assert_equal(list(gothist),
99 [(1, 3, hist[2]),
119 [(1, 3, hist[2]),
100 (2, 1, newcmds[0]),
120 newhist[2],
101 (2, 3, newcmds[2])])
121 newhist[3]])
122
102 gothist = ip.history_manager.search("b*", output=True)
123 gothist = ip.history_manager.search("b*", output=True)
103 nt.assert_equal(list(gothist), [(1,3,(hist[2],"spam"))] )
124 nt.assert_equal(list(gothist), [(1,3,(hist[2],"spam"))] )
104
125
105 # Cross testing: check that magic %save can get previous session.
126 # Cross testing: check that magic %save can get previous session.
106 testfilename = os.path.realpath(os.path.join(tmpdir, "test.py"))
127 testfilename = os.path.realpath(os.path.join(tmpdir, "test.py"))
107 ip.magic("save " + testfilename + " ~1/1-3")
128 ip.magic("save " + testfilename + " ~1/1-3")
108 with py3compat.open(testfilename, encoding='utf-8') as testfile:
129 with py3compat.open(testfilename, encoding='utf-8') as testfile:
109 nt.assert_equal(testfile.read(),
130 nt.assert_equal(testfile.read(),
110 u"# coding: utf-8\n" + u"\n".join(hist)+u"\n")
131 u"# coding: utf-8\n" + u"\n".join(hist)+u"\n")
111
132
112 # Duplicate line numbers - check that it doesn't crash, and
133 # Duplicate line numbers - check that it doesn't crash, and
113 # gets a new session
134 # gets a new session
114 ip.history_manager.store_inputs(1, "rogue")
135 ip.history_manager.store_inputs(1, "rogue")
115 ip.history_manager.writeout_cache()
136 ip.history_manager.writeout_cache()
116 nt.assert_equal(ip.history_manager.session_number, 3)
137 nt.assert_equal(ip.history_manager.session_number, 3)
117 finally:
138 finally:
118 # Restore history manager
139 # Restore history manager
119 ip.history_manager = hist_manager_ori
140 ip.history_manager = hist_manager_ori
120
141
121
142
122 def test_extract_hist_ranges():
143 def test_extract_hist_ranges():
123 instr = "1 2/3 ~4/5-6 ~4/7-~4/9 ~9/2-~7/5"
144 instr = "1 2/3 ~4/5-6 ~4/7-~4/9 ~9/2-~7/5"
124 expected = [(0, 1, 2), # 0 == current session
145 expected = [(0, 1, 2), # 0 == current session
125 (2, 3, 4),
146 (2, 3, 4),
126 (-4, 5, 7),
147 (-4, 5, 7),
127 (-4, 7, 10),
148 (-4, 7, 10),
128 (-9, 2, None), # None == to end
149 (-9, 2, None), # None == to end
129 (-8, 1, None),
150 (-8, 1, None),
130 (-7, 1, 6)]
151 (-7, 1, 6)]
131 actual = list(extract_hist_ranges(instr))
152 actual = list(extract_hist_ranges(instr))
132 nt.assert_equal(actual, expected)
153 nt.assert_equal(actual, expected)
133
154
134 def test_magic_rerun():
155 def test_magic_rerun():
135 """Simple test for %rerun (no args -> rerun last line)"""
156 """Simple test for %rerun (no args -> rerun last line)"""
136 ip = get_ipython()
157 ip = get_ipython()
137 ip.run_cell("a = 10", store_history=True)
158 ip.run_cell("a = 10", store_history=True)
138 ip.run_cell("a += 1", store_history=True)
159 ip.run_cell("a += 1", store_history=True)
139 nt.assert_equal(ip.user_ns["a"], 11)
160 nt.assert_equal(ip.user_ns["a"], 11)
140 ip.run_cell("%rerun", store_history=True)
161 ip.run_cell("%rerun", store_history=True)
141 nt.assert_equal(ip.user_ns["a"], 12)
162 nt.assert_equal(ip.user_ns["a"], 12)
142
163
143 def test_timestamp_type():
164 def test_timestamp_type():
144 ip = get_ipython()
165 ip = get_ipython()
145 info = ip.history_manager.get_session_info()
166 info = ip.history_manager.get_session_info()
146 nt.assert_true(isinstance(info[1], datetime))
167 nt.assert_true(isinstance(info[1], datetime))
147
168
148 def test_hist_file_config():
169 def test_hist_file_config():
149 cfg = Config()
170 cfg = Config()
150 tfile = tempfile.NamedTemporaryFile(delete=False)
171 tfile = tempfile.NamedTemporaryFile(delete=False)
151 cfg.HistoryManager.hist_file = tfile.name
172 cfg.HistoryManager.hist_file = tfile.name
152 try:
173 try:
153 hm = HistoryManager(shell=get_ipython(), config=cfg)
174 hm = HistoryManager(shell=get_ipython(), config=cfg)
154 nt.assert_equal(hm.hist_file, cfg.HistoryManager.hist_file)
175 nt.assert_equal(hm.hist_file, cfg.HistoryManager.hist_file)
155 finally:
176 finally:
156 try:
177 try:
157 os.remove(tfile.name)
178 os.remove(tfile.name)
158 except OSError:
179 except OSError:
159 # same catch as in testing.tools.TempFileMixin
180 # same catch as in testing.tools.TempFileMixin
160 # On Windows, even though we close the file, we still can't
181 # On Windows, even though we close the file, we still can't
161 # delete it. I have no clue why
182 # delete it. I have no clue why
162 pass
183 pass
163
184
General Comments 0
You need to be logged in to leave comments. Login now