Skip to content

Commit

Permalink
Merge pull request #2278 from minrk/disable_history
Browse files Browse the repository at this point in the history
allow disabling SQLite history

* adds HistoryAccessor.disabled configurable for turning off the SQLite history
* also provides connection_options configurable dict for relaying kwargs to sqlite3.connect.  This is a just-in-case measure, but I think valuable if people have a reason to adjust the SQLite connection flags.

Mainly for use in situations where threading or filesystem issues can cause problems for sqlite, e.g. embedding in django.

closes #2276
  • Loading branch information
fperez committed Aug 10, 2012
2 parents 2092555 + 5ed8fae commit b6697c0
Showing 1 changed file with 52 additions and 16 deletions.
68 changes: 52 additions & 16 deletions IPython/core/history.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,9 @@
from IPython.config.configurable import Configurable
from IPython.external.decorator import decorator
from IPython.utils.path import locate_profile
from IPython.utils.traitlets import Bool, Dict, Instance, Integer, List, Unicode
from IPython.utils.traitlets import (
Any, Bool, Dict, Instance, Integer, List, Unicode, TraitError,
)
from IPython.utils.warn import warn

#-----------------------------------------------------------------------------
Expand All @@ -52,12 +54,12 @@ def __exit__(self, *args, **kwargs):


@decorator
def needs_sqlite(f,*a,**kw):
def needs_sqlite(f, self, *a, **kw):
"""return an empty list in the absence of sqlite"""
if sqlite3 is None:
if sqlite3 is None or not self.enabled:
return []
else:
return f(*a,**kw)
return f(self, *a, **kw)


class HistoryAccessor(Configurable):
Expand All @@ -81,12 +83,33 @@ class HistoryAccessor(Configurable):
ipython --HistoryManager.hist_file=/tmp/ipython_hist.sqlite
""")

enabled = Bool(True, config=True,
help="""enable the SQLite history
set enabled=False to disable the SQLite history,
in which case there will be no stored history, no SQLite connection,
and no background saving thread. This may be necessary in some
threaded environments where IPython is embedded.
"""
)

connection_options = Dict(config=True,
help="""Options for configuring the SQLite connection
These options are passed as keyword args to sqlite3.connect
when establishing database conenctions.
"""
)

# The SQLite database
if sqlite3:
db = Instance(sqlite3.Connection)
else:
db = Instance(DummyDB)
db = Any()
def _db_changed(self, name, old, new):
"""validate the db, since it can be an Instance of two different types"""
if not isinstance(new, (sqlite3.Connection, DummyDB)):
msg = "%s.db must be sqlite3 Connection or DummyDB, not %r" % \
(self.__class__.__name__, new)
raise TraitError(msg)

def __init__(self, profile='default', hist_file=u'', config=None, **traits):
"""Create a new history accessor.
Expand All @@ -113,14 +136,18 @@ def __init__(self, profile='default', hist_file=u'', config=None, **traits):
# No one has set the hist_file, yet.
self.hist_file = self._get_hist_file_name(profile)

if sqlite3 is None:
if sqlite3 is None and self.enabled:
warn("IPython History requires SQLite, your history will not be saved\n")
self.db = DummyDB()
return
self.enabled = False

if sqlite3 is not None:
DatabaseError = sqlite3.DatabaseError
else:
DatabaseError = Exception

try:
self.init_db()
except sqlite3.DatabaseError:
except DatabaseError:
if os.path.isfile(self.hist_file):
# Try to move the file out of the way
base,ext = os.path.splitext(self.hist_file)
Expand Down Expand Up @@ -148,9 +175,14 @@ def _get_hist_file_name(self, profile='default'):

def init_db(self):
"""Connect to the database, and create tables if necessary."""
if not self.enabled:
self.db = DummyDB()
return

# use detect_types so that timestamps return datetime objects
self.db = sqlite3.connect(self.hist_file,
detect_types=sqlite3.PARSE_DECLTYPES|sqlite3.PARSE_COLNAMES)
kwargs = dict(detect_types=sqlite3.PARSE_DECLTYPES|sqlite3.PARSE_COLNAMES)
kwargs.update(self.connection_options)
self.db = sqlite3.connect(self.hist_file, **kwargs)
self.db.execute("""CREATE TABLE IF NOT EXISTS sessions (session integer
primary key autoincrement, start timestamp,
end timestamp, num_cmds integer, remark text)""")
Expand Down Expand Up @@ -402,7 +434,7 @@ def __init__(self, shell=None, config=None, **traits):
self.save_flag = threading.Event()
self.db_input_cache_lock = threading.Lock()
self.db_output_cache_lock = threading.Lock()
if self.hist_file != ':memory:':
if self.enabled and self.hist_file != ':memory:':
self.save_thread = HistorySavingThread(self)
self.save_thread.start()

Expand Down Expand Up @@ -638,16 +670,20 @@ class HistorySavingThread(threading.Thread):
the cache size reaches a defined threshold."""
daemon = True
stop_now = False
enabled = True
def __init__(self, history_manager):
super(HistorySavingThread, self).__init__()
self.history_manager = history_manager
self.enabled = history_manager.enabled
atexit.register(self.stop)

@needs_sqlite
def run(self):
# We need a separate db connection per thread:
try:
self.db = sqlite3.connect(self.history_manager.hist_file)
self.db = sqlite3.connect(self.history_manager.hist_file,
**self.history_manager.connection_options
)
while True:
self.history_manager.save_flag.wait()
if self.stop_now:
Expand Down

0 comments on commit b6697c0

Please sign in to comment.