Show More
@@ -6,7 +6,7 b'' | |||||
6 |
|
6 | |||
7 | import atexit |
|
7 | import atexit | |
8 | import datetime |
|
8 | import datetime | |
9 | import os |
|
9 | from pathlib import Path | |
10 | import re |
|
10 | import re | |
11 | import sqlite3 |
|
11 | import sqlite3 | |
12 | import threading |
|
12 | import threading | |
@@ -16,8 +16,8 b' from decorator import decorator' | |||||
16 | from IPython.utils.decorators import undoc |
|
16 | from IPython.utils.decorators import undoc | |
17 | from IPython.paths import locate_profile |
|
17 | from IPython.paths import locate_profile | |
18 | from traitlets import ( |
|
18 | from traitlets import ( | |
19 | Any, Bool, Dict, Instance, Integer, List, Unicode, TraitError, |
|
19 | Any, Bool, Dict, Instance, Integer, List, Unicode, Union, TraitError, | |
20 |
default, observe |
|
20 | default, observe | |
21 | ) |
|
21 | ) | |
22 |
|
22 | |||
23 | #----------------------------------------------------------------------------- |
|
23 | #----------------------------------------------------------------------------- | |
@@ -27,17 +27,17 b' from traitlets import (' | |||||
27 | @undoc |
|
27 | @undoc | |
28 | class DummyDB(object): |
|
28 | class DummyDB(object): | |
29 | """Dummy DB that will act as a black hole for history. |
|
29 | """Dummy DB that will act as a black hole for history. | |
30 |
|
30 | |||
31 | Only used in the absence of sqlite""" |
|
31 | Only used in the absence of sqlite""" | |
32 | def execute(*args, **kwargs): |
|
32 | def execute(*args, **kwargs): | |
33 | return [] |
|
33 | return [] | |
34 |
|
34 | |||
35 | def commit(self, *args, **kwargs): |
|
35 | def commit(self, *args, **kwargs): | |
36 | pass |
|
36 | pass | |
37 |
|
37 | |||
38 | def __enter__(self, *args, **kwargs): |
|
38 | def __enter__(self, *args, **kwargs): | |
39 | pass |
|
39 | pass | |
40 |
|
40 | |||
41 | def __exit__(self, *args, **kwargs): |
|
41 | def __exit__(self, *args, **kwargs): | |
42 | pass |
|
42 | pass | |
43 |
|
43 | |||
@@ -73,17 +73,18 b' def catch_corrupt_db(f, self, *a, **kw):' | |||||
73 | if self._corrupt_db_counter > self._corrupt_db_limit: |
|
73 | if self._corrupt_db_counter > self._corrupt_db_limit: | |
74 | self.hist_file = ':memory:' |
|
74 | self.hist_file = ':memory:' | |
75 | self.log.error("Failed to load history too many times, history will not be saved.") |
|
75 | self.log.error("Failed to load history too many times, history will not be saved.") | |
76 |
elif |
|
76 | elif self.hist_file.is_file(): | |
77 | # move the file out of the way |
|
77 | # move the file out of the way | |
78 |
base |
|
78 | base = str(self.hist_file.parent / self.hist_file.stem) | |
79 |
|
|
79 | ext = self.hist_file.suffix | |
|
80 | size = self.hist_file.stat().st_size | |||
80 | if size >= _SAVE_DB_SIZE: |
|
81 | if size >= _SAVE_DB_SIZE: | |
81 | # if there's significant content, avoid clobbering |
|
82 | # if there's significant content, avoid clobbering | |
82 | now = datetime.datetime.now().isoformat().replace(':', '.') |
|
83 | now = datetime.datetime.now().isoformat().replace(':', '.') | |
83 | newpath = base + '-corrupt-' + now + ext |
|
84 | newpath = base + '-corrupt-' + now + ext | |
84 | # don't clobber previous corrupt backups |
|
85 | # don't clobber previous corrupt backups | |
85 | for i in range(100): |
|
86 | for i in range(100): | |
86 |
if not |
|
87 | if not Path(newpath).exists(): | |
87 | break |
|
88 | break | |
88 | else: |
|
89 | else: | |
89 | newpath = base + '-corrupt-' + now + (u'-%i' % i) + ext |
|
90 | newpath = base + '-corrupt-' + now + (u'-%i' % i) + ext | |
@@ -91,14 +92,14 b' def catch_corrupt_db(f, self, *a, **kw):' | |||||
91 | # not much content, possibly empty; don't worry about clobbering |
|
92 | # not much content, possibly empty; don't worry about clobbering | |
92 | # maybe we should just delete it? |
|
93 | # maybe we should just delete it? | |
93 | newpath = base + '-corrupt' + ext |
|
94 | newpath = base + '-corrupt' + ext | |
94 |
|
|
95 | self.hist_file.rename(newpath) | |
95 | self.log.error("History file was moved to %s and a new file created.", newpath) |
|
96 | self.log.error("History file was moved to %s and a new file created.", newpath) | |
96 | self.init_db() |
|
97 | self.init_db() | |
97 | return [] |
|
98 | return [] | |
98 | else: |
|
99 | else: | |
99 | # Failed with :memory:, something serious is wrong |
|
100 | # Failed with :memory:, something serious is wrong | |
100 | raise |
|
101 | raise | |
101 |
|
102 | |||
102 | class HistoryAccessorBase(LoggingConfigurable): |
|
103 | class HistoryAccessorBase(LoggingConfigurable): | |
103 | """An abstract class for History Accessors """ |
|
104 | """An abstract class for History Accessors """ | |
104 |
|
105 | |||
@@ -118,7 +119,7 b' class HistoryAccessorBase(LoggingConfigurable):' | |||||
118 |
|
119 | |||
119 | class HistoryAccessor(HistoryAccessorBase): |
|
120 | class HistoryAccessor(HistoryAccessorBase): | |
120 | """Access the history database without adding to it. |
|
121 | """Access the history database without adding to it. | |
121 |
|
122 | |||
122 | This is intended for use by standalone history tools. IPython shells use |
|
123 | This is intended for use by standalone history tools. IPython shells use | |
123 | HistoryManager, below, which is a subclass of this.""" |
|
124 | HistoryManager, below, which is a subclass of this.""" | |
124 |
|
125 | |||
@@ -128,37 +129,38 b' class HistoryAccessor(HistoryAccessorBase):' | |||||
128 | _corrupt_db_limit = 2 |
|
129 | _corrupt_db_limit = 2 | |
129 |
|
130 | |||
130 | # String holding the path to the history file |
|
131 | # String holding the path to the history file | |
131 | hist_file = Unicode( |
|
132 | hist_file = Union([Instance(Path), Unicode()], | |
132 | help="""Path to file to use for SQLite history database. |
|
133 | help="""Path to file to use for SQLite history database. | |
133 |
|
134 | |||
134 | By default, IPython will put the history database in the IPython |
|
135 | By default, IPython will put the history database in the IPython | |
135 | profile directory. If you would rather share one history among |
|
136 | profile directory. If you would rather share one history among | |
136 | profiles, you can set this value in each, so that they are consistent. |
|
137 | profiles, you can set this value in each, so that they are consistent. | |
137 |
|
138 | |||
138 | Due to an issue with fcntl, SQLite is known to misbehave on some NFS |
|
139 | Due to an issue with fcntl, SQLite is known to misbehave on some NFS | |
139 | mounts. If you see IPython hanging, try setting this to something on a |
|
140 | mounts. If you see IPython hanging, try setting this to something on a | |
140 | local disk, e.g:: |
|
141 | local disk, e.g:: | |
141 |
|
142 | |||
142 | ipython --HistoryManager.hist_file=/tmp/ipython_hist.sqlite |
|
143 | ipython --HistoryManager.hist_file=/tmp/ipython_hist.sqlite | |
143 |
|
144 | |||
144 | you can also use the specific value `:memory:` (including the colon |
|
145 | you can also use the specific value `:memory:` (including the colon | |
145 | at both end but not the back ticks), to avoid creating an history file. |
|
146 | at both end but not the back ticks), to avoid creating an history file. | |
146 |
|
147 | |||
147 | """).tag(config=True) |
|
148 | """ | |
148 |
|
149 | ).tag(config=True) | ||
|
150 | ||||
149 | enabled = Bool(True, |
|
151 | enabled = Bool(True, | |
150 | help="""enable the SQLite history |
|
152 | help="""enable the SQLite history | |
151 |
|
153 | |||
152 | set enabled=False to disable the SQLite history, |
|
154 | set enabled=False to disable the SQLite history, | |
153 | in which case there will be no stored history, no SQLite connection, |
|
155 | in which case there will be no stored history, no SQLite connection, | |
154 | and no background saving thread. This may be necessary in some |
|
156 | and no background saving thread. This may be necessary in some | |
155 | threaded environments where IPython is embedded. |
|
157 | threaded environments where IPython is embedded. | |
156 | """ |
|
158 | """ | |
157 | ).tag(config=True) |
|
159 | ).tag(config=True) | |
158 |
|
160 | |||
159 | connection_options = Dict( |
|
161 | connection_options = Dict( | |
160 | help="""Options for configuring the SQLite connection |
|
162 | help="""Options for configuring the SQLite connection | |
161 |
|
163 | |||
162 | These options are passed as keyword args to sqlite3.connect |
|
164 | These options are passed as keyword args to sqlite3.connect | |
163 | when establishing database connections. |
|
165 | when establishing database connections. | |
164 | """ |
|
166 | """ | |
@@ -175,10 +177,10 b' class HistoryAccessor(HistoryAccessorBase):' | |||||
175 | msg = "%s.db must be sqlite3 Connection or DummyDB, not %r" % \ |
|
177 | msg = "%s.db must be sqlite3 Connection or DummyDB, not %r" % \ | |
176 | (self.__class__.__name__, new) |
|
178 | (self.__class__.__name__, new) | |
177 | raise TraitError(msg) |
|
179 | raise TraitError(msg) | |
178 |
|
180 | |||
179 |
def __init__(self, profile='default', hist_file= |
|
181 | def __init__(self, profile='default', hist_file="", **traits): | |
180 | """Create a new history accessor. |
|
182 | """Create a new history accessor. | |
181 |
|
183 | |||
182 | Parameters |
|
184 | Parameters | |
183 | ---------- |
|
185 | ---------- | |
184 | profile : str |
|
186 | profile : str | |
@@ -196,33 +198,35 b' class HistoryAccessor(HistoryAccessorBase):' | |||||
196 | # set by config |
|
198 | # set by config | |
197 | if hist_file: |
|
199 | if hist_file: | |
198 | self.hist_file = hist_file |
|
200 | self.hist_file = hist_file | |
199 |
|
201 | |||
200 | if self.hist_file == u'': |
|
202 | try: | |
|
203 | self.hist_file | |||
|
204 | except TraitError: | |||
201 | # No one has set the hist_file, yet. |
|
205 | # No one has set the hist_file, yet. | |
202 | self.hist_file = self._get_hist_file_name(profile) |
|
206 | self.hist_file = self._get_hist_file_name(profile) | |
203 |
|
207 | |||
204 | self.init_db() |
|
208 | self.init_db() | |
205 |
|
209 | |||
206 | def _get_hist_file_name(self, profile='default'): |
|
210 | def _get_hist_file_name(self, profile='default'): | |
207 | """Find the history file for the given profile name. |
|
211 | """Find the history file for the given profile name. | |
208 |
|
212 | |||
209 | This is overridden by the HistoryManager subclass, to use the shell's |
|
213 | This is overridden by the HistoryManager subclass, to use the shell's | |
210 | active profile. |
|
214 | active profile. | |
211 |
|
215 | |||
212 | Parameters |
|
216 | Parameters | |
213 | ---------- |
|
217 | ---------- | |
214 | profile : str |
|
218 | profile : str | |
215 | The name of a profile which has a history file. |
|
219 | The name of a profile which has a history file. | |
216 | """ |
|
220 | """ | |
217 |
return |
|
221 | return Path(locate_profile(profile)) / 'history.sqlite' | |
218 |
|
222 | |||
219 | @catch_corrupt_db |
|
223 | @catch_corrupt_db | |
220 | def init_db(self): |
|
224 | def init_db(self): | |
221 | """Connect to the database, and create tables if necessary.""" |
|
225 | """Connect to the database, and create tables if necessary.""" | |
222 | if not self.enabled: |
|
226 | if not self.enabled: | |
223 | self.db = DummyDB() |
|
227 | self.db = DummyDB() | |
224 | return |
|
228 | return | |
225 |
|
229 | |||
226 | # use detect_types so that timestamps return datetime objects |
|
230 | # use detect_types so that timestamps return datetime objects | |
227 | kwargs = dict(detect_types=sqlite3.PARSE_DECLTYPES|sqlite3.PARSE_COLNAMES) |
|
231 | kwargs = dict(detect_types=sqlite3.PARSE_DECLTYPES|sqlite3.PARSE_COLNAMES) | |
228 | kwargs.update(self.connection_options) |
|
232 | kwargs.update(self.connection_options) | |
@@ -290,7 +294,7 b' class HistoryAccessor(HistoryAccessorBase):' | |||||
290 |
|
294 | |||
291 | Returns |
|
295 | Returns | |
292 | ------- |
|
296 | ------- | |
293 |
|
297 | |||
294 | session_id : int |
|
298 | session_id : int | |
295 | Session ID number |
|
299 | Session ID number | |
296 | start : datetime |
|
300 | start : datetime | |
@@ -308,7 +312,7 b' class HistoryAccessor(HistoryAccessorBase):' | |||||
308 | @catch_corrupt_db |
|
312 | @catch_corrupt_db | |
309 | def get_last_session_id(self): |
|
313 | def get_last_session_id(self): | |
310 | """Get the last session ID currently in the database. |
|
314 | """Get the last session ID currently in the database. | |
311 |
|
315 | |||
312 | Within IPython, this should be the same as the value stored in |
|
316 | Within IPython, this should be the same as the value stored in | |
313 | :attr:`HistoryManager.session_number`. |
|
317 | :attr:`HistoryManager.session_number`. | |
314 | """ |
|
318 | """ | |
@@ -384,7 +388,7 b' class HistoryAccessor(HistoryAccessorBase):' | |||||
384 | if n is not None: |
|
388 | if n is not None: | |
385 | return reversed(list(cur)) |
|
389 | return reversed(list(cur)) | |
386 | return cur |
|
390 | return cur | |
387 |
|
391 | |||
388 | @catch_corrupt_db |
|
392 | @catch_corrupt_db | |
389 | def get_range(self, session, start=1, stop=None, raw=True,output=False): |
|
393 | def get_range(self, session, start=1, stop=None, raw=True,output=False): | |
390 | """Retrieve input by session. |
|
394 | """Retrieve input by session. | |
@@ -461,7 +465,7 b' class HistoryManager(HistoryAccessor):' | |||||
461 | @default('dir_hist') |
|
465 | @default('dir_hist') | |
462 | def _dir_hist_default(self): |
|
466 | def _dir_hist_default(self): | |
463 | try: |
|
467 | try: | |
464 |
return [ |
|
468 | return [Path.cwd()] | |
465 | except OSError: |
|
469 | except OSError: | |
466 | return [] |
|
470 | return [] | |
467 |
|
471 | |||
@@ -473,7 +477,7 b' class HistoryManager(HistoryAccessor):' | |||||
473 |
|
477 | |||
474 | # The number of the current session in the history database |
|
478 | # The number of the current session in the history database | |
475 | session_number = Integer() |
|
479 | session_number = Integer() | |
476 |
|
480 | |||
477 | db_log_output = Bool(False, |
|
481 | db_log_output = Bool(False, | |
478 | help="Should the history database include output? (default: no)" |
|
482 | help="Should the history database include output? (default: no)" | |
479 | ).tag(config=True) |
|
483 | ).tag(config=True) | |
@@ -484,12 +488,12 b' class HistoryManager(HistoryAccessor):' | |||||
484 | # The input and output caches |
|
488 | # The input and output caches | |
485 | db_input_cache = List() |
|
489 | db_input_cache = List() | |
486 | db_output_cache = List() |
|
490 | db_output_cache = List() | |
487 |
|
491 | |||
488 | # History saving in separate thread |
|
492 | # History saving in separate thread | |
489 | save_thread = Instance('IPython.core.history.HistorySavingThread', |
|
493 | save_thread = Instance('IPython.core.history.HistorySavingThread', | |
490 | allow_none=True) |
|
494 | allow_none=True) | |
491 | save_flag = Instance(threading.Event, allow_none=True) |
|
495 | save_flag = Instance(threading.Event, allow_none=True) | |
492 |
|
496 | |||
493 | # Private interface |
|
497 | # Private interface | |
494 | # Variables used to store the three last inputs from the user. On each new |
|
498 | # Variables used to store the three last inputs from the user. On each new | |
495 | # history update, we populate the user's namespace with these, shifted as |
|
499 | # history update, we populate the user's namespace with these, shifted as | |
@@ -513,37 +517,37 b' class HistoryManager(HistoryAccessor):' | |||||
513 | self.save_flag = threading.Event() |
|
517 | self.save_flag = threading.Event() | |
514 | self.db_input_cache_lock = threading.Lock() |
|
518 | self.db_input_cache_lock = threading.Lock() | |
515 | self.db_output_cache_lock = threading.Lock() |
|
519 | self.db_output_cache_lock = threading.Lock() | |
516 |
|
520 | |||
517 | try: |
|
521 | try: | |
518 | self.new_session() |
|
522 | self.new_session() | |
519 | except sqlite3.OperationalError: |
|
523 | except sqlite3.OperationalError: | |
520 | self.log.error("Failed to create history session in %s. History will not be saved.", |
|
524 | self.log.error("Failed to create history session in %s. History will not be saved.", | |
521 | self.hist_file, exc_info=True) |
|
525 | self.hist_file, exc_info=True) | |
522 | self.hist_file = ':memory:' |
|
526 | self.hist_file = ':memory:' | |
523 |
|
527 | |||
524 | if self.enabled and self.hist_file != ':memory:': |
|
528 | if self.enabled and self.hist_file != ':memory:': | |
525 | self.save_thread = HistorySavingThread(self) |
|
529 | self.save_thread = HistorySavingThread(self) | |
526 | self.save_thread.start() |
|
530 | self.save_thread.start() | |
527 |
|
531 | |||
528 | def _get_hist_file_name(self, profile=None): |
|
532 | def _get_hist_file_name(self, profile=None): | |
529 | """Get default history file name based on the Shell's profile. |
|
533 | """Get default history file name based on the Shell's profile. | |
530 |
|
534 | |||
531 | The profile parameter is ignored, but must exist for compatibility with |
|
535 | The profile parameter is ignored, but must exist for compatibility with | |
532 | the parent class.""" |
|
536 | the parent class.""" | |
533 | profile_dir = self.shell.profile_dir.location |
|
537 | profile_dir = self.shell.profile_dir.location | |
534 |
return |
|
538 | return Path(profile_dir)/'history.sqlite' | |
535 |
|
539 | |||
536 | @only_when_enabled |
|
540 | @only_when_enabled | |
537 | def new_session(self, conn=None): |
|
541 | def new_session(self, conn=None): | |
538 | """Get a new session number.""" |
|
542 | """Get a new session number.""" | |
539 | if conn is None: |
|
543 | if conn is None: | |
540 | conn = self.db |
|
544 | conn = self.db | |
541 |
|
545 | |||
542 | with conn: |
|
546 | with conn: | |
543 | cur = conn.execute("""INSERT INTO sessions VALUES (NULL, ?, NULL, |
|
547 | cur = conn.execute("""INSERT INTO sessions VALUES (NULL, ?, NULL, | |
544 | NULL, "") """, (datetime.datetime.now(),)) |
|
548 | NULL, "") """, (datetime.datetime.now(),)) | |
545 | self.session_number = cur.lastrowid |
|
549 | self.session_number = cur.lastrowid | |
546 |
|
550 | |||
547 | def end_session(self): |
|
551 | def end_session(self): | |
548 | """Close the database session, filling in the end time and line count.""" |
|
552 | """Close the database session, filling in the end time and line count.""" | |
549 | self.writeout_cache() |
|
553 | self.writeout_cache() | |
@@ -552,20 +556,20 b' class HistoryManager(HistoryAccessor):' | |||||
552 | session==?""", (datetime.datetime.now(), |
|
556 | session==?""", (datetime.datetime.now(), | |
553 | len(self.input_hist_parsed)-1, self.session_number)) |
|
557 | len(self.input_hist_parsed)-1, self.session_number)) | |
554 | self.session_number = 0 |
|
558 | self.session_number = 0 | |
555 |
|
559 | |||
556 | def name_session(self, name): |
|
560 | def name_session(self, name): | |
557 | """Give the current session a name in the history database.""" |
|
561 | """Give the current session a name in the history database.""" | |
558 | with self.db: |
|
562 | with self.db: | |
559 | self.db.execute("UPDATE sessions SET remark=? WHERE session==?", |
|
563 | self.db.execute("UPDATE sessions SET remark=? WHERE session==?", | |
560 | (name, self.session_number)) |
|
564 | (name, self.session_number)) | |
561 |
|
565 | |||
562 | def reset(self, new_session=True): |
|
566 | def reset(self, new_session=True): | |
563 | """Clear the session history, releasing all object references, and |
|
567 | """Clear the session history, releasing all object references, and | |
564 | optionally open a new session.""" |
|
568 | optionally open a new session.""" | |
565 | self.output_hist.clear() |
|
569 | self.output_hist.clear() | |
566 | # The directory history can't be completely empty |
|
570 | # The directory history can't be completely empty | |
567 |
self.dir_hist[:] = [ |
|
571 | self.dir_hist[:] = [Path.cwd()] | |
568 |
|
572 | |||
569 | if new_session: |
|
573 | if new_session: | |
570 | if self.session_number: |
|
574 | if self.session_number: | |
571 | self.end_session() |
|
575 | self.end_session() | |
@@ -588,7 +592,7 b' class HistoryManager(HistoryAccessor):' | |||||
588 |
|
592 | |||
589 | Returns |
|
593 | Returns | |
590 | ------- |
|
594 | ------- | |
591 |
|
595 | |||
592 | session_id : int |
|
596 | session_id : int | |
593 | Session ID number |
|
597 | Session ID number | |
594 | start : datetime |
|
598 | start : datetime | |
@@ -609,7 +613,7 b' class HistoryManager(HistoryAccessor):' | |||||
609 | """Get input and output history from the current session. Called by |
|
613 | """Get input and output history from the current session. Called by | |
610 | get_range, and takes similar parameters.""" |
|
614 | get_range, and takes similar parameters.""" | |
611 | input_hist = self.input_hist_raw if raw else self.input_hist_parsed |
|
615 | input_hist = self.input_hist_raw if raw else self.input_hist_parsed | |
612 |
|
616 | |||
613 | n = len(input_hist) |
|
617 | n = len(input_hist) | |
614 | if start < 0: |
|
618 | if start < 0: | |
615 | start += n |
|
619 | start += n | |
@@ -617,17 +621,17 b' class HistoryManager(HistoryAccessor):' | |||||
617 | stop = n |
|
621 | stop = n | |
618 | elif stop < 0: |
|
622 | elif stop < 0: | |
619 | stop += n |
|
623 | stop += n | |
620 |
|
624 | |||
621 | for i in range(start, stop): |
|
625 | for i in range(start, stop): | |
622 | if output: |
|
626 | if output: | |
623 | line = (input_hist[i], self.output_hist_reprs.get(i)) |
|
627 | line = (input_hist[i], self.output_hist_reprs.get(i)) | |
624 | else: |
|
628 | else: | |
625 | line = input_hist[i] |
|
629 | line = input_hist[i] | |
626 | yield (0, i, line) |
|
630 | yield (0, i, line) | |
627 |
|
631 | |||
628 | def get_range(self, session=0, start=1, stop=None, raw=True,output=False): |
|
632 | def get_range(self, session=0, start=1, stop=None, raw=True,output=False): | |
629 | """Retrieve input by session. |
|
633 | """Retrieve input by session. | |
630 |
|
634 | |||
631 | Parameters |
|
635 | Parameters | |
632 | ---------- |
|
636 | ---------- | |
633 | session : int |
|
637 | session : int | |
@@ -645,7 +649,7 b' class HistoryManager(HistoryAccessor):' | |||||
645 | objects for the current session, or text reprs from previous |
|
649 | objects for the current session, or text reprs from previous | |
646 | sessions if db_log_output was enabled at the time. Where no output |
|
650 | sessions if db_log_output was enabled at the time. Where no output | |
647 | is found, None is used. |
|
651 | is found, None is used. | |
648 |
|
652 | |||
649 | Returns |
|
653 | Returns | |
650 | ------- |
|
654 | ------- | |
651 | entries |
|
655 | entries | |
@@ -709,7 +713,7 b' class HistoryManager(HistoryAccessor):' | |||||
709 | '_ii': self._ii, |
|
713 | '_ii': self._ii, | |
710 | '_iii': self._iii, |
|
714 | '_iii': self._iii, | |
711 | new_i : self._i00 } |
|
715 | new_i : self._i00 } | |
712 |
|
716 | |||
713 | if self.shell is not None: |
|
717 | if self.shell is not None: | |
714 | self.shell.push(to_main, interactive=False) |
|
718 | self.shell.push(to_main, interactive=False) | |
715 |
|
719 |
@@ -7,7 +7,7 b'' | |||||
7 |
|
7 | |||
8 | # stdlib |
|
8 | # stdlib | |
9 | import io |
|
9 | import io | |
10 | import os |
|
10 | from pathlib import Path | |
11 | import sys |
|
11 | import sys | |
12 | import tempfile |
|
12 | import tempfile | |
13 | from datetime import datetime |
|
13 | from datetime import datetime | |
@@ -29,8 +29,9 b' def test_proper_default_encoding():' | |||||
29 | def test_history(): |
|
29 | def test_history(): | |
30 | ip = get_ipython() |
|
30 | ip = get_ipython() | |
31 | with TemporaryDirectory() as tmpdir: |
|
31 | with TemporaryDirectory() as tmpdir: | |
|
32 | tmp_path = Path(tmpdir) | |||
32 | hist_manager_ori = ip.history_manager |
|
33 | hist_manager_ori = ip.history_manager | |
33 |
hist_file = |
|
34 | hist_file = tmp_path / 'history.sqlite' | |
34 | try: |
|
35 | try: | |
35 | ip.history_manager = HistoryManager(shell=ip, hist_file=hist_file) |
|
36 | ip.history_manager = HistoryManager(shell=ip, hist_file=hist_file) | |
36 | hist = [u'a=1', u'def f():\n test = 1\n return test', u"b='€Æ¾÷ß'"] |
|
37 | hist = [u'a=1', u'def f():\n test = 1\n return test', u"b='€Æ¾÷ß'"] | |
@@ -55,10 +56,10 b' def test_history():' | |||||
55 | ip.magic('%hist 2-500') |
|
56 | ip.magic('%hist 2-500') | |
56 |
|
57 | |||
57 | # Check that we can write non-ascii characters to a file |
|
58 | # Check that we can write non-ascii characters to a file | |
58 |
ip.magic("%%hist -f %s" % |
|
59 | ip.magic("%%hist -f %s" % (tmp_path / "test1")) | |
59 |
ip.magic("%%hist -pf %s" % |
|
60 | ip.magic("%%hist -pf %s" % (tmp_path / "test2")) | |
60 |
ip.magic("%%hist -nf %s" % |
|
61 | ip.magic("%%hist -nf %s" % (tmp_path / "test3")) | |
61 |
ip.magic("%%save %s 1-10" % |
|
62 | ip.magic("%%save %s 1-10" % (tmp_path / "test4")) | |
62 |
|
63 | |||
63 | # New session |
|
64 | # New session | |
64 | ip.history_manager.reset() |
|
65 | ip.history_manager.reset() | |
@@ -126,8 +127,8 b' def test_history():' | |||||
126 | nt.assert_equal(list(gothist), [(1,3,(hist[2],"spam"))] ) |
|
127 | nt.assert_equal(list(gothist), [(1,3,(hist[2],"spam"))] ) | |
127 |
|
128 | |||
128 | # Cross testing: check that magic %save can get previous session. |
|
129 | # Cross testing: check that magic %save can get previous session. | |
129 |
testfilename = |
|
130 | testfilename = (tmp_path /"test.py").resolve() | |
130 | ip.magic("save " + testfilename + " ~1/1-3") |
|
131 | ip.magic("save " + str(testfilename) + " ~1/1-3") | |
131 | with io.open(testfilename, encoding='utf-8') as testfile: |
|
132 | with io.open(testfilename, encoding='utf-8') as testfile: | |
132 | nt.assert_equal(testfile.read(), |
|
133 | nt.assert_equal(testfile.read(), | |
133 | u"# coding: utf-8\n" + u"\n".join(hist)+u"\n") |
|
134 | u"# coding: utf-8\n" + u"\n".join(hist)+u"\n") | |
@@ -176,13 +177,13 b' def test_timestamp_type():' | |||||
176 | def test_hist_file_config(): |
|
177 | def test_hist_file_config(): | |
177 | cfg = Config() |
|
178 | cfg = Config() | |
178 | tfile = tempfile.NamedTemporaryFile(delete=False) |
|
179 | tfile = tempfile.NamedTemporaryFile(delete=False) | |
179 | cfg.HistoryManager.hist_file = tfile.name |
|
180 | cfg.HistoryManager.hist_file = Path(tfile.name) | |
180 | try: |
|
181 | try: | |
181 | hm = HistoryManager(shell=get_ipython(), config=cfg) |
|
182 | hm = HistoryManager(shell=get_ipython(), config=cfg) | |
182 | nt.assert_equal(hm.hist_file, cfg.HistoryManager.hist_file) |
|
183 | nt.assert_equal(hm.hist_file, cfg.HistoryManager.hist_file) | |
183 | finally: |
|
184 | finally: | |
184 | try: |
|
185 | try: | |
185 |
|
|
186 | Path(tfile.name).unlink() | |
186 | except OSError: |
|
187 | except OSError: | |
187 | # same catch as in testing.tools.TempFileMixin |
|
188 | # same catch as in testing.tools.TempFileMixin | |
188 | # On Windows, even though we close the file, we still can't |
|
189 | # On Windows, even though we close the file, we still can't | |
@@ -197,7 +198,7 b' def test_histmanager_disabled():' | |||||
197 | ip = get_ipython() |
|
198 | ip = get_ipython() | |
198 | with TemporaryDirectory() as tmpdir: |
|
199 | with TemporaryDirectory() as tmpdir: | |
199 | hist_manager_ori = ip.history_manager |
|
200 | hist_manager_ori = ip.history_manager | |
200 |
hist_file = |
|
201 | hist_file = Path(tmpdir) / 'history.sqlite' | |
201 | cfg.HistoryManager.hist_file = hist_file |
|
202 | cfg.HistoryManager.hist_file = hist_file | |
202 | try: |
|
203 | try: | |
203 | ip.history_manager = HistoryManager(shell=ip, config=cfg) |
|
204 | ip.history_manager = HistoryManager(shell=ip, config=cfg) | |
@@ -211,4 +212,4 b' def test_histmanager_disabled():' | |||||
211 | ip.history_manager = hist_manager_ori |
|
212 | ip.history_manager = hist_manager_ori | |
212 |
|
213 | |||
213 | # hist_file should not be created |
|
214 | # hist_file should not be created | |
214 |
nt.assert_false( |
|
215 | nt.assert_false(hist_file.exists()) |
@@ -10,6 +10,7 b' Authors' | |||||
10 | # Distributed under the terms of the Modified BSD License. |
|
10 | # Distributed under the terms of the Modified BSD License. | |
11 |
|
11 | |||
12 | import os |
|
12 | import os | |
|
13 | from pathlib import Path | |||
13 | import re |
|
14 | import re | |
14 | import sys |
|
15 | import sys | |
15 | import tempfile |
|
16 | import tempfile | |
@@ -142,7 +143,7 b' def default_config():' | |||||
142 | config.TerminalTerminalInteractiveShell.term_title = False, |
|
143 | config.TerminalTerminalInteractiveShell.term_title = False, | |
143 | config.TerminalInteractiveShell.autocall = 0 |
|
144 | config.TerminalInteractiveShell.autocall = 0 | |
144 | f = tempfile.NamedTemporaryFile(suffix=u'test_hist.sqlite', delete=False) |
|
145 | f = tempfile.NamedTemporaryFile(suffix=u'test_hist.sqlite', delete=False) | |
145 | config.HistoryManager.hist_file = f.name |
|
146 | config.HistoryManager.hist_file = Path(f.name) | |
146 | f.close() |
|
147 | f.close() | |
147 | config.HistoryManager.db_cache_size = 10000 |
|
148 | config.HistoryManager.db_cache_size = 10000 | |
148 | return config |
|
149 | return config |
General Comments 0
You need to be logged in to leave comments.
Login now