mypy icon indicating copy to clipboard operation
mypy copied to clipboard

Mypy plugins: Changing class signature impossible here?

Open eliegoudout opened this issue 7 months ago • 4 comments

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

eliegoudout avatar Jun 01 '25 12:06 eliegoudout

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)

A5rocks avatar Jun 02 '25 00:06 A5rocks

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!

eliegoudout avatar Jun 02 '25 06:06 eliegoudout

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.

A5rocks avatar Jun 06 '25 03:06 A5rocks

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.

eliegoudout avatar Jun 06 '25 06:06 eliegoudout