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