Compare commits

...

1 commit

Author SHA1 Message Date
Simon Willison
62f673835c Prototype of deregister and with db.register_functions, refs #589 2023-09-03 12:36:10 -07:00

View file

@ -413,6 +413,7 @@ class Database:
deterministic: bool = False, deterministic: bool = False,
replace: bool = False, replace: bool = False,
name: Optional[str] = None, name: Optional[str] = None,
deregister: bool = False,
): ):
""" """
``fn`` will be made available as a function within SQL, with the same name and number ``fn`` will be made available as a function within SQL, with the same name and number
@ -434,28 +435,42 @@ class Database:
:param deterministic: set ``True`` for functions that always returns the same output for a given input :param deterministic: set ``True`` for functions that always returns the same output for a given input
:param replace: set ``True`` to replace an existing function with the same name - otherwise throw an error :param replace: set ``True`` to replace an existing function with the same name - otherwise throw an error
:param name: name of the SQLite function - if not specified, the Python function name will be used :param name: name of the SQLite function - if not specified, the Python function name will be used
:param deregister: set ``True`` to deregister the function
""" """
def register(fn): def register(fn):
fn_name = name or fn.__name__ fn_name = name or fn.__name__
fn_to_register = fn
arity = len(inspect.signature(fn).parameters) arity = len(inspect.signature(fn).parameters)
if not replace and (fn_name, arity) in self._registered_functions: if (
not replace
and not deregister
and (fn_name, arity) in self._registered_functions
):
return fn return fn
kwargs = {} kwargs = {}
if deregister:
fn_to_register = None
registered = False registered = False
if deterministic: if deterministic:
# Try this, but fall back if sqlite3.NotSupportedError # Try this, but fall back if sqlite3.NotSupportedError
try: try:
self.conn.create_function( self.conn.create_function(
fn_name, arity, fn, **dict(kwargs, deterministic=True) fn_name,
arity,
fn_to_register,
**dict(kwargs, deterministic=True),
) )
registered = True registered = True
except (sqlite3.NotSupportedError, TypeError): except (sqlite3.NotSupportedError, TypeError):
# TypeError is Python 3.7 "function takes at most 3 arguments" # TypeError is Python 3.7 "function takes at most 3 arguments"
pass pass
if not registered: if not registered:
self.conn.create_function(fn_name, arity, fn, **kwargs) self.conn.create_function(fn_name, arity, fn_to_register, **kwargs)
self._registered_functions.add((fn_name, arity)) if deregister:
self._registered_functions.remove((fn_name, arity))
else:
self._registered_functions.add((fn_name, arity))
return fn return fn
if fn is None: if fn is None:
@ -463,6 +478,18 @@ class Database:
else: else:
register(fn) register(fn)
@contextlib.contextmanager
def register_functions(self, *functions: Callable):
"Register functions for the duration of a with block, then unregister them"
for func in functions:
self.register_function(func)
try:
yield
finally:
# Unregister the functions
for func in functions:
self.register_function(func, deregister=True)
def register_fts4_bm25(self): def register_fts4_bm25(self):
"Register the ``rank_bm25(match_info)`` function used for calculating relevance with SQLite FTS4." "Register the ``rank_bm25(match_info)`` function used for calculating relevance with SQLite FTS4."
self.register_function(rank_bm25, deterministic=True, replace=True) self.register_function(rank_bm25, deterministic=True, replace=True)