Show More
@@ -27,7 +27,9 b' import threading' | |||||
27 | from IPython.config.configurable import Configurable |
|
27 | from IPython.config.configurable import Configurable | |
28 | from IPython.external.decorator import decorator |
|
28 | from IPython.external.decorator import decorator | |
29 | from IPython.utils.path import locate_profile |
|
29 | from IPython.utils.path import locate_profile | |
30 |
from IPython.utils.traitlets import |
|
30 | from IPython.utils.traitlets import ( | |
|
31 | Any, Bool, Dict, Instance, Integer, List, Unicode, TraitError, | |||
|
32 | ) | |||
31 | from IPython.utils.warn import warn |
|
33 | from IPython.utils.warn import warn | |
32 |
|
34 | |||
33 | #----------------------------------------------------------------------------- |
|
35 | #----------------------------------------------------------------------------- | |
@@ -52,12 +54,12 b' class DummyDB(object):' | |||||
52 |
|
54 | |||
53 |
|
55 | |||
54 | @decorator |
|
56 | @decorator | |
55 | def needs_sqlite(f,*a,**kw): |
|
57 | def needs_sqlite(f, self, *a, **kw): | |
56 | """return an empty list in the absence of sqlite""" |
|
58 | """return an empty list in the absence of sqlite""" | |
57 |
if sqlite |
|
59 | if sqlite is None or self.disabled: | |
58 | return [] |
|
60 | return [] | |
59 | else: |
|
61 | else: | |
60 | return f(*a,**kw) |
|
62 | return f(self, *a, **kw) | |
61 |
|
63 | |||
62 |
|
64 | |||
63 | class HistoryAccessor(Configurable): |
|
65 | class HistoryAccessor(Configurable): | |
@@ -81,12 +83,32 b' class HistoryAccessor(Configurable):' | |||||
81 | ipython --HistoryManager.hist_file=/tmp/ipython_hist.sqlite |
|
83 | ipython --HistoryManager.hist_file=/tmp/ipython_hist.sqlite | |
82 |
|
84 | |||
83 | """) |
|
85 | """) | |
|
86 | ||||
|
87 | disabled = Bool(False, config=True, | |||
|
88 | help="""disable the SQLite history | |||
|
89 | ||||
|
90 | If True there will be no stored history, no SQLite connection, | |||
|
91 | and no background saving thread. This may be necessary in some | |||
|
92 | threaded environments where IPython is embedded. | |||
|
93 | """ | |||
|
94 | ) | |||
|
95 | ||||
|
96 | connection_options = Dict(config=True, | |||
|
97 | help="""Options for configuring the SQLite connection | |||
|
98 | ||||
|
99 | These options are passed as keyword args to sqlite3.connect | |||
|
100 | when establishing database conenctions. | |||
|
101 | """ | |||
|
102 | ) | |||
84 |
|
103 | |||
85 | # The SQLite database |
|
104 | # The SQLite database | |
86 | if sqlite3: |
|
105 | db = Any() | |
87 | db = Instance(sqlite3.Connection) |
|
106 | def _db_changed(self, name, old, new): | |
88 | else: |
|
107 | """validate the db, since it can be an Instance of two different types""" | |
89 | db = Instance(DummyDB) |
|
108 | if not isinstance(new, (sqlite3.Connection, DummyDB)): | |
|
109 | msg = "%s.db must be sqlite3 Connection or DummyDB, not %r" % \ | |||
|
110 | (self.__class__.__name__, new) | |||
|
111 | raise TraitError(msg) | |||
90 |
|
112 | |||
91 | def __init__(self, profile='default', hist_file=u'', config=None, **traits): |
|
113 | def __init__(self, profile='default', hist_file=u'', config=None, **traits): | |
92 | """Create a new history accessor. |
|
114 | """Create a new history accessor. | |
@@ -113,14 +135,18 b' class HistoryAccessor(Configurable):' | |||||
113 | # No one has set the hist_file, yet. |
|
135 | # No one has set the hist_file, yet. | |
114 | self.hist_file = self._get_hist_file_name(profile) |
|
136 | self.hist_file = self._get_hist_file_name(profile) | |
115 |
|
137 | |||
116 | if sqlite3 is None: |
|
138 | if sqlite3 is None and not self.disabled: | |
117 | warn("IPython History requires SQLite, your history will not be saved\n") |
|
139 | warn("IPython History requires SQLite, your history will not be saved\n") | |
118 |
self.db = |
|
140 | self.disabled = True | |
119 |
|
|
141 | ||
|
142 | if sqlite3 is not None: | |||
|
143 | DatabaseError = sqlite3.DatabaseError | |||
|
144 | else: | |||
|
145 | DatabaseError = Exception | |||
120 |
|
146 | |||
121 | try: |
|
147 | try: | |
122 | self.init_db() |
|
148 | self.init_db() | |
123 |
except |
|
149 | except DatabaseError: | |
124 | if os.path.isfile(self.hist_file): |
|
150 | if os.path.isfile(self.hist_file): | |
125 | # Try to move the file out of the way |
|
151 | # Try to move the file out of the way | |
126 | base,ext = os.path.splitext(self.hist_file) |
|
152 | base,ext = os.path.splitext(self.hist_file) | |
@@ -148,9 +174,14 b' class HistoryAccessor(Configurable):' | |||||
148 |
|
174 | |||
149 | def init_db(self): |
|
175 | def init_db(self): | |
150 | """Connect to the database, and create tables if necessary.""" |
|
176 | """Connect to the database, and create tables if necessary.""" | |
|
177 | if self.disabled: | |||
|
178 | self.db = DummyDB() | |||
|
179 | return | |||
|
180 | ||||
151 | # use detect_types so that timestamps return datetime objects |
|
181 | # use detect_types so that timestamps return datetime objects | |
152 | self.db = sqlite3.connect(self.hist_file, |
|
182 | kwargs = dict(detect_types=sqlite3.PARSE_DECLTYPES|sqlite3.PARSE_COLNAMES) | |
153 | detect_types=sqlite3.PARSE_DECLTYPES|sqlite3.PARSE_COLNAMES) |
|
183 | kwargs.update(self.connection_options) | |
|
184 | self.db = sqlite3.connect(self.hist_file, **kwargs) | |||
154 | self.db.execute("""CREATE TABLE IF NOT EXISTS sessions (session integer |
|
185 | self.db.execute("""CREATE TABLE IF NOT EXISTS sessions (session integer | |
155 | primary key autoincrement, start timestamp, |
|
186 | primary key autoincrement, start timestamp, | |
156 | end timestamp, num_cmds integer, remark text)""") |
|
187 | end timestamp, num_cmds integer, remark text)""") | |
@@ -402,7 +433,7 b' class HistoryManager(HistoryAccessor):' | |||||
402 | self.save_flag = threading.Event() |
|
433 | self.save_flag = threading.Event() | |
403 | self.db_input_cache_lock = threading.Lock() |
|
434 | self.db_input_cache_lock = threading.Lock() | |
404 | self.db_output_cache_lock = threading.Lock() |
|
435 | self.db_output_cache_lock = threading.Lock() | |
405 | if self.hist_file != ':memory:': |
|
436 | if not self.disabled and self.hist_file != ':memory:': | |
406 | self.save_thread = HistorySavingThread(self) |
|
437 | self.save_thread = HistorySavingThread(self) | |
407 | self.save_thread.start() |
|
438 | self.save_thread.start() | |
408 |
|
439 | |||
@@ -638,16 +669,20 b' class HistorySavingThread(threading.Thread):' | |||||
638 | the cache size reaches a defined threshold.""" |
|
669 | the cache size reaches a defined threshold.""" | |
639 | daemon = True |
|
670 | daemon = True | |
640 | stop_now = False |
|
671 | stop_now = False | |
|
672 | disabled = False | |||
641 | def __init__(self, history_manager): |
|
673 | def __init__(self, history_manager): | |
642 | super(HistorySavingThread, self).__init__() |
|
674 | super(HistorySavingThread, self).__init__() | |
643 | self.history_manager = history_manager |
|
675 | self.history_manager = history_manager | |
|
676 | self.disabled = history_manager.disabled | |||
644 | atexit.register(self.stop) |
|
677 | atexit.register(self.stop) | |
645 |
|
678 | |||
646 | @needs_sqlite |
|
679 | @needs_sqlite | |
647 | def run(self): |
|
680 | def run(self): | |
648 | # We need a separate db connection per thread: |
|
681 | # We need a separate db connection per thread: | |
649 | try: |
|
682 | try: | |
650 |
self.db = sqlite3.connect(self.history_manager.hist_file |
|
683 | self.db = sqlite3.connect(self.history_manager.hist_file, | |
|
684 | **self.history_manager.connection_options | |||
|
685 | ) | |||
651 | while True: |
|
686 | while True: | |
652 | self.history_manager.save_flag.wait() |
|
687 | self.history_manager.save_flag.wait() | |
653 | if self.stop_now: |
|
688 | if self.stop_now: |
General Comments 0
You need to be logged in to leave comments.
Login now