feat: Update the AccessEntry class with a new condition attribute and unit tests by chalmerlowe · Pull Request #2163 · googleapis/python-bigquery · GitHub | Latest TMZ Celebrity News & Gossip | Watch TMZ Live
Skip to content

feat: Update the AccessEntry class with a new condition attribute and unit tests #2163

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 19 commits into from
Apr 29, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
4e20423
feat: adds condition class and assoc. unit tests
chalmerlowe Apr 10, 2025
bbfb818
Merge branch 'main' into feat-b330869964-add-dataset-condition-access…
chalmerlowe Apr 10, 2025
04fdc8e
Updates AccessEntry with condition setter/getter
chalmerlowe Apr 15, 2025
f41df12
Adds condition attr to AccessEntry and unit tests
chalmerlowe Apr 16, 2025
137e0a9
Merge branch 'main' into feat-b330869964-update-accessentry-class
chalmerlowe Apr 16, 2025
a129e33
adds tests for Condition dunder methods to ensure coverage
chalmerlowe Apr 17, 2025
ca8d734
Merge branch 'main' into feat-b330869964-update-accessentry-class
chalmerlowe Apr 25, 2025
152133e
moves the entity_type logic out of _from_api_repr to entity_type setter
chalmerlowe Apr 25, 2025
5451c58
Updates logic in entity_type getter
chalmerlowe Apr 25, 2025
a323c9d
updates several AccessEntry related tests
chalmerlowe Apr 25, 2025
6d0d1d1
Updates AccessEntry condition setter test to use a dict
chalmerlowe Apr 28, 2025
9340c00
udpates entity_id handling
chalmerlowe Apr 28, 2025
ae2cb44
Updates _entity_type access
chalmerlowe Apr 28, 2025
447472a
tweaks type hinting
chalmerlowe Apr 28, 2025
dc83110
Merge branch 'main' into feat-b330869964-update-accessentry-class
chalmerlowe Apr 28, 2025
5190018
Update tests/unit/test_dataset.py
chalmerlowe Apr 28, 2025
9a6f0b6
Update tests/unit/test_dataset.py
chalmerlowe Apr 28, 2025
635a1f4
Updates DatasetReference in test and __eq__ check
chalmerlowe Apr 29, 2025
4c060a4
remove debug print statement
chalmerlowe Apr 29, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
126 changes: 110 additions & 16 deletions google/cloud/bigquery/dataset.py
Original file line number Diff line number Diff line change
Expand Up @@ -298,12 +298,15 @@ def __init__(
role: Optional[str] = None,
entity_type: Optional[str] = None,
entity_id: Optional[Union[Dict[str, Any], str]] = None,
**kwargs,
):
self._properties = {}
self._properties: Dict[str, Any] = {}
if entity_type is not None:
self._properties[entity_type] = entity_id
self._properties["role"] = role
self._entity_type = entity_type
self._entity_type: Optional[str] = entity_type
for prop, val in kwargs.items():
setattr(self, prop, val)

@property
def role(self) -> Optional[str]:
Expand All @@ -330,6 +333,9 @@ def dataset(self, value):
if isinstance(value, str):
value = DatasetReference.from_string(value).to_api_repr()

if isinstance(value, DatasetReference):
value = value.to_api_repr()

if isinstance(value, (Dataset, DatasetListItem)):
value = value.reference.to_api_repr()

Expand Down Expand Up @@ -437,15 +443,65 @@ def special_group(self) -> Optional[str]:
def special_group(self, value):
self._properties["specialGroup"] = value

@property
def condition(self) -> Optional["Condition"]:
"""Optional[Condition]: The IAM condition associated with this entry."""
value = typing.cast(Dict[str, Any], self._properties.get("condition"))
return Condition.from_api_repr(value) if value else None

@condition.setter
def condition(self, value: Union["Condition", dict, None]):
"""Set the IAM condition for this entry."""
if value is None:
self._properties["condition"] = None
elif isinstance(value, Condition):
self._properties["condition"] = value.to_api_repr()
elif isinstance(value, dict):
self._properties["condition"] = value
else:
raise TypeError("condition must be a Condition object, dict, or None")

@property
def entity_type(self) -> Optional[str]:
"""The entity_type of the entry."""

# The api_repr for an AccessEntry object is expected to be a dict with
# only a few keys. Two keys that may be present are role and condition.
# Any additional key is going to have one of ~eight different names:
# userByEmail, groupByEmail, domain, dataset, specialGroup, view,
# routine, iamMember

# if self._entity_type is None, see if it needs setting
# i.e. is there a key: value pair that should be associated with
# entity_type and entity_id?
if self._entity_type is None:
resource = self._properties.copy()
# we are empyting the dict to get to the last `key: value`` pair
# so we don't keep these first entries
_ = resource.pop("role", None)
_ = resource.pop("condition", None)

try:
# we only need entity_type, because entity_id gets set elsewhere.
entity_type, _ = resource.popitem()
except KeyError:
entity_type = None

self._entity_type = entity_type

return self._entity_type

@property
def entity_id(self) -> Optional[Union[Dict[str, Any], str]]:
"""The entity_id of the entry."""
return self._properties.get(self._entity_type) if self._entity_type else None
if self.entity_type:
entity_type = self.entity_type
else:
Comment on lines +497 to +499
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Optional: This could use the "walrus operator" to save a call to the entity_type property.

Suggested change
if self.entity_type:
entity_type = self.entity_type
else:
if not (entity_type := self.entity_type):

return None
return typing.cast(
Optional[Union[Dict[str, Any], str]],
self._properties.get(entity_type, None),
)

def __eq__(self, other):
if not isinstance(other, AccessEntry):
Expand All @@ -464,7 +520,16 @@ def _key(self):
Returns:
Tuple: The contents of this :class:`~google.cloud.bigquery.dataset.AccessEntry`.
"""

properties = self._properties.copy()

# Dicts are not hashable.
# Convert condition to a hashable datatype(s)
condition = properties.get("condition")
if isinstance(condition, dict):
condition_key = tuple(sorted(condition.items()))
properties["condition"] = condition_key

prop_tup = tuple(sorted(properties.items()))
return (self.role, self._entity_type, self.entity_id, prop_tup)

Expand All @@ -491,19 +556,11 @@ def from_api_repr(cls, resource: dict) -> "AccessEntry":
Returns:
google.cloud.bigquery.dataset.AccessEntry:
Access entry parsed from ``resource``.

Raises:
ValueError:
If the resource has more keys than ``role`` and one additional
key.
"""
entry = resource.copy()
role = entry.pop("role", None)
entity_type, entity_id = entry.popitem()
if len(entry) != 0:
raise ValueError("Entry has unexpected keys remaining.", entry)

return cls(role, entity_type, entity_id)
access_entry = cls()
access_entry._properties = resource.copy()
return access_entry


class Dataset(object):
Expand Down Expand Up @@ -1160,6 +1217,43 @@ def from_api_repr(cls, resource: Dict[str, Any]) -> "Condition":

return cls(
expression=resource["expression"],
title=resource.get("title"),
description=resource.get("description"),
title=resource.get("title", None),
description=resource.get("description", None),
)

def __eq__(self, other: object) -> bool:
"""Check for equality based on expression, title, and description."""
if not isinstance(other, Condition):
return NotImplemented
return self._key() == other._key()

def _key(self):
"""A tuple key that uniquely describes this field.
Used to compute this instance's hashcode and evaluate equality.
Returns:
Tuple: The contents of this :class:`~google.cloud.bigquery.dataset.AccessEntry`.
"""

properties = self._properties.copy()

# Dicts are not hashable.
# Convert object to a hashable datatype(s)
prop_tup = tuple(sorted(properties.items()))
return prop_tup

def __ne__(self, other: object) -> bool:
"""Check for inequality."""
return not self == other

def __hash__(self) -> int:
"""Generate a hash based on expression, title, and description."""
return hash(self._key())

def __repr__(self) -> str:
"""Return a string representation of the Condition object."""
parts = [f"expression={self.expression!r}"]
if self.title is not None:
parts.append(f"title={self.title!r}")
if self.description is not None:
parts.append(f"description={self.description!r}")
return f"Condition({', '.join(parts)})"
Loading

TMZ Celebrity News – Breaking Stories, Videos & Gossip

Looking for the latest TMZ celebrity news? You've come to the right place. From shocking Hollywood scandals to exclusive videos, TMZ delivers it all in real time.

Whether it’s a red carpet slip-up, a viral paparazzi moment, or a legal drama involving your favorite stars, TMZ news is always first to break the story. Stay in the loop with daily updates, insider tips, and jaw-dropping photos.

🎥 Watch TMZ Live

TMZ Live brings you daily celebrity news and interviews straight from the TMZ newsroom. Don’t miss a beat—watch now and see what’s trending in Hollywood.