##// 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 26 from IPython.testing.skipdoctest import skip_doctest
27 27 from IPython.utils import io
28 from IPython.utils.path import locate_profile
28 29 from IPython.utils.traitlets import Bool, Dict, Instance, Int, CInt, List, Unicode
29 30 from IPython.utils.warn import warn
30 31
@@ -32,79 +33,39 b' from IPython.utils.warn import warn'
32 33 # Classes and functions
33 34 #-----------------------------------------------------------------------------
34 35
35 class HistoryManager(Configurable):
36 """A class to organize all history-related functionality in one place.
37 """
38 # Public interface
39
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
36 class HistoryAccessor(Configurable):
37 """Access the history database without adding to it.
38
39 This is intended for use by standalone history tools. IPython shells use
40 HistoryManager, below, which is a subclass of this."""
60 41 # String holding the path to the history file
61 42 hist_file = Unicode(config=True)
62 43
63 44 # The SQLite database
64 45 db = Instance(sqlite3.Connection)
65 # The number of the current session in the history database
66 session_number = CInt()
67 # Should we log output to the database? (default no)
68 db_log_output = Bool(False, config=True)
69 # Write to database every x commands (higher values save disk access & power)
70 # Values of 1 or less effectively disable caching.
71 db_cache_size = Int(0, config=True)
72 # The input and output caches
73 db_input_cache = List()
74 db_output_cache = List()
75
76 # History saving in separate thread
77 save_thread = Instance('IPython.core.history.HistorySavingThread')
78 try: # Event is a function returning an instance of _Event...
79 save_flag = Instance(threading._Event)
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.
46
47 def __init__(self, profile='default', hist_file=u'', shell=None, config=None, **traits):
48 """Create a new history accessor.
49
50 Parameters
51 ----------
52 profile : str
53 The name of the profile from which to open history.
54 hist_file : str
55 Path to an SQLite history database stored by IPython. If specified,
56 hist_file overrides profile.
57 shell :
58 InteractiveShell object, for use by HistoryManager subclass
59 config :
60 Config object. hist_file can also be set through this.
99 61 """
100 62 # We need a pointer back to the shell for various tasks.
101 super(HistoryManager, self).__init__(shell=shell, config=config,
102 **traits)
63 super(HistoryAccessor, self).__init__(shell=shell, config=config,
64 hist_file=hist_file, **traits)
103 65
104 66 if self.hist_file == u'':
105 67 # No one has set the hist_file, yet.
106 histfname = 'history'
107 self.hist_file = os.path.join(shell.profile_dir.location, histfname + '.sqlite')
68 self.hist_file = self._get_hist_file_name(profile)
108 69
109 70 try:
110 71 self.init_db()
@@ -119,16 +80,20 b' class HistoryManager(Configurable):'
119 80 else:
120 81 # The hist_file is probably :memory: or something else.
121 82 raise
122
123 self.save_flag = threading.Event()
124 self.db_input_cache_lock = threading.Lock()
125 self.db_output_cache_lock = threading.Lock()
126 self.save_thread = HistorySavingThread(self)
127 self.save_thread.start()
128
129 self.new_session()
130
131
83
84 def _get_hist_file_name(self, profile='default'):
85 """Find the history file for the given profile name.
86
87 This is overridden by the HistoryManager subclass, to use the shell's
88 active profile.
89
90 Parameters
91 ----------
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 97 def init_db(self):
133 98 """Connect to the database, and create tables if necessary."""
134 99 # use detect_types so that timestamps return datetime objects
@@ -146,48 +111,10 b' class HistoryManager(Configurable):'
146 111 PRIMARY KEY (session, line))""")
147 112 self.db.commit()
148 113
149 def new_session(self, conn=None):
150 """Get a new session number."""
151 if conn is None:
152 conn = self.db
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()
114 def writeout_cache(self):
115 """Overridden by HistoryManager to dump the cache before certain
116 database lookups."""
117 pass
191 118
192 119 ## -------------------------------
193 120 ## Methods for retrieving history:
@@ -219,7 +146,6 b' class HistoryManager(Configurable):'
219 146 return ((ses, lin, (inp, out)) for ses, lin, inp, out in cur)
220 147 return cur
221 148
222
223 149 def get_session_info(self, session=0):
224 150 """get info about a session
225 151
@@ -246,7 +172,6 b' class HistoryManager(Configurable):'
246 172 query = "SELECT * from sessions where session == ?"
247 173 return self.db.execute(query, (session,)).fetchone()
248 174
249
250 175 def get_tail(self, n=10, raw=True, output=False, include_latest=False):
251 176 """Get the last n lines from the history database.
252 177
@@ -298,35 +223,14 b' class HistoryManager(Configurable):'
298 223 self.writeout_cache()
299 224 return self._run_sql("WHERE %s GLOB ?" % tosearch, (pattern,),
300 225 raw=raw, output=output)
301
302 def _get_range_session(self, 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):
226
227 def get_range(self, session, start=1, stop=None, raw=True,output=False):
323 228 """Retrieve input by session.
324 229
325 230 Parameters
326 231 ----------
327 232 session : int
328 Session number to retrieve. The current session is 0, and negative
329 numbers count back from current session, so -1 is previous session.
233 Session number to retrieve.
330 234 start : int
331 235 First line to retrieve.
332 236 stop : int
@@ -346,11 +250,6 b' class HistoryManager(Configurable):'
346 250 (session, line, input) if output is False, or
347 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 253 if stop:
355 254 lineclause = "line >= ? AND line < ?"
356 255 params = (session, start, stop)
@@ -381,6 +280,181 b' class HistoryManager(Configurable):'
381 280 for line in self.get_range(sess, s, e, raw=raw, output=output):
382 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 459 ## Methods for storing history:
386 460 ## ----------------------------
@@ -38,6 +38,12 b' def test_history():'
38 38 ip.history_manager.store_output(3)
39 39
40 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 48 # Check whether specifying a range beyond the end of the current
43 49 # session results in an error (gh-804)
@@ -372,6 +372,18 b' def get_ipython_module_path(module_str):'
372 372 the_path = the_path.replace('.pyo', '.py')
373 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 388 def expand_path(s):
377 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