Repository: praw-dev/praw

Issue:
Accessing wiki revisions by ID
I was thinking more about #907 and I think there ought to be a better way to access a wiki page directly by its revision ID. Right now you can get a page's revisions and that listing will have a bunch of appropriately-initialized `WikiPage` objects, but if you (for example) store some IDs in a database then the ways I can think of to get a revision don't sound ideal:

1. Get a `WikiPage` the normal way, then change its `_revision` attribute before it fetches. This kills the abstraction boundary.
2. Initialize `praw.models.WikiPage` directly. This goes against PRAW's usual idiom of using factory methods rather than directly using constructors in client code.

One possible alternative would be to provide a `revision` method on `WikiPage`, so that you can use as in:

    revision = reddit.subreddit('test').wiki['praw_test'].revision('FAKE-ID')


Discussion:
Love it. Would you like to implement?

Resolving patch:
diff --git a/praw/models/reddit/wikipage.py b/praw/models/reddit/wikipage.py
--- a/praw/models/reddit/wikipage.py
+++ b/praw/models/reddit/wikipage.py
@@ -81,6 +81,19 @@ def edit(self, content, reason=None, **other_settings):
         self._reddit.post(API_PATH['wiki_edit'].format(
             subreddit=self.subreddit), data=other_settings)
 
+    def revision(self, revision):
+        """Return a specific version of this page by revision ID.
+
+        To view revision ``[ID]`` of ``'praw_test'`` in ``'/r/test'``:
+
+        .. code:: python
+
+           page = reddit.subreddit('test').wiki['praw_test'].revision('[ID]')
+
+        """
+        return WikiPage(self.subreddit._reddit, self.subreddit, self.name,
+                        revision)
+
     def revisions(self, **generator_kwargs):
         """Return a generator for page revisions.
 


Test patch:
diff --git a/tests/integration/cassettes/TestWikiPage.test_revision.json b/tests/integration/cassettes/TestWikiPage.test_revision.json
new file mode 100644
--- /dev/null
+++ b/tests/integration/cassettes/TestWikiPage.test_revision.json
@@ -0,0 +1,110 @@
+{
+  "http_interactions": [
+    {
+      "recorded_at": "2018-04-05T01:21:03",
+      "request": {
+        "body": {
+          "encoding": "utf-8",
+          "string": "grant_type=client_credentials"
+        },
+        "headers": {
+          "Accept": "*/*",
+          "Accept-Encoding": "identity",
+          "Authorization": "Basic <BASIC_AUTH>",
+          "Connection": "keep-alive",
+          "Content-Length": "29",
+          "Content-Type": "application/x-www-form-urlencoded",
+          "User-Agent": "<USER_AGENT> PRAW/5.5.0.dev0 prawcore/0.15.0"
+        },
+        "method": "POST",
+        "uri": "https://www.reddit.com/api/v1/access_token"
+      },
+      "response": {
+        "body": {
+          "encoding": "UTF-8",
+          "string": "{\"access_token\": \"<ACCESS_TOKEN>\", \"token_type\": \"bearer\", \"expires_in\": 3600, \"scope\": \"*\"}"
+        },
+        "headers": {
+          "Accept-Ranges": "bytes",
+          "Connection": "keep-alive",
+          "Content-Length": "105",
+          "Content-Type": "application/json; charset=UTF-8",
+          "Date": "Thu, 05 Apr 2018 01:21:03 GMT",
+          "Server": "snooserv",
+          "Set-Cookie": "edgebucket=MqoSb4D9QYEnZawwkW; Domain=reddit.com; Max-Age=63071999; Path=/;  secure",
+          "Strict-Transport-Security": "max-age=15552000; includeSubDomains; preload",
+          "Via": "1.1 varnish",
+          "X-Cache": "MISS",
+          "X-Cache-Hits": "0",
+          "X-Moose": "majestic",
+          "X-Served-By": "cache-mdw17324-MDW",
+          "X-Timer": "S1522891263.363098,VS0,VE35",
+          "cache-control": "max-age=0, must-revalidate",
+          "x-content-type-options": "nosniff",
+          "x-frame-options": "SAMEORIGIN",
+          "x-xss-protection": "1; mode=block"
+        },
+        "status": {
+          "code": 200,
+          "message": "OK"
+        },
+        "url": "https://www.reddit.com/api/v1/access_token"
+      }
+    },
+    {
+      "recorded_at": "2018-04-05T01:21:03",
+      "request": {
+        "body": {
+          "encoding": "utf-8",
+          "string": ""
+        },
+        "headers": {
+          "Accept": "*/*",
+          "Accept-Encoding": "identity",
+          "Authorization": "bearer <ACCESS_TOKEN>",
+          "Connection": "keep-alive",
+          "Cookie": "edgebucket=MqoSb4D9QYEnZawwkW",
+          "User-Agent": "<USER_AGENT> PRAW/5.5.0.dev0 prawcore/0.15.0"
+        },
+        "method": "GET",
+        "uri": "https://oauth.reddit.com/r/<TEST_SUBREDDIT>/wiki/index?v=f31e1988-07d0-11e6-b927-0ea0743a0543&raw_json=1"
+      },
+      "response": {
+        "body": {
+          "encoding": "UTF-8",
+          "string": "{\"kind\": \"wikipage\", \"data\": {\"may_revise\": false, \"revision_date\": 1461279269.0, \"content_html\": \"\\u003C!-- SC_OFF --\\u003E\\u003Cdiv class=\\\"md wiki\\\"\\u003E\\u003Cdiv class=\\\"toc\\\"\\u003E\\u003Cul\\u003E\\u003Cli class=\\\"wiki_welcome_to_.2Fr.2F<TEST_SUBREDDIT>\\\"\\u003E\\u003Ca href=\\\"#wiki_welcome_to_.2Fr.2F<TEST_SUBREDDIT>\\\"\\u003EWelcome to /r/<TEST_SUBREDDIT>\\u003C/a\\u003E\\u003C/li\\u003E\\u003C/ul\\u003E\\u003C/div\\u003E\\u003Ch1 id=\\\"wiki_welcome_to_.2Fr.2F<TEST_SUBREDDIT>\\\"\\u003EWelcome to \\u003Ca href=\\\"/r/<TEST_SUBREDDIT>\\\" rel=\\\"nofollow\\\"\\u003E/r/<TEST_SUBREDDIT>\\u003C/a\\u003E\\u003C/h1\\u003E\\n\\u003Cp\\u003EIf you\\u2019re new to \\u003Ca href=\\\"/r/<TEST_SUBREDDIT>\\\" rel=\\\"nofollow\\\"\\u003E/r/<TEST_SUBREDDIT>\\u003C/a\\u003E, welcome! We\\u2019re a community dedicated to discussing <TEST_SUBREDDIT>-related topics, problems, and questions.\\u003C/p\\u003E\\n\\u003Cp\\u003E\\u003Cstrong\\u003E\\u003Ca href=\\\"http://www.reddit.com/r/<TEST_SUBREDDIT>/wiki/Introduction\\\" rel=\\\"nofollow\\\"\\u003EIntroduction\\u003C/a\\u003E\\u003C/strong\\u003E\\u003C/p\\u003E\\n\\u003Cp\\u003E\\u003Cstrong\\u003E\\u003Ca href=\\\"http://www.reddit.com/r/<TEST_SUBREDDIT>/wiki/FAQ\\\" rel=\\\"nofollow\\\"\\u003EFAQ\\u003C/a\\u003E\\u003C/strong\\u003E\\u003C/p\\u003E\\n\\u003Cp\\u003E\\u003Cstrong\\u003E\\u003Ca href=\\\"http://www.reddit.com/r/<TEST_SUBREDDIT>/wiki/ReadingList\\\" rel=\\\"nofollow\\\"\\u003EReading List\\u003C/a\\u003E\\u003C/strong\\u003E\\u003C/p\\u003E\\n\\u003Cp\\u003E\\u003Cstrong\\u003E\\u003Ca href=\\\"http://www.reddit.com/r/<TEST_SUBREDDIT>/wiki/SubLinks\\\" rel=\\\"nofollow\\\"\\u003EOther Philosophy Sub-Reddits\\u003C/a\\u003E\\u003C/strong\\u003E\\u003C/p\\u003E\\n\\u003C/div\\u003E\\u003C!-- SC_ON --\\u003E\", \"revision_by\": {\"kind\": \"t2\", \"data\": {\"is_employee\": false, \"icon_img\": \"https://www.redditstatic.com/avatars/avatar_default_17_FFD635.png\", \"pref_show_snoovatar\": false, \"name\": \"ADefiniteDescription\", \"is_friend\": false, \"created\": 1331890715.0, \"has_subscribed\": true, \"hide_from_robots\": true, \"created_utc\": 1331861915.0, \"link_karma\": 137480, \"comment_karma\": 112307, \"is_gold\": false, \"is_mod\": true, \"verified\": false, \"subreddit\": null, \"has_verified_email\": true, \"id\": \"76l9s\"}}, \"content_md\": \"#Welcome to /r/<TEST_SUBREDDIT>\\r\\n\\r\\nIf you\\u2019re new to /r/<TEST_SUBREDDIT>, welcome! We\\u2019re a community dedicated to discussing <TEST_SUBREDDIT>-related topics, problems, and questions.\\r\\n\\r\\n**[Introduction](http://www.reddit.com/r/<TEST_SUBREDDIT>/wiki/Introduction)**\\r\\n\\r\\n**[FAQ](http://www.reddit.com/r/<TEST_SUBREDDIT>/wiki/FAQ)**\\r\\n\\r\\n**[Reading List](http://www.reddit.com/r/<TEST_SUBREDDIT>/wiki/ReadingList)**\\r\\n\\r\\n**[Other Philosophy Sub-Reddits](http://www.reddit.com/r/<TEST_SUBREDDIT>/wiki/SubLinks)**\\r\\n\\r\\n\\r\\n\"}}"
+        },
+        "headers": {
+          "Accept-Ranges": "bytes",
+          "Connection": "keep-alive",
+          "Content-Length": "2586",
+          "Content-Type": "application/json; charset=UTF-8",
+          "Date": "Thu, 05 Apr 2018 01:21:03 GMT",
+          "Server": "snooserv",
+          "Strict-Transport-Security": "max-age=15552000; includeSubDomains; preload",
+          "Vary": "accept-encoding",
+          "Via": "1.1 varnish",
+          "X-Cache": "MISS",
+          "X-Cache-Hits": "0",
+          "X-Moose": "majestic",
+          "X-Served-By": "cache-ord1733-ORD",
+          "X-Timer": "S1522891264.537637,VS0,VE73",
+          "access-control-allow-origin": "*",
+          "access-control-expose-headers": "X-Moose",
+          "cache-control": "max-age=0, must-revalidate",
+          "x-content-type-options": "nosniff",
+          "x-frame-options": "SAMEORIGIN",
+          "x-robots-tag": "noindex, nofollow",
+          "x-ua-compatible": "IE=edge",
+          "x-xss-protection": "1; mode=block"
+        },
+        "status": {
+          "code": 200,
+          "message": "OK"
+        },
+        "url": "https://oauth.reddit.com/r/<TEST_SUBREDDIT>/wiki/index?v=f31e1988-07d0-11e6-b927-0ea0743a0543&raw_json=1"
+      }
+    }
+  ],
+  "recorded_with": "betamax/0.8.0"
+}
\ No newline at end of file
diff --git a/tests/integration/models/reddit/test_wikipage.py b/tests/integration/models/reddit/test_wikipage.py
--- a/tests/integration/models/reddit/test_wikipage.py
+++ b/tests/integration/models/reddit/test_wikipage.py
@@ -68,6 +68,15 @@ def test_revision_by(self):
         with self.recorder.use_cassette('TestWikiPage.test_revision_by'):
             assert isinstance(page.revision_by, Redditor)
 
+    def test_revision(self):
+        subreddit = self.reddit.subreddit(
+            pytest.placeholders.test_subreddit)
+        revision_id = 'f31e1988-07d0-11e6-b927-0ea0743a0543'
+        page = subreddit.wiki['index'].revision(revision_id)
+
+        with self.recorder.use_cassette('TestWikiPage.test_revision'):
+            assert len(page.content_md) > 0
+
     @mock.patch('time.sleep', return_value=None)
     def test_revisions(self, _):
         subreddit = self.reddit.subreddit(