about summary refs log tree commit diff
path: root/.venv/lib/python3.12/site-packages/msrest/universal_http
diff options
context:
space:
mode:
authorS. Solomon Darnell2025-03-28 21:52:21 -0500
committerS. Solomon Darnell2025-03-28 21:52:21 -0500
commit4a52a71956a8d46fcb7294ac71734504bb09bcc2 (patch)
treeee3dc5af3b6313e921cd920906356f5d4febc4ed /.venv/lib/python3.12/site-packages/msrest/universal_http
parentcc961e04ba734dd72309fb548a2f97d67d578813 (diff)
downloadgn-ai-master.tar.gz
two version of R2R are here HEAD master
Diffstat (limited to '.venv/lib/python3.12/site-packages/msrest/universal_http')
-rw-r--r--.venv/lib/python3.12/site-packages/msrest/universal_http/__init__.py460
-rw-r--r--.venv/lib/python3.12/site-packages/msrest/universal_http/aiohttp.py101
-rw-r--r--.venv/lib/python3.12/site-packages/msrest/universal_http/async_abc.py90
-rw-r--r--.venv/lib/python3.12/site-packages/msrest/universal_http/async_requests.py240
-rw-r--r--.venv/lib/python3.12/site-packages/msrest/universal_http/requests.py464
5 files changed, 1355 insertions, 0 deletions
diff --git a/.venv/lib/python3.12/site-packages/msrest/universal_http/__init__.py b/.venv/lib/python3.12/site-packages/msrest/universal_http/__init__.py
new file mode 100644
index 00000000..60927559
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/msrest/universal_http/__init__.py
@@ -0,0 +1,460 @@
+# --------------------------------------------------------------------------
+#
+# Copyright (c) Microsoft Corporation. All rights reserved.
+#
+# The MIT License (MIT)
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the ""Software""), to
+# deal in the Software without restriction, including without limitation the
+# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+# sell copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+#
+# --------------------------------------------------------------------------
+from __future__ import absolute_import  # we have a "requests" module that conflicts with "requests" on Py2.7
+import abc
+try:
+    import configparser
+    from configparser import NoOptionError
+except ImportError:
+    import ConfigParser as configparser  # type: ignore
+    from ConfigParser import NoOptionError  # type: ignore
+import json
+import logging
+import os.path
+try:
+    from urlparse import urlparse
+except ImportError:
+    from urllib.parse import urlparse
+import xml.etree.ElementTree as ET
+
+from typing import TYPE_CHECKING, Generic, TypeVar, cast, IO, List, Union, Any, Mapping, Dict, Optional, Tuple, Callable, Iterator, MutableMapping  # pylint: disable=unused-import
+
+HTTPResponseType = TypeVar("HTTPResponseType", bound='HTTPClientResponse')
+
+# This file is NOT using any "requests" HTTP implementation
+# However, the CaseInsensitiveDict is handy.
+# If one day we reach the point where "requests" can be skip totally,
+# might provide our own implementation
+from requests.structures import CaseInsensitiveDict
+
+from ..exceptions import ClientRequestError, raise_with_traceback
+
+if TYPE_CHECKING:
+    from ..serialization import Model  # pylint: disable=unused-import
+
+
+_LOGGER = logging.getLogger(__name__)
+
+try:
+    ABC = abc.ABC
+except AttributeError: # Python 2.7, abc exists, but not ABC
+    ABC = abc.ABCMeta('ABC', (object,), {'__slots__': ()})  # type: ignore
+
+try:
+    from contextlib import AbstractContextManager  # type: ignore
+except ImportError: # Python <= 3.5
+    class AbstractContextManager(object):  # type: ignore
+        def __enter__(self):
+            """Return `self` upon entering the runtime context."""
+            return self
+
+        @abc.abstractmethod
+        def __exit__(self, exc_type, exc_value, traceback):
+            """Raise any exception triggered within the runtime context."""
+            return None
+
+
+class HTTPSender(AbstractContextManager, ABC):
+    """An http sender ABC.
+    """
+
+    @abc.abstractmethod
+    def send(self, request, **config):
+        # type: (ClientRequest, Any) -> ClientResponse
+        """Send the request using this HTTP sender.
+        """
+        pass
+
+
+class HTTPSenderConfiguration(object):
+    """HTTP sender configuration.
+
+    This is composed of generic HTTP configuration, and could be use as a common
+    HTTP configuration format.
+
+    :param str filepath: Path to existing config file (optional).
+    """
+
+    def __init__(self, filepath=None):
+        # Communication configuration
+        self.connection = ClientConnection()
+
+        # Headers (sent with every requests)
+        self.headers = {}  # type: Dict[str, str]
+
+        # ProxyConfiguration
+        self.proxies = ClientProxies()
+
+        # Redirect configuration
+        self.redirect_policy = ClientRedirectPolicy()
+
+        self._config = configparser.ConfigParser()
+        self._config.optionxform = str  # type: ignore
+
+        if filepath:
+            self.load(filepath)
+
+    def _clear_config(self):
+        # type: () -> None
+        """Clearout config object in memory."""
+        for section in self._config.sections():
+            self._config.remove_section(section)
+
+    def save(self, filepath):
+        # type: (str) -> None
+        """Save current configuration to file.
+
+        :param str filepath: Path to file where settings will be saved.
+        :raises: ValueError if supplied filepath cannot be written to.
+        """
+        sections = [
+            "Connection",
+            "Proxies",
+            "RedirectPolicy"]
+        for section in sections:
+            self._config.add_section(section)
+
+        self._config.set("Connection", "timeout", self.connection.timeout)
+        self._config.set("Connection", "verify", self.connection.verify)
+        self._config.set("Connection", "cert", self.connection.cert)
+
+        self._config.set("Proxies", "proxies", self.proxies.proxies)
+        self._config.set("Proxies", "env_settings",
+                         self.proxies.use_env_settings)
+
+        self._config.set("RedirectPolicy", "allow", self.redirect_policy.allow)
+        self._config.set("RedirectPolicy", "max_redirects",
+                         self.redirect_policy.max_redirects)
+
+        try:
+            with open(filepath, 'w') as configfile:
+                self._config.write(configfile)
+        except (KeyError, EnvironmentError):
+            error = "Supplied config filepath invalid."
+            raise_with_traceback(ValueError, error)
+        finally:
+            self._clear_config()
+
+    def load(self, filepath):
+        # type: (str) -> None
+        """Load configuration from existing file.
+
+        :param str filepath: Path to existing config file.
+        :raises: ValueError if supplied config file is invalid.
+        """
+        try:
+            self._config.read(filepath)
+            import ast
+            self.connection.timeout = \
+                self._config.getint("Connection", "timeout")
+            self.connection.verify = \
+                self._config.getboolean("Connection", "verify")
+            self.connection.cert = \
+                self._config.get("Connection", "cert")
+
+            self.proxies.proxies = \
+                ast.literal_eval(self._config.get("Proxies", "proxies"))
+            self.proxies.use_env_settings = \
+                self._config.getboolean("Proxies", "env_settings")
+
+            self.redirect_policy.allow = \
+                self._config.getboolean("RedirectPolicy", "allow")
+            self.redirect_policy.max_redirects = \
+                self._config.getint("RedirectPolicy", "max_redirects")
+
+        except (ValueError, EnvironmentError, NoOptionError):
+            error = "Supplied config file incompatible."
+            raise_with_traceback(ValueError, error)
+        finally:
+            self._clear_config()
+
+
+class ClientRequest(object):
+    """Represents a HTTP request.
+
+    URL can be given without query parameters, to be added later using "format_parameters".
+
+    Instance can be created without data, to be added later using "add_content"
+
+    Instance can be created without files, to be added later using "add_formdata"
+
+    :param str method: HTTP method (GET, HEAD, etc.)
+    :param str url: At least complete scheme/host/path
+    :param dict[str,str] headers: HTTP headers
+    :param files: Files list.
+    :param data: Body to be sent.
+    :type data: bytes or str.
+    """
+    def __init__(self, method, url, headers=None, files=None, data=None):
+        # type: (str, str, Mapping[str, str], Any, Any) -> None
+        self.method = method
+        self.url = url
+        self.headers = CaseInsensitiveDict(headers)
+        self.files = files
+        self.data = data
+
+    def __repr__(self):
+        return '<ClientRequest [%s]>' % (self.method)
+
+    @property
+    def body(self):
+        """Alias to data."""
+        return self.data
+
+    @body.setter
+    def body(self, value):
+        self.data = value
+
+    def format_parameters(self, params):
+        # type: (Dict[str, str]) -> None
+        """Format parameters into a valid query string.
+        It's assumed all parameters have already been quoted as
+        valid URL strings.
+
+        :param dict params: A dictionary of parameters.
+        """
+        query = urlparse(self.url).query
+        if query:
+            self.url = self.url.partition('?')[0]
+            existing_params = {
+                p[0]: p[-1]
+                for p in [p.partition('=') for p in query.split('&')]
+            }
+            params.update(existing_params)
+        query_params = ["{}={}".format(k, v) for k, v in params.items()]
+        query = '?' + '&'.join(query_params)
+        self.url = self.url + query
+
+    def add_content(self, data):
+        # type: (Optional[Union[Dict[str, Any], ET.Element]]) -> None
+        """Add a body to the request.
+
+        :param data: Request body data, can be a json serializable
+         object (e.g. dictionary) or a generator (e.g. file data).
+        """
+        if data is None:
+            return
+
+        if isinstance(data, ET.Element):
+            bytes_data = ET.tostring(data, encoding="utf8")
+            self.headers['Content-Length'] = str(len(bytes_data))
+            self.data = bytes_data
+            return
+
+        # By default, assume JSON
+        try:
+            self.data = json.dumps(data)
+            self.headers['Content-Length'] = str(len(self.data))
+        except TypeError:
+            self.data = data
+
+    @staticmethod
+    def _format_data(data):
+        # type: (Union[str, IO]) -> Union[Tuple[None, str], Tuple[Optional[str], IO, str]]
+        """Format field data according to whether it is a stream or
+        a string for a form-data request.
+
+        :param data: The request field data.
+        :type data: str or file-like object.
+        """
+        if hasattr(data, 'read'):
+            data = cast(IO, data)
+            data_name = None
+            try:
+                if data.name[0] != '<' and data.name[-1] != '>':
+                    data_name = os.path.basename(data.name)
+            except (AttributeError, TypeError):
+                pass
+            return (data_name, data, "application/octet-stream")
+        return (None, cast(str, data))
+
+    def add_formdata(self, content=None):
+        # type: (Optional[Dict[str, str]]) -> None
+        """Add data as a multipart form-data request to the request.
+
+        We only deal with file-like objects or strings at this point.
+        The requests is not yet streamed.
+
+        :param dict headers: Any headers to add to the request.
+        :param dict content: Dictionary of the fields of the formdata.
+        """
+        if content is None:
+            content = {}
+        content_type = self.headers.pop('Content-Type', None) if self.headers else None
+
+        if content_type and content_type.lower() == 'application/x-www-form-urlencoded':
+            # Do NOT use "add_content" that assumes input is JSON
+            self.data = {f: d for f, d in content.items() if d is not None}
+        else: # Assume "multipart/form-data"
+            self.files = {f: self._format_data(d) for f, d in content.items() if d is not None}
+
+class HTTPClientResponse(object):
+    """Represent a HTTP response.
+
+    No body is defined here on purpose, since async pipeline
+    will provide async ways to access the body
+
+    You have two different types of body:
+    - Full in-memory using "body" as bytes
+    """
+    def __init__(self, request, internal_response):
+        # type: (ClientRequest, Any) -> None
+        self.request = request
+        self.internal_response = internal_response
+        self.status_code = None  # type: Optional[int]
+        self.headers = {}  # type: MutableMapping[str, str]
+        self.reason = None  # type: Optional[str]
+
+    def body(self):
+        # type: () -> bytes
+        """Return the whole body as bytes in memory.
+        """
+        pass
+
+    def text(self, encoding=None):
+        # type: (str) -> str
+        """Return the whole body as a string.
+
+        :param str encoding: The encoding to apply. If None, use "utf-8-sig".
+         Implementation can be smarter if they want (using headers).
+        """
+        return self.body().decode(encoding or "utf-8-sig")
+
+    def raise_for_status(self):
+        """Raise for status. Should be overridden, but basic implementation provided.
+        """
+        if self.status_code >= 400:
+            raise ClientRequestError("Received status code {}".format(self.status_code))
+
+
+class ClientResponse(HTTPClientResponse):
+
+    def stream_download(self, chunk_size=None, callback=None):
+        # type: (Optional[int], Optional[Callable]) -> Iterator[bytes]
+        """Generator for streaming request body data.
+
+        Should be implemented by sub-classes if streaming download
+        is supported.
+
+        :param callback: Custom callback for monitoring progress.
+        :param int chunk_size:
+        """
+        pass
+
+
+class ClientRedirectPolicy(object):
+    """Redirect configuration settings.
+    """
+
+    def __init__(self):
+        self.allow = True
+        self.max_redirects = 30
+
+    def __bool__(self):
+        # type: () -> bool
+        """Whether redirects are allowed."""
+        return self.allow
+
+    def __call__(self):
+        # type: () -> int
+        """Return configuration to be applied to connection."""
+        debug = "Configuring redirects: allow=%r, max=%r"
+        _LOGGER.debug(debug, self.allow, self.max_redirects)
+        return self.max_redirects
+
+
+class ClientProxies(object):
+    """Proxy configuration settings.
+    Proxies can also be configured using HTTP_PROXY and HTTPS_PROXY
+    environment variables, in which case set use_env_settings to True.
+    """
+
+    def __init__(self):
+        self.proxies = {}
+        self.use_env_settings = True
+
+    def __call__(self):
+        # type: () -> Dict[str, str]
+        """Return configuration to be applied to connection."""
+        proxy_string = "\n".join(
+            ["    {}: {}".format(k, v) for k, v in self.proxies.items()])
+
+        _LOGGER.debug("Configuring proxies: %r", proxy_string)
+        debug = "Evaluate proxies against ENV settings: %r"
+        _LOGGER.debug(debug, self.use_env_settings)
+        return self.proxies
+
+    def add(self, protocol, proxy_url):
+        # type: (str, str) -> None
+        """Add proxy.
+
+        :param str protocol: Protocol for which proxy is to be applied. Can
+         be 'http', 'https', etc. Can also include host.
+        :param str proxy_url: The proxy URL. Where basic auth is required,
+         use the format: http://user:password@host
+        """
+        self.proxies[protocol] = proxy_url
+
+
+class ClientConnection(object):
+    """Request connection configuration settings.
+    """
+
+    def __init__(self):
+        self.timeout = 100
+        self.verify = True
+        self.cert = None
+        self.data_block_size = 4096
+
+    def __call__(self):
+        # type: () -> Dict[str, Union[str, int]]
+        """Return configuration to be applied to connection."""
+        debug = "Configuring request: timeout=%r, verify=%r, cert=%r"
+        _LOGGER.debug(debug, self.timeout, self.verify, self.cert)
+        return {'timeout': self.timeout,
+                'verify': self.verify,
+                'cert': self.cert}
+
+
+__all__ = [
+    'ClientRequest',
+    'ClientResponse',
+    'HTTPSender',
+    # Generic HTTP configuration
+    'HTTPSenderConfiguration',
+    'ClientRedirectPolicy',
+    'ClientProxies',
+    'ClientConnection'
+]
+
+try:
+    from .async_abc import AsyncHTTPSender, AsyncClientResponse  # pylint: disable=unused-import
+    from .async_abc import __all__ as _async_all
+    __all__ += _async_all
+except SyntaxError: # Python 2
+    pass
+except ImportError: # pyinstaller won't include Py3 files in Py2.7 mode
+    pass
diff --git a/.venv/lib/python3.12/site-packages/msrest/universal_http/aiohttp.py b/.venv/lib/python3.12/site-packages/msrest/universal_http/aiohttp.py
new file mode 100644
index 00000000..0be8f299
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/msrest/universal_http/aiohttp.py
@@ -0,0 +1,101 @@
+# --------------------------------------------------------------------------
+#
+# Copyright (c) Microsoft Corporation. All rights reserved.
+#
+# The MIT License (MIT)
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the ""Software""), to
+# deal in the Software without restriction, including without limitation the
+# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+# sell copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+#
+# --------------------------------------------------------------------------
+from typing import Any, Callable, AsyncIterator, Optional
+
+import aiohttp
+from multidict import CIMultiDict
+
+from . import AsyncHTTPSender, ClientRequest, AsyncClientResponse
+
+# Matching requests, because why not?
+CONTENT_CHUNK_SIZE = 10 * 1024
+
+
+class AioHTTPSender(AsyncHTTPSender):
+    """AioHttp HTTP sender implementation.
+    """
+
+    def __init__(self, *, loop=None):
+        self._session = aiohttp.ClientSession(loop=loop)
+
+    async def __aenter__(self):
+        await self._session.__aenter__()
+        return self
+
+    async def __aexit__(self, *exc_details):  # pylint: disable=arguments-differ
+        await self._session.__aexit__(*exc_details)
+
+    async def send(self, request: ClientRequest, **config: Any) -> AsyncClientResponse:
+        """Send the request using this HTTP sender.
+
+        Will pre-load the body into memory to be available with a sync method.
+        pass stream=True to avoid this behavior.
+        """
+        result = await self._session.request(
+            request.method,
+            request.url,
+            **config
+        )
+        response = AioHttpClientResponse(request, result)
+        if not config.get("stream", False):
+            await response.load_body()
+        return response
+
+
+class AioHttpClientResponse(AsyncClientResponse):
+    def __init__(self, request: ClientRequest, aiohttp_response: aiohttp.ClientResponse) -> None:
+        super(AioHttpClientResponse, self).__init__(request, aiohttp_response)
+        # https://aiohttp.readthedocs.io/en/stable/client_reference.html#aiohttp.ClientResponse
+        self.status_code = aiohttp_response.status
+        self.headers = CIMultiDict(aiohttp_response.headers)
+        self.reason = aiohttp_response.reason
+        self._body = None
+
+    def body(self) -> bytes:
+        """Return the whole body as bytes in memory.
+        """
+        if not self._body:
+            raise ValueError("Body is not available. Call async method load_body, or do your call with stream=False.")
+        return self._body
+
+    async def load_body(self) -> None:
+        """Load in memory the body, so it could be accessible from sync methods."""
+        self._body = await self.internal_response.read()
+
+    def raise_for_status(self):
+        self.internal_response.raise_for_status()
+
+    def stream_download(self, chunk_size: Optional[int] = None, callback: Optional[Callable] = None) -> AsyncIterator[bytes]:
+        """Generator for streaming request body data.
+        """
+        chunk_size = chunk_size or CONTENT_CHUNK_SIZE
+        async def async_gen(resp):
+            while True:
+                chunk = await resp.content.read(chunk_size)
+                if not chunk:
+                    break
+                callback(chunk, resp)
+        return async_gen(self.internal_response)
diff --git a/.venv/lib/python3.12/site-packages/msrest/universal_http/async_abc.py b/.venv/lib/python3.12/site-packages/msrest/universal_http/async_abc.py
new file mode 100644
index 00000000..1b7f4c7c
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/msrest/universal_http/async_abc.py
@@ -0,0 +1,90 @@
+# --------------------------------------------------------------------------
+#
+# Copyright (c) Microsoft Corporation. All rights reserved.
+#
+# The MIT License (MIT)
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the ""Software""), to
+# deal in the Software without restriction, including without limitation the
+# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+# sell copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+#
+# --------------------------------------------------------------------------
+import abc
+
+from typing import Any, List, Union, Callable, AsyncIterator, Optional
+
+try:
+    from contextlib import AbstractAsyncContextManager  # type: ignore
+except ImportError: # Python <= 3.7
+    class AbstractAsyncContextManager(object):  # type: ignore
+        async def __aenter__(self):
+            """Return `self` upon entering the runtime context."""
+            return self
+
+        @abc.abstractmethod
+        async def __aexit__(self, exc_type, exc_value, traceback):
+            """Raise any exception triggered within the runtime context."""
+            return None
+
+from . import ClientRequest, HTTPClientResponse
+
+
+class AsyncClientResponse(HTTPClientResponse):
+
+    def stream_download(self, chunk_size: Optional[int] = None, callback: Optional[Callable] = None) -> AsyncIterator[bytes]:
+        """Generator for streaming request body data.
+
+        Should be implemented by sub-classes if streaming download
+        is supported.
+
+        :param callback: Custom callback for monitoring progress.
+        :param int chunk_size:
+        """
+        pass
+
+
+class AsyncHTTPSender(AbstractAsyncContextManager, abc.ABC):
+    """An http sender ABC.
+    """
+
+    @abc.abstractmethod
+    async def send(self, request: ClientRequest, **config: Any) -> AsyncClientResponse:
+        """Send the request using this HTTP sender.
+        """
+        pass
+
+    def build_context(self) -> Any:
+        """Allow the sender to build a context that will be passed
+        across the pipeline with the request.
+
+        Return type has no constraints. Implementation is not
+        required and None by default.
+        """
+        return None
+
+    def __enter__(self):
+        raise TypeError("Use 'async with' instead")
+
+    def __exit__(self, exc_type, exc_val, exc_tb):
+        # __exit__ should exist in pair with __enter__ but never executed
+        pass  # pragma: no cover
+
+
+__all__ = [
+    'AsyncHTTPSender',
+    'AsyncClientResponse'
+]
\ No newline at end of file
diff --git a/.venv/lib/python3.12/site-packages/msrest/universal_http/async_requests.py b/.venv/lib/python3.12/site-packages/msrest/universal_http/async_requests.py
new file mode 100644
index 00000000..40a3a352
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/msrest/universal_http/async_requests.py
@@ -0,0 +1,240 @@
+# --------------------------------------------------------------------------
+#
+# Copyright (c) Microsoft Corporation. All rights reserved.
+#
+# The MIT License (MIT)
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the ""Software""), to
+# deal in the Software without restriction, including without limitation the
+# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+# sell copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+#
+# --------------------------------------------------------------------------
+import asyncio
+from collections.abc import AsyncIterator
+import functools
+import logging
+from typing import Any, Callable, Optional, AsyncIterator as AsyncIteratorType
+
+from oauthlib import oauth2
+import requests
+from requests.models import CONTENT_CHUNK_SIZE
+
+from ..exceptions import (
+    TokenExpiredError,
+    ClientRequestError,
+    raise_with_traceback)
+from . import AsyncHTTPSender, ClientRequest, AsyncClientResponse
+from .requests import (
+    BasicRequestsHTTPSender,
+    RequestsHTTPSender,
+    HTTPRequestsClientResponse
+)
+
+
+_LOGGER = logging.getLogger(__name__)
+
+
+class AsyncBasicRequestsHTTPSender(BasicRequestsHTTPSender, AsyncHTTPSender):  # type: ignore
+
+    async def __aenter__(self):
+        return super(AsyncBasicRequestsHTTPSender, self).__enter__()
+
+    async def __aexit__(self, *exc_details):  # pylint: disable=arguments-differ
+        return super(AsyncBasicRequestsHTTPSender, self).__exit__()
+
+    async def send(self, request: ClientRequest, **kwargs: Any) -> AsyncClientResponse:  # type: ignore
+        """Send the request using this HTTP sender.
+        """
+        # It's not recommended to provide its own session, and is mostly
+        # to enable some legacy code to plug correctly
+        session = kwargs.pop('session', self.session)
+
+        loop = kwargs.get("loop", asyncio.get_event_loop())
+        future = loop.run_in_executor(
+            None,
+            functools.partial(
+                session.request,
+                request.method,
+                request.url,
+                **kwargs
+            )
+        )
+        try:
+            return AsyncRequestsClientResponse(
+                request,
+                await future
+            )
+        except requests.RequestException as err:
+            msg = "Error occurred in request."
+            raise_with_traceback(ClientRequestError, msg, err)
+
+class AsyncRequestsHTTPSender(AsyncBasicRequestsHTTPSender, RequestsHTTPSender):  # type: ignore
+
+    async def send(self, request: ClientRequest, **kwargs: Any) -> AsyncClientResponse:  # type: ignore
+        """Send the request using this HTTP sender.
+        """
+        requests_kwargs = self._configure_send(request, **kwargs)
+        return await super(AsyncRequestsHTTPSender, self).send(request, **requests_kwargs)
+
+
+class _MsrestStopIteration(Exception):
+    pass
+
+def _msrest_next(iterator):
+    """"To avoid:
+    TypeError: StopIteration interacts badly with generators and cannot be raised into a Future
+    """
+    try:
+        return next(iterator)
+    except StopIteration:
+        raise _MsrestStopIteration()
+
+class StreamDownloadGenerator(AsyncIterator):
+
+    def __init__(self, response: requests.Response, user_callback: Optional[Callable] = None, block: Optional[int] = None) -> None:
+        self.response = response
+        self.block = block or CONTENT_CHUNK_SIZE
+        self.user_callback = user_callback
+        self.iter_content_func = self.response.iter_content(self.block)
+
+    async def __anext__(self):
+        loop = asyncio.get_event_loop()
+        try:
+            chunk = await loop.run_in_executor(
+                None,
+                _msrest_next,
+                self.iter_content_func,
+            )
+            if not chunk:
+                raise _MsrestStopIteration()
+            if self.user_callback and callable(self.user_callback):
+                self.user_callback(chunk, self.response)
+            return chunk
+        except _MsrestStopIteration:
+            self.response.close()
+            raise StopAsyncIteration()
+        except Exception as err:
+            _LOGGER.warning("Unable to stream download: %s", err)
+            self.response.close()
+            raise
+
+class AsyncRequestsClientResponse(AsyncClientResponse, HTTPRequestsClientResponse):
+
+    def stream_download(self, chunk_size: Optional[int] = None, callback: Optional[Callable] = None) -> AsyncIteratorType[bytes]:
+        """Generator for streaming request body data.
+
+        :param callback: Custom callback for monitoring progress.
+        :param int chunk_size:
+        """
+        return StreamDownloadGenerator(
+            self.internal_response,
+            callback,
+            chunk_size
+        )
+
+
+# Trio support
+try:
+    import trio
+
+    class TrioStreamDownloadGenerator(AsyncIterator):
+
+        def __init__(self, response: requests.Response, user_callback: Optional[Callable] = None, block: Optional[int] = None) -> None:
+            self.response = response
+            self.block = block or CONTENT_CHUNK_SIZE
+            self.user_callback = user_callback
+            self.iter_content_func = self.response.iter_content(self.block)
+
+        async def __anext__(self):
+            try:
+                chunk = await trio.to_thread.run_sync(
+                    _msrest_next,
+                    self.iter_content_func,
+                )
+                if not chunk:
+                    raise _MsrestStopIteration()
+                if self.user_callback and callable(self.user_callback):
+                    self.user_callback(chunk, self.response)
+                return chunk
+            except _MsrestStopIteration:
+                self.response.close()
+                raise StopAsyncIteration()
+            except Exception as err:
+                _LOGGER.warning("Unable to stream download: %s", err)
+                self.response.close()
+                raise
+
+    class TrioAsyncRequestsClientResponse(AsyncClientResponse, HTTPRequestsClientResponse):
+
+        def stream_download(self, chunk_size: Optional[int] = None, callback: Optional[Callable] = None) -> AsyncIteratorType[bytes]:
+            """Generator for streaming request body data.
+
+            :param callback: Custom callback for monitoring progress.
+            :param int chunk_size:
+            """
+            return TrioStreamDownloadGenerator(
+                self.internal_response,
+                callback,
+                chunk_size
+            )
+
+
+    class AsyncTrioBasicRequestsHTTPSender(BasicRequestsHTTPSender, AsyncHTTPSender):  # type: ignore
+
+        async def __aenter__(self):
+            return super(AsyncTrioBasicRequestsHTTPSender, self).__enter__()
+
+        async def __aexit__(self, *exc_details):  # pylint: disable=arguments-differ
+            return super(AsyncTrioBasicRequestsHTTPSender, self).__exit__()
+
+        async def send(self, request: ClientRequest, **kwargs: Any) -> AsyncClientResponse:  # type: ignore
+            """Send the request using this HTTP sender.
+            """
+            # It's not recommended to provide its own session, and is mostly
+            # to enable some legacy code to plug correctly
+            session = kwargs.pop('session', self.session)
+
+            trio_limiter = kwargs.get("trio_limiter", None)
+            future = trio.to_thread.run_sync(
+                functools.partial(
+                    session.request,
+                    request.method,
+                    request.url,
+                    **kwargs
+                ),
+                limiter=trio_limiter
+            )
+            try:
+                return TrioAsyncRequestsClientResponse(
+                    request,
+                    await future
+                )
+            except requests.RequestException as err:
+                msg = "Error occurred in request."
+                raise_with_traceback(ClientRequestError, msg, err)
+
+    class AsyncTrioRequestsHTTPSender(AsyncTrioBasicRequestsHTTPSender, RequestsHTTPSender):  # type: ignore
+
+        async def send(self, request: ClientRequest, **kwargs: Any) -> AsyncClientResponse:  # type: ignore
+            """Send the request using this HTTP sender.
+            """
+            requests_kwargs = self._configure_send(request, **kwargs)
+            return await super(AsyncTrioRequestsHTTPSender, self).send(request, **requests_kwargs)
+
+except ImportError:
+    # trio not installed
+    pass
\ No newline at end of file
diff --git a/.venv/lib/python3.12/site-packages/msrest/universal_http/requests.py b/.venv/lib/python3.12/site-packages/msrest/universal_http/requests.py
new file mode 100644
index 00000000..e5e04632
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/msrest/universal_http/requests.py
@@ -0,0 +1,464 @@
+# --------------------------------------------------------------------------
+#
+# Copyright (c) Microsoft Corporation. All rights reserved.
+#
+# The MIT License (MIT)
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the ""Software""), to
+# deal in the Software without restriction, including without limitation the
+# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+# sell copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+#
+# --------------------------------------------------------------------------
+"""
+This module is the requests implementation of Pipeline ABC
+"""
+from __future__ import absolute_import  # we have a "requests" module that conflicts with "requests" on Py2.7
+import contextlib
+import logging
+import threading
+from typing import TYPE_CHECKING, List, Callable, Iterator, Any, Union, Dict, Optional  # pylint: disable=unused-import
+import warnings
+
+try:
+    from configparser import NoOptionError
+except ImportError:
+    from ConfigParser import NoOptionError  # type: ignore
+
+from oauthlib import oauth2
+import requests
+from requests.models import CONTENT_CHUNK_SIZE
+
+from urllib3 import Retry  # Needs requests 2.16 at least to be safe
+
+from ..exceptions import (
+    TokenExpiredError,
+    ClientRequestError,
+    raise_with_traceback)
+from . import HTTPSender, HTTPClientResponse, ClientResponse, HTTPSenderConfiguration
+
+if TYPE_CHECKING:
+    from . import ClientRequest  # pylint: disable=unused-import
+
+
+_LOGGER = logging.getLogger(__name__)
+
+
+
+class HTTPRequestsClientResponse(HTTPClientResponse):
+    def __init__(self, request, requests_response):
+        super(HTTPRequestsClientResponse, self).__init__(request, requests_response)
+        self.status_code = requests_response.status_code
+        self.headers = requests_response.headers
+        self.reason = requests_response.reason
+
+    def body(self):
+        return self.internal_response.content
+
+    def text(self, encoding=None):
+        if encoding:
+            self.internal_response.encoding = encoding
+        return self.internal_response.text
+
+    def raise_for_status(self):
+        self.internal_response.raise_for_status()
+
+class RequestsClientResponse(HTTPRequestsClientResponse, ClientResponse):
+
+    def stream_download(self, chunk_size=None, callback=None):
+        # type: (Optional[int], Optional[Callable]) -> Iterator[bytes]
+        """Generator for streaming request body data.
+
+        :param callback: Custom callback for monitoring progress.
+        :param int chunk_size:
+        """
+        chunk_size = chunk_size or CONTENT_CHUNK_SIZE
+        with contextlib.closing(self.internal_response) as response:
+            # https://github.com/PyCQA/pylint/issues/1437
+            for chunk in response.iter_content(chunk_size):  # pylint: disable=no-member
+                if not chunk:
+                    break
+                if callback and callable(callback):
+                    callback(chunk, response=response)
+                yield chunk
+
+
+class BasicRequestsHTTPSender(HTTPSender):
+    """Implements a basic requests HTTP sender.
+
+    Since requests team recommends to use one session per requests, you should
+    not consider this class as thread-safe, since it will use one Session
+    per instance.
+
+    In this simple implementation:
+    - You provide the configured session if you want to, or a basic session is created.
+    - All kwargs received by "send" are sent to session.request directly
+    """
+
+    def __init__(self, session=None):
+        # type: (Optional[requests.Session]) -> None
+        self.session = session or requests.Session()
+
+    def __enter__(self):
+        # type: () -> BasicRequestsHTTPSender
+        return self
+
+    def __exit__(self, *exc_details):  # pylint: disable=arguments-differ
+        self.close()
+
+    def close(self):
+        self.session.close()
+
+    def send(self, request, **kwargs):
+        # type: (ClientRequest, Any) -> ClientResponse
+        """Send request object according to configuration.
+
+        Allowed kwargs are:
+        - session : will override the driver session and use yours. Should NOT be done unless really required.
+        - anything else is sent straight to requests.
+
+        :param ClientRequest request: The request object to be sent.
+        """
+        # It's not recommended to provide its own session, and is mostly
+        # to enable some legacy code to plug correctly
+        session = kwargs.pop('session', self.session)
+        try:
+            response = session.request(
+                request.method,
+                request.url,
+                **kwargs)
+        except requests.RequestException as err:
+            msg = "Error occurred in request."
+            raise_with_traceback(ClientRequestError, msg, err)
+
+        return RequestsClientResponse(request, response)
+
+
+def _patch_redirect(session):
+    # type: (requests.Session) -> None
+    """Whether redirect policy should be applied based on status code.
+
+    HTTP spec says that on 301/302 not HEAD/GET, should NOT redirect.
+    But requests does, to follow browser more than spec
+    https://github.com/requests/requests/blob/f6e13ccfc4b50dc458ee374e5dba347205b9a2da/requests/sessions.py#L305-L314
+
+    This patches "requests" to be more HTTP compliant.
+
+    Note that this is super dangerous, since technically this is not public API.
+    """
+    def enforce_http_spec(resp, request):
+        if resp.status_code in (301, 302) and \
+                request.method not in ['GET', 'HEAD']:
+            return False
+        return True
+
+    redirect_logic = session.resolve_redirects
+
+    def wrapped_redirect(resp, req, **kwargs):
+        attempt = enforce_http_spec(resp, req)
+        return redirect_logic(resp, req, **kwargs) if attempt else []
+    wrapped_redirect.is_msrest_patched = True  # type: ignore
+
+    session.resolve_redirects = wrapped_redirect  # type: ignore
+
+class RequestsHTTPSender(BasicRequestsHTTPSender):
+    """A requests HTTP sender that can consume a msrest.Configuration object.
+
+    This instance will consume the following configuration attributes:
+    - connection
+    - proxies
+    - retry_policy
+    - redirect_policy
+    - enable_http_logger
+    - hooks
+    - session_configuration_callback
+    """
+
+    _protocols = ['http://', 'https://']
+
+    # Set of authorized kwargs at the operation level
+    _REQUESTS_KWARGS = [
+        'cookies',
+        'verify',
+        'timeout',
+        'allow_redirects',
+        'proxies',
+        'verify',
+        'cert'
+    ]
+
+    def __init__(self, config=None):
+        # type: (Optional[RequestHTTPSenderConfiguration]) -> None
+        self._session_mapping = threading.local()
+        self.config = config or RequestHTTPSenderConfiguration()
+        super(RequestsHTTPSender, self).__init__()
+
+    @property  # type: ignore
+    def session(self):
+        try:
+            return self._session_mapping.session
+        except AttributeError:
+            self._session_mapping.session = requests.Session()
+            self._init_session(self._session_mapping.session)
+            return self._session_mapping.session
+
+    @session.setter
+    def session(self, value):
+        self._init_session(value)
+        self._session_mapping.session = value
+
+    def _init_session(self, session):
+        # type: (requests.Session) -> None
+        """Init session level configuration of requests.
+
+        This is initialization I want to do once only on a session.
+        """
+        _patch_redirect(session)
+
+        # Change max_retries in current all installed adapters
+        max_retries = self.config.retry_policy()
+        for protocol in self._protocols:
+            session.adapters[protocol].max_retries = max_retries
+
+    def _configure_send(self, request, **kwargs):
+        # type: (ClientRequest, Any) -> Dict[str, str]
+        """Configure the kwargs to use with requests.
+
+        See "send" for kwargs details.
+
+        :param ClientRequest request: The request object to be sent.
+        :returns: The requests.Session.request kwargs
+        :rtype: dict[str,str]
+        """
+        requests_kwargs = {}  # type: Any
+        session = kwargs.pop('session', self.session)
+
+        # If custom session was not create here
+        if session is not self.session:
+            self._init_session(session)
+
+        session.max_redirects = int(self.config.redirect_policy())
+        session.trust_env = bool(self.config.proxies.use_env_settings)
+
+        # Initialize requests_kwargs with "config" value
+        requests_kwargs.update(self.config.connection())
+        requests_kwargs['allow_redirects'] = bool(self.config.redirect_policy)
+        requests_kwargs['headers'] = self.config.headers.copy()
+
+        proxies = self.config.proxies()
+        if proxies:
+            requests_kwargs['proxies'] = proxies
+
+        # Replace by operation level kwargs
+        # We allow some of them, since some like stream or json are controlled by msrest
+        for key in kwargs:
+            if key in self._REQUESTS_KWARGS:
+                requests_kwargs[key] = kwargs[key]
+
+        # Hooks. Deprecated, should be a policy
+        def make_user_hook_cb(user_hook, session):
+            def user_hook_cb(r, *args, **kwargs):
+                kwargs.setdefault("msrest", {})['session'] = session
+                return user_hook(r, *args, **kwargs)
+            return user_hook_cb
+
+        hooks = []
+        for user_hook in self.config.hooks:
+            hooks.append(make_user_hook_cb(user_hook, self.session))
+
+        if hooks:
+            requests_kwargs['hooks'] = {'response': hooks}
+
+        # Configuration callback. Deprecated, should be a policy
+        output_kwargs = self.config.session_configuration_callback(
+            session,
+            self.config,
+            kwargs,
+            **requests_kwargs
+        )
+        if output_kwargs is not None:
+            requests_kwargs = output_kwargs
+
+        # If custom session was not create here
+        if session is not self.session:
+            requests_kwargs['session'] = session
+
+        ### Autorest forced kwargs now ###
+
+        # If Autorest needs this response to be streamable. True for compat.
+        requests_kwargs['stream'] = kwargs.get('stream', True)
+
+        if request.files:
+            requests_kwargs['files'] = request.files
+        elif request.data:
+            requests_kwargs['data'] = request.data
+        requests_kwargs['headers'].update(request.headers)
+
+        return requests_kwargs
+
+    def send(self, request, **kwargs):
+        # type: (ClientRequest, Any) -> ClientResponse
+        """Send request object according to configuration.
+
+        Available kwargs:
+        - session : will override the driver session and use yours. Should NOT be done unless really required.
+        - A subset of what requests.Session.request can receive:
+
+            - cookies
+            - verify
+            - timeout
+            - allow_redirects
+            - proxies
+            - verify
+            - cert
+
+        Everything else will be silently ignored.
+
+        :param ClientRequest request: The request object to be sent.
+        """
+        requests_kwargs = self._configure_send(request, **kwargs)
+        return super(RequestsHTTPSender, self).send(request, **requests_kwargs)
+
+
+class ClientRetryPolicy(object):
+    """Retry configuration settings.
+    Container for retry policy object.
+    """
+
+    safe_codes = [i for i in range(500) if i != 408] + [501, 505]
+
+    def __init__(self):
+        self.policy = Retry()
+        self.policy.total = 3
+        self.policy.connect = 3
+        self.policy.read = 3
+        self.policy.backoff_factor = 0.8
+        self.policy.BACKOFF_MAX = 90
+
+        retry_codes = [i for i in range(999) if i not in self.safe_codes]
+        self.policy.status_forcelist = retry_codes
+        self.policy.method_whitelist = ['HEAD', 'TRACE', 'GET', 'PUT',
+                                        'OPTIONS', 'DELETE', 'POST', 'PATCH']
+
+    def __call__(self):
+        # type: () -> Retry
+        """Return configuration to be applied to connection."""
+        debug = ("Configuring retry: max_retries=%r, "
+                 "backoff_factor=%r, max_backoff=%r")
+        _LOGGER.debug(
+            debug, self.retries, self.backoff_factor, self.max_backoff)
+        return self.policy
+
+    @property
+    def retries(self):
+        # type: () -> int
+        """Total number of allowed retries."""
+        return self.policy.total
+
+    @retries.setter
+    def retries(self, value):
+        # type: (int) -> None
+        self.policy.total = value
+        self.policy.connect = value
+        self.policy.read = value
+
+    @property
+    def backoff_factor(self):
+        # type: () -> Union[int, float]
+        """Factor by which back-off delay is incrementally increased."""
+        return self.policy.backoff_factor
+
+    @backoff_factor.setter
+    def backoff_factor(self, value):
+        # type: (Union[int, float]) -> None
+        self.policy.backoff_factor = value
+
+    @property
+    def max_backoff(self):
+        # type: () -> int
+        """Max retry back-off delay."""
+        return self.policy.BACKOFF_MAX
+
+    @max_backoff.setter
+    def max_backoff(self, value):
+        # type: (int) -> None
+        self.policy.BACKOFF_MAX = value
+
+def default_session_configuration_callback(session, global_config, local_config, **kwargs):  # pylint: disable=unused-argument
+    # type: (requests.Session, RequestHTTPSenderConfiguration, Dict[str,str], str) -> Dict[str, str]
+    """Configuration callback if you need to change default session configuration.
+
+    :param requests.Session session: The session.
+    :param Configuration global_config: The global configuration.
+    :param dict[str,str] local_config: The on-the-fly configuration passed on the call.
+    :param dict[str,str] kwargs: The current computed values for session.request method.
+    :return: Must return kwargs, to be passed to session.request. If None is return, initial kwargs will be used.
+    :rtype: dict[str,str]
+    """
+    return kwargs
+
+class RequestHTTPSenderConfiguration(HTTPSenderConfiguration):
+    """Requests specific HTTP sender configuration.
+
+    :param str filepath: Path to existing config file (optional).
+    """
+
+    def __init__(self, filepath=None):
+        # type: (Optional[str]) -> None
+
+        super(RequestHTTPSenderConfiguration, self).__init__()
+
+        # Retry configuration
+        self.retry_policy = ClientRetryPolicy()
+
+        # Requests hooks. Must respect requests hook callback signature
+        # Note that we will inject the following parameters:
+        # - kwargs['msrest']['session'] with the current session
+        self.hooks = []  # type: List[Callable[[requests.Response, str, str], None]]
+
+        self.session_configuration_callback = default_session_configuration_callback
+
+        if filepath:
+            self.load(filepath)
+
+    def save(self, filepath):
+        """Save current configuration to file.
+
+        :param str filepath: Path to file where settings will be saved.
+        :raises: ValueError if supplied filepath cannot be written to.
+        """
+        self._config.add_section("RetryPolicy")
+        self._config.set("RetryPolicy", "retries", str(self.retry_policy.retries))
+        self._config.set("RetryPolicy", "backoff_factor",
+                         str(self.retry_policy.backoff_factor))
+        self._config.set("RetryPolicy", "max_backoff",
+                         str(self.retry_policy.max_backoff))
+        super(RequestHTTPSenderConfiguration, self).save(filepath)
+
+    def load(self, filepath):
+        try:
+            self.retry_policy.retries = \
+                self._config.getint("RetryPolicy", "retries")
+            self.retry_policy.backoff_factor = \
+                self._config.getfloat("RetryPolicy", "backoff_factor")
+            self.retry_policy.max_backoff = \
+                self._config.getint("RetryPolicy", "max_backoff")
+        except (ValueError, EnvironmentError, NoOptionError):
+            error = "Supplied config file incompatible."
+            raise_with_traceback(ValueError, error)
+        finally:
+            self._clear_config()
+        super(RequestHTTPSenderConfiguration, self).load(filepath)