@@ -298,12 +298,15 @@ def __init__(
298
298
role : Optional [str ] = None ,
299
299
entity_type : Optional [str ] = None ,
300
300
entity_id : Optional [Union [Dict [str , Any ], str ]] = None ,
301
+ ** kwargs ,
301
302
):
302
- self ._properties = {}
303
+ self ._properties : Dict [ str , Any ] = {}
303
304
if entity_type is not None :
304
305
self ._properties [entity_type ] = entity_id
305
306
self ._properties ["role" ] = role
306
- self ._entity_type = entity_type
307
+ self ._entity_type : Optional [str ] = entity_type
308
+ for prop , val in kwargs .items ():
309
+ setattr (self , prop , val )
307
310
308
311
@property
309
312
def role (self ) -> Optional [str ]:
@@ -330,6 +333,9 @@ def dataset(self, value):
330
333
if isinstance (value , str ):
331
334
value = DatasetReference .from_string (value ).to_api_repr ()
332
335
336
+ if isinstance (value , DatasetReference ):
337
+ value = value .to_api_repr ()
338
+
333
339
if isinstance (value , (Dataset , DatasetListItem )):
334
340
value = value .reference .to_api_repr ()
335
341
@@ -437,15 +443,65 @@ def special_group(self) -> Optional[str]:
437
443
def special_group (self , value ):
438
444
self ._properties ["specialGroup" ] = value
439
445
446
+ @property
447
+ def condition (self ) -> Optional ["Condition" ]:
448
+ """Optional[Condition]: The IAM condition associated with this entry."""
449
+ value = typing .cast (Dict [str , Any ], self ._properties .get ("condition" ))
450
+ return Condition .from_api_repr (value ) if value else None
451
+
452
+ @condition .setter
453
+ def condition (self , value : Union ["Condition" , dict , None ]):
454
+ """Set the IAM condition for this entry."""
455
+ if value is None :
456
+ self ._properties ["condition" ] = None
457
+ elif isinstance (value , Condition ):
458
+ self ._properties ["condition" ] = value .to_api_repr ()
459
+ elif isinstance (value , dict ):
460
+ self ._properties ["condition" ] = value
461
+ else :
462
+ raise TypeError ("condition must be a Condition object, dict, or None" )
463
+
440
464
@property
441
465
def entity_type (self ) -> Optional [str ]:
442
466
"""The entity_type of the entry."""
467
+
468
+ # The api_repr for an AccessEntry object is expected to be a dict with
469
+ # only a few keys. Two keys that may be present are role and condition.
470
+ # Any additional key is going to have one of ~eight different names:
471
+ # userByEmail, groupByEmail, domain, dataset, specialGroup, view,
472
+ # routine, iamMember
473
+
474
+ # if self._entity_type is None, see if it needs setting
475
+ # i.e. is there a key: value pair that should be associated with
476
+ # entity_type and entity_id?
477
+ if self ._entity_type is None :
478
+ resource = self ._properties .copy ()
479
+ # we are empyting the dict to get to the last `key: value`` pair
480
+ # so we don't keep these first entries
481
+ _ = resource .pop ("role" , None )
482
+ _ = resource .pop ("condition" , None )
483
+
484
+ try :
485
+ # we only need entity_type, because entity_id gets set elsewhere.
486
+ entity_type , _ = resource .popitem ()
487
+ except KeyError :
488
+ entity_type = None
489
+
490
+ self ._entity_type = entity_type
491
+
443
492
return self ._entity_type
444
493
445
494
@property
446
495
def entity_id (self ) -> Optional [Union [Dict [str , Any ], str ]]:
447
496
"""The entity_id of the entry."""
448
- return self ._properties .get (self ._entity_type ) if self ._entity_type else None
497
+ if self .entity_type :
498
+ entity_type = self .entity_type
499
+ else :
500
+ return None
501
+ return typing .cast (
502
+ Optional [Union [Dict [str , Any ], str ]],
503
+ self ._properties .get (entity_type , None ),
504
+ )
449
505
450
506
def __eq__ (self , other ):
451
507
if not isinstance (other , AccessEntry ):
@@ -464,7 +520,16 @@ def _key(self):
464
520
Returns:
465
521
Tuple: The contents of this :class:`~google.cloud.bigquery.dataset.AccessEntry`.
466
522
"""
523
+
467
524
properties = self ._properties .copy ()
525
+
526
+ # Dicts are not hashable.
527
+ # Convert condition to a hashable datatype(s)
528
+ condition = properties .get ("condition" )
529
+ if isinstance (condition , dict ):
530
+ condition_key = tuple (sorted (condition .items ()))
531
+ properties ["condition" ] = condition_key
532
+
468
533
prop_tup = tuple (sorted (properties .items ()))
469
534
return (self .role , self ._entity_type , self .entity_id , prop_tup )
470
535
@@ -491,19 +556,11 @@ def from_api_repr(cls, resource: dict) -> "AccessEntry":
491
556
Returns:
492
557
google.cloud.bigquery.dataset.AccessEntry:
493
558
Access entry parsed from ``resource``.
494
-
495
- Raises:
496
- ValueError:
497
- If the resource has more keys than ``role`` and one additional
498
- key.
499
559
"""
500
- entry = resource .copy ()
501
- role = entry .pop ("role" , None )
502
- entity_type , entity_id = entry .popitem ()
503
- if len (entry ) != 0 :
504
- raise ValueError ("Entry has unexpected keys remaining." , entry )
505
560
506
- return cls (role , entity_type , entity_id )
561
+ access_entry = cls ()
562
+ access_entry ._properties = resource .copy ()
563
+ return access_entry
507
564
508
565
509
566
class Dataset (object ):
@@ -1160,6 +1217,43 @@ def from_api_repr(cls, resource: Dict[str, Any]) -> "Condition":
1160
1217
1161
1218
return cls (
1162
1219
expression = resource ["expression" ],
1163
- title = resource .get ("title" ),
1164
- description = resource .get ("description" ),
1220
+ title = resource .get ("title" , None ),
1221
+ description = resource .get ("description" , None ),
1165
1222
)
1223
+
1224
+ def __eq__ (self , other : object ) -> bool :
1225
+ """Check for equality based on expression, title, and description."""
1226
+ if not isinstance (other , Condition ):
1227
+ return NotImplemented
1228
+ return self ._key () == other ._key ()
1229
+
1230
+ def _key (self ):
1231
+ """A tuple key that uniquely describes this field.
1232
+ Used to compute this instance's hashcode and evaluate equality.
1233
+ Returns:
1234
+ Tuple: The contents of this :class:`~google.cloud.bigquery.dataset.AccessEntry`.
1235
+ """
1236
+
1237
+ properties = self ._properties .copy ()
1238
+
1239
+ # Dicts are not hashable.
1240
+ # Convert object to a hashable datatype(s)
1241
+ prop_tup = tuple (sorted (properties .items ()))
1242
+ return prop_tup
1243
+
1244
+ def __ne__ (self , other : object ) -> bool :
1245
+ """Check for inequality."""
1246
+ return not self == other
1247
+
1248
+ def __hash__ (self ) -> int :
1249
+ """Generate a hash based on expression, title, and description."""
1250
+ return hash (self ._key ())
1251
+
1252
+ def __repr__ (self ) -> str :
1253
+ """Return a string representation of the Condition object."""
1254
+ parts = [f"expression={ self .expression !r} " ]
1255
+ if self .title is not None :
1256
+ parts .append (f"title={ self .title !r} " )
1257
+ if self .description is not None :
1258
+ parts .append (f"description={ self .description !r} " )
1259
+ return f"Condition({ ', ' .join (parts )} )"
0 commit comments