-
Notifications
You must be signed in to change notification settings - Fork 11
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
Handle deleted buckets gracefully #190
Conversation
7f3659a
to
fc22f99
Compare
The pattern here seems rather weird. Shouldn't we rather declare proper exception class for the case of failing to create a bucket and raise that instead of the current behavior of leaking random provider specific exceptions? |
@rikonen the reasoning was that:
|
fc22f99
to
ddd5d1d
Compare
It is true that to make it not backward compatible some parameter to control the behavior would still be needed, and that would mean the interface is still a bit awkward. The So you'd have something like
where the |
I picked the names to avoid the name We could have a solution that looks roughly like this: def get_transfer(storage_config: Config, ensure_object_store_available: bool = True) -> BaseTransfer[Any]:
"""
By default, any transfer object will make sure it can establish a connection and setup the necessary storage on initialization.
When ensure_object_store_available is False, the backing storage (e.g. bucket) is not validated or created until first use.
"""
transfer_obj = get_transfer_from_model(..., ensure_object_store_available=ensure_object_store_available)
return transfer_obj
class BaseTransfer:
def __init__(self, ..., ensure_object_store_available: bool = True):
...
if ensure_object_store_available:
self._verify_object_storage_unwrapped()
self._create_object_store_if_needed_unwrapped()
def _verify_object_storage_unwrapped(self):
self.client.get(...)
def verify_object_storage(self):
try:
self._verify_object_storage_unwrapped()
except (HttpError, RuntimeError):
raise TransferObjectStoreInitializationError()
def _create_object_store_if_needed_unwrapped(self):
self.client.post(...)
def create_object_store_if_needed(self):
try:
self._create_object_store_if_needed_unwrapped()
except (HttpError, RuntimeError):
raise TransferObjectStoreCreateError()
class TransferObjectStoreConfigurationError(rohmu.errors.Error):
"""Raised when a transient network or permission issue does not allow us to validate access to the object store"""
...
class TransferObjectStoreMissingError(rohmu.errors.Error):
"""Raised when we know for sure the bucket is missing"""
...
class TransferObjectStoreCreateError(rohmu.errors.Error):
"""When we fail to create the object store, regardless of the underlying cause"""
... With such an implementation, we preserve the behaviour of And it would cover our use case with such a usage: transfer = get_transfer(config, ensure_object_store_available=False)
try:
transfer.verify_object_storage()
except TransferObjectStoreConfigurationError:
# This is probably a misconiguration or a misconfiguration, retry later
...
except TransferObjectStoreMissingError:
# The bucket was deleted, do not treat this as an error
pass
# We do not call create_object_store_if_needed() at all in this case In a next major version, the current users would start using the API as follows to keep the current behviour: transfer = get_transfer(..., ensure_object_store_available=False)
try:
transfer.verify_object_storage()
transfer.create_object_store_if_needed()
except rohmu.errors.Error: # or more specific subclasses, do not catch google-specific or aws-specific exceptions
...
# Operate on the transfer object as usual |
ddd5d1d
to
334b15e
Compare
That suggestion sounds good to me |
85556ed
to
470e7c2
Compare
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.
LGTM
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 didn't read this fully but the interface looks reasonable
rohmu/object_storage/azure.py
Outdated
# TODO resolve new mypy error that seems unrelated to my changes | ||
destination_client.abort_copy(copy_props.id, timeout=timeout) # type: ignore |
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.
Fixing this separately in #191
470e7c2
to
30f580b
Compare
30f580b
to
31bfb06
Compare
About this change - What it does
Introduce the
ensure_object_store_available
parameter to allow creating a transfer object without necessarily creating the backing resources.When that flag is passed the constructor will not attempt to create missing resources (such as storage buckets), which would allow consumers of the API to handle error cases without having to expose exceptions from rohmu's implementation details.
Instead they can call
verify_object_storage
to check for existence/access, and/orcreate_object_store_if_needed
to create the bucket/container if it doesn't already exist.Resolves: #xxxxx
Why this way
This provides a nicer interface to handle potential errors related to network issues/permission issues/etc outside of the constructor. It keeps the current behaviour by default for backwards compatibility.
It's a first step towards untangling the I/O from the initialization process.
This approach avoids adding additional exceptions thrown in the constructor, or alternatively relying even more on the content of the error message string.
An earlier draft of the PR was relying on an
initialized
flag, but new exceptions and methods are more flexible and provide a nicer API.