aboutsummaryrefslogtreecommitdiffstats
path: root/python/werkzeug/contrib/wrappers.py
diff options
context:
space:
mode:
Diffstat (limited to 'python/werkzeug/contrib/wrappers.py')
-rw-r--r--python/werkzeug/contrib/wrappers.py385
1 files changed, 385 insertions, 0 deletions
diff --git a/python/werkzeug/contrib/wrappers.py b/python/werkzeug/contrib/wrappers.py
new file mode 100644
index 0000000..49b82a7
--- /dev/null
+++ b/python/werkzeug/contrib/wrappers.py
@@ -0,0 +1,385 @@
+# -*- coding: utf-8 -*-
+"""
+ werkzeug.contrib.wrappers
+ ~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ Extra wrappers or mixins contributed by the community. These wrappers can
+ be mixed in into request objects to add extra functionality.
+
+ Example::
+
+ from werkzeug.wrappers import Request as RequestBase
+ from werkzeug.contrib.wrappers import JSONRequestMixin
+
+ class Request(RequestBase, JSONRequestMixin):
+ pass
+
+ Afterwards this request object provides the extra functionality of the
+ :class:`JSONRequestMixin`.
+
+ :copyright: 2007 Pallets
+ :license: BSD-3-Clause
+"""
+import codecs
+import warnings
+
+from .._compat import wsgi_decoding_dance
+from ..exceptions import BadRequest
+from ..http import dump_options_header
+from ..http import parse_options_header
+from ..utils import cached_property
+from ..wrappers.json import JSONMixin as _JSONMixin
+
+
+def is_known_charset(charset):
+ """Checks if the given charset is known to Python."""
+ try:
+ codecs.lookup(charset)
+ except LookupError:
+ return False
+ return True
+
+
+class JSONRequestMixin(_JSONMixin):
+ """
+ .. deprecated:: 0.15
+ Moved to :class:`werkzeug.wrappers.json.JSONMixin`. This old
+ import will be removed in version 1.0.
+ """
+
+ @property
+ def json(self):
+ warnings.warn(
+ "'werkzeug.contrib.wrappers.JSONRequestMixin' has moved to"
+ " 'werkzeug.wrappers.json.JSONMixin'. This old import will"
+ " be removed in version 1.0.",
+ DeprecationWarning,
+ stacklevel=2,
+ )
+ return super(JSONRequestMixin, self).json
+
+
+class ProtobufRequestMixin(object):
+
+ """Add protobuf parsing method to a request object. This will parse the
+ input data through `protobuf`_ if possible.
+
+ :exc:`~werkzeug.exceptions.BadRequest` will be raised if the content-type
+ is not protobuf or if the data itself cannot be parsed property.
+
+ .. _protobuf: https://github.com/protocolbuffers/protobuf
+
+ .. deprecated:: 0.15
+ This mixin will be removed in version 1.0.
+ """
+
+ #: by default the :class:`ProtobufRequestMixin` will raise a
+ #: :exc:`~werkzeug.exceptions.BadRequest` if the object is not
+ #: initialized. You can bypass that check by setting this
+ #: attribute to `False`.
+ protobuf_check_initialization = True
+
+ def parse_protobuf(self, proto_type):
+ """Parse the data into an instance of proto_type."""
+ warnings.warn(
+ "'werkzeug.contrib.wrappers.ProtobufRequestMixin' is"
+ " deprecated as of version 0.15 and will be removed in"
+ " version 1.0.",
+ DeprecationWarning,
+ stacklevel=2,
+ )
+ if "protobuf" not in self.environ.get("CONTENT_TYPE", ""):
+ raise BadRequest("Not a Protobuf request")
+
+ obj = proto_type()
+ try:
+ obj.ParseFromString(self.data)
+ except Exception:
+ raise BadRequest("Unable to parse Protobuf request")
+
+ # Fail if not all required fields are set
+ if self.protobuf_check_initialization and not obj.IsInitialized():
+ raise BadRequest("Partial Protobuf request")
+
+ return obj
+
+
+class RoutingArgsRequestMixin(object):
+
+ """This request mixin adds support for the wsgiorg routing args
+ `specification`_.
+
+ .. _specification: https://wsgi.readthedocs.io/en/latest/
+ specifications/routing_args.html
+
+ .. deprecated:: 0.15
+ This mixin will be removed in version 1.0.
+ """
+
+ def _get_routing_args(self):
+ warnings.warn(
+ "'werkzeug.contrib.wrappers.RoutingArgsRequestMixin' is"
+ " deprecated as of version 0.15 and will be removed in"
+ " version 1.0.",
+ DeprecationWarning,
+ stacklevel=2,
+ )
+ return self.environ.get("wsgiorg.routing_args", (()))[0]
+
+ def _set_routing_args(self, value):
+ warnings.warn(
+ "'werkzeug.contrib.wrappers.RoutingArgsRequestMixin' is"
+ " deprecated as of version 0.15 and will be removed in"
+ " version 1.0.",
+ DeprecationWarning,
+ stacklevel=2,
+ )
+ if self.shallow:
+ raise RuntimeError(
+ "A shallow request tried to modify the WSGI "
+ "environment. If you really want to do that, "
+ "set `shallow` to False."
+ )
+ self.environ["wsgiorg.routing_args"] = (value, self.routing_vars)
+
+ routing_args = property(
+ _get_routing_args,
+ _set_routing_args,
+ doc="""
+ The positional URL arguments as `tuple`.""",
+ )
+ del _get_routing_args, _set_routing_args
+
+ def _get_routing_vars(self):
+ warnings.warn(
+ "'werkzeug.contrib.wrappers.RoutingArgsRequestMixin' is"
+ " deprecated as of version 0.15 and will be removed in"
+ " version 1.0.",
+ DeprecationWarning,
+ stacklevel=2,
+ )
+ rv = self.environ.get("wsgiorg.routing_args")
+ if rv is not None:
+ return rv[1]
+ rv = {}
+ if not self.shallow:
+ self.routing_vars = rv
+ return rv
+
+ def _set_routing_vars(self, value):
+ warnings.warn(
+ "'werkzeug.contrib.wrappers.RoutingArgsRequestMixin' is"
+ " deprecated as of version 0.15 and will be removed in"
+ " version 1.0.",
+ DeprecationWarning,
+ stacklevel=2,
+ )
+ if self.shallow:
+ raise RuntimeError(
+ "A shallow request tried to modify the WSGI "
+ "environment. If you really want to do that, "
+ "set `shallow` to False."
+ )
+ self.environ["wsgiorg.routing_args"] = (self.routing_args, value)
+
+ routing_vars = property(
+ _get_routing_vars,
+ _set_routing_vars,
+ doc="""
+ The keyword URL arguments as `dict`.""",
+ )
+ del _get_routing_vars, _set_routing_vars
+
+
+class ReverseSlashBehaviorRequestMixin(object):
+
+ """This mixin reverses the trailing slash behavior of :attr:`script_root`
+ and :attr:`path`. This makes it possible to use :func:`~urlparse.urljoin`
+ directly on the paths.
+
+ Because it changes the behavior or :class:`Request` this class has to be
+ mixed in *before* the actual request class::
+
+ class MyRequest(ReverseSlashBehaviorRequestMixin, Request):
+ pass
+
+ This example shows the differences (for an application mounted on
+ `/application` and the request going to `/application/foo/bar`):
+
+ +---------------+-------------------+---------------------+
+ | | normal behavior | reverse behavior |
+ +===============+===================+=====================+
+ | `script_root` | ``/application`` | ``/application/`` |
+ +---------------+-------------------+---------------------+
+ | `path` | ``/foo/bar`` | ``foo/bar`` |
+ +---------------+-------------------+---------------------+
+
+ .. deprecated:: 0.15
+ This mixin will be removed in version 1.0.
+ """
+
+ @cached_property
+ def path(self):
+ """Requested path as unicode. This works a bit like the regular path
+ info in the WSGI environment but will not include a leading slash.
+ """
+ warnings.warn(
+ "'werkzeug.contrib.wrappers.ReverseSlashBehaviorRequestMixin'"
+ " is deprecated as of version 0.15 and will be removed in"
+ " version 1.0.",
+ DeprecationWarning,
+ stacklevel=2,
+ )
+ path = wsgi_decoding_dance(
+ self.environ.get("PATH_INFO") or "", self.charset, self.encoding_errors
+ )
+ return path.lstrip("/")
+
+ @cached_property
+ def script_root(self):
+ """The root path of the script includling a trailing slash."""
+ warnings.warn(
+ "'werkzeug.contrib.wrappers.ReverseSlashBehaviorRequestMixin'"
+ " is deprecated as of version 0.15 and will be removed in"
+ " version 1.0.",
+ DeprecationWarning,
+ stacklevel=2,
+ )
+ path = wsgi_decoding_dance(
+ self.environ.get("SCRIPT_NAME") or "", self.charset, self.encoding_errors
+ )
+ return path.rstrip("/") + "/"
+
+
+class DynamicCharsetRequestMixin(object):
+
+ """"If this mixin is mixed into a request class it will provide
+ a dynamic `charset` attribute. This means that if the charset is
+ transmitted in the content type headers it's used from there.
+
+ Because it changes the behavior or :class:`Request` this class has
+ to be mixed in *before* the actual request class::
+
+ class MyRequest(DynamicCharsetRequestMixin, Request):
+ pass
+
+ By default the request object assumes that the URL charset is the
+ same as the data charset. If the charset varies on each request
+ based on the transmitted data it's not a good idea to let the URLs
+ change based on that. Most browsers assume either utf-8 or latin1
+ for the URLs if they have troubles figuring out. It's strongly
+ recommended to set the URL charset to utf-8::
+
+ class MyRequest(DynamicCharsetRequestMixin, Request):
+ url_charset = 'utf-8'
+
+ .. deprecated:: 0.15
+ This mixin will be removed in version 1.0.
+
+ .. versionadded:: 0.6
+ """
+
+ #: the default charset that is assumed if the content type header
+ #: is missing or does not contain a charset parameter. The default
+ #: is latin1 which is what HTTP specifies as default charset.
+ #: You may however want to set this to utf-8 to better support
+ #: browsers that do not transmit a charset for incoming data.
+ default_charset = "latin1"
+
+ def unknown_charset(self, charset):
+ """Called if a charset was provided but is not supported by
+ the Python codecs module. By default latin1 is assumed then
+ to not lose any information, you may override this method to
+ change the behavior.
+
+ :param charset: the charset that was not found.
+ :return: the replacement charset.
+ """
+ return "latin1"
+
+ @cached_property
+ def charset(self):
+ """The charset from the content type."""
+ warnings.warn(
+ "'werkzeug.contrib.wrappers.DynamicCharsetRequestMixin'"
+ " is deprecated as of version 0.15 and will be removed in"
+ " version 1.0.",
+ DeprecationWarning,
+ stacklevel=2,
+ )
+ header = self.environ.get("CONTENT_TYPE")
+ if header:
+ ct, options = parse_options_header(header)
+ charset = options.get("charset")
+ if charset:
+ if is_known_charset(charset):
+ return charset
+ return self.unknown_charset(charset)
+ return self.default_charset
+
+
+class DynamicCharsetResponseMixin(object):
+
+ """If this mixin is mixed into a response class it will provide
+ a dynamic `charset` attribute. This means that if the charset is
+ looked up and stored in the `Content-Type` header and updates
+ itself automatically. This also means a small performance hit but
+ can be useful if you're working with different charsets on
+ responses.
+
+ Because the charset attribute is no a property at class-level, the
+ default value is stored in `default_charset`.
+
+ Because it changes the behavior or :class:`Response` this class has
+ to be mixed in *before* the actual response class::
+
+ class MyResponse(DynamicCharsetResponseMixin, Response):
+ pass
+
+ .. deprecated:: 0.15
+ This mixin will be removed in version 1.0.
+
+ .. versionadded:: 0.6
+ """
+
+ #: the default charset.
+ default_charset = "utf-8"
+
+ def _get_charset(self):
+ warnings.warn(
+ "'werkzeug.contrib.wrappers.DynamicCharsetResponseMixin'"
+ " is deprecated as of version 0.15 and will be removed in"
+ " version 1.0.",
+ DeprecationWarning,
+ stacklevel=2,
+ )
+ header = self.headers.get("content-type")
+ if header:
+ charset = parse_options_header(header)[1].get("charset")
+ if charset:
+ return charset
+ return self.default_charset
+
+ def _set_charset(self, charset):
+ warnings.warn(
+ "'werkzeug.contrib.wrappers.DynamicCharsetResponseMixin'"
+ " is deprecated as of version 0.15 and will be removed in"
+ " version 1.0.",
+ DeprecationWarning,
+ stacklevel=2,
+ )
+ header = self.headers.get("content-type")
+ ct, options = parse_options_header(header)
+ if not ct:
+ raise TypeError("Cannot set charset if Content-Type header is missing.")
+ options["charset"] = charset
+ self.headers["Content-Type"] = dump_options_header(ct, options)
+
+ charset = property(
+ _get_charset,
+ _set_charset,
+ doc="""
+ The charset for the response. It's stored inside the
+ Content-Type header as a parameter.""",
+ )
+ del _get_charset, _set_charset