From 44ac64aa7235362acbb96e0f12aa27534580e575 Mon Sep 17 00:00:00 2001 From: Maximilian Hils Date: Wed, 18 May 2016 18:46:42 -0700 Subject: add MultiDict This commit introduces MultiDict, a multi-dictionary similar to ODict, but with improved semantics (as in the Headers class). MultiDict fixes a few issues that were present in the Request/Response API. In particular, `request.cookies["foo"] = "bar"` has previously been a no-op, as the cookies property returned a mutable _copy_ of the cookies. --- netlib/http/message.py | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) (limited to 'netlib/http/message.py') diff --git a/netlib/http/message.py b/netlib/http/message.py index da9681a0..262ef3e1 100644 --- a/netlib/http/message.py +++ b/netlib/http/message.py @@ -4,6 +4,7 @@ import warnings import six +from ..multidict import MultiDict from .headers import Headers from .. import encoding, utils @@ -235,3 +236,37 @@ class decoded(object): def __exit__(self, type, value, tb): if self.ce: self.message.encode(self.ce) + + +class MessageMultiDict(MultiDict): + """ + A MultiDict that provides a proxy view to the underlying message. + """ + + def __init__(self, attr, message): + if False: + # We do not want to call the parent constructor here as that + # would cause an unnecessary parse/unparse pass. + # This is here to silence linters. Message + super(MessageMultiDict, self).__init__(None) + self._attr = attr + self._message = message # type: Message + + @staticmethod + def _kconv(key): + # All request-attributes are case-sensitive. + return key + + @staticmethod + def _reduce_values(values): + # We just return the first element if + # multiple elements exist with the same key. + return values[0] + + @property + def fields(self): + return getattr(self._message, "_" + self._attr) + + @fields.setter + def fields(self, value): + setattr(self._message, self._attr, value) -- cgit v1.2.3 From 6f8db2d7eb32684a8328e0ae8bdd73eceb861707 Mon Sep 17 00:00:00 2001 From: Maximilian Hils Date: Wed, 18 May 2016 22:50:19 -0700 Subject: improve MultiDict, add ImmutableMultiDict, adjust response.cookies --- netlib/http/message.py | 41 ++++++++++++++++++++++++++++++++++++++--- 1 file changed, 38 insertions(+), 3 deletions(-) (limited to 'netlib/http/message.py') diff --git a/netlib/http/message.py b/netlib/http/message.py index 262ef3e1..3c731ea6 100644 --- a/netlib/http/message.py +++ b/netlib/http/message.py @@ -238,9 +238,44 @@ class decoded(object): self.message.encode(self.ce) -class MessageMultiDict(MultiDict): +class MultiDictView(MultiDict): """ - A MultiDict that provides a proxy view to the underlying message. + Some parts in HTTP (Cookies, URL query strings, ...) require a specific data structure: A MultiDict. + It behaves mostly like an ordered dict but it can have several values for the same key. + + The MultiDictView provides a MultiDict *view* on an :py:class:`Request` or :py:class:`Response`. + That is, it represents a part of the request as a MultiDict, but doesn't contain state/data themselves. + + For example, ``request.cookies`` provides a view on the ``Cookie: ...`` header. + Any change to ``request.cookies`` will also modify the ``Cookie`` header. + Any change to the ``Cookie`` header will also modify ``request.cookies``. + + Example: + + .. code-block:: python + + # Cookies are represented as a MultiDict. + >>> request.cookies + MultiDictView[("name", "value"), ("a", "false"), ("a", "42")] + + # MultiDicts mostly behave like a normal dict. + >>> request.cookies["name"] + "value" + + # If there is more than one value, only the first value is returned. + >>> request.cookies["a"] + "false" + + # `.get_all(key)` returns a list of all values. + >>> request.cookies.get_all("a") + ["false", "42"] + + # Changes to the headers are immediately reflected in the cookies. + >>> request.cookies + MultiDictView[("name", "value"), ...] + >>> del request.headers["Cookie"] + >>> request.cookies + MultiDictView[] # empty now """ def __init__(self, attr, message): @@ -248,7 +283,7 @@ class MessageMultiDict(MultiDict): # We do not want to call the parent constructor here as that # would cause an unnecessary parse/unparse pass. # This is here to silence linters. Message - super(MessageMultiDict, self).__init__(None) + super(MultiDictView, self).__init__(None) self._attr = attr self._message = message # type: Message -- cgit v1.2.3 From b538138ead1dc8550f2d4e4a3f30ff70abb95f53 Mon Sep 17 00:00:00 2001 From: Maximilian Hils Date: Fri, 20 May 2016 11:04:27 -0700 Subject: tests++ --- netlib/http/message.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'netlib/http/message.py') diff --git a/netlib/http/message.py b/netlib/http/message.py index 3c731ea6..db4054b1 100644 --- a/netlib/http/message.py +++ b/netlib/http/message.py @@ -279,7 +279,7 @@ class MultiDictView(MultiDict): """ def __init__(self, attr, message): - if False: + if False: # pragma: no cover # We do not want to call the parent constructor here as that # would cause an unnecessary parse/unparse pass. # This is here to silence linters. Message -- cgit v1.2.3 From a5c4cd034081d7dcdbd4b46bd69718edb45d4719 Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Sat, 21 May 2016 11:37:36 +1200 Subject: A clearer implementation of MultiDictView This makes MultiDictView work with a simple getter/setter pair, rather than using attributes with implicit leading underscores. Also move MultiDictView into multidict.py and adds some simple unit tests. --- netlib/http/message.py | 69 -------------------------------------------------- 1 file changed, 69 deletions(-) (limited to 'netlib/http/message.py') diff --git a/netlib/http/message.py b/netlib/http/message.py index db4054b1..9b0180cf 100644 --- a/netlib/http/message.py +++ b/netlib/http/message.py @@ -236,72 +236,3 @@ class decoded(object): def __exit__(self, type, value, tb): if self.ce: self.message.encode(self.ce) - - -class MultiDictView(MultiDict): - """ - Some parts in HTTP (Cookies, URL query strings, ...) require a specific data structure: A MultiDict. - It behaves mostly like an ordered dict but it can have several values for the same key. - - The MultiDictView provides a MultiDict *view* on an :py:class:`Request` or :py:class:`Response`. - That is, it represents a part of the request as a MultiDict, but doesn't contain state/data themselves. - - For example, ``request.cookies`` provides a view on the ``Cookie: ...`` header. - Any change to ``request.cookies`` will also modify the ``Cookie`` header. - Any change to the ``Cookie`` header will also modify ``request.cookies``. - - Example: - - .. code-block:: python - - # Cookies are represented as a MultiDict. - >>> request.cookies - MultiDictView[("name", "value"), ("a", "false"), ("a", "42")] - - # MultiDicts mostly behave like a normal dict. - >>> request.cookies["name"] - "value" - - # If there is more than one value, only the first value is returned. - >>> request.cookies["a"] - "false" - - # `.get_all(key)` returns a list of all values. - >>> request.cookies.get_all("a") - ["false", "42"] - - # Changes to the headers are immediately reflected in the cookies. - >>> request.cookies - MultiDictView[("name", "value"), ...] - >>> del request.headers["Cookie"] - >>> request.cookies - MultiDictView[] # empty now - """ - - def __init__(self, attr, message): - if False: # pragma: no cover - # We do not want to call the parent constructor here as that - # would cause an unnecessary parse/unparse pass. - # This is here to silence linters. Message - super(MultiDictView, self).__init__(None) - self._attr = attr - self._message = message # type: Message - - @staticmethod - def _kconv(key): - # All request-attributes are case-sensitive. - return key - - @staticmethod - def _reduce_values(values): - # We just return the first element if - # multiple elements exist with the same key. - return values[0] - - @property - def fields(self): - return getattr(self._message, "_" + self._attr) - - @fields.setter - def fields(self, value): - setattr(self._message, self._attr, value) -- cgit v1.2.3