Merge pull request #6 from channelcat/master
merge upstreaming master branch
This commit is contained in:
commit
55cb371569
28
README.rst
28
README.rst
|
@ -11,34 +11,6 @@ Sanic is developed `on GitHub <https://github.com/channelcat/sanic/>`_. Contribu
|
||||||
|
|
||||||
If you have a project that utilizes Sanic make sure to comment on the `issue <https://github.com/channelcat/sanic/issues/396>`_ that we use to track those projects!
|
If you have a project that utilizes Sanic make sure to comment on the `issue <https://github.com/channelcat/sanic/issues/396>`_ that we use to track those projects!
|
||||||
|
|
||||||
Benchmarks
|
|
||||||
----------
|
|
||||||
|
|
||||||
All tests were run on an AWS medium instance running ubuntu, using 1
|
|
||||||
process. Each script delivered a small JSON response and was tested with
|
|
||||||
wrk using 100 connections. Pypy was tested for Falcon and Flask but did
|
|
||||||
not speed up requests.
|
|
||||||
|
|
||||||
+-----------+-----------------------+----------------+---------------+
|
|
||||||
| Server | Implementation | Requests/sec | Avg Latency |
|
|
||||||
+===========+=======================+================+===============+
|
|
||||||
| Sanic | Python 3.5 + uvloop | 33,342 | 2.96ms |
|
|
||||||
+-----------+-----------------------+----------------+---------------+
|
|
||||||
| Wheezy | gunicorn + meinheld | 20,244 | 4.94ms |
|
|
||||||
+-----------+-----------------------+----------------+---------------+
|
|
||||||
| Falcon | gunicorn + meinheld | 18,972 | 5.27ms |
|
|
||||||
+-----------+-----------------------+----------------+---------------+
|
|
||||||
| Bottle | gunicorn + meinheld | 13,596 | 7.36ms |
|
|
||||||
+-----------+-----------------------+----------------+---------------+
|
|
||||||
| Flask | gunicorn + meinheld | 4,988 | 20.08ms |
|
|
||||||
+-----------+-----------------------+----------------+---------------+
|
|
||||||
| Kyoukai | Python 3.5 + uvloop | 3,889 | 27.44ms |
|
|
||||||
+-----------+-----------------------+----------------+---------------+
|
|
||||||
| Aiohttp | Python 3.5 + uvloop | 2,979 | 33.42ms |
|
|
||||||
+-----------+-----------------------+----------------+---------------+
|
|
||||||
| Tornado | Python 3.5 | 2,138 | 46.66ms |
|
|
||||||
+-----------+-----------------------+----------------+---------------+
|
|
||||||
|
|
||||||
Hello World Example
|
Hello World Example
|
||||||
-------------------
|
-------------------
|
||||||
|
|
||||||
|
|
225
docs/Makefile
225
docs/Makefile
|
@ -1,20 +1,225 @@
|
||||||
# Minimal makefile for Sphinx documentation
|
# Makefile for Sphinx documentation
|
||||||
#
|
#
|
||||||
|
|
||||||
# You can set these variables from the command line.
|
# You can set these variables from the command line.
|
||||||
SPHINXOPTS =
|
SPHINXOPTS =
|
||||||
SPHINXBUILD = sphinx-build
|
SPHINXBUILD = sphinx-build
|
||||||
SPHINXPROJ = Sanic
|
PAPER =
|
||||||
SOURCEDIR = .
|
|
||||||
BUILDDIR = _build
|
BUILDDIR = _build
|
||||||
|
|
||||||
# Put it first so that "make" without argument is like "make help".
|
# Internal variables.
|
||||||
|
PAPEROPT_a4 = -D latex_paper_size=a4
|
||||||
|
PAPEROPT_letter = -D latex_paper_size=letter
|
||||||
|
ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
|
||||||
|
# the i18n builder cannot share the environment and doctrees with the others
|
||||||
|
I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
|
||||||
|
|
||||||
|
.PHONY: help
|
||||||
help:
|
help:
|
||||||
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
@echo "Please use \`make <target>' where <target> is one of"
|
||||||
|
@echo " html to make standalone HTML files"
|
||||||
|
@echo " dirhtml to make HTML files named index.html in directories"
|
||||||
|
@echo " singlehtml to make a single large HTML file"
|
||||||
|
@echo " pickle to make pickle files"
|
||||||
|
@echo " json to make JSON files"
|
||||||
|
@echo " htmlhelp to make HTML files and a HTML help project"
|
||||||
|
@echo " qthelp to make HTML files and a qthelp project"
|
||||||
|
@echo " applehelp to make an Apple Help Book"
|
||||||
|
@echo " devhelp to make HTML files and a Devhelp project"
|
||||||
|
@echo " epub to make an epub"
|
||||||
|
@echo " epub3 to make an epub3"
|
||||||
|
@echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
|
||||||
|
@echo " latexpdf to make LaTeX files and run them through pdflatex"
|
||||||
|
@echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx"
|
||||||
|
@echo " text to make text files"
|
||||||
|
@echo " man to make manual pages"
|
||||||
|
@echo " texinfo to make Texinfo files"
|
||||||
|
@echo " info to make Texinfo files and run them through makeinfo"
|
||||||
|
@echo " gettext to make PO message catalogs"
|
||||||
|
@echo " changes to make an overview of all changed/added/deprecated items"
|
||||||
|
@echo " xml to make Docutils-native XML files"
|
||||||
|
@echo " pseudoxml to make pseudoxml-XML files for display purposes"
|
||||||
|
@echo " linkcheck to check all external links for integrity"
|
||||||
|
@echo " doctest to run all doctests embedded in the documentation (if enabled)"
|
||||||
|
@echo " coverage to run coverage check of the documentation (if enabled)"
|
||||||
|
@echo " dummy to check syntax errors of document sources"
|
||||||
|
|
||||||
.PHONY: help Makefile
|
.PHONY: clean
|
||||||
|
clean:
|
||||||
|
rm -rf $(BUILDDIR)/*
|
||||||
|
|
||||||
# Catch-all target: route all unknown targets to Sphinx using the new
|
.PHONY: html
|
||||||
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
|
html:
|
||||||
%: Makefile
|
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
|
||||||
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
@echo
|
||||||
|
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
|
||||||
|
|
||||||
|
.PHONY: dirhtml
|
||||||
|
dirhtml:
|
||||||
|
$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
|
||||||
|
@echo
|
||||||
|
@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
|
||||||
|
|
||||||
|
.PHONY: singlehtml
|
||||||
|
singlehtml:
|
||||||
|
$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
|
||||||
|
@echo
|
||||||
|
@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
|
||||||
|
|
||||||
|
.PHONY: pickle
|
||||||
|
pickle:
|
||||||
|
$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
|
||||||
|
@echo
|
||||||
|
@echo "Build finished; now you can process the pickle files."
|
||||||
|
|
||||||
|
.PHONY: json
|
||||||
|
json:
|
||||||
|
$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
|
||||||
|
@echo
|
||||||
|
@echo "Build finished; now you can process the JSON files."
|
||||||
|
|
||||||
|
.PHONY: htmlhelp
|
||||||
|
htmlhelp:
|
||||||
|
$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
|
||||||
|
@echo
|
||||||
|
@echo "Build finished; now you can run HTML Help Workshop with the" \
|
||||||
|
".hhp project file in $(BUILDDIR)/htmlhelp."
|
||||||
|
|
||||||
|
.PHONY: qthelp
|
||||||
|
qthelp:
|
||||||
|
$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
|
||||||
|
@echo
|
||||||
|
@echo "Build finished; now you can run "qcollectiongenerator" with the" \
|
||||||
|
".qhcp project file in $(BUILDDIR)/qthelp, like this:"
|
||||||
|
@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/aiographite.qhcp"
|
||||||
|
@echo "To view the help file:"
|
||||||
|
@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/aiographite.qhc"
|
||||||
|
|
||||||
|
.PHONY: applehelp
|
||||||
|
applehelp:
|
||||||
|
$(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp
|
||||||
|
@echo
|
||||||
|
@echo "Build finished. The help book is in $(BUILDDIR)/applehelp."
|
||||||
|
@echo "N.B. You won't be able to view it unless you put it in" \
|
||||||
|
"~/Library/Documentation/Help or install it in your application" \
|
||||||
|
"bundle."
|
||||||
|
|
||||||
|
.PHONY: devhelp
|
||||||
|
devhelp:
|
||||||
|
$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
|
||||||
|
@echo
|
||||||
|
@echo "Build finished."
|
||||||
|
@echo "To view the help file:"
|
||||||
|
@echo "# mkdir -p $$HOME/.local/share/devhelp/aiographite"
|
||||||
|
@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/aiographite"
|
||||||
|
@echo "# devhelp"
|
||||||
|
|
||||||
|
.PHONY: epub
|
||||||
|
epub:
|
||||||
|
$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
|
||||||
|
@echo
|
||||||
|
@echo "Build finished. The epub file is in $(BUILDDIR)/epub."
|
||||||
|
|
||||||
|
.PHONY: epub3
|
||||||
|
epub3:
|
||||||
|
$(SPHINXBUILD) -b epub3 $(ALLSPHINXOPTS) $(BUILDDIR)/epub3
|
||||||
|
@echo
|
||||||
|
@echo "Build finished. The epub3 file is in $(BUILDDIR)/epub3."
|
||||||
|
|
||||||
|
.PHONY: latex
|
||||||
|
latex:
|
||||||
|
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
|
||||||
|
@echo
|
||||||
|
@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
|
||||||
|
@echo "Run \`make' in that directory to run these through (pdf)latex" \
|
||||||
|
"(use \`make latexpdf' here to do that automatically)."
|
||||||
|
|
||||||
|
.PHONY: latexpdf
|
||||||
|
latexpdf:
|
||||||
|
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
|
||||||
|
@echo "Running LaTeX files through pdflatex..."
|
||||||
|
$(MAKE) -C $(BUILDDIR)/latex all-pdf
|
||||||
|
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
|
||||||
|
|
||||||
|
.PHONY: latexpdfja
|
||||||
|
latexpdfja:
|
||||||
|
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
|
||||||
|
@echo "Running LaTeX files through platex and dvipdfmx..."
|
||||||
|
$(MAKE) -C $(BUILDDIR)/latex all-pdf-ja
|
||||||
|
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
|
||||||
|
|
||||||
|
.PHONY: text
|
||||||
|
text:
|
||||||
|
$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
|
||||||
|
@echo
|
||||||
|
@echo "Build finished. The text files are in $(BUILDDIR)/text."
|
||||||
|
|
||||||
|
.PHONY: man
|
||||||
|
man:
|
||||||
|
$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
|
||||||
|
@echo
|
||||||
|
@echo "Build finished. The manual pages are in $(BUILDDIR)/man."
|
||||||
|
|
||||||
|
.PHONY: texinfo
|
||||||
|
texinfo:
|
||||||
|
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
|
||||||
|
@echo
|
||||||
|
@echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
|
||||||
|
@echo "Run \`make' in that directory to run these through makeinfo" \
|
||||||
|
"(use \`make info' here to do that automatically)."
|
||||||
|
|
||||||
|
.PHONY: info
|
||||||
|
info:
|
||||||
|
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
|
||||||
|
@echo "Running Texinfo files through makeinfo..."
|
||||||
|
make -C $(BUILDDIR)/texinfo info
|
||||||
|
@echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
|
||||||
|
|
||||||
|
.PHONY: gettext
|
||||||
|
gettext:
|
||||||
|
$(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
|
||||||
|
@echo
|
||||||
|
@echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
|
||||||
|
|
||||||
|
.PHONY: changes
|
||||||
|
changes:
|
||||||
|
$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
|
||||||
|
@echo
|
||||||
|
@echo "The overview file is in $(BUILDDIR)/changes."
|
||||||
|
|
||||||
|
.PHONY: linkcheck
|
||||||
|
linkcheck:
|
||||||
|
$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
|
||||||
|
@echo
|
||||||
|
@echo "Link check complete; look for any errors in the above output " \
|
||||||
|
"or in $(BUILDDIR)/linkcheck/output.txt."
|
||||||
|
|
||||||
|
.PHONY: doctest
|
||||||
|
doctest:
|
||||||
|
$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
|
||||||
|
@echo "Testing of doctests in the sources finished, look at the " \
|
||||||
|
"results in $(BUILDDIR)/doctest/output.txt."
|
||||||
|
|
||||||
|
.PHONY: coverage
|
||||||
|
coverage:
|
||||||
|
$(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage
|
||||||
|
@echo "Testing of coverage in the sources finished, look at the " \
|
||||||
|
"results in $(BUILDDIR)/coverage/python.txt."
|
||||||
|
|
||||||
|
.PHONY: xml
|
||||||
|
xml:
|
||||||
|
$(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml
|
||||||
|
@echo
|
||||||
|
@echo "Build finished. The XML files are in $(BUILDDIR)/xml."
|
||||||
|
|
||||||
|
.PHONY: pseudoxml
|
||||||
|
pseudoxml:
|
||||||
|
$(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml
|
||||||
|
@echo
|
||||||
|
@echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml."
|
||||||
|
|
||||||
|
.PHONY: dummy
|
||||||
|
dummy:
|
||||||
|
$(SPHINXBUILD) -b dummy $(ALLSPHINXOPTS) $(BUILDDIR)/dummy
|
||||||
|
@echo
|
||||||
|
@echo "Build finished. Dummy builder generates no files."
|
||||||
|
|
12
docs/conf.py
12
docs/conf.py
|
@ -13,6 +13,9 @@ import sys
|
||||||
# Add support for Markdown documentation using Recommonmark
|
# Add support for Markdown documentation using Recommonmark
|
||||||
from recommonmark.parser import CommonMarkParser
|
from recommonmark.parser import CommonMarkParser
|
||||||
|
|
||||||
|
# Add support for auto-doc
|
||||||
|
from recommonmark.transform import AutoStructify
|
||||||
|
|
||||||
# Ensure that sanic is present in the path, to allow sphinx-apidoc to
|
# Ensure that sanic is present in the path, to allow sphinx-apidoc to
|
||||||
# autogenerate documentation from docstrings
|
# autogenerate documentation from docstrings
|
||||||
root_directory = os.path.dirname(os.getcwd())
|
root_directory = os.path.dirname(os.getcwd())
|
||||||
|
@ -140,3 +143,12 @@ epub_exclude_files = ['search.html']
|
||||||
# -- Custom Settings -------------------------------------------------------
|
# -- Custom Settings -------------------------------------------------------
|
||||||
|
|
||||||
suppress_warnings = ['image.nonlocal_uri']
|
suppress_warnings = ['image.nonlocal_uri']
|
||||||
|
|
||||||
|
|
||||||
|
# app setup hook
|
||||||
|
def setup(app):
|
||||||
|
app.add_config_value('recommonmark_config', {
|
||||||
|
'enable_eval_rst': True,
|
||||||
|
'enable_auto_doc_ref': True,
|
||||||
|
}, True)
|
||||||
|
app.add_transform(AutoStructify)
|
||||||
|
|
|
@ -16,6 +16,7 @@ Guides
|
||||||
sanic/blueprints
|
sanic/blueprints
|
||||||
sanic/config
|
sanic/config
|
||||||
sanic/cookies
|
sanic/cookies
|
||||||
|
sanic/decorators
|
||||||
sanic/streaming
|
sanic/streaming
|
||||||
sanic/class_based_views
|
sanic/class_based_views
|
||||||
sanic/custom_protocol
|
sanic/custom_protocol
|
||||||
|
@ -25,6 +26,7 @@ Guides
|
||||||
sanic/deploying
|
sanic/deploying
|
||||||
sanic/extensions
|
sanic/extensions
|
||||||
sanic/contributing
|
sanic/contributing
|
||||||
|
sanic/api_reference
|
||||||
|
|
||||||
|
|
||||||
Module Documentation
|
Module Documentation
|
||||||
|
@ -33,4 +35,5 @@ Module Documentation
|
||||||
.. toctree::
|
.. toctree::
|
||||||
|
|
||||||
* :ref:`genindex`
|
* :ref:`genindex`
|
||||||
|
* :ref:`modindex`
|
||||||
* :ref:`search`
|
* :ref:`search`
|
||||||
|
|
265
docs/make.bat
265
docs/make.bat
|
@ -1,19 +1,64 @@
|
||||||
@ECHO OFF
|
@ECHO OFF
|
||||||
|
|
||||||
pushd %~dp0
|
|
||||||
|
|
||||||
REM Command file for Sphinx documentation
|
REM Command file for Sphinx documentation
|
||||||
|
|
||||||
if "%SPHINXBUILD%" == "" (
|
if "%SPHINXBUILD%" == "" (
|
||||||
set SPHINXBUILD=sphinx-build
|
set SPHINXBUILD=sphinx-build
|
||||||
)
|
)
|
||||||
set SOURCEDIR=.
|
|
||||||
set BUILDDIR=_build
|
set BUILDDIR=_build
|
||||||
set SPHINXPROJ=Sanic
|
set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% .
|
||||||
|
set I18NSPHINXOPTS=%SPHINXOPTS% .
|
||||||
|
if NOT "%PAPER%" == "" (
|
||||||
|
set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS%
|
||||||
|
set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS%
|
||||||
|
)
|
||||||
|
|
||||||
if "%1" == "" goto help
|
if "%1" == "" goto help
|
||||||
|
|
||||||
%SPHINXBUILD% >NUL 2>NUL
|
if "%1" == "help" (
|
||||||
|
:help
|
||||||
|
echo.Please use `make ^<target^>` where ^<target^> is one of
|
||||||
|
echo. html to make standalone HTML files
|
||||||
|
echo. dirhtml to make HTML files named index.html in directories
|
||||||
|
echo. singlehtml to make a single large HTML file
|
||||||
|
echo. pickle to make pickle files
|
||||||
|
echo. json to make JSON files
|
||||||
|
echo. htmlhelp to make HTML files and a HTML help project
|
||||||
|
echo. qthelp to make HTML files and a qthelp project
|
||||||
|
echo. devhelp to make HTML files and a Devhelp project
|
||||||
|
echo. epub to make an epub
|
||||||
|
echo. epub3 to make an epub3
|
||||||
|
echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter
|
||||||
|
echo. text to make text files
|
||||||
|
echo. man to make manual pages
|
||||||
|
echo. texinfo to make Texinfo files
|
||||||
|
echo. gettext to make PO message catalogs
|
||||||
|
echo. changes to make an overview over all changed/added/deprecated items
|
||||||
|
echo. xml to make Docutils-native XML files
|
||||||
|
echo. pseudoxml to make pseudoxml-XML files for display purposes
|
||||||
|
echo. linkcheck to check all external links for integrity
|
||||||
|
echo. doctest to run all doctests embedded in the documentation if enabled
|
||||||
|
echo. coverage to run coverage check of the documentation if enabled
|
||||||
|
echo. dummy to check syntax errors of document sources
|
||||||
|
goto end
|
||||||
|
)
|
||||||
|
|
||||||
|
if "%1" == "clean" (
|
||||||
|
for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i
|
||||||
|
del /q /s %BUILDDIR%\*
|
||||||
|
goto end
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
REM Check if sphinx-build is available and fallback to Python version if any
|
||||||
|
%SPHINXBUILD% 1>NUL 2>NUL
|
||||||
|
if errorlevel 9009 goto sphinx_python
|
||||||
|
goto sphinx_ok
|
||||||
|
|
||||||
|
:sphinx_python
|
||||||
|
|
||||||
|
set SPHINXBUILD=python -m sphinx.__init__
|
||||||
|
%SPHINXBUILD% 2> nul
|
||||||
if errorlevel 9009 (
|
if errorlevel 9009 (
|
||||||
echo.
|
echo.
|
||||||
echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
|
echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
|
||||||
|
@ -26,11 +71,211 @@ if errorlevel 9009 (
|
||||||
exit /b 1
|
exit /b 1
|
||||||
)
|
)
|
||||||
|
|
||||||
%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS%
|
:sphinx_ok
|
||||||
goto end
|
|
||||||
|
|
||||||
:help
|
|
||||||
%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS%
|
if "%1" == "html" (
|
||||||
|
%SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html
|
||||||
|
if errorlevel 1 exit /b 1
|
||||||
|
echo.
|
||||||
|
echo.Build finished. The HTML pages are in %BUILDDIR%/html.
|
||||||
|
goto end
|
||||||
|
)
|
||||||
|
|
||||||
|
if "%1" == "dirhtml" (
|
||||||
|
%SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml
|
||||||
|
if errorlevel 1 exit /b 1
|
||||||
|
echo.
|
||||||
|
echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml.
|
||||||
|
goto end
|
||||||
|
)
|
||||||
|
|
||||||
|
if "%1" == "singlehtml" (
|
||||||
|
%SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml
|
||||||
|
if errorlevel 1 exit /b 1
|
||||||
|
echo.
|
||||||
|
echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml.
|
||||||
|
goto end
|
||||||
|
)
|
||||||
|
|
||||||
|
if "%1" == "pickle" (
|
||||||
|
%SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle
|
||||||
|
if errorlevel 1 exit /b 1
|
||||||
|
echo.
|
||||||
|
echo.Build finished; now you can process the pickle files.
|
||||||
|
goto end
|
||||||
|
)
|
||||||
|
|
||||||
|
if "%1" == "json" (
|
||||||
|
%SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json
|
||||||
|
if errorlevel 1 exit /b 1
|
||||||
|
echo.
|
||||||
|
echo.Build finished; now you can process the JSON files.
|
||||||
|
goto end
|
||||||
|
)
|
||||||
|
|
||||||
|
if "%1" == "htmlhelp" (
|
||||||
|
%SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp
|
||||||
|
if errorlevel 1 exit /b 1
|
||||||
|
echo.
|
||||||
|
echo.Build finished; now you can run HTML Help Workshop with the ^
|
||||||
|
.hhp project file in %BUILDDIR%/htmlhelp.
|
||||||
|
goto end
|
||||||
|
)
|
||||||
|
|
||||||
|
if "%1" == "qthelp" (
|
||||||
|
%SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp
|
||||||
|
if errorlevel 1 exit /b 1
|
||||||
|
echo.
|
||||||
|
echo.Build finished; now you can run "qcollectiongenerator" with the ^
|
||||||
|
.qhcp project file in %BUILDDIR%/qthelp, like this:
|
||||||
|
echo.^> qcollectiongenerator %BUILDDIR%\qthelp\aiographite.qhcp
|
||||||
|
echo.To view the help file:
|
||||||
|
echo.^> assistant -collectionFile %BUILDDIR%\qthelp\aiographite.ghc
|
||||||
|
goto end
|
||||||
|
)
|
||||||
|
|
||||||
|
if "%1" == "devhelp" (
|
||||||
|
%SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp
|
||||||
|
if errorlevel 1 exit /b 1
|
||||||
|
echo.
|
||||||
|
echo.Build finished.
|
||||||
|
goto end
|
||||||
|
)
|
||||||
|
|
||||||
|
if "%1" == "epub" (
|
||||||
|
%SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub
|
||||||
|
if errorlevel 1 exit /b 1
|
||||||
|
echo.
|
||||||
|
echo.Build finished. The epub file is in %BUILDDIR%/epub.
|
||||||
|
goto end
|
||||||
|
)
|
||||||
|
|
||||||
|
if "%1" == "epub3" (
|
||||||
|
%SPHINXBUILD% -b epub3 %ALLSPHINXOPTS% %BUILDDIR%/epub3
|
||||||
|
if errorlevel 1 exit /b 1
|
||||||
|
echo.
|
||||||
|
echo.Build finished. The epub3 file is in %BUILDDIR%/epub3.
|
||||||
|
goto end
|
||||||
|
)
|
||||||
|
|
||||||
|
if "%1" == "latex" (
|
||||||
|
%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
|
||||||
|
if errorlevel 1 exit /b 1
|
||||||
|
echo.
|
||||||
|
echo.Build finished; the LaTeX files are in %BUILDDIR%/latex.
|
||||||
|
goto end
|
||||||
|
)
|
||||||
|
|
||||||
|
if "%1" == "latexpdf" (
|
||||||
|
%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
|
||||||
|
cd %BUILDDIR%/latex
|
||||||
|
make all-pdf
|
||||||
|
cd %~dp0
|
||||||
|
echo.
|
||||||
|
echo.Build finished; the PDF files are in %BUILDDIR%/latex.
|
||||||
|
goto end
|
||||||
|
)
|
||||||
|
|
||||||
|
if "%1" == "latexpdfja" (
|
||||||
|
%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
|
||||||
|
cd %BUILDDIR%/latex
|
||||||
|
make all-pdf-ja
|
||||||
|
cd %~dp0
|
||||||
|
echo.
|
||||||
|
echo.Build finished; the PDF files are in %BUILDDIR%/latex.
|
||||||
|
goto end
|
||||||
|
)
|
||||||
|
|
||||||
|
if "%1" == "text" (
|
||||||
|
%SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text
|
||||||
|
if errorlevel 1 exit /b 1
|
||||||
|
echo.
|
||||||
|
echo.Build finished. The text files are in %BUILDDIR%/text.
|
||||||
|
goto end
|
||||||
|
)
|
||||||
|
|
||||||
|
if "%1" == "man" (
|
||||||
|
%SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man
|
||||||
|
if errorlevel 1 exit /b 1
|
||||||
|
echo.
|
||||||
|
echo.Build finished. The manual pages are in %BUILDDIR%/man.
|
||||||
|
goto end
|
||||||
|
)
|
||||||
|
|
||||||
|
if "%1" == "texinfo" (
|
||||||
|
%SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo
|
||||||
|
if errorlevel 1 exit /b 1
|
||||||
|
echo.
|
||||||
|
echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo.
|
||||||
|
goto end
|
||||||
|
)
|
||||||
|
|
||||||
|
if "%1" == "gettext" (
|
||||||
|
%SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale
|
||||||
|
if errorlevel 1 exit /b 1
|
||||||
|
echo.
|
||||||
|
echo.Build finished. The message catalogs are in %BUILDDIR%/locale.
|
||||||
|
goto end
|
||||||
|
)
|
||||||
|
|
||||||
|
if "%1" == "changes" (
|
||||||
|
%SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes
|
||||||
|
if errorlevel 1 exit /b 1
|
||||||
|
echo.
|
||||||
|
echo.The overview file is in %BUILDDIR%/changes.
|
||||||
|
goto end
|
||||||
|
)
|
||||||
|
|
||||||
|
if "%1" == "linkcheck" (
|
||||||
|
%SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck
|
||||||
|
if errorlevel 1 exit /b 1
|
||||||
|
echo.
|
||||||
|
echo.Link check complete; look for any errors in the above output ^
|
||||||
|
or in %BUILDDIR%/linkcheck/output.txt.
|
||||||
|
goto end
|
||||||
|
)
|
||||||
|
|
||||||
|
if "%1" == "doctest" (
|
||||||
|
%SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest
|
||||||
|
if errorlevel 1 exit /b 1
|
||||||
|
echo.
|
||||||
|
echo.Testing of doctests in the sources finished, look at the ^
|
||||||
|
results in %BUILDDIR%/doctest/output.txt.
|
||||||
|
goto end
|
||||||
|
)
|
||||||
|
|
||||||
|
if "%1" == "coverage" (
|
||||||
|
%SPHINXBUILD% -b coverage %ALLSPHINXOPTS% %BUILDDIR%/coverage
|
||||||
|
if errorlevel 1 exit /b 1
|
||||||
|
echo.
|
||||||
|
echo.Testing of coverage in the sources finished, look at the ^
|
||||||
|
results in %BUILDDIR%/coverage/python.txt.
|
||||||
|
goto end
|
||||||
|
)
|
||||||
|
|
||||||
|
if "%1" == "xml" (
|
||||||
|
%SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml
|
||||||
|
if errorlevel 1 exit /b 1
|
||||||
|
echo.
|
||||||
|
echo.Build finished. The XML files are in %BUILDDIR%/xml.
|
||||||
|
goto end
|
||||||
|
)
|
||||||
|
|
||||||
|
if "%1" == "pseudoxml" (
|
||||||
|
%SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml
|
||||||
|
if errorlevel 1 exit /b 1
|
||||||
|
echo.
|
||||||
|
echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml.
|
||||||
|
goto end
|
||||||
|
)
|
||||||
|
|
||||||
|
if "%1" == "dummy" (
|
||||||
|
%SPHINXBUILD% -b dummy %ALLSPHINXOPTS% %BUILDDIR%/dummy
|
||||||
|
if errorlevel 1 exit /b 1
|
||||||
|
echo.
|
||||||
|
echo.Build finished. Dummy builder generates no files.
|
||||||
|
goto end
|
||||||
|
)
|
||||||
|
|
||||||
:end
|
:end
|
||||||
popd
|
|
||||||
|
|
150
docs/sanic/api_reference.rst
Normal file
150
docs/sanic/api_reference.rst
Normal file
|
@ -0,0 +1,150 @@
|
||||||
|
API Reference
|
||||||
|
=============
|
||||||
|
|
||||||
|
Submodules
|
||||||
|
----------
|
||||||
|
|
||||||
|
sanic.app module
|
||||||
|
----------------
|
||||||
|
|
||||||
|
.. automodule:: sanic.app
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
|
sanic.blueprints module
|
||||||
|
-----------------------
|
||||||
|
|
||||||
|
.. automodule:: sanic.blueprints
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
|
sanic.config module
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
.. automodule:: sanic.config
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
|
sanic.constants module
|
||||||
|
----------------------
|
||||||
|
|
||||||
|
.. automodule:: sanic.constants
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
|
sanic.cookies module
|
||||||
|
--------------------
|
||||||
|
|
||||||
|
.. automodule:: sanic.cookies
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
|
sanic.exceptions module
|
||||||
|
-----------------------
|
||||||
|
|
||||||
|
.. automodule:: sanic.exceptions
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
|
sanic.handlers module
|
||||||
|
---------------------
|
||||||
|
|
||||||
|
.. automodule:: sanic.handlers
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
|
sanic.log module
|
||||||
|
----------------
|
||||||
|
|
||||||
|
.. automodule:: sanic.log
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
|
sanic.request module
|
||||||
|
--------------------
|
||||||
|
|
||||||
|
.. automodule:: sanic.request
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
|
sanic.response module
|
||||||
|
---------------------
|
||||||
|
|
||||||
|
.. automodule:: sanic.response
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
|
sanic.router module
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
.. automodule:: sanic.router
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
|
sanic.server module
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
.. automodule:: sanic.server
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
|
sanic.static module
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
.. automodule:: sanic.static
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
|
sanic.testing module
|
||||||
|
--------------------
|
||||||
|
|
||||||
|
.. automodule:: sanic.testing
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
|
sanic.views module
|
||||||
|
------------------
|
||||||
|
|
||||||
|
.. automodule:: sanic.views
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
|
sanic.websocket module
|
||||||
|
----------------------
|
||||||
|
|
||||||
|
.. automodule:: sanic.websocket
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
|
sanic.worker module
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
.. automodule:: sanic.worker
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
|
|
||||||
|
Module contents
|
||||||
|
---------------
|
||||||
|
|
||||||
|
.. automodule:: sanic
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
|
@ -169,7 +169,7 @@ app.run(host='0.0.0.0', port=8000, debug=True)
|
||||||
If you wish to generate a URL for a route inside of a blueprint, remember that the endpoint name
|
If you wish to generate a URL for a route inside of a blueprint, remember that the endpoint name
|
||||||
takes the format `<blueprint_name>.<handler_name>`. For example:
|
takes the format `<blueprint_name>.<handler_name>`. For example:
|
||||||
|
|
||||||
```
|
```python
|
||||||
@blueprint_v1.route('/')
|
@blueprint_v1.route('/')
|
||||||
async def root(request):
|
async def root(request):
|
||||||
url = app.url_for('v1.post_handler', post_id=5) # --> '/v1/post/5'
|
url = app.url_for('v1.post_handler', post_id=5) # --> '/v1/post/5'
|
||||||
|
|
|
@ -214,4 +214,3 @@ and `recv` methods to send and receive data respectively.
|
||||||
|
|
||||||
WebSocket support requires the [websockets](https://github.com/aaugustin/websockets)
|
WebSocket support requires the [websockets](https://github.com/aaugustin/websockets)
|
||||||
package by Aymeric Augustin.
|
package by Aymeric Augustin.
|
||||||
|
|
||||||
|
|
50
docs/sanic/versioning.md
Normal file
50
docs/sanic/versioning.md
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
# Versioning
|
||||||
|
|
||||||
|
You can pass the `version` keyword to the route decorators, or to a blueprint initializer. It will result in the `v{version}` url prefix where `{version}` is the version number.
|
||||||
|
|
||||||
|
## Per route
|
||||||
|
|
||||||
|
You can pass a version number to the routes directly.
|
||||||
|
|
||||||
|
```python
|
||||||
|
from sanic import response
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/text', verion=1)
|
||||||
|
def handle_request(request):
|
||||||
|
return response.text('Hello world! Version 1')
|
||||||
|
|
||||||
|
@app.route('/text', verion=2)
|
||||||
|
def handle_request(request):
|
||||||
|
return response.text('Hello world! Version 2')
|
||||||
|
|
||||||
|
app.run(port=80)
|
||||||
|
```
|
||||||
|
|
||||||
|
Then with curl:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl localhost/v1/text
|
||||||
|
curl localhost/v2/text
|
||||||
|
```
|
||||||
|
|
||||||
|
## Global blueprint version
|
||||||
|
|
||||||
|
You can also pass a version number to the blueprint, which will apply to all routes.
|
||||||
|
|
||||||
|
```python
|
||||||
|
from sanic import response
|
||||||
|
from sanic.blueprints import Blueprint
|
||||||
|
|
||||||
|
bp = Blueprint('test', version=1)
|
||||||
|
|
||||||
|
@bp.route('/html')
|
||||||
|
def handle_request(request):
|
||||||
|
return response.html('<p>Hello world!</p>')
|
||||||
|
```
|
||||||
|
|
||||||
|
Then with curl:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl localhost/v1/html
|
||||||
|
```
|
44
sanic/app.py
44
sanic/app.py
|
@ -113,7 +113,7 @@ class Sanic:
|
||||||
|
|
||||||
# Decorator
|
# Decorator
|
||||||
def route(self, uri, methods=frozenset({'GET'}), host=None,
|
def route(self, uri, methods=frozenset({'GET'}), host=None,
|
||||||
strict_slashes=False, stream=False):
|
strict_slashes=False, stream=False, version=None):
|
||||||
"""Decorate a function to be registered as a route
|
"""Decorate a function to be registered as a route
|
||||||
|
|
||||||
:param uri: path of the URL
|
:param uri: path of the URL
|
||||||
|
@ -136,42 +136,49 @@ class Sanic:
|
||||||
if stream:
|
if stream:
|
||||||
handler.is_stream = stream
|
handler.is_stream = stream
|
||||||
self.router.add(uri=uri, methods=methods, handler=handler,
|
self.router.add(uri=uri, methods=methods, handler=handler,
|
||||||
host=host, strict_slashes=strict_slashes)
|
host=host, strict_slashes=strict_slashes,
|
||||||
|
version=version)
|
||||||
return handler
|
return handler
|
||||||
|
|
||||||
return response
|
return response
|
||||||
|
|
||||||
# Shorthand method decorators
|
# Shorthand method decorators
|
||||||
def get(self, uri, host=None, strict_slashes=False):
|
def get(self, uri, host=None, strict_slashes=False, version=None):
|
||||||
return self.route(uri, methods=frozenset({"GET"}), host=host,
|
return self.route(uri, methods=frozenset({"GET"}), host=host,
|
||||||
strict_slashes=strict_slashes)
|
strict_slashes=strict_slashes, version=version)
|
||||||
|
|
||||||
def post(self, uri, host=None, strict_slashes=False, stream=False):
|
def post(self, uri, host=None, strict_slashes=False, stream=False,
|
||||||
|
version=None):
|
||||||
return self.route(uri, methods=frozenset({"POST"}), host=host,
|
return self.route(uri, methods=frozenset({"POST"}), host=host,
|
||||||
strict_slashes=strict_slashes, stream=stream)
|
strict_slashes=strict_slashes, stream=stream,
|
||||||
|
version=version)
|
||||||
|
|
||||||
def put(self, uri, host=None, strict_slashes=False, stream=False):
|
def put(self, uri, host=None, strict_slashes=False, stream=False,
|
||||||
|
version=None):
|
||||||
return self.route(uri, methods=frozenset({"PUT"}), host=host,
|
return self.route(uri, methods=frozenset({"PUT"}), host=host,
|
||||||
strict_slashes=strict_slashes, stream=stream)
|
strict_slashes=strict_slashes, stream=stream,
|
||||||
|
version=version)
|
||||||
|
|
||||||
def head(self, uri, host=None, strict_slashes=False):
|
def head(self, uri, host=None, strict_slashes=False, version=None):
|
||||||
return self.route(uri, methods=frozenset({"HEAD"}), host=host,
|
return self.route(uri, methods=frozenset({"HEAD"}), host=host,
|
||||||
strict_slashes=strict_slashes)
|
strict_slashes=strict_slashes, version=version)
|
||||||
|
|
||||||
def options(self, uri, host=None, strict_slashes=False):
|
def options(self, uri, host=None, strict_slashes=False, version=None):
|
||||||
return self.route(uri, methods=frozenset({"OPTIONS"}), host=host,
|
return self.route(uri, methods=frozenset({"OPTIONS"}), host=host,
|
||||||
strict_slashes=strict_slashes)
|
strict_slashes=strict_slashes, version=version)
|
||||||
|
|
||||||
def patch(self, uri, host=None, strict_slashes=False, stream=False):
|
def patch(self, uri, host=None, strict_slashes=False, stream=False,
|
||||||
|
version=None):
|
||||||
return self.route(uri, methods=frozenset({"PATCH"}), host=host,
|
return self.route(uri, methods=frozenset({"PATCH"}), host=host,
|
||||||
strict_slashes=strict_slashes, stream=stream)
|
strict_slashes=strict_slashes, stream=stream,
|
||||||
|
version=version)
|
||||||
|
|
||||||
def delete(self, uri, host=None, strict_slashes=False):
|
def delete(self, uri, host=None, strict_slashes=False, version=None):
|
||||||
return self.route(uri, methods=frozenset({"DELETE"}), host=host,
|
return self.route(uri, methods=frozenset({"DELETE"}), host=host,
|
||||||
strict_slashes=strict_slashes)
|
strict_slashes=strict_slashes, version=version)
|
||||||
|
|
||||||
def add_route(self, handler, uri, methods=frozenset({'GET'}), host=None,
|
def add_route(self, handler, uri, methods=frozenset({'GET'}), host=None,
|
||||||
strict_slashes=False):
|
strict_slashes=False, version=None):
|
||||||
"""A helper method to register class instance or
|
"""A helper method to register class instance or
|
||||||
functions as a handler to the application url
|
functions as a handler to the application url
|
||||||
routes.
|
routes.
|
||||||
|
@ -204,7 +211,8 @@ class Sanic:
|
||||||
break
|
break
|
||||||
|
|
||||||
self.route(uri=uri, methods=methods, host=host,
|
self.route(uri=uri, methods=methods, host=host,
|
||||||
strict_slashes=strict_slashes, stream=stream)(handler)
|
strict_slashes=strict_slashes, stream=stream,
|
||||||
|
version=version)(handler)
|
||||||
return handler
|
return handler
|
||||||
|
|
||||||
# Decorator
|
# Decorator
|
||||||
|
|
|
@ -4,8 +4,8 @@ from sanic.constants import HTTP_METHODS
|
||||||
from sanic.views import CompositionView
|
from sanic.views import CompositionView
|
||||||
|
|
||||||
FutureRoute = namedtuple('Route',
|
FutureRoute = namedtuple('Route',
|
||||||
['handler', 'uri', 'methods',
|
['handler', 'uri', 'methods', 'host',
|
||||||
'host', 'strict_slashes', 'stream'])
|
'strict_slashes', 'stream', 'version'])
|
||||||
FutureListener = namedtuple('Listener', ['handler', 'uri', 'methods', 'host'])
|
FutureListener = namedtuple('Listener', ['handler', 'uri', 'methods', 'host'])
|
||||||
FutureMiddleware = namedtuple('Route', ['middleware', 'args', 'kwargs'])
|
FutureMiddleware = namedtuple('Route', ['middleware', 'args', 'kwargs'])
|
||||||
FutureException = namedtuple('Route', ['handler', 'args', 'kwargs'])
|
FutureException = namedtuple('Route', ['handler', 'args', 'kwargs'])
|
||||||
|
@ -14,7 +14,7 @@ FutureStatic = namedtuple('Route',
|
||||||
|
|
||||||
|
|
||||||
class Blueprint:
|
class Blueprint:
|
||||||
def __init__(self, name, url_prefix=None, host=None):
|
def __init__(self, name, url_prefix=None, host=None, version=None):
|
||||||
"""Create a new blueprint
|
"""Create a new blueprint
|
||||||
|
|
||||||
:param name: unique name of the blueprint
|
:param name: unique name of the blueprint
|
||||||
|
@ -30,6 +30,7 @@ class Blueprint:
|
||||||
self.listeners = defaultdict(list)
|
self.listeners = defaultdict(list)
|
||||||
self.middlewares = []
|
self.middlewares = []
|
||||||
self.statics = []
|
self.statics = []
|
||||||
|
self.version = version
|
||||||
|
|
||||||
def register(self, app, options):
|
def register(self, app, options):
|
||||||
"""Register the blueprint to the sanic app."""
|
"""Register the blueprint to the sanic app."""
|
||||||
|
@ -43,12 +44,16 @@ class Blueprint:
|
||||||
future.handler.__blueprintname__ = self.name
|
future.handler.__blueprintname__ = self.name
|
||||||
# Prepend the blueprint URI prefix if available
|
# Prepend the blueprint URI prefix if available
|
||||||
uri = url_prefix + future.uri if url_prefix else future.uri
|
uri = url_prefix + future.uri if url_prefix else future.uri
|
||||||
|
|
||||||
|
version = future.version or self.version
|
||||||
|
|
||||||
app.route(
|
app.route(
|
||||||
uri=uri[1:] if uri.startswith('//') else uri,
|
uri=uri[1:] if uri.startswith('//') else uri,
|
||||||
methods=future.methods,
|
methods=future.methods,
|
||||||
host=future.host or self.host,
|
host=future.host or self.host,
|
||||||
strict_slashes=future.strict_slashes,
|
strict_slashes=future.strict_slashes,
|
||||||
stream=future.stream
|
stream=future.stream,
|
||||||
|
version=version
|
||||||
)(future.handler)
|
)(future.handler)
|
||||||
|
|
||||||
for future in self.websocket_routes:
|
for future in self.websocket_routes:
|
||||||
|
@ -89,7 +94,7 @@ class Blueprint:
|
||||||
app.listener(event)(listener)
|
app.listener(event)(listener)
|
||||||
|
|
||||||
def route(self, uri, methods=frozenset({'GET'}), host=None,
|
def route(self, uri, methods=frozenset({'GET'}), host=None,
|
||||||
strict_slashes=False, stream=False):
|
strict_slashes=False, stream=False, version=None):
|
||||||
"""Create a blueprint route from a decorated function.
|
"""Create a blueprint route from a decorated function.
|
||||||
|
|
||||||
:param uri: endpoint at which the route will be accessible.
|
:param uri: endpoint at which the route will be accessible.
|
||||||
|
@ -97,13 +102,13 @@ class Blueprint:
|
||||||
"""
|
"""
|
||||||
def decorator(handler):
|
def decorator(handler):
|
||||||
route = FutureRoute(
|
route = FutureRoute(
|
||||||
handler, uri, methods, host, strict_slashes, stream)
|
handler, uri, methods, host, strict_slashes, stream, version)
|
||||||
self.routes.append(route)
|
self.routes.append(route)
|
||||||
return handler
|
return handler
|
||||||
return decorator
|
return decorator
|
||||||
|
|
||||||
def add_route(self, handler, uri, methods=frozenset({'GET'}), host=None,
|
def add_route(self, handler, uri, methods=frozenset({'GET'}), host=None,
|
||||||
strict_slashes=False):
|
strict_slashes=False, version=None):
|
||||||
"""Create a blueprint route from a function.
|
"""Create a blueprint route from a function.
|
||||||
|
|
||||||
:param handler: function for handling uri requests. Accepts function,
|
:param handler: function for handling uri requests. Accepts function,
|
||||||
|
@ -125,21 +130,22 @@ class Blueprint:
|
||||||
methods = handler.handlers.keys()
|
methods = handler.handlers.keys()
|
||||||
|
|
||||||
self.route(uri=uri, methods=methods, host=host,
|
self.route(uri=uri, methods=methods, host=host,
|
||||||
strict_slashes=strict_slashes)(handler)
|
strict_slashes=strict_slashes, version=version)(handler)
|
||||||
return handler
|
return handler
|
||||||
|
|
||||||
def websocket(self, uri, host=None, strict_slashes=False):
|
def websocket(self, uri, host=None, strict_slashes=False, version=None):
|
||||||
"""Create a blueprint websocket route from a decorated function.
|
"""Create a blueprint websocket route from a decorated function.
|
||||||
|
|
||||||
:param uri: endpoint at which the route will be accessible.
|
:param uri: endpoint at which the route will be accessible.
|
||||||
"""
|
"""
|
||||||
def decorator(handler):
|
def decorator(handler):
|
||||||
route = FutureRoute(handler, uri, [], host, strict_slashes, False)
|
route = FutureRoute(handler, uri, [], host, strict_slashes,
|
||||||
|
False, version)
|
||||||
self.websocket_routes.append(route)
|
self.websocket_routes.append(route)
|
||||||
return handler
|
return handler
|
||||||
return decorator
|
return decorator
|
||||||
|
|
||||||
def add_websocket_route(self, handler, uri, host=None):
|
def add_websocket_route(self, handler, uri, host=None, version=None):
|
||||||
"""Create a blueprint websocket route from a function.
|
"""Create a blueprint websocket route from a function.
|
||||||
|
|
||||||
:param handler: function for handling uri requests. Accepts function,
|
:param handler: function for handling uri requests. Accepts function,
|
||||||
|
@ -147,7 +153,7 @@ class Blueprint:
|
||||||
:param uri: endpoint at which the route will be accessible.
|
:param uri: endpoint at which the route will be accessible.
|
||||||
:return: function or class instance
|
:return: function or class instance
|
||||||
"""
|
"""
|
||||||
self.websocket(uri=uri, host=host)(handler)
|
self.websocket(uri=uri, host=host, version=version)(handler)
|
||||||
return handler
|
return handler
|
||||||
|
|
||||||
def listener(self, event):
|
def listener(self, event):
|
||||||
|
@ -193,30 +199,36 @@ class Blueprint:
|
||||||
self.statics.append(static)
|
self.statics.append(static)
|
||||||
|
|
||||||
# Shorthand method decorators
|
# Shorthand method decorators
|
||||||
def get(self, uri, host=None, strict_slashes=False):
|
def get(self, uri, host=None, strict_slashes=False, version=None):
|
||||||
return self.route(uri, methods=["GET"], host=host,
|
return self.route(uri, methods=["GET"], host=host,
|
||||||
strict_slashes=strict_slashes)
|
strict_slashes=strict_slashes, version=version)
|
||||||
|
|
||||||
def post(self, uri, host=None, strict_slashes=False, stream=False):
|
def post(self, uri, host=None, strict_slashes=False, stream=False,
|
||||||
|
version=None):
|
||||||
return self.route(uri, methods=["POST"], host=host,
|
return self.route(uri, methods=["POST"], host=host,
|
||||||
strict_slashes=strict_slashes, stream=stream)
|
strict_slashes=strict_slashes, stream=stream,
|
||||||
|
version=version)
|
||||||
|
|
||||||
def put(self, uri, host=None, strict_slashes=False, stream=False):
|
def put(self, uri, host=None, strict_slashes=False, stream=False,
|
||||||
|
version=None):
|
||||||
return self.route(uri, methods=["PUT"], host=host,
|
return self.route(uri, methods=["PUT"], host=host,
|
||||||
strict_slashes=strict_slashes, stream=stream)
|
strict_slashes=strict_slashes, stream=stream,
|
||||||
|
version=version)
|
||||||
|
|
||||||
def head(self, uri, host=None, strict_slashes=False):
|
def head(self, uri, host=None, strict_slashes=False, version=None):
|
||||||
return self.route(uri, methods=["HEAD"], host=host,
|
return self.route(uri, methods=["HEAD"], host=host,
|
||||||
strict_slashes=strict_slashes)
|
strict_slashes=strict_slashes, version=version)
|
||||||
|
|
||||||
def options(self, uri, host=None, strict_slashes=False):
|
def options(self, uri, host=None, strict_slashes=False, version=None):
|
||||||
return self.route(uri, methods=["OPTIONS"], host=host,
|
return self.route(uri, methods=["OPTIONS"], host=host,
|
||||||
strict_slashes=strict_slashes)
|
strict_slashes=strict_slashes, version=version)
|
||||||
|
|
||||||
def patch(self, uri, host=None, strict_slashes=False, stream=False):
|
def patch(self, uri, host=None, strict_slashes=False, stream=False,
|
||||||
|
version=None):
|
||||||
return self.route(uri, methods=["PATCH"], host=host,
|
return self.route(uri, methods=["PATCH"], host=host,
|
||||||
strict_slashes=strict_slashes, stream=stream)
|
strict_slashes=strict_slashes, stream=stream,
|
||||||
|
version=version)
|
||||||
|
|
||||||
def delete(self, uri, host=None, strict_slashes=False):
|
def delete(self, uri, host=None, strict_slashes=False, version=None):
|
||||||
return self.route(uri, methods=["DELETE"], host=host,
|
return self.route(uri, methods=["DELETE"], host=host,
|
||||||
strict_slashes=strict_slashes)
|
strict_slashes=strict_slashes, version=version)
|
||||||
|
|
|
@ -17,6 +17,7 @@ _address_dict = {
|
||||||
|
|
||||||
LOGGING = {
|
LOGGING = {
|
||||||
'version': 1,
|
'version': 1,
|
||||||
|
'disable_existing_loggers': False,
|
||||||
'filters': {
|
'filters': {
|
||||||
'accessFilter': {
|
'accessFilter': {
|
||||||
'()': DefaultFilter,
|
'()': DefaultFilter,
|
||||||
|
@ -196,7 +197,7 @@ class Config(dict):
|
||||||
|
|
||||||
def load_environment_vars(self):
|
def load_environment_vars(self):
|
||||||
"""
|
"""
|
||||||
Looks for any SANIC_ prefixed environment variables and applies
|
Looks for any ``SANIC_`` prefixed environment variables and applies
|
||||||
them to the configuration if present.
|
them to the configuration if present.
|
||||||
"""
|
"""
|
||||||
for k, v in os.environ.items():
|
for k, v in os.environ.items():
|
||||||
|
|
|
@ -211,8 +211,8 @@ class Unauthorized(SanicException):
|
||||||
:param scheme: Name of the authentication scheme to be used.
|
:param scheme: Name of the authentication scheme to be used.
|
||||||
:param realm: Description of the protected area. (optional)
|
:param realm: Description of the protected area. (optional)
|
||||||
:param challenge: A dict containing values to add to the WWW-Authenticate
|
:param challenge: A dict containing values to add to the WWW-Authenticate
|
||||||
header that is generated. This is especially useful when dealing with the
|
header that is generated. This is especially useful when
|
||||||
Digest scheme. (optional)
|
dealing with the Digest scheme. (optional)
|
||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@ -235,6 +235,7 @@ def abort(status_code, message=None):
|
||||||
"""
|
"""
|
||||||
Raise an exception based on SanicException. Returns the HTTP response
|
Raise an exception based on SanicException. Returns the HTTP response
|
||||||
message appropriate for the given status code, unless provided.
|
message appropriate for the given status code, unless provided.
|
||||||
|
|
||||||
:param status_code: The HTTP status code to return.
|
:param status_code: The HTTP status code to return.
|
||||||
:param message: The HTTP response body. Defaults to the messages
|
:param message: The HTTP response body. Defaults to the messages
|
||||||
in response.py for the given status code.
|
in response.py for the given status code.
|
||||||
|
|
|
@ -238,15 +238,15 @@ def parse_multipart_form(body, boundary):
|
||||||
break
|
break
|
||||||
|
|
||||||
colon_index = form_line.index(':')
|
colon_index = form_line.index(':')
|
||||||
form_header_field = form_line[0:colon_index]
|
form_header_field = form_line[0:colon_index].lower()
|
||||||
form_header_value, form_parameters = parse_header(
|
form_header_value, form_parameters = parse_header(
|
||||||
form_line[colon_index + 2:])
|
form_line[colon_index + 2:])
|
||||||
|
|
||||||
if form_header_field == 'Content-Disposition':
|
if form_header_field == 'content-disposition':
|
||||||
if 'filename' in form_parameters:
|
if 'filename' in form_parameters:
|
||||||
file_name = form_parameters['filename']
|
file_name = form_parameters['filename']
|
||||||
field_name = form_parameters.get('name')
|
field_name = form_parameters.get('name')
|
||||||
elif form_header_field == 'Content-Type':
|
elif form_header_field == 'content-type':
|
||||||
file_type = form_header_value
|
file_type = form_header_value
|
||||||
|
|
||||||
post_data = form_part[line_index:-4]
|
post_data = form_part[line_index:-4]
|
||||||
|
|
|
@ -237,6 +237,7 @@ def json(body, status=200, headers=None,
|
||||||
content_type="application/json", **kwargs):
|
content_type="application/json", **kwargs):
|
||||||
"""
|
"""
|
||||||
Returns response object with body in json format.
|
Returns response object with body in json format.
|
||||||
|
|
||||||
:param body: Response data to be serialized.
|
:param body: Response data to be serialized.
|
||||||
:param status: Response code.
|
:param status: Response code.
|
||||||
:param headers: Custom Headers.
|
:param headers: Custom Headers.
|
||||||
|
@ -250,6 +251,7 @@ def text(body, status=200, headers=None,
|
||||||
content_type="text/plain; charset=utf-8"):
|
content_type="text/plain; charset=utf-8"):
|
||||||
"""
|
"""
|
||||||
Returns response object with body in text format.
|
Returns response object with body in text format.
|
||||||
|
|
||||||
:param body: Response data to be encoded.
|
:param body: Response data to be encoded.
|
||||||
:param status: Response code.
|
:param status: Response code.
|
||||||
:param headers: Custom Headers.
|
:param headers: Custom Headers.
|
||||||
|
@ -264,6 +266,7 @@ def raw(body, status=200, headers=None,
|
||||||
content_type="application/octet-stream"):
|
content_type="application/octet-stream"):
|
||||||
"""
|
"""
|
||||||
Returns response object without encoding the body.
|
Returns response object without encoding the body.
|
||||||
|
|
||||||
:param body: Response data.
|
:param body: Response data.
|
||||||
:param status: Response code.
|
:param status: Response code.
|
||||||
:param headers: Custom Headers.
|
:param headers: Custom Headers.
|
||||||
|
@ -276,6 +279,7 @@ def raw(body, status=200, headers=None,
|
||||||
def html(body, status=200, headers=None):
|
def html(body, status=200, headers=None):
|
||||||
"""
|
"""
|
||||||
Returns response object with body in html format.
|
Returns response object with body in html format.
|
||||||
|
|
||||||
:param body: Response data to be encoded.
|
:param body: Response data to be encoded.
|
||||||
:param status: Response code.
|
:param status: Response code.
|
||||||
:param headers: Custom Headers.
|
:param headers: Custom Headers.
|
||||||
|
|
|
@ -98,8 +98,25 @@ class Router:
|
||||||
|
|
||||||
return name, _type, pattern
|
return name, _type, pattern
|
||||||
|
|
||||||
def add(self, uri, methods, handler, host=None, strict_slashes=False):
|
def add(self, uri, methods, handler, host=None, strict_slashes=False,
|
||||||
|
version=None):
|
||||||
|
"""Add a handler to the route list
|
||||||
|
|
||||||
|
:param uri: path to match
|
||||||
|
:param methods: sequence of accepted method names. If none are
|
||||||
|
provided, any method is allowed
|
||||||
|
:param handler: request handler function.
|
||||||
|
When executed, it should provide a response object.
|
||||||
|
:param strict_slashes: strict to trailing slash
|
||||||
|
:param version: current version of the route or blueprint. See
|
||||||
|
docs for further details.
|
||||||
|
:return: Nothing
|
||||||
|
"""
|
||||||
|
if version is not None:
|
||||||
|
if uri.startswith('/'):
|
||||||
|
uri = "/".join(["/v{}".format(str(version)), uri[1:]])
|
||||||
|
else:
|
||||||
|
uri = "/".join(["/v{}".format(str(version)), uri])
|
||||||
# add regular version
|
# add regular version
|
||||||
self._add(uri, methods, handler, host)
|
self._add(uri, methods, handler, host)
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@ import sys
|
||||||
import signal
|
import signal
|
||||||
import asyncio
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
|
import traceback
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import ssl
|
import ssl
|
||||||
|
@ -69,9 +70,15 @@ class GunicornWorker(base.Worker):
|
||||||
trigger_events(self._server_settings.get('before_stop', []),
|
trigger_events(self._server_settings.get('before_stop', []),
|
||||||
self.loop)
|
self.loop)
|
||||||
self.loop.run_until_complete(self.close())
|
self.loop.run_until_complete(self.close())
|
||||||
|
except:
|
||||||
|
traceback.print_exc()
|
||||||
finally:
|
finally:
|
||||||
|
try:
|
||||||
trigger_events(self._server_settings.get('after_stop', []),
|
trigger_events(self._server_settings.get('after_stop', []),
|
||||||
self.loop)
|
self.loop)
|
||||||
|
except:
|
||||||
|
traceback.print_exc()
|
||||||
|
finally:
|
||||||
self.loop.close()
|
self.loop.close()
|
||||||
|
|
||||||
sys.exit(self.exit_code)
|
sys.exit(self.exit_code)
|
||||||
|
|
|
@ -1,16 +1,42 @@
|
||||||
import asyncio
|
import asyncio
|
||||||
import inspect
|
import inspect
|
||||||
|
import pytest
|
||||||
|
|
||||||
from sanic import Sanic
|
from sanic import Sanic
|
||||||
from sanic.blueprints import Blueprint
|
from sanic.blueprints import Blueprint
|
||||||
from sanic.response import json, text
|
from sanic.response import json, text
|
||||||
from sanic.exceptions import NotFound, ServerError, InvalidUsage
|
from sanic.exceptions import NotFound, ServerError, InvalidUsage
|
||||||
|
from sanic.constants import HTTP_METHODS
|
||||||
|
|
||||||
|
|
||||||
# ------------------------------------------------------------ #
|
# ------------------------------------------------------------ #
|
||||||
# GET
|
# GET
|
||||||
# ------------------------------------------------------------ #
|
# ------------------------------------------------------------ #
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('method', HTTP_METHODS)
|
||||||
|
def test_versioned_routes_get(method):
|
||||||
|
app = Sanic('test_shorhand_routes_get')
|
||||||
|
bp = Blueprint('test_text')
|
||||||
|
|
||||||
|
method = method.lower()
|
||||||
|
|
||||||
|
func = getattr(bp, method)
|
||||||
|
if callable(func):
|
||||||
|
@func('/{}'.format(method), version=1)
|
||||||
|
def handler(request):
|
||||||
|
return text('OK')
|
||||||
|
else:
|
||||||
|
print(func)
|
||||||
|
raise
|
||||||
|
|
||||||
|
app.blueprint(bp)
|
||||||
|
|
||||||
|
client_method = getattr(app.test_client, method)
|
||||||
|
|
||||||
|
request, response = client_method('/v1/{}'.format(method))
|
||||||
|
assert response.status == 200
|
||||||
|
|
||||||
|
|
||||||
def test_bp():
|
def test_bp():
|
||||||
app = Sanic('test_text')
|
app = Sanic('test_text')
|
||||||
bp = Blueprint('test_text')
|
bp = Blueprint('test_text')
|
||||||
|
|
|
@ -259,20 +259,26 @@ def test_post_form_urlencoded():
|
||||||
|
|
||||||
assert request.form.get('test') == 'OK'
|
assert request.form.get('test') == 'OK'
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
def test_post_form_multipart_form_data():
|
'payload', [
|
||||||
|
'------sanic\r\n' \
|
||||||
|
'Content-Disposition: form-data; name="test"\r\n' \
|
||||||
|
'\r\n' \
|
||||||
|
'OK\r\n' \
|
||||||
|
'------sanic--\r\n',
|
||||||
|
'------sanic\r\n' \
|
||||||
|
'content-disposition: form-data; name="test"\r\n' \
|
||||||
|
'\r\n' \
|
||||||
|
'OK\r\n' \
|
||||||
|
'------sanic--\r\n',
|
||||||
|
])
|
||||||
|
def test_post_form_multipart_form_data(payload):
|
||||||
app = Sanic('test_post_form_multipart_form_data')
|
app = Sanic('test_post_form_multipart_form_data')
|
||||||
|
|
||||||
@app.route('/', methods=['POST'])
|
@app.route('/', methods=['POST'])
|
||||||
async def handler(request):
|
async def handler(request):
|
||||||
return text('OK')
|
return text('OK')
|
||||||
|
|
||||||
payload = '------sanic\r\n' \
|
|
||||||
'Content-Disposition: form-data; name="test"\r\n' \
|
|
||||||
'\r\n' \
|
|
||||||
'OK\r\n' \
|
|
||||||
'------sanic--\r\n'
|
|
||||||
|
|
||||||
headers = {'content-type': 'multipart/form-data; boundary=----sanic'}
|
headers = {'content-type': 'multipart/form-data; boundary=----sanic'}
|
||||||
|
|
||||||
request, response = app.test_client.post(data=payload, headers=headers)
|
request, response = app.test_client.post(data=payload, headers=headers)
|
||||||
|
|
|
@ -4,12 +4,33 @@ import pytest
|
||||||
from sanic import Sanic
|
from sanic import Sanic
|
||||||
from sanic.response import text
|
from sanic.response import text
|
||||||
from sanic.router import RouteExists, RouteDoesNotExist
|
from sanic.router import RouteExists, RouteDoesNotExist
|
||||||
|
from sanic.constants import HTTP_METHODS
|
||||||
|
|
||||||
|
|
||||||
# ------------------------------------------------------------ #
|
# ------------------------------------------------------------ #
|
||||||
# UTF-8
|
# UTF-8
|
||||||
# ------------------------------------------------------------ #
|
# ------------------------------------------------------------ #
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('method', HTTP_METHODS)
|
||||||
|
def test_versioned_routes_get(method):
|
||||||
|
app = Sanic('test_shorhand_routes_get')
|
||||||
|
|
||||||
|
method = method.lower()
|
||||||
|
|
||||||
|
func = getattr(app, method)
|
||||||
|
if callable(func):
|
||||||
|
@func('/{}'.format(method), version=1)
|
||||||
|
def handler(request):
|
||||||
|
return text('OK')
|
||||||
|
else:
|
||||||
|
print(func)
|
||||||
|
raise
|
||||||
|
|
||||||
|
client_method = getattr(app.test_client, method)
|
||||||
|
|
||||||
|
request, response = client_method('/v1/{}'.format(method))
|
||||||
|
assert response.status== 200
|
||||||
|
|
||||||
def test_shorthand_routes_get():
|
def test_shorthand_routes_get():
|
||||||
app = Sanic('test_shorhand_routes_get')
|
app = Sanic('test_shorhand_routes_get')
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user