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

Type hints #199

Open
guacs opened this issue Aug 8, 2023 · 5 comments
Open

Type hints #199

guacs opened this issue Aug 8, 2023 · 5 comments
Labels
enhancement New feature or request help wanted Extra attention is needed

Comments

@guacs
Copy link

guacs commented Aug 8, 2023

I was wondering if there is any interest in specifying the types of each response instead of just using Any. The types could be taken from the official API written in Typescript. I looked through #9 and so instead of using dataclasses, I'm proposing type hinting everything using TypedDict. This would make things quite easy for the users since they would get features like autocomplete and would be able run their code through a type checker. Essentially I'm suggesting something like the following:

class Database(TypedDict):
    ...

class DatabasesEndpoint(Endpoint):

    def retrieve(self, db_id: str) -> Database:
        ...

However, one possible issue I'm seeing with this approach is that the return types of the endpoints currently are not suitable for this as far as I know. The SyncAsync[T] does not work well with type checkers so instead separate endpoints classes for synchronous and asynchronous endpoints would need to be maintained.

@ramnes
Copy link
Owner

ramnes commented Aug 8, 2023

Yes, I'm definitely interested if you're willing to add them and most importantly maintain them afterwards to stay up to date with the TS client. :)

@guacs
Copy link
Author

guacs commented Aug 9, 2023

Yupp I'm willing to add them and maintain them as well. I'll start working on it and hopefully if this makenotion/notion-sdk-js#294 gets resolved, then maintenance would be much easier.

@ramnes ramnes added enhancement New feature or request help wanted Extra attention is needed labels Aug 10, 2023
@ramnes ramnes changed the title Feature Request: add types for all responses using TypedDict Type hints Jan 30, 2024
@superleesa
Copy link

@ramnes

I think we can do something like this? I checked that this works for mypy without errors. This way, we can still have one class for each set of endpoints (no separate classes for sync and async clients). This uses two overloads for each endpoint instead.

from __future__ import annotations
from typing import Generic, TypeVar, Any, TypedDict, Awaitable, Union, overload
from abc import ABC, abstractmethod


T = TypeVar('T', bound=Union[dict, Awaitable[dict]])
ResultDictType = TypeVar('ResultDictType')
MyDict = TypedDict('MyDict', {'name': str, 'age': int})


class Endpoint(Generic[T], ABC):
    def __init__(self, parent: BaseClient[T]) -> None:
        self.parent = parent


class MyDictOutputEndpoint(Endpoint[T]):
    @overload
    def get_something(self: MyDictOutputEndpoint[dict]) -> MyDict:
        ...
    
    @overload
    def get_something(self: MyDictOutputEndpoint[Awaitable[dict]]) -> Awaitable[MyDict]:
        ...
    
    def get_something(self) -> Awaitable[MyDict] | MyDict:
        return self.parent.request(method="get", cast_to=MyDict)


class BaseClient(Generic[T], ABC):
    def __init__(self):
        self.mydictoutput_endpoint = MyDictOutputEndpoint[T](self)
    
    @abstractmethod
    def request(self, method: str, cast_to: type[ResultDictType]) -> Awaitable[ResultDictType] | ResultDictType:
        pass
    

class Client(BaseClient[dict]):
    def request(self, method: str, cast_to: type[ResultDictType]) -> ResultDictType:
        return cast_to(**{'name': 'John', 'age': 42})


class AsyncClient(BaseClient[Awaitable[dict]]):
    async def request(self, method: str, cast_to: type[ResultDictType]) -> ResultDictType:
        return cast_to(**{'name': 'John', 'age': 42})


# how to use
mydict = Client().mydictoutput_endpoint.get_something()  # return type is MyDict

async def get_async() -> MyDict:
    return await AsyncClient().mydictoutput_endpoint.get_something()  # return type is Awaitable[MyDict]

@ramnes
Copy link
Owner

ramnes commented Oct 17, 2024

Yes, we can definitely use @overload, have a look here for a PoC I wrote earlier: #200 (comment)

@superleesa
Copy link

Thanks! I read your code, and the implementation was basically the same!! I'll make a draft pull request.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request help wanted Extra attention is needed
Projects
None yet
Development

No branches or pull requests

3 participants