Repository: praw-dev/praw

Issue:
Add support for mod.comment.lock
## Feature Request

Reddit has recently added a way for mods to lock individual comments. PRAW should support this feature, ideally under comment.mod.lock

https://www.reddit.com/r/modnews/comments/brgr8i/moderators_you_may_now_lock_individual_comments/eodrqq5/

https://www.reddit.com/dev/api#POST_api_lock


Discussion:

Resolving patch:
diff --git a/praw/models/reddit/mixins/__init__.py b/praw/models/reddit/mixins/__init__.py
--- a/praw/models/reddit/mixins/__init__.py
+++ b/praw/models/reddit/mixins/__init__.py
@@ -97,6 +97,25 @@ def ignore_reports(self):
             API_PATH["ignore_reports"], data={"id": self.thing.fullname}
         )
 
+    def lock(self):
+        """Lock the a :class:`~.Comment` or :class:`~.Submission`.
+
+        Example usage:
+
+           # lock a comment:
+           comment = reddit.comment('dkk4qjd')
+           comment.mod.lock()
+           # lock a submission:
+           submission = reddit.submission(id='5or86n')
+           submission.mod.lock()
+
+        See also :meth:`~.unlock`
+
+        """
+        self.thing._reddit.post(
+            API_PATH["lock"], data={"id": self.thing.fullname}
+        )
+
     def remove(self, spam=False):
         """Remove a :class:`~.Comment` or :class:`~.Submission`.
 
@@ -199,6 +218,25 @@ def unignore_reports(self):
             API_PATH["unignore_reports"], data={"id": self.thing.fullname}
         )
 
+    def unlock(self):
+        """Unlock the a :class:`~.Comment` or :class:`~.Submission`.
+
+        Example usage:
+
+           # unlock a comment:
+           comment = reddit.comment('dkk4qjd')
+           comment.mod.unlock()
+           # unlock a submission:
+           submission = reddit.submission(id='5or86n')
+           submission.mod.unlock()
+
+        See also :meth:`~.lock`
+
+        """
+        self.thing._reddit.post(
+            API_PATH["unlock"], data={"id": self.thing.fullname}
+        )
+
 
 class UserContentMixin(
     EditableMixin,
diff --git a/praw/models/reddit/submission.py b/praw/models/reddit/submission.py
--- a/praw/models/reddit/submission.py
+++ b/praw/models/reddit/submission.py
@@ -465,23 +465,6 @@ def flair(self, text="", css_class=""):
         url = API_PATH["flair"].format(subreddit=self.thing.subreddit)
         self.thing._reddit.post(url, data=data)
 
-    def lock(self):
-        """Lock the submission.
-
-        Example usage:
-
-        .. code:: python
-
-           submission = reddit.submission(id='5or86n')
-           submission.mod.lock()
-
-        See also :meth:`~.unlock`
-
-        """
-        self.thing._reddit.post(
-            API_PATH["lock"], data={"id": self.thing.fullname}
-        )
-
     def nsfw(self):
         """Mark as not safe for work.
 
@@ -582,23 +565,6 @@ def suggested_sort(self, sort="blank"):
             data={"id": self.thing.fullname, "sort": sort},
         )
 
-    def unlock(self):
-        """Unlock the submission.
-
-        Example:
-
-        .. code:: python
-
-           submission = reddit.submission(id='5or86n')
-           submission.mod.unlock()
-
-        See also :meth:`~.lock`
-
-        """
-        self.thing._reddit.post(
-            API_PATH["unlock"], data={"id": self.thing.fullname}
-        )
-
     def unspoiler(self):
         """Indicate that the submission does not contain spoilers.
 


Test patch:
diff --git a/tests/integration/cassettes/TestCommentModeration.test_lock.json b/tests/integration/cassettes/TestCommentModeration.test_lock.json
new file mode 100644
--- /dev/null
+++ b/tests/integration/cassettes/TestCommentModeration.test_lock.json
@@ -0,0 +1,227 @@
+{
+  "http_interactions": [
+    {
+      "recorded_at": "2019-05-22T15:52:05",
+      "request": {
+        "body": {
+          "encoding": "utf-8",
+          "string": "grant_type=password&password=<PASSWORD>&username=<USERNAME>"
+        },
+        "headers": {
+          "Accept": [
+            "*/*"
+          ],
+          "Accept-Encoding": [
+            "identity"
+          ],
+          "Authorization": [
+            "Basic <BASIC_AUTH>"
+          ],
+          "Connection": [
+            "keep-alive"
+          ],
+          "Content-Length": [
+            "59"
+          ],
+          "Content-Type": [
+            "application/x-www-form-urlencoded"
+          ],
+          "User-Agent": [
+            "<USER_AGENT> PRAW/6.2.1dev0 prawcore/1.0.1"
+          ]
+        },
+        "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": [
+            "113"
+          ],
+          "Content-Type": [
+            "application/json; charset=UTF-8"
+          ],
+          "Date": [
+            "Wed, 22 May 2019 15:52:06 GMT"
+          ],
+          "Server": [
+            "snooserv"
+          ],
+          "Set-Cookie": [
+            "edgebucket=bMCMWOqlVrSegOc1cS; 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-lax8626-LAX"
+          ],
+          "X-Timer": [
+            "S1558540326.885250,VS0,VE342"
+          ],
+          "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": "2019-05-22T15:52:05",
+      "request": {
+        "body": {
+          "encoding": "utf-8",
+          "string": "api_type=json&id=t1_da2g6ne"
+        },
+        "headers": {
+          "Accept": [
+            "*/*"
+          ],
+          "Accept-Encoding": [
+            "identity"
+          ],
+          "Authorization": [
+            "bearer <ACCESS_TOKEN>"
+          ],
+          "Connection": [
+            "keep-alive"
+          ],
+          "Content-Length": [
+            "27"
+          ],
+          "Content-Type": [
+            "application/x-www-form-urlencoded"
+          ],
+          "Cookie": [
+            "edgebucket=bMCMWOqlVrSegOc1cS"
+          ],
+          "User-Agent": [
+            "<USER_AGENT> PRAW/6.2.1dev0 prawcore/1.0.1"
+          ]
+        },
+        "method": "POST",
+        "uri": "https://oauth.reddit.com/api/lock/?raw_json=1"
+      },
+      "response": {
+        "body": {
+          "encoding": "UTF-8",
+          "string": "{}"
+        },
+        "headers": {
+          "Accept-Ranges": [
+            "bytes"
+          ],
+          "Connection": [
+            "keep-alive"
+          ],
+          "Content-Length": [
+            "2"
+          ],
+          "Content-Type": [
+            "application/json; charset=UTF-8"
+          ],
+          "Date": [
+            "Wed, 22 May 2019 15:52:06 GMT"
+          ],
+          "Server": [
+            "snooserv"
+          ],
+          "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-lax8638-LAX"
+          ],
+          "X-Timer": [
+            "S1558540326.368332,VS0,VE236"
+          ],
+          "cache-control": [
+            "private, s-maxage=0, max-age=0, must-revalidate, max-age=0, must-revalidate"
+          ],
+          "expires": [
+            "-1"
+          ],
+          "set-cookie": [
+            "loid=00000000000004cixf.2.1284611485984.Z0FBQUFBQmM1WEFtQk9KOUs1bE9sTG1TYWlXemp5Z3M5aW9oOGszekxuUlBGZG83RU9tQ21tZVgtMjI2RGhxbk14ZWsxal9VM0VaU2hHbjl1UG1mbTJWTUFQRUN6V3QtaEIwd0N3OUliV3dfcHkzNkpNdXhnVGh6Wk05NFhsQTVKcTZxeGMxSmZoc1M; Domain=reddit.com; Max-Age=63071999; Path=/; expires=Fri, 21-May-2021 15:52:06 GMT; secure",
+            "redesign_optout=true; Domain=reddit.com; Max-Age=94607999; Path=/; expires=Sat, 21-May-2022 15:52:06 GMT; secure",
+            "session_tracker=u9bhuENu946fostVDd.0.1558540326427.Z0FBQUFBQmM1WEFtUkNvbjhaamttZmNIZnM1Q3JzTXRwU1pUQnVzaThmZ09FaXRlT21OOXVLNDRDbjU1WUI2YmxzcmRzNEJabmdjMmlmOTZwYW5QaERuX29ENEF6d1RiUXprTmVvblZCMmtrNUp4b2lNQ0JDZE5DdEo0RDZGVlplb0MtZ0tTem0wSVc; Domain=reddit.com; Max-Age=7199; Path=/; expires=Wed, 22-May-2019 17:52:06 GMT; secure"
+          ],
+          "x-content-type-options": [
+            "nosniff"
+          ],
+          "x-frame-options": [
+            "SAMEORIGIN"
+          ],
+          "x-ratelimit-remaining": [
+            "599.0"
+          ],
+          "x-ratelimit-reset": [
+            "474"
+          ],
+          "x-ratelimit-used": [
+            "1"
+          ],
+          "x-ua-compatible": [
+            "IE=edge"
+          ],
+          "x-xss-protection": [
+            "1; mode=block"
+          ]
+        },
+        "status": {
+          "code": 200,
+          "message": "OK"
+        },
+        "url": "https://oauth.reddit.com/api/lock/?raw_json=1"
+      }
+    }
+  ],
+  "recorded_with": "betamax/0.8.1"
+}
\ No newline at end of file
diff --git a/tests/integration/cassettes/TestCommentModeration.test_unlock.json b/tests/integration/cassettes/TestCommentModeration.test_unlock.json
new file mode 100644
--- /dev/null
+++ b/tests/integration/cassettes/TestCommentModeration.test_unlock.json
@@ -0,0 +1,227 @@
+{
+  "http_interactions": [
+    {
+      "recorded_at": "2019-05-22T15:52:06",
+      "request": {
+        "body": {
+          "encoding": "utf-8",
+          "string": "grant_type=password&password=<PASSWORD>&username=<USERNAME>"
+        },
+        "headers": {
+          "Accept": [
+            "*/*"
+          ],
+          "Accept-Encoding": [
+            "identity"
+          ],
+          "Authorization": [
+            "Basic <BASIC_AUTH>"
+          ],
+          "Connection": [
+            "keep-alive"
+          ],
+          "Content-Length": [
+            "59"
+          ],
+          "Content-Type": [
+            "application/x-www-form-urlencoded"
+          ],
+          "User-Agent": [
+            "<USER_AGENT> PRAW/6.2.1dev0 prawcore/1.0.1"
+          ]
+        },
+        "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": [
+            "113"
+          ],
+          "Content-Type": [
+            "application/json; charset=UTF-8"
+          ],
+          "Date": [
+            "Wed, 22 May 2019 15:52:07 GMT"
+          ],
+          "Server": [
+            "snooserv"
+          ],
+          "Set-Cookie": [
+            "edgebucket=EBXrAAR5ViSASNUG8t; 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-lax8650-LAX"
+          ],
+          "X-Timer": [
+            "S1558540327.731773,VS0,VE339"
+          ],
+          "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": "2019-05-22T15:52:06",
+      "request": {
+        "body": {
+          "encoding": "utf-8",
+          "string": "api_type=json&id=t1_da2g6ne"
+        },
+        "headers": {
+          "Accept": [
+            "*/*"
+          ],
+          "Accept-Encoding": [
+            "identity"
+          ],
+          "Authorization": [
+            "bearer <ACCESS_TOKEN>"
+          ],
+          "Connection": [
+            "keep-alive"
+          ],
+          "Content-Length": [
+            "27"
+          ],
+          "Content-Type": [
+            "application/x-www-form-urlencoded"
+          ],
+          "Cookie": [
+            "edgebucket=EBXrAAR5ViSASNUG8t"
+          ],
+          "User-Agent": [
+            "<USER_AGENT> PRAW/6.2.1dev0 prawcore/1.0.1"
+          ]
+        },
+        "method": "POST",
+        "uri": "https://oauth.reddit.com/api/unlock/?raw_json=1"
+      },
+      "response": {
+        "body": {
+          "encoding": "UTF-8",
+          "string": "{}"
+        },
+        "headers": {
+          "Accept-Ranges": [
+            "bytes"
+          ],
+          "Connection": [
+            "keep-alive"
+          ],
+          "Content-Length": [
+            "2"
+          ],
+          "Content-Type": [
+            "application/json; charset=UTF-8"
+          ],
+          "Date": [
+            "Wed, 22 May 2019 15:52:07 GMT"
+          ],
+          "Server": [
+            "snooserv"
+          ],
+          "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-lax8623-LAX"
+          ],
+          "X-Timer": [
+            "S1558540327.155779,VS0,VE228"
+          ],
+          "cache-control": [
+            "private, s-maxage=0, max-age=0, must-revalidate, max-age=0, must-revalidate"
+          ],
+          "expires": [
+            "-1"
+          ],
+          "set-cookie": [
+            "loid=00000000000004cixf.2.1284611485984.Z0FBQUFBQmM1WEFuR3FZWDhERTFpVU43dXV2ckFGUEc0NmhEUlFtSFdyWmpHTkdzdHQ2LXlnM2IzVFJEMXFiRG9JS2QzU3E5RVVPLUVqU1hSZ1JMdFpkbmdVN3p4OU9kbVBXcTJlNzZtTHJZWmZobEZkZHlUNV9QLU1renFCUS1WY2tqbEw1RFZXd20; Domain=reddit.com; Max-Age=63071999; Path=/; expires=Fri, 21-May-2021 15:52:07 GMT; secure",
+            "redesign_optout=true; Domain=reddit.com; Max-Age=94607999; Path=/; expires=Sat, 21-May-2022 15:52:07 GMT; secure",
+            "session_tracker=3ni71uOVkRaZ8NvVM1.0.1558540327205.Z0FBQUFBQmM1WEFubGR2ci1CNTZEQ1ljOHV3UGtmanc4QWczakl0TE5jajhINDhIVHpGNVBmSXVzRnpweWVsNW1tWWx5Z1FFTVVsb1kzM1ZueDhJTkFrdTRCM2ZSMXRkd2pubWExMGRVaVJ4UWZzOC01dWdaMnNxbXpTU1Fwei1zU2VKNXo1Q083aGM; Domain=reddit.com; Max-Age=7199; Path=/; expires=Wed, 22-May-2019 17:52:07 GMT; secure"
+          ],
+          "x-content-type-options": [
+            "nosniff"
+          ],
+          "x-frame-options": [
+            "SAMEORIGIN"
+          ],
+          "x-ratelimit-remaining": [
+            "598.0"
+          ],
+          "x-ratelimit-reset": [
+            "473"
+          ],
+          "x-ratelimit-used": [
+            "2"
+          ],
+          "x-ua-compatible": [
+            "IE=edge"
+          ],
+          "x-xss-protection": [
+            "1; mode=block"
+          ]
+        },
+        "status": {
+          "code": 200,
+          "message": "OK"
+        },
+        "url": "https://oauth.reddit.com/api/unlock/?raw_json=1"
+      }
+    }
+  ],
+  "recorded_with": "betamax/0.8.1"
+}
\ No newline at end of file
diff --git a/tests/integration/models/reddit/test_comment.py b/tests/integration/models/reddit/test_comment.py
--- a/tests/integration/models/reddit/test_comment.py
+++ b/tests/integration/models/reddit/test_comment.py
@@ -267,11 +267,21 @@ def test_ignore_reports(self):
         ):
             self.reddit.comment("da2g5y6").mod.ignore_reports()
 
+    def test_lock(self):
+        self.reddit.read_only = False
+        with self.recorder.use_cassette("TestCommentModeration.test_lock"):
+            Comment(self.reddit, "da2g6ne").mod.lock()
+
     def test_remove(self):
         self.reddit.read_only = False
         with self.recorder.use_cassette("TestCommentModeration.test_remove"):
             self.reddit.comment("da2g5y6").mod.remove(spam=True)
 
+    def test_unlock(self):
+        self.reddit.read_only = False
+        with self.recorder.use_cassette("TestCommentModeration.test_unlock"):
+            Comment(self.reddit, "da2g6ne").mod.unlock()
+
     @mock.patch("time.sleep", return_value=None)
     def test_send_removal_message(self, _):
         self.reddit.read_only = False