diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 000000000..e32c8029d --- /dev/null +++ b/.editorconfig @@ -0,0 +1,13 @@ +root = true + +[*] +indent_style = space +indent_size = 4 +insert_final_newline = true +trim_trailing_whitespace = true +end_of_line = lf +charset = utf-8 +max_line_length = 88 + +[*.{yml,yaml,json,js,css,html}] +indent_size = 2 diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000..764a4428d --- /dev/null +++ b/.gitattributes @@ -0,0 +1,8 @@ +# Normalize CRLF to LF for all text files +* text=auto + +# Declare binary file types so they won't be normalized +*.png binary +*.jpg binary +tests/**/*.http binary +tests/res/test.txt binary diff --git a/.github/ISSUE_TEMPLATE/bug-report.md b/.github/ISSUE_TEMPLATE/bug-report.md new file mode 100644 index 000000000..eb5e22b21 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug-report.md @@ -0,0 +1,27 @@ +--- +name: Bug report +about: Report a bug in Werkzeug (not other projects which depend on Werkzeug) +--- + + + + + + + +Environment: + +- Python version: +- Werkzeug version: diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 000000000..9df4cec0e --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,11 @@ +blank_issues_enabled: false +contact_links: + - name: Security issue + url: security@palletsprojects.com + about: Do not report security issues publicly. Email our security contact. + - name: Questions + url: https://stackoverflow.com/questions/tagged/werkzeug?tab=Frequent + about: Search for and ask questions about your code on Stack Overflow. + - name: Questions and discussions + url: https://discord.gg/pallets + about: Discuss questions about your code on our Discord chat. diff --git a/.github/ISSUE_TEMPLATE/feature-request.md b/.github/ISSUE_TEMPLATE/feature-request.md new file mode 100644 index 000000000..48698798f --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature-request.md @@ -0,0 +1,15 @@ +--- +name: Feature request +about: Suggest a new feature for Werkzeug +--- + + + + diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 000000000..90f94bc32 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,9 @@ +version: 2 +updates: +- package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "monthly" + day: "monday" + time: "16:00" + timezone: "UTC" diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 000000000..29fd35f85 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,30 @@ + + + + +- fixes # + + + +Checklist: + +- [ ] Add tests that demonstrate the correct behavior of the change. Tests should fail without the change. +- [ ] Add or update relevant docs, in the docs folder and in code. +- [ ] Add an entry in `CHANGES.rst` summarizing the change and linking to the issue. +- [ ] Add `.. versionchanged::` entries in any relevant code docs. +- [ ] Run `pre-commit` hooks and fix any issues. +- [ ] Run `pytest` and `tox`, no tests failed. diff --git a/.github/workflows/lock.yaml b/.github/workflows/lock.yaml new file mode 100644 index 000000000..b4f763387 --- /dev/null +++ b/.github/workflows/lock.yaml @@ -0,0 +1,15 @@ +name: 'Lock threads' + +on: + schedule: + - cron: '0 0 * * *' + +jobs: + lock: + runs-on: ubuntu-latest + steps: + - uses: dessant/lock-threads@v3 + with: + github-token: ${{ github.token }} + issue-inactive-days: 14 + pr-inactive-days: 14 diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml new file mode 100644 index 000000000..d4441fff0 --- /dev/null +++ b/.github/workflows/tests.yaml @@ -0,0 +1,55 @@ +name: Tests +on: + push: + branches: + - main + - '*.x' + paths-ignore: + - 'docs/**' + - '*.md' + - '*.rst' + pull_request: + branches: + - main + - '*.x' + paths-ignore: + - 'docs/**' + - '*.md' + - '*.rst' +jobs: + tests: + name: ${{ matrix.name }} + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + include: + - {name: Linux, python: '3.10', os: ubuntu-latest, tox: py310} + - {name: Windows, python: '3.10', os: windows-latest, tox: py310} + - {name: Mac, python: '3.10', os: macos-latest, tox: py310} + - {name: '3.11-dev', python: '3.11-dev', os: ubuntu-latest, tox: py311} + - {name: '3.9', python: '3.9', os: ubuntu-latest, tox: py39} + - {name: '3.8', python: '3.8', os: ubuntu-latest, tox: py38} + - {name: '3.7', python: '3.7', os: ubuntu-latest, tox: py37} + - {name: 'PyPy', python: 'pypy-3.7', os: ubuntu-latest, tox: pypy37} + - {name: Typing, python: '3.10', os: ubuntu-latest, tox: typing} + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python }} + cache: 'pip' + cache-dependency-path: 'requirements/*.txt' + - name: update pip + run: | + pip install -U wheel + pip install -U setuptools + python -m pip install -U pip + - name: cache mypy + uses: actions/cache@v3.0.4 + with: + path: ./.mypy_cache + key: mypy|${{ matrix.python }}|${{ hashFiles('setup.cfg') }} + if: matrix.tox == 'typing' + - run: pip install tox + - run: tox -e ${{ matrix.tox }} diff --git a/.gitignore b/.gitignore index 6e9006102..36f36703b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,18 +1,26 @@ MANIFEST build dist -*.egg-info +/src/Werkzeug.egg-info *.pyc *.pyo env .DS_Store -.tox docs/_build bench/a bench/b +.tox .coverage +.coverage.* coverage_out +htmlcov .cache .xprocess -htmlcov .hypothesis +test_uwsgi_failed +.idea +.pytest_cache/ +venv/ +.vscode +.mypy_cache/ +.dmypy.json diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 000000000..55f8c1357 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,44 @@ +ci: + autoupdate_branch: "2.2.x" + autoupdate_schedule: monthly +repos: + - repo: https://github.com/asottile/pyupgrade + rev: v2.37.3 + hooks: + - id: pyupgrade + args: ["--py37-plus"] + - repo: https://github.com/asottile/reorder_python_imports + rev: v3.8.2 + hooks: + - id: reorder-python-imports + name: Reorder Python imports (src, tests) + files: "^(?!examples/)" + args: ["--application-directories", ".:src"] + additional_dependencies: ["setuptools>60.9"] + - id: reorder-python-imports + name: Reorder Python imports (examples) + files: "^examples/" + args: ["--application-directories", "examples"] + additional_dependencies: ["setuptools>60.9"] + - repo: https://github.com/psf/black + rev: 22.6.0 + hooks: + - id: black + - repo: https://github.com/PyCQA/flake8 + rev: 5.0.4 + hooks: + - id: flake8 + additional_dependencies: + - flake8-bugbear + - flake8-implicit-str-concat + - repo: https://github.com/peterdemin/pip-compile-multi + rev: v2.4.6 + hooks: + - id: pip-compile-multi-verify + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.3.0 + hooks: + - id: fix-byte-order-marker + - id: trailing-whitespace + - id: end-of-file-fixer + exclude: "^tests/.*.http$" diff --git a/.readthedocs.yaml b/.readthedocs.yaml new file mode 100644 index 000000000..346900b20 --- /dev/null +++ b/.readthedocs.yaml @@ -0,0 +1,13 @@ +version: 2 +build: + os: ubuntu-20.04 + tools: + python: "3.10" +python: + install: + - requirements: requirements/docs.txt + - method: pip + path: . +sphinx: + builder: dirhtml + fail_on_warning: true diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 684cdfb7f..000000000 --- a/.travis.yml +++ /dev/null @@ -1,63 +0,0 @@ -sudo: false -language: python -python: - - 2.6 - - 2.7 - - pypy - - 3.3 - - 3.4 - - 3.5 - - 3.6 - - nightly -env: - - TOXENV_SUFFIX=normal - - TOXENV_SUFFIX=stylecheck - - TOXENV_SUFFIX=uwsgi - -matrix: - exclude: - - python: pypy - env: TOXENV_SUFFIX=uwsgi - - python: 2.6 # flake8 doesn't run on 2.6 - env: TOXENV_SUFFIX=stylecheck - include: - - os: osx - language: generic - env: TOXENV_SUFFIX=normal - -os: linux - -before_install: - - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then - brew update; - brew install python3 redis memcached; - virtualenv ~/py-env -p python3; - . ~/py-env/bin/activate; - fi - # Travis uses an outdated PyPy, this installs the most recent one. - # This makes the tests run on Travis' legacy infrastructure, but so be it. - # temporary pyenv installation to get pypy-2.6 before container infra upgrade - - if [ "$TRAVIS_PYTHON_VERSION" = "pypy" ]; then - git clone https://github.com/yyuu/pyenv.git ~/.pyenv; - PYENV_ROOT="$HOME/.pyenv"; - PATH="$PYENV_ROOT/bin:$PATH"; - eval "$(pyenv init -)"; - pyenv install pypy-4.0.1; - pyenv global pypy-4.0.1; - fi - - python --version - -install: - - pip install tox flake8 - -script: - - tox -e py-$TOXENV_SUFFIX - -branches: - only: - - master - - auto - - /^.*-maintenance$/ - -notifications: - email: false diff --git a/AUTHORS b/AUTHORS deleted file mode 100644 index bc0180e93..000000000 --- a/AUTHORS +++ /dev/null @@ -1,61 +0,0 @@ -Werkzeug is written and maintained by the Werkzeug Team and various -contributors: - -Project Leader / Developer: - -- Armin Ronacher - -- Georg Brandl -- Leif K-Brooks -- Thomas Johansson -- Marian Sigler -- Ronny Pfannschmidt -- Noah Slater -- Alec Thomas -- Shannon Behrens -- Christoph Rauch -- Clemens Hermann -- Jason Kirtland -- Ali Afshar -- Christopher Grebs -- Sean Cazzell -- Florent Xicluna -- Kyle Dawkins -- Pedro Algarvio -- Zahari Petkov -- Ludvig Ericson -- Kenneth Reitz -- Daniel Neuhäuser -- Markus Unterwaditzer -- Joe Esposito -- Abhinav Upadhyay -- immerrr -- Cédric Krier -- Phil Jones -- Michael Hunsinger -- Lars Holm Nielsen -- Joël Charles -- Benjamin Dopplinger -- Nils Steinger - -Contributors of code for werkzeug/examples are: - -- Itay Neeman - -The SSL related parts of the Werkzeug development server are partially -taken from Paste. Same thing is true for the range support which comes -from WebOb which is a Paste project. The original code is MIT licensed which -is largely compatible with the modfied BSD license. The following copyrights -apply: - -- (c) 2005 Ian Bicking and contributors -- (c) 2005 Clark C. Evans - -The rename() function from the posixemulation was taken almost unmodified -from the Trac project's utility module. The original code is BSD licensed -with the following copyrights from that module: - -- (c) 2003-2009 Edgewall Software -- (c) 2003-2006 Jonas Borgström -- (c) 2006 Matthew Good -- (c) 2005-2006 Christian Boos diff --git a/CHANGES b/CHANGES deleted file mode 100644 index d83a2af86..000000000 --- a/CHANGES +++ /dev/null @@ -1,1182 +0,0 @@ -Werkzeug Changelog -================== - -Version 0.12.2 --------------- - -Released on May 16 2017 - -- Fix regression: Pull request ``#892`` prevented Werkzeug from correctly - logging the IP of a remote client behind a reverse proxy, even when using - `ProxyFix`. -- Fix a bug in `safe_join` on Windows. - -Version 0.12.1 --------------- - -Released on March 15th 2017 - -- Fix crash of reloader (used on debug mode) on Windows. - (`OSError: [WinError 10038]`). See pull request ``#1081`` -- Partially revert change to class hierarchy of `Headers`. See ``#1084``. - -Version 0.12 ------------- - -Released on March 10th 2017 - -- Spit out big deprecation warnings for werkzeug.script -- Use `inspect.getfullargspec` internally when available as - `inspect.getargspec` is gone in 3.6 -- Added support for status code 451 and 423 -- Improved the build error suggestions. In particular only if - someone stringifies the error will the suggestions be calculated. -- Added support for uWSGI's caching backend. -- Fix a bug where iterating over a `FileStorage` would result in an infinite - loop. -- Datastructures now inherit from the relevant baseclasses from the - `collections` module in the stdlib. See #794. -- Add support for recognizing NetBSD, OpenBSD, FreeBSD, DragonFlyBSD platforms - in the user agent string. -- Recognize SeaMonkey browser name and version correctly -- Recognize Baiduspider, and bingbot user agents -- If `LocalProxy`'s wrapped object is a function, refer to it with __wrapped__ - attribute. -- The defaults of ``generate_password_hash`` have been changed to more secure - ones, see pull request ``#753``. -- Add support for encoding in options header parsing, see pull request - ``#933``. -- ``test.Client`` now properly handles Location headers with relative URLs, see - pull request ``#879``. -- When `HTTPException` is raised, it now prints the description, for easier - debugging. -- Werkzeug's dict-like datastructures now have ``view``-methods under Python 2, - see pull request ``#968``. -- Fix a bug in ``MultiPartParser`` when no ``stream_factory`` was provided - during initialization, see pull request ``#973``. -- Disable autocorrect and spellchecker in the debugger middleware's Python - prompt, see pull request ``#994``. -- Don't redirect to slash route when method doesn't match, see pull request - ``#907``. -- Fix a bug when using ``SharedDataMiddleware`` with frozen packages, see pull - request ``#959``. -- `Range` header parsing function fixed for invalid values ``#974``. -- Add support for byte Range Requests, see pull request ``#978``. -- Use modern cryptographic defaults in the dev servers ``#1004``. -- the post() method of the test client now accept file object through the data - parameter. -- Color run_simple's terminal output based on HTTP codes ``#1013``. -- Fix self-XSS in debugger console, see ``#1031``. -- Fix IPython 5.x shell support, see ``#1033``. - -Version 0.11.16 ---------------- - -- werkzeug.serving: set CONTENT_TYPE / CONTENT_LENGTH if only they're provided by the client -- werkzeug.serving: Fix crash of reloader when using `python -m werkzeug.serving`. - -Version 0.11.15 ---------------- - -Released on December 30th 2016. - -- Bugfix for the bugfix in the previous release. - -Version 0.11.14 ---------------- - -Released on December 30th 2016. - -- Check if platform can fork before importing ``ForkingMixIn``, raise exception - when creating ``ForkingWSGIServer`` on such a platform, see PR ``#999``. - -Version 0.11.13 ---------------- - -Released on December 26th 2016. - -- Correct fix for the reloader issuer on certain Windows installations. - -Version 0.11.12 ---------------- - -Released on December 26th 2016. - -- Fix more bugs in multidicts regarding empty lists. See ``#1000``. -- Add some docstrings to some `EnvironBuilder` properties that were previously - unintentionally missing. -- Added a workaround for the reloader on windows. - -Version 0.11.11 ---------------- - -Released on August 31st 2016. - -- Fix JSONRequestMixin for Python3. See #731 -- Fix broken string handling in test client when passing integers. See #852 -- Fix a bug in ``parse_options_header`` where an invalid content type - starting with comma or semi-colon would result in an invalid return value, - see issue ``#995``. -- Fix a bug in multidicts when passing empty lists as values, see issue - ``#979``. -- Fix a security issue that allows XSS on the Werkzeug debugger. See ``#1001``. - -Version 0.11.10 ---------------- - -Released on May 24th 2016. - -- Fixed a bug that occurs when running on Python 2.6 and using a broken locale. - See pull request #912. -- Fixed a crash when running the debugger on Google App Engine. See issue #925. -- Fixed an issue with multipart parsing that could cause memory exhaustion. - -Version 0.11.9 --------------- - -Released on April 24th 2016. - -- Corrected an issue that caused the debugger not to use the - machine GUID on POSIX systems. -- Corrected a Unicode error on Python 3 for the debugger's - PIN usage. -- Corrected the timestamp verification in the pin debug code. - Without this fix the pin was remembered for too long. - -Version 0.11.8 --------------- - -Released on April 15th 2016. - -- fixed a problem with the machine GUID detection code on OS X - on Python 3. - -Version 0.11.7 --------------- - -Released on April 14th 2016. - -- fixed a regression on Python 3 for the debugger. - -Version 0.11.6 --------------- - -Released on April 14th 2016. - -- werkzeug.serving: Still show the client address on bad requests. -- improved the PIN based protection for the debugger to make it harder to - brute force via trying cookies. Please keep in mind that the debugger - *is not intended for running on production environments* -- increased the pin timeout to a week to make it less annoying for people - which should decrease the chance that users disable the pin check - entirely. -- werkzeug.serving: Fix broken HTTP_HOST when path starts with double slash. - -Version 0.11.5 --------------- - -Released on March 22nd 2016. - -- werkzeug.serving: Fix crash when attempting SSL connection to HTTP server. - -Version 0.11.4 --------------- - -Released on February 14th 2016. - -- Fixed werkzeug.serving not working from -m flag. -- Fixed incorrect weak etag handling. - -Version 0.11.3 --------------- - -Released on December 20th 2015. - -- Fixed an issue with copy operations not working against - proxies. -- Changed the logging operations of the development server to - correctly log where the server is running in all situations - again. -- Fixed another regression with SSL wrapping similar to the - fix in 0.11.2 but for a different code path. - -Version 0.11.2 --------------- - -Released on November 12th 2015. - -- Fix inheritable sockets on Windows on Python 3. -- Fixed an issue with the forking server not starting any longer. -- Fixed SSL wrapping on platforms that supported opening sockets - by file descriptor. -- No longer log from the watchdog reloader. -- Unicode errors in hosts are now better caught or converted into - bad request errors. - -Version 0.11.1 --------------- - -Released on November 10th 2015. - -- Fixed a regression on Python 3 in the debugger. - -Version 0.11 ------------- - -Released on November 8th 2015, codename Gleisbaumaschine. - -- Added ``reloader_paths`` option to ``run_simple`` and other functions in - ``werkzeug.serving``. This allows the user to completely override the Python - module watching of Werkzeug with custom paths. -- Many custom cached properties of Werkzeug's classes are now subclasses of - Python's ``property`` type (issue ``#616``). -- ``bind_to_environ`` now doesn't differentiate between implicit and explicit - default port numbers in ``HTTP_HOST`` (pull request ``#204``). -- ``BuildErrors`` are now more informative. They come with a complete sentence - as error message, and also provide suggestions (pull request ``#691``). -- Fix a bug in the user agent parser where Safari's build number instead of - version would be extracted (pull request ``#703``). -- Fixed issue where RedisCache set_many was broken for twemproxy, which doesn't - support the default MULTI command (pull request ``#702``). -- ``mimetype`` parameters on request and response classes are now always - converted to lowercase. -- Changed cache so that cache never expires if timeout is 0. This also fixes - an issue with redis setex (issue ``#550``) -- Werkzeug now assumes ``UTF-8`` as filesystem encoding on Unix if Python - detected it as ASCII. -- New optional `has` method on caches. -- Fixed various bugs in `parse_options_header` (pull request ``#643``). -- If the reloader is enabled the server will now open the socket in the parent - process if this is possible. This means that when the reloader kicks in - the connection from client will wait instead of tearing down. This does - not work on all Python versions. -- Implemented PIN based authentication for the debugger. This can optionally - be disabled but is discouraged. This change was necessary as it has been - discovered that too many people run the debugger in production. -- Devserver no longer requires SSL module to be installed. - -Version 0.10.5 --------------- - -(bugfix release, release date yet to be decided) - -- Reloader: Correctly detect file changes made by moving temporary files over - the original, which is e.g. the case with PyCharm (pull request ``#722``). -- Fix bool behavior of ``werkzeug.datastructures.ETags`` under Python 3 (issue - ``#744``). - -Version 0.10.4 --------------- - -(bugfix release, released on March 26th 2015) - -- Re-release of 0.10.3 with packaging artifacts manually removed. - -Version 0.10.3 --------------- - -(bugfix release, released on March 26th 2015) - -- Re-release of 0.10.2 without packaging artifacts. - -Version 0.10.2 --------------- - -(bugfix release, released on March 26th 2015) - -- Fixed issue where ``empty`` could break third-party libraries that relied on - keyword arguments (pull request ``#675``) -- Improved ``Rule.empty`` by providing a ```get_empty_kwargs`` to allow setting - custom kwargs without having to override entire ``empty`` method. (pull - request ``#675``) -- Fixed ```extra_files``` parameter for reloader to not cause startup - to crash when included in server params -- Using `MultiDict` when building URLs is now not supported again. The behavior - introduced several regressions. -- Fix performance problems with stat-reloader (pull request ``#715``). - -Version 0.10.1 --------------- - -(bugfix release, released on February 3rd 2015) - -- Fixed regression with multiple query values for URLs (pull request ``#667``). -- Fix issues with eventlet's monkeypatching and the builtin server (pull - request ``#663``). - -Version 0.10 ------------- - -Released on January 30th 2015, codename Bagger. - -- Changed the error handling of and improved testsuite for the caches in - ``contrib.cache``. -- Fixed a bug on Python 3 when creating adhoc ssl contexts, due to `sys.maxint` - not being defined. -- Fixed a bug on Python 3, that caused - :func:`~werkzeug.serving.make_ssl_devcert` to fail with an exception. -- Added exceptions for 504 and 505. -- Added support for ChromeOS detection. -- Added UUID converter to the routing system. -- Added message that explains how to quit the server. -- Fixed a bug on Python 2, that caused ``len`` for - :class:`werkzeug.datastructures.CombinedMultiDict` to crash. -- Added support for stdlib pbkdf2 hmac if a compatible digest - is found. -- Ported testsuite to use ``py.test``. -- Minor optimizations to various middlewares (pull requests ``#496`` and - ``#571``). -- Use stdlib ``ssl`` module instead of ``OpenSSL`` for the builtin server - (issue ``#434``). This means that OpenSSL contexts are not supported anymore, - but instead ``ssl.SSLContext`` from the stdlib. -- Allow protocol-relative URLs when building external URLs. -- Fixed Atom syndication to print time zone offset for tz-aware datetime - objects (pull request ``#254``). -- Improved reloader to track added files and to recover from broken - sys.modules setups with syntax errors in packages. -- ``cache.RedisCache`` now supports arbitrary ``**kwargs`` for the redis - object. -- ``werkzeug.test.Client`` now uses the original request method when resolving - 307 redirects (pull request ``#556``). -- ``werkzeug.datastructures.MIMEAccept`` now properly deals with mimetype - parameters (pull request ``#205``). -- ``werkzeug.datastructures.Accept`` now handles a quality of ``0`` as - intolerable, as per RFC 2616 (pull request ``#536``). -- ``werkzeug.urls.url_fix`` now properly encodes hostnames with ``idna`` - encoding (issue ``#559``). It also doesn't crash on malformed URLs anymore - (issue ``#582``). -- ``werkzeug.routing.MapAdapter.match`` now recognizes the difference between - the path ``/`` and an empty one (issue ``#360``). -- The interactive debugger now tries to decode non-ascii filenames (issue - ``#469``). -- Increased default key size of generated SSL certificates to 1024 bits (issue - ``#611``). -- Added support for specifying a ``Response`` subclass to use when calling - :func:`~werkzeug.utils.redirect`\ . -- ``werkzeug.test.EnvironBuilder`` now doesn't use the request method anymore - to guess the content type, and purely relies on the ``form``, ``files`` and - ``input_stream`` properties (issue ``#620``). -- Added Symbian to the user agent platform list. -- Fixed make_conditional to respect automatically_set_content_length -- Unset ``Content-Length`` when writing to response.stream (issue ``#451``) -- ``wrappers.Request.method`` is now always uppercase, eliminating - inconsistencies of the WSGI environment (issue ``647``). -- ``routing.Rule.empty`` now works correctly with subclasses of ``Rule`` (pull - request ``#645``). -- Made map updating safe in light of concurrent updates. -- Allow multiple values for the same field for url building (issue ``#658``). - -Version 0.9.7 -------------- - -(bugfix release, release date to be decided) - -- Fix unicode problems in ``werkzeug.debug.tbtools``. -- Fix Python 3-compatibility problems in ``werkzeug.posixemulation``. -- Backport fix of fatal typo for ``ImmutableList`` (issue ``#492``). -- Make creation of the cache dir for ``FileSystemCache`` atomic (issue - ``#468``). -- Use native strings for memcached keys to work with Python 3 client (issue - ``#539``). -- Fix charset detection for ``werkzeug.debug.tbtools.Frame`` objects (issues - ``#547`` and ``#532``). -- Fix ``AttributeError`` masking in ``werkzeug.utils.import_string`` (issue - ``#182``). -- Explicitly shut down server (issue ``#519``). -- Fix timeouts greater than 2592000 being misinterpreted as UNIX timestamps in - ``werkzeug.contrib.cache.MemcachedCache`` (issue ``#533``). -- Fix bug where ``werkzeug.exceptions.abort`` would raise an arbitrary subclass - of the expected class (issue ``#422``). -- Fix broken ``jsrouting`` (due to removal of ``werkzeug.templates``) -- ``werkzeug.urls.url_fix`` now doesn't crash on malformed URLs anymore, but - returns them unmodified. This is a cheap workaround for ``#582``, the proper - fix is included in version 0.10. -- The repr of ``werkzeug.wrappers.Request`` doesn't crash on non-ASCII-values - anymore (pull request ``#466``). -- Fix bug in ``cache.RedisCache`` when combined with ``redis.StrictRedis`` - object (pull request ``#583``). -- The ``qop`` parameter for ``WWW-Authenticate`` headers is now always quoted, - as required by RFC 2617 (issue ``#633``). -- Fix bug in ``werkzeug.contrib.cache.SimpleCache`` with Python 3 where add/set - may throw an exception when pruning old entries from the cache (pull request - ``#651``). - -Version 0.9.6 -------------- - -(bugfix release, released on June 7th 2014) - -- Added a safe conversion for IRI to URI conversion and use that - internally to work around issues with spec violations for - protocols such as ``itms-service``. - -Version 0.9.7 -------------- - -- Fixed uri_to_iri() not re-encoding hashes in query string parameters. - -Version 0.9.5 -------------- - -(bugfix release, released on June 7th 2014) - -- Forward charset argument from request objects to the environ - builder. -- Fixed error handling for missing boundaries in multipart data. -- Fixed session creation on systems without ``os.urandom()``. -- Fixed pluses in dictionary keys not being properly URL encoded. -- Fixed a problem with deepcopy not working for multi dicts. -- Fixed a double quoting issue on redirects. -- Fixed a problem with unicode keys appearing in headers on 2.x. -- Fixed a bug with unicode strings in the test builder. -- Fixed a unicode bug on Python 3 in the WSGI profiler. -- Fixed an issue with the safe string compare function on - Python 2.7.7 and Python 3.4. - -Version 0.9.4 -------------- - -(bugfix release, released on August 26th 2013) - -- Fixed an issue with Python 3.3 and an edge case in cookie parsing. -- Fixed decoding errors not handled properly through the WSGI - decoding dance. -- Fixed URI to IRI conversion incorrectly decoding percent signs. - -Version 0.9.3 -------------- - -(bugfix release, released on July 25th 2013) - -- Restored behavior of the ``data`` descriptor of the request class to pre 0.9 - behavior. This now also means that ``.data`` and ``.get_data()`` have - different behavior. New code should use ``.get_data()`` always. - - In addition to that there is now a flag for the ``.get_data()`` method that - controls what should happen with form data parsing and the form parser will - honor cached data. This makes dealing with custom form data more consistent. - -Version 0.9.2 -------------- - -(bugfix release, released on July 18th 2013) - -- Added `unsafe` parameter to :func:`~werkzeug.urls.url_quote`. -- Fixed an issue with :func:`~werkzeug.urls.url_quote_plus` not quoting - `'+'` correctly. -- Ported remaining parts of :class:`~werkzeug.contrib.RedisCache` to - Python 3.3. -- Ported remaining parts of :class:`~werkzeug.contrib.MemcachedCache` to - Python 3.3 -- Fixed a deprecation warning in the contrib atom module. -- Fixed a regression with setting of content types through the - headers dictionary instead with the content type parameter. -- Use correct name for stdlib secure string comparison function. -- Fixed a wrong reference in the docstring of - :func:`~werkzeug.local.release_local`. -- Fixed an `AttributeError` that sometimes occurred when accessing the - :attr:`werkzeug.wrappers.BaseResponse.is_streamed` attribute. - -Version 0.9.1 -------------- - -(bugfix release, released on June 14th 2013) - -- Fixed an issue with integers no longer being accepted in certain - parts of the routing system or URL quoting functions. -- Fixed an issue with `url_quote` not producing the right escape - codes for single digit codepoints. -- Fixed an issue with :class:`~werkzeug.wsgi.SharedDataMiddleware` not - reading the path correctly and breaking on etag generation in some - cases. -- Properly handle `Expect: 100-continue` in the development server - to resolve issues with curl. -- Automatically exhaust the input stream on request close. This should - fix issues where not touching request files results in a timeout. -- Fixed exhausting of streams not doing anything if a non-limited - stream was passed into the multipart parser. -- Raised the buffer sizes for the multipart parser. - -Version 0.9 ------------ - -Released on June 13nd 2013, codename Planierraupe. - -- Added support for :meth:`~werkzeug.wsgi.LimitedStream.tell` - on the limited stream. -- :class:`~werkzeug.datastructures.ETags` now is nonzero if it - contains at least one etag of any kind, including weak ones. -- Added a workaround for a bug in the stdlib for SSL servers. -- Improved SSL interface of the devserver so that it can generate - certificates easily and load them from files. -- Refactored test client to invoke the open method on the class - for redirects. This makes subclassing more powerful. -- :func:`werkzeug.wsgi.make_chunk_iter` and - :func:`werkzeug.wsgi.make_line_iter` now support processing of - iterators and streams. -- URL generation by the routing system now no longer quotes - ``+``. -- URL fixing now no longer quotes certain reserved characters. -- The :func:`werkzeug.security.generate_password_hash` and - check functions now support any of the hashlib algorithms. -- `wsgi.get_current_url` is now ascii safe for browsers sending - non-ascii data in query strings. -- improved parsing behavior for :func:`werkzeug.http.parse_options_header` -- added more operators to local proxies. -- added a hook to override the default converter in the routing - system. -- The description field of HTTP exceptions is now always escaped. - Use markup objects to disable that. -- Added number of proxy argument to the proxy fix to make it more - secure out of the box on common proxy setups. It will by default - no longer trust the x-forwarded-for header as much as it did - before. -- Added support for fragment handling in URI/IRI functions. -- Added custom class support for :func:`werkzeug.http.parse_dict_header`. -- Renamed `LighttpdCGIRootFix` to `CGIRootFix`. -- Always treat `+` as safe when fixing URLs as people love misusing them. -- Added support to profiling into directories in the contrib profiler. -- The escape function now by default escapes quotes. -- Changed repr of exceptions to be less magical. -- Simplified exception interface to no longer require environments - to be passed to receive the response object. -- Added sentinel argument to IterIO objects. -- Added pbkdf2 support for the security module. -- Added a plain request type that disables all form parsing to only - leave the stream behind. -- Removed support for deprecated `fix_headers`. -- Removed support for deprecated `header_list`. -- Removed support for deprecated parameter for `iter_encoded`. -- Removed support for deprecated non-silent usage of the limited - stream object. -- Removed support for previous dummy `writable` parameter on - the cached property. -- Added support for explicitly closing request objects to close - associated resources. -- Conditional request handling or access to the data property on responses no - longer ignores direct passthrough mode. -- Removed werkzeug.templates and werkzeug.contrib.kickstart. -- Changed host lookup logic for forwarded hosts to allow lists of - hosts in which case only the first one is picked up. -- Added `wsgi.get_query_string`, `wsgi.get_path_info` and - `wsgi.get_script_name` and made the `wsgi.pop_path_info` and - `wsgi.peek_path_info` functions perform unicode decoding. This - was necessary to avoid having to expose the WSGI encoding dance - on Python 3. -- Added `content_encoding` and `content_md5` to the request object's - common request descriptor mixin. -- added `options` and `trace` to the test client. -- Overhauled the utilization of the input stream to be easier to use - and better to extend. The detection of content payload on the input - side is now more compliant with HTTP by detecting off the content - type header instead of the request method. This also now means that - the stream property on the request class is always available instead - of just when the parsing fails. -- Added support for using :class:`werkzeug.wrappers.BaseResponse` in a with - statement. -- Changed `get_app_iter` to fetch the response early so that it does not - fail when wrapping a response iterable. This makes filtering easier. -- Introduced `get_data` and `set_data` methods for responses. -- Introduced `get_data` for requests. -- Soft deprecated the `data` descriptors for request and response objects. -- Added `as_bytes` operations to some of the headers to simplify working - with things like cookies. -- Made the debugger paste tracebacks into github's gist service as - private pastes. - -Version 0.8.4 -------------- - -(bugfix release, release date to be announced) - -- Added a favicon to the debugger which fixes problem with - state changes being triggered through a request to - /favicon.ico in Google Chrome. This should fix some - problems with Flask and other frameworks that use - context local objects on a stack with context preservation - on errors. -- Fixed an issue with scrolling up in the debugger. -- Fixed an issue with debuggers running on a different URL - than the URL root. -- Fixed a problem with proxies not forwarding some rarely - used special methods properly. -- Added a workaround to prevent the XSS protection from Chrome - breaking the debugger. -- Skip redis tests if redis is not running. -- Fixed a typo in the multipart parser that caused content-type - to not be picked up properly. - -Version 0.8.3 -------------- - -(bugfix release, released on February 5th 2012) - -- Fixed another issue with :func:`werkzeug.wsgi.make_line_iter` - where lines longer than the buffer size were not handled - properly. -- Restore stdout after debug console finished executing so - that the debugger can be used on GAE better. -- Fixed a bug with the redis cache for int subclasses - (affects bool caching). -- Fixed an XSS problem with redirect targets coming from - untrusted sources. -- Redis cache backend now supports password authentication. - -Version 0.8.2 -------------- - -(bugfix release, released on December 16th 2011) - -- Fixed a problem with request handling of the builtin server - not responding to socket errors properly. -- The routing request redirect exception's code attribute is now - used properly. -- Fixed a bug with shutdowns on Windows. -- Fixed a few unicode issues with non-ascii characters being - hardcoded in URL rules. -- Fixed two property docstrings being assigned to fdel instead - of ``__doc__``. -- Fixed an issue where CRLF line endings could be split into two - by the line iter function, causing problems with multipart file - uploads. - -Version 0.8.1 -------------- - -(bugfix release, released on September 30th 2011) - -- Fixed an issue with the memcache not working properly. -- Fixed an issue for Python 2.7.1 and higher that broke - copying of multidicts with :func:`copy.copy`. -- Changed hashing methodology of immutable ordered multi dicts - for a potential problem with alternative Python implementations. - -Version 0.8 ------------ - -Released on September 29th 2011, codename Lötkolben - -- Removed data structure specific KeyErrors for a general - purpose :exc:`~werkzeug.exceptions.BadRequestKeyError`. -- Documented :meth:`werkzeug.wrappers.BaseRequest._load_form_data`. -- The routing system now also accepts strings instead of - dictionaries for the `query_args` parameter since we're only - passing them through for redirects. -- Werkzeug now automatically sets the content length immediately when - the :attr:`~werkzeug.wrappers.BaseResponse.data` attribute is set - for efficiency and simplicity reasons. -- The routing system will now normalize server names to lowercase. -- The routing system will no longer raise ValueErrors in case the - configuration for the server name was incorrect. This should make - deployment much easier because you can ignore that factor now. -- Fixed a bug with parsing HTTP digest headers. It rejected headers - with missing nc and nonce params. -- Proxy fix now also updates wsgi.url_scheme based on X-Forwarded-Proto. -- Added support for key prefixes to the redis cache. -- Added the ability to suppress some auto corrections in the wrappers - that are now controlled via `autocorrect_location_header` and - `automatically_set_content_length` on the response objects. -- Werkzeug now uses a new method to check that the length of incoming - data is complete and will raise IO errors by itself if the server - fails to do so. -- :func:`~werkzeug.wsgi.make_line_iter` now requires a limit that is - not higher than the length the stream can provide. -- Refactored form parsing into a form parser class that makes it possible - to hook into individual parts of the parsing process for debugging and - extending. -- For conditional responses the content length is no longer set when it - is already there and added if missing. -- Immutable datastructures are hashable now. -- Headers datastructure no longer allows newlines in values to avoid - header injection attacks. -- Made it possible through subclassing to select a different remote - addr in the proxy fix. -- Added stream based URL decoding. This reduces memory usage on large - transmitted form data that is URL decoded since Werkzeug will no longer - load all the unparsed data into memory. -- Memcache client now no longer uses the buggy cmemcache module and - supports pylibmc. GAE is not tried automatically and the dedicated - class is no longer necessary. -- Redis cache now properly serializes data. -- Removed support for Python 2.4 - -Version 0.7.2 -------------- - -(bugfix release, released on September 30th 2011) - -- Fixed a CSRF problem with the debugger. -- The debugger is now generating private pastes on lodgeit. -- If URL maps are now bound to environments the query arguments - are properly decoded from it for redirects. - -Version 0.7.1 -------------- - -(bugfix release, released on July 26th 2011) - -- Fixed a problem with newer versions of IPython. -- Disabled pyinotify based reloader which does not work reliably. - -Version 0.7 ------------ - -Released on July 24th 2011, codename Schraubschlüssel - -- Add support for python-libmemcached to the Werkzeug cache abstraction - layer. -- Improved :func:`url_decode` and :func:`url_encode` performance. -- Fixed an issue where the SharedDataMiddleware could cause an - internal server error on weird paths when loading via pkg_resources. -- Fixed an URL generation bug that caused URLs to be invalid if a - generated component contains a colon. -- :func:`werkzeug.import_string` now works with partially set up - packages properly. -- Disabled automatic socket switching for IPv6 on the development - server due to problems it caused. -- Werkzeug no longer overrides the Date header when creating a - conditional HTTP response. -- The routing system provides a method to retrieve the matching - methods for a given path. -- The routing system now accepts a parameter to change the encoding - error behaviour. -- The local manager can now accept custom ident functions in the - constructor that are forwarded to the wrapped local objects. -- url_unquote_plus now accepts unicode strings again. -- Fixed an issue with the filesystem session support's prune - function and concurrent usage. -- Fixed a problem with external URL generation discarding the port. -- Added support for pylibmc to the Werkzeug cache abstraction layer. -- Fixed an issue with the new multipart parser that happened when - a linebreak happened to be on the chunk limit. -- Cookies are now set properly if ports are in use. A runtime error - is raised if one tries to set a cookie for a domain without a dot. -- Fixed an issue with Template.from_file not working for file - descriptors. -- Reloader can now use inotify to track reloads. This requires the - pyinotify library to be installed. -- Werkzeug debugger can now submit to custom lodgeit installations. -- redirect function's status code assertion now allows 201 to be used - as redirection code. While it's not a real redirect, it shares - enough with redirects for the function to still be useful. -- Fixed securecookie for pypy. -- Fixed `ValueErrors` being raised on calls to `best_match` on - `MIMEAccept` objects when invalid user data was supplied. -- Deprecated `werkzeug.contrib.kickstart` and `werkzeug.contrib.testtools` -- URL routing now can be passed the URL arguments to keep them for - redirects. In the future matching on URL arguments might also be - possible. -- Header encoding changed from utf-8 to latin1 to support a port to - Python 3. Bytestrings passed to the object stay untouched which - makes it possible to have utf-8 cookies. This is a part where - the Python 3 version will later change in that it will always - operate on latin1 values. -- Fixed a bug in the form parser that caused the last character to - be dropped off if certain values in multipart data are used. -- Multipart parser now looks at the part-individual content type - header to override the global charset. -- Introduced mimetype and mimetype_params attribute for the file - storage object. -- Changed FileStorage filename fallback logic to skip special filenames - that Python uses for marking special files like stdin. -- Introduced more HTTP exception classes. -- `call_on_close` now can be used as a decorator. -- Support for redis as cache backend. -- Added `BaseRequest.scheme`. -- Support for the RFC 5789 PATCH method. -- New custom routing parser and better ordering. -- Removed support for `is_behind_proxy`. Use a WSGI middleware - instead that rewrites the `REMOTE_ADDR` according to your setup. - Also see the :class:`werkzeug.contrib.fixers.ProxyFix` for - a drop-in replacement. -- Added cookie forging support to the test client. -- Added support for host based matching in the routing system. -- Switched from the default 'ignore' to the better 'replace' - unicode error handling mode. -- The builtin server now adds a function named 'werkzeug.server.shutdown' - into the WSGI env to initiate a shutdown. This currently only works - in Python 2.6 and later. -- Headers are now assumed to be latin1 for better compatibility with - Python 3 once we have support. -- Added :func:`werkzeug.security.safe_join`. -- Added `accept_json` property analogous to `accept_html` on the - :class:`werkzeug.datastructures.MIMEAccept`. -- :func:`werkzeug.utils.import_string` now fails with much better - error messages that pinpoint to the problem. -- Added support for parsing of the `If-Range` header - (:func:`werkzeug.http.parse_if_range_header` and - :class:`werkzeug.datastructures.IfRange`). -- Added support for parsing of the `Range` header - (:func:`werkzeug.http.parse_range_header` and - :class:`werkzeug.datastructures.Range`). -- Added support for parsing of the `Content-Range` header of responses - and provided an accessor object for it - (:func:`werkzeug.http.parse_content_range_header` and - :class:`werkzeug.datastructures.ContentRange`). - -Version 0.6.2 -------------- - -(bugfix release, released on April 23th 2010) - -- renamed the attribute `implicit_seqence_conversion` attribute of the - request object to `implicit_sequence_conversion`. - -Version 0.6.1 -------------- - -(bugfix release, released on April 13th 2010) - -- heavily improved local objects. Should pick up standalone greenlet - builds now and support proxies to free callables as well. There is - also a stacked local now that makes it possible to invoke the same - application from within itself by pushing current request/response - on top of the stack. -- routing build method will also build non-default method rules properly - if no method is provided. -- added proper IPv6 support for the builtin server. -- windows specific filesystem session store fixes. - (should now be more stable under high concurrency) -- fixed a `NameError` in the session system. -- fixed a bug with empty arguments in the werkzeug.script system. -- fixed a bug where log lines will be duplicated if an application uses - :meth:`logging.basicConfig` (#499) -- added secure password hashing and checking functions. -- `HEAD` is now implicitly added as method in the routing system if - `GET` is present. Not doing that was considered a bug because often - code assumed that this is the case and in web servers that do not - normalize `HEAD` to `GET` this could break `HEAD` requests. -- the script support can start SSL servers now. - -Version 0.6 ------------ - -Released on Feb 19th 2010, codename Hammer. - -- removed pending deprecations -- sys.path is now printed from the testapp. -- fixed an RFC 2068 incompatibility with cookie value quoting. -- the :class:`FileStorage` now gives access to the multipart headers. -- `cached_property.writeable` has been deprecated. -- :meth:`MapAdapter.match` now accepts a `return_rule` keyword argument - that returns the matched `Rule` instead of just the `endpoint` -- :meth:`routing.Map.bind_to_environ` raises a more correct error message - now if the map was bound to an invalid WSGI environment. -- added support for SSL to the builtin development server. -- Response objects are no longer modified in place when they are evaluated - as WSGI applications. For backwards compatibility the `fix_headers` - function is still called in case it was overridden. - You should however change your application to use `get_wsgi_headers` if - you need header modifications before responses are sent as the backwards - compatibility support will go away in future versions. -- :func:`append_slash_redirect` no longer requires the QUERY_STRING to be - in the WSGI environment. -- added :class:`~werkzeug.contrib.wrappers.DynamicCharsetResponseMixin` -- added :class:`~werkzeug.contrib.wrappers.DynamicCharsetRequestMixin` -- added :attr:`BaseRequest.url_charset` -- request and response objects have a default `__repr__` now. -- builtin data structures can be pickled now. -- the form data parser will now look at the filename instead the - content type to figure out if it should treat the upload as regular - form data or file upload. This fixes a bug with Google Chrome. -- improved performance of `make_line_iter` and the multipart parser - for binary uploads. -- fixed :attr:`~werkzeug.BaseResponse.is_streamed` -- fixed a path quoting bug in `EnvironBuilder` that caused PATH_INFO and - SCRIPT_NAME to end up in the environ unquoted. -- :meth:`werkzeug.BaseResponse.freeze` now sets the content length. -- for unknown HTTP methods the request stream is now always limited - instead of being empty. This makes it easier to implement DAV - and other protocols on top of Werkzeug. -- added :meth:`werkzeug.MIMEAccept.best_match` -- multi-value test-client posts from a standard dictionary are now - supported. Previously you had to use a multi dict. -- rule templates properly work with submounts, subdomains and - other rule factories now. -- deprecated non-silent usage of the :class:`werkzeug.LimitedStream`. -- added support for IRI handling to many parts of Werkzeug. -- development server properly logs to the werkzeug logger now. -- added :func:`werkzeug.extract_path_info` -- fixed a querystring quoting bug in :func:`url_fix` -- added `fallback_mimetype` to :class:`werkzeug.SharedDataMiddleware`. -- deprecated :meth:`BaseResponse.iter_encoded`'s charset parameter. -- added :meth:`BaseResponse.make_sequence`, - :attr:`BaseResponse.is_sequence` and - :meth:`BaseResponse._ensure_sequence`. -- added better __repr__ of :class:`werkzeug.Map` -- `import_string` accepts unicode strings as well now. -- development server doesn't break on double slashes after the host name. -- better `__repr__` and `__str__` of - :exc:`werkzeug.exceptions.HTTPException` -- test client works correctly with multiple cookies now. -- the :class:`werkzeug.routing.Map` now has a class attribute with - the default converter mapping. This helps subclasses to override - the converters without passing them to the constructor. -- implemented :class:`OrderedMultiDict` -- improved the session support for more efficient session storing - on the filesystem. Also added support for listing of sessions - currently stored in the filesystem session store. -- werkzeug no longer utilizes the Python time module for parsing - which means that dates in a broader range can be parsed. -- the wrappers have no class attributes that make it possible to - swap out the dict and list types it uses. -- werkzeug debugger should work on the appengine dev server now. -- the URL builder supports dropping of unexpected arguments now. - Previously they were always appended to the URL as query string. -- profiler now writes to the correct stream. - -Version 0.5.1 -------------- -(bugfix release for 0.5, released on July 9th 2009) - -- fixed boolean check of :class:`FileStorage` -- url routing system properly supports unicode URL rules now. -- file upload streams no longer have to provide a truncate() - method. -- implemented :meth:`BaseRequest._form_parsing_failed`. -- fixed #394 -- :meth:`ImmutableDict.copy`, :meth:`ImmutableMultiDict.copy` and - :meth:`ImmutableTypeConversionDict.copy` return mutable shallow - copies. -- fixed a bug with the `make_runserver` script action. -- :meth:`MultiDict.items` and :meth:`MutiDict.iteritems` now accept an - argument to return a pair for each value of each key. -- the multipart parser works better with hand-crafted multipart - requests now that have extra newlines added. This fixes a bug - with setuptools uploads not handled properly (#390) -- fixed some minor bugs in the atom feed generator. -- fixed a bug with client cookie header parsing being case sensitive. -- fixed a not-working deprecation warning. -- fixed package loading for :class:`SharedDataMiddleware`. -- fixed a bug in the secure cookie that made server-side expiration - on servers with a local time that was not set to UTC impossible. -- fixed console of the interactive debugger. - - -Version 0.5 ------------ - -Released on April 24th, codename Schlagbohrer. - -- requires Python 2.4 now -- fixed a bug in :class:`~contrib.IterIO` -- added :class:`MIMEAccept` and :class:`CharsetAccept` that work like the - regular :class:`Accept` but have extra special normalization for mimetypes - and charsets and extra convenience methods. -- switched the serving system from wsgiref to something homebrew. -- the :class:`Client` now supports cookies. -- added the :mod:`~werkzeug.contrib.fixers` module with various - fixes for webserver bugs and hosting setup side-effects. -- added :mod:`werkzeug.contrib.wrappers` -- added :func:`is_hop_by_hop_header` -- added :func:`is_entity_header` -- added :func:`remove_hop_by_hop_headers` -- added :func:`pop_path_info` -- added :func:`peek_path_info` -- added :func:`wrap_file` and :class:`FileWrapper` -- moved `LimitedStream` from the contrib package into the regular - werkzeug one and changed the default behavior to raise exceptions - rather than stopping without warning. The old class will stick in - the module until 0.6. -- implemented experimental multipart parser that replaces the old CGI hack. -- added :func:`dump_options_header` and :func:`parse_options_header` -- added :func:`quote_header_value` and :func:`unquote_header_value` -- :func:`url_encode` and :func:`url_decode` now accept a separator - argument to switch between `&` and `;` as pair separator. The magic - switch is no longer in place. -- all form data parsing functions as well as the :class:`BaseRequest` - object have parameters (or attributes) to limit the number of - incoming bytes (either totally or per field). -- added :class:`LanguageAccept` -- request objects are now enforced to be read only for all collections. -- added many new collection classes, refactored collections in general. -- test support was refactored, semi-undocumented `werkzeug.test.File` - was replaced by :class:`werkzeug.FileStorage`. -- :class:`EnvironBuilder` was added and unifies the previous distinct - :func:`create_environ`, :class:`Client` and - :meth:`BaseRequest.from_values`. They all work the same now which - is less confusing. -- officially documented imports from the internal modules as undefined - behavior. These modules were never exposed as public interfaces. -- removed `FileStorage.__len__` which previously made the object - falsy for browsers not sending the content length which all browsers - do. -- :class:`SharedDataMiddleware` uses `wrap_file` now and has a - configurable cache timeout. -- added :class:`CommonRequestDescriptorsMixin` -- added :attr:`CommonResponseDescriptorsMixin.mimetype_params` -- added :mod:`werkzeug.contrib.lint` -- added `passthrough_errors` to `run_simple`. -- added `secure_filename` -- added :func:`make_line_iter` -- :class:`MultiDict` copies now instead of revealing internal - lists to the caller for `getlist` and iteration functions that - return lists. -- added :attr:`follow_redirect` to the :func:`open` of :class:`Client`. -- added support for `extra_files` in - :func:`~werkzeug.script.make_runserver` - -Version 0.4.1 -------------- - -(Bugfix release, released on January 11th 2009) - -- `werkzeug.contrib.cache.Memcached` accepts now objects that - implement the memcache.Client interface as alternative to a list of - strings with server addresses. - There is also now a `GAEMemcachedCache` that connects to the Google - appengine cache. -- explicitly convert secret keys to bytestrings now because Python - 2.6 no longer does that. -- `url_encode` and all interfaces that call it, support ordering of - options now which however is disabled by default. -- the development server no longer resolves the addresses of clients. -- Fixed a typo in `werkzeug.test` that broke `File`. -- `Map.bind_to_environ` uses the `Host` header now if available. -- Fixed `BaseCache.get_dict` (#345) -- `werkzeug.test.Client` can now run the application buffered in which - case the application is properly closed automatically. -- Fixed `Headers.set` (#354). Caused header duplication before. -- Fixed `Headers.pop` (#349). default parameter was not properly - handled. -- Fixed UnboundLocalError in `create_environ` (#351) -- `Headers` is more compatible with wsgiref now. -- `Template.render` accepts multidicts now. -- dropped support for Python 2.3 - -Version 0.4 ------------ - -Released on November 23rd 2008, codename Schraubenzieher. - -- `Client` supports an empty `data` argument now. -- fixed a bug in `Response.application` that made it impossible to use it - as method decorator. -- the session system should work on appengine now -- the secure cookie works properly in load balanced environments with - different cpu architectures now. -- `CacheControl.no_cache` and `CacheControl.private` behavior changed to - reflect the possibilities of the HTTP RFC. Setting these attributes to - `None` or `True` now sets the value to "the empty value". - More details in the documentation. -- fixed `werkzeug.contrib.atom.AtomFeed.__call__`. (#338) -- `BaseResponse.make_conditional` now always returns `self`. Previously - it didn't for post requests and such. -- fixed a bug in boolean attribute handling of `html` and `xhtml`. -- added graceful error handling to the debugger pastebin feature. -- added a more list like interface to `Headers` (slicing and indexing - works now) -- fixed a bug with the `__setitem__` method of `Headers` that didn't - properly remove all keys on replacing. -- added `remove_entity_headers` which removes all entity headers from - a list of headers (or a `Headers` object) -- the responses now automatically call `remove_entity_headers` if the - status code is 304. -- fixed a bug with `Href` query parameter handling. Previously the last - item of a call to `Href` was not handled properly if it was a dict. -- headers now support a `pop` operation to better work with environ - properties. - - -Version 0.3.1 -------------- - -(bugfix release, released on June 24th 2008) - -- fixed a security problem with `werkzeug.contrib.SecureCookie`. - More details available in the `release announcement`_. - -.. _release announcement: http://lucumr.pocoo.org/cogitations/2008/06/24/werkzeug-031-released/ - -Version 0.3 ------------ - -Released on June 14th 2008, codename EUR325CAT6. - -- added support for redirecting in url routing. -- added `Authorization` and `AuthorizationMixin` -- added `WWWAuthenticate` and `WWWAuthenticateMixin` -- added `parse_list_header` -- added `parse_dict_header` -- added `parse_authorization_header` -- added `parse_www_authenticate_header` -- added `_get_current_object` method to `LocalProxy` objects -- added `parse_form_data` -- `MultiDict`, `CombinedMultiDict`, `Headers`, and `EnvironHeaders` raise - special key errors now that are subclasses of `BadRequest` so if you - don't catch them they give meaningful HTTP responses. -- added support for alternative encoding error handling and the new - `HTTPUnicodeError` which (if not caught) behaves like a `BadRequest`. -- added `BadRequest.wrap`. -- added ETag support to the SharedDataMiddleware and added an option - to disable caching. -- fixed `is_xhr` on the request objects. -- fixed error handling of the url adapter's `dispatch` method. (#318) -- fixed bug with `SharedDataMiddleware`. -- fixed `Accept.values`. -- `EnvironHeaders` contain content-type and content-length now -- `url_encode` treats lists and tuples in dicts passed to it as multiple - values for the same key so that one doesn't have to pass a `MultiDict` - to the function. -- added `validate_arguments` -- added `BaseRequest.application` -- improved Python 2.3 support -- `run_simple` accepts `use_debugger` and `use_evalex` parameters now, - like the `make_runserver` factory function from the script module. -- the `environ_property` is now read-only by default -- it's now possible to initialize requests as "shallow" requests which - causes runtime errors if the request object tries to consume the - input stream. - - -Version 0.2 ------------ - -Released Feb 14th 2008, codename Faustkeil. - -- Added `AnyConverter` to the routing system. -- Added `werkzeug.contrib.securecookie` -- Exceptions have a ``get_response()`` method that return a response object -- fixed the path ordering bug (#293), thanks Thomas Johansson -- `BaseReporterStream` is now part of the werkzeug contrib module. From - Werkzeug 0.3 onwards you will have to import it from there. -- added `DispatcherMiddleware`. -- `RequestRedirect` is now a subclass of `HTTPException` and uses a - 301 status code instead of 302. -- `url_encode` and `url_decode` can optionally treat keys as unicode strings - now, too. -- `werkzeug.script` has a different caller format for boolean arguments now. -- renamed `lazy_property` to `cached_property`. -- added `import_string`. -- added is_* properties to request objects. -- added `empty()` method to routing rules. -- added `werkzeug.contrib.profiler`. -- added `extends` to `Headers`. -- added `dump_cookie` and `parse_cookie`. -- added `as_tuple` to the `Client`. -- added `werkzeug.contrib.testtools`. -- added `werkzeug.unescape` -- added `BaseResponse.freeze` -- added `werkzeug.contrib.atom` -- the HTTPExceptions accept an argument `description` now which overrides the - default description. -- the `MapAdapter` has a default for path info now. If you use - `bind_to_environ` you don't have to pass the path later. -- the wsgiref subclass werkzeug uses for the dev server does not use direct - sys.stderr logging any more but a logger called "werkzeug". -- implemented `Href`. -- implemented `find_modules` -- refactored request and response objects into base objects, mixins and - full featured subclasses that implement all mixins. -- added simple user agent parser -- werkzeug's routing raises `MethodNotAllowed` now if it matches a - rule but for a different method. -- many fixes and small improvements - - -Version 0.1 ------------ - -Released on Dec 9th 2007, codename Wictorinoxger. - -- Initial release diff --git a/CHANGES.rst b/CHANGES.rst new file mode 100644 index 000000000..18e68af3c --- /dev/null +++ b/CHANGES.rst @@ -0,0 +1,2259 @@ +.. currentmodule:: werkzeug + +Version 2.2.2 +------------- + +Released 2022-08-08 + +- Fix router to restore the 2.1 ``strict_slashes == False`` behaviour + whereby leaf-requests match branch rules and vice + versa. :pr:`2489` +- Fix router to identify invalid rules rather than hang parsing them, + and to correctly parse ``/`` within converter arguments. :pr:`2489` +- Update subpackage imports in :mod:`werkzeug.routing` to use the + ``import as`` syntax for explicitly re-exporting public attributes. + :pr:`2493` +- Parsing of some invalid header characters is more robust. :pr:`2494` +- When starting the development server, a warning not to use it in a + production deployment is always shown. :issue:`2480` +- ``LocalProxy.__wrapped__`` is always set to the wrapped object when + the proxy is unbound, fixing an issue in doctest that would cause it + to fail. :issue:`2485` +- Address one ``ResourceWarning`` related to the socket used by + ``run_simple``. :issue:`2421` + + +Version 2.2.1 +------------- + +Released 2022-07-27 + +- Fix router so that ``/path/`` will match a rule ``/path`` if strict + slashes mode is disabled for the rule. :issue:`2467` +- Fix router so that partial part matches are not allowed + i.e. ``/2df`` does not match ``/``. :pr:`2470` +- Fix router static part weighting, so that simpler routes are matched + before more complex ones. :issue:`2471` +- Restore ``ValidationError`` to be importable from + ``werkzeug.routing``. :issue:`2465` + + +Version 2.2.0 +------------- + +Released 2022-07-23 + +- Deprecated ``get_script_name``, ``get_query_string``, + ``peek_path_info``, ``pop_path_info``, and + ``extract_path_info``. :pr:`2461` +- Remove previously deprecated code. :pr:`2461` +- Add MarkupSafe as a dependency and use it to escape values when + rendering HTML. :issue:`2419` +- Added the ``werkzeug.debug.preserve_context`` mechanism for + restoring context-local data for a request when running code in the + debug console. :pr:`2439` +- Fix compatibility with Python 3.11 by ensuring that ``end_lineno`` + and ``end_col_offset`` are present on AST nodes. :issue:`2425` +- Add a new faster matching router based on a state + machine. :pr:`2433` +- Fix branch leaf path masking branch paths when strict-slashes is + disabled. :issue:`1074` +- Names within options headers are always converted to lowercase. This + matches :rfc:`6266` that the case is not relevant. :issue:`2442` +- ``AnyConverter`` validates the value passed for it when building + URLs. :issue:`2388` +- The debugger shows enhanced error locations in tracebacks in Python + 3.11. :issue:`2407` +- Added Sans-IO ``is_resource_modified`` and ``parse_cookie`` functions + based on WSGI versions. :issue:`2408` +- Added Sans-IO ``get_content_length`` function. :pr:`2415` +- Don't assume a mimetype for test responses. :issue:`2450` +- Type checking ``FileStorage`` accepts ``os.PathLike``. :pr:`2418` + + +Version 2.1.2 +------------- + +Released 2022-04-28 + +- The development server does not set ``Transfer-Encoding: chunked`` + for 1xx, 204, 304, and HEAD responses. :issue:`2375` +- Response HTML for exceptions and redirects starts with + ```` and ````. :issue:`2390` +- Fix ability to set some ``cache_control`` attributes to ``False``. + :issue:`2379` +- Disable ``keep-alive`` connections in the development server, which + are not supported sufficiently by Python's ``http.server``. + :issue:`2397` + + +Version 2.1.1 +------------- + +Released 2022-04-01 + +- ``ResponseCacheControl.s_maxage`` converts its value to an int, like + ``max_age``. :issue:`2364` + + +Version 2.1.0 +------------- + +Released 2022-03-28 + +- Drop support for Python 3.6. :pr:`2277` +- Using gevent or eventlet requires greenlet>=1.0 or PyPy>=7.3.7. + ``werkzeug.locals`` and ``contextvars`` will not work correctly with + older versions. :pr:`2278` +- Remove previously deprecated code. :pr:`2276` + + - Remove the non-standard ``shutdown`` function from the WSGI + environ when running the development server. See the docs for + alternatives. + - Request and response mixins have all been merged into the + ``Request`` and ``Response`` classes. + - The user agent parser and the ``useragents`` module is removed. + The ``user_agent`` module provides an interface that can be + subclassed to add a parser, such as ua-parser. By default it + only stores the whole string. + - The test client returns ``TestResponse`` instances and can no + longer be treated as a tuple. All data is available as + properties on the response. + - Remove ``locals.get_ident`` and related thread-local code from + ``locals``, it no longer makes sense when moving to a + contextvars-based implementation. + - Remove the ``python -m werkzeug.serving`` CLI. + - The ``has_key`` method on some mapping datastructures; use + ``key in data`` instead. + - ``Request.disable_data_descriptor`` is removed, pass + ``shallow=True`` instead. + - Remove the ``no_etag`` parameter from ``Response.freeze()``. + - Remove the ``HTTPException.wrap`` class method. + - Remove the ``cookie_date`` function. Use ``http_date`` instead. + - Remove the ``pbkdf2_hex``, ``pbkdf2_bin``, and ``safe_str_cmp`` + functions. Use equivalents in ``hashlib`` and ``hmac`` modules + instead. + - Remove the ``Href`` class. + - Remove the ``HTMLBuilder`` class. + - Remove the ``invalidate_cached_property`` function. Use + ``del obj.attr`` instead. + - Remove ``bind_arguments`` and ``validate_arguments``. Use + :meth:`Signature.bind` and :func:`inspect.signature` instead. + - Remove ``detect_utf_encoding``, it's built-in to ``json.loads``. + - Remove ``format_string``, use :class:`string.Template` instead. + - Remove ``escape`` and ``unescape``. Use MarkupSafe instead. + +- The ``multiple`` parameter of ``parse_options_header`` is + deprecated. :pr:`2357` +- Rely on :pep:`538` and :pep:`540` to handle decoding file names + with the correct filesystem encoding. The ``filesystem`` module is + removed. :issue:`1760` +- Default values passed to ``Headers`` are validated the same way + values added later are. :issue:`1608` +- Setting ``CacheControl`` int properties, such as ``max_age``, will + convert the value to an int. :issue:`2230` +- Always use ``socket.fromfd`` when restarting the dev server. + :pr:`2287` +- When passing a dict of URL values to ``Map.build``, list values do + not filter out ``None`` or collapse to a single value. Passing a + ``MultiDict`` does collapse single items. This undoes a previous + change that made it difficult to pass a list, or ``None`` values in + a list, to custom URL converters. :issue:`2249` +- ``run_simple`` shows instructions for dealing with "address already + in use" errors, including extra instructions for macOS. :pr:`2321` +- Extend list of characters considered always safe in URLs based on + :rfc:`3986`. :issue:`2319` +- Optimize the stat reloader to avoid watching unnecessary files in + more cases. The watchdog reloader is still recommended for + performance and accuracy. :issue:`2141` +- The development server uses ``Transfer-Encoding: chunked`` for + streaming responses when it is configured for HTTP/1.1. + :issue:`2090, 1327`, :pr:`2091` +- The development server uses HTTP/1.1, which enables keep-alive + connections and chunked streaming responses, when ``threaded`` or + ``processes`` is enabled. :pr:`2323` +- ``cached_property`` works for classes with ``__slots__`` if a + corresponding ``_cache_{name}`` slot is added. :pr:`2332` +- Refactor the debugger traceback formatter to use Python's built-in + ``traceback`` module as much as possible. :issue:`1753` +- The ``TestResponse.text`` property is a shortcut for + ``r.get_data(as_text=True)``, for convenient testing against text + instead of bytes. :pr:`2337` +- ``safe_join`` ensures that the path remains relative if the trusted + directory is the empty string. :pr:`2349` +- Percent-encoded newlines (``%0a``), which are decoded by WSGI + servers, are considered when routing instead of terminating the + match early. :pr:`2350` +- The test client doesn't set duplicate headers for ``CONTENT_LENGTH`` + and ``CONTENT_TYPE``. :pr:`2348` +- ``append_slash_redirect`` handles ``PATH_INFO`` with internal + slashes. :issue:`1972`, :pr:`2338` +- The default status code for ``append_slash_redirect`` is 308 instead + of 301. This preserves the request body, and matches a previous + change to ``strict_slashes`` in routing. :issue:`2351` +- Fix ``ValueError: I/O operation on closed file.`` with the test + client when following more than one redirect. :issue:`2353` +- ``Response.autocorrect_location_header`` is disabled by default. + The ``Location`` header URL will remain relative, and exclude the + scheme and domain, by default. :issue:`2352` +- ``Request.get_json()`` will raise a 400 ``BadRequest`` error if the + ``Content-Type`` header is not ``application/json``. This makes a + very common source of confusion more visible. :issue:`2339` + + +Version 2.0.3 +------------- + +Released 2022-02-07 + +- ``ProxyFix`` supports IPv6 addresses. :issue:`2262` +- Type annotation for ``Response.make_conditional``, + ``HTTPException.get_response``, and ``Map.bind_to_environ`` accepts + ``Request`` in addition to ``WSGIEnvironment`` for the first + parameter. :pr:`2290` +- Fix type annotation for ``Request.user_agent_class``. :issue:`2273` +- Accessing ``LocalProxy.__class__`` and ``__doc__`` on an unbound + proxy returns the fallback value instead of a method object. + :issue:`2188` +- Redirects with the test client set ``RAW_URI`` and ``REQUEST_URI`` + correctly. :issue:`2151` + + +Version 2.0.2 +------------- + +Released 2021-10-05 + +- Handle multiple tokens in ``Connection`` header when routing + WebSocket requests. :issue:`2131` +- Set the debugger pin cookie secure flag when on https. :pr:`2150` +- Fix type annotation for ``MultiDict.update`` to accept iterable + values :pr:`2142` +- Prevent double encoding of redirect URL when ``merge_slash=True`` + for ``Rule.match``. :issue:`2157` +- ``CombinedMultiDict.to_dict`` with ``flat=False`` considers all + component dicts when building value lists. :issue:`2189` +- ``send_file`` only sets a detected ``Content-Encoding`` if + ``as_attachment`` is disabled to avoid browsers saving + decompressed ``.tar.gz`` files. :issue:`2149` +- Fix type annotations for ``TypeConversionDict.get`` to not return an + ``Optional`` value if both ``default`` and ``type`` are not + ``None``. :issue:`2169` +- Fix type annotation for routing rule factories to accept + ``Iterable[RuleFactory]`` instead of ``Iterable[Rule]`` for the + ``rules`` parameter. :issue:`2183` +- Add missing type annotation for ``FileStorage.__getattr__`` + :issue:`2155` +- The debugger pin cookie is set with ``SameSite`` set to ``Strict`` + instead of ``None`` to be compatible with modern browser security. + :issue:`2156` +- Type annotations use ``IO[bytes]`` and ``IO[str]`` instead of + ``BinaryIO`` and ``TextIO`` for wider type compatibility. + :issue:`2130` +- Ad-hoc TLS certs are generated with SAN matching CN. :issue:`2158` +- Fix memory usage for locals when using Python 3.6 or pre 0.4.17 + greenlet versions. :pr:`2212` +- Fix type annotation in ``CallbackDict``, because it is not + utilizing a bound TypeVar. :issue:`2235` +- Fix setting CSP header options on the response. :pr:`2237` +- Fix an issue with with the interactive debugger where lines would + not expand on click for very long tracebacks. :pr:`2239` +- The interactive debugger handles displaying an exception that does + not have a traceback, such as from ``ProcessPoolExecutor``. + :issue:`2217` + + +Version 2.0.1 +------------- + +Released 2021-05-17 + +- Fix type annotation for ``send_file`` ``max_age`` callable. Don't + pass ``pathlib.Path`` to ``max_age``. :issue:`2119` +- Mark top-level names as exported so type checking understands + imports in user projects. :issue:`2122` +- Fix some types that weren't available in Python 3.6.0. :issue:`2123` +- ``cached_property`` is generic over its return type, properties + decorated with it report the correct type. :issue:`2113` +- Fix multipart parsing bug when boundary contains special regex + characters. :issue:`2125` +- Type checking understands that calling ``headers.get`` with a string + default will always return a string. :issue:`2128` +- If ``HTTPException.description`` is not a string, + ``get_description`` will convert it to a string. :issue:`2115` + + +Version 2.0.0 +------------- + +Released 2021-05-11 + +- Drop support for Python 2 and 3.5. :pr:`1693` +- Deprecate :func:`utils.format_string`, use :class:`string.Template` + instead. :issue:`1756` +- Deprecate :func:`utils.bind_arguments` and + :func:`utils.validate_arguments`, use :meth:`Signature.bind` and + :func:`inspect.signature` instead. :issue:`1757` +- Deprecate :class:`utils.HTMLBuilder`. :issue:`1761` +- Deprecate :func:`utils.escape` and :func:`utils.unescape`, use + MarkupSafe instead. :issue:`1758` +- Deprecate the undocumented ``python -m werkzeug.serving`` CLI. + :issue:`1834` +- Deprecate the ``environ["werkzeug.server.shutdown"]`` function + that is available when running the development server. :issue:`1752` +- Deprecate the ``useragents`` module and the built-in user agent + parser. Use a dedicated parser library instead by subclassing + ``user_agent.UserAgent`` and setting ``Request.user_agent_class``. + :issue:`2078` +- Remove the unused, internal ``posixemulation`` module. :issue:`1759` +- All ``datetime`` values are timezone-aware with + ``tzinfo=timezone.utc``. This applies to anything using + ``http.parse_date``: ``Request.date``, ``.if_modified_since``, + ``.if_unmodified_since``; ``Response.date``, ``.expires``, + ``.last_modified``, ``.retry_after``; ``parse_if_range_header``, and + ``IfRange.date``. When comparing values, the other values must also + be aware, or these values must be made naive. When passing + parameters or setting attributes, naive values are still assumed to + be in UTC. :pr:`2040` +- Merge all request and response wrapper mixin code into single + ``Request`` and ``Response`` classes. Using the mixin classes is no + longer necessary and will show a deprecation warning. Checking + ``isinstance`` or ``issubclass`` against ``BaseRequest`` and + ``BaseResponse`` will show a deprecation warning and check against + ``Request`` or ``Response`` instead. :issue:`1963` +- JSON support no longer uses simplejson if it's installed. To use + another JSON module, override ``Request.json_module`` and + ``Response.json_module``. :pr:`1766` +- ``Response.get_json()`` no longer caches the result, and the + ``cache`` parameter is removed. :issue:`1698` +- ``Response.freeze()`` generates an ``ETag`` header if one is not + set. The ``no_etag`` parameter (which usually wasn't visible + anyway) is no longer used. :issue:`1963` +- Add a ``url_scheme`` argument to :meth:`~routing.MapAdapter.build` + to override the bound scheme. :pr:`1721` +- Passing an empty list as a query string parameter to ``build()`` + won't append an unnecessary ``?``. Also drop any number of ``None`` + items in a list. :issue:`1992` +- When passing a ``Headers`` object to a test client method or + ``EnvironBuilder``, multiple values for a key are joined into one + comma separated value. This matches the HTTP spec on multi-value + headers. :issue:`1655` +- Setting ``Response.status`` and ``status_code`` uses identical + parsing and error checking. :issue:`1658`, :pr:`1728` +- ``MethodNotAllowed`` and ``RequestedRangeNotSatisfiable`` take a + ``response`` kwarg, consistent with other HTTP errors. :pr:`1748` +- The response generated by :exc:`~exceptions.Unauthorized` produces + one ``WWW-Authenticate`` header per value in ``www_authenticate``, + rather than joining them into a single value, to improve + interoperability with browsers and other clients. :pr:`1755` +- If ``parse_authorization_header`` can't decode the header value, it + returns ``None`` instead of raising a ``UnicodeDecodeError``. + :issue:`1816` +- The debugger no longer uses jQuery. :issue:`1807` +- The test client includes the query string in ``REQUEST_URI`` and + ``RAW_URI``. :issue:`1781` +- Switch the parameter order of ``default_stream_factory`` to match + the order used when calling it. :pr:`1085` +- Add ``send_file`` function to generate a response that serves a + file. Adapted from Flask's implementation. :issue:`265`, :pr:`1850` +- Add ``send_from_directory`` function to safely serve an untrusted + path within a trusted directory. Adapted from Flask's + implementation. :issue:`1880` +- ``send_file`` takes ``download_name``, which is passed even if + ``as_attachment=False`` by using ``Content-Disposition: inline``. + ``download_name`` replaces Flask's ``attachment_filename``. + :issue:`1869` +- ``send_file`` sets ``conditional=True`` and ``max_age=None`` by + default. ``Cache-Control`` is set to ``no-cache`` if ``max_age`` is + not set, otherwise ``public``. This tells browsers to validate + conditional requests instead of using a timed cache. + ``max_age=None`` replaces Flask's ``cache_timeout=43200``. + :issue:`1882` +- ``send_file`` can be called with ``etag="string"`` to set a custom + ETag instead of generating one. ``etag`` replaces Flask's + ``add_etags``. :issue:`1868` +- ``send_file`` sets the ``Content-Encoding`` header if an encoding is + returned when guessing ``mimetype`` from ``download_name``. + :pr:`3896` +- Update the defaults used by ``generate_password_hash``. Increase + PBKDF2 iterations to 260000 from 150000. Increase salt length to 16 + from 8. Use ``secrets`` module to generate salt. :pr:`1935` +- The reloader doesn't crash if ``sys.stdin`` is somehow ``None``. + :pr:`1915` +- Add arguments to ``delete_cookie`` to match ``set_cookie`` and the + attributes modern browsers expect. :pr:`1889` +- ``utils.cookie_date`` is deprecated, use ``utils.http_date`` + instead. The value for ``Set-Cookie expires`` is no longer "-" + delimited. :pr:`2040` +- Use ``request.headers`` instead of ``request.environ`` to look up + header attributes. :pr:`1808` +- The test ``Client`` request methods (``client.get``, etc.) always + return an instance of ``TestResponse``. In addition to the normal + behavior of ``Response``, this class provides ``request`` with the + request that produced the response, and ``history`` to track + intermediate responses when ``follow_redirects`` is used. + :issue:`763, 1894` +- The test ``Client`` request methods takes an ``auth`` parameter to + add an ``Authorization`` header. It can be an ``Authorization`` + object or a ``(username, password)`` tuple for ``Basic`` auth. + :pr:`1809` +- Calling ``response.close()`` on a response from the test ``Client`` + will close the request input stream. This matches file behavior + and can prevent a ``ResourceWarning`` in some cases. :issue:`1785` +- ``EnvironBuilder.from_environ`` decodes values encoded for WSGI, to + avoid double encoding the new values. :pr:`1959` +- The default stat reloader will watch Python files under + non-system/virtualenv ``sys.path`` entries, which should contain + most user code. It will also watch all Python files under + directories given in ``extra_files``. :pr:`1945` +- The reloader ignores ``__pycache__`` directories again. :pr:`1945` +- ``run_simple`` takes ``exclude_patterns`` a list of ``fnmatch`` + patterns that will not be scanned by the reloader. :issue:`1333` +- Cookie names are no longer unquoted. This was against :rfc:`6265` + and potentially allowed setting ``__Secure`` prefixed cookies. + :pr:`1965` +- Fix some word matches for user agent platform when the word can be a + substring. :issue:`1923` +- The development server logs ignored SSL errors. :pr:`1967` +- Temporary files for form data are opened in ``rb+`` instead of + ``wb+`` mode for better compatibility with some libraries. + :issue:`1961` +- Use SHA-1 instead of MD5 for generating ETags and the debugger pin, + and in some tests. MD5 is not available in some environments, such + as FIPS 140. This may invalidate some caches since the ETag will be + different. :issue:`1897` +- Add ``Cross-Origin-Opener-Policy`` and + ``Cross-Origin-Embedder-Policy`` response header properties. + :pr:`2008` +- ``run_simple`` tries to show a valid IP address when binding to all + addresses, instead of ``0.0.0.0`` or ``::``. It also warns about not + running the development server in production in this case. + :issue:`1964` +- Colors in the development server log are displayed if Colorama is + installed on Windows. For all platforms, style support no longer + requires Click. :issue:`1832` +- A range request for an empty file (or other data with length 0) will + return a 200 response with the empty file instead of a 416 error. + :issue:`1937` +- New sans-IO base classes for ``Request`` and ``Response`` have been + extracted to contain all the behavior that is not WSGI or IO + dependent. These are not a public API, they are part of an ongoing + refactor to let ASGI frameworks use Werkzeug. :pr:`2005` +- Parsing ``multipart/form-data`` has been refactored to use sans-io + patterns. This should also make parsing forms with large binary file + uploads significantly faster. :issue:`1788, 875` +- ``LocalProxy`` matches the current Python data model special + methods, including all r-ops, in-place ops, and async. ``__class__`` + is proxied, so the proxy will look like the object in more cases, + including ``isinstance``. Use ``issubclass(type(obj), LocalProxy)`` + to check if an object is actually a proxy. :issue:`1754` +- ``Local`` uses ``ContextVar`` on Python 3.7+ instead of + ``threading.local``. :pr:`1778` +- ``request.values`` does not include ``form`` for GET requests (even + though GET bodies are undefined). This prevents bad caching proxies + from caching form data instead of query strings. :pr:`2037` +- The development server adds the underlying socket to ``environ`` as + ``werkzeug.socket``. This is non-standard and specific to the dev + server, other servers may expose this under their own key. It is + useful for handling a WebSocket upgrade request. :issue:`2052` +- URL matching assumes ``websocket=True`` mode for WebSocket upgrade + requests. :issue:`2052` +- Updated ``UserAgentParser`` to handle more cases. :issue:`1971` +- ``werzeug.DechunkedInput.readinto`` will not read beyond the size of + the buffer. :issue:`2021` +- Fix connection reset when exceeding max content size. :pr:`2051` +- ``pbkdf2_hex``, ``pbkdf2_bin``, and ``safe_str_cmp`` are deprecated. + ``hashlib`` and ``hmac`` provide equivalents. :pr:`2083` +- ``invalidate_cached_property`` is deprecated. Use ``del obj.name`` + instead. :pr:`2084` +- ``Href`` is deprecated. Use ``werkzeug.routing`` instead. + :pr:`2085` +- ``Request.disable_data_descriptor`` is deprecated. Create the + request with ``shallow=True`` instead. :pr:`2085` +- ``HTTPException.wrap`` is deprecated. Create a subclass manually + instead. :pr:`2085` + + +Version 1.0.1 +------------- + +Released 2020-03-31 + +- Make the argument to ``RequestRedirect.get_response`` optional. + :issue:`1718` +- Only allow a single access control allow origin value. :pr:`1723` +- Fix crash when trying to parse a non-existent Content Security + Policy header. :pr:`1731` +- ``http_date`` zero fills years < 1000 to always output four digits. + :issue:`1739` +- Fix missing local variables in interactive debugger console. + :issue:`1746` +- Fix passing file-like objects like ``io.BytesIO`` to + ``FileStorage.save``. :issue:`1733` + + +Version 1.0.0 +------------- + +Released 2020-02-06 + +- Drop support for Python 3.4. (:issue:`1478`) +- Remove code that issued deprecation warnings in version 0.15. + (:issue:`1477`) +- Remove most top-level attributes provided by the ``werkzeug`` + module in favor of direct imports. For example, instead of + ``import werkzeug; werkzeug.url_quote``, do + ``from werkzeug.urls import url_quote``. Install version 0.16 first + to see deprecation warnings while upgrading. :issue:`2`, :pr:`1640` +- Added ``utils.invalidate_cached_property()`` to invalidate cached + properties. (:pr:`1474`) +- Directive keys for the ``Set-Cookie`` response header are not + ignored when parsing the ``Cookie`` request header. This allows + cookies with names such as "expires" and "version". (:issue:`1495`) +- Request cookies are parsed into a ``MultiDict`` to capture all + values for cookies with the same key. ``cookies[key]`` returns the + first value rather than the last. Use ``cookies.getlist(key)`` to + get all values. ``parse_cookie`` also defaults to a ``MultiDict``. + :issue:`1562`, :pr:`1458` +- Add ``charset=utf-8`` to an HTTP exception response's + ``CONTENT_TYPE`` header. (:pr:`1526`) +- The interactive debugger handles outer variables in nested scopes + such as lambdas and comprehensions. :issue:`913`, :issue:`1037`, + :pr:`1532` +- The user agent for Opera 60 on Mac is correctly reported as + "opera" instead of "chrome". :issue:`1556` +- The platform for Crosswalk on Android is correctly reported as + "android" instead of "chromeos". (:pr:`1572`) +- Issue a warning when the current server name does not match the + configured server name. :issue:`760` +- A configured server name with the default port for a scheme will + match the current server name without the port if the current scheme + matches. :pr:`1584` +- :exc:`~exceptions.InternalServerError` has a ``original_exception`` + attribute that frameworks can use to track the original cause of the + error. :pr:`1590` +- Headers are tested for equality independent of the header key case, + such that ``X-Foo`` is the same as ``x-foo``. :pr:`1605` +- :meth:`http.dump_cookie` accepts ``'None'`` as a value for + ``samesite``. :issue:`1549` +- :meth:`~test.Client.set_cookie` accepts a ``samesite`` argument. + :pr:`1705` +- Support the Content Security Policy header through the + `Response.content_security_policy` data structure. :pr:`1617` +- ``LanguageAccept`` will fall back to matching "en" for "en-US" or + "en-US" for "en" to better support clients or translations that + only match at the primary language tag. :issue:`450`, :pr:`1507` +- ``MIMEAccept`` uses MIME parameters for specificity when matching. + :issue:`458`, :pr:`1574` +- If the development server is started with an ``SSLContext`` + configured to verify client certificates, the certificate in PEM + format will be available as ``environ["SSL_CLIENT_CERT"]``. + :pr:`1469` +- ``is_resource_modified`` will run for methods other than ``GET`` and + ``HEAD``, rather than always returning ``False``. :issue:`409` +- ``SharedDataMiddleware`` returns 404 rather than 500 when trying to + access a directory instead of a file with the package loader. The + dependency on setuptools and pkg_resources is removed. + :issue:`1599` +- Add a ``response.cache_control.immutable`` flag. Keep in mind that + browser support for this ``Cache-Control`` header option is still + experimental and may not be implemented. :issue:`1185` +- Optional request log highlighting with the development server is + handled by Click instead of termcolor. :issue:`1235` +- Optional ad-hoc TLS support for the development server is handled + by cryptography instead of pyOpenSSL. :pr:`1555` +- ``FileStorage.save()`` supports ``pathlib`` and :pep:`519` + ``PathLike`` objects. :issue:`1653` +- The debugger security pin is unique in containers managed by Podman. + :issue:`1661` +- Building a URL when ``host_matching`` is enabled takes into account + the current host when there are duplicate endpoints with different + hosts. :issue:`488` +- The ``429 TooManyRequests`` and ``503 ServiceUnavailable`` HTTP + exceptions takes a ``retry_after`` parameter to set the + ``Retry-After`` header. :issue:`1657` +- ``Map`` and ``Rule`` have a ``merge_slashes`` option to collapse + multiple slashes into one, similar to how many HTTP servers behave. + This is enabled by default. :pr:`1286, 1694` +- Add HTTP 103, 208, 306, 425, 506, 508, and 511 to the list of status + codes. :pr:`1678` +- Add ``update``, ``setlist``, and ``setlistdefault`` methods to the + ``Headers`` data structure. ``extend`` method can take ``MultiDict`` + and kwargs. :pr:`1687, 1697` +- The development server accepts paths that start with two slashes, + rather than stripping off the first path segment. :issue:`491` +- Add access control (Cross Origin Request Sharing, CORS) header + properties to the ``Request`` and ``Response`` wrappers. :pr:`1699` +- ``Accept`` values are no longer ordered alphabetically for equal + quality tags. Instead the initial order is preserved. :issue:`1686` +- Added ``Map.lock_class`` attribute for alternative + implementations. :pr:`1702` +- Support matching and building WebSocket rules in the routing system, + for use by async frameworks. :pr:`1709` +- Range requests that span an entire file respond with 206 instead of + 200, to be more compliant with :rfc:`7233`. This may help serving + media to older browsers. :issue:`410, 1704` +- The :class:`~middleware.shared_data.SharedDataMiddleware` default + ``fallback_mimetype`` is ``application/octet-stream``. If a filename + looks like a text mimetype, the ``utf-8`` charset is added to it. + This matches the behavior of :class:`~wrappers.BaseResponse` and + Flask's ``send_file()``. :issue:`1689` + + +Version 0.16.1 +-------------- + +Released 2020-01-27 + +- Fix import location in deprecation messages for subpackages. + :issue:`1663` +- Fix an SSL error on Python 3.5 when the dev server responds with no + content. :issue:`1659` + + +Version 0.16.0 +-------------- + +Released 2019-09-19 + +- Deprecate most top-level attributes provided by the ``werkzeug`` + module in favor of direct imports. The deprecated imports will be + removed in version 1.0. + + For example, instead of ``import werkzeug; werkzeug.url_quote``, do + ``from werkzeug.urls import url_quote``. A deprecation warning will + show the correct import to use. ``werkzeug.exceptions`` and + ``werkzeug.routing`` should also be imported instead of accessed, + but for technical reasons can't show a warning. + + :issue:`2`, :pr:`1640` + + +Version 0.15.6 +-------------- + +Released 2019-09-04 + +- Work around a bug in pip that caused the reloader to fail on + Windows when the script was an entry point. This fixes the issue + with Flask's `flask run` command failing with "No module named + Scripts\flask". :issue:`1614` +- ``ProxyFix`` trusts the ``X-Forwarded-Proto`` header by default. + :issue:`1630` +- The deprecated ``num_proxies`` argument to ``ProxyFix`` sets + ``x_for``, ``x_proto``, and ``x_host`` to match 0.14 behavior. This + is intended to make intermediate upgrades less disruptive, but the + argument will still be removed in 1.0. :issue:`1630` + + +Version 0.15.5 +-------------- + +Released 2019-07-17 + +- Fix a ``TypeError`` due to changes to ``ast.Module`` in Python 3.8. + :issue:`1551` +- Fix a C assertion failure in debug builds of some Python 2.7 + releases. :issue:`1553` +- :class:`~exceptions.BadRequestKeyError` adds the ``KeyError`` + message to the description if ``e.show_exception`` is set to + ``True``. This is a more secure default than the original 0.15.0 + behavior and makes it easier to control without losing information. + :pr:`1592` +- Upgrade the debugger to jQuery 3.4.1. :issue:`1581` +- Work around an issue in some external debuggers that caused the + reloader to fail. :issue:`1607` +- Work around an issue where the reloader couldn't introspect a + setuptools script installed as an egg. :issue:`1600` +- The reloader will use ``sys.executable`` even if the script is + marked executable, reverting a behavior intended for NixOS + introduced in 0.15. The reloader should no longer cause + ``OSError: [Errno 8] Exec format error``. :issue:`1482`, + :issue:`1580` +- ``SharedDataMiddleware`` safely handles paths with Windows drive + names. :issue:`1589` + + +Version 0.15.4 +-------------- + +Released 2019-05-14 + +- Fix a ``SyntaxError`` on Python 2.7.5. (:issue:`1544`) + + +Version 0.15.3 +-------------- + +Released 2019-05-14 + +- Properly handle multi-line header folding in development server in + Python 2.7. (:issue:`1080`) +- Restore the ``response`` argument to :exc:`~exceptions.Unauthorized`. + (:pr:`1527`) +- :exc:`~exceptions.Unauthorized` doesn't add the ``WWW-Authenticate`` + header if ``www_authenticate`` is not given. (:issue:`1516`) +- The default URL converter correctly encodes bytes to string rather + than representing them with ``b''``. (:issue:`1502`) +- Fix the filename format string in + :class:`~middleware.profiler.ProfilerMiddleware` to correctly handle + float values. (:issue:`1511`) +- Update :class:`~middleware.lint.LintMiddleware` to work on Python 3. + (:issue:`1510`) +- The debugger detects cycles in chained exceptions and does not time + out in that case. (:issue:`1536`) +- When running the development server in Docker, the debugger security + pin is now unique per container. + + +Version 0.15.2 +-------------- + +Released 2019-04-02 + +- ``Rule`` code generation uses a filename that coverage will ignore. + The previous value, "generated", was causing coverage to fail. + (:issue:`1487`) +- The test client removes the cookie header if there are no persisted + cookies. This fixes an issue introduced in 0.15.0 where the cookies + from the original request were used for redirects, causing functions + such as logout to fail. (:issue:`1491`) +- The test client copies the environ before passing it to the app, to + prevent in-place modifications from affecting redirect requests. + (:issue:`1498`) +- The ``"werkzeug"`` logger only adds a handler if there is no handler + configured for its level in the logging chain. This avoids double + logging if other code configures logging first. (:issue:`1492`) + + +Version 0.15.1 +-------------- + +Released 2019-03-21 + +- :exc:`~exceptions.Unauthorized` takes ``description`` as the first + argument, restoring previous behavior. The new ``www_authenticate`` + argument is listed second. (:issue:`1483`) + + +Version 0.15.0 +-------------- + +Released 2019-03-19 + +- Building URLs is ~7x faster. Each :class:`~routing.Rule` compiles + an optimized function for building itself. (:pr:`1281`) +- :meth:`MapAdapter.build() ` can be passed + a :class:`~datastructures.MultiDict` to represent multiple values + for a key. It already did this when passing a dict with a list + value. (:pr:`724`) +- ``path_info`` defaults to ``'/'`` for + :meth:`Map.bind() `. (:issue:`740`, :pr:`768`, + :pr:`1316`) +- Change ``RequestRedirect`` code from 301 to 308, preserving the verb + and request body (form data) during redirect. (:pr:`1342`) +- ``int`` and ``float`` converters in URL rules will handle negative + values if passed the ``signed=True`` parameter. For example, + ``/jump/``. (:pr:`1355`) +- ``Location`` autocorrection in :func:`Response.get_wsgi_headers() + ` is relative to the current + path rather than the root path. (:issue:`693`, :pr:`718`, + :pr:`1315`) +- 412 responses once again include entity headers and an error message + in the body. They were originally omitted when implementing + ``If-Match`` (:pr:`1233`), but the spec doesn't seem to disallow it. + (:issue:`1231`, :pr:`1255`) +- The Content-Length header is removed for 1xx and 204 responses. This + fixes a previous change where no body would be sent, but the header + would still be present. The new behavior matches RFC 7230. + (:pr:`1294`) +- :class:`~exceptions.Unauthorized` takes a ``www_authenticate`` + parameter to set the ``WWW-Authenticate`` header for the response, + which is technically required for a valid 401 response. + (:issue:`772`, :pr:`795`) +- Add support for status code 424 :exc:`~exceptions.FailedDependency`. + (:pr:`1358`) +- :func:`http.parse_cookie` ignores empty segments rather than + producing a cookie with no key or value. (:issue:`1245`, :pr:`1301`) +- :func:`~http.parse_authorization_header` (and + :class:`~datastructures.Authorization`, + :attr:`~wrappers.Request.authorization`) treats the authorization + header as UTF-8. On Python 2, basic auth username and password are + ``unicode``. (:pr:`1325`) +- :func:`~http.parse_options_header` understands :rfc:`2231` parameter + continuations. (:pr:`1417`) +- :func:`~urls.uri_to_iri` does not unquote ASCII characters in the + unreserved class, such as space, and leaves invalid bytes quoted + when decoding. :func:`~urls.iri_to_uri` does not quote reserved + characters. See :rfc:`3987` for these character classes. + (:pr:`1433`) +- ``get_content_type`` appends a charset for any mimetype that ends + with ``+xml``, not just those that start with ``application/``. + Known text types such as ``application/javascript`` are also given + charsets. (:pr:`1439`) +- Clean up ``werkzeug.security`` module, remove outdated hashlib + support. (:pr:`1282`) +- In :func:`~security.generate_password_hash`, PBKDF2 uses 150000 + iterations by default, increased from 50000. (:pr:`1377`) +- :class:`~wsgi.ClosingIterator` calls ``close`` on the wrapped + *iterable*, not the internal iterator. This doesn't affect objects + where ``__iter__`` returned ``self``. For other objects, the method + was not called before. (:issue:`1259`, :pr:`1260`) +- Bytes may be used as keys in :class:`~datastructures.Headers`, they + will be decoded as Latin-1 like values are. (:pr:`1346`) +- :class:`~datastructures.Range` validates that list of range tuples + passed to it would produce a valid ``Range`` header. (:pr:`1412`) +- :class:`~datastructures.FileStorage` looks up attributes on + ``stream._file`` if they don't exist on ``stream``, working around + an issue where :func:`tempfile.SpooledTemporaryFile` didn't + implement all of :class:`io.IOBase`. See + https://github.com/python/cpython/pull/3249. (:pr:`1409`) +- :class:`CombinedMultiDict.copy() ` + returns a shallow mutable copy as a + :class:`~datastructures.MultiDict`. The copy no longer reflects + changes to the combined dicts, but is more generally useful. + (:pr:`1420`) +- The version of jQuery used by the debugger is updated to 3.3.1. + (:pr:`1390`) +- The debugger correctly renders long ``markupsafe.Markup`` instances. + (:pr:`1393`) +- The debugger can serve resources when Werkzeug is installed as a + zip file. ``DebuggedApplication.get_resource`` uses + ``pkgutil.get_data``. (:pr:`1401`) +- The debugger and server log support Python 3's chained exceptions. + (:pr:`1396`) +- The interactive debugger highlights frames that come from user code + to make them easy to pick out in a long stack trace. Note that if an + env was created with virtualenv instead of venv, the debugger may + incorrectly classify some frames. (:pr:`1421`) +- Clicking the error message at the top of the interactive debugger + will jump down to the bottom of the traceback. (:pr:`1422`) +- When generating a PIN, the debugger will ignore a ``KeyError`` + raised when the current UID doesn't have an associated username, + which can happen in Docker. (:issue:`1471`) +- :class:`~exceptions.BadRequestKeyError` adds the ``KeyError`` + message to the description, making it clearer what caused the 400 + error. Frameworks like Flask can omit this information in production + by setting ``e.args = ()``. (:pr:`1395`) +- If a nested ``ImportError`` occurs from :func:`~utils.import_string` + the traceback mentions the nested import. Removes an untested code + path for handling "modules not yet set up by the parent." + (:pr:`735`) +- Triggering a reload while using a tool such as PDB no longer hides + input. (:pr:`1318`) +- The reloader will not prepend the Python executable to the command + line if the Python file is marked executable. This allows the + reloader to work on NixOS. (:pr:`1242`) +- Fix an issue where ``sys.path`` would change between reloads when + running with ``python -m app``. The reloader can detect that a + module was run with "-m" and reconstructs that instead of the file + path in ``sys.argv`` when reloading. (:pr:`1416`) +- The dev server can bind to a Unix socket by passing a hostname like + ``unix://app.socket``. (:pr:`209`, :pr:`1019`) +- Server uses ``IPPROTO_TCP`` constant instead of ``SOL_TCP`` for + Jython compatibility. (:pr:`1375`) +- When using an adhoc SSL cert with :func:`~serving.run_simple`, the + cert is shown as self-signed rather than signed by an invalid + authority. (:pr:`1430`) +- The development server logs the unquoted IRI rather than the raw + request line, to make it easier to work with Unicode in request + paths during development. (:issue:`1115`) +- The development server recognizes ``ConnectionError`` on Python 3 to + silence client disconnects, and does not silence other ``OSErrors`` + that may have been raised inside the application. (:pr:`1418`) +- The environ keys ``REQUEST_URI`` and ``RAW_URI`` contain the raw + path before it was percent-decoded. This is non-standard, but many + WSGI servers add them. Middleware could replace ``PATH_INFO`` with + this to route based on the raw value. (:pr:`1419`) +- :class:`~test.EnvironBuilder` doesn't set ``CONTENT_TYPE`` or + ``CONTENT_LENGTH`` in the environ if they aren't set. Previously + these used default values if they weren't set. Now it's possible to + distinguish between empty and unset values. (:pr:`1308`) +- The test client raises a ``ValueError`` if a query string argument + would overwrite a query string in the path. (:pr:`1338`) +- :class:`test.EnvironBuilder` and :class:`test.Client` take a + ``json`` argument instead of manually passing ``data`` and + ``content_type``. This is serialized using the + :meth:`test.EnvironBuilder.json_dumps` method. (:pr:`1404`) +- :class:`test.Client` redirect handling is rewritten. (:pr:`1402`) + + - The redirect environ is copied from the initial request environ. + - Script root and path are correctly distinguished when + redirecting to a path under the root. + - The HEAD method is not changed to GET. + - 307 and 308 codes preserve the method and body. All others + ignore the body and related headers. + - Headers are passed to the new request for all codes, following + what browsers do. + - :class:`test.EnvironBuilder` sets the content type and length + headers in addition to the WSGI keys when detecting them from + the data. + - Intermediate response bodies are iterated over even when + ``buffered=False`` to ensure iterator middleware can run cleanup + code safely. Only the last response is not buffered. (:pr:`988`) + +- :class:`~test.EnvironBuilder`, :class:`~datastructures.FileStorage`, + and :func:`wsgi.get_input_stream` no longer share a global + ``_empty_stream`` instance. This improves test isolation by + preventing cases where closing the stream in one request would + affect other usages. (:pr:`1340`) +- The default ``SecureCookie.serialization_method`` will change from + :mod:`pickle` to :mod:`json` in 1.0. To upgrade existing tokens, + override :meth:`~contrib.securecookie.SecureCookie.unquote` to try + ``pickle`` if ``json`` fails. (:pr:`1413`) +- ``CGIRootFix`` no longer modifies ``PATH_INFO`` for very old + versions of Lighttpd. ``LighttpdCGIRootFix`` was renamed to + ``CGIRootFix`` in 0.9. Both are deprecated and will be removed in + version 1.0. (:pr:`1141`) +- :class:`werkzeug.wrappers.json.JSONMixin` has been replaced with + Flask's implementation. Check the docs for the full API. + (:pr:`1445`) +- The contrib modules are deprecated and will either be moved into + ``werkzeug`` core or removed completely in version 1.0. Some modules + that already issued deprecation warnings have been removed. Be sure + to run or test your code with + ``python -W default::DeprecationWarning`` to catch any deprecated + code you're using. (:issue:`4`) + + - ``LintMiddleware`` has moved to :mod:`werkzeug.middleware.lint`. + - ``ProfilerMiddleware`` has moved to + :mod:`werkzeug.middleware.profiler`. + - ``ProxyFix`` has moved to :mod:`werkzeug.middleware.proxy_fix`. + - ``JSONRequestMixin`` has moved to :mod:`werkzeug.wrappers.json`. + - ``cache`` has been extracted into a separate project, + `cachelib `_. The version + in Werkzeug is deprecated. + - ``securecookie`` and ``sessions`` have been extracted into a + separate project, + `secure-cookie `_. The + version in Werkzeug is deprecated. + - Everything in ``fixers``, except ``ProxyFix``, is deprecated. + - Everything in ``wrappers``, except ``JSONMixin``, is deprecated. + - ``atom`` is deprecated. This did not fit in with the rest of + Werkzeug, and is better served by a dedicated library in the + community. + - ``jsrouting`` is removed. Set URLs when rendering templates + or JSON responses instead. + - ``limiter`` is removed. Its specific use is handled by Werkzeug + directly, but stream limiting is better handled by the WSGI + server in general. + - ``testtools`` is removed. It did not offer significant benefit + over the default test client. + - ``iterio`` is deprecated. + +- :func:`wsgi.get_host` no longer looks at ``X-Forwarded-For``. Use + :class:`~middleware.proxy_fix.ProxyFix` to handle that. + (:issue:`609`, :pr:`1303`) +- :class:`~middleware.proxy_fix.ProxyFix` is refactored to support + more headers, multiple values, and more secure configuration. + + - Each header supports multiple values. The trusted number of + proxies is configured separately for each header. The + ``num_proxies`` argument is deprecated. (:pr:`1314`) + - Sets ``SERVER_NAME`` and ``SERVER_PORT`` based on + ``X-Forwarded-Host``. (:pr:`1314`) + - Sets ``SERVER_PORT`` and modifies ``HTTP_HOST`` based on + ``X-Forwarded-Port``. (:issue:`1023`, :pr:`1304`) + - Sets ``SCRIPT_NAME`` based on ``X-Forwarded-Prefix``. + (:issue:`1237`) + - The original WSGI environment values are stored in the + ``werkzeug.proxy_fix.orig`` key, a dict. The individual keys + ``werkzeug.proxy_fix.orig_remote_addr``, + ``werkzeug.proxy_fix.orig_wsgi_url_scheme``, and + ``werkzeug.proxy_fix.orig_http_host`` are deprecated. + +- Middleware from ``werkzeug.wsgi`` has moved to separate modules + under ``werkzeug.middleware``, along with the middleware moved from + ``werkzeug.contrib``. The old ``werkzeug.wsgi`` imports are + deprecated and will be removed in version 1.0. (:pr:`1452`) + + - ``werkzeug.wsgi.DispatcherMiddleware`` has moved to + :class:`werkzeug.middleware.dispatcher.DispatcherMiddleware`. + - ``werkzeug.wsgi.ProxyMiddleware`` as moved to + :class:`werkzeug.middleware.http_proxy.ProxyMiddleware`. + - ``werkzeug.wsgi.SharedDataMiddleware`` has moved to + :class:`werkzeug.middleware.shared_data.SharedDataMiddleware`. + +- :class:`~middleware.http_proxy.ProxyMiddleware` proxies the query + string. (:pr:`1252`) +- The filenames generated by + :class:`~middleware.profiler.ProfilerMiddleware` can be customized. + (:issue:`1283`) +- The ``werkzeug.wrappers`` module has been converted to a package, + and its various classes have been organized into separate modules. + Any previously documented classes, understood to be the existing + public API, are still importable from ``werkzeug.wrappers``, or may + be imported from their specific modules. (:pr:`1456`) + + +Version 0.14.1 +-------------- + +Released on December 31st 2017 + +- Resolved a regression with status code handling in the integrated + development server. + +Version 0.14 +------------ + +Released on December 31st 2017 + +- HTTP exceptions are now automatically caught by + ``Request.application``. +- Added support for edge as browser. +- Added support for platforms that lack ``SpooledTemporaryFile``. +- Add support for etag handling through if-match +- Added support for the SameSite cookie attribute. +- Added ``werkzeug.wsgi.ProxyMiddleware`` +- Implemented ``has`` for ``NullCache`` +- ``get_multi`` on cache clients now returns lists all the time. +- Improved the watchdog observer shutdown for the reloader to not crash + on exit on older Python versions. +- Added support for ``filename*`` filename attributes according to + RFC 2231 +- Resolved an issue where machine ID for the reloader PIN was not + read accurately on windows. +- Added a workaround for syntax errors in init files in the reloader. +- Added support for using the reloader with console scripts on windows. +- The built-in HTTP server will no longer close a connection in cases + where no HTTP body is expected (204, 204, HEAD requests etc.) +- The ``EnvironHeaders`` object now skips over empty content type and + lengths if they are set to falsy values. +- Werkzeug will no longer send the content-length header on 1xx or + 204/304 responses. +- Cookie values are now also permitted to include slashes and equal + signs without quoting. +- Relaxed the regex for the routing converter arguments. +- If cookies are sent without values they are now assumed to have an + empty value and the parser accepts this. Previously this could have + corrupted cookies that followed the value. +- The test ``Client`` and ``EnvironBuilder`` now support mimetypes like + the request object does. +- Added support for static weights in URL rules. +- Better handle some more complex reloader scenarios where sys.path + contained non directory paths. +- ``EnvironHeaders`` no longer raises weird errors if non string keys + are passed to it. + + +Version 0.13 +------------ + +Released on December 7th 2017 + +- **Deprecate support for Python 2.6 and 3.3.** CI tests will not run + for these versions, and support will be dropped completely in the next + version. (:issue:`pallets/meta#24`) +- Raise ``TypeError`` when port is not an integer. (:pr:`1088`) +- Fully deprecate ``werkzeug.script``. Use `Click`_ instead. + (:pr:`1090`) +- ``response.age`` is parsed as a ``timedelta``. Previously, it was + incorrectly treated as a ``datetime``. The header value is an integer + number of seconds, not a date string. (:pr:`414`) +- Fix a bug in ``TypeConversionDict`` where errors are not propagated + when using the converter. (:issue:`1102`) +- ``Authorization.qop`` is a string instead of a set, to comply with + RFC 2617. (:pr:`984`) +- An exception is raised when an encoded cookie is larger than, by + default, 4093 bytes. Browsers may silently ignore cookies larger than + this. ``BaseResponse`` has a new attribute ``max_cookie_size`` and + ``dump_cookie`` has a new argument ``max_size`` to configure this. + (:pr:`780`, :pr:`1109`) +- Fix a TypeError in ``werkzeug.contrib.lint.GuardedIterator.close``. + (:pr:`1116`) +- ``BaseResponse.calculate_content_length`` now correctly works for + Unicode responses on Python 3. It first encodes using + ``iter_encoded``. (:issue:`705`) +- Secure cookie contrib works with string secret key on Python 3. + (:pr:`1205`) +- Shared data middleware accepts a list instead of a dict of static + locations to preserve lookup order. (:pr:`1197`) +- HTTP header values without encoding can contain single quotes. + (:pr:`1208`) +- The built-in dev server supports receiving requests with chunked + transfer encoding. (:pr:`1198`) + +.. _Click: https://palletsprojects.com/p/click/ + + +Version 0.12.2 +-------------- + +Released on May 16 2017 + +- Fix regression: Pull request ``#892`` prevented Werkzeug from correctly + logging the IP of a remote client behind a reverse proxy, even when using + `ProxyFix`. +- Fix a bug in `safe_join` on Windows. + +Version 0.12.1 +-------------- + +Released on March 15th 2017 + +- Fix crash of reloader (used on debug mode) on Windows. + (`OSError: [WinError 10038]`). See pull request ``#1081`` +- Partially revert change to class hierarchy of `Headers`. See ``#1084``. + +Version 0.12 +------------ + +Released on March 10th 2017 + +- Spit out big deprecation warnings for werkzeug.script +- Use `inspect.getfullargspec` internally when available as + `inspect.getargspec` is gone in 3.6 +- Added support for status code 451 and 423 +- Improved the build error suggestions. In particular only if + someone stringifies the error will the suggestions be calculated. +- Added support for uWSGI's caching backend. +- Fix a bug where iterating over a `FileStorage` would result in an infinite + loop. +- Datastructures now inherit from the relevant baseclasses from the + `collections` module in the stdlib. See #794. +- Add support for recognizing NetBSD, OpenBSD, FreeBSD, DragonFlyBSD platforms + in the user agent string. +- Recognize SeaMonkey browser name and version correctly +- Recognize Baiduspider, and bingbot user agents +- If `LocalProxy`'s wrapped object is a function, refer to it with __wrapped__ + attribute. +- The defaults of ``generate_password_hash`` have been changed to more secure + ones, see pull request ``#753``. +- Add support for encoding in options header parsing, see pull request + ``#933``. +- ``test.Client`` now properly handles Location headers with relative URLs, see + pull request ``#879``. +- When `HTTPException` is raised, it now prints the description, for easier + debugging. +- Werkzeug's dict-like datastructures now have ``view``-methods under Python 2, + see pull request ``#968``. +- Fix a bug in ``MultiPartParser`` when no ``stream_factory`` was provided + during initialization, see pull request ``#973``. +- Disable autocorrect and spellchecker in the debugger middleware's Python + prompt, see pull request ``#994``. +- Don't redirect to slash route when method doesn't match, see pull request + ``#907``. +- Fix a bug when using ``SharedDataMiddleware`` with frozen packages, see pull + request ``#959``. +- `Range` header parsing function fixed for invalid values ``#974``. +- Add support for byte Range Requests, see pull request ``#978``. +- Use modern cryptographic defaults in the dev servers ``#1004``. +- the post() method of the test client now accept file object through the data + parameter. +- Color run_simple's terminal output based on HTTP codes ``#1013``. +- Fix self-XSS in debugger console, see ``#1031``. +- Fix IPython 5.x shell support, see ``#1033``. +- Change Accept datastructure to sort by specificity first, allowing for more + accurate results when using ``best_match`` for mime types (for example in + ``requests.accept_mimetypes.best_match``) + +Version 0.11.16 +--------------- + +- werkzeug.serving: set CONTENT_TYPE / CONTENT_LENGTH if only they're provided by the client +- werkzeug.serving: Fix crash of reloader when using `python -m werkzeug.serving`. + +Version 0.11.15 +--------------- + +Released on December 30th 2016. + +- Bugfix for the bugfix in the previous release. + +Version 0.11.14 +--------------- + +Released on December 30th 2016. + +- Check if platform can fork before importing ``ForkingMixIn``, raise exception + when creating ``ForkingWSGIServer`` on such a platform, see PR ``#999``. + +Version 0.11.13 +--------------- + +Released on December 26th 2016. + +- Correct fix for the reloader issuer on certain Windows installations. + +Version 0.11.12 +--------------- + +Released on December 26th 2016. + +- Fix more bugs in multidicts regarding empty lists. See ``#1000``. +- Add some docstrings to some `EnvironBuilder` properties that were previously + unintentionally missing. +- Added a workaround for the reloader on windows. + +Version 0.11.11 +--------------- + +Released on August 31st 2016. + +- Fix JSONRequestMixin for Python3. See #731 +- Fix broken string handling in test client when passing integers. See #852 +- Fix a bug in ``parse_options_header`` where an invalid content type + starting with comma or semi-colon would result in an invalid return value, + see issue ``#995``. +- Fix a bug in multidicts when passing empty lists as values, see issue + ``#979``. +- Fix a security issue that allows XSS on the Werkzeug debugger. See ``#1001``. + +Version 0.11.10 +--------------- + +Released on May 24th 2016. + +- Fixed a bug that occurs when running on Python 2.6 and using a broken locale. + See pull request #912. +- Fixed a crash when running the debugger on Google App Engine. See issue #925. +- Fixed an issue with multipart parsing that could cause memory exhaustion. + +Version 0.11.9 +-------------- + +Released on April 24th 2016. + +- Corrected an issue that caused the debugger not to use the + machine GUID on POSIX systems. +- Corrected a Unicode error on Python 3 for the debugger's + PIN usage. +- Corrected the timestamp verification in the pin debug code. + Without this fix the pin was remembered for too long. + +Version 0.11.8 +-------------- + +Released on April 15th 2016. + +- fixed a problem with the machine GUID detection code on OS X + on Python 3. + +Version 0.11.7 +-------------- + +Released on April 14th 2016. + +- fixed a regression on Python 3 for the debugger. + +Version 0.11.6 +-------------- + +Released on April 14th 2016. + +- werkzeug.serving: Still show the client address on bad requests. +- improved the PIN based protection for the debugger to make it harder to + brute force via trying cookies. Please keep in mind that the debugger + *is not intended for running on production environments* +- increased the pin timeout to a week to make it less annoying for people + which should decrease the chance that users disable the pin check + entirely. +- werkzeug.serving: Fix broken HTTP_HOST when path starts with double slash. + +Version 0.11.5 +-------------- + +Released on March 22nd 2016. + +- werkzeug.serving: Fix crash when attempting SSL connection to HTTP server. + +Version 0.11.4 +-------------- + +Released on February 14th 2016. + +- Fixed werkzeug.serving not working from -m flag. +- Fixed incorrect weak etag handling. + +Version 0.11.3 +-------------- + +Released on December 20th 2015. + +- Fixed an issue with copy operations not working against + proxies. +- Changed the logging operations of the development server to + correctly log where the server is running in all situations + again. +- Fixed another regression with SSL wrapping similar to the + fix in 0.11.2 but for a different code path. + +Version 0.11.2 +-------------- + +Released on November 12th 2015. + +- Fix inheritable sockets on Windows on Python 3. +- Fixed an issue with the forking server not starting any longer. +- Fixed SSL wrapping on platforms that supported opening sockets + by file descriptor. +- No longer log from the watchdog reloader. +- Unicode errors in hosts are now better caught or converted into + bad request errors. + +Version 0.11.1 +-------------- + +Released on November 10th 2015. + +- Fixed a regression on Python 3 in the debugger. + +Version 0.11 +------------ + +Released on November 8th 2015, codename Gleisbaumaschine. + +- Added ``reloader_paths`` option to ``run_simple`` and other functions in + ``werkzeug.serving``. This allows the user to completely override the Python + module watching of Werkzeug with custom paths. +- Many custom cached properties of Werkzeug's classes are now subclasses of + Python's ``property`` type (issue ``#616``). +- ``bind_to_environ`` now doesn't differentiate between implicit and explicit + default port numbers in ``HTTP_HOST`` (pull request ``#204``). +- ``BuildErrors`` are now more informative. They come with a complete sentence + as error message, and also provide suggestions (pull request ``#691``). +- Fix a bug in the user agent parser where Safari's build number instead of + version would be extracted (pull request ``#703``). +- Fixed issue where RedisCache set_many was broken for twemproxy, which doesn't + support the default MULTI command (pull request ``#702``). +- ``mimetype`` parameters on request and response classes are now always + converted to lowercase. +- Changed cache so that cache never expires if timeout is 0. This also fixes + an issue with redis setex (issue ``#550``) +- Werkzeug now assumes ``UTF-8`` as filesystem encoding on Unix if Python + detected it as ASCII. +- New optional `has` method on caches. +- Fixed various bugs in `parse_options_header` (pull request ``#643``). +- If the reloader is enabled the server will now open the socket in the parent + process if this is possible. This means that when the reloader kicks in + the connection from client will wait instead of tearing down. This does + not work on all Python versions. +- Implemented PIN based authentication for the debugger. This can optionally + be disabled but is discouraged. This change was necessary as it has been + discovered that too many people run the debugger in production. +- Devserver no longer requires SSL module to be installed. + +Version 0.10.5 +-------------- + +(bugfix release, release date yet to be decided) + +- Reloader: Correctly detect file changes made by moving temporary files over + the original, which is e.g. the case with PyCharm (pull request ``#722``). +- Fix bool behavior of ``werkzeug.datastructures.ETags`` under Python 3 (issue + ``#744``). + +Version 0.10.4 +-------------- + +(bugfix release, released on March 26th 2015) + +- Re-release of 0.10.3 with packaging artifacts manually removed. + +Version 0.10.3 +-------------- + +(bugfix release, released on March 26th 2015) + +- Re-release of 0.10.2 without packaging artifacts. + +Version 0.10.2 +-------------- + +(bugfix release, released on March 26th 2015) + +- Fixed issue where ``empty`` could break third-party libraries that relied on + keyword arguments (pull request ``#675``) +- Improved ``Rule.empty`` by providing a ```get_empty_kwargs`` to allow setting + custom kwargs without having to override entire ``empty`` method. (pull + request ``#675``) +- Fixed ```extra_files``` parameter for reloader to not cause startup + to crash when included in server params +- Using `MultiDict` when building URLs is now not supported again. The behavior + introduced several regressions. +- Fix performance problems with stat-reloader (pull request ``#715``). + +Version 0.10.1 +-------------- + +(bugfix release, released on February 3rd 2015) + +- Fixed regression with multiple query values for URLs (pull request ``#667``). +- Fix issues with eventlet's monkeypatching and the builtin server (pull + request ``#663``). + +Version 0.10 +------------ + +Released on January 30th 2015, codename Bagger. + +- Changed the error handling of and improved testsuite for the caches in + ``contrib.cache``. +- Fixed a bug on Python 3 when creating adhoc ssl contexts, due to `sys.maxint` + not being defined. +- Fixed a bug on Python 3, that caused + :func:`~werkzeug.serving.make_ssl_devcert` to fail with an exception. +- Added exceptions for 504 and 505. +- Added support for ChromeOS detection. +- Added UUID converter to the routing system. +- Added message that explains how to quit the server. +- Fixed a bug on Python 2, that caused ``len`` for + :class:`werkzeug.datastructures.CombinedMultiDict` to crash. +- Added support for stdlib pbkdf2 hmac if a compatible digest + is found. +- Ported testsuite to use ``py.test``. +- Minor optimizations to various middlewares (pull requests ``#496`` and + ``#571``). +- Use stdlib ``ssl`` module instead of ``OpenSSL`` for the builtin server + (issue ``#434``). This means that OpenSSL contexts are not supported anymore, + but instead ``ssl.SSLContext`` from the stdlib. +- Allow protocol-relative URLs when building external URLs. +- Fixed Atom syndication to print time zone offset for tz-aware datetime + objects (pull request ``#254``). +- Improved reloader to track added files and to recover from broken + sys.modules setups with syntax errors in packages. +- ``cache.RedisCache`` now supports arbitrary ``**kwargs`` for the redis + object. +- ``werkzeug.test.Client`` now uses the original request method when resolving + 307 redirects (pull request ``#556``). +- ``werkzeug.datastructures.MIMEAccept`` now properly deals with mimetype + parameters (pull request ``#205``). +- ``werkzeug.datastructures.Accept`` now handles a quality of ``0`` as + intolerable, as per RFC 2616 (pull request ``#536``). +- ``werkzeug.urls.url_fix`` now properly encodes hostnames with ``idna`` + encoding (issue ``#559``). It also doesn't crash on malformed URLs anymore + (issue ``#582``). +- ``werkzeug.routing.MapAdapter.match`` now recognizes the difference between + the path ``/`` and an empty one (issue ``#360``). +- The interactive debugger now tries to decode non-ascii filenames (issue + ``#469``). +- Increased default key size of generated SSL certificates to 1024 bits (issue + ``#611``). +- Added support for specifying a ``Response`` subclass to use when calling + :func:`~werkzeug.utils.redirect`\ . +- ``werkzeug.test.EnvironBuilder`` now doesn't use the request method anymore + to guess the content type, and purely relies on the ``form``, ``files`` and + ``input_stream`` properties (issue ``#620``). +- Added Symbian to the user agent platform list. +- Fixed make_conditional to respect automatically_set_content_length +- Unset ``Content-Length`` when writing to response.stream (issue ``#451``) +- ``wrappers.Request.method`` is now always uppercase, eliminating + inconsistencies of the WSGI environment (issue ``647``). +- ``routing.Rule.empty`` now works correctly with subclasses of ``Rule`` (pull + request ``#645``). +- Made map updating safe in light of concurrent updates. +- Allow multiple values for the same field for url building (issue ``#658``). + +Version 0.9.7 +------------- + +(bugfix release, release date to be decided) + +- Fix unicode problems in ``werkzeug.debug.tbtools``. +- Fix Python 3-compatibility problems in ``werkzeug.posixemulation``. +- Backport fix of fatal typo for ``ImmutableList`` (issue ``#492``). +- Make creation of the cache dir for ``FileSystemCache`` atomic (issue + ``#468``). +- Use native strings for memcached keys to work with Python 3 client (issue + ``#539``). +- Fix charset detection for ``werkzeug.debug.tbtools.Frame`` objects (issues + ``#547`` and ``#532``). +- Fix ``AttributeError`` masking in ``werkzeug.utils.import_string`` (issue + ``#182``). +- Explicitly shut down server (issue ``#519``). +- Fix timeouts greater than 2592000 being misinterpreted as UNIX timestamps in + ``werkzeug.contrib.cache.MemcachedCache`` (issue ``#533``). +- Fix bug where ``werkzeug.exceptions.abort`` would raise an arbitrary subclass + of the expected class (issue ``#422``). +- Fix broken ``jsrouting`` (due to removal of ``werkzeug.templates``) +- ``werkzeug.urls.url_fix`` now doesn't crash on malformed URLs anymore, but + returns them unmodified. This is a cheap workaround for ``#582``, the proper + fix is included in version 0.10. +- The repr of ``werkzeug.wrappers.Request`` doesn't crash on non-ASCII-values + anymore (pull request ``#466``). +- Fix bug in ``cache.RedisCache`` when combined with ``redis.StrictRedis`` + object (pull request ``#583``). +- The ``qop`` parameter for ``WWW-Authenticate`` headers is now always quoted, + as required by RFC 2617 (issue ``#633``). +- Fix bug in ``werkzeug.contrib.cache.SimpleCache`` with Python 3 where add/set + may throw an exception when pruning old entries from the cache (pull request + ``#651``). + +Version 0.9.6 +------------- + +(bugfix release, released on June 7th 2014) + +- Added a safe conversion for IRI to URI conversion and use that + internally to work around issues with spec violations for + protocols such as ``itms-service``. + +Version 0.9.7 +------------- + +- Fixed uri_to_iri() not re-encoding hashes in query string parameters. + +Version 0.9.5 +------------- + +(bugfix release, released on June 7th 2014) + +- Forward charset argument from request objects to the environ + builder. +- Fixed error handling for missing boundaries in multipart data. +- Fixed session creation on systems without ``os.urandom()``. +- Fixed pluses in dictionary keys not being properly URL encoded. +- Fixed a problem with deepcopy not working for multi dicts. +- Fixed a double quoting issue on redirects. +- Fixed a problem with unicode keys appearing in headers on 2.x. +- Fixed a bug with unicode strings in the test builder. +- Fixed a unicode bug on Python 3 in the WSGI profiler. +- Fixed an issue with the safe string compare function on + Python 2.7.7 and Python 3.4. + +Version 0.9.4 +------------- + +(bugfix release, released on August 26th 2013) + +- Fixed an issue with Python 3.3 and an edge case in cookie parsing. +- Fixed decoding errors not handled properly through the WSGI + decoding dance. +- Fixed URI to IRI conversion incorrectly decoding percent signs. + +Version 0.9.3 +------------- + +(bugfix release, released on July 25th 2013) + +- Restored behavior of the ``data`` descriptor of the request class to pre 0.9 + behavior. This now also means that ``.data`` and ``.get_data()`` have + different behavior. New code should use ``.get_data()`` always. + + In addition to that there is now a flag for the ``.get_data()`` method that + controls what should happen with form data parsing and the form parser will + honor cached data. This makes dealing with custom form data more consistent. + +Version 0.9.2 +------------- + +(bugfix release, released on July 18th 2013) + +- Added `unsafe` parameter to :func:`~werkzeug.urls.url_quote`. +- Fixed an issue with :func:`~werkzeug.urls.url_quote_plus` not quoting + `'+'` correctly. +- Ported remaining parts of :class:`~werkzeug.contrib.RedisCache` to + Python 3.3. +- Ported remaining parts of :class:`~werkzeug.contrib.MemcachedCache` to + Python 3.3 +- Fixed a deprecation warning in the contrib atom module. +- Fixed a regression with setting of content types through the + headers dictionary instead with the content type parameter. +- Use correct name for stdlib secure string comparison function. +- Fixed a wrong reference in the docstring of + :func:`~werkzeug.local.release_local`. +- Fixed an `AttributeError` that sometimes occurred when accessing the + :attr:`werkzeug.wrappers.BaseResponse.is_streamed` attribute. + +Version 0.9.1 +------------- + +(bugfix release, released on June 14th 2013) + +- Fixed an issue with integers no longer being accepted in certain + parts of the routing system or URL quoting functions. +- Fixed an issue with `url_quote` not producing the right escape + codes for single digit codepoints. +- Fixed an issue with :class:`~werkzeug.wsgi.SharedDataMiddleware` not + reading the path correctly and breaking on etag generation in some + cases. +- Properly handle `Expect: 100-continue` in the development server + to resolve issues with curl. +- Automatically exhaust the input stream on request close. This should + fix issues where not touching request files results in a timeout. +- Fixed exhausting of streams not doing anything if a non-limited + stream was passed into the multipart parser. +- Raised the buffer sizes for the multipart parser. + +Version 0.9 +----------- + +Released on June 13nd 2013, codename Planierraupe. + +- Added support for :meth:`~werkzeug.wsgi.LimitedStream.tell` + on the limited stream. +- :class:`~werkzeug.datastructures.ETags` now is nonzero if it + contains at least one etag of any kind, including weak ones. +- Added a workaround for a bug in the stdlib for SSL servers. +- Improved SSL interface of the devserver so that it can generate + certificates easily and load them from files. +- Refactored test client to invoke the open method on the class + for redirects. This makes subclassing more powerful. +- :func:`werkzeug.wsgi.make_chunk_iter` and + :func:`werkzeug.wsgi.make_line_iter` now support processing of + iterators and streams. +- URL generation by the routing system now no longer quotes + ``+``. +- URL fixing now no longer quotes certain reserved characters. +- The :func:`werkzeug.security.generate_password_hash` and + check functions now support any of the hashlib algorithms. +- `wsgi.get_current_url` is now ascii safe for browsers sending + non-ascii data in query strings. +- improved parsing behavior for :func:`werkzeug.http.parse_options_header` +- added more operators to local proxies. +- added a hook to override the default converter in the routing + system. +- The description field of HTTP exceptions is now always escaped. + Use markup objects to disable that. +- Added number of proxy argument to the proxy fix to make it more + secure out of the box on common proxy setups. It will by default + no longer trust the x-forwarded-for header as much as it did + before. +- Added support for fragment handling in URI/IRI functions. +- Added custom class support for :func:`werkzeug.http.parse_dict_header`. +- Renamed `LighttpdCGIRootFix` to `CGIRootFix`. +- Always treat `+` as safe when fixing URLs as people love misusing them. +- Added support to profiling into directories in the contrib profiler. +- The escape function now by default escapes quotes. +- Changed repr of exceptions to be less magical. +- Simplified exception interface to no longer require environments + to be passed to receive the response object. +- Added sentinel argument to IterIO objects. +- Added pbkdf2 support for the security module. +- Added a plain request type that disables all form parsing to only + leave the stream behind. +- Removed support for deprecated `fix_headers`. +- Removed support for deprecated `header_list`. +- Removed support for deprecated parameter for `iter_encoded`. +- Removed support for deprecated non-silent usage of the limited + stream object. +- Removed support for previous dummy `writable` parameter on + the cached property. +- Added support for explicitly closing request objects to close + associated resources. +- Conditional request handling or access to the data property on responses no + longer ignores direct passthrough mode. +- Removed werkzeug.templates and werkzeug.contrib.kickstart. +- Changed host lookup logic for forwarded hosts to allow lists of + hosts in which case only the first one is picked up. +- Added `wsgi.get_query_string`, `wsgi.get_path_info` and + `wsgi.get_script_name` and made the `wsgi.pop_path_info` and + `wsgi.peek_path_info` functions perform unicode decoding. This + was necessary to avoid having to expose the WSGI encoding dance + on Python 3. +- Added `content_encoding` and `content_md5` to the request object's + common request descriptor mixin. +- added `options` and `trace` to the test client. +- Overhauled the utilization of the input stream to be easier to use + and better to extend. The detection of content payload on the input + side is now more compliant with HTTP by detecting off the content + type header instead of the request method. This also now means that + the stream property on the request class is always available instead + of just when the parsing fails. +- Added support for using :class:`werkzeug.wrappers.BaseResponse` in a with + statement. +- Changed `get_app_iter` to fetch the response early so that it does not + fail when wrapping a response iterable. This makes filtering easier. +- Introduced `get_data` and `set_data` methods for responses. +- Introduced `get_data` for requests. +- Soft deprecated the `data` descriptors for request and response objects. +- Added `as_bytes` operations to some of the headers to simplify working + with things like cookies. +- Made the debugger paste tracebacks into github's gist service as + private pastes. + +Version 0.8.4 +------------- + +(bugfix release, release date to be announced) + +- Added a favicon to the debugger which fixes problem with + state changes being triggered through a request to + /favicon.ico in Google Chrome. This should fix some + problems with Flask and other frameworks that use + context local objects on a stack with context preservation + on errors. +- Fixed an issue with scrolling up in the debugger. +- Fixed an issue with debuggers running on a different URL + than the URL root. +- Fixed a problem with proxies not forwarding some rarely + used special methods properly. +- Added a workaround to prevent the XSS protection from Chrome + breaking the debugger. +- Skip redis tests if redis is not running. +- Fixed a typo in the multipart parser that caused content-type + to not be picked up properly. + +Version 0.8.3 +------------- + +(bugfix release, released on February 5th 2012) + +- Fixed another issue with :func:`werkzeug.wsgi.make_line_iter` + where lines longer than the buffer size were not handled + properly. +- Restore stdout after debug console finished executing so + that the debugger can be used on GAE better. +- Fixed a bug with the redis cache for int subclasses + (affects bool caching). +- Fixed an XSS problem with redirect targets coming from + untrusted sources. +- Redis cache backend now supports password authentication. + +Version 0.8.2 +------------- + +(bugfix release, released on December 16th 2011) + +- Fixed a problem with request handling of the builtin server + not responding to socket errors properly. +- The routing request redirect exception's code attribute is now + used properly. +- Fixed a bug with shutdowns on Windows. +- Fixed a few unicode issues with non-ascii characters being + hardcoded in URL rules. +- Fixed two property docstrings being assigned to fdel instead + of ``__doc__``. +- Fixed an issue where CRLF line endings could be split into two + by the line iter function, causing problems with multipart file + uploads. + +Version 0.8.1 +------------- + +(bugfix release, released on September 30th 2011) + +- Fixed an issue with the memcache not working properly. +- Fixed an issue for Python 2.7.1 and higher that broke + copying of multidicts with :func:`copy.copy`. +- Changed hashing methodology of immutable ordered multi dicts + for a potential problem with alternative Python implementations. + +Version 0.8 +----------- + +Released on September 29th 2011, codename Lötkolben + +- Removed data structure specific KeyErrors for a general + purpose :exc:`~werkzeug.exceptions.BadRequestKeyError`. +- Documented :meth:`werkzeug.wrappers.BaseRequest._load_form_data`. +- The routing system now also accepts strings instead of + dictionaries for the `query_args` parameter since we're only + passing them through for redirects. +- Werkzeug now automatically sets the content length immediately when + the :attr:`~werkzeug.wrappers.BaseResponse.data` attribute is set + for efficiency and simplicity reasons. +- The routing system will now normalize server names to lowercase. +- The routing system will no longer raise ValueErrors in case the + configuration for the server name was incorrect. This should make + deployment much easier because you can ignore that factor now. +- Fixed a bug with parsing HTTP digest headers. It rejected headers + with missing nc and nonce params. +- Proxy fix now also updates wsgi.url_scheme based on X-Forwarded-Proto. +- Added support for key prefixes to the redis cache. +- Added the ability to suppress some auto corrections in the wrappers + that are now controlled via `autocorrect_location_header` and + `automatically_set_content_length` on the response objects. +- Werkzeug now uses a new method to check that the length of incoming + data is complete and will raise IO errors by itself if the server + fails to do so. +- :func:`~werkzeug.wsgi.make_line_iter` now requires a limit that is + not higher than the length the stream can provide. +- Refactored form parsing into a form parser class that makes it possible + to hook into individual parts of the parsing process for debugging and + extending. +- For conditional responses the content length is no longer set when it + is already there and added if missing. +- Immutable datastructures are hashable now. +- Headers datastructure no longer allows newlines in values to avoid + header injection attacks. +- Made it possible through subclassing to select a different remote + addr in the proxy fix. +- Added stream based URL decoding. This reduces memory usage on large + transmitted form data that is URL decoded since Werkzeug will no longer + load all the unparsed data into memory. +- Memcache client now no longer uses the buggy cmemcache module and + supports pylibmc. GAE is not tried automatically and the dedicated + class is no longer necessary. +- Redis cache now properly serializes data. +- Removed support for Python 2.4 + +Version 0.7.2 +------------- + +(bugfix release, released on September 30th 2011) + +- Fixed a CSRF problem with the debugger. +- The debugger is now generating private pastes on lodgeit. +- If URL maps are now bound to environments the query arguments + are properly decoded from it for redirects. + +Version 0.7.1 +------------- + +(bugfix release, released on July 26th 2011) + +- Fixed a problem with newer versions of IPython. +- Disabled pyinotify based reloader which does not work reliably. + +Version 0.7 +----------- + +Released on July 24th 2011, codename Schraubschlüssel + +- Add support for python-libmemcached to the Werkzeug cache abstraction + layer. +- Improved :func:`url_decode` and :func:`url_encode` performance. +- Fixed an issue where the SharedDataMiddleware could cause an + internal server error on weird paths when loading via pkg_resources. +- Fixed an URL generation bug that caused URLs to be invalid if a + generated component contains a colon. +- :func:`werkzeug.import_string` now works with partially set up + packages properly. +- Disabled automatic socket switching for IPv6 on the development + server due to problems it caused. +- Werkzeug no longer overrides the Date header when creating a + conditional HTTP response. +- The routing system provides a method to retrieve the matching + methods for a given path. +- The routing system now accepts a parameter to change the encoding + error behaviour. +- The local manager can now accept custom ident functions in the + constructor that are forwarded to the wrapped local objects. +- url_unquote_plus now accepts unicode strings again. +- Fixed an issue with the filesystem session support's prune + function and concurrent usage. +- Fixed a problem with external URL generation discarding the port. +- Added support for pylibmc to the Werkzeug cache abstraction layer. +- Fixed an issue with the new multipart parser that happened when + a linebreak happened to be on the chunk limit. +- Cookies are now set properly if ports are in use. A runtime error + is raised if one tries to set a cookie for a domain without a dot. +- Fixed an issue with Template.from_file not working for file + descriptors. +- Reloader can now use inotify to track reloads. This requires the + pyinotify library to be installed. +- Werkzeug debugger can now submit to custom lodgeit installations. +- redirect function's status code assertion now allows 201 to be used + as redirection code. While it's not a real redirect, it shares + enough with redirects for the function to still be useful. +- Fixed securecookie for pypy. +- Fixed `ValueErrors` being raised on calls to `best_match` on + `MIMEAccept` objects when invalid user data was supplied. +- Deprecated `werkzeug.contrib.kickstart` and `werkzeug.contrib.testtools` +- URL routing now can be passed the URL arguments to keep them for + redirects. In the future matching on URL arguments might also be + possible. +- Header encoding changed from utf-8 to latin1 to support a port to + Python 3. Bytestrings passed to the object stay untouched which + makes it possible to have utf-8 cookies. This is a part where + the Python 3 version will later change in that it will always + operate on latin1 values. +- Fixed a bug in the form parser that caused the last character to + be dropped off if certain values in multipart data are used. +- Multipart parser now looks at the part-individual content type + header to override the global charset. +- Introduced mimetype and mimetype_params attribute for the file + storage object. +- Changed FileStorage filename fallback logic to skip special filenames + that Python uses for marking special files like stdin. +- Introduced more HTTP exception classes. +- `call_on_close` now can be used as a decorator. +- Support for redis as cache backend. +- Added `BaseRequest.scheme`. +- Support for the RFC 5789 PATCH method. +- New custom routing parser and better ordering. +- Removed support for `is_behind_proxy`. Use a WSGI middleware + instead that rewrites the `REMOTE_ADDR` according to your setup. + Also see the :class:`werkzeug.contrib.fixers.ProxyFix` for + a drop-in replacement. +- Added cookie forging support to the test client. +- Added support for host based matching in the routing system. +- Switched from the default 'ignore' to the better 'replace' + unicode error handling mode. +- The builtin server now adds a function named 'werkzeug.server.shutdown' + into the WSGI env to initiate a shutdown. This currently only works + in Python 2.6 and later. +- Headers are now assumed to be latin1 for better compatibility with + Python 3 once we have support. +- Added :func:`werkzeug.security.safe_join`. +- Added `accept_json` property analogous to `accept_html` on the + :class:`werkzeug.datastructures.MIMEAccept`. +- :func:`werkzeug.utils.import_string` now fails with much better + error messages that pinpoint to the problem. +- Added support for parsing of the `If-Range` header + (:func:`werkzeug.http.parse_if_range_header` and + :class:`werkzeug.datastructures.IfRange`). +- Added support for parsing of the `Range` header + (:func:`werkzeug.http.parse_range_header` and + :class:`werkzeug.datastructures.Range`). +- Added support for parsing of the `Content-Range` header of responses + and provided an accessor object for it + (:func:`werkzeug.http.parse_content_range_header` and + :class:`werkzeug.datastructures.ContentRange`). + +Version 0.6.2 +------------- + +(bugfix release, released on April 23th 2010) + +- renamed the attribute `implicit_seqence_conversion` attribute of the + request object to `implicit_sequence_conversion`. + +Version 0.6.1 +------------- + +(bugfix release, released on April 13th 2010) + +- heavily improved local objects. Should pick up standalone greenlet + builds now and support proxies to free callables as well. There is + also a stacked local now that makes it possible to invoke the same + application from within itself by pushing current request/response + on top of the stack. +- routing build method will also build non-default method rules properly + if no method is provided. +- added proper IPv6 support for the builtin server. +- windows specific filesystem session store fixes. + (should now be more stable under high concurrency) +- fixed a `NameError` in the session system. +- fixed a bug with empty arguments in the werkzeug.script system. +- fixed a bug where log lines will be duplicated if an application uses + :meth:`logging.basicConfig` (#499) +- added secure password hashing and checking functions. +- `HEAD` is now implicitly added as method in the routing system if + `GET` is present. Not doing that was considered a bug because often + code assumed that this is the case and in web servers that do not + normalize `HEAD` to `GET` this could break `HEAD` requests. +- the script support can start SSL servers now. + +Version 0.6 +----------- + +Released on Feb 19th 2010, codename Hammer. + +- removed pending deprecations +- sys.path is now printed from the testapp. +- fixed an RFC 2068 incompatibility with cookie value quoting. +- the :class:`FileStorage` now gives access to the multipart headers. +- `cached_property.writeable` has been deprecated. +- :meth:`MapAdapter.match` now accepts a `return_rule` keyword argument + that returns the matched `Rule` instead of just the `endpoint` +- :meth:`routing.Map.bind_to_environ` raises a more correct error message + now if the map was bound to an invalid WSGI environment. +- added support for SSL to the builtin development server. +- Response objects are no longer modified in place when they are evaluated + as WSGI applications. For backwards compatibility the `fix_headers` + function is still called in case it was overridden. + You should however change your application to use `get_wsgi_headers` if + you need header modifications before responses are sent as the backwards + compatibility support will go away in future versions. +- :func:`append_slash_redirect` no longer requires the QUERY_STRING to be + in the WSGI environment. +- added :class:`~werkzeug.contrib.wrappers.DynamicCharsetResponseMixin` +- added :class:`~werkzeug.contrib.wrappers.DynamicCharsetRequestMixin` +- added :attr:`BaseRequest.url_charset` +- request and response objects have a default `__repr__` now. +- builtin data structures can be pickled now. +- the form data parser will now look at the filename instead the + content type to figure out if it should treat the upload as regular + form data or file upload. This fixes a bug with Google Chrome. +- improved performance of `make_line_iter` and the multipart parser + for binary uploads. +- fixed :attr:`~werkzeug.BaseResponse.is_streamed` +- fixed a path quoting bug in `EnvironBuilder` that caused PATH_INFO and + SCRIPT_NAME to end up in the environ unquoted. +- :meth:`werkzeug.BaseResponse.freeze` now sets the content length. +- for unknown HTTP methods the request stream is now always limited + instead of being empty. This makes it easier to implement DAV + and other protocols on top of Werkzeug. +- added :meth:`werkzeug.MIMEAccept.best_match` +- multi-value test-client posts from a standard dictionary are now + supported. Previously you had to use a multi dict. +- rule templates properly work with submounts, subdomains and + other rule factories now. +- deprecated non-silent usage of the :class:`werkzeug.LimitedStream`. +- added support for IRI handling to many parts of Werkzeug. +- development server properly logs to the werkzeug logger now. +- added :func:`werkzeug.extract_path_info` +- fixed a querystring quoting bug in :func:`url_fix` +- added `fallback_mimetype` to :class:`werkzeug.SharedDataMiddleware`. +- deprecated :meth:`BaseResponse.iter_encoded`'s charset parameter. +- added :meth:`BaseResponse.make_sequence`, + :attr:`BaseResponse.is_sequence` and + :meth:`BaseResponse._ensure_sequence`. +- added better __repr__ of :class:`werkzeug.Map` +- `import_string` accepts unicode strings as well now. +- development server doesn't break on double slashes after the host name. +- better `__repr__` and `__str__` of + :exc:`werkzeug.exceptions.HTTPException` +- test client works correctly with multiple cookies now. +- the :class:`werkzeug.routing.Map` now has a class attribute with + the default converter mapping. This helps subclasses to override + the converters without passing them to the constructor. +- implemented :class:`OrderedMultiDict` +- improved the session support for more efficient session storing + on the filesystem. Also added support for listing of sessions + currently stored in the filesystem session store. +- werkzeug no longer utilizes the Python time module for parsing + which means that dates in a broader range can be parsed. +- the wrappers have no class attributes that make it possible to + swap out the dict and list types it uses. +- werkzeug debugger should work on the appengine dev server now. +- the URL builder supports dropping of unexpected arguments now. + Previously they were always appended to the URL as query string. +- profiler now writes to the correct stream. + +Version 0.5.1 +------------- +(bugfix release for 0.5, released on July 9th 2009) + +- fixed boolean check of :class:`FileStorage` +- url routing system properly supports unicode URL rules now. +- file upload streams no longer have to provide a truncate() + method. +- implemented :meth:`BaseRequest._form_parsing_failed`. +- fixed #394 +- :meth:`ImmutableDict.copy`, :meth:`ImmutableMultiDict.copy` and + :meth:`ImmutableTypeConversionDict.copy` return mutable shallow + copies. +- fixed a bug with the `make_runserver` script action. +- :meth:`MultiDict.items` and :meth:`MutiDict.iteritems` now accept an + argument to return a pair for each value of each key. +- the multipart parser works better with hand-crafted multipart + requests now that have extra newlines added. This fixes a bug + with setuptools uploads not handled properly (#390) +- fixed some minor bugs in the atom feed generator. +- fixed a bug with client cookie header parsing being case sensitive. +- fixed a not-working deprecation warning. +- fixed package loading for :class:`SharedDataMiddleware`. +- fixed a bug in the secure cookie that made server-side expiration + on servers with a local time that was not set to UTC impossible. +- fixed console of the interactive debugger. + + +Version 0.5 +----------- + +Released on April 24th, codename Schlagbohrer. + +- requires Python 2.4 now +- fixed a bug in :class:`~contrib.IterIO` +- added :class:`MIMEAccept` and :class:`CharsetAccept` that work like the + regular :class:`Accept` but have extra special normalization for mimetypes + and charsets and extra convenience methods. +- switched the serving system from wsgiref to something homebrew. +- the :class:`Client` now supports cookies. +- added the :mod:`~werkzeug.contrib.fixers` module with various + fixes for webserver bugs and hosting setup side-effects. +- added :mod:`werkzeug.contrib.wrappers` +- added :func:`is_hop_by_hop_header` +- added :func:`is_entity_header` +- added :func:`remove_hop_by_hop_headers` +- added :func:`pop_path_info` +- added :func:`peek_path_info` +- added :func:`wrap_file` and :class:`FileWrapper` +- moved `LimitedStream` from the contrib package into the regular + werkzeug one and changed the default behavior to raise exceptions + rather than stopping without warning. The old class will stick in + the module until 0.6. +- implemented experimental multipart parser that replaces the old CGI hack. +- added :func:`dump_options_header` and :func:`parse_options_header` +- added :func:`quote_header_value` and :func:`unquote_header_value` +- :func:`url_encode` and :func:`url_decode` now accept a separator + argument to switch between `&` and `;` as pair separator. The magic + switch is no longer in place. +- all form data parsing functions as well as the :class:`BaseRequest` + object have parameters (or attributes) to limit the number of + incoming bytes (either totally or per field). +- added :class:`LanguageAccept` +- request objects are now enforced to be read only for all collections. +- added many new collection classes, refactored collections in general. +- test support was refactored, semi-undocumented `werkzeug.test.File` + was replaced by :class:`werkzeug.FileStorage`. +- :class:`EnvironBuilder` was added and unifies the previous distinct + :func:`create_environ`, :class:`Client` and + :meth:`BaseRequest.from_values`. They all work the same now which + is less confusing. +- officially documented imports from the internal modules as undefined + behavior. These modules were never exposed as public interfaces. +- removed `FileStorage.__len__` which previously made the object + falsy for browsers not sending the content length which all browsers + do. +- :class:`SharedDataMiddleware` uses `wrap_file` now and has a + configurable cache timeout. +- added :class:`CommonRequestDescriptorsMixin` +- added :attr:`CommonResponseDescriptorsMixin.mimetype_params` +- added :mod:`werkzeug.contrib.lint` +- added `passthrough_errors` to `run_simple`. +- added `secure_filename` +- added :func:`make_line_iter` +- :class:`MultiDict` copies now instead of revealing internal + lists to the caller for `getlist` and iteration functions that + return lists. +- added :attr:`follow_redirect` to the :func:`open` of :class:`Client`. +- added support for `extra_files` in + :func:`~werkzeug.script.make_runserver` + +Version 0.4.1 +------------- + +(Bugfix release, released on January 11th 2009) + +- `werkzeug.contrib.cache.Memcached` accepts now objects that + implement the memcache.Client interface as alternative to a list of + strings with server addresses. + There is also now a `GAEMemcachedCache` that connects to the Google + appengine cache. +- explicitly convert secret keys to bytestrings now because Python + 2.6 no longer does that. +- `url_encode` and all interfaces that call it, support ordering of + options now which however is disabled by default. +- the development server no longer resolves the addresses of clients. +- Fixed a typo in `werkzeug.test` that broke `File`. +- `Map.bind_to_environ` uses the `Host` header now if available. +- Fixed `BaseCache.get_dict` (#345) +- `werkzeug.test.Client` can now run the application buffered in which + case the application is properly closed automatically. +- Fixed `Headers.set` (#354). Caused header duplication before. +- Fixed `Headers.pop` (#349). default parameter was not properly + handled. +- Fixed UnboundLocalError in `create_environ` (#351) +- `Headers` is more compatible with wsgiref now. +- `Template.render` accepts multidicts now. +- dropped support for Python 2.3 + +Version 0.4 +----------- + +Released on November 23rd 2008, codename Schraubenzieher. + +- `Client` supports an empty `data` argument now. +- fixed a bug in `Response.application` that made it impossible to use it + as method decorator. +- the session system should work on appengine now +- the secure cookie works properly in load balanced environments with + different cpu architectures now. +- `CacheControl.no_cache` and `CacheControl.private` behavior changed to + reflect the possibilities of the HTTP RFC. Setting these attributes to + `None` or `True` now sets the value to "the empty value". + More details in the documentation. +- fixed `werkzeug.contrib.atom.AtomFeed.__call__`. (#338) +- `BaseResponse.make_conditional` now always returns `self`. Previously + it didn't for post requests and such. +- fixed a bug in boolean attribute handling of `html` and `xhtml`. +- added graceful error handling to the debugger pastebin feature. +- added a more list like interface to `Headers` (slicing and indexing + works now) +- fixed a bug with the `__setitem__` method of `Headers` that didn't + properly remove all keys on replacing. +- added `remove_entity_headers` which removes all entity headers from + a list of headers (or a `Headers` object) +- the responses now automatically call `remove_entity_headers` if the + status code is 304. +- fixed a bug with `Href` query parameter handling. Previously the last + item of a call to `Href` was not handled properly if it was a dict. +- headers now support a `pop` operation to better work with environ + properties. + + +Version 0.3.1 +------------- + +(bugfix release, released on June 24th 2008) + +- fixed a security problem with `werkzeug.contrib.SecureCookie`. + + +Version 0.3 +----------- + +Released on June 14th 2008, codename EUR325CAT6. + +- added support for redirecting in url routing. +- added `Authorization` and `AuthorizationMixin` +- added `WWWAuthenticate` and `WWWAuthenticateMixin` +- added `parse_list_header` +- added `parse_dict_header` +- added `parse_authorization_header` +- added `parse_www_authenticate_header` +- added `_get_current_object` method to `LocalProxy` objects +- added `parse_form_data` +- `MultiDict`, `CombinedMultiDict`, `Headers`, and `EnvironHeaders` raise + special key errors now that are subclasses of `BadRequest` so if you + don't catch them they give meaningful HTTP responses. +- added support for alternative encoding error handling and the new + `HTTPUnicodeError` which (if not caught) behaves like a `BadRequest`. +- added `BadRequest.wrap`. +- added ETag support to the SharedDataMiddleware and added an option + to disable caching. +- fixed `is_xhr` on the request objects. +- fixed error handling of the url adapter's `dispatch` method. (#318) +- fixed bug with `SharedDataMiddleware`. +- fixed `Accept.values`. +- `EnvironHeaders` contain content-type and content-length now +- `url_encode` treats lists and tuples in dicts passed to it as multiple + values for the same key so that one doesn't have to pass a `MultiDict` + to the function. +- added `validate_arguments` +- added `BaseRequest.application` +- improved Python 2.3 support +- `run_simple` accepts `use_debugger` and `use_evalex` parameters now, + like the `make_runserver` factory function from the script module. +- the `environ_property` is now read-only by default +- it's now possible to initialize requests as "shallow" requests which + causes runtime errors if the request object tries to consume the + input stream. + + +Version 0.2 +----------- + +Released Feb 14th 2008, codename Faustkeil. + +- Added `AnyConverter` to the routing system. +- Added `werkzeug.contrib.securecookie` +- Exceptions have a ``get_response()`` method that return a response object +- fixed the path ordering bug (#293), thanks Thomas Johansson +- `BaseReporterStream` is now part of the werkzeug contrib module. From + Werkzeug 0.3 onwards you will have to import it from there. +- added `DispatcherMiddleware`. +- `RequestRedirect` is now a subclass of `HTTPException` and uses a + 301 status code instead of 302. +- `url_encode` and `url_decode` can optionally treat keys as unicode strings + now, too. +- `werkzeug.script` has a different caller format for boolean arguments now. +- renamed `lazy_property` to `cached_property`. +- added `import_string`. +- added is_* properties to request objects. +- added `empty()` method to routing rules. +- added `werkzeug.contrib.profiler`. +- added `extends` to `Headers`. +- added `dump_cookie` and `parse_cookie`. +- added `as_tuple` to the `Client`. +- added `werkzeug.contrib.testtools`. +- added `werkzeug.unescape` +- added `BaseResponse.freeze` +- added `werkzeug.contrib.atom` +- the HTTPExceptions accept an argument `description` now which overrides the + default description. +- the `MapAdapter` has a default for path info now. If you use + `bind_to_environ` you don't have to pass the path later. +- the wsgiref subclass werkzeug uses for the dev server does not use direct + sys.stderr logging any more but a logger called "werkzeug". +- implemented `Href`. +- implemented `find_modules` +- refactored request and response objects into base objects, mixins and + full featured subclasses that implement all mixins. +- added simple user agent parser +- werkzeug's routing raises `MethodNotAllowed` now if it matches a + rule but for a different method. +- many fixes and small improvements + + +Version 0.1 +----------- + +Released on Dec 9th 2007, codename Wictorinoxger. + +- Initial release diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 000000000..f4ba197de --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,76 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as +contributors and maintainers pledge to making participation in our project and +our community a harassment-free experience for everyone, regardless of age, body +size, disability, ethnicity, sex characteristics, gender identity and expression, +level of experience, education, socio-economic status, nationality, personal +appearance, race, religion, or sexual identity and orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment +include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or + advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic + address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable +behavior and are expected to take appropriate and fair corrective action in +response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or +reject comments, commits, code, wiki edits, issues, and other contributions +that are not aligned to this Code of Conduct, or to ban temporarily or +permanently any contributor for other behaviors that they deem inappropriate, +threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces +when an individual is representing the project or its community. Examples of +representing a project or community include using an official project e-mail +address, posting via an official social media account, or acting as an appointed +representative at an online or offline event. Representation of a project may be +further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported by contacting the project team at report@palletsprojects.com. All +complaints will be reviewed and investigated and will result in a response that +is deemed necessary and appropriate to the circumstances. The project team is +obligated to maintain confidentiality with regard to the reporter of an incident. +Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good +faith may face temporary or permanent repercussions as determined by other +members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, +available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html + +[homepage]: https://www.contributor-covenant.org + +For answers to common questions about this code of conduct, see +https://www.contributor-covenant.org/faq diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index ad5082dda..9f4080030 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -1,72 +1,222 @@ -============================= How to contribute to Werkzeug ============================= -Thanks for considering contributing to Werkzeug. +Thank you for considering contributing to Werkzeug! + Support questions -================= +----------------- + +Please don't use the issue tracker for this. The issue tracker is a +tool to address bugs and feature requests in Werkzeug itself. Use one of +the following resources for questions about using Werkzeug or issues +with your own code: + +- The ``#get-help`` channel on our Discord chat: + https://discord.gg/pallets +- The mailing list flask@python.org for long term discussion or larger + issues. +- Ask on `Stack Overflow`_. Search with Google first using: + ``site:stackoverflow.com werkzeug {search term, exception message, etc.}`` + +.. _Stack Overflow: https://stackoverflow.com/questions/tagged/werkzeug?tab=Frequent -Please, don't use the issue tracker for this. Check whether the `Pocoo IRC -channel `_ can help with your issue. If your problem -is not strictly Werkzeug- or Flask-specific, ``#python`` on Freenode is -generally more active. `StackOverflow `_ is also -worth considering. Reporting issues -================ +---------------- + +Include the following information in your post: + +- Describe what you expected to happen. +- If possible, include a `minimal reproducible example`_ to help us + identify the issue. This also helps check that the issue is not with + your own code. +- Describe what actually happened. Include the full traceback if there + was an exception. +- List your Python and Werkzeug versions. If possible, check if this + issue is already fixed in the latest releases or the latest code in + the repository. -- Under which versions of Python does this happen? This is even more important - if your issue is encoding related. +.. _minimal reproducible example: https://stackoverflow.com/help/minimal-reproducible-example -- Under which versions of Werkzeug does this happen? Check if this issue is - fixed in the repository. Submitting patches -================== +------------------ + +If there is not an open issue for what you want to submit, prefer +opening one for discussion before working on a PR. You can work on any +issue that doesn't have an open PR linked to it or a maintainer assigned +to it. These show up in the sidebar. No need to ask if you can work on +an issue that interests you. + +Include the following in your patch: + +- Use `Black`_ to format your code. This and other tools will run + automatically if you install `pre-commit`_ using the instructions + below. +- Include tests if your patch adds or changes code. Make sure the test + fails without your patch. +- Update any relevant docs pages and docstrings. Docs pages and + docstrings should be wrapped at 72 characters. +- Add an entry in ``CHANGES.rst``. Use the same style as other + entries. Also include ``.. versionchanged::`` inline changelogs in + relevant docstrings. + +.. _Black: https://black.readthedocs.io +.. _pre-commit: https://pre-commit.com + + +First time setup +~~~~~~~~~~~~~~~~ + +- Download and install the `latest version of git`_. +- Configure git with your `username`_ and `email`_. + + .. code-block:: text + + $ git config --global user.name 'your name' + $ git config --global user.email 'your email' + +- Make sure you have a `GitHub account`_. +- Fork Werkzeug to your GitHub account by clicking the `Fork`_ button. +- `Clone`_ the main repository locally. + + .. code-block:: text + + $ git clone https://github.com/pallets/werkzeug + $ cd werkzeug + +- Add your fork as a remote to push your work to. Replace + ``{username}`` with your username. This names the remote "fork", the + default Pallets remote is "origin". + + .. code-block:: text + + $ git remote add fork https://github.com/{username}/werkzeug + +- Create a virtualenv. + + .. code-block:: text + + $ python3 -m venv env + $ . env/bin/activate + + On Windows, activating is different. + + .. code-block:: text + + > env\Scripts\activate + +- Upgrade pip and setuptools. + + .. code-block:: text + + $ python -m pip install --upgrade pip setuptools + +- Install the development dependencies, then install Werkzeug in + editable mode. + + .. code-block:: text + + $ pip install -r requirements/dev.txt && pip install -e . + +- Install the pre-commit hooks. + + .. code-block:: text + + $ pre-commit install + +.. _latest version of git: https://git-scm.com/downloads +.. _username: https://docs.github.com/en/github/using-git/setting-your-username-in-git +.. _email: https://docs.github.com/en/github/setting-up-and-managing-your-github-user-account/setting-your-commit-email-address +.. _GitHub account: https://github.com/join +.. _Fork: https://github.com/pallets/werkzeug/fork +.. _Clone: https://docs.github.com/en/github/getting-started-with-github/fork-a-repo#step-2-create-a-local-clone-of-your-fork + + +Start coding +~~~~~~~~~~~~ + +- Create a branch to identify the issue you would like to work on. If + you're submitting a bug or documentation fix, branch off of the + latest ".x" branch. + + .. code-block:: text + + $ git fetch origin + $ git checkout -b your-branch-name origin/2.0.x + + If you're submitting a feature addition or change, branch off of the + "main" branch. + + .. code-block:: text + + $ git fetch origin + $ git checkout -b your-branch-name origin/main + +- Using your favorite editor, make your changes, + `committing as you go`_. +- Include tests that cover any code changes you make. Make sure the + test fails without your patch. Run the tests as described below. +- Push your commits to your fork on GitHub and + `create a pull request`_. Link to the issue being addressed with + ``fixes #123`` in the pull request. + + .. code-block:: text + + $ git push --set-upstream fork your-branch-name + +.. _committing as you go: https://dont-be-afraid-to-commit.readthedocs.io/en/latest/git/commandlinegit.html#commit-your-changes +.. _create a pull request: https://docs.github.com/en/github/collaborating-with-issues-and-pull-requests/creating-a-pull-request + + +Running the tests +~~~~~~~~~~~~~~~~~ + +Run the basic test suite with pytest. + +.. code-block:: text + + $ pytest -- Please do not use pull requests as a way to suggest behavior changes. Open an - issue for discussion first. This helps keeping the discussions of concept and - implementation separate. +This runs the tests for the current environment, which is usually +sufficient. CI will run the full suite when you submit your pull +request. You can run the full test suite with tox if you don't want to +wait. -- Include tests if your patch is supposed to solve a bug, and explain - clearly under which circumstances the bug happens. Make sure the test fails - without your patch. +.. code-block:: text -- Try to follow `PEP8 `_, but you - may ignore the line-length-limit if following it would make the code uglier. + $ tox -- Add an entry to ``CHANGES`` and your name to ``AUTHORS``. +Running test coverage +~~~~~~~~~~~~~~~~~~~~~ -Running the testsuite ---------------------- +Generating a report of lines that do not have test coverage can indicate +where to start contributing. Run ``pytest`` using ``coverage`` and +generate a report. -You probably want to set up a `virtualenv -`_. +.. code-block:: text -Werkzeug must be installed for all tests to pass:: + $ pip install coverage + $ coverage run -m pytest + $ coverage html - pip install -e . +Open ``htmlcov/index.html`` in your browser to explore the report. -The minimal requirement for running the testsuite is ``py.test``. You can -install it with:: +Read more about `coverage `__. - pip install pytest -Then you can run the testsuite with:: +Building the docs +~~~~~~~~~~~~~~~~~ - py.test +Build the docs in the ``docs`` directory using Sphinx. -With only py.test installed, a large part of the testsuite will get skipped -though. Whether this is relevant depends on which part of Werkzeug you're -working on. Travis is set up to run the full testsuite when you submit your -pull request anyways. +.. code-block:: text -If you really want to test everything, you will have to install ``tox`` instead -of ``pytest``. You can install it with:: + $ cd docs + $ make html - pip install tox +Open ``_build/html/index.html`` in your browser to view the docs. -The ``tox`` command will then run all tests against multiple combinations -Python versions and dependency versions. +Read more about `Sphinx `__. diff --git a/LICENSE b/LICENSE deleted file mode 100644 index 1c2e0b7dc..000000000 --- a/LICENSE +++ /dev/null @@ -1,29 +0,0 @@ -Copyright (c) 2014 by the Werkzeug Team, see AUTHORS for more details. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above - copyright notice, this list of conditions and the following - disclaimer in the documentation and/or other materials provided - with the distribution. - - * The names of the contributors may not be used to endorse or - promote products derived from this software without specific - prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/LICENSE.rst b/LICENSE.rst new file mode 100644 index 000000000..c37cae49e --- /dev/null +++ b/LICENSE.rst @@ -0,0 +1,28 @@ +Copyright 2007 Pallets + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED +TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/MANIFEST.in b/MANIFEST.in index 2e81e03c7..89424810e 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,10 +1,12 @@ -include Makefile CHANGES LICENSE AUTHORS -recursive-include werkzeug/debug/shared * -recursive-include tests * -recursive-include docs * -recursive-include artwork * -recursive-include examples * - +include CHANGES.rst +include tox.ini +include requirements/*.txt +graft artwork +graft docs prune docs/_build -prune docs/_themes -global-exclude *.py[cdo] __pycache__ *.so *.pyd +graft examples +graft tests +include src/werkzeug/py.typed +include src/werkzeug/*.pyi +graft src/werkzeug/debug/shared +global-exclude *.pyc diff --git a/Makefile b/Makefile deleted file mode 100644 index b5e3c9835..000000000 --- a/Makefile +++ /dev/null @@ -1,35 +0,0 @@ -# -# Werkzeug Makefile -# ~~~~~~~~~~~~~~~~~ -# -# Shortcuts for various tasks. -# -# :copyright: (c) 2008 by the Werkzeug Team, see AUTHORS for more details. -# :license: BSD, see LICENSE for more details. -# - -documentation: - @(cd docs; make html) - -release: - python scripts/make-release.py - -test: - py.test --tb=native - -tox-test: - tox - -coverage: - @(coverage run --source=werkzeug --module py.test $(TEST_OPTIONS) $(TESTS)) - -doctest: - @(cd docs; sphinx-build -b doctest . _build/doctest) - -upload-docs: - $(MAKE) -C docs html dirhtml latex - $(MAKE) -C docs/_build/latex all-pdf - cd docs/_build/; mv html werkzeug-docs; zip -r werkzeug-docs.zip werkzeug-docs; mv werkzeug-docs html - rsync -a docs/_build/dirhtml/ flow.srv.pocoo.org:/srv/websites/werkzeug.pocoo.org/docs/ - rsync -a docs/_build/latex/Werkzeug.pdf flow.srv.pocoo.org:/srv/websites/werkzeug.pocoo.org/docs/ - rsync -a docs/_build/werkzeug-docs.zip flow.srv.pocoo.org:/srv/websites/werkzeug.pocoo.org/docs/werkzeug-docs.zip diff --git a/README.rst b/README.rst index 5d55b26c2..f1592a569 100644 --- a/README.rst +++ b/README.rst @@ -1,40 +1,91 @@ Werkzeug ======== -Werkzeug started as simple collection of various utilities for WSGI -applications and has become one of the most advanced WSGI utility -modules. It includes a powerful debugger, full-featured request and -response objects, HTTP utilities to handle entity tags, cache control -headers, HTTP dates, cookie handling, file uploads, a powerful URL -routing system and a bunch of community-contributed addon modules. - -Werkzeug is unicode aware and doesn't enforce a specific template -engine, database adapter or anything else. It doesn't even enforce -a specific way of handling requests and leaves all that up to the -developer. It's most useful for end user applications which should work -on as many server environments as possible (such as blogs, wikis, -bulletin boards, etc.). - -Details and example applications are available on the -`Werkzeug website `_. - - -Branches --------- - -+----------------------+-------------------------------------------------------------------------------+ -| ``master`` | .. image:: https://travis-ci.org/pallets/werkzeug.svg?branch=master | -| | :target: https://travis-ci.org/pallets/werkzeug | -+----------------------+-------------------------------------------------------------------------------+ -| ``0.12-maintenance`` | .. image:: https://travis-ci.org/pallets/werkzeug.svg?branch=0.12-maintenance | -| | :target: https://travis-ci.org/pallets/werkzeug | -+----------------------+-------------------------------------------------------------------------------+ -| ``0.11-maintenance`` | .. image:: https://travis-ci.org/pallets/werkzeug.svg?branch=0.11-maintenance | -| | :target: https://travis-ci.org/pallets/werkzeug | -+----------------------+-------------------------------------------------------------------------------+ -| ``0.10-maintenance`` | .. image:: https://travis-ci.org/pallets/werkzeug.svg?branch=0.10-maintenance | -| | :target: https://travis-ci.org/pallets/werkzeug | -+----------------------+-------------------------------------------------------------------------------+ -| ``0.9-maintenance`` | .. image:: https://travis-ci.org/pallets/werkzeug.svg?branch=0.9-maintenance | -| | :target: https://travis-ci.org/pallets/werkzeug | -+----------------------+-------------------------------------------------------------------------------+ +*werkzeug* German noun: "tool". Etymology: *werk* ("work"), *zeug* ("stuff") + +Werkzeug is a comprehensive `WSGI`_ web application library. It began as +a simple collection of various utilities for WSGI applications and has +become one of the most advanced WSGI utility libraries. + +It includes: + +- An interactive debugger that allows inspecting stack traces and + source code in the browser with an interactive interpreter for any + frame in the stack. +- A full-featured request object with objects to interact with + headers, query args, form data, files, and cookies. +- A response object that can wrap other WSGI applications and handle + streaming data. +- A routing system for matching URLs to endpoints and generating URLs + for endpoints, with an extensible system for capturing variables + from URLs. +- HTTP utilities to handle entity tags, cache control, dates, user + agents, cookies, files, and more. +- A threaded WSGI server for use while developing applications + locally. +- A test client for simulating HTTP requests during testing without + requiring running a server. + +Werkzeug doesn't enforce any dependencies. It is up to the developer to +choose a template engine, database adapter, and even how to handle +requests. It can be used to build all sorts of end user applications +such as blogs, wikis, or bulletin boards. + +`Flask`_ wraps Werkzeug, using it to handle the details of WSGI while +providing more structure and patterns for defining powerful +applications. + +.. _WSGI: https://wsgi.readthedocs.io/en/latest/ +.. _Flask: https://www.palletsprojects.com/p/flask/ + + +Installing +---------- + +Install and update using `pip`_: + +.. code-block:: text + + pip install -U Werkzeug + +.. _pip: https://pip.pypa.io/en/stable/getting-started/ + + +A Simple Example +---------------- + +.. code-block:: python + + from werkzeug.wrappers import Request, Response + + @Request.application + def application(request): + return Response('Hello, World!') + + if __name__ == '__main__': + from werkzeug.serving import run_simple + run_simple('localhost', 4000, application) + + +Donate +------ + +The Pallets organization develops and supports Werkzeug and other +popular packages. In order to grow the community of contributors and +users, and allow the maintainers to devote more time to the projects, +`please donate today`_. + +.. _please donate today: https://palletsprojects.com/donate + + +Links +----- + +- Documentation: https://werkzeug.palletsprojects.com/ +- Changes: https://werkzeug.palletsprojects.com/changes/ +- PyPI Releases: https://pypi.org/project/Werkzeug/ +- Source Code: https://github.com/pallets/werkzeug/ +- Issue Tracker: https://github.com/pallets/werkzeug/issues/ +- Website: https://palletsprojects.com/p/werkzeug/ +- Twitter: https://twitter.com/PalletsTeam +- Chat: https://discord.gg/pallets diff --git a/appveyor.yml b/appveyor.yml deleted file mode 100644 index 463d2b18f..000000000 --- a/appveyor.yml +++ /dev/null @@ -1,18 +0,0 @@ -build: false # Not a C# project, build stuff at the test step instead. -environment: - matrix: - - PYTHON: "C:/Python27" - - PYTHON: "C:/Python33" - - PYTHON: "C:/Python34" - -init: - - "ECHO %PYTHON%" - - ps: "ls C:/Python*" - -install: - - ps: (new-object net.webclient).DownloadFile('https://bootstrap.pypa.io/get-pip.py', 'C:/get-pip.py') - - "%PYTHON%/python.exe C:/get-pip.py" - - "%PYTHON%/Scripts/pip.exe install tox" - -test_script: - - "%PYTHON%/Scripts/tox.exe -e py-normal" diff --git a/bench/wzbench.py b/bench/wzbench.py deleted file mode 100755 index 10d4fa463..000000000 --- a/bench/wzbench.py +++ /dev/null @@ -1,461 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -""" - wzbench - ~~~~~~~ - - A werkzeug internal benchmark module. It's used in combination with - hg bisect to find out how the Werkzeug performance of some internal - core parts changes over time. - - :copyright: 2014 by the Werkzeug Team, see AUTHORS for more details. - :license: BSD, see LICENSE for more details. -""" -from __future__ import division -import os -import gc -import sys -import subprocess -from cStringIO import StringIO -from timeit import default_timer as timer -from types import FunctionType - -PY2 = sys.version_info[0] == 2 - -if not PY2: - xrange = range - - -# create a new module where we later store all the werkzeug attributes. -wz = type(sys)('werkzeug_nonlazy') -sys.path.insert(0, '') -null_out = open(os.devnull, 'w') - - -# ±4% are ignored -TOLERANCE = 0.04 -MIN_RESOLUTION = 0.002 - -# we run each test 5 times -TEST_RUNS = 5 - - -def find_hg_tag(path): - """Returns the current node or tag for the given path.""" - tags = {} - try: - client = subprocess.Popen(['hg', 'cat', '-r', 'tip', '.hgtags'], - stdout=subprocess.PIPE, cwd=path) - for line in client.communicate()[0].splitlines(): - line = line.strip() - if not line: - continue - hash, tag = line.split() - tags[hash] = tag - except OSError: - return - - client = subprocess.Popen(['hg', 'parent', '--template', '#node#'], - stdout=subprocess.PIPE, cwd=path) - - tip = client.communicate()[0].strip() - tag = tags.get(tip) - if tag is not None: - return tag - return tip - - -def load_werkzeug(path): - """Load werkzeug.""" - sys.path[0] = path - - # get rid of already imported stuff - wz.__dict__.clear() - for key in sys.modules.keys(): - if key.startswith('werkzeug.') or key == 'werkzeug': - sys.modules.pop(key, None) - - # import werkzeug again. - import werkzeug - for key in werkzeug.__all__: - setattr(wz, key, getattr(werkzeug, key)) - - # get the hg tag - hg_tag = find_hg_tag(path) - - # get the real version from the setup file - try: - f = open(os.path.join(path, 'setup.py')) - except IOError: - pass - else: - try: - for line in f: - line = line.strip() - if line.startswith('version='): - return line[8:].strip(' \t,')[1:-1], hg_tag - finally: - f.close() - print >> sys.stderr, 'Unknown werkzeug version loaded' - sys.exit(2) - - -def median(seq): - seq = sorted(seq) - if not seq: - return 0.0 - return seq[len(seq) // 2] - - -def format_func(func): - if type(func) is FunctionType: - name = func.__name__ - else: - name = func - if name.startswith('time_'): - name = name[5:] - return name.replace('_', ' ').title() - - -def bench(func): - """Times a single function.""" - sys.stdout.write('%44s ' % format_func(func)) - sys.stdout.flush() - - # figure out how many times we have to run the function to - # get reliable timings. - for i in xrange(3, 10): - rounds = 1 << i - t = timer() - for x in xrange(rounds): - func() - if timer() - t >= 0.2: - break - - # now run the tests without gc TEST_RUNS times and use the median - # value of these runs. - def _run(): - gc.collect() - gc.disable() - try: - t = timer() - for x in xrange(rounds): - func() - return (timer() - t) / rounds * 1000 - finally: - gc.enable() - - delta = median(_run() for x in xrange(TEST_RUNS)) - sys.stdout.write('%.4f\n' % delta) - sys.stdout.flush() - - return delta - - -def main(): - """The main entrypoint.""" - from optparse import OptionParser - parser = OptionParser(usage='%prog [options]') - parser.add_option('--werkzeug-path', '-p', dest='path', default='..', - help='the path to the werkzeug package. defaults to cwd') - parser.add_option('--compare', '-c', dest='compare', nargs=2, - default=False, help='compare two hg nodes of Werkzeug') - parser.add_option('--init-compare', dest='init_compare', - action='store_true', default=False, - help='Initializes the comparison feature') - options, args = parser.parse_args() - if args: - parser.error('Script takes no arguments') - if options.compare: - compare(*options.compare) - elif options.init_compare: - init_compare() - else: - run(options.path) - - -def init_compare(): - """Initializes the comparison feature.""" - print('Initializing comparison feature') - subprocess.Popen(['hg', 'clone', '..', 'a']).wait() - subprocess.Popen(['hg', 'clone', '..', 'b']).wait() - - -def compare(node1, node2): - """Compares two Werkzeug hg versions.""" - if not os.path.isdir('a'): - print >> sys.stderr, 'error: comparison feature not initialized' - sys.exit(4) - - print('=' * 80) - print('WERKZEUG INTERNAL BENCHMARK -- COMPARE MODE'.center(80)) - print('-' * 80) - - def _error(msg): - print >> sys.stderr, 'error:', msg - sys.exit(1) - - def _hg_update(repo, node): - hg = lambda *x: subprocess.call(['hg'] + list(x), cwd=repo, - stdout=null_out, stderr=null_out) - hg('revert', '-a', '--no-backup') - client = subprocess.Popen(['hg', 'status', '--unknown', '-n', '-0'], - stdout=subprocess.PIPE, cwd=repo) - unknown = client.communicate()[0] - if unknown: - client = subprocess.Popen(['xargs', '-0', 'rm', '-f'], cwd=repo, - stdout=null_out, stdin=subprocess.PIPE) - client.communicate(unknown) - hg('pull', '../..') - hg('update', node) - if node == 'tip': - diff = subprocess.Popen(['hg', 'diff'], cwd='..', - stdout=subprocess.PIPE).communicate()[0] - if diff: - client = subprocess.Popen(['hg', 'import', '--no-commit', '-'], - cwd=repo, stdout=null_out, - stdin=subprocess.PIPE) - client.communicate(diff) - - _hg_update('a', node1) - _hg_update('b', node2) - d1 = run('a', no_header=True) - d2 = run('b', no_header=True) - - print('DIRECT COMPARISON'.center(80)) - print('-' * 80) - for key in sorted(d1): - delta = d1[key] - d2[key] - if abs(1 - d1[key] / d2[key]) < TOLERANCE or \ - abs(delta) < MIN_RESOLUTION: - delta = '==' - else: - delta = '%+.4f (%+d%%)' % \ - (delta, round(d2[key] / d1[key] * 100 - 100)) - print('%36s %.4f %.4f %s' % - (format_func(key), d1[key], d2[key], delta)) - print('-' * 80) - - -def run(path, no_header=False): - path = os.path.abspath(path) - wz_version, hg_tag = load_werkzeug(path) - result = {} - if not no_header: - print('=' * 80) - print('WERKZEUG INTERNAL BENCHMARK'.center(80)) - print('-' * 80) - print('Path: %s' % path) - print('Version: %s' % wz_version) - if hg_tag is not None: - print('HG Tag: %s' % hg_tag) - print('-' * 80) - for key, value in sorted(globals().items()): - if key.startswith('time_'): - before = globals().get('before_' + key[5:]) - if before: - before() - result[key] = bench(value) - after = globals().get('after_' + key[5:]) - if after: - after() - print('-' * 80) - return result - - -URL_DECODED_DATA = dict((str(x), str(x)) for x in xrange(100)) -URL_ENCODED_DATA = '&'.join('%s=%s' % x for x in URL_DECODED_DATA.items()) -MULTIPART_ENCODED_DATA = '\n'.join(( - '--foo', - 'Content-Disposition: form-data; name=foo', - '', - 'this is just bar', - '--foo', - 'Content-Disposition: form-data; name=bar', - '', - 'blafasel', - '--foo', - 'Content-Disposition: form-data; name=foo; filename=wzbench.py', - 'Content-Type: text/plain', - '', - open(__file__.rstrip('c')).read(), - '--foo--' -)) -MULTIDICT = None -REQUEST = None -TEST_ENV = None -LOCAL = None -LOCAL_MANAGER = None - - -def time_url_decode(): - wz.url_decode(URL_ENCODED_DATA) - - -def time_url_encode(): - wz.url_encode(URL_DECODED_DATA) - - -def time_parse_form_data_multipart(): - # use a hand written env creator so that we don't bench - # from_values which is known to be slowish in 0.5.1 and higher. - # we don't want to bench two things at once. - environ = { - 'REQUEST_METHOD': 'POST', - 'CONTENT_TYPE': 'multipart/form-data; boundary=foo', - 'wsgi.input': StringIO(MULTIPART_ENCODED_DATA), - 'CONTENT_LENGTH': str(len(MULTIPART_ENCODED_DATA)) - } - request = wz.Request(environ) - request.form - - -def before_multidict_lookup_hit(): - global MULTIDICT - MULTIDICT = wz.MultiDict({'foo': 'bar'}) - - -def time_multidict_lookup_hit(): - MULTIDICT['foo'] - - -def after_multidict_lookup_hit(): - global MULTIDICT - MULTIDICT = None - - -def before_multidict_lookup_miss(): - global MULTIDICT - MULTIDICT = wz.MultiDict() - - -def time_multidict_lookup_miss(): - try: - MULTIDICT['foo'] - except KeyError: - pass - - -def after_multidict_lookup_miss(): - global MULTIDICT - MULTIDICT = None - - -def time_cached_property(): - class Foo(object): - @wz.cached_property - def x(self): - return 42 - - f = Foo() - for x in xrange(60): - f.x - - -def before_request_form_access(): - global REQUEST - data = 'foo=bar&blah=blub' - REQUEST = wz.Request({ - 'CONTENT_LENGTH': str(len(data)), - 'wsgi.input': StringIO(data), - 'REQUEST_METHOD': 'POST', - 'wsgi.version': (1, 0), - 'QUERY_STRING': data, - 'CONTENT_TYPE': 'application/x-www-form-urlencoded', - 'PATH_INFO': '/', - 'SCRIPT_NAME': '' - }) - - -def time_request_form_access(): - for x in xrange(30): - REQUEST.path - REQUEST.script_root - REQUEST.args['foo'] - REQUEST.form['foo'] - - -def after_request_form_access(): - global REQUEST - REQUEST = None - - -def time_request_from_values(): - wz.Request.from_values(base_url='http://www.google.com/', - query_string='foo=bar&blah=blaz', - input_stream=StringIO(MULTIPART_ENCODED_DATA), - content_length=len(MULTIPART_ENCODED_DATA), - content_type='multipart/form-data; ' - 'boundary=foo', method='POST') - - -def before_request_shallow_init(): - global TEST_ENV - TEST_ENV = wz.create_environ() - - -def time_request_shallow_init(): - wz.Request(TEST_ENV, shallow=True) - - -def after_request_shallow_init(): - global TEST_ENV - TEST_ENV = None - - -def time_response_iter_performance(): - resp = wz.Response(u'Hällo Wörld ' * 1000, - mimetype='text/html') - for item in resp({'REQUEST_METHOD': 'GET'}, lambda *s: None): - pass - - -def time_response_iter_head_performance(): - resp = wz.Response(u'Hällo Wörld ' * 1000, - mimetype='text/html') - for item in resp({'REQUEST_METHOD': 'HEAD'}, lambda *s: None): - pass - - -def before_local_manager_dispatch(): - global LOCAL_MANAGER, LOCAL - LOCAL = wz.Local() - LOCAL_MANAGER = wz.LocalManager([LOCAL]) - - -def time_local_manager_dispatch(): - for x in xrange(10): - LOCAL.x = 42 - for x in xrange(10): - LOCAL.x - - -def after_local_manager_dispatch(): - global LOCAL_MANAGER, LOCAL - LOCAL = LOCAL_MANAGER = None - - -def before_html_builder(): - global TABLE - TABLE = [['col 1', 'col 2', 'col 3', '4', '5', '6'] for x in range(10)] - - -def time_html_builder(): - html_rows = [] - for row in TABLE: # noqa - html_cols = [wz.html.td(col, class_='col') for col in row] - html_rows.append(wz.html.tr(class_='row', *html_cols)) - wz.html.table(*html_rows) - - -def after_html_builder(): - global TABLE - TABLE = None - - -if __name__ == '__main__': - os.chdir(os.path.dirname(__file__) or os.path.curdir) - try: - main() - except KeyboardInterrupt: - print >> sys.stderr, 'interrupted!' diff --git a/docs/Makefile b/docs/Makefile index 52d78d9ef..51285967a 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -1,118 +1,19 @@ -# Makefile for Sphinx documentation +# Minimal makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build -PAPER = +SOURCEDIR = . BUILDDIR = _build -# Internal variables. -PAPEROPT_a4 = -D latex_paper_size=a4 -PAPEROPT_letter = -D latex_paper_size=letter -ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . - -.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp epub latex changes linkcheck doctest - +# Put it first so that "make" without argument is like "make help". help: - @echo "Please use \`make ' where is one of" - @echo " html to make standalone HTML files" - @echo " dirhtml to make HTML files named index.html in directories" - @echo " singlehtml to make a single large HTML file" - @echo " pickle to make pickle files" - @echo " json to make JSON files" - @echo " htmlhelp to make HTML files and a HTML help project" - @echo " qthelp to make HTML files and a qthelp project" - @echo " devhelp to make HTML files and a Devhelp project" - @echo " epub to make an epub" - @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" - @echo " latexpdf to make LaTeX files and run them through pdflatex" - @echo " changes to make an overview of all changed/added/deprecated items" - @echo " linkcheck to check all external links for integrity" - @echo " doctest to run all doctests embedded in the documentation (if enabled)" - -clean: - -rm -rf $(BUILDDIR)/* - -html: - $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html - @echo - @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." - -dirhtml: - $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml - @echo - @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." - -singlehtml: - $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml - @echo - @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." - -pickle: - $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle - @echo - @echo "Build finished; now you can process the pickle files." - -json: - $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json - @echo - @echo "Build finished; now you can process the JSON files." - -htmlhelp: - $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp - @echo - @echo "Build finished; now you can run HTML Help Workshop with the" \ - ".hhp project file in $(BUILDDIR)/htmlhelp." - -qthelp: - $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp - @echo - @echo "Build finished; now you can run "qcollectiongenerator" with the" \ - ".qhcp project file in $(BUILDDIR)/qthelp, like this:" - @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/Flask.qhcp" - @echo "To view the help file:" - @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Flask.qhc" - -devhelp: - $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) _build/devhelp - @echo - @echo "Build finished." - @echo "To view the help file:" - @echo "# mkdir -p $$HOME/.local/share/devhelp/Flask" - @echo "# ln -s _build/devhelp $$HOME/.local/share/devhelp/Flask" - @echo "# devhelp" - -epub: - $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub - @echo - @echo "Build finished. The epub file is in $(BUILDDIR)/epub." - -latex: - $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex - @echo - @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." - @echo "Run \`make all-pdf' or \`make all-ps' in that directory to" \ - "run these through (pdf)latex." - -latexpdf: latex - $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) _build/latex - @echo "Running LaTeX files through pdflatex..." - make -C _build/latex all-pdf - @echo "pdflatex finished; the PDF files are in _build/latex." - -changes: - $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes - @echo - @echo "The overview file is in $(BUILDDIR)/changes." + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) -linkcheck: - $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck - @echo - @echo "Link check complete; look for any errors in the above output " \ - "or in $(BUILDDIR)/linkcheck/output.txt." +.PHONY: help Makefile -doctest: - $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest - @echo "Testing of doctests in the sources finished, look at the " \ - "results in $(BUILDDIR)/doctest/output.txt." +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/docs/_static/background.png b/docs/_static/background.png deleted file mode 100644 index 928957aa3..000000000 Binary files a/docs/_static/background.png and /dev/null differ diff --git a/docs/_static/codebackground.png b/docs/_static/codebackground.png deleted file mode 100644 index 3368da2ea..000000000 Binary files a/docs/_static/codebackground.png and /dev/null differ diff --git a/docs/_static/contents.png b/docs/_static/contents.png deleted file mode 100644 index 6f993b5e3..000000000 Binary files a/docs/_static/contents.png and /dev/null differ diff --git a/docs/_static/header.png b/docs/_static/header.png deleted file mode 100644 index 30d4e3adc..000000000 Binary files a/docs/_static/header.png and /dev/null differ diff --git a/docs/_static/navigation.png b/docs/_static/navigation.png deleted file mode 100644 index 1081dc143..000000000 Binary files a/docs/_static/navigation.png and /dev/null differ diff --git a/docs/_static/navigation_active.png b/docs/_static/navigation_active.png deleted file mode 100644 index c82b875a3..000000000 Binary files a/docs/_static/navigation_active.png and /dev/null differ diff --git a/docs/_static/shorty-screenshot.png b/docs/_static/shorty-screenshot.png deleted file mode 100644 index c20c935ed..000000000 Binary files a/docs/_static/shorty-screenshot.png and /dev/null differ diff --git a/docs/_static/style.css b/docs/_static/style.css deleted file mode 100644 index bdc61c74d..000000000 --- a/docs/_static/style.css +++ /dev/null @@ -1,423 +0,0 @@ -body { - font-family: 'Lucida Grande', 'Lucida Sans Unicode', 'Geneva', 'Verdana', sans-serif; - font-size: 14px; - letter-spacing: -0.01em; - line-height: 150%; - text-align: center; - background: #AFC1C4 url(background.png); - color: black; - margin: 0; - padding: 0; -} - -a { - color: #CA7900; - text-decoration: none; -} - -a:hover { - color: #2491CF; -} - -pre { - font-family: 'Consolas', 'Deja Vu Sans Mono', 'Bitstream Vera Sans Mono', monospace; - font-size: 0.85em; - letter-spacing: 0.015em; - padding: 0.3em 0.7em; - border: 1px solid #aaa; - border-right-color: #ddd; - border-bottom-color: #ddd; - background: #f8f8f8 url(codebackground.png); -} - -cite, code, tt { - font-family: 'Consolas', 'Deja Vu Sans Mono', 'Bitstream Vera Sans Mono', monospace; - font-size: 0.95em; - letter-spacing: 0.01em; - font-style: normal; -} - -tt { - background-color: #f2f2f2; - border-bottom: 1px solid #ddd; - color: #333; -} - -tt.func-signature { - font-family: 'Lucida Grande', 'Lucida Sans Unicode', 'Geneva', 'Verdana', sans-serif; - font-size: 0.85em; - background-color: transparent; - border-bottom: none; - color: #555; -} - -dt { - margin-top: 0.8em; -} - -dd p.first { - margin-top: 0; -} - -dd p.last { - margin-bottom: 0; -} - -pre { - line-height: 150%; -} - -pre a { - color: inherit; - text-decoration: underline; -} - -div.syntax { - background-color: transparent; -} - -div.page { - background: white url(contents.png) 0 130px; - border: 1px solid #aaa; - width: 740px; - margin: 20px auto 20px auto; - text-align: left; -} - -div.header { - background-image: url(header.png); - height: 100px; - border-bottom: 1px solid #aaa; -} - -div.header h1 { - float: right; - position: absolute; - margin: -43px 0 0 585px; - height: 180px; - width: 180px; -} - -div.header h1 a { - display: block; - background-image: url(werkzeug.png); - background-repeat: no-repeat; - height: 180px; - width: 180px; - text-decoration: none; - color: white!important; -} - -div.header span { - display: none; -} - -div.header p { - background-image: url(header_invert.png); - margin: 0; - padding: 10px; - height: 80px; - color: white; - display: none; -} - -ul.navigation { - background-image: url(navigation.png); - height: 2em; - list-style: none; - border-top: 1px solid #ddd; - border-bottom: 1px solid #ddd; - margin: 0; - padding: 0; -} - -ul.navigation li { - margin: 0; - padding: 0; - height: 2em; - line-height: 1.75em; - float: left; -} - -ul.navigation li a { - margin: 0; - padding: 0 10px 0 10px; - color: #EE9816; -} - -ul.navigation li a:hover { - color: #3CA8E7; -} - -ul.navigation li.active { - background-image: url(navigation_active.png); -} - -ul.navigation li.active a { - color: black; -} - -ul.navigation li.indexlink a { - font-size: 0.9em; - font-weight: bold; - color: #11557C; -} - -div.body { - margin: 0 20px 0 20px; - padding: 0.5em 0 20px 0; -} - -p { - margin: 0.8em 0 0.5em 0; -} - -h1 { - margin: 0; - padding: 0.7em 0 0.3em 0; - font-size: 1.5em; - color: #11557C; -} - -h2 { - margin: 1.3em 0 0.2em 0; - font-size: 1.35em; - padding: 0; -} - -h3 { - margin: 1em 0 -0.3em 0; -} - -h2 a, h3 a, h4 a, h5 a, h6 a { - color: black!important; -} - -a.headerlink { - color: #B4B4B4!important; - font-size: 0.8em; - padding: 0 4px 0 4px; - text-decoration: none!important; - visibility: hidden; -} - -h1:hover > a.headerlink, -h2:hover > a.headerlink, -h3:hover > a.headerlink, -h4:hover > a.headerlink, -h5:hover > a.headerlink, -h6:hover > a.headerlink, -dt:hover > a.headerlink { - visibility: visible; -} - -a.headerlink:hover { - background-color: #B4B4B4; - color: #F0F0F0!important; -} - -table { - border-collapse: collapse; - margin: 0 -0.5em 0 -0.5em; -} - -table td, table th { - padding: 0.2em 0.5em 0.2em 0.5em; -} - -div.footer { - background-color: #E3EFF1; - color: #86989B; - padding: 3px 8px 3px 0; - clear: both; - font-size: 0.8em; - text-align: right; -} - -div.footer a { - color: #86989B; - text-decoration: underline; -} - -div.toc { - float: right; - background-color: white; - border: 1px solid #86989B; - padding: 0; - margin: 0 0 1em 1em; - width: 10em; -} - -div.toc h4 { - margin: 0; - font-size: 0.9em; - padding: 0.1em 0 0.1em 0.6em; - margin: 0; - color: white; - border-bottom: 1px solid #86989B; - background-color: #AFC1C4; -} - -div.toc ul { - margin: 1em 0 1em 0; - padding: 0 0 0 1em; - list-style: none; -} - -div.toc ul li { - margin: 0.5em 0 0.5em 0; - font-size: 0.9em; - line-height: 130%; -} - -div.toc ul li p { - margin: 0; - padding: 0; -} - -div.toc ul ul { - margin: 0.2em 0 0.2em 0; - padding: 0 0 0 1.8em; -} - -div.toc ul ul li { - padding: 0; -} - -div.admonition, div.warning, div#toc { - font-size: 0.9em; - margin: 1em 0 0 0; - border: 1px solid #86989B; - background-color: #f7f7f7; -} - -div.admonition p, div.warning p, div#toc p { - margin: 0.5em 1em 0.5em 1em; - padding: 0; -} - -div.admonition pre, div.warning pre, div#toc pre { - margin: 0.4em 1em 0.4em 1em; -} - -div.admonition p.admonition-title, -div.warning p.admonition-title, -div#toc h3 { - margin: 0; - padding: 0.1em 0 0.1em 0.5em; - color: white; - border-bottom: 1px solid #86989B; - font-weight: bold; - background-color: #AFC1C4; -} - -div.warning { - border: 1px solid #940000; -} - -div.warning p.admonition-title { - background-color: #CF0000; - border-bottom-color: #940000; -} - -div.admonition ul, div.admonition ol, -div.warning ul, div.warning ol, -div#toc ul, div#toc ol { - margin: 0.1em 0.5em 0.5em 3em; - padding: 0; -} - -div#toc div.inner { - border-top: 1px solid #86989B; - padding: 10px; -} - -div#toc h3 { - border-bottom: none; - cursor: pointer; - font-size: 13px; -} - -div#toc h3:hover { - background-color: #86989B; -} - -div#toc ul { - margin: 2px 0 2px 20px; - padding: 0; -} - -div#toc ul li { - line-height: 125%; -} - -dl.function dt, -dl.class dt, -dl.exception dt, -dl.method dt, -dl.attribute dt { - font-weight: normal; -} - -dt .descname { - font-weight: bold; - margin-right: 4px; -} - -dt .descname, dt .descclassname { - padding: 0; - background: transparent; - border-bottom: 1px solid #111; -} - -dt .descclassname { - margin-left: 2px; -} - -dl dt big { - font-size: 100%; -} - -dl p { - margin: 0; -} - -dl p + p { - margin-top: 10px; -} - -span.versionmodified { - color: #4B4A49; - font-weight: bold; -} - -span.versionadded { - color: #30691A; - font-weight: bold; -} - -table.field-list td.field-body ul.simple { - margin: 0; - padding: 0!important; - list-style: none; -} - -table.indextable td { - width: 50%; - vertical-align: top; -} - -table.indextable dt { - margin: 0; -} - -table.indextable dd dt a { - color: black!important; - font-size: 0.8em; -} - -div.jumpbox { - padding: 1em 0 0.4em 0; - border-bottom: 1px solid #ddd; - color: #aaa; -} diff --git a/docs/_static/werkzeug.js b/docs/_static/werkzeug.js deleted file mode 100644 index 6ab549a33..000000000 --- a/docs/_static/werkzeug.js +++ /dev/null @@ -1,10 +0,0 @@ -(function() { - Werkzeug = {}; - - $(function() { - $('#toc h3').click(function() { - $(this).next().slideToggle(); - $(this).parent().toggleClass('toc-collapsed'); - }).next().hide().parent().addClass('toc-collapsed'); - }); -})(); diff --git a/docs/_templates/sidebarintro.html b/docs/_templates/sidebarintro.html deleted file mode 100644 index 80eabe615..000000000 --- a/docs/_templates/sidebarintro.html +++ /dev/null @@ -1,19 +0,0 @@ -

About Werkzeug

-

- Werkzeug is a WSGI utility library. It can serve as the basis for a - custom framework. -

-

Other Formats

-

- You can download the documentation in other formats as well: -

- -

Useful Links

- diff --git a/docs/_templates/sidebarlogo.html b/docs/_templates/sidebarlogo.html deleted file mode 100644 index c1a7ba7a7..000000000 --- a/docs/_templates/sidebarlogo.html +++ /dev/null @@ -1,3 +0,0 @@ - diff --git a/docs/_themes/LICENSE b/docs/_themes/LICENSE deleted file mode 100644 index 08cbb7fc6..000000000 --- a/docs/_themes/LICENSE +++ /dev/null @@ -1,37 +0,0 @@ -Copyright (c) 2011 by Armin Ronacher. - -Some rights reserved. - -Redistribution and use in source and binary forms of the theme, with or -without modification, are permitted provided that the following conditions -are met: - -* Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - -* Redistributions in binary form must reproduce the above - copyright notice, this list of conditions and the following - disclaimer in the documentation and/or other materials provided - with the distribution. - -* The names of the contributors may not be used to endorse or - promote products derived from this software without specific - prior written permission. - -We kindly ask you to only use these themes in an unmodified manner just -for Flask and Flask-related products, not for unrelated projects. If you -like the visual style and want to use it for your own projects, please -consider making some larger changes to the themes (such as changing -font faces, sizes, colors or margins). - -THIS THEME IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE -LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -ARISING IN ANY WAY OUT OF THE USE OF THIS THEME, EVEN IF ADVISED OF THE -POSSIBILITY OF SUCH DAMAGE. diff --git a/docs/_themes/README b/docs/_themes/README deleted file mode 100644 index b3292bdff..000000000 --- a/docs/_themes/README +++ /dev/null @@ -1,31 +0,0 @@ -Flask Sphinx Styles -=================== - -This repository contains sphinx styles for Flask and Flask related -projects. To use this style in your Sphinx documentation, follow -this guide: - -1. put this folder as _themes into your docs folder. Alternatively - you can also use git submodules to check out the contents there. -2. add this to your conf.py: - - sys.path.append(os.path.abspath('_themes')) - html_theme_path = ['_themes'] - html_theme = 'flask' - -The following themes exist: - -- 'flask' - the standard flask documentation theme for large - projects -- 'flask_small' - small one-page theme. Intended to be used by - very small addon libraries for flask. - -The following options exist for the flask_small theme: - - [options] - index_logo = '' filename of a picture in _static - to be used as replacement for the - h1 in the index.rst file. - index_logo_height = 120px height of the index logo - github_fork = '' repository name on github for the - "fork me" badge diff --git a/docs/_themes/werkzeug/layout.html b/docs/_themes/werkzeug/layout.html deleted file mode 100644 index a0c9cab04..000000000 --- a/docs/_themes/werkzeug/layout.html +++ /dev/null @@ -1,8 +0,0 @@ -{%- extends "basic/layout.html" %} -{%- block relbar2 %}{% endblock %} -{%- block footer %} - -{%- endblock %} diff --git a/docs/_themes/werkzeug/relations.html b/docs/_themes/werkzeug/relations.html deleted file mode 100644 index 3bbcde85b..000000000 --- a/docs/_themes/werkzeug/relations.html +++ /dev/null @@ -1,19 +0,0 @@ -

Related Topics

- diff --git a/docs/_themes/werkzeug/static/werkzeug.css_t b/docs/_themes/werkzeug/static/werkzeug.css_t deleted file mode 100644 index 0193276f0..000000000 --- a/docs/_themes/werkzeug/static/werkzeug.css_t +++ /dev/null @@ -1,395 +0,0 @@ -/* - * werkzeug.css_t - * ~~~~~~~~~~~~~~ - * - * :copyright: Copyright 2011 by Armin Ronacher. - * :license: Flask Design License, see LICENSE for details. - */ - -{% set page_width = '940px' %} -{% set sidebar_width = '220px' %} -{% set font_family = "'Lucida Grande', 'Lucida Sans Unicode', 'Geneva', 'Verdana', sans-serif" %} -{% set header_font_family = "'Ubuntu', " ~ font_family %} - -@import url("basic.css"); -@import url(http://fonts.googleapis.com/css?family=Ubuntu); - -/* -- page layout ----------------------------------------------------------- */ - -body { - font-family: {{ font_family }}; - font-size: 15px; - background-color: white; - color: #000; - margin: 0; - padding: 0; -} - -div.document { - width: {{ page_width }}; - margin: 30px auto 0 auto; -} - -div.documentwrapper { - float: left; - width: 100%; -} - -div.bodywrapper { - margin: 0 0 0 {{ sidebar_width }}; -} - -div.sphinxsidebar { - width: {{ sidebar_width }}; -} - -hr { - border: 1px solid #B1B4B6; -} - -div.body { - background-color: #ffffff; - color: #3E4349; - padding: 0 30px 0 30px; -} - -img.floatingflask { - padding: 0 0 10px 10px; - float: right; -} - -div.footer { - width: {{ page_width }}; - margin: 20px auto 30px auto; - font-size: 14px; - color: #888; - text-align: right; -} - -div.footer a { - color: #888; -} - -div.related { - display: none; -} - -div.sphinxsidebar a { - color: #444; - text-decoration: none; - border-bottom: 1px dotted #999; -} - -div.sphinxsidebar a:hover { - border-bottom: 1px solid #999; -} - -div.sphinxsidebar { - font-size: 13px; - line-height: 1.5; -} - -div.sphinxsidebarwrapper { - padding: 18px 10px; -} - -div.sphinxsidebarwrapper p.logo { - padding: 0 0 20px 0; - margin: 0; - text-align: center; -} - -div.sphinxsidebar h3, -div.sphinxsidebar h4 { - font-family: {{ font_family }}; - color: #444; - font-size: 24px; - font-weight: normal; - margin: 0 0 5px 0; - padding: 0; -} - -div.sphinxsidebar h4 { - font-size: 20px; -} - -div.sphinxsidebar h3 a { - color: #444; -} - -div.sphinxsidebar p.logo a, -div.sphinxsidebar h3 a, -div.sphinxsidebar p.logo a:hover, -div.sphinxsidebar h3 a:hover { - border: none; -} - -div.sphinxsidebar p { - color: #555; - margin: 10px 0; -} - -div.sphinxsidebar ul { - margin: 10px 0; - padding: 0; - color: #000; -} - -div.sphinxsidebar input { - border: 1px solid #ccc; - font-family: {{ font_family }}; - font-size: 14px; -} - -div.sphinxsidebar form.search input[name="q"] { - width: 130px; -} - -/* -- body styles ----------------------------------------------------------- */ - -a { - color: #185F6D; - text-decoration: underline; -} - -a:hover { - color: #2794AA; - text-decoration: underline; -} - -div.body h1, -div.body h2, -div.body h3, -div.body h4, -div.body h5, -div.body h6 { - font-family: {{ header_font_family }}; - font-weight: normal; - margin: 30px 0px 10px 0px; - padding: 0; - color: black; -} - -div.body h1 { margin-top: 0; padding-top: 0; font-size: 240%; } -div.body h2 { font-size: 180%; } -div.body h3 { font-size: 150%; } -div.body h4 { font-size: 130%; } -div.body h5 { font-size: 100%; } -div.body h6 { font-size: 100%; } - -a.headerlink { - color: #ddd; - padding: 0 4px; - text-decoration: none; -} - -a.headerlink:hover { - color: #444; - background: #eaeaea; -} - -div.body p, div.body dd, div.body li { - line-height: 1.4em; -} - -div.admonition { - background: #fafafa; - margin: 20px -30px; - padding: 10px 30px; - border-top: 1px solid #ccc; - border-bottom: 1px solid #ccc; -} - -div.admonition tt.xref, div.admonition a tt { - border-bottom: 1px solid #fafafa; -} - -dd div.admonition { - margin-left: -60px; - padding-left: 60px; -} - -div.admonition p.admonition-title { - font-family: {{ font_family }}; - font-weight: normal; - font-size: 24px; - margin: 0 0 10px 0; - padding: 0; - line-height: 1; -} - -div.admonition p.last { - margin-bottom: 0; -} - -div.highlight { - background-color: white; -} - -dt:target, .highlight { - background: #FAF3E8; -} - -div.note { - background-color: #eee; - border: 1px solid #ccc; -} - -div.seealso { - background-color: #ffc; - border: 1px solid #ff6; -} - -div.topic { - background-color: #eee; -} - -p.admonition-title { - display: inline; -} - -p.admonition-title:after { - content: ":"; -} - -pre, tt { - font-family: 'Consolas', 'Menlo', 'Deja Vu Sans Mono', 'Bitstream Vera Sans Mono', monospace; - font-size: 0.9em; -} - -img.screenshot { -} - -tt.descname, tt.descclassname { - font-size: 0.95em; -} - -tt.descname { - padding-right: 0.08em; -} - -img.screenshot { - -moz-box-shadow: 2px 2px 4px #eee; - -webkit-box-shadow: 2px 2px 4px #eee; - box-shadow: 2px 2px 4px #eee; -} - -table.docutils { - border: 1px solid #888; - -moz-box-shadow: 2px 2px 4px #eee; - -webkit-box-shadow: 2px 2px 4px #eee; - box-shadow: 2px 2px 4px #eee; -} - -table.docutils td, table.docutils th { - border: 1px solid #888; - padding: 0.25em 0.7em; -} - -table.field-list, table.footnote { - border: none; - -moz-box-shadow: none; - -webkit-box-shadow: none; - box-shadow: none; -} - -table.footnote { - margin: 15px 0; - width: 100%; - border: 1px solid #eee; - background: #fdfdfd; - font-size: 0.9em; -} - -table.footnote + table.footnote { - margin-top: -15px; - border-top: none; -} - -table.field-list th { - padding: 0 0.8em 0 0; -} - -table.field-list td { - padding: 0; -} - -table.footnote td.label { - width: 0px; - padding: 0.3em 0 0.3em 0.5em; -} - -table.footnote td { - padding: 0.3em 0.5em; -} - -dl { - margin: 0; - padding: 0; -} - -dl dd { - margin-left: 30px; -} - -blockquote { - margin: 0 0 0 30px; - padding: 0; -} - -ul, ol { - margin: 10px 0 10px 30px; - padding: 0; -} - -pre { - background: #E8EFF0; - padding: 7px 30px; - margin: 15px -30px; - line-height: 1.3em; -} - -dl pre, blockquote pre, li pre { - margin-left: -60px; - padding-left: 60px; -} - -dl dl pre { - margin-left: -90px; - padding-left: 90px; -} - -tt { - background-color: #E8EFF0; - color: #222; - /* padding: 1px 2px; */ -} - -tt.xref, a tt { - background-color: #E8EFF0; - border-bottom: 1px solid white; -} - -a.reference { - text-decoration: none; - border-bottom: 1px dotted #2BABC4; -} - -a.reference:hover { - border-bottom: 1px solid #2794AA; -} - -a.footnote-reference { - text-decoration: none; - font-size: 0.7em; - vertical-align: top; - border-bottom: 1px dotted #004B6B; -} - -a.footnote-reference:hover { - border-bottom: 1px solid #6D4100; -} - -a:hover tt { - background: #EEE; -} diff --git a/docs/_themes/werkzeug/theme.conf b/docs/_themes/werkzeug/theme.conf deleted file mode 100644 index d9c8dbba0..000000000 --- a/docs/_themes/werkzeug/theme.conf +++ /dev/null @@ -1,4 +0,0 @@ -[theme] -inherit = basic -stylesheet = werkzeug.css -pygments_style = werkzeug_theme_support.WerkzeugStyle diff --git a/docs/_themes/werkzeug_theme_support.py b/docs/_themes/werkzeug_theme_support.py deleted file mode 100644 index b138a9293..000000000 --- a/docs/_themes/werkzeug_theme_support.py +++ /dev/null @@ -1,85 +0,0 @@ -from pygments.style import Style -from pygments.token import Keyword, Name, Comment, String, Error, \ - Number, Operator, Generic, Whitespace, Punctuation, Other, Literal - - -class WerkzeugStyle(Style): - background_color = "#f8f8f8" - default_style = "" - - styles = { - # No corresponding class for the following: - #Text: "", # class: '' - Whitespace: "underline #f8f8f8", # class: 'w' - Error: "#a40000 border:#ef2929", # class: 'err' - Other: "#000000", # class 'x' - - Comment: "italic #8f5902", # class: 'c' - Comment.Preproc: "noitalic", # class: 'cp' - - Keyword: "bold #004461", # class: 'k' - Keyword.Constant: "bold #004461", # class: 'kc' - Keyword.Declaration: "bold #004461", # class: 'kd' - Keyword.Namespace: "bold #004461", # class: 'kn' - Keyword.Pseudo: "bold #004461", # class: 'kp' - Keyword.Reserved: "bold #004461", # class: 'kr' - Keyword.Type: "bold #004461", # class: 'kt' - - Operator: "#582800", # class: 'o' - Operator.Word: "bold #004461", # class: 'ow' - like keywords - - Punctuation: "bold #000000", # class: 'p' - - # because special names such as Name.Class, Name.Function, etc. - # are not recognized as such later in the parsing, we choose them - # to look the same as ordinary variables. - Name: "#000000", # class: 'n' - Name.Attribute: "#c4a000", # class: 'na' - to be revised - Name.Builtin: "#004461", # class: 'nb' - Name.Builtin.Pseudo: "#3465a4", # class: 'bp' - Name.Class: "#000000", # class: 'nc' - to be revised - Name.Constant: "#000000", # class: 'no' - to be revised - Name.Decorator: "#1B5C66", # class: 'nd' - to be revised - Name.Entity: "#ce5c00", # class: 'ni' - Name.Exception: "bold #cc0000", # class: 'ne' - Name.Function: "#000000", # class: 'nf' - Name.Property: "#000000", # class: 'py' - Name.Label: "#f57900", # class: 'nl' - Name.Namespace: "#000000", # class: 'nn' - to be revised - Name.Other: "#000000", # class: 'nx' - Name.Tag: "bold #004461", # class: 'nt' - like a keyword - Name.Variable: "#000000", # class: 'nv' - to be revised - Name.Variable.Class: "#000000", # class: 'vc' - to be revised - Name.Variable.Global: "#000000", # class: 'vg' - to be revised - Name.Variable.Instance: "#000000", # class: 'vi' - to be revised - - Number: "#990000", # class: 'm' - - Literal: "#000000", # class: 'l' - Literal.Date: "#000000", # class: 'ld' - - String: "#4e9a06", # class: 's' - String.Backtick: "#4e9a06", # class: 'sb' - String.Char: "#4e9a06", # class: 'sc' - String.Doc: "italic #8f5902", # class: 'sd' - like a comment - String.Double: "#4e9a06", # class: 's2' - String.Escape: "#4e9a06", # class: 'se' - String.Heredoc: "#4e9a06", # class: 'sh' - String.Interpol: "#4e9a06", # class: 'si' - String.Other: "#4e9a06", # class: 'sx' - String.Regex: "#4e9a06", # class: 'sr' - String.Single: "#4e9a06", # class: 's1' - String.Symbol: "#4e9a06", # class: 'ss' - - Generic: "#000000", # class: 'g' - Generic.Deleted: "#a40000", # class: 'gd' - Generic.Emph: "italic #000000", # class: 'ge' - Generic.Error: "#ef2929", # class: 'gr' - Generic.Heading: "bold #000080", # class: 'gh' - Generic.Inserted: "#00A000", # class: 'gi' - Generic.Output: "#888", # class: 'go' - Generic.Prompt: "#745334", # class: 'gp' - Generic.Strong: "bold #000000", # class: 'gs' - Generic.Subheading: "bold #800080", # class: 'gu' - Generic.Traceback: "bold #a40000", # class: 'gt' - } diff --git a/docs/changes.rst b/docs/changes.rst index 4e4a409a9..955deaf27 100644 --- a/docs/changes.rst +++ b/docs/changes.rst @@ -1,109 +1,4 @@ -================== -Werkzeug Changelog -================== +Changes +======= -.. module:: werkzeug - -This file lists all major changes in Werkzeug over the versions. -For API breaking changes have a look at :ref:`api-changes`, they -are listed there in detail. - -.. include:: ../CHANGES - -.. _api-changes: - -API Changes -=========== - -`0.9` - - Soft-deprecated the :attr:`BaseRequest.data` and - :attr:`BaseResponse.data` attributes and introduced new methods - to interact with entity data. This will allows in the future to - make better APIs to deal with request and response entity - bodies. So far there is no deprecation warning but users are - strongly encouraged to update. - - The :class:`Headers` and :class:`EnvironHeaders` datastructures - are now designed to operate on unicode data. This is a backwards - incompatible change and was necessary for the Python 3 support. - - The :class:`Headers` object no longer supports in-place operations - through the old ``linked`` method. This has been removed without - replacement due to changes on the encoding model. - -`0.6.2` - - renamed the attribute `implicit_seqence_conversion` attribute of - the request object to `implicit_sequence_conversion`. Because - this is a feature that is typically unused and was only in there - for the 0.6 series we consider this a bug that does not require - backwards compatibility support which would be impossible to - properly implement. - -`0.6` - - Old deprecations were removed. - - `cached_property.writeable` was deprecated. - - :meth:`BaseResponse.get_wsgi_headers` replaces the older - `BaseResponse.fix_headers` method. The older method stays - around for backwards compatibility reasons until 0.7. - - `BaseResponse.header_list` was deprecated. You should not - need this function, `get_wsgi_headers` and the `to_list` - method on the regular headers should serve as a replacement. - - Deprecated `BaseResponse.iter_encoded`'s charset parameter. - - :class:`LimitedStream` non-silent usage was deprecated. - - the `__repr__` of HTTP exceptions changed. This might break - doctests. - -`0.5` - - Werkzeug switched away from wsgiref as library for the builtin - webserver. - - The `encoding` parameter for :class:`Template`\s is now called - `charset`. The older one will work for another two versions - but warn with a :exc:`DeprecationWarning`. - - The :class:`Client` has cookie support now which is enabled - by default. - - :meth:`BaseResponse._get_file_stream` is now passed more parameters - to make the function more useful. In 0.6 the old way to invoke - the method will no longer work. To support both newer and older - Werkzeug versions you can add all arguments to the signature and - provide default values for each of them. - - :func:`url_decode` no longer supports both `&` and `;` as - separator. This has to be specified explicitly now. - - The request object is now enforced to be read-only for all - attributes. If your code relies on modifications of some values - makes sure to create copies of them using the mutable counterparts! - - Some data structures that were only used on request objects are - now immutable as well. (:class:`Authorization` / :class:`Accept` - and subclasses) - - `CacheControl` was split up into :class:`RequestCacheControl` - and :class:`ResponseCacheControl`, the former being immutable. - The old class will go away in 0.6 - - undocumented `werkzeug.test.File` was replaced by - :class:`FileWrapper`. - - it's not longer possible to pass dicts inside the `data` dict - in :class:`Client`. Use tuples instead. - - It's save to modify the return value of :meth:`MultiDict.getlist` - and methods that return lists in the :class:`MultiDict` now. The - class creates copies instead of revealing the internal lists. - However :class:`MultiDict.setlistdefault` still (and intentionally) - returns the internal list for modifications. - -`0.3` - - Werkzeug 0.3 will be the last release with Python 2.3 compatibility. - - The `environ_property` is now read-only by default. This decision was - made because the request in general should be considered read-only. - -`0.2` - - The `BaseReporterStream` is now part of the contrib module, the - new module is `werkzeug.contrib.reporterstream`. Starting with - `0.3`, the old import will not work any longer. - - `RequestRedirect` now uses a 301 status code. Previously a 302 - status code was used incorrectly. If you want to continue using - this 302 code, use ``response = redirect(e.new_url, 302)``. - - `lazy_property` is now called `cached_property`. The alias for - the old name will disappear in Werkzeug 0.3. - - `match` can now raise `MethodNotAllowed` if configured for - methods and there was no method for that request. - - The `response_body` attribute on the response object is now called - `data`. With Werkzeug 0.3 the old name will not work any longer. - - The file-like methods on the response object are deprecated. If - you want to use the response object as file like object use the - `Response` class or a subclass of `BaseResponse` and mix the new - `ResponseStreamMixin` class and use `response.stream`. +.. include:: ../CHANGES.rst diff --git a/docs/conf.py b/docs/conf.py index 540ae20d8..96e998b80 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,214 +1,55 @@ -# -*- coding: utf-8 -*- -# -# Werkzeug documentation build configuration file, created by -# sphinx-quickstart on Fri Jan 16 23:10:43 2009. -# -# This file is execfile()d with the current directory set to its containing dir. -# -# The contents of this file are pickled, so don't put values in the namespace -# that aren't pickleable (module imports are okay, they're removed automatically). -# -# Note that not all possible configuration values are present in this -# autogenerated file. -# -# All configuration values have a default; values that are commented out -# serve to show the default. +from pallets_sphinx_themes import get_version +from pallets_sphinx_themes import ProjectLink -import sys, os +# Project -------------------------------------------------------------- -# If your extensions are in another directory, add it here. If the directory -# is relative to the documentation root, use os.path.abspath to make it -# absolute, like shown here. -sys.path.append(os.path.abspath('.')) -sys.path.append(os.path.abspath('_themes')) +project = "Werkzeug" +copyright = "2007 Pallets" +author = "Pallets" +release, version = get_version("Werkzeug") -# General configuration -# --------------------- +# General -------------------------------------------------------------- -# Add any Sphinx extension module names here, as strings. They can be extensions -# coming with Sphinx (named 'sphinx.ext.*') or your custom ones. -extensions = ['sphinx.ext.autodoc', 'sphinx.ext.intersphinx', - 'sphinx.ext.doctest', 'werkzeugext'] - -# Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] - -# The suffix of source filenames. -source_suffix = '.rst' - -# The encoding of source files. -#source_encoding = 'utf-8' - -# The master toctree document. -master_doc = 'index' - -# General information about the project. -project = u'Werkzeug' -copyright = u'2011, The Werkzeug Team' - -# The version info for the project you're documenting, acts as replacement for -# |version| and |release|, also used in various other places throughout the -# built documents. - -import re -try: - import werkzeug -except ImportError: - sys.path.append(os.path.abspath('../')) -from werkzeug import __version__ as release -if 'dev' in release: - release = release[:release.find('dev') + 3] -if release == 'unknown': - version = release -else: - version = re.match(r'\d+\.\d+(?:\.\d+)?', release).group() - -# The language for content autogenerated by Sphinx. Refer to documentation -# for a list of supported languages. -#language = None - -# There are two options for replacing |today|: either, you set today to some -# non-false value, then it is used: -#today = '' -# Else, today_fmt is used as the format for a strftime call. -#today_fmt = '%B %d, %Y' - -# List of documents that shouldn't be included in the build. -#unused_docs = [] - -# List of directories, relative to source directory, that shouldn't be searched -# for source files. -exclude_trees = ['_build'] - -# The reST default role (used for this markup: `text`) to use for all documents. -#default_role = None - -# If true, '()' will be appended to :func: etc. cross-reference text. -add_function_parentheses = True - -# If true, the current module name will be prepended to all description -# unit titles (such as .. function::). -#add_module_names = True - -# If true, sectionauthor and moduleauthor directives will be shown in the -# output. They are ignored by default. -#show_authors = False - -# The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'werkzeug_theme_support.WerkzeugStyle' - -# doctest setup code -doctest_global_setup = '''\ -from werkzeug import * -''' - - -# Options for HTML output -# ----------------------- - -html_theme = 'werkzeug' -html_theme_path = ['_themes'] - -# The name for this set of Sphinx documents. If None, it defaults to -# " v documentation". -#html_title = None - -# A shorter title for the navigation bar. Default is the same as html_title. -#html_short_title = None - -# The name of an image file (relative to this directory) to place at the top -# of the sidebar. -#html_logo = None - -# The name of an image file (within the static path) to use as favicon of the -# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 -# pixels large. -#html_favicon = None - -# Add any paths that contain custom static files (such as style sheets) here, -# relative to this directory. They are copied after the builtin static files, -# so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] - -# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, -# using the given strftime format. -#html_last_updated_fmt = '%b %d, %Y' - -# If true, SmartyPants will be used to convert quotes and dashes to -# typographically correct entities. -#html_use_smartypants = True - -# Custom sidebar templates, maps document names to template names. +master_doc = "index" +extensions = [ + "sphinx.ext.autodoc", + "sphinx.ext.intersphinx", + "pallets_sphinx_themes", + "sphinx_issues", + "sphinxcontrib.log_cabinet", +] +autoclass_content = "both" +autodoc_typehints = "description" +intersphinx_mapping = {"python": ("https://docs.python.org/3/", None)} +issues_github_path = "pallets/werkzeug" + +# HTML ----------------------------------------------------------------- + +html_theme = "werkzeug" +html_context = { + "project_links": [ + ProjectLink("Donate", "https://palletsprojects.com/donate"), + ProjectLink("PyPI Releases", "https://pypi.org/project/Werkzeug/"), + ProjectLink("Source Code", "https://github.com/pallets/werkzeug/"), + ProjectLink("Issue Tracker", "https://github.com/pallets/werkzeug/issues/"), + ProjectLink("Website", "https://palletsprojects.com/p/werkzeug/"), + ProjectLink("Twitter", "https://twitter.com/PalletsTeam"), + ProjectLink("Chat", "https://discord.gg/pallets"), + ] +} html_sidebars = { - 'index': ['sidebarlogo.html', 'sidebarintro.html', 'sourcelink.html', - 'searchbox.html'], - '**': ['sidebarlogo.html', 'localtoc.html', 'relations.html', - 'sourcelink.html', 'searchbox.html'] + "index": ["project.html", "localtoc.html", "searchbox.html", "ethicalads.html"], + "**": ["localtoc.html", "relations.html", "searchbox.html", "ethicalads.html"], } +singlehtml_sidebars = {"index": ["project.html", "localtoc.html", "ethicalads.html"]} +html_static_path = ["_static"] +html_favicon = "_static/favicon.ico" +html_logo = "_static/werkzeug.png" +html_title = f"Werkzeug Documentation ({version})" +html_show_sourcelink = False -# Additional templates that should be rendered to pages, maps page names to -# template names. -#html_additional_pages = {} - -# If false, no module index is generated. -#html_use_modindex = True - -# If false, no index is generated. -#html_use_index = True - -# If true, the index is split into individual pages for each letter. -#html_split_index = False - -# If true, links to the reST sources are added to the pages. -#html_show_sourcelink = True - -# If true, an OpenSearch description file will be output, and all pages will -# contain a tag referring to it. The value of this option must be the -# base URL from which the finished HTML is served. -#html_use_opensearch = '' - -# If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml"). -#html_file_suffix = '' - -# Output file base name for HTML help builder. -htmlhelp_basename = 'Werkzeugdoc' +# LaTeX ---------------------------------------------------------------- - -# Options for LaTeX output -# ------------------------ - -# The paper size ('letter' or 'a4'). -latex_paper_size = 'a4' - -# Grouping the document tree into LaTeX files. List of tuples -# (source start file, target name, title, author, document class [howto/manual]). latex_documents = [ - ('latexindex', 'Werkzeug.tex', ur'Werkzeug Documentation', - ur'The Werkzeug Team', 'manual'), + (master_doc, f"Werkzeug-{version}.tex", html_title, author, "manual") ] - -# Additional stuff for LaTeX -latex_elements = { - 'fontpkg': r'\usepackage{mathpazo}', - 'papersize': 'a4paper', - 'pointsize': '12pt', - 'preamble': r''' -\usepackage{werkzeugstyle} - -% i hate you latex, here too -\DeclareUnicodeCharacter{2603}{\\N\{SNOWMAN\}} -''' -} - -latex_use_parts = True - -latex_additional_files = ['werkzeugstyle.sty', 'logo.pdf'] - -latex_use_modindex = False - - -# Example configuration for intersphinx: refer to the Python standard library. -intersphinx_mapping = { - 'http://docs.python.org/dev': None, - 'http://docs.sqlalchemy.org/en/latest/': None -} diff --git a/docs/contents.rst.inc b/docs/contents.rst.inc deleted file mode 100644 index d5d29a85b..000000000 --- a/docs/contents.rst.inc +++ /dev/null @@ -1,85 +0,0 @@ -Getting Started ---------------- - -If you are new to Werkzeug or WSGI development in general you -should start here. - -.. toctree:: - :maxdepth: 2 - - installation - transition - tutorial - levels - quickstart - python3 - -Serving and Testing -------------------- - -The development server and testing support and management script -utilities are covered here: - -.. toctree:: - :maxdepth: 2 - - serving - test - debug - -Reference ---------- - -.. toctree:: - :maxdepth: 2 - - wrappers - routing - wsgi - filesystem - http - datastructures - utils - urls - local - middlewares - exceptions - -Deployment ----------- - -This section covers running your application in production on a web -server such as Apache or lighttpd. - -.. toctree:: - :maxdepth: 3 - - deployment/index - -Contributed Modules -------------------- - -A lot of useful code contributed by the community is shipped with Werkzeug -as part of the `contrib` module: - -.. toctree:: - :maxdepth: 3 - - contrib/index - -Additional Information ----------------------- - -.. toctree:: - :maxdepth: 2 - - terms - unicode - request_data - changes - -If you can’t find the information you’re looking for, have a look at the -index or try to find it using the search function: - -* :ref:`genindex` -* :ref:`search` diff --git a/docs/contrib/atom.rst b/docs/contrib/atom.rst deleted file mode 100644 index a2e583437..000000000 --- a/docs/contrib/atom.rst +++ /dev/null @@ -1,10 +0,0 @@ -================ -Atom Syndication -================ - -.. automodule:: werkzeug.contrib.atom - -.. autoclass:: AtomFeed - :members: - -.. autoclass:: FeedEntry diff --git a/docs/contrib/cache.rst b/docs/contrib/cache.rst deleted file mode 100644 index 7437ea6d9..000000000 --- a/docs/contrib/cache.rst +++ /dev/null @@ -1,36 +0,0 @@ -===== -Cache -===== - -.. automodule:: werkzeug.contrib.cache - - -Cache System API -================ - -.. autoclass:: BaseCache - :members: - - -Cache Systems -============= - -.. autoclass:: NullCache - -.. autoclass:: SimpleCache - -.. autoclass:: MemcachedCache - -.. class:: GAEMemcachedCache - - This class is deprecated in favour of :class:`MemcachedCache` which - now supports Google Appengine as well. - - .. versionchanged:: 0.8 - Deprecated in favour of :class:`MemcachedCache`. - -.. autoclass:: RedisCache - -.. autoclass:: FileSystemCache - -.. autoclass:: UWSGICache diff --git a/docs/contrib/fixers.rst b/docs/contrib/fixers.rst deleted file mode 100644 index 662b5c24b..000000000 --- a/docs/contrib/fixers.rst +++ /dev/null @@ -1,16 +0,0 @@ -====== -Fixers -====== - -.. automodule:: werkzeug.contrib.fixers - -.. autoclass:: CGIRootFix - -.. autoclass:: PathInfoFromRequestUriFix - -.. autoclass:: ProxyFix - :members: - -.. autoclass:: HeaderRewriterFix - -.. autoclass:: InternetExplorerFix diff --git a/docs/contrib/index.rst b/docs/contrib/index.rst deleted file mode 100644 index 991f50619..000000000 --- a/docs/contrib/index.rst +++ /dev/null @@ -1,19 +0,0 @@ -=================== -Contributed Modules -=================== - -A lot of useful code contributed by the community is shipped with Werkzeug -as part of the `contrib` module: - -.. toctree:: - :maxdepth: 2 - - atom - sessions - securecookie - cache - wrappers - iterio - fixers - profiler - lint diff --git a/docs/contrib/iterio.rst b/docs/contrib/iterio.rst deleted file mode 100644 index e7b0fe999..000000000 --- a/docs/contrib/iterio.rst +++ /dev/null @@ -1,8 +0,0 @@ -======= -Iter IO -======= - -.. automodule:: werkzeug.contrib.iterio - -.. autoclass:: IterIO - :members: diff --git a/docs/contrib/lint.rst b/docs/contrib/lint.rst deleted file mode 100644 index fe3e015c2..000000000 --- a/docs/contrib/lint.rst +++ /dev/null @@ -1,9 +0,0 @@ -========================== -Lint Validation Middleware -========================== - -.. currentmodule:: werkzeug.contrib.lint - -.. automodule:: werkzeug.contrib.lint - -.. autoclass:: LintMiddleware diff --git a/docs/contrib/profiler.rst b/docs/contrib/profiler.rst deleted file mode 100644 index 187039ebe..000000000 --- a/docs/contrib/profiler.rst +++ /dev/null @@ -1,11 +0,0 @@ -========================= -WSGI Application Profiler -========================= - -.. automodule:: werkzeug.contrib.profiler - -.. autoclass:: MergeStream - -.. autoclass:: ProfilerMiddleware - -.. autofunction:: make_action diff --git a/docs/contrib/securecookie.rst b/docs/contrib/securecookie.rst deleted file mode 100644 index e75572b64..000000000 --- a/docs/contrib/securecookie.rst +++ /dev/null @@ -1,55 +0,0 @@ -============= -Secure Cookie -============= - -.. automodule:: werkzeug.contrib.securecookie - -Security -======== - -The default implementation uses Pickle as this is the only module that -used to be available in the standard library when this module was created. -If you have simplejson available it's strongly recommended to create a -subclass and replace the serialization method:: - - import json - from werkzeug.contrib.securecookie import SecureCookie - - class JSONSecureCookie(SecureCookie): - serialization_method = json - -The weakness of Pickle is that if someone gains access to the secret key -the attacker can not only modify the session but also execute arbitrary -code on the server. - - -Reference -========= - -.. autoclass:: SecureCookie - :members: - - .. attribute:: new - - `True` if the cookie was newly created, otherwise `False` - - .. attribute:: modified - - Whenever an item on the cookie is set, this attribute is set to `True`. - However this does not track modifications inside mutable objects - in the cookie: - - >>> c = SecureCookie() - >>> c["foo"] = [1, 2, 3] - >>> c.modified - True - >>> c.modified = False - >>> c["foo"].append(4) - >>> c.modified - False - - In that situation it has to be set to `modified` by hand so that - :attr:`should_save` can pick it up. - - -.. autoexception:: UnquoteError diff --git a/docs/contrib/sessions.rst b/docs/contrib/sessions.rst deleted file mode 100644 index 28bdb4147..000000000 --- a/docs/contrib/sessions.rst +++ /dev/null @@ -1,50 +0,0 @@ -======== -Sessions -======== - -.. automodule:: werkzeug.contrib.sessions - -.. testsetup:: - - from werkzeug.contrib.sessions import * - -Reference -========= - -.. autoclass:: Session - - .. attribute:: sid - - The session ID as string. - - .. attribute:: new - - `True` is the cookie was newly created, otherwise `False` - - .. attribute:: modified - - Whenever an item on the cookie is set, this attribute is set to `True`. - However this does not track modifications inside mutable objects - in the session: - - >>> c = Session({}, sid='deadbeefbabe2c00ffee') - >>> c["foo"] = [1, 2, 3] - >>> c.modified - True - >>> c.modified = False - >>> c["foo"].append(4) - >>> c.modified - False - - In that situation it has to be set to `modified` by hand so that - :attr:`should_save` can pick it up. - - .. autoattribute:: should_save - -.. autoclass:: SessionStore - :members: - -.. autoclass:: FilesystemSessionStore - :members: list - -.. autoclass:: SessionMiddleware diff --git a/docs/contrib/wrappers.rst b/docs/contrib/wrappers.rst deleted file mode 100644 index 292208a56..000000000 --- a/docs/contrib/wrappers.rst +++ /dev/null @@ -1,23 +0,0 @@ -============== -Extra Wrappers -============== - -.. automodule:: werkzeug.contrib.wrappers - -.. autoclass:: JSONRequestMixin - :members: - -.. autoclass:: ProtobufRequestMixin - :members: - -.. autoclass:: RoutingArgsRequestMixin - :members: - -.. autoclass:: ReverseSlashBehaviorRequestMixin - :members: - -.. autoclass:: DynamicCharsetRequestMixin - :members: - -.. autoclass:: DynamicCharsetResponseMixin - :members: diff --git a/docs/datastructures.rst b/docs/datastructures.rst index 10e3715e2..01432f413 100644 --- a/docs/datastructures.rst +++ b/docs/datastructures.rst @@ -122,7 +122,8 @@ Others .. attribute:: filename - The filename of the file on the client. + The filename of the file on the client. Can be a ``str``, or an + instance of ``os.PathLike``. .. attribute:: name diff --git a/docs/debug.rst b/docs/debug.rst index 78563e435..25a9f0b2d 100644 --- a/docs/debug.rst +++ b/docs/debug.rst @@ -1,43 +1,57 @@ -====================== Debugging Applications ====================== .. module:: werkzeug.debug -Depending on the WSGI gateway/server, exceptions are handled differently. -But most of the time, exceptions go to stderr or the error log. +Depending on the WSGI gateway/server, exceptions are handled +differently. Most of the time, exceptions go to stderr or the error log, +and a generic "500 Internal Server Error" message is displayed. Since this is not the best debugging environment, Werkzeug provides a -WSGI middleware that renders nice debugging tracebacks, optionally with an -AJAX based debugger (which allows to execute code in the context of the -traceback's frames). +WSGI middleware that renders nice tracebacks, optionally with an +interactive debug console to execute code in any frame. + +.. danger:: + + The debugger allows the execution of arbitrary code which makes it a + major security risk. **The debugger must never be used on production + machines. We cannot stress this enough. Do not enable the debugger + in production.** + +.. note:: + + The interactive debugger does not work in forking environments, such + as a server that starts multiple processes. Most such environments + are production servers, where the debugger should not be enabled + anyway. -The interactive debugger however does not work in forking environments -which makes it nearly impossible to use on production servers. Also the -debugger allows the execution of arbitrary code which makes it a major -security risk and **must never be used on production machines** because of -that. **We cannot stress this enough. Do not enable this in -production.** Enabling the Debugger -===================== +--------------------- -You can enable the debugger by wrapping the application in a -:class:`DebuggedApplication` middleware. Additionally there are -parameters to the :func:`run_simple` function to enable it because this -is a common task during development. +Enable the debugger by wrapping the application with the +:class:`DebuggedApplication` middleware. Alternatively, you can pass +``use_debugger=True`` to :func:`run_simple` and it will do that for you. .. autoclass:: DebuggedApplication + Using the Debugger -================== +------------------ + +Once enabled and an error happens during a request you will see a +detailed traceback instead of a generic "internal server error". The +traceback is still output to the terminal as well. -Once enabled and an error happens during a request you will see a detailed -traceback instead of a general "internal server error". If you have the -`evalex` feature enabled you can also get a traceback for every frame in -the traceback by clicking on the console icon. +The error message is displayed at the top. Clicking it jumps to the +bottom of the traceback. Frames that represent user code, as opposed to +built-ins or installed packages, are highlighted blue. Clicking a +frame will show more lines for context, clicking again will hide them. -Once clicked a console opens where you can execute Python code in: +If you have the ``evalex`` feature enabled you can get a console for +every frame in the traceback by hovering over a frame and clicking the +console icon that appears at the right. Once clicked a console opens +where you can execute Python code in: .. image:: _static/debug-screenshot.png :alt: a screenshot of the interactive debugger @@ -45,45 +59,43 @@ Once clicked a console opens where you can execute Python code in: Inside the interactive consoles you can execute any kind of Python code. Unlike regular Python consoles the output of the object reprs is colored -and stripped to a reasonable size by default. If the output is longer +and stripped to a reasonable size by default. If the output is longer than what the console decides to display a small plus sign is added to the repr and a click will expand the repr. To display all variables that are defined in the current frame you can -use the `dump()` function. You can call it without arguments to get a +use the ``dump()`` function. You can call it without arguments to get a detailed list of all variables and their values, or with an object as argument to get a detailed list of all the attributes it has. + Debugger PIN -============ - -Starting with Werkzeug 0.11 the debugger is additionally protected by a -PIN. This is a security helper to make it less likely for the debugger to -be exploited in production as it has happened to people to keep the -debugger active. The PIN based authentication is enabled by default. - -When the debugger comes up, on first usage it will prompt for a PIN that -is printed to the command line. The PIN is generated in a stable way that -is specific to the project. In some situations it might be not possible -to generate a stable PIN between restarts in which case an explicit PIN -can be provided through the environment variable ``WERKZEUG_DEBUG_PIN``. -This can be set to a number and will become the PIN. This variable can -also be set to the value ``off`` to disable the PIN check entirely. - -If the PIN is entered too many times incorrectly the server needs to be -restarted. +------------ -**This feature is not supposed to entirely secure the debugger. It's -intended to make it harder for an attacker to exploit the debugger. Never -enable the debugger in production.** +Starting with Werkzeug 0.11 the debug console is protected by a PIN. +This is a security helper to make it less likely for the debugger to be +exploited if you forget to disable it when deploying to production. The +PIN based authentication is enabled by default. -Pasting Errors -============== +The first time a console is opened, a dialog will prompt for a PIN that +is printed to the command line. The PIN is generated in a stable way +that is specific to the project. An explicit PIN can be provided through +the environment variable ``WERKZEUG_DEBUG_PIN``. This can be set to a +number and will become the PIN. This variable can also be set to the +value ``off`` to disable the PIN check entirely. -If you click on the `Traceback` title, the traceback switches over to a text -based one. The text based one can be pasted to `gist.github.com `_ with one -click. +If an incorrect PIN is entered too many times the server needs to be +restarted. + +**This feature is not meant to entirely secure the debugger. It is +intended to make it harder for an attacker to exploit the debugger. +Never enable the debugger in production.** -.. _paste.pocoo.org: https://gist.github.com +Pasting Errors +-------------- +If you click on the "Traceback (most recent call last)" header, the +view switches to a traditional text-based traceback. You can copy and +paste this in order to provide information when asking a question or +reporting an issue. diff --git a/docs/deployment/apache-httpd.rst b/docs/deployment/apache-httpd.rst new file mode 100644 index 000000000..42fc01fa0 --- /dev/null +++ b/docs/deployment/apache-httpd.rst @@ -0,0 +1,82 @@ +Apache httpd +============ + +`Apache httpd`_ is a fast, production level HTTP server. When serving +your application with one of the WSGI servers listed in :doc:`index`, it +is often good or necessary to put a dedicated HTTP server in front of +it. This "reverse proxy" can handle incoming requests, TLS, and other +security and performance concerns better than the WSGI server. + +httpd can be installed using your system package manager, or a pre-built +executable for Windows. Installing and running httpd itself is outside +the scope of this doc. This page outlines the basics of configuring +httpd to proxy your application. Be sure to read its documentation to +understand what features are available. + +.. _Apache httpd: https://httpd.apache.org/ + + +Domain Name +----------- + +Acquiring and configuring a domain name is outside the scope of this +doc. In general, you will buy a domain name from a registrar, pay for +server space with a hosting provider, and then point your registrar +at the hosting provider's name servers. + +To simulate this, you can also edit your ``hosts`` file, located at +``/etc/hosts`` on Linux. Add a line that associates a name with the +local IP. + +Modern Linux systems may be configured to treat any domain name that +ends with ``.localhost`` like this without adding it to the ``hosts`` +file. + +.. code-block:: python + :caption: ``/etc/hosts`` + + 127.0.0.1 hello.localhost + + +Configuration +------------- + +The httpd configuration is located at ``/etc/httpd/conf/httpd.conf`` on +Linux. It may be different depending on your operating system. Check the +docs and look for ``httpd.conf``. + +Remove or comment out any existing ``DocumentRoot`` directive. Add the +config lines below. We'll assume the WSGI server is listening locally at +``http://127.0.0.1:8000``. + +.. code-block:: apache + :caption: ``/etc/httpd/conf/httpd.conf`` + + LoadModule proxy_module modules/mod_proxy.so + LoadModule proxy_http_module modules/mod_proxy_http.so + ProxyPass / http://127.0.0.1:8000/ + RequestHeader set X-Forwarded-Proto http + RequestHeader set X-Forwarded-Prefix / + +The ``LoadModule`` lines might already exist. If so, make sure they are +uncommented instead of adding them manually. + +Then :doc:`proxy_fix` so that your application uses the ``X-Forwarded`` +headers. ``X-Forwarded-For`` and ``X-Forwarded-Host`` are automatically +set by ``ProxyPass``. + + +Static Files +------------ + +If your application has static files such as JavaScript, CSS, and +images, it will be more efficient to let Nginx serve them directly +rather than going through the Python application. + +Assuming the static files are expected to be available under the +``/static/`` URL, and are stored at ``/home/project/static/``, add the +following to the config above. + +.. code-block:: apache + + Alias /static/ /home/project/static/ diff --git a/docs/deployment/cgi.rst b/docs/deployment/cgi.rst deleted file mode 100644 index 5c79d5afc..000000000 --- a/docs/deployment/cgi.rst +++ /dev/null @@ -1,44 +0,0 @@ -=== -CGI -=== - -If all other deployment methods do not work, CGI will work for sure. CGI -is supported by all major servers but usually has a less-than-optimal -performance. - -This is also the way you can use a Werkzeug application on Google's -`AppEngine`_, there however the execution does happen in a CGI-like -environment. The application's performance is unaffected because of that. - -.. _AppEngine: http://code.google.com/appengine/ - -Creating a `.cgi` file -====================== - -First you need to create the CGI application file. Let's call it -`yourapplication.cgi`:: - - #!/usr/bin/python - from wsgiref.handlers import CGIHandler - from yourapplication import make_app - - application = make_app() - CGIHandler().run(application) - -If you're running Python 2.4 you will need the :mod:`wsgiref` package. Python -2.5 and higher ship this as part of the standard library. - -Server Setup -============ - -Usually there are two ways to configure the server. Either just copy the -`.cgi` into a `cgi-bin` (and use `mod_rerwite` or something similar to -rewrite the URL) or let the server point to the file directly. - -In Apache for example you can put a like like this into the config: - -.. sourcecode:: apache - - ScriptAlias /app /path/to/the/application.cgi - -For more information consult the documentation of your webserver. diff --git a/docs/deployment/eventlet.rst b/docs/deployment/eventlet.rst new file mode 100644 index 000000000..243be5ebb --- /dev/null +++ b/docs/deployment/eventlet.rst @@ -0,0 +1,80 @@ +eventlet +======== + +Prefer using :doc:`gunicorn` with eventlet workers rather than using +`eventlet`_ directly. Gunicorn provides a much more configurable and +production-tested server. + +`eventlet`_ allows writing asynchronous, coroutine-based code that looks +like standard synchronous Python. It uses `greenlet`_ to enable task +switching without writing ``async/await`` or using ``asyncio``. + +:doc:`gevent` is another library that does the same thing. Certain +dependencies you have, or other considerations, may affect which of the +two you choose to use. + +eventlet provides a WSGI server that can handle many connections at once +instead of one per worker process. You must actually use eventlet in +your own code to see any benefit to using the server. + +.. _eventlet: https://eventlet.net/ +.. _greenlet: https://greenlet.readthedocs.io/en/latest/ + + +Installing +---------- + +When using eventlet, greenlet>=1.0 is required, otherwise context locals +such as ``request`` will not work as expected. When using PyPy, +PyPy>=7.3.7 is required. + +Create a virtualenv, install your application, then install +``eventlet``. + +.. code-block:: text + + $ cd hello-app + $ python -m venv venv + $ . venv/bin/activate + $ pip install . # install your application + $ pip install eventlet + + +Running +------- + +To use eventlet to serve your application, write a script that imports +its ``wsgi.server``, as well as your app or app factory. + +.. code-block:: python + :caption: ``wsgi.py`` + + import eventlet + from eventlet import wsgi + from hello import create_app + + app = create_app() + wsgi.server(eventlet.listen(("127.0.0.1", 8000), app) + +.. code-block:: text + + $ python wsgi.py + (x) wsgi starting up on http://127.0.0.1:8000 + + +Binding Externally +------------------ + +eventlet should not be run as root because it would cause your +application code to run as root, which is not secure. However, this +means it will not be possible to bind to port 80 or 443. Instead, a +reverse proxy such as :doc:`nginx` or :doc:`apache-httpd` should be used +in front of eventlet. + +You can bind to all external IPs on a non-privileged port by using +``0.0.0.0`` in the server arguments shown in the previous section. +Don't do this when using a reverse proxy setup, otherwise it will be +possible to bypass the proxy. + +``0.0.0.0`` is not a valid address to navigate to, you'd use a specific +IP address in your browser. diff --git a/docs/deployment/fastcgi.rst b/docs/deployment/fastcgi.rst deleted file mode 100644 index 84d09877f..000000000 --- a/docs/deployment/fastcgi.rst +++ /dev/null @@ -1,142 +0,0 @@ -======= -FastCGI -======= - -A very popular deployment setup on servers like `lighttpd`_ and `nginx`_ -is FastCGI. To use your WSGI application with any of them you will need -a FastCGI server first. - -The most popular one is `flup`_ which we will use for this guide. Make -sure to have it installed. - -Creating a `.fcgi` file -======================= - -First you need to create the FastCGI server file. Let's call it -`yourapplication.fcgi`:: - - #!/usr/bin/python - from flup.server.fcgi import WSGIServer - from yourapplication import make_app - - if __name__ == '__main__': - application = make_app() - WSGIServer(application).run() - -This is enough for Apache to work, however ngingx and older versions of -lighttpd need a socket to be explicitly passed to communicate with the FastCGI -server. For that to work you need to pass the path to the socket to the -:class:`~flup.server.fcgi.WSGIServer`:: - - WSGIServer(application, bindAddress='/path/to/fcgi.sock').run() - -The path has to be the exact same path you define in the server -config. - -Save the `yourapplication.fcgi` file somewhere you will find it again. -It makes sense to have that in `/var/www/yourapplication` or something -similar. - -Make sure to set the executable bit on that file so that the servers -can execute it:: - - # chmod +x /var/www/yourapplication/yourapplication.fcgi - -Configuring lighttpd -==================== - -A basic FastCGI configuration for lighttpd looks like this:: - - fastcgi.server = ("/yourapplication.fcgi" => - (( - "socket" => "/tmp/yourapplication-fcgi.sock", - "bin-path" => "/var/www/yourapplication/yourapplication.fcgi", - "check-local" => "disable", - "max-procs" -> 1 - )) - ) - - alias.url = ( - "/static/" => "/path/to/your/static" - ) - - url.rewrite-once = ( - "^(/static.*)$" => "$1", - "^(/.*)$" => "/yourapplication.fcgi$1" - -Remember to enable the FastCGI, alias and rewrite modules. This configuration -binds the application to `/yourapplication`. If you want the application to -work in the URL root you have to work around a lighttpd bug with the -:class:`~werkzeug.contrib.fixers.LighttpdCGIRootFix` middleware. - -Make sure to apply it only if you are mounting the application the URL -root. Also, see the Lighty docs for more information on `FastCGI and Python -`_ (note that -explicitly passing a socket to run() is no longer necessary). - -Configuring nginx -================= - -Installing FastCGI applications on nginx is a bit tricky because by default -some FastCGI parameters are not properly forwarded. - -A basic FastCGI configuration for nginx looks like this:: - - location /yourapplication/ { - include fastcgi_params; - if ($uri ~ ^/yourapplication/(.*)?) { - set $path_url $1; - } - fastcgi_param PATH_INFO $path_url; - fastcgi_param SCRIPT_NAME /yourapplication; - fastcgi_pass unix:/tmp/yourapplication-fcgi.sock; - } - -This configuration binds the application to `/yourapplication`. If you want -to have it in the URL root it's a bit easier because you don't have to figure -out how to calculate `PATH_INFO` and `SCRIPT_NAME`:: - - location /yourapplication/ { - include fastcgi_params; - fastcgi_param PATH_INFO $fastcgi_script_name; - fastcgi_param SCRIPT_NAME ""; - fastcgi_pass unix:/tmp/yourapplication-fcgi.sock; - } - -Since Nginx doesn't load FastCGI apps, you have to do it by yourself. You -can either write an `init.d` script for that or execute it inside a screen -session:: - - $ screen - $ /var/www/yourapplication/yourapplication.fcgi - -Debugging -========= - -FastCGI deployments tend to be hard to debug on most webservers. Very often the -only thing the server log tells you is something along the lines of "premature -end of headers". In order to debug the application the only thing that can -really give you ideas why it breaks is switching to the correct user and -executing the application by hand. - -This example assumes your application is called `application.fcgi` and that your -webserver user is `www-data`:: - - $ su www-data - $ cd /var/www/yourapplication - $ python application.fcgi - Traceback (most recent call last): - File "yourapplication.fcg", line 4, in - ImportError: No module named yourapplication - -In this case the error seems to be "yourapplication" not being on the python -path. Common problems are: - -- relative paths being used. Don't rely on the current working directory -- the code depending on environment variables that are not set by the - web server. -- different python interpreters being used. - -.. _lighttpd: http://www.lighttpd.net/ -.. _nginx: http://nginx.net/ -.. _flup: http://trac.saddi.com/flup diff --git a/docs/deployment/gevent.rst b/docs/deployment/gevent.rst new file mode 100644 index 000000000..aae63e89e --- /dev/null +++ b/docs/deployment/gevent.rst @@ -0,0 +1,80 @@ +gevent +====== + +Prefer using :doc:`gunicorn` or :doc:`uwsgi` with gevent workers rather +than using `gevent`_ directly. Gunicorn and uWSGI provide much more +configurable and production-tested servers. + +`gevent`_ allows writing asynchronous, coroutine-based code that looks +like standard synchronous Python. It uses `greenlet`_ to enable task +switching without writing ``async/await`` or using ``asyncio``. + +:doc:`eventlet` is another library that does the same thing. Certain +dependencies you have, or other considerations, may affect which of the +two you choose to use. + +gevent provides a WSGI server that can handle many connections at once +instead of one per worker process. You must actually use gevent in your +own code to see any benefit to using the server. + +.. _gevent: https://www.gevent.org/ +.. _greenlet: https://greenlet.readthedocs.io/en/latest/ + + +Installing +---------- + +When using gevent, greenlet>=1.0 is required, otherwise context locals +such as ``request`` will not work as expected. When using PyPy, +PyPy>=7.3.7 is required. + +Create a virtualenv, install your application, then install ``gevent``. + +.. code-block:: text + + $ cd hello-app + $ python -m venv venv + $ . venv/bin/activate + $ pip install . # install your application + $ pip install gevent + + +Running +------- + +To use gevent to serve your application, write a script that imports its +``WSGIServer``, as well as your app or app factory. + +.. code-block:: python + :caption: ``wsgi.py`` + + from gevent.pywsgi import WSGIServer + from hello import create_app + + app = create_app() + http_server = WSGIServer(("127.0.0.1", 8000), app) + http_server.serve_forever() + +.. code-block:: text + + $ python wsgi.py + +No output is shown when the server starts. + + +Binding Externally +------------------ + +gevent should not be run as root because it would cause your +application code to run as root, which is not secure. However, this +means it will not be possible to bind to port 80 or 443. Instead, a +reverse proxy such as :doc:`nginx` or :doc:`apache-httpd` should be used +in front of gevent. + +You can bind to all external IPs on a non-privileged port by using +``0.0.0.0`` in the server arguments shown in the previous section. Don't +do this when using a reverse proxy setup, otherwise it will be possible +to bypass the proxy. + +``0.0.0.0`` is not a valid address to navigate to, you'd use a specific +IP address in your browser. diff --git a/docs/deployment/gunicorn.rst b/docs/deployment/gunicorn.rst new file mode 100644 index 000000000..82fe8ab75 --- /dev/null +++ b/docs/deployment/gunicorn.rst @@ -0,0 +1,130 @@ +Gunicorn +======== + +`Gunicorn`_ is a pure Python WSGI server with simple configuration and +multiple worker implementations for performance tuning. + +* It tends to integrate easily with hosting platforms. +* It does not support Windows (but does run on WSL). +* It is easy to install as it does not require additional dependencies + or compilation. +* It has built-in async worker support using gevent or eventlet. + +This page outlines the basics of running Gunicorn. Be sure to read its +`documentation`_ and use ``gunicorn --help`` to understand what features +are available. + +.. _Gunicorn: https://gunicorn.org/ +.. _documentation: https://docs.gunicorn.org/ + + +Installing +---------- + +Gunicorn is easy to install, as it does not require external +dependencies or compilation. It runs on Windows only under WSL. + +Create a virtualenv, install your application, then install +``gunicorn``. + +.. code-block:: text + + $ cd hello-app + $ python -m venv venv + $ . venv/bin/activate + $ pip install . # install your application + $ pip install gunicorn + + +Running +------- + +The only required argument to Gunicorn tells it how to load your +application. The syntax is ``{module_import}:{app_variable}``. +``module_import`` is the dotted import name to the module with your +application. ``app_variable`` is the variable with the application. It +can also be a function call (with any arguments) if you're using the +app factory pattern. + +.. code-block:: text + + # equivalent to 'from hello import app' + $ gunicorn -w 4 'hello:app' + + # equivalent to 'from hello import create_app; create_app()' + $ gunicorn -w 4 'hello:create_app()' + + Starting gunicorn 20.1.0 + Listening at: http://127.0.0.1:8000 (x) + Using worker: sync + Booting worker with pid: x + Booting worker with pid: x + Booting worker with pid: x + Booting worker with pid: x + +The ``-w`` option specifies the number of processes to run; a starting +value could be ``CPU * 2``. The default is only 1 worker, which is +probably not what you want for the default worker type. + +Logs for each request aren't shown by default, only worker info and +errors are shown. To show access logs on stdout, use the +``--access-logfile=-`` option. + + +Binding Externally +------------------ + +Gunicorn should not be run as root because it would cause your +application code to run as root, which is not secure. However, this +means it will not be possible to bind to port 80 or 443. Instead, a +reverse proxy such as :doc:`nginx` or :doc:`apache-httpd` should be used +in front of Gunicorn. + +You can bind to all external IPs on a non-privileged port using the +``-b 0.0.0.0`` option. Don't do this when using a reverse proxy setup, +otherwise it will be possible to bypass the proxy. + +.. code-block:: text + + $ gunicorn -w 4 -b 0.0.0.0 'hello:create_app()' + Listening at: http://0.0.0.0:8000 (x) + +``0.0.0.0`` is not a valid address to navigate to, you'd use a specific +IP address in your browser. + + +Async with gevent or eventlet +----------------------------- + +The default sync worker is appropriate for many use cases. If you need +asynchronous support, Gunicorn provides workers using either `gevent`_ +or `eventlet`_. This is not the same as Python's ``async/await``, or the +ASGI server spec. You must actually use gevent/eventlet in your own code +to see any benefit to using the workers. + +When using either gevent or eventlet, greenlet>=1.0 is required, +otherwise context locals such as ``request`` will not work as expected. +When using PyPy, PyPy>=7.3.7 is required. + +To use gevent: + +.. code-block:: text + + $ gunicorn -k gevent 'hello:create_app()' + Starting gunicorn 20.1.0 + Listening at: http://127.0.0.1:8000 (x) + Using worker: gevent + Booting worker with pid: x + +To use eventlet: + +.. code-block:: text + + $ gunicorn -k eventlet 'hello:create_app()' + Starting gunicorn 20.1.0 + Listening at: http://127.0.0.1:8000 (x) + Using worker: eventlet + Booting worker with pid: x + +.. _gevent: https://www.gevent.org/ +.. _eventlet: https://eventlet.net/ diff --git a/docs/deployment/index.rst b/docs/deployment/index.rst index 524d52834..f884f080e 100644 --- a/docs/deployment/index.rst +++ b/docs/deployment/index.rst @@ -1,16 +1,71 @@ -.. _deployment: +Deploying to Production +======================= -====================== -Application Deployment -====================== +After developing your application, you'll want to make it available +publicly to other users. When you're developing locally, you're probably +using the built-in development server, debugger, and reloader. These +should not be used in production. Instead, you should use a dedicated +WSGI server or hosting platform, some of which will be described here. -This section covers running your application in production on a web -server such as Apache or lighttpd. +"Production" means "not development", which applies whether you're +serving your application publicly to millions of users or privately / +locally to a single user. **Do not use the development server when +deploying to production. It is intended for use only during local +development. It is not designed to be particularly secure, stable, or +efficient.** + +Self-Hosted Options +------------------- + +Werkzeug is a WSGI *application*. A WSGI *server* is used to run the +application, converting incoming HTTP requests to the standard WSGI +environ, and converting outgoing WSGI responses to HTTP responses. + +The primary goal of these docs is to familiarize you with the concepts +involved in running a WSGI application using a production WSGI server +and HTTP server. There are many WSGI servers and HTTP servers, with many +configuration possibilities. The pages below discuss the most common +servers, and show the basics of running each one. The next section +discusses platforms that can manage this for you. + +.. toctree:: + :maxdepth: 1 + + gunicorn + waitress + mod_wsgi + uwsgi + gevent + eventlet + +WSGI servers have HTTP servers built-in. However, a dedicated HTTP +server may be safer, more efficient, or more capable. Putting an HTTP +server in front of the WSGI server is called a "reverse proxy." .. toctree:: - :maxdepth: 2 + :maxdepth: 1 + + proxy_fix + nginx + apache-httpd + +This list is not exhaustive, and you should evaluate these and other +servers based on your application's needs. Different servers will have +different capabilities, configuration, and support. + + +Hosting Platforms +----------------- + +There are many services available for hosting web applications without +needing to maintain your own server, networking, domain, etc. Some +services may have a free tier up to a certain time or bandwidth. Many of +these services use one of the WSGI servers described above, or a similar +interface. + +You should evaluate services based on your application's needs. +Different services will have different capabilities, configuration, +pricing, and support. - cgi - mod_wsgi - fastcgi - proxying +You'll probably need to :doc:`proxy_fix` when using most hosting +platforms. diff --git a/docs/deployment/mod_wsgi.rst b/docs/deployment/mod_wsgi.rst index 85eb965b9..f32631bdf 100644 --- a/docs/deployment/mod_wsgi.rst +++ b/docs/deployment/mod_wsgi.rst @@ -1,82 +1,94 @@ -=================== -`mod_wsgi` (Apache) -=================== +mod_wsgi +======== -If you are using the `Apache`_ webserver you should consider using `mod_wsgi`_. +`mod_wsgi`_ is a WSGI server integrated with the `Apache httpd`_ server. +The modern `mod_wsgi-express`_ command makes it easy to configure and +start the server without needing to write Apache httpd configuration. -.. _Apache: http://httpd.apache.org/ +* Tightly integrated with Apache httpd. +* Supports Windows directly. +* Requires a compiler and the Apache development headers to install. +* Does not require a reverse proxy setup. -Installing `mod_wsgi` -===================== +This page outlines the basics of running mod_wsgi-express, not the more +complex installation and configuration with httpd. Be sure to read the +`mod_wsgi-express`_, `mod_wsgi`_, and `Apache httpd`_ documentation to +understand what features are available. -If you don't have `mod_wsgi` installed yet you have to either install it using -a package manager or compile it yourself. +.. _mod_wsgi-express: https://pypi.org/project/mod-wsgi/ +.. _mod_wsgi: https://modwsgi.readthedocs.io/ +.. _Apache httpd: https://httpd.apache.org/ -The mod_wsgi `installation instructions`_ cover installation instructions for -source installations on UNIX systems. -If you are using ubuntu / debian you can apt-get it and activate it as follows:: +Installing +---------- - # apt-get install libapache2-mod-wsgi +Installing mod_wsgi requires a compiler and the Apache server and +development headers installed. You will get an error if they are not. +How to install them depends on the OS and package manager that you use. -On FreeBSD install `mod_wsgi` by compiling the `www/mod_wsgi` port or by using -pkg_add:: +Create a virtualenv, install your application, then install +``mod_wsgi``. - # pkg_add -r mod_wsgi +.. code-block:: text -If you are using pkgsrc you can install `mod_wsgi` by compiling the -`www/ap2-wsgi` package. + $ cd hello-app + $ python -m venv venv + $ . venv/bin/activate + $ pip install . # install your application + $ pip install mod_wsgi -If you encounter segfaulting child processes after the first apache reload you -can safely ignore them. Just restart the server. -Creating a `.wsgi` file -======================= +Running +------- -To run your application you need a `yourapplication.wsgi` file. This file -contains the code `mod_wsgi` is executing on startup to get the application -object. The object called `application` in that file is then used as -application. +The only argument to ``mod_wsgi-express`` specifies a script containing +your application, which must be called ``application``. You can +write a small script to import your app with this name, or to create it +if using the app factory pattern. -For most applications the following file should be sufficient:: +.. code-block:: python + :caption: ``wsgi.py`` - from yourapplication import make_app - application = make_app() + from hello import app -If you don't have a factory function for application creation but a singleton -instance you can directly import that one as `application`. + application = app -Store that file somewhere where you will find it again (eg: -`/var/www/yourapplication`) and make sure that `yourapplication` and all -the libraries that are in use are on the python load path. If you don't -want to install it system wide consider using a `virtual python`_ instance. +.. code-block:: python + :caption: ``wsgi.py`` -Configuring Apache -================== + from hello import create_app -The last thing you have to do is to create an Apache configuration file for -your application. In this example we are telling `mod_wsgi` to execute the -application under a different user for security reasons: + application = create_app() -.. sourcecode:: apache +Now run the ``mod_wsgi-express start-server`` command. - - ServerName example.com +.. code-block:: text - WSGIDaemonProcess yourapplication user=user1 group=group1 processes=2 threads=5 - WSGIScriptAlias / /var/www/yourapplication/yourapplication.wsgi + $ mod_wsgi-express start-server wsgi.py --processes 4 - - WSGIProcessGroup yourapplication - WSGIApplicationGroup %{GLOBAL} - Order deny,allow - Allow from all - - +The ``--processes`` option specifies the number of worker processes to +run; a starting value could be ``CPU * 2``. -For more information consult the `mod_wsgi wiki`_. +Logs for each request aren't show in the terminal. If an error occurs, +its information is written to the error log file shown when starting the +server. -.. _mod_wsgi: http://code.google.com/p/modwsgi/ -.. _installation instructions: http://code.google.com/p/modwsgi/wiki/QuickInstallationGuide -.. _virtual python: http://pypi.python.org/pypi/virtualenv -.. _mod_wsgi wiki: http://code.google.com/p/modwsgi/wiki/ + +Binding Externally +------------------ + +Unlike the other WSGI servers in these docs, mod_wsgi can be run as +root to bind to privileged ports like 80 and 443. However, it must be +configured to drop permissions to a different user and group for the +worker processes. + +For example, if you created a ``hello`` user and group, you should +install your virtualenv and application as that user, then tell +mod_wsgi to drop to that user after starting. + +.. code-block:: text + + $ sudo /home/hello/venv/bin/mod_wsgi-express start-server \ + /home/hello/wsgi.py \ + --user hello --group hello --port 80 --processes 4 diff --git a/docs/deployment/nginx.rst b/docs/deployment/nginx.rst new file mode 100644 index 000000000..5b136e4d0 --- /dev/null +++ b/docs/deployment/nginx.rst @@ -0,0 +1,87 @@ +nginx +===== + +`nginx`_ is a fast, production level HTTP server. When serving your +application with one of the WSGI servers listed in :doc:`index`, it is +often good or necessary to put a dedicated HTTP server in front of it. +This "reverse proxy" can handle incoming requests, TLS, and other +security and performance concerns better than the WSGI server. + +Nginx can be installed using your system package manager, or a pre-built +executable for Windows. Installing and running Nginx itself is outside +the scope of this doc. This page outlines the basics of configuring +Nginx to proxy your application. Be sure to read its documentation to +understand what features are available. + +.. _nginx: https://nginx.org/ + + +Domain Name +----------- + +Acquiring and configuring a domain name is outside the scope of this +doc. In general, you will buy a domain name from a registrar, pay for +server space with a hosting provider, and then point your registrar +at the hosting provider's name servers. + +To simulate this, you can also edit your ``hosts`` file, located at +``/etc/hosts`` on Linux. Add a line that associates a name with the +local IP. + +Modern Linux systems may be configured to treat any domain name that +ends with ``.localhost`` like this without adding it to the ``hosts`` +file. + +.. code-block:: python + :caption: ``/etc/hosts`` + + 127.0.0.1 hello.localhost + + +Configuration +------------- + +The nginx configuration is located at ``/etc/nginx/nginx.conf`` on +Linux. It may be different depending on your operating system. Check the +docs and look for ``nginx.conf``. + +Remove or comment out any existing ``server`` section. Add a ``server`` +section and use the ``proxy_pass`` directive to point to the address the +WSGI server is listening on. We'll assume the WSGI server is listening +locally at ``http://127.0.0.1:8000``. + +.. code-block:: nginx + :caption: ``/etc/nginx.conf`` + + server { + listen 80; + server_name _; + + location / { + proxy_pass http://127.0.0.1:8000/; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-Host $host; + proxy_set_header X-Forwarded-Prefix /; + } + } + +Then :doc:`proxy_fix` so that your application uses these headers. + + +Static Files +------------ + +If your application has static files such as JavaScript, CSS, and +images, it will be more efficient to let Nginx serve them directly +rather than going through the Python application. + +Assuming the static files are expected to be available under the +``/static/`` URL, and are stored at ``/home/project/static/``, add the +following to the ``server`` block above. + +.. code-block:: nginx + + location /static { + alias /home/project/static; + } diff --git a/docs/deployment/proxy_fix.rst b/docs/deployment/proxy_fix.rst new file mode 100644 index 000000000..7b163b1fc --- /dev/null +++ b/docs/deployment/proxy_fix.rst @@ -0,0 +1,33 @@ +Tell Werkzeug it is Behind a Proxy +================================== + +When using a reverse proxy, or many Python hosting platforms, the proxy +will intercept and forward all external requests to the local WSGI +server. + +From the WSGI server and application's perspectives, requests are now +coming from the HTTP server to the local address, rather than from +the remote address to the external server address. + +HTTP servers should set ``X-Forwarded-`` headers to pass on the real +values to the application. The application can then be told to trust and +use those values by wrapping it with the +:doc:`../middleware/proxy_fix` middleware provided by Werkzeug. + +This middleware should only be used if the application is actually +behind a proxy, and should be configured with the number of proxies that +are chained in front of it. Not all proxies set all the headers. Since +incoming headers can be faked, you must set how many proxies are setting +each header so the middleware knows what to trust. + +.. code-block:: python + + from werkzeug.middleware.proxy_fix import ProxyFix + + app.wsgi_app = ProxyFix( + app.wsgi_app, x_for=1, x_proto=1, x_host=1, x_prefix=1 + ) + +Remember, only apply this middleware if you are behind a proxy, and set +the correct number of proxies that set each header. It can be a security +issue if you get this configuration wrong. diff --git a/docs/deployment/proxying.rst b/docs/deployment/proxying.rst deleted file mode 100644 index d88dbcb2a..000000000 --- a/docs/deployment/proxying.rst +++ /dev/null @@ -1,54 +0,0 @@ -============= -HTTP Proxying -============= - -Many people prefer using a standalone Python HTTP server and proxying that -server via nginx, Apache etc. - -A very stable Python server is CherryPy. This part of the documentation -shows you how to combine your WSGI application with the CherryPy WSGI -server and how to configure the webserver for proxying. - - -Creating a `.py` server -======================= - -To run your application you need a `start-server.py` file that starts up -the WSGI Server. - -It looks something along these lines:: - - from cherrypy import wsgiserver - from yourapplication import make_app - server = wsgiserver.CherryPyWSGIServer(('localhost', 8080), make_app()) - try: - server.start() - except KeyboardInterrupt: - server.stop() - -If you now start the file the server will listen on `localhost:8080`. Keep -in mind that WSGI applications behave slightly different for proxied setups. -If you have not developed your application for proxying in mind, you can -apply the :class:`~werkzeug.contrib.fixers.ProxyFix` middleware. - - -Configuring nginx -================= - -As an example we show here how to configure nginx to proxy to the server. - -The basic nginx configuration looks like this:: - - location / { - proxy_set_header Host $host; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_pass http://127.0.0.1:8080; - proxy_redirect default; - } - -Since Nginx doesn't start your server for you, you have to do it by yourself. You -can either write an `init.d` script for that or execute it inside a screen -session:: - - $ screen - $ python start-server.py diff --git a/docs/deployment/uwsgi.rst b/docs/deployment/uwsgi.rst new file mode 100644 index 000000000..2da5efe2d --- /dev/null +++ b/docs/deployment/uwsgi.rst @@ -0,0 +1,145 @@ +uWSGI +===== + +`uWSGI`_ is a fast, compiled server suite with extensive configuration +and capabilities beyond a basic server. + +* It can be very performant due to being a compiled program. +* It is complex to configure beyond the basic application, and has so + many options that it can be difficult for beginners to understand. +* It does not support Windows (but does run on WSL). +* It requires a compiler to install in some cases. + +This page outlines the basics of running uWSGI. Be sure to read its +documentation to understand what features are available. + +.. _uWSGI: https://uwsgi-docs.readthedocs.io/en/latest/ + + +Installing +---------- + +uWSGI has multiple ways to install it. The most straightforward is to +install the ``pyuwsgi`` package, which provides precompiled wheels for +common platforms. However, it does not provide SSL support, which can be +provided with a reverse proxy instead. + +Create a virtualenv, install your application, then install ``pyuwsgi``. + +.. code-block:: text + + $ cd hello-app + $ python -m venv venv + $ . venv/bin/activate + $ pip install . # install your application + $ pip install pyuwsgi + +If you have a compiler available, you can install the ``uwsgi`` package +instead. Or install the ``pyuwsgi`` package from sdist instead of wheel. +Either method will include SSL support. + +.. code-block:: text + + $ pip install uwsgi + + # or + $ pip install --no-binary pyuwsgi pyuwsgi + + +Running +------- + +The most basic way to run uWSGI is to tell it to start an HTTP server +and import your application. + +.. code-block:: text + + $ uwsgi --http 127.0.0.1:8000 --master -p 4 -w hello:app + + *** Starting uWSGI 2.0.20 (64bit) on [x] *** + *** Operational MODE: preforking *** + mounting hello:app on / + spawned uWSGI master process (pid: x) + spawned uWSGI worker 1 (pid: x, cores: 1) + spawned uWSGI worker 2 (pid: x, cores: 1) + spawned uWSGI worker 3 (pid: x, cores: 1) + spawned uWSGI worker 4 (pid: x, cores: 1) + spawned uWSGI http 1 (pid: x) + +If you're using the app factory pattern, you'll need to create a small +Python file to create the app, then point uWSGI at that. + +.. code-block:: python + :caption: ``wsgi.py`` + + from hello import create_app + + app = create_app() + +.. code-block:: text + + $ uwsgi --http 127.0.0.1:8000 --master -p 4 -w wsgi:app + +The ``--http`` option starts an HTTP server at 127.0.0.1 port 8000. The +``--master`` option specifies the standard worker manager. The ``-p`` +option starts 4 worker processes; a starting value could be ``CPU * 2``. +The ``-w`` option tells uWSGI how to import your application + + +Binding Externally +------------------ + +uWSGI should not be run as root with the configuration shown in this doc +because it would cause your application code to run as root, which is +not secure. However, this means it will not be possible to bind to port +80 or 443. Instead, a reverse proxy such as :doc:`nginx` or +:doc:`apache-httpd` should be used in front of uWSGI. It is possible to +run uWSGI as root securely, but that is beyond the scope of this doc. + +uWSGI has optimized integration with `Nginx uWSGI`_ and +`Apache mod_proxy_uwsgi`_, and possibly other servers, instead of using +a standard HTTP proxy. That configuration is beyond the scope of this +doc, see the links for more information. + +.. _Nginx uWSGI: https://uwsgi-docs.readthedocs.io/en/latest/Nginx.html +.. _Apache mod_proxy_uwsgi: https://uwsgi-docs.readthedocs.io/en/latest/Apache.html#mod-proxy-uwsgi + +You can bind to all external IPs on a non-privileged port using the +``--http 0.0.0.0:8000`` option. Don't do this when using a reverse proxy +setup, otherwise it will be possible to bypass the proxy. + +.. code-block:: text + + $ uwsgi --http 0.0.0.0:8000 --master -p 4 -w wsgi:app + +``0.0.0.0`` is not a valid address to navigate to, you'd use a specific +IP address in your browser. + + +Async with gevent +----------------- + +The default sync worker is appropriate for many use cases. If you need +asynchronous support, uWSGI provides a `gevent`_ worker. This is not the +same as Python's ``async/await``, or the ASGI server spec. You must +actually use gevent in your own code to see any benefit to using the +worker. + +When using gevent, greenlet>=1.0 is required, otherwise context locals +such as ``request`` will not work as expected. When using PyPy, +PyPy>=7.3.7 is required. + +.. code-block:: text + + $ uwsgi --http 127.0.0.1:8000 --master --gevent 100 -w wsgi:app + + *** Starting uWSGI 2.0.20 (64bit) on [x] *** + *** Operational MODE: async *** + mounting hello:app on / + spawned uWSGI master process (pid: x) + spawned uWSGI worker 1 (pid: x, cores: 100) + spawned uWSGI http 1 (pid: x) + *** running gevent loop engine [addr:x] *** + + +.. _gevent: https://www.gevent.org/ diff --git a/docs/deployment/waitress.rst b/docs/deployment/waitress.rst new file mode 100644 index 000000000..b44223d2d --- /dev/null +++ b/docs/deployment/waitress.rst @@ -0,0 +1,75 @@ +Waitress +======== + +`Waitress`_ is a pure Python WSGI server. + +* It is easy to configure. +* It supports Windows directly. +* It is easy to install as it does not require additional dependencies + or compilation. +* It does not support streaming requests, full request data is always + buffered. +* It uses a single process with multiple thread workers. + +This page outlines the basics of running Waitress. Be sure to read its +documentation and ``waitress-serve --help`` to understand what features +are available. + +.. _Waitress: https://docs.pylonsproject.org/projects/waitress/ + + +Installing +---------- + +Create a virtualenv, install your application, then install +``waitress``. + +.. code-block:: text + + $ cd hello-app + $ python -m venv venv + $ . venv/bin/activate + $ pip install . # install your application + $ pip install waitress + + +Running +------- + +The only required argument to ``waitress-serve`` tells it how to load +your application. The syntax is ``{module}:{app}``. ``module`` is +the dotted import name to the module with your application. ``app`` is +the variable with the application. If you're using the app factory +pattern, use ``--call {module}:{factory}`` instead. + +.. code-block:: text + + # equivalent to 'from hello import app' + $ waitress-serve hello:app --host 127.0.0.1 + + # equivalent to 'from hello import create_app; create_app()' + $ waitress-serve --call hello:create_app --host 127.0.0.1 + + Serving on http://127.0.0.1:8080 + +The ``--host`` option binds the server to local ``127.0.0.1`` only. + +Logs for each request aren't shown, only errors are shown. Logging can +be configured through the Python interface instead of the command line. + + +Binding Externally +------------------ + +Waitress should not be run as root because it would cause your +application code to run as root, which is not secure. However, this +means it will not be possible to bind to port 80 or 443. Instead, a +reverse proxy such as :doc:`nginx` or :doc:`apache-httpd` should be used +in front of Waitress. + +You can bind to all external IPs on a non-privileged port by not +specifying the ``--host`` option. Don't do this when using a revers +proxy setup, otherwise it will be possible to bypass the proxy. + +``0.0.0.0`` is not a valid address to navigate to, you'd use a specific +IP address in your browser. diff --git a/docs/exceptions.rst b/docs/exceptions.rst index 6f978c4c7..88a309d45 100644 --- a/docs/exceptions.rst +++ b/docs/exceptions.rst @@ -44,13 +44,22 @@ The following error classes exist in Werkzeug: .. autoexception:: ImATeapot +.. autoexception:: UnprocessableEntity + +.. autoexception:: Locked + +.. autoexception:: FailedDependency + .. autoexception:: PreconditionRequired .. autoexception:: TooManyRequests .. autoexception:: RequestHeaderFieldsTooLarge +.. autoexception:: UnavailableForLegalReasons + .. autoexception:: InternalServerError + :members: .. autoexception:: NotImplemented @@ -58,10 +67,9 @@ The following error classes exist in Werkzeug: .. autoexception:: ServiceUnavailable -.. exception:: HTTPUnicodeError +.. autoexception:: GatewayTimeout - This exception is used to signal unicode decode errors of request - data. For more information see the :ref:`unicode` chapter. +.. autoexception:: HTTPVersionNotSupported .. autoexception:: ClientDisconnected @@ -100,6 +108,8 @@ If `title` or `body` are missing in the form, a special key error will be raised which behaves like a :exc:`KeyError` but also a :exc:`BadRequest` exception. +.. autoexception:: BadRequestKeyError + Simple Aborting =============== @@ -140,8 +150,6 @@ methods. In any case you should have a look at the sourcecode of the exceptions module. You can override the default description in the constructor with the -`description` parameter (it's the first argument for all exceptions -except of the :exc:`MethodNotAllowed` which accepts a list of allowed methods -as first argument):: +``description`` parameter:: - raise BadRequest('Request failed because X was not present') + raise BadRequest(description='Request failed because X was not present') diff --git a/docs/filesystem.rst b/docs/filesystem.rst deleted file mode 100644 index 0ac92838a..000000000 --- a/docs/filesystem.rst +++ /dev/null @@ -1,11 +0,0 @@ -==================== -Filesystem Utilities -==================== - -Various utilities for the local filesystem. - -.. module:: werkzeug.filesystem - -.. autoclass:: BrokenFilesystemWarning - -.. autofunction:: get_filesystem_encoding diff --git a/docs/http.rst b/docs/http.rst index 89841c092..cbf4e04ed 100644 --- a/docs/http.rst +++ b/docs/http.rst @@ -9,21 +9,30 @@ that are useful when implementing WSGI middlewares or whenever you are operating on a lower level layer. All this functionality is also exposed from request and response objects. -Date Functions -============== -The following functions simplify working with times in an HTTP context. -Werkzeug uses offset-naive :class:`~datetime.datetime` objects internally -that store the time in UTC. If you're working with timezones in your -application make sure to replace the tzinfo attribute with a UTC timezone -information before processing the values. +Datetime Functions +================== -.. autofunction:: cookie_date +These functions simplify working with times in an HTTP context. Werkzeug +produces timezone-aware :class:`~datetime.datetime` objects in UTC. When +passing datetime objects to Werkzeug, it assumes any naive datetime is +in UTC. -.. autofunction:: http_date +When comparing datetime values from Werkzeug, your own datetime objects +must also be timezone-aware, or you must make the values from Werkzeug +naive. + +* ``dt = datetime.now(timezone.utc)`` gets the current time in UTC. +* ``dt = datetime(..., tzinfo=timezone.utc)`` creates a time in UTC. +* ``dt = dt.replace(tzinfo=timezone.utc)`` makes a naive object aware + by assuming it's in UTC. +* ``dt = dt.replace(tzinfo=None)`` makes an aware object naive. .. autofunction:: parse_date +.. autofunction:: http_date + + Header Parsing ============== @@ -130,17 +139,23 @@ by any of the modern web browsers. Usage example: ->>> from cStringIO import StringIO ->>> data = '--foo\r\nContent-Disposition: form-data; name="test"\r\n' \ -... '\r\nHello World!\r\n--foo--' ->>> environ = {'wsgi.input': StringIO(data), 'CONTENT_LENGTH': str(len(data)), -... 'CONTENT_TYPE': 'multipart/form-data; boundary=foo', -... 'REQUEST_METHOD': 'POST'} +>>> from io import BytesIO +>>> from werkzeug.formparser import parse_form_data +>>> data = ( +... b'--foo\r\nContent-Disposition: form-data; name="test"\r\n' +... b"\r\nHello World!\r\n--foo--" +... ) +>>> environ = { +... "wsgi.input": BytesIO(data), +... "CONTENT_LENGTH": str(len(data)), +... "CONTENT_TYPE": "multipart/form-data; boundary=foo", +... "REQUEST_METHOD": "POST", +... } >>> stream, form, files = parse_form_data(environ) >>> stream.read() -'' +b'' >>> form['test'] -u'Hello World!' +'Hello World!' >>> not files True @@ -152,5 +167,3 @@ environments for unittesting you might want to use the .. autoclass:: FormDataParser .. autofunction:: parse_form_data - -.. autofunction:: parse_multipart_headers diff --git a/docs/index.rst b/docs/index.rst index 48c0da563..c4f00194d 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -1,7 +1,78 @@ -====================== -Documentation Overview -====================== +Werkzeug +======== -Welcome to the Werkzeug |version| documentation. +*werkzeug* German noun: "tool". +Etymology: *werk* ("work"), *zeug* ("stuff") -.. include:: contents.rst.inc +Werkzeug is a comprehensive `WSGI`_ web application library. It began as +a simple collection of various utilities for WSGI applications and has +become one of the most advanced WSGI utility libraries. + +Werkzeug doesn't enforce any dependencies. It is up to the developer to +choose a template engine, database adapter, and even how to handle +requests. + +.. _WSGI: https://wsgi.readthedocs.io/en/latest/ + + +Getting Started +--------------- + +.. toctree:: + :maxdepth: 2 + + installation + tutorial + levels + quickstart + + +Serving and Testing +------------------- + +.. toctree:: + :maxdepth: 2 + + serving + test + debug + + +Reference +--------- + +.. toctree:: + :maxdepth: 2 + + wrappers + routing + wsgi + http + datastructures + utils + urls + local + middleware/index + exceptions + + +Deployment +---------- + +.. toctree:: + :maxdepth: 3 + + deployment/index + + +Additional Information +---------------------- + +.. toctree:: + :maxdepth: 2 + + terms + unicode + request_data + license + changes diff --git a/docs/installation.rst b/docs/installation.rst index c07d4521a..9c5aa7f72 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -1,133 +1,111 @@ -============ Installation ============ -Werkzeug requires at least Python 2.6 to work correctly. If you do need -to support an older version you can download an older version of Werkzeug -though we strongly recommend against that. Werkzeug currently has -experimental support for Python 3. For more information about the -Python 3 support see :ref:`python3`. +Python Version +-------------- -Installing a released version -============================= +We recommend using the latest version of Python. Werkzeug supports +Python 3.7 and newer. -As a Python egg (via easy_install or pip) ------------------------------------------ -You can install the most recent Werkzeug version using `easy_install`_:: +Dependencies +------------ - easy_install Werkzeug +Werkzeug does not have any direct dependencies. -Alternatively you can also use pip:: - pip install Werkzeug +Optional dependencies +~~~~~~~~~~~~~~~~~~~~~ -Either way we strongly recommend using these tools in combination with -:ref:`virtualenv`. +These distributions will not be installed automatically. Werkzeug will +detect and use them if you install them. -This will install a Werkzeug egg in your Python installation's `site-packages` -directory. +* `Colorama`_ provides request log highlighting when using the + development server on Windows. This works automatically on other + systems. +* `Watchdog`_ provides a faster, more efficient reloader for the + development server. -From the tarball release -------------------------- +.. _Colorama: https://pypi.org/project/colorama/ +.. _Watchdog: https://pypi.org/project/watchdog/ -1. Download the most recent tarball from the `download page`_. -2. Unpack the tarball. -3. ``python setup.py install`` -Note that the last command will automatically download and install -`setuptools`_ if you don't already have it installed. This requires a working -Internet connection. +greenlet +~~~~~~~~ -This will install Werkzeug into your Python installation's `site-packages` -directory. +You may choose to use gevent or eventlet with your application. In this +case, greenlet>=1.0 is required. When using PyPy, PyPy>=7.3.7 is +required. +These are not minimum supported versions, they only indicate the first +versions that added necessary features. You should use the latest +versions of each. -Installing the development version -================================== -1. Install `Git`_ -2. ``git clone git://github.com/pallets/werkzeug.git`` -3. ``cd werkzeug`` -4. ``pip install --editable .`` +Virtual environments +-------------------- -.. _virtualenv: +Use a virtual environment to manage the dependencies for your project, +both in development and in production. -virtualenv -========== +What problem does a virtual environment solve? The more Python +projects you have, the more likely it is that you need to work with +different versions of Python libraries, or even Python itself. Newer +versions of libraries for one project can break compatibility in +another project. -Virtualenv is probably what you want to use during development, and in -production too if you have shell access there. +Virtual environments are independent groups of Python libraries, one for +each project. Packages installed for one project will not affect other +projects or the operating system's packages. -What problem does virtualenv solve? If you like Python as I do, -chances are you want to use it for other projects besides Werkzeug-based -web applications. But the more projects you have, the more likely it is -that you will be working with different versions of Python itself, or at -least different versions of Python libraries. Let's face it; quite often -libraries break backwards compatibility, and it's unlikely that any serious -application will have zero dependencies. So what do you do if two or more -of your projects have conflicting dependencies? +Python comes bundled with the :mod:`venv` module to create virtual +environments. -Virtualenv to the rescue! It basically enables multiple side-by-side -installations of Python, one for each project. It doesn't actually -install separate copies of Python, but it does provide a clever way -to keep different project environments isolated. -So let's see how virtualenv works! +Create an environment +~~~~~~~~~~~~~~~~~~~~~ -If you are on Mac OS X or Linux, chances are that one of the following two -commands will work for you:: +Create a project folder and a :file:`venv` folder within: - $ sudo easy_install virtualenv +.. code-block:: sh -or even better:: + mkdir myproject + cd myproject + python3 -m venv venv - $ sudo pip install virtualenv +On Windows: -One of these will probably install virtualenv on your system. Maybe it's -even in your package manager. If you use Ubuntu, try:: +.. code-block:: bat - $ sudo apt-get install python-virtualenv + py -3 -m venv venv -If you are on Windows and don't have the `easy_install` command, you must -install it first. Once you have it installed, run the same commands as -above, but without the `sudo` prefix. -Once you have virtualenv installed, just fire up a shell and create -your own environment. I usually create a project folder and an `env` -folder within:: +Activate the environment +~~~~~~~~~~~~~~~~~~~~~~~~ - $ mkdir myproject - $ cd myproject - $ virtualenv env - New python executable in env/bin/python - Installing setuptools............done. +Before you work on your project, activate the corresponding environment: -Now, whenever you want to work on a project, you only have to activate -the corresponding environment. On OS X and Linux, do the following:: +.. code-block:: sh - $ . env/bin/activate + . venv/bin/activate -(Note the space between the dot and the script name. The dot means that -this script should run in the context of the current shell. If this command -does not work in your shell, try replacing the dot with ``source``) +On Windows: -If you are a Windows user, the following command is for you:: +.. code-block:: bat - $ env\scripts\activate + venv\Scripts\activate -Either way, you should now be using your virtualenv (see how the prompt of -your shell has changed to show the virtualenv). +Your shell prompt will change to show the name of the activated +environment. -Now you can just enter the following command to get Werkzeug activated in -your virtualenv:: - $ pip install Werkzeug +Install Werkzeug +---------------- -A few seconds later you are good to go. +Within the activated environment, use the following command to install +Werkzeug: -.. _download page: https://pypi.python.org/pypi/Werkzeug -.. _setuptools: http://peak.telecommunity.com/DevCenter/setuptools -.. _easy_install: http://peak.telecommunity.com/DevCenter/EasyInstall -.. _Git: http://git-scm.org/ +.. code-block:: sh + + pip install Werkzeug diff --git a/docs/latexindex.rst b/docs/latexindex.rst deleted file mode 100644 index 159ffea1a..000000000 --- a/docs/latexindex.rst +++ /dev/null @@ -1,6 +0,0 @@ -:orphan: - -Werkzeug Documentation -====================== - -.. include:: contents.rst.inc diff --git a/docs/levels.rst b/docs/levels.rst index 5a0190a19..a07fd86d9 100644 --- a/docs/levels.rst +++ b/docs/levels.rst @@ -2,7 +2,7 @@ API Levels ========== -.. module:: werkzeug +.. currentmodule:: werkzeug Werkzeug is intended to be a utility rather than a framework. Because of that the user-friendly API is separated from the lower-level API so that Werkzeug @@ -15,16 +15,18 @@ Example ======= This example implements a small `Hello World` application that greets the -user with the name entered:: +user with the name entered. - from werkzeug.utils import escape +.. code-block:: python + + from markupsafe import escape from werkzeug.wrappers import Request, Response @Request.application def hello_world(request): result = ['Greeter'] if request.method == 'POST': - result.append('

Hello %s!

' % escape(request.form['name'])) + result.append(f"

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

") result.append('''

Name: @@ -36,14 +38,14 @@ user with the name entered:: Alternatively the same application could be used without request and response objects but by taking advantage of the parsing functions werkzeug provides:: + from markupsafe import escape from werkzeug.formparser import parse_form_data - from werkzeug.utils import escape def hello_world(environ, start_response): result = ['Greeter'] if environ['REQUEST_METHOD'] == 'POST': form = parse_form_data(environ)[1] - result.append('

Hello %s!

' % escape(form['name'])) + result.append(f"

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

") result.append('''

Name: @@ -51,7 +53,7 @@ objects but by taking advantage of the parsing functions werkzeug provides::

''') start_response('200 OK', [('Content-Type', 'text/html; charset=utf-8')]) - return [''.join(result)] + return [''.join(result).encode('utf-8')] High or Low? ============ diff --git a/docs/license.rst b/docs/license.rst new file mode 100644 index 000000000..a53a98cf3 --- /dev/null +++ b/docs/license.rst @@ -0,0 +1,4 @@ +BSD-3-Clause License +==================== + +.. include:: ../LICENSE.rst diff --git a/docs/local.rst b/docs/local.rst index 4d776e975..015b0e3e9 100644 --- a/docs/local.rst +++ b/docs/local.rst @@ -1,94 +1,110 @@ -============== Context Locals ============== .. module:: werkzeug.local -Sooner or later you have some things you want to have in every single view -or helper function or whatever. In PHP the way to go are global -variables. However, that isn't possible in WSGI applications without a -major drawback: As soon as you operate on the global namespace your -application isn't thread-safe any longer. +You may find that you have some data during each request that you want +to use across functions. Instead of passing these as arguments between +every function, you may want to access them as global data. However, +using global variables in Python web applications is not thread safe; +different workers might interfere with each others' data. + +Instead of storing common data during a request using global variables, +you must use context-local variables instead. A context local is +defined/imported globally, but the data it contains is specific to the +current thread, asyncio task, or greenlet. You won't accidentally get +or overwrite another worker's data. + +The current approach for storing per-context data in Python is the +:class:`contextvars` module. Context vars store data per thread, async +task, or greenlet. This replaces the older :class:`threading.local` +which only handled threads. + +Werkzeug provides wrappers around :class:`~contextvars.ContextVar` to +make it easier to work with. -The Python standard library has a concept called "thread locals" (or thread-local -data). A thread local is a global object in which you can put stuff in and get back -later in a thread-safe and thread-specific way. That means that whenever you set -or get a value on a thread local object, the thread local object checks in which -thread you are and retrieves the value corresponding to your thread (if one exists). -So, you won't accidentally get another thread's data. -This approach, however, has a few disadvantages. For example, besides threads, -there are other types of concurrency in Python. A very popular one -is greenlets. Also, whether every request gets its own thread is not -guaranteed in WSGI. It could be that a request is reusing a thread from -a previous request, and hence data is left over in the thread local object. +Proxy Objects +============= -Werkzeug provides its own implementation of local data storage called `werkzeug.local`. -This approach provides a similar functionality to thread locals but also works with -greenlets. +:class:`LocalProxy` allows treating a context var as an object directly +instead of needing to use and check +:meth:`ContextVar.get() `. If the context +var is set, the local proxy will look and behave like the object the var +is set to. If it's not set, a ``RuntimeError`` is raised for most +operations. -Here's a simple example of how one could use werkzeug.local:: +.. code-block:: python - from werkzeug.local import Local, LocalManager + from contextvars import ContextVar + from werkzeug.local import LocalProxy - local = Local() - local_manager = LocalManager([local]) + _request_var = ContextVar("request") + request = LocalProxy(_request_var) - def application(environ, start_response): - local.request = request = Request(environ) + from werkzeug.wrappers import Request + + @Request.application + def app(r): + _request_var.set(r) + check_auth() ... - application = local_manager.make_middleware(application) + from werkzeug.exceptions import Unauthorized -This binds the request to `local.request`. Every other piece of code executed -after this assignment in the same context can safely access local.request and -will get the same request object. The `make_middleware` method on the local -manager ensures that all references to the local objects are cleared up after -the request. + def check_auth(): + if request.form["username"] != "admin": + raise Unauthorized() -The same context means the same greenlet (if you're using greenlets) in -the same thread and same process. +Accessing ``request`` will point to the specific request that each +server worker is handling. You can treat ``request`` just like an actual +``Request`` object. -If a request object is not yet set on the local object and you try to -access it, you will get an `AttributeError`. You can use `getattr` to avoid -that:: +``bool(proxy)`` will always return ``False`` if the var is not set. If +you need access to the object directly instead of the proxy, you can get +it with the :meth:`~LocalProxy._get_current_object` method. - def get_request(): - return getattr(local, 'request', None) +.. autoclass:: LocalProxy + :members: _get_current_object -This will try to get the request or return `None` if the request is not -(yet?) available. -Note that local objects cannot manage themselves, for that you need a local -manager. You can pass a local manager multiple locals or add additionals -later by appending them to `manager.locals` and every time the manager -cleans up it will clean up all the data left in the locals for this -context. +Stacks and Namespaces +===================== -.. autofunction:: release_local +:class:`~contextvars.ContextVar` stores one value at a time. You may +find that you need to store a stack of items, or a namespace with +multiple attributes. A list or dict can be used for these, but using +them as context var values requires some extra care. Werkzeug provides +:class:`LocalStack` which wraps a list, and :class:`Local` which wraps a +dict. -.. autoclass:: LocalManager - :members: cleanup, make_middleware, middleware, get_ident +There is some amount of performance penalty associated with these +objects. Because lists and dicts are mutable, :class:`LocalStack` and +:class:`Local` need to do extra work to ensure data isn't shared between +nested contexts. If possible, design your application to use +:class:`LocalProxy` around a context var directly. .. autoclass:: LocalStack - :members: push, pop, top + :members: push, pop, top, __call__ + +.. autoclass:: Local + :members: __call__ -.. autoclass:: LocalProxy - :members: _get_current_object - Keep in mind that ``repr()`` is also forwarded, so if you want to find - out if you are dealing with a proxy you can do an ``isinstance()`` check: +Releasing Data +============== - .. sourcecode:: pycon +A previous implementation of ``Local`` used internal data structures +which could not be cleaned up automatically when each context ended. +Instead, the following utilities could be used to release the data. - >>> from werkzeug.local import LocalProxy - >>> isinstance(request, LocalProxy) - True +.. warning:: - You can also create proxy objects by hand: + This should not be needed with the modern implementation, as the + data in context vars is automatically managed by Python. It is kept + for compatibility for now, but may be removed in the future. - .. sourcecode:: python +.. autoclass:: LocalManager + :members: cleanup, make_middleware, middleware - from werkzeug.local import Local, LocalProxy - local = Local() - request = LocalProxy(local, 'request') +.. autofunction:: release_local diff --git a/docs/logo.pdf b/docs/logo.pdf deleted file mode 100644 index 00cc0c357..000000000 Binary files a/docs/logo.pdf and /dev/null differ diff --git a/docs/make.bat b/docs/make.bat index c0f34eb5b..7893348a1 100644 --- a/docs/make.bat +++ b/docs/make.bat @@ -1,95 +1,35 @@ @ECHO OFF +pushd %~dp0 + REM Command file for Sphinx documentation -set SPHINXBUILD=sphinx-build -set ALLSPHINXOPTS=-d _build/doctrees %SPHINXOPTS% . -if NOT "%PAPER%" == "" ( - set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build ) +set SOURCEDIR=. +set BUILDDIR=_build if "%1" == "" goto help -if "%1" == "help" ( - :help - echo.Please use `make ^` where ^ is one of - echo. html to make standalone HTML files - echo. pickle to make pickle files - echo. json to make JSON files - echo. htmlhelp to make HTML files and a HTML help project - echo. qthelp to make HTML files and a qthelp project - echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter - echo. changes to make an overview over all changed/added/deprecated items - echo. linkcheck to check all external links for integrity - goto end -) - -if "%1" == "clean" ( - for /d %%i in (_build\*) do rmdir /q /s %%i - del /q /s _build\* - goto end -) - -if "%1" == "html" ( - %SPHINXBUILD% -b html %ALLSPHINXOPTS% _build/html - echo. - echo.Build finished. The HTML pages are in _build/html. - goto end -) - -if "%1" == "pickle" ( - %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% _build/pickle +%SPHINXBUILD% >NUL 2>NUL +if errorlevel 9009 ( echo. - echo.Build finished; now you can process the pickle files. - goto end -) - -if "%1" == "json" ( - %SPHINXBUILD% -b json %ALLSPHINXOPTS% _build/json + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. echo. - echo.Build finished; now you can process the JSON files. - goto end + echo.If you don't have Sphinx installed, grab it from + echo.http://sphinx-doc.org/ + exit /b 1 ) -if "%1" == "htmlhelp" ( - %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% _build/htmlhelp - echo. - echo.Build finished; now you can run HTML Help Workshop with the ^ -.hhp project file in _build/htmlhelp. - goto end -) +%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% +goto end -if "%1" == "qthelp" ( - %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% _build/qthelp - echo. - echo.Build finished; now you can run "qcollectiongenerator" with the ^ -.qhcp project file in _build/qthelp, like this: - echo.^> qcollectiongenerator _build\qthelp\Werkzeug.qhcp - echo.To view the help file: - echo.^> assistant -collectionFile _build\qthelp\Werkzeug.ghc - goto end -) - -if "%1" == "latex" ( - %SPHINXBUILD% -b latex %ALLSPHINXOPTS% _build/latex - echo. - echo.Build finished; the LaTeX files are in _build/latex. - goto end -) - -if "%1" == "changes" ( - %SPHINXBUILD% -b changes %ALLSPHINXOPTS% _build/changes - echo. - echo.The overview file is in _build/changes. - goto end -) - -if "%1" == "linkcheck" ( - %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% _build/linkcheck - echo. - echo.Link check complete; look for any errors in the above output ^ -or in _build/linkcheck/output.txt. - goto end -) +:help +%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% :end +popd diff --git a/docs/makearchive.py b/docs/makearchive.py deleted file mode 100644 index 62e208e35..000000000 --- a/docs/makearchive.py +++ /dev/null @@ -1,7 +0,0 @@ -import os -import conf -name = "werkzeug-docs-" + conf.version -os.chdir("_build") -os.rename("html", name) -os.system("tar czf %s.tar.gz %s" % (name, name)) -os.rename(name, "html") diff --git a/docs/middleware/dispatcher.rst b/docs/middleware/dispatcher.rst new file mode 100644 index 000000000..cc1cb3bec --- /dev/null +++ b/docs/middleware/dispatcher.rst @@ -0,0 +1 @@ +.. automodule:: werkzeug.middleware.dispatcher diff --git a/docs/middleware/http_proxy.rst b/docs/middleware/http_proxy.rst new file mode 100644 index 000000000..dcda2e888 --- /dev/null +++ b/docs/middleware/http_proxy.rst @@ -0,0 +1 @@ +.. automodule:: werkzeug.middleware.http_proxy diff --git a/docs/middleware/index.rst b/docs/middleware/index.rst new file mode 100644 index 000000000..70cddeefc --- /dev/null +++ b/docs/middleware/index.rst @@ -0,0 +1 @@ +.. automodule:: werkzeug.middleware diff --git a/docs/middleware/lint.rst b/docs/middleware/lint.rst new file mode 100644 index 000000000..e831572eb --- /dev/null +++ b/docs/middleware/lint.rst @@ -0,0 +1 @@ +.. automodule:: werkzeug.middleware.lint diff --git a/docs/middleware/profiler.rst b/docs/middleware/profiler.rst new file mode 100644 index 000000000..472a63a9b --- /dev/null +++ b/docs/middleware/profiler.rst @@ -0,0 +1 @@ +.. automodule:: werkzeug.middleware.profiler diff --git a/docs/middleware/proxy_fix.rst b/docs/middleware/proxy_fix.rst new file mode 100644 index 000000000..6c6d22eae --- /dev/null +++ b/docs/middleware/proxy_fix.rst @@ -0,0 +1 @@ +.. automodule:: werkzeug.middleware.proxy_fix diff --git a/docs/middleware/shared_data.rst b/docs/middleware/shared_data.rst new file mode 100644 index 000000000..4d56743a1 --- /dev/null +++ b/docs/middleware/shared_data.rst @@ -0,0 +1 @@ +.. automodule:: werkzeug.middleware.shared_data diff --git a/docs/middlewares.rst b/docs/middlewares.rst deleted file mode 100644 index 41f9a98ae..000000000 --- a/docs/middlewares.rst +++ /dev/null @@ -1,19 +0,0 @@ -=========== -Middlewares -=========== - -.. module:: werkzeug.wsgi - -Middlewares wrap applications to dispatch between them or provide -additional request handling. Additionally to the middlewares documented -here, there is also the :class:`DebuggedApplication` class that is -implemented as a WSGI middleware. - -.. autoclass:: SharedDataMiddleware - :members: is_allowed - -.. autoclass:: DispatcherMiddleware - -Also there's the … - -.. autofunction:: werkzeug._internal._easteregg diff --git a/docs/python3.rst b/docs/python3.rst deleted file mode 100644 index 5597fe542..000000000 --- a/docs/python3.rst +++ /dev/null @@ -1,73 +0,0 @@ -.. _python3: - -============== -Python 3 Notes -============== - -Since version 0.9, Werkzeug supports Python 3.3+ in addition to versions 2.6 -and 2.7. Older Python 3 versions such as 3.2 or 3.1 are not supported. - -This part of the documentation outlines special information required to -use Werkzeug and WSGI on Python 3. - -.. warning:: - - Python 3 support in Werkzeug is currently highly experimental. Please - give feedback on it and help us improve it. - - -WSGI Environment -================ - -The WSGI environment on Python 3 works slightly different than it does on -Python 2. For the most part Werkzeug hides the differences from you if -you work on the higher level APIs. The main difference between Python 2 -and Python 3 is that on Python 2 the WSGI environment contains bytes -whereas the environment on Python 3 contains a range of differently -encoded strings. - -There are two different kinds of strings in the WSGI environ on Python 3: - -- unicode strings restricted to latin1 values. These are used for - HTTP headers and a few other things. -- unicode strings carrying binary payload, roundtripped through latin1 - values. This is usually referred as “WSGI encoding dance” throughout - Werkzeug. - -Werkzeug provides you with functionality to deal with these automatically -so that you don't need to be aware of the inner workings. The following -functions and classes should be used to read information out of the -WSGI environment: - -- :func:`~werkzeug.wsgi.get_current_url` -- :func:`~werkzeug.wsgi.get_host` -- :func:`~werkzeug.wsgi.get_script_name` -- :func:`~werkzeug.wsgi.get_path_info` -- :func:`~werkzeug.wsgi.get_query_string` -- :func:`~werkzeug.datastructures.EnvironHeaders` - -Applications are strongly discouraged to create and modify a WSGI -environment themselves on Python 3 unless they take care of the proper -decoding step. All high level interfaces in Werkzeug will apply the -correct encoding and decoding steps as necessary. - -URLs -==== - -URLs in Werkzeug attempt to represent themselves as unicode strings on -Python 3. All the parsing functions generally also provide functionality -that allow operations on bytes. In some cases functions that deal with -URLs allow passing in `None` as charset to change the return value to byte -objects. Internally Werkzeug will now unify URIs and IRIs as much as -possible. - -Request Cleanup -=============== - -Request objects on Python 3 and PyPy require explicit closing when file -uploads are involved. This is required to properly close temporary file -objects created by the multipart parser. For that purpose the ``close()`` -method was introduced. - -In addition to that request objects now also act as context managers that -automatically close. diff --git a/docs/quickstart.rst b/docs/quickstart.rst index 30ec04082..1568892b0 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -1,21 +1,11 @@ -========== Quickstart ========== -.. module:: werkzeug +.. currentmodule:: werkzeug This part of the documentation shows how to use the most important parts of Werkzeug. It's intended as a starting point for developers with basic -understanding of :pep:`333` (WSGI) and :rfc:`2616` (HTTP). - -.. warning:: - - Make sure to import all objects from the places the documentation - suggests. It is theoretically possible in some situations to import - objects from different locations but this is not supported. - - For example :class:`MultiDict` is a member of the `werkzeug` module - but internally implemented in a different one. +understanding of :pep:`3333` (WSGI) and :rfc:`2616` (HTTP). WSGI Environment @@ -37,9 +27,9 @@ Now we have an environment to play around: >>> environ['SERVER_NAME'] 'localhost' -Usually nobody wants to work with the environ directly because it is limited -to bytestrings and does not provide any way to access the form data besides -parsing that data by hand. +Usually nobody wants to work with the environ directly because it uses a +confusing string encoding scheme, and it does not provide any way to +access the form data besides parsing that data by hand. Enter Request @@ -58,9 +48,9 @@ requests is set to `utf-8` but you can change that by subclassing :class:`Request`. >>> request.path -u'/foo' +'/foo' >>> request.script_root -u'' +'' >>> request.host 'localhost:8080' >>> request.url @@ -75,9 +65,9 @@ This way we can also access URL arguments (the query string) and data that was transmitted in a POST/PUT request. For testing purposes we can create a request object from supplied data -using the :meth:`~BaseRequest.from_values` method: +using the :meth:`~Request.from_values` method: ->>> from cStringIO import StringIO +>>> from io import StringIO >>> data = "name=this+is+encoded+form+data&another_key=another+one" >>> request = Request.from_values(query_string='foo=bar&blah=blafasel', ... content_length=len(data), input_stream=StringIO(data), @@ -92,12 +82,12 @@ Now we can access the URL parameters easily: >>> request.args.keys() ['blah', 'foo'] >>> request.args['blah'] -u'blafasel' +'blafasel' Same for the supplied form data: >>> request.form['name'] -u'this is encoded form data' +'this is encoded form data' Handling for uploaded files is not much harder as you can see from this example:: @@ -112,7 +102,7 @@ example:: The files are represented as :class:`FileStorage` objects which provide some common operations to work with them. -Request headers can be accessed by using the :class:`~BaseRequest.headers` +Request headers can be accessed by using the :class:`~Request.headers` attribute: >>> request.headers['Content-Length'] @@ -134,7 +124,6 @@ so that we can play with it: >>> environ = create_environ() >>> environ.update( -... HTTP_USER_AGENT='Mozilla/5.0 (Macintosh; U; Mac OS X 10.5; en-US; ) Firefox/3.1', ... HTTP_ACCEPT='text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', ... HTTP_ACCEPT_LANGUAGE='de-at,en-us;q=0.8,en;q=0.5', ... HTTP_ACCEPT_ENCODING='gzip,deflate', @@ -146,20 +135,9 @@ so that we can play with it: ... >>> request = Request(environ) -Let's start with the most useless header: the user agent: - ->>> request.user_agent.browser -'firefox' ->>> request.user_agent.platform -'macos' ->>> request.user_agent.version -'3.1' ->>> request.user_agent.language -'en-US' - -A more useful header is the accept header. With this header the browser -informs the web application what mimetypes it can handle and how well. All -accept headers are sorted by the quality, the best item being the first: +With the accept header the browser informs the web application what +mimetypes it can handle and how well. All accept headers are sorted by +the quality, the best item being the first: >>> request.accept_mimetypes.best 'text/html' @@ -195,7 +173,7 @@ True E-tags and other conditional headers are available in parsed form as well: >>> request.if_modified_since -datetime.datetime(2009, 2, 20, 10, 10, 25) +datetime.datetime(2009, 2, 20, 10, 10, 25, tzinfo=datetime.timezone.utc) >>> request.if_none_match >>> request.cache_control @@ -255,7 +233,7 @@ code or provide a message as well: '400 BAD REQUEST' As you can see attributes work in both directions. So you can set both -:attr:`~BaseResponse.status` and :attr:`~BaseResponse.status_code` and the +:attr:`~Response.status` and :attr:`~Response.status_code` and the change will be reflected to the other. Also common headers are exposed as attributes or with methods to set / @@ -263,8 +241,8 @@ retrieve them: >>> response.content_length 12 ->>> from datetime import datetime ->>> response.date = datetime(2009, 2, 20, 17, 42, 51) +>>> from datetime import datetime, timezone +>>> response.date = datetime(2009, 2, 20, 17, 42, 51, tzinfo=timezone.utc) >>> response.headers['Date'] 'Fri, 20 Feb 2009 17:42:51 GMT' @@ -317,7 +295,7 @@ response conditional against a request. Which means that if the request can assure that it has the information already, no data besides the headers is sent over the network which saves traffic. For that you should set at least an etag (which is used for comparison) and the date header and then -call :class:`~BaseRequest.make_conditional` with the request object. +call :class:`~Request.make_conditional` with the request object. The response is modified accordingly (status code changed, response body removed, entity headers removed etc.) diff --git a/docs/request_data.rst b/docs/request_data.rst index 4f32ab66f..83c627804 100644 --- a/docs/request_data.rst +++ b/docs/request_data.rst @@ -1,9 +1,7 @@ -.. _dealing-with-request-data: - Dealing with Request Data ========================= -.. module:: werkzeug +.. currentmodule:: werkzeug The most important rule about web development is "Do not trust the user". This is especially true for incoming request data on the input stream. @@ -20,7 +18,7 @@ The input stream has no end-of-file marker. If you would call the application to hang on conforming servers. This is actually intentional however painful. Werkzeug solves that problem by wrapping the input stream in a special :class:`LimitedStream`. The input stream is exposed -on the request objects as :attr:`~BaseRequest.stream`. This one is either +on the request objects as :attr:`~Request.stream`. This one is either an empty stream (if the form data was parsed) or a limited stream with the contents of the input stream. @@ -30,8 +28,8 @@ When does Werkzeug Parse? Werkzeug parses the incoming data under the following situations: -- you access either :attr:`~BaseRequest.form`, :attr:`~BaseRequest.files`, - or :attr:`~BaseRequest.stream` and the request method was +- you access either :attr:`~Request.form`, :attr:`~Request.files`, + or :attr:`~Request.stream` and the request method was `POST` or `PUT`. - if you call :func:`parse_form_data`. @@ -54,31 +52,31 @@ How does it Parse? The standard Werkzeug parsing behavior handles three cases: - input content type was `multipart/form-data`. In this situation the - :class:`~BaseRequest.stream` will be empty and - :class:`~BaseRequest.form` will contain the regular `POST` / `PUT` - data, :class:`~BaseRequest.files` will contain the uploaded + :class:`~Request.stream` will be empty and + :class:`~Request.form` will contain the regular `POST` / `PUT` + data, :class:`~Request.files` will contain the uploaded files as :class:`FileStorage` objects. - input content type was `application/x-www-form-urlencoded`. Then the - :class:`~BaseRequest.stream` will be empty and - :class:`~BaseRequest.form` will contain the regular `POST` / `PUT` - data and :class:`~BaseRequest.files` will be empty. -- the input content type was neither of them, :class:`~BaseRequest.stream` + :class:`~Request.stream` will be empty and + :class:`~Request.form` will contain the regular `POST` / `PUT` + data and :class:`~Request.files` will be empty. +- the input content type was neither of them, :class:`~Request.stream` points to a :class:`LimitedStream` with the input data for further processing. -Special note on the :attr:`~BaseRequest.get_data` method: Calling this +Special note on the :attr:`~Request.get_data` method: Calling this loads the full request data into memory. This is only safe to do if the -:attr:`~BaseRequest.max_content_length` is set. Also you can *either* -read the stream *or* call :meth:`~BaseRequest.get_data`. +:attr:`~Request.max_content_length` is set. Also you can *either* +read the stream *or* call :meth:`~Request.get_data`. Limiting Request Data --------------------- To avoid being the victim of a DDOS attack you can set the maximum -accepted content length and request field sizes. The :class:`BaseRequest` -class has two attributes for that: :attr:`~BaseRequest.max_content_length` -and :attr:`~BaseRequest.max_form_memory_size`. +accepted content length and request field sizes. The :class:`Request` +class has two attributes for that: :attr:`~Request.max_content_length` +and :attr:`~Request.max_form_memory_size`. The first one can be used to limit the total content length. For example by setting it to ``1024 * 1024 * 16`` the request won't accept more than @@ -86,7 +84,7 @@ by setting it to ``1024 * 1024 * 16`` the request won't accept more than Because certain data can't be moved to the hard disk (regular post data) whereas temporary files can, there is a second limit you can set. The -:attr:`~BaseRequest.max_form_memory_size` limits the size of `POST` +:attr:`~Request.max_form_memory_size` limits the size of `POST` transmitted form data. By setting it to ``1024 * 1024 * 2`` you can make sure that all in memory-stored fields are not more than 2MB in size. @@ -98,19 +96,5 @@ How to extend Parsing? ---------------------- Modern web applications transmit a lot more than multipart form data or -url encoded data. Extending the parsing capabilities by subclassing -the :class:`BaseRequest` is simple. The following example implements -parsing for incoming JSON data:: - - from werkzeug.utils import cached_property - from werkzeug.wrappers import Request - from simplejson import loads - - class JSONRequest(Request): - # accept up to 4MB of transmitted data. - max_content_length = 1024 * 1024 * 4 - - @cached_property - def json(self): - if self.headers.get('content-type') == 'application/json': - return loads(self.data) +url encoded data. To extend the capabilities, subclass :class:`Request` +or :class:`Request` and add or extend methods. diff --git a/docs/routing.rst b/docs/routing.rst index 0b1d8269d..8c04a233d 100644 --- a/docs/routing.rst +++ b/docs/routing.rst @@ -1,15 +1,9 @@ -.. _routing: - =========== URL Routing =========== .. module:: werkzeug.routing -.. testsetup:: - - from werkzeug.routing import * - When it comes to combining multiple controller or view functions (however you want to call them), you need a dispatcher. A simple way would be applying regular expression tests on ``PATH_INFO`` and call registered @@ -19,7 +13,7 @@ Werkzeug provides a much more powerful system, similar to `Routes`_. All the objects mentioned on this page must be imported from :mod:`werkzeug.routing`, not from :mod:`werkzeug`! -.. _Routes: http://routes.groovie.org/ +.. _Routes: https://routes.readthedocs.io/en/latest/ Quickstart @@ -48,7 +42,7 @@ Here is a simple example which could be the URL definition for a blog:: except HTTPException, e: return e(environ, start_response) start_response('200 OK', [('Content-Type', 'text/plain')]) - return ['Rule points to %r with arguments %r' % (endpoint, args)] + return [f'Rule points to {endpoint!r} with arguments {args!r}'.encode()] So what does that do? First of all we create a new :class:`Map` which stores a bunch of URL rules. Then we pass it a list of :class:`Rule` objects. @@ -74,24 +68,30 @@ exceptions have a look at the documentation of the :meth:`MapAdapter.match` meth Rule Format =========== -Rule strings basically are just normal URL paths with placeholders in the -format ````, where converter and the arguments -are optional. If no converter is defined, the `default` converter is used -(which means `string` in the normal configuration). +Rule strings are URL paths with placeholders for variable parts in the +format ````. ``converter`` and ``arguments`` +(with parentheses) are optional. If no converter is given, the +``default`` converter is used (``string`` by default). The available +converters are discussed below. -URL rules that end with a slash are branch URLs, others are leaves. If you -have `strict_slashes` enabled (which is the default), all branch URLs that are -visited without a trailing slash will trigger a redirect to the same URL with -that slash appended. +Rules that end with a slash are "branches", others are "leaves". If +``strict_slashes`` is enabled (the default), visiting a branch URL +without a trailing slash will redirect to the URL with a slash appended. -The list of converters can be extended, the default converters are explained -below. +Many HTTP servers merge consecutive slashes into one when receiving +requests. If ``merge_slashes`` is enabled (the default), rules will +merge slashes in non-variable parts when matching and building. Visiting +a URL with consecutive slashes will redirect to the URL with slashes +merged. If you want to disable ``merge_slashes`` for a :class:`Rule` or +:class:`Map`, you'll also need to configure your web server +appropriately. -Builtin Converters -================== +Built-in Converters +=================== -Here a list of converters that come with Werkzeug: +Converters for common types of URL variables are built-in. The available +converters can be overridden or extended through :attr:`Map.converters`. .. autoclass:: UnicodeConverter @@ -127,6 +127,13 @@ Maps, Rules and Adapters :members: empty +Matchers +======== + +.. autoclass:: StateMachineMatcher + :members: + + Rule Factories ============== @@ -149,38 +156,66 @@ Rule Templates Custom Converters ================= -You can easily add custom converters. The only thing you have to do is to -subclass :class:`BaseConverter` and pass that new converter to the url_map. -A converter has to provide two public methods: `to_python` and `to_url`, -as well as a member that represents a regular expression. Here is a small -example:: +You can add custom converters that add behaviors not provided by the +built-in converters. To make a custom converter, subclass +:class:`BaseConverter` then pass the new class to the :class:`Map` +``converters`` parameter, or add it to +:attr:`url_map.converters `. + +The converter should have a ``regex`` attribute with a regular +expression to match with. If the converter can take arguments in a URL +rule, it should accept them in its ``__init__`` method. The entire +regex expression will be matched as a group and used as the value for +conversion. + +If a custom converter can match a forward slash, ``/``, it should have +the attribute ``part_isolating`` set to ``False``. This will ensure +that rules using the custom converter are correctly matched. + +It can implement a ``to_python`` method to convert the matched string to +some other object. This can also do extra validation that wasn't +possible with the ``regex`` attribute, and should raise a +:exc:`werkzeug.routing.ValidationError` in that case. Raising any other +errors will cause a 500 error. + +It can implement a ``to_url`` method to convert a Python object to a +string when building a URL. Any error raised here will be converted to a +:exc:`werkzeug.routing.BuildError` and eventually cause a 500 error. + +This example implements a ``BooleanConverter`` that will match the +strings ``"yes"``, ``"no"``, and ``"maybe"``, returning a random value +for ``"maybe"``. :: from random import randrange - from werkzeug.routing import Rule, Map, BaseConverter, ValidationError + from werkzeug.routing import BaseConverter, ValidationError class BooleanConverter(BaseConverter): + regex = r"(?:yes|no|maybe)" - def __init__(self, url_map, randomify=False): - super(BooleanConverter, self).__init__(url_map) - self.randomify = randomify - self.regex = '(?:yes|no|maybe)' + def __init__(self, url_map, maybe=False): + super().__init__(url_map) + self.maybe = maybe def to_python(self, value): - if value == 'maybe': - if self.randomify: + if value == "maybe": + if self.maybe: return not randrange(2) - raise ValidationError() + raise ValidationError return value == 'yes' def to_url(self, value): - return value and 'yes' or 'no' + return "yes" if value else "no" + + from werkzeug.routing import Map, Rule url_map = Map([ - Rule('/vote/', endpoint='vote'), - Rule('/vote/', endpoint='foo') + Rule("/vote/", endpoint="vote"), + Rule("/guess/", endpoint="guess") ], converters={'bool': BooleanConverter}) -If you want that converter to be the default converter, name it ``'default'``. +If you want to change the default converter, assign a different +converter to the ``"default"`` key. + Host Matching ============= @@ -203,3 +238,70 @@ Variable parts are of course also possible in the host section:: Rule('/', endpoint='www_index', host='www.example.com'), Rule('/', endpoint='user_index', host='.example.com') ], host_matching=True) + + +WebSockets +========== + +.. versionadded:: 1.0 + +If a :class:`Rule` is created with ``websocket=True``, it will only +match if the :class:`Map` is bound to a request with a ``url_scheme`` of +``ws`` or ``wss``. + +.. note:: + + Werkzeug has no further WebSocket support beyond routing. This + functionality is mostly of use to ASGI projects. + +.. code-block:: python + + url_map = Map([ + Rule("/ws", endpoint="comm", websocket=True), + ]) + adapter = map.bind("example.org", "/ws", url_scheme="ws") + assert adapter.match() == ("comm", {}) + +If the only match is a WebSocket rule and the bind is HTTP (or the +only match is HTTP and the bind is WebSocket) a +:exc:`WebsocketMismatch` (derives from +:exc:`~werkzeug.exceptions.BadRequest`) exception is raised. + +As WebSocket URLs have a different scheme, rules are always built with a +scheme and host, ``force_external=True`` is implied. + +.. code-block:: python + + url = adapter.build("comm") + assert url == "ws://example.org/ws" + + +State Machine Matching +====================== + +The default matching algorithm uses a state machine that transitions +between parts of the request path to find a match. To understand how +this works consider this rule:: + + /resource/ + +Firstly this rule is decomposed into two ``RulePart``. The first is a +static part with a content equal to ``resource``, the second is +dynamic and requires a regex match to ``[^/]+``. + +A state machine is then created with an initial state that represents +the rule's first ``/``. This initial state has a single, static +transition to the next state which represents the rule's second +``/``. This second state has a single dynamic transition to the final +state which includes the rule. + +To match a path the matcher starts and the initial state and follows +transitions that work. Clearly a trial path of ``/resource/2`` has the +parts ``""``, ``resource``, and ``2`` which match the transitions and +hence a rule will match. Whereas ``/other/2`` will not match as there +is no transition for the ``other`` part from the initial state. + +The only diversion from this rule is if a ``RulePart`` is not +part-isolating i.e. it will match ``/``. In this case the ``RulePart`` +is considered final and represents a transition that must include all +the subsequent parts of the trial path. diff --git a/docs/serving.rst b/docs/serving.rst index 39f618f3a..693774c53 100644 --- a/docs/serving.rst +++ b/docs/serving.rst @@ -12,9 +12,6 @@ with a builtin development server. The easiest way is creating a small ``start-myproject.py`` file that runs the application using the builtin server:: - #!/usr/bin/env python - # -*- coding: utf-8 -*- - from werkzeug.serving import run_simple from myproject import make_app @@ -35,7 +32,7 @@ additional files (like configuration files) you want to observe. The development server is not intended to be used on production systems. It was designed especially for development purposes and performs poorly under high load. For deployment setups have a look at the - :ref:`deployment` pages. + :doc:`/deployment/index` pages. .. _reloader: @@ -55,7 +52,7 @@ Since version 0.10, there are two backends the reloader supports: ``stat`` and drain a laptop's battery. - The ``watchdog`` backend uses filesystem events, and is much faster than - ``stat``. It requires the `watchdog `_ + ``stat``. It requires the `watchdog `_ module to be installed. The recommended way to achieve this is to add ``Werkzeug[watchdog]`` to your requirements file. @@ -72,11 +69,16 @@ polling and ``'watchdog'`` forces it to the watchdog backend. handled by the stat reloader for performance reasons. The watchdog reloader monitors such files too. + Colored Logging --------------- -Werkzeug is able to color the output of request logs when ran from a terminal, just install the `termcolor -`_ package. Windows users need to install `colorama -`_ in addition to termcolor for this to work. + +The development server highlights the request logs in different colors +based on the status code. On Windows, `Colorama`_ must be installed as +well to enable this. + +.. _Colorama: https://pypi.org/project/colorama/ + Virtual Hosts ------------- @@ -102,24 +104,50 @@ You can open the file with your favorite text editor and add a new name after Save the changes and after a while you should be able to access the development server on these host names as well. You can use the -:ref:`routing` system to dispatch between different hosts or parse +:doc:`/routing` system to dispatch between different hosts or parse :attr:`request.host` yourself. + Shutting Down The Server ------------------------ -.. versionadded:: 0.7 +In some cases it can be useful to shut down a server after handling a +request. For example, a local command line tool that needs OAuth +authentication could temporarily start a server to listen for a +response, record the user's token, then stop the server. + +One method to do this could be to start a server in a +:mod:`multiprocessing` process, then terminate the process after a value +is passed back to the parent. + +.. code-block:: python + + import multiprocessing + from werkzeug import Request, Response, run_simple -Starting with Werkzeug 0.7 the development server provides a way to shut -down the server after a request. This currently only works with Python -2.6 and later and will only work with the development server. To initiate -the shutdown you have to call a function named -``'werkzeug.server.shutdown'`` in the WSGI environment:: + def get_token(q: multiprocessing.Queue) -> None: + @Request.application + def app(request: Request) -> Response: + q.put(request.args["token"]) + return Response("", 204) + + run_simple("localhost", 5000, app) + + if __name__ == "__main__": + q = multiprocessing.Queue() + p = multiprocessing.Process(target=get_token, args=(q,)) + p.start() + print("waiting") + token = q.get(block=True) + p.terminate() + print(token) + +That example uses Werkzeug's development server, but any production +server that can be started as a Python process could use the same +technique and should be preferred for security. Another method could be +to start a :mod:`subprocess` process and send the value back over +``stdout``. - def shutdown_server(environ): - if not 'werkzeug.server.shutdown' in environ: - raise RuntimeError('Not running the development server') - environ['werkzeug.server.shutdown']() Troubleshooting --------------- @@ -151,7 +179,7 @@ preferring ipv6, you will be unable to connect to your server. In that situation, you can either remove the localhost entry for ``::1`` or explicitly bind the hostname to an ipv4 address (`127.0.0.1`) -.. _hosts file: http://en.wikipedia.org/wiki/Hosts_file +.. _hosts file: https://en.wikipedia.org/wiki/Hosts_file SSL --- @@ -188,12 +216,13 @@ You will have to acknowledge the certificate in your browser once then. Loading Contexts by Hand ```````````````````````` -In Python 2.7.9 and 3+ you also have the option to use a ``ssl.SSLContext`` -object instead of a simple tuple. This way you have better control over the SSL -behavior of Werkzeug's builtin server:: +You can use a ``ssl.SSLContext`` object instead of a tuple for full +control over the TLS configuration. + +.. code-block:: python import ssl - ctx = ssl.SSLContext(ssl.PROTOCOL_SSLv23) + ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER) ctx.load_cert_chain('ssl.cert', 'ssl.key') run_simple('localhost', 4000, application, ssl_context=ctx) @@ -224,4 +253,15 @@ certificate each time the server is reloaded. Adhoc certificates are discouraged because modern browsers do a bad job at supporting them for security reasons. -This feature requires the pyOpenSSL library to be installed. +This feature requires the cryptography library to be installed. + + +Unix Sockets +------------ + +The dev server can bind to a Unix socket instead of a TCP socket. +:func:`run_simple` will bind to a Unix socket if the ``hostname`` +parameter starts with ``'unix://'``. :: + + from werkzeug.serving import run_simple + run_simple('unix://example.sock', 0, app) diff --git a/docs/terms.rst b/docs/terms.rst index f2b85820c..088aaa45a 100644 --- a/docs/terms.rst +++ b/docs/terms.rst @@ -2,7 +2,7 @@ Important Terms =============== -.. module:: werkzeug +.. currentmodule:: werkzeug This page covers important terms used in the documentation and Werkzeug itself. @@ -12,7 +12,7 @@ WSGI ---- WSGI a specification for Python web applications Werkzeug follows. It was -specified in the :pep:`333` and is widely supported. Unlike previous solutions +specified in the :pep:`3333` and is widely supported. Unlike previous solutions it guarantees that web applications, servers and utilities can work together. Response Object @@ -23,7 +23,7 @@ application but does not do any request processing. Usually you have a view function or controller method that processes the request and assembles a response object. -A response object is *not* necessarily the :class:`BaseResponse` object or a +A response object is *not* necessarily the :class:`Response` class or a subclass thereof. For example Pylons/webob provide a very similar response class that can diff --git a/docs/test.rst b/docs/test.rst index eb0130b3d..efb449a1f 100644 --- a/docs/test.rst +++ b/docs/test.rst @@ -1,172 +1,111 @@ -============== -Test Utilities -============== - .. module:: werkzeug.test -Quite often you want to unittest your application or just check the output -from an interactive python session. In theory that is pretty simple because -you can fake a WSGI environment and call the application with a dummy -`start_response` and iterate over the application iterator but there are -argumentably better ways to interact with an application. - +Testing WSGI Applications +========================= -Diving In -========= -Werkzeug provides a `Client` object which you can pass a WSGI application (and -optionally a response wrapper) which you can use to send virtual requests to -the application. +Test Client +----------- -A response wrapper is a callable that takes three arguments: the application -iterator, the status and finally a list of headers. The default response -wrapper returns a tuple. Because response objects have the same signature, -you can use them as response wrapper, ideally by subclassing them and hooking -in test functionality. +Werkzeug provides a :class:`Client` to simulate requests to a WSGI +application without starting a server. The client has methods for making +different types of requests, as well as managing cookies across +requests. >>> from werkzeug.test import Client >>> from werkzeug.testapp import test_app ->>> from werkzeug.wrappers import BaseResponse ->>> c = Client(test_app, BaseResponse) ->>> resp = c.get('/') ->>> resp.status_code +>>> c = Client(test_app) +>>> response = c.get("/") +>>> response.status_code 200 >>> resp.headers -Headers([('Content-Type', 'text/html; charset=utf-8'), ('Content-Length', '8339')]) ->>> resp.data.splitlines()[0] -'>> c = Client(test_app) ->>> app_iter, status, headers = c.get('/') ->>> status -'200 OK' ->>> headers -[('Content-Type', 'text/html; charset=utf-8'), ('Content-Length', '8339')] ->>> ''.join(app_iter).splitlines()[0] -'>> from werkzeug.test import EnvironBuilder ->>> from StringIO import StringIO ->>> builder = EnvironBuilder(method='POST', data={'foo': 'this is some text', -... 'file': (StringIO('my file contents'), 'test.txt')}) ->>> env = builder.get_environ() - -The resulting environment is a regular WSGI environment that can be used for -further processing: - ->>> from werkzeug.wrappers import Request ->>> req = Request(env) ->>> req.form['foo'] -u'this is some text' ->>> req.files['file'] - ->>> req.files['file'].read() -'my file contents' +Headers([('Content-Type', 'text/html; charset=utf-8'), ('Content-Length', '6658')]) +>>> response.get_data(as_text=True) +'...' -The :class:`EnvironBuilder` figures out the content type automatically if you -pass a dict to the constructor as `data`. If you provide a string or an -input stream you have to do that yourself. +The client's request methods return instances of :class:`TestResponse`. +This provides extra attributes and methods on top of +:class:`~werkzeug.wrappers.Response` that are useful for testing. -By default it will try to use ``application/x-www-form-urlencoded`` and only -use ``multipart/form-data`` if files are uploaded: ->>> builder = EnvironBuilder(method='POST', data={'foo': 'bar'}) ->>> builder.content_type -'application/x-www-form-urlencoded' ->>> builder.files['foo'] = StringIO('contents') ->>> builder.content_type -'multipart/form-data' +Request Body +------------ -If a string is provided as data (or an input stream) you have to specify -the content type yourself: +By passing a dict to ``data``, the client will construct a request body +with file and form data. It will set the content type to +``application/x-www-form-urlencoded`` if there are no files, or +``multipart/form-data`` there are. ->>> builder = EnvironBuilder(method='POST', data='{"json": "this is"}') ->>> builder.content_type ->>> builder.content_type = 'application/json' +.. code-block:: python + import io -Testing API -=========== + response = client.post(data={ + "name": "test", + "file": (BytesIO("file contents".encode("utf8")), "test.txt") + }) -.. autoclass:: EnvironBuilder - :members: - - .. attribute:: path - - The path of the application. (aka `PATH_INFO`) - - .. attribute:: charset +Pass a string, bytes, or file-like object to ``data`` to use that as the +raw request body. In that case, you should set the content type +appropriately. For example, to post YAML: - The charset used to encode unicode data. +.. code-block:: python - .. attribute:: headers + response = client.post( + data="a: value\nb: 1\n", content_type="application/yaml" + ) - A :class:`Headers` object with the request headers. +A shortcut when testing JSON APIs is to pass a dict to ``json`` instead +of using ``data``. This will automatically call ``json.dumps()`` and +set the content type to ``application/json``. Additionally, if the +app returns JSON, ``response.json`` will automatically call +``json.loads()``. - .. attribute:: errors_stream +.. code-block:: python - The error stream used for the `wsgi.errors` stream. + response = client.post("/api", json={"a": "value", "b": 1}) + obj = response.json() - .. attribute:: multithread - The value of `wsgi.multithread` +Environment Builder +------------------- - .. attribute:: multiprocess +:class:`EnvironBuilder` is used to construct a WSGI environ dict. The +test client uses this internally to prepare its requests. The arguments +passed to the client request methods are the same as the builder. - The value of `wsgi.multiprocess` +Sometimes, it can be useful to construct a WSGI environment manually. +An environ builder or dict can be passed to the test client request +methods in place of other arguments to use a custom environ. - .. attribute:: environ_base +.. code-block:: Python - The dict used as base for the newly create environ. + from werkzeug.test import EnvironBuilder + builder = EnvironBuilder(...) + # build an environ dict + environ = builder.get_environ() + # build an environ dict wrapped in a request + request = builder.get_request() - .. attribute:: environ_overrides +The test client responses make this available through +:attr:`TestResponse.request` and ``response.request.environ``. - A dict with values that are used to override the generated environ. - .. attribute:: input_stream - - The optional input stream. This and :attr:`form` / :attr:`files` - is mutually exclusive. Also do not provide this stream if the - request method is not `POST` / `PUT` or something comparable. +API +--- .. autoclass:: Client + :members: + :member-order: bysource - .. automethod:: open - - Shortcut methods are available for many HTTP methods: - - .. automethod:: get - - .. automethod:: patch - - .. automethod:: post - - .. automethod:: head - - .. automethod:: put - - .. automethod:: delete - - .. automethod:: options - - .. automethod:: trace +.. autoclass:: TestResponse + :members: + :member-order: bysource +.. autoclass:: EnvironBuilder + :members: + :member-order: bysource -.. autofunction:: create_environ([options]) +.. autofunction:: create_environ .. autofunction:: run_wsgi_app diff --git a/docs/transition.rst b/docs/transition.rst deleted file mode 100644 index a1257e170..000000000 --- a/docs/transition.rst +++ /dev/null @@ -1,73 +0,0 @@ -Transition to Werkzeug 1.0 -========================== - -Werkzeug originally had a magical import system hook that enabled -everything to be imported from one module and still loading the actual -implementations lazily as necessary. Unfortunately this turned out to be -slow and also unreliable on alternative Python implementations and -Google's App Engine. - -Starting with 0.7 we recommend against the short imports and strongly -encourage starting importing from the actual implementation module. -Werkzeug 1.0 will disable the magical import hook completely. - -Because finding out where the actual functions are imported and rewriting -them by hand is a painful and boring process we wrote a tool that aids in -making this transition. - -Automatically Rewriting Imports -------------------------------- - -For instance, with Werkzeug < 0.7 the recommended way to use the escape function -was this:: - - from werkzeug import escape - -With Werkzeug 0.7, the recommended way to import this function is -directly from the utils module (and with 1.0 this will become mandatory). -To automatically rewrite all imports one can use the -`werkzeug-import-rewrite `_ script. - -You can use it by executing it with Python and with a list of folders with -Werkzeug based code. It will then spit out a hg/git compatible patch -file. Example patch file creation:: - - $ python werkzeug-import-rewrite.py . > new-imports.udiff - -To apply the patch one of the following methods work: - -hg: - - :: - - hg import new-imports.udiff - -git: - - :: - - git apply new-imports.udiff - -patch: - - :: - - patch -p1 < new-imports.udiff - -Stop Using Deprecated Things ----------------------------- - -A few things in Werkzeug will stop being supported and for others, we're -suggesting alternatives even if they will stick around for a longer time. - -Do not use: - -- `werkzeug.script`, replace it with custom scripts written with - `argparse` or something similar. -- `werkzeug.template`, replace with a proper template engine. -- `werkzeug.contrib.jsrouting`, stop using URL generation for - JavaScript, it does not scale well with many public routing. -- `werkzeug.contrib.kickstart`, replace with hand written code, the - Werkzeug API became better in general that this is no longer - necessary. -- `werkzeug.contrib.testtools`, not useful really. diff --git a/docs/tutorial.rst b/docs/tutorial.rst index 99f84e0af..943787a7c 100644 --- a/docs/tutorial.rst +++ b/docs/tutorial.rst @@ -2,7 +2,7 @@ Werkzeug Tutorial ================= -.. module:: werkzeug +.. currentmodule:: werkzeug Welcome to the Werkzeug tutorial in which we will create a `TinyURL`_ clone that stores URLs in a redis instance. The libraries we will use for this @@ -45,9 +45,9 @@ The final result will look something like this: .. image:: _static/shortly.png :alt: a screenshot of shortly -.. _TinyURL: http://tinyurl.com/ +.. _TinyURL: https://tinyurl.com/ .. _Jinja: http://jinja.pocoo.org/ -.. _redis: http://redis.io/ +.. _redis: https://redis.io/ Step 0: A Basic WSGI Introduction --------------------------------- @@ -61,7 +61,7 @@ looks like this:: def application(environ, start_response): start_response('200 OK', [('Content-Type', 'text/plain')]) - return ['Hello World!'] + return ['Hello World!'.encode('utf-8')] A WSGI application is something you can call and pass an environ dict and a ``start_response`` callable. The environ contains all incoming @@ -89,7 +89,7 @@ against another word):: def application(environ, start_response): request = Request(environ) - text = 'Hello %s!' % request.args.get('name', 'World') + text = f"Hello {request.args.get('name', 'World')}!" response = Response(text, mimetype='text/plain') return response(environ, start_response) @@ -123,11 +123,11 @@ if they are not used right away, to keep it from being confusing:: import os import redis - import urlparse + from werkzeug.urls import url_parse from werkzeug.wrappers import Request, Response from werkzeug.routing import Map, Rule from werkzeug.exceptions import HTTPException, NotFound - from werkzeug.wsgi import SharedDataMiddleware + from werkzeug.middleware.shared_data import SharedDataMiddleware from werkzeug.utils import redirect from jinja2 import Environment, FileSystemLoader @@ -138,7 +138,9 @@ that exports all the files on the `static` folder on the web:: class Shortly(object): def __init__(self, config): - self.redis = redis.Redis(config['redis_host'], config['redis_port']) + self.redis = redis.Redis( + config['redis_host'], config['redis_port'], decode_responses=True + ) def dispatch_request(self, request): return Response('Hello World!') @@ -195,7 +197,7 @@ Intermezzo: Running the Application Now you should be able to execute the file with `python` and see a server on your local machine:: - $ python shortly.py + $ python shortly.py * Running on http://127.0.0.1:5000/ * Restarting with reloader: stat() polling @@ -258,8 +260,8 @@ The way we will do it in this tutorial is by calling the method ``on_`` adapter = self.url_map.bind_to_environ(request.environ) try: endpoint, values = adapter.match() - return getattr(self, 'on_' + endpoint)(request, **values) - except HTTPException, e: + return getattr(self, f'on_{endpoint}')(request, **values) + except HTTPException as e: return e We bind the URL map to the current environment and get back a @@ -270,7 +272,7 @@ endpoint and a dictionary of values in the URL. For instance the rule for to ``http://localhost:5000/foo`` we will get the following values back:: endpoint = 'follow_short_link' - values = {'short_id': u'foo'} + values = {'short_id': 'foo'} If it does not match anything, it will raise a :exc:`~werkzeug.exceptions.NotFound` exception, which is an @@ -296,7 +298,7 @@ Let's start with the first view: the one for new URLs:: error = 'Please enter a valid URL' else: short_id = self.insert_url(url) - return redirect('/%s+' % short_id) + return redirect(f"/{short_id}+") return self.render_template('new_url.html', error=error, url=url) This logic should be easy to understand. Basically we are checking that @@ -306,19 +308,19 @@ we need to write a function and a helper method. For URL validation this is good enough:: def is_valid_url(url): - parts = urlparse.urlparse(url) + parts = url_parse(url) return parts.scheme in ('http', 'https') For inserting the URL, all we need is this little method on our class:: def insert_url(self, url): - short_id = self.redis.get('reverse-url:' + url) + short_id = self.redis.get(f'reverse-url:{url}') if short_id is not None: return short_id url_num = self.redis.incr('last-url-id') short_id = base36_encode(url_num) - self.redis.set('url-target:' + short_id, url) - self.redis.set('reverse-url:' + url, short_id) + self.redis.set(f'url-target:{short_id}', url) + self.redis.set(f'reverse-url:{url}', short_id) return short_id ``reverse-url:`` + the URL will store the short id. If the URL was @@ -349,10 +351,10 @@ redis and redirect to it. Additionally we will also increment a counter so that we know how often a link was clicked:: def on_follow_short_link(self, request, short_id): - link_target = self.redis.get('url-target:' + short_id) + link_target = self.redis.get(f'url-target:{short_id}') if link_target is None: raise NotFound() - self.redis.incr('click-count:' + short_id) + self.redis.incr(f'click-count:{short_id}') return redirect(link_target) In this case we will raise a :exc:`~werkzeug.exceptions.NotFound` exception @@ -369,10 +371,10 @@ number of times the link was clicked and let it default to zero if such a key does not yet exist:: def on_short_link_details(self, request, short_id): - link_target = self.redis.get('url-target:' + short_id) + link_target = self.redis.get(f'url-target:{short_id}') if link_target is None: raise NotFound() - click_count = int(self.redis.get('click-count:' + short_id) or 0) + click_count = int(self.redis.get(f'click-count:{short_id}') or 0) return self.render_template('short_link_details.html', link_target=link_target, short_id=short_id, @@ -445,6 +447,8 @@ Step 9: The Style For this to look better than ugly black and white, here a simple stylesheet that goes along: +*static/style.css*: + .. sourcecode:: css body { background: #E8EFF0; margin: 0; padding: 0; } @@ -472,4 +476,4 @@ Look at the implementation in the example dictionary in the Werkzeug repository to see a version of this tutorial with some small refinements such as a custom 404 page. -- `shortly in the example folder `_ +- `shortly in the example folder `_ diff --git a/docs/unicode.rst b/docs/unicode.rst index 0dc977a7b..30f76f5dd 100644 --- a/docs/unicode.rst +++ b/docs/unicode.rst @@ -1,158 +1,76 @@ -.. _unicode: - -======= Unicode ======= -.. module:: werkzeug +.. currentmodule:: werkzeug -Since early Python 2 days unicode was part of all default Python builds. It -allows developers to write applications that deal with non-ASCII characters -in a straightforward way. But working with unicode requires a basic knowledge -about that matter, especially when working with libraries that do not support -it. +Werkzeug uses strings internally everwhere text data is assumed, even if +the HTTP standard is not Unicode aware. Basically all incoming data is +decoded from the charset (UTF-8 by default) so that you don't work with +bytes directly. Outgoing data is encoded into the target charset. -Werkzeug uses unicode internally everywhere text data is assumed, even if the -HTTP standard is not unicode aware as it. Basically all incoming data is -decoded from the charset specified (per default `utf-8`) so that you don't -operate on bytestrings any more. Outgoing unicode data is then encoded into -the target charset again. Unicode in Python -================= +----------------- -In Python 2 there are two basic string types: `str` and `unicode`. `str` may -carry encoded unicode data but it's always represented in bytes whereas the -`unicode` type does not contain bytes but charpoints. What does this mean? -Imagine you have the German Umlaut `ö`. In ASCII you cannot represent that -character, but in the `latin-1` and `utf-8` character sets you can represent -it, but they look differently when encoded: +Imagine you have the German Umlaut ``ö``. In ASCII you cannot represent +that character, but in the ``latin-1`` and ``utf-8`` character sets you +can represent it, but they look different when encoded: ->>> u'ö'.encode('latin1') -'\xf6' ->>> u'ö'.encode('utf-8') -'\xc3\xb6' +>>> "ö".encode("latin1") +b'\xf6' +>>> "ö".encode("utf-8") +b'\xc3\xb6' -So an `ö` might look totally different depending on the encoding which makes -it hard to work with it. The solution is using the `unicode` type (as we did -above, note the `u` prefix before the string). The unicode type does not -store the bytes for `ö` but the information, that this is a -``LATIN SMALL LETTER O WITH DIAERESIS``. +An ``ö`` looks different depending on the encoding which makes it hard +to work with it as bytes. Instead, Python treats strings as Unicode text +and stores the information ``LATIN SMALL LETTER O WITH DIAERESIS`` +instead of the bytes for ``ö`` in a specific encoding. The length of a +string with 1 character will be 1, where the length of the bytes might +be some other value. -Doing ``len(u'ö')`` will always give us the expected "1" but ``len('ö')`` -might give different results depending on the encoding of ``'ö'``. Unicode in HTTP -=============== - -The problem with unicode is that HTTP does not know what unicode is. HTTP -is limited to bytes but this is not a big problem as Werkzeug decodes and -encodes for us automatically all incoming and outgoing data. Basically what -this means is that data sent from the browser to the web application is per -default decoded from an utf-8 bytestring into a `unicode` string. Data sent -from the application back to the browser that is not yet a bytestring is then -encoded back to utf-8. - -Usually this "just works" and we don't have to worry about it, but there are -situations where this behavior is problematic. For example the Python 2 IO -layer is not unicode aware. This means that whenever you work with data from -the file system you have to properly decode it. The correct way to load -a text file from the file system looks like this:: - - f = file('/path/to/the_file.txt', 'r') - try: - text = f.decode('utf-8') # assuming the file is utf-8 encoded - finally: - f.close() - -There is also the codecs module which provides an open function that decodes -automatically from the given encoding. +--------------- -Error Handling -============== - -With Werkzeug 0.3 onwards you can further control the way Werkzeug works with -unicode. In the past Werkzeug ignored encoding errors silently on incoming -data. This decision was made to avoid internal server errors if the user -tampered with the submitted data. However there are situations where you -want to abort with a `400 BAD REQUEST` instead of silently ignoring the error. - -All the functions that do internal decoding now accept an `errors` keyword -argument that behaves like the `errors` parameter of the builtin string method -`decode`. The following values are possible: - -`ignore` - This is the default behavior and tells the codec to ignore characters that - it doesn't understand silently. - -`replace` - The codec will replace unknown characters with a replacement character - (`U+FFFD` ``REPLACEMENT CHARACTER``) - -`strict` - Raise an exception if decoding fails. - -Unlike the regular python decoding Werkzeug does not raise an -:exc:`UnicodeDecodeError` if the decoding failed but an -:exc:`~exceptions.HTTPUnicodeError` which -is a direct subclass of `UnicodeError` and the `BadRequest` HTTP exception. -The reason is that if this exception is not caught by the application but -a catch-all for HTTP exceptions exists a default `400 BAD REQUEST` error -page is displayed. - -There is additional error handling available which is a Werkzeug extension -to the regular codec error handling which is called `fallback`. Often you -want to use utf-8 but support latin1 as legacy encoding too if decoding -failed. For this case you can use the `fallback` error handling. For -example you can specify ``'fallback:iso-8859-15'`` to tell Werkzeug it should -try with `iso-8859-15` if `utf-8` failed. If this decoding fails too (which -should not happen for most legacy charsets such as `iso-8859-15`) the error -is silently ignored as if the error handling was `ignore`. - -Further details are available as part of the API documentation of the concrete -implementations of the functions or classes working with unicode. +However, the HTTP spec was written in a time where ASCII bytes were the +common way data was represented. To work around this for the modern +web, Werkzeug decodes and encodes incoming and outgoing data +automatically. Data sent from the browser to the web application is +decoded from UTF-8 bytes into a string. Data sent from the application +back to the browser is encoded back to UTF-8. -Request and Response Objects -============================ -As request and response objects usually are the central entities of Werkzeug -powered applications you can change the default encoding Werkzeug operates on -by subclassing these two classes. For example you can easily set the -application to utf-7 and strict error handling:: - - from werkzeug.wrappers import BaseRequest, BaseResponse +Error Handling +-------------- - class Request(BaseRequest): - charset = 'utf-7' - encoding_errors = 'strict' +Functions that do internal encoding or decoding accept an ``errors`` +keyword argument that is passed to :meth:`str.decode` and +:meth:`str.encode`. The default is ``'replace'`` so that errors are easy +to spot. It might be useful to set it to ``'strict'`` in order to catch +the error and report the bad data to the client. - class Response(BaseResponse): - charset = 'utf-7' -Keep in mind that the error handling is only customizable for all decoding -but not encoding. If Werkzeug encounters an encoding error it will raise a -:exc:`UnicodeEncodeError`. It's your responsibility to not create data that is -not present in the target charset (a non issue with all unicode encodings -such as utf-8). +Request and Response Objects +---------------------------- -.. _filesystem-encoding: +In most cases, you should stick with Werkzeug's default encoding of +UTF-8. If you have a specific reason to, you can subclass +:class:`wrappers.Request` and :class:`wrappers.Response` to change the +encoding and error handling. -The Filesystem -============== +.. code-block:: python -.. versionchanged:: 0.11 + from werkzeug.wrappers.request import Request + from werkzeug.wrappers.response import Response -Up until version 0.11, Werkzeug used Python's stdlib functionality to detect -the filesystem encoding. However, several bug reports against Werkzeug have -shown that the value of :py:func:`sys.getfilesystemencoding` cannot be -trusted under traditional UNIX systems. The usual problems come from -misconfigured systems, where ``LANG`` and similar environment variables are not -set. In such cases, Python would default to ASCII as filesystem encoding, a -very conservative default that is usually wrong and causes more problems than -it avoids. + class Latin1Request(Request): + charset = "latin1" + encoding_errors = "strict" -Therefore Werkzeug will force the filesystem encoding to ``UTF-8`` and issue a -warning whenever it detects that it is running under BSD or Linux, and -:py:func:`sys.getfilesystemencoding` is returning an ASCII encoding. + class Latin1Response(Response): + charset = "latin1" -See also :py:mod:`werkzeug.filesystem`. +The error handling can only be changed for the request. Werkzeug will +always raise errors when encoding to bytes in the response. It's your +responsibility to not create data that is not present in the target +charset. This is not an issue for UTF-8. diff --git a/docs/utils.rst b/docs/utils.rst index 89b6ef9d1..0d4e3391c 100644 --- a/docs/utils.rst +++ b/docs/utils.rst @@ -4,18 +4,8 @@ Utilities Various utility functions shipped with Werkzeug. - -HTML Helpers -============ - .. module:: werkzeug.utils -.. autoclass:: HTMLBuilder - -.. autofunction:: escape - -.. autofunction:: unescape - General Helpers =============== @@ -27,37 +17,33 @@ General Helpers .. autoclass:: header_property -.. autofunction:: parse_cookie - -.. autofunction:: dump_cookie - .. autofunction:: redirect .. autofunction:: append_slash_redirect +.. autofunction:: send_file + .. autofunction:: import_string .. autofunction:: find_modules -.. autofunction:: validate_arguments - .. autofunction:: secure_filename -.. autofunction:: bind_arguments - URL Helpers =========== Please refer to :doc:`urls`. -UserAgent Parsing -================= -.. module:: werkzeug.useragents +User Agent API +============== + +.. module:: werkzeug.user_agent .. autoclass:: UserAgent - :members: + :members: + :member-order: bysource Security Helpers @@ -65,16 +51,24 @@ Security Helpers .. module:: werkzeug.security -.. versionadded:: 0.6.1 - .. autofunction:: generate_password_hash .. autofunction:: check_password_hash -.. autofunction:: safe_str_cmp - .. autofunction:: safe_join -.. autofunction:: pbkdf2_hex -.. autofunction:: pbkdf2_bin +Logging +======= + +Werkzeug uses standard Python :mod:`logging`. The logger is named +``"werkzeug"``. + +.. code-block:: python + + import logging + logger = logging.getLogger("werkzeug") + +If the logger level is not set, it will be set to :data:`~logging.INFO` +on first use. If there is no handler for that level, a +:class:`~logging.StreamHandler` is added. diff --git a/docs/werkzeugext.py b/docs/werkzeugext.py deleted file mode 100644 index ba72e7052..000000000 --- a/docs/werkzeugext.py +++ /dev/null @@ -1,15 +0,0 @@ -# -*- coding: utf-8 -*- -""" - Werkzeug Sphinx Extensions - ~~~~~~~~~~~~~~~~~~~~~~~~~~ - - Provides some more helpers for the werkzeug docs. - - :copyright: (c) 2009 by the Werkzeug Team, see AUTHORS for more details. - :license: BSD, see LICENSE for more details. -""" -from sphinx.ext.autodoc import cut_lines - - -def setup(app): - app.connect('autodoc-process-docstring', cut_lines(3, 3, what=['module'])) diff --git a/docs/werkzeugstyle.sty b/docs/werkzeugstyle.sty deleted file mode 100644 index 5f5e53984..000000000 --- a/docs/werkzeugstyle.sty +++ /dev/null @@ -1,119 +0,0 @@ -\definecolor{TitleColor}{rgb}{0,0,0} -\definecolor{InnerLinkColor}{rgb}{0,0,0} -\definecolor{OuterLinkColor}{rgb}{1.0,0.5,0.0} - -\renewcommand{\maketitle}{% - \begin{titlepage}% - \let\footnotesize\small - \let\footnoterule\relax - \ifsphinxpdfoutput - \begingroup - % This \def is required to deal with multi-line authors; it - % changes \\ to ', ' (comma-space), making it pass muster for - % generating document info in the PDF file. - \def\\{, } - \pdfinfo{ - /Author (\@author) - /Title (\@title) - } - \endgroup - \fi - \begin{flushright}% - %\sphinxlogo% - {\center - \vspace*{3cm} - \includegraphics{logo.pdf} - \vspace{3cm} - \par - {\rm\Huge \@title \par}% - {\em\LARGE \py@release\releaseinfo \par} - {\large - \@date \par - \py@authoraddress \par - }}% - \end{flushright}%\par - \@thanks - \end{titlepage}% - \cleardoublepage% - \setcounter{footnote}{0}% - \let\thanks\relax\let\maketitle\relax - %\gdef\@thanks{}\gdef\@author{}\gdef\@title{} -} - -\fancypagestyle{normal}{ - \fancyhf{} - \fancyfoot[LE,RO]{{\thepage}} - \fancyfoot[LO]{{\nouppercase{\rightmark}}} - \fancyfoot[RE]{{\nouppercase{\leftmark}}} - \fancyhead[LE,RO]{{ \@title, \py@release}} - \renewcommand{\headrulewidth}{0.4pt} - \renewcommand{\footrulewidth}{0.4pt} -} - -\fancypagestyle{plain}{ - \fancyhf{} - \fancyfoot[LE,RO]{{\thepage}} - \renewcommand{\headrulewidth}{0pt} - \renewcommand{\footrulewidth}{0.4pt} -} - -\titleformat{\section}{\Large}% - {\py@TitleColor\thesection}{0.5em}{\py@TitleColor}{\py@NormalColor} -\titleformat{\subsection}{\large}% - {\py@TitleColor\thesubsection}{0.5em}{\py@TitleColor}{\py@NormalColor} -\titleformat{\subsubsection}{}% - {\py@TitleColor\thesubsubsection}{0.5em}{\py@TitleColor}{\py@NormalColor} -\titleformat{\paragraph}{\large}% - {\py@TitleColor}{0em}{\py@TitleColor}{\py@NormalColor} - -\ChNameVar{\raggedleft\normalsize} -\ChNumVar{\raggedleft \bfseries\Large} -\ChTitleVar{\raggedleft \rm\Huge} - -\renewcommand\thepart{\@Roman\c@part} -\renewcommand\part{% - \pagestyle{plain} - \if@noskipsec \leavevmode \fi - \cleardoublepage - \vspace*{6cm}% - \@afterindentfalse - \secdef\@part\@spart} - -\def\@part[#1]#2{% - \ifnum \c@secnumdepth >\m@ne - \refstepcounter{part}% - \addcontentsline{toc}{part}{\thepart\hspace{1em}#1}% - \else - \addcontentsline{toc}{part}{#1}% - \fi - {\parindent \z@ %\center - \interlinepenalty \@M - \normalfont - \ifnum \c@secnumdepth >\m@ne - \rm\Large \partname~\thepart - \par\nobreak - \fi - \MakeUppercase{\rm\Huge #2}% - \markboth{}{}\par}% - \nobreak - \vskip 8ex - \@afterheading} -\def\@spart#1{% - {\parindent \z@ %\center - \interlinepenalty \@M - \normalfont - \huge \bfseries #1\par}% - \nobreak - \vskip 3ex - \@afterheading} - -% use inconsolata font -\usepackage{inconsolata} - -% fix single quotes, for inconsolata. (does not work) -%%\usepackage{textcomp} -%%\begingroup -%% \catcode`'=\active -%% \g@addto@macro\@noligs{\let'\textsinglequote} -%% \endgroup -%%\endinput diff --git a/docs/wrappers.rst b/docs/wrappers.rst index aa79e7943..1f0d5aaf1 100644 --- a/docs/wrappers.rst +++ b/docs/wrappers.rst @@ -1,5 +1,3 @@ -.. _wrappers: - ========================== Request / Response Objects ========================== @@ -31,7 +29,7 @@ processing:: def application(environ, start_response): request = Request(environ) - response = Response("Hello %s!" % request.args.get('name', 'World!')) + response = Response(f"Hello {request.args.get('name', 'World!')}!") return response(environ, start_response) Because this is a very common task the :class:`~Request` object provides @@ -41,7 +39,7 @@ a helper for that. The above code can be rewritten like this:: @Request.application def application(request): - return Response("Hello %s!" % request.args.get('name', 'World!')) + return Response(f"Hello {request.args.get('name', 'World!')}!") The `application` is still a valid WSGI application that accepts the environment and `start_response` callable. @@ -75,104 +73,20 @@ For the response object the following rules apply: 4. It's possible to create copies using `copy.deepcopy`. -Base Wrappers -============= - -These objects implement a common set of operations. They are missing fancy -addon functionality like user agent parsing or etag handling. These features -are available by mixing in various mixin classes or using :class:`Request` and -:class:`Response`. - -.. autoclass:: BaseRequest - :members: - - .. attribute:: environ - - The WSGI environment that the request object uses for data retrival. - - .. attribute:: shallow - - `True` if this request object is shallow (does not modify :attr:`environ`), - `False` otherwise. - - .. automethod:: _get_file_stream - - -.. autoclass:: BaseResponse - :members: - - .. attribute:: response - - The application iterator. If constructed from a string this will be a - list, otherwise the object provided as application iterator. (The first - argument passed to :class:`BaseResponse`) - - .. attribute:: headers - - A :class:`Headers` object representing the response headers. - - .. attribute:: status_code - - The response status as integer. - - .. attribute:: direct_passthrough - - If ``direct_passthrough=True`` was passed to the response object or if - this attribute was set to `True` before using the response object as - WSGI application, the wrapped iterator is returned unchanged. This - makes it possible to pass a special `wsgi.file_wrapper` to the response - object. See :func:`wrap_file` for more details. - - .. automethod:: __call__ - - .. automethod:: _ensure_sequence - - -Mixin Classes -============= - -Werkzeug also provides helper mixins for various HTTP related functionality -such as etags, cache control, user agents etc. When subclassing you can -mix those classes in to extend the functionality of the :class:`BaseRequest` -or :class:`BaseResponse` object. Here a small example for a request object -that parses accept headers:: - - from werkzeug.wrappers import AcceptMixin, BaseRequest - - class Request(BaseRequest, AcceptMixin): - pass - -The :class:`Request` and :class:`Response` classes subclass the :class:`BaseRequest` -and :class:`BaseResponse` classes and implement all the mixins Werkzeug provides: - +Wrapper Classes +=============== .. autoclass:: Request + :members: + :inherited-members: -.. autoclass:: Response - -.. autoclass:: AcceptMixin - :members: - -.. autoclass:: AuthorizationMixin - :members: + .. automethod:: _get_file_stream -.. autoclass:: ETagRequestMixin - :members: -.. autoclass:: ETagResponseMixin - :members: - -.. autoclass:: ResponseStreamMixin - :members: - -.. autoclass:: CommonRequestDescriptorsMixin - :members: - -.. autoclass:: CommonResponseDescriptorsMixin - :members: +.. autoclass:: Response + :members: + :inherited-members: -.. autoclass:: WWWAuthenticateMixin - :members: + .. automethod:: __call__ -.. autoclass:: UserAgentMixin - :members: + .. automethod:: _ensure_sequence diff --git a/docs/wsgi.rst b/docs/wsgi.rst index 4391c89a6..a96916b52 100644 --- a/docs/wsgi.rst +++ b/docs/wsgi.rst @@ -1,17 +1,16 @@ -============ WSGI Helpers ============ .. module:: werkzeug.wsgi The following classes and functions are designed to make working with -the WSGI specification easier or operate on the WSGI layer. All the +the WSGI specification easier or operate on the WSGI layer. All the functionality from this module is available on the high-level -:ref:`Request/Response classes `. +:doc:`/wrappers`. Iterator / Stream Helpers -========================= +------------------------- These classes and functions simplify working with the WSGI application iterator and the input stream. @@ -21,7 +20,7 @@ iterator and the input stream. .. autoclass:: FileWrapper .. autoclass:: LimitedStream - :members: + :members: .. autofunction:: make_line_iter @@ -31,7 +30,7 @@ iterator and the input stream. Environ Helpers -=============== +--------------- These functions operate on the WSGI environment. They extract useful information or perform common manipulations: @@ -58,9 +57,59 @@ information or perform common manipulations: .. autofunction:: host_is_trusted + Convenience Helpers -=================== +------------------- .. autofunction:: responder .. autofunction:: werkzeug.testapp.test_app + + +Bytes, Strings, and Encodings +----------------------------- + +The values in HTTP requests come in as bytes representing (or encoded +to) ASCII. The WSGI specification (:pep:`3333`) decided to always use +the ``str`` type to represent values. To accomplish this, the raw bytes +are decoded using the ISO-8859-1 charset to produce a string. + +Strings in the WSGI environment are restricted to ISO-8859-1 code +points. If a string read from the environment might contain characters +outside that charset, it must first be decoded to bytes as ISO-8859-1, +then encoded to a string using the proper charset (typically UTF-8). The +reverse is done when writing to the environ. This is known as the "WSGI +encoding dance". + +Werkzeug provides functions to deal with this automatically so that you +don't need to be aware of the inner workings. Use the functions on this +page as well as :func:`~werkzeug.datastructures.EnvironHeaders` to read +data out of the WSGI environment. + +Applications should avoid manually creating or modifying a WSGI +environment unless they take care of the proper encoding or decoding +step. All high level interfaces in Werkzeug will apply the encoding and +decoding as necessary. + + +Raw Request URI and Path Encoding +--------------------------------- + +The ``PATH_INFO`` in the environ is the path value after +percent-decoding. For example, the raw path ``/hello%2fworld`` would +show up from the WSGI server to Werkzeug as ``/hello/world``. This loses +the information that the slash was a raw character as opposed to a path +separator. + +The WSGI specification (:pep:`3333`) does not provide a way to get the +original value, so it is impossible to route some types of data in the +path. The most compatible way to work around this is to send problematic +data in the query string instead of the path. + +However, many WSGI servers add a non-standard environ key with the raw +path. To match this behavior, Werkzeug's test client and development +server will add the raw value to both the ``REQUEST_URI`` and +``RAW_URI`` keys. If you want to route based on this value, you can use +middleware to replace ``PATH_INFO`` in the environ before it reaches the +application. However, keep in mind that these keys are non-standard and +not guaranteed to be present. diff --git a/examples/README b/examples/README.rst similarity index 88% rename from examples/README rename to examples/README.rst index e99dd0fdd..31b50ef5e 100644 --- a/examples/README +++ b/examples/README.rst @@ -6,8 +6,9 @@ This directory contains various example applications and example code of Werkzeug powered applications. Beside the proof of concept applications and code snippets in the partial -folder they all have external depencencies for template engines or database -adapters (SQLAlchemy only so far). +folder they all have external dependencies for template engines or database +adapters (SQLAlchemy only so far). Also, every application has click as +external dependency, used to create the command line interface. Full Example Applications @@ -18,9 +19,11 @@ find in real life :-) `simplewiki` + A simple Wiki implementation. Requirements: + - SQLAlchemy - Creoleparser >= 0.7 - genshi @@ -43,11 +46,13 @@ find in real life :-) no such variable is provided "sqlite:////tmp/simplewiki.db" is assumed. `plnt` + A planet called plnt, pronounce plant. Requirements: + - SQLAlchemy - - Jinja + - Jinja2 - feedparser You can obtain all packages in the Cheeseshop via easy_install. @@ -67,9 +72,11 @@ find in real life :-) can add more in a python shell by playing with the `Blog` model. `shorty` + A tinyurl clone for the Werkzeug tutorial. Requirements: + - SQLAlchemy - Jinja2 @@ -87,15 +94,18 @@ find in real life :-) tutorial. `couchy` + Like shorty, but implemented using CouchDB. Requirements : + - werkzeug : http://werkzeug.pocoo.org - jinja : http://jinja.pocoo.org - - couchdb 0.72 & above : http://www.couchdb.org + - couchdb 0.72 & above : https://couchdb.apache.org/ `cupoftee` - A `Teeworlds `_ server browser. This application + + A `Teeworlds `_ server browser. This application works best in a non forking environment and won't work for CGI. Usage:: diff --git a/examples/contrib/README b/examples/contrib/README deleted file mode 100644 index 387df5bd1..000000000 --- a/examples/contrib/README +++ /dev/null @@ -1 +0,0 @@ -This folder includes example applications for werkzeug.contrib diff --git a/examples/contrib/securecookie.py b/examples/contrib/securecookie.py deleted file mode 100644 index da7b4f119..000000000 --- a/examples/contrib/securecookie.py +++ /dev/null @@ -1,50 +0,0 @@ -# -*- coding: utf-8 -*- -""" - Secure Cookie Example - ~~~~~~~~~~~~~~~~~~~~~ - - Stores session on the client. - - :copyright: (c) 2009 by the Werkzeug Team, see AUTHORS for more details. - :license: BSD. -""" -from time import asctime -from werkzeug.serving import run_simple -from werkzeug.wrappers import BaseRequest, BaseResponse -from werkzeug.contrib.securecookie import SecureCookie - -SECRET_KEY = 'V\x8a$m\xda\xe9\xc3\x0f|f\x88\xbccj>\x8bI^3+' - - -class Request(BaseRequest): - - def __init__(self, environ): - BaseRequest.__init__(self, environ) - self.session = SecureCookie.load_cookie(self, secret_key=SECRET_KEY) - - -def index(request): - return 'Set the Time or Get the time' - - -def get_time(request): - return 'Time: %s' % request.session.get('time', 'not set') - - -def set_time(request): - request.session['time'] = time = asctime() - return 'Time set to %s' % time - - -def application(environ, start_response): - request = Request(environ) - response = BaseResponse({ - 'get': get_time, - 'set': set_time - }.get(request.path.strip('/'), index)(request), mimetype='text/html') - request.session.save_cookie(response) - return response(environ, start_response) - - -if __name__ == '__main__': - run_simple('localhost', 5000, application) diff --git a/examples/contrib/sessions.py b/examples/contrib/sessions.py deleted file mode 100644 index ecf464a24..000000000 --- a/examples/contrib/sessions.py +++ /dev/null @@ -1,43 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -from werkzeug.serving import run_simple -from werkzeug.contrib.sessions import SessionStore, SessionMiddleware - - -class MemorySessionStore(SessionStore): - - def __init__(self, session_class=None): - SessionStore.__init__(self, session_class=None) - self.sessions = {} - - def save(self, session): - self.sessions[session.sid] = session - - def delete(self, session): - self.sessions.pop(session.id, None) - - def get(self, sid): - if not self.is_valid_key(sid) or sid not in self.sessions: - return self.new() - return self.session_class(self.sessions[sid], sid, False) - - -def application(environ, start_response): - session = environ['werkzeug.session'] - session['visit_count'] = session.get('visit_count', 0) + 1 - - start_response('200 OK', [('Content-Type', 'text/html')]) - return [''' - - Session Example -

Session Example

-

You visited this page %d times.

- ''' % session['visit_count']] - - -def make_app(): - return SessionMiddleware(application, MemorySessionStore()) - - -if __name__ == '__main__': - run_simple('localhost', 5000, make_app()) diff --git a/examples/cookieauth.py b/examples/cookieauth.py deleted file mode 100644 index 13f32c9e4..000000000 --- a/examples/cookieauth.py +++ /dev/null @@ -1,108 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -""" - Cookie Based Auth - ~~~~~~~~~~~~~~~~~ - - This is a very simple application that uses a secure cookie to do the - user authentification. - - :copyright: Copyright 2009 by the Werkzeug Team, see AUTHORS for more details. - :license: BSD, see LICENSE for more details. -""" -from werkzeug.serving import run_simple -from werkzeug.utils import cached_property, escape, redirect -from werkzeug.wrappers import Request, Response -from werkzeug.contrib.securecookie import SecureCookie - - -# don't use this key but a different one; you could just use -# os.unrandom(20) to get something random. Changing this key -# invalidates all sessions at once. -SECRET_KEY = '\xfa\xdd\xb8z\xae\xe0}4\x8b\xea' - -# the cookie name for the session -COOKIE_NAME = 'session' - -# the users that may access -USERS = { - 'admin': 'default', - 'user1': 'default' -} - - -class AppRequest(Request): - """A request with a secure cookie session.""" - - def logout(self): - """Log the user out.""" - self.session.pop('username', None) - - def login(self, username): - """Log the user in.""" - self.session['username'] = username - - @property - def logged_in(self): - """Is the user logged in?""" - return self.user is not None - - @property - def user(self): - """The user that is logged in.""" - return self.session.get('username') - - @cached_property - def session(self): - data = self.cookies.get(COOKIE_NAME) - if not data: - return SecureCookie(secret_key=SECRET_KEY) - return SecureCookie.unserialize(data, SECRET_KEY) - - -def login_form(request): - error = '' - if request.method == 'POST': - username = request.form.get('username') - password = request.form.get('password') - if password and USERS.get(username) == password: - request.login(username) - return redirect('') - error = '

Invalid credentials' - return Response(''' - Login

Login

-

Not logged in. - %s -

-

- - - - -

''' % error, mimetype='text/html') - - -def index(request): - return Response(''' - Logged in -

Logged in

-

Logged in as %s -

Logout - ''' % escape(request.user), mimetype='text/html') - - -@AppRequest.application -def application(request): - if request.args.get('do') == 'logout': - request.logout() - response = redirect('.') - elif request.logged_in: - response = index(request) - else: - response = login_form(request) - request.session.save_cookie(response) - return response - - -if __name__ == '__main__': - run_simple('localhost', 4000, application) diff --git a/examples/coolmagic/__init__.py b/examples/coolmagic/__init__.py index f54417572..2d6c9047e 100644 --- a/examples/coolmagic/__init__.py +++ b/examples/coolmagic/__init__.py @@ -1,11 +1 @@ -# -*- coding: utf-8 -*- -""" - coolmagic - ~~~~~~~~~ - - Package description goes here. - - :copyright: (c) 2009 by the Werkzeug Team, see AUTHORS for more details. - :license: BSD, see LICENSE for more details. -""" -from coolmagic.application import make_app +from .application import make_app diff --git a/examples/coolmagic/application.py b/examples/coolmagic/application.py index 83c8e04d2..819616b00 100644 --- a/examples/coolmagic/application.py +++ b/examples/coolmagic/application.py @@ -1,24 +1,24 @@ -# -*- coding: utf-8 -*- -""" - coolmagic.application - ~~~~~~~~~~~~~~~~~~~~~ +"""This module provides the WSGI application. - This module provides the WSGI application. +The WSGI middlewares are applied in the `make_app` factory function that +automatically wraps the application within the require middlewares. Per +default only the `SharedDataMiddleware` is applied. +""" +from os import listdir +from os import path - The WSGI middlewares are applied in the `make_app` factory function - that automatically wraps the application within the require - middlewares. Per default only the `SharedDataMiddleware` is applied. +from werkzeug.exceptions import HTTPException +from werkzeug.exceptions import NotFound +from werkzeug.middleware.shared_data import SharedDataMiddleware +from werkzeug.routing import Map +from werkzeug.routing import RequestRedirect +from werkzeug.routing import Rule - :copyright: (c) 2009 by the Werkzeug Team, see AUTHORS for more details. - :license: BSD, see LICENSE for more details. -""" -from os import path, listdir -from coolmagic.utils import Request, local_manager, redirect -from werkzeug.routing import Map, Rule, RequestRedirect -from werkzeug.exceptions import HTTPException, NotFound +from .utils import local_manager +from .utils import Request -class CoolMagicApplication(object): +class CoolMagicApplication: """ The application class. It's passed a directory with configuration values. """ @@ -26,20 +26,20 @@ class CoolMagicApplication(object): def __init__(self, config): self.config = config - for fn in listdir(path.join(path.dirname(__file__), 'views')): - if fn.endswith('.py') and fn != '__init__.py': - __import__('coolmagic.views.' + fn[:-3]) + for fn in listdir(path.join(path.dirname(__file__), "views")): + if fn.endswith(".py") and fn != "__init__.py": + __import__(f"coolmagic.views.{fn[:-3]}") from coolmagic.utils import exported_views + rules = [ # url for shared data. this will always be unmatched # because either the middleware or the webserver # handles that request first. - Rule('/public/', - endpoint='shared_data') + Rule("/public/", endpoint="shared_data") ] self.views = {} - for endpoint, (func, rule, extra) in exported_views.iteritems(): + for endpoint, (func, rule, extra) in exported_views.items(): if rule is not None: rules.append(Rule(rule, endpoint=endpoint, **extra)) self.views[endpoint] = func @@ -51,9 +51,9 @@ def __call__(self, environ, start_response): try: endpoint, args = urls.match(req.path) resp = self.views[endpoint](**args) - except NotFound, e: - resp = self.views['static.not_found']() - except (HTTPException, RequestRedirect), e: + except NotFound: + resp = self.views["static.not_found"]() + except (HTTPException, RequestRedirect) as e: resp = e return resp(environ, start_response) @@ -67,10 +67,9 @@ def make_app(config=None): app = CoolMagicApplication(config) # static stuff - from werkzeug.utils import SharedDataMiddleware - app = SharedDataMiddleware(app, { - '/public': path.join(path.dirname(__file__), 'public') - }) + app = SharedDataMiddleware( + app, {"/public": path.join(path.dirname(__file__), "public")} + ) # clean up locals app = local_manager.make_middleware(app) diff --git a/examples/coolmagic/helpers.py b/examples/coolmagic/helpers.py index 7c2ac210d..9074a2414 100644 --- a/examples/coolmagic/helpers.py +++ b/examples/coolmagic/helpers.py @@ -1,17 +1,4 @@ -# -*- coding: utf-8 -*- -""" - coolmagic.helpers - ~~~~~~~~~~~~~~~~~ - - The star-import module for all views. - - :copyright: (c) 2009 by the Werkzeug Team, see AUTHORS for more details. - :license: BSD, see LICENSE for more details. -""" -from coolmagic.utils import Response, TemplateResponse, ThreadedRequest, \ - export, url_for, redirect -from werkzeug.utils import escape - +from .utils import ThreadedRequest #: a thread local proxy request object request = ThreadedRequest() diff --git a/examples/coolmagic/templates/layout.html b/examples/coolmagic/templates/layout.html index 82597816d..a87c9544d 100644 --- a/examples/coolmagic/templates/layout.html +++ b/examples/coolmagic/templates/layout.html @@ -1,5 +1,4 @@ - + {{ page_title }} — Cool Magic! diff --git a/examples/coolmagic/utils.py b/examples/coolmagic/utils.py index 473352d70..85ad3f786 100644 --- a/examples/coolmagic/utils.py +++ b/examples/coolmagic/utils.py @@ -1,28 +1,22 @@ -# -*- coding: utf-8 -*- +"""Subclasses of the base request and response objects provided by +werkzeug. The subclasses know about their charset and implement some +additional functionality like the ability to link to view functions. """ - coolmagic.utils - ~~~~~~~~~~~~~~~ +from os.path import dirname +from os.path import join - This module contains the subclasses of the base request and response - objects provided by werkzeug. The subclasses know about their charset - and implement some additional functionallity like the ability to link - to view functions. - - :copyright: (c) 2009 by the Werkzeug Team, see AUTHORS for more details. - :license: BSD, see LICENSE for more details. -""" -from os.path import dirname, join -from jinja import Environment, FileSystemLoader -from werkzeug.local import Local, LocalManager -from werkzeug.utils import redirect -from werkzeug.wrappers import BaseRequest, BaseResponse +from jinja2 import Environment +from jinja2 import FileSystemLoader +from werkzeug.local import Local +from werkzeug.local import LocalManager +from werkzeug.wrappers import Request as BaseRequest +from werkzeug.wrappers import Response as BaseResponse local = Local() local_manager = LocalManager([local]) template_env = Environment( - loader=FileSystemLoader(join(dirname(__file__), 'templates'), - use_memcache=False) + loader=FileSystemLoader(join(dirname(__file__), "templates")) ) exported_views = {} @@ -32,19 +26,23 @@ def export(string, template=None, **extra): Decorator for registering view functions and adding templates to it. """ + def wrapped(f): - endpoint = (f.__module__ + '.' + f.__name__)[16:] + endpoint = f"{f.__module__}.{f.__name__}"[16:] if template is not None: old_f = f + def f(**kwargs): rv = old_f(**kwargs) if not isinstance(rv, Response): rv = TemplateResponse(template, **(rv or {})) return rv + f.__name__ = old_f.__name__ f.__doc__ = old_f.__doc__ exported_views[endpoint] = (f, string, extra) return f + return wrapped @@ -60,24 +58,24 @@ class Request(BaseRequest): The concrete request object used in the WSGI application. It has some helper functions that can be used to build URLs. """ - charset = 'utf-8' + + charset = "utf-8" def __init__(self, environ, url_adapter): - BaseRequest.__init__(self, environ) + super().__init__(environ) self.url_adapter = url_adapter local.request = self -class ThreadedRequest(object): +class ThreadedRequest: """ - A pseudo request object that always poins to the current + A pseudo request object that always points to the current context active request. """ def __getattr__(self, name): - if name == '__members__': - return [x for x in dir(local.request) if not - x.startswith('_')] + if name == "__members__": + return [x for x in dir(local.request) if not x.startswith("_")] return getattr(local.request, name) def __setattr__(self, name, value): @@ -88,8 +86,9 @@ class Response(BaseResponse): """ The concrete response object for the WSGI application. """ - charset = 'utf-8' - default_mimetype = 'text/html' + + charset = "utf-8" + default_mimetype = "text/html" class TemplateResponse(Response): @@ -99,9 +98,7 @@ class TemplateResponse(Response): def __init__(self, template_name, **values): from coolmagic import helpers - values.update( - request=local.request, - h=helpers - ) + + values.update(request=local.request, h=helpers) template = template_env.get_template(template_name) Response.__init__(self, template.render(values)) diff --git a/examples/coolmagic/views/__init__.py b/examples/coolmagic/views/__init__.py index c0e988e90..e69de29bb 100644 --- a/examples/coolmagic/views/__init__.py +++ b/examples/coolmagic/views/__init__.py @@ -1,10 +0,0 @@ -# -*- coding: utf-8 -*- -""" - coolmagic.views - ~~~~~~~~~~~~~~~ - - This module collects and assambles the urls. - - :copyright: (c) 2009 by the Werkzeug Team, see AUTHORS for more details. - :license: BSD, see LICENSE for more details. -""" diff --git a/examples/coolmagic/views/static.py b/examples/coolmagic/views/static.py index 34b0a2f6b..c61e01427 100644 --- a/examples/coolmagic/views/static.py +++ b/examples/coolmagic/views/static.py @@ -1,33 +1,22 @@ -# -*- coding: utf-8 -*- -""" - coolmagic.views.static - ~~~~~~~~~~~~~~~~~~~~~~ +from coolmagic.utils import export - Some static views. - :copyright: (c) 2009 by the Werkzeug Team, see AUTHORS for more details. - :license: BSD, see LICENSE for more details. -""" -from coolmagic.helpers import * - - -@export('/', template='static/index.html') +@export("/", template="static/index.html") def index(): pass -@export('/about', template='static/about.html') +@export("/about", template="static/about.html") def about(): pass -@export('/broken') +@export("/broken") def broken(): - foo = request.args.get('foo', 42) - raise RuntimeError('that\'s really broken') + raise RuntimeError("that's really broken") -@export(None, template='static/not_found.html') +@export(None, template="static/not_found.html") def not_found(): """ This function is always executed if an url does not diff --git a/examples/couchy/README b/examples/couchy/README index 1bfc3a977..249604480 100644 --- a/examples/couchy/README +++ b/examples/couchy/README @@ -3,6 +3,5 @@ couchy README Requirements : - werkzeug : http://werkzeug.pocoo.org - jinja : http://jinja.pocoo.org -- couchdb 0.72 & above : http://www.couchdb.org -- couchdb-python 0.3 & above : http://code.google.com/p/couchdb-python - +- couchdb 0.72 & above : https://couchdb.apache.org/ +- couchdb-python 0.3 & above : https://github.com/djc/couchdb-python diff --git a/examples/couchy/application.py b/examples/couchy/application.py index cc5d0c604..04ef6232c 100644 --- a/examples/couchy/application.py +++ b/examples/couchy/application.py @@ -1,27 +1,28 @@ from couchdb.client import Server -from couchy.utils import STATIC_PATH, local, local_manager, \ - url_map +from werkzeug.exceptions import HTTPException +from werkzeug.exceptions import NotFound +from werkzeug.middleware.shared_data import SharedDataMiddleware from werkzeug.wrappers import Request -from werkzeug.wsgi import ClosingIterator, SharedDataMiddleware -from werkzeug.exceptions import HTTPException, NotFound -from couchy import views -from couchy.models import URL -import couchy.models +from werkzeug.wsgi import ClosingIterator +from . import views +from .models import URL +from .utils import local +from .utils import local_manager +from .utils import STATIC_PATH +from .utils import url_map -class Couchy(object): +class Couchy: def __init__(self, db_uri): local.application = self server = Server(db_uri) try: - db = server.create('urls') - except: - db = server['urls'] - self.dispatch = SharedDataMiddleware(self.dispatch, { - '/static': STATIC_PATH - }) + db = server.create("urls") + except Exception: + db = server["urls"] + self.dispatch = SharedDataMiddleware(self.dispatch, {"/static": STATIC_PATH}) URL.db = db @@ -33,13 +34,14 @@ def dispatch(self, environ, start_response): endpoint, values = adapter.match() handler = getattr(views, endpoint) response = handler(request, **values) - except NotFound, e: + except NotFound: response = views.not_found(request) response.status_code = 404 - except HTTPException, e: + except HTTPException as e: response = e - return ClosingIterator(response(environ, start_response), - [local_manager.cleanup]) + return ClosingIterator( + response(environ, start_response), [local_manager.cleanup] + ) def __call__(self, environ, start_response): return self.dispatch(environ, start_response) diff --git a/examples/couchy/models.py b/examples/couchy/models.py index dd7aea385..57bb6395f 100644 --- a/examples/couchy/models.py +++ b/examples/couchy/models.py @@ -1,6 +1,12 @@ from datetime import datetime -from couchdb.schema import Document, TextField, BooleanField, DateTimeField -from couchy.utils import url_for, get_random_uid + +from couchdb.mapping import BooleanField +from couchdb.mapping import DateTimeField +from couchdb.mapping import Document +from couchdb.mapping import TextField + +from .utils import get_random_uid +from .utils import url_for class URL(Document): @@ -11,33 +17,34 @@ class URL(Document): db = None @classmethod - def load(self, id): - return super(URL, self).load(URL.db, id) + def load(cls, id): + return super().load(URL.db, id) @classmethod - def query(self, code): + def query(cls, code): return URL.db.query(code) def store(self): - if getattr(self._data, 'id', None) is None: + if getattr(self._data, "id", None) is None: new_id = self.shorty_id if self.shorty_id else None while 1: id = new_id if new_id else get_random_uid() - docid = None try: - docid = URL.db.resource.put(content=self._data, path='/%s/' % str(id))['id'] - except: + docid = URL.db.resource.put(content=self._data, path=f"/{id}/")[ + "id" + ] + except Exception: continue if docid: break self._data = URL.db.get(docid) else: - super(URL, self).store(URL.db) + super().store(URL.db) return self @property def short_url(self): - return url_for('link', uid=self.id, _external=True) + return url_for("link", uid=self.id, _external=True) def __repr__(self): - return '' % self.id + return f"" diff --git a/examples/couchy/templates/layout.html b/examples/couchy/templates/layout.html index 8715740e3..f4968065a 100644 --- a/examples/couchy/templates/layout.html +++ b/examples/couchy/templates/layout.html @@ -1,5 +1,4 @@ - + Shorty diff --git a/examples/couchy/utils.py b/examples/couchy/utils.py index bfcb885a4..03d168139 100644 --- a/examples/couchy/utils.py +++ b/examples/couchy/utils.py @@ -1,49 +1,62 @@ from os import path -from urlparse import urlparse -from random import sample, randrange -from jinja import Environment, FileSystemLoader -from werkzeug.local import Local, LocalManager +from random import randrange +from random import sample + +from jinja2 import Environment +from jinja2 import FileSystemLoader +from werkzeug.local import Local +from werkzeug.local import LocalManager +from werkzeug.routing import Map +from werkzeug.routing import Rule +from werkzeug.urls import url_parse from werkzeug.utils import cached_property from werkzeug.wrappers import Response -from werkzeug.routing import Map, Rule -TEMPLATE_PATH = path.join(path.dirname(__file__), 'templates') -STATIC_PATH = path.join(path.dirname(__file__), 'static') -ALLOWED_SCHEMES = frozenset(['http', 'https', 'ftp', 'ftps']) -URL_CHARS = 'abcdefghijkmpqrstuvwxyzABCDEFGHIJKLMNPQRST23456789' +TEMPLATE_PATH = path.join(path.dirname(__file__), "templates") +STATIC_PATH = path.join(path.dirname(__file__), "static") +ALLOWED_SCHEMES = frozenset(["http", "https", "ftp", "ftps"]) +URL_CHARS = "abcdefghijkmpqrstuvwxyzABCDEFGHIJKLMNPQRST23456789" local = Local() local_manager = LocalManager([local]) -application = local('application') +application = local("application") -url_map = Map([Rule('/static/', endpoint='static', build_only=True)]) +url_map = Map([Rule("/static/", endpoint="static", build_only=True)]) jinja_env = Environment(loader=FileSystemLoader(TEMPLATE_PATH)) def expose(rule, **kw): def decorate(f): - kw['endpoint'] = f.__name__ + kw["endpoint"] = f.__name__ url_map.add(Rule(rule, **kw)) return f + return decorate + def url_for(endpoint, _external=False, **values): return local.url_adapter.build(endpoint, values, force_external=_external) -jinja_env.globals['url_for'] = url_for + + +jinja_env.globals["url_for"] = url_for + def render_template(template, **context): - return Response(jinja_env.get_template(template).render(**context), - mimetype='text/html') + return Response( + jinja_env.get_template(template).render(**context), mimetype="text/html" + ) + def validate_url(url): - return urlparse(url)[0] in ALLOWED_SCHEMES + return url_parse(url)[0] in ALLOWED_SCHEMES + def get_random_uid(): - return ''.join(sample(URL_CHARS, randrange(3, 9))) + return "".join(sample(URL_CHARS, randrange(3, 9))) -class Pagination(object): +class Pagination: def __init__(self, results, per_page, page, endpoint): self.results = results self.per_page = per_page @@ -56,10 +69,33 @@ def count(self): @cached_property def entries(self): - return self.results[((self.page - 1) * self.per_page):(((self.page - 1) * self.per_page)+self.per_page)] + return self.results[ + ((self.page - 1) * self.per_page) : ( + ((self.page - 1) * self.per_page) + self.per_page + ) + ] + + @property + def has_previous(self): + """Return True if there are pages before the current one.""" + return self.page > 1 + + @property + def has_next(self): + """Return True if there are pages after the current one.""" + return self.page < self.pages + + @property + def previous(self): + """Return the URL for the previous page.""" + return url_for(self.endpoint, page=self.page - 1) + + @property + def next(self): + """Return the URL for the next page.""" + return url_for(self.endpoint, page=self.page + 1) - has_previous = property(lambda x: x.page > 1) - has_next = property(lambda x: x.page < x.pages) - previous = property(lambda x: url_for(x.endpoint, page=x.page - 1)) - next = property(lambda x: url_for(x.endpoint, page=x.page + 1)) - pages = property(lambda x: max(0, x.count - 1) // x.per_page + 1) + @property + def pages(self): + """Return the number of pages.""" + return max(0, self.count - 1) // self.per_page + 1 diff --git a/examples/couchy/views.py b/examples/couchy/views.py index 39c8ea296..c1547e7d2 100644 --- a/examples/couchy/views.py +++ b/examples/couchy/views.py @@ -1,61 +1,73 @@ -from werkzeug.utils import redirect from werkzeug.exceptions import NotFound -from couchy.utils import render_template, expose, \ - validate_url, url_for, Pagination -from couchy.models import URL +from werkzeug.utils import redirect +from .models import URL +from .utils import expose +from .utils import Pagination +from .utils import render_template +from .utils import url_for +from .utils import validate_url -@expose('/') + +@expose("/") def new(request): - error = url = '' - if request.method == 'POST': - url = request.form.get('url') - alias = request.form.get('alias') + error = url = "" + if request.method == "POST": + url = request.form.get("url") + alias = request.form.get("alias") if not validate_url(url): error = "I'm sorry but you cannot shorten this URL." elif alias: if len(alias) > 140: - error = 'Your alias is too long' - elif '/' in alias: - error = 'Your alias might not include a slash' + error = "Your alias is too long" + elif "/" in alias: + error = "Your alias might not include a slash" elif URL.load(alias): - error = 'The alias you have requested exists already' + error = "The alias you have requested exists already" if not error: - url = URL(target=url, public='private' not in request.form, shorty_id=alias if alias else None) + url = URL( + target=url, + public="private" not in request.form, + shorty_id=alias if alias else None, + ) url.store() uid = url.id - return redirect(url_for('display', uid=uid)) - return render_template('new.html', error=error, url=url) + return redirect(url_for("display", uid=uid)) + return render_template("new.html", error=error, url=url) + -@expose('/display/') +@expose("/display/") def display(request, uid): url = URL.load(uid) if not url: raise NotFound() - return render_template('display.html', url=url) + return render_template("display.html", url=url) -@expose('/u/') + +@expose("/u/") def link(request, uid): url = URL.load(uid) if not url: raise NotFound() return redirect(url.target, 301) -@expose('/list/', defaults={'page': 1}) -@expose('/list/') + +@expose("/list/", defaults={"page": 1}) +@expose("/list/") def list(request, page): def wrap(doc): data = doc.value - data['_id'] = doc.id + data["_id"] = doc.id return URL.wrap(data) - code = '''function(doc) { if (doc.public){ map([doc._id], doc); }}''' + code = """function(doc) { if (doc.public){ map([doc._id], doc); }}""" docResults = URL.query(code) results = [wrap(doc) for doc in docResults] - pagination = Pagination(results, 1, page, 'list') + pagination = Pagination(results, 1, page, "list") if pagination.page > 1 and not pagination.entries: raise NotFound() - return render_template('list.html', pagination=pagination) + return render_template("list.html", pagination=pagination) + def not_found(request): - return render_template('not_found.html') + return render_template("not_found.html") diff --git a/examples/cupoftee/__init__.py b/examples/cupoftee/__init__.py index 26cc9724b..d009f775d 100644 --- a/examples/cupoftee/__init__.py +++ b/examples/cupoftee/__init__.py @@ -1,11 +1,2 @@ -# -*- coding: utf-8 -*- -""" - cupoftee - ~~~~~~~~ - - Werkzeug powered Teeworlds Server Browser. - - :copyright: (c) 2009 by the Werkzeug Team, see AUTHORS for more details. - :license: BSD, see LICENSE for more details. -""" -from cupoftee.application import make_app +"""Werkzeug powered Teeworlds Server Browser.""" +from .application import make_app diff --git a/examples/cupoftee/application.py b/examples/cupoftee/application.py index 526cf188a..7104cfd40 100644 --- a/examples/cupoftee/application.py +++ b/examples/cupoftee/application.py @@ -1,50 +1,58 @@ -# -*- coding: utf-8 -*- -""" - cupoftee.application - ~~~~~~~~~~~~~~~~~~~~ - - The WSGI appliction for the cup of tee browser. - - :copyright: (c) 2009 by the Werkzeug Team, see AUTHORS for more details. - :license: BSD, see LICENSE for more details. -""" import time from os import path from threading import Thread -from cupoftee.db import Database -from cupoftee.network import ServerBrowser -from werkzeug.templates import Template -from werkzeug.wrappers import Request, Response -from werkzeug.wsgi import SharedDataMiddleware -from werkzeug.exceptions import HTTPException, NotFound -from werkzeug.routing import Map, Rule +from jinja2 import Environment +from jinja2 import PackageLoader +from werkzeug.exceptions import HTTPException +from werkzeug.exceptions import NotFound +from werkzeug.middleware.shared_data import SharedDataMiddleware +from werkzeug.routing import Map +from werkzeug.routing import Rule +from werkzeug.wrappers import Request +from werkzeug.wrappers import Response + +from .db import Database +from .network import ServerBrowser -templates = path.join(path.dirname(__file__), 'templates') + +templates = path.join(path.dirname(__file__), "templates") pages = {} -url_map = Map([Rule('/shared/', endpoint='shared')]) +url_map = Map([Rule("/shared/", endpoint="shared")]) -def make_app(database, interval=60): - return SharedDataMiddleware(Cup(database), { - '/shared': path.join(path.dirname(__file__), 'shared') - }) +def make_app(database, interval=120): + return SharedDataMiddleware( + Cup(database, interval), + {"/shared": path.join(path.dirname(__file__), "shared")}, + ) class PageMeta(type): - def __init__(cls, name, bases, d): type.__init__(cls, name, bases, d) - if d.get('url_rule') is not None: + if d.get("url_rule") is not None: pages[cls.identifier] = cls - url_map.add(Rule(cls.url_rule, endpoint=cls.identifier, - **cls.url_arguments)) + url_map.add( + Rule(cls.url_rule, endpoint=cls.identifier, **cls.url_arguments) + ) + + @property + def identifier(cls): + return cls.__name__.lower() - identifier = property(lambda x: x.__name__.lower()) +def _with_metaclass(meta, *bases): + """Create a base class with a metaclass.""" -class Page(object): - __metaclass__ = PageMeta + class metaclass(type): + def __new__(metacls, name, this_bases, d): + return meta(name, bases, d) + + return type.__new__(metaclass, "temporary_class", (), {}) + + +class Page(_with_metaclass(PageMeta, object)): url_arguments = {} def __init__(self, cup, request, url_adapter): @@ -60,32 +68,28 @@ def process(self): def render_template(self, template=None): if template is None: - template = self.__class__.identifier + '.html' + template = f"{type(self).identifier}.html" context = dict(self.__dict__) context.update(url_for=self.url_for, self=self) - body_tmpl = Template.from_file(path.join(templates, template)) - layout_tmpl = Template.from_file(path.join(templates, 'layout.html')) - context['body'] = body_tmpl.render(context) - return layout_tmpl.render(context) + return self.cup.render_template(template, context) def get_response(self): - return Response(self.render_template(), mimetype='text/html') + return Response(self.render_template(), mimetype="text/html") -class Cup(object): - +class Cup: def __init__(self, database, interval=120): + self.jinja_env = Environment(loader=PackageLoader("cupoftee"), autoescape=True) self.interval = interval self.db = Database(database) - self.master = ServerBrowser(self) - self.updater = Thread(None, self.update_master) - self.updater.setDaemon(True) + self.server_browser = ServerBrowser(self) + self.updater = Thread(None, self.update_server_browser) + self.updater.daemon = True self.updater.start() - def update_master(self): - wait = self.interval + def update_server_browser(self): while 1: - if self.master.sync(): + if self.server_browser.sync(): wait = self.interval else: wait = self.interval // 2 @@ -97,10 +101,10 @@ def dispatch_request(self, request): endpoint, values = url_adapter.match() page = pages[endpoint](self, request, url_adapter) response = page.process(**values) - except NotFound, e: + except NotFound: page = MissingPage(self, request, url_adapter) response = page.process() - except HTTPException, e: + except HTTPException as e: return e return response or page.get_response() @@ -108,5 +112,9 @@ def __call__(self, environ, start_response): request = Request(environ) return self.dispatch_request(request)(environ, start_response) + def render_template(self, name, **context): + template = self.jinja_env.get_template(name) + return template.render(context) + from cupoftee.pages import MissingPage diff --git a/examples/cupoftee/db.py b/examples/cupoftee/db.py index 6644e0b43..97c2c51cb 100644 --- a/examples/cupoftee/db.py +++ b/examples/cupoftee/db.py @@ -1,25 +1,16 @@ -# -*- coding: utf-8 -*- +"""A simple object database. As long as the server is not running in +multiprocess mode that's good enough. """ - cupoftee.db - ~~~~~~~~~~~ - - A simple object database. As long as the server is not running in - multiprocess mode that's good enough. - - :copyright: (c) 2009 by the Werkzeug Team, see AUTHORS for more details. - :license: BSD, see LICENSE for more details. -""" -from __future__ import with_statement -import gdbm +import dbm +from pickle import dumps +from pickle import loads from threading import Lock -from pickle import dumps, loads - -class Database(object): +class Database: def __init__(self, filename): self.filename = filename - self._fs = gdbm.open(filename, 'cf') + self._fs = dbm.open(filename, "cf") self._local = {} self._lock = Lock() @@ -37,10 +28,10 @@ def _load_key(self, key): def __setitem__(self, key, value): self._local[key] = value - def __delitem__(self, key, value): + def __delitem__(self, key): with self._lock: self._local.pop(key, None) - if self._fs.has_key(key): + if key in self._fs: del self._fs[key] def __del__(self): @@ -64,7 +55,7 @@ def setdefault(self, key, factory): def sync(self): with self._lock: - for key, value in self._local.iteritems(): + for key, value in self._local.items(): self._fs[key] = dumps(value, 2) self._fs.sync() @@ -72,5 +63,5 @@ def close(self): try: self.sync() self._fs.close() - except: + except Exception: pass diff --git a/examples/cupoftee/network.py b/examples/cupoftee/network.py index 668f98c23..5cbb9c205 100644 --- a/examples/cupoftee/network.py +++ b/examples/cupoftee/network.py @@ -1,68 +1,60 @@ -# -*- coding: utf-8 -*- -""" - cupyoftee.network - ~~~~~~~~~~~~~~~~~ - - Query the servers for information. - - :copyright: (c) 2009 by the Werkzeug Team, see AUTHORS for more details. - :license: BSD, see LICENSE for more details. -""" -import time +"""Query the servers for information.""" import socket -from math import log from datetime import datetime -from cupoftee.utils import unicodecmp +from math import log + +from .utils import unicodecmp class ServerError(Exception): pass -class Syncable(object): +class Syncable: last_sync = None def sync(self): try: self._sync() - except (socket.error, socket.timeout, IOError): + except (OSError, socket.timeout): return False self.last_sync = datetime.utcnow() return True class ServerBrowser(Syncable): - def __init__(self, cup): self.cup = cup - self.servers = cup.db.setdefault('servers', dict) + self.servers = cup.db.setdefault("servers", dict) def _sync(self): to_delete = set(self.servers) - for x in xrange(1, 17): - addr = ('master%d.teeworlds.com' % x, 8300) - print addr + for x in range(1, 17): + addr = (f"master{x}.teeworlds.com", 8300) + print(addr) try: - self._sync_master(addr, to_delete) - except (socket.error, socket.timeout, IOError), e: + self._sync_server_browser(addr, to_delete) + except (OSError, socket.timeout): continue for server_id in to_delete: self.servers.pop(server_id, None) if not self.servers: - raise IOError('no servers found') + raise OSError("no servers found") self.cup.db.sync() - def _sync_master(self, addr, to_delete): + def _sync_server_browser(self, addr, to_delete): s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) s.settimeout(5) - s.sendto('\x20\x00\x00\x00\x00\x48\xff\xff\xff\xffreqt', addr) + s.sendto(b"\x20\x00\x00\x00\x00\x48\xff\xff\xff\xffreqt", addr) data = s.recvfrom(1024)[0][14:] s.close() - for n in xrange(0, len(data) / 6): - addr = ('.'.join(map(str, map(ord, data[n * 6:n * 6 + 4]))), - ord(data[n * 6 + 5]) * 256 + ord(data[n * 6 + 4])) - server_id = '%s:%d' % addr + for n in range(0, len(data) // 6): + addr = ( + ".".join(map(str, map(ord, data[n * 6 : n * 6 + 4]))), + ord(data[n * 6 + 5]) * 256 + ord(data[n * 6 + 4]), + ) + server_id = f"{addr[0]}:{addr[1]}" if server_id in self.servers: if not self.servers[server_id].sync(): continue @@ -75,31 +67,31 @@ def _sync_master(self, addr, to_delete): class Server(Syncable): - def __init__(self, addr, server_id): self.addr = addr self.id = server_id self.players = [] if not self.sync(): - raise ServerError('server not responding in time') + raise ServerError("server not responding in time") def _sync(self): s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) s.settimeout(1) - s.sendto('\xff\xff\xff\xff\xff\xff\xff\xff\xff\xffgief', self.addr) - bits = s.recvfrom(1024)[0][14:].split('\x00') + s.sendto(b"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xffgief", self.addr) + bits = s.recvfrom(1024)[0][14:].split(b"\x00") s.close() self.version, server_name, map_name = bits[:3] - self.name = server_name.decode('latin1') - self.map = map_name.decode('latin1') + self.name = server_name.decode("latin1") + self.map = map_name.decode("latin1") self.gametype = bits[3] - self.flags, self.progression, player_count, \ - self.max_players = map(int, bits[4:8]) + self.flags, self.progression, player_count, self.max_players = map( + int, bits[4:8] + ) # sync the player stats - players = dict((p.name, p) for p in self.players) - for i in xrange(player_count): - name = bits[8 + i * 2].decode('latin1') + players = {p.name: p for p in self.players} + for i in range(player_count): + name = bits[8 + i * 2].decode("latin1") score = int(bits[9 + i * 2]) # update existing player @@ -110,10 +102,10 @@ def _sync(self): else: self.players.append(Player(self, name, score)) # delete players that left - for player in players.itervalues(): + for player in players.values(): try: self.players.remove(player) - except: + except Exception: pass # sort the player list and count them @@ -124,8 +116,7 @@ def __cmp__(self, other): return unicodecmp(self.name, other.name) -class Player(object): - +class Player: def __init__(self, server, name, score): self.server = server self.name = name diff --git a/examples/cupoftee/pages.py b/examples/cupoftee/pages.py index 92b2046b3..3cbf842dd 100644 --- a/examples/cupoftee/pages.py +++ b/examples/cupoftee/pages.py @@ -1,84 +1,75 @@ -# -*- coding: utf-8 -*- -""" - cupoftee.pages - ~~~~~~~~~~~~~~ +from functools import reduce - The pages. - - :copyright: (c) 2009 by the Werkzeug Team, see AUTHORS for more details. - :license: BSD, see LICENSE for more details. -""" - -from werkzeug.utils import redirect from werkzeug.exceptions import NotFound -from cupoftee.application import Page -from cupoftee.utils import unicodecmp +from werkzeug.utils import redirect + +from .application import Page +from .utils import unicodecmp class ServerList(Page): - url_rule = '/' + url_rule = "/" def order_link(self, name, title): - cls = '' - link = '?order_by=' + name + cls = "" + link = f"?order_by={name}" desc = False if name == self.order_by: desc = not self.order_desc - cls = ' class="%s"' % (desc and 'down' or 'up') + cls = f' class="{"down" if desc else "up"}"' if desc: - link += '&dir=desc' - return '%s' % (link, cls, title) + link += "&dir=desc" + return f'{title}' def process(self): - self.order_by = self.request.args.get('order_by') or 'name' + self.order_by = self.request.args.get("order_by") or "name" sort_func = { - 'name': lambda x: x, - 'map': lambda x: x.map, - 'gametype': lambda x: x.gametype, - 'players': lambda x: x.player_count, - 'progression': lambda x: x.progression, + "name": lambda x: x, + "map": lambda x: x.map, + "gametype": lambda x: x.gametype, + "players": lambda x: x.player_count, + "progression": lambda x: x.progression, }.get(self.order_by) if sort_func is None: - return redirect(self.url_for('serverlist')) + return redirect(self.url_for("serverlist")) - self.servers = self.cup.master.servers.values() + self.servers = self.cup.server_browser.servers.values() self.servers.sort(key=sort_func) - if self.request.args.get('dir') == 'desc': + if self.request.args.get("dir") == "desc": self.servers.reverse() self.order_desc = True else: self.order_desc = False self.players = reduce(lambda a, b: a + b.players, self.servers, []) - self.players.sort(lambda a, b: unicodecmp(a.name, b.name)) + self.players = sorted(self.players, key=lambda a, b: unicodecmp(a.name, b.name)) class Server(Page): - url_rule = '/server/' + url_rule = "/server/" def process(self, id): try: - self.server = self.cup.master.servers[id] + self.server = self.cup.server_browser.servers[id] except KeyError: - raise NotFound() + raise NotFound() from None class Search(Page): - url_rule = '/search' + url_rule = "/search" def process(self): - self.user = self.request.args.get('user') + self.user = self.request.args.get("user") if self.user: self.results = [] - for server in self.cup.master.servers.itervalues(): + for server in self.cup.server_browser.servers.values(): for player in server.players: if player.name == self.user: self.results.append(server) class MissingPage(Page): - def get_response(self): - response = super(MissingPage, self).get_response() + response = super().get_response() response.status_code = 404 return response diff --git a/examples/cupoftee/templates/layout.html b/examples/cupoftee/templates/layout.html index 3aa88a20e..b434051dd 100644 --- a/examples/cupoftee/templates/layout.html +++ b/examples/cupoftee/templates/layout.html @@ -1,18 +1,17 @@ - + Teeworlds Server Browser - + -

Teeworlds Server Browser

+

Teeworlds Server Browser

- ${body} + {% block body %}{% endblock %}