Compare commits
	
		
			49 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | c3003413d3 | ||
|   | fe3fdc5d83 | ||
|   | b66fb6f9e8 | ||
|   | bf6175fb20 | ||
|   | 7475897a03 | ||
|   | 58ca887be4 | ||
|   | 449bc417a3 | ||
|   | 262f89f2b6 | ||
|   | 38337446cf | ||
|   | ac1331ea4c | ||
|   | 2b947e831f | ||
|   | 112715eb80 | ||
|   | ea9cf365bc | ||
|   | b9b3b4051a | ||
|   | ecb6db29e6 | ||
|   | 6515dde64b | ||
|   | 01d2a2aa3c | ||
|   | 39e12accb8 | ||
|   | 39fe6ea5b1 | ||
|   | fc4b7df088 | ||
|   | 35f28f7a64 | ||
|   | 614be40438 | ||
|   | bde0428d0c | ||
|   | 63567c2ae4 | ||
|   | ec10f337b6 | ||
|   | d0f0e73e96 | ||
|   | b4fe2c8a6b | ||
|   | 33da0771d1 | ||
|   | 75994cd915 | ||
|   | c0839afdde | ||
|   | 5961da3f57 | ||
|   | 41f1809351 | ||
|   | 5fbdcb62e4 | ||
|   | 677b83e9f8 | ||
|   | 6a5c8becac | ||
|   | fd23b99d60 | ||
|   | 634b586df3 | ||
|   | 4ca3e98082 | ||
|   | d18a776964 | ||
|   | d6b4d7d265 | ||
|   | 33ee4c21b3 | ||
|   | a026cd7195 | ||
|   | 7b1bce8d90 | ||
|   | 217a7c5161 | ||
|   | 2949e3422d | ||
|   | 16ea99b0c0 | ||
|   | 19b84ce9f0 | ||
|   | 12521cd5b4 | ||
|   | f41435fae3 | 
| @@ -17,6 +17,12 @@ environment: | |||||||
|       PYTHON_VERSION: "3.8.x" |       PYTHON_VERSION: "3.8.x" | ||||||
|       PYTHON_ARCH: "64" |       PYTHON_ARCH: "64" | ||||||
|  |  | ||||||
|  |     # - TOXENV: py39-no-ext | ||||||
|  |     #   PYTHON: "C:\\Python39-x64\\python" | ||||||
|  |     #   PYTHONPATH: "C:\\Python39-x64" | ||||||
|  |     #   PYTHON_VERSION: "3.9.x" | ||||||
|  |     #   PYTHON_ARCH: "64" | ||||||
|  |  | ||||||
| init: SET "PATH=%PYTHON%;%PYTHON%\\Scripts;%PATH%" | init: SET "PATH=%PYTHON%;%PYTHON%\\Scripts;%PATH%" | ||||||
|  |  | ||||||
| install: | install: | ||||||
|   | |||||||
							
								
								
									
										12
									
								
								.github/FUNDING.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								.github/FUNDING.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,12 @@ | |||||||
|  | # These are supported funding model platforms | ||||||
|  |  | ||||||
|  | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] | ||||||
|  | patreon: # Replace with a single Patreon username | ||||||
|  | open_collective: sanic-org # Replace with a single Open Collective username | ||||||
|  | ko_fi: # Replace with a single Ko-fi username | ||||||
|  | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel | ||||||
|  | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry | ||||||
|  | liberapay: # Replace with a single Liberapay username | ||||||
|  | issuehunt: # Replace with a single IssueHunt username | ||||||
|  | otechie: # Replace with a single Otechie username | ||||||
|  | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] | ||||||
							
								
								
									
										27
									
								
								.travis.yml
									
									
									
									
									
								
							
							
						
						
									
										27
									
								
								.travis.yml
									
									
									
									
									
								
							| @@ -31,6 +31,16 @@ matrix: | |||||||
|       dist: xenial |       dist: xenial | ||||||
|       sudo: true |       sudo: true | ||||||
|       name: "Python 3.8 without Extensions" |       name: "Python 3.8 without Extensions" | ||||||
|  |     - env: TOX_ENV=py39 | ||||||
|  |       python: 3.9 | ||||||
|  |       dist: bionic | ||||||
|  |       sudo: true | ||||||
|  |       name: "Python 3.9 with Extensions" | ||||||
|  |     - env: TOX_ENV=py39-no-ext | ||||||
|  |       python: 3.9 | ||||||
|  |       dist: bionic | ||||||
|  |       sudo: true | ||||||
|  |       name: "Python 3.9 without Extensions" | ||||||
|     - env: TOX_ENV=type-checking |     - env: TOX_ENV=type-checking | ||||||
|       python: 3.6 |       python: 3.6 | ||||||
|       name: "Python 3.6 Type checks" |       name: "Python 3.6 Type checks" | ||||||
| @@ -40,6 +50,10 @@ matrix: | |||||||
|     - env: TOX_ENV=type-checking |     - env: TOX_ENV=type-checking | ||||||
|       python: 3.8 |       python: 3.8 | ||||||
|       name: "Python 3.8 Type checks" |       name: "Python 3.8 Type checks" | ||||||
|  |     - env: TOX_ENV=type-checking | ||||||
|  |       python: 3.9 | ||||||
|  |       dist: bionic | ||||||
|  |       name: "Python 3.9 Type checks" | ||||||
|     - env: TOX_ENV=lint |     - env: TOX_ENV=lint | ||||||
|       python: 3.6 |       python: 3.6 | ||||||
|       name: "Python 3.6 Linter checks" |       name: "Python 3.6 Linter checks" | ||||||
| @@ -61,23 +75,28 @@ matrix: | |||||||
|       dist: xenial |       dist: xenial | ||||||
|       sudo: true |       sudo: true | ||||||
|       name: "Python 3.8 Bandit security scan" |       name: "Python 3.8 Bandit security scan" | ||||||
|  |     - env: TOX_ENV=security | ||||||
|  |       python: 3.9 | ||||||
|  |       dist: bionic | ||||||
|  |       sudo: true | ||||||
|  |       name: "Python 3.9 Bandit security scan" | ||||||
|     - env: TOX_ENV=docs |     - env: TOX_ENV=docs | ||||||
|       python: 3.7 |       python: 3.7 | ||||||
|       dist: xenial |       dist: xenial | ||||||
|       sudo: true |       sudo: true | ||||||
|       name: "Python 3.7 Documentation tests" |       name: "Python 3.7 Documentation tests" | ||||||
|     - env: TOX_ENV=pyNightly |     - env: TOX_ENV=pyNightly | ||||||
|       python: 'nightly' |       python: "nightly" | ||||||
|       name: "Python nightly with Extensions" |       name: "Python nightly with Extensions" | ||||||
|     - env: TOX_ENV=pyNightly-no-ext |     - env: TOX_ENV=pyNightly-no-ext | ||||||
|       python: 'nightly' |       python: "nightly" | ||||||
|       name: "Python nightly without Extensions" |       name: "Python nightly without Extensions" | ||||||
|   allow_failures: |   allow_failures: | ||||||
|     - env: TOX_ENV=pyNightly |     - env: TOX_ENV=pyNightly | ||||||
|       python: 'nightly' |       python: "nightly" | ||||||
|       name: "Python nightly with Extensions" |       name: "Python nightly with Extensions" | ||||||
|     - env: TOX_ENV=pyNightly-no-ext |     - env: TOX_ENV=pyNightly-no-ext | ||||||
|       python: 'nightly' |       python: "nightly" | ||||||
|       name: "Python nightly without Extensions" |       name: "Python nightly without Extensions" | ||||||
| install: | install: | ||||||
|   - pip install -U tox |   - pip install -U tox | ||||||
|   | |||||||
							
								
								
									
										112
									
								
								CHANGELOG.rst
									
									
									
									
									
								
							
							
						
						
									
										112
									
								
								CHANGELOG.rst
									
									
									
									
									
								
							| @@ -1,3 +1,115 @@ | |||||||
|  | Version 20.12.0 | ||||||
|  | =============== | ||||||
|  |  | ||||||
|  | Features | ||||||
|  | ******** | ||||||
|  |  | ||||||
|  |   * | ||||||
|  |     `#1945 <https://github.com/huge-success/sanic/pull/1945>`_ | ||||||
|  |     Static route more verbose if file not found | ||||||
|  |  | ||||||
|  |   * | ||||||
|  |     `#1954 <https://github.com/huge-success/sanic/pull/1954>`_ | ||||||
|  |     Fix static routes registration on a blueprint | ||||||
|  |  | ||||||
|  |   * | ||||||
|  |     `#1961 <https://github.com/huge-success/sanic/pull/1961>`_ | ||||||
|  |     Add Python 3.9 support | ||||||
|  |  | ||||||
|  |   * | ||||||
|  |     `#1962 <https://github.com/huge-success/sanic/pull/1962>`_ | ||||||
|  |     Sanic CLI upgrade | ||||||
|  |  | ||||||
|  |   * | ||||||
|  |     `#1967 <https://github.com/huge-success/sanic/pull/1967>`_ | ||||||
|  |     Update aiofile version requirements | ||||||
|  |  | ||||||
|  |   * | ||||||
|  |     `#1969 <https://github.com/huge-success/sanic/pull/1969>`_ | ||||||
|  |     Update multidict version requirements | ||||||
|  |  | ||||||
|  |   * | ||||||
|  |     `#1970 <https://github.com/huge-success/sanic/pull/1970>`_ | ||||||
|  |     Add py.typed file | ||||||
|  |  | ||||||
|  |   * | ||||||
|  |     `#1972 <https://github.com/huge-success/sanic/pull/1972>`_ | ||||||
|  |     Speed optimization in request handler | ||||||
|  |  | ||||||
|  |   * | ||||||
|  |     `#1979 <https://github.com/huge-success/sanic/pull/1979>`_ | ||||||
|  |     Add app registry and Sanic class level app retrieval | ||||||
|  |  | ||||||
|  | Bugfixes | ||||||
|  | ******** | ||||||
|  |  | ||||||
|  |   * | ||||||
|  |     `#1965 <https://github.com/huge-success/sanic/pull/1965>`_ | ||||||
|  |     Fix Chunked Transport-Encoding in ASGI streaming response | ||||||
|  |  | ||||||
|  | Deprecations and Removals | ||||||
|  | ************************* | ||||||
|  |  | ||||||
|  |   * | ||||||
|  |     `#1981 <https://github.com/huge-success/sanic/pull/1981>`_ | ||||||
|  |     Cleanup and remove deprecated code | ||||||
|  |  | ||||||
|  | Developer infrastructure | ||||||
|  | ************************ | ||||||
|  |  | ||||||
|  |   * | ||||||
|  |     `#1956 <https://github.com/huge-success/sanic/pull/1956>`_ | ||||||
|  |     Fix load module test | ||||||
|  |  | ||||||
|  |   * | ||||||
|  |     `#1973 <https://github.com/huge-success/sanic/pull/1973>`_ | ||||||
|  |     Transition Travis from .org to .com | ||||||
|  |  | ||||||
|  |   * | ||||||
|  |     `#1986 <https://github.com/huge-success/sanic/pull/1986>`_ | ||||||
|  |     Update tox requirements | ||||||
|  |  | ||||||
|  | Improved Documentation | ||||||
|  | ********************** | ||||||
|  |  | ||||||
|  |   * | ||||||
|  |     `#1951 <https://github.com/huge-success/sanic/pull/1951>`_ | ||||||
|  |     Documentation improvements | ||||||
|  |  | ||||||
|  |   * | ||||||
|  |     `#1983 <https://github.com/huge-success/sanic/pull/1983>`_ | ||||||
|  |     Remove duplicate contents in testing.rst | ||||||
|  |  | ||||||
|  |   * | ||||||
|  |     `#1984 <https://github.com/huge-success/sanic/pull/1984>`_ | ||||||
|  |     Fix typo in routing.rst | ||||||
|  |  | ||||||
|  |  | ||||||
|  | Version 20.9.1 | ||||||
|  | =============== | ||||||
|  |  | ||||||
|  | Bugfixes | ||||||
|  | ******** | ||||||
|  |  | ||||||
|  |   * | ||||||
|  |     `#1954 <https://github.com/huge-success/sanic/pull/1954>`_ | ||||||
|  |     Fix static route registration on blueprints | ||||||
|  |   * | ||||||
|  |     `#1957 <https://github.com/huge-success/sanic/pull/1957>`_ | ||||||
|  |     Removes duplicate headers in ASGI streaming body | ||||||
|  |  | ||||||
|  |  | ||||||
|  | Version 19.12.3 | ||||||
|  | =============== | ||||||
|  |  | ||||||
|  | Bugfixes | ||||||
|  | ******** | ||||||
|  |  | ||||||
|  |   * | ||||||
|  |     `#1959 <https://github.com/huge-success/sanic/pull/1959>`_ | ||||||
|  |     Removes duplicate headers in ASGI streaming body | ||||||
|  |  | ||||||
|  |  | ||||||
| Version 20.9.0 | Version 20.9.0 | ||||||
| =============== | =============== | ||||||
|  |  | ||||||
|   | |||||||
| @@ -26,8 +26,8 @@ Sanic | Build fast. Run fast. | |||||||
|    :target: https://gitter.im/sanic-python/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge |    :target: https://gitter.im/sanic-python/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge | ||||||
| .. |Codecov| image:: https://codecov.io/gh/huge-success/sanic/branch/master/graph/badge.svg | .. |Codecov| image:: https://codecov.io/gh/huge-success/sanic/branch/master/graph/badge.svg | ||||||
|     :target: https://codecov.io/gh/huge-success/sanic |     :target: https://codecov.io/gh/huge-success/sanic | ||||||
| .. |Build Status| image:: https://travis-ci.org/huge-success/sanic.svg?branch=master | .. |Build Status| image:: https://travis-ci.com/huge-success/sanic.svg?branch=master | ||||||
|    :target: https://travis-ci.org/huge-success/sanic |    :target: https://travis-ci.com/huge-success/sanic | ||||||
| .. |AppVeyor Build Status| image:: https://ci.appveyor.com/api/projects/status/d8pt3ids0ynexi8c/branch/master?svg=true | .. |AppVeyor Build Status| image:: https://ci.appveyor.com/api/projects/status/d8pt3ids0ynexi8c/branch/master?svg=true | ||||||
|    :target: https://ci.appveyor.com/project/huge-success/sanic |    :target: https://ci.appveyor.com/project/huge-success/sanic | ||||||
| .. |Documentation| image:: https://readthedocs.org/projects/sanic/badge/?version=latest | .. |Documentation| image:: https://readthedocs.org/projects/sanic/badge/?version=latest | ||||||
|   | |||||||
							
								
								
									
										1
									
								
								changelogs/1970.misc.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								changelogs/1970.misc.rst
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | |||||||
|  | Adds py.typed file to expose type information to other packages. | ||||||
| @@ -60,3 +60,26 @@ Open the address `http://0.0.0.0:8000 <http://0.0.0.0:8000>`_ in your web browse | |||||||
| the message *Hello world!*. | the message *Hello world!*. | ||||||
|  |  | ||||||
| You now have a working Sanic server! | You now have a working Sanic server! | ||||||
|  |  | ||||||
|  | 5. Application registry | ||||||
|  | ----------------------- | ||||||
|  |  | ||||||
|  | When you instantiate a Sanic instance, that can be retrieved at a later time from the Sanic app registry. This can be useful, for example, if you need to access your Sanic instance from a location where it is not otherwise accessible. | ||||||
|  |  | ||||||
|  | .. code-block:: python | ||||||
|  |  | ||||||
|  |     # ./path/to/server.py | ||||||
|  |     from sanic import Sanic | ||||||
|  |  | ||||||
|  |     app = Sanic("my_awesome_server") | ||||||
|  |  | ||||||
|  |     # ./path/to/somewhere_else.py | ||||||
|  |     from sanic import Sanic | ||||||
|  |  | ||||||
|  |     app = Sanic.get_app("my_awesome_server") | ||||||
|  |  | ||||||
|  | If you call ``Sanic.get_app("non-existing")`` on an app that does not exist, it will raise ``SanicException`` by default. You can, instead, force the method to return a new instance of ``Sanic`` with that name: | ||||||
|  |  | ||||||
|  | .. code-block:: python | ||||||
|  |  | ||||||
|  |     app = Sanic.get_app("my_awesome_server", force_create=True) | ||||||
|   | |||||||
| @@ -133,7 +133,7 @@ which allows the handler function to work with any of the HTTP methods in the li | |||||||
|     async def get_handler(request): |     async def get_handler(request): | ||||||
|         return text('GET request - {}'.format(request.args)) |         return text('GET request - {}'.format(request.args)) | ||||||
|  |  | ||||||
| There is also an optional `host` argument (which can be a list or a string). This restricts a route to the host or hosts provided. If there is a also a route with no host, it will be the default. | There is also an optional `host` argument (which can be a list or a string). This restricts a route to the host or hosts provided. If there is also a route with no host, it will be the default. | ||||||
|  |  | ||||||
| .. code-block:: python | .. code-block:: python | ||||||
|  |  | ||||||
|   | |||||||
| @@ -88,5 +88,5 @@ When `stream_large_files` is `True`, Sanic will use `file_stream()` instead of ` | |||||||
|  |  | ||||||
|     app = Sanic(__name__) |     app = Sanic(__name__) | ||||||
|  |  | ||||||
|     chunk_size = 1024 * 1024 * 8 # Set chunk size to 8KB |     chunk_size = 1024 * 1024 * 8 # Set chunk size to 8MiB | ||||||
|     app.static('/large_video.mp4', '/home/ubuntu/large_video.mp4', stream_large_files=chunk_size) |     app.static('/large_video.mp4', '/home/ubuntu/large_video.mp4', stream_large_files=chunk_size) | ||||||
|   | |||||||
| @@ -58,10 +58,6 @@ More information about | |||||||
| the available arguments to `httpx` can be found | the available arguments to `httpx` can be found | ||||||
| [in the documentation for `httpx <https://www.encode.io/httpx/>`_. | [in the documentation for `httpx <https://www.encode.io/httpx/>`_. | ||||||
|  |  | ||||||
| Additionally, Sanic has an asynchronous testing client. The difference is that the async client will not stand up an |  | ||||||
| instance of your application, but will instead reach inside it using ASGI. All listeners and middleware are still |  | ||||||
| executed. |  | ||||||
|  |  | ||||||
| .. code-block:: python | .. code-block:: python | ||||||
|     @pytest.mark.asyncio |     @pytest.mark.asyncio | ||||||
|     async def test_index_returns_200(): |     async def test_index_returns_200(): | ||||||
|   | |||||||
| @@ -10,6 +10,7 @@ from pathlib import Path | |||||||
|  |  | ||||||
| from sanic import Sanic, response | from sanic import Sanic, response | ||||||
|  |  | ||||||
|  |  | ||||||
| app = Sanic(__name__) | app = Sanic(__name__) | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -42,7 +43,9 @@ async def handler_file(request): | |||||||
|  |  | ||||||
| @app.route("/file_stream") | @app.route("/file_stream") | ||||||
| async def handler_file_stream(request): | async def handler_file_stream(request): | ||||||
|     return await response.file_stream(Path("../") / "setup.py", chunk_size=1024) |     return await response.file_stream( | ||||||
|  |         Path("../") / "setup.py", chunk_size=1024 | ||||||
|  |     ) | ||||||
|  |  | ||||||
|  |  | ||||||
| @app.route("/stream", stream=True) | @app.route("/stream", stream=True) | ||||||
|   | |||||||
| @@ -1,28 +1,83 @@ | |||||||
| import os | import os | ||||||
| import sys | import sys | ||||||
|  |  | ||||||
| from argparse import ArgumentParser | from argparse import ArgumentParser, RawDescriptionHelpFormatter | ||||||
| from importlib import import_module | from importlib import import_module | ||||||
| from typing import Any, Dict, Optional | from typing import Any, Dict, Optional | ||||||
|  |  | ||||||
|  | from sanic import __version__ | ||||||
| from sanic.app import Sanic | from sanic.app import Sanic | ||||||
|  | from sanic.config import BASE_LOGO | ||||||
| from sanic.log import logger | from sanic.log import logger | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class SanicArgumentParser(ArgumentParser): | ||||||
|  |     def add_bool_arguments(self, *args, **kwargs): | ||||||
|  |         group = self.add_mutually_exclusive_group() | ||||||
|  |         group.add_argument(*args, action="store_true", **kwargs) | ||||||
|  |         kwargs["help"] = "no " + kwargs["help"] | ||||||
|  |         group.add_argument( | ||||||
|  |             "--no-" + args[0][2:], *args[1:], action="store_false", **kwargs | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |  | ||||||
| def main(): | def main(): | ||||||
|     parser = ArgumentParser(prog="sanic") |     parser = SanicArgumentParser( | ||||||
|     parser.add_argument("--host", dest="host", type=str, default="127.0.0.1") |         prog="sanic", | ||||||
|     parser.add_argument("--port", dest="port", type=int, default=8000) |         description=BASE_LOGO, | ||||||
|     parser.add_argument("--unix", dest="unix", type=str, default="") |         formatter_class=RawDescriptionHelpFormatter, | ||||||
|  |     ) | ||||||
|  |     parser.add_argument( | ||||||
|  |         "-H", | ||||||
|  |         "--host", | ||||||
|  |         dest="host", | ||||||
|  |         type=str, | ||||||
|  |         default="127.0.0.1", | ||||||
|  |         help="host address [default 127.0.0.1]", | ||||||
|  |     ) | ||||||
|  |     parser.add_argument( | ||||||
|  |         "-p", | ||||||
|  |         "--port", | ||||||
|  |         dest="port", | ||||||
|  |         type=int, | ||||||
|  |         default=8000, | ||||||
|  |         help="port to serve on [default 8000]", | ||||||
|  |     ) | ||||||
|  |     parser.add_argument( | ||||||
|  |         "-u", | ||||||
|  |         "--unix", | ||||||
|  |         dest="unix", | ||||||
|  |         type=str, | ||||||
|  |         default="", | ||||||
|  |         help="location of unix socket", | ||||||
|  |     ) | ||||||
|     parser.add_argument( |     parser.add_argument( | ||||||
|         "--cert", dest="cert", type=str, help="location of certificate for SSL" |         "--cert", dest="cert", type=str, help="location of certificate for SSL" | ||||||
|     ) |     ) | ||||||
|     parser.add_argument( |     parser.add_argument( | ||||||
|         "--key", dest="key", type=str, help="location of keyfile for SSL." |         "--key", dest="key", type=str, help="location of keyfile for SSL." | ||||||
|     ) |     ) | ||||||
|     parser.add_argument("--workers", dest="workers", type=int, default=1) |     parser.add_argument( | ||||||
|  |         "-w", | ||||||
|  |         "--workers", | ||||||
|  |         dest="workers", | ||||||
|  |         type=int, | ||||||
|  |         default=1, | ||||||
|  |         help="number of worker processes [default 1]", | ||||||
|  |     ) | ||||||
|     parser.add_argument("--debug", dest="debug", action="store_true") |     parser.add_argument("--debug", dest="debug", action="store_true") | ||||||
|     parser.add_argument("module") |     parser.add_bool_arguments( | ||||||
|  |         "--access-logs", dest="access_log", help="display access logs" | ||||||
|  |     ) | ||||||
|  |     parser.add_argument( | ||||||
|  |         "-v", | ||||||
|  |         "--version", | ||||||
|  |         action="version", | ||||||
|  |         version=f"Sanic {__version__}", | ||||||
|  |     ) | ||||||
|  |     parser.add_argument( | ||||||
|  |         "module", help="path to your Sanic app. Example: path.to.server:app" | ||||||
|  |     ) | ||||||
|     args = parser.parse_args() |     args = parser.parse_args() | ||||||
|  |  | ||||||
|     try: |     try: | ||||||
| @@ -30,6 +85,9 @@ def main(): | |||||||
|         if module_path not in sys.path: |         if module_path not in sys.path: | ||||||
|             sys.path.append(module_path) |             sys.path.append(module_path) | ||||||
|  |  | ||||||
|  |         if ":" in args.module: | ||||||
|  |             module_name, app_name = args.module.rsplit(":", 1) | ||||||
|  |         else: | ||||||
|             module_parts = args.module.split(".") |             module_parts = args.module.split(".") | ||||||
|             module_name = ".".join(module_parts[:-1]) |             module_name = ".".join(module_parts[:-1]) | ||||||
|             app_name = module_parts[-1] |             app_name = module_parts[-1] | ||||||
| @@ -57,6 +115,7 @@ def main(): | |||||||
|             unix=args.unix, |             unix=args.unix, | ||||||
|             workers=args.workers, |             workers=args.workers, | ||||||
|             debug=args.debug, |             debug=args.debug, | ||||||
|  |             access_log=args.access_log, | ||||||
|             ssl=ssl, |             ssl=ssl, | ||||||
|         ) |         ) | ||||||
|     except ImportError as e: |     except ImportError as e: | ||||||
|   | |||||||
| @@ -1 +1 @@ | |||||||
| __version__ = "20.9.1" | __version__ = "20.12.1" | ||||||
|   | |||||||
							
								
								
									
										122
									
								
								sanic/app.py
									
									
									
									
									
								
							
							
						
						
									
										122
									
								
								sanic/app.py
									
									
									
									
									
								
							| @@ -2,12 +2,11 @@ import logging | |||||||
| import logging.config | import logging.config | ||||||
| import os | import os | ||||||
| import re | import re | ||||||
| import warnings |  | ||||||
|  |  | ||||||
| from asyncio import CancelledError, Protocol, ensure_future, get_event_loop | from asyncio import CancelledError, Protocol, ensure_future, get_event_loop | ||||||
| from collections import defaultdict, deque | from collections import defaultdict, deque | ||||||
| from functools import partial | from functools import partial | ||||||
| from inspect import getmodulename, isawaitable, signature, stack | from inspect import isawaitable, signature | ||||||
| from socket import socket | from socket import socket | ||||||
| from ssl import Purpose, SSLContext, create_default_context | from ssl import Purpose, SSLContext, create_default_context | ||||||
| from traceback import format_exc | from traceback import format_exc | ||||||
| @@ -38,6 +37,9 @@ from sanic.websocket import ConnectionClosed, WebSocketProtocol | |||||||
|  |  | ||||||
|  |  | ||||||
| class Sanic: | class Sanic: | ||||||
|  |     _app_registry: Dict[str, "Sanic"] = {} | ||||||
|  |     test_mode = False | ||||||
|  |  | ||||||
|     def __init__( |     def __init__( | ||||||
|         self, |         self, | ||||||
|         name=None, |         name=None, | ||||||
| @@ -48,19 +50,15 @@ class Sanic: | |||||||
|         strict_slashes=False, |         strict_slashes=False, | ||||||
|         log_config=None, |         log_config=None, | ||||||
|         configure_logging=True, |         configure_logging=True, | ||||||
|  |         register=None, | ||||||
|     ): |     ): | ||||||
|  |  | ||||||
|         # Get name from previous stack frame |         # Get name from previous stack frame | ||||||
|         if name is None: |         if name is None: | ||||||
|             warnings.warn( |             raise SanicException( | ||||||
|                 "Sanic(name=None) is deprecated and None value support " |                 "Sanic instance cannot be unnamed. " | ||||||
|                 "for `name` will be removed in the next release. " |  | ||||||
|                 "Please use Sanic(name='your_application_name') instead.", |                 "Please use Sanic(name='your_application_name') instead.", | ||||||
|                 DeprecationWarning, |  | ||||||
|                 stacklevel=2, |  | ||||||
|             ) |             ) | ||||||
|             frame_records = stack()[1] |  | ||||||
|             name = getmodulename(frame_records[1]) |  | ||||||
|  |  | ||||||
|         # logging |         # logging | ||||||
|         if configure_logging: |         if configure_logging: | ||||||
| @@ -68,7 +66,7 @@ class Sanic: | |||||||
|  |  | ||||||
|         self.name = name |         self.name = name | ||||||
|         self.asgi = False |         self.asgi = False | ||||||
|         self.router = router or Router() |         self.router = router or Router(self) | ||||||
|         self.request_class = request_class |         self.request_class = request_class | ||||||
|         self.error_handler = error_handler or ErrorHandler() |         self.error_handler = error_handler or ErrorHandler() | ||||||
|         self.config = Config(load_env=load_env) |         self.config = Config(load_env=load_env) | ||||||
| @@ -90,7 +88,12 @@ class Sanic: | |||||||
|         self.named_response_middleware = {} |         self.named_response_middleware = {} | ||||||
|         # Register alternative method names |         # Register alternative method names | ||||||
|         self.go_fast = self.run |         self.go_fast = self.run | ||||||
|         self.test_mode = False |  | ||||||
|  |         if register is not None: | ||||||
|  |             self.config.REGISTER = register | ||||||
|  |  | ||||||
|  |         if self.config.REGISTER: | ||||||
|  |             self.__class__.register_app(self) | ||||||
|  |  | ||||||
|     @property |     @property | ||||||
|     def loop(self): |     def loop(self): | ||||||
| @@ -714,28 +717,6 @@ class Sanic: | |||||||
|             self._blueprint_order.append(blueprint) |             self._blueprint_order.append(blueprint) | ||||||
|         blueprint.register(self, options) |         blueprint.register(self, options) | ||||||
|  |  | ||||||
|     def register_blueprint(self, *args, **kwargs): |  | ||||||
|         """ |  | ||||||
|         Proxy method provided for invoking the :func:`blueprint` method |  | ||||||
|  |  | ||||||
|         .. note:: |  | ||||||
|             To be deprecated in 1.0. Use :func:`blueprint` instead. |  | ||||||
|  |  | ||||||
|         :param args: Blueprint object or (list, tuple) thereof |  | ||||||
|         :param kwargs: option dictionary with blueprint defaults |  | ||||||
|         :return: None |  | ||||||
|         """ |  | ||||||
|  |  | ||||||
|         if self.debug: |  | ||||||
|             warnings.simplefilter("default") |  | ||||||
|         warnings.warn( |  | ||||||
|             "Use of register_blueprint will be deprecated in " |  | ||||||
|             "version 1.0.  Please use the blueprint method" |  | ||||||
|             " instead", |  | ||||||
|             DeprecationWarning, |  | ||||||
|         ) |  | ||||||
|         return self.blueprint(*args, **kwargs) |  | ||||||
|  |  | ||||||
|     def url_for(self, view_name: str, **kwargs): |     def url_for(self, view_name: str, **kwargs): | ||||||
|         r"""Build a URL based on a view name and the values provided. |         r"""Build a URL based on a view name and the values provided. | ||||||
|  |  | ||||||
| @@ -900,7 +881,9 @@ class Sanic: | |||||||
|         name = None |         name = None | ||||||
|         try: |         try: | ||||||
|             # Fetch handler from router |             # Fetch handler from router | ||||||
|             handler, args, kwargs, uri, name = self.router.get(request) |             handler, args, kwargs, uri, name, endpoint = self.router.get( | ||||||
|  |                 request | ||||||
|  |             ) | ||||||
|  |  | ||||||
|             # -------------------------------------------- # |             # -------------------------------------------- # | ||||||
|             # Request Middleware |             # Request Middleware | ||||||
| @@ -922,16 +905,8 @@ class Sanic: | |||||||
|                             "handler from the router" |                             "handler from the router" | ||||||
|                         ) |                         ) | ||||||
|                     ) |                     ) | ||||||
|                 else: |  | ||||||
|                     if not getattr(handler, "__blueprintname__", False): |                 request.endpoint = endpoint | ||||||
|                         request.endpoint = self._build_endpoint_name( |  | ||||||
|                             handler.__name__ |  | ||||||
|                         ) |  | ||||||
|                     else: |  | ||||||
|                         request.endpoint = self._build_endpoint_name( |  | ||||||
|                             getattr(handler, "__blueprintname__", ""), |  | ||||||
|                             handler.__name__, |  | ||||||
|                         ) |  | ||||||
|  |  | ||||||
|                 # Run response handler |                 # Run response handler | ||||||
|                 response = handler(request, *args, **kwargs) |                 response = handler(request, *args, **kwargs) | ||||||
| @@ -1032,7 +1007,6 @@ class Sanic: | |||||||
|         workers: int = 1, |         workers: int = 1, | ||||||
|         protocol: Optional[Type[Protocol]] = None, |         protocol: Optional[Type[Protocol]] = None, | ||||||
|         backlog: int = 100, |         backlog: int = 100, | ||||||
|         stop_event: Any = None, |  | ||||||
|         register_sys_signals: bool = True, |         register_sys_signals: bool = True, | ||||||
|         access_log: Optional[bool] = None, |         access_log: Optional[bool] = None, | ||||||
|         unix: Optional[str] = None, |         unix: Optional[str] = None, | ||||||
| @@ -1062,9 +1036,6 @@ class Sanic: | |||||||
|         :param backlog: a number of unaccepted connections that the system |         :param backlog: a number of unaccepted connections that the system | ||||||
|                         will allow before refusing new connections |                         will allow before refusing new connections | ||||||
|         :type backlog: int |         :type backlog: int | ||||||
|         :param stop_event: event to be triggered |  | ||||||
|                            before stopping the app - deprecated |  | ||||||
|         :type stop_event: None |  | ||||||
|         :param register_sys_signals: Register SIG* events |         :param register_sys_signals: Register SIG* events | ||||||
|         :type register_sys_signals: bool |         :type register_sys_signals: bool | ||||||
|         :param access_log: Enables writing access logs (slows server) |         :param access_log: Enables writing access logs (slows server) | ||||||
| @@ -1092,13 +1063,6 @@ class Sanic: | |||||||
|             protocol = ( |             protocol = ( | ||||||
|                 WebSocketProtocol if self.websocket_enabled else HttpProtocol |                 WebSocketProtocol if self.websocket_enabled else HttpProtocol | ||||||
|             ) |             ) | ||||||
|         if stop_event is not None: |  | ||||||
|             if debug: |  | ||||||
|                 warnings.simplefilter("default") |  | ||||||
|             warnings.warn( |  | ||||||
|                 "stop_event will be removed from future versions.", |  | ||||||
|                 DeprecationWarning, |  | ||||||
|             ) |  | ||||||
|         # if access_log is passed explicitly change config.ACCESS_LOG |         # if access_log is passed explicitly change config.ACCESS_LOG | ||||||
|         if access_log is not None: |         if access_log is not None: | ||||||
|             self.config.ACCESS_LOG = access_log |             self.config.ACCESS_LOG = access_log | ||||||
| @@ -1155,7 +1119,6 @@ class Sanic: | |||||||
|         sock: Optional[socket] = None, |         sock: Optional[socket] = None, | ||||||
|         protocol: Type[Protocol] = None, |         protocol: Type[Protocol] = None, | ||||||
|         backlog: int = 100, |         backlog: int = 100, | ||||||
|         stop_event: Any = None, |  | ||||||
|         access_log: Optional[bool] = None, |         access_log: Optional[bool] = None, | ||||||
|         unix: Optional[str] = None, |         unix: Optional[str] = None, | ||||||
|         return_asyncio_server=False, |         return_asyncio_server=False, | ||||||
| @@ -1188,9 +1151,6 @@ class Sanic: | |||||||
|         :param backlog: a number of unaccepted connections that the system |         :param backlog: a number of unaccepted connections that the system | ||||||
|                         will allow before refusing new connections |                         will allow before refusing new connections | ||||||
|         :type backlog: int |         :type backlog: int | ||||||
|         :param stop_event: event to be triggered |  | ||||||
|                            before stopping the app - deprecated |  | ||||||
|         :type stop_event: None |  | ||||||
|         :param access_log: Enables writing access logs (slows server) |         :param access_log: Enables writing access logs (slows server) | ||||||
|         :type access_log: bool |         :type access_log: bool | ||||||
|         :param return_asyncio_server: flag that defines whether there's a need |         :param return_asyncio_server: flag that defines whether there's a need | ||||||
| @@ -1210,13 +1170,6 @@ class Sanic: | |||||||
|             protocol = ( |             protocol = ( | ||||||
|                 WebSocketProtocol if self.websocket_enabled else HttpProtocol |                 WebSocketProtocol if self.websocket_enabled else HttpProtocol | ||||||
|             ) |             ) | ||||||
|         if stop_event is not None: |  | ||||||
|             if debug: |  | ||||||
|                 warnings.simplefilter("default") |  | ||||||
|             warnings.warn( |  | ||||||
|                 "stop_event will be removed from future versions.", |  | ||||||
|                 DeprecationWarning, |  | ||||||
|             ) |  | ||||||
|         # if access_log is passed explicitly change config.ACCESS_LOG |         # if access_log is passed explicitly change config.ACCESS_LOG | ||||||
|         if access_log is not None: |         if access_log is not None: | ||||||
|             self.config.ACCESS_LOG = access_log |             self.config.ACCESS_LOG = access_log | ||||||
| @@ -1298,7 +1251,6 @@ class Sanic: | |||||||
|         loop=None, |         loop=None, | ||||||
|         protocol=HttpProtocol, |         protocol=HttpProtocol, | ||||||
|         backlog=100, |         backlog=100, | ||||||
|         stop_event=None, |  | ||||||
|         register_sys_signals=True, |         register_sys_signals=True, | ||||||
|         run_async=False, |         run_async=False, | ||||||
|         auto_reload=False, |         auto_reload=False, | ||||||
| @@ -1313,13 +1265,6 @@ class Sanic: | |||||||
|             context = create_default_context(purpose=Purpose.CLIENT_AUTH) |             context = create_default_context(purpose=Purpose.CLIENT_AUTH) | ||||||
|             context.load_cert_chain(cert, keyfile=key) |             context.load_cert_chain(cert, keyfile=key) | ||||||
|             ssl = context |             ssl = context | ||||||
|         if stop_event is not None: |  | ||||||
|             if debug: |  | ||||||
|                 warnings.simplefilter("default") |  | ||||||
|             warnings.warn( |  | ||||||
|                 "stop_event will be removed from future versions.", |  | ||||||
|                 DeprecationWarning, |  | ||||||
|             ) |  | ||||||
|         if self.config.PROXIES_COUNT and self.config.PROXIES_COUNT < 0: |         if self.config.PROXIES_COUNT and self.config.PROXIES_COUNT < 0: | ||||||
|             raise ValueError( |             raise ValueError( | ||||||
|                 "PROXIES_COUNT cannot be negative. " |                 "PROXIES_COUNT cannot be negative. " | ||||||
| @@ -1454,12 +1399,41 @@ class Sanic: | |||||||
|         asgi_app = await ASGIApp.create(self, scope, receive, send) |         asgi_app = await ASGIApp.create(self, scope, receive, send) | ||||||
|         await asgi_app() |         await asgi_app() | ||||||
|  |  | ||||||
|  |     _asgi_single_callable = True  # We conform to ASGI 3.0 single-callable | ||||||
|  |  | ||||||
|     # -------------------------------------------------------------------- # |     # -------------------------------------------------------------------- # | ||||||
|     # Configuration |     # Configuration | ||||||
|     # -------------------------------------------------------------------- # |     # -------------------------------------------------------------------- # | ||||||
|  |  | ||||||
|     def update_config(self, config: Union[bytes, str, dict, Any]): |     def update_config(self, config: Union[bytes, str, dict, Any]): | ||||||
|         """Update app.config. |         """Update app.config. | ||||||
|  |  | ||||||
|         Please refer to config.py::Config.update_config for documentation.""" |         Please refer to config.py::Config.update_config for documentation.""" | ||||||
|  |  | ||||||
|         self.config.update_config(config) |         self.config.update_config(config) | ||||||
|  |  | ||||||
|  |     # -------------------------------------------------------------------- # | ||||||
|  |     # Class methods | ||||||
|  |     # -------------------------------------------------------------------- # | ||||||
|  |  | ||||||
|  |     @classmethod | ||||||
|  |     def register_app(cls, app: "Sanic") -> None: | ||||||
|  |         """Register a Sanic instance""" | ||||||
|  |         if not isinstance(app, cls): | ||||||
|  |             raise SanicException("Registered app must be an instance of Sanic") | ||||||
|  |  | ||||||
|  |         name = app.name | ||||||
|  |         if name in cls._app_registry and not cls.test_mode: | ||||||
|  |             raise SanicException(f'Sanic app name "{name}" already in use.') | ||||||
|  |  | ||||||
|  |         cls._app_registry[name] = app | ||||||
|  |  | ||||||
|  |     @classmethod | ||||||
|  |     def get_app(cls, name: str, *, force_create: bool = False) -> "Sanic": | ||||||
|  |         """Retrieve an instantiated Sanic instance""" | ||||||
|  |         try: | ||||||
|  |             return cls._app_registry[name] | ||||||
|  |         except KeyError: | ||||||
|  |             if force_create: | ||||||
|  |                 return cls(name) | ||||||
|  |             raise SanicException(f'Sanic app name "{name}" not found.') | ||||||
|   | |||||||
| @@ -312,13 +312,19 @@ class ASGIApp: | |||||||
|         callback = None if self.ws else self.stream_callback |         callback = None if self.ws else self.stream_callback | ||||||
|         await handler(self.request, None, callback) |         await handler(self.request, None, callback) | ||||||
|  |  | ||||||
|     async def stream_callback(self, response: HTTPResponse) -> None: |     _asgi_single_callable = True  # We conform to ASGI 3.0 single-callable | ||||||
|  |  | ||||||
|  |     async def stream_callback( | ||||||
|  |         self, response: Union[HTTPResponse, StreamingHTTPResponse] | ||||||
|  |     ) -> None: | ||||||
|         """ |         """ | ||||||
|         Write the response. |         Write the response. | ||||||
|         """ |         """ | ||||||
|         headers: List[Tuple[bytes, bytes]] = [] |         headers: List[Tuple[bytes, bytes]] = [] | ||||||
|         cookies: Dict[str, str] = {} |         cookies: Dict[str, str] = {} | ||||||
|  |         content_length: List[str] = [] | ||||||
|         try: |         try: | ||||||
|  |             content_length = response.headers.popall("content-length", []) | ||||||
|             cookies = { |             cookies = { | ||||||
|                 v.key: v |                 v.key: v | ||||||
|                 for _, v in list( |                 for _, v in list( | ||||||
| @@ -351,12 +357,22 @@ class ASGIApp: | |||||||
|             ] |             ] | ||||||
|  |  | ||||||
|         response.asgi = True |         response.asgi = True | ||||||
|  |         is_streaming = isinstance(response, StreamingHTTPResponse) | ||||||
|         if "content-length" not in response.headers and not isinstance( |         if is_streaming and getattr(response, "chunked", False): | ||||||
|             response, StreamingHTTPResponse |             # disable sanic chunking, this is done at the ASGI-server level | ||||||
|         ): |             setattr(response, "chunked", False) | ||||||
|  |             # content-length header is removed to signal to the ASGI-server | ||||||
|  |             # to use automatic-chunking if it supports it | ||||||
|  |         elif len(content_length) > 0: | ||||||
|             headers += [ |             headers += [ | ||||||
|                 (b"content-length", str(len(response.body)).encode("latin-1")) |                 (b"content-length", str(content_length[0]).encode("latin-1")) | ||||||
|  |             ] | ||||||
|  |         elif not is_streaming: | ||||||
|  |             headers += [ | ||||||
|  |                 ( | ||||||
|  |                     b"content-length", | ||||||
|  |                     str(len(getattr(response, "body", b""))).encode("latin-1"), | ||||||
|  |                 ) | ||||||
|             ] |             ] | ||||||
|  |  | ||||||
|         if "content-type" not in response.headers: |         if "content-type" not in response.headers: | ||||||
|   | |||||||
| @@ -40,6 +40,7 @@ DEFAULT_CONFIG = { | |||||||
|     "PROXIES_COUNT": None, |     "PROXIES_COUNT": None, | ||||||
|     "FORWARDED_FOR_HEADER": "X-Forwarded-For", |     "FORWARDED_FOR_HEADER": "X-Forwarded-For", | ||||||
|     "FALLBACK_ERROR_FORMAT": "html", |     "FALLBACK_ERROR_FORMAT": "html", | ||||||
|  |     "REGISTER": True, | ||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
|   | |||||||
| @@ -179,8 +179,8 @@ def abort(status_code, message=None): | |||||||
|     message appropriate for the given status code, unless provided. |     message appropriate for the given status code, unless provided. | ||||||
|  |  | ||||||
|     :param status_code: The HTTP status code to return. |     :param status_code: The HTTP status code to return. | ||||||
|     :param message: The HTTP response body. Defaults to the messages |     :param message: The HTTP response body. Defaults to the messages in | ||||||
|                     in response.py for the given status code. |     STATUS_CODES from sanic.helpers for the given status code. | ||||||
|     """ |     """ | ||||||
|     if message is None: |     if message is None: | ||||||
|         message = STATUS_CODES.get(status_code) |         message = STATUS_CODES.get(status_code) | ||||||
|   | |||||||
							
								
								
									
										0
									
								
								sanic/py.typed
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								sanic/py.typed
									
									
									
									
									
										Normal file
									
								
							| @@ -136,15 +136,18 @@ class Request: | |||||||
|         return f"<{class_name}: {self.method} {self.path}>" |         return f"<{class_name}: {self.method} {self.path}>" | ||||||
|  |  | ||||||
|     def body_init(self): |     def body_init(self): | ||||||
|         """.. deprecated:: 20.3""" |         """.. deprecated:: 20.3 | ||||||
|  |         To be removed in 21.3""" | ||||||
|         self.body = [] |         self.body = [] | ||||||
|  |  | ||||||
|     def body_push(self, data): |     def body_push(self, data): | ||||||
|         """.. deprecated:: 20.3""" |         """.. deprecated:: 20.3 | ||||||
|  |         To be removed in 21.3""" | ||||||
|         self.body.append(data) |         self.body.append(data) | ||||||
|  |  | ||||||
|     def body_finish(self): |     def body_finish(self): | ||||||
|         """.. deprecated:: 20.3""" |         """.. deprecated:: 20.3 | ||||||
|  |         To be removed in 21.3""" | ||||||
|         self.body = b"".join(self.body) |         self.body = b"".join(self.body) | ||||||
|  |  | ||||||
|     async def receive_body(self): |     async def receive_body(self): | ||||||
|   | |||||||
| @@ -1,5 +1,3 @@ | |||||||
| import warnings |  | ||||||
|  |  | ||||||
| from functools import partial | from functools import partial | ||||||
| from mimetypes import guess_type | from mimetypes import guess_type | ||||||
| from os import path | from os import path | ||||||
| @@ -26,6 +24,8 @@ class BaseHTTPResponse: | |||||||
|         self.asgi = False |         self.asgi = False | ||||||
|  |  | ||||||
|     def _encode_body(self, data): |     def _encode_body(self, data): | ||||||
|  |         if data is None: | ||||||
|  |             return b"" | ||||||
|         return data.encode() if hasattr(data, "encode") else data |         return data.encode() if hasattr(data, "encode") else data | ||||||
|  |  | ||||||
|     def _parse_headers(self): |     def _parse_headers(self): | ||||||
| @@ -45,7 +45,7 @@ class BaseHTTPResponse: | |||||||
|         body=b"", |         body=b"", | ||||||
|     ): |     ): | ||||||
|         """.. deprecated:: 20.3: |         """.. deprecated:: 20.3: | ||||||
|         This function is not public API and will be removed.""" |         This function is not public API and will be removed in 21.3.""" | ||||||
|  |  | ||||||
|         # self.headers get priority over content_type |         # self.headers get priority over content_type | ||||||
|         if self.content_type and "Content-Type" not in self.headers: |         if self.content_type and "Content-Type" not in self.headers: | ||||||
| @@ -100,6 +100,8 @@ class StreamingHTTPResponse(BaseHTTPResponse): | |||||||
|         """ |         """ | ||||||
|         data = self._encode_body(data) |         data = self._encode_body(data) | ||||||
|  |  | ||||||
|  |         # `chunked` will always be False in ASGI-mode, even if the underlying | ||||||
|  |         # ASGI Transport implements Chunked transport. That does it itself. | ||||||
|         if self.chunked: |         if self.chunked: | ||||||
|             await self.protocol.push_data(b"%x\r\n%b\r\n" % (len(data), data)) |             await self.protocol.push_data(b"%x\r\n%b\r\n" % (len(data), data)) | ||||||
|         else: |         else: | ||||||
| @@ -147,22 +149,15 @@ class HTTPResponse(BaseHTTPResponse): | |||||||
|         status=200, |         status=200, | ||||||
|         headers=None, |         headers=None, | ||||||
|         content_type=None, |         content_type=None, | ||||||
|         body_bytes=b"", |  | ||||||
|     ): |     ): | ||||||
|         super().__init__() |         super().__init__() | ||||||
|  |  | ||||||
|         self.content_type = content_type |         self.content_type = content_type | ||||||
|         self.body = body_bytes if body is None else self._encode_body(body) |         self.body = self._encode_body(body) | ||||||
|         self.status = status |         self.status = status | ||||||
|         self.headers = Header(headers or {}) |         self.headers = Header(headers or {}) | ||||||
|         self._cookies = None |         self._cookies = None | ||||||
|  |  | ||||||
|         if body_bytes: |  | ||||||
|             warnings.warn( |  | ||||||
|                 "Parameter `body_bytes` is deprecated, use `body` instead", |  | ||||||
|                 DeprecationWarning, |  | ||||||
|             ) |  | ||||||
|  |  | ||||||
|     def output(self, version="1.1", keep_alive=False, keep_alive_timeout=None): |     def output(self, version="1.1", keep_alive=False, keep_alive_timeout=None): | ||||||
|         body = b"" |         body = b"" | ||||||
|         if has_message_body(self.status): |         if has_message_body(self.status): | ||||||
| @@ -226,20 +221,10 @@ def text( | |||||||
|     :param content_type: the content type (string) of the response |     :param content_type: the content type (string) of the response | ||||||
|     """ |     """ | ||||||
|     if not isinstance(body, str): |     if not isinstance(body, str): | ||||||
|         warnings.warn( |         raise TypeError( | ||||||
|             "Types other than str will be deprecated in future versions for" |             f"Bad body type. Expected str, got {type(body).__name__})" | ||||||
|             f" response.text, got type {type(body).__name__})", |  | ||||||
|             DeprecationWarning, |  | ||||||
|         ) |         ) | ||||||
|     # Type conversions are deprecated and quite b0rked but still supported for |  | ||||||
|     # text() until applications get fixed. This try-except should be removed. |  | ||||||
|     try: |  | ||||||
|         # Avoid repr(body).encode() b0rkage for body that is already encoded. |  | ||||||
|         # memoryview used only to test bytes-ishness. |  | ||||||
|         with memoryview(body): |  | ||||||
|             pass |  | ||||||
|     except TypeError: |  | ||||||
|         body = f"{body}"  # no-op if body is already str |  | ||||||
|     return HTTPResponse( |     return HTTPResponse( | ||||||
|         body, status=status, headers=headers, content_type=content_type |         body, status=status, headers=headers, content_type=content_type | ||||||
|     ) |     ) | ||||||
|   | |||||||
| @@ -11,7 +11,16 @@ from sanic.views import CompositionView | |||||||
|  |  | ||||||
|  |  | ||||||
| Route = namedtuple( | Route = namedtuple( | ||||||
|     "Route", ["handler", "methods", "pattern", "parameters", "name", "uri"] |     "Route", | ||||||
|  |     [ | ||||||
|  |         "handler", | ||||||
|  |         "methods", | ||||||
|  |         "pattern", | ||||||
|  |         "parameters", | ||||||
|  |         "name", | ||||||
|  |         "uri", | ||||||
|  |         "endpoint", | ||||||
|  |     ], | ||||||
| ) | ) | ||||||
| Parameter = namedtuple("Parameter", ["name", "cast"]) | Parameter = namedtuple("Parameter", ["name", "cast"]) | ||||||
|  |  | ||||||
| @@ -79,7 +88,8 @@ class Router: | |||||||
|     routes_always_check = None |     routes_always_check = None | ||||||
|     parameter_pattern = re.compile(r"<(.+?)>") |     parameter_pattern = re.compile(r"<(.+?)>") | ||||||
|  |  | ||||||
|     def __init__(self): |     def __init__(self, app): | ||||||
|  |         self.app = app | ||||||
|         self.routes_all = {} |         self.routes_all = {} | ||||||
|         self.routes_names = {} |         self.routes_names = {} | ||||||
|         self.routes_static_files = {} |         self.routes_static_files = {} | ||||||
| @@ -299,11 +309,15 @@ class Router: | |||||||
|  |  | ||||||
|             handler_name = f"{bp_name}.{name or handler.__name__}" |             handler_name = f"{bp_name}.{name or handler.__name__}" | ||||||
|         else: |         else: | ||||||
|             handler_name = name or getattr(handler, "__name__", None) |             handler_name = name or getattr( | ||||||
|  |                 handler, "__name__", handler.__class__.__name__ | ||||||
|  |             ) | ||||||
|  |  | ||||||
|         if route: |         if route: | ||||||
|             route = merge_route(route, methods, handler) |             route = merge_route(route, methods, handler) | ||||||
|         else: |         else: | ||||||
|  |             endpoint = self.app._build_endpoint_name(handler_name) | ||||||
|  |  | ||||||
|             route = Route( |             route = Route( | ||||||
|                 handler=handler, |                 handler=handler, | ||||||
|                 methods=methods, |                 methods=methods, | ||||||
| @@ -311,6 +325,7 @@ class Router: | |||||||
|                 parameters=parameters, |                 parameters=parameters, | ||||||
|                 name=handler_name, |                 name=handler_name, | ||||||
|                 uri=uri, |                 uri=uri, | ||||||
|  |                 endpoint=endpoint, | ||||||
|             ) |             ) | ||||||
|  |  | ||||||
|         self.routes_all[uri] = route |         self.routes_all[uri] = route | ||||||
| @@ -449,7 +464,8 @@ class Router: | |||||||
|         route_handler = route.handler |         route_handler = route.handler | ||||||
|         if hasattr(route_handler, "handlers"): |         if hasattr(route_handler, "handlers"): | ||||||
|             route_handler = route_handler.handlers[method] |             route_handler = route_handler.handlers[method] | ||||||
|         return route_handler, [], kwargs, route.uri, route.name |  | ||||||
|  |         return route_handler, [], kwargs, route.uri, route.name, route.endpoint | ||||||
|  |  | ||||||
|     def is_stream_handler(self, request): |     def is_stream_handler(self, request): | ||||||
|         """Handler for request is stream or not. |         """Handler for request is stream or not. | ||||||
|   | |||||||
| @@ -13,6 +13,7 @@ from sanic.exceptions import ( | |||||||
|     InvalidUsage, |     InvalidUsage, | ||||||
| ) | ) | ||||||
| from sanic.handlers import ContentRangeHandler | from sanic.handlers import ContentRangeHandler | ||||||
|  | from sanic.log import error_logger | ||||||
| from sanic.response import HTTPResponse, file, file_stream | from sanic.response import HTTPResponse, file, file_stream | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -40,6 +41,10 @@ async def _static_request_handler( | |||||||
|     # match filenames which got encoded (filenames with spaces etc) |     # match filenames which got encoded (filenames with spaces etc) | ||||||
|     file_path = path.abspath(unquote(file_path)) |     file_path = path.abspath(unquote(file_path)) | ||||||
|     if not file_path.startswith(path.abspath(unquote(root_path))): |     if not file_path.startswith(path.abspath(unquote(root_path))): | ||||||
|  |         error_logger.exception( | ||||||
|  |             f"File not found: path={file_or_directory}, " | ||||||
|  |             f"relative_url={file_uri}" | ||||||
|  |         ) | ||||||
|         raise FileNotFound( |         raise FileNotFound( | ||||||
|             "File not found", path=file_or_directory, relative_url=file_uri |             "File not found", path=file_or_directory, relative_url=file_uri | ||||||
|         ) |         ) | ||||||
| @@ -94,6 +99,10 @@ async def _static_request_handler( | |||||||
|     except ContentRangeError: |     except ContentRangeError: | ||||||
|         raise |         raise | ||||||
|     except Exception: |     except Exception: | ||||||
|  |         error_logger.exception( | ||||||
|  |             f"File not found: path={file_or_directory}, " | ||||||
|  |             f"relative_url={file_uri}" | ||||||
|  |         ) | ||||||
|         raise FileNotFound( |         raise FileNotFound( | ||||||
|             "File not found", path=file_or_directory, relative_url=file_uri |             "File not found", path=file_or_directory, relative_url=file_uri | ||||||
|         ) |         ) | ||||||
|   | |||||||
| @@ -90,6 +90,7 @@ class CompositionView: | |||||||
|  |  | ||||||
|     def __init__(self): |     def __init__(self): | ||||||
|         self.handlers = {} |         self.handlers = {} | ||||||
|  |         self.name = self.__class__.__name__ | ||||||
|  |  | ||||||
|     def add(self, methods, handler, stream=False): |     def add(self, methods, handler, stream=False): | ||||||
|         if stream: |         if stream: | ||||||
|   | |||||||
							
								
								
									
										23
									
								
								setup.py
									
									
									
									
									
								
							
							
						
						
									
										23
									
								
								setup.py
									
									
									
									
									
								
							| @@ -5,6 +5,7 @@ import codecs | |||||||
| import os | import os | ||||||
| import re | import re | ||||||
| import sys | import sys | ||||||
|  |  | ||||||
| from distutils.util import strtobool | from distutils.util import strtobool | ||||||
|  |  | ||||||
| from setuptools import setup | from setuptools import setup | ||||||
| @@ -24,6 +25,7 @@ class PyTest(TestCommand): | |||||||
|  |  | ||||||
|     def run_tests(self): |     def run_tests(self): | ||||||
|         import shlex |         import shlex | ||||||
|  |  | ||||||
|         import pytest |         import pytest | ||||||
|  |  | ||||||
|         errno = pytest.main(shlex.split(self.pytest_args)) |         errno = pytest.main(shlex.split(self.pytest_args)) | ||||||
| @@ -38,7 +40,9 @@ def open_local(paths, mode="r", encoding="utf8"): | |||||||
|  |  | ||||||
| with open_local(["sanic", "__version__.py"], encoding="latin1") as fp: | with open_local(["sanic", "__version__.py"], encoding="latin1") as fp: | ||||||
|     try: |     try: | ||||||
|         version = re.findall(r"^__version__ = \"([^']+)\"\r?$", fp.read(), re.M)[0] |         version = re.findall( | ||||||
|  |             r"^__version__ = \"([^']+)\"\r?$", fp.read(), re.M | ||||||
|  |         )[0] | ||||||
|     except IndexError: |     except IndexError: | ||||||
|         raise RuntimeError("Unable to determine version.") |         raise RuntimeError("Unable to determine version.") | ||||||
|  |  | ||||||
| @@ -57,6 +61,7 @@ setup_kwargs = { | |||||||
|     ), |     ), | ||||||
|     "long_description": long_description, |     "long_description": long_description, | ||||||
|     "packages": ["sanic"], |     "packages": ["sanic"], | ||||||
|  |     "package_data": {"sanic": ["py.typed"]}, | ||||||
|     "platforms": "any", |     "platforms": "any", | ||||||
|     "python_requires": ">=3.6", |     "python_requires": ">=3.6", | ||||||
|     "classifiers": [ |     "classifiers": [ | ||||||
| @@ -66,11 +71,14 @@ setup_kwargs = { | |||||||
|         "Programming Language :: Python :: 3.6", |         "Programming Language :: Python :: 3.6", | ||||||
|         "Programming Language :: Python :: 3.7", |         "Programming Language :: Python :: 3.7", | ||||||
|         "Programming Language :: Python :: 3.8", |         "Programming Language :: Python :: 3.8", | ||||||
|  |         "Programming Language :: Python :: 3.9", | ||||||
|     ], |     ], | ||||||
|     "entry_points": {"console_scripts": ["sanic = sanic.__main__:main"]}, |     "entry_points": {"console_scripts": ["sanic = sanic.__main__:main"]}, | ||||||
| } | } | ||||||
|  |  | ||||||
| env_dependency = '; sys_platform != "win32" ' 'and implementation_name == "cpython"' | env_dependency = ( | ||||||
|  |     '; sys_platform != "win32" ' 'and implementation_name == "cpython"' | ||||||
|  | ) | ||||||
| ujson = "ujson>=1.35" + env_dependency | ujson = "ujson>=1.35" + env_dependency | ||||||
| uvloop = "uvloop>=0.5.3" + env_dependency | uvloop = "uvloop>=0.5.3" + env_dependency | ||||||
|  |  | ||||||
| @@ -78,24 +86,25 @@ requirements = [ | |||||||
|     "httptools>=0.0.10", |     "httptools>=0.0.10", | ||||||
|     uvloop, |     uvloop, | ||||||
|     ujson, |     ujson, | ||||||
|     "aiofiles>=0.3.0", |     "aiofiles>=0.6.0", | ||||||
|     "websockets>=8.1,<9.0", |     "websockets>=8.1,<9.0", | ||||||
|     "multidict==5.0.0", |     "multidict>=5.0,<6.0", | ||||||
|     "httpx==0.15.4", |     "httpx==0.15.4", | ||||||
| ] | ] | ||||||
|  |  | ||||||
| tests_require = [ | tests_require = [ | ||||||
|     "pytest==5.2.1", |     "pytest==5.2.1", | ||||||
|     "multidict==5.0.0", |     "multidict>=5.0,<6.0", | ||||||
|     "gunicorn", |     "gunicorn==20.0.4", | ||||||
|     "pytest-cov", |     "pytest-cov", | ||||||
|     "httpcore==0.3.0", |     "httpcore==0.11.*", | ||||||
|     "beautifulsoup4", |     "beautifulsoup4", | ||||||
|     uvloop, |     uvloop, | ||||||
|     ujson, |     ujson, | ||||||
|     "pytest-sanic", |     "pytest-sanic", | ||||||
|     "pytest-sugar", |     "pytest-sugar", | ||||||
|     "pytest-benchmark", |     "pytest-benchmark", | ||||||
|  |     "pytest-dependency", | ||||||
| ] | ] | ||||||
|  |  | ||||||
| docs_require = [ | docs_require = [ | ||||||
|   | |||||||
| @@ -11,6 +11,7 @@ from sanic.router import RouteExists, Router | |||||||
|  |  | ||||||
|  |  | ||||||
| random.seed("Pack my box with five dozen liquor jugs.") | random.seed("Pack my box with five dozen liquor jugs.") | ||||||
|  | Sanic.test_mode = True | ||||||
|  |  | ||||||
| if sys.platform in ["win32", "cygwin"]: | if sys.platform in ["win32", "cygwin"]: | ||||||
|     collect_ignore = ["test_worker.py"] |     collect_ignore = ["test_worker.py"] | ||||||
| @@ -95,10 +96,10 @@ class RouteStringGenerator: | |||||||
|  |  | ||||||
|  |  | ||||||
| @pytest.fixture(scope="function") | @pytest.fixture(scope="function") | ||||||
| def sanic_router(): | def sanic_router(app): | ||||||
|     # noinspection PyProtectedMember |     # noinspection PyProtectedMember | ||||||
|     def _setup(route_details: tuple) -> (Router, tuple): |     def _setup(route_details: tuple) -> (Router, tuple): | ||||||
|         router = Router() |         router = Router(app) | ||||||
|         added_router = [] |         added_router = [] | ||||||
|         for method, route in route_details: |         for method, route in route_details: | ||||||
|             try: |             try: | ||||||
|   | |||||||
| @@ -3,6 +3,7 @@ import logging | |||||||
| import sys | import sys | ||||||
|  |  | ||||||
| from inspect import isawaitable | from inspect import isawaitable | ||||||
|  | from os import environ | ||||||
| from unittest.mock import patch | from unittest.mock import patch | ||||||
|  |  | ||||||
| import pytest | import pytest | ||||||
| @@ -117,7 +118,7 @@ def test_app_route_raise_value_error(app): | |||||||
|  |  | ||||||
| def test_app_handle_request_handler_is_none(app, monkeypatch): | def test_app_handle_request_handler_is_none(app, monkeypatch): | ||||||
|     def mockreturn(*args, **kwargs): |     def mockreturn(*args, **kwargs): | ||||||
|         return None, [], {}, "", "" |         return None, [], {}, "", "", None | ||||||
|  |  | ||||||
|     # Not sure how to make app.router.get() return None, so use mock here. |     # Not sure how to make app.router.get() return None, so use mock here. | ||||||
|     monkeypatch.setattr(app.router, "get", mockreturn) |     monkeypatch.setattr(app.router, "get", mockreturn) | ||||||
| @@ -258,7 +259,7 @@ def test_handle_request_with_nested_sanic_exception(app, monkeypatch, caplog): | |||||||
|  |  | ||||||
|  |  | ||||||
| def test_app_name_required(): | def test_app_name_required(): | ||||||
|     with pytest.deprecated_call(): |     with pytest.raises(SanicException): | ||||||
|         Sanic() |         Sanic() | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -274,14 +275,50 @@ def test_app_has_test_mode_sync(): | |||||||
|     assert response.status == 200 |     assert response.status == 200 | ||||||
|  |  | ||||||
|  |  | ||||||
| # @pytest.mark.asyncio | def test_app_registry(): | ||||||
| # async def test_app_has_test_mode_async(): |     instance = Sanic("test") | ||||||
| #     app = Sanic("test") |     assert Sanic._app_registry["test"] is instance | ||||||
|  |  | ||||||
| #     @app.get("/") |  | ||||||
| #     async def handler(request): |  | ||||||
| #         assert request.app.test_mode |  | ||||||
| #         return text("test") |  | ||||||
|  |  | ||||||
| #     _, response = await app.asgi_client.get("/") | def test_app_registry_wrong_type(): | ||||||
| #     assert response.status == 200 |     with pytest.raises(SanicException): | ||||||
|  |         Sanic.register_app(1) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def test_app_registry_name_reuse(): | ||||||
|  |     Sanic("test") | ||||||
|  |     Sanic.test_mode = False | ||||||
|  |     with pytest.raises(SanicException): | ||||||
|  |         Sanic("test") | ||||||
|  |     Sanic.test_mode = True | ||||||
|  |     Sanic("test") | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def test_app_registry_retrieval(): | ||||||
|  |     instance = Sanic("test") | ||||||
|  |     assert Sanic.get_app("test") is instance | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def test_get_app_does_not_exist(): | ||||||
|  |     with pytest.raises(SanicException): | ||||||
|  |         Sanic.get_app("does-not-exist") | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def test_get_app_does_not_exist_force_create(): | ||||||
|  |     assert isinstance( | ||||||
|  |         Sanic.get_app("does-not-exist", force_create=True), Sanic | ||||||
|  |     ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def test_app_no_registry(): | ||||||
|  |     Sanic("no-register", register=False) | ||||||
|  |     with pytest.raises(SanicException): | ||||||
|  |         Sanic.get_app("no-register") | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def test_app_no_registry_env(): | ||||||
|  |     environ["SANIC_REGISTER"] = "False" | ||||||
|  |     Sanic("no-register") | ||||||
|  |     with pytest.raises(SanicException): | ||||||
|  |         Sanic.get_app("no-register") | ||||||
|  |     del environ["SANIC_REGISTER"] | ||||||
|   | |||||||
| @@ -735,6 +735,7 @@ def test_static_blueprint_name(app: Sanic, static_file_directory, file_name): | |||||||
|     _, response = app.test_client.get("/static/test.file/") |     _, response = app.test_client.get("/static/test.file/") | ||||||
|     assert response.status == 200 |     assert response.status == 200 | ||||||
|  |  | ||||||
|  |  | ||||||
| @pytest.mark.parametrize("file_name", ["test.file"]) | @pytest.mark.parametrize("file_name", ["test.file"]) | ||||||
| def test_static_blueprintp_mw(app: Sanic, static_file_directory, file_name): | def test_static_blueprintp_mw(app: Sanic, static_file_directory, file_name): | ||||||
|     current_file = inspect.getfile(inspect.currentframe()) |     current_file = inspect.getfile(inspect.currentframe()) | ||||||
| @@ -745,7 +746,7 @@ def test_static_blueprintp_mw(app: Sanic, static_file_directory, file_name): | |||||||
|  |  | ||||||
|     bp = Blueprint(name="test_mw", url_prefix="") |     bp = Blueprint(name="test_mw", url_prefix="") | ||||||
|  |  | ||||||
|     @bp.middleware('request') |     @bp.middleware("request") | ||||||
|     def bp_mw1(request): |     def bp_mw1(request): | ||||||
|         nonlocal triggered |         nonlocal triggered | ||||||
|         triggered = True |         triggered = True | ||||||
| @@ -754,7 +755,7 @@ def test_static_blueprintp_mw(app: Sanic, static_file_directory, file_name): | |||||||
|         "/test.file", |         "/test.file", | ||||||
|         get_file_path(static_file_directory, file_name), |         get_file_path(static_file_directory, file_name), | ||||||
|         strict_slashes=True, |         strict_slashes=True, | ||||||
|         name="static" |         name="static", | ||||||
|     ) |     ) | ||||||
|  |  | ||||||
|     app.blueprint(bp) |     app.blueprint(bp) | ||||||
| @@ -824,21 +825,6 @@ def test_duplicate_blueprint(app): | |||||||
|     ) |     ) | ||||||
|  |  | ||||||
|  |  | ||||||
| @pytest.mark.parametrize("debug", [True, False, None]) |  | ||||||
| def test_register_blueprint(app, debug): |  | ||||||
|     bp = Blueprint("bp") |  | ||||||
|  |  | ||||||
|     app.debug = debug |  | ||||||
|     with pytest.warns(DeprecationWarning) as record: |  | ||||||
|         app.register_blueprint(bp) |  | ||||||
|  |  | ||||||
|     assert record[0].message.args[0] == ( |  | ||||||
|         "Use of register_blueprint will be deprecated in " |  | ||||||
|         "version 1.0.  Please use the blueprint method" |  | ||||||
|         " instead" |  | ||||||
|     ) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_strict_slashes_behavior_adoption(app): | def test_strict_slashes_behavior_adoption(app): | ||||||
|     app.strict_slashes = True |     app.strict_slashes = True | ||||||
|  |  | ||||||
|   | |||||||
| @@ -20,7 +20,9 @@ def test_load_module_from_file_location(loaded_module_from_file_location): | |||||||
|  |  | ||||||
|  |  | ||||||
| @pytest.mark.dependency(depends=["test_load_module_from_file_location"]) | @pytest.mark.dependency(depends=["test_load_module_from_file_location"]) | ||||||
| def test_loaded_module_from_file_location_name(loaded_module_from_file_location,): | def test_loaded_module_from_file_location_name( | ||||||
|  |     loaded_module_from_file_location, | ||||||
|  | ): | ||||||
|     name = loaded_module_from_file_location.__name__ |     name = loaded_module_from_file_location.__name__ | ||||||
|     if "C:\\" in name: |     if "C:\\" in name: | ||||||
|         name = name.split("\\")[-1] |         name = name.split("\\")[-1] | ||||||
|   | |||||||
| @@ -41,7 +41,8 @@ def test_response_body_not_a_string(app): | |||||||
|         return text(random_num) |         return text(random_num) | ||||||
|  |  | ||||||
|     request, response = app.test_client.get("/hello") |     request, response = app.test_client.get("/hello") | ||||||
|     assert response.text == str(random_num) |     assert response.status == 500 | ||||||
|  |     assert b"Internal Server Error" in response.body | ||||||
|  |  | ||||||
|  |  | ||||||
| async def sample_streaming_fn(response): | async def sample_streaming_fn(response): | ||||||
| @@ -238,7 +239,7 @@ def test_chunked_streaming_returns_correct_content(streaming_app): | |||||||
| @pytest.mark.asyncio | @pytest.mark.asyncio | ||||||
| async def test_chunked_streaming_returns_correct_content_asgi(streaming_app): | async def test_chunked_streaming_returns_correct_content_asgi(streaming_app): | ||||||
|     request, response = await streaming_app.asgi_client.get("/") |     request, response = await streaming_app.asgi_client.get("/") | ||||||
|     assert response.text == "4\r\nfoo,\r\n3\r\nbar\r\n0\r\n\r\n" |     assert response.text == "foo,bar" | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_non_chunked_streaming_adds_correct_headers(non_chunked_streaming_app): | def test_non_chunked_streaming_adds_correct_headers(non_chunked_streaming_app): | ||||||
| @@ -624,17 +625,3 @@ def test_empty_response(app): | |||||||
|     request, response = app.test_client.get("/test") |     request, response = app.test_client.get("/test") | ||||||
|     assert response.content_type is None |     assert response.content_type is None | ||||||
|     assert response.body == b"" |     assert response.body == b"" | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_response_body_bytes_deprecated(app): |  | ||||||
|     with warnings.catch_warnings(record=True) as w: |  | ||||||
|         warnings.simplefilter("always") |  | ||||||
|  |  | ||||||
|         HTTPResponse(body_bytes=b"bytes") |  | ||||||
|  |  | ||||||
|         assert len(w) == 1 |  | ||||||
|         assert issubclass(w[0].category, DeprecationWarning) |  | ||||||
|         assert ( |  | ||||||
|             "Parameter `body_bytes` is deprecated, use `body` instead" |  | ||||||
|             in str(w[0].message) |  | ||||||
|         ) |  | ||||||
|   | |||||||
							
								
								
									
										16
									
								
								tox.ini
									
									
									
									
									
								
							
							
						
						
									
										16
									
								
								tox.ini
									
									
									
									
									
								
							| @@ -1,24 +1,24 @@ | |||||||
| [tox] | [tox] | ||||||
| envlist = py36, py37, py38, pyNightly, {py36,py37,py38,pyNightly}-no-ext, lint, check, security, docs | envlist = py36, py37, py38, py39, pyNightly, {py36,py37,py38,py39,pyNightly}-no-ext, lint, check, security, docs | ||||||
|  |  | ||||||
| [testenv] | [testenv] | ||||||
| usedevelop = True | usedevelop = True | ||||||
| setenv = | setenv = | ||||||
|     {py36,py37,py38,pyNightly}-no-ext: SANIC_NO_UJSON=1 |     {py36,py37,py38,py39,pyNightly}-no-ext: SANIC_NO_UJSON=1 | ||||||
|     {py36,py37,py38,pyNightly}-no-ext: SANIC_NO_UVLOOP=1 |     {py36,py37,py38,py39,pyNightly}-no-ext: SANIC_NO_UVLOOP=1 | ||||||
| deps = | deps = | ||||||
|     coverage |     coverage==5.3 | ||||||
|     pytest==5.2.1 |     pytest==5.2.1 | ||||||
|     pytest-cov |     pytest-cov | ||||||
|     pytest-sanic |     pytest-sanic | ||||||
|     pytest-sugar |     pytest-sugar | ||||||
|     pytest-benchmark |     pytest-benchmark | ||||||
|     pytest-dependency |     pytest-dependency | ||||||
|     httpcore==0.3.0 |     httpcore==0.11.* | ||||||
|     httpx==0.15.4 |     httpx==0.15.4 | ||||||
|     chardet<=2.3.0 |     chardet==3.* | ||||||
|     beautifulsoup4 |     beautifulsoup4 | ||||||
|     gunicorn |     gunicorn==20.0.4 | ||||||
|     uvicorn |     uvicorn | ||||||
|     websockets>=8.1,<9.0 |     websockets>=8.1,<9.0 | ||||||
| commands = | commands = | ||||||
| @@ -76,7 +76,7 @@ deps = | |||||||
|     recommonmark>=0.5.0 |     recommonmark>=0.5.0 | ||||||
|     docutils |     docutils | ||||||
|     pygments |     pygments | ||||||
|     gunicorn |     gunicorn==20.0.4 | ||||||
|  |  | ||||||
| commands = | commands = | ||||||
|     make docs-test |     make docs-test | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user