22 changed files with 1013 additions and 2506 deletions
@ -1,192 +1,19 @@ |
|||
# Makefile for Sphinx documentation
|
|||
# Minimal makefile for Sphinx documentation
|
|||
#
|
|||
|
|||
# You can set these variables from the command line.
|
|||
SPHINXOPTS = |
|||
SPHINXBUILD = sphinx-build |
|||
PAPER = |
|||
SOURCEDIR = . |
|||
BUILDDIR = _build |
|||
|
|||
# User-friendly check for sphinx-build
|
|||
ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) |
|||
$(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/) |
|||
endif |
|||
|
|||
# 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 clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest coverage gettext |
|||
|
|||
# Put it first so that "make" without argument is like "make help".
|
|||
help: |
|||
@echo "Please use \`make <target>' where <target> is one of" |
|||
@echo " html to make standalone HTML files" |
|||
@echo " dirhtml to make HTML files named index.html in directories" |
|||
@echo " singlehtml to make a single large HTML file" |
|||
@echo " pickle to make pickle files" |
|||
@echo " json to make JSON files" |
|||
@echo " htmlhelp to make HTML files and a HTML help project" |
|||
@echo " qthelp to make HTML files and a qthelp project" |
|||
@echo " applehelp to make an Apple Help Book" |
|||
@echo " devhelp to make HTML files and a Devhelp project" |
|||
@echo " epub to make an epub" |
|||
@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)" |
|||
|
|||
clean: |
|||
rm -rf $(BUILDDIR)/* |
|||
|
|||
html: |
|||
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html |
|||
@echo |
|||
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html." |
|||
|
|||
dirhtml: |
|||
$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml |
|||
@echo |
|||
@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." |
|||
|
|||
singlehtml: |
|||
$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml |
|||
@echo |
|||
@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." |
|||
|
|||
pickle: |
|||
$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle |
|||
@echo |
|||
@echo "Build finished; now you can process the pickle files." |
|||
|
|||
json: |
|||
$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json |
|||
@echo |
|||
@echo "Build finished; now you can process the JSON files." |
|||
|
|||
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." |
|||
|
|||
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/socketio.qhcp" |
|||
@echo "To view the help file:" |
|||
@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/socketio.qhc" |
|||
|
|||
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." |
|||
|
|||
devhelp: |
|||
$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp |
|||
@echo |
|||
@echo "Build finished." |
|||
@echo "To view the help file:" |
|||
@echo "# mkdir -p $$HOME/.local/share/devhelp/socketio" |
|||
@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/socketio" |
|||
@echo "# devhelp" |
|||
|
|||
epub: |
|||
$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub |
|||
@echo |
|||
@echo "Build finished. The epub file is in $(BUILDDIR)/epub." |
|||
|
|||
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)." |
|||
|
|||
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." |
|||
|
|||
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." |
|||
|
|||
text: |
|||
$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text |
|||
@echo |
|||
@echo "Build finished. The text files are in $(BUILDDIR)/text." |
|||
|
|||
man: |
|||
$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man |
|||
@echo |
|||
@echo "Build finished. The manual pages are in $(BUILDDIR)/man." |
|||
|
|||
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)." |
|||
|
|||
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." |
|||
|
|||
gettext: |
|||
$(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale |
|||
@echo |
|||
@echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." |
|||
|
|||
changes: |
|||
$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes |
|||
@echo |
|||
@echo "The overview file is in $(BUILDDIR)/changes." |
|||
|
|||
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." |
|||
|
|||
doctest: |
|||
$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest |
|||
@echo "Testing of doctests in the sources finished, look at the " \
|
|||
"results in $(BUILDDIR)/doctest/output.txt." |
|||
|
|||
coverage: |
|||
$(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage |
|||
@echo "Testing of coverage in the sources finished, look at the " \
|
|||
"results in $(BUILDDIR)/coverage/python.txt." |
|||
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) |
|||
|
|||
xml: |
|||
$(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml |
|||
@echo |
|||
@echo "Build finished. The XML files are in $(BUILDDIR)/xml." |
|||
.PHONY: help Makefile |
|||
|
|||
pseudoxml: |
|||
$(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml |
|||
@echo |
|||
@echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." |
|||
# 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) |
@ -0,0 +1 @@ |
|||
Place static files used by the documentation here. |
@ -1,12 +0,0 @@ |
|||
<!DOCTYPE html> |
|||
<html> |
|||
<head> |
|||
<title>python-socketio documentation</title> |
|||
<meta http-equiv="refresh" content="0; URL=http://python-socketio.readthedocs.org/"> |
|||
<meta name="keywords" content="automatic redirection"> |
|||
</head> |
|||
<body> |
|||
The python-socketio documentation is available at <a href="http://python-socketio.readthedocs.org">Read the Docs</a>. |
|||
If your browser does not automatically redirect you, please <a href="http://python-socketio.readthedocs.org">click here</a>. |
|||
</body> |
|||
</html> |
Before Width: | Height: | Size: 54 KiB |
@ -1,37 +0,0 @@ |
|||
Copyright (c) 2010 by Armin Ronacher. |
|||
|
|||
Some rights reserved. |
|||
|
|||
Redistribution and use in source and binary forms of the theme, with or |
|||
without modification, are permitted provided that the following conditions |
|||
are met: |
|||
|
|||
* Redistributions of source code must retain the above copyright |
|||
notice, this list of conditions and the following disclaimer. |
|||
|
|||
* Redistributions in binary form must reproduce the above |
|||
copyright notice, this list of conditions and the following |
|||
disclaimer in the documentation and/or other materials provided |
|||
with the distribution. |
|||
|
|||
* The names of the contributors may not be used to endorse or |
|||
promote products derived from this software without specific |
|||
prior written permission. |
|||
|
|||
We kindly ask you to only use these themes in an unmodified manner just |
|||
for Flask and Flask-related products, not for unrelated projects. If you |
|||
like the visual style and want to use it for your own projects, please |
|||
consider making some larger changes to the themes (such as changing |
|||
font faces, sizes, colors or margins). |
|||
|
|||
THIS THEME IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
|||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
|||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
|||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE |
|||
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
|||
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
|||
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
|||
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
|||
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
|||
ARISING IN ANY WAY OUT OF THE USE OF THIS THEME, EVEN IF ADVISED OF THE |
|||
POSSIBILITY OF SUCH DAMAGE. |
@ -1,31 +0,0 @@ |
|||
Flask Sphinx Styles |
|||
=================== |
|||
|
|||
This repository contains sphinx styles for Flask and Flask related |
|||
projects. To use this style in your Sphinx documentation, follow |
|||
this guide: |
|||
|
|||
1. put this folder as _themes into your docs folder. Alternatively |
|||
you can also use git submodules to check out the contents there. |
|||
2. add this to your conf.py: |
|||
|
|||
sys.path.append(os.path.abspath('_themes')) |
|||
html_theme_path = ['_themes'] |
|||
html_theme = 'flask' |
|||
|
|||
The following themes exist: |
|||
|
|||
- 'flask' - the standard flask documentation theme for large |
|||
projects |
|||
- 'flask_small' - small one-page theme. Intended to be used by |
|||
very small addon libraries for flask. |
|||
|
|||
The following options exist for the flask_small theme: |
|||
|
|||
[options] |
|||
index_logo = '' filename of a picture in _static |
|||
to be used as replacement for the |
|||
h1 in the index.rst file. |
|||
index_logo_height = 120px height of the index logo |
|||
github_fork = '' repository name on github for the |
|||
"fork me" badge |
@ -1,24 +0,0 @@ |
|||
{%- extends "basic/layout.html" %} |
|||
{%- block extrahead %} |
|||
{{ super() }} |
|||
{% if theme_touch_icon %} |
|||
<link rel="apple-touch-icon" href="{{ pathto('_static/' ~ theme_touch_icon, 1) }}" /> |
|||
{% endif %} |
|||
<meta name="viewport" content="width=device-width, initial-scale=0.9, maximum-scale=0.9"> |
|||
{% endblock %} |
|||
{%- block relbar2 %}{% endblock %} |
|||
{% block header %} |
|||
{{ super() }} |
|||
{% if pagename == 'index' %} |
|||
<div class=indexwrapper> |
|||
{% endif %} |
|||
{% endblock %} |
|||
{%- block footer %} |
|||
<div class="footer"> |
|||
© Copyright {{ copyright }}. |
|||
Created using <a href="http://sphinx.pocoo.org/">Sphinx</a>. |
|||
</div> |
|||
{% if pagename == 'index' %} |
|||
</div> |
|||
{% endif %} |
|||
{%- endblock %} |
@ -1,19 +0,0 @@ |
|||
<h3>Related Topics</h3> |
|||
<ul> |
|||
<li><a href="{{ pathto(master_doc) }}">Documentation overview</a><ul> |
|||
{%- for parent in parents %} |
|||
<li><a href="{{ parent.link|e }}">{{ parent.title }}</a><ul> |
|||
{%- endfor %} |
|||
{%- if prev %} |
|||
<li>Previous: <a href="{{ prev.link|e }}" title="{{ _('previous chapter') |
|||
}}">{{ prev.title }}</a></li> |
|||
{%- endif %} |
|||
{%- if next %} |
|||
<li>Next: <a href="{{ next.link|e }}" title="{{ _('next chapter') |
|||
}}">{{ next.title }}</a></li> |
|||
{%- endif %} |
|||
{%- for parent in parents %} |
|||
</ul></li> |
|||
{%- endfor %} |
|||
</ul></li> |
|||
</ul> |
@ -1,577 +0,0 @@ |
|||
/* |
|||
* flasky.css_t |
|||
* ~~~~~~~~~~~~ |
|||
* |
|||
* :copyright: Copyright 2010 by Armin Ronacher. |
|||
* :license: Flask Design License, see LICENSE for details. |
|||
*/ |
|||
|
|||
{% set page_width = '940px' %} |
|||
{% set sidebar_width = '220px' %} |
|||
|
|||
@import url("basic.css"); |
|||
|
|||
/* -- page layout ----------------------------------------------------------- */ |
|||
|
|||
body { |
|||
font-family: 'Georgia', serif; |
|||
font-size: 17px; |
|||
background-color: white; |
|||
color: #000; |
|||
margin: 0; |
|||
padding: 0; |
|||
} |
|||
|
|||
div.document { |
|||
width: {{ page_width }}; |
|||
margin: 30px auto 0 auto; |
|||
} |
|||
|
|||
div.documentwrapper { |
|||
float: left; |
|||
width: 100%; |
|||
} |
|||
|
|||
div.bodywrapper { |
|||
margin: 0 0 0 {{ sidebar_width }}; |
|||
} |
|||
|
|||
div.sphinxsidebar { |
|||
width: {{ sidebar_width }}; |
|||
} |
|||
|
|||
hr { |
|||
border: 1px solid #B1B4B6; |
|||
} |
|||
|
|||
div.body { |
|||
background-color: #ffffff; |
|||
color: #3E4349; |
|||
padding: 0 30px 0 30px; |
|||
} |
|||
|
|||
img.floatingflask { |
|||
padding: 0 0 10px 10px; |
|||
float: right; |
|||
} |
|||
|
|||
div.footer { |
|||
width: {{ page_width }}; |
|||
margin: 20px auto 30px auto; |
|||
font-size: 14px; |
|||
color: #888; |
|||
text-align: right; |
|||
} |
|||
|
|||
div.footer a { |
|||
color: #888; |
|||
} |
|||
|
|||
div.related { |
|||
display: none; |
|||
} |
|||
|
|||
div.sphinxsidebar a { |
|||
color: #444; |
|||
text-decoration: none; |
|||
border-bottom: 1px dotted #999; |
|||
} |
|||
|
|||
div.sphinxsidebar a:hover { |
|||
border-bottom: 1px solid #999; |
|||
} |
|||
|
|||
div.sphinxsidebar { |
|||
font-size: 14px; |
|||
line-height: 1.5; |
|||
} |
|||
|
|||
div.sphinxsidebarwrapper { |
|||
padding: 18px 10px; |
|||
} |
|||
|
|||
div.sphinxsidebarwrapper p.logo { |
|||
padding: 0 0 20px 0; |
|||
margin: 0; |
|||
text-align: center; |
|||
} |
|||
|
|||
div.sphinxsidebar h3, |
|||
div.sphinxsidebar h4 { |
|||
font-family: 'Garamond', 'Georgia', serif; |
|||
color: #444; |
|||
font-size: 24px; |
|||
font-weight: normal; |
|||
margin: 0 0 5px 0; |
|||
padding: 0; |
|||
} |
|||
|
|||
div.sphinxsidebar h4 { |
|||
font-size: 20px; |
|||
} |
|||
|
|||
div.sphinxsidebar h3 a { |
|||
color: #444; |
|||
} |
|||
|
|||
div.sphinxsidebar p.logo a, |
|||
div.sphinxsidebar h3 a, |
|||
div.sphinxsidebar p.logo a:hover, |
|||
div.sphinxsidebar h3 a:hover { |
|||
border: none; |
|||
} |
|||
|
|||
div.sphinxsidebar p { |
|||
color: #555; |
|||
margin: 10px 0; |
|||
} |
|||
|
|||
div.sphinxsidebar ul { |
|||
margin: 10px 0; |
|||
padding: 0; |
|||
color: #000; |
|||
} |
|||
|
|||
div.sphinxsidebar input { |
|||
border: 1px solid #ccc; |
|||
font-family: 'Georgia', serif; |
|||
font-size: 1em; |
|||
} |
|||
|
|||
/* -- body styles ----------------------------------------------------------- */ |
|||
|
|||
a { |
|||
color: #004B6B; |
|||
text-decoration: underline; |
|||
} |
|||
|
|||
a:hover { |
|||
color: #6D4100; |
|||
text-decoration: underline; |
|||
} |
|||
|
|||
div.body h1, |
|||
div.body h2, |
|||
div.body h3, |
|||
div.body h4, |
|||
div.body h5, |
|||
div.body h6 { |
|||
font-family: 'Garamond', 'Georgia', serif; |
|||
font-weight: normal; |
|||
margin: 30px 0px 10px 0px; |
|||
padding: 0; |
|||
} |
|||
|
|||
{% if theme_index_logo %} |
|||
div.indexwrapper h1 { |
|||
text-indent: -999999px; |
|||
background: url({{ theme_index_logo }}) no-repeat center center; |
|||
height: {{ theme_index_logo_height }}; |
|||
} |
|||
{% endif %} |
|||
div.body h1 { margin-top: 0; padding-top: 0; font-size: 240%; } |
|||
div.body h2 { font-size: 180%; } |
|||
div.body h3 { font-size: 150%; } |
|||
div.body h4 { font-size: 130%; } |
|||
div.body h5 { font-size: 100%; } |
|||
div.body h6 { font-size: 100%; } |
|||
|
|||
a.headerlink { |
|||
color: #ddd; |
|||
padding: 0 4px; |
|||
text-decoration: none; |
|||
} |
|||
|
|||
a.headerlink:hover { |
|||
color: #444; |
|||
background: #eaeaea; |
|||
} |
|||
|
|||
div.body p, div.body dd, div.body li { |
|||
line-height: 1.4em; |
|||
} |
|||
|
|||
div.admonition { |
|||
background: #fafafa; |
|||
margin: 20px -30px; |
|||
padding: 10px 30px; |
|||
border-top: 1px solid #ccc; |
|||
border-bottom: 1px solid #ccc; |
|||
} |
|||
|
|||
div.admonition tt.xref, div.admonition a tt { |
|||
border-bottom: 1px solid #fafafa; |
|||
} |
|||
|
|||
dd div.admonition { |
|||
margin-left: -60px; |
|||
padding-left: 60px; |
|||
} |
|||
|
|||
div.admonition p.admonition-title { |
|||
font-family: 'Garamond', 'Georgia', serif; |
|||
font-weight: normal; |
|||
font-size: 24px; |
|||
margin: 0 0 10px 0; |
|||
padding: 0; |
|||
line-height: 1; |
|||
} |
|||
|
|||
div.admonition p.last { |
|||
margin-bottom: 0; |
|||
} |
|||
|
|||
div.highlight { |
|||
background-color: white; |
|||
} |
|||
|
|||
dt:target, .highlight { |
|||
background: #FAF3E8; |
|||
} |
|||
|
|||
div.note { |
|||
background-color: #eee; |
|||
border: 1px solid #ccc; |
|||
} |
|||
|
|||
div.seealso { |
|||
background-color: #ffc; |
|||
border: 1px solid #ff6; |
|||
} |
|||
|
|||
div.topic { |
|||
background-color: #eee; |
|||
} |
|||
|
|||
p.admonition-title { |
|||
display: inline; |
|||
} |
|||
|
|||
p.admonition-title:after { |
|||
content: ":"; |
|||
} |
|||
|
|||
pre, tt { |
|||
font-family: 'Consolas', 'Menlo', 'Deja Vu Sans Mono', 'Bitstream Vera Sans Mono', monospace; |
|||
font-size: 0.9em; |
|||
} |
|||
|
|||
img.screenshot { |
|||
} |
|||
|
|||
tt.descname, tt.descclassname { |
|||
font-size: 0.95em; |
|||
} |
|||
|
|||
tt.descname { |
|||
padding-right: 0.08em; |
|||
} |
|||
|
|||
img.screenshot { |
|||
-moz-box-shadow: 2px 2px 4px #eee; |
|||
-webkit-box-shadow: 2px 2px 4px #eee; |
|||
box-shadow: 2px 2px 4px #eee; |
|||
} |
|||
|
|||
table.docutils { |
|||
border: 1px solid #888; |
|||
-moz-box-shadow: 2px 2px 4px #eee; |
|||
-webkit-box-shadow: 2px 2px 4px #eee; |
|||
box-shadow: 2px 2px 4px #eee; |
|||
} |
|||
|
|||
table.docutils td, table.docutils th { |
|||
border: 1px solid #888; |
|||
padding: 0.25em 0.7em; |
|||
} |
|||
|
|||
table.field-list, table.footnote { |
|||
border: none; |
|||
-moz-box-shadow: none; |
|||
-webkit-box-shadow: none; |
|||
box-shadow: none; |
|||
} |
|||
|
|||
table.footnote { |
|||
margin: 15px 0; |
|||
width: 100%; |
|||
border: 1px solid #eee; |
|||
background: #fdfdfd; |
|||
font-size: 0.9em; |
|||
} |
|||
|
|||
table.footnote + table.footnote { |
|||
margin-top: -15px; |
|||
border-top: none; |
|||
} |
|||
|
|||
table.field-list th { |
|||
padding: 0 0.8em 0 0; |
|||
} |
|||
|
|||
table.field-list td { |
|||
padding: 0; |
|||
} |
|||
|
|||
table.footnote td.label { |
|||
width: 0px; |
|||
padding: 0.3em 0 0.3em 0.5em; |
|||
} |
|||
|
|||
table.footnote td { |
|||
padding: 0.3em 0.5em; |
|||
} |
|||
|
|||
dl { |
|||
margin: 0; |
|||
padding: 0; |
|||
} |
|||
|
|||
dl dd { |
|||
margin-left: 30px; |
|||
} |
|||
|
|||
blockquote { |
|||
margin: 0 0 0 30px; |
|||
padding: 0; |
|||
} |
|||
|
|||
ul, ol { |
|||
margin: 10px 0 10px 30px; |
|||
padding: 0; |
|||
} |
|||
|
|||
pre { |
|||
background: #eee; |
|||
padding: 7px 30px; |
|||
margin: 15px -30px; |
|||
line-height: 1.3em; |
|||
} |
|||
|
|||
dl pre, blockquote pre, li pre { |
|||
margin-left: -60px; |
|||
padding-left: 60px; |
|||
} |
|||
|
|||
dl dl pre { |
|||
margin-left: -90px; |
|||
padding-left: 90px; |
|||
} |
|||
|
|||
tt { |
|||
background-color: #ecf0f3; |
|||
color: #222; |
|||
/* padding: 1px 2px; */ |
|||
} |
|||
|
|||
tt.xref, a tt { |
|||
background-color: #FBFBFB; |
|||
border-bottom: 1px solid white; |
|||
} |
|||
|
|||
a.reference { |
|||
text-decoration: none; |
|||
border-bottom: 1px dotted #004B6B; |
|||
} |
|||
|
|||
a.reference:hover { |
|||
border-bottom: 1px solid #6D4100; |
|||
} |
|||
|
|||
a.footnote-reference { |
|||
text-decoration: none; |
|||
font-size: 0.7em; |
|||
vertical-align: top; |
|||
border-bottom: 1px dotted #004B6B; |
|||
} |
|||
|
|||
a.footnote-reference:hover { |
|||
border-bottom: 1px solid #6D4100; |
|||
} |
|||
|
|||
a:hover tt { |
|||
background: #EEE; |
|||
} |
|||
|
|||
|
|||
@media screen and (max-width: 870px) { |
|||
|
|||
div.sphinxsidebar { |
|||
display: none; |
|||
} |
|||
|
|||
div.document { |
|||
width: 100%; |
|||
|
|||
} |
|||
|
|||
div.documentwrapper { |
|||
margin-left: 0; |
|||
margin-top: 0; |
|||
margin-right: 0; |
|||
margin-bottom: 0; |
|||
} |
|||
|
|||
div.bodywrapper { |
|||
margin-top: 0; |
|||
margin-right: 0; |
|||
margin-bottom: 0; |
|||
margin-left: 0; |
|||
} |
|||
|
|||
ul { |
|||
margin-left: 0; |
|||
} |
|||
|
|||
.document { |
|||
width: auto; |
|||
} |
|||
|
|||
.footer { |
|||
width: auto; |
|||
} |
|||
|
|||
.bodywrapper { |
|||
margin: 0; |
|||
} |
|||
|
|||
.footer { |
|||
width: auto; |
|||
} |
|||
|
|||
.github { |
|||
display: none; |
|||
} |
|||
|
|||
|
|||
|
|||
} |
|||
|
|||
|
|||
|
|||
@media screen and (max-width: 875px) { |
|||
|
|||
body { |
|||
margin: 0; |
|||
padding: 20px 30px; |
|||
} |
|||
|
|||
div.documentwrapper { |
|||
float: none; |
|||
background: white; |
|||
} |
|||
|
|||
div.sphinxsidebar { |
|||
display: block; |
|||
float: none; |
|||
width: 102.5%; |
|||
margin: 50px -30px -20px -30px; |
|||
padding: 10px 20px; |
|||
background: #333; |
|||
color: white; |
|||
} |
|||
|
|||
div.sphinxsidebar h3, div.sphinxsidebar h4, div.sphinxsidebar p, |
|||
div.sphinxsidebar h3 a { |
|||
color: white; |
|||
} |
|||
|
|||
div.sphinxsidebar a { |
|||
color: #aaa; |
|||
} |
|||
|
|||
div.sphinxsidebar p.logo { |
|||
display: none; |
|||
} |
|||
|
|||
div.document { |
|||
width: 100%; |
|||
margin: 0; |
|||
} |
|||
|
|||
div.related { |
|||
display: block; |
|||
margin: 0; |
|||
padding: 10px 0 20px 0; |
|||
} |
|||
|
|||
div.related ul, |
|||
div.related ul li { |
|||
margin: 0; |
|||
padding: 0; |
|||
} |
|||
|
|||
div.footer { |
|||
display: none; |
|||
} |
|||
|
|||
div.bodywrapper { |
|||
margin: 0; |
|||
} |
|||
|
|||
div.body { |
|||
min-height: 0; |
|||
padding: 0; |
|||
} |
|||
|
|||
.rtd_doc_footer { |
|||
display: none; |
|||
} |
|||
|
|||
.document { |
|||
width: auto; |
|||
} |
|||
|
|||
.footer { |
|||
width: auto; |
|||
} |
|||
|
|||
.footer { |
|||
width: auto; |
|||
} |
|||
|
|||
.github { |
|||
display: none; |
|||
} |
|||
} |
|||
|
|||
|
|||
/* scrollbars */ |
|||
|
|||
::-webkit-scrollbar { |
|||
width: 6px; |
|||
height: 6px; |
|||
} |
|||
|
|||
::-webkit-scrollbar-button:start:decrement, |
|||
::-webkit-scrollbar-button:end:increment { |
|||
display: block; |
|||
height: 10px; |
|||
} |
|||
|
|||
::-webkit-scrollbar-button:vertical:increment { |
|||
background-color: #fff; |
|||
} |
|||
|
|||
::-webkit-scrollbar-track-piece { |
|||
background-color: #eee; |
|||
-webkit-border-radius: 3px; |
|||
} |
|||
|
|||
::-webkit-scrollbar-thumb:vertical { |
|||
height: 50px; |
|||
background-color: #ccc; |
|||
-webkit-border-radius: 3px; |
|||
} |
|||
|
|||
::-webkit-scrollbar-thumb:horizontal { |
|||
width: 50px; |
|||
background-color: #ccc; |
|||
-webkit-border-radius: 3px; |
|||
} |
|||
|
|||
/* misc. */ |
|||
|
|||
.revsys-inline { |
|||
display: none!important; |
|||
} |
@ -1,9 +0,0 @@ |
|||
[theme] |
|||
inherit = basic |
|||
stylesheet = flasky.css |
|||
pygments_style = flask_theme_support.FlaskyStyle |
|||
|
|||
[options] |
|||
index_logo = '' |
|||
index_logo_height = 120px |
|||
touch_icon = |
@ -1,22 +0,0 @@ |
|||
{% extends "basic/layout.html" %} |
|||
{% block header %} |
|||
{{ super() }} |
|||
{% if pagename == 'index' %} |
|||
<div class=indexwrapper> |
|||
{% endif %} |
|||
{% endblock %} |
|||
{% block footer %} |
|||
{% if pagename == 'index' %} |
|||
</div> |
|||
{% endif %} |
|||
{% endblock %} |
|||
{# do not display relbars #} |
|||
{% block relbar1 %}{% endblock %} |
|||
{% block relbar2 %} |
|||
{% if theme_github_fork %} |
|||
<a href="http://github.com/{{ theme_github_fork }}"><img style="position: fixed; top: 0; right: 0; border: 0;" |
|||
src="http://s3.amazonaws.com/github/ribbons/forkme_right_darkblue_121621.png" alt="Fork me on GitHub" /></a> |
|||
{% endif %} |
|||
{% endblock %} |
|||
{% block sidebar1 %}{% endblock %} |
|||
{% block sidebar2 %}{% endblock %} |
@ -1,291 +0,0 @@ |
|||
/* |
|||
* flasky.css_t |
|||
* ~~~~~~~~~~~~ |
|||
* |
|||
* Sphinx stylesheet -- flasky theme based on nature theme. |
|||
* |
|||
* :copyright: Copyright 2007-2010 by the Sphinx team, see AUTHORS. |
|||
* :license: BSD, see LICENSE for details. |
|||
* |
|||
*/ |
|||
|
|||
@import url("basic.css"); |
|||
|
|||
/* -- page layout ----------------------------------------------------------- */ |
|||
|
|||
body { |
|||
font-family: 'Georgia', serif; |
|||
font-size: 17px; |
|||
color: #000; |
|||
background: white; |
|||
margin: 0; |
|||
padding: 0; |
|||
} |
|||
|
|||
div.documentwrapper { |
|||
float: left; |
|||
width: 100%; |
|||
} |
|||
|
|||
div.bodywrapper { |
|||
margin: 40px auto 0 auto; |
|||
width: 700px; |
|||
} |
|||
|
|||
hr { |
|||
border: 1px solid #B1B4B6; |
|||
} |
|||
|
|||
div.body { |
|||
background-color: #ffffff; |
|||
color: #3E4349; |
|||
padding: 0 30px 30px 30px; |
|||
} |
|||
|
|||
img.floatingflask { |
|||
padding: 0 0 10px 10px; |
|||
float: right; |
|||
} |
|||
|
|||
div.footer { |
|||
text-align: right; |
|||
color: #888; |
|||
padding: 10px; |
|||
font-size: 14px; |
|||
width: 650px; |
|||
margin: 0 auto 40px auto; |
|||
} |
|||
|
|||
div.footer a { |
|||
color: #888; |
|||
text-decoration: underline; |
|||
} |
|||
|
|||
div.related { |
|||
line-height: 32px; |
|||
color: #888; |
|||
} |
|||
|
|||
div.related ul { |
|||
padding: 0 0 0 10px; |
|||
} |
|||
|
|||
div.related a { |
|||
color: #444; |
|||
} |
|||
|
|||
/* -- body styles ----------------------------------------------------------- */ |
|||
|
|||
a { |
|||
color: #004B6B; |
|||
text-decoration: underline; |
|||
} |
|||
|
|||
a:hover { |
|||
color: #6D4100; |
|||
text-decoration: underline; |
|||
} |
|||
|
|||
div.body { |
|||
padding-bottom: 40px; /* saved for footer */ |
|||
} |
|||
|
|||
div.body h1, |
|||
div.body h2, |
|||
div.body h3, |
|||
div.body h4, |
|||
div.body h5, |
|||
div.body h6 { |
|||
font-family: 'Garamond', 'Georgia', serif; |
|||
font-weight: normal; |
|||
margin: 30px 0px 10px 0px; |
|||
padding: 0; |
|||
} |
|||
|
|||
{% if theme_index_logo %} |
|||
div.indexwrapper h1 { |
|||
text-indent: -999999px; |
|||
background: url({{ theme_index_logo }}) no-repeat center center; |
|||
height: {{ theme_index_logo_height }}; |
|||
} |
|||
{% endif %} |
|||
|
|||
div.body h2 { font-size: 180%; } |
|||
div.body h3 { font-size: 150%; } |
|||
div.body h4 { font-size: 130%; } |
|||
div.body h5 { font-size: 100%; } |
|||
div.body h6 { font-size: 100%; } |
|||
|
|||
a.headerlink { |
|||
color: white; |
|||
padding: 0 4px; |
|||
text-decoration: none; |
|||
} |
|||
|
|||
a.headerlink:hover { |
|||
color: #444; |
|||
background: #eaeaea; |
|||
} |
|||
|
|||
div.body p, div.body dd, div.body li { |
|||
line-height: 1.4em; |
|||
} |
|||
|
|||
div.admonition { |
|||
background: #fafafa; |
|||
margin: 20px -30px; |
|||
padding: 10px 30px; |
|||
border-top: 1px solid #ccc; |
|||
border-bottom: 1px solid #ccc; |
|||
} |
|||
|
|||
div.admonition p.admonition-title { |
|||
font-family: 'Garamond', 'Georgia', serif; |
|||
font-weight: normal; |
|||
font-size: 24px; |
|||
margin: 0 0 10px 0; |
|||
padding: 0; |
|||
line-height: 1; |
|||
} |
|||
|
|||
div.admonition p.last { |
|||
margin-bottom: 0; |
|||
} |
|||
|
|||
div.highlight{ |
|||
background-color: white; |
|||
} |
|||
|
|||
dt:target, .highlight { |
|||
background: #FAF3E8; |
|||
} |
|||
|
|||
div.note { |
|||
background-color: #eee; |
|||
border: 1px solid #ccc; |
|||
} |
|||
|
|||
div.seealso { |
|||
background-color: #ffc; |
|||
border: 1px solid #ff6; |
|||
} |
|||
|
|||
div.topic { |
|||
background-color: #eee; |
|||
} |
|||
|
|||
div.warning { |
|||
background-color: #ffe4e4; |
|||
border: 1px solid #f66; |
|||
} |
|||
|
|||
p.admonition-title { |
|||
display: inline; |
|||
} |
|||
|
|||
p.admonition-title:after { |
|||
content: ":"; |
|||
} |
|||
|
|||
pre, tt { |
|||
font-family: 'Consolas', 'Menlo', 'Deja Vu Sans Mono', 'Bitstream Vera Sans Mono', monospace; |
|||
font-size: 0.85em; |
|||
} |
|||
|
|||
img.screenshot { |
|||
} |
|||
|
|||
tt.descname, tt.descclassname { |
|||
font-size: 0.95em; |
|||
} |
|||
|
|||
tt.descname { |
|||
padding-right: 0.08em; |
|||
} |
|||
|
|||
img.screenshot { |
|||
-moz-box-shadow: 2px 2px 4px #eee; |
|||
-webkit-box-shadow: 2px 2px 4px #eee; |
|||
box-shadow: 2px 2px 4px #eee; |
|||
} |
|||
|
|||
table.docutils { |
|||
border: 1px solid #888; |
|||
-moz-box-shadow: 2px 2px 4px #eee; |
|||
-webkit-box-shadow: 2px 2px 4px #eee; |
|||
box-shadow: 2px 2px 4px #eee; |
|||
} |
|||
|
|||
table.docutils td, table.docutils th { |
|||
border: 1px solid #888; |
|||
padding: 0.25em 0.7em; |
|||
} |
|||
|
|||
table.field-list, table.footnote { |
|||
border: none; |
|||
-moz-box-shadow: none; |
|||
-webkit-box-shadow: none; |
|||
box-shadow: none; |
|||
} |
|||
|
|||
table.footnote { |
|||
margin: 15px 0; |
|||
width: 100%; |
|||
border: 1px solid #eee; |
|||
} |
|||
|
|||
table.field-list th { |
|||
padding: 0 0.8em 0 0; |
|||
} |
|||
|
|||
table.field-list td { |
|||
padding: 0; |
|||
} |
|||
|
|||
table.footnote td { |
|||
padding: 0.5em; |
|||
} |
|||
|
|||
dl { |
|||
margin: 0; |
|||
padding: 0; |
|||
} |
|||
|
|||
dl dd { |
|||
margin-left: 30px; |
|||
} |
|||
|
|||
pre { |
|||
padding: 0; |
|||
margin: 15px -30px; |
|||
padding: 8px; |
|||
line-height: 1.3em; |
|||
padding: 7px 30px; |
|||
background: #eee; |
|||
border-radius: 2px; |
|||
-moz-border-radius: 2px; |
|||
-webkit-border-radius: 2px; |
|||
} |
|||
|
|||
dl pre { |
|||
margin-left: -60px; |
|||
padding-left: 60px; |
|||
} |
|||
|
|||
dl.class { |
|||
margin-bottom: 50px; |
|||
} |
|||
|
|||
tt { |
|||
background-color: #ecf0f3; |
|||
color: #222; |
|||
/* padding: 1px 2px; */ |
|||
} |
|||
|
|||
tt.xref, a tt { |
|||
background-color: #FBFBFB; |
|||
} |
|||
|
|||
a:hover tt { |
|||
background: #EEE; |
|||
} |
@ -1,10 +0,0 @@ |
|||
[theme] |
|||
inherit = basic |
|||
stylesheet = flasky.css |
|||
nosidebar = true |
|||
pygments_style = flask_theme_support.FlaskyStyle |
|||
|
|||
[options] |
|||
index_logo = '' |
|||
index_logo_height = 120px |
|||
github_fork = '' |
@ -1,86 +0,0 @@ |
|||
# flasky extensions. flasky pygments style based on tango style |
|||
from pygments.style import Style |
|||
from pygments.token import Keyword, Name, Comment, String, Error, \ |
|||
Number, Operator, Generic, Whitespace, Punctuation, Other, Literal |
|||
|
|||
|
|||
class FlaskyStyle(Style): |
|||
background_color = "#f8f8f8" |
|||
default_style = "" |
|||
|
|||
styles = { |
|||
# No corresponding class for the following: |
|||
#Text: "", # class: '' |
|||
Whitespace: "underline #f8f8f8", # class: 'w' |
|||
Error: "#a40000 border:#ef2929", # class: 'err' |
|||
Other: "#000000", # class 'x' |
|||
|
|||
Comment: "italic #8f5902", # class: 'c' |
|||
Comment.Preproc: "noitalic", # class: 'cp' |
|||
|
|||
Keyword: "bold #004461", # class: 'k' |
|||
Keyword.Constant: "bold #004461", # class: 'kc' |
|||
Keyword.Declaration: "bold #004461", # class: 'kd' |
|||
Keyword.Namespace: "bold #004461", # class: 'kn' |
|||
Keyword.Pseudo: "bold #004461", # class: 'kp' |
|||
Keyword.Reserved: "bold #004461", # class: 'kr' |
|||
Keyword.Type: "bold #004461", # class: 'kt' |
|||
|
|||
Operator: "#582800", # class: 'o' |
|||
Operator.Word: "bold #004461", # class: 'ow' - like keywords |
|||
|
|||
Punctuation: "bold #000000", # class: 'p' |
|||
|
|||
# because special names such as Name.Class, Name.Function, etc. |
|||
# are not recognized as such later in the parsing, we choose them |
|||
# to look the same as ordinary variables. |
|||
Name: "#000000", # class: 'n' |
|||
Name.Attribute: "#c4a000", # class: 'na' - to be revised |
|||
Name.Builtin: "#004461", # class: 'nb' |
|||
Name.Builtin.Pseudo: "#3465a4", # class: 'bp' |
|||
Name.Class: "#000000", # class: 'nc' - to be revised |
|||
Name.Constant: "#000000", # class: 'no' - to be revised |
|||
Name.Decorator: "#888", # class: 'nd' - to be revised |
|||
Name.Entity: "#ce5c00", # class: 'ni' |
|||
Name.Exception: "bold #cc0000", # class: 'ne' |
|||
Name.Function: "#000000", # class: 'nf' |
|||
Name.Property: "#000000", # class: 'py' |
|||
Name.Label: "#f57900", # class: 'nl' |
|||
Name.Namespace: "#000000", # class: 'nn' - to be revised |
|||
Name.Other: "#000000", # class: 'nx' |
|||
Name.Tag: "bold #004461", # class: 'nt' - like a keyword |
|||
Name.Variable: "#000000", # class: 'nv' - to be revised |
|||
Name.Variable.Class: "#000000", # class: 'vc' - to be revised |
|||
Name.Variable.Global: "#000000", # class: 'vg' - to be revised |
|||
Name.Variable.Instance: "#000000", # class: 'vi' - to be revised |
|||
|
|||
Number: "#990000", # class: 'm' |
|||
|
|||
Literal: "#000000", # class: 'l' |
|||
Literal.Date: "#000000", # class: 'ld' |
|||
|
|||
String: "#4e9a06", # class: 's' |
|||
String.Backtick: "#4e9a06", # class: 'sb' |
|||
String.Char: "#4e9a06", # class: 'sc' |
|||
String.Doc: "italic #8f5902", # class: 'sd' - like a comment |
|||
String.Double: "#4e9a06", # class: 's2' |
|||
String.Escape: "#4e9a06", # class: 'se' |
|||
String.Heredoc: "#4e9a06", # class: 'sh' |
|||
String.Interpol: "#4e9a06", # class: 'si' |
|||
String.Other: "#4e9a06", # class: 'sx' |
|||
String.Regex: "#4e9a06", # class: 'sr' |
|||
String.Single: "#4e9a06", # class: 's1' |
|||
String.Symbol: "#4e9a06", # class: 'ss' |
|||
|
|||
Generic: "#000000", # class: 'g' |
|||
Generic.Deleted: "#a40000", # class: 'gd' |
|||
Generic.Emph: "italic #000000", # class: 'ge' |
|||
Generic.Error: "#ef2929", # class: 'gr' |
|||
Generic.Heading: "bold #000080", # class: 'gh' |
|||
Generic.Inserted: "#00A000", # class: 'gi' |
|||
Generic.Output: "#888", # class: 'go' |
|||
Generic.Prompt: "#745334", # class: 'gp' |
|||
Generic.Strong: "bold #000000", # class: 'gs' |
|||
Generic.Subheading: "bold #800080", # class: 'gu' |
|||
Generic.Traceback: "bold #a40000", # class: 'gt' |
|||
} |
@ -0,0 +1,88 @@ |
|||
API Reference |
|||
============= |
|||
|
|||
.. toctree:: |
|||
:maxdepth: 3 |
|||
|
|||
.. module:: socketio |
|||
|
|||
``Server`` class |
|||
---------------- |
|||
|
|||
.. autoclass:: Server |
|||
:members: |
|||
|
|||
``AsyncServer`` class |
|||
--------------------- |
|||
|
|||
.. autoclass:: AsyncServer |
|||
:members: |
|||
:inherited-members: |
|||
|
|||
``WSGIApp`` class |
|||
----------------- |
|||
|
|||
.. autoclass:: WSGIApp |
|||
:members: |
|||
|
|||
``ASGIApp`` class |
|||
----------------- |
|||
|
|||
.. autoclass:: ASGIApp |
|||
:members: |
|||
|
|||
``Middleware`` class (deprecated) |
|||
--------------------------------- |
|||
|
|||
.. autoclass:: Middleware |
|||
:members: |
|||
|
|||
``Namespace`` class |
|||
------------------- |
|||
|
|||
.. autoclass:: Namespace |
|||
:members: |
|||
|
|||
``AsyncNamespace`` class |
|||
------------------------ |
|||
|
|||
.. autoclass:: AsyncNamespace |
|||
:members: |
|||
:inherited-members: |
|||
|
|||
``BaseManager`` class |
|||
--------------------- |
|||
|
|||
.. autoclass:: BaseManager |
|||
:members: |
|||
|
|||
``PubSubManager`` class |
|||
----------------------- |
|||
|
|||
.. autoclass:: PubSubManager |
|||
:members: |
|||
|
|||
``KombuManager`` class |
|||
---------------------- |
|||
|
|||
.. autoclass:: KombuManager |
|||
:members: |
|||
|
|||
``RedisManager`` class |
|||
---------------------- |
|||
|
|||
.. autoclass:: RedisManager |
|||
:members: |
|||
|
|||
``AsyncManager`` class |
|||
---------------------- |
|||
|
|||
.. autoclass:: AsyncManager |
|||
:members: |
|||
:inherited-members: |
|||
|
|||
``AsyncRedisManager`` class |
|||
--------------------------- |
|||
|
|||
.. autoclass:: AsyncRedisManager |
|||
:members: |
@ -0,0 +1,277 @@ |
|||
Deployment |
|||
========== |
|||
|
|||
The following sections describe a variety of deployment strategies for |
|||
Socket.IO servers. |
|||
|
|||
aiohttp |
|||
------- |
|||
|
|||
`Aiohttp <http://aiohttp.readthedocs.io/>`_ is a framework with support for HTTP |
|||
and WebSocket, based on asyncio. Support for this framework is limited to Python |
|||
3.5 and newer. |
|||
|
|||
Instances of class ``socketio.AsyncServer`` will automatically use aiohttp |
|||
for asynchronous operations if the library is installed. To request its use |
|||
explicitly, the ``async_mode`` option can be given in the constructor:: |
|||
|
|||
sio = socketio.AsyncServer(async_mode='aiohttp') |
|||
|
|||
A server configured for aiohttp must be attached to an existing application:: |
|||
|
|||
app = web.Application() |
|||
sio.attach(app) |
|||
|
|||
The aiohttp application can define regular routes that will coexist with the |
|||
Socket.IO server. A typical pattern is to add routes that serve a client |
|||
application and any associated static files. |
|||
|
|||
The aiohttp application is then executed in the usual manner:: |
|||
|
|||
if __name__ == '__main__': |
|||
web.run_app(app) |
|||
|
|||
Tornado |
|||
------- |
|||
|
|||
`Tornado <http://www.tornadoweb.org//>`_ is a web framework with support |
|||
for HTTP and WebSocket. Support for this framework requires Python 3.5 and |
|||
newer. Only Tornado version 5 and newer are supported, thanks to its tight |
|||
integration with asyncio. |
|||
|
|||
Instances of class ``socketio.AsyncServer`` will automatically use tornado |
|||
for asynchronous operations if the library is installed. To request its use |
|||
explicitly, the ``async_mode`` option can be given in the constructor:: |
|||
|
|||
sio = socketio.AsyncServer(async_mode='tornado') |
|||
|
|||
A server configured for tornado must include a request handler for |
|||
Engine.IO:: |
|||
|
|||
app = tornado.web.Application( |
|||
[ |
|||
(r"/socketio.io/", socketio.get_tornado_handler(sio)), |
|||
], |
|||
# ... other application options |
|||
) |
|||
|
|||
The tornado application can define other routes that will coexist with the |
|||
Socket.IO server. A typical pattern is to add routes that serve a client |
|||
application and any associated static files. |
|||
|
|||
The tornado application is then executed in the usual manner:: |
|||
|
|||
app.listen(port) |
|||
tornado.ioloop.IOLoop.current().start() |
|||
|
|||
Sanic |
|||
----- |
|||
|
|||
`Sanic <http://sanic.readthedocs.io/>`_ is a very efficient asynchronous web |
|||
server for Python 3.5 and newer. |
|||
|
|||
Instances of class ``socketio.AsyncServer`` will automatically use Sanic for |
|||
asynchronous operations if the framework is installed. To request its use |
|||
explicitly, the ``async_mode`` option can be given in the constructor:: |
|||
|
|||
sio = socketio.AsyncServer(async_mode='sanic') |
|||
|
|||
A server configured for aiohttp must be attached to an existing application:: |
|||
|
|||
app = web.Application() |
|||
sio.attach(app) |
|||
|
|||
The Sanic application can define regular routes that will coexist with the |
|||
Socket.IO server. A typical pattern is to add routes that serve a client |
|||
application and any associated static files. |
|||
|
|||
The Sanic application is then executed in the usual manner:: |
|||
|
|||
if __name__ == '__main__': |
|||
app.run() |
|||
|
|||
Uvicorn, Daphne, and other ASGI servers |
|||
--------------------------------------- |
|||
|
|||
The ``socketio.ASGIApp`` class is an ASGI compatible application that can |
|||
forward Socket.IO traffic to an ``socketio.AsyncServer`` instance:: |
|||
|
|||
sio = socketio.AsyncServer(async_mode='asgi') |
|||
app = socketio.ASGIApp(sio) |
|||
|
|||
The application can then be deployed with any ASGI compatible web server. |
|||
|
|||
Eventlet |
|||
-------- |
|||
|
|||
`Eventlet <http://eventlet.net/>`_ is a high performance concurrent networking |
|||
library for Python 2 and 3 that uses coroutines, enabling code to be written in |
|||
the same style used with the blocking standard library functions. An Socket.IO |
|||
server deployed with eventlet has access to the long-polling and WebSocket |
|||
transports. |
|||
|
|||
Instances of class ``socketio.Server`` will automatically use eventlet for |
|||
asynchronous operations if the library is installed. To request its use |
|||
explicitly, the ``async_mode`` option can be given in the constructor:: |
|||
|
|||
sio = socketio.Server(async_mode='eventlet') |
|||
|
|||
A server configured for eventlet is deployed as a regular WSGI application, |
|||
using the provided ``socketio.Middleware``:: |
|||
|
|||
app = socketio.Middleware(sio) |
|||
import eventlet |
|||
eventlet.wsgi.server(eventlet.listen(('', 8000)), app) |
|||
|
|||
Using Gunicorn with Eventlet |
|||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
|||
|
|||
An alternative to running the eventlet WSGI server as above is to use |
|||
`gunicorn <gunicorn.org>`_, a fully featured pure Python web server. The |
|||
command to launch the application under gunicorn is shown below:: |
|||
|
|||
$ gunicorn -k eventlet -w 1 module:app |
|||
|
|||
Due to limitations in its load balancing algorithm, gunicorn can only be used |
|||
with one worker process, so the ``-w`` option cannot be set to a value higher |
|||
than 1. A single eventlet worker can handle a large number of concurrent |
|||
clients, each handled by a greenlet. |
|||
|
|||
Eventlet provides a ``monkey_patch()`` function that replaces all the blocking |
|||
functions in the standard library with equivalent asynchronous versions. While |
|||
python-socketio does not require monkey patching, other libraries such as |
|||
database drivers are likely to require it. |
|||
|
|||
Gevent |
|||
------ |
|||
|
|||
`Gevent <http://gevent.org/>`_ is another asynchronous framework based on |
|||
coroutines, very similar to eventlet. An Socket.IO server deployed with |
|||
gevent has access to the long-polling transport. If project |
|||
`gevent-websocket <https://bitbucket.org/Jeffrey/gevent-websocket/>`_ is |
|||
installed, the WebSocket transport is also available. |
|||
|
|||
Instances of class ``socketio.Server`` will automatically use gevent for |
|||
asynchronous operations if the library is installed and eventlet is not |
|||
installed. To request gevent to be selected explicitly, the ``async_mode`` |
|||
option can be given in the constructor:: |
|||
|
|||
sio = socketio.Server(async_mode='gevent') |
|||
|
|||
A server configured for gevent is deployed as a regular WSGI application, |
|||
using the provided ``socketio.Middleware``:: |
|||
|
|||
app = socketio.Middleware(sio) |
|||
from gevent import pywsgi |
|||
pywsgi.WSGIServer(('', 8000), app).serve_forever() |
|||
|
|||
If the WebSocket transport is installed, then the server must be started as |
|||
follows:: |
|||
|
|||
from gevent import pywsgi |
|||
from geventwebsocket.handler import WebSocketHandler |
|||
app = socketio.Middleware(sio) |
|||
pywsgi.WSGIServer(('', 8000), app, |
|||
handler_class=WebSocketHandler).serve_forever() |
|||
|
|||
Using Gunicorn with Gevent |
|||
~~~~~~~~~~~~~~~~~~~~~~~~~~ |
|||
|
|||
An alternative to running the gevent WSGI server as above is to use |
|||
`gunicorn <gunicorn.org>`_, a fully featured pure Python web server. The |
|||
command to launch the application under gunicorn is shown below:: |
|||
|
|||
$ gunicorn -k gevent -w 1 module:app |
|||
|
|||
Or to include WebSocket:: |
|||
|
|||
$ gunicorn -k geventwebsocket.gunicorn.workers.GeventWebSocketWorker -w 1 module: app |
|||
|
|||
Same as with eventlet, due to limitations in its load balancing algorithm, |
|||
gunicorn can only be used with one worker process, so the ``-w`` option cannot |
|||
be higher than 1. A single gevent worker can handle a large number of |
|||
concurrent clients through the use of greenlets. |
|||
|
|||
Gevent provides a ``monkey_patch()`` function that replaces all the blocking |
|||
functions in the standard library with equivalent asynchronous versions. While |
|||
python-socketio does not require monkey patching, other libraries such as |
|||
database drivers are likely to require it. |
|||
|
|||
uWSGI |
|||
----- |
|||
|
|||
When using the uWSGI server in combination with gevent, the Socket.IO server |
|||
can take advantage of uWSGI's native WebSocket support. |
|||
|
|||
Instances of class ``socketio.Server`` will automatically use this option for |
|||
asynchronous operations if both gevent and uWSGI are installed and eventlet is |
|||
not installed. To request this asynchronous mode explicitly, the |
|||
``async_mode`` option can be given in the constructor:: |
|||
|
|||
# gevent with uWSGI |
|||
sio = socketio.Server(async_mode='gevent_uwsgi') |
|||
|
|||
A complete explanation of the configuration and usage of the uWSGI server is |
|||
beyond the scope of this documentation. The uWSGI server is a fairly complex |
|||
package that provides a large and comprehensive set of options. It must be |
|||
compiled with WebSocket and SSL support for the WebSocket transport to be |
|||
available. As way of an introduction, the following command starts a uWSGI |
|||
server for the ``latency.py`` example on port 5000:: |
|||
|
|||
$ uwsgi --http :5000 --gevent 1000 --http-websockets --master --wsgi-file latency.py --callable app |
|||
|
|||
Standard Threads |
|||
---------------- |
|||
|
|||
While not comparable to eventlet and gevent in terms of performance, |
|||
the Socket.IO server can also be configured to work with multi-threaded web |
|||
servers that use standard Python threads. This is an ideal setup to use with |
|||
development servers such as `Werkzeug <http://werkzeug.pocoo.org>`_. Only the |
|||
long-polling transport is currently available when using standard threads. |
|||
|
|||
Instances of class ``socketio.Server`` will automatically use the threading |
|||
mode if neither eventlet nor gevent are not installed. To request the |
|||
threading mode explicitly, the ``async_mode`` option can be given in the |
|||
constructor:: |
|||
|
|||
sio = socketio.Server(async_mode='threading') |
|||
|
|||
A server configured for threading is deployed as a regular web application, |
|||
using any WSGI complaint multi-threaded server. The example below deploys an |
|||
Socket.IO application combined with a Flask web application, using Flask's |
|||
development web server based on Werkzeug:: |
|||
|
|||
sio = socketio.Server(async_mode='threading') |
|||
app = Flask(__name__) |
|||
app.wsgi_app = socketio.Middleware(sio, app.wsgi_app) |
|||
|
|||
# ... Socket.IO and Flask handler functions ... |
|||
|
|||
if __name__ == '__main__': |
|||
app.run(threaded=True) |
|||
|
|||
When using the threading mode, it is important to ensure that the WSGI server |
|||
can handle multiple concurrent requests using threads, since a client can have |
|||
up to two outstanding requests at any given time. The Werkzeug server is |
|||
single-threaded by default, so the ``threaded=True`` option is required. |
|||
|
|||
Note that servers that use worker processes instead of threads, such as |
|||
gunicorn, do not support a Socket.IO server configured in threading mode. |
|||
|
|||
Scalability Notes |
|||
----------------- |
|||
|
|||
Socket.IO is a stateful protocol, which makes horizontal scaling more |
|||
difficult. To deploy a cluster of Socket.IO processes hosted on one or |
|||
multiple servers, the following conditions must be met: |
|||
|
|||
- Each Socket.IO process must be able to handle multiple requests |
|||
concurrently. This is required because long-polling clients send two |
|||
requests in parallel. Worker processes that can only handle one request at a |
|||
time are not supported. |
|||
- The load balancer must be configured to always forward requests from a |
|||
client to the same worker process. Load balancers call this *sticky |
|||
sessions*, or *session affinity*. |
|||
- The worker processes need to communicate with each other to coordinate |
|||
complex operations such as broadcasts. This is done through a configured |
|||
message queue. See the section on using message queues for details. |
@ -0,0 +1,346 @@ |
|||
User's Guide |
|||
============ |
|||
|
|||
The ``Server`` and ``AsyncServer`` classes |
|||
------------------------------------------ |
|||
|
|||
A Socket.IO server is an instance of class :class:`socketio.Server`. This |
|||
instance can be transformed into a standard WSGI application by wrapping it |
|||
with the :class:`socketio.WSGIApp` class:: |
|||
|
|||
import socketio |
|||
|
|||
# create a Socket.IO server |
|||
sio = socketio.Server() |
|||
|
|||
# wrap with a WSGI application |
|||
app = socketio.WSGIApp(sio) |
|||
|
|||
For asyncio based servers, the :class:`socketio.AsyncServer` class provides |
|||
the same functionality, but in a coroutine friendly format. If desired, The |
|||
:class:`socketio.ASGIApp` class can transform the server into a standard |
|||
ASGI application:: |
|||
|
|||
# create a Socket.IO server |
|||
sio = socketio.AsyncServer() |
|||
|
|||
# wrap with ASGI application |
|||
app = socketio.ASGIApp(sio) |
|||
|
|||
The WSGI and ASGI application wrappers support serving static files, which is |
|||
a convenient way to deliver JavaScript based Socket.IO clients to the web |
|||
browser:: |
|||
|
|||
app = socketio.ASGIApp(sio, static_files={ |
|||
'/': {'content_type': 'text/html', 'filename': 'latency.html'}, |
|||
'/static/style.css': {'content_type': 'text/css', |
|||
'filename': 'static/style.css'}, |
|||
}) |
|||
|
|||
The dictionary provided with the ``static_files`` argument has static file |
|||
endpoints as keys. For each of these endpoints, a dictionary with the file's |
|||
content type and local filename is given. |
|||
|
|||
These wrappers can also act as middlewares, forwarding any traffic that is not |
|||
intended to Socket.IO server to another application. This allows Socket.IO |
|||
servers to integrate easily into existing WSGI or ASGI applications:: |
|||
|
|||
from wsgi import app # a Flask, Django, etc. application |
|||
|
|||
app = socketio.WSGIApp(sio, app) |
|||
|
|||
Receiving Events |
|||
---------------- |
|||
|
|||
The Socket.IO protocol is event based. When a client wants to communicate with |
|||
the server it *emits* an event. Each event has a name, and a list of |
|||
arguments. The server registers event handler functions with the |
|||
:func:`socketio.Server.on` decorator:: |
|||
|
|||
@sio.on('my custom event') |
|||
def my_custom_event(sid, data): |
|||
pass |
|||
|
|||
For asyncio servers, event handlers can optionally be given as coroutines:: |
|||
|
|||
@sio.on('my custom event') |
|||
async def my_custom_event(sid, data): |
|||
pass |
|||
|
|||
The ``sid`` argument is the Socket.IO session id, a unique identifier of each |
|||
client connection. All the events sent by a given client will have the same |
|||
``sid`` value. |
|||
|
|||
The ``connect`` and ``disconnect`` are special; they are invoked automatically |
|||
when a client connects or disconnects from the server:: |
|||
|
|||
@sio.on('connect') |
|||
def connect(sid, environ): |
|||
print('connect ', sid) |
|||
|
|||
@sio.on('disconnect') |
|||
def disconnect(sid): |
|||
print('disconnect ', sid) |
|||
|
|||
The ``connect`` event is an ideal place to perform user authentication, and |
|||
any necessary mapping between user entities in the application and the ``sid`` |
|||
that was assigned to the client. The ``environ`` argument is a dictionary in |
|||
standard WSGI format containing the request information, including HTTP |
|||
headers. After inspecting the request, the connect event handler can return |
|||
``False`` to reject the connection with the client. |
|||
|
|||
Sending Events |
|||
-------------- |
|||
|
|||
Socket.IO is a bidirectional protocol, so at any time the server can send an |
|||
event to its connected clients. The :func:`socketio.Server.emit` method is |
|||
used for this task:: |
|||
|
|||
sio.emit('my event', {'data': 'foobar'}) |
|||
|
|||
Sometimes the server may want to send an event just to a particular client. |
|||
This can be achieved by adding a ``room`` argument to the emit call:: |
|||
|
|||
sio.emit('my event', {'data': 'foobar'}, room=user_sid) |
|||
|
|||
The :func:`socketio.Server.emit` method takes an event name, a message payload |
|||
of type ``str``, ``bytes``, ``list``, ``dict`` or ``tuple``, and the recipient |
|||
room. When sending a ``tuple``, the elements in it need to be of any of the |
|||
other four allowed types. The elements of the tuple will be passed as multiple |
|||
arguments to the client-side event handler function. The ``room`` argument is |
|||
used to identify the client that should receive the event, and is set to the |
|||
``sid`` value assigned to that client's connection with the server. When |
|||
omitted, the event is broadcasted to all connected clients. |
|||
|
|||
Rooms |
|||
----- |
|||
|
|||
To make it easy for the server to emit events to groups of related clients, |
|||
the application can put its clients into "rooms", and then address messages to |
|||
these rooms. |
|||
|
|||
In the previous section the ``room`` argument of the |
|||
:func:`socketio.SocketIO.emit` method was used to designate a specific |
|||
client as the recipient of the event. This is because upon connection, a |
|||
personal room for each client is created and named with the ``sid`` assigned |
|||
to the connection. The application is then free to create additional rooms and |
|||
manage which clients are in them using the :func:`socketio.Server.enter_room` |
|||
and :func:`socketio.Server.leave_room` methods. Clients can be in as many |
|||
rooms as needed and can be moved between rooms as often as necessary. |
|||
|
|||
:: |
|||
|
|||
@sio.on('chat') |
|||
def begin_chat(sid): |
|||
sio.enter_room(sid, 'chat_users') |
|||
|
|||
@sio.on('exit_chat') |
|||
def exit_chat(sid): |
|||
sio.leave_room(sid, 'chat_users') |
|||
|
|||
In chat applications it is often desired that an event is broadcasted to all |
|||
the members of the room except one, which is the originator of the event such |
|||
as a chat message. The :func:`socketio.Server.emit` method provides an |
|||
optional ``skip_sid`` argument to indicate a client that should be skipped |
|||
during the broadcast. |
|||
|
|||
:: |
|||
|
|||
@sio.on('my message') |
|||
def message(sid, data): |
|||
sio.emit('my reply', data, room='chat_users', skip_sid=sid) |
|||
|
|||
Event Callbacks |
|||
--------------- |
|||
|
|||
When a client sends an event to the server, it can optionally provide a |
|||
callback function, to be invoked as a way of acknowledgment that the server |
|||
has processed the event. While this is entirely managed by the client, the |
|||
server can provide a list of values that are to be passed on to the callback |
|||
function, simply by returning them from the handler function:: |
|||
|
|||
@sio.on('my event', namespace='/chat') |
|||
def my_event_handler(sid, data): |
|||
# handle the message |
|||
return "OK", 123 |
|||
|
|||
Likewise, the server can request a callback function to be invoked after a |
|||
client has processed an event. The :func:`socketio.Server.emit` method has an |
|||
optional ``callback`` argument that can be set to a callable. If this |
|||
argument is given, the callable will be invoked after the client has processed |
|||
the event, and any values returned by the client will be passed as arguments |
|||
to this function. Using callback functions when broadcasting to multiple |
|||
clients is not recommended, as the callback function will be invoked once for |
|||
each client that received the message. |
|||
|
|||
Namespaces |
|||
---------- |
|||
|
|||
The Socket.IO protocol supports multiple logical connections, all multiplexed |
|||
on the same physical connection. Clients can open multiple connections by |
|||
specifying a different *namespace* on each. A namespace is given by the client |
|||
as a pathname following the hostname and port. For example, connecting to |
|||
*http://example.com:8000/chat* would open a connection to the namespace |
|||
*/chat*. |
|||
|
|||
Each namespace is handled independently from the others, with separate session |
|||
IDs (``sid``\ s), event handlers and rooms. It is important that applications |
|||
that use multiple namespaces specify the correct namespace when setting up |
|||
their event handlers and rooms, using the optional ``namespace`` argument |
|||
available in all the methods in the :class:`socketio.Server` class:: |
|||
|
|||
@sio.on('my custom event', namespace='/chat') |
|||
def my_custom_event(sid, data): |
|||
pass |
|||
|
|||
When emitting an event, the ``namespace`` optional argument is used to specify |
|||
which namespace to send it on. When the ``namespace`` argument is omitted, the |
|||
default Socket.IO namespace, which is named ``/``, is used. |
|||
|
|||
Class-Based Namespaces |
|||
---------------------- |
|||
|
|||
As an alternative to the decorator-based event handlers, the event handlers |
|||
that belong to a namespace can be created as methods of a subclass of |
|||
:class:`socketio.Namespace`:: |
|||
|
|||
class MyCustomNamespace(socketio.Namespace): |
|||
def on_connect(self, sid, environ): |
|||
pass |
|||
|
|||
def on_disconnect(self, sid): |
|||
pass |
|||
|
|||
def on_my_event(self, sid, data): |
|||
self.emit('my_response', data) |
|||
|
|||
sio.register_namespace(MyCustomNamespace('/test')) |
|||
|
|||
For asyncio based severs, namespaces must inherit from |
|||
:class:`socketio.AsyncNamespace`, and can define event handlers as coroutines |
|||
if desired:: |
|||
|
|||
class MyCustomNamespace(socketio.AsyncNamespace): |
|||
def on_connect(self, sid, environ): |
|||
pass |
|||
|
|||
def on_disconnect(self, sid): |
|||
pass |
|||
|
|||
async def on_my_event(self, sid, data): |
|||
await self.emit('my_response', data) |
|||
|
|||
sio.register_namespace(MyCustomNamespace('/test')) |
|||
|
|||
When class-based namespaces are used, any events received by the server are |
|||
dispatched to a method named as the event name with the ``on_`` prefix. For |
|||
example, event ``my_event`` will be handled by a method named ``on_my_event``. |
|||
If an event is received for which there is no corresponding method defined in |
|||
the namespace class, then the event is ignored. All event names used in |
|||
class-based namespaces must use characters that are legal in method names. |
|||
|
|||
As a convenience to methods defined in a class-based namespace, the namespace |
|||
instance includes versions of several of the methods in the |
|||
:class:`socketio.Server` and :class:`socketio.AsyncServer` classes that default |
|||
to the proper namespace when the ``namespace`` argument is not given. |
|||
|
|||
In the case that an event has a handler in a class-based namespace, and also a |
|||
decorator-based function handler, only the standalone function handler is |
|||
invoked. |
|||
|
|||
It is important to note that class-based namespaces are singletons. This means |
|||
that a single instance of a namespace class is used for all clients, and |
|||
consequently, a namespace instance cannot be used to store client specific |
|||
information. |
|||
|
|||
Using a Message Queue |
|||
--------------------- |
|||
|
|||
When working with distributed applications, it is often necessary to access |
|||
the functionality of the Socket.IO from multiple processes. There are two |
|||
specific use cases: |
|||
|
|||
- Applications that use a work queues such as |
|||
`Celery <http://www.celeryproject.org/>`_ may need to emit an event to a |
|||
client once a background job completes. The most convenient place to carry |
|||
out this task is the worker process that handled this job. |
|||
|
|||
- Highly available applications may want to use horizontal scaling of the |
|||
Socket.IO server to be able to handle very large number of concurrent |
|||
clients. |
|||
|
|||
As a solution to the above problems, the Socket.IO server can be configured |
|||
to connect to a message queue such as `Redis <http://redis.io/>`_ or |
|||
`RabbitMQ <https://www.rabbitmq.com/>`_, to communicate with other related |
|||
Socket.IO servers or auxiliary workers. |
|||
|
|||
Redis |
|||
~~~~~ |
|||
|
|||
To use a Redis message queue, a Python Redis client must be installed:: |
|||
|
|||
# socketio.Server class |
|||
pip install redis |
|||
|
|||
# socketio.AsyncServer class |
|||
pip install aioredis |
|||
|
|||
The Redis queue is configured through the :class:`socketio.RedisManager` and |
|||
:class:`socketio.AsyncRedisManager` classes. These classes connect directly to |
|||
the Redis store and use the queue's pub/sub functionality:: |
|||
|
|||
# socketio.Server class |
|||
mgr = socketio.RedisManager('redis://') |
|||
sio = socketio.Server(client_manager=mgr) |
|||
|
|||
# socketio.AsyncServer class |
|||
mgr = socketio.AsyncRedisManager('redis://') |
|||
sio = socketio.AsyncServer(client_manager=mgr) |
|||
|
|||
The ``client_manager`` argument instructs the server to connect to the given |
|||
message queue, and to coordinate with other processes connected to the queue. |
|||
|
|||
Kombu |
|||
~~~~~ |
|||
|
|||
`Kombu <http://kombu.readthedocs.org/en/latest/>`_ is a Python package that |
|||
provides access to RabbitMQ and many other message queues. It can be installed |
|||
with pip:: |
|||
|
|||
pip install kombu |
|||
|
|||
To use RabbitMQ or other AMQP protocol compatible queues, that is the only |
|||
required dependency. But for other message queues, Kombu may require |
|||
additional packages. For example, to use a Redis queue via Kombu, the Python |
|||
package for Redis needs to be installed as well:: |
|||
|
|||
pip install redis |
|||
|
|||
The queue is configured through the :class:`socketio.KombuManager`:: |
|||
|
|||
mgr = socketio.KombuManager('amqp://') |
|||
sio = socketio.Server(client_manager=mgr) |
|||
|
|||
The connection URL passed to the :class:`KombuManager` constructor is passed |
|||
directly to Kombu's `Connection object |
|||
<http://kombu.readthedocs.org/en/latest/userguide/connections.html>`_, so |
|||
the Kombu documentation should be consulted for information on how to build |
|||
the correct URL for a given message queue. |
|||
|
|||
Note that Kombu currently does not support asyncio, so it cannot be used with |
|||
the :class:`socketio.AsyncServer` class. |
|||
|
|||
Emitting from external processes |
|||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
|||
|
|||
To have a process other than a server connect to the queue to emit a message, |
|||
the same client manager classes can be used as standalone objects. In this |
|||
case, the ``write_only`` argument should be set to ``True`` to disable the |
|||
creation of a listening thread, which only makes sense in a server. For |
|||
example:: |
|||
|
|||
# connect to the redis queue as an external process |
|||
external_sio = socketio.RedisManager('redis://', write_only=True) |
|||
|
|||
# emit an event |
|||
external_sio.emit('my event', data={'foo': 'bar'}, room='my room') |
@ -1,756 +1,25 @@ |
|||
.. socketio documentation master file, created by |
|||
sphinx-quickstart on Sat Jun 13 23:41:23 2015. |
|||
.. python-socketio documentation master file, created by |
|||
sphinx-quickstart on Sun Nov 25 11:52:38 2018. |
|||
You can adapt this file completely to your liking, but it should at least |
|||
contain the root `toctree` directive. |
|||
|
|||
socketio documentation |
|||
====================== |
|||
python-socketio |
|||
=============== |
|||
|
|||
This project implements a Python Socket.IO server that can run standalone or |
|||
integrated with a web application. The following are some of its |
|||
features: |
|||
This projects implements a Socket.IO server that can run standalone or |
|||
integrated with a variety of Python web frameworks. |
|||
|
|||
- Fully compatible with the |
|||
`Javascript <https://github.com/Automattic/socket.io-client>`_, |
|||
`Swift <https://github.com/socketio/socket.io-client-swift>`_, |
|||
`C++ <https://github.com/socketio/socket.io-client-cpp>`_ and |
|||
`Java <https://github.com/socketio/socket.io-client-java>`_ official |
|||
Socket.IO clients, plus any third party clients that comply with the |
|||
Socket.IO specification. |
|||
- Compatible with Python 2.7 and Python 3.3+. |
|||
- Supports large number of clients even on modest hardware when used with an |
|||
asynchronous server based on `asyncio <https://docs.python.org/3/library/asyncio.html>`_ |
|||
(`sanic <http://sanic.readthedocs.io/>`_, `aiohttp <http://aiohttp.readthedocs.io/>`_ or |
|||
`tornado <http://www.tornadoweb.org/>`_), |
|||
`eventlet <http://eventlet.net/>`_ or `gevent <http://gevent.org>`_. For |
|||
development and testing, any WSGI compliant multi-threaded server can also be |
|||
used. |
|||
- Includes a WSGI middleware that integrates Socket.IO traffic with standard |
|||
WSGI applications. |
|||
- Broadcasting of messages to all connected clients, or to subsets of them |
|||
assigned to "rooms". |
|||
- Optional support for multiple servers, connected through a messaging queue |
|||
such as Redis or RabbitMQ. |
|||
- Send messages to clients from external processes, such as Celery workers or |
|||
auxiliary scripts. |
|||
- Event-based architecture implemented with decorators that hides the details |
|||
of the protocol. |
|||
- Support for HTTP long-polling and WebSocket transports. |
|||
- Support for XHR2 and XHR browsers. |
|||
- Support for text and binary messages. |
|||
- Support for gzip and deflate HTTP compression. |
|||
- Configurable CORS responses, to avoid cross-origin problems with browsers. |
|||
.. toctree:: |
|||
:maxdepth: 2 |
|||
|
|||
What is Socket.IO? |
|||
------------------ |
|||
|
|||
Socket.IO is a transport protocol that enables real-time bidirectional |
|||
event-based communication between clients (typically web browsers) and a |
|||
server. The original implementations of the client and server components are |
|||
written in JavaScript. |
|||
|
|||
Getting Started |
|||
--------------- |
|||
|
|||
The Socket.IO server can be installed with pip:: |
|||
|
|||
pip install python-socketio |
|||
|
|||
The following is a basic example of a Socket.IO server that uses the |
|||
`aiohttp <http://aiohttp.readthedocs.io/>`_ framework for asyncio (Python 3.5+ |
|||
only): |
|||
|
|||
.. code:: python |
|||
|
|||
from aiohttp import web |
|||
import socketio |
|||
|
|||
sio = socketio.AsyncServer() |
|||
app = web.Application() |
|||
sio.attach(app) |
|||
|
|||
async def index(request): |
|||
"""Serve the client-side application.""" |
|||
with open('index.html') as f: |
|||
return web.Response(text=f.read(), content_type='text/html') |
|||
|
|||
@sio.on('connect', namespace='/chat') |
|||
def connect(sid, environ): |
|||
print("connect ", sid) |
|||
|
|||
@sio.on('chat message', namespace='/chat') |
|||
async def message(sid, data): |
|||
print("message ", data) |
|||
await sio.emit('reply', room=sid) |
|||
|
|||
@sio.on('disconnect', namespace='/chat') |
|||
def disconnect(sid): |
|||
print('disconnect ', sid) |
|||
|
|||
app.router.add_static('/static', 'static') |
|||
app.router.add_get('/', index) |
|||
|
|||
if __name__ == '__main__': |
|||
web.run_app(app) |
|||
|
|||
And below is a similar example, but using Flask and Eventlet. This example is |
|||
compatible with Python 2.7 and 3.3+:: |
|||
|
|||
import socketio |
|||
import eventlet |
|||
from flask import Flask, render_template |
|||
|
|||
sio = socketio.Server() |
|||
app = Flask(__name__) |
|||
|
|||
@app.route('/') |
|||
def index(): |
|||
"""Serve the client-side application.""" |
|||
return render_template('index.html') |
|||
|
|||
@sio.on('connect') |
|||
def connect(sid, environ): |
|||
print('connect ', sid) |
|||
|
|||
@sio.on('my message') |
|||
def message(sid, data): |
|||
print('message ', data) |
|||
|
|||
@sio.on('disconnect') |
|||
def disconnect(sid): |
|||
print('disconnect ', sid) |
|||
|
|||
if __name__ == '__main__': |
|||
# wrap Flask application with socketio's middleware |
|||
app = socketio.Middleware(sio, app) |
|||
|
|||
# deploy as an eventlet WSGI server |
|||
eventlet.wsgi.server(eventlet.listen(('', 8000)), app) |
|||
|
|||
The client-side application must include the |
|||
`socket.io-client <https://github.com/Automattic/socket.io-client>`_ library |
|||
(versions 1.3.5 or newer recommended). |
|||
|
|||
Each time a client connects to the server the ``connect`` event handler is |
|||
invoked with the ``sid`` (session ID) assigned to the connection and the WSGI |
|||
environment dictionary. The server can inspect authentication or other headers |
|||
to decide if the client is allowed to connect. To reject a client the handler |
|||
must return ``False``. |
|||
|
|||
When the client sends an event to the server, the appropriate event handler is |
|||
invoked with the ``sid`` and the message, which can be a single or multiple |
|||
arguments. The application can define as many events as needed and associate |
|||
them with event handlers. An event is defined simply by a name. |
|||
|
|||
When a connection with a client is broken, the ``disconnect`` event is called, |
|||
allowing the application to perform cleanup. |
|||
|
|||
Server |
|||
------ |
|||
|
|||
Socket.IO servers are instances of class :class:`socketio.Server`, which can be |
|||
combined with a WSGI compliant application using :class:`socketio.Middleware`:: |
|||
|
|||
# create a Socket.IO server |
|||
sio = socketio.Server() |
|||
|
|||
# wrap WSGI application with socketio's middleware |
|||
app = socketio.Middleware(sio, app) |
|||
|
|||
|
|||
For asyncio based servers, the :class:`socketio.AsyncServer` class provides a |
|||
coroutine friendly server:: |
|||
|
|||
# create a Socket.IO server |
|||
sio = socketio.AsyncServer() |
|||
|
|||
# attach server to application |
|||
sio.attach(app) |
|||
|
|||
Event handlers for servers are registered using the :func:`socketio.Server.on` |
|||
method:: |
|||
|
|||
@sio.on('my custom event') |
|||
def my_custom_event(): |
|||
pass |
|||
|
|||
For asyncio servers, event handlers can be regular functions or coroutines:: |
|||
|
|||
@sio.on('my custom event') |
|||
async def my_custom_event(): |
|||
await sio.emit('my reply') |
|||
|
|||
Rooms |
|||
----- |
|||
|
|||
Because Socket.IO is a bidirectional protocol, the server can send messages to |
|||
any connected client at any time. To make it easy to address groups of clients, |
|||
the application can put clients into rooms, and then address messages to the |
|||
entire room. |
|||
|
|||
When clients first connect, they are assigned to their own rooms, named with |
|||
the session ID (the ``sid`` argument passed to all event handlers). The |
|||
application is free to create additional rooms and manage which clients are in |
|||
them using the :func:`socketio.Server.enter_room` and |
|||
:func:`socketio.Server.leave_room` methods. Clients can be in as many rooms as |
|||
needed and can be moved between rooms as often as necessary. The individual |
|||
rooms assigned to clients when they connect are not special in any way, the |
|||
application is free to add or remove clients from them, though once it does |
|||
that it will lose the ability to address individual clients. |
|||
|
|||
:: |
|||
|
|||
@sio.on('enter room') |
|||
def enter_room(sid, data): |
|||
sio.enter_room(sid, data['room']) |
|||
|
|||
@sio.on('leave room') |
|||
def leave_room(sid, data): |
|||
sio.leave_room(sid, data['room']) |
|||
|
|||
The :func:`socketio.Server.emit` method takes an event name, a message payload |
|||
of type ``str``, ``bytes``, ``list``, ``dict`` or ``tuple``, and the recipient |
|||
room. When sending a ``tuple``, the elements in it need to be of any of the |
|||
other four allowed types. The elements of the tuple will be passed as multiple |
|||
arguments to the client-side callback function. To address an individual |
|||
client, the ``sid`` of that client should be given as room (assuming the |
|||
application did not alter these initial rooms). To address all connected |
|||
clients, the ``room`` argument should be omitted. |
|||
|
|||
:: |
|||
|
|||
@sio.on('my message') |
|||
def message(sid, data): |
|||
print('message ', data) |
|||
sio.emit('my reply', data, room='my room') |
|||
|
|||
Often when broadcasting a message to group of users in a room, it is desirable |
|||
that the sender does not receive its own message. The |
|||
:func:`socketio.Server.emit` method provides an optional ``skip_sid`` argument |
|||
to specify a client that should be skipped during the broadcast. |
|||
|
|||
:: |
|||
|
|||
@sio.on('my message') |
|||
def message(sid, data): |
|||
print('message ', data) |
|||
sio.emit('my reply', data, room='my room', skip_sid=sid) |
|||
|
|||
Responses |
|||
--------- |
|||
|
|||
When a client sends an event to the server, it can optionally provide a |
|||
callback function, to be invoked with a response provided by the server. The |
|||
server can provide a response simply by returning it from the corresponding |
|||
event handler. |
|||
|
|||
:: |
|||
|
|||
@sio.on('my event', namespace='/chat') |
|||
def my_event_handler(sid, data): |
|||
# handle the message |
|||
return "OK", 123 |
|||
|
|||
The event handler can return a single value, or a tuple with several values. |
|||
The callback function on the client side will be invoked with these returned |
|||
values as arguments. |
|||
|
|||
Callbacks |
|||
--------- |
|||
|
|||
The server can also request a response to an event sent to a client. The |
|||
:func:`socketio.Server.emit` method has an optional ``callback`` argument that |
|||
can be set to a callable. When this argument is given, the callable will be |
|||
invoked with the arguments returned by the client as a response. |
|||
|
|||
Using callback functions when broadcasting to multiple clients is not |
|||
recommended, as the callback function will be invoked once for each client |
|||
that received the message. |
|||
|
|||
Namespaces |
|||
---------- |
|||
|
|||
The Socket.IO protocol supports multiple logical connections, all multiplexed |
|||
on the same physical connection. Clients can open multiple connections by |
|||
specifying a different *namespace* on each. A namespace is given by the client |
|||
as a pathname following the hostname and port. For example, connecting to |
|||
*http://example.com:8000/chat* would open a connection to the namespace |
|||
*/chat*. |
|||
|
|||
Each namespace is handled independently from the others, with separate session |
|||
IDs (``sid``\ s), event handlers and rooms. It is important that applications |
|||
that use multiple namespaces specify the correct namespace when setting up |
|||
their event handlers and rooms, using the optional ``namespace`` argument |
|||
available in all the methods in the :class:`socketio.Server` class. |
|||
|
|||
When the ``namespace`` argument is omitted, set to ``None`` or to ``'/'``, a |
|||
default namespace is used. |
|||
|
|||
Class-Based Namespaces |
|||
---------------------- |
|||
|
|||
As an alternative to the decorator-based event handlers, the event handlers |
|||
that belong to a namespace can be created as methods of a subclass of |
|||
:class:`socketio.Namespace`:: |
|||
|
|||
class MyCustomNamespace(socketio.Namespace): |
|||
def on_connect(self, sid, environ): |
|||
pass |
|||
|
|||
def on_disconnect(self, sid): |
|||
pass |
|||
|
|||
def on_my_event(self, sid, data): |
|||
self.emit('my_response', data) |
|||
|
|||
sio.register_namespace(MyCustomNamespace('/test')) |
|||
|
|||
For asyncio based severs, namespaces must inherit from |
|||
:class:`socketio.AsyncNamespace`, and can define event handlers as regular |
|||
methods or coroutines:: |
|||
|
|||
class MyCustomNamespace(socketio.AsyncNamespace): |
|||
def on_connect(self, sid, environ): |
|||
pass |
|||
|
|||
def on_disconnect(self, sid): |
|||
pass |
|||
|
|||
async def on_my_event(self, sid, data): |
|||
await self.emit('my_response', data) |
|||
|
|||
sio.register_namespace(MyCustomNamespace('/test')) |
|||
|
|||
When class-based namespaces are used, any events received by the server are |
|||
dispatched to a method named as the event name with the ``on_`` prefix. For |
|||
example, event ``my_event`` will be handled by a method named ``on_my_event``. |
|||
If an event is received for which there is no corresponding method defined in |
|||
the namespace class, then the event is ignored. All event names used in |
|||
class-based namespaces must used characters that are legal in method names. |
|||
|
|||
As a convenience to methods defined in a class-based namespace, the namespace |
|||
instance includes versions of several of the methods in the |
|||
:class:`socketio.Server` and :class:`socketio.AsyncServer` classes that default |
|||
to the proper namespace when the ``namespace`` argument is not given. |
|||
|
|||
In the case that an event has a handler in a class-based namespace, and also a |
|||
decorator-based function handler, only the standalone function handler is |
|||
invoked. |
|||
|
|||
It is important to note that class-based namespaces are singletons. This means |
|||
that a single instance of a namespace class is used for all clients, and |
|||
consequently, a namespace instance cannot be used to store client specific |
|||
information. |
|||
|
|||
Using a Message Queue |
|||
--------------------- |
|||
|
|||
The Socket.IO server owns the socket connections to all the clients, so it is |
|||
the only process that can emit events to them. Unfortunately this becomes a |
|||
limitation for many applications that use more than one process. A common need |
|||
is to emit events to clients from a process other than the server, for example |
|||
a `Celery <http://www.celeryproject.org/>`_ worker. |
|||
|
|||
To enable these auxiliary processes to emit events, the server can be |
|||
configured to listen for externally issued events on a message queue such as |
|||
`Redis <http://redis.io/>`_ or `RabbitMQ <https://www.rabbitmq.com/>`_. |
|||
Processes that need to emit events to client then post these events to the |
|||
queue. |
|||
|
|||
Another situation in which the use of a message queue is necessary is with |
|||
high traffic applications that work with large number of clients. To support |
|||
these clients, it may be necessary to horizontally scale the Socket.IO |
|||
server by splitting the client list among multiple server processes. In this |
|||
type of installation, each server processes owns the connections to a subset |
|||
of the clients. To make broadcasting work in this environment, the servers |
|||
communicate with each other through the message queue. |
|||
|
|||
Kombu |
|||
~~~~~ |
|||
|
|||
One of the messaging options offered by this package to access the message |
|||
queue is `Kombu <http://kombu.readthedocs.org/en/latest/>`_ , which means that |
|||
any message queue supported by this package can be used. Kombu can be installed |
|||
with pip:: |
|||
|
|||
pip install kombu |
|||
|
|||
To use RabbitMQ or other AMQP protocol compatible queues, that is the only |
|||
required dependency. But for other message queues, Kombu may require |
|||
additional packages. For example, to use a Redis queue, Kombu needs the Python |
|||
package for Redis installed as well:: |
|||
|
|||
pip install redis |
|||
|
|||
The appropriate message queue service, such as RabbitMQ or Redis, must also be |
|||
installed. To configure a Socket.IO server to connect to a Kombu queue, the |
|||
``client_manager`` argument must be passed in the server creation. The |
|||
following example instructs the server to connect to a Redis service running |
|||
on the same host and on the default port:: |
|||
|
|||
mgr = socketio.KombuManager('redis://') |
|||
sio = socketio.Server(client_manager=mgr) |
|||
|
|||
For a RabbitMQ queue also running on the local server with default |
|||
credentials, the configuration is as follows:: |
|||
|
|||
mgr = socketio.KombuManager('amqp://') |
|||
sio = socketio.Server(client_manager=mgr) |
|||
|
|||
The URL passed to the :class:`KombuManager` constructor is passed directly to |
|||
Kombu's `Connection object |
|||
<http://kombu.readthedocs.org/en/latest/userguide/connections.html>`_, so |
|||
the Kombu documentation should be consulted for information on how to |
|||
connect to the message queue appropriately. |
|||
|
|||
Note that Kombu currently does not support asyncio, so it cannot be used with |
|||
the :class:`socketio.AsyncServer` class. |
|||
|
|||
Redis |
|||
~~~~~ |
|||
intro |
|||
guide |
|||
deployment |
|||
api |
|||
|
|||
To use a Redis message queue, the Python package for Redis must also be |
|||
installed:: |
|||
|
|||
# WSGI server |
|||
pip install redis |
|||
|
|||
# asyncio server |
|||
pip install aioredis |
|||
|
|||
Native Redis support is accessed through the :class:`socketio.RedisManager` and |
|||
:class:`socketio.AsyncRedisManager` classes. These classes connect directly to |
|||
the Redis store and use the queue's pub/sub functionality:: |
|||
|
|||
# WSGI server |
|||
mgr = socketio.RedisManager('redis://') |
|||
sio = socketio.Server(client_manager=mgr) |
|||
|
|||
# asyncio server |
|||
mgr = socketio.AsyncRedisManager('redis://') |
|||
sio = socketio.AsyncServer(client_manager=mgr) |
|||
|
|||
Horizontal scaling |
|||
~~~~~~~~~~~~~~~~~~ |
|||
|
|||
If multiple Socket.IO servers are connected to the same message queue, they |
|||
automatically communicate with each other and manage a combined client list, |
|||
without any need for additional configuration. When a load balancer such as |
|||
nginx is used, this provides virtually unlimited scaling capabilities for the |
|||
server. |
|||
|
|||
Emitting from external processes |
|||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
|||
|
|||
To have a process other than a server connect to the queue to emit a message, |
|||
the same client manager classes can be used as standalone objects. In this |
|||
case, the ``write_only`` argument should be set to ``True`` to disable the |
|||
creation of a listening thread, which only makes sense in a server. For |
|||
example:: |
|||
|
|||
# connect to the redis queue through Kombu |
|||
external_sio = socketio.KombuManager('redis://', write_only=True) |
|||
|
|||
# emit an event |
|||
external_sio.emit('my event', data={'foo': 'bar'}, room='my room') |
|||
|
|||
Deployment |
|||
---------- |
|||
|
|||
The following sections describe a variety of deployment strategies for |
|||
Socket.IO servers. |
|||
|
|||
Sanic |
|||
~~~~~ |
|||
|
|||
`Sanic <http://sanic.readthedocs.io/>`_ is a very efficient asynchronous web |
|||
server for Python 3.5 and newer. |
|||
|
|||
Instances of class ``socketio.AsyncServer`` will automatically use Sanic for |
|||
asynchronous operations if the framework is installed. To request its use |
|||
explicitly, the ``async_mode`` option can be given in the constructor:: |
|||
|
|||
sio = socketio.AsyncServer(async_mode='sanic') |
|||
|
|||
A server configured for aiohttp must be attached to an existing application:: |
|||
|
|||
app = web.Application() |
|||
sio.attach(app) |
|||
|
|||
The Sanic application can define regular routes that will coexist with the |
|||
Socket.IO server. A typical pattern is to add routes that serve a client |
|||
application and any associated static files. |
|||
|
|||
The Sanic application is then executed in the usual manner:: |
|||
|
|||
if __name__ == '__main__': |
|||
app.run() |
|||
|
|||
aiohttp |
|||
~~~~~~~ |
|||
|
|||
`Aiohttp <http://aiohttp.readthedocs.io/>`_ is a framework with support for HTTP |
|||
and WebSocket, based on asyncio. Support for this framework is limited to Python |
|||
3.5 and newer. |
|||
|
|||
Instances of class ``socketio.AsyncServer`` will automatically use aiohttp |
|||
for asynchronous operations if the library is installed. To request its use |
|||
explicitly, the ``async_mode`` option can be given in the constructor:: |
|||
|
|||
sio = socketio.AsyncServer(async_mode='aiohttp') |
|||
|
|||
A server configured for aiohttp must be attached to an existing application:: |
|||
|
|||
app = web.Application() |
|||
sio.attach(app) |
|||
|
|||
The aiohttp application can define regular routes that will coexist with the |
|||
Socket.IO server. A typical pattern is to add routes that serve a client |
|||
application and any associated static files. |
|||
|
|||
The aiohttp application is then executed in the usual manner:: |
|||
|
|||
if __name__ == '__main__': |
|||
web.run_app(app) |
|||
|
|||
Tornado |
|||
~~~~~~~ |
|||
|
|||
`Tornado <http://www.tornadoweb.org//>`_ is a web framework with support |
|||
for HTTP and WebSocket. Support for this framework requires Python 3.5 and |
|||
newer. Only Tornado version 5 and newer are supported, thanks to its tight |
|||
integration with asyncio. |
|||
|
|||
Instances of class ``socketio.AsyncServer`` will automatically use tornado |
|||
for asynchronous operations if the library is installed. To request its use |
|||
explicitly, the ``async_mode`` option can be given in the constructor:: |
|||
|
|||
sio = socketio.AsyncServer(async_mode='tornado') |
|||
|
|||
A server configured for tornado must include a request handler for |
|||
Engine.IO:: |
|||
|
|||
app = tornado.web.Application( |
|||
[ |
|||
(r"/socketio.io/", socketio.get_tornado_handler(sio)), |
|||
], |
|||
# ... other application options |
|||
) |
|||
|
|||
The tornado application can define other routes that will coexist with the |
|||
Socket.IO server. A typical pattern is to add routes that serve a client |
|||
application and any associated static files. |
|||
|
|||
The tornado application is then executed in the usual manner:: |
|||
|
|||
app.listen(port) |
|||
tornado.ioloop.IOLoop.current().start() |
|||
|
|||
Eventlet |
|||
~~~~~~~~ |
|||
|
|||
`Eventlet <http://eventlet.net/>`_ is a high performance concurrent networking |
|||
library for Python 2 and 3 that uses coroutines, enabling code to be written in |
|||
the same style used with the blocking standard library functions. An Socket.IO |
|||
server deployed with eventlet has access to the long-polling and WebSocket |
|||
transports. |
|||
|
|||
Instances of class ``socketio.Server`` will automatically use eventlet for |
|||
asynchronous operations if the library is installed. To request its use |
|||
explicitly, the ``async_mode`` option can be given in the constructor:: |
|||
|
|||
sio = socketio.Server(async_mode='eventlet') |
|||
|
|||
A server configured for eventlet is deployed as a regular WSGI application, |
|||
using the provided ``socketio.Middleware``:: |
|||
|
|||
app = socketio.Middleware(sio) |
|||
import eventlet |
|||
eventlet.wsgi.server(eventlet.listen(('', 8000)), app) |
|||
|
|||
An alternative to running the eventlet WSGI server as above is to use |
|||
`gunicorn <gunicorn.org>`_, a fully featured pure Python web server. The |
|||
command to launch the application under gunicorn is shown below:: |
|||
|
|||
$ gunicorn -k eventlet -w 1 module:app |
|||
|
|||
Due to limitations in its load balancing algorithm, gunicorn can only be used |
|||
with one worker process, so the ``-w`` option cannot be set to a value higher |
|||
than 1. A single eventlet worker can handle a large number of concurrent |
|||
clients, each handled by a greenlet. |
|||
|
|||
Eventlet provides a ``monkey_patch()`` function that replaces all the blocking |
|||
functions in the standard library with equivalent asynchronous versions. While |
|||
python-socketio does not require monkey patching, other libraries such as |
|||
database drivers are likely to require it. |
|||
|
|||
Gevent |
|||
~~~~~~ |
|||
|
|||
`Gevent <http://gevent.org/>`_ is another asynchronous framework based on |
|||
coroutines, very similar to eventlet. An Socket.IO server deployed with |
|||
gevent has access to the long-polling transport. If project |
|||
`gevent-websocket <https://bitbucket.org/Jeffrey/gevent-websocket/>`_ is |
|||
installed, the WebSocket transport is also available. |
|||
|
|||
Instances of class ``socketio.Server`` will automatically use gevent for |
|||
asynchronous operations if the library is installed and eventlet is not |
|||
installed. To request gevent to be selected explicitly, the ``async_mode`` |
|||
option can be given in the constructor:: |
|||
|
|||
sio = socketio.Server(async_mode='gevent') |
|||
|
|||
A server configured for gevent is deployed as a regular WSGI application, |
|||
using the provided ``socketio.Middleware``:: |
|||
|
|||
app = socketio.Middleware(sio) |
|||
from gevent import pywsgi |
|||
pywsgi.WSGIServer(('', 8000), app).serve_forever() |
|||
|
|||
If the WebSocket transport is installed, then the server must be started as |
|||
follows:: |
|||
|
|||
from gevent import pywsgi |
|||
from geventwebsocket.handler import WebSocketHandler |
|||
app = socketio.Middleware(sio) |
|||
pywsgi.WSGIServer(('', 8000), app, |
|||
handler_class=WebSocketHandler).serve_forever() |
|||
|
|||
An alternative to running the gevent WSGI server as above is to use |
|||
`gunicorn <gunicorn.org>`_, a fully featured pure Python web server. The |
|||
command to launch the application under gunicorn is shown below:: |
|||
|
|||
$ gunicorn -k gevent -w 1 module:app |
|||
|
|||
Or to include WebSocket:: |
|||
|
|||
$ gunicorn -k geventwebsocket.gunicorn.workers.GeventWebSocketWorker -w 1 module: app |
|||
|
|||
Same as with eventlet, due to limitations in its load balancing algorithm, |
|||
gunicorn can only be used with one worker process, so the ``-w`` option cannot |
|||
be higher than 1. A single gevent worker can handle a large number of |
|||
concurrent clients through the use of greenlets. |
|||
|
|||
Gevent provides a ``monkey_patch()`` function that replaces all the blocking |
|||
functions in the standard library with equivalent asynchronous versions. While |
|||
python-socketio does not require monkey patching, other libraries such as |
|||
database drivers are likely to require it. |
|||
|
|||
Gevent with uWSGI |
|||
~~~~~~~~~~~~~~~~~ |
|||
|
|||
When using the uWSGI server in combination with gevent, the Socket.IO server |
|||
can take advantage of uWSGI's native WebSocket support. |
|||
|
|||
Instances of class ``socketio.Server`` will automatically use this option for |
|||
asynchronous operations if both gevent and uWSGI are installed and eventlet is |
|||
not installed. To request this asynchronous mode explicitly, the |
|||
``async_mode`` option can be given in the constructor:: |
|||
|
|||
# gevent with uWSGI |
|||
sio = socketio.Server(async_mode='gevent_uwsgi') |
|||
|
|||
A complete explanation of the configuration and usage of the uWSGI server is |
|||
beyond the scope of this documentation. The uWSGI server is a fairly complex |
|||
package that provides a large and comprehensive set of options. It must be |
|||
compiled with WebSocket and SSL support for the WebSocket transport to be |
|||
available. As way of an introduction, the following command starts a uWSGI |
|||
server for the ``latency.py`` example on port 5000:: |
|||
|
|||
$ uwsgi --http :5000 --gevent 1000 --http-websockets --master --wsgi-file latency.py --callable app |
|||
|
|||
Standard Threading Library |
|||
~~~~~~~~~~~~~~~~~~~~~~~~~~ |
|||
|
|||
While not comparable to eventlet and gevent in terms of performance, |
|||
the Socket.IO server can also be configured to work with multi-threaded web |
|||
servers that use standard Python threads. This is an ideal setup to use with |
|||
development servers such as `Werkzeug <http://werkzeug.pocoo.org>`_. Only the |
|||
long-polling transport is currently available when using standard threads. |
|||
|
|||
Instances of class ``socketio.Server`` will automatically use the threading |
|||
mode if neither eventlet nor gevent are not installed. To request the |
|||
threading mode explicitly, the ``async_mode`` option can be given in the |
|||
constructor:: |
|||
|
|||
sio = socketio.Server(async_mode='threading') |
|||
|
|||
A server configured for threading is deployed as a regular web application, |
|||
using any WSGI complaint multi-threaded server. The example below deploys an |
|||
Socket.IO application combined with a Flask web application, using Flask's |
|||
development web server based on Werkzeug:: |
|||
|
|||
sio = socketio.Server(async_mode='threading') |
|||
app = Flask(__name__) |
|||
app.wsgi_app = socketio.Middleware(sio, app.wsgi_app) |
|||
|
|||
# ... Socket.IO and Flask handler functions ... |
|||
|
|||
if __name__ == '__main__': |
|||
app.run(threaded=True) |
|||
|
|||
When using the threading mode, it is important to ensure that the WSGI server |
|||
can handle multiple concurrent requests using threads, since a client can have |
|||
up to two outstanding requests at any given time. The Werkzeug server is |
|||
single-threaded by default, so the ``threaded=True`` option is required. |
|||
|
|||
Note that servers that use worker processes instead of threads, such as |
|||
gunicorn, do not support a Socket.IO server configured in threading mode. |
|||
|
|||
Multi-process deployments |
|||
~~~~~~~~~~~~~~~~~~~~~~~~~ |
|||
|
|||
Socket.IO is a stateful protocol, which makes horizontal scaling more |
|||
difficult. To deploy a cluster of Socket.IO processes (hosted on one or |
|||
multiple servers), the following conditions must be met: |
|||
|
|||
- Each Socket.IO process must be able to handle multiple requests, either by |
|||
using asyncio, eventlet, gevent, or standard threads. Worker processes that |
|||
only handle one request at a time are not supported. |
|||
- The load balancer must be configured to always forward requests from a |
|||
client to the same worker process. Load balancers call this *sticky |
|||
sessions*, or *session affinity*. |
|||
- The worker processes communicate with each other through a message queue, |
|||
which must be installed and configured. See the section on using message |
|||
queues above for instructions. |
|||
|
|||
API Reference |
|||
------------- |
|||
|
|||
.. module:: socketio |
|||
|
|||
.. autoclass:: Middleware |
|||
:members: |
|||
|
|||
.. autoclass:: Server |
|||
:members: |
|||
|
|||
.. autoclass:: AsyncServer |
|||
:members: |
|||
:inherited-members: |
|||
|
|||
.. autoclass:: Namespace |
|||
:members: |
|||
|
|||
.. autoclass:: AsyncNamespace |
|||
:members: |
|||
:inherited-members: |
|||
|
|||
.. autoclass:: BaseManager |
|||
:members: |
|||
|
|||
.. autoclass:: PubSubManager |
|||
:members: |
|||
|
|||
.. autoclass:: KombuManager |
|||
:members: |
|||
|
|||
.. autoclass:: RedisManager |
|||
:members: |
|||
|
|||
.. autoclass:: AsyncManager |
|||
:members: |
|||
:inherited-members: |
|||
Indices and tables |
|||
------------------ |
|||
|
|||
.. autoclass:: AsyncRedisManager |
|||
:members: |
|||
* :ref:`genindex` |
|||
* :ref:`modindex` |
|||
* :ref:`search` |
|||
|
@ -0,0 +1,148 @@ |
|||
.. socketio documentation master file, created by |
|||
sphinx-quickstart on Sat Jun 13 23:41:23 2015. |
|||
You can adapt this file completely to your liking, but it should at least |
|||
contain the root `toctree` directive. |
|||
|
|||
Getting Started |
|||
=============== |
|||
|
|||
What is Socket.IO? |
|||
------------------ |
|||
|
|||
Socket.IO is a transport protocol that enables real-time bidirectional |
|||
event-based communication between clients (typically web browsers or |
|||
smartphones) and a server. There are Socket.IO clients and servers implemented |
|||
in a variety of languages, including JavaScript, Python, C++, Swift, C# and |
|||
PHP. |
|||
|
|||
Features |
|||
-------- |
|||
|
|||
- Fully compatible with the |
|||
`Javascript <https://github.com/Automattic/socket.io-client>`_, |
|||
`Swift <https://github.com/socketio/socket.io-client-swift>`_, |
|||
`C++ <https://github.com/socketio/socket.io-client-cpp>`_ and |
|||
`Java <https://github.com/socketio/socket.io-client-java>`_ official |
|||
Socket.IO clients, plus any third party clients that comply with the |
|||
Socket.IO specification. |
|||
- Compatible with Python 2.7 and Python 3.3+. |
|||
- Supports large number of clients even on modest hardware due to being |
|||
asynchronous, even when asyncio is not used. |
|||
- Compatible with `aiohttp <http://aiohttp.readthedocs.io/>`_, |
|||
`sanic <http://sanic.readthedocs.io/>`_, |
|||
`tornado <http://www.tornadoweb.org/>`_, |
|||
`eventlet <http://eventlet.net/>`_, |
|||
`gevent <http://gevent.org>`_, |
|||
or any `WSGI <https://wsgi.readthedocs.io/en/latest/index.html>`_ or |
|||
`ASGI <https://asgi.readthedocs.io/en/latest/>`_ compatible server. |
|||
- Includes WSGI and ASGI middlewares that integrate Socket.IO traffic with |
|||
other web applications. |
|||
- Broadcasting of messages to all connected clients, or to subsets of them |
|||
assigned to "rooms". |
|||
- Optional support for multiple servers, connected through a messaging queue |
|||
such as Redis or RabbitMQ. |
|||
- Send messages to clients from external processes, such as Celery workers or |
|||
auxiliary scripts. |
|||
- Event-based architecture implemented with decorators that hides the details |
|||
of the protocol. |
|||
- Support for HTTP long-polling and WebSocket transports. |
|||
- Support for XHR2 and XHR browsers. |
|||
- Support for text and binary messages. |
|||
- Support for gzip and deflate HTTP compression. |
|||
- Configurable CORS responses, to avoid cross-origin problems with browsers. |
|||
|
|||
Examples |
|||
-------- |
|||
|
|||
The Socket.IO server can be installed with pip:: |
|||
|
|||
pip install python-socketio |
|||
|
|||
The following is a basic example of a Socket.IO server that uses the |
|||
`aiohttp <http://aiohttp.readthedocs.io/>`_ framework for asyncio (Python 3.5+ |
|||
only): |
|||
|
|||
.. code:: python |
|||
|
|||
from aiohttp import web |
|||
import socketio |
|||
|
|||
sio = socketio.AsyncServer() |
|||
app = web.Application() |
|||
sio.attach(app) |
|||
|
|||
async def index(request): |
|||
"""Serve the client-side application.""" |
|||
with open('index.html') as f: |
|||
return web.Response(text=f.read(), content_type='text/html') |
|||
|
|||
@sio.on('connect', namespace='/chat') |
|||
def connect(sid, environ): |
|||
print("connect ", sid) |
|||
|
|||
@sio.on('chat message', namespace='/chat') |
|||
async def message(sid, data): |
|||
print("message ", data) |
|||
await sio.emit('reply', room=sid) |
|||
|
|||
@sio.on('disconnect', namespace='/chat') |
|||
def disconnect(sid): |
|||
print('disconnect ', sid) |
|||
|
|||
app.router.add_static('/static', 'static') |
|||
app.router.add_get('/', index) |
|||
|
|||
if __name__ == '__main__': |
|||
web.run_app(app) |
|||
|
|||
And below is a similar example, but using Flask and Eventlet. This example is |
|||
compatible with Python 2.7 and 3.3+:: |
|||
|
|||
import socketio |
|||
import eventlet |
|||
from flask import Flask, render_template |
|||
|
|||
sio = socketio.Server() |
|||
app = Flask(__name__) |
|||
|
|||
@app.route('/') |
|||
def index(): |
|||
"""Serve the client-side application.""" |
|||
return render_template('index.html') |
|||
|
|||
@sio.on('connect') |
|||
def connect(sid, environ): |
|||
print('connect ', sid) |
|||
|
|||
@sio.on('my message') |
|||
def message(sid, data): |
|||
print('message ', data) |
|||
|
|||
@sio.on('disconnect') |
|||
def disconnect(sid): |
|||
print('disconnect ', sid) |
|||
|
|||
if __name__ == '__main__': |
|||
# wrap Flask application with socketio's middleware |
|||
app = socketio.WSGIApp(sio, app) |
|||
|
|||
# deploy as an eventlet WSGI server |
|||
eventlet.wsgi.server(eventlet.listen(('', 8000)), app) |
|||
|
|||
The client-side application must include the |
|||
`socket.io-client <https://github.com/Automattic/socket.io-client>`_ library |
|||
(versions 1.3.5 or newer recommended). |
|||
|
|||
Each time a client connects to the server the ``connect`` event handler is |
|||
invoked with the ``sid`` (session ID) assigned to the connection and the WSGI |
|||
environment dictionary. The server can inspect authentication or other headers |
|||
to decide if the client is allowed to connect. To reject a client the handler |
|||
must return ``False``. |
|||
|
|||
When the client sends an event to the server, the appropriate event handler is |
|||
invoked with the ``sid`` and the message, which can be a single or multiple |
|||
arguments. The application can define as many events as needed and associate |
|||
them with event handlers. An event is defined simply by a name. |
|||
|
|||
When a connection with a client is broken, the ``disconnect`` event is called, |
|||
allowing the application to perform cleanup. |
@ -1,263 +1,35 @@ |
|||
@ECHO OFF |
|||
|
|||
REM Command file for Sphinx documentation |
|||
|
|||
if "%SPHINXBUILD%" == "" ( |
|||
set SPHINXBUILD=sphinx-build |
|||
) |
|||
set BUILDDIR=_build |
|||
set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . |
|||
set I18NSPHINXOPTS=%SPHINXOPTS% . |
|||
if NOT "%PAPER%" == "" ( |
|||
set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% |
|||
set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% |
|||
) |
|||
|
|||
if "%1" == "" goto help |
|||
|
|||
if "%1" == "help" ( |
|||
:help |
|||
echo.Please use `make ^<target^>` where ^<target^> is one of |
|||
echo. html to make standalone HTML files |
|||
echo. dirhtml to make HTML files named index.html in directories |
|||
echo. singlehtml to make a single large HTML file |
|||
echo. pickle to make pickle files |
|||
echo. json to make JSON files |
|||
echo. htmlhelp to make HTML files and a HTML help project |
|||
echo. qthelp to make HTML files and a qthelp project |
|||
echo. devhelp to make HTML files and a Devhelp project |
|||
echo. epub to make an epub |
|||
echo. 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 |
|||
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% 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 |
|||
echo.installed, then set the SPHINXBUILD environment variable to point |
|||
echo.to the full path of the 'sphinx-build' executable. Alternatively you |
|||
echo.may add the Sphinx directory to PATH. |
|||
echo. |
|||
echo.If you don't have Sphinx installed, grab it from |
|||
echo.http://sphinx-doc.org/ |
|||
exit /b 1 |
|||
) |
|||
|
|||
:sphinx_ok |
|||
|
|||
|
|||
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\socketio.qhcp |
|||
echo.To view the help file: |
|||
echo.^> assistant -collectionFile %BUILDDIR%\qthelp\socketio.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" == "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 |
|||
) |
|||
|
|||
:end |
|||
@ECHO OFF |
|||
|
|||
pushd %~dp0 |
|||
|
|||
REM Command file for Sphinx documentation |
|||
|
|||
if "%SPHINXBUILD%" == "" ( |
|||
set SPHINXBUILD=sphinx-build |
|||
) |
|||
set SOURCEDIR=. |
|||
set BUILDDIR=_build |
|||
|
|||
if "%1" == "" goto help |
|||
|
|||
%SPHINXBUILD% >NUL 2>NUL |
|||
if errorlevel 9009 ( |
|||
echo. |
|||
echo.The 'sphinx-build' command was not found. Make sure you have Sphinx |
|||
echo.installed, then set the SPHINXBUILD environment variable to point |
|||
echo.to the full path of the 'sphinx-build' executable. Alternatively you |
|||
echo.may add the Sphinx directory to PATH. |
|||
echo. |
|||
echo.If you don't have Sphinx installed, grab it from |
|||
echo.http://sphinx-doc.org/ |
|||
exit /b 1 |
|||
) |
|||
|
|||
%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% |
|||
goto end |
|||
|
|||
:help |
|||
%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% |
|||
|
|||
:end |
|||
popd |
|||
|
Loading…
Reference in new issue