Repository: googleapis/python-bigquery

Issue:
Allow setting of api_endpoint via environment variable like google-cloud-storage
Thanks for stopping by to let us know something could be better!

**PLEASE READ**: If you have a support contract with Google, please create an issue in the [support console](https://cloud.google.com/support/) instead of filing on GitHub. This will ensure a timely response.

 **Is your feature request related to a problem? Please describe.**
I'd like a way to have a bigquery emulator running as a local docker container which I can use for local integration testing.

 **Describe the solution you'd like**
I'm able to achieve something similar with GCS using https://github.com/oittaa/gcp-storage-emulator and setting the STORAGE_EMULATOR_HOST environment variable which is read during the storage Client constructor and used to set the api_endpoint in the client_info see [here](https://github.com/googleapis/python-storage/blob/c7bf615909a04f3bab3efb1047a9f4ba659bba19/google/cloud/storage/_helpers.py#L66) and [here](https://github.com/googleapis/python-storage/blob/c7bf615909a04f3bab3efb1047a9f4ba659bba19/google/cloud/storage/client.py#L133).   I've found I can construct a BQ Client using something like `bigquery.Client(client_options={"api_endpoint": "http://localhost:8080"})` and the calls will be redirected to that host/port.  However, that requires changing code which creates clients.  Whereas with the environment variable in storage I can redirect without changing code.  I'd like this library to implement similar logic to check for the existence of a BIGQUERY_EMULATOR_HOST or some similar environment variable and use that to set the api_endpoint in the same way as the STORAGE_EMULATOR_HOST does so that I could then stub out a mock BigQuery emulator to use in testing.

 **Describe alternatives you've considered**
As described above I've looked at changing any constructors in my code base to include the api_endpoint and that is an okay work around, but it seems like having a consistent way to specify an alternate via an environment variable would be better.

 **Additional context**
Hopefully the links above are sufficient extra context.


Discussion:

Resolving patch:
diff --git a/google/cloud/bigquery/_helpers.py b/google/cloud/bigquery/_helpers.py
--- a/google/cloud/bigquery/_helpers.py
+++ b/google/cloud/bigquery/_helpers.py
@@ -19,6 +19,7 @@
 import decimal
 import math
 import re
+import os
 from typing import Optional, Union
 
 from dateutil import relativedelta
@@ -28,8 +29,8 @@
 from google.cloud._helpers import _RFC3339_MICROS
 from google.cloud._helpers import _RFC3339_NO_FRACTION
 from google.cloud._helpers import _to_bytes
-import packaging.version
 
+import packaging.version
 
 _RFC3339_MICROS_NO_ZULU = "%Y-%m-%dT%H:%M:%S.%f"
 _TIMEONLY_WO_MICROS = "%H:%M:%S"
@@ -51,6 +52,16 @@
 
 _BQ_STORAGE_OPTIONAL_READ_SESSION_VERSION = packaging.version.Version("2.6.0")
 
+BIGQUERY_EMULATOR_HOST = "BIGQUERY_EMULATOR_HOST"
+"""Environment variable defining host for emulator."""
+
+_DEFAULT_HOST = "https://bigquery.googleapis.com"
+"""Default host for JSON API."""
+
+
+def _get_bigquery_host():
+    return os.environ.get(BIGQUERY_EMULATOR_HOST, _DEFAULT_HOST)
+
 
 class BQStorageVersions:
     """Version comparisons for google-cloud-bigqueyr-storage package."""
diff --git a/google/cloud/bigquery/client.py b/google/cloud/bigquery/client.py
--- a/google/cloud/bigquery/client.py
+++ b/google/cloud/bigquery/client.py
@@ -56,7 +56,6 @@
 import google.cloud._helpers  # type: ignore
 from google.cloud import exceptions  # pytype: disable=import-error
 from google.cloud.client import ClientWithProject  # type: ignore  # pytype: disable=import-error
-
 from google.cloud.bigquery_storage_v1.services.big_query_read.client import (
     DEFAULT_CLIENT_INFO as DEFAULT_BQSTORAGE_CLIENT_INFO,
 )
@@ -67,6 +66,8 @@
 from google.cloud.bigquery._helpers import _record_field_to_json
 from google.cloud.bigquery._helpers import _str_or_none
 from google.cloud.bigquery._helpers import _verify_job_config_type
+from google.cloud.bigquery._helpers import _get_bigquery_host
+from google.cloud.bigquery._helpers import _DEFAULT_HOST
 from google.cloud.bigquery._http import Connection
 from google.cloud.bigquery import _pandas_helpers
 from google.cloud.bigquery.dataset import Dataset
@@ -230,6 +231,8 @@ def __init__(
         )
 
         kw_args = {"client_info": client_info}
+        bq_host = _get_bigquery_host()
+        kw_args["api_endpoint"] = bq_host if bq_host != _DEFAULT_HOST else None
         if client_options:
             if type(client_options) == dict:
                 client_options = google.api_core.client_options.from_dict(


Test patch:
diff --git a/tests/unit/test__helpers.py b/tests/unit/test__helpers.py
--- a/tests/unit/test__helpers.py
+++ b/tests/unit/test__helpers.py
@@ -1288,3 +1288,29 @@ def test_decimal_as_float_api_repr():
         "parameterValue": {"value": 42.0},
         "name": "x",
     }
+
+
+class Test__get_bigquery_host(unittest.TestCase):
+    @staticmethod
+    def _call_fut():
+        from google.cloud.bigquery._helpers import _get_bigquery_host
+
+        return _get_bigquery_host()
+
+    def test_wo_env_var(self):
+        from google.cloud.bigquery._helpers import _DEFAULT_HOST
+
+        with mock.patch("os.environ", {}):
+            host = self._call_fut()
+
+        self.assertEqual(host, _DEFAULT_HOST)
+
+    def test_w_env_var(self):
+        from google.cloud.bigquery._helpers import BIGQUERY_EMULATOR_HOST
+
+        HOST = "https://api.example.com"
+
+        with mock.patch("os.environ", {BIGQUERY_EMULATOR_HOST: HOST}):
+            host = self._call_fut()
+
+        self.assertEqual(host, HOST)