diff --git a/.appveyor.yml b/.appveyor.yml deleted file mode 100644 index 9e59c684d..000000000 --- a/.appveyor.yml +++ /dev/null @@ -1,15 +0,0 @@ -environment: - global: - TOXENV: "py" - - matrix: - - PYTHON: "C:\\Python27" - - PYTHON: "C:\\Python36" - -install: - - "%PYTHON%\\python.exe -m pip install -U pip setuptools wheel tox" - -build: false - -test_script: - - "%PYTHON%\\python.exe -m tox" diff --git a/.azure-pipelines.yml b/.azure-pipelines.yml new file mode 100644 index 000000000..c85fe0539 --- /dev/null +++ b/.azure-pipelines.yml @@ -0,0 +1,64 @@ +trigger: + - master + - '*.x' + +variables: + vmImage: ubuntu-latest + python.version: 3.7 + TOXENV: py,coverage-ci + hasTestResults: true + +strategy: + matrix: + Python 3.7 Linux: + vmImage: ubuntu-latest + Python 3.7 Windows: + vmImage: windows-latest + Python 3.7 Mac: + vmImage: macos-latest + PyPy 3 Linux: + python.version: pypy3 + Python 3.6 Linux: + python.version: 3.6 + Python 3.5 Linux: + python.version: 3.5 + Python 2.7 Linux: + python.version: 2.7 + Python 2.7 Windows: + python.version: 2.7 + vmImage: windows-latest + Docs: + TOXENV: docs-html + hasTestResults: false + Style: + TOXENV: style + hasTestResults: false + +pool: + vmImage: $[ variables.vmImage ] + +steps: + - task: UsePythonVersion@0 + inputs: + versionSpec: $(python.version) + displayName: Use Python $(python.version) + + - script: pip --disable-pip-version-check install -U tox + displayName: Install tox + + - script: tox -s false -- --junit-xml=test-results.xml + displayName: Run tox + + - task: PublishTestResults@2 + inputs: + testResultsFiles: test-results.xml + testRunTitle: $(Agent.JobName) + condition: eq(variables['hasTestResults'], 'true') + displayName: Publish test results + + - task: PublishCodeCoverageResults@1 + inputs: + codeCoverageTool: Cobertura + summaryFileLocation: coverage.xml + condition: eq(variables['hasTestResults'], 'true') + displayName: Publish coverage results diff --git a/.coveragerc b/.coveragerc deleted file mode 100644 index 24a4bfb3a..000000000 --- a/.coveragerc +++ /dev/null @@ -1,11 +0,0 @@ -[run] -branch = True -source = - werkzeug - tests - -[paths] -source = - werkzeug - .tox/*/lib/python*/site-packages/werkzeug - .tox/pypy/site-packages/werkzeug 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 index 96d79dad6..764a4428d 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,2 +1,8 @@ -tests/res/chunked.txt binary +# 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/.gitignore b/.gitignore index 9a2a9ee40..9b312a490 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,7 @@ MANIFEST build dist -*.egg-info +/src/Werkzeug.egg-info *.pyc *.pyo env @@ -19,3 +19,4 @@ htmlcov .hypothesis test_uwsgi_failed .idea +.pytest_cache/ diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 000000000..2fb466196 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,28 @@ +repos: + - repo: https://github.com/asottile/reorder_python_imports + rev: v1.4.0 + hooks: + - id: reorder-python-imports + name: Reorder Python imports (src, tests) + files: "^(?!examples/)" + args: ["--application-directories", ".:src"] + - id: reorder-python-imports + name: Reorder Python imports (examples) + files: "^examples/" + args: ["--application-directories", "examples"] + - repo: https://github.com/ambv/black + rev: 18.9b0 + hooks: + - id: black + - repo: https://gitlab.com/pycqa/flake8 + rev: 3.7.7 + hooks: + - id: flake8 + additional_dependencies: [flake8-bugbear] + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v2.1.0 + hooks: + - id: check-byte-order-marker + - id: trailing-whitespace + - id: end-of-file-fixer + exclude: "^tests/.*.http$" diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 224f986ed..000000000 --- a/.travis.yml +++ /dev/null @@ -1,68 +0,0 @@ -os: linux -sudo: false -language: python - -matrix: - include: - - python: 3.6 - env: TOXENV=hypothesis-uwsgi,codecov,stylecheck,docs-html - - python: 3.5 - env: TOXENV=py,codecov - - python: 3.4 - env: TOXENV=py,codecov - - python: 2.7 - env: TOXENV=py,codecov - - python: pypy - env: TOXENV=py,codecov - - python: nightly - env: TOXENV=py - - os: osx - language: generic - env: TOXENV=py - allow_failures: - - os: osx - language: generic - env: TOXENV=py - fast_finish: true - -before_install: - - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then - brew update; - brew install python3 redis memcached; - virtualenv -p python3 ~/py-env; - . ~/py-env/bin/activate; - fi - # Travis uses an outdated PyPy, this installs a more recent one. - - if [[ "$TRAVIS_PYTHON_VERSION" == "pypy" ]]; then - git clone https://github.com/pyenv/pyenv.git ~/.pyenv; - PYENV_ROOT="$HOME/.pyenv"; - PATH="$PYENV_ROOT/bin:$PATH"; - eval "$(pyenv init -)"; - pyenv install pypy2.7-5.8.0; - pyenv global pypy2.7-5.8.0; - fi - - if [[ "$TRAVIS_PYTHON_VERSION" == "pypy3" ]]; then - git clone https://github.com/pyenv/pyenv.git ~/.pyenv; - PYENV_ROOT="$HOME/.pyenv"; - PATH="$PYENV_ROOT/bin:$PATH"; - eval "$(pyenv init -)"; - pyenv install pypy3.5-5.8.0; - pyenv global pypy3.5-5.8.0; - fi - -install: - - pip install tox - -script: - - tox - -cache: - - pip - -branches: - only: - - master - - /^.*-maintenance$/ - -notifications: - email: false diff --git a/AUTHORS b/AUTHORS deleted file mode 100644 index dc1c35283..000000000 --- a/AUTHORS +++ /dev/null @@ -1,65 +0,0 @@ -Werkzeug is developed and maintained by the Pallets team and community -contributors. It was created by Armin Ronacher. The core maintainers -are: - -- Armin Ronacher (mitsuhiko) -- Marcus Unterwaditzer (untitaker) -- Adrian Mönnich (ThiefMaster) -- David Lord (davidism) - -A full list of contributors is available from git with: - -- 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 -- Mark Szymanski -- Andrew Bednar -- Craig Blaszczyk -- Felix König - -The SSL parts of the Werkzeug development server are partially taken -from Paste. The same is true for the range support which comes from -WebOb, a Paste project. The original code is MIT licensed and largely -compatible with the BSD 3-clause 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.rst b/CHANGES.rst index 3ca5a629a..b3faa0e03 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,5 +1,345 @@ -Werkzeug Changelog -================== +.. currentmodule:: werkzeug + +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 :attr:`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 :doc:`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 -------------- @@ -59,50 +399,38 @@ 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. (`pallets/meta#24`_) -- Raise ``TypeError`` when port is not an integer. (`#1088`_) -- Fully deprecate ``werkzeug.script``. Use `Click`_ instead. (`#1090`_) + 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. (`#414`_) + number of seconds, not a date string. (:pr:`414`) - Fix a bug in ``TypeConversionDict`` where errors are not propagated - when using the converter. (`#1102`_) + when using the converter. (:issue:`1102`) - ``Authorization.qop`` is a string instead of a set, to comply with - RFC 2617. (`#984`_) + 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. - (`#780`_, `#1109`_) + (:pr:`780`, :pr:`1109`) - Fix a TypeError in ``werkzeug.contrib.lint.GuardedIterator.close``. - (`#1116`_) + (:pr:`1116`) - ``BaseResponse.calculate_content_length`` now correctly works for Unicode responses on Python 3. It first encodes using - ``iter_encoded``. (`#705`_) + ``iter_encoded``. (:issue:`705`) - Secure cookie contrib works with string secret key on Python 3. - (`#1205`_) + (:pr:`1205`) - Shared data middleware accepts a list instead of a dict of static - locations to preserve lookup order. (`#1197`_) + locations to preserve lookup order. (:pr:`1197`) - HTTP header values without encoding can contain single quotes. - (`#1208`_) + (:pr:`1208`) - The built-in dev server supports receiving requests with chunked - transfer encoding. (`#1198`_) - -.. _Click: https://www.palletsprojects.com/p/click/ -.. _pallets/meta#24: https://github.com/pallets/meta/issues/24 -.. _#414: https://github.com/pallets/werkzeug/pull/414 -.. _#705: https://github.com/pallets/werkzeug/pull/705 -.. _#780: https://github.com/pallets/werkzeug/pull/780 -.. _#984: https://github.com/pallets/werkzeug/pull/984 -.. _#1088: https://github.com/pallets/werkzeug/pull/1088 -.. _#1090: https://github.com/pallets/werkzeug/pull/1090 -.. _#1102: https://github.com/pallets/werkzeug/pull/1102 -.. _#1109: https://github.com/pallets/werkzeug/pull/1109 -.. _#1116: https://github.com/pallets/werkzeug/pull/1116 -.. _#1197: https://github.com/pallets/werkzeug/pull/1197 -.. _#1198: https://github.com/pallets/werkzeug/pull/1198 -.. _#1205: https://github.com/pallets/werkzeug/pull/1205 -.. _#1208: https://github.com/pallets/werkzeug/pull/1208 + transfer encoding. (:pr:`1198`) + +.. _Click: https://palletsprojects.com/p/click/ + Version 0.12.2 -------------- @@ -1190,9 +1518,7 @@ 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 ----------- diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index c98d40b94..cf88893b7 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -131,7 +131,7 @@ reports from all runs. .. _email: https://help.github.com/articles/setting-your-email-in-git/ .. _Fork: https://github.com/pallets/werkzeug/pull/2305#fork-destination-box .. _Clone: https://help.github.com/articles/fork-a-repo/#step-2-create-a-local-clone-of-your-fork -.. _committing as you go: http://dont-be-afraid-to-commit.readthedocs.io/en/latest/git/commandlinegit.html#commit-your-changes +.. _committing as you go: https://dont-be-afraid-to-commit.readthedocs.io/en/latest/git/commandlinegit.html#commit-your-changes .. _PEP8: https://pep8.org/ .. _create a pull request: https://help.github.com/articles/creating-a-pull-request/ .. _coverage: https://coverage.readthedocs.io diff --git a/LICENSE b/LICENSE deleted file mode 100644 index 1cc75bb0e..000000000 --- a/LICENSE +++ /dev/null @@ -1,31 +0,0 @@ -Copyright © 2007 by the Pallets team. - -Some rights reserved. - -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. - -* 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 AND DOCUMENTATION 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 AND DOCUMENTATION, 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 0cc1a4079..2d4aaac86 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,9 +1,10 @@ -include CHANGES.rst LICENSE AUTHORS tox.ini -graft werkzeug/debug/shared -graft tests -graft docs +include CHANGES.rst +include LICENSE.rst +include tox.ini graft artwork -graft examples +graft docs prune docs/_build -prune docs/_themes -global-exclude *.py[cdo] __pycache__ *.so +graft examples +graft tests +graft src/werkzeug/debug/shared +global-exclude *.py[co] diff --git a/Makefile b/Makefile deleted file mode 100644 index cc165b763..000000000 --- a/Makefile +++ /dev/null @@ -1,25 +0,0 @@ -documentation: - @(cd docs; make html) - -release: - python scripts/make-release.py - -test: - pytest - -tox-test: - tox - -coverage: - @(coverage run --module pytest $(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 90a826307..02d2ea9dc 100644 --- a/README.rst +++ b/README.rst @@ -1,27 +1,30 @@ 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. +- 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 is Unicode aware and doesn't enforce any dependencies. It is up to the developer to choose a template engine, database adapter, and even @@ -62,16 +65,13 @@ A Simple Example Links ----- -* Website: https://www.palletsprojects.com/p/werkzeug/ -* Releases: https://pypi.org/project/Werkzeug/ -* Code: https://github.com/pallets/werkzeug -* Issue tracker: https://github.com/pallets/werkzeug/issues -* Test status: - - * Linux, Mac: https://travis-ci.org/pallets/werkzeug - * Windows: https://ci.appveyor.com/project/davidism/werkzeug - -* Test coverage: https://codecov.io/gh/pallets/werkzeug +- Website: https://palletsprojects.com/p/werkzeug/ +- Documentation: https://werkzeug.palletsprojects.com/ +- Releases: https://pypi.org/project/Werkzeug/ +- Code: https://github.com/pallets/werkzeug +- Issue tracker: https://github.com/pallets/werkzeug/issues +- Test status: https://dev.azure.com/pallets/werkzeug/_build +- Official chat: https://discord.gg/t6rrQZH .. _WSGI: https://wsgi.readthedocs.io/en/latest/ .. _Flask: https://www.palletsprojects.com/p/flask/ diff --git a/bench/wzbench.py b/bench/wzbench.py index 10d4fa463..6270b6208 100755 --- a/bench/wzbench.py +++ b/bench/wzbench.py @@ -8,18 +8,24 @@ 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. + :copyright: 2007 Pallets + :license: BSD-3-Clause """ from __future__ import division -import os +from __future__ import print_function + import gc -import sys +import os import subprocess -from cStringIO import StringIO +import sys from timeit import default_timer as timer from types import FunctionType +try: + from cStringIO import StringIO +except ImportError: + from io import StringIO + PY2 = sys.version_info[0] == 2 if not PY2: @@ -27,10 +33,9 @@ # 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') - +wz = type(sys)("werkzeug_nonlazy") +sys.path.insert(0, "") +null_out = open(os.devnull, "w") # ±4% are ignored TOLERANCE = 0.04 @@ -44,8 +49,9 @@ 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) + 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: @@ -55,8 +61,9 @@ def find_hg_tag(path): except OSError: return - client = subprocess.Popen(['hg', 'parent', '--template', '#node#'], - stdout=subprocess.PIPE, cwd=path) + client = subprocess.Popen( + ["hg", "parent", "--template", "#node#"], stdout=subprocess.PIPE, cwd=path + ) tip = client.communicate()[0].strip() tag = tags.get(tip) @@ -72,11 +79,12 @@ def load_werkzeug(path): # get rid of already imported stuff wz.__dict__.clear() for key in sys.modules.keys(): - if key.startswith('werkzeug.') or key == 'werkzeug': + 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)) @@ -85,18 +93,18 @@ def load_werkzeug(path): # get the real version from the setup file try: - f = open(os.path.join(path, 'setup.py')) + 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 + if line.startswith("version="): + return line[8:].strip(" \t,")[1:-1], hg_tag finally: f.close() - print >> sys.stderr, 'Unknown werkzeug version loaded' + print("Unknown werkzeug version loaded", file=sys.stderr) sys.exit(2) @@ -112,14 +120,14 @@ def format_func(func): name = func.__name__ else: name = func - if name.startswith('time_'): + if name.startswith("time_"): name = name[5:] - return name.replace('_', ' ').title() + return name.replace("_", " ").title() def bench(func): """Times a single function.""" - sys.stdout.write('%44s ' % format_func(func)) + sys.stdout.write("%44s " % format_func(func)) sys.stdout.flush() # figure out how many times we have to run the function to @@ -127,7 +135,7 @@ def bench(func): for i in xrange(3, 10): rounds = 1 << i t = timer() - for x in xrange(rounds): + for _ in xrange(rounds): func() if timer() - t >= 0.2: break @@ -139,14 +147,14 @@ def _run(): gc.disable() try: t = timer() - for x in xrange(rounds): + for _ 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.write("%.4f\n" % delta) sys.stdout.flush() return delta @@ -155,17 +163,33 @@ def _run(): 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') + + 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') + parser.error("Script takes no arguments") if options.compare: compare(*options.compare) elif options.init_compare: @@ -176,65 +200,70 @@ def main(): def init_compare(): """Initializes the comparison feature.""" - print('Initializing comparison feature') - subprocess.Popen(['hg', 'clone', '..', 'a']).wait() - subprocess.Popen(['hg', 'clone', '..', 'b']).wait() + 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' + if not os.path.isdir("a"): + print("error: comparison feature not initialized", file=sys.stderr) 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) + print("=" * 80) + print("WERKZEUG INTERNAL BENCHMARK -- COMPARE MODE".center(80)) + print("-" * 80) 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) + def hg(*x): + return 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 = 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] + 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 = 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) + _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) + 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 = '==' + 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) + 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): @@ -242,45 +271,47 @@ def run(path, no_header=False): 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) + 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) + 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 key.startswith("time_"): + before = globals().get("before_" + key[5:]) if before: before() result[key] = bench(value) - after = globals().get('after_' + key[5:]) + after = globals().get("after_" + key[5:]) if after: after() - print('-' * 80) + 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--' -)) +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 @@ -301,10 +332,10 @@ def time_parse_form_data_multipart(): # 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_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 @@ -312,11 +343,11 @@ def time_parse_form_data_multipart(): def before_multidict_lookup_hit(): global MULTIDICT - MULTIDICT = wz.MultiDict({'foo': 'bar'}) + MULTIDICT = wz.MultiDict({"foo": "bar"}) def time_multidict_lookup_hit(): - MULTIDICT['foo'] + MULTIDICT["foo"] def after_multidict_lookup_hit(): @@ -331,7 +362,7 @@ def before_multidict_lookup_miss(): def time_multidict_lookup_miss(): try: - MULTIDICT['foo'] + MULTIDICT["foo"] except KeyError: pass @@ -348,31 +379,33 @@ def x(self): return 42 f = Foo() - for x in xrange(60): + for _ 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': '' - }) + 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): + for _ in xrange(30): REQUEST.path REQUEST.script_root - REQUEST.args['foo'] - REQUEST.form['foo'] + REQUEST.args["foo"] + REQUEST.form["foo"] def after_request_form_access(): @@ -381,12 +414,14 @@ def after_request_form_access(): 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') + 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(): @@ -404,16 +439,14 @@ def after_request_shallow_init(): 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): + resp = wz.Response(u"Hällo Wörld " * 1000, mimetype="text/html") + for _ 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): + resp = wz.Response(u"Hällo Wörld " * 1000, mimetype="text/html") + for _ in resp({"REQUEST_METHOD": "HEAD"}, lambda *s: None): pass @@ -424,9 +457,9 @@ def before_local_manager_dispatch(): def time_local_manager_dispatch(): - for x in xrange(10): + for _ in xrange(10): LOCAL.x = 42 - for x in xrange(10): + for _ in xrange(10): LOCAL.x @@ -437,14 +470,14 @@ def after_local_manager_dispatch(): def before_html_builder(): global TABLE - TABLE = [['col 1', 'col 2', 'col 3', '4', '5', '6'] for x in range(10)] + 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)) + 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) @@ -453,9 +486,9 @@ def after_html_builder(): TABLE = None -if __name__ == '__main__': +if __name__ == "__main__": os.chdir(os.path.dirname(__file__) or os.path.curdir) try: main() except KeyboardInterrupt: - print >> sys.stderr, 'interrupted!' + print("\nInterrupted!", file=sys.stderr) 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 fedaadfe3..218fe3339 100644 --- a/docs/changes.rst +++ b/docs/changes.rst @@ -1,109 +1,4 @@ -================== -Werkzeug Changelog -================== - -.. 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. +Changelog +========= .. include:: ../CHANGES.rst - -.. _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`. diff --git a/docs/conf.py b/docs/conf.py index 540ae20d8..a053617f8 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,214 +1,50 @@ -# -*- 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", +] +intersphinx_mapping = {"python": ("https://docs.python.org/3/", None)} +issues_github_path = "pallets/werkzeug" + +# HTML ----------------------------------------------------------------- + +html_theme = "werkzeug" +html_context = { + "project_links": [ + ProjectLink("Donate to Pallets", "https://www.palletsprojects.com/donate"), + ProjectLink("Werkzeug Website", "https://palletsprojects.com/p/werkzeug/"), + 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/"), + ] +} 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"], + "**": ["localtoc.html", "relations.html", "searchbox.html"], } +singlehtml_sidebars = {"index": ["project.html", "localtoc.html"]} +html_static_path = ["_static"] +html_favicon = "_static/favicon.ico" +html_logo = "_static/werkzeug.png" +html_title = "Werkzeug Documentation ({})".format(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, "Werkzeug-{}.tex".format(version), 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 index a2e583437..5eda789e5 100644 --- a/docs/contrib/atom.rst +++ b/docs/contrib/atom.rst @@ -2,6 +2,11 @@ Atom Syndication ================ +.. warning:: + .. deprecated:: 0.15 + This will be removed in version 1.0. Use a dedicated feed + library instead. + .. automodule:: werkzeug.contrib.atom .. autoclass:: AtomFeed diff --git a/docs/contrib/cache.rst b/docs/contrib/cache.rst index 7437ea6d9..f12a489b9 100644 --- a/docs/contrib/cache.rst +++ b/docs/contrib/cache.rst @@ -2,6 +2,11 @@ Cache ===== +.. warning:: + .. deprecated:: 0.15 + This will be removed in version 1.0. It has been extracted to + `cachelib `_. + .. automodule:: werkzeug.contrib.cache diff --git a/docs/contrib/fixers.rst b/docs/contrib/fixers.rst index 662b5c24b..8bf8d13e7 100644 --- a/docs/contrib/fixers.rst +++ b/docs/contrib/fixers.rst @@ -1,16 +1 @@ -====== -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 index 991f50619..cd959f01c 100644 --- a/docs/contrib/index.rst +++ b/docs/contrib/index.rst @@ -2,8 +2,13 @@ Contributed Modules =================== -A lot of useful code contributed by the community is shipped with Werkzeug -as part of the `contrib` module: +Some useful code contributed by the community is shipped with Werkzeug +as part of the ``contrib`` module. + +.. warning:: + The code in this module is being deprecated, to be moved or removed + in version 1.0. Be sure to pay attention to any deprecation warnings + and update your code appropriately. .. toctree:: :maxdepth: 2 diff --git a/docs/contrib/iterio.rst b/docs/contrib/iterio.rst index e7b0fe999..23de71c66 100644 --- a/docs/contrib/iterio.rst +++ b/docs/contrib/iterio.rst @@ -1,7 +1,10 @@ -======= Iter IO ======= +.. warning:: + .. deprecated:: 0.15 + This will be removed in version 1.0. + .. automodule:: werkzeug.contrib.iterio .. autoclass:: IterIO diff --git a/docs/contrib/lint.rst b/docs/contrib/lint.rst index fe3e015c2..be249bbe4 100644 --- a/docs/contrib/lint.rst +++ b/docs/contrib/lint.rst @@ -1,9 +1,7 @@ -========================== Lint Validation Middleware ========================== -.. currentmodule:: werkzeug.contrib.lint - -.. automodule:: werkzeug.contrib.lint - -.. autoclass:: LintMiddleware +.. warning:: + ``werkzeug.contrib.lint`` has moved to + :mod:`werkzeug.middleware.lint`. The old import is deprecated as of + version 0.15 and will be removed in version 1.0. diff --git a/docs/contrib/profiler.rst b/docs/contrib/profiler.rst index 187039ebe..ccbd66a7d 100644 --- a/docs/contrib/profiler.rst +++ b/docs/contrib/profiler.rst @@ -1,11 +1,9 @@ -========================= WSGI Application Profiler ========================= -.. automodule:: werkzeug.contrib.profiler - -.. autoclass:: MergeStream - -.. autoclass:: ProfilerMiddleware +.. warning:: + ``werkzeug.contrib.profiler`` has moved to + :mod:`werkzeug.middleware.profiler`. The old import is deprecated as + of version 0.15 and will be removed in version 1.0. -.. autofunction:: make_action +.. autoclass:: werkzeug.contrib.profiler.MergeStream diff --git a/docs/contrib/securecookie.rst b/docs/contrib/securecookie.rst index e75572b64..95be7db96 100644 --- a/docs/contrib/securecookie.rst +++ b/docs/contrib/securecookie.rst @@ -2,6 +2,11 @@ Secure Cookie ============= +.. warning:: + .. deprecated:: 0.15 + This will be removed in version 1.0. It has moved to + https://github.com/pallets/secure-cookie. + .. automodule:: werkzeug.contrib.securecookie Security diff --git a/docs/contrib/sessions.rst b/docs/contrib/sessions.rst index 28bdb4147..7ec4601d4 100644 --- a/docs/contrib/sessions.rst +++ b/docs/contrib/sessions.rst @@ -2,11 +2,13 @@ Sessions ======== -.. automodule:: werkzeug.contrib.sessions +.. warning:: + .. deprecated:: 0.15 + This will be removed in version 1.0. It has moved to + https://github.com/pallets/secure-cookie. -.. testsetup:: +.. automodule:: werkzeug.contrib.sessions - from werkzeug.contrib.sessions import * Reference ========= @@ -14,7 +16,7 @@ Reference .. autoclass:: Session .. attribute:: sid - + The session ID as string. .. attribute:: new diff --git a/docs/contrib/wrappers.rst b/docs/contrib/wrappers.rst index 292208a56..3ed617e23 100644 --- a/docs/contrib/wrappers.rst +++ b/docs/contrib/wrappers.rst @@ -2,10 +2,15 @@ Extra Wrappers ============== +.. warning:: + .. deprecated:: 0.15 + All classes in this module have been moved or deprecated and + will be removed in version 1.0. Check the docs for the status + of each class. + .. automodule:: werkzeug.contrib.wrappers .. autoclass:: JSONRequestMixin - :members: .. autoclass:: ProtobufRequestMixin :members: diff --git a/docs/debug.rst b/docs/debug.rst index 78563e435..c553f99e9 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 tradition text-based traceback. The text can be +copied, or automatically pasted to `gist.github.com +`_ with one click. diff --git a/docs/deployment/cgi.rst b/docs/deployment/cgi.rst index 5c79d5afc..e43f4e203 100644 --- a/docs/deployment/cgi.rst +++ b/docs/deployment/cgi.rst @@ -10,7 +10,7 @@ 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/ +.. _AppEngine: https://cloud.google.com/appengine/ Creating a `.cgi` file ====================== @@ -32,10 +32,10 @@ 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 +`.cgi` into a `cgi-bin` (and use `mod_rewrite` 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: +In Apache for example you can put something like this into the config: .. sourcecode:: apache diff --git a/docs/deployment/fastcgi.rst b/docs/deployment/fastcgi.rst index 84d09877f..8211ea2ad 100644 --- a/docs/deployment/fastcgi.rst +++ b/docs/deployment/fastcgi.rst @@ -18,7 +18,7 @@ First you need to create the FastCGI server file. Let's call it #!/usr/bin/python from flup.server.fcgi import WSGIServer from yourapplication import make_app - + if __name__ == '__main__': application = make_app() WSGIServer(application).run() @@ -65,14 +65,10 @@ A basic FastCGI configuration for lighttpd looks like this:: "^(/.*)$" => "/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. +binds the application to `/yourapplication`. -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). +See the Lighty docs for more information on `FastCGI and Python +`_. Configuring nginx ================= @@ -137,6 +133,6 @@ path. Common problems are: web server. - different python interpreters being used. -.. _lighttpd: http://www.lighttpd.net/ -.. _nginx: http://nginx.net/ -.. _flup: http://trac.saddi.com/flup +.. _lighttpd: https://www.lighttpd.net/ +.. _nginx: https://nginx.org/ +.. _flup: https://pypi.org/project/flup/ diff --git a/docs/deployment/mod_wsgi.rst b/docs/deployment/mod_wsgi.rst index 85eb965b9..93d48a96e 100644 --- a/docs/deployment/mod_wsgi.rst +++ b/docs/deployment/mod_wsgi.rst @@ -4,7 +4,7 @@ If you are using the `Apache`_ webserver you should consider using `mod_wsgi`_. -.. _Apache: http://httpd.apache.org/ +.. _Apache: https://httpd.apache.org/ Installing `mod_wsgi` ===================== @@ -74,9 +74,6 @@ application under a different user for security reasons: -For more information consult the `mod_wsgi wiki`_. - -.. _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/ +.. _mod_wsgi: https://modwsgi.readthedocs.io/en/develop/ +.. _installation instructions: https://modwsgi.readthedocs.io/en/develop/installation.html +.. _virtual python: https://pypi.org/project/virtualenv/ diff --git a/docs/deployment/proxying.rst b/docs/deployment/proxying.rst index d88dbcb2a..d3e47c0c4 100644 --- a/docs/deployment/proxying.rst +++ b/docs/deployment/proxying.rst @@ -29,7 +29,7 @@ It looks something along these lines:: 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. +apply the :class:`~werkzeug.middleware.proxy_fix.ProxyFix` middleware. Configuring nginx @@ -43,7 +43,7 @@ The basic nginx configuration looks like this:: 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; + proxy_redirect default; } Since Nginx doesn't start your server for you, you have to do it by yourself. You diff --git a/docs/exceptions.rst b/docs/exceptions.rst index 6f978c4c7..f491def57 100644 --- a/docs/exceptions.rst +++ b/docs/exceptions.rst @@ -44,6 +44,8 @@ The following error classes exist in Werkzeug: .. autoexception:: ImATeapot +.. autoexception:: FailedDependency + .. autoexception:: PreconditionRequired .. autoexception:: TooManyRequests @@ -100,6 +102,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 +144,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/index.rst b/docs/index.rst index 48c0da563..3be4882ea 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -1,7 +1,88 @@ -====================== -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 is Unicode aware and 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 + transition + tutorial + levels + quickstart + + +Serving and Testing +------------------- + +.. toctree:: + :maxdepth: 2 + + serving + test + debug + + +Reference +--------- + +.. toctree:: + :maxdepth: 2 + + wrappers + routing + wsgi + filesystem + http + datastructures + utils + urls + local + middleware/index + exceptions + + +Deployment +---------- + +.. toctree:: + :maxdepth: 3 + + deployment/index + + +Contributed Modules +------------------- + +.. toctree:: + :maxdepth: 3 + + contrib/index + + +Additional Information +---------------------- + +.. toctree:: + :maxdepth: 2 + + terms + unicode + request_data + changes diff --git a/docs/installation.rst b/docs/installation.rst index c07d4521a..583accf77 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -1,133 +1,173 @@ -============ +.. _installation: + 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 3. Werkzeug supports +Python 3.4 and newer and Python 2.7. -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 +~~~~~~~~~~~~~~~~~~~~~ + +These distributions will not be installed automatically. Werkzeug will +detect and use them if you install them. + +* `SimpleJSON`_ is a fast JSON implementation that is compatible with + Python's ``json`` module. It is preferred for JSON operations if it is + installed. +* `termcolor`_ provides request log highlighting when using the + development server. +* `Watchdog`_ provides a faster, more efficient reloader for the + development server. + +.. _SimpleJSON: https://simplejson.readthedocs.io/en/latest/ +.. _termcolor: https://pypi.org/project/termcolor/ +.. _Watchdog: https://pypi.org/project/watchdog/ + + +Virtual environments +-------------------- + +Use a virtual environment to manage the dependencies for your project, +both in development and in production. + +What problem does a virtual environment solve? The more Python +projects you have, the more likely it is that you need to work with +different versions of Python libraries, or even Python itself. Newer +versions of libraries for one project can break compatibility in +another project. + +Virtual environments are independent groups of Python libraries, one for +each project. Packages installed for one project will not affect other +projects or the operating system's packages. + +Python 3 comes bundled with the :mod:`venv` module to create virtual +environments. If you're using a modern version of Python, you can +continue on to the next section. + +If you're using Python 2, see :ref:`install-install-virtualenv` first. + +.. _install-create-env: + +Create an environment +~~~~~~~~~~~~~~~~~~~~~ -Either way we strongly recommend using these tools in combination with -:ref:`virtualenv`. +Create a project folder and a :file:`venv` folder within: -This will install a Werkzeug egg in your Python installation's `site-packages` -directory. +.. code-block:: sh -From the tarball release -------------------------- + mkdir myproject + cd myproject + python3 -m venv venv -1. Download the most recent tarball from the `download page`_. -2. Unpack the tarball. -3. ``python setup.py install`` +On Windows: -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. +.. code-block:: bat -This will install Werkzeug into your Python installation's `site-packages` -directory. + py -3 -m venv venv +If you needed to install virtualenv because you are on an older version +of Python, use the following command instead: -Installing the development version -================================== +.. code-block:: sh -1. Install `Git`_ -2. ``git clone git://github.com/pallets/werkzeug.git`` -3. ``cd werkzeug`` -4. ``pip install --editable .`` + virtualenv venv -.. _virtualenv: +On Windows: -virtualenv -========== +.. code-block:: bat -Virtualenv is probably what you want to use during development, and in -production too if you have shell access there. + \Python27\Scripts\virtualenv.exe venv + + +Activate the environment +~~~~~~~~~~~~~~~~~~~~~~~~ + +Before you work on your project, activate the corresponding environment: + +.. code-block:: sh + + . venv/bin/activate + +On Windows: + +.. code-block:: bat + + venv\Scripts\activate + +Your shell prompt will change to show the name of the activated +environment. + + +Install Werkzeug +---------------- + +Within the activated environment, use the following command to install +Werkzeug: + +.. code-block:: sh + + pip install Werkzeug -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? -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. +Living on the edge +~~~~~~~~~~~~~~~~~~ -So let's see how virtualenv works! +If you want to work with the latest Werkzeug code before it's released, +install or update the code from the master branch: -If you are on Mac OS X or Linux, chances are that one of the following two -commands will work for you:: +.. code-block:: sh - $ sudo easy_install virtualenv + pip install -U https://github.com/pallets/werkzeug/archive/master.tar.gz -or even better:: - $ sudo pip install virtualenv +.. _install-install-virtualenv: -One of these will probably install virtualenv on your system. Maybe it's -even in your package manager. If you use Ubuntu, try:: +Install virtualenv +------------------ - $ sudo apt-get install python-virtualenv +If you are using Python 2, the venv module is not available. Instead, +install `virtualenv`_. -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. +On Linux, virtualenv is provided by your package manager: -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:: +.. code-block:: sh - $ mkdir myproject - $ cd myproject - $ virtualenv env - New python executable in env/bin/python - Installing setuptools............done. + # Debian, Ubuntu + sudo apt-get install python-virtualenv -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:: + # CentOS, Fedora + sudo yum install python-virtualenv - $ . env/bin/activate + # Arch + sudo pacman -S python-virtualenv -(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``) +If you are on Mac OS X or Windows, download `get-pip.py`_, then: -If you are a Windows user, the following command is for you:: +.. code-block:: sh - $ env\scripts\activate + sudo python2 Downloads/get-pip.py + sudo python2 -m pip install virtualenv -Either way, you should now be using your virtualenv (see how the prompt of -your shell has changed to show the virtualenv). +On Windows, as an administrator: -Now you can just enter the following command to get Werkzeug activated in -your virtualenv:: +.. code-block:: bat - $ pip install Werkzeug + \Python27\python.exe Downloads\get-pip.py + \Python27\python.exe -m pip install virtualenv -A few seconds later you are good to go. +Now you can continue to :ref:`install-create-env`. -.. _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/ +.. _virtualenv: https://virtualenv.pypa.io/en/latest/ +.. _get-pip.py: https://bootstrap.pypa.io/get-pip.py 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..1629355f5 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 @@ -51,7 +51,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/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 index ced87ad68..c2569b555 100644 --- a/docs/middlewares.rst +++ b/docs/middlewares.rst @@ -1,21 +1,6 @@ -=========== -Middlewares -=========== +:orphan: -.. module:: werkzeug.wsgi +Middleware +========== -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:: ProxyMiddleware - -.. autoclass:: DispatcherMiddleware - -Also there's the … - -.. autofunction:: werkzeug._internal._easteregg +Moved to :doc:`/middleware/index`. 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..125787669 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -2,7 +2,7 @@ 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 diff --git a/docs/request_data.rst b/docs/request_data.rst index 4f32ab66f..dfbd08d3e 100644 --- a/docs/request_data.rst +++ b/docs/request_data.rst @@ -3,7 +3,7 @@ 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. @@ -98,19 +98,25 @@ 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:: +url encoded data. To extend the capabilities, subclass :class:`BaseRequest` +or :class:`Request` and add or extend methods. + +There is already a mixin that provides JSON parsing:: + + from werkzeug.wrappers import Request + from werkzeug.wrappers.json import JSONMixin + + class JSONRequest(JSONMixin, Request): + pass + +The basic implementation of that looks like:: from werkzeug.utils import cached_property from werkzeug.wrappers import Request - from simplejson import loads + import simplejson as json 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) + if self.mimetype == "application/json": + return json.loads(self.data) diff --git a/docs/requirements.txt b/docs/requirements.txt new file mode 100644 index 000000000..5d106d6c9 --- /dev/null +++ b/docs/requirements.txt @@ -0,0 +1,3 @@ +Sphinx~=1.8.3 +Pallets-Sphinx-Themes~=1.1.2 +sphinx-issues~=1.2.0 diff --git a/docs/routing.rst b/docs/routing.rst index 0b1d8269d..f5a768cc1 100644 --- a/docs/routing.rst +++ b/docs/routing.rst @@ -6,10 +6,6 @@ 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 +15,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 @@ -149,38 +145,60 @@ 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. + +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): + def __init__(self, url_map, maybe=False): super(BooleanConverter, self).__init__(url_map) - self.randomify = randomify - self.regex = '(?:yes|no|maybe)' + 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 ============= diff --git a/docs/serving.rst b/docs/serving.rst index 39f618f3a..defb33480 100644 --- a/docs/serving.rst +++ b/docs/serving.rst @@ -55,7 +55,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. @@ -75,8 +75,8 @@ polling and ``'watchdog'`` forces it to the watchdog backend. 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. +`_ package. Windows users need to install `colorama +`_ in addition to termcolor for this to work. Virtual Hosts ------------- @@ -151,7 +151,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 --- @@ -225,3 +225,14 @@ discouraged because modern browsers do a bad job at supporting them for security reasons. This feature requires the pyOpenSSL 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..f2d8fc649 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. diff --git a/docs/test.rst b/docs/test.rst index eb0130b3d..c7e213f85 100644 --- a/docs/test.rst +++ b/docs/test.rst @@ -139,7 +139,7 @@ Testing API 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. diff --git a/docs/transition.rst b/docs/transition.rst index 54cf2a0ea..b4e69a3e1 100644 --- a/docs/transition.rst +++ b/docs/transition.rst @@ -26,7 +26,7 @@ was this:: 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. +`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 @@ -54,20 +54,33 @@ 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`, `click` 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. +Deprecated and Removed Code +--------------------------- + +Some things that were relevant to Werkzeug's core (working with WSGI and +HTTP) have been removed. These were not commonly used, or are better +served by dedicated libraries. + +- ``werkzeug.script``, replace with `Click`_ or another dedicated + command line library. +- ``werkzeug.template``, replace with `Jinja`_ or another dedicated + template library. +- ``werkzeug.contrib.jsrouting``, this type of URL generation in + JavaScript did not scale well. Instead, generate URLs when + rendering templates, or add a URL field to a JSON response. +- ``werkzeug.contrib.kickstart``, replace with custom code if needed, + the Werkzeug API has improved in general. `Flask`_ is a higher-level + version of this. +- ``werkzeug.contrib.testtools``, was not significantly useful over + the default behavior. +- ``werkzeug.contrib.cache``, has been extracted to `cachelib`_. +- ``werkzeug.contrib.atom``, was outside the core focus of Werkzeug, + replace with a dedicated feed generation library. +- ``werkzeug.contrib.limiter``, stream limiting is better handled by + the WSGI server library instead of middleware. + +.. _Click: https://click.palletsprojects.com/ +.. _Jinja: http://jinja.pocoo.org/docs/ +.. _Flask: http://flask.pocoo.org/docs/ +.. _cachelib: https://github.com/pallets/cachelib diff --git a/docs/tutorial.rst b/docs/tutorial.rst index 9435e34e3..d90f986ad 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 --------------------------------- @@ -474,4 +474,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..446febf9d 100644 --- a/docs/unicode.rst +++ b/docs/unicode.rst @@ -4,7 +4,7 @@ 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 @@ -95,7 +95,7 @@ argument that behaves like the `errors` parameter of the builtin string method 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. +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. diff --git a/docs/utils.rst b/docs/utils.rst index 89b6ef9d1..689c48821 100644 --- a/docs/utils.rst +++ b/docs/utils.rst @@ -27,10 +27,6 @@ General Helpers .. autoclass:: header_property -.. autofunction:: parse_cookie - -.. autofunction:: dump_cookie - .. autofunction:: redirect .. autofunction:: append_slash_redirect 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..2a8c406f3 100644 --- a/docs/wrappers.rst +++ b/docs/wrappers.rst @@ -176,3 +176,23 @@ and :class:`BaseResponse` classes and implement all the mixins Werkzeug provides .. autoclass:: UserAgentMixin :members: + + +Extra Mixin Classes +=================== + +These mixins are not included in the default :class:`Request` and +:class:`Response` classes. They provide extra behavior that needs to be +opted into by creating your own subclasses:: + + class Response(JSONMixin, BaseResponse): + pass + + +.. module:: werkzeug.wrappers.json + +JSON +---- + +.. autoclass:: JSONMixin + :members: diff --git a/docs/wsgi.rst b/docs/wsgi.rst index 4391c89a6..699a07ee8 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 `. +:ref:`Request / Response classes `. 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,65 @@ information or perform common manipulations: .. autofunction:: host_is_trusted + Convenience Helpers -=================== +------------------- .. autofunction:: responder .. autofunction:: werkzeug.testapp.test_app + + +Bytes, Strings, and Encodings +----------------------------- + +The WSGI environment on Python 3 works slightly different than it does +on Python 2. Werkzeug hides the differences from you if you use the +higher level APIs. + +The WSGI specification (`PEP 3333`_) decided to always use the native +``str`` type. On Python 2 this means the raw bytes are passed through +and can be decoded directly. On Python 3, however, the raw bytes are +always decoded using the ISO-8859-1 charset to produce a Unicode string. + +Python 3 Unicode 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 Unicode 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. + +.. _PEP 3333: https://www.python.org/dev/peps/pep-3333/#unicode-issues + + +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 94% rename from examples/README rename to examples/README.rst index 8f74b4f09..2b9df866a 100644 --- a/examples/README +++ b/examples/README.rst @@ -19,9 +19,11 @@ find in real life :-) `simplewiki` + A simple Wiki implementation. Requirements: + - SQLAlchemy - Creoleparser >= 0.7 - genshi @@ -44,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. @@ -68,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 @@ -88,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/securecookie.py b/examples/contrib/securecookie.py index da7b4f119..2f6544d32 100644 --- a/examples/contrib/securecookie.py +++ b/examples/contrib/securecookie.py @@ -5,19 +5,20 @@ Stores session on the client. - :copyright: (c) 2009 by the Werkzeug Team, see AUTHORS for more details. - :license: BSD. + :copyright: 2007 Pallets + :license: BSD-3-Clause """ from time import asctime -from werkzeug.serving import run_simple -from werkzeug.wrappers import BaseRequest, BaseResponse + from werkzeug.contrib.securecookie import SecureCookie +from werkzeug.serving import run_simple +from werkzeug.wrappers import BaseRequest +from werkzeug.wrappers import BaseResponse -SECRET_KEY = 'V\x8a$m\xda\xe9\xc3\x0f|f\x88\xbccj>\x8bI^3+' +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) @@ -28,23 +29,23 @@ def index(request): def get_time(request): - return 'Time: %s' % request.session.get('time', 'not set') + return "Time: %s" % request.session.get("time", "not set") def set_time(request): - request.session['time'] = time = asctime() - return 'Time set to %s' % time + 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') + 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) +if __name__ == "__main__": + run_simple("localhost", 5000, application) diff --git a/examples/contrib/sessions.py b/examples/contrib/sessions.py index ecf464a24..c5eef576a 100644 --- a/examples/contrib/sessions.py +++ b/examples/contrib/sessions.py @@ -1,11 +1,11 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- +from werkzeug.contrib.sessions import SessionMiddleware +from werkzeug.contrib.sessions import SessionStore 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 = {} @@ -23,21 +23,22 @@ def get(self, sid): def application(environ, start_response): - session = environ['werkzeug.session'] - session['visit_count'] = session.get('visit_count', 0) + 1 + session = environ["werkzeug.session"] + session["visit_count"] = session.get("visit_count", 0) + 1 - start_response('200 OK', [('Content-Type', 'text/html')]) - return [''' - + start_response("200 OK", [("Content-Type", "text/html")]) + return [ + """ Session Example

Session Example

-

You visited this page %d times.

- ''' % session['visit_count']] +

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()) +if __name__ == "__main__": + run_simple("localhost", 5000, make_app()) diff --git a/examples/cookieauth.py b/examples/cookieauth.py index 13f32c9e4..ba23bda47 100644 --- a/examples/cookieauth.py +++ b/examples/cookieauth.py @@ -7,28 +7,28 @@ 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. + :copyright: 2007 Pallets + :license: BSD-3-Clause """ -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 +from werkzeug.serving import run_simple +from werkzeug.utils import cached_property +from werkzeug.utils import escape +from werkzeug.utils import redirect +from werkzeug.wrappers import Request +from werkzeug.wrappers import Response # 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' +SECRET_KEY = "\xfa\xdd\xb8z\xae\xe0}4\x8b\xea" # the cookie name for the session -COOKIE_NAME = 'session' +COOKIE_NAME = "session" # the users that may access -USERS = { - 'admin': 'default', - 'user1': 'default' -} +USERS = {"admin": "default", "user1": "default"} class AppRequest(Request): @@ -36,11 +36,11 @@ class AppRequest(Request): def logout(self): """Log the user out.""" - self.session.pop('username', None) + self.session.pop("username", None) def login(self, username): """Log the user in.""" - self.session['username'] = username + self.session["username"] = username @property def logged_in(self): @@ -50,7 +50,7 @@ def logged_in(self): @property def user(self): """The user that is logged in.""" - return self.session.get('username') + return self.session.get("username") @cached_property def session(self): @@ -61,16 +61,16 @@ def session(self): def login_form(request): - error = '' - if request.method == 'POST': - username = request.form.get('username') - password = request.form.get('password') + 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

+ return redirect("") + error = "

Invalid credentials" + return Response( + """Login

Login

Not logged in. %s

@@ -79,23 +79,28 @@ def login_form(request): -
''' % error, mimetype='text/html') + """ + % error, + mimetype="text/html", + ) def index(request): - return Response(''' - Logged in + return Response( + """Logged in

Logged in

Logged in as %s -

Logout - ''' % escape(request.user), mimetype='text/html') +

Logout""" + % escape(request.user), + mimetype="text/html", + ) @AppRequest.application def application(request): - if request.args.get('do') == 'logout': + if request.args.get("do") == "logout": request.logout() - response = redirect('.') + response = redirect(".") elif request.logged_in: response = index(request) else: @@ -104,5 +109,5 @@ def application(request): return response -if __name__ == '__main__': - run_simple('localhost', 4000, application) +if __name__ == "__main__": + run_simple("localhost", 4000, application) diff --git a/examples/coolmagic/__init__.py b/examples/coolmagic/__init__.py index f54417572..0526f1d69 100644 --- a/examples/coolmagic/__init__.py +++ b/examples/coolmagic/__init__.py @@ -5,7 +5,7 @@ Package description goes here. - :copyright: (c) 2009 by the Werkzeug Team, see AUTHORS for more details. - :license: BSD, see LICENSE for more details. + :copyright: 2007 Pallets + :license: BSD-3-Clause """ -from coolmagic.application import make_app +from .application import make_app diff --git a/examples/coolmagic/application.py b/examples/coolmagic/application.py index eb010b9fd..730f4d83e 100644 --- a/examples/coolmagic/application.py +++ b/examples/coolmagic/application.py @@ -9,13 +9,21 @@ that automatically wraps the application within the require middlewares. Per default only the `SharedDataMiddleware` is applied. - :copyright: (c) 2009 by the Werkzeug Team, see AUTHORS for more details. - :license: BSD, see LICENSE for more details. + :copyright: 2007 Pallets + :license: BSD-3-Clause """ -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 os import listdir +from os import path + +from werkzeug.exceptions import HTTPException +from werkzeug.exceptions import NotFound +from werkzeug.middleware.shared_data import SharedDataMiddleware +from werkzeug.routing import Map +from werkzeug.routing import RequestRedirect +from werkzeug.routing import Rule + +from .utils import local_manager +from .utils import Request class CoolMagicApplication(object): @@ -26,20 +34,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__("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 +59,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 +75,9 @@ def make_app(config=None): app = CoolMagicApplication(config) # static stuff - from werkzeug.wsgi 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..4cd4ac45a 100644 --- a/examples/coolmagic/helpers.py +++ b/examples/coolmagic/helpers.py @@ -5,12 +5,10 @@ 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. + :copyright: 2007 Pallets + :license: BSD-3-Clause """ -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 diff --git a/examples/coolmagic/utils.py b/examples/coolmagic/utils.py index 473352d70..f4cf20d5b 100644 --- a/examples/coolmagic/utils.py +++ b/examples/coolmagic/utils.py @@ -8,21 +8,24 @@ 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. + :copyright: 2007 Pallets + :license: BSD-3-Clause """ -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 os.path import dirname +from os.path import join + +from jinja2 import Environment +from jinja2 import FileSystemLoader +from werkzeug.local import Local +from werkzeug.local import LocalManager +from werkzeug.wrappers import BaseRequest +from werkzeug.wrappers import 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"), use_memcache=False) ) exported_views = {} @@ -32,19 +35,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.__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,7 +67,8 @@ 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) @@ -75,9 +83,8 @@ class ThreadedRequest(object): """ 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 +95,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 +107,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..1dba83e69 100644 --- a/examples/coolmagic/views/__init__.py +++ b/examples/coolmagic/views/__init__.py @@ -5,6 +5,6 @@ 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. + :copyright: 2007 Pallets + :license: BSD-3-Clause """ diff --git a/examples/coolmagic/views/static.py b/examples/coolmagic/views/static.py index 34b0a2f6b..f4f954092 100644 --- a/examples/coolmagic/views/static.py +++ b/examples/coolmagic/views/static.py @@ -5,29 +5,28 @@ Some static views. - :copyright: (c) 2009 by the Werkzeug Team, see AUTHORS for more details. - :license: BSD, see LICENSE for more details. + :copyright: 2007 Pallets + :license: BSD-3-Clause """ -from coolmagic.helpers import * +from coolmagic.utils import export -@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..b958dcbc3 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(object): 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 be06d512a..a0b50ca17 100644 --- a/examples/couchy/models.py +++ b/examples/couchy/models.py @@ -1,6 +1,12 @@ from datetime import datetime -from couchdb.mapping 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,22 +17,23 @@ class URL(Document): db = None @classmethod - def load(self, id): - return super(URL, self).load(URL.db, id) + def load(cls, id): + return super(URL, cls).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="/%s/" % str(id) + )["id"] + except Exception: continue if docid: break @@ -37,7 +44,7 @@ def store(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 "" % self.id diff --git a/examples/couchy/utils.py b/examples/couchy/utils.py index bfcb885a4..571a7ed98 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(object): def __init__(self, results, per_page, page, endpoint): self.results = results self.per_page = per_page @@ -56,10 +69,14 @@ 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 + ) + ] - 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) + has_previous = property(lambda self: self.page > 1) + has_next = property(lambda self: self.page < self.pages) + previous = property(lambda self: url_for(self.endpoint, page=self.page - 1)) + next = property(lambda self: url_for(self.endpoint, page=self.page + 1)) + pages = property(lambda self: 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..184c5d0d9 100644 --- a/examples/cupoftee/__init__.py +++ b/examples/cupoftee/__init__.py @@ -5,7 +5,7 @@ Werkzeug powered Teeworlds Server Browser. - :copyright: (c) 2009 by the Werkzeug Team, see AUTHORS for more details. - :license: BSD, see LICENSE for more details. + :copyright: 2007 Pallets + :license: BSD-3-Clause """ -from cupoftee.application import make_app +from .application import make_app diff --git a/examples/cupoftee/application.py b/examples/cupoftee/application.py index 526cf188a..540e3f59f 100644 --- a/examples/cupoftee/application.py +++ b/examples/cupoftee/application.py @@ -5,46 +5,62 @@ 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. + :copyright: 2007 Pallets + :license: BSD-3-Clause """ 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 -templates = path.join(path.dirname(__file__), 'templates') +from .db import Database +from .network import ServerBrowser + + +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) + ) + + identifier = property(lambda self: self.__name__.lower()) + - identifier = property(lambda x: x.__name__.lower()) +def _with_metaclass(meta, *bases): + """Create a base class with a metaclass.""" + class metaclass(type): + def __new__(metacls, name, this_bases, d): + return meta(name, bases, d) -class Page(object): - __metaclass__ = PageMeta + return type.__new__(metaclass, "temporary_class", (), {}) + + +class Page(_with_metaclass(PageMeta, object)): url_arguments = {} def __init__(self, cup, request, url_adapter): @@ -60,21 +76,18 @@ def process(self): def render_template(self, template=None): if template is None: - template = self.__class__.identifier + '.html' + template = self.__class__.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): - 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) @@ -83,7 +96,6 @@ def __init__(self, database, interval=120): self.updater.start() def update_master(self): - wait = self.interval while 1: if self.master.sync(): wait = self.interval @@ -97,10 +109,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 +120,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..7f0412207 100644 --- a/examples/cupoftee/db.py +++ b/examples/cupoftee/db.py @@ -6,20 +6,23 @@ 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. + :copyright: 2007 Pallets + :license: BSD-3-Clause """ -from __future__ import with_statement -import gdbm +from pickle import dumps +from pickle import loads from threading import Lock -from pickle import dumps, loads +try: + import dbm +except ImportError: + import anydbm as dbm -class Database(object): +class Database(object): def __init__(self, filename): self.filename = filename - self._fs = gdbm.open(filename, 'cf') + self._fs = dbm.open(filename, "cf") self._local = {} self._lock = Lock() @@ -40,7 +43,7 @@ def __setitem__(self, key, value): def __delitem__(self, key, value): 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 +67,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 +75,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..74c775aa1 100644 --- a/examples/cupoftee/network.py +++ b/examples/cupoftee/network.py @@ -5,14 +5,14 @@ Query the servers for information. - :copyright: (c) 2009 by the Werkzeug Team, see AUTHORS for more details. - :license: BSD, see LICENSE for more details. + :copyright: 2007 Pallets + :license: BSD-3-Clause """ -import time 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): @@ -32,37 +32,38 @@ def sync(self): 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 = ("master%d.teeworlds.com" % x, 8300) + print(addr) try: self._sync_master(addr, to_delete) - except (socket.error, socket.timeout, IOError), e: + except (socket.error, socket.timeout, IOError): continue for server_id in to_delete: self.servers.pop(server_id, None) if not self.servers: - raise IOError('no servers found') + raise IOError("no servers found") self.cup.db.sync() def _sync_master(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 = "%s:%d" % addr if server_id in self.servers: if not self.servers[server_id].sync(): continue @@ -75,31 +76,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') + for i in range(player_count): + name = bits[8 + i * 2].decode("latin1") score = int(bits[9 + i * 2]) # update existing player @@ -110,10 +111,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 @@ -125,7 +126,6 @@ def __cmp__(self, other): class Player(object): - 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..c1a823b72 100644 --- a/examples/cupoftee/pages.py +++ b/examples/cupoftee/pages.py @@ -5,56 +5,58 @@ The pages. - :copyright: (c) 2009 by the Werkzeug Team, see AUTHORS for more details. - :license: BSD, see LICENSE for more details. + :copyright: 2007 Pallets + :license: BSD-3-Clause """ +from functools import reduce -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 = "?order_by=" + name desc = False if name == self.order_by: desc = not self.order_desc - cls = ' class="%s"' % (desc and 'down' or 'up') + cls = ' class="%s"' % ("down" if desc else "up") if desc: - link += '&dir=desc' + link += "&dir=desc" return '%s' % (link, cls, 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.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: @@ -64,20 +66,19 @@ def process(self, id): 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.master.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.status_code = 404 diff --git a/examples/cupoftee/templates/layout.html b/examples/cupoftee/templates/layout.html index 3aa88a20e..9d8056794 100644 --- a/examples/cupoftee/templates/layout.html +++ b/examples/cupoftee/templates/layout.html @@ -4,15 +4,15 @@ Teeworlds Server Browser - + -

Teeworlds Server Browser

+

Teeworlds Server Browser

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