Mypy plugins: Changing class signature impossible here?
Hello,
I'm trying to write a plugin for my paramclasses library. I already posted about it on gitter to ask a few questions (without answer at the moment).
I'm still trying to get to work on my library's typing issues whilst being a complete noob on the subject.
So I started by writing a logger-like dummy plugin which shows the called hooked with their args/kwargs (so that I understand the mechanisms a bit better), and trying to solve the signature problem.
1. Nonparamclass MWE: looks OK
Here's a MWE:
class A:
def __init__(self, *args: object, **kwargs: object) -> None:
"""Takes anything at runtime but raises unless only 'foo' kwarg."""
if args or list(kwargs) != ["foo"]:
msg = "Only 'foo' kwarg is acceptable"
raise ValueError(msg)
self.foo = kwargs.get("foo")
A(bar="should fail")
I would like mypy to pick up the fact that the signature is in fact A(*, foo). The logger picks up this (filtered to keep only signature related hooks):
(removing the report hook):
[get_function_signature_hook] → ('builtins.list',), {}
[get_method_signature_hook] → ('builtins.list.__ne__',), {}
[get_function_signature_hook] → ('builtins.ValueError',), {}
[get_method_signature_hook] → ('builtins.dict.get',), {}
[get_method_signature_hook] → ('builtins.dict.get',), {}
[get_method_signature_hook] → ('builtins.object.__init_subclass__',), {}
[get_function_signature_hook] → ('mwe.A',), {}
Q1. Am I right to assume that I logically should use get_function_signature_hook with mwe.A for this example?If so, I think this works fine since I have a way to write a plugin to do what I want.
Now on to my use case
2. Paramclass MWE: not OK
My usecase MWE would be:
from paramclasses import ParamClass
class A(ParamClass):
foo: int
A(bar="should fail")
When I filter out the logger plugin output, to keep only the "signature" hook, I only get this which refers to internals that seem unrelated:
[get_function_signature_hook] → ('paramclasses.paramclasses._MissingType',), {}
[get_function_signature_hook] → ('paramclasses.paramclasses._run_once',), {}
[get_function_signature_hook] → ('paramclasses.paramclasses._run_once',), {}
[get_function_signature_hook] → ('paramclasses.paramclasses._run_once',), {}
So in the end, I'm completely lost on what I should/could do.
Q2. Is there a way to think about understanding the problem? Note that if useful, the __signature__ attribute returns the desired result at runtim.
Thanks in advance! Élie
FYI I think the dataclasses plugin might work for inspiration (I think it supports subclassing?).
(Also maybe check out https://docs.python.org/3/library/typing.html#typing.dataclass_transform for the spec it implements)
Thank you for your comment. I do plan on looking at it more closely, but I strongly suspects that it relies on the get_class_decorator_hook() hook (which seems kind of unnatural when you think about it, which makes me think it may have been added specifically for this case).
My library relies on inheritance through a metaclass instead (so end users do not need any knowledge about this) which is really quite different.
I still take a look just in case, thanks, and dataclass_transform does look interesting!
Turns out dataclass_transform is special cased in mypy, so I imagine there's no way to do this at the moment:
https://github.com/python/mypy/blob/b025bda885027aa12965135e376a060d7582df06/mypy/semanal_main.py#L529-L536
I'm sure a PR to add an extra hook would be appreciated here.
Hey,
Thanks for coming back to this. Indeed, I tried a bit and it works only to some extent as it is clearly made for dataclasses.
It is nice to know that addi g a hook can be considered! I will dive into this! However, I noticed that the get_method_signature_hook() explicitly is not called on __init__ and __new__. Is there a specific reason? Maybe this would be the only thing to change? I'm not quite sure yet though.