Repository: DataDog/dd-trace-py

Issue:
Django patch breaks cache 
<!--
Thanks for taking the time for reporting an issue!

Before reporting an issue on dd-trace-py, please be sure to provide all
necessary information.

If you're hitting a bug, make sure that you're using the latest version of this
library.
-->

### Summary of problem
Django cache patch is evaluating the cache result, which might not be valid.

https://github.com/DataDog/dd-trace-py/blob/1.11/ddtrace/contrib/django/patch.py#L182

That line is calling `bool` on `result`, which e.g. is not defined for a pandas DataFrame

### Which version of dd-trace-py are you using?
1.11

### Which version of pip are you using?
pip 22.0.4

### Which libraries and their versions are you using?
django 3.2.18

<details>
  <summary>`pip freeze`</summary>
	```
	asgiref==3.6.0
	attrs==22.2.0
	bytecode==0.14.0
	cattrs==22.2.0
	ddsketch==2.0.4
	ddtrace==1.11.0
	Deprecated==1.2.13
	Django==4.2
	envier==0.4.0
	importlib-metadata==6.0.1
	jsonschema==4.17.3
	numpy==1.24.2
	opentelemetry-api==1.17.0
	packaging==23.0
	pandas==2.0.0
	protobuf==4.22.1
	pyrsistent==0.19.3
	python-dateutil==2.8.2
	pytz==2023.3
	six==1.16.0
	sqlparse==0.4.3
	tenacity==8.2.2
	typing_extensions==4.5.0
	tzdata==2023.3
	wrapt==1.15.0
	xmltodict==0.13.0
	zipp==3.15.0
	```
</details>

### How can we reproduce your problem?
```python

import os
from ddtrace import patch_all

patch_all()

os.environ.setdefault("DJANGO_SETTINGS_MODULE", "settings")

import django
django.setup()

class NoBool:
    def __bool__(self):
        raise NotImplementedError

from django.core.cache import cache
cache.set(1, NoBool())
cache.get(1)


```


### What is the result that you get?
On the example:
```
NotImplementedError
```
If you are using pandas DataFrame:
```
ValueError: The truth value of a DataFrame is ambiguous. Use a.empty, a.bool(), a.item(), a.any() or a.all().
```

### What is the result that you expected?
The saved dataframe without trying to evaluate it



Discussion:
I have the same issue. It looks like it was introduced in v1.10.0: https://github.com/DataDog/dd-trace-py/blob/v1.10.0/ddtrace/contrib/django/patch.py#L180 , and it appears that v1.9.5 is the latest stable version without the bug. 
This change was introduced in https://github.com/DataDog/dd-trace-py/pull/5052. 

@wconti27, can you look into this?

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
@@ -190,8 +190,9 @@ def traced_cache(django, pin, func, instance, args, kwargs):
         elif command_name == "get":
             # if valid result and check for special case for Django~3.0 that returns an empty Sentinel object as
             # missing key
-            if result and result != getattr(instance, "_missing_key", None):
+            if result is not None and result != getattr(instance, "_missing_key", None):
                 span.set_metric(db.ROWCOUNT, 1)
+            # else result is invalid or None, set row count to 0
             else:
                 span.set_metric(db.ROWCOUNT, 0)
         return result


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
@@ -771,6 +771,46 @@ def test_cache_get_rowcount_missing_key(test_spans):
     assert_dict_issuperset(span.get_metrics(), {"db.row_count": 0})
 
 
+class NoBool:
+    def __bool__(self):
+        raise NotImplementedError
+
+
+def test_cache_get_rowcount_empty_key(test_spans):
+    # get the default cache
+    cache = django.core.cache.caches["default"]
+    cache.set(1, NoBool())
+
+    result = cache.get(1)
+
+    assert isinstance(result, NoBool) is True
+
+    spans = test_spans.get_spans()
+    assert len(spans) == 2
+
+    get_span = spans[1]
+    assert get_span.service == "django"
+    assert get_span.resource == "django.core.cache.backends.locmem.get"
+
+    assert_dict_issuperset(get_span.get_metrics(), {"db.row_count": 1})
+
+
+def test_cache_get_rowcount_missing_key_with_default(test_spans):
+    # get the default cache
+    cache = django.core.cache.caches["default"]
+
+    # This is the diff with `test_cache_get_rowcount_missing_key`,
+    # we are setting a default value to be returned in case of a cache miss
+    cache.get("missing_key", default="default_value")
+
+    spans = test_spans.get_spans()
+    assert len(spans) == 1
+    span = spans[0]
+    assert span.service == "django"
+    assert span.resource == "django.core.cache.backends.locmem.get"
+    assert_dict_issuperset(span.get_metrics(), {"db.row_count": 1})
+
+
 def test_cache_get_unicode(test_spans):
     # get the default cache
     cache = django.core.cache.caches["default"]