Compare commits

..

64 Commits

Author SHA1 Message Date
Adam Hopkins
97635111af Align setup.py 2021-02-16 09:33:48 +02:00
Adam Hopkins
7f3fe40cd4 Bump version 2021-02-16 08:50:35 +02:00
Adam Hopkins
ea34bcd849 Merge branch '20.12LTS' of github.com:sanic-org/sanic into 20.12LTS 2021-02-16 08:43:15 +02:00
Adam Hopkins
05f758583b Merge pull request #2029 from ashleysommer/tox_requires_2012
Fix tox requirements conflicts for 20.12LTS
2021-02-16 08:37:59 +02:00
Ashley Sommer
760c74a293 Merge remote-tracking branch 'origin/20.12LTS' into tox_requires_2012 2021-02-16 10:31:42 +10:00
Ashley Sommer
9def46beb8 Remove old chardet requirement, add our real multidict requirement 2021-02-16 10:03:40 +10:00
Ashley Sommer
04be8e95a5 Merge pull request #2026 from sanic-org/fix-uvloop-2012
Fix uvloop version for 20.12LTS
2021-02-16 09:23:57 +10:00
Adam Hopkins
78ced20fc7 fix uvloop version 2021-02-15 14:30:57 +02:00
Adam Hopkins
c3003413d3 Bump to version 20.12.1 2021-01-05 18:26:47 +02:00
Adam Hopkins
fe3fdc5d83 #1993 Disable registry (#1994)
* Bump to v20.12 (#1987)

* Bump to v20.12

* Update Changelog

* Add disable app registry

* squash

* Create FUNDING.yml (#1995)
2021-01-05 17:00:25 +02:00
Adam Hopkins
b66fb6f9e8 Merge branch 'master' into 20.12LTS 2020-12-28 23:21:46 +02:00
Adam Hopkins
bf6175fb20 Update Changelog 2020-12-28 23:18:19 +02:00
Tomasz Drożdż
7475897a03 Making static route more verbose if file not found (#1945)
Co-authored-by: Adam Hopkins <admhpkns@gmail.com>
2020-12-28 23:17:32 +02:00
Adam Hopkins
58ca887be4 Bump to v20.12 2020-12-28 23:11:29 +02:00
Adam Hopkins
449bc417a3 App registry (#1979)
* Add app level registry

* Add documentation for app registry

* Remove unused import

* Add force_create keyword to Sanic.get_app

* Add force_commit to docs
2020-12-28 22:47:31 +02:00
Adam Hopkins
262f89f2b6 Merge pull request #1984 from markgras/patch-1
Fix typo in routing.rst
2020-12-25 08:29:21 +02:00
Adam Hopkins
38337446cf Merge branch 'master' into patch-1 2020-12-25 06:48:42 +02:00
Adam Hopkins
ac1331ea4c Merge pull request #1983 from sinabeuro/dev-typo_doc_r0
Remove duplicate contents in document
2020-12-25 06:48:26 +02:00
Adam Hopkins
2b947e831f Merge branch 'master' into dev-typo_doc_r0 2020-12-25 01:35:22 +02:00
Adam Hopkins
112715eb80 Merge pull request #1986 from huge-success/tox-reqs
Update reqs to get tox running
2020-12-25 01:34:45 +02:00
Adam Hopkins
ea9cf365bc Turn off Appyveyor 3.9 2020-12-24 22:09:51 +02:00
Adam Hopkins
b9b3b4051a Update reqs to get tox running 2020-12-24 21:56:35 +02:00
Adam Hopkins
ecb6db29e6 Merge branch 'master' into dev-typo_doc_r0 2020-12-24 21:00:20 +02:00
Adam Hopkins
6515dde64b Merge pull request #1981 from huge-success/deprecation-cleanup
Cleanup and remove some deprecated code
2020-12-24 19:48:58 +02:00
Mark Grassi
01d2a2aa3c Fix typo in routing.rst
This fixes a small typo in the routing docs.
2020-12-20 16:08:51 -05:00
sinabeuro
39e12accb8 Remove duplicate contents in document
Since the contents of line 61 and line 75 of the 'testing' document are
duplicated, the content of line 61 is removed for context.

Signed-off-by: sinabeuro <ican312@hanmail.net>
2020-12-18 12:25:24 +09:00
Adam Hopkins
39fe6ea5b1 Cleanup and remove some deprecated code 2020-12-14 09:23:13 +02:00
Adam Hopkins
fc4b7df088 Merge pull request #1961 from huge-success/py39
Update testing for Python 3.9
2020-12-10 09:25:12 +02:00
Adam Hopkins
35f28f7a64 Merge branch 'master' into py39 2020-12-09 11:52:49 +02:00
Adam Hopkins
614be40438 Name endpoints at startup (#1972)
* Name endpoints at startup

* Beautify

* Fix reformatting
2020-11-29 23:26:12 +02:00
Adam Hopkins
bde0428d0c Update README.rst (#1973)
Change `.org` to `.com` for transition in Travis.
2020-11-23 02:02:33 +02:00
Trevor Bekolay
63567c2ae4 Add py.typed file (#1970) 2020-11-19 11:18:25 +02:00
Ashley Sommer
ec10f337b6 Merge pull request #1969 from all2ham/remove-upper-bound-multidict-pin
loosen pin on multidict, add higher upper bound to multidict requirement
2020-11-18 15:25:18 +10:00
allandialpad
d0f0e73e96 remove upper bound for multidict 2020-11-17 11:18:18 -05:00
7
b4fe2c8a6b bump up aiofile version constraint (#1967) 2020-11-06 08:32:04 +02:00
Adam Hopkins
33da0771d1 Merge pull request #1965 from ashleysommer/asgs_chunk_length
Fix Chunked Transport-Encoding in ASGI streaming response
2020-11-05 09:02:18 +02:00
Adam Hopkins
75994cd915 Fixes for linting and type hints 2020-11-05 08:49:55 +02:00
Ashley Sommer
c0839afdde Fix Chunked Transport-Encoding in ASGI streaming response
In ASGI-mode, don't do sanic-side response chunk encoding, leave that to the ASGI-response-transport
Don't set content-length when using chunked-encoding in ASGI mode, this is incompatible with ASGI Chunked Transport-Encoding.
2020-11-05 15:27:01 +10:00
Ashley Sommer
5961da3f57 Merge pull request #1960 from huge-success/release-notes-19.12.3-20.9.1
Update changelog for 19.12.3 and 20.9.1
2020-10-26 11:43:53 +10:00
Ashley Sommer
41f1809351 Merge branch 'master' into release-notes-19.12.3-20.9.1 2020-10-26 09:58:19 +10:00
Ashley Sommer
5fbdcb62e4 Merge pull request #1962 from huge-success/cli-upgrade
Sanic CLI upgrade
2020-10-26 08:31:45 +10:00
Ashley Sommer
677b83e9f8 Merge branch 'master' into release-notes-19.12.3-20.9.1 2020-10-26 08:21:44 +10:00
Adam Hopkins
6a5c8becac Merge branch 'master' into cli-upgrade 2020-10-25 22:46:37 +02:00
Adam Hopkins
fd23b99d60 Merge pull request #1951 from tomaszdrozdz/Improving-documentation
Improving documentation.
2020-10-25 22:45:37 +02:00
Adam Hopkins
634b586df3 Merge branch 'master' into Improving-documentation 2020-10-25 21:32:54 +02:00
Ashley Sommer
4ca3e98082 Add pytest-dependency requirement to tests_require list in setup.py (#1955)
Co-authored-by: Adam Hopkins <admhpkns@gmail.com>
2020-10-25 21:31:34 +02:00
Adam Hopkins
d18a776964 squash 2020-10-25 21:22:19 +02:00
Adam Hopkins
d6b4d7d265 Add bionic in travis and change path in appveyor 2020-10-25 21:01:31 +02:00
Adam Hopkins
33ee4c21b3 Add BASE_LOGO to sanic cli 2020-10-25 20:45:06 +02:00
Adam Hopkins
a026cd7195 add --access-logs flag to sanic cli 2020-10-25 20:36:22 +02:00
Adam Hopkins
7b1bce8d90 Add some help messages and a user friendly cli experience 2020-10-25 20:21:09 +02:00
Adam Hopkins
217a7c5161 Small changes to sanic-cli to make it more user friendly 2020-10-25 20:09:42 +02:00
Adam Hopkins
2949e3422d Add 3.9 to appveyor 2020-10-25 15:37:48 +02:00
Adam Hopkins
16ea99b0c0 Update testing for Python 3.9 2020-10-25 15:21:48 +02:00
Adam Hopkins
19b84ce9f0 Update changelog for 19.12.3 and 20.9.1 2020-10-25 15:11:39 +02:00
Adam Hopkins
e5aed4c067 Ignore writing headers when in ASGI mode (#1957)
* Ignore writing headers when in ASGI mode for streaming responses

* Move asgi set on streaming until after response type check

* Adds multidict==5.0.0 to pass tests

* Bump version to 20.9.1
2020-10-25 15:01:53 +02:00
Ashley Sommer
9e048bc0c3 Merge pull request #1956 from huge-success/fix-load-module-test
Fix load module test
2020-10-25 22:09:24 +10:00
Adam Hopkins
5d7b0735ce Merge branch 'master' into fix-load-module-test 2020-10-25 08:27:25 +02:00
Adam Hopkins
12521cd5b4 Merge branch 'master' into Improving-documentation 2020-10-25 00:02:32 +03:00
Adam Hopkins
7dbd3eb5e8 Update multidict version 2020-10-24 23:49:55 +03:00
Adam Hopkins
96364aacc0 squash 2020-10-24 23:42:38 +03:00
Adam Hopkins
fc18f86964 Resolve broken test in appveyor 2020-10-24 23:03:25 +03:00
Ashley Sommer
fb3d368a78 Add ability for app.static() to return the routes it created. (#1954)
This allows blueprint registration to add the bp's static routes to its list of own routes. So now blueprint middlewares will apply to a blueprint's static file routes.
Fixes #1953
2020-10-24 22:57:02 +03:00
tomaszdrozdz
f41435fae3 Improving documentation. 2020-10-19 10:12:20 +02:00
31 changed files with 591 additions and 270 deletions

View File

@@ -17,6 +17,12 @@ environment:
PYTHON_VERSION: "3.8.x"
PYTHON_ARCH: "64"
# - TOXENV: py39-no-ext
# PYTHON: "C:\\Python39-x64\\python"
# PYTHONPATH: "C:\\Python39-x64"
# PYTHON_VERSION: "3.9.x"
# PYTHON_ARCH: "64"
init: SET "PATH=%PYTHON%;%PYTHON%\\Scripts;%PATH%"
install:

12
.github/FUNDING.yml vendored Normal file
View File

@@ -0,0 +1,12 @@
# These are supported funding model platforms
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
patreon: # Replace with a single Patreon username
open_collective: sanic-org # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
otechie: # Replace with a single Otechie username
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']

View File

@@ -31,6 +31,16 @@ matrix:
dist: xenial
sudo: true
name: "Python 3.8 without Extensions"
- env: TOX_ENV=py39
python: 3.9
dist: bionic
sudo: true
name: "Python 3.9 with Extensions"
- env: TOX_ENV=py39-no-ext
python: 3.9
dist: bionic
sudo: true
name: "Python 3.9 without Extensions"
- env: TOX_ENV=type-checking
python: 3.6
name: "Python 3.6 Type checks"
@@ -40,6 +50,10 @@ matrix:
- env: TOX_ENV=type-checking
python: 3.8
name: "Python 3.8 Type checks"
- env: TOX_ENV=type-checking
python: 3.9
dist: bionic
name: "Python 3.9 Type checks"
- env: TOX_ENV=lint
python: 3.6
name: "Python 3.6 Linter checks"
@@ -61,23 +75,28 @@ matrix:
dist: xenial
sudo: true
name: "Python 3.8 Bandit security scan"
- env: TOX_ENV=security
python: 3.9
dist: bionic
sudo: true
name: "Python 3.9 Bandit security scan"
- env: TOX_ENV=docs
python: 3.7
dist: xenial
sudo: true
name: "Python 3.7 Documentation tests"
- env: TOX_ENV=pyNightly
python: 'nightly'
python: "nightly"
name: "Python nightly with Extensions"
- env: TOX_ENV=pyNightly-no-ext
python: 'nightly'
python: "nightly"
name: "Python nightly without Extensions"
allow_failures:
- env: TOX_ENV=pyNightly
python: 'nightly'
python: "nightly"
name: "Python nightly with Extensions"
- env: TOX_ENV=pyNightly-no-ext
python: 'nightly'
python: "nightly"
name: "Python nightly without Extensions"
install:
- pip install -U tox

View File

@@ -1,3 +1,115 @@
Version 20.12.0
===============
Features
********
*
`#1945 <https://github.com/huge-success/sanic/pull/1945>`_
Static route more verbose if file not found
*
`#1954 <https://github.com/huge-success/sanic/pull/1954>`_
Fix static routes registration on a blueprint
*
`#1961 <https://github.com/huge-success/sanic/pull/1961>`_
Add Python 3.9 support
*
`#1962 <https://github.com/huge-success/sanic/pull/1962>`_
Sanic CLI upgrade
*
`#1967 <https://github.com/huge-success/sanic/pull/1967>`_
Update aiofile version requirements
*
`#1969 <https://github.com/huge-success/sanic/pull/1969>`_
Update multidict version requirements
*
`#1970 <https://github.com/huge-success/sanic/pull/1970>`_
Add py.typed file
*
`#1972 <https://github.com/huge-success/sanic/pull/1972>`_
Speed optimization in request handler
*
`#1979 <https://github.com/huge-success/sanic/pull/1979>`_
Add app registry and Sanic class level app retrieval
Bugfixes
********
*
`#1965 <https://github.com/huge-success/sanic/pull/1965>`_
Fix Chunked Transport-Encoding in ASGI streaming response
Deprecations and Removals
*************************
*
`#1981 <https://github.com/huge-success/sanic/pull/1981>`_
Cleanup and remove deprecated code
Developer infrastructure
************************
*
`#1956 <https://github.com/huge-success/sanic/pull/1956>`_
Fix load module test
*
`#1973 <https://github.com/huge-success/sanic/pull/1973>`_
Transition Travis from .org to .com
*
`#1986 <https://github.com/huge-success/sanic/pull/1986>`_
Update tox requirements
Improved Documentation
**********************
*
`#1951 <https://github.com/huge-success/sanic/pull/1951>`_
Documentation improvements
*
`#1983 <https://github.com/huge-success/sanic/pull/1983>`_
Remove duplicate contents in testing.rst
*
`#1984 <https://github.com/huge-success/sanic/pull/1984>`_
Fix typo in routing.rst
Version 20.9.1
===============
Bugfixes
********
*
`#1954 <https://github.com/huge-success/sanic/pull/1954>`_
Fix static route registration on blueprints
*
`#1957 <https://github.com/huge-success/sanic/pull/1957>`_
Removes duplicate headers in ASGI streaming body
Version 19.12.3
===============
Bugfixes
********
*
`#1959 <https://github.com/huge-success/sanic/pull/1959>`_
Removes duplicate headers in ASGI streaming body
Version 20.9.0
===============
@@ -12,27 +124,27 @@ Features
*
`#1894 <https://github.com/huge-success/sanic/pull/1894>`_
Automatically set ``test_mode`` flag on app instance
*
`#1903 <https://github.com/huge-success/sanic/pull/1903>`_
`#1903 <https://github.com/huge-success/sanic/pull/1903>`_
Add new unified method for updating app values
*
`#1906 <https://github.com/huge-success/sanic/pull/1906>`_,
`#1909 <https://github.com/huge-success/sanic/pull/1909>`_
Adds WEBSOCKET_PING_TIMEOUT and WEBSOCKET_PING_INTERVAL configuration values
*
`#1935 <https://github.com/huge-success/sanic/pull/1935>`_
httpx version dependency updated, it is slated for removal as a dependency in v20.12
*
`#1937 <https://github.com/huge-success/sanic/pull/1937>`_
`#1937 <https://github.com/huge-success/sanic/pull/1937>`_
Added auto, text, and json fallback error handlers (in v21.3, the default will change form html to auto)
Bugfixes
********
*
`#1897 <https://github.com/huge-success/sanic/pull/1897>`_
Resolves exception from unread bytes in stream
@@ -73,7 +185,7 @@ Version 20.6.3
Bugfixes
********
*
`#1884 <https://github.com/huge-success/sanic/pull/1884>`_
Revert change to multiprocessing mode
@@ -84,7 +196,7 @@ Version 20.6.2
Features
********
*
`#1641 <https://github.com/huge-success/sanic/pull/1641>`_
Socket binding implemented properly for IPv6 and UNIX sockets
@@ -95,7 +207,7 @@ Version 20.6.1
Features
********
*
`#1760 <https://github.com/huge-success/sanic/pull/1760>`_
Add version parameter to websocket routes
@@ -106,7 +218,7 @@ Features
*
`#1880 <https://github.com/huge-success/sanic/pull/1880>`_
Add handler names for websockets for url_for usage
Add handler names for websockets for url_for usage
Bugfixes
********
@@ -126,7 +238,7 @@ Bugfixes
*
`#1848 <https://github.com/huge-success/sanic/pull/1848>`_
Reverse named_response_middlware execution order, to match normal response middleware execution order
*
`#1853 <https://github.com/huge-success/sanic/pull/1853>`_
Fix pickle error when attempting to pickle an application which contains websocket routes
@@ -178,28 +290,28 @@ Version 20.3.0
Features
********
*
*
`#1762 <https://github.com/huge-success/sanic/pull/1762>`_
Add ``srv.start_serving()`` and ``srv.serve_forever()`` to ``AsyncioServer``
*
*
`#1767 <https://github.com/huge-success/sanic/pull/1767>`_
Make Sanic usable on ``hypercorn -k trio myweb.app``
*
*
`#1768 <https://github.com/huge-success/sanic/pull/1768>`_
No tracebacks on normal errors and prettier error pages
*
*
`#1769 <https://github.com/huge-success/sanic/pull/1769>`_
Code cleanup in file responses
*
*
`#1793 <https://github.com/huge-success/sanic/pull/1793>`_ and
`#1819 <https://github.com/huge-success/sanic/pull/1819>`_
`#1819 <https://github.com/huge-success/sanic/pull/1819>`_
Upgrade ``str.format()`` to f-strings
*
*
`#1798 <https://github.com/huge-success/sanic/pull/1798>`_
Allow multiple workers on MacOS with Python 3.8
@@ -210,19 +322,19 @@ Features
Bugfixes
********
*
*
`#1748 <https://github.com/huge-success/sanic/pull/1748>`_
Remove loop argument in ``asyncio.Event`` in Python 3.8
*
*
`#1764 <https://github.com/huge-success/sanic/pull/1764>`_
Allow route decorators to stack up again
*
*
`#1789 <https://github.com/huge-success/sanic/pull/1789>`_
Fix tests using hosts yielding incorrect ``url_for``
*
*
`#1808 <https://github.com/huge-success/sanic/pull/1808>`_
Fix Ctrl+C and tests on Windows
@@ -236,7 +348,7 @@ Deprecations and Removals
*
`#1801 <https://github.com/huge-success/sanic/pull/1801>`_
Complete deprecation from `#1666 <https://github.com/huge-success/sanic/pull/1666>`_ of dictionary context on ``request`` objects.
*
`#1807 <https://github.com/huge-success/sanic/pull/1807>`_
Remove server config args that can be read directly from app
@@ -259,22 +371,22 @@ Dependencies
Developer infrastructure
************************
*
*
`#1833 <https://github.com/huge-success/sanic/pull/1833>`_
Resolve broken documentation builds
Improved Documentation
**********************
*
*
`#1755 <https://github.com/huge-success/sanic/pull/1755>`_
Usage of ``response.empty()``
*
*
`#1778 <https://github.com/huge-success/sanic/pull/1778>`_
Update README
*
*
`#1783 <https://github.com/huge-success/sanic/pull/1783>`_
Fix typo
@@ -301,7 +413,7 @@ Improved Documentation
*
`#1834 <https://github.com/huge-success/sanic/pull/1834>`_
Order of listeners
Version 19.12.0
===============
@@ -367,16 +479,16 @@ Version 19.6.2
Features
********
*
*
`#1562 <https://github.com/huge-success/sanic/pull/1562>`_
Remove ``aiohttp`` dependency and create new ``SanicTestClient`` based upon
`requests-async <https://github.com/encode/requests-async>`_
*
*
`#1475 <https://github.com/huge-success/sanic/pull/1475>`_
Added ASGI support (Beta)
*
*
`#1436 <https://github.com/huge-success/sanic/pull/1436>`_
Add Configure support from object string
@@ -384,19 +496,19 @@ Features
Bugfixes
********
*
*
`#1587 <https://github.com/huge-success/sanic/pull/1587>`_
Add missing handle for Expect header.
*
*
`#1560 <https://github.com/huge-success/sanic/pull/1560>`_
Allow to disable Transfer-Encoding: chunked.
*
*
`#1558 <https://github.com/huge-success/sanic/pull/1558>`_
Fix graceful shutdown.
*
*
`#1594 <https://github.com/huge-success/sanic/pull/1594>`_
Strict Slashes behavior fix
@@ -407,11 +519,11 @@ Deprecations and Removals
`#1544 <https://github.com/huge-success/sanic/pull/1544>`_
Drop dependency on distutil
*
*
`#1562 <https://github.com/huge-success/sanic/pull/1562>`_
Drop support for Python 3.5
*
*
`#1568 <https://github.com/huge-success/sanic/pull/1568>`_
Deprecate route removal.
@@ -428,39 +540,39 @@ Version 19.3
Features
********
*
*
`#1497 <https://github.com/huge-success/sanic/pull/1497>`_
Add support for zero-length and RFC 5987 encoded filename for
multipart/form-data requests.
*
*
`#1484 <https://github.com/huge-success/sanic/pull/1484>`_
The type of ``expires`` attribute of ``sanic.cookies.Cookie`` is now
enforced to be of type ``datetime``.
*
*
`#1482 <https://github.com/huge-success/sanic/pull/1482>`_
Add support for the ``stream`` parameter of ``sanic.Sanic.add_route()``
available to ``sanic.Blueprint.add_route()``.
*
*
`#1481 <https://github.com/huge-success/sanic/pull/1481>`_
Accept negative values for route parameters with type ``int`` or ``number``.
*
*
`#1476 <https://github.com/huge-success/sanic/pull/1476>`_
Deprecated the use of ``sanic.request.Request.raw_args`` - it has a
fundamental flaw in which is drops repeated query string parameters.
Added ``sanic.request.Request.query_args`` as a replacement for the
original use-case.
*
*
`#1472 <https://github.com/huge-success/sanic/pull/1472>`_
Remove an unwanted ``None`` check in Request class ``repr`` implementation.
This changes the default ``repr`` of a Request from ``<Request>`` to
``<Request: None />``
*
*
`#1470 <https://github.com/huge-success/sanic/pull/1470>`_
Added 2 new parameters to ``sanic.app.Sanic.create_server``\ :
@@ -471,21 +583,21 @@ Features
This is a breaking change.
*
*
`#1499 <https://github.com/huge-success/sanic/pull/1499>`_
Added a set of test cases that test and benchmark route resolution.
*
*
`#1457 <https://github.com/huge-success/sanic/pull/1457>`_
The type of the ``"max-age"`` value in a ``sanic.cookies.Cookie`` is now
enforced to be an integer. Non-integer values are replaced with ``0``.
*
*
`#1445 <https://github.com/huge-success/sanic/pull/1445>`_
Added the ``endpoint`` attribute to an incoming ``request``\ , containing the
name of the handler function.
*
*
`#1423 <https://github.com/huge-success/sanic/pull/1423>`_
Improved request streaming. ``request.stream`` is now a bounded-size buffer
instead of an unbounded queue. Callers must now call
@@ -498,7 +610,7 @@ Bugfixes
********
*
*
`#1502 <https://github.com/huge-success/sanic/pull/1502>`_
Sanic was prefetching ``time.time()`` and updating it once per second to
avoid excessive ``time.time()`` calls. The implementation was observed to
@@ -506,25 +618,25 @@ Bugfixes
to negligible, so this has been removed. Fixes
`#1500 <https://github.com/huge-success/sanic/pull/1500>`_
*
*
`#1501 <https://github.com/huge-success/sanic/pull/1501>`_
Fix a bug in the auto-reloader when the process was launched as a module
i.e. ``python -m init0.mod1`` where the sanic server is started
in ``init0/mod1.py`` with ``debug`` enabled and imports another module in
``init0``.
*
*
`#1376 <https://github.com/huge-success/sanic/pull/1376>`_
Allow sanic test client to bind to a random port by specifying
``port=None`` when constructing a ``SanicTestClient``
*
*
`#1399 <https://github.com/huge-success/sanic/pull/1399>`_
Added the ability to specify middleware on a blueprint group, so that all
routes produced from the blueprints in the group have the middleware
applied.
*
*
`#1442 <https://github.com/huge-success/sanic/pull/1442>`_
Allow the the use the ``SANIC_ACCESS_LOG`` environment variable to
enable/disable the access log when not explicitly passed to ``app.run()``.
@@ -566,7 +678,7 @@ Version 18.12
18.12.0
*******
*
*
Changes:
@@ -584,7 +696,7 @@ Version 18.12
* Deprecate Handler.log
* Pinned httptools requirement to version 0.0.10+
*
*
Fixes:

View File

@@ -26,8 +26,8 @@ Sanic | Build fast. Run fast.
:target: https://gitter.im/sanic-python/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge
.. |Codecov| image:: https://codecov.io/gh/huge-success/sanic/branch/master/graph/badge.svg
:target: https://codecov.io/gh/huge-success/sanic
.. |Build Status| image:: https://travis-ci.org/huge-success/sanic.svg?branch=master
:target: https://travis-ci.org/huge-success/sanic
.. |Build Status| image:: https://travis-ci.com/huge-success/sanic.svg?branch=master
:target: https://travis-ci.com/huge-success/sanic
.. |AppVeyor Build Status| image:: https://ci.appveyor.com/api/projects/status/d8pt3ids0ynexi8c/branch/master?svg=true
:target: https://ci.appveyor.com/project/huge-success/sanic
.. |Documentation| image:: https://readthedocs.org/projects/sanic/badge/?version=latest

1
changelogs/1970.misc.rst Normal file
View File

@@ -0,0 +1 @@
Adds py.typed file to expose type information to other packages.

View File

@@ -60,3 +60,26 @@ Open the address `http://0.0.0.0:8000 <http://0.0.0.0:8000>`_ in your web browse
the message *Hello world!*.
You now have a working Sanic server!
5. Application registry
-----------------------
When you instantiate a Sanic instance, that can be retrieved at a later time from the Sanic app registry. This can be useful, for example, if you need to access your Sanic instance from a location where it is not otherwise accessible.
.. code-block:: python
# ./path/to/server.py
from sanic import Sanic
app = Sanic("my_awesome_server")
# ./path/to/somewhere_else.py
from sanic import Sanic
app = Sanic.get_app("my_awesome_server")
If you call ``Sanic.get_app("non-existing")`` on an app that does not exist, it will raise ``SanicException`` by default. You can, instead, force the method to return a new instance of ``Sanic`` with that name:
.. code-block:: python
app = Sanic.get_app("my_awesome_server", force_create=True)

View File

@@ -133,7 +133,7 @@ which allows the handler function to work with any of the HTTP methods in the li
async def get_handler(request):
return text('GET request - {}'.format(request.args))
There is also an optional `host` argument (which can be a list or a string). This restricts a route to the host or hosts provided. If there is a also a route with no host, it will be the default.
There is also an optional `host` argument (which can be a list or a string). This restricts a route to the host or hosts provided. If there is also a route with no host, it will be the default.
.. code-block:: python

View File

@@ -88,5 +88,5 @@ When `stream_large_files` is `True`, Sanic will use `file_stream()` instead of `
app = Sanic(__name__)
chunk_size = 1024 * 1024 * 8 # Set chunk size to 8KB
chunk_size = 1024 * 1024 * 8 # Set chunk size to 8MiB
app.static('/large_video.mp4', '/home/ubuntu/large_video.mp4', stream_large_files=chunk_size)

View File

@@ -58,10 +58,6 @@ More information about
the available arguments to `httpx` can be found
[in the documentation for `httpx <https://www.encode.io/httpx/>`_.
Additionally, Sanic has an asynchronous testing client. The difference is that the async client will not stand up an
instance of your application, but will instead reach inside it using ASGI. All listeners and middleware are still
executed.
.. code-block:: python
@pytest.mark.asyncio
async def test_index_returns_200():

View File

@@ -7,6 +7,7 @@
"""
from pathlib import Path
from sanic import Sanic, response

View File

@@ -1,28 +1,83 @@
import os
import sys
from argparse import ArgumentParser
from argparse import ArgumentParser, RawDescriptionHelpFormatter
from importlib import import_module
from typing import Any, Dict, Optional
from sanic import __version__
from sanic.app import Sanic
from sanic.config import BASE_LOGO
from sanic.log import logger
class SanicArgumentParser(ArgumentParser):
def add_bool_arguments(self, *args, **kwargs):
group = self.add_mutually_exclusive_group()
group.add_argument(*args, action="store_true", **kwargs)
kwargs["help"] = "no " + kwargs["help"]
group.add_argument(
"--no-" + args[0][2:], *args[1:], action="store_false", **kwargs
)
def main():
parser = ArgumentParser(prog="sanic")
parser.add_argument("--host", dest="host", type=str, default="127.0.0.1")
parser.add_argument("--port", dest="port", type=int, default=8000)
parser.add_argument("--unix", dest="unix", type=str, default="")
parser = SanicArgumentParser(
prog="sanic",
description=BASE_LOGO,
formatter_class=RawDescriptionHelpFormatter,
)
parser.add_argument(
"-H",
"--host",
dest="host",
type=str,
default="127.0.0.1",
help="host address [default 127.0.0.1]",
)
parser.add_argument(
"-p",
"--port",
dest="port",
type=int,
default=8000,
help="port to serve on [default 8000]",
)
parser.add_argument(
"-u",
"--unix",
dest="unix",
type=str,
default="",
help="location of unix socket",
)
parser.add_argument(
"--cert", dest="cert", type=str, help="location of certificate for SSL"
)
parser.add_argument(
"--key", dest="key", type=str, help="location of keyfile for SSL."
)
parser.add_argument("--workers", dest="workers", type=int, default=1)
parser.add_argument(
"-w",
"--workers",
dest="workers",
type=int,
default=1,
help="number of worker processes [default 1]",
)
parser.add_argument("--debug", dest="debug", action="store_true")
parser.add_argument("module")
parser.add_bool_arguments(
"--access-logs", dest="access_log", help="display access logs"
)
parser.add_argument(
"-v",
"--version",
action="version",
version=f"Sanic {__version__}",
)
parser.add_argument(
"module", help="path to your Sanic app. Example: path.to.server:app"
)
args = parser.parse_args()
try:
@@ -30,9 +85,12 @@ def main():
if module_path not in sys.path:
sys.path.append(module_path)
module_parts = args.module.split(".")
module_name = ".".join(module_parts[:-1])
app_name = module_parts[-1]
if ":" in args.module:
module_name, app_name = args.module.rsplit(":", 1)
else:
module_parts = args.module.split(".")
module_name = ".".join(module_parts[:-1])
app_name = module_parts[-1]
module = import_module(module_name)
app = getattr(module, app_name, None)
@@ -57,6 +115,7 @@ def main():
unix=args.unix,
workers=args.workers,
debug=args.debug,
access_log=args.access_log,
ssl=ssl,
)
except ImportError as e:

View File

@@ -1 +1 @@
__version__ = "20.9.0"
__version__ = "20.12.2"

View File

@@ -2,12 +2,11 @@ import logging
import logging.config
import os
import re
import warnings
from asyncio import CancelledError, Protocol, ensure_future, get_event_loop
from collections import defaultdict, deque
from functools import partial
from inspect import getmodulename, isawaitable, signature, stack
from inspect import isawaitable, signature
from socket import socket
from ssl import Purpose, SSLContext, create_default_context
from traceback import format_exc
@@ -38,6 +37,9 @@ from sanic.websocket import ConnectionClosed, WebSocketProtocol
class Sanic:
_app_registry: Dict[str, "Sanic"] = {}
test_mode = False
def __init__(
self,
name=None,
@@ -48,19 +50,15 @@ class Sanic:
strict_slashes=False,
log_config=None,
configure_logging=True,
register=None,
):
# Get name from previous stack frame
if name is None:
warnings.warn(
"Sanic(name=None) is deprecated and None value support "
"for `name` will be removed in the next release. "
raise SanicException(
"Sanic instance cannot be unnamed. "
"Please use Sanic(name='your_application_name') instead.",
DeprecationWarning,
stacklevel=2,
)
frame_records = stack()[1]
name = getmodulename(frame_records[1])
# logging
if configure_logging:
@@ -68,7 +66,7 @@ class Sanic:
self.name = name
self.asgi = False
self.router = router or Router()
self.router = router or Router(self)
self.request_class = request_class
self.error_handler = error_handler or ErrorHandler()
self.config = Config(load_env=load_env)
@@ -90,7 +88,12 @@ class Sanic:
self.named_response_middleware = {}
# Register alternative method names
self.go_fast = self.run
self.test_mode = False
if register is not None:
self.config.REGISTER = register
if self.config.REGISTER:
self.__class__.register_app(self)
@property
def loop(self):
@@ -676,9 +679,10 @@ class Sanic:
:param strict_slashes: Instruct :class:`Sanic` to check if the request
URLs need to terminate with a */*
:param content_type: user defined content type for header
:return: None
:return: routes registered on the router
:rtype: List[sanic.router.Route]
"""
static_register(
return static_register(
self,
uri,
file_or_directory,
@@ -713,28 +717,6 @@ class Sanic:
self._blueprint_order.append(blueprint)
blueprint.register(self, options)
def register_blueprint(self, *args, **kwargs):
"""
Proxy method provided for invoking the :func:`blueprint` method
.. note::
To be deprecated in 1.0. Use :func:`blueprint` instead.
:param args: Blueprint object or (list, tuple) thereof
:param kwargs: option dictionary with blueprint defaults
:return: None
"""
if self.debug:
warnings.simplefilter("default")
warnings.warn(
"Use of register_blueprint will be deprecated in "
"version 1.0. Please use the blueprint method"
" instead",
DeprecationWarning,
)
return self.blueprint(*args, **kwargs)
def url_for(self, view_name: str, **kwargs):
r"""Build a URL based on a view name and the values provided.
@@ -899,7 +881,9 @@ class Sanic:
name = None
try:
# Fetch handler from router
handler, args, kwargs, uri, name = self.router.get(request)
handler, args, kwargs, uri, name, endpoint = self.router.get(
request
)
# -------------------------------------------- #
# Request Middleware
@@ -921,16 +905,8 @@ class Sanic:
"handler from the router"
)
)
else:
if not getattr(handler, "__blueprintname__", False):
request.endpoint = self._build_endpoint_name(
handler.__name__
)
else:
request.endpoint = self._build_endpoint_name(
getattr(handler, "__blueprintname__", ""),
handler.__name__,
)
request.endpoint = endpoint
# Run response handler
response = handler(request, *args, **kwargs)
@@ -1031,7 +1007,6 @@ class Sanic:
workers: int = 1,
protocol: Optional[Type[Protocol]] = None,
backlog: int = 100,
stop_event: Any = None,
register_sys_signals: bool = True,
access_log: Optional[bool] = None,
unix: Optional[str] = None,
@@ -1061,9 +1036,6 @@ class Sanic:
:param backlog: a number of unaccepted connections that the system
will allow before refusing new connections
:type backlog: int
:param stop_event: event to be triggered
before stopping the app - deprecated
:type stop_event: None
:param register_sys_signals: Register SIG* events
:type register_sys_signals: bool
:param access_log: Enables writing access logs (slows server)
@@ -1091,13 +1063,6 @@ class Sanic:
protocol = (
WebSocketProtocol if self.websocket_enabled else HttpProtocol
)
if stop_event is not None:
if debug:
warnings.simplefilter("default")
warnings.warn(
"stop_event will be removed from future versions.",
DeprecationWarning,
)
# if access_log is passed explicitly change config.ACCESS_LOG
if access_log is not None:
self.config.ACCESS_LOG = access_log
@@ -1154,7 +1119,6 @@ class Sanic:
sock: Optional[socket] = None,
protocol: Type[Protocol] = None,
backlog: int = 100,
stop_event: Any = None,
access_log: Optional[bool] = None,
unix: Optional[str] = None,
return_asyncio_server=False,
@@ -1187,9 +1151,6 @@ class Sanic:
:param backlog: a number of unaccepted connections that the system
will allow before refusing new connections
:type backlog: int
:param stop_event: event to be triggered
before stopping the app - deprecated
:type stop_event: None
:param access_log: Enables writing access logs (slows server)
:type access_log: bool
:param return_asyncio_server: flag that defines whether there's a need
@@ -1209,13 +1170,6 @@ class Sanic:
protocol = (
WebSocketProtocol if self.websocket_enabled else HttpProtocol
)
if stop_event is not None:
if debug:
warnings.simplefilter("default")
warnings.warn(
"stop_event will be removed from future versions.",
DeprecationWarning,
)
# if access_log is passed explicitly change config.ACCESS_LOG
if access_log is not None:
self.config.ACCESS_LOG = access_log
@@ -1297,7 +1251,6 @@ class Sanic:
loop=None,
protocol=HttpProtocol,
backlog=100,
stop_event=None,
register_sys_signals=True,
run_async=False,
auto_reload=False,
@@ -1312,13 +1265,6 @@ class Sanic:
context = create_default_context(purpose=Purpose.CLIENT_AUTH)
context.load_cert_chain(cert, keyfile=key)
ssl = context
if stop_event is not None:
if debug:
warnings.simplefilter("default")
warnings.warn(
"stop_event will be removed from future versions.",
DeprecationWarning,
)
if self.config.PROXIES_COUNT and self.config.PROXIES_COUNT < 0:
raise ValueError(
"PROXIES_COUNT cannot be negative. "
@@ -1453,12 +1399,41 @@ class Sanic:
asgi_app = await ASGIApp.create(self, scope, receive, send)
await asgi_app()
_asgi_single_callable = True # We conform to ASGI 3.0 single-callable
# -------------------------------------------------------------------- #
# Configuration
# -------------------------------------------------------------------- #
def update_config(self, config: Union[bytes, str, dict, Any]):
"""Update app.config.
Please refer to config.py::Config.update_config for documentation."""
self.config.update_config(config)
# -------------------------------------------------------------------- #
# Class methods
# -------------------------------------------------------------------- #
@classmethod
def register_app(cls, app: "Sanic") -> None:
"""Register a Sanic instance"""
if not isinstance(app, cls):
raise SanicException("Registered app must be an instance of Sanic")
name = app.name
if name in cls._app_registry and not cls.test_mode:
raise SanicException(f'Sanic app name "{name}" already in use.')
cls._app_registry[name] = app
@classmethod
def get_app(cls, name: str, *, force_create: bool = False) -> "Sanic":
"""Retrieve an instantiated Sanic instance"""
try:
return cls._app_registry[name]
except KeyError:
if force_create:
return cls(name)
raise SanicException(f'Sanic app name "{name}" not found.')

View File

@@ -312,13 +312,19 @@ class ASGIApp:
callback = None if self.ws else self.stream_callback
await handler(self.request, None, callback)
async def stream_callback(self, response: HTTPResponse) -> None:
_asgi_single_callable = True # We conform to ASGI 3.0 single-callable
async def stream_callback(
self, response: Union[HTTPResponse, StreamingHTTPResponse]
) -> None:
"""
Write the response.
"""
headers: List[Tuple[bytes, bytes]] = []
cookies: Dict[str, str] = {}
content_length: List[str] = []
try:
content_length = response.headers.popall("content-length", [])
cookies = {
v.key: v
for _, v in list(
@@ -350,11 +356,23 @@ class ASGIApp:
if name not in (b"Set-Cookie",)
]
if "content-length" not in response.headers and not isinstance(
response, StreamingHTTPResponse
):
response.asgi = True
is_streaming = isinstance(response, StreamingHTTPResponse)
if is_streaming and getattr(response, "chunked", False):
# disable sanic chunking, this is done at the ASGI-server level
setattr(response, "chunked", False)
# content-length header is removed to signal to the ASGI-server
# to use automatic-chunking if it supports it
elif len(content_length) > 0:
headers += [
(b"content-length", str(len(response.body)).encode("latin-1"))
(b"content-length", str(content_length[0]).encode("latin-1"))
]
elif not is_streaming:
headers += [
(
b"content-length",
str(len(getattr(response, "body", b""))).encode("latin-1"),
)
]
if "content-type" not in response.headers:

View File

@@ -143,7 +143,18 @@ class Blueprint:
if _routes:
routes += _routes
# Static Files
for future in self.statics:
# Prepend the blueprint URI prefix if available
uri = url_prefix + future.uri if url_prefix else future.uri
_routes = app.static(
uri, future.file_or_directory, *future.args, **future.kwargs
)
if _routes:
routes += _routes
route_names = [route.name for route in routes if route]
# Middleware
for future in self.middlewares:
if future.args or future.kwargs:
@@ -160,14 +171,6 @@ class Blueprint:
for future in self.exceptions:
app.exception(*future.args, **future.kwargs)(future.handler)
# Static Files
for future in self.statics:
# Prepend the blueprint URI prefix if available
uri = url_prefix + future.uri if url_prefix else future.uri
app.static(
uri, future.file_or_directory, *future.args, **future.kwargs
)
# Event listeners
for event, listeners in self.listeners.items():
for listener in listeners:

View File

@@ -40,6 +40,7 @@ DEFAULT_CONFIG = {
"PROXIES_COUNT": None,
"FORWARDED_FOR_HEADER": "X-Forwarded-For",
"FALLBACK_ERROR_FORMAT": "html",
"REGISTER": True,
}

View File

@@ -179,8 +179,8 @@ def abort(status_code, message=None):
message appropriate for the given status code, unless provided.
:param status_code: The HTTP status code to return.
:param message: The HTTP response body. Defaults to the messages
in response.py for the given status code.
:param message: The HTTP response body. Defaults to the messages in
STATUS_CODES from sanic.helpers for the given status code.
"""
if message is None:
message = STATUS_CODES.get(status_code)

0
sanic/py.typed Normal file
View File

View File

@@ -136,15 +136,18 @@ class Request:
return f"<{class_name}: {self.method} {self.path}>"
def body_init(self):
""".. deprecated:: 20.3"""
""".. deprecated:: 20.3
To be removed in 21.3"""
self.body = []
def body_push(self, data):
""".. deprecated:: 20.3"""
""".. deprecated:: 20.3
To be removed in 21.3"""
self.body.append(data)
def body_finish(self):
""".. deprecated:: 20.3"""
""".. deprecated:: 20.3
To be removed in 21.3"""
self.body = b"".join(self.body)
async def receive_body(self):

View File

@@ -1,5 +1,3 @@
import warnings
from functools import partial
from mimetypes import guess_type
from os import path
@@ -22,7 +20,12 @@ except ImportError:
class BaseHTTPResponse:
def __init__(self):
self.asgi = False
def _encode_body(self, data):
if data is None:
return b""
return data.encode() if hasattr(data, "encode") else data
def _parse_headers(self):
@@ -42,7 +45,7 @@ class BaseHTTPResponse:
body=b"",
):
""".. deprecated:: 20.3:
This function is not public API and will be removed."""
This function is not public API and will be removed in 21.3."""
# self.headers get priority over content_type
if self.content_type and "Content-Type" not in self.headers:
@@ -80,6 +83,8 @@ class StreamingHTTPResponse(BaseHTTPResponse):
content_type="text/plain; charset=utf-8",
chunked=True,
):
super().__init__()
self.content_type = content_type
self.streaming_fn = streaming_fn
self.status = status
@@ -95,6 +100,8 @@ class StreamingHTTPResponse(BaseHTTPResponse):
"""
data = self._encode_body(data)
# `chunked` will always be False in ASGI-mode, even if the underlying
# ASGI Transport implements Chunked transport. That does it itself.
if self.chunked:
await self.protocol.push_data(b"%x\r\n%b\r\n" % (len(data), data))
else:
@@ -109,13 +116,14 @@ class StreamingHTTPResponse(BaseHTTPResponse):
"""
if version != "1.1":
self.chunked = False
headers = self.get_headers(
version,
keep_alive=keep_alive,
keep_alive_timeout=keep_alive_timeout,
)
await self.protocol.push_data(headers)
await self.protocol.drain()
if not getattr(self, "asgi", False):
headers = self.get_headers(
version,
keep_alive=keep_alive,
keep_alive_timeout=keep_alive_timeout,
)
await self.protocol.push_data(headers)
await self.protocol.drain()
await self.streaming_fn(self)
if self.chunked:
await self.protocol.push_data(b"0\r\n\r\n")
@@ -141,20 +149,15 @@ class HTTPResponse(BaseHTTPResponse):
status=200,
headers=None,
content_type=None,
body_bytes=b"",
):
super().__init__()
self.content_type = content_type
self.body = body_bytes if body is None else self._encode_body(body)
self.body = self._encode_body(body)
self.status = status
self.headers = Header(headers or {})
self._cookies = None
if body_bytes:
warnings.warn(
"Parameter `body_bytes` is deprecated, use `body` instead",
DeprecationWarning,
)
def output(self, version="1.1", keep_alive=False, keep_alive_timeout=None):
body = b""
if has_message_body(self.status):
@@ -218,20 +221,10 @@ def text(
:param content_type: the content type (string) of the response
"""
if not isinstance(body, str):
warnings.warn(
"Types other than str will be deprecated in future versions for"
f" response.text, got type {type(body).__name__})",
DeprecationWarning,
raise TypeError(
f"Bad body type. Expected str, got {type(body).__name__})"
)
# Type conversions are deprecated and quite b0rked but still supported for
# text() until applications get fixed. This try-except should be removed.
try:
# Avoid repr(body).encode() b0rkage for body that is already encoded.
# memoryview used only to test bytes-ishness.
with memoryview(body):
pass
except TypeError:
body = f"{body}" # no-op if body is already str
return HTTPResponse(
body, status=status, headers=headers, content_type=content_type
)

View File

@@ -11,7 +11,16 @@ from sanic.views import CompositionView
Route = namedtuple(
"Route", ["handler", "methods", "pattern", "parameters", "name", "uri"]
"Route",
[
"handler",
"methods",
"pattern",
"parameters",
"name",
"uri",
"endpoint",
],
)
Parameter = namedtuple("Parameter", ["name", "cast"])
@@ -79,7 +88,8 @@ class Router:
routes_always_check = None
parameter_pattern = re.compile(r"<(.+?)>")
def __init__(self):
def __init__(self, app):
self.app = app
self.routes_all = {}
self.routes_names = {}
self.routes_static_files = {}
@@ -299,11 +309,15 @@ class Router:
handler_name = f"{bp_name}.{name or handler.__name__}"
else:
handler_name = name or getattr(handler, "__name__", None)
handler_name = name or getattr(
handler, "__name__", handler.__class__.__name__
)
if route:
route = merge_route(route, methods, handler)
else:
endpoint = self.app._build_endpoint_name(handler_name)
route = Route(
handler=handler,
methods=methods,
@@ -311,6 +325,7 @@ class Router:
parameters=parameters,
name=handler_name,
uri=uri,
endpoint=endpoint,
)
self.routes_all[uri] = route
@@ -449,7 +464,8 @@ class Router:
route_handler = route.handler
if hasattr(route_handler, "handlers"):
route_handler = route_handler.handlers[method]
return route_handler, [], kwargs, route.uri, route.name
return route_handler, [], kwargs, route.uri, route.name, route.endpoint
def is_stream_handler(self, request):
"""Handler for request is stream or not.

View File

@@ -13,6 +13,7 @@ from sanic.exceptions import (
InvalidUsage,
)
from sanic.handlers import ContentRangeHandler
from sanic.log import error_logger
from sanic.response import HTTPResponse, file, file_stream
@@ -40,6 +41,10 @@ async def _static_request_handler(
# match filenames which got encoded (filenames with spaces etc)
file_path = path.abspath(unquote(file_path))
if not file_path.startswith(path.abspath(unquote(root_path))):
error_logger.exception(
f"File not found: path={file_or_directory}, "
f"relative_url={file_uri}"
)
raise FileNotFound(
"File not found", path=file_or_directory, relative_url=file_uri
)
@@ -94,6 +99,10 @@ async def _static_request_handler(
except ContentRangeError:
raise
except Exception:
error_logger.exception(
f"File not found: path={file_or_directory}, "
f"relative_url={file_uri}"
)
raise FileNotFound(
"File not found", path=file_or_directory, relative_url=file_uri
)
@@ -134,6 +143,8 @@ def register(
threshold size to switch to file_stream()
:param name: user defined name used for url_for
:param content_type: user defined content type for header
:return: registered static routes
:rtype: List[sanic.router.Route]
"""
# If we're not trying to match a file directly,
# serve from the folder
@@ -155,10 +166,11 @@ def register(
)
)
app.route(
_routes, _ = app.route(
uri,
methods=["GET", "HEAD"],
name=name,
host=host,
strict_slashes=strict_slashes,
)(_handler)
return _routes

View File

@@ -90,6 +90,7 @@ class CompositionView:
def __init__(self):
self.handlers = {}
self.name = self.__class__.__name__
def add(self, methods, handler, stream=False):
if stream:

View File

@@ -5,6 +5,7 @@ import codecs
import os
import re
import sys
from distutils.util import strtobool
from setuptools import setup
@@ -24,6 +25,7 @@ class PyTest(TestCommand):
def run_tests(self):
import shlex
import pytest
errno = pytest.main(shlex.split(self.pytest_args))
@@ -38,7 +40,9 @@ def open_local(paths, mode="r", encoding="utf8"):
with open_local(["sanic", "__version__.py"], encoding="latin1") as fp:
try:
version = re.findall(r"^__version__ = \"([^']+)\"\r?$", fp.read(), re.M)[0]
version = re.findall(
r"^__version__ = \"([^']+)\"\r?$", fp.read(), re.M
)[0]
except IndexError:
raise RuntimeError("Unable to determine version.")
@@ -53,10 +57,12 @@ setup_kwargs = {
"author": "Sanic Community",
"author_email": "admhpkns@gmail.com",
"description": (
"A web server and web framework that's written to go fast. Build fast. Run fast."
"A web server and web framework that's written to go fast. "
"Build fast. Run fast."
),
"long_description": long_description,
"packages": ["sanic"],
"package_data": {"sanic": ["py.typed"]},
"platforms": "any",
"python_requires": ">=3.6",
"classifiers": [
@@ -66,36 +72,40 @@ setup_kwargs = {
"Programming Language :: Python :: 3.6",
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
],
"entry_points": {"console_scripts": ["sanic = sanic.__main__:main"]},
}
env_dependency = '; sys_platform != "win32" ' 'and implementation_name == "cpython"'
env_dependency = (
'; sys_platform != "win32" ' 'and implementation_name == "cpython"'
)
ujson = "ujson>=1.35" + env_dependency
uvloop = "uvloop>=0.5.3" + env_dependency
uvloop = "uvloop>=0.5.3,<0.15.0" + env_dependency
requirements = [
"httptools>=0.0.10",
uvloop,
ujson,
"aiofiles>=0.3.0",
"aiofiles>=0.6.0",
"websockets>=8.1,<9.0",
"multidict>=4.0,<5.0",
"multidict>=5.0,<6.0",
"httpx==0.15.4",
]
tests_require = [
"pytest==5.2.1",
"multidict>=4.0,<5.0",
"gunicorn",
"multidict>=5.0,<6.0",
"gunicorn==20.0.4",
"pytest-cov",
"httpcore==0.3.0",
"httpcore==0.11.*",
"beautifulsoup4",
uvloop,
ujson,
"pytest-sanic",
"pytest-sugar",
"pytest-benchmark",
"pytest-dependency",
]
docs_require = [

View File

@@ -11,6 +11,7 @@ from sanic.router import RouteExists, Router
random.seed("Pack my box with five dozen liquor jugs.")
Sanic.test_mode = True
if sys.platform in ["win32", "cygwin"]:
collect_ignore = ["test_worker.py"]
@@ -95,10 +96,10 @@ class RouteStringGenerator:
@pytest.fixture(scope="function")
def sanic_router():
def sanic_router(app):
# noinspection PyProtectedMember
def _setup(route_details: tuple) -> (Router, tuple):
router = Router()
router = Router(app)
added_router = []
for method, route in route_details:
try:

View File

@@ -3,6 +3,7 @@ import logging
import sys
from inspect import isawaitable
from os import environ
from unittest.mock import patch
import pytest
@@ -117,7 +118,7 @@ def test_app_route_raise_value_error(app):
def test_app_handle_request_handler_is_none(app, monkeypatch):
def mockreturn(*args, **kwargs):
return None, [], {}, "", ""
return None, [], {}, "", "", None
# Not sure how to make app.router.get() return None, so use mock here.
monkeypatch.setattr(app.router, "get", mockreturn)
@@ -258,7 +259,7 @@ def test_handle_request_with_nested_sanic_exception(app, monkeypatch, caplog):
def test_app_name_required():
with pytest.deprecated_call():
with pytest.raises(SanicException):
Sanic()
@@ -274,14 +275,50 @@ def test_app_has_test_mode_sync():
assert response.status == 200
# @pytest.mark.asyncio
# async def test_app_has_test_mode_async():
# app = Sanic("test")
def test_app_registry():
instance = Sanic("test")
assert Sanic._app_registry["test"] is instance
# @app.get("/")
# async def handler(request):
# assert request.app.test_mode
# return text("test")
# _, response = await app.asgi_client.get("/")
# assert response.status == 200
def test_app_registry_wrong_type():
with pytest.raises(SanicException):
Sanic.register_app(1)
def test_app_registry_name_reuse():
Sanic("test")
Sanic.test_mode = False
with pytest.raises(SanicException):
Sanic("test")
Sanic.test_mode = True
Sanic("test")
def test_app_registry_retrieval():
instance = Sanic("test")
assert Sanic.get_app("test") is instance
def test_get_app_does_not_exist():
with pytest.raises(SanicException):
Sanic.get_app("does-not-exist")
def test_get_app_does_not_exist_force_create():
assert isinstance(
Sanic.get_app("does-not-exist", force_create=True), Sanic
)
def test_app_no_registry():
Sanic("no-register", register=False)
with pytest.raises(SanicException):
Sanic.get_app("no-register")
def test_app_no_registry_env():
environ["SANIC_REGISTER"] = "False"
Sanic("no-register")
with pytest.raises(SanicException):
Sanic.get_app("no-register")
del environ["SANIC_REGISTER"]

View File

@@ -736,6 +736,37 @@ def test_static_blueprint_name(app: Sanic, static_file_directory, file_name):
assert response.status == 200
@pytest.mark.parametrize("file_name", ["test.file"])
def test_static_blueprintp_mw(app: Sanic, static_file_directory, file_name):
current_file = inspect.getfile(inspect.currentframe())
with open(current_file, "rb") as file:
file.read()
triggered = False
bp = Blueprint(name="test_mw", url_prefix="")
@bp.middleware("request")
def bp_mw1(request):
nonlocal triggered
triggered = True
bp.static(
"/test.file",
get_file_path(static_file_directory, file_name),
strict_slashes=True,
name="static",
)
app.blueprint(bp)
uri = app.url_for("test_mw.static")
assert uri == "/test.file"
_, response = app.test_client.get("/test.file")
assert triggered is True
def test_route_handler_add(app: Sanic):
view = CompositionView()
@@ -794,21 +825,6 @@ def test_duplicate_blueprint(app):
)
@pytest.mark.parametrize("debug", [True, False, None])
def test_register_blueprint(app, debug):
bp = Blueprint("bp")
app.debug = debug
with pytest.warns(DeprecationWarning) as record:
app.register_blueprint(bp)
assert record[0].message.args[0] == (
"Use of register_blueprint will be deprecated in "
"version 1.0. Please use the blueprint method"
" instead"
)
def test_strict_slashes_behavior_adoption(app):
app.strict_slashes = True

View File

@@ -10,7 +10,7 @@ from sanic.utils import load_module_from_file_location
@pytest.fixture
def loaded_module_from_file_location():
return load_module_from_file_location(
str(Path(__file__).parent / "static/app_test_config.py")
str(Path(__file__).parent / "static" / "app_test_config.py")
)
@@ -23,7 +23,10 @@ def test_load_module_from_file_location(loaded_module_from_file_location):
def test_loaded_module_from_file_location_name(
loaded_module_from_file_location,
):
assert loaded_module_from_file_location.__name__ == "app_test_config"
name = loaded_module_from_file_location.__name__
if "C:\\" in name:
name = name.split("\\")[-1]
assert name == "app_test_config"
def test_load_module_from_file_location_with_non_existing_env_variable():

View File

@@ -41,7 +41,8 @@ def test_response_body_not_a_string(app):
return text(random_num)
request, response = app.test_client.get("/hello")
assert response.text == str(random_num)
assert response.status == 500
assert b"Internal Server Error" in response.body
async def sample_streaming_fn(response):
@@ -235,6 +236,12 @@ def test_chunked_streaming_returns_correct_content(streaming_app):
assert response.text == "foo,bar"
@pytest.mark.asyncio
async def test_chunked_streaming_returns_correct_content_asgi(streaming_app):
request, response = await streaming_app.asgi_client.get("/")
assert response.text == "foo,bar"
def test_non_chunked_streaming_adds_correct_headers(non_chunked_streaming_app):
request, response = non_chunked_streaming_app.test_client.get("/")
assert "Transfer-Encoding" not in response.headers
@@ -242,6 +249,16 @@ def test_non_chunked_streaming_adds_correct_headers(non_chunked_streaming_app):
assert response.headers["Content-Length"] == "7"
@pytest.mark.asyncio
async def test_non_chunked_streaming_adds_correct_headers_asgi(
non_chunked_streaming_app,
):
request, response = await non_chunked_streaming_app.asgi_client.get("/")
assert "Transfer-Encoding" not in response.headers
assert response.headers["Content-Type"] == "text/csv"
assert response.headers["Content-Length"] == "7"
def test_non_chunked_streaming_returns_correct_content(
non_chunked_streaming_app,
):
@@ -608,17 +625,3 @@ def test_empty_response(app):
request, response = app.test_client.get("/test")
assert response.content_type is None
assert response.body == b""
def test_response_body_bytes_deprecated(app):
with warnings.catch_warnings(record=True) as w:
warnings.simplefilter("always")
HTTPResponse(body_bytes=b"bytes")
assert len(w) == 1
assert issubclass(w[0].category, DeprecationWarning)
assert (
"Parameter `body_bytes` is deprecated, use `body` instead"
in str(w[0].message)
)

16
tox.ini
View File

@@ -1,24 +1,24 @@
[tox]
envlist = py36, py37, py38, pyNightly, {py36,py37,py38,pyNightly}-no-ext, lint, check, security, docs
envlist = py36, py37, py38, py39, pyNightly, {py36,py37,py38,py39,pyNightly}-no-ext, lint, check, security, docs
[testenv]
usedevelop = True
setenv =
{py36,py37,py38,pyNightly}-no-ext: SANIC_NO_UJSON=1
{py36,py37,py38,pyNightly}-no-ext: SANIC_NO_UVLOOP=1
{py36,py37,py38,py39,pyNightly}-no-ext: SANIC_NO_UJSON=1
{py36,py37,py38,py39,pyNightly}-no-ext: SANIC_NO_UVLOOP=1
deps =
coverage
coverage==5.3
pytest==5.2.1
pytest-cov
pytest-sanic
pytest-sugar
pytest-benchmark
pytest-dependency
httpcore==0.3.0
httpcore==0.11.*
httpx==0.15.4
chardet<=2.3.0
multidict>=5.0,<6.0
beautifulsoup4
gunicorn
gunicorn==20.0.4
uvicorn
websockets>=8.1,<9.0
commands =
@@ -76,7 +76,7 @@ deps =
recommonmark>=0.5.0
docutils
pygments
gunicorn
gunicorn==20.0.4
commands =
make docs-test