[redis] fix pipeline return types#4989
Conversation
Pipeline is a subclass of Redis, but instead of the commands returning results, they return Pipeline.
Previously the types incorrectly said `Redis().pipeline().rpush("mylist")` returned `int`. The updated types now type the response as `Pipeline`.
With this change the following code now type checks.
```
from redis import Redis
r = Redis()
list_length, _ = r.pipeline().rpush("mylist").ltrim("mylist", 0, 100).execute()
```
| def reset(self): ... | ||
| def reset(self) -> None: ... | ||
| def multi(self) -> None: ... | ||
| def execute_command(self, *args, **kwargs): ... |
There was a problem hiding this comment.
execute_command, along with parse_response, watch, and unwatch were duplicated in the methods I copied from Redis, so I deleted these and updated the copied ones below.
| def unwatch(self): ... | ||
| def script_load_for_pipeline(self, script): ... | ||
|
|
||
| class StrictPipeline(BasePipeline, StrictRedis): ... |
There was a problem hiding this comment.
StrictPipeline and BasePipeline don't exist in the library, so I think it's safe to use use the name Pipeline: https://github.com/andymccurdy/redis-py/blob/1a41cfd95a53a95b078084d8627be6b6fba3bb71/redis/client.py
There was a problem hiding this comment.
This probably was leftover from an old redis-py version.
| # methods from `Redis` client. | ||
| # arguments match `Redis`, but with some exceptions return types are | ||
| # `Pipeline` or `None` |
There was a problem hiding this comment.
Basically all the commands have the same arguments as Redis, but the return types are different.
| def sort( | ||
| self, | ||
| name: _Key, | ||
| start: Optional[int] = ..., | ||
| num: Optional[int] = ..., | ||
| by: Optional[_Key] = ..., | ||
| get: Optional[Union[_Key, Sequence[_Key]]] = ..., | ||
| desc: bool = ..., | ||
| alpha: bool = ..., | ||
| store: Optional[_Key] = ..., | ||
| groups: bool = ..., | ||
| ) -> Pipeline: ... |
There was a problem hiding this comment.
Redis().sort() is overloaded to provide an accurate return type, but for Pipeline the return type is always Pipeline, so I removed the overloads.
typeshed/stubs/redis/redis/client.pyi
Lines 495 to 534 in e2fd852
| def smembers(self, name: _Key) -> Pipeline: ... | ||
| def smove(self, src, dst, value) -> Pipeline: ... | ||
| def spop(self, name, count: Optional[int] = ...) -> Pipeline: ... | ||
| def srandmember(self, name: _Key, number: Optional[int] = ...) -> Pipeline: ... |
There was a problem hiding this comment.
I removed the overload here too for the same reason.
typeshed/stubs/redis/redis/client.pyi
Lines 555 to 558 in e2fd852
| groups: bool = ..., | ||
| ) -> Pipeline: ... | ||
| def scan(self, cursor: int = ..., match: Optional[_Key] = ..., count: Optional[int] = ...) -> Pipeline: ... | ||
| def scan_iter(self, match: Optional[Text] = ..., count: Optional[int] = ...) -> Iterator[Any]: ... |
There was a problem hiding this comment.
The scan_iter and other _iter methods return generators that explode when next() is called on them.
| def script_flush(self) -> Pipeline: ... | ||
| def script_kill(self) -> Pipeline: ... | ||
| def script_load(self, script) -> Pipeline: ... | ||
| def register_script(self, script: Union[Text, _StrType]) -> Script: ... |
There was a problem hiding this comment.
unlike other methods, this one actually returns Script
| def pubsub_numpat(self) -> Pipeline: ... | ||
| def monitor(self) -> Monitor: ... | ||
| def cluster(self, cluster_arg: str, *args: Any) -> Pipeline: ... | ||
| def client(self) -> Any: ... |
There was a problem hiding this comment.
this method explodes when called
| def pubsub_channels(self, pattern: _Key = ...) -> Pipeline: ... | ||
| def pubsub_numsub(self, *args: _Key) -> Pipeline: ... | ||
| def pubsub_numpat(self) -> Pipeline: ... | ||
| def monitor(self) -> Monitor: ... |
There was a problem hiding this comment.
This also returns Monitor instead of Pipeline
| xx: bool = ..., | ||
| keepttl: bool = ..., | ||
| ) -> Pipeline: ... | ||
| def __setitem__(self, name, value) -> None: ... |
There was a problem hiding this comment.
should all the dunder methods be organized at the top?
There was a problem hiding this comment.
Personally, I try to use the order the implementation uses. But it is your choice.
| def object(self, infotype, key) -> Pipeline: ... | ||
| def ping(self) -> Pipeline: ... | ||
| def save(self) -> Pipeline: ... | ||
| def sentinel(self, *args) -> None: ... |
There was a problem hiding this comment.
this deprecated method doesn't do or return anything: https://github.com/andymccurdy/redis-py/blob/1a41cfd95a53a95b078084d8627be6b6fba3bb71/redis/client.py#L1448-L1451
srittau
left a comment
There was a problem hiding this comment.
Thank you for this PR and sorry for the long delay. Only one issue below.
| def ping(self, message: Optional[_Value] = ...) -> None: ... | ||
|
|
||
| class BasePipeline: | ||
| class Pipeline: |
There was a problem hiding this comment.
This devices from Redis in the implementation, so it should do so here as well. This will probably need # type: ignores where the signature of Pipeline does not match that of Redis. (But since Pipeline is a subtype of Redis that shouldn't be much of a concern.)
| xx: bool = ..., | ||
| keepttl: bool = ..., | ||
| ) -> Pipeline: ... | ||
| def __setitem__(self, name, value) -> None: ... |
There was a problem hiding this comment.
Personally, I try to use the order the implementation uses. But it is your choice.
|
I updated The ordering of methods should match the |
|
I think |
Pipelineis a subclass ofRedis, but instead of the commands returning results, they returnPipeline.Previously the types said
Redis().pipeline().rpush("mylist")returnedint. The updated types now type the response asPipeline.With this change the following code now type checks.