Repository: googleapis/google-cloud-python

Issue:
BigQuery: query_parameters fails if None is bound as parameter
OS Type & Version: Ubuntu 19.04 x64
Python version: Python 3.7.3
Packges: latest up to this date:
```
'google-cloud-bigquery==1.18.0',
```

**Steps to reproduce**
1. Create a query, bind `None` (`NULL`) as parameter
2. Execute it
3. Call query_parameters

**Code example**
```py
from google.cloud import bigquery

client = bigquery.Client.from_service_account_json(
    <...>
)
job = client.query(
    "SELECT LOWER(@none_value)",
    job_config=bigquery.QueryJobConfig(
        query_parameters=[
            bigquery.ScalarQueryParameter('none_value', 'STRING', None)
        ]
    )
)
result = list(job.result())
query_parameters = job.query_parameters
```

**Stack trace**
```
Traceback (most recent call last):
  File "test.py", line 16, in <module>
    query_parameters = job.query_parameters
  File "/test/venv/lib/python3.7/site-packages/google/cloud/bigquery/job.py", line 2472, in query_parameters
    return self._configuration.query_parameters
  File "/test/venv/lib/python3.7/site-packages/google/cloud/bigquery/job.py", line 2200, in query_parameters
    return _from_api_repr_query_parameters(prop)
  File "/test/venv/lib/python3.7/site-packages/google/cloud/bigquery/job.py", line 1965, in _from_api_repr_query_parameters
    return [_query_param_from_api_repr(mapping) for mapping in resource]
  File "/test/venv/lib/python3.7/site-packages/google/cloud/bigquery/job.py", line 1965, in <listcomp>
    return [_query_param_from_api_repr(mapping) for mapping in resource]
  File "/test/venv/lib/python3.7/site-packages/google/cloud/bigquery/query.py", line 625, in _query_param_from_api_repr
    return klass.from_api_repr(resource)
  File "/test/venv/lib/python3.7/site-packages/google/cloud/bigquery/query.py", line 129, in from_api_repr
    value = resource["parameterValue"]["value"]
KeyError: 'parameterValue'
```

This is related to https://github.com/googleapis/google-cloud-python/issues/7309


Discussion:
I can confirm this, reproduced the issue with bigquery version `1.18.0`.

Resolving patch:
diff --git a/bigquery/google/cloud/bigquery/query.py b/bigquery/google/cloud/bigquery/query.py
--- a/bigquery/google/cloud/bigquery/query.py
+++ b/bigquery/google/cloud/bigquery/query.py
@@ -126,8 +126,15 @@ def from_api_repr(cls, resource):
         """
         name = resource.get("name")
         type_ = resource["parameterType"]["type"]
-        value = resource["parameterValue"]["value"]
-        converted = _QUERY_PARAMS_FROM_JSON[type_](value, None)
+
+        # parameterValue might not be present if JSON resource originates
+        # from the back-end - the latter omits it for None values.
+        value = resource.get("parameterValue", {}).get("value")
+        if value is not None:
+            converted = _QUERY_PARAMS_FROM_JSON[type_](value, None)
+        else:
+            converted = None
+
         return cls(name, type_, converted)
 
     def to_api_repr(self):


Test patch:
diff --git a/bigquery/tests/unit/test_query.py b/bigquery/tests/unit/test_query.py
--- a/bigquery/tests/unit/test_query.py
+++ b/bigquery/tests/unit/test_query.py
@@ -121,6 +121,15 @@ def test_from_api_repr_wo_name(self):
         self.assertEqual(param.type_, "INT64")
         self.assertEqual(param.value, 123)
 
+    def test_from_api_repr_wo_value(self):
+        # Back-end may not send back values for None params. See #9027
+        RESOURCE = {"name": "foo", "parameterType": {"type": "INT64"}}
+        klass = self._get_target_class()
+        param = klass.from_api_repr(RESOURCE)
+        self.assertEqual(param.name, "foo")
+        self.assertEqual(param.type_, "INT64")
+        self.assertIs(param.value, None)
+
     def test_to_api_repr_w_name(self):
         EXPECTED = {
             "name": "foo",