Repository: googleapis/python-bigquery

Issue:
feat: add builder / copy method to `google.cloud.bigquery.query.ScalarQueryParameterType` that makes a copy with a new name
 **Is your feature request related to a problem? Please describe.**

When constructing a [StructQueryParameterType](https://github.com/googleapis/python-bigquery/blob/6a48e80bc7d347f381b181f4cf81fef105d0ad0d/google/cloud/bigquery/query.py#L195), it is necessary to pass in types for sub-fields. We have created a [SqlParameterScalarTypes](https://github.com/googleapis/python-bigquery/blob/6a48e80bc7d347f381b181f4cf81fef105d0ad0d/google/cloud/bigquery/enums.py#L219-L238) enum to make this easier, but often times a struct's sub-fields are named. In this case, the enum values are less useful.

 **Describe the solution you'd like**

It'd be helpful to construct a type for the struct sub-fields by using the enum classes. Perhaps

```
google.cloud.bigquery.enums.SqlParameterScalarTypes.STRING.with_name("first_name")
```

could create a copy of `google.cloud.bigquery.enums.SqlParameterScalarTypes.STRING`, but with the name field populated?

 **Describe alternatives you've considered**

Possible that the `SqlParameterScalarTypes` enum was unnecessary, but it's part of our public interface now.

 **Additional context**

https://github.com/googleapis/python-bigquery/issues/633#issuecomment-830170608


Discussion:

Patch:
diff --git a/google/cloud/bigquery/query.py b/google/cloud/bigquery/query.py
index 3751eb12..d1e9a45a 100644
--- a/google/cloud/bigquery/query.py
+++ b/google/cloud/bigquery/query.py
@@ -16,6 +16,7 @@
 
 from collections import OrderedDict
 import copy
+from typing import Union
 
 from google.cloud.bigquery.table import _parse_schema_resource
 from google.cloud.bigquery._helpers import _rows_from_json
@@ -119,6 +120,20 @@ class ScalarQueryParameterType(_AbstractQueryParameterType):
         # attributes in the API representation when needed. Here we omit them.
         return {"type": self._type}
 
+    def with_name(self, new_name: Union[str, None]):
+        """Return a copy of the instance with ``name`` set to ``new_name``.
+
+        Args:
+            name (Union[str, None]):
+                The new name of the query parameter type. If ``None``, the existing
+                name is cleared.
+
+        Returns:
+            google.cloud.bigquery.query.ScalarQueryParameterType:
+               A new instance with updated name.
+        """
+        return type(self)(self._type, name=new_name, description=self.description)
+
     def __repr__(self):
         name = f", name={self.name!r}" if self.name is not None else ""
         description = (


Test patch:
diff --git a/samples/geography/requirements-test.txt b/samples/geography/requirements-test.txt
index 299d90b6..b0cf7672 100644
--- a/samples/geography/requirements-test.txt
+++ b/samples/geography/requirements-test.txt
@@ -1,2 +1,2 @@
-pytest==6.2.3
+pytest==6.2.4
 mock==4.0.3
diff --git a/samples/snippets/requirements-test.txt b/samples/snippets/requirements-test.txt
index 299d90b6..b0cf7672 100644
--- a/samples/snippets/requirements-test.txt
+++ b/samples/snippets/requirements-test.txt
@@ -1,2 +1,2 @@
-pytest==6.2.3
+pytest==6.2.4
 mock==4.0.3
diff --git a/tests/unit/test_query.py b/tests/unit/test_query.py
index c8be2911..90fc30b2 100644
--- a/tests/unit/test_query.py
+++ b/tests/unit/test_query.py
@@ -98,6 +98,26 @@ class Test_ScalarQueryParameterType(unittest.TestCase):
             "ScalarQueryParameterType('BYTES', name='foo', description='this is foo')",
         )
 
+    def test_with_name_returns_copy_w_changed_name(self):
+        param_type = self._make_one("BOOLEAN", name=None, description="Some checkbox.")
+        modified_type = param_type.with_name("allow_emails")
+
+        self.assertIsNot(modified_type, param_type)  # Result is a copy.
+        self.assertEqual(modified_type.name, "allow_emails")
+
+        # The rest of the The rest of the fields should have been preserved.
+        self.assertEqual(modified_type._type, param_type._type)
+        self.assertEqual(modified_type.description, param_type.description)
+
+    def test_with_name_clearing_the_value(self):
+        param_type = self._make_one(
+            "BOOLEAN", name="allow_emails", description="Some checkbox."
+        )
+        modified_type = param_type.with_name(None)
+
+        self.assertIsNone(modified_type.name)
+        self.assertEqual(param_type.name, "allow_emails")  # original unchanged
+
 
 class Test_ArrayQueryParameterType(unittest.TestCase):
     @staticmethod