Repository: googleapis/python-bigquery

Issue:
Bump minimum OpenTelemetry version to support type checks
See the following comment for details: https://github.com/googleapis/python-bigquery/pull/1036#discussion_r739787329

If confirmed, we should bump to at least `opentelemetry-*==1.1.0`, and adjust our OpenTelemetry logic to the changes in the library API.


Discussion:
Good discovery. Bummer there were breaking changes.

Looks like Spanner has already updated, so we'll need this for compatibility with them as well. https://github.com/googleapis/python-spanner/pull/358
I see they bumped it to `>= 1.1.0`, thus we should probably just do the same.
Switching to type bug, since I believe our client will break if installed in the same environment as a Spanner client (and opentelemetry is installed)

Resolving patch:
diff --git a/google/cloud/bigquery/opentelemetry_tracing.py b/google/cloud/bigquery/opentelemetry_tracing.py
--- a/google/cloud/bigquery/opentelemetry_tracing.py
+++ b/google/cloud/bigquery/opentelemetry_tracing.py
@@ -19,7 +19,7 @@
 logger = logging.getLogger(__name__)
 try:
     from opentelemetry import trace
-    from opentelemetry.instrumentation.utils import http_status_to_canonical_code
+    from opentelemetry.instrumentation.utils import http_status_to_status_code
     from opentelemetry.trace.status import Status
 
     HAS_OPENTELEMETRY = True
@@ -65,9 +65,10 @@ def create_span(name, attributes=None, client=None, job_ref=None):
         if not _warned_telemetry:
             logger.debug(
                 "This service is instrumented using OpenTelemetry. "
-                "OpenTelemetry could not be imported; please "
-                "add opentelemetry-api and opentelemetry-instrumentation "
-                "packages in order to get BigQuery Tracing data."
+                "OpenTelemetry or one of its components could not be imported; "
+                "please add compatible versions of opentelemetry-api and "
+                "opentelemetry-instrumentation packages in order to get BigQuery "
+                "Tracing data."
             )
             _warned_telemetry = True
 
@@ -81,7 +82,7 @@ def create_span(name, attributes=None, client=None, job_ref=None):
             yield span
         except GoogleAPICallError as error:
             if error.code is not None:
-                span.set_status(Status(http_status_to_canonical_code(error.code)))
+                span.set_status(Status(http_status_to_status_code(error.code)))
             raise
 
 
diff --git a/setup.py b/setup.py
--- a/setup.py
+++ b/setup.py
@@ -63,9 +63,9 @@
     "bignumeric_type": pyarrow_dep,
     "tqdm": ["tqdm >= 4.7.4, <5.0.0dev"],
     "opentelemetry": [
-        "opentelemetry-api >= 0.11b0",
-        "opentelemetry-sdk >= 0.11b0",
-        "opentelemetry-instrumentation >= 0.11b0",
+        "opentelemetry-api >= 1.1.0",
+        "opentelemetry-sdk >= 1.1.0",
+        "opentelemetry-instrumentation >= 0.20b0",
     ],
 }
 


Test patch:
diff --git a/testing/constraints-3.6.txt b/testing/constraints-3.6.txt
--- a/testing/constraints-3.6.txt
+++ b/testing/constraints-3.6.txt
@@ -11,9 +11,9 @@ google-cloud-bigquery-storage==2.0.0
 google-cloud-core==1.4.1
 google-resumable-media==0.6.0
 grpcio==1.38.1
-opentelemetry-api==0.11b0
-opentelemetry-instrumentation==0.11b0
-opentelemetry-sdk==0.11b0
+opentelemetry-api==1.1.0
+opentelemetry-instrumentation==0.20b0
+opentelemetry-sdk==1.1.0
 pandas==0.24.2
 proto-plus==1.10.0
 protobuf==3.12.0
diff --git a/tests/unit/test_client.py b/tests/unit/test_client.py
--- a/tests/unit/test_client.py
+++ b/tests/unit/test_client.py
@@ -36,16 +36,24 @@
     import pandas
 except (ImportError, AttributeError):  # pragma: NO COVER
     pandas = None
+
 try:
     import opentelemetry
-    from opentelemetry import trace
-    from opentelemetry.sdk.trace import TracerProvider
-    from opentelemetry.sdk.trace.export import SimpleExportSpanProcessor
-    from opentelemetry.sdk.trace.export.in_memory_span_exporter import (
-        InMemorySpanExporter,
-    )
-except (ImportError, AttributeError):  # pragma: NO COVER
+except ImportError:
     opentelemetry = None
+
+if opentelemetry is not None:
+    try:
+        from opentelemetry import trace
+        from opentelemetry.sdk.trace import TracerProvider
+        from opentelemetry.sdk.trace.export import SimpleSpanProcessor
+        from opentelemetry.sdk.trace.export.in_memory_span_exporter import (
+            InMemorySpanExporter,
+        )
+    except (ImportError, AttributeError) as exc:  # pragma: NO COVER
+        msg = "Error importing from opentelemetry, is the installed version compatible?"
+        raise ImportError(msg) from exc
+
 try:
     import pyarrow
 except (ImportError, AttributeError):  # pragma: NO COVER
@@ -784,9 +792,12 @@ def test_span_status_is_set(self):
 
         tracer_provider = TracerProvider()
         memory_exporter = InMemorySpanExporter()
-        span_processor = SimpleExportSpanProcessor(memory_exporter)
+        span_processor = SimpleSpanProcessor(memory_exporter)
         tracer_provider.add_span_processor(span_processor)
-        trace.set_tracer_provider(tracer_provider)
+
+        # OpenTelemetry API >= 0.12b0 does not allow overriding the tracer once
+        # initialized, thus directly override the internal global var.
+        tracer_patcher = mock.patch.object(trace, "_TRACER_PROVIDER", tracer_provider)
 
         creds = _make_credentials()
         client = self._make_one(project=self.PROJECT, credentials=creds)
@@ -797,7 +808,7 @@ def test_span_status_is_set(self):
         full_routine_id = "test-routine-project.test_routines.minimal_routine"
         routine = Routine(full_routine_id)
 
-        with pytest.raises(google.api_core.exceptions.AlreadyExists):
+        with pytest.raises(google.api_core.exceptions.AlreadyExists), tracer_patcher:
             client.create_routine(routine)
 
         span_list = memory_exporter.get_finished_spans()
diff --git a/tests/unit/test_opentelemetry_tracing.py b/tests/unit/test_opentelemetry_tracing.py
--- a/tests/unit/test_opentelemetry_tracing.py
+++ b/tests/unit/test_opentelemetry_tracing.py
@@ -20,14 +20,21 @@
 
 try:
     import opentelemetry
-    from opentelemetry import trace
-    from opentelemetry.sdk.trace import TracerProvider
-    from opentelemetry.sdk.trace.export import SimpleExportSpanProcessor
-    from opentelemetry.sdk.trace.export.in_memory_span_exporter import (
-        InMemorySpanExporter,
-    )
 except ImportError:  # pragma: NO COVER
     opentelemetry = None
+
+if opentelemetry is not None:
+    try:
+        from opentelemetry import trace
+        from opentelemetry.sdk.trace import TracerProvider
+        from opentelemetry.sdk.trace.export import SimpleSpanProcessor
+        from opentelemetry.sdk.trace.export.in_memory_span_exporter import (
+            InMemorySpanExporter,
+        )
+    except (ImportError, AttributeError) as exc:  # pragma: NO COVER
+        msg = "Error importing from opentelemetry, is the installed version compatible?"
+        raise ImportError(msg) from exc
+
 import pytest
 
 from google.cloud.bigquery import opentelemetry_tracing
@@ -42,7 +49,7 @@ def setup():
     importlib.reload(opentelemetry_tracing)
     tracer_provider = TracerProvider()
     memory_exporter = InMemorySpanExporter()
-    span_processor = SimpleExportSpanProcessor(memory_exporter)
+    span_processor = SimpleSpanProcessor(memory_exporter)
     tracer_provider.add_span_processor(span_processor)
     trace.set_tracer_provider(tracer_provider)
     yield memory_exporter