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 History |
|
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(History |
|
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