diff options
author | S. Solomon Darnell | 2025-03-28 21:52:21 -0500 |
---|---|---|
committer | S. Solomon Darnell | 2025-03-28 21:52:21 -0500 |
commit | 4a52a71956a8d46fcb7294ac71734504bb09bcc2 (patch) | |
tree | ee3dc5af3b6313e921cd920906356f5d4febc4ed /.venv/lib/python3.12/site-packages/msrest/pipeline | |
parent | cc961e04ba734dd72309fb548a2f97d67d578813 (diff) | |
download | gn-ai-master.tar.gz |
Diffstat (limited to '.venv/lib/python3.12/site-packages/msrest/pipeline')
6 files changed, 1132 insertions, 0 deletions
diff --git a/.venv/lib/python3.12/site-packages/msrest/pipeline/__init__.py b/.venv/lib/python3.12/site-packages/msrest/pipeline/__init__.py new file mode 100644 index 00000000..428cf8cf --- /dev/null +++ b/.venv/lib/python3.12/site-packages/msrest/pipeline/__init__.py @@ -0,0 +1,329 @@ +# -------------------------------------------------------------------------- +# +# 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 # pylint: disable=unused-import + +HTTPResponseType = TypeVar("HTTPResponseType") +HTTPRequestType = TypeVar("HTTPRequestType") + +# 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 + + +_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 HTTPPolicy(ABC, Generic[HTTPRequestType, HTTPResponseType]): + """An http policy ABC. + """ + def __init__(self): + self.next = None + + @abc.abstractmethod + def send(self, request, **kwargs): + # type: (Request[HTTPRequestType], Any) -> Response[HTTPRequestType, HTTPResponseType] + """Mutate the request. + + Context content is dependent of the HTTPSender. + """ + pass + +class SansIOHTTPPolicy(Generic[HTTPRequestType, HTTPResponseType]): + """Represents a sans I/O policy. + + This policy can act before the I/O, and after the I/O. + Use this policy if the actual I/O in the middle is an implementation + detail. + + Context is not available, since it's implementation dependent. + if a policy needs a context of the Sender, it can't be universal. + + Example: setting a UserAgent does not need to be tight to + sync or async implementation or specific HTTP lib + """ + def on_request(self, request, **kwargs): + # type: (Request[HTTPRequestType], Any) -> None + """Is executed before sending the request to next policy. + """ + pass + + def on_response(self, request, response, **kwargs): + # type: (Request[HTTPRequestType], Response[HTTPRequestType, HTTPResponseType], Any) -> None + """Is executed after the request comes back from the policy. + """ + pass + + def on_exception(self, request, **kwargs): + # type: (Request[HTTPRequestType], Any) -> bool + """Is executed if an exception comes back from the following + policy. + + Return True if the exception has been handled and should not + be forwarded to the caller. + + This method is executed inside the exception handler. + To get the exception, raise and catch it: + + try: + raise + except MyError: + do_something() + + or use + + exc_type, exc_value, exc_traceback = sys.exc_info() + """ + return False + +class _SansIOHTTPPolicyRunner(HTTPPolicy, Generic[HTTPRequestType, HTTPResponseType]): + """Sync implementation of the SansIO policy. + """ + + def __init__(self, policy): + # type: (SansIOHTTPPolicy) -> None + super(_SansIOHTTPPolicyRunner, self).__init__() + self._policy = policy + + def send(self, request, **kwargs): + # type: (Request[HTTPRequestType], Any) -> Response[HTTPRequestType, HTTPResponseType] + self._policy.on_request(request, **kwargs) + try: + response = self.next.send(request, **kwargs) + except Exception: + if not self._policy.on_exception(request, **kwargs): + raise + else: + self._policy.on_response(request, response, **kwargs) + return response + +class Pipeline(AbstractContextManager, Generic[HTTPRequestType, HTTPResponseType]): + """A pipeline implementation. + + This is implemented as a context manager, that will activate the context + of the HTTP sender. + """ + + def __init__(self, policies=None, sender=None): + # type: (List[Union[HTTPPolicy, SansIOHTTPPolicy]], HTTPSender) -> None + self._impl_policies = [] # type: List[HTTPPolicy] + if not sender: + # Import default only if nothing is provided + from .requests import PipelineRequestsHTTPSender + self._sender = cast(HTTPSender, PipelineRequestsHTTPSender()) + else: + self._sender = sender + for policy in (policies or []): + if isinstance(policy, SansIOHTTPPolicy): + self._impl_policies.append(_SansIOHTTPPolicyRunner(policy)) + else: + self._impl_policies.append(policy) + for index in range(len(self._impl_policies)-1): + self._impl_policies[index].next = self._impl_policies[index+1] + if self._impl_policies: + self._impl_policies[-1].next = self._sender + + def __enter__(self): + # type: () -> Pipeline + self._sender.__enter__() + return self + + def __exit__(self, *exc_details): # pylint: disable=arguments-differ + self._sender.__exit__(*exc_details) + + def run(self, request, **kwargs): + # type: (HTTPRequestType, Any) -> Response + context = self._sender.build_context() + pipeline_request = Request(request, context) # type: Request[HTTPRequestType] + first_node = self._impl_policies[0] if self._impl_policies else self._sender + return first_node.send(pipeline_request, **kwargs) # type: ignore + +class HTTPSender(AbstractContextManager, ABC, Generic[HTTPRequestType, HTTPResponseType]): + """An http sender ABC. + """ + + @abc.abstractmethod + def send(self, request, **config): + # type: (Request[HTTPRequestType], Any) -> Response[HTTPRequestType, HTTPResponseType] + """Send the request using this HTTP sender. + """ + pass + + def build_context(self): + # type: () -> 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 + + +class Request(Generic[HTTPRequestType]): + """Represents a HTTP request in a Pipeline. + + 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, http_request, context=None): + # type: (HTTPRequestType, Optional[Any]) -> None + self.http_request = http_request + self.context = context + + +class Response(Generic[HTTPRequestType, HTTPResponseType]): + """A pipeline response object. + + The Response interface exposes an HTTP response object as it returns through the pipeline of Policy objects. + This ensures that Policy objects have access to the HTTP response. + + This also have a "context" dictionary where policy can put additional fields. + Policy SHOULD update the "context" dictionary with additional post-processed field if they create them. + However, nothing prevents a policy to actually sub-class this class a return it instead of the initial instance. + """ + def __init__(self, request, http_response, context=None): + # type: (Request[HTTPRequestType], HTTPResponseType, Optional[Dict[str, Any]]) -> None + self.request = request + self.http_response = http_response + self.context = context or {} + + +# ClientRawResponse is in Pipeline for compat, but technically there is nothing Pipeline here, this is deserialization + +if TYPE_CHECKING: + from ..universal_http import ClientResponse + +class ClientRawResponse(object): + """Wrapper for response object. + This allows for additional data to be gathereded from the response, + for example deserialized headers. + It also allows the raw response object to be passed back to the user. + + :param output: Deserialized response object. This is the type that would have been returned + directly by the main operation without raw=True. + :param response: Raw response object (by default requests.Response instance) + :type response: ~requests.Response + """ + + def __init__(self, output, response): + # type: (Union[Any], Optional[Union[Response, ClientResponse]]) -> None + from ..serialization import Deserializer + + if isinstance(response, Response): + # If pipeline response, remove that layer + response = response.http_response + + try: + # If universal driver, remove that layer + self.response = response.internal_response # type: ignore + except AttributeError: + self.response = response + + self.output = output + self.headers = {} # type: Dict[str, Optional[Any]] + self._deserialize = Deserializer() + + def add_headers(self, header_dict): + # type: (Dict[str, str]) -> None + """Deserialize a specific header. + + :param dict header_dict: A dictionary containing the name of the + header and the type to deserialize to. + """ + if not self.response: + return + for name, data_type in header_dict.items(): + value = self.response.headers.get(name) + value = self._deserialize(data_type, value) + self.headers[name] = value + + + +__all__ = [ + 'Request', + 'Response', + 'Pipeline', + 'HTTPPolicy', + 'SansIOHTTPPolicy', + 'HTTPSender', + # backward compat + 'ClientRawResponse', +] + +try: + from .async_abc import AsyncPipeline, AsyncHTTPPolicy, AsyncHTTPSender # 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/pipeline/aiohttp.py b/.venv/lib/python3.12/site-packages/msrest/pipeline/aiohttp.py new file mode 100644 index 00000000..cefb1a5a --- /dev/null +++ b/.venv/lib/python3.12/site-packages/msrest/pipeline/aiohttp.py @@ -0,0 +1,62 @@ +# -------------------------------------------------------------------------- +# +# 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, Optional + +from ..universal_http.aiohttp import AioHTTPSender as _AioHTTPSenderDriver +from . import AsyncHTTPSender, Request, Response + +# Matching requests, because why not? +CONTENT_CHUNK_SIZE = 10 * 1024 + +class AioHTTPSender(AsyncHTTPSender): + """AioHttp HTTP sender implementation. + """ + + def __init__(self, driver: Optional[_AioHTTPSenderDriver] = None, *, loop=None) -> None: + self.driver = driver or _AioHTTPSenderDriver(loop=loop) + + async def __aenter__(self): + await self.driver.__aenter__() + + async def __aexit__(self, *exc_details): # pylint: disable=arguments-differ + await self.driver.__aexit__(*exc_details) + + 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 + + async def send(self, request: Request, **config: Any) -> Response: + """Send the request using this HTTP sender. + """ + return Response( + request, + await self.driver.send(request.http_request) + ) diff --git a/.venv/lib/python3.12/site-packages/msrest/pipeline/async_abc.py b/.venv/lib/python3.12/site-packages/msrest/pipeline/async_abc.py new file mode 100644 index 00000000..3fa73dda --- /dev/null +++ b/.venv/lib/python3.12/site-packages/msrest/pipeline/async_abc.py @@ -0,0 +1,165 @@ +# -------------------------------------------------------------------------- +# +# 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, Generic, TypeVar + +from . import Request, Response, Pipeline, SansIOHTTPPolicy + + +AsyncHTTPResponseType = TypeVar("AsyncHTTPResponseType") +HTTPRequestType = TypeVar("HTTPRequestType") + +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 + + + + +class AsyncHTTPPolicy(abc.ABC, Generic[HTTPRequestType, AsyncHTTPResponseType]): + """An http policy ABC. + """ + def __init__(self) -> None: + # next will be set once in the pipeline + self.next = None # type: Optional[Union[AsyncHTTPPolicy[HTTPRequestType, AsyncHTTPResponseType], AsyncHTTPSender[HTTPRequestType, AsyncHTTPResponseType]]] + + @abc.abstractmethod + async def send(self, request: Request, **kwargs: Any) -> Response[HTTPRequestType, AsyncHTTPResponseType]: + """Mutate the request. + + Context content is dependent of the HTTPSender. + """ + pass + + +class _SansIOAsyncHTTPPolicyRunner(AsyncHTTPPolicy[HTTPRequestType, AsyncHTTPResponseType]): + """Async implementation of the SansIO policy. + """ + + def __init__(self, policy: SansIOHTTPPolicy) -> None: + super(_SansIOAsyncHTTPPolicyRunner, self).__init__() + self._policy = policy + + async def send(self, request: Request, **kwargs: Any) -> Response[HTTPRequestType, AsyncHTTPResponseType]: + self._policy.on_request(request, **kwargs) + try: + response = await self.next.send(request, **kwargs) # type: ignore + except Exception: + if not self._policy.on_exception(request, **kwargs): + raise + else: + self._policy.on_response(request, response, **kwargs) + return response + + +class AsyncHTTPSender(AbstractAsyncContextManager, abc.ABC, Generic[HTTPRequestType, AsyncHTTPResponseType]): + """An http sender ABC. + """ + + @abc.abstractmethod + async def send(self, request: Request[HTTPRequestType], **config: Any) -> Response[HTTPRequestType, AsyncHTTPResponseType]: + """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 + + +class AsyncPipeline(AbstractAsyncContextManager, Generic[HTTPRequestType, AsyncHTTPResponseType]): + """A pipeline implementation. + + This is implemented as a context manager, that will activate the context + of the HTTP sender. + """ + + def __init__(self, policies: List[Union[AsyncHTTPPolicy, SansIOHTTPPolicy]] = None, sender: Optional[AsyncHTTPSender[HTTPRequestType, AsyncHTTPResponseType]] = None) -> None: + self._impl_policies = [] # type: List[AsyncHTTPPolicy[HTTPRequestType, AsyncHTTPResponseType]] + if sender: + self._sender = sender + else: + # Import default only if nothing is provided + from .aiohttp import AioHTTPSender + self._sender = AioHTTPSender() + + for policy in (policies or []): + if isinstance(policy, SansIOHTTPPolicy): + self._impl_policies.append(_SansIOAsyncHTTPPolicyRunner(policy)) + else: + self._impl_policies.append(policy) + for index in range(len(self._impl_policies)-1): + self._impl_policies[index].next = self._impl_policies[index+1] + if self._impl_policies: + self._impl_policies[-1].next = self._sender + + 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 + + async def __aenter__(self) -> 'AsyncPipeline': + await self._sender.__aenter__() + return self + + async def __aexit__(self, *exc_details): # pylint: disable=arguments-differ + await self._sender.__aexit__(*exc_details) + + async def run(self, request: Request, **kwargs: Any) -> Response[HTTPRequestType, AsyncHTTPResponseType]: + context = self._sender.build_context() + pipeline_request = Request(request, context) + first_node = self._impl_policies[0] if self._impl_policies else self._sender + return await first_node.send(pipeline_request, **kwargs) # type: ignore + +__all__ = [ + 'AsyncHTTPPolicy', + 'AsyncHTTPSender', + 'AsyncPipeline', +]
\ No newline at end of file diff --git a/.venv/lib/python3.12/site-packages/msrest/pipeline/async_requests.py b/.venv/lib/python3.12/site-packages/msrest/pipeline/async_requests.py new file mode 100644 index 00000000..d5d9ec2f --- /dev/null +++ b/.venv/lib/python3.12/site-packages/msrest/pipeline/async_requests.py @@ -0,0 +1,129 @@ +# -------------------------------------------------------------------------- +# +# 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 ..universal_http.async_requests import AsyncBasicRequestsHTTPSender +from . import AsyncHTTPSender, AsyncHTTPPolicy, Response, Request +from .requests import RequestsContext + + +_LOGGER = logging.getLogger(__name__) + + +class AsyncPipelineRequestsHTTPSender(AsyncHTTPSender): + """Implements a basic Pipeline, that supports universal HTTP lib "requests" driver. + """ + + def __init__(self, universal_http_requests_driver: Optional[AsyncBasicRequestsHTTPSender]=None) -> None: + self.driver = universal_http_requests_driver or AsyncBasicRequestsHTTPSender() + + async def __aenter__(self) -> 'AsyncPipelineRequestsHTTPSender': + await self.driver.__aenter__() + return self + + async def __aexit__(self, *exc_details): # pylint: disable=arguments-differ + await self.driver.__aexit__(*exc_details) + + async def close(self): + await self.__aexit__() + + def build_context(self): + # type: () -> RequestsContext + return RequestsContext( + session=self.driver.session, + ) + + async def send(self, request: Request, **kwargs) -> Response: + """Send request object according to configuration. + + :param Request request: The request object to be sent. + """ + if request.context is None: # Should not happen, but make mypy happy and does not hurt + request.context = self.build_context() + + if request.context.session is not self.driver.session: + kwargs['session'] = request.context.session + + return Response( + request, + await self.driver.send(request.http_request, **kwargs) + ) + + +class AsyncRequestsCredentialsPolicy(AsyncHTTPPolicy): + """Implementation of request-oauthlib except and retry logic. + """ + def __init__(self, credentials): + super(AsyncRequestsCredentialsPolicy, self).__init__() + self._creds = credentials + + async def send(self, request, **kwargs): + session = request.context.session + try: + self._creds.signed_session(session) + except TypeError: # Credentials does not support session injection + _LOGGER.warning("Your credentials class does not support session injection. Performance will not be at the maximum.") + request.context.session = session = self._creds.signed_session() + + try: + try: + return await self.next.send(request, **kwargs) + except (oauth2.rfc6749.errors.InvalidGrantError, + oauth2.rfc6749.errors.TokenExpiredError) as err: + error = "Token expired or is invalid. Attempting to refresh." + _LOGGER.warning(error) + + try: + try: + self._creds.refresh_session(session) + except TypeError: # Credentials does not support session injection + _LOGGER.warning("Your credentials class does not support session injection. Performance will not be at the maximum.") + request.context.session = session = self._creds.refresh_session() + + return await self.next.send(request, **kwargs) + except (oauth2.rfc6749.errors.InvalidGrantError, + oauth2.rfc6749.errors.TokenExpiredError) as err: + msg = "Token expired or is invalid." + raise_with_traceback(TokenExpiredError, msg, err) + + except (requests.RequestException, + oauth2.rfc6749.errors.OAuth2Error) as err: + msg = "Error occurred in request." + raise_with_traceback(ClientRequestError, msg, err) + diff --git a/.venv/lib/python3.12/site-packages/msrest/pipeline/requests.py b/.venv/lib/python3.12/site-packages/msrest/pipeline/requests.py new file mode 100644 index 00000000..2dac5a3b --- /dev/null +++ b/.venv/lib/python3.12/site-packages/msrest/pipeline/requests.py @@ -0,0 +1,194 @@ +# -------------------------------------------------------------------------- +# +# 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 + +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 ..universal_http import ClientRequest +from ..universal_http.requests import BasicRequestsHTTPSender +from . import HTTPSender, HTTPPolicy, Response, Request + + +_LOGGER = logging.getLogger(__name__) + + +class RequestsCredentialsPolicy(HTTPPolicy): + """Implementation of request-oauthlib except and retry logic. + """ + def __init__(self, credentials): + super(RequestsCredentialsPolicy, self).__init__() + self._creds = credentials + + def send(self, request, **kwargs): + session = request.context.session + try: + self._creds.signed_session(session) + except TypeError: # Credentials does not support session injection + _LOGGER.warning("Your credentials class does not support session injection. Performance will not be at the maximum.") + request.context.session = session = self._creds.signed_session() + + try: + try: + return self.next.send(request, **kwargs) + except (oauth2.rfc6749.errors.InvalidGrantError, + oauth2.rfc6749.errors.TokenExpiredError) as err: + error = "Token expired or is invalid. Attempting to refresh." + _LOGGER.warning(error) + + try: + try: + self._creds.refresh_session(session) + except TypeError: # Credentials does not support session injection + _LOGGER.warning("Your credentials class does not support session injection. Performance will not be at the maximum.") + request.context.session = session = self._creds.refresh_session() + + return self.next.send(request, **kwargs) + except (oauth2.rfc6749.errors.InvalidGrantError, + oauth2.rfc6749.errors.TokenExpiredError) as err: + msg = "Token expired or is invalid." + raise_with_traceback(TokenExpiredError, msg, err) + + except (requests.RequestException, + oauth2.rfc6749.errors.OAuth2Error) as err: + msg = "Error occurred in request." + raise_with_traceback(ClientRequestError, msg, err) + +class RequestsPatchSession(HTTPPolicy): + """Implements request level configuration + that are actually to be done at the session level. + + This is highly deprecated, and is totally legacy. + The pipeline structure allows way better design for this. + """ + _protocols = ['http://', 'https://'] + + def send(self, request, **kwargs): + """Patch the current session with Request level operation config. + + This is deprecated, we shouldn't patch the session with + arguments at the Request, and "config" should be used. + """ + session = request.context.session + + old_max_redirects = None + if 'max_redirects' in kwargs: + warnings.warn("max_redirects in operation kwargs is deprecated, use config.redirect_policy instead", + DeprecationWarning) + old_max_redirects = session.max_redirects + session.max_redirects = int(kwargs['max_redirects']) + + old_trust_env = None + if 'use_env_proxies' in kwargs: + warnings.warn("use_env_proxies in operation kwargs is deprecated, use config.proxies instead", + DeprecationWarning) + old_trust_env = session.trust_env + session.trust_env = bool(kwargs['use_env_proxies']) + + old_retries = {} + if 'retries' in kwargs: + warnings.warn("retries in operation kwargs is deprecated, use config.retry_policy instead", + DeprecationWarning) + max_retries = kwargs['retries'] + for protocol in self._protocols: + old_retries[protocol] = session.adapters[protocol].max_retries + session.adapters[protocol].max_retries = max_retries + + try: + return self.next.send(request, **kwargs) + finally: + if old_max_redirects: + session.max_redirects = old_max_redirects + + if old_trust_env: + session.trust_env = old_trust_env + + if old_retries: + for protocol in self._protocols: + session.adapters[protocol].max_retries = old_retries[protocol] + +class RequestsContext(object): + def __init__(self, session): + self.session = session + + +class PipelineRequestsHTTPSender(HTTPSender): + """Implements a basic Pipeline, that supports universal HTTP lib "requests" driver. + """ + + def __init__(self, universal_http_requests_driver=None): + # type: (Optional[BasicRequestsHTTPSender]) -> None + self.driver = universal_http_requests_driver or BasicRequestsHTTPSender() + + def __enter__(self): + # type: () -> PipelineRequestsHTTPSender + self.driver.__enter__() + return self + + def __exit__(self, *exc_details): # pylint: disable=arguments-differ + self.driver.__exit__(*exc_details) + + def close(self): + self.__exit__() + + def build_context(self): + # type: () -> RequestsContext + return RequestsContext( + session=self.driver.session, + ) + + def send(self, request, **kwargs): + # type: (Request[ClientRequest], Any) -> Response + """Send request object according to configuration. + + :param Request request: The request object to be sent. + """ + if request.context is None: # Should not happen, but make mypy happy and does not hurt + request.context = self.build_context() + + if request.context.session is not self.driver.session: + kwargs['session'] = request.context.session + + return Response( + request, + self.driver.send(request.http_request, **kwargs) + ) diff --git a/.venv/lib/python3.12/site-packages/msrest/pipeline/universal.py b/.venv/lib/python3.12/site-packages/msrest/pipeline/universal.py new file mode 100644 index 00000000..b8dc40c9 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/msrest/pipeline/universal.py @@ -0,0 +1,253 @@ +# -------------------------------------------------------------------------- +# +# 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 represents universal policy that works whatever the HTTPSender implementation +""" +import json +import logging +import os +import xml.etree.ElementTree as ET +import platform +import codecs +import re + +from typing import Mapping, Any, Optional, AnyStr, Union, IO, cast, TYPE_CHECKING # pylint: disable=unused-import + +from ..version import msrest_version as _msrest_version +from . import SansIOHTTPPolicy +from ..exceptions import DeserializationError, raise_with_traceback +from ..http_logger import log_request, log_response + +if TYPE_CHECKING: + from . import Request, Response # pylint: disable=unused-import + + +_LOGGER = logging.getLogger(__name__) + +_BOM = codecs.BOM_UTF8.decode(encoding='utf-8') + + +class HeadersPolicy(SansIOHTTPPolicy): + """A simple policy that sends the given headers + with the request. + + This overwrite any headers already defined in the request. + """ + def __init__(self, headers): + # type: (Mapping[str, str]) -> None + self.headers = headers + + def on_request(self, request, **kwargs): + # type: (Request, Any) -> None + http_request = request.http_request + http_request.headers.update(self.headers) + +class UserAgentPolicy(SansIOHTTPPolicy): + _USERAGENT = "User-Agent" + _ENV_ADDITIONAL_USER_AGENT = 'AZURE_HTTP_USER_AGENT' + + def __init__(self, user_agent=None, overwrite=False): + # type: (Optional[str], bool) -> None + self._overwrite = overwrite + if user_agent is None: + self._user_agent = "python/{} ({}) msrest/{}".format( + platform.python_version(), + platform.platform(), + _msrest_version + ) + else: + self._user_agent = user_agent + + # Whatever you gave me a header explicitly or not, + # if the env variable is set, add to it. + add_user_agent_header = os.environ.get(self._ENV_ADDITIONAL_USER_AGENT, None) + if add_user_agent_header is not None: + self.add_user_agent(add_user_agent_header) + + @property + def user_agent(self): + # type: () -> str + """The current user agent value.""" + return self._user_agent + + def add_user_agent(self, value): + # type: (str) -> None + """Add value to current user agent with a space. + + :param str value: value to add to user agent. + """ + self._user_agent = "{} {}".format(self._user_agent, value) + + def on_request(self, request, **kwargs): + # type: (Request, Any) -> None + http_request = request.http_request + if self._overwrite or self._USERAGENT not in http_request.headers: + http_request.headers[self._USERAGENT] = self._user_agent + +class HTTPLogger(SansIOHTTPPolicy): + """A policy that logs HTTP request and response to the DEBUG logger. + + This accepts both global configuration, and kwargs request level with "enable_http_logger" + """ + def __init__(self, enable_http_logger = False): + self.enable_http_logger = enable_http_logger + + def on_request(self, request, **kwargs): + # type: (Request, Any) -> None + http_request = request.http_request + if kwargs.get("enable_http_logger", self.enable_http_logger): + log_request(None, http_request) + + def on_response(self, request, response, **kwargs): + # type: (Request, Response, Any) -> None + http_request = request.http_request + if kwargs.get("enable_http_logger", self.enable_http_logger): + log_response(None, http_request, response.http_response, result=response) + + +class RawDeserializer(SansIOHTTPPolicy): + + # Accept "text" because we're open minded people... + JSON_REGEXP = re.compile(r'^(application|text)/([a-z+.]+\+)?json$') + + # Name used in context + CONTEXT_NAME = "deserialized_data" + + @classmethod + def deserialize_from_text(cls, data, content_type=None): + # type: (Optional[Union[AnyStr, IO]], Optional[str]) -> Any + """Decode data according to content-type. + + Accept a stream of data as well, but will be load at once in memory for now. + + If no content-type, will return the string version (not bytes, not stream) + + :param data: Input, could be bytes or stream (will be decoded with UTF8) or text + :type data: str or bytes or IO + :param str content_type: The content type. + """ + if hasattr(data, 'read'): + # Assume a stream + data = cast(IO, data).read() + + if isinstance(data, bytes): + data_as_str = data.decode(encoding='utf-8-sig') + else: + # Explain to mypy the correct type. + data_as_str = cast(str, data) + + # Remove Byte Order Mark if present in string + data_as_str = data_as_str.lstrip(_BOM) + + if content_type is None: + return data + + if cls.JSON_REGEXP.match(content_type): + try: + return json.loads(data_as_str) + except ValueError as err: + raise DeserializationError("JSON is invalid: {}".format(err), err) + elif "xml" in (content_type or []): + try: + + try: + if isinstance(data, unicode): # type: ignore + # If I'm Python 2.7 and unicode XML will scream if I try a "fromstring" on unicode string + data_as_str = data_as_str.encode(encoding="utf-8") # type: ignore + except NameError: + pass + + return ET.fromstring(data_as_str) + except ET.ParseError: + # It might be because the server has an issue, and returned JSON with + # content-type XML.... + # So let's try a JSON load, and if it's still broken + # let's flow the initial exception + def _json_attemp(data): + try: + return True, json.loads(data) + except ValueError: + return False, None # Don't care about this one + success, json_result = _json_attemp(data) + if success: + return json_result + # If i'm here, it's not JSON, it's not XML, let's scream + # and raise the last context in this block (the XML exception) + # The function hack is because Py2.7 messes up with exception + # context otherwise. + _LOGGER.critical("Wasn't XML not JSON, failing") + raise_with_traceback(DeserializationError, "XML is invalid") + raise DeserializationError("Cannot deserialize content-type: {}".format(content_type)) + + @classmethod + def deserialize_from_http_generics(cls, body_bytes, headers): + # type: (Optional[Union[AnyStr, IO]], Mapping) -> Any + """Deserialize from HTTP response. + + Use bytes and headers to NOT use any requests/aiohttp or whatever + specific implementation. + Headers will tested for "content-type" + """ + # Try to use content-type from headers if available + content_type = None + if 'content-type' in headers: + content_type = headers['content-type'].split(";")[0].strip().lower() + # Ouch, this server did not declare what it sent... + # Let's guess it's JSON... + # Also, since Autorest was considering that an empty body was a valid JSON, + # need that test as well.... + else: + content_type = "application/json" + + if body_bytes: + return cls.deserialize_from_text(body_bytes, content_type) + return None + + def on_response(self, request, response, **kwargs): + # type: (Request, Response, Any) -> None + """Extract data from the body of a REST response object. + + This will load the entire payload in memory. + + Will follow Content-Type to parse. + We assume everything is UTF8 (BOM acceptable). + + :param raw_data: Data to be processed. + :param content_type: How to parse if raw_data is a string/bytes. + :raises JSONDecodeError: If JSON is requested and parsing is impossible. + :raises UnicodeDecodeError: If bytes is not UTF8 + :raises xml.etree.ElementTree.ParseError: If bytes is not valid XML + """ + # If response was asked as stream, do NOT read anything and quit now + if kwargs.get("stream", True): + return + + http_response = response.http_response + + response.context[self.CONTEXT_NAME] = self.deserialize_from_http_generics( + http_response.text(), + http_response.headers + ) |