Repository: googleapis/google-cloud-python

Issue:
BigQuery: raise a `TypeError` if a dictionary is passed to `insert_rows_json`
 **Is your feature request related to a problem? Please describe.**

If I want to only insert a single row at a time into a table, it's easy to accidentally try something like:

```python
json_row = {"col1": "hello", "col2": "world"}
errors = client.insert_rows_json(
    table,
    json_row
)
```

This results in a `400 BadRequest` error from the API, because it expects a list of rows, not a single row.

 **Describe the solution you'd like**

It's difficult to debug this situation from the API response, so it'd be better if we raised a client-side error for passing in the wrong type for `json_rows`.

 **Describe alternatives you've considered**

Leave as-is and request a better server-side message. This may be difficult to do, as the error happens at a level above BigQuery, which translates JSON to Protobuf for internal use.

 **Additional context**

This issue was encountered by a customer engineer, and it took me a bit of debugging to figure out the actual issue. I expect other customers will encounter this problem as well.


Discussion:

Resolving patch:
diff --git a/bigquery/google/cloud/bigquery/client.py b/bigquery/google/cloud/bigquery/client.py
--- a/bigquery/google/cloud/bigquery/client.py
+++ b/bigquery/google/cloud/bigquery/client.py
@@ -2506,6 +2506,8 @@ def insert_rows_json(
                 identifies the row, and the "errors" key contains a list of
                 the mappings describing one or more problems with the row.
         """
+        if not isinstance(json_rows, collections_abc.Sequence):
+            raise TypeError("json_rows argument should be a sequence of dicts")
         # Convert table to just a reference because unlike insert_rows,
         # insert_rows_json doesn't need the table schema. It's not doing any
         # type conversions.


Test patch:
diff --git a/bigquery/tests/unit/test_client.py b/bigquery/tests/unit/test_client.py
--- a/bigquery/tests/unit/test_client.py
+++ b/bigquery/tests/unit/test_client.py
@@ -5384,6 +5384,31 @@ def test_insert_rows_json_w_explicit_none_insert_ids(self):
             timeout=None,
         )
 
+    def test_insert_rows_w_wrong_arg(self):
+        from google.cloud.bigquery.dataset import DatasetReference
+        from google.cloud.bigquery.schema import SchemaField
+        from google.cloud.bigquery.table import Table
+
+        PROJECT = "PROJECT"
+        DS_ID = "DS_ID"
+        TABLE_ID = "TABLE_ID"
+        ROW = {"full_name": "Bhettye Rhubble", "age": "27", "joined": None}
+
+        creds = _make_credentials()
+        client = self._make_one(project=PROJECT, credentials=creds, _http=object())
+        client._connection = make_connection({})
+
+        table_ref = DatasetReference(PROJECT, DS_ID).table(TABLE_ID)
+        schema = [
+            SchemaField("full_name", "STRING", mode="REQUIRED"),
+            SchemaField("age", "INTEGER", mode="REQUIRED"),
+            SchemaField("joined", "TIMESTAMP", mode="NULLABLE"),
+        ]
+        table = Table(table_ref, schema=schema)
+
+        with self.assertRaises(TypeError):
+            client.insert_rows_json(table, ROW)
+
     def test_list_partitions(self):
         from google.cloud.bigquery.table import Table