Closed kalekundert closed 2 years ago
My first instinct is that I want this to be transparent to the user, which means not using different arguments for functions requiring different signatures. For example, imagine a cast function that takes a string path and makes it relative to the file it was loaded from. This is something I might add to BYOC, and I don't want user to have to remember to call it differently. Plus, with multiple arguments, there's the question of which argument is called in which order, and people could try to abuse that.
My thought instead is to wrap the function. BYOC would detect the wrapper and provide it with the extra info.
# Somewhere in BYOC
@with_context
def relpath(context):
return context.meta.path.parent / context.value
class with_context:
def __init__(self, f):
self.f = f
def __call__(self, context):
self.f(context)
class Context:
def __init__(self, app, value, meta):
self.app = app
self.value = value
self.meta = meta
# Somewhere in param/getter:
if isinstance(cast, with_context):
value = Context(app, value, meta)
# Somewhere in a user's code:
class MyApp:
x = byoc.param(FileConfig, 'path', cast=byoc.relpath)
I could also abuse type annotations or default values. Basically, instead of isinstance(cast, with_context)
, I would check that the type annotation is Context
or that the default value is some sort of sentinel. That's kinda appealing, actually. I might as well use a type annotation, because I really am annotating which type to expect, here. My code in param
/Key
would then just be a very simple form of multiple dispatch.
def relpath(context: Context):
return context.meta.path.parent / context.value
Another way to think of this is as a version of the get argument that doesn't get called every time.
I also want to think of a way to allow the cast function to access the current
Meta
object. So it might be worth thinking about this issue in the context of a more general system for passing extra arguments to this function.