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