-
Notifications
You must be signed in to change notification settings - Fork 1.7k
feat(storage): add object contexts and list filter support #17002
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
base: main
Are you sure you want to change the base?
Changes from 1 commit
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 |
|---|---|---|
|
|
@@ -101,6 +101,7 @@ | |
| "crc32c", | ||
| "customTime", | ||
| "md5Hash", | ||
| "contexts", | ||
| "metadata", | ||
| "name", | ||
| "retention", | ||
|
|
@@ -5008,6 +5009,16 @@ def retention(self): | |
| info = self._properties.get("retention", {}) | ||
| return Retention.from_api_repr(info, self) | ||
|
|
||
| @property | ||
| def contexts(self): | ||
| """Retrieve the object contexts for this object. | ||
|
|
||
| :rtype: :class:`ObjectContexts` | ||
| :returns: an instance for managing the object's contexts. | ||
| """ | ||
| info = self._properties.get("contexts", {}) | ||
| return ObjectContexts.from_api_repr(info, self) | ||
|
|
||
| @property | ||
| def soft_delete_time(self): | ||
| """If this object has been soft-deleted, returns the time at which it became soft-deleted. | ||
|
|
@@ -5300,3 +5311,78 @@ def retention_expiration_time(self): | |
| retention_expiration_time = self.get("retentionExpirationTime") | ||
| if retention_expiration_time is not None: | ||
| return _rfc3339_nanos_to_datetime(retention_expiration_time) | ||
|
|
||
|
|
||
| class ObjectContexts(dict): | ||
| """Map an object's contexts. | ||
|
|
||
| :type blob: :class:`Blob` | ||
| :param blob: blob for which these contexts apply to. | ||
|
|
||
| :type custom: dict or ``NoneType`` | ||
| :param custom: | ||
| (Optional) A map of custom contexts. | ||
| """ | ||
|
|
||
| def __init__(self, blob, custom=None): | ||
| super().__init__({"custom": custom}) | ||
| self._blob = blob | ||
|
|
||
| @classmethod | ||
| def from_api_repr(cls, resource, blob): | ||
| """Factory: construct instance from resource. | ||
|
|
||
| :type blob: :class:`Blob` | ||
| :param blob: Blob for which these contexts apply to. | ||
|
|
||
| :type resource: dict | ||
| :param resource: mapping as returned from API call. | ||
|
|
||
| :rtype: :class:`ObjectContexts` | ||
| :returns: ObjectContexts created from resource. | ||
| """ | ||
| instance = cls(blob) | ||
| if resource: | ||
| instance.update(resource) | ||
| return instance | ||
|
|
||
| @property | ||
| def blob(self): | ||
| """Blob for which these contexts apply to. | ||
|
|
||
| :rtype: :class:`Blob` | ||
| :returns: the instance's blob. | ||
| """ | ||
| return self._blob | ||
|
|
||
| def set_custom_context(self, key, value): | ||
| """Set a custom context. | ||
|
|
||
| :type key: str | ||
| :param key: The key of the custom context. | ||
|
|
||
| :type value: str | ||
| :param value: The value of the custom context. | ||
| """ | ||
| custom = self.get("custom") | ||
| if custom is None: | ||
| custom = {} | ||
| self["custom"] = custom | ||
| custom[key] = {"value": value} | ||
| self.blob._patch_property("contexts", self) | ||
|
Contributor
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. Triggering an immediate network request via Consider removing the immediate patch call from
Contributor
Author
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. @jules make these changes
Contributor
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. I have updated |
||
|
|
||
| def delete_custom_context(self, key): | ||
| """Delete a custom context. | ||
|
|
||
| :type key: str | ||
| :param key: The key of the custom context to delete. | ||
| """ | ||
| custom = self.get("custom") | ||
| if custom is not None: | ||
| custom[key] = None | ||
| self.blob._patch_property("contexts", self) | ||
|
|
||
| def clear_custom_contexts(self): | ||
| """Clear all custom contexts.""" | ||
| self["custom"] = None | ||
| self.blob._patch_property("contexts", self) | ||
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.
The
contextsproperty currently creates a newObjectContextsinstance on every access. BecauseObjectContextsis a mutabledictsubclass that triggers immediate server-side patches, this implementation is prone to data loss. For example, if two references are held (e.g.,c1 = blob.contexts; c2 = blob.contexts), modifications made viac1will be overwritten whenc2is used, asc2sends its own (stale) state to the server during its own patch operation.To ensure consistency and prevent data loss, the
ObjectContextsinstance should be cached on theBlobobject, following the pattern used for theaclproperty in this class.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.
@jules make these changes
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 have updated the
contextsproperty to useobjectContextsinternally, which aligns with the GCS JSON API field name. I've also implemented caching usingself._contextsand updated_set_propertiesto clear the cache when properties are reloaded.