Newsletter
TechAnV Blog
Get updates on security engineering, Rust, eBPF, and DevSecOps. No spam, unsubscribe anytime.
Check your inbox and click the confirmation link to complete your subscription.
Decorators with optional arguments#
sqlite-utils provides a decorator for registering custom Python functions that looks like this:
1from sqlite_utils import Database2
3db = Database(memory=True)4
5@db.register_function6def reverse_string(s):7 return "".join(reversed(list(s)))I wanted to add an optional deterministic=True parameter to the decorator.
Problem: the decorator currently does not take any arguments. But I wanted this to work as well:
1@db.register_function(deterministic=True)2def reverse_string(s):3 return "".join(reversed(list(s)))I don’t want to break backwards compatibility with existing code.
First lesson: any time you are designing a decorator that might accept arguments in the future, design it to work like this!
1@db.register_function()2def reverse_string(s):3 return "".join(reversed(list(s)))Since I hadn’t done that, I needed an alternative pattern that could differentiate between the two ways in which the decorator might be called. I ended up going with this:
1 def register_function(self, fn=None, deterministic=None):2 def register(fn):3 name = fn.__name__4 arity = len(inspect.signature(fn).parameters)5 kwargs = {}6 if deterministic and sys.version_info >= (3, 8):7 kwargs["deterministic"] = True8 self.conn.create_function(name, arity, fn, **kwargs)9 return fn10
11 if fn is None:12 return register13 else:14 register(fn)