##// END OF EJS Templates
Start separating out methods for accessing history.
Thomas Kluyver -
Show More
@@ -32,79 +32,27 b' from IPython.utils.warn import warn'
32 # Classes and functions
32 # Classes and functions
33 #-----------------------------------------------------------------------------
33 #-----------------------------------------------------------------------------
34
34
35 class HistoryManager(Configurable):
35 class HistoryAccessor(Configurable):
36 """A class to organize all history-related functionality in one place.
36 """Access the history database without adding to it. For use by standalone
37 """
37 history tools."""
38 # Public interface
39
40 # An instance of the IPython shell we are attached to
41 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC')
42 # Lists to hold processed and raw history. These start with a blank entry
43 # so that we can index them starting from 1
44 input_hist_parsed = List([""])
45 input_hist_raw = List([""])
46 # A list of directories visited during session
47 dir_hist = List()
48 def _dir_hist_default(self):
49 try:
50 return [os.getcwdu()]
51 except OSError:
52 return []
53
54 # A dict of output history, keyed with ints from the shell's
55 # execution count.
56 output_hist = Dict()
57 # The text/plain repr of outputs.
58 output_hist_reprs = Dict()
59
60 # String holding the path to the history file
38 # String holding the path to the history file
61 hist_file = Unicode(config=True)
39 hist_file = Unicode(config=True)
62
40
63 # The SQLite database
41 # The SQLite database
64 db = Instance(sqlite3.Connection)
42 db = Instance(sqlite3.Connection)
65 # The number of the current session in the history database
43
66 session_number = CInt()
44 def __init__(self, hist_file=u'', shell=None, config=None, **traits):
67 # Should we log output to the database? (default no)
45 """Create a new history accessor.
68 db_log_output = Bool(False, config=True)
46
69 # Write to database every x commands (higher values save disk access & power)
47 hist_file must be given, either as an argument or through config.
70 # Values of 1 or less effectively disable caching.
71 db_cache_size = Int(0, config=True)
72 # The input and output caches
73 db_input_cache = List()
74 db_output_cache = List()
75
76 # History saving in separate thread
77 save_thread = Instance('IPython.core.history.HistorySavingThread')
78 try: # Event is a function returning an instance of _Event...
79 save_flag = Instance(threading._Event)
80 except AttributeError: # ...until Python 3.3, when it's a class.
81 save_flag = Instance(threading.Event)
82
83 # Private interface
84 # Variables used to store the three last inputs from the user. On each new
85 # history update, we populate the user's namespace with these, shifted as
86 # necessary.
87 _i00 = Unicode(u'')
88 _i = Unicode(u'')
89 _ii = Unicode(u'')
90 _iii = Unicode(u'')
91
92 # A regex matching all forms of the exit command, so that we don't store
93 # them in the history (it's annoying to rewind the first entry and land on
94 # an exit call).
95 _exit_re = re.compile(r"(exit|quit)(\s*\(.*\))?$")
96
97 def __init__(self, shell, config=None, **traits):
98 """Create a new history manager associated with a shell instance.
99 """
48 """
100 # We need a pointer back to the shell for various tasks.
49 # We need a pointer back to the shell for various tasks.
101 super(HistoryManager, self).__init__(shell=shell, config=config,
50 super(HistoryAccessor, self).__init__(shell=shell, config=config,
102 **traits)
51 hist_file=hist_file, **traits)
103
52
104 if self.hist_file == u'':
53 if self.hist_file == u'':
105 # No one has set the hist_file, yet.
54 # No one has set the hist_file, yet.
106 histfname = 'history'
55 self.hist_file = self._get_hist_file_name(hist_file)
107 self.hist_file = os.path.join(shell.profile_dir.location, histfname + '.sqlite')
108
56
109 try:
57 try:
110 self.init_db()
58 self.init_db()
@@ -119,16 +67,11 b' class HistoryManager(Configurable):'
119 else:
67 else:
120 # The hist_file is probably :memory: or something else.
68 # The hist_file is probably :memory: or something else.
121 raise
69 raise
122
70
123 self.save_flag = threading.Event()
71 def _get_hist_file_name(self, hist_file=None):
124 self.db_input_cache_lock = threading.Lock()
72 "Override to produce a default history file name."
125 self.db_output_cache_lock = threading.Lock()
73 raise NotImplementedError("No default history file")
126 self.save_thread = HistorySavingThread(self)
74
127 self.save_thread.start()
128
129 self.new_session()
130
131
132 def init_db(self):
75 def init_db(self):
133 """Connect to the database, and create tables if necessary."""
76 """Connect to the database, and create tables if necessary."""
134 # use detect_types so that timestamps return datetime objects
77 # use detect_types so that timestamps return datetime objects
@@ -146,49 +89,6 b' class HistoryManager(Configurable):'
146 PRIMARY KEY (session, line))""")
89 PRIMARY KEY (session, line))""")
147 self.db.commit()
90 self.db.commit()
148
91
149 def new_session(self, conn=None):
150 """Get a new session number."""
151 if conn is None:
152 conn = self.db
153
154 with conn:
155 # N.B. 'insert into' here is lower case because of a bug in the
156 # sqlite3 module that affects the Turkish locale. This should be
157 # fixed for Python 2.7.3 and 3.2.3, as well as 3.3 onwards.
158 # http://bugs.python.org/issue13099
159 cur = conn.execute("""insert into sessions VALUES (NULL, ?, NULL,
160 NULL, "") """, (datetime.datetime.now(),))
161 self.session_number = cur.lastrowid
162
163 def end_session(self):
164 """Close the database session, filling in the end time and line count."""
165 self.writeout_cache()
166 with self.db:
167 self.db.execute("""UPDATE sessions SET end=?, num_cmds=? WHERE
168 session==?""", (datetime.datetime.now(),
169 len(self.input_hist_parsed)-1, self.session_number))
170 self.session_number = 0
171
172 def name_session(self, name):
173 """Give the current session a name in the history database."""
174 with self.db:
175 self.db.execute("UPDATE sessions SET remark=? WHERE session==?",
176 (name, self.session_number))
177
178 def reset(self, new_session=True):
179 """Clear the session history, releasing all object references, and
180 optionally open a new session."""
181 self.output_hist.clear()
182 # The directory history can't be completely empty
183 self.dir_hist[:] = [os.getcwdu()]
184
185 if new_session:
186 if self.session_number:
187 self.end_session()
188 self.input_hist_parsed[:] = [""]
189 self.input_hist_raw[:] = [""]
190 self.new_session()
191
192 ## -------------------------------
92 ## -------------------------------
193 ## Methods for retrieving history:
93 ## Methods for retrieving history:
194 ## -------------------------------
94 ## -------------------------------
@@ -299,26 +199,6 b' class HistoryManager(Configurable):'
299 return self._run_sql("WHERE %s GLOB ?" % tosearch, (pattern,),
199 return self._run_sql("WHERE %s GLOB ?" % tosearch, (pattern,),
300 raw=raw, output=output)
200 raw=raw, output=output)
301
201
302 def _get_range_session(self, start=1, stop=None, raw=True, output=False):
303 """Get input and output history from the current session. Called by
304 get_range, and takes similar parameters."""
305 input_hist = self.input_hist_raw if raw else self.input_hist_parsed
306
307 n = len(input_hist)
308 if start < 0:
309 start += n
310 if not stop or (stop > n):
311 stop = n
312 elif stop < 0:
313 stop += n
314
315 for i in range(start, stop):
316 if output:
317 line = (input_hist[i], self.output_hist_reprs.get(i))
318 else:
319 line = input_hist[i]
320 yield (0, i, line)
321
322 def get_range(self, session=0, start=1, stop=None, raw=True,output=False):
202 def get_range(self, session=0, start=1, stop=None, raw=True,output=False):
323 """Retrieve input by session.
203 """Retrieve input by session.
324
204
@@ -381,6 +261,144 b' class HistoryManager(Configurable):'
381 for line in self.get_range(sess, s, e, raw=raw, output=output):
261 for line in self.get_range(sess, s, e, raw=raw, output=output):
382 yield line
262 yield line
383
263
264
265 class HistoryManager(HistoryAccessor):
266 """A class to organize all history-related functionality in one place.
267 """
268 # Public interface
269
270 # An instance of the IPython shell we are attached to
271 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC')
272 # Lists to hold processed and raw history. These start with a blank entry
273 # so that we can index them starting from 1
274 input_hist_parsed = List([""])
275 input_hist_raw = List([""])
276 # A list of directories visited during session
277 dir_hist = List()
278 def _dir_hist_default(self):
279 try:
280 return [os.getcwdu()]
281 except OSError:
282 return []
283
284 # A dict of output history, keyed with ints from the shell's
285 # execution count.
286 output_hist = Dict()
287 # The text/plain repr of outputs.
288 output_hist_reprs = Dict()
289
290 # The number of the current session in the history database
291 session_number = CInt()
292 # Should we log output to the database? (default no)
293 db_log_output = Bool(False, config=True)
294 # Write to database every x commands (higher values save disk access & power)
295 # Values of 1 or less effectively disable caching.
296 db_cache_size = Int(0, config=True)
297 # The input and output caches
298 db_input_cache = List()
299 db_output_cache = List()
300
301 # History saving in separate thread
302 save_thread = Instance('IPython.core.history.HistorySavingThread')
303 try: # Event is a function returning an instance of _Event...
304 save_flag = Instance(threading._Event)
305 except AttributeError: # ...until Python 3.3, when it's a class.
306 save_flag = Instance(threading.Event)
307
308 # Private interface
309 # Variables used to store the three last inputs from the user. On each new
310 # history update, we populate the user's namespace with these, shifted as
311 # necessary.
312 _i00 = Unicode(u'')
313 _i = Unicode(u'')
314 _ii = Unicode(u'')
315 _iii = Unicode(u'')
316
317 # A regex matching all forms of the exit command, so that we don't store
318 # them in the history (it's annoying to rewind the first entry and land on
319 # an exit call).
320 _exit_re = re.compile(r"(exit|quit)(\s*\(.*\))?$")
321
322 def __init__(self, shell=None, config=None, **traits):
323 """Create a new history manager associated with a shell instance.
324 """
325 # We need a pointer back to the shell for various tasks.
326 super(HistoryManager, self).__init__(shell=shell, config=config,
327 **traits)
328 self.save_flag = threading.Event()
329 self.db_input_cache_lock = threading.Lock()
330 self.db_output_cache_lock = threading.Lock()
331 self.save_thread = HistorySavingThread(self)
332 self.save_thread.start()
333
334 self.new_session()
335
336 def _get_hist_file_name(self, hist_file=None):
337 profile_dir = self.shell.profile_dir.location
338 return os.path.join(profile_dir, 'history.sqlite')
339
340 def new_session(self, conn=None):
341 """Get a new session number."""
342 if conn is None:
343 conn = self.db
344
345 with conn:
346 cur = conn.execute("""INSERT INTO sessions VALUES (NULL, ?, NULL,
347 NULL, "") """, (datetime.datetime.now(),))
348 self.session_number = cur.lastrowid
349
350 def end_session(self):
351 """Close the database session, filling in the end time and line count."""
352 self.writeout_cache()
353 with self.db:
354 self.db.execute("""UPDATE sessions SET end=?, num_cmds=? WHERE
355 session==?""", (datetime.datetime.now(),
356 len(self.input_hist_parsed)-1, self.session_number))
357 self.session_number = 0
358
359 def name_session(self, name):
360 """Give the current session a name in the history database."""
361 with self.db:
362 self.db.execute("UPDATE sessions SET remark=? WHERE session==?",
363 (name, self.session_number))
364
365 def reset(self, new_session=True):
366 """Clear the session history, releasing all object references, and
367 optionally open a new session."""
368 self.output_hist.clear()
369 # The directory history can't be completely empty
370 self.dir_hist[:] = [os.getcwdu()]
371
372 if new_session:
373 if self.session_number:
374 self.end_session()
375 self.input_hist_parsed[:] = [""]
376 self.input_hist_raw[:] = [""]
377 self.new_session()
378
379 # ------------------------------
380 # Methods for retrieving history
381 # ------------------------------
382 def _get_range_session(self, start=1, stop=None, raw=True, output=False):
383 """Get input and output history from the current session. Called by
384 get_range, and takes similar parameters."""
385 input_hist = self.input_hist_raw if raw else self.input_hist_parsed
386
387 n = len(input_hist)
388 if start < 0:
389 start += n
390 if not stop or (stop > n):
391 stop = n
392 elif stop < 0:
393 stop += n
394
395 for i in range(start, stop):
396 if output:
397 line = (input_hist[i], self.output_hist_reprs.get(i))
398 else:
399 line = input_hist[i]
400 yield (0, i, line)
401
384 ## ----------------------------
402 ## ----------------------------
385 ## Methods for storing history:
403 ## Methods for storing history:
386 ## ----------------------------
404 ## ----------------------------
General Comments 0
You need to be logged in to leave comments. Login now