##// END OF EJS Templates
Flush cache before querying database.
Thomas Kluyver -
Show More
@@ -1,596 +1,598 b''
1 1 """ History related magics and functionality """
2 2 #-----------------------------------------------------------------------------
3 3 # Copyright (C) 2010 The IPython Development Team.
4 4 #
5 5 # Distributed under the terms of the BSD License.
6 6 #
7 7 # The full license is in the file COPYING.txt, distributed with this software.
8 8 #-----------------------------------------------------------------------------
9 9
10 10 #-----------------------------------------------------------------------------
11 11 # Imports
12 12 #-----------------------------------------------------------------------------
13 13 from __future__ import print_function
14 14
15 15 # Stdlib imports
16 16 import os
17 17 import re
18 18 import sqlite3
19 19
20 20 # Our own packages
21 21 from IPython.config.configurable import Configurable
22 22 import IPython.utils.io
23 23
24 24 from IPython.testing import decorators as testdec
25 25 from IPython.utils.io import ask_yes_no
26 26 from IPython.utils.traitlets import Bool, Dict, Instance, Int, List, Unicode
27 27 from IPython.utils.warn import warn
28 28
29 29 #-----------------------------------------------------------------------------
30 30 # Classes and functions
31 31 #-----------------------------------------------------------------------------
32 32
33 33 class HistoryManager(Configurable):
34 34 """A class to organize all history-related functionality in one place.
35 35 """
36 36 # Public interface
37 37
38 38 # An instance of the IPython shell we are attached to
39 39 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC')
40 40 # Lists to hold processed and raw history. These start with a blank entry
41 41 # so that we can index them starting from 1
42 42 input_hist_parsed = List([""])
43 43 input_hist_raw = List([""])
44 44 # A list of directories visited during session
45 45 dir_hist = List()
46 46 # A dict of output history, keyed with ints from the shell's execution count
47 47 output_hist = Dict()
48 48 # String holding the path to the history file
49 49 hist_file = Unicode()
50 50 # The SQLite database
51 51 db = Instance(sqlite3.Connection)
52 52 # The number of the current session in the history database
53 53 session_number = Int()
54 54 # Should we log output to the database? (default no)
55 55 db_log_output = Bool(False, config=True)
56 56 # Write to database every x commands (higher values save disk access & power)
57 57 # Values of 1 or less effectively disable caching.
58 58 db_cache_size = Int(0, config=True)
59 59 # The input and output caches
60 60 db_input_cache = List()
61 61 db_output_cache = List()
62 62
63 63 # Private interface
64 64 # Variables used to store the three last inputs from the user. On each new
65 65 # history update, we populate the user's namespace with these, shifted as
66 66 # necessary.
67 67 _i00, _i, _ii, _iii = '','','',''
68 68
69 69 # A set with all forms of the exit command, so that we don't store them in
70 70 # the history (it's annoying to rewind the first entry and land on an exit
71 71 # call).
72 72 _exit_commands = None
73 73
74 74 def __init__(self, shell, config=None):
75 75 """Create a new history manager associated with a shell instance.
76 76 """
77 77 # We need a pointer back to the shell for various tasks.
78 78 super(HistoryManager, self).__init__(shell=shell, config=config)
79 79
80 80 # list of visited directories
81 81 try:
82 82 self.dir_hist = [os.getcwd()]
83 83 except OSError:
84 84 self.dir_hist = []
85 85
86 86 # Now the history file
87 87 if shell.profile:
88 88 histfname = 'history-%s' % shell.profile
89 89 else:
90 90 histfname = 'history'
91 91 self.hist_file = os.path.join(shell.ipython_dir, histfname + '.sqlite')
92 92 self.init_db()
93 93
94 94 self._i00, self._i, self._ii, self._iii = '','','',''
95 95
96 96 self._exit_commands = set(['Quit', 'quit', 'Exit', 'exit', '%Quit',
97 97 '%quit', '%Exit', '%exit'])
98 98
99 99 def init_db(self):
100 100 """Connect to the database and get new session number."""
101 101 self.db = sqlite3.connect(self.hist_file)
102 102 self.db.execute("""CREATE TABLE IF NOT EXISTS history
103 103 (session integer, line integer, source text, source_raw text,
104 104 PRIMARY KEY (session, line))""")
105 105 # Output history is optional, but ensure the table's there so it can be
106 106 # enabled later.
107 107 self.db.execute("""CREATE TABLE IF NOT EXISTS output_history
108 108 (session integer, line integer, output text,
109 109 PRIMARY KEY (session, line))""")
110 110 cur = self.db.execute("""SELECT name FROM sqlite_master WHERE
111 111 type='table' AND name='singletons'""")
112 112 if not cur.fetchone():
113 113 self.db.execute("""CREATE TABLE singletons
114 114 (name text PRIMARY KEY, value)""")
115 115 self.db.execute("""INSERT INTO singletons VALUES
116 116 ('session_number', 1)""")
117 117 self.db.commit()
118 118 cur = self.db.execute("""SELECT value FROM singletons WHERE
119 119 name='session_number'""")
120 120 self.session_number = cur.fetchone()[0]
121 121
122 122 #Increment by one for next session.
123 123 self.db.execute("""UPDATE singletons SET value=? WHERE
124 124 name='session_number'""", (self.session_number+1,))
125 125 self.db.commit()
126 126
127 127 def _get_hist_sql(self, sql, params, raw=True, output=False):
128 128 """Prepares and runs an SQL query for the history database.
129 129
130 130 Parameters
131 131 ----------
132 132 sql : str
133 133 Any filtering expressions to go after SELECT ... FROM ...
134 134 params : tuple
135 135 Parameters passed to the SQL query (to replace "?")
136 136 raw : bool
137 137 If True, get raw input.
138 138 output : bool
139 139 If True, include output where available.
140 140
141 141 Returns
142 142 -------
143 143 An iterator over 3-tuples: (session, line_number, command), or if output
144 144 is True, (session, line_number, (command, output)).
145 145 """
146 146 toget = 'source_raw' if raw else 'source'
147 147 sqlfrom = "history"
148 148 if output:
149 149 sqlfrom = "history LEFT JOIN output_history USING (session, line)"
150 150 toget = "history.%s, output_history.output" % toget
151 151 cur = self.db.execute("SELECT session, line, %s FROM %s " %\
152 152 (toget, sqlfrom) + sql, params)
153 153 if output: # Regroup into 3-tuples
154 154 return ((ses, lin, (inp, out)) for ses, lin, inp, out in cur)
155 155 return cur
156 156
157 157
158 158 def get_hist_tail(self, n=10, raw=True, output=False):
159 159 """Get the last n lines from the history database."""
160 self.writeout_cache()
160 161 cur = self._get_hist_sql("ORDER BY session DESC, line DESC LIMIT ?",
161 162 (n,), raw=raw, output=output)
162 163 return reversed(list(cur))
163 164
164 165 def get_hist_search(self, pattern="*", raw=True, output=False):
165 166 """Search the database using unix glob-style matching (wildcards * and
166 167 ?, escape using \).
167 168
168 169 Returns
169 170 -------
170 171 An iterator over tuples: (session, line_number, command)
171 172 """
172 173 tosearch = "source_raw" if raw else "source"
173 174 if output:
174 175 tosearch = "history." + tosearch
176 self.writeout_cache()
175 177 return self._get_hist_sql("WHERE %s GLOB ?" % tosearch, (pattern,),
176 178 raw=raw, output=output)
177 179
178 180 def _get_hist_session(self, start=1, stop=None, raw=True, output=False):
179 181 """Get input and output history from the current session. Called by
180 182 get_history, and takes similar parameters."""
181 183 input_hist = self.input_hist_raw if raw else self.input_hist_parsed
182 184
183 185 n = len(input_hist)
184 186 if start < 0:
185 187 start += n
186 188 if not stop:
187 189 stop = n
188 190 elif stop < 0:
189 191 stop += n
190 192
191 193 for i in range(start, stop):
192 194 if output:
193 195 line = (input_hist[i], repr(self.output_hist.get(i)))
194 196 else:
195 197 line = input_hist[i]
196 198 yield (0, i, line)
197 199
198 200 def get_history(self, session=0, start=1, stop=None, raw=True,output=False):
199 201 """Retrieve input by session.
200 202
201 203 Parameters
202 204 ----------
203 205 session : int
204 206 Session number to retrieve. The current session is 0, and negative
205 207 numbers count back from current session, so -1 is previous session.
206 208 start : int
207 209 First line to retrieve.
208 210 stop : int
209 211 End of line range (excluded from output itself). If None, retrieve
210 212 to the end of the session.
211 213 raw : bool
212 214 If True, return untranslated input
213 215 output : bool
214 216 If True, attempt to include output. This will be 'real' Python
215 217 objects for the current session, or text reprs from previous
216 218 sessions if db_log_output was enabled at the time. Where no output
217 219 is found, None is used.
218 220
219 221 Returns
220 222 -------
221 223 An iterator over the desired lines. Each line is a 3-tuple, either
222 224 (session, line, input) if output is False, or
223 225 (session, line, (input, output)) if output is True.
224 226 """
225 227 if session == 0 or session==self.session_number: # Current session
226 228 return self._get_hist_session(start, stop, raw, output)
227 229 if session < 0:
228 230 session += self.session_number
229 231
230 232 if stop:
231 233 lineclause = "line >= ? AND line < ?"
232 234 params = (session, start, stop)
233 235 else:
234 236 lineclause = "line>=?"
235 237 params = (session, start)
236 238
237 239 return self._get_hist_sql("WHERE session==? AND %s""" % lineclause,
238 240 params, raw=raw, output=output)
239 241
240 242 def get_hist_from_rangestr(self, rangestr, raw=True, output=False):
241 243 """Get lines of history from a string of ranges, as used by magic
242 244 commands %hist, %save, %macro, etc."""
243 245 for sess, s, e in extract_hist_ranges(rangestr):
244 246 for line in self.get_history(sess, s, e, raw=raw, output=output):
245 247 yield line
246 248
247 249 def store_inputs(self, line_num, source, source_raw=None):
248 250 """Store source and raw input in history and create input cache
249 251 variables _i*.
250 252
251 253 Parameters
252 254 ----------
253 255 line_num : int
254 256 The prompt number of this input.
255 257
256 258 source : str
257 259 Python input.
258 260
259 261 source_raw : str, optional
260 262 If given, this is the raw input without any IPython transformations
261 263 applied to it. If not given, ``source`` is used.
262 264 """
263 265 if source_raw is None:
264 266 source_raw = source
265 267
266 268 # do not store exit/quit commands
267 269 if source_raw.strip() in self._exit_commands:
268 270 return
269 271
270 272 self.input_hist_parsed.append(source.rstrip())
271 273 self.input_hist_raw.append(source_raw.rstrip())
272 274
273 275 self.db_input_cache.append((self.session_number, line_num,
274 276 source, source_raw))
275 277 # Trigger to flush cache and write to DB.
276 278 if len(self.db_input_cache) >= self.db_cache_size:
277 279 self.writeout_cache()
278 280
279 281 # update the auto _i variables
280 282 self._iii = self._ii
281 283 self._ii = self._i
282 284 self._i = self._i00
283 285 self._i00 = source_raw
284 286
285 287 # hackish access to user namespace to create _i1,_i2... dynamically
286 288 new_i = '_i%s' % line_num
287 289 to_main = {'_i': self._i,
288 290 '_ii': self._ii,
289 291 '_iii': self._iii,
290 292 new_i : self._i00 }
291 293 self.shell.user_ns.update(to_main)
292 294
293 295 def store_output(self, line_num, output):
294 296 if not self.db_log_output:
295 297 return
296 298 db_row = (self.session_number, line_num, output)
297 299 if self.db_cache_size > 1:
298 300 self.db_output_cache.append(db_row)
299 301 else:
300 302 with self.db:
301 303 self.db.execute("INSERT INTO output_history VALUES (?,?,?)", db_row)
302 304
303 305 def writeout_cache(self):
304 306 #print(self.db_input_cache)
305 307 with self.db:
306 308 self.db.executemany("INSERT INTO history VALUES (?, ?, ?, ?)",
307 309 self.db_input_cache)
308 310 self.db.executemany("INSERT INTO output_history VALUES (?, ?, ?)",
309 311 self.db_output_cache)
310 312 self.db_input_cache = []
311 313 self.db_output_cache = []
312 314
313 315 def sync_inputs(self):
314 316 """Ensure raw and translated histories have same length."""
315 317 lr = len(self.input_hist_raw)
316 318 lp = len(self.input_hist_parsed)
317 319 if lp < lr:
318 320 self.input_hist_raw[:lr-lp] = []
319 321 elif lr < lp:
320 322 self.input_hist_parsed[:lp-lr] = []
321 323
322 324 def reset(self, new_session=True):
323 325 """Clear the current session's history, and (optionally) start a new
324 326 session."""
325 327 self.input_hist_parsed[:] = [""]
326 328 self.input_hist_raw[:] = [""]
327 329 self.output_hist.clear()
328 330 # The directory history can't be completely empty
329 331 self.dir_hist[:] = [os.getcwd()]
330 332
331 333 if new_session:
332 334 self.writeout_cache()
333 335 self.init_db() # Get new session number
334 336
335 337 # To match, e.g. ~5/8-~2/3
336 338 range_re = re.compile(r"""
337 339 ((?P<startsess>~?\d+)/)?
338 340 (?P<start>\d+) # Only the start line num is compulsory
339 341 ((?P<sep>[\-:])
340 342 ((?P<endsess>~?\d+)/)?
341 343 (?P<end>\d+))?
342 344 """, re.VERBOSE)
343 345
344 346 def extract_hist_ranges(ranges_str):
345 347 """Turn a string of history ranges into 3-tuples of (session, start, stop).
346 348
347 349 Examples
348 350 --------
349 351 list(extract_input_ranges("~8/5-~7/4 2"))
350 352 [(-8, 5, None), (-7, 1, 4), (0, 2, 3)]
351 353 """
352 354 for range_str in ranges_str.split():
353 355 rmatch = range_re.match(range_str)
354 356 start = int(rmatch.group("start"))
355 357 end = rmatch.group("end")
356 358 end = int(end) if end else start+1 # If no end specified, get (a, a+1)
357 359 if rmatch.group("sep") == "-": # 1-3 == 1:4 --> [1, 2, 3]
358 360 end += 1
359 361 startsess = rmatch.group("startsess") or "0"
360 362 endsess = rmatch.group("endsess") or startsess
361 363 startsess = int(startsess.replace("~","-"))
362 364 endsess = int(endsess.replace("~","-"))
363 365 assert endsess >= startsess
364 366
365 367 if endsess == startsess:
366 368 yield (startsess, start, end)
367 369 continue
368 370 # Multiple sessions in one range:
369 371 yield (startsess, start, None)
370 372 for sess in range(startsess+1, endsess):
371 373 yield (sess, 1, None)
372 374 yield (endsess, 1, end)
373 375
374 376 def _format_lineno(session, line):
375 377 """Helper function to format line numbers properly."""
376 378 if session == 0:
377 379 return str(line)
378 380 return "%s#%s" % (session, line)
379 381
380 382 @testdec.skip_doctest
381 383 def magic_history(self, parameter_s = ''):
382 384 """Print input history (_i<n> variables), with most recent last.
383 385
384 386 %history -> print at most 40 inputs (some may be multi-line)\\
385 387 %history n -> print at most n inputs\\
386 388 %history n1 n2 -> print inputs between n1 and n2 (n2 not included)\\
387 389
388 390 By default, input history is printed without line numbers so it can be
389 391 directly pasted into an editor.
390 392
391 393 With -n, each input's number <n> is shown, and is accessible as the
392 394 automatically generated variable _i<n> as well as In[<n>]. Multi-line
393 395 statements are printed starting at a new line for easy copy/paste.
394 396
395 397 Options:
396 398
397 399 -n: print line numbers for each input.
398 400 This feature is only available if numbered prompts are in use.
399 401
400 402 -o: also print outputs for each input.
401 403
402 404 -p: print classic '>>>' python prompts before each input. This is useful
403 405 for making documentation, and in conjunction with -o, for producing
404 406 doctest-ready output.
405 407
406 408 -r: (default) print the 'raw' history, i.e. the actual commands you typed.
407 409
408 410 -t: print the 'translated' history, as IPython understands it. IPython
409 411 filters your input and converts it all into valid Python source before
410 412 executing it (things like magics or aliases are turned into function
411 413 calls, for example). With this option, you'll see the native history
412 414 instead of the user-entered version: '%cd /' will be seen as
413 415 'get_ipython().magic("%cd /")' instead of '%cd /'.
414 416
415 417 -g: treat the arg as a pattern to grep for in (full) history.
416 418 This includes the saved history (almost all commands ever written).
417 419 Use '%hist -g' to show full saved history (may be very long).
418 420
419 421 -l: get the last n lines from all sessions. Specify n as a single arg, or
420 422 the default is the last 10 lines.
421 423
422 424 -f FILENAME: instead of printing the output to the screen, redirect it to
423 425 the given file. The file is always overwritten, though IPython asks for
424 426 confirmation first if it already exists.
425 427
426 428 Examples
427 429 --------
428 430 ::
429 431
430 432 In [6]: %hist -n 4 6
431 433 4:a = 12
432 434 5:print a**2
433 435
434 436 """
435 437
436 438 if not self.shell.displayhook.do_full_cache:
437 439 print('This feature is only available if numbered prompts are in use.')
438 440 return
439 441 opts,args = self.parse_options(parameter_s,'noprtglf:',mode='string')
440 442
441 443 # For brevity
442 444 history_manager = self.shell.history_manager
443 445
444 446 def _format_lineno(session, line):
445 447 """Helper function to format line numbers properly."""
446 448 if session in (0, history_manager.session_number):
447 449 return str(line)
448 450 return "%s/%s" % (session, line)
449 451
450 452 # Check if output to specific file was requested.
451 453 try:
452 454 outfname = opts['f']
453 455 except KeyError:
454 456 outfile = IPython.utils.io.Term.cout # default
455 457 # We don't want to close stdout at the end!
456 458 close_at_end = False
457 459 else:
458 460 if os.path.exists(outfname):
459 461 if not ask_yes_no("File %r exists. Overwrite?" % outfname):
460 462 print('Aborting.')
461 463 return
462 464
463 465 outfile = open(outfname,'w')
464 466 close_at_end = True
465 467
466 468 print_nums = 'n' in opts
467 469 get_output = 'o' in opts
468 470 pyprompts = 'p' in opts
469 471 # Raw history is the default
470 472 raw = not('t' in opts)
471 473
472 474 default_length = 40
473 475 pattern = None
474 476
475 477 if 'g' in opts: # Glob search
476 478 pattern = "*" + args + "*" if args else "*"
477 479 hist = history_manager.get_hist_search(pattern, raw=raw,
478 480 output=get_output)
479 481 elif 'l' in opts: # Get 'tail'
480 482 try:
481 483 n = int(args)
482 484 except ValueError, IndexError:
483 485 n = 10
484 486 hist = history_manager.get_hist_tail(n, raw=raw, output=get_output)
485 487 else:
486 488 if args: # Get history by ranges
487 489 hist = history_manager.get_hist_from_rangestr(args, raw, get_output)
488 490 else: # Just get history for the current session
489 491 hist = history_manager.get_history(raw=raw, output=get_output)
490 492
491 493 # We could be displaying the entire history, so let's not try to pull it
492 494 # into a list in memory. Anything that needs more space will just misalign.
493 495 width = 4
494 496
495 497 for session, lineno, inline in hist:
496 498 # Print user history with tabs expanded to 4 spaces. The GUI clients
497 499 # use hard tabs for easier usability in auto-indented code, but we want
498 500 # to produce PEP-8 compliant history for safe pasting into an editor.
499 501 if get_output:
500 502 inline, output = inline
501 503 inline = inline.expandtabs(4).rstrip()
502 504
503 505 multiline = "\n" in inline
504 506 line_sep = '\n' if multiline else ' '
505 507 if print_nums:
506 508 print('%s:%s' % (_format_lineno(session, lineno).rjust(width),
507 509 line_sep), file=outfile, end='')
508 510 if pyprompts:
509 511 print(">>> ", end="", file=outfile)
510 512 if multiline:
511 513 inline = "\n... ".join(inline.splitlines()) + "\n..."
512 514 print(inline, file=outfile)
513 515 if get_output and output:
514 516 print(output, file=outfile)
515 517
516 518 if close_at_end:
517 519 outfile.close()
518 520
519 521 # %hist is an alternative name
520 522 magic_hist = magic_history
521 523
522 524
523 525 def rep_f(self, arg):
524 526 r""" Repeat a command, or get command to input line for editing
525 527
526 528 - %rep (no arguments):
527 529
528 530 Place a string version of last computation result (stored in the special '_'
529 531 variable) to the next input prompt. Allows you to create elaborate command
530 532 lines without using copy-paste::
531 533
532 534 $ l = ["hei", "vaan"]
533 535 $ "".join(l)
534 536 ==> heivaan
535 537 $ %rep
536 538 $ heivaan_ <== cursor blinking
537 539
538 540 %rep 45
539 541
540 542 Place history line 45 to next input prompt. Use %hist to find out the
541 543 number.
542 544
543 545 %rep 1-4 6-7 3
544 546
545 547 Repeat the specified lines immediately. Input slice syntax is the same as
546 548 in %macro and %save.
547 549
548 550 %rep foo
549 551
550 552 Place the most recent line that has the substring "foo" to next input.
551 553 (e.g. 'svn ci -m foobar').
552 554 """
553 555
554 556 opts,args = self.parse_options(arg,'',mode='list')
555 557 if not args:
556 558 self.set_next_input(str(self.shell.user_ns["_"]))
557 559 return
558 560
559 561 if len(args) == 1 and not '-' in args[0]:
560 562 arg = args[0]
561 563 if len(arg) > 1 and arg.startswith('0'):
562 564 # get from shadow hist
563 565 num = int(arg[1:])
564 566 line = self.shell.shadowhist.get(num)
565 567 self.set_next_input(str(line))
566 568 return
567 569 try:
568 570 num = int(args[0])
569 571 self.set_next_input(str(self.shell.input_hist_raw[num]).rstrip())
570 572 return
571 573 except ValueError:
572 574 pass
573 575
574 576 for h in reversed(self.shell.input_hist_raw):
575 577 if 'rep' in h:
576 578 continue
577 579 if fnmatch.fnmatch(h,'*' + arg + '*'):
578 580 self.set_next_input(str(h).rstrip())
579 581 return
580 582
581 583 try:
582 584 lines = self.extract_input_slices(args, True)
583 585 print("lines", lines)
584 586 self.run_cell(lines)
585 587 except ValueError:
586 588 print("Not found in recent history:", args)
587 589
588 590
589 591 def init_ipython(ip):
590 592 ip.define_magic("rep",rep_f)
591 593 ip.define_magic("hist",magic_hist)
592 594 ip.define_magic("history",magic_history)
593 595
594 596 # XXX - ipy_completers are in quarantine, need to be updated to new apis
595 597 #import ipy_completers
596 598 #ipy_completers.quick_completer('%hist' ,'-g -t -r -n')
General Comments 0
You need to be logged in to leave comments. Login now