From 3fff685c444672cfbc1f8d8d2d9432b905c67686 Mon Sep 17 00:00:00 2001 From: Yun Xu Date: Sat, 1 Jul 2017 23:46:34 -0700 Subject: [PATCH 01/11] add auto-doc support --- docs/conf.py | 13 +++++++++++++ docs/sanic/request_data.md | 8 ++++++++ docs/sanic/response.md | 8 ++++++++ docs/sanic/routing.md | 1 - sanic/response.py | 4 ++++ sanic/router.py | 9 +++++++++ 6 files changed, 42 insertions(+), 1 deletion(-) diff --git a/docs/conf.py b/docs/conf.py index c97f3c19..33f5097d 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,13 @@ epub_exclude_files = ['search.html'] # -- Custom Settings ------------------------------------------------------- suppress_warnings = ['image.nonlocal_uri'] + + +# app setup hook +def setup(app): + app.add_config_value('recommonmark_config', { + 'auto_toc_tree_section': 'Contents', + 'enable_eval_rst': True, + 'enable_auto_doc_ref': True, + }, True) + app.add_transform(AutoStructify) diff --git a/docs/sanic/request_data.md b/docs/sanic/request_data.md index bf5ae4a8..82bf19c9 100644 --- a/docs/sanic/request_data.md +++ b/docs/sanic/request_data.md @@ -117,3 +117,11 @@ args.get('titles') # => 'Post 1' args.getlist('titles') # => ['Post 1', 'Post 2'] ``` + + +## Full API Reference + +```eval_rst +.. autoclass:: sanic.request.Request + :members: json, token, form, files, args, raw_args, cookies, ip, scheme, host, content_type, path, query_string, url +``` \ No newline at end of file diff --git a/docs/sanic/response.md b/docs/sanic/response.md index 9c3c95f7..91e0da83 100644 --- a/docs/sanic/response.md +++ b/docs/sanic/response.md @@ -110,3 +110,11 @@ def handle_request(request): status=200 ) ``` + + +## Full API Reference + +```eval_rst +.. automodule:: sanic.response + :members: json, text, raw, html, file, file_stream, stream, redirect +``` \ No newline at end of file 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/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..ce491881 100644 --- a/sanic/router.py +++ b/sanic/router.py @@ -99,7 +99,16 @@ class Router: return name, _type, pattern def add(self, uri, methods, handler, host=None, strict_slashes=False): + """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 + :return: Nothing + """ # add regular version self._add(uri, methods, handler, host) From 5d00717f39a44f0d9402a2963ab0b1fd89e17919 Mon Sep 17 00:00:00 2001 From: Yun Xu Date: Sun, 2 Jul 2017 10:02:04 -0700 Subject: [PATCH 02/11] improve doc and remove warnings --- docs/Makefile | 225 +++++++++++++++++++++++++++-- docs/conf.py | 1 - docs/index.rst | 3 + docs/make.bat | 265 +++++++++++++++++++++++++++++++++-- docs/sanic/api_reference.rst | 150 ++++++++++++++++++++ docs/sanic/request_data.md | 8 -- docs/sanic/response.md | 8 -- sanic/config.py | 2 +- sanic/exceptions.py | 7 +- 9 files changed, 628 insertions(+), 41 deletions(-) create mode 100644 docs/sanic/api_reference.rst 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 33f5097d..e254c183 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -148,7 +148,6 @@ suppress_warnings = ['image.nonlocal_uri'] # app setup hook def setup(app): app.add_config_value('recommonmark_config', { - 'auto_toc_tree_section': 'Contents', 'enable_eval_rst': True, 'enable_auto_doc_ref': True, }, True) 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/request_data.md b/docs/sanic/request_data.md index 82bf19c9..bf5ae4a8 100644 --- a/docs/sanic/request_data.md +++ b/docs/sanic/request_data.md @@ -117,11 +117,3 @@ args.get('titles') # => 'Post 1' args.getlist('titles') # => ['Post 1', 'Post 2'] ``` - - -## Full API Reference - -```eval_rst -.. autoclass:: sanic.request.Request - :members: json, token, form, files, args, raw_args, cookies, ip, scheme, host, content_type, path, query_string, url -``` \ No newline at end of file diff --git a/docs/sanic/response.md b/docs/sanic/response.md index 91e0da83..9c3c95f7 100644 --- a/docs/sanic/response.md +++ b/docs/sanic/response.md @@ -110,11 +110,3 @@ def handle_request(request): status=200 ) ``` - - -## Full API Reference - -```eval_rst -.. automodule:: sanic.response - :members: json, text, raw, html, file, file_stream, stream, redirect -``` \ No newline at end of file diff --git a/sanic/config.py b/sanic/config.py index e3563bc1..159b6197 100644 --- a/sanic/config.py +++ b/sanic/config.py @@ -195,7 +195,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 95e41b4a..c74f3873 100644 --- a/sanic/exceptions.py +++ b/sanic/exceptions.py @@ -206,8 +206,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 @@ -230,9 +230,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, From e48bd08095e3f6318d62494813d5be9fb1bbf0d4 Mon Sep 17 00:00:00 2001 From: Yun Xu Date: Sun, 2 Jul 2017 10:05:33 -0700 Subject: [PATCH 03/11] make flake8 happy --- sanic/exceptions.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sanic/exceptions.py b/sanic/exceptions.py index c74f3873..e3285d1d 100644 --- a/sanic/exceptions.py +++ b/sanic/exceptions.py @@ -206,8 +206,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 From eb9af8bceb16349089892ebc46d705f8de8c5c13 Mon Sep 17 00:00:00 2001 From: Andrew Svetlov Date: Sun, 9 Jul 2017 08:18:45 +0200 Subject: [PATCH 04/11] Drop aiohttp from benchmark table The reason is: aiohttp with disabled access log shows about 16,000 RPS on sanic's own benchmark. It's pretty much faster than 3,000 RPS from the table. I'm not a Sanic dev team member. You should not trust users to update this table but manage periodic updates yourself. If you don't want to do it --- it's up to you. Please just drop very incorrect and outdated numbers from README in this case. --- README.rst | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.rst b/README.rst index 8a7b2706..8da47f5a 100644 --- a/README.rst +++ b/README.rst @@ -34,8 +34,6 @@ not speed up requests. +-----------+-----------------------+----------------+---------------+ | 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 | +-----------+-----------------------+----------------+---------------+ From 3d75e6ed95c3fe83be93f7f73c03de0ca13b5721 Mon Sep 17 00:00:00 2001 From: Yun Xu Date: Mon, 10 Jul 2017 12:29:47 -0700 Subject: [PATCH 05/11] case-insensitive check for header fields --- sanic/request.py | 4 ++-- tests/test_requests.py | 22 ++++++++++++++-------- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/sanic/request.py b/sanic/request.py index e21b8282..da9a8cd0 100644 --- a/sanic/request.py +++ b/sanic/request.py @@ -242,11 +242,11 @@ def parse_multipart_form(body, boundary): form_header_value, form_parameters = parse_header( form_line[colon_index + 2:]) - if form_header_field == 'Content-Disposition': + if form_header_field.lower() == '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.lower() == 'content-type': file_type = form_header_value post_data = form_part[line_index:-4] 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) From 235687d98344b4090c7766de65a34770e2c8f6ab Mon Sep 17 00:00:00 2001 From: Yun Xu Date: Mon, 10 Jul 2017 12:37:21 -0700 Subject: [PATCH 06/11] should call lower just once --- sanic/request.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sanic/request.py b/sanic/request.py index da9a8cd0..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.lower() == '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.lower() == 'content-type': + elif form_header_field == 'content-type': file_type = form_header_value post_data = form_part[line_index:-4] From 04ff393875cd28b94e087676b845328d563b1f4b Mon Sep 17 00:00:00 2001 From: Stefane Fermigier Date: Mon, 10 Jul 2017 22:11:12 +0200 Subject: [PATCH 07/11] Add missing code block qualifier --- docs/sanic/blueprints.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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' From be0f3731b430bab7efdee4167d60ee30acc237fa Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Wed, 12 Jul 2017 22:26:58 +0900 Subject: [PATCH 08/11] ensure loop.close() and sys.exit() in gunicorn worker --- sanic/worker.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) 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) From 4265ad5f23425b428e9bc0f1de3b5e30675858f2 Mon Sep 17 00:00:00 2001 From: Raphael Deem Date: Wed, 12 Jul 2017 20:18:56 -0700 Subject: [PATCH 09/11] add versioning --- docs/sanic/versioning.md | 50 +++++++++++++++++++++++++++++++ sanic/app.py | 44 ++++++++++++++++----------- sanic/blueprints.py | 64 ++++++++++++++++++++++++---------------- sanic/router.py | 10 ++++++- tests/test_blueprints.py | 26 ++++++++++++++++ tests/test_routes.py | 21 +++++++++++++ 6 files changed, 170 insertions(+), 45 deletions(-) create mode 100644 docs/sanic/versioning.md 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/router.py b/sanic/router.py index ce491881..efc48f37 100644 --- a/sanic/router.py +++ b/sanic/router.py @@ -98,7 +98,8 @@ 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 @@ -107,8 +108,15 @@ class Router: :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/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_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') From 426e00b6f464b8c2be8f1f36fe5603d2033aacf7 Mon Sep 17 00:00:00 2001 From: zenix Date: Thu, 13 Jul 2017 15:09:04 +0900 Subject: [PATCH 10/11] dont let dictConfig influence already exists configs --- sanic/config.py | 1 + 1 file changed, 1 insertion(+) diff --git a/sanic/config.py b/sanic/config.py index a9bbff72..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, From b2017cae77721d02cdb3b847dc10b16867121df0 Mon Sep 17 00:00:00 2001 From: Andrew Svetlov Date: Thu, 13 Jul 2017 23:41:04 +0200 Subject: [PATCH 11/11] Drop benchmarks from readme --- README.rst | 26 -------------------------- 1 file changed, 26 deletions(-) diff --git a/README.rst b/README.rst index 8da47f5a..410bd0b8 100644 --- a/README.rst +++ b/README.rst @@ -11,32 +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 | -+-----------+-----------------------+----------------+---------------+ -| Tornado | Python 3.5 | 2,138 | 46.66ms | -+-----------+-----------------------+----------------+---------------+ - Hello World Example -------------------