-
-
Notifications
You must be signed in to change notification settings - Fork 148
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
Full response type hints #199
Comments
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. :) |
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. |
TypedDict
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] |
Yes, we can definitely use |
Thanks! I read your code, and the implementation was basically the same!! I'll make a draft pull request. |
Hi everyone! 👋 I wanted to share that I’ve started working on a project called pydantic-api-sdk-notion, a Python SDK for the Notion API with type-safe support powered by Pydantic. This SDK leverages the excellent work done by To make this possible, I’ve also created a separate repository called pydantic-api-models-notion, which maintains up-to-date Notion API data models based on the latest documentation. Most models are already complete, and I’m currently working on Block support. If you're someone who enjoys the benefits of type hints or needs them for better IntelliSense and validation, feel free to try it out! GitHub repositories: I’d love to hear your feedback, and I’m happy to collaborate with anyone interested in improving type-hint support for Notion's API! 😊 |
I was just about to suggest pydantic as an alternative to TypedDict here! Unfortunately it would be a breaking change, so perhaps wrapping the existing API methods makes more sense than modifying them. |
Why do you guys prefer Pydantic over typed dicts if what we want here is only type hints for autocompletion and such? I know (and like) Pydantic quite well but I'm not sure why we'd want to use it here, so I'm curious. :) |
I like to deal with API input/outputs using classes that explicitly model the data and interfaces. It's largely an aesthetic preference – code written with dicts looks and feels different than code with model objects. If the SDK provides such models then I don't have to write my own, but can still subclass them if needed to add methods that I need. In some scenarios pydantic can also provide some assurances via data validation. As an aside this issue is scoped to response data, though I would consider input data (e.g. query filter predicates) the same. |
Personally, I prefer Pydantic over TypedDict not just for type hints and validation, but also because it aligns better with my work on agentic AI systems. Using Pydantic allows me to semantically enrich Notion API models with descriptions, examples, and constraints, making them more agent-friendly. When interacting with AI agents, I can leverage model_schema_json() to convey structured API data models instead of relying on free-text API documentation, which can sometimes be ambiguous or outdated (as seen in Notion’s docs). This approach improves interpretability and automation when AI needs to reason about API data. Just wanted to share this perspective |
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 usingTypedDict
. 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: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.The text was updated successfully, but these errors were encountered: