diff --git a/README.rst b/README.rst
index 8a7b2706..410bd0b8 100644
--- a/README.rst
+++ b/README.rst
@@ -11,34 +11,6 @@ Sanic is developed `on GitHub `_. Contribu
If you have a project that utilizes Sanic make sure to comment on the `issue `_ 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
-------------------
diff --git a/docs/Makefile b/docs/Makefile
index ef166d7d..72b82bed 100644
--- a/docs/Makefile
+++ b/docs/Makefile
@@ -1,20 +1,225 @@
-# Minimal makefile for Sphinx documentation
+# Makefile for Sphinx documentation
#
# You can set these variables from the command line.
SPHINXOPTS =
SPHINXBUILD = sphinx-build
-SPHINXPROJ = Sanic
-SOURCEDIR = .
+PAPER =
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:
- @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
+ @echo "Please use \`make ' where 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
-# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
-%: Makefile
- @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
\ No newline at end of file
+.PHONY: html
+html:
+ $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
+ @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."
diff --git a/docs/conf.py b/docs/conf.py
index c97f3c19..e254c183 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -13,6 +13,9 @@ import sys
# Add support for Markdown documentation using Recommonmark
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
# autogenerate documentation from docstrings
root_directory = os.path.dirname(os.getcwd())
@@ -140,3 +143,12 @@ epub_exclude_files = ['search.html']
# -- Custom Settings -------------------------------------------------------
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)
diff --git a/docs/index.rst b/docs/index.rst
index 80e7e70f..9f4fa00c 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -16,6 +16,7 @@ Guides
sanic/blueprints
sanic/config
sanic/cookies
+ sanic/decorators
sanic/streaming
sanic/class_based_views
sanic/custom_protocol
@@ -25,6 +26,7 @@ Guides
sanic/deploying
sanic/extensions
sanic/contributing
+ sanic/api_reference
Module Documentation
@@ -33,4 +35,5 @@ Module Documentation
.. toctree::
* :ref:`genindex`
+* :ref:`modindex`
* :ref:`search`
diff --git a/docs/make.bat b/docs/make.bat
index 54191087..3887c127 100644
--- a/docs/make.bat
+++ b/docs/make.bat
@@ -1,19 +1,64 @@
@ECHO OFF
-pushd %~dp0
-
REM Command file for Sphinx documentation
if "%SPHINXBUILD%" == "" (
set SPHINXBUILD=sphinx-build
)
-set SOURCEDIR=.
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
-%SPHINXBUILD% >NUL 2>NUL
+if "%1" == "help" (
+ :help
+ echo.Please use `make ^` where ^ 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 (
echo.
echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
@@ -26,11 +71,211 @@ if errorlevel 9009 (
exit /b 1
)
-%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS%
-goto end
+:sphinx_ok
-: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
-popd
diff --git a/docs/sanic/api_reference.rst b/docs/sanic/api_reference.rst
new file mode 100644
index 00000000..5ca7556a
--- /dev/null
+++ b/docs/sanic/api_reference.rst
@@ -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:
diff --git a/docs/sanic/blueprints.md b/docs/sanic/blueprints.md
index 5fe20e54..b4fd06d7 100644
--- a/docs/sanic/blueprints.md
+++ b/docs/sanic/blueprints.md
@@ -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
takes the format `.`. For example:
-```
+```python
@blueprint_v1.route('/')
async def root(request):
url = app.url_for('v1.post_handler', post_id=5) # --> '/v1/post/5'
diff --git a/docs/sanic/routing.md b/docs/sanic/routing.md
index 9b4d060f..e039e249 100644
--- a/docs/sanic/routing.md
+++ b/docs/sanic/routing.md
@@ -214,4 +214,3 @@ and `recv` methods to send and receive data respectively.
WebSocket support requires the [websockets](https://github.com/aaugustin/websockets)
package by Aymeric Augustin.
-
diff --git a/docs/sanic/versioning.md b/docs/sanic/versioning.md
new file mode 100644
index 00000000..85cbd278
--- /dev/null
+++ b/docs/sanic/versioning.md
@@ -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('Hello world!
')
+```
+
+Then with curl:
+
+```bash
+curl localhost/v1/html
+```
diff --git a/sanic/app.py b/sanic/app.py
index 5b071200..f1e8be7e 100644
--- a/sanic/app.py
+++ b/sanic/app.py
@@ -113,7 +113,7 @@ class Sanic:
# Decorator
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
:param uri: path of the URL
@@ -136,42 +136,49 @@ class Sanic:
if stream:
handler.is_stream = stream
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 response
# 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,
- 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,
- 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,
- 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,
- 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,
- 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,
- 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,
- strict_slashes=strict_slashes)
+ strict_slashes=strict_slashes, version=version)
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
functions as a handler to the application url
routes.
@@ -204,7 +211,8 @@ class Sanic:
break
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
# Decorator
diff --git a/sanic/blueprints.py b/sanic/blueprints.py
index b3866cbd..0e97903b 100644
--- a/sanic/blueprints.py
+++ b/sanic/blueprints.py
@@ -4,8 +4,8 @@ from sanic.constants import HTTP_METHODS
from sanic.views import CompositionView
FutureRoute = namedtuple('Route',
- ['handler', 'uri', 'methods',
- 'host', 'strict_slashes', 'stream'])
+ ['handler', 'uri', 'methods', 'host',
+ 'strict_slashes', 'stream', 'version'])
FutureListener = namedtuple('Listener', ['handler', 'uri', 'methods', 'host'])
FutureMiddleware = namedtuple('Route', ['middleware', 'args', 'kwargs'])
FutureException = namedtuple('Route', ['handler', 'args', 'kwargs'])
@@ -14,7 +14,7 @@ FutureStatic = namedtuple('Route',
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
:param name: unique name of the blueprint
@@ -30,6 +30,7 @@ class Blueprint:
self.listeners = defaultdict(list)
self.middlewares = []
self.statics = []
+ self.version = version
def register(self, app, options):
"""Register the blueprint to the sanic app."""
@@ -43,12 +44,16 @@ class Blueprint:
future.handler.__blueprintname__ = self.name
# Prepend the blueprint URI prefix if available
uri = url_prefix + future.uri if url_prefix else future.uri
+
+ version = future.version or self.version
+
app.route(
uri=uri[1:] if uri.startswith('//') else uri,
methods=future.methods,
host=future.host or self.host,
strict_slashes=future.strict_slashes,
- stream=future.stream
+ stream=future.stream,
+ version=version
)(future.handler)
for future in self.websocket_routes:
@@ -89,7 +94,7 @@ class Blueprint:
app.listener(event)(listener)
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.
:param uri: endpoint at which the route will be accessible.
@@ -97,13 +102,13 @@ class Blueprint:
"""
def decorator(handler):
route = FutureRoute(
- handler, uri, methods, host, strict_slashes, stream)
+ handler, uri, methods, host, strict_slashes, stream, version)
self.routes.append(route)
return handler
return decorator
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.
:param handler: function for handling uri requests. Accepts function,
@@ -125,21 +130,22 @@ class Blueprint:
methods = handler.handlers.keys()
self.route(uri=uri, methods=methods, host=host,
- strict_slashes=strict_slashes)(handler)
+ strict_slashes=strict_slashes, version=version)(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.
:param uri: endpoint at which the route will be accessible.
"""
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)
return handler
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.
: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.
:return: function or class instance
"""
- self.websocket(uri=uri, host=host)(handler)
+ self.websocket(uri=uri, host=host, version=version)(handler)
return handler
def listener(self, event):
@@ -193,30 +199,36 @@ class Blueprint:
self.statics.append(static)
# 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,
- 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,
- 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,
- 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,
- 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,
- 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,
- 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,
- strict_slashes=strict_slashes)
+ strict_slashes=strict_slashes, version=version)
diff --git a/sanic/config.py b/sanic/config.py
index 4af31532..b8637bfa 100644
--- a/sanic/config.py
+++ b/sanic/config.py
@@ -17,6 +17,7 @@ _address_dict = {
LOGGING = {
'version': 1,
+ 'disable_existing_loggers': False,
'filters': {
'accessFilter': {
'()': DefaultFilter,
@@ -196,7 +197,7 @@ class Config(dict):
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.
"""
for k, v in os.environ.items():
diff --git a/sanic/exceptions.py b/sanic/exceptions.py
index 1bc55db4..8998aab8 100644
--- a/sanic/exceptions.py
+++ b/sanic/exceptions.py
@@ -211,8 +211,8 @@ class Unauthorized(SanicException):
:param scheme: Name of the authentication scheme to be used.
:param realm: Description of the protected area. (optional)
:param challenge: A dict containing values to add to the WWW-Authenticate
- header that is generated. This is especially useful when dealing with the
- Digest scheme. (optional)
+ header that is generated. This is especially useful when
+ dealing with the Digest scheme. (optional)
"""
pass
@@ -235,9 +235,10 @@ def abort(status_code, message=None):
"""
Raise an exception based on SanicException. Returns the HTTP response
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.
+ in response.py for the given status code.
"""
if message is None:
message = COMMON_STATUS_CODES.get(status_code,
diff --git a/sanic/request.py b/sanic/request.py
index e21b8282..d8674c48 100644
--- a/sanic/request.py
+++ b/sanic/request.py
@@ -238,15 +238,15 @@ def parse_multipart_form(body, boundary):
break
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_line[colon_index + 2:])
- if form_header_field == 'Content-Disposition':
+ if form_header_field == 'content-disposition':
if 'filename' in form_parameters:
file_name = form_parameters['filename']
field_name = form_parameters.get('name')
- elif form_header_field == 'Content-Type':
+ elif form_header_field == 'content-type':
file_type = form_header_value
post_data = form_part[line_index:-4]
diff --git a/sanic/response.py b/sanic/response.py
index ea233d9a..f4fb1ea6 100644
--- a/sanic/response.py
+++ b/sanic/response.py
@@ -237,6 +237,7 @@ def json(body, status=200, headers=None,
content_type="application/json", **kwargs):
"""
Returns response object with body in json format.
+
:param body: Response data to be serialized.
:param status: Response code.
:param headers: Custom Headers.
@@ -250,6 +251,7 @@ def text(body, status=200, headers=None,
content_type="text/plain; charset=utf-8"):
"""
Returns response object with body in text format.
+
:param body: Response data to be encoded.
:param status: Response code.
:param headers: Custom Headers.
@@ -264,6 +266,7 @@ def raw(body, status=200, headers=None,
content_type="application/octet-stream"):
"""
Returns response object without encoding the body.
+
:param body: Response data.
:param status: Response code.
:param headers: Custom Headers.
@@ -276,6 +279,7 @@ def raw(body, status=200, headers=None,
def html(body, status=200, headers=None):
"""
Returns response object with body in html format.
+
:param body: Response data to be encoded.
:param status: Response code.
:param headers: Custom Headers.
diff --git a/sanic/router.py b/sanic/router.py
index 691f1388..efc48f37 100644
--- a/sanic/router.py
+++ b/sanic/router.py
@@ -98,8 +98,25 @@ class Router:
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
self._add(uri, methods, handler, host)
diff --git a/sanic/worker.py b/sanic/worker.py
index 7c02053c..9f950c34 100644
--- a/sanic/worker.py
+++ b/sanic/worker.py
@@ -3,6 +3,7 @@ import sys
import signal
import asyncio
import logging
+import traceback
try:
import ssl
@@ -69,10 +70,16 @@ class GunicornWorker(base.Worker):
trigger_events(self._server_settings.get('before_stop', []),
self.loop)
self.loop.run_until_complete(self.close())
+ except:
+ traceback.print_exc()
finally:
- trigger_events(self._server_settings.get('after_stop', []),
- self.loop)
- self.loop.close()
+ try:
+ trigger_events(self._server_settings.get('after_stop', []),
+ self.loop)
+ except:
+ traceback.print_exc()
+ finally:
+ self.loop.close()
sys.exit(self.exit_code)
diff --git a/tests/test_blueprints.py b/tests/test_blueprints.py
index 9ab387be..5cb356c2 100644
--- a/tests/test_blueprints.py
+++ b/tests/test_blueprints.py
@@ -1,16 +1,42 @@
import asyncio
import inspect
+import pytest
from sanic import Sanic
from sanic.blueprints import Blueprint
from sanic.response import json, text
from sanic.exceptions import NotFound, ServerError, InvalidUsage
+from sanic.constants import HTTP_METHODS
# ------------------------------------------------------------ #
# 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():
app = Sanic('test_text')
bp = Blueprint('test_text')
diff --git a/tests/test_requests.py b/tests/test_requests.py
index 81fe1a5c..997f32cb 100644
--- a/tests/test_requests.py
+++ b/tests/test_requests.py
@@ -259,20 +259,26 @@ def test_post_form_urlencoded():
assert request.form.get('test') == 'OK'
-
-def test_post_form_multipart_form_data():
+@pytest.mark.parametrize(
+ '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.route('/', methods=['POST'])
async def handler(request):
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'}
request, response = app.test_client.post(data=payload, headers=headers)
diff --git a/tests/test_routes.py b/tests/test_routes.py
index 4afb4a9c..04a682a0 100644
--- a/tests/test_routes.py
+++ b/tests/test_routes.py
@@ -4,12 +4,33 @@ import pytest
from sanic import Sanic
from sanic.response import text
from sanic.router import RouteExists, RouteDoesNotExist
+from sanic.constants import HTTP_METHODS
# ------------------------------------------------------------ #
# 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():
app = Sanic('test_shorhand_routes_get')