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