Import Upstream version 2.2.2

This commit is contained in:
su-fang 2023-03-14 14:48:50 +08:00
commit 473845b537
291 changed files with 45178 additions and 0 deletions

13
.editorconfig Normal file
View File

@ -0,0 +1,13 @@
root = true
[*]
indent_style = space
indent_size = 4
insert_final_newline = true
trim_trailing_whitespace = true
end_of_line = lf
charset = utf-8
max_line_length = 88
[*.{yml,yaml,json,js,css,html}]
indent_size = 2

8
.gitattributes vendored Normal file
View File

@ -0,0 +1,8 @@
# Normalize CRLF to LF for all text files
* text=auto
# Declare binary file types so they won't be normalized
*.png binary
*.jpg binary
tests/**/*.http binary
tests/res/test.txt binary

27
.github/ISSUE_TEMPLATE/bug-report.md vendored Normal file
View File

@ -0,0 +1,27 @@
---
name: Bug report
about: Report a bug in Werkzeug (not other projects which depend on Werkzeug)
---
<!--
This issue tracker is a tool to address bugs in Werkzeug itself. Please
use Pallets Discord or Stack Overflow for questions about your own code.
Replace this comment with a clear outline of what the bug is.
-->
<!--
Describe how to replicate the bug.
Include a minimal reproducible example that demonstrates the bug.
Include the full traceback if there was an exception.
-->
<!--
Describe the expected behavior that should have happened but didn't.
-->
Environment:
- Python version:
- Werkzeug version:

11
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View File

@ -0,0 +1,11 @@
blank_issues_enabled: false
contact_links:
- name: Security issue
url: security@palletsprojects.com
about: Do not report security issues publicly. Email our security contact.
- name: Questions
url: https://stackoverflow.com/questions/tagged/werkzeug?tab=Frequent
about: Search for and ask questions about your code on Stack Overflow.
- name: Questions and discussions
url: https://discord.gg/pallets
about: Discuss questions about your code on our Discord chat.

View File

@ -0,0 +1,15 @@
---
name: Feature request
about: Suggest a new feature for Werkzeug
---
<!--
Replace this comment with a description of what the feature should do.
Include details such as links relevant specs or previous discussions.
-->
<!--
Replace this comment with an example of the problem which this feature
would resolve. Is this problem solvable without changes to Werkzeug,
such as by subclassing or using an extension?
-->

9
.github/dependabot.yml vendored Normal file
View File

@ -0,0 +1,9 @@
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "monthly"
day: "monday"
time: "16:00"
timezone: "UTC"

30
.github/pull_request_template.md vendored Normal file
View File

@ -0,0 +1,30 @@
<!--
Before opening a PR, open a ticket describing the issue or feature the
PR will address. Follow the steps in CONTRIBUTING.rst.
Replace this comment with a description of the change. Describe how it
addresses the linked ticket.
-->
<!--
Link to relevant issues or previous PRs, one per line. Use "fixes" to
automatically close an issue.
-->
- fixes #<issue number>
<!--
Ensure each step in CONTRIBUTING.rst is complete by adding an "x" to
each box below.
If only docs were changed, these aren't relevant and can be removed.
-->
Checklist:
- [ ] Add tests that demonstrate the correct behavior of the change. Tests should fail without the change.
- [ ] Add or update relevant docs, in the docs folder and in code.
- [ ] Add an entry in `CHANGES.rst` summarizing the change and linking to the issue.
- [ ] Add `.. versionchanged::` entries in any relevant code docs.
- [ ] Run `pre-commit` hooks and fix any issues.
- [ ] Run `pytest` and `tox`, no tests failed.

15
.github/workflows/lock.yaml vendored Normal file
View File

@ -0,0 +1,15 @@
name: 'Lock threads'
on:
schedule:
- cron: '0 0 * * *'
jobs:
lock:
runs-on: ubuntu-latest
steps:
- uses: dessant/lock-threads@v3
with:
github-token: ${{ github.token }}
issue-inactive-days: 14
pr-inactive-days: 14

55
.github/workflows/tests.yaml vendored Normal file
View File

@ -0,0 +1,55 @@
name: Tests
on:
push:
branches:
- main
- '*.x'
paths-ignore:
- 'docs/**'
- '*.md'
- '*.rst'
pull_request:
branches:
- main
- '*.x'
paths-ignore:
- 'docs/**'
- '*.md'
- '*.rst'
jobs:
tests:
name: ${{ matrix.name }}
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
include:
- {name: Linux, python: '3.10', os: ubuntu-latest, tox: py310}
- {name: Windows, python: '3.10', os: windows-latest, tox: py310}
- {name: Mac, python: '3.10', os: macos-latest, tox: py310}
- {name: '3.11-dev', python: '3.11-dev', os: ubuntu-latest, tox: py311}
- {name: '3.9', python: '3.9', os: ubuntu-latest, tox: py39}
- {name: '3.8', python: '3.8', os: ubuntu-latest, tox: py38}
- {name: '3.7', python: '3.7', os: ubuntu-latest, tox: py37}
- {name: 'PyPy', python: 'pypy-3.7', os: ubuntu-latest, tox: pypy37}
- {name: Typing, python: '3.10', os: ubuntu-latest, tox: typing}
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python }}
cache: 'pip'
cache-dependency-path: 'requirements/*.txt'
- name: update pip
run: |
pip install -U wheel
pip install -U setuptools
python -m pip install -U pip
- name: cache mypy
uses: actions/cache@v3.0.4
with:
path: ./.mypy_cache
key: mypy|${{ matrix.python }}|${{ hashFiles('setup.cfg') }}
if: matrix.tox == 'typing'
- run: pip install tox
- run: tox -e ${{ matrix.tox }}

26
.gitignore vendored Normal file
View File

@ -0,0 +1,26 @@
MANIFEST
build
dist
/src/Werkzeug.egg-info
*.pyc
*.pyo
env
.DS_Store
docs/_build
bench/a
bench/b
.tox
.coverage
.coverage.*
coverage_out
htmlcov
.cache
.xprocess
.hypothesis
test_uwsgi_failed
.idea
.pytest_cache/
venv/
.vscode
.mypy_cache/
.dmypy.json

44
.pre-commit-config.yaml Normal file
View File

@ -0,0 +1,44 @@
ci:
autoupdate_branch: "2.2.x"
autoupdate_schedule: monthly
repos:
- repo: https://github.com/asottile/pyupgrade
rev: v2.37.3
hooks:
- id: pyupgrade
args: ["--py37-plus"]
- repo: https://github.com/asottile/reorder_python_imports
rev: v3.8.2
hooks:
- id: reorder-python-imports
name: Reorder Python imports (src, tests)
files: "^(?!examples/)"
args: ["--application-directories", ".:src"]
additional_dependencies: ["setuptools>60.9"]
- id: reorder-python-imports
name: Reorder Python imports (examples)
files: "^examples/"
args: ["--application-directories", "examples"]
additional_dependencies: ["setuptools>60.9"]
- repo: https://github.com/psf/black
rev: 22.6.0
hooks:
- id: black
- repo: https://github.com/PyCQA/flake8
rev: 5.0.4
hooks:
- id: flake8
additional_dependencies:
- flake8-bugbear
- flake8-implicit-str-concat
- repo: https://github.com/peterdemin/pip-compile-multi
rev: v2.4.6
hooks:
- id: pip-compile-multi-verify
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.3.0
hooks:
- id: fix-byte-order-marker
- id: trailing-whitespace
- id: end-of-file-fixer
exclude: "^tests/.*.http$"

13
.readthedocs.yaml Normal file
View File

@ -0,0 +1,13 @@
version: 2
build:
os: ubuntu-20.04
tools:
python: "3.10"
python:
install:
- requirements: requirements/docs.txt
- method: pip
path: .
sphinx:
builder: dirhtml
fail_on_warning: true

2259
CHANGES.rst Normal file

File diff suppressed because it is too large Load Diff

76
CODE_OF_CONDUCT.md Normal file
View File

@ -0,0 +1,76 @@
# Contributor Covenant Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as
contributors and maintainers pledge to making participation in our project and
our community a harassment-free experience for everyone, regardless of age, body
size, disability, ethnicity, sex characteristics, gender identity and expression,
level of experience, education, socio-economic status, nationality, personal
appearance, race, religion, or sexual identity and orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment
include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or
advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic
address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable
behavior and are expected to take appropriate and fair corrective action in
response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or
reject comments, commits, code, wiki edits, issues, and other contributions
that are not aligned to this Code of Conduct, or to ban temporarily or
permanently any contributor for other behaviors that they deem inappropriate,
threatening, offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces
when an individual is representing the project or its community. Examples of
representing a project or community include using an official project e-mail
address, posting via an official social media account, or acting as an appointed
representative at an online or offline event. Representation of a project may be
further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting the project team at report@palletsprojects.com. All
complaints will be reviewed and investigated and will result in a response that
is deemed necessary and appropriate to the circumstances. The project team is
obligated to maintain confidentiality with regard to the reporter of an incident.
Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good
faith may face temporary or permanent repercussions as determined by other
members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see
https://www.contributor-covenant.org/faq

222
CONTRIBUTING.rst Normal file
View File

@ -0,0 +1,222 @@
How to contribute to Werkzeug
=============================
Thank you for considering contributing to Werkzeug!
Support questions
-----------------
Please don't use the issue tracker for this. The issue tracker is a
tool to address bugs and feature requests in Werkzeug itself. Use one of
the following resources for questions about using Werkzeug or issues
with your own code:
- The ``#get-help`` channel on our Discord chat:
https://discord.gg/pallets
- The mailing list flask@python.org for long term discussion or larger
issues.
- Ask on `Stack Overflow`_. Search with Google first using:
``site:stackoverflow.com werkzeug {search term, exception message, etc.}``
.. _Stack Overflow: https://stackoverflow.com/questions/tagged/werkzeug?tab=Frequent
Reporting issues
----------------
Include the following information in your post:
- Describe what you expected to happen.
- If possible, include a `minimal reproducible example`_ to help us
identify the issue. This also helps check that the issue is not with
your own code.
- Describe what actually happened. Include the full traceback if there
was an exception.
- List your Python and Werkzeug versions. If possible, check if this
issue is already fixed in the latest releases or the latest code in
the repository.
.. _minimal reproducible example: https://stackoverflow.com/help/minimal-reproducible-example
Submitting patches
------------------
If there is not an open issue for what you want to submit, prefer
opening one for discussion before working on a PR. You can work on any
issue that doesn't have an open PR linked to it or a maintainer assigned
to it. These show up in the sidebar. No need to ask if you can work on
an issue that interests you.
Include the following in your patch:
- Use `Black`_ to format your code. This and other tools will run
automatically if you install `pre-commit`_ using the instructions
below.
- Include tests if your patch adds or changes code. Make sure the test
fails without your patch.
- Update any relevant docs pages and docstrings. Docs pages and
docstrings should be wrapped at 72 characters.
- Add an entry in ``CHANGES.rst``. Use the same style as other
entries. Also include ``.. versionchanged::`` inline changelogs in
relevant docstrings.
.. _Black: https://black.readthedocs.io
.. _pre-commit: https://pre-commit.com
First time setup
~~~~~~~~~~~~~~~~
- Download and install the `latest version of git`_.
- Configure git with your `username`_ and `email`_.
.. code-block:: text
$ git config --global user.name 'your name'
$ git config --global user.email 'your email'
- Make sure you have a `GitHub account`_.
- Fork Werkzeug to your GitHub account by clicking the `Fork`_ button.
- `Clone`_ the main repository locally.
.. code-block:: text
$ git clone https://github.com/pallets/werkzeug
$ cd werkzeug
- Add your fork as a remote to push your work to. Replace
``{username}`` with your username. This names the remote "fork", the
default Pallets remote is "origin".
.. code-block:: text
$ git remote add fork https://github.com/{username}/werkzeug
- Create a virtualenv.
.. code-block:: text
$ python3 -m venv env
$ . env/bin/activate
On Windows, activating is different.
.. code-block:: text
> env\Scripts\activate
- Upgrade pip and setuptools.
.. code-block:: text
$ python -m pip install --upgrade pip setuptools
- Install the development dependencies, then install Werkzeug in
editable mode.
.. code-block:: text
$ pip install -r requirements/dev.txt && pip install -e .
- Install the pre-commit hooks.
.. code-block:: text
$ pre-commit install
.. _latest version of git: https://git-scm.com/downloads
.. _username: https://docs.github.com/en/github/using-git/setting-your-username-in-git
.. _email: https://docs.github.com/en/github/setting-up-and-managing-your-github-user-account/setting-your-commit-email-address
.. _GitHub account: https://github.com/join
.. _Fork: https://github.com/pallets/werkzeug/fork
.. _Clone: https://docs.github.com/en/github/getting-started-with-github/fork-a-repo#step-2-create-a-local-clone-of-your-fork
Start coding
~~~~~~~~~~~~
- Create a branch to identify the issue you would like to work on. If
you're submitting a bug or documentation fix, branch off of the
latest ".x" branch.
.. code-block:: text
$ git fetch origin
$ git checkout -b your-branch-name origin/2.0.x
If you're submitting a feature addition or change, branch off of the
"main" branch.
.. code-block:: text
$ git fetch origin
$ git checkout -b your-branch-name origin/main
- Using your favorite editor, make your changes,
`committing as you go`_.
- Include tests that cover any code changes you make. Make sure the
test fails without your patch. Run the tests as described below.
- Push your commits to your fork on GitHub and
`create a pull request`_. Link to the issue being addressed with
``fixes #123`` in the pull request.
.. code-block:: text
$ git push --set-upstream fork your-branch-name
.. _committing as you go: https://dont-be-afraid-to-commit.readthedocs.io/en/latest/git/commandlinegit.html#commit-your-changes
.. _create a pull request: https://docs.github.com/en/github/collaborating-with-issues-and-pull-requests/creating-a-pull-request
Running the tests
~~~~~~~~~~~~~~~~~
Run the basic test suite with pytest.
.. code-block:: text
$ pytest
This runs the tests for the current environment, which is usually
sufficient. CI will run the full suite when you submit your pull
request. You can run the full test suite with tox if you don't want to
wait.
.. code-block:: text
$ tox
Running test coverage
~~~~~~~~~~~~~~~~~~~~~
Generating a report of lines that do not have test coverage can indicate
where to start contributing. Run ``pytest`` using ``coverage`` and
generate a report.
.. code-block:: text
$ pip install coverage
$ coverage run -m pytest
$ coverage html
Open ``htmlcov/index.html`` in your browser to explore the report.
Read more about `coverage <https://coverage.readthedocs.io>`__.
Building the docs
~~~~~~~~~~~~~~~~~
Build the docs in the ``docs`` directory using Sphinx.
.. code-block:: text
$ cd docs
$ make html
Open ``_build/html/index.html`` in your browser to view the docs.
Read more about `Sphinx <https://www.sphinx-doc.org/en/stable/>`__.

28
LICENSE.rst Normal file
View File

@ -0,0 +1,28 @@
Copyright 2007 Pallets
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. 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.
3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE 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
HOLDER 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
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

12
MANIFEST.in Normal file
View File

@ -0,0 +1,12 @@
include CHANGES.rst
include tox.ini
include requirements/*.txt
graft artwork
graft docs
prune docs/_build
graft examples
graft tests
include src/werkzeug/py.typed
include src/werkzeug/*.pyi
graft src/werkzeug/debug/shared
global-exclude *.pyc

91
README.rst Normal file
View File

@ -0,0 +1,91 @@
Werkzeug
========
*werkzeug* German noun: "tool". Etymology: *werk* ("work"), *zeug* ("stuff")
Werkzeug is a comprehensive `WSGI`_ web application library. It began as
a simple collection of various utilities for WSGI applications and has
become one of the most advanced WSGI utility libraries.
It includes:
- An interactive debugger that allows inspecting stack traces and
source code in the browser with an interactive interpreter for any
frame in the stack.
- A full-featured request object with objects to interact with
headers, query args, form data, files, and cookies.
- A response object that can wrap other WSGI applications and handle
streaming data.
- A routing system for matching URLs to endpoints and generating URLs
for endpoints, with an extensible system for capturing variables
from URLs.
- HTTP utilities to handle entity tags, cache control, dates, user
agents, cookies, files, and more.
- A threaded WSGI server for use while developing applications
locally.
- A test client for simulating HTTP requests during testing without
requiring running a server.
Werkzeug doesn't enforce any dependencies. It is up to the developer to
choose a template engine, database adapter, and even how to handle
requests. It can be used to build all sorts of end user applications
such as blogs, wikis, or bulletin boards.
`Flask`_ wraps Werkzeug, using it to handle the details of WSGI while
providing more structure and patterns for defining powerful
applications.
.. _WSGI: https://wsgi.readthedocs.io/en/latest/
.. _Flask: https://www.palletsprojects.com/p/flask/
Installing
----------
Install and update using `pip`_:
.. code-block:: text
pip install -U Werkzeug
.. _pip: https://pip.pypa.io/en/stable/getting-started/
A Simple Example
----------------
.. code-block:: python
from werkzeug.wrappers import Request, Response
@Request.application
def application(request):
return Response('Hello, World!')
if __name__ == '__main__':
from werkzeug.serving import run_simple
run_simple('localhost', 4000, application)
Donate
------
The Pallets organization develops and supports Werkzeug and other
popular packages. In order to grow the community of contributors and
users, and allow the maintainers to devote more time to the projects,
`please donate today`_.
.. _please donate today: https://palletsprojects.com/donate
Links
-----
- Documentation: https://werkzeug.palletsprojects.com/
- Changes: https://werkzeug.palletsprojects.com/changes/
- PyPI Releases: https://pypi.org/project/Werkzeug/
- Source Code: https://github.com/pallets/werkzeug/
- Issue Tracker: https://github.com/pallets/werkzeug/issues/
- Website: https://palletsprojects.com/p/werkzeug/
- Twitter: https://twitter.com/PalletsTeam
- Chat: https://discord.gg/pallets

BIN
artwork/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

88
artwork/logo.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 16 KiB

19
docs/Makefile Normal file
View File

@ -0,0 +1,19 @@
# Minimal makefile for Sphinx documentation
#
# You can set these variables from the command line.
SPHINXOPTS =
SPHINXBUILD = sphinx-build
SOURCEDIR = .
BUILDDIR = _build
# Put it first so that "make" without argument is like "make help".
help:
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
.PHONY: help Makefile
# 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)

BIN
docs/_static/debug-screenshot.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 204 KiB

BIN
docs/_static/favicon.ico vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
docs/_static/shortly.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

BIN
docs/_static/werkzeug.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

4
docs/changes.rst Normal file
View File

@ -0,0 +1,4 @@
Changes
=======
.. include:: ../CHANGES.rst

55
docs/conf.py Normal file
View File

@ -0,0 +1,55 @@
from pallets_sphinx_themes import get_version
from pallets_sphinx_themes import ProjectLink
# Project --------------------------------------------------------------
project = "Werkzeug"
copyright = "2007 Pallets"
author = "Pallets"
release, version = get_version("Werkzeug")
# General --------------------------------------------------------------
master_doc = "index"
extensions = [
"sphinx.ext.autodoc",
"sphinx.ext.intersphinx",
"pallets_sphinx_themes",
"sphinx_issues",
"sphinxcontrib.log_cabinet",
]
autoclass_content = "both"
autodoc_typehints = "description"
intersphinx_mapping = {"python": ("https://docs.python.org/3/", None)}
issues_github_path = "pallets/werkzeug"
# HTML -----------------------------------------------------------------
html_theme = "werkzeug"
html_context = {
"project_links": [
ProjectLink("Donate", "https://palletsprojects.com/donate"),
ProjectLink("PyPI Releases", "https://pypi.org/project/Werkzeug/"),
ProjectLink("Source Code", "https://github.com/pallets/werkzeug/"),
ProjectLink("Issue Tracker", "https://github.com/pallets/werkzeug/issues/"),
ProjectLink("Website", "https://palletsprojects.com/p/werkzeug/"),
ProjectLink("Twitter", "https://twitter.com/PalletsTeam"),
ProjectLink("Chat", "https://discord.gg/pallets"),
]
}
html_sidebars = {
"index": ["project.html", "localtoc.html", "searchbox.html", "ethicalads.html"],
"**": ["localtoc.html", "relations.html", "searchbox.html", "ethicalads.html"],
}
singlehtml_sidebars = {"index": ["project.html", "localtoc.html", "ethicalads.html"]}
html_static_path = ["_static"]
html_favicon = "_static/favicon.ico"
html_logo = "_static/werkzeug.png"
html_title = f"Werkzeug Documentation ({version})"
html_show_sourcelink = False
# LaTeX ----------------------------------------------------------------
latex_documents = [
(master_doc, f"Werkzeug-{version}.tex", html_title, author, "manual")
]

138
docs/datastructures.rst Normal file
View File

@ -0,0 +1,138 @@
===============
Data Structures
===============
.. module:: werkzeug.datastructures
Werkzeug provides some subclasses of common Python objects to extend them
with additional features. Some of them are used to make them immutable, others
are used to change some semantics to better work with HTTP.
General Purpose
===============
.. versionchanged:: 0.6
The general purpose classes are now pickleable in each protocol as long
as the contained objects are pickleable. This means that the
:class:`FileMultiDict` won't be pickleable as soon as it contains a
file.
.. autoclass:: TypeConversionDict
:members:
.. autoclass:: ImmutableTypeConversionDict
:members: copy
.. autoclass:: MultiDict
:members:
:inherited-members:
.. autoclass:: OrderedMultiDict
.. autoclass:: ImmutableMultiDict
:members: copy
.. autoclass:: ImmutableOrderedMultiDict
:members: copy
.. autoclass:: CombinedMultiDict
.. autoclass:: ImmutableDict
:members: copy
.. autoclass:: ImmutableList
.. autoclass:: FileMultiDict
:members:
.. _http-datastructures:
HTTP Related
============
.. autoclass:: Headers([defaults])
:members:
.. autoclass:: EnvironHeaders
.. autoclass:: HeaderSet
:members:
.. autoclass:: Accept
:members:
.. autoclass:: MIMEAccept
:members: accept_html, accept_xhtml, accept_json
.. autoclass:: CharsetAccept
.. autoclass:: LanguageAccept
.. autoclass:: RequestCacheControl
:members:
.. autoattribute:: no_cache
.. autoattribute:: no_store
.. autoattribute:: max_age
.. autoattribute:: no_transform
.. autoclass:: ResponseCacheControl
:members:
.. autoattribute:: no_cache
.. autoattribute:: no_store
.. autoattribute:: max_age
.. autoattribute:: no_transform
.. autoclass:: ETags
:members:
.. autoclass:: Authorization
:members:
.. autoclass:: WWWAuthenticate
:members:
.. autoclass:: IfRange
:members:
.. autoclass:: Range
:members:
.. autoclass:: ContentRange
:members:
Others
======
.. autoclass:: FileStorage
:members:
.. attribute:: stream
The input stream for the uploaded file. This usually points to an
open temporary file.
.. attribute:: filename
The filename of the file on the client. Can be a ``str``, or an
instance of ``os.PathLike``.
.. attribute:: name
The name of the form field.
.. attribute:: headers
The multipart headers as :class:`Headers` object. This usually contains
irrelevant information but in combination with custom multipart requests
the raw headers might be interesting.
.. versionadded:: 0.6

101
docs/debug.rst Normal file
View File

@ -0,0 +1,101 @@
Debugging Applications
======================
.. module:: werkzeug.debug
Depending on the WSGI gateway/server, exceptions are handled
differently. Most of the time, exceptions go to stderr or the error log,
and a generic "500 Internal Server Error" message is displayed.
Since this is not the best debugging environment, Werkzeug provides a
WSGI middleware that renders nice tracebacks, optionally with an
interactive debug console to execute code in any frame.
.. danger::
The debugger allows the execution of arbitrary code which makes it a
major security risk. **The debugger must never be used on production
machines. We cannot stress this enough. Do not enable the debugger
in production.**
.. note::
The interactive debugger does not work in forking environments, such
as a server that starts multiple processes. Most such environments
are production servers, where the debugger should not be enabled
anyway.
Enabling the Debugger
---------------------
Enable the debugger by wrapping the application with the
:class:`DebuggedApplication` middleware. Alternatively, you can pass
``use_debugger=True`` to :func:`run_simple` and it will do that for you.
.. autoclass:: DebuggedApplication
Using the Debugger
------------------
Once enabled and an error happens during a request you will see a
detailed traceback instead of a generic "internal server error". The
traceback is still output to the terminal as well.
The error message is displayed at the top. Clicking it jumps to the
bottom of the traceback. Frames that represent user code, as opposed to
built-ins or installed packages, are highlighted blue. Clicking a
frame will show more lines for context, clicking again will hide them.
If you have the ``evalex`` feature enabled you can get a console for
every frame in the traceback by hovering over a frame and clicking the
console icon that appears at the right. Once clicked a console opens
where you can execute Python code in:
.. image:: _static/debug-screenshot.png
:alt: a screenshot of the interactive debugger
:align: center
Inside the interactive consoles you can execute any kind of Python code.
Unlike regular Python consoles the output of the object reprs is colored
and stripped to a reasonable size by default. If the output is longer
than what the console decides to display a small plus sign is added to
the repr and a click will expand the repr.
To display all variables that are defined in the current frame you can
use the ``dump()`` function. You can call it without arguments to get a
detailed list of all variables and their values, or with an object as
argument to get a detailed list of all the attributes it has.
Debugger PIN
------------
Starting with Werkzeug 0.11 the debug console is protected by a PIN.
This is a security helper to make it less likely for the debugger to be
exploited if you forget to disable it when deploying to production. The
PIN based authentication is enabled by default.
The first time a console is opened, a dialog will prompt for a PIN that
is printed to the command line. The PIN is generated in a stable way
that is specific to the project. An explicit PIN can be provided through
the environment variable ``WERKZEUG_DEBUG_PIN``. This can be set to a
number and will become the PIN. This variable can also be set to the
value ``off`` to disable the PIN check entirely.
If an incorrect PIN is entered too many times the server needs to be
restarted.
**This feature is not meant to entirely secure the debugger. It is
intended to make it harder for an attacker to exploit the debugger.
Never enable the debugger in production.**
Pasting Errors
--------------
If you click on the "Traceback (most recent call last)" header, the
view switches to a traditional text-based traceback. You can copy and
paste this in order to provide information when asking a question or
reporting an issue.

View File

@ -0,0 +1,82 @@
Apache httpd
============
`Apache httpd`_ is a fast, production level HTTP server. When serving
your application with one of the WSGI servers listed in :doc:`index`, it
is often good or necessary to put a dedicated HTTP server in front of
it. This "reverse proxy" can handle incoming requests, TLS, and other
security and performance concerns better than the WSGI server.
httpd can be installed using your system package manager, or a pre-built
executable for Windows. Installing and running httpd itself is outside
the scope of this doc. This page outlines the basics of configuring
httpd to proxy your application. Be sure to read its documentation to
understand what features are available.
.. _Apache httpd: https://httpd.apache.org/
Domain Name
-----------
Acquiring and configuring a domain name is outside the scope of this
doc. In general, you will buy a domain name from a registrar, pay for
server space with a hosting provider, and then point your registrar
at the hosting provider's name servers.
To simulate this, you can also edit your ``hosts`` file, located at
``/etc/hosts`` on Linux. Add a line that associates a name with the
local IP.
Modern Linux systems may be configured to treat any domain name that
ends with ``.localhost`` like this without adding it to the ``hosts``
file.
.. code-block:: python
:caption: ``/etc/hosts``
127.0.0.1 hello.localhost
Configuration
-------------
The httpd configuration is located at ``/etc/httpd/conf/httpd.conf`` on
Linux. It may be different depending on your operating system. Check the
docs and look for ``httpd.conf``.
Remove or comment out any existing ``DocumentRoot`` directive. Add the
config lines below. We'll assume the WSGI server is listening locally at
``http://127.0.0.1:8000``.
.. code-block:: apache
:caption: ``/etc/httpd/conf/httpd.conf``
LoadModule proxy_module modules/mod_proxy.so
LoadModule proxy_http_module modules/mod_proxy_http.so
ProxyPass / http://127.0.0.1:8000/
RequestHeader set X-Forwarded-Proto http
RequestHeader set X-Forwarded-Prefix /
The ``LoadModule`` lines might already exist. If so, make sure they are
uncommented instead of adding them manually.
Then :doc:`proxy_fix` so that your application uses the ``X-Forwarded``
headers. ``X-Forwarded-For`` and ``X-Forwarded-Host`` are automatically
set by ``ProxyPass``.
Static Files
------------
If your application has static files such as JavaScript, CSS, and
images, it will be more efficient to let Nginx serve them directly
rather than going through the Python application.
Assuming the static files are expected to be available under the
``/static/`` URL, and are stored at ``/home/project/static/``, add the
following to the config above.
.. code-block:: apache
Alias /static/ /home/project/static/

View File

@ -0,0 +1,80 @@
eventlet
========
Prefer using :doc:`gunicorn` with eventlet workers rather than using
`eventlet`_ directly. Gunicorn provides a much more configurable and
production-tested server.
`eventlet`_ allows writing asynchronous, coroutine-based code that looks
like standard synchronous Python. It uses `greenlet`_ to enable task
switching without writing ``async/await`` or using ``asyncio``.
:doc:`gevent` is another library that does the same thing. Certain
dependencies you have, or other considerations, may affect which of the
two you choose to use.
eventlet provides a WSGI server that can handle many connections at once
instead of one per worker process. You must actually use eventlet in
your own code to see any benefit to using the server.
.. _eventlet: https://eventlet.net/
.. _greenlet: https://greenlet.readthedocs.io/en/latest/
Installing
----------
When using eventlet, greenlet>=1.0 is required, otherwise context locals
such as ``request`` will not work as expected. When using PyPy,
PyPy>=7.3.7 is required.
Create a virtualenv, install your application, then install
``eventlet``.
.. code-block:: text
$ cd hello-app
$ python -m venv venv
$ . venv/bin/activate
$ pip install . # install your application
$ pip install eventlet
Running
-------
To use eventlet to serve your application, write a script that imports
its ``wsgi.server``, as well as your app or app factory.
.. code-block:: python
:caption: ``wsgi.py``
import eventlet
from eventlet import wsgi
from hello import create_app
app = create_app()
wsgi.server(eventlet.listen(("127.0.0.1", 8000), app)
.. code-block:: text
$ python wsgi.py
(x) wsgi starting up on http://127.0.0.1:8000
Binding Externally
------------------
eventlet should not be run as root because it would cause your
application code to run as root, which is not secure. However, this
means it will not be possible to bind to port 80 or 443. Instead, a
reverse proxy such as :doc:`nginx` or :doc:`apache-httpd` should be used
in front of eventlet.
You can bind to all external IPs on a non-privileged port by using
``0.0.0.0`` in the server arguments shown in the previous section.
Don't do this when using a reverse proxy setup, otherwise it will be
possible to bypass the proxy.
``0.0.0.0`` is not a valid address to navigate to, you'd use a specific
IP address in your browser.

View File

@ -0,0 +1,80 @@
gevent
======
Prefer using :doc:`gunicorn` or :doc:`uwsgi` with gevent workers rather
than using `gevent`_ directly. Gunicorn and uWSGI provide much more
configurable and production-tested servers.
`gevent`_ allows writing asynchronous, coroutine-based code that looks
like standard synchronous Python. It uses `greenlet`_ to enable task
switching without writing ``async/await`` or using ``asyncio``.
:doc:`eventlet` is another library that does the same thing. Certain
dependencies you have, or other considerations, may affect which of the
two you choose to use.
gevent provides a WSGI server that can handle many connections at once
instead of one per worker process. You must actually use gevent in your
own code to see any benefit to using the server.
.. _gevent: https://www.gevent.org/
.. _greenlet: https://greenlet.readthedocs.io/en/latest/
Installing
----------
When using gevent, greenlet>=1.0 is required, otherwise context locals
such as ``request`` will not work as expected. When using PyPy,
PyPy>=7.3.7 is required.
Create a virtualenv, install your application, then install ``gevent``.
.. code-block:: text
$ cd hello-app
$ python -m venv venv
$ . venv/bin/activate
$ pip install . # install your application
$ pip install gevent
Running
-------
To use gevent to serve your application, write a script that imports its
``WSGIServer``, as well as your app or app factory.
.. code-block:: python
:caption: ``wsgi.py``
from gevent.pywsgi import WSGIServer
from hello import create_app
app = create_app()
http_server = WSGIServer(("127.0.0.1", 8000), app)
http_server.serve_forever()
.. code-block:: text
$ python wsgi.py
No output is shown when the server starts.
Binding Externally
------------------
gevent should not be run as root because it would cause your
application code to run as root, which is not secure. However, this
means it will not be possible to bind to port 80 or 443. Instead, a
reverse proxy such as :doc:`nginx` or :doc:`apache-httpd` should be used
in front of gevent.
You can bind to all external IPs on a non-privileged port by using
``0.0.0.0`` in the server arguments shown in the previous section. Don't
do this when using a reverse proxy setup, otherwise it will be possible
to bypass the proxy.
``0.0.0.0`` is not a valid address to navigate to, you'd use a specific
IP address in your browser.

View File

@ -0,0 +1,130 @@
Gunicorn
========
`Gunicorn`_ is a pure Python WSGI server with simple configuration and
multiple worker implementations for performance tuning.
* It tends to integrate easily with hosting platforms.
* It does not support Windows (but does run on WSL).
* It is easy to install as it does not require additional dependencies
or compilation.
* It has built-in async worker support using gevent or eventlet.
This page outlines the basics of running Gunicorn. Be sure to read its
`documentation`_ and use ``gunicorn --help`` to understand what features
are available.
.. _Gunicorn: https://gunicorn.org/
.. _documentation: https://docs.gunicorn.org/
Installing
----------
Gunicorn is easy to install, as it does not require external
dependencies or compilation. It runs on Windows only under WSL.
Create a virtualenv, install your application, then install
``gunicorn``.
.. code-block:: text
$ cd hello-app
$ python -m venv venv
$ . venv/bin/activate
$ pip install . # install your application
$ pip install gunicorn
Running
-------
The only required argument to Gunicorn tells it how to load your
application. The syntax is ``{module_import}:{app_variable}``.
``module_import`` is the dotted import name to the module with your
application. ``app_variable`` is the variable with the application. It
can also be a function call (with any arguments) if you're using the
app factory pattern.
.. code-block:: text
# equivalent to 'from hello import app'
$ gunicorn -w 4 'hello:app'
# equivalent to 'from hello import create_app; create_app()'
$ gunicorn -w 4 'hello:create_app()'
Starting gunicorn 20.1.0
Listening at: http://127.0.0.1:8000 (x)
Using worker: sync
Booting worker with pid: x
Booting worker with pid: x
Booting worker with pid: x
Booting worker with pid: x
The ``-w`` option specifies the number of processes to run; a starting
value could be ``CPU * 2``. The default is only 1 worker, which is
probably not what you want for the default worker type.
Logs for each request aren't shown by default, only worker info and
errors are shown. To show access logs on stdout, use the
``--access-logfile=-`` option.
Binding Externally
------------------
Gunicorn should not be run as root because it would cause your
application code to run as root, which is not secure. However, this
means it will not be possible to bind to port 80 or 443. Instead, a
reverse proxy such as :doc:`nginx` or :doc:`apache-httpd` should be used
in front of Gunicorn.
You can bind to all external IPs on a non-privileged port using the
``-b 0.0.0.0`` option. Don't do this when using a reverse proxy setup,
otherwise it will be possible to bypass the proxy.
.. code-block:: text
$ gunicorn -w 4 -b 0.0.0.0 'hello:create_app()'
Listening at: http://0.0.0.0:8000 (x)
``0.0.0.0`` is not a valid address to navigate to, you'd use a specific
IP address in your browser.
Async with gevent or eventlet
-----------------------------
The default sync worker is appropriate for many use cases. If you need
asynchronous support, Gunicorn provides workers using either `gevent`_
or `eventlet`_. This is not the same as Python's ``async/await``, or the
ASGI server spec. You must actually use gevent/eventlet in your own code
to see any benefit to using the workers.
When using either gevent or eventlet, greenlet>=1.0 is required,
otherwise context locals such as ``request`` will not work as expected.
When using PyPy, PyPy>=7.3.7 is required.
To use gevent:
.. code-block:: text
$ gunicorn -k gevent 'hello:create_app()'
Starting gunicorn 20.1.0
Listening at: http://127.0.0.1:8000 (x)
Using worker: gevent
Booting worker with pid: x
To use eventlet:
.. code-block:: text
$ gunicorn -k eventlet 'hello:create_app()'
Starting gunicorn 20.1.0
Listening at: http://127.0.0.1:8000 (x)
Using worker: eventlet
Booting worker with pid: x
.. _gevent: https://www.gevent.org/
.. _eventlet: https://eventlet.net/

71
docs/deployment/index.rst Normal file
View File

@ -0,0 +1,71 @@
Deploying to Production
=======================
After developing your application, you'll want to make it available
publicly to other users. When you're developing locally, you're probably
using the built-in development server, debugger, and reloader. These
should not be used in production. Instead, you should use a dedicated
WSGI server or hosting platform, some of which will be described here.
"Production" means "not development", which applies whether you're
serving your application publicly to millions of users or privately /
locally to a single user. **Do not use the development server when
deploying to production. It is intended for use only during local
development. It is not designed to be particularly secure, stable, or
efficient.**
Self-Hosted Options
-------------------
Werkzeug is a WSGI *application*. A WSGI *server* is used to run the
application, converting incoming HTTP requests to the standard WSGI
environ, and converting outgoing WSGI responses to HTTP responses.
The primary goal of these docs is to familiarize you with the concepts
involved in running a WSGI application using a production WSGI server
and HTTP server. There are many WSGI servers and HTTP servers, with many
configuration possibilities. The pages below discuss the most common
servers, and show the basics of running each one. The next section
discusses platforms that can manage this for you.
.. toctree::
:maxdepth: 1
gunicorn
waitress
mod_wsgi
uwsgi
gevent
eventlet
WSGI servers have HTTP servers built-in. However, a dedicated HTTP
server may be safer, more efficient, or more capable. Putting an HTTP
server in front of the WSGI server is called a "reverse proxy."
.. toctree::
:maxdepth: 1
proxy_fix
nginx
apache-httpd
This list is not exhaustive, and you should evaluate these and other
servers based on your application's needs. Different servers will have
different capabilities, configuration, and support.
Hosting Platforms
-----------------
There are many services available for hosting web applications without
needing to maintain your own server, networking, domain, etc. Some
services may have a free tier up to a certain time or bandwidth. Many of
these services use one of the WSGI servers described above, or a similar
interface.
You should evaluate services based on your application's needs.
Different services will have different capabilities, configuration,
pricing, and support.
You'll probably need to :doc:`proxy_fix` when using most hosting
platforms.

View File

@ -0,0 +1,94 @@
mod_wsgi
========
`mod_wsgi`_ is a WSGI server integrated with the `Apache httpd`_ server.
The modern `mod_wsgi-express`_ command makes it easy to configure and
start the server without needing to write Apache httpd configuration.
* Tightly integrated with Apache httpd.
* Supports Windows directly.
* Requires a compiler and the Apache development headers to install.
* Does not require a reverse proxy setup.
This page outlines the basics of running mod_wsgi-express, not the more
complex installation and configuration with httpd. Be sure to read the
`mod_wsgi-express`_, `mod_wsgi`_, and `Apache httpd`_ documentation to
understand what features are available.
.. _mod_wsgi-express: https://pypi.org/project/mod-wsgi/
.. _mod_wsgi: https://modwsgi.readthedocs.io/
.. _Apache httpd: https://httpd.apache.org/
Installing
----------
Installing mod_wsgi requires a compiler and the Apache server and
development headers installed. You will get an error if they are not.
How to install them depends on the OS and package manager that you use.
Create a virtualenv, install your application, then install
``mod_wsgi``.
.. code-block:: text
$ cd hello-app
$ python -m venv venv
$ . venv/bin/activate
$ pip install . # install your application
$ pip install mod_wsgi
Running
-------
The only argument to ``mod_wsgi-express`` specifies a script containing
your application, which must be called ``application``. You can
write a small script to import your app with this name, or to create it
if using the app factory pattern.
.. code-block:: python
:caption: ``wsgi.py``
from hello import app
application = app
.. code-block:: python
:caption: ``wsgi.py``
from hello import create_app
application = create_app()
Now run the ``mod_wsgi-express start-server`` command.
.. code-block:: text
$ mod_wsgi-express start-server wsgi.py --processes 4
The ``--processes`` option specifies the number of worker processes to
run; a starting value could be ``CPU * 2``.
Logs for each request aren't show in the terminal. If an error occurs,
its information is written to the error log file shown when starting the
server.
Binding Externally
------------------
Unlike the other WSGI servers in these docs, mod_wsgi can be run as
root to bind to privileged ports like 80 and 443. However, it must be
configured to drop permissions to a different user and group for the
worker processes.
For example, if you created a ``hello`` user and group, you should
install your virtualenv and application as that user, then tell
mod_wsgi to drop to that user after starting.
.. code-block:: text
$ sudo /home/hello/venv/bin/mod_wsgi-express start-server \
/home/hello/wsgi.py \
--user hello --group hello --port 80 --processes 4

87
docs/deployment/nginx.rst Normal file
View File

@ -0,0 +1,87 @@
nginx
=====
`nginx`_ is a fast, production level HTTP server. When serving your
application with one of the WSGI servers listed in :doc:`index`, it is
often good or necessary to put a dedicated HTTP server in front of it.
This "reverse proxy" can handle incoming requests, TLS, and other
security and performance concerns better than the WSGI server.
Nginx can be installed using your system package manager, or a pre-built
executable for Windows. Installing and running Nginx itself is outside
the scope of this doc. This page outlines the basics of configuring
Nginx to proxy your application. Be sure to read its documentation to
understand what features are available.
.. _nginx: https://nginx.org/
Domain Name
-----------
Acquiring and configuring a domain name is outside the scope of this
doc. In general, you will buy a domain name from a registrar, pay for
server space with a hosting provider, and then point your registrar
at the hosting provider's name servers.
To simulate this, you can also edit your ``hosts`` file, located at
``/etc/hosts`` on Linux. Add a line that associates a name with the
local IP.
Modern Linux systems may be configured to treat any domain name that
ends with ``.localhost`` like this without adding it to the ``hosts``
file.
.. code-block:: python
:caption: ``/etc/hosts``
127.0.0.1 hello.localhost
Configuration
-------------
The nginx configuration is located at ``/etc/nginx/nginx.conf`` on
Linux. It may be different depending on your operating system. Check the
docs and look for ``nginx.conf``.
Remove or comment out any existing ``server`` section. Add a ``server``
section and use the ``proxy_pass`` directive to point to the address the
WSGI server is listening on. We'll assume the WSGI server is listening
locally at ``http://127.0.0.1:8000``.
.. code-block:: nginx
:caption: ``/etc/nginx.conf``
server {
listen 80;
server_name _;
location / {
proxy_pass http://127.0.0.1:8000/;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Prefix /;
}
}
Then :doc:`proxy_fix` so that your application uses these headers.
Static Files
------------
If your application has static files such as JavaScript, CSS, and
images, it will be more efficient to let Nginx serve them directly
rather than going through the Python application.
Assuming the static files are expected to be available under the
``/static/`` URL, and are stored at ``/home/project/static/``, add the
following to the ``server`` block above.
.. code-block:: nginx
location /static {
alias /home/project/static;
}

View File

@ -0,0 +1,33 @@
Tell Werkzeug it is Behind a Proxy
==================================
When using a reverse proxy, or many Python hosting platforms, the proxy
will intercept and forward all external requests to the local WSGI
server.
From the WSGI server and application's perspectives, requests are now
coming from the HTTP server to the local address, rather than from
the remote address to the external server address.
HTTP servers should set ``X-Forwarded-`` headers to pass on the real
values to the application. The application can then be told to trust and
use those values by wrapping it with the
:doc:`../middleware/proxy_fix` middleware provided by Werkzeug.
This middleware should only be used if the application is actually
behind a proxy, and should be configured with the number of proxies that
are chained in front of it. Not all proxies set all the headers. Since
incoming headers can be faked, you must set how many proxies are setting
each header so the middleware knows what to trust.
.. code-block:: python
from werkzeug.middleware.proxy_fix import ProxyFix
app.wsgi_app = ProxyFix(
app.wsgi_app, x_for=1, x_proto=1, x_host=1, x_prefix=1
)
Remember, only apply this middleware if you are behind a proxy, and set
the correct number of proxies that set each header. It can be a security
issue if you get this configuration wrong.

145
docs/deployment/uwsgi.rst Normal file
View File

@ -0,0 +1,145 @@
uWSGI
=====
`uWSGI`_ is a fast, compiled server suite with extensive configuration
and capabilities beyond a basic server.
* It can be very performant due to being a compiled program.
* It is complex to configure beyond the basic application, and has so
many options that it can be difficult for beginners to understand.
* It does not support Windows (but does run on WSL).
* It requires a compiler to install in some cases.
This page outlines the basics of running uWSGI. Be sure to read its
documentation to understand what features are available.
.. _uWSGI: https://uwsgi-docs.readthedocs.io/en/latest/
Installing
----------
uWSGI has multiple ways to install it. The most straightforward is to
install the ``pyuwsgi`` package, which provides precompiled wheels for
common platforms. However, it does not provide SSL support, which can be
provided with a reverse proxy instead.
Create a virtualenv, install your application, then install ``pyuwsgi``.
.. code-block:: text
$ cd hello-app
$ python -m venv venv
$ . venv/bin/activate
$ pip install . # install your application
$ pip install pyuwsgi
If you have a compiler available, you can install the ``uwsgi`` package
instead. Or install the ``pyuwsgi`` package from sdist instead of wheel.
Either method will include SSL support.
.. code-block:: text
$ pip install uwsgi
# or
$ pip install --no-binary pyuwsgi pyuwsgi
Running
-------
The most basic way to run uWSGI is to tell it to start an HTTP server
and import your application.
.. code-block:: text
$ uwsgi --http 127.0.0.1:8000 --master -p 4 -w hello:app
*** Starting uWSGI 2.0.20 (64bit) on [x] ***
*** Operational MODE: preforking ***
mounting hello:app on /
spawned uWSGI master process (pid: x)
spawned uWSGI worker 1 (pid: x, cores: 1)
spawned uWSGI worker 2 (pid: x, cores: 1)
spawned uWSGI worker 3 (pid: x, cores: 1)
spawned uWSGI worker 4 (pid: x, cores: 1)
spawned uWSGI http 1 (pid: x)
If you're using the app factory pattern, you'll need to create a small
Python file to create the app, then point uWSGI at that.
.. code-block:: python
:caption: ``wsgi.py``
from hello import create_app
app = create_app()
.. code-block:: text
$ uwsgi --http 127.0.0.1:8000 --master -p 4 -w wsgi:app
The ``--http`` option starts an HTTP server at 127.0.0.1 port 8000. The
``--master`` option specifies the standard worker manager. The ``-p``
option starts 4 worker processes; a starting value could be ``CPU * 2``.
The ``-w`` option tells uWSGI how to import your application
Binding Externally
------------------
uWSGI should not be run as root with the configuration shown in this doc
because it would cause your application code to run as root, which is
not secure. However, this means it will not be possible to bind to port
80 or 443. Instead, a reverse proxy such as :doc:`nginx` or
:doc:`apache-httpd` should be used in front of uWSGI. It is possible to
run uWSGI as root securely, but that is beyond the scope of this doc.
uWSGI has optimized integration with `Nginx uWSGI`_ and
`Apache mod_proxy_uwsgi`_, and possibly other servers, instead of using
a standard HTTP proxy. That configuration is beyond the scope of this
doc, see the links for more information.
.. _Nginx uWSGI: https://uwsgi-docs.readthedocs.io/en/latest/Nginx.html
.. _Apache mod_proxy_uwsgi: https://uwsgi-docs.readthedocs.io/en/latest/Apache.html#mod-proxy-uwsgi
You can bind to all external IPs on a non-privileged port using the
``--http 0.0.0.0:8000`` option. Don't do this when using a reverse proxy
setup, otherwise it will be possible to bypass the proxy.
.. code-block:: text
$ uwsgi --http 0.0.0.0:8000 --master -p 4 -w wsgi:app
``0.0.0.0`` is not a valid address to navigate to, you'd use a specific
IP address in your browser.
Async with gevent
-----------------
The default sync worker is appropriate for many use cases. If you need
asynchronous support, uWSGI provides a `gevent`_ worker. This is not the
same as Python's ``async/await``, or the ASGI server spec. You must
actually use gevent in your own code to see any benefit to using the
worker.
When using gevent, greenlet>=1.0 is required, otherwise context locals
such as ``request`` will not work as expected. When using PyPy,
PyPy>=7.3.7 is required.
.. code-block:: text
$ uwsgi --http 127.0.0.1:8000 --master --gevent 100 -w wsgi:app
*** Starting uWSGI 2.0.20 (64bit) on [x] ***
*** Operational MODE: async ***
mounting hello:app on /
spawned uWSGI master process (pid: x)
spawned uWSGI worker 1 (pid: x, cores: 100)
spawned uWSGI http 1 (pid: x)
*** running gevent loop engine [addr:x] ***
.. _gevent: https://www.gevent.org/

View File

@ -0,0 +1,75 @@
Waitress
========
`Waitress`_ is a pure Python WSGI server.
* It is easy to configure.
* It supports Windows directly.
* It is easy to install as it does not require additional dependencies
or compilation.
* It does not support streaming requests, full request data is always
buffered.
* It uses a single process with multiple thread workers.
This page outlines the basics of running Waitress. Be sure to read its
documentation and ``waitress-serve --help`` to understand what features
are available.
.. _Waitress: https://docs.pylonsproject.org/projects/waitress/
Installing
----------
Create a virtualenv, install your application, then install
``waitress``.
.. code-block:: text
$ cd hello-app
$ python -m venv venv
$ . venv/bin/activate
$ pip install . # install your application
$ pip install waitress
Running
-------
The only required argument to ``waitress-serve`` tells it how to load
your application. The syntax is ``{module}:{app}``. ``module`` is
the dotted import name to the module with your application. ``app`` is
the variable with the application. If you're using the app factory
pattern, use ``--call {module}:{factory}`` instead.
.. code-block:: text
# equivalent to 'from hello import app'
$ waitress-serve hello:app --host 127.0.0.1
# equivalent to 'from hello import create_app; create_app()'
$ waitress-serve --call hello:create_app --host 127.0.0.1
Serving on http://127.0.0.1:8080
The ``--host`` option binds the server to local ``127.0.0.1`` only.
Logs for each request aren't shown, only errors are shown. Logging can
be configured through the Python interface instead of the command line.
Binding Externally
------------------
Waitress should not be run as root because it would cause your
application code to run as root, which is not secure. However, this
means it will not be possible to bind to port 80 or 443. Instead, a
reverse proxy such as :doc:`nginx` or :doc:`apache-httpd` should be used
in front of Waitress.
You can bind to all external IPs on a non-privileged port by not
specifying the ``--host`` option. Don't do this when using a revers
proxy setup, otherwise it will be possible to bypass the proxy.
``0.0.0.0`` is not a valid address to navigate to, you'd use a specific
IP address in your browser.

155
docs/exceptions.rst Normal file
View File

@ -0,0 +1,155 @@
===============
HTTP Exceptions
===============
.. automodule:: werkzeug.exceptions
Error Classes
=============
The following error classes exist in Werkzeug:
.. autoexception:: BadRequest
.. autoexception:: Unauthorized
.. autoexception:: Forbidden
.. autoexception:: NotFound
.. autoexception:: MethodNotAllowed
.. autoexception:: NotAcceptable
.. autoexception:: RequestTimeout
.. autoexception:: Conflict
.. autoexception:: Gone
.. autoexception:: LengthRequired
.. autoexception:: PreconditionFailed
.. autoexception:: RequestEntityTooLarge
.. autoexception:: RequestURITooLarge
.. autoexception:: UnsupportedMediaType
.. autoexception:: RequestedRangeNotSatisfiable
.. autoexception:: ExpectationFailed
.. autoexception:: ImATeapot
.. autoexception:: UnprocessableEntity
.. autoexception:: Locked
.. autoexception:: FailedDependency
.. autoexception:: PreconditionRequired
.. autoexception:: TooManyRequests
.. autoexception:: RequestHeaderFieldsTooLarge
.. autoexception:: UnavailableForLegalReasons
.. autoexception:: InternalServerError
:members:
.. autoexception:: NotImplemented
.. autoexception:: BadGateway
.. autoexception:: ServiceUnavailable
.. autoexception:: GatewayTimeout
.. autoexception:: HTTPVersionNotSupported
.. autoexception:: ClientDisconnected
.. autoexception:: SecurityError
Baseclass
=========
All the exceptions implement this common interface:
.. autoexception:: HTTPException
:members: get_response, __call__
Special HTTP Exceptions
=======================
Starting with Werkzeug 0.3 some of the builtin classes raise exceptions that
look like regular python exceptions (eg :exc:`KeyError`) but are
:exc:`BadRequest` HTTP exceptions at the same time. This decision was made
to simplify a common pattern where you want to abort if the client tampered
with the submitted form data in a way that the application can't recover
properly and should abort with ``400 BAD REQUEST``.
Assuming the application catches all HTTP exceptions and reacts to them
properly a view function could do the following safely and doesn't have to
check if the keys exist::
def new_post(request):
post = Post(title=request.form['title'], body=request.form['body'])
post.save()
return redirect(post.url)
If `title` or `body` are missing in the form, a special key error will be
raised which behaves like a :exc:`KeyError` but also a :exc:`BadRequest`
exception.
.. autoexception:: BadRequestKeyError
Simple Aborting
===============
Sometimes it's convenient to just raise an exception by the error code,
without importing the exception and looking up the name etc. For this
purpose there is the :func:`abort` function.
.. autofunction:: abort
If you want to use this functionality with custom exceptions you can
create an instance of the aborter class:
.. autoclass:: Aborter
Custom Errors
=============
As you can see from the list above not all status codes are available as
errors. Especially redirects and other non 200 status codes that do not
represent errors are missing. For redirects you can use the :func:`redirect`
function from the utilities.
If you want to add an error yourself you can subclass :exc:`HTTPException`::
from werkzeug.exceptions import HTTPException
class PaymentRequired(HTTPException):
code = 402
description = '<p>Payment required.</p>'
This is the minimal code you need for your own exception. If you want to
add more logic to the errors you can override the
:meth:`~HTTPException.get_description`, :meth:`~HTTPException.get_body`,
:meth:`~HTTPException.get_headers` and :meth:`~HTTPException.get_response`
methods. In any case you should have a look at the sourcecode of the
exceptions module.
You can override the default description in the constructor with the
``description`` parameter::
raise BadRequest(description='Request failed because X was not present')

169
docs/http.rst Normal file
View File

@ -0,0 +1,169 @@
==============
HTTP Utilities
==============
.. module:: werkzeug.http
Werkzeug provides a couple of functions to parse and generate HTTP headers
that are useful when implementing WSGI middlewares or whenever you are
operating on a lower level layer. All this functionality is also exposed
from request and response objects.
Datetime Functions
==================
These functions simplify working with times in an HTTP context. Werkzeug
produces timezone-aware :class:`~datetime.datetime` objects in UTC. When
passing datetime objects to Werkzeug, it assumes any naive datetime is
in UTC.
When comparing datetime values from Werkzeug, your own datetime objects
must also be timezone-aware, or you must make the values from Werkzeug
naive.
* ``dt = datetime.now(timezone.utc)`` gets the current time in UTC.
* ``dt = datetime(..., tzinfo=timezone.utc)`` creates a time in UTC.
* ``dt = dt.replace(tzinfo=timezone.utc)`` makes a naive object aware
by assuming it's in UTC.
* ``dt = dt.replace(tzinfo=None)`` makes an aware object naive.
.. autofunction:: parse_date
.. autofunction:: http_date
Header Parsing
==============
The following functions can be used to parse incoming HTTP headers.
Because Python does not provide data structures with the semantics required
by :rfc:`2616`, Werkzeug implements some custom data structures that are
:ref:`documented separately <http-datastructures>`.
.. autofunction:: parse_options_header
.. autofunction:: parse_set_header
.. autofunction:: parse_list_header
.. autofunction:: parse_dict_header
.. autofunction:: parse_accept_header(value, [class])
.. autofunction:: parse_cache_control_header
.. autofunction:: parse_authorization_header
.. autofunction:: parse_www_authenticate_header
.. autofunction:: parse_if_range_header
.. autofunction:: parse_range_header
.. autofunction:: parse_content_range_header
Header Utilities
================
The following utilities operate on HTTP headers well but do not parse
them. They are useful if you're dealing with conditional responses or if
you want to proxy arbitrary requests but want to remove WSGI-unsupported
hop-by-hop headers. Also there is a function to create HTTP header
strings from the parsed data.
.. autofunction:: is_entity_header
.. autofunction:: is_hop_by_hop_header
.. autofunction:: remove_entity_headers
.. autofunction:: remove_hop_by_hop_headers
.. autofunction:: is_byte_range_valid
.. autofunction:: quote_header_value
.. autofunction:: unquote_header_value
.. autofunction:: dump_header
Cookies
=======
.. autofunction:: parse_cookie
.. autofunction:: dump_cookie
Conditional Response Helpers
============================
For conditional responses the following functions might be useful:
.. autofunction:: parse_etags
.. autofunction:: quote_etag
.. autofunction:: unquote_etag
.. autofunction:: generate_etag
.. autofunction:: is_resource_modified
Constants
=========
.. data:: HTTP_STATUS_CODES
A dict of status code -> default status message pairs. This is used
by the wrappers and other places where an integer status code is expanded
to a string throughout Werkzeug.
Form Data Parsing
=================
.. module:: werkzeug.formparser
Werkzeug provides the form parsing functions separately from the request
object so that you can access form data from a plain WSGI environment.
The following formats are currently supported by the form data parser:
- `application/x-www-form-urlencoded`
- `multipart/form-data`
Nested multipart is not currently supported (Werkzeug 0.9), but it isn't used
by any of the modern web browsers.
Usage example:
>>> from io import BytesIO
>>> from werkzeug.formparser import parse_form_data
>>> data = (
... b'--foo\r\nContent-Disposition: form-data; name="test"\r\n'
... b"\r\nHello World!\r\n--foo--"
... )
>>> environ = {
... "wsgi.input": BytesIO(data),
... "CONTENT_LENGTH": str(len(data)),
... "CONTENT_TYPE": "multipart/form-data; boundary=foo",
... "REQUEST_METHOD": "POST",
... }
>>> stream, form, files = parse_form_data(environ)
>>> stream.read()
b''
>>> form['test']
'Hello World!'
>>> not files
True
Normally the WSGI environment is provided by the WSGI gateway with the
incoming data as part of it. If you want to generate such fake-WSGI
environments for unittesting you might want to use the
:func:`create_environ` function or the :class:`EnvironBuilder` instead.
.. autoclass:: FormDataParser
.. autofunction:: parse_form_data

78
docs/index.rst Normal file
View File

@ -0,0 +1,78 @@
Werkzeug
========
*werkzeug* German noun: "tool".
Etymology: *werk* ("work"), *zeug* ("stuff")
Werkzeug is a comprehensive `WSGI`_ web application library. It began as
a simple collection of various utilities for WSGI applications and has
become one of the most advanced WSGI utility libraries.
Werkzeug doesn't enforce any dependencies. It is up to the developer to
choose a template engine, database adapter, and even how to handle
requests.
.. _WSGI: https://wsgi.readthedocs.io/en/latest/
Getting Started
---------------
.. toctree::
:maxdepth: 2
installation
tutorial
levels
quickstart
Serving and Testing
-------------------
.. toctree::
:maxdepth: 2
serving
test
debug
Reference
---------
.. toctree::
:maxdepth: 2
wrappers
routing
wsgi
http
datastructures
utils
urls
local
middleware/index
exceptions
Deployment
----------
.. toctree::
:maxdepth: 3
deployment/index
Additional Information
----------------------
.. toctree::
:maxdepth: 2
terms
unicode
request_data
license
changes

111
docs/installation.rst Normal file
View File

@ -0,0 +1,111 @@
Installation
============
Python Version
--------------
We recommend using the latest version of Python. Werkzeug supports
Python 3.7 and newer.
Dependencies
------------
Werkzeug does not have any direct dependencies.
Optional dependencies
~~~~~~~~~~~~~~~~~~~~~
These distributions will not be installed automatically. Werkzeug will
detect and use them if you install them.
* `Colorama`_ provides request log highlighting when using the
development server on Windows. This works automatically on other
systems.
* `Watchdog`_ provides a faster, more efficient reloader for the
development server.
.. _Colorama: https://pypi.org/project/colorama/
.. _Watchdog: https://pypi.org/project/watchdog/
greenlet
~~~~~~~~
You may choose to use gevent or eventlet with your application. In this
case, greenlet>=1.0 is required. When using PyPy, PyPy>=7.3.7 is
required.
These are not minimum supported versions, they only indicate the first
versions that added necessary features. You should use the latest
versions of each.
Virtual environments
--------------------
Use a virtual environment to manage the dependencies for your project,
both in development and in production.
What problem does a virtual environment solve? The more Python
projects you have, the more likely it is that you need to work with
different versions of Python libraries, or even Python itself. Newer
versions of libraries for one project can break compatibility in
another project.
Virtual environments are independent groups of Python libraries, one for
each project. Packages installed for one project will not affect other
projects or the operating system's packages.
Python comes bundled with the :mod:`venv` module to create virtual
environments.
Create an environment
~~~~~~~~~~~~~~~~~~~~~
Create a project folder and a :file:`venv` folder within:
.. code-block:: sh
mkdir myproject
cd myproject
python3 -m venv venv
On Windows:
.. code-block:: bat
py -3 -m venv venv
Activate the environment
~~~~~~~~~~~~~~~~~~~~~~~~
Before you work on your project, activate the corresponding environment:
.. code-block:: sh
. venv/bin/activate
On Windows:
.. code-block:: bat
venv\Scripts\activate
Your shell prompt will change to show the name of the activated
environment.
Install Werkzeug
----------------
Within the activated environment, use the following command to install
Werkzeug:
.. code-block:: sh
pip install Werkzeug

72
docs/levels.rst Normal file
View File

@ -0,0 +1,72 @@
==========
API Levels
==========
.. currentmodule:: werkzeug
Werkzeug is intended to be a utility rather than a framework. Because of that
the user-friendly API is separated from the lower-level API so that Werkzeug
can easily be used to extend another system.
All the functionality the :class:`Request` and :class:`Response` objects (aka
the "wrappers") provide is also available in small utility functions.
Example
=======
This example implements a small `Hello World` application that greets the
user with the name entered.
.. code-block:: python
from markupsafe import escape
from werkzeug.wrappers import Request, Response
@Request.application
def hello_world(request):
result = ['<title>Greeter</title>']
if request.method == 'POST':
result.append(f"<h1>Hello {escape(request.form['name'])}!</h1>")
result.append('''
<form action="" method="post">
<p>Name: <input type="text" name="name" size="20">
<input type="submit" value="Greet me">
</form>
''')
return Response(''.join(result), mimetype='text/html')
Alternatively the same application could be used without request and response
objects but by taking advantage of the parsing functions werkzeug provides::
from markupsafe import escape
from werkzeug.formparser import parse_form_data
def hello_world(environ, start_response):
result = ['<title>Greeter</title>']
if environ['REQUEST_METHOD'] == 'POST':
form = parse_form_data(environ)[1]
result.append(f"<h1>Hello {escape(form['name'])}!</h1>")
result.append('''
<form action="" method="post">
<p>Name: <input type="text" name="name" size="20">
<input type="submit" value="Greet me">
</form>
''')
start_response('200 OK', [('Content-Type', 'text/html; charset=utf-8')])
return [''.join(result).encode('utf-8')]
High or Low?
============
Usually you want to use the high-level layer (the request and response
objects). But there are situations where this might not be what you want.
For example you might be maintaining code for an application written in
Django or another framework and you have to parse HTTP headers. You can
utilize Werkzeug for that by accessing the lower-level HTTP header parsing
functions.
Another situation where the low level parsing functions can be useful are
custom WSGI frameworks, unit-testing or modernizing an old CGI/mod_python
application to WSGI as well as WSGI middlewares where you want to keep the
overhead low.

4
docs/license.rst Normal file
View File

@ -0,0 +1,4 @@
BSD-3-Clause License
====================
.. include:: ../LICENSE.rst

110
docs/local.rst Normal file
View File

@ -0,0 +1,110 @@
Context Locals
==============
.. module:: werkzeug.local
You may find that you have some data during each request that you want
to use across functions. Instead of passing these as arguments between
every function, you may want to access them as global data. However,
using global variables in Python web applications is not thread safe;
different workers might interfere with each others' data.
Instead of storing common data during a request using global variables,
you must use context-local variables instead. A context local is
defined/imported globally, but the data it contains is specific to the
current thread, asyncio task, or greenlet. You won't accidentally get
or overwrite another worker's data.
The current approach for storing per-context data in Python is the
:class:`contextvars` module. Context vars store data per thread, async
task, or greenlet. This replaces the older :class:`threading.local`
which only handled threads.
Werkzeug provides wrappers around :class:`~contextvars.ContextVar` to
make it easier to work with.
Proxy Objects
=============
:class:`LocalProxy` allows treating a context var as an object directly
instead of needing to use and check
:meth:`ContextVar.get() <contextvars.ContextVar.get>`. If the context
var is set, the local proxy will look and behave like the object the var
is set to. If it's not set, a ``RuntimeError`` is raised for most
operations.
.. code-block:: python
from contextvars import ContextVar
from werkzeug.local import LocalProxy
_request_var = ContextVar("request")
request = LocalProxy(_request_var)
from werkzeug.wrappers import Request
@Request.application
def app(r):
_request_var.set(r)
check_auth()
...
from werkzeug.exceptions import Unauthorized
def check_auth():
if request.form["username"] != "admin":
raise Unauthorized()
Accessing ``request`` will point to the specific request that each
server worker is handling. You can treat ``request`` just like an actual
``Request`` object.
``bool(proxy)`` will always return ``False`` if the var is not set. If
you need access to the object directly instead of the proxy, you can get
it with the :meth:`~LocalProxy._get_current_object` method.
.. autoclass:: LocalProxy
:members: _get_current_object
Stacks and Namespaces
=====================
:class:`~contextvars.ContextVar` stores one value at a time. You may
find that you need to store a stack of items, or a namespace with
multiple attributes. A list or dict can be used for these, but using
them as context var values requires some extra care. Werkzeug provides
:class:`LocalStack` which wraps a list, and :class:`Local` which wraps a
dict.
There is some amount of performance penalty associated with these
objects. Because lists and dicts are mutable, :class:`LocalStack` and
:class:`Local` need to do extra work to ensure data isn't shared between
nested contexts. If possible, design your application to use
:class:`LocalProxy` around a context var directly.
.. autoclass:: LocalStack
:members: push, pop, top, __call__
.. autoclass:: Local
:members: __call__
Releasing Data
==============
A previous implementation of ``Local`` used internal data structures
which could not be cleaned up automatically when each context ended.
Instead, the following utilities could be used to release the data.
.. warning::
This should not be needed with the modern implementation, as the
data in context vars is automatically managed by Python. It is kept
for compatibility for now, but may be removed in the future.
.. autoclass:: LocalManager
:members: cleanup, make_middleware, middleware
.. autofunction:: release_local

35
docs/make.bat Normal file
View File

@ -0,0 +1,35 @@
@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

View File

@ -0,0 +1 @@
.. automodule:: werkzeug.middleware.dispatcher

View File

@ -0,0 +1 @@
.. automodule:: werkzeug.middleware.http_proxy

View File

@ -0,0 +1 @@
.. automodule:: werkzeug.middleware

1
docs/middleware/lint.rst Normal file
View File

@ -0,0 +1 @@
.. automodule:: werkzeug.middleware.lint

View File

@ -0,0 +1 @@
.. automodule:: werkzeug.middleware.profiler

View File

@ -0,0 +1 @@
.. automodule:: werkzeug.middleware.proxy_fix

View File

@ -0,0 +1 @@
.. automodule:: werkzeug.middleware.shared_data

301
docs/quickstart.rst Normal file
View File

@ -0,0 +1,301 @@
Quickstart
==========
.. currentmodule:: werkzeug
This part of the documentation shows how to use the most important parts of
Werkzeug. It's intended as a starting point for developers with basic
understanding of :pep:`3333` (WSGI) and :rfc:`2616` (HTTP).
WSGI Environment
================
The WSGI environment contains all the information the user request transmits
to the application. It is passed to the WSGI application but you can also
create a WSGI environ dict using the :func:`create_environ` helper:
>>> from werkzeug.test import create_environ
>>> environ = create_environ('/foo', 'http://localhost:8080/')
Now we have an environment to play around:
>>> environ['PATH_INFO']
'/foo'
>>> environ['SCRIPT_NAME']
''
>>> environ['SERVER_NAME']
'localhost'
Usually nobody wants to work with the environ directly because it uses a
confusing string encoding scheme, and it does not provide any way to
access the form data besides parsing that data by hand.
Enter Request
=============
For access to the request data the :class:`Request` object is much more fun.
It wraps the `environ` and provides a read-only access to the data from
there:
>>> from werkzeug.wrappers import Request
>>> request = Request(environ)
Now you can access the important variables and Werkzeug will parse them
for you and decode them where it makes sense. The default charset for
requests is set to `utf-8` but you can change that by subclassing
:class:`Request`.
>>> request.path
'/foo'
>>> request.script_root
''
>>> request.host
'localhost:8080'
>>> request.url
'http://localhost:8080/foo'
We can also find out which HTTP method was used for the request:
>>> request.method
'GET'
This way we can also access URL arguments (the query string) and data that
was transmitted in a POST/PUT request.
For testing purposes we can create a request object from supplied data
using the :meth:`~Request.from_values` method:
>>> from io import StringIO
>>> data = "name=this+is+encoded+form+data&another_key=another+one"
>>> request = Request.from_values(query_string='foo=bar&blah=blafasel',
... content_length=len(data), input_stream=StringIO(data),
... content_type='application/x-www-form-urlencoded',
... method='POST')
...
>>> request.method
'POST'
Now we can access the URL parameters easily:
>>> request.args.keys()
['blah', 'foo']
>>> request.args['blah']
'blafasel'
Same for the supplied form data:
>>> request.form['name']
'this is encoded form data'
Handling for uploaded files is not much harder as you can see from this
example::
def store_file(request):
file = request.files.get('my_file')
if file:
file.save('/where/to/store/the/file.txt')
else:
handle_the_error()
The files are represented as :class:`FileStorage` objects which provide
some common operations to work with them.
Request headers can be accessed by using the :class:`~Request.headers`
attribute:
>>> request.headers['Content-Length']
'54'
>>> request.headers['Content-Type']
'application/x-www-form-urlencoded'
The keys for the headers are of course case insensitive.
Header Parsing
==============
There is more. Werkzeug provides convenient access to often used HTTP headers
and other request data.
Let's create a request object with all the data a typical web browser transmits
so that we can play with it:
>>> environ = create_environ()
>>> environ.update(
... HTTP_ACCEPT='text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
... HTTP_ACCEPT_LANGUAGE='de-at,en-us;q=0.8,en;q=0.5',
... HTTP_ACCEPT_ENCODING='gzip,deflate',
... HTTP_ACCEPT_CHARSET='ISO-8859-1,utf-8;q=0.7,*;q=0.7',
... HTTP_IF_MODIFIED_SINCE='Fri, 20 Feb 2009 10:10:25 GMT',
... HTTP_IF_NONE_MATCH='"e51c9-1e5d-46356dc86c640"',
... HTTP_CACHE_CONTROL='max-age=0'
... )
...
>>> request = Request(environ)
With the accept header the browser informs the web application what
mimetypes it can handle and how well. All accept headers are sorted by
the quality, the best item being the first:
>>> request.accept_mimetypes.best
'text/html'
>>> 'application/xhtml+xml' in request.accept_mimetypes
True
>>> print request.accept_mimetypes["application/json"]
0.8
The same works for languages:
>>> request.accept_languages.best
'de-at'
>>> request.accept_languages.values()
['de-at', 'en-us', 'en']
And of course encodings and charsets:
>>> 'gzip' in request.accept_encodings
True
>>> request.accept_charsets.best
'ISO-8859-1'
>>> 'utf-8' in request.accept_charsets
True
Normalization is available, so you can safely use alternative forms to perform
containment checking:
>>> 'UTF8' in request.accept_charsets
True
>>> 'de_AT' in request.accept_languages
True
E-tags and other conditional headers are available in parsed form as well:
>>> request.if_modified_since
datetime.datetime(2009, 2, 20, 10, 10, 25, tzinfo=datetime.timezone.utc)
>>> request.if_none_match
<ETags '"e51c9-1e5d-46356dc86c640"'>
>>> request.cache_control
<RequestCacheControl 'max-age=0'>
>>> request.cache_control.max_age
0
>>> 'e51c9-1e5d-46356dc86c640' in request.if_none_match
True
Responses
=========
Response objects are the opposite of request objects. They are used to send
data back to the client. In reality, response objects are nothing more than
glorified WSGI applications.
So what you are doing is not *returning* the response objects from your WSGI
application but *calling* it as WSGI application inside your WSGI application
and returning the return value of that call.
So imagine your standard WSGI "Hello World" application::
def application(environ, start_response):
start_response('200 OK', [('Content-Type', 'text/plain')])
return ['Hello World!']
With response objects it would look like this::
from werkzeug.wrappers import Response
def application(environ, start_response):
response = Response('Hello World!')
return response(environ, start_response)
Also, unlike request objects, response objects are designed to be modified.
So here is what you can do with them:
>>> from werkzeug.wrappers import Response
>>> response = Response("Hello World!")
>>> response.headers['content-type']
'text/plain; charset=utf-8'
>>> response.data
'Hello World!'
>>> response.headers['content-length'] = len(response.data)
You can modify the status of the response in the same way. Either just the
code or provide a message as well:
>>> response.status
'200 OK'
>>> response.status = '404 Not Found'
>>> response.status_code
404
>>> response.status_code = 400
>>> response.status
'400 BAD REQUEST'
As you can see attributes work in both directions. So you can set both
:attr:`~Response.status` and :attr:`~Response.status_code` and the
change will be reflected to the other.
Also common headers are exposed as attributes or with methods to set /
retrieve them:
>>> response.content_length
12
>>> from datetime import datetime, timezone
>>> response.date = datetime(2009, 2, 20, 17, 42, 51, tzinfo=timezone.utc)
>>> response.headers['Date']
'Fri, 20 Feb 2009 17:42:51 GMT'
Because etags can be weak or strong there are methods to set them:
>>> response.set_etag("12345-abcd")
>>> response.headers['etag']
'"12345-abcd"'
>>> response.get_etag()
('12345-abcd', False)
>>> response.set_etag("12345-abcd", weak=True)
>>> response.get_etag()
('12345-abcd', True)
Some headers are available as mutable structures. For example most
of the `Content-` headers are sets of values:
>>> response.content_language.add('en-us')
>>> response.content_language.add('en')
>>> response.headers['Content-Language']
'en-us, en'
Also here this works in both directions:
>>> response.headers['Content-Language'] = 'de-AT, de'
>>> response.content_language
HeaderSet(['de-AT', 'de'])
Authentication headers can be set that way as well:
>>> response.www_authenticate.set_basic("My protected resource")
>>> response.headers['www-authenticate']
'Basic realm="My protected resource"'
Cookies can be set as well:
>>> response.set_cookie('name', 'value')
>>> response.headers['Set-Cookie']
'name=value; Path=/'
>>> response.set_cookie('name2', 'value2')
If headers appear multiple times you can use the :meth:`~Headers.getlist`
method to get all values for a header:
>>> response.headers.getlist('Set-Cookie')
['name=value; Path=/', 'name2=value2; Path=/']
Finally if you have set all the conditional values, you can make the
response conditional against a request. Which means that if the request
can assure that it has the information already, no data besides the headers
is sent over the network which saves traffic. For that you should set at
least an etag (which is used for comparison) and the date header and then
call :class:`~Request.make_conditional` with the request object.
The response is modified accordingly (status code changed, response body
removed, entity headers removed etc.)

100
docs/request_data.rst Normal file
View File

@ -0,0 +1,100 @@
Dealing with Request Data
=========================
.. currentmodule:: werkzeug
The most important rule about web development is "Do not trust the user".
This is especially true for incoming request data on the input stream.
With WSGI this is actually a bit harder than you would expect. Because
of that Werkzeug wraps the request stream for you to save you from the
most prominent problems with it.
Missing EOF Marker on Input Stream
----------------------------------
The input stream has no end-of-file marker. If you would call the
:meth:`~file.read` method on the `wsgi.input` stream you would cause your
application to hang on conforming servers. This is actually intentional
however painful. Werkzeug solves that problem by wrapping the input
stream in a special :class:`LimitedStream`. The input stream is exposed
on the request objects as :attr:`~Request.stream`. This one is either
an empty stream (if the form data was parsed) or a limited stream with
the contents of the input stream.
When does Werkzeug Parse?
-------------------------
Werkzeug parses the incoming data under the following situations:
- you access either :attr:`~Request.form`, :attr:`~Request.files`,
or :attr:`~Request.stream` and the request method was
`POST` or `PUT`.
- if you call :func:`parse_form_data`.
These calls are not interchangeable. If you invoke :func:`parse_form_data`
you must not use the request object or at least not the attributes that
trigger the parsing process.
This is also true if you read from the `wsgi.input` stream before the
parsing.
**General rule:** Leave the WSGI input stream alone. Especially in
WSGI middlewares. Use either the parsing functions or the request
object. Do not mix multiple WSGI utility libraries for form data
parsing or anything else that works on the input stream.
How does it Parse?
------------------
The standard Werkzeug parsing behavior handles three cases:
- input content type was `multipart/form-data`. In this situation the
:class:`~Request.stream` will be empty and
:class:`~Request.form` will contain the regular `POST` / `PUT`
data, :class:`~Request.files` will contain the uploaded
files as :class:`FileStorage` objects.
- input content type was `application/x-www-form-urlencoded`. Then the
:class:`~Request.stream` will be empty and
:class:`~Request.form` will contain the regular `POST` / `PUT`
data and :class:`~Request.files` will be empty.
- the input content type was neither of them, :class:`~Request.stream`
points to a :class:`LimitedStream` with the input data for further
processing.
Special note on the :attr:`~Request.get_data` method: Calling this
loads the full request data into memory. This is only safe to do if the
:attr:`~Request.max_content_length` is set. Also you can *either*
read the stream *or* call :meth:`~Request.get_data`.
Limiting Request Data
---------------------
To avoid being the victim of a DDOS attack you can set the maximum
accepted content length and request field sizes. The :class:`Request`
class has two attributes for that: :attr:`~Request.max_content_length`
and :attr:`~Request.max_form_memory_size`.
The first one can be used to limit the total content length. For example
by setting it to ``1024 * 1024 * 16`` the request won't accept more than
16MB of transmitted data.
Because certain data can't be moved to the hard disk (regular post data)
whereas temporary files can, there is a second limit you can set. The
:attr:`~Request.max_form_memory_size` limits the size of `POST`
transmitted form data. By setting it to ``1024 * 1024 * 2`` you can make
sure that all in memory-stored fields are not more than 2MB in size.
This however does *not* affect in-memory stored files if the
`stream_factory` used returns a in-memory file.
How to extend Parsing?
----------------------
Modern web applications transmit a lot more than multipart form data or
url encoded data. To extend the capabilities, subclass :class:`Request`
or :class:`Request` and add or extend methods.

307
docs/routing.rst Normal file
View File

@ -0,0 +1,307 @@
===========
URL Routing
===========
.. module:: werkzeug.routing
When it comes to combining multiple controller or view functions (however
you want to call them), you need a dispatcher. A simple way would be
applying regular expression tests on ``PATH_INFO`` and call registered
callback functions that return the value.
Werkzeug provides a much more powerful system, similar to `Routes`_. All the
objects mentioned on this page must be imported from :mod:`werkzeug.routing`, not
from :mod:`werkzeug`!
.. _Routes: https://routes.readthedocs.io/en/latest/
Quickstart
==========
Here is a simple example which could be the URL definition for a blog::
from werkzeug.routing import Map, Rule, NotFound, RequestRedirect
url_map = Map([
Rule('/', endpoint='blog/index'),
Rule('/<int:year>/', endpoint='blog/archive'),
Rule('/<int:year>/<int:month>/', endpoint='blog/archive'),
Rule('/<int:year>/<int:month>/<int:day>/', endpoint='blog/archive'),
Rule('/<int:year>/<int:month>/<int:day>/<slug>',
endpoint='blog/show_post'),
Rule('/about', endpoint='blog/about_me'),
Rule('/feeds/', endpoint='blog/feeds'),
Rule('/feeds/<feed_name>.rss', endpoint='blog/show_feed')
])
def application(environ, start_response):
urls = url_map.bind_to_environ(environ)
try:
endpoint, args = urls.match()
except HTTPException, e:
return e(environ, start_response)
start_response('200 OK', [('Content-Type', 'text/plain')])
return [f'Rule points to {endpoint!r} with arguments {args!r}'.encode()]
So what does that do? First of all we create a new :class:`Map` which stores
a bunch of URL rules. Then we pass it a list of :class:`Rule` objects.
Each :class:`Rule` object is instantiated with a string that represents a rule
and an endpoint which will be the alias for what view the rule represents.
Multiple rules can have the same endpoint, but should have different arguments
to allow URL construction.
The format for the URL rules is straightforward, but explained in detail below.
Inside the WSGI application we bind the url_map to the current request which will
return a new :class:`MapAdapter`. This url_map adapter can then be used to match
or build domains for the current request.
The :meth:`MapAdapter.match` method can then either return a tuple in the form
``(endpoint, args)`` or raise one of the three exceptions
:exc:`~werkzeug.exceptions.NotFound`, :exc:`~werkzeug.exceptions.MethodNotAllowed`,
or :exc:`~werkzeug.exceptions.RequestRedirect`. For more details about those
exceptions have a look at the documentation of the :meth:`MapAdapter.match` method.
Rule Format
===========
Rule strings are URL paths with placeholders for variable parts in the
format ``<converter(arguments):name>``. ``converter`` and ``arguments``
(with parentheses) are optional. If no converter is given, the
``default`` converter is used (``string`` by default). The available
converters are discussed below.
Rules that end with a slash are "branches", others are "leaves". If
``strict_slashes`` is enabled (the default), visiting a branch URL
without a trailing slash will redirect to the URL with a slash appended.
Many HTTP servers merge consecutive slashes into one when receiving
requests. If ``merge_slashes`` is enabled (the default), rules will
merge slashes in non-variable parts when matching and building. Visiting
a URL with consecutive slashes will redirect to the URL with slashes
merged. If you want to disable ``merge_slashes`` for a :class:`Rule` or
:class:`Map`, you'll also need to configure your web server
appropriately.
Built-in Converters
===================
Converters for common types of URL variables are built-in. The available
converters can be overridden or extended through :attr:`Map.converters`.
.. autoclass:: UnicodeConverter
.. autoclass:: PathConverter
.. autoclass:: AnyConverter
.. autoclass:: IntegerConverter
.. autoclass:: FloatConverter
.. autoclass:: UUIDConverter
Maps, Rules and Adapters
========================
.. autoclass:: Map
:members:
.. attribute:: converters
The dictionary of converters. This can be modified after the class
was created, but will only affect rules added after the
modification. If the rules are defined with the list passed to the
class, the `converters` parameter to the constructor has to be used
instead.
.. autoclass:: MapAdapter
:members:
.. autoclass:: Rule
:members: empty
Matchers
========
.. autoclass:: StateMachineMatcher
:members:
Rule Factories
==============
.. autoclass:: RuleFactory
:members: get_rules
.. autoclass:: Subdomain
.. autoclass:: Submount
.. autoclass:: EndpointPrefix
Rule Templates
==============
.. autoclass:: RuleTemplate
Custom Converters
=================
You can add custom converters that add behaviors not provided by the
built-in converters. To make a custom converter, subclass
:class:`BaseConverter` then pass the new class to the :class:`Map`
``converters`` parameter, or add it to
:attr:`url_map.converters <Map.converters>`.
The converter should have a ``regex`` attribute with a regular
expression to match with. If the converter can take arguments in a URL
rule, it should accept them in its ``__init__`` method. The entire
regex expression will be matched as a group and used as the value for
conversion.
If a custom converter can match a forward slash, ``/``, it should have
the attribute ``part_isolating`` set to ``False``. This will ensure
that rules using the custom converter are correctly matched.
It can implement a ``to_python`` method to convert the matched string to
some other object. This can also do extra validation that wasn't
possible with the ``regex`` attribute, and should raise a
:exc:`werkzeug.routing.ValidationError` in that case. Raising any other
errors will cause a 500 error.
It can implement a ``to_url`` method to convert a Python object to a
string when building a URL. Any error raised here will be converted to a
:exc:`werkzeug.routing.BuildError` and eventually cause a 500 error.
This example implements a ``BooleanConverter`` that will match the
strings ``"yes"``, ``"no"``, and ``"maybe"``, returning a random value
for ``"maybe"``. ::
from random import randrange
from werkzeug.routing import BaseConverter, ValidationError
class BooleanConverter(BaseConverter):
regex = r"(?:yes|no|maybe)"
def __init__(self, url_map, maybe=False):
super().__init__(url_map)
self.maybe = maybe
def to_python(self, value):
if value == "maybe":
if self.maybe:
return not randrange(2)
raise ValidationError
return value == 'yes'
def to_url(self, value):
return "yes" if value else "no"
from werkzeug.routing import Map, Rule
url_map = Map([
Rule("/vote/<bool:werkzeug_rocks>", endpoint="vote"),
Rule("/guess/<bool(maybe=True):foo>", endpoint="guess")
], converters={'bool': BooleanConverter})
If you want to change the default converter, assign a different
converter to the ``"default"`` key.
Host Matching
=============
.. versionadded:: 0.7
Starting with Werkzeug 0.7 it's also possible to do matching on the whole
host names instead of just the subdomain. To enable this feature you need
to pass ``host_matching=True`` to the :class:`Map` constructor and provide
the `host` argument to all routes::
url_map = Map([
Rule('/', endpoint='www_index', host='www.example.com'),
Rule('/', endpoint='help_index', host='help.example.com')
], host_matching=True)
Variable parts are of course also possible in the host section::
url_map = Map([
Rule('/', endpoint='www_index', host='www.example.com'),
Rule('/', endpoint='user_index', host='<user>.example.com')
], host_matching=True)
WebSockets
==========
.. versionadded:: 1.0
If a :class:`Rule` is created with ``websocket=True``, it will only
match if the :class:`Map` is bound to a request with a ``url_scheme`` of
``ws`` or ``wss``.
.. note::
Werkzeug has no further WebSocket support beyond routing. This
functionality is mostly of use to ASGI projects.
.. code-block:: python
url_map = Map([
Rule("/ws", endpoint="comm", websocket=True),
])
adapter = map.bind("example.org", "/ws", url_scheme="ws")
assert adapter.match() == ("comm", {})
If the only match is a WebSocket rule and the bind is HTTP (or the
only match is HTTP and the bind is WebSocket) a
:exc:`WebsocketMismatch` (derives from
:exc:`~werkzeug.exceptions.BadRequest`) exception is raised.
As WebSocket URLs have a different scheme, rules are always built with a
scheme and host, ``force_external=True`` is implied.
.. code-block:: python
url = adapter.build("comm")
assert url == "ws://example.org/ws"
State Machine Matching
======================
The default matching algorithm uses a state machine that transitions
between parts of the request path to find a match. To understand how
this works consider this rule::
/resource/<id>
Firstly this rule is decomposed into two ``RulePart``. The first is a
static part with a content equal to ``resource``, the second is
dynamic and requires a regex match to ``[^/]+``.
A state machine is then created with an initial state that represents
the rule's first ``/``. This initial state has a single, static
transition to the next state which represents the rule's second
``/``. This second state has a single dynamic transition to the final
state which includes the rule.
To match a path the matcher starts and the initial state and follows
transitions that work. Clearly a trial path of ``/resource/2`` has the
parts ``""``, ``resource``, and ``2`` which match the transitions and
hence a rule will match. Whereas ``/other/2`` will not match as there
is no transition for the ``other`` part from the initial state.
The only diversion from this rule is if a ``RulePart`` is not
part-isolating i.e. it will match ``/``. In this case the ``RulePart``
is considered final and represents a transition that must include all
the subsequent parts of the trial path.

267
docs/serving.rst Normal file
View File

@ -0,0 +1,267 @@
=========================
Serving WSGI Applications
=========================
.. module:: werkzeug.serving
There are many ways to serve a WSGI application. While you're developing it,
you usually don't want to have a full-blown webserver like Apache up and
running, but instead a simple standalone one. Because of that Werkzeug comes
with a builtin development server.
The easiest way is creating a small ``start-myproject.py`` file that runs the
application using the builtin server::
from werkzeug.serving import run_simple
from myproject import make_app
app = make_app(...)
run_simple('localhost', 8080, app, use_reloader=True)
You can also pass it the `extra_files` keyword argument with a list of
additional files (like configuration files) you want to observe.
.. autofunction:: run_simple
.. autofunction:: is_running_from_reloader
.. autofunction:: make_ssl_devcert
.. admonition:: Information
The development server is not intended to be used on production systems.
It was designed especially for development purposes and performs poorly
under high load. For deployment setups have a look at the
:doc:`/deployment/index` pages.
.. _reloader:
Reloader
--------
.. versionchanged:: 0.10
The Werkzeug reloader constantly monitors modules and paths of your web
application, and restarts the server if any of the observed files change.
Since version 0.10, there are two backends the reloader supports: ``stat`` and
``watchdog``.
- The default ``stat`` backend simply checks the ``mtime`` of all files in a
regular interval. This is sufficient for most cases, however, it is known to
drain a laptop's battery.
- The ``watchdog`` backend uses filesystem events, and is much faster than
``stat``. It requires the `watchdog <https://pypi.org/project/watchdog/>`_
module to be installed. The recommended way to achieve this is to add
``Werkzeug[watchdog]`` to your requirements file.
If ``watchdog`` is installed and available it will automatically be used
instead of the builtin ``stat`` reloader.
To switch between the backends you can use the `reloader_type` parameter of the
:func:`run_simple` function. ``'stat'`` sets it to the default stat based
polling and ``'watchdog'`` forces it to the watchdog backend.
.. note::
Some edge cases, like modules that failed to import correctly, are not
handled by the stat reloader for performance reasons. The watchdog reloader
monitors such files too.
Colored Logging
---------------
The development server highlights the request logs in different colors
based on the status code. On Windows, `Colorama`_ must be installed as
well to enable this.
.. _Colorama: https://pypi.org/project/colorama/
Virtual Hosts
-------------
Many web applications utilize multiple subdomains. This can be a bit tricky
to simulate locally. Fortunately there is the `hosts file`_ that can be used
to assign the local computer multiple names.
This allows you to call your local computer `yourapplication.local` and
`api.yourapplication.local` (or anything else) in addition to `localhost`.
You can find the hosts file on the following location:
=============== ==============================================
Windows ``%SystemRoot%\system32\drivers\etc\hosts``
Linux / OS X ``/etc/hosts``
=============== ==============================================
You can open the file with your favorite text editor and add a new name after
`localhost`::
127.0.0.1 localhost yourapplication.local api.yourapplication.local
Save the changes and after a while you should be able to access the
development server on these host names as well. You can use the
:doc:`/routing` system to dispatch between different hosts or parse
:attr:`request.host` yourself.
Shutting Down The Server
------------------------
In some cases it can be useful to shut down a server after handling a
request. For example, a local command line tool that needs OAuth
authentication could temporarily start a server to listen for a
response, record the user's token, then stop the server.
One method to do this could be to start a server in a
:mod:`multiprocessing` process, then terminate the process after a value
is passed back to the parent.
.. code-block:: python
import multiprocessing
from werkzeug import Request, Response, run_simple
def get_token(q: multiprocessing.Queue) -> None:
@Request.application
def app(request: Request) -> Response:
q.put(request.args["token"])
return Response("", 204)
run_simple("localhost", 5000, app)
if __name__ == "__main__":
q = multiprocessing.Queue()
p = multiprocessing.Process(target=get_token, args=(q,))
p.start()
print("waiting")
token = q.get(block=True)
p.terminate()
print(token)
That example uses Werkzeug's development server, but any production
server that can be started as a Python process could use the same
technique and should be preferred for security. Another method could be
to start a :mod:`subprocess` process and send the value back over
``stdout``.
Troubleshooting
---------------
On operating systems that support ipv6 and have it configured such as modern
Linux systems, OS X 10.4 or higher as well as Windows Vista some browsers can
be painfully slow if accessing your local server. The reason for this is that
sometimes "localhost" is configured to be available on both ipv4 and ipv6 sockets
and some browsers will try to access ipv6 first and then ipv4.
At the current time the integrated webserver does not support ipv6 and ipv4 at
the same time and for better portability ipv4 is the default.
If you notice that the web browser takes ages to load the page there are two ways
around this issue. If you don't need ipv6 support you can disable the ipv6 entry
in the `hosts file`_ by removing this line::
::1 localhost
Alternatively you can also disable ipv6 support in your browser. For example
if Firefox shows this behavior you can disable it by going to ``about:config``
and disabling the `network.dns.disableIPv6` key. This however is not
recommended as of Werkzeug 0.6.1!
Starting with Werkzeug 0.6.1, the server will now switch between ipv4 and
ipv6 based on your operating system's configuration. This means if that
you disabled ipv6 support in your browser but your operating system is
preferring ipv6, you will be unable to connect to your server. In that
situation, you can either remove the localhost entry for ``::1`` or
explicitly bind the hostname to an ipv4 address (`127.0.0.1`)
.. _hosts file: https://en.wikipedia.org/wiki/Hosts_file
SSL
---
.. versionadded:: 0.6
The builtin server supports SSL for testing purposes. If an SSL context is
provided it will be used. That means a server can either run in HTTP or HTTPS
mode, but not both.
Quickstart
``````````
The easiest way to do SSL based development with Werkzeug is by using it
to generate an SSL certificate and private key and storing that somewhere
and to then put it there. For the certificate you need to provide the
name of your server on generation or a `CN`.
1. Generate an SSL key and store it somewhere:
>>> from werkzeug.serving import make_ssl_devcert
>>> make_ssl_devcert('/path/to/the/key', host='localhost')
('/path/to/the/key.crt', '/path/to/the/key.key')
2. Now this tuple can be passed as ``ssl_context`` to the
:func:`run_simple` method::
run_simple('localhost', 4000, application,
ssl_context=('/path/to/the/key.crt',
'/path/to/the/key.key'))
You will have to acknowledge the certificate in your browser once then.
Loading Contexts by Hand
````````````````````````
You can use a ``ssl.SSLContext`` object instead of a tuple for full
control over the TLS configuration.
.. code-block:: python
import ssl
ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
ctx.load_cert_chain('ssl.cert', 'ssl.key')
run_simple('localhost', 4000, application, ssl_context=ctx)
.. versionchanged 0.10:: ``OpenSSL`` contexts are not supported anymore.
Generating Certificates
```````````````````````
A key and certificate can be created in advance using the openssl tool
instead of the :func:`make_ssl_devcert`. This requires that you have
the `openssl` command installed on your system::
$ openssl genrsa 1024 > ssl.key
$ openssl req -new -x509 -nodes -sha1 -days 365 -key ssl.key > ssl.cert
Adhoc Certificates
``````````````````
The easiest way to enable SSL is to start the server in adhoc-mode. In
that case Werkzeug will generate an SSL certificate for you::
run_simple('localhost', 4000, application,
ssl_context='adhoc')
The downside of this of course is that you will have to acknowledge the
certificate each time the server is reloaded. Adhoc certificates are
discouraged because modern browsers do a bad job at supporting them for
security reasons.
This feature requires the cryptography library to be installed.
Unix Sockets
------------
The dev server can bind to a Unix socket instead of a TCP socket.
:func:`run_simple` will bind to a Unix socket if the ``hostname``
parameter starts with ``'unix://'``. ::
from werkzeug.serving import run_simple
run_simple('unix://example.sock', 0, app)

44
docs/terms.rst Normal file
View File

@ -0,0 +1,44 @@
===============
Important Terms
===============
.. currentmodule:: werkzeug
This page covers important terms used in the documentation and Werkzeug
itself.
WSGI
----
WSGI a specification for Python web applications Werkzeug follows. It was
specified in the :pep:`3333` and is widely supported. Unlike previous solutions
it guarantees that web applications, servers and utilities can work together.
Response Object
---------------
For Werkzeug, a response object is an object that works like a WSGI
application but does not do any request processing. Usually you have a view
function or controller method that processes the request and assembles a
response object.
A response object is *not* necessarily the :class:`Response` class or a
subclass thereof.
For example Pylons/webob provide a very similar response class that can
be used as well (:class:`webob.Response`).
View Function
-------------
Often people speak of MVC (Model, View, Controller) when developing web
applications. However, the Django framework coined MTV (Model, Template,
View) which basically means the same but reduces the concept to the data
model, a function that processes data from the request and the database and
renders a template.
Werkzeug itself does not tell you how you should develop applications, but the
documentation often speaks of view functions that work roughly the same. The
idea of a view function is that it's called with a request object (and
optionally some parameters from an URL rule) and returns a response object.

111
docs/test.rst Normal file
View File

@ -0,0 +1,111 @@
.. module:: werkzeug.test
Testing WSGI Applications
=========================
Test Client
-----------
Werkzeug provides a :class:`Client` to simulate requests to a WSGI
application without starting a server. The client has methods for making
different types of requests, as well as managing cookies across
requests.
>>> from werkzeug.test import Client
>>> from werkzeug.testapp import test_app
>>> c = Client(test_app)
>>> response = c.get("/")
>>> response.status_code
200
>>> resp.headers
Headers([('Content-Type', 'text/html; charset=utf-8'), ('Content-Length', '6658')])
>>> response.get_data(as_text=True)
'<!doctype html>...'
The client's request methods return instances of :class:`TestResponse`.
This provides extra attributes and methods on top of
:class:`~werkzeug.wrappers.Response` that are useful for testing.
Request Body
------------
By passing a dict to ``data``, the client will construct a request body
with file and form data. It will set the content type to
``application/x-www-form-urlencoded`` if there are no files, or
``multipart/form-data`` there are.
.. code-block:: python
import io
response = client.post(data={
"name": "test",
"file": (BytesIO("file contents".encode("utf8")), "test.txt")
})
Pass a string, bytes, or file-like object to ``data`` to use that as the
raw request body. In that case, you should set the content type
appropriately. For example, to post YAML:
.. code-block:: python
response = client.post(
data="a: value\nb: 1\n", content_type="application/yaml"
)
A shortcut when testing JSON APIs is to pass a dict to ``json`` instead
of using ``data``. This will automatically call ``json.dumps()`` and
set the content type to ``application/json``. Additionally, if the
app returns JSON, ``response.json`` will automatically call
``json.loads()``.
.. code-block:: python
response = client.post("/api", json={"a": "value", "b": 1})
obj = response.json()
Environment Builder
-------------------
:class:`EnvironBuilder` is used to construct a WSGI environ dict. The
test client uses this internally to prepare its requests. The arguments
passed to the client request methods are the same as the builder.
Sometimes, it can be useful to construct a WSGI environment manually.
An environ builder or dict can be passed to the test client request
methods in place of other arguments to use a custom environ.
.. code-block:: Python
from werkzeug.test import EnvironBuilder
builder = EnvironBuilder(...)
# build an environ dict
environ = builder.get_environ()
# build an environ dict wrapped in a request
request = builder.get_request()
The test client responses make this available through
:attr:`TestResponse.request` and ``response.request.environ``.
API
---
.. autoclass:: Client
:members:
:member-order: bysource
.. autoclass:: TestResponse
:members:
:member-order: bysource
.. autoclass:: EnvironBuilder
:members:
:member-order: bysource
.. autofunction:: create_environ
.. autofunction:: run_wsgi_app

479
docs/tutorial.rst Normal file
View File

@ -0,0 +1,479 @@
=================
Werkzeug Tutorial
=================
.. currentmodule:: werkzeug
Welcome to the Werkzeug tutorial in which we will create a `TinyURL`_ clone
that stores URLs in a redis instance. The libraries we will use for this
applications are `Jinja`_ 2 for the templates, `redis`_ for the database
layer and, of course, Werkzeug for the WSGI layer.
You can use `pip` to install the required libraries::
pip install Jinja2 redis Werkzeug
Also make sure to have a redis server running on your local machine. If
you are on OS X, you can use `brew` to install it::
brew install redis
If you are on Ubuntu or Debian, you can use apt-get::
sudo apt-get install redis-server
Redis was developed for UNIX systems and was never really designed to
work on Windows. For development purposes, the unofficial ports however
work well enough. You can get them from `github
<https://github.com/dmajkic/redis/downloads>`_.
Introducing Shortly
-------------------
In this tutorial, we will together create a simple URL shortener service
with Werkzeug. Please keep in mind that Werkzeug is not a framework, it's
a library with utilities to create your own framework or application and
as such is very flexible. The approach we use here is just one of many you
can use.
As data store, we will use `redis`_ here instead of a relational database
to keep this simple and because that's the kind of job that `redis`_
excels at.
The final result will look something like this:
.. image:: _static/shortly.png
:alt: a screenshot of shortly
.. _TinyURL: https://tinyurl.com/
.. _Jinja: http://jinja.pocoo.org/
.. _redis: https://redis.io/
Step 0: A Basic WSGI Introduction
---------------------------------
Werkzeug is a utility library for WSGI. WSGI itself is a protocol or
convention that ensures that your web application can speak with the
webserver and more importantly that web applications work nicely together.
A basic “Hello World” application in WSGI without the help of Werkzeug
looks like this::
def application(environ, start_response):
start_response('200 OK', [('Content-Type', 'text/plain')])
return ['Hello World!'.encode('utf-8')]
A WSGI application is something you can call and pass an environ dict
and a ``start_response`` callable. The environ contains all incoming
information, the ``start_response`` function can be used to indicate the
start of the response. With Werkzeug you don't have to deal directly with
either as request and response objects are provided to work with them.
The request data takes the environ object and allows you to access the
data from that environ in a nice manner. The response object is a WSGI
application in itself and provides a much nicer way to create responses.
Here is how you would write that application with response objects::
from werkzeug.wrappers import Response
def application(environ, start_response):
response = Response('Hello World!', mimetype='text/plain')
return response(environ, start_response)
And here an expanded version that looks at the query string in the URL
(more importantly at the `name` parameter in the URL to substitute “World”
against another word)::
from werkzeug.wrappers import Request, Response
def application(environ, start_response):
request = Request(environ)
text = f"Hello {request.args.get('name', 'World')}!"
response = Response(text, mimetype='text/plain')
return response(environ, start_response)
And that's all you need to know about WSGI.
Step 1: Creating the Folders
----------------------------
Before we get started, lets create the folders needed for this application::
/shortly
/static
/templates
The shortly folder is not a python package, but just something where we
drop our files. Directly into this folder we will then put our main
module in the following steps. The files inside the static folder are
available to users of the application via HTTP. This is the place where
CSS and JavaScript files go. Inside the templates folder we will make
Jinja2 look for templates. The templates you create later in the tutorial
will go in this directory.
Step 2: The Base Structure
--------------------------
Now let's get right into it and create a module for our application. Let's
create a file called `shortly.py` in the `shortly` folder. At first we
will need a bunch of imports. I will pull in all the imports here, even
if they are not used right away, to keep it from being confusing::
import os
import redis
from werkzeug.urls import url_parse
from werkzeug.wrappers import Request, Response
from werkzeug.routing import Map, Rule
from werkzeug.exceptions import HTTPException, NotFound
from werkzeug.middleware.shared_data import SharedDataMiddleware
from werkzeug.utils import redirect
from jinja2 import Environment, FileSystemLoader
Then we can create the basic structure for our application and a function
to create a new instance of it, optionally with a piece of WSGI middleware
that exports all the files on the `static` folder on the web::
class Shortly(object):
def __init__(self, config):
self.redis = redis.Redis(
config['redis_host'], config['redis_port'], decode_responses=True
)
def dispatch_request(self, request):
return Response('Hello World!')
def wsgi_app(self, environ, start_response):
request = Request(environ)
response = self.dispatch_request(request)
return response(environ, start_response)
def __call__(self, environ, start_response):
return self.wsgi_app(environ, start_response)
def create_app(redis_host='localhost', redis_port=6379, with_static=True):
app = Shortly({
'redis_host': redis_host,
'redis_port': redis_port
})
if with_static:
app.wsgi_app = SharedDataMiddleware(app.wsgi_app, {
'/static': os.path.join(os.path.dirname(__file__), 'static')
})
return app
Lastly we can add a piece of code that will start a local development
server with automatic code reloading and a debugger::
if __name__ == '__main__':
from werkzeug.serving import run_simple
app = create_app()
run_simple('127.0.0.1', 5000, app, use_debugger=True, use_reloader=True)
The basic idea here is that our ``Shortly`` class is an actual WSGI
application. The ``__call__`` method directly dispatches to ``wsgi_app``.
This is done so that we can wrap ``wsgi_app`` to apply middlewares like we
do in the ``create_app`` function. The actual ``wsgi_app`` method then
creates a :class:`Request` object and calls the ``dispatch_request``
method which then has to return a :class:`Response` object which is then
evaluated as WSGI application again. As you can see: turtles all the way
down. Both the ``Shortly`` class we create, as well as any request object
in Werkzeug implements the WSGI interface. As a result of that you could
even return another WSGI application from the ``dispatch_request`` method.
The ``create_app`` factory function can be used to create a new instance
of our application. Not only will it pass some parameters as
configuration to the application but also optionally add a WSGI middleware
that exports static files. This way we have access to the files from the
static folder even when we are not configuring our server to provide them
which is very helpful for development.
Intermezzo: Running the Application
-----------------------------------
Now you should be able to execute the file with `python` and see a server
on your local machine::
$ python shortly.py
* Running on http://127.0.0.1:5000/
* Restarting with reloader: stat() polling
It also tells you that the reloader is active. It will use various
techniques to figure out if any file changed on the disk and then
automatically restart.
Just go to the URL and you should see “Hello World!”.
Step 3: The Environment
-----------------------
Now that we have the basic application class, we can make the constructor
do something useful and provide a few helpers on there that can come in
handy. We will need to be able to render templates and connect to redis,
so let's extend the class a bit::
def __init__(self, config):
self.redis = redis.Redis(config['redis_host'], config['redis_port'])
template_path = os.path.join(os.path.dirname(__file__), 'templates')
self.jinja_env = Environment(loader=FileSystemLoader(template_path),
autoescape=True)
def render_template(self, template_name, **context):
t = self.jinja_env.get_template(template_name)
return Response(t.render(context), mimetype='text/html')
Step 4: The Routing
-------------------
Next up is routing. Routing is the process of matching and parsing the URL to
something we can use. Werkzeug provides a flexible integrated routing
system which we can use for that. The way it works is that you create a
:class:`~werkzeug.routing.Map` instance and add a bunch of
:class:`~werkzeug.routing.Rule` objects. Each rule has a pattern it will
try to match the URL against and an “endpoint”. The endpoint is typically
a string and can be used to uniquely identify the URL. We could also use
this to automatically reverse the URL, but that's not what we will do in this
tutorial.
Just put this into the constructor::
self.url_map = Map([
Rule('/', endpoint='new_url'),
Rule('/<short_id>', endpoint='follow_short_link'),
Rule('/<short_id>+', endpoint='short_link_details')
])
Here we create a URL map with three rules. ``/`` for the root of the URL
space where we will just dispatch to a function that implements the logic
to create a new URL. And then one that follows the short link to the
target URL and another one with the same rule but a plus (``+``) at the
end to show the link details.
So how do we find our way from the endpoint to a function? That's up to you.
The way we will do it in this tutorial is by calling the method ``on_``
+ endpoint on the class itself. Here is how this works::
def dispatch_request(self, request):
adapter = self.url_map.bind_to_environ(request.environ)
try:
endpoint, values = adapter.match()
return getattr(self, f'on_{endpoint}')(request, **values)
except HTTPException as e:
return e
We bind the URL map to the current environment and get back a
:class:`~werkzeug.routing.URLAdapter`. The adapter can be used to match
the request but also to reverse URLs. The match method will return the
endpoint and a dictionary of values in the URL. For instance the rule for
``follow_short_link`` has a variable part called ``short_id``. When we go
to ``http://localhost:5000/foo`` we will get the following values back::
endpoint = 'follow_short_link'
values = {'short_id': 'foo'}
If it does not match anything, it will raise a
:exc:`~werkzeug.exceptions.NotFound` exception, which is an
:exc:`~werkzeug.exceptions.HTTPException`. All HTTP exceptions are also
WSGI applications by themselves which render a default error page. So we
just catch all of them down and return the error itself.
If all works well, we call the function ``on_`` + endpoint and pass it the
request as argument as well as all the URL arguments as keyword arguments
and return the response object that method returns.
Step 5: The First View
----------------------
Let's start with the first view: the one for new URLs::
def on_new_url(self, request):
error = None
url = ''
if request.method == 'POST':
url = request.form['url']
if not is_valid_url(url):
error = 'Please enter a valid URL'
else:
short_id = self.insert_url(url)
return redirect(f"/{short_id}+")
return self.render_template('new_url.html', error=error, url=url)
This logic should be easy to understand. Basically we are checking that
the request method is POST, in which case we validate the URL and add a
new entry to the database, then redirect to the detail page. This means
we need to write a function and a helper method. For URL validation this
is good enough::
def is_valid_url(url):
parts = url_parse(url)
return parts.scheme in ('http', 'https')
For inserting the URL, all we need is this little method on our class::
def insert_url(self, url):
short_id = self.redis.get(f'reverse-url:{url}')
if short_id is not None:
return short_id
url_num = self.redis.incr('last-url-id')
short_id = base36_encode(url_num)
self.redis.set(f'url-target:{short_id}', url)
self.redis.set(f'reverse-url:{url}', short_id)
return short_id
``reverse-url:`` + the URL will store the short id. If the URL was
already submitted this won't be None and we can just return that value
which will be the short ID. Otherwise we increment the ``last-url-id``
key and convert it to base36. Then we store the link and the reverse
entry in redis. And here the function to convert to base 36::
def base36_encode(number):
assert number >= 0, 'positive integer required'
if number == 0:
return '0'
base36 = []
while number != 0:
number, i = divmod(number, 36)
base36.append('0123456789abcdefghijklmnopqrstuvwxyz'[i])
return ''.join(reversed(base36))
So what is missing for this view to work is the template. We will create
this later, let's first also write the other views and then do the
templates in one go.
Step 6: Redirect View
---------------------
The redirect view is easy. All it has to do is to look for the link in
redis and redirect to it. Additionally we will also increment a counter
so that we know how often a link was clicked::
def on_follow_short_link(self, request, short_id):
link_target = self.redis.get(f'url-target:{short_id}')
if link_target is None:
raise NotFound()
self.redis.incr(f'click-count:{short_id}')
return redirect(link_target)
In this case we will raise a :exc:`~werkzeug.exceptions.NotFound` exception
by hand if the URL does not exist, which will bubble up to the
``dispatch_request`` function and be converted into a default 404
response.
Step 7: Detail View
-------------------
The link detail view is very similar, we just render a template
again. In addition to looking up the target, we also ask redis for the
number of times the link was clicked and let it default to zero if such
a key does not yet exist::
def on_short_link_details(self, request, short_id):
link_target = self.redis.get(f'url-target:{short_id}')
if link_target is None:
raise NotFound()
click_count = int(self.redis.get(f'click-count:{short_id}') or 0)
return self.render_template('short_link_details.html',
link_target=link_target,
short_id=short_id,
click_count=click_count
)
Please be aware that redis always works with strings, so you have to convert
the click count to :class:`int` by hand.
Step 8: Templates
-----------------
And here are all the templates. Just drop them into the `templates`
folder. Jinja2 supports template inheritance, so the first thing we will
do is create a layout template with blocks that act as placeholders. We
also set up Jinja2 so that it automatically escapes strings with HTML
rules, so we don't have to spend time on that ourselves. This prevents
XSS attacks and rendering errors.
*layout.html*:
.. sourcecode:: html+jinja
<!doctype html>
<title>{% block title %}{% endblock %} | shortly</title>
<link rel=stylesheet href=/static/style.css type=text/css>
<div class=box>
<h1><a href=/>shortly</a></h1>
<p class=tagline>Shortly is a URL shortener written with Werkzeug
{% block body %}{% endblock %}
</div>
*new_url.html*:
.. sourcecode:: html+jinja
{% extends "layout.html" %}
{% block title %}Create New Short URL{% endblock %}
{% block body %}
<h2>Submit URL</h2>
<form action="" method=post>
{% if error %}
<p class=error><strong>Error:</strong> {{ error }}
{% endif %}
<p>URL:
<input type=text name=url value="{{ url }}" class=urlinput>
<input type=submit value="Shorten">
</form>
{% endblock %}
*short_link_details.html*:
.. sourcecode:: html+jinja
{% extends "layout.html" %}
{% block title %}Details about /{{ short_id }}{% endblock %}
{% block body %}
<h2><a href="/{{ short_id }}">/{{ short_id }}</a></h2>
<dl>
<dt>Full link
<dd class=link><div>{{ link_target }}</div>
<dt>Click count:
<dd>{{ click_count }}
</dl>
{% endblock %}
Step 9: The Style
-----------------
For this to look better than ugly black and white, here a simple
stylesheet that goes along:
*static/style.css*:
.. sourcecode:: css
body { background: #E8EFF0; margin: 0; padding: 0; }
body, input { font-family: 'Helvetica Neue', Arial,
sans-serif; font-weight: 300; font-size: 18px; }
.box { width: 500px; margin: 60px auto; padding: 20px;
background: white; box-shadow: 0 1px 4px #BED1D4;
border-radius: 2px; }
a { color: #11557C; }
h1, h2 { margin: 0; color: #11557C; }
h1 a { text-decoration: none; }
h2 { font-weight: normal; font-size: 24px; }
.tagline { color: #888; font-style: italic; margin: 0 0 20px 0; }
.link div { overflow: auto; font-size: 0.8em; white-space: pre;
padding: 4px 10px; margin: 5px 0; background: #E5EAF1; }
dt { font-weight: normal; }
.error { background: #E8EFF0; padding: 3px 8px; color: #11557C;
font-size: 0.9em; border-radius: 2px; }
.urlinput { width: 300px; }
Bonus: Refinements
------------------
Look at the implementation in the example dictionary in the Werkzeug
repository to see a version of this tutorial with some small refinements
such as a custom 404 page.
- `shortly in the example folder <https://github.com/pallets/werkzeug/tree/main/examples/shortly>`_

76
docs/unicode.rst Normal file
View File

@ -0,0 +1,76 @@
Unicode
=======
.. currentmodule:: werkzeug
Werkzeug uses strings internally everwhere text data is assumed, even if
the HTTP standard is not Unicode aware. Basically all incoming data is
decoded from the charset (UTF-8 by default) so that you don't work with
bytes directly. Outgoing data is encoded into the target charset.
Unicode in Python
-----------------
Imagine you have the German Umlaut ``ö``. In ASCII you cannot represent
that character, but in the ``latin-1`` and ``utf-8`` character sets you
can represent it, but they look different when encoded:
>>> "ö".encode("latin1")
b'\xf6'
>>> "ö".encode("utf-8")
b'\xc3\xb6'
An ``ö`` looks different depending on the encoding which makes it hard
to work with it as bytes. Instead, Python treats strings as Unicode text
and stores the information ``LATIN SMALL LETTER O WITH DIAERESIS``
instead of the bytes for ``ö`` in a specific encoding. The length of a
string with 1 character will be 1, where the length of the bytes might
be some other value.
Unicode in HTTP
---------------
However, the HTTP spec was written in a time where ASCII bytes were the
common way data was represented. To work around this for the modern
web, Werkzeug decodes and encodes incoming and outgoing data
automatically. Data sent from the browser to the web application is
decoded from UTF-8 bytes into a string. Data sent from the application
back to the browser is encoded back to UTF-8.
Error Handling
--------------
Functions that do internal encoding or decoding accept an ``errors``
keyword argument that is passed to :meth:`str.decode` and
:meth:`str.encode`. The default is ``'replace'`` so that errors are easy
to spot. It might be useful to set it to ``'strict'`` in order to catch
the error and report the bad data to the client.
Request and Response Objects
----------------------------
In most cases, you should stick with Werkzeug's default encoding of
UTF-8. If you have a specific reason to, you can subclass
:class:`wrappers.Request` and :class:`wrappers.Response` to change the
encoding and error handling.
.. code-block:: python
from werkzeug.wrappers.request import Request
from werkzeug.wrappers.response import Response
class Latin1Request(Request):
charset = "latin1"
encoding_errors = "strict"
class Latin1Response(Response):
charset = "latin1"
The error handling can only be changed for the request. Werkzeug will
always raise errors when encoding to bytes in the response. It's your
responsibility to not create data that is not present in the target
charset. This is not an issue for UTF-8.

6
docs/urls.rst Normal file
View File

@ -0,0 +1,6 @@
===========
URL Helpers
===========
.. automodule:: werkzeug.urls
:members:

74
docs/utils.rst Normal file
View File

@ -0,0 +1,74 @@
=========
Utilities
=========
Various utility functions shipped with Werkzeug.
.. module:: werkzeug.utils
General Helpers
===============
.. autoclass:: cached_property
:members:
.. autoclass:: environ_property
.. autoclass:: header_property
.. autofunction:: redirect
.. autofunction:: append_slash_redirect
.. autofunction:: send_file
.. autofunction:: import_string
.. autofunction:: find_modules
.. autofunction:: secure_filename
URL Helpers
===========
Please refer to :doc:`urls`.
User Agent API
==============
.. module:: werkzeug.user_agent
.. autoclass:: UserAgent
:members:
:member-order: bysource
Security Helpers
================
.. module:: werkzeug.security
.. autofunction:: generate_password_hash
.. autofunction:: check_password_hash
.. autofunction:: safe_join
Logging
=======
Werkzeug uses standard Python :mod:`logging`. The logger is named
``"werkzeug"``.
.. code-block:: python
import logging
logger = logging.getLogger("werkzeug")
If the logger level is not set, it will be set to :data:`~logging.INFO`
on first use. If there is no handler for that level, a
:class:`~logging.StreamHandler` is added.

92
docs/wrappers.rst Normal file
View File

@ -0,0 +1,92 @@
==========================
Request / Response Objects
==========================
.. module:: werkzeug.wrappers
The request and response objects wrap the WSGI environment or the return
value from a WSGI application so that it is another WSGI application
(wraps a whole application).
How they Work
=============
Your WSGI application is always passed two arguments. The WSGI "environment"
and the WSGI `start_response` function that is used to start the response
phase. The :class:`Request` class wraps the `environ` for easier access to
request variables (form data, request headers etc.).
The :class:`Response` on the other hand is a standard WSGI application that
you can create. The simple hello world in Werkzeug looks like this::
from werkzeug.wrappers import Response
application = Response('Hello World!')
To make it more useful you can replace it with a function and do some
processing::
from werkzeug.wrappers import Request, Response
def application(environ, start_response):
request = Request(environ)
response = Response(f"Hello {request.args.get('name', 'World!')}!")
return response(environ, start_response)
Because this is a very common task the :class:`~Request` object provides
a helper for that. The above code can be rewritten like this::
from werkzeug.wrappers import Request, Response
@Request.application
def application(request):
return Response(f"Hello {request.args.get('name', 'World!')}!")
The `application` is still a valid WSGI application that accepts the
environment and `start_response` callable.
Mutability and Reusability of Wrappers
======================================
The implementation of the Werkzeug request and response objects are trying
to guard you from common pitfalls by disallowing certain things as much as
possible. This serves two purposes: high performance and avoiding of
pitfalls.
For the request object the following rules apply:
1. The request object is immutable. Modifications are not supported by
default, you may however replace the immutable attributes with mutable
attributes if you need to modify it.
2. The request object may be shared in the same thread, but is not thread
safe itself. If you need to access it from multiple threads, use
locks around calls.
3. It's not possible to pickle the request object.
For the response object the following rules apply:
1. The response object is mutable
2. The response object can be pickled or copied after `freeze()` was
called.
3. Since Werkzeug 0.6 it's safe to use the same response object for
multiple WSGI responses.
4. It's possible to create copies using `copy.deepcopy`.
Wrapper Classes
===============
.. autoclass:: Request
:members:
:inherited-members:
.. automethod:: _get_file_stream
.. autoclass:: Response
:members:
:inherited-members:
.. automethod:: __call__
.. automethod:: _ensure_sequence

115
docs/wsgi.rst Normal file
View File

@ -0,0 +1,115 @@
WSGI Helpers
============
.. module:: werkzeug.wsgi
The following classes and functions are designed to make working with
the WSGI specification easier or operate on the WSGI layer. All the
functionality from this module is available on the high-level
:doc:`/wrappers`.
Iterator / Stream Helpers
-------------------------
These classes and functions simplify working with the WSGI application
iterator and the input stream.
.. autoclass:: ClosingIterator
.. autoclass:: FileWrapper
.. autoclass:: LimitedStream
:members:
.. autofunction:: make_line_iter
.. autofunction:: make_chunk_iter
.. autofunction:: wrap_file
Environ Helpers
---------------
These functions operate on the WSGI environment. They extract useful
information or perform common manipulations:
.. autofunction:: get_host
.. autofunction:: get_content_length
.. autofunction:: get_input_stream
.. autofunction:: get_current_url
.. autofunction:: get_query_string
.. autofunction:: get_script_name
.. autofunction:: get_path_info
.. autofunction:: pop_path_info
.. autofunction:: peek_path_info
.. autofunction:: extract_path_info
.. autofunction:: host_is_trusted
Convenience Helpers
-------------------
.. autofunction:: responder
.. autofunction:: werkzeug.testapp.test_app
Bytes, Strings, and Encodings
-----------------------------
The values in HTTP requests come in as bytes representing (or encoded
to) ASCII. The WSGI specification (:pep:`3333`) decided to always use
the ``str`` type to represent values. To accomplish this, the raw bytes
are decoded using the ISO-8859-1 charset to produce a string.
Strings in the WSGI environment are restricted to ISO-8859-1 code
points. If a string read from the environment might contain characters
outside that charset, it must first be decoded to bytes as ISO-8859-1,
then encoded to a string using the proper charset (typically UTF-8). The
reverse is done when writing to the environ. This is known as the "WSGI
encoding dance".
Werkzeug provides functions to deal with this automatically so that you
don't need to be aware of the inner workings. Use the functions on this
page as well as :func:`~werkzeug.datastructures.EnvironHeaders` to read
data out of the WSGI environment.
Applications should avoid manually creating or modifying a WSGI
environment unless they take care of the proper encoding or decoding
step. All high level interfaces in Werkzeug will apply the encoding and
decoding as necessary.
Raw Request URI and Path Encoding
---------------------------------
The ``PATH_INFO`` in the environ is the path value after
percent-decoding. For example, the raw path ``/hello%2fworld`` would
show up from the WSGI server to Werkzeug as ``/hello/world``. This loses
the information that the slash was a raw character as opposed to a path
separator.
The WSGI specification (:pep:`3333`) does not provide a way to get the
original value, so it is impossible to route some types of data in the
path. The most compatible way to work around this is to send problematic
data in the query string instead of the path.
However, many WSGI servers add a non-standard environ key with the raw
path. To match this behavior, Werkzeug's test client and development
server will add the raw value to both the ``REQUEST_URI`` and
``RAW_URI`` keys. If you want to route based on this value, you can use
middleware to replace ``PATH_INFO`` in the environ before it reaches the
application. However, keep in mind that these keys are non-standard and
not guaranteed to be present.

113
examples/README.rst Normal file
View File

@ -0,0 +1,113 @@
=================
Werkzeug Examples
=================
This directory contains various example applications and example code of
Werkzeug powered applications.
Beside the proof of concept applications and code snippets in the partial
folder they all have external dependencies for template engines or database
adapters (SQLAlchemy only so far). Also, every application has click as
external dependency, used to create the command line interface.
Full Example Applications
=========================
The following example applications are application types you would actually
find in real life :-)
`simplewiki`
A simple Wiki implementation.
Requirements:
- SQLAlchemy
- Creoleparser >= 0.7
- genshi
You can obtain all packages in the Cheeseshop via easy_install. You have
to have at least version 0.7 of Creoleparser.
Usage::
./manage-simplewiki.py initdb
./manage-simplewiki.py runserver
Or of course you can just use the application object
(`simplewiki.SimpleWiki`) and hook that into your favourite WSGI gateway.
The constructor of the application object takes a single argument which is
the SQLAlchemy URI for the database.
The management script for the devserver looks up the an environment var
called `SIMPLEWIKI_DATABASE_URI` and uses that for the database URI. If
no such variable is provided "sqlite:////tmp/simplewiki.db" is assumed.
`plnt`
A planet called plnt, pronounce plant.
Requirements:
- SQLAlchemy
- Jinja2
- feedparser
You can obtain all packages in the Cheeseshop via easy_install.
Usage::
./manage-plnt.py initdb
./manage-plnt.py sync
./manage-plnt.py runserver
The WSGI application is called `plnt.Plnt` which, like the simple wiki,
accepts a database URI as first argument. The environment variable for
the database key is called `PLNT_DATABASE_URI` and the default is
"sqlite:////tmp/plnt.db".
Per default a few python related blogs are added to the database, you
can add more in a python shell by playing with the `Blog` model.
`shorty`
A tinyurl clone for the Werkzeug tutorial.
Requirements:
- SQLAlchemy
- Jinja2
You can obtain all packages in the Cheeseshop via easy_install.
Usage::
./manage-shorty.py initdb
./manage-shorty.py runserver
The WSGI application is called `shorty.application.Shorty` which, like the
simple wiki, accepts a database URI as first argument.
The source code of the application is explained in detail in the Werkzeug
tutorial.
`couchy`
Like shorty, but implemented using CouchDB.
Requirements :
- werkzeug : http://werkzeug.pocoo.org
- jinja : http://jinja.pocoo.org
- couchdb 0.72 & above : https://couchdb.apache.org/
`cupoftee`
A `Teeworlds <https://www.teeworlds.com/>`_ server browser. This application
works best in a non forking environment and won't work for CGI.
Usage::
./manage-cupoftee.py runserver

View File

@ -0,0 +1 @@
from .application import make_app

View File

@ -0,0 +1,77 @@
"""This module provides the WSGI application.
The WSGI middlewares are applied in the `make_app` factory function that
automatically wraps the application within the require middlewares. Per
default only the `SharedDataMiddleware` is applied.
"""
from os import listdir
from os import path
from werkzeug.exceptions import HTTPException
from werkzeug.exceptions import NotFound
from werkzeug.middleware.shared_data import SharedDataMiddleware
from werkzeug.routing import Map
from werkzeug.routing import RequestRedirect
from werkzeug.routing import Rule
from .utils import local_manager
from .utils import Request
class CoolMagicApplication:
"""
The application class. It's passed a directory with configuration values.
"""
def __init__(self, config):
self.config = config
for fn in listdir(path.join(path.dirname(__file__), "views")):
if fn.endswith(".py") and fn != "__init__.py":
__import__(f"coolmagic.views.{fn[:-3]}")
from coolmagic.utils import exported_views
rules = [
# url for shared data. this will always be unmatched
# because either the middleware or the webserver
# handles that request first.
Rule("/public/<path:file>", endpoint="shared_data")
]
self.views = {}
for endpoint, (func, rule, extra) in exported_views.items():
if rule is not None:
rules.append(Rule(rule, endpoint=endpoint, **extra))
self.views[endpoint] = func
self.url_map = Map(rules)
def __call__(self, environ, start_response):
urls = self.url_map.bind_to_environ(environ)
req = Request(environ, urls)
try:
endpoint, args = urls.match(req.path)
resp = self.views[endpoint](**args)
except NotFound:
resp = self.views["static.not_found"]()
except (HTTPException, RequestRedirect) as e:
resp = e
return resp(environ, start_response)
def make_app(config=None):
"""
Factory function that creates a new `CoolmagicApplication`
object. Optional WSGI middlewares should be applied here.
"""
config = config or {}
app = CoolMagicApplication(config)
# static stuff
app = SharedDataMiddleware(
app, {"/public": path.join(path.dirname(__file__), "public")}
)
# clean up locals
app = local_manager.make_middleware(app)
return app

View File

@ -0,0 +1,5 @@
from .utils import ThreadedRequest
#: a thread local proxy request object
request = ThreadedRequest()
del ThreadedRequest

View File

@ -0,0 +1,10 @@
body {
margin: 0;
padding: 20px;
font-family: sans-serif;
font-size: 15px;
}
h1, a {
color: #a00;
}

View File

@ -0,0 +1,12 @@
<!doctype html>
<html>
<head>
<title>{{ page_title }} &mdash; Cool Magic!</title>
<link rel="stylesheet" href="{{ h.url_for('shared_data', file='style.css') }}" type="text/css">
</head>
<body>
<h1>Cool Magic</h1>
<h2>{{ page_title }}</h2>
{% block page_body %}{% endblock %}
</body>
</html>

View File

@ -0,0 +1,10 @@
{% extends "layout.html" %}
{% set page_title = 'About the Magic' %}
{% block page_body %}
<p>
Nothing to see. It's just magic.
</p>
<p>
<a href="{{ h.url_for('static.index') }}">back to the index</a>
</p>
{% endblock %}

View File

@ -0,0 +1,13 @@
{% extends "layout.html" %}
{% set page_title = 'Welcome to the Magic' %}
{% block page_body %}
<p>
Welcome to the magic! This is a bigger example for the
Werkzeug toolkit. And it contains a lot of magic.
</p>
<p>
<a href="{{ h.url_for('static.about') }}">about the implementation</a> or
click here if you want to see a <a href="{{ h.url_for('static.broken')
}}">broken view</a>.
</p>
{% endblock %}

View File

@ -0,0 +1,8 @@
{% extends "layout.html" %}
{% set page_title = 'Missing Magic' %}
{% block page_body %}
<p>
The requested magic really does not exist. Maybe you want
to look for it on the <a href="{{ h.url_for('static.index') }}">index</a>.
</p>
{% endblock %}

104
examples/coolmagic/utils.py Normal file
View File

@ -0,0 +1,104 @@
"""Subclasses of the base request and response objects provided by
werkzeug. The subclasses know about their charset and implement some
additional functionality like the ability to link to view functions.
"""
from os.path import dirname
from os.path import join
from jinja2 import Environment
from jinja2 import FileSystemLoader
from werkzeug.local import Local
from werkzeug.local import LocalManager
from werkzeug.wrappers import Request as BaseRequest
from werkzeug.wrappers import Response as BaseResponse
local = Local()
local_manager = LocalManager([local])
template_env = Environment(
loader=FileSystemLoader(join(dirname(__file__), "templates"))
)
exported_views = {}
def export(string, template=None, **extra):
"""
Decorator for registering view functions and adding
templates to it.
"""
def wrapped(f):
endpoint = f"{f.__module__}.{f.__name__}"[16:]
if template is not None:
old_f = f
def f(**kwargs):
rv = old_f(**kwargs)
if not isinstance(rv, Response):
rv = TemplateResponse(template, **(rv or {}))
return rv
f.__name__ = old_f.__name__
f.__doc__ = old_f.__doc__
exported_views[endpoint] = (f, string, extra)
return f
return wrapped
def url_for(endpoint, **values):
"""
Build a URL
"""
return local.request.url_adapter.build(endpoint, values)
class Request(BaseRequest):
"""
The concrete request object used in the WSGI application.
It has some helper functions that can be used to build URLs.
"""
charset = "utf-8"
def __init__(self, environ, url_adapter):
super().__init__(environ)
self.url_adapter = url_adapter
local.request = self
class ThreadedRequest:
"""
A pseudo request object that always points to the current
context active request.
"""
def __getattr__(self, name):
if name == "__members__":
return [x for x in dir(local.request) if not x.startswith("_")]
return getattr(local.request, name)
def __setattr__(self, name, value):
return setattr(local.request, name, value)
class Response(BaseResponse):
"""
The concrete response object for the WSGI application.
"""
charset = "utf-8"
default_mimetype = "text/html"
class TemplateResponse(Response):
"""
Render a template to a response.
"""
def __init__(self, template_name, **values):
from coolmagic import helpers
values.update(request=local.request, h=helpers)
template = template_env.get_template(template_name)
Response.__init__(self, template.render(values))

View File

View File

@ -0,0 +1,25 @@
from coolmagic.utils import export
@export("/", template="static/index.html")
def index():
pass
@export("/about", template="static/about.html")
def about():
pass
@export("/broken")
def broken():
raise RuntimeError("that's really broken")
@export(None, template="static/not_found.html")
def not_found():
"""
This function is always executed if an url does not
match or a `NotFound` exception is raised.
"""
pass

7
examples/couchy/README Normal file
View File

@ -0,0 +1,7 @@
couchy README
Requirements :
- werkzeug : http://werkzeug.pocoo.org
- jinja : http://jinja.pocoo.org
- couchdb 0.72 & above : https://couchdb.apache.org/
- couchdb-python 0.3 & above : https://github.com/djc/couchdb-python

View File

View File

@ -0,0 +1,47 @@
from couchdb.client import Server
from werkzeug.exceptions import HTTPException
from werkzeug.exceptions import NotFound
from werkzeug.middleware.shared_data import SharedDataMiddleware
from werkzeug.wrappers import Request
from werkzeug.wsgi import ClosingIterator
from . import views
from .models import URL
from .utils import local
from .utils import local_manager
from .utils import STATIC_PATH
from .utils import url_map
class Couchy:
def __init__(self, db_uri):
local.application = self
server = Server(db_uri)
try:
db = server.create("urls")
except Exception:
db = server["urls"]
self.dispatch = SharedDataMiddleware(self.dispatch, {"/static": STATIC_PATH})
URL.db = db
def dispatch(self, environ, start_response):
local.application = self
request = Request(environ)
local.url_adapter = adapter = url_map.bind_to_environ(environ)
try:
endpoint, values = adapter.match()
handler = getattr(views, endpoint)
response = handler(request, **values)
except NotFound:
response = views.not_found(request)
response.status_code = 404
except HTTPException as e:
response = e
return ClosingIterator(
response(environ, start_response), [local_manager.cleanup]
)
def __call__(self, environ, start_response):
return self.dispatch(environ, start_response)

50
examples/couchy/models.py Normal file
View File

@ -0,0 +1,50 @@
from datetime import datetime
from couchdb.mapping import BooleanField
from couchdb.mapping import DateTimeField
from couchdb.mapping import Document
from couchdb.mapping import TextField
from .utils import get_random_uid
from .utils import url_for
class URL(Document):
target = TextField()
public = BooleanField()
added = DateTimeField(default=datetime.utcnow())
shorty_id = TextField(default=None)
db = None
@classmethod
def load(cls, id):
return super().load(URL.db, id)
@classmethod
def query(cls, code):
return URL.db.query(code)
def store(self):
if getattr(self._data, "id", None) is None:
new_id = self.shorty_id if self.shorty_id else None
while 1:
id = new_id if new_id else get_random_uid()
try:
docid = URL.db.resource.put(content=self._data, path=f"/{id}/")[
"id"
]
except Exception:
continue
if docid:
break
self._data = URL.db.get(docid)
else:
super().store(URL.db)
return self
@property
def short_url(self):
return url_for("link", uid=self.id, _external=True)
def __repr__(self):
return f"<URL {self.id!r}>"

View File

@ -0,0 +1,108 @@
body {
background-color: #333;
font-family: 'Lucida Sans', 'Verdana', sans-serif;
font-size: 16px;
margin: 3em 0 3em 0;
padding: 0;
text-align: center;
}
a {
color: #0C4850;
}
a:hover {
color: #1C818F;
}
h1 {
width: 500px;
background-color: #24C0CE;
text-align: center;
font-size: 3em;
margin: 0 auto 0 auto;
padding: 0;
}
h1 a {
display: block;
padding: 0.3em;
color: #fff;
text-decoration: none;
}
h1 a:hover {
color: #ADEEF7;
background-color: #0E8A96;
}
div.footer {
margin: 0 auto 0 auto;
font-size: 13px;
text-align: right;
padding: 10px;
width: 480px;
background-color: #004C63;
color: white;
}
div.footer a {
color: #A0E9FF;
}
div.body {
margin: 0 auto 0 auto;
padding: 20px;
width: 460px;
background-color: #98CE24;
color: black;
}
div.body h2 {
margin: 0 0 0.5em 0;
text-align: center;
}
div.body input {
margin: 0.2em 0 0.2em 0;
font-family: 'Lucida Sans', 'Verdana', sans-serif;
font-size: 20px;
background-color: #CCEB98;
color: black;
}
div.body #url {
width: 400px;
}
div.body #alias {
width: 300px;
margin-right: 10px;
}
div.body #submit {
width: 90px;
}
div.body p {
margin: 0;
padding: 0.2em 0 0.2em 0;
}
div.body ul {
margin: 1em 0 1em 0;
padding: 0;
list-style: none;
}
div.error {
margin: 1em 0 1em 0;
border: 2px solid #AC0202;
background-color: #9E0303;
font-weight: bold;
color: white;
}
div.pagination {
font-size: 13px;
}

View File

@ -0,0 +1,8 @@
{% extends 'layout.html' %}
{% block body %}
<h2>Shortened URL</h2>
<p>
The URL {{ url.target|urlize(40, true) }}
was shortened to {{ url.short_url|urlize }}.
</p>
{% endblock %}

View File

@ -0,0 +1,16 @@
<!doctype html>
<html>
<head>
<title>Shorty</title>
<link rel="stylesheet" type="text/css" href="{{ url_for('static', file='style.css') }}">
</head>
<body>
<h1><a href="{{ url_for('new') }}">Shorty</a></h1>
<div class="body">{% block body %}{% endblock %}</div>
<div class="footer">
<a href="{{ url_for('new') }}">new</a> |
<a href="{{ url_for('list') }}">list</a> |
use shorty for good, not for evil
</div>
</body>
</html>

View File

@ -0,0 +1,19 @@
{% extends 'layout.html' %}
{% block body %}
<h2>List of URLs</h2>
<ul>
{%- for url in pagination.entries %}
<li><a href="{{ url.short_url|e }}">{{ url.id|e }}</a> &raquo;
<small>{{ url.target|urlize(38, true) }}</small></li>
{%- else %}
<li><em>no URLs shortened yet</em></li>
{%- endfor %}
</ul>
<div class="pagination">
{%- if pagination.has_previous %}<a href="{{ pagination.previous }}">&laquo; Previous</a>
{%- else %}<span class="inactive">&laquo; Previous</span>{% endif %}
| {{ pagination.page }} |
{% if pagination.has_next %}<a href="{{ pagination.next }}">Next &raquo;</a>
{%- else %}<span class="inactive">Next &raquo;</span>{% endif %}
</div>
{% endblock %}

View File

@ -0,0 +1,14 @@
{% extends 'layout.html' %}
{% block body %}
<h2>Create a Shorty-URL!</h2>
{% if error %}<div class="error">{{ error }}</div>{% endif -%}
<form action="" method="post">
<p>Enter the URL you want to shorten</p>
<p><input type="text" name="url" id="url" value="{{ url|e(true) }}"></p>
<p>Optionally you can give the URL a memorable name</p>
<p><input type="text" id="alias" name="alias">{#
#}<input type="submit" id="submit" value="Do!"></p>
<p><input type="checkbox" name="private" id="private">
<label for="private">make this URL private, so don't list it</label></p>
</form>
{% endblock %}

View File

@ -0,0 +1,8 @@
{% extends 'layout.html' %}
{% block body %}
<h2>Page Not Found</h2>
<p>
The page you have requested does not exist on this server. What about
<a href="{{ url_for('new') }}">adding a new URL</a>?
</p>
{% endblock %}

101
examples/couchy/utils.py Normal file
View File

@ -0,0 +1,101 @@
from os import path
from random import randrange
from random import sample
from jinja2 import Environment
from jinja2 import FileSystemLoader
from werkzeug.local import Local
from werkzeug.local import LocalManager
from werkzeug.routing import Map
from werkzeug.routing import Rule
from werkzeug.urls import url_parse
from werkzeug.utils import cached_property
from werkzeug.wrappers import Response
TEMPLATE_PATH = path.join(path.dirname(__file__), "templates")
STATIC_PATH = path.join(path.dirname(__file__), "static")
ALLOWED_SCHEMES = frozenset(["http", "https", "ftp", "ftps"])
URL_CHARS = "abcdefghijkmpqrstuvwxyzABCDEFGHIJKLMNPQRST23456789"
local = Local()
local_manager = LocalManager([local])
application = local("application")
url_map = Map([Rule("/static/<file>", endpoint="static", build_only=True)])
jinja_env = Environment(loader=FileSystemLoader(TEMPLATE_PATH))
def expose(rule, **kw):
def decorate(f):
kw["endpoint"] = f.__name__
url_map.add(Rule(rule, **kw))
return f
return decorate
def url_for(endpoint, _external=False, **values):
return local.url_adapter.build(endpoint, values, force_external=_external)
jinja_env.globals["url_for"] = url_for
def render_template(template, **context):
return Response(
jinja_env.get_template(template).render(**context), mimetype="text/html"
)
def validate_url(url):
return url_parse(url)[0] in ALLOWED_SCHEMES
def get_random_uid():
return "".join(sample(URL_CHARS, randrange(3, 9)))
class Pagination:
def __init__(self, results, per_page, page, endpoint):
self.results = results
self.per_page = per_page
self.page = page
self.endpoint = endpoint
@cached_property
def count(self):
return len(self.results)
@cached_property
def entries(self):
return self.results[
((self.page - 1) * self.per_page) : (
((self.page - 1) * self.per_page) + self.per_page
)
]
@property
def has_previous(self):
"""Return True if there are pages before the current one."""
return self.page > 1
@property
def has_next(self):
"""Return True if there are pages after the current one."""
return self.page < self.pages
@property
def previous(self):
"""Return the URL for the previous page."""
return url_for(self.endpoint, page=self.page - 1)
@property
def next(self):
"""Return the URL for the next page."""
return url_for(self.endpoint, page=self.page + 1)
@property
def pages(self):
"""Return the number of pages."""
return max(0, self.count - 1) // self.per_page + 1

73
examples/couchy/views.py Normal file
View File

@ -0,0 +1,73 @@
from werkzeug.exceptions import NotFound
from werkzeug.utils import redirect
from .models import URL
from .utils import expose
from .utils import Pagination
from .utils import render_template
from .utils import url_for
from .utils import validate_url
@expose("/")
def new(request):
error = url = ""
if request.method == "POST":
url = request.form.get("url")
alias = request.form.get("alias")
if not validate_url(url):
error = "I'm sorry but you cannot shorten this URL."
elif alias:
if len(alias) > 140:
error = "Your alias is too long"
elif "/" in alias:
error = "Your alias might not include a slash"
elif URL.load(alias):
error = "The alias you have requested exists already"
if not error:
url = URL(
target=url,
public="private" not in request.form,
shorty_id=alias if alias else None,
)
url.store()
uid = url.id
return redirect(url_for("display", uid=uid))
return render_template("new.html", error=error, url=url)
@expose("/display/<uid>")
def display(request, uid):
url = URL.load(uid)
if not url:
raise NotFound()
return render_template("display.html", url=url)
@expose("/u/<uid>")
def link(request, uid):
url = URL.load(uid)
if not url:
raise NotFound()
return redirect(url.target, 301)
@expose("/list/", defaults={"page": 1})
@expose("/list/<int:page>")
def list(request, page):
def wrap(doc):
data = doc.value
data["_id"] = doc.id
return URL.wrap(data)
code = """function(doc) { if (doc.public){ map([doc._id], doc); }}"""
docResults = URL.query(code)
results = [wrap(doc) for doc in docResults]
pagination = Pagination(results, 1, page, "list")
if pagination.page > 1 and not pagination.entries:
raise NotFound()
return render_template("list.html", pagination=pagination)
def not_found(request):
return render_template("not_found.html")

View File

@ -0,0 +1,2 @@
"""Werkzeug powered Teeworlds Server Browser."""
from .application import make_app

View File

@ -0,0 +1,120 @@
import time
from os import path
from threading import Thread
from jinja2 import Environment
from jinja2 import PackageLoader
from werkzeug.exceptions import HTTPException
from werkzeug.exceptions import NotFound
from werkzeug.middleware.shared_data import SharedDataMiddleware
from werkzeug.routing import Map
from werkzeug.routing import Rule
from werkzeug.wrappers import Request
from werkzeug.wrappers import Response
from .db import Database
from .network import ServerBrowser
templates = path.join(path.dirname(__file__), "templates")
pages = {}
url_map = Map([Rule("/shared/<file>", endpoint="shared")])
def make_app(database, interval=120):
return SharedDataMiddleware(
Cup(database, interval),
{"/shared": path.join(path.dirname(__file__), "shared")},
)
class PageMeta(type):
def __init__(cls, name, bases, d):
type.__init__(cls, name, bases, d)
if d.get("url_rule") is not None:
pages[cls.identifier] = cls
url_map.add(
Rule(cls.url_rule, endpoint=cls.identifier, **cls.url_arguments)
)
@property
def identifier(cls):
return cls.__name__.lower()
def _with_metaclass(meta, *bases):
"""Create a base class with a metaclass."""
class metaclass(type):
def __new__(metacls, name, this_bases, d):
return meta(name, bases, d)
return type.__new__(metaclass, "temporary_class", (), {})
class Page(_with_metaclass(PageMeta, object)):
url_arguments = {}
def __init__(self, cup, request, url_adapter):
self.cup = cup
self.request = request
self.url_adapter = url_adapter
def url_for(self, endpoint, **values):
return self.url_adapter.build(endpoint, values)
def process(self):
pass
def render_template(self, template=None):
if template is None:
template = f"{type(self).identifier}.html"
context = dict(self.__dict__)
context.update(url_for=self.url_for, self=self)
return self.cup.render_template(template, context)
def get_response(self):
return Response(self.render_template(), mimetype="text/html")
class Cup:
def __init__(self, database, interval=120):
self.jinja_env = Environment(loader=PackageLoader("cupoftee"), autoescape=True)
self.interval = interval
self.db = Database(database)
self.server_browser = ServerBrowser(self)
self.updater = Thread(None, self.update_server_browser)
self.updater.daemon = True
self.updater.start()
def update_server_browser(self):
while 1:
if self.server_browser.sync():
wait = self.interval
else:
wait = self.interval // 2
time.sleep(wait)
def dispatch_request(self, request):
url_adapter = url_map.bind_to_environ(request.environ)
try:
endpoint, values = url_adapter.match()
page = pages[endpoint](self, request, url_adapter)
response = page.process(**values)
except NotFound:
page = MissingPage(self, request, url_adapter)
response = page.process()
except HTTPException as e:
return e
return response or page.get_response()
def __call__(self, environ, start_response):
request = Request(environ)
return self.dispatch_request(request)(environ, start_response)
def render_template(self, name, **context):
template = self.jinja_env.get_template(name)
return template.render(context)
from cupoftee.pages import MissingPage

67
examples/cupoftee/db.py Normal file
View File

@ -0,0 +1,67 @@
"""A simple object database. As long as the server is not running in
multiprocess mode that's good enough.
"""
import dbm
from pickle import dumps
from pickle import loads
from threading import Lock
class Database:
def __init__(self, filename):
self.filename = filename
self._fs = dbm.open(filename, "cf")
self._local = {}
self._lock = Lock()
def __getitem__(self, key):
with self._lock:
return self._load_key(key)
def _load_key(self, key):
if key in self._local:
return self._local[key]
rv = loads(self._fs[key])
self._local[key] = rv
return rv
def __setitem__(self, key, value):
self._local[key] = value
def __delitem__(self, key):
with self._lock:
self._local.pop(key, None)
if key in self._fs:
del self._fs[key]
def __del__(self):
self.close()
def __contains__(self, key):
with self._lock:
try:
self._load_key(key)
except KeyError:
pass
return key in self._local
def setdefault(self, key, factory):
with self._lock:
try:
rv = self._load_key(key)
except KeyError:
self._local[key] = rv = factory()
return rv
def sync(self):
with self._lock:
for key, value in self._local.items():
self._fs[key] = dumps(value, 2)
self._fs.sync()
def close(self):
try:
self.sync()
self._fs.close()
except Exception:
pass

View File

@ -0,0 +1,124 @@
"""Query the servers for information."""
import socket
from datetime import datetime
from math import log
from .utils import unicodecmp
class ServerError(Exception):
pass
class Syncable:
last_sync = None
def sync(self):
try:
self._sync()
except (OSError, socket.timeout):
return False
self.last_sync = datetime.utcnow()
return True
class ServerBrowser(Syncable):
def __init__(self, cup):
self.cup = cup
self.servers = cup.db.setdefault("servers", dict)
def _sync(self):
to_delete = set(self.servers)
for x in range(1, 17):
addr = (f"master{x}.teeworlds.com", 8300)
print(addr)
try:
self._sync_server_browser(addr, to_delete)
except (OSError, socket.timeout):
continue
for server_id in to_delete:
self.servers.pop(server_id, None)
if not self.servers:
raise OSError("no servers found")
self.cup.db.sync()
def _sync_server_browser(self, addr, to_delete):
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.settimeout(5)
s.sendto(b"\x20\x00\x00\x00\x00\x48\xff\xff\xff\xffreqt", addr)
data = s.recvfrom(1024)[0][14:]
s.close()
for n in range(0, len(data) // 6):
addr = (
".".join(map(str, map(ord, data[n * 6 : n * 6 + 4]))),
ord(data[n * 6 + 5]) * 256 + ord(data[n * 6 + 4]),
)
server_id = f"{addr[0]}:{addr[1]}"
if server_id in self.servers:
if not self.servers[server_id].sync():
continue
else:
try:
self.servers[server_id] = Server(addr, server_id)
except ServerError:
pass
to_delete.discard(server_id)
class Server(Syncable):
def __init__(self, addr, server_id):
self.addr = addr
self.id = server_id
self.players = []
if not self.sync():
raise ServerError("server not responding in time")
def _sync(self):
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.settimeout(1)
s.sendto(b"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xffgief", self.addr)
bits = s.recvfrom(1024)[0][14:].split(b"\x00")
s.close()
self.version, server_name, map_name = bits[:3]
self.name = server_name.decode("latin1")
self.map = map_name.decode("latin1")
self.gametype = bits[3]
self.flags, self.progression, player_count, self.max_players = map(
int, bits[4:8]
)
# sync the player stats
players = {p.name: p for p in self.players}
for i in range(player_count):
name = bits[8 + i * 2].decode("latin1")
score = int(bits[9 + i * 2])
# update existing player
if name in players:
player = players.pop(name)
player.score = score
# add new player
else:
self.players.append(Player(self, name, score))
# delete players that left
for player in players.values():
try:
self.players.remove(player)
except Exception:
pass
# sort the player list and count them
self.players.sort(key=lambda x: -x.score)
self.player_count = len(self.players)
def __cmp__(self, other):
return unicodecmp(self.name, other.name)
class Player:
def __init__(self, server, name, score):
self.server = server
self.name = name
self.score = score
self.size = round(100 + log(max(score, 1)) * 25, 2)

View File

@ -0,0 +1,75 @@
from functools import reduce
from werkzeug.exceptions import NotFound
from werkzeug.utils import redirect
from .application import Page
from .utils import unicodecmp
class ServerList(Page):
url_rule = "/"
def order_link(self, name, title):
cls = ""
link = f"?order_by={name}"
desc = False
if name == self.order_by:
desc = not self.order_desc
cls = f' class="{"down" if desc else "up"}"'
if desc:
link += "&amp;dir=desc"
return f'<a href="{link}"{cls}>{title}</a>'
def process(self):
self.order_by = self.request.args.get("order_by") or "name"
sort_func = {
"name": lambda x: x,
"map": lambda x: x.map,
"gametype": lambda x: x.gametype,
"players": lambda x: x.player_count,
"progression": lambda x: x.progression,
}.get(self.order_by)
if sort_func is None:
return redirect(self.url_for("serverlist"))
self.servers = self.cup.server_browser.servers.values()
self.servers.sort(key=sort_func)
if self.request.args.get("dir") == "desc":
self.servers.reverse()
self.order_desc = True
else:
self.order_desc = False
self.players = reduce(lambda a, b: a + b.players, self.servers, [])
self.players = sorted(self.players, key=lambda a, b: unicodecmp(a.name, b.name))
class Server(Page):
url_rule = "/server/<id>"
def process(self, id):
try:
self.server = self.cup.server_browser.servers[id]
except KeyError:
raise NotFound() from None
class Search(Page):
url_rule = "/search"
def process(self):
self.user = self.request.args.get("user")
if self.user:
self.results = []
for server in self.cup.server_browser.servers.values():
for player in server.players:
if player.name == self.user:
self.results.append(server)
class MissingPage(Page):
def get_response(self):
response = super().get_response()
response.status_code = 404
return response

Binary file not shown.

After

Width:  |  Height:  |  Size: 230 B

BIN
examples/cupoftee/shared/down.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 376 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Some files were not shown because too many files have changed in this diff Show More