Open berendkleinhaneveld opened 2 years ago
Inspiring docs: https://vuejs.org/guide/components/props.html#prop-validation
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.
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.
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:
Using the props specifications, we can further improve the compilation of template expressions.