Skip to content

Commit 332c8ff

Browse files
author
Gurov Ilya
authored
refactor(storage): move 'create_bucket' implementation from 'Bucket' to 'Client' (#8604)
towards #7762
1 parent 5137c03 commit 332c8ff

File tree

3 files changed

+255
-43
lines changed

3 files changed

+255
-43
lines changed

storage/google/cloud/storage/client.py

Lines changed: 63 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@
2828
from google.cloud.storage.bucket import Bucket
2929
from google.cloud.storage.blob import Blob
3030
from google.cloud.storage.hmac_key import HMACKeyMetadata
31+
from google.cloud.storage.acl import BucketACL
32+
from google.cloud.storage.acl import DefaultObjectACL
3133

3234

3335
_marker = object()
@@ -324,7 +326,16 @@ def lookup_bucket(self, bucket_name):
324326
except NotFound:
325327
return None
326328

327-
def create_bucket(self, bucket_or_name, requester_pays=None, project=None):
329+
def create_bucket(
330+
self,
331+
bucket_or_name,
332+
requester_pays=None,
333+
project=None,
334+
user_project=None,
335+
location=None,
336+
predefined_acl=None,
337+
predefined_default_object_acl=None,
338+
):
328339
"""API call: create a new bucket via a POST request.
329340
330341
See
@@ -340,8 +351,21 @@ def create_bucket(self, bucket_or_name, requester_pays=None, project=None):
340351
Optional. Whether requester pays for API requests for this
341352
bucket and its blobs.
342353
project (str):
343-
Optional. the project under which the bucket is to be created.
354+
Optional. The project under which the bucket is to be created.
344355
If not passed, uses the project set on the client.
356+
user_project (str):
357+
Optional. The project ID to be billed for API requests
358+
made via created bucket.
359+
location (str):
360+
Optional. The location of the bucket. If not passed,
361+
the default location, US, will be used. See
362+
https://cloud.google.com/storage/docs/bucket-locations
363+
predefined_acl (str):
364+
Optional. Name of predefined ACL to apply to bucket. See:
365+
https://cloud.google.com/storage/docs/access-control/lists#predefined-acl
366+
predefined_default_object_acl (str):
367+
Optional. Name of predefined ACL to apply to bucket's objects. See:
368+
https://cloud.google.com/storage/docs/access-control/lists#predefined-acl
345369
346370
Returns:
347371
google.cloud.storage.bucket.Bucket
@@ -374,9 +398,45 @@ def create_bucket(self, bucket_or_name, requester_pays=None, project=None):
374398
"""
375399
bucket = self._bucket_arg_to_bucket(bucket_or_name)
376400

401+
if project is None:
402+
project = self.project
403+
404+
if project is None:
405+
raise ValueError("Client project not set: pass an explicit project.")
406+
377407
if requester_pays is not None:
378408
bucket.requester_pays = requester_pays
379-
bucket.create(client=self, project=project)
409+
410+
query_params = {"project": project}
411+
412+
if predefined_acl is not None:
413+
predefined_acl = BucketACL.validate_predefined(predefined_acl)
414+
query_params["predefinedAcl"] = predefined_acl
415+
416+
if predefined_default_object_acl is not None:
417+
predefined_default_object_acl = DefaultObjectACL.validate_predefined(
418+
predefined_default_object_acl
419+
)
420+
query_params["predefinedDefaultObjectAcl"] = predefined_default_object_acl
421+
422+
if user_project is not None:
423+
query_params["userProject"] = user_project
424+
425+
properties = {key: bucket._properties[key] for key in bucket._changes}
426+
properties["name"] = bucket.name
427+
428+
if location is not None:
429+
properties["location"] = location
430+
431+
api_response = self._connection.api_request(
432+
method="POST",
433+
path="/b",
434+
query_params=query_params,
435+
data=properties,
436+
_target_object=bucket,
437+
)
438+
439+
bucket._set_properties(api_response)
380440
return bucket
381441

382442
def download_blob_to_file(self, blob_or_uri, file_obj, start=None, end=None):

storage/tests/unit/test_bucket.py

Lines changed: 91 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,15 @@
1919
import pytest
2020

2121

22+
def _make_connection(*responses):
23+
import google.cloud.storage._http
24+
25+
mock_connection = mock.create_autospec(google.cloud.storage._http.Connection)
26+
mock_connection.user_agent = "testing 1.2.3"
27+
mock_connection.api_request.side_effect = list(responses)
28+
return mock_connection
29+
30+
2231
def _create_signing_credentials():
2332
import google.auth.credentials
2433

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

601610
def test_create_w_user_project(self):
611+
from google.cloud.storage.client import Client
612+
602613
PROJECT = "PROJECT"
603614
BUCKET_NAME = "bucket-name"
604615
USER_PROJECT = "user-project-123"
605-
connection = _Connection()
606-
client = _Client(connection, project=PROJECT)
616+
617+
client = Client(project=PROJECT)
618+
client._base_connection = _Connection()
619+
607620
bucket = self._make_one(client, BUCKET_NAME, user_project=USER_PROJECT)
608621

609622
with self.assertRaises(ValueError):
610623
bucket.create()
611624

612625
def test_create_w_missing_client_project(self):
626+
from google.cloud.storage.client import Client
627+
613628
BUCKET_NAME = "bucket-name"
614-
connection = _Connection()
615-
client = _Client(connection, project=None)
629+
630+
client = Client(project=None)
616631
bucket = self._make_one(client, BUCKET_NAME)
617632

618633
with self.assertRaises(ValueError):
619634
bucket.create()
620635

621636
def test_create_w_explicit_project(self):
637+
from google.cloud.storage.client import Client
638+
622639
PROJECT = "PROJECT"
623640
BUCKET_NAME = "bucket-name"
624641
OTHER_PROJECT = "other-project-123"
625642
DATA = {"name": BUCKET_NAME}
626-
connection = _Connection(DATA)
627-
client = _Client(connection, project=PROJECT)
628-
bucket = self._make_one(client, BUCKET_NAME)
643+
connection = _make_connection(DATA)
629644

630-
bucket.create(project=OTHER_PROJECT)
645+
client = Client(project=PROJECT)
646+
client._base_connection = connection
631647

632-
kw, = connection._requested
633-
self.assertEqual(kw["method"], "POST")
634-
self.assertEqual(kw["path"], "/b")
635-
self.assertEqual(kw["query_params"], {"project": OTHER_PROJECT})
636-
self.assertEqual(kw["data"], DATA)
648+
bucket = self._make_one(client, BUCKET_NAME)
649+
bucket.create(project=OTHER_PROJECT)
650+
connection.api_request.assert_called_once_with(
651+
method="POST",
652+
path="/b",
653+
query_params={"project": OTHER_PROJECT},
654+
data=DATA,
655+
_target_object=bucket,
656+
)
637657

638658
def test_create_w_explicit_location(self):
659+
from google.cloud.storage.client import Client
660+
639661
PROJECT = "PROJECT"
640662
BUCKET_NAME = "bucket-name"
641663
LOCATION = "us-central1"
642664
DATA = {"location": LOCATION, "name": BUCKET_NAME}
643-
connection = _Connection(
665+
666+
connection = _make_connection(
644667
DATA, "{'location': 'us-central1', 'name': 'bucket-name'}"
645668
)
646-
client = _Client(connection, project=PROJECT)
647-
bucket = self._make_one(client, BUCKET_NAME)
648669

670+
client = Client(project=PROJECT)
671+
client._base_connection = connection
672+
673+
bucket = self._make_one(client, BUCKET_NAME)
649674
bucket.create(location=LOCATION)
650675

651-
kw, = connection._requested
652-
self.assertEqual(kw["method"], "POST")
653-
self.assertEqual(kw["path"], "/b")
654-
self.assertEqual(kw["data"], DATA)
676+
connection.api_request.assert_called_once_with(
677+
method="POST",
678+
path="/b",
679+
data=DATA,
680+
_target_object=bucket,
681+
query_params={"project": "PROJECT"},
682+
)
655683
self.assertEqual(bucket.location, LOCATION)
656684

657685
def test_create_hit(self):
686+
from google.cloud.storage.client import Client
687+
658688
PROJECT = "PROJECT"
659689
BUCKET_NAME = "bucket-name"
660690
DATA = {"name": BUCKET_NAME}
661-
connection = _Connection(DATA)
662-
client = _Client(connection, project=PROJECT)
691+
connection = _make_connection(DATA)
692+
client = Client(project=PROJECT)
693+
client._base_connection = connection
694+
663695
bucket = self._make_one(client=client, name=BUCKET_NAME)
664696
bucket.create()
665697

666-
kw, = connection._requested
667-
self.assertEqual(kw["method"], "POST")
668-
self.assertEqual(kw["path"], "/b")
669-
self.assertEqual(kw["query_params"], {"project": PROJECT})
670-
self.assertEqual(kw["data"], DATA)
698+
connection.api_request.assert_called_once_with(
699+
method="POST",
700+
path="/b",
701+
query_params={"project": PROJECT},
702+
data=DATA,
703+
_target_object=bucket,
704+
)
671705

672706
def test_create_w_extra_properties(self):
707+
from google.cloud.storage.client import Client
708+
673709
BUCKET_NAME = "bucket-name"
674710
PROJECT = "PROJECT"
675711
CORS = [
@@ -694,8 +730,11 @@ def test_create_w_extra_properties(self):
694730
"billing": {"requesterPays": True},
695731
"labels": LABELS,
696732
}
697-
connection = _Connection(DATA)
698-
client = _Client(connection, project=PROJECT)
733+
734+
connection = _make_connection(DATA)
735+
client = Client(project=PROJECT)
736+
client._base_connection = connection
737+
699738
bucket = self._make_one(client=client, name=BUCKET_NAME)
700739
bucket.cors = CORS
701740
bucket.lifecycle_rules = LIFECYCLE_RULES
@@ -705,29 +744,37 @@ def test_create_w_extra_properties(self):
705744
bucket.labels = LABELS
706745
bucket.create(location=LOCATION)
707746

708-
kw, = connection._requested
709-
self.assertEqual(kw["method"], "POST")
710-
self.assertEqual(kw["path"], "/b")
711-
self.assertEqual(kw["query_params"], {"project": PROJECT})
712-
self.assertEqual(kw["data"], DATA)
747+
connection.api_request.assert_called_once_with(
748+
method="POST",
749+
path="/b",
750+
query_params={"project": PROJECT},
751+
data=DATA,
752+
_target_object=bucket,
753+
)
713754

714755
def test_create_w_predefined_acl_invalid(self):
756+
from google.cloud.storage.client import Client
757+
715758
PROJECT = "PROJECT"
716759
BUCKET_NAME = "bucket-name"
717760
DATA = {"name": BUCKET_NAME}
718761
connection = _Connection(DATA)
719-
client = _Client(connection, project=PROJECT)
762+
client = Client(project=PROJECT)
763+
client._base_connection = connection
720764
bucket = self._make_one(client=client, name=BUCKET_NAME)
721765

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

725769
def test_create_w_predefined_acl_valid(self):
770+
from google.cloud.storage.client import Client
771+
726772
PROJECT = "PROJECT"
727773
BUCKET_NAME = "bucket-name"
728774
DATA = {"name": BUCKET_NAME}
729775
connection = _Connection(DATA)
730-
client = _Client(connection, project=PROJECT)
776+
client = Client(project=PROJECT)
777+
client._base_connection = connection
731778
bucket = self._make_one(client=client, name=BUCKET_NAME)
732779
bucket.create(predefined_acl="publicRead")
733780

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

741788
def test_create_w_predefined_default_object_acl_invalid(self):
789+
from google.cloud.storage.client import Client
790+
742791
PROJECT = "PROJECT"
743792
BUCKET_NAME = "bucket-name"
744793
DATA = {"name": BUCKET_NAME}
745794
connection = _Connection(DATA)
746-
client = _Client(connection, project=PROJECT)
795+
client = Client(project=PROJECT)
796+
client._base_connection = connection
747797
bucket = self._make_one(client=client, name=BUCKET_NAME)
748798

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

752802
def test_create_w_predefined_default_object_acl_valid(self):
803+
from google.cloud.storage.client import Client
804+
753805
PROJECT = "PROJECT"
754806
BUCKET_NAME = "bucket-name"
755807
DATA = {"name": BUCKET_NAME}
756808
connection = _Connection(DATA)
757-
client = _Client(connection, project=PROJECT)
809+
client = Client(project=PROJECT)
810+
client._base_connection = connection
758811
bucket = self._make_one(client=client, name=BUCKET_NAME)
759812
bucket.create(predefined_default_object_acl="publicRead")
760813

0 commit comments

Comments
 (0)