Repository: aio-libs/aiohttp

Issue:
Refactor reify
1. Make reify really readonly property (now it allows setting property value).
2. Implement optional Cython accelerator for reify.
3. Request's properties `.host`, `.path` and `.query_string` should use `reify` instead of calculating values in constructor.

All of them simplifies `request.clone()` implementation.

See also #401 



Discussion:

Resolving patch:
diff --git a/aiohttp/helpers.py b/aiohttp/helpers.py
--- a/aiohttp/helpers.py
+++ b/aiohttp/helpers.py
@@ -290,6 +290,9 @@ def __getitem__(self, k):
             return '-'
 
 
+_marker = object()
+
+
 class reify:
     """Use as a class method decorator.  It operates almost exactly like
     the Python ``@property`` decorator, but it puts the result of the
@@ -305,10 +308,17 @@ def __init__(self, wrapped):
             self.__doc__ = wrapped.__doc__
         except:  # pragma: no cover
             pass
+        self.name = wrapped.__name__
 
-    def __get__(self, inst, objtype=None):
+    def __get__(self, inst, owner, _marker=_marker):
         if inst is None:
             return self
+        val = inst.__dict__.get(self.name, _marker)
+        if val is not _marker:
+            return val
         val = self.wrapped(inst)
-        setattr(inst, self.wrapped.__name__, val)
+        inst.__dict__[self.name] = val
         return val
+
+    def __set__(self, inst, value):
+        raise AttributeError("reified property is read-only")
diff --git a/aiohttp/web_reqrep.py b/aiohttp/web_reqrep.py
--- a/aiohttp/web_reqrep.py
+++ b/aiohttp/web_reqrep.py
@@ -92,9 +92,6 @@ def __init__(self, app, message, payload, transport, reader, writer, *,
         self._method = message.method
         self._host = message.headers.get(_HOST)
         self._path_qs = message.path
-        res = urlsplit(message.path)
-        self._path = unquote(res.path)
-        self._query_string = res.query
         self._post = None
         self._post_files_cache = None
         self._headers = CIMultiDictProxy(message.headers)
@@ -162,21 +159,25 @@ def path_qs(self):
         """
         return self._path_qs
 
+    @reify
+    def _splitted_path(self):
+        return urlsplit(self._path_qs)
+
     @property
     def path(self):
         """The URL including *PATH INFO* without the host or scheme.
 
         E.g., ``/app/blog``
         """
-        return self._path
+        return unquote(self._splitted_path.path)
 
-    @property
+    @reify
     def query_string(self):
         """The query string in the URL.
 
         E.g., id=10
         """
-        return self._query_string
+        return self._splitted_path.query
 
     @reify
     def GET(self):
@@ -184,7 +185,7 @@ def GET(self):
 
         Lazy property.
         """
-        return MultiDictProxy(MultiDict(parse_qsl(self._query_string)))
+        return MultiDictProxy(MultiDict(parse_qsl(self.query_string)))
 
     @reify
     def POST(self):


Test patch:
diff --git a/tests/test_helpers.py b/tests/test_helpers.py
--- a/tests/test_helpers.py
+++ b/tests/test_helpers.py
@@ -104,6 +104,17 @@ def prop(self):
         self.assertIsInstance(A.prop, helpers.reify)
         self.assertEqual('Docstring.', A.prop.__doc__)
 
+    def test_reify_assignment(self):
+        class A:
+            @helpers.reify
+            def prop(self):
+                return 1
+
+        a = A()
+
+        with self.assertRaises(AttributeError):
+            a.prop = 123
+
 
 class TestSafeAtoms(unittest.TestCase):