Repository: praw-dev/praw

Issue:
Make Subreddit.flair callable
SubredditFlair instances should be callable so that single Redditors can be passed in, as well as other parameters such as limit.

This change should be similar to: https://github.com/praw-dev/praw/commit/483be01730ed3b37e2dae3fd8fb7bb137729cb10

See: https://www.reddit.com/r/redditdev/comments/5j5ypc/praw4_attributeerror_subreddit_object_has_no/dbdufv1/?context=3


Discussion:

Resolving patch:
diff --git a/praw/models/reddit/subreddit.py b/praw/models/reddit/subreddit.py
--- a/praw/models/reddit/subreddit.py
+++ b/praw/models/reddit/subreddit.py
@@ -143,7 +143,7 @@ def flair(self):
 
         .. code-block:: python
 
-           for flair in reddit.subreddit('NAME').flair:
+           for flair in reddit.subreddit('NAME').flair():
                print(flair)
 
         """
@@ -495,6 +495,19 @@ def remove(self, subreddit):
 class SubredditFlair(object):
     """Provide a set of functions to interact with a Subreddit's flair."""
 
+    def __call__(self, redditor=None, **generator_kwargs):
+        """Return a generator for Redditors and their associated flair.
+
+        :param redditor: Yield at most a single :class:`~.Redditor`
+            instance.
+
+        """
+        Subreddit._safely_add_arguments(generator_kwargs, 'params',
+                                        name=redditor)
+        url = API_PATH['flairlist'].format(subreddit=self.subreddit)
+        return ListingGenerator(self.subreddit._reddit, url,
+                                **generator_kwargs)
+
     def __init__(self, subreddit):
         """Create a SubredditFlair instance.
 
@@ -514,6 +527,10 @@ def __iter__(self):
            for flair in reddit.subreddit('NAME').flair:
                print(flair)
 
+        .. warning:: (Deprecated) This method will be removed in PRAW 5. Prefer
+                     calling ``subreddit.flair(limit=None)`` instead of
+                     ``subreddit.flair``.
+
         """
         url = API_PATH['flairlist'].format(subreddit=self.subreddit)
         params = {'unique': self.subreddit._reddit._next_unique}


Test patch:
diff --git a/tests/integration/cassettes/TestSubredditFlair.test__call.json b/tests/integration/cassettes/TestSubredditFlair.test__call.json
new file mode 100644
--- /dev/null
+++ b/tests/integration/cassettes/TestSubredditFlair.test__call.json
@@ -0,0 +1,111 @@
+{
+  "http_interactions": [
+    {
+      "recorded_at": "2016-12-21T20:56:52",
+      "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": "57",
+          "Content-Type": "application/x-www-form-urlencoded",
+          "User-Agent": "<USER_AGENT> PRAW/4.1.0dev prawcore/0.5.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": "Wed, 21 Dec 2016 20:56:53 GMT",
+          "Server": "snooserv",
+          "Set-Cookie": "loid=UpeM8b3UNnM6bcIfG0; 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-ord1730-ORD",
+          "X-Timer": "S1482353813.130223,VS0,VE360",
+          "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": "2016-12-21T20:56:53",
+      "request": {
+        "body": {
+          "encoding": "utf-8",
+          "string": ""
+        },
+        "headers": {
+          "Accept": "*/*",
+          "Accept-Encoding": "identity",
+          "Authorization": "bearer <ACCESS_TOKEN>",
+          "Connection": "keep-alive",
+          "Cookie": "loid=UpeM8b3UNnM6bcIfG0; loidcreated=1482353813000",
+          "User-Agent": "<USER_AGENT> PRAW/4.1.0dev prawcore/0.5.0"
+        },
+        "method": "GET",
+        "uri": "https://oauth.reddit.com/r/<TEST_SUBREDDIT>/api/flairlist/?raw_json=1&limit=100"
+      },
+      "response": {
+        "body": {
+          "encoding": "UTF-8",
+          "string": "{\"users\": [{\"flair_css_class\": \"\", \"user\": \"BJO_test_user\", \"flair_text\": \"ayy .:lmao\"}, {\"flair_css_class\": \"\", \"user\": \"BJO_test_mod\", \"flair_text\": \"ayy lmao\"}]}"
+        },
+        "headers": {
+          "Accept-Ranges": "bytes",
+          "Connection": "keep-alive",
+          "Content-Length": "164",
+          "Content-Type": "application/json; charset=UTF-8",
+          "Date": "Wed, 21 Dec 2016 20:56:53 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-ord1744-ORD",
+          "X-Timer": "S1482353813.814401,VS0,VE86",
+          "cache-control": "private, s-maxage=0, max-age=0, must-revalidate, max-age=0, must-revalidate",
+          "expires": "-1",
+          "x-content-type-options": "nosniff",
+          "x-frame-options": "SAMEORIGIN",
+          "x-ratelimit-remaining": "599.0",
+          "x-ratelimit-reset": "187",
+          "x-ratelimit-used": "1",
+          "x-reddit-tracking": "https://pixel.redditmedia.com/pixel/of_destiny.png?v=TJrg%2FJBM12OQL2N8T%2BLeqfk%2BsVeznxx%2BvR3lliXWOLbrrGjShriGEnjEgM9Gova3Hhrn%2FVtka08P51oL4ApeiZ2F5WkC0z%2F4",
+          "x-ua-compatible": "IE=edge",
+          "x-xss-protection": "1; mode=block"
+        },
+        "status": {
+          "code": 200,
+          "message": "OK"
+        },
+        "url": "https://oauth.reddit.com/r/<TEST_SUBREDDIT>/api/flairlist/?raw_json=1&limit=100"
+      }
+    }
+  ],
+  "recorded_with": "betamax/0.8.0"
+}
\ No newline at end of file
diff --git a/tests/integration/cassettes/TestSubredditFlair.test__call_user_filter.json b/tests/integration/cassettes/TestSubredditFlair.test__call_user_filter.json
new file mode 100644
--- /dev/null
+++ b/tests/integration/cassettes/TestSubredditFlair.test__call_user_filter.json
@@ -0,0 +1,111 @@
+{
+  "http_interactions": [
+    {
+      "recorded_at": "2016-12-21T20:56:53",
+      "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": "57",
+          "Content-Type": "application/x-www-form-urlencoded",
+          "User-Agent": "<USER_AGENT> PRAW/4.1.0dev prawcore/0.5.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": "Wed, 21 Dec 2016 20:56:54 GMT",
+          "Server": "snooserv",
+          "Set-Cookie": "loid=STog10VG9uWkRUonkF; 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-ord1738-ORD",
+          "X-Timer": "S1482353814.221933,VS0,VE354",
+          "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": "2016-12-21T20:56:54",
+      "request": {
+        "body": {
+          "encoding": "utf-8",
+          "string": ""
+        },
+        "headers": {
+          "Accept": "*/*",
+          "Accept-Encoding": "identity",
+          "Authorization": "bearer <ACCESS_TOKEN>",
+          "Connection": "keep-alive",
+          "Cookie": "loid=STog10VG9uWkRUonkF; loidcreated=1482353814000",
+          "User-Agent": "<USER_AGENT> PRAW/4.1.0dev prawcore/0.5.0"
+        },
+        "method": "GET",
+        "uri": "https://oauth.reddit.com/r/<TEST_SUBREDDIT>/api/flairlist/?raw_json=1&limit=100&name=<USERNAME>"
+      },
+      "response": {
+        "body": {
+          "encoding": "UTF-8",
+          "string": "{\"users\": [{\"flair_css_class\": \"\", \"user\": \"BJO_test_mod\", \"flair_text\": \"ayy lmao\"}]}"
+        },
+        "headers": {
+          "Accept-Ranges": "bytes",
+          "Connection": "keep-alive",
+          "Content-Length": "86",
+          "Content-Type": "application/json; charset=UTF-8",
+          "Date": "Wed, 21 Dec 2016 20:56:54 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-ord1724-ORD",
+          "X-Timer": "S1482353814.891707,VS0,VE57",
+          "cache-control": "private, s-maxage=0, max-age=0, must-revalidate, max-age=0, must-revalidate",
+          "expires": "-1",
+          "x-content-type-options": "nosniff",
+          "x-frame-options": "SAMEORIGIN",
+          "x-ratelimit-remaining": "598.0",
+          "x-ratelimit-reset": "186",
+          "x-ratelimit-used": "2",
+          "x-reddit-tracking": "https://pixel.redditmedia.com/pixel/of_destiny.png?v=YwtUXAzo5z0FlokqFvmYU5MI5FQkd4MklgSpIUuEc%2FzYK7g00VNQ1o6gWjdQ6SAIZt%2FakN4eEV9%2Fzsi8NEbnWmGTzI6ETurC",
+          "x-ua-compatible": "IE=edge",
+          "x-xss-protection": "1; mode=block"
+        },
+        "status": {
+          "code": 200,
+          "message": "OK"
+        },
+        "url": "https://oauth.reddit.com/r/<TEST_SUBREDDIT>/api/flairlist/?raw_json=1&limit=100&name=<USERNAME>"
+      }
+    }
+  ],
+  "recorded_with": "betamax/0.8.0"
+}
\ No newline at end of file
diff --git a/tests/integration/models/reddit/test_subreddit.py b/tests/integration/models/reddit/test_subreddit.py
--- a/tests/integration/models/reddit/test_subreddit.py
+++ b/tests/integration/models/reddit/test_subreddit.py
@@ -232,10 +232,25 @@ def test_remove__non_special(self, _):
 
 
 class TestSubredditFlair(IntegrationTest):
+    REDDITOR = pytest.placeholders.username
+
     @property
     def subreddit(self):
         return self.reddit.subreddit(pytest.placeholders.test_subreddit)
 
+    def test__call(self):
+        self.reddit.read_only = False
+        with self.recorder.use_cassette('TestSubredditFlair.test__call'):
+            mapping = self.subreddit.flair()
+            assert len(list(mapping)) > 0
+
+    def test__call__user_filter(self):
+        self.reddit.read_only = False
+        with self.recorder.use_cassette(
+                'TestSubredditFlair.test__call_user_filter'):
+            mapping = self.subreddit.flair(redditor=self.REDDITOR)
+            assert len(list(mapping)) == 1
+
     def test__iter(self):
         self.reddit.read_only = False
         with self.recorder.use_cassette('TestSubredditFlair.test__iter'):