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