##// END OF EJS Templates
Fix doctest
Thomas Kluyver -
Show More
@@ -1,854 +1,854 b''
1 1 """ History related magics and functionality """
2 2 #-----------------------------------------------------------------------------
3 3 # Copyright (C) 2010-2011 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 atexit
17 17 import datetime
18 18 import os
19 19 import re
20 20 try:
21 21 import sqlite3
22 22 except ImportError:
23 23 try:
24 24 from pysqlite2 import dbapi2 as sqlite3
25 25 except ImportError:
26 26 sqlite3 = None
27 27 import threading
28 28
29 29 # Our own packages
30 30 from IPython.config.configurable import Configurable
31 31 from IPython.external.decorator import decorator
32 32 from IPython.utils.decorators import undoc
33 33 from IPython.utils.path import locate_profile
34 34 from IPython.utils import py3compat
35 35 from IPython.utils.traitlets import (
36 36 Any, Bool, Dict, Instance, Integer, List, Unicode, TraitError,
37 37 )
38 38 from IPython.utils.warn import warn
39 39
40 40 #-----------------------------------------------------------------------------
41 41 # Classes and functions
42 42 #-----------------------------------------------------------------------------
43 43
44 44 @undoc
45 45 class DummyDB(object):
46 46 """Dummy DB that will act as a black hole for history.
47 47
48 48 Only used in the absence of sqlite"""
49 49 def execute(*args, **kwargs):
50 50 return []
51 51
52 52 def commit(self, *args, **kwargs):
53 53 pass
54 54
55 55 def __enter__(self, *args, **kwargs):
56 56 pass
57 57
58 58 def __exit__(self, *args, **kwargs):
59 59 pass
60 60
61 61
62 62 @decorator
63 63 def needs_sqlite(f, self, *a, **kw):
64 64 """Decorator: return an empty list in the absence of sqlite."""
65 65 if sqlite3 is None or not self.enabled:
66 66 return []
67 67 else:
68 68 return f(self, *a, **kw)
69 69
70 70
71 71 if sqlite3 is not None:
72 72 DatabaseError = sqlite3.DatabaseError
73 73 else:
74 74 @undoc
75 75 class DatabaseError(Exception):
76 76 "Dummy exception when sqlite could not be imported. Should never occur."
77 77
78 78 @decorator
79 79 def catch_corrupt_db(f, self, *a, **kw):
80 80 """A decorator which wraps HistoryAccessor method calls to catch errors from
81 81 a corrupt SQLite database, move the old database out of the way, and create
82 82 a new one.
83 83 """
84 84 try:
85 85 return f(self, *a, **kw)
86 86 except DatabaseError:
87 87 if os.path.isfile(self.hist_file):
88 88 # Try to move the file out of the way
89 89 base,ext = os.path.splitext(self.hist_file)
90 90 newpath = base + '-corrupt' + ext
91 91 os.rename(self.hist_file, newpath)
92 92 self.init_db()
93 93 print("ERROR! History file wasn't a valid SQLite database.",
94 94 "It was moved to %s" % newpath, "and a new file created.")
95 95 return []
96 96
97 97 else:
98 98 # The hist_file is probably :memory: or something else.
99 99 raise
100 100
101 101
102 102
103 103 class HistoryAccessor(Configurable):
104 104 """Access the history database without adding to it.
105 105
106 106 This is intended for use by standalone history tools. IPython shells use
107 107 HistoryManager, below, which is a subclass of this."""
108 108
109 109 # String holding the path to the history file
110 110 hist_file = Unicode(config=True,
111 111 help="""Path to file to use for SQLite history database.
112 112
113 113 By default, IPython will put the history database in the IPython
114 114 profile directory. If you would rather share one history among
115 115 profiles, you can set this value in each, so that they are consistent.
116 116
117 117 Due to an issue with fcntl, SQLite is known to misbehave on some NFS
118 118 mounts. If you see IPython hanging, try setting this to something on a
119 119 local disk, e.g::
120 120
121 121 ipython --HistoryManager.hist_file=/tmp/ipython_hist.sqlite
122 122
123 123 """)
124 124
125 125 enabled = Bool(True, config=True,
126 126 help="""enable the SQLite history
127 127
128 128 set enabled=False to disable the SQLite history,
129 129 in which case there will be no stored history, no SQLite connection,
130 130 and no background saving thread. This may be necessary in some
131 131 threaded environments where IPython is embedded.
132 132 """
133 133 )
134 134
135 135 connection_options = Dict(config=True,
136 136 help="""Options for configuring the SQLite connection
137 137
138 138 These options are passed as keyword args to sqlite3.connect
139 139 when establishing database conenctions.
140 140 """
141 141 )
142 142
143 143 # The SQLite database
144 144 db = Any()
145 145 def _db_changed(self, name, old, new):
146 146 """validate the db, since it can be an Instance of two different types"""
147 147 connection_types = (DummyDB,)
148 148 if sqlite3 is not None:
149 149 connection_types = (DummyDB, sqlite3.Connection)
150 150 if not isinstance(new, connection_types):
151 151 msg = "%s.db must be sqlite3 Connection or DummyDB, not %r" % \
152 152 (self.__class__.__name__, new)
153 153 raise TraitError(msg)
154 154
155 155 def __init__(self, profile='default', hist_file=u'', **traits):
156 156 """Create a new history accessor.
157 157
158 158 Parameters
159 159 ----------
160 160 profile : str
161 161 The name of the profile from which to open history.
162 162 hist_file : str
163 163 Path to an SQLite history database stored by IPython. If specified,
164 164 hist_file overrides profile.
165 165 config : :class:`~IPython.config.loader.Config`
166 166 Config object. hist_file can also be set through this.
167 167 """
168 168 # We need a pointer back to the shell for various tasks.
169 169 super(HistoryAccessor, self).__init__(**traits)
170 170 # defer setting hist_file from kwarg until after init,
171 171 # otherwise the default kwarg value would clobber any value
172 172 # set by config
173 173 if hist_file:
174 174 self.hist_file = hist_file
175 175
176 176 if self.hist_file == u'':
177 177 # No one has set the hist_file, yet.
178 178 self.hist_file = self._get_hist_file_name(profile)
179 179
180 180 if sqlite3 is None and self.enabled:
181 181 warn("IPython History requires SQLite, your history will not be saved")
182 182 self.enabled = False
183 183
184 184 self.init_db()
185 185
186 186 def _get_hist_file_name(self, profile='default'):
187 187 """Find the history file for the given profile name.
188 188
189 189 This is overridden by the HistoryManager subclass, to use the shell's
190 190 active profile.
191 191
192 192 Parameters
193 193 ----------
194 194 profile : str
195 195 The name of a profile which has a history file.
196 196 """
197 197 return os.path.join(locate_profile(profile), 'history.sqlite')
198 198
199 199 @catch_corrupt_db
200 200 def init_db(self):
201 201 """Connect to the database, and create tables if necessary."""
202 202 if not self.enabled:
203 203 self.db = DummyDB()
204 204 return
205 205
206 206 # use detect_types so that timestamps return datetime objects
207 207 kwargs = dict(detect_types=sqlite3.PARSE_DECLTYPES|sqlite3.PARSE_COLNAMES)
208 208 kwargs.update(self.connection_options)
209 209 self.db = sqlite3.connect(self.hist_file, **kwargs)
210 210 self.db.execute("""CREATE TABLE IF NOT EXISTS sessions (session integer
211 211 primary key autoincrement, start timestamp,
212 212 end timestamp, num_cmds integer, remark text)""")
213 213 self.db.execute("""CREATE TABLE IF NOT EXISTS history
214 214 (session integer, line integer, source text, source_raw text,
215 215 PRIMARY KEY (session, line))""")
216 216 # Output history is optional, but ensure the table's there so it can be
217 217 # enabled later.
218 218 self.db.execute("""CREATE TABLE IF NOT EXISTS output_history
219 219 (session integer, line integer, output text,
220 220 PRIMARY KEY (session, line))""")
221 221 self.db.commit()
222 222
223 223 def writeout_cache(self):
224 224 """Overridden by HistoryManager to dump the cache before certain
225 225 database lookups."""
226 226 pass
227 227
228 228 ## -------------------------------
229 229 ## Methods for retrieving history:
230 230 ## -------------------------------
231 231 def _run_sql(self, sql, params, raw=True, output=False):
232 232 """Prepares and runs an SQL query for the history database.
233 233
234 234 Parameters
235 235 ----------
236 236 sql : str
237 237 Any filtering expressions to go after SELECT ... FROM ...
238 238 params : tuple
239 239 Parameters passed to the SQL query (to replace "?")
240 240 raw, output : bool
241 241 See :meth:`get_range`
242 242
243 243 Returns
244 244 -------
245 245 Tuples as :meth:`get_range`
246 246 """
247 247 toget = 'source_raw' if raw else 'source'
248 248 sqlfrom = "history"
249 249 if output:
250 250 sqlfrom = "history LEFT JOIN output_history USING (session, line)"
251 251 toget = "history.%s, output_history.output" % toget
252 252 cur = self.db.execute("SELECT session, line, %s FROM %s " %\
253 253 (toget, sqlfrom) + sql, params)
254 254 if output: # Regroup into 3-tuples, and parse JSON
255 255 return ((ses, lin, (inp, out)) for ses, lin, inp, out in cur)
256 256 return cur
257 257
258 258 @needs_sqlite
259 259 @catch_corrupt_db
260 260 def get_session_info(self, session):
261 261 """Get info about a session.
262 262
263 263 Parameters
264 264 ----------
265 265
266 266 session : int
267 267 Session number to retrieve.
268 268
269 269 Returns
270 270 -------
271 271
272 272 session_id : int
273 273 Session ID number
274 274 start : datetime
275 275 Timestamp for the start of the session.
276 276 end : datetime
277 277 Timestamp for the end of the session, or None if IPython crashed.
278 278 num_cmds : int
279 279 Number of commands run, or None if IPython crashed.
280 280 remark : unicode
281 281 A manually set description.
282 282 """
283 283 query = "SELECT * from sessions where session == ?"
284 284 return self.db.execute(query, (session,)).fetchone()
285 285
286 286 @catch_corrupt_db
287 287 def get_last_session_id(self):
288 288 """Get the last session ID currently in the database.
289 289
290 290 Within IPython, this should be the same as the value stored in
291 291 :attr:`HistoryManager.session_number`.
292 292 """
293 293 for record in self.get_tail(n=1, include_latest=True):
294 294 return record[0]
295 295
296 296 @catch_corrupt_db
297 297 def get_tail(self, n=10, raw=True, output=False, include_latest=False):
298 298 """Get the last n lines from the history database.
299 299
300 300 Parameters
301 301 ----------
302 302 n : int
303 303 The number of lines to get
304 304 raw, output : bool
305 305 See :meth:`get_range`
306 306 include_latest : bool
307 307 If False (default), n+1 lines are fetched, and the latest one
308 308 is discarded. This is intended to be used where the function
309 309 is called by a user command, which it should not return.
310 310
311 311 Returns
312 312 -------
313 313 Tuples as :meth:`get_range`
314 314 """
315 315 self.writeout_cache()
316 316 if not include_latest:
317 317 n += 1
318 318 cur = self._run_sql("ORDER BY session DESC, line DESC LIMIT ?",
319 319 (n,), raw=raw, output=output)
320 320 if not include_latest:
321 321 return reversed(list(cur)[1:])
322 322 return reversed(list(cur))
323 323
324 324 @catch_corrupt_db
325 325 def search(self, pattern="*", raw=True, search_raw=True,
326 326 output=False, n=None, unique=False):
327 327 """Search the database using unix glob-style matching (wildcards
328 328 * and ?).
329 329
330 330 Parameters
331 331 ----------
332 332 pattern : str
333 333 The wildcarded pattern to match when searching
334 334 search_raw : bool
335 335 If True, search the raw input, otherwise, the parsed input
336 336 raw, output : bool
337 337 See :meth:`get_range`
338 338 n : None or int
339 339 If an integer is given, it defines the limit of
340 340 returned entries.
341 341 unique : bool
342 342 When it is true, return only unique entries.
343 343
344 344 Returns
345 345 -------
346 346 Tuples as :meth:`get_range`
347 347 """
348 348 tosearch = "source_raw" if search_raw else "source"
349 349 if output:
350 350 tosearch = "history." + tosearch
351 351 self.writeout_cache()
352 352 sqlform = "WHERE %s GLOB ?" % tosearch
353 353 params = (pattern,)
354 354 if unique:
355 355 sqlform += ' GROUP BY {0}'.format(tosearch)
356 356 if n is not None:
357 357 sqlform += " ORDER BY session DESC, line DESC LIMIT ?"
358 358 params += (n,)
359 359 elif unique:
360 360 sqlform += " ORDER BY session, line"
361 361 cur = self._run_sql(sqlform, params, raw=raw, output=output)
362 362 if n is not None:
363 363 return reversed(list(cur))
364 364 return cur
365 365
366 366 @catch_corrupt_db
367 367 def get_range(self, session, start=1, stop=None, raw=True,output=False):
368 368 """Retrieve input by session.
369 369
370 370 Parameters
371 371 ----------
372 372 session : int
373 373 Session number to retrieve.
374 374 start : int
375 375 First line to retrieve.
376 376 stop : int
377 377 End of line range (excluded from output itself). If None, retrieve
378 378 to the end of the session.
379 379 raw : bool
380 380 If True, return untranslated input
381 381 output : bool
382 382 If True, attempt to include output. This will be 'real' Python
383 383 objects for the current session, or text reprs from previous
384 384 sessions if db_log_output was enabled at the time. Where no output
385 385 is found, None is used.
386 386
387 387 Returns
388 388 -------
389 389 entries
390 390 An iterator over the desired lines. Each line is a 3-tuple, either
391 391 (session, line, input) if output is False, or
392 392 (session, line, (input, output)) if output is True.
393 393 """
394 394 if stop:
395 395 lineclause = "line >= ? AND line < ?"
396 396 params = (session, start, stop)
397 397 else:
398 398 lineclause = "line>=?"
399 399 params = (session, start)
400 400
401 401 return self._run_sql("WHERE session==? AND %s" % lineclause,
402 402 params, raw=raw, output=output)
403 403
404 404 def get_range_by_str(self, rangestr, raw=True, output=False):
405 405 """Get lines of history from a string of ranges, as used by magic
406 406 commands %hist, %save, %macro, etc.
407 407
408 408 Parameters
409 409 ----------
410 410 rangestr : str
411 411 A string specifying ranges, e.g. "5 ~2/1-4". See
412 412 :func:`magic_history` for full details.
413 413 raw, output : bool
414 414 As :meth:`get_range`
415 415
416 416 Returns
417 417 -------
418 418 Tuples as :meth:`get_range`
419 419 """
420 420 for sess, s, e in extract_hist_ranges(rangestr):
421 421 for line in self.get_range(sess, s, e, raw=raw, output=output):
422 422 yield line
423 423
424 424
425 425 class HistoryManager(HistoryAccessor):
426 426 """A class to organize all history-related functionality in one place.
427 427 """
428 428 # Public interface
429 429
430 430 # An instance of the IPython shell we are attached to
431 431 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC')
432 432 # Lists to hold processed and raw history. These start with a blank entry
433 433 # so that we can index them starting from 1
434 434 input_hist_parsed = List([""])
435 435 input_hist_raw = List([""])
436 436 # A list of directories visited during session
437 437 dir_hist = List()
438 438 def _dir_hist_default(self):
439 439 try:
440 440 return [py3compat.getcwd()]
441 441 except OSError:
442 442 return []
443 443
444 444 # A dict of output history, keyed with ints from the shell's
445 445 # execution count.
446 446 output_hist = Dict()
447 447 # The text/plain repr of outputs.
448 448 output_hist_reprs = Dict()
449 449
450 450 # The number of the current session in the history database
451 451 session_number = Integer()
452 452
453 453 db_log_output = Bool(False, config=True,
454 454 help="Should the history database include output? (default: no)"
455 455 )
456 456 db_cache_size = Integer(0, config=True,
457 457 help="Write to database every x commands (higher values save disk access & power).\n"
458 458 "Values of 1 or less effectively disable caching."
459 459 )
460 460 # The input and output caches
461 461 db_input_cache = List()
462 462 db_output_cache = List()
463 463
464 464 # History saving in separate thread
465 465 save_thread = Instance('IPython.core.history.HistorySavingThread')
466 466 try: # Event is a function returning an instance of _Event...
467 467 save_flag = Instance(threading._Event)
468 468 except AttributeError: # ...until Python 3.3, when it's a class.
469 469 save_flag = Instance(threading.Event)
470 470
471 471 # Private interface
472 472 # Variables used to store the three last inputs from the user. On each new
473 473 # history update, we populate the user's namespace with these, shifted as
474 474 # necessary.
475 475 _i00 = Unicode(u'')
476 476 _i = Unicode(u'')
477 477 _ii = Unicode(u'')
478 478 _iii = Unicode(u'')
479 479
480 480 # A regex matching all forms of the exit command, so that we don't store
481 481 # them in the history (it's annoying to rewind the first entry and land on
482 482 # an exit call).
483 483 _exit_re = re.compile(r"(exit|quit)(\s*\(.*\))?$")
484 484
485 485 def __init__(self, shell=None, config=None, **traits):
486 486 """Create a new history manager associated with a shell instance.
487 487 """
488 488 # We need a pointer back to the shell for various tasks.
489 489 super(HistoryManager, self).__init__(shell=shell, config=config,
490 490 **traits)
491 491 self.save_flag = threading.Event()
492 492 self.db_input_cache_lock = threading.Lock()
493 493 self.db_output_cache_lock = threading.Lock()
494 494 if self.enabled and self.hist_file != ':memory:':
495 495 self.save_thread = HistorySavingThread(self)
496 496 self.save_thread.start()
497 497
498 498 self.new_session()
499 499
500 500 def _get_hist_file_name(self, profile=None):
501 501 """Get default history file name based on the Shell's profile.
502 502
503 503 The profile parameter is ignored, but must exist for compatibility with
504 504 the parent class."""
505 505 profile_dir = self.shell.profile_dir.location
506 506 return os.path.join(profile_dir, 'history.sqlite')
507 507
508 508 @needs_sqlite
509 509 def new_session(self, conn=None):
510 510 """Get a new session number."""
511 511 if conn is None:
512 512 conn = self.db
513 513
514 514 with conn:
515 515 cur = conn.execute("""INSERT INTO sessions VALUES (NULL, ?, NULL,
516 516 NULL, "") """, (datetime.datetime.now(),))
517 517 self.session_number = cur.lastrowid
518 518
519 519 def end_session(self):
520 520 """Close the database session, filling in the end time and line count."""
521 521 self.writeout_cache()
522 522 with self.db:
523 523 self.db.execute("""UPDATE sessions SET end=?, num_cmds=? WHERE
524 524 session==?""", (datetime.datetime.now(),
525 525 len(self.input_hist_parsed)-1, self.session_number))
526 526 self.session_number = 0
527 527
528 528 def name_session(self, name):
529 529 """Give the current session a name in the history database."""
530 530 with self.db:
531 531 self.db.execute("UPDATE sessions SET remark=? WHERE session==?",
532 532 (name, self.session_number))
533 533
534 534 def reset(self, new_session=True):
535 535 """Clear the session history, releasing all object references, and
536 536 optionally open a new session."""
537 537 self.output_hist.clear()
538 538 # The directory history can't be completely empty
539 539 self.dir_hist[:] = [py3compat.getcwd()]
540 540
541 541 if new_session:
542 542 if self.session_number:
543 543 self.end_session()
544 544 self.input_hist_parsed[:] = [""]
545 545 self.input_hist_raw[:] = [""]
546 546 self.new_session()
547 547
548 548 # ------------------------------
549 549 # Methods for retrieving history
550 550 # ------------------------------
551 551 def get_session_info(self, session=0):
552 552 """Get info about a session.
553 553
554 554 Parameters
555 555 ----------
556 556
557 557 session : int
558 558 Session number to retrieve. The current session is 0, and negative
559 559 numbers count back from current session, so -1 is the previous session.
560 560
561 561 Returns
562 562 -------
563 563
564 564 session_id : int
565 565 Session ID number
566 566 start : datetime
567 567 Timestamp for the start of the session.
568 568 end : datetime
569 569 Timestamp for the end of the session, or None if IPython crashed.
570 570 num_cmds : int
571 571 Number of commands run, or None if IPython crashed.
572 572 remark : unicode
573 573 A manually set description.
574 574 """
575 575 if session <= 0:
576 576 session += self.session_number
577 577
578 578 return super(HistoryManager, self).get_session_info(session=session)
579 579
580 580 def _get_range_session(self, start=1, stop=None, raw=True, output=False):
581 581 """Get input and output history from the current session. Called by
582 582 get_range, and takes similar parameters."""
583 583 input_hist = self.input_hist_raw if raw else self.input_hist_parsed
584 584
585 585 n = len(input_hist)
586 586 if start < 0:
587 587 start += n
588 588 if not stop or (stop > n):
589 589 stop = n
590 590 elif stop < 0:
591 591 stop += n
592 592
593 593 for i in range(start, stop):
594 594 if output:
595 595 line = (input_hist[i], self.output_hist_reprs.get(i))
596 596 else:
597 597 line = input_hist[i]
598 598 yield (0, i, line)
599 599
600 600 def get_range(self, session=0, start=1, stop=None, raw=True,output=False):
601 601 """Retrieve input by session.
602 602
603 603 Parameters
604 604 ----------
605 605 session : int
606 606 Session number to retrieve. The current session is 0, and negative
607 607 numbers count back from current session, so -1 is previous session.
608 608 start : int
609 609 First line to retrieve.
610 610 stop : int
611 611 End of line range (excluded from output itself). If None, retrieve
612 612 to the end of the session.
613 613 raw : bool
614 614 If True, return untranslated input
615 615 output : bool
616 616 If True, attempt to include output. This will be 'real' Python
617 617 objects for the current session, or text reprs from previous
618 618 sessions if db_log_output was enabled at the time. Where no output
619 619 is found, None is used.
620 620
621 621 Returns
622 622 -------
623 623 entries
624 624 An iterator over the desired lines. Each line is a 3-tuple, either
625 625 (session, line, input) if output is False, or
626 626 (session, line, (input, output)) if output is True.
627 627 """
628 628 if session <= 0:
629 629 session += self.session_number
630 630 if session==self.session_number: # Current session
631 631 return self._get_range_session(start, stop, raw, output)
632 632 return super(HistoryManager, self).get_range(session, start, stop, raw,
633 633 output)
634 634
635 635 ## ----------------------------
636 636 ## Methods for storing history:
637 637 ## ----------------------------
638 638 def store_inputs(self, line_num, source, source_raw=None):
639 639 """Store source and raw input in history and create input cache
640 640 variables ``_i*``.
641 641
642 642 Parameters
643 643 ----------
644 644 line_num : int
645 645 The prompt number of this input.
646 646
647 647 source : str
648 648 Python input.
649 649
650 650 source_raw : str, optional
651 651 If given, this is the raw input without any IPython transformations
652 652 applied to it. If not given, ``source`` is used.
653 653 """
654 654 if source_raw is None:
655 655 source_raw = source
656 656 source = source.rstrip('\n')
657 657 source_raw = source_raw.rstrip('\n')
658 658
659 659 # do not store exit/quit commands
660 660 if self._exit_re.match(source_raw.strip()):
661 661 return
662 662
663 663 self.input_hist_parsed.append(source)
664 664 self.input_hist_raw.append(source_raw)
665 665
666 666 with self.db_input_cache_lock:
667 667 self.db_input_cache.append((line_num, source, source_raw))
668 668 # Trigger to flush cache and write to DB.
669 669 if len(self.db_input_cache) >= self.db_cache_size:
670 670 self.save_flag.set()
671 671
672 672 # update the auto _i variables
673 673 self._iii = self._ii
674 674 self._ii = self._i
675 675 self._i = self._i00
676 676 self._i00 = source_raw
677 677
678 678 # hackish access to user namespace to create _i1,_i2... dynamically
679 679 new_i = '_i%s' % line_num
680 680 to_main = {'_i': self._i,
681 681 '_ii': self._ii,
682 682 '_iii': self._iii,
683 683 new_i : self._i00 }
684 684
685 685 if self.shell is not None:
686 686 self.shell.push(to_main, interactive=False)
687 687
688 688 def store_output(self, line_num):
689 689 """If database output logging is enabled, this saves all the
690 690 outputs from the indicated prompt number to the database. It's
691 691 called by run_cell after code has been executed.
692 692
693 693 Parameters
694 694 ----------
695 695 line_num : int
696 696 The line number from which to save outputs
697 697 """
698 698 if (not self.db_log_output) or (line_num not in self.output_hist_reprs):
699 699 return
700 700 output = self.output_hist_reprs[line_num]
701 701
702 702 with self.db_output_cache_lock:
703 703 self.db_output_cache.append((line_num, output))
704 704 if self.db_cache_size <= 1:
705 705 self.save_flag.set()
706 706
707 707 def _writeout_input_cache(self, conn):
708 708 with conn:
709 709 for line in self.db_input_cache:
710 710 conn.execute("INSERT INTO history VALUES (?, ?, ?, ?)",
711 711 (self.session_number,)+line)
712 712
713 713 def _writeout_output_cache(self, conn):
714 714 with conn:
715 715 for line in self.db_output_cache:
716 716 conn.execute("INSERT INTO output_history VALUES (?, ?, ?)",
717 717 (self.session_number,)+line)
718 718
719 719 @needs_sqlite
720 720 def writeout_cache(self, conn=None):
721 721 """Write any entries in the cache to the database."""
722 722 if conn is None:
723 723 conn = self.db
724 724
725 725 with self.db_input_cache_lock:
726 726 try:
727 727 self._writeout_input_cache(conn)
728 728 except sqlite3.IntegrityError:
729 729 self.new_session(conn)
730 730 print("ERROR! Session/line number was not unique in",
731 731 "database. History logging moved to new session",
732 732 self.session_number)
733 733 try:
734 734 # Try writing to the new session. If this fails, don't
735 735 # recurse
736 736 self._writeout_input_cache(conn)
737 737 except sqlite3.IntegrityError:
738 738 pass
739 739 finally:
740 740 self.db_input_cache = []
741 741
742 742 with self.db_output_cache_lock:
743 743 try:
744 744 self._writeout_output_cache(conn)
745 745 except sqlite3.IntegrityError:
746 746 print("!! Session/line number for output was not unique",
747 747 "in database. Output will not be stored.")
748 748 finally:
749 749 self.db_output_cache = []
750 750
751 751
752 752 class HistorySavingThread(threading.Thread):
753 753 """This thread takes care of writing history to the database, so that
754 754 the UI isn't held up while that happens.
755 755
756 756 It waits for the HistoryManager's save_flag to be set, then writes out
757 757 the history cache. The main thread is responsible for setting the flag when
758 758 the cache size reaches a defined threshold."""
759 759 daemon = True
760 760 stop_now = False
761 761 enabled = True
762 762 def __init__(self, history_manager):
763 763 super(HistorySavingThread, self).__init__(name="IPythonHistorySavingThread")
764 764 self.history_manager = history_manager
765 765 self.enabled = history_manager.enabled
766 766 atexit.register(self.stop)
767 767
768 768 @needs_sqlite
769 769 def run(self):
770 770 # We need a separate db connection per thread:
771 771 try:
772 772 self.db = sqlite3.connect(self.history_manager.hist_file,
773 773 **self.history_manager.connection_options
774 774 )
775 775 while True:
776 776 self.history_manager.save_flag.wait()
777 777 if self.stop_now:
778 778 return
779 779 self.history_manager.save_flag.clear()
780 780 self.history_manager.writeout_cache(self.db)
781 781 except Exception as e:
782 782 print(("The history saving thread hit an unexpected error (%s)."
783 783 "History will not be written to the database.") % repr(e))
784 784
785 785 def stop(self):
786 786 """This can be called from the main thread to safely stop this thread.
787 787
788 788 Note that it does not attempt to write out remaining history before
789 789 exiting. That should be done by calling the HistoryManager's
790 790 end_session method."""
791 791 self.stop_now = True
792 792 self.history_manager.save_flag.set()
793 793 self.join()
794 794
795 795
796 796 # To match, e.g. ~5/8-~2/3
797 797 range_re = re.compile(r"""
798 798 ((?P<startsess>~?\d+)/)?
799 799 (?P<start>\d+)?
800 800 ((?P<sep>[\-:])
801 801 ((?P<endsess>~?\d+)/)?
802 802 (?P<end>\d+))?
803 803 $""", re.VERBOSE)
804 804
805 805
806 806 def extract_hist_ranges(ranges_str):
807 807 """Turn a string of history ranges into 3-tuples of (session, start, stop).
808 808
809 809 Examples
810 810 --------
811 >>> list(extract_input_ranges("~8/5-~7/4 2"))
812 [(-8, 5, None), (-7, 1, 4), (0, 2, 3)]
811 >>> list(extract_hist_ranges("~8/5-~7/4 2"))
812 [(-8, 5, None), (-7, 1, 5), (0, 2, 3)]
813 813 """
814 814 for range_str in ranges_str.split():
815 815 rmatch = range_re.match(range_str)
816 816 if not rmatch:
817 817 continue
818 818 start = rmatch.group("start")
819 819 if start:
820 820 start = int(start)
821 821 end = rmatch.group("end")
822 822 # If no end specified, get (a, a + 1)
823 823 end = int(end) if end else start + 1
824 824 else: # start not specified
825 825 if not rmatch.group('startsess'): # no startsess
826 826 continue
827 827 start = 1
828 828 end = None # provide the entire session hist
829 829
830 830 if rmatch.group("sep") == "-": # 1-3 == 1:4 --> [1, 2, 3]
831 831 end += 1
832 832 startsess = rmatch.group("startsess") or "0"
833 833 endsess = rmatch.group("endsess") or startsess
834 834 startsess = int(startsess.replace("~","-"))
835 835 endsess = int(endsess.replace("~","-"))
836 836 assert endsess >= startsess, "start session must be earlier than end session"
837 837
838 838 if endsess == startsess:
839 839 yield (startsess, start, end)
840 840 continue
841 841 # Multiple sessions in one range:
842 842 yield (startsess, start, None)
843 843 for sess in range(startsess+1, endsess):
844 844 yield (sess, 1, None)
845 845 yield (endsess, 1, end)
846 846
847 847
848 848 def _format_lineno(session, line):
849 849 """Helper function to format line numbers properly."""
850 850 if session == 0:
851 851 return str(line)
852 852 return "%s#%s" % (session, line)
853 853
854 854
General Comments 0
You need to be logged in to leave comments. Login now