Repository: DataDog/dd-trace-py

Issue:
TypeError when patching render template with non string
https://github.com/DataDog/dd-trace-py/pull/2174 removed the check to ensure that the tag is a string and in this case the template_name = `PosixPath('filepath.json')` and fails

### Which version of dd-trace-py are you using? 
0.48.2

### Which version of pip are you using? 
20.2.1 

### How can we reproduce your problem? 

```
from pathlib import Path
from django.template.loader import render_to_string
base_definition_dir_path = Path("./jobsheets/definitions")
for path in sorted(base_definition_dir_path.glob("**/*.json")):
     definition = render_to_string(path.relative_to(base_definition_dir_path), {})
```

### What is the result that you get? 
`TypeError not expecting type '<class 'pathlib.PosixPath'>'`

### What is the result that you expected? 
no Error


Discussion:

Resolving patch:
diff --git a/ddtrace/contrib/django/patch.py b/ddtrace/contrib/django/patch.py
--- a/ddtrace/contrib/django/patch.py
+++ b/ddtrace/contrib/django/patch.py
@@ -32,6 +32,7 @@
 from ddtrace.ext import SpanTypes
 from ddtrace.ext import http
 from ddtrace.ext import sql as sqlx
+from ddtrace.internal.compat import maybe_stringify
 from ddtrace.internal.logger import get_logger
 from ddtrace.propagation.utils import from_wsgi_header
 from ddtrace.utils.formats import asbool
@@ -466,7 +467,7 @@ def traced_get_response(django, pin, func, instance, args, kwargs):
 @trace_utils.with_traced_module
 def traced_template_render(django, pin, wrapped, instance, args, kwargs):
     """Instrument django.template.base.Template.render for tracing template rendering."""
-    template_name = getattr(instance, "name", None)
+    template_name = maybe_stringify(getattr(instance, "name", None))
     if template_name:
         resource = template_name
     else:
diff --git a/ddtrace/internal/compat.py b/ddtrace/internal/compat.py
--- a/ddtrace/internal/compat.py
+++ b/ddtrace/internal/compat.py
@@ -6,6 +6,7 @@
 import threading
 from typing import Any
 from typing import AnyStr
+from typing import Optional
 from typing import Text
 from typing import Union
 
@@ -22,6 +23,7 @@
     "urlencode",
     "parse",
     "reraise",
+    "maybe_stringify",
 ]
 
 PYTHON_VERSION_INFO = sys.version_info
@@ -242,3 +244,10 @@ def ensure_pep562(module_name):
     def ensure_pep562(module_name):
         # type: (str) -> None
         pass
+
+
+def maybe_stringify(obj):
+    # type: (Any) -> Optional[str]
+    if obj is not None:
+        return stringify(obj)
+    return None


Test patch:
diff --git a/tests/contrib/django/test_django.py b/tests/contrib/django/test_django.py
--- a/tests/contrib/django/test_django.py
+++ b/tests/contrib/django/test_django.py
@@ -23,6 +23,7 @@
 from ddtrace.ext import errors
 from ddtrace.ext import http
 from ddtrace.ext.priority import USER_KEEP
+from ddtrace.internal.compat import PY2
 from ddtrace.internal.compat import binary_type
 from ddtrace.internal.compat import string_type
 from ddtrace.propagation.http import HTTP_HEADER_PARENT_ID
@@ -1304,6 +1305,26 @@ def test_template(test_spans):
     assert span.get_tag("django.template.name") == "my-template"
 
 
+@pytest.mark.skipif(PY2, reason="pathlib is not part of the Python 2 stdlib")
+def test_template_name(test_spans):
+    from pathlib import PosixPath
+
+    # prepare a base template using the default engine
+    template = django.template.Template("Hello {{name}}!")
+
+    # DEV: template.name can be an instance of PosixPath (see
+    # https://github.com/DataDog/dd-trace-py/issues/2418)
+    template.name = PosixPath("/my-template")
+    template.render(django.template.Context({"name": "Django"}))
+
+    spans = test_spans.get_spans()
+    assert len(spans) == 1
+
+    (span,) = spans
+    assert span.get_tag("django.template.name") == "/my-template"
+    assert span.resource == "/my-template"
+
+
 """
 OpenTracing tests
 """
diff --git a/tests/tracer/test_compat.py b/tests/tracer/test_compat.py
--- a/tests/tracer/test_compat.py
+++ b/tests/tracer/test_compat.py
@@ -2,13 +2,16 @@
 # Define source file encoding to support raw unicode characters in Python 2
 import sys
 
-# Third party
+from hypothesis import given
+from hypothesis import settings
+import hypothesis.strategies as st
 import pytest
+import six
 
-# Project
 from ddtrace.internal.compat import PY3
 from ddtrace.internal.compat import get_connection_response
 from ddtrace.internal.compat import is_integer
+from ddtrace.internal.compat import maybe_stringify
 from ddtrace.internal.compat import reraise
 from ddtrace.internal.compat import to_unicode
 
@@ -132,3 +135,21 @@ def test_pep562():
     from tests.pep562_test import whatever
 
     assert whatever == "good module attribute"
+
+
+@given(
+    obj=st.one_of(
+        st.none(),
+        st.booleans(),
+        st.text(),
+        st.complex_numbers(),
+        st.dates(),
+        st.integers(),
+        st.decimals(),
+        st.lists(st.text()),
+        st.dictionaries(st.text(), st.text()),
+    )
+)
+@settings(max_examples=100)
+def test_maybe_stringify(obj):
+    assert type(maybe_stringify(obj)) is (obj is not None and six.text_type or type(None))