##// END OF EJS Templates
Add a number of types to history.py...
M Bussonnier -
Show More
@@ -1,989 +1,1107
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
6
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
14 from decorator import decorator
17 from decorator import decorator
15 from traitlets import (
18 from traitlets import (
16 Any,
19 Any,
17 Bool,
20 Bool,
18 Dict,
21 Dict,
19 Instance,
22 Instance,
20 Integer,
23 Integer,
21 List,
24 List,
22 TraitError,
25 TraitError,
23 Unicode,
26 Unicode,
24 Union,
27 Union,
25 default,
28 default,
26 observe,
29 observe,
27 )
30 )
28 from traitlets.config.configurable import LoggingConfigurable
31 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 []
60 else:
87 else:
61 return f(self, *a, **kw)
88 return f(self, *a, **kw)
62
89
63
90
64 # use 16kB as threshold for whether a corrupt history db should be saved
91 # use 16kB as threshold for whether a corrupt history db should be saved
65 # that should be at least 100 entries or so
92 # that should be at least 100 entries or so
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.
73
100
74 We avoid clobbering larger databases because this may be triggered due to filesystem issues,
101 We avoid clobbering larger databases because this may be triggered due to filesystem issues,
75 not just a corrupt file.
102 not just a corrupt file.
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:':
83 if self._corrupt_db_counter > self._corrupt_db_limit:
110 if self._corrupt_db_counter > self._corrupt_db_limit:
84 self.hist_file = ':memory:'
111 self.hist_file = ':memory:'
85 self.log.error("Failed to load history too many times, history will not be saved.")
112 self.log.error("Failed to load history too many times, history will not be saved.")
86 elif self.hist_file.is_file():
113 elif self.hist_file.is_file():
87 # move the file out of the way
114 # move the file out of the way
88 base = str(self.hist_file.parent / self.hist_file.stem)
115 base = str(self.hist_file.parent / self.hist_file.stem)
89 ext = self.hist_file.suffix
116 ext = self.hist_file.suffix
90 size = self.hist_file.stat().st_size
117 size = self.hist_file.stat().st_size
91 if size >= _SAVE_DB_SIZE:
118 if size >= _SAVE_DB_SIZE:
92 # if there's significant content, avoid clobbering
119 # if there's significant content, avoid clobbering
93 now = datetime.datetime.now().isoformat().replace(':', '.')
120 now = datetime.datetime.now().isoformat().replace(':', '.')
94 newpath = base + '-corrupt-' + now + ext
121 newpath = base + '-corrupt-' + now + ext
95 # don't clobber previous corrupt backups
122 # don't clobber previous corrupt backups
96 for i in range(100):
123 for i in range(100):
97 if not Path(newpath).exists():
124 if not Path(newpath).exists():
98 break
125 break
99 else:
126 else:
100 newpath = base + '-corrupt-' + now + (u'-%i' % i) + ext
127 newpath = base + '-corrupt-' + now + (u'-%i' % i) + ext
101 else:
128 else:
102 # not much content, possibly empty; don't worry about clobbering
129 # not much content, possibly empty; don't worry about clobbering
103 # maybe we should just delete it?
130 # maybe we should just delete it?
104 newpath = base + '-corrupt' + ext
131 newpath = base + '-corrupt' + ext
105 self.hist_file.rename(newpath)
132 self.hist_file.rename(newpath)
106 self.log.error("History file was moved to %s and a new file created.", newpath)
133 self.log.error("History file was moved to %s and a new file created.", newpath)
107 self.init_db()
134 self.init_db()
108 return []
135 return []
109 else:
136 else:
110 # Failed with :memory:, something serious is wrong
137 # Failed with :memory:, something serious is wrong
111 raise
138 raise
112
139
113
140
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
131 class HistoryAccessor(HistoryAccessorBase):
180 class HistoryAccessor(HistoryAccessorBase):
132 """Access the history database without adding to it.
181 """Access the history database without adding to it.
133
182
134 This is intended for use by standalone history tools. IPython shells use
183 This is intended for use by standalone history tools. IPython shells use
135 HistoryManager, below, which is a subclass of this."""
184 HistoryManager, below, which is a subclass of this."""
136
185
137 # counter for init_db retries, so we don't keep trying over and over
186 # counter for init_db retries, so we don't keep trying over and over
138 _corrupt_db_counter = 0
187 _corrupt_db_counter = 0
139 # after two failures, fallback on :memory:
188 # after two failures, fallback on :memory:
140 _corrupt_db_limit = 2
189 _corrupt_db_limit = 2
141
190
142 # String holding the path to the history file
191 # String holding the path to the history file
143 hist_file = Union(
192 hist_file = Union(
144 [Instance(Path), Unicode()],
193 [Instance(Path), Unicode()],
145 help="""Path to file to use for SQLite history database.
194 help="""Path to file to use for SQLite history database.
146
195
147 By default, IPython will put the history database in the IPython
196 By default, IPython will put the history database in the IPython
148 profile directory. If you would rather share one history among
197 profile directory. If you would rather share one history among
149 profiles, you can set this value in each, so that they are consistent.
198 profiles, you can set this value in each, so that they are consistent.
150
199
151 Due to an issue with fcntl, SQLite is known to misbehave on some NFS
200 Due to an issue with fcntl, SQLite is known to misbehave on some NFS
152 mounts. If you see IPython hanging, try setting this to something on a
201 mounts. If you see IPython hanging, try setting this to something on a
153 local disk, e.g::
202 local disk, e.g::
154
203
155 ipython --HistoryManager.hist_file=/tmp/ipython_hist.sqlite
204 ipython --HistoryManager.hist_file=/tmp/ipython_hist.sqlite
156
205
157 you can also use the specific value `:memory:` (including the colon
206 you can also use the specific value `:memory:` (including the colon
158 at both end but not the back ticks), to avoid creating an history file.
207 at both end but not the back ticks), to avoid creating an history file.
159
208
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,
167 in which case there will be no stored history, no SQLite connection,
217 in which case there will be no stored history, no SQLite connection,
168 and no background saving thread. This may be necessary in some
218 and no background saving thread. This may be necessary in some
169 threaded environments where IPython is embedded.
219 threaded environments where IPython is embedded.
170 """,
220 """,
171 ).tag(config=True)
221 ).tag(config=True)
172
222
173 connection_options = Dict(
223 connection_options = Dict(
174 help="""Options for configuring the SQLite connection
224 help="""Options for configuring the SQLite connection
175
225
176 These options are passed as keyword args to sqlite3.connect
226 These options are passed as keyword args to sqlite3.connect
177 when establishing database connections.
227 when establishing database connections.
178 """
228 """
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)
192 if not isinstance(new, connection_types):
243 if not isinstance(new, connection_types):
193 msg = "%s.db must be sqlite3 Connection or DummyDB, not %r" % \
244 msg = "%s.db must be sqlite3 Connection or DummyDB, not %r" % \
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
201 ----------
254 ----------
202 profile : str
255 profile : str
203 The name of the profile from which to open history.
256 The name of the profile from which to open history.
204 hist_file : str
257 hist_file : str
205 Path to an SQLite history database stored by IPython. If specified,
258 Path to an SQLite history database stored by IPython. If specified,
206 hist_file overrides profile.
259 hist_file overrides profile.
207 config : :class:`~traitlets.config.loader.Config`
260 config : :class:`~traitlets.config.loader.Config`
208 Config object. hist_file can also be set through this.
261 Config object. hist_file can also be set through this.
209 """
262 """
210 super(HistoryAccessor, self).__init__(**traits)
263 super(HistoryAccessor, self).__init__(**traits)
211 # defer setting hist_file from kwarg until after init,
264 # defer setting hist_file from kwarg until after init,
212 # otherwise the default kwarg value would clobber any value
265 # otherwise the default kwarg value would clobber any value
213 # set by config
266 # set by config
214 if hist_file:
267 if hist_file:
215 self.hist_file = hist_file
268 self.hist_file = hist_file
216
269
217 try:
270 try:
218 self.hist_file
271 self.hist_file
219 except TraitError:
272 except TraitError:
220 # No one has set the hist_file, yet.
273 # No one has set the hist_file, yet.
221 self.hist_file = self._get_hist_file_name(profile)
274 self.hist_file = self._get_hist_file_name(profile)
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
229 active profile.
282 active profile.
230
283
231 Parameters
284 Parameters
232 ----------
285 ----------
233 profile : str
286 profile : str
234 The name of a profile which has a history file.
287 The name of a profile which has a history file.
235 """
288 """
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()
243 return
296 return
244
297
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
252 primary key autoincrement, start timestamp,
305 primary key autoincrement, start timestamp,
253 end timestamp, num_cmds integer, remark text)"""
306 end timestamp, num_cmds integer, remark text)"""
254 )
307 )
255 self.db.execute(
308 self.db.execute(
256 """CREATE TABLE IF NOT EXISTS history
309 """CREATE TABLE IF NOT EXISTS history
257 (session integer, line integer, source text, source_raw text,
310 (session integer, line integer, source text, source_raw text,
258 PRIMARY KEY (session, line))"""
311 PRIMARY KEY (session, line))"""
259 )
312 )
260 # Output history is optional, but ensure the table's there so it can be
313 # Output history is optional, but ensure the table's there so it can be
261 # enabled later.
314 # enabled later.
262 self.db.execute(
315 self.db.execute(
263 """CREATE TABLE IF NOT EXISTS output_history
316 """CREATE TABLE IF NOT EXISTS output_history
264 (session integer, line integer, output text,
317 (session integer, line integer, output text,
265 PRIMARY KEY (session, line))"""
318 PRIMARY KEY (session, line))"""
266 )
319 )
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
274
327
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
282 ----------
342 ----------
283 sql : str
343 sql : str
284 Any filtering expressions to go after SELECT ... FROM ...
344 Any filtering expressions to go after SELECT ... FROM ...
285 params : tuple
345 params : tuple
286 Parameters passed to the SQL query (to replace "?")
346 Parameters passed to the SQL query (to replace "?")
287 raw, output : bool
347 raw, output : bool
288 See :meth:`get_range`
348 See :meth:`get_range`
289 latest : bool
349 latest : bool
290 Select rows with max (session, line)
350 Select rows with max (session, line)
291
351
292 Returns
352 Returns
293 -------
353 -------
294 Tuples as :meth:`get_range`
354 Tuples as :meth:`get_range`
295 """
355 """
296 toget = 'source_raw' if raw else 'source'
356 toget = 'source_raw' if raw else 'source'
297 sqlfrom = "history"
357 sqlfrom = "history"
298 if output:
358 if output:
299 sqlfrom = "history LEFT JOIN output_history USING (session, line)"
359 sqlfrom = "history LEFT JOIN output_history USING (session, line)"
300 toget = "history.%s, output_history.output" % toget
360 toget = "history.%s, output_history.output" % toget
301 if latest:
361 if latest:
302 toget += ", MAX(session * 128 * 1024 + line)"
362 toget += ", MAX(session * 128 * 1024 + line)"
303 this_querry = "SELECT session, line, %s FROM %s " % (toget, sqlfrom) + sql
363 this_querry = "SELECT session, line, %s FROM %s " % (toget, sqlfrom) + sql
304 cur = self.db.execute(this_querry, params)
364 cur = self.db.execute(this_querry, params)
305 if latest:
365 if latest:
306 cur = (row[:-1] for row in cur)
366 cur = (row[:-1] for row in cur)
307 if output: # Regroup into 3-tuples, and parse JSON
367 if output: # Regroup into 3-tuples, and parse JSON
308 return ((ses, lin, (inp, out)) for ses, lin, inp, out in cur)
368 return ((ses, lin, (inp, out)) for ses, lin, inp, out in cur)
309 return cur
369 return cur
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
317 ----------
379 ----------
318 session : int
380 session : int
319 Session number to retrieve.
381 Session number to retrieve.
320
382
321 Returns
383 Returns
322 -------
384 -------
323 session_id : int
385 session_id : int
324 Session ID number
386 Session ID number
325 start : datetime
387 start : datetime
326 Timestamp for the start of the session.
388 Timestamp for the start of the session.
327 end : datetime
389 end : datetime
328 Timestamp for the end of the session, or None if IPython crashed.
390 Timestamp for the end of the session, or None if IPython crashed.
329 num_cmds : int
391 num_cmds : int
330 Number of commands run, or None if IPython crashed.
392 Number of commands run, or None if IPython crashed.
331 remark : unicode
393 remark : unicode
332 A manually set description.
394 A manually set description.
333 """
395 """
334 query = "SELECT * from sessions where session == ?"
396 query = "SELECT * from sessions where session == ?"
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
342 :attr:`HistoryManager.session_number`.
404 :attr:`HistoryManager.session_number`.
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
352 ----------
421 ----------
353 n : int
422 n : int
354 The number of lines to get
423 The number of lines to get
355 raw, output : bool
424 raw, output : bool
356 See :meth:`get_range`
425 See :meth:`get_range`
357 include_latest : bool
426 include_latest : bool
358 If False (default), n+1 lines are fetched, and the latest one
427 If False (default), n+1 lines are fetched, and the latest one
359 is discarded. This is intended to be used where the function
428 is discarded. This is intended to be used where the function
360 is called by a user command, which it should not return.
429 is called by a user command, which it should not return.
361
430
362 Returns
431 Returns
363 -------
432 -------
364 Tuples as :meth:`get_range`
433 Tuples as :meth:`get_range`
365 """
434 """
366 self.writeout_cache()
435 self.writeout_cache()
367 if not include_latest:
436 if not include_latest:
368 n += 1
437 n += 1
369 cur = self._run_sql(
438 cur = self._run_sql(
370 "ORDER BY session DESC, line DESC LIMIT ?", (n,), raw=raw, output=output
439 "ORDER BY session DESC, line DESC LIMIT ?", (n,), raw=raw, output=output
371 )
440 )
372 if not include_latest:
441 if not include_latest:
373 return reversed(list(cur)[1:])
442 return reversed(list(cur)[1:])
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
382 Parameters
458 Parameters
383 ----------
459 ----------
384 pattern : str
460 pattern : str
385 The wildcarded pattern to match when searching
461 The wildcarded pattern to match when searching
386 search_raw : bool
462 search_raw : bool
387 If True, search the raw input, otherwise, the parsed input
463 If True, search the raw input, otherwise, the parsed input
388 raw, output : bool
464 raw, output : bool
389 See :meth:`get_range`
465 See :meth:`get_range`
390 n : None or int
466 n : None or int
391 If an integer is given, it defines the limit of
467 If an integer is given, it defines the limit of
392 returned entries.
468 returned entries.
393 unique : bool
469 unique : bool
394 When it is true, return only unique entries.
470 When it is true, return only unique entries.
395
471
396 Returns
472 Returns
397 -------
473 -------
398 Tuples as :meth:`get_range`
474 Tuples as :meth:`get_range`
399 """
475 """
400 tosearch = "source_raw" if search_raw else "source"
476 tosearch = "source_raw" if search_raw else "source"
401 if output:
477 if output:
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:
409 sqlform += " ORDER BY session DESC, line DESC LIMIT ?"
485 sqlform += " ORDER BY session DESC, line DESC LIMIT ?"
410 params += (n,)
486 params += (n,)
411 elif unique:
487 elif unique:
412 sqlform += " ORDER BY session, line"
488 sqlform += " ORDER BY session, line"
413 cur = self._run_sql(sqlform, params, raw=raw, output=output, latest=unique)
489 cur = self._run_sql(sqlform, params, raw=raw, output=output, latest=unique)
414 if n is not None:
490 if n is not None:
415 return reversed(list(cur))
491 return reversed(list(cur))
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
423 ----------
506 ----------
424 session : int
507 session : int
425 Session number to retrieve.
508 Session number to retrieve.
426 start : int
509 start : int
427 First line to retrieve.
510 First line to retrieve.
428 stop : int
511 stop : int
429 End of line range (excluded from output itself). If None, retrieve
512 End of line range (excluded from output itself). If None, retrieve
430 to the end of the session.
513 to the end of the session.
431 raw : bool
514 raw : bool
432 If True, return untranslated input
515 If True, return untranslated input
433 output : bool
516 output : bool
434 If True, attempt to include output. This will be 'real' Python
517 If True, attempt to include output. This will be 'real' Python
435 objects for the current session, or text reprs from previous
518 objects for the current session, or text reprs from previous
436 sessions if db_log_output was enabled at the time. Where no output
519 sessions if db_log_output was enabled at the time. Where no output
437 is found, None is used.
520 is found, None is used.
438
521
439 Returns
522 Returns
440 -------
523 -------
441 entries
524 entries
442 An iterator over the desired lines. Each line is a 3-tuple, either
525 An iterator over the desired lines. Each line is a 3-tuple, either
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)
449 else:
533 else:
450 lineclause = "line>=?"
534 lineclause = "line>=?"
451 params = (session, start)
535 params = (session, start)
452
536
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
460 Parameters
546 Parameters
461 ----------
547 ----------
462 rangestr : str
548 rangestr : str
463 A string specifying ranges, e.g. "5 ~2/1-4". If empty string is used,
549 A string specifying ranges, e.g. "5 ~2/1-4". If empty string is used,
464 this will return everything from current session's history.
550 this will return everything from current session's history.
465
551
466 See the documentation of :func:`%history` for the full details.
552 See the documentation of :func:`%history` for the full details.
467
553
468 raw, output : bool
554 raw, output : bool
469 As :meth:`get_range`
555 As :meth:`get_range`
470
556
471 Returns
557 Returns
472 -------
558 -------
473 Tuples as :meth:`get_range`
559 Tuples as :meth:`get_range`
474 """
560 """
475 for sess, s, e in extract_hist_ranges(rangestr):
561 for sess, s, e in extract_hist_ranges(rangestr):
476 for line in self.get_range(sess, s, e, raw=raw, output=output):
562 for line in self.get_range(sess, s, e, raw=raw, output=output):
477 yield line
563 yield line
478
564
479
565
480 class HistoryManager(HistoryAccessor):
566 class HistoryManager(HistoryAccessor):
481 """A class to organize all history-related functionality in one place.
567 """A class to organize all history-related functionality in one place.
482 """
568 """
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([""])
491 input_hist_raw = List([""])
578 input_hist_raw = List([""])
492 # A list of directories visited during session
579 # A list of directories visited during session
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:
500 return []
587 return []
501
588
502 # A dict of output history, keyed with ints from the shell's
589 # A dict of output history, keyed with ints from the shell's
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)"
513 ).tag(config=True)
600 ).tag(config=True)
514 db_cache_size = Integer(0,
601 db_cache_size = Integer(0,
515 help="Write to database every x commands (higher values save disk access & power).\n"
602 help="Write to database every x commands (higher values save disk access & power).\n"
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
529 # history update, we populate the user's namespace with these, shifted as
616 # history update, we populate the user's namespace with these, shifted as
530 # necessary.
617 # necessary.
531 _i00 = Unicode("")
618 _i00 = Unicode("")
532 _i = Unicode("")
619 _i = Unicode("")
533 _ii = Unicode("")
620 _ii = Unicode("")
534 _iii = Unicode("")
621 _iii = Unicode("")
535
622
536 # A regex matching all forms of the exit command, so that we don't store
623 # A regex matching all forms of the exit command, so that we don't store
537 # them in the history (it's annoying to rewind the first entry and land on
624 # them in the history (it's annoying to rewind the first entry and land on
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:'
556
646
557 if self.enabled and self.hist_file != ':memory:':
647 if self.enabled and self.hist_file != ':memory:':
558 self.save_thread = HistorySavingThread(self)
648 self.save_thread = HistorySavingThread(self)
559 try:
649 try:
560 self.save_thread.start()
650 self.save_thread.start()
561 except RuntimeError:
651 except RuntimeError:
562 self.log.error(
652 self.log.error(
563 "Failed to start history saving thread. History will not be saved.",
653 "Failed to start history saving thread. History will not be saved.",
564 exc_info=True,
654 exc_info=True,
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
572 the parent class."""
662 the parent class."""
573 profile_dir = self.shell.profile_dir.location
663 profile_dir = self.shell.profile_dir.location
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
581
671
582 with conn:
672 with conn:
583 cur = conn.execute(
673 cur = conn.execute(
584 """INSERT INTO sessions VALUES (NULL, ?, NULL,
674 """INSERT INTO sessions VALUES (NULL, ?, NULL,
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:
594 self.db.execute(
685 self.db.execute(
595 """UPDATE sessions SET end=?, num_cmds=? WHERE
686 """UPDATE sessions SET end=?, num_cmds=? WHERE
596 session==?""",
687 session==?""",
597 (
688 (
598 datetime.datetime.now().isoformat(" "),
689 datetime.datetime.now().isoformat(" "),
599 len(self.input_hist_parsed) - 1,
690 len(self.input_hist_parsed) - 1,
600 self.session_number,
691 self.session_number,
601 ),
692 ),
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()
615 # The directory history can't be completely empty
706 # The directory history can't be completely empty
616 self.dir_hist[:] = [Path.cwd()]
707 self.dir_hist[:] = [Path.cwd()]
617
708
618 if new_session:
709 if new_session:
619 if self.session_number:
710 if self.session_number:
620 self.end_session()
711 self.end_session()
621 self.input_hist_parsed[:] = [""]
712 self.input_hist_parsed[:] = [""]
622 self.input_hist_raw[:] = [""]
713 self.input_hist_raw[:] = [""]
623 self.new_session()
714 self.new_session()
624
715
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
632 ----------
725 ----------
633 session : int
726 session : int
634 Session number to retrieve. The current session is 0, and negative
727 Session number to retrieve. The current session is 0, and negative
635 numbers count back from current session, so -1 is the previous session.
728 numbers count back from current session, so -1 is the previous session.
636
729
637 Returns
730 Returns
638 -------
731 -------
639 session_id : int
732 session_id : int
640 Session ID number
733 Session ID number
641 start : datetime
734 start : datetime
642 Timestamp for the start of the session.
735 Timestamp for the start of the session.
643 end : datetime
736 end : datetime
644 Timestamp for the end of the session, or None if IPython crashed.
737 Timestamp for the end of the session, or None if IPython crashed.
645 num_cmds : int
738 num_cmds : int
646 Number of commands run, or None if IPython crashed.
739 Number of commands run, or None if IPython crashed.
647 remark : unicode
740 remark : unicode
648 A manually set description.
741 A manually set description.
649 """
742 """
650 if session <= 0:
743 if session <= 0:
651 session += self.session_number
744 session += self.session_number
652
745
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.
660
759
661 Completion will be reordered so that that the last ones are when
760 Completion will be reordered so that that the last ones are when
662 possible from current session.
761 possible from current session.
663
762
664 Parameters
763 Parameters
665 ----------
764 ----------
666 n : int
765 n : int
667 The number of lines to get
766 The number of lines to get
668 raw, output : bool
767 raw, output : bool
669 See :meth:`get_range`
768 See :meth:`get_range`
670 include_latest : bool
769 include_latest : bool
671 If False (default), n+1 lines are fetched, and the latest one
770 If False (default), n+1 lines are fetched, and the latest one
672 is discarded. This is intended to be used where the function
771 is discarded. This is intended to be used where the function
673 is called by a user command, which it should not return.
772 is called by a user command, which it should not return.
674
773
675 Returns
774 Returns
676 -------
775 -------
677 Tuples as :meth:`get_range`
776 Tuples as :meth:`get_range`
678 """
777 """
679 self.writeout_cache()
778 self.writeout_cache()
680 if not include_latest:
779 if not include_latest:
681 n += 1
780 n += 1
682 # cursor/line/entry
781 # cursor/line/entry
683 this_cur = list(
782 this_cur = list(
684 self._run_sql(
783 self._run_sql(
685 "WHERE session == ? ORDER BY line DESC LIMIT ? ",
784 "WHERE session == ? ORDER BY line DESC LIMIT ? ",
686 (self.session_number, n),
785 (self.session_number, n),
687 raw=raw,
786 raw=raw,
688 output=output,
787 output=output,
689 )
788 )
690 )
789 )
691 other_cur = list(
790 other_cur = list(
692 self._run_sql(
791 self._run_sql(
693 "WHERE session != ? ORDER BY session DESC, line DESC LIMIT ?",
792 "WHERE session != ? ORDER BY session DESC, line DESC LIMIT ?",
694 (self.session_number, n),
793 (self.session_number, n),
695 raw=raw,
794 raw=raw,
696 output=output,
795 output=output,
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
704 if not include_latest:
803 if not include_latest:
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
712
817
713 n = len(input_hist)
818 n = len(input_hist)
714 if start < 0:
819 if start < 0:
715 start += n
820 start += n
716 if not stop or (stop > n):
821 if not stop or (stop > n):
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))
724 else:
829 else:
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
732 ----------
844 ----------
733 session : int
845 session : int
734 Session number to retrieve. The current session is 0, and negative
846 Session number to retrieve. The current session is 0, and negative
735 numbers count back from current session, so -1 is previous session.
847 numbers count back from current session, so -1 is previous session.
736 start : int
848 start : int
737 First line to retrieve.
849 First line to retrieve.
738 stop : int
850 stop : int
739 End of line range (excluded from output itself). If None, retrieve
851 End of line range (excluded from output itself). If None, retrieve
740 to the end of the session.
852 to the end of the session.
741 raw : bool
853 raw : bool
742 If True, return untranslated input
854 If True, return untranslated input
743 output : bool
855 output : bool
744 If True, attempt to include output. This will be 'real' Python
856 If True, attempt to include output. This will be 'real' Python
745 objects for the current session, or text reprs from previous
857 objects for the current session, or text reprs from previous
746 sessions if db_log_output was enabled at the time. Where no output
858 sessions if db_log_output was enabled at the time. Where no output
747 is found, None is used.
859 is found, None is used.
748
860
749 Returns
861 Returns
750 -------
862 -------
751 entries
863 entries
752 An iterator over the desired lines. Each line is a 3-tuple, either
864 An iterator over the desired lines. Each line is a 3-tuple, either
753 (session, line, input) if output is False, or
865 (session, line, input) if output is False, or
754 (session, line, (input, output)) if output is True.
866 (session, line, (input, output)) if output is True.
755 """
867 """
756 if session <= 0:
868 if session <= 0:
757 session += self.session_number
869 session += self.session_number
758 if session==self.session_number: # Current session
870 if session==self.session_number: # Current session
759 return self._get_range_session(start, stop, raw, output)
871 return self._get_range_session(start, stop, raw, output)
760 return super(HistoryManager, self).get_range(session, start, stop, raw,
872 return super(HistoryManager, self).get_range(session, start, stop, raw,
761 output)
873 output)
762
874
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
770 Parameters
884 Parameters
771 ----------
885 ----------
772 line_num : int
886 line_num : int
773 The prompt number of this input.
887 The prompt number of this input.
774 source : str
888 source : str
775 Python input.
889 Python input.
776 source_raw : str, optional
890 source_raw : str, optional
777 If given, this is the raw input without any IPython transformations
891 If given, this is the raw input without any IPython transformations
778 applied to it. If not given, ``source`` is used.
892 applied to it. If not given, ``source`` is used.
779 """
893 """
780 if source_raw is None:
894 if source_raw is None:
781 source_raw = source
895 source_raw = source
782 source = source.rstrip('\n')
896 source = source.rstrip('\n')
783 source_raw = source_raw.rstrip('\n')
897 source_raw = source_raw.rstrip('\n')
784
898
785 # do not store exit/quit commands
899 # do not store exit/quit commands
786 if self._exit_re.match(source_raw.strip()):
900 if self._exit_re.match(source_raw.strip()):
787 return
901 return
788
902
789 self.input_hist_parsed.append(source)
903 self.input_hist_parsed.append(source)
790 self.input_hist_raw.append(source_raw)
904 self.input_hist_raw.append(source_raw)
791
905
792 with self.db_input_cache_lock:
906 with self.db_input_cache_lock:
793 self.db_input_cache.append((line_num, source, source_raw))
907 self.db_input_cache.append((line_num, source, source_raw))
794 # Trigger to flush cache and write to DB.
908 # Trigger to flush cache and write to DB.
795 if len(self.db_input_cache) >= self.db_cache_size:
909 if len(self.db_input_cache) >= self.db_cache_size:
796 self.save_flag.set()
910 self.save_flag.set()
797
911
798 # update the auto _i variables
912 # update the auto _i variables
799 self._iii = self._ii
913 self._iii = self._ii
800 self._ii = self._i
914 self._ii = self._i
801 self._i = self._i00
915 self._i = self._i00
802 self._i00 = source_raw
916 self._i00 = source_raw
803
917
804 # hackish access to user namespace to create _i1,_i2... dynamically
918 # hackish access to user namespace to create _i1,_i2... dynamically
805 new_i = '_i%s' % line_num
919 new_i = '_i%s' % line_num
806 to_main = {'_i': self._i,
920 to_main = {'_i': self._i,
807 '_ii': self._ii,
921 '_ii': self._ii,
808 '_iii': self._iii,
922 '_iii': self._iii,
809 new_i : self._i00 }
923 new_i : self._i00 }
810
924
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.
818
932
819 Parameters
933 Parameters
820 ----------
934 ----------
821 line_num : int
935 line_num : int
822 The line number from which to save outputs
936 The line number from which to save outputs
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:
829 self.db_output_cache.append((line_num, output))
944 self.db_output_cache.append((line_num, output))
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
850
965
851 with self.db_input_cache_lock:
966 with self.db_input_cache_lock:
852 try:
967 try:
853 self._writeout_input_cache(conn)
968 self._writeout_input_cache(conn)
854 except sqlite3.IntegrityError:
969 except sqlite3.IntegrityError:
855 self.new_session(conn)
970 self.new_session(conn)
856 print("ERROR! Session/line number was not unique in",
971 print("ERROR! Session/line number was not unique in",
857 "database. History logging moved to new session",
972 "database. History logging moved to new session",
858 self.session_number)
973 self.session_number)
859 try:
974 try:
860 # Try writing to the new session. If this fails, don't
975 # Try writing to the new session. If this fails, don't
861 # recurse
976 # recurse
862 self._writeout_input_cache(conn)
977 self._writeout_input_cache(conn)
863 except sqlite3.IntegrityError:
978 except sqlite3.IntegrityError:
864 pass
979 pass
865 finally:
980 finally:
866 self.db_input_cache = []
981 self.db_input_cache = []
867
982
868 with self.db_output_cache_lock:
983 with self.db_output_cache_lock:
869 try:
984 try:
870 self._writeout_output_cache(conn)
985 self._writeout_output_cache(conn)
871 except sqlite3.IntegrityError:
986 except sqlite3.IntegrityError:
872 print("!! Session/line number for output was not unique",
987 print("!! Session/line number for output was not unique",
873 "in database. Output will not be stored.")
988 "in database. Output will not be stored.")
874 finally:
989 finally:
875 self.db_output_cache = []
990 self.db_output_cache = []
876
991
877
992
878 class HistorySavingThread(threading.Thread):
993 class HistorySavingThread(threading.Thread):
879 """This thread takes care of writing history to the database, so that
994 """This thread takes care of writing history to the database, so that
880 the UI isn't held up while that happens.
995 the UI isn't held up while that happens.
881
996
882 It waits for the HistoryManager's save_flag to be set, then writes out
997 It waits for the HistoryManager's save_flag to be set, then writes out
883 the history cache. The main thread is responsible for setting the flag when
998 the history cache. The main thread is responsible for setting the flag when
884 the cache size reaches a defined threshold."""
999 the cache size reaches a defined threshold."""
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:
898 self.db = sqlite3.connect(
1015 self.db = sqlite3.connect(
899 str(self.history_manager.hist_file),
1016 str(self.history_manager.hist_file),
900 **self.history_manager.connection_options,
1017 **self.history_manager.connection_options,
901 )
1018 )
902 while True:
1019 while True:
903 self.history_manager.save_flag.wait()
1020 self.history_manager.save_flag.wait()
904 if self.stop_now:
1021 if self.stop_now:
905 self.db.close()
1022 self.db.close()
906 return
1023 return
907 self.history_manager.save_flag.clear()
1024 self.history_manager.save_flag.clear()
908 self.history_manager.writeout_cache(self.db)
1025 self.history_manager.writeout_cache(self.db)
909 except Exception as e:
1026 except Exception as e:
910 print(("The history saving thread hit an unexpected error (%s)."
1027 print(("The history saving thread hit an unexpected error (%s)."
911 "History will not be written to the database.") % repr(e))
1028 "History will not be written to the database.") % repr(e))
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
919 exiting. That should be done by calling the HistoryManager's
1036 exiting. That should be done by calling the HistoryManager's
920 end_session method."""
1037 end_session method."""
921 self.stop_now = True
1038 self.stop_now = True
922 self.history_manager.save_flag.set()
1039 self.history_manager.save_flag.set()
923 self.join()
1040 self.join()
924
1041
925
1042
926 # To match, e.g. ~5/8-~2/3
1043 # To match, e.g. ~5/8-~2/3
927 range_re = re.compile(r"""
1044 range_re = re.compile(r"""
928 ((?P<startsess>~?\d+)/)?
1045 ((?P<startsess>~?\d+)/)?
929 (?P<start>\d+)?
1046 (?P<start>\d+)?
930 ((?P<sep>[\-:])
1047 ((?P<sep>[\-:])
931 ((?P<endsess>~?\d+)/)?
1048 ((?P<endsess>~?\d+)/)?
932 (?P<end>\d+))?
1049 (?P<end>\d+))?
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
940 session".
1057 session".
941
1058
942 Examples
1059 Examples
943 --------
1060 --------
944 >>> list(extract_hist_ranges("~8/5-~7/4 2"))
1061 >>> list(extract_hist_ranges("~8/5-~7/4 2"))
945 [(-8, 5, None), (-7, 1, 5), (0, 2, 3)]
1062 [(-8, 5, None), (-7, 1, 5), (0, 2, 3)]
946 """
1063 """
947 if ranges_str == "":
1064 if ranges_str == "":
948 yield (0, 1, None) # Everything from current session
1065 yield (0, 1, None) # Everything from current session
949 return
1066 return
950
1067
951 for range_str in ranges_str.split():
1068 for range_str in ranges_str.split():
952 rmatch = range_re.match(range_str)
1069 rmatch = range_re.match(range_str)
953 if not rmatch:
1070 if not rmatch:
954 continue
1071 continue
955 start = rmatch.group("start")
1072 start = rmatch.group("start")
956 if start:
1073 if start:
957 start = int(start)
1074 start = int(start)
958 end = rmatch.group("end")
1075 end = rmatch.group("end")
959 # If no end specified, get (a, a + 1)
1076 # If no end specified, get (a, a + 1)
960 end = int(end) if end else start + 1
1077 end = int(end) if end else start + 1
961 else: # start not specified
1078 else: # start not specified
962 if not rmatch.group('startsess'): # no startsess
1079 if not rmatch.group('startsess'): # no startsess
963 continue
1080 continue
964 start = 1
1081 start = 1
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
971 startsess = int(startsess.replace("~","-"))
1089 startsess = int(startsess.replace("~","-"))
972 endsess = int(endsess.replace("~","-"))
1090 endsess = int(endsess.replace("~","-"))
973 assert endsess >= startsess, "start session must be earlier than end session"
1091 assert endsess >= startsess, "start session must be earlier than end session"
974
1092
975 if endsess == startsess:
1093 if endsess == startsess:
976 yield (startsess, start, end)
1094 yield (startsess, start, end)
977 continue
1095 continue
978 # Multiple sessions in one range:
1096 # Multiple sessions in one range:
979 yield (startsess, start, None)
1097 yield (startsess, start, None)
980 for sess in range(startsess+1, endsess):
1098 for sess in range(startsess+1, endsess):
981 yield (sess, 1, None)
1099 yield (sess, 1, None)
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)
989 return "%s#%s" % (session, line)
1107 return "%s#%s" % (session, line)
@@ -1,388 +1,387
1 [build-system]
1 [build-system]
2 requires = ["setuptools>=61.2"]
2 requires = ["setuptools>=61.2"]
3 # We need access to the 'setupbase' module at build time.
3 # We need access to the 'setupbase' module at build time.
4 # Hence we declare a custom build backend.
4 # Hence we declare a custom build backend.
5 build-backend = "_build_meta" # just re-exports setuptools.build_meta definitions
5 build-backend = "_build_meta" # just re-exports setuptools.build_meta definitions
6 backend-path = ["."]
6 backend-path = ["."]
7
7
8 [project]
8 [project]
9 name = "ipython"
9 name = "ipython"
10 description = "IPython: Productive Interactive Computing"
10 description = "IPython: Productive Interactive Computing"
11 keywords = ["Interactive", "Interpreter", "Shell", "Embedding"]
11 keywords = ["Interactive", "Interpreter", "Shell", "Embedding"]
12 classifiers = [
12 classifiers = [
13 "Framework :: IPython",
13 "Framework :: IPython",
14 "Framework :: Jupyter",
14 "Framework :: Jupyter",
15 "Intended Audience :: Developers",
15 "Intended Audience :: Developers",
16 "Intended Audience :: Science/Research",
16 "Intended Audience :: Science/Research",
17 "License :: OSI Approved :: BSD License",
17 "License :: OSI Approved :: BSD License",
18 "Programming Language :: Python",
18 "Programming Language :: Python",
19 "Programming Language :: Python :: 3",
19 "Programming Language :: Python :: 3",
20 "Programming Language :: Python :: 3 :: Only",
20 "Programming Language :: Python :: 3 :: Only",
21 "Topic :: System :: Shells",
21 "Topic :: System :: Shells",
22 ]
22 ]
23 requires-python = ">=3.11"
23 requires-python = ">=3.11"
24 dependencies = [
24 dependencies = [
25 'colorama; sys_platform == "win32"',
25 'colorama; sys_platform == "win32"',
26 "decorator",
26 "decorator",
27 "jedi>=0.16",
27 "jedi>=0.16",
28 "matplotlib-inline",
28 "matplotlib-inline",
29 'pexpect>4.3; sys_platform != "win32" and sys_platform != "emscripten"',
29 'pexpect>4.3; sys_platform != "win32" and sys_platform != "emscripten"',
30 "prompt_toolkit>=3.0.41,<3.1.0",
30 "prompt_toolkit>=3.0.41,<3.1.0",
31 "pygments>=2.4.0",
31 "pygments>=2.4.0",
32 "stack_data",
32 "stack_data",
33 "traitlets>=5.13.0",
33 "traitlets>=5.13.0",
34 "typing_extensions>=4.6; python_version<'3.12'",
34 "typing_extensions>=4.6; python_version<'3.12'",
35 ]
35 ]
36 dynamic = ["authors", "license", "version"]
36 dynamic = ["authors", "license", "version"]
37
37
38 [project.entry-points."pygments.lexers"]
38 [project.entry-points."pygments.lexers"]
39 ipythonconsole = "IPython.lib.lexers:IPythonConsoleLexer"
39 ipythonconsole = "IPython.lib.lexers:IPythonConsoleLexer"
40 ipython = "IPython.lib.lexers:IPythonLexer"
40 ipython = "IPython.lib.lexers:IPythonLexer"
41 ipython3 = "IPython.lib.lexers:IPython3Lexer"
41 ipython3 = "IPython.lib.lexers:IPython3Lexer"
42
42
43 [project.scripts]
43 [project.scripts]
44 ipython = "IPython:start_ipython"
44 ipython = "IPython:start_ipython"
45 ipython3 = "IPython:start_ipython"
45 ipython3 = "IPython:start_ipython"
46
46
47 [project.readme]
47 [project.readme]
48 file = "long_description.rst"
48 file = "long_description.rst"
49 content-type = "text/x-rst"
49 content-type = "text/x-rst"
50
50
51 [project.urls]
51 [project.urls]
52 Homepage = "https://ipython.org"
52 Homepage = "https://ipython.org"
53 Documentation = "https://ipython.readthedocs.io/"
53 Documentation = "https://ipython.readthedocs.io/"
54 Funding = "https://numfocus.org/"
54 Funding = "https://numfocus.org/"
55 Source = "https://github.com/ipython/ipython"
55 Source = "https://github.com/ipython/ipython"
56 Tracker = "https://github.com/ipython/ipython/issues"
56 Tracker = "https://github.com/ipython/ipython/issues"
57
57
58 [project.optional-dependencies]
58 [project.optional-dependencies]
59 black = [
59 black = [
60 "black",
60 "black",
61 ]
61 ]
62 doc = [
62 doc = [
63 "docrepr",
63 "docrepr",
64 "exceptiongroup",
64 "exceptiongroup",
65 "intersphinx_registry",
65 "intersphinx_registry",
66 "ipykernel",
66 "ipykernel",
67 "ipython[test]",
67 "ipython[test]",
68 "matplotlib",
68 "matplotlib",
69 "setuptools>=18.5",
69 "setuptools>=18.5",
70 "sphinx-rtd-theme",
70 "sphinx-rtd-theme",
71 "sphinx>=1.3",
71 "sphinx>=1.3",
72 "sphinxcontrib-jquery",
72 "sphinxcontrib-jquery",
73 ]
73 ]
74 kernel = [
74 kernel = [
75 "ipykernel",
75 "ipykernel",
76 ]
76 ]
77 nbconvert = [
77 nbconvert = [
78 "nbconvert",
78 "nbconvert",
79 ]
79 ]
80 nbformat = [
80 nbformat = [
81 "nbformat",
81 "nbformat",
82 ]
82 ]
83 notebook = [
83 notebook = [
84 "ipywidgets",
84 "ipywidgets",
85 "notebook",
85 "notebook",
86 ]
86 ]
87 parallel = [
87 parallel = [
88 "ipyparallel",
88 "ipyparallel",
89 ]
89 ]
90 qtconsole = [
90 qtconsole = [
91 "qtconsole",
91 "qtconsole",
92 ]
92 ]
93 terminal = []
93 terminal = []
94 test = [
94 test = [
95 "pytest",
95 "pytest",
96 "pytest-asyncio<0.22",
96 "pytest-asyncio<0.22",
97 "testpath",
97 "testpath",
98 "pickleshare",
98 "pickleshare",
99 "packaging",
99 "packaging",
100 ]
100 ]
101 test_extra = [
101 test_extra = [
102 "ipython[test]",
102 "ipython[test]",
103 "curio",
103 "curio",
104 "matplotlib!=3.2.0",
104 "matplotlib!=3.2.0",
105 "nbformat",
105 "nbformat",
106 "numpy>=1.23",
106 "numpy>=1.23",
107 "pandas",
107 "pandas",
108 "trio",
108 "trio",
109 ]
109 ]
110 matplotlib = [
110 matplotlib = [
111 "matplotlib"
111 "matplotlib"
112 ]
112 ]
113 all = [
113 all = [
114 "ipython[black,doc,kernel,nbconvert,nbformat,notebook,parallel,qtconsole,matplotlib]",
114 "ipython[black,doc,kernel,nbconvert,nbformat,notebook,parallel,qtconsole,matplotlib]",
115 "ipython[test,test_extra]",
115 "ipython[test,test_extra]",
116 ]
116 ]
117
117
118 [tool.mypy]
118 [tool.mypy]
119 python_version = "3.10"
119 python_version = "3.10"
120 ignore_missing_imports = true
120 ignore_missing_imports = true
121 follow_imports = 'silent'
121 follow_imports = 'silent'
122 exclude = [
122 exclude = [
123 'test_\.+\.py',
123 'test_\.+\.py',
124 'IPython.utils.tests.test_wildcard',
124 'IPython.utils.tests.test_wildcard',
125 'testing',
125 'testing',
126 'tests',
126 'tests',
127 'PyColorize.py',
127 'PyColorize.py',
128 '_process_win32_controller.py',
128 '_process_win32_controller.py',
129 'IPython/core/application.py',
129 'IPython/core/application.py',
130 'IPython/core/profileapp.py',
130 'IPython/core/profileapp.py',
131 'IPython/lib/deepreload.py',
131 'IPython/lib/deepreload.py',
132 'IPython/sphinxext/ipython_directive.py',
132 'IPython/sphinxext/ipython_directive.py',
133 'IPython/terminal/ipapp.py',
133 'IPython/terminal/ipapp.py',
134 'IPython/utils/_process_win32.py',
134 'IPython/utils/_process_win32.py',
135 'IPython/utils/path.py',
135 'IPython/utils/path.py',
136 ]
136 ]
137 # check_untyped_defs = true
137 # check_untyped_defs = true
138 # disallow_untyped_calls = true
138 # disallow_untyped_calls = true
139 # disallow_untyped_decorators = true
139 # disallow_untyped_decorators = true
140 # ignore_errors = false
140 # ignore_errors = false
141 # ignore_missing_imports = false
141 # ignore_missing_imports = false
142 disallow_incomplete_defs = true
142 disallow_incomplete_defs = true
143 disallow_untyped_defs = true
143 disallow_untyped_defs = true
144 warn_redundant_casts = true
144 warn_redundant_casts = true
145
145
146 [[tool.mypy.overrides]]
146 [[tool.mypy.overrides]]
147 module = [
147 module = [
148 "IPython.core.crashhandler",
148 "IPython.core.crashhandler",
149 ]
149 ]
150 check_untyped_defs = true
150 check_untyped_defs = true
151 disallow_incomplete_defs = true
151 disallow_incomplete_defs = true
152 disallow_untyped_calls = true
152 disallow_untyped_calls = true
153 disallow_untyped_decorators = true
153 disallow_untyped_decorators = true
154 disallow_untyped_defs = true
154 disallow_untyped_defs = true
155 ignore_errors = false
155 ignore_errors = false
156 ignore_missing_imports = false
156 ignore_missing_imports = false
157
157
158 [[tool.mypy.overrides]]
158 [[tool.mypy.overrides]]
159 module = [
159 module = [
160 "IPython.utils.text",
160 "IPython.utils.text",
161 ]
161 ]
162 disallow_untyped_defs = true
162 disallow_untyped_defs = true
163 check_untyped_defs = false
163 check_untyped_defs = false
164 disallow_untyped_decorators = true
164 disallow_untyped_decorators = true
165
165
166 [[tool.mypy.overrides]]
166 [[tool.mypy.overrides]]
167 module = [
167 module = [
168 ]
168 ]
169 disallow_untyped_defs = false
169 disallow_untyped_defs = false
170 ignore_errors = true
170 ignore_errors = true
171 ignore_missing_imports = true
171 ignore_missing_imports = true
172 disallow_untyped_calls = false
172 disallow_untyped_calls = false
173 disallow_incomplete_defs = false
173 disallow_incomplete_defs = false
174 check_untyped_defs = false
174 check_untyped_defs = false
175 disallow_untyped_decorators = false
175 disallow_untyped_decorators = false
176
176
177
177
178 # gloabl ignore error
178 # gloabl ignore error
179 [[tool.mypy.overrides]]
179 [[tool.mypy.overrides]]
180 module = [
180 module = [
181 "IPython",
181 "IPython",
182 "IPython.conftest",
182 "IPython.conftest",
183 "IPython.core.alias",
183 "IPython.core.alias",
184 "IPython.core.async_helpers",
184 "IPython.core.async_helpers",
185 "IPython.core.autocall",
185 "IPython.core.autocall",
186 "IPython.core.builtin_trap",
186 "IPython.core.builtin_trap",
187 "IPython.core.compilerop",
187 "IPython.core.compilerop",
188 "IPython.core.completer",
188 "IPython.core.completer",
189 "IPython.core.completerlib",
189 "IPython.core.completerlib",
190 "IPython.core.debugger",
190 "IPython.core.debugger",
191 "IPython.core.display",
191 "IPython.core.display",
192 "IPython.core.display_functions",
192 "IPython.core.display_functions",
193 "IPython.core.display_trap",
193 "IPython.core.display_trap",
194 "IPython.core.displayhook",
194 "IPython.core.displayhook",
195 "IPython.core.displaypub",
195 "IPython.core.displaypub",
196 "IPython.core.events",
196 "IPython.core.events",
197 "IPython.core.excolors",
197 "IPython.core.excolors",
198 "IPython.core.extensions",
198 "IPython.core.extensions",
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",
206 "IPython.core.inputtransformer2",
205 "IPython.core.inputtransformer2",
207 "IPython.core.interactiveshell",
206 "IPython.core.interactiveshell",
208 "IPython.core.logger",
207 "IPython.core.logger",
209 "IPython.core.macro",
208 "IPython.core.macro",
210 "IPython.core.magic",
209 "IPython.core.magic",
211 "IPython.core.magic_arguments",
210 "IPython.core.magic_arguments",
212 "IPython.core.magics.ast_mod",
211 "IPython.core.magics.ast_mod",
213 "IPython.core.magics.auto",
212 "IPython.core.magics.auto",
214 "IPython.core.magics.basic",
213 "IPython.core.magics.basic",
215 "IPython.core.magics.code",
214 "IPython.core.magics.code",
216 "IPython.core.magics.config",
215 "IPython.core.magics.config",
217 "IPython.core.magics.display",
216 "IPython.core.magics.display",
218 "IPython.core.magics.execution",
217 "IPython.core.magics.execution",
219 "IPython.core.magics.extension",
218 "IPython.core.magics.extension",
220 "IPython.core.magics.history",
219 "IPython.core.magics.history",
221 "IPython.core.magics.logging",
220 "IPython.core.magics.logging",
222 "IPython.core.magics.namespace",
221 "IPython.core.magics.namespace",
223 "IPython.core.magics.osm",
222 "IPython.core.magics.osm",
224 "IPython.core.magics.packaging",
223 "IPython.core.magics.packaging",
225 "IPython.core.magics.pylab",
224 "IPython.core.magics.pylab",
226 "IPython.core.magics.script",
225 "IPython.core.magics.script",
227 "IPython.core.oinspect",
226 "IPython.core.oinspect",
228 "IPython.core.page",
227 "IPython.core.page",
229 "IPython.core.payload",
228 "IPython.core.payload",
230 "IPython.core.payloadpage",
229 "IPython.core.payloadpage",
231 "IPython.core.prefilter",
230 "IPython.core.prefilter",
232 "IPython.core.profiledir",
231 "IPython.core.profiledir",
233 "IPython.core.prompts",
232 "IPython.core.prompts",
234 "IPython.core.pylabtools",
233 "IPython.core.pylabtools",
235 "IPython.core.shellapp",
234 "IPython.core.shellapp",
236 "IPython.core.splitinput",
235 "IPython.core.splitinput",
237 "IPython.core.ultratb",
236 "IPython.core.ultratb",
238 "IPython.extensions.autoreload",
237 "IPython.extensions.autoreload",
239 "IPython.extensions.storemagic",
238 "IPython.extensions.storemagic",
240 "IPython.external.qt_for_kernel",
239 "IPython.external.qt_for_kernel",
241 "IPython.external.qt_loaders",
240 "IPython.external.qt_loaders",
242 "IPython.lib.backgroundjobs",
241 "IPython.lib.backgroundjobs",
243 "IPython.lib.clipboard",
242 "IPython.lib.clipboard",
244 "IPython.lib.demo",
243 "IPython.lib.demo",
245 "IPython.lib.display",
244 "IPython.lib.display",
246 "IPython.lib.editorhooks",
245 "IPython.lib.editorhooks",
247 "IPython.lib.guisupport",
246 "IPython.lib.guisupport",
248 "IPython.lib.latextools",
247 "IPython.lib.latextools",
249 "IPython.lib.lexers",
248 "IPython.lib.lexers",
250 "IPython.lib.pretty",
249 "IPython.lib.pretty",
251 "IPython.paths",
250 "IPython.paths",
252 "IPython.sphinxext.ipython_console_highlighting",
251 "IPython.sphinxext.ipython_console_highlighting",
253 "IPython.terminal.debugger",
252 "IPython.terminal.debugger",
254 "IPython.terminal.embed",
253 "IPython.terminal.embed",
255 "IPython.terminal.interactiveshell",
254 "IPython.terminal.interactiveshell",
256 "IPython.terminal.magics",
255 "IPython.terminal.magics",
257 "IPython.terminal.prompts",
256 "IPython.terminal.prompts",
258 "IPython.terminal.pt_inputhooks",
257 "IPython.terminal.pt_inputhooks",
259 "IPython.terminal.pt_inputhooks.asyncio",
258 "IPython.terminal.pt_inputhooks.asyncio",
260 "IPython.terminal.pt_inputhooks.glut",
259 "IPython.terminal.pt_inputhooks.glut",
261 "IPython.terminal.pt_inputhooks.gtk",
260 "IPython.terminal.pt_inputhooks.gtk",
262 "IPython.terminal.pt_inputhooks.gtk3",
261 "IPython.terminal.pt_inputhooks.gtk3",
263 "IPython.terminal.pt_inputhooks.gtk4",
262 "IPython.terminal.pt_inputhooks.gtk4",
264 "IPython.terminal.pt_inputhooks.osx",
263 "IPython.terminal.pt_inputhooks.osx",
265 "IPython.terminal.pt_inputhooks.pyglet",
264 "IPython.terminal.pt_inputhooks.pyglet",
266 "IPython.terminal.pt_inputhooks.qt",
265 "IPython.terminal.pt_inputhooks.qt",
267 "IPython.terminal.pt_inputhooks.tk",
266 "IPython.terminal.pt_inputhooks.tk",
268 "IPython.terminal.pt_inputhooks.wx",
267 "IPython.terminal.pt_inputhooks.wx",
269 "IPython.terminal.ptutils",
268 "IPython.terminal.ptutils",
270 "IPython.terminal.shortcuts",
269 "IPython.terminal.shortcuts",
271 "IPython.terminal.shortcuts.auto_match",
270 "IPython.terminal.shortcuts.auto_match",
272 "IPython.terminal.shortcuts.auto_suggest",
271 "IPython.terminal.shortcuts.auto_suggest",
273 "IPython.terminal.shortcuts.filters",
272 "IPython.terminal.shortcuts.filters",
274 "IPython.utils._process_cli",
273 "IPython.utils._process_cli",
275 "IPython.utils._process_common",
274 "IPython.utils._process_common",
276 "IPython.utils._process_emscripten",
275 "IPython.utils._process_emscripten",
277 "IPython.utils._process_posix",
276 "IPython.utils._process_posix",
278 "IPython.utils.capture",
277 "IPython.utils.capture",
279 "IPython.utils.coloransi",
278 "IPython.utils.coloransi",
280 "IPython.utils.contexts",
279 "IPython.utils.contexts",
281 "IPython.utils.data",
280 "IPython.utils.data",
282 "IPython.utils.decorators",
281 "IPython.utils.decorators",
283 "IPython.utils.dir2",
282 "IPython.utils.dir2",
284 "IPython.utils.encoding",
283 "IPython.utils.encoding",
285 "IPython.utils.frame",
284 "IPython.utils.frame",
286 "IPython.utils.generics",
285 "IPython.utils.generics",
287 "IPython.utils.importstring",
286 "IPython.utils.importstring",
288 "IPython.utils.io",
287 "IPython.utils.io",
289 "IPython.utils.ipstruct",
288 "IPython.utils.ipstruct",
290 "IPython.utils.module_paths",
289 "IPython.utils.module_paths",
291 "IPython.utils.openpy",
290 "IPython.utils.openpy",
292 "IPython.utils.process",
291 "IPython.utils.process",
293 "IPython.utils.py3compat",
292 "IPython.utils.py3compat",
294 "IPython.utils.sentinel",
293 "IPython.utils.sentinel",
295 "IPython.utils.strdispatch",
294 "IPython.utils.strdispatch",
296 "IPython.utils.sysinfo",
295 "IPython.utils.sysinfo",
297 "IPython.utils.syspathcontext",
296 "IPython.utils.syspathcontext",
298 "IPython.utils.tempdir",
297 "IPython.utils.tempdir",
299 "IPython.utils.terminal",
298 "IPython.utils.terminal",
300 "IPython.utils.timing",
299 "IPython.utils.timing",
301 "IPython.utils.tokenutil",
300 "IPython.utils.tokenutil",
302 "IPython.utils.version",
301 "IPython.utils.version",
303 "IPython.utils.wildcard",
302 "IPython.utils.wildcard",
304
303
305 ]
304 ]
306 disallow_untyped_defs = false
305 disallow_untyped_defs = false
307 ignore_errors = true
306 ignore_errors = true
308 ignore_missing_imports = true
307 ignore_missing_imports = true
309 disallow_untyped_calls = false
308 disallow_untyped_calls = false
310 disallow_incomplete_defs = false
309 disallow_incomplete_defs = false
311 check_untyped_defs = false
310 check_untyped_defs = false
312 disallow_untyped_decorators = false
311 disallow_untyped_decorators = false
313
312
314 [tool.pytest.ini_options]
313 [tool.pytest.ini_options]
315 addopts = [
314 addopts = [
316 "--durations=10",
315 "--durations=10",
317 "-pIPython.testing.plugin.pytest_ipdoctest",
316 "-pIPython.testing.plugin.pytest_ipdoctest",
318 "--ipdoctest-modules",
317 "--ipdoctest-modules",
319 "--ignore=docs",
318 "--ignore=docs",
320 "--ignore=examples",
319 "--ignore=examples",
321 "--ignore=htmlcov",
320 "--ignore=htmlcov",
322 "--ignore=ipython_kernel",
321 "--ignore=ipython_kernel",
323 "--ignore=ipython_parallel",
322 "--ignore=ipython_parallel",
324 "--ignore=results",
323 "--ignore=results",
325 "--ignore=tmp",
324 "--ignore=tmp",
326 "--ignore=tools",
325 "--ignore=tools",
327 "--ignore=traitlets",
326 "--ignore=traitlets",
328 "--ignore=IPython/core/tests/daft_extension",
327 "--ignore=IPython/core/tests/daft_extension",
329 "--ignore=IPython/sphinxext",
328 "--ignore=IPython/sphinxext",
330 "--ignore=IPython/terminal/pt_inputhooks",
329 "--ignore=IPython/terminal/pt_inputhooks",
331 "--ignore=IPython/__main__.py",
330 "--ignore=IPython/__main__.py",
332 "--ignore=IPython/external/qt_for_kernel.py",
331 "--ignore=IPython/external/qt_for_kernel.py",
333 "--ignore=IPython/html/widgets/widget_link.py",
332 "--ignore=IPython/html/widgets/widget_link.py",
334 "--ignore=IPython/html/widgets/widget_output.py",
333 "--ignore=IPython/html/widgets/widget_output.py",
335 "--ignore=IPython/terminal/console.py",
334 "--ignore=IPython/terminal/console.py",
336 "--ignore=IPython/utils/_process_cli.py",
335 "--ignore=IPython/utils/_process_cli.py",
337 "--ignore=IPython/utils/_process_posix.py",
336 "--ignore=IPython/utils/_process_posix.py",
338 "--ignore=IPython/utils/_process_win32.py",
337 "--ignore=IPython/utils/_process_win32.py",
339 "--ignore=IPython/utils/_process_win32_controller.py",
338 "--ignore=IPython/utils/_process_win32_controller.py",
340 "--ignore=IPython/utils/daemonize.py",
339 "--ignore=IPython/utils/daemonize.py",
341 "--ignore=IPython/utils/eventful.py",
340 "--ignore=IPython/utils/eventful.py",
342 "--ignore=IPython/kernel",
341 "--ignore=IPython/kernel",
343 "--ignore=IPython/consoleapp.py",
342 "--ignore=IPython/consoleapp.py",
344 "--ignore=IPython/lib/kernel.py",
343 "--ignore=IPython/lib/kernel.py",
345 "--ignore=IPython/utils/jsonutil.py",
344 "--ignore=IPython/utils/jsonutil.py",
346 "--ignore=IPython/utils/localinterfaces.py",
345 "--ignore=IPython/utils/localinterfaces.py",
347 "--ignore=IPython/utils/log.py",
346 "--ignore=IPython/utils/log.py",
348 "--ignore=IPython/utils/signatures.py",
347 "--ignore=IPython/utils/signatures.py",
349 "--ignore=IPython/utils/version.py"
348 "--ignore=IPython/utils/version.py"
350 ]
349 ]
351 doctest_optionflags = [
350 doctest_optionflags = [
352 "NORMALIZE_WHITESPACE",
351 "NORMALIZE_WHITESPACE",
353 "ELLIPSIS"
352 "ELLIPSIS"
354 ]
353 ]
355 ipdoctest_optionflags = [
354 ipdoctest_optionflags = [
356 "NORMALIZE_WHITESPACE",
355 "NORMALIZE_WHITESPACE",
357 "ELLIPSIS"
356 "ELLIPSIS"
358 ]
357 ]
359 asyncio_mode = "strict"
358 asyncio_mode = "strict"
360
359
361 [tool.pyright]
360 [tool.pyright]
362 pythonPlatform="All"
361 pythonPlatform="All"
363
362
364 [tool.setuptools]
363 [tool.setuptools]
365 zip-safe = false
364 zip-safe = false
366 platforms = ["Linux", "Mac OSX", "Windows"]
365 platforms = ["Linux", "Mac OSX", "Windows"]
367 license-files = ["LICENSE"]
366 license-files = ["LICENSE"]
368 include-package-data = false
367 include-package-data = false
369
368
370 [tool.setuptools.packages.find]
369 [tool.setuptools.packages.find]
371 exclude = ["setupext"]
370 exclude = ["setupext"]
372 namespaces = false
371 namespaces = false
373
372
374 [tool.setuptools.package-data]
373 [tool.setuptools.package-data]
375 "IPython" = ["py.typed"]
374 "IPython" = ["py.typed"]
376 "IPython.core" = ["profile/README*"]
375 "IPython.core" = ["profile/README*"]
377 "IPython.core.tests" = ["*.png", "*.jpg", "daft_extension/*.py"]
376 "IPython.core.tests" = ["*.png", "*.jpg", "daft_extension/*.py"]
378 "IPython.lib.tests" = ["*.wav"]
377 "IPython.lib.tests" = ["*.wav"]
379 "IPython.testing.plugin" = ["*.txt"]
378 "IPython.testing.plugin" = ["*.txt"]
380
379
381 [tool.setuptools.dynamic]
380 [tool.setuptools.dynamic]
382 version = {attr = "IPython.core.release.__version__"}
381 version = {attr = "IPython.core.release.__version__"}
383
382
384 [tool.coverage.run]
383 [tool.coverage.run]
385 omit = [
384 omit = [
386 # omit everything in /tmp as we run tempfile
385 # omit everything in /tmp as we run tempfile
387 "/tmp/*",
386 "/tmp/*",
388 ]
387 ]
General Comments 0
You need to be logged in to leave comments. Login now