##// END OF EJS Templates
Fix HistoryAccessor.get_tail bug (#13666)...
Aleksey Bogdanov -
Show More
@@ -202,7 +202,6 b' class HistoryAccessor(HistoryAccessorBase):'
202 config : :class:`~traitlets.config.loader.Config`
202 config : :class:`~traitlets.config.loader.Config`
203 Config object. hist_file can also be set through this.
203 Config object. hist_file can also be set through this.
204 """
204 """
205 # We need a pointer back to the shell for various tasks.
206 super(HistoryAccessor, self).__init__(**traits)
205 super(HistoryAccessor, self).__init__(**traits)
207 # defer setting hist_file from kwarg until after init,
206 # defer setting hist_file from kwarg until after init,
208 # otherwise the default kwarg value would clobber any value
207 # otherwise the default kwarg value would clobber any value
@@ -344,11 +343,6 b' class HistoryAccessor(HistoryAccessorBase):'
344 def get_tail(self, n=10, raw=True, output=False, include_latest=False):
343 def get_tail(self, n=10, raw=True, output=False, include_latest=False):
345 """Get the last n lines from the history database.
344 """Get the last n lines from the history database.
346
345
347 Most recent entry last.
348
349 Completion will be reordered so that that the last ones are when
350 possible from current session.
351
352 Parameters
346 Parameters
353 ----------
347 ----------
354 n : int
348 n : int
@@ -367,31 +361,11 b' class HistoryAccessor(HistoryAccessorBase):'
367 self.writeout_cache()
361 self.writeout_cache()
368 if not include_latest:
362 if not include_latest:
369 n += 1
363 n += 1
370 # cursor/line/entry
364 cur = self._run_sql("ORDER BY session DESC, line DESC LIMIT ?",
371 this_cur = list(
365 (n,), raw=raw, output=output)
372 self._run_sql(
373 "WHERE session == ? ORDER BY line DESC LIMIT ? ",
374 (self.session_number, n),
375 raw=raw,
376 output=output,
377 )
378 )
379 other_cur = list(
380 self._run_sql(
381 "WHERE session != ? ORDER BY session DESC, line DESC LIMIT ?",
382 (self.session_number, n),
383 raw=raw,
384 output=output,
385 )
386 )
387
388 everything = this_cur + other_cur
389
390 everything = everything[:n]
391
392 if not include_latest:
366 if not include_latest:
393 return list(everything)[:0:-1]
367 return reversed(list(cur)[1:])
394 return list(everything)[::-1]
368 return reversed(list(cur))
395
369
396 @catch_corrupt_db
370 @catch_corrupt_db
397 def search(self, pattern="*", raw=True, search_raw=True,
371 def search(self, pattern="*", raw=True, search_raw=True,
@@ -560,7 +534,6 b' class HistoryManager(HistoryAccessor):'
560 def __init__(self, shell=None, config=None, **traits):
534 def __init__(self, shell=None, config=None, **traits):
561 """Create a new history manager associated with a shell instance.
535 """Create a new history manager associated with a shell instance.
562 """
536 """
563 # We need a pointer back to the shell for various tasks.
564 super(HistoryManager, self).__init__(shell=shell, config=config,
537 super(HistoryManager, self).__init__(shell=shell, config=config,
565 **traits)
538 **traits)
566 self.save_flag = threading.Event()
539 self.save_flag = threading.Event()
@@ -656,6 +629,59 b' class HistoryManager(HistoryAccessor):'
656
629
657 return super(HistoryManager, self).get_session_info(session=session)
630 return super(HistoryManager, self).get_session_info(session=session)
658
631
632 @catch_corrupt_db
633 def get_tail(self, n=10, raw=True, output=False, include_latest=False):
634 """Get the last n lines from the history database.
635
636 Most recent entry last.
637
638 Completion will be reordered so that that the last ones are when
639 possible from current session.
640
641 Parameters
642 ----------
643 n : int
644 The number of lines to get
645 raw, output : bool
646 See :meth:`get_range`
647 include_latest : bool
648 If False (default), n+1 lines are fetched, and the latest one
649 is discarded. This is intended to be used where the function
650 is called by a user command, which it should not return.
651
652 Returns
653 -------
654 Tuples as :meth:`get_range`
655 """
656 self.writeout_cache()
657 if not include_latest:
658 n += 1
659 # cursor/line/entry
660 this_cur = list(
661 self._run_sql(
662 "WHERE session == ? ORDER BY line DESC LIMIT ? ",
663 (self.session_number, n),
664 raw=raw,
665 output=output,
666 )
667 )
668 other_cur = list(
669 self._run_sql(
670 "WHERE session != ? ORDER BY session DESC, line DESC LIMIT ?",
671 (self.session_number, n),
672 raw=raw,
673 output=output,
674 )
675 )
676
677 everything = this_cur + other_cur
678
679 everything = everything[:n]
680
681 if not include_latest:
682 return list(everything)[:0:-1]
683 return list(everything)[::-1]
684
659 def _get_range_session(self, start=1, stop=None, raw=True, output=False):
685 def _get_range_session(self, start=1, stop=None, raw=True, output=False):
660 """Get input and output history from the current session. Called by
686 """Get input and output history from the current session. Called by
661 get_range, and takes similar parameters."""
687 get_range, and takes similar parameters."""
@@ -17,7 +17,7 b' from tempfile import TemporaryDirectory'
17 # our own packages
17 # our own packages
18 from traitlets.config.loader import Config
18 from traitlets.config.loader import Config
19
19
20 from IPython.core.history import HistoryManager, extract_hist_ranges
20 from IPython.core.history import HistoryAccessor, HistoryManager, extract_hist_ranges
21
21
22
22
23 def test_proper_default_encoding():
23 def test_proper_default_encoding():
@@ -227,3 +227,68 b' def test_histmanager_disabled():'
227
227
228 # hist_file should not be created
228 # hist_file should not be created
229 assert hist_file.exists() is False
229 assert hist_file.exists() is False
230
231 def test_get_tail_session_awareness():
232 """Test .get_tail() is:
233 - session specific in HistoryManager
234 - session agnostic in HistoryAccessor
235 same for .get_last_session_id()
236 """
237 ip = get_ipython()
238 with TemporaryDirectory() as tmpdir:
239 tmp_path = Path(tmpdir)
240 hist_file = tmp_path / "history.sqlite"
241 get_source = lambda x: x[2]
242
243 # hm1 creates a new session and adds history entries,
244 # ha catches up
245 hm1 = HistoryManager(shell=ip, hist_file=hist_file)
246 hm1_last_sid = hm1.get_last_session_id
247 ha = HistoryAccessor(hist_file=hist_file)
248 ha_last_sid = ha.get_last_session_id
249
250 hist1 = ["a=1", "b=1", "c=1"]
251 for i, h in enumerate(hist1 + [""], start=1):
252 hm1.store_inputs(i, h)
253 assert list(map(get_source, hm1.get_tail())) == hist1
254 assert list(map(get_source, ha.get_tail())) == hist1
255 sid1 = hm1_last_sid()
256 assert sid1 is not None
257 assert ha_last_sid() == sid1
258
259 # hm2 creates a new session and adds entries,
260 # ha catches up
261 hm2 = HistoryManager(shell=ip, hist_file=hist_file)
262 hm2_last_sid = hm2.get_last_session_id
263
264 hist2 = ["a=2", "b=2", "c=2"]
265 for i, h in enumerate(hist2 + [""], start=1):
266 hm2.store_inputs(i, h)
267 tail = hm2.get_tail(n=3)
268 assert list(map(get_source, tail)) == hist2
269 tail = ha.get_tail(n=3)
270 assert list(map(get_source, tail)) == hist2
271 sid2 = hm2_last_sid()
272 assert sid2 is not None
273 assert ha_last_sid() == sid2
274 assert sid2 != sid1
275
276 # but hm1 still maintains its point of reference
277 # and adding more entries to it doesn't change others
278 # immediate perspective
279 assert hm1_last_sid() == sid1
280 tail = hm1.get_tail(n=3)
281 assert list(map(get_source, tail)) == hist1
282
283 hist3 = ["a=3", "b=3", "c=3"]
284 for i, h in enumerate(hist3 + [""], start=5):
285 hm1.store_inputs(i, h)
286 tail = hm1.get_tail(n=7)
287 assert list(map(get_source, tail)) == hist1 + [""] + hist3
288 tail = hm2.get_tail(n=3)
289 assert list(map(get_source, tail)) == hist2
290 tail = ha.get_tail(n=3)
291 assert list(map(get_source, tail)) == hist2
292 assert hm1_last_sid() == sid1
293 assert hm2_last_sid() == sid2
294 assert ha_last_sid() == sid2
General Comments 0
You need to be logged in to leave comments. Login now