-
Notifications
You must be signed in to change notification settings - Fork 2.6k
Provide aclose() / close() for classes requiring lifetime management #2898
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
Changes from all commits
158660c
25d29a3
2a37691
79e7c26
3d09daf
d7b4e7a
bcca824
cba80e0
3496e50
b16d3e1
b60dcbe
4b394a6
b55d266
09076bc
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -14,7 +14,6 @@ | |
List, | ||
Mapping, | ||
MutableMapping, | ||
NoReturn, | ||
Optional, | ||
Set, | ||
Tuple, | ||
|
@@ -65,6 +64,7 @@ | |
from redis.utils import ( | ||
HIREDIS_AVAILABLE, | ||
_set_info_logger, | ||
deprecated_function, | ||
get_lib_version, | ||
safe_str, | ||
str_if_bytes, | ||
|
@@ -527,7 +527,7 @@ async def __aenter__(self: _RedisT) -> _RedisT: | |
return await self.initialize() | ||
|
||
async def __aexit__(self, exc_type, exc_value, traceback): | ||
await self.close() | ||
await self.aclose() | ||
|
||
_DEL_MESSAGE = "Unclosed Redis client" | ||
|
||
|
@@ -539,7 +539,7 @@ def __del__(self, _warnings: Any = warnings) -> None: | |
context = {"client": self, "message": self._DEL_MESSAGE} | ||
asyncio.get_running_loop().call_exception_handler(context) | ||
|
||
async def close(self, close_connection_pool: Optional[bool] = None) -> None: | ||
async def aclose(self, close_connection_pool: Optional[bool] = None) -> None: | ||
""" | ||
Closes Redis client connection | ||
|
||
|
@@ -557,6 +557,13 @@ async def close(self, close_connection_pool: Optional[bool] = None) -> None: | |
): | ||
await self.connection_pool.disconnect() | ||
|
||
@deprecated_function(version="5.0.0", reason="Use aclose() instead", name="close") | ||
async def close(self, close_connection_pool: Optional[bool] = None) -> None: | ||
""" | ||
Alias for aclose(), for backwards compatibility | ||
""" | ||
await self.aclose(close_connection_pool) | ||
|
||
async def _send_command_parse_response(self, conn, command_name, *args, **options): | ||
""" | ||
Send a command and parse the response | ||
|
@@ -764,13 +771,18 @@ async def __aenter__(self): | |
return self | ||
|
||
async def __aexit__(self, exc_type, exc_value, traceback): | ||
await self.reset() | ||
await self.aclose() | ||
|
||
def __del__(self): | ||
if self.connection: | ||
self.connection.clear_connect_callbacks() | ||
|
||
async def reset(self): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This now removes a public method named reset - something we've shied away from doing. WDYT about keeping it defined, and also marking as deprecated? At the very least we should mark as breakingchange and then not merge to 5.1 otherwise. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah, I think you will find that I left it in place. If you can provide me with a guideline on how to mark methods and attributes as deprecated, and how we can schedule those for future removal, that would be awesome. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @kristjanvalur see my comment bellow about how to mark function as deprecated. for attributes, you can't use the decorator, so you just need to import warnings and warn deprecated if the user try to use this attribute, like we did here. |
||
async def aclose(self): | ||
# In case a connection property does not yet exist | ||
# (due to a crash earlier in the Redis() constructor), return | ||
# immediately as there is nothing to clean-up. | ||
if not hasattr(self, "connection"): | ||
return | ||
async with self._lock: | ||
if self.connection: | ||
await self.connection.disconnect() | ||
|
@@ -782,13 +794,15 @@ async def reset(self): | |
self.patterns = {} | ||
self.pending_unsubscribe_patterns = set() | ||
|
||
def close(self) -> Awaitable[NoReturn]: | ||
# In case a connection property does not yet exist | ||
# (due to a crash earlier in the Redis() constructor), return | ||
# immediately as there is nothing to clean-up. | ||
if not hasattr(self, "connection"): | ||
return | ||
return self.reset() | ||
@deprecated_function(version="5.0.0", reason="Use aclose() instead", name="close") | ||
async def close(self) -> None: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good catch. This never should have returned Awaitable[NoReturn]. For the curious readers see this. Note that only a function that unconditionally raises an exception should do so. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, and the pattern of returning awaitables through, rather than have an intermediate "await" is not a nice one and only serves to ofuscate. I guess people think they are optimizing (removing one 'await' level) but the effect is virtually unmeaasurable. |
||
"""Alias for aclose(), for backwards compatibility""" | ||
await self.aclose() | ||
|
||
@deprecated_function(version="5.0.0", reason="Use aclose() instead", name="reset") | ||
async def reset(self) -> None: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. IMHO the None in implied. It's a nit, do you feel differently? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. oh, depending on mypy warning levels, a return type is required. I have grown into the habit of always providing one explicitly. |
||
"""Alias for aclose(), for backwards compatibility""" | ||
await self.aclose() | ||
|
||
async def on_connect(self, connection: Connection): | ||
"""Re-subscribe to any channels and patterns previously subscribed to""" | ||
|
@@ -1232,6 +1246,10 @@ async def reset(self): | |
await self.connection_pool.release(self.connection) | ||
self.connection = None | ||
|
||
async def aclose(self) -> None: | ||
"""Alias for reset(), a standard method name for cleanup""" | ||
await self.reset() | ||
|
||
def multi(self): | ||
""" | ||
Start a transactional block of the pipeline after WATCH commands | ||
|
@@ -1264,14 +1282,14 @@ async def _disconnect_reset_raise(self, conn, error): | |
# valid since this connection has died. raise a WatchError, which | ||
# indicates the user should retry this transaction. | ||
if self.watching: | ||
await self.reset() | ||
await self.aclose() | ||
raise WatchError( | ||
"A ConnectionError occurred on while watching one or more keys" | ||
) | ||
# if retry_on_timeout is not set, or the error is not | ||
# a TimeoutError, raise it | ||
if not (conn.retry_on_timeout and isinstance(error, TimeoutError)): | ||
await self.reset() | ||
await self.aclose() | ||
raise | ||
|
||
async def immediate_execute_command(self, *args, **options): | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i.e deprecating here - as yo mentioned