Repository: open-telemetry/opentelemetry-python-contrib

Issue:
Support for pymemcache 2.x and 3.x
The `pymemcache` integration is currently locked to version 1.x. Current version is ~=3.5.

https://github.com/open-telemetry/opentelemetry-python-contrib/blob/0b9e96dae0bfcafa1e0b28dc695e3697fbcf2864/instrumentation/opentelemetry-instrumentation-pymemcache/src/opentelemetry/instrumentation/pymemcache/package.py#L16


Discussion:
duplicate of #842 

Resolving patch:
diff --git a/instrumentation/opentelemetry-instrumentation-pymemcache/src/opentelemetry/instrumentation/pymemcache/package.py b/instrumentation/opentelemetry-instrumentation-pymemcache/src/opentelemetry/instrumentation/pymemcache/package.py
--- a/instrumentation/opentelemetry-instrumentation-pymemcache/src/opentelemetry/instrumentation/pymemcache/package.py
+++ b/instrumentation/opentelemetry-instrumentation-pymemcache/src/opentelemetry/instrumentation/pymemcache/package.py
@@ -13,4 +13,4 @@
 # limitations under the License.
 
 
-_instruments = ("pymemcache ~= 1.3",)
+_instruments = ("pymemcache >= 1.3.5, < 4",)
diff --git a/opentelemetry-instrumentation/src/opentelemetry/instrumentation/bootstrap_gen.py b/opentelemetry-instrumentation/src/opentelemetry/instrumentation/bootstrap_gen.py
--- a/opentelemetry-instrumentation/src/opentelemetry/instrumentation/bootstrap_gen.py
+++ b/opentelemetry-instrumentation/src/opentelemetry/instrumentation/bootstrap_gen.py
@@ -93,7 +93,7 @@
         "instrumentation": "opentelemetry-instrumentation-psycopg2==0.29b0",
     },
     "pymemcache": {
-        "library": "pymemcache ~= 1.3",
+        "library": "pymemcache >= 1.3.5, < 4",
         "instrumentation": "opentelemetry-instrumentation-pymemcache==0.29b0",
     },
     "pymongo": {


Test patch:
diff --git a/instrumentation/opentelemetry-instrumentation-pymemcache/tests/test_pymemcache.py b/instrumentation/opentelemetry-instrumentation-pymemcache/tests/test_pymemcache.py
--- a/instrumentation/opentelemetry-instrumentation-pymemcache/tests/test_pymemcache.py
+++ b/instrumentation/opentelemetry-instrumentation-pymemcache/tests/test_pymemcache.py
@@ -14,6 +14,8 @@
 from unittest import mock
 
 import pymemcache
+from packaging import version as get_package_version
+from pymemcache import __version__ as pymemcache_package_version
 from pymemcache.exceptions import (
     MemcacheClientError,
     MemcacheIllegalInputError,
@@ -33,6 +35,22 @@
 TEST_HOST = "localhost"
 TEST_PORT = 117711
 
+pymemcache_version = get_package_version.parse(pymemcache_package_version)
+
+# In pymemcache versions greater than 2, set_multi, set_many and delete_multi
+# now use a batched call
+# https://github.com/pinterest/pymemcache/blob/master/ChangeLog.rst#new-in-version-200  # noqa
+pymemcache_version_lt_2 = pymemcache_version < get_package_version.parse(
+    "2.0.0"
+)
+
+# In pymemcache versions greater than 3.4.1, the stats command no
+# longer sends trailing whitespace in the command
+# https://github.com/pinterest/pymemcache/blob/master/ChangeLog.rst#new-in-version-342  # noqa
+pymemcache_version_gt_341 = pymemcache_version > get_package_version.parse(
+    "3.4.1"
+)
+
 
 class PymemcacheClientTestCase(
     TestBase
@@ -126,11 +144,16 @@ def test_set_multi_success(self):
         client = self.make_client([b"STORED\r\n"])
         # Alias for set_many, a convienance function that calls set for every key
         result = client.set_multi({b"key": b"value"}, noreply=False)
-        self.assertTrue(result)
-
         spans = self.memory_exporter.get_finished_spans()
 
-        self.check_spans(spans, 2, ["set key", "set_multi key"])
+        # In pymemcache versions greater than 2, set_multi now uses a batched call
+        # https://github.com/pinterest/pymemcache/blob/master/ChangeLog.rst#new-in-version-200  # noqa
+        if pymemcache_version_lt_2:
+            self.assertTrue(result)
+            self.check_spans(spans, 2, ["set key", "set_multi key"])
+        else:
+            assert len(result) == 0
+            self.check_spans(spans, 1, ["set_multi key"])
 
     def test_delete_not_found(self):
         client = self.make_client([b"NOT_FOUND\r\n"])
@@ -191,19 +214,30 @@ def test_delete_many_found(self):
 
         spans = self.memory_exporter.get_finished_spans()
 
-        self.check_spans(
-            spans, 3, ["add key", "delete key", "delete_many key"]
-        )
+        # In pymemcache versions greater than 2, delete_many now uses a batched call
+        # https://github.com/pinterest/pymemcache/blob/master/ChangeLog.rst#new-in-version-200  # noqa
+        if pymemcache_version_lt_2:
+            self.check_spans(
+                spans, 3, ["add key", "delete key", "delete_many key"]
+            )
+        else:
+            self.check_spans(spans, 2, ["add key", "delete_many key"])
 
     def test_set_many_success(self):
         client = self.make_client([b"STORED\r\n"])
         # a convienance function that calls set for every key
         result = client.set_many({b"key": b"value"}, noreply=False)
-        self.assertTrue(result)
 
         spans = self.memory_exporter.get_finished_spans()
 
-        self.check_spans(spans, 2, ["set key", "set_many key"])
+        # In pymemcache versions greater than 2, set_many now uses a batched call
+        # https://github.com/pinterest/pymemcache/blob/master/ChangeLog.rst#new-in-version-200  # noqa
+        if pymemcache_version_lt_2:
+            self.assertTrue(result)
+            self.check_spans(spans, 2, ["set key", "set_many key"])
+        else:
+            assert len(result) == 0
+            self.check_spans(spans, 1, ["set_many key"])
 
     def test_set_get(self):
         client = self.make_client(
@@ -243,7 +277,7 @@ def test_prepend_stored(self):
 
     def test_cas_stored(self):
         client = self.make_client([b"STORED\r\n"])
-        result = client.cas(b"key", b"value", b"cas", noreply=False)
+        result = client.cas(b"key", b"value", b"1", noreply=False)
         self.assertTrue(result)
 
         spans = self.memory_exporter.get_finished_spans()
@@ -252,7 +286,7 @@ def test_cas_stored(self):
 
     def test_cas_exists(self):
         client = self.make_client([b"EXISTS\r\n"])
-        result = client.cas(b"key", b"value", b"cas", noreply=False)
+        result = client.cas(b"key", b"value", b"1", noreply=False)
         assert result is False
 
         spans = self.memory_exporter.get_finished_spans()
@@ -261,7 +295,7 @@ def test_cas_exists(self):
 
     def test_cas_not_found(self):
         client = self.make_client([b"NOT_FOUND\r\n"])
-        result = client.cas(b"key", b"value", b"cas", noreply=False)
+        result = client.cas(b"key", b"value", b"1", noreply=False)
         assert result is None
 
         spans = self.memory_exporter.get_finished_spans()
@@ -445,7 +479,14 @@ def test_version_success(self):
     def test_stats(self):
         client = self.make_client([b"STAT fake_stats 1\r\n", b"END\r\n"])
         result = client.stats()
-        assert client.sock.send_bufs == [b"stats \r\n"]
+
+        # In pymemcache versions greater than 3.4.1, the stats command no
+        # longer sends trailing whitespace in the command
+        # https://github.com/pinterest/pymemcache/blob/master/ChangeLog.rst#new-in-version-342  # noqa
+        if pymemcache_version_gt_341:
+            assert client.sock.send_bufs == [b"stats\r\n"]
+        else:
+            assert client.sock.send_bufs == [b"stats \r\n"]
         assert result == {b"fake_stats": 1}
 
         spans = self.memory_exporter.get_finished_spans()
@@ -544,5 +585,4 @@ def test_delete_many_found(self):
         self.assertTrue(result)
 
         spans = self.memory_exporter.get_finished_spans()
-
         self.check_spans(spans, 2, ["add key", "delete key"])