modern-python / that-depends

DI-framework, inspired by python-dependency-injector, but without wiring. Python 3.12 is supported
https://that-depends.readthedocs.io/
MIT License
156 stars 12 forks source link

pyright finds errors in basic Container usage #31

Closed jasonprado closed 4 months ago

jasonprado commented 4 months ago

I'm seeing typecheck errors from pyright when I make a Container in what I think is an idiomatic way.

My environment:

$ cat requirements.txt | grep that-                                                                                                        
that-depends==1.10.0
$ mypy --version
mypy 1.10.0 (compiled: yes)
$ pyright --version  
pyright 1.1.366

Code:

from typing import Iterator
from that_depends import BaseContainer, providers

class MyClient:
    def __init__(self, service_url: str):
        self.service_url = service_url

    def __repr__(self) -> str:
        return f"MyClient(service_url={self.service_url})"

class MyService:
    def __init__(self, client: MyClient):
        self.client = client

    def __repr__(self) -> str:
        return f"MyService(client={self.client})"

def get_service_url() -> Iterator[str]:
    yield "http://localhost:5000"

class MyContainer(BaseContainer):
    service_url = providers.Resource(get_service_url)

    my_client = providers.Factory(MyClient, service_url=service_url)
    my_service = providers.Factory(MyService, client=my_client)

program output:

$ python main.py   
MyService(client=MyClient(service_url=http://localhost:5000))

mypy output:

$ mypy .        
Success: no issues found in 1 source file

pyright output:

$ pyright .
[...]
main.py
main.py:28:57 - error: Argument of type "Resource[str]" cannot be assigned to parameter "service_url" of type "str"
    "Resource[str]" is incompatible with "str" (reportArgumentType)
main.py:29:54 - error: Argument of type "Factory[MyClient]" cannot be assigned to parameter "client" of type "MyClient"
    "Factory[MyClient]" is incompatible with "MyClient" (reportArgumentType)
2 errors, 0 warnings, 0 informations 
lesnik512 commented 4 months ago

@jasonprado Please, try adding .cast when passing service_url and my_client, like this:

class MyContainer(BaseContainer):
    service_url = providers.Resource(get_service_url)

    my_client = providers.Factory(MyClient, service_url=service_url.cast)
    my_service = providers.Factory(MyService, client=my_client.cast)
jasonprado commented 4 months ago

@lesnik512 Adding .cast seems to do the right thing, thanks!

In case this is useful to anyone else, I tried making mypy find errors in a container and I haven't been able to yet.

Correct code, no .cast:

class MyContainer(BaseContainer):
    service_url = providers.Resource(get_service_url)
    my_client = providers.Factory(MyClient, service_url=service_url)
    my_service = providers.Factory(MyService, client=my_client)

mypy errors: None pyright errors: "Resource[str]" is incompatible with "str" (reportArgumentType) as above

Correct code, with cast:

class MyContainer(BaseContainer):
    service_url = providers.Resource(get_service_url)
    my_client = providers.Factory(MyClient, service_url=service_url.cast)
    my_service = providers.Factory(MyService, client=my_client.cast)

mypy errors: None pyright errors: None

Incorrect code, with cast:

class MyContainer(BaseContainer):
    service_url = providers.Resource(get_service_url)
    my_client = providers.Factory(MyClient, service_url=service_url.cast)
    my_service = providers.Factory(MyService, client=service_url.cast)  # This should be an error

mypy errors: None pyright errors: Argument of type "Resource[str]" cannot be assigned to parameter "client" of type "MyClient"

It doesn't seem like mypy can find type errors with this kind of generic/ParamSpec usage.

lesnik512 commented 4 months ago

I didn't find a way to make mypy checking signatures for classes. Because looks like there is no way to describe signature of class's init function

But mypy is able to check params for functions and generators.