Skip to content
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鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: allow routine references #378

Merged
merged 5 commits into from Nov 10, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
46 changes: 32 additions & 14 deletions google/cloud/bigquery/dataset.py
Expand Up @@ -79,38 +79,47 @@ class AccessEntry(object):
"""Represents grant of an access role to an entity.

An entry must have exactly one of the allowed :attr:`ENTITY_TYPES`. If
anything but ``view`` is set, a ``role`` is also required. ``role`` is
omitted for a ``view``, because ``view`` s are always read-only.
anything but ``view`` or ``routine`` are set, a ``role`` is also required.
``role`` is omitted for ``view`` and ``routine``, because they are always
read-only.

See https://cloud.google.com/bigquery/docs/reference/rest/v2/datasets.

Args:
role (str):
Role granted to the entity. The following string values are
supported: `'READER'`, `'WRITER'`, `'OWNER'`. It may also be
:data:`None` if the ``entity_type`` is ``view``.
:data:`None` if the ``entity_type`` is ``view`` or ``routine``.

entity_type (str):
Type of entity being granted the role. One of :attr:`ENTITY_TYPES`.

entity_id (Union[str, Dict[str, str]]):
If the ``entity_type`` is not 'view', the ``entity_id`` is the
``str`` ID of the entity being granted the role. If the
``entity_type`` is 'view', the ``entity_id`` is a ``dict``
representing the view from a different dataset to grant access to
in the following format::
If the ``entity_type`` is not 'view' or 'routine', the ``entity_id``
is the ``str`` ID of the entity being granted the role. If the
``entity_type`` is 'view' or 'routine', the ``entity_id`` is a ``dict``
representing the view or routine from a different dataset to grant
access to in the following format for views::

{
'projectId': string,
'datasetId': string,
'tableId': string
}

For routines::

{
'projectId': string,
'datasetId': string,
'routineId': string
}

Raises:
ValueError:
If the ``entity_type`` is not among :attr:`ENTITY_TYPES`, or if a
``view`` has ``role`` set, or a non ``view`` **does not** have a
``role`` set.
``view`` or a ``routine`` has ``role`` set, or a non ``view`` and
non ``routine`` **does not** have a ``role`` set.

Examples:
>>> entry = AccessEntry('OWNER', 'userByEmail', 'user@example.com')
Expand All @@ -124,7 +133,15 @@ class AccessEntry(object):
"""

ENTITY_TYPES = frozenset(
["userByEmail", "groupByEmail", "domain", "specialGroup", "view", "iamMember"]
[
"userByEmail",
"groupByEmail",
"domain",
"specialGroup",
"view",
"iamMember",
"routine",
]
)
"""Allowed entity types."""

Expand All @@ -135,10 +152,11 @@ def __init__(self, role, entity_type, entity_id):
", ".join(self.ENTITY_TYPES),
)
raise ValueError(message)
if entity_type == "view":
if entity_type in ("view", "routine"):
if role is not None:
raise ValueError(
"Role must be None for a view. Received " "role: %r" % (role,)
"Role must be None for a %r. Received "
"role: %r" % (entity_type, role)
)
else:
if role is None:
Expand Down Expand Up @@ -409,7 +427,7 @@ def access_entries(self):
entries.

``role`` augments the entity type and must be present **unless** the
entity type is ``view``.
entity type is ``view`` or ``routine``.

Raises:
TypeError: If 'value' is not a sequence
Expand Down
26 changes: 26 additions & 0 deletions tests/unit/test_dataset.py
Expand Up @@ -53,6 +53,21 @@ def test_ctor_view_success(self):
self.assertEqual(entry.entity_type, entity_type)
self.assertEqual(entry.entity_id, entity_id)

def test_ctor_routine_with_role(self):
role = "READER"
entity_type = "routine"
with self.assertRaises(ValueError):
self._make_one(role, entity_type, None)

def test_ctor_routine_success(self):
role = None
entity_type = "routine"
entity_id = object()
entry = self._make_one(role, entity_type, entity_id)
self.assertEqual(entry.role, role)
self.assertEqual(entry.entity_type, entity_type)
self.assertEqual(entry.entity_id, entity_id)

def test_ctor_nonview_without_role(self):
role = None
entity_type = "userByEmail"
Expand Down Expand Up @@ -115,6 +130,17 @@ def test_to_api_repr_view(self):
exp_resource = {"view": view}
self.assertEqual(resource, exp_resource)

def test_to_api_repr_routine(self):
routine = {
"projectId": "my-project",
"datasetId": "my_dataset",
"routineId": "my_routine",
}
entry = self._make_one(None, "routine", routine)
resource = entry.to_api_repr()
exp_resource = {"routine": routine}
self.assertEqual(resource, exp_resource)

def test_from_api_repr(self):
resource = {"role": "OWNER", "userByEmail": "salmon@example.com"}
entry = self._get_target_class().from_api_repr(resource)
Expand Down