├── .gitignore ├── LICENSE ├── README.rst ├── doc-requirements.txt ├── docs ├── Makefile ├── api │ ├── flask_aiohttp.rst │ └── modules.rst ├── conf.py ├── coroutine.rst ├── firstofall.rst ├── index.rst ├── make.bat └── websocket.rst ├── examples └── app.py ├── flask_aiohttp ├── __init__.py ├── handler.py ├── helper.py ├── tests │ ├── __init__.py │ └── test_aiowebsocket.py └── util.py ├── requirements.txt ├── setup.py └── tox.ini /.gitignore: -------------------------------------------------------------------------------- 1 | ### Windows ### 2 | # Windows image file caches 3 | Thumbs.db 4 | ehthumbs.db 5 | 6 | # Folder config file 7 | Desktop.ini 8 | 9 | # Recycle Bin used on file shares 10 | $RECYCLE.BIN/ 11 | 12 | # Windows Installer files 13 | *.cab 14 | *.msi 15 | *.msm 16 | *.msp 17 | 18 | # Windows shortcuts 19 | *.lnk 20 | 21 | 22 | ### OSX ### 23 | .DS_Store 24 | .AppleDouble 25 | .LSOverride 26 | 27 | # Icon must end with two \r 28 | Icon 29 | 30 | 31 | # Thumbnails 32 | ._* 33 | 34 | # Files that might appear in the root of a volume 35 | .DocumentRevisions-V100 36 | .fseventsd 37 | .Spotlight-V100 38 | .TemporaryItems 39 | .Trashes 40 | .VolumeIcon.icns 41 | 42 | # Directories potentially created on remote AFP share 43 | .AppleDB 44 | .AppleDesktop 45 | Network Trash Folder 46 | Temporary Items 47 | .apdisk 48 | 49 | 50 | ### Linux ### 51 | *~ 52 | 53 | # KDE directory preferences 54 | .directory 55 | 56 | # Linux trash folder which might appear on any partition or disk 57 | .Trash-* 58 | 59 | 60 | ### Python ### 61 | # Byte-compiled / optimized / DLL files 62 | __pycache__/ 63 | *.py[cod] 64 | 65 | # C extensions 66 | *.so 67 | 68 | # Distribution / packaging 69 | .Python 70 | env/ 71 | build/ 72 | develop-eggs/ 73 | dist/ 74 | downloads/ 75 | eggs/ 76 | .eggs/ 77 | lib/ 78 | lib64/ 79 | parts/ 80 | sdist/ 81 | var/ 82 | *.egg-info/ 83 | .installed.cfg 84 | *.egg 85 | 86 | # PyInstaller 87 | # Usually these files are written by a python script from a template 88 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 89 | *.manifest 90 | *.spec 91 | 92 | # Installer logs 93 | pip-log.txt 94 | pip-delete-this-directory.txt 95 | 96 | # Unit test / coverage reports 97 | htmlcov/ 98 | .tox/ 99 | .coverage 100 | .coverage.* 101 | .cache 102 | nosetests.xml 103 | coverage.xml 104 | *,cover 105 | 106 | # Translations 107 | *.mo 108 | *.pot 109 | 110 | # Django stuff: 111 | *.log 112 | 113 | # Sphinx documentation 114 | docs/_build/ 115 | 116 | # PyBuilder 117 | target/ 118 | 119 | 120 | ### PyCharm ### 121 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm 122 | 123 | *.iml 124 | 125 | ## Directory-based project format: 126 | .idea/ 127 | # if you remove the above rule, at least ignore the following: 128 | 129 | # User-specific stuff: 130 | # .idea/workspace.xml 131 | # .idea/tasks.xml 132 | # .idea/dictionaries 133 | 134 | # Sensitive or high-churn files: 135 | # .idea/dataSources.ids 136 | # .idea/dataSources.xml 137 | # .idea/sqlDataSources.xml 138 | # .idea/dynamic.xml 139 | # .idea/uiDesigner.xml 140 | 141 | # Gradle: 142 | # .idea/gradle.xml 143 | # .idea/libraries 144 | 145 | # Mongo Explorer plugin: 146 | # .idea/mongoSettings.xml 147 | 148 | ## File-based project format: 149 | *.ipr 150 | *.iws 151 | 152 | ## Plugin-specific files: 153 | 154 | # IntelliJ 155 | /out/ 156 | 157 | # mpeltonen/sbt-idea plugin 158 | .idea_modules/ 159 | 160 | # JIRA plugin 161 | atlassian-ide-plugin.xml 162 | 163 | # Crashlytics plugin (for Android Studio and IntelliJ) 164 | com_crashlytics_export_strings.xml 165 | crashlytics.properties 166 | crashlytics-build.properties 167 | 168 | 169 | ### Emacs ### 170 | # -*- mode: gitignore; -*- 171 | *~ 172 | \#*\# 173 | /.emacs.desktop 174 | /.emacs.desktop.lock 175 | *.elc 176 | auto-save-list 177 | tramp 178 | .\#* 179 | 180 | # Org-mode 181 | .org-id-locations 182 | *_archive 183 | 184 | # flymake-mode 185 | *_flymake.* 186 | 187 | # eshell files 188 | /eshell/history 189 | /eshell/lastdir 190 | 191 | # elpa packages 192 | /elpa/ 193 | 194 | # reftex files 195 | *.rel 196 | 197 | # AUCTeX auto folder 198 | /auto/ 199 | 200 | # cask packages 201 | .cask/ 202 | 203 | 204 | ### Vim ### 205 | [._]*.s[a-w][a-z] 206 | [._]s[a-w][a-z] 207 | *.un~ 208 | Session.vim 209 | .netrwhist 210 | *~ 211 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | Copyright (c) 2016 Geonu Choi 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 5 | 6 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 7 | 8 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 9 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | Flask-aiohttp --- Asynchronous Flask using aiohttp. 2 | =================================================== 3 | 4 | .. image:: http://unmaintained.tech/badge.svg 5 | :target: http://unmaintained.tech/ 6 | :alt: No Maintenance Intended 7 | 8 | **EXPERIMENTAL** 9 | ---------------- 10 | 11 | I made this project for testing compatability between WSGI & Async IO. 12 | 13 | Since WSGI has no consideration of Async IO, Flask-aiohttp cannot be perfect. 14 | 15 | So, I don't recommend you to use this library for production. Libraries that was made for Async IO would be better choice (Like gevent, Tornado or AioHTTP). 16 | 17 | 18 | Features 19 | -------- 20 | 21 | * Coroutine view function 22 | 23 | You can make view function to asyncio coroutine using :func:`async`. :: 24 | 25 | @app.route('/slow') 26 | @async 27 | def slow(): 28 | yield from asyncio.sleep(3) 29 | return 'sorry!' 30 | 31 | * Asynchronous I/O in view function 32 | 33 | You can do asynchronous I/O in your view function. :: 34 | 35 | @app.route('/zuck') 36 | @async 37 | def zuck(): 38 | response = yield from aiohttp.request( 39 | 'GET', 'https://graph.facebook.com/zuck') 40 | data = yield from response.read() 41 | return data 42 | 43 | * Websocket 44 | 45 | You can use aiohttp's WebSocketResponse in your Flask view function. :: 46 | 47 | @app.route('/echo') 48 | @websocket 49 | def echo(): 50 | while True: 51 | msg = yield from aio.ws.receive_msg() 52 | 53 | if msg.tp == aiohttp.MsgType.text: 54 | aio.ws.send_str(msg.data) 55 | elif msg.tp == aiohttp.MsgType.close: 56 | print('websocket connection closed') 57 | break 58 | elif msg.tp == aiohttp.MsgType.error: 59 | print('ws connection closed with exception %s', 60 | aio.ws.exception()) 61 | break 62 | 63 | * Flask URL routing 64 | 65 | It surely compatible with flask's view routing. :: 66 | 67 | @app.route('/param/') 68 | @websocket 69 | def param(arg): 70 | while True: 71 | msg = yield from aio.ws.receive_msg() 72 | 73 | if msg.tp == aiohttp.MsgType.text: 74 | aio.ws.send_str(arg) 75 | elif msg.tp == aiohttp.MsgType.close: 76 | print('websocket connection closed') 77 | break 78 | elif msg.tp == aiohttp.MsgType.error: 79 | print('ws connection closed with exception %s', 80 | aio.ws.exception()) 81 | break 82 | 83 | Usage 84 | ----- 85 | 86 | You can use it just like plain Flask extensions. :: 87 | 88 | import flask 89 | from flask.ext.aiohttp import AioHTTP 90 | 91 | app = flask.Flask(__name__) 92 | aio = AioHTTP(app) 93 | 94 | or :: 95 | 96 | aio = AioHTTP() 97 | aio.init_app(app) 98 | 99 | But, you have to run the application using our runner to be run asynchronously. 100 | :: 101 | 102 | if __name__ == '__main__': 103 | aio.run(app, debug=True) 104 | 105 | Documentation 106 | ------------- 107 | 108 | `Read the docs` 109 | -------------------------------------------------------------------------------- /doc-requirements.txt: -------------------------------------------------------------------------------- 1 | -r requirements.txt 2 | sphinx 3 | alabaster 4 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = _build 9 | 10 | # User-friendly check for sphinx-build 11 | ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) 12 | $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) 13 | endif 14 | 15 | # Internal variables. 16 | PAPEROPT_a4 = -D latex_paper_size=a4 17 | PAPEROPT_letter = -D latex_paper_size=letter 18 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 19 | # the i18n builder cannot share the environment and doctrees with the others 20 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 21 | 22 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest coverage gettext 23 | 24 | help: 25 | @echo "Please use \`make ' where is one of" 26 | @echo " html to make standalone HTML files" 27 | @echo " dirhtml to make HTML files named index.html in directories" 28 | @echo " singlehtml to make a single large HTML file" 29 | @echo " pickle to make pickle files" 30 | @echo " json to make JSON files" 31 | @echo " htmlhelp to make HTML files and a HTML help project" 32 | @echo " qthelp to make HTML files and a qthelp project" 33 | @echo " applehelp to make an Apple Help Book" 34 | @echo " devhelp to make HTML files and a Devhelp project" 35 | @echo " epub to make an epub" 36 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 37 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 38 | @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" 39 | @echo " text to make text files" 40 | @echo " man to make manual pages" 41 | @echo " texinfo to make Texinfo files" 42 | @echo " info to make Texinfo files and run them through makeinfo" 43 | @echo " gettext to make PO message catalogs" 44 | @echo " changes to make an overview of all changed/added/deprecated items" 45 | @echo " xml to make Docutils-native XML files" 46 | @echo " pseudoxml to make pseudoxml-XML files for display purposes" 47 | @echo " linkcheck to check all external links for integrity" 48 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 49 | @echo " coverage to run coverage check of the documentation (if enabled)" 50 | 51 | clean: 52 | rm -rf $(BUILDDIR)/* 53 | 54 | html: 55 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 56 | @echo 57 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 58 | 59 | dirhtml: 60 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 61 | @echo 62 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 63 | 64 | singlehtml: 65 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 66 | @echo 67 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 68 | 69 | pickle: 70 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 71 | @echo 72 | @echo "Build finished; now you can process the pickle files." 73 | 74 | json: 75 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 76 | @echo 77 | @echo "Build finished; now you can process the JSON files." 78 | 79 | htmlhelp: 80 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 81 | @echo 82 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 83 | ".hhp project file in $(BUILDDIR)/htmlhelp." 84 | 85 | qthelp: 86 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 87 | @echo 88 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 89 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 90 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/Flask-aiohttp.qhcp" 91 | @echo "To view the help file:" 92 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Flask-aiohttp.qhc" 93 | 94 | applehelp: 95 | $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp 96 | @echo 97 | @echo "Build finished. The help book is in $(BUILDDIR)/applehelp." 98 | @echo "N.B. You won't be able to view it unless you put it in" \ 99 | "~/Library/Documentation/Help or install it in your application" \ 100 | "bundle." 101 | 102 | devhelp: 103 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 104 | @echo 105 | @echo "Build finished." 106 | @echo "To view the help file:" 107 | @echo "# mkdir -p $$HOME/.local/share/devhelp/Flask-aiohttp" 108 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Flask-aiohttp" 109 | @echo "# devhelp" 110 | 111 | epub: 112 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 113 | @echo 114 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 115 | 116 | latex: 117 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 118 | @echo 119 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 120 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 121 | "(use \`make latexpdf' here to do that automatically)." 122 | 123 | latexpdf: 124 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 125 | @echo "Running LaTeX files through pdflatex..." 126 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 127 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 128 | 129 | latexpdfja: 130 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 131 | @echo "Running LaTeX files through platex and dvipdfmx..." 132 | $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja 133 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 134 | 135 | text: 136 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 137 | @echo 138 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 139 | 140 | man: 141 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 142 | @echo 143 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 144 | 145 | texinfo: 146 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 147 | @echo 148 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 149 | @echo "Run \`make' in that directory to run these through makeinfo" \ 150 | "(use \`make info' here to do that automatically)." 151 | 152 | info: 153 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 154 | @echo "Running Texinfo files through makeinfo..." 155 | make -C $(BUILDDIR)/texinfo info 156 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 157 | 158 | gettext: 159 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 160 | @echo 161 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 162 | 163 | changes: 164 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 165 | @echo 166 | @echo "The overview file is in $(BUILDDIR)/changes." 167 | 168 | linkcheck: 169 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 170 | @echo 171 | @echo "Link check complete; look for any errors in the above output " \ 172 | "or in $(BUILDDIR)/linkcheck/output.txt." 173 | 174 | doctest: 175 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 176 | @echo "Testing of doctests in the sources finished, look at the " \ 177 | "results in $(BUILDDIR)/doctest/output.txt." 178 | 179 | coverage: 180 | $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage 181 | @echo "Testing of coverage in the sources finished, look at the " \ 182 | "results in $(BUILDDIR)/coverage/python.txt." 183 | 184 | xml: 185 | $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml 186 | @echo 187 | @echo "Build finished. The XML files are in $(BUILDDIR)/xml." 188 | 189 | pseudoxml: 190 | $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml 191 | @echo 192 | @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." 193 | -------------------------------------------------------------------------------- /docs/api/flask_aiohttp.rst: -------------------------------------------------------------------------------- 1 | flask_aiohttp package 2 | ===================== 3 | 4 | Submodules 5 | ---------- 6 | 7 | flask_aiohttp.handler module 8 | ---------------------------- 9 | 10 | .. automodule:: flask_aiohttp.handler 11 | :members: 12 | :undoc-members: 13 | :show-inheritance: 14 | 15 | flask_aiohttp.helper module 16 | --------------------------- 17 | 18 | .. automodule:: flask_aiohttp.helper 19 | :members: 20 | :undoc-members: 21 | :show-inheritance: 22 | 23 | flask_aiohttp.util module 24 | ------------------------- 25 | 26 | .. automodule:: flask_aiohttp.util 27 | :members: 28 | :undoc-members: 29 | :show-inheritance: 30 | 31 | 32 | Module contents 33 | --------------- 34 | 35 | .. automodule:: flask_aiohttp 36 | :members: 37 | :undoc-members: 38 | :show-inheritance: 39 | -------------------------------------------------------------------------------- /docs/api/modules.rst: -------------------------------------------------------------------------------- 1 | flask_aiohttp 2 | ============= 3 | 4 | .. toctree:: 5 | :maxdepth: 4 6 | 7 | flask_aiohttp 8 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # 4 | # Flask-aiohttp documentation build configuration file, created by 5 | # sphinx-quickstart on Fri Apr 3 15:37:04 2015. 6 | # 7 | # This file is execfile()d with the current directory set to its 8 | # containing dir. 9 | # 10 | # Note that not all possible configuration values are present in this 11 | # autogenerated file. 12 | # 13 | # All configuration values have a default; values that are commented out 14 | # serve to show the default. 15 | 16 | import sys 17 | import os 18 | import shlex 19 | 20 | # If extensions (or modules to document with autodoc) are in another directory, 21 | # add these directories to sys.path here. If the directory is relative to the 22 | # documentation root, use os.path.abspath to make it absolute, like shown here. 23 | #sys.path.insert(0, os.path.abspath('.')) 24 | 25 | # -- General configuration ------------------------------------------------ 26 | 27 | # If your documentation needs a minimal Sphinx version, state it here. 28 | #needs_sphinx = '1.0' 29 | 30 | # Add any Sphinx extension module names here, as strings. They can be 31 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 32 | # ones. 33 | extensions = [ 34 | 'sphinx.ext.autodoc', 35 | 'sphinx.ext.viewcode', 36 | ] 37 | 38 | # Add any paths that contain templates here, relative to this directory. 39 | templates_path = ['_templates'] 40 | 41 | # The suffix(es) of source filenames. 42 | # You can specify multiple suffix as a list of string: 43 | # source_suffix = ['.rst', '.md'] 44 | source_suffix = '.rst' 45 | 46 | # The encoding of source files. 47 | #source_encoding = 'utf-8-sig' 48 | 49 | # The master toctree document. 50 | master_doc = 'index' 51 | 52 | # General information about the project. 53 | project = 'Flask-aiohttp' 54 | copyright = '2015, Geonu Choi' 55 | author = 'Geonu Choi' 56 | 57 | # The version info for the project you're documenting, acts as replacement for 58 | # |version| and |release|, also used in various other places throughout the 59 | # built documents. 60 | # 61 | # The short X.Y version. 62 | version = '0.1.0' 63 | # The full version, including alpha/beta/rc tags. 64 | release = '0.1.0' 65 | 66 | # The language for content autogenerated by Sphinx. Refer to documentation 67 | # for a list of supported languages. 68 | # 69 | # This is also used if you do content translation via gettext catalogs. 70 | # Usually you set "language" from the command line for these cases. 71 | language = None 72 | 73 | # There are two options for replacing |today|: either, you set today to some 74 | # non-false value, then it is used: 75 | #today = '' 76 | # Else, today_fmt is used as the format for a strftime call. 77 | #today_fmt = '%B %d, %Y' 78 | 79 | # List of patterns, relative to source directory, that match files and 80 | # directories to ignore when looking for source files. 81 | exclude_patterns = ['_build'] 82 | 83 | # The reST default role (used for this markup: `text`) to use for all 84 | # documents. 85 | #default_role = None 86 | 87 | # If true, '()' will be appended to :func: etc. cross-reference text. 88 | #add_function_parentheses = True 89 | 90 | # If true, the current module name will be prepended to all description 91 | # unit titles (such as .. function::). 92 | #add_module_names = True 93 | 94 | # If true, sectionauthor and moduleauthor directives will be shown in the 95 | # output. They are ignored by default. 96 | #show_authors = False 97 | 98 | # The name of the Pygments (syntax highlighting) style to use. 99 | pygments_style = 'sphinx' 100 | 101 | # A list of ignored prefixes for module index sorting. 102 | #modindex_common_prefix = [] 103 | 104 | # If true, keep warnings as "system message" paragraphs in the built documents. 105 | #keep_warnings = False 106 | 107 | # If true, `todo` and `todoList` produce output, else they produce nothing. 108 | todo_include_todos = False 109 | 110 | 111 | # -- Options for HTML output ---------------------------------------------- 112 | 113 | # The theme to use for HTML and HTML Help pages. See the documentation for 114 | # a list of builtin themes. 115 | html_theme = 'alabaster' 116 | on_rtd = os.environ.get('READTHEDOCS', None) == 'True' 117 | 118 | if on_rtd: # only import and set the theme if we're building docs locally 119 | html_theme = 'sphinx_rtd_theme' 120 | 121 | # Theme options are theme-specific and customize the look and feel of a theme 122 | # further. For a list of options available for each theme, see the 123 | # documentation. 124 | #html_theme_options = {} 125 | 126 | # Add any paths that contain custom themes here, relative to this directory. 127 | #html_theme_path = [] 128 | 129 | # The name for this set of Sphinx documents. If None, it defaults to 130 | # " v documentation". 131 | #html_title = None 132 | 133 | # A shorter title for the navigation bar. Default is the same as html_title. 134 | #html_short_title = None 135 | 136 | # The name of an image file (relative to this directory) to place at the top 137 | # of the sidebar. 138 | #html_logo = None 139 | 140 | # The name of an image file (within the static path) to use as favicon of the 141 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 142 | # pixels large. 143 | #html_favicon = None 144 | 145 | # Add any paths that contain custom static files (such as style sheets) here, 146 | # relative to this directory. They are copied after the builtin static files, 147 | # so a file named "default.css" will overwrite the builtin "default.css". 148 | html_static_path = ['_static'] 149 | 150 | # Add any extra paths that contain custom files (such as robots.txt or 151 | # .htaccess) here, relative to this directory. These files are copied 152 | # directly to the root of the documentation. 153 | #html_extra_path = [] 154 | 155 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 156 | # using the given strftime format. 157 | #html_last_updated_fmt = '%b %d, %Y' 158 | 159 | # If true, SmartyPants will be used to convert quotes and dashes to 160 | # typographically correct entities. 161 | #html_use_smartypants = True 162 | 163 | # Custom sidebar templates, maps document names to template names. 164 | #html_sidebars = {} 165 | 166 | # Additional templates that should be rendered to pages, maps page names to 167 | # template names. 168 | #html_additional_pages = {} 169 | 170 | # If false, no module index is generated. 171 | #html_domain_indices = True 172 | 173 | # If false, no index is generated. 174 | #html_use_index = True 175 | 176 | # If true, the index is split into individual pages for each letter. 177 | #html_split_index = False 178 | 179 | # If true, links to the reST sources are added to the pages. 180 | #html_show_sourcelink = True 181 | 182 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 183 | #html_show_sphinx = True 184 | 185 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 186 | #html_show_copyright = True 187 | 188 | # If true, an OpenSearch description file will be output, and all pages will 189 | # contain a tag referring to it. The value of this option must be the 190 | # base URL from which the finished HTML is served. 191 | #html_use_opensearch = '' 192 | 193 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 194 | #html_file_suffix = None 195 | 196 | # Language to be used for generating the HTML full-text search index. 197 | # Sphinx supports the following languages: 198 | # 'da', 'de', 'en', 'es', 'fi', 'fr', 'h', 'it', 'ja' 199 | # 'nl', 'no', 'pt', 'ro', 'r', 'sv', 'tr' 200 | #html_search_language = 'en' 201 | 202 | # A dictionary with options for the search language support, empty by default. 203 | # Now only 'ja' uses this config value 204 | #html_search_options = {'type': 'default'} 205 | 206 | # The name of a javascript file (relative to the configuration directory) that 207 | # implements a search results scorer. If empty, the default will be used. 208 | #html_search_scorer = 'scorer.js' 209 | 210 | # Output file base name for HTML help builder. 211 | htmlhelp_basename = 'Flask-aiohttpdoc' 212 | 213 | # -- Options for LaTeX output --------------------------------------------- 214 | 215 | latex_elements = { 216 | # The paper size ('letterpaper' or 'a4paper'). 217 | #'papersize': 'letterpaper', 218 | 219 | # The font size ('10pt', '11pt' or '12pt'). 220 | #'pointsize': '10pt', 221 | 222 | # Additional stuff for the LaTeX preamble. 223 | #'preamble': '', 224 | 225 | # Latex figure (float) alignment 226 | #'figure_align': 'htbp', 227 | } 228 | 229 | # Grouping the document tree into LaTeX files. List of tuples 230 | # (source start file, target name, title, 231 | # author, documentclass [howto, manual, or own class]). 232 | latex_documents = [ 233 | (master_doc, 'Flask-aiohttp.tex', 'Flask-aiohttp Documentation', 234 | 'Geonu Choi', 'manual'), 235 | ] 236 | 237 | # The name of an image file (relative to this directory) to place at the top of 238 | # the title page. 239 | #latex_logo = None 240 | 241 | # For "manual" documents, if this is true, then toplevel headings are parts, 242 | # not chapters. 243 | #latex_use_parts = False 244 | 245 | # If true, show page references after internal links. 246 | #latex_show_pagerefs = False 247 | 248 | # If true, show URL addresses after external links. 249 | #latex_show_urls = False 250 | 251 | # Documents to append as an appendix to all manuals. 252 | #latex_appendices = [] 253 | 254 | # If false, no module index is generated. 255 | #latex_domain_indices = True 256 | 257 | 258 | # -- Options for manual page output --------------------------------------- 259 | 260 | # One entry per manual page. List of tuples 261 | # (source start file, name, description, authors, manual section). 262 | man_pages = [ 263 | (master_doc, 'flask-aiohttp', 'Flask-aiohttp Documentation', 264 | [author], 1) 265 | ] 266 | 267 | # If true, show URL addresses after external links. 268 | #man_show_urls = False 269 | 270 | 271 | # -- Options for Texinfo output ------------------------------------------- 272 | 273 | # Grouping the document tree into Texinfo files. List of tuples 274 | # (source start file, target name, title, author, 275 | # dir menu entry, description, category) 276 | texinfo_documents = [ 277 | (master_doc, 'Flask-aiohttp', 'Flask-aiohttp Documentation', 278 | author, 'Flask-aiohttp', 'One line description of project.', 279 | 'Miscellaneous'), 280 | ] 281 | 282 | # Documents to append as an appendix to all manuals. 283 | #texinfo_appendices = [] 284 | 285 | # If false, no module index is generated. 286 | #texinfo_domain_indices = True 287 | 288 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 289 | #texinfo_show_urls = 'footnote' 290 | 291 | # If true, do not generate a @detailmenu in the "Top" node's menu. 292 | #texinfo_no_detailmenu = False 293 | -------------------------------------------------------------------------------- /docs/coroutine.rst: -------------------------------------------------------------------------------- 1 | Coroutine 2 | ========= 3 | 4 | You can use asyncio's coroutine [#]_ in flask's view function using this 5 | extension. :: 6 | 7 | from flask.ext.aiohttp import async 8 | 9 | @app.route('/late-response') 10 | @async # It marks view function as asyncio coroutine 11 | def late_response(): 12 | yield from asyncio.sleep(3) 13 | return "Sorry, I'm late!" 14 | 15 | 16 | So, you can use aiohttp's request modules in flask. :: 17 | 18 | from flask.ext.aiohttp import async 19 | 20 | @app.route('/zuck') 21 | @async 22 | def zuck(): 23 | response = yield from aiohttp.request( 24 | 'GET', 'https://graph.facebook.com/zuck') 25 | data = yield from response.read() 26 | 27 | return data 28 | 29 | And you can surely use flask's common feature. :: 30 | 31 | import json 32 | import urllib.parse 33 | 34 | from flask import current_app, request 35 | from flask.ext.aiohttp import async 36 | 37 | @app.route('/fb/') 38 | @async 39 | def facebook_profile(name): 40 | if request.args.get('secure', False): 41 | url = 'https://graph.facebook.com/' 42 | else: 43 | url = 'http://graph.facebook.com/' 44 | url = url + urllib.parse.quote(name) 45 | response = yield from aiohttp.request('GET', url) 46 | data = yield from response.read() 47 | data = json.loads(data) 48 | 49 | def stream(): 50 | if request.args.get('wrap', False): 51 | data = { 52 | 'data': data 53 | } 54 | yield json.dumps(data) 55 | return current_app.response_class(stream()) 56 | 57 | 58 | .. note:: 59 | 60 | Since coroutine implemented by using streaming response, you have to be 61 | care about using request hook. 62 | 63 | :func:`~flask.app.Flask.before_request`, 64 | :func:`~flask.app.Flask.after_request`, 65 | :func:`~flask.app.Flask.teardown_request` will be called twice. 66 | 67 | Each asynchronous request's functions will be called in following sequence. 68 | 69 | 1. :func:`~flask.app.Flask.before_request` 70 | 2. **Flask-aiohttp's** streaming response containing coroutine 71 | 3. :func:`~flask.app.Flask.after_request` 72 | 4. :func:`~flask.app.Flask.teardown_request` 73 | 74 | *Streaming response starts here* 75 | 76 | 5. :func:`~flask.app.Flask.before_request` 77 | 6. Your coroutine response 78 | 7. :func:`~flask.app.Flask.after_request` 79 | 8. :func:`~flask.app.Flask.teardown_request` 80 | 81 | 82 | .. [#] https://docs.python.org/3/library/asyncio-task.html#coroutines 83 | -------------------------------------------------------------------------------- /docs/firstofall.rst: -------------------------------------------------------------------------------- 1 | First of All 2 | ============ 3 | 4 | First of all, you can use this extension like another plain Flask extensions. 5 | 6 | For example, You can initialize the extension like :: 7 | 8 | from flask import Flask 9 | from flask.ext.aiohttp import AioHTTP 10 | 11 | app = Flask(__name__) 12 | 13 | aio = AioHTTP(app) 14 | 15 | or you can initialize it later :: 16 | 17 | from flask import Flask 18 | from flask.ext.aiohttp import AioHTTP 19 | 20 | def create_app(): 21 | app = Flask(__name__) 22 | aio.init_app(app) 23 | 24 | aio = AioHTTP() 25 | 26 | But, its application running method is different then plain Flask apps one. 27 | You have to run it on the asyncio's run loop. :: 28 | 29 | if __name__ == '__main__': 30 | aio.run(app) 31 | 32 | You can also debug it using werkzeug debugger :: 33 | 34 | if __name__ == '__main__': 35 | aio.run(app, debug=True) 36 | 37 | You can use gunicorn using aiohttp 38 | 39 | In myapp.py (or some module name you want to use) :: 40 | 41 | from you_application_module import app as flask_app 42 | 43 | app = flask_app.aiohttp_app 44 | 45 | And run gunicorn by :: 46 | 47 | gunicorn myapp:app -k aiohttp.worker.GunicornWebWorker -b localhost:8080 48 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | Flask-aiohttp --- Asynchronous Flask application with aiohttp. 2 | ============================================================== 3 | 4 | **Flask-aiohttp** adds asyncio_ & 5 | `websocket `_ [#]_ support to Flask 6 | using aiohttp_. 7 | 8 | .. _asyncio: https://docs.python.org/3/library/asyncio.html 9 | .. _aiohttp: https://aiohttp.readthedocs.org/ 10 | 11 | For example :: 12 | 13 | @app.route('/use-external-api') 14 | @async 15 | def use_external_api(): 16 | response = yield from aiohttp.request( 17 | 'GET', 'https://api.example.com/data/1') 18 | data = yield from response.read() 19 | 20 | return data 21 | 22 | You can find more guides from following list: 23 | 24 | .. toctree:: 25 | :maxdepth: 1 26 | 27 | firstofall 28 | coroutine 29 | websocket 30 | 31 | API 32 | 33 | .. toctree:: 34 | :maxdepth: 1 35 | 36 | api/modules 37 | 38 | 39 | Indices and tables 40 | ================== 41 | 42 | * :ref:`genindex` 43 | * :ref:`modindex` 44 | * :ref:`search` 45 | 46 | .. [#] http://aiohttp.readthedocs.org/en/v0.15.1/web.html#websockets 47 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | REM Command file for Sphinx documentation 4 | 5 | if "%SPHINXBUILD%" == "" ( 6 | set SPHINXBUILD=sphinx-build 7 | ) 8 | set BUILDDIR=_build 9 | set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . 10 | set I18NSPHINXOPTS=%SPHINXOPTS% . 11 | if NOT "%PAPER%" == "" ( 12 | set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% 13 | set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% 14 | ) 15 | 16 | if "%1" == "" goto help 17 | 18 | if "%1" == "help" ( 19 | :help 20 | echo.Please use `make ^` where ^ is one of 21 | echo. html to make standalone HTML files 22 | echo. dirhtml to make HTML files named index.html in directories 23 | echo. singlehtml to make a single large HTML file 24 | echo. pickle to make pickle files 25 | echo. json to make JSON files 26 | echo. htmlhelp to make HTML files and a HTML help project 27 | echo. qthelp to make HTML files and a qthelp project 28 | echo. devhelp to make HTML files and a Devhelp project 29 | echo. epub to make an epub 30 | echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter 31 | echo. text to make text files 32 | echo. man to make manual pages 33 | echo. texinfo to make Texinfo files 34 | echo. gettext to make PO message catalogs 35 | echo. changes to make an overview over all changed/added/deprecated items 36 | echo. xml to make Docutils-native XML files 37 | echo. pseudoxml to make pseudoxml-XML files for display purposes 38 | echo. linkcheck to check all external links for integrity 39 | echo. doctest to run all doctests embedded in the documentation if enabled 40 | echo. coverage to run coverage check of the documentation if enabled 41 | goto end 42 | ) 43 | 44 | if "%1" == "clean" ( 45 | for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i 46 | del /q /s %BUILDDIR%\* 47 | goto end 48 | ) 49 | 50 | 51 | REM Check if sphinx-build is available and fallback to Python version if any 52 | %SPHINXBUILD% 2> nul 53 | if errorlevel 9009 goto sphinx_python 54 | goto sphinx_ok 55 | 56 | :sphinx_python 57 | 58 | set SPHINXBUILD=python -m sphinx.__init__ 59 | %SPHINXBUILD% 2> nul 60 | if errorlevel 9009 ( 61 | echo. 62 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 63 | echo.installed, then set the SPHINXBUILD environment variable to point 64 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 65 | echo.may add the Sphinx directory to PATH. 66 | echo. 67 | echo.If you don't have Sphinx installed, grab it from 68 | echo.http://sphinx-doc.org/ 69 | exit /b 1 70 | ) 71 | 72 | :sphinx_ok 73 | 74 | 75 | if "%1" == "html" ( 76 | %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html 77 | if errorlevel 1 exit /b 1 78 | echo. 79 | echo.Build finished. The HTML pages are in %BUILDDIR%/html. 80 | goto end 81 | ) 82 | 83 | if "%1" == "dirhtml" ( 84 | %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml 85 | if errorlevel 1 exit /b 1 86 | echo. 87 | echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. 88 | goto end 89 | ) 90 | 91 | if "%1" == "singlehtml" ( 92 | %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml 93 | if errorlevel 1 exit /b 1 94 | echo. 95 | echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. 96 | goto end 97 | ) 98 | 99 | if "%1" == "pickle" ( 100 | %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle 101 | if errorlevel 1 exit /b 1 102 | echo. 103 | echo.Build finished; now you can process the pickle files. 104 | goto end 105 | ) 106 | 107 | if "%1" == "json" ( 108 | %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json 109 | if errorlevel 1 exit /b 1 110 | echo. 111 | echo.Build finished; now you can process the JSON files. 112 | goto end 113 | ) 114 | 115 | if "%1" == "htmlhelp" ( 116 | %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp 117 | if errorlevel 1 exit /b 1 118 | echo. 119 | echo.Build finished; now you can run HTML Help Workshop with the ^ 120 | .hhp project file in %BUILDDIR%/htmlhelp. 121 | goto end 122 | ) 123 | 124 | if "%1" == "qthelp" ( 125 | %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp 126 | if errorlevel 1 exit /b 1 127 | echo. 128 | echo.Build finished; now you can run "qcollectiongenerator" with the ^ 129 | .qhcp project file in %BUILDDIR%/qthelp, like this: 130 | echo.^> qcollectiongenerator %BUILDDIR%\qthelp\Flask-aiohttp.qhcp 131 | echo.To view the help file: 132 | echo.^> assistant -collectionFile %BUILDDIR%\qthelp\Flask-aiohttp.ghc 133 | goto end 134 | ) 135 | 136 | if "%1" == "devhelp" ( 137 | %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp 138 | if errorlevel 1 exit /b 1 139 | echo. 140 | echo.Build finished. 141 | goto end 142 | ) 143 | 144 | if "%1" == "epub" ( 145 | %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub 146 | if errorlevel 1 exit /b 1 147 | echo. 148 | echo.Build finished. The epub file is in %BUILDDIR%/epub. 149 | goto end 150 | ) 151 | 152 | if "%1" == "latex" ( 153 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 154 | if errorlevel 1 exit /b 1 155 | echo. 156 | echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. 157 | goto end 158 | ) 159 | 160 | if "%1" == "latexpdf" ( 161 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 162 | cd %BUILDDIR%/latex 163 | make all-pdf 164 | cd %~dp0 165 | echo. 166 | echo.Build finished; the PDF files are in %BUILDDIR%/latex. 167 | goto end 168 | ) 169 | 170 | if "%1" == "latexpdfja" ( 171 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 172 | cd %BUILDDIR%/latex 173 | make all-pdf-ja 174 | cd %~dp0 175 | echo. 176 | echo.Build finished; the PDF files are in %BUILDDIR%/latex. 177 | goto end 178 | ) 179 | 180 | if "%1" == "text" ( 181 | %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text 182 | if errorlevel 1 exit /b 1 183 | echo. 184 | echo.Build finished. The text files are in %BUILDDIR%/text. 185 | goto end 186 | ) 187 | 188 | if "%1" == "man" ( 189 | %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man 190 | if errorlevel 1 exit /b 1 191 | echo. 192 | echo.Build finished. The manual pages are in %BUILDDIR%/man. 193 | goto end 194 | ) 195 | 196 | if "%1" == "texinfo" ( 197 | %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo 198 | if errorlevel 1 exit /b 1 199 | echo. 200 | echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. 201 | goto end 202 | ) 203 | 204 | if "%1" == "gettext" ( 205 | %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale 206 | if errorlevel 1 exit /b 1 207 | echo. 208 | echo.Build finished. The message catalogs are in %BUILDDIR%/locale. 209 | goto end 210 | ) 211 | 212 | if "%1" == "changes" ( 213 | %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes 214 | if errorlevel 1 exit /b 1 215 | echo. 216 | echo.The overview file is in %BUILDDIR%/changes. 217 | goto end 218 | ) 219 | 220 | if "%1" == "linkcheck" ( 221 | %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck 222 | if errorlevel 1 exit /b 1 223 | echo. 224 | echo.Link check complete; look for any errors in the above output ^ 225 | or in %BUILDDIR%/linkcheck/output.txt. 226 | goto end 227 | ) 228 | 229 | if "%1" == "doctest" ( 230 | %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest 231 | if errorlevel 1 exit /b 1 232 | echo. 233 | echo.Testing of doctests in the sources finished, look at the ^ 234 | results in %BUILDDIR%/doctest/output.txt. 235 | goto end 236 | ) 237 | 238 | if "%1" == "coverage" ( 239 | %SPHINXBUILD% -b coverage %ALLSPHINXOPTS% %BUILDDIR%/coverage 240 | if errorlevel 1 exit /b 1 241 | echo. 242 | echo.Testing of coverage in the sources finished, look at the ^ 243 | results in %BUILDDIR%/coverage/python.txt. 244 | goto end 245 | ) 246 | 247 | if "%1" == "xml" ( 248 | %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml 249 | if errorlevel 1 exit /b 1 250 | echo. 251 | echo.Build finished. The XML files are in %BUILDDIR%/xml. 252 | goto end 253 | ) 254 | 255 | if "%1" == "pseudoxml" ( 256 | %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml 257 | if errorlevel 1 exit /b 1 258 | echo. 259 | echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. 260 | goto end 261 | ) 262 | 263 | :end 264 | -------------------------------------------------------------------------------- /docs/websocket.rst: -------------------------------------------------------------------------------- 1 | WebSocket 2 | ========= 3 | 4 | Flask-aiohttp injects :class:`~aiohttp.web.WebSocketResponse` into your WSGI 5 | environ, and provides api to use it. 6 | 7 | This is not elegant solution for using websocket. But, it would be best 8 | solution for now. 9 | 10 | :: 11 | 12 | from flask.ext.aiohttp import websocket 13 | 14 | 15 | @app.route('/echo') 16 | @websocket 17 | def echo(): 18 | while True: 19 | msg = yield from aio.ws.receive_msg() 20 | 21 | if msg.tp == aiohttp.MsgType.text: 22 | aio.ws.send_str(msg.data) 23 | elif msg.tp == aiohttp.MsgType.close: 24 | print('websocket connection closed') 25 | break 26 | elif msg.tp == aiohttp.MsgType.error: 27 | print('ws connection closed with exception %s', 28 | aio.ws.exception()) 29 | break 30 | 31 | 32 | You also can use most features of flask with websocket. :: 33 | 34 | from flask.ext.aiohttp import websocket 35 | 36 | 37 | @app.route('/hello/') 38 | @websocket 39 | def hello(name): 40 | while True: 41 | msg = yield from aio.ws.receive_msg() 42 | 43 | if msg.tp == aiohttp.MsgType.text: 44 | aio.ws.send_str('Hello, {}'.format(name)) 45 | elif msg.tp == aiohttp.MsgType.close: 46 | print('websocket connection closed') 47 | break 48 | elif msg.tp == aiohttp.MsgType.error: 49 | print('ws connection closed with exception %s', 50 | aio.ws.exception()) 51 | break 52 | -------------------------------------------------------------------------------- /examples/app.py: -------------------------------------------------------------------------------- 1 | import json 2 | import asyncio 3 | 4 | import aiohttp 5 | from flask import Flask, current_app 6 | 7 | from flask_aiohttp import AioHTTP 8 | from flask_aiohttp.helper import async, websocket 9 | 10 | app = Flask(__name__) 11 | 12 | aio = AioHTTP(app) 13 | 14 | 15 | @app.route('/echo') 16 | @websocket 17 | def echo(): 18 | while True: 19 | msg = yield from aio.ws.receive_msg() 20 | 21 | if msg.tp == aiohttp.MsgType.text: 22 | aio.ws.send_str(msg.data) 23 | elif msg.tp == aiohttp.MsgType.close: 24 | print('websocket connection closed') 25 | break 26 | elif msg.tp == aiohttp.MsgType.error: 27 | print('ws connection closed with exception %s', 28 | aio.ws.exception()) 29 | break 30 | 31 | 32 | @app.route('/api') 33 | @async 34 | def api(): 35 | response = yield from aiohttp.request( 36 | 'GET', 'https://graph.facebook.com/zuck') 37 | data = yield from response.read() 38 | return data 39 | 40 | 41 | @app.route('/param/') 42 | @websocket 43 | def param(arg): 44 | while True: 45 | msg = yield from aio.ws.receive_msg() 46 | 47 | if msg.tp == aiohttp.MsgType.text: 48 | aio.ws.send_str(arg) 49 | elif msg.tp == aiohttp.MsgType.close: 50 | print('websocket connection closed') 51 | break 52 | elif msg.tp == aiohttp.MsgType.error: 53 | print('ws connection closed with exception %s', 54 | aio.ws.exception()) 55 | break 56 | 57 | 58 | @app.route('/late') 59 | @async 60 | def late(): 61 | yield from asyncio.sleep(3) 62 | 63 | data = { 64 | 'data': 'done' 65 | } 66 | 67 | data = json.dumps(data) 68 | current_app.response_class(data, headers={ 69 | 'Content-Type': 'application/json', 70 | }, status=201) 71 | return 'done' 72 | 73 | 74 | @app.route('/plain') 75 | def plain(): 76 | return 'Hello, World!' 77 | 78 | 79 | @app.route('/stream') 80 | def stream(): 81 | def f(): 82 | yield 'Hello, ' 83 | yield 'World!' 84 | return app.response_class(f()) 85 | 86 | 87 | @app.route('/async-stream') 88 | @async 89 | def async_stream(): 90 | def f(): 91 | yield 'I\'m ' 92 | yield 'sorry!' 93 | yield from asyncio.sleep(1) 94 | return app.response_class(f()) 95 | 96 | 97 | def main(): 98 | aio.run(app, debug=True) 99 | 100 | if __name__ == '__main__': 101 | main() 102 | -------------------------------------------------------------------------------- /flask_aiohttp/__init__.py: -------------------------------------------------------------------------------- 1 | """:mod:`flask_aiohttp` --- Asynchronous Flask with aiohttp 2 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 3 | 4 | Provides Flask extension for asynchronous I/O. 5 | 6 | With this extension, we can use `asyncio.coroutine` as Flask's view function. 7 | So, we can add 8 | asyncio-redis `_, or 9 | websocket support to your application. 10 | 11 | To make view asynchronous, just simply add :func:`helper.async` decorator to 12 | your view function :: 13 | 14 | @app.route('/foo') 15 | @async 16 | def lazy(): 17 | yield from asyncio.sleep(3) 18 | return 'Done' 19 | 20 | You have to run your flask application with :class:`AioHTTP` :: 21 | 22 | aio = AioHTTP(app) 23 | 24 | aio.run(app) 25 | 26 | And you can also use gunicorn :: 27 | 28 | aio = AioHTTP(flask_app) 29 | 30 | app = aio.create_aiohttp_app(flask_app) 31 | 32 | # Run gunicorn by 33 | # 34 | # gunicorn your_module:app -k aiohttp.worker.GunicornWebWorker 35 | # -b localhost:8080 36 | 37 | You can even use aiohttp's websocket in your Flask application using 38 | :func:`helper.websocket` :: 39 | 40 | aio = AioHTTP(flask_app) 41 | 42 | @app.route('echo') 43 | @websocket 44 | def echo(): 45 | while True: 46 | msg = yield from aio.ws.receive_msg() 47 | 48 | if msg.tp == aiohttp.MsgType.text: 49 | aio.ws.send_str(msg.data) 50 | elif msg.tp == aiohttp.MsgType.close: 51 | print('websocket connection closed') 52 | break 53 | elif msg.tp == aiohttp.MsgType.error: 54 | print('ws connection closed with exception %s', 55 | aio.ws.exception()) 56 | break 57 | 58 | """ 59 | import os 60 | import asyncio 61 | import logging 62 | 63 | import flask 64 | import aiohttp.web 65 | from flask import request 66 | from werkzeug.debug import DebuggedApplication 67 | from werkzeug.serving import run_with_reloader 68 | 69 | from .helper import async, websocket, has_websocket, wrap_wsgi_middleware 70 | from .handler import WSGIHandlerBase, WSGIWebSocketHandler 71 | 72 | 73 | __all__ = ['AioHTTP', 'async', 'websocket', 'has_websocket', 74 | 'wrap_wsgi_middleware'] 75 | 76 | 77 | class AioHTTP(object): 78 | """Flask middleware for aiohttp""" 79 | 80 | def __init__(self, app: flask.Flask=None, *, 81 | handler_factory=WSGIWebSocketHandler): 82 | """ 83 | 84 | :param app: 85 | Flask application 86 | 87 | :param handler_factory: 88 | aiohttp request handler factory. Factory should accept a single 89 | flask application. 90 | 91 | """ 92 | self.handler_factory = handler_factory 93 | if app is not None: 94 | self.init_app(app) 95 | 96 | def init_app(self, app: flask.Flask): 97 | """Init Flask app 98 | 99 | :param app: Flask application 100 | 101 | """ 102 | app.aiohttp_app = self.create_aiohttp_app(app) 103 | 104 | def create_aiohttp_app(self, app: flask.Flask) -> aiohttp.web.Application: 105 | """Create aiohttp web application from Flask application 106 | 107 | :param app: Flask application 108 | :returns: aiohttp web application 109 | 110 | """ 111 | # aiohttp web application instance 112 | aio_app = aiohttp.web.Application() 113 | 114 | # WSGI handler for aiohttp 115 | wsgi_handler = self.handler_factory(app) 116 | 117 | # aiohttp's router should accept any possible HTTP method of request. 118 | aio_app.router.add_route('*', r'/{path:.*}', wsgi_handler) 119 | return aio_app 120 | 121 | @staticmethod 122 | def run(app: flask.Flask, *, 123 | host='127.0.0.1', port=None, debug=False, loop=None): 124 | """Run Flask application on aiohttp 125 | 126 | :param app: Flask application 127 | :param host: host name or ip 128 | :param port: port (default is 5000) 129 | :param debug: debug? 130 | 131 | """ 132 | # Check initialization status of flask app. 133 | if getattr(app, 'aiohttp_app', None) is None: 134 | raise RuntimeError( 135 | "This application is not initialized for Flask-aiohttp. " 136 | "Please initialize the app by `aio.init_app(app)`.") 137 | 138 | # Configure args 139 | if port is None: 140 | server_name = app.config['SERVER_NAME'] 141 | if server_name and ':' in server_name: 142 | port = int(server_name.rsplit(':', 1)[-1]) 143 | else: 144 | port = 5000 145 | loop = loop or asyncio.get_event_loop() 146 | 147 | # Define run_server 148 | def run_server(): 149 | # run_server can be called in another thread 150 | asyncio.set_event_loop(loop) 151 | coroutine = loop.create_server( 152 | app.aiohttp_app.make_handler(), host, port) 153 | loop.run_until_complete(coroutine) 154 | try: 155 | loop.run_forever() 156 | except KeyboardInterrupt: 157 | pass 158 | 159 | # Configure logging 160 | file_handler = logging.StreamHandler() 161 | app.logger.setLevel(logging.INFO) 162 | app.logger.addHandler(file_handler) 163 | 164 | if debug: 165 | # Logging 166 | app.logger.setLevel(logging.DEBUG) 167 | 168 | # Wrap WSGI app with werkzeug debugger. 169 | app.wsgi_app = wrap_wsgi_middleware(DebuggedApplication)( 170 | app.wsgi_app) 171 | 172 | if os.environ.get('WERKZEUG_RUN_MAIN') != 'true': 173 | app.logger.info(' * Running on http://{}:{}/' 174 | .format(host, port)) 175 | 176 | # Run with reloader 177 | run_with_reloader(run_server) 178 | else: 179 | app.logger.info(' * Running on http://{}:{}/'.format(host, port)) 180 | run_server() 181 | 182 | @property 183 | def ws(self) -> aiohttp.web.WebSocketResponse: 184 | """Websocket response of aiohttp""" 185 | 186 | ws = request.environ.get('wsgi.websocket', None) 187 | if ws is None: 188 | raise RuntimeError('Request context is not a WebSocket context.') 189 | return ws 190 | -------------------------------------------------------------------------------- /flask_aiohttp/handler.py: -------------------------------------------------------------------------------- 1 | import abc 2 | import asyncio 3 | import itertools 4 | 5 | import aiohttp.web 6 | from aiohttp.wsgi import WSGIServerHttpProtocol 7 | 8 | from .util import is_websocket_request 9 | 10 | 11 | class WSGIHandlerBase(metaclass=abc.ABCMeta): 12 | @abc.abstractmethod 13 | def handle_request(self, request: aiohttp.web.Request): 14 | pass 15 | 16 | @asyncio.coroutine 17 | def __call__(self, request: aiohttp.web.Request): 18 | response = yield from self.handle_request(request) 19 | return response 20 | 21 | 22 | class WSGIWebSocketHandler(WSGIHandlerBase): 23 | """WSGI request handler for aiohttp web application.""" 24 | 25 | def __init__(self, wsgi): 26 | self.wsgi = wsgi 27 | 28 | @asyncio.coroutine 29 | def handle_request(self, request: aiohttp.web.Request) -> \ 30 | aiohttp.web.StreamResponse: 31 | """Handle WSGI request with aiohttp""" 32 | 33 | # Use aiohttp's WSGI implementation 34 | protocol = WSGIServerHttpProtocol(request.app, True) 35 | protocol.transport = request.transport 36 | 37 | # Build WSGI Response 38 | environ = protocol.create_wsgi_environ(request, request.content) 39 | 40 | # Create responses 41 | ws = aiohttp.web.WebSocketResponse() 42 | response = aiohttp.web.StreamResponse() 43 | 44 | #: Write delegate 45 | @asyncio.coroutine 46 | def write(data): 47 | yield from response.write(data) 48 | 49 | #: EOF Write delegate 50 | @asyncio.coroutine 51 | def write_eof(): 52 | yield from response.write_eof() 53 | 54 | # WSGI start_response function 55 | def start_response(status, headers, exc_info=None): 56 | if exc_info: 57 | raise exc_info[1] 58 | 59 | status_parts = status.split(' ', 1) 60 | status = int(status_parts.pop(0)) 61 | reason = status_parts[0] if status_parts else None 62 | 63 | response.set_status(status, reason=reason) 64 | 65 | for name, value in headers: 66 | response.headers[name] = value 67 | 68 | response.start(request) 69 | 70 | return write 71 | if is_websocket_request(request): 72 | ws.start(request) 73 | 74 | # WSGI HTTP responses in websocket are meaningless. 75 | def start_response(status, headers, exc_info=None): 76 | if exc_info: 77 | raise exc_info[1] 78 | ws.start(request) 79 | return [] 80 | 81 | @asyncio.coroutine 82 | def write(data): 83 | return 84 | 85 | @asyncio.coroutine 86 | def write_eof(): 87 | return 88 | 89 | response = ws 90 | else: 91 | ws = None 92 | 93 | # Add websocket response to WSGI environment 94 | environ['wsgi.websocket'] = ws 95 | 96 | # Run WSGI app 97 | response_iter = self.wsgi(environ, start_response) 98 | 99 | try: 100 | iterator = iter(response_iter) 101 | 102 | wsgi_response = [] 103 | try: 104 | item = next(iterator) 105 | except StopIteration as stop: 106 | try: 107 | iterator = iter(stop.value) 108 | except TypeError: 109 | pass 110 | else: 111 | wsgi_response = iterator 112 | else: 113 | if isinstance(item, bytes): 114 | # This is plain WSGI response iterator 115 | wsgi_response = itertools.chain([item], iterator) 116 | else: 117 | # This is coroutine 118 | yield item 119 | wsgi_response = yield from iterator 120 | for item in wsgi_response: 121 | yield from write(item) 122 | 123 | yield from write_eof() 124 | finally: 125 | if hasattr(response_iter, 'close'): 126 | response_iter.close() 127 | 128 | # Return selected response 129 | return response 130 | -------------------------------------------------------------------------------- /flask_aiohttp/helper.py: -------------------------------------------------------------------------------- 1 | """:mod:`helper` --- Helpers 2 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 3 | 4 | Provides various utilities. 5 | 6 | """ 7 | import asyncio 8 | import functools 9 | 10 | from flask import current_app, request, abort 11 | 12 | from .util import async_response 13 | 14 | 15 | __all__ = ['async', 'websocket', 'has_websocket', 'wrap_wsgi_middleware'] 16 | 17 | 18 | def async(fn): 19 | """Decorate flask's view function for asyncio. 20 | 21 | :: 22 | 23 | @async 24 | def foo(): 25 | yield from asyncio.sleep(3) 26 | return 'foo' 27 | 28 | 29 | :param fn: Function to be decorated. 30 | 31 | :returns: decorator. 32 | 33 | """ 34 | fn = asyncio.coroutine(fn) 35 | 36 | @functools.wraps(fn) 37 | def wrapper(*args, **kwargs): 38 | coroutine = functools.partial(fn, *args, **kwargs) 39 | return async_response(coroutine(), current_app, request) 40 | return wrapper 41 | 42 | 43 | def websocket(fn=None, *, failure_status_code: int=400): 44 | """Decorate flask's view function for websocket 45 | 46 | :param failure_status_code: status code for failure 47 | 48 | :: 49 | 50 | @async 51 | def foo(): 52 | data = yield from aio.ws.receive() 53 | ... 54 | 55 | Or :: 56 | 57 | @async(failure_status_code=404) 58 | def bar(): 59 | data = yield from aio.ws.receive() 60 | ... 61 | 62 | """ 63 | if fn is not None: 64 | # For simple `@async` call 65 | return websocket(failure_status_code=400)(fn) 66 | 67 | def decorator(func): 68 | @functools.wraps(func) 69 | def wrapper(*args, **kwargs): 70 | if not has_websocket(): 71 | # Accept only websocket request 72 | abort(failure_status_code) 73 | else: 74 | yield from func(*args, **kwargs) 75 | return 'Done', 200 76 | return async(wrapper) 77 | return decorator 78 | 79 | 80 | def has_websocket() -> bool: 81 | """Does current request contains websocket?""" 82 | return request.environ.get('wsgi.websocket', None) is not None 83 | 84 | 85 | def wrap_wsgi_middleware(middleware, *args): 86 | """Wrap plain WSGI middleware to working asynchronously. 87 | 88 | Most WSGI middlewares called like following break our coroutine call :: 89 | 90 | flask.wsgi_app = middleware(wsgi_app) 91 | 92 | So we can prevent breaking coroutine by this wrapper :: 93 | 94 | flask.wsgi_app = wrap_wsgi_middleware(middleware)(wsgi_app) 95 | 96 | Like most of asyncio functions, you have to 97 | 98 | :param middleware: WSGI middleware to be wrapped 99 | :return: wrapped middleware 100 | 101 | """ 102 | def wrapper(wsgi): 103 | _signal = object() 104 | 105 | # Wrap the coroutine WSGI app 106 | @functools.wraps(wsgi) 107 | def wsgi_wrapper(environ, start_response): 108 | rv = yield from wsgi(environ, start_response) 109 | # Yield signal for end original yield 110 | yield _signal 111 | yield rv 112 | 113 | # Create concrete middleware 114 | concrete_middleware = middleware(wsgi_wrapper, *args) 115 | 116 | # Wrap the middleware again 117 | @functools.wraps(concrete_middleware) 118 | @asyncio.coroutine 119 | def wrapped(environ, start_response): 120 | iterator = iter(concrete_middleware(environ, start_response)) 121 | while True: 122 | item = next(iterator) 123 | if item is not _signal: 124 | yield item 125 | else: 126 | break 127 | # Now, original yielding ended. next yielded value is return 128 | # value of coroutine. 129 | rv = next(iterator) 130 | return rv 131 | return wrapped 132 | return wrapper 133 | -------------------------------------------------------------------------------- /flask_aiohttp/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hardtack/Flask-aiohttp/50cfc8c1f71bae9e10a4a475b06b5f6bd124e33b/flask_aiohttp/tests/__init__.py -------------------------------------------------------------------------------- /flask_aiohttp/tests/test_aiowebsocket.py: -------------------------------------------------------------------------------- 1 | import time 2 | import pytest 3 | import asyncio 4 | import threading 5 | import contextlib 6 | import urllib.parse 7 | import urllib.request 8 | 9 | import aiohttp 10 | from flask import Flask, request 11 | from websocket import WebSocket 12 | from werkzeug.debug import DebuggedApplication 13 | 14 | from .. import AioHTTP, wrap_wsgi_middleware, async, websocket 15 | 16 | 17 | class Server(contextlib.ContextDecorator): 18 | def __init__(self, app: Flask, aio: AioHTTP, *, 19 | host='127.0.0.1', port=0): 20 | super().__init__() 21 | self.app = app 22 | self.aio = aio 23 | self.host = host 24 | self.port = port 25 | self.loop = asyncio.get_event_loop() 26 | self._server = None 27 | self.condition = threading.Condition(threading.Lock()) 28 | 29 | def start(self): 30 | # Wrap WSGI app with werkzeug debugger. 31 | self.app.wsgi_app = wrap_wsgi_middleware(DebuggedApplication)( 32 | self.app.wsgi_app) 33 | 34 | thread = threading.Thread(target=self.run) 35 | thread.start() 36 | 37 | def stop(self): 38 | self.loop.call_soon_threadsafe(self.loop.stop) 39 | 40 | def __enter__(self): 41 | self.start() 42 | return self 43 | 44 | def __exit__(self, exc_type, exc_val, exc_tb): 45 | time.sleep(0.001) # For bypassing unknown exception at stopping loop. 46 | self.stop() 47 | 48 | @property 49 | def server(self): 50 | with self.condition: 51 | if self._server is None: 52 | self.condition.wait() 53 | return self._server 54 | 55 | @server.setter 56 | def server(self, server): 57 | with self.condition: 58 | self._server = server 59 | if server is not None: 60 | self.condition.notify_all() 61 | 62 | def run(self): 63 | asyncio.set_event_loop(self.loop) 64 | 65 | # Create coroutine 66 | coroutine = self.loop.create_server( 67 | self.app.aiohttp_app.make_handler(), self.host, self.port) 68 | # Get server 69 | server = self.loop.run_until_complete(coroutine) 70 | self.server = server 71 | # Run until `stop()` 72 | self.loop.run_forever() 73 | 74 | @property 75 | def address(self): 76 | #: :type: socket.socket 77 | sock = self.server.sockets[0] 78 | return '{}:{}'.format(*sock.getsockname()) 79 | 80 | @property 81 | def base_url(self): 82 | return 'http://' + self.address 83 | 84 | @property 85 | def ws_base_url(self): 86 | return 'ws://' + self.address 87 | 88 | def ws_url(self, path, **params): 89 | url = self.ws_base_url + path 90 | if params: 91 | url += '?' + urllib.parse.urlencode(params) 92 | return url 93 | 94 | def url(self, path, **params): 95 | url = self.base_url + path 96 | if params: 97 | url += '?' + urllib.parse.urlencode(params) 98 | return url 99 | 100 | def request(self, method, path, params=None): 101 | r = urllib.request.Request(self.url(path, **params), 102 | method=method.upper()) 103 | with urllib.request.urlopen(r) as response: 104 | return response.readall().decode('utf-8') 105 | 106 | def get(self, path, **kwargs): 107 | return self.request('GET', path, params=kwargs) 108 | 109 | 110 | @pytest.fixture 111 | def app(): 112 | app = Flask(__name__) 113 | return app 114 | 115 | 116 | @pytest.fixture 117 | def aio(app: Flask): 118 | return AioHTTP(app) 119 | 120 | 121 | def test_flask(app: Flask, aio: AioHTTP): 122 | """Test for checking flask working find""" 123 | @app.route('/foo') 124 | def foo(): 125 | return 'foo' 126 | 127 | @app.route('/bar') 128 | def bar(): 129 | def stream(): 130 | yield 'bar' 131 | return app.response_class(stream()) 132 | 133 | with Server(app, aio) as server: 134 | assert 'foo' == server.get('/foo') 135 | assert 'bar' == server.get('/bar') 136 | 137 | 138 | def test_async(app: Flask, aio: AioHTTP): 139 | """Test for asynchronous I/O in Flask view""" 140 | @app.route('/foo') 141 | def foo(): 142 | return 'foo' 143 | 144 | @app.route('/lazy-foo') 145 | @async 146 | def lazy_foo(): 147 | response = yield from aiohttp.request('GET', request.host_url + 'foo') 148 | data = yield from response.read() 149 | return data 150 | 151 | @app.route('/streaming-foo') 152 | @async 153 | def streaming_foo(): 154 | response = yield from aiohttp.request('GET', request.host_url + 'foo') 155 | data = yield from response.read() 156 | 157 | def stream(): 158 | yield data 159 | return app.response_class(stream()) 160 | 161 | with Server(app, aio) as server: 162 | assert 'foo' == server.get('/foo') 163 | assert 'foo' == server.get('/lazy-foo') 164 | assert 'foo' == server.get('/streaming-foo') 165 | 166 | 167 | def test_websocket(app: Flask, aio: AioHTTP): 168 | """Test for websocket""" 169 | @app.route('/echo') 170 | @websocket 171 | def echo(): 172 | while True: 173 | msg = yield from aio.ws.receive_msg() 174 | 175 | if msg.tp == aiohttp.MsgType.text: 176 | aio.ws.send_str(msg.data) 177 | elif msg.tp == aiohttp.MsgType.close: 178 | break 179 | elif msg.tp == aiohttp.MsgType.error: 180 | break 181 | 182 | with Server(app, aio) as server: 183 | ws = WebSocket() 184 | ws.connect(server.ws_url('/echo')) 185 | try: 186 | ws.send('foo') 187 | assert 'foo' == ws.recv() 188 | finally: 189 | ws.close() 190 | 191 | 192 | def test_request_hook(app: Flask, aio: AioHTTP): 193 | """Test for Flask request hook""" 194 | @app.before_request 195 | def before_request(): 196 | request.foo = [] 197 | request.foo.append('a') 198 | 199 | @app.after_request 200 | def after_request(response): 201 | request.foo.append('c') 202 | return response 203 | 204 | @app.teardown_request 205 | def teardown_request(exc): 206 | request.foo.append('d') 207 | 208 | @app.route('/hook') 209 | @async 210 | def hook(): 211 | request.foo.append('b') 212 | 213 | return ''.join(request.foo) 214 | 215 | with Server(app, aio) as server: 216 | assert 'ab' == server.get('/hook') 217 | -------------------------------------------------------------------------------- /flask_aiohttp/util.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | 3 | import flask 4 | import aiohttp.web 5 | from aiohttp import hdrs 6 | from flask.ctx import RequestContext 7 | from werkzeug.local import LocalProxy 8 | 9 | 10 | def is_websocket_request(request: aiohttp.web.Request) -> bool: 11 | """Is the request websocket request? 12 | 13 | :param request: aiohttp web request object 14 | 15 | """ 16 | upgrade = request.headers.get(hdrs.UPGRADE, '').lower().strip() 17 | connection = request.headers.get(hdrs.CONNECTION, '').lower() 18 | return 'websocket' == upgrade and 'upgrade' in connection 19 | 20 | 21 | def freeze(object_or_proxy): 22 | """Get current object of `object_or_proxy` if it is LocalProxy""" 23 | if isinstance(object_or_proxy, LocalProxy): 24 | return object_or_proxy._get_current_object() 25 | return object_or_proxy 26 | 27 | 28 | def async_response(coroutine, 29 | app: flask.Flask or LocalProxy, 30 | request: flask.Request or LocalProxy) -> \ 31 | flask.Response: 32 | """Convert coroutine to asynchronous flask response. 33 | 34 | :param coroutine: coroutine 35 | :param app: Flask application 36 | :param request: Current request 37 | :returns: asynchronous Flask response 38 | 39 | """ 40 | 41 | #: :type: flask.Flask 42 | app = freeze(app) 43 | 44 | # :type: flask.Request 45 | request = freeze(request) 46 | 47 | class AsyncResponse(app.response_class): 48 | def __init__(self): 49 | super().__init__(coroutine) 50 | 51 | @asyncio.coroutine 52 | def call_response(self): 53 | rv = app.preprocess_request() 54 | if rv is None: 55 | try: 56 | rv = yield from self.response 57 | except Exception as e: 58 | rv = app.handle_user_exception(e) 59 | if asyncio.iscoroutine(rv): 60 | rv = yield from rv 61 | response = app.make_response(rv) 62 | response = app.process_response(response) 63 | return response 64 | 65 | @asyncio.coroutine 66 | def __call__(self, environ, start_response): 67 | with RequestContext(app, environ, request): 68 | try: 69 | # Fetch data from coroutine 70 | rv = yield from self.call_response() 71 | except Exception as e: 72 | rv = app.handle_exception(e) 73 | if asyncio.iscoroutine(rv): 74 | rv = yield from rv 75 | rv = app.make_response(rv) 76 | if asyncio.iscoroutine(rv): 77 | rv = yield from rv 78 | 79 | # Call as WSGI app 80 | if isinstance(rv, app.response_class): 81 | return rv(environ, start_response) 82 | 83 | status = self.status 84 | headers = self.get_wsgi_headers(environ) 85 | app_iter = [] 86 | 87 | start_response(status, headers) 88 | return app_iter 89 | 90 | return AsyncResponse() 91 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | -e . 2 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | import setuptools 4 | from setuptools import setup 5 | from setuptools.command.test import test as TestCommand 6 | 7 | 8 | class PyTest(TestCommand): 9 | user_options = [('pytest-args=', 'a', "Arguments to pass to py.test")] 10 | 11 | def initialize_options(self): 12 | TestCommand.initialize_options(self) 13 | self.pytest_args = [] 14 | 15 | def finalize_options(self): 16 | TestCommand.finalize_options(self) 17 | self.test_args = [] 18 | self.test_suite = True 19 | 20 | def run_tests(self): 21 | # import here, cause outside the eggs aren't loaded 22 | import pytest 23 | errno = pytest.main(self.pytest_args) 24 | sys.exit(errno) 25 | 26 | 27 | setup( 28 | name='Flask-aiohttp', 29 | version='0.1.0', 30 | packages=setuptools.find_packages(exclude=['flask_aiohttp.tests']), 31 | url='https://github.com/hardtack/flask-aiohttp', 32 | license='MIT LICENSE', 33 | author='Geonu Choi', 34 | author_email='6566gun@gmail.com', 35 | description="Asynchronous Flask using aiohttp", 36 | install_requires=[ 37 | 'aiohttp >= 0.15', 38 | 'Flask >= 0.10.0', 39 | ], 40 | 41 | # Test 42 | test_requires=[ 43 | 'pytest', 44 | 'websocket', 45 | ], 46 | 47 | # Cmd 48 | cmdclass={ 49 | 'test': PyTest, 50 | } 51 | ) 52 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = py34 3 | 4 | [testenv] 5 | deps=pytest, websocket 6 | commands=py.test 7 | --------------------------------------------------------------------------------