msrest-0.4.14/0000775000372000037200000000000013147325477013774 5ustar travistravis00000000000000msrest-0.4.14/MANIFEST.in0000664000372000037200000000001513147325371015517 0ustar travistravis00000000000000include *.rstmsrest-0.4.14/README.rst0000664000372000037200000001424213147325371015457 0ustar travistravis00000000000000AutoRest: Python Client Runtime ================================ .. image:: https://travis-ci.org/Azure/msrest-for-python.svg?branch=master :target: https://travis-ci.org/Azure/msrest-for-python .. image:: https://codecov.io/gh/azure/msrest-for-python/branch/master/graph/badge.svg :target: https://codecov.io/gh/azure/msrest-for-python Installation ------------ To install: .. code-block:: bash $ pip install msrest Release History --------------- 2017-08-23 Version 0.4.14 +++++++++++++++++++++++++ **Bugfixes** - Fix regression introduced in msrest 0.4.12 - dict syntax with enum modeled as string and enum used 2017-08-22 Version 0.4.13 +++++++++++++++++++++++++ **Bugfixes** - Fix regression introduced in msrest 0.4.12 - dict syntax using isodate.Duration (#42) 2017-08-21 Version 0.4.12 +++++++++++++++++++++++++ **Features** - Input is now more lenient - Model have a "validate" method to check content constraints - Model have now 4 new methods: - "serialize" that gives the RestAPI that will be sent - "as_dict" that returns a dict version of the Model. Callbacks are available. - "deserialize" the parses the RestAPI JSON into a Model - "from_dict" that parses several dict syntax into a Model. Callbacks are available. More details and examples in the Wiki article on Github: https://github.com/Azure/msrest-for-python/wiki/msrest-0.4.12---Serialization-change **Bugfixes** - Better Enum checking (#38) 2017-06-21 Version 0.4.11 +++++++++++++++++++++++++ **Bugfixes** - Fix incorrect dependency to "requests" 2.14.x, instead of 2.x meant in 0.4.8 2017-06-15 Version 0.4.10 +++++++++++++++++++++++++ **Features** - Add requests hooks to configuration 2017-06-08 Version 0.4.9 ++++++++++++++++++++++++ **Bugfixes** - Accept "null" value for paging array as an empty list and do not raise (#30) 2017-05-22 Version 0.4.8 ++++++++++++++++++++++++ **Bugfixes** - Fix random "pool is closed" error (#29) - Fix requests dependency to version 2.x, since version 3.x is annunced to be breaking. 2017-04-04 Version 0.4.7 ++++++++++++++++++++++++ **BugFixes** - Refactor paging #22: - "next" is renamed "advance_page" and "next" returns only 1 element (Python 2 expected behavior) - paging objects are now real generator and support the "next()" built-in function without need for "iter()" - Raise accurate DeserialisationError on incorrect RestAPI discriminator usage #27 - Fix discriminator usage of the base class name #27 - Remove default mutable arguments in Clients #20 - Fix object comparison in some scenarios #24 2017-03-06 Version 0.4.6 ++++++++++++++++++++++++ **Bugfixes** - Allow Model sub-classes to be serialized if type is "object" 2017-02-13 Version 0.4.5 ++++++++++++++++++++++++ **Bugfixes** - Fix polymorphic deserialization #11 - Fix regexp validation if '\\w' is used in Python 2.7 #13 - Fix dict deserialization if keys are unicode in Python 2.7 **Improvements** - Add polymorphic serialisation from dict objects - Remove chardet and use HTTP charset declaration (fallback to utf8) 2016-09-14 Version 0.4.4 ++++++++++++++++++++++++ **Bugfixes** - Remove paging URL validation, part of fix https://github.com/Azure/autorest/pull/1420 **Disclaimer** In order to get paging fixes for impacted clients, you need this package and Autorest > 0.17.0 Nightly 20160913 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. msrest-0.4.14/msrest.egg-info/0000775000372000037200000000000013147325477017003 5ustar travistravis00000000000000msrest-0.4.14/msrest.egg-info/requires.txt0000664000372000037200000000016113147325477021401 0ustar travistravis00000000000000requests~=2.14 requests_oauthlib>=0.5.0 isodate>=0.5.4 certifi>=2017.4.17 [:python_version<'3.4'] enum34>=1.0.4 msrest-0.4.14/msrest.egg-info/SOURCES.txt0000664000372000037200000000062413147325477020671 0ustar travistravis00000000000000MANIFEST.in README.rst setup.cfg setup.py msrest/__init__.py msrest/authentication.py msrest/configuration.py msrest/exceptions.py msrest/http_logger.py msrest/paging.py msrest/pipeline.py msrest/serialization.py msrest/service_client.py msrest/version.py msrest.egg-info/PKG-INFO msrest.egg-info/SOURCES.txt msrest.egg-info/dependency_links.txt msrest.egg-info/requires.txt msrest.egg-info/top_level.txtmsrest-0.4.14/msrest.egg-info/dependency_links.txt0000664000372000037200000000000113147325477023051 0ustar travistravis00000000000000 msrest-0.4.14/msrest.egg-info/PKG-INFO0000664000372000037200000002162413147325477020105 0ustar travistravis00000000000000Metadata-Version: 1.1 Name: msrest Version: 0.4.14 Summary: AutoRest swagger generator Python client runtime. Home-page: https://github.com/Azure/msrest-for-python Author: Microsoft Corporation Author-email: UNKNOWN License: MIT License Description: AutoRest: Python Client Runtime ================================ .. image:: https://travis-ci.org/Azure/msrest-for-python.svg?branch=master :target: https://travis-ci.org/Azure/msrest-for-python .. image:: https://codecov.io/gh/azure/msrest-for-python/branch/master/graph/badge.svg :target: https://codecov.io/gh/azure/msrest-for-python Installation ------------ To install: .. code-block:: bash $ pip install msrest Release History --------------- 2017-08-23 Version 0.4.14 +++++++++++++++++++++++++ **Bugfixes** - Fix regression introduced in msrest 0.4.12 - dict syntax with enum modeled as string and enum used 2017-08-22 Version 0.4.13 +++++++++++++++++++++++++ **Bugfixes** - Fix regression introduced in msrest 0.4.12 - dict syntax using isodate.Duration (#42) 2017-08-21 Version 0.4.12 +++++++++++++++++++++++++ **Features** - Input is now more lenient - Model have a "validate" method to check content constraints - Model have now 4 new methods: - "serialize" that gives the RestAPI that will be sent - "as_dict" that returns a dict version of the Model. Callbacks are available. - "deserialize" the parses the RestAPI JSON into a Model - "from_dict" that parses several dict syntax into a Model. Callbacks are available. More details and examples in the Wiki article on Github: https://github.com/Azure/msrest-for-python/wiki/msrest-0.4.12---Serialization-change **Bugfixes** - Better Enum checking (#38) 2017-06-21 Version 0.4.11 +++++++++++++++++++++++++ **Bugfixes** - Fix incorrect dependency to "requests" 2.14.x, instead of 2.x meant in 0.4.8 2017-06-15 Version 0.4.10 +++++++++++++++++++++++++ **Features** - Add requests hooks to configuration 2017-06-08 Version 0.4.9 ++++++++++++++++++++++++ **Bugfixes** - Accept "null" value for paging array as an empty list and do not raise (#30) 2017-05-22 Version 0.4.8 ++++++++++++++++++++++++ **Bugfixes** - Fix random "pool is closed" error (#29) - Fix requests dependency to version 2.x, since version 3.x is annunced to be breaking. 2017-04-04 Version 0.4.7 ++++++++++++++++++++++++ **BugFixes** - Refactor paging #22: - "next" is renamed "advance_page" and "next" returns only 1 element (Python 2 expected behavior) - paging objects are now real generator and support the "next()" built-in function without need for "iter()" - Raise accurate DeserialisationError on incorrect RestAPI discriminator usage #27 - Fix discriminator usage of the base class name #27 - Remove default mutable arguments in Clients #20 - Fix object comparison in some scenarios #24 2017-03-06 Version 0.4.6 ++++++++++++++++++++++++ **Bugfixes** - Allow Model sub-classes to be serialized if type is "object" 2017-02-13 Version 0.4.5 ++++++++++++++++++++++++ **Bugfixes** - Fix polymorphic deserialization #11 - Fix regexp validation if '\\w' is used in Python 2.7 #13 - Fix dict deserialization if keys are unicode in Python 2.7 **Improvements** - Add polymorphic serialisation from dict objects - Remove chardet and use HTTP charset declaration (fallback to utf8) 2016-09-14 Version 0.4.4 ++++++++++++++++++++++++ **Bugfixes** - Remove paging URL validation, part of fix https://github.com/Azure/autorest/pull/1420 **Disclaimer** In order to get paging fixes for impacted clients, you need this package and Autorest > 0.17.0 Nightly 20160913 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. 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: Programming Language :: Python :: 3.6 Classifier: License :: OSI Approved :: MIT License Classifier: Topic :: Software Development msrest-0.4.14/msrest.egg-info/top_level.txt0000664000372000037200000000000713147325477021532 0ustar travistravis00000000000000msrest msrest-0.4.14/setup.cfg0000664000372000037200000000010313147325477015607 0ustar travistravis00000000000000[bdist_wheel] universal = 1 [egg_info] tag_build = tag_date = 0 msrest-0.4.14/setup.py0000664000372000037200000000455013147325371015503 0ustar travistravis00000000000000# -------------------------------------------------------------------------- # # 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 setuptools import setup setup( name='msrest', version='0.4.14', author='Microsoft Corporation', packages=['msrest'], url=("https://github.com/Azure/msrest-for-python"), license='MIT License', description='AutoRest swagger generator Python client runtime.', long_description=open('README.rst').read(), 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', 'Programming Language :: Python :: 3.6', 'License :: OSI Approved :: MIT License', 'Topic :: Software Development'], install_requires=[ "requests~=2.14", "requests_oauthlib>=0.5.0", "isodate>=0.5.4", "certifi>=2017.4.17", ], extras_require={ ":python_version<'3.4'": ['enum34>=1.0.4'], } ) msrest-0.4.14/msrest/0000775000372000037200000000000013147325477015311 5ustar travistravis00000000000000msrest-0.4.14/msrest/http_logger.py0000664000372000037200000000740613147325371020201 0ustar travistravis00000000000000# -------------------------------------------------------------------------- # # 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(): if header.lower() == 'authorization': value = '*****' _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'] msrest-0.4.14/msrest/exceptions.py0000664000372000037200000001324213147325371020037 0ustar travistravis00000000000000# -------------------------------------------------------------------------- # # 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-specified 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) msrest-0.4.14/msrest/version.py0000664000372000037200000000246613147325371017351 0ustar travistravis00000000000000# -------------------------------------------------------------------------- # # 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.14" msrest-0.4.14/msrest/authentication.py0000664000372000037200000001001413147325371020667 0ustar travistravis00000000000000# -------------------------------------------------------------------------- # # 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) msrest-0.4.14/msrest/service_client.py0000664000372000037200000003052013147325371020652 0ustar travistravis00000000000000# -------------------------------------------------------------------------- # # 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.adapters from .authentication import Authentication from .pipeline import 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._headers = {} 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 def log_hook(r, *args, **kwargs): log_request(None, r.request) log_response(None, r.request, r, result=r) session.hooks['response'].append(log_hook) def make_user_hook_cb(user_hook, session): def user_hook_cb(r, *args, **kwargs): kwargs.setdefault("msrest", {})['session'] = session return user_hook(r, *args, **kwargs) return user_hook_cb for user_hook in self.config.hooks: session.hooks['response'].append(make_user_hook_cb(user_hook, session)) max_retries = config.get( 'retries', self.config.retry_policy()) for protocol in self._protocols: session.mount(protocol, requests.adapters.HTTPAdapter(max_retries=max_retries)) return kwargs def send_formdata(self, request, headers=None, content=None, **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. """ if content is None: content = {} file_data = {f: self._format_data(d) for f, d in content.items()} if headers: headers.pop('Content-Type', None) 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() 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_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=None): """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=None): """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=None): """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=None): """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=None): """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=None): """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=None): """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 msrest-0.4.14/msrest/paging.py0000664000372000037200000001002413147325371017116 0ustar travistravis00000000000000# -------------------------------------------------------------------------- # # 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: from collections.abc import Iterator xrange = range except ImportError: from collections import Iterator from .serialization import Deserializer from .pipeline import ClientRawResponse class Paged(Iterator): """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): # Sets next_link, current_page, and _current_page_iter_index. self.reset() self._derserializer = Deserializer(classes) self._get_next = command self._response = None self._raw_headers = raw_headers def __iter__(self): """Return 'self'.""" # Since iteration mutates this object, consider it an iterator in-and-of # itself. return self @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 get(self, url): """Get an arbitrary page. This resets the iterator and then fully consumes it to return the specific page **only**. :param str url: URL to arbitrary page results. """ self.reset() self.next_link = url return self.advance_page() def reset(self): """Reset iterator to first page.""" self.next_link = "" self.current_page = [] self._current_page_iter_index = 0 def advance_page(self): if self.next_link is None: raise StopIteration("End of paging") self._current_page_iter_index = 0 self._response = self._get_next(self.next_link) self._derserializer(self, self._response) return self.current_page def __next__(self): """Iterate through responses.""" # Storing the list iterator might work out better, but there's no # guarantee that some code won't replace the list entirely with a copy, # invalidating an list iterator that might be saved between iterations. if self.current_page and self._current_page_iter_index < len(self.current_page): response = self.current_page[self._current_page_iter_index] self._current_page_iter_index += 1 return response else: self.advance_page() return self.__next__() next = __next__ # Python 2 compatibility. msrest-0.4.14/msrest/serialization.py0000664000372000037200000014463013147325371020541 0ustar travistravis00000000000000# -------------------------------------------------------------------------- # # 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 import sys try: from urllib import quote except ImportError: from urllib.parse import quote 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() _FLATTEN = re.compile(r"(? 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, re.UNICODE), "unique": lambda x, y: len(x) != len(set(x)), "multiple": lambda x, y: x % y != 0 } def __init__(self, classes=None): self.serialize_type = { 'iso-8601': Serializer.serialize_iso, 'rfc-1123': Serializer.serialize_rfc, 'unix-time': Serializer.serialize_unix, 'duration': Serializer.serialize_duration, 'date': Serializer.serialize_date, 'decimal': Serializer.serialize_decimal, 'long': Serializer.serialize_long, 'bytearray': Serializer.serialize_bytearray, 'base64': Serializer.serialize_base64, 'object': self.serialize_object, '[]': self.serialize_iter, '{}': self.serialize_dict } self.dependencies = dict(classes) if classes else {} self.key_transformer = full_restapi_key_transformer def _serialize(self, target_obj, data_type=None, **kwargs): """Serialize data into a string according to type. :param target_obj: The data to be serialized. :param str data_type: The type to be serialized from. :rtype: str, dict :raises: SerializationError if serialization fails. """ key_transformer = kwargs.get("key_transformer", self.key_transformer) keep_readonly = kwargs.get("keep_readonly", False) if target_obj is None: return None serialized = {} attr_name = None class_name = target_obj.__class__.__name__ if data_type: return self.serialize_data( target_obj, data_type, **kwargs) if not hasattr(target_obj, "_attribute_map"): data_type = type(target_obj).__name__ if data_type in self.basic_types.values(): return self.serialize_data( target_obj, data_type, **kwargs) try: attributes = target_obj._attribute_map for attr, attr_desc in attributes.items(): attr_name = attr if not keep_readonly and target_obj._validation.get(attr_name, {}).get('readonly', False): continue try: orig_attr = getattr(target_obj, attr) keys, orig_attr = key_transformer(attr, attr_desc.copy(), orig_attr) keys = keys if isinstance(keys, list) else [keys] attr_type = attr_desc['type'] new_attr = self.serialize_data( orig_attr, attr_type, **kwargs) for k in reversed(keys): unflattened = {k: new_attr} new_attr = unflattened _new_attr = new_attr _serialized = serialized for k in keys: if k not in _serialized: _serialized.update(_new_attr) _new_attr = _new_attr[k] _serialized = _serialized[k] except ValueError: continue except (AttributeError, KeyError, TypeError) as err: msg = "Attribute {} in object {} cannot be serialized.\n{}".format( attr_name, class_name, str(target_obj)) raise_with_traceback(SerializationError, msg, err) else: return serialized def body(self, data, data_type, **kwargs): """Serialize data intended for a request body. :param data: The data to be serialized. :param str data_type: The type to be serialized from. :rtype: dict :raises: SerializationError if serialization fails. :raises: ValueError if data is None """ if data is None: raise ValidationError("required", "body", True) # Just in case this is a dict internal_data_type = data_type.strip('[]{}') if internal_data_type in self.dependencies and not isinstance(internal_data_type, Enum): try: deserializer = Deserializer(self.dependencies) deserializer.key_extractors = [ rest_key_case_insensitive_extractor, attribute_key_case_insensitive_extractor, last_rest_key_case_insensitive_extractor ] data = deserializer(data_type, data) except DeserializationError as err: raise_with_traceback( SerializationError, "Unable to build a model: "+str(err), err) errors = _recursive_validate(data_type, data) if errors: raise errors[0] return self._serialize(data, data_type, **kwargs) def url(self, name, data, data_type, **kwargs): """Serialize data intended for a URL path. :param data: The data to be serialized. :param str data_type: The type to be serialized from. :rtype: str :raises: TypeError if serialization fails. :raises: ValueError if data is None """ data = self.validate(data, name, required=True, **kwargs) try: output = self.serialize_data(data, data_type, **kwargs) if data_type == 'bool': output = json.dumps(output) if kwargs.get('skip_quote') is True: output = str(output) else: output = quote(str(output), safe='') except SerializationError: raise TypeError("{} must be type {}.".format(name, data_type)) else: return output def query(self, name, data, data_type, **kwargs): """Serialize data intended for a URL query. :param data: The data to be serialized. :param str data_type: The type to be serialized from. :rtype: str :raises: TypeError if serialization fails. :raises: ValueError if data is None """ data = self.validate(data, name, required=True, **kwargs) try: if data_type in ['[str]']: data = ["" if d is None else d for d in data] output = self.serialize_data(data, data_type, **kwargs) if data_type == 'bool': output = json.dumps(output) if kwargs.get('skip_quote') is True: output = str(output) else: output = quote(str(output), safe='') except SerializationError: raise TypeError("{} must be type {}.".format(name, data_type)) else: return str(output) def header(self, name, data, data_type, **kwargs): """Serialize data intended for a request header. :param data: The data to be serialized. :param str data_type: The type to be serialized from. :rtype: str :raises: TypeError if serialization fails. :raises: ValueError if data is None """ data = self.validate(data, name, required=True, **kwargs) try: if data_type in ['[str]']: data = ["" if d is None else d for d in data] output = self.serialize_data(data, data_type, **kwargs) if data_type == 'bool': output = json.dumps(output) except SerializationError: raise TypeError("{} must be type {}.".format(name, data_type)) else: return str(output) @classmethod def validate(cls, data, name, **kwargs): """Validate that a piece of data meets certain conditions""" required = kwargs.get('required', False) if required and data is None: raise ValidationError("required", name, True) elif data is None: return elif kwargs.get('readonly'): return try: for key, value in kwargs.items(): validator = cls.validation.get(key, lambda x, y: False) if validator(data, value): raise ValidationError(key, name, value) except TypeError: raise ValidationError("unknown", name, "unknown") else: return data def serialize_data(self, data, data_type, **kwargs): """Serialize generic data according to supplied data type. :param data: The data to be serialized. :param str data_type: The type to be serialized from. :param bool required: Whether it's essential that the data not be empty or None :raises: AttributeError if required data is None. :raises: ValueError if data is None :raises: SerializationError if serialization fails. """ if data is None: raise ValueError("No value for given attribute") try: if data_type in self.basic_types.values(): return self.serialize_basic(data, data_type) elif data_type in self.serialize_type: return self.serialize_type[data_type](data, **kwargs) # If dependencies is empty, try with current data class # It has to be a subclass of Enum anyway enum_type = self.dependencies.get(data_type, data.__class__) if issubclass(enum_type, Enum): return Serializer.serialize_enum(data, enum_obj=enum_type) iter_type = data_type[0] + data_type[-1] if iter_type in self.serialize_type: return self.serialize_type[iter_type]( data, data_type[1:-1], **kwargs) except (ValueError, TypeError) as err: msg = "Unable to serialize value: {!r} as type: {!r}." raise_with_traceback( SerializationError, msg.format(data, data_type), err) else: return self._serialize(data, **kwargs) def serialize_basic(self, data, data_type): """Serialize basic builting data type. Serializes objects to str, int, float or bool. :param data: Object to be serialized. :param str data_type: Type of object in the iterable. """ if data_type == 'str': return self.serialize_unicode(data) return eval(data_type)(data) def serialize_unicode(self, data): """Special handling for serializing unicode strings in Py2. Encode to UTF-8 if unicode, otherwise handle as a str. :param data: Object to be serialized. :rtype: str """ try: return data.value except AttributeError: pass try: if isinstance(data, unicode): return data.encode(encoding='utf-8') except NameError: return str(data) else: return str(data) def serialize_iter(self, data, iter_type, div=None, **kwargs): """Serialize iterable. :param list attr: Object to be serialized. :param str iter_type: Type of object in the iterable. :param bool required: Whether the objects in the iterable must not be None or empty. :param str div: If set, this str will be used to combine the elements in the iterable into a combined string. Default is 'None'. :rtype: list, str """ serialized = [] for d in data: try: serialized.append( self.serialize_data(d, iter_type, **kwargs)) except ValueError: serialized.append(None) if div: serialized = ['' if s is None else s for s in serialized] serialized = div.join(serialized) return serialized def serialize_dict(self, attr, dict_type, **kwargs): """Serialize a dictionary of objects. :param dict attr: Object to be serialized. :param str dict_type: Type of object in the dictionary. :param bool required: Whether the objects in the dictionary must not be None or empty. :rtype: dict """ serialized = {} for key, value in attr.items(): try: serialized[self.serialize_unicode(key)] = self.serialize_data( value, dict_type, **kwargs) except ValueError: serialized[self.serialize_unicode(key)] = None return serialized def serialize_object(self, attr, **kwargs): """Serialize a generic object. This will be handled as a dictionary. If object passed in is not a basic type (str, int, float, dict, list) it will simply be cast to str. :param dict attr: Object to be serialized. :rtype: dict or str """ if attr is None: return None obj_type = type(attr) if obj_type in self.basic_types: return self.serialize_basic(attr, self.basic_types[obj_type]) # If it's a model or I know this dependency, serialize as a Model elif obj_type in self.dependencies.values() or isinstance(obj_type, Model): return self._serialize(attr) if obj_type == dict: serialized = {} for key, value in attr.items(): try: serialized[self.serialize_unicode(key)] = self.serialize_object( value, **kwargs) except ValueError: serialized[self.serialize_unicode(key)] = None return serialized if obj_type == list: serialized = [] for obj in attr: try: serialized.append(self.serialize_object( obj, **kwargs)) except ValueError: pass return serialized return str(attr) @staticmethod def serialize_enum(attr, enum_obj=None): try: result = attr.value except AttributeError: result = attr try: enum_obj(result) return result except ValueError: for enum_value in enum_obj: if enum_value.value.lower() == str(attr).lower(): return enum_value.value error = "{!r} is not valid value for enum {!r}" raise SerializationError(error.format(attr, enum_obj)) @staticmethod def serialize_bytearray(attr, **kwargs): """Serialize bytearray into base-64 string. :param attr: Object to be serialized. :rtype: str """ return b64encode(attr).decode() @staticmethod def serialize_base64(attr, **kwargs): """Serialize str into base-64 string. :param attr: Object to be serialized. :rtype: str """ encoded = b64encode(attr).decode('ascii') return encoded.strip('=').replace('+', '-').replace('/', '_') @staticmethod def serialize_decimal(attr, **kwargs): """Serialize Decimal object to float. :param attr: Object to be serialized. :rtype: float """ return float(attr) @staticmethod def serialize_long(attr, **kwargs): """Serialize long (Py2) or int (Py3). :param attr: Object to be serialized. :rtype: int/long """ try: return long(attr) except NameError: return int(attr) @staticmethod def serialize_date(attr, **kwargs): """Serialize Date object into ISO-8601 formatted string. :param Date attr: Object to be serialized. :rtype: str """ if isinstance(attr, str): attr = isodate.parse_date(attr) t = "{:04}-{:02}-{:02}".format(attr.year, attr.month, attr.day) return t @staticmethod def serialize_duration(attr, **kwargs): """Serialize TimeDelta object into ISO-8601 formatted string. :param TimeDelta attr: Object to be serialized. :rtype: str """ if isinstance(attr, str): attr = isodate.parse_duration(attr) return isodate.duration_isoformat(attr) @staticmethod def serialize_rfc(attr, **kwargs): """Serialize Datetime object into RFC-1123 formatted string. :param Datetime attr: Object to be serialized. :rtype: str :raises: TypeError if format invalid. """ try: if not attr.tzinfo: _LOGGER.warning( "Datetime with no tzinfo will be considered UTC.") utc = attr.utctimetuple() except AttributeError: raise TypeError("RFC1123 object must be valid Datetime object.") return "{}, {:02} {} {:04} {:02}:{:02}:{:02} GMT".format( Serializer.days[utc.tm_wday], utc.tm_mday, Serializer.months[utc.tm_mon], utc.tm_year, utc.tm_hour, utc.tm_min, utc.tm_sec) @staticmethod def serialize_iso(attr, **kwargs): """Serialize Datetime object into ISO-8601 formatted string. :param Datetime attr: Object to be serialized. :rtype: str :raises: SerializationError if format invalid. """ if isinstance(attr, str): attr = isodate.parse_datetime(attr) try: if not attr.tzinfo: _LOGGER.warning( "Datetime with no tzinfo will be considered UTC.") utc = attr.utctimetuple() if utc.tm_year > 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.") def rest_key_extractor(attr, attr_desc, data): key = attr_desc['key'] working_data = data while '.' in key: dict_keys = _FLATTEN.split(key) if len(dict_keys) == 1: key = _decode_attribute_map_key(dict_keys[0]) break working_key = _decode_attribute_map_key(dict_keys[0]) working_data = working_data.get(working_key, data) key = '.'.join(dict_keys[1:]) return working_data.get(key) def rest_key_case_insensitive_extractor(attr, attr_desc, data): key = attr_desc['key'] working_data = data while '.' in key: dict_keys = _FLATTEN.split(key) if len(dict_keys) == 1: key = _decode_attribute_map_key(dict_keys[0]) break working_key = _decode_attribute_map_key(dict_keys[0]) working_data = attribute_key_case_insensitive_extractor(working_key, None, working_data) key = '.'.join(dict_keys[1:]) if working_data: return attribute_key_case_insensitive_extractor(key, None, working_data) def last_rest_key_extractor(attr, attr_desc, data): key = attr_desc['key'] dict_keys = _FLATTEN.split(key) return attribute_key_extractor(dict_keys[-1], None, data) def last_rest_key_case_insensitive_extractor(attr, attr_desc, data): key = attr_desc['key'] dict_keys = _FLATTEN.split(key) return attribute_key_case_insensitive_extractor(dict_keys[-1], None, data) def attribute_key_extractor(attr, _, data): return data.get(attr) def attribute_key_case_insensitive_extractor(attr, _, data): found_key = None lower_attr = attr.lower() for key in data: if lower_attr == key.lower(): found_key = key break return data.get(found_key) 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}' r'\.?\d*Z?[-+]?[\d{2}]?:?[\d{2}]?') def __init__(self, classes=None): self.deserialize_type = { 'iso-8601': Deserializer.deserialize_iso, 'rfc-1123': Deserializer.deserialize_rfc, 'unix-time': Deserializer.deserialize_unix, 'duration': Deserializer.deserialize_duration, 'date': Deserializer.deserialize_date, 'decimal': Deserializer.deserialize_decimal, 'long': Deserializer.deserialize_long, 'bytearray': Deserializer.deserialize_bytearray, 'base64': Deserializer.deserialize_base64, 'object': self.deserialize_object, '[]': self.deserialize_iter, '{}': self.deserialize_dict } self.deserialize_expected_types = { 'duration': (isodate.Duration, datetime.timedelta) } self.dependencies = dict(classes) if classes else {} self.key_extractors = [ rest_key_extractor ] def __call__(self, target_obj, response_data): """Call the deserializer to process a REST response. :param str target_obj: Target data type to deserialize to. :param requests.Response response_data: REST response object. :raises: DeserializationError if deserialization fails. :return: Deserialized object. """ # This is already a model, go recursive just in case if hasattr(response_data, "_attribute_map"): constants = [name for name, config in getattr(response_data, '_validation', {}).items() if config.get('constant')] try: for attr, mapconfig in response_data._attribute_map.items(): if attr in constants: continue value = getattr(response_data, attr) if value is None: continue local_type = mapconfig['type'] internal_data_type = local_type.strip('[]{}') if internal_data_type not in self.dependencies or isinstance(internal_data_type, Enum): continue setattr( response_data, attr, self(local_type, value) ) return response_data except AttributeError: return data = self._unpack_content(response_data) response, class_name = self._classify_target(target_obj, data) if isinstance(response, basestring): return self.deserialize_data(data, response) elif isinstance(response, type) and issubclass(response, Enum): return self.deserialize_enum(data, response) if data is None: return data try: attributes = response._attribute_map d_attrs = {} for attr, attr_desc in attributes.items(): raw_value = None for key_extractor in self.key_extractors: found_value = key_extractor(attr, attr_desc, data) if found_value is not None: if raw_value is not None and raw_value != found_value: raise KeyError('Use twice the key: "{}"'.format(attr)) raw_value = found_value value = self.deserialize_data(raw_value, attr_desc['type']) d_attrs[attr] = value except (AttributeError, TypeError, KeyError) as err: msg = "Unable to deserialize to object: " + class_name raise_with_traceback(DeserializationError, msg, err) else: return self._instantiate_model(response, d_attrs) def _classify_target(self, target, data): """Check to see whether the deserialization target object can be classified into a subclass. Once classification has been determined, initialize object. :param str target: The target object type to deserialize to. :param str/dict data: The response data to deseralize. """ if target is None: return None, None if isinstance(target, basestring): try: target = self.dependencies[target] except KeyError: return target, target try: target = target._classify(data, self.dependencies) except AttributeError: pass # Target is not a Model, no classify return target, target.__class__.__name__ def _unpack_content(self, raw_data): """Extract data from the body of a REST response object. :param raw_data: Data to be processed. This could be a requests.Response object, in which case the json content will be be returned. """ if raw_data and isinstance(raw_data, bytes): data = raw_data.decode(encoding='utf-8') else: data = raw_data try: # This is a requests.Response, json valid if nothing fail if not raw_data.text: return None return json.loads(raw_data.text) except (ValueError, TypeError, AttributeError): pass return data def _instantiate_model(self, response, attrs): """Instantiate a response model passing in deserialized args. :param response: The response model class. :param d_attrs: The deserialized response attributes. """ if callable(response): subtype = getattr(response, '_subtype_map', {}) try: readonly = [k for k, v in response._validation.items() if v.get('readonly')] const = [k for k, v in response._validation.items() if v.get('constant')] kwargs = {k: v for k, v in attrs.items() if k not in subtype and k not in readonly + const} response_obj = response(**kwargs) for attr in readonly: setattr(response_obj, attr, attrs.get(attr)) return response_obj except TypeError as err: msg = "Unable to deserialize {} into model {}. ".format( kwargs, response) raise DeserializationError(msg + str(err)) else: try: for attr, value in attrs.items(): setattr(response, attr, value) return response except Exception as exp: msg = "Unable to populate response model. " msg += "Type: {}, Error: {}".format(type(response), exp) raise DeserializationError(msg) def deserialize_data(self, data, data_type): """Process data for deserialization according to data type. :param str data: The response string to be deserialized. :param str data_type: The type to deserialize to. :raises: DeserializationError if deserialization fails. :return: Deserialized object. """ if data is None: return data try: if not data_type: return data if data_type in self.basic_types.values(): return self.deserialize_basic(data, data_type) if data_type in self.deserialize_type: if isinstance(data, self.deserialize_expected_types.get(data_type, tuple())): return data data_val = self.deserialize_type[data_type](data) return data_val iter_type = data_type[0] + data_type[-1] if iter_type in self.deserialize_type: return self.deserialize_type[iter_type](data, data_type[1:-1]) obj_type = self.dependencies[data_type] if issubclass(obj_type, Enum): return self.deserialize_enum(data, obj_type) except (ValueError, TypeError, AttributeError) as err: msg = "Unable to deserialize response data." msg += " Data: {}, {}".format(data, data_type) raise_with_traceback(DeserializationError, msg, err) else: return self(obj_type, data) def deserialize_iter(self, attr, iter_type): """Deserialize an iterable. :param list attr: Iterable to be deserialized. :param str iter_type: The type of object in the iterable. :rtype: list """ if not attr and not isinstance(attr, list): return None return [self.deserialize_data(a, iter_type) for a in attr] def deserialize_dict(self, attr, dict_type): """Deserialize a dictionary. :param dict/list attr: Dictionary to be deserialized. Also accepts a list of key, value pairs. :param str dict_type: The object type of the items in the dictionary. :rtype: dict """ if isinstance(attr, list): return {x['key']: self.deserialize_data( x['value'], dict_type) for x in attr} return {k: self.deserialize_data( v, dict_type) for k, v in attr.items()} def deserialize_object(self, attr, **kwargs): """Deserialize a generic object. This will be handled as a dictionary. :param dict attr: Dictionary to be deserialized. :rtype: dict :raises: TypeError if non-builtin datatype encountered. """ if attr is None: return None if isinstance(attr, basestring): return self.deserialize_basic(attr, 'str') obj_type = type(attr) if obj_type in self.basic_types: return self.deserialize_basic(attr, self.basic_types[obj_type]) if obj_type == dict: deserialized = {} for key, value in attr.items(): try: deserialized[key] = self.deserialize_object( value, **kwargs) except ValueError: deserialized[key] = None return deserialized if obj_type == list: deserialized = [] for obj in attr: try: deserialized.append(self.deserialize_object( obj, **kwargs)) except ValueError: pass return deserialized else: error = "Cannot deserialize generic object with type: " raise TypeError(error + str(obj_type)) def deserialize_basic(self, attr, data_type): """Deserialize baisc builtin data type from string. Will attempt to convert to str, int, float and bool. This function will also accept '1', '0', 'true' and 'false' as valid bool values. :param str attr: response string to be deserialized. :param str data_type: deserialization data type. :rtype: str, int, float or bool :raises: TypeError if string format is not valid. """ if data_type == 'bool': if attr in [True, False, 1, 0]: return bool(attr) elif isinstance(attr, basestring): if attr.lower() in ['true', '1']: return True elif attr.lower() in ['false', '0']: return False raise TypeError("Invalid boolean value: {}".format(attr)) if data_type == 'str': return self.deserialize_unicode(attr) return eval(data_type)(attr) def deserialize_unicode(self, data): """Preserve unicode objects in Python 2, otherwise return data as a string. :param str data: response string to be deserialized. :rtype: str or unicode """ # We might be here because we have an enum modeled as string, # and we try to deserialize a partial dict with enum inside if isinstance(data, Enum): return data # Consider this is real string try: if isinstance(data, unicode): return data except NameError: return str(data) else: return str(data) def deserialize_enum(self, data, enum_obj): """Deserialize string into enum object. :param str data: response string to be deserialized. :param Enum enum_obj: Enum object to deserialize to. :rtype: Enum :raises: DeserializationError if string is not valid enum value. """ if isinstance(data, enum_obj): return data if isinstance(data, int): # Workaround. We might consider remove it in the future. # https://github.com/Azure/azure-rest-api-specs/issues/141 try: return list(enum_obj.__members__.values())[data] except IndexError: error = "{!r} is not a valid index for enum {!r}" raise DeserializationError(error.format(data, enum_obj)) try: return enum_obj(str(data)) except ValueError: for enum_value in enum_obj: if enum_value.value.lower() == str(data).lower(): return enum_value error = "{!r} is not valid value for enum {!r}" raise DeserializationError(error.format(data, enum_obj)) @staticmethod def deserialize_bytearray(attr): """Deserialize string into bytearray. :param str attr: response string to be deserialized. :rtype: bytearray :raises: TypeError if string format invalid. """ return bytearray(b64decode(attr)) @staticmethod def deserialize_base64(attr): """Deserialize base64 encoded string into string. :param str attr: response string to be deserialized. :rtype: bytearray :raises: TypeError if string format invalid. """ padding = '=' * (3 - (len(attr) + 3) % 4) attr = attr + padding encoded = attr.replace('-', '+').replace('_', '/') return b64decode(encoded) @staticmethod def deserialize_decimal(attr): """Deserialize string into Decimal object. :param str attr: response string to be deserialized. :rtype: Decimal :raises: DeserializationError if string format invalid. """ try: return decimal.Decimal(attr) except decimal.DecimalException as err: msg = "Invalid decimal {}".format(attr) raise_with_traceback(DeserializationError, msg, err) @staticmethod def deserialize_long(attr): """Deserialize string into long (Py2) or int (Py3). :param str attr: response string to be deserialized. :rtype: long or int :raises: ValueError if string format invalid. """ try: return long(attr) except NameError: return int(attr) @staticmethod def deserialize_duration(attr): """Deserialize ISO-8601 formatted string into TimeDelta object. :param str attr: response string to be deserialized. :rtype: TimeDelta :raises: DeserializationError if string format invalid. """ try: duration = isodate.parse_duration(attr) except(ValueError, OverflowError, AttributeError) as err: msg = "Cannot deserialize duration object." raise_with_traceback(DeserializationError, msg, err) else: return duration @staticmethod def deserialize_date(attr): """Deserialize ISO-8601 formatted string into Date object. :param str attr: response string to be deserialized. :rtype: Date :raises: DeserializationError if string format invalid. """ return isodate.parse_date(attr) @staticmethod def deserialize_rfc(attr): """Deserialize RFC-1123 formatted string into Datetime object. :param str attr: response string to be deserialized. :rtype: Datetime :raises: DeserializationError if string format invalid. """ try: date_obj = datetime.datetime.strptime( attr, "%a, %d %b %Y %H:%M:%S %Z") if not date_obj.tzinfo: date_obj = date_obj.replace(tzinfo=TZ_UTC) except ValueError as err: msg = "Cannot deserialize to rfc datetime object." raise_with_traceback(DeserializationError, msg, err) else: return date_obj @staticmethod def deserialize_iso(attr): """Deserialize ISO-8601 formatted string into Datetime object. :param str attr: response string to be deserialized. :rtype: Datetime :raises: DeserializationError if string format invalid. """ try: attr = attr.upper() match = Deserializer.valid_date.match(attr) if not match: raise ValueError("Invalid datetime string: " + attr) check_decimal = attr.split('.') if len(check_decimal) > 1: decimal_str = "" for digit in check_decimal[1]: if digit.isdigit(): decimal_str += digit else: break if len(decimal_str) > 6: attr = attr.replace(decimal_str, decimal_str[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 msrest-0.4.14/msrest/configuration.py0000664000372000037200000001504613147325371020531 0ustar travistravis00000000000000# -------------------------------------------------------------------------- # # 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) # Requests hooks. Must respect requests hook callback signature # Note that we will inject the following parameters: # - kwargs['msrest']['session'] with the current session self.hooks = [] 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() msrest-0.4.14/msrest/pipeline.py0000664000372000037200000002045613147325371017470 0ustar travistravis00000000000000# -------------------------------------------------------------------------- # # 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 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 ClientRetryPolicy(object): """Retry configuration settings. Container for retry policy object. """ safe_codes = [i for i in range(500) if i != 408] + [501, 505] def __init__(self): self.policy = Retry() self.policy.total = 3 self.policy.connect = 3 self.policy.read = 3 self.policy.backoff_factor = 0.8 self.policy.BACKOFF_MAX = 90 retry_codes = [i for i in range(999) if i not in self.safe_codes] self.policy.status_forcelist = retry_codes self.policy.method_whitelist = ['HEAD', 'TRACE', 'GET', 'PUT', 'OPTIONS', 'DELETE', 'POST', 'PATCH'] def __call__(self): """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} msrest-0.4.14/msrest/__init__.py0000664000372000037200000000310613147325371017413 0ustar travistravis00000000000000# -------------------------------------------------------------------------- # # 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 msrest-0.4.14/PKG-INFO0000664000372000037200000002162413147325477015076 0ustar travistravis00000000000000Metadata-Version: 1.1 Name: msrest Version: 0.4.14 Summary: AutoRest swagger generator Python client runtime. Home-page: https://github.com/Azure/msrest-for-python Author: Microsoft Corporation Author-email: UNKNOWN License: MIT License Description: AutoRest: Python Client Runtime ================================ .. image:: https://travis-ci.org/Azure/msrest-for-python.svg?branch=master :target: https://travis-ci.org/Azure/msrest-for-python .. image:: https://codecov.io/gh/azure/msrest-for-python/branch/master/graph/badge.svg :target: https://codecov.io/gh/azure/msrest-for-python Installation ------------ To install: .. code-block:: bash $ pip install msrest Release History --------------- 2017-08-23 Version 0.4.14 +++++++++++++++++++++++++ **Bugfixes** - Fix regression introduced in msrest 0.4.12 - dict syntax with enum modeled as string and enum used 2017-08-22 Version 0.4.13 +++++++++++++++++++++++++ **Bugfixes** - Fix regression introduced in msrest 0.4.12 - dict syntax using isodate.Duration (#42) 2017-08-21 Version 0.4.12 +++++++++++++++++++++++++ **Features** - Input is now more lenient - Model have a "validate" method to check content constraints - Model have now 4 new methods: - "serialize" that gives the RestAPI that will be sent - "as_dict" that returns a dict version of the Model. Callbacks are available. - "deserialize" the parses the RestAPI JSON into a Model - "from_dict" that parses several dict syntax into a Model. Callbacks are available. More details and examples in the Wiki article on Github: https://github.com/Azure/msrest-for-python/wiki/msrest-0.4.12---Serialization-change **Bugfixes** - Better Enum checking (#38) 2017-06-21 Version 0.4.11 +++++++++++++++++++++++++ **Bugfixes** - Fix incorrect dependency to "requests" 2.14.x, instead of 2.x meant in 0.4.8 2017-06-15 Version 0.4.10 +++++++++++++++++++++++++ **Features** - Add requests hooks to configuration 2017-06-08 Version 0.4.9 ++++++++++++++++++++++++ **Bugfixes** - Accept "null" value for paging array as an empty list and do not raise (#30) 2017-05-22 Version 0.4.8 ++++++++++++++++++++++++ **Bugfixes** - Fix random "pool is closed" error (#29) - Fix requests dependency to version 2.x, since version 3.x is annunced to be breaking. 2017-04-04 Version 0.4.7 ++++++++++++++++++++++++ **BugFixes** - Refactor paging #22: - "next" is renamed "advance_page" and "next" returns only 1 element (Python 2 expected behavior) - paging objects are now real generator and support the "next()" built-in function without need for "iter()" - Raise accurate DeserialisationError on incorrect RestAPI discriminator usage #27 - Fix discriminator usage of the base class name #27 - Remove default mutable arguments in Clients #20 - Fix object comparison in some scenarios #24 2017-03-06 Version 0.4.6 ++++++++++++++++++++++++ **Bugfixes** - Allow Model sub-classes to be serialized if type is "object" 2017-02-13 Version 0.4.5 ++++++++++++++++++++++++ **Bugfixes** - Fix polymorphic deserialization #11 - Fix regexp validation if '\\w' is used in Python 2.7 #13 - Fix dict deserialization if keys are unicode in Python 2.7 **Improvements** - Add polymorphic serialisation from dict objects - Remove chardet and use HTTP charset declaration (fallback to utf8) 2016-09-14 Version 0.4.4 ++++++++++++++++++++++++ **Bugfixes** - Remove paging URL validation, part of fix https://github.com/Azure/autorest/pull/1420 **Disclaimer** In order to get paging fixes for impacted clients, you need this package and Autorest > 0.17.0 Nightly 20160913 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. 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: Programming Language :: Python :: 3.6 Classifier: License :: OSI Approved :: MIT License Classifier: Topic :: Software Development