Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow for specifying props on components #32

Open
berendkleinhaneveld opened this issue May 5, 2022 · 4 comments
Open

Allow for specifying props on components #32

berendkleinhaneveld opened this issue May 5, 2022 · 4 comments

Comments

@berendkleinhaneveld
Copy link
Collaborator

Using the props specifications, we can further improve the compilation of template expressions.

@berendkleinhaneveld
Copy link
Collaborator Author

@berendkleinhaneveld
Copy link
Collaborator Author

I propose a class attribute __props__, like in the following example:

class Component:
    __props__ = {"base": "Component", "foo": "foo"}


class Sub(Component):
    __props__ = {"base": "Sub", "bar": "bar"}


def all_props(cls):
    result = {}
    for cls in reversed(cls.__mro__):
        if props := getattr(cls, "__props__", None):
            result.update(props)
    return result


if __name__ == "__main__":
    props = all_props(Sub)
    assert "base" in props
    assert "foo" in props
    assert "bar" in props
    assert props["base"] == "Sub"

Which also shows a method to retrieve all the props for a subclass of the component.

@berendkleinhaneveld
Copy link
Collaborator Author

I've been thinking about this a little bit and wanted to share my thoughts. I've come up with another method (which might be a bit more Pythonic) that would work as follows:

class CounterA(Component):
    def __init__(self, *, prop_a=0, prop_b=None, **kwargs):
        super().__init__(prop_a=prop_a, prop_b=prop_b, **kwargs)

Here, the user expresses the existing props by specifying keyword arguments of the __init__ function.

The following code figures out all the names of props for a component type:

import inspect

def get_prop_names(component_type):
    return [
        par.name
        for sig in [
            inspect.signature(cls.__init__) for cls in inspect.getmro(component_type)
        ]
        for par in sig.parameters.values()
        if par.kind == par.KEYWORD_ONLY and not par.name.startswith("_")
    ]

These signatures have to be cached somewhere of course for easy/quick lookup, based on component type.

Signature of __init__ method of Component would need to look something like the following, in order to pass the parent component as argument:

class Component:
    def __init__(self, __parent=None, **props):
        ...

Letting the user express the props this way would be pretty pythonic, I think, but for me the main reason against it would be that it requires 'duplicating' the __init__ arguments into the call to super().__init__(), which might result in user errors if people forget to pass it through. Seems like a lot of boilerplate.

Another consideration is that the user would be responsible mostly for checking and validating props. And the user would only be able to do that on __init__. Might be that that is just fine though. For instance, to check for required props could be done as follows:

def __init__(self, *, value=None, **kwargs):
    if value is None:
        raise RuntimeError(f"Missing required prop: {value}")
    super().__init__(value=value, **kwargs)

I like that this solution makes use of Python's type system, but the verbosity and limitations are things to consider.

@Korijn
Copy link
Collaborator

Korijn commented Sep 14, 2023

A common approach is to use class attributes, like in a dataclass. It's less pythonic, but it does require less boiler plate. There are a few popular libraries as well that did this before it was introduced to the stdlib:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants