Skip to content

Commit

Permalink
refactor(storage): move 'create_bucket' implementation from 'Bucket' …
Browse files Browse the repository at this point in the history
…to 'Client' (#8604)

towards #7762
  • Loading branch information
Gurov Ilya authored Nov 8, 2019
1 parent 5137c03 commit 332c8ff
Show file tree
Hide file tree
Showing 3 changed files with 255 additions and 43 deletions.
66 changes: 63 additions & 3 deletions storage/google/cloud/storage/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@
from google.cloud.storage.bucket import Bucket
from google.cloud.storage.blob import Blob
from google.cloud.storage.hmac_key import HMACKeyMetadata
from google.cloud.storage.acl import BucketACL
from google.cloud.storage.acl import DefaultObjectACL


_marker = object()
Expand Down Expand Up @@ -324,7 +326,16 @@ def lookup_bucket(self, bucket_name):
except NotFound:
return None

def create_bucket(self, bucket_or_name, requester_pays=None, project=None):
def create_bucket(
self,
bucket_or_name,
requester_pays=None,
project=None,
user_project=None,
location=None,
predefined_acl=None,
predefined_default_object_acl=None,
):
"""API call: create a new bucket via a POST request.
See
Expand All @@ -340,8 +351,21 @@ def create_bucket(self, bucket_or_name, requester_pays=None, project=None):
Optional. Whether requester pays for API requests for this
bucket and its blobs.
project (str):
Optional. the project under which the bucket is to be created.
Optional. The project under which the bucket is to be created.
If not passed, uses the project set on the client.
user_project (str):
Optional. The project ID to be billed for API requests
made via created bucket.
location (str):
Optional. The location of the bucket. If not passed,
the default location, US, will be used. See
https://cloud.google.com/storage/docs/bucket-locations
predefined_acl (str):
Optional. Name of predefined ACL to apply to bucket. See:
https://cloud.google.com/storage/docs/access-control/lists#predefined-acl
predefined_default_object_acl (str):
Optional. Name of predefined ACL to apply to bucket's objects. See:
https://cloud.google.com/storage/docs/access-control/lists#predefined-acl
Returns:
google.cloud.storage.bucket.Bucket
Expand Down Expand Up @@ -374,9 +398,45 @@ def create_bucket(self, bucket_or_name, requester_pays=None, project=None):
"""
bucket = self._bucket_arg_to_bucket(bucket_or_name)

if project is None:
project = self.project

if project is None:
raise ValueError("Client project not set: pass an explicit project.")

if requester_pays is not None:
bucket.requester_pays = requester_pays
bucket.create(client=self, project=project)

query_params = {"project": project}

if predefined_acl is not None:
predefined_acl = BucketACL.validate_predefined(predefined_acl)
query_params["predefinedAcl"] = predefined_acl

if predefined_default_object_acl is not None:
predefined_default_object_acl = DefaultObjectACL.validate_predefined(
predefined_default_object_acl
)
query_params["predefinedDefaultObjectAcl"] = predefined_default_object_acl

if user_project is not None:
query_params["userProject"] = user_project

properties = {key: bucket._properties[key] for key in bucket._changes}
properties["name"] = bucket.name

if location is not None:
properties["location"] = location

api_response = self._connection.api_request(
method="POST",
path="/b",
query_params=query_params,
data=properties,
_target_object=bucket,
)

bucket._set_properties(api_response)
return bucket

def download_blob_to_file(self, blob_or_uri, file_obj, start=None, end=None):
Expand Down
129 changes: 91 additions & 38 deletions storage/tests/unit/test_bucket.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,15 @@
import pytest


def _make_connection(*responses):
import google.cloud.storage._http

mock_connection = mock.create_autospec(google.cloud.storage._http.Connection)
mock_connection.user_agent = "testing 1.2.3"
mock_connection.api_request.side_effect = list(responses)
return mock_connection


def _create_signing_credentials():
import google.auth.credentials

Expand Down Expand Up @@ -599,77 +608,104 @@ def api_request(cls, *args, **kwargs):
self.assertEqual(_FakeConnection._called_with, expected_cw)

def test_create_w_user_project(self):
from google.cloud.storage.client import Client

PROJECT = "PROJECT"
BUCKET_NAME = "bucket-name"
USER_PROJECT = "user-project-123"
connection = _Connection()
client = _Client(connection, project=PROJECT)

client = Client(project=PROJECT)
client._base_connection = _Connection()

bucket = self._make_one(client, BUCKET_NAME, user_project=USER_PROJECT)

with self.assertRaises(ValueError):
bucket.create()

def test_create_w_missing_client_project(self):
from google.cloud.storage.client import Client

BUCKET_NAME = "bucket-name"
connection = _Connection()
client = _Client(connection, project=None)

client = Client(project=None)
bucket = self._make_one(client, BUCKET_NAME)

with self.assertRaises(ValueError):
bucket.create()

def test_create_w_explicit_project(self):
from google.cloud.storage.client import Client

PROJECT = "PROJECT"
BUCKET_NAME = "bucket-name"
OTHER_PROJECT = "other-project-123"
DATA = {"name": BUCKET_NAME}
connection = _Connection(DATA)
client = _Client(connection, project=PROJECT)
bucket = self._make_one(client, BUCKET_NAME)
connection = _make_connection(DATA)

bucket.create(project=OTHER_PROJECT)
client = Client(project=PROJECT)
client._base_connection = connection

kw, = connection._requested
self.assertEqual(kw["method"], "POST")
self.assertEqual(kw["path"], "/b")
self.assertEqual(kw["query_params"], {"project": OTHER_PROJECT})
self.assertEqual(kw["data"], DATA)
bucket = self._make_one(client, BUCKET_NAME)
bucket.create(project=OTHER_PROJECT)
connection.api_request.assert_called_once_with(
method="POST",
path="/b",
query_params={"project": OTHER_PROJECT},
data=DATA,
_target_object=bucket,
)

def test_create_w_explicit_location(self):
from google.cloud.storage.client import Client

PROJECT = "PROJECT"
BUCKET_NAME = "bucket-name"
LOCATION = "us-central1"
DATA = {"location": LOCATION, "name": BUCKET_NAME}
connection = _Connection(

connection = _make_connection(
DATA, "{'location': 'us-central1', 'name': 'bucket-name'}"
)
client = _Client(connection, project=PROJECT)
bucket = self._make_one(client, BUCKET_NAME)

client = Client(project=PROJECT)
client._base_connection = connection

bucket = self._make_one(client, BUCKET_NAME)
bucket.create(location=LOCATION)

kw, = connection._requested
self.assertEqual(kw["method"], "POST")
self.assertEqual(kw["path"], "/b")
self.assertEqual(kw["data"], DATA)
connection.api_request.assert_called_once_with(
method="POST",
path="/b",
data=DATA,
_target_object=bucket,
query_params={"project": "PROJECT"},
)
self.assertEqual(bucket.location, LOCATION)

def test_create_hit(self):
from google.cloud.storage.client import Client

PROJECT = "PROJECT"
BUCKET_NAME = "bucket-name"
DATA = {"name": BUCKET_NAME}
connection = _Connection(DATA)
client = _Client(connection, project=PROJECT)
connection = _make_connection(DATA)
client = Client(project=PROJECT)
client._base_connection = connection

bucket = self._make_one(client=client, name=BUCKET_NAME)
bucket.create()

kw, = connection._requested
self.assertEqual(kw["method"], "POST")
self.assertEqual(kw["path"], "/b")
self.assertEqual(kw["query_params"], {"project": PROJECT})
self.assertEqual(kw["data"], DATA)
connection.api_request.assert_called_once_with(
method="POST",
path="/b",
query_params={"project": PROJECT},
data=DATA,
_target_object=bucket,
)

def test_create_w_extra_properties(self):
from google.cloud.storage.client import Client

BUCKET_NAME = "bucket-name"
PROJECT = "PROJECT"
CORS = [
Expand All @@ -694,8 +730,11 @@ def test_create_w_extra_properties(self):
"billing": {"requesterPays": True},
"labels": LABELS,
}
connection = _Connection(DATA)
client = _Client(connection, project=PROJECT)

connection = _make_connection(DATA)
client = Client(project=PROJECT)
client._base_connection = connection

bucket = self._make_one(client=client, name=BUCKET_NAME)
bucket.cors = CORS
bucket.lifecycle_rules = LIFECYCLE_RULES
Expand All @@ -705,29 +744,37 @@ def test_create_w_extra_properties(self):
bucket.labels = LABELS
bucket.create(location=LOCATION)

kw, = connection._requested
self.assertEqual(kw["method"], "POST")
self.assertEqual(kw["path"], "/b")
self.assertEqual(kw["query_params"], {"project": PROJECT})
self.assertEqual(kw["data"], DATA)
connection.api_request.assert_called_once_with(
method="POST",
path="/b",
query_params={"project": PROJECT},
data=DATA,
_target_object=bucket,
)

def test_create_w_predefined_acl_invalid(self):
from google.cloud.storage.client import Client

PROJECT = "PROJECT"
BUCKET_NAME = "bucket-name"
DATA = {"name": BUCKET_NAME}
connection = _Connection(DATA)
client = _Client(connection, project=PROJECT)
client = Client(project=PROJECT)
client._base_connection = connection
bucket = self._make_one(client=client, name=BUCKET_NAME)

with self.assertRaises(ValueError):
bucket.create(predefined_acl="bogus")

def test_create_w_predefined_acl_valid(self):
from google.cloud.storage.client import Client

PROJECT = "PROJECT"
BUCKET_NAME = "bucket-name"
DATA = {"name": BUCKET_NAME}
connection = _Connection(DATA)
client = _Client(connection, project=PROJECT)
client = Client(project=PROJECT)
client._base_connection = connection
bucket = self._make_one(client=client, name=BUCKET_NAME)
bucket.create(predefined_acl="publicRead")

Expand All @@ -739,22 +786,28 @@ def test_create_w_predefined_acl_valid(self):
self.assertEqual(kw["data"], DATA)

def test_create_w_predefined_default_object_acl_invalid(self):
from google.cloud.storage.client import Client

PROJECT = "PROJECT"
BUCKET_NAME = "bucket-name"
DATA = {"name": BUCKET_NAME}
connection = _Connection(DATA)
client = _Client(connection, project=PROJECT)
client = Client(project=PROJECT)
client._base_connection = connection
bucket = self._make_one(client=client, name=BUCKET_NAME)

with self.assertRaises(ValueError):
bucket.create(predefined_default_object_acl="bogus")

def test_create_w_predefined_default_object_acl_valid(self):
from google.cloud.storage.client import Client

PROJECT = "PROJECT"
BUCKET_NAME = "bucket-name"
DATA = {"name": BUCKET_NAME}
connection = _Connection(DATA)
client = _Client(connection, project=PROJECT)
client = Client(project=PROJECT)
client._base_connection = connection
bucket = self._make_one(client=client, name=BUCKET_NAME)
bucket.create(predefined_default_object_acl="publicRead")

Expand Down
Loading

0 comments on commit 332c8ff

Please sign in to comment.