Repository: getpelican/pelican

Issue:
More flexible page content testing for unit tests
- [x] I have searched the [issues](https://github.com/getpelican/pelican/issues?q=is%3Aissue) (including closed ones) and believe that this is not a duplicate.
- [x] I have searched the [documentation](https://docs.getpelican.com/) and believe that my question is not covered.
- [ ] I am willing to lend a hand to help implement this feature. <!-- optional but encouraged -->

## Feature Request

I am currently working on automated tests for a Pelican plugin. While documentation on this appears to be quite sparse and I took some of the other plugins for inspiration, especially testing the rendered page content itself seems to impose some issues for me.

The plugin will transform the source files to support custom types of blocks. To test this (and some intermediate transformation steps), I tried using `Content.content`, but this appears to be impossible for now, as this will do hard caching: https://github.com/getpelican/pelican/blob/b473280eac576705bc39b8151d27a1e05040d0e8/pelican/contents.py#L386-L396 While I could use `Content._content` as in the plugin itself, the high-level API feels much more suitable for testing purposes.

For this reason, it would be nice to somehow be able to reset the cached value of `pelican.contents.Content.get_content` (which uses `pelican.utils.memoized`) like Python allows for `functools.lru_cache` and `functools.cached_property` for example.


Discussion:
I did not test it but changing the `__get__` in `memoized`: https://github.com/getpelican/pelican/blob/4db5c7ca4b4903d6fbebc406f922419a6a2bb283/pelican/utils.py#L156-L158
to something like:
```python
    def get(self, obj, objtype):
        fn = partial(self.__call__, obj)
        fn.cache = self.cache
        return fn
```
should (in theory) allow for something like

```python
assert some_article.content == "foo"
some_article._content = "bar"
some_article.get_content.cache.clear()
assert some_article.content == "bar"
```
Thanks for the snippet. I just gave it a quick try by modifying my local *pelican* distribution and it seems to work as intended.
@FriedrichFroebel: Glad to hear that the suggestion from @avaris appears to address your target use case. Would you be willing to submit a pull request that implements these changes?


Resolving patch:
diff --git a/pelican/utils.py b/pelican/utils.py
--- a/pelican/utils.py
+++ b/pelican/utils.py
@@ -155,7 +155,9 @@ def __repr__(self):
 
     def __get__(self, obj, objtype):
         '''Support instance methods.'''
-        return partial(self.__call__, obj)
+        fn = partial(self.__call__, obj)
+        fn.cache = self.cache
+        return fn
 
 
 def deprecated_attribute(old, new, since=None, remove=None, doc=None):


Test patch:
diff --git a/pelican/tests/test_utils.py b/pelican/tests/test_utils.py
--- a/pelican/tests/test_utils.py
+++ b/pelican/tests/test_utils.py
@@ -860,3 +860,34 @@ def test_pass_deep_subpaths(self):
             utils.posixize_path(
                 os.path.abspath(os.path.join("/foo/bar", "test")))
         )
+
+
+class TestMemoized(unittest.TestCase):
+    def test_memoized(self):
+        class Container:
+            def _get(self, key):
+                pass
+
+            @utils.memoized
+            def get(self, key):
+                return self._get(key)
+
+        container = Container()
+
+        with unittest.mock.patch.object(
+                container, "_get", side_effect=lambda x: x
+        ) as get_mock:
+            self.assertEqual("foo", container.get("foo"))
+            get_mock.assert_called_once_with("foo")
+
+            get_mock.reset_mock()
+            self.assertEqual("foo", container.get("foo"))
+            get_mock.assert_not_called()
+
+            self.assertEqual("bar", container.get("bar"))
+            get_mock.assert_called_once_with("bar")
+
+            get_mock.reset_mock()
+            container.get.cache.clear()
+            self.assertEqual("bar", container.get("bar"))
+            get_mock.assert_called_once_with("bar")