@@ -560,56 +560,108 @@ async def _list_objects(self, path, prefix="", versions=False, **kwargs):
560560 raise
561561
562562 async def _mkdir (
563- self , path , create_parents = False , enable_hierarchical_namespace = False , ** kwargs
563+ self ,
564+ path ,
565+ create_parents = False ,
566+ enable_hierarchical_namespace = False ,
567+ placement = None ,
568+ location = None ,
569+ ** kwargs ,
564570 ):
565571 """
566- If the path does not contain an object key, a new bucket is created.
567- If `enable_hierarchical_namespace` is True, the bucket will have Hierarchical Namespace enabled.
568-
569- For HNS-enabled buckets, this method creates a folder object. If
570- `create_parents` is True, any missing parent folders are also created.
571-
572- If bucket doesn't exist, enable_hierarchical_namespace and create_parents are set to True
573- and the path includes a key then HNS-enabled bucket will be created
574- and also the folders within that bucket.
572+ Create a directory or bucket.
575573
576- If `create_parents` is False and a parent does not exist , a
577- FileNotFoundError is raised .
574+ If the path refers to a bucket (no object key) , a new bucket is created.
575+ If the path refers to a directory (includes object key), a directory is created .
578576
579- For non-HNS buckets, it falls back to the parent implementation which
580- may involve creating a bucket or doing nothing (as GCS has no true empty directories).
577+ Parameters
578+ ----------
579+ path : str
580+ Path to create.
581+ create_parents : bool
582+ If True, create parent directories if they do not exist.
583+ If the path includes a bucket that does not exist, the bucket will also be created.
584+ enable_hierarchical_namespace : bool
585+ If True, and a bucket is being created, the bucket will have Hierarchical
586+ Namespace (HNS) enabled.
587+ placement : str, optional
588+ If set to a zone (e.g. "us-central1-a"), a Zonal bucket is created.
589+ Zonal buckets are HNS-enabled by default.
590+ When creating a Zonal bucket, `location` must be passed as a
591+ region (e.g. "us-central1"). If `location` is not specified, it defaults
592+ to `self.default_location`. The zone specified in `placement` must belong
593+ to the region specified in `location`.
594+ location : str, optional
595+ Location where buckets are created, like 'US' or 'EUROPE-WEST3'.
596+ If not provided, defaults to `self.default_location`.
597+ **kwargs : dict
598+ Additional arguments passed to the bucket creation API.
599+
600+ Notes
601+ -----
602+ - For HNS-enabled buckets (including Zonal buckets), this method creates a
603+ native folder object.
604+ - If `create_parents` is False and a parent directory does not exist in an
605+ HNS/Zonal bucket, a FileNotFoundError is raised.
606+ - For non-HNS buckets, this falls back to the parent implementation. Since
607+ standard GCS has no true directories, `mkdir` on a path with a key is
608+ typically a no-op unless `create_parents=True` triggers bucket creation.
581609 """
582610 path = self ._strip_protocol (path )
583- if enable_hierarchical_namespace :
584- kwargs ["hierarchicalNamespace" ] = {"enabled" : True }
611+ bucket , key , _ = self .split_path (path )
612+
613+ # Determine if we are requesting creation of a Zonal or HNS bucket
614+ should_create_zonal_bucket = placement is not None
615+ should_create_hns_bucket = (
616+ enable_hierarchical_namespace or should_create_zonal_bucket
617+ )
618+
619+ # Prepare arguments for bucket creation
620+ bucket_kwargs = kwargs .copy ()
621+ if location :
622+ bucket_kwargs ["location" ] = location
623+ if should_create_zonal_bucket :
624+ bucket_kwargs ["customPlacementConfig" ] = {"dataLocations" : [placement ]}
625+ bucket_kwargs ["storageClass" ] = "RAPID"
626+
627+ if should_create_hns_bucket :
628+ bucket_kwargs ["hierarchicalNamespace" ] = {"enabled" : True }
585629 # HNS buckets require uniform bucket-level access.
586- kwargs ["iamConfiguration" ] = {"uniformBucketLevelAccess" : {"enabled" : True }}
630+ bucket_kwargs ["iamConfiguration" ] = {
631+ "uniformBucketLevelAccess" : {"enabled" : True }
632+ }
587633 # When uniformBucketLevelAccess is enabled, ACLs cannot be used.
588634 # We must explicitly set them to None to prevent the parent
589635 # method from using default ACLs.
590- kwargs ["acl" ] = None
591- kwargs ["default_acl" ] = None
636+ bucket_kwargs ["acl" ] = None
637+ bucket_kwargs ["default_acl" ] = None
592638
593- bucket , key , _ = self .split_path (path )
594- # If the key is empty, the path refers to a bucket, not an object.
595- # Defer to the parent method to handle bucket creation.
639+ # Case 1: Path is just a bucket
596640 if not key :
597- return await super ()._mkdir (path , create_parents = create_parents , ** kwargs )
641+ return await super ()._mkdir (
642+ path , create_parents = create_parents , ** bucket_kwargs
643+ )
598644
599- is_hns = False
600- # If creating an HNS bucket, check for its existence first.
601- if create_parents and enable_hierarchical_namespace :
645+ # Case 2: Path is a folder
646+ is_hns_bucket = False
647+
648+ # If creating parents and HNS/Zonal requested, ensure bucket exists with correct config
649+ if create_parents and should_create_hns_bucket :
602650 if not await self ._exists (bucket ):
603- await super ()._mkdir (bucket , create_parents = True , ** kwargs )
604- is_hns = True # Skip HNS check since we just created it.
651+ await super ()._mkdir (bucket , create_parents = True , ** bucket_kwargs )
652+ is_hns_bucket = True
605653
606- if not is_hns :
607- # If the bucket was not created above, we need to check its type.
608- is_hns = await self ._is_bucket_hns_enabled (bucket )
654+ if not is_hns_bucket :
655+ is_hns_bucket = await self ._is_bucket_hns_enabled (bucket )
609656
610- if not is_hns :
611- return await super ()._mkdir (path , create_parents = create_parents , ** kwargs )
657+ if is_hns_bucket :
658+ return await self ._create_hns_folder (path , bucket , key , create_parents )
659+
660+ return await super ()._mkdir (
661+ path , create_parents = create_parents , ** bucket_kwargs
662+ )
612663
664+ async def _create_hns_folder (self , path , bucket , key , create_parents ):
613665 logger .debug (f"Using HNS-aware mkdir for '{ path } '." )
614666 parent = f"projects/_/buckets/{ bucket } "
615667 folder_id = key .rstrip ("/" )
0 commit comments