diff --git a/.github/sync-repo-settings.yaml b/.github/sync-repo-settings.yaml index 19c1d0ba4..073e7d995 100644 --- a/.github/sync-repo-settings.yaml +++ b/.github/sync-repo-settings.yaml @@ -10,5 +10,5 @@ branchProtectionRules: - 'Kokoro' - 'cla/google' - 'Kokoro system-3.14' - - 'Kokoro system-3.9' + - 'Kokoro system-3.10' - 'OwlBot Post Processor' diff --git a/.kokoro/presubmit/system-3.9.cfg b/.kokoro/presubmit/system-3.10.cfg similarity index 91% rename from .kokoro/presubmit/system-3.9.cfg rename to .kokoro/presubmit/system-3.10.cfg index d21467d02..26958ac2a 100644 --- a/.kokoro/presubmit/system-3.9.cfg +++ b/.kokoro/presubmit/system-3.10.cfg @@ -3,7 +3,7 @@ # Only run this nox session. env_vars: { key: "NOX_SESSION" - value: "system-3.9" + value: "system-3.10" } # Credentials needed to test universe domain. diff --git a/.librarian/generator-input/noxfile.py b/.librarian/generator-input/noxfile.py index ca527decd..c9ada0739 100644 --- a/.librarian/generator-input/noxfile.py +++ b/.librarian/generator-input/noxfile.py @@ -27,8 +27,8 @@ BLACK_PATHS = ["docs", "google", "tests", "noxfile.py", "setup.py"] DEFAULT_PYTHON_VERSION = "3.14" -SYSTEM_TEST_PYTHON_VERSIONS = ["3.9", "3.14"] -UNIT_TEST_PYTHON_VERSIONS = ["3.7", "3.8", "3.9", "3.10", "3.11", "3.12", "3.13", "3.14"] +SYSTEM_TEST_PYTHON_VERSIONS = ["3.10", "3.14"] +UNIT_TEST_PYTHON_VERSIONS = ["3.10", "3.11", "3.12", "3.13", "3.14"] CONFORMANCE_TEST_PYTHON_VERSIONS = ["3.12"] CURRENT_DIRECTORY = pathlib.Path(__file__).parent.absolute() @@ -44,9 +44,6 @@ "lint", "lint_setup_py", "system", - # TODO(https://github.com/googleapis/python-storage/issues/1499): - # Remove or restore testing for Python 3.7/3.8 - "unit-3.9", "unit-3.10", "unit-3.11", "unit-3.12", diff --git a/.librarian/generator-input/setup.py b/.librarian/generator-input/setup.py index 89971aa33..294e63892 100644 --- a/.librarian/generator-input/setup.py +++ b/.librarian/generator-input/setup.py @@ -87,9 +87,6 @@ "License :: OSI Approved :: Apache Software License", "Programming Language :: Python", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.7", - "Programming Language :: Python :: 3.8", - "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", diff --git a/google/cloud/storage/abstracts/base_blob.py b/google/cloud/storage/abstracts/base_blob.py new file mode 100644 index 000000000..b2a2bc07c --- /dev/null +++ b/google/cloud/storage/abstracts/base_blob.py @@ -0,0 +1,243 @@ +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""The abstract for python-storage Blob.""" + +import abc + + +class BaseBlob(abc.ABC): + """The abstract for python-storage Blob""" + + @property + @abc.abstractmethod + def encryption_key(self): + """Retrieve the customer-supplied encryption key for the object.""" + raise NotImplementedError("Not Yet Implemented") + + @encryption_key.setter + @abc.abstractmethod + def encryption_key(self, value): + """Set the blob's encryption key.""" + raise NotImplementedError("Not Yet Implemented") + + @property + @abc.abstractmethod + def chunk_size(self): + """Get the blob's default chunk size.""" + raise NotImplementedError("Not Yet Implemented") + + @chunk_size.setter + @abc.abstractmethod + def chunk_size(self, value): + """Set the blob's default chunk size.""" + raise NotImplementedError("Not Yet Implemented") + + @property + @abc.abstractmethod + def metadata(self): + """Retrieve arbitrary/application specific metadata for the object.""" + raise NotImplementedError("Not Yet Implemented") + + @metadata.setter + @abc.abstractmethod + def metadata(self, value): + """Update arbitrary/application specific metadata for the object.""" + raise NotImplementedError("Not Yet Implemented") + + @property + @abc.abstractmethod + def kms_key_name(self): + """Resource name of Cloud KMS key used to encrypt the blob's contents.""" + raise NotImplementedError("Not Yet Implemented") + + @kms_key_name.setter + @abc.abstractmethod + def kms_key_name(self, value): + """Set KMS encryption key for object.""" + raise NotImplementedError("Not Yet Implemented") + + @property + @abc.abstractmethod + def custom_time(self): + """Retrieve the custom time for the object.""" + raise NotImplementedError("Not Yet Implemented") + + @custom_time.setter + @abc.abstractmethod + def custom_time(self, value): + """Set the custom time for the object.""" + raise NotImplementedError("Not Yet Implemented") + + @property + @abc.abstractmethod + def bucket(self): + """Bucket which contains the object.""" + raise NotImplementedError("Not Yet Implemented") + + @property + @abc.abstractmethod + def acl(self): + """Create our ACL on demand.""" + raise NotImplementedError("Not Yet Implemented") + + @property + @abc.abstractmethod + def path(self): + """Getter property for the URL path to this Blob.""" + raise NotImplementedError("Not Yet Implemented") + + @property + @abc.abstractmethod + def client(self): + """The client bound to this blob.""" + raise NotImplementedError("Not Yet Implemented") + + @property + @abc.abstractmethod + def user_project(self): + """Project ID billed for API requests made via this blob.""" + raise NotImplementedError("Not Yet Implemented") + + @property + @abc.abstractmethod + def public_url(self): + """The public URL for this blob.""" + raise NotImplementedError("Not Yet Implemented") + + @property + @abc.abstractmethod + def component_count(self): + """Number of underlying components that make up this object.""" + raise NotImplementedError("Not Yet Implemented") + + @property + @abc.abstractmethod + def etag(self): + """Retrieve the ETag for the object.""" + raise NotImplementedError("Not Yet Implemented") + + @property + @abc.abstractmethod + def generation(self): + """Retrieve the generation for the object.""" + raise NotImplementedError("Not Yet Implemented") + + @property + @abc.abstractmethod + def id(self): + """Retrieve the ID for the object.""" + raise NotImplementedError("Not Yet Implemented") + + @property + @abc.abstractmethod + def media_link(self): + """Retrieve the media download URI for the object.""" + raise NotImplementedError("Not Yet Implemented") + + @property + @abc.abstractmethod + def metageneration(self): + """Retrieve the metageneration for the object.""" + raise NotImplementedError("Not Yet Implemented") + + @property + @abc.abstractmethod + def owner(self): + """Retrieve info about the owner of the object.""" + raise NotImplementedError("Not Yet Implemented") + + @property + @abc.abstractmethod + def retention_expiration_time(self): + """Retrieve timestamp at which the object's retention period expires.""" + raise NotImplementedError("Not Yet Implemented") + + @property + @abc.abstractmethod + def self_link(self): + """Retrieve the URI for the object.""" + raise NotImplementedError("Not Yet Implemented") + + @property + @abc.abstractmethod + def size(self): + """Size of the object, in bytes.""" + raise NotImplementedError("Not Yet Implemented") + + @property + @abc.abstractmethod + def time_deleted(self): + """Retrieve the timestamp at which the object was deleted.""" + raise NotImplementedError("Not Yet Implemented") + + @property + @abc.abstractmethod + def time_created(self): + """Retrieve the timestamp at which the object was created.""" + raise NotImplementedError("Not Yet Implemented") + + @property + @abc.abstractmethod + def updated(self): + """Retrieve the timestamp at which the object was updated.""" + raise NotImplementedError("Not Yet Implemented") + + @property + @abc.abstractmethod + def retention(self): + """Retrieve the retention configuration for this object.""" + raise NotImplementedError("Not Yet Implemented") + + @property + @abc.abstractmethod + def soft_delete_time(self): + """If this object has been soft-deleted, returns the time at which it became soft-deleted.""" + raise NotImplementedError("Not Yet Implemented") + + @property + @abc.abstractmethod + def hard_delete_time(self): + """If this object has been soft-deleted, returns the time at which it will be permanently deleted.""" + raise NotImplementedError("Not Yet Implemented") + + @abc.abstractmethod + def reload( + self, + client=None, + projection="noAcl", + if_etag_match=None, + if_etag_not_match=None, + if_generation_match=None, + if_generation_not_match=None, + if_metageneration_match=None, + if_metageneration_not_match=None, + timeout=None, + retry=None, + soft_deleted=None, + ): + raise NotImplementedError("Not Yet Implemented.") + + @abc.abstractmethod + def open( + self, + mode="r", + chunk_size=None, + ignore_flush=None, + encoding=None, + errors=None, + newline=None, + **kwargs, + ): + """Create a file handler for file-like I/O to or from this blob.""" + raise NotImplementedError("Not Yet Implemented") diff --git a/google/cloud/storage/abstracts/base_bucket.py b/google/cloud/storage/abstracts/base_bucket.py new file mode 100644 index 000000000..3910feb1d --- /dev/null +++ b/google/cloud/storage/abstracts/base_bucket.py @@ -0,0 +1,391 @@ +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""The abstract for python-storage Bucket.""" + +import abc + + +class BaseBucket(abc.ABC): + """The abstract for python-storage Bucket""" + + @property + @abc.abstractmethod + def rpo(self): + """Get the RPO (Recovery Point Objective) of this bucket""" + raise NotImplementedError("Not yet Implemented") + + @property + @abc.abstractmethod + def retention_period(self): + """Retrieve or set the retention period for items in the bucket.""" + raise NotImplementedError("Not yet Implemented") + + @retention_period.setter + @abc.abstractmethod + def retention_period(self, value): + """Set the retention period for items in the bucket.""" + raise NotImplementedError("Not yet Implemented") + + @property + @abc.abstractmethod + def storage_class(self): + """Retrieve or set the storage class for the bucket.""" + raise NotImplementedError("Not yet Implemented") + + @storage_class.setter + @abc.abstractmethod + def storage_class(self, value): + """Set the storage class for the bucket.""" + raise NotImplementedError("Not yet Implemented") + + @property + @abc.abstractmethod + def versioning_enabled(self): + """Is versioning enabled for this bucket?""" + raise NotImplementedError("Not yet Implemented") + + @versioning_enabled.setter + @abc.abstractmethod + def versioning_enabled(self, value): + """Enable versioning for this bucket.""" + raise NotImplementedError("Not yet Implemented") + + @property + @abc.abstractmethod + def requester_pays(self): + """Does the requester pay for API requests for this bucket?""" + raise NotImplementedError("Not yet Implemented") + + @requester_pays.setter + @abc.abstractmethod + def requester_pays(self, value): + """Update whether requester pays for API requests for this bucket.""" + raise NotImplementedError("Not yet Implemented") + + @property + @abc.abstractmethod + def autoclass_enabled(self): + """Whether Autoclass is enabled for this bucket.""" + raise NotImplementedError("Not yet Implemented") + + @autoclass_enabled.setter + @abc.abstractmethod + def autoclass_enabled(self, value): + """Enable or disable Autoclass at the bucket-level.""" + raise NotImplementedError("Not yet Implemented") + + @property + @abc.abstractmethod + def autoclass_terminal_storage_class(self): + """The storage class that objects in an Autoclass bucket eventually transition to if + they are not read for a certain length of time. Valid values are NEARLINE and ARCHIVE. + """ + raise NotImplementedError("Not yet Implemented") + + @autoclass_terminal_storage_class.setter + @abc.abstractmethod + def autoclass_terminal_storage_class(self, value): + """The storage class that objects in an Autoclass bucket eventually transition to if + they are not read for a certain length of time. Valid values are NEARLINE and ARCHIVE. + """ + raise NotImplementedError("Not yet Implemented") + + @property + @abc.abstractmethod + def hierarchical_namespace_enabled(self): + """Whether hierarchical namespace is enabled for this bucket.""" + raise NotImplementedError("Not yet Implemented") + + @hierarchical_namespace_enabled.setter + @abc.abstractmethod + def hierarchical_namespace_enabled(self, value): + """Enable or disable hierarchical namespace at the bucket-level.""" + raise NotImplementedError("Not yet Implemented") + + @property + @abc.abstractmethod + def cors(self): + """Retrieve or set CORS policies configured for this bucket.""" + raise NotImplementedError("Not yet Implemented") + + @cors.setter + @abc.abstractmethod + def cors(self, entries): + """Set CORS policies configured for this bucket.""" + raise NotImplementedError("Not yet Implemented") + + @property + @abc.abstractmethod + def default_kms_key_name(self): + """Retrieve / set default KMS encryption key for objects in the bucket.""" + raise NotImplementedError("Not yet Implemented") + + @default_kms_key_name.setter + @abc.abstractmethod + def default_kms_key_name(self, value): + """Set default KMS encryption key for objects in the bucket.""" + raise NotImplementedError("Not yet Implemented") + + @property + @abc.abstractmethod + def labels(self): + """Retrieve or set labels assigned to this bucket.""" + raise NotImplementedError("Not yet Implemented") + + @labels.setter + @abc.abstractmethod + def labels(self, mapping): + """Set labels assigned to this bucket.""" + raise NotImplementedError("Not yet Implemented") + + @property + @abc.abstractmethod + def ip_filter(self): + """Retrieve or set the IP Filter configuration for this bucket.""" + raise NotImplementedError("Not yet Implemented") + + @ip_filter.setter + @abc.abstractmethod + def ip_filter(self, value): + """Retrieve or set the IP Filter configuration for this bucket.""" + raise NotImplementedError("Not yet Implemented") + + @property + @abc.abstractmethod + def lifecycle_rules(self): + """Retrieve or set lifecycle rules configured for this bucket.""" + raise NotImplementedError("Not yet Implemented") + + @lifecycle_rules.setter + @abc.abstractmethod + def lifecycle_rules(self, rules): + """Set lifecycle rules configured for this bucket.""" + raise NotImplementedError("Not yet Implemented") + + @property + @abc.abstractmethod + def location(self): + """Retrieve location configured for this bucket.""" + raise NotImplementedError("Not yet Implemented") + + @location.setter + @abc.abstractmethod + def location(self, value): + """(Deprecated) Set `Bucket.location`""" + raise NotImplementedError("Not yet Implemented") + + @property + @abc.abstractmethod + def generation(self): + """Retrieve the generation for the bucket.""" + raise NotImplementedError("Not yet Implemented") + + @property + @abc.abstractmethod + def soft_delete_time(self): + """If this bucket has been soft-deleted, returns the time at which it became soft-deleted.""" + raise NotImplementedError("Not yet Implemented") + + @property + @abc.abstractmethod + def hard_delete_time(self): + """If this bucket has been soft-deleted, returns the time at which it will be permanently deleted.""" + raise NotImplementedError("Not yet Implemented") + + @property + @abc.abstractmethod + def autoclass_terminal_storage_class_update_time(self): + """The time at which the Autoclass terminal_storage_class field was last updated for this bucket""" + raise NotImplementedError("Not yet Implemented") + + @property + @abc.abstractmethod + def object_retention_mode(self): + """Retrieve the object retention mode set on the bucket.""" + raise NotImplementedError("Not yet Implemented") + + @property + @abc.abstractmethod + def user_project(self): + """Project ID to be billed for API requests made via this bucket.""" + raise NotImplementedError("Not yet Implemented") + + @property + @abc.abstractmethod + def autoclass_toggle_time(self): + """Retrieve the toggle time when Autoclaass was last enabled or disabled for the bucket.""" + raise NotImplementedError("Not yet Implemented") + + @property + @abc.abstractmethod + def time_created(self): + """Retrieve the timestamp at which the bucket was created.""" + raise NotImplementedError("Not yet Implemented") + + @property + @abc.abstractmethod + def updated(self): + """Retrieve the timestamp at which the bucket was last updated.""" + raise NotImplementedError("Not yet Implemented") + + @property + @abc.abstractmethod + def acl(self): + """Create our ACL on demand.""" + raise NotImplementedError("Not yet Implemented") + + @property + @abc.abstractmethod + def default_object_acl(self): + """Create our defaultObjectACL on demand.""" + raise NotImplementedError("Not yet Implemented") + + @property + @abc.abstractmethod + def etag(self): + """Retrieve the ETag for the bucket.""" + raise NotImplementedError("Not yet Implemented") + + @property + @abc.abstractmethod + def id(self): + """Retrieve the ID for the bucket.""" + raise NotImplementedError("Not yet Implemented") + + @property + @abc.abstractmethod + def iam_configuration(self): + """Retrieve IAM configuration for this bucket.""" + raise NotImplementedError("Not yet Implemented") + + @property + @abc.abstractmethod + def soft_delete_policy(self): + """Retrieve the soft delete policy for this bucket.""" + raise NotImplementedError("Not yet Implemented") + + @property + @abc.abstractmethod + def data_locations(self): + """Retrieve the list of regional locations for custom dual-region buckets.""" + raise NotImplementedError("Not yet Implemented") + + @property + @abc.abstractmethod + def location_type(self): + """Retrieve the location type for the bucket.""" + raise NotImplementedError("Not yet Implemented") + + @property + @abc.abstractmethod + def path(self): + """The URL path to this bucket.""" + raise NotImplementedError("Not yet Implemented") + + @property + @abc.abstractmethod + def metageneration(self): + """Retrieve the metageneration for the bucket.""" + raise NotImplementedError("Not yet Implemented") + + @property + @abc.abstractmethod + def owner(self): + """Retrieve info about the owner of the bucket.""" + raise NotImplementedError("Not yet Implemented") + + @property + @abc.abstractmethod + def project_number(self): + """Retrieve the number of the project to which the bucket is assigned.""" + raise NotImplementedError("Not yet Implemented") + + @property + @abc.abstractmethod + def retention_policy_effective_time(self): + """Retrieve the effective time of the bucket's retention policy.""" + raise NotImplementedError("Not yet Implemented") + + @property + @abc.abstractmethod + def retention_policy_locked(self): + """Retrieve whthere the bucket's retention policy is locked.""" + raise NotImplementedError("Not yet Implemented") + + @property + @abc.abstractmethod + def self_link(self): + """Retrieve the URI for the bucket.""" + raise NotImplementedError("Not yet Implemented") + + @abc.abstractmethod + def reload( + self, + client=None, + projection="noAcl", + timeout=None, + if_etag_match=None, + if_etag_not_match=None, + if_metageneration_match=None, + if_metageneration_not_match=None, + retry=None, + soft_deleted=None, + ): + """Load the bucket metadata into bucket instance.""" + raise NotImplementedError("Not Implemented Yet") + + @abc.abstractmethod + def patch( + self, + client=None, + if_metageneration_match=None, + if_metageneration_not_match=None, + timeout=None, + retry=None, + ): + """Patch the bucket metadata into bucket instance.""" + raise NotImplementedError("Not Implemented Yet") + + @abc.abstractmethod + def blob( + self, + blob_name, + chunk_size=None, + encryption_key=None, + kms_key_name=None, + generation=None, + ): + """Factory constructor for blob object.""" + raise NotImplementedError("Not Implemented Yet") + + @abc.abstractmethod + def get_blob( + self, + blob_name, + client=None, + encryption_key=None, + generation=None, + if_etag_match=None, + if_etag_not_match=None, + if_generation_match=None, + if_generation_not_match=None, + if_metageneration_match=None, + if_metageneration_not_match=None, + timeout=None, + retry=None, + soft_deleted=None, + **kwargs, + ): + """Get a blob object by name.""" + raise NotImplementedError("Not Implemented Yet") diff --git a/google/cloud/storage/blob.py b/google/cloud/storage/blob.py index 746334d1c..ba168c298 100644 --- a/google/cloud/storage/blob.py +++ b/google/cloud/storage/blob.py @@ -80,6 +80,7 @@ from google.cloud.storage.retry import DEFAULT_RETRY_IF_GENERATION_SPECIFIED from google.cloud.storage.fileio import BlobReader from google.cloud.storage.fileio import BlobWriter +from google.cloud.storage.abstracts.base_blob import BaseBlob _DEFAULT_CONTENT_TYPE = "application/octet-stream" @@ -150,7 +151,7 @@ _logger = logging.getLogger(__name__) -class Blob(_PropertyMixin): +class Blob(_PropertyMixin, BaseBlob): """A wrapper around Cloud Storage's concept of an ``Object``. :type name: str diff --git a/google/cloud/storage/bucket.py b/google/cloud/storage/bucket.py index 1621f879e..73fd95f90 100644 --- a/google/cloud/storage/bucket.py +++ b/google/cloud/storage/bucket.py @@ -64,6 +64,7 @@ from google.cloud.storage.retry import DEFAULT_RETRY_IF_GENERATION_SPECIFIED from google.cloud.storage.retry import DEFAULT_RETRY_IF_ETAG_IN_JSON from google.cloud.storage.retry import DEFAULT_RETRY_IF_METAGENERATION_SPECIFIED +from google.cloud.storage.abstracts.base_bucket import BaseBucket _UBLA_BPO_ENABLED_MESSAGE = ( @@ -621,7 +622,7 @@ def bucket_policy_only_locked_time(self): return self.uniform_bucket_level_access_locked_time -class Bucket(_PropertyMixin): +class Bucket(_PropertyMixin, BaseBucket): """A class representing a Bucket on Cloud Storage. :type client: :class:`google.cloud.storage.client.Client` diff --git a/noxfile.py b/noxfile.py index 14dfb29d0..4c2b70193 100644 --- a/noxfile.py +++ b/noxfile.py @@ -17,7 +17,6 @@ from __future__ import absolute_import import os import pathlib -import re import shutil import nox @@ -27,9 +26,8 @@ BLACK_PATHS = ["docs", "google", "tests", "noxfile.py", "setup.py"] DEFAULT_PYTHON_VERSION = "3.14" -SYSTEM_TEST_PYTHON_VERSIONS = ["3.9", "3.14"] +SYSTEM_TEST_PYTHON_VERSIONS = ["3.10", "3.14"] UNIT_TEST_PYTHON_VERSIONS = [ - "3.9", "3.10", "3.11", "3.12", @@ -51,9 +49,6 @@ "lint", "lint_setup_py", "system", - # TODO(https://github.com/googleapis/python-storage/issues/1499): - # Remove or restore testing for Python 3.7/3.8 - "unit-3.9", "unit-3.10", "unit-3.11", "unit-3.12", diff --git a/setup.py b/setup.py index b45053856..d3215cff6 100644 --- a/setup.py +++ b/setup.py @@ -99,9 +99,6 @@ "License :: OSI Approved :: Apache Software License", "Programming Language :: Python", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.7", - "Programming Language :: Python :: 3.8", - "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", @@ -114,7 +111,7 @@ packages=packages, install_requires=dependencies, extras_require=extras, - python_requires=">=3.7", + python_requires=">=3.10", include_package_data=True, zip_safe=False, ) diff --git a/tests/unit/abstracts/__init__.py b/tests/unit/abstracts/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/unit/abstracts/test_base_blob.py b/tests/unit/abstracts/test_base_blob.py new file mode 100644 index 000000000..281c2de55 --- /dev/null +++ b/tests/unit/abstracts/test_base_blob.py @@ -0,0 +1,69 @@ +import pytest +from unittest import mock + +from google.cloud.storage.abstracts.base_blob import BaseBlob + + +@pytest.fixture +def base_blob(): + # Temporarily remove abstract methods restriction to allow direct instantiation + with mock.patch.object(BaseBlob, "__abstractmethods__", set()): + yield BaseBlob() + + +# Properties that have both getters and setters +READ_WRITE_PROPS = [ + "encryption_key", + "chunk_size", + "metadata", + "kms_key_name", + "custom_time", +] + +# Properties that only have getters +READ_ONLY_PROPS = [ + "bucket", + "acl", + "path", + "client", + "user_project", + "public_url", + "component_count", + "etag", + "generation", + "id", + "media_link", + "metageneration", + "owner", + "retention_expiration_time", + "self_link", + "size", + "time_deleted", + "time_created", + "updated", + "retention", + "soft_delete_time", + "hard_delete_time", +] + + +@pytest.mark.parametrize("prop", READ_WRITE_PROPS + READ_ONLY_PROPS) +def test_property_getters(base_blob, prop): + with pytest.raises(NotImplementedError, match="Not Yet Implemented"): + getattr(base_blob, prop) + + +@pytest.mark.parametrize("prop", READ_WRITE_PROPS) +def test_property_setters(base_blob, prop): + with pytest.raises(NotImplementedError, match="Not Yet Implemented"): + setattr(base_blob, prop, "dummy_value") + + +def test_reload(base_blob): + with pytest.raises(NotImplementedError, match="Not Yet Implemented"): + base_blob.reload() + + +def test_open(base_blob): + with pytest.raises(NotImplementedError, match="Not Yet Implemented"): + base_blob.open() diff --git a/tests/unit/abstracts/test_base_bucket.py b/tests/unit/abstracts/test_base_bucket.py new file mode 100644 index 000000000..2383d9979 --- /dev/null +++ b/tests/unit/abstracts/test_base_bucket.py @@ -0,0 +1,90 @@ +import pytest +from unittest import mock + +from google.cloud.storage.abstracts.base_bucket import BaseBucket + + +@pytest.fixture +def base_bucket(): + # Temporarily remove abstract methods restriction to allow direct instantiation + with mock.patch.object(BaseBucket, "__abstractmethods__", set()): + yield BaseBucket() + + +# Properties that have both getters and setters +READ_WRITE_PROPS = [ + "retention_period", + "storage_class", + "versioning_enabled", + "requester_pays", + "autoclass_enabled", + "autoclass_terminal_storage_class", + "hierarchical_namespace_enabled", + "cors", + "default_kms_key_name", + "labels", + "ip_filter", + "lifecycle_rules", + "location", +] + +# Properties that only have getters +READ_ONLY_PROPS = [ + "rpo", + "generation", + "soft_delete_time", + "hard_delete_time", + "autoclass_terminal_storage_class_update_time", + "object_retention_mode", + "user_project", + "autoclass_toggle_time", + "time_created", + "updated", + "acl", + "default_object_acl", + "etag", + "id", + "iam_configuration", + "soft_delete_policy", + "data_locations", + "location_type", + "path", + "metageneration", + "owner", + "project_number", + "retention_policy_effective_time", + "retention_policy_locked", + "self_link", +] + + +@pytest.mark.parametrize("prop", READ_WRITE_PROPS + READ_ONLY_PROPS) +def test_property_getters(base_bucket, prop): + with pytest.raises(NotImplementedError): + getattr(base_bucket, prop) + + +@pytest.mark.parametrize("prop", READ_WRITE_PROPS) +def test_property_setters(base_bucket, prop): + with pytest.raises(NotImplementedError): + setattr(base_bucket, prop, "dummy_value") + + +def test_reload(base_bucket): + with pytest.raises(NotImplementedError): + base_bucket.reload() + + +def test_patch(base_bucket): + with pytest.raises(NotImplementedError): + base_bucket.patch() + + +def test_blob(base_bucket): + with pytest.raises(NotImplementedError): + base_bucket.blob("dummy_blob_name") + + +def test_get_blob(base_bucket): + with pytest.raises(NotImplementedError): + base_bucket.get_blob("dummy_blob_name")