I create the Google API Resource
class for a specific type (in this case blogger
).
from googleapiclient.discovery import Resource, build
def get_google_service(api_type) -> Resource:
credentials = ...
return build(api_type, 'v3', credentials=credentials)
def blog_service():
return get_google_service('blogger')
def list_blogs():
return blog_service().blogs()
The problem arises when using the list_blogs
function.
Since I am providing a specific service name, I know that the return value of blog_service
has a blogs
method, but my IDE doesn't recognize it.
Is there a way to annotate the blog_service
function (or any other part of the code) to help my IDE recognize the available methods like blogs
?
The problem here is that the Resource
class dynamically sets arbitrary attributes/methods based on what collections the underlying API provides.
In your case apparently there is a blogs
collection, which means the blogs
method is dynamically constructed on that Resource
object.
Expecting static type annotations, when your types are created dynamically is a tall order. (I assume this is one of the reasons the maintainers of google-api-python-client
do not even bother with type-hinting in that package.)
But depending on your goals with those functions, you might improve your IDE experience by using protocols.
If you know that your blog_service
function returns an object that has a blogs
method, you can define a corresponding protocol. The problem is of course kicked down the road because whatever that blogs
method returns may also have arbitrary methods. But depending on if this is important to you, you can apply the same principle for that again.
The upside is that Resource
itself actually seems to have very few public methods, essentially just close
and the context manager protocol. So you could emulate that in some sort of base protocol and use inheritance to construct different resource-protocols.
Here is an example to illustrate:
from typing import Any, Protocol, Self
from googleapiclient.discovery import build # type: ignore[import]
class ResourceProtocol(Protocol):
def close(self) -> None: ...
def __enter__(self) -> Self: ...
def __exit__(self, *args: Any) -> None: ...
class BloggerProtocol(ResourceProtocol):
def blogs(self) -> Any: ...
def get_google_service(api_type: str) -> Any:
credentials = ...
return build(api_type, 'v3', credentials=credentials)
def blog_service() -> BloggerProtocol:
return get_google_service('blogger') # type: ignore[no-any-return]
def list_blogs() -> Any:
return blog_service().blogs()
(Note that those are all literal ellipses ...
. Also, if you are on Python <3.11
, you should be able to import Self
from typing_extensions
instead.)
Now your IDE should be able to detect that the object returned by blog_service
has a blogs
method and can be used as a context manager (using with
).
Notice that I annotated the return type of blogs
as Any
because I don't know what type it is supposed to return.