├── .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 |
--------------------------------------------------------------------------------