pbelskiy / ujenkins

Python client for Jenkins (sync + async)
https://ujenkins.readthedocs.io
MIT License
20 stars 9 forks source link

Better types definitions - pyright #16

Open DanielWarloch opened 1 year ago

DanielWarloch commented 1 year ago

Can ujenkins better support return types(mostly for async staff)? When I use AsyncJenkinsClient and call for example hw_jenkins.builds.get_artifact() the return type is Bytes so when I await for that then pyright will return error: "bytes" is not awaitable (reportGeneralTypeIssues). To resolve these issues it is required to do casting on each function call cast(Awaitable[bytes], hw_jenkins.builds.get_artifact()).

pbelskiy commented 1 year ago

Thanks for bug report. I need time to investigate it.

It's probably because one code base for both sync and async adapters, but function signatures are same.

If you have some good solution or idea your PR is welcome!

Screenshot 2023-08-01 at 23 32 43
pbelskiy commented 1 year ago

I will try to use overloads and approach described here https://github.com/microsoft/pyright/discussions/5629#discussioncomment-6621771

https://github.com/python/typing/issues/1183

pbelskiy commented 1 year ago

Original problem:

import asyncio

from typing import Any

class Builds:
    def __init__(self, jenkins) -> None:
        self.jenkins = jenkins

    def get(self, name: str) -> str:
        return self.jenkins._request(name)

class Jenkins:
    def __init__(self) -> None:
        self.builds = Builds(self)

class AsyncJenkinsClient(Jenkins):
    async def _request(self, name: str) -> Any:
        return name

class JenkinsClient(Jenkins):
    def _request(self, name: str) -> Any:
        return name

async def main():
    client = AsyncJenkinsClient()
    result = await client.builds.get('async')
    print(result)

    client = JenkinsClient()
    result = client.builds.get('sync')
    print(result)

asyncio.run(main())
pbelskiy commented 1 year ago

I suggest to use .pyi files for function signatures with @propery to prevent less code readability.

https://github.com/microsoft/pyright/discussions/6424

PoC with correct types:


import asyncio

from typing import Any, Coroutine, overload, TypeVar, Generic, Union

ClientT = TypeVar('ClientT', bound=Union['JenkinsClient', 'AsyncJenkinsClient'])

class Builds(Generic[ClientT]):
    def __init__(self, jenkins: ClientT) -> None:
        self.jenkins = jenkins

    @overload
    def get(self: 'Builds[JenkinsClient]', name: str) -> str:
        ...

    @overload
    def get(self: 'Builds[AsyncJenkinsClient]', name: str) -> Coroutine[Any, Any, str]:
        ...

    def get(self, name: str) -> str | Coroutine[Any, Any, str]:
        return self.jenkins._request(name)

class AsyncJenkinsClient:
    def __init__(self) -> None:
        self.builds = Builds(self)

    async def _request(self, name: str) -> Any:
        return name

class JenkinsClient:
    def __init__(self) -> None:
        self.builds = Builds(self)

    def _request(self, name: str) -> Any:
        return name

async def main() -> None:
    async_client = AsyncJenkinsClient()
    result = await async_client.builds.get('async')
    print(result)

    client = JenkinsClient()
    result = client.builds.get('sync')
    print(result)

asyncio.run(main())