├── .devcontainer ├── devcontainer.json └── on-create-command.sh ├── .editorconfig ├── .gitattributes ├── .github ├── ISSUE_TEMPLATE │ ├── bug-report.md │ ├── config.yml │ └── feature-request.md ├── pull_request_template.md └── workflows │ ├── lock.yaml │ ├── pre-commit.yaml │ ├── publish.yaml │ └── tests.yaml ├── .gitignore ├── .pre-commit-config.yaml ├── .readthedocs.yaml ├── CHANGES.rst ├── LICENSE.txt ├── README.md ├── docs ├── Makefile ├── _static │ ├── debug-screenshot.png │ ├── shortcut-icon.png │ ├── shortly.png │ ├── werkzeug-horizontal.png │ └── werkzeug-vertical.png ├── changes.rst ├── conf.py ├── datastructures.rst ├── debug.rst ├── deployment │ ├── apache-httpd.rst │ ├── eventlet.rst │ ├── gevent.rst │ ├── gunicorn.rst │ ├── index.rst │ ├── mod_wsgi.rst │ ├── nginx.rst │ ├── proxy_fix.rst │ ├── uwsgi.rst │ └── waitress.rst ├── exceptions.rst ├── http.rst ├── index.rst ├── installation.rst ├── levels.rst ├── license.rst ├── local.rst ├── make.bat ├── middleware │ ├── dispatcher.rst │ ├── http_proxy.rst │ ├── index.rst │ ├── lint.rst │ ├── profiler.rst │ ├── proxy_fix.rst │ └── shared_data.rst ├── quickstart.rst ├── request_data.rst ├── routing.rst ├── serving.rst ├── terms.rst ├── test.rst ├── tutorial.rst ├── urls.rst ├── utils.rst ├── wrappers.rst └── wsgi.rst ├── examples ├── README.rst ├── coolmagic │ ├── __init__.py │ ├── application.py │ ├── helpers.py │ ├── public │ │ └── style.css │ ├── templates │ │ ├── layout.html │ │ └── static │ │ │ ├── about.html │ │ │ ├── index.html │ │ │ └── not_found.html │ ├── utils.py │ └── views │ │ ├── __init__.py │ │ └── static.py ├── couchy │ ├── README │ ├── __init__.py │ ├── application.py │ ├── models.py │ ├── static │ │ └── style.css │ ├── templates │ │ ├── display.html │ │ ├── layout.html │ │ ├── list.html │ │ ├── new.html │ │ └── not_found.html │ ├── utils.py │ └── views.py ├── cupoftee │ ├── __init__.py │ ├── application.py │ ├── db.py │ ├── network.py │ ├── pages.py │ ├── shared │ │ ├── content.png │ │ ├── down.png │ │ ├── favicon.ico │ │ ├── header.png │ │ ├── logo.png │ │ ├── style.css │ │ └── up.png │ ├── templates │ │ ├── layout.html │ │ ├── missingpage.html │ │ ├── search.html │ │ ├── server.html │ │ └── serverlist.html │ └── utils.py ├── httpbasicauth.py ├── i18nurls │ ├── __init__.py │ ├── application.py │ ├── templates │ │ ├── about.html │ │ ├── blog.html │ │ ├── index.html │ │ └── layout.html │ ├── urls.py │ └── views.py ├── manage-coolmagic.py ├── manage-couchy.py ├── manage-cupoftee.py ├── manage-i18nurls.py ├── manage-plnt.py ├── manage-shorty.py ├── manage-simplewiki.py ├── manage-webpylike.py ├── partial │ ├── README │ └── complex_routing.py ├── plnt │ ├── __init__.py │ ├── database.py │ ├── shared │ │ └── style.css │ ├── sync.py │ ├── templates │ │ ├── about.html │ │ ├── index.html │ │ └── layout.html │ ├── utils.py │ ├── views.py │ └── webapp.py ├── shortly │ ├── shortly.py │ ├── static │ │ └── style.css │ └── templates │ │ ├── 404.html │ │ ├── layout.html │ │ ├── new_url.html │ │ └── short_link_details.html ├── shorty │ ├── __init__.py │ ├── application.py │ ├── models.py │ ├── static │ │ └── style.css │ ├── templates │ │ ├── display.html │ │ ├── layout.html │ │ ├── list.html │ │ ├── new.html │ │ └── not_found.html │ ├── utils.py │ └── views.py ├── simplewiki │ ├── __init__.py │ ├── actions.py │ ├── application.py │ ├── database.py │ ├── shared │ │ └── style.css │ ├── specialpages.py │ ├── templates │ │ ├── action_diff.html │ │ ├── action_edit.html │ │ ├── action_log.html │ │ ├── action_revert.html │ │ ├── action_show.html │ │ ├── layout.html │ │ ├── macros.xml │ │ ├── missing_action.html │ │ ├── page_index.html │ │ ├── page_missing.html │ │ └── recent_changes.html │ └── utils.py ├── upload.py ├── webpylike │ ├── example.py │ └── webpylike.py └── wsecho.py ├── pyproject.toml ├── src └── werkzeug │ ├── __init__.py │ ├── _internal.py │ ├── _reloader.py │ ├── datastructures │ ├── __init__.py │ ├── accept.py │ ├── auth.py │ ├── cache_control.py │ ├── csp.py │ ├── etag.py │ ├── file_storage.py │ ├── headers.py │ ├── mixins.py │ ├── range.py │ └── structures.py │ ├── debug │ ├── __init__.py │ ├── console.py │ ├── repr.py │ ├── shared │ │ ├── ICON_LICENSE.md │ │ ├── console.png │ │ ├── debugger.js │ │ ├── less.png │ │ ├── more.png │ │ └── style.css │ └── tbtools.py │ ├── exceptions.py │ ├── formparser.py │ ├── http.py │ ├── local.py │ ├── middleware │ ├── __init__.py │ ├── dispatcher.py │ ├── http_proxy.py │ ├── lint.py │ ├── profiler.py │ ├── proxy_fix.py │ └── shared_data.py │ ├── py.typed │ ├── routing │ ├── __init__.py │ ├── converters.py │ ├── exceptions.py │ ├── map.py │ ├── matcher.py │ └── rules.py │ ├── sansio │ ├── __init__.py │ ├── http.py │ ├── multipart.py │ ├── request.py │ ├── response.py │ └── utils.py │ ├── security.py │ ├── serving.py │ ├── test.py │ ├── testapp.py │ ├── urls.py │ ├── user_agent.py │ ├── utils.py │ ├── wrappers │ ├── __init__.py │ ├── request.py │ └── response.py │ └── wsgi.py ├── tests ├── conftest.py ├── live_apps │ ├── data_app.py │ ├── reloader_app.py │ ├── run.py │ ├── standard_app.py │ └── streaming_app.py ├── middleware │ ├── test_dispatcher.py │ ├── test_http_proxy.py │ ├── test_lint.py │ ├── test_profiler.py │ ├── test_proxy_fix.py │ └── test_shared_data.py ├── multipart │ ├── firefox3-2png1txt │ │ ├── file1.png │ │ ├── file2.png │ │ ├── request.http │ │ └── text.txt │ ├── firefox3-2pnglongtext │ │ ├── file1.png │ │ ├── file2.png │ │ ├── request.http │ │ └── text.txt │ ├── ie6-2png1txt │ │ ├── file1.png │ │ ├── file2.png │ │ ├── request.http │ │ └── text.txt │ ├── ie7_full_path_request.http │ ├── opera8-2png1txt │ │ ├── file1.png │ │ ├── file2.png │ │ ├── request.http │ │ └── text.txt │ └── webkit3-2png1txt │ │ ├── file1.png │ │ ├── file2.png │ │ ├── request.http │ │ └── text.txt ├── res │ ├── index.html │ └── test.txt ├── sansio │ ├── __init__.py │ ├── test_multipart.py │ ├── test_request.py │ └── test_utils.py ├── test_datastructures.py ├── test_debug.py ├── test_exceptions.py ├── test_formparser.py ├── test_http.py ├── test_internal.py ├── test_local.py ├── test_routing.py ├── test_security.py ├── test_send_file.py ├── test_serving.py ├── test_test.py ├── test_urls.py ├── test_utils.py ├── test_wrappers.py └── test_wsgi.py └── uv.lock /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pallets/werkzeug", 3 | "image": "mcr.microsoft.com/devcontainers/python:3", 4 | "customizations": { 5 | "vscode": { 6 | "settings": { 7 | "python.defaultInterpreterPath": "${workspaceFolder}/.venv", 8 | "python.terminal.activateEnvInCurrentTerminal": true, 9 | "python.terminal.launchArgs": [ 10 | "-X", 11 | "dev" 12 | ] 13 | } 14 | } 15 | }, 16 | "onCreateCommand": ".devcontainer/on-create-command.sh" 17 | } 18 | -------------------------------------------------------------------------------- /.devcontainer/on-create-command.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | python3 -m venv --upgrade-deps .venv 4 | . .venv/bin/activate 5 | pip install -r requirements/dev.txt 6 | pip install -e . 7 | pre-commit install --install-hooks 8 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 4 6 | insert_final_newline = true 7 | trim_trailing_whitespace = true 8 | end_of_line = lf 9 | charset = utf-8 10 | max_line_length = 88 11 | 12 | [*.{css,html,js,json,jsx,scss,ts,tsx,yaml,yml}] 13 | indent_size = 2 14 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Normalize CRLF to LF for all text files 2 | * text=auto 3 | 4 | # Declare binary file types so they won't be normalized 5 | *.png binary 6 | *.jpg binary 7 | tests/**/*.http binary 8 | tests/res/test.txt binary 9 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug-report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Report a bug in Werkzeug (not other projects which depend on Werkzeug) 4 | --- 5 | 6 | 12 | 13 | 19 | 20 | 23 | 24 | Environment: 25 | 26 | - Python version: 27 | - Werkzeug version: 28 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: Questions on Discussions 4 | url: https://github.com/pallets/werkzeug/discussions/ 5 | about: Ask questions about your own code on the Discussions tab. 6 | - name: Questions on Chat 7 | url: https://discord.gg/pallets 8 | about: Ask questions about your own code on our Discord chat. 9 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature-request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest a new feature for Werkzeug 4 | --- 5 | 6 | 10 | 11 | 16 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | 9 | 10 | 16 | 17 | 26 | -------------------------------------------------------------------------------- /.github/workflows/lock.yaml: -------------------------------------------------------------------------------- 1 | name: Lock inactive closed issues 2 | # Lock closed issues that have not received any further activity for two weeks. 3 | # This does not close open issues, only humans may do that. It is easier to 4 | # respond to new issues with fresh examples rather than continuing discussions 5 | # on old issues. 6 | 7 | on: 8 | schedule: 9 | - cron: '0 0 * * *' 10 | permissions: 11 | issues: write 12 | pull-requests: write 13 | discussions: write 14 | concurrency: 15 | group: lock 16 | jobs: 17 | lock: 18 | runs-on: ubuntu-latest 19 | steps: 20 | - uses: dessant/lock-threads@1bf7ec25051fe7c00bdd17e6a7cf3d7bfb7dc771 # v5.0.1 21 | with: 22 | issue-inactive-days: 14 23 | pr-inactive-days: 14 24 | discussion-inactive-days: 14 25 | -------------------------------------------------------------------------------- /.github/workflows/pre-commit.yaml: -------------------------------------------------------------------------------- 1 | name: pre-commit 2 | on: 3 | pull_request: 4 | push: 5 | branches: [main, stable] 6 | jobs: 7 | main: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 11 | - uses: astral-sh/setup-uv@6b9c6063abd6010835644d4c2e1bef4cf5cd0fca # v6.0.1 12 | with: 13 | enable-cache: true 14 | prune-cache: false 15 | - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 16 | id: setup-python 17 | with: 18 | python-version-file: pyproject.toml 19 | - uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 20 | with: 21 | path: ~/.cache/pre-commit 22 | key: pre-commit|${{ hashFiles('pyproject.toml', '.pre-commit-config.yaml') }} 23 | - run: uv run --locked --group pre-commit pre-commit run --show-diff-on-failure --color=always --all-files 24 | - uses: pre-commit-ci/lite-action@5d6cc0eb514c891a40562a58a8e71576c5c7fb43 # v1.1.0 25 | if: ${{ !cancelled() }} 26 | -------------------------------------------------------------------------------- /.github/workflows/publish.yaml: -------------------------------------------------------------------------------- 1 | name: Publish 2 | on: 3 | push: 4 | tags: ['*'] 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | outputs: 9 | hash: ${{ steps.hash.outputs.hash }} 10 | steps: 11 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 12 | - uses: astral-sh/setup-uv@6b9c6063abd6010835644d4c2e1bef4cf5cd0fca # v6.0.1 13 | with: 14 | enable-cache: true 15 | prune-cache: false 16 | - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 17 | with: 18 | python-version-file: pyproject.toml 19 | - run: echo "SOURCE_DATE_EPOCH=$(git log -1 --pretty=%ct)" >> $GITHUB_ENV 20 | - run: uv build 21 | - name: generate hash 22 | id: hash 23 | run: cd dist && echo "hash=$(sha256sum * | base64 -w0)" >> $GITHUB_OUTPUT 24 | - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 25 | with: 26 | path: ./dist 27 | provenance: 28 | needs: [build] 29 | permissions: 30 | actions: read 31 | id-token: write 32 | contents: write 33 | # Can't pin with hash due to how this workflow works. 34 | uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v2.1.0 35 | with: 36 | base64-subjects: ${{ needs.build.outputs.hash }} 37 | create-release: 38 | needs: [provenance] 39 | runs-on: ubuntu-latest 40 | permissions: 41 | contents: write 42 | steps: 43 | - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 44 | - name: create release 45 | run: > 46 | gh release create --draft --repo ${{ github.repository }} 47 | ${{ github.ref_name }} 48 | *.intoto.jsonl/* artifact/* 49 | env: 50 | GH_TOKEN: ${{ github.token }} 51 | publish-pypi: 52 | needs: [provenance] 53 | environment: 54 | name: publish 55 | url: https://pypi.org/project/Werkzeug/${{ github.ref_name }} 56 | runs-on: ubuntu-latest 57 | permissions: 58 | id-token: write 59 | steps: 60 | - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 61 | - uses: pypa/gh-action-pypi-publish@76f52bc884231f62b9a034ebfe128415bbaabdfc # v1.12.4 62 | with: 63 | packages-dir: artifact/ 64 | -------------------------------------------------------------------------------- /.github/workflows/tests.yaml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | on: 3 | pull_request: 4 | paths-ignore: ['docs/**', 'README.md'] 5 | push: 6 | branches: [main, stable] 7 | paths-ignore: ['docs/**', 'README.md'] 8 | jobs: 9 | tests: 10 | name: ${{ matrix.name || matrix.python }} 11 | runs-on: ${{ matrix.os || 'ubuntu-latest' }} 12 | strategy: 13 | fail-fast: false 14 | matrix: 15 | include: 16 | - {python: '3.13'} 17 | - {name: Windows, python: '3.13', os: windows-latest} 18 | - {name: Mac, python: '3.13', os: macos-latest} 19 | - {python: '3.12'} 20 | - {python: '3.11'} 21 | - {python: '3.10'} 22 | - {python: '3.9'} 23 | - {name: PyPy, python: 'pypy-3.11', tox: pypy3.11} 24 | steps: 25 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 26 | - uses: astral-sh/setup-uv@6b9c6063abd6010835644d4c2e1bef4cf5cd0fca # v6.0.1 27 | with: 28 | enable-cache: true 29 | prune-cache: false 30 | - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 31 | with: 32 | python-version: ${{ matrix.python }} 33 | - run: uv run --locked tox run -e ${{ matrix.tox || format('py{0}', matrix.python) }} 34 | typing: 35 | runs-on: ubuntu-latest 36 | steps: 37 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 38 | - uses: astral-sh/setup-uv@6b9c6063abd6010835644d4c2e1bef4cf5cd0fca # v6.0.1 39 | with: 40 | enable-cache: true 41 | prune-cache: false 42 | - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 43 | with: 44 | python-version-file: pyproject.toml 45 | - name: cache mypy 46 | uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 47 | with: 48 | path: ./.mypy_cache 49 | key: mypy|${{ hashFiles('pyproject.toml') }} 50 | - run: uv run --locked tox run -e typing 51 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | .vscode/ 3 | __pycache__/ 4 | dist/ 5 | .coverage* 6 | htmlcov/ 7 | .tox/ 8 | docs/_build/ 9 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/astral-sh/ruff-pre-commit 3 | rev: 24e02b24b8ab2b7c76225602d13fa60e12d114e6 # frozen: v0.11.9 4 | hooks: 5 | - id: ruff 6 | - id: ruff-format 7 | - repo: https://github.com/astral-sh/uv-pre-commit 8 | rev: 14ac15b122e538e407d036ff45e3895b7cf4a2bf # frozen: 0.7.3 9 | hooks: 10 | - id: uv-lock 11 | - repo: https://github.com/pre-commit/pre-commit-hooks 12 | rev: cef0300fd0fc4d2a87a85fa2093c6b283ea36f4b # frozen: v5.0.0 13 | hooks: 14 | - id: check-merge-conflict 15 | - id: debug-statements 16 | - id: fix-byte-order-marker 17 | - id: trailing-whitespace 18 | - id: end-of-file-fixer 19 | -------------------------------------------------------------------------------- /.readthedocs.yaml: -------------------------------------------------------------------------------- 1 | version: 2 2 | build: 3 | os: ubuntu-24.04 4 | tools: 5 | python: '3.13' 6 | commands: 7 | - asdf plugin add uv 8 | - asdf install uv latest 9 | - asdf global uv latest 10 | - uv run --group docs sphinx-build -W -b dirhtml docs $READTHEDOCS_OUTPUT/html 11 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright 2007 Pallets 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | 1. Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | 10 | 2. Redistributions in binary form must reproduce the above copyright 11 | notice, this list of conditions and the following disclaimer in the 12 | documentation and/or other materials provided with the distribution. 13 | 14 | 3. Neither the name of the copyright holder nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 21 | PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 | HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 24 | TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 25 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 26 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 27 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 28 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Werkzeug 2 | 3 | *werkzeug* German noun: "tool". Etymology: *werk* ("work"), *zeug* ("stuff") 4 | 5 | Werkzeug is a comprehensive [WSGI][] web application library. It began as 6 | a simple collection of various utilities for WSGI applications and has 7 | become one of the most advanced WSGI utility libraries. 8 | 9 | It includes: 10 | 11 | - An interactive debugger that allows inspecting stack traces and 12 | source code in the browser with an interactive interpreter for any 13 | frame in the stack. 14 | - A full-featured request object with objects to interact with 15 | headers, query args, form data, files, and cookies. 16 | - A response object that can wrap other WSGI applications and handle 17 | streaming data. 18 | - A routing system for matching URLs to endpoints and generating URLs 19 | for endpoints, with an extensible system for capturing variables 20 | from URLs. 21 | - HTTP utilities to handle entity tags, cache control, dates, user 22 | agents, cookies, files, and more. 23 | - A threaded WSGI server for use while developing applications 24 | locally. 25 | - A test client for simulating HTTP requests during testing without 26 | requiring running a server. 27 | 28 | Werkzeug doesn't enforce any dependencies. It is up to the developer to 29 | choose a template engine, database adapter, and even how to handle 30 | requests. It can be used to build all sorts of end user applications 31 | such as blogs, wikis, or bulletin boards. 32 | 33 | [Flask][] wraps Werkzeug, using it to handle the details of WSGI while 34 | providing more structure and patterns for defining powerful 35 | applications. 36 | 37 | [WSGI]: https://wsgi.readthedocs.io/en/latest/ 38 | [Flask]: https://www.palletsprojects.com/p/flask/ 39 | 40 | 41 | ## A Simple Example 42 | 43 | ```python 44 | # save this as app.py 45 | from werkzeug.wrappers import Request, Response 46 | 47 | @Request.application 48 | def application(request: Request) -> Response: 49 | return Response("Hello, World!") 50 | 51 | if __name__ == "__main__": 52 | from werkzeug.serving import run_simple 53 | run_simple("127.0.0.1", 5000, application) 54 | ``` 55 | 56 | ``` 57 | $ python -m app 58 | * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit) 59 | ``` 60 | 61 | 62 | ## Donate 63 | 64 | The Pallets organization develops and supports Werkzeug and other 65 | popular packages. In order to grow the community of contributors and 66 | users, and allow the maintainers to devote more time to the projects, 67 | [please donate today][]. 68 | 69 | [please donate today]: https://palletsprojects.com/donate 70 | 71 | ## Contributing 72 | 73 | See our [detailed contributing documentation][contrib] for many ways to 74 | contribute, including reporting issues, requesting features, asking or answering 75 | questions, and making PRs. 76 | 77 | [contrib]: https://palletsprojects.com/contributing/ 78 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | SOURCEDIR = . 8 | BUILDDIR = _build 9 | 10 | # Put it first so that "make" without argument is like "make help". 11 | help: 12 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 13 | 14 | .PHONY: help Makefile 15 | 16 | # Catch-all target: route all unknown targets to Sphinx using the new 17 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 18 | %: Makefile 19 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 20 | -------------------------------------------------------------------------------- /docs/_static/debug-screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pallets/werkzeug/99fc293bc7dccd6a7dc96b96012d4aa849794ba4/docs/_static/debug-screenshot.png -------------------------------------------------------------------------------- /docs/_static/shortcut-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pallets/werkzeug/99fc293bc7dccd6a7dc96b96012d4aa849794ba4/docs/_static/shortcut-icon.png -------------------------------------------------------------------------------- /docs/_static/shortly.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pallets/werkzeug/99fc293bc7dccd6a7dc96b96012d4aa849794ba4/docs/_static/shortly.png -------------------------------------------------------------------------------- /docs/_static/werkzeug-horizontal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pallets/werkzeug/99fc293bc7dccd6a7dc96b96012d4aa849794ba4/docs/_static/werkzeug-horizontal.png -------------------------------------------------------------------------------- /docs/_static/werkzeug-vertical.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pallets/werkzeug/99fc293bc7dccd6a7dc96b96012d4aa849794ba4/docs/_static/werkzeug-vertical.png -------------------------------------------------------------------------------- /docs/changes.rst: -------------------------------------------------------------------------------- 1 | Changes 2 | ======= 3 | 4 | .. include:: ../CHANGES.rst 5 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | from pallets_sphinx_themes import get_version 2 | from pallets_sphinx_themes import ProjectLink 3 | 4 | # Project -------------------------------------------------------------- 5 | 6 | project = "Werkzeug" 7 | copyright = "2007 Pallets" 8 | author = "Pallets" 9 | release, version = get_version("Werkzeug") 10 | 11 | # General -------------------------------------------------------------- 12 | 13 | default_role = "code" 14 | extensions = [ 15 | "sphinx.ext.autodoc", 16 | "sphinx.ext.extlinks", 17 | "sphinx.ext.intersphinx", 18 | "sphinxcontrib.log_cabinet", 19 | "pallets_sphinx_themes", 20 | ] 21 | autoclass_content = "both" 22 | autodoc_member_order = "bysource" 23 | autodoc_typehints = "description" 24 | autodoc_preserve_defaults = True 25 | extlinks = { 26 | "issue": ("https://github.com/pallets/werkzeug/issues/%s", "#%s"), 27 | "pr": ("https://github.com/pallets/werkzeug/pull/%s", "#%s"), 28 | "ghsa": ("https://github.com/advisories/GHSA-%s", "GHSA-%s"), 29 | } 30 | intersphinx_mapping = { 31 | "python": ("https://docs.python.org/3/", None), 32 | } 33 | 34 | # HTML ----------------------------------------------------------------- 35 | 36 | html_theme = "werkzeug" 37 | html_theme_options = {"index_sidebar_logo": False} 38 | html_context = { 39 | "project_links": [ 40 | ProjectLink("Donate", "https://palletsprojects.com/donate"), 41 | ProjectLink("PyPI Releases", "https://pypi.org/project/Werkzeug/"), 42 | ProjectLink("Source Code", "https://github.com/pallets/werkzeug/"), 43 | ProjectLink("Issue Tracker", "https://github.com/pallets/werkzeug/issues/"), 44 | ProjectLink("Chat", "https://discord.gg/pallets"), 45 | ] 46 | } 47 | html_sidebars = { 48 | "index": ["project.html", "localtoc.html", "searchbox.html", "ethicalads.html"], 49 | "**": ["localtoc.html", "relations.html", "searchbox.html", "ethicalads.html"], 50 | } 51 | singlehtml_sidebars = {"index": ["project.html", "localtoc.html", "ethicalads.html"]} 52 | html_static_path = ["_static"] 53 | html_favicon = "_static/shortcut-icon.png" 54 | html_logo = "_static/werkzeug-vertical.png" 55 | html_title = f"Werkzeug Documentation ({version})" 56 | html_show_sourcelink = False 57 | -------------------------------------------------------------------------------- /docs/deployment/apache-httpd.rst: -------------------------------------------------------------------------------- 1 | Apache httpd 2 | ============ 3 | 4 | `Apache httpd`_ is a fast, production level HTTP server. When serving 5 | your application with one of the WSGI servers listed in :doc:`index`, it 6 | is often good or necessary to put a dedicated HTTP server in front of 7 | it. This "reverse proxy" can handle incoming requests, TLS, and other 8 | security and performance concerns better than the WSGI server. 9 | 10 | httpd can be installed using your system package manager, or a pre-built 11 | executable for Windows. Installing and running httpd itself is outside 12 | the scope of this doc. This page outlines the basics of configuring 13 | httpd to proxy your application. Be sure to read its documentation to 14 | understand what features are available. 15 | 16 | .. _Apache httpd: https://httpd.apache.org/ 17 | 18 | 19 | Domain Name 20 | ----------- 21 | 22 | Acquiring and configuring a domain name is outside the scope of this 23 | doc. In general, you will buy a domain name from a registrar, pay for 24 | server space with a hosting provider, and then point your registrar 25 | at the hosting provider's name servers. 26 | 27 | To simulate this, you can also edit your ``hosts`` file, located at 28 | ``/etc/hosts`` on Linux. Add a line that associates a name with the 29 | local IP. 30 | 31 | Modern Linux systems may be configured to treat any domain name that 32 | ends with ``.localhost`` like this without adding it to the ``hosts`` 33 | file. 34 | 35 | .. code-block:: python 36 | :caption: ``/etc/hosts`` 37 | 38 | 127.0.0.1 hello.localhost 39 | 40 | 41 | Configuration 42 | ------------- 43 | 44 | The httpd configuration is located at ``/etc/httpd/conf/httpd.conf`` on 45 | Linux. It may be different depending on your operating system. Check the 46 | docs and look for ``httpd.conf``. 47 | 48 | Remove or comment out any existing ``DocumentRoot`` directive. Add the 49 | config lines below. We'll assume the WSGI server is listening locally at 50 | ``http://127.0.0.1:8000``. 51 | 52 | .. code-block:: apache 53 | :caption: ``/etc/httpd/conf/httpd.conf`` 54 | 55 | LoadModule proxy_module modules/mod_proxy.so 56 | LoadModule proxy_http_module modules/mod_proxy_http.so 57 | ProxyPass / http://127.0.0.1:8000/ 58 | RequestHeader set X-Forwarded-Proto http 59 | RequestHeader set X-Forwarded-Prefix / 60 | 61 | The ``LoadModule`` lines might already exist. If so, make sure they are 62 | uncommented instead of adding them manually. 63 | 64 | Then :doc:`proxy_fix` so that your application uses the ``X-Forwarded`` 65 | headers. ``X-Forwarded-For`` and ``X-Forwarded-Host`` are automatically 66 | set by ``ProxyPass``. 67 | 68 | 69 | Static Files 70 | ------------ 71 | 72 | If your application has static files such as JavaScript, CSS, and 73 | images, it will be more efficient to let Nginx serve them directly 74 | rather than going through the Python application. 75 | 76 | Assuming the static files are expected to be available under the 77 | ``/static/`` URL, and are stored at ``/home/project/static/``, add the 78 | following to the config above. 79 | 80 | .. code-block:: apache 81 | 82 | Alias /static/ /home/project/static/ 83 | -------------------------------------------------------------------------------- /docs/deployment/eventlet.rst: -------------------------------------------------------------------------------- 1 | eventlet 2 | ======== 3 | 4 | Prefer using :doc:`gunicorn` with eventlet workers rather than using 5 | `eventlet`_ directly. Gunicorn provides a much more configurable and 6 | production-tested server. 7 | 8 | `eventlet`_ allows writing asynchronous, coroutine-based code that looks 9 | like standard synchronous Python. It uses `greenlet`_ to enable task 10 | switching without writing ``async/await`` or using ``asyncio``. 11 | 12 | :doc:`gevent` is another library that does the same thing. Certain 13 | dependencies you have, or other considerations, may affect which of the 14 | two you choose to use. 15 | 16 | eventlet provides a WSGI server that can handle many connections at once 17 | instead of one per worker process. You must actually use eventlet in 18 | your own code to see any benefit to using the server. 19 | 20 | .. _eventlet: https://eventlet.net/ 21 | .. _greenlet: https://greenlet.readthedocs.io/en/latest/ 22 | 23 | 24 | Installing 25 | ---------- 26 | 27 | When using eventlet, greenlet>=1.0 is required, otherwise context locals 28 | such as ``request`` will not work as expected. When using PyPy, 29 | PyPy>=7.3.7 is required. 30 | 31 | Create a virtualenv, install your application, then install 32 | ``eventlet``. 33 | 34 | .. code-block:: text 35 | 36 | $ cd hello-app 37 | $ python -m venv venv 38 | $ . venv/bin/activate 39 | $ pip install . # install your application 40 | $ pip install eventlet 41 | 42 | 43 | Running 44 | ------- 45 | 46 | To use eventlet to serve your application, write a script that imports 47 | its ``wsgi.server``, as well as your app or app factory. 48 | 49 | .. code-block:: python 50 | :caption: ``wsgi.py`` 51 | 52 | import eventlet 53 | from eventlet import wsgi 54 | from hello import create_app 55 | 56 | app = create_app() 57 | wsgi.server(eventlet.listen(("127.0.0.1", 8000), app) 58 | 59 | .. code-block:: text 60 | 61 | $ python wsgi.py 62 | (x) wsgi starting up on http://127.0.0.1:8000 63 | 64 | 65 | Binding Externally 66 | ------------------ 67 | 68 | eventlet should not be run as root because it would cause your 69 | application code to run as root, which is not secure. However, this 70 | means it will not be possible to bind to port 80 or 443. Instead, a 71 | reverse proxy such as :doc:`nginx` or :doc:`apache-httpd` should be used 72 | in front of eventlet. 73 | 74 | You can bind to all external IPs on a non-privileged port by using 75 | ``0.0.0.0`` in the server arguments shown in the previous section. 76 | Don't do this when using a reverse proxy setup, otherwise it will be 77 | possible to bypass the proxy. 78 | 79 | ``0.0.0.0`` is not a valid address to navigate to, you'd use a specific 80 | IP address in your browser. 81 | -------------------------------------------------------------------------------- /docs/deployment/gevent.rst: -------------------------------------------------------------------------------- 1 | gevent 2 | ====== 3 | 4 | Prefer using :doc:`gunicorn` or :doc:`uwsgi` with gevent workers rather 5 | than using `gevent`_ directly. Gunicorn and uWSGI provide much more 6 | configurable and production-tested servers. 7 | 8 | `gevent`_ allows writing asynchronous, coroutine-based code that looks 9 | like standard synchronous Python. It uses `greenlet`_ to enable task 10 | switching without writing ``async/await`` or using ``asyncio``. 11 | 12 | :doc:`eventlet` is another library that does the same thing. Certain 13 | dependencies you have, or other considerations, may affect which of the 14 | two you choose to use. 15 | 16 | gevent provides a WSGI server that can handle many connections at once 17 | instead of one per worker process. You must actually use gevent in your 18 | own code to see any benefit to using the server. 19 | 20 | .. _gevent: https://www.gevent.org/ 21 | .. _greenlet: https://greenlet.readthedocs.io/en/latest/ 22 | 23 | 24 | Installing 25 | ---------- 26 | 27 | When using gevent, greenlet>=1.0 is required, otherwise context locals 28 | such as ``request`` will not work as expected. When using PyPy, 29 | PyPy>=7.3.7 is required. 30 | 31 | Create a virtualenv, install your application, then install ``gevent``. 32 | 33 | .. code-block:: text 34 | 35 | $ cd hello-app 36 | $ python -m venv venv 37 | $ . venv/bin/activate 38 | $ pip install . # install your application 39 | $ pip install gevent 40 | 41 | 42 | Running 43 | ------- 44 | 45 | To use gevent to serve your application, write a script that imports its 46 | ``WSGIServer``, as well as your app or app factory. 47 | 48 | .. code-block:: python 49 | :caption: ``wsgi.py`` 50 | 51 | from gevent.pywsgi import WSGIServer 52 | from hello import create_app 53 | 54 | app = create_app() 55 | http_server = WSGIServer(("127.0.0.1", 8000), app) 56 | http_server.serve_forever() 57 | 58 | .. code-block:: text 59 | 60 | $ python wsgi.py 61 | 62 | No output is shown when the server starts. 63 | 64 | 65 | Binding Externally 66 | ------------------ 67 | 68 | gevent should not be run as root because it would cause your 69 | application code to run as root, which is not secure. However, this 70 | means it will not be possible to bind to port 80 or 443. Instead, a 71 | reverse proxy such as :doc:`nginx` or :doc:`apache-httpd` should be used 72 | in front of gevent. 73 | 74 | You can bind to all external IPs on a non-privileged port by using 75 | ``0.0.0.0`` in the server arguments shown in the previous section. Don't 76 | do this when using a reverse proxy setup, otherwise it will be possible 77 | to bypass the proxy. 78 | 79 | ``0.0.0.0`` is not a valid address to navigate to, you'd use a specific 80 | IP address in your browser. 81 | -------------------------------------------------------------------------------- /docs/deployment/index.rst: -------------------------------------------------------------------------------- 1 | Deploying to Production 2 | ======================= 3 | 4 | After developing your application, you'll want to make it available 5 | publicly to other users. When you're developing locally, you're probably 6 | using the built-in development server, debugger, and reloader. These 7 | should not be used in production. Instead, you should use a dedicated 8 | WSGI server or hosting platform, some of which will be described here. 9 | 10 | "Production" means "not development", which applies whether you're 11 | serving your application publicly to millions of users or privately / 12 | locally to a single user. **Do not use the development server when 13 | deploying to production. It is intended for use only during local 14 | development. It is not designed to be particularly secure, stable, or 15 | efficient.** 16 | 17 | Self-Hosted Options 18 | ------------------- 19 | 20 | Werkzeug is a WSGI *application*. A WSGI *server* is used to run the 21 | application, converting incoming HTTP requests to the standard WSGI 22 | environ, and converting outgoing WSGI responses to HTTP responses. 23 | 24 | The primary goal of these docs is to familiarize you with the concepts 25 | involved in running a WSGI application using a production WSGI server 26 | and HTTP server. There are many WSGI servers and HTTP servers, with many 27 | configuration possibilities. The pages below discuss the most common 28 | servers, and show the basics of running each one. The next section 29 | discusses platforms that can manage this for you. 30 | 31 | .. toctree:: 32 | :maxdepth: 1 33 | 34 | gunicorn 35 | waitress 36 | mod_wsgi 37 | uwsgi 38 | gevent 39 | eventlet 40 | 41 | WSGI servers have HTTP servers built-in. However, a dedicated HTTP 42 | server may be safer, more efficient, or more capable. Putting an HTTP 43 | server in front of the WSGI server is called a "reverse proxy." 44 | 45 | .. toctree:: 46 | :maxdepth: 1 47 | 48 | proxy_fix 49 | nginx 50 | apache-httpd 51 | 52 | This list is not exhaustive, and you should evaluate these and other 53 | servers based on your application's needs. Different servers will have 54 | different capabilities, configuration, and support. 55 | 56 | 57 | Hosting Platforms 58 | ----------------- 59 | 60 | There are many services available for hosting web applications without 61 | needing to maintain your own server, networking, domain, etc. Some 62 | services may have a free tier up to a certain time or bandwidth. Many of 63 | these services use one of the WSGI servers described above, or a similar 64 | interface. 65 | 66 | You should evaluate services based on your application's needs. 67 | Different services will have different capabilities, configuration, 68 | pricing, and support. 69 | 70 | You'll probably need to :doc:`proxy_fix` when using most hosting 71 | platforms. 72 | -------------------------------------------------------------------------------- /docs/deployment/mod_wsgi.rst: -------------------------------------------------------------------------------- 1 | mod_wsgi 2 | ======== 3 | 4 | `mod_wsgi`_ is a WSGI server integrated with the `Apache httpd`_ server. 5 | The modern `mod_wsgi-express`_ command makes it easy to configure and 6 | start the server without needing to write Apache httpd configuration. 7 | 8 | * Tightly integrated with Apache httpd. 9 | * Supports Windows directly. 10 | * Requires a compiler and the Apache development headers to install. 11 | * Does not require a reverse proxy setup. 12 | 13 | This page outlines the basics of running mod_wsgi-express, not the more 14 | complex installation and configuration with httpd. Be sure to read the 15 | `mod_wsgi-express`_, `mod_wsgi`_, and `Apache httpd`_ documentation to 16 | understand what features are available. 17 | 18 | .. _mod_wsgi-express: https://pypi.org/project/mod-wsgi/ 19 | .. _mod_wsgi: https://modwsgi.readthedocs.io/ 20 | .. _Apache httpd: https://httpd.apache.org/ 21 | 22 | 23 | Installing 24 | ---------- 25 | 26 | Installing mod_wsgi requires a compiler and the Apache server and 27 | development headers installed. You will get an error if they are not. 28 | How to install them depends on the OS and package manager that you use. 29 | 30 | Create a virtualenv, install your application, then install 31 | ``mod_wsgi``. 32 | 33 | .. code-block:: text 34 | 35 | $ cd hello-app 36 | $ python -m venv venv 37 | $ . venv/bin/activate 38 | $ pip install . # install your application 39 | $ pip install mod_wsgi 40 | 41 | 42 | Running 43 | ------- 44 | 45 | The only argument to ``mod_wsgi-express`` specifies a script containing 46 | your application, which must be called ``application``. You can 47 | write a small script to import your app with this name, or to create it 48 | if using the app factory pattern. 49 | 50 | .. code-block:: python 51 | :caption: ``wsgi.py`` 52 | 53 | from hello import app 54 | 55 | application = app 56 | 57 | .. code-block:: python 58 | :caption: ``wsgi.py`` 59 | 60 | from hello import create_app 61 | 62 | application = create_app() 63 | 64 | Now run the ``mod_wsgi-express start-server`` command. 65 | 66 | .. code-block:: text 67 | 68 | $ mod_wsgi-express start-server wsgi.py --processes 4 69 | 70 | The ``--processes`` option specifies the number of worker processes to 71 | run; a starting value could be ``CPU * 2``. 72 | 73 | Logs for each request aren't show in the terminal. If an error occurs, 74 | its information is written to the error log file shown when starting the 75 | server. 76 | 77 | 78 | Binding Externally 79 | ------------------ 80 | 81 | Unlike the other WSGI servers in these docs, mod_wsgi can be run as 82 | root to bind to privileged ports like 80 and 443. However, it must be 83 | configured to drop permissions to a different user and group for the 84 | worker processes. 85 | 86 | For example, if you created a ``hello`` user and group, you should 87 | install your virtualenv and application as that user, then tell 88 | mod_wsgi to drop to that user after starting. 89 | 90 | .. code-block:: text 91 | 92 | $ sudo /home/hello/venv/bin/mod_wsgi-express start-server \ 93 | /home/hello/wsgi.py \ 94 | --user hello --group hello --port 80 --processes 4 95 | -------------------------------------------------------------------------------- /docs/deployment/nginx.rst: -------------------------------------------------------------------------------- 1 | nginx 2 | ===== 3 | 4 | `nginx`_ is a fast, production level HTTP server. When serving your 5 | application with one of the WSGI servers listed in :doc:`index`, it is 6 | often good or necessary to put a dedicated HTTP server in front of it. 7 | This "reverse proxy" can handle incoming requests, TLS, and other 8 | security and performance concerns better than the WSGI server. 9 | 10 | Nginx can be installed using your system package manager, or a pre-built 11 | executable for Windows. Installing and running Nginx itself is outside 12 | the scope of this doc. This page outlines the basics of configuring 13 | Nginx to proxy your application. Be sure to read its documentation to 14 | understand what features are available. 15 | 16 | .. _nginx: https://nginx.org/ 17 | 18 | 19 | Domain Name 20 | ----------- 21 | 22 | Acquiring and configuring a domain name is outside the scope of this 23 | doc. In general, you will buy a domain name from a registrar, pay for 24 | server space with a hosting provider, and then point your registrar 25 | at the hosting provider's name servers. 26 | 27 | To simulate this, you can also edit your ``hosts`` file, located at 28 | ``/etc/hosts`` on Linux. Add a line that associates a name with the 29 | local IP. 30 | 31 | Modern Linux systems may be configured to treat any domain name that 32 | ends with ``.localhost`` like this without adding it to the ``hosts`` 33 | file. 34 | 35 | .. code-block:: python 36 | :caption: ``/etc/hosts`` 37 | 38 | 127.0.0.1 hello.localhost 39 | 40 | 41 | Configuration 42 | ------------- 43 | 44 | The nginx configuration is located at ``/etc/nginx/nginx.conf`` on 45 | Linux. It may be different depending on your operating system. Check the 46 | docs and look for ``nginx.conf``. 47 | 48 | Remove or comment out any existing ``server`` section. Add a ``server`` 49 | section and use the ``proxy_pass`` directive to point to the address the 50 | WSGI server is listening on. We'll assume the WSGI server is listening 51 | locally at ``http://127.0.0.1:8000``. 52 | 53 | .. code-block:: nginx 54 | :caption: ``/etc/nginx.conf`` 55 | 56 | server { 57 | listen 80; 58 | server_name _; 59 | 60 | location / { 61 | proxy_pass http://127.0.0.1:8000/; 62 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 63 | proxy_set_header X-Forwarded-Proto $scheme; 64 | proxy_set_header X-Forwarded-Host $host; 65 | proxy_set_header X-Forwarded-Prefix /; 66 | } 67 | } 68 | 69 | Then :doc:`proxy_fix` so that your application uses these headers. 70 | 71 | 72 | Static Files 73 | ------------ 74 | 75 | If your application has static files such as JavaScript, CSS, and 76 | images, it will be more efficient to let Nginx serve them directly 77 | rather than going through the Python application. 78 | 79 | Assuming the static files are expected to be available under the 80 | ``/static/`` URL, and are stored at ``/home/project/static/``, add the 81 | following to the ``server`` block above. 82 | 83 | .. code-block:: nginx 84 | 85 | location /static { 86 | alias /home/project/static; 87 | } 88 | -------------------------------------------------------------------------------- /docs/deployment/proxy_fix.rst: -------------------------------------------------------------------------------- 1 | Tell Werkzeug it is Behind a Proxy 2 | ================================== 3 | 4 | When using a reverse proxy, or many Python hosting platforms, the proxy 5 | will intercept and forward all external requests to the local WSGI 6 | server. 7 | 8 | From the WSGI server and application's perspectives, requests are now 9 | coming from the HTTP server to the local address, rather than from 10 | the remote address to the external server address. 11 | 12 | HTTP servers should set ``X-Forwarded-`` headers to pass on the real 13 | values to the application. The application can then be told to trust and 14 | use those values by wrapping it with the 15 | :doc:`../middleware/proxy_fix` middleware provided by Werkzeug. 16 | 17 | This middleware should only be used if the application is actually 18 | behind a proxy, and should be configured with the number of proxies that 19 | are chained in front of it. Not all proxies set all the headers. Since 20 | incoming headers can be faked, you must set how many proxies are setting 21 | each header so the middleware knows what to trust. 22 | 23 | .. code-block:: python 24 | 25 | from werkzeug.middleware.proxy_fix import ProxyFix 26 | 27 | app.wsgi_app = ProxyFix( 28 | app.wsgi_app, x_for=1, x_proto=1, x_host=1, x_prefix=1 29 | ) 30 | 31 | Remember, only apply this middleware if you are behind a proxy, and set 32 | the correct number of proxies that set each header. It can be a security 33 | issue if you get this configuration wrong. 34 | -------------------------------------------------------------------------------- /docs/deployment/waitress.rst: -------------------------------------------------------------------------------- 1 | Waitress 2 | ======== 3 | 4 | `Waitress`_ is a pure Python WSGI server. 5 | 6 | * It is easy to configure. 7 | * It supports Windows directly. 8 | * It is easy to install as it does not require additional dependencies 9 | or compilation. 10 | * It does not support streaming requests, full request data is always 11 | buffered. 12 | * It uses a single process with multiple thread workers. 13 | 14 | This page outlines the basics of running Waitress. Be sure to read its 15 | documentation and ``waitress-serve --help`` to understand what features 16 | are available. 17 | 18 | .. _Waitress: https://docs.pylonsproject.org/projects/waitress/ 19 | 20 | 21 | Installing 22 | ---------- 23 | 24 | Create a virtualenv, install your application, then install 25 | ``waitress``. 26 | 27 | .. code-block:: text 28 | 29 | $ cd hello-app 30 | $ python -m venv venv 31 | $ . venv/bin/activate 32 | $ pip install . # install your application 33 | $ pip install waitress 34 | 35 | 36 | Running 37 | ------- 38 | 39 | The only required argument to ``waitress-serve`` tells it how to load 40 | your application. The syntax is ``{module}:{app}``. ``module`` is 41 | the dotted import name to the module with your application. ``app`` is 42 | the variable with the application. If you're using the app factory 43 | pattern, use ``--call {module}:{factory}`` instead. 44 | 45 | .. code-block:: text 46 | 47 | # equivalent to 'from hello import app' 48 | $ waitress-serve hello:app --host 127.0.0.1 49 | 50 | # equivalent to 'from hello import create_app; create_app()' 51 | $ waitress-serve --call hello:create_app --host 127.0.0.1 52 | 53 | Serving on http://127.0.0.1:8080 54 | 55 | The ``--host`` option binds the server to local ``127.0.0.1`` only. 56 | 57 | Logs for each request aren't shown, only errors are shown. Logging can 58 | be configured through the Python interface instead of the command line. 59 | 60 | 61 | Binding Externally 62 | ------------------ 63 | 64 | Waitress should not be run as root because it would cause your 65 | application code to run as root, which is not secure. However, this 66 | means it will not be possible to bind to port 80 or 443. Instead, a 67 | reverse proxy such as :doc:`nginx` or :doc:`apache-httpd` should be used 68 | in front of Waitress. 69 | 70 | You can bind to all external IPs on a non-privileged port by not 71 | specifying the ``--host`` option. Don't do this when using a revers 72 | proxy setup, otherwise it will be possible to bypass the proxy. 73 | 74 | ``0.0.0.0`` is not a valid address to navigate to, you'd use a specific 75 | IP address in your browser. 76 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. rst-class:: hide-header 2 | 3 | Werkzeug 4 | ======== 5 | 6 | .. image:: _static/werkzeug-horizontal.png 7 | :align: center 8 | :target: https://werkzeug.palletsprojects.com 9 | 10 | *werkzeug* German noun: "tool". 11 | Etymology: *werk* ("work"), *zeug* ("stuff") 12 | 13 | Werkzeug is a comprehensive `WSGI`_ web application library. It began as 14 | a simple collection of various utilities for WSGI applications and has 15 | become one of the most advanced WSGI utility libraries. 16 | 17 | Werkzeug doesn't enforce any dependencies. It is up to the developer to 18 | choose a template engine, database adapter, and even how to handle 19 | requests. 20 | 21 | .. _WSGI: https://wsgi.readthedocs.io/en/latest/ 22 | 23 | 24 | Getting Started 25 | --------------- 26 | 27 | .. toctree:: 28 | :maxdepth: 2 29 | 30 | installation 31 | tutorial 32 | levels 33 | quickstart 34 | 35 | 36 | Serving and Testing 37 | ------------------- 38 | 39 | .. toctree:: 40 | :maxdepth: 2 41 | 42 | serving 43 | test 44 | debug 45 | 46 | 47 | Reference 48 | --------- 49 | 50 | .. toctree:: 51 | :maxdepth: 2 52 | 53 | wrappers 54 | routing 55 | wsgi 56 | http 57 | datastructures 58 | utils 59 | urls 60 | local 61 | middleware/index 62 | exceptions 63 | 64 | 65 | Deployment 66 | ---------- 67 | 68 | .. toctree:: 69 | :maxdepth: 3 70 | 71 | deployment/index 72 | 73 | 74 | Additional Information 75 | ---------------------- 76 | 77 | .. toctree:: 78 | :maxdepth: 2 79 | 80 | terms 81 | request_data 82 | license 83 | changes 84 | -------------------------------------------------------------------------------- /docs/installation.rst: -------------------------------------------------------------------------------- 1 | Installation 2 | ============ 3 | 4 | 5 | Python Version 6 | -------------- 7 | 8 | We recommend using the latest version of Python. Werkzeug supports 9 | Python 3.9 and newer. 10 | 11 | 12 | Optional dependencies 13 | ~~~~~~~~~~~~~~~~~~~~~ 14 | 15 | These distributions will not be installed automatically. Werkzeug will 16 | detect and use them if you install them. 17 | 18 | * `Colorama`_ provides request log highlighting when using the 19 | development server on Windows. This works automatically on other 20 | systems. 21 | * `Watchdog`_ provides a faster, more efficient reloader for the 22 | development server. 23 | 24 | .. _Colorama: https://pypi.org/project/colorama/ 25 | .. _Watchdog: https://pypi.org/project/watchdog/ 26 | 27 | 28 | greenlet 29 | ~~~~~~~~ 30 | 31 | You may choose to use gevent or eventlet with your application. In this 32 | case, greenlet>=1.0 is required. When using PyPy, PyPy>=7.3.7 is 33 | required. 34 | 35 | These are not minimum supported versions, they only indicate the first 36 | versions that added necessary features. You should use the latest 37 | versions of each. 38 | 39 | 40 | Virtual environments 41 | -------------------- 42 | 43 | Use a virtual environment to manage the dependencies for your project, 44 | both in development and in production. 45 | 46 | What problem does a virtual environment solve? The more Python 47 | projects you have, the more likely it is that you need to work with 48 | different versions of Python libraries, or even Python itself. Newer 49 | versions of libraries for one project can break compatibility in 50 | another project. 51 | 52 | Virtual environments are independent groups of Python libraries, one for 53 | each project. Packages installed for one project will not affect other 54 | projects or the operating system's packages. 55 | 56 | Python comes bundled with the :mod:`venv` module to create virtual 57 | environments. 58 | 59 | 60 | Create an environment 61 | ~~~~~~~~~~~~~~~~~~~~~ 62 | 63 | Create a project folder and a :file:`venv` folder within: 64 | 65 | .. code-block:: sh 66 | 67 | mkdir myproject 68 | cd myproject 69 | python3 -m venv venv 70 | 71 | On Windows: 72 | 73 | .. code-block:: bat 74 | 75 | py -3 -m venv venv 76 | 77 | 78 | Activate the environment 79 | ~~~~~~~~~~~~~~~~~~~~~~~~ 80 | 81 | Before you work on your project, activate the corresponding environment: 82 | 83 | .. code-block:: sh 84 | 85 | . venv/bin/activate 86 | 87 | On Windows: 88 | 89 | .. code-block:: bat 90 | 91 | venv\Scripts\activate 92 | 93 | Your shell prompt will change to show the name of the activated 94 | environment. 95 | 96 | 97 | Install Werkzeug 98 | ---------------- 99 | 100 | Within the activated environment, use the following command to install 101 | Werkzeug: 102 | 103 | .. code-block:: sh 104 | 105 | pip install Werkzeug 106 | -------------------------------------------------------------------------------- /docs/levels.rst: -------------------------------------------------------------------------------- 1 | ========== 2 | API Levels 3 | ========== 4 | 5 | .. currentmodule:: werkzeug 6 | 7 | Werkzeug is intended to be a utility rather than a framework. Because of that 8 | the user-friendly API is separated from the lower-level API so that Werkzeug 9 | can easily be used to extend another system. 10 | 11 | All the functionality the :class:`Request` and :class:`Response` objects (aka 12 | the "wrappers") provide is also available in small utility functions. 13 | 14 | Example 15 | ======= 16 | 17 | This example implements a small `Hello World` application that greets the 18 | user with the name entered. 19 | 20 | .. code-block:: python 21 | 22 | from markupsafe import escape 23 | from werkzeug.wrappers import Request, Response 24 | 25 | @Request.application 26 | def hello_world(request): 27 | result = ['Greeter'] 28 | if request.method == 'POST': 29 | result.append(f"

Hello {escape(request.form['name'])}!

") 30 | result.append(''' 31 |
32 |

Name: 33 | 34 |

35 | ''') 36 | return Response(''.join(result), mimetype='text/html') 37 | 38 | Alternatively the same application could be used without request and response 39 | objects but by taking advantage of the parsing functions werkzeug provides:: 40 | 41 | from markupsafe import escape 42 | from werkzeug.formparser import parse_form_data 43 | 44 | def hello_world(environ, start_response): 45 | result = ['Greeter'] 46 | if environ['REQUEST_METHOD'] == 'POST': 47 | form = parse_form_data(environ)[1] 48 | result.append(f"

Hello {escape(form['name'])}!

") 49 | result.append(''' 50 |
51 |

Name: 52 | 53 |

54 | ''') 55 | start_response('200 OK', [('Content-Type', 'text/html; charset=utf-8')]) 56 | return [''.join(result).encode('utf-8')] 57 | 58 | High or Low? 59 | ============ 60 | 61 | Usually you want to use the high-level layer (the request and response 62 | objects). But there are situations where this might not be what you want. 63 | 64 | For example you might be maintaining code for an application written in 65 | Django or another framework and you have to parse HTTP headers. You can 66 | utilize Werkzeug for that by accessing the lower-level HTTP header parsing 67 | functions. 68 | 69 | Another situation where the low level parsing functions can be useful are 70 | custom WSGI frameworks, unit-testing or modernizing an old CGI/mod_python 71 | application to WSGI as well as WSGI middlewares where you want to keep the 72 | overhead low. 73 | -------------------------------------------------------------------------------- /docs/license.rst: -------------------------------------------------------------------------------- 1 | BSD-3-Clause License 2 | ==================== 3 | 4 | .. literalinclude:: ../LICENSE.txt 5 | :language: text 6 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=. 11 | set BUILDDIR=_build 12 | 13 | if "%1" == "" goto help 14 | 15 | %SPHINXBUILD% >NUL 2>NUL 16 | if errorlevel 9009 ( 17 | echo. 18 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 19 | echo.installed, then set the SPHINXBUILD environment variable to point 20 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 21 | echo.may add the Sphinx directory to PATH. 22 | echo. 23 | echo.If you don't have Sphinx installed, grab it from 24 | echo.http://sphinx-doc.org/ 25 | exit /b 1 26 | ) 27 | 28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% 29 | goto end 30 | 31 | :help 32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% 33 | 34 | :end 35 | popd 36 | -------------------------------------------------------------------------------- /docs/middleware/dispatcher.rst: -------------------------------------------------------------------------------- 1 | .. automodule:: werkzeug.middleware.dispatcher 2 | -------------------------------------------------------------------------------- /docs/middleware/http_proxy.rst: -------------------------------------------------------------------------------- 1 | .. automodule:: werkzeug.middleware.http_proxy 2 | -------------------------------------------------------------------------------- /docs/middleware/index.rst: -------------------------------------------------------------------------------- 1 | Middleware 2 | ========== 3 | 4 | A WSGI middleware is a WSGI application that wraps another application 5 | in order to observe or change its behavior. Werkzeug provides some 6 | middleware for common use cases. 7 | 8 | .. toctree:: 9 | :maxdepth: 1 10 | 11 | proxy_fix 12 | shared_data 13 | dispatcher 14 | http_proxy 15 | lint 16 | profiler 17 | 18 | The :doc:`interactive debugger ` is also a middleware that can 19 | be applied manually, although it is typically used automatically with 20 | the :doc:`development server `. 21 | -------------------------------------------------------------------------------- /docs/middleware/lint.rst: -------------------------------------------------------------------------------- 1 | .. automodule:: werkzeug.middleware.lint 2 | -------------------------------------------------------------------------------- /docs/middleware/profiler.rst: -------------------------------------------------------------------------------- 1 | .. automodule:: werkzeug.middleware.profiler 2 | -------------------------------------------------------------------------------- /docs/middleware/proxy_fix.rst: -------------------------------------------------------------------------------- 1 | .. automodule:: werkzeug.middleware.proxy_fix 2 | -------------------------------------------------------------------------------- /docs/middleware/shared_data.rst: -------------------------------------------------------------------------------- 1 | .. automodule:: werkzeug.middleware.shared_data 2 | -------------------------------------------------------------------------------- /docs/terms.rst: -------------------------------------------------------------------------------- 1 | =============== 2 | Important Terms 3 | =============== 4 | 5 | .. currentmodule:: werkzeug 6 | 7 | This page covers important terms used in the documentation and Werkzeug 8 | itself. 9 | 10 | 11 | WSGI 12 | ---- 13 | 14 | WSGI a specification for Python web applications Werkzeug follows. It was 15 | specified in the :pep:`3333` and is widely supported. Unlike previous solutions 16 | it guarantees that web applications, servers and utilities can work together. 17 | 18 | Response Object 19 | --------------- 20 | 21 | For Werkzeug, a response object is an object that works like a WSGI 22 | application but does not do any request processing. Usually you have a view 23 | function or controller method that processes the request and assembles a 24 | response object. 25 | 26 | A response object is *not* necessarily the :class:`Response` class or a 27 | subclass thereof. 28 | 29 | For example Pylons/webob provide a very similar response class that can 30 | be used as well (:class:`webob.Response`). 31 | 32 | View Function 33 | ------------- 34 | 35 | Often people speak of MVC (Model, View, Controller) when developing web 36 | applications. However, the Django framework coined MTV (Model, Template, 37 | View) which basically means the same but reduces the concept to the data 38 | model, a function that processes data from the request and the database and 39 | renders a template. 40 | 41 | Werkzeug itself does not tell you how you should develop applications, but the 42 | documentation often speaks of view functions that work roughly the same. The 43 | idea of a view function is that it's called with a request object (and 44 | optionally some parameters from an URL rule) and returns a response object. 45 | -------------------------------------------------------------------------------- /docs/test.rst: -------------------------------------------------------------------------------- 1 | .. module:: werkzeug.test 2 | 3 | Testing WSGI Applications 4 | ========================= 5 | 6 | 7 | Test Client 8 | ----------- 9 | 10 | Werkzeug provides a :class:`Client` to simulate requests to a WSGI 11 | application without starting a server. The client has methods for making 12 | different types of requests, as well as managing cookies across 13 | requests. 14 | 15 | >>> from werkzeug.test import Client 16 | >>> from werkzeug.testapp import test_app 17 | >>> c = Client(test_app) 18 | >>> response = c.get("/") 19 | >>> response.status_code 20 | 200 21 | >>> response.headers 22 | Headers([('Content-Type', 'text/html; charset=utf-8'), ('Content-Length', '5211')]) 23 | >>> response.get_data(as_text=True) 24 | '...' 25 | 26 | The client's request methods return instances of :class:`TestResponse`. 27 | This provides extra attributes and methods on top of 28 | :class:`~werkzeug.wrappers.Response` that are useful for testing. 29 | 30 | 31 | Request Body 32 | ------------ 33 | 34 | By passing a dict to ``data``, the client will construct a request body 35 | with file and form data. It will set the content type to 36 | ``application/x-www-form-urlencoded`` if there are no files, or 37 | ``multipart/form-data`` there are. 38 | 39 | .. code-block:: python 40 | 41 | import io 42 | 43 | response = client.post(data={ 44 | "name": "test", 45 | "file": (BytesIO("file contents".encode("utf8")), "test.txt") 46 | }) 47 | 48 | Pass a string, bytes, or file-like object to ``data`` to use that as the 49 | raw request body. In that case, you should set the content type 50 | appropriately. For example, to post YAML: 51 | 52 | .. code-block:: python 53 | 54 | response = client.post( 55 | data="a: value\nb: 1\n", content_type="application/yaml" 56 | ) 57 | 58 | A shortcut when testing JSON APIs is to pass a dict to ``json`` instead 59 | of using ``data``. This will automatically call ``json.dumps()`` and 60 | set the content type to ``application/json``. Additionally, if the 61 | app returns JSON, ``response.json`` will automatically call 62 | ``json.loads()``. 63 | 64 | .. code-block:: python 65 | 66 | response = client.post("/api", json={"a": "value", "b": 1}) 67 | obj = response.json() 68 | 69 | 70 | Environment Builder 71 | ------------------- 72 | 73 | :class:`EnvironBuilder` is used to construct a WSGI environ dict. The 74 | test client uses this internally to prepare its requests. The arguments 75 | passed to the client request methods are the same as the builder. 76 | 77 | Sometimes, it can be useful to construct a WSGI environment manually. 78 | An environ builder or dict can be passed to the test client request 79 | methods in place of other arguments to use a custom environ. 80 | 81 | .. code-block:: Python 82 | 83 | from werkzeug.test import EnvironBuilder 84 | builder = EnvironBuilder(...) 85 | # build an environ dict 86 | environ = builder.get_environ() 87 | # build an environ dict wrapped in a request 88 | request = builder.get_request() 89 | 90 | The test client responses make this available through 91 | :attr:`TestResponse.request` and ``response.request.environ``. 92 | 93 | 94 | API 95 | --- 96 | 97 | .. autoclass:: Client 98 | :members: 99 | :member-order: bysource 100 | 101 | .. autoclass:: TestResponse 102 | :members: 103 | :member-order: bysource 104 | 105 | .. autoclass:: Cookie 106 | :members: 107 | :member-order: bysource 108 | 109 | .. autoclass:: EnvironBuilder 110 | :members: 111 | :member-order: bysource 112 | 113 | .. autofunction:: create_environ 114 | 115 | .. autofunction:: run_wsgi_app 116 | -------------------------------------------------------------------------------- /docs/urls.rst: -------------------------------------------------------------------------------- 1 | =========== 2 | URL Helpers 3 | =========== 4 | 5 | .. automodule:: werkzeug.urls 6 | :members: 7 | -------------------------------------------------------------------------------- /docs/utils.rst: -------------------------------------------------------------------------------- 1 | ========= 2 | Utilities 3 | ========= 4 | 5 | Various utility functions shipped with Werkzeug. 6 | 7 | .. module:: werkzeug.utils 8 | 9 | 10 | General Helpers 11 | =============== 12 | 13 | .. autoclass:: cached_property 14 | :members: 15 | 16 | .. autoclass:: environ_property 17 | 18 | .. autoclass:: header_property 19 | 20 | .. autofunction:: redirect 21 | 22 | .. autofunction:: append_slash_redirect 23 | 24 | .. autofunction:: send_file 25 | 26 | .. autofunction:: send_from_directory 27 | 28 | .. autofunction:: import_string 29 | 30 | .. autofunction:: find_modules 31 | 32 | .. autofunction:: secure_filename 33 | 34 | 35 | URL Helpers 36 | =========== 37 | 38 | Please refer to :doc:`urls`. 39 | 40 | 41 | User Agent API 42 | ============== 43 | 44 | .. module:: werkzeug.user_agent 45 | 46 | .. autoclass:: UserAgent 47 | :members: 48 | :member-order: bysource 49 | 50 | 51 | Security Helpers 52 | ================ 53 | 54 | .. module:: werkzeug.security 55 | 56 | .. autofunction:: generate_password_hash 57 | 58 | .. autofunction:: check_password_hash 59 | 60 | .. autofunction:: safe_join 61 | 62 | 63 | Logging 64 | ======= 65 | 66 | Werkzeug uses standard Python :mod:`logging`. The logger is named 67 | ``"werkzeug"``. 68 | 69 | .. code-block:: python 70 | 71 | import logging 72 | logger = logging.getLogger("werkzeug") 73 | 74 | If the logger level is not set, it will be set to :data:`~logging.INFO` 75 | on first use. If there is no handler for that level, a 76 | :class:`~logging.StreamHandler` is added. 77 | -------------------------------------------------------------------------------- /docs/wrappers.rst: -------------------------------------------------------------------------------- 1 | ========================== 2 | Request / Response Objects 3 | ========================== 4 | 5 | .. module:: werkzeug.wrappers 6 | 7 | The request and response objects wrap the WSGI environment or the return 8 | value from a WSGI application so that it is another WSGI application 9 | (wraps a whole application). 10 | 11 | How they Work 12 | ============= 13 | 14 | Your WSGI application is always passed two arguments. The WSGI "environment" 15 | and the WSGI `start_response` function that is used to start the response 16 | phase. The :class:`Request` class wraps the `environ` for easier access to 17 | request variables (form data, request headers etc.). 18 | 19 | The :class:`Response` on the other hand is a standard WSGI application that 20 | you can create. The simple hello world in Werkzeug looks like this:: 21 | 22 | from werkzeug.wrappers import Response 23 | application = Response('Hello World!') 24 | 25 | To make it more useful you can replace it with a function and do some 26 | processing:: 27 | 28 | from werkzeug.wrappers import Request, Response 29 | 30 | def application(environ, start_response): 31 | request = Request(environ) 32 | response = Response(f"Hello {request.args.get('name', 'World!')}!") 33 | return response(environ, start_response) 34 | 35 | Because this is a very common task the :class:`~Request` object provides 36 | a helper for that. The above code can be rewritten like this:: 37 | 38 | from werkzeug.wrappers import Request, Response 39 | 40 | @Request.application 41 | def application(request): 42 | return Response(f"Hello {request.args.get('name', 'World!')}!") 43 | 44 | The `application` is still a valid WSGI application that accepts the 45 | environment and `start_response` callable. 46 | 47 | 48 | Mutability and Reusability of Wrappers 49 | ====================================== 50 | 51 | The implementation of the Werkzeug request and response objects are trying 52 | to guard you from common pitfalls by disallowing certain things as much as 53 | possible. This serves two purposes: high performance and avoiding of 54 | pitfalls. 55 | 56 | For the request object the following rules apply: 57 | 58 | 1. The request object is immutable. Modifications are not supported by 59 | default, you may however replace the immutable attributes with mutable 60 | attributes if you need to modify it. 61 | 2. The request object may be shared in the same thread, but is not thread 62 | safe itself. If you need to access it from multiple threads, use 63 | locks around calls. 64 | 3. It's not possible to pickle the request object. 65 | 66 | For the response object the following rules apply: 67 | 68 | 1. The response object is mutable 69 | 2. The response object can be pickled or copied after `freeze()` was 70 | called. 71 | 3. Since Werkzeug 0.6 it's safe to use the same response object for 72 | multiple WSGI responses. 73 | 4. It's possible to create copies using `copy.deepcopy`. 74 | 75 | 76 | Wrapper Classes 77 | =============== 78 | 79 | .. autoclass:: Request 80 | :members: 81 | :inherited-members: 82 | 83 | .. automethod:: _get_file_stream 84 | 85 | 86 | .. autoclass:: Response 87 | :members: 88 | :inherited-members: 89 | 90 | .. automethod:: __call__ 91 | 92 | .. automethod:: _ensure_sequence 93 | -------------------------------------------------------------------------------- /examples/README.rst: -------------------------------------------------------------------------------- 1 | ================= 2 | Werkzeug Examples 3 | ================= 4 | 5 | This directory contains various example applications and example code of 6 | Werkzeug powered applications. 7 | 8 | Beside the proof of concept applications and code snippets in the partial 9 | folder they all have external dependencies for template engines or database 10 | adapters (SQLAlchemy only so far). Also, every application has click as 11 | external dependency, used to create the command line interface. 12 | 13 | 14 | Full Example Applications 15 | ========================= 16 | 17 | The following example applications are application types you would actually 18 | find in real life :-) 19 | 20 | 21 | `simplewiki` 22 | 23 | A simple Wiki implementation. 24 | 25 | Requirements: 26 | 27 | - SQLAlchemy 28 | - Creoleparser >= 0.7 29 | - genshi 30 | 31 | You can obtain all packages in the Cheeseshop via easy_install. You have 32 | to have at least version 0.7 of Creoleparser. 33 | 34 | Usage:: 35 | 36 | ./manage-simplewiki.py initdb 37 | ./manage-simplewiki.py runserver 38 | 39 | Or of course you can just use the application object 40 | (`simplewiki.SimpleWiki`) and hook that into your favourite WSGI gateway. 41 | The constructor of the application object takes a single argument which is 42 | the SQLAlchemy URI for the database. 43 | 44 | The management script for the devserver looks up the an environment var 45 | called `SIMPLEWIKI_DATABASE_URI` and uses that for the database URI. If 46 | no such variable is provided "sqlite:////tmp/simplewiki.db" is assumed. 47 | 48 | `plnt` 49 | 50 | A planet called plnt, pronounce plant. 51 | 52 | Requirements: 53 | 54 | - SQLAlchemy 55 | - Jinja2 56 | - feedparser 57 | 58 | You can obtain all packages in the Cheeseshop via easy_install. 59 | 60 | Usage:: 61 | 62 | ./manage-plnt.py initdb 63 | ./manage-plnt.py sync 64 | ./manage-plnt.py runserver 65 | 66 | The WSGI application is called `plnt.Plnt` which, like the simple wiki, 67 | accepts a database URI as first argument. The environment variable for 68 | the database key is called `PLNT_DATABASE_URI` and the default is 69 | "sqlite:////tmp/plnt.db". 70 | 71 | Per default a few python related blogs are added to the database, you 72 | can add more in a python shell by playing with the `Blog` model. 73 | 74 | `shorty` 75 | 76 | A tinyurl clone for the Werkzeug tutorial. 77 | 78 | Requirements: 79 | 80 | - SQLAlchemy 81 | - Jinja2 82 | 83 | You can obtain all packages in the Cheeseshop via easy_install. 84 | 85 | Usage:: 86 | 87 | ./manage-shorty.py initdb 88 | ./manage-shorty.py runserver 89 | 90 | The WSGI application is called `shorty.application.Shorty` which, like the 91 | simple wiki, accepts a database URI as first argument. 92 | 93 | The source code of the application is explained in detail in the Werkzeug 94 | tutorial. 95 | 96 | `couchy` 97 | 98 | Like shorty, but implemented using CouchDB. 99 | 100 | Requirements : 101 | 102 | - werkzeug : http://werkzeug.pocoo.org 103 | - jinja : http://jinja.pocoo.org 104 | - couchdb 0.72 & above : https://couchdb.apache.org/ 105 | 106 | `cupoftee` 107 | 108 | A `Teeworlds `_ server browser. This application 109 | works best in a non forking environment and won't work for CGI. 110 | 111 | Usage:: 112 | 113 | ./manage-cupoftee.py runserver 114 | -------------------------------------------------------------------------------- /examples/coolmagic/__init__.py: -------------------------------------------------------------------------------- 1 | from .application import make_app 2 | -------------------------------------------------------------------------------- /examples/coolmagic/application.py: -------------------------------------------------------------------------------- 1 | """This module provides the WSGI application. 2 | 3 | The WSGI middlewares are applied in the `make_app` factory function that 4 | automatically wraps the application within the require middlewares. Per 5 | default only the `SharedDataMiddleware` is applied. 6 | """ 7 | from os import listdir 8 | from os import path 9 | 10 | from werkzeug.exceptions import HTTPException 11 | from werkzeug.exceptions import NotFound 12 | from werkzeug.middleware.shared_data import SharedDataMiddleware 13 | from werkzeug.routing import Map 14 | from werkzeug.routing import RequestRedirect 15 | from werkzeug.routing import Rule 16 | 17 | from .utils import local_manager 18 | from .utils import Request 19 | 20 | 21 | class CoolMagicApplication: 22 | """ 23 | The application class. It's passed a directory with configuration values. 24 | """ 25 | 26 | def __init__(self, config): 27 | self.config = config 28 | 29 | for fn in listdir(path.join(path.dirname(__file__), "views")): 30 | if fn.endswith(".py") and fn != "__init__.py": 31 | __import__(f"coolmagic.views.{fn[:-3]}") 32 | 33 | from coolmagic.utils import exported_views 34 | 35 | rules = [ 36 | # url for shared data. this will always be unmatched 37 | # because either the middleware or the webserver 38 | # handles that request first. 39 | Rule("/public/", endpoint="shared_data") 40 | ] 41 | self.views = {} 42 | for endpoint, (func, rule, extra) in exported_views.items(): 43 | if rule is not None: 44 | rules.append(Rule(rule, endpoint=endpoint, **extra)) 45 | self.views[endpoint] = func 46 | self.url_map = Map(rules) 47 | 48 | def __call__(self, environ, start_response): 49 | urls = self.url_map.bind_to_environ(environ) 50 | req = Request(environ, urls) 51 | try: 52 | endpoint, args = urls.match(req.path) 53 | resp = self.views[endpoint](**args) 54 | except NotFound: 55 | resp = self.views["static.not_found"]() 56 | except (HTTPException, RequestRedirect) as e: 57 | resp = e 58 | return resp(environ, start_response) 59 | 60 | 61 | def make_app(config=None): 62 | """ 63 | Factory function that creates a new `CoolmagicApplication` 64 | object. Optional WSGI middlewares should be applied here. 65 | """ 66 | config = config or {} 67 | app = CoolMagicApplication(config) 68 | 69 | # static stuff 70 | app = SharedDataMiddleware( 71 | app, {"/public": path.join(path.dirname(__file__), "public")} 72 | ) 73 | 74 | # clean up locals 75 | app = local_manager.make_middleware(app) 76 | 77 | return app 78 | -------------------------------------------------------------------------------- /examples/coolmagic/helpers.py: -------------------------------------------------------------------------------- 1 | from .utils import ThreadedRequest 2 | 3 | #: a thread local proxy request object 4 | request = ThreadedRequest() 5 | del ThreadedRequest 6 | -------------------------------------------------------------------------------- /examples/coolmagic/public/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | padding: 20px; 4 | font-family: sans-serif; 5 | font-size: 15px; 6 | } 7 | 8 | h1, a { 9 | color: #a00; 10 | } 11 | -------------------------------------------------------------------------------- /examples/coolmagic/templates/layout.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {{ page_title }} — Cool Magic! 5 | 6 | 7 | 8 |

Cool Magic

9 |

{{ page_title }}

10 | {% block page_body %}{% endblock %} 11 | 12 | 13 | -------------------------------------------------------------------------------- /examples/coolmagic/templates/static/about.html: -------------------------------------------------------------------------------- 1 | {% extends "layout.html" %} 2 | {% set page_title = 'About the Magic' %} 3 | {% block page_body %} 4 |

5 | Nothing to see. It's just magic. 6 |

7 |

8 | back to the index 9 |

10 | {% endblock %} 11 | -------------------------------------------------------------------------------- /examples/coolmagic/templates/static/index.html: -------------------------------------------------------------------------------- 1 | {% extends "layout.html" %} 2 | {% set page_title = 'Welcome to the Magic' %} 3 | {% block page_body %} 4 |

5 | Welcome to the magic! This is a bigger example for the 6 | Werkzeug toolkit. And it contains a lot of magic. 7 |

8 |

9 | about the implementation or 10 | click here if you want to see a broken view. 12 |

13 | {% endblock %} 14 | -------------------------------------------------------------------------------- /examples/coolmagic/templates/static/not_found.html: -------------------------------------------------------------------------------- 1 | {% extends "layout.html" %} 2 | {% set page_title = 'Missing Magic' %} 3 | {% block page_body %} 4 |

5 | The requested magic really does not exist. Maybe you want 6 | to look for it on the index. 7 |

8 | {% endblock %} 9 | -------------------------------------------------------------------------------- /examples/coolmagic/utils.py: -------------------------------------------------------------------------------- 1 | """Subclasses of the base request and response objects provided by 2 | werkzeug. The subclasses know about their charset and implement some 3 | additional functionality like the ability to link to view functions. 4 | """ 5 | from os.path import dirname 6 | from os.path import join 7 | 8 | from jinja2 import Environment 9 | from jinja2 import FileSystemLoader 10 | from werkzeug.local import Local 11 | from werkzeug.local import LocalManager 12 | from werkzeug.wrappers import Request as BaseRequest 13 | from werkzeug.wrappers import Response as BaseResponse 14 | 15 | 16 | local = Local() 17 | local_manager = LocalManager([local]) 18 | template_env = Environment( 19 | loader=FileSystemLoader(join(dirname(__file__), "templates")) 20 | ) 21 | exported_views = {} 22 | 23 | 24 | def export(string, template=None, **extra): 25 | """ 26 | Decorator for registering view functions and adding 27 | templates to it. 28 | """ 29 | 30 | def wrapped(f): 31 | endpoint = f"{f.__module__}.{f.__name__}"[16:] 32 | if template is not None: 33 | old_f = f 34 | 35 | def f(**kwargs): 36 | rv = old_f(**kwargs) 37 | if not isinstance(rv, Response): 38 | rv = TemplateResponse(template, **(rv or {})) 39 | return rv 40 | 41 | f.__name__ = old_f.__name__ 42 | f.__doc__ = old_f.__doc__ 43 | exported_views[endpoint] = (f, string, extra) 44 | return f 45 | 46 | return wrapped 47 | 48 | 49 | def url_for(endpoint, **values): 50 | """ 51 | Build a URL 52 | """ 53 | return local.request.url_adapter.build(endpoint, values) 54 | 55 | 56 | class Request(BaseRequest): 57 | """ 58 | The concrete request object used in the WSGI application. 59 | It has some helper functions that can be used to build URLs. 60 | """ 61 | 62 | charset = "utf-8" 63 | 64 | def __init__(self, environ, url_adapter): 65 | super().__init__(environ) 66 | self.url_adapter = url_adapter 67 | local.request = self 68 | 69 | 70 | class ThreadedRequest: 71 | """ 72 | A pseudo request object that always points to the current 73 | context active request. 74 | """ 75 | 76 | def __getattr__(self, name): 77 | if name == "__members__": 78 | return [x for x in dir(local.request) if not x.startswith("_")] 79 | return getattr(local.request, name) 80 | 81 | def __setattr__(self, name, value): 82 | return setattr(local.request, name, value) 83 | 84 | 85 | class Response(BaseResponse): 86 | """ 87 | The concrete response object for the WSGI application. 88 | """ 89 | 90 | charset = "utf-8" 91 | default_mimetype = "text/html" 92 | 93 | 94 | class TemplateResponse(Response): 95 | """ 96 | Render a template to a response. 97 | """ 98 | 99 | def __init__(self, template_name, **values): 100 | from coolmagic import helpers 101 | 102 | values.update(request=local.request, h=helpers) 103 | template = template_env.get_template(template_name) 104 | Response.__init__(self, template.render(values)) 105 | -------------------------------------------------------------------------------- /examples/coolmagic/views/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pallets/werkzeug/99fc293bc7dccd6a7dc96b96012d4aa849794ba4/examples/coolmagic/views/__init__.py -------------------------------------------------------------------------------- /examples/coolmagic/views/static.py: -------------------------------------------------------------------------------- 1 | from coolmagic.utils import export 2 | 3 | 4 | @export("/", template="static/index.html") 5 | def index(): 6 | pass 7 | 8 | 9 | @export("/about", template="static/about.html") 10 | def about(): 11 | pass 12 | 13 | 14 | @export("/broken") 15 | def broken(): 16 | raise RuntimeError("that's really broken") 17 | 18 | 19 | @export(None, template="static/not_found.html") 20 | def not_found(): 21 | """ 22 | This function is always executed if an url does not 23 | match or a `NotFound` exception is raised. 24 | """ 25 | pass 26 | -------------------------------------------------------------------------------- /examples/couchy/README: -------------------------------------------------------------------------------- 1 | couchy README 2 | 3 | Requirements : 4 | - werkzeug : http://werkzeug.pocoo.org 5 | - jinja : http://jinja.pocoo.org 6 | - couchdb 0.72 & above : https://couchdb.apache.org/ 7 | - couchdb-python 0.3 & above : https://github.com/djc/couchdb-python 8 | -------------------------------------------------------------------------------- /examples/couchy/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pallets/werkzeug/99fc293bc7dccd6a7dc96b96012d4aa849794ba4/examples/couchy/__init__.py -------------------------------------------------------------------------------- /examples/couchy/application.py: -------------------------------------------------------------------------------- 1 | from couchdb.client import Server 2 | from werkzeug.exceptions import HTTPException 3 | from werkzeug.exceptions import NotFound 4 | from werkzeug.middleware.shared_data import SharedDataMiddleware 5 | from werkzeug.wrappers import Request 6 | from werkzeug.wsgi import ClosingIterator 7 | 8 | from . import views 9 | from .models import URL 10 | from .utils import local 11 | from .utils import local_manager 12 | from .utils import STATIC_PATH 13 | from .utils import url_map 14 | 15 | 16 | class Couchy: 17 | def __init__(self, db_uri): 18 | local.application = self 19 | 20 | server = Server(db_uri) 21 | try: 22 | db = server.create("urls") 23 | except Exception: 24 | db = server["urls"] 25 | self.dispatch = SharedDataMiddleware(self.dispatch, {"/static": STATIC_PATH}) 26 | 27 | URL.db = db 28 | 29 | def dispatch(self, environ, start_response): 30 | local.application = self 31 | request = Request(environ) 32 | local.url_adapter = adapter = url_map.bind_to_environ(environ) 33 | try: 34 | endpoint, values = adapter.match() 35 | handler = getattr(views, endpoint) 36 | response = handler(request, **values) 37 | except NotFound: 38 | response = views.not_found(request) 39 | response.status_code = 404 40 | except HTTPException as e: 41 | response = e 42 | return ClosingIterator( 43 | response(environ, start_response), [local_manager.cleanup] 44 | ) 45 | 46 | def __call__(self, environ, start_response): 47 | return self.dispatch(environ, start_response) 48 | -------------------------------------------------------------------------------- /examples/couchy/models.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | 3 | from couchdb.mapping import BooleanField 4 | from couchdb.mapping import DateTimeField 5 | from couchdb.mapping import Document 6 | from couchdb.mapping import TextField 7 | 8 | from .utils import get_random_uid 9 | from .utils import url_for 10 | 11 | 12 | class URL(Document): 13 | target = TextField() 14 | public = BooleanField() 15 | added = DateTimeField(default=datetime.utcnow()) 16 | shorty_id = TextField(default=None) 17 | db = None 18 | 19 | @classmethod 20 | def load(cls, id): 21 | return super().load(URL.db, id) 22 | 23 | @classmethod 24 | def query(cls, code): 25 | return URL.db.query(code) 26 | 27 | def store(self): 28 | if getattr(self._data, "id", None) is None: 29 | new_id = self.shorty_id if self.shorty_id else None 30 | while 1: 31 | id = new_id if new_id else get_random_uid() 32 | try: 33 | docid = URL.db.resource.put(content=self._data, path=f"/{id}/")[ 34 | "id" 35 | ] 36 | except Exception: 37 | continue 38 | if docid: 39 | break 40 | self._data = URL.db.get(docid) 41 | else: 42 | super().store(URL.db) 43 | return self 44 | 45 | @property 46 | def short_url(self): 47 | return url_for("link", uid=self.id, _external=True) 48 | 49 | def __repr__(self): 50 | return f"" 51 | -------------------------------------------------------------------------------- /examples/couchy/static/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | background-color: #333; 3 | font-family: 'Lucida Sans', 'Verdana', sans-serif; 4 | font-size: 16px; 5 | margin: 3em 0 3em 0; 6 | padding: 0; 7 | text-align: center; 8 | } 9 | 10 | a { 11 | color: #0C4850; 12 | } 13 | 14 | a:hover { 15 | color: #1C818F; 16 | } 17 | 18 | h1 { 19 | width: 500px; 20 | background-color: #24C0CE; 21 | text-align: center; 22 | font-size: 3em; 23 | margin: 0 auto 0 auto; 24 | padding: 0; 25 | } 26 | 27 | h1 a { 28 | display: block; 29 | padding: 0.3em; 30 | color: #fff; 31 | text-decoration: none; 32 | } 33 | 34 | h1 a:hover { 35 | color: #ADEEF7; 36 | background-color: #0E8A96; 37 | } 38 | 39 | div.footer { 40 | margin: 0 auto 0 auto; 41 | font-size: 13px; 42 | text-align: right; 43 | padding: 10px; 44 | width: 480px; 45 | background-color: #004C63; 46 | color: white; 47 | } 48 | 49 | div.footer a { 50 | color: #A0E9FF; 51 | } 52 | 53 | div.body { 54 | margin: 0 auto 0 auto; 55 | padding: 20px; 56 | width: 460px; 57 | background-color: #98CE24; 58 | color: black; 59 | } 60 | 61 | div.body h2 { 62 | margin: 0 0 0.5em 0; 63 | text-align: center; 64 | } 65 | 66 | div.body input { 67 | margin: 0.2em 0 0.2em 0; 68 | font-family: 'Lucida Sans', 'Verdana', sans-serif; 69 | font-size: 20px; 70 | background-color: #CCEB98; 71 | color: black; 72 | } 73 | 74 | div.body #url { 75 | width: 400px; 76 | } 77 | 78 | div.body #alias { 79 | width: 300px; 80 | margin-right: 10px; 81 | } 82 | 83 | div.body #submit { 84 | width: 90px; 85 | } 86 | 87 | div.body p { 88 | margin: 0; 89 | padding: 0.2em 0 0.2em 0; 90 | } 91 | 92 | div.body ul { 93 | margin: 1em 0 1em 0; 94 | padding: 0; 95 | list-style: none; 96 | } 97 | 98 | div.error { 99 | margin: 1em 0 1em 0; 100 | border: 2px solid #AC0202; 101 | background-color: #9E0303; 102 | font-weight: bold; 103 | color: white; 104 | } 105 | 106 | div.pagination { 107 | font-size: 13px; 108 | } 109 | -------------------------------------------------------------------------------- /examples/couchy/templates/display.html: -------------------------------------------------------------------------------- 1 | {% extends 'layout.html' %} 2 | {% block body %} 3 |

Shortened URL

4 |

5 | The URL {{ url.target|urlize(40, true) }} 6 | was shortened to {{ url.short_url|urlize }}. 7 |

8 | {% endblock %} 9 | -------------------------------------------------------------------------------- /examples/couchy/templates/layout.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Shorty 5 | 6 | 7 | 8 |

Shorty

9 |
{% block body %}{% endblock %}
10 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /examples/couchy/templates/list.html: -------------------------------------------------------------------------------- 1 | {% extends 'layout.html' %} 2 | {% block body %} 3 |

List of URLs

4 |
    5 | {%- for url in pagination.entries %} 6 |
  • {{ url.id|e }} » 7 | {{ url.target|urlize(38, true) }}
  • 8 | {%- else %} 9 |
  • no URLs shortened yet
  • 10 | {%- endfor %} 11 |
12 | 19 | {% endblock %} 20 | -------------------------------------------------------------------------------- /examples/couchy/templates/new.html: -------------------------------------------------------------------------------- 1 | {% extends 'layout.html' %} 2 | {% block body %} 3 |

Create a Shorty-URL!

4 | {% if error %}
{{ error }}
{% endif -%} 5 |
6 |

Enter the URL you want to shorten

7 |

8 |

Optionally you can give the URL a memorable name

9 |

{# 10 | #}

11 |

12 |

13 |
14 | {% endblock %} 15 | -------------------------------------------------------------------------------- /examples/couchy/templates/not_found.html: -------------------------------------------------------------------------------- 1 | {% extends 'layout.html' %} 2 | {% block body %} 3 |

Page Not Found

4 |

5 | The page you have requested does not exist on this server. What about 6 | adding a new URL? 7 |

8 | {% endblock %} 9 | -------------------------------------------------------------------------------- /examples/couchy/utils.py: -------------------------------------------------------------------------------- 1 | from os import path 2 | from random import randrange 3 | from random import sample 4 | from urllib.parse import urlsplit 5 | 6 | from jinja2 import Environment 7 | from jinja2 import FileSystemLoader 8 | from werkzeug.local import Local 9 | from werkzeug.local import LocalManager 10 | from werkzeug.routing import Map 11 | from werkzeug.routing import Rule 12 | from werkzeug.utils import cached_property 13 | from werkzeug.wrappers import Response 14 | 15 | TEMPLATE_PATH = path.join(path.dirname(__file__), "templates") 16 | STATIC_PATH = path.join(path.dirname(__file__), "static") 17 | ALLOWED_SCHEMES = frozenset(["http", "https", "ftp", "ftps"]) 18 | URL_CHARS = "abcdefghijkmpqrstuvwxyzABCDEFGHIJKLMNPQRST23456789" 19 | 20 | local = Local() 21 | local_manager = LocalManager([local]) 22 | application = local("application") 23 | 24 | url_map = Map([Rule("/static/", endpoint="static", build_only=True)]) 25 | 26 | jinja_env = Environment(loader=FileSystemLoader(TEMPLATE_PATH)) 27 | 28 | 29 | def expose(rule, **kw): 30 | def decorate(f): 31 | kw["endpoint"] = f.__name__ 32 | url_map.add(Rule(rule, **kw)) 33 | return f 34 | 35 | return decorate 36 | 37 | 38 | def url_for(endpoint, _external=False, **values): 39 | return local.url_adapter.build(endpoint, values, force_external=_external) 40 | 41 | 42 | jinja_env.globals["url_for"] = url_for 43 | 44 | 45 | def render_template(template, **context): 46 | return Response( 47 | jinja_env.get_template(template).render(**context), mimetype="text/html" 48 | ) 49 | 50 | 51 | def validate_url(url): 52 | return urlsplit(url)[0] in ALLOWED_SCHEMES 53 | 54 | 55 | def get_random_uid(): 56 | return "".join(sample(URL_CHARS, randrange(3, 9))) 57 | 58 | 59 | class Pagination: 60 | def __init__(self, results, per_page, page, endpoint): 61 | self.results = results 62 | self.per_page = per_page 63 | self.page = page 64 | self.endpoint = endpoint 65 | 66 | @cached_property 67 | def count(self): 68 | return len(self.results) 69 | 70 | @cached_property 71 | def entries(self): 72 | return self.results[ 73 | ((self.page - 1) * self.per_page) : ( 74 | ((self.page - 1) * self.per_page) + self.per_page 75 | ) 76 | ] 77 | 78 | @property 79 | def has_previous(self): 80 | """Return True if there are pages before the current one.""" 81 | return self.page > 1 82 | 83 | @property 84 | def has_next(self): 85 | """Return True if there are pages after the current one.""" 86 | return self.page < self.pages 87 | 88 | @property 89 | def previous(self): 90 | """Return the URL for the previous page.""" 91 | return url_for(self.endpoint, page=self.page - 1) 92 | 93 | @property 94 | def next(self): 95 | """Return the URL for the next page.""" 96 | return url_for(self.endpoint, page=self.page + 1) 97 | 98 | @property 99 | def pages(self): 100 | """Return the number of pages.""" 101 | return max(0, self.count - 1) // self.per_page + 1 102 | -------------------------------------------------------------------------------- /examples/couchy/views.py: -------------------------------------------------------------------------------- 1 | from werkzeug.exceptions import NotFound 2 | from werkzeug.utils import redirect 3 | 4 | from .models import URL 5 | from .utils import expose 6 | from .utils import Pagination 7 | from .utils import render_template 8 | from .utils import url_for 9 | from .utils import validate_url 10 | 11 | 12 | @expose("/") 13 | def new(request): 14 | error = url = "" 15 | if request.method == "POST": 16 | url = request.form.get("url") 17 | alias = request.form.get("alias") 18 | if not validate_url(url): 19 | error = "I'm sorry but you cannot shorten this URL." 20 | elif alias: 21 | if len(alias) > 140: 22 | error = "Your alias is too long" 23 | elif "/" in alias: 24 | error = "Your alias might not include a slash" 25 | elif URL.load(alias): 26 | error = "The alias you have requested exists already" 27 | if not error: 28 | url = URL( 29 | target=url, 30 | public="private" not in request.form, 31 | shorty_id=alias if alias else None, 32 | ) 33 | url.store() 34 | uid = url.id 35 | return redirect(url_for("display", uid=uid)) 36 | return render_template("new.html", error=error, url=url) 37 | 38 | 39 | @expose("/display/") 40 | def display(request, uid): 41 | url = URL.load(uid) 42 | if not url: 43 | raise NotFound() 44 | return render_template("display.html", url=url) 45 | 46 | 47 | @expose("/u/") 48 | def link(request, uid): 49 | url = URL.load(uid) 50 | if not url: 51 | raise NotFound() 52 | return redirect(url.target, 301) 53 | 54 | 55 | @expose("/list/", defaults={"page": 1}) 56 | @expose("/list/") 57 | def list(request, page): 58 | def wrap(doc): 59 | data = doc.value 60 | data["_id"] = doc.id 61 | return URL.wrap(data) 62 | 63 | code = """function(doc) { if (doc.public){ map([doc._id], doc); }}""" 64 | docResults = URL.query(code) 65 | results = [wrap(doc) for doc in docResults] 66 | pagination = Pagination(results, 1, page, "list") 67 | if pagination.page > 1 and not pagination.entries: 68 | raise NotFound() 69 | return render_template("list.html", pagination=pagination) 70 | 71 | 72 | def not_found(request): 73 | return render_template("not_found.html") 74 | -------------------------------------------------------------------------------- /examples/cupoftee/__init__.py: -------------------------------------------------------------------------------- 1 | """Werkzeug powered Teeworlds Server Browser.""" 2 | from .application import make_app 3 | -------------------------------------------------------------------------------- /examples/cupoftee/db.py: -------------------------------------------------------------------------------- 1 | """A simple object database. As long as the server is not running in 2 | multiprocess mode that's good enough. 3 | """ 4 | import dbm 5 | from pickle import dumps 6 | from pickle import loads 7 | from threading import Lock 8 | 9 | 10 | class Database: 11 | def __init__(self, filename): 12 | self.filename = filename 13 | self._fs = dbm.open(filename, "cf") 14 | self._local = {} 15 | self._lock = Lock() 16 | 17 | def __getitem__(self, key): 18 | with self._lock: 19 | return self._load_key(key) 20 | 21 | def _load_key(self, key): 22 | if key in self._local: 23 | return self._local[key] 24 | rv = loads(self._fs[key]) 25 | self._local[key] = rv 26 | return rv 27 | 28 | def __setitem__(self, key, value): 29 | self._local[key] = value 30 | 31 | def __delitem__(self, key): 32 | with self._lock: 33 | self._local.pop(key, None) 34 | if key in self._fs: 35 | del self._fs[key] 36 | 37 | def __del__(self): 38 | self.close() 39 | 40 | def __contains__(self, key): 41 | with self._lock: 42 | try: 43 | self._load_key(key) 44 | except KeyError: 45 | pass 46 | return key in self._local 47 | 48 | def setdefault(self, key, factory): 49 | with self._lock: 50 | try: 51 | rv = self._load_key(key) 52 | except KeyError: 53 | self._local[key] = rv = factory() 54 | return rv 55 | 56 | def sync(self): 57 | with self._lock: 58 | for key, value in self._local.items(): 59 | self._fs[key] = dumps(value, 2) 60 | self._fs.sync() 61 | 62 | def close(self): 63 | try: 64 | self.sync() 65 | self._fs.close() 66 | except Exception: 67 | pass 68 | -------------------------------------------------------------------------------- /examples/cupoftee/pages.py: -------------------------------------------------------------------------------- 1 | from functools import reduce 2 | 3 | from werkzeug.exceptions import NotFound 4 | from werkzeug.utils import redirect 5 | 6 | from .application import Page 7 | from .utils import unicodecmp 8 | 9 | 10 | class ServerList(Page): 11 | url_rule = "/" 12 | 13 | def order_link(self, name, title): 14 | cls = "" 15 | link = f"?order_by={name}" 16 | desc = False 17 | if name == self.order_by: 18 | desc = not self.order_desc 19 | cls = f' class="{"down" if desc else "up"}"' 20 | if desc: 21 | link += "&dir=desc" 22 | return f'{title}' 23 | 24 | def process(self): 25 | self.order_by = self.request.args.get("order_by") or "name" 26 | sort_func = { 27 | "name": lambda x: x, 28 | "map": lambda x: x.map, 29 | "gametype": lambda x: x.gametype, 30 | "players": lambda x: x.player_count, 31 | "progression": lambda x: x.progression, 32 | }.get(self.order_by) 33 | if sort_func is None: 34 | return redirect(self.url_for("serverlist")) 35 | 36 | self.servers = self.cup.server_browser.servers.values() 37 | self.servers.sort(key=sort_func) 38 | if self.request.args.get("dir") == "desc": 39 | self.servers.reverse() 40 | self.order_desc = True 41 | else: 42 | self.order_desc = False 43 | 44 | self.players = reduce(lambda a, b: a + b.players, self.servers, []) 45 | self.players = sorted(self.players, key=lambda a, b: unicodecmp(a.name, b.name)) 46 | 47 | 48 | class Server(Page): 49 | url_rule = "/server/" 50 | 51 | def process(self, id): 52 | try: 53 | self.server = self.cup.server_browser.servers[id] 54 | except KeyError: 55 | raise NotFound() from None 56 | 57 | 58 | class Search(Page): 59 | url_rule = "/search" 60 | 61 | def process(self): 62 | self.user = self.request.args.get("user") 63 | if self.user: 64 | self.results = [] 65 | for server in self.cup.server_browser.servers.values(): 66 | for player in server.players: 67 | if player.name == self.user: 68 | self.results.append(server) 69 | 70 | 71 | class MissingPage(Page): 72 | def get_response(self): 73 | response = super().get_response() 74 | response.status_code = 404 75 | return response 76 | -------------------------------------------------------------------------------- /examples/cupoftee/shared/content.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pallets/werkzeug/99fc293bc7dccd6a7dc96b96012d4aa849794ba4/examples/cupoftee/shared/content.png -------------------------------------------------------------------------------- /examples/cupoftee/shared/down.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pallets/werkzeug/99fc293bc7dccd6a7dc96b96012d4aa849794ba4/examples/cupoftee/shared/down.png -------------------------------------------------------------------------------- /examples/cupoftee/shared/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pallets/werkzeug/99fc293bc7dccd6a7dc96b96012d4aa849794ba4/examples/cupoftee/shared/favicon.ico -------------------------------------------------------------------------------- /examples/cupoftee/shared/header.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pallets/werkzeug/99fc293bc7dccd6a7dc96b96012d4aa849794ba4/examples/cupoftee/shared/header.png -------------------------------------------------------------------------------- /examples/cupoftee/shared/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pallets/werkzeug/99fc293bc7dccd6a7dc96b96012d4aa849794ba4/examples/cupoftee/shared/logo.png -------------------------------------------------------------------------------- /examples/cupoftee/shared/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: 'Verdana', sans-serif; 3 | background: #2b93ad; 4 | margin: 0; 5 | padding: 0; 6 | font-size: 15px; 7 | text-align: center; 8 | } 9 | 10 | h1 { 11 | font-size: 0; 12 | margin: 0; 13 | padding: 10px 0 0 10px; 14 | height: 124px; 15 | line-height: 100px; 16 | background: url(header.png); 17 | color: white; 18 | } 19 | 20 | h1 a { 21 | display: block; 22 | margin: 0 auto 0 auto; 23 | height: 90px; 24 | width: 395px; 25 | background: url(logo.png); 26 | } 27 | 28 | div.contents { 29 | background: white url(content.png) repeat-x; 30 | margin: -8px auto 0 auto; 31 | text-align: left; 32 | padding: 15px; 33 | max-width: 1000px; 34 | } 35 | 36 | div.contents a { 37 | margin: 0 5px 0 5px; 38 | } 39 | 40 | div.footer { 41 | max-width: 1014px; 42 | margin: 0 auto 0 auto; 43 | background: #1a6f96; 44 | padding: 8px; 45 | font-size: 10px; 46 | color: white; 47 | } 48 | 49 | div.footer a { 50 | color: #79b9d7; 51 | } 52 | 53 | a { 54 | color: #1a6f96; 55 | text-decoration: none; 56 | } 57 | 58 | a:hover { 59 | color: #ffb735; 60 | } 61 | 62 | h2 { 63 | margin: 0 0 0.5em 0; 64 | padding: 0 0 0.1em 0; 65 | color: #ffb735; 66 | font-size: 2em; 67 | border-bottom: 1px solid #ccc; 68 | } 69 | 70 | h3 { 71 | margin: 1em 0 0.7em 0; 72 | color: #ffb735; 73 | font-size: 1.5em; 74 | } 75 | 76 | table { 77 | width: 100%; 78 | border-collapse: collapse; 79 | border: 3px solid #79b9d7; 80 | } 81 | 82 | table td, table th { 83 | border: 1px solid #79b9d7; 84 | padding: 3px 6px 3px 6px; 85 | font-weight: normal; 86 | text-align: center; 87 | font-size: 13px; 88 | } 89 | 90 | table th { 91 | background: #f2f8fb; 92 | text-align: left; 93 | } 94 | 95 | table thead th { 96 | font-weight: bold; 97 | background-color: #79b9d7; 98 | text-align: center; 99 | } 100 | 101 | table thead th a { 102 | color: white; 103 | } 104 | 105 | table thead th a.up { 106 | background: url(up.png) no-repeat right; 107 | padding-right: 20px; 108 | } 109 | 110 | table thead th a.down { 111 | background: url(down.png) no-repeat right; 112 | padding-right: 20px; 113 | } 114 | 115 | div.players { 116 | font-size: 11px; 117 | } 118 | 119 | dl dt { 120 | font-weight: bold; 121 | padding: 5px 0 0 0; 122 | } 123 | -------------------------------------------------------------------------------- /examples/cupoftee/shared/up.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pallets/werkzeug/99fc293bc7dccd6a7dc96b96012d4aa849794ba4/examples/cupoftee/shared/up.png -------------------------------------------------------------------------------- /examples/cupoftee/templates/layout.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Teeworlds Server Browser 5 | 6 | 7 | 8 | 9 |

Teeworlds Server Browser

10 |
11 | {% block body %}{% endblock %} 12 |
13 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /examples/cupoftee/templates/missingpage.html: -------------------------------------------------------------------------------- 1 | {% extends "layout.html" %} 2 | {% block body %} 3 |

Page Not Found

4 |

5 | The requested page does not exist on this server. If you expect something 6 | here (for example a server) it probably went away after the last update. 7 |

8 |

9 | go back to the server list. 10 |

11 | {% endblock %} 12 | -------------------------------------------------------------------------------- /examples/cupoftee/templates/search.html: -------------------------------------------------------------------------------- 1 | {% extends "layout.html" %} 2 | {% block body %} 3 |

Nick Search

4 | {% if not user %} 5 |
6 |

7 | You have to enter a nickname. 8 |

9 |

10 | 11 | 12 |

13 |

14 | Take me back to the server list. 15 |

16 |
17 | {% else %} 18 | {% if results %} 19 |

20 | The nickname "{{ user }}" is currently playing on the 21 | following {{ 'server' if results|length == 1 else 'servers' }}: 22 |

23 | 28 | {% else %} 29 |

30 | The nickname "{{ user }}" is currently not playing. 31 |

32 | {% endif %} 33 |

34 | You can bookmark this link 35 | to search for "{{ user }}" quickly or return 36 | to the server list. 37 |

38 | {% endif %} 39 | {% endblock %} 40 | -------------------------------------------------------------------------------- /examples/cupoftee/templates/server.html: -------------------------------------------------------------------------------- 1 | {% extends "layout.html" %} 2 | {% block body %} 3 |

{{ server.name }}

4 |

5 | Take me back to the server list. 6 |

7 |
8 |
Map
9 |
{{ server.map }}
10 |
Gametype
11 |
{{ server.gametype }}
12 |
Number of players
13 |
{{ server.player_count }}
14 |
Server version
15 |
{{ server.version }}
16 |
Maximum number of players
17 |
{{ server.max_players }}
18 | {% if server.progression >= 0 %} 19 |
Game progression
20 |
{{ server.progression }}%
21 | {% endif %} 22 |
23 | {% if server.players %} 24 |

Players

25 |
    26 | {% for player in server.players %} 27 |
  • {{ player.name }} 28 | ({{ player.score }}
  • 29 | {% endfor %} 30 |
31 | {% endif %} 32 | {% endblock %} 33 | -------------------------------------------------------------------------------- /examples/cupoftee/templates/serverlist.html: -------------------------------------------------------------------------------- 1 | {% extends "layout.html" %} 2 | {% block body %} 3 |

Server List

4 |

5 | Currently {{ len(players) }} players are playing on 6 | {{ len(servers) }} servers. 7 | {% if cup.server_browser.last_sync %} 8 | This list was last synced on 9 | {{ cup.server_browser.last_sync.strftime('%d %B %Y at %H:%M UTC') }}. 10 | {% else %} 11 | Synchronization with main server in progress. Reload the page in a minute 12 | or two, to see the server list. 13 | {% endif %} 14 |

15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | {% for server in servers %} 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | {% endfor %} 35 | 36 |
{{ self.order_link('name', 'Name') }}{{ self.order_link('map', 'Map') }}{{ self.order_link('gametype', 'Gametype') }}{{ self.order_link('players', 'Players') }}{{ self.order_link('progression', 'Progression') }}
{{ server.name }}{{ server.map }}{{ server.gametype }}{{ server.player_count }} / {{ server.max_players }}{{ '%d%%' % server.progression if server.progression >= 0 else '?' }}
37 |

Players online

38 |

39 | The following map represents the users playing currently. The bigger their name 40 | the higher their score in the current game. Clicking on the name takes you to 41 | the detail page of the server for some more information. 42 |

43 |
44 | {% for player in players %} 45 | {{ player.name }} 47 | {% endfor %} 48 |
49 |

Find User

50 |

51 | Find a user by username. The result page contains a link you can bookmark to 52 | find your buddy easily. Because currently there is no central user database 53 | users can appear on multiple servers for too generic usernames (like the 54 | default "nameless tee" user). 55 |

56 |
57 |

58 | 59 | 60 |

61 |
62 | {% endblock %} 63 | -------------------------------------------------------------------------------- /examples/cupoftee/utils.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | 4 | _sort_re = re.compile(r"\w+") 5 | 6 | 7 | def unicodecmp(a, b): 8 | x, y = map(_sort_re.search, [a, b]) 9 | x = (x.group() if x else a).lower() 10 | y = (y.group() if y else b).lower() 11 | return (x > y) - (x < y) 12 | -------------------------------------------------------------------------------- /examples/httpbasicauth.py: -------------------------------------------------------------------------------- 1 | """Shows how you can implement HTTP basic auth support without an 2 | additional component. 3 | """ 4 | from werkzeug.serving import run_simple 5 | from werkzeug.wrappers import Request 6 | from werkzeug.wrappers import Response 7 | 8 | 9 | class Application: 10 | def __init__(self, users, realm="login required"): 11 | self.users = users 12 | self.realm = realm 13 | 14 | def check_auth(self, username, password): 15 | return username in self.users and self.users[username] == password 16 | 17 | def auth_required(self, request): 18 | return Response( 19 | "Could not verify your access level for that URL.\n" 20 | "You have to login with proper credentials", 21 | 401, 22 | {"WWW-Authenticate": f'Basic realm="{self.realm}"'}, 23 | ) 24 | 25 | def dispatch_request(self, request): 26 | return Response(f"Logged in as {request.authorization.username}") 27 | 28 | def __call__(self, environ, start_response): 29 | request = Request(environ) 30 | auth = request.authorization 31 | if not auth or not self.check_auth(auth.username, auth.password): 32 | response = self.auth_required(request) 33 | else: 34 | response = self.dispatch_request(request) 35 | return response(environ, start_response) 36 | 37 | 38 | if __name__ == "__main__": 39 | application = Application({"user1": "password", "user2": "password"}) 40 | run_simple("localhost", 5000, application) 41 | -------------------------------------------------------------------------------- /examples/i18nurls/__init__.py: -------------------------------------------------------------------------------- 1 | from .application import Application as make_app 2 | -------------------------------------------------------------------------------- /examples/i18nurls/application.py: -------------------------------------------------------------------------------- 1 | from os import path 2 | 3 | from jinja2 import Environment 4 | from jinja2 import PackageLoader 5 | from werkzeug.exceptions import HTTPException 6 | from werkzeug.exceptions import NotFound 7 | from werkzeug.routing import RequestRedirect 8 | from werkzeug.wrappers import Request as BaseRequest 9 | from werkzeug.wrappers import Response as BaseResponse 10 | 11 | from .urls import map 12 | 13 | TEMPLATES = path.join(path.dirname(__file__), "templates") 14 | views = {} 15 | 16 | 17 | def expose(name): 18 | """Register the function as view.""" 19 | 20 | def wrapped(f): 21 | views[name] = f 22 | return f 23 | 24 | return wrapped 25 | 26 | 27 | class Request(BaseRequest): 28 | def __init__(self, environ, urls): 29 | super().__init__(environ) 30 | self.urls = urls 31 | self.matched_url = None 32 | 33 | def url_for(self, endpoint, **args): 34 | if "lang_code" not in args: 35 | args["lang_code"] = self.language 36 | if endpoint == "this": 37 | endpoint = self.matched_url[0] 38 | tmp = self.matched_url[1].copy() 39 | tmp.update(args) 40 | args = tmp 41 | return self.urls.build(endpoint, args) 42 | 43 | 44 | class Response(BaseResponse): 45 | pass 46 | 47 | 48 | class TemplateResponse(Response): 49 | jinja_env = Environment(loader=PackageLoader("i18nurls"), autoescape=True) 50 | 51 | def __init__(self, template_name, **values): 52 | self.template_name = template_name 53 | self.template_values = values 54 | Response.__init__(self, mimetype="text/html") 55 | 56 | def __call__(self, environ, start_response): 57 | req = environ["werkzeug.request"] 58 | values = self.template_values.copy() 59 | values["req"] = req 60 | self.data = self.render_template(self.template_name, values) 61 | return super().__call__(environ, start_response) 62 | 63 | def render_template(self, name, values): 64 | template = self.jinja_env.get_template(name) 65 | return template.render(values) 66 | 67 | 68 | class Application: 69 | def __init__(self): 70 | from i18nurls import views 71 | 72 | self.not_found = views.page_not_found 73 | 74 | def __call__(self, environ, start_response): 75 | urls = map.bind_to_environ(environ) 76 | req = Request(environ, urls) 77 | try: 78 | endpoint, args = urls.match(req.path) 79 | req.matched_url = (endpoint, args) 80 | if endpoint == "#language_select": 81 | lng = req.accept_languages.best 82 | lng = lng.split("-")[0].lower() if lng else "en" 83 | index_url = urls.build("index", {"lang_code": lng}) 84 | resp = Response(f"Moved to {index_url}", status=302) 85 | resp.headers["Location"] = index_url 86 | else: 87 | req.language = args.pop("lang_code", None) 88 | resp = views[endpoint](req, **args) 89 | except NotFound: 90 | resp = self.not_found(req) 91 | except (RequestRedirect, HTTPException) as e: 92 | resp = e 93 | return resp(environ, start_response) 94 | -------------------------------------------------------------------------------- /examples/i18nurls/templates/about.html: -------------------------------------------------------------------------------- 1 | {% extends "layout.html" %} 2 | {% block body %} 3 |

4 | This is just another page. Maybe you want to head over to the 5 | blog. 6 |

7 | {% endblock %} 8 | -------------------------------------------------------------------------------- /examples/i18nurls/templates/blog.html: -------------------------------------------------------------------------------- 1 | {% extends "layout.html" %} 2 | {% block body %} 3 |

Blog {% if mode == 'index' %}Index{% else %}Post {{ post_id }}{% endif %}

4 |

5 | How about going to the index. 6 |

7 | {% endblock %} 8 | -------------------------------------------------------------------------------- /examples/i18nurls/templates/index.html: -------------------------------------------------------------------------------- 1 | {% extends "layout.html" %} 2 | {% block body %} 3 |

Hello in the i18n URL example application.

4 |

Because I'm too lazy to translate here is just english content.

5 | 8 | {% endblock %} 9 | -------------------------------------------------------------------------------- /examples/i18nurls/templates/layout.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {{ title }} | Example Application 5 | 6 | 7 |

Example Application

8 |

9 | Request Language: {{ req.language }} 10 |

11 | {% block body %}{% endblock %} 12 |
13 |

This page in other languages: 14 |

    15 | {% for lng in ['en', 'de', 'fr'] %} 16 |
  • {{ lng }}
  • 17 | {% endfor %} 18 |
19 |
20 | 21 | 22 | -------------------------------------------------------------------------------- /examples/i18nurls/urls.py: -------------------------------------------------------------------------------- 1 | from werkzeug.routing import Map 2 | from werkzeug.routing import Rule 3 | from werkzeug.routing import Submount 4 | 5 | map = Map( 6 | [ 7 | Rule("/", endpoint="#language_select"), 8 | Submount( 9 | "/", 10 | [ 11 | Rule("/", endpoint="index"), 12 | Rule("/about", endpoint="about"), 13 | Rule("/blog/", endpoint="blog/index"), 14 | Rule("/blog/", endpoint="blog/show"), 15 | ], 16 | ), 17 | ] 18 | ) 19 | -------------------------------------------------------------------------------- /examples/i18nurls/views.py: -------------------------------------------------------------------------------- 1 | from .application import expose 2 | from .application import Response 3 | from .application import TemplateResponse 4 | 5 | 6 | @expose("index") 7 | def index(req): 8 | return TemplateResponse("index.html", title="Index") 9 | 10 | 11 | @expose("about") 12 | def about(req): 13 | return TemplateResponse("about.html", title="About") 14 | 15 | 16 | @expose("blog/index") 17 | def blog_index(req): 18 | return TemplateResponse("blog.html", title="Blog Index", mode="index") 19 | 20 | 21 | @expose("blog/show") 22 | def blog_show(req, post_id): 23 | return TemplateResponse( 24 | "blog.html", title=f"Blog Post #{post_id}", post_id=post_id, mode="show" 25 | ) 26 | 27 | 28 | def page_not_found(req): 29 | return Response("

Page Not Found

", mimetype="text/html") 30 | -------------------------------------------------------------------------------- /examples/manage-coolmagic.py: -------------------------------------------------------------------------------- 1 | import click 2 | from werkzeug.serving import run_simple 3 | 4 | from coolmagic import make_app 5 | 6 | 7 | @click.group() 8 | def cli(): 9 | pass 10 | 11 | 12 | @cli.command() 13 | @click.option("-h", "--hostname", type=str, default="localhost", help="localhost") 14 | @click.option("-p", "--port", type=int, default=5000, help="5000") 15 | @click.option("--no-reloader", is_flag=True, default=False) 16 | @click.option("--debugger", is_flag=True) 17 | @click.option("--no-evalex", is_flag=True, default=False) 18 | @click.option("--threaded", is_flag=True) 19 | @click.option("--processes", type=int, default=1, help="1") 20 | def runserver(hostname, port, no_reloader, debugger, no_evalex, threaded, processes): 21 | """Start a new development server.""" 22 | app = make_app() 23 | reloader = not no_reloader 24 | evalex = not no_evalex 25 | run_simple( 26 | hostname, 27 | port, 28 | app, 29 | use_reloader=reloader, 30 | use_debugger=debugger, 31 | use_evalex=evalex, 32 | threaded=threaded, 33 | processes=processes, 34 | ) 35 | 36 | 37 | @cli.command() 38 | @click.option("--no-ipython", is_flag=True, default=False) 39 | def shell(no_ipython): 40 | """Start a new interactive python session.""" 41 | banner = "Interactive Werkzeug Shell" 42 | namespace = dict() 43 | if not no_ipython: 44 | try: 45 | try: 46 | from IPython.frontend.terminal.embed import InteractiveShellEmbed 47 | 48 | sh = InteractiveShellEmbed.instance(banner1=banner) 49 | except ImportError: 50 | from IPython.Shell import IPShellEmbed 51 | 52 | sh = IPShellEmbed(banner=banner) 53 | except ImportError: 54 | pass 55 | else: 56 | sh(local_ns=namespace) 57 | return 58 | from code import interact 59 | 60 | interact(banner, local=namespace) 61 | 62 | 63 | if __name__ == "__main__": 64 | cli() 65 | -------------------------------------------------------------------------------- /examples/manage-couchy.py: -------------------------------------------------------------------------------- 1 | import click 2 | from werkzeug.serving import run_simple 3 | 4 | 5 | def make_app(): 6 | from couchy.application import Couchy 7 | 8 | return Couchy("http://localhost:5984") 9 | 10 | 11 | def make_shell(): 12 | from couchy import models, utils 13 | 14 | application = make_app() 15 | return {"application": application, "models": models, "utils": utils} 16 | 17 | 18 | @click.group() 19 | def cli(): 20 | pass 21 | 22 | 23 | @cli.command() 24 | def initdb(): 25 | from couchy.application import Couchy 26 | 27 | Couchy("http://localhost:5984").init_database() 28 | 29 | 30 | @cli.command() 31 | @click.option("-h", "--hostname", type=str, default="localhost", help="localhost") 32 | @click.option("-p", "--port", type=int, default=5000, help="5000") 33 | @click.option("--no-reloader", is_flag=True, default=False) 34 | @click.option("--debugger", is_flag=True) 35 | @click.option("--no-evalex", is_flag=True, default=False) 36 | @click.option("--threaded", is_flag=True) 37 | @click.option("--processes", type=int, default=1, help="1") 38 | def runserver(hostname, port, no_reloader, debugger, no_evalex, threaded, processes): 39 | """Start a new development server.""" 40 | app = make_app() 41 | reloader = not no_reloader 42 | evalex = not no_evalex 43 | run_simple( 44 | hostname, 45 | port, 46 | app, 47 | use_reloader=reloader, 48 | use_debugger=debugger, 49 | use_evalex=evalex, 50 | threaded=threaded, 51 | processes=processes, 52 | ) 53 | 54 | 55 | @cli.command() 56 | @click.option("--no-ipython", is_flag=True, default=False) 57 | def shell(no_ipython): 58 | """Start a new interactive python session.""" 59 | banner = "Interactive Werkzeug Shell" 60 | namespace = make_shell() 61 | if not no_ipython: 62 | try: 63 | try: 64 | from IPython.frontend.terminal.embed import InteractiveShellEmbed 65 | 66 | sh = InteractiveShellEmbed.instance(banner1=banner) 67 | except ImportError: 68 | from IPython.Shell import IPShellEmbed 69 | 70 | sh = IPShellEmbed(banner=banner) 71 | except ImportError: 72 | pass 73 | else: 74 | sh(local_ns=namespace) 75 | return 76 | from code import interact 77 | 78 | interact(banner, local=namespace) 79 | 80 | 81 | if __name__ == "__main__": 82 | cli() 83 | -------------------------------------------------------------------------------- /examples/manage-cupoftee.py: -------------------------------------------------------------------------------- 1 | """ 2 | Manage Cup Of Tee 3 | ~~~~~~~~~~~~~~~~~ 4 | 5 | Manage the cup of tee application. 6 | 7 | :copyright: 2007 Pallets 8 | :license: BSD-3-Clause 9 | """ 10 | import click 11 | from werkzeug.serving import run_simple 12 | 13 | 14 | def make_app(): 15 | from cupoftee import make_app 16 | 17 | return make_app("/tmp/cupoftee.db") 18 | 19 | 20 | @click.group() 21 | def cli(): 22 | pass 23 | 24 | 25 | @cli.command() 26 | @click.option("-h", "--hostname", type=str, default="localhost", help="localhost") 27 | @click.option("-p", "--port", type=int, default=5000, help="5000") 28 | @click.option("--reloader", is_flag=True, default=False) 29 | @click.option("--debugger", is_flag=True) 30 | @click.option("--evalex", is_flag=True, default=False) 31 | @click.option("--threaded", is_flag=True) 32 | @click.option("--processes", type=int, default=1, help="1") 33 | def runserver(hostname, port, reloader, debugger, evalex, threaded, processes): 34 | """Start a new development server.""" 35 | app = make_app() 36 | run_simple( 37 | hostname, 38 | port, 39 | app, 40 | use_reloader=reloader, 41 | use_debugger=debugger, 42 | use_evalex=evalex, 43 | threaded=threaded, 44 | processes=processes, 45 | ) 46 | 47 | 48 | if __name__ == "__main__": 49 | cli() 50 | -------------------------------------------------------------------------------- /examples/manage-i18nurls.py: -------------------------------------------------------------------------------- 1 | import click 2 | from werkzeug.serving import run_simple 3 | 4 | from i18nurls import make_app 5 | 6 | 7 | @click.group() 8 | def cli(): 9 | pass 10 | 11 | 12 | @cli.command() 13 | @click.option("-h", "--hostname", type=str, default="localhost", help="localhost") 14 | @click.option("-p", "--port", type=int, default=5000, help="5000") 15 | @click.option("--no-reloader", is_flag=True, default=False) 16 | @click.option("--debugger", is_flag=True) 17 | @click.option("--no-evalex", is_flag=True, default=False) 18 | @click.option("--threaded", is_flag=True) 19 | @click.option("--processes", type=int, default=1, help="1") 20 | def runserver(hostname, port, no_reloader, debugger, no_evalex, threaded, processes): 21 | """Start a new development server.""" 22 | app = make_app() 23 | reloader = not no_reloader 24 | evalex = not no_evalex 25 | run_simple( 26 | hostname, 27 | port, 28 | app, 29 | use_reloader=reloader, 30 | use_debugger=debugger, 31 | use_evalex=evalex, 32 | threaded=threaded, 33 | processes=processes, 34 | ) 35 | 36 | 37 | @cli.command() 38 | @click.option("--no-ipython", is_flag=True, default=False) 39 | def shell(no_ipython): 40 | """Start a new interactive python session.""" 41 | banner = "Interactive Werkzeug Shell" 42 | namespace = dict() 43 | if not no_ipython: 44 | try: 45 | try: 46 | from IPython.frontend.terminal.embed import InteractiveShellEmbed 47 | 48 | sh = InteractiveShellEmbed.instance(banner1=banner) 49 | except ImportError: 50 | from IPython.Shell import IPShellEmbed 51 | 52 | sh = IPShellEmbed(banner=banner) 53 | except ImportError: 54 | pass 55 | else: 56 | sh(local_ns=namespace) 57 | return 58 | from code import interact 59 | 60 | interact(banner, local=namespace) 61 | 62 | 63 | if __name__ == "__main__": 64 | cli() 65 | -------------------------------------------------------------------------------- /examples/manage-shorty.py: -------------------------------------------------------------------------------- 1 | import os 2 | import tempfile 3 | 4 | import click 5 | from werkzeug.serving import run_simple 6 | 7 | 8 | def make_app(): 9 | from shorty.application import Shorty 10 | 11 | filename = os.path.join(tempfile.gettempdir(), "shorty.db") 12 | return Shorty(f"sqlite:///{filename}") 13 | 14 | 15 | def make_shell(): 16 | from shorty import models, utils 17 | 18 | application = make_app() 19 | return {"application": application, "models": models, "utils": utils} 20 | 21 | 22 | @click.group() 23 | def cli(): 24 | pass 25 | 26 | 27 | @cli.command() 28 | def initdb(): 29 | make_app().init_database() 30 | 31 | 32 | @cli.command() 33 | @click.option("-h", "--hostname", type=str, default="localhost", help="localhost") 34 | @click.option("-p", "--port", type=int, default=5000, help="5000") 35 | @click.option("--no-reloader", is_flag=True, default=False) 36 | @click.option("--debugger", is_flag=True) 37 | @click.option("--no-evalex", is_flag=True, default=False) 38 | @click.option("--threaded", is_flag=True) 39 | @click.option("--processes", type=int, default=1, help="1") 40 | def runserver(hostname, port, no_reloader, debugger, no_evalex, threaded, processes): 41 | """Start a new development server.""" 42 | app = make_app() 43 | reloader = not no_reloader 44 | evalex = not no_evalex 45 | run_simple( 46 | hostname, 47 | port, 48 | app, 49 | use_reloader=reloader, 50 | use_debugger=debugger, 51 | use_evalex=evalex, 52 | threaded=threaded, 53 | processes=processes, 54 | ) 55 | 56 | 57 | @cli.command() 58 | @click.option("--no-ipython", is_flag=True, default=False) 59 | def shell(no_ipython): 60 | """Start a new interactive python session.""" 61 | banner = "Interactive Werkzeug Shell" 62 | namespace = make_shell() 63 | if not no_ipython: 64 | try: 65 | try: 66 | from IPython.frontend.terminal.embed import InteractiveShellEmbed 67 | 68 | sh = InteractiveShellEmbed.instance(banner1=banner) 69 | except ImportError: 70 | from IPython.Shell import IPShellEmbed 71 | 72 | sh = IPShellEmbed(banner=banner) 73 | except ImportError: 74 | pass 75 | else: 76 | sh(local_ns=namespace) 77 | return 78 | from code import interact 79 | 80 | interact(banner, local=namespace) 81 | 82 | 83 | if __name__ == "__main__": 84 | cli() 85 | -------------------------------------------------------------------------------- /examples/manage-simplewiki.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | import click 4 | from werkzeug.serving import run_simple 5 | 6 | 7 | def make_wiki(): 8 | """Helper function that creates a new wiki instance.""" 9 | from simplewiki import SimpleWiki 10 | 11 | database_uri = os.environ.get("SIMPLEWIKI_DATABASE_URI") 12 | return SimpleWiki(database_uri or "sqlite:////tmp/simplewiki.db") 13 | 14 | 15 | def make_shell(): 16 | from simplewiki import database 17 | 18 | wiki = make_wiki() 19 | wiki.bind_to_context() 20 | return {"wiki": wiki, "db": database} 21 | 22 | 23 | @click.group() 24 | def cli(): 25 | pass 26 | 27 | 28 | @cli.command() 29 | def initdb(): 30 | make_wiki().init_database() 31 | 32 | 33 | @cli.command() 34 | @click.option("-h", "--hostname", type=str, default="localhost", help="localhost") 35 | @click.option("-p", "--port", type=int, default=5000, help="5000") 36 | @click.option("--no-reloader", is_flag=True, default=False) 37 | @click.option("--debugger", is_flag=True) 38 | @click.option("--no-evalex", is_flag=True, default=False) 39 | @click.option("--threaded", is_flag=True) 40 | @click.option("--processes", type=int, default=1, help="1") 41 | def runserver(hostname, port, no_reloader, debugger, no_evalex, threaded, processes): 42 | """Start a new development server.""" 43 | app = make_wiki() 44 | reloader = not no_reloader 45 | evalex = not no_evalex 46 | run_simple( 47 | hostname, 48 | port, 49 | app, 50 | use_reloader=reloader, 51 | use_debugger=debugger, 52 | use_evalex=evalex, 53 | threaded=threaded, 54 | processes=processes, 55 | ) 56 | 57 | 58 | @cli.command() 59 | @click.option("--no-ipython", is_flag=True, default=False) 60 | def shell(no_ipython): 61 | """Start a new interactive python session.""" 62 | banner = "Interactive Werkzeug Shell" 63 | namespace = make_shell() 64 | if not no_ipython: 65 | try: 66 | try: 67 | from IPython.frontend.terminal.embed import InteractiveShellEmbed 68 | 69 | sh = InteractiveShellEmbed.instance(banner1=banner) 70 | except ImportError: 71 | from IPython.Shell import IPShellEmbed 72 | 73 | sh = IPShellEmbed(banner=banner) 74 | except ImportError: 75 | pass 76 | else: 77 | sh(local_ns=namespace) 78 | return 79 | from code import interact 80 | 81 | interact(banner, local=namespace) 82 | 83 | 84 | if __name__ == "__main__": 85 | cli() 86 | -------------------------------------------------------------------------------- /examples/manage-webpylike.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | 4 | import click 5 | from werkzeug.serving import run_simple 6 | 7 | from webpylike.example import app 8 | 9 | sys.path.append(os.path.join(os.path.dirname(__file__), "webpylike")) 10 | 11 | 12 | @click.group() 13 | def cli(): 14 | pass 15 | 16 | 17 | @cli.command() 18 | @click.option("-h", "--hostname", type=str, default="localhost", help="localhost") 19 | @click.option("-p", "--port", type=int, default=5000, help="5000") 20 | @click.option("--no-reloader", is_flag=True, default=False) 21 | @click.option("--debugger", is_flag=True) 22 | @click.option("--no-evalex", is_flag=True, default=False) 23 | @click.option("--threaded", is_flag=True) 24 | @click.option("--processes", type=int, default=1, help="1") 25 | def runserver(hostname, port, no_reloader, debugger, no_evalex, threaded, processes): 26 | """Start a new development server.""" 27 | reloader = not no_reloader 28 | evalex = not no_evalex 29 | run_simple( 30 | hostname, 31 | port, 32 | app, 33 | use_reloader=reloader, 34 | use_debugger=debugger, 35 | use_evalex=evalex, 36 | threaded=threaded, 37 | processes=processes, 38 | ) 39 | 40 | 41 | @cli.command() 42 | @click.option("--no-ipython", is_flag=True, default=False) 43 | def shell(no_ipython): 44 | """Start a new interactive python session.""" 45 | banner = "Interactive Werkzeug Shell" 46 | namespace = dict() 47 | if not no_ipython: 48 | try: 49 | try: 50 | from IPython.frontend.terminal.embed import InteractiveShellEmbed 51 | 52 | sh = InteractiveShellEmbed.instance(banner1=banner) 53 | except ImportError: 54 | from IPython.Shell import IPShellEmbed 55 | 56 | sh = IPShellEmbed(banner=banner) 57 | except ImportError: 58 | pass 59 | else: 60 | sh(local_ns=namespace) 61 | return 62 | from code import interact 63 | 64 | interact(banner, local=namespace) 65 | 66 | 67 | if __name__ == "__main__": 68 | cli() 69 | -------------------------------------------------------------------------------- /examples/partial/README: -------------------------------------------------------------------------------- 1 | This directory contains modules that have code but that are 2 | not excutable. For example routing definitions to play around 3 | in the python interactive prompt. 4 | -------------------------------------------------------------------------------- /examples/partial/complex_routing.py: -------------------------------------------------------------------------------- 1 | from werkzeug.routing import EndpointPrefix 2 | from werkzeug.routing import Map 3 | from werkzeug.routing import Rule 4 | from werkzeug.routing import Subdomain 5 | from werkzeug.routing import Submount 6 | 7 | m = Map( 8 | [ 9 | # Static URLs 10 | EndpointPrefix( 11 | "static/", 12 | [ 13 | Rule("/", endpoint="index"), 14 | Rule("/about", endpoint="about"), 15 | Rule("/help", endpoint="help"), 16 | ], 17 | ), 18 | # Knowledge Base 19 | Subdomain( 20 | "kb", 21 | [ 22 | EndpointPrefix( 23 | "kb/", 24 | [ 25 | Rule("/", endpoint="index"), 26 | Submount( 27 | "/browse", 28 | [ 29 | Rule("/", endpoint="browse"), 30 | Rule( 31 | "//", 32 | defaults={"page": 1}, 33 | endpoint="browse", 34 | ), 35 | Rule("//", endpoint="browse"), 36 | ], 37 | ), 38 | ], 39 | ) 40 | ], 41 | ), 42 | ] 43 | ) 44 | -------------------------------------------------------------------------------- /examples/plnt/__init__.py: -------------------------------------------------------------------------------- 1 | """A planet application, pronounced "plant".""" 2 | from .webapp import Plnt 3 | -------------------------------------------------------------------------------- /examples/plnt/database.py: -------------------------------------------------------------------------------- 1 | from sqlalchemy import Column 2 | from sqlalchemy import DateTime 3 | from sqlalchemy import ForeignKey 4 | from sqlalchemy import Integer 5 | from sqlalchemy import MetaData 6 | from sqlalchemy import String 7 | from sqlalchemy import Table 8 | from sqlalchemy.orm import create_session 9 | from sqlalchemy.orm import dynamic_loader 10 | from sqlalchemy.orm import mapper 11 | from sqlalchemy.orm import scoped_session 12 | 13 | from .utils import application 14 | 15 | try: 16 | from greenlet import getcurrent as get_ident 17 | except ImportError: 18 | from threading import get_ident 19 | 20 | 21 | def new_db_session(): 22 | return create_session(application.database_engine, autoflush=True, autocommit=False) 23 | 24 | 25 | metadata = MetaData() 26 | session = scoped_session(new_db_session, get_ident) 27 | 28 | 29 | blog_table = Table( 30 | "blogs", 31 | metadata, 32 | Column("id", Integer, primary_key=True), 33 | Column("name", String(120)), 34 | Column("description", String), 35 | Column("url", String(200)), 36 | Column("feed_url", String(250)), 37 | ) 38 | 39 | entry_table = Table( 40 | "entries", 41 | metadata, 42 | Column("id", Integer, primary_key=True), 43 | Column("blog_id", Integer, ForeignKey("blogs.id")), 44 | Column("guid", String(200), unique=True), 45 | Column("title", String(140)), 46 | Column("url", String(200)), 47 | Column("text", String), 48 | Column("pub_date", DateTime), 49 | Column("last_update", DateTime), 50 | ) 51 | 52 | 53 | class Blog: 54 | query = session.query_property() 55 | 56 | def __init__(self, name, url, feed_url, description=""): 57 | self.name = name 58 | self.url = url 59 | self.feed_url = feed_url 60 | self.description = description 61 | 62 | def __repr__(self): 63 | return f"<{type(self).__name__} {self.url!r}>" 64 | 65 | 66 | class Entry: 67 | query = session.query_property() 68 | 69 | def __repr__(self): 70 | return f"<{type(self).__name__} {self.guid!r}>" 71 | 72 | 73 | mapper(Entry, entry_table) 74 | mapper(Blog, blog_table, properties=dict(entries=dynamic_loader(Entry, backref="blog"))) 75 | -------------------------------------------------------------------------------- /examples/plnt/shared/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: 'Luxi Sans', 'Lucida Sans', 'Verdana', sans-serif; 3 | margin: 1em; 4 | padding: 0; 5 | background-color: #BDE1EC; 6 | color: #0B2B35; 7 | } 8 | 9 | a { 10 | color: #50ACC4; 11 | } 12 | 13 | a:hover { 14 | color: #0B2B35; 15 | } 16 | 17 | div.header { 18 | display: block; 19 | margin: -1em -1em 0 -1em; 20 | padding: 1em; 21 | background-color: #0B2B35; 22 | color: white; 23 | } 24 | 25 | div.header h1 { 26 | font-family: 'Georgia', serif; 27 | margin: 0; 28 | font-size: 1.8em; 29 | } 30 | 31 | div.header blockquote { 32 | margin: 0; 33 | padding: 0.5em 0 0 1em; 34 | font-size: 0.9em; 35 | } 36 | 37 | div.footer { 38 | margin: 1em -1em -1em -1em; 39 | padding: 0.5em; 40 | color: #F3F7F8; 41 | background-color: #1F6070; 42 | } 43 | 44 | div.footer p { 45 | margin: 0; 46 | padding: 0; 47 | font-size: 0.8em; 48 | text-align: right; 49 | } 50 | 51 | ul.navigation { 52 | float: right; 53 | padding: 0.7em 1em 0.7em 1em; 54 | background-color: #F3F7F8; 55 | border: 1px solid #85CADB; 56 | border-right-color: #50ACC4; 57 | border-bottom-color: #50ACC4; 58 | list-style: none; 59 | } 60 | 61 | ul.navigation li { 62 | padding: 0.3em 0 0.3em 0; 63 | } 64 | 65 | ul.navigation li a { 66 | color: #0B2B35; 67 | } 68 | 69 | ul.navigation li a:hover { 70 | color: #50ACC4; 71 | } 72 | 73 | div.pagination { 74 | margin: 0.5em 0 0.5em 0; 75 | padding: 0.7em; 76 | text-align: center; 77 | max-width: 50em; 78 | background-color: white; 79 | border: 1px solid #B1CDD4; 80 | } 81 | 82 | div.day, div.page { 83 | max-width: 50em; 84 | background-color: white; 85 | border: 1px solid #50ACC4; 86 | margin: 1em 0 1em 0; 87 | padding: 0.7em; 88 | } 89 | 90 | div.day h2, div.page h2 { 91 | margin: 0 0 0.5em 0; 92 | padding: 0; 93 | color: black; 94 | font-size: 1.7em; 95 | } 96 | 97 | div.page p { 98 | margin: 0.7em 1em 0.7em 1em; 99 | line-height: 1.5em; 100 | } 101 | 102 | div.day div.entry { 103 | margin: 0.5em 0.25em 0.5em 1em; 104 | padding: 1em; 105 | background-color: #F3F7F8; 106 | border: 1px solid #85CADB; 107 | border-left-color: #50ACC4; 108 | border-top-color: #50ACC4; 109 | } 110 | 111 | div.day div.entry h3 { 112 | margin: 0; 113 | padding: 0; 114 | } 115 | 116 | div.day div.entry h3 a { 117 | color: #1C6D81; 118 | text-decoration: none; 119 | } 120 | 121 | div.day div.entry p.meta { 122 | color: #666; 123 | font-size: 0.85em; 124 | margin: 0.3em 0 0.6em 0; 125 | } 126 | 127 | div.day div.entry p.meta a { 128 | color: #666; 129 | } 130 | 131 | div.day div.entry div.text { 132 | padding: 0 0 0 0.5em; 133 | } 134 | -------------------------------------------------------------------------------- /examples/plnt/templates/about.html: -------------------------------------------------------------------------------- 1 | {% extends "layout.html" %} 2 | {% block body %} 3 |
4 |

About Plnt

5 |

6 | Plnt is a small example application written using the 7 | Werkzeug WSGI toolkit, 8 | the Jinja template language, 9 | the SQLAlchemy database abstraction 10 | layer and ORM and last but not least the awesome 11 | feedparser library. 12 |

13 |

14 | It's one of the example applications developed to show some of the 15 | features werkzeug provides and could be the base of a real planet 16 | software. 17 |

18 |
19 | {% endblock %} 20 | -------------------------------------------------------------------------------- /examples/plnt/templates/index.html: -------------------------------------------------------------------------------- 1 | {% extends "layout.html" %} 2 | {% block body %} 3 | {% for day in days %} 4 |
5 |

{{ day.date.strftime('%d %B %Y') }}

6 | {%- for entry in day.entries %} 7 |
8 |

{{ entry.title }}

9 |

by {{ entry.blog.name|e }} 10 | at {{ entry.pub_date.strftime('%H:%m') }}

11 |
{{ entry.text }}
12 |
13 | {%- endfor %} 14 |
15 | {%- endfor %} 16 | 17 | {% if pagination.pages > 1 %} 18 | 25 | {% endif %} 26 | {% endblock %} 27 | -------------------------------------------------------------------------------- /examples/plnt/templates/layout.html: -------------------------------------------------------------------------------- 1 | 2 | Plnt Planet 3 | 4 | 5 |
6 |

Plnt Planet

7 |
This is the Plnt Planet Werkzeug Example Application
8 |
9 | 10 | 14 | 15 |
16 | {% block body %}{% endblock %} 17 |
18 | 19 | 23 | -------------------------------------------------------------------------------- /examples/plnt/views.py: -------------------------------------------------------------------------------- 1 | """Display the aggregated feeds.""" 2 | from datetime import date 3 | 4 | from .database import Entry 5 | from .utils import expose 6 | from .utils import Pagination 7 | from .utils import render_template 8 | 9 | 10 | #: number of items per page 11 | PER_PAGE = 30 12 | 13 | 14 | @expose("/", defaults={"page": 1}) 15 | @expose("/page/") 16 | def index(request, page): 17 | """Show the index page or any an offset of it.""" 18 | days = [] 19 | days_found = set() 20 | query = Entry.query.order_by(Entry.pub_date.desc()) 21 | pagination = Pagination(query, PER_PAGE, page, "index") 22 | for entry in pagination.entries: 23 | day = date(*entry.pub_date.timetuple()[:3]) 24 | if day not in days_found: 25 | days_found.add(day) 26 | days.append({"date": day, "entries": []}) 27 | days[-1]["entries"].append(entry) 28 | return render_template("index.html", days=days, pagination=pagination) 29 | 30 | 31 | @expose("/about") 32 | def about(request): 33 | """Show the about page, so that we have another view func ;-)""" 34 | return render_template("about.html") 35 | -------------------------------------------------------------------------------- /examples/plnt/webapp.py: -------------------------------------------------------------------------------- 1 | from os import path 2 | 3 | from sqlalchemy import create_engine 4 | from werkzeug.exceptions import HTTPException 5 | from werkzeug.middleware.shared_data import SharedDataMiddleware 6 | from werkzeug.wrappers import Request 7 | from werkzeug.wsgi import ClosingIterator 8 | 9 | from . import views # noqa: F401 10 | from .database import metadata 11 | from .database import session 12 | from .utils import endpoints 13 | from .utils import local 14 | from .utils import local_manager 15 | from .utils import url_map 16 | 17 | #: path to shared data 18 | SHARED_DATA = path.join(path.dirname(__file__), "shared") 19 | 20 | 21 | class Plnt: 22 | def __init__(self, database_uri): 23 | self.database_engine = create_engine(database_uri) 24 | 25 | self._dispatch = local_manager.middleware(self.dispatch_request) 26 | self._dispatch = SharedDataMiddleware(self._dispatch, {"/shared": SHARED_DATA}) 27 | 28 | def init_database(self): 29 | metadata.create_all(self.database_engine) 30 | 31 | def bind_to_context(self): 32 | local.application = self 33 | 34 | def dispatch_request(self, environ, start_response): 35 | self.bind_to_context() 36 | local.request = request = Request(environ, start_response) 37 | local.url_adapter = adapter = url_map.bind_to_environ(environ) 38 | try: 39 | endpoint, values = adapter.match(request.path) 40 | response = endpoints[endpoint](request, **values) 41 | except HTTPException as e: 42 | response = e 43 | return ClosingIterator(response(environ, start_response), session.remove) 44 | 45 | def __call__(self, environ, start_response): 46 | return self._dispatch(environ, start_response) 47 | -------------------------------------------------------------------------------- /examples/shortly/static/style.css: -------------------------------------------------------------------------------- 1 | body { background: #E8EFF0; margin: 0; padding: 0; } 2 | body, input { font-family: 'Helvetica Neue', Arial, 3 | sans-serif; font-weight: 300; font-size: 18px; } 4 | .box { width: 500px; margin: 60px auto; padding: 20px; 5 | background: white; box-shadow: 0 1px 4px #BED1D4; 6 | border-radius: 2px; } 7 | a { color: #11557C; } 8 | h1, h2 { margin: 0; color: #11557C; } 9 | h1 a { text-decoration: none; } 10 | h2 { font-weight: normal; font-size: 24px; } 11 | .tagline { color: #888; font-style: italic; margin: 0 0 20px 0; } 12 | .link div { overflow: auto; font-size: 0.8em; white-space: pre; 13 | padding: 4px 10px; margin: 5px 0; background: #E5EAF1; } 14 | dt { font-weight: normal; } 15 | .error { background: #E8EFF0; padding: 3px 8px; color: #11557C; 16 | font-size: 0.9em; border-radius: 2px; } 17 | .urlinput { width: 300px; } 18 | -------------------------------------------------------------------------------- /examples/shortly/templates/404.html: -------------------------------------------------------------------------------- 1 | {% extends "layout.html" %} 2 | {% block title %}Page Not Found{% endblock %} 3 | {% block body %} 4 |

Page Not Found

5 |

I am sorry, but no such page was found here. 6 | {% endblock %} 7 | -------------------------------------------------------------------------------- /examples/shortly/templates/layout.html: -------------------------------------------------------------------------------- 1 | 2 | {% block title %}{% endblock %} | shortly 3 | 4 |

5 |

shortly

6 |

Shortly is a URL shortener written with Werkzeug 7 | {% block body %}{% endblock %} 8 |

9 | -------------------------------------------------------------------------------- /examples/shortly/templates/new_url.html: -------------------------------------------------------------------------------- 1 | {% extends "layout.html" %} 2 | {% block title %}Create New Short URL{% endblock %} 3 | {% block body %} 4 |

Submit URL

5 |
6 | {% if error %} 7 |

Error: {{ error }} 8 | {% endif %} 9 |

URL: 10 | 11 | 12 |

13 | {% endblock %} 14 | -------------------------------------------------------------------------------- /examples/shortly/templates/short_link_details.html: -------------------------------------------------------------------------------- 1 | {% extends "layout.html" %} 2 | {% block title %}Details about /{{ short_id }}{% endblock %} 3 | {% block body %} 4 |

/{{ short_id }}

5 |
6 |
Target host: 7 |
{{ link_target|hostname }} 8 |
Full link 9 |
Click count: 11 |
{{ click_count }} 12 |
13 | {% endblock %} 14 | -------------------------------------------------------------------------------- /examples/shorty/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pallets/werkzeug/99fc293bc7dccd6a7dc96b96012d4aa849794ba4/examples/shorty/__init__.py -------------------------------------------------------------------------------- /examples/shorty/application.py: -------------------------------------------------------------------------------- 1 | from sqlalchemy import create_engine 2 | from werkzeug.exceptions import HTTPException 3 | from werkzeug.exceptions import NotFound 4 | from werkzeug.middleware.shared_data import SharedDataMiddleware 5 | from werkzeug.wrappers import Request 6 | from werkzeug.wsgi import ClosingIterator 7 | 8 | from . import views 9 | from .utils import local 10 | from .utils import local_manager 11 | from .utils import metadata 12 | from .utils import session 13 | from .utils import STATIC_PATH 14 | from .utils import url_map 15 | 16 | 17 | class Shorty: 18 | def __init__(self, db_uri): 19 | local.application = self 20 | self.database_engine = create_engine(db_uri, convert_unicode=True) 21 | 22 | self.dispatch = SharedDataMiddleware(self.dispatch, {"/static": STATIC_PATH}) 23 | 24 | def init_database(self): 25 | metadata.create_all(self.database_engine) 26 | 27 | def dispatch(self, environ, start_response): 28 | local.application = self 29 | request = Request(environ) 30 | local.url_adapter = adapter = url_map.bind_to_environ(environ) 31 | try: 32 | endpoint, values = adapter.match() 33 | handler = getattr(views, endpoint) 34 | response = handler(request, **values) 35 | except NotFound: 36 | response = views.not_found(request) 37 | response.status_code = 404 38 | except HTTPException as e: 39 | response = e 40 | return ClosingIterator( 41 | response(environ, start_response), [session.remove, local_manager.cleanup] 42 | ) 43 | 44 | def __call__(self, environ, start_response): 45 | return self.dispatch(environ, start_response) 46 | -------------------------------------------------------------------------------- /examples/shorty/models.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | 3 | from sqlalchemy import Boolean 4 | from sqlalchemy import Column 5 | from sqlalchemy import DateTime 6 | from sqlalchemy import String 7 | from sqlalchemy import Table 8 | from sqlalchemy.orm import mapper 9 | 10 | from .utils import get_random_uid 11 | from .utils import metadata 12 | from .utils import session 13 | from .utils import url_for 14 | 15 | url_table = Table( 16 | "urls", 17 | metadata, 18 | Column("uid", String(140), primary_key=True), 19 | Column("target", String(500)), 20 | Column("added", DateTime), 21 | Column("public", Boolean), 22 | ) 23 | 24 | 25 | class URL: 26 | query = session.query_property() 27 | 28 | def __init__(self, target, public=True, uid=None, added=None): 29 | self.target = target 30 | self.public = public 31 | self.added = added or datetime.utcnow() 32 | if not uid: 33 | while 1: 34 | uid = get_random_uid() 35 | if not URL.query.get(uid): 36 | break 37 | self.uid = uid 38 | session.add(self) 39 | 40 | @property 41 | def short_url(self): 42 | return url_for("link", uid=self.uid, _external=True) 43 | 44 | def __repr__(self): 45 | return f"" 46 | 47 | 48 | mapper(URL, url_table) 49 | -------------------------------------------------------------------------------- /examples/shorty/static/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | background-color: #333; 3 | font-family: 'Lucida Sans', 'Verdana', sans-serif; 4 | font-size: 16px; 5 | margin: 3em 0 3em 0; 6 | padding: 0; 7 | text-align: center; 8 | } 9 | 10 | a { 11 | color: #0C4850; 12 | } 13 | 14 | a:hover { 15 | color: #1C818F; 16 | } 17 | 18 | h1 { 19 | width: 500px; 20 | background-color: #24C0CE; 21 | text-align: center; 22 | font-size: 3em; 23 | margin: 0 auto 0 auto; 24 | padding: 0; 25 | } 26 | 27 | h1 a { 28 | display: block; 29 | padding: 0.3em; 30 | color: #fff; 31 | text-decoration: none; 32 | } 33 | 34 | h1 a:hover { 35 | color: #ADEEF7; 36 | background-color: #0E8A96; 37 | } 38 | 39 | div.footer { 40 | margin: 0 auto 0 auto; 41 | font-size: 13px; 42 | text-align: right; 43 | padding: 10px; 44 | width: 480px; 45 | background-color: #004C63; 46 | color: white; 47 | } 48 | 49 | div.footer a { 50 | color: #A0E9FF; 51 | } 52 | 53 | div.body { 54 | margin: 0 auto 0 auto; 55 | padding: 20px; 56 | width: 460px; 57 | background-color: #98CE24; 58 | color: black; 59 | } 60 | 61 | div.body h2 { 62 | margin: 0 0 0.5em 0; 63 | text-align: center; 64 | } 65 | 66 | div.body input { 67 | margin: 0.2em 0 0.2em 0; 68 | font-family: 'Lucida Sans', 'Verdana', sans-serif; 69 | font-size: 20px; 70 | background-color: #CCEB98; 71 | color: black; 72 | } 73 | 74 | div.body #url { 75 | width: 400px; 76 | } 77 | 78 | div.body #alias { 79 | width: 300px; 80 | margin-right: 10px; 81 | } 82 | 83 | div.body #submit { 84 | width: 90px; 85 | } 86 | 87 | div.body p { 88 | margin: 0; 89 | padding: 0.2em 0 0.2em 0; 90 | } 91 | 92 | div.body ul { 93 | margin: 1em 0 1em 0; 94 | padding: 0; 95 | list-style: none; 96 | } 97 | 98 | div.error { 99 | margin: 1em 0 1em 0; 100 | border: 2px solid #AC0202; 101 | background-color: #9E0303; 102 | font-weight: bold; 103 | color: white; 104 | } 105 | 106 | div.pagination { 107 | font-size: 13px; 108 | } 109 | -------------------------------------------------------------------------------- /examples/shorty/templates/display.html: -------------------------------------------------------------------------------- 1 | {% extends 'layout.html' %} 2 | {% block body %} 3 |

Shortened URL

4 |

5 | The URL {{ url.target|urlize(40, true) }} 6 | was shortened to {{ url.short_url|urlize }}. 7 |

8 | {% endblock %} 9 | -------------------------------------------------------------------------------- /examples/shorty/templates/layout.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Shorty 5 | 6 | 7 | 8 |

Shorty

9 |
{% block body %}{% endblock %}
10 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /examples/shorty/templates/list.html: -------------------------------------------------------------------------------- 1 | {% extends 'layout.html' %} 2 | {% block body %} 3 |

List of URLs

4 |
    5 | {%- for url in pagination.entries %} 6 |
  • {{ url.uid|e }} » 7 | {{ url.target|urlize(38, true) }}
  • 8 | {%- else %} 9 |
  • no URLs shortened yet
  • 10 | {%- endfor %} 11 |
12 | 19 | {% endblock %} 20 | -------------------------------------------------------------------------------- /examples/shorty/templates/new.html: -------------------------------------------------------------------------------- 1 | {% extends 'layout.html' %} 2 | {% block body %} 3 |

Create a Shorty-URL!

4 | {% if error %}
{{ error }}
{% endif %} 5 |
6 |

Enter the URL you want to shorten

7 |

8 |

Optionally you can give the URL a memorable name

9 |

{# 10 | #}

11 |

12 |

13 |
14 | {% endblock %} 15 | -------------------------------------------------------------------------------- /examples/shorty/templates/not_found.html: -------------------------------------------------------------------------------- 1 | {% extends 'layout.html' %} 2 | {% block body %} 3 |

Page Not Found

4 |

5 | The page you have requested does not exist on this server. What about 6 | adding a new URL? 7 |

8 | {% endblock %} 9 | -------------------------------------------------------------------------------- /examples/shorty/utils.py: -------------------------------------------------------------------------------- 1 | from os import path 2 | from random import randrange 3 | from random import sample 4 | from urllib.parse import urlsplit 5 | 6 | from jinja2 import Environment 7 | from jinja2 import FileSystemLoader 8 | from sqlalchemy import MetaData 9 | from sqlalchemy.orm import create_session 10 | from sqlalchemy.orm import scoped_session 11 | from werkzeug.local import Local 12 | from werkzeug.local import LocalManager 13 | from werkzeug.routing import Map 14 | from werkzeug.routing import Rule 15 | from werkzeug.utils import cached_property 16 | from werkzeug.wrappers import Response 17 | 18 | 19 | TEMPLATE_PATH = path.join(path.dirname(__file__), "templates") 20 | STATIC_PATH = path.join(path.dirname(__file__), "static") 21 | ALLOWED_SCHEMES = frozenset(["http", "https", "ftp", "ftps"]) 22 | URL_CHARS = "abcdefghijkmpqrstuvwxyzABCDEFGHIJKLMNPQRST23456789" 23 | 24 | local = Local() 25 | local_manager = LocalManager([local]) 26 | application = local("application") 27 | 28 | metadata = MetaData() 29 | url_map = Map([Rule("/static/", endpoint="static", build_only=True)]) 30 | 31 | session = scoped_session( 32 | lambda: create_session( 33 | application.database_engine, autocommit=False, autoflush=False 34 | ) 35 | ) 36 | jinja_env = Environment(loader=FileSystemLoader(TEMPLATE_PATH)) 37 | 38 | 39 | def expose(rule, **kw): 40 | def decorate(f): 41 | kw["endpoint"] = f.__name__ 42 | url_map.add(Rule(rule, **kw)) 43 | return f 44 | 45 | return decorate 46 | 47 | 48 | def url_for(endpoint, _external=False, **values): 49 | return local.url_adapter.build(endpoint, values, force_external=_external) 50 | 51 | 52 | jinja_env.globals["url_for"] = url_for 53 | 54 | 55 | def render_template(template, **context): 56 | return Response( 57 | jinja_env.get_template(template).render(**context), mimetype="text/html" 58 | ) 59 | 60 | 61 | def validate_url(url): 62 | return urlsplit(url)[0] in ALLOWED_SCHEMES 63 | 64 | 65 | def get_random_uid(): 66 | return "".join(sample(URL_CHARS, randrange(3, 9))) 67 | 68 | 69 | class Pagination: 70 | def __init__(self, query, per_page, page, endpoint): 71 | self.query = query 72 | self.per_page = per_page 73 | self.page = page 74 | self.endpoint = endpoint 75 | 76 | @cached_property 77 | def count(self): 78 | return self.query.count() 79 | 80 | @cached_property 81 | def entries(self): 82 | return ( 83 | self.query.offset((self.page - 1) * self.per_page) 84 | .limit(self.per_page) 85 | .all() 86 | ) 87 | 88 | @property 89 | def has_previous(self): 90 | """Return True if there are pages before the current one.""" 91 | return self.page > 1 92 | 93 | @property 94 | def has_next(self): 95 | """Return True if there are pages after the current one.""" 96 | return self.page < self.pages 97 | 98 | @property 99 | def previous(self): 100 | """Return the URL for the previous page.""" 101 | return url_for(self.endpoint, page=self.page - 1) 102 | 103 | @property 104 | def next(self): 105 | """Return the URL for the next page.""" 106 | return url_for(self.endpoint, page=self.page + 1) 107 | 108 | @property 109 | def pages(self): 110 | """Return the number of pages.""" 111 | return max(0, self.count - 1) // self.per_page + 1 112 | -------------------------------------------------------------------------------- /examples/shorty/views.py: -------------------------------------------------------------------------------- 1 | from werkzeug.exceptions import NotFound 2 | from werkzeug.utils import redirect 3 | 4 | from .models import URL 5 | from .utils import expose 6 | from .utils import Pagination 7 | from .utils import render_template 8 | from .utils import session 9 | from .utils import url_for 10 | from .utils import validate_url 11 | 12 | 13 | @expose("/") 14 | def new(request): 15 | error = url = "" 16 | if request.method == "POST": 17 | url = request.form.get("url") 18 | alias = request.form.get("alias") 19 | if not validate_url(url): 20 | error = "I'm sorry but you cannot shorten this URL." 21 | elif alias: 22 | if len(alias) > 140: 23 | error = "Your alias is too long" 24 | elif "/" in alias: 25 | error = "Your alias might not include a slash" 26 | elif URL.query.get(alias): 27 | error = "The alias you have requested exists already" 28 | if not error: 29 | uid = URL(url, "private" not in request.form, alias).uid 30 | session.commit() 31 | return redirect(url_for("display", uid=uid)) 32 | return render_template("new.html", error=error, url=url) 33 | 34 | 35 | @expose("/display/") 36 | def display(request, uid): 37 | url = URL.query.get(uid) 38 | if not url: 39 | raise NotFound() 40 | return render_template("display.html", url=url) 41 | 42 | 43 | @expose("/u/") 44 | def link(request, uid): 45 | url = URL.query.get(uid) 46 | if not url: 47 | raise NotFound() 48 | return redirect(url.target, 301) 49 | 50 | 51 | @expose("/list/", defaults={"page": 1}) 52 | @expose("/list/") 53 | def list(request, page): 54 | query = URL.query.filter_by(public=True) 55 | pagination = Pagination(query, 30, page, "list") 56 | if pagination.page > 1 and not pagination.entries: 57 | raise NotFound() 58 | return render_template("list.html", pagination=pagination) 59 | 60 | 61 | def not_found(request): 62 | return render_template("not_found.html") 63 | -------------------------------------------------------------------------------- /examples/simplewiki/__init__.py: -------------------------------------------------------------------------------- 1 | """Very simple wiki application based on Genshi, Werkzeug and 2 | SQLAlchemy. Additionally the creoleparser is used for the wiki markup. 3 | """ 4 | from .application import SimpleWiki 5 | -------------------------------------------------------------------------------- /examples/simplewiki/specialpages.py: -------------------------------------------------------------------------------- 1 | """Special pages such as the recent changes page.""" 2 | from .actions import page_missing 3 | from .database import Page 4 | from .database import RevisionedPage 5 | from .utils import generate_template 6 | from .utils import Pagination 7 | from .utils import Response 8 | 9 | 10 | def page_index(request): 11 | """Index of all pages.""" 12 | letters = {} 13 | for page in Page.query.order_by(Page.name): 14 | letters.setdefault(page.name.capitalize()[0], []).append(page) 15 | return Response( 16 | generate_template("page_index.html", letters=sorted(letters.items())) 17 | ) 18 | 19 | 20 | def recent_changes(request): 21 | """Display the recent changes.""" 22 | page = max(1, request.args.get("page", type=int)) 23 | query = RevisionedPage.query.order_by(RevisionedPage.revision_id.desc()) 24 | return Response( 25 | generate_template( 26 | "recent_changes.html", 27 | pagination=Pagination(query, 20, page, "Special:Recent_Changes"), 28 | ) 29 | ) 30 | 31 | 32 | def page_not_found(request, page_name): 33 | """ 34 | Displays an error message if a user tried to access 35 | a not existing special page. 36 | """ 37 | return page_missing(request, page_name, True) 38 | 39 | 40 | pages = {"Index": page_index, "Recent_Changes": recent_changes} 41 | -------------------------------------------------------------------------------- /examples/simplewiki/templates/action_diff.html: -------------------------------------------------------------------------------- 1 | 4 | 6 | 7 | View Diff 8 | 9 | 10 | 11 |

Diff for “${page.title}

12 |

13 | Below you can see the differences between the revision from 14 | ${format_datetime(old_revision.timestamp)} and the 16 | revision from ${format_datetime(new_revision.timestamp)} in unified 18 | diff format. 19 |

20 |
${diff}
21 |
22 | 23 |

Cannot Display Diff

24 |

${error}

25 |
26 | 27 | 28 | -------------------------------------------------------------------------------- /examples/simplewiki/templates/action_edit.html: -------------------------------------------------------------------------------- 1 | 5 | 7 | 8 | ${'Create' if new else 'Edit'} Page 9 | 10 | 11 |

${'Create' if new else 'Edit'} “${page.title or page_name}”

12 |

13 | You can now ${'create' if new else 'modify'} the page contents. To 14 | format your text you can use creole markup. 15 |

16 |

${error}

17 |
18 |

19 |
20 | 21 | 22 | 23 |
24 |
25 | 26 | 27 | -------------------------------------------------------------------------------- /examples/simplewiki/templates/action_log.html: -------------------------------------------------------------------------------- 1 | 4 | 6 | 7 | Revisions for “${page.title}” 8 | 9 | 10 |

Revisions for “${page.title}

11 |

12 | In this list you can see all the revisions of the requested page. 13 |

14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 25 | 26 | 27 | 33 | 40 | 41 |
DateChange NoteActions
${format_datetime(revision.timestamp)}${revision.change_note} 28 | 30 | 32 | 34 | show 35 | | 36 | revert 38 | 39 |
42 |
43 | 44 | 45 | -------------------------------------------------------------------------------- /examples/simplewiki/templates/action_revert.html: -------------------------------------------------------------------------------- 1 | 4 | 6 | 7 | Revert Old Revision 8 | 9 | 10 | 11 |

Revert Old Revision of “${page.title}

12 |

13 | If you want to restore the old revision from 14 | ${format_datetime(old_revision.timestamp)} enter your change 16 | note and click “Revert”. 17 |

18 |
19 |
20 | 21 | 22 | 23 |
24 |
25 |
26 | 27 |

Cannot Revert

28 |

${error}

29 |
30 | 31 | 32 | -------------------------------------------------------------------------------- /examples/simplewiki/templates/action_show.html: -------------------------------------------------------------------------------- 1 | 4 | 6 | 7 | ${page.title} 8 | 9 | 10 | ${page.render()} 11 | 12 | 13 | -------------------------------------------------------------------------------- /examples/simplewiki/templates/layout.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | <py:if 7 | test="title">${title} — </py:if>SimpleWiki 8 | 9 | ${select('*[local-name()!="title"]')} 10 | 11 | 12 | 13 | 14 |
15 |
16 |

Simple Wiki

17 |
18 |
19 |
20 | This revision 21 | was created on ${format_datetime(page.timestamp)}. 22 |
23 | 38 | ${select('*|text()')} 39 |
40 | 43 |
44 | 45 |
46 | 47 | -------------------------------------------------------------------------------- /examples/simplewiki/templates/macros.xml: -------------------------------------------------------------------------------- 1 |
3 | 4 | 5 | 16 | 17 | 18 |
19 | -------------------------------------------------------------------------------- /examples/simplewiki/templates/missing_action.html: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | Action Not Found 6 | 7 | 8 |

Action “${action}” Not Found

9 |

The requested action does not exist.

10 |

Try to access the same URL without parameters.

11 | 12 | 13 | -------------------------------------------------------------------------------- /examples/simplewiki/templates/page_index.html: -------------------------------------------------------------------------------- 1 | 4 | 6 | 7 | Index 8 | 9 | 10 |

Index

11 | 12 |

${letter}

13 | 16 |
17 | 18 | 19 | -------------------------------------------------------------------------------- /examples/simplewiki/templates/page_missing.html: -------------------------------------------------------------------------------- 1 | 4 | 6 | 7 | Page Not Found 8 | 9 | 10 |

Page Not Found

11 |

The page you requested does not exist.

12 |

13 | It also could be that there is no such revision of that page. 14 |

15 |

16 | Feel free to create such a page. 18 |

19 |

20 | Although this page does not exist by now you cannot create it because 21 | the system protected the page name for future use. 22 |

23 | 24 | 25 | -------------------------------------------------------------------------------- /examples/simplewiki/templates/recent_changes.html: -------------------------------------------------------------------------------- 1 | 4 | 6 | 7 | Recent Changes 8 | 9 | 10 |

Recent Changes

11 | 12 | 13 | 14 | 15 | 16 | 17 | 19 | 20 | 21 | 22 | 23 |
DatePageChange Note
${format_datetime(entry.timestamp)}${entry.title}${entry.change_note}
24 | ${render_pagination(pagination)} 25 | 26 | 27 | -------------------------------------------------------------------------------- /examples/upload.py: -------------------------------------------------------------------------------- 1 | """All uploaded files are directly send back to the client.""" 2 | from werkzeug.serving import run_simple 3 | from werkzeug.wrappers import Request 4 | from werkzeug.wrappers import Response 5 | from werkzeug.wsgi import wrap_file 6 | 7 | 8 | def view_file(req): 9 | if "uploaded_file" not in req.files: 10 | return Response("no file uploaded") 11 | f = req.files["uploaded_file"] 12 | return Response( 13 | wrap_file(req.environ, f), mimetype=f.content_type, direct_passthrough=True 14 | ) 15 | 16 | 17 | def upload_file(req): 18 | return Response( 19 | """

Upload File

20 |
21 | 22 | 23 |
""", 24 | mimetype="text/html", 25 | ) 26 | 27 | 28 | def application(environ, start_response): 29 | req = Request(environ) 30 | if req.method == "POST": 31 | resp = view_file(req) 32 | else: 33 | resp = upload_file(req) 34 | return resp(environ, start_response) 35 | 36 | 37 | if __name__ == "__main__": 38 | run_simple("localhost", 5000, application, use_debugger=True) 39 | -------------------------------------------------------------------------------- /examples/webpylike/example.py: -------------------------------------------------------------------------------- 1 | from .webpylike import Response 2 | from .webpylike import View 3 | from .webpylike import WebPyApp 4 | 5 | 6 | urls = ("/", "index", "/about", "about") 7 | 8 | 9 | class index(View): 10 | def GET(self): 11 | return Response("Hello World") 12 | 13 | 14 | class about(View): 15 | def GET(self): 16 | return Response("This is the about page") 17 | 18 | 19 | app = WebPyApp(urls, globals()) 20 | -------------------------------------------------------------------------------- /examples/webpylike/webpylike.py: -------------------------------------------------------------------------------- 1 | """Implements web.py like dispatching. What this module does not 2 | implement is a stream system that hooks into sys.stdout like web.py 3 | provides. 4 | """ 5 | import re 6 | 7 | from werkzeug.exceptions import HTTPException 8 | from werkzeug.exceptions import MethodNotAllowed 9 | from werkzeug.exceptions import NotFound 10 | from werkzeug.exceptions import NotImplemented 11 | from werkzeug.wrappers import Request 12 | from werkzeug.wrappers import Response # noqa: F401 13 | 14 | 15 | class View: 16 | """Baseclass for our views.""" 17 | 18 | def __init__(self, app, req): 19 | self.app = app 20 | self.req = req 21 | 22 | def GET(self): 23 | raise MethodNotAllowed() 24 | 25 | POST = DELETE = PUT = GET 26 | 27 | def HEAD(self): 28 | return self.GET() 29 | 30 | 31 | class WebPyApp: 32 | """ 33 | An interface to a web.py like application. It works like the web.run 34 | function in web.py 35 | """ 36 | 37 | def __init__(self, urls, views): 38 | self.urls = [ 39 | (re.compile(f"^{urls[i]}$"), urls[i + 1]) for i in range(0, len(urls), 2) 40 | ] 41 | self.views = views 42 | 43 | def __call__(self, environ, start_response): 44 | try: 45 | req = Request(environ) 46 | for regex, view in self.urls: 47 | match = regex.match(req.path) 48 | if match is not None: 49 | view = self.views[view](self, req) 50 | if req.method not in ("GET", "HEAD", "POST", "DELETE", "PUT"): 51 | raise NotImplemented() # noqa: F901 52 | resp = getattr(view, req.method)(*match.groups()) 53 | break 54 | else: 55 | raise NotFound() 56 | except HTTPException as e: 57 | resp = e 58 | return resp(environ, start_response) 59 | -------------------------------------------------------------------------------- /examples/wsecho.py: -------------------------------------------------------------------------------- 1 | """Shows how you can implement a simple WebSocket echo server using the 2 | wsproto library. 3 | """ 4 | from werkzeug.exceptions import InternalServerError 5 | from werkzeug.serving import run_simple 6 | from werkzeug.wrappers import Request 7 | from werkzeug.wrappers import Response 8 | from wsproto import ConnectionType 9 | from wsproto import WSConnection 10 | from wsproto.events import AcceptConnection 11 | from wsproto.events import CloseConnection 12 | from wsproto.events import Message 13 | from wsproto.events import Ping 14 | from wsproto.events import Request as WSRequest 15 | from wsproto.events import TextMessage 16 | from wsproto.frame_protocol import CloseReason 17 | 18 | 19 | @Request.application 20 | def websocket(request): 21 | # The underlying socket must be provided by the server. Gunicorn and 22 | # Werkzeug's dev server are known to support this. 23 | stream = request.environ.get("werkzeug.socket") 24 | 25 | if stream is None: 26 | stream = request.environ.get("gunicorn.socket") 27 | 28 | if stream is None: 29 | raise InternalServerError() 30 | 31 | # Initialize the wsproto connection. Need to recreate the request 32 | # data that was read by the WSGI server already. 33 | ws = WSConnection(ConnectionType.SERVER) 34 | in_data = b"GET %s HTTP/1.1\r\n" % request.path.encode("utf8") 35 | 36 | for header, value in request.headers.items(): 37 | in_data += f"{header}: {value}\r\n".encode() 38 | 39 | in_data += b"\r\n" 40 | ws.receive_data(in_data) 41 | running = True 42 | 43 | while True: 44 | out_data = b"" 45 | 46 | for event in ws.events(): 47 | if isinstance(event, WSRequest): 48 | out_data += ws.send(AcceptConnection()) 49 | elif isinstance(event, CloseConnection): 50 | out_data += ws.send(event.response()) 51 | running = False 52 | elif isinstance(event, Ping): 53 | out_data += ws.send(event.response()) 54 | elif isinstance(event, TextMessage): 55 | # echo the incoming message back to the client 56 | if event.data == "quit": 57 | out_data += ws.send( 58 | CloseConnection(CloseReason.NORMAL_CLOSURE, "bye") 59 | ) 60 | running = False 61 | else: 62 | out_data += ws.send(Message(data=event.data)) 63 | 64 | if out_data: 65 | stream.send(out_data) 66 | 67 | if not running: 68 | break 69 | 70 | in_data = stream.recv(4096) 71 | ws.receive_data(in_data) 72 | 73 | # The connection will be closed at this point, but WSGI still 74 | # requires a response. 75 | return Response("", status=204) 76 | 77 | 78 | if __name__ == "__main__": 79 | run_simple("localhost", 5000, websocket) 80 | -------------------------------------------------------------------------------- /src/werkzeug/__init__.py: -------------------------------------------------------------------------------- 1 | from .serving import run_simple as run_simple 2 | from .test import Client as Client 3 | from .wrappers import Request as Request 4 | from .wrappers import Response as Response 5 | -------------------------------------------------------------------------------- /src/werkzeug/datastructures/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import typing as t 4 | 5 | from .accept import Accept as Accept 6 | from .accept import CharsetAccept as CharsetAccept 7 | from .accept import LanguageAccept as LanguageAccept 8 | from .accept import MIMEAccept as MIMEAccept 9 | from .auth import Authorization as Authorization 10 | from .auth import WWWAuthenticate as WWWAuthenticate 11 | from .cache_control import RequestCacheControl as RequestCacheControl 12 | from .cache_control import ResponseCacheControl as ResponseCacheControl 13 | from .csp import ContentSecurityPolicy as ContentSecurityPolicy 14 | from .etag import ETags as ETags 15 | from .file_storage import FileMultiDict as FileMultiDict 16 | from .file_storage import FileStorage as FileStorage 17 | from .headers import EnvironHeaders as EnvironHeaders 18 | from .headers import Headers as Headers 19 | from .mixins import ImmutableDictMixin as ImmutableDictMixin 20 | from .mixins import ImmutableHeadersMixin as ImmutableHeadersMixin 21 | from .mixins import ImmutableListMixin as ImmutableListMixin 22 | from .mixins import ImmutableMultiDictMixin as ImmutableMultiDictMixin 23 | from .mixins import UpdateDictMixin as UpdateDictMixin 24 | from .range import ContentRange as ContentRange 25 | from .range import IfRange as IfRange 26 | from .range import Range as Range 27 | from .structures import CallbackDict as CallbackDict 28 | from .structures import CombinedMultiDict as CombinedMultiDict 29 | from .structures import HeaderSet as HeaderSet 30 | from .structures import ImmutableDict as ImmutableDict 31 | from .structures import ImmutableList as ImmutableList 32 | from .structures import ImmutableMultiDict as ImmutableMultiDict 33 | from .structures import ImmutableTypeConversionDict as ImmutableTypeConversionDict 34 | from .structures import iter_multi_items as iter_multi_items 35 | from .structures import MultiDict as MultiDict 36 | from .structures import TypeConversionDict as TypeConversionDict 37 | 38 | 39 | def __getattr__(name: str) -> t.Any: 40 | import warnings 41 | 42 | if name == "OrderedMultiDict": 43 | from .structures import _OrderedMultiDict 44 | 45 | warnings.warn( 46 | "'OrderedMultiDict' is deprecated and will be removed in Werkzeug" 47 | " 3.2. Use 'MultiDict' instead.", 48 | DeprecationWarning, 49 | stacklevel=2, 50 | ) 51 | return _OrderedMultiDict 52 | 53 | if name == "ImmutableOrderedMultiDict": 54 | from .structures import _ImmutableOrderedMultiDict 55 | 56 | warnings.warn( 57 | "'OrderedMultiDict' is deprecated and will be removed in Werkzeug" 58 | " 3.2. Use 'ImmutableMultiDict' instead.", 59 | DeprecationWarning, 60 | stacklevel=2, 61 | ) 62 | return _ImmutableOrderedMultiDict 63 | 64 | raise AttributeError(name) 65 | -------------------------------------------------------------------------------- /src/werkzeug/debug/shared/ICON_LICENSE.md: -------------------------------------------------------------------------------- 1 | Silk icon set 1.3 by Mark James 2 | 3 | http://www.famfamfam.com/lab/icons/silk/ 4 | 5 | License: [CC-BY-2.5](https://creativecommons.org/licenses/by/2.5/) 6 | or [CC-BY-3.0](https://creativecommons.org/licenses/by/3.0/) 7 | -------------------------------------------------------------------------------- /src/werkzeug/debug/shared/console.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pallets/werkzeug/99fc293bc7dccd6a7dc96b96012d4aa849794ba4/src/werkzeug/debug/shared/console.png -------------------------------------------------------------------------------- /src/werkzeug/debug/shared/less.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pallets/werkzeug/99fc293bc7dccd6a7dc96b96012d4aa849794ba4/src/werkzeug/debug/shared/less.png -------------------------------------------------------------------------------- /src/werkzeug/debug/shared/more.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pallets/werkzeug/99fc293bc7dccd6a7dc96b96012d4aa849794ba4/src/werkzeug/debug/shared/more.png -------------------------------------------------------------------------------- /src/werkzeug/middleware/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pallets/werkzeug/99fc293bc7dccd6a7dc96b96012d4aa849794ba4/src/werkzeug/middleware/__init__.py -------------------------------------------------------------------------------- /src/werkzeug/middleware/dispatcher.py: -------------------------------------------------------------------------------- 1 | """ 2 | Application Dispatcher 3 | ====================== 4 | 5 | This middleware creates a single WSGI application that dispatches to 6 | multiple other WSGI applications mounted at different URL paths. 7 | 8 | A common example is writing a Single Page Application, where you have a 9 | backend API and a frontend written in JavaScript that does the routing 10 | in the browser rather than requesting different pages from the server. 11 | The frontend is a single HTML and JS file that should be served for any 12 | path besides "/api". 13 | 14 | This example dispatches to an API app under "/api", an admin app 15 | under "/admin", and an app that serves frontend files for all other 16 | requests:: 17 | 18 | app = DispatcherMiddleware(serve_frontend, { 19 | '/api': api_app, 20 | '/admin': admin_app, 21 | }) 22 | 23 | In production, you might instead handle this at the HTTP server level, 24 | serving files or proxying to application servers based on location. The 25 | API and admin apps would each be deployed with a separate WSGI server, 26 | and the static files would be served directly by the HTTP server. 27 | 28 | .. autoclass:: DispatcherMiddleware 29 | 30 | :copyright: 2007 Pallets 31 | :license: BSD-3-Clause 32 | """ 33 | 34 | from __future__ import annotations 35 | 36 | import typing as t 37 | 38 | if t.TYPE_CHECKING: 39 | from _typeshed.wsgi import StartResponse 40 | from _typeshed.wsgi import WSGIApplication 41 | from _typeshed.wsgi import WSGIEnvironment 42 | 43 | 44 | class DispatcherMiddleware: 45 | """Combine multiple applications as a single WSGI application. 46 | Requests are dispatched to an application based on the path it is 47 | mounted under. 48 | 49 | :param app: The WSGI application to dispatch to if the request 50 | doesn't match a mounted path. 51 | :param mounts: Maps path prefixes to applications for dispatching. 52 | """ 53 | 54 | def __init__( 55 | self, 56 | app: WSGIApplication, 57 | mounts: dict[str, WSGIApplication] | None = None, 58 | ) -> None: 59 | self.app = app 60 | self.mounts = mounts or {} 61 | 62 | def __call__( 63 | self, environ: WSGIEnvironment, start_response: StartResponse 64 | ) -> t.Iterable[bytes]: 65 | script = environ.get("PATH_INFO", "") 66 | path_info = "" 67 | 68 | while "/" in script: 69 | if script in self.mounts: 70 | app = self.mounts[script] 71 | break 72 | 73 | script, last_item = script.rsplit("/", 1) 74 | path_info = f"/{last_item}{path_info}" 75 | else: 76 | app = self.mounts.get(script, self.app) 77 | 78 | original_script_name = environ.get("SCRIPT_NAME", "") 79 | environ["SCRIPT_NAME"] = original_script_name + script 80 | environ["PATH_INFO"] = path_info 81 | return app(environ, start_response) 82 | -------------------------------------------------------------------------------- /src/werkzeug/py.typed: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pallets/werkzeug/99fc293bc7dccd6a7dc96b96012d4aa849794ba4/src/werkzeug/py.typed -------------------------------------------------------------------------------- /src/werkzeug/sansio/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pallets/werkzeug/99fc293bc7dccd6a7dc96b96012d4aa849794ba4/src/werkzeug/sansio/__init__.py -------------------------------------------------------------------------------- /src/werkzeug/user_agent.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | 4 | class UserAgent: 5 | """Represents a parsed user agent header value. 6 | 7 | The default implementation does no parsing, only the :attr:`string` 8 | attribute is set. A subclass may parse the string to set the 9 | common attributes or expose other information. Set 10 | :attr:`werkzeug.wrappers.Request.user_agent_class` to use a 11 | subclass. 12 | 13 | :param string: The header value to parse. 14 | 15 | .. versionadded:: 2.0 16 | This replaces the previous ``useragents`` module, but does not 17 | provide a built-in parser. 18 | """ 19 | 20 | platform: str | None = None 21 | """The OS name, if it could be parsed from the string.""" 22 | 23 | browser: str | None = None 24 | """The browser name, if it could be parsed from the string.""" 25 | 26 | version: str | None = None 27 | """The browser version, if it could be parsed from the string.""" 28 | 29 | language: str | None = None 30 | """The browser language, if it could be parsed from the string.""" 31 | 32 | def __init__(self, string: str) -> None: 33 | self.string: str = string 34 | """The original header value.""" 35 | 36 | def __repr__(self) -> str: 37 | return f"<{type(self).__name__} {self.browser}/{self.version}>" 38 | 39 | def __str__(self) -> str: 40 | return self.string 41 | 42 | def __bool__(self) -> bool: 43 | return bool(self.browser) 44 | 45 | def to_header(self) -> str: 46 | """Convert to a header value.""" 47 | return self.string 48 | -------------------------------------------------------------------------------- /src/werkzeug/wrappers/__init__.py: -------------------------------------------------------------------------------- 1 | from .request import Request as Request 2 | from .response import Response as Response 3 | from .response import ResponseStream as ResponseStream 4 | -------------------------------------------------------------------------------- /tests/live_apps/data_app.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | from werkzeug.wrappers import Request 4 | from werkzeug.wrappers import Response 5 | 6 | 7 | @Request.application 8 | def app(request: Request) -> Response: 9 | return Response( 10 | json.dumps( 11 | { 12 | "environ": request.environ, 13 | "form": request.form.to_dict(), 14 | "files": {k: v.read().decode() for k, v in request.files.items()}, 15 | }, 16 | default=lambda x: str(x), 17 | ), 18 | content_type="application/json", 19 | ) 20 | -------------------------------------------------------------------------------- /tests/live_apps/reloader_app.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | 4 | from werkzeug import _reloader 5 | from werkzeug.wrappers import Request 6 | from werkzeug.wrappers import Response 7 | 8 | # Tox puts the tmp dir in the venv sys.prefix, patch the reloader so 9 | # it doesn't skip real_app. 10 | if "TOX_ENV_DIR" in os.environ: 11 | _reloader._stat_ignore_scan = tuple( 12 | set(_reloader._stat_ignore_scan) - {sys.prefix, sys.exec_prefix} 13 | ) 14 | 15 | 16 | @Request.application 17 | def app(request): 18 | import real_app # type: ignore 19 | 20 | return Response.from_app(real_app.app, request.environ) 21 | 22 | 23 | kwargs = {"use_reloader": True, "reloader_interval": 0.1} 24 | -------------------------------------------------------------------------------- /tests/live_apps/run.py: -------------------------------------------------------------------------------- 1 | import json 2 | import sys 3 | from importlib import import_module 4 | 5 | from werkzeug.serving import generate_adhoc_ssl_context 6 | from werkzeug.serving import run_simple 7 | from werkzeug.serving import WSGIRequestHandler 8 | from werkzeug.wrappers import Request 9 | from werkzeug.wrappers import Response 10 | 11 | name = sys.argv[1] 12 | mod = import_module(f"{name}_app") 13 | 14 | 15 | @Request.application 16 | def app(request): 17 | if request.path == "/ensure": 18 | return Response() 19 | 20 | return Response.from_app(mod.app, request.environ) 21 | 22 | 23 | kwargs = getattr(mod, "kwargs", {}) 24 | kwargs.update(hostname="127.0.0.1", port=5000, application=app) 25 | kwargs.update(json.loads(sys.argv[2])) 26 | ssl_context = kwargs.get("ssl_context") 27 | override_client_addr = kwargs.pop("override_client_addr", None) 28 | 29 | if ssl_context == "custom": 30 | kwargs["ssl_context"] = generate_adhoc_ssl_context() 31 | elif isinstance(ssl_context, list): 32 | kwargs["ssl_context"] = tuple(ssl_context) 33 | 34 | if override_client_addr: 35 | WSGIRequestHandler.address_string = lambda _: override_client_addr 36 | 37 | run_simple(**kwargs) 38 | -------------------------------------------------------------------------------- /tests/live_apps/standard_app.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | from werkzeug.wrappers import Request 4 | from werkzeug.wrappers import Response 5 | 6 | 7 | @Request.application 8 | def app(request): 9 | if request.path == "/crash": 10 | raise Exception("crash requested") 11 | 12 | return Response( 13 | json.dumps(request.environ, default=lambda x: str(x)), 14 | content_type="application/json", 15 | ) 16 | -------------------------------------------------------------------------------- /tests/live_apps/streaming_app.py: -------------------------------------------------------------------------------- 1 | from werkzeug.wrappers import Request 2 | from werkzeug.wrappers import Response 3 | 4 | 5 | @Request.application 6 | def app(request): 7 | def gen(): 8 | for x in range(5): 9 | yield f"{x}\n" 10 | 11 | if request.path == "/crash": 12 | raise Exception("crash requested") 13 | 14 | return Response(gen()) 15 | -------------------------------------------------------------------------------- /tests/middleware/test_dispatcher.py: -------------------------------------------------------------------------------- 1 | from werkzeug.middleware.dispatcher import DispatcherMiddleware 2 | from werkzeug.test import create_environ 3 | from werkzeug.test import run_wsgi_app 4 | 5 | 6 | def test_dispatcher(): 7 | def null_application(environ, start_response): 8 | start_response("404 NOT FOUND", [("Content-Type", "text/plain")]) 9 | yield b"NOT FOUND" 10 | 11 | def dummy_application(environ, start_response): 12 | start_response("200 OK", [("Content-Type", "text/plain")]) 13 | yield environ["SCRIPT_NAME"].encode() 14 | 15 | app = DispatcherMiddleware( 16 | null_application, 17 | {"/test1": dummy_application, "/test2/very": dummy_application}, 18 | ) 19 | tests = { 20 | "/test1": ("/test1", "/test1/asfd", "/test1/very"), 21 | "/test2/very": ("/test2/very", "/test2/very/long/path/after/script/name"), 22 | } 23 | 24 | for name, urls in tests.items(): 25 | for p in urls: 26 | environ = create_environ(p) 27 | app_iter, status, headers = run_wsgi_app(app, environ) 28 | assert status == "200 OK" 29 | assert b"".join(app_iter).strip() == name.encode() 30 | 31 | app_iter, status, headers = run_wsgi_app(app, create_environ("/missing")) 32 | assert status == "404 NOT FOUND" 33 | assert b"".join(app_iter).strip() == b"NOT FOUND" 34 | -------------------------------------------------------------------------------- /tests/middleware/test_http_proxy.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from werkzeug.middleware.http_proxy import ProxyMiddleware 4 | from werkzeug.test import Client 5 | from werkzeug.wrappers import Response 6 | 7 | 8 | @pytest.mark.dev_server 9 | def test_http_proxy(standard_app): 10 | app = ProxyMiddleware( 11 | Response("ROOT"), 12 | { 13 | "/foo": { 14 | "target": standard_app.url, 15 | "host": "faked.invalid", 16 | "headers": {"X-Special": "foo"}, 17 | }, 18 | "/bar": { 19 | "target": standard_app.url, 20 | "host": None, 21 | "remove_prefix": True, 22 | "headers": {"X-Special": "bar"}, 23 | }, 24 | "/autohost": {"target": standard_app.url}, 25 | }, 26 | ) 27 | 28 | client = Client(app) 29 | 30 | r = client.get("/") 31 | assert r.data == b"ROOT" 32 | 33 | r = client.get("/foo/bar") 34 | assert r.json["HTTP_X_SPECIAL"] == "foo" 35 | assert r.json["HTTP_HOST"] == "faked.invalid" 36 | assert r.json["PATH_INFO"] == "/foo/bar" 37 | 38 | r = client.get("/bar/baz?a=a&b=b") 39 | assert r.json["HTTP_X_SPECIAL"] == "bar" 40 | assert r.json["HTTP_HOST"] == "localhost" 41 | assert r.json["PATH_INFO"] == "/baz" 42 | assert r.json["QUERY_STRING"] == "a=a&b=b" 43 | 44 | r = client.get("/autohost/aha") 45 | assert "HTTP_X_SPECIAL" not in r.json 46 | assert r.json["HTTP_HOST"] == "127.0.0.1" 47 | assert r.json["PATH_INFO"] == "/autohost/aha" 48 | 49 | # test if characters allowed in URL are not encoded by proxy 50 | r = client.get("/autohost/$") 51 | assert r.json["REQUEST_URI"] == "/autohost/$" 52 | -------------------------------------------------------------------------------- /tests/middleware/test_lint.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from werkzeug.middleware.lint import HTTPWarning 4 | from werkzeug.middleware.lint import LintMiddleware 5 | from werkzeug.middleware.lint import WSGIWarning 6 | from werkzeug.test import create_environ 7 | from werkzeug.test import run_wsgi_app 8 | 9 | 10 | def dummy_application(environ, start_response): 11 | start_response("200 OK", [("Content-Type", "text/plain")]) 12 | return [b"Foo"] 13 | 14 | 15 | def test_lint_middleware(): 16 | """Test lint middleware runs for a dummy applications without warnings""" 17 | app = LintMiddleware(dummy_application) 18 | 19 | environ = create_environ("/test") 20 | app_iter, status, headers = run_wsgi_app(app, environ, buffered=True) 21 | assert status == "200 OK" 22 | 23 | 24 | @pytest.mark.parametrize( 25 | "key, value, message", 26 | [ 27 | ("wsgi.version", (0, 7), "Environ is not a WSGI 1.0 environ."), 28 | ("SCRIPT_NAME", "test", "'SCRIPT_NAME' does not start with a slash:"), 29 | ("PATH_INFO", "test", "'PATH_INFO' does not start with a slash:"), 30 | ], 31 | ) 32 | def test_lint_middleware_check_environ(key, value, message): 33 | app = LintMiddleware(dummy_application) 34 | 35 | environ = create_environ("/test") 36 | environ[key] = value 37 | with pytest.warns(WSGIWarning, match=message): 38 | app_iter, status, headers = run_wsgi_app(app, environ, buffered=True) 39 | assert status == "200 OK" 40 | 41 | 42 | def test_lint_middleware_invalid_status(): 43 | def my_dummy_application(environ, start_response): 44 | start_response("20 OK", [("Content-Type", "text/plain")]) 45 | return [b"Foo"] 46 | 47 | app = LintMiddleware(my_dummy_application) 48 | 49 | environ = create_environ("/test") 50 | with pytest.warns(WSGIWarning) as record: 51 | run_wsgi_app(app, environ, buffered=True) 52 | 53 | # Returning status 20 should raise three different warnings 54 | assert len(record) == 3 55 | 56 | 57 | @pytest.mark.parametrize( 58 | "headers, message", 59 | [ 60 | (tuple([("Content-Type", "text/plain")]), "Header list is not a list."), 61 | (["fo"], "Header items must be 2-item tuples."), 62 | ([("status", "foo")], "The status header is not supported."), 63 | ], 64 | ) 65 | def test_lint_middleware_http_headers(headers, message): 66 | def my_dummy_application(environ, start_response): 67 | start_response("200 OK", headers) 68 | return [b"Foo"] 69 | 70 | app = LintMiddleware(my_dummy_application) 71 | 72 | environ = create_environ("/test") 73 | with pytest.warns(WSGIWarning, match=message): 74 | run_wsgi_app(app, environ, buffered=True) 75 | 76 | 77 | def test_lint_middleware_invalid_location(): 78 | def my_dummy_application(environ, start_response): 79 | start_response("200 OK", [("location", "foo")]) 80 | return [b"Foo"] 81 | 82 | app = LintMiddleware(my_dummy_application) 83 | 84 | environ = create_environ("/test") 85 | with pytest.warns(HTTPWarning, match="Absolute URLs required for location header."): 86 | run_wsgi_app(app, environ, buffered=True) 87 | -------------------------------------------------------------------------------- /tests/middleware/test_profiler.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | import os 3 | from unittest.mock import ANY 4 | from unittest.mock import MagicMock 5 | from unittest.mock import patch 6 | 7 | from werkzeug.middleware.profiler import Profile 8 | from werkzeug.middleware.profiler import ProfilerMiddleware 9 | from werkzeug.test import Client 10 | 11 | 12 | def dummy_application(environ, start_response): 13 | start_response("200 OK", [("Content-Type", "text/plain")]) 14 | return [b"Foo"] 15 | 16 | 17 | def test_filename_format_function(): 18 | # This should be called once with the generated file name 19 | mock_capture_name = MagicMock() 20 | 21 | def filename_format(env): 22 | now = datetime.datetime.fromtimestamp(env["werkzeug.profiler"]["time"]) 23 | timestamp = now.strftime("%Y-%m-%d:%H:%M:%S") 24 | path = ( 25 | "_".join(token for token in env["PATH_INFO"].split("/") if token) or "ROOT" 26 | ) 27 | elapsed = env["werkzeug.profiler"]["elapsed"] 28 | name = f"{timestamp}.{env['REQUEST_METHOD']}.{path}.{elapsed:.0f}ms.prof" 29 | mock_capture_name(name=name) 30 | return name 31 | 32 | client = Client( 33 | ProfilerMiddleware( 34 | dummy_application, 35 | stream=None, 36 | profile_dir="profiles", 37 | filename_format=filename_format, 38 | ) 39 | ) 40 | 41 | # Replace the Profile class with a function that simulates an __init__() 42 | # call and returns our mock instance. 43 | mock_profile = MagicMock(wraps=Profile()) 44 | mock_profile.dump_stats = MagicMock() 45 | with patch("werkzeug.middleware.profiler.Profile", lambda: mock_profile): 46 | client.get("/foo/bar") 47 | 48 | mock_capture_name.assert_called_once_with(name=ANY) 49 | name = mock_capture_name.mock_calls[0].kwargs["name"] 50 | mock_profile.dump_stats.assert_called_once_with(os.path.join("profiles", name)) 51 | -------------------------------------------------------------------------------- /tests/middleware/test_shared_data.py: -------------------------------------------------------------------------------- 1 | import os 2 | from contextlib import closing 3 | 4 | from werkzeug.middleware.shared_data import SharedDataMiddleware 5 | from werkzeug.test import create_environ 6 | from werkzeug.test import run_wsgi_app 7 | 8 | 9 | def test_get_file_loader(): 10 | app = SharedDataMiddleware(None, {}) 11 | assert callable(app.get_file_loader("foo")) 12 | 13 | 14 | def test_shared_data_middleware(tmpdir): 15 | def null_application(environ, start_response): 16 | start_response("404 NOT FOUND", [("Content-Type", "text/plain")]) 17 | yield b"NOT FOUND" 18 | 19 | test_dir = str(tmpdir) 20 | 21 | with open(os.path.join(test_dir, "äöü"), "w") as test_file: 22 | test_file.write("FOUND") 23 | 24 | for t in [list, dict]: 25 | app = SharedDataMiddleware( 26 | null_application, 27 | t( 28 | [ 29 | ("/", os.path.join(os.path.dirname(__file__), "..", "res")), 30 | ("/sources", os.path.join(os.path.dirname(__file__), "..", "res")), 31 | ("/pkg", ("werkzeug.debug", "shared")), 32 | ("/foo", test_dir), 33 | ] 34 | ), 35 | ) 36 | 37 | for p in "/test.txt", "/sources/test.txt", "/foo/äöü": 38 | app_iter, status, headers = run_wsgi_app(app, create_environ(p)) 39 | assert status == "200 OK" 40 | 41 | if p.endswith(".txt"): 42 | content_type = next(v for k, v in headers if k == "Content-Type") 43 | assert content_type == "text/plain; charset=utf-8" 44 | 45 | with closing(app_iter) as app_iter: 46 | data = b"".join(app_iter).strip() 47 | 48 | assert data == b"FOUND" 49 | 50 | app_iter, status, headers = run_wsgi_app( 51 | app, create_environ("/pkg/debugger.js") 52 | ) 53 | 54 | with closing(app_iter) as app_iter: 55 | contents = b"".join(app_iter) 56 | 57 | assert b"docReady(() =>" in contents 58 | 59 | for path in ("/missing", "/pkg", "/pkg/", "/pkg/missing.txt"): 60 | app_iter, status, headers = run_wsgi_app(app, create_environ(path)) 61 | assert status == "404 NOT FOUND" 62 | assert b"".join(app_iter).strip() == b"NOT FOUND" 63 | -------------------------------------------------------------------------------- /tests/multipart/firefox3-2png1txt/file1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pallets/werkzeug/99fc293bc7dccd6a7dc96b96012d4aa849794ba4/tests/multipart/firefox3-2png1txt/file1.png -------------------------------------------------------------------------------- /tests/multipart/firefox3-2png1txt/file2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pallets/werkzeug/99fc293bc7dccd6a7dc96b96012d4aa849794ba4/tests/multipart/firefox3-2png1txt/file2.png -------------------------------------------------------------------------------- /tests/multipart/firefox3-2png1txt/request.http: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pallets/werkzeug/99fc293bc7dccd6a7dc96b96012d4aa849794ba4/tests/multipart/firefox3-2png1txt/request.http -------------------------------------------------------------------------------- /tests/multipart/firefox3-2png1txt/text.txt: -------------------------------------------------------------------------------- 1 | example text 2 | -------------------------------------------------------------------------------- /tests/multipart/firefox3-2pnglongtext/file1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pallets/werkzeug/99fc293bc7dccd6a7dc96b96012d4aa849794ba4/tests/multipart/firefox3-2pnglongtext/file1.png -------------------------------------------------------------------------------- /tests/multipart/firefox3-2pnglongtext/file2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pallets/werkzeug/99fc293bc7dccd6a7dc96b96012d4aa849794ba4/tests/multipart/firefox3-2pnglongtext/file2.png -------------------------------------------------------------------------------- /tests/multipart/firefox3-2pnglongtext/request.http: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pallets/werkzeug/99fc293bc7dccd6a7dc96b96012d4aa849794ba4/tests/multipart/firefox3-2pnglongtext/request.http -------------------------------------------------------------------------------- /tests/multipart/firefox3-2pnglongtext/text.txt: -------------------------------------------------------------------------------- 1 | --long text 2 | --with boundary 3 | --lookalikes-- 4 | -------------------------------------------------------------------------------- /tests/multipart/ie6-2png1txt/file1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pallets/werkzeug/99fc293bc7dccd6a7dc96b96012d4aa849794ba4/tests/multipart/ie6-2png1txt/file1.png -------------------------------------------------------------------------------- /tests/multipart/ie6-2png1txt/file2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pallets/werkzeug/99fc293bc7dccd6a7dc96b96012d4aa849794ba4/tests/multipart/ie6-2png1txt/file2.png -------------------------------------------------------------------------------- /tests/multipart/ie6-2png1txt/request.http: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pallets/werkzeug/99fc293bc7dccd6a7dc96b96012d4aa849794ba4/tests/multipart/ie6-2png1txt/request.http -------------------------------------------------------------------------------- /tests/multipart/ie6-2png1txt/text.txt: -------------------------------------------------------------------------------- 1 | ie6 sucks :-/ 2 | -------------------------------------------------------------------------------- /tests/multipart/ie7_full_path_request.http: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pallets/werkzeug/99fc293bc7dccd6a7dc96b96012d4aa849794ba4/tests/multipart/ie7_full_path_request.http -------------------------------------------------------------------------------- /tests/multipart/opera8-2png1txt/file1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pallets/werkzeug/99fc293bc7dccd6a7dc96b96012d4aa849794ba4/tests/multipart/opera8-2png1txt/file1.png -------------------------------------------------------------------------------- /tests/multipart/opera8-2png1txt/file2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pallets/werkzeug/99fc293bc7dccd6a7dc96b96012d4aa849794ba4/tests/multipart/opera8-2png1txt/file2.png -------------------------------------------------------------------------------- /tests/multipart/opera8-2png1txt/request.http: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pallets/werkzeug/99fc293bc7dccd6a7dc96b96012d4aa849794ba4/tests/multipart/opera8-2png1txt/request.http -------------------------------------------------------------------------------- /tests/multipart/opera8-2png1txt/text.txt: -------------------------------------------------------------------------------- 1 | blafasel öäü 2 | -------------------------------------------------------------------------------- /tests/multipart/webkit3-2png1txt/file1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pallets/werkzeug/99fc293bc7dccd6a7dc96b96012d4aa849794ba4/tests/multipart/webkit3-2png1txt/file1.png -------------------------------------------------------------------------------- /tests/multipart/webkit3-2png1txt/file2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pallets/werkzeug/99fc293bc7dccd6a7dc96b96012d4aa849794ba4/tests/multipart/webkit3-2png1txt/file2.png -------------------------------------------------------------------------------- /tests/multipart/webkit3-2png1txt/request.http: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pallets/werkzeug/99fc293bc7dccd6a7dc96b96012d4aa849794ba4/tests/multipart/webkit3-2png1txt/request.http -------------------------------------------------------------------------------- /tests/multipart/webkit3-2png1txt/text.txt: -------------------------------------------------------------------------------- 1 | this is another text with ümläüts 2 | -------------------------------------------------------------------------------- /tests/res/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Title 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /tests/res/test.txt: -------------------------------------------------------------------------------- 1 | FOUND 2 | -------------------------------------------------------------------------------- /tests/sansio/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pallets/werkzeug/99fc293bc7dccd6a7dc96b96012d4aa849794ba4/tests/sansio/__init__.py -------------------------------------------------------------------------------- /tests/sansio/test_request.py: -------------------------------------------------------------------------------- 1 | import typing as t 2 | 3 | import pytest 4 | 5 | from werkzeug.datastructures import Headers 6 | from werkzeug.sansio.request import Request 7 | 8 | 9 | @pytest.mark.parametrize( 10 | "headers, expected", 11 | [ 12 | (Headers({"Transfer-Encoding": "chunked", "Content-Length": "6"}), None), 13 | (Headers({"Transfer-Encoding": "something", "Content-Length": "6"}), 6), 14 | (Headers({"Content-Length": "6"}), 6), 15 | (Headers({"Content-Length": "-6"}), 0), 16 | (Headers({"Content-Length": "+123"}), 0), 17 | (Headers({"Content-Length": "1_23"}), 0), 18 | (Headers({"Content-Length": "🯱🯲🯳"}), 0), 19 | (Headers(), None), 20 | ], 21 | ) 22 | def test_content_length(headers: Headers, expected: t.Optional[int]) -> None: 23 | req = Request("POST", "http", None, "", "", b"", headers, None) 24 | assert req.content_length == expected 25 | 26 | 27 | def test_cookies() -> None: 28 | headers = Headers([("Cookie", "a=b"), ("Content-Type", "text"), ("Cookie", "a=c")]) 29 | req = Request("GET", "http", None, "", "", b"", headers, None) 30 | assert req.cookies.get("a") == "b" 31 | assert req.cookies.getlist("a") == ["b", "c"] 32 | -------------------------------------------------------------------------------- /tests/sansio/test_utils.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import pytest 4 | 5 | from werkzeug.sansio.utils import get_content_length 6 | from werkzeug.sansio.utils import get_host 7 | 8 | 9 | @pytest.mark.parametrize( 10 | ("scheme", "host_header", "server", "expected"), 11 | [ 12 | ("http", "spam", None, "spam"), 13 | ("http", "spam:80", None, "spam"), 14 | ("https", "spam", None, "spam"), 15 | ("https", "spam:443", None, "spam"), 16 | ("http", "spam:8080", None, "spam:8080"), 17 | ("http", "127.0.0.1:8080", None, "127.0.0.1:8080"), 18 | ("http", "[::1]:8080", None, "[::1]:8080"), 19 | ("ws", "spam", None, "spam"), 20 | ("ws", "spam:80", None, "spam"), 21 | ("wss", "spam", None, "spam"), 22 | ("wss", "spam:443", None, "spam"), 23 | ("http", None, ("spam", 80), "spam"), 24 | ("http", None, ("spam", 8080), "spam:8080"), 25 | ("http", None, ("127.0.0.1", 8080), "127.0.0.1:8080"), 26 | ("http", None, ("::1", 8080), "[::1]:8080"), 27 | ("http", None, ("unix/socket", None), "unix/socket"), 28 | ("http", "spam", ("eggs", 80), "spam"), 29 | ], 30 | ) 31 | def test_get_host( 32 | scheme: str, 33 | host_header: str | None, 34 | server: tuple[str, int | None] | None, 35 | expected: str, 36 | ) -> None: 37 | assert get_host(scheme, host_header, server) == expected 38 | 39 | 40 | @pytest.mark.parametrize( 41 | ("http_content_length", "http_transfer_encoding", "expected"), 42 | [ 43 | ("2", None, 2), 44 | (" 2", None, 2), 45 | ("2 ", None, 2), 46 | (None, None, None), 47 | (None, "chunked", None), 48 | ("a", None, 0), 49 | ("-2", None, 0), 50 | ], 51 | ) 52 | def test_get_content_length( 53 | http_content_length: str | None, 54 | http_transfer_encoding: str | None, 55 | expected: int | None, 56 | ) -> None: 57 | assert get_content_length(http_content_length, http_transfer_encoding) == expected 58 | -------------------------------------------------------------------------------- /tests/test_internal.py: -------------------------------------------------------------------------------- 1 | from werkzeug.test import create_environ 2 | from werkzeug.wrappers import Request 3 | from werkzeug.wrappers import Response 4 | 5 | 6 | def test_wrapper_internals(): 7 | req = Request.from_values(data={"foo": "bar"}, method="POST") 8 | req._load_form_data() 9 | assert req.form.to_dict() == {"foo": "bar"} 10 | 11 | # second call does not break 12 | req._load_form_data() 13 | assert req.form.to_dict() == {"foo": "bar"} 14 | 15 | # check reprs 16 | assert repr(req) == "" 17 | resp = Response() 18 | assert repr(resp) == "" 19 | resp.set_data("Hello World!") 20 | assert repr(resp) == "" 21 | resp.response = iter(["Test"]) 22 | assert repr(resp) == "" 23 | 24 | response = Response(["Hällo Wörld"]) 25 | headers = response.get_wsgi_headers(create_environ()) 26 | assert "Content-Length" in headers 27 | 28 | response = Response(["Hällo Wörld".encode()]) 29 | headers = response.get_wsgi_headers(create_environ()) 30 | assert "Content-Length" in headers 31 | -------------------------------------------------------------------------------- /tests/test_security.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | 4 | import pytest 5 | 6 | from werkzeug.security import check_password_hash 7 | from werkzeug.security import generate_password_hash 8 | from werkzeug.security import safe_join 9 | 10 | 11 | def test_default_password_method(): 12 | value = generate_password_hash("secret") 13 | assert value.startswith("scrypt:") 14 | 15 | 16 | @pytest.mark.xfail( 17 | sys.implementation.name == "pypy", reason="scrypt unavailable on pypy" 18 | ) 19 | def test_scrypt(): 20 | value = generate_password_hash("secret", method="scrypt") 21 | assert check_password_hash(value, "secret") 22 | assert value.startswith("scrypt:32768:8:1$") 23 | 24 | 25 | def test_pbkdf2(): 26 | value = generate_password_hash("secret", method="pbkdf2") 27 | assert check_password_hash(value, "secret") 28 | assert value.startswith("pbkdf2:sha256:1000000$") 29 | 30 | 31 | def test_salted_hashes(): 32 | hash1 = generate_password_hash("secret") 33 | hash2 = generate_password_hash("secret") 34 | assert hash1 != hash2 35 | assert check_password_hash(hash1, "secret") 36 | assert check_password_hash(hash2, "secret") 37 | 38 | 39 | def test_require_salt(): 40 | with pytest.raises(ValueError): 41 | generate_password_hash("secret", salt_length=0) 42 | 43 | 44 | def test_invalid_method(): 45 | with pytest.raises(ValueError, match="Invalid hash method"): 46 | generate_password_hash("secret", "sha256") 47 | 48 | 49 | @pytest.mark.parametrize( 50 | ("path", "expect"), 51 | [ 52 | ("b/c", "a/b/c"), 53 | ("../b/c", None), 54 | ("b\\c", None if os.name == "nt" else "a/b\\c"), 55 | ("//b/c", None), 56 | ], 57 | ) 58 | def test_safe_join(path, expect): 59 | assert safe_join("a", path) == expect 60 | 61 | 62 | def test_safe_join_os_sep(): 63 | import werkzeug.security as sec 64 | 65 | prev_value = sec._os_alt_seps 66 | sec._os_alt_seps = "*" 67 | assert safe_join("foo", "bar/baz*") is None 68 | sec._os_alt_steps = prev_value 69 | 70 | 71 | def test_safe_join_empty_trusted(): 72 | assert safe_join("", "c:test.txt") == "./c:test.txt" 73 | --------------------------------------------------------------------------------