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

Pylance seem to have difference with class decorators #6272

Closed
gintsmurans opened this issue Aug 15, 2024 · 4 comments
Closed

Pylance seem to have difference with class decorators #6272

gintsmurans opened this issue Aug 15, 2024 · 4 comments
Assignees
Labels
needs repro Issue has not been reproduced yet

Comments

@gintsmurans
Copy link

Hi!

This is a weird one. At first I thought there was an issue with extraPaths as those has been troubling, but turns out the issue was with my decorator.

When I use following decorator, pylance seem to be unable to find class properties:

def ignore_unknown_kwargs(cls: Type[Any]) -> Type[Any]:
    """
    Class decorator that modifies the __init__ method to ignore unknown keyword arguments.
    """
    originalInit = cls.__init__

    def newInit(self: Any, *args: list[Any], **kwargs: dict[str, Any]):
        # Filter out kwargs that are not properties of the class
        valid_kwargs = {k: v for k, v in kwargs.items() if hasattr(self, k)}
        originalInit(self, *args, **valid_kwargs)

    cls.__init__ = newInit  # type: ignore
    return cls

When I rewrite the decorator to this:

T = TypeVar("T", bound=Type[Any])

def ignore_unknown_kwargs() -> Callable[[T], T]:
    """
    Class decorator factory that modifies the __init__ method to ignore unknown keyword arguments.
    """

    def decorator(cls: T) -> T:
        originalInit = cls.__init__

        # @wraps(originalInit)
        def newInit(self: Any, *args: Any, **kwargs: Any):
            # Filter out kwargs that are not properties of the class
            valid_kwargs = {k: v for k, v in kwargs.items() if hasattr(self, k)}
            originalInit(self, *args, **valid_kwargs)

        cls.__init__ = newInit  # type: ignore
        return cls

    return decorator

Pylance seem to be able to find both correct and incorrect properties.

@github-actions github-actions bot added the needs repro Issue has not been reproduced yet label Aug 15, 2024
@rchiodo
Copy link
Contributor

rchiodo commented Aug 15, 2024

Thanks for the issue.

Do you have an example of where you're using this decorator?

@gintsmurans
Copy link
Author

Thanks for the issue.

Do you have an example of where you're using this decorator?

Yes, lets say I have a class defined like this:


@ignore_unknown_kwargs
@dataclass
class RV4ProductionJobInfo(DBDataModel):
    """RV4ProductionJobInfo"""
    
    aField: int | None = field(...

And then in some other file I am doing something like this:

jobInfo = RV4ProductionJobInfo()
jobInfo.aField = 1 # This would be where properties are unknown. 

@erictraut
Copy link
Contributor

Your decorator is erasing all type information because it returns a value of type type[Any].

If you want to preserve the signature of the decorated class (or its constructor), there are several ways to express this.

You could use a ParamSpec:

def ignore_unknown_kwargs[**P, R](cls: Callable[P, R]) -> Callable[P, R]: ...

Or you could use a regular type variable with a Callable upper bound:

def ignore_unknown_kwargs[T: Callable](cls: T) -> T: ...

Or you could use a regular type variable but specify that the pre-decorated type is a class:

def ignore_unknown_kwargs[T](cls: type[T]) -> type[T]: ...

There's (currently) no way in the Python type system to transform a signature in a way that adds an arbitrary number of untyped keyword arguments to a signature, so I don't see a way to reflect what your decorator is doing here.

@gintsmurans
Copy link
Author

Well, that's interesting, because class it self defines those attributes that vscode cannot find. But I will not pretend to know inner workings of a type checker, so will close this for now and if somebody else have this same issue, they can find a solution in this thread.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
needs repro Issue has not been reproduced yet
Projects
None yet
Development

No branches or pull requests

4 participants