|
@@
-25,6
+25,7
b' from IPython.config.configurable import Configurable'
|
|
25
|
25
|
|
|
26
|
26
|
from IPython.testing.skipdoctest import skip_doctest
|
|
27
|
27
|
from IPython.utils import io
|
|
|
28
|
from IPython.utils.path import locate_profile
|
|
28
|
29
|
from IPython.utils.traitlets import Bool, Dict, Instance, Int, CInt, List, Unicode
|
|
29
|
30
|
from IPython.utils.warn import warn
|
|
30
|
31
|
|
|
@@
-32,79
+33,39
b' from IPython.utils.warn import warn'
|
|
32
|
33
|
# Classes and functions
|
|
33
|
34
|
#-----------------------------------------------------------------------------
|
|
34
|
35
|
|
|
35
|
|
class HistoryManager(Configurable):
|
|
36
|
|
"""A class to organize all history-related functionality in one place.
|
|
37
|
|
"""
|
|
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
|
|
|
|
|
36
|
class HistoryAccessor(Configurable):
|
|
|
37
|
"""Access the history database without adding to it.
|
|
|
38
|
|
|
|
39
|
This is intended for use by standalone history tools. IPython shells use
|
|
|
40
|
HistoryManager, below, which is a subclass of this."""
|
|
60
|
41
|
# String holding the path to the history file
|
|
61
|
42
|
hist_file = Unicode(config=True)
|
|
62
|
43
|
|
|
63
|
44
|
# The SQLite database
|
|
64
|
45
|
db = Instance(sqlite3.Connection)
|
|
65
|
|
# The number of the current session in the history database
|
|
66
|
|
session_number = CInt()
|
|
67
|
|
# Should we log output to the database? (default no)
|
|
68
|
|
db_log_output = Bool(False, config=True)
|
|
69
|
|
# Write to database every x commands (higher values save disk access & power)
|
|
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.
|
|
|
46
|
|
|
|
47
|
def __init__(self, profile='default', hist_file=u'', shell=None, config=None, **traits):
|
|
|
48
|
"""Create a new history accessor.
|
|
|
49
|
|
|
|
50
|
Parameters
|
|
|
51
|
----------
|
|
|
52
|
profile : str
|
|
|
53
|
The name of the profile from which to open history.
|
|
|
54
|
hist_file : str
|
|
|
55
|
Path to an SQLite history database stored by IPython. If specified,
|
|
|
56
|
hist_file overrides profile.
|
|
|
57
|
shell :
|
|
|
58
|
InteractiveShell object, for use by HistoryManager subclass
|
|
|
59
|
config :
|
|
|
60
|
Config object. hist_file can also be set through this.
|
|
99
|
61
|
"""
|
|
100
|
62
|
# We need a pointer back to the shell for various tasks.
|
|
101
|
|
super(HistoryManager, self).__init__(shell=shell, config=config,
|
|
102
|
|
**traits)
|
|
|
63
|
super(HistoryAccessor, self).__init__(shell=shell, config=config,
|
|
|
64
|
hist_file=hist_file, **traits)
|
|
103
|
65
|
|
|
104
|
66
|
if self.hist_file == u'':
|
|
105
|
67
|
# No one has set the hist_file, yet.
|
|
106
|
|
histfname = 'history'
|
|
107
|
|
self.hist_file = os.path.join(shell.profile_dir.location, histfname + '.sqlite')
|
|
|
68
|
self.hist_file = self._get_hist_file_name(profile)
|
|
108
|
69
|
|
|
109
|
70
|
try:
|
|
110
|
71
|
self.init_db()
|
|
@@
-119,16
+80,20
b' class HistoryManager(Configurable):'
|
|
119
|
80
|
else:
|
|
120
|
81
|
# The hist_file is probably :memory: or something else.
|
|
121
|
82
|
raise
|
|
122
|
|
|
|
123
|
|
self.save_flag = threading.Event()
|
|
124
|
|
self.db_input_cache_lock = threading.Lock()
|
|
125
|
|
self.db_output_cache_lock = threading.Lock()
|
|
126
|
|
self.save_thread = HistorySavingThread(self)
|
|
127
|
|
self.save_thread.start()
|
|
128
|
|
|
|
129
|
|
self.new_session()
|
|
130
|
|
|
|
131
|
|
|
|
|
83
|
|
|
|
84
|
def _get_hist_file_name(self, profile='default'):
|
|
|
85
|
"""Find the history file for the given profile name.
|
|
|
86
|
|
|
|
87
|
This is overridden by the HistoryManager subclass, to use the shell's
|
|
|
88
|
active profile.
|
|
|
89
|
|
|
|
90
|
Parameters
|
|
|
91
|
----------
|
|
|
92
|
profile : str
|
|
|
93
|
The name of a profile which has a history file.
|
|
|
94
|
"""
|
|
|
95
|
return os.path.join(locate_profile(profile), 'history.sqlite')
|
|
|
96
|
|
|
132
|
97
|
def init_db(self):
|
|
133
|
98
|
"""Connect to the database, and create tables if necessary."""
|
|
134
|
99
|
# use detect_types so that timestamps return datetime objects
|
|
@@
-146,48
+111,10
b' class HistoryManager(Configurable):'
|
|
146
|
111
|
PRIMARY KEY (session, line))""")
|
|
147
|
112
|
self.db.commit()
|
|
148
|
113
|
|
|
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()
|
|
|
114
|
def writeout_cache(self):
|
|
|
115
|
"""Overridden by HistoryManager to dump the cache before certain
|
|
|
116
|
database lookups."""
|
|
|
117
|
pass
|
|
191
|
118
|
|
|
192
|
119
|
## -------------------------------
|
|
193
|
120
|
## Methods for retrieving history:
|
|
@@
-219,7
+146,6
b' class HistoryManager(Configurable):'
|
|
219
|
146
|
return ((ses, lin, (inp, out)) for ses, lin, inp, out in cur)
|
|
220
|
147
|
return cur
|
|
221
|
148
|
|
|
222
|
|
|
|
223
|
149
|
def get_session_info(self, session=0):
|
|
224
|
150
|
"""get info about a session
|
|
225
|
151
|
|
|
@@
-246,7
+172,6
b' class HistoryManager(Configurable):'
|
|
246
|
172
|
query = "SELECT * from sessions where session == ?"
|
|
247
|
173
|
return self.db.execute(query, (session,)).fetchone()
|
|
248
|
174
|
|
|
249
|
|
|
|
250
|
175
|
def get_tail(self, n=10, raw=True, output=False, include_latest=False):
|
|
251
|
176
|
"""Get the last n lines from the history database.
|
|
252
|
177
|
|
|
@@
-298,35
+223,14
b' class HistoryManager(Configurable):'
|
|
298
|
223
|
self.writeout_cache()
|
|
299
|
224
|
return self._run_sql("WHERE %s GLOB ?" % tosearch, (pattern,),
|
|
300
|
225
|
raw=raw, output=output)
|
|
301
|
|
|
|
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):
|
|
|
226
|
|
|
|
227
|
def get_range(self, session, start=1, stop=None, raw=True,output=False):
|
|
323
|
228
|
"""Retrieve input by session.
|
|
324
|
229
|
|
|
325
|
230
|
Parameters
|
|
326
|
231
|
----------
|
|
327
|
232
|
session : int
|
|
328
|
|
Session number to retrieve. The current session is 0, and negative
|
|
329
|
|
numbers count back from current session, so -1 is previous session.
|
|
|
233
|
Session number to retrieve.
|
|
330
|
234
|
start : int
|
|
331
|
235
|
First line to retrieve.
|
|
332
|
236
|
stop : int
|
|
@@
-346,11
+250,6
b' class HistoryManager(Configurable):'
|
|
346
|
250
|
(session, line, input) if output is False, or
|
|
347
|
251
|
(session, line, (input, output)) if output is True.
|
|
348
|
252
|
"""
|
|
349
|
|
if session == 0 or session==self.session_number: # Current session
|
|
350
|
|
return self._get_range_session(start, stop, raw, output)
|
|
351
|
|
if session < 0:
|
|
352
|
|
session += self.session_number
|
|
353
|
|
|
|
354
|
253
|
if stop:
|
|
355
|
254
|
lineclause = "line >= ? AND line < ?"
|
|
356
|
255
|
params = (session, start, stop)
|
|
@@
-381,6
+280,181
b' class HistoryManager(Configurable):'
|
|
381
|
280
|
for line in self.get_range(sess, s, e, raw=raw, output=output):
|
|
382
|
281
|
yield line
|
|
383
|
282
|
|
|
|
283
|
|
|
|
284
|
class HistoryManager(HistoryAccessor):
|
|
|
285
|
"""A class to organize all history-related functionality in one place.
|
|
|
286
|
"""
|
|
|
287
|
# Public interface
|
|
|
288
|
|
|
|
289
|
# An instance of the IPython shell we are attached to
|
|
|
290
|
shell = Instance('IPython.core.interactiveshell.InteractiveShellABC')
|
|
|
291
|
# Lists to hold processed and raw history. These start with a blank entry
|
|
|
292
|
# so that we can index them starting from 1
|
|
|
293
|
input_hist_parsed = List([""])
|
|
|
294
|
input_hist_raw = List([""])
|
|
|
295
|
# A list of directories visited during session
|
|
|
296
|
dir_hist = List()
|
|
|
297
|
def _dir_hist_default(self):
|
|
|
298
|
try:
|
|
|
299
|
return [os.getcwdu()]
|
|
|
300
|
except OSError:
|
|
|
301
|
return []
|
|
|
302
|
|
|
|
303
|
# A dict of output history, keyed with ints from the shell's
|
|
|
304
|
# execution count.
|
|
|
305
|
output_hist = Dict()
|
|
|
306
|
# The text/plain repr of outputs.
|
|
|
307
|
output_hist_reprs = Dict()
|
|
|
308
|
|
|
|
309
|
# The number of the current session in the history database
|
|
|
310
|
session_number = CInt()
|
|
|
311
|
# Should we log output to the database? (default no)
|
|
|
312
|
db_log_output = Bool(False, config=True)
|
|
|
313
|
# Write to database every x commands (higher values save disk access & power)
|
|
|
314
|
# Values of 1 or less effectively disable caching.
|
|
|
315
|
db_cache_size = Int(0, config=True)
|
|
|
316
|
# The input and output caches
|
|
|
317
|
db_input_cache = List()
|
|
|
318
|
db_output_cache = List()
|
|
|
319
|
|
|
|
320
|
# History saving in separate thread
|
|
|
321
|
save_thread = Instance('IPython.core.history.HistorySavingThread')
|
|
|
322
|
try: # Event is a function returning an instance of _Event...
|
|
|
323
|
save_flag = Instance(threading._Event)
|
|
|
324
|
except AttributeError: # ...until Python 3.3, when it's a class.
|
|
|
325
|
save_flag = Instance(threading.Event)
|
|
|
326
|
|
|
|
327
|
# Private interface
|
|
|
328
|
# Variables used to store the three last inputs from the user. On each new
|
|
|
329
|
# history update, we populate the user's namespace with these, shifted as
|
|
|
330
|
# necessary.
|
|
|
331
|
_i00 = Unicode(u'')
|
|
|
332
|
_i = Unicode(u'')
|
|
|
333
|
_ii = Unicode(u'')
|
|
|
334
|
_iii = Unicode(u'')
|
|
|
335
|
|
|
|
336
|
# A regex matching all forms of the exit command, so that we don't store
|
|
|
337
|
# them in the history (it's annoying to rewind the first entry and land on
|
|
|
338
|
# an exit call).
|
|
|
339
|
_exit_re = re.compile(r"(exit|quit)(\s*\(.*\))?$")
|
|
|
340
|
|
|
|
341
|
def __init__(self, shell=None, config=None, **traits):
|
|
|
342
|
"""Create a new history manager associated with a shell instance.
|
|
|
343
|
"""
|
|
|
344
|
# We need a pointer back to the shell for various tasks.
|
|
|
345
|
super(HistoryManager, self).__init__(shell=shell, config=config,
|
|
|
346
|
**traits)
|
|
|
347
|
self.save_flag = threading.Event()
|
|
|
348
|
self.db_input_cache_lock = threading.Lock()
|
|
|
349
|
self.db_output_cache_lock = threading.Lock()
|
|
|
350
|
self.save_thread = HistorySavingThread(self)
|
|
|
351
|
self.save_thread.start()
|
|
|
352
|
|
|
|
353
|
self.new_session()
|
|
|
354
|
|
|
|
355
|
def _get_hist_file_name(self, profile=None):
|
|
|
356
|
"""Get default history file name based on the Shell's profile.
|
|
|
357
|
|
|
|
358
|
The profile parameter is ignored, but must exist for compatibility with
|
|
|
359
|
the parent class."""
|
|
|
360
|
profile_dir = self.shell.profile_dir.location
|
|
|
361
|
return os.path.join(profile_dir, 'history.sqlite')
|
|
|
362
|
|
|
|
363
|
def new_session(self, conn=None):
|
|
|
364
|
"""Get a new session number."""
|
|
|
365
|
if conn is None:
|
|
|
366
|
conn = self.db
|
|
|
367
|
|
|
|
368
|
with conn:
|
|
|
369
|
cur = conn.execute("""INSERT INTO sessions VALUES (NULL, ?, NULL,
|
|
|
370
|
NULL, "") """, (datetime.datetime.now(),))
|
|
|
371
|
self.session_number = cur.lastrowid
|
|
|
372
|
|
|
|
373
|
def end_session(self):
|
|
|
374
|
"""Close the database session, filling in the end time and line count."""
|
|
|
375
|
self.writeout_cache()
|
|
|
376
|
with self.db:
|
|
|
377
|
self.db.execute("""UPDATE sessions SET end=?, num_cmds=? WHERE
|
|
|
378
|
session==?""", (datetime.datetime.now(),
|
|
|
379
|
len(self.input_hist_parsed)-1, self.session_number))
|
|
|
380
|
self.session_number = 0
|
|
|
381
|
|
|
|
382
|
def name_session(self, name):
|
|
|
383
|
"""Give the current session a name in the history database."""
|
|
|
384
|
with self.db:
|
|
|
385
|
self.db.execute("UPDATE sessions SET remark=? WHERE session==?",
|
|
|
386
|
(name, self.session_number))
|
|
|
387
|
|
|
|
388
|
def reset(self, new_session=True):
|
|
|
389
|
"""Clear the session history, releasing all object references, and
|
|
|
390
|
optionally open a new session."""
|
|
|
391
|
self.output_hist.clear()
|
|
|
392
|
# The directory history can't be completely empty
|
|
|
393
|
self.dir_hist[:] = [os.getcwdu()]
|
|
|
394
|
|
|
|
395
|
if new_session:
|
|
|
396
|
if self.session_number:
|
|
|
397
|
self.end_session()
|
|
|
398
|
self.input_hist_parsed[:] = [""]
|
|
|
399
|
self.input_hist_raw[:] = [""]
|
|
|
400
|
self.new_session()
|
|
|
401
|
|
|
|
402
|
# ------------------------------
|
|
|
403
|
# Methods for retrieving history
|
|
|
404
|
# ------------------------------
|
|
|
405
|
def _get_range_session(self, start=1, stop=None, raw=True, output=False):
|
|
|
406
|
"""Get input and output history from the current session. Called by
|
|
|
407
|
get_range, and takes similar parameters."""
|
|
|
408
|
input_hist = self.input_hist_raw if raw else self.input_hist_parsed
|
|
|
409
|
|
|
|
410
|
n = len(input_hist)
|
|
|
411
|
if start < 0:
|
|
|
412
|
start += n
|
|
|
413
|
if not stop or (stop > n):
|
|
|
414
|
stop = n
|
|
|
415
|
elif stop < 0:
|
|
|
416
|
stop += n
|
|
|
417
|
|
|
|
418
|
for i in range(start, stop):
|
|
|
419
|
if output:
|
|
|
420
|
line = (input_hist[i], self.output_hist_reprs.get(i))
|
|
|
421
|
else:
|
|
|
422
|
line = input_hist[i]
|
|
|
423
|
yield (0, i, line)
|
|
|
424
|
|
|
|
425
|
def get_range(self, session=0, start=1, stop=None, raw=True,output=False):
|
|
|
426
|
"""Retrieve input by session.
|
|
|
427
|
|
|
|
428
|
Parameters
|
|
|
429
|
----------
|
|
|
430
|
session : int
|
|
|
431
|
Session number to retrieve. The current session is 0, and negative
|
|
|
432
|
numbers count back from current session, so -1 is previous session.
|
|
|
433
|
start : int
|
|
|
434
|
First line to retrieve.
|
|
|
435
|
stop : int
|
|
|
436
|
End of line range (excluded from output itself). If None, retrieve
|
|
|
437
|
to the end of the session.
|
|
|
438
|
raw : bool
|
|
|
439
|
If True, return untranslated input
|
|
|
440
|
output : bool
|
|
|
441
|
If True, attempt to include output. This will be 'real' Python
|
|
|
442
|
objects for the current session, or text reprs from previous
|
|
|
443
|
sessions if db_log_output was enabled at the time. Where no output
|
|
|
444
|
is found, None is used.
|
|
|
445
|
|
|
|
446
|
Returns
|
|
|
447
|
-------
|
|
|
448
|
An iterator over the desired lines. Each line is a 3-tuple, either
|
|
|
449
|
(session, line, input) if output is False, or
|
|
|
450
|
(session, line, (input, output)) if output is True.
|
|
|
451
|
"""
|
|
|
452
|
if session <= 0:
|
|
|
453
|
session += self.session_number
|
|
|
454
|
if session==self.session_number: # Current session
|
|
|
455
|
return self._get_range_session(start, stop, raw, output)
|
|
|
456
|
return super(HistoryManager, self).get_range(session, start, stop, raw, output)
|
|
|
457
|
|
|
384
|
458
|
## ----------------------------
|
|
385
|
459
|
## Methods for storing history:
|
|
386
|
460
|
## ----------------------------
|