Skip to content

Commit

Permalink
feat: allow routine references (#378)
Browse files Browse the repository at this point in the history
* feat: allow routine references in dataset access property

* build: black formatting
  • Loading branch information
cguardia committed Nov 10, 2020
1 parent 86f6a51 commit f9480dc
Show file tree
Hide file tree
Showing 2 changed files with 58 additions and 14 deletions.
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

0 comments on commit f9480dc

Please sign in to comment.