##// END OF EJS Templates
Add more type in history.py (#14616)...
M Bussonnier -
r29019:89d672ee merge
parent child Browse files
Show More
@@ -1,5 +1,7
1 """ History related magics and functionality """
1 """ History related magics and functionality """
2
2
3 from __future__ import annotations
4
3 # Copyright (c) IPython Development Team.
5 # Copyright (c) IPython Development Team.
4 # Distributed under the terms of the Modified BSD License.
6 # Distributed under the terms of the Modified BSD License.
5
7
@@ -7,7 +9,8
7 import atexit
9 import atexit
8 import datetime
10 import datetime
9 import re
11 import re
10 import sqlite3
12
13
11 import threading
14 import threading
12 from pathlib import Path
15 from pathlib import Path
13
16
@@ -29,31 +32,55 from traitlets.config.configurable import LoggingConfigurable
29
32
30 from IPython.paths import locate_profile
33 from IPython.paths import locate_profile
31 from IPython.utils.decorators import undoc
34 from IPython.utils.decorators import undoc
35 from typing import Iterable, Tuple, Optional, TYPE_CHECKING
36 import typing
37
38 if TYPE_CHECKING:
39 from IPython.core.interactiveshell import InteractiveShell
40 from IPython.config.Configuration import Configuration
41
42 try:
43 from sqlite3 import DatabaseError, OperationalError
44 import sqlite3
45
46 sqlite3_found = True
47 except ModuleNotFoundError:
48 sqlite3_found = False
49
50 class DatabaseError(Exception): # type: ignore [no-redef]
51 pass
52
53 class OperationalError(Exception): # type: ignore [no-redef]
54 pass
55
56
57 InOrInOut = typing.Union[str, Tuple[str, Optional[str]]]
32
58
33 #-----------------------------------------------------------------------------
59 #-----------------------------------------------------------------------------
34 # Classes and functions
60 # Classes and functions
35 #-----------------------------------------------------------------------------
61 #-----------------------------------------------------------------------------
36
62
37 @undoc
63 @undoc
38 class DummyDB(object):
64 class DummyDB:
39 """Dummy DB that will act as a black hole for history.
65 """Dummy DB that will act as a black hole for history.
40
66
41 Only used in the absence of sqlite"""
67 Only used in the absence of sqlite"""
42 def execute(*args, **kwargs):
68
69 def execute(*args: typing.Any, **kwargs: typing.Any) -> typing.List:
43 return []
70 return []
44
71
45 def commit(self, *args, **kwargs):
72 def commit(self, *args, **kwargs): # type: ignore [no-untyped-def]
46 pass
73 pass
47
74
48 def __enter__(self, *args, **kwargs):
75 def __enter__(self, *args, **kwargs): # type: ignore [no-untyped-def]
49 pass
76 pass
50
77
51 def __exit__(self, *args, **kwargs):
78 def __exit__(self, *args, **kwargs): # type: ignore [no-untyped-def]
52 pass
79 pass
53
80
54
81
55 @decorator
82 @decorator
56 def only_when_enabled(f, self, *a, **kw):
83 def only_when_enabled(f, self, *a, **kw): # type: ignore [no-untyped-def]
57 """Decorator: return an empty list in the absence of sqlite."""
84 """Decorator: return an empty list in the absence of sqlite."""
58 if not self.enabled:
85 if not self.enabled:
59 return []
86 return []
@@ -66,7 +93,7 def only_when_enabled(f, self, *a, **kw):
66 _SAVE_DB_SIZE = 16384
93 _SAVE_DB_SIZE = 16384
67
94
68 @decorator
95 @decorator
69 def catch_corrupt_db(f, self, *a, **kw):
96 def catch_corrupt_db(f, self, *a, **kw): # type: ignore [no-untyped-def]
70 """A decorator which wraps HistoryAccessor method calls to catch errors from
97 """A decorator which wraps HistoryAccessor method calls to catch errors from
71 a corrupt SQLite database, move the old database out of the way, and create
98 a corrupt SQLite database, move the old database out of the way, and create
72 a new one.
99 a new one.
@@ -76,7 +103,7 def catch_corrupt_db(f, self, *a, **kw):
76 """
103 """
77 try:
104 try:
78 return f(self, *a, **kw)
105 return f(self, *a, **kw)
79 except (sqlite3.DatabaseError, sqlite3.OperationalError) as e:
106 except (DatabaseError, OperationalError) as e:
80 self._corrupt_db_counter += 1
107 self._corrupt_db_counter += 1
81 self.log.error("Failed to open SQLite history %s (%s).", self.hist_file, e)
108 self.log.error("Failed to open SQLite history %s (%s).", self.hist_file, e)
82 if self.hist_file != ':memory:':
109 if self.hist_file != ':memory:':
@@ -114,17 +141,39 def catch_corrupt_db(f, self, *a, **kw):
114 class HistoryAccessorBase(LoggingConfigurable):
141 class HistoryAccessorBase(LoggingConfigurable):
115 """An abstract class for History Accessors """
142 """An abstract class for History Accessors """
116
143
117 def get_tail(self, n=10, raw=True, output=False, include_latest=False):
144 def get_tail(
145 self,
146 n: int = 10,
147 raw: bool = True,
148 output: bool = False,
149 include_latest: bool = False,
150 ) -> Iterable[Tuple[int, int, InOrInOut]]:
118 raise NotImplementedError
151 raise NotImplementedError
119
152
120 def search(self, pattern="*", raw=True, search_raw=True,
153 def search(
121 output=False, n=None, unique=False):
154 self,
155 pattern: str = "*",
156 raw: bool = True,
157 search_raw: bool = True,
158 output: bool = False,
159 n: Optional[int] = None,
160 unique: bool = False,
161 ) -> Iterable[Tuple[int, int, InOrInOut]]:
122 raise NotImplementedError
162 raise NotImplementedError
123
163
124 def get_range(self, session, start=1, stop=None, raw=True,output=False):
164 def get_range(
165 self,
166 session: int,
167 start: int = 1,
168 stop: Optional[int] = None,
169 raw: bool = True,
170 output: bool = False,
171 ) -> Iterable[Tuple[int, int, InOrInOut]]:
125 raise NotImplementedError
172 raise NotImplementedError
126
173
127 def get_range_by_str(self, rangestr, raw=True, output=False):
174 def get_range_by_str(
175 self, rangestr: str, raw: bool = True, output: bool = False
176 ) -> Iterable[Tuple[int, int, InOrInOut]]:
128 raise NotImplementedError
177 raise NotImplementedError
129
178
130
179
@@ -160,7 +209,8 class HistoryAccessor(HistoryAccessorBase):
160 """,
209 """,
161 ).tag(config=True)
210 ).tag(config=True)
162
211
163 enabled = Bool(True,
212 enabled = Bool(
213 sqlite3_found,
164 help="""enable the SQLite history
214 help="""enable the SQLite history
165
215
166 set enabled=False to disable the SQLite history,
216 set enabled=False to disable the SQLite history,
@@ -179,13 +229,14 class HistoryAccessor(HistoryAccessorBase):
179 ).tag(config=True)
229 ).tag(config=True)
180
230
181 @default("connection_options")
231 @default("connection_options")
182 def _default_connection_options(self):
232 def _default_connection_options(self) -> typing.Dict[str, bool]:
183 return dict(check_same_thread=False)
233 return dict(check_same_thread=False)
184
234
185 # The SQLite database
235 # The SQLite database
186 db = Any()
236 db = Any()
187 @observe('db')
237 @observe('db')
188 def _db_changed(self, change):
238 @only_when_enabled
239 def _db_changed(self, change): # type: ignore [no-untyped-def]
189 """validate the db, since it can be an Instance of two different types"""
240 """validate the db, since it can be an Instance of two different types"""
190 new = change['new']
241 new = change['new']
191 connection_types = (DummyDB, sqlite3.Connection)
242 connection_types = (DummyDB, sqlite3.Connection)
@@ -194,7 +245,9 class HistoryAccessor(HistoryAccessorBase):
194 (self.__class__.__name__, new)
245 (self.__class__.__name__, new)
195 raise TraitError(msg)
246 raise TraitError(msg)
196
247
197 def __init__(self, profile="default", hist_file="", **traits):
248 def __init__(
249 self, profile: str = "default", hist_file: str = "", **traits: typing.Any
250 ) -> None:
198 """Create a new history accessor.
251 """Create a new history accessor.
199
252
200 Parameters
253 Parameters
@@ -222,7 +275,7 class HistoryAccessor(HistoryAccessorBase):
222
275
223 self.init_db()
276 self.init_db()
224
277
225 def _get_hist_file_name(self, profile='default'):
278 def _get_hist_file_name(self, profile: str = "default") -> Path:
226 """Find the history file for the given profile name.
279 """Find the history file for the given profile name.
227
280
228 This is overridden by the HistoryManager subclass, to use the shell's
281 This is overridden by the HistoryManager subclass, to use the shell's
@@ -236,7 +289,7 class HistoryAccessor(HistoryAccessorBase):
236 return Path(locate_profile(profile)) / "history.sqlite"
289 return Path(locate_profile(profile)) / "history.sqlite"
237
290
238 @catch_corrupt_db
291 @catch_corrupt_db
239 def init_db(self):
292 def init_db(self) -> None:
240 """Connect to the database, and create tables if necessary."""
293 """Connect to the database, and create tables if necessary."""
241 if not self.enabled:
294 if not self.enabled:
242 self.db = DummyDB()
295 self.db = DummyDB()
@@ -245,7 +298,7 class HistoryAccessor(HistoryAccessorBase):
245 # use detect_types so that timestamps return datetime objects
298 # use detect_types so that timestamps return datetime objects
246 kwargs = dict(detect_types=sqlite3.PARSE_DECLTYPES|sqlite3.PARSE_COLNAMES)
299 kwargs = dict(detect_types=sqlite3.PARSE_DECLTYPES|sqlite3.PARSE_COLNAMES)
247 kwargs.update(self.connection_options)
300 kwargs.update(self.connection_options)
248 self.db = sqlite3.connect(str(self.hist_file), **kwargs)
301 self.db = sqlite3.connect(str(self.hist_file), **kwargs) # type: ignore [call-overload]
249 with self.db:
302 with self.db:
250 self.db.execute(
303 self.db.execute(
251 """CREATE TABLE IF NOT EXISTS sessions (session integer
304 """CREATE TABLE IF NOT EXISTS sessions (session integer
@@ -267,7 +320,7 class HistoryAccessor(HistoryAccessorBase):
267 # success! reset corrupt db count
320 # success! reset corrupt db count
268 self._corrupt_db_counter = 0
321 self._corrupt_db_counter = 0
269
322
270 def writeout_cache(self):
323 def writeout_cache(self) -> None:
271 """Overridden by HistoryManager to dump the cache before certain
324 """Overridden by HistoryManager to dump the cache before certain
272 database lookups."""
325 database lookups."""
273 pass
326 pass
@@ -275,7 +328,14 class HistoryAccessor(HistoryAccessorBase):
275 ## -------------------------------
328 ## -------------------------------
276 ## Methods for retrieving history:
329 ## Methods for retrieving history:
277 ## -------------------------------
330 ## -------------------------------
278 def _run_sql(self, sql, params, raw=True, output=False, latest=False):
331 def _run_sql(
332 self,
333 sql: str,
334 params: typing.Tuple,
335 raw: bool = True,
336 output: bool = False,
337 latest: bool = False,
338 ) -> Iterable[Tuple[int, int, InOrInOut]]:
279 """Prepares and runs an SQL query for the history database.
339 """Prepares and runs an SQL query for the history database.
280
340
281 Parameters
341 Parameters
@@ -310,7 +370,9 class HistoryAccessor(HistoryAccessorBase):
310
370
311 @only_when_enabled
371 @only_when_enabled
312 @catch_corrupt_db
372 @catch_corrupt_db
313 def get_session_info(self, session):
373 def get_session_info(
374 self, session: int
375 ) -> Tuple[int, datetime.datetime, Optional[datetime.datetime], Optional[int], str]:
314 """Get info about a session.
376 """Get info about a session.
315
377
316 Parameters
378 Parameters
@@ -335,7 +397,7 class HistoryAccessor(HistoryAccessorBase):
335 return self.db.execute(query, (session,)).fetchone()
397 return self.db.execute(query, (session,)).fetchone()
336
398
337 @catch_corrupt_db
399 @catch_corrupt_db
338 def get_last_session_id(self):
400 def get_last_session_id(self) -> Optional[int]:
339 """Get the last session ID currently in the database.
401 """Get the last session ID currently in the database.
340
402
341 Within IPython, this should be the same as the value stored in
403 Within IPython, this should be the same as the value stored in
@@ -343,9 +405,16 class HistoryAccessor(HistoryAccessorBase):
343 """
405 """
344 for record in self.get_tail(n=1, include_latest=True):
406 for record in self.get_tail(n=1, include_latest=True):
345 return record[0]
407 return record[0]
408 return None
346
409
347 @catch_corrupt_db
410 @catch_corrupt_db
348 def get_tail(self, n=10, raw=True, output=False, include_latest=False):
411 def get_tail(
412 self,
413 n: int = 10,
414 raw: bool = True,
415 output: bool = False,
416 include_latest: bool = False,
417 ) -> Iterable[Tuple[int, int, InOrInOut]]:
349 """Get the last n lines from the history database.
418 """Get the last n lines from the history database.
350
419
351 Parameters
420 Parameters
@@ -374,8 +443,15 class HistoryAccessor(HistoryAccessorBase):
374 return reversed(list(cur))
443 return reversed(list(cur))
375
444
376 @catch_corrupt_db
445 @catch_corrupt_db
377 def search(self, pattern="*", raw=True, search_raw=True,
446 def search(
378 output=False, n=None, unique=False):
447 self,
448 pattern: str = "*",
449 raw: bool = True,
450 search_raw: bool = True,
451 output: bool = False,
452 n: Optional[int] = None,
453 unique: bool = False,
454 ) -> Iterable[Tuple[int, int, InOrInOut]]:
379 """Search the database using unix glob-style matching (wildcards
455 """Search the database using unix glob-style matching (wildcards
380 * and ?).
456 * and ?).
381
457
@@ -402,7 +478,7 class HistoryAccessor(HistoryAccessorBase):
402 tosearch = "history." + tosearch
478 tosearch = "history." + tosearch
403 self.writeout_cache()
479 self.writeout_cache()
404 sqlform = "WHERE %s GLOB ?" % tosearch
480 sqlform = "WHERE %s GLOB ?" % tosearch
405 params = (pattern,)
481 params: typing.Tuple[typing.Any, ...] = (pattern,)
406 if unique:
482 if unique:
407 sqlform += ' GROUP BY {0}'.format(tosearch)
483 sqlform += ' GROUP BY {0}'.format(tosearch)
408 if n is not None:
484 if n is not None:
@@ -416,7 +492,14 class HistoryAccessor(HistoryAccessorBase):
416 return cur
492 return cur
417
493
418 @catch_corrupt_db
494 @catch_corrupt_db
419 def get_range(self, session, start=1, stop=None, raw=True,output=False):
495 def get_range(
496 self,
497 session: int,
498 start: int = 1,
499 stop: Optional[int] = None,
500 raw: bool = True,
501 output: bool = False,
502 ) -> Iterable[Tuple[int, int, InOrInOut]]:
420 """Retrieve input by session.
503 """Retrieve input by session.
421
504
422 Parameters
505 Parameters
@@ -443,6 +526,7 class HistoryAccessor(HistoryAccessorBase):
443 (session, line, input) if output is False, or
526 (session, line, input) if output is False, or
444 (session, line, (input, output)) if output is True.
527 (session, line, (input, output)) if output is True.
445 """
528 """
529 params: typing.Tuple[typing.Any, ...]
446 if stop:
530 if stop:
447 lineclause = "line >= ? AND line < ?"
531 lineclause = "line >= ? AND line < ?"
448 params = (session, start, stop)
532 params = (session, start, stop)
@@ -453,7 +537,9 class HistoryAccessor(HistoryAccessorBase):
453 return self._run_sql("WHERE session==? AND %s" % lineclause,
537 return self._run_sql("WHERE session==? AND %s" % lineclause,
454 params, raw=raw, output=output)
538 params, raw=raw, output=output)
455
539
456 def get_range_by_str(self, rangestr, raw=True, output=False):
540 def get_range_by_str(
541 self, rangestr: str, raw: bool = True, output: bool = False
542 ) -> Iterable[Tuple[int, int, InOrInOut]]:
457 """Get lines of history from a string of ranges, as used by magic
543 """Get lines of history from a string of ranges, as used by magic
458 commands %hist, %save, %macro, etc.
544 commands %hist, %save, %macro, etc.
459
545
@@ -483,8 +569,9 class HistoryManager(HistoryAccessor):
483 # Public interface
569 # Public interface
484
570
485 # An instance of the IPython shell we are attached to
571 # An instance of the IPython shell we are attached to
486 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC',
572 shell = Instance(
487 allow_none=True)
573 "IPython.core.interactiveshell.InteractiveShellABC", allow_none=False
574 )
488 # Lists to hold processed and raw history. These start with a blank entry
575 # Lists to hold processed and raw history. These start with a blank entry
489 # so that we can index them starting from 1
576 # so that we can index them starting from 1
490 input_hist_parsed = List([""])
577 input_hist_parsed = List([""])
@@ -493,7 +580,7 class HistoryManager(HistoryAccessor):
493 dir_hist: List = List()
580 dir_hist: List = List()
494
581
495 @default("dir_hist")
582 @default("dir_hist")
496 def _dir_hist_default(self):
583 def _dir_hist_default(self) -> typing.List[Path]:
497 try:
584 try:
498 return [Path.cwd()]
585 return [Path.cwd()]
499 except OSError:
586 except OSError:
@@ -503,10 +590,10 class HistoryManager(HistoryAccessor):
503 # execution count.
590 # execution count.
504 output_hist = Dict()
591 output_hist = Dict()
505 # The text/plain repr of outputs.
592 # The text/plain repr of outputs.
506 output_hist_reprs = Dict()
593 output_hist_reprs: typing.Dict[int, str] = Dict() # type: ignore [assignment]
507
594
508 # The number of the current session in the history database
595 # The number of the current session in the history database
509 session_number = Integer()
596 session_number: int = Integer() # type: ignore [assignment]
510
597
511 db_log_output = Bool(False,
598 db_log_output = Bool(False,
512 help="Should the history database include output? (default: no)"
599 help="Should the history database include output? (default: no)"
@@ -516,13 +603,13 class HistoryManager(HistoryAccessor):
516 "Values of 1 or less effectively disable caching."
603 "Values of 1 or less effectively disable caching."
517 ).tag(config=True)
604 ).tag(config=True)
518 # The input and output caches
605 # The input and output caches
519 db_input_cache: List = List()
606 db_input_cache: List[Tuple[int, str, str]] = List()
520 db_output_cache: List = List()
607 db_output_cache: List[Tuple[int, str]] = List()
521
608
522 # History saving in separate thread
609 # History saving in separate thread
523 save_thread = Instance('IPython.core.history.HistorySavingThread',
610 save_thread = Instance('IPython.core.history.HistorySavingThread',
524 allow_none=True)
611 allow_none=True)
525 save_flag = Instance(threading.Event, allow_none=True)
612 save_flag = Instance(threading.Event, allow_none=False)
526
613
527 # Private interface
614 # Private interface
528 # Variables used to store the three last inputs from the user. On each new
615 # Variables used to store the three last inputs from the user. On each new
@@ -538,18 +625,21 class HistoryManager(HistoryAccessor):
538 # an exit call).
625 # an exit call).
539 _exit_re = re.compile(r"(exit|quit)(\s*\(.*\))?$")
626 _exit_re = re.compile(r"(exit|quit)(\s*\(.*\))?$")
540
627
541 def __init__(self, shell=None, config=None, **traits):
628 def __init__(
542 """Create a new history manager associated with a shell instance.
629 self,
543 """
630 shell: InteractiveShell,
544 super(HistoryManager, self).__init__(shell=shell, config=config,
631 config: Optional[Configuration] = None,
545 **traits)
632 **traits: typing.Any,
633 ):
634 """Create a new history manager associated with a shell instance."""
635 super().__init__(shell=shell, config=config, **traits)
546 self.save_flag = threading.Event()
636 self.save_flag = threading.Event()
547 self.db_input_cache_lock = threading.Lock()
637 self.db_input_cache_lock = threading.Lock()
548 self.db_output_cache_lock = threading.Lock()
638 self.db_output_cache_lock = threading.Lock()
549
639
550 try:
640 try:
551 self.new_session()
641 self.new_session()
552 except sqlite3.OperationalError:
642 except OperationalError:
553 self.log.error("Failed to create history session in %s. History will not be saved.",
643 self.log.error("Failed to create history session in %s. History will not be saved.",
554 self.hist_file, exc_info=True)
644 self.hist_file, exc_info=True)
555 self.hist_file = ':memory:'
645 self.hist_file = ':memory:'
@@ -565,7 +655,7 class HistoryManager(HistoryAccessor):
565 )
655 )
566 self.hist_file = ":memory:"
656 self.hist_file = ":memory:"
567
657
568 def _get_hist_file_name(self, profile=None):
658 def _get_hist_file_name(self, profile: Optional[str] = None) -> Path:
569 """Get default history file name based on the Shell's profile.
659 """Get default history file name based on the Shell's profile.
570
660
571 The profile parameter is ignored, but must exist for compatibility with
661 The profile parameter is ignored, but must exist for compatibility with
@@ -574,7 +664,7 class HistoryManager(HistoryAccessor):
574 return Path(profile_dir) / "history.sqlite"
664 return Path(profile_dir) / "history.sqlite"
575
665
576 @only_when_enabled
666 @only_when_enabled
577 def new_session(self, conn=None):
667 def new_session(self, conn: Optional[sqlite3.Connection] = None) -> None:
578 """Get a new session number."""
668 """Get a new session number."""
579 if conn is None:
669 if conn is None:
580 conn = self.db
670 conn = self.db
@@ -585,9 +675,10 class HistoryManager(HistoryAccessor):
585 NULL, '') """,
675 NULL, '') """,
586 (datetime.datetime.now().isoformat(" "),),
676 (datetime.datetime.now().isoformat(" "),),
587 )
677 )
678 assert isinstance(cur.lastrowid, int)
588 self.session_number = cur.lastrowid
679 self.session_number = cur.lastrowid
589
680
590 def end_session(self):
681 def end_session(self) -> None:
591 """Close the database session, filling in the end time and line count."""
682 """Close the database session, filling in the end time and line count."""
592 self.writeout_cache()
683 self.writeout_cache()
593 with self.db:
684 with self.db:
@@ -602,13 +693,13 class HistoryManager(HistoryAccessor):
602 )
693 )
603 self.session_number = 0
694 self.session_number = 0
604
695
605 def name_session(self, name):
696 def name_session(self, name: str) -> None:
606 """Give the current session a name in the history database."""
697 """Give the current session a name in the history database."""
607 with self.db:
698 with self.db:
608 self.db.execute("UPDATE sessions SET remark=? WHERE session==?",
699 self.db.execute("UPDATE sessions SET remark=? WHERE session==?",
609 (name, self.session_number))
700 (name, self.session_number))
610
701
611 def reset(self, new_session=True):
702 def reset(self, new_session: bool = True) -> None:
612 """Clear the session history, releasing all object references, and
703 """Clear the session history, releasing all object references, and
613 optionally open a new session."""
704 optionally open a new session."""
614 self.output_hist.clear()
705 self.output_hist.clear()
@@ -625,7 +716,9 class HistoryManager(HistoryAccessor):
625 # ------------------------------
716 # ------------------------------
626 # Methods for retrieving history
717 # Methods for retrieving history
627 # ------------------------------
718 # ------------------------------
628 def get_session_info(self, session=0):
719 def get_session_info(
720 self, session: int = 0
721 ) -> Tuple[int, datetime.datetime, Optional[datetime.datetime], Optional[int], str]:
629 """Get info about a session.
722 """Get info about a session.
630
723
631 Parameters
724 Parameters
@@ -653,7 +746,13 class HistoryManager(HistoryAccessor):
653 return super(HistoryManager, self).get_session_info(session=session)
746 return super(HistoryManager, self).get_session_info(session=session)
654
747
655 @catch_corrupt_db
748 @catch_corrupt_db
656 def get_tail(self, n=10, raw=True, output=False, include_latest=False):
749 def get_tail(
750 self,
751 n: int = 10,
752 raw: bool = True,
753 output: bool = False,
754 include_latest: bool = False,
755 ) -> Iterable[Tuple[int, int, InOrInOut]]:
657 """Get the last n lines from the history database.
756 """Get the last n lines from the history database.
658
757
659 Most recent entry last.
758 Most recent entry last.
@@ -697,7 +796,7 class HistoryManager(HistoryAccessor):
697 )
796 )
698 )
797 )
699
798
700 everything = this_cur + other_cur
799 everything: typing.List[Tuple[int, int, InOrInOut]] = this_cur + other_cur
701
800
702 everything = everything[:n]
801 everything = everything[:n]
703
802
@@ -705,7 +804,13 class HistoryManager(HistoryAccessor):
705 return list(everything)[:0:-1]
804 return list(everything)[:0:-1]
706 return list(everything)[::-1]
805 return list(everything)[::-1]
707
806
708 def _get_range_session(self, start=1, stop=None, raw=True, output=False):
807 def _get_range_session(
808 self,
809 start: int = 1,
810 stop: Optional[int] = None,
811 raw: bool = True,
812 output: bool = False,
813 ) -> Iterable[Tuple[int, int, InOrInOut]]:
709 """Get input and output history from the current session. Called by
814 """Get input and output history from the current session. Called by
710 get_range, and takes similar parameters."""
815 get_range, and takes similar parameters."""
711 input_hist = self.input_hist_raw if raw else self.input_hist_parsed
816 input_hist = self.input_hist_raw if raw else self.input_hist_parsed
@@ -717,7 +822,7 class HistoryManager(HistoryAccessor):
717 stop = n
822 stop = n
718 elif stop < 0:
823 elif stop < 0:
719 stop += n
824 stop += n
720
825 line: InOrInOut
721 for i in range(start, stop):
826 for i in range(start, stop):
722 if output:
827 if output:
723 line = (input_hist[i], self.output_hist_reprs.get(i))
828 line = (input_hist[i], self.output_hist_reprs.get(i))
@@ -725,7 +830,14 class HistoryManager(HistoryAccessor):
725 line = input_hist[i]
830 line = input_hist[i]
726 yield (0, i, line)
831 yield (0, i, line)
727
832
728 def get_range(self, session=0, start=1, stop=None, raw=True,output=False):
833 def get_range(
834 self,
835 session: int = 0,
836 start: int = 1,
837 stop: Optional[int] = None,
838 raw: bool = True,
839 output: bool = False,
840 ) -> Iterable[Tuple[int, int, InOrInOut]]:
729 """Retrieve input by session.
841 """Retrieve input by session.
730
842
731 Parameters
843 Parameters
@@ -763,7 +875,9 class HistoryManager(HistoryAccessor):
763 ## ----------------------------
875 ## ----------------------------
764 ## Methods for storing history:
876 ## Methods for storing history:
765 ## ----------------------------
877 ## ----------------------------
766 def store_inputs(self, line_num, source, source_raw=None):
878 def store_inputs(
879 self, line_num: int, source: str, source_raw: Optional[str] = None
880 ) -> None:
767 """Store source and raw input in history and create input cache
881 """Store source and raw input in history and create input cache
768 variables ``_i*``.
882 variables ``_i*``.
769
883
@@ -811,7 +925,7 class HistoryManager(HistoryAccessor):
811 if self.shell is not None:
925 if self.shell is not None:
812 self.shell.push(to_main, interactive=False)
926 self.shell.push(to_main, interactive=False)
813
927
814 def store_output(self, line_num):
928 def store_output(self, line_num: int) -> None:
815 """If database output logging is enabled, this saves all the
929 """If database output logging is enabled, this saves all the
816 outputs from the indicated prompt number to the database. It's
930 outputs from the indicated prompt number to the database. It's
817 called by run_cell after code has been executed.
931 called by run_cell after code has been executed.
@@ -823,6 +937,7 class HistoryManager(HistoryAccessor):
823 """
937 """
824 if (not self.db_log_output) or (line_num not in self.output_hist_reprs):
938 if (not self.db_log_output) or (line_num not in self.output_hist_reprs):
825 return
939 return
940 lnum: int = line_num
826 output = self.output_hist_reprs[line_num]
941 output = self.output_hist_reprs[line_num]
827
942
828 with self.db_output_cache_lock:
943 with self.db_output_cache_lock:
@@ -830,20 +945,20 class HistoryManager(HistoryAccessor):
830 if self.db_cache_size <= 1:
945 if self.db_cache_size <= 1:
831 self.save_flag.set()
946 self.save_flag.set()
832
947
833 def _writeout_input_cache(self, conn):
948 def _writeout_input_cache(self, conn: sqlite3.Connection) -> None:
834 with conn:
949 with conn:
835 for line in self.db_input_cache:
950 for line in self.db_input_cache:
836 conn.execute("INSERT INTO history VALUES (?, ?, ?, ?)",
951 conn.execute("INSERT INTO history VALUES (?, ?, ?, ?)",
837 (self.session_number,)+line)
952 (self.session_number,)+line)
838
953
839 def _writeout_output_cache(self, conn):
954 def _writeout_output_cache(self, conn: sqlite3.Connection) -> None:
840 with conn:
955 with conn:
841 for line in self.db_output_cache:
956 for line in self.db_output_cache:
842 conn.execute("INSERT INTO output_history VALUES (?, ?, ?)",
957 conn.execute("INSERT INTO output_history VALUES (?, ?, ?)",
843 (self.session_number,)+line)
958 (self.session_number,)+line)
844
959
845 @only_when_enabled
960 @only_when_enabled
846 def writeout_cache(self, conn=None):
961 def writeout_cache(self, conn: Optional[sqlite3.Connection] = None) -> None:
847 """Write any entries in the cache to the database."""
962 """Write any entries in the cache to the database."""
848 if conn is None:
963 if conn is None:
849 conn = self.db
964 conn = self.db
@@ -885,13 +1000,15 class HistorySavingThread(threading.Thread):
885 daemon = True
1000 daemon = True
886 stop_now = False
1001 stop_now = False
887 enabled = True
1002 enabled = True
888 def __init__(self, history_manager):
1003 history_manager: HistoryManager
1004
1005 def __init__(self, history_manager: HistoryManager) -> None:
889 super(HistorySavingThread, self).__init__(name="IPythonHistorySavingThread")
1006 super(HistorySavingThread, self).__init__(name="IPythonHistorySavingThread")
890 self.history_manager = history_manager
1007 self.history_manager = history_manager
891 self.enabled = history_manager.enabled
1008 self.enabled = history_manager.enabled
892
1009
893 @only_when_enabled
1010 @only_when_enabled
894 def run(self):
1011 def run(self) -> None:
895 atexit.register(self.stop)
1012 atexit.register(self.stop)
896 # We need a separate db connection per thread:
1013 # We need a separate db connection per thread:
897 try:
1014 try:
@@ -912,7 +1029,7 class HistorySavingThread(threading.Thread):
912 finally:
1029 finally:
913 atexit.unregister(self.stop)
1030 atexit.unregister(self.stop)
914
1031
915 def stop(self):
1032 def stop(self) -> None:
916 """This can be called from the main thread to safely stop this thread.
1033 """This can be called from the main thread to safely stop this thread.
917
1034
918 Note that it does not attempt to write out remaining history before
1035 Note that it does not attempt to write out remaining history before
@@ -933,7 +1050,7 range_re = re.compile(r"""
933 $""", re.VERBOSE)
1050 $""", re.VERBOSE)
934
1051
935
1052
936 def extract_hist_ranges(ranges_str):
1053 def extract_hist_ranges(ranges_str: str) -> Iterable[Tuple[int, int, Optional[int]]]:
937 """Turn a string of history ranges into 3-tuples of (session, start, stop).
1054 """Turn a string of history ranges into 3-tuples of (session, start, stop).
938
1055
939 Empty string results in a `[(0, 1, None)]`, i.e. "everything from current
1056 Empty string results in a `[(0, 1, None)]`, i.e. "everything from current
@@ -965,6 +1082,7 def extract_hist_ranges(ranges_str):
965 end = None # provide the entire session hist
1082 end = None # provide the entire session hist
966
1083
967 if rmatch.group("sep") == "-": # 1-3 == 1:4 --> [1, 2, 3]
1084 if rmatch.group("sep") == "-": # 1-3 == 1:4 --> [1, 2, 3]
1085 assert end is not None
968 end += 1
1086 end += 1
969 startsess = rmatch.group("startsess") or "0"
1087 startsess = rmatch.group("startsess") or "0"
970 endsess = rmatch.group("endsess") or startsess
1088 endsess = rmatch.group("endsess") or startsess
@@ -982,7 +1100,7 def extract_hist_ranges(ranges_str):
982 yield (endsess, 1, end)
1100 yield (endsess, 1, end)
983
1101
984
1102
985 def _format_lineno(session, line):
1103 def _format_lineno(session: int, line: int) -> str:
986 """Helper function to format line numbers properly."""
1104 """Helper function to format line numbers properly."""
987 if session == 0:
1105 if session == 0:
988 return str(line)
1106 return str(line)
@@ -199,7 +199,6 module = [
199 "IPython.core.formatters",
199 "IPython.core.formatters",
200 "IPython.core.getipython",
200 "IPython.core.getipython",
201 "IPython.core.guarded_eval",
201 "IPython.core.guarded_eval",
202 "IPython.core.history",
203 "IPython.core.historyapp",
202 "IPython.core.historyapp",
204 "IPython.core.hooks",
203 "IPython.core.hooks",
205 "IPython.core.inputtransformer",
204 "IPython.core.inputtransformer",
General Comments 0
You need to be logged in to leave comments. Login now