Format: text/x-python
⬇ Download File (5.7 KB)
   1  # encoding: utf-8
   2  """
   3  An application for managing IPython history.
   4  
   5  To be invoked as the `ipython history` subcommand.
   6  """
   7  
   8  import sqlite3
   9  from pathlib import Path
  10  
  11  from traitlets.config.application import Application
  12  from .application import BaseIPythonApplication
  13  from traitlets import Bool, Int, Dict
  14  from ..utils.io import ask_yes_no
  15  
  16  trim_hist_help = """Trim the IPython history database to the last 1000 entries.
  17  
  18  This actually copies the last 1000 entries to a new database, and then replaces
  19  the old file with the new. Use the `--keep=` argument to specify a number
  20  other than 1000.
  21  """
  22  
  23  clear_hist_help = """Clear the IPython history database, deleting all entries.
  24  
  25  Because this is a destructive operation, IPython will prompt the user if they
  26  really want to do this. Passing a `-f` flag will force clearing without a
  27  prompt.
  28  
  29  This is an handy alias to `ipython history trim --keep=0`
  30  """
  31  
  32  
  33  class HistoryTrim(BaseIPythonApplication):
  34      description = trim_hist_help
  35  
  36      backup = Bool(False, help="Keep the old history file as history.sqlite.<N>").tag(
  37          config=True
  38      )
  39  
  40      keep = Int(1000, help="Number of recent lines to keep in the database.").tag(
  41          config=True
  42      )
  43  
  44      flags = Dict(  # type: ignore
  45          dict(backup=({"HistoryTrim": {"backup": True}}, backup.help))
  46      )
  47  
  48      aliases = Dict(dict(keep="HistoryTrim.keep"))  # type: ignore
  49  
  50      def start(self):
  51          profile_dir = Path(self.profile_dir.location)
  52          hist_file = profile_dir / "history.sqlite"
  53          con = sqlite3.connect(hist_file)
  54  
  55          # Grab the recent history from the current database.
  56          inputs = list(con.execute('SELECT session, line, source, source_raw FROM '
  57                                  'history ORDER BY session DESC, line DESC LIMIT ?', (self.keep+1,)))
  58          if len(inputs) <= self.keep:
  59              print("There are already at most %d entries in the history database." % self.keep)
  60              print("Not doing anything. Use --keep= argument to keep fewer entries")
  61              return
  62          
  63          print("Trimming history to the most recent %d entries." % self.keep)
  64          
  65          inputs.pop() # Remove the extra element we got to check the length.
  66          inputs.reverse()
  67          if inputs:
  68              first_session = inputs[0][0]
  69              outputs = list(con.execute('SELECT session, line, output FROM '
  70                                         'output_history WHERE session >= ?', (first_session,)))
  71              sessions = list(con.execute('SELECT session, start, end, num_cmds, remark FROM '
  72                                          'sessions WHERE session >= ?', (first_session,)))
  73          con.close()
  74          
  75          # Create the new history database.
  76          new_hist_file = profile_dir / "history.sqlite.new"
  77          i = 0
  78          while new_hist_file.exists():
  79              # Make sure we don't interfere with an existing file.
  80              i += 1
  81              new_hist_file = profile_dir / ("history.sqlite.new" + str(i))
  82          new_db = sqlite3.connect(new_hist_file)
  83          new_db.execute("""CREATE TABLE IF NOT EXISTS sessions (session integer
  84                              primary key autoincrement, start timestamp,
  85                              end timestamp, num_cmds integer, remark text)""")
  86          new_db.execute("""CREATE TABLE IF NOT EXISTS history
  87                          (session integer, line integer, source text, source_raw text,
  88                          PRIMARY KEY (session, line))""")
  89          new_db.execute("""CREATE TABLE IF NOT EXISTS output_history
  90                          (session integer, line integer, output text,
  91                          PRIMARY KEY (session, line))""")
  92          new_db.commit()
  93  
  94  
  95          if inputs:
  96              with new_db:
  97                  # Add the recent history into the new database.
  98                  new_db.executemany('insert into sessions values (?,?,?,?,?)', sessions)
  99                  new_db.executemany('insert into history values (?,?,?,?)', inputs)
 100                  new_db.executemany('insert into output_history values (?,?,?)', outputs)
 101          new_db.close()
 102  
 103          if self.backup:
 104              i = 1
 105              backup_hist_file = profile_dir / ("history.sqlite.old.%d" % i)
 106              while backup_hist_file.exists():
 107                  i += 1
 108                  backup_hist_file = profile_dir / ("history.sqlite.old.%d" % i)
 109              hist_file.rename(backup_hist_file)
 110              print("Backed up longer history file to", backup_hist_file)
 111          else:
 112              hist_file.unlink()
 113  
 114          new_hist_file.rename(hist_file)
 115  
 116  
 117  class HistoryClear(HistoryTrim):
 118      description = clear_hist_help
 119      keep = Int(0, help="Number of recent lines to keep in the database.")
 120  
 121      force = Bool(False, help="Don't prompt user for confirmation").tag(config=True)
 122  
 123      flags = Dict(  # type: ignore
 124          dict(
 125              force=({"HistoryClear": {"force": True}}, force.help),
 126              f=({"HistoryTrim": {"force": True}}, force.help),
 127          )
 128      )
 129      aliases = Dict()  # type: ignore
 130  
 131      def start(self):
 132          if self.force or ask_yes_no(
 133              "Really delete all ipython history? ", default="no", interrupt="no"
 134          ):
 135              HistoryTrim.start(self)
 136  
 137  
 138  class HistoryApp(Application):
 139      name = "ipython-history"
 140      description = "Manage the IPython history database."
 141  
 142      subcommands = Dict(dict(
 143          trim = (HistoryTrim, HistoryTrim.description.splitlines()[0]),
 144          clear = (HistoryClear, HistoryClear.description.splitlines()[0]),
 145      ))
 146  
 147      def start(self):
 148          if self.subapp is None:
 149              print(
 150                  "No subcommand specified. Must specify one of: "
 151                  + ", ".join(map(repr, self.subcommands))
 152                  + ".\n"
 153              )
 154              self.print_description()
 155              self.print_subcommands()
 156              self.exit(1)
 157          else:
 158              return self.subapp.start()
 159