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