kalekundert / byoc

MIT License
0 stars 0 forks source link

Allow `Key(cast=...)` to access self #36

Closed kalekundert closed 2 years ago

kalekundert commented 2 years ago

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.

kalekundert commented 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