-
Notifications
You must be signed in to change notification settings - Fork 46
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
MyPy errors when using sync objects #491
Comments
Yeah I can reproduce that. I also see a similar error of I try and assign to a typed variable. from kr8s.objects import Pod
pods: list[Pod] = Pod.list() $ mypy test.py
test.py:3: error: Incompatible types in assignment (expression has type "Coroutine[Any, Any, APIObject | list[APIObject]]", variable has type "list[Pod]") [assignment]
Found 1 error in 1 file (checked 1 source file) I think there are two potential solutions here:
|
A third option could be to do away with the # kr8s/objects.py
from kr8s._objects import Pod as _Pod
from kr8s._async_utils import run_sync
class Pod(_Pod):
"""..."""
...
@classmethod
def list(cls, **kwargs) -> APIObject | list[APIObject]:
"""List objects in Kubernetes.
Args:
**kwargs: Keyword arguments to pass to :func:`kr8s.get`.
Returns:
A list of objects.
"""
return run_sync(super().list)(**kwargs)
... The upsides of this are:
But the downsides are:
|
I've tried several approaches, but none seem to work for the first option. The third option is also problematic because:
There doesn't seem to be an easy fix |
Thanks for taking the time to dig into this. I did think more about option 1 and I guess it's problematic because even if we write a plugin specifically for kr8s, all code that depends on kr8s would need the plugin, which is not something we can ask users to do. I agree for option 3 it would be best to return For option 2 I wonder if we need some custom stub generation script that we can invoke as part of the pre-commit hooks. This way we can explicitly tell mypy what to expect, but we don't need to worry about drift if they are auto-generated. |
Chiming in here, the easiest would be just separating the api surface
|
@ion-elgreco I appreciate the input by that's not quite how things work in this library. All objects are created as async first where all methods are async, and then wrapped with a decorator that makes them sync to expose the sync API. Lines 200 to 203 in 903f869
I think what you are suggesting is option 3, which is to do away with the |
@jacobtomlinson yes that option, but probably with some clever inheritance you could reduce the duplication. It would be definitely a breaking change and warrant for a 1.0 perhaps |
@ion-elgreco if you have some thoughts on how to do this I'd be really interested to hear. I've been thinking about this for months at this point and still haven't found an elegant solution that doesn't involve a whole bunch of code duplication. The best option I've found so far is to make a script that generates type stubs for the sync API (see #493). This way we can leave the async->sync conversion as it is and just fix the type hints after the fact. But this is non-trivial and I havent got a robust implementation finished yet. I'm keen to explore separating out the APIs though if there is a better implementation. For example:
If we removed the Are you suggesting there is something we could do with a mixin to remove this duplication? |
@jacobtomlinson There aren't many solutions tbh. You always have to declare the sync method explicitly. Maintainability wise, this seems simpler class APIObject(APIObjectBase):
def get(
cls,
name: str | None = None,
namespace: str | None = None,
api: Api | None = None,
label_selector: str | dict[str, str] | None = None,
field_selector: str | dict[str, str] | None = None,
timeout: int = 2,
**kwargs,
) -> Self:
return run_sync(cls.get_async(
name=name,
namespace=namespace,
api=api,
label_selector=label_selector,
field_selector=field_selector,
timeout=timeout,
kwargs=kwargs
)) # type: ignore Or it's dynamically adding methods and creating the stubs, like you did |
Sure. I guess I'm intrigured by your commend about clever inheritance stuff to reduce duplication. What did you have in mind? |
Nothing fancy, just having a private Bass class holds async methods that are reused across the api surface. Main APIObject inherits that and you explicitly set the sync versions of it, which calls the async ones. This would be more static and just once some work upfront. |
Which project are you reporting a bug for?
kr8s
What happened?
It seems that MyPy continues to interpret methods as asynchronous for sync objects, even though at runtime they have been converted to synchronous methods with the
sync
decorator. This leads to MyPy errors like:with code like:
Anything else?
No response
The text was updated successfully, but these errors were encountered: