Repository: tefra/xsdata

Issue:
Support requests sessions
WSDL connections might require SSL connections.

class SessionTransport(DefaultTransport):

    def __init__(self, session: Optional[Session] = None, *args, **kwargs):
        super().__init__(*args, **kwargs)
        if session is None:
            session = Session()
        self.session = session

    def get(self, url: str, params: Dict, headers: Dict) -> bytes:
        return self.handle_response(self.session.get(
            url, params=params, headers=headers, timeout=self.timeout,
        ))

    def post(self, url: str, data: Any, headers: Dict) -> Any:
        return self.handle_response(self.session.post(
            url, data=data, headers=headers, timeout=self.timeout,
        ))
        
  session = Session()
  session.cert = ('crt', 'key')
  

Discussion:
sonarcloud[bot]: Kudos, SonarCloud Quality Gate passed!&nbsp; &nbsp; [![Quality Gate passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/passed-16px.png 'Quality Gate passed')](https://sonarcloud.io/dashboard?id=tefra_xsdata&pullRequest=798)

[![Bug](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/bug-16px.png 'Bug')](https://sonarcloud.io/project/issues?id=tefra_xsdata&pullRequest=798&resolved=false&types=BUG) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=tefra_xsdata&pullRequest=798&resolved=false&types=BUG) [0 Bugs](https://sonarcloud.io/project/issues?id=tefra_xsdata&pullRequest=798&resolved=false&types=BUG)  
[![Vulnerability](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/vulnerability-16px.png 'Vulnerability')](https://sonarcloud.io/project/issues?id=tefra_xsdata&pullRequest=798&resolved=false&types=VULNERABILITY) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=tefra_xsdata&pullRequest=798&resolved=false&types=VULNERABILITY) [0 Vulnerabilities](https://sonarcloud.io/project/issues?id=tefra_xsdata&pullRequest=798&resolved=false&types=VULNERABILITY)  
[![Security Hotspot](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/security_hotspot-16px.png 'Security Hotspot')](https://sonarcloud.io/project/security_hotspots?id=tefra_xsdata&pullRequest=798&resolved=false&types=SECURITY_HOTSPOT) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/security_hotspots?id=tefra_xsdata&pullRequest=798&resolved=false&types=SECURITY_HOTSPOT) [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=tefra_xsdata&pullRequest=798&resolved=false&types=SECURITY_HOTSPOT)  
[![Code Smell](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/code_smell-16px.png 'Code Smell')](https://sonarcloud.io/project/issues?id=tefra_xsdata&pullRequest=798&resolved=false&types=CODE_SMELL) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=tefra_xsdata&pullRequest=798&resolved=false&types=CODE_SMELL) [0 Code Smells](https://sonarcloud.io/project/issues?id=tefra_xsdata&pullRequest=798&resolved=false&types=CODE_SMELL)

[![No Coverage information](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/CoverageChart/NoCoverageInfo-16px.png 'No Coverage information')](https://sonarcloud.io/component_measures?id=tefra_xsdata&pullRequest=798) No Coverage information  
[![0.0%](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/Duplications/3-16px.png '0.0%')](https://sonarcloud.io/component_measures?id=tefra_xsdata&pullRequest=798&metric=new_duplicated_lines_density&view=list) [0.0% Duplication](https://sonarcloud.io/component_measures?id=tefra_xsdata&pullRequest=798&metric=new_duplicated_lines_density&view=list)



Patch:
diff --git a/.gitignore b/.gitignore
index e153b964..082e58ee 100644
--- a/.gitignore
+++ b/.gitignore
@@ -108,3 +108,6 @@ venv.bak/
 
 # vscode
 .vscode/
+
+# idea runs
+.run/
diff --git a/docs/wsdl.rst b/docs/wsdl.rst
index f9675a93..4f689c9b 100644
--- a/docs/wsdl.rst
+++ b/docs/wsdl.rst
@@ -95,6 +95,16 @@ Or if you know what you are doing
     client = Client(config=config)
 
 
+Initialize a transport with a custom requests session instance.
+
+.. code-block::
+
+    from requests import Session
+
+    transport = DefaultTransport(session=Session())
+    client = Client(config=config, transport=transport)
+
+
 Performing Requests
 -------------------
 
diff --git a/xsdata/formats/dataclass/transports.py b/xsdata/formats/dataclass/transports.py
index 9fe209a1..d135f5e3 100644
--- a/xsdata/formats/dataclass/transports.py
+++ b/xsdata/formats/dataclass/transports.py
@@ -1,8 +1,10 @@
 import abc
 from typing import Any
 from typing import Dict
+from typing import Optional
 
-import requests
+from requests import Response
+from requests import Session
 
 
 class Transport(abc.ABC):
@@ -24,27 +26,30 @@ class DefaultTransport(Transport):
     :param timeout: Read timeout
     """
 
-    __slots__ = "timeout"
+    __slots__ = "timeout", "session"
 
-    def __init__(self, timeout: float = 2.0):
+    def __init__(self, timeout: float = 2.0, session: Optional[Session] = None):
         self.timeout = timeout
+        self.session = session or Session()
 
     def get(self, url: str, params: Dict, headers: Dict) -> bytes:
         """
         :raises HTTPError: if status code is not valid for content unmarshalling.
         """
-        res = requests.get(url, params=params, headers=headers, timeout=self.timeout)
+        res = self.session.get(
+            url, params=params, headers=headers, timeout=self.timeout
+        )
         return self.handle_response(res)
 
     def post(self, url: str, data: Any, headers: Dict) -> Any:
         """
         :raises HTTPError: if status code is not valid for content unmarshalling.
         """
-        res = requests.post(url, data=data, headers=headers, timeout=self.timeout)
+        res = self.session.post(url, data=data, headers=headers, timeout=self.timeout)
         return self.handle_response(res)
 
     @classmethod
-    def handle_response(cls, response: requests.Response) -> bytes:
+    def handle_response(cls, response: Response) -> bytes:
         """
         Status codes 200 or 500 means that we can unmarshall the response.
 


Test patch:
diff --git a/tests/formats/dataclass/test_transport.py b/tests/formats/dataclass/test_transport.py
index 29261cc6..a7073a85 100644
--- a/tests/formats/dataclass/test_transport.py
+++ b/tests/formats/dataclass/test_transport.py
@@ -3,6 +3,7 @@ from unittest import TestCase
 
 from requests import HTTPError
 from requests import Response
+from requests import Session
 
 from xsdata.formats.dataclass.transports import DefaultTransport
 
@@ -10,7 +11,7 @@ from xsdata.formats.dataclass.transports import DefaultTransport
 class DefaultTransportTest(TestCase):
     @mock.patch.object(Response, "content", new_callable=mock.PropertyMock)
     @mock.patch.object(Response, "raise_for_status")
-    @mock.patch("xsdata.formats.dataclass.transports.requests.get")
+    @mock.patch.object(Session, "get")
     def test_get(self, mock_get, mock_raise_for_status, mock_content):
         transport = DefaultTransport()
         params = {"a": "b"}
@@ -31,7 +32,7 @@ class DefaultTransportTest(TestCase):
 
     @mock.patch.object(Response, "content", new_callable=mock.PropertyMock)
     @mock.patch.object(Response, "raise_for_status")
-    @mock.patch("xsdata.formats.dataclass.transports.requests.post")
+    @mock.patch.object(Session, "post")
     def test_post(self, mock_post, mock_raise_for_status, mock_content):
         transport = DefaultTransport(timeout=1.0)
         data = {"a": "b"}