Repository: googleapis/python-api-core
Pull request #147 (2021-02-09 13:33:29)

docs: update python contributing guide
This PR was generated using Autosynth. :rainbow:

Synth log will be available here:
https://source.cloud.google.com/results/invocations/9d4e6069-5c18-4f79-97fb-99ebae377691/targets

- [ ] To automatically regenerate this PR, check this box.

Source-Link: https://github.com/googleapis/synthtool/commit/4679e7e415221f03ff2a71e3ffad75b9ec41d87e


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index df88b0d..65cbeef 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,20 @@
 
 [1]: https://pypi.org/project/google-api-core/#history
 
+### [1.26.2](https://www.github.com/googleapis/python-api-core/compare/v1.26.1...v1.26.2) (2021-03-23)
+
+
+### Bug Fixes
+
+* save empty IAM policy bindings ([#155](https://www.github.com/googleapis/python-api-core/issues/155)) ([536c2ca](https://www.github.com/googleapis/python-api-core/commit/536c2cad814b8fa8cd346a3d7bd5f6b9889c4a6f))
+
+### [1.26.1](https://www.github.com/googleapis/python-api-core/compare/v1.26.0...v1.26.1) (2021-02-12)
+
+
+### Bug Fixes
+
+* add operation name to x-goog-request-params in async client ([#137](https://www.github.com/googleapis/python-api-core/issues/137)) ([7271b23](https://www.github.com/googleapis/python-api-core/commit/7271b23afddb032e49e957525704d0cd5bfa4c65))
+
 ## [1.26.0](https://www.github.com/googleapis/python-api-core/compare/v1.25.1...v1.26.0) (2021-02-08)
 
 
diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst
index 643fbdb..1116150 100644
--- a/CONTRIBUTING.rst
+++ b/CONTRIBUTING.rst
@@ -70,9 +70,14 @@ We use `nox <https://nox.readthedocs.io/en/latest/>`__ to instrument our tests.
 - To test your changes, run unit tests with ``nox``::
 
     $ nox -s unit-2.7
-    $ nox -s unit-3.7
+    $ nox -s unit-3.8
     $ ...
 
+- Args to pytest can be passed through the nox command separated by a `--`. For
+  example, to run a single test::
+
+    $ nox -s unit-3.8 -- -k <name of test>
+
   .. note::
 
     The unit tests and system tests are described in the
@@ -93,8 +98,12 @@ On Debian/Ubuntu::
 ************
 Coding Style
 ************
+- We use the automatic code formatter ``black``. You can run it using
+  the nox session ``blacken``. This will eliminate many lint errors. Run via::
+
+   $ nox -s blacken
 
-- PEP8 compliance, with exceptions defined in the linter configuration.
+- PEP8 compliance is required, with exceptions defined in the linter configuration.
   If you have ``nox`` installed, you can test that you have not introduced
   any non-compliant code via::
 
@@ -133,13 +142,18 @@ Running System Tests
 
 - To run system tests, you can execute::
 
-   $ nox -s system-3.7
+   # Run all system tests
+   $ nox -s system-3.8
    $ nox -s system-2.7
 
+   # Run a single system test
+   $ nox -s system-3.8 -- -k <name of test>
+
+
   .. note::
 
       System tests are only configured to run under Python 2.7 and
-      Python 3.7. For expediency, we do not run them in older versions
+      Python 3.8. For expediency, we do not run them in older versions
       of Python 3.
 
   This alone will not run the tests. You'll need to change some local
diff --git a/google/api_core/iam.py b/google/api_core/iam.py
index f130936..d83cbf3 100644
--- a/google/api_core/iam.py
+++ b/google/api_core/iam.py
@@ -136,7 +136,10 @@ class Policy(collections_abc.MutableMapping):
         for b in self._bindings:
             if b["role"] == key:
                 return b["members"]
-        return set()
+        # binding does not yet exist, create one
+        new_binding = {"role": key, "members": set()}
+        self._bindings.append(new_binding)
+        return new_binding["members"]
 
     def __setitem__(self, key, value):
         self.__check_version__()
diff --git a/google/api_core/operations_v1/operations_async_client.py b/google/api_core/operations_v1/operations_async_client.py
index 5d7b26c..b3d0135 100644
--- a/google/api_core/operations_v1/operations_async_client.py
+++ b/google/api_core/operations_v1/operations_async_client.py
@@ -120,6 +120,11 @@ class OperationsAsyncClient:
                 subclass will be raised.
         """
         request = operations_pb2.GetOperationRequest(name=name)
+
+        # Add routing header
+        metadata = metadata or []
+        metadata.append(gapic_v1.routing_header.to_grpc_metadata({"name": name}))
+
         return await self._get_operation(request, retry=retry, timeout=timeout, metadata=metadata)
 
     async def list_operations(
@@ -182,6 +187,10 @@ class OperationsAsyncClient:
         # Create the request object.
         request = operations_pb2.ListOperationsRequest(name=name, filter=filter_)
 
+        # Add routing header
+        metadata = metadata or []
+        metadata.append(gapic_v1.routing_header.to_grpc_metadata({"name": name}))
+
         # Create the method used to fetch pages
         method = functools.partial(self._list_operations, retry=retry, timeout=timeout, metadata=metadata)
 
@@ -246,6 +255,11 @@ class OperationsAsyncClient:
         """
         # Create the request object.
         request = operations_pb2.CancelOperationRequest(name=name)
+
+        # Add routing header
+        metadata = metadata or []
+        metadata.append(gapic_v1.routing_header.to_grpc_metadata({"name": name}))
+
         await self._cancel_operation(request, retry=retry, timeout=timeout, metadata=metadata)
 
     async def delete_operation(
@@ -292,4 +306,9 @@ class OperationsAsyncClient:
         """
         # Create the request object.
         request = operations_pb2.DeleteOperationRequest(name=name)
+
+        # Add routing header
+        metadata = metadata or []
+        metadata.append(gapic_v1.routing_header.to_grpc_metadata({"name": name}))
+
         await self._delete_operation(request, retry=retry, timeout=timeout, metadata=metadata)
diff --git a/google/api_core/version.py b/google/api_core/version.py
index 7fcd17a..3c06c88 100644
--- a/google/api_core/version.py
+++ b/google/api_core/version.py
@@ -12,4 +12,4 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-__version__ = "1.26.0"
+__version__ = "1.26.2"
diff --git a/synth.metadata b/synth.metadata
index fec849e..cf254e7 100644
--- a/synth.metadata
+++ b/synth.metadata
@@ -4,14 +4,14 @@
       "git": {
         "name": ".",
         "remote": "https://github.com/googleapis/python-api-core.git",
-        "sha": "94c76e0873e5b2f42331d5b1ad286c1e63b61395"
+        "sha": "7273090a011c8b840d2e90e29dda3fc8f0eed792"
       }
     },
     {
       "git": {
         "name": "synthtool",
         "remote": "https://github.com/googleapis/synthtool.git",
-        "sha": "33366574ffb9e11737b3547eb6f020ecae0536e8"
+        "sha": "4679e7e415221f03ff2a71e3ffad75b9ec41d87e"
       }
     }
   ],


Test patch:
diff --git a/tests/asyncio/operations_v1/test_operations_async_client.py b/tests/asyncio/operations_v1/test_operations_async_client.py
index 830cd46..a646901 100644
--- a/tests/asyncio/operations_v1/test_operations_async_client.py
+++ b/tests/asyncio/operations_v1/test_operations_async_client.py
@@ -36,10 +36,11 @@ async def test_get_operation():
         operations_pb2.Operation(name="meep"))
     client = operations_v1.OperationsAsyncClient(mocked_channel)
 
-    response = await client.get_operation("name", metadata=[("x-goog-request-params", "foo")])
+    response = await client.get_operation("name", metadata=[("header", "foo")])
     assert method.call_count == 1
     assert tuple(method.call_args_list[0])[0][0].name == "name"
-    assert ("x-goog-request-params", "foo") in tuple(method.call_args_list[0])[1]["metadata"]
+    assert ("header", "foo") in tuple(method.call_args_list[0])[1]["metadata"]
+    assert ("x-goog-request-params", "name=name") in tuple(method.call_args_list[0])[1]["metadata"]
     assert response == fake_call.response
 
 
@@ -54,7 +55,7 @@ async def test_list_operations():
     mocked_channel, method, fake_call = _mock_grpc_objects(list_response)
     client = operations_v1.OperationsAsyncClient(mocked_channel)
 
-    pager = await client.list_operations("name", "filter", metadata=[("x-goog-request-params", "foo")])
+    pager = await client.list_operations("name", "filter", metadata=[("header", "foo")])
 
     assert isinstance(pager, page_iterator_async.AsyncIterator)
     responses = []
@@ -64,7 +65,8 @@ async def test_list_operations():
     assert responses == operations
 
     assert method.call_count == 1
-    assert ("x-goog-request-params", "foo") in tuple(method.call_args_list[0])[1]["metadata"]
+    assert ("header", "foo") in tuple(method.call_args_list[0])[1]["metadata"]
+    assert ("x-goog-request-params", "name=name") in tuple(method.call_args_list[0])[1]["metadata"]
     request = tuple(method.call_args_list[0])[0][0]
     assert isinstance(request, operations_pb2.ListOperationsRequest)
     assert request.name == "name"
@@ -77,11 +79,12 @@ async def test_delete_operation():
         empty_pb2.Empty())
     client = operations_v1.OperationsAsyncClient(mocked_channel)
 
-    await client.delete_operation("name", metadata=[("x-goog-request-params", "foo")])
+    await client.delete_operation("name", metadata=[("header", "foo")])
 
     assert method.call_count == 1
     assert tuple(method.call_args_list[0])[0][0].name == "name"
-    assert ("x-goog-request-params", "foo") in tuple(method.call_args_list[0])[1]["metadata"]
+    assert ("header", "foo") in tuple(method.call_args_list[0])[1]["metadata"]
+    assert ("x-goog-request-params", "name=name") in tuple(method.call_args_list[0])[1]["metadata"]
 
 
 @pytest.mark.asyncio
@@ -90,8 +93,9 @@ async def test_cancel_operation():
         empty_pb2.Empty())
     client = operations_v1.OperationsAsyncClient(mocked_channel)
 
-    await client.cancel_operation("name", metadata=[("x-goog-request-params", "foo")])
+    await client.cancel_operation("name", metadata=[("header", "foo")])
 
     assert method.call_count == 1
     assert tuple(method.call_args_list[0])[0][0].name == "name"
-    assert ("x-goog-request-params", "foo") in tuple(method.call_args_list[0])[1]["metadata"]
+    assert ("header", "foo") in tuple(method.call_args_list[0])[1]["metadata"]
+    assert ("x-goog-request-params", "name=name") in tuple(method.call_args_list[0])[1]["metadata"]
diff --git a/tests/unit/test_iam.py b/tests/unit/test_iam.py
index 896e10d..f9771f0 100644
--- a/tests/unit/test_iam.py
+++ b/tests/unit/test_iam.py
@@ -32,11 +32,11 @@ class TestPolicy:
         policy = self._make_one()
         assert policy.etag is None
         assert policy.version is None
+        assert len(policy) == 0
+        assert dict(policy) == {}
         assert policy.owners == empty
         assert policy.editors == empty
         assert policy.viewers == empty
-        assert len(policy) == 0
-        assert dict(policy) == {}
 
     def test_ctor_explicit(self):
         VERSION = 1
@@ -45,16 +45,24 @@ class TestPolicy:
         policy = self._make_one(ETAG, VERSION)
         assert policy.etag == ETAG
         assert policy.version == VERSION
+        assert len(policy) == 0
+        assert dict(policy) == {}
         assert policy.owners == empty
         assert policy.editors == empty
         assert policy.viewers == empty
-        assert len(policy) == 0
-        assert dict(policy) == {}
 
     def test___getitem___miss(self):
         policy = self._make_one()
         assert policy["nonesuch"] == set()
 
+    def test__getitem___and_set(self):
+        from google.api_core.iam import OWNER_ROLE
+        policy = self._make_one()
+
+        # get the policy using the getter and then modify it
+        policy[OWNER_ROLE].add("user:phred@example.com")
+        assert dict(policy) == {OWNER_ROLE: {"user:phred@example.com"}}
+
     def test___getitem___version3(self):
         policy = self._make_one("DEADBEEF", 3)
         with pytest.raises(InvalidOperationException, match=_DICT_ACCESS_MSG):
@@ -293,10 +301,10 @@ class TestPolicy:
         policy = klass.from_api_repr(RESOURCE)
         assert policy.etag == "ACAB"
         assert policy.version is None
+        assert dict(policy) == {}
         assert policy.owners == empty
         assert policy.editors == empty
         assert policy.viewers == empty
-        assert dict(policy) == {}
 
     def test_from_api_repr_complete(self):
         from google.api_core.iam import OWNER_ROLE, EDITOR_ROLE, VIEWER_ROLE