Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
__init__.py461 linesDownload Raw Back to universal_http
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, MutableMapping  # pylint: disable=unused-import44 45HTTPResponseType = TypeVar("HTTPResponseType", bound='HTTPClientResponse')46 47# This file is NOT using any "requests" HTTP implementation48# However, the CaseInsensitiveDict is handy.49# If one day we reach the point where "requests" can be skip totally,50# might provide our own implementation51from requests.structures import CaseInsensitiveDict52 53from ..exceptions import ClientRequestError, raise_with_traceback54 55if TYPE_CHECKING:56    from ..serialization import Model  # pylint: disable=unused-import57 58 59_LOGGER = logging.getLogger(__name__)60 61try:62    ABC = abc.ABC63except AttributeError: # Python 2.7, abc exists, but not ABC64    ABC = abc.ABCMeta('ABC', (object,), {'__slots__': ()})  # type: ignore65 66try:67    from contextlib import AbstractContextManager  # type: ignore68except ImportError: # Python <= 3.569    class AbstractContextManager(object):  # type: ignore70        def __enter__(self):71            """Return `self` upon entering the runtime context."""72            return self73 74        @abc.abstractmethod75        def __exit__(self, exc_type, exc_value, traceback):76            """Raise any exception triggered within the runtime context."""77            return None78 79 80class HTTPSender(AbstractContextManager, ABC):81    """An http sender ABC.82    """83 84    @abc.abstractmethod85    def send(self, request, **config):86        # type: (ClientRequest, Any) -> ClientResponse87        """Send the request using this HTTP sender.88        """89        pass90 91 92class HTTPSenderConfiguration(object):93    """HTTP sender configuration.94 95    This is composed of generic HTTP configuration, and could be use as a common96    HTTP configuration format.97 98    :param str filepath: Path to existing config file (optional).99    """100 101    def __init__(self, filepath=None):102        # Communication configuration103        self.connection = ClientConnection()104 105        # Headers (sent with every requests)106        self.headers = {}  # type: Dict[str, str]107 108        # ProxyConfiguration109        self.proxies = ClientProxies()110 111        # Redirect configuration112        self.redirect_policy = ClientRedirectPolicy()113 114        self._config = configparser.ConfigParser()115        self._config.optionxform = str  # type: ignore116 117        if filepath:118            self.load(filepath)119 120    def _clear_config(self):121        # type: () -> None122        """Clearout config object in memory."""123        for section in self._config.sections():124            self._config.remove_section(section)125 126    def save(self, filepath):127        # type: (str) -> None128        """Save current configuration to file.129 130        :param str filepath: Path to file where settings will be saved.131        :raises: ValueError if supplied filepath cannot be written to.132        """133        sections = [134            "Connection",135            "Proxies",136            "RedirectPolicy"]137        for section in sections:138            self._config.add_section(section)139 140        self._config.set("Connection", "timeout", self.connection.timeout)141        self._config.set("Connection", "verify", self.connection.verify)142        self._config.set("Connection", "cert", self.connection.cert)143 144        self._config.set("Proxies", "proxies", self.proxies.proxies)145        self._config.set("Proxies", "env_settings",146                         self.proxies.use_env_settings)147 148        self._config.set("RedirectPolicy", "allow", self.redirect_policy.allow)149        self._config.set("RedirectPolicy", "max_redirects",150                         self.redirect_policy.max_redirects)151 152        try:153            with open(filepath, 'w') as configfile:154                self._config.write(configfile)155        except (KeyError, EnvironmentError):156            error = "Supplied config filepath invalid."157            raise_with_traceback(ValueError, error)158        finally:159            self._clear_config()160 161    def load(self, filepath):162        # type: (str) -> None163        """Load configuration from existing file.164 165        :param str filepath: Path to existing config file.166        :raises: ValueError if supplied config file is invalid.167        """168        try:169            self._config.read(filepath)170            import ast171            self.connection.timeout = \172                self._config.getint("Connection", "timeout")173            self.connection.verify = \174                self._config.getboolean("Connection", "verify")175            self.connection.cert = \176                self._config.get("Connection", "cert")177 178            self.proxies.proxies = \179                ast.literal_eval(self._config.get("Proxies", "proxies"))180            self.proxies.use_env_settings = \181                self._config.getboolean("Proxies", "env_settings")182 183            self.redirect_policy.allow = \184                self._config.getboolean("RedirectPolicy", "allow")185            self.redirect_policy.max_redirects = \186                self._config.getint("RedirectPolicy", "max_redirects")187 188        except (ValueError, EnvironmentError, NoOptionError):189            error = "Supplied config file incompatible."190            raise_with_traceback(ValueError, error)191        finally:192            self._clear_config()193 194 195class ClientRequest(object):196    """Represents a HTTP request.197 198    URL can be given without query parameters, to be added later using "format_parameters".199 200    Instance can be created without data, to be added later using "add_content"201 202    Instance can be created without files, to be added later using "add_formdata"203 204    :param str method: HTTP method (GET, HEAD, etc.)205    :param str url: At least complete scheme/host/path206    :param dict[str,str] headers: HTTP headers207    :param files: Files list.208    :param data: Body to be sent.209    :type data: bytes or str.210    """211    def __init__(self, method, url, headers=None, files=None, data=None):212        # type: (str, str, Mapping[str, str], Any, Any) -> None213        self.method = method214        self.url = url215        self.headers = CaseInsensitiveDict(headers)216        self.files = files217        self.data = data218 219    def __repr__(self):220        return '<ClientRequest [%s]>' % (self.method)221 222    @property223    def body(self):224        """Alias to data."""225        return self.data226 227    @body.setter228    def body(self, value):229        self.data = value230 231    def format_parameters(self, params):232        # type: (Dict[str, str]) -> None233        """Format parameters into a valid query string.234        It's assumed all parameters have already been quoted as235        valid URL strings.236 237        :param dict params: A dictionary of parameters.238        """239        query = urlparse(self.url).query240        if query:241            self.url = self.url.partition('?')[0]242            existing_params = {243                p[0]: p[-1]244                for p in [p.partition('=') for p in query.split('&')]245            }246            params.update(existing_params)247        query_params = ["{}={}".format(k, v) for k, v in params.items()]248        query = '?' + '&'.join(query_params)249        self.url = self.url + query250 251    def add_content(self, data):252        # type: (Optional[Union[Dict[str, Any], ET.Element]]) -> None253        """Add a body to the request.254 255        :param data: Request body data, can be a json serializable256         object (e.g. dictionary) or a generator (e.g. file data).257        """258        if data is None:259            return260 261        if isinstance(data, ET.Element):262            bytes_data = ET.tostring(data, encoding="utf8")263            self.headers['Content-Length'] = str(len(bytes_data))264            self.data = bytes_data265            return266 267        # By default, assume JSON268        try:269            self.data = json.dumps(data)270            self.headers['Content-Length'] = str(len(self.data))271        except TypeError:272            self.data = data273 274    @staticmethod275    def _format_data(data):276        # type: (Union[str, IO]) -> Union[Tuple[None, str], Tuple[Optional[str], IO, str]]277        """Format field data according to whether it is a stream or278        a string for a form-data request.279 280        :param data: The request field data.281        :type data: str or file-like object.282        """283        if hasattr(data, 'read'):284            data = cast(IO, data)285            data_name = None286            try:287                if data.name[0] != '<' and data.name[-1] != '>':288                    data_name = os.path.basename(data.name)289            except (AttributeError, TypeError):290                pass291            return (data_name, data, "application/octet-stream")292        return (None, cast(str, data))293 294    def add_formdata(self, content=None):295        # type: (Optional[Dict[str, str]]) -> None296        """Add data as a multipart form-data request to the request.297 298        We only deal with file-like objects or strings at this point.299        The requests is not yet streamed.300 301        :param dict headers: Any headers to add to the request.302        :param dict content: Dictionary of the fields of the formdata.303        """304        if content is None:305            content = {}306        content_type = self.headers.pop('Content-Type', None) if self.headers else None307 308        if content_type and content_type.lower() == 'application/x-www-form-urlencoded':309            # Do NOT use "add_content" that assumes input is JSON310            self.data = {f: d for f, d in content.items() if d is not None}311        else: # Assume "multipart/form-data"312            self.files = {f: self._format_data(d) for f, d in content.items() if d is not None}313 314class HTTPClientResponse(object):315    """Represent a HTTP response.316 317    No body is defined here on purpose, since async pipeline318    will provide async ways to access the body319 320    You have two different types of body:321    - Full in-memory using "body" as bytes322    """323    def __init__(self, request, internal_response):324        # type: (ClientRequest, Any) -> None325        self.request = request326        self.internal_response = internal_response327        self.status_code = None  # type: Optional[int]328        self.headers = {}  # type: MutableMapping[str, str]329        self.reason = None  # type: Optional[str]330 331    def body(self):332        # type: () -> bytes333        """Return the whole body as bytes in memory.334        """335        pass336 337    def text(self, encoding=None):338        # type: (str) -> str339        """Return the whole body as a string.340 341        :param str encoding: The encoding to apply. If None, use "utf-8-sig".342         Implementation can be smarter if they want (using headers).343        """344        return self.body().decode(encoding or "utf-8-sig")345 346    def raise_for_status(self):347        """Raise for status. Should be overridden, but basic implementation provided.348        """349        if self.status_code >= 400:350            raise ClientRequestError("Received status code {}".format(self.status_code))351 352 353class ClientResponse(HTTPClientResponse):354 355    def stream_download(self, chunk_size=None, callback=None):356        # type: (Optional[int], Optional[Callable]) -> Iterator[bytes]357        """Generator for streaming request body data.358 359        Should be implemented by sub-classes if streaming download360        is supported.361 362        :param callback: Custom callback for monitoring progress.363        :param int chunk_size:364        """365        pass366 367 368class ClientRedirectPolicy(object):369    """Redirect configuration settings.370    """371 372    def __init__(self):373        self.allow = True374        self.max_redirects = 30375 376    def __bool__(self):377        # type: () -> bool378        """Whether redirects are allowed."""379        return self.allow380 381    def __call__(self):382        # type: () -> int383        """Return configuration to be applied to connection."""384        debug = "Configuring redirects: allow=%r, max=%r"385        _LOGGER.debug(debug, self.allow, self.max_redirects)386        return self.max_redirects387 388 389class ClientProxies(object):390    """Proxy configuration settings.391    Proxies can also be configured using HTTP_PROXY and HTTPS_PROXY392    environment variables, in which case set use_env_settings to True.393    """394 395    def __init__(self):396        self.proxies = {}397        self.use_env_settings = True398 399    def __call__(self):400        # type: () -> Dict[str, str]401        """Return configuration to be applied to connection."""402        proxy_string = "\n".join(403            ["    {}: {}".format(k, v) for k, v in self.proxies.items()])404 405        _LOGGER.debug("Configuring proxies: %r", proxy_string)406        debug = "Evaluate proxies against ENV settings: %r"407        _LOGGER.debug(debug, self.use_env_settings)408        return self.proxies409 410    def add(self, protocol, proxy_url):411        # type: (str, str) -> None412        """Add proxy.413 414        :param str protocol: Protocol for which proxy is to be applied. Can415         be 'http', 'https', etc. Can also include host.416        :param str proxy_url: The proxy URL. Where basic auth is required,417         use the format: http://user:password@host418        """419        self.proxies[protocol] = proxy_url420 421 422class ClientConnection(object):423    """Request connection configuration settings.424    """425 426    def __init__(self):427        self.timeout = 100428        self.verify = True429        self.cert = None430        self.data_block_size = 4096431 432    def __call__(self):433        # type: () -> Dict[str, Union[str, int]]434        """Return configuration to be applied to connection."""435        debug = "Configuring request: timeout=%r, verify=%r, cert=%r"436        _LOGGER.debug(debug, self.timeout, self.verify, self.cert)437        return {'timeout': self.timeout,438                'verify': self.verify,439                'cert': self.cert}440 441 442__all__ = [443    'ClientRequest',444    'ClientResponse',445    'HTTPSender',446    # Generic HTTP configuration447    'HTTPSenderConfiguration',448    'ClientRedirectPolicy',449    'ClientProxies',450    'ClientConnection'451]452 453try:454    from .async_abc import AsyncHTTPSender, AsyncClientResponse  # pylint: disable=unused-import455    from .async_abc import __all__ as _async_all456    __all__ += _async_all457except SyntaxError: # Python 2458    pass459except ImportError: # pyinstaller won't include Py3 files in Py2.7 mode460    pass461