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