PKlœIýVb¶H¤H¤msrest/serialization.py# -------------------------------------------------------------------------- # # 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 base64 import b64decode, b64encode import calendar import datetime import decimal from enum import Enum import json import logging import re try: from urllib import quote except ImportError: from urllib.parse import quote import chardet import isodate from .exceptions import ( ValidationError, SerializationError, DeserializationError, raise_with_traceback) try: basestring except NameError: basestring = str _LOGGER = logging.getLogger(__name__) class UTC(datetime.tzinfo): """Time Zone info for handling UTC""" def utcoffset(self, dt): """UTF offset for UTC is 0.""" return datetime.timedelta(0) def tzname(self, dt): """Timestamp representation.""" return "Z" def dst(self, dt): """No daylight saving for UTC.""" return datetime.timedelta(hours=1) try: from datetime import timezone TZ_UTC = timezone.utc except ImportError: TZ_UTC = UTC() class Model(object): """Mixin for all client request body/response body models to support serialization and deserialization. """ _subtype_map = {} _attribute_map = {} _validation = {} def __init__(self, *args, **kwargs): """Allow attribute setting via kwargs on initialization.""" for k in kwargs: setattr(self, k, kwargs[k]) def __eq__(self, other): """Compare objects by comparing all attributes.""" if isinstance(other, self.__class__): return self.__class__.__dict__ == other.__class__.__dict__ return False def __ne__(self, other): """Compare objects by comparing all attributes.""" return not self.__eq__(other) def __str__(self): return str(self.__dict__) @classmethod def _get_subtype_map(cls): attr = '_subtype_map' parents = cls.__bases__ for base in parents: if hasattr(base, attr) and base._subtype_map: return base._subtype_map return {} @classmethod def _classify(cls, response, objects): """Check the class _subtype_map for any child classes. We want to ignore any inheirited _subtype_maps. """ try: map = cls.__dict__.get('_subtype_map', {}) for _type, _classes in map.items(): classification = response.get(_type) try: return objects[_classes[classification]] except KeyError: pass for c in _classes: try: _cls = objects[_classes[c]] return _cls._classify(response, objects) except (KeyError, TypeError): continue raise TypeError("Object cannot be classified futher.") except AttributeError: raise TypeError("Object cannot be classified futher.") def _convert_to_datatype(data, data_type, localtypes): if data is None: return data data_obj = localtypes.get(data_type.strip('{[]}')) if data_obj: if data_type.startswith('['): data = [ _convert_to_datatype( param, data_type[1:-1], localtypes) for param in data ] elif data_type.startswith('{'): data = { key: _convert_to_datatype( data[key], data_type[1:-1], localtypes) for key in data } elif issubclass(data_obj, Enum): return data elif not isinstance(data, data_obj): result = { key: _convert_to_datatype( data[key], data_obj._attribute_map[key]['type'], localtypes) for key in data } data = data_obj(**result) else: try: for attr, map in data._attribute_map.items(): setattr(data, attr, _convert_to_datatype( getattr(data, attr), map['type'], localtypes)) except AttributeError: pass return data class Serializer(object): """Request object model serializer.""" basic_types = {str: 'str', int: 'int', bool: 'bool', float: 'float'} days = {0: "Mon", 1: "Tue", 2: "Wed", 3: "Thu", 4: "Fri", 5: "Sat", 6: "Sun"} months = {1: "Jan", 2: "Feb", 3: "Mar", 4: "Apr", 5: "May", 6: "Jun", 7: "Jul", 8: "Aug", 9: "Sep", 10: "Oct", 11: "Nov", 12: "Dec"} validation = { "min_length": lambda x, y: len(x) < y, "max_length": lambda x, y: len(x) > y, "minimum": lambda x, y: x < y, "maximum": lambda x, y: x > y, "minimum_ex": lambda x, y: x <= y, "maximum_ex": lambda x, y: x >= y, "min_items": lambda x, y: len(x) < y, "max_items": lambda x, y: len(x) > y, "pattern": lambda x, y: not re.match(y, x), "unique": lambda x, y: len(x) != len(set(x)), "multiple": lambda x, y: x % y != 0 } flattten = re.compile(r"(? 9999 or utc.tm_year < 1: raise OverflowError("Hit max or min date") microseconds = str(float(attr.microsecond)*1e-6)[1:].ljust(4, '0') date = "{:04}-{:02}-{:02}T{:02}:{:02}:{:02}".format( utc.tm_year, utc.tm_mon, utc.tm_mday, utc.tm_hour, utc.tm_min, utc.tm_sec) return date + microseconds + 'Z' except (ValueError, OverflowError) as err: msg = "Unable to serialize datetime object." raise_with_traceback(SerializationError, msg, err) except AttributeError as err: msg = "ISO-8601 object must be valid Datetime object." raise_with_traceback(TypeError, msg, err) @staticmethod def serialize_unix(attr, **kwargs): """Serialize Datetime object into IntTime format. This is represented as seconds. :param Datetime attr: Object to be serialized. :rtype: int :raises: SerializationError if format invalid """ if isinstance(attr, int): return attr try: if not attr.tzinfo: _LOGGER.warning( "Datetime with no tzinfo will be considered UTC.") return int(calendar.timegm(attr.utctimetuple())) except AttributeError: raise TypeError("Unix time object must be valid Datetime object.") class Deserializer(object): """Response object model deserializer. :param dict classes: Class type dictionary for deserializing complex types. """ basic_types = {str: 'str', int: 'int', bool: 'bool', float: 'float'} valid_date = re.compile( r'\d{4}[-]\d{2}[-]\d{2}T\d{2}:\d{2}:\d{2}' '\.?\d*Z?[-+]?[\d{2}]?:?[\d{2}]?') flatten = re.compile(r"(? 1: decimal = "" for digit in check_decimal[1]: if digit.isdigit(): decimal += digit else: break if len(decimal) > 6: attr = attr.replace(decimal, decimal[0:-1]) date_obj = isodate.parse_datetime(attr) test_utc = date_obj.utctimetuple() if test_utc.tm_year > 9999 or test_utc.tm_year < 1: raise OverflowError("Hit max or min date") except(ValueError, OverflowError, AttributeError) as err: msg = "Cannot deserialize datetime object." raise_with_traceback(DeserializationError, msg, err) else: return date_obj @staticmethod def deserialize_unix(attr): """Serialize Datetime object into IntTime format. This is represented as seconds. :param int attr: Object to be serialized. :rtype: Datetime :raises: DeserializationError if format invalid """ try: date_obj = datetime.datetime.fromtimestamp(attr, TZ_UTC) except ValueError as err: msg = "Cannot deserialize to unix datetime object." raise_with_traceback(DeserializationError, msg, err) else: return date_obj PKð±!IŒä=û55msrest/version.py# -------------------------------------------------------------------------- # # 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. # # -------------------------------------------------------------------------- msrest_version = "0.4.3" PKð±!I×y°££msrest/exceptions.py# -------------------------------------------------------------------------- # # 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 logging import sys from requests import RequestException _LOGGER = logging.getLogger(__name__) def raise_with_traceback(exception, message="", *args, **kwargs): """Raise exception with a specified traceback. :param Exception exception: Error type to be raised. :param str message: Message to include with error, empty by default. :param args: Any additional args to be included with exception. """ exc_type, exc_value, exc_traceback = sys.exc_info() exc_msg = "{}, {}: {}".format(message, exc_type.__name__, exc_value) error = exception(exc_msg, *args, **kwargs) try: raise error.with_traceback(exc_traceback) except AttributeError: error.__traceback__ = exc_traceback raise error class ClientException(Exception): """Base exception for all Client Runtime exceptions.""" def __init__(self, message, inner_exception=None, *args, **kwargs): self.inner_exception = inner_exception _LOGGER.debug(message) super(ClientException, self).__init__(message, *args, **kwargs) class SerializationError(ClientException): """Error raised during request serialization.""" pass class DeserializationError(ClientException): """Error raised during response deserialization.""" pass class TokenExpiredError(ClientException): """OAuth token expired, request failed.""" pass class ValidationError(ClientException): """Request parameter validation failed.""" messages = { "min_length": "must have length greater than {!r}.", "max_length": "must have length less than {!r}.", "minimum": "must be greater than {!r}.", "maximum": "must be less than {!r}.", "minimum_ex": "must be equal to or greater than {!r}.", "maximum_ex": "must be equal to or less than {!r}.", "min_items": "must contain at least {!r} items.", "max_items": "must contain at most {!r} items.", "pattern": "must conform to the following pattern: {!r}.", "unique": "must contain only unique items.", "multiple": "must be a multiple of {!r}.", "required": "can not be None." } def __init__(self, rule, target, value, *args, **kwargs): self.rule = rule self.target = target message = "Parameter {!r} ".format(target) reason = self.messages.get( rule, "failed to meet validation requirement.") message += reason.format(value) super(ValidationError, self).__init__(message, *args, **kwargs) class ClientRequestError(ClientException): """Client request failed.""" pass class AuthenticationError(ClientException): """Client request failed to authentication.""" pass class HttpOperationError(ClientException): """Client request failed due to server-specificed HTTP operation error. Attempts to deserialize response into specific error object. :param Deserializer deserialize: Deserializer with data on custom error objects. :param requests.Response response: Server response :param str resp_type: Objects type to deserialize response. :param args: Additional args to pass to exception object. """ def __str__(self): return str(self.message) def __init__(self, deserialize, response, resp_type=None, *args, **kwargs): self.error = None self.message = None self.response = response try: if resp_type: self.error = deserialize(resp_type, response) if self.error is None: self.error = deserialize.dependencies[resp_type]() self.message = self.error.message except (DeserializationError, AttributeError, KeyError): pass if not self.error or not self.message: try: response.raise_for_status() except RequestException as err: if not self.error: self.error = err if not self.message: msg = "Operation returned an invalid status code {!r}" self.message = msg.format(response.reason) else: if not self.error: self.error = response if not self.message: self.message = "Unknown error" super(HttpOperationError, self).__init__( self.message, self.error, *args, **kwargs) PK.±IYÀí"  msrest/authentication.py# -------------------------------------------------------------------------- # # 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 requests from requests.auth import HTTPBasicAuth import requests_oauthlib as oauth class Authentication(object): """Default, simple auth object. Doesn't actually add any auth headers. """ header = "Authorization" def signed_session(self): """Create requests session with any required auth headers applied. :rtype: requests.Session. """ return requests.Session() class BasicAuthentication(Authentication): """Implmentation of Basic Authentication. :param str username: Authentication username. :param str password: Authentication password. """ def __init__(self, username, password): self.scheme = 'Basic' self.username = username self.password = password def signed_session(self): """Create requests session with any required auth headers applied. :rtype: requests.Session. """ session = super(BasicAuthentication, self).signed_session() session.auth = HTTPBasicAuth(self.username, self.password) return session class BasicTokenAuthentication(Authentication): """Simple Token Authentication. Does not adhere to OAuth, simply adds provided token as a header. :param dict token: Authentication token, must have 'access_token' key. """ def __init__(self, token): self.scheme = 'Bearer' self.token = token def signed_session(self): """Create requests session with any required auth headers applied. :rtype: requests.Session. """ session = super(BasicTokenAuthentication, self).signed_session() header = "{} {}".format(self.scheme, self.token['access_token']) session.headers['Authorization'] = header return session class OAuthTokenAuthentication(Authentication): """OAuth Token Authentication. Requires that supplied token contains an expires_in field. :param str client_id: Account Client ID. :param dict token: OAuth2 token. """ def __init__(self, client_id, token): self.scheme = 'Bearer' self.id = client_id self.token = token self.store_key = self.id def construct_auth(self): """Format token header. :rtype: str. """ return "{} {}".format(self.scheme, self.token) def refresh_session(self): """Return updated session if token has expired, attempts to refresh using refresh token. :rtype: requests.Session. """ return self.signed_session() def signed_session(self): """Create requests session with any required auth headers applied. :rtype: requests.Session. """ return oauth.OAuth2Session(self.id, token=self.token) PK.±Ií´´msrest/http_logger.py# -------------------------------------------------------------------------- # # 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 logging import re import types _LOGGER = logging.getLogger(__name__) def log_request(adapter, request, *args, **kwargs): """Log a client request. :param ClientHTTPAdapter adapter: Adapter making the request. :param requests.Request request: The request object. """ try: _LOGGER.debug("Request URL: %r", request.url) _LOGGER.debug("Request method: %r", request.method) _LOGGER.debug("Request headers:") for header, value in request.headers.items(): _LOGGER.debug(" %r: %r", header, value) _LOGGER.debug("Request body:") # We don't want to log the binary data of a file upload. if isinstance(request.body, types.GeneratorType): _LOGGER.debug("File upload") else: _LOGGER.debug(str(request.body)) except Exception as err: _LOGGER.debug("Failed to log request: %r", err) def log_response(adapter, request, response, *args, **kwargs): """Log a server response. :param ClientHTTPAdapter adapter: Adapter making the request. :param requests.Request request: The request object. :param requests.Response response: The response object. """ try: result = kwargs['result'] _LOGGER.debug("Response status: %r", result.status_code) _LOGGER.debug("Response headers:") for header, value in result.headers.items(): _LOGGER.debug(" %r: %r", header, value) # We don't want to log binary data if the response is a file. _LOGGER.debug("Response content:") pattern = re.compile(r'attachment; ?filename=["\w.]+', re.IGNORECASE) header = result.headers.get('content-disposition') if header and pattern.match(header): filename = header.partition('=')[2] _LOGGER.debug("File attachments: " + filename) elif result.headers.get("content-type", "").endswith("octet-stream"): _LOGGER.debug("Body contains binary data.") elif result.headers.get("content-type", "").startswith("image"): _LOGGER.debug("Body contains image data.") elif result.headers.get("transfer-encoding") == 'chunked': _LOGGER.debug("Body contains chunked data.") else: _LOGGER.debug(str(result.content)) return result except Exception as err: _LOGGER.debug("Failed to log response: " + repr(err)) return kwargs['result'] PK†±I¡d‡Â<Â<msrest/pipeline.py# -------------------------------------------------------------------------- # # 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 functools import json import logging try: from urlparse import urlparse except ImportError: from urllib.parse import urlparse import requests from requests.packages.urllib3 import Retry from requests.packages.urllib3 import HTTPConnectionPool from .serialization import Deserializer _LOGGER = logging.getLogger(__name__) class ClientHTTPAdapter(requests.adapters.HTTPAdapter): """HTTP Adapter to customize REST pipeline in Requests. Handles both request and response objects. """ def __init__(self, config): self._client_hooks = { 'request': ClientPipelineHook(), 'response': ClientPipelineHook()} super(ClientHTTPAdapter, self).__init__() def _test_pipeline(self, *args, **kwargs): """ Custom pipeline manipulation for test framework, """ test_hosts = [('http', 'localhost', 3000), ('http', 'localhost.', 3000)] for host in test_hosts: self.poolmanager.pools[host] = \ ClientHTTPConnectionPool(host[1], port=host[2]) def event_hook(event): """Function decorator to wrap events with hook callbacks.""" def event_wrapper(func): @functools.wraps(func) def execute_hook(self, *args, **kwargs): return self._client_hooks[event](func, self, *args, **kwargs) return execute_hook return event_wrapper def add_hook(self, event, callback, precall=True, overwrite=False): """Add an event callback to hook into the REST pipeline. :param str event: The event to hook. Currently supports 'request' and 'response'. :param callable callback: The function to call. :param bool precall: Whether the function will be called before or after the event. :param bool overwrite: Whether the function will overwrite the original event. :raises: TypeError if the callback is not a function. :raises: KeyError if the event is not supported. """ if not callable(callback): raise TypeError("Callback must be callable.") if event not in self._client_hooks: raise KeyError( "Event: {!r} is not able to be hooked.".format(event)) if precall: debug = "Adding %r callback before event: %r" _LOGGER.debug(debug, callback.__name__, event) self._client_hooks[event].precalls.append(callback) else: debug = "Adding %r callback after event: %r" _LOGGER.debug(debug, callback.__name__, event) self._client_hooks[event].postcalls.append(callback) debug = "Callback to overwrite original call: %r" _LOGGER.debug(debug, overwrite) self._client_hooks[event].overwrite_call = overwrite def remove_hook(self, event, callback): """Remove a specified event hook from the pipeline. :param str event: The event to hook. Currently supports 'request' and 'response'. :param callable callback: The function to remove. :raises: KeyError if the event is not supported. """ try: hook_event = self._client_hooks[event] except KeyError: raise KeyError( "Event: {!r} is not able to be hooked.".format(event)) else: self._client_hooks[event].precalls = [ c for c in hook_event.precalls if c != callback] self._client_hooks[event].postcalls = [ c for c in hook_event.postcalls if c != callback] @event_hook("request") def send(self, request, stream=False, timeout=None, verify=True, cert=None, proxies=None): """Sends the request object.""" return super(ClientHTTPAdapter, self).send( request, stream, timeout, verify, cert, proxies) @event_hook("response") def build_response(self, req, resp): """Builds the response object.""" return super(ClientHTTPAdapter, self).build_response(req, resp) class ClientPipelineHook(object): """Pipeline hook to wrap a specific event. :param bool overwrite: Whether to overwrite the original event. """ def __init__(self, overwrite=False): self.precalls = [] self.postcalls = [] self.overwrite_call = overwrite def __call__(self, func, *args, **kwargs): """Execute event and any wrapping callbacks. The result of the event is passed into all post-event callbacks with a 'result' keyword arg. """ result = requests.Response() for call in self.precalls: # Execute any pre-event callabcks call(*args, **kwargs) if not self.overwrite_call: # Execute original event result = func(*args, **kwargs) for call in self.postcalls: # Execute any post-event callbacks result = call(result=result, *args, **kwargs) return result class ClientRequest(requests.Request): """Wrapper for requests.Request object.""" def add_header(self, header, value): """Add a header to the single request. :param str header: The header name. :param str value: The header value. """ self.headers[header] = value def add_headers(self, headers): """Add multiple headers to the single request. :param dict headers: A dictionary of headers. """ for key, value in headers.items(): self.add_header(key, value) def format_parameters(self, params): """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): """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 try: self.data = json.dumps(data) self.headers['Content-Length'] = str(len(self.data)) except TypeError: self.data = data 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. :param response: Raw response object. """ def __init__(self, output, response): self.response = response self.output = output self.headers = {} self._deserialize = Deserializer() def add_headers(self, header_dict): """Deserialize a specific header. :param dict header_dict: A dictionary containing the name of the header and the type to deserialize to. """ for name, data_type in header_dict.items(): value = self.response.headers.get(name) value = self._deserialize(data_type, value) self.headers[name] = value class ClientRetry(Retry): """Wrapper for urllib3 Retry object. """ def __init__(self, **kwargs): self.retry_cookie = None return super(ClientRetry, self).__init__(**kwargs) def increment(self, method=None, url=None, response=None, error=None, _pool=None, _stacktrace=None): increment = super(ClientRetry, self).increment( method, url, response, error, _pool, _stacktrace) if response: # Fixes open socket warnings in Python 3. response.close() response.release_conn() # Collect retry cookie - we only do this for the test server # at this point, unless we implement a proper cookie policy. increment.retry_cookie = response.getheader("Set-Cookie") return increment def is_forced_retry(self, method, status_code): debug = "Received status: %r for method %r" _LOGGER.debug(debug, status_code, method) output = super(ClientRetry, self).is_forced_retry(method, status_code) _LOGGER.debug("Is forced retry: %r", output) return output 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 = ClientRetry() 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): """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): """Total number of allowed retries.""" return self.policy.total @retries.setter def retries(self, value): self.policy.total = value self.policy.connect = value self.policy.read = value @property def backoff_factor(self): """Factor by which back-off delay is incementally increased.""" return self.policy.backoff_factor @backoff_factor.setter def backoff_factor(self, value): self.policy.backoff_factor = value @property def max_backoff(self): """Max retry back-off delay.""" return self.policy.BACKOFF_MAX @max_backoff.setter def max_backoff(self, value): self.policy.BACKOFF_MAX = value class ClientRedirectPolicy(object): """Redirect configuration settings. """ def __init__(self): self.allow = True self.max_redirects = 30 def __bool__(self): """Whether redirects are allowed.""" return self.allow def __call__(self): """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 def check_redirect(self, resp, request): """Whether redirect policy should be applied based on status code.""" if resp.status_code in (301, 302) and \ request.method not in ['GET', 'HEAD']: return False return True 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): """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): """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): """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} class ClientHTTPConnectionPool(HTTPConnectionPool): """Cookie logic only used for test server (localhost)""" def _add_test_cookie(self, retries, headers): host = self.host.strip('.') if retries.retry_cookie and host == 'localhost': if headers: headers['cookie'] = retries.retry_cookie else: self.headers['cookie'] = retries.retry_cookie def _remove_test_cookie(self, retries, headers): host = self.host.strip('.') if retries.retry_cookie and host == 'localhost': retries.retry_cookie = None if headers: del headers['cookie'] else: del self.headers['cookie'] def urlopen(self, method, url, body=None, headers=None, retries=None, *args, **kwargs): if hasattr(retries, 'retry_cookie'): self._add_test_cookie(retries, headers) response = super(ClientHTTPConnectionPool, self).urlopen( method, url, body, headers, retries, *args, **kwargs) if hasattr(retries, 'retry_cookie'): self._remove_test_cookie(retries, headers) return response PK.±IqD"_44msrest/paging.py# -------------------------------------------------------------------------- # # 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 collections try: from urlparse import urlparse except ImportError: from urllib.parse import urlparse from .serialization import Deserializer from .pipeline import ClientRawResponse class Paged(collections.Iterable): """A container for paged REST responses. :param requests.Response response: server response object. :param callable command: Function to retrieve the next page of items. :param dict classes: A dictionary of class dependencies for deserialization. """ _validation = {} _attribute_map = {} def __init__(self, command, classes, raw_headers=None): self.next_link = "" self.current_page = [] self._derserializer = Deserializer(classes) self._get_next = command self._response = None self._raw_headers = raw_headers def __iter__(self): """Iterate over response items in current page, automatically retrieves next page. """ for i in self.current_page: yield i while self.next_link is not None: for i in self.next(): yield i @classmethod def _get_subtype_map(cls): """Required for parity to Model object for deserialization.""" return {} @property def raw(self): raw = ClientRawResponse(self.current_page, self._response) if self._raw_headers: raw.add_headers(self._raw_headers) return raw def _validate_url(self): """Validate next page URL.""" if self.next_link: parsed = urlparse(self.next_link) if not parsed.scheme or not parsed.netloc: raise ValueError("Invalid URL: " + self.next_link) def get(self, url): """Get arbitrary page. :param str url: URL to arbitrary page results. """ self.next_link = url return self.next() def reset(self): """Reset iterator to first page.""" self.next_link = "" self.current_page = [] def next(self): """Get next page.""" if self.next_link is None: raise GeneratorExit("End of paging") self._validate_url() self._response = self._get_next(self.next_link) self._derserializer(self, self._response) return self.current_page PK.±IÊ\Ð!FFmsrest/__init__.py# -------------------------------------------------------------------------- # # 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 .configuration import Configuration from .service_client import ServiceClient from .serialization import Serializer, Deserializer from .version import msrest_version __all__ = [ "ServiceClient", "Serializer", "Deserializer", "Configuration" ] __version__ = msrest_version PK.±IÈ+vGGmsrest/configuration.py# -------------------------------------------------------------------------- # # 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. # # -------------------------------------------------------------------------- try: import configparser from configparser import NoOptionError except ImportError: import ConfigParser as configparser from ConfigParser import NoOptionError import platform import requests from .exceptions import raise_with_traceback from .pipeline import ( ClientRetryPolicy, ClientRedirectPolicy, ClientProxies, ClientConnection) from .version import msrest_version class Configuration(object): """Client configuration. :param str baseurl: REST API base URL. :param str filepath: Path to existing config file (optional). """ def __init__(self, base_url, filepath=None): # Service self.base_url = base_url # Communication configuration self.connection = ClientConnection() # ProxyConfiguration self.proxies = ClientProxies() # Retry configuration self.retry_policy = ClientRetryPolicy() # Redirect configuration self.redirect_policy = ClientRedirectPolicy() # User-Agent Header self._user_agent = "python/{} ({}) requests/{} msrest/{}".format( platform.python_version(), platform.platform(), requests.__version__, msrest_version) self._config = configparser.ConfigParser() self._config.optionxform = str if filepath: self.load(filepath) @property def user_agent(self): return self._user_agent def add_user_agent(self, value): self._user_agent = "{} {}".format(self._user_agent, value) def _clear_config(self): """Clearout config object in memory.""" for section in self._config.sections(): self._config.remove_section(section) 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. :rtype: None """ sections = [ "Connection", "Proxies", "RetryPolicy", "RedirectPolicy"] for section in sections: self._config.add_section(section) self._config.set("Connection", "base_url", self.base_url) 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("RetryPolicy", "retries", self.retry_policy.retries) self._config.set("RetryPolicy", "backoff_factor", self.retry_policy.backoff_factor) self._config.set("RetryPolicy", "max_backoff", self.retry_policy.max_backoff) 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): """Load configuration from existing file. :param str filepath: Path to existing config file. :raises: ValueError if supplied config file is invalid. :rtype: None """ try: self._config.read(filepath) self.base_url = \ self._config.get("Connection", "base_url") 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 = \ eval(self._config.get("Proxies", "proxies")) self.proxies.use_env_settings = \ self._config.getboolean("Proxies", "env_settings") 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") self.redirect_policy.allow = \ self._config.getboolean("RedirectPolicy", "allow") self.redirect_policy.max_redirects = \ self._config.set("RedirectPolicy", "max_redirects") except (ValueError, EnvironmentError, NoOptionError): error = "Supplied config file incompatible." raise_with_traceback(ValueError, error) finally: self._clear_config() PK.±I@¥ Ð22msrest/service_client.py# -------------------------------------------------------------------------- # # 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 contextlib import logging import os try: from urlparse import urljoin, urlparse except ImportError: from urllib.parse import urljoin, urlparse from oauthlib import oauth2 import requests from .authentication import Authentication from .pipeline import ClientHTTPAdapter, ClientRequest from .http_logger import log_request, log_response from .exceptions import ( TokenExpiredError, ClientRequestError, raise_with_traceback) _LOGGER = logging.getLogger(__name__) class ServiceClient(object): """REST Service Client. Maintains client pipeline and handles all requests and responses. :param Configuration config: Service configuration. :param Authentication creds: Authenticated credentials. """ _protocols = ['http://', 'https://'] def __init__(self, creds, config): self.config = config self.creds = creds if creds else Authentication() self._adapter = ClientHTTPAdapter(config) self._headers = {} self._adapter.add_hook("request", log_request) self._adapter.add_hook("response", log_response, precall=False) def _format_data(self, data): """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. """ content = [None, data] if hasattr(data, 'read'): content.append("application/octet-stream") try: if data.name[0] != '<' and data.name[-1] != '>': content[0] = os.path.basename(data.name) except (AttributeError, TypeError): pass return tuple(content) def _request(self, url, params): """Create ClientRequest object. :param str url: URL for the request. :param dict params: URL query parameters. """ request = ClientRequest() if url: request.url = self.format_url(url) if params: request.format_parameters(params) return request def _configure_session(self, session, **config): """Apply configuration to session. :param requests.Session session: Current request session. :param config: Specific configuration overrides. """ kwargs = self.config.connection() for opt in ['timeout', 'verify', 'cert']: kwargs[opt] = config.get(opt, kwargs[opt]) for opt in ['cookies', 'files']: kwargs[opt] = config.get(opt) kwargs['stream'] = True kwargs['allow_redirects'] = config.get( 'allow_redirects', bool(self.config.redirect_policy)) session.headers.update(self._headers) session.headers['User-Agent'] = self.config.user_agent session.headers['Accept'] = 'application/json' session.max_redirects = config.get( 'max_redirects', self.config.redirect_policy()) session.proxies = config.get( 'proxies', self.config.proxies()) session.trust_env = config.get( 'use_env_proxies', self.config.proxies.use_env_settings) redirect_logic = session.resolve_redirects def wrapped_redirect(resp, req, **kwargs): attempt = self.config.redirect_policy.check_redirect(resp, req) return redirect_logic(resp, req, **kwargs) if attempt else [] session.resolve_redirects = wrapped_redirect self._adapter.max_retries = config.get( 'retries', self.config.retry_policy()) for protocol in self._protocols: session.mount(protocol, self._adapter) return kwargs def send_formdata(self, request, headers={}, content={}, **config): """Send data as a multipart form-data request. We only deal with file-like objects or strings at this point. The requests is not yet streamed. :param ClientRequest request: The request object to be sent. :param dict headers: Any headers to add to the request. :param dict content: Dictionary of the fields of the formdata. :param config: Any specific config overrides. """ file_data = {f: self._format_data(d) for f, d in content.items()} try: del headers['Content-Type'] except KeyError: pass return self.send(request, headers, None, files=file_data, **config) def send(self, request, headers=None, content=None, **config): """Prepare and send request object according to configuration. :param ClientRequest request: The request object to be sent. :param dict headers: Any headers to add to the request. :param content: Any body data to add to the request. :param config: Any specific config overrides """ response = None session = self.creds.signed_session() kwargs = self._configure_session(session, **config) request.add_headers(headers if headers else {}) if not kwargs.get('files'): request.add_content(content) try: try: response = session.request( request.method, request.url, data=request.data, headers=request.headers, **kwargs) return response except (oauth2.rfc6749.errors.InvalidGrantError, oauth2.rfc6749.errors.TokenExpiredError) as err: error = "Token expired or is invalid. Attempting to refresh." _LOGGER.warning(error) try: session = self.creds.refresh_session() kwargs = self._configure_session(session) response = session.request( request.method, request.url, request.data, request.headers, **kwargs) return response 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) finally: if not response or response._content_consumed: session.close() def stream_download(self, data, callback): """Generator for streaming request body data. :param data: A response object to be streamed. :param callback: Custom callback for monitoring progress. """ block = self.config.connection.data_block_size if not data._content_consumed: with contextlib.closing(data) as response: for chunk in response.iter_content(block): if not chunk: break if callback and callable(callback): callback(chunk, response=response) yield chunk else: for chunk in data.iter_content(block): if not chunk: break if callback and callable(callback): callback(chunk, response=data) yield chunk data.close() self._adapter.close() def stream_upload(self, data, callback): """Generator for streaming request body data. :param data: A file-like object to be streamed. :param callback: Custom callback for monitoring progress. """ while True: chunk = data.read(self.config.connection.data_block_size) if not chunk: break if callback and callable(callback): callback(chunk, response=None) yield chunk def format_url(self, url, **kwargs): """Format request URL with the client base URL, unless the supplied URL is already absolute. :param str url: The request URL to be formatted if necessary. """ url = url.format(**kwargs) parsed = urlparse(url) if not parsed.scheme or not parsed.netloc: url = url.lstrip('/') base = self.config.base_url.format(**kwargs).rstrip('/') url = urljoin(base + '/', url) return url def add_hook(self, event, hook, precall=True, overwrite=False): """ Add event callback. :param str event: The pipeline event to hook. Currently supports 'request' and 'response'. :param callable hook: The callback function. """ self._adapter.add_hook(event, hook, precall, overwrite) def remove_hook(self, event, hook): """ Remove event callback. :param str event: The pipeline event to hook. Currently supports 'request' and 'response'. :param callable hook: The callback function. """ self._adapter.remove_hook(event, hook) def add_header(self, header, value): """Add a persistent header - this header will be applied to all requests sent during the current client session. :param str header: The header name. :param str value: The header value. """ self._headers[header] = value def get(self, url=None, params={}): """Create a GET request object. :param str url: The request URL. :param dict params: Request URL parameters. """ request = self._request(url, params) request.method = 'GET' return request def put(self, url=None, params={}): """Create a PUT request object. :param str url: The request URL. :param dict params: Request URL parameters. """ request = self._request(url, params) request.method = 'PUT' return request def post(self, url=None, params={}): """Create a POST request object. :param str url: The request URL. :param dict params: Request URL parameters. """ request = self._request(url, params) request.method = 'POST' return request def head(self, url=None, params={}): """Create a HEAD request object. :param str url: The request URL. :param dict params: Request URL parameters. """ request = self._request(url, params) request.method = 'HEAD' return request def patch(self, url=None, params={}): """Create a PATCH request object. :param str url: The request URL. :param dict params: Request URL parameters. """ request = self._request(url, params) request.method = 'PATCH' return request def delete(self, url=None, params={}): """Create a DELETE request object. :param str url: The request URL. :param dict params: Request URL parameters. """ request = self._request(url, params) request.method = 'DELETE' return request def merge(self, url=None, params={}): """Create a MERGE request object. :param str url: The request URL. :param dict params: Request URL parameters. """ request = self._request(url, params) request.method = 'MERGE' return request PK²!Iô¶ÙIE E &msrest-0.4.3.dist-info/DESCRIPTION.rstAutoRest: Python Client Runtime ================================ Installation ------------ To install: .. code-block:: bash $ pip install msrest Release History --------------- 2016-09-01 Version 0.4.3 ++++++++++++++++++++++++ **Bugfixes** - Better exception message (https://github.com/Azure/autorest/pull/1300) 2016-08-15 Version 0.4.2 ++++++++++++++++++++++++ **Bugfixes** - Fix serialization if "object" type contains None (https://github.com/Azure/autorest/issues/1353) 2016-08-08 Version 0.4.1 ++++++++++++++++++++++++ **Bugfixes** - Fix compatibility issues with requests 2.11.0 (https://github.com/Azure/autorest/issues/1337) - Allow url of ClientRequest to have parameters (https://github.com/Azure/autorest/issues/1217) 2016-05-25 Version 0.4.0 ++++++++++++++++++++++++ This version has no bug fixes, but implements new features of Autorest: - Base64 url type - unixtime type - x-ms-enum modelAsString flag **Behaviour changes** - Add Platform information in UserAgent - Needs Autorest > 0.17.0 Nightly 20160525 2016-04-26 Version 0.3.0 ++++++++++++++++++++++++ **Bugfixes** - Read only values are no longer in __init__ or sent to the server (https://github.com/Azure/autorest/pull/959) - Useless kwarg removed **Behaviour changes** - Needs Autorest > 0.16.0 Nightly 20160426 2016-03-25 Version 0.2.0 ++++++++++++++++++++++++ **Bugfixes** - Manage integer enum values (https://github.com/Azure/autorest/pull/879) - Add missing application/json Accept HTTP header (https://github.com/Azure/azure-sdk-for-python/issues/553) **Behaviour changes** - Needs Autorest > 0.16.0 Nightly 20160324 2016-03-21 Version 0.1.3 ++++++++++++++++++++++++ **Bugfixes** - Deserialisation of generic resource if null in JSON (https://github.com/Azure/azure-sdk-for-python/issues/544) 2016-03-14 Version 0.1.2 ++++++++++++++++++++++++ **Bugfixes** - urllib3 side effect (https://github.com/Azure/autorest/issues/824) 2016-03-04 Version 0.1.1 ++++++++++++++++++++++++ **Bugfixes** - Source package corrupted in Pypi (https://github.com/Azure/autorest/issues/799) 2016-03-04 Version 0.1.0 +++++++++++++++++++++++++ **Behavioural Changes** - Removed custom logging set up and configuration. All loggers are now children of the root logger 'msrest' with no pre-defined configurations. - Replaced _required attribute in Model class with more extensive _validation dict. **Improvement** - Removed hierarchy scanning for attribute maps from base Model class - relies on generator to populate attribute maps according to hierarchy. - Base class Paged now inherits from collections.Iterable. - Data validation during serialization using custom parameters (e.g. max, min etc). - Added ValidationError to be raised if invalid data encountered during serialization. 2016-02-29 Version 0.0.3 ++++++++++++++++++++++++ **Bugfixes** - Source package corrupted in Pypi (https://github.com/Azure/autorest/issues/718) 2016-02-19 Version 0.0.2 ++++++++++++++++++++++++ **Bugfixes** - Fixed bug in exception logging before logger configured. 2016-02-19 Version 0.0.1 ++++++++++++++++++++++++ - Initial release. PK²!IlZ#))$msrest-0.4.3.dist-info/metadata.json{"classifiers": ["Development Status :: 4 - Beta", "Programming Language :: Python", "Programming Language :: Python :: 2", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.3", "Programming Language :: Python :: 3.4", "Programming Language :: Python :: 3.5", "License :: OSI Approved :: MIT License", "Topic :: Software Development"], "extensions": {"python.details": {"contacts": [{"name": "Microsoft Corporation", "role": "author"}], "document_names": {"description": "DESCRIPTION.rst"}, "project_urls": {"Home": "https://github.com/xingwu1/autorest/tree/python/ClientRuntimes/Python/msrest"}}}, "extras": [], "generator": "bdist_wheel (0.26.0)", "license": "MIT License", "metadata_version": "2.0", "name": "msrest", "run_requires": [{"requires": ["certifi (>=2015.9.6.2)", "chardet (>=2.3.0)", "enum34 (>=1.0.4)", "isodate (>=0.5.4)", "keyring (>=5.6)", "requests (>=2.7.0)", "requests-oauthlib (>=0.5.0)"]}], "summary": "AutoRest swagger generator Python client runtime.", "version": "0.4.3"}PK²!IOº`ˆ$msrest-0.4.3.dist-info/top_level.txtmsrest PK²!IŒ''\\msrest-0.4.3.dist-info/WHEELWheel-Version: 1.0 Generator: bdist_wheel (0.26.0) Root-Is-Purelib: true Tag: py2-none-any PK²!IZÉ'55msrest-0.4.3.dist-info/METADATAMetadata-Version: 2.0 Name: msrest Version: 0.4.3 Summary: AutoRest swagger generator Python client runtime. Home-page: https://github.com/xingwu1/autorest/tree/python/ClientRuntimes/Python/msrest Author: Microsoft Corporation Author-email: UNKNOWN License: MIT License Platform: UNKNOWN Classifier: Development Status :: 4 - Beta Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.3 Classifier: Programming Language :: Python :: 3.4 Classifier: Programming Language :: Python :: 3.5 Classifier: License :: OSI Approved :: MIT License Classifier: Topic :: Software Development Requires-Dist: certifi (>=2015.9.6.2) Requires-Dist: chardet (>=2.3.0) Requires-Dist: enum34 (>=1.0.4) Requires-Dist: isodate (>=0.5.4) Requires-Dist: keyring (>=5.6) Requires-Dist: requests (>=2.7.0) Requires-Dist: requests-oauthlib (>=0.5.0) AutoRest: Python Client Runtime ================================ Installation ------------ To install: .. code-block:: bash $ pip install msrest Release History --------------- 2016-09-01 Version 0.4.3 ++++++++++++++++++++++++ **Bugfixes** - Better exception message (https://github.com/Azure/autorest/pull/1300) 2016-08-15 Version 0.4.2 ++++++++++++++++++++++++ **Bugfixes** - Fix serialization if "object" type contains None (https://github.com/Azure/autorest/issues/1353) 2016-08-08 Version 0.4.1 ++++++++++++++++++++++++ **Bugfixes** - Fix compatibility issues with requests 2.11.0 (https://github.com/Azure/autorest/issues/1337) - Allow url of ClientRequest to have parameters (https://github.com/Azure/autorest/issues/1217) 2016-05-25 Version 0.4.0 ++++++++++++++++++++++++ This version has no bug fixes, but implements new features of Autorest: - Base64 url type - unixtime type - x-ms-enum modelAsString flag **Behaviour changes** - Add Platform information in UserAgent - Needs Autorest > 0.17.0 Nightly 20160525 2016-04-26 Version 0.3.0 ++++++++++++++++++++++++ **Bugfixes** - Read only values are no longer in __init__ or sent to the server (https://github.com/Azure/autorest/pull/959) - Useless kwarg removed **Behaviour changes** - Needs Autorest > 0.16.0 Nightly 20160426 2016-03-25 Version 0.2.0 ++++++++++++++++++++++++ **Bugfixes** - Manage integer enum values (https://github.com/Azure/autorest/pull/879) - Add missing application/json Accept HTTP header (https://github.com/Azure/azure-sdk-for-python/issues/553) **Behaviour changes** - Needs Autorest > 0.16.0 Nightly 20160324 2016-03-21 Version 0.1.3 ++++++++++++++++++++++++ **Bugfixes** - Deserialisation of generic resource if null in JSON (https://github.com/Azure/azure-sdk-for-python/issues/544) 2016-03-14 Version 0.1.2 ++++++++++++++++++++++++ **Bugfixes** - urllib3 side effect (https://github.com/Azure/autorest/issues/824) 2016-03-04 Version 0.1.1 ++++++++++++++++++++++++ **Bugfixes** - Source package corrupted in Pypi (https://github.com/Azure/autorest/issues/799) 2016-03-04 Version 0.1.0 +++++++++++++++++++++++++ **Behavioural Changes** - Removed custom logging set up and configuration. All loggers are now children of the root logger 'msrest' with no pre-defined configurations. - Replaced _required attribute in Model class with more extensive _validation dict. **Improvement** - Removed hierarchy scanning for attribute maps from base Model class - relies on generator to populate attribute maps according to hierarchy. - Base class Paged now inherits from collections.Iterable. - Data validation during serialization using custom parameters (e.g. max, min etc). - Added ValidationError to be raised if invalid data encountered during serialization. 2016-02-29 Version 0.0.3 ++++++++++++++++++++++++ **Bugfixes** - Source package corrupted in Pypi (https://github.com/Azure/autorest/issues/718) 2016-02-19 Version 0.0.2 ++++++++++++++++++++++++ **Bugfixes** - Fixed bug in exception logging before logger configured. 2016-02-19 Version 0.0.1 ++++++++++++++++++++++++ - Initial release. PK²!IH¹æÉúúmsrest-0.4.3.dist-info/RECORDmsrest/__init__.py,sha256=ZfffpxRqPds-4N7QDdqa2QYmyW_r0NyuF193wzBMxWY,1606 msrest/authentication.py,sha256=5FMTiCBG_Qd-dDfTdVU4ofYyQZbF-Q9dFyFDXY9ZZVQ,4108 msrest/configuration.py,sha256=qq89T87vANK-5-iMv7CEAp4-rqVlLm_YtCJbTkuZQ-0,6471 msrest/exceptions.py,sha256=Q-MsA4g8om0aJpObqdVl_qithnY5txoLL3sfmdPO8DQ,5795 msrest/http_logger.py,sha256=HvUJ8ep7xyupmFwA0dXrNg3sbI3ccPYSAmsdUZK-1Pk,3764 msrest/paging.py,sha256=AIYuleC8Ga_r37Y0RAuSE4CDhERwM9Xtu7WkdthTVUU,3636 msrest/pipeline.py,sha256=kITI1a7MjlZMeQ3ueoUKOIT7q4DucmBqTJe7IoogGl0,15554 msrest/serialization.py,sha256=-xnJFdBAiEuCz0K2aysMMVT-OpAjhMlg_LGSj7bWhwk,42056 msrest/service_client.py,sha256=HRiD-S6-U1EpaM-PCDjVhouZNQinpmIUrcQQJi1uZpo,12826 msrest/version.py,sha256=mU8L0OoTmrq41RgSG7YJP2hTkckwu5EfjtQ5JtMYFcI,1333 msrest-0.4.3.dist-info/DESCRIPTION.rst,sha256=skc9uC8qFsjKYMIgmjQvAw572iSovCIFakzs9IfT2Wk,3141 msrest-0.4.3.dist-info/METADATA,sha256=ao8vn1MCPFtvVUNBqZ7h1-tPtiplz0L-8_6pMtQ83ng,4149 msrest-0.4.3.dist-info/RECORD,, msrest-0.4.3.dist-info/WHEEL,sha256=JTb7YztR8fkPg6aSjc571Q4eiVHCwmUDlX8PhuuqIIE,92 msrest-0.4.3.dist-info/metadata.json,sha256=b7euzmVSFIrkkPnRHuD1Rdiu-xjxRqgid4Oqu_C_qk0,1065 msrest-0.4.3.dist-info/top_level.txt,sha256=fTM0WfmxiY5eFZgIwGfhfCMBAq84y9Uw6zfp_Ctbr0s,7 PKlœIýVb¶H¤H¤msrest/serialization.pyPKð±!IŒä=û55}¤msrest/version.pyPKð±!I×y°££á©msrest/exceptions.pyPK.±IYÀí"  ¶Àmsrest/authentication.pyPK.±Ií´´øÐmsrest/http_logger.pyPK†±I¡d‡Â<Â<ßßmsrest/pipeline.pyPK.±IqD"_44Ñmsrest/paging.pyPK.±IÊ\Ð!FF3+msrest/__init__.pyPK.±IÈ+vGG©1msrest/configuration.pyPK.±I@¥ Ð22%Kmsrest/service_client.pyPK²!Iô¶ÙIE E &u}msrest-0.4.3.dist-info/DESCRIPTION.rstPK²!IlZ#))$þ‰msrest-0.4.3.dist-info/metadata.jsonPK²!IOº`ˆ$iŽmsrest-0.4.3.dist-info/top_level.txtPK²!IŒ''\\²Žmsrest-0.4.3.dist-info/WHEELPK²!IZÉ'55Hmsrest-0.4.3.dist-info/METADATAPK²!IH¹æÉúúºŸmsrest-0.4.3.dist-info/RECORDPKrï¤