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