LTS v21.12 Deprecations (#2306)

Co-authored-by: Néstor Pérez <25409753+prryplatypus@users.noreply.github.com>
This commit is contained in:
Adam Hopkins 2021-12-24 00:30:27 +02:00 committed by GitHub
parent 98ce4bdeb2
commit 8c07e388cd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
72 changed files with 638 additions and 847 deletions

View File

@ -1,12 +1,12 @@
.. note:: .. note::
From v21.9, CHANGELOG files are maintained in ``./docs/sanic/releases`` CHANGELOG files are maintained in ``./docs/sanic/releases``. To view the full CHANGELOG, please visit https://sanic.readthedocs.io/en/stable/sanic/changelog.html.
Version 21.6.1 Version 21.6.1
-------------- --------------
Bugfixes **Bugfixes**
********
* `#2178 <https://github.com/sanic-org/sanic/pull/2178>`_ * `#2178 <https://github.com/sanic-org/sanic/pull/2178>`_
Update sanic-routing to allow for better splitting of complex URI templates Update sanic-routing to allow for better splitting of complex URI templates
@ -20,8 +20,7 @@ Bugfixes
Version 21.6.0 Version 21.6.0
-------------- --------------
Features **Features**
********
* `#2094 <https://github.com/sanic-org/sanic/pull/2094>`_ * `#2094 <https://github.com/sanic-org/sanic/pull/2094>`_
Add ``response.eof()`` method for closing a stream in a handler Add ``response.eof()`` method for closing a stream in a handler
@ -68,8 +67,7 @@ Features
* `#2170 <https://github.com/sanic-org/sanic/pull/2170>`_ * `#2170 <https://github.com/sanic-org/sanic/pull/2170>`_
Additional methods for attaching ``HTTPMethodView`` Additional methods for attaching ``HTTPMethodView``
Bugfixes **Bugfixes**
********
* `#2091 <https://github.com/sanic-org/sanic/pull/2091>`_ * `#2091 <https://github.com/sanic-org/sanic/pull/2091>`_
Fix ``UserWarning`` in ASGI mode for missing ``__slots__`` Fix ``UserWarning`` in ASGI mode for missing ``__slots__``
@ -85,8 +83,7 @@ Bugfixes
Fix issue where Blueprint exception handlers do not consistently route to proper handler Fix issue where Blueprint exception handlers do not consistently route to proper handler
Deprecations and Removals **Deprecations and Removals**
*************************
* `#2156 <https://github.com/sanic-org/sanic/pull/2156>`_ * `#2156 <https://github.com/sanic-org/sanic/pull/2156>`_
Remove config value ``REQUEST_BUFFER_QUEUE_SIZE`` Remove config value ``REQUEST_BUFFER_QUEUE_SIZE``
@ -95,14 +92,12 @@ Deprecations and Removals
* `#2172 <https://github.com/sanic-org/sanic/pull/2170>`_ * `#2172 <https://github.com/sanic-org/sanic/pull/2170>`_
Deprecate StreamingHTTPResponse Deprecate StreamingHTTPResponse
Developer infrastructure **Developer infrastructure**
************************
* `#2149 <https://github.com/sanic-org/sanic/pull/2149>`_ * `#2149 <https://github.com/sanic-org/sanic/pull/2149>`_
Remove Travis CI in favor of GitHub Actions Remove Travis CI in favor of GitHub Actions
Improved Documentation **Improved Documentation**
**********************
* `#2164 <https://github.com/sanic-org/sanic/pull/2164>`_ * `#2164 <https://github.com/sanic-org/sanic/pull/2164>`_
Fix typo in documentation Fix typo in documentation
@ -112,8 +107,7 @@ Improved Documentation
Version 21.3.2 Version 21.3.2
-------------- --------------
Bugfixes **Bugfixes**
********
* `#2081 <https://github.com/sanic-org/sanic/pull/2081>`_ * `#2081 <https://github.com/sanic-org/sanic/pull/2081>`_
Disable response timeout on websocket connections Disable response timeout on websocket connections
@ -124,8 +118,7 @@ Bugfixes
Version 21.3.1 Version 21.3.1
-------------- --------------
Bugfixes **Bugfixes**
********
* `#2076 <https://github.com/sanic-org/sanic/pull/2076>`_ * `#2076 <https://github.com/sanic-org/sanic/pull/2076>`_
Static files inside subfolders are not accessible (404) Static files inside subfolders are not accessible (404)
@ -135,8 +128,7 @@ Version 21.3.0
`Release Notes <https://sanicframework.org/en/guide/release-notes/v21.3.html>`_ `Release Notes <https://sanicframework.org/en/guide/release-notes/v21.3.html>`_
Features **Features**
********
* *
`#1876 <https://github.com/sanic-org/sanic/pull/1876>`_ `#1876 <https://github.com/sanic-org/sanic/pull/1876>`_
@ -189,8 +181,7 @@ Features
`#2063 <https://github.com/sanic-org/sanic/pull/2063>`_ `#2063 <https://github.com/sanic-org/sanic/pull/2063>`_
App and connection level context objects App and connection level context objects
Bugfixes and issues resolved **Bugfixes**
****************************
* Resolve `#1420 <https://github.com/sanic-org/sanic/pull/1420>`_ * Resolve `#1420 <https://github.com/sanic-org/sanic/pull/1420>`_
``url_for`` where ``strict_slashes`` are on for a path ending in ``/`` ``url_for`` where ``strict_slashes`` are on for a path ending in ``/``
@ -220,8 +211,7 @@ Bugfixes and issues resolved
`#2001 <https://github.com/sanic-org/sanic/pull/2001>`_ `#2001 <https://github.com/sanic-org/sanic/pull/2001>`_
Raise ValueError when cookie max-age is not an integer Raise ValueError when cookie max-age is not an integer
Deprecations and Removals **Deprecations and Removals**
*************************
* *
`#2007 <https://github.com/sanic-org/sanic/pull/2007>`_ `#2007 <https://github.com/sanic-org/sanic/pull/2007>`_
@ -240,8 +230,7 @@ Deprecations and Removals
* ``Request.endpoint`` deprecated in favor of ``Request.name`` * ``Request.endpoint`` deprecated in favor of ``Request.name``
* handler type name prefixes removed (static, websocket, etc) * handler type name prefixes removed (static, websocket, etc)
Developer infrastructure **Developer infrastructure**
************************
* *
`#1995 <https://github.com/sanic-org/sanic/pull/1995>`_ `#1995 <https://github.com/sanic-org/sanic/pull/1995>`_
@ -259,8 +248,7 @@ Developer infrastructure
`#2049 <https://github.com/sanic-org/sanic/pull/2049>`_ `#2049 <https://github.com/sanic-org/sanic/pull/2049>`_
Updated setup.py to use ``find_packages`` Updated setup.py to use ``find_packages``
Improved Documentation **Improved Documentation**
**********************
* *
`#1218 <https://github.com/sanic-org/sanic/pull/1218>`_ `#1218 <https://github.com/sanic-org/sanic/pull/1218>`_
@ -282,8 +270,7 @@ Improved Documentation
`#2052 <https://github.com/sanic-org/sanic/pull/2052>`_ `#2052 <https://github.com/sanic-org/sanic/pull/2052>`_
Fix some examples and docs Fix some examples and docs
Miscellaneous **Miscellaneous**
*************
* ``Request.route`` property * ``Request.route`` property
* Better websocket subprotocols support * Better websocket subprotocols support
@ -329,8 +316,7 @@ Miscellaneous
Version 20.12.3 Version 20.12.3
--------------- ---------------
Bugfixes **Bugfixes**
********
* *
`#2021 <https://github.com/sanic-org/sanic/pull/2021>`_ `#2021 <https://github.com/sanic-org/sanic/pull/2021>`_
@ -339,8 +325,7 @@ Bugfixes
Version 20.12.2 Version 20.12.2
--------------- ---------------
Dependencies **Dependencies**
************
* *
`#2026 <https://github.com/sanic-org/sanic/pull/2026>`_ `#2026 <https://github.com/sanic-org/sanic/pull/2026>`_
@ -353,8 +338,7 @@ Dependencies
Version 19.12.5 Version 19.12.5
--------------- ---------------
Dependencies **Dependencies**
************
* *
`#2025 <https://github.com/sanic-org/sanic/pull/2025>`_ `#2025 <https://github.com/sanic-org/sanic/pull/2025>`_
@ -367,8 +351,7 @@ Dependencies
Version 20.12.0 Version 20.12.0
--------------- ---------------
Features **Features**
********
* *
`#1993 <https://github.com/sanic-org/sanic/pull/1993>`_ `#1993 <https://github.com/sanic-org/sanic/pull/1993>`_
@ -377,8 +360,7 @@ Features
Version 20.12.0 Version 20.12.0
--------------- ---------------
Features **Features**
********
* *
`#1945 <https://github.com/sanic-org/sanic/pull/1945>`_ `#1945 <https://github.com/sanic-org/sanic/pull/1945>`_
@ -416,22 +398,19 @@ Features
`#1979 <https://github.com/sanic-org/sanic/pull/1979>`_ `#1979 <https://github.com/sanic-org/sanic/pull/1979>`_
Add app registry and Sanic class level app retrieval Add app registry and Sanic class level app retrieval
Bugfixes **Bugfixes**
********
* *
`#1965 <https://github.com/sanic-org/sanic/pull/1965>`_ `#1965 <https://github.com/sanic-org/sanic/pull/1965>`_
Fix Chunked Transport-Encoding in ASGI streaming response Fix Chunked Transport-Encoding in ASGI streaming response
Deprecations and Removals **Deprecations and Removals**
*************************
* *
`#1981 <https://github.com/sanic-org/sanic/pull/1981>`_ `#1981 <https://github.com/sanic-org/sanic/pull/1981>`_
Cleanup and remove deprecated code Cleanup and remove deprecated code
Developer infrastructure **Developer infrastructure**
************************
* *
`#1956 <https://github.com/sanic-org/sanic/pull/1956>`_ `#1956 <https://github.com/sanic-org/sanic/pull/1956>`_
@ -445,8 +424,7 @@ Developer infrastructure
`#1986 <https://github.com/sanic-org/sanic/pull/1986>`_ `#1986 <https://github.com/sanic-org/sanic/pull/1986>`_
Update tox requirements Update tox requirements
Improved Documentation **Improved Documentation**
**********************
* *
`#1951 <https://github.com/sanic-org/sanic/pull/1951>`_ `#1951 <https://github.com/sanic-org/sanic/pull/1951>`_
@ -464,8 +442,7 @@ Improved Documentation
Version 20.9.1 Version 20.9.1
--------------- ---------------
Bugfixes **Bugfixes**
********
* *
`#1954 <https://github.com/sanic-org/sanic/pull/1954>`_ `#1954 <https://github.com/sanic-org/sanic/pull/1954>`_
@ -478,8 +455,7 @@ Bugfixes
Version 19.12.3 Version 19.12.3
--------------- ---------------
Bugfixes **Bugfixes**
********
* *
`#1959 <https://github.com/sanic-org/sanic/pull/1959>`_ `#1959 <https://github.com/sanic-org/sanic/pull/1959>`_
@ -490,8 +466,7 @@ Version 20.9.0
--------------- ---------------
Features **Features**
********
* *
`#1887 <https://github.com/sanic-org/sanic/pull/1887>`_ `#1887 <https://github.com/sanic-org/sanic/pull/1887>`_
@ -518,22 +493,19 @@ Features
`#1937 <https://github.com/sanic-org/sanic/pull/1937>`_ `#1937 <https://github.com/sanic-org/sanic/pull/1937>`_
Added auto, text, and json fallback error handlers (in v21.3, the default will change form html to auto) Added auto, text, and json fallback error handlers (in v21.3, the default will change form html to auto)
Bugfixes **Bugfixes**
********
* *
`#1897 <https://github.com/sanic-org/sanic/pull/1897>`_ `#1897 <https://github.com/sanic-org/sanic/pull/1897>`_
Resolves exception from unread bytes in stream Resolves exception from unread bytes in stream
Deprecations and Removals **Deprecations and Removals**
*************************
* *
`#1903 <https://github.com/sanic-org/sanic/pull/1903>`_ `#1903 <https://github.com/sanic-org/sanic/pull/1903>`_
config.from_envar, config.from_pyfile, and config.from_object are deprecated and set to be removed in v21.3 config.from_envar, config.from_pyfile, and config.from_object are deprecated and set to be removed in v21.3
Developer infrastructure **Developer infrastructure**
************************
* *
`#1890 <https://github.com/sanic-org/sanic/pull/1890>`_, `#1890 <https://github.com/sanic-org/sanic/pull/1890>`_,
@ -548,8 +520,7 @@ Developer infrastructure
`#1924 <https://github.com/sanic-org/sanic/pull/1924>`_ `#1924 <https://github.com/sanic-org/sanic/pull/1924>`_
Adding --strict-markers for pytest Adding --strict-markers for pytest
Improved Documentation **Improved Documentation**
**********************
* *
`#1922 <https://github.com/sanic-org/sanic/pull/1922>`_ `#1922 <https://github.com/sanic-org/sanic/pull/1922>`_
@ -559,8 +530,7 @@ Improved Documentation
Version 20.6.3 Version 20.6.3
--------------- ---------------
Bugfixes **Bugfixes**
********
* *
`#1884 <https://github.com/sanic-org/sanic/pull/1884>`_ `#1884 <https://github.com/sanic-org/sanic/pull/1884>`_
@ -570,8 +540,7 @@ Bugfixes
Version 20.6.2 Version 20.6.2
--------------- ---------------
Features **Features**
********
* *
`#1641 <https://github.com/sanic-org/sanic/pull/1641>`_ `#1641 <https://github.com/sanic-org/sanic/pull/1641>`_
@ -581,8 +550,7 @@ Features
Version 20.6.1 Version 20.6.1
--------------- ---------------
Features **Features**
********
* *
`#1760 <https://github.com/sanic-org/sanic/pull/1760>`_ `#1760 <https://github.com/sanic-org/sanic/pull/1760>`_
@ -596,8 +564,7 @@ Features
`#1880 <https://github.com/sanic-org/sanic/pull/1880>`_ `#1880 <https://github.com/sanic-org/sanic/pull/1880>`_
Add handler names for websockets for url_for usage Add handler names for websockets for url_for usage
Bugfixes **Bugfixes**
********
* *
`#1776 <https://github.com/sanic-org/sanic/pull/1776>`_ `#1776 <https://github.com/sanic-org/sanic/pull/1776>`_
@ -619,15 +586,13 @@ Bugfixes
`#1853 <https://github.com/sanic-org/sanic/pull/1853>`_ `#1853 <https://github.com/sanic-org/sanic/pull/1853>`_
Fix pickle error when attempting to pickle an application which contains websocket routes Fix pickle error when attempting to pickle an application which contains websocket routes
Deprecations and Removals **Deprecations and Removals**
*************************
* *
`#1739 <https://github.com/sanic-org/sanic/pull/1739>`_ `#1739 <https://github.com/sanic-org/sanic/pull/1739>`_
Deprecate body_bytes to merge into body Deprecate body_bytes to merge into body
Developer infrastructure **Developer infrastructure**
************************
* *
`#1852 <https://github.com/sanic-org/sanic/pull/1852>`_ `#1852 <https://github.com/sanic-org/sanic/pull/1852>`_
@ -642,8 +607,7 @@ Developer infrastructure
Wrap run()'s "protocol" type annotation in Optional[] Wrap run()'s "protocol" type annotation in Optional[]
Improved Documentation **Improved Documentation**
**********************
* *
`#1846 <https://github.com/sanic-org/sanic/pull/1846>`_ `#1846 <https://github.com/sanic-org/sanic/pull/1846>`_
@ -663,8 +627,7 @@ Version 20.6.0
Version 20.3.0 Version 20.3.0
--------------- ---------------
Features **Features**
********
* *
`#1762 <https://github.com/sanic-org/sanic/pull/1762>`_ `#1762 <https://github.com/sanic-org/sanic/pull/1762>`_
@ -695,8 +658,7 @@ Features
`#1820 <https://github.com/sanic-org/sanic/pull/1820>`_ `#1820 <https://github.com/sanic-org/sanic/pull/1820>`_
Do not set content-type and content-length headers in exceptions Do not set content-type and content-length headers in exceptions
Bugfixes **Bugfixes**
********
* *
`#1748 <https://github.com/sanic-org/sanic/pull/1748>`_ `#1748 <https://github.com/sanic-org/sanic/pull/1748>`_
@ -714,8 +676,7 @@ Bugfixes
`#1808 <https://github.com/sanic-org/sanic/pull/1808>`_ `#1808 <https://github.com/sanic-org/sanic/pull/1808>`_
Fix Ctrl+C and tests on Windows Fix Ctrl+C and tests on Windows
Deprecations and Removals **Deprecations and Removals**
*************************
* *
`#1800 <https://github.com/sanic-org/sanic/pull/1800>`_ `#1800 <https://github.com/sanic-org/sanic/pull/1800>`_
@ -733,8 +694,7 @@ Deprecations and Removals
`#1818 <https://github.com/sanic-org/sanic/pull/1818>`_ `#1818 <https://github.com/sanic-org/sanic/pull/1818>`_
Complete deprecation of ``app.remove_route`` and ``request.raw_args`` Complete deprecation of ``app.remove_route`` and ``request.raw_args``
Dependencies **Dependencies**
************
* *
`#1794 <https://github.com/sanic-org/sanic/pull/1794>`_ `#1794 <https://github.com/sanic-org/sanic/pull/1794>`_
@ -744,15 +704,13 @@ Dependencies
`#1806 <https://github.com/sanic-org/sanic/pull/1806>`_ `#1806 <https://github.com/sanic-org/sanic/pull/1806>`_
Import ``ASGIDispatch`` from top-level ``httpx`` (from third-party deprecation) Import ``ASGIDispatch`` from top-level ``httpx`` (from third-party deprecation)
Developer infrastructure **Developer infrastructure**
************************
* *
`#1833 <https://github.com/sanic-org/sanic/pull/1833>`_ `#1833 <https://github.com/sanic-org/sanic/pull/1833>`_
Resolve broken documentation builds Resolve broken documentation builds
Improved Documentation **Improved Documentation**
**********************
* *
`#1755 <https://github.com/sanic-org/sanic/pull/1755>`_ `#1755 <https://github.com/sanic-org/sanic/pull/1755>`_
@ -794,8 +752,7 @@ Improved Documentation
Version 19.12.0 Version 19.12.0
--------------- ---------------
Bugfixes **Bugfixes**
********
- Fix blueprint middleware application - Fix blueprint middleware application
@ -814,8 +771,7 @@ Bugfixes
due to an `AttributeError`. This fix makes the availability of `SERVER_NAME` on our `app.config` an optional behavior. (`#1707 <https://github.com/sanic-org/sanic/issues/1707>`__) due to an `AttributeError`. This fix makes the availability of `SERVER_NAME` on our `app.config` an optional behavior. (`#1707 <https://github.com/sanic-org/sanic/issues/1707>`__)
Improved Documentation **Improved Documentation**
**********************
- Move docs from MD to RST - Move docs from MD to RST
@ -829,8 +785,7 @@ Improved Documentation
Version 19.6.3 Version 19.6.3
-------------- --------------
Features **Features**
********
- Enable Towncrier Support - Enable Towncrier Support
@ -838,8 +793,7 @@ Features
of generating and managing change logs as part of each of pull requests. (`#1631 <https://github.com/sanic-org/sanic/issues/1631>`__) of generating and managing change logs as part of each of pull requests. (`#1631 <https://github.com/sanic-org/sanic/issues/1631>`__)
Improved Documentation **Improved Documentation**
**********************
- Documentation infrastructure changes - Documentation infrastructure changes
@ -852,8 +806,7 @@ Improved Documentation
Version 19.6.2 Version 19.6.2
-------------- --------------
Features **Features**
********
* *
`#1562 <https://github.com/sanic-org/sanic/pull/1562>`_ `#1562 <https://github.com/sanic-org/sanic/pull/1562>`_
@ -869,8 +822,7 @@ Features
Add Configure support from object string Add Configure support from object string
Bugfixes **Bugfixes**
********
* *
`#1587 <https://github.com/sanic-org/sanic/pull/1587>`_ `#1587 <https://github.com/sanic-org/sanic/pull/1587>`_
@ -888,8 +840,7 @@ Bugfixes
`#1594 <https://github.com/sanic-org/sanic/pull/1594>`_ `#1594 <https://github.com/sanic-org/sanic/pull/1594>`_
Strict Slashes behavior fix Strict Slashes behavior fix
Deprecations and Removals **Deprecations and Removals**
*************************
* *
`#1544 <https://github.com/sanic-org/sanic/pull/1544>`_ `#1544 <https://github.com/sanic-org/sanic/pull/1544>`_
@ -913,8 +864,7 @@ Deprecations and Removals
Version 19.3 Version 19.3
------------ ------------
Features **Features**
********
* *
`#1497 <https://github.com/sanic-org/sanic/pull/1497>`_ `#1497 <https://github.com/sanic-org/sanic/pull/1497>`_
@ -982,8 +932,7 @@ Features
This is a breaking change. This is a breaking change.
Bugfixes **Bugfixes**
********
* *
@ -1019,8 +968,7 @@ Bugfixes
This allows the access log to be disabled for example when running via This allows the access log to be disabled for example when running via
gunicorn. gunicorn.
Developer infrastructure **Developer infrastructure**
************************
* `#1529 <https://github.com/sanic-org/sanic/pull/1529>`_ Update project PyPI credentials * `#1529 <https://github.com/sanic-org/sanic/pull/1529>`_ Update project PyPI credentials
* `#1515 <https://github.com/sanic-org/sanic/pull/1515>`_ fix linter issue causing travis build failures (fix #1514) * `#1515 <https://github.com/sanic-org/sanic/pull/1515>`_ fix linter issue causing travis build failures (fix #1514)
@ -1028,8 +976,7 @@ Developer infrastructure
* `#1478 <https://github.com/sanic-org/sanic/pull/1478>`_ Upgrade setuptools version and use native docutils in doc build * `#1478 <https://github.com/sanic-org/sanic/pull/1478>`_ Upgrade setuptools version and use native docutils in doc build
* `#1464 <https://github.com/sanic-org/sanic/pull/1464>`_ Upgrade pytest, and fix caplog unit tests * `#1464 <https://github.com/sanic-org/sanic/pull/1464>`_ Upgrade pytest, and fix caplog unit tests
Improved Documentation **Improved Documentation**
**********************
* `#1516 <https://github.com/sanic-org/sanic/pull/1516>`_ Fix typo at the exception documentation * `#1516 <https://github.com/sanic-org/sanic/pull/1516>`_ Fix typo at the exception documentation
* `#1510 <https://github.com/sanic-org/sanic/pull/1510>`_ fix typo in Asyncio example * `#1510 <https://github.com/sanic-org/sanic/pull/1510>`_ fix typo in Asyncio example
@ -1096,15 +1043,13 @@ Version 18.12
Version 0.8 Version 0.8
----------- -----------
0.8.3 **0.8.3**
*****
* Changes: * Changes:
* Ownership changed to org 'sanic-org' * Ownership changed to org 'sanic-org'
0.8.0 **0.8.0**
*****
* Changes: * Changes:
@ -1184,19 +1129,16 @@ Version 0.1
----------- -----------
0.1.7 **0.1.7**
*****
* Reversed static url and directory arguments to meet spec * Reversed static url and directory arguments to meet spec
0.1.6 **0.1.6**
*****
* Static files * Static files
* Lazy Cookie Loading * Lazy Cookie Loading
0.1.5 **0.1.5**
*****
* Cookies * Cookies
* Blueprint listeners and ordering * Blueprint listeners and ordering
@ -1204,23 +1146,19 @@ Version 0.1
* Fix: Incomplete file reads on medium+ sized post requests * Fix: Incomplete file reads on medium+ sized post requests
* Breaking: after_start and before_stop now pass sanic as their first argument * Breaking: after_start and before_stop now pass sanic as their first argument
0.1.4 **0.1.4**
*****
* Multiprocessing * Multiprocessing
0.1.3 **0.1.3**
*****
* Blueprint support * Blueprint support
* Faster Response processing * Faster Response processing
0.1.1 - 0.1.2 **0.1.1 - 0.1.2**
*************
* Struggling to update pypi via CI * Struggling to update pypi via CI
0.1.0 **0.1.0**
*****
* Released to public * Released to public

View File

@ -38,10 +38,3 @@ sanic.views
.. automodule:: sanic.views .. automodule:: sanic.views
:members: :members:
:show-inheritance: :show-inheritance:
sanic.websocket
---------------
.. automodule:: sanic.websocket
:members:
:show-inheritance:

View File

@ -1,6 +1,6 @@
📜 Changelog 📜 Changelog
============ ============
.. mdinclude:: ./releases/21.9.md .. mdinclude:: ./releases/21/21.12.md
.. mdinclude:: ./releases/21/21.9.md
.. include:: ../../CHANGELOG.rst .. include:: ../../CHANGELOG.rst

View File

@ -0,0 +1,58 @@
## Version 21.12.0
### Features
- [#2260](https://github.com/sanic-org/sanic/pull/2260) Allow early Blueprint registrations to still apply later added objects
- [#2262](https://github.com/sanic-org/sanic/pull/2262) Noisy exceptions - force logging of all exceptions
- [#2264](https://github.com/sanic-org/sanic/pull/2264) Optional `uvloop` by configuration
- [#2270](https://github.com/sanic-org/sanic/pull/2270) Vhost support using multiple TLS certificates
- [#2277](https://github.com/sanic-org/sanic/pull/2277) Change signal routing for increased consistency
- *BREAKING CHANGE*: If you were manually routing signals there is a breaking change. The signal router's `get` is no longer 100% determinative. There is now an additional step to loop thru the returned signals for proper matching on the requirements. If signals are being dispatched using `app.dispatch` or `bp.dispatch`, there is no change.
- [#2290](https://github.com/sanic-org/sanic/pull/2290) Add contextual exceptions
- [#2291](https://github.com/sanic-org/sanic/pull/2291) Increase join concat performance
- [#2295](https://github.com/sanic-org/sanic/pull/2295), [#2316](https://github.com/sanic-org/sanic/pull/2316), [#2331](https://github.com/sanic-org/sanic/pull/2331) Restructure of CLI and application state with new displays and more command parity with `app.run`
- [#2302](https://github.com/sanic-org/sanic/pull/2302) Add route context at definition time
- [#2304](https://github.com/sanic-org/sanic/pull/2304) Named tasks and new API for managing background tasks
- [#2307](https://github.com/sanic-org/sanic/pull/2307) On app auto-reload, provide insight of changed files
- [#2308](https://github.com/sanic-org/sanic/pull/2308) Auto extend application with [Sanic Extensions](https://sanicframework.org/en/plugins/sanic-ext/getting-started.html) if it is installed, and provide first class support for accessing the extensions
- [#2309](https://github.com/sanic-org/sanic/pull/2309) Builtin signals changed to `Enum`
- [#2313](https://github.com/sanic-org/sanic/pull/2313) Support additional config implementation use case
- [#2321](https://github.com/sanic-org/sanic/pull/2321) Refactor environment variable hydration logic
- [#2327](https://github.com/sanic-org/sanic/pull/2327) Prevent sending multiple or mixed responses on a single request
- [#2330](https://github.com/sanic-org/sanic/pull/2330) Custom type casting on environment variables
- [#2332](https://github.com/sanic-org/sanic/pull/2332) Make all deprecation notices consistent
- [#2335](https://github.com/sanic-org/sanic/pull/2335) Allow underscore to start instance names
### Bugfixes
- [#2273](https://github.com/sanic-org/sanic/pull/2273) Replace assignation by typing for `websocket_handshake`
- [#2285](https://github.com/sanic-org/sanic/pull/2285) Fix IPv6 display in startup logs
- [#2299](https://github.com/sanic-org/sanic/pull/2299) Dispatch `http.lifecyle.response` from exception handler
### Deprecations and Removals
- [#2306](https://github.com/sanic-org/sanic/pull/2306) Removal of deprecated items
- `Sanic` and `Blueprint` may no longer have arbitrary properties attached to them
- `Sanic` and `Blueprint` forced to have compliant names
- alphanumeric + `_` + `-`
- must start with letter or `_`
- `load_env` keyword argument of `Sanic`
- `sanic.exceptions.abort`
- `sanic.views.CompositionView`
- `sanic.response.StreamingHTTPResponse`
- *NOTE:* the `stream()` response method (where you pass a callable streaming function) has been deprecated and will be removed in v22.6. You should upgrade all streaming responses to the new style: https://sanicframework.org/en/guide/advanced/streaming.html#response-streaming
- [#2320](https://github.com/sanic-org/sanic/pull/2320) Remove app instance from Config for error handler setting
### Developer infrastructure
- [#2251](https://github.com/sanic-org/sanic/pull/2251) Change dev install command
- [#2286](https://github.com/sanic-org/sanic/pull/2286) Change codeclimate complexity threshold from 5 to 10
- [#2287](https://github.com/sanic-org/sanic/pull/2287) Update host test function names so they are not overwritten
- [#2292](https://github.com/sanic-org/sanic/pull/2292) Fail CI on error
- [#2311](https://github.com/sanic-org/sanic/pull/2311), [#2324](https://github.com/sanic-org/sanic/pull/2324) Do not run tests for draft PRs
- [#2336](https://github.com/sanic-org/sanic/pull/2336) Remove paths from coverage checks
- [#2338](https://github.com/sanic-org/sanic/pull/2338) Cleanup ports on tests
### Improved Documentation
- [#2269](https://github.com/sanic-org/sanic/pull/2269), [#2329](https://github.com/sanic-org/sanic/pull/2329), [#2333](https://github.com/sanic-org/sanic/pull/2333) Cleanup typos and fix language
### Miscellaneous
- [#2257](https://github.com/sanic-org/sanic/pull/2257), [#2294](https://github.com/sanic-org/sanic/pull/2294), [#2341](https://github.com/sanic-org/sanic/pull/2341) Add Python 3.10 support
- [#2279](https://github.com/sanic-org/sanic/pull/2279), [#2317](https://github.com/sanic-org/sanic/pull/2317), [#2322](https://github.com/sanic-org/sanic/pull/2322) Add/correct missing type annotations
- [#2305](https://github.com/sanic-org/sanic/pull/2305) Fix examples to use modern implementations

View File

@ -1,4 +1,14 @@
## Version 21.9 ## Version 21.9.3
*Rerelease of v21.9.2 with some cleanup*
## Version 21.9.2
- [#2268](https://github.com/sanic-org/sanic/pull/2268) Make HTTP connections start in IDLE stage, avoiding delays and error messages
- [#2310](https://github.com/sanic-org/sanic/pull/2310) More consistent config setting with post-FALLBACK_ERROR_FORMAT apply
## Version 21.9.1
- [#2259](https://github.com/sanic-org/sanic/pull/2259) Allow non-conforming ErrorHandlers
## Version 21.9.0
### Features ### Features
- [#2158](https://github.com/sanic-org/sanic/pull/2158), [#2248](https://github.com/sanic-org/sanic/pull/2248) Complete overhaul of I/O to websockets - [#2158](https://github.com/sanic-org/sanic/pull/2158), [#2248](https://github.com/sanic-org/sanic/pull/2248) Complete overhaul of I/O to websockets

View File

@ -5,7 +5,7 @@ import asyncio
from sanic import Sanic from sanic import Sanic
app = Sanic(__name__) app = Sanic("Example")
async def notify_server_started_after_five_seconds(): async def notify_server_started_after_five_seconds():

View File

@ -4,7 +4,7 @@ from sanic import Sanic
from sanic.response import text from sanic.response import text
app = Sanic(__name__) app = Sanic("Example")
@app.middleware("request") @app.middleware("request")

View File

@ -6,7 +6,7 @@ from sanic import Sanic
from sanic.response import json from sanic.response import json
app = Sanic(__name__) app = Sanic("Example")
def check_request_for_authorization_status(request): def check_request_for_authorization_status(request):

View File

@ -8,9 +8,9 @@ are added. And blueprint response middleware are executed in _reverse_ order.
On a valid request, it should print "1 2 3 6 5 4" to terminal On a valid request, it should print "1 2 3 6 5 4" to terminal
""" """
app = Sanic(__name__) app = Sanic("Example")
bp = Blueprint("bp_" + __name__) bp = Blueprint("bp_example")
@bp.on_request @bp.on_request

View File

@ -2,10 +2,10 @@ from sanic import Blueprint, Sanic
from sanic.response import file, json from sanic.response import file, json
app = Sanic(__name__) app = Sanic("Example")
blueprint = Blueprint("name", url_prefix="/my_blueprint") blueprint = Blueprint("bp_example", url_prefix="/my_blueprint")
blueprint2 = Blueprint("name2", url_prefix="/my_blueprint2") blueprint2 = Blueprint("bp_example2", url_prefix="/my_blueprint2")
blueprint3 = Blueprint("name3", url_prefix="/my_blueprint3") blueprint3 = Blueprint("bp_example3", url_prefix="/my_blueprint3")
@blueprint.route("/foo") @blueprint.route("/foo")

View File

@ -3,7 +3,7 @@ from asyncio import sleep
from sanic import Sanic, response from sanic import Sanic, response
app = Sanic(__name__, strict_slashes=True) app = Sanic("DelayedResponseApp", strict_slashes=True)
@app.get("/") @app.get("/")

View File

@ -41,7 +41,7 @@ from sanic import Sanic
handler = CustomHandler() handler = CustomHandler()
app = Sanic(__name__, error_handler=handler) app = Sanic("Example", error_handler=handler)
@app.route("/") @app.route("/")

View File

@ -1,7 +1,7 @@
from sanic import Sanic from sanic import Sanic, response
from sanic import response
app = Sanic(__name__)
app = Sanic("Example")
@app.route("/") @app.route("/")
@ -9,5 +9,5 @@ async def test(request):
return response.json({"test": True}) return response.json({"test": True})
if __name__ == '__main__': if __name__ == "__main__":
app.run(host="0.0.0.0", port=8000) app.run(host="0.0.0.0", port=8000)

View File

@ -6,7 +6,7 @@ from sanic import Sanic
from sanic.response import json from sanic.response import json
app = Sanic(__name__) app = Sanic("Example")
sem = None sem = None

View File

@ -44,7 +44,7 @@ LOG_SETTINGS = {
} }
app = Sanic(__name__, log_config=LOG_SETTINGS) app = Sanic("Example", log_config=LOG_SETTINGS)
@app.on_request @app.on_request

View File

@ -43,7 +43,7 @@ logdna = logging.getLogger(__name__)
logdna.setLevel(logging.INFO) logdna.setLevel(logging.INFO)
logdna.addHandler(logdna_handler) logdna.addHandler(logdna_handler)
app = Sanic(__name__) app = Sanic("Example")
@app.middleware @app.middleware

View File

@ -2,27 +2,29 @@
Modify header or status in response Modify header or status in response
""" """
from sanic import Sanic from sanic import Sanic, response
from sanic import response
app = Sanic(__name__)
@app.route('/') app = Sanic("Example")
@app.route("/")
def handle_request(request): def handle_request(request):
return response.json( return response.json(
{'message': 'Hello world!'}, {"message": "Hello world!"},
headers={'X-Served-By': 'sanic'}, headers={"X-Served-By": "sanic"},
status=200 status=200,
) )
@app.route('/unauthorized') @app.route("/unauthorized")
def handle_request(request): def handle_request(request):
return response.json( return response.json(
{'message': 'You are not authorized'}, {"message": "You are not authorized"},
headers={'X-Served-By': 'sanic'}, headers={"X-Served-By": "sanic"},
status=404 status=404,
) )
if __name__ == "__main__":
app.run(host="0.0.0.0", port=8000, debug=True) app.run(host="0.0.0.0", port=8000, debug=True)

View File

@ -32,7 +32,7 @@ def test_port(worker_id):
@pytest.fixture(scope="session") @pytest.fixture(scope="session")
def app(): def app():
app = Sanic() app = Sanic("Example")
@app.route("/") @app.route("/")
async def index(request): async def index(request):

View File

@ -8,7 +8,6 @@ from sanic.handlers import ErrorHandler
class RaygunExceptionReporter(ErrorHandler): class RaygunExceptionReporter(ErrorHandler):
def __init__(self, raygun_api_key=None): def __init__(self, raygun_api_key=None):
super().__init__() super().__init__()
if raygun_api_key is None: if raygun_api_key is None:
@ -22,16 +21,13 @@ class RaygunExceptionReporter(ErrorHandler):
raygun_error_reporter = RaygunExceptionReporter() raygun_error_reporter = RaygunExceptionReporter()
app = Sanic(__name__, error_handler=raygun_error_reporter) app = Sanic("Example", error_handler=raygun_error_reporter)
@app.route("/raise") @app.route("/raise")
async def test(request): async def test(request):
raise SanicException('You Broke It!') raise SanicException("You Broke It!")
if __name__ == '__main__': if __name__ == "__main__":
app.run( app.run(host="0.0.0.0", port=getenv("PORT", 8080))
host="0.0.0.0",
port=getenv("PORT", 8080)
)

View File

@ -1,18 +1,18 @@
from sanic import Sanic from sanic import Sanic, response
from sanic import response
app = Sanic(__name__)
@app.route('/') app = Sanic("Example")
@app.route("/")
def handle_request(request): def handle_request(request):
return response.redirect('/redirect') return response.redirect("/redirect")
@app.route('/redirect') @app.route("/redirect")
async def test(request): async def test(request):
return response.json({"Redirected": True}) return response.json({"Redirected": True})
if __name__ == '__main__': if __name__ == "__main__":
app.run(host="0.0.0.0", port=8000) app.run(host="0.0.0.0", port=8000)

View File

@ -1,65 +1,63 @@
from sanic import Sanic from sanic import Sanic
from sanic.views import CompositionView
from sanic.views import HTTPMethodView
from sanic.views import stream as stream_decorator
from sanic.blueprints import Blueprint from sanic.blueprints import Blueprint
from sanic.response import stream, text from sanic.response import stream, text
from sanic.views import HTTPMethodView
from sanic.views import stream as stream_decorator
bp = Blueprint('blueprint_request_stream')
app = Sanic('request_stream') bp = Blueprint("bp_example")
app = Sanic("Example")
class SimpleView(HTTPMethodView): class SimpleView(HTTPMethodView):
@stream_decorator @stream_decorator
async def post(self, request): async def post(self, request):
result = '' result = ""
while True: while True:
body = await request.stream.get() body = await request.stream.get()
if body is None: if body is None:
break break
result += body.decode('utf-8') result += body.decode("utf-8")
return text(result) return text(result)
@app.post('/stream', stream=True) @app.post("/stream", stream=True)
async def handler(request): async def handler(request):
async def streaming(response): async def streaming(response):
while True: while True:
body = await request.stream.get() body = await request.stream.get()
if body is None: if body is None:
break break
body = body.decode('utf-8').replace('1', 'A') body = body.decode("utf-8").replace("1", "A")
await response.write(body) await response.write(body)
return stream(streaming) return stream(streaming)
@bp.put('/bp_stream', stream=True) @bp.put("/bp_stream", stream=True)
async def bp_handler(request): async def bp_handler(request):
result = '' result = ""
while True: while True:
body = await request.stream.get() body = await request.stream.get()
if body is None: if body is None:
break break
result += body.decode('utf-8').replace('1', 'A') result += body.decode("utf-8").replace("1", "A")
return text(result) return text(result)
async def post_handler(request): async def post_handler(request):
result = '' result = ""
while True: while True:
body = await request.stream.get() body = await request.stream.get()
if body is None: if body is None:
break break
result += body.decode('utf-8') result += body.decode("utf-8")
return text(result) return text(result)
app.blueprint(bp) app.blueprint(bp)
app.add_route(SimpleView.as_view(), '/method_view') app.add_route(SimpleView.as_view(), "/method_view")
view = CompositionView()
view.add(['POST'], post_handler, stream=True)
app.add_route(view, '/composition_view')
if __name__ == '__main__': if __name__ == "__main__":
app.run(host='0.0.0.0', port=8000) app.run(host="0.0.0.0", port=8000)

View File

@ -1,21 +1,23 @@
import asyncio import asyncio
from sanic import Sanic
from sanic import response from sanic import Sanic, response
from sanic.config import Config from sanic.config import Config
from sanic.exceptions import RequestTimeout from sanic.exceptions import RequestTimeout
Config.REQUEST_TIMEOUT = 1 Config.REQUEST_TIMEOUT = 1
app = Sanic(__name__) app = Sanic("Example")
@app.route('/') @app.route("/")
async def test(request): async def test(request):
await asyncio.sleep(3) await asyncio.sleep(3)
return response.text('Hello, world!') return response.text("Hello, world!")
@app.exception(RequestTimeout) @app.exception(RequestTimeout)
def timeout(request, exception): def timeout(request, exception):
return response.text('RequestTimeout from error_handler.', 408) return response.text("RequestTimeout from error_handler.", 408)
app.run(host='0.0.0.0', port=8000)
app.run(host="0.0.0.0", port=8000)

View File

@ -1,21 +1,22 @@
from os import getenv
import rollbar import rollbar
from sanic.handlers import ErrorHandler
from sanic import Sanic from sanic import Sanic
from sanic.exceptions import SanicException from sanic.exceptions import SanicException
from os import getenv from sanic.handlers import ErrorHandler
rollbar.init(getenv("ROLLBAR_API_KEY")) rollbar.init(getenv("ROLLBAR_API_KEY"))
class RollbarExceptionHandler(ErrorHandler): class RollbarExceptionHandler(ErrorHandler):
def default(self, request, exception): def default(self, request, exception):
rollbar.report_message(str(exception)) rollbar.report_message(str(exception))
return super().default(request, exception) return super().default(request, exception)
app = Sanic(__name__, error_handler=RollbarExceptionHandler()) app = Sanic("Example", error_handler=RollbarExceptionHandler())
@app.route("/raise") @app.route("/raise")
@ -24,7 +25,4 @@ def create_error(request):
if __name__ == "__main__": if __name__ == "__main__":
app.run( app.run(host="0.0.0.0", port=getenv("PORT", 8080))
host="0.0.0.0",
port=getenv("PORT", 8080)
)

View File

@ -11,7 +11,7 @@ from pathlib import Path
from sanic import Sanic, response from sanic import Sanic, response
app = Sanic(__name__) app = Sanic("Example")
@app.route("/text") @app.route("/text")

View File

@ -5,7 +5,7 @@ import uvloop
from sanic import Sanic, response from sanic import Sanic, response
app = Sanic(__name__) app = Sanic("Example")
@app.route("/") @app.route("/")

View File

@ -8,7 +8,7 @@ from sanic import Sanic, response
from sanic.server import AsyncioServer from sanic.server import AsyncioServer
app = Sanic(__name__) app = Sanic("Example")
@app.before_server_start @app.before_server_start

View File

@ -6,20 +6,19 @@ from sentry_sdk.integrations.sanic import SanicIntegration
from sanic import Sanic from sanic import Sanic
from sanic.response import json from sanic.response import json
sentry_init( sentry_init(
dsn=getenv("SENTRY_DSN"), dsn=getenv("SENTRY_DSN"),
integrations=[SanicIntegration()], integrations=[SanicIntegration()],
) )
app = Sanic(__name__) app = Sanic("Example")
# noinspection PyUnusedLocal # noinspection PyUnusedLocal
@app.route("/working") @app.route("/working")
async def working_path(request): async def working_path(request):
return json({ return json({"response": "Working API Response"})
"response": "Working API Response"
})
# noinspection PyUnusedLocal # noinspection PyUnusedLocal
@ -28,8 +27,5 @@ async def raise_error(request):
raise Exception("Testing Sentry Integration") raise Exception("Testing Sentry Integration")
if __name__ == '__main__': if __name__ == "__main__":
app.run( app.run(host="0.0.0.0", port=getenv("PORT", 8080))
host="0.0.0.0",
port=getenv("PORT", 8080)
)

View File

@ -1,6 +1,6 @@
from sanic import Sanic from sanic import Sanic
app = Sanic(__name__) app = Sanic("Example")
app.static("/", "./static") app.static("/", "./static")

View File

@ -1,13 +1,14 @@
from sanic import Sanic from sanic import Sanic
from sanic import response as res from sanic import response as res
app = Sanic(__name__)
app = Sanic("Example")
@app.route("/") @app.route("/")
async def test(req): async def test(req):
return res.text("I\'m a teapot", status=418) return res.text("I'm a teapot", status=418)
if __name__ == '__main__': if __name__ == "__main__":
app.run(host="0.0.0.0", port=8000) app.run(host="0.0.0.0", port=8000)

View File

@ -5,7 +5,7 @@ from sanic.exceptions import ServerError
from sanic.log import logger as log from sanic.log import logger as log
app = Sanic(__name__) app = Sanic("Example")
@app.route("/") @app.route("/")

View File

@ -4,7 +4,7 @@ import socket
from sanic import Sanic, response from sanic import Sanic, response
app = Sanic(__name__) app = Sanic("Example")
@app.route("/test") @app.route("/test")

View File

@ -1,7 +1,7 @@
from sanic import Sanic, response from sanic import Sanic, response
app = Sanic(__name__) app = Sanic("Example")
@app.route("/") @app.route("/")

View File

@ -8,7 +8,7 @@ from sanic.blueprints import Blueprint
# curl -H "Host: bp.example.com" localhost:8000/question # curl -H "Host: bp.example.com" localhost:8000/question
# curl -H "Host: bp.example.com" localhost:8000/answer # curl -H "Host: bp.example.com" localhost:8000/answer
app = Sanic(__name__) app = Sanic("Example")
bp = Blueprint("bp", host="bp.example.com") bp = Blueprint("bp", host="bp.example.com")

View File

@ -2,7 +2,7 @@ from sanic import Sanic
from sanic.response import redirect from sanic.response import redirect
app = Sanic(__name__) app = Sanic("Example")
app.static("index.html", "websocket.html") app.static("index.html", "websocket.html")

View File

@ -1 +1 @@
__version__ = "21.12.0dev" __version__ = "21.12.0"

View File

@ -44,7 +44,7 @@ from typing import (
Union, Union,
) )
from urllib.parse import urlencode, urlunparse from urllib.parse import urlencode, urlunparse
from warnings import filterwarnings, warn from warnings import filterwarnings
from sanic_routing.exceptions import ( # type: ignore from sanic_routing.exceptions import ( # type: ignore
FinalizationError, FinalizationError,
@ -57,7 +57,7 @@ from sanic.application.logo import get_logo
from sanic.application.motd import MOTD from sanic.application.motd import MOTD
from sanic.application.state import ApplicationState, Mode from sanic.application.state import ApplicationState, Mode
from sanic.asgi import ASGIApp from sanic.asgi import ASGIApp
from sanic.base import BaseSanic from sanic.base.root import BaseSanic
from sanic.blueprint_group import BlueprintGroup from sanic.blueprint_group import BlueprintGroup
from sanic.blueprints import Blueprint from sanic.blueprints import Blueprint
from sanic.compat import OS_IS_WINDOWS, enable_windows_color_support from sanic.compat import OS_IS_WINDOWS, enable_windows_color_support
@ -71,7 +71,13 @@ from sanic.exceptions import (
from sanic.handlers import ErrorHandler from sanic.handlers import ErrorHandler
from sanic.helpers import _default from sanic.helpers import _default
from sanic.http import Stage from sanic.http import Stage
from sanic.log import LOGGING_CONFIG_DEFAULTS, Colors, error_logger, logger from sanic.log import (
LOGGING_CONFIG_DEFAULTS,
Colors,
deprecation,
error_logger,
logger,
)
from sanic.mixins.listeners import ListenerEvent from sanic.mixins.listeners import ListenerEvent
from sanic.models.futures import ( from sanic.models.futures import (
FutureException, FutureException,
@ -85,7 +91,7 @@ from sanic.models.futures import (
from sanic.models.handler_types import ListenerType, MiddlewareType from sanic.models.handler_types import ListenerType, MiddlewareType
from sanic.models.handler_types import Sanic as SanicVar from sanic.models.handler_types import Sanic as SanicVar
from sanic.request import Request from sanic.request import Request
from sanic.response import BaseHTTPResponse, HTTPResponse from sanic.response import BaseHTTPResponse, HTTPResponse, ResponseStream
from sanic.router import Router from sanic.router import Router
from sanic.server import AsyncioServer, HttpProtocol from sanic.server import AsyncioServer, HttpProtocol
from sanic.server import Signal as ServerSignal from sanic.server import Signal as ServerSignal
@ -114,8 +120,7 @@ class Sanic(BaseSanic, metaclass=TouchUpMeta):
"_run_response_middleware", "_run_response_middleware",
"_run_request_middleware", "_run_request_middleware",
) )
__fake_slots__ = ( __slots__ = (
"_app_registry",
"_asgi_app", "_asgi_app",
"_asgi_client", "_asgi_client",
"_blueprint_order", "_blueprint_order",
@ -131,19 +136,12 @@ class Sanic(BaseSanic, metaclass=TouchUpMeta):
"_task_registry", "_task_registry",
"_test_client", "_test_client",
"_test_manager", "_test_manager",
"_uvloop_setting", # TODO: Remove in v22.6
"asgi",
"auto_reload",
"auto_reload",
"blueprints", "blueprints",
"config", "config",
"configure_logging", "configure_logging",
"ctx", "ctx",
"debug",
"error_handler", "error_handler",
"go_fast", "go_fast",
"is_running",
"is_stopping",
"listeners", "listeners",
"name", "name",
"named_request_middleware", "named_request_middleware",
@ -155,13 +153,12 @@ class Sanic(BaseSanic, metaclass=TouchUpMeta):
"signal_router", "signal_router",
"sock", "sock",
"strict_slashes", "strict_slashes",
"test_mode",
"websocket_enabled", "websocket_enabled",
"websocket_tasks", "websocket_tasks",
) )
_app_registry: Dict[str, "Sanic"] = {} _app_registry: Dict[str, "Sanic"] = {}
_uvloop_setting = None _uvloop_setting = None # TODO: Remove in v22.6
test_mode = False test_mode = False
def __init__( def __init__(
@ -172,7 +169,6 @@ class Sanic(BaseSanic, metaclass=TouchUpMeta):
router: Optional[Router] = None, router: Optional[Router] = None,
signal_router: Optional[SignalRouter] = None, signal_router: Optional[SignalRouter] = None,
error_handler: Optional[ErrorHandler] = None, error_handler: Optional[ErrorHandler] = None,
load_env: Union[bool, str] = True,
env_prefix: Optional[str] = SANIC_PREFIX, env_prefix: Optional[str] = SANIC_PREFIX,
request_class: Optional[Type[Request]] = None, request_class: Optional[Type[Request]] = None,
strict_slashes: bool = False, strict_slashes: bool = False,
@ -188,17 +184,16 @@ class Sanic(BaseSanic, metaclass=TouchUpMeta):
dict_config = log_config or LOGGING_CONFIG_DEFAULTS dict_config = log_config or LOGGING_CONFIG_DEFAULTS
logging.config.dictConfig(dict_config) # type: ignore logging.config.dictConfig(dict_config) # type: ignore
if config and (load_env is not True or env_prefix != SANIC_PREFIX): if config and env_prefix != SANIC_PREFIX:
raise SanicException( raise SanicException(
"When instantiating Sanic with config, you cannot also pass " "When instantiating Sanic with config, you cannot also pass "
"load_env or env_prefix" "env_prefix"
) )
self.config: Config = config or Config( # First setup config
load_env=load_env, self.config: Config = config or Config(env_prefix=env_prefix)
env_prefix=env_prefix,
)
# Then we can do the rest
self._asgi_client: Any = None self._asgi_client: Any = None
self._blueprint_order: List[Blueprint] = [] self._blueprint_order: List[Blueprint] = []
self._delayed_tasks: List[str] = [] self._delayed_tasks: List[str] = []
@ -231,6 +226,12 @@ class Sanic(BaseSanic, metaclass=TouchUpMeta):
self.go_fast = self.run self.go_fast = self.run
if register is not None: if register is not None:
deprecation(
"The register argument is deprecated and will stop working "
"in v22.6. After v22.6 all apps will be added to the Sanic "
"app registry.",
22.6,
)
self.config.REGISTER = register self.config.REGISTER = register
if self.config.REGISTER: if self.config.REGISTER:
self.__class__.register_app(self) self.__class__.register_app(self)
@ -740,7 +741,7 @@ class Sanic(BaseSanic, metaclass=TouchUpMeta):
exception, request.name if request else None exception, request.name if request else None
) )
if handler: if handler:
warn( deprecation(
"An error occurred while handling the request after at " "An error occurred while handling the request after at "
"least some part of the response was sent to the client. " "least some part of the response was sent to the client. "
"Therefore, the response from your custom exception " "Therefore, the response from your custom exception "
@ -755,7 +756,7 @@ class Sanic(BaseSanic, metaclass=TouchUpMeta):
"For further information, please see the docs: " "For further information, please see the docs: "
"https://sanicframework.org/en/guide/advanced/" "https://sanicframework.org/en/guide/advanced/"
"signals.html", "signals.html",
DeprecationWarning, 22.6,
) )
try: try:
response = self.error_handler.response(request, exception) response = self.error_handler.response(request, exception)
@ -808,6 +809,9 @@ class Sanic(BaseSanic, metaclass=TouchUpMeta):
else: else:
if request.stream: if request.stream:
response = request.stream.response response = request.stream.response
# Marked for cleanup and DRY with handle_request/handle_exception
# when ResponseStream is no longer supporder
if isinstance(response, BaseHTTPResponse): if isinstance(response, BaseHTTPResponse):
await self.dispatch( await self.dispatch(
"http.lifecycle.response", "http.lifecycle.response",
@ -818,6 +822,17 @@ class Sanic(BaseSanic, metaclass=TouchUpMeta):
}, },
) )
await response.send(end_stream=True) await response.send(end_stream=True)
elif isinstance(response, ResponseStream):
resp = await response(request)
await self.dispatch(
"http.lifecycle.response",
inline=True,
context={
"request": request,
"response": resp,
},
)
await response.eof()
else: else:
raise ServerError( raise ServerError(
f"Invalid response type {response!r} (need HTTPResponse)" f"Invalid response type {response!r} (need HTTPResponse)"
@ -921,7 +936,8 @@ class Sanic(BaseSanic, metaclass=TouchUpMeta):
elif not hasattr(handler, "is_websocket"): elif not hasattr(handler, "is_websocket"):
response = request.stream.response # type: ignore response = request.stream.response # type: ignore
# Make sure that response is finished / run StreamingHTTP callback # Marked for cleanup and DRY with handle_request/handle_exception
# when ResponseStream is no longer supporder
if isinstance(response, BaseHTTPResponse): if isinstance(response, BaseHTTPResponse):
await self.dispatch( await self.dispatch(
"http.lifecycle.response", "http.lifecycle.response",
@ -932,6 +948,17 @@ class Sanic(BaseSanic, metaclass=TouchUpMeta):
}, },
) )
await response.send(end_stream=True) await response.send(end_stream=True)
elif isinstance(response, ResponseStream):
resp = await response(request)
await self.dispatch(
"http.lifecycle.response",
inline=True,
context={
"request": request,
"response": resp,
},
)
await response.eof()
else: else:
if not hasattr(handler, "is_websocket"): if not hasattr(handler, "is_websocket"):
raise ServerError( raise ServerError(

0
sanic/base/__init__.py Normal file
View File

6
sanic/base/meta.py Normal file
View File

@ -0,0 +1,6 @@
class SanicMeta(type):
@classmethod
def __prepare__(metaclass, name, bases, **kwds):
cls = super().__prepare__(metaclass, name, bases, **kwds)
cls["__slots__"] = ()
return cls

View File

@ -1,8 +1,8 @@
import re import re
from typing import Any, Tuple from typing import Any
from warnings import warn
from sanic.base.meta import SanicMeta
from sanic.exceptions import SanicException from sanic.exceptions import SanicException
from sanic.mixins.exceptions import ExceptionMixin from sanic.mixins.exceptions import ExceptionMixin
from sanic.mixins.listeners import ListenerMixin from sanic.mixins.listeners import ListenerMixin
@ -20,8 +20,9 @@ class BaseSanic(
ListenerMixin, ListenerMixin,
ExceptionMixin, ExceptionMixin,
SignalMixin, SignalMixin,
metaclass=SanicMeta,
): ):
__fake_slots__: Tuple[str, ...] __slots__ = ("name",)
def __init__(self, name: str = None, *args: Any, **kwargs: Any) -> None: def __init__(self, name: str = None, *args: Any, **kwargs: Any) -> None:
class_name = self.__class__.__name__ class_name = self.__class__.__name__
@ -33,11 +34,10 @@ class BaseSanic(
) )
if not VALID_NAME.match(name): if not VALID_NAME.match(name):
warn( raise SanicException(
f"{class_name} instance named '{name}' uses a format that is" f"{class_name} instance named '{name}' uses an invalid "
f"deprecated. Starting in version 21.12, {class_name} objects " "format. Names must begin with a character and may only "
"must be named only using alphanumeric characters, _, or -.", "contain alphanumeric characters, _, or -."
DeprecationWarning,
) )
self.name = name self.name = name
@ -52,15 +52,12 @@ class BaseSanic(
return f'{self.__class__.__name__}(name="{self.name}")' return f'{self.__class__.__name__}(name="{self.name}")'
def __setattr__(self, name: str, value: Any) -> None: def __setattr__(self, name: str, value: Any) -> None:
# This is a temporary compat layer so we can raise a warning until try:
# setting attributes on the app instance can be removed and deprecated
# with a proper implementation of __slots__
if name not in self.__fake_slots__:
warn(
f"Setting variables on {self.__class__.__name__} instances is "
"deprecated and will be removed in version 21.12. You should "
f"change your {self.__class__.__name__} instance to use "
f"instance.ctx.{name} instead.",
DeprecationWarning,
)
super().__setattr__(name, value) super().__setattr__(name, value)
except AttributeError as e:
raise AttributeError(
f"Setting variables on {self.__class__.__name__} instances is "
"not allowed. You should change your "
f"{self.__class__.__name__} instance to use "
f"instance.ctx.{name} instead.",
) from e

View File

@ -24,7 +24,7 @@ from typing import (
from sanic_routing.exceptions import NotFound # type: ignore from sanic_routing.exceptions import NotFound # type: ignore
from sanic_routing.route import Route # type: ignore from sanic_routing.route import Route # type: ignore
from sanic.base import BaseSanic from sanic.base.root import BaseSanic
from sanic.blueprint_group import BlueprintGroup from sanic.blueprint_group import BlueprintGroup
from sanic.exceptions import SanicException from sanic.exceptions import SanicException
from sanic.helpers import Default, _default from sanic.helpers import Default, _default
@ -85,7 +85,7 @@ class Blueprint(BaseSanic):
trailing */* trailing */*
""" """
__fake_slots__ = ( __slots__ = (
"_apps", "_apps",
"_future_routes", "_future_routes",
"_future_statics", "_future_statics",
@ -98,7 +98,6 @@ class Blueprint(BaseSanic):
"host", "host",
"listeners", "listeners",
"middlewares", "middlewares",
"name",
"routes", "routes",
"statics", "statics",
"strict_slashes", "strict_slashes",

View File

@ -4,12 +4,11 @@ from inspect import getmembers, isclass, isdatadescriptor
from os import environ from os import environ
from pathlib import Path from pathlib import Path
from typing import Any, Callable, Dict, Optional, Sequence, Union from typing import Any, Callable, Dict, Optional, Sequence, Union
from warnings import warn
from sanic.errorpages import DEFAULT_FORMAT, check_error_format from sanic.errorpages import DEFAULT_FORMAT, check_error_format
from sanic.helpers import Default, _default from sanic.helpers import Default, _default
from sanic.http import Http from sanic.http import Http
from sanic.log import error_logger from sanic.log import deprecation, error_logger
from sanic.utils import load_module_from_file_location, str_to_bool from sanic.utils import load_module_from_file_location, str_to_bool
@ -88,7 +87,6 @@ class Config(dict, metaclass=DescriptorMeta):
def __init__( def __init__(
self, self,
defaults: Dict[str, Union[str, bool, int, float, None]] = None, defaults: Dict[str, Union[str, bool, int, float, None]] = None,
load_env: Optional[Union[bool, str]] = True,
env_prefix: Optional[str] = SANIC_PREFIX, env_prefix: Optional[str] = SANIC_PREFIX,
keep_alive: Optional[bool] = None, keep_alive: Optional[bool] = None,
*, *,
@ -110,15 +108,6 @@ class Config(dict, metaclass=DescriptorMeta):
if env_prefix != SANIC_PREFIX: if env_prefix != SANIC_PREFIX:
if env_prefix: if env_prefix:
self.load_environment_vars(env_prefix) self.load_environment_vars(env_prefix)
elif load_env is not True:
if load_env:
self.load_environment_vars(prefix=load_env)
warn(
"Use of load_env is deprecated and will be removed in "
"21.12. Modify the configuration prefix by passing "
"env_prefix instead.",
DeprecationWarning,
)
else: else:
self.load_environment_vars(SANIC_PREFIX) self.load_environment_vars(SANIC_PREFIX)
@ -161,10 +150,10 @@ class Config(dict, metaclass=DescriptorMeta):
self._configure_header_size() self._configure_header_size()
elif attr == "LOGO": elif attr == "LOGO":
self._LOGO = value self._LOGO = value
warn( deprecation(
"Setting the config.LOGO is deprecated and will no longer " "Setting the config.LOGO is deprecated and will no longer "
"be supported starting in v22.6.", "be supported starting in v22.6.",
DeprecationWarning, 22.6,
) )
@property @property

View File

@ -244,25 +244,3 @@ class InvalidSignal(SanicException):
class WebsocketClosed(SanicException): class WebsocketClosed(SanicException):
quiet = True quiet = True
message = "Client has closed the websocket connection" message = "Client has closed the websocket connection"
def abort(status_code: int, message: Optional[Union[str, bytes]] = None):
"""
Raise an exception based on SanicException. Returns the HTTP response
message appropriate for the given status code, unless provided.
STATUS_CODES from sanic.helpers for the given status code.
:param status_code: The HTTP status code to return.
:param message: The HTTP response body. Defaults to the messages in
"""
import warnings
warnings.warn(
"sanic.exceptions.abort has been marked as deprecated, and will be "
"removed in release 21.12.\n To migrate your code, simply replace "
"abort(status_code, msg) with raise SanicException(msg, status_code), "
"or even better, raise an appropriate SanicException subclass."
)
raise SanicException(message=message, status_code=status_code)

View File

@ -2,7 +2,6 @@ from __future__ import annotations
from inspect import signature from inspect import signature
from typing import Dict, List, Optional, Tuple, Type, Union from typing import Dict, List, Optional, Tuple, Type, Union
from warnings import warn
from sanic.config import Config from sanic.config import Config
from sanic.errorpages import ( from sanic.errorpages import (
@ -18,7 +17,7 @@ from sanic.exceptions import (
SanicException, SanicException,
) )
from sanic.helpers import Default, _default from sanic.helpers import Default, _default
from sanic.log import error_logger from sanic.log import deprecation, error_logger
from sanic.models.handler_types import RouteHandler from sanic.models.handler_types import RouteHandler
from sanic.response import text from sanic.response import text
@ -71,12 +70,12 @@ class ErrorHandler:
@staticmethod @staticmethod
def _warn_fallback_deprecation(): def _warn_fallback_deprecation():
warn( deprecation(
"Setting the ErrorHandler fallback value directly is " "Setting the ErrorHandler fallback value directly is "
"deprecated and no longer supported. This feature will " "deprecated and no longer supported. This feature will "
"be removed in v22.6. Instead, use " "be removed in v22.6. Instead, use "
"app.config.FALLBACK_ERROR_FORMAT.", "app.config.FALLBACK_ERROR_FORMAT.",
DeprecationWarning, 22.6,
) )
@classmethod @classmethod
@ -100,19 +99,19 @@ class ErrorHandler:
config: Optional[Config] = None, config: Optional[Config] = None,
): ):
if fallback: if fallback:
warn( deprecation(
"Setting the ErrorHandler fallback value via finalize() " "Setting the ErrorHandler fallback value via finalize() "
"is deprecated and no longer supported. This feature will " "is deprecated and no longer supported. This feature will "
"be removed in v22.6. Instead, use " "be removed in v22.6. Instead, use "
"app.config.FALLBACK_ERROR_FORMAT.", "app.config.FALLBACK_ERROR_FORMAT.",
DeprecationWarning, 22.6,
) )
if config is None: if config is None:
warn( deprecation(
"Starting in v22.3, config will be a required argument " "Starting in v22.3, config will be a required argument "
"for ErrorHandler.finalize().", "for ErrorHandler.finalize().",
DeprecationWarning, 22.3,
) )
if fallback and fallback != DEFAULT_FORMAT: if fallback and fallback != DEFAULT_FORMAT:
@ -131,7 +130,7 @@ class ErrorHandler:
sig = signature(error_handler.lookup) sig = signature(error_handler.lookup)
if len(sig.parameters) == 1: if len(sig.parameters) == 1:
warn( deprecation(
"You are using a deprecated error handler. The lookup " "You are using a deprecated error handler. The lookup "
"method should accept two positional parameters: " "method should accept two positional parameters: "
"(exception, route_name: Optional[str]). " "(exception, route_name: Optional[str]). "
@ -139,7 +138,7 @@ class ErrorHandler:
"specific exceptions will not work properly. Beginning " "specific exceptions will not work properly. Beginning "
"in v22.3, the legacy style lookup method will not " "in v22.3, the legacy style lookup method will not "
"work at all.", "work at all.",
DeprecationWarning, 22.3,
) )
legacy_lookup = error_handler._legacy_lookup legacy_lookup = error_handler._legacy_lookup
error_handler._lookup = legacy_lookup # type: ignore error_handler._lookup = legacy_lookup # type: ignore

View File

@ -3,6 +3,7 @@ import sys
from enum import Enum from enum import Enum
from typing import Any, Dict from typing import Any, Dict
from warnings import warn
LOGGING_CONFIG_DEFAULTS: Dict[str, Any] = dict( LOGGING_CONFIG_DEFAULTS: Dict[str, Any] = dict(
@ -78,3 +79,11 @@ access_logger = logging.getLogger("sanic.access")
""" """
Logger used by Sanic for access logging Logger used by Sanic for access logging
""" """
def deprecation(message: str, version: float):
version_info = f"[DEPRECATION v{version}] "
if sys.stdout.isatty():
version_info = f"{Colors.RED}{version_info}"
message = f"{Colors.YELLOW}{message}{Colors.END}"
warn(version_info + message, DeprecationWarning)

View File

@ -1,9 +1,10 @@
from typing import Set from typing import Set
from sanic.base.meta import SanicMeta
from sanic.models.futures import FutureException from sanic.models.futures import FutureException
class ExceptionMixin: class ExceptionMixin(metaclass=SanicMeta):
def __init__(self, *args, **kwargs) -> None: def __init__(self, *args, **kwargs) -> None:
self._future_exceptions: Set[FutureException] = set() self._future_exceptions: Set[FutureException] = set()

View File

@ -2,6 +2,7 @@ from enum import Enum, auto
from functools import partial from functools import partial
from typing import List, Optional, Union from typing import List, Optional, Union
from sanic.base.meta import SanicMeta
from sanic.models.futures import FutureListener from sanic.models.futures import FutureListener
from sanic.models.handler_types import ListenerType, Sanic from sanic.models.handler_types import ListenerType, Sanic
@ -18,7 +19,7 @@ class ListenerEvent(str, Enum):
MAIN_PROCESS_STOP = auto() MAIN_PROCESS_STOP = auto()
class ListenerMixin: class ListenerMixin(metaclass=SanicMeta):
def __init__(self, *args, **kwargs) -> None: def __init__(self, *args, **kwargs) -> None:
self._future_listeners: List[FutureListener] = [] self._future_listeners: List[FutureListener] = []

View File

@ -1,10 +1,11 @@
from functools import partial from functools import partial
from typing import List from typing import List
from sanic.base.meta import SanicMeta
from sanic.models.futures import FutureMiddleware from sanic.models.futures import FutureMiddleware
class MiddlewareMixin: class MiddlewareMixin(metaclass=SanicMeta):
def __init__(self, *args, **kwargs) -> None: def __init__(self, *args, **kwargs) -> None:
self._future_middleware: List[FutureMiddleware] = [] self._future_middleware: List[FutureMiddleware] = []

View File

@ -1,4 +1,5 @@
from ast import NodeVisitor, Return, parse from ast import NodeVisitor, Return, parse
from contextlib import suppress
from functools import partial, wraps from functools import partial, wraps
from inspect import getsource, signature from inspect import getsource, signature
from mimetypes import guess_type from mimetypes import guess_type
@ -12,6 +13,7 @@ from urllib.parse import unquote
from sanic_routing.route import Route # type: ignore from sanic_routing.route import Route # type: ignore
from sanic.base.meta import SanicMeta
from sanic.compat import stat_async from sanic.compat import stat_async
from sanic.constants import DEFAULT_HTTP_CONTENT_TYPE, HTTP_METHODS from sanic.constants import DEFAULT_HTTP_CONTENT_TYPE, HTTP_METHODS
from sanic.errorpages import RESPONSE_MAPPING from sanic.errorpages import RESPONSE_MAPPING
@ -22,12 +24,11 @@ from sanic.exceptions import (
InvalidUsage, InvalidUsage,
) )
from sanic.handlers import ContentRangeHandler from sanic.handlers import ContentRangeHandler
from sanic.log import error_logger from sanic.log import deprecation, error_logger
from sanic.models.futures import FutureRoute, FutureStatic from sanic.models.futures import FutureRoute, FutureStatic
from sanic.models.handler_types import RouteHandler from sanic.models.handler_types import RouteHandler
from sanic.response import HTTPResponse, file, file_stream from sanic.response import HTTPResponse, file, file_stream
from sanic.types import HashableDict from sanic.types import HashableDict
from sanic.views import CompositionView
RouteWrapper = Callable[ RouteWrapper = Callable[
@ -43,7 +44,7 @@ RESTRICTED_ROUTE_CONTEXT = (
) )
class RouteMixin: class RouteMixin(metaclass=SanicMeta):
name: str name: str
def __init__(self, *args, **kwargs) -> None: def __init__(self, *args, **kwargs) -> None:
@ -253,14 +254,6 @@ class RouteMixin:
if hasattr(_handler, "is_stream"): if hasattr(_handler, "is_stream"):
stream = True stream = True
# handle composition view differently
if isinstance(handler, CompositionView):
methods = handler.handlers.keys()
for _handler in handler.handlers.values():
if hasattr(_handler, "is_stream"):
stream = True
break
if strict_slashes is None: if strict_slashes is None:
strict_slashes = self.strict_slashes strict_slashes = self.strict_slashes
@ -982,19 +975,16 @@ class RouteMixin:
return route return route
def _determine_error_format(self, handler) -> Optional[str]: def _determine_error_format(self, handler) -> str:
if not isinstance(handler, CompositionView): with suppress(OSError, TypeError):
try:
src = dedent(getsource(handler)) src = dedent(getsource(handler))
tree = parse(src) tree = parse(src)
http_response_types = self._get_response_types(tree) http_response_types = self._get_response_types(tree)
if len(http_response_types) == 1: if len(http_response_types) == 1:
return next(iter(http_response_types)) return next(iter(http_response_types))
except (OSError, TypeError):
...
return None return ""
def _get_response_types(self, node): def _get_response_types(self, node):
types = set() types = set()
@ -1003,7 +993,18 @@ class RouteMixin:
def visit_Return(self, node: Return) -> Any: def visit_Return(self, node: Return) -> Any:
nonlocal types nonlocal types
try: with suppress(AttributeError):
if node.value.func.id == "stream": # type: ignore
deprecation(
"The sanic.response.stream method has been "
"deprecated and will be removed in v22.6. Please "
"upgrade your application to use the new style "
"streaming pattern. See "
"https://sanicframework.org/en/guide/advanced/"
"streaming.html#response-streaming for more "
"information.",
22.6,
)
checks = [node.value.func.id] # type: ignore checks = [node.value.func.id] # type: ignore
if node.value.keywords: # type: ignore if node.value.keywords: # type: ignore
checks += [ checks += [
@ -1015,8 +1016,6 @@ class RouteMixin:
for check in checks: for check in checks:
if check in RESPONSE_MAPPING: if check in RESPONSE_MAPPING:
types.add(RESPONSE_MAPPING[check]) types.add(RESPONSE_MAPPING[check])
except AttributeError:
...
HttpResponseVisitor().visit(node) HttpResponseVisitor().visit(node)

View File

@ -1,13 +1,14 @@
from enum import Enum from enum import Enum
from typing import Any, Callable, Dict, Optional, Set, Union from typing import Any, Callable, Dict, Optional, Set, Union
from sanic.base.meta import SanicMeta
from sanic.models.futures import FutureSignal from sanic.models.futures import FutureSignal
from sanic.models.handler_types import SignalHandler from sanic.models.handler_types import SignalHandler
from sanic.signals import Signal from sanic.signals import Signal
from sanic.types import HashableDict from sanic.types import HashableDict
class SignalMixin: class SignalMixin(metaclass=SanicMeta):
def __init__(self, *args, **kwargs) -> None: def __init__(self, *args, **kwargs) -> None:
self._future_signals: Set[FutureSignal] = set() self._future_signals: Set[FutureSignal] = set()

View File

@ -1,3 +1,5 @@
from __future__ import annotations
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
@ -12,10 +14,10 @@ from typing import (
Iterator, Iterator,
Optional, Optional,
Tuple, Tuple,
TypeVar,
Union, Union,
) )
from urllib.parse import quote_plus from urllib.parse import quote_plus
from warnings import warn
from sanic.compat import Header, open_async from sanic.compat import Header, open_async
from sanic.constants import DEFAULT_HTTP_CONTENT_TYPE from sanic.constants import DEFAULT_HTTP_CONTENT_TYPE
@ -28,6 +30,10 @@ from sanic.models.protocol_types import HTMLProtocol, Range
if TYPE_CHECKING: if TYPE_CHECKING:
from sanic.asgi import ASGIApp from sanic.asgi import ASGIApp
from sanic.request import Request
else:
Request = TypeVar("Request")
try: try:
from ujson import dumps as json_dumps from ujson import dumps as json_dumps
@ -136,95 +142,6 @@ class BaseHTTPResponse:
await self.stream.send(data, end_stream=end_stream) await self.stream.send(data, end_stream=end_stream)
StreamingFunction = Callable[[BaseHTTPResponse], Coroutine[Any, Any, None]]
class StreamingHTTPResponse(BaseHTTPResponse):
"""
Old style streaming response where you pass a streaming function:
.. code-block:: python
async def sample_streaming_fn(response):
await response.write("foo")
await asyncio.sleep(1)
await response.write("bar")
await asyncio.sleep(1)
@app.post("/")
async def test(request):
return stream(sample_streaming_fn)
.. warning::
**Deprecated** and set for removal in v21.12. You can now achieve the
same functionality without a callback.
.. code-block:: python
@app.post("/")
async def test(request):
response = await request.respond()
await response.send("foo", False)
await asyncio.sleep(1)
await response.send("bar", False)
await asyncio.sleep(1)
await response.send("", True)
return response
"""
__slots__ = (
"streaming_fn",
"status",
"content_type",
"headers",
"_cookies",
)
def __init__(
self,
streaming_fn: StreamingFunction,
status: int = 200,
headers: Optional[Union[Header, Dict[str, str]]] = None,
content_type: str = "text/plain; charset=utf-8",
ignore_deprecation_notice: bool = False,
):
if not ignore_deprecation_notice:
warn(
"Use of the StreamingHTTPResponse is deprecated in v21.6, and "
"will be removed in v21.12. Please upgrade your streaming "
"response implementation. You can learn more here: "
"https://sanicframework.org/en/guide/advanced/streaming.html"
"#response-streaming. If you use the builtin stream() or "
"file_stream() methods, this upgrade will be be done for you."
)
super().__init__()
self.content_type = content_type
self.streaming_fn = streaming_fn
self.status = status
self.headers = Header(headers or {})
self._cookies = None
async def write(self, data):
"""Writes a chunk of data to the streaming response.
:param data: str or bytes-ish data to be written.
"""
await super().send(self._encode_body(data))
async def send(self, *args, **kwargs):
if self.streaming_fn is not None:
await self.streaming_fn(self)
self.streaming_fn = None
await super().send(*args, **kwargs)
async def eof(self):
raise NotImplementedError
class HTTPResponse(BaseHTTPResponse): class HTTPResponse(BaseHTTPResponse):
""" """
HTTP response to be sent back to the client. HTTP response to be sent back to the client.
@ -419,6 +336,109 @@ async def file(
) )
def redirect(
to: str,
headers: Optional[Dict[str, str]] = None,
status: int = 302,
content_type: str = "text/html; charset=utf-8",
) -> HTTPResponse:
"""
Abort execution and cause a 302 redirect (by default) by setting a
Location header.
:param to: path or fully qualified URL to redirect to
:param headers: optional dict of headers to include in the new request
:param status: status code (int) of the new request, defaults to 302
:param content_type: the content type (string) of the response
"""
headers = headers or {}
# URL Quote the URL before redirecting
safe_to = quote_plus(to, safe=":/%#?&=@[]!$&'()*+,;")
# According to RFC 7231, a relative URI is now permitted.
headers["Location"] = safe_to
return HTTPResponse(
status=status, headers=headers, content_type=content_type
)
class ResponseStream:
"""
ResponseStream is a compat layer to bridge the gap after the deprecation
of StreamingHTTPResponse. In v22.6 it will be removed when:
- stream is removed
- file_stream is moved to new style streaming
- file and file_stream are combined into a single API
"""
__slots__ = (
"_cookies",
"content_type",
"headers",
"request",
"response",
"status",
"streaming_fn",
)
def __init__(
self,
streaming_fn: Callable[
[Union[BaseHTTPResponse, ResponseStream]],
Coroutine[Any, Any, None],
],
status: int = 200,
headers: Optional[Union[Header, Dict[str, str]]] = None,
content_type: Optional[str] = None,
):
self.streaming_fn = streaming_fn
self.status = status
self.headers = headers or Header()
self.content_type = content_type
self.request: Optional[Request] = None
self._cookies: Optional[CookieJar] = None
async def write(self, message: str):
await self.response.send(message)
async def stream(self) -> HTTPResponse:
if not self.request:
raise ServerError("Attempted response to unknown request")
self.response = await self.request.respond(
headers=self.headers,
status=self.status,
content_type=self.content_type,
)
await self.streaming_fn(self)
return self.response
async def eof(self) -> None:
await self.response.eof()
@property
def cookies(self) -> CookieJar:
if self._cookies is None:
self._cookies = CookieJar(self.headers)
return self._cookies
@property
def processed_headers(self):
return self.response.processed_headers
@property
def body(self):
return self.response.body
def __call__(self, request: Request) -> ResponseStream:
self.request = request
return self
def __await__(self):
return self.stream().__await__()
async def file_stream( async def file_stream(
location: Union[str, PurePath], location: Union[str, PurePath],
status: int = 200, status: int = 200,
@ -427,7 +447,7 @@ async def file_stream(
headers: Optional[Dict[str, str]] = None, headers: Optional[Dict[str, str]] = None,
filename: Optional[str] = None, filename: Optional[str] = None,
_range: Optional[Range] = None, _range: Optional[Range] = None,
) -> StreamingHTTPResponse: ) -> ResponseStream:
"""Return a streaming response object with file data. """Return a streaming response object with file data.
:param location: Location of file on system. :param location: Location of file on system.
@ -435,7 +455,6 @@ async def file_stream(
:param mime_type: Specific mime_type. :param mime_type: Specific mime_type.
:param headers: Custom Headers. :param headers: Custom Headers.
:param filename: Override filename. :param filename: Override filename.
:param chunked: Deprecated
:param _range: :param _range:
""" """
headers = headers or {} headers = headers or {}
@ -471,23 +490,24 @@ async def file_stream(
break break
await response.write(content) await response.write(content)
return StreamingHTTPResponse( return ResponseStream(
streaming_fn=_streaming_fn, streaming_fn=_streaming_fn,
status=status, status=status,
headers=headers, headers=headers,
content_type=mime_type, content_type=mime_type,
ignore_deprecation_notice=True,
) )
def stream( def stream(
streaming_fn: StreamingFunction, streaming_fn: Callable[
[Union[BaseHTTPResponse, ResponseStream]], Coroutine[Any, Any, None]
],
status: int = 200, status: int = 200,
headers: Optional[Dict[str, str]] = None, headers: Optional[Dict[str, str]] = None,
content_type: str = "text/plain; charset=utf-8", content_type: str = "text/plain; charset=utf-8",
): ) -> ResponseStream:
"""Accepts an coroutine `streaming_fn` which can be used to """Accepts a coroutine `streaming_fn` which can be used to
write chunks to a streaming response. Returns a `StreamingHTTPResponse`. write chunks to a streaming response. Returns a `ResponseStream`.
Example usage:: Example usage::
@ -501,42 +521,13 @@ def stream(
:param streaming_fn: A coroutine accepts a response and :param streaming_fn: A coroutine accepts a response and
writes content to that response. writes content to that response.
:param mime_type: Specific mime_type. :param status: HTTP status.
:param content_type: Specific content_type.
:param headers: Custom Headers. :param headers: Custom Headers.
:param chunked: Deprecated
""" """
return StreamingHTTPResponse( return ResponseStream(
streaming_fn, streaming_fn,
headers=headers, headers=headers,
content_type=content_type, content_type=content_type,
status=status, status=status,
ignore_deprecation_notice=True,
)
def redirect(
to: str,
headers: Optional[Dict[str, str]] = None,
status: int = 302,
content_type: str = "text/html; charset=utf-8",
) -> HTTPResponse:
"""
Abort execution and cause a 302 redirect (by default) by setting a
Location header.
:param to: path or fully qualified URL to redirect to
:param headers: optional dict of headers to include in the new request
:param status: status code (int) of the new request, defaults to 302
:param content_type: the content type (string) of the response
"""
headers = headers or {}
# URL Quote the URL before redirecting
safe_to = quote_plus(to, safe=":/%#?&=@[]!$&'()*+,;")
# According to RFC 7231, a relative URI is now permitted.
headers["Location"] = safe_to
return HTTPResponse(
status=status, headers=headers, content_type=content_type
) )

View File

@ -3,9 +3,9 @@ from __future__ import annotations
import asyncio import asyncio
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from warnings import warn
from sanic.exceptions import SanicException from sanic.exceptions import SanicException
from sanic.log import deprecation
if TYPE_CHECKING: if TYPE_CHECKING:
@ -37,10 +37,10 @@ class AsyncioServer:
@property @property
def init(self): def init(self):
warn( deprecation(
"AsyncioServer.init has been deprecated and will be removed " "AsyncioServer.init has been deprecated and will be removed "
"in v22.6. Use Sanic.state.is_started instead.", "in v22.6. Use Sanic.state.is_started instead.",
DeprecationWarning, 22.6,
) )
return self.app.state.is_started return self.app.state.is_started

View File

@ -1,12 +1,11 @@
from typing import TYPE_CHECKING, Optional, Sequence, cast from typing import TYPE_CHECKING, Optional, Sequence, cast
from warnings import warn
from websockets.connection import CLOSED, CLOSING, OPEN from websockets.connection import CLOSED, CLOSING, OPEN
from websockets.server import ServerConnection from websockets.server import ServerConnection
from websockets.typing import Subprotocol from websockets.typing import Subprotocol
from sanic.exceptions import ServerError from sanic.exceptions import ServerError
from sanic.log import error_logger from sanic.log import deprecation, error_logger
from sanic.server import HttpProtocol from sanic.server import HttpProtocol
from ..websockets.impl import WebsocketImplProtocol from ..websockets.impl import WebsocketImplProtocol
@ -17,6 +16,14 @@ if TYPE_CHECKING:
class WebSocketProtocol(HttpProtocol): class WebSocketProtocol(HttpProtocol):
__slots__ = (
"websocket",
"websocket_timeout",
"websocket_max_size",
"websocket_ping_interval",
"websocket_ping_timeout",
)
def __init__( def __init__(
self, self,
*args, *args,
@ -35,24 +42,24 @@ class WebSocketProtocol(HttpProtocol):
self.websocket_max_size = websocket_max_size self.websocket_max_size = websocket_max_size
if websocket_max_queue is not None and websocket_max_queue > 0: if websocket_max_queue is not None and websocket_max_queue > 0:
# TODO: Reminder remove this warning in v22.3 # TODO: Reminder remove this warning in v22.3
warn( deprecation(
"Websocket no longer uses queueing, so websocket_max_queue" "Websocket no longer uses queueing, so websocket_max_queue"
" is no longer required.", " is no longer required.",
DeprecationWarning, 22.3,
) )
if websocket_read_limit is not None and websocket_read_limit > 0: if websocket_read_limit is not None and websocket_read_limit > 0:
# TODO: Reminder remove this warning in v22.3 # TODO: Reminder remove this warning in v22.3
warn( deprecation(
"Websocket no longer uses read buffers, so " "Websocket no longer uses read buffers, so "
"websocket_read_limit is not required.", "websocket_read_limit is not required.",
DeprecationWarning, 22.3,
) )
if websocket_write_limit is not None and websocket_write_limit > 0: if websocket_write_limit is not None and websocket_write_limit > 0:
# TODO: Reminder remove this warning in v22.3 # TODO: Reminder remove this warning in v22.3
warn( deprecation(
"Websocket no longer uses write buffers, so " "Websocket no longer uses write buffers, so "
"websocket_write_limit is not required.", "websocket_write_limit is not required.",
DeprecationWarning, 22.3,
) )
self.websocket_ping_interval = websocket_ping_interval self.websocket_ping_interval = websocket_ping_interval
self.websocket_ping_timeout = websocket_ping_timeout self.websocket_ping_timeout = websocket_ping_timeout

View File

@ -1,9 +1,10 @@
from sanic.base.meta import SanicMeta
from sanic.exceptions import SanicException from sanic.exceptions import SanicException
from .service import TouchUp from .service import TouchUp
class TouchUpMeta(type): class TouchUpMeta(SanicMeta):
def __new__(cls, name, bases, attrs, **kwargs): def __new__(cls, name, bases, attrs, **kwargs):
gen_class = super().__new__(cls, name, bases, attrs, **kwargs) gen_class = super().__new__(cls, name, bases, attrs, **kwargs)

View File

@ -9,10 +9,7 @@ from typing import (
Optional, Optional,
Union, Union,
) )
from warnings import warn
from sanic.constants import HTTP_METHODS
from sanic.exceptions import InvalidUsage
from sanic.models.handler_types import RouteHandler from sanic.models.handler_types import RouteHandler
@ -136,48 +133,3 @@ class HTTPMethodView:
def stream(func): def stream(func):
func.is_stream = True func.is_stream = True
return func return func
class CompositionView:
"""Simple method-function mapped view for the sanic.
You can add handler functions to methods (get, post, put, patch, delete)
for every HTTP method you want to support.
For example:
.. code-block:: python
view = CompositionView()
view.add(['GET'], lambda request: text('I am get method'))
view.add(['POST', 'PUT'], lambda request: text('I am post/put method'))
If someone tries to use a non-implemented method, there will be a
405 response.
"""
def __init__(self):
self.handlers = {}
self.name = self.__class__.__name__
warn(
"CompositionView has been deprecated and will be removed in "
"v21.12. Please update your view to HTTPMethodView.",
DeprecationWarning,
)
def __name__(self):
return self.name
def add(self, methods, handler, stream=False):
if stream:
handler.is_stream = stream
for method in methods:
if method not in HTTP_METHODS:
raise InvalidUsage(f"{method} is not a valid HTTP method.")
if method in self.handlers:
raise InvalidUsage(f"Method {method} is already registered.")
self.handlers[method] = handler
def __call__(self, request, *args, **kwargs):
handler = self.handlers[request.method.upper()]
return handler(request, *args, **kwargs)

View File

@ -1,5 +1,4 @@
import json import json
import logging
from sanic import Sanic, text from sanic import Sanic, text
from sanic.log import LOGGING_CONFIG_DEFAULTS, logger from sanic.log import LOGGING_CONFIG_DEFAULTS, logger
@ -9,7 +8,7 @@ LOGGING_CONFIG = {**LOGGING_CONFIG_DEFAULTS}
LOGGING_CONFIG["formatters"]["generic"]["format"] = "%(message)s" LOGGING_CONFIG["formatters"]["generic"]["format"] = "%(message)s"
LOGGING_CONFIG["loggers"]["sanic.root"]["level"] = "DEBUG" LOGGING_CONFIG["loggers"]["sanic.root"]["level"] = "DEBUG"
app = Sanic(__name__, log_config=LOGGING_CONFIG) app = Sanic("FakeServer", log_config=LOGGING_CONFIG)
@app.get("/") @app.get("/")

View File

@ -8,14 +8,15 @@ from os import environ
from unittest.mock import Mock, patch from unittest.mock import Mock, patch
import pytest import pytest
import sanic import sanic
from sanic import Sanic from sanic import Sanic
from sanic.compat import OS_IS_WINDOWS, UVLOOP_INSTALLED from sanic.compat import OS_IS_WINDOWS
from sanic.config import Config from sanic.config import Config
from sanic.exceptions import SanicException from sanic.exceptions import SanicException
from sanic.response import text
from sanic.helpers import _default from sanic.helpers import _default
from sanic.response import text
@pytest.fixture(autouse=True) @pytest.fixture(autouse=True)
@ -392,6 +393,22 @@ def test_app_no_registry():
Sanic.get_app("no-register") Sanic.get_app("no-register")
def test_app_no_registry_deprecation_message():
with pytest.warns(DeprecationWarning) as records:
Sanic("no-register", register=False)
Sanic("yes-register", register=True)
message = (
"[DEPRECATION v22.6] The register argument is deprecated and will "
"stop working in v22.6. After v22.6 all apps will be added to the "
"Sanic app registry."
)
assert len(records) == 2
for record in records:
assert record.message.args[0] == message
def test_app_no_registry_env(): def test_app_no_registry_env():
environ["SANIC_REGISTER"] = "False" environ["SANIC_REGISTER"] = "False"
Sanic("no-register") Sanic("no-register")
@ -403,15 +420,12 @@ def test_app_no_registry_env():
def test_app_set_attribute_warning(app): def test_app_set_attribute_warning(app):
with pytest.warns(DeprecationWarning) as record: message = (
app.foo = 1 "Setting variables on Sanic instances is not allowed. You should "
"change your Sanic instance to use instance.ctx.foo instead."
assert len(record) == 1
assert record[0].message.args[0] == (
"Setting variables on Sanic instances is deprecated "
"and will be removed in version 21.12. You should change your "
"Sanic instance to use instance.ctx.foo instead."
) )
with pytest.raises(AttributeError, match=message):
app.foo = 1
def test_app_set_context(app): def test_app_set_context(app):
@ -433,15 +447,7 @@ def test_bad_custom_config():
SanicException, SanicException,
match=( match=(
"When instantiating Sanic with config, you cannot also pass " "When instantiating Sanic with config, you cannot also pass "
"load_env or env_prefix" "env_prefix"
),
):
Sanic("test", config=1, load_env=1)
with pytest.raises(
SanicException,
match=(
"When instantiating Sanic with config, you cannot also pass "
"load_env or env_prefix"
), ),
): ):
Sanic("test", config=1, env_prefix=1) Sanic("test", config=1, env_prefix=1)
@ -494,11 +500,7 @@ def test_uvloop_config(app, monkeypatch):
def test_uvloop_cannot_never_called_with_create_server(caplog, monkeypatch): def test_uvloop_cannot_never_called_with_create_server(caplog, monkeypatch):
apps = ( apps = (Sanic("default-uvloop"), Sanic("no-uvloop"), Sanic("yes-uvloop"))
Sanic("default-uvloop"),
Sanic("no-uvloop"),
Sanic("yes-uvloop")
)
apps[1].config.USE_UVLOOP = False apps[1].config.USE_UVLOOP = False
apps[2].config.USE_UVLOOP = True apps[2].config.USE_UVLOOP = True
@ -512,7 +514,7 @@ def test_uvloop_cannot_never_called_with_create_server(caplog, monkeypatch):
for app in apps: for app in apps:
srv_coro = app.create_server( srv_coro = app.create_server(
return_asyncio_server=True, return_asyncio_server=True,
asyncio_server_kwargs=dict(start_serving=False) asyncio_server_kwargs=dict(start_serving=False),
) )
loop.run_until_complete(srv_coro) loop.run_until_complete(srv_coro)
@ -547,7 +549,7 @@ def test_multiple_uvloop_configs_display_warning(caplog):
for app in (default_uvloop, no_uvloop, yes_uvloop): for app in (default_uvloop, no_uvloop, yes_uvloop):
srv_coro = app.create_server( srv_coro = app.create_server(
return_asyncio_server=True, return_asyncio_server=True,
asyncio_server_kwargs=dict(start_serving=False) asyncio_server_kwargs=dict(start_serving=False),
) )
srv = loop.run_until_complete(srv_coro) srv = loop.run_until_complete(srv_coro)
loop.run_until_complete(srv.startup()) loop.run_until_complete(srv.startup())

View File

@ -1,6 +1,7 @@
import pytest import pytest
from sanic import Blueprint, Sanic from sanic import Blueprint, Sanic
from sanic.exceptions import SanicException
@pytest.fixture @pytest.fixture
@ -79,24 +80,18 @@ def test_names_okay(name):
) )
def test_names_not_okay(name): def test_names_not_okay(name):
app_message = ( app_message = (
f"Sanic instance named '{name}' uses a format that isdeprecated. " f"Sanic instance named '{name}' uses an invalid format. Names must "
"Starting in version 21.12, Sanic objects must be named only using " "begin with a character and may only contain alphanumeric "
"alphanumeric characters, _, or -." "characters, _, or -."
) )
bp_message = ( bp_message = (
f"Blueprint instance named '{name}' uses a format that isdeprecated. " f"Blueprint instance named '{name}' uses an invalid format. Names "
"Starting in version 21.12, Blueprint objects must be named only using " "must begin with a character and may only contain alphanumeric "
"alphanumeric characters, _, or -." "characters, _, or -."
) )
with pytest.warns(DeprecationWarning) as app_e: with pytest.raises(SanicException, match=app_message):
app = Sanic(name) Sanic(name)
with pytest.warns(DeprecationWarning) as bp_e: with pytest.raises(SanicException, match=bp_message):
bp = Blueprint(name) Blueprint(name)
assert app.name == name
assert bp.name == name
assert app_e[0].message.args[0] == app_message
assert bp_e[0].message.args[0] == bp_message

View File

@ -15,7 +15,6 @@ from sanic.exceptions import (
) )
from sanic.request import Request from sanic.request import Request
from sanic.response import json, text from sanic.response import json, text
from sanic.views import CompositionView
# ------------------------------------------------------------ # # ------------------------------------------------------------ #
@ -833,7 +832,7 @@ def test_static_blueprint_name(static_file_directory, file_name):
@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()) # type: ignore
with open(current_file, "rb") as file: with open(current_file, "rb") as file:
file.read() file.read()
@ -862,31 +861,6 @@ def test_static_blueprintp_mw(app: Sanic, static_file_directory, file_name):
assert triggered is True assert triggered is True
def test_route_handler_add(app: Sanic):
view = CompositionView()
async def get_handler(request):
return json({"response": "OK"})
view.add(["GET"], get_handler, stream=False)
async def default_handler(request):
return text("OK")
bp = Blueprint(name="handler", url_prefix="/handler")
bp.add_route(default_handler, uri="/default/", strict_slashes=True)
bp.add_route(view, uri="/view", name="test")
app.blueprint(bp)
_, response = app.test_client.get("/handler/default/")
assert response.text == "OK"
_, response = app.test_client.get("/handler/view")
assert response.json["response"] == "OK"
def test_websocket_route(app: Sanic): def test_websocket_route(app: Sanic):
event = asyncio.Event() event = asyncio.Event()
@ -1079,15 +1053,12 @@ def test_blueprint_registered_multiple_apps():
def test_bp_set_attribute_warning(): def test_bp_set_attribute_warning():
bp = Blueprint("bp") bp = Blueprint("bp")
with pytest.warns(DeprecationWarning) as record: message = (
bp.foo = 1 "Setting variables on Blueprint instances is not allowed. You should "
"change your Blueprint instance to use instance.ctx.foo instead."
assert len(record) == 1
assert record[0].message.args[0] == (
"Setting variables on Blueprint instances is deprecated "
"and will be removed in version 21.12. You should change your "
"Blueprint instance to use instance.ctx.foo instead."
) )
with pytest.raises(AttributeError, match=message):
bp.foo = 1
def test_early_registration(app): def test_early_registration(app):

View File

@ -81,26 +81,6 @@ def test_auto_bool_env_prefix():
del environ["SANIC_TEST_ANSWER"] del environ["SANIC_TEST_ANSWER"]
def test_dont_load_env():
environ["SANIC_TEST_ANSWER"] = "42"
app = Sanic(name=__name__, load_env=False)
assert getattr(app.config, "TEST_ANSWER", None) is None
del environ["SANIC_TEST_ANSWER"]
@pytest.mark.parametrize("load_env", [None, False, "", "MYAPP_"])
def test_load_env_deprecation(load_env):
with pytest.warns(DeprecationWarning, match=r"21\.12"):
_ = Sanic(name=__name__, load_env=load_env)
def test_load_env_prefix():
environ["MYAPP_TEST_ANSWER"] = "42"
app = Sanic(name=__name__, load_env="MYAPP_")
assert app.config.TEST_ANSWER == 42
del environ["MYAPP_TEST_ANSWER"]
@pytest.mark.parametrize("env_prefix", [None, ""]) @pytest.mark.parametrize("env_prefix", [None, ""])
def test_empty_load_env_prefix(env_prefix): def test_empty_load_env_prefix(env_prefix):
environ["SANIC_TEST_ANSWER"] = "42" environ["SANIC_TEST_ANSWER"] = "42"
@ -109,20 +89,6 @@ def test_empty_load_env_prefix(env_prefix):
del environ["SANIC_TEST_ANSWER"] del environ["SANIC_TEST_ANSWER"]
def test_load_env_prefix_float_values():
environ["MYAPP_TEST_ROI"] = "2.3"
app = Sanic(name=__name__, load_env="MYAPP_")
assert app.config.TEST_ROI == 2.3
del environ["MYAPP_TEST_ROI"]
def test_load_env_prefix_string_value():
environ["MYAPP_TEST_TOKEN"] = "somerandomtesttoken"
app = Sanic(name=__name__, load_env="MYAPP_")
assert app.config.TEST_TOKEN == "somerandomtesttoken"
del environ["MYAPP_TEST_TOKEN"]
def test_env_prefix(): def test_env_prefix():
environ["MYAPP_TEST_ANSWER"] = "42" environ["MYAPP_TEST_ANSWER"] = "42"
app = Sanic(name=__name__, env_prefix="MYAPP_") app = Sanic(name=__name__, env_prefix="MYAPP_")

View File

@ -0,0 +1,9 @@
import pytest
from sanic.log import deprecation
def test_deprecation():
message = r"\[DEPRECATION v9\.9\] hello"
with pytest.warns(DeprecationWarning, match=message):
deprecation("hello", 9.9)

View File

@ -13,7 +13,6 @@ from sanic.exceptions import (
SanicException, SanicException,
ServerError, ServerError,
Unauthorized, Unauthorized,
abort,
) )
from sanic.response import text from sanic.response import text
@ -88,10 +87,6 @@ def exception_app():
def handler_500_error(request): def handler_500_error(request):
raise SanicException(status_code=500) raise SanicException(status_code=500)
@app.route("/old_abort")
def handler_old_abort_error(request):
abort(500)
@app.route("/abort/message") @app.route("/abort/message")
def handler_abort_message(request): def handler_abort_message(request):
raise SanicException(message="Custom Message", status_code=500) raise SanicException(message="Custom Message", status_code=500)
@ -239,11 +234,6 @@ def test_sanic_exception(exception_app):
assert response.status == 500 assert response.status == 500
assert "Custom Message" in response.text assert "Custom Message" in response.text
with warnings.catch_warnings(record=True) as w:
request, response = exception_app.test_client.get("/old_abort")
assert response.status == 500
assert len(w) == 1 and "deprecated" in w[0].message.args[0]
def test_custom_exception_default_message(exception_app): def test_custom_exception_default_message(exception_app):
class TeaError(SanicException): class TeaError(SanicException):
@ -262,7 +252,7 @@ def test_custom_exception_default_message(exception_app):
def test_exception_in_ws_logged(caplog): def test_exception_in_ws_logged(caplog):
app = Sanic(__file__) app = Sanic(__name__)
@app.websocket("/feed") @app.websocket("/feed")
async def feed(request, ws): async def feed(request, ws):

View File

@ -226,11 +226,12 @@ def test_single_arg_exception_handler_notice(
exception_handler_app.error_handler = CustomErrorHandler() exception_handler_app.error_handler = CustomErrorHandler()
message = ( message = (
"You are using a deprecated error handler. The lookup method should " "[DEPRECATION v22.3] You are using a deprecated error handler. The "
"accept two positional parameters: (exception, route_name: " "lookup method should accept two positional parameters: (exception, "
"Optional[str]). Until you upgrade your ErrorHandler.lookup, " "route_name: Optional[str]). Until you upgrade your "
"Blueprint specific exceptions will not work properly. Beginning in " "ErrorHandler.lookup, Blueprint specific exceptions will not work "
"v22.3, the legacy style lookup method will not work at all." "properly. Beginning in v22.3, the legacy style lookup method will "
"not work at all."
) )
with pytest.warns(DeprecationWarning) as record: with pytest.warns(DeprecationWarning) as record:
_, response = exception_handler_app.test_client.get("/1") _, response = exception_handler_app.test_client.get("/1")

View File

@ -45,7 +45,7 @@ def default_back_to_ujson():
def test_change_encoder(): def test_change_encoder():
Sanic("...", dumps=sdumps) Sanic("Test", dumps=sdumps)
assert BaseHTTPResponse._dumps == sdumps assert BaseHTTPResponse._dumps == sdumps
@ -53,7 +53,7 @@ def test_change_encoder_to_some_custom():
def my_custom_encoder(): def my_custom_encoder():
return "foo" return "foo"
Sanic("...", dumps=my_custom_encoder) Sanic("Test", dumps=my_custom_encoder)
assert BaseHTTPResponse._dumps == my_custom_encoder assert BaseHTTPResponse._dumps == my_custom_encoder
@ -68,7 +68,7 @@ def test_json_response_ujson(payload):
): ):
json(payload, dumps=sdumps) json(payload, dumps=sdumps)
Sanic("...", dumps=sdumps) Sanic("Test", dumps=sdumps)
with pytest.raises( with pytest.raises(
TypeError, match="Object of type Foo is not JSON serializable" TypeError, match="Object of type Foo is not JSON serializable"
): ):
@ -87,6 +87,6 @@ def test_json_response_json():
response = json(too_big_for_ujson, dumps=sdumps) response = json(too_big_for_ujson, dumps=sdumps)
assert sys.getsizeof(response.body) == 54 assert sys.getsizeof(response.body) == 54
Sanic("...", dumps=sdumps) Sanic("Test", dumps=sdumps)
response = json(too_big_for_ujson) response = json(too_big_for_ujson)
assert sys.getsizeof(response.body) == 54 assert sys.getsizeof(response.body) == 54

View File

@ -8,13 +8,13 @@ from sanic.response import stream, text
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_request_cancel_when_connection_lost(app): async def test_request_cancel_when_connection_lost(app):
app.still_serving_cancelled_request = False app.ctx.still_serving_cancelled_request = False
@app.get("/") @app.get("/")
async def handler(request): async def handler(request):
await asyncio.sleep(1.0) await asyncio.sleep(1.0)
# at this point client is already disconnected # at this point client is already disconnected
app.still_serving_cancelled_request = True app.ctx.still_serving_cancelled_request = True
return text("OK") return text("OK")
# schedule client call # schedule client call
@ -32,12 +32,12 @@ async def test_request_cancel_when_connection_lost(app):
# Wait for server and check if it's still serving the cancelled request # Wait for server and check if it's still serving the cancelled request
await asyncio.sleep(1.0) await asyncio.sleep(1.0)
assert app.still_serving_cancelled_request is False assert app.ctx.still_serving_cancelled_request is False
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_stream_request_cancel_when_conn_lost(app): async def test_stream_request_cancel_when_conn_lost(app):
app.still_serving_cancelled_request = False app.ctx.still_serving_cancelled_request = False
@app.post("/post/<id>", stream=True) @app.post("/post/<id>", stream=True)
async def post(request, id): async def post(request, id):
@ -52,7 +52,7 @@ async def test_stream_request_cancel_when_conn_lost(app):
await asyncio.sleep(1.0) await asyncio.sleep(1.0)
# at this point client is already disconnected # at this point client is already disconnected
app.still_serving_cancelled_request = True app.ctx.still_serving_cancelled_request = True
return stream(streaming) return stream(streaming)
@ -71,4 +71,4 @@ async def test_stream_request_cancel_when_conn_lost(app):
# Wait for server and check if it's still serving the cancelled request # Wait for server and check if it's still serving the cancelled request
await asyncio.sleep(1.0) await asyncio.sleep(1.0)
assert app.still_serving_cancelled_request is False assert app.ctx.still_serving_cancelled_request is False

View File

@ -68,11 +68,11 @@ def test_app_injection(app):
@app.listener("after_server_start") @app.listener("after_server_start")
async def inject_data(app, loop): async def inject_data(app, loop):
app.injected = expected app.ctx.injected = expected
@app.get("/") @app.get("/")
async def handler(request): async def handler(request):
return json({"injected": request.app.injected}) return json({"injected": request.app.ctx.injected})
request, response = app.test_client.get("/") request, response = app.test_client.get("/")

View File

@ -8,7 +8,7 @@ import pytest
from sanic import Sanic from sanic import Sanic
from sanic.blueprints import Blueprint from sanic.blueprints import Blueprint
from sanic.response import json, text from sanic.response import json, text
from sanic.views import CompositionView, HTTPMethodView from sanic.views import HTTPMethodView
from sanic.views import stream as stream_decorator from sanic.views import stream as stream_decorator
@ -423,33 +423,6 @@ def test_request_stream_blueprint(app):
assert response.text == data assert response.text == data
def test_request_stream_composition_view(app):
def get_handler(request):
return text("OK")
async def post_handler(request):
result = ""
while True:
body = await request.stream.read()
if body is None:
break
result += body.decode("utf-8")
return text(result)
view = CompositionView()
view.add(["GET"], get_handler)
view.add(["POST"], post_handler, stream=True)
app.add_route(view, "/composition_view")
request, response = app.test_client.get("/composition_view")
assert response.status == 200
assert response.text == "OK"
request, response = app.test_client.post("/composition_view", data=data)
assert response.status == 200
assert response.text == data
def test_request_stream(app): def test_request_stream(app):
"""test for complex application""" """test for complex application"""
bp = Blueprint("test_blueprint_request_stream") bp = Blueprint("test_blueprint_request_stream")
@ -510,14 +483,8 @@ def test_request_stream(app):
app.add_route(SimpleView.as_view(), "/method_view") app.add_route(SimpleView.as_view(), "/method_view")
view = CompositionView()
view.add(["GET"], get_handler)
view.add(["POST"], post_handler, stream=True)
app.blueprint(bp) app.blueprint(bp)
app.add_route(view, "/composition_view")
request, response = app.test_client.get("/method_view") request, response = app.test_client.get("/method_view")
assert response.status == 200 assert response.status == 200
assert response.text == "OK" assert response.text == "OK"
@ -526,14 +493,6 @@ def test_request_stream(app):
assert response.status == 200 assert response.status == 200
assert response.text == data assert response.text == data
request, response = app.test_client.get("/composition_view")
assert response.status == 200
assert response.text == "OK"
request, response = app.test_client.post("/composition_view", data=data)
assert response.status == 200
assert response.text == data
request, response = app.test_client.get("/get") request, response = app.test_client.get("/get")
assert response.status == 200 assert response.status == 200
assert response.text == "OK" assert response.text == "OK"

View File

@ -15,6 +15,8 @@ from aiofiles import os as async_os
from pytest import LogCaptureFixture from pytest import LogCaptureFixture
from sanic import Request, Sanic from sanic import Request, Sanic
from sanic.compat import Header
from sanic.cookies import CookieJar
from sanic.response import ( from sanic.response import (
HTTPResponse, HTTPResponse,
empty, empty,
@ -277,7 +279,7 @@ def test_non_chunked_streaming_returns_correct_content(
assert response.text == "foo,bar" assert response.text == "foo,bar"
def test_stream_response_with_cookies(app): def test_stream_response_with_cookies_legacy(app):
@app.route("/") @app.route("/")
async def test(request: Request): async def test(request: Request):
response = stream(sample_streaming_fn, content_type="text/csv") response = stream(sample_streaming_fn, content_type="text/csv")
@ -289,6 +291,25 @@ def test_stream_response_with_cookies(app):
assert response.cookies["test"] == "pass" assert response.cookies["test"] == "pass"
def test_stream_response_with_cookies(app):
@app.route("/")
async def test(request: Request):
headers = Header()
cookies = CookieJar(headers)
cookies["test"] = "modified"
cookies["test"] = "pass"
response = await request.respond(
content_type="text/csv", headers=headers
)
await response.send("foo,")
await asyncio.sleep(0.001)
await response.send("bar")
request, response = app.test_client.get("/")
assert response.cookies["test"] == "pass"
def test_stream_response_without_cookies(app): def test_stream_response_without_cookies(app):
@app.route("/") @app.route("/")
async def test(request: Request): async def test(request: Request):
@ -561,37 +582,37 @@ def test_multiple_responses(
message_in_records: Callable[[List[LogRecord], str], bool], message_in_records: Callable[[List[LogRecord], str], bool],
): ):
@app.route("/1") @app.route("/1")
async def handler(request: Request): async def handler1(request: Request):
response = await request.respond() response = await request.respond()
await response.send("foo") await response.send("foo")
response = await request.respond() response = await request.respond()
@app.route("/2") @app.route("/2")
async def handler(request: Request): async def handler2(request: Request):
response = await request.respond() response = await request.respond()
response = await request.respond() response = await request.respond()
await response.send("foo") await response.send("foo")
@app.get("/3") @app.get("/3")
async def handler(request: Request): async def handler3(request: Request):
response = await request.respond() response = await request.respond()
await response.send("foo,") await response.send("foo,")
response = await request.respond() response = await request.respond()
await response.send("bar") await response.send("bar")
@app.get("/4") @app.get("/4")
async def handler(request: Request): async def handler4(request: Request):
response = await request.respond(headers={"one": "one"}) response = await request.respond(headers={"one": "one"})
return json({"foo": "bar"}, headers={"one": "two"}) return json({"foo": "bar"}, headers={"one": "two"})
@app.get("/5") @app.get("/5")
async def handler(request: Request): async def handler5(request: Request):
response = await request.respond(headers={"one": "one"}) response = await request.respond(headers={"one": "one"})
await response.send("foo") await response.send("foo")
return json({"foo": "bar"}, headers={"one": "two"}) return json({"foo": "bar"}, headers={"one": "two"})
@app.get("/6") @app.get("/6")
async def handler(request: Request): async def handler6(request: Request):
response = await request.respond(headers={"one": "one"}) response = await request.respond(headers={"one": "one"})
await response.send("foo, ") await response.send("foo, ")
json_response = json({"foo": "bar"}, headers={"one": "two"}) json_response = json({"foo": "bar"}, headers={"one": "two"})

View File

@ -101,7 +101,7 @@ async def test_trigger_before_events_create_server(app):
@app.listener("before_server_start") @app.listener("before_server_start")
async def init_db(app, loop): async def init_db(app, loop):
app.db = MySanicDb() app.ctx.db = MySanicDb()
srv = await app.create_server( srv = await app.create_server(
debug=True, return_asyncio_server=True, port=PORT debug=True, return_asyncio_server=True, port=PORT
@ -109,8 +109,8 @@ async def test_trigger_before_events_create_server(app):
await srv.startup() await srv.startup()
await srv.before_start() await srv.before_start()
assert hasattr(app, "db") assert hasattr(app.ctx, "db")
assert isinstance(app.db, MySanicDb) assert isinstance(app.ctx.db, MySanicDb)
@pytest.mark.asyncio @pytest.mark.asyncio
@ -122,9 +122,9 @@ async def test_trigger_before_events_create_server_missing_event(app):
@app.listener @app.listener
async def init_db(app, loop): async def init_db(app, loop):
app.db = MySanicDb() app.ctx.db = MySanicDb()
assert not hasattr(app, "db") assert not hasattr(app.ctx, "db")
def test_create_server_trigger_events(app): def test_create_server_trigger_events(app):

View File

@ -76,8 +76,11 @@ async def test_purge_tasks(app: Sanic):
@pytest.mark.skipif(sys.version_info < (3, 8), reason="Not supported in 3.7") @pytest.mark.skipif(sys.version_info < (3, 8), reason="Not supported in 3.7")
def test_shutdown_tasks_on_app_stop(app: Sanic): def test_shutdown_tasks_on_app_stop():
app.shutdown_tasks = Mock() class TestSanic(Sanic):
shutdown_tasks = Mock()
app = TestSanic("Test")
@app.route("/") @app.route("/")
async def handler(_): async def handler(_):

View File

@ -15,12 +15,12 @@ from sanic import Sanic
from sanic.response import text from sanic.response import text
httpx_version = tuple(
map(int, httpx.__version__.strip(ascii_lowercase).split("."))
)
pytestmark = pytest.mark.skipif(os.name != "posix", reason="UNIX only") pytestmark = pytest.mark.skipif(os.name != "posix", reason="UNIX only")
SOCKPATH = "/tmp/sanictest.sock" SOCKPATH = "/tmp/sanictest.sock"
SOCKPATH2 = "/tmp/sanictest2.sock" SOCKPATH2 = "/tmp/sanictest2.sock"
httpx_version = tuple(
map(int, httpx.__version__.strip(ascii_lowercase).split("."))
)
@pytest.fixture(autouse=True) @pytest.fixture(autouse=True)
@ -222,7 +222,10 @@ async def test_zero_downtime():
processes = [spawn()] processes = [spawn()]
while not os.path.exists(SOCKPATH): while not os.path.exists(SOCKPATH):
if processes[0].poll() is not None: if processes[0].poll() is not None:
raise Exception("Worker did not start properly") raise Exception(
"Worker did not start properly. "
f"stderr: {processes[0].stderr.read()}"
)
await asyncio.sleep(0.0001) await asyncio.sleep(0.0001)
ino = os.stat(SOCKPATH).st_ino ino = os.stat(SOCKPATH).st_ino
task = asyncio.get_event_loop().create_task(client()) task = asyncio.get_event_loop().create_task(client())

View File

@ -19,7 +19,7 @@ def test_route(app, handler):
def test_bp(app, handler): def test_bp(app, handler):
bp = Blueprint(__file__, version=1) bp = Blueprint(__name__, version=1)
bp.route("/")(handler) bp.route("/")(handler)
app.blueprint(bp) app.blueprint(bp)
@ -28,7 +28,7 @@ def test_bp(app, handler):
def test_bp_use_route(app, handler): def test_bp_use_route(app, handler):
bp = Blueprint(__file__, version=1) bp = Blueprint(__name__, version=1)
bp.route("/", version=1.1)(handler) bp.route("/", version=1.1)(handler)
app.blueprint(bp) app.blueprint(bp)
@ -37,7 +37,7 @@ def test_bp_use_route(app, handler):
def test_bp_group(app, handler): def test_bp_group(app, handler):
bp = Blueprint(__file__) bp = Blueprint(__name__)
bp.route("/")(handler) bp.route("/")(handler)
group = Blueprint.group(bp, version=1) group = Blueprint.group(bp, version=1)
app.blueprint(group) app.blueprint(group)
@ -47,7 +47,7 @@ def test_bp_group(app, handler):
def test_bp_group_use_bp(app, handler): def test_bp_group_use_bp(app, handler):
bp = Blueprint(__file__, version=1.1) bp = Blueprint(__name__, version=1.1)
bp.route("/")(handler) bp.route("/")(handler)
group = Blueprint.group(bp, version=1) group = Blueprint.group(bp, version=1)
app.blueprint(group) app.blueprint(group)
@ -57,7 +57,7 @@ def test_bp_group_use_bp(app, handler):
def test_bp_group_use_registration(app, handler): def test_bp_group_use_registration(app, handler):
bp = Blueprint(__file__, version=1.1) bp = Blueprint(__name__, version=1.1)
bp.route("/")(handler) bp.route("/")(handler)
group = Blueprint.group(bp, version=1) group = Blueprint.group(bp, version=1)
app.blueprint(group, version=1.2) app.blueprint(group, version=1.2)
@ -67,7 +67,7 @@ def test_bp_group_use_registration(app, handler):
def test_bp_group_use_route(app, handler): def test_bp_group_use_route(app, handler):
bp = Blueprint(__file__, version=1.1) bp = Blueprint(__name__, version=1.1)
bp.route("/", version=1.3)(handler) bp.route("/", version=1.3)(handler)
group = Blueprint.group(bp, version=1) group = Blueprint.group(bp, version=1)
app.blueprint(group, version=1.2) app.blueprint(group, version=1.2)
@ -84,7 +84,7 @@ def test_version_prefix_route(app, handler):
def test_version_prefix_bp(app, handler): def test_version_prefix_bp(app, handler):
bp = Blueprint(__file__, version=1, version_prefix="/api/v") bp = Blueprint(__name__, version=1, version_prefix="/api/v")
bp.route("/")(handler) bp.route("/")(handler)
app.blueprint(bp) app.blueprint(bp)
@ -93,7 +93,7 @@ def test_version_prefix_bp(app, handler):
def test_version_prefix_bp_use_route(app, handler): def test_version_prefix_bp_use_route(app, handler):
bp = Blueprint(__file__, version=1, version_prefix="/ignore/v") bp = Blueprint(__name__, version=1, version_prefix="/ignore/v")
bp.route("/", version=1.1, version_prefix="/api/v")(handler) bp.route("/", version=1.1, version_prefix="/api/v")(handler)
app.blueprint(bp) app.blueprint(bp)
@ -102,7 +102,7 @@ def test_version_prefix_bp_use_route(app, handler):
def test_version_prefix_bp_group(app, handler): def test_version_prefix_bp_group(app, handler):
bp = Blueprint(__file__) bp = Blueprint(__name__)
bp.route("/")(handler) bp.route("/")(handler)
group = Blueprint.group(bp, version=1, version_prefix="/api/v") group = Blueprint.group(bp, version=1, version_prefix="/api/v")
app.blueprint(group) app.blueprint(group)
@ -112,7 +112,7 @@ def test_version_prefix_bp_group(app, handler):
def test_version_prefix_bp_group_use_bp(app, handler): def test_version_prefix_bp_group_use_bp(app, handler):
bp = Blueprint(__file__, version=1.1, version_prefix="/api/v") bp = Blueprint(__name__, version=1.1, version_prefix="/api/v")
bp.route("/")(handler) bp.route("/")(handler)
group = Blueprint.group(bp, version=1, version_prefix="/ignore/v") group = Blueprint.group(bp, version=1, version_prefix="/ignore/v")
app.blueprint(group) app.blueprint(group)
@ -122,7 +122,7 @@ def test_version_prefix_bp_group_use_bp(app, handler):
def test_version_prefix_bp_group_use_registration(app, handler): def test_version_prefix_bp_group_use_registration(app, handler):
bp = Blueprint(__file__, version=1.1, version_prefix="/alsoignore/v") bp = Blueprint(__name__, version=1.1, version_prefix="/alsoignore/v")
bp.route("/")(handler) bp.route("/")(handler)
group = Blueprint.group(bp, version=1, version_prefix="/ignore/v") group = Blueprint.group(bp, version=1, version_prefix="/ignore/v")
app.blueprint(group, version=1.2, version_prefix="/api/v") app.blueprint(group, version=1.2, version_prefix="/api/v")
@ -132,7 +132,7 @@ def test_version_prefix_bp_group_use_registration(app, handler):
def test_version_prefix_bp_group_use_route(app, handler): def test_version_prefix_bp_group_use_route(app, handler):
bp = Blueprint(__file__, version=1.1, version_prefix="/alsoignore/v") bp = Blueprint(__name__, version=1.1, version_prefix="/alsoignore/v")
bp.route("/", version=1.3, version_prefix="/api/v")(handler) bp.route("/", version=1.3, version_prefix="/api/v")(handler)
group = Blueprint.group(bp, version=1, version_prefix="/ignore/v") group = Blueprint.group(bp, version=1, version_prefix="/ignore/v")
app.blueprint(group, version=1.2, version_prefix="/stillignoring/v") app.blueprint(group, version=1.2, version_prefix="/stillignoring/v")

View File

@ -5,7 +5,7 @@ from sanic.constants import HTTP_METHODS
from sanic.exceptions import InvalidUsage from sanic.exceptions import InvalidUsage
from sanic.request import Request from sanic.request import Request
from sanic.response import HTTPResponse, text from sanic.response import HTTPResponse, text
from sanic.views import CompositionView, HTTPMethodView from sanic.views import HTTPMethodView
@pytest.mark.parametrize("method", HTTP_METHODS) @pytest.mark.parametrize("method", HTTP_METHODS)
@ -225,81 +225,3 @@ def test_with_decorator(app):
request, response = app.test_client.get("/") request, response = app.test_client.get("/")
assert response.text == "I am get method" assert response.text == "I am get method"
assert results[0] == 1 assert results[0] == 1
def test_composition_view_rejects_incorrect_methods():
def foo(request):
return text("Foo")
view = CompositionView()
with pytest.raises(InvalidUsage) as e:
view.add(["GET", "FOO"], foo)
assert str(e.value) == "FOO is not a valid HTTP method."
def test_composition_view_rejects_duplicate_methods():
def foo(request):
return text("Foo")
view = CompositionView()
with pytest.raises(InvalidUsage) as e:
view.add(["GET", "POST", "GET"], foo)
assert str(e.value) == "Method GET is already registered."
@pytest.mark.parametrize("method", HTTP_METHODS)
def test_composition_view_runs_methods_as_expected(app, method):
view = CompositionView()
def first(request):
return text("first method")
view.add(["GET", "POST", "PUT"], first)
view.add(["DELETE", "PATCH"], lambda x: text("second method"))
app.add_route(view, "/")
if method in ["GET", "POST", "PUT"]:
request, response = getattr(app.test_client, method.lower())("/")
assert response.status == 200
assert response.text == "first method"
response = view(request)
assert response.body.decode() == "first method"
if method in ["DELETE", "PATCH"]:
request, response = getattr(app.test_client, method.lower())("/")
assert response.text == "second method"
response = view(request)
assert response.body.decode() == "second method"
@pytest.mark.parametrize("method", HTTP_METHODS)
def test_composition_view_rejects_invalid_methods(app, method):
view = CompositionView()
view.add(["GET", "POST", "PUT"], lambda x: text("first method"))
app.add_route(view, "/")
if method in ["GET", "POST", "PUT"]:
request, response = getattr(app.test_client, method.lower())("/")
assert response.status == 200
assert response.text == "first method"
if method in ["DELETE", "PATCH"]:
request, response = getattr(app.test_client, method.lower())("/")
assert response.status == 405
def test_composition_view_deprecation():
message = (
"CompositionView has been deprecated and will be removed in v21.12. "
"Please update your view to HTTPMethodView."
)
with pytest.warns(DeprecationWarning, match=message):
CompositionView()