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