From f9480dc2a1bc58367083176bd74725aa8b903301 Mon Sep 17 00:00:00 2001 From: Carlos de la Guardia Date: Tue, 10 Nov 2020 16:02:15 -0600 Subject: [PATCH] feat: allow routine references (#378) * feat: allow routine references in dataset access property * build: black formatting --- google/cloud/bigquery/dataset.py | 46 ++++++++++++++++++++++---------- tests/unit/test_dataset.py | 26 ++++++++++++++++++ 2 files changed, 58 insertions(+), 14 deletions(-) diff --git a/google/cloud/bigquery/dataset.py b/google/cloud/bigquery/dataset.py index 9a80f30b5..ce07c8048 100644 --- a/google/cloud/bigquery/dataset.py +++ b/google/cloud/bigquery/dataset.py @@ -79,8 +79,9 @@ 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. @@ -88,17 +89,17 @@ class AccessEntry(object): 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, @@ -106,11 +107,19 @@ class AccessEntry(object): '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') @@ -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.""" @@ -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: @@ -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 diff --git a/tests/unit/test_dataset.py b/tests/unit/test_dataset.py index e4977a270..b3a53a08d 100644 --- a/tests/unit/test_dataset.py +++ b/tests/unit/test_dataset.py @@ -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" @@ -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)