##// END OF EJS Templates
Merge branch 'history-access'
Thomas Kluyver -
r4990:401962b8 merge
parent child Browse files
Show More
@@ -0,0 +1,38 b''
1 #!/usr/bin/env python
2 """Extract a session from the IPython input history.
3
4 Usage:
5 ipython-get-history.py sessionnumber [outputfile]
6
7 If outputfile is not given, the relevant history is written to stdout. If
8 outputfile has a .py extension, the translated history (without IPython's
9 special syntax) will be extracted.
10
11 Example:
12 ./ipython-get-history.py 57 record.ipy
13
14
15 This script is a simple demonstration of HistoryAccessor. It should be possible
16 to build much more flexible and powerful tools to browse and pull from the
17 history database.
18 """
19 import sys
20 import codecs
21
22 from IPython.core.history import HistoryAccessor
23
24 session_number = int(sys.argv[1])
25 if len(sys.argv) > 2:
26 dest = open(sys.argv[2], "w")
27 raw = not sys.argv[2].endswith('.py')
28 else:
29 dest = sys.stdout
30 raw = True
31 dest.write("# coding: utf-8\n")
32
33 # Profiles other than 'default' can be specified here with a profile= argument:
34 hist = HistoryAccessor()
35
36 for session, lineno, cell in hist.get_range(session=session_number, raw=raw):
37 # To use this in Python 3, remove the .encode() here:
38 dest.write(cell.encode('utf-8') + '\n')
@@ -25,6 +25,7 b' from IPython.config.configurable import Configurable'
25
25
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 from IPython.utils.path import locate_profile
28 from IPython.utils.traitlets import Bool, Dict, Instance, Int, CInt, List, Unicode
29 from IPython.utils.traitlets import Bool, Dict, Instance, Int, CInt, List, Unicode
29 from IPython.utils.warn import warn
30 from IPython.utils.warn import warn
30
31
@@ -32,79 +33,39 b' from IPython.utils.warn import warn'
32 # Classes and functions
33 # Classes and functions
33 #-----------------------------------------------------------------------------
34 #-----------------------------------------------------------------------------
34
35
35 class HistoryManager(Configurable):
36 class HistoryAccessor(Configurable):
36 """A class to organize all history-related functionality in one place.
37 """Access the history database without adding to it.
37 """
38
38 # Public interface
39 This is intended for use by standalone history tools. IPython shells use
39
40 HistoryManager, below, which is a subclass of this."""
40 # An instance of the IPython shell we are attached to
41 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC')
42 # Lists to hold processed and raw history. These start with a blank entry
43 # so that we can index them starting from 1
44 input_hist_parsed = List([""])
45 input_hist_raw = List([""])
46 # A list of directories visited during session
47 dir_hist = List()
48 def _dir_hist_default(self):
49 try:
50 return [os.getcwdu()]
51 except OSError:
52 return []
53
54 # A dict of output history, keyed with ints from the shell's
55 # execution count.
56 output_hist = Dict()
57 # The text/plain repr of outputs.
58 output_hist_reprs = Dict()
59
60 # String holding the path to the history file
41 # String holding the path to the history file
61 hist_file = Unicode(config=True)
42 hist_file = Unicode(config=True)
62
43
63 # The SQLite database
44 # The SQLite database
64 db = Instance(sqlite3.Connection)
45 db = Instance(sqlite3.Connection)
65 # The number of the current session in the history database
46
66 session_number = CInt()
47 def __init__(self, profile='default', hist_file=u'', shell=None, config=None, **traits):
67 # Should we log output to the database? (default no)
48 """Create a new history accessor.
68 db_log_output = Bool(False, config=True)
49
69 # Write to database every x commands (higher values save disk access & power)
50 Parameters
70 # Values of 1 or less effectively disable caching.
51 ----------
71 db_cache_size = Int(0, config=True)
52 profile : str
72 # The input and output caches
53 The name of the profile from which to open history.
73 db_input_cache = List()
54 hist_file : str
74 db_output_cache = List()
55 Path to an SQLite history database stored by IPython. If specified,
75
56 hist_file overrides profile.
76 # History saving in separate thread
57 shell :
77 save_thread = Instance('IPython.core.history.HistorySavingThread')
58 InteractiveShell object, for use by HistoryManager subclass
78 try: # Event is a function returning an instance of _Event...
59 config :
79 save_flag = Instance(threading._Event)
60 Config object. hist_file can also be set through this.
80 except AttributeError: # ...until Python 3.3, when it's a class.
81 save_flag = Instance(threading.Event)
82
83 # Private interface
84 # Variables used to store the three last inputs from the user. On each new
85 # history update, we populate the user's namespace with these, shifted as
86 # necessary.
87 _i00 = Unicode(u'')
88 _i = Unicode(u'')
89 _ii = Unicode(u'')
90 _iii = Unicode(u'')
91
92 # A regex matching all forms of the exit command, so that we don't store
93 # them in the history (it's annoying to rewind the first entry and land on
94 # an exit call).
95 _exit_re = re.compile(r"(exit|quit)(\s*\(.*\))?$")
96
97 def __init__(self, shell, config=None, **traits):
98 """Create a new history manager associated with a shell instance.
99 """
61 """
100 # We need a pointer back to the shell for various tasks.
62 # We need a pointer back to the shell for various tasks.
101 super(HistoryManager, self).__init__(shell=shell, config=config,
63 super(HistoryAccessor, self).__init__(shell=shell, config=config,
102 **traits)
64 hist_file=hist_file, **traits)
103
65
104 if self.hist_file == u'':
66 if self.hist_file == u'':
105 # No one has set the hist_file, yet.
67 # No one has set the hist_file, yet.
106 histfname = 'history'
68 self.hist_file = self._get_hist_file_name(profile)
107 self.hist_file = os.path.join(shell.profile_dir.location, histfname + '.sqlite')
108
69
109 try:
70 try:
110 self.init_db()
71 self.init_db()
@@ -119,16 +80,20 b' class HistoryManager(Configurable):'
119 else:
80 else:
120 # The hist_file is probably :memory: or something else.
81 # The hist_file is probably :memory: or something else.
121 raise
82 raise
122
83
123 self.save_flag = threading.Event()
84 def _get_hist_file_name(self, profile='default'):
124 self.db_input_cache_lock = threading.Lock()
85 """Find the history file for the given profile name.
125 self.db_output_cache_lock = threading.Lock()
86
126 self.save_thread = HistorySavingThread(self)
87 This is overridden by the HistoryManager subclass, to use the shell's
127 self.save_thread.start()
88 active profile.
128
89
129 self.new_session()
90 Parameters
130
91 ----------
131
92 profile : str
93 The name of a profile which has a history file.
94 """
95 return os.path.join(locate_profile(profile), 'history.sqlite')
96
132 def init_db(self):
97 def init_db(self):
133 """Connect to the database, and create tables if necessary."""
98 """Connect to the database, and create tables if necessary."""
134 # use detect_types so that timestamps return datetime objects
99 # use detect_types so that timestamps return datetime objects
@@ -146,48 +111,10 b' class HistoryManager(Configurable):'
146 PRIMARY KEY (session, line))""")
111 PRIMARY KEY (session, line))""")
147 self.db.commit()
112 self.db.commit()
148
113
149 def new_session(self, conn=None):
114 def writeout_cache(self):
150 """Get a new session number."""
115 """Overridden by HistoryManager to dump the cache before certain
151 if conn is None:
116 database lookups."""
152 conn = self.db
117 pass
153
154 with conn:
155 # N.B. 'insert into' here is lower case because of a bug in the
156 # sqlite3 module that affects the Turkish locale. This should be
157 # fixed for Python 2.7.3 and 3.2.3, as well as 3.3 onwards.
158 # http://bugs.python.org/issue13099
159 cur = conn.execute("""insert into sessions VALUES (NULL, ?, NULL,
160 NULL, "") """, (datetime.datetime.now(),))
161 self.session_number = cur.lastrowid
162
163 def end_session(self):
164 """Close the database session, filling in the end time and line count."""
165 self.writeout_cache()
166 with self.db:
167 self.db.execute("""UPDATE sessions SET end=?, num_cmds=? WHERE
168 session==?""", (datetime.datetime.now(),
169 len(self.input_hist_parsed)-1, self.session_number))
170 self.session_number = 0
171
172 def name_session(self, name):
173 """Give the current session a name in the history database."""
174 with self.db:
175 self.db.execute("UPDATE sessions SET remark=? WHERE session==?",
176 (name, self.session_number))
177
178 def reset(self, new_session=True):
179 """Clear the session history, releasing all object references, and
180 optionally open a new session."""
181 self.output_hist.clear()
182 # The directory history can't be completely empty
183 self.dir_hist[:] = [os.getcwdu()]
184
185 if new_session:
186 if self.session_number:
187 self.end_session()
188 self.input_hist_parsed[:] = [""]
189 self.input_hist_raw[:] = [""]
190 self.new_session()
191
118
192 ## -------------------------------
119 ## -------------------------------
193 ## Methods for retrieving history:
120 ## Methods for retrieving history:
@@ -219,7 +146,6 b' class HistoryManager(Configurable):'
219 return ((ses, lin, (inp, out)) for ses, lin, inp, out in cur)
146 return ((ses, lin, (inp, out)) for ses, lin, inp, out in cur)
220 return cur
147 return cur
221
148
222
223 def get_session_info(self, session=0):
149 def get_session_info(self, session=0):
224 """get info about a session
150 """get info about a session
225
151
@@ -246,7 +172,6 b' class HistoryManager(Configurable):'
246 query = "SELECT * from sessions where session == ?"
172 query = "SELECT * from sessions where session == ?"
247 return self.db.execute(query, (session,)).fetchone()
173 return self.db.execute(query, (session,)).fetchone()
248
174
249
250 def get_tail(self, n=10, raw=True, output=False, include_latest=False):
175 def get_tail(self, n=10, raw=True, output=False, include_latest=False):
251 """Get the last n lines from the history database.
176 """Get the last n lines from the history database.
252
177
@@ -298,35 +223,14 b' class HistoryManager(Configurable):'
298 self.writeout_cache()
223 self.writeout_cache()
299 return self._run_sql("WHERE %s GLOB ?" % tosearch, (pattern,),
224 return self._run_sql("WHERE %s GLOB ?" % tosearch, (pattern,),
300 raw=raw, output=output)
225 raw=raw, output=output)
301
226
302 def _get_range_session(self, start=1, stop=None, raw=True, output=False):
227 def get_range(self, session, start=1, stop=None, raw=True,output=False):
303 """Get input and output history from the current session. Called by
304 get_range, and takes similar parameters."""
305 input_hist = self.input_hist_raw if raw else self.input_hist_parsed
306
307 n = len(input_hist)
308 if start < 0:
309 start += n
310 if not stop or (stop > n):
311 stop = n
312 elif stop < 0:
313 stop += n
314
315 for i in range(start, stop):
316 if output:
317 line = (input_hist[i], self.output_hist_reprs.get(i))
318 else:
319 line = input_hist[i]
320 yield (0, i, line)
321
322 def get_range(self, session=0, start=1, stop=None, raw=True,output=False):
323 """Retrieve input by session.
228 """Retrieve input by session.
324
229
325 Parameters
230 Parameters
326 ----------
231 ----------
327 session : int
232 session : int
328 Session number to retrieve. The current session is 0, and negative
233 Session number to retrieve.
329 numbers count back from current session, so -1 is previous session.
330 start : int
234 start : int
331 First line to retrieve.
235 First line to retrieve.
332 stop : int
236 stop : int
@@ -346,11 +250,6 b' class HistoryManager(Configurable):'
346 (session, line, input) if output is False, or
250 (session, line, input) if output is False, or
347 (session, line, (input, output)) if output is True.
251 (session, line, (input, output)) if output is True.
348 """
252 """
349 if session == 0 or session==self.session_number: # Current session
350 return self._get_range_session(start, stop, raw, output)
351 if session < 0:
352 session += self.session_number
353
354 if stop:
253 if stop:
355 lineclause = "line >= ? AND line < ?"
254 lineclause = "line >= ? AND line < ?"
356 params = (session, start, stop)
255 params = (session, start, stop)
@@ -381,6 +280,181 b' class HistoryManager(Configurable):'
381 for line in self.get_range(sess, s, e, raw=raw, output=output):
280 for line in self.get_range(sess, s, e, raw=raw, output=output):
382 yield line
281 yield line
383
282
283
284 class HistoryManager(HistoryAccessor):
285 """A class to organize all history-related functionality in one place.
286 """
287 # Public interface
288
289 # An instance of the IPython shell we are attached to
290 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC')
291 # Lists to hold processed and raw history. These start with a blank entry
292 # so that we can index them starting from 1
293 input_hist_parsed = List([""])
294 input_hist_raw = List([""])
295 # A list of directories visited during session
296 dir_hist = List()
297 def _dir_hist_default(self):
298 try:
299 return [os.getcwdu()]
300 except OSError:
301 return []
302
303 # A dict of output history, keyed with ints from the shell's
304 # execution count.
305 output_hist = Dict()
306 # The text/plain repr of outputs.
307 output_hist_reprs = Dict()
308
309 # The number of the current session in the history database
310 session_number = CInt()
311 # Should we log output to the database? (default no)
312 db_log_output = Bool(False, config=True)
313 # Write to database every x commands (higher values save disk access & power)
314 # Values of 1 or less effectively disable caching.
315 db_cache_size = Int(0, config=True)
316 # The input and output caches
317 db_input_cache = List()
318 db_output_cache = List()
319
320 # History saving in separate thread
321 save_thread = Instance('IPython.core.history.HistorySavingThread')
322 try: # Event is a function returning an instance of _Event...
323 save_flag = Instance(threading._Event)
324 except AttributeError: # ...until Python 3.3, when it's a class.
325 save_flag = Instance(threading.Event)
326
327 # Private interface
328 # Variables used to store the three last inputs from the user. On each new
329 # history update, we populate the user's namespace with these, shifted as
330 # necessary.
331 _i00 = Unicode(u'')
332 _i = Unicode(u'')
333 _ii = Unicode(u'')
334 _iii = Unicode(u'')
335
336 # A regex matching all forms of the exit command, so that we don't store
337 # them in the history (it's annoying to rewind the first entry and land on
338 # an exit call).
339 _exit_re = re.compile(r"(exit|quit)(\s*\(.*\))?$")
340
341 def __init__(self, shell=None, config=None, **traits):
342 """Create a new history manager associated with a shell instance.
343 """
344 # We need a pointer back to the shell for various tasks.
345 super(HistoryManager, self).__init__(shell=shell, config=config,
346 **traits)
347 self.save_flag = threading.Event()
348 self.db_input_cache_lock = threading.Lock()
349 self.db_output_cache_lock = threading.Lock()
350 self.save_thread = HistorySavingThread(self)
351 self.save_thread.start()
352
353 self.new_session()
354
355 def _get_hist_file_name(self, profile=None):
356 """Get default history file name based on the Shell's profile.
357
358 The profile parameter is ignored, but must exist for compatibility with
359 the parent class."""
360 profile_dir = self.shell.profile_dir.location
361 return os.path.join(profile_dir, 'history.sqlite')
362
363 def new_session(self, conn=None):
364 """Get a new session number."""
365 if conn is None:
366 conn = self.db
367
368 with conn:
369 cur = conn.execute("""INSERT INTO sessions VALUES (NULL, ?, NULL,
370 NULL, "") """, (datetime.datetime.now(),))
371 self.session_number = cur.lastrowid
372
373 def end_session(self):
374 """Close the database session, filling in the end time and line count."""
375 self.writeout_cache()
376 with self.db:
377 self.db.execute("""UPDATE sessions SET end=?, num_cmds=? WHERE
378 session==?""", (datetime.datetime.now(),
379 len(self.input_hist_parsed)-1, self.session_number))
380 self.session_number = 0
381
382 def name_session(self, name):
383 """Give the current session a name in the history database."""
384 with self.db:
385 self.db.execute("UPDATE sessions SET remark=? WHERE session==?",
386 (name, self.session_number))
387
388 def reset(self, new_session=True):
389 """Clear the session history, releasing all object references, and
390 optionally open a new session."""
391 self.output_hist.clear()
392 # The directory history can't be completely empty
393 self.dir_hist[:] = [os.getcwdu()]
394
395 if new_session:
396 if self.session_number:
397 self.end_session()
398 self.input_hist_parsed[:] = [""]
399 self.input_hist_raw[:] = [""]
400 self.new_session()
401
402 # ------------------------------
403 # Methods for retrieving history
404 # ------------------------------
405 def _get_range_session(self, start=1, stop=None, raw=True, output=False):
406 """Get input and output history from the current session. Called by
407 get_range, and takes similar parameters."""
408 input_hist = self.input_hist_raw if raw else self.input_hist_parsed
409
410 n = len(input_hist)
411 if start < 0:
412 start += n
413 if not stop or (stop > n):
414 stop = n
415 elif stop < 0:
416 stop += n
417
418 for i in range(start, stop):
419 if output:
420 line = (input_hist[i], self.output_hist_reprs.get(i))
421 else:
422 line = input_hist[i]
423 yield (0, i, line)
424
425 def get_range(self, session=0, start=1, stop=None, raw=True,output=False):
426 """Retrieve input by session.
427
428 Parameters
429 ----------
430 session : int
431 Session number to retrieve. The current session is 0, and negative
432 numbers count back from current session, so -1 is previous session.
433 start : int
434 First line to retrieve.
435 stop : int
436 End of line range (excluded from output itself). If None, retrieve
437 to the end of the session.
438 raw : bool
439 If True, return untranslated input
440 output : bool
441 If True, attempt to include output. This will be 'real' Python
442 objects for the current session, or text reprs from previous
443 sessions if db_log_output was enabled at the time. Where no output
444 is found, None is used.
445
446 Returns
447 -------
448 An iterator over the desired lines. Each line is a 3-tuple, either
449 (session, line, input) if output is False, or
450 (session, line, (input, output)) if output is True.
451 """
452 if session <= 0:
453 session += self.session_number
454 if session==self.session_number: # Current session
455 return self._get_range_session(start, stop, raw, output)
456 return super(HistoryManager, self).get_range(session, start, stop, raw, output)
457
384 ## ----------------------------
458 ## ----------------------------
385 ## Methods for storing history:
459 ## Methods for storing history:
386 ## ----------------------------
460 ## ----------------------------
@@ -38,6 +38,12 b' def test_history():'
38 ip.history_manager.store_output(3)
38 ip.history_manager.store_output(3)
39
39
40 nt.assert_equal(ip.history_manager.input_hist_raw, [''] + hist)
40 nt.assert_equal(ip.history_manager.input_hist_raw, [''] + hist)
41
42 # Detailed tests for _get_range_session
43 grs = ip.history_manager._get_range_session
44 nt.assert_equal(list(grs(start=2,stop=-1)), zip([0], [2], hist[1:-1]))
45 nt.assert_equal(list(grs(start=-2)), zip([0,0], [2,3], hist[-2:]))
46 nt.assert_equal(list(grs(output=True)), zip([0,0,0], [1,2,3], zip(hist, [None,None,'spam'])))
41
47
42 # Check whether specifying a range beyond the end of the current
48 # Check whether specifying a range beyond the end of the current
43 # session results in an error (gh-804)
49 # session results in an error (gh-804)
@@ -372,6 +372,18 b' def get_ipython_module_path(module_str):'
372 the_path = the_path.replace('.pyo', '.py')
372 the_path = the_path.replace('.pyo', '.py')
373 return py3compat.cast_unicode(the_path, fs_encoding)
373 return py3compat.cast_unicode(the_path, fs_encoding)
374
374
375 def locate_profile(profile='default'):
376 """Find the path to the folder associated with a given profile.
377
378 I.e. find $IPYTHON_DIR/profile_whatever.
379 """
380 from IPython.core.profiledir import ProfileDir, ProfileDirError
381 try:
382 pd = ProfileDir.find_profile_dir_by_name(get_ipython_dir(), profile)
383 except ProfileDirError:
384 # IOError makes more sense when people are expecting a path
385 raise IOError("Couldn't find profile %r" % profile)
386 return pd.location
375
387
376 def expand_path(s):
388 def expand_path(s):
377 """Expand $VARS and ~names in a string, like a shell
389 """Expand $VARS and ~names in a string, like a shell
General Comments 0
You need to be logged in to leave comments. Login now