├── .coveragerc ├── .gitignore ├── .travis.yml ├── .vscode └── settings.json ├── LICENSE.md ├── MANIFEST.in ├── README.rst ├── SECURITY.md ├── autorest_setup.sh ├── dev_requirements.txt ├── doc ├── conf.py ├── index.md ├── make.bat └── requirements.txt ├── msrest ├── __init__.py ├── async_client.py ├── async_paging.py ├── authentication.py ├── configuration.py ├── exceptions.py ├── http_logger.py ├── paging.py ├── pipeline │ ├── __init__.py │ ├── aiohttp.py │ ├── async_abc.py │ ├── async_requests.py │ ├── requests.py │ └── universal.py ├── polling │ ├── __init__.py │ ├── async_poller.py │ └── poller.py ├── py.typed ├── serialization.py ├── service_client.py ├── universal_http │ ├── __init__.py │ ├── aiohttp.py │ ├── async_abc.py │ ├── async_requests.py │ └── requests.py └── version.py ├── pylintrc ├── setup.cfg ├── setup.py ├── tests ├── __init__.py ├── asynctests │ ├── test_async_client.py │ ├── test_async_paging.py │ ├── test_pipeline.py │ ├── test_polling.py │ └── test_universal_http.py ├── conftest.py ├── storage_models │ ├── __init__.py │ ├── account_sas_parameters.py │ ├── check_name_availability_result.py │ ├── custom_domain.py │ ├── encryption.py │ ├── encryption_service.py │ ├── encryption_services.py │ ├── endpoints.py │ ├── list_account_sas_response.py │ ├── list_service_sas_response.py │ ├── resource.py │ ├── service_sas_parameters.py │ ├── sku.py │ ├── storage_account.py │ ├── storage_account_check_name_availability_parameters.py │ ├── storage_account_create_parameters.py │ ├── storage_account_key.py │ ├── storage_account_list_keys_result.py │ ├── storage_account_paged.py │ ├── storage_account_regenerate_key_parameters.py │ ├── storage_account_update_parameters.py │ ├── storage_management_client_enums.py │ ├── usage.py │ ├── usage_name.py │ └── usage_paged.py ├── test_auth.py ├── test_client.py ├── test_exceptions.py ├── test_paging.py ├── test_pipeline.py ├── test_polling.py ├── test_requests_universal.py ├── test_runtime.py ├── test_serialization.py ├── test_universal_pipeline.py └── test_xml_serialization.py └── tox.ini /.coveragerc: -------------------------------------------------------------------------------- 1 | [report] 2 | exclude_lines = 3 | pragma: no cover 4 | if TYPE_CHECKING: 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | env* 3 | .venv 4 | *.egg-info 5 | .tox 6 | .coverage 7 | coverage.xml 8 | *.pyc 9 | .idea 10 | build/ 11 | dist/ 12 | .cache 13 | .pytest_cache 14 | .mypy_cache -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | dist: xenial 2 | language: python 3 | cache: pip 4 | _test: &_test 5 | install: 6 | - pip install tox tox-virtualenv-no-download 7 | script: 8 | - tox 9 | after_success: 10 | - bash <(curl -s https://codecov.io/bash) -e TOXENV -f $TRAVIS_BUILD_DIR/coverage.xml 11 | _autorest_install: &_autorest_install 12 | before_install: 13 | - git clone https://github.com/Azure/autorest.python.git --branch autorestv3 --single-branch 14 | - nvm install 10 15 | - pushd autorest.python 16 | - npm install "@microsoft.azure/autorest.testserver" # Install test server pre-requisites 17 | - popd 18 | jobs: 19 | include: 20 | - stage: MyPy 21 | python: 3.6 22 | install: 23 | - pip install -r dev_requirements.txt 24 | script: 25 | - mypy msrest 26 | - python -c 'import typing; typing.TYPE_CHECKING = True; import msrest' # Testing there is no circular dependencies in Type checking mode 27 | - stage: Test 28 | python: 2.7 29 | env: TOXENV=py27 30 | <<: *_test 31 | - stage: Test 32 | python: 3.5 33 | env: TOXENV=py35 34 | <<: *_test 35 | - stage: Test 36 | python: 3.6 37 | env: TOXENV=py36 38 | <<: *_test 39 | - stage: Test 40 | python: 3.7 41 | env: TOXENV=py37 42 | <<: *_test 43 | - stage: Test 44 | python: 3.8 45 | env: TOXENV=py38 46 | <<: *_test 47 | - stage: Test 48 | python: 2.7 49 | env: TOXENV=py27-autorest 50 | <<: *_autorest_install 51 | <<: *_test 52 | - stage: Test 53 | python: 3.5 54 | env: TOXENV=py35-autorest 55 | <<: *_autorest_install 56 | <<: *_test 57 | - stage: Test 58 | python: 3.6 59 | env: TOXENV=py36-autorest 60 | <<: *_autorest_install 61 | <<: *_test 62 | - stage: Test 63 | python: 3.7 64 | env: TOXENV=py37-autorest 65 | <<: *_autorest_install 66 | <<: *_test 67 | - stage: Test 68 | python: 3.8 69 | env: TOXENV=py38-autorest 70 | <<: *_autorest_install 71 | <<: *_test 72 | 73 | allow_failures: 74 | - env: TOXENV=py27-autorest 75 | - env: TOXENV=py35-autorest 76 | - env: TOXENV=py36-autorest 77 | - env: TOXENV=py37-autorest 78 | - env: TOXENV=py38-autorest 79 | deploy: 80 | provider: pypi 81 | user: Laurent.Mazuel 82 | skip_upload_docs: true 83 | # password: use $PYPI_PASSWORD 84 | distributions: "sdist bdist_wheel" 85 | on: 86 | tags: true 87 | python: '3.6' 88 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | // Place your settings in this file to overwrite default and user settings. 2 | { 3 | "python.testing.pytestArgs": [], 4 | "python.testing.pytestEnabled": true, 5 | "files.exclude": { 6 | "**/*.pyc": true 7 | }, 8 | "git.ignoreLimitWarning": true, 9 | "python.linting.pylintEnabled": true 10 | } -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Microsoft Azure 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include *.rst 2 | recursive-include tests *.py 3 | include msrest/py.typed -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Security 4 | 5 | Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/Microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/). 6 | 7 | If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://aka.ms/opensource/security/definition), please report it to us as described below. 8 | 9 | ## Reporting Security Issues 10 | 11 | **Please do not report security vulnerabilities through public GitHub issues.** 12 | 13 | Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://aka.ms/opensource/security/create-report). 14 | 15 | If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://aka.ms/opensource/security/pgpkey). 16 | 17 | You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://aka.ms/opensource/security/msrc). 18 | 19 | Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue: 20 | 21 | * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.) 22 | * Full paths of source file(s) related to the manifestation of the issue 23 | * The location of the affected source code (tag/branch/commit or direct URL) 24 | * Any special configuration required to reproduce the issue 25 | * Step-by-step instructions to reproduce the issue 26 | * Proof-of-concept or exploit code (if possible) 27 | * Impact of the issue, including how an attacker might exploit the issue 28 | 29 | This information will help us triage your report more quickly. 30 | 31 | If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://aka.ms/opensource/security/bounty) page for more details about our active programs. 32 | 33 | ## Preferred Languages 34 | 35 | We prefer all communications to be in English. 36 | 37 | ## Policy 38 | 39 | Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://aka.ms/opensource/security/cvd). 40 | 41 | 42 | -------------------------------------------------------------------------------- /autorest_setup.sh: -------------------------------------------------------------------------------- 1 | pushd autorest.python/test/vanilla/ && pip install -r requirements.txt && popd -------------------------------------------------------------------------------- /dev_requirements.txt: -------------------------------------------------------------------------------- 1 | -e . 2 | mock;python_version<="2.7" 3 | futures;python_version<="2.7" 4 | httpretty>=0.8.10 5 | coverage<5.0.0 6 | pytest 7 | pytest-cov 8 | pytest-asyncio;python_full_version>="3.5.2" 9 | mypy;python_full_version>="3.5.2" 10 | pylint 11 | aiohttp;python_full_version>="3.5.2" 12 | # async in msrest was experimental, we won't update 13 | trio==0.14.0;python_version == '3.5' 14 | trio==0.16.0;python_version >= '3.6' and python_version < '3.10' 15 | trio==0.20.0;python_version >= '3.10' 16 | -------------------------------------------------------------------------------- /doc/index.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | msrest's documentation has moved from ReadTheDocs to docs.microsoft.com. 6 | 7 | -------------------------------------------------------------------------------- /doc/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | REM Command file for Sphinx documentation 4 | 5 | if "%SPHINXBUILD%" == "" ( 6 | set SPHINXBUILD=sphinx-build 7 | ) 8 | set BUILDDIR=_build 9 | set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . 10 | set I18NSPHINXOPTS=%SPHINXOPTS% . 11 | if NOT "%PAPER%" == "" ( 12 | set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% 13 | set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% 14 | ) 15 | 16 | if "%1" == "" goto help 17 | 18 | if "%1" == "help" ( 19 | :help 20 | echo.Please use `make ^` where ^ is one of 21 | echo. html to make standalone HTML files 22 | echo. dirhtml to make HTML files named index.html in directories 23 | echo. singlehtml to make a single large HTML file 24 | echo. pickle to make pickle files 25 | echo. json to make JSON files 26 | echo. htmlhelp to make HTML files and a HTML help project 27 | echo. qthelp to make HTML files and a qthelp project 28 | echo. devhelp to make HTML files and a Devhelp project 29 | echo. epub to make an epub 30 | echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter 31 | echo. text to make text files 32 | echo. man to make manual pages 33 | echo. texinfo to make Texinfo files 34 | echo. gettext to make PO message catalogs 35 | echo. changes to make an overview over all changed/added/deprecated items 36 | echo. xml to make Docutils-native XML files 37 | echo. pseudoxml to make pseudoxml-XML files for display purposes 38 | echo. linkcheck to check all external links for integrity 39 | echo. doctest to run all doctests embedded in the documentation if enabled 40 | goto end 41 | ) 42 | 43 | if "%1" == "clean" ( 44 | for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i 45 | del /q /s %BUILDDIR%\* 46 | goto end 47 | ) 48 | 49 | 50 | %SPHINXBUILD% 2> nul 51 | if errorlevel 9009 ( 52 | echo. 53 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 54 | echo.installed, then set the SPHINXBUILD environment variable to point 55 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 56 | echo.may add the Sphinx directory to PATH. 57 | echo. 58 | echo.If you don't have Sphinx installed, grab it from 59 | echo.http://sphinx-doc.org/ 60 | exit /b 1 61 | ) 62 | 63 | if "%1" == "html" ( 64 | %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html 65 | if errorlevel 1 exit /b 1 66 | echo. 67 | echo.Build finished. The HTML pages are in %BUILDDIR%/html. 68 | goto end 69 | ) 70 | 71 | if "%1" == "dirhtml" ( 72 | %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml 73 | if errorlevel 1 exit /b 1 74 | echo. 75 | echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. 76 | goto end 77 | ) 78 | 79 | if "%1" == "singlehtml" ( 80 | %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml 81 | if errorlevel 1 exit /b 1 82 | echo. 83 | echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. 84 | goto end 85 | ) 86 | 87 | if "%1" == "pickle" ( 88 | %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle 89 | if errorlevel 1 exit /b 1 90 | echo. 91 | echo.Build finished; now you can process the pickle files. 92 | goto end 93 | ) 94 | 95 | if "%1" == "json" ( 96 | %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json 97 | if errorlevel 1 exit /b 1 98 | echo. 99 | echo.Build finished; now you can process the JSON files. 100 | goto end 101 | ) 102 | 103 | if "%1" == "htmlhelp" ( 104 | %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp 105 | if errorlevel 1 exit /b 1 106 | echo. 107 | echo.Build finished; now you can run HTML Help Workshop with the ^ 108 | .hhp project file in %BUILDDIR%/htmlhelp. 109 | goto end 110 | ) 111 | 112 | if "%1" == "qthelp" ( 113 | %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp 114 | if errorlevel 1 exit /b 1 115 | echo. 116 | echo.Build finished; now you can run "qcollectiongenerator" with the ^ 117 | .qhcp project file in %BUILDDIR%/qthelp, like this: 118 | echo.^> qcollectiongenerator %BUILDDIR%\qthelp\pydocumentdb.qhcp 119 | echo.To view the help file: 120 | echo.^> assistant -collectionFile %BUILDDIR%\qthelp\pydocumentdb.ghc 121 | goto end 122 | ) 123 | 124 | if "%1" == "devhelp" ( 125 | %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp 126 | if errorlevel 1 exit /b 1 127 | echo. 128 | echo.Build finished. 129 | goto end 130 | ) 131 | 132 | if "%1" == "epub" ( 133 | %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub 134 | if errorlevel 1 exit /b 1 135 | echo. 136 | echo.Build finished. The epub file is in %BUILDDIR%/epub. 137 | goto end 138 | ) 139 | 140 | if "%1" == "latex" ( 141 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 142 | if errorlevel 1 exit /b 1 143 | echo. 144 | echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. 145 | goto end 146 | ) 147 | 148 | if "%1" == "latexpdf" ( 149 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 150 | cd %BUILDDIR%/latex 151 | make all-pdf 152 | cd %BUILDDIR%/.. 153 | echo. 154 | echo.Build finished; the PDF files are in %BUILDDIR%/latex. 155 | goto end 156 | ) 157 | 158 | if "%1" == "latexpdfja" ( 159 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 160 | cd %BUILDDIR%/latex 161 | make all-pdf-ja 162 | cd %BUILDDIR%/.. 163 | echo. 164 | echo.Build finished; the PDF files are in %BUILDDIR%/latex. 165 | goto end 166 | ) 167 | 168 | if "%1" == "text" ( 169 | %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text 170 | if errorlevel 1 exit /b 1 171 | echo. 172 | echo.Build finished. The text files are in %BUILDDIR%/text. 173 | goto end 174 | ) 175 | 176 | if "%1" == "man" ( 177 | %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man 178 | if errorlevel 1 exit /b 1 179 | echo. 180 | echo.Build finished. The manual pages are in %BUILDDIR%/man. 181 | goto end 182 | ) 183 | 184 | if "%1" == "texinfo" ( 185 | %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo 186 | if errorlevel 1 exit /b 1 187 | echo. 188 | echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. 189 | goto end 190 | ) 191 | 192 | if "%1" == "gettext" ( 193 | %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale 194 | if errorlevel 1 exit /b 1 195 | echo. 196 | echo.Build finished. The message catalogs are in %BUILDDIR%/locale. 197 | goto end 198 | ) 199 | 200 | if "%1" == "changes" ( 201 | %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes 202 | if errorlevel 1 exit /b 1 203 | echo. 204 | echo.The overview file is in %BUILDDIR%/changes. 205 | goto end 206 | ) 207 | 208 | if "%1" == "linkcheck" ( 209 | %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck 210 | if errorlevel 1 exit /b 1 211 | echo. 212 | echo.Link check complete; look for any errors in the above output ^ 213 | or in %BUILDDIR%/linkcheck/output.txt. 214 | goto end 215 | ) 216 | 217 | if "%1" == "doctest" ( 218 | %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest 219 | if errorlevel 1 exit /b 1 220 | echo. 221 | echo.Testing of doctests in the sources finished, look at the ^ 222 | results in %BUILDDIR%/doctest/output.txt. 223 | goto end 224 | ) 225 | 226 | if "%1" == "xml" ( 227 | %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml 228 | if errorlevel 1 exit /b 1 229 | echo. 230 | echo.Build finished. The XML files are in %BUILDDIR%/xml. 231 | goto end 232 | ) 233 | 234 | if "%1" == "pseudoxml" ( 235 | %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml 236 | if errorlevel 1 exit /b 1 237 | echo. 238 | echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. 239 | goto end 240 | ) 241 | 242 | :end 243 | -------------------------------------------------------------------------------- /doc/requirements.txt: -------------------------------------------------------------------------------- 1 | sphinx 2 | sphinx_rtd_theme 3 | recommonmark -------------------------------------------------------------------------------- /msrest/__init__.py: -------------------------------------------------------------------------------- 1 | # -------------------------------------------------------------------------- 2 | # 3 | # Copyright (c) Microsoft Corporation. All rights reserved. 4 | # 5 | # The MIT License (MIT) 6 | # 7 | # Permission is hereby granted, free of charge, to any person obtaining a copy 8 | # of this software and associated documentation files (the ""Software""), to 9 | # deal in the Software without restriction, including without limitation the 10 | # rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 11 | # sell copies of the Software, and to permit persons to whom the Software is 12 | # furnished to do so, subject to the following conditions: 13 | # 14 | # The above copyright notice and this permission notice shall be included in 15 | # all copies or substantial portions of the Software. 16 | # 17 | # THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 22 | # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 23 | # IN THE SOFTWARE. 24 | # 25 | # -------------------------------------------------------------------------- 26 | 27 | from .version import msrest_version 28 | from .configuration import Configuration 29 | from .service_client import ServiceClient, SDKClient 30 | from .serialization import Serializer, Deserializer 31 | 32 | __all__ = [ 33 | "ServiceClient", 34 | "SDKClient", 35 | "Serializer", 36 | "Deserializer", 37 | "Configuration" 38 | ] 39 | 40 | __version__ = msrest_version 41 | -------------------------------------------------------------------------------- /msrest/async_client.py: -------------------------------------------------------------------------------- 1 | # -------------------------------------------------------------------------- 2 | # 3 | # Copyright (c) Microsoft Corporation. All rights reserved. 4 | # 5 | # The MIT License (MIT) 6 | # 7 | # Permission is hereby granted, free of charge, to any person obtaining a copy 8 | # of this software and associated documentation files (the ""Software""), to 9 | # deal in the Software without restriction, including without limitation the 10 | # rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 11 | # sell copies of the Software, and to permit persons to whom the Software is 12 | # furnished to do so, subject to the following conditions: 13 | # 14 | # The above copyright notice and this permission notice shall be included in 15 | # all copies or substantial portions of the Software. 16 | # 17 | # THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 22 | # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 23 | # IN THE SOFTWARE. 24 | # 25 | # -------------------------------------------------------------------------- 26 | 27 | import functools 28 | import logging 29 | 30 | from typing import Any, Dict, List, Union, TYPE_CHECKING 31 | 32 | from .universal_http import ClientRequest 33 | from .universal_http.async_requests import AsyncRequestsHTTPSender 34 | from .pipeline import Request, AsyncPipeline, AsyncHTTPPolicy, SansIOHTTPPolicy 35 | from .pipeline.async_requests import ( 36 | AsyncPipelineRequestsHTTPSender, 37 | AsyncRequestsCredentialsPolicy 38 | ) 39 | from .pipeline.universal import ( 40 | HTTPLogger, 41 | RawDeserializer, 42 | ) 43 | 44 | from .service_client import _ServiceClientCore 45 | 46 | if TYPE_CHECKING: 47 | from .configuration import Configuration # pylint: disable=unused-import 48 | 49 | _LOGGER = logging.getLogger(__name__) 50 | 51 | 52 | class SDKClientAsync: 53 | """The base class of all generated SDK async client. 54 | """ 55 | 56 | def __init__(self, config: 'Configuration') -> None: 57 | self._client = ServiceClientAsync(config) 58 | 59 | async def __aenter__(self): 60 | await self._client.__aenter__() 61 | return self 62 | 63 | async def __aexit__(self, *exc_details): 64 | await self._client.__aexit__(*exc_details) 65 | 66 | 67 | class ServiceClientAsync(_ServiceClientCore): 68 | 69 | def __init__(self, config: 'Configuration') -> None: 70 | super(ServiceClientAsync, self).__init__(config) 71 | 72 | self.config.pipeline = self._create_default_pipeline() # type: ignore 73 | 74 | def _create_default_pipeline(self): 75 | creds = self.config.credentials 76 | 77 | policies = [ 78 | self.config.user_agent_policy, # UserAgent policy 79 | RawDeserializer(), # Deserialize the raw bytes 80 | self.config.http_logger_policy # HTTP request/response log 81 | ] # type: List[Union[AsyncHTTPPolicy, SansIOHTTPPolicy]] 82 | if creds: 83 | if isinstance(creds, (AsyncHTTPPolicy, SansIOHTTPPolicy)): 84 | policies.insert(1, creds) 85 | else: 86 | # Assume this is the old credentials class, and then requests. Wrap it. 87 | policies.insert(1, AsyncRequestsCredentialsPolicy(creds)) 88 | 89 | return AsyncPipeline( 90 | policies, 91 | AsyncPipelineRequestsHTTPSender( 92 | AsyncRequestsHTTPSender(self.config) # Send HTTP request using requests 93 | ) 94 | ) 95 | 96 | async def __aenter__(self): 97 | await self.config.pipeline.__aenter__() 98 | return self 99 | 100 | async def __aexit__(self, *exc_details): 101 | await self.config.pipeline.__aexit__(*exc_details) 102 | 103 | async def async_send(self, request, **kwargs): 104 | """Prepare and send request object according to configuration. 105 | 106 | :param ClientRequest request: The request object to be sent. 107 | :param dict headers: Any headers to add to the request. 108 | :param content: Any body data to add to the request. 109 | :param config: Any specific config overrides 110 | """ 111 | kwargs.setdefault('stream', True) 112 | # In the current backward compatible implementation, return the HTTP response 113 | # and plug context inside. Could be remove if we modify Autorest, 114 | # but we still need it to be backward compatible 115 | pipeline_response = await self.config.pipeline.run(request, **kwargs) 116 | response = pipeline_response.http_response 117 | response.context = pipeline_response.context 118 | return response 119 | 120 | def stream_download_async(self, response, user_callback): 121 | """Async Generator for streaming request body data. 122 | 123 | :param response: The initial response 124 | :param user_callback: Custom callback for monitoring progress. 125 | """ 126 | block = self.config.connection.data_block_size 127 | return response.stream_download(block, user_callback) 128 | -------------------------------------------------------------------------------- /msrest/async_paging.py: -------------------------------------------------------------------------------- 1 | # -------------------------------------------------------------------------- 2 | # 3 | # Copyright (c) Microsoft Corporation. All rights reserved. 4 | # 5 | # The MIT License (MIT) 6 | # 7 | # Permission is hereby granted, free of charge, to any person obtaining a copy 8 | # of this software and associated documentation files (the ""Software""), to 9 | # deal in the Software without restriction, including without limitation the 10 | # rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 11 | # sell copies of the Software, and to permit persons to whom the Software is 12 | # furnished to do so, subject to the following conditions: 13 | # 14 | # The above copyright notice and this permission notice shall be included in 15 | # all copies or substantial portions of the Software. 16 | # 17 | # THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 22 | # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 23 | # IN THE SOFTWARE. 24 | # 25 | # -------------------------------------------------------------------------- 26 | from collections.abc import AsyncIterator 27 | import logging 28 | 29 | _LOGGER = logging.getLogger(__name__) 30 | 31 | class AsyncPagedMixin(AsyncIterator): 32 | 33 | def __init__(self, *args, **kwargs): 34 | """Bring async to Paging. 35 | 36 | "async_command" is mandatory keyword argument for this mixin to work. 37 | """ 38 | self._async_get_next = kwargs.get("async_command") 39 | if not self._async_get_next: 40 | _LOGGER.debug("Paging async iterator protocol is not available for %s", 41 | self.__class__.__name__) 42 | 43 | async def async_get(self, url): 44 | """Get an arbitrary page. 45 | 46 | This resets the iterator and then fully consumes it to return the 47 | specific page **only**. 48 | 49 | :param str url: URL to arbitrary page results. 50 | """ 51 | self.reset() 52 | self.next_link = url 53 | return await self.async_advance_page() 54 | 55 | async def async_advance_page(self): 56 | if not self._async_get_next: 57 | raise NotImplementedError( 58 | "The class %s does not support async paging at the moment.", 59 | self.__class__.__name__ 60 | ) 61 | if self.next_link is None: 62 | raise StopAsyncIteration("End of paging") 63 | self._current_page_iter_index = 0 64 | self._response = await self._async_get_next(self.next_link) 65 | self._derserializer(self, self._response) 66 | return self.current_page 67 | 68 | async def __anext__(self): 69 | """Iterate through responses.""" 70 | # Storing the list iterator might work out better, but there's no 71 | # guarantee that some code won't replace the list entirely with a copy, 72 | # invalidating an list iterator that might be saved between iterations. 73 | if self.current_page and self._current_page_iter_index < len(self.current_page): 74 | response = self.current_page[self._current_page_iter_index] 75 | self._current_page_iter_index += 1 76 | return response 77 | else: 78 | await self.async_advance_page() 79 | return await self.__anext__() 80 | -------------------------------------------------------------------------------- /msrest/configuration.py: -------------------------------------------------------------------------------- 1 | # -------------------------------------------------------------------------- 2 | # 3 | # Copyright (c) Microsoft Corporation. All rights reserved. 4 | # 5 | # The MIT License (MIT) 6 | # 7 | # Permission is hereby granted, free of charge, to any person obtaining a copy 8 | # of this software and associated documentation files (the ""Software""), to 9 | # deal in the Software without restriction, including without limitation the 10 | # rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 11 | # sell copies of the Software, and to permit persons to whom the Software is 12 | # furnished to do so, subject to the following conditions: 13 | # 14 | # The above copyright notice and this permission notice shall be included in 15 | # all copies or substantial portions of the Software. 16 | # 17 | # THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 22 | # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 23 | # IN THE SOFTWARE. 24 | # 25 | # -------------------------------------------------------------------------- 26 | 27 | 28 | try: 29 | import configparser 30 | from configparser import NoOptionError 31 | except ImportError: 32 | import ConfigParser as configparser # type: ignore 33 | from ConfigParser import NoOptionError # type: ignore 34 | 35 | from typing import TYPE_CHECKING, Optional, Dict, List, Any, Callable, Union # pylint: disable=unused-import 36 | 37 | from .pipeline import Pipeline 38 | from .universal_http.requests import ( 39 | RequestHTTPSenderConfiguration 40 | ) 41 | from .pipeline.universal import ( 42 | UserAgentPolicy, 43 | HTTPLogger, 44 | ) 45 | 46 | if TYPE_CHECKING: 47 | from .pipeline import AsyncPipeline 48 | 49 | class Configuration(RequestHTTPSenderConfiguration): 50 | """Client configuration. 51 | 52 | :param str baseurl: REST API base URL. 53 | :param str filepath: Path to existing config file (optional). 54 | """ 55 | 56 | def __init__(self, base_url, filepath=None): 57 | # type: (str, Optional[str]) -> None 58 | 59 | super(Configuration, self).__init__(filepath) 60 | # Service 61 | self.base_url = base_url 62 | 63 | # User-Agent as a policy 64 | self.user_agent_policy = UserAgentPolicy() 65 | 66 | # HTTP logger policy 67 | self.http_logger_policy = HTTPLogger() 68 | 69 | # The pipeline. We don't know until a ServiceClient use this configuration if it will be sync or async 70 | # We instantiate with a default empty Pipeline for mypy mostly, trying to use a pipeline from a pure 71 | # configuration object doesn't make sense. 72 | self.pipeline = Pipeline() # type: Union[Pipeline, AsyncPipeline] 73 | 74 | # If set to True, ServiceClient will own the sessionn 75 | self.keep_alive = False 76 | 77 | # Potential credentials pre-declared 78 | self.credentials = None 79 | 80 | if filepath: 81 | self.load(filepath) 82 | 83 | @property 84 | def user_agent(self): 85 | # type: () -> str 86 | """The current user agent value.""" 87 | return self.user_agent_policy.user_agent 88 | 89 | def add_user_agent(self, value): 90 | # type: (str) -> None 91 | """Add value to current user agent with a space. 92 | 93 | :param str value: value to add to user agent. 94 | """ 95 | self.user_agent_policy.add_user_agent(value) 96 | 97 | @property 98 | def enable_http_logger(self): 99 | return self.http_logger_policy.enable_http_logger 100 | 101 | @enable_http_logger.setter 102 | def enable_http_logger(self, value): 103 | self.http_logger_policy.enable_http_logger = value 104 | -------------------------------------------------------------------------------- /msrest/http_logger.py: -------------------------------------------------------------------------------- 1 | # -------------------------------------------------------------------------- 2 | # 3 | # Copyright (c) Microsoft Corporation. All rights reserved. 4 | # 5 | # The MIT License (MIT) 6 | # 7 | # Permission is hereby granted, free of charge, to any person obtaining a copy 8 | # of this software and associated documentation files (the ""Software""), to 9 | # deal in the Software without restriction, including without limitation the 10 | # rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 11 | # sell copies of the Software, and to permit persons to whom the Software is 12 | # furnished to do so, subject to the following conditions: 13 | # 14 | # The above copyright notice and this permission notice shall be included in 15 | # all copies or substantial portions of the Software. 16 | # 17 | # THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 22 | # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 23 | # IN THE SOFTWARE. 24 | # 25 | # -------------------------------------------------------------------------- 26 | 27 | import logging 28 | import re 29 | import types 30 | 31 | from typing import Any, Optional, TYPE_CHECKING # pylint: disable=unused-import 32 | 33 | if TYPE_CHECKING: 34 | from .universal_http import ClientRequest, ClientResponse # pylint: disable=unused-import 35 | 36 | _LOGGER = logging.getLogger(__name__) 37 | 38 | 39 | def log_request(_, request, *_args, **_kwargs): 40 | # type: (Any, ClientRequest, str, str) -> None 41 | """Log a client request. 42 | 43 | :param _: Unused in current version (will be None) 44 | :param requests.Request request: The request object. 45 | """ 46 | if not _LOGGER.isEnabledFor(logging.DEBUG): 47 | return 48 | 49 | try: 50 | _LOGGER.debug("Request URL: %r", request.url) 51 | _LOGGER.debug("Request method: %r", request.method) 52 | _LOGGER.debug("Request headers:") 53 | for header, value in request.headers.items(): 54 | if header.lower() == 'authorization': 55 | value = '*****' 56 | _LOGGER.debug(" %r: %r", header, value) 57 | _LOGGER.debug("Request body:") 58 | 59 | # We don't want to log the binary data of a file upload. 60 | if isinstance(request.body, types.GeneratorType): 61 | _LOGGER.debug("File upload") 62 | else: 63 | _LOGGER.debug(str(request.body)) 64 | except Exception as err: # pylint: disable=broad-except 65 | _LOGGER.debug("Failed to log request: %r", err) 66 | 67 | 68 | def log_response(_, _request, response, *_args, **kwargs): 69 | # type: (Any, ClientRequest, ClientResponse, str, Any) -> Optional[ClientResponse] 70 | """Log a server response. 71 | 72 | :param _: Unused in current version (will be None) 73 | :param requests.Request request: The request object. 74 | :param requests.Response response: The response object. 75 | """ 76 | if not _LOGGER.isEnabledFor(logging.DEBUG): 77 | return None 78 | 79 | try: 80 | _LOGGER.debug("Response status: %r", response.status_code) 81 | _LOGGER.debug("Response headers:") 82 | for res_header, value in response.headers.items(): 83 | _LOGGER.debug(" %r: %r", res_header, value) 84 | 85 | # We don't want to log binary data if the response is a file. 86 | _LOGGER.debug("Response content:") 87 | pattern = re.compile(r'attachment; ?filename=["\w.]+', re.IGNORECASE) 88 | header = response.headers.get('content-disposition') 89 | 90 | if header and pattern.match(header): 91 | filename = header.partition('=')[2] 92 | _LOGGER.debug("File attachments: %s", filename) 93 | elif response.headers.get("content-type", "").endswith("octet-stream"): 94 | _LOGGER.debug("Body contains binary data.") 95 | elif response.headers.get("content-type", "").startswith("image"): 96 | _LOGGER.debug("Body contains image data.") 97 | else: 98 | if kwargs.get('stream', False): 99 | _LOGGER.debug("Body is streamable") 100 | else: 101 | _LOGGER.debug(response.text()) 102 | return response 103 | except Exception as err: # pylint: disable=broad-except 104 | _LOGGER.debug("Failed to log response: %s", repr(err)) 105 | return response 106 | -------------------------------------------------------------------------------- /msrest/paging.py: -------------------------------------------------------------------------------- 1 | # -------------------------------------------------------------------------- 2 | # 3 | # Copyright (c) Microsoft Corporation. All rights reserved. 4 | # 5 | # The MIT License (MIT) 6 | # 7 | # Permission is hereby granted, free of charge, to any person obtaining a copy 8 | # of this software and associated documentation files (the ""Software""), to 9 | # deal in the Software without restriction, including without limitation the 10 | # rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 11 | # sell copies of the Software, and to permit persons to whom the Software is 12 | # furnished to do so, subject to the following conditions: 13 | # 14 | # The above copyright notice and this permission notice shall be included in 15 | # all copies or substantial portions of the Software. 16 | # 17 | # THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 22 | # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 23 | # IN THE SOFTWARE. 24 | # 25 | # -------------------------------------------------------------------------- 26 | import sys 27 | try: 28 | from collections.abc import Iterator 29 | xrange = range 30 | except ImportError: 31 | from collections import Iterator 32 | 33 | from typing import Dict, Any, List, Callable, Optional, TYPE_CHECKING # pylint: disable=unused-import 34 | 35 | from .serialization import Deserializer 36 | from .pipeline import ClientRawResponse 37 | 38 | if TYPE_CHECKING: 39 | from .universal_http import ClientResponse # pylint: disable=unused-import 40 | from .serialization import Model # pylint: disable=unused-import 41 | 42 | if sys.version_info >= (3, 5, 2): 43 | # Not executed on old Python, no syntax error 44 | from .async_paging import AsyncPagedMixin # type: ignore 45 | else: 46 | class AsyncPagedMixin(object): # type: ignore 47 | pass 48 | 49 | class Paged(AsyncPagedMixin, Iterator): 50 | """A container for paged REST responses. 51 | 52 | :param ClientResponse response: server response object. 53 | :param callable command: Function to retrieve the next page of items. 54 | :param dict classes: A dictionary of class dependencies for 55 | deserialization. 56 | :param dict raw_headers: A dict of raw headers to add if "raw" is called 57 | """ 58 | _validation = {} # type: Dict[str, Dict[str, Any]] 59 | _attribute_map = {} # type: Dict[str, Dict[str, Any]] 60 | 61 | def __init__(self, command, classes, raw_headers=None, **kwargs): 62 | # type: (Callable[[str], ClientResponse], Dict[str, Model], Dict[str, str], Any) -> None 63 | super(Paged, self).__init__(**kwargs) # type: ignore 64 | # Sets next_link, current_page, and _current_page_iter_index. 65 | self.next_link = "" 66 | self._current_page_iter_index = 0 67 | self.reset() 68 | self._derserializer = Deserializer(classes) 69 | self._get_next = command 70 | self._response = None # type: Optional[ClientResponse] 71 | self._raw_headers = raw_headers 72 | 73 | def __iter__(self): 74 | """Return 'self'.""" 75 | # Since iteration mutates this object, consider it an iterator in-and-of 76 | # itself. 77 | return self 78 | 79 | @classmethod 80 | def _get_subtype_map(cls): 81 | """Required for parity to Model object for deserialization.""" 82 | return {} 83 | 84 | @property 85 | def raw(self): 86 | # type: () -> ClientRawResponse 87 | """Get current page as ClientRawResponse. 88 | 89 | :rtype: ClientRawResponse 90 | """ 91 | raw = ClientRawResponse(self.current_page, self._response) 92 | if self._raw_headers: 93 | raw.add_headers(self._raw_headers) 94 | return raw 95 | 96 | def get(self, url): 97 | # type: (str) -> List[Model] 98 | """Get an arbitrary page. 99 | 100 | This resets the iterator and then fully consumes it to return the 101 | specific page **only**. 102 | 103 | :param str url: URL to arbitrary page results. 104 | """ 105 | self.reset() 106 | self.next_link = url 107 | return self.advance_page() 108 | 109 | def reset(self): 110 | # type: () -> None 111 | """Reset iterator to first page.""" 112 | self.next_link = "" 113 | self.current_page = [] # type: List[Model] 114 | self._current_page_iter_index = 0 115 | 116 | def advance_page(self): 117 | # type: () -> List[Model] 118 | """Force moving the cursor to the next azure call. 119 | 120 | This method is for advanced usage, iterator protocol is preferred. 121 | 122 | :raises: StopIteration if no further page 123 | :return: The current page list 124 | :rtype: list 125 | """ 126 | if self.next_link is None: 127 | raise StopIteration("End of paging") 128 | self._current_page_iter_index = 0 129 | self._response = self._get_next(self.next_link) 130 | self._derserializer(self, self._response) 131 | return self.current_page 132 | 133 | def __next__(self): 134 | """Iterate through responses.""" 135 | # Storing the list iterator might work out better, but there's no 136 | # guarantee that some code won't replace the list entirely with a copy, 137 | # invalidating an list iterator that might be saved between iterations. 138 | if self.current_page and self._current_page_iter_index < len(self.current_page): 139 | response = self.current_page[self._current_page_iter_index] 140 | self._current_page_iter_index += 1 141 | return response 142 | else: 143 | self.advance_page() 144 | return self.__next__() 145 | 146 | next = __next__ # Python 2 compatibility. 147 | -------------------------------------------------------------------------------- /msrest/pipeline/aiohttp.py: -------------------------------------------------------------------------------- 1 | # -------------------------------------------------------------------------- 2 | # 3 | # Copyright (c) Microsoft Corporation. All rights reserved. 4 | # 5 | # The MIT License (MIT) 6 | # 7 | # Permission is hereby granted, free of charge, to any person obtaining a copy 8 | # of this software and associated documentation files (the ""Software""), to 9 | # deal in the Software without restriction, including without limitation the 10 | # rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 11 | # sell copies of the Software, and to permit persons to whom the Software is 12 | # furnished to do so, subject to the following conditions: 13 | # 14 | # The above copyright notice and this permission notice shall be included in 15 | # all copies or substantial portions of the Software. 16 | # 17 | # THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 22 | # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 23 | # IN THE SOFTWARE. 24 | # 25 | # -------------------------------------------------------------------------- 26 | from typing import Any, Optional 27 | 28 | from ..universal_http.aiohttp import AioHTTPSender as _AioHTTPSenderDriver 29 | from . import AsyncHTTPSender, Request, Response 30 | 31 | # Matching requests, because why not? 32 | CONTENT_CHUNK_SIZE = 10 * 1024 33 | 34 | class AioHTTPSender(AsyncHTTPSender): 35 | """AioHttp HTTP sender implementation. 36 | """ 37 | 38 | def __init__(self, driver: Optional[_AioHTTPSenderDriver] = None, *, loop=None) -> None: 39 | self.driver = driver or _AioHTTPSenderDriver(loop=loop) 40 | 41 | async def __aenter__(self): 42 | await self.driver.__aenter__() 43 | 44 | async def __aexit__(self, *exc_details): # pylint: disable=arguments-differ 45 | await self.driver.__aexit__(*exc_details) 46 | 47 | def build_context(self) -> Any: 48 | """Allow the sender to build a context that will be passed 49 | across the pipeline with the request. 50 | 51 | Return type has no constraints. Implementation is not 52 | required and None by default. 53 | """ 54 | return None 55 | 56 | async def send(self, request: Request, **config: Any) -> Response: 57 | """Send the request using this HTTP sender. 58 | """ 59 | return Response( 60 | request, 61 | await self.driver.send(request.http_request) 62 | ) 63 | -------------------------------------------------------------------------------- /msrest/pipeline/async_abc.py: -------------------------------------------------------------------------------- 1 | # -------------------------------------------------------------------------- 2 | # 3 | # Copyright (c) Microsoft Corporation. All rights reserved. 4 | # 5 | # The MIT License (MIT) 6 | # 7 | # Permission is hereby granted, free of charge, to any person obtaining a copy 8 | # of this software and associated documentation files (the ""Software""), to 9 | # deal in the Software without restriction, including without limitation the 10 | # rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 11 | # sell copies of the Software, and to permit persons to whom the Software is 12 | # furnished to do so, subject to the following conditions: 13 | # 14 | # The above copyright notice and this permission notice shall be included in 15 | # all copies or substantial portions of the Software. 16 | # 17 | # THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 22 | # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 23 | # IN THE SOFTWARE. 24 | # 25 | # -------------------------------------------------------------------------- 26 | import abc 27 | 28 | from typing import Any, List, Union, Callable, AsyncIterator, Optional, Generic, TypeVar 29 | 30 | from . import Request, Response, Pipeline, SansIOHTTPPolicy 31 | 32 | 33 | AsyncHTTPResponseType = TypeVar("AsyncHTTPResponseType") 34 | HTTPRequestType = TypeVar("HTTPRequestType") 35 | 36 | try: 37 | from contextlib import AbstractAsyncContextManager # type: ignore 38 | except ImportError: # Python <= 3.7 39 | class AbstractAsyncContextManager(object): # type: ignore 40 | async def __aenter__(self): 41 | """Return `self` upon entering the runtime context.""" 42 | return self 43 | 44 | @abc.abstractmethod 45 | async def __aexit__(self, exc_type, exc_value, traceback): 46 | """Raise any exception triggered within the runtime context.""" 47 | return None 48 | 49 | 50 | 51 | 52 | class AsyncHTTPPolicy(abc.ABC, Generic[HTTPRequestType, AsyncHTTPResponseType]): 53 | """An http policy ABC. 54 | """ 55 | def __init__(self) -> None: 56 | # next will be set once in the pipeline 57 | self.next = None # type: Optional[Union[AsyncHTTPPolicy[HTTPRequestType, AsyncHTTPResponseType], AsyncHTTPSender[HTTPRequestType, AsyncHTTPResponseType]]] 58 | 59 | @abc.abstractmethod 60 | async def send(self, request: Request, **kwargs: Any) -> Response[HTTPRequestType, AsyncHTTPResponseType]: 61 | """Mutate the request. 62 | 63 | Context content is dependent of the HTTPSender. 64 | """ 65 | pass 66 | 67 | 68 | class _SansIOAsyncHTTPPolicyRunner(AsyncHTTPPolicy[HTTPRequestType, AsyncHTTPResponseType]): 69 | """Async implementation of the SansIO policy. 70 | """ 71 | 72 | def __init__(self, policy: SansIOHTTPPolicy) -> None: 73 | super(_SansIOAsyncHTTPPolicyRunner, self).__init__() 74 | self._policy = policy 75 | 76 | async def send(self, request: Request, **kwargs: Any) -> Response[HTTPRequestType, AsyncHTTPResponseType]: 77 | self._policy.on_request(request, **kwargs) 78 | try: 79 | response = await self.next.send(request, **kwargs) # type: ignore 80 | except Exception: 81 | if not self._policy.on_exception(request, **kwargs): 82 | raise 83 | else: 84 | self._policy.on_response(request, response, **kwargs) 85 | return response 86 | 87 | 88 | class AsyncHTTPSender(AbstractAsyncContextManager, abc.ABC, Generic[HTTPRequestType, AsyncHTTPResponseType]): 89 | """An http sender ABC. 90 | """ 91 | 92 | @abc.abstractmethod 93 | async def send(self, request: Request[HTTPRequestType], **config: Any) -> Response[HTTPRequestType, AsyncHTTPResponseType]: 94 | """Send the request using this HTTP sender. 95 | """ 96 | pass 97 | 98 | def build_context(self) -> Any: 99 | """Allow the sender to build a context that will be passed 100 | across the pipeline with the request. 101 | 102 | Return type has no constraints. Implementation is not 103 | required and None by default. 104 | """ 105 | return None 106 | 107 | def __enter__(self): 108 | raise TypeError("Use async with instead") 109 | 110 | def __exit__(self, exc_type, exc_val, exc_tb): 111 | # __exit__ should exist in pair with __enter__ but never executed 112 | pass # pragma: no cover 113 | 114 | 115 | class AsyncPipeline(AbstractAsyncContextManager, Generic[HTTPRequestType, AsyncHTTPResponseType]): 116 | """A pipeline implementation. 117 | 118 | This is implemented as a context manager, that will activate the context 119 | of the HTTP sender. 120 | """ 121 | 122 | def __init__(self, policies: List[Union[AsyncHTTPPolicy, SansIOHTTPPolicy]] = None, sender: Optional[AsyncHTTPSender[HTTPRequestType, AsyncHTTPResponseType]] = None) -> None: 123 | self._impl_policies = [] # type: List[AsyncHTTPPolicy[HTTPRequestType, AsyncHTTPResponseType]] 124 | if sender: 125 | self._sender = sender 126 | else: 127 | # Import default only if nothing is provided 128 | from .aiohttp import AioHTTPSender 129 | self._sender = AioHTTPSender() 130 | 131 | for policy in (policies or []): 132 | if isinstance(policy, SansIOHTTPPolicy): 133 | self._impl_policies.append(_SansIOAsyncHTTPPolicyRunner(policy)) 134 | else: 135 | self._impl_policies.append(policy) 136 | for index in range(len(self._impl_policies)-1): 137 | self._impl_policies[index].next = self._impl_policies[index+1] 138 | if self._impl_policies: 139 | self._impl_policies[-1].next = self._sender 140 | 141 | def __enter__(self): 142 | raise TypeError("Use 'async with' instead") 143 | 144 | def __exit__(self, exc_type, exc_val, exc_tb): 145 | # __exit__ should exist in pair with __enter__ but never executed 146 | pass # pragma: no cover 147 | 148 | async def __aenter__(self) -> 'AsyncPipeline': 149 | await self._sender.__aenter__() 150 | return self 151 | 152 | async def __aexit__(self, *exc_details): # pylint: disable=arguments-differ 153 | await self._sender.__aexit__(*exc_details) 154 | 155 | async def run(self, request: Request, **kwargs: Any) -> Response[HTTPRequestType, AsyncHTTPResponseType]: 156 | context = self._sender.build_context() 157 | pipeline_request = Request(request, context) 158 | first_node = self._impl_policies[0] if self._impl_policies else self._sender 159 | return await first_node.send(pipeline_request, **kwargs) # type: ignore 160 | 161 | __all__ = [ 162 | 'AsyncHTTPPolicy', 163 | 'AsyncHTTPSender', 164 | 'AsyncPipeline', 165 | ] -------------------------------------------------------------------------------- /msrest/pipeline/async_requests.py: -------------------------------------------------------------------------------- 1 | # -------------------------------------------------------------------------- 2 | # 3 | # Copyright (c) Microsoft Corporation. All rights reserved. 4 | # 5 | # The MIT License (MIT) 6 | # 7 | # Permission is hereby granted, free of charge, to any person obtaining a copy 8 | # of this software and associated documentation files (the ""Software""), to 9 | # deal in the Software without restriction, including without limitation the 10 | # rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 11 | # sell copies of the Software, and to permit persons to whom the Software is 12 | # furnished to do so, subject to the following conditions: 13 | # 14 | # The above copyright notice and this permission notice shall be included in 15 | # all copies or substantial portions of the Software. 16 | # 17 | # THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 22 | # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 23 | # IN THE SOFTWARE. 24 | # 25 | # -------------------------------------------------------------------------- 26 | import asyncio 27 | from collections.abc import AsyncIterator 28 | import functools 29 | import logging 30 | from typing import Any, Callable, Optional, AsyncIterator as AsyncIteratorType 31 | 32 | from oauthlib import oauth2 33 | import requests 34 | from requests.models import CONTENT_CHUNK_SIZE 35 | 36 | from ..exceptions import ( 37 | TokenExpiredError, 38 | ClientRequestError, 39 | raise_with_traceback 40 | ) 41 | from ..universal_http.async_requests import AsyncBasicRequestsHTTPSender 42 | from . import AsyncHTTPSender, AsyncHTTPPolicy, Response, Request 43 | from .requests import RequestsContext 44 | 45 | 46 | _LOGGER = logging.getLogger(__name__) 47 | 48 | 49 | class AsyncPipelineRequestsHTTPSender(AsyncHTTPSender): 50 | """Implements a basic Pipeline, that supports universal HTTP lib "requests" driver. 51 | """ 52 | 53 | def __init__(self, universal_http_requests_driver: Optional[AsyncBasicRequestsHTTPSender]=None) -> None: 54 | self.driver = universal_http_requests_driver or AsyncBasicRequestsHTTPSender() 55 | 56 | async def __aenter__(self) -> 'AsyncPipelineRequestsHTTPSender': 57 | await self.driver.__aenter__() 58 | return self 59 | 60 | async def __aexit__(self, *exc_details): # pylint: disable=arguments-differ 61 | await self.driver.__aexit__(*exc_details) 62 | 63 | async def close(self): 64 | await self.__aexit__() 65 | 66 | def build_context(self): 67 | # type: () -> RequestsContext 68 | return RequestsContext( 69 | session=self.driver.session, 70 | ) 71 | 72 | async def send(self, request: Request, **kwargs) -> Response: 73 | """Send request object according to configuration. 74 | 75 | :param Request request: The request object to be sent. 76 | """ 77 | if request.context is None: # Should not happen, but make mypy happy and does not hurt 78 | request.context = self.build_context() 79 | 80 | if request.context.session is not self.driver.session: 81 | kwargs['session'] = request.context.session 82 | 83 | return Response( 84 | request, 85 | await self.driver.send(request.http_request, **kwargs) 86 | ) 87 | 88 | 89 | class AsyncRequestsCredentialsPolicy(AsyncHTTPPolicy): 90 | """Implementation of request-oauthlib except and retry logic. 91 | """ 92 | def __init__(self, credentials): 93 | super(AsyncRequestsCredentialsPolicy, self).__init__() 94 | self._creds = credentials 95 | 96 | async def send(self, request, **kwargs): 97 | session = request.context.session 98 | try: 99 | self._creds.signed_session(session) 100 | except TypeError: # Credentials does not support session injection 101 | _LOGGER.warning("Your credentials class does not support session injection. Performance will not be at the maximum.") 102 | request.context.session = session = self._creds.signed_session() 103 | 104 | try: 105 | try: 106 | return await self.next.send(request, **kwargs) 107 | except (oauth2.rfc6749.errors.InvalidGrantError, 108 | oauth2.rfc6749.errors.TokenExpiredError) as err: 109 | error = "Token expired or is invalid. Attempting to refresh." 110 | _LOGGER.warning(error) 111 | 112 | try: 113 | try: 114 | self._creds.refresh_session(session) 115 | except TypeError: # Credentials does not support session injection 116 | _LOGGER.warning("Your credentials class does not support session injection. Performance will not be at the maximum.") 117 | request.context.session = session = self._creds.refresh_session() 118 | 119 | return await self.next.send(request, **kwargs) 120 | except (oauth2.rfc6749.errors.InvalidGrantError, 121 | oauth2.rfc6749.errors.TokenExpiredError) as err: 122 | msg = "Token expired or is invalid." 123 | raise_with_traceback(TokenExpiredError, msg, err) 124 | 125 | except (requests.RequestException, 126 | oauth2.rfc6749.errors.OAuth2Error) as err: 127 | msg = "Error occurred in request." 128 | raise_with_traceback(ClientRequestError, msg, err) 129 | 130 | -------------------------------------------------------------------------------- /msrest/pipeline/requests.py: -------------------------------------------------------------------------------- 1 | # -------------------------------------------------------------------------- 2 | # 3 | # Copyright (c) Microsoft Corporation. All rights reserved. 4 | # 5 | # The MIT License (MIT) 6 | # 7 | # Permission is hereby granted, free of charge, to any person obtaining a copy 8 | # of this software and associated documentation files (the ""Software""), to 9 | # deal in the Software without restriction, including without limitation the 10 | # rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 11 | # sell copies of the Software, and to permit persons to whom the Software is 12 | # furnished to do so, subject to the following conditions: 13 | # 14 | # The above copyright notice and this permission notice shall be included in 15 | # all copies or substantial portions of the Software. 16 | # 17 | # THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 22 | # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 23 | # IN THE SOFTWARE. 24 | # 25 | # -------------------------------------------------------------------------- 26 | """ 27 | This module is the requests implementation of Pipeline ABC 28 | """ 29 | from __future__ import absolute_import # we have a "requests" module that conflicts with "requests" on Py2.7 30 | import contextlib 31 | import logging 32 | import threading 33 | from typing import TYPE_CHECKING, List, Callable, Iterator, Any, Union, Dict, Optional # pylint: disable=unused-import 34 | import warnings 35 | 36 | from oauthlib import oauth2 37 | import requests 38 | from requests.models import CONTENT_CHUNK_SIZE 39 | 40 | from urllib3 import Retry # Needs requests 2.16 at least to be safe 41 | 42 | from ..exceptions import ( 43 | TokenExpiredError, 44 | ClientRequestError, 45 | raise_with_traceback 46 | ) 47 | from ..universal_http import ClientRequest 48 | from ..universal_http.requests import BasicRequestsHTTPSender 49 | from . import HTTPSender, HTTPPolicy, Response, Request 50 | 51 | 52 | _LOGGER = logging.getLogger(__name__) 53 | 54 | 55 | class RequestsCredentialsPolicy(HTTPPolicy): 56 | """Implementation of request-oauthlib except and retry logic. 57 | """ 58 | def __init__(self, credentials): 59 | super(RequestsCredentialsPolicy, self).__init__() 60 | self._creds = credentials 61 | 62 | def send(self, request, **kwargs): 63 | session = request.context.session 64 | try: 65 | self._creds.signed_session(session) 66 | except TypeError: # Credentials does not support session injection 67 | _LOGGER.warning("Your credentials class does not support session injection. Performance will not be at the maximum.") 68 | request.context.session = session = self._creds.signed_session() 69 | 70 | try: 71 | try: 72 | return self.next.send(request, **kwargs) 73 | except (oauth2.rfc6749.errors.InvalidGrantError, 74 | oauth2.rfc6749.errors.TokenExpiredError) as err: 75 | error = "Token expired or is invalid. Attempting to refresh." 76 | _LOGGER.warning(error) 77 | 78 | try: 79 | try: 80 | self._creds.refresh_session(session) 81 | except TypeError: # Credentials does not support session injection 82 | _LOGGER.warning("Your credentials class does not support session injection. Performance will not be at the maximum.") 83 | request.context.session = session = self._creds.refresh_session() 84 | 85 | return self.next.send(request, **kwargs) 86 | except (oauth2.rfc6749.errors.InvalidGrantError, 87 | oauth2.rfc6749.errors.TokenExpiredError) as err: 88 | msg = "Token expired or is invalid." 89 | raise_with_traceback(TokenExpiredError, msg, err) 90 | 91 | except (requests.RequestException, 92 | oauth2.rfc6749.errors.OAuth2Error) as err: 93 | msg = "Error occurred in request." 94 | raise_with_traceback(ClientRequestError, msg, err) 95 | 96 | class RequestsPatchSession(HTTPPolicy): 97 | """Implements request level configuration 98 | that are actually to be done at the session level. 99 | 100 | This is highly deprecated, and is totally legacy. 101 | The pipeline structure allows way better design for this. 102 | """ 103 | _protocols = ['http://', 'https://'] 104 | 105 | def send(self, request, **kwargs): 106 | """Patch the current session with Request level operation config. 107 | 108 | This is deprecated, we shouldn't patch the session with 109 | arguments at the Request, and "config" should be used. 110 | """ 111 | session = request.context.session 112 | 113 | old_max_redirects = None 114 | if 'max_redirects' in kwargs: 115 | warnings.warn("max_redirects in operation kwargs is deprecated, use config.redirect_policy instead", 116 | DeprecationWarning) 117 | old_max_redirects = session.max_redirects 118 | session.max_redirects = int(kwargs['max_redirects']) 119 | 120 | old_trust_env = None 121 | if 'use_env_proxies' in kwargs: 122 | warnings.warn("use_env_proxies in operation kwargs is deprecated, use config.proxies instead", 123 | DeprecationWarning) 124 | old_trust_env = session.trust_env 125 | session.trust_env = bool(kwargs['use_env_proxies']) 126 | 127 | old_retries = {} 128 | if 'retries' in kwargs: 129 | warnings.warn("retries in operation kwargs is deprecated, use config.retry_policy instead", 130 | DeprecationWarning) 131 | max_retries = kwargs['retries'] 132 | for protocol in self._protocols: 133 | old_retries[protocol] = session.adapters[protocol].max_retries 134 | session.adapters[protocol].max_retries = max_retries 135 | 136 | try: 137 | return self.next.send(request, **kwargs) 138 | finally: 139 | if old_max_redirects: 140 | session.max_redirects = old_max_redirects 141 | 142 | if old_trust_env: 143 | session.trust_env = old_trust_env 144 | 145 | if old_retries: 146 | for protocol in self._protocols: 147 | session.adapters[protocol].max_retries = old_retries[protocol] 148 | 149 | class RequestsContext(object): 150 | def __init__(self, session): 151 | self.session = session 152 | 153 | 154 | class PipelineRequestsHTTPSender(HTTPSender): 155 | """Implements a basic Pipeline, that supports universal HTTP lib "requests" driver. 156 | """ 157 | 158 | def __init__(self, universal_http_requests_driver=None): 159 | # type: (Optional[BasicRequestsHTTPSender]) -> None 160 | self.driver = universal_http_requests_driver or BasicRequestsHTTPSender() 161 | 162 | def __enter__(self): 163 | # type: () -> PipelineRequestsHTTPSender 164 | self.driver.__enter__() 165 | return self 166 | 167 | def __exit__(self, *exc_details): # pylint: disable=arguments-differ 168 | self.driver.__exit__(*exc_details) 169 | 170 | def close(self): 171 | self.__exit__() 172 | 173 | def build_context(self): 174 | # type: () -> RequestsContext 175 | return RequestsContext( 176 | session=self.driver.session, 177 | ) 178 | 179 | def send(self, request, **kwargs): 180 | # type: (Request[ClientRequest], Any) -> Response 181 | """Send request object according to configuration. 182 | 183 | :param Request request: The request object to be sent. 184 | """ 185 | if request.context is None: # Should not happen, but make mypy happy and does not hurt 186 | request.context = self.build_context() 187 | 188 | if request.context.session is not self.driver.session: 189 | kwargs['session'] = request.context.session 190 | 191 | return Response( 192 | request, 193 | self.driver.send(request.http_request, **kwargs) 194 | ) 195 | -------------------------------------------------------------------------------- /msrest/polling/__init__.py: -------------------------------------------------------------------------------- 1 | # -------------------------------------------------------------------------- 2 | # 3 | # Copyright (c) Microsoft Corporation. All rights reserved. 4 | # 5 | # The MIT License (MIT) 6 | # 7 | # Permission is hereby granted, free of charge, to any person obtaining a copy 8 | # of this software and associated documentation files (the ""Software""), to 9 | # deal in the Software without restriction, including without limitation the 10 | # rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 11 | # sell copies of the Software, and to permit persons to whom the Software is 12 | # furnished to do so, subject to the following conditions: 13 | # 14 | # The above copyright notice and this permission notice shall be included in 15 | # all copies or substantial portions of the Software. 16 | # 17 | # THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 22 | # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 23 | # IN THE SOFTWARE. 24 | # 25 | # -------------------------------------------------------------------------- 26 | import sys 27 | 28 | from .poller import LROPoller, NoPolling, PollingMethod 29 | __all__ = ['LROPoller', 'NoPolling', 'PollingMethod'] 30 | 31 | if sys.version_info >= (3, 5, 2): 32 | # Not executed on old Python, no syntax error 33 | from .async_poller import AsyncNoPolling, AsyncPollingMethod, async_poller 34 | __all__ += ['AsyncNoPolling', 'AsyncPollingMethod', 'async_poller'] 35 | -------------------------------------------------------------------------------- /msrest/polling/async_poller.py: -------------------------------------------------------------------------------- 1 | # -------------------------------------------------------------------------- 2 | # 3 | # Copyright (c) Microsoft Corporation. All rights reserved. 4 | # 5 | # The MIT License (MIT) 6 | # 7 | # Permission is hereby granted, free of charge, to any person obtaining a copy 8 | # of this software and associated documentation files (the ""Software""), to 9 | # deal in the Software without restriction, including without limitation the 10 | # rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 11 | # sell copies of the Software, and to permit persons to whom the Software is 12 | # furnished to do so, subject to the following conditions: 13 | # 14 | # The above copyright notice and this permission notice shall be included in 15 | # all copies or substantial portions of the Software. 16 | # 17 | # THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 22 | # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 23 | # IN THE SOFTWARE. 24 | # 25 | # -------------------------------------------------------------------------- 26 | from .poller import NoPolling as _NoPolling 27 | 28 | from ..serialization import Model 29 | from ..async_client import ServiceClientAsync 30 | from ..pipeline import ClientRawResponse 31 | 32 | class AsyncPollingMethod(object): 33 | """ABC class for polling method. 34 | """ 35 | def initialize(self, client, initial_response, deserialization_callback): 36 | raise NotImplementedError("This method needs to be implemented") 37 | 38 | async def run(self): 39 | raise NotImplementedError("This method needs to be implemented") 40 | 41 | def status(self): 42 | raise NotImplementedError("This method needs to be implemented") 43 | 44 | def finished(self): 45 | raise NotImplementedError("This method needs to be implemented") 46 | 47 | def resource(self): 48 | raise NotImplementedError("This method needs to be implemented") 49 | 50 | 51 | class AsyncNoPolling(_NoPolling): 52 | """An empty async poller that returns the deserialized initial response. 53 | """ 54 | async def run(self): 55 | """Empty run, no polling. 56 | 57 | Just override initial run to add "async" 58 | """ 59 | pass 60 | 61 | 62 | async def async_poller(client, initial_response, deserialization_callback, polling_method): 63 | """Async Poller for long running operations. 64 | 65 | :param client: A msrest service client. Can be a SDK client and it will be casted to a ServiceClient. 66 | :type client: msrest.service_client.ServiceClient 67 | :param initial_response: The initial call response 68 | :type initial_response: msrest.universal_http.ClientResponse or msrest.pipeline.ClientRawResponse 69 | :param deserialization_callback: A callback that takes a Response and return a deserialized object. If a subclass of Model is given, this passes "deserialize" as callback. 70 | :type deserialization_callback: callable or msrest.serialization.Model 71 | :param polling_method: The polling strategy to adopt 72 | :type polling_method: msrest.polling.PollingMethod 73 | """ 74 | 75 | try: 76 | client = client if isinstance(client, ServiceClientAsync) else client._client 77 | except AttributeError: 78 | raise ValueError("Poller client parameter must be a low-level msrest Service Client or a SDK client.") 79 | response = initial_response.response if isinstance(initial_response, ClientRawResponse) else initial_response 80 | 81 | if isinstance(deserialization_callback, type) and issubclass(deserialization_callback, Model): 82 | deserialization_callback = deserialization_callback.deserialize 83 | 84 | # Might raise a CloudError 85 | polling_method.initialize(client, response, deserialization_callback) 86 | 87 | await polling_method.run() 88 | return polling_method.resource() 89 | -------------------------------------------------------------------------------- /msrest/py.typed: -------------------------------------------------------------------------------- 1 | # Marker file for PEP 561. -------------------------------------------------------------------------------- /msrest/universal_http/aiohttp.py: -------------------------------------------------------------------------------- 1 | # -------------------------------------------------------------------------- 2 | # 3 | # Copyright (c) Microsoft Corporation. All rights reserved. 4 | # 5 | # The MIT License (MIT) 6 | # 7 | # Permission is hereby granted, free of charge, to any person obtaining a copy 8 | # of this software and associated documentation files (the ""Software""), to 9 | # deal in the Software without restriction, including without limitation the 10 | # rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 11 | # sell copies of the Software, and to permit persons to whom the Software is 12 | # furnished to do so, subject to the following conditions: 13 | # 14 | # The above copyright notice and this permission notice shall be included in 15 | # all copies or substantial portions of the Software. 16 | # 17 | # THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 22 | # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 23 | # IN THE SOFTWARE. 24 | # 25 | # -------------------------------------------------------------------------- 26 | from typing import Any, Callable, AsyncIterator, Optional 27 | 28 | import aiohttp 29 | from multidict import CIMultiDict 30 | 31 | from . import AsyncHTTPSender, ClientRequest, AsyncClientResponse 32 | 33 | # Matching requests, because why not? 34 | CONTENT_CHUNK_SIZE = 10 * 1024 35 | 36 | 37 | class AioHTTPSender(AsyncHTTPSender): 38 | """AioHttp HTTP sender implementation. 39 | """ 40 | 41 | def __init__(self, *, loop=None): 42 | self._session = aiohttp.ClientSession(loop=loop) 43 | 44 | async def __aenter__(self): 45 | await self._session.__aenter__() 46 | return self 47 | 48 | async def __aexit__(self, *exc_details): # pylint: disable=arguments-differ 49 | await self._session.__aexit__(*exc_details) 50 | 51 | async def send(self, request: ClientRequest, **config: Any) -> AsyncClientResponse: 52 | """Send the request using this HTTP sender. 53 | 54 | Will pre-load the body into memory to be available with a sync method. 55 | pass stream=True to avoid this behavior. 56 | """ 57 | result = await self._session.request( 58 | request.method, 59 | request.url, 60 | **config 61 | ) 62 | response = AioHttpClientResponse(request, result) 63 | if not config.get("stream", False): 64 | await response.load_body() 65 | return response 66 | 67 | 68 | class AioHttpClientResponse(AsyncClientResponse): 69 | def __init__(self, request: ClientRequest, aiohttp_response: aiohttp.ClientResponse) -> None: 70 | super(AioHttpClientResponse, self).__init__(request, aiohttp_response) 71 | # https://aiohttp.readthedocs.io/en/stable/client_reference.html#aiohttp.ClientResponse 72 | self.status_code = aiohttp_response.status 73 | self.headers = CIMultiDict(aiohttp_response.headers) 74 | self.reason = aiohttp_response.reason 75 | self._body = None 76 | 77 | def body(self) -> bytes: 78 | """Return the whole body as bytes in memory. 79 | """ 80 | if not self._body: 81 | raise ValueError("Body is not available. Call async method load_body, or do your call with stream=False.") 82 | return self._body 83 | 84 | async def load_body(self) -> None: 85 | """Load in memory the body, so it could be accessible from sync methods.""" 86 | self._body = await self.internal_response.read() 87 | 88 | def raise_for_status(self): 89 | self.internal_response.raise_for_status() 90 | 91 | def stream_download(self, chunk_size: Optional[int] = None, callback: Optional[Callable] = None) -> AsyncIterator[bytes]: 92 | """Generator for streaming request body data. 93 | """ 94 | chunk_size = chunk_size or CONTENT_CHUNK_SIZE 95 | async def async_gen(resp): 96 | while True: 97 | chunk = await resp.content.read(chunk_size) 98 | if not chunk: 99 | break 100 | callback(chunk, resp) 101 | return async_gen(self.internal_response) 102 | -------------------------------------------------------------------------------- /msrest/universal_http/async_abc.py: -------------------------------------------------------------------------------- 1 | # -------------------------------------------------------------------------- 2 | # 3 | # Copyright (c) Microsoft Corporation. All rights reserved. 4 | # 5 | # The MIT License (MIT) 6 | # 7 | # Permission is hereby granted, free of charge, to any person obtaining a copy 8 | # of this software and associated documentation files (the ""Software""), to 9 | # deal in the Software without restriction, including without limitation the 10 | # rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 11 | # sell copies of the Software, and to permit persons to whom the Software is 12 | # furnished to do so, subject to the following conditions: 13 | # 14 | # The above copyright notice and this permission notice shall be included in 15 | # all copies or substantial portions of the Software. 16 | # 17 | # THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 22 | # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 23 | # IN THE SOFTWARE. 24 | # 25 | # -------------------------------------------------------------------------- 26 | import abc 27 | 28 | from typing import Any, List, Union, Callable, AsyncIterator, Optional 29 | 30 | try: 31 | from contextlib import AbstractAsyncContextManager # type: ignore 32 | except ImportError: # Python <= 3.7 33 | class AbstractAsyncContextManager(object): # type: ignore 34 | async def __aenter__(self): 35 | """Return `self` upon entering the runtime context.""" 36 | return self 37 | 38 | @abc.abstractmethod 39 | async def __aexit__(self, exc_type, exc_value, traceback): 40 | """Raise any exception triggered within the runtime context.""" 41 | return None 42 | 43 | from . import ClientRequest, HTTPClientResponse 44 | 45 | 46 | class AsyncClientResponse(HTTPClientResponse): 47 | 48 | def stream_download(self, chunk_size: Optional[int] = None, callback: Optional[Callable] = None) -> AsyncIterator[bytes]: 49 | """Generator for streaming request body data. 50 | 51 | Should be implemented by sub-classes if streaming download 52 | is supported. 53 | 54 | :param callback: Custom callback for monitoring progress. 55 | :param int chunk_size: 56 | """ 57 | pass 58 | 59 | 60 | class AsyncHTTPSender(AbstractAsyncContextManager, abc.ABC): 61 | """An http sender ABC. 62 | """ 63 | 64 | @abc.abstractmethod 65 | async def send(self, request: ClientRequest, **config: Any) -> AsyncClientResponse: 66 | """Send the request using this HTTP sender. 67 | """ 68 | pass 69 | 70 | def build_context(self) -> Any: 71 | """Allow the sender to build a context that will be passed 72 | across the pipeline with the request. 73 | 74 | Return type has no constraints. Implementation is not 75 | required and None by default. 76 | """ 77 | return None 78 | 79 | def __enter__(self): 80 | raise TypeError("Use 'async with' instead") 81 | 82 | def __exit__(self, exc_type, exc_val, exc_tb): 83 | # __exit__ should exist in pair with __enter__ but never executed 84 | pass # pragma: no cover 85 | 86 | 87 | __all__ = [ 88 | 'AsyncHTTPSender', 89 | 'AsyncClientResponse' 90 | ] -------------------------------------------------------------------------------- /msrest/version.py: -------------------------------------------------------------------------------- 1 | # -------------------------------------------------------------------------- 2 | # 3 | # Copyright (c) Microsoft Corporation. All rights reserved. 4 | # 5 | # The MIT License (MIT) 6 | # 7 | # Permission is hereby granted, free of charge, to any person obtaining a copy 8 | # of this software and associated documentation files (the ""Software""), to 9 | # deal in the Software without restriction, including without limitation the 10 | # rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 11 | # sell copies of the Software, and to permit persons to whom the Software is 12 | # furnished to do so, subject to the following conditions: 13 | # 14 | # The above copyright notice and this permission notice shall be included in 15 | # all copies or substantial portions of the Software. 16 | # 17 | # THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 22 | # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 23 | # IN THE SOFTWARE. 24 | # 25 | # -------------------------------------------------------------------------- 26 | 27 | #: version of this package. Use msrest.__version__ instead 28 | msrest_version = "0.7.1" 29 | -------------------------------------------------------------------------------- /pylintrc: -------------------------------------------------------------------------------- 1 | [MASTER] 2 | ignore-patterns=test_* 3 | reports=no 4 | 5 | [MESSAGES CONTROL] 6 | # For all codes, run 'pylint --list-msgs' or go to 'https://pylint.readthedocs.io/en/latest/reference_guide/features.html' 7 | # locally-disabled: Warning locally suppressed using disable-msg 8 | # cyclic-import: because of https://github.com/PyCQA/pylint/issues/850 9 | # too-many-arguments: Due to the nature of the CLI many commands have large arguments set which reflect in large arguments set in corresponding methods. 10 | disable=missing-docstring,locally-disabled,fixme,cyclic-import,too-many-arguments,invalid-name,duplicate-code 11 | 12 | [FORMAT] 13 | max-line-length=120 14 | 15 | [VARIABLES] 16 | # Tells whether we should check for unused import in __init__ files. 17 | init-import=yes 18 | 19 | [DESIGN] 20 | # Maximum number of locals for function / method body 21 | max-locals=25 22 | # Maximum number of branch for function / method body 23 | max-branches=20 24 | 25 | [SIMILARITIES] 26 | min-similarity-lines=10 27 | 28 | [BASIC] 29 | # Naming hints based on PEP 8 (https://www.python.org/dev/peps/pep-0008/#naming-conventions). 30 | # Consider these guidelines and not hard rules. Read PEP 8 for more details. 31 | 32 | # The invalid-name checker must be **enabled** for these hints to be used. 33 | include-naming-hint=yes 34 | 35 | module-name-hint=lowercase (keep short; underscores are discouraged) 36 | const-name-hint=UPPER_CASE_WITH_UNDERSCORES 37 | class-name-hint=CapitalizedWords 38 | class-attribute-name-hint=lower_case_with_underscores 39 | attr-name-hint=lower_case_with_underscores 40 | method-name-hint=lower_case_with_underscores 41 | function-name-hint=lower_case_with_underscores 42 | argument-name-hint=lower_case_with_underscores 43 | variable-name-hint=lower_case_with_underscores 44 | inlinevar-name-hint=lower_case_with_underscores (short is OK) -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [mypy] 2 | ignore_missing_imports = True 3 | 4 | [tool:pytest] 5 | addopts = --durations=10 -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # -------------------------------------------------------------------------- 2 | # 3 | # Copyright (c) Microsoft Corporation. All rights reserved. 4 | # 5 | # The MIT License (MIT) 6 | # 7 | # Permission is hereby granted, free of charge, to any person obtaining a copy 8 | # of this software and associated documentation files (the ""Software""), to 9 | # deal in the Software without restriction, including without limitation the 10 | # rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 11 | # sell copies of the Software, and to permit persons to whom the Software is 12 | # furnished to do so, subject to the following conditions: 13 | # 14 | # The above copyright notice and this permission notice shall be included in 15 | # all copies or substantial portions of the Software. 16 | # 17 | # THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 22 | # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 23 | # IN THE SOFTWARE. 24 | # 25 | # -------------------------------------------------------------------------- 26 | 27 | from setuptools import setup, find_packages 28 | 29 | setup( 30 | name='msrest', 31 | version='0.7.1', 32 | author='Microsoft Corporation', 33 | packages=find_packages(exclude=["tests", "tests.*"]), 34 | url="https://github.com/Azure/msrest-for-python", 35 | license='MIT License', 36 | description='AutoRest swagger generator Python client runtime.', 37 | long_description=open('README.rst').read(), 38 | classifiers=[ 39 | 'Development Status :: 4 - Beta', 40 | 'Programming Language :: Python', 41 | 'Programming Language :: Python :: 3 :: Only', 42 | 'Programming Language :: Python :: 3', 43 | 'Programming Language :: Python :: 3.6', 44 | 'Programming Language :: Python :: 3.7', 45 | 'Programming Language :: Python :: 3.8', 46 | 'Programming Language :: Python :: 3.9', 47 | 'Programming Language :: Python :: 3.10', 48 | 'Programming Language :: Python :: 3.11', 49 | 'License :: OSI Approved :: MIT License', 50 | 'Topic :: Software Development'], 51 | python_requires=">=3.6", 52 | install_requires=[ 53 | "requests~=2.16", 54 | "requests_oauthlib>=0.5.0", 55 | "isodate>=0.6.0", 56 | "certifi>=2017.4.17", 57 | "azure-core>=1.24.0", 58 | ], 59 | include_package_data=True, 60 | package_data={ 61 | 'pytyped': ['py.typed'], 62 | }, 63 | extras_require={ 64 | "async:python_version>='3.5'": [ 65 | 'aiohttp>=3.0', 66 | 'aiodns' 67 | ], 68 | } 69 | ) 70 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- 1 | # -------------------------------------------------------------------------- 2 | # 3 | # Copyright (c) Microsoft Corporation. All rights reserved. 4 | # 5 | # The MIT License (MIT) 6 | # 7 | # Permission is hereby granted, free of charge, to any person obtaining a copy 8 | # of this software and associated documentation files (the ""Software""), to 9 | # deal in the Software without restriction, including without limitation the 10 | # rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 11 | # sell copies of the Software, and to permit persons to whom the Software is 12 | # furnished to do so, subject to the following conditions: 13 | # 14 | # The above copyright notice and this permission notice shall be included in 15 | # all copies or substantial portions of the Software. 16 | # 17 | # THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 22 | # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 23 | # IN THE SOFTWARE. 24 | # 25 | # -------------------------------------------------------------------------- 26 | 27 | import os 28 | from unittest import TestLoader, TextTestRunner 29 | import logging 30 | #logging.basicConfig(level=logging.DEBUG, filename="d:/log.txt") 31 | 32 | if __name__ == '__main__': 33 | 34 | runner = TextTestRunner(verbosity=2) 35 | test_dir = os.path.dirname(__file__) 36 | 37 | test_loader = TestLoader() 38 | suite = test_loader.discover(test_dir, pattern="unittest_*.py") 39 | runner.run(suite) 40 | -------------------------------------------------------------------------------- /tests/asynctests/test_async_client.py: -------------------------------------------------------------------------------- 1 | #-------------------------------------------------------------------------- 2 | # 3 | # Copyright (c) Microsoft Corporation. All rights reserved. 4 | # 5 | # The MIT License (MIT) 6 | # 7 | # Permission is hereby granted, free of charge, to any person obtaining a copy 8 | # of this software and associated documentation files (the ""Software""), to deal 9 | # in the Software without restriction, including without limitation the rights 10 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | # copies of the Software, and to permit persons to whom the Software is 12 | # furnished to do so, subject to the following conditions: 13 | # 14 | # The above copyright notice and this permission notice shall be included in 15 | # all copies or substantial portions of the Software. 16 | # 17 | # THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | # THE SOFTWARE. 24 | # 25 | #-------------------------------------------------------------------------- 26 | 27 | import io 28 | import asyncio 29 | import json 30 | import unittest 31 | try: 32 | from unittest import mock 33 | except ImportError: 34 | import mock 35 | import sys 36 | 37 | import pytest 38 | 39 | import requests 40 | from requests.adapters import HTTPAdapter 41 | from oauthlib import oauth2 42 | 43 | from msrest.async_client import ServiceClientAsync 44 | from msrest.authentication import OAuthTokenAuthentication 45 | from msrest.configuration import Configuration 46 | 47 | from msrest import Configuration 48 | from msrest.exceptions import ClientRequestError, TokenExpiredError 49 | from msrest.universal_http import ClientRequest 50 | from msrest.universal_http.async_requests import AsyncRequestsClientResponse 51 | 52 | 53 | @unittest.skipIf(sys.version_info < (3, 5, 2), "Async tests only on 3.5.2 minimal") 54 | class TestServiceClient(object): 55 | 56 | @pytest.mark.asyncio 57 | async def test_client_send(self): 58 | 59 | cfg = Configuration("/") 60 | cfg.headers = {'Test': 'true'} 61 | cfg.credentials = mock.create_autospec(OAuthTokenAuthentication) 62 | 63 | client = ServiceClientAsync(cfg) 64 | 65 | req_response = requests.Response() 66 | req_response._content = br'{"real": true}' # Has to be valid bytes JSON 67 | req_response._content_consumed = True 68 | req_response.status_code = 200 69 | 70 | def side_effect(*args, **kwargs): 71 | return req_response 72 | 73 | session = mock.create_autospec(requests.Session) 74 | session.request.side_effect = side_effect 75 | session.adapters = { 76 | "http://": HTTPAdapter(), 77 | "https://": HTTPAdapter(), 78 | } 79 | # Be sure the mock does not trick me 80 | assert not hasattr(session.resolve_redirects, 'is_msrest_patched') 81 | 82 | client.config.pipeline._sender.driver.session = session 83 | client.config.credentials.signed_session.return_value = session 84 | client.config.credentials.refresh_session.return_value = session 85 | 86 | request = ClientRequest('GET', '/') 87 | await client.async_send(request, stream=False) 88 | session.request.call_count = 0 89 | session.request.assert_called_with( 90 | 'GET', 91 | '/', 92 | allow_redirects=True, 93 | cert=None, 94 | headers={ 95 | 'User-Agent': cfg.user_agent, 96 | 'Test': 'true' # From global config 97 | }, 98 | stream=False, 99 | timeout=100, 100 | verify=True 101 | ) 102 | assert session.resolve_redirects.is_msrest_patched 103 | 104 | request = client.get('/', headers={'id':'1234'}, content={'Test':'Data'}) 105 | await client.async_send(request, stream=False) 106 | session.request.assert_called_with( 107 | 'GET', 108 | '/', 109 | data='{"Test": "Data"}', 110 | allow_redirects=True, 111 | cert=None, 112 | headers={ 113 | 'User-Agent': cfg.user_agent, 114 | 'Content-Length': '16', 115 | 'id':'1234', 116 | 'Accept': 'application/json', 117 | 'Test': 'true' # From global config 118 | }, 119 | stream=False, 120 | timeout=100, 121 | verify=True 122 | ) 123 | assert session.request.call_count == 1 124 | session.request.call_count = 0 125 | assert session.resolve_redirects.is_msrest_patched 126 | 127 | request = client.get('/', headers={'id':'1234'}, content={'Test':'Data'}) 128 | session.request.side_effect = requests.RequestException("test") 129 | with pytest.raises(ClientRequestError): 130 | await client.async_send(request, test='value', stream=False) 131 | session.request.assert_called_with( 132 | 'GET', 133 | '/', 134 | data='{"Test": "Data"}', 135 | allow_redirects=True, 136 | cert=None, 137 | headers={ 138 | 'User-Agent': cfg.user_agent, 139 | 'Content-Length': '16', 140 | 'id':'1234', 141 | 'Accept': 'application/json', 142 | 'Test': 'true' # From global config 143 | }, 144 | stream=False, 145 | timeout=100, 146 | verify=True 147 | ) 148 | assert session.request.call_count == 1 149 | session.request.call_count = 0 150 | assert session.resolve_redirects.is_msrest_patched 151 | 152 | session.request.side_effect = oauth2.rfc6749.errors.InvalidGrantError("test") 153 | with pytest.raises(TokenExpiredError): 154 | await client.async_send(request, headers={'id':'1234'}, content={'Test':'Data'}, test='value') 155 | assert session.request.call_count == 2 156 | session.request.call_count = 0 157 | 158 | session.request.side_effect = ValueError("test") 159 | with pytest.raises(ValueError): 160 | await client.async_send(request, headers={'id':'1234'}, content={'Test':'Data'}, test='value') 161 | 162 | @pytest.mark.asyncio 163 | async def test_client_stream_download(self): 164 | 165 | req_response = requests.Response() 166 | req_response._content = "abc" 167 | req_response._content_consumed = True 168 | req_response.status_code = 200 169 | 170 | client_response = AsyncRequestsClientResponse( 171 | None, 172 | req_response 173 | ) 174 | 175 | def user_callback(chunk, response): 176 | assert response is req_response 177 | assert chunk in ["a", "b", "c"] 178 | 179 | async_iterator = client_response.stream_download(1, user_callback) 180 | result = "" 181 | async for value in async_iterator: 182 | result += value 183 | assert result == "abc" 184 | 185 | 186 | if __name__ == '__main__': 187 | unittest.main() -------------------------------------------------------------------------------- /tests/asynctests/test_async_paging.py: -------------------------------------------------------------------------------- 1 | #-------------------------------------------------------------------------- 2 | # 3 | # Copyright (c) Microsoft Corporation. All rights reserved. 4 | # 5 | # The MIT License (MIT) 6 | # 7 | # Permission is hereby granted, free of charge, to any person obtaining a copy 8 | # of this software and associated documentation files (the ""Software""), to deal 9 | # in the Software without restriction, including without limitation the rights 10 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | # copies of the Software, and to permit persons to whom the Software is 12 | # furnished to do so, subject to the following conditions: 13 | # 14 | # The above copyright notice and this permission notice shall be included in 15 | # all copies or substantial portions of the Software. 16 | # 17 | # THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | # THE SOFTWARE. 24 | # 25 | #-------------------------------------------------------------------------- 26 | import sys 27 | import unittest 28 | import pytest 29 | 30 | from msrest.paging import Paged 31 | 32 | class FakePaged(Paged): 33 | _attribute_map = { 34 | 'next_link': {'key': 'nextLink', 'type': 'str'}, 35 | 'current_page': {'key': 'value', 'type': '[str]'} 36 | } 37 | 38 | def __init__(self, *args, **kwargs): 39 | super(FakePaged, self).__init__(*args, **kwargs) 40 | 41 | class TestPaging(object): 42 | 43 | @pytest.mark.asyncio 44 | async def test_basic_paging(self): 45 | 46 | async def internal_paging(next_link=None, raw=False): 47 | if not next_link: 48 | return { 49 | 'nextLink': 'page2', 50 | 'value': ['value1.0', 'value1.1'] 51 | } 52 | else: 53 | return { 54 | 'nextLink': None, 55 | 'value': ['value2.0', 'value2.1'] 56 | } 57 | 58 | deserialized = FakePaged(None, {}, async_command=internal_paging) 59 | 60 | # 3.6 only : result_iterated = [obj async for obj in deserialized] 61 | result_iterated = [] 62 | async for obj in deserialized: 63 | result_iterated.append(obj) 64 | 65 | assert ['value1.0', 'value1.1', 'value2.0', 'value2.1'] == result_iterated 66 | 67 | @pytest.mark.asyncio 68 | async def test_advance_paging(self): 69 | 70 | async def internal_paging(next_link=None, raw=False): 71 | if not next_link: 72 | return { 73 | 'nextLink': 'page2', 74 | 'value': ['value1.0', 'value1.1'] 75 | } 76 | else: 77 | return { 78 | 'nextLink': None, 79 | 'value': ['value2.0', 'value2.1'] 80 | } 81 | 82 | deserialized = FakePaged(None, {}, async_command=internal_paging) 83 | page1 = await deserialized.async_advance_page() 84 | assert ['value1.0', 'value1.1'] == page1 85 | 86 | page2 = await deserialized.async_advance_page() 87 | assert ['value2.0', 'value2.1'] == page2 88 | 89 | with pytest.raises(StopAsyncIteration): 90 | await deserialized.async_advance_page() 91 | 92 | @pytest.mark.asyncio 93 | async def test_get_paging(self): 94 | 95 | async def internal_paging(next_link=None, raw=False): 96 | if not next_link: 97 | return { 98 | 'nextLink': 'page2', 99 | 'value': ['value1.0', 'value1.1'] 100 | } 101 | elif next_link == 'page2': 102 | return { 103 | 'nextLink': 'page3', 104 | 'value': ['value2.0', 'value2.1'] 105 | } 106 | else: 107 | return { 108 | 'nextLink': None, 109 | 'value': ['value3.0', 'value3.1'] 110 | } 111 | 112 | deserialized = FakePaged(None, {}, async_command=internal_paging) 113 | page2 = await deserialized.async_get('page2') 114 | assert ['value2.0', 'value2.1'] == page2 115 | 116 | page3 = await deserialized.async_get('page3') 117 | assert ['value3.0', 'value3.1'] == page3 118 | 119 | @pytest.mark.asyncio 120 | async def test_reset_paging(self): 121 | 122 | async def internal_paging(next_link=None, raw=False): 123 | if not next_link: 124 | return { 125 | 'nextLink': 'page2', 126 | 'value': ['value1.0', 'value1.1'] 127 | } 128 | else: 129 | return { 130 | 'nextLink': None, 131 | 'value': ['value2.0', 'value2.1'] 132 | } 133 | 134 | deserialized = FakePaged(None, {}, async_command=internal_paging) 135 | deserialized.reset() 136 | 137 | # 3.6 only : result_iterated = [obj async for obj in deserialized] 138 | result_iterated = [] 139 | async for obj in deserialized: 140 | result_iterated.append(obj) 141 | 142 | assert ['value1.0', 'value1.1', 'value2.0', 'value2.1'] == result_iterated 143 | 144 | deserialized = FakePaged(None, {}, async_command=internal_paging) 145 | # Push the iterator to the last element 146 | async for element in deserialized: 147 | if element == "value2.0": 148 | break 149 | deserialized.reset() 150 | 151 | # 3.6 only : result_iterated = [obj async for obj in deserialized] 152 | result_iterated = [] 153 | async for obj in deserialized: 154 | result_iterated.append(obj) 155 | 156 | assert ['value1.0', 'value1.1', 'value2.0', 'value2.1'] == result_iterated 157 | 158 | @pytest.mark.asyncio 159 | async def test_none_value(self): 160 | async def internal_paging(next_link=None, raw=False): 161 | return { 162 | 'nextLink': None, 163 | 'value': None 164 | } 165 | 166 | deserialized = FakePaged(None, {}, async_command=internal_paging) 167 | 168 | # 3.6 only : result_iterated = [obj async for obj in deserialized] 169 | result_iterated = [] 170 | async for obj in deserialized: 171 | result_iterated.append(obj) 172 | 173 | assert len(result_iterated) == 0 174 | -------------------------------------------------------------------------------- /tests/asynctests/test_pipeline.py: -------------------------------------------------------------------------------- 1 | #-------------------------------------------------------------------------- 2 | # 3 | # Copyright (c) Microsoft Corporation. All rights reserved. 4 | # 5 | # The MIT License (MIT) 6 | # 7 | # Permission is hereby granted, free of charge, to any person obtaining a copy 8 | # of this software and associated documentation files (the ""Software""), to deal 9 | # in the Software without restriction, including without limitation the rights 10 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | # copies of the Software, and to permit persons to whom the Software is 12 | # furnished to do so, subject to the following conditions: 13 | # 14 | # The above copyright notice and this permission notice shall be included in 15 | # all copies or substantial portions of the Software. 16 | # 17 | # THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | # THE SOFTWARE. 24 | # 25 | #-------------------------------------------------------------------------- 26 | import sys 27 | 28 | from msrest.universal_http import ( 29 | ClientRequest, 30 | ) 31 | from msrest.universal_http.async_requests import ( 32 | AsyncRequestsHTTPSender, 33 | AsyncTrioRequestsHTTPSender, 34 | ) 35 | 36 | from msrest.pipeline import ( 37 | AsyncPipeline, 38 | AsyncHTTPSender, 39 | SansIOHTTPPolicy 40 | ) 41 | from msrest.pipeline.async_requests import AsyncPipelineRequestsHTTPSender 42 | from msrest.pipeline.universal import UserAgentPolicy 43 | from msrest.pipeline.aiohttp import AioHTTPSender 44 | 45 | from msrest.configuration import Configuration 46 | 47 | import trio 48 | 49 | import pytest 50 | 51 | 52 | @pytest.mark.asyncio 53 | async def test_sans_io_exception(): 54 | class BrokenSender(AsyncHTTPSender): 55 | async def send(self, request, **config): 56 | raise ValueError("Broken") 57 | 58 | async def __aexit__(self, exc_type, exc_value, traceback): 59 | """Raise any exception triggered within the runtime context.""" 60 | return None 61 | 62 | pipeline = AsyncPipeline([SansIOHTTPPolicy()], BrokenSender()) 63 | 64 | req = ClientRequest('GET', '/') 65 | with pytest.raises(ValueError): 66 | await pipeline.run(req) 67 | 68 | class SwapExec(SansIOHTTPPolicy): 69 | def on_exception(self, requests, **kwargs): 70 | exc_type, exc_value, exc_traceback = sys.exc_info() 71 | raise NotImplementedError(exc_value) 72 | 73 | pipeline = AsyncPipeline([SwapExec()], BrokenSender()) 74 | with pytest.raises(NotImplementedError): 75 | await pipeline.run(req) 76 | 77 | 78 | @pytest.mark.asyncio 79 | async def test_basic_aiohttp(): 80 | 81 | request = ClientRequest("GET", "http://bing.com") 82 | policies = [ 83 | UserAgentPolicy("myusergant") 84 | ] 85 | async with AsyncPipeline(policies) as pipeline: 86 | response = await pipeline.run(request) 87 | 88 | assert pipeline._sender.driver._session.closed 89 | assert response.http_response.status_code == 200 90 | 91 | @pytest.mark.asyncio 92 | async def test_basic_async_requests(): 93 | 94 | request = ClientRequest("GET", "http://bing.com") 95 | policies = [ 96 | UserAgentPolicy("myusergant") 97 | ] 98 | async with AsyncPipeline(policies, AsyncPipelineRequestsHTTPSender()) as pipeline: 99 | response = await pipeline.run(request) 100 | 101 | assert response.http_response.status_code == 200 102 | 103 | @pytest.mark.asyncio 104 | async def test_conf_async_requests(): 105 | 106 | conf = Configuration("http://bing.com/") 107 | request = ClientRequest("GET", "http://bing.com/") 108 | policies = [ 109 | UserAgentPolicy("myusergant") 110 | ] 111 | async with AsyncPipeline(policies, AsyncPipelineRequestsHTTPSender(AsyncRequestsHTTPSender(conf))) as pipeline: 112 | response = await pipeline.run(request) 113 | 114 | assert response.http_response.status_code == 200 115 | 116 | def test_conf_async_trio_requests(): 117 | 118 | async def do(): 119 | conf = Configuration("http://bing.com/") 120 | request = ClientRequest("GET", "http://bing.com/") 121 | policies = [ 122 | UserAgentPolicy("myusergant") 123 | ] 124 | async with AsyncPipeline(policies, AsyncPipelineRequestsHTTPSender(AsyncTrioRequestsHTTPSender(conf))) as pipeline: 125 | return await pipeline.run(request) 126 | 127 | response = trio.run(do) 128 | assert response.http_response.status_code == 200 -------------------------------------------------------------------------------- /tests/asynctests/test_polling.py: -------------------------------------------------------------------------------- 1 | #-------------------------------------------------------------------------- 2 | # 3 | # Copyright (c) Microsoft Corporation. All rights reserved. 4 | # 5 | # The MIT License (MIT) 6 | # 7 | # Permission is hereby granted, free of charge, to any person obtaining a copy 8 | # of this software and associated documentation files (the ""Software""), to deal 9 | # in the Software without restriction, including without limitation the rights 10 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | # copies of the Software, and to permit persons to whom the Software is 12 | # furnished to do so, subject to the following conditions: 13 | # 14 | # The above copyright notice and this permission notice shall be included in 15 | # all copies or substantial portions of the Software. 16 | # 17 | # THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | # THE SOFTWARE. 24 | # 25 | #-------------------------------------------------------------------------- 26 | import asyncio 27 | try: 28 | from unittest import mock 29 | except ImportError: 30 | import mock 31 | 32 | import pytest 33 | 34 | from msrest.polling.async_poller import * 35 | from msrest.async_client import ServiceClientAsync 36 | from msrest.serialization import Model 37 | from msrest.configuration import Configuration 38 | 39 | 40 | @pytest.mark.asyncio 41 | async def test_abc_polling(): 42 | abc_polling = AsyncPollingMethod() 43 | 44 | with pytest.raises(NotImplementedError): 45 | abc_polling.initialize(None, None, None) 46 | 47 | with pytest.raises(NotImplementedError): 48 | await abc_polling.run() 49 | 50 | with pytest.raises(NotImplementedError): 51 | abc_polling.status() 52 | 53 | with pytest.raises(NotImplementedError): 54 | abc_polling.finished() 55 | 56 | with pytest.raises(NotImplementedError): 57 | abc_polling.resource() 58 | 59 | 60 | @pytest.mark.asyncio 61 | async def test_no_polling(): 62 | no_polling = AsyncNoPolling() 63 | 64 | initial_response = "initial response" 65 | def deserialization_cb(response): 66 | assert response == initial_response 67 | return "Treated: "+response 68 | 69 | no_polling.initialize(None, initial_response, deserialization_cb) 70 | await no_polling.run() # Should no raise and do nothing 71 | assert no_polling.status() == "succeeded" 72 | assert no_polling.finished() 73 | assert no_polling.resource() == "Treated: "+initial_response 74 | 75 | 76 | class PollingTwoSteps(AsyncPollingMethod): 77 | """An empty poller that returns the deserialized initial response. 78 | """ 79 | def __init__(self, sleep=0): 80 | self._initial_response = None 81 | self._deserialization_callback = None 82 | self._sleep = sleep 83 | 84 | def initialize(self, _, initial_response, deserialization_callback): 85 | self._initial_response = initial_response 86 | self._deserialization_callback = deserialization_callback 87 | self._finished = False 88 | 89 | async def run(self): 90 | """Empty run, no polling. 91 | """ 92 | self._finished = True 93 | await asyncio.sleep(self._sleep) # Give me time to add callbacks! 94 | 95 | def status(self): 96 | """Return the current status as a string. 97 | :rtype: str 98 | """ 99 | return "succeeded" if self._finished else "running" 100 | 101 | def finished(self): 102 | """Is this polling finished? 103 | :rtype: bool 104 | """ 105 | return self._finished 106 | 107 | def resource(self): 108 | return self._deserialization_callback(self._initial_response) 109 | 110 | @pytest.fixture 111 | def client(): 112 | # We need a ServiceClientAsync instance, but the poller itself don't use it, so we don't need 113 | # Something functional 114 | return ServiceClientAsync(Configuration("http://example.org")) 115 | 116 | @pytest.mark.asyncio 117 | async def test_poller(client): 118 | 119 | # Same the poller itself doesn't care about the initial_response, and there is no type constraint here 120 | initial_response = "Initial response" 121 | 122 | # Same for deserialization_callback, just pass to the polling_method 123 | def deserialization_callback(response): 124 | assert response == initial_response 125 | return "Treated: "+response 126 | 127 | method = AsyncNoPolling() 128 | 129 | result = await async_poller(client, initial_response, deserialization_callback, method) 130 | assert result == "Treated: "+initial_response 131 | 132 | # Test with a basic Model 133 | class MockedModel(Model): 134 | called = False 135 | @classmethod 136 | def deserialize(cls, data): 137 | assert data == initial_response 138 | cls.called = True 139 | 140 | result = await async_poller(client, initial_response, MockedModel, method) 141 | assert MockedModel.called 142 | 143 | # Test poller that method do a run 144 | method = PollingTwoSteps(sleep=2) 145 | result = await async_poller(client, initial_response, deserialization_callback, method) 146 | 147 | assert result == "Treated: "+initial_response 148 | 149 | @pytest.mark.asyncio 150 | async def test_broken_poller(client): 151 | 152 | with pytest.raises(ValueError): 153 | await async_poller(None, None, None, None) 154 | 155 | class NoPollingError(PollingTwoSteps): 156 | async def run(self): 157 | raise ValueError("Something bad happened") 158 | 159 | initial_response = "Initial response" 160 | def deserialization_callback(response): 161 | return "Treated: "+response 162 | 163 | method = NoPollingError() 164 | 165 | with pytest.raises(ValueError) as excinfo: 166 | await async_poller(client, initial_response, deserialization_callback, method) 167 | assert "Something bad happened" in str(excinfo.value) 168 | -------------------------------------------------------------------------------- /tests/asynctests/test_universal_http.py: -------------------------------------------------------------------------------- 1 | #-------------------------------------------------------------------------- 2 | # 3 | # Copyright (c) Microsoft Corporation. All rights reserved. 4 | # 5 | # The MIT License (MIT) 6 | # 7 | # Permission is hereby granted, free of charge, to any person obtaining a copy 8 | # of this software and associated documentation files (the ""Software""), to deal 9 | # in the Software without restriction, including without limitation the rights 10 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | # copies of the Software, and to permit persons to whom the Software is 12 | # furnished to do so, subject to the following conditions: 13 | # 14 | # The above copyright notice and this permission notice shall be included in 15 | # all copies or substantial portions of the Software. 16 | # 17 | # THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | # THE SOFTWARE. 24 | # 25 | #-------------------------------------------------------------------------- 26 | import sys 27 | 28 | from msrest.universal_http import ( 29 | ClientRequest, 30 | AsyncHTTPSender, 31 | ) 32 | from msrest.universal_http.aiohttp import AioHTTPSender 33 | from msrest.universal_http.async_requests import ( 34 | AsyncBasicRequestsHTTPSender, 35 | AsyncRequestsHTTPSender, 36 | AsyncTrioRequestsHTTPSender, 37 | ) 38 | 39 | from msrest.configuration import Configuration 40 | 41 | import trio 42 | 43 | import pytest 44 | 45 | 46 | @pytest.mark.asyncio 47 | async def test_basic_aiohttp(): 48 | 49 | request = ClientRequest("GET", "http://bing.com") 50 | async with AioHTTPSender() as sender: 51 | response = await sender.send(request) 52 | assert response.body() is not None 53 | 54 | assert sender._session.closed 55 | assert response.status_code == 200 56 | 57 | @pytest.mark.asyncio 58 | async def test_basic_async_requests(): 59 | 60 | request = ClientRequest("GET", "http://bing.com") 61 | async with AsyncBasicRequestsHTTPSender() as sender: 62 | response = await sender.send(request) 63 | assert response.body() is not None 64 | 65 | assert response.status_code == 200 66 | 67 | @pytest.mark.asyncio 68 | async def test_conf_async_requests(): 69 | 70 | conf = Configuration("http://bing.com/") 71 | request = ClientRequest("GET", "http://bing.com/") 72 | async with AsyncRequestsHTTPSender(conf) as sender: 73 | response = await sender.send(request) 74 | assert response.body() is not None 75 | 76 | assert response.status_code == 200 77 | 78 | def test_conf_async_trio_requests(): 79 | 80 | async def do(): 81 | conf = Configuration("http://bing.com/") 82 | request = ClientRequest("GET", "http://bing.com/") 83 | async with AsyncTrioRequestsHTTPSender(conf) as sender: 84 | return await sender.send(request) 85 | assert response.body() is not None 86 | 87 | response = trio.run(do) 88 | assert response.status_code == 200 -------------------------------------------------------------------------------- /tests/conftest.py: -------------------------------------------------------------------------------- 1 | # -------------------------------------------------------------------------- 2 | # 3 | # Copyright (c) Microsoft Corporation. All rights reserved. 4 | # 5 | # The MIT License (MIT) 6 | # 7 | # Permission is hereby granted, free of charge, to any person obtaining a copy 8 | # of this software and associated documentation files (the ""Software""), to 9 | # deal in the Software without restriction, including without limitation the 10 | # rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 11 | # sell copies of the Software, and to permit persons to whom the Software is 12 | # furnished to do so, subject to the following conditions: 13 | # 14 | # The above copyright notice and this permission notice shall be included in 15 | # all copies or substantial portions of the Software. 16 | # 17 | # THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 22 | # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 23 | # IN THE SOFTWARE. 24 | # 25 | # -------------------------------------------------------------------------- 26 | import sys 27 | 28 | # Ignore collection of async tests for Python 2 29 | collect_ignore = [] 30 | if sys.version_info < (3, 5): 31 | collect_ignore.append("asynctests") 32 | -------------------------------------------------------------------------------- /tests/storage_models/__init__.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | # -------------------------------------------------------------------------- 3 | # Copyright (c) Microsoft Corporation. All rights reserved. 4 | # Licensed under the MIT License. See License.txt in the project root for 5 | # license information. 6 | # 7 | # Code generated by Microsoft (R) AutoRest Code Generator. 8 | # Changes may cause incorrect behavior and will be lost if the code is 9 | # regenerated. 10 | # -------------------------------------------------------------------------- 11 | 12 | from .storage_account_check_name_availability_parameters import StorageAccountCheckNameAvailabilityParameters 13 | from .check_name_availability_result import CheckNameAvailabilityResult 14 | from .sku import Sku 15 | from .custom_domain import CustomDomain 16 | from .encryption_service import EncryptionService 17 | from .encryption_services import EncryptionServices 18 | from .encryption import Encryption 19 | from .storage_account_create_parameters import StorageAccountCreateParameters 20 | from .endpoints import Endpoints 21 | from .storage_account import StorageAccount 22 | from .storage_account_key import StorageAccountKey 23 | from .storage_account_list_keys_result import StorageAccountListKeysResult 24 | from .storage_account_regenerate_key_parameters import StorageAccountRegenerateKeyParameters 25 | from .storage_account_update_parameters import StorageAccountUpdateParameters 26 | from .usage_name import UsageName 27 | from .usage import Usage 28 | from .resource import Resource 29 | from .account_sas_parameters import AccountSasParameters 30 | from .list_account_sas_response import ListAccountSasResponse 31 | from .service_sas_parameters import ServiceSasParameters 32 | from .list_service_sas_response import ListServiceSasResponse 33 | from .storage_account_paged import StorageAccountPaged 34 | from .usage_paged import UsagePaged 35 | from .storage_management_client_enums import ( 36 | Reason, 37 | SkuName, 38 | SkuTier, 39 | AccessTier, 40 | Kind, 41 | ProvisioningState, 42 | AccountStatus, 43 | KeyPermission, 44 | UsageUnit, 45 | HttpProtocol, 46 | ) 47 | 48 | __all__ = [ 49 | 'StorageAccountCheckNameAvailabilityParameters', 50 | 'CheckNameAvailabilityResult', 51 | 'Sku', 52 | 'CustomDomain', 53 | 'EncryptionService', 54 | 'EncryptionServices', 55 | 'Encryption', 56 | 'StorageAccountCreateParameters', 57 | 'Endpoints', 58 | 'StorageAccount', 59 | 'StorageAccountKey', 60 | 'StorageAccountListKeysResult', 61 | 'StorageAccountRegenerateKeyParameters', 62 | 'StorageAccountUpdateParameters', 63 | 'UsageName', 64 | 'Usage', 65 | 'Resource', 66 | 'AccountSasParameters', 67 | 'ListAccountSasResponse', 68 | 'ServiceSasParameters', 69 | 'ListServiceSasResponse', 70 | 'StorageAccountPaged', 71 | 'UsagePaged', 72 | 'Reason', 73 | 'SkuName', 74 | 'SkuTier', 75 | 'AccessTier', 76 | 'Kind', 77 | 'ProvisioningState', 78 | 'AccountStatus', 79 | 'KeyPermission', 80 | 'UsageUnit', 81 | 'HttpProtocol', 82 | ] 83 | -------------------------------------------------------------------------------- /tests/storage_models/account_sas_parameters.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | # -------------------------------------------------------------------------- 3 | # Copyright (c) Microsoft Corporation. All rights reserved. 4 | # Licensed under the MIT License. See License.txt in the project root for 5 | # license information. 6 | # 7 | # Code generated by Microsoft (R) AutoRest Code Generator. 8 | # Changes may cause incorrect behavior and will be lost if the code is 9 | # regenerated. 10 | # -------------------------------------------------------------------------- 11 | 12 | from msrest.serialization import Model 13 | 14 | 15 | class AccountSasParameters(Model): 16 | """The parameters to list SAS credentials of a storage account. 17 | 18 | :param services: The signed services accessible with the account SAS. 19 | Possible values include: Blob (b), Queue (q), Table (t), File (f). 20 | Possible values include: 'b', 'q', 't', 'f' 21 | :type services: str or :class:`enum 22 | ` 23 | :param resource_types: The signed resource types that are accessible with 24 | the account SAS. Service (s): Access to service-level APIs; Container (c): 25 | Access to container-level APIs; Object (o): Access to object-level APIs 26 | for blobs, queue messages, table entities, and files. Possible values 27 | include: 's', 'c', 'o' 28 | :type resource_types: str or :class:`enum 29 | ` 30 | :param permissions: The signed permissions for the account SAS. Possible 31 | values include: Read (r), Write (w), Delete (d), List (l), Add (a), Create 32 | (c), Update (u) and Process (p). Possible values include: 'r', 'd', 'w', 33 | 'l', 'a', 'c', 'u', 'p' 34 | :type permissions: str or :class:`enum 35 | ` 36 | :param ip_address_or_range: An IP address or a range of IP addresses from 37 | which to accept requests. 38 | :type ip_address_or_range: str 39 | :param protocols: The protocol permitted for a request made with the 40 | account SAS. Possible values include: 'https,http', 'https' 41 | :type protocols: str or :class:`HttpProtocol 42 | ` 43 | :param shared_access_start_time: The time at which the SAS becomes valid. 44 | :type shared_access_start_time: datetime 45 | :param shared_access_expiry_time: The time at which the shared access 46 | signature becomes invalid. 47 | :type shared_access_expiry_time: datetime 48 | :param key_to_sign: The key to sign the account SAS token with. 49 | :type key_to_sign: str 50 | """ 51 | 52 | _validation = { 53 | 'services': {'required': True}, 54 | 'resource_types': {'required': True}, 55 | 'permissions': {'required': True}, 56 | 'shared_access_expiry_time': {'required': True}, 57 | } 58 | 59 | _attribute_map = { 60 | 'services': {'key': 'signedServices', 'type': 'str'}, 61 | 'resource_types': {'key': 'signedResourceTypes', 'type': 'str'}, 62 | 'permissions': {'key': 'signedPermission', 'type': 'str'}, 63 | 'ip_address_or_range': {'key': 'signedIp', 'type': 'str'}, 64 | 'protocols': {'key': 'signedProtocol', 'type': 'HttpProtocol'}, 65 | 'shared_access_start_time': {'key': 'signedStart', 'type': 'iso-8601'}, 66 | 'shared_access_expiry_time': {'key': 'signedExpiry', 'type': 'iso-8601'}, 67 | 'key_to_sign': {'key': 'keyToSign', 'type': 'str'}, 68 | } 69 | 70 | def __init__(self, services, resource_types, permissions, shared_access_expiry_time, ip_address_or_range=None, protocols=None, shared_access_start_time=None, key_to_sign=None): 71 | self.services = services 72 | self.resource_types = resource_types 73 | self.permissions = permissions 74 | self.ip_address_or_range = ip_address_or_range 75 | self.protocols = protocols 76 | self.shared_access_start_time = shared_access_start_time 77 | self.shared_access_expiry_time = shared_access_expiry_time 78 | self.key_to_sign = key_to_sign 79 | -------------------------------------------------------------------------------- /tests/storage_models/check_name_availability_result.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | # -------------------------------------------------------------------------- 3 | # Copyright (c) Microsoft Corporation. All rights reserved. 4 | # Licensed under the MIT License. See License.txt in the project root for 5 | # license information. 6 | # 7 | # Code generated by Microsoft (R) AutoRest Code Generator. 8 | # Changes may cause incorrect behavior and will be lost if the code is 9 | # regenerated. 10 | # -------------------------------------------------------------------------- 11 | 12 | from msrest.serialization import Model 13 | 14 | 15 | class CheckNameAvailabilityResult(Model): 16 | """The CheckNameAvailability operation response. 17 | 18 | Variables are only populated by the server, and will be ignored when 19 | sending a request. 20 | 21 | :ivar name_available: Gets a boolean value that indicates whether the name 22 | is available for you to use. If true, the name is available. If false, the 23 | name has already been taken or is invalid and cannot be used. 24 | :vartype name_available: bool 25 | :ivar reason: Gets the reason that a storage account name could not be 26 | used. The Reason element is only returned if NameAvailable is false. 27 | Possible values include: 'AccountNameInvalid', 'AlreadyExists' 28 | :vartype reason: str or :class:`Reason 29 | ` 30 | :ivar message: Gets an error message explaining the Reason value in more 31 | detail. 32 | :vartype message: str 33 | """ 34 | 35 | _validation = { 36 | 'name_available': {'readonly': True}, 37 | 'reason': {'readonly': True}, 38 | 'message': {'readonly': True}, 39 | } 40 | 41 | _attribute_map = { 42 | 'name_available': {'key': 'nameAvailable', 'type': 'bool'}, 43 | 'reason': {'key': 'reason', 'type': 'Reason'}, 44 | 'message': {'key': 'message', 'type': 'str'}, 45 | } 46 | 47 | def __init__(self): 48 | self.name_available = None 49 | self.reason = None 50 | self.message = None 51 | -------------------------------------------------------------------------------- /tests/storage_models/custom_domain.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | # -------------------------------------------------------------------------- 3 | # Copyright (c) Microsoft Corporation. All rights reserved. 4 | # Licensed under the MIT License. See License.txt in the project root for 5 | # license information. 6 | # 7 | # Code generated by Microsoft (R) AutoRest Code Generator. 8 | # Changes may cause incorrect behavior and will be lost if the code is 9 | # regenerated. 10 | # -------------------------------------------------------------------------- 11 | 12 | from msrest.serialization import Model 13 | 14 | 15 | class CustomDomain(Model): 16 | """The custom domain assigned to this storage account. This can be set via 17 | Update. 18 | 19 | :param name: Gets or sets the custom domain name assigned to the storage 20 | account. Name is the CNAME source. 21 | :type name: str 22 | :param use_sub_domain: Indicates whether indirect CName validation is 23 | enabled. Default value is false. This should only be set on updates. 24 | :type use_sub_domain: bool 25 | """ 26 | 27 | _validation = { 28 | 'name': {'required': True}, 29 | } 30 | 31 | _attribute_map = { 32 | 'name': {'key': 'name', 'type': 'str'}, 33 | 'use_sub_domain': {'key': 'useSubDomain', 'type': 'bool'}, 34 | } 35 | 36 | def __init__(self, name, use_sub_domain=None): 37 | self.name = name 38 | self.use_sub_domain = use_sub_domain 39 | -------------------------------------------------------------------------------- /tests/storage_models/encryption.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | # -------------------------------------------------------------------------- 3 | # Copyright (c) Microsoft Corporation. All rights reserved. 4 | # Licensed under the MIT License. See License.txt in the project root for 5 | # license information. 6 | # 7 | # Code generated by Microsoft (R) AutoRest Code Generator. 8 | # Changes may cause incorrect behavior and will be lost if the code is 9 | # regenerated. 10 | # -------------------------------------------------------------------------- 11 | 12 | from msrest.serialization import Model 13 | 14 | 15 | class Encryption(Model): 16 | """The encryption settings on the storage account. 17 | 18 | Variables are only populated by the server, and will be ignored when 19 | sending a request. 20 | 21 | :param services: List of services which support encryption. 22 | :type services: :class:`EncryptionServices 23 | ` 24 | :ivar key_source: The encryption keySource (provider). Possible values 25 | (case-insensitive): Microsoft.Storage. Default value: "Microsoft.Storage" 26 | . 27 | :vartype key_source: str 28 | """ 29 | 30 | _validation = { 31 | 'key_source': {'required': True, 'constant': True}, 32 | } 33 | 34 | _attribute_map = { 35 | 'services': {'key': 'services', 'type': 'EncryptionServices'}, 36 | 'key_source': {'key': 'keySource', 'type': 'str'}, 37 | } 38 | 39 | key_source = "Microsoft.Storage" 40 | 41 | def __init__(self, services=None): 42 | self.services = services 43 | -------------------------------------------------------------------------------- /tests/storage_models/encryption_service.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | # -------------------------------------------------------------------------- 3 | # Copyright (c) Microsoft Corporation. All rights reserved. 4 | # Licensed under the MIT License. See License.txt in the project root for 5 | # license information. 6 | # 7 | # Code generated by Microsoft (R) AutoRest Code Generator. 8 | # Changes may cause incorrect behavior and will be lost if the code is 9 | # regenerated. 10 | # -------------------------------------------------------------------------- 11 | 12 | from msrest.serialization import Model 13 | 14 | 15 | class EncryptionService(Model): 16 | """A service that allows server-side encryption to be used. 17 | 18 | Variables are only populated by the server, and will be ignored when 19 | sending a request. 20 | 21 | :param enabled: A boolean indicating whether or not the service encrypts 22 | the data as it is stored. 23 | :type enabled: bool 24 | :ivar last_enabled_time: Gets a rough estimate of the date/time when the 25 | encryption was last enabled by the user. Only returned when encryption is 26 | enabled. There might be some unencrypted blobs which were written after 27 | this time, as it is just a rough estimate. 28 | :vartype last_enabled_time: datetime 29 | """ 30 | 31 | _validation = { 32 | 'last_enabled_time': {'readonly': True}, 33 | } 34 | 35 | _attribute_map = { 36 | 'enabled': {'key': 'enabled', 'type': 'bool'}, 37 | 'last_enabled_time': {'key': 'lastEnabledTime', 'type': 'iso-8601'}, 38 | } 39 | 40 | def __init__(self, enabled=None): 41 | self.enabled = enabled 42 | self.last_enabled_time = None 43 | -------------------------------------------------------------------------------- /tests/storage_models/encryption_services.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | # -------------------------------------------------------------------------- 3 | # Copyright (c) Microsoft Corporation. All rights reserved. 4 | # Licensed under the MIT License. See License.txt in the project root for 5 | # license information. 6 | # 7 | # Code generated by Microsoft (R) AutoRest Code Generator. 8 | # Changes may cause incorrect behavior and will be lost if the code is 9 | # regenerated. 10 | # -------------------------------------------------------------------------- 11 | 12 | from msrest.serialization import Model 13 | 14 | 15 | class EncryptionServices(Model): 16 | """A list of services that support encryption. 17 | 18 | Variables are only populated by the server, and will be ignored when 19 | sending a request. 20 | 21 | :param blob: The encryption function of the blob storage service. 22 | :type blob: :class:`EncryptionService 23 | ` 24 | :param file: The encryption function of the file storage service. 25 | :type file: :class:`EncryptionService 26 | ` 27 | :ivar table: The encryption function of the table storage service. 28 | :vartype table: :class:`EncryptionService 29 | ` 30 | :ivar queue: The encryption function of the queue storage service. 31 | :vartype queue: :class:`EncryptionService 32 | ` 33 | """ 34 | 35 | _validation = { 36 | 'table': {'readonly': True}, 37 | 'queue': {'readonly': True}, 38 | } 39 | 40 | _attribute_map = { 41 | 'blob': {'key': 'blob', 'type': 'EncryptionService'}, 42 | 'file': {'key': 'file', 'type': 'EncryptionService'}, 43 | 'table': {'key': 'table', 'type': 'EncryptionService'}, 44 | 'queue': {'key': 'queue', 'type': 'EncryptionService'}, 45 | } 46 | 47 | def __init__(self, blob=None, file=None): 48 | self.blob = blob 49 | self.file = file 50 | self.table = None 51 | self.queue = None 52 | -------------------------------------------------------------------------------- /tests/storage_models/endpoints.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | # -------------------------------------------------------------------------- 3 | # Copyright (c) Microsoft Corporation. All rights reserved. 4 | # Licensed under the MIT License. See License.txt in the project root for 5 | # license information. 6 | # 7 | # Code generated by Microsoft (R) AutoRest Code Generator. 8 | # Changes may cause incorrect behavior and will be lost if the code is 9 | # regenerated. 10 | # -------------------------------------------------------------------------- 11 | 12 | from msrest.serialization import Model 13 | 14 | 15 | class Endpoints(Model): 16 | """The URIs that are used to perform a retrieval of a public blob, queue, or 17 | table object. 18 | 19 | Variables are only populated by the server, and will be ignored when 20 | sending a request. 21 | 22 | :ivar blob: Gets the blob endpoint. 23 | :vartype blob: str 24 | :ivar queue: Gets the queue endpoint. 25 | :vartype queue: str 26 | :ivar table: Gets the table endpoint. 27 | :vartype table: str 28 | :ivar file: Gets the file endpoint. 29 | :vartype file: str 30 | """ 31 | 32 | _validation = { 33 | 'blob': {'readonly': True}, 34 | 'queue': {'readonly': True}, 35 | 'table': {'readonly': True}, 36 | 'file': {'readonly': True}, 37 | } 38 | 39 | _attribute_map = { 40 | 'blob': {'key': 'blob', 'type': 'str'}, 41 | 'queue': {'key': 'queue', 'type': 'str'}, 42 | 'table': {'key': 'table', 'type': 'str'}, 43 | 'file': {'key': 'file', 'type': 'str'}, 44 | } 45 | 46 | def __init__(self): 47 | self.blob = None 48 | self.queue = None 49 | self.table = None 50 | self.file = None 51 | -------------------------------------------------------------------------------- /tests/storage_models/list_account_sas_response.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | # -------------------------------------------------------------------------- 3 | # Copyright (c) Microsoft Corporation. All rights reserved. 4 | # Licensed under the MIT License. See License.txt in the project root for 5 | # license information. 6 | # 7 | # Code generated by Microsoft (R) AutoRest Code Generator. 8 | # Changes may cause incorrect behavior and will be lost if the code is 9 | # regenerated. 10 | # -------------------------------------------------------------------------- 11 | 12 | from msrest.serialization import Model 13 | 14 | 15 | class ListAccountSasResponse(Model): 16 | """The List SAS credentials operation response. 17 | 18 | Variables are only populated by the server, and will be ignored when 19 | sending a request. 20 | 21 | :ivar account_sas_token: List SAS credentials of storage account. 22 | :vartype account_sas_token: str 23 | """ 24 | 25 | _validation = { 26 | 'account_sas_token': {'readonly': True}, 27 | } 28 | 29 | _attribute_map = { 30 | 'account_sas_token': {'key': 'accountSasToken', 'type': 'str'}, 31 | } 32 | 33 | def __init__(self): 34 | self.account_sas_token = None 35 | -------------------------------------------------------------------------------- /tests/storage_models/list_service_sas_response.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | # -------------------------------------------------------------------------- 3 | # Copyright (c) Microsoft Corporation. All rights reserved. 4 | # Licensed under the MIT License. See License.txt in the project root for 5 | # license information. 6 | # 7 | # Code generated by Microsoft (R) AutoRest Code Generator. 8 | # Changes may cause incorrect behavior and will be lost if the code is 9 | # regenerated. 10 | # -------------------------------------------------------------------------- 11 | 12 | from msrest.serialization import Model 13 | 14 | 15 | class ListServiceSasResponse(Model): 16 | """The List service SAS credentials operation response. 17 | 18 | Variables are only populated by the server, and will be ignored when 19 | sending a request. 20 | 21 | :ivar service_sas_token: List service SAS credentials of specific 22 | resource. 23 | :vartype service_sas_token: str 24 | """ 25 | 26 | _validation = { 27 | 'service_sas_token': {'readonly': True}, 28 | } 29 | 30 | _attribute_map = { 31 | 'service_sas_token': {'key': 'serviceSasToken', 'type': 'str'}, 32 | } 33 | 34 | def __init__(self): 35 | self.service_sas_token = None 36 | -------------------------------------------------------------------------------- /tests/storage_models/resource.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | # -------------------------------------------------------------------------- 3 | # Copyright (c) Microsoft Corporation. All rights reserved. 4 | # Licensed under the MIT License. See License.txt in the project root for 5 | # license information. 6 | # 7 | # Code generated by Microsoft (R) AutoRest Code Generator. 8 | # Changes may cause incorrect behavior and will be lost if the code is 9 | # regenerated. 10 | # -------------------------------------------------------------------------- 11 | 12 | from msrest.serialization import Model 13 | 14 | 15 | class Resource(Model): 16 | """Describes a storage resource. 17 | 18 | Variables are only populated by the server, and will be ignored when 19 | sending a request. 20 | 21 | :ivar id: Resource Id 22 | :vartype id: str 23 | :ivar name: Resource name 24 | :vartype name: str 25 | :ivar type: Resource type 26 | :vartype type: str 27 | :param location: Resource location 28 | :type location: str 29 | :param tags: Tags assigned to a resource; can be used for viewing and 30 | grouping a resource (across resource groups). 31 | :type tags: dict 32 | """ 33 | 34 | _validation = { 35 | 'id': {'readonly': True}, 36 | 'name': {'readonly': True}, 37 | 'type': {'readonly': True}, 38 | } 39 | 40 | _attribute_map = { 41 | 'id': {'key': 'id', 'type': 'str'}, 42 | 'name': {'key': 'name', 'type': 'str'}, 43 | 'type': {'key': 'type', 'type': 'str'}, 44 | 'location': {'key': 'location', 'type': 'str'}, 45 | 'tags': {'key': 'tags', 'type': '{str}'}, 46 | } 47 | 48 | def __init__(self, location=None, tags=None): 49 | self.id = None 50 | self.name = None 51 | self.type = None 52 | self.location = location 53 | self.tags = tags 54 | -------------------------------------------------------------------------------- /tests/storage_models/service_sas_parameters.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | # -------------------------------------------------------------------------- 3 | # Copyright (c) Microsoft Corporation. All rights reserved. 4 | # Licensed under the MIT License. See License.txt in the project root for 5 | # license information. 6 | # 7 | # Code generated by Microsoft (R) AutoRest Code Generator. 8 | # Changes may cause incorrect behavior and will be lost if the code is 9 | # regenerated. 10 | # -------------------------------------------------------------------------- 11 | 12 | from msrest.serialization import Model 13 | 14 | 15 | class ServiceSasParameters(Model): 16 | """The parameters to list service SAS credentials of a specific resource. 17 | 18 | :param canonicalized_resource: The canonical path to the signed resource. 19 | :type canonicalized_resource: str 20 | :param resource: The signed services accessible with the service SAS. 21 | Possible values include: Blob (b), Container (c), File (f), Share (s). 22 | Possible values include: 'b', 'c', 'f', 's' 23 | :type resource: str or :class:`enum 24 | ` 25 | :param permissions: The signed permissions for the service SAS. Possible 26 | values include: Read (r), Write (w), Delete (d), List (l), Add (a), Create 27 | (c), Update (u) and Process (p). Possible values include: 'r', 'd', 'w', 28 | 'l', 'a', 'c', 'u', 'p' 29 | :type permissions: str or :class:`enum 30 | ` 31 | :param ip_address_or_range: An IP address or a range of IP addresses from 32 | which to accept requests. 33 | :type ip_address_or_range: str 34 | :param protocols: The protocol permitted for a request made with the 35 | account SAS. Possible values include: 'https,http', 'https' 36 | :type protocols: str or :class:`HttpProtocol 37 | ` 38 | :param shared_access_start_time: The time at which the SAS becomes valid. 39 | :type shared_access_start_time: datetime 40 | :param shared_access_expiry_time: The time at which the shared access 41 | signature becomes invalid. 42 | :type shared_access_expiry_time: datetime 43 | :param identifier: A unique value up to 64 characters in length that 44 | correlates to an access policy specified for the container, queue, or 45 | table. 46 | :type identifier: str 47 | :param partition_key_start: The start of partition key. 48 | :type partition_key_start: str 49 | :param partition_key_end: The end of partition key. 50 | :type partition_key_end: str 51 | :param row_key_start: The start of row key. 52 | :type row_key_start: str 53 | :param row_key_end: The end of row key. 54 | :type row_key_end: str 55 | :param key_to_sign: The key to sign the account SAS token with. 56 | :type key_to_sign: str 57 | :param cache_control: The response header override for cache control. 58 | :type cache_control: str 59 | :param content_disposition: The response header override for content 60 | disposition. 61 | :type content_disposition: str 62 | :param content_encoding: The response header override for content 63 | encoding. 64 | :type content_encoding: str 65 | :param content_language: The response header override for content 66 | language. 67 | :type content_language: str 68 | :param content_type: The response header override for content type. 69 | :type content_type: str 70 | """ 71 | 72 | _validation = { 73 | 'canonicalized_resource': {'required': True}, 74 | 'resource': {'required': True}, 75 | 'identifier': {'max_length': 64}, 76 | } 77 | 78 | _attribute_map = { 79 | 'canonicalized_resource': {'key': 'canonicalizedResource', 'type': 'str'}, 80 | 'resource': {'key': 'signedResource', 'type': 'str'}, 81 | 'permissions': {'key': 'signedPermission', 'type': 'str'}, 82 | 'ip_address_or_range': {'key': 'signedIp', 'type': 'str'}, 83 | 'protocols': {'key': 'signedProtocol', 'type': 'HttpProtocol'}, 84 | 'shared_access_start_time': {'key': 'signedStart', 'type': 'iso-8601'}, 85 | 'shared_access_expiry_time': {'key': 'signedExpiry', 'type': 'iso-8601'}, 86 | 'identifier': {'key': 'signedIdentifier', 'type': 'str'}, 87 | 'partition_key_start': {'key': 'startPk', 'type': 'str'}, 88 | 'partition_key_end': {'key': 'endPk', 'type': 'str'}, 89 | 'row_key_start': {'key': 'startRk', 'type': 'str'}, 90 | 'row_key_end': {'key': 'endRk', 'type': 'str'}, 91 | 'key_to_sign': {'key': 'keyToSign', 'type': 'str'}, 92 | 'cache_control': {'key': 'rscc', 'type': 'str'}, 93 | 'content_disposition': {'key': 'rscd', 'type': 'str'}, 94 | 'content_encoding': {'key': 'rsce', 'type': 'str'}, 95 | 'content_language': {'key': 'rscl', 'type': 'str'}, 96 | 'content_type': {'key': 'rsct', 'type': 'str'}, 97 | } 98 | 99 | def __init__(self, canonicalized_resource, resource, permissions=None, ip_address_or_range=None, protocols=None, shared_access_start_time=None, shared_access_expiry_time=None, identifier=None, partition_key_start=None, partition_key_end=None, row_key_start=None, row_key_end=None, key_to_sign=None, cache_control=None, content_disposition=None, content_encoding=None, content_language=None, content_type=None): 100 | self.canonicalized_resource = canonicalized_resource 101 | self.resource = resource 102 | self.permissions = permissions 103 | self.ip_address_or_range = ip_address_or_range 104 | self.protocols = protocols 105 | self.shared_access_start_time = shared_access_start_time 106 | self.shared_access_expiry_time = shared_access_expiry_time 107 | self.identifier = identifier 108 | self.partition_key_start = partition_key_start 109 | self.partition_key_end = partition_key_end 110 | self.row_key_start = row_key_start 111 | self.row_key_end = row_key_end 112 | self.key_to_sign = key_to_sign 113 | self.cache_control = cache_control 114 | self.content_disposition = content_disposition 115 | self.content_encoding = content_encoding 116 | self.content_language = content_language 117 | self.content_type = content_type 118 | -------------------------------------------------------------------------------- /tests/storage_models/sku.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | # -------------------------------------------------------------------------- 3 | # Copyright (c) Microsoft Corporation. All rights reserved. 4 | # Licensed under the MIT License. See License.txt in the project root for 5 | # license information. 6 | # 7 | # Code generated by Microsoft (R) AutoRest Code Generator. 8 | # Changes may cause incorrect behavior and will be lost if the code is 9 | # regenerated. 10 | # -------------------------------------------------------------------------- 11 | 12 | from msrest.serialization import Model 13 | 14 | 15 | class Sku(Model): 16 | """The SKU of the storage account. 17 | 18 | Variables are only populated by the server, and will be ignored when 19 | sending a request. 20 | 21 | :param name: Gets or sets the sku name. Required for account creation; 22 | optional for update. Note that in older versions, sku name was called 23 | accountType. Possible values include: 'Standard_LRS', 'Standard_GRS', 24 | 'Standard_RAGRS', 'Standard_ZRS', 'Premium_LRS' 25 | :type name: str or :class:`SkuName 26 | ` 27 | :ivar tier: Gets the sku tier. This is based on the SKU name. Possible 28 | values include: 'Standard', 'Premium' 29 | :vartype tier: str or :class:`SkuTier 30 | ` 31 | """ 32 | 33 | _validation = { 34 | 'name': {'required': True}, 35 | 'tier': {'readonly': True}, 36 | } 37 | 38 | _attribute_map = { 39 | 'name': {'key': 'name', 'type': 'SkuName'}, 40 | 'tier': {'key': 'tier', 'type': 'SkuTier'}, 41 | } 42 | 43 | def __init__(self, name): 44 | self.name = name 45 | self.tier = None 46 | -------------------------------------------------------------------------------- /tests/storage_models/storage_account.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | # -------------------------------------------------------------------------- 3 | # Copyright (c) Microsoft Corporation. All rights reserved. 4 | # Licensed under the MIT License. See License.txt in the project root for 5 | # license information. 6 | # 7 | # Code generated by Microsoft (R) AutoRest Code Generator. 8 | # Changes may cause incorrect behavior and will be lost if the code is 9 | # regenerated. 10 | # -------------------------------------------------------------------------- 11 | 12 | from .resource import Resource 13 | 14 | 15 | class StorageAccount(Resource): 16 | """The storage account. 17 | 18 | Variables are only populated by the server, and will be ignored when 19 | sending a request. 20 | 21 | :ivar id: Resource Id 22 | :vartype id: str 23 | :ivar name: Resource name 24 | :vartype name: str 25 | :ivar type: Resource type 26 | :vartype type: str 27 | :param location: Resource location 28 | :type location: str 29 | :param tags: Tags assigned to a resource; can be used for viewing and 30 | grouping a resource (across resource groups). 31 | :type tags: dict 32 | :ivar sku: Gets the SKU. 33 | :vartype sku: :class:`Sku ` 34 | :ivar kind: Gets the Kind. Possible values include: 'Storage', 35 | 'BlobStorage' 36 | :vartype kind: str or :class:`Kind 37 | ` 38 | :ivar provisioning_state: Gets the status of the storage account at the 39 | time the operation was called. Possible values include: 'Creating', 40 | 'ResolvingDNS', 'Succeeded' 41 | :vartype provisioning_state: str or :class:`ProvisioningState 42 | ` 43 | :ivar primary_endpoints: Gets the URLs that are used to perform a 44 | retrieval of a public blob, queue, or table object. Note that Standard_ZRS 45 | and Premium_LRS accounts only return the blob endpoint. 46 | :vartype primary_endpoints: :class:`Endpoints 47 | ` 48 | :ivar primary_location: Gets the location of the primary data center for 49 | the storage account. 50 | :vartype primary_location: str 51 | :ivar status_of_primary: Gets the status indicating whether the primary 52 | location of the storage account is available or unavailable. Possible 53 | values include: 'available', 'unavailable' 54 | :vartype status_of_primary: str or :class:`AccountStatus 55 | ` 56 | :ivar last_geo_failover_time: Gets the timestamp of the most recent 57 | instance of a failover to the secondary location. Only the most recent 58 | timestamp is retained. This element is not returned if there has never 59 | been a failover instance. Only available if the accountType is 60 | Standard_GRS or Standard_RAGRS. 61 | :vartype last_geo_failover_time: datetime 62 | :ivar secondary_location: Gets the location of the geo-replicated 63 | secondary for the storage account. Only available if the accountType is 64 | Standard_GRS or Standard_RAGRS. 65 | :vartype secondary_location: str 66 | :ivar status_of_secondary: Gets the status indicating whether the 67 | secondary location of the storage account is available or unavailable. 68 | Only available if the SKU name is Standard_GRS or Standard_RAGRS. Possible 69 | values include: 'available', 'unavailable' 70 | :vartype status_of_secondary: str or :class:`AccountStatus 71 | ` 72 | :ivar creation_time: Gets the creation date and time of the storage 73 | account in UTC. 74 | :vartype creation_time: datetime 75 | :ivar custom_domain: Gets the custom domain the user assigned to this 76 | storage account. 77 | :vartype custom_domain: :class:`CustomDomain 78 | ` 79 | :ivar secondary_endpoints: Gets the URLs that are used to perform a 80 | retrieval of a public blob, queue, or table object from the secondary 81 | location of the storage account. Only available if the SKU name is 82 | Standard_RAGRS. 83 | :vartype secondary_endpoints: :class:`Endpoints 84 | ` 85 | :ivar encryption: Gets the encryption settings on the account. If 86 | unspecified, the account is unencrypted. 87 | :vartype encryption: :class:`Encryption 88 | ` 89 | :ivar access_tier: Required for storage accounts where kind = BlobStorage. 90 | The access tier used for billing. Possible values include: 'Hot', 'Cool' 91 | :vartype access_tier: str or :class:`AccessTier 92 | ` 93 | :param enable_https_traffic_only: Allows https traffic only to storage 94 | service if sets to true. Default value: False . 95 | :type enable_https_traffic_only: bool 96 | """ 97 | 98 | _validation = { 99 | 'id': {'readonly': True}, 100 | 'name': {'readonly': True}, 101 | 'type': {'readonly': True}, 102 | 'sku': {'readonly': True}, 103 | 'kind': {'readonly': True}, 104 | 'provisioning_state': {'readonly': True}, 105 | 'primary_endpoints': {'readonly': True}, 106 | 'primary_location': {'readonly': True}, 107 | 'status_of_primary': {'readonly': True}, 108 | 'last_geo_failover_time': {'readonly': True}, 109 | 'secondary_location': {'readonly': True}, 110 | 'status_of_secondary': {'readonly': True}, 111 | 'creation_time': {'readonly': True}, 112 | 'custom_domain': {'readonly': True}, 113 | 'secondary_endpoints': {'readonly': True}, 114 | 'encryption': {'readonly': True}, 115 | 'access_tier': {'readonly': True}, 116 | } 117 | 118 | _attribute_map = { 119 | 'id': {'key': 'id', 'type': 'str'}, 120 | 'name': {'key': 'name', 'type': 'str'}, 121 | 'type': {'key': 'type', 'type': 'str'}, 122 | 'location': {'key': 'location', 'type': 'str'}, 123 | 'tags': {'key': 'tags', 'type': '{str}'}, 124 | 'sku': {'key': 'sku', 'type': 'Sku'}, 125 | 'kind': {'key': 'kind', 'type': 'Kind'}, 126 | 'provisioning_state': {'key': 'properties.provisioningState', 'type': 'ProvisioningState'}, 127 | 'primary_endpoints': {'key': 'properties.primaryEndpoints', 'type': 'Endpoints'}, 128 | 'primary_location': {'key': 'properties.primaryLocation', 'type': 'str'}, 129 | 'status_of_primary': {'key': 'properties.statusOfPrimary', 'type': 'AccountStatus'}, 130 | 'last_geo_failover_time': {'key': 'properties.lastGeoFailoverTime', 'type': 'iso-8601'}, 131 | 'secondary_location': {'key': 'properties.secondaryLocation', 'type': 'str'}, 132 | 'status_of_secondary': {'key': 'properties.statusOfSecondary', 'type': 'AccountStatus'}, 133 | 'creation_time': {'key': 'properties.creationTime', 'type': 'iso-8601'}, 134 | 'custom_domain': {'key': 'properties.customDomain', 'type': 'CustomDomain'}, 135 | 'secondary_endpoints': {'key': 'properties.secondaryEndpoints', 'type': 'Endpoints'}, 136 | 'encryption': {'key': 'properties.encryption', 'type': 'Encryption'}, 137 | 'access_tier': {'key': 'properties.accessTier', 'type': 'AccessTier'}, 138 | 'enable_https_traffic_only': {'key': 'properties.supportsHttpsTrafficOnly', 'type': 'bool'}, 139 | } 140 | 141 | def __init__(self, location=None, tags=None, enable_https_traffic_only=False): 142 | super(StorageAccount, self).__init__(location=location, tags=tags) 143 | self.sku = None 144 | self.kind = None 145 | self.provisioning_state = None 146 | self.primary_endpoints = None 147 | self.primary_location = None 148 | self.status_of_primary = None 149 | self.last_geo_failover_time = None 150 | self.secondary_location = None 151 | self.status_of_secondary = None 152 | self.creation_time = None 153 | self.custom_domain = None 154 | self.secondary_endpoints = None 155 | self.encryption = None 156 | self.access_tier = None 157 | self.enable_https_traffic_only = enable_https_traffic_only 158 | -------------------------------------------------------------------------------- /tests/storage_models/storage_account_check_name_availability_parameters.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | # -------------------------------------------------------------------------- 3 | # Copyright (c) Microsoft Corporation. All rights reserved. 4 | # Licensed under the MIT License. See License.txt in the project root for 5 | # license information. 6 | # 7 | # Code generated by Microsoft (R) AutoRest Code Generator. 8 | # Changes may cause incorrect behavior and will be lost if the code is 9 | # regenerated. 10 | # -------------------------------------------------------------------------- 11 | 12 | from msrest.serialization import Model 13 | 14 | 15 | class StorageAccountCheckNameAvailabilityParameters(Model): 16 | """The parameters used to check the availability of the storage account name. 17 | 18 | Variables are only populated by the server, and will be ignored when 19 | sending a request. 20 | 21 | :param name: 22 | :type name: str 23 | :ivar type: Default value: "Microsoft.Storage/storageAccounts" . 24 | :vartype type: str 25 | """ 26 | 27 | _validation = { 28 | 'name': {'required': True}, 29 | 'type': {'required': True, 'constant': True}, 30 | } 31 | 32 | _attribute_map = { 33 | 'name': {'key': 'name', 'type': 'str'}, 34 | 'type': {'key': 'type', 'type': 'str'}, 35 | } 36 | 37 | type = "Microsoft.Storage/storageAccounts" 38 | 39 | def __init__(self, name): 40 | self.name = name 41 | -------------------------------------------------------------------------------- /tests/storage_models/storage_account_create_parameters.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | # -------------------------------------------------------------------------- 3 | # Copyright (c) Microsoft Corporation. All rights reserved. 4 | # Licensed under the MIT License. See License.txt in the project root for 5 | # license information. 6 | # 7 | # Code generated by Microsoft (R) AutoRest Code Generator. 8 | # Changes may cause incorrect behavior and will be lost if the code is 9 | # regenerated. 10 | # -------------------------------------------------------------------------- 11 | 12 | from msrest.serialization import Model 13 | 14 | 15 | class StorageAccountCreateParameters(Model): 16 | """The parameters used when creating a storage account. 17 | 18 | :param sku: Required. Gets or sets the sku name. 19 | :type sku: :class:`Sku ` 20 | :param kind: Required. Indicates the type of storage account. Possible 21 | values include: 'Storage', 'BlobStorage' 22 | :type kind: str or :class:`Kind 23 | ` 24 | :param location: Required. Gets or sets the location of the resource. This 25 | will be one of the supported and registered Azure Geo Regions (e.g. West 26 | US, East US, Southeast Asia, etc.). The geo region of a resource cannot be 27 | changed once it is created, but if an identical geo region is specified on 28 | update, the request will succeed. 29 | :type location: str 30 | :param tags: Gets or sets a list of key value pairs that describe the 31 | resource. These tags can be used for viewing and grouping this resource 32 | (across resource groups). A maximum of 15 tags can be provided for a 33 | resource. Each tag must have a key with a length no greater than 128 34 | characters and a value with a length no greater than 256 characters. 35 | :type tags: dict 36 | :param custom_domain: User domain assigned to the storage account. Name is 37 | the CNAME source. Only one custom domain is supported per storage account 38 | at this time. To clear the existing custom domain, use an empty string for 39 | the custom domain name property. 40 | :type custom_domain: :class:`CustomDomain 41 | ` 42 | :param encryption: Provides the encryption settings on the account. If 43 | left unspecified the account encryption settings will remain the same. The 44 | default setting is unencrypted. 45 | :type encryption: :class:`Encryption 46 | ` 47 | :param access_tier: Required for storage accounts where kind = 48 | BlobStorage. The access tier used for billing. Possible values include: 49 | 'Hot', 'Cool' 50 | :type access_tier: str or :class:`AccessTier 51 | ` 52 | :param enable_https_traffic_only: Allows https traffic only to storage 53 | service if sets to true. Default value: False . 54 | :type enable_https_traffic_only: bool 55 | """ 56 | 57 | _validation = { 58 | 'sku': {'required': True}, 59 | 'kind': {'required': True}, 60 | 'location': {'required': True}, 61 | } 62 | 63 | _attribute_map = { 64 | 'sku': {'key': 'sku', 'type': 'Sku'}, 65 | 'kind': {'key': 'kind', 'type': 'Kind'}, 66 | 'location': {'key': 'location', 'type': 'str'}, 67 | 'tags': {'key': 'tags', 'type': '{str}'}, 68 | 'custom_domain': {'key': 'properties.customDomain', 'type': 'CustomDomain'}, 69 | 'encryption': {'key': 'properties.encryption', 'type': 'Encryption'}, 70 | 'access_tier': {'key': 'properties.accessTier', 'type': 'AccessTier'}, 71 | 'enable_https_traffic_only': {'key': 'properties.supportsHttpsTrafficOnly', 'type': 'bool'}, 72 | } 73 | 74 | def __init__(self, sku, kind, location, tags=None, custom_domain=None, encryption=None, access_tier=None, enable_https_traffic_only=False): 75 | self.sku = sku 76 | self.kind = kind 77 | self.location = location 78 | self.tags = tags 79 | self.custom_domain = custom_domain 80 | self.encryption = encryption 81 | self.access_tier = access_tier 82 | self.enable_https_traffic_only = enable_https_traffic_only 83 | -------------------------------------------------------------------------------- /tests/storage_models/storage_account_key.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | # -------------------------------------------------------------------------- 3 | # Copyright (c) Microsoft Corporation. All rights reserved. 4 | # Licensed under the MIT License. See License.txt in the project root for 5 | # license information. 6 | # 7 | # Code generated by Microsoft (R) AutoRest Code Generator. 8 | # Changes may cause incorrect behavior and will be lost if the code is 9 | # regenerated. 10 | # -------------------------------------------------------------------------- 11 | 12 | from msrest.serialization import Model 13 | 14 | 15 | class StorageAccountKey(Model): 16 | """An access key for the storage account. 17 | 18 | Variables are only populated by the server, and will be ignored when 19 | sending a request. 20 | 21 | :ivar key_name: Name of the key. 22 | :vartype key_name: str 23 | :ivar value: Base 64-encoded value of the key. 24 | :vartype value: str 25 | :ivar permissions: Permissions for the key -- read-only or full 26 | permissions. Possible values include: 'Read', 'Full' 27 | :vartype permissions: str or :class:`KeyPermission 28 | ` 29 | """ 30 | 31 | _validation = { 32 | 'key_name': {'readonly': True}, 33 | 'value': {'readonly': True}, 34 | 'permissions': {'readonly': True}, 35 | } 36 | 37 | _attribute_map = { 38 | 'key_name': {'key': 'keyName', 'type': 'str'}, 39 | 'value': {'key': 'value', 'type': 'str'}, 40 | 'permissions': {'key': 'permissions', 'type': 'KeyPermission'}, 41 | } 42 | 43 | def __init__(self): 44 | self.key_name = None 45 | self.value = None 46 | self.permissions = None 47 | -------------------------------------------------------------------------------- /tests/storage_models/storage_account_list_keys_result.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | # -------------------------------------------------------------------------- 3 | # Copyright (c) Microsoft Corporation. All rights reserved. 4 | # Licensed under the MIT License. See License.txt in the project root for 5 | # license information. 6 | # 7 | # Code generated by Microsoft (R) AutoRest Code Generator. 8 | # Changes may cause incorrect behavior and will be lost if the code is 9 | # regenerated. 10 | # -------------------------------------------------------------------------- 11 | 12 | from msrest.serialization import Model 13 | 14 | 15 | class StorageAccountListKeysResult(Model): 16 | """The response from the ListKeys operation. 17 | 18 | Variables are only populated by the server, and will be ignored when 19 | sending a request. 20 | 21 | :ivar keys: Gets the list of storage account keys and their properties for 22 | the specified storage account. 23 | :vartype keys: list of :class:`StorageAccountKey 24 | ` 25 | """ 26 | 27 | _validation = { 28 | 'keys': {'readonly': True}, 29 | } 30 | 31 | _attribute_map = { 32 | 'keys': {'key': 'keys', 'type': '[StorageAccountKey]'}, 33 | } 34 | 35 | def __init__(self): 36 | self.keys = None 37 | -------------------------------------------------------------------------------- /tests/storage_models/storage_account_paged.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | # -------------------------------------------------------------------------- 3 | # Copyright (c) Microsoft Corporation. All rights reserved. 4 | # Licensed under the MIT License. See License.txt in the project root for 5 | # license information. 6 | # 7 | # Code generated by Microsoft (R) AutoRest Code Generator. 8 | # Changes may cause incorrect behavior and will be lost if the code is 9 | # regenerated. 10 | # -------------------------------------------------------------------------- 11 | 12 | from msrest.paging import Paged 13 | 14 | 15 | class StorageAccountPaged(Paged): 16 | """ 17 | A paging container for iterating over a list of :class:`StorageAccount ` object 18 | """ 19 | 20 | _attribute_map = { 21 | 'next_link': {'key': 'nextLink', 'type': 'str'}, 22 | 'current_page': {'key': 'value', 'type': '[StorageAccount]'} 23 | } 24 | 25 | def __init__(self, *args, **kwargs): 26 | 27 | super(StorageAccountPaged, self).__init__(*args, **kwargs) 28 | -------------------------------------------------------------------------------- /tests/storage_models/storage_account_regenerate_key_parameters.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | # -------------------------------------------------------------------------- 3 | # Copyright (c) Microsoft Corporation. All rights reserved. 4 | # Licensed under the MIT License. See License.txt in the project root for 5 | # license information. 6 | # 7 | # Code generated by Microsoft (R) AutoRest Code Generator. 8 | # Changes may cause incorrect behavior and will be lost if the code is 9 | # regenerated. 10 | # -------------------------------------------------------------------------- 11 | 12 | from msrest.serialization import Model 13 | 14 | 15 | class StorageAccountRegenerateKeyParameters(Model): 16 | """The parameters used to regenerate the storage account key. 17 | 18 | :param key_name: 19 | :type key_name: str 20 | """ 21 | 22 | _validation = { 23 | 'key_name': {'required': True}, 24 | } 25 | 26 | _attribute_map = { 27 | 'key_name': {'key': 'keyName', 'type': 'str'}, 28 | } 29 | 30 | def __init__(self, key_name): 31 | self.key_name = key_name 32 | -------------------------------------------------------------------------------- /tests/storage_models/storage_account_update_parameters.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | # -------------------------------------------------------------------------- 3 | # Copyright (c) Microsoft Corporation. All rights reserved. 4 | # Licensed under the MIT License. See License.txt in the project root for 5 | # license information. 6 | # 7 | # Code generated by Microsoft (R) AutoRest Code Generator. 8 | # Changes may cause incorrect behavior and will be lost if the code is 9 | # regenerated. 10 | # -------------------------------------------------------------------------- 11 | 12 | from msrest.serialization import Model 13 | 14 | 15 | class StorageAccountUpdateParameters(Model): 16 | """The parameters that can be provided when updating the storage account 17 | properties. 18 | 19 | :param sku: Gets or sets the SKU name. Note that the SKU name cannot be 20 | updated to Standard_ZRS or Premium_LRS, nor can accounts of those sku 21 | names be updated to any other value. 22 | :type sku: :class:`Sku ` 23 | :param tags: Gets or sets a list of key value pairs that describe the 24 | resource. These tags can be used in viewing and grouping this resource 25 | (across resource groups). A maximum of 15 tags can be provided for a 26 | resource. Each tag must have a key no greater in length than 128 27 | characters and a value no greater in length than 256 characters. 28 | :type tags: dict 29 | :param custom_domain: Custom domain assigned to the storage account by the 30 | user. Name is the CNAME source. Only one custom domain is supported per 31 | storage account at this time. To clear the existing custom domain, use an 32 | empty string for the custom domain name property. 33 | :type custom_domain: :class:`CustomDomain 34 | ` 35 | :param encryption: Provides the encryption settings on the account. The 36 | default setting is unencrypted. 37 | :type encryption: :class:`Encryption 38 | ` 39 | :param access_tier: Required for storage accounts where kind = 40 | BlobStorage. The access tier used for billing. Possible values include: 41 | 'Hot', 'Cool' 42 | :type access_tier: str or :class:`AccessTier 43 | ` 44 | :param enable_https_traffic_only: Allows https traffic only to storage 45 | service if sets to true. Default value: False . 46 | :type enable_https_traffic_only: bool 47 | """ 48 | 49 | _attribute_map = { 50 | 'sku': {'key': 'sku', 'type': 'Sku'}, 51 | 'tags': {'key': 'tags', 'type': '{str}'}, 52 | 'custom_domain': {'key': 'properties.customDomain', 'type': 'CustomDomain'}, 53 | 'encryption': {'key': 'properties.encryption', 'type': 'Encryption'}, 54 | 'access_tier': {'key': 'properties.accessTier', 'type': 'AccessTier'}, 55 | 'enable_https_traffic_only': {'key': 'properties.supportsHttpsTrafficOnly', 'type': 'bool'}, 56 | } 57 | 58 | def __init__(self, sku=None, tags=None, custom_domain=None, encryption=None, access_tier=None, enable_https_traffic_only=False): 59 | self.sku = sku 60 | self.tags = tags 61 | self.custom_domain = custom_domain 62 | self.encryption = encryption 63 | self.access_tier = access_tier 64 | self.enable_https_traffic_only = enable_https_traffic_only 65 | -------------------------------------------------------------------------------- /tests/storage_models/storage_management_client_enums.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | # -------------------------------------------------------------------------- 3 | # Copyright (c) Microsoft Corporation. All rights reserved. 4 | # Licensed under the MIT License. See License.txt in the project root for 5 | # license information. 6 | # 7 | # Code generated by Microsoft (R) AutoRest Code Generator. 8 | # Changes may cause incorrect behavior and will be lost if the code is 9 | # regenerated. 10 | # -------------------------------------------------------------------------- 11 | 12 | from enum import Enum 13 | 14 | 15 | class Reason(Enum): 16 | 17 | account_name_invalid = "AccountNameInvalid" 18 | already_exists = "AlreadyExists" 19 | 20 | 21 | class SkuName(Enum): 22 | 23 | standard_lrs = "Standard_LRS" 24 | standard_grs = "Standard_GRS" 25 | standard_ragrs = "Standard_RAGRS" 26 | standard_zrs = "Standard_ZRS" 27 | premium_lrs = "Premium_LRS" 28 | 29 | 30 | class SkuTier(Enum): 31 | 32 | standard = "Standard" 33 | premium = "Premium" 34 | 35 | 36 | class AccessTier(Enum): 37 | 38 | hot = "Hot" 39 | cool = "Cool" 40 | 41 | 42 | class Kind(Enum): 43 | 44 | storage = "Storage" 45 | blob_storage = "BlobStorage" 46 | 47 | 48 | class ProvisioningState(Enum): 49 | 50 | creating = "Creating" 51 | resolving_dns = "ResolvingDNS" 52 | succeeded = "Succeeded" 53 | 54 | 55 | class AccountStatus(Enum): 56 | 57 | available = "available" 58 | unavailable = "unavailable" 59 | 60 | 61 | class KeyPermission(Enum): 62 | 63 | read = "Read" 64 | full = "Full" 65 | 66 | 67 | class UsageUnit(Enum): 68 | 69 | count = "Count" 70 | bytes = "Bytes" 71 | seconds = "Seconds" 72 | percent = "Percent" 73 | counts_per_second = "CountsPerSecond" 74 | bytes_per_second = "BytesPerSecond" 75 | 76 | 77 | class HttpProtocol(Enum): 78 | 79 | httpshttp = "https,http" 80 | https = "https" 81 | -------------------------------------------------------------------------------- /tests/storage_models/usage.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | # -------------------------------------------------------------------------- 3 | # Copyright (c) Microsoft Corporation. All rights reserved. 4 | # Licensed under the MIT License. See License.txt in the project root for 5 | # license information. 6 | # 7 | # Code generated by Microsoft (R) AutoRest Code Generator. 8 | # Changes may cause incorrect behavior and will be lost if the code is 9 | # regenerated. 10 | # -------------------------------------------------------------------------- 11 | 12 | from msrest.serialization import Model 13 | 14 | 15 | class Usage(Model): 16 | """Describes Storage Resource Usage. 17 | 18 | Variables are only populated by the server, and will be ignored when 19 | sending a request. 20 | 21 | :ivar unit: Gets the unit of measurement. Possible values include: 22 | 'Count', 'Bytes', 'Seconds', 'Percent', 'CountsPerSecond', 23 | 'BytesPerSecond' 24 | :vartype unit: str or :class:`UsageUnit 25 | ` 26 | :ivar current_value: Gets the current count of the allocated resources in 27 | the subscription. 28 | :vartype current_value: int 29 | :ivar limit: Gets the maximum count of the resources that can be allocated 30 | in the subscription. 31 | :vartype limit: int 32 | :ivar name: Gets the name of the type of usage. 33 | :vartype name: :class:`UsageName 34 | ` 35 | """ 36 | 37 | _validation = { 38 | 'unit': {'readonly': True}, 39 | 'current_value': {'readonly': True}, 40 | 'limit': {'readonly': True}, 41 | 'name': {'readonly': True}, 42 | } 43 | 44 | _attribute_map = { 45 | 'unit': {'key': 'unit', 'type': 'UsageUnit'}, 46 | 'current_value': {'key': 'currentValue', 'type': 'int'}, 47 | 'limit': {'key': 'limit', 'type': 'int'}, 48 | 'name': {'key': 'name', 'type': 'UsageName'}, 49 | } 50 | 51 | def __init__(self): 52 | self.unit = None 53 | self.current_value = None 54 | self.limit = None 55 | self.name = None 56 | -------------------------------------------------------------------------------- /tests/storage_models/usage_name.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | # -------------------------------------------------------------------------- 3 | # Copyright (c) Microsoft Corporation. All rights reserved. 4 | # Licensed under the MIT License. See License.txt in the project root for 5 | # license information. 6 | # 7 | # Code generated by Microsoft (R) AutoRest Code Generator. 8 | # Changes may cause incorrect behavior and will be lost if the code is 9 | # regenerated. 10 | # -------------------------------------------------------------------------- 11 | 12 | from msrest.serialization import Model 13 | 14 | 15 | class UsageName(Model): 16 | """The usage names that can be used; currently limited to StorageAccount. 17 | 18 | Variables are only populated by the server, and will be ignored when 19 | sending a request. 20 | 21 | :ivar value: Gets a string describing the resource name. 22 | :vartype value: str 23 | :ivar localized_value: Gets a localized string describing the resource 24 | name. 25 | :vartype localized_value: str 26 | """ 27 | 28 | _validation = { 29 | 'value': {'readonly': True}, 30 | 'localized_value': {'readonly': True}, 31 | } 32 | 33 | _attribute_map = { 34 | 'value': {'key': 'value', 'type': 'str'}, 35 | 'localized_value': {'key': 'localizedValue', 'type': 'str'}, 36 | } 37 | 38 | def __init__(self): 39 | self.value = None 40 | self.localized_value = None 41 | -------------------------------------------------------------------------------- /tests/storage_models/usage_paged.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | # -------------------------------------------------------------------------- 3 | # Copyright (c) Microsoft Corporation. All rights reserved. 4 | # Licensed under the MIT License. See License.txt in the project root for 5 | # license information. 6 | # 7 | # Code generated by Microsoft (R) AutoRest Code Generator. 8 | # Changes may cause incorrect behavior and will be lost if the code is 9 | # regenerated. 10 | # -------------------------------------------------------------------------- 11 | 12 | from msrest.paging import Paged 13 | 14 | 15 | class UsagePaged(Paged): 16 | """ 17 | A paging container for iterating over a list of :class:`Usage ` object 18 | """ 19 | 20 | _attribute_map = { 21 | 'next_link': {'key': 'nextLink', 'type': 'str'}, 22 | 'current_page': {'key': 'value', 'type': '[Usage]'} 23 | } 24 | 25 | def __init__(self, *args, **kwargs): 26 | 27 | super(UsagePaged, self).__init__(*args, **kwargs) 28 | -------------------------------------------------------------------------------- /tests/test_auth.py: -------------------------------------------------------------------------------- 1 | #-------------------------------------------------------------------------- 2 | # 3 | # Copyright (c) Microsoft Corporation. All rights reserved. 4 | # 5 | # The MIT License (MIT) 6 | # 7 | # Permission is hereby granted, free of charge, to any person obtaining a copy 8 | # of this software and associated documentation files (the ""Software""), to deal 9 | # in the Software without restriction, including without limitation the rights 10 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | # copies of the Software, and to permit persons to whom the Software is 12 | # furnished to do so, subject to the following conditions: 13 | # 14 | # The above copyright notice and this permission notice shall be included in 15 | # all copies or substantial portions of the Software. 16 | # 17 | # THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | # THE SOFTWARE. 24 | # 25 | #-------------------------------------------------------------------------- 26 | 27 | import os 28 | import sys 29 | import json 30 | import isodate 31 | from datetime import datetime 32 | import base64 33 | from base64 import b64decode 34 | import unittest 35 | try: 36 | from unittest import mock 37 | except ImportError: 38 | import mock 39 | 40 | from msrest.authentication import ( 41 | BasicAuthentication, 42 | BasicTokenAuthentication, 43 | OAuthTokenAuthentication, 44 | ApiKeyCredentials, 45 | CognitiveServicesCredentials, 46 | TopicCredentials, 47 | DomainCredentials 48 | ) 49 | 50 | from requests import Request, PreparedRequest 51 | 52 | 53 | class TestAuthentication(unittest.TestCase): 54 | 55 | def setUp(self): 56 | 57 | self.request = mock.create_autospec(Request) 58 | self.request.headers = {} 59 | self.request.cookies = {} 60 | self.request.auth = None 61 | self.request.url = "http://my_endpoint.com" 62 | self.request.method = 'GET' 63 | self.request.files = None 64 | self.request.data = None 65 | self.request.json = None 66 | self.request.params = {} 67 | self.request.hooks = {} 68 | 69 | return super(TestAuthentication, self).setUp() 70 | 71 | def test_basic_auth(self): 72 | 73 | basic = BasicAuthentication("username", "password") 74 | session = basic.signed_session() 75 | 76 | req = session.auth(self.request) 77 | self.assertTrue('Authorization' in req.headers) 78 | self.assertTrue(req.headers['Authorization'].startswith('Basic ')) 79 | 80 | def test_basic_token_auth(self): 81 | 82 | token = { 83 | 'access_token': '123456789' 84 | } 85 | basic = BasicTokenAuthentication(token) 86 | basic.set_token() # Just check that this does not raise 87 | session = basic.signed_session() 88 | 89 | req = session.prepare_request(self.request) 90 | assert 'Authorization' in req.headers 91 | assert req.headers['Authorization'] == 'Bearer 123456789' 92 | 93 | def test_token_auth(self): 94 | 95 | token = { 96 | 'access_token': '123456789' 97 | } 98 | auth = OAuthTokenAuthentication("client_id", token) 99 | session = auth.signed_session() 100 | 101 | request = PreparedRequest() 102 | request.prepare("GET", "https://example.org") 103 | session.auth(request) 104 | assert request.headers == {'Authorization': 'Bearer 123456789'} 105 | 106 | def test_apikey_auth(self): 107 | auth = ApiKeyCredentials( 108 | in_headers={ 109 | 'testheader' : 'testheadervalue' 110 | } 111 | ) 112 | session = auth.signed_session() 113 | prep_req = session.prepare_request(self.request) 114 | self.assertDictContainsSubset({'testheader' : 'testheadervalue'}, prep_req.headers) 115 | 116 | auth = ApiKeyCredentials( 117 | in_query={ 118 | 'testquery' : 'testparamvalue' 119 | } 120 | ) 121 | session = auth.signed_session() 122 | prep_req = session.prepare_request(self.request) 123 | assert "testquery=testparamvalue" in prep_req.path_url 124 | 125 | def test_cs_auth(self): 126 | auth = CognitiveServicesCredentials("mysubkey") 127 | session = auth.signed_session() 128 | prep_req = session.prepare_request(self.request) 129 | self.assertDictContainsSubset({'Ocp-Apim-Subscription-Key' : 'mysubkey'}, prep_req.headers) 130 | 131 | def test_eventgrid_auth(self): 132 | auth = TopicCredentials("mytopickey") 133 | session = auth.signed_session() 134 | prep_req = session.prepare_request(self.request) 135 | self.assertDictContainsSubset({'aeg-sas-key' : 'mytopickey'}, prep_req.headers) 136 | 137 | def test_eventgrid_domain_auth(self): 138 | auth = DomainCredentials("mydomainkey") 139 | session = auth.signed_session() 140 | prep_req = session.prepare_request(self.request) 141 | self.assertDictContainsSubset({'aeg-sas-key' : 'mydomainkey'}, prep_req.headers) 142 | 143 | if __name__ == '__main__': 144 | unittest.main() 145 | 146 | -------------------------------------------------------------------------------- /tests/test_exceptions.py: -------------------------------------------------------------------------------- 1 | #-------------------------------------------------------------------------- 2 | # 3 | # Copyright (c) Microsoft Corporation. All rights reserved. 4 | # 5 | # The MIT License (MIT) 6 | # 7 | # Permission is hereby granted, free of charge, to any person obtaining a copy 8 | # of this software and associated documentation files (the ""Software""), to deal 9 | # in the Software without restriction, including without limitation the rights 10 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | # copies of the Software, and to permit persons to whom the Software is 12 | # furnished to do so, subject to the following conditions: 13 | # 14 | # The above copyright notice and this permission notice shall be included in 15 | # all copies or substantial portions of the Software. 16 | # 17 | # THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | # THE SOFTWARE. 24 | # 25 | #-------------------------------------------------------------------------- 26 | import json 27 | import unittest 28 | try: 29 | from unittest import mock 30 | except ImportError: 31 | import mock 32 | 33 | import requests 34 | 35 | from msrest.serialization import Model, Deserializer 36 | from msrest.exceptions import HttpOperationError 37 | 38 | 39 | class TestExceptions(unittest.TestCase): 40 | 41 | def test_request_exception(self): 42 | def raise_for_status(): 43 | raise requests.RequestException() 44 | 45 | deserializer = Deserializer() 46 | response = mock.create_autospec(requests.Response) 47 | response.raise_for_status = raise_for_status 48 | response.reason = "TESTING" 49 | 50 | excep = HttpOperationError(deserializer, response) 51 | 52 | self.assertIn("TESTING", str(excep)) 53 | self.assertIn("Operation returned an invalid status code", str(excep)) 54 | 55 | def test_custom_exception(self): 56 | 57 | class ErrorResponse(Model): 58 | _attribute_map = { 59 | 'error': {'key': 'error', 'type': 'ErrorDetails'}, 60 | } 61 | def __init__(self, error=None): 62 | self.error = error 63 | 64 | 65 | class ErrorResponseException(HttpOperationError): 66 | def __init__(self, deserialize, response, *args): 67 | super(ErrorResponseException, self).__init__(deserialize, response, 'ErrorResponse', *args) 68 | 69 | class ErrorDetails(Model): 70 | _validation = { 71 | 'code': {'readonly': True}, 72 | 'message': {'readonly': True}, 73 | 'target': {'readonly': True}, 74 | } 75 | 76 | _attribute_map = { 77 | 'code': {'key': 'code', 'type': 'str'}, 78 | 'message': {'key': 'message', 'type': 'str'}, 79 | 'target': {'key': 'target', 'type': 'str'}, 80 | } 81 | 82 | def __init__(self): 83 | self.code = None 84 | self.message = None 85 | self.target = None 86 | 87 | deserializer = Deserializer({ 88 | 'ErrorResponse': ErrorResponse, 89 | 'ErrorDetails': ErrorDetails 90 | }) 91 | 92 | response = requests.Response() 93 | response._content_consumed = True 94 | response._content = json.dumps( 95 | { 96 | "error": { 97 | "code": "NotOptedIn", 98 | "message": "You are not allowed to download invoices. Please contact your account administrator to turn on access in the management portal for allowing to download invoices through the API." 99 | } 100 | } 101 | ).encode('utf-8') 102 | response.headers = {"content-type": "application/json; charset=utf8"} 103 | 104 | excep = ErrorResponseException(deserializer, response) 105 | 106 | self.assertIn("NotOptedIn", str(excep)) 107 | self.assertIn("You are not allowed to download invoices", str(excep)) 108 | -------------------------------------------------------------------------------- /tests/test_paging.py: -------------------------------------------------------------------------------- 1 | #-------------------------------------------------------------------------- 2 | # 3 | # Copyright (c) Microsoft Corporation. All rights reserved. 4 | # 5 | # The MIT License (MIT) 6 | # 7 | # Permission is hereby granted, free of charge, to any person obtaining a copy 8 | # of this software and associated documentation files (the ""Software""), to deal 9 | # in the Software without restriction, including without limitation the rights 10 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | # copies of the Software, and to permit persons to whom the Software is 12 | # furnished to do so, subject to the following conditions: 13 | # 14 | # The above copyright notice and this permission notice shall be included in 15 | # all copies or substantial portions of the Software. 16 | # 17 | # THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | # THE SOFTWARE. 24 | # 25 | #-------------------------------------------------------------------------- 26 | 27 | import unittest 28 | 29 | from msrest.paging import Paged 30 | 31 | class FakePaged(Paged): 32 | _attribute_map = { 33 | 'next_link': {'key': 'nextLink', 'type': 'str'}, 34 | 'current_page': {'key': 'value', 'type': '[str]'} 35 | } 36 | 37 | def __init__(self, *args, **kwargs): 38 | super(FakePaged, self).__init__(*args, **kwargs) 39 | 40 | class TestPaging(unittest.TestCase): 41 | 42 | def test_basic_paging(self): 43 | 44 | def internal_paging(next_link=None, raw=False): 45 | if not next_link: 46 | return { 47 | 'nextLink': 'page2', 48 | 'value': ['value1.0', 'value1.1'] 49 | } 50 | else: 51 | return { 52 | 'nextLink': None, 53 | 'value': ['value2.0', 'value2.1'] 54 | } 55 | 56 | deserialized = FakePaged(internal_paging, {}) 57 | result_iterated = list(deserialized) 58 | self.assertListEqual( 59 | ['value1.0', 'value1.1', 'value2.0', 'value2.1'], 60 | result_iterated 61 | ) 62 | 63 | def test_advance_paging(self): 64 | 65 | def internal_paging(next_link=None, raw=False): 66 | if not next_link: 67 | return { 68 | 'nextLink': 'page2', 69 | 'value': ['value1.0', 'value1.1'] 70 | } 71 | else: 72 | return { 73 | 'nextLink': None, 74 | 'value': ['value2.0', 'value2.1'] 75 | } 76 | 77 | deserialized = FakePaged(internal_paging, {}) 78 | page1 = deserialized.advance_page() 79 | self.assertListEqual( 80 | ['value1.0', 'value1.1'], 81 | page1 82 | ) 83 | page2 = deserialized.advance_page() 84 | self.assertListEqual( 85 | ['value2.0', 'value2.1'], 86 | page2 87 | ) 88 | with self.assertRaises(StopIteration): 89 | deserialized.advance_page() 90 | 91 | def test_get_paging(self): 92 | 93 | def internal_paging(next_link=None, raw=False): 94 | if not next_link: 95 | return { 96 | 'nextLink': 'page2', 97 | 'value': ['value1.0', 'value1.1'] 98 | } 99 | elif next_link == 'page2': 100 | return { 101 | 'nextLink': 'page3', 102 | 'value': ['value2.0', 'value2.1'] 103 | } 104 | else: 105 | return { 106 | 'nextLink': None, 107 | 'value': ['value3.0', 'value3.1'] 108 | } 109 | 110 | deserialized = FakePaged(internal_paging, {}) 111 | page2 = deserialized.get('page2') 112 | self.assertListEqual( 113 | ['value2.0', 'value2.1'], 114 | page2 115 | ) 116 | page3 = deserialized.get('page3') 117 | self.assertListEqual( 118 | ['value3.0', 'value3.1'], 119 | page3 120 | ) 121 | 122 | def test_reset_paging(self): 123 | 124 | def internal_paging(next_link=None, raw=False): 125 | if not next_link: 126 | return { 127 | 'nextLink': 'page2', 128 | 'value': ['value1.0', 'value1.1'] 129 | } 130 | else: 131 | return { 132 | 'nextLink': None, 133 | 'value': ['value2.0', 'value2.1'] 134 | } 135 | 136 | deserialized = FakePaged(internal_paging, {}) 137 | deserialized.reset() 138 | result_iterated = list(deserialized) 139 | self.assertListEqual( 140 | ['value1.0', 'value1.1', 'value2.0', 'value2.1'], 141 | result_iterated 142 | ) 143 | 144 | deserialized = FakePaged(internal_paging, {}) 145 | # Push the iterator to the last element 146 | for element in deserialized: 147 | if element == "value2.0": 148 | break 149 | deserialized.reset() 150 | result_iterated = list(deserialized) 151 | self.assertListEqual( 152 | ['value1.0', 'value1.1', 'value2.0', 'value2.1'], 153 | result_iterated 154 | ) 155 | 156 | def test_none_value(self): 157 | def internal_paging(next_link=None, raw=False): 158 | return { 159 | 'nextLink': None, 160 | 'value': None 161 | } 162 | 163 | deserialized = FakePaged(internal_paging, {}) 164 | result_iterated = list(deserialized) 165 | self.assertEqual(len(result_iterated), 0) 166 | -------------------------------------------------------------------------------- /tests/test_pipeline.py: -------------------------------------------------------------------------------- 1 | #-------------------------------------------------------------------------- 2 | # 3 | # Copyright (c) Microsoft Corporation. All rights reserved. 4 | # 5 | # The MIT License (MIT) 6 | # 7 | # Permission is hereby granted, free of charge, to any person obtaining a copy 8 | # of this software and associated documentation files (the ""Software""), to deal 9 | # in the Software without restriction, including without limitation the rights 10 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | # copies of the Software, and to permit persons to whom the Software is 12 | # furnished to do so, subject to the following conditions: 13 | # 14 | # The above copyright notice and this permission notice shall be included in 15 | # all copies or substantial portions of the Software. 16 | # 17 | # THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | # THE SOFTWARE. 24 | # 25 | #-------------------------------------------------------------------------- 26 | 27 | import json 28 | import requests 29 | import datetime 30 | from enum import Enum 31 | import unittest 32 | try: 33 | from unittest import mock 34 | except ImportError: 35 | import mock 36 | import xml.etree.ElementTree as ET 37 | import sys 38 | 39 | import pytest 40 | 41 | from msrest.universal_http import ( 42 | ClientRequest, 43 | ) 44 | from msrest.pipeline import ( 45 | ClientRawResponse, 46 | SansIOHTTPPolicy, 47 | Pipeline, 48 | HTTPSender 49 | ) 50 | 51 | from msrest import Configuration 52 | 53 | 54 | def test_sans_io_exception(): 55 | class BrokenSender(HTTPSender): 56 | def send(self, request, **config): 57 | raise ValueError("Broken") 58 | 59 | def __exit__(self, exc_type, exc_value, traceback): 60 | """Raise any exception triggered within the runtime context.""" 61 | return None 62 | 63 | pipeline = Pipeline([SansIOHTTPPolicy()], BrokenSender()) 64 | 65 | req = ClientRequest('GET', '/') 66 | with pytest.raises(ValueError): 67 | pipeline.run(req) 68 | 69 | class SwapExec(SansIOHTTPPolicy): 70 | def on_exception(self, requests, **kwargs): 71 | exc_type, exc_value, exc_traceback = sys.exc_info() 72 | raise NotImplementedError(exc_value) 73 | 74 | pipeline = Pipeline([SwapExec()], BrokenSender()) 75 | with pytest.raises(NotImplementedError): 76 | pipeline.run(req) 77 | 78 | 79 | class TestClientRequest(unittest.TestCase): 80 | 81 | def test_request_data(self): 82 | 83 | request = ClientRequest('GET', '/') 84 | data = "Lots of dataaaa" 85 | request.add_content(data) 86 | 87 | self.assertEqual(request.data, json.dumps(data)) 88 | self.assertEqual(request.headers.get('Content-Length'), '17') 89 | 90 | def test_request_xml(self): 91 | request = ClientRequest('GET', '/') 92 | data = ET.Element("root") 93 | request.add_content(data) 94 | 95 | assert request.data == b"\n" 96 | 97 | def test_request_url_with_params(self): 98 | 99 | request = ClientRequest('GET', '/') 100 | request.url = "a/b/c?t=y" 101 | request.format_parameters({'g': 'h'}) 102 | 103 | self.assertIn(request.url, [ 104 | 'a/b/c?g=h&t=y', 105 | 'a/b/c?t=y&g=h' 106 | ]) 107 | 108 | class TestClientResponse(unittest.TestCase): 109 | 110 | class Colors(Enum): 111 | red = 'red' 112 | blue = 'blue' 113 | 114 | def test_raw_response(self): 115 | 116 | response = mock.create_autospec(requests.Response) 117 | response.headers = {} 118 | response.headers["my-test"] = '1999-12-31T23:59:59-23:59' 119 | response.headers["colour"] = "red" 120 | 121 | raw = ClientRawResponse([], response) 122 | 123 | raw.add_headers({'my-test': 'iso-8601', 124 | 'another_header': 'str', 125 | 'colour': TestClientResponse.Colors}) 126 | self.assertIsInstance(raw.headers['my-test'], datetime.datetime) 127 | 128 | if __name__ == '__main__': 129 | unittest.main() 130 | -------------------------------------------------------------------------------- /tests/test_polling.py: -------------------------------------------------------------------------------- 1 | #-------------------------------------------------------------------------- 2 | # 3 | # Copyright (c) Microsoft Corporation. All rights reserved. 4 | # 5 | # The MIT License (MIT) 6 | # 7 | # Permission is hereby granted, free of charge, to any person obtaining a copy 8 | # of this software and associated documentation files (the ""Software""), to deal 9 | # in the Software without restriction, including without limitation the rights 10 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | # copies of the Software, and to permit persons to whom the Software is 12 | # furnished to do so, subject to the following conditions: 13 | # 14 | # The above copyright notice and this permission notice shall be included in 15 | # all copies or substantial portions of the Software. 16 | # 17 | # THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | # THE SOFTWARE. 24 | # 25 | #-------------------------------------------------------------------------- 26 | import time 27 | try: 28 | from unittest import mock 29 | except ImportError: 30 | import mock 31 | 32 | import pytest 33 | 34 | from msrest.polling import * 35 | from msrest.service_client import ServiceClient 36 | from msrest.serialization import Model 37 | from msrest.configuration import Configuration 38 | 39 | 40 | def test_abc_polling(): 41 | abc_polling = PollingMethod() 42 | 43 | with pytest.raises(NotImplementedError): 44 | abc_polling.initialize(None, None, None) 45 | 46 | with pytest.raises(NotImplementedError): 47 | abc_polling.run() 48 | 49 | with pytest.raises(NotImplementedError): 50 | abc_polling.status() 51 | 52 | with pytest.raises(NotImplementedError): 53 | abc_polling.finished() 54 | 55 | with pytest.raises(NotImplementedError): 56 | abc_polling.resource() 57 | 58 | def test_no_polling(): 59 | no_polling = NoPolling() 60 | 61 | initial_response = "initial response" 62 | def deserialization_cb(response): 63 | assert response == initial_response 64 | return "Treated: "+response 65 | 66 | no_polling.initialize(None, initial_response, deserialization_cb) 67 | no_polling.run() # Should no raise and do nothing 68 | assert no_polling.status() == "succeeded" 69 | assert no_polling.finished() 70 | assert no_polling.resource() == "Treated: "+initial_response 71 | 72 | 73 | class PollingTwoSteps(PollingMethod): 74 | """An empty poller that returns the deserialized initial response. 75 | """ 76 | def __init__(self, sleep=0): 77 | self._initial_response = None 78 | self._deserialization_callback = None 79 | self._sleep = sleep 80 | 81 | def initialize(self, _, initial_response, deserialization_callback): 82 | self._initial_response = initial_response 83 | self._deserialization_callback = deserialization_callback 84 | self._finished = False 85 | 86 | def run(self): 87 | """Empty run, no polling. 88 | """ 89 | self._finished = True 90 | time.sleep(self._sleep) # Give me time to add callbacks! 91 | 92 | def status(self): 93 | """Return the current status as a string. 94 | :rtype: str 95 | """ 96 | return "succeeded" if self._finished else "running" 97 | 98 | def finished(self): 99 | """Is this polling finished? 100 | :rtype: bool 101 | """ 102 | return self._finished 103 | 104 | def resource(self): 105 | return self._deserialization_callback(self._initial_response) 106 | 107 | @pytest.fixture 108 | def client(): 109 | # We need a ServiceClient instance, but the poller itself don't use it, so we don't need 110 | # Something functional 111 | return ServiceClient(None, Configuration("http://example.org")) 112 | 113 | def test_poller(client): 114 | 115 | # Same the poller itself doesn't care about the initial_response, and there is no type constraint here 116 | initial_response = "Initial response" 117 | 118 | # Same for deserialization_callback, just pass to the polling_method 119 | def deserialization_callback(response): 120 | assert response == initial_response 121 | return "Treated: "+response 122 | 123 | method = NoPolling() 124 | 125 | poller = LROPoller(client, initial_response, deserialization_callback, method) 126 | 127 | done_cb = mock.MagicMock() 128 | poller.add_done_callback(done_cb) 129 | 130 | result = poller.result() 131 | assert poller.done() 132 | assert result == "Treated: "+initial_response 133 | assert poller.status() == "succeeded" 134 | done_cb.assert_called_once_with(method) 135 | 136 | # Test with a basic Model 137 | poller = LROPoller(client, initial_response, Model, method) 138 | assert poller._polling_method._deserialization_callback == Model.deserialize 139 | 140 | # Test poller that method do a run 141 | method = PollingTwoSteps(sleep=1) 142 | poller = LROPoller(client, initial_response, deserialization_callback, method) 143 | 144 | done_cb = mock.MagicMock() 145 | done_cb2 = mock.MagicMock() 146 | poller.add_done_callback(done_cb) 147 | poller.remove_done_callback(done_cb2) 148 | 149 | result = poller.result() 150 | assert result == "Treated: "+initial_response 151 | assert poller.status() == "succeeded" 152 | done_cb.assert_called_once_with(method) 153 | done_cb2.assert_not_called() 154 | 155 | with pytest.raises(ValueError) as excinfo: 156 | poller.remove_done_callback(done_cb) 157 | assert "Process is complete" in str(excinfo.value) 158 | 159 | def test_broken_poller(client): 160 | 161 | with pytest.raises(ValueError): 162 | LROPoller(None, None, None, None) 163 | 164 | class NoPollingError(PollingTwoSteps): 165 | def run(self): 166 | raise ValueError("Something bad happened") 167 | 168 | initial_response = "Initial response" 169 | def deserialization_callback(response): 170 | return "Treated: "+response 171 | 172 | method = NoPollingError() 173 | poller = LROPoller(client, initial_response, deserialization_callback, method) 174 | 175 | with pytest.raises(ValueError) as excinfo: 176 | poller.result() 177 | assert "Something bad happened" in str(excinfo.value) 178 | -------------------------------------------------------------------------------- /tests/test_requests_universal.py: -------------------------------------------------------------------------------- 1 | #-------------------------------------------------------------------------- 2 | # 3 | # Copyright (c) Microsoft Corporation. All rights reserved. 4 | # 5 | # The MIT License (MIT) 6 | # 7 | # Permission is hereby granted, free of charge, to any person obtaining a copy 8 | # of this software and associated documentation files (the ""Software""), to deal 9 | # in the Software without restriction, including without limitation the rights 10 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | # copies of the Software, and to permit persons to whom the Software is 12 | # furnished to do so, subject to the following conditions: 13 | # 14 | # The above copyright notice and this permission notice shall be included in 15 | # all copies or substantial portions of the Software. 16 | # 17 | # THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | # THE SOFTWARE. 24 | # 25 | #-------------------------------------------------------------------------- 26 | import concurrent.futures 27 | 28 | from requests.adapters import HTTPAdapter 29 | 30 | from msrest.universal_http import ( 31 | ClientRequest 32 | ) 33 | from msrest.universal_http.requests import ( 34 | BasicRequestsHTTPSender, 35 | RequestsHTTPSender, 36 | RequestHTTPSenderConfiguration 37 | ) 38 | 39 | def test_session_callback(): 40 | 41 | cfg = RequestHTTPSenderConfiguration() 42 | with RequestsHTTPSender(cfg) as driver: 43 | 44 | def callback(session, global_config, local_config, **kwargs): 45 | assert session is driver.session 46 | assert global_config is cfg 47 | assert local_config["test"] 48 | my_kwargs = kwargs.copy() 49 | my_kwargs.update({'used_callback': True}) 50 | return my_kwargs 51 | 52 | cfg.session_configuration_callback = callback 53 | 54 | request = ClientRequest('GET', 'http://127.0.0.1/') 55 | output_kwargs = driver._configure_send(request, **{"test": True}) 56 | assert output_kwargs['used_callback'] 57 | 58 | def test_max_retries_on_default_adapter(): 59 | # max_retries must be applied only on the default adapters of requests 60 | # If the user adds its own adapter, don't touch it 61 | cfg = RequestHTTPSenderConfiguration() 62 | max_retries = cfg.retry_policy() 63 | 64 | with RequestsHTTPSender(cfg) as driver: 65 | request = ClientRequest('GET', '/') 66 | driver.session.mount('"http://127.0.0.1/"', HTTPAdapter()) 67 | 68 | driver._configure_send(request) 69 | assert driver.session.adapters["http://"].max_retries is max_retries 70 | assert driver.session.adapters["https://"].max_retries is max_retries 71 | assert driver.session.adapters['"http://127.0.0.1/"'].max_retries is not max_retries 72 | 73 | 74 | def test_threading_basic_requests(): 75 | # Basic should have the session for all threads, it's why it's not recommended 76 | sender = BasicRequestsHTTPSender() 77 | main_thread_session = sender.session 78 | 79 | def thread_body(local_sender): 80 | # Should be the same session 81 | assert local_sender.session is main_thread_session 82 | 83 | return True 84 | 85 | with concurrent.futures.ThreadPoolExecutor(max_workers=1) as executor: 86 | future = executor.submit(thread_body, sender) 87 | assert future.result() 88 | 89 | def test_threading_cfg_requests(): 90 | cfg = RequestHTTPSenderConfiguration() 91 | 92 | # The one with conf however, should have one session per thread automatically 93 | sender = RequestsHTTPSender(cfg) 94 | main_thread_session = sender.session 95 | 96 | # Check that this main session is patched 97 | assert main_thread_session.resolve_redirects.is_msrest_patched 98 | 99 | def thread_body(local_sender): 100 | # Should have it's own session 101 | assert local_sender.session is not main_thread_session 102 | # But should be patched as the main thread session 103 | assert local_sender.session.resolve_redirects.is_msrest_patched 104 | return True 105 | 106 | with concurrent.futures.ThreadPoolExecutor(max_workers=1) as executor: 107 | future = executor.submit(thread_body, sender) 108 | assert future.result() 109 | -------------------------------------------------------------------------------- /tests/test_universal_pipeline.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | #-------------------------------------------------------------------------- 3 | # 4 | # Copyright (c) Microsoft Corporation. All rights reserved. 5 | # 6 | # The MIT License (MIT) 7 | # 8 | # Permission is hereby granted, free of charge, to any person obtaining a copy 9 | # of this software and associated documentation files (the ""Software""), to deal 10 | # in the Software without restriction, including without limitation the rights 11 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | # copies of the Software, and to permit persons to whom the Software is 13 | # furnished to do so, subject to the following conditions: 14 | # 15 | # The above copyright notice and this permission notice shall be included in 16 | # all copies or substantial portions of the Software. 17 | # 18 | # THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | # THE SOFTWARE. 25 | # 26 | #-------------------------------------------------------------------------- 27 | try: 28 | from unittest import mock 29 | except ImportError: 30 | import mock 31 | 32 | import requests 33 | 34 | import pytest 35 | 36 | from msrest.exceptions import DeserializationError 37 | from msrest.universal_http import ( 38 | ClientRequest, 39 | ClientResponse, 40 | HTTPClientResponse, 41 | ) 42 | from msrest.universal_http.requests import RequestsClientResponse 43 | 44 | from msrest.pipeline import ( 45 | Response, 46 | Request 47 | ) 48 | from msrest.pipeline.universal import ( 49 | HTTPLogger, 50 | RawDeserializer, 51 | UserAgentPolicy 52 | ) 53 | 54 | def test_user_agent(): 55 | 56 | with mock.patch.dict('os.environ', {'AZURE_HTTP_USER_AGENT': "mytools"}): 57 | policy = UserAgentPolicy() 58 | assert policy.user_agent.endswith("mytools") 59 | 60 | request = ClientRequest('GET', 'http://127.0.0.1/') 61 | policy.on_request(Request(request)) 62 | assert request.headers["user-agent"].endswith("mytools") 63 | 64 | @mock.patch('msrest.http_logger._LOGGER') 65 | def test_no_log(mock_http_logger): 66 | universal_request = ClientRequest('GET', 'http://127.0.0.1/') 67 | request = Request(universal_request) 68 | http_logger = HTTPLogger() 69 | response = Response(request, ClientResponse(universal_request, None)) 70 | 71 | # By default, no log handler for HTTP 72 | http_logger.on_request(request) 73 | mock_http_logger.debug.assert_not_called() 74 | http_logger.on_response(request, response) 75 | mock_http_logger.debug.assert_not_called() 76 | mock_http_logger.reset_mock() 77 | 78 | # I can enable it per request 79 | http_logger.on_request(request, **{"enable_http_logger": True}) 80 | assert mock_http_logger.debug.call_count >= 1 81 | http_logger.on_response(request, response, **{"enable_http_logger": True}) 82 | assert mock_http_logger.debug.call_count >= 1 83 | mock_http_logger.reset_mock() 84 | 85 | # I can enable it per request (bool value should be honored) 86 | http_logger.on_request(request, **{"enable_http_logger": False}) 87 | mock_http_logger.debug.assert_not_called() 88 | http_logger.on_response(request, response, **{"enable_http_logger": False}) 89 | mock_http_logger.debug.assert_not_called() 90 | mock_http_logger.reset_mock() 91 | 92 | # I can enable it globally 93 | http_logger.enable_http_logger = True 94 | http_logger.on_request(request) 95 | assert mock_http_logger.debug.call_count >= 1 96 | http_logger.on_response(request, response) 97 | assert mock_http_logger.debug.call_count >= 1 98 | mock_http_logger.reset_mock() 99 | 100 | # I can enable it globally and override it locally 101 | http_logger.enable_http_logger = True 102 | http_logger.on_request(request, **{"enable_http_logger": False}) 103 | mock_http_logger.debug.assert_not_called() 104 | http_logger.on_response(request, response, **{"enable_http_logger": False}) 105 | mock_http_logger.debug.assert_not_called() 106 | mock_http_logger.reset_mock() 107 | 108 | 109 | def test_raw_deserializer(): 110 | raw_deserializer = RawDeserializer() 111 | 112 | def build_response(body, content_type=None): 113 | class MockResponse(HTTPClientResponse): 114 | def __init__(self, body, content_type): 115 | super(MockResponse, self).__init__(None, None) 116 | self._body = body 117 | if content_type: 118 | self.headers['content-type'] = content_type 119 | 120 | def body(self): 121 | return self._body 122 | return Response(None, MockResponse(body, content_type)) 123 | 124 | # I deserialize XML 125 | response = build_response(b"", content_type="application/xml") 126 | raw_deserializer.on_response(None, response, stream=False) 127 | result = response.context["deserialized_data"] 128 | assert result.tag == "groot" 129 | 130 | # The basic deserializer works with unicode XML 131 | result = raw_deserializer.deserialize_from_text(u'', content_type="application/xml") 132 | assert result.attrib["language"] == u"français" 133 | 134 | # Catch some weird situation where content_type is XML, but content is JSON 135 | response = build_response(b'{"ugly": true}', content_type="application/xml") 136 | raw_deserializer.on_response(None, response, stream=False) 137 | result = response.context["deserialized_data"] 138 | assert result["ugly"] is True 139 | 140 | # Be sure I catch the correct exception if it's neither XML nor JSON 141 | with pytest.raises(DeserializationError): 142 | response = build_response(b'gibberish', content_type="application/xml") 143 | raw_deserializer.on_response(None, response, stream=False) 144 | with pytest.raises(DeserializationError): 145 | response = build_response(b'{{gibberish}}', content_type="application/xml") 146 | raw_deserializer.on_response(None, response, stream=False) 147 | 148 | # Simple JSON 149 | response = build_response(b'{"success": true}', content_type="application/json") 150 | raw_deserializer.on_response(None, response, stream=False) 151 | result = response.context["deserialized_data"] 152 | assert result["success"] is True 153 | 154 | # Simple JSON with complex content_type 155 | response = build_response(b'{"success": true}', content_type="application/vnd.microsoft.appconfig.kv+json") 156 | raw_deserializer.on_response(None, response, stream=False) 157 | result = response.context["deserialized_data"] 158 | assert result["success"] is True 159 | 160 | # JSON with UTF-8 BOM 161 | response = build_response(b'\xef\xbb\xbf{"success": true}', content_type="application/json; charset=utf-8") 162 | raw_deserializer.on_response(None, response, stream=False) 163 | result = response.context["deserialized_data"] 164 | assert result["success"] is True 165 | 166 | # For compat, if no content-type, decode JSON 167 | response = build_response(b'"data"') 168 | raw_deserializer.on_response(None, response, stream=False) 169 | result = response.context["deserialized_data"] 170 | assert result == "data" 171 | 172 | # Try with a mock of requests 173 | 174 | req_response = requests.Response() 175 | req_response.headers["content-type"] = "application/json" 176 | req_response._content = b'{"success": true}' 177 | req_response._content_consumed = True 178 | response = Response(None, RequestsClientResponse(None, req_response)) 179 | 180 | raw_deserializer.on_response(None, response, stream=False) 181 | result = response.context["deserialized_data"] 182 | assert result["success"] is True 183 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist=py{36,37,38,39,310,311} 3 | skipsdist=True 4 | 5 | [testenv] 6 | setenv = 7 | PYTHONPATH = {toxinidir}:{toxinidir}/msrest 8 | PythonLogLevel=30 9 | deps= 10 | -rdev_requirements.txt 11 | commands_pre= 12 | autorest: bash ./autorest_setup.sh 13 | commands= 14 | pytest --cov=msrest tests/ 15 | autorest: pytest --cov=msrest --cov-append autorest.python/test/vanilla/ 16 | coverage report --fail-under=40 17 | coverage xml --ignore-errors # At this point, don't fail for "async" keyword in 2.7/3.4 18 | --------------------------------------------------------------------------------