codekingpro/portable-devtools
114k
1# --------------------------------------------------------------------------2#3# Copyright (c) Microsoft Corporation. All rights reserved.4#5# The MIT License (MIT)6#7# Permission is hereby granted, free of charge, to any person obtaining a copy8# of this software and associated documentation files (the ""Software""), to9# deal in the Software without restriction, including without limitation the10# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or11# sell copies of the Software, and to permit persons to whom the Software is12# furnished to do so, subject to the following conditions:13#14# The above copyright notice and this permission notice shall be included in15# all copies or substantial portions of the Software.16#17# THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR18# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,19# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE20# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER21# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING22# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS23# IN THE SOFTWARE.24#25# --------------------------------------------------------------------------26from __future__ import absolute_import # we have a "requests" module that conflicts with "requests" on Py2.727import abc28try:29 import configparser30 from configparser import NoOptionError31except ImportError:32 import ConfigParser as configparser # type: ignore33 from ConfigParser import NoOptionError # type: ignore34import json35import logging36import os.path37try:38 from urlparse import urlparse39except ImportError:40 from urllib.parse import urlparse41import xml.etree.ElementTree as ET42 43from typing import TYPE_CHECKING, Generic, TypeVar, cast, IO, List, Union, Any, Mapping, Dict, Optional, Tuple, Callable, Iterator # pylint: disable=unused-import44 45HTTPResponseType = TypeVar("HTTPResponseType")46HTTPRequestType = TypeVar("HTTPRequestType")47 48# This file is NOT using any "requests" HTTP implementation49# However, the CaseInsensitiveDict is handy.50# If one day we reach the point where "requests" can be skip totally,51# might provide our own implementation52from requests.structures import CaseInsensitiveDict53 54 55_LOGGER = logging.getLogger(__name__)56 57try:58 ABC = abc.ABC59except AttributeError: # Python 2.7, abc exists, but not ABC60 ABC = abc.ABCMeta('ABC', (object,), {'__slots__': ()}) # type: ignore61 62try:63 from contextlib import AbstractContextManager # type: ignore64except ImportError: # Python <= 3.565 class AbstractContextManager(object): # type: ignore66 def __enter__(self):67 """Return `self` upon entering the runtime context."""68 return self69 70 @abc.abstractmethod71 def __exit__(self, exc_type, exc_value, traceback):72 """Raise any exception triggered within the runtime context."""73 return None74 75class HTTPPolicy(ABC, Generic[HTTPRequestType, HTTPResponseType]):76 """An http policy ABC.77 """78 def __init__(self):79 self.next = None80 81 @abc.abstractmethod82 def send(self, request, **kwargs):83 # type: (Request[HTTPRequestType], Any) -> Response[HTTPRequestType, HTTPResponseType]84 """Mutate the request.85 86 Context content is dependent of the HTTPSender.87 """88 pass89 90class SansIOHTTPPolicy(Generic[HTTPRequestType, HTTPResponseType]):91 """Represents a sans I/O policy.92 93 This policy can act before the I/O, and after the I/O.94 Use this policy if the actual I/O in the middle is an implementation95 detail.96 97 Context is not available, since it's implementation dependent.98 if a policy needs a context of the Sender, it can't be universal.99 100 Example: setting a UserAgent does not need to be tight to101 sync or async implementation or specific HTTP lib102 """103 def on_request(self, request, **kwargs):104 # type: (Request[HTTPRequestType], Any) -> None105 """Is executed before sending the request to next policy.106 """107 pass108 109 def on_response(self, request, response, **kwargs):110 # type: (Request[HTTPRequestType], Response[HTTPRequestType, HTTPResponseType], Any) -> None111 """Is executed after the request comes back from the policy.112 """113 pass114 115 def on_exception(self, request, **kwargs):116 # type: (Request[HTTPRequestType], Any) -> bool117 """Is executed if an exception comes back from the following118 policy.119 120 Return True if the exception has been handled and should not121 be forwarded to the caller.122 123 This method is executed inside the exception handler.124 To get the exception, raise and catch it:125 126 try:127 raise128 except MyError:129 do_something()130 131 or use132 133 exc_type, exc_value, exc_traceback = sys.exc_info()134 """135 return False136 137class _SansIOHTTPPolicyRunner(HTTPPolicy, Generic[HTTPRequestType, HTTPResponseType]):138 """Sync implementation of the SansIO policy.139 """140 141 def __init__(self, policy):142 # type: (SansIOHTTPPolicy) -> None143 super(_SansIOHTTPPolicyRunner, self).__init__()144 self._policy = policy145 146 def send(self, request, **kwargs):147 # type: (Request[HTTPRequestType], Any) -> Response[HTTPRequestType, HTTPResponseType]148 self._policy.on_request(request, **kwargs)149 try:150 response = self.next.send(request, **kwargs)151 except Exception:152 if not self._policy.on_exception(request, **kwargs):153 raise154 else:155 self._policy.on_response(request, response, **kwargs)156 return response157 158class Pipeline(AbstractContextManager, Generic[HTTPRequestType, HTTPResponseType]):159 """A pipeline implementation.160 161 This is implemented as a context manager, that will activate the context162 of the HTTP sender.163 """164 165 def __init__(self, policies=None, sender=None):166 # type: (List[Union[HTTPPolicy, SansIOHTTPPolicy]], HTTPSender) -> None167 self._impl_policies = [] # type: List[HTTPPolicy]168 if not sender:169 # Import default only if nothing is provided170 from .requests import PipelineRequestsHTTPSender171 self._sender = cast(HTTPSender, PipelineRequestsHTTPSender())172 else:173 self._sender = sender174 for policy in (policies or []):175 if isinstance(policy, SansIOHTTPPolicy):176 self._impl_policies.append(_SansIOHTTPPolicyRunner(policy))177 else:178 self._impl_policies.append(policy)179 for index in range(len(self._impl_policies)-1):180 self._impl_policies[index].next = self._impl_policies[index+1]181 if self._impl_policies:182 self._impl_policies[-1].next = self._sender183 184 def __enter__(self):185 # type: () -> Pipeline186 self._sender.__enter__()187 return self188 189 def __exit__(self, *exc_details): # pylint: disable=arguments-differ190 self._sender.__exit__(*exc_details)191 192 def run(self, request, **kwargs):193 # type: (HTTPRequestType, Any) -> Response194 context = self._sender.build_context()195 pipeline_request = Request(request, context) # type: Request[HTTPRequestType]196 first_node = self._impl_policies[0] if self._impl_policies else self._sender197 return first_node.send(pipeline_request, **kwargs) # type: ignore198 199class HTTPSender(AbstractContextManager, ABC, Generic[HTTPRequestType, HTTPResponseType]):200 """An http sender ABC.201 """202 203 @abc.abstractmethod204 def send(self, request, **config):205 # type: (Request[HTTPRequestType], Any) -> Response[HTTPRequestType, HTTPResponseType]206 """Send the request using this HTTP sender.207 """208 pass209 210 def build_context(self):211 # type: () -> Any212 """Allow the sender to build a context that will be passed213 across the pipeline with the request.214 215 Return type has no constraints. Implementation is not216 required and None by default.217 """218 return None219 220 221class Request(Generic[HTTPRequestType]):222 """Represents a HTTP request in a Pipeline.223 224 URL can be given without query parameters, to be added later using "format_parameters".225 226 Instance can be created without data, to be added later using "add_content"227 228 Instance can be created without files, to be added later using "add_formdata"229 230 :param str method: HTTP method (GET, HEAD, etc.)231 :param str url: At least complete scheme/host/path232 :param dict[str,str] headers: HTTP headers233 :param files: Files list.234 :param data: Body to be sent.235 :type data: bytes or str.236 """237 def __init__(self, http_request, context=None):238 # type: (HTTPRequestType, Optional[Any]) -> None239 self.http_request = http_request240 self.context = context241 242 243class Response(Generic[HTTPRequestType, HTTPResponseType]):244 """A pipeline response object.245 246 The Response interface exposes an HTTP response object as it returns through the pipeline of Policy objects.247 This ensures that Policy objects have access to the HTTP response.248 249 This also have a "context" dictionary where policy can put additional fields.250 Policy SHOULD update the "context" dictionary with additional post-processed field if they create them.251 However, nothing prevents a policy to actually sub-class this class a return it instead of the initial instance.252 """253 def __init__(self, request, http_response, context=None):254 # type: (Request[HTTPRequestType], HTTPResponseType, Optional[Dict[str, Any]]) -> None255 self.request = request256 self.http_response = http_response257 self.context = context or {}258 259 260# ClientRawResponse is in Pipeline for compat, but technically there is nothing Pipeline here, this is deserialization261 262if TYPE_CHECKING:263 from ..universal_http import ClientResponse264 265class ClientRawResponse(object):266 """Wrapper for response object.267 This allows for additional data to be gathereded from the response,268 for example deserialized headers.269 It also allows the raw response object to be passed back to the user.270 271 :param output: Deserialized response object. This is the type that would have been returned272 directly by the main operation without raw=True.273 :param response: Raw response object (by default requests.Response instance)274 :type response: ~requests.Response275 """276 277 def __init__(self, output, response):278 # type: (Union[Any], Optional[Union[Response, ClientResponse]]) -> None279 from ..serialization import Deserializer280 281 if isinstance(response, Response):282 # If pipeline response, remove that layer283 response = response.http_response284 285 try:286 # If universal driver, remove that layer287 self.response = response.internal_response # type: ignore288 except AttributeError:289 self.response = response290 291 self.output = output292 self.headers = {} # type: Dict[str, Optional[Any]]293 self._deserialize = Deserializer()294 295 def add_headers(self, header_dict):296 # type: (Dict[str, str]) -> None297 """Deserialize a specific header.298 299 :param dict header_dict: A dictionary containing the name of the300 header and the type to deserialize to.301 """302 if not self.response:303 return304 for name, data_type in header_dict.items():305 value = self.response.headers.get(name)306 value = self._deserialize(data_type, value)307 self.headers[name] = value308 309 310 311__all__ = [312 'Request',313 'Response',314 'Pipeline',315 'HTTPPolicy',316 'SansIOHTTPPolicy',317 'HTTPSender',318 # backward compat319 'ClientRawResponse',320]321 322try:323 from .async_abc import AsyncPipeline, AsyncHTTPPolicy, AsyncHTTPSender # pylint: disable=unused-import324 from .async_abc import __all__ as _async_all325 __all__ += _async_all326except SyntaxError: # Python 2327 pass328except ImportError: # pyinstaller won't include Py3 files in Py2.7 mode329 pass330 