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