├── .gitignore ├── .python-version ├── .travis.yml ├── LICENSE ├── README.rst ├── docs ├── Makefile ├── async_fixture.rst ├── development.rst ├── example.rst ├── fixtures.rst ├── index.rst ├── installation.rst ├── make.bat ├── reference.rst └── tips.rst ├── poetry.lock ├── pyproject.toml ├── pytest_sanic ├── __init__.py ├── plugin.py └── utils.py ├── setup.py └── tests ├── __init__.py ├── conftest.py ├── test_async.py ├── test_client.py ├── test_client_websocket.py ├── test_fixture.py ├── test_server.py └── test_simple_fixtures.py /.gitignore: -------------------------------------------------------------------------------- 1 | .Python 2 | .uranium 3 | bin 4 | include 5 | lib 6 | *.egg-info 7 | dist/ 8 | docs/_build 9 | .cache 10 | .coverage 11 | __pycache__ 12 | *.pyc 13 | .DS_Store 14 | access.log 15 | error.log 16 | pip-selfcheck.json 17 | .pytest_cache/ 18 | LICENSE.md -------------------------------------------------------------------------------- /.python-version: -------------------------------------------------------------------------------- 1 | 3.8.1 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: python 3 | python: 4 | - "3.8" 5 | install: 6 | - pip install poetry 7 | - pip install coveralls 8 | script: poetry install && poetry run pytest ./tests --cov pytest_sanic 9 | after_success: 10 | coveralls 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2017 Yun Xu 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | pytest-sanic 2 | ============ 3 | 4 | .. start-badges 5 | 6 | .. list-table:: 7 | :stub-columns: 1 8 | 9 | * - Build 10 | - | |travis| 11 | * - Docs 12 | - |docs| 13 | * - Package 14 | - | |version| |wheel| |supported-versions| |supported-implementations| 15 | 16 | .. |travis| image:: https://travis-ci.org/yunstanford/pytest-sanic.svg?branch=master 17 | :alt: Travis-CI Build Status 18 | :target: https://travis-ci.org/yunstanford/pytest-sanic 19 | 20 | .. |docs| image:: https://readthedocs.org/projects/pytest-sanic/badge/?style=flat 21 | :target: https://readthedocs.org/projects/pytest-sanic 22 | :alt: Documentation Status 23 | 24 | .. |version| image:: https://img.shields.io/pypi/v/pytest-sanic.svg 25 | :alt: PyPI Package latest release 26 | :target: https://pypi.python.org/pypi/pytest-sanic 27 | 28 | .. |wheel| image:: https://img.shields.io/pypi/wheel/pytest-sanic.svg 29 | :alt: PyPI Wheel 30 | :target: https://pypi.python.org/pypi/pytest-sanic 31 | 32 | .. |supported-versions| image:: https://img.shields.io/pypi/pyversions/pytest-sanic.svg 33 | :alt: Supported versions 34 | :target: https://pypi.python.org/pypi/pytest-sanic 35 | 36 | .. |supported-implementations| image:: https://img.shields.io/pypi/implementation/pytest-sanic.svg 37 | :alt: Supported implementations 38 | :target: https://pypi.python.org/pypi/pytest-sanic 39 | 40 | .. end-badges 41 | 42 | A pytest plugin for `Sanic `_. It helps you to test your code asynchronously. 43 | 44 | This plugin provides: 45 | 46 | * very easy testing with async coroutines 47 | * common and useful fixtures 48 | * asynchronous fixture support 49 | * test_client/sanic_client for Sanic application 50 | * test_server for Sanic application 51 | 52 | 53 | You can find out more here: 54 | 55 | http://pytest-sanic.readthedocs.io/en/latest/ 56 | 57 | 58 | Releases and change logs can be found here: 59 | 60 | https://github.com/yunstanford/pytest-sanic/releases 61 | 62 | 63 | ------- 64 | Install 65 | ------- 66 | 67 | .. code:: 68 | 69 | pip install pytest-sanic 70 | 71 | 72 | ----------- 73 | Quick Start 74 | ----------- 75 | 76 | You don't have to load ``pytest-sanic`` explicitly. ``pytest`` will do it for you. 77 | 78 | You can set up a fixture for your ``app`` like this: 79 | 80 | .. code-block:: python 81 | 82 | import pytest 83 | from .app import create_app 84 | 85 | @pytest.yield_fixture 86 | def app(): 87 | app = create_app(test_config, **params) 88 | yield app 89 | 90 | This ``app`` fixture can then be used from tests: 91 | 92 | .. code-block:: python 93 | 94 | async def test_sanic_db_find_by_id(app): 95 | """ 96 | Let's assume that, in db we have, 97 | { 98 | "id": "123", 99 | "name": "Kobe Bryant", 100 | "team": "Lakers", 101 | } 102 | """ 103 | doc = await app.db["players"].find_by_id("123") 104 | assert doc.name == "Kobe Bryant" 105 | assert doc.team == "Lakers" 106 | 107 | To send requests to your ``app``, you set up a client fixture using the loop_ and sanic_client_ fixtures: 108 | 109 | .. code-block:: python 110 | 111 | @pytest.fixture 112 | def test_cli(loop, app, sanic_client): 113 | return loop.run_until_complete(sanic_client(app)) 114 | 115 | This ``test_cli`` fixture can then be used to send requests to your ``app``: 116 | 117 | .. code-block:: python 118 | 119 | async def test_index(test_cli): 120 | resp = await test_cli.get('/') 121 | assert resp.status_code == 200 122 | 123 | async def test_player(test_cli): 124 | resp = await test_cli.get('/player') 125 | assert resp.status_code == 200 126 | 127 | -------------------- 128 | asynchronous fixture 129 | -------------------- 130 | 131 | ``pytest-sanic`` also supports asynchronous fixtures, just writes them like common pytest fixtures. 132 | 133 | .. code-block:: python 134 | 135 | @pytest.fixture 136 | async def async_fixture_sleep(): 137 | await asyncio.sleep(0.1) 138 | return "sleep..." 139 | 140 | 141 | -------- 142 | Fixtures 143 | -------- 144 | 145 | Some fixtures for easy testing. 146 | 147 | ``loop`` 148 | ~~~~~~~~ 149 | 150 | ``pytest-sanic`` creates an event loop and injects it as a fixture. ``pytest`` will use this event loop to run your ``async tests``. 151 | By default, fixture ``loop`` is an instance of `asyncio.new_event_loop`. But `uvloop` is also an option for you, by simpy passing 152 | ``--loop uvloop``. Keep mind to just use one single event loop. 153 | 154 | 155 | ``unused_port`` 156 | ~~~~~~~~~~~~~~~ 157 | 158 | an unused TCP port on the localhost. 159 | 160 | 161 | ``test_server`` 162 | ~~~~~~~~~~~~~~~ 163 | 164 | Creates a TestServer instance by giving a ``Sanic`` application. It's very easy to utilize ``test_server`` to create your `Sanic` 165 | application server for testing. 166 | 167 | .. code-block:: python 168 | 169 | @pytest.yield_fixture 170 | def app(): 171 | app = Sanic("test_sanic_app") 172 | 173 | @app.route("/test_get", methods=['GET']) 174 | async def test_get(request): 175 | return response.json({"GET": True}) 176 | 177 | yield app 178 | 179 | @pytest.fixture 180 | def sanic_server(loop, app, test_server): 181 | return loop.run_until_complete(test_server(app)) 182 | 183 | You can also very easily override this ``loop`` fixture by creating your own, simply like, 184 | 185 | .. code-block:: python 186 | 187 | @pytest.yield_fixture 188 | def loop(): 189 | loop = MyEventLoop() 190 | yield loop 191 | loop.close() 192 | 193 | ``test_client`` 194 | ~~~~~~~~~~~~~~~ 195 | 196 | ``test_client`` has been deprecated, please use `sanic_client` instead, check out `issue `_ for more context. 197 | 198 | 199 | ``sanic_client`` 200 | ~~~~~~~~~~~~~~~~ 201 | 202 | Creates a TestClient instance by giving a ``Sanic`` application. You can simply have a client by using ``sanic_client``, like 203 | 204 | .. code-block:: python 205 | 206 | @pytest.yield_fixture 207 | def app(): 208 | app = Sanic("test_sanic_app") 209 | 210 | @app.route("/test_get", methods=['GET']) 211 | async def test_get(request): 212 | return response.json({"GET": True}) 213 | 214 | @app.route("/test_post", methods=['POST']) 215 | async def test_post(request): 216 | return response.json({"POST": True}) 217 | 218 | @app.route("/test_put", methods=['PUT']) 219 | async def test_put(request): 220 | return response.json({"PUT": True}) 221 | 222 | @app.route("/test_delete", methods=['DELETE']) 223 | async def test_delete(request): 224 | return response.json({"DELETE": True}) 225 | 226 | @app.route("/test_patch", methods=['PATCH']) 227 | async def test_patch(request): 228 | return response.json({"PATCH": True}) 229 | 230 | @app.route("/test_options", methods=['OPTIONS']) 231 | async def test_options(request): 232 | return response.json({"OPTIONS": True}) 233 | 234 | @app.route("/test_head", methods=['HEAD']) 235 | async def test_head(request): 236 | return response.json({"HEAD": True}) 237 | 238 | @app.websocket("/test_ws") 239 | async def test_ws(request, ws): 240 | data = await ws.recv() 241 | await ws.send(data) 242 | 243 | yield app 244 | 245 | @pytest.fixture 246 | def test_cli(loop, app, sanic_client): 247 | return loop.run_until_complete(sanic_client(app, protocol=WebSocketProtocol)) 248 | 249 | ######### 250 | # Tests # 251 | ######### 252 | 253 | async def test_fixture_test_client_get(test_cli): 254 | """ 255 | GET request 256 | """ 257 | resp = await test_cli.get('/test_get') 258 | assert resp.status_code == 200 259 | resp_json = resp.json() 260 | assert resp_json == {"GET": True} 261 | 262 | async def test_fixture_test_client_post(test_cli): 263 | """ 264 | POST request 265 | """ 266 | resp = await test_cli.post('/test_post') 267 | assert resp.status_code == 200 268 | resp_json = resp.json() 269 | assert resp_json == {"POST": True} 270 | 271 | async def test_fixture_test_client_put(test_cli): 272 | """ 273 | PUT request 274 | """ 275 | resp = await test_cli.put('/test_put') 276 | assert resp.status_code == 200 277 | resp_json = resp.json() 278 | assert resp_json == {"PUT": True} 279 | 280 | async def test_fixture_test_client_delete(test_cli): 281 | """ 282 | DELETE request 283 | """ 284 | resp = await test_cli.delete('/test_delete') 285 | assert resp.status_code == 200 286 | resp_json = resp.json() 287 | assert resp_json == {"DELETE": True} 288 | 289 | async def test_fixture_test_client_patch(test_cli): 290 | """ 291 | PATCH request 292 | """ 293 | resp = await test_cli.patch('/test_patch') 294 | assert resp.status_code == 200 295 | resp_json = resp.json() 296 | assert resp_json == {"PATCH": True} 297 | 298 | async def test_fixture_test_client_options(test_cli): 299 | """ 300 | OPTIONS request 301 | """ 302 | resp = await test_cli.options('/test_options') 303 | assert resp.status_code == 200 304 | resp_json = resp.json() 305 | assert resp_json == {"OPTIONS": True} 306 | 307 | async def test_fixture_test_client_head(test_cli): 308 | """ 309 | HEAD request 310 | """ 311 | resp = await test_cli.head('/test_head') 312 | assert resp.status_code == 200 313 | resp_json = resp.json() 314 | # HEAD should not have body 315 | assert resp_json is None 316 | 317 | async def test_fixture_test_client_ws(test_cli): 318 | """ 319 | Websockets 320 | """ 321 | ws_conn = await test_cli.ws_connect('/test_ws') 322 | data = 'hello world!' 323 | await ws_conn.send(data) 324 | msg = await ws_conn.recv() 325 | assert msg == data 326 | await ws_conn.close() 327 | 328 | 329 | small notes: 330 | 331 | ``test_cli.ws_connect`` does not work in ``sanic.__version__ <= '0.5.4'``, because of a Sanic bug, but it 332 | has been fixed in master branch. And ``websockets.__version__ >= '4.0'`` has broken websockets in ``sanic.__version__ <= '0.6.0'``, but it has been fixed in `master `_. 333 | 334 | 335 | ---- 336 | Tips 337 | ---- 338 | 339 | * `Blueprints Testing `_ 340 | * ``test_cli.ws_connect`` does not work in ``sanic.__version__ <= '0.5.4'``, because of a Sanic bug, but it has been fixed in master branch. 341 | * `Importing app has loop already running `_ when you have `db_init` listeners. 342 | * `Incorrect coverage report `_ with ``pytest-cov``, but we can have workarounds for this issue, it's a pytest loading plugin problem essentially. 343 | * Websockets > 4.0 has broken websockets in ``sanic.__version__ <= '0.6.0'``, but it has been fixed in `this commit `_ 344 | 345 | 346 | Feel free to create issue if you have any question. You can also check out `closed issues `_ 347 | 348 | 349 | ----------- 350 | Development 351 | ----------- 352 | 353 | ``pytest-sanic`` accepts contributions on GitHub, in the form of issues or pull requests. 354 | 355 | 356 | Build. 357 | 358 | .. code:: 359 | 360 | poetry install 361 | 362 | 363 | Run unit tests. 364 | 365 | .. code:: 366 | 367 | poetry run pytest ./tests --cov pytest_sanic 368 | 369 | 370 | --------- 371 | Reference 372 | --------- 373 | 374 | Some useful pytest plugins: 375 | 376 | * `pytest-tornado `_ 377 | * `pytest-asyncio `_ 378 | * `pytest-aiohttp `_ 379 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = _build 9 | 10 | # User-friendly check for sphinx-build 11 | ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) 12 | $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don\'t have Sphinx installed, grab it from http://sphinx-doc.org/) 13 | endif 14 | 15 | # Internal variables. 16 | PAPEROPT_a4 = -D latex_paper_size=a4 17 | PAPEROPT_letter = -D latex_paper_size=letter 18 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 19 | # the i18n builder cannot share the environment and doctrees with the others 20 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 21 | 22 | .PHONY: help 23 | help: 24 | @echo "Please use \`make ' where is one of" 25 | @echo " html to make standalone HTML files" 26 | @echo " dirhtml to make HTML files named index.html in directories" 27 | @echo " singlehtml to make a single large HTML file" 28 | @echo " pickle to make pickle files" 29 | @echo " json to make JSON files" 30 | @echo " htmlhelp to make HTML files and a HTML help project" 31 | @echo " qthelp to make HTML files and a qthelp project" 32 | @echo " applehelp to make an Apple Help Book" 33 | @echo " devhelp to make HTML files and a Devhelp project" 34 | @echo " epub to make an epub" 35 | @echo " epub3 to make an epub3" 36 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 37 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 38 | @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" 39 | @echo " text to make text files" 40 | @echo " man to make manual pages" 41 | @echo " texinfo to make Texinfo files" 42 | @echo " info to make Texinfo files and run them through makeinfo" 43 | @echo " gettext to make PO message catalogs" 44 | @echo " changes to make an overview of all changed/added/deprecated items" 45 | @echo " xml to make Docutils-native XML files" 46 | @echo " pseudoxml to make pseudoxml-XML files for display purposes" 47 | @echo " linkcheck to check all external links for integrity" 48 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 49 | @echo " coverage to run coverage check of the documentation (if enabled)" 50 | @echo " dummy to check syntax errors of document sources" 51 | 52 | .PHONY: clean 53 | clean: 54 | rm -rf $(BUILDDIR)/* 55 | 56 | .PHONY: html 57 | html: 58 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 59 | @echo 60 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 61 | 62 | .PHONY: dirhtml 63 | dirhtml: 64 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 65 | @echo 66 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 67 | 68 | .PHONY: singlehtml 69 | singlehtml: 70 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 71 | @echo 72 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 73 | 74 | .PHONY: pickle 75 | pickle: 76 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 77 | @echo 78 | @echo "Build finished; now you can process the pickle files." 79 | 80 | .PHONY: json 81 | json: 82 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 83 | @echo 84 | @echo "Build finished; now you can process the JSON files." 85 | 86 | .PHONY: htmlhelp 87 | htmlhelp: 88 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 89 | @echo 90 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 91 | ".hhp project file in $(BUILDDIR)/htmlhelp." 92 | 93 | .PHONY: qthelp 94 | qthelp: 95 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 96 | @echo 97 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 98 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 99 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/aiohttp-transmute.qhcp" 100 | @echo "To view the help file:" 101 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/aiohttp-transmute.qhc" 102 | 103 | .PHONY: applehelp 104 | applehelp: 105 | $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp 106 | @echo 107 | @echo "Build finished. The help book is in $(BUILDDIR)/applehelp." 108 | @echo "N.B. You won't be able to view it unless you put it in" \ 109 | "~/Library/Documentation/Help or install it in your application" \ 110 | "bundle." 111 | 112 | .PHONY: devhelp 113 | devhelp: 114 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 115 | @echo 116 | @echo "Build finished." 117 | @echo "To view the help file:" 118 | @echo "# mkdir -p $$HOME/.local/share/devhelp/aiohttp-transmute" 119 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/aiohttp-transmute" 120 | @echo "# devhelp" 121 | 122 | .PHONY: epub 123 | epub: 124 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 125 | @echo 126 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 127 | 128 | .PHONY: epub3 129 | epub3: 130 | $(SPHINXBUILD) -b epub3 $(ALLSPHINXOPTS) $(BUILDDIR)/epub3 131 | @echo 132 | @echo "Build finished. The epub3 file is in $(BUILDDIR)/epub3." 133 | 134 | .PHONY: latex 135 | latex: 136 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 137 | @echo 138 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 139 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 140 | "(use \`make latexpdf' here to do that automatically)." 141 | 142 | .PHONY: latexpdf 143 | latexpdf: 144 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 145 | @echo "Running LaTeX files through pdflatex..." 146 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 147 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 148 | 149 | .PHONY: latexpdfja 150 | latexpdfja: 151 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 152 | @echo "Running LaTeX files through platex and dvipdfmx..." 153 | $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja 154 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 155 | 156 | .PHONY: text 157 | text: 158 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 159 | @echo 160 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 161 | 162 | .PHONY: man 163 | man: 164 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 165 | @echo 166 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 167 | 168 | .PHONY: texinfo 169 | texinfo: 170 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 171 | @echo 172 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 173 | @echo "Run \`make' in that directory to run these through makeinfo" \ 174 | "(use \`make info' here to do that automatically)." 175 | 176 | .PHONY: info 177 | info: 178 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 179 | @echo "Running Texinfo files through makeinfo..." 180 | make -C $(BUILDDIR)/texinfo info 181 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 182 | 183 | .PHONY: gettext 184 | gettext: 185 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 186 | @echo 187 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 188 | 189 | .PHONY: changes 190 | changes: 191 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 192 | @echo 193 | @echo "The overview file is in $(BUILDDIR)/changes." 194 | 195 | .PHONY: linkcheck 196 | linkcheck: 197 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 198 | @echo 199 | @echo "Link check complete; look for any errors in the above output " \ 200 | "or in $(BUILDDIR)/linkcheck/output.txt." 201 | 202 | .PHONY: doctest 203 | doctest: 204 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 205 | @echo "Testing of doctests in the sources finished, look at the " \ 206 | "results in $(BUILDDIR)/doctest/output.txt." 207 | 208 | .PHONY: coverage 209 | coverage: 210 | $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage 211 | @echo "Testing of coverage in the sources finished, look at the " \ 212 | "results in $(BUILDDIR)/coverage/python.txt." 213 | 214 | .PHONY: xml 215 | xml: 216 | $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml 217 | @echo 218 | @echo "Build finished. The XML files are in $(BUILDDIR)/xml." 219 | 220 | .PHONY: pseudoxml 221 | pseudoxml: 222 | $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml 223 | @echo 224 | @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." 225 | 226 | .PHONY: dummy 227 | dummy: 228 | $(SPHINXBUILD) -b dummy $(ALLSPHINXOPTS) $(BUILDDIR)/dummy 229 | @echo 230 | @echo "Build finished. Dummy builder generates no files." -------------------------------------------------------------------------------- /docs/async_fixture.rst: -------------------------------------------------------------------------------- 1 | ==================== 2 | asynchronous fixture 3 | ==================== 4 | 5 | ``pytest-sanic`` also supports asynchronous fixtures, just writes them like common pytest fixtures. 6 | 7 | .. code-block:: python 8 | 9 | @pytest.fixture 10 | async def async_fixture_sleep(): 11 | await asyncio.sleep(0.1) 12 | return "sleep..." 13 | -------------------------------------------------------------------------------- /docs/development.rst: -------------------------------------------------------------------------------- 1 | ============ 2 | Development 3 | ============ 4 | 5 | ------------ 6 | Contribution 7 | ------------ 8 | 9 | ``pytest-sanic`` accepts contributions on GitHub, in the form of issues or pull requests. 10 | 11 | 12 | Build. 13 | 14 | .. code:: 15 | 16 | poetry install 17 | 18 | 19 | Run unit tests. 20 | 21 | .. code:: 22 | 23 | poetry run pytest ./tests --cov pytest_sanic -------------------------------------------------------------------------------- /docs/example.rst: -------------------------------------------------------------------------------- 1 | ======= 2 | Example 3 | ======= 4 | 5 | A simple example. 6 | 7 | ----------- 8 | Quick Start 9 | ----------- 10 | 11 | You don't have to load ``pytest-sanic`` explicitly. ``pytest`` will do it for you. Just write tests like, 12 | 13 | .. code-block:: python 14 | 15 | async def test_sanic_db_find_by_id(app): 16 | """ 17 | Let's assume that, in db we have, 18 | { 19 | "id": "123", 20 | "name": "Kobe Bryant", 21 | "team": "Lakers", 22 | } 23 | """ 24 | doc = await app.db["players"].find_by_id("123") 25 | assert doc.name == "Kobe Bryant" 26 | assert doc.team == "Lakers" 27 | -------------------------------------------------------------------------------- /docs/fixtures.rst: -------------------------------------------------------------------------------- 1 | ======== 2 | Fixtures 3 | ======== 4 | 5 | Some fixtures for easy testing. 6 | 7 | ---- 8 | loop 9 | ---- 10 | 11 | ``pytest-sanic`` creates an event loop and injects it as a fixture. ``pytest`` will use this event loop to run your ``async tests``. 12 | By default, fixture ``loop`` is an instance of `asyncio.new_event_loop`. But `uvloop` is also an option for you, by simpy passing 13 | ``--loop uvloop``. Keep mind to just use one single event loop. 14 | 15 | 16 | ----------- 17 | unused_port 18 | ----------- 19 | 20 | an unused TCP port on the localhost. 21 | 22 | 23 | ----------- 24 | test_server 25 | ----------- 26 | 27 | Creates a TestServer instance by giving a ``Sanic`` application. It's very easy to utilize ``test_server`` to create your `Sanic` 28 | application server for testing. 29 | 30 | .. code-block:: python 31 | 32 | @pytest.yield_fixture 33 | def app(): 34 | app = Sanic("test_sanic_app") 35 | 36 | @app.route("/test_get", methods=['GET']) 37 | async def test_get(request): 38 | return response.json({"GET": True}) 39 | 40 | yield app 41 | 42 | @pytest.fixture 43 | def sanic_server(loop, app, test_server): 44 | return loop.run_until_complete(test_server(app)) 45 | 46 | You can also very easily override this ``loop`` fixture by creating your own, simply like, 47 | 48 | .. code-block:: python 49 | 50 | @pytest.yield_fixture 51 | def loop(): 52 | loop = MyEventLoop() 53 | yield loop 54 | loop.close() 55 | 56 | 57 | ----------- 58 | test_client 59 | ----------- 60 | 61 | ``test_client`` has been deprecated, please use `sanic_client` instead, check out `issue `_ for more context. 62 | 63 | 64 | ------------ 65 | sanic_client 66 | ------------ 67 | 68 | Creates a TestClient instance by giving a ``Sanic`` application. You can simply have a client by using ``sanic_client``, like 69 | 70 | .. code-block:: python 71 | 72 | @pytest.yield_fixture 73 | def app(): 74 | app = Sanic("test_sanic_app") 75 | 76 | @app.route("/test_get", methods=['GET']) 77 | async def test_get(request): 78 | return response.json({"GET": True}) 79 | 80 | @app.route("/test_post", methods=['POST']) 81 | async def test_post(request): 82 | return response.json({"POST": True}) 83 | 84 | @app.route("/test_put", methods=['PUT']) 85 | async def test_put(request): 86 | return response.json({"PUT": True}) 87 | 88 | @app.route("/test_delete", methods=['DELETE']) 89 | async def test_delete(request): 90 | return response.json({"DELETE": True}) 91 | 92 | @app.route("/test_patch", methods=['PATCH']) 93 | async def test_patch(request): 94 | return response.json({"PATCH": True}) 95 | 96 | @app.route("/test_options", methods=['OPTIONS']) 97 | async def test_options(request): 98 | return response.json({"OPTIONS": True}) 99 | 100 | @app.route("/test_head", methods=['HEAD']) 101 | async def test_head(request): 102 | return response.json({"HEAD": True}) 103 | 104 | @app.websocket("/test_ws") 105 | async def test_ws(request, ws): 106 | data = await ws.recv() 107 | await ws.send(data) 108 | 109 | yield app 110 | 111 | @pytest.fixture 112 | def test_cli(loop, app, sanic_client): 113 | return loop.run_until_complete(sanic_client(app, protocol=WebSocketProtocol)) 114 | 115 | ######### 116 | # Tests # 117 | ######### 118 | 119 | async def test_fixture_test_client_get(test_cli): 120 | """ 121 | GET request 122 | """ 123 | resp = await test_cli.get('/test_get') 124 | assert resp.status_code == 200 125 | resp_json = resp.json() 126 | assert resp_json == {"GET": True} 127 | 128 | async def test_fixture_test_client_post(test_cli): 129 | """ 130 | POST request 131 | """ 132 | resp = await test_cli.post('/test_post') 133 | assert resp.status_code == 200 134 | resp_json = resp.json() 135 | assert resp_json == {"POST": True} 136 | 137 | async def test_fixture_test_client_put(test_cli): 138 | """ 139 | PUT request 140 | """ 141 | resp = await test_cli.put('/test_put') 142 | assert resp.status_code == 200 143 | resp_json = resp.json() 144 | assert resp_json == {"PUT": True} 145 | 146 | async def test_fixture_test_client_delete(test_cli): 147 | """ 148 | DELETE request 149 | """ 150 | resp = await test_cli.delete('/test_delete') 151 | assert resp.status_code == 200 152 | resp_json = resp.json() 153 | assert resp_json == {"DELETE": True} 154 | 155 | async def test_fixture_test_client_patch(test_cli): 156 | """ 157 | PATCH request 158 | """ 159 | resp = await test_cli.patch('/test_patch') 160 | assert resp.status_code == 200 161 | resp_json = resp.json() 162 | assert resp_json == {"PATCH": True} 163 | 164 | async def test_fixture_test_client_options(test_cli): 165 | """ 166 | OPTIONS request 167 | """ 168 | resp = await test_cli.options('/test_options') 169 | assert resp.status_code == 200 170 | resp_json = resp.json() 171 | assert resp_json == {"OPTIONS": True} 172 | 173 | async def test_fixture_test_client_head(test_cli): 174 | """ 175 | HEAD request 176 | """ 177 | resp = await test_cli.head('/test_head') 178 | assert resp.status_code == 200 179 | resp_json = resp.json() 180 | # HEAD should not have body 181 | assert resp_json is None 182 | 183 | async def test_fixture_test_client_ws(test_cli): 184 | """ 185 | Websockets 186 | """ 187 | ws_conn = await test_cli.ws_connect('/test_ws') 188 | data = 'hello world!' 189 | await ws_conn.send(data) 190 | msg = await ws_conn.recv() 191 | assert msg == data 192 | await ws_conn.close() 193 | 194 | 195 | small notes: 196 | 197 | ``test_cli.ws_connect`` does not work in ``sanic.__version__ <= '0.5.4'``, because of a Sanic bug, but it 198 | has been fixed in master branch. And ``websockets.__version__ >= '4.0'`` has broken websockets in ``sanic.__version__ <= '0.6.0'``, but it has been fixed in `master `_. 199 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. pytest-sanic documentation master file, created by 2 | sphinx-quickstart on Wed Aug 17 13:50:33 2016. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | pytest-sanic 7 | ============ 8 | 9 | .. image:: https://travis-ci.org/yunstanford/pytest-sanic.svg?branch=master 10 | :alt: build status 11 | :target: https://travis-ci.org/yunstanford/pytest-sanic 12 | 13 | A pytest plugin for `Sanic `_. It helps you to test your code asynchronously. 14 | 15 | This plugin provides: 16 | 17 | * very easy testing with async coroutines 18 | * common and useful fixtures 19 | * asynchronous fixture support 20 | * test_client/sanic_client for Sanic application 21 | * test_server for Sanic application 22 | 23 | 24 | ----------- 25 | Quick Start 26 | ----------- 27 | 28 | You don't have to load ``pytest-sanic`` explicitly. ``pytest`` will do it for you. Just write tests like, 29 | 30 | .. code-block:: python 31 | 32 | async def test_sanic_db_find_by_id(app): 33 | """ 34 | Let's assume that, in db we have, 35 | { 36 | "id": "123", 37 | "name": "Kobe Bryant", 38 | "team": "Lakers", 39 | } 40 | """ 41 | doc = await app.db["players"].find_by_id("123") 42 | assert doc.name == "Kobe Bryant" 43 | assert doc.team == "Lakers" 44 | 45 | 46 | Here's much more realistic & useful example, 47 | 48 | .. code-block:: python 49 | 50 | from .app import create_app 51 | 52 | @pytest.yield_fixture 53 | def app(): 54 | app = create_app(test_config, **params) 55 | yield app 56 | 57 | @pytest.fixture 58 | def test_cli(loop, app, sanic_client): 59 | return loop.run_until_complete(sanic_client(app)) 60 | 61 | async def test_index(test_cli): 62 | resp = await test_cli.get('/') 63 | assert resp.status_code == 200 64 | 65 | async def test_player(test_cli): 66 | resp = await test_cli.get('/player') 67 | assert resp.status_code == 200 68 | 69 | 70 | Contents: 71 | 72 | .. toctree:: 73 | :maxdepth: 2 74 | 75 | installation 76 | example 77 | fixtures 78 | tips 79 | development 80 | reference 81 | 82 | 83 | Indices and tables 84 | ================== 85 | 86 | * :ref:`genindex` 87 | * :ref:`modindex` 88 | * :ref:`search` 89 | -------------------------------------------------------------------------------- /docs/installation.rst: -------------------------------------------------------------------------------- 1 | ============ 2 | Installation 3 | ============ 4 | 5 | ---------------------- 6 | Installing it globally 7 | ---------------------- 8 | 9 | You can install pytest-sanic globally with any Python package manager: 10 | 11 | .. code:: 12 | 13 | pip install pytest-sanic 14 | -------------------------------------------------------------------------------- /docs/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. epub3 to make an epub3 31 | echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter 32 | echo. text to make text files 33 | echo. man to make manual pages 34 | echo. texinfo to make Texinfo files 35 | echo. gettext to make PO message catalogs 36 | echo. changes to make an overview over all changed/added/deprecated items 37 | echo. xml to make Docutils-native XML files 38 | echo. pseudoxml to make pseudoxml-XML files for display purposes 39 | echo. linkcheck to check all external links for integrity 40 | echo. doctest to run all doctests embedded in the documentation if enabled 41 | echo. coverage to run coverage check of the documentation if enabled 42 | echo. dummy to check syntax errors of document sources 43 | goto end 44 | ) 45 | 46 | if "%1" == "clean" ( 47 | for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i 48 | del /q /s %BUILDDIR%\* 49 | goto end 50 | ) 51 | 52 | 53 | REM Check if sphinx-build is available and fallback to Python version if any 54 | %SPHINXBUILD% 1>NUL 2>NUL 55 | if errorlevel 9009 goto sphinx_python 56 | goto sphinx_ok 57 | 58 | :sphinx_python 59 | 60 | set SPHINXBUILD=python -m sphinx.__init__ 61 | %SPHINXBUILD% 2> nul 62 | if errorlevel 9009 ( 63 | echo. 64 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 65 | echo.installed, then set the SPHINXBUILD environment variable to point 66 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 67 | echo.may add the Sphinx directory to PATH. 68 | echo. 69 | echo.If you don't have Sphinx installed, grab it from 70 | echo.http://sphinx-doc.org/ 71 | exit /b 1 72 | ) 73 | 74 | :sphinx_ok 75 | 76 | 77 | if "%1" == "html" ( 78 | %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html 79 | if errorlevel 1 exit /b 1 80 | echo. 81 | echo.Build finished. The HTML pages are in %BUILDDIR%/html. 82 | goto end 83 | ) 84 | 85 | if "%1" == "dirhtml" ( 86 | %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml 87 | if errorlevel 1 exit /b 1 88 | echo. 89 | echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. 90 | goto end 91 | ) 92 | 93 | if "%1" == "singlehtml" ( 94 | %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml 95 | if errorlevel 1 exit /b 1 96 | echo. 97 | echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. 98 | goto end 99 | ) 100 | 101 | if "%1" == "pickle" ( 102 | %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle 103 | if errorlevel 1 exit /b 1 104 | echo. 105 | echo.Build finished; now you can process the pickle files. 106 | goto end 107 | ) 108 | 109 | if "%1" == "json" ( 110 | %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json 111 | if errorlevel 1 exit /b 1 112 | echo. 113 | echo.Build finished; now you can process the JSON files. 114 | goto end 115 | ) 116 | 117 | if "%1" == "htmlhelp" ( 118 | %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp 119 | if errorlevel 1 exit /b 1 120 | echo. 121 | echo.Build finished; now you can run HTML Help Workshop with the ^ 122 | .hhp project file in %BUILDDIR%/htmlhelp. 123 | goto end 124 | ) 125 | 126 | if "%1" == "qthelp" ( 127 | %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp 128 | if errorlevel 1 exit /b 1 129 | echo. 130 | echo.Build finished; now you can run "qcollectiongenerator" with the ^ 131 | .qhcp project file in %BUILDDIR%/qthelp, like this: 132 | echo.^> qcollectiongenerator %BUILDDIR%\qthelp\aiohttp-transmute.qhcp 133 | echo.To view the help file: 134 | echo.^> assistant -collectionFile %BUILDDIR%\qthelp\aiohttp-transmute.ghc 135 | goto end 136 | ) 137 | 138 | if "%1" == "devhelp" ( 139 | %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp 140 | if errorlevel 1 exit /b 1 141 | echo. 142 | echo.Build finished. 143 | goto end 144 | ) 145 | 146 | if "%1" == "epub" ( 147 | %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub 148 | if errorlevel 1 exit /b 1 149 | echo. 150 | echo.Build finished. The epub file is in %BUILDDIR%/epub. 151 | goto end 152 | ) 153 | 154 | if "%1" == "epub3" ( 155 | %SPHINXBUILD% -b epub3 %ALLSPHINXOPTS% %BUILDDIR%/epub3 156 | if errorlevel 1 exit /b 1 157 | echo. 158 | echo.Build finished. The epub3 file is in %BUILDDIR%/epub3. 159 | goto end 160 | ) 161 | 162 | if "%1" == "latex" ( 163 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 164 | if errorlevel 1 exit /b 1 165 | echo. 166 | echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. 167 | goto end 168 | ) 169 | 170 | if "%1" == "latexpdf" ( 171 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 172 | cd %BUILDDIR%/latex 173 | make all-pdf 174 | cd %~dp0 175 | echo. 176 | echo.Build finished; the PDF files are in %BUILDDIR%/latex. 177 | goto end 178 | ) 179 | 180 | if "%1" == "latexpdfja" ( 181 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 182 | cd %BUILDDIR%/latex 183 | make all-pdf-ja 184 | cd %~dp0 185 | echo. 186 | echo.Build finished; the PDF files are in %BUILDDIR%/latex. 187 | goto end 188 | ) 189 | 190 | if "%1" == "text" ( 191 | %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text 192 | if errorlevel 1 exit /b 1 193 | echo. 194 | echo.Build finished. The text files are in %BUILDDIR%/text. 195 | goto end 196 | ) 197 | 198 | if "%1" == "man" ( 199 | %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man 200 | if errorlevel 1 exit /b 1 201 | echo. 202 | echo.Build finished. The manual pages are in %BUILDDIR%/man. 203 | goto end 204 | ) 205 | 206 | if "%1" == "texinfo" ( 207 | %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo 208 | if errorlevel 1 exit /b 1 209 | echo. 210 | echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. 211 | goto end 212 | ) 213 | 214 | if "%1" == "gettext" ( 215 | %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale 216 | if errorlevel 1 exit /b 1 217 | echo. 218 | echo.Build finished. The message catalogs are in %BUILDDIR%/locale. 219 | goto end 220 | ) 221 | 222 | if "%1" == "changes" ( 223 | %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes 224 | if errorlevel 1 exit /b 1 225 | echo. 226 | echo.The overview file is in %BUILDDIR%/changes. 227 | goto end 228 | ) 229 | 230 | if "%1" == "linkcheck" ( 231 | %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck 232 | if errorlevel 1 exit /b 1 233 | echo. 234 | echo.Link check complete; look for any errors in the above output ^ 235 | or in %BUILDDIR%/linkcheck/output.txt. 236 | goto end 237 | ) 238 | 239 | if "%1" == "doctest" ( 240 | %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest 241 | if errorlevel 1 exit /b 1 242 | echo. 243 | echo.Testing of doctests in the sources finished, look at the ^ 244 | results in %BUILDDIR%/doctest/output.txt. 245 | goto end 246 | ) 247 | 248 | if "%1" == "coverage" ( 249 | %SPHINXBUILD% -b coverage %ALLSPHINXOPTS% %BUILDDIR%/coverage 250 | if errorlevel 1 exit /b 1 251 | echo. 252 | echo.Testing of coverage in the sources finished, look at the ^ 253 | results in %BUILDDIR%/coverage/python.txt. 254 | goto end 255 | ) 256 | 257 | if "%1" == "xml" ( 258 | %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml 259 | if errorlevel 1 exit /b 1 260 | echo. 261 | echo.Build finished. The XML files are in %BUILDDIR%/xml. 262 | goto end 263 | ) 264 | 265 | if "%1" == "pseudoxml" ( 266 | %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml 267 | if errorlevel 1 exit /b 1 268 | echo. 269 | echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. 270 | goto end 271 | ) 272 | 273 | if "%1" == "dummy" ( 274 | %SPHINXBUILD% -b dummy %ALLSPHINXOPTS% %BUILDDIR%/dummy 275 | if errorlevel 1 exit /b 1 276 | echo. 277 | echo.Build finished. Dummy builder generates no files. 278 | goto end 279 | ) 280 | 281 | :end -------------------------------------------------------------------------------- /docs/reference.rst: -------------------------------------------------------------------------------- 1 | ========= 2 | Reference 3 | ========= 4 | 5 | Some useful pytest plugins: 6 | 7 | * `pytest-tornado `_ 8 | * `pytest-asyncio `_ 9 | * `pytest-aiohttp `_ 10 | -------------------------------------------------------------------------------- /docs/tips.rst: -------------------------------------------------------------------------------- 1 | === 2 | Q&A 3 | === 4 | 5 | ---- 6 | Tips 7 | ---- 8 | 9 | * `Blueprints Testing `_ 10 | * ``test_cli.ws_connect`` does not work in ``sanic.__version__ <= '0.5.4'``, because of a Sanic bug, but it has been fixed in master branch. 11 | * `Importing app has loop already running `_ when you have `db_init` listeners. 12 | * `Incorrect coverage report `_ with ``pytest-cov``, but we can have workarounds for this issue, it's a pytest loading plugin problem essentially. 13 | * Websockets > 4.0 has broken websockets in ``sanic.__version__ <= '0.6.0'``, but it has been fixed in `master `_ 14 | 15 | * ``sanic_client/test_client`` is not an instance of ``sanic.testing.SanicTestClient``. It is a simpler interface designed for easier testing. It randomizes the port used. The return value of ``get``, ``post``, etc is ``Response`` unlike ``(Request, Response)`` as that of ``SanicTestClient``. `See here `_ for more info. 16 | 17 | 18 | Feel free to create issue if you have any question. You can also check out `closed issues `_ 19 | -------------------------------------------------------------------------------- /poetry.lock: -------------------------------------------------------------------------------- 1 | [[package]] 2 | category = "dev" 3 | description = "File support for asyncio." 4 | name = "aiofiles" 5 | optional = false 6 | python-versions = "*" 7 | version = "0.6.0" 8 | 9 | [[package]] 10 | category = "main" 11 | description = "High level compatibility layer for multiple asynchronous event loop implementations" 12 | name = "anyio" 13 | optional = false 14 | python-versions = ">=3.6.2" 15 | version = "3.3.0" 16 | 17 | [package.dependencies] 18 | idna = ">=2.8" 19 | sniffio = ">=1.1" 20 | 21 | [package.dependencies.typing-extensions] 22 | python = "<3.8" 23 | version = "*" 24 | 25 | [package.extras] 26 | doc = ["sphinx-rtd-theme", "sphinx-autodoc-typehints (>=1.2.0)"] 27 | test = ["coverage (>=4.5)", "hypothesis (>=4.0)", "pytest (>=6.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (<0.15)", "mock (>=4)", "uvloop (>=0.15)"] 28 | trio = ["trio (>=0.16)"] 29 | 30 | [[package]] 31 | category = "main" 32 | description = "Async generators and context managers for Python 3.5+" 33 | name = "async-generator" 34 | optional = false 35 | python-versions = ">=3.5" 36 | version = "1.10" 37 | 38 | [[package]] 39 | category = "main" 40 | description = "Atomic file writes." 41 | marker = "sys_platform == \"win32\"" 42 | name = "atomicwrites" 43 | optional = false 44 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 45 | version = "1.4.0" 46 | 47 | [[package]] 48 | category = "main" 49 | description = "Classes Without Boilerplate" 50 | name = "attrs" 51 | optional = false 52 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 53 | version = "21.2.0" 54 | 55 | [package.extras] 56 | dev = ["coverage (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "furo", "sphinx", "sphinx-notfound-page", "pre-commit"] 57 | docs = ["furo", "sphinx", "zope.interface", "sphinx-notfound-page"] 58 | tests = ["coverage (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface"] 59 | tests_no_zope = ["coverage (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins"] 60 | 61 | [[package]] 62 | category = "main" 63 | description = "Python package for providing Mozilla's CA Bundle." 64 | name = "certifi" 65 | optional = false 66 | python-versions = "*" 67 | version = "2021.5.30" 68 | 69 | [[package]] 70 | category = "main" 71 | description = "Cross-platform colored terminal text." 72 | marker = "sys_platform == \"win32\"" 73 | name = "colorama" 74 | optional = false 75 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 76 | version = "0.4.4" 77 | 78 | [[package]] 79 | category = "dev" 80 | description = "Code coverage measurement for Python" 81 | name = "coverage" 82 | optional = false 83 | python-versions = "*" 84 | version = "4.4.2" 85 | 86 | [[package]] 87 | category = "main" 88 | description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" 89 | name = "h11" 90 | optional = false 91 | python-versions = ">=3.6" 92 | version = "0.12.0" 93 | 94 | [[package]] 95 | category = "main" 96 | description = "A minimal low-level HTTP client." 97 | name = "httpcore" 98 | optional = false 99 | python-versions = ">=3.6" 100 | version = "0.13.6" 101 | 102 | [package.dependencies] 103 | anyio = ">=3.0.0,<4.0.0" 104 | h11 = ">=0.11,<0.13" 105 | sniffio = ">=1.0.0,<2.0.0" 106 | 107 | [package.extras] 108 | http2 = ["h2 (>=3,<5)"] 109 | 110 | [[package]] 111 | category = "dev" 112 | description = "A collection of framework independent HTTP protocol utils." 113 | name = "httptools" 114 | optional = false 115 | python-versions = "*" 116 | version = "0.2.0" 117 | 118 | [package.extras] 119 | test = ["Cython (0.29.22)"] 120 | 121 | [[package]] 122 | category = "main" 123 | description = "The next generation HTTP client." 124 | name = "httpx" 125 | optional = false 126 | python-versions = ">=3.6" 127 | version = "0.18.2" 128 | 129 | [package.dependencies] 130 | certifi = "*" 131 | httpcore = ">=0.13.3,<0.14.0" 132 | sniffio = "*" 133 | 134 | [package.dependencies.rfc3986] 135 | extras = ["idna2008"] 136 | version = ">=1.3,<2" 137 | 138 | [package.extras] 139 | brotli = ["brotlicffi (>=1.0.0,<2.0.0)"] 140 | http2 = ["h2 (>=3.0.0,<4.0.0)"] 141 | 142 | [[package]] 143 | category = "main" 144 | description = "Internationalized Domain Names in Applications (IDNA)" 145 | name = "idna" 146 | optional = false 147 | python-versions = ">=3.5" 148 | version = "3.2" 149 | 150 | [[package]] 151 | category = "main" 152 | description = "Read metadata from Python packages" 153 | marker = "python_version < \"3.8\"" 154 | name = "importlib-metadata" 155 | optional = false 156 | python-versions = ">=3.6" 157 | version = "4.6.1" 158 | 159 | [package.dependencies] 160 | zipp = ">=0.5" 161 | 162 | [package.dependencies.typing-extensions] 163 | python = "<3.8" 164 | version = ">=3.6.4" 165 | 166 | [package.extras] 167 | docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] 168 | perf = ["ipython"] 169 | testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "packaging", "pep517", "pyfakefs", "flufl.flake8", "pytest-perf (>=0.9.2)", "pytest-black (>=0.3.7)", "pytest-mypy", "importlib-resources (>=1.3)"] 170 | 171 | [[package]] 172 | category = "main" 173 | description = "iniconfig: brain-dead simple config-ini parsing" 174 | name = "iniconfig" 175 | optional = false 176 | python-versions = "*" 177 | version = "1.1.1" 178 | 179 | [[package]] 180 | category = "dev" 181 | description = "multidict implementation" 182 | name = "multidict" 183 | optional = false 184 | python-versions = ">=3.6" 185 | version = "5.1.0" 186 | 187 | [[package]] 188 | category = "main" 189 | description = "Core utilities for Python packages" 190 | name = "packaging" 191 | optional = false 192 | python-versions = ">=3.6" 193 | version = "21.0" 194 | 195 | [package.dependencies] 196 | pyparsing = ">=2.0.2" 197 | 198 | [[package]] 199 | category = "main" 200 | description = "plugin and hook calling mechanisms for python" 201 | name = "pluggy" 202 | optional = false 203 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 204 | version = "0.13.1" 205 | 206 | [package.dependencies] 207 | [package.dependencies.importlib-metadata] 208 | python = "<3.8" 209 | version = ">=0.12" 210 | 211 | [package.extras] 212 | dev = ["pre-commit", "tox"] 213 | 214 | [[package]] 215 | category = "main" 216 | description = "library with cross-python path, ini-parsing, io, code, log facilities" 217 | name = "py" 218 | optional = false 219 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 220 | version = "1.10.0" 221 | 222 | [[package]] 223 | category = "main" 224 | description = "Python parsing module" 225 | name = "pyparsing" 226 | optional = false 227 | python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" 228 | version = "2.4.7" 229 | 230 | [[package]] 231 | category = "main" 232 | description = "pytest: simple powerful testing with Python" 233 | name = "pytest" 234 | optional = false 235 | python-versions = ">=3.6" 236 | version = "6.2.4" 237 | 238 | [package.dependencies] 239 | atomicwrites = ">=1.0" 240 | attrs = ">=19.2.0" 241 | colorama = "*" 242 | iniconfig = "*" 243 | packaging = "*" 244 | pluggy = ">=0.12,<1.0.0a1" 245 | py = ">=1.8.2" 246 | toml = "*" 247 | 248 | [package.dependencies.importlib-metadata] 249 | python = "<3.8" 250 | version = ">=0.12" 251 | 252 | [package.extras] 253 | testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"] 254 | 255 | [[package]] 256 | category = "dev" 257 | description = "Pytest plugin for measuring coverage." 258 | name = "pytest-cov" 259 | optional = false 260 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 261 | version = "2.10.1" 262 | 263 | [package.dependencies] 264 | coverage = ">=4.4" 265 | pytest = ">=4.6" 266 | 267 | [package.extras] 268 | testing = ["fields", "hunter", "process-tests (2.0.2)", "six", "pytest-xdist", "virtualenv"] 269 | 270 | [[package]] 271 | category = "main" 272 | description = "Validating URI References per RFC 3986" 273 | name = "rfc3986" 274 | optional = false 275 | python-versions = "*" 276 | version = "1.5.0" 277 | 278 | [package.dependencies] 279 | [package.dependencies.idna] 280 | optional = true 281 | version = "*" 282 | 283 | [package.extras] 284 | idna2008 = ["idna"] 285 | 286 | [[package]] 287 | category = "dev" 288 | description = "A web server and web framework that's written to go fast. Build fast. Run fast." 289 | name = "sanic" 290 | optional = false 291 | python-versions = ">=3.7" 292 | version = "21.6.0" 293 | 294 | [package.dependencies] 295 | aiofiles = ">=0.6.0" 296 | httptools = ">=0.0.10" 297 | multidict = ">=5.0,<6.0" 298 | sanic-routing = "0.7.0" 299 | ujson = ">=1.35" 300 | uvloop = ">=0.5.3" 301 | websockets = ">=9.0" 302 | 303 | [package.extras] 304 | all = ["sanic-testing (>=0.6.0)", "pytest (5.2.1)", "multidict (>=5.0,<6.0)", "gunicorn (20.0.4)", "pytest-cov", "beautifulsoup4", "pytest-sanic", "pytest-sugar", "pytest-benchmark", "aiofiles", "tox", "black", "flake8", "bandit", "towncrier", "sphinx (>=2.1.2)", "sphinx-rtd-theme", "recommonmark (>=0.5.0)", "docutils", "pygments", "uvloop (>=0.5.3)", "ujson (>=1.35)"] 305 | dev = ["sanic-testing (>=0.6.0)", "pytest (5.2.1)", "multidict (>=5.0,<6.0)", "gunicorn (20.0.4)", "pytest-cov", "beautifulsoup4", "pytest-sanic", "pytest-sugar", "pytest-benchmark", "aiofiles", "tox", "black", "flake8", "bandit", "towncrier", "uvloop (>=0.5.3)", "ujson (>=1.35)"] 306 | docs = ["sphinx (>=2.1.2)", "sphinx-rtd-theme", "recommonmark (>=0.5.0)", "docutils", "pygments"] 307 | test = ["sanic-testing (>=0.6.0)", "pytest (5.2.1)", "multidict (>=5.0,<6.0)", "gunicorn (20.0.4)", "pytest-cov", "beautifulsoup4", "pytest-sanic", "pytest-sugar", "pytest-benchmark", "uvloop (>=0.5.3)", "ujson (>=1.35)"] 308 | 309 | [[package]] 310 | category = "dev" 311 | description = "Core routing component for Sanic" 312 | name = "sanic-routing" 313 | optional = false 314 | python-versions = "*" 315 | version = "0.7.0" 316 | 317 | [[package]] 318 | category = "main" 319 | description = "Sniff out which async library your code is running under" 320 | name = "sniffio" 321 | optional = false 322 | python-versions = ">=3.5" 323 | version = "1.2.0" 324 | 325 | [[package]] 326 | category = "main" 327 | description = "Python Library for Tom's Obvious, Minimal Language" 328 | name = "toml" 329 | optional = false 330 | python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" 331 | version = "0.10.2" 332 | 333 | [[package]] 334 | category = "main" 335 | description = "Backported and Experimental Type Hints for Python 3.5+" 336 | marker = "python_version < \"3.8\"" 337 | name = "typing-extensions" 338 | optional = false 339 | python-versions = "*" 340 | version = "3.10.0.0" 341 | 342 | [[package]] 343 | category = "dev" 344 | description = "Ultra fast JSON encoder and decoder for Python" 345 | marker = "sys_platform != \"win32\" and implementation_name == \"cpython\"" 346 | name = "ujson" 347 | optional = false 348 | python-versions = ">=3.6" 349 | version = "4.0.2" 350 | 351 | [[package]] 352 | category = "dev" 353 | description = "Fast implementation of asyncio event loop on top of libuv" 354 | marker = "sys_platform != \"win32\" and implementation_name == \"cpython\"" 355 | name = "uvloop" 356 | optional = false 357 | python-versions = ">=3.7" 358 | version = "0.15.3" 359 | 360 | [package.extras] 361 | dev = ["Cython (>=0.29.20,<0.30.0)", "pytest (>=3.6.0)", "Sphinx (>=1.7.3,<1.8.0)", "sphinxcontrib-asyncio (>=0.2.0,<0.3.0)", "sphinx-rtd-theme (>=0.2.4,<0.3.0)", "aiohttp", "flake8 (>=3.8.4,<3.9.0)", "psutil", "pycodestyle (>=2.6.0,<2.7.0)", "pyOpenSSL (>=19.0.0,<19.1.0)", "mypy (>=0.800)"] 362 | docs = ["Sphinx (>=1.7.3,<1.8.0)", "sphinxcontrib-asyncio (>=0.2.0,<0.3.0)", "sphinx-rtd-theme (>=0.2.4,<0.3.0)"] 363 | test = ["aiohttp", "flake8 (>=3.8.4,<3.9.0)", "psutil", "pycodestyle (>=2.6.0,<2.7.0)", "pyOpenSSL (>=19.0.0,<19.1.0)", "mypy (>=0.800)"] 364 | 365 | [[package]] 366 | category = "main" 367 | description = "An implementation of the WebSocket Protocol (RFC 6455 & 7692)" 368 | name = "websockets" 369 | optional = false 370 | python-versions = ">=3.6.1" 371 | version = "9.1" 372 | 373 | [[package]] 374 | category = "main" 375 | description = "Backport of pathlib-compatible object wrapper for zip files" 376 | marker = "python_version < \"3.8\"" 377 | name = "zipp" 378 | optional = false 379 | python-versions = ">=3.6" 380 | version = "3.5.0" 381 | 382 | [package.extras] 383 | docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] 384 | testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy"] 385 | 386 | [metadata] 387 | content-hash = "4c2e52608332b6f026143785313eb2c0eac068e792b2b52324a6681e281234e7" 388 | python-versions = ">=3.7" 389 | 390 | [metadata.files] 391 | aiofiles = [ 392 | {file = "aiofiles-0.6.0-py3-none-any.whl", hash = "sha256:bd3019af67f83b739f8e4053c6c0512a7f545b9a8d91aaeab55e6e0f9d123c27"}, 393 | {file = "aiofiles-0.6.0.tar.gz", hash = "sha256:e0281b157d3d5d59d803e3f4557dcc9a3dff28a4dd4829a9ff478adae50ca092"}, 394 | ] 395 | anyio = [ 396 | {file = "anyio-3.3.0-py3-none-any.whl", hash = "sha256:929a6852074397afe1d989002aa96d457e3e1e5441357c60d03e7eea0e65e1b0"}, 397 | {file = "anyio-3.3.0.tar.gz", hash = "sha256:ae57a67583e5ff8b4af47666ff5651c3732d45fd26c929253748e796af860374"}, 398 | ] 399 | async-generator = [ 400 | {file = "async_generator-1.10-py3-none-any.whl", hash = "sha256:01c7bf666359b4967d2cda0000cc2e4af16a0ae098cbffcb8472fb9e8ad6585b"}, 401 | {file = "async_generator-1.10.tar.gz", hash = "sha256:6ebb3d106c12920aaae42ccb6f787ef5eefdcdd166ea3d628fa8476abe712144"}, 402 | ] 403 | atomicwrites = [ 404 | {file = "atomicwrites-1.4.0-py2.py3-none-any.whl", hash = "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197"}, 405 | {file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"}, 406 | ] 407 | attrs = [ 408 | {file = "attrs-21.2.0-py2.py3-none-any.whl", hash = "sha256:149e90d6d8ac20db7a955ad60cf0e6881a3f20d37096140088356da6c716b0b1"}, 409 | {file = "attrs-21.2.0.tar.gz", hash = "sha256:ef6aaac3ca6cd92904cdd0d83f629a15f18053ec84e6432106f7a4d04ae4f5fb"}, 410 | ] 411 | certifi = [ 412 | {file = "certifi-2021.5.30-py2.py3-none-any.whl", hash = "sha256:50b1e4f8446b06f41be7dd6338db18e0990601dce795c2b1686458aa7e8fa7d8"}, 413 | {file = "certifi-2021.5.30.tar.gz", hash = "sha256:2bbf76fd432960138b3ef6dda3dde0544f27cbf8546c458e60baf371917ba9ee"}, 414 | ] 415 | colorama = [ 416 | {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"}, 417 | {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"}, 418 | ] 419 | coverage = [ 420 | {file = "coverage-4.4.2-cp26-cp26m-macosx_10_10_x86_64.whl", hash = "sha256:d1ee76f560c3c3e8faada866a07a32485445e16ed2206ac8378bd90dadffb9f0"}, 421 | {file = "coverage-4.4.2-cp26-cp26m-manylinux1_i686.whl", hash = "sha256:007eeef7e23f9473622f7d94a3e029a45d55a92a1f083f0f3512f5ab9a669b05"}, 422 | {file = "coverage-4.4.2-cp26-cp26m-manylinux1_x86_64.whl", hash = "sha256:17307429935f96c986a1b1674f78079528833410750321d22b5fb35d1883828e"}, 423 | {file = "coverage-4.4.2-cp26-cp26mu-manylinux1_i686.whl", hash = "sha256:845fddf89dca1e94abe168760a38271abfc2e31863fbb4ada7f9a99337d7c3dc"}, 424 | {file = "coverage-4.4.2-cp26-cp26mu-manylinux1_x86_64.whl", hash = "sha256:3f4d0b3403d3e110d2588c275540649b1841725f5a11a7162620224155d00ba2"}, 425 | {file = "coverage-4.4.2-cp27-cp27m-macosx_10_12_intel.whl", hash = "sha256:4c4f368ffe1c2e7602359c2c50233269f3abe1c48ca6b288dcd0fb1d1c679733"}, 426 | {file = "coverage-4.4.2-cp27-cp27m-macosx_10_12_x86_64.whl", hash = "sha256:f8c55dd0f56d3d618dfacf129e010cbe5d5f94b6951c1b2f13ab1a2f79c284da"}, 427 | {file = "coverage-4.4.2-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:cdd92dd9471e624cd1d8c1a2703d25f114b59b736b0f1f659a98414e535ffb3d"}, 428 | {file = "coverage-4.4.2-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:2ad357d12971e77360034c1596011a03f50c0f9e1ecd12e081342b8d1aee2236"}, 429 | {file = "coverage-4.4.2-cp27-cp27m-win32.whl", hash = "sha256:700d7579995044dc724847560b78ac786f0ca292867447afda7727a6fbaa082e"}, 430 | {file = "coverage-4.4.2-cp27-cp27m-win_amd64.whl", hash = "sha256:66f393e10dd866be267deb3feca39babba08ae13763e0fc7a1063cbe1f8e49f6"}, 431 | {file = "coverage-4.4.2-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:e9a0e1caed2a52f15c96507ab78a48f346c05681a49c5b003172f8073da6aa6b"}, 432 | {file = "coverage-4.4.2-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:eea9135432428d3ca7ee9be86af27cb8e56243f73764a9b6c3e0bda1394916be"}, 433 | {file = "coverage-4.4.2-cp33-cp33m-macosx_10_10_x86_64.whl", hash = "sha256:5ff16548492e8a12e65ff3d55857ccd818584ed587a6c2898a9ebbe09a880674"}, 434 | {file = "coverage-4.4.2-cp33-cp33m-manylinux1_i686.whl", hash = "sha256:d00e29b78ff610d300b2c37049a41234d48ea4f2d2581759ebcf67caaf731c31"}, 435 | {file = "coverage-4.4.2-cp33-cp33m-manylinux1_x86_64.whl", hash = "sha256:87d942863fe74b1c3be83a045996addf1639218c2cb89c5da18c06c0fe3917ea"}, 436 | {file = "coverage-4.4.2-cp34-cp34m-macosx_10_10_x86_64.whl", hash = "sha256:358d635b1fc22a425444d52f26287ae5aea9e96e254ff3c59c407426f44574f4"}, 437 | {file = "coverage-4.4.2-cp34-cp34m-manylinux1_i686.whl", hash = "sha256:81912cfe276e0069dca99e1e4e6be7b06b5fc8342641c6b472cb2fed7de7ae18"}, 438 | {file = "coverage-4.4.2-cp34-cp34m-manylinux1_x86_64.whl", hash = "sha256:079248312838c4c8f3494934ab7382a42d42d5f365f0cf7516f938dbb3f53f3f"}, 439 | {file = "coverage-4.4.2-cp34-cp34m-win32.whl", hash = "sha256:b0059630ca5c6b297690a6bf57bf2fdac1395c24b7935fd73ee64190276b743b"}, 440 | {file = "coverage-4.4.2-cp34-cp34m-win_amd64.whl", hash = "sha256:493082f104b5ca920e97a485913de254cbe351900deed72d4264571c73464cd0"}, 441 | {file = "coverage-4.4.2-cp35-cp35m-macosx_10_10_x86_64.whl", hash = "sha256:e3ba9b14607c23623cf38f90b23f5bed4a3be87cbfa96e2e9f4eabb975d1e98b"}, 442 | {file = "coverage-4.4.2-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:82cbd3317320aa63c65555aa4894bf33a13fb3a77f079059eb5935eea415938d"}, 443 | {file = "coverage-4.4.2-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:9721f1b7275d3112dc7ccf63f0553c769f09b5c25a26ee45872c7f5c09edf6c1"}, 444 | {file = "coverage-4.4.2-cp35-cp35m-win32.whl", hash = "sha256:bd4800e32b4c8d99c3a2c943f1ac430cbf80658d884123d19639bcde90dad44a"}, 445 | {file = "coverage-4.4.2-cp35-cp35m-win_amd64.whl", hash = "sha256:f29841e865590af72c4b90d7b5b8e93fd560f5dea436c1d5ee8053788f9285de"}, 446 | {file = "coverage-4.4.2-cp36-cp36m-macosx_10_12_x86_64.whl", hash = "sha256:f3a5c6d054c531536a83521c00e5d4004f1e126e2e2556ce399bef4180fbe540"}, 447 | {file = "coverage-4.4.2-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:dd707a21332615108b736ef0b8513d3edaf12d2a7d5fc26cd04a169a8ae9b526"}, 448 | {file = "coverage-4.4.2-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:2e1a5c6adebb93c3b175103c2f855eda957283c10cf937d791d81bef8872d6ca"}, 449 | {file = "coverage-4.4.2-cp36-cp36m-win32.whl", hash = "sha256:f87f522bde5540d8a4b11df80058281ac38c44b13ce29ced1e294963dd51a8f8"}, 450 | {file = "coverage-4.4.2-cp36-cp36m-win_amd64.whl", hash = "sha256:a7cfaebd8f24c2b537fa6a271229b051cdac9c1734bb6f939ccfc7c055689baa"}, 451 | {file = "coverage-4.4.2.tar.gz", hash = "sha256:309d91bd7a35063ec7a0e4d75645488bfab3f0b66373e7722f23da7f5b0f34cc"}, 452 | {file = "coverage-4.4.2.win-amd64-py2.7.exe", hash = "sha256:b6cebae1502ce5b87d7c6f532fa90ab345cfbda62b95aeea4e431e164d498a3d"}, 453 | {file = "coverage-4.4.2.win-amd64-py3.4.exe", hash = "sha256:a4497faa4f1c0fc365ba05eaecfb6b5d24e3c8c72e95938f9524e29dadb15e76"}, 454 | {file = "coverage-4.4.2.win-amd64-py3.5.exe", hash = "sha256:2b4d7f03a8a6632598cbc5df15bbca9f778c43db7cf1a838f4fa2c8599a8691a"}, 455 | {file = "coverage-4.4.2.win-amd64-py3.6.exe", hash = "sha256:1afccd7e27cac1b9617be8c769f6d8a6d363699c9b86820f40c74cfb3328921c"}, 456 | {file = "coverage-4.4.2.win32-py2.7.exe", hash = "sha256:0388c12539372bb92d6dde68b4627f0300d948965bbb7fc104924d715fdc0965"}, 457 | {file = "coverage-4.4.2.win32-py3.4.exe", hash = "sha256:ab3508df9a92c1d3362343d235420d08e2662969b83134f8a97dc1451cbe5e84"}, 458 | {file = "coverage-4.4.2.win32-py3.5.exe", hash = "sha256:43a155eb76025c61fc20c3d03b89ca28efa6f5be572ab6110b2fb68eda96bfea"}, 459 | {file = "coverage-4.4.2.win32-py3.6.exe", hash = "sha256:f98b461cb59f117887aa634a66022c0bd394278245ed51189f63a036516e32de"}, 460 | ] 461 | h11 = [ 462 | {file = "h11-0.12.0-py3-none-any.whl", hash = "sha256:36a3cb8c0a032f56e2da7084577878a035d3b61d104230d4bd49c0c6b555a9c6"}, 463 | {file = "h11-0.12.0.tar.gz", hash = "sha256:47222cb6067e4a307d535814917cd98fd0a57b6788ce715755fa2b6c28b56042"}, 464 | ] 465 | httpcore = [ 466 | {file = "httpcore-0.13.6-py3-none-any.whl", hash = "sha256:db4c0dcb8323494d01b8c6d812d80091a31e520033e7b0120883d6f52da649ff"}, 467 | {file = "httpcore-0.13.6.tar.gz", hash = "sha256:b0d16f0012ec88d8cc848f5a55f8a03158405f4bca02ee49bc4ca2c1fda49f3e"}, 468 | ] 469 | httptools = [ 470 | {file = "httptools-0.2.0-cp35-cp35m-macosx_10_14_x86_64.whl", hash = "sha256:79dbc21f3612a78b28384e989b21872e2e3cf3968532601544696e4ed0007ce5"}, 471 | {file = "httptools-0.2.0-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:78d03dd39b09c99ec917d50189e6743adbfd18c15d5944392d2eabda688bf149"}, 472 | {file = "httptools-0.2.0-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:a23166e5ae2775709cf4f7ad4c2048755ebfb272767d244e1a96d55ac775cca7"}, 473 | {file = "httptools-0.2.0-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:3ab1f390d8867f74b3b5ee2a7ecc9b8d7f53750bd45714bf1cb72a953d7dfa77"}, 474 | {file = "httptools-0.2.0-cp36-cp36m-win_amd64.whl", hash = "sha256:a7594f9a010cdf1e16a58b3bf26c9da39bbf663e3b8d46d39176999d71816658"}, 475 | {file = "httptools-0.2.0-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:01b392a166adcc8bc2f526a939a8aabf89fe079243e1543fd0e7dc1b58d737cb"}, 476 | {file = "httptools-0.2.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:80ffa04fe8c8dfacf6e4cef8277347d35b0442c581f5814f3b0cf41b65c43c6e"}, 477 | {file = "httptools-0.2.0-cp37-cp37m-win_amd64.whl", hash = "sha256:d5682eeb10cca0606c4a8286a3391d4c3c5a36f0c448e71b8bd05be4e1694bfb"}, 478 | {file = "httptools-0.2.0-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:a289c27ccae399a70eacf32df9a44059ca2ba4ac444604b00a19a6c1f0809943"}, 479 | {file = "httptools-0.2.0-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:813871f961edea6cb2fe312f2d9b27d12a51ba92545380126f80d0de1917ea15"}, 480 | {file = "httptools-0.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:cc9be041e428c10f8b6ab358c6b393648f9457094e1dcc11b4906026d43cd380"}, 481 | {file = "httptools-0.2.0-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:b08d00d889a118f68f37f3c43e359aab24ee29eb2e3fe96d64c6a2ba8b9d6557"}, 482 | {file = "httptools-0.2.0-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:fd3b8905e21431ad306eeaf56644a68fdd621bf8f3097eff54d0f6bdf7262065"}, 483 | {file = "httptools-0.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:200fc1cdf733a9ff554c0bb97a4047785cfaad9875307d6087001db3eb2b417f"}, 484 | {file = "httptools-0.2.0.tar.gz", hash = "sha256:94505026be56652d7a530ab03d89474dc6021019d6b8682281977163b3471ea0"}, 485 | ] 486 | httpx = [ 487 | {file = "httpx-0.18.2-py3-none-any.whl", hash = "sha256:979afafecb7d22a1d10340bafb403cf2cb75aff214426ff206521fc79d26408c"}, 488 | {file = "httpx-0.18.2.tar.gz", hash = "sha256:9f99c15d33642d38bce8405df088c1c4cfd940284b4290cacbfb02e64f4877c6"}, 489 | ] 490 | idna = [ 491 | {file = "idna-3.2-py3-none-any.whl", hash = "sha256:14475042e284991034cb48e06f6851428fb14c4dc953acd9be9a5e95c7b6dd7a"}, 492 | {file = "idna-3.2.tar.gz", hash = "sha256:467fbad99067910785144ce333826c71fb0e63a425657295239737f7ecd125f3"}, 493 | ] 494 | importlib-metadata = [ 495 | {file = "importlib_metadata-4.6.1-py3-none-any.whl", hash = "sha256:9f55f560e116f8643ecf2922d9cd3e1c7e8d52e683178fecd9d08f6aa357e11e"}, 496 | {file = "importlib_metadata-4.6.1.tar.gz", hash = "sha256:079ada16b7fc30dfbb5d13399a5113110dab1aa7c2bc62f66af75f0b717c8cac"}, 497 | ] 498 | iniconfig = [ 499 | {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, 500 | {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, 501 | ] 502 | multidict = [ 503 | {file = "multidict-5.1.0-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:b7993704f1a4b204e71debe6095150d43b2ee6150fa4f44d6d966ec356a8d61f"}, 504 | {file = "multidict-5.1.0-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:9dd6e9b1a913d096ac95d0399bd737e00f2af1e1594a787e00f7975778c8b2bf"}, 505 | {file = "multidict-5.1.0-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:f21756997ad8ef815d8ef3d34edd98804ab5ea337feedcd62fb52d22bf531281"}, 506 | {file = "multidict-5.1.0-cp36-cp36m-manylinux2014_i686.whl", hash = "sha256:1ab820665e67373de5802acae069a6a05567ae234ddb129f31d290fc3d1aa56d"}, 507 | {file = "multidict-5.1.0-cp36-cp36m-manylinux2014_ppc64le.whl", hash = "sha256:9436dc58c123f07b230383083855593550c4d301d2532045a17ccf6eca505f6d"}, 508 | {file = "multidict-5.1.0-cp36-cp36m-manylinux2014_s390x.whl", hash = "sha256:830f57206cc96ed0ccf68304141fec9481a096c4d2e2831f311bde1c404401da"}, 509 | {file = "multidict-5.1.0-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:2e68965192c4ea61fff1b81c14ff712fc7dc15d2bd120602e4a3494ea6584224"}, 510 | {file = "multidict-5.1.0-cp36-cp36m-win32.whl", hash = "sha256:2f1a132f1c88724674271d636e6b7351477c27722f2ed789f719f9e3545a3d26"}, 511 | {file = "multidict-5.1.0-cp36-cp36m-win_amd64.whl", hash = "sha256:3a4f32116f8f72ecf2a29dabfb27b23ab7cdc0ba807e8459e59a93a9be9506f6"}, 512 | {file = "multidict-5.1.0-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:46c73e09ad374a6d876c599f2328161bcd95e280f84d2060cf57991dec5cfe76"}, 513 | {file = "multidict-5.1.0-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:018132dbd8688c7a69ad89c4a3f39ea2f9f33302ebe567a879da8f4ca73f0d0a"}, 514 | {file = "multidict-5.1.0-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:4b186eb7d6ae7c06eb4392411189469e6a820da81447f46c0072a41c748ab73f"}, 515 | {file = "multidict-5.1.0-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:3a041b76d13706b7fff23b9fc83117c7b8fe8d5fe9e6be45eee72b9baa75f348"}, 516 | {file = "multidict-5.1.0-cp37-cp37m-manylinux2014_ppc64le.whl", hash = "sha256:051012ccee979b2b06be928a6150d237aec75dd6bf2d1eeeb190baf2b05abc93"}, 517 | {file = "multidict-5.1.0-cp37-cp37m-manylinux2014_s390x.whl", hash = "sha256:6a4d5ce640e37b0efcc8441caeea8f43a06addace2335bd11151bc02d2ee31f9"}, 518 | {file = "multidict-5.1.0-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:5cf3443199b83ed9e955f511b5b241fd3ae004e3cb81c58ec10f4fe47c7dce37"}, 519 | {file = "multidict-5.1.0-cp37-cp37m-win32.whl", hash = "sha256:f200755768dc19c6f4e2b672421e0ebb3dd54c38d5a4f262b872d8cfcc9e93b5"}, 520 | {file = "multidict-5.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:05c20b68e512166fddba59a918773ba002fdd77800cad9f55b59790030bab632"}, 521 | {file = "multidict-5.1.0-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:54fd1e83a184e19c598d5e70ba508196fd0bbdd676ce159feb412a4a6664f952"}, 522 | {file = "multidict-5.1.0-cp38-cp38-manylinux1_i686.whl", hash = "sha256:0e3c84e6c67eba89c2dbcee08504ba8644ab4284863452450520dad8f1e89b79"}, 523 | {file = "multidict-5.1.0-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:dc862056f76443a0db4509116c5cd480fe1b6a2d45512a653f9a855cc0517456"}, 524 | {file = "multidict-5.1.0-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:0e929169f9c090dae0646a011c8b058e5e5fb391466016b39d21745b48817fd7"}, 525 | {file = "multidict-5.1.0-cp38-cp38-manylinux2014_ppc64le.whl", hash = "sha256:d81eddcb12d608cc08081fa88d046c78afb1bf8107e6feab5d43503fea74a635"}, 526 | {file = "multidict-5.1.0-cp38-cp38-manylinux2014_s390x.whl", hash = "sha256:585fd452dd7782130d112f7ddf3473ffdd521414674c33876187e101b588738a"}, 527 | {file = "multidict-5.1.0-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:37e5438e1c78931df5d3c0c78ae049092877e5e9c02dd1ff5abb9cf27a5914ea"}, 528 | {file = "multidict-5.1.0-cp38-cp38-win32.whl", hash = "sha256:07b42215124aedecc6083f1ce6b7e5ec5b50047afa701f3442054373a6deb656"}, 529 | {file = "multidict-5.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:929006d3c2d923788ba153ad0de8ed2e5ed39fdbe8e7be21e2f22ed06c6783d3"}, 530 | {file = "multidict-5.1.0-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:b797515be8743b771aa868f83563f789bbd4b236659ba52243b735d80b29ed93"}, 531 | {file = "multidict-5.1.0-cp39-cp39-manylinux1_i686.whl", hash = "sha256:d5c65bdf4484872c4af3150aeebe101ba560dcfb34488d9a8ff8dbcd21079647"}, 532 | {file = "multidict-5.1.0-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:b47a43177a5e65b771b80db71e7be76c0ba23cc8aa73eeeb089ed5219cdbe27d"}, 533 | {file = "multidict-5.1.0-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:806068d4f86cb06af37cd65821554f98240a19ce646d3cd24e1c33587f313eb8"}, 534 | {file = "multidict-5.1.0-cp39-cp39-manylinux2014_ppc64le.whl", hash = "sha256:46dd362c2f045095c920162e9307de5ffd0a1bfbba0a6e990b344366f55a30c1"}, 535 | {file = "multidict-5.1.0-cp39-cp39-manylinux2014_s390x.whl", hash = "sha256:ace010325c787c378afd7f7c1ac66b26313b3344628652eacd149bdd23c68841"}, 536 | {file = "multidict-5.1.0-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:ecc771ab628ea281517e24fd2c52e8f31c41e66652d07599ad8818abaad38cda"}, 537 | {file = "multidict-5.1.0-cp39-cp39-win32.whl", hash = "sha256:fc13a9524bc18b6fb6e0dbec3533ba0496bbed167c56d0aabefd965584557d80"}, 538 | {file = "multidict-5.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:7df80d07818b385f3129180369079bd6934cf70469f99daaebfac89dca288359"}, 539 | {file = "multidict-5.1.0.tar.gz", hash = "sha256:25b4e5f22d3a37ddf3effc0710ba692cfc792c2b9edfb9c05aefe823256e84d5"}, 540 | ] 541 | packaging = [ 542 | {file = "packaging-21.0-py3-none-any.whl", hash = "sha256:c86254f9220d55e31cc94d69bade760f0847da8000def4dfe1c6b872fd14ff14"}, 543 | {file = "packaging-21.0.tar.gz", hash = "sha256:7dc96269f53a4ccec5c0670940a4281106dd0bb343f47b7471f779df49c2fbe7"}, 544 | ] 545 | pluggy = [ 546 | {file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"}, 547 | {file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"}, 548 | ] 549 | py = [ 550 | {file = "py-1.10.0-py2.py3-none-any.whl", hash = "sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a"}, 551 | {file = "py-1.10.0.tar.gz", hash = "sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3"}, 552 | ] 553 | pyparsing = [ 554 | {file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"}, 555 | {file = "pyparsing-2.4.7.tar.gz", hash = "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1"}, 556 | ] 557 | pytest = [ 558 | {file = "pytest-6.2.4-py3-none-any.whl", hash = "sha256:91ef2131a9bd6be8f76f1f08eac5c5317221d6ad1e143ae03894b862e8976890"}, 559 | {file = "pytest-6.2.4.tar.gz", hash = "sha256:50bcad0a0b9c5a72c8e4e7c9855a3ad496ca6a881a3641b4260605450772c54b"}, 560 | ] 561 | pytest-cov = [ 562 | {file = "pytest-cov-2.10.1.tar.gz", hash = "sha256:47bd0ce14056fdd79f93e1713f88fad7bdcc583dcd7783da86ef2f085a0bb88e"}, 563 | {file = "pytest_cov-2.10.1-py2.py3-none-any.whl", hash = "sha256:45ec2d5182f89a81fc3eb29e3d1ed3113b9e9a873bcddb2a71faaab066110191"}, 564 | ] 565 | rfc3986 = [ 566 | {file = "rfc3986-1.5.0-py2.py3-none-any.whl", hash = "sha256:a86d6e1f5b1dc238b218b012df0aa79409667bb209e58da56d0b94704e712a97"}, 567 | {file = "rfc3986-1.5.0.tar.gz", hash = "sha256:270aaf10d87d0d4e095063c65bf3ddbc6ee3d0b226328ce21e036f946e421835"}, 568 | ] 569 | sanic = [ 570 | {file = "sanic-21.6.0-py3-none-any.whl", hash = "sha256:bc79831325b149a9eade5f9913d78da6a7f3682b98f2700117d1dafe530cb4fe"}, 571 | {file = "sanic-21.6.0.tar.gz", hash = "sha256:eeae886e94f8ee9cee61bf7e3a9c1974cb9cfd666002d2a0a4b82e007aa4f2dd"}, 572 | ] 573 | sanic-routing = [ 574 | {file = "sanic-routing-0.7.0.tar.gz", hash = "sha256:bd6e30b9252d3500e0224e9bb64ba52561638991cefa1f7281c3ba75795c7df9"}, 575 | {file = "sanic_routing-0.7.0-py3-none-any.whl", hash = "sha256:278771ba4182ff60e75dbf8b674158409283f133eb19948a3df7dff02f642719"}, 576 | ] 577 | sniffio = [ 578 | {file = "sniffio-1.2.0-py3-none-any.whl", hash = "sha256:471b71698eac1c2112a40ce2752bb2f4a4814c22a54a3eed3676bc0f5ca9f663"}, 579 | {file = "sniffio-1.2.0.tar.gz", hash = "sha256:c4666eecec1d3f50960c6bdf61ab7bc350648da6c126e3cf6898d8cd4ddcd3de"}, 580 | ] 581 | toml = [ 582 | {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, 583 | {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, 584 | ] 585 | typing-extensions = [ 586 | {file = "typing_extensions-3.10.0.0-py2-none-any.whl", hash = "sha256:0ac0f89795dd19de6b97debb0c6af1c70987fd80a2d62d1958f7e56fcc31b497"}, 587 | {file = "typing_extensions-3.10.0.0-py3-none-any.whl", hash = "sha256:779383f6086d90c99ae41cf0ff39aac8a7937a9283ce0a414e5dd782f4c94a84"}, 588 | {file = "typing_extensions-3.10.0.0.tar.gz", hash = "sha256:50b6f157849174217d0656f99dc82fe932884fb250826c18350e159ec6cdf342"}, 589 | ] 590 | ujson = [ 591 | {file = "ujson-4.0.2-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:e390df0dcc7897ffb98e17eae1f4c442c39c91814c298ad84d935a3c5c7a32fa"}, 592 | {file = "ujson-4.0.2-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:84b1dca0d53b0a8d58835f72ea2894e4d6cf7a5dd8f520ab4cbd698c81e49737"}, 593 | {file = "ujson-4.0.2-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:91396a585ba51f84dc71c8da60cdc86de6b60ba0272c389b6482020a1fac9394"}, 594 | {file = "ujson-4.0.2-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:eb6b25a7670c7537a5998e695fa62ff13c7f9c33faf82927adf4daa460d5f62e"}, 595 | {file = "ujson-4.0.2-cp36-cp36m-win_amd64.whl", hash = "sha256:f8aded54c2bc554ce20b397f72101737dd61ee7b81c771684a7dd7805e6cca0c"}, 596 | {file = "ujson-4.0.2-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:30962467c36ff6de6161d784cd2a6aac1097f0128b522d6e9291678e34fb2b47"}, 597 | {file = "ujson-4.0.2-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:fc51e545d65689c398161f07fd405104956ec27f22453de85898fa088b2cd4bb"}, 598 | {file = "ujson-4.0.2-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:e6e90330670c78e727d6637bb5a215d3e093d8e3570d439fd4922942f88da361"}, 599 | {file = "ujson-4.0.2-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:5e1636b94c7f1f59a8ead4c8a7bab1b12cc52d4c21ababa295ffec56b445fd2a"}, 600 | {file = "ujson-4.0.2-cp37-cp37m-win_amd64.whl", hash = "sha256:e2cadeb0ddc98e3963bea266cc5b884e5d77d73adf807f0bda9eca64d1c509d5"}, 601 | {file = "ujson-4.0.2-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:a214ba5a21dad71a43c0f5aef917cd56a2d70bc974d845be211c66b6742a471c"}, 602 | {file = "ujson-4.0.2-cp38-cp38-manylinux1_i686.whl", hash = "sha256:0190d26c0e990c17ad072ec8593647218fe1c675d11089cd3d1440175b568967"}, 603 | {file = "ujson-4.0.2-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:f273a875c0b42c2a019c337631bc1907f6fdfbc84210cc0d1fff0e2019bbfaec"}, 604 | {file = "ujson-4.0.2-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:d3a87888c40b5bfcf69b4030427cd666893e826e82cc8608d1ba8b4b5e04ea99"}, 605 | {file = "ujson-4.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:7333e8bc45ea28c74ae26157eacaed5e5629dbada32e0103c23eb368f93af108"}, 606 | {file = "ujson-4.0.2-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:b3a6dcc660220539aa718bcc9dbd6dedf2a01d19c875d1033f028f212e36d6bb"}, 607 | {file = "ujson-4.0.2-cp39-cp39-manylinux1_i686.whl", hash = "sha256:0ea07fe57f9157118ca689e7f6db72759395b99121c0ff038d2e38649c626fb1"}, 608 | {file = "ujson-4.0.2-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:4d6d061563470cac889c0a9fd367013a5dbd8efc36ad01ab3e67a57e56cad720"}, 609 | {file = "ujson-4.0.2-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:b5c70704962cf93ec6ea3271a47d952b75ae1980d6c56b8496cec2a722075939"}, 610 | {file = "ujson-4.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:aad6d92f4d71e37ea70e966500f1951ecd065edca3a70d3861b37b176dd6702c"}, 611 | {file = "ujson-4.0.2.tar.gz", hash = "sha256:c615a9e9e378a7383b756b7e7a73c38b22aeb8967a8bfbffd4741f7ffd043c4d"}, 612 | ] 613 | uvloop = [ 614 | {file = "uvloop-0.15.3-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:e71fb9038bfcd7646ca126c5ef19b17e48d4af9e838b2bcfda7a9f55a6552a32"}, 615 | {file = "uvloop-0.15.3-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:7522df4e45e4f25b50adbbbeb5bb9847495c438a628177099d2721f2751ff825"}, 616 | {file = "uvloop-0.15.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ae2b325c0f6d748027f7463077e457006b4fdb35a8788f01754aadba825285ee"}, 617 | {file = "uvloop-0.15.3-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:0de811931e90ae2da9e19ce70ffad73047ab0c1dba7c6e74f9ae1a3aabeb89bd"}, 618 | {file = "uvloop-0.15.3-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:7f4b8a905df909a407c5791fb582f6c03b0d3b491ecdc1cdceaefbc9bf9e08f6"}, 619 | {file = "uvloop-0.15.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2d8ffe44ae709f839c54bacf14ed283f41bee90430c3b398e521e10f8d117b3a"}, 620 | {file = "uvloop-0.15.3-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:63a3288abbc9c8ee979d7e34c34e780b2fbab3e7e53d00b6c80271119f277399"}, 621 | {file = "uvloop-0.15.3-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:5cda65fc60a645470b8525ce014516b120b7057b576fa876cdfdd5e60ab1efbb"}, 622 | {file = "uvloop-0.15.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1ff05116ede1ebdd81802df339e5b1d4cab1dfbd99295bf27e90b4cec64d70e9"}, 623 | {file = "uvloop-0.15.3.tar.gz", hash = "sha256:905f0adb0c09c9f44222ee02f6b96fd88b493478fffb7a345287f9444e926030"}, 624 | ] 625 | websockets = [ 626 | {file = "websockets-9.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:d144b350045c53c8ff09aa1cfa955012dd32f00c7e0862c199edcabb1a8b32da"}, 627 | {file = "websockets-9.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:b4ad84b156cf50529b8ac5cc1638c2cf8680490e3fccb6121316c8c02620a2e4"}, 628 | {file = "websockets-9.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:2cf04601633a4ec176b9cc3d3e73789c037641001dbfaf7c411f89cd3e04fcaf"}, 629 | {file = "websockets-9.1-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:5c8f0d82ea2468282e08b0cf5307f3ad022290ed50c45d5cb7767957ca782880"}, 630 | {file = "websockets-9.1-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:caa68c95bc1776d3521f81eeb4d5b9438be92514ec2a79fececda814099c8314"}, 631 | {file = "websockets-9.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:d2c2d9b24d3c65b5a02cac12cbb4e4194e590314519ed49db2f67ef561c3cf58"}, 632 | {file = "websockets-9.1-cp36-cp36m-win32.whl", hash = "sha256:f31722f1c033c198aa4a39a01905951c00bd1c74f922e8afc1b1c62adbcdd56a"}, 633 | {file = "websockets-9.1-cp36-cp36m-win_amd64.whl", hash = "sha256:3ddff38894c7857c476feb3538dd847514379d6dc844961dc99f04b0384b1b1b"}, 634 | {file = "websockets-9.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:51d04df04ed9d08077d10ccbe21e6805791b78eac49d16d30a1f1fe2e44ba0af"}, 635 | {file = "websockets-9.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:f68c352a68e5fdf1e97288d5cec9296664c590c25932a8476224124aaf90dbcd"}, 636 | {file = "websockets-9.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:b43b13e5622c5a53ab12f3272e6f42f1ce37cd5b6684b2676cb365403295cd40"}, 637 | {file = "websockets-9.1-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:9147868bb0cc01e6846606cd65cbf9c58598f187b96d14dd1ca17338b08793bb"}, 638 | {file = "websockets-9.1-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:836d14eb53b500fd92bd5db2fc5894f7c72b634f9c2a28f546f75967503d8e25"}, 639 | {file = "websockets-9.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:48c222feb3ced18f3dc61168ca18952a22fb88e5eb8902d2bf1b50faefdc34a2"}, 640 | {file = "websockets-9.1-cp37-cp37m-win32.whl", hash = "sha256:900589e19200be76dd7cbaa95e9771605b5ce3f62512d039fb3bc5da9014912a"}, 641 | {file = "websockets-9.1-cp37-cp37m-win_amd64.whl", hash = "sha256:ab5ee15d3462198c794c49ccd31773d8a2b8c17d622aa184f669d2b98c2f0857"}, 642 | {file = "websockets-9.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:85e701a6c316b7067f1e8675c638036a796fe5116783a4c932e7eb8e305a3ffe"}, 643 | {file = "websockets-9.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:b2e71c4670ebe1067fa8632f0d081e47254ee2d3d409de54168b43b0ba9147e0"}, 644 | {file = "websockets-9.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:230a3506df6b5f446fed2398e58dcaafdff12d67fe1397dff196411a9e820d02"}, 645 | {file = "websockets-9.1-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:7df3596838b2a0c07c6f6d67752c53859a54993d4f062689fdf547cb56d0f84f"}, 646 | {file = "websockets-9.1-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:826ccf85d4514609219725ba4a7abd569228c2c9f1968e8be05be366f68291ec"}, 647 | {file = "websockets-9.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:0dd4eb8e0bbf365d6f652711ce21b8fd2b596f873d32aabb0fbb53ec604418cc"}, 648 | {file = "websockets-9.1-cp38-cp38-win32.whl", hash = "sha256:1d0971cc7251aeff955aa742ec541ee8aaea4bb2ebf0245748fbec62f744a37e"}, 649 | {file = "websockets-9.1-cp38-cp38-win_amd64.whl", hash = "sha256:7189e51955f9268b2bdd6cc537e0faa06f8fffda7fb386e5922c6391de51b077"}, 650 | {file = "websockets-9.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e9e5fd6dbdf95d99bc03732ded1fc8ef22ebbc05999ac7e0c7bf57fe6e4e5ae2"}, 651 | {file = "websockets-9.1-cp39-cp39-manylinux1_i686.whl", hash = "sha256:9e7fdc775fe7403dbd8bc883ba59576a6232eac96dacb56512daacf7af5d618d"}, 652 | {file = "websockets-9.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:597c28f3aa7a09e8c070a86b03107094ee5cdafcc0d55f2f2eac92faac8dc67d"}, 653 | {file = "websockets-9.1-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:ad893d889bc700a5835e0a95a3e4f2c39e91577ab232a3dc03c262a0f8fc4b5c"}, 654 | {file = "websockets-9.1-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:1d6b4fddb12ab9adf87b843cd4316c4bd602db8d5efd2fb83147f0458fe85135"}, 655 | {file = "websockets-9.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:ebf459a1c069f9866d8569439c06193c586e72c9330db1390af7c6a0a32c4afd"}, 656 | {file = "websockets-9.1-cp39-cp39-win32.whl", hash = "sha256:be5fd35e99970518547edc906efab29afd392319f020c3c58b0e1a158e16ed20"}, 657 | {file = "websockets-9.1-cp39-cp39-win_amd64.whl", hash = "sha256:85db8090ba94e22d964498a47fdd933b8875a1add6ebc514c7ac8703eb97bbf0"}, 658 | {file = "websockets-9.1.tar.gz", hash = "sha256:276d2339ebf0df4f45df453923ebd2270b87900eda5dfd4a6b0cfa15f82111c3"}, 659 | ] 660 | zipp = [ 661 | {file = "zipp-3.5.0-py3-none-any.whl", hash = "sha256:957cfda87797e389580cb8b9e3870841ca991e2125350677b2ca83a0e99390a3"}, 662 | {file = "zipp-3.5.0.tar.gz", hash = "sha256:f5812b1e007e48cff63449a5e9f4e7ebea716b4111f9c4f9a645f91d579bf0c4"}, 663 | ] 664 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "pytest-sanic" 3 | version = "1.9.1" 4 | description = "a pytest plugin for Sanic" 5 | authors = ["Yun Xu "] 6 | license = "MIT" 7 | readme = "README.rst" 8 | homepage = "https://github.com/yunstanford/pytest-sanic" 9 | repository = "https://github.com/yunstanford/pytest-sanic" 10 | documentation = "https://pytest-sanic.readthedocs.io/en/latest/" 11 | classifiers = [ 12 | "Topic :: Software Development :: Libraries :: Python Modules" 13 | ] 14 | 15 | [tool.poetry.dependencies] 16 | python = ">=3.7" 17 | pytest = ">=5.2" 18 | async_generator = "^1.10" 19 | httpx = ">=0.18.1" 20 | websockets = ">=9.1,<11.0" 21 | 22 | [tool.poetry.dev-dependencies] 23 | pytest = ">=5.3.5" 24 | sanic = "^21.6" 25 | pytest-cov = "^2.8.1" 26 | 27 | [tool.poetry.plugins."pytest11"] 28 | "sanic" = "pytest_sanic.plugin" 29 | 30 | [build-system] 31 | requires = ["poetry>=0.12"] 32 | build-backend = "poetry.masonry.api" 33 | -------------------------------------------------------------------------------- /pytest_sanic/__init__.py: -------------------------------------------------------------------------------- 1 | __version__ = '1.9.1' 2 | -------------------------------------------------------------------------------- /pytest_sanic/plugin.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import pytest 3 | import inspect 4 | import socket 5 | import warnings 6 | from .utils import TestServer, TestClient 7 | 8 | try: 9 | from async_generator import isasyncgenfunction 10 | except ImportError: 11 | from inspect import isasyncgenfunction 12 | 13 | try: 14 | import uvloop 15 | except: # pragma: no cover 16 | uvloop = None 17 | 18 | 19 | LOOP_INIT = None 20 | LOOP_KEY = 'loop' 21 | 22 | def pytest_addoption(parser): 23 | parser.addoption( 24 | '--loop', default=None, 25 | help='run tests with specific loop: aioloop, uvloop') 26 | 27 | 28 | def pytest_configure(config): 29 | global LOOP_INIT 30 | loop_name = config.getoption('--loop') 31 | factory = { 32 | "aioloop": asyncio.new_event_loop, 33 | } 34 | if uvloop is not None: 35 | factory["uvloop"] = uvloop.new_event_loop 36 | 37 | if loop_name: 38 | if loop_name not in factory: 39 | raise ValueError( 40 | "{name} is not valid option".format(name=loop_name) 41 | ) 42 | LOOP_INIT = factory[loop_name] 43 | else: 44 | LOOP_INIT = factory["aioloop"] 45 | 46 | 47 | @pytest.fixture 48 | def loop(): 49 | """ 50 | Default event loop, you should only use this event loop in your tests. 51 | """ 52 | loop = LOOP_INIT() 53 | asyncio.set_event_loop(loop) 54 | yield loop 55 | loop.close() 56 | 57 | 58 | def pytest_pycollect_makeitem(collector, name, obj): 59 | """ 60 | pytest should also collect coroutines. 61 | """ 62 | if collector.funcnamefilter(name) and _is_coroutine(obj): 63 | return list(collector._genfunctions(name, obj)) 64 | 65 | 66 | def pytest_pyfunc_call(pyfuncitem): 67 | """ 68 | Run test coroutines in an event loop. 69 | """ 70 | if _is_coroutine(pyfuncitem.function): 71 | loop = pyfuncitem.funcargs[LOOP_KEY] 72 | funcargs = pyfuncitem.funcargs 73 | testargs = {} 74 | for arg in pyfuncitem._fixtureinfo.argnames: 75 | testargs[arg] = funcargs[arg] 76 | loop.run_until_complete( 77 | loop.create_task( 78 | pyfuncitem.obj(**testargs) 79 | ) 80 | ) 81 | return True 82 | 83 | 84 | def pytest_fixture_setup(fixturedef): 85 | """ 86 | Allow fixtures to be coroutines. Run coroutine fixtures in an event loop. 87 | """ 88 | if isasyncgenfunction(fixturedef.func): 89 | func = fixturedef.func 90 | 91 | strip_request = False 92 | if 'request' not in fixturedef.argnames: 93 | fixturedef.argnames += ('request',) 94 | strip_request = True 95 | 96 | def wrapper(*args, **kwargs): 97 | request = kwargs['request'] 98 | 99 | if strip_request: 100 | del kwargs['request'] 101 | 102 | if 'loop' not in request.fixturenames: 103 | raise Exception( 104 | "Asynchronous fixtures must depend on the 'loop' fixture or " 105 | "be used in tests depending from it." 106 | ) 107 | 108 | loop = request.getfixturevalue('loop') 109 | # for async generators, we need to advance the generator once, 110 | # then advance it again in a finalizer 111 | gen = func(*args, **kwargs) 112 | 113 | def finalizer(): 114 | try: 115 | return loop.run_until_complete(gen.__anext__()) 116 | except StopAsyncIteration: # NOQA 117 | pass 118 | 119 | request.addfinalizer(finalizer) 120 | return loop.run_until_complete(gen.__anext__()) 121 | 122 | fixturedef.func = wrapper 123 | 124 | elif asyncio.iscoroutinefunction(fixturedef.func): 125 | func = fixturedef.func 126 | 127 | strip_request = False 128 | if 'request' not in fixturedef.argnames: 129 | fixturedef.argnames += ('request',) 130 | strip_request = True 131 | 132 | def wrapper(*args, **kwargs): 133 | request = kwargs['request'] 134 | if 'loop' not in request.fixturenames: 135 | raise Exception( 136 | "Asynchronous fixtures must depend on the 'loop' fixture or " 137 | "be used in tests depending from it." 138 | ) 139 | 140 | loop = request.getfixturevalue('loop') 141 | 142 | if strip_request: 143 | del kwargs['request'] 144 | 145 | return loop.run_until_complete(func(*args, **kwargs)) 146 | 147 | fixturedef.func = wrapper 148 | 149 | else: 150 | return 151 | 152 | 153 | def pytest_runtest_setup(item): 154 | """ 155 | append a loop fixture to all test func. 156 | """ 157 | if hasattr(item, 'fixturenames') and LOOP_KEY not in item.fixturenames: 158 | item.fixturenames.append(LOOP_KEY) 159 | 160 | 161 | @pytest.fixture 162 | def unused_port(): 163 | with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: 164 | s.bind(('127.0.0.1', 0)) 165 | return s.getsockname()[1] 166 | 167 | 168 | @pytest.fixture 169 | def test_server(loop): 170 | """ 171 | Create a TestServer instance based on a Sanic Application. 172 | 173 | test_server(app, **kwargs) 174 | """ 175 | 176 | servers = [] 177 | 178 | async def create_server(app, **kwargs): 179 | server = TestServer(app, **kwargs) 180 | await server.start_server() 181 | servers.append(server) 182 | return server 183 | 184 | yield create_server 185 | 186 | # Close Server 187 | if servers: 188 | for server in servers: 189 | loop.run_until_complete(server.close()) 190 | 191 | 192 | @pytest.fixture 193 | def sanic_client(loop): 194 | """ 195 | Create a TestClient instance for test easy use. 196 | 197 | test_client(app, **kwargs) 198 | """ 199 | clients = [] 200 | 201 | async def create_client(app, **kwargs): 202 | client = TestClient(app, **kwargs) 203 | await client.start_server() 204 | clients.append(client) 205 | return client 206 | 207 | yield create_client 208 | 209 | # Clean up 210 | if clients: 211 | for client in clients: 212 | loop.run_until_complete(client.close()) 213 | 214 | 215 | @pytest.fixture 216 | def test_client(loop): 217 | warnings.warn("test_client is deprecated, please use sanic_client instead.", 218 | DeprecationWarning, 219 | stacklevel=2) 220 | clients = [] 221 | 222 | async def create_client(app, **kwargs): 223 | client = TestClient(app, **kwargs) 224 | await client.start_server() 225 | clients.append(client) 226 | return client 227 | 228 | yield create_client 229 | 230 | # Clean up 231 | if clients: 232 | for client in clients: 233 | loop.run_until_complete(client.close()) 234 | 235 | 236 | # Helper Functions 237 | def _is_coroutine(obj): 238 | return asyncio.iscoroutinefunction(obj) or inspect.isgeneratorfunction(obj) 239 | -------------------------------------------------------------------------------- /pytest_sanic/utils.py: -------------------------------------------------------------------------------- 1 | import socket 2 | import asyncio 3 | import warnings 4 | import httpx 5 | import websockets 6 | 7 | from sanic.server import serve, HttpProtocol 8 | from inspect import isawaitable 9 | from sanic.app import Sanic 10 | 11 | 12 | HEAD = 'HEAD' 13 | GET = 'GET' 14 | DELETE = 'DELETE' 15 | OPTIONS = 'OPTIONS' 16 | PATCH = 'PATCH' 17 | POST = 'POST' 18 | PUT = 'PUT' 19 | 20 | 21 | async def trigger_events(events, loop): 22 | """Trigger events (functions or async) 23 | 24 | :param events: one or more sync or async functions to execute 25 | :param loop: event loop 26 | """ 27 | for event in events: 28 | result = event(loop) 29 | if isawaitable(result): 30 | await result 31 | 32 | 33 | class TestServer: 34 | 35 | """ 36 | a test server class designed for easy testing in Sanic-based Application. 37 | """ 38 | 39 | def __init__(self, app, host='127.0.0.1', 40 | loop=None, protocol=None, 41 | backlog=100, ssl=None, 42 | scheme=None, connections=None, 43 | **kwargs): 44 | if not isinstance(app, Sanic): 45 | raise TypeError("app should be a Sanic application.") 46 | if loop: 47 | warnings.warn("passing through `loop` is deprecated.", 48 | DeprecationWarning, 49 | stacklevel=2) 50 | self.app = app 51 | self.loop = loop or asyncio.get_event_loop() 52 | self.host = host 53 | self.protocol = protocol or HttpProtocol 54 | self.backlog = backlog 55 | self.server = None 56 | self.port = None 57 | self.connections = connections if connections else set() 58 | self.ssl = ssl 59 | if scheme is None: 60 | if self.ssl: 61 | self.scheme = "https" 62 | else: 63 | self.scheme = "http" 64 | else: 65 | self.scheme = scheme 66 | 67 | # Listeners 68 | self.before_server_start = None 69 | self.after_server_start = None 70 | self.before_server_stop = None 71 | self.after_server_stop = None 72 | 73 | # state 74 | self.closed = None 75 | self.is_running = False 76 | 77 | async def start_server(self): 78 | self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 79 | self.socket.bind((self.host, 0)) 80 | self.port = self.socket.getsockname()[1] 81 | 82 | # server settings 83 | server_settings = self.app._helper( 84 | host=self.host, port=self.port, 85 | ssl=self.ssl, sock=self.socket, 86 | loop=self.loop, protocol=self.protocol, 87 | backlog=self.backlog, run_async=True) 88 | 89 | # clean up host/port. 90 | # host/port should not be used. 91 | server_settings['host'] = None 92 | server_settings['port'] = None 93 | 94 | # Let's get listeners 95 | self.before_server_start = server_settings.get('before_start', []) 96 | self.after_server_start = server_settings.get('after_start', []) 97 | self.before_server_stop = server_settings.get('before_stop', []) 98 | self.after_server_stop = server_settings.get('after_stop', []) 99 | 100 | server_settings.pop("main_start", None) 101 | server_settings.pop("main_stop", None) 102 | 103 | # Trigger before_start events 104 | await trigger_events(self.before_server_start, self.loop) 105 | 106 | # Connections 107 | server_settings["connections"] = self.connections 108 | 109 | # start server 110 | self.server = await serve(**server_settings) 111 | self.is_running = True 112 | self.app.is_running = True 113 | 114 | # Trigger after_start events 115 | await trigger_events(self.after_server_start, self.loop) 116 | 117 | async def close(self): 118 | """ 119 | Close server. 120 | """ 121 | if self.is_running and not self.closed: 122 | # Trigger before_stop events 123 | await trigger_events(self.before_server_stop, self.loop) 124 | 125 | # Stop Server 126 | self.server.close() 127 | await self.server.wait_closed() 128 | 129 | for connection in self.connections: 130 | connection.close_if_idle() 131 | 132 | # Force close connections 133 | coros = [] 134 | for conn in self.connections: 135 | if hasattr(conn, "websocket") and conn.websocket: 136 | coros.append(conn.websocket.close_connection()) 137 | else: 138 | conn.close() 139 | await asyncio.gather(*coros) 140 | 141 | # Trigger after_stop events 142 | await trigger_events(self.after_server_stop, self.loop) 143 | 144 | self.closed = True 145 | self.is_running = False 146 | self.app.is_running = False 147 | self.port = None 148 | 149 | def has_started(self): 150 | """ 151 | Check if server has started. 152 | """ 153 | return self.server is not None 154 | 155 | def make_url(self, uri): 156 | return "{scheme}://{host}:{port}{uri}".format( 157 | scheme=self.scheme, 158 | host=self.host, 159 | port=self.port, 160 | uri=uri 161 | ) 162 | 163 | 164 | class TestClient: 165 | 166 | """ 167 | a test client class designed for easy testing in Sanic-based Application. 168 | """ 169 | 170 | def __init__(self, app, loop=None, 171 | host='127.0.0.1', 172 | protocol=None, 173 | ssl=None, 174 | scheme=None, 175 | **kwargs): 176 | if not isinstance(app, Sanic): 177 | raise TypeError("app should be a Sanic application.") 178 | 179 | if loop: 180 | warnings.warn("passing through `loop` is deprecated.", 181 | DeprecationWarning, 182 | stacklevel=2) 183 | self._app = app 184 | # we should use '127.0.0.1' in most cases. 185 | self._host = host 186 | self._ssl = ssl 187 | self._scheme = scheme 188 | self._protocol = HttpProtocol if protocol is None else protocol 189 | self._closed = False 190 | self._server = TestServer( 191 | self._app, loop=loop, 192 | protocol=self._protocol, ssl=self._ssl, 193 | scheme=self._scheme) 194 | self._session = httpx.AsyncClient(**kwargs) 195 | # Let's collect responses objects and websocket objects, 196 | # and clean up when test is done. 197 | self._responses = [] 198 | self._websockets = [] 199 | 200 | @property 201 | def app(self): 202 | return self._app 203 | 204 | @property 205 | def host(self): 206 | return self._server.host 207 | 208 | @property 209 | def port(self): 210 | return self._server.port 211 | 212 | @property 213 | def server(self): 214 | return self._server 215 | 216 | @property 217 | def session(self): 218 | return self._session 219 | 220 | def make_url(self, uri): 221 | return self._server.make_url(uri) 222 | 223 | async def start_server(self): 224 | """ 225 | Start a TestServer that running Sanic application. 226 | """ 227 | await self._server.start_server() 228 | 229 | async def close(self): 230 | """ 231 | Close TestClient obj, and cleanup all fixtures created by test client. 232 | """ 233 | if not self._closed: 234 | for resp in self._responses: 235 | await resp.aclose() 236 | for ws in self._websockets: 237 | await ws.close() 238 | await self._session.aclose() 239 | await self._server.close() 240 | self._closed = True 241 | 242 | async def _request(self, method, uri, *args, **kwargs): 243 | url = self._server.make_url(uri) 244 | response = await self._session.request( 245 | method, url, *args, **kwargs 246 | ) 247 | self._responses.append(response) 248 | return response 249 | 250 | async def get(self, uri, *args, **kwargs): 251 | return await self._request(GET, uri, *args, **kwargs) 252 | 253 | async def post(self, uri, *args, **kwargs): 254 | return await self._request(POST, uri, *args, **kwargs) 255 | 256 | async def put(self, uri, *args, **kwargs): 257 | return await self._request(PUT, uri, *args, **kwargs) 258 | 259 | async def delete(self, uri, *args, **kwargs): 260 | return await self._request(DELETE, uri, *args, **kwargs) 261 | 262 | async def patch(self, uri, *args, **kwargs): 263 | return await self._request(PATCH, uri, *args, **kwargs) 264 | 265 | async def options(self, uri, *args, **kwargs): 266 | return await self._request(OPTIONS, uri, *args, **kwargs) 267 | 268 | async def head(self, uri, *args, **kwargs): 269 | return await self._request(HEAD, uri, *args, **kwargs) 270 | 271 | async def ws_connect(self, uri, *args, **kwargs): 272 | """ 273 | Create a websocket connection. 274 | """ 275 | url = self._server.make_url(uri) 276 | ws_conn = await websockets.connect(url, *args, **kwargs) 277 | # Save it, clean up later. 278 | self._websockets.append(ws_conn) 279 | return ws_conn 280 | 281 | # Context Manager 282 | async def __aenter__(self): 283 | await self.start_server() 284 | return self 285 | 286 | async def __aexit__(self, exc_type, exc_value, traceback): 287 | await self.close() 288 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | from setuptools import setup, find_packages 4 | 5 | base = os.path.dirname(os.path.abspath(__file__)) 6 | 7 | README_PATH = os.path.join(base, "README.rst") 8 | 9 | install_requires = [ 10 | 'pytest>=5.2', 11 | 'httpx>=0.15.4', 12 | 'async_generator>=1.10', 13 | 'websockets>=9.1,<11.0', 14 | ] 15 | 16 | tests_require = [] 17 | 18 | setup(name='pytest-sanic', 19 | version='1.9.1', 20 | description='a pytest plugin for Sanic', 21 | long_description=open(README_PATH).read(), 22 | author='Yun Xu', 23 | author_email='yunxu1992@gmail.com', 24 | license='ASL 2.0', 25 | url='https://github.com/yunstanford/pytest-sanic/', 26 | packages=find_packages(exclude=["tests.*", "tests"]), 27 | install_requires=install_requires, 28 | classifiers=[ 29 | 'Development Status :: 5 - Production/Stable', 30 | 'Operating System :: MacOS', 31 | 'Operating System :: POSIX :: Linux', 32 | 'Topic :: System :: Software Distribution', 33 | 'License :: OSI Approved :: MIT License', 34 | 'Programming Language :: Python', 35 | 'Programming Language :: Python :: 3', 36 | 'Programming Language :: Python :: 3.7', 37 | 'Programming Language :: Python :: 3.8', 38 | ], 39 | entry_points={ 40 | 'pytest11': ['sanic = pytest_sanic.plugin'], 41 | }, 42 | ) 43 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yunstanford/pytest-sanic/08395f2d5ff9886525f20276458b702299f3a5f1/tests/__init__.py -------------------------------------------------------------------------------- /tests/conftest.py: -------------------------------------------------------------------------------- 1 | import sanic 2 | import asyncio 3 | import pytest 4 | 5 | from sanic import Sanic 6 | from sanic import Blueprint 7 | from sanic.websocket import WebSocketProtocol 8 | from sanic import response 9 | 10 | 11 | collect_ignore = [] 12 | Sanic.test_mode = True 13 | 14 | if sanic.__version__ <= '0.6.0': 15 | collect_ignore.append("test_client_websocket.py") 16 | 17 | 18 | # pytest_plugins = 'pytest_sanic.plugin' 19 | 20 | 21 | @pytest.fixture 22 | def app(): 23 | app = Sanic("test_sanic_app") 24 | 25 | @app.route("/test_get", methods=['GET']) 26 | async def test_get(request): 27 | return response.json({"GET": True}) 28 | 29 | @app.route("/test_post", methods=['POST']) 30 | async def test_post(request): 31 | return response.json({"POST": True}) 32 | 33 | @app.route("/test_put", methods=['PUT']) 34 | async def test_put(request): 35 | return response.json({"PUT": True}) 36 | 37 | @app.route("/test_delete", methods=['DELETE']) 38 | async def test_delete(request): 39 | return response.json({"DELETE": True}) 40 | 41 | @app.route("/test_patch", methods=['PATCH']) 42 | async def test_patch(request): 43 | return response.json({"PATCH": True}) 44 | 45 | @app.route("/test_options", methods=['OPTIONS']) 46 | async def test_options(request): 47 | return response.json({"OPTIONS": True}) 48 | 49 | @app.route("/test_head", methods=['HEAD']) 50 | async def test_head(request): 51 | return response.json({"HEAD": True}) 52 | 53 | @app.websocket("/test_ws") 54 | async def test_ws(request, ws): 55 | data = await ws.recv() 56 | await ws.send(data) 57 | 58 | @app.route("/test_passing_headers", methods=['GET']) 59 | async def test_get(request): 60 | return response.json({"headers": dict(request.headers)}) 61 | 62 | @app.listener("before_server_start") 63 | async def mock_init_db(app, loop): 64 | await asyncio.sleep(0.01) 65 | 66 | # For Blueprint Group 67 | bp = Blueprint("blueprint_route", url_prefix="/bp_route") 68 | 69 | @bp.route("/test_get") 70 | async def bp_root(request): 71 | return response.json({"blueprint": "get"}) 72 | 73 | bp_group = Blueprint.group(bp, url_prefix="/bp_group") 74 | 75 | app.blueprint(bp_group) 76 | 77 | yield app 78 | 79 | 80 | @pytest.fixture 81 | def sanic_server(loop, app, test_server): 82 | return loop.run_until_complete(test_server(app)) 83 | 84 | 85 | @pytest.fixture 86 | def test_cli(loop, app, sanic_client): 87 | return loop.run_until_complete(sanic_client(app)) 88 | 89 | 90 | @pytest.fixture 91 | def test_cli_ws(loop, app, sanic_client): 92 | return loop.run_until_complete(sanic_client(app, scheme='ws', protocol=WebSocketProtocol)) 93 | 94 | -------------------------------------------------------------------------------- /tests/test_async.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import asyncio 3 | 4 | 5 | async def test_simple_async_func(): 6 | await asyncio.sleep(0.1) 7 | assert True -------------------------------------------------------------------------------- /tests/test_client.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from sanic.app import Sanic 4 | from sanic import response 5 | 6 | 7 | async def test_fixture_sanic_client_get_properties(test_cli): 8 | assert test_cli.app is not None 9 | assert test_cli.host is not None 10 | assert test_cli.port is not None 11 | assert test_cli.server is not None 12 | assert test_cli.session is not None 13 | 14 | 15 | async def test_fixture_sanic_client_make_url(test_cli): 16 | uri = '/test' 17 | url = test_cli.make_url(uri) 18 | assert url == "http://127.0.0.1:{port}/test".format(port=str(test_cli.port)) 19 | 20 | 21 | async def test_fixture_sanic_client_get(test_cli): 22 | resp = await test_cli.get('/test_get') 23 | assert resp.status_code == 200 24 | resp_json = resp.json() 25 | assert resp_json == {"GET": True} 26 | 27 | 28 | async def test_fixture_sanic_client_post(test_cli): 29 | resp = await test_cli.post('/test_post') 30 | assert resp.status_code == 200 31 | resp_json = resp.json() 32 | assert resp_json == {"POST": True} 33 | 34 | 35 | async def test_fixture_sanic_client_put(test_cli): 36 | resp = await test_cli.put('/test_put') 37 | assert resp.status_code == 200 38 | resp_json = resp.json() 39 | assert resp_json == {"PUT": True} 40 | 41 | 42 | async def test_fixture_sanic_client_delete(test_cli): 43 | resp = await test_cli.delete('/test_delete') 44 | assert resp.status_code == 200 45 | resp_json = resp.json() 46 | assert resp_json == {"DELETE": True} 47 | 48 | 49 | async def test_fixture_sanic_client_patch(test_cli): 50 | resp = await test_cli.patch('/test_patch') 51 | assert resp.status_code == 200 52 | resp_json = resp.json() 53 | assert resp_json == {"PATCH": True} 54 | 55 | 56 | async def test_fixture_sanic_client_options(test_cli): 57 | resp = await test_cli.options('/test_options') 58 | assert resp.status_code == 200 59 | resp_json = resp.json() 60 | assert resp_json == {"OPTIONS": True} 61 | 62 | 63 | async def test_fixture_sanic_client_head(test_cli): 64 | resp = await test_cli.head('/test_head') 65 | assert resp.status_code == 200 66 | # HEAD should not have body 67 | assert resp.content == b"" 68 | 69 | 70 | async def test_fixture_sanic_client_blueprint_get(test_cli): 71 | resp = await test_cli.get('/bp_group/bp_route/test_get') 72 | assert resp.status_code == 200 73 | resp_json = resp.json() 74 | assert resp_json == {"blueprint": "get"} 75 | 76 | 77 | async def test_fixture_sanic_client_close(test_cli): 78 | resp = await test_cli.get('/test_get') 79 | assert resp.status_code == 200 80 | resp_json = resp.json() 81 | assert resp_json == {"GET": True} 82 | await test_cli.close() 83 | assert test_cli._closed == True 84 | 85 | 86 | async def test_fixture_sanic_client_passing_headers(test_cli): 87 | headers={"authorization": "Basic bG9naW46cGFzcw=="} 88 | resp = await test_cli.get('/test_passing_headers', headers=headers) 89 | assert resp.status_code == 200 90 | resp_json = resp.json() 91 | assert resp_json["headers"]["authorization"] == headers["authorization"] 92 | 93 | 94 | async def test_fixture_sanic_client_context_manager(app, sanic_client): 95 | async with await sanic_client(app) as test_cli: 96 | resp = await test_cli.get('/test_get') 97 | assert resp.status_code == 200 98 | resp_json = resp.json() 99 | assert resp_json == {"GET": True} 100 | 101 | 102 | async def test_fixture_test_client_context_manager(app, test_client): 103 | async with await test_client(app) as test_cli: 104 | resp = await test_cli.get('/test_get') 105 | assert resp.status_code == 200 106 | resp_json = resp.json() 107 | assert resp_json == {"GET": True} 108 | 109 | 110 | async def test_fixture_sanic_client_raise_exception_for_non_sanic_app(sanic_client): 111 | class SimpleApplication: 112 | pass 113 | with pytest.raises(TypeError): 114 | await sanic_client(SimpleApplication()) 115 | 116 | 117 | async def test_fixture_sanic_client_app_is_running(test_cli): 118 | assert test_cli.app.is_running == True 119 | -------------------------------------------------------------------------------- /tests/test_client_websocket.py: -------------------------------------------------------------------------------- 1 | # This is a special test. it will fail for sanic <= 0.5.4 2 | async def test_fixture_sanic_client_ws(test_cli_ws): 3 | ws_conn = await test_cli_ws.ws_connect('/test_ws') 4 | data = 'hello world!' 5 | await ws_conn.send(data) 6 | msg = await ws_conn.recv() 7 | assert msg == data 8 | await ws_conn.close() 9 | -------------------------------------------------------------------------------- /tests/test_fixture.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import asyncio 3 | 4 | from unittest import mock 5 | from async_generator import async_generator, yield_ 6 | 7 | 8 | @pytest.fixture 9 | async def async_gen_fixture_sleep(): 10 | await asyncio.sleep(0.1) 11 | yield_('a value') 12 | 13 | 14 | @pytest.fixture 15 | async def async_fixture_sleep(): 16 | return await asyncio.sleep(0.1) 17 | 18 | 19 | @pytest.fixture 20 | async def async_fixture(): 21 | client = mock.Mock() 22 | future = asyncio.Future() 23 | future.set_result({"foo": "bar"}) 24 | client.assume_role.return_value = future 25 | return await client.assume_role() 26 | 27 | 28 | async def test_async_fixture(async_fixture): 29 | assert async_fixture == {"foo": "bar"} 30 | 31 | 32 | @pytest.fixture 33 | def mock_fixture(): 34 | return mock.Mock(return_value={"foo": "bar"}) 35 | 36 | 37 | @pytest.fixture 38 | @async_generator 39 | async def async_gen_fixture(mock_fixture): 40 | await yield_(mock_fixture()) 41 | 42 | 43 | async def test_async_gen_fixture(async_gen_fixture, mock_fixture): 44 | assert mock_fixture.called 45 | assert async_gen_fixture == {"foo": "bar"} 46 | -------------------------------------------------------------------------------- /tests/test_server.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | 4 | async def test_fixture_test_server_get_properties(sanic_server): 5 | assert sanic_server.is_running is True 6 | assert sanic_server.has_started() is True 7 | assert sanic_server.port is not None 8 | url = sanic_server.make_url('/test') 9 | assert url == 'http://127.0.0.1:{port}/test'.format(port=str(sanic_server.port)) 10 | 11 | 12 | async def test_fixture_test_server_close(sanic_server): 13 | # close 14 | await sanic_server.close() 15 | assert sanic_server.is_running is False 16 | assert sanic_server.closed is True 17 | assert sanic_server.port is None 18 | 19 | 20 | async def test_fixture_test_server_raise_exception_for_non_sanic_app(test_server): 21 | 22 | class SimpleApplication: 23 | pass 24 | 25 | with pytest.raises(TypeError): 26 | await test_server(SimpleApplication()) -------------------------------------------------------------------------------- /tests/test_simple_fixtures.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | 4 | def test_fixture_unused_port(unused_port): 5 | assert unused_port > 1024 and unused_port < 65535 6 | 7 | 8 | class MyEventLoop: 9 | 10 | def close(self): 11 | pass 12 | 13 | 14 | @pytest.fixture 15 | def loop(): 16 | loop = MyEventLoop() 17 | yield loop 18 | loop.close() 19 | 20 | 21 | def test_loop_override(loop): 22 | assert isinstance(loop, MyEventLoop) is True 23 | --------------------------------------------------------------------------------