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