##// END OF EJS Templates
deprecare remark and reformat history.py
M Bussonnier -
Show More
@@ -34,6 +34,7 from IPython.paths import locate_profile
34 34 from IPython.utils.decorators import undoc
35 35 from typing import Iterable, Tuple, Optional, TYPE_CHECKING
36 36 import typing
37 from warnings import warn
37 38
38 39 if TYPE_CHECKING:
39 40 from IPython.core.interactiveshell import InteractiveShell
@@ -60,6 +61,7 InOrInOut = typing.Union[str, Tuple[str, Optional[str]]]
60 61 # Classes and functions
61 62 #-----------------------------------------------------------------------------
62 63
64
63 65 @undoc
64 66 class DummyDB:
65 67 """Dummy DB that will act as a black hole for history.
@@ -92,6 +94,7 def only_when_enabled(f, self, *a, **kw): # type: ignore [no-untyped-def]
92 94 # that should be at least 100 entries or so
93 95 _SAVE_DB_SIZE = 16384
94 96
97
95 98 @decorator
96 99 def catch_corrupt_db(f, self, *a, **kw): # type: ignore [no-untyped-def]
97 100 """A decorator which wraps HistoryAccessor method calls to catch errors from
@@ -106,10 +109,12 def catch_corrupt_db(f, self, *a, **kw): # type: ignore [no-untyped-def]
106 109 except (DatabaseError, OperationalError) as e:
107 110 self._corrupt_db_counter += 1
108 111 self.log.error("Failed to open SQLite history %s (%s).", self.hist_file, e)
109 if self.hist_file != ':memory:':
112 if self.hist_file != ":memory:":
110 113 if self._corrupt_db_counter > self._corrupt_db_limit:
111 self.hist_file = ':memory:'
112 self.log.error("Failed to load history too many times, history will not be saved.")
114 self.hist_file = ":memory:"
115 self.log.error(
116 "Failed to load history too many times, history will not be saved."
117 )
113 118 elif self.hist_file.is_file():
114 119 # move the file out of the way
115 120 base = str(self.hist_file.parent / self.hist_file.stem)
@@ -117,20 +122,22 def catch_corrupt_db(f, self, *a, **kw): # type: ignore [no-untyped-def]
117 122 size = self.hist_file.stat().st_size
118 123 if size >= _SAVE_DB_SIZE:
119 124 # if there's significant content, avoid clobbering
120 now = datetime.datetime.now().isoformat().replace(':', '.')
121 newpath = base + '-corrupt-' + now + ext
125 now = datetime.datetime.now().isoformat().replace(":", ".")
126 newpath = base + "-corrupt-" + now + ext
122 127 # don't clobber previous corrupt backups
123 128 for i in range(100):
124 129 if not Path(newpath).exists():
125 130 break
126 131 else:
127 newpath = base + '-corrupt-' + now + (u'-%i' % i) + ext
132 newpath = base + "-corrupt-" + now + ("-%i" % i) + ext
128 133 else:
129 134 # not much content, possibly empty; don't worry about clobbering
130 135 # maybe we should just delete it?
131 newpath = base + '-corrupt' + ext
136 newpath = base + "-corrupt" + ext
132 137 self.hist_file.rename(newpath)
133 self.log.error("History file was moved to %s and a new file created.", newpath)
138 self.log.error(
139 "History file was moved to %s and a new file created.", newpath
140 )
134 141 self.init_db()
135 142 return []
136 143 else:
@@ -234,15 +241,18 class HistoryAccessor(HistoryAccessorBase):
234 241
235 242 # The SQLite database
236 243 db = Any()
237 @observe('db')
244
245 @observe("db")
238 246 @only_when_enabled
239 247 def _db_changed(self, change): # type: ignore [no-untyped-def]
240 248 """validate the db, since it can be an Instance of two different types"""
241 new = change['new']
249 new = change["new"]
242 250 connection_types = (DummyDB, sqlite3.Connection)
243 251 if not isinstance(new, connection_types):
244 msg = "%s.db must be sqlite3 Connection or DummyDB, not %r" % \
245 (self.__class__.__name__, new)
252 msg = "%s.db must be sqlite3 Connection or DummyDB, not %r" % (
253 self.__class__.__name__,
254 new,
255 )
246 256 raise TraitError(msg)
247 257
248 258 def __init__(
@@ -353,7 +363,7 class HistoryAccessor(HistoryAccessorBase):
353 363 -------
354 364 Tuples as :meth:`get_range`
355 365 """
356 toget = 'source_raw' if raw else 'source'
366 toget = "source_raw" if raw else "source"
357 367 sqlfrom = "history"
358 368 if output:
359 369 sqlfrom = "history LEFT JOIN output_history USING (session, line)"
@@ -390,7 +400,7 class HistoryAccessor(HistoryAccessorBase):
390 400 Timestamp for the end of the session, or None if IPython crashed.
391 401 num_cmds : int
392 402 Number of commands run, or None if IPython crashed.
393 remark : unicode
403 remark : str
394 404 A manually set description.
395 405 """
396 406 query = "SELECT * from sessions where session == ?"
@@ -480,7 +490,7 class HistoryAccessor(HistoryAccessorBase):
480 490 sqlform = "WHERE %s GLOB ?" % tosearch
481 491 params: typing.Tuple[typing.Any, ...] = (pattern,)
482 492 if unique:
483 sqlform += ' GROUP BY {0}'.format(tosearch)
493 sqlform += " GROUP BY {0}".format(tosearch)
484 494 if n is not None:
485 495 sqlform += " ORDER BY session DESC, line DESC LIMIT ?"
486 496 params += (n,)
@@ -534,8 +544,9 class HistoryAccessor(HistoryAccessorBase):
534 544 lineclause = "line>=?"
535 545 params = (session, start)
536 546
537 return self._run_sql("WHERE session==? AND %s" % lineclause,
538 params, raw=raw, output=output)
547 return self._run_sql(
548 "WHERE session==? AND %s" % lineclause, params, raw=raw, output=output
549 )
539 550
540 551 def get_range_by_str(
541 552 self, rangestr: str, raw: bool = True, output: bool = False
@@ -564,8 +575,8 class HistoryAccessor(HistoryAccessorBase):
564 575
565 576
566 577 class HistoryManager(HistoryAccessor):
567 """A class to organize all history-related functionality in one place.
568 """
578 """A class to organize all history-related functionality in one place."""
579
569 580 # Public interface
570 581
571 582 # An instance of the IPython shell we are attached to
@@ -595,20 +606,20 class HistoryManager(HistoryAccessor):
595 606 # The number of the current session in the history database
596 607 session_number: int = Integer() # type: ignore [assignment]
597 608
598 db_log_output = Bool(False,
599 help="Should the history database include output? (default: no)"
609 db_log_output = Bool(
610 False, help="Should the history database include output? (default: no)"
600 611 ).tag(config=True)
601 db_cache_size = Integer(0,
612 db_cache_size = Integer(
613 0,
602 614 help="Write to database every x commands (higher values save disk access & power).\n"
603 "Values of 1 or less effectively disable caching."
615 "Values of 1 or less effectively disable caching.",
604 616 ).tag(config=True)
605 617 # The input and output caches
606 618 db_input_cache: List[Tuple[int, str, str]] = List()
607 619 db_output_cache: List[Tuple[int, str]] = List()
608 620
609 621 # History saving in separate thread
610 save_thread = Instance('IPython.core.history.HistorySavingThread',
611 allow_none=True)
622 save_thread = Instance("IPython.core.history.HistorySavingThread", allow_none=True)
612 623 save_flag = Instance(threading.Event, allow_none=False)
613 624
614 625 # Private interface
@@ -640,11 +651,14 class HistoryManager(HistoryAccessor):
640 651 try:
641 652 self.new_session()
642 653 except OperationalError:
643 self.log.error("Failed to create history session in %s. History will not be saved.",
644 self.hist_file, exc_info=True)
645 self.hist_file = ':memory:'
654 self.log.error(
655 "Failed to create history session in %s. History will not be saved.",
656 self.hist_file,
657 exc_info=True,
658 )
659 self.hist_file = ":memory:"
646 660
647 if self.enabled and self.hist_file != ':memory:':
661 if self.enabled and self.hist_file != ":memory:":
648 662 self.save_thread = HistorySavingThread(self)
649 663 try:
650 664 self.save_thread.start()
@@ -695,9 +709,16 class HistoryManager(HistoryAccessor):
695 709
696 710 def name_session(self, name: str) -> None:
697 711 """Give the current session a name in the history database."""
712 warn(
713 "name_session is deprecated in IPython 9.0 and will be removed in future versions",
714 DeprecationWarning,
715 stacklevel=2,
716 )
698 717 with self.db:
699 self.db.execute("UPDATE sessions SET remark=? WHERE session==?",
700 (name, self.session_number))
718 self.db.execute(
719 "UPDATE sessions SET remark=? WHERE session==?",
720 (name, self.session_number),
721 )
701 722
702 723 def reset(self, new_session: bool = True) -> None:
703 724 """Clear the session history, releasing all object references, and
@@ -737,7 +758,7 class HistoryManager(HistoryAccessor):
737 758 Timestamp for the end of the session, or None if IPython crashed.
738 759 num_cmds : int
739 760 Number of commands run, or None if IPython crashed.
740 remark : unicode
761 remark : str
741 762 A manually set description.
742 763 """
743 764 if session <= 0:
@@ -869,8 +890,7 class HistoryManager(HistoryAccessor):
869 890 session += self.session_number
870 891 if session==self.session_number: # Current session
871 892 return self._get_range_session(start, stop, raw, output)
872 return super(HistoryManager, self).get_range(session, start, stop, raw,
873 output)
893 return super(HistoryManager, self).get_range(session, start, stop, raw, output)
874 894
875 895 ## ----------------------------
876 896 ## Methods for storing history:
@@ -893,8 +913,8 class HistoryManager(HistoryAccessor):
893 913 """
894 914 if source_raw is None:
895 915 source_raw = source
896 source = source.rstrip('\n')
897 source_raw = source_raw.rstrip('\n')
916 source = source.rstrip("\n")
917 source_raw = source_raw.rstrip("\n")
898 918
899 919 # do not store exit/quit commands
900 920 if self._exit_re.match(source_raw.strip()):
@@ -916,11 +936,8 class HistoryManager(HistoryAccessor):
916 936 self._i00 = source_raw
917 937
918 938 # hackish access to user namespace to create _i1,_i2... dynamically
919 new_i = '_i%s' % line_num
920 to_main = {'_i': self._i,
921 '_ii': self._ii,
922 '_iii': self._iii,
923 new_i : self._i00 }
939 new_i = "_i%s" % line_num
940 to_main = {"_i": self._i, "_ii": self._ii, "_iii": self._iii, new_i: self._i00}
924 941
925 942 if self.shell is not None:
926 943 self.shell.push(to_main, interactive=False)
@@ -947,14 +964,18 class HistoryManager(HistoryAccessor):
947 964 def _writeout_input_cache(self, conn: sqlite3.Connection) -> None:
948 965 with conn:
949 966 for line in self.db_input_cache:
950 conn.execute("INSERT INTO history VALUES (?, ?, ?, ?)",
951 (self.session_number,)+line)
967 conn.execute(
968 "INSERT INTO history VALUES (?, ?, ?, ?)",
969 (self.session_number,) + line,
970 )
952 971
953 972 def _writeout_output_cache(self, conn: sqlite3.Connection) -> None:
954 973 with conn:
955 974 for line in self.db_output_cache:
956 conn.execute("INSERT INTO output_history VALUES (?, ?, ?)",
957 (self.session_number,)+line)
975 conn.execute(
976 "INSERT INTO output_history VALUES (?, ?, ?)",
977 (self.session_number,) + line,
978 )
958 979
959 980 @only_when_enabled
960 981 def writeout_cache(self, conn: Optional[sqlite3.Connection] = None) -> None:
@@ -967,9 +988,11 class HistoryManager(HistoryAccessor):
967 988 self._writeout_input_cache(conn)
968 989 except sqlite3.IntegrityError:
969 990 self.new_session(conn)
970 print("ERROR! Session/line number was not unique in",
991 print(
992 "ERROR! Session/line number was not unique in",
971 993 "database. History logging moved to new session",
972 self.session_number)
994 self.session_number,
995 )
973 996 try:
974 997 # Try writing to the new session. If this fails, don't
975 998 # recurse
@@ -983,8 +1006,10 class HistoryManager(HistoryAccessor):
983 1006 try:
984 1007 self._writeout_output_cache(conn)
985 1008 except sqlite3.IntegrityError:
986 print("!! Session/line number for output was not unique",
987 "in database. Output will not be stored.")
1009 print(
1010 "!! Session/line number for output was not unique",
1011 "in database. Output will not be stored.",
1012 )
988 1013 finally:
989 1014 self.db_output_cache = []
990 1015
@@ -996,6 +1021,7 class HistorySavingThread(threading.Thread):
996 1021 It waits for the HistoryManager's save_flag to be set, then writes out
997 1022 the history cache. The main thread is responsible for setting the flag when
998 1023 the cache size reaches a defined threshold."""
1024
999 1025 daemon = True
1000 1026 stop_now = False
1001 1027 enabled = True
@@ -1023,8 +1049,13 class HistorySavingThread(threading.Thread):
1023 1049 self.history_manager.save_flag.clear()
1024 1050 self.history_manager.writeout_cache(self.db)
1025 1051 except Exception as e:
1026 print(("The history saving thread hit an unexpected error (%s)."
1027 "History will not be written to the database.") % repr(e))
1052 print(
1053 (
1054 "The history saving thread hit an unexpected error (%s)."
1055 "History will not be written to the database."
1056 )
1057 % repr(e)
1058 )
1028 1059 finally:
1029 1060 atexit.unregister(self.stop)
1030 1061
@@ -1040,13 +1071,16 class HistorySavingThread(threading.Thread):
1040 1071
1041 1072
1042 1073 # To match, e.g. ~5/8-~2/3
1043 range_re = re.compile(r"""
1074 range_re = re.compile(
1075 r"""
1044 1076 ((?P<startsess>~?\d+)/)?
1045 1077 (?P<start>\d+)?
1046 1078 ((?P<sep>[\-:])
1047 1079 ((?P<endsess>~?\d+)/)?
1048 1080 (?P<end>\d+))?
1049 $""", re.VERBOSE)
1081 $""",
1082 re.VERBOSE,
1083 )
1050 1084
1051 1085
1052 1086 def extract_hist_ranges(ranges_str: str) -> Iterable[Tuple[int, int, Optional[int]]]:
@@ -1075,7 +1109,7 def extract_hist_ranges(ranges_str: str) -> Iterable[Tuple[int, int, Optional[in
1075 1109 # If no end specified, get (a, a + 1)
1076 1110 end = int(end) if end else start + 1
1077 1111 else: # start not specified
1078 if not rmatch.group('startsess'): # no startsess
1112 if not rmatch.group("startsess"): # no startsess
1079 1113 continue
1080 1114 start = 1
1081 1115 end = None # provide the entire session hist
General Comments 0
You need to be logged in to leave comments. Login now