Repository: open-telemetry/opentelemetry-python-contrib

Issue:
OT Trace propagator fails with exception when no headers are present
**Describe your environment**
Latest version (main branch) of both opentelemetry-python and opentelemetry-python-contrib

**Steps to reproduce**
```python
from opentelemetry.propagators.ot_trace import OTTracePropagator
from opentelemetry.propagators.textmap import DictGetter

getter = DictGetter()
carrier = {}

propagator = OTTracePropagator()
propagator.extract(getter, carrier)
```
results in:
```
Traceback (most recent call last):
  File "repro.py", line 8, in <module>
    propagator.extract(getter, {})
  File "(...)/opentelemetry-python-contrib/propagator/opentelemetry-propagator-ot-trace/src/opentelemetry/propagators/ot_trace/__init__.py", line 76, in extract
    and _valid_extract_traceid.fullmatch(traceid) is not None
TypeError: expected string or bytes-like object
```
**What is the expected behavior?**
Propagator extraction returns an unmodified context due to lack of relevant headers in the carrier.
> If a value can not be parsed from the carrier, for a cross-cutting concern, the implementation MUST NOT throw an exception and MUST NOT store a new value in the Context, in order to preserve any previously existing valid value.

_from [OTel specification](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/context/api-propagators.md#extract)_

**What is the actual behavior?**
Propagator extraction fails with an exception when no headers are present in the carrier.

**Additional context**
N/A



Discussion:

Resolving patch:
diff --git a/propagator/opentelemetry-propagator-ot-trace/src/opentelemetry/propagators/ot_trace/__init__.py b/propagator/opentelemetry-propagator-ot-trace/src/opentelemetry/propagators/ot_trace/__init__.py
--- a/propagator/opentelemetry-propagator-ot-trace/src/opentelemetry/propagators/ot_trace/__init__.py
+++ b/propagator/opentelemetry-propagator-ot-trace/src/opentelemetry/propagators/ot_trace/__init__.py
@@ -13,7 +13,7 @@
 # limitations under the License.
 
 from re import compile as re_compile
-from typing import Iterable, Optional
+from typing import Any, Iterable, Optional
 
 from opentelemetry.baggage import get_all, set_baggage
 from opentelemetry.context import Context
@@ -55,10 +55,12 @@ def extract(
     ) -> Context:
 
         traceid = _extract_first_element(
-            getter.get(carrier, OT_TRACE_ID_HEADER)
+            getter.get(carrier, OT_TRACE_ID_HEADER), INVALID_TRACE_ID
         )
 
-        spanid = _extract_first_element(getter.get(carrier, OT_SPAN_ID_HEADER))
+        spanid = _extract_first_element(
+            getter.get(carrier, OT_SPAN_ID_HEADER), INVALID_SPAN_ID
+        )
 
         sampled = _extract_first_element(
             getter.get(carrier, OT_SAMPLED_HEADER)
@@ -163,8 +165,8 @@ def fields(self):
 
 
 def _extract_first_element(
-    items: Iterable[TextMapPropagatorT],
+    items: Iterable[TextMapPropagatorT], default: Any = None,
 ) -> Optional[TextMapPropagatorT]:
     if items is None:
-        return None
+        return default
     return next(iter(items), None)


Test patch:
diff --git a/propagator/opentelemetry-propagator-ot-trace/tests/test_ot_trace_propagator.py b/propagator/opentelemetry-propagator-ot-trace/tests/test_ot_trace_propagator.py
--- a/propagator/opentelemetry-propagator-ot-trace/tests/test_ot_trace_propagator.py
+++ b/propagator/opentelemetry-propagator-ot-trace/tests/test_ot_trace_propagator.py
@@ -366,3 +366,12 @@ def test_extract_baggage(self):
 
         self.assertEqual(baggage["abc"], "abc")
         self.assertEqual(baggage["def"], "def")
+
+    def test_extract_empty(self):
+        "Test extraction when no headers are present"
+
+        span_context = get_current_span(
+            self.ot_trace_propagator.extract(carrier_getter, {})
+        ).get_span_context()
+
+        self.assertEqual(span_context, INVALID_SPAN_CONTEXT)