This is an automated email from the ASF dual-hosted git repository.

wusheng pushed a commit to branch feat/urllib3-v2-plugin
in repository https://gitbox.apache.org/repos/asf/skywalking-python.git

commit a64c4e951cf8d27b22b46b19097a4f8170040814
Author: Wu Sheng <[email protected]>
AuthorDate: Sun Apr 12 08:33:54 2026 +0800

    feat(plugin): add urllib3 2.x support for Python 3.12+
    
    urllib3 2.x removed `urllib3.request.RequestMethods` which the existing
    plugin hooks. Add a new plugin `sw_urllib3_v2` that hooks
    `PoolManager.request` directly (the 2.x entry point).
    
    Auto-detection logic:
    - sw_urllib3: tries `from urllib3.request import RequestMethods`.
      Succeeds on 1.x, fails on 2.x (skipped).
    - sw_urllib3_v2: checks if `RequestMethods` exists.
      If yes (1.x), returns early. If no (2.x), hooks PoolManager.
    
    Both plugins share the same test directory. The test merges
    version vectors from both plugins' support_matrix.
    
    Verified locally on Python 3.13:
    - urllib3==2.3 PASSED (span validation)
    - urllib3==2.0 PASSED (span validation)
    
    Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
---
 docs/en/setup/Plugins.md                           |  3 +++
 skywalking/plugins/sw_urllib3.py                   |  4 ++--
 .../plugins/{sw_urllib3.py => sw_urllib3_v2.py}    | 26 +++++++++++++++-------
 tests/plugin/http/sw_urllib3/test_urllib3.py       |  9 ++++++--
 4 files changed, 30 insertions(+), 12 deletions(-)

diff --git a/docs/en/setup/Plugins.md b/docs/en/setup/Plugins.md
index 0aa38a3..baea214 100644
--- a/docs/en/setup/Plugins.md
+++ b/docs/en/setup/Plugins.md
@@ -47,6 +47,7 @@ or a limitation of SkyWalking auto-instrumentation (welcome 
to contribute!)
 | [sanic](https://sanic.readthedocs.io/en/latest) | Python >=3.10 - NOT 
SUPPORTED YET; Python >=3.7 - ['20.12'];  | `sw_sanic` |
 | [tornado](https://www.tornadoweb.org) | Python >=3.14 - ['6.4']; Python 
>=3.10 - ['6.0', '6.1'];  | `sw_tornado` |
 | [urllib3](https://urllib3.readthedocs.io/en/latest/) | Python >=3.12 - NOT 
SUPPORTED YET; Python >=3.10 - ['1.26', '1.25'];  | `sw_urllib3` |
+| [urllib3](https://urllib3.readthedocs.io/en/latest/) | Python >=3.12 - 
['2.3', '2.0'];  | `sw_urllib3_v2` |
 | [urllib_request](https://docs.python.org/3/library/urllib.request.html) | 
Python >=3.7 - ['*'];  | `sw_urllib_request` |
 | [websockets](https://websockets.readthedocs.io) | Python >=3.7 - ['10.3', 
'10.4'];  | `sw_websockets` |
 ### Notes
@@ -58,6 +59,8 @@ Hug is believed to be abandoned project, use this plugin with 
a bit more caution
 Instead of Hug, plugin test should move to test actual Falcon.
 - The Neo4j plugin integrates neo4j python driver 5.x.x versions which
 support both Neo4j 5 and 4.4 DBMS.
+- urllib3 1.x plugin. For urllib3 2.x, see sw_urllib3_v2.
+- urllib3 2.x plugin. For urllib3 1.x, see sw_urllib3.
 - The websocket instrumentation only traces client side connection handshake,
 the actual message exchange (send/recv) is not traced since injecting headers 
to socket message
 body is the only way to propagate the trace context, which requires 
customization of message structure
diff --git a/skywalking/plugins/sw_urllib3.py b/skywalking/plugins/sw_urllib3.py
index ae57b9c..56f7f73 100644
--- a/skywalking/plugins/sw_urllib3.py
+++ b/skywalking/plugins/sw_urllib3.py
@@ -23,11 +23,11 @@ from skywalking.trace.tags import TagHttpMethod, 
TagHttpURL, TagHttpStatusCode
 link_vector = ['https://urllib3.readthedocs.io/en/latest/']
 support_matrix = {
     'urllib3': {
-        '>=3.12': [],  # urllib3 2.x removed urllib3.request.RequestMethods, 
plugin needs adaptation
+        '>=3.12': [],  # urllib3 1.x can't install on 3.12+, see sw_urllib3_v2 
for 2.x
         '>=3.10': ['1.26', '1.25'],
     }
 }
-note = """"""
+note = """urllib3 1.x plugin. For urllib3 2.x, see sw_urllib3_v2."""
 
 
 def install():
diff --git a/skywalking/plugins/sw_urllib3.py 
b/skywalking/plugins/sw_urllib3_v2.py
similarity index 68%
copy from skywalking/plugins/sw_urllib3.py
copy to skywalking/plugins/sw_urllib3_v2.py
index ae57b9c..1e8e3e0 100644
--- a/skywalking/plugins/sw_urllib3.py
+++ b/skywalking/plugins/sw_urllib3_v2.py
@@ -23,19 +23,27 @@ from skywalking.trace.tags import TagHttpMethod, 
TagHttpURL, TagHttpStatusCode
 link_vector = ['https://urllib3.readthedocs.io/en/latest/']
 support_matrix = {
     'urllib3': {
-        '>=3.12': [],  # urllib3 2.x removed urllib3.request.RequestMethods, 
plugin needs adaptation
-        '>=3.10': ['1.26', '1.25'],
+        '>=3.12': ['2.3', '2.0'],
     }
 }
-note = """"""
+note = """urllib3 2.x plugin. For urllib3 1.x, see sw_urllib3."""
 
 
 def install():
-    from urllib3.request import RequestMethods
+    from urllib3 import PoolManager
 
-    _request = RequestMethods.request
+    # urllib3 2.x removed RequestMethods base class;
+    # PoolManager.request is the direct entry point.
+    # Guard: if RequestMethods still exists, this is urllib3 1.x — let 
sw_urllib3 handle it.
+    try:
+        from urllib3.request import RequestMethods  # noqa: F401
+        return  # urllib3 1.x detected, skip — sw_urllib3 handles it
+    except ImportError:
+        pass  # urllib3 2.x, proceed
 
-    def _sw_request(this: RequestMethods, method, url, fields=None, 
headers=None, **urlopen_kw):
+    _request = PoolManager.request
+
+    def _sw_request(this: PoolManager, method, url, body=None, fields=None, 
headers=None, json=None, **urlopen_kw):
         from skywalking.utils.filter import sw_urlparse
 
         url_param = sw_urlparse(url)
@@ -50,13 +58,15 @@ def install():
 
             if headers is None:
                 headers = {}
+            else:
+                headers = dict(headers)
             for item in carrier:
                 headers[item.key] = item.val
 
             span.tag(TagHttpMethod(method.upper()))
             span.tag(TagHttpURL(url_param.geturl()))
 
-            res = _request(this, method, url, fields=fields, headers=headers, 
**urlopen_kw)
+            res = _request(this, method, url, body=body, fields=fields, 
headers=headers, json=json, **urlopen_kw)
 
             span.tag(TagHttpStatusCode(res.status))
             if res.status >= 400:
@@ -64,4 +74,4 @@ def install():
 
             return res
 
-    RequestMethods.request = _sw_request
+    PoolManager.request = _sw_request
diff --git a/tests/plugin/http/sw_urllib3/test_urllib3.py 
b/tests/plugin/http/sw_urllib3/test_urllib3.py
index 082cd33..2fff31d 100644
--- a/tests/plugin/http/sw_urllib3/test_urllib3.py
+++ b/tests/plugin/http/sw_urllib3/test_urllib3.py
@@ -19,10 +19,15 @@ from typing import Callable
 import pytest
 import requests
 
-from skywalking.plugins.sw_urllib3 import support_matrix
+from skywalking.plugins.sw_urllib3 import support_matrix as v1_matrix
+from skywalking.plugins.sw_urllib3_v2 import support_matrix as v2_matrix
 from tests.orchestrator import get_test_vector
 from tests.plugin.base import TestPluginBase
 
+# Merge v1 and v2 test vectors — get_test_vector returns versions for the 
current Python
+_versions = (get_test_vector(lib_name='urllib3', support_matrix=v1_matrix)
+             + get_test_vector(lib_name='urllib3', support_matrix=v2_matrix))
+
 
 @pytest.fixture
 def prepare():
@@ -31,6 +36,6 @@ def prepare():
 
 
 class TestPlugin(TestPluginBase):
-    @pytest.mark.parametrize('version', get_test_vector(lib_name='urllib3', 
support_matrix=support_matrix))
+    @pytest.mark.parametrize('version', _versions)
     def test_plugin(self, docker_compose, version):
         self.validate()

Reply via email to