Commits

Anonymous committed 0acaeb3

initial upload

  • Participants

Comments (0)

Files changed (18)

+syntax: glob
+*~
+*.pyc
+*.pyo
+docs/_build/*
+*egg-info*
+dist/*
+Copyright (c) 2010 Matthew "LeafStorm" Frazier
+
+Permission is hereby granted, free of charge, to any person
+obtaining a copy of this software and associated documentation
+files (the "Software"), to deal in the Software without
+restriction, including without limitation the rights to use,
+copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following
+conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
+Flask-Uploads
+
+Description goes here

File docs/Makefile

+# Makefile for Sphinx documentation
+#
+
+# You can set these variables from the command line.
+SPHINXOPTS    =
+SPHINXBUILD   = sphinx-build
+PAPER         =
+BUILDDIR      = _build
+
+# Internal variables.
+PAPEROPT_a4     = -D latex_paper_size=a4
+PAPEROPT_letter = -D latex_paper_size=letter
+ALLSPHINXOPTS   = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
+
+.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest
+
+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 "  latexpdf   to make LaTeX files and run them through pdflatex"
+	@echo "  text       to make text files"
+	@echo "  man        to make manual pages"
+	@echo "  changes    to make an overview of all changed/added/deprecated items"
+	@echo "  linkcheck  to check all external links for integrity"
+	@echo "  doctest    to run all doctests embedded in 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/Flask-Uploads.qhcp"
+	@echo "To view the help file:"
+	@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Flask-Uploads.qhc"
+
+devhelp:
+	$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
+	@echo
+	@echo "Build finished."
+	@echo "To view the help file:"
+	@echo "# mkdir -p $$HOME/.local/share/devhelp/Flask-Uploads"
+	@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Flask-Uploads"
+	@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."
+
+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."
+
+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."

File docs/_themes/README

+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

File docs/_themes/flask/static/flasky.css_t

+/*
+ * 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;
+    background-color: #ddd;
+    color: #000;
+    margin: 0;
+    padding: 0;
+}
+
+div.document {
+    background: #fafafa;
+}
+
+div.documentwrapper {
+    float: left;
+    width: 100%;
+}
+
+div.bodywrapper {
+    margin: 0 0 0 230px;
+}
+
+hr {
+    border: 1px solid #B1B4B6;
+}
+ 
+div.body {
+    background-color: #ffffff;
+    color: #3E4349;
+    padding: 0 30px 30px 30px;
+    min-height: 34em;
+}
+
+img.floatingflask {
+    padding: 0 0 10px 10px;
+    float: right;
+}
+ 
+div.footer {
+    position: absolute;
+    right: 0;
+    margin-top: -70px;
+    text-align: right;
+    color: #888;
+    padding: 10px;
+    font-size: 14px;
+}
+ 
+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;
+}
+ 
+div.sphinxsidebar {
+    font-size: 14px;
+    line-height: 1.5;
+}
+
+div.sphinxsidebarwrapper {
+    padding: 0 20px;
+}
+
+div.sphinxsidebarwrapper p.logo {
+    padding: 20px 0 10px 0;
+    margin: 0;
+    text-align: center;
+}
+ 
+div.sphinxsidebar h3,
+div.sphinxsidebar h4 {
+    font-family: 'Garamond', 'Georgia', serif;
+    color: #222;
+    font-size: 24px;
+    font-weight: normal;
+    margin: 20px 0 5px 0;
+    padding: 0;
+}
+
+div.sphinxsidebar h4 {
+    font-size: 20px;
+}
+ 
+div.sphinxsidebar h3 a {
+    color: #444;
+}
+ 
+div.sphinxsidebar p {
+    color: #555;
+    margin: 10px 0;
+}
+ 
+div.sphinxsidebar ul {
+    margin: 10px 0;
+    padding: 0;
+    color: #000;
+}
+ 
+div.sphinxsidebar a {
+    color: #444;
+    text-decoration: none;
+}
+
+div.sphinxsidebar a:hover {
+    text-decoration: underline;
+}
+ 
+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 {
+    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;
+}
+ 
+div.body h1 { margin-top: 0; padding-top: 20px; 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: 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.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;
+}
+
+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 {
+    background: #eee;
+    padding: 7px 30px;
+    margin: 15px -30px;
+    line-height: 1.3em;
+}
+
+dl 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;
+}
+
+a:hover tt {
+    background: #EEE;
+}

File docs/_themes/flask/theme.conf

+[theme]
+inherit = basic
+stylesheet = flasky.css
+pygments_style = flask_theme_support.FlaskyStyle

File docs/_themes/flask_small/layout.html

+{% 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 %}

File docs/_themes/flask_small/static/flasky.css_t

+/*
+ * 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;
+}
+
+tt {
+    background-color: #ecf0f3;
+    color: #222;
+    /* padding: 1px 2px; */
+}
+
+tt.xref, a tt {
+    background-color: #FBFBFB;
+}
+
+a:hover tt {
+    background: #EEE;
+}

File docs/_themes/flask_small/theme.conf

+[theme]
+inherit = basic
+stylesheet = flasky.css
+nosidebar = true
+pygments_style = flask_theme_support.FlaskyStyle
+
+[options]
+index_logo = ''
+index_logo_height = 120px
+github_fork = ''

File docs/_themes/flask_theme_support.py

+# 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'
+    }

File docs/conf.py

+# -*- coding: utf-8 -*-
+#
+# Flask-Uploads documentation build configuration file, created by
+# sphinx-quickstart on Thu Jul  8 14:12:15 2010.
+#
+# This file is execfile()d with the current directory set to its containing dir.
+#
+# Note that not all possible configuration values are present in this
+# autogenerated file.
+#
+# All configuration values have a default; values that are commented out
+# serve to show the default.
+
+import sys, os
+
+# If extensions (or modules to document with autodoc) are in another directory,
+# add these directories to sys.path here. If the directory is relative to the
+# documentation root, use os.path.abspath to make it absolute, like shown here.
+sys.path.append(os.path.abspath('_themes'))
+
+# -- General configuration -----------------------------------------------------
+
+# If your documentation needs a minimal Sphinx version, state it here.
+#needs_sphinx = '1.0'
+
+# Add any Sphinx extension module names here, as strings. They can be extensions
+# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
+extensions = ['sphinx.ext.autodoc', 'sphinx.ext.intersphinx']
+
+# Add any paths that contain templates here, relative to this directory.
+templates_path = ['_templates']
+
+# The suffix of source filenames.
+source_suffix = '.rst'
+
+# The encoding of source files.
+#source_encoding = 'utf-8-sig'
+
+# The master toctree document.
+master_doc = 'index'
+
+# General information about the project.
+project = u'Flask-Uploads'
+copyright = u'2010, Matthew "LeafStorm" Frazier'
+
+# The version info for the project you're documenting, acts as replacement for
+# |version| and |release|, also used in various other places throughout the
+# built documents.
+#
+# The short X.Y version.
+version = '0.1'
+# The full version, including alpha/beta/rc tags.
+release = '0.1'
+
+# The language for content autogenerated by Sphinx. Refer to documentation
+# for a list of supported languages.
+#language = None
+
+# There are two options for replacing |today|: either, you set today to some
+# non-false value, then it is used:
+#today = ''
+# Else, today_fmt is used as the format for a strftime call.
+#today_fmt = '%B %d, %Y'
+
+# List of patterns, relative to source directory, that match files and
+# directories to ignore when looking for source files.
+exclude_patterns = ['_build']
+
+# The reST default role (used for this markup: `text`) to use for all documents.
+default_role = 'obj'
+
+# If true, '()' will be appended to :func: etc. cross-reference text.
+#add_function_parentheses = True
+
+# If true, the current module name will be prepended to all description
+# unit titles (such as .. function::).
+#add_module_names = True
+
+# If true, sectionauthor and moduleauthor directives will be shown in the
+# output. They are ignored by default.
+#show_authors = False
+
+# The name of the Pygments (syntax highlighting) style to use.
+#pygments_style = 'sphinx'
+
+# A list of ignored prefixes for module index sorting.
+#modindex_common_prefix = []
+
+
+# -- Options for HTML output ---------------------------------------------------
+
+# The theme to use for HTML and HTML Help pages.  See the documentation for
+# a list of builtin themes.
+html_theme = 'flask_small'
+
+# Theme options are theme-specific and customize the look and feel of a theme
+# further.  For a list of options available for each theme, see the
+# documentation.
+html_theme_options = {'github_fork': None, 'index_logo': None}
+
+# Add any paths that contain custom themes here, relative to this directory.
+html_theme_path = ['_themes']
+
+# The name for this set of Sphinx documents.  If None, it defaults to
+# "<project> v<release> documentation".
+#html_title = None
+
+# A shorter title for the navigation bar.  Default is the same as html_title.
+#html_short_title = None
+
+# The name of an image file (relative to this directory) to place at the top
+# of the sidebar.
+#html_logo = None
+
+# The name of an image file (within the static path) to use as favicon of the
+# docs.  This file should be a Windows icon file (.ico) being 16x16 or 32x32
+# pixels large.
+#html_favicon = None
+
+# Add any paths that contain custom static files (such as style sheets) here,
+# relative to this directory. They are copied after the builtin static files,
+# so a file named "default.css" will overwrite the builtin "default.css".
+html_static_path = ['_static']
+
+# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
+# using the given strftime format.
+#html_last_updated_fmt = '%b %d, %Y'
+
+# If true, SmartyPants will be used to convert quotes and dashes to
+# typographically correct entities.
+#html_use_smartypants = True
+
+# Custom sidebar templates, maps document names to template names.
+#html_sidebars = {}
+
+# Additional templates that should be rendered to pages, maps page names to
+# template names.
+#html_additional_pages = {}
+
+# If false, no module index is generated.
+#html_domain_indices = True
+
+# If false, no index is generated.
+#html_use_index = True
+
+# If true, the index is split into individual pages for each letter.
+#html_split_index = False
+
+# If true, links to the reST sources are added to the pages.
+#html_show_sourcelink = True
+
+# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
+#html_show_sphinx = True
+
+# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
+#html_show_copyright = True
+
+# If true, an OpenSearch description file will be output, and all pages will
+# contain a <link> tag referring to it.  The value of this option must be the
+# base URL from which the finished HTML is served.
+#html_use_opensearch = ''
+
+# If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml").
+#html_file_suffix = ''
+
+# Output file base name for HTML help builder.
+htmlhelp_basename = 'Flask-Uploadsdoc'
+
+
+# -- Options for LaTeX output --------------------------------------------------
+
+# The paper size ('letter' or 'a4').
+#latex_paper_size = 'letter'
+
+# The font size ('10pt', '11pt' or '12pt').
+#latex_font_size = '10pt'
+
+# Grouping the document tree into LaTeX files. List of tuples
+# (source start file, target name, title, author, documentclass [howto/manual]).
+latex_documents = [
+  ('index', 'Flask-Uploads.tex', u'Flask-Uploads Documentation',
+   u'Matthew "LeafStorm" Frazier', 'manual'),
+]
+
+# The name of an image file (relative to this directory) to place at the top of
+# the title page.
+#latex_logo = None
+
+# For "manual" documents, if this is true, then toplevel headings are parts,
+# not chapters.
+#latex_use_parts = False
+
+# If true, show page references after internal links.
+#latex_show_pagerefs = False
+
+# If true, show URL addresses after external links.
+#latex_show_urls = False
+
+# Additional stuff for the LaTeX preamble.
+#latex_preamble = ''
+
+# Documents to append as an appendix to all manuals.
+#latex_appendices = []
+
+# If false, no module index is generated.
+#latex_domain_indices = True
+
+
+# -- Options for manual page output --------------------------------------------
+
+# One entry per manual page. List of tuples
+# (source start file, name, description, authors, manual section).
+man_pages = [
+    ('index', 'flask-uploads', u'Flask-Uploads Documentation',
+     [u'Matthew "LeafStorm" Frazier'], 1)
+]
+
+
+# Example configuration for intersphinx: refer to the Python standard library.
+intersphinx_mapping = {'http://docs.python.org/': None,
+                       'http://flask.pocoo.org/docs/': None}

File docs/index.rst

+=============
+Flask-Uploads
+=============
+.. currentmodule:: flaskext.uploads
+
+Flask-Uploads allows your application to flexibly and efficiently handle file
+uploading and serving the uploaded files.
+You can create different sets of uploads - one for document attachments, one
+for photos, etc. - and the application can be configured to save them all in
+different places and to generate different URLs for them.
+
+
+Configuration
+=============
+If you're just deploying an application that uses Flask-Uploads, you can
+customize its behavior extensively from the application's configuration.
+Check the application's documentation or source code to see how it loads its
+configuration.
+
+The settings below apply for a single set of uploads, replacing `FILES` with
+the name of the set (i.e. `PHOTOS`, `ATTACHMENTS`):
+
+`UPLOADED_FILES_DEST`
+    This indicates the directory uploaded files will be saved to.
+
+`UPLOADED_FILES_URL`
+    If you have a server set up to serve the files in this set, this should be
+    the URL they are publicly accessible from. Include the trailing slash.
+
+`UPLOADED_FILES_ALLOW`
+    This lets you allow file extensions not allowed by the upload set in the
+    code.
+
+`UPLOADED_FILES_DENY`
+    This lets you deny file extensions allowed by the upload set in the code.
+
+However, to save on configuration time, there are two settings you can provide
+that apply as "defaults" if you don't provide the proper settings otherwise.
+
+`UPLOADS_DEFAULT_DEST`
+    If you set this, then if an upload set's destination isn't otherwise
+    declared, then its uploads will be stored in a subdirectory of this
+    directory. For example, if you set this to ``/var/uploads``, then a set
+    named photos will store its uploads in ``/var/uploads/photos``.
+
+`UPLOADS_DEFAULT_URL`
+    If you have a server set up to serve from `UPLOADS_DEFAULT_DEST`, then
+    set the server's base URL here. Continuing the example above, if
+    ``/var/uploads`` is accessible from ``http://localhost:5001``, then you
+    would set this to ``http://localhost:5001/`` and URLs for the photos set
+    would start with ``http://localhost:5001/photos``. Include the trailing
+    slash.
+
+However, you don't have to set any of the ``_URL`` settings - if you don't,
+then they will be served internally by Flask. They are just there so if you
+have heavy upload traffic, you can have a faster production server like Nginx
+or Lighttpd serve the uploads.
+
+
+Upload Sets
+===========
+An "upload set" is a single collection of files. You just declare them in the
+code::
+
+    photos = UploadSet('photos', IMAGES)
+
+And then you can use the `~UploadSet.save` method to save uploaded files and
+`~UploadSet.path` and `~UploadSet.url` to access them. For example::
+
+    @app.route('/upload', methods=['GET', 'POST'])
+    def upload():
+        if request.method == 'POST' and 'photo' in request.files:
+            filename = photos.save(request.files['photo'])
+            rec = Photo(filename=filename, user=g.user.id)
+            rec.store()
+            flash("Photo saved.")
+            return redirect(url_for('show', id=rec.id))
+        return render_template('upload.html')
+    
+    @app.route('/photo/<id>')
+    def show(id):
+        photo = Photo.load(id)
+        if photo is None:
+            abort(404)
+        url = photos.url(photo.filename)
+        return render_template('show.html', url=url, photo=photo)
+
+If you have a "default location" for storing uploads - for example, if your
+app has an "instance" directory like `Zine`_ and uploads should be saved to
+the instance directory's ``uploads`` folder - you can pass a ``default_dest``
+callable to the set constructor. It takes the application as its argument.
+For example::
+
+    media = UploadSet('media', default_dest=lambda app: app.instance_root)
+
+This won't prevent a different destination from being set in the config,
+though. It's just to save your users a little configuration time.
+
+.. _Zine: http://zine.pocoo.org/
+
+
+App Configuration
+=================
+An upload set's configuration is stored on an app. That way, you can have
+upload sets being used by multiple apps at once. You use the
+`configure_uploads` function to load the configuration for the upload sets.
+You pass in the app and all of the upload sets you want configured. Calling
+`configure_uploads` more than once is safe. ::
+
+    configure_uploads(app, (photos, media))
+
+If your app has a factory function, that is a good place to place this call.
+
+In addition, you can also use `patch_request_class` to patch your app's
+`~flask.Flask.request_class` to have a maximum size for uploads. By default,
+there is no limit, so it's possible for script kiddies to crash your server
+by uploading gigantic files. Calling it will install a limit that prevents
+it from loading more than a certain amount of data. ::
+
+    patch_request_class(app)        # 16 megabytes
+    patch_request_class(app, 32 * 1024 * 1024)
+                                    # 32 megabytes
+
+If you need to upload huge files, you may want to look into another solution
+like rsync.
+
+
+API
+===
+Here are the API docs. These are generated directly from the source code.
+
+
+Upload Sets
+-----------
+.. autoclass:: UploadSet
+   :members:
+
+.. autoclass:: UploadConfiguration
+
+
+Application Setup
+-----------------
+.. autofunction:: configure_uploads
+
+.. autofunction:: patch_request_class
+
+
+Extension Constants
+-------------------
+These are some default sets of extensions you can pass to the `UploadSet`
+constructor.
+
+.. autoclass:: AllExcept
+
+.. autodata:: DEFAULTS
+
+.. autodata:: ALL
+
+.. autodata:: TEXT
+
+.. autodata:: IMAGES
+
+.. autodata:: AUDIO
+
+.. autodata:: DOCUMENTS
+
+.. autodata:: DATA
+
+.. autodata:: SCRIPTS
+
+.. autodata:: ARCHIVES
+
+.. autodata:: EXECUTABLES
+
+
+Testing Utilities
+-----------------
+.. autoclass:: TestingFileStorage

File docs/make.bat

+@ECHO OFF
+
+REM Command file for Sphinx documentation
+
+if "%SPHINXBUILD%" == "" (
+	set SPHINXBUILD=sphinx-build
+)
+set BUILDDIR=_build
+set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% .
+if NOT "%PAPER%" == "" (
+	set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS%
+)
+
+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.  changes    to make an overview over all changed/added/deprecated items
+	echo.  linkcheck  to check all external links for integrity
+	echo.  doctest    to run all doctests embedded in 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
+)
+
+if "%1" == "html" (
+	%SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html
+	echo.
+	echo.Build finished. The HTML pages are in %BUILDDIR%/html.
+	goto end
+)
+
+if "%1" == "dirhtml" (
+	%SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml
+	echo.
+	echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml.
+	goto end
+)
+
+if "%1" == "singlehtml" (
+	%SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml
+	echo.
+	echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml.
+	goto end
+)
+
+if "%1" == "pickle" (
+	%SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle
+	echo.
+	echo.Build finished; now you can process the pickle files.
+	goto end
+)
+
+if "%1" == "json" (
+	%SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json
+	echo.
+	echo.Build finished; now you can process the JSON files.
+	goto end
+)
+
+if "%1" == "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.
+	goto end
+)
+
+if "%1" == "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\Flask-Uploads.qhcp
+	echo.To view the help file:
+	echo.^> assistant -collectionFile %BUILDDIR%\qthelp\Flask-Uploads.ghc
+	goto end
+)
+
+if "%1" == "devhelp" (
+	%SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp
+	echo.
+	echo.Build finished.
+	goto end
+)
+
+if "%1" == "epub" (
+	%SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub
+	echo.
+	echo.Build finished. The epub file is in %BUILDDIR%/epub.
+	goto end
+)
+
+if "%1" == "latex" (
+	%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
+	echo.
+	echo.Build finished; the LaTeX files are in %BUILDDIR%/latex.
+	goto end
+)
+
+if "%1" == "text" (
+	%SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text
+	echo.
+	echo.Build finished. The text files are in %BUILDDIR%/text.
+	goto end
+)
+
+if "%1" == "man" (
+	%SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man
+	echo.
+	echo.Build finished. The manual pages are in %BUILDDIR%/man.
+	goto end
+)
+
+if "%1" == "changes" (
+	%SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes
+	echo.
+	echo.The overview file is in %BUILDDIR%/changes.
+	goto end
+)
+
+if "%1" == "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.
+	goto end
+)
+
+if "%1" == "doctest" (
+	%SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest
+	echo.
+	echo.Testing of doctests in the sources finished, look at the ^
+results in %BUILDDIR%/doctest/output.txt.
+	goto end
+)
+
+:end

File flaskext/__init__.py

+__import__('pkg_resources').declare_namespace(__name__)

File flaskext/uploads.py

+# -*- coding: utf-8 -*-
+"""
+flaskext.uploads
+================
+This module provides upload support for Flask. The basic pattern is to set up
+an `UploadSet` object and upload your files to it.
+
+:copyright: 2010 Matthew "LeafStorm" Frazier
+:license:   MIT/X11, see LICENSE for details
+"""
+import os.path
+import posixpath
+from flask import current_app, Module, send_from_directory, abort, url_for
+from itertools import chain
+from werkzeug import secure_filename, FileStorage
+
+# Extension presets
+
+#: This just contains plain text files (.txt).
+TEXT = ('txt',)
+
+#: This contains various office document formats (.rtf, .odf, .ods, .gnumeric,
+#: .abw, .doc, .docx, .xls, and .xlsx). Note that the macro-enabled versions
+#: of Microsoft Office 2007 files are not included.
+DOCUMENTS = tuple('rtf odf ods gnumeric abw doc docx xls xlsx'.split())
+
+#: This contains basic image types that are viewable from most browsers (.jpg,
+#: .jpe, .jpeg, .png, .gif, .svg, and .bmp).
+IMAGES = tuple('jpg jpe jpeg png gif svg bmp'.split())
+
+#: This contains audio file types (.wav, .mp3, .aac, .ogg, .oga, and .flac).
+AUDIO = tuple('wav mp3 aac ogg oga flac'.split())
+
+#: This is for structured data files (.csv, .ini, .json, .plist, .xml, .yaml,
+#: and .yml).
+DATA = tuple('csv ini json plist xml yaml yml'.split())
+
+#: This contains various types of scripts (.js, .php, .pl, .py .rb, and .sh).
+#: If your Web server has PHP installed and set to auto-run, you might want to
+#: add ``php`` to the DENY setting.
+SCRIPTS = tuple('js php pl py rb sh'.split())
+
+#: This contains archive and compression formats (.gz, .bz2, .zip, .tar,
+#: .tgz, .txz, and .7z).
+ARCHIVES = tuple('gz bz2 zip tar tgz txz 7z'.split())
+
+#: This contains shared libraries and executable files (.so, .exe and .dll).
+#: Most of the time, you will not want to allow this - it's better suited for
+#: use with `AllExcept`.
+EXECUTABLES = tuple('so exe dll'.split())
+
+#: The default allowed extensions - `TEXT`, `DOCUMENTS`, `DATA`, and `IMAGES`.
+DEFAULTS = TEXT + DOCUMENTS + IMAGES + DATA
+
+
+class UploadNotAllowed(Exception):
+    """
+    This exception is raised if the upload was not allowed. You should catch
+    it in your view code and display an appropriate message to the user.
+    """
+
+
+def tuple_from(*iters):
+    return tuple(itertools.chain(*iters))
+
+
+def extension(filename):
+    return filename.rsplit('.', 1)[-1]
+
+
+def addslash(url):
+    if url.endswith('/'):
+        return url
+    return url + '/'
+
+
+def patch_request_class(app, size=16 * 1024 * 1024):
+    """
+    By default, Flask will accept uploads to an arbitrary size. Unfortunately,
+    this could lead to a security hole: someone uploads a gigantic file, and
+    crashes your server when it runs out of memory. Calling this on an
+    application will patch the app's request class so that when it hits a
+    certain size, it will automatically raise an HTTP error.
+    
+    :param app: The app to patch the request class of.
+    :param size: The maximum size to accept, in bytes. The default is 16 MiB.
+    """
+    reqclass = app.request_class
+    patched = type(reqclass.__name__, (reqclass,),
+                   {'max_form_memory_size': size})
+    app.request_class = patched
+
+
+def config_for_set(uset, app, defaults=None):
+    """
+    This is a helper function for `configure_uploads` that extracts the
+    configuration for a single set.
+    
+    :param uset: The upload set.
+    :param app: The app to load the configuration from.
+    :param defaults: A dict with keys `url` and `dest` from the
+                     `UPLOADS_DEFAULT_DEST` and `DEFAULT_UPLOADS_URL`
+                     settings.
+    """
+    config = app.config
+    prefix = 'UPLOADED_%s_' % uset.name.upper()
+    using_defaults = False
+    if defaults is None:
+        defaults = dict(dest=None, url=None)
+    
+    allow_extns = tuple(config.get(prefix + 'ALLOW', ()))
+    deny_extns = tuple(config.get(prefix + 'DENY', ()))
+    destination = config.get(prefix + 'DEST')
+    base_url = config.get(prefix + 'URL')
+    
+    if destination is None:
+        # the upload set's destination wasn't given
+        if uset.default_dest:
+            # use the "default_dest" callable
+            destination = uset.default_dest(app)
+        if destination is None: # still
+            # use the default dest from the config
+            if defaults['dest'] is not None:
+                using_defaults = True
+                destination = os.path.join(defaults['dest'], uset.name)
+            else:
+                raise RuntimeError("no destination for set %s" % uset.name)
+    
+    if base_url is None and using_defaults and defaults['url']:
+        base_url = addslash(defaults['url']) + uset.name + '/'
+    
+    return UploadConfiguration(destination, base_url, allow_extns, deny_extns)
+
+
+def configure_uploads(app, upload_sets):
+    """
+    Call this after the app has been configured. It will go through all the
+    upload sets, get their configuration, and store the configuration on the
+    app. It will also register the uploads module if it hasn't been set.
+    
+    :param app: The `~flask.Flask` instance to get the configuration from.
+    :param upload_sets: The `UploadSet` instances to configure.
+    """
+    if isinstance(upload_sets, UploadSet):
+        upload_sets = (upload_sets,)
+    if '_uploads' not in app.modules:
+        app.register_module(uploads_mod)
+    
+    if not hasattr(app, 'upload_set_config'):
+        app.upload_set_config = {}
+    set_config = app.upload_set_config
+    defaults = dict(dest=app.config.get('UPLOADS_DEFAULT_DEST'),
+                    url=app.config.get('UPLOADS_DEFAULT_URL'))
+    
+    for uset in upload_sets:
+        config = config_for_set(uset, app, defaults)
+        set_config[uset.name] = config
+
+
+class All(object):
+    """
+    This type can be used to allow all extensions. There is a predefined
+    instance named `ALL`.
+    """
+    def __contains__(self, item):
+        return True
+
+
+#: This "contains" all items. You can use it to allow all extensions to be
+#: uploaded.
+ALL = All()
+
+
+class AllExcept(object):
+    """
+    This can be used to allow all file types except certain ones. For example,
+    to ban .exe and .iso files, pass::
+    
+        AllExcept(('exe', 'iso'))
+    
+    to the `UploadSet` constructor as `extensions`. You can use any container,
+    for example::
+    
+        AllExcept(SCRIPTS + EXECUTABLES)
+    """
+    def __init__(self, items):
+        self.items = items
+    
+    def __contains__(self, item):
+        return item not in self.items
+
+
+class UploadConfiguration(object):
+    """
+    This holds the configuration for a single `UploadSet`. The constructor's
+    arguments are also the attributes.
+    
+    :param destination: The directory to save files to.
+    :param base_url: The URL (ending with a /) that files can be downloaded
+                     from. If this is `None`, Flask-Uploads will serve the
+                     files itself.
+    :param allow: A list of extensions to allow, even if they're not in the
+                  `UploadSet` extensions list.
+    :param deny: A list of extensions to deny, even if they are in the
+                 `UploadSet` extensions list.
+    """
+    def __init__(self, destination, base_url=None, allow=(), deny=()):
+        self.destination = destination
+        self.base_url = base_url
+        self.allow = allow
+        self.deny = deny
+    
+    @property
+    def tuple(self):
+        return (self.destination, self.base_url, self.allow, self.deny)
+    
+    def __eq__(self, other):
+        return self.tuple == other.tuple
+
+
+class UploadSet(object):
+    """
+    This represents a single set of uploaded files. Each upload set is
+    independent of the others. This can be reused across multiple application
+    instances, as all configuration is stored on the application object itself
+    and found with `flask.current_app`.
+    
+    :param name: The name of this upload set. It defaults to ``files``, but
+                 you can pick any alphanumeric name you want. (For simplicity,
+                 it's best to use a plural noun.)
+    :param extensions: The extensions to allow uploading in this set. The
+                       easiest way to do this is to add together the extension
+                       presets (for example, ``TEXT + DOCUMENTS + IMAGES``).
+                       It can be overridden by the configuration with the
+                       `UPLOADED_X_ALLOW` and `UPLOADED_X_DENY` configuration
+                       parameters. The default is `DEFAULTS`.
+    :param default_dest: If given, this should be a callable. If you call it
+                         with the app, it should return the default upload
+                         destination path for that app.
+    """
+    def __init__(self, name='files', extensions=DEFAULTS, default_dest=None):
+        if not name.isalnum():
+            raise ValueError("Name must be alphanumeric (no underscores)")
+        self.name = name
+        self.extensions = extensions
+        self._config = None
+        self.default_dest = default_dest
+    
+    @property
+    def config(self):
+        """
+        This gets the current configuration. By default, it looks up the
+        current application and gets the configuration from there. But if you
+        don't want to go to the full effort of setting an application, or it's
+        otherwise outside of a request context, set the `_config` attribute to
+        an `UploadConfiguration` instance, then set it back to `None` when
+        you're done.
+        """
+        if self._config is not None:
+            return self._config
+        try:
+            return current_app.upload_set_config[self.name]
+        except AttributeError:
+            raise RuntimeError("cannot access configuration outside request")
+    
+    def url(self, filename):
+        """
+        This function gets the URL a file uploaded to this set would be
+        accessed at. It doesn't check whether said file exists.
+        
+        :param filename: The filename to return the URL for.
+        """
+        base = self.config.base_url
+        if base is None:
+            return url_for('_uploads.uploaded_file', setname=self.name,
+                           filename=filename, _external=True)
+        else:
+            return base + filename
+    
+    def path(self, filename):
+        """
+        This returns the absolute path of a file uploaded to this set. It
+        doesn't actually check whether said file exists.
+        
+        :param filename: The filename to return the path for.
+        """
+        dest = self.config.destination
+        return os.path.join(dest, filename)
+    
+    def file_allowed(self, storage):
+        """
+        This tells whether a file is allowed. It should return `True` if the
+        given `werkzeug.FileStorage` object can be saved, and `False` if it
+        can't. The default implementation just checks the extension, so you
+        can override this if you want.
+        
+        :param storage: The `werkzeug.FileStorage` to check.
+        """
+        return self.extension_allowed(extension(storage.filename))
+    
+    def extension_allowed(self, ext):
+        """
+        This determines whether a specific extension is allowed. It is called
+        by `file_allowed`, so if you override that but still want to check
+        extensions, call back into this.
+        
+        :param ext: The extension to check, without the dot.
+        """
+        return ((ext in self.config.allow) or
+                (ext in self.extensions and ext not in self.config.deny))
+    
+    def save(self, storage, folder=None, name=None):
+        """
+        This saves a `werkzeug.FileStorage` into this upload set. If the
+        upload is not allowed, an `UploadNotAllowed` error will be raised.
+        Otherwise, the file will be saved and its name (including the folder)
+        will be returned.
+        
+        :param storage: The uploaded file to save.
+        :param folder: The subfolder within the upload set to save to.
+        :param name: The name to save the file as. If it ends with a dot, the
+                     file's extension will be appended to the end.
+        """
+        if not isinstance(storage, FileStorage):
+            raise TypeError("storage must be a werkzeug.FileStorage")
+        if not self.file_allowed(storage):
+            raise UploadNotAllowed()
+        basename = secure_filename(storage.filename)
+        if name:
+            if name.endswith('.'):
+                basename = name + extension(basename)
+            else:
+                basename = name
+        
+        if folder:
+            target_folder = os.path.join(self.config.destination, folder)
+        else:
+            target_folder = self.config.destination
+        if os.path.exists(os.path.join(target_folder, basename)):
+            basename = self.resolve_conflict(target_folder, basename)
+        
+        target = os.path.join(target_folder, basename)
+        storage.save(target)
+        if folder:
+            return posixpath.join(folder, basename)
+        else:
+            return basename
+    
+    def resolve_conflict(self, target_folder, basename):
+        """
+        If a file with the selected name already exists in the target folder,
+        this method is called to resolve the conflict. It should return a new
+        basename for the file.
+        
+        The default implementation splits the name and extension and adds a
+        suffix to the name consisting of an underscore and a number, and tries
+        that until it finds one that doesn't exist.
+        
+        :param target_folder: The absolute path to the target.
+        :param basename: The file's original basename.
+        """
+        name, ext = basename.rsplit('.', 1)
+        count = 0
+        while True:
+            count = count + 1
+            newname = '%s_%d.%s' % (name, count, ext)
+            if not os.path.exists(os.path.join(target_folder, newname)):
+                return newname
+
+
+uploads_mod = Module(__name__, name='_uploads', url_prefix='/_uploads')
+
+@uploads_mod.route('/<setname>/<path:filename>')
+def uploaded_file(setname, filename):
+    config = app.upload_set_config.get(setname)
+    if config is None:
+        abort(404)
+    send_from_directory(config.destination, filename)
+
+
+class TestingFileStorage(FileStorage):
+    """
+    This is a helper for testing upload behavior in your application. You
+    can manually create it, and its save method is overloaded to set `saved`
+    to the name of the file it was saved to. All of these parameters are
+    optional, so only bother setting the ones relevant to your application.
+    
+    :param stream: A stream. The default is an empty stream.
+    :param filename: The filename uploaded from the client. The default is the
+                     stream's name.
+    :param name: The name of the form field it was loaded from. The default is
+                 `None`.
+    :param content_type: The content type it was uploaded as. The default is
+                         ``application/octet-stream``.
+    :param content_length: How long it is. The default is -1.
+    :param headers: Multipart headers as a `werkzeug.Headers`. The default is
+                    `None`.
+    """
+    def __init__(self, stream=None, filename=None, name=None,
+                 content_type='application/octet-stream', content_length=-1,
+                 headers=None):
+        FileStorage.__init__(self, stream, filename, name=name,
+            content_type=content_type, content_length=content_length,
+            headers=None)
+        self.saved = None
+    
+    def save(self, dst, buffer_size=16384):
+        """
+        This marks the file as saved by setting the `saved` attribute to the
+        name of the file it was saved to.
+        
+        :param dst: The file to save to.
+        :param buffer_size: Ignored.
+        """
+        if isinstance(dst, basestring):
+            self.saved = dst
+        else:
+            self.saved = dst.name
+"""
+Flask-Uploads
+-------------
+Flask-Uploads provides flexible upload handling for Flask applications. It
+lets you divide your uploads into sets that the application user can deploy
+separately.
+
+Links
+`````
+* `documentation <http://packages.python.org/Flask-Uploads>`_
+* `development version
+  <http://bitbucket.org/leafstorm/flask-uploads/get/tip.gz#egg=Flask-Uploads-dev`_
+
+
+"""
+from setuptools import setup
+
+
+setup(
+    name='Flask-Uploads',
+    version='0.1',
+    url='http://bitbucket.org/leafstorm/flask-uploads/',
+    license='MIT',
+    author='Matthew "LeafStorm" Frazier',
+    author_email='leafstormrush@gmail.com',
+    description='Flexible and efficient upload handling for Flask',
+    long_description=__doc__,
+    packages=['flaskext'],
+    namespace_packages=['flaskext'],
+    zip_safe=False,
+    platforms='any',
+    install_requires=[
+        'Flask>=0.5'
+    ],
+    classifiers=[
+        'Development Status :: 4 - Beta',
+        'Environment :: Web Environment',
+        'Intended Audience :: Developers',
+        'License :: OSI Approved :: MIT License',
+        'Operating System :: OS Independent',
+        'Programming Language :: Python',
+        'Topic :: Internet :: WWW/HTTP :: Dynamic Content',
+        'Topic :: Software Development :: Libraries :: Python Modules'
+    ]
+)

File tests/test-uploads.py

+# -*- coding: utf-8 -*-
+"""
+tests/test-uploads.py
+=====================
+This is a Nose testing file for Flask-Uploads.
+
+NOTE: While Flask-Uploads will probably work on Windows, all the filenames in
+this testing file are in the POSIX style. So, no guarantees that the tests
+will pass.
+
+:copyright: 2010 Matthew "LeafStorm" Frazier
+:license:   MIT/X11, see LICENSE for details
+"""
+import os.path
+from flask import Flask, url_for
+from flaskext.uploads import (UploadSet, UploadConfiguration, extension,
+    TestingFileStorage, patch_request_class, configure_uploads, addslash,
+    ALL, AllExcept)
+
+
+class TestMiscellaneous(object):
+    def test_patch_request_class(self):
+        app = Flask(__name__)
+        assert app.request_class.max_form_memory_size is None
+        patch_request_class(app)
+        assert app.request_class.max_form_memory_size == 16 * 1024 * 1024
+    
+    def test_tfs(self):
+        tfs = TestingFileStorage(filename='foo.bar')
+        assert tfs.filename == 'foo.bar'
+        assert tfs.name is None
+        assert tfs.saved is None
+        tfs.save('foo_bar.txt')
+        assert tfs.saved == 'foo_bar.txt'
+    
+    def test_extension(self):
+        assert extension('foo.txt') == 'txt'
+        assert extension('foo') == 'foo'
+        assert extension('archive.tar.gz') == 'gz'
+        assert extension('audio.m4a') == 'm4a'
+    
+    def test_addslash(self):
+        assert (addslash('http://localhost:4000') ==
+                'http://localhost:4000/')
+        assert (addslash('http://localhost/uploads') ==
+                'http://localhost/uploads/')
+        assert (addslash('http://localhost:4000/') ==
+                'http://localhost:4000/')
+        assert (addslash('http://localhost/uploads/') ==
+                'http://localhost/uploads/')
+    
+    def test_custom_iterables(self):
+        assert 'txt' in ALL
+        assert 'exe' in ALL
+        ax = AllExcept(['exe'])
+        assert 'txt' in ax
+        assert 'exe' not in ax
+
+
+Config = UploadConfiguration
+
+class TestConfiguration(object):
+    def setup(self):
+        self.app = Flask(__name__)
+    
+    def teardown(self):
+        del self.app
+    
+    def configure(self, *sets, **options):
+        self.app.config.update(options)
+        configure_uploads(self.app, sets)
+        return self.app.upload_set_config
+    
+    def test_manual(self):
+        f, p = UploadSet('files'), UploadSet('photos')
+        setconfig = self.configure(f, p,
+            UPLOADED_FILES_DEST = '/var/files',
+            UPLOADED_FILES_URL = 'http://localhost:6001/',
+            UPLOADED_PHOTOS_DEST = '/mnt/photos',
+            UPLOADED_PHOTOS_URL = 'http://localhost:6002/'
+        )
+        fconf, pconf = setconfig['files'], setconfig['photos']
+        assert fconf == Config('/var/files', 'http://localhost:6001/')
+        assert pconf == Config('/mnt/photos', 'http://localhost:6002/')
+    
+    def test_selfserve(self):
+        f, p = UploadSet('files'), UploadSet('photos')
+        setconfig = self.configure(f, p,
+            UPLOADED_FILES_DEST = '/var/files',
+            UPLOADED_PHOTOS_DEST = '/mnt/photos'
+        )
+        fconf, pconf = setconfig['files'], setconfig['photos']
+        assert fconf == Config('/var/files', None)