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