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