Compare commits
49 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c3003413d3 | ||
|
|
fe3fdc5d83 | ||
|
|
b66fb6f9e8 | ||
|
|
bf6175fb20 | ||
|
|
7475897a03 | ||
|
|
58ca887be4 | ||
|
|
449bc417a3 | ||
|
|
262f89f2b6 | ||
|
|
38337446cf | ||
|
|
ac1331ea4c | ||
|
|
2b947e831f | ||
|
|
112715eb80 | ||
|
|
ea9cf365bc | ||
|
|
b9b3b4051a | ||
|
|
ecb6db29e6 | ||
|
|
6515dde64b | ||
|
|
01d2a2aa3c | ||
|
|
39e12accb8 | ||
|
|
39fe6ea5b1 | ||
|
|
fc4b7df088 | ||
|
|
35f28f7a64 | ||
|
|
614be40438 | ||
|
|
bde0428d0c | ||
|
|
63567c2ae4 | ||
|
|
ec10f337b6 | ||
|
|
d0f0e73e96 | ||
|
|
b4fe2c8a6b | ||
|
|
33da0771d1 | ||
|
|
75994cd915 | ||
|
|
c0839afdde | ||
|
|
5961da3f57 | ||
|
|
41f1809351 | ||
|
|
5fbdcb62e4 | ||
|
|
677b83e9f8 | ||
|
|
6a5c8becac | ||
|
|
fd23b99d60 | ||
|
|
634b586df3 | ||
|
|
4ca3e98082 | ||
|
|
d18a776964 | ||
|
|
d6b4d7d265 | ||
|
|
33ee4c21b3 | ||
|
|
a026cd7195 | ||
|
|
7b1bce8d90 | ||
|
|
217a7c5161 | ||
|
|
2949e3422d | ||
|
|
16ea99b0c0 | ||
|
|
19b84ce9f0 | ||
|
|
12521cd5b4 | ||
|
|
f41435fae3 |
@@ -17,6 +17,12 @@ environment:
|
||||
PYTHON_VERSION: "3.8.x"
|
||||
PYTHON_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
12
.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
|
||||
patreon: # Replace with a single Patreon username
|
||||
open_collective: sanic-org # Replace with a single Open Collective username
|
||||
ko_fi: # Replace with a single Ko-fi username
|
||||
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
||||
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
|
||||
liberapay: # Replace with a single Liberapay username
|
||||
issuehunt: # Replace with a single IssueHunt username
|
||||
otechie: # Replace with a single Otechie username
|
||||
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
|
||||
27
.travis.yml
27
.travis.yml
@@ -31,6 +31,16 @@ matrix:
|
||||
dist: xenial
|
||||
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
|
||||
|
||||
222
CHANGELOG.rst
222
CHANGELOG.rst
@@ -1,3 +1,115 @@
|
||||
Version 20.12.0
|
||||
===============
|
||||
|
||||
Features
|
||||
********
|
||||
|
||||
*
|
||||
`#1945 <https://github.com/huge-success/sanic/pull/1945>`_
|
||||
Static route more verbose if file not found
|
||||
|
||||
*
|
||||
`#1954 <https://github.com/huge-success/sanic/pull/1954>`_
|
||||
Fix static routes registration on a blueprint
|
||||
|
||||
*
|
||||
`#1961 <https://github.com/huge-success/sanic/pull/1961>`_
|
||||
Add Python 3.9 support
|
||||
|
||||
*
|
||||
`#1962 <https://github.com/huge-success/sanic/pull/1962>`_
|
||||
Sanic CLI upgrade
|
||||
|
||||
*
|
||||
`#1967 <https://github.com/huge-success/sanic/pull/1967>`_
|
||||
Update aiofile version requirements
|
||||
|
||||
*
|
||||
`#1969 <https://github.com/huge-success/sanic/pull/1969>`_
|
||||
Update multidict version requirements
|
||||
|
||||
*
|
||||
`#1970 <https://github.com/huge-success/sanic/pull/1970>`_
|
||||
Add py.typed file
|
||||
|
||||
*
|
||||
`#1972 <https://github.com/huge-success/sanic/pull/1972>`_
|
||||
Speed optimization in request handler
|
||||
|
||||
*
|
||||
`#1979 <https://github.com/huge-success/sanic/pull/1979>`_
|
||||
Add app registry and Sanic class level app retrieval
|
||||
|
||||
Bugfixes
|
||||
********
|
||||
|
||||
*
|
||||
`#1965 <https://github.com/huge-success/sanic/pull/1965>`_
|
||||
Fix Chunked Transport-Encoding in ASGI streaming response
|
||||
|
||||
Deprecations and Removals
|
||||
*************************
|
||||
|
||||
*
|
||||
`#1981 <https://github.com/huge-success/sanic/pull/1981>`_
|
||||
Cleanup and remove deprecated code
|
||||
|
||||
Developer infrastructure
|
||||
************************
|
||||
|
||||
*
|
||||
`#1956 <https://github.com/huge-success/sanic/pull/1956>`_
|
||||
Fix load module test
|
||||
|
||||
*
|
||||
`#1973 <https://github.com/huge-success/sanic/pull/1973>`_
|
||||
Transition Travis from .org to .com
|
||||
|
||||
*
|
||||
`#1986 <https://github.com/huge-success/sanic/pull/1986>`_
|
||||
Update tox requirements
|
||||
|
||||
Improved Documentation
|
||||
**********************
|
||||
|
||||
*
|
||||
`#1951 <https://github.com/huge-success/sanic/pull/1951>`_
|
||||
Documentation improvements
|
||||
|
||||
*
|
||||
`#1983 <https://github.com/huge-success/sanic/pull/1983>`_
|
||||
Remove duplicate contents in testing.rst
|
||||
|
||||
*
|
||||
`#1984 <https://github.com/huge-success/sanic/pull/1984>`_
|
||||
Fix typo in routing.rst
|
||||
|
||||
|
||||
Version 20.9.1
|
||||
===============
|
||||
|
||||
Bugfixes
|
||||
********
|
||||
|
||||
*
|
||||
`#1954 <https://github.com/huge-success/sanic/pull/1954>`_
|
||||
Fix static route registration on blueprints
|
||||
*
|
||||
`#1957 <https://github.com/huge-success/sanic/pull/1957>`_
|
||||
Removes duplicate headers in ASGI streaming body
|
||||
|
||||
|
||||
Version 19.12.3
|
||||
===============
|
||||
|
||||
Bugfixes
|
||||
********
|
||||
|
||||
*
|
||||
`#1959 <https://github.com/huge-success/sanic/pull/1959>`_
|
||||
Removes duplicate headers in ASGI streaming body
|
||||
|
||||
|
||||
Version 20.9.0
|
||||
===============
|
||||
|
||||
@@ -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:
|
||||
|
||||
|
||||
|
||||
@@ -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
1
changelogs/1970.misc.rst
Normal file
@@ -0,0 +1 @@
|
||||
Adds py.typed file to expose type information to other packages.
|
||||
@@ -60,3 +60,26 @@ Open the address `http://0.0.0.0:8000 <http://0.0.0.0:8000>`_ in your web browse
|
||||
the message *Hello world!*.
|
||||
|
||||
You now have a working Sanic server!
|
||||
|
||||
5. Application registry
|
||||
-----------------------
|
||||
|
||||
When you instantiate a Sanic instance, that can be retrieved at a later time from the Sanic app registry. This can be useful, for example, if you need to access your Sanic instance from a location where it is not otherwise accessible.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# ./path/to/server.py
|
||||
from sanic import Sanic
|
||||
|
||||
app = Sanic("my_awesome_server")
|
||||
|
||||
# ./path/to/somewhere_else.py
|
||||
from sanic import Sanic
|
||||
|
||||
app = Sanic.get_app("my_awesome_server")
|
||||
|
||||
If you call ``Sanic.get_app("non-existing")`` on an app that does not exist, it will raise ``SanicException`` by default. You can, instead, force the method to return a new instance of ``Sanic`` with that name:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
app = Sanic.get_app("my_awesome_server", force_create=True)
|
||||
|
||||
@@ -133,7 +133,7 @@ which allows the handler function to work with any of the HTTP methods in the li
|
||||
async def get_handler(request):
|
||||
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
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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():
|
||||
|
||||
@@ -10,6 +10,7 @@ from pathlib import Path
|
||||
|
||||
from sanic import Sanic, response
|
||||
|
||||
|
||||
app = Sanic(__name__)
|
||||
|
||||
|
||||
@@ -42,7 +43,9 @@ async def handler_file(request):
|
||||
|
||||
@app.route("/file_stream")
|
||||
async def handler_file_stream(request):
|
||||
return await response.file_stream(Path("../") / "setup.py", chunk_size=1024)
|
||||
return await response.file_stream(
|
||||
Path("../") / "setup.py", chunk_size=1024
|
||||
)
|
||||
|
||||
|
||||
@app.route("/stream", stream=True)
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -1 +1 @@
|
||||
__version__ = "20.9.1"
|
||||
__version__ = "20.12.1"
|
||||
|
||||
122
sanic/app.py
122
sanic/app.py
@@ -2,12 +2,11 @@ import logging
|
||||
import logging.config
|
||||
import 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):
|
||||
@@ -714,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.
|
||||
|
||||
@@ -900,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
|
||||
@@ -922,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)
|
||||
@@ -1032,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,
|
||||
@@ -1062,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)
|
||||
@@ -1092,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
|
||||
@@ -1155,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,
|
||||
@@ -1188,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
|
||||
@@ -1210,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
|
||||
@@ -1298,7 +1251,6 @@ class Sanic:
|
||||
loop=None,
|
||||
protocol=HttpProtocol,
|
||||
backlog=100,
|
||||
stop_event=None,
|
||||
register_sys_signals=True,
|
||||
run_async=False,
|
||||
auto_reload=False,
|
||||
@@ -1313,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. "
|
||||
@@ -1454,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.')
|
||||
|
||||
@@ -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(
|
||||
@@ -351,12 +357,22 @@ class ASGIApp:
|
||||
]
|
||||
|
||||
response.asgi = True
|
||||
|
||||
if "content-length" not in response.headers and not isinstance(
|
||||
response, StreamingHTTPResponse
|
||||
):
|
||||
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:
|
||||
|
||||
@@ -40,6 +40,7 @@ DEFAULT_CONFIG = {
|
||||
"PROXIES_COUNT": None,
|
||||
"FORWARDED_FOR_HEADER": "X-Forwarded-For",
|
||||
"FALLBACK_ERROR_FORMAT": "html",
|
||||
"REGISTER": True,
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -179,8 +179,8 @@ def abort(status_code, message=None):
|
||||
message appropriate for the given status code, unless provided.
|
||||
|
||||
: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
0
sanic/py.typed
Normal 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):
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import warnings
|
||||
|
||||
from functools import partial
|
||||
from mimetypes import guess_type
|
||||
from os import path
|
||||
@@ -26,6 +24,8 @@ class BaseHTTPResponse:
|
||||
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):
|
||||
@@ -45,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:
|
||||
@@ -100,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:
|
||||
@@ -147,22 +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):
|
||||
@@ -226,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
|
||||
)
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -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:
|
||||
|
||||
23
setup.py
23
setup.py
@@ -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.")
|
||||
|
||||
@@ -57,6 +61,7 @@ setup_kwargs = {
|
||||
),
|
||||
"long_description": long_description,
|
||||
"packages": ["sanic"],
|
||||
"package_data": {"sanic": ["py.typed"]},
|
||||
"platforms": "any",
|
||||
"python_requires": ">=3.6",
|
||||
"classifiers": [
|
||||
@@ -66,11 +71,14 @@ 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
|
||||
|
||||
@@ -78,24 +86,25 @@ requirements = [
|
||||
"httptools>=0.0.10",
|
||||
uvloop,
|
||||
ujson,
|
||||
"aiofiles>=0.3.0",
|
||||
"aiofiles>=0.6.0",
|
||||
"websockets>=8.1,<9.0",
|
||||
"multidict==5.0.0",
|
||||
"multidict>=5.0,<6.0",
|
||||
"httpx==0.15.4",
|
||||
]
|
||||
|
||||
tests_require = [
|
||||
"pytest==5.2.1",
|
||||
"multidict==5.0.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 = [
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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"]
|
||||
|
||||
@@ -735,6 +735,7 @@ def test_static_blueprint_name(app: Sanic, static_file_directory, file_name):
|
||||
_, response = app.test_client.get("/static/test.file/")
|
||||
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())
|
||||
@@ -745,7 +746,7 @@ def test_static_blueprintp_mw(app: Sanic, static_file_directory, file_name):
|
||||
|
||||
bp = Blueprint(name="test_mw", url_prefix="")
|
||||
|
||||
@bp.middleware('request')
|
||||
@bp.middleware("request")
|
||||
def bp_mw1(request):
|
||||
nonlocal triggered
|
||||
triggered = True
|
||||
@@ -754,7 +755,7 @@ def test_static_blueprintp_mw(app: Sanic, static_file_directory, file_name):
|
||||
"/test.file",
|
||||
get_file_path(static_file_directory, file_name),
|
||||
strict_slashes=True,
|
||||
name="static"
|
||||
name="static",
|
||||
)
|
||||
|
||||
app.blueprint(bp)
|
||||
@@ -824,21 +825,6 @@ def test_duplicate_blueprint(app):
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("debug", [True, False, None])
|
||||
def test_register_blueprint(app, debug):
|
||||
bp = Blueprint("bp")
|
||||
|
||||
app.debug = debug
|
||||
with pytest.warns(DeprecationWarning) as record:
|
||||
app.register_blueprint(bp)
|
||||
|
||||
assert record[0].message.args[0] == (
|
||||
"Use of register_blueprint will be deprecated in "
|
||||
"version 1.0. Please use the blueprint method"
|
||||
" instead"
|
||||
)
|
||||
|
||||
|
||||
def test_strict_slashes_behavior_adoption(app):
|
||||
app.strict_slashes = True
|
||||
|
||||
|
||||
@@ -20,7 +20,9 @@ def test_load_module_from_file_location(loaded_module_from_file_location):
|
||||
|
||||
|
||||
@pytest.mark.dependency(depends=["test_load_module_from_file_location"])
|
||||
def test_loaded_module_from_file_location_name(loaded_module_from_file_location,):
|
||||
def test_loaded_module_from_file_location_name(
|
||||
loaded_module_from_file_location,
|
||||
):
|
||||
name = loaded_module_from_file_location.__name__
|
||||
if "C:\\" in name:
|
||||
name = name.split("\\")[-1]
|
||||
|
||||
@@ -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):
|
||||
@@ -238,7 +239,7 @@ def test_chunked_streaming_returns_correct_content(streaming_app):
|
||||
@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 == "4\r\nfoo,\r\n3\r\nbar\r\n0\r\n\r\n"
|
||||
assert response.text == "foo,bar"
|
||||
|
||||
|
||||
def test_non_chunked_streaming_adds_correct_headers(non_chunked_streaming_app):
|
||||
@@ -624,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
16
tox.ini
@@ -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
|
||||
chardet==3.*
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user