##// END OF EJS Templates
Add command to trim the history database.
Thomas Kluyver -
Show More
@@ -0,0 +1,119 b''
1 # encoding: utf-8
2 """
3 An application for managing IPython history.
4
5 To be invoked as the `ipython history` subcommand.
6 """
7 from __future__ import print_function
8
9 import os
10 import sqlite3
11
12 from IPython.config.application import Application
13 from IPython.core.application import BaseIPythonApplication
14 from IPython.utils.traitlets import Bool, Int, Dict
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.
20 """
21
22 class HistoryTrim(BaseIPythonApplication):
23 description = trim_hist_help
24
25 backup = Bool(False, config=True,
26 help="Keep the old history file as history.sqlite.<N>")
27
28 keep = Int(1000, config=True,
29 help="Number of recent lines to keep in the database.")
30
31 flags = Dict(dict(
32 backup = ({'HistoryTrim' : {'backup' : True}},
33 "Set Application.log_level to 0, maximizing log output."
34 )
35 ))
36
37 def start(self):
38 profile_dir = self.profile_dir.location
39 hist_file = os.path.join(profile_dir, 'history.sqlite')
40 con = sqlite3.connect(hist_file)
41
42 # Grab the recent history from the current database.
43 inputs = list(con.execute('SELECT session, line, source, source_raw FROM '
44 'history ORDER BY session DESC, line DESC LIMIT ?', (self.keep+1,)))
45 if len(inputs) <= self.keep:
46 print("There are already at most %d entries in the history database." % self.keep)
47 print("Not doing anything.")
48 return
49
50 print("Trimming history to the most recent %d entries." % self.keep)
51
52 inputs.pop() # Remove the extra element we got to check the length.
53 inputs.reverse()
54 first_session = inputs[0][0]
55 outputs = list(con.execute('SELECT session, line, output FROM '
56 'output_history WHERE session >= ?', (first_session,)))
57 sessions = list(con.execute('SELECT session, start, end, num_cmds, remark FROM '
58 'sessions WHERE session >= ?', (first_session,)))
59 con.close()
60
61 # Create the new history database.
62 new_hist_file = os.path.join(profile_dir, 'history.sqlite.new')
63 i = 0
64 while os.path.exists(new_hist_file):
65 # Make sure we don't interfere with an existing file.
66 i += 1
67 new_hist_file = os.path.join(profile_dir, 'history.sqlite.new'+str(i))
68 new_db = sqlite3.connect(new_hist_file)
69 new_db.execute("""CREATE TABLE IF NOT EXISTS sessions (session integer
70 primary key autoincrement, start timestamp,
71 end timestamp, num_cmds integer, remark text)""")
72 new_db.execute("""CREATE TABLE IF NOT EXISTS history
73 (session integer, line integer, source text, source_raw text,
74 PRIMARY KEY (session, line))""")
75 new_db.execute("""CREATE TABLE IF NOT EXISTS output_history
76 (session integer, line integer, output text,
77 PRIMARY KEY (session, line))""")
78 new_db.commit()
79
80
81 with new_db:
82 # Add the recent history into the new database.
83 new_db.executemany('insert into sessions values (?,?,?,?,?)', sessions)
84 new_db.executemany('insert into history values (?,?,?,?)', inputs)
85 new_db.executemany('insert into output_history values (?,?,?)', outputs)
86 new_db.close()
87
88 if self.backup:
89 i = 1
90 backup_hist_file = os.path.join(profile_dir, 'history.sqlite.old.%d' % i)
91 while os.path.exists(backup_hist_file):
92 i += 1
93 backup_hist_file = os.path.join(profile_dir, 'history.sqlite.old.%d' % i)
94 os.rename(hist_file, backup_hist_file)
95 print("Backed up longer history file to", backup_hist_file)
96 else:
97 os.remove(hist_file)
98
99 os.rename(new_hist_file, hist_file)
100
101
102 class HistoryApp(Application):
103 name = u'ipython-history'
104 description = "Manage the IPython history database."
105
106 subcommands = Dict(dict(
107 trim = (HistoryTrim, HistoryTrim.description.splitlines()[0]),
108 ))
109
110 def start(self):
111 if self.subapp is None:
112 print("No subcommand specified. Must specify one of: %s" % \
113 (self.subcommands.keys()))
114 print()
115 self.print_description()
116 self.print_subcommands()
117 self.exit(1)
118 else:
119 return self.subapp.start()
@@ -1,392 +1,395 b''
1 1 #!/usr/bin/env python
2 2 # encoding: utf-8
3 3 """
4 4 The :class:`~IPython.core.application.Application` object for the command
5 5 line :command:`ipython` program.
6 6
7 7 Authors
8 8 -------
9 9
10 10 * Brian Granger
11 11 * Fernando Perez
12 12 * Min Ragan-Kelley
13 13 """
14 14
15 15 #-----------------------------------------------------------------------------
16 16 # Copyright (C) 2008-2011 The IPython Development Team
17 17 #
18 18 # Distributed under the terms of the BSD License. The full license is in
19 19 # the file COPYING, distributed as part of this software.
20 20 #-----------------------------------------------------------------------------
21 21
22 22 #-----------------------------------------------------------------------------
23 23 # Imports
24 24 #-----------------------------------------------------------------------------
25 25
26 26 from __future__ import absolute_import
27 27
28 28 import logging
29 29 import os
30 30 import sys
31 31
32 32 from IPython.config.loader import (
33 33 Config, PyFileConfigLoader, ConfigFileNotFound
34 34 )
35 35 from IPython.config.application import boolean_flag, catch_config_error
36 36 from IPython.core import release
37 37 from IPython.core import usage
38 38 from IPython.core.completer import IPCompleter
39 39 from IPython.core.crashhandler import CrashHandler
40 40 from IPython.core.formatters import PlainTextFormatter
41 41 from IPython.core.history import HistoryManager
42 42 from IPython.core.prompts import PromptManager
43 43 from IPython.core.application import (
44 44 ProfileDir, BaseIPythonApplication, base_flags, base_aliases
45 45 )
46 46 from IPython.core.magics import ScriptMagics
47 47 from IPython.core.shellapp import (
48 48 InteractiveShellApp, shell_flags, shell_aliases
49 49 )
50 50 from IPython.frontend.terminal.interactiveshell import TerminalInteractiveShell
51 51 from IPython.utils import warn
52 52 from IPython.utils.path import get_ipython_dir, check_for_old_config
53 53 from IPython.utils.traitlets import (
54 54 Bool, List, Dict, CaselessStrEnum
55 55 )
56 56
57 57 #-----------------------------------------------------------------------------
58 58 # Globals, utilities and helpers
59 59 #-----------------------------------------------------------------------------
60 60
61 61 #: The default config file name for this application.
62 62 default_config_file_name = u'ipython_config.py'
63 63
64 64 _examples = """
65 65 ipython --pylab # start in pylab mode
66 66 ipython --pylab=qt # start in pylab mode with the qt4 backend
67 67 ipython --log-level=DEBUG # set logging to DEBUG
68 68 ipython --profile=foo # start with profile foo
69 69
70 70 ipython qtconsole # start the qtconsole GUI application
71 71 ipython help qtconsole # show the help for the qtconsole subcmd
72 72
73 73 ipython console # start the terminal-based console application
74 74 ipython help console # show the help for the console subcmd
75 75
76 76 ipython notebook # start the IPython notebook
77 77 ipython help notebook # show the help for the notebook subcmd
78 78
79 79 ipython profile create foo # create profile foo w/ default config files
80 80 ipython help profile # show the help for the profile subcmd
81 81
82 82 ipython locate # print the path to the IPython directory
83 83 ipython locate profile foo # print the path to the directory for profile `foo`
84 84 """
85 85
86 86 #-----------------------------------------------------------------------------
87 87 # Crash handler for this application
88 88 #-----------------------------------------------------------------------------
89 89
90 90 class IPAppCrashHandler(CrashHandler):
91 91 """sys.excepthook for IPython itself, leaves a detailed report on disk."""
92 92
93 93 def __init__(self, app):
94 94 contact_name = release.author
95 95 contact_email = release.author_email
96 96 bug_tracker = 'https://github.com/ipython/ipython/issues'
97 97 super(IPAppCrashHandler,self).__init__(
98 98 app, contact_name, contact_email, bug_tracker
99 99 )
100 100
101 101 def make_report(self,traceback):
102 102 """Return a string containing a crash report."""
103 103
104 104 sec_sep = self.section_sep
105 105 # Start with parent report
106 106 report = [super(IPAppCrashHandler, self).make_report(traceback)]
107 107 # Add interactive-specific info we may have
108 108 rpt_add = report.append
109 109 try:
110 110 rpt_add(sec_sep+"History of session input:")
111 111 for line in self.app.shell.user_ns['_ih']:
112 112 rpt_add(line)
113 113 rpt_add('\n*** Last line of input (may not be in above history):\n')
114 114 rpt_add(self.app.shell._last_input_line+'\n')
115 115 except:
116 116 pass
117 117
118 118 return ''.join(report)
119 119
120 120 #-----------------------------------------------------------------------------
121 121 # Aliases and Flags
122 122 #-----------------------------------------------------------------------------
123 123 flags = dict(base_flags)
124 124 flags.update(shell_flags)
125 125 frontend_flags = {}
126 126 addflag = lambda *args: frontend_flags.update(boolean_flag(*args))
127 127 addflag('autoedit-syntax', 'TerminalInteractiveShell.autoedit_syntax',
128 128 'Turn on auto editing of files with syntax errors.',
129 129 'Turn off auto editing of files with syntax errors.'
130 130 )
131 131 addflag('banner', 'TerminalIPythonApp.display_banner',
132 132 "Display a banner upon starting IPython.",
133 133 "Don't display a banner upon starting IPython."
134 134 )
135 135 addflag('confirm-exit', 'TerminalInteractiveShell.confirm_exit',
136 136 """Set to confirm when you try to exit IPython with an EOF (Control-D
137 137 in Unix, Control-Z/Enter in Windows). By typing 'exit' or 'quit',
138 138 you can force a direct exit without any confirmation.""",
139 139 "Don't prompt the user when exiting."
140 140 )
141 141 addflag('term-title', 'TerminalInteractiveShell.term_title',
142 142 "Enable auto setting the terminal title.",
143 143 "Disable auto setting the terminal title."
144 144 )
145 145 classic_config = Config()
146 146 classic_config.InteractiveShell.cache_size = 0
147 147 classic_config.PlainTextFormatter.pprint = False
148 148 classic_config.PromptManager.in_template = '>>> '
149 149 classic_config.PromptManager.in2_template = '... '
150 150 classic_config.PromptManager.out_template = ''
151 151 classic_config.InteractiveShell.separate_in = ''
152 152 classic_config.InteractiveShell.separate_out = ''
153 153 classic_config.InteractiveShell.separate_out2 = ''
154 154 classic_config.InteractiveShell.colors = 'NoColor'
155 155 classic_config.InteractiveShell.xmode = 'Plain'
156 156
157 157 frontend_flags['classic']=(
158 158 classic_config,
159 159 "Gives IPython a similar feel to the classic Python prompt."
160 160 )
161 161 # # log doesn't make so much sense this way anymore
162 162 # paa('--log','-l',
163 163 # action='store_true', dest='InteractiveShell.logstart',
164 164 # help="Start logging to the default log file (./ipython_log.py).")
165 165 #
166 166 # # quick is harder to implement
167 167 frontend_flags['quick']=(
168 168 {'TerminalIPythonApp' : {'quick' : True}},
169 169 "Enable quick startup with no config files."
170 170 )
171 171
172 172 frontend_flags['i'] = (
173 173 {'TerminalIPythonApp' : {'force_interact' : True}},
174 174 """If running code from the command line, become interactive afterwards.
175 175 Note: can also be given simply as '-i.'"""
176 176 )
177 177 flags.update(frontend_flags)
178 178
179 179 aliases = dict(base_aliases)
180 180 aliases.update(shell_aliases)
181 181
182 182 #-----------------------------------------------------------------------------
183 183 # Main classes and functions
184 184 #-----------------------------------------------------------------------------
185 185
186 186
187 187 class LocateIPythonApp(BaseIPythonApplication):
188 188 description = """print the path to the IPython dir"""
189 189 subcommands = Dict(dict(
190 190 profile=('IPython.core.profileapp.ProfileLocate',
191 191 "print the path to an IPython profile directory",
192 192 ),
193 193 ))
194 194 def start(self):
195 195 if self.subapp is not None:
196 196 return self.subapp.start()
197 197 else:
198 198 print self.ipython_dir
199 199
200 200
201 201 class TerminalIPythonApp(BaseIPythonApplication, InteractiveShellApp):
202 202 name = u'ipython'
203 203 description = usage.cl_usage
204 204 default_config_file_name = default_config_file_name
205 205 crash_handler_class = IPAppCrashHandler
206 206 examples = _examples
207 207
208 208 flags = Dict(flags)
209 209 aliases = Dict(aliases)
210 210 classes = List()
211 211 def _classes_default(self):
212 212 """This has to be in a method, for TerminalIPythonApp to be available."""
213 213 return [
214 214 InteractiveShellApp, # ShellApp comes before TerminalApp, because
215 215 self.__class__, # it will also affect subclasses (e.g. QtConsole)
216 216 TerminalInteractiveShell,
217 217 PromptManager,
218 218 HistoryManager,
219 219 ProfileDir,
220 220 PlainTextFormatter,
221 221 IPCompleter,
222 222 ScriptMagics,
223 223 ]
224 224
225 225 subcommands = Dict(dict(
226 226 qtconsole=('IPython.frontend.qt.console.qtconsoleapp.IPythonQtConsoleApp',
227 227 """Launch the IPython Qt Console."""
228 228 ),
229 229 notebook=('IPython.frontend.html.notebook.notebookapp.NotebookApp',
230 230 """Launch the IPython HTML Notebook Server."""
231 231 ),
232 232 profile = ("IPython.core.profileapp.ProfileApp",
233 233 "Create and manage IPython profiles."
234 234 ),
235 235 kernel = ("IPython.kernel.zmq.kernelapp.IPKernelApp",
236 236 "Start a kernel without an attached frontend."
237 237 ),
238 238 console=('IPython.frontend.terminal.console.app.ZMQTerminalIPythonApp',
239 239 """Launch the IPython terminal-based Console."""
240 240 ),
241 241 locate=('IPython.frontend.terminal.ipapp.LocateIPythonApp',
242 242 LocateIPythonApp.description
243 243 ),
244 history=('IPython.core.historyapp.HistoryApp',
245 "Manage the IPython history database."
246 ),
244 247 ))
245 248
246 249 # *do* autocreate requested profile, but don't create the config file.
247 250 auto_create=Bool(True)
248 251 # configurables
249 252 ignore_old_config=Bool(False, config=True,
250 253 help="Suppress warning messages about legacy config files"
251 254 )
252 255 quick = Bool(False, config=True,
253 256 help="""Start IPython quickly by skipping the loading of config files."""
254 257 )
255 258 def _quick_changed(self, name, old, new):
256 259 if new:
257 260 self.load_config_file = lambda *a, **kw: None
258 261 self.ignore_old_config=True
259 262
260 263 display_banner = Bool(True, config=True,
261 264 help="Whether to display a banner upon starting IPython."
262 265 )
263 266
264 267 # if there is code of files to run from the cmd line, don't interact
265 268 # unless the --i flag (App.force_interact) is true.
266 269 force_interact = Bool(False, config=True,
267 270 help="""If a command or file is given via the command-line,
268 271 e.g. 'ipython foo.py"""
269 272 )
270 273 def _force_interact_changed(self, name, old, new):
271 274 if new:
272 275 self.interact = True
273 276
274 277 def _file_to_run_changed(self, name, old, new):
275 278 if new:
276 279 self.something_to_run = True
277 280 if new and not self.force_interact:
278 281 self.interact = False
279 282 _code_to_run_changed = _file_to_run_changed
280 283 _module_to_run_changed = _file_to_run_changed
281 284
282 285 # internal, not-configurable
283 286 interact=Bool(True)
284 287 something_to_run=Bool(False)
285 288
286 289 def parse_command_line(self, argv=None):
287 290 """override to allow old '-pylab' flag with deprecation warning"""
288 291
289 292 argv = sys.argv[1:] if argv is None else argv
290 293
291 294 if '-pylab' in argv:
292 295 # deprecated `-pylab` given,
293 296 # warn and transform into current syntax
294 297 argv = argv[:] # copy, don't clobber
295 298 idx = argv.index('-pylab')
296 299 warn.warn("`-pylab` flag has been deprecated.\n"
297 300 " Use `--pylab` instead, or `--pylab=foo` to specify a backend.")
298 301 sub = '--pylab'
299 302 if len(argv) > idx+1:
300 303 # check for gui arg, as in '-pylab qt'
301 304 gui = argv[idx+1]
302 305 if gui in ('wx', 'qt', 'qt4', 'gtk', 'auto'):
303 306 sub = '--pylab='+gui
304 307 argv.pop(idx+1)
305 308 argv[idx] = sub
306 309
307 310 return super(TerminalIPythonApp, self).parse_command_line(argv)
308 311
309 312 @catch_config_error
310 313 def initialize(self, argv=None):
311 314 """Do actions after construct, but before starting the app."""
312 315 super(TerminalIPythonApp, self).initialize(argv)
313 316 if self.subapp is not None:
314 317 # don't bother initializing further, starting subapp
315 318 return
316 319 if not self.ignore_old_config:
317 320 check_for_old_config(self.ipython_dir)
318 321 # print self.extra_args
319 322 if self.extra_args and not self.something_to_run:
320 323 self.file_to_run = self.extra_args[0]
321 324 self.init_path()
322 325 # create the shell
323 326 self.init_shell()
324 327 # and draw the banner
325 328 self.init_banner()
326 329 # Now a variety of things that happen after the banner is printed.
327 330 self.init_gui_pylab()
328 331 self.init_extensions()
329 332 self.init_code()
330 333
331 334 def init_shell(self):
332 335 """initialize the InteractiveShell instance"""
333 336 # Create an InteractiveShell instance.
334 337 # shell.display_banner should always be False for the terminal
335 338 # based app, because we call shell.show_banner() by hand below
336 339 # so the banner shows *before* all extension loading stuff.
337 340 self.shell = TerminalInteractiveShell.instance(config=self.config,
338 341 display_banner=False, profile_dir=self.profile_dir,
339 342 ipython_dir=self.ipython_dir)
340 343 self.shell.configurables.append(self)
341 344
342 345 def init_banner(self):
343 346 """optionally display the banner"""
344 347 if self.display_banner and self.interact:
345 348 self.shell.show_banner()
346 349 # Make sure there is a space below the banner.
347 350 if self.log_level <= logging.INFO: print
348 351
349 352 def _pylab_changed(self, name, old, new):
350 353 """Replace --pylab='inline' with --pylab='auto'"""
351 354 if new == 'inline':
352 355 warn.warn("'inline' not available as pylab backend, "
353 356 "using 'auto' instead.")
354 357 self.pylab = 'auto'
355 358
356 359 def start(self):
357 360 if self.subapp is not None:
358 361 return self.subapp.start()
359 362 # perform any prexec steps:
360 363 if self.interact:
361 364 self.log.debug("Starting IPython's mainloop...")
362 365 self.shell.mainloop()
363 366 else:
364 367 self.log.debug("IPython not interactive...")
365 368
366 369
367 370 def load_default_config(ipython_dir=None):
368 371 """Load the default config file from the default ipython_dir.
369 372
370 373 This is useful for embedded shells.
371 374 """
372 375 if ipython_dir is None:
373 376 ipython_dir = get_ipython_dir()
374 377 profile_dir = os.path.join(ipython_dir, 'profile_default')
375 378 cl = PyFileConfigLoader(default_config_file_name, profile_dir)
376 379 try:
377 380 config = cl.load_config()
378 381 except ConfigFileNotFound:
379 382 # no config found
380 383 config = Config()
381 384 return config
382 385
383 386
384 387 def launch_new_instance():
385 388 """Create and run a full blown IPython instance"""
386 389 app = TerminalIPythonApp.instance()
387 390 app.initialize()
388 391 app.start()
389 392
390 393
391 394 if __name__ == '__main__':
392 395 launch_new_instance()
General Comments 0
You need to be logged in to leave comments. Login now